blob: f46a7f2ef07bc1659ca4465f6c8776f80b5d14ac [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include "content/web_test/renderer/test_runner.h"
#include <stddef.h>
#include <algorithm>
#include <clocale>
#include <limits>
#include <string_view>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/skia_paint_canvas.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/renderer/render_thread_impl.h"
#include "content/web_test/common/web_test_constants.h"
#include "content/web_test/common/web_test_string_util.h"
#include "content/web_test/renderer/app_banner_service.h"
#include "content/web_test/renderer/blink_test_helpers.h"
#include "content/web_test/renderer/fake_subresource_filter.h"
#include "content/web_test/renderer/spell_check_client.h"
#include "content/web_test/renderer/test_preferences.h"
#include "content/web_test/renderer/web_frame_test_proxy.h"
#include "gin/arguments.h"
#include "gin/array_buffer.h"
#include "gin/dictionary.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
#include "mojo/public/mojom/base/text_direction.mojom-forward.h"
#include "net/base/filename_util.h"
#include "printing/metafile_skia.h"
#include "printing/mojom/print.mojom.h"
#include "printing/page_number.h"
#include "printing/page_range.h"
#include "printing/print_settings.h"
#include "services/network/public/mojom/cors.mojom.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/app_banner/app_banner.mojom.h"
#include "third_party/blink/public/mojom/clipboard/clipboard.mojom.h"
#include "third_party/blink/public/platform/file_path_conversion.h"
#include "third_party/blink/public/platform/web_cache.h"
#include "third_party/blink/public/platform/web_data.h"
#include "third_party/blink/public/platform/web_isolated_world_info.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/test/frame_widget_test_helper.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_array_buffer.h"
#include "third_party/blink/public/web/web_array_buffer_converter.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_document_loader.h"
#include "third_party/blink/public/web/web_element_collection.h"
#include "third_party/blink/public/web/web_frame.h"
#include "third_party/blink/public/web/web_frame_widget.h"
#include "third_party/blink/public/web/web_input_element.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_manifest_manager.h"
#include "third_party/blink/public/web/web_print_params.h"
#include "third_party/blink/public/web/web_render_theme.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_security_policy.h"
#include "third_party/blink/public/web/web_serialized_script_value.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/public/web/web_testing_support.h"
#include "third_party/blink/public/web/web_view.h"
#include "third_party/blink/public/web/web_view_observer.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/test/icc_profiles.h"
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
#include "third_party/blink/public/platform/web_font_render_style.h"
#endif
namespace content {
namespace {
// TODO(https://github.com/web-platform-tests/wpt/issues/40788): According to
// http://web-platform-tests.org/writing-tests/print-reftests.html the default
// page size for print reftests is 5 by 3 inches. Margins are not mentioned, but
// there are tests that expect them to be 0.5in. Firefox also does this. There
// are 96 CSS pixels per inch, so multiply by that.
const int kWPTPrintWidth = 5 * 96;
const int kWPTPrintHeight = 3 * 96;
const int kWPTPrintMargins = 96 / 2;
// A V8 callback with bound arguments, and the ability to pass additional
// arguments at time of calling Run().
using BoundV8Callback =
base::OnceCallback<void(const v8::LocalVector<v8::Value>&)>;
// Returns an empty set of args for running the BoundV8Callback.
v8::LocalVector<v8::Value> NoV8Args(v8::Isolate* isolate) {
return v8::LocalVector<v8::Value>(isolate);
}
// Returns 3 arguments, width, height, and an array of pixel values. Takes a
// v8::Context::Scope just to prove one exists in the caller.
v8::LocalVector<v8::Value> ConvertBitmapToV8(
v8::Isolate* isolate,
const v8::Context::Scope& context_scope,
const SkBitmap& bitmap) {
v8::LocalVector<v8::Value> args(isolate);
// Note that the bitmap size can be 0 if there's no pixels.
args.push_back(v8::Number::New(isolate, bitmap.info().width()));
args.push_back(v8::Number::New(isolate, bitmap.info().height()));
if (bitmap.isNull()) {
// The 3rd argument will be undefined (an empty argument is not valid and
// would crash).
return args;
}
// Always produce pixels in RGBA order, regardless of the platform default.
SkImageInfo info = bitmap.info().makeColorType(kRGBA_8888_SkColorType);
size_t row_bytes = info.minRowBytes();
blink::WebArrayBuffer buffer =
blink::WebArrayBuffer::Create(info.computeByteSize(row_bytes), 1);
bool read = bitmap.readPixels(info, buffer.Data(), row_bytes, 0, 0);
CHECK(read);
args.push_back(blink::WebArrayBufferConverter::ToV8Value(&buffer, isolate));
return args;
}
void ConvertAndSet(gin::Arguments* args, int* set_param) {
v8::Local<v8::Value> value = args->PeekNext();
v8::Maybe<int> result = value->Int32Value(args->GetHolderCreationContext());
if (result.IsNothing()) {
// Skip so the error is thrown for the correct argument as PeekNext doesn't
// update the current argument pointer.
args->Skip();
args->ThrowError();
return;
}
*set_param = result.ToChecked();
}
void ConvertAndSet(gin::Arguments* args, bool* set_param) {
v8::Local<v8::Value> value = args->PeekNext();
*set_param = value->BooleanValue(args->isolate());
}
void ConvertAndSet(gin::Arguments* args, blink::WebString* set_param) {
v8::Local<v8::Value> value = args->PeekNext();
v8::MaybeLocal<v8::String> result =
value->ToString(args->GetHolderCreationContext());
if (result.IsEmpty()) {
// Skip so the error is thrown for the correct argument as PeekNext doesn't
// update the current argument pointer.
args->Skip();
args->ThrowError();
return;
}
*set_param = web_test_string_util::V8StringToWebString(
args->isolate(), result.ToLocalChecked());
}
} // namespace
class TestRunnerBindings : public gin::Wrappable<TestRunnerBindings> {
public:
static gin::WrapperInfo kWrapperInfo;
TestRunnerBindings(const TestRunnerBindings&) = delete;
TestRunnerBindings& operator=(const TestRunnerBindings&) = delete;
static void Install(TestRunner* test_runner,
WebFrameTestProxy* frame,
SpellCheckClient* spell_check,
bool is_wpt_reftest,
bool is_main_test_window);
// Wraps the V8 function in a base::OnceCallback that binds in the given V8
// arguments. The callback will do nothing when Run() if the
// TestRunnerBindings has been destroyed, so it is safe to PostTask(). At the
// time of Run(), further arguments can be passed to the V8 function.
BoundV8Callback WrapV8Callback(v8::Local<v8::Function> v8_callback);
BoundV8Callback WrapV8Callback(v8::Local<v8::Function> v8_callback,
v8::LocalVector<v8::Value> args_to_bind);
// Same as WrapV8Callback but Run() takes no arguments, so only bound
// arguments can be passed to the V8 function.
base::OnceClosure WrapV8Closure(v8::Local<v8::Function> v8_callback);
base::OnceClosure WrapV8Closure(v8::Local<v8::Function> v8_callback,
v8::LocalVector<v8::Value> args_to_bind);
// Calls WrapV8Callback() and then posts the resulting callback to the frame's
// task runner.
void PostV8Callback(v8::Local<v8::Function> v8_callback);
void PostV8Callback(v8::Local<v8::Function> v8_callback,
v8::LocalVector<v8::Value> args);
blink::WebLocalFrame* GetWebFrame() {
CHECK(frame_);
return frame_->GetWebFrame();
}
private:
// Watches for the RenderFrame that the TestRunnerBindings is attached to
// being destroyed.
class TestRunnerBindingsRenderFrameObserver : public RenderFrameObserver {
public:
TestRunnerBindingsRenderFrameObserver(TestRunnerBindings* bindings,
RenderFrame* frame)
: RenderFrameObserver(frame), bindings_(bindings) {}
// RenderFrameObserver implementation.
void OnDestruct() override { bindings_->OnFrameDestroyed(); }
private:
const raw_ptr<TestRunnerBindings> bindings_;
};
explicit TestRunnerBindings(TestRunner* test_runner,
WebFrameTestProxy* frame,
SpellCheckClient* spell_check);
~TestRunnerBindings() override;
// gin::Wrappable overrides.
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override;
void AddOriginAccessAllowListEntry(const std::string& source_origin,
const std::string& destination_protocol,
const std::string& destination_host,
bool allow_destination_subdomains);
void AddWebPageOverlay();
void AllowPointerLock();
void SetHighlightAds();
#if BUILDFLAG(ENABLE_PRINTING)
void CapturePrintingPixelsThen(v8::Local<v8::Function> callback);
#endif
void CheckForLeakedWindows();
void ClearAllDatabases();
void ClearTrustTokenState(v8::Local<v8::Function> callback);
void CopyImageThen(int x, int y, v8::Local<v8::Function> callback);
void DisableMockScreenOrientation();
void DispatchBeforeInstallPromptEvent(
const std::vector<std::string>& event_platforms,
v8::Local<v8::Function> callback);
void DropPointerLock();
void DumpAsMarkup();
void DumpAsText();
void DumpAsTextWithPixelResults();
void DumpAsLayout();
void DumpAsLayoutWithPixelResults();
void DumpChildFrames();
void DumpBackForwardList();
void DumpCreateView();
void DumpDragImage();
void DumpEditingCallbacks();
void DumpFrameLoadCallbacks();
void DumpIconChanges();
void DumpNavigationPolicy();
void DumpPermissionClientCallbacks();
void DumpPingLoaderCallbacks();
void DumpSelectionRect();
void DumpTitleChanges();
void DumpUserGestureInFrameLoadCallbacks();
void EvaluateScriptInIsolatedWorld(int world_id, const std::string& script);
void EvaluateScriptInOwnTask(const std::string& script,
const std::string& source_url,
v8::Local<v8::Function> v8_callback);
void ExecCommand(gin::Arguments* args);
void TriggerTestInspectorIssue(gin::Arguments* args);
void FocusDevtoolsSecondaryWindow();
void ForceNextDrawingBufferCreationToFail();
void ForceNextWebGLContextCreationToFail();
void GetBluetoothManualChooserEvents(v8::Local<v8::Function> callback);
void GetManifestThen(v8::Local<v8::Function> callback);
std::string GetWritableDirectory();
void InsertStyleSheet(const std::string& source_code);
void UpdateAllLifecyclePhasesAndComposite();
void UpdateAllLifecyclePhasesAndCompositeThen(
v8::Local<v8::Function> callback);
void SetAnimationRequiresRaster(bool do_raster);
void LogToStderr(const std::string& output);
void NotImplemented(const gin::Arguments& args);
void NotifyDone();
void OverridePreference(gin::Arguments* args);
void QueueBackNavigation(int how_far_back);
void QueueForwardNavigation(int how_far_forward);
void QueueLoad(gin::Arguments* args);
void QueueLoadingScript(const std::string& script);
void QueueNonLoadingScript(const std::string& script);
void QueueReload();
void RemoveSpellCheckResolvedCallback();
void RemoveWebPageOverlay();
void ResolveBeforeInstallPromptPromise(const std::string& platform);
void SendBluetoothManualChooserEvent(const std::string& event,
const std::string& argument);
void SetAcceptLanguages(const std::string& accept_languages);
void SetAllowFileAccessFromFileURLs(bool allow);
void SetAllowRunningOfInsecureContent(bool allowed);
void SetBlockThirdPartyCookies(bool block);
void SetAudioData(const gin::ArrayBufferView& view);
void SetBackingScaleFactor(double value, v8::Local<v8::Function> callback);
void SetBluetoothFakeAdapter(const std::string& adapter_name,
v8::Local<v8::Function> callback);
void SetBluetoothManualChooser(bool enable);
void SetBrowserHandlesFocus(bool enable);
void SetCaretBrowsingEnabled();
void SetColorProfile(const std::string& name,
v8::Local<v8::Function> callback);
void SetCustomPolicyDelegate(gin::Arguments* args);
void SetCustomTextOutput(const std::string& output);
void SetDatabaseQuota(int quota);
void SetDisallowedSubresourcePathSuffixes(std::vector<std::string> suffixes,
bool block_subresources);
void SetDomainRelaxationForbiddenForURLScheme(bool forbidden,
const std::string& scheme);
void SetDumpConsoleMessages(bool value);
void SetDumpJavaScriptDialogs(bool value);
void SetEffectiveConnectionType(const std::string& connection_type);
void SetFilePathForMockFileDialog(const std::string& path);
void SetMockSpellCheckerEnabled(bool enabled);
void SetIsolatedWorldInfo(int world_id,
v8::Local<v8::Value> security_origin,
v8::Local<v8::Value> content_security_policy);
void SetJavaScriptCanAccessClipboard(bool can_access);
void SetMockScreenOrientation(const std::string& orientation);
void SetPOSIXLocale(const std::string& locale);
void SetMainWindowHidden(bool hidden);
void SetFrameWindowHidden(bool hidden);
void SetWindowRect(const gin::Dictionary& rect);
void SetPermission(const std::string& name,
const std::string& value,
const std::string& origin,
const std::string& embedding_origin);
void SetPluginsAllowed(bool allowed);
void SetPluginsEnabled(bool enabled);
void SetPointerLockWillFail();
void SetPointerLockWillRespondAsynchronously();
void SetPopupBlockingEnabled(bool block_popups);
void SetPrinting();
void SetPrintingForFrame(const std::string& frame_name);
void SetPrintingSize(int width, int height);
void SetPrintingMargin(int);
void SetShouldCenterAndShrinkToFitPaper(bool);
void SetPrintingScaleFactor(float);
void SetShouldGeneratePixelResults(bool);
void SetShouldStayOnPageAfterHandlingBeforeUnload(bool value);
void SetSpellCheckResolvedCallback(v8::Local<v8::Function> callback);
void SetStorageAllowed(bool allowed);
void SetTabKeyCyclesThroughElements(bool tab_key_cycles_through_elements);
void SetTextDirection(const std::string& direction_name);
void SetTextSubpixelPositioning(bool value);
void SetTrustTokenKeyCommitments(const std::string& raw_commitments,
v8::Local<v8::Function> callback);
void SetWillSendRequestClearHeader(const std::string& header);
void SetWillSendRequestClearReferrer();
void SetRphRegistrationMode(gin::Arguments* args);
void SimulateBrowserWindowFocus(bool value);
void NavigateSecondaryWindow(const std::string& url);
void InspectSecondaryWindow();
void SimulateWebNotificationClick(gin::Arguments* args);
void SimulateWebNotificationClose(const std::string& title, bool by_user);
void SimulateWebContentIndexDelete(const std::string& id);
void WaitForPolicyDelegate();
void WaitUntilDone();
void WaitUntilExternalURLLoad();
void DisableAutoResizeMode(int new_width, int new_height);
void EnableAutoResizeMode(int min_width,
int min_height,
int max_width,
int max_height);
void DisableAutomaticDragDrop();
void GoToOffset(int offset);
v8::Local<v8::Value> EvaluateScriptInIsolatedWorldAndReturnValue(
int world_id,
const std::string& script);
bool FindString(const std::string& search_text,
const std::vector<std::string>& options_array);
bool IsCommandEnabled(const std::string& command);
std::string PathToLocalResource(const std::string& path);
std::string PlatformName();
std::string SelectionAsMarkup();
void TextZoomIn();
void TextZoomOut();
void ZoomPageIn();
void ZoomPageOut();
void SetPageZoomFactor(double factor);
std::string TooltipText();
int WebHistoryItemCount();
int WindowCount();
void InvokeV8Callback(v8::UniquePersistent<v8::Function> callback,
std::vector<v8::UniquePersistent<v8::Value>> bound_args,
const v8::LocalVector<v8::Value>& runtime_args);
// Hears about the RenderFrame in |frame_| being destroyed. The
// TestRunningBindings should not do anything thereafter.
void OnFrameDestroyed() { frame_ = nullptr; }
// Observer for the |frame_| the TestRunningBindings is bound to.
TestRunnerBindingsRenderFrameObserver frame_observer_;
raw_ptr<TestRunner, DanglingUntriaged> runner_;
raw_ptr<WebFrameTestProxy, DanglingUntriaged> frame_;
const raw_ptr<SpellCheckClient, DanglingUntriaged> spell_check_;
TestPreferences prefs_;
std::unique_ptr<AppBannerService> app_banner_service_;
base::WeakPtrFactory<TestRunnerBindings> weak_ptr_factory_{this};
};
gin::WrapperInfo TestRunnerBindings::kWrapperInfo = {gin::kEmbedderNativeGin};
// static
void TestRunnerBindings::Install(TestRunner* test_runner,
WebFrameTestProxy* frame,
SpellCheckClient* spell_check,
bool is_wpt_test,
bool is_main_test_window) {
blink::WebLocalFrame* web_frame = frame->GetWebFrame();
v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
CHECK(!context.IsEmpty());
v8::Context::Scope context_scope(context);
TestRunnerBindings* wrapped =
new TestRunnerBindings(test_runner, frame, spell_check);
gin::Handle<TestRunnerBindings> bindings =
gin::CreateHandle(isolate, wrapped);
CHECK(!bindings.IsEmpty());
v8::Local<v8::Object> global = context->Global();
v8::Local<v8::Value> v8_bindings = bindings.ToV8();
global->Set(context, gin::StringToV8(isolate, "testRunner"), v8_bindings)
.Check();
// Inject some JavaScript to the top-level frame of a reftest in the
// web-platform-tests suite to have the same reftest screenshot timing as
// upstream WPT:
//
// 1. For normal reftest, we would like to take screenshots after web fonts
// are loaded, i.e. replicate the behavior of this injected script:
// https://github.com/web-platform-tests/wpt/blob/master/tools/wptrunner/wptrunner/executors/reftest-wait_webdriver.js
// 2. For reftests with a 'reftest-wait' or crash tests with a 'test-wait'
// class on the root element, reference comparison is delayed (and a
// TestRendered event emitted in its place) until that class attribute is
// removed. To support this feature, we use a mutation observer.
// https://web-platform-tests.org/writing-tests/reftests.html#controlling-when-comparison-occurs
// https://web-platform-tests.org/writing-tests/crashtest.html
//
// Note that this method may be called multiple times on a frame, so we put
// the code behind a flag. The flag is safe to be installed on testRunner
// because WPT reftests never access this object.
if (is_wpt_test && is_main_test_window && !web_frame->Parent() &&
!web_frame->Opener()) {
web_frame->ExecuteScript(blink::WebScriptSource(blink::WebString(
R"(if (!window.testRunner._wpt_reftest_setup) {
window.testRunner._wpt_reftest_setup = true;
function observeRefTestFinished() {
if (window.assert_equals) // In case of a testharness test.
return;
window.testRunner.waitUntilDone();
const target = document.documentElement;
if (target != null &&
(target.classList.contains('reftest-wait') ||
target.classList.contains('test-wait'))) {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (!target.classList.contains('reftest-wait') &&
!target.classList.contains('test-wait')) {
// This is the same as https://github.com/web-platform-tests/wpt/blob/master/tools/wptrunner/wptrunner/executors/test-wait.js
requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.testRunner.notifyDone();
});
});
}
});
});
const config = {attributes: true};
observer.observe(target, config);
var event = new Event('TestRendered', {bubbles: true});
target.dispatchEvent(event);
} else {
document.fonts.ready.then(() => window.testRunner.notifyDone());
}
};
window.addEventListener('load', () => {
if (document.prerendering) {
document.addEventListener('prerenderingchange',
observeRefTestFinished);
} else {
observeRefTestFinished();
}
});
})")));
}
}
TestRunnerBindings::TestRunnerBindings(TestRunner* runner,
WebFrameTestProxy* frame,
SpellCheckClient* spell_check)
: frame_observer_(this, frame),
runner_(runner),
frame_(frame),
spell_check_(spell_check) {}
TestRunnerBindings::~TestRunnerBindings() = default;
gin::ObjectTemplateBuilder TestRunnerBindings::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return gin::Wrappable<TestRunnerBindings>::GetObjectTemplateBuilder(isolate)
.SetMethod("abortModal", &TestRunnerBindings::NotImplemented)
.SetMethod("addDisallowedURL", &TestRunnerBindings::NotImplemented)
.SetMethod("addOriginAccessAllowListEntry",
&TestRunnerBindings::AddOriginAccessAllowListEntry)
// Permits the adding of only one opaque overlay. May only be called from
// inside the main frame.
.SetMethod("addWebPageOverlay", &TestRunnerBindings::AddWebPageOverlay)
#if BUILDFLAG(ENABLE_PRINTING)
.SetMethod("capturePrintingPixelsThen",
&TestRunnerBindings::CapturePrintingPixelsThen)
#endif
// If the test will be closing its windows explicitly, and wants to look
// for leaks due to those windows closing incorrectly, it can specify this
// to avoid having them closed at the end of the test before the leak
// checker.
.SetMethod("checkForLeakedWindows",
&TestRunnerBindings::CheckForLeakedWindows)
// Clears WebSQL databases.
.SetMethod("clearAllDatabases", &TestRunnerBindings::ClearAllDatabases)
.SetMethod("clearBackForwardList", &TestRunnerBindings::NotImplemented)
// Clears persistent Trust Tokens state in the browser. See
// https://github.com/wicg/trust-token-api.
.SetMethod("clearTrustTokenState",
&TestRunnerBindings::ClearTrustTokenState)
.SetMethod("copyImageThen", &TestRunnerBindings::CopyImageThen)
// While holding a pointer lock, this breaks the lock. Or if
// setPointerLockWillRespondAsynchronously() was called, and a lock is
// pending it rejects the lock request.
.SetMethod("dropPointerLock", &TestRunnerBindings::DropPointerLock)
// When setPointerLockWillRespondAsynchronously() was called, this is used
// to respond to the async pointer request.
.SetMethod("allowPointerLock", &TestRunnerBindings::AllowPointerLock)
// Causes the next pointer lock request to fail in the renderer.
.SetMethod("setPointerLockWillFail",
&TestRunnerBindings::SetPointerLockWillFail)
// Causes the next pointer lock request to delay until the test calls
// either allowPointerLock() or dropPointerLock().
.SetMethod("setPointerLockWillRespondAsynchronously",
&TestRunnerBindings::SetPointerLockWillRespondAsynchronously)
.SetMethod("disableAutoResizeMode",
&TestRunnerBindings::DisableAutoResizeMode)
.SetMethod("disableMockScreenOrientation",
&TestRunnerBindings::DisableMockScreenOrientation)
// Sets up a mock DocumentSubresourceFilter to disallow subsequent
// subresource loads within the current document with the given path
// |suffixes|. The filter is created and injected even if |suffixes| is
// empty. If |suffixes| contains the empty string, all subresource loads
// will be disallowed. If |block_subresources| is false, matching
// resources will not be blocked but instead marked as matching a
// disallowed resource.
.SetMethod("setDisallowedSubresourcePathSuffixes",
&TestRunnerBindings::SetDisallowedSubresourcePathSuffixes)
// Causes the beforeinstallprompt event to be sent to the renderer.
.SetMethod("dispatchBeforeInstallPromptEvent",
&TestRunnerBindings::DispatchBeforeInstallPromptEvent)
.SetMethod("dumpAsMarkup", &TestRunnerBindings::DumpAsMarkup)
.SetMethod("dumpAsText", &TestRunnerBindings::DumpAsText)
.SetMethod("dumpAsTextWithPixelResults",
&TestRunnerBindings::DumpAsTextWithPixelResults)
.SetMethod("dumpAsLayout", &TestRunnerBindings::DumpAsLayout)
.SetMethod("dumpAsLayoutWithPixelResults",
&TestRunnerBindings::DumpAsLayoutWithPixelResults)
.SetMethod("dumpBackForwardList",
&TestRunnerBindings::DumpBackForwardList)
.SetMethod("dumpChildFrames", &TestRunnerBindings::DumpChildFrames)
.SetMethod("dumpDatabaseCallbacks", &TestRunnerBindings::NotImplemented)
.SetMethod("dumpDragImage", &TestRunnerBindings::DumpDragImage)
.SetMethod("dumpEditingCallbacks",
&TestRunnerBindings::DumpEditingCallbacks)
.SetMethod("dumpFrameLoadCallbacks",
&TestRunnerBindings::DumpFrameLoadCallbacks)
.SetMethod("dumpIconChanges", &TestRunnerBindings::DumpIconChanges)
.SetMethod("dumpNavigationPolicy",
&TestRunnerBindings::DumpNavigationPolicy)
.SetMethod("dumpPermissionClientCallbacks",
&TestRunnerBindings::DumpPermissionClientCallbacks)
.SetMethod("dumpPingLoaderCallbacks",
&TestRunnerBindings::DumpPingLoaderCallbacks)
.SetMethod("dumpSelectionRect", &TestRunnerBindings::DumpSelectionRect)
.SetMethod("dumpTitleChanges", &TestRunnerBindings::DumpTitleChanges)
.SetMethod("dumpUserGestureInFrameLoadCallbacks",
&TestRunnerBindings::DumpUserGestureInFrameLoadCallbacks)
.SetMethod("enableAutoResizeMode",
&TestRunnerBindings::EnableAutoResizeMode)
.SetMethod("evaluateScriptInIsolatedWorld",
&TestRunnerBindings::EvaluateScriptInIsolatedWorld)
.SetMethod("evaluateScriptInOwnTask",
&TestRunnerBindings::EvaluateScriptInOwnTask)
.SetMethod(
"evaluateScriptInIsolatedWorldAndReturnValue",
&TestRunnerBindings::EvaluateScriptInIsolatedWorldAndReturnValue)
// Executes an internal command (superset of document.execCommand()
// commands) on the frame's document.
.SetMethod("execCommand", &TestRunnerBindings::ExecCommand)
// Trigger an inspector issue for the frame.
.SetMethod("triggerTestInspectorIssue",
&TestRunnerBindings::TriggerTestInspectorIssue)
.SetMethod("findString", &TestRunnerBindings::FindString)
// Moves focus and active state to the secondary devtools window, which
// exists only in devtools JS tests.
.SetMethod("focusDevtoolsSecondaryWindow",
&TestRunnerBindings::FocusDevtoolsSecondaryWindow)
// Sets a flag causing the next call to WebGLRenderingContext::Create() to
// fail.
.SetMethod("forceNextDrawingBufferCreationToFail",
&TestRunnerBindings::ForceNextDrawingBufferCreationToFail)
// Sets a flag causing the next call to DrawingBuffer::Create() to fail.
.SetMethod("forceNextWebGLContextCreationToFail",
&TestRunnerBindings::ForceNextWebGLContextCreationToFail)
// The Bluetooth functions are specified at
// https://webbluetoothcg.github.io/web-bluetooth/tests/.
//
// Returns the events recorded since the last call to this function.
.SetMethod("getBluetoothManualChooserEvents",
&TestRunnerBindings::GetBluetoothManualChooserEvents)
.SetMethod("getManifestThen", &TestRunnerBindings::GetManifestThen)
// Returns the absolute path to a directory this test can write data in.
// This returns the path to a fresh empty directory every time this method
// is called. Additionally when this method is called any previously
// created directories will be deleted.
.SetMethod("getWritableDirectory",
&TestRunnerBindings::GetWritableDirectory)
.SetMethod("insertStyleSheet", &TestRunnerBindings::InsertStyleSheet)
// Checks if an internal editing command is currently available for the
// frame's document.
.SetMethod("isCommandEnabled", &TestRunnerBindings::IsCommandEnabled)
.SetMethod("keepWebHistory", &TestRunnerBindings::NotImplemented)
.SetMethod("updateAllLifecyclePhasesAndComposite",
&TestRunnerBindings::UpdateAllLifecyclePhasesAndComposite)
// Note, the reply callback is executed synchronously. Wrap in
// setTimeout() to run asynchronously.
.SetMethod("updateAllLifecyclePhasesAndCompositeThen",
&TestRunnerBindings::UpdateAllLifecyclePhasesAndCompositeThen)
.SetMethod("setAnimationRequiresRaster",
&TestRunnerBindings::SetAnimationRequiresRaster)
.SetMethod("logToStderr", &TestRunnerBindings::LogToStderr)
.SetMethod("notifyDone", &TestRunnerBindings::NotifyDone)
.SetMethod("overridePreference", &TestRunnerBindings::OverridePreference)
.SetMethod("pathToLocalResource",
&TestRunnerBindings::PathToLocalResource)
.SetProperty("platformName", &TestRunnerBindings::PlatformName)
.SetMethod("queueBackNavigation",
&TestRunnerBindings::QueueBackNavigation)
.SetMethod("queueForwardNavigation",
&TestRunnerBindings::QueueForwardNavigation)
.SetMethod("queueLoad", &TestRunnerBindings::QueueLoad)
.SetMethod("queueLoadingScript", &TestRunnerBindings::QueueLoadingScript)
.SetMethod("queueNonLoadingScript",
&TestRunnerBindings::QueueNonLoadingScript)
.SetMethod("queueReload", &TestRunnerBindings::QueueReload)
.SetMethod("removeSpellCheckResolvedCallback",
&TestRunnerBindings::RemoveSpellCheckResolvedCallback)
// Removes an overlay added by addWebPageOverlay(). May only be called
// from inside the main frame.
.SetMethod("removeWebPageOverlay",
&TestRunnerBindings::RemoveWebPageOverlay)
.SetMethod("resolveBeforeInstallPromptPromise",
&TestRunnerBindings::ResolveBeforeInstallPromptPromise)
.SetMethod("selectionAsMarkup", &TestRunnerBindings::SelectionAsMarkup)
// The Bluetooth functions are specified at
// https://webbluetoothcg.github.io/web-bluetooth/tests/.
// Calls the BluetoothChooser::EventHandler with the arguments here. Valid
// event strings are:
// * "cancel" - simulates the user canceling the chooser.
// * "select" - simulates the user selecting a device whose device ID is
// in the 2nd parameter.
.SetMethod("sendBluetoothManualChooserEvent",
&TestRunnerBindings::SendBluetoothManualChooserEvent)
.SetMethod("setAcceptLanguages", &TestRunnerBindings::SetAcceptLanguages)
.SetMethod("setAllowFileAccessFromFileURLs",
&TestRunnerBindings::SetAllowFileAccessFromFileURLs)
.SetMethod("setAllowRunningOfInsecureContent",
&TestRunnerBindings::SetAllowRunningOfInsecureContent)
// Controls whether all cookies should be accepted or writing cookies in a
// third-party context is blocked:
// - Allows all cookies when |block| is false
// - Blocks only third-party cookies when |block| is true
.SetMethod("setBlockThirdPartyCookies",
&TestRunnerBindings::SetBlockThirdPartyCookies)
.SetMethod("setAudioData", &TestRunnerBindings::SetAudioData)
.SetMethod("setBackingScaleFactor",
&TestRunnerBindings::SetBackingScaleFactor)
// Set the bluetooth adapter while running a web test.
.SetMethod("setBluetoothFakeAdapter",
&TestRunnerBindings::SetBluetoothFakeAdapter)
// If |enable| is true, makes the Bluetooth chooser record its input and
// wait for instructions from the test program on how to proceed.
// Otherwise falls back to the browser's default chooser.
.SetMethod("setBluetoothManualChooser",
&TestRunnerBindings::SetBluetoothManualChooser)
.SetMethod("setBrowserHandlesFocus",
&TestRunnerBindings::SetBrowserHandlesFocus)
.SetMethod("setCallCloseOnWebViews", &TestRunnerBindings::NotImplemented)
.SetMethod("setCaretBrowsingEnabled",
&TestRunnerBindings::SetCaretBrowsingEnabled)
.SetMethod("setColorProfile", &TestRunnerBindings::SetColorProfile)
.SetMethod("setCustomPolicyDelegate",
&TestRunnerBindings::SetCustomPolicyDelegate)
.SetMethod("setCustomTextOutput",
&TestRunnerBindings::SetCustomTextOutput)
// Setting quota to kDefaultDatabaseQuota will reset it to the default
// value.
.SetMethod("setDatabaseQuota", &TestRunnerBindings::SetDatabaseQuota)
.SetMethod("setDomainRelaxationForbiddenForURLScheme",
&TestRunnerBindings::SetDomainRelaxationForbiddenForURLScheme)
.SetMethod("setDumpConsoleMessages",
&TestRunnerBindings::SetDumpConsoleMessages)
.SetMethod("setDumpJavaScriptDialogs",
&TestRunnerBindings::SetDumpJavaScriptDialogs)
.SetMethod("setEffectiveConnectionType",
&TestRunnerBindings::SetEffectiveConnectionType)
// Sets the path that should be returned when the test shows a file
// dialog.
.SetMethod("setFilePathForMockFileDialog",
&TestRunnerBindings::SetFilePathForMockFileDialog)
.SetMethod("setHighlightAds", &TestRunnerBindings::SetHighlightAds)
.SetMethod("setMockSpellCheckerEnabled",
&TestRunnerBindings::SetMockSpellCheckerEnabled)
.SetMethod("setIconDatabaseEnabled", &TestRunnerBindings::NotImplemented)
.SetMethod("setIsolatedWorldInfo",
&TestRunnerBindings::SetIsolatedWorldInfo)
.SetMethod("setJavaScriptCanAccessClipboard",
&TestRunnerBindings::SetJavaScriptCanAccessClipboard)
.SetMethod("setMainFrameIsFirstResponder",
&TestRunnerBindings::NotImplemented)
.SetMethod("setMockScreenOrientation",
&TestRunnerBindings::SetMockScreenOrientation)
// Calls setlocale(LC_ALL, ...) for a specified locale.
.SetMethod("setPOSIXLocale", &TestRunnerBindings::SetPOSIXLocale)
// Hide or show the main window. Watch for the |document.visibilityState|
// on the primary window's Document to change in order to wait for the
// side effects of calling this.
.SetMethod("setMainWindowHidden",
&TestRunnerBindings::SetMainWindowHidden)
// Hide or show the window displaying this frame. Watch for the
// |document.visibilityState| to change in order to wait for the side
// effects of calling this.
.SetMethod("setFrameWindowHidden",
&TestRunnerBindings::SetFrameWindowHidden)
.SetMethod("setWindowRect", &TestRunnerBindings::SetWindowRect)
// Sets the permission's |name| to |value| for a given {origin, embedder}
// tuple. Sends a message to the WebTestPermissionManager in order for it
// to update its database.
.SetMethod("setPermission", &TestRunnerBindings::SetPermission)
.SetMethod("setPluginsAllowed", &TestRunnerBindings::SetPluginsAllowed)
.SetMethod("setPluginsEnabled", &TestRunnerBindings::SetPluginsEnabled)
.SetMethod("setPopupBlockingEnabled",
&TestRunnerBindings::SetPopupBlockingEnabled)
.SetMethod("setPrinting", &TestRunnerBindings::SetPrinting)
.SetMethod("setPrintingForFrame",
&TestRunnerBindings::SetPrintingForFrame)
.SetMethod("setPrintingSize", &TestRunnerBindings::SetPrintingSize)
.SetMethod("setPrintingMargin", &TestRunnerBindings::SetPrintingMargin)
.SetMethod("setShouldCenterAndShrinkToFitPaper",
&TestRunnerBindings::SetShouldCenterAndShrinkToFitPaper)
.SetMethod("setPrintingScaleFactor",
&TestRunnerBindings::SetPrintingScaleFactor)
.SetMethod("setRphRegistrationMode",
&TestRunnerBindings::SetRphRegistrationMode)
.SetMethod("setScrollbarPolicy", &TestRunnerBindings::NotImplemented)
.SetMethod("setShouldGeneratePixelResults",
&TestRunnerBindings::SetShouldGeneratePixelResults)
.SetMethod(
"setShouldStayOnPageAfterHandlingBeforeUnload",
&TestRunnerBindings::SetShouldStayOnPageAfterHandlingBeforeUnload)
.SetMethod("setSpellCheckResolvedCallback",
&TestRunnerBindings::SetSpellCheckResolvedCallback)
.SetMethod("setStorageAllowed", &TestRunnerBindings::SetStorageAllowed)
// Method that controls whether pressing Tab key cycles through page
// elements or inserts a '\t' char in text area
.SetMethod("setTabKeyCyclesThroughElements",
&TestRunnerBindings::SetTabKeyCyclesThroughElements)
// Changes the direction of text for the frame's focused element.
.SetMethod("setTextDirection", &TestRunnerBindings::SetTextDirection)
.SetMethod("setTextSubpixelPositioning",
&TestRunnerBindings::SetTextSubpixelPositioning)
// Sets the network service-global Trust Tokens key commitments.
// Takes a |raw_commitments| string that should be JSON-encoded according
// to the format expected by NetworkService::SetTrustTokenKeyCommitments.
.SetMethod("setTrustTokenKeyCommitments",
&TestRunnerBindings::SetTrustTokenKeyCommitments)
.SetMethod("setUseDashboardCompatibilityMode",
&TestRunnerBindings::NotImplemented)
.SetMethod("setWillSendRequestClearHeader",
&TestRunnerBindings::SetWillSendRequestClearHeader)
.SetMethod("setWillSendRequestClearReferrer",
&TestRunnerBindings::SetWillSendRequestClearReferrer)
.SetMethod("setWindowFocus",
&TestRunnerBindings::SimulateBrowserWindowFocus)
// Simulates a click on a Web Notification.
.SetMethod("simulateWebNotificationClick",
&TestRunnerBindings::SimulateWebNotificationClick)
// Simulates closing a Web Notification.
.SetMethod("simulateWebNotificationClose",
&TestRunnerBindings::SimulateWebNotificationClose)
// Simulates a user deleting a content index entry.
.SetMethod("simulateWebContentIndexDelete",
&TestRunnerBindings::SimulateWebContentIndexDelete)
.SetMethod("textZoomIn", &TestRunnerBindings::TextZoomIn)
.SetMethod("textZoomOut", &TestRunnerBindings::TextZoomOut)
.SetMethod("zoomPageIn", &TestRunnerBindings::ZoomPageIn)
.SetMethod("zoomPageOut", &TestRunnerBindings::ZoomPageOut)
.SetMethod("setPageZoomFactor", &TestRunnerBindings::SetPageZoomFactor)
.SetProperty("tooltipText", &TestRunnerBindings::TooltipText)
.SetMethod("waitForPolicyDelegate",
&TestRunnerBindings::WaitForPolicyDelegate)
.SetMethod("waitUntilDone", &TestRunnerBindings::WaitUntilDone)
.SetMethod("waitUntilExternalURLLoad",
&TestRunnerBindings::WaitUntilExternalURLLoad)
// webHistoryItemCount is used by tests in web_tests\http\tests\history
.SetProperty("webHistoryItemCount",
&TestRunnerBindings::WebHistoryItemCount)
.SetMethod("windowCount", &TestRunnerBindings::WindowCount)
.SetMethod("disableAutomaticDragDrop",
&TestRunnerBindings::DisableAutomaticDragDrop)
.SetMethod("goToOffset", &TestRunnerBindings::GoToOffset);
}
BoundV8Callback TestRunnerBindings::WrapV8Callback(
v8::Local<v8::Function> v8_callback) {
v8::Isolate* isolate = GetWebFrame()->GetAgentGroupScheduler()->Isolate();
return WrapV8Callback(v8_callback, NoV8Args(isolate));
}
BoundV8Callback TestRunnerBindings::WrapV8Callback(
v8::Local<v8::Function> v8_callback,
v8::LocalVector<v8::Value> args_to_bind) {
v8::Isolate* isolate = GetWebFrame()->GetAgentGroupScheduler()->Isolate();
auto persistent_callback =
v8::UniquePersistent<v8::Function>(isolate, std::move(v8_callback));
std::vector<v8::UniquePersistent<v8::Value>> persistent_args;
persistent_args.reserve(args_to_bind.size());
for (auto& arg : args_to_bind)
persistent_args.emplace_back(isolate, std::move(arg));
return base::BindOnce(
&TestRunnerBindings::InvokeV8Callback, weak_ptr_factory_.GetWeakPtr(),
std::move(persistent_callback), std::move(persistent_args));
}
base::OnceClosure TestRunnerBindings::WrapV8Closure(
v8::Local<v8::Function> v8_callback) {
v8::Isolate* isolate = GetWebFrame()->GetAgentGroupScheduler()->Isolate();
return WrapV8Closure(v8_callback, NoV8Args(isolate));
}
base::OnceClosure TestRunnerBindings::WrapV8Closure(
v8::Local<v8::Function> v8_callback,
v8::LocalVector<v8::Value> args_to_bind) {
v8::Isolate* isolate = GetWebFrame()->GetAgentGroupScheduler()->Isolate();
return base::BindOnce(
WrapV8Callback(std::move(v8_callback), std::move(args_to_bind)),
NoV8Args(isolate));
}
void TestRunnerBindings::PostV8Callback(v8::Local<v8::Function> v8_callback) {
v8::Isolate* isolate = GetWebFrame()->GetAgentGroupScheduler()->Isolate();
return PostV8Callback(v8_callback, NoV8Args(isolate));
}
void TestRunnerBindings::PostV8Callback(v8::Local<v8::Function> v8_callback,
v8::LocalVector<v8::Value> args) {
if (!frame_) {
return;
}
const auto& task_runner =
GetWebFrame()->GetTaskRunner(blink::TaskType::kInternalTest);
task_runner->PostTask(FROM_HERE,
WrapV8Closure(std::move(v8_callback), std::move(args)));
}
void TestRunnerBindings::InvokeV8Callback(
v8::UniquePersistent<v8::Function> callback,
std::vector<v8::UniquePersistent<v8::Value>> bound_args,
const v8::LocalVector<v8::Value>& runtime_args) {
if (!frame_) {
return;
}
blink::WebLocalFrame* web_frame = GetWebFrame();
v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
CHECK(!context.IsEmpty());
v8::Context::Scope context_scope(context);
v8::LocalVector<v8::Value> local_args(isolate);
for (auto& arg : bound_args)
local_args.push_back(v8::Local<v8::Value>::New(isolate, std::move(arg)));
for (const auto& arg : runtime_args)
local_args.push_back(arg);
web_frame->CallFunctionEvenIfScriptDisabled(
v8::Local<v8::Function>::New(isolate, std::move(callback)),
context->Global(), local_args.size(), local_args.data());
}
void TestRunnerBindings::LogToStderr(const std::string& output) {
if (!frame_) {
return;
}
TRACE_EVENT1("shell", "TestRunner::LogToStderr", "output", output);
LOG(ERROR) << output;
}
void TestRunnerBindings::NotifyDone() {
if (!frame_) {
return;
}
runner_->NotifyDone(*frame_);
}
void TestRunnerBindings::WaitUntilDone() {
if (!frame_) {
return;
}
runner_->WaitUntilDone(*frame_);
}
void TestRunnerBindings::QueueBackNavigation(int how_far_back) {
if (!frame_) {
return;
}
runner_->QueueBackNavigation(how_far_back, *frame_);
}
void TestRunnerBindings::QueueForwardNavigation(int how_far_forward) {
if (!frame_) {
return;
}
runner_->QueueForwardNavigation(how_far_forward, *frame_);
}
void TestRunnerBindings::QueueReload() {
if (!frame_) {
return;
}
runner_->QueueReload(*frame_);
}
void TestRunnerBindings::QueueLoadingScript(const std::string& script) {
if (!frame_) {
return;
}
runner_->QueueLoadingScript(script, *frame_);
}
void TestRunnerBindings::QueueNonLoadingScript(const std::string& script) {
if (!frame_) {
return;
}
runner_->QueueNonLoadingScript(script, *frame_);
}
void TestRunnerBindings::QueueLoad(gin::Arguments* args) {
if (!frame_) {
return;
}
std::string url;
std::string target;
args->GetNext(&url);
args->GetNext(&target);
runner_->QueueLoad(GURL(GetWebFrame()->GetDocument().Url()), url, target,
*frame_);
}
void TestRunnerBindings::SetCustomPolicyDelegate(gin::Arguments* args) {
if (!frame_) {
return;
}
runner_->SetCustomPolicyDelegate(args, *frame_);
}
void TestRunnerBindings::WaitForPolicyDelegate() {
if (!frame_) {
return;
}
runner_->WaitForPolicyDelegate(*frame_);
}
int TestRunnerBindings::WindowCount() {
if (!frame_) {
return 0;
}
return runner_->InProcessWindowCount();
}
void TestRunnerBindings::SetTabKeyCyclesThroughElements(
bool tab_key_cycles_through_elements) {
if (!frame_) {
return;
}
blink::WebView* web_view = GetWebFrame()->View();
web_view->SetTabKeyCyclesThroughElements(tab_key_cycles_through_elements);
}
void TestRunnerBindings::ExecCommand(gin::Arguments* args) {
if (!frame_) {
return;
}
std::string command;
args->GetNext(&command);
std::string value;
if (args->Length() >= 3) {
// Ignore the second parameter (which is userInterface)
// since this command emulates a manual action.
args->Skip();
args->GetNext(&value);
}
// Note: webkit's version does not return the boolean, so neither do we.
GetWebFrame()->ExecuteCommand(blink::WebString::FromUTF8(command),
blink::WebString::FromUTF8(value));
}
void TestRunnerBindings::TriggerTestInspectorIssue(gin::Arguments* args) {
if (!frame_) {
return;
}
GetWebFrame()->AddInspectorIssue(
blink::mojom::InspectorIssueCode::kCookieIssue);
}
bool TestRunnerBindings::IsCommandEnabled(const std::string& command) {
if (!frame_) {
return false;
}
return GetWebFrame()->IsCommandEnabled(blink::WebString::FromUTF8(command));
}
void TestRunnerBindings::SetDomainRelaxationForbiddenForURLScheme(
bool forbidden,
const std::string& scheme) {
if (!frame_) {
return;
}
blink::SetDomainRelaxationForbiddenForTest(
forbidden, blink::WebString::FromUTF8(scheme));
}
void TestRunnerBindings::SetDumpConsoleMessages(bool enabled) {
if (!frame_) {
return;
}
runner_->SetDumpConsoleMessages(enabled, *frame_);
}
void TestRunnerBindings::SetDumpJavaScriptDialogs(bool enabled) {
if (!frame_) {
return;
}
runner_->SetDumpJavaScriptDialogs(enabled, *frame_);
}
void TestRunnerBindings::SetEffectiveConnectionType(
const std::string& connection_type) {
if (!frame_) {
return;
}
blink::WebEffectiveConnectionType web_type =
blink::WebEffectiveConnectionType::kTypeUnknown;
if (connection_type == "TypeUnknown")
web_type = blink::WebEffectiveConnectionType::kTypeUnknown;
else if (connection_type == "TypeOffline")
web_type = blink::WebEffectiveConnectionType::kTypeOffline;
else if (connection_type == "TypeSlow2G")
web_type = blink::WebEffectiveConnectionType::kTypeSlow2G;
else if (connection_type == "Type2G")
web_type = blink::WebEffectiveConnectionType::kType2G;
else if (connection_type == "Type3G")
web_type = blink::WebEffectiveConnectionType::kType3G;
else if (connection_type == "Type4G")
web_type = blink::WebEffectiveConnectionType::kType4G;
else
NOTREACHED_IN_MIGRATION();
if (runner_)
runner_->SetEffectiveConnectionType(web_type);
}
std::string TestRunnerBindings::GetWritableDirectory() {
if (!frame_) {
return {};
}
base::FilePath result;
frame_->GetWebTestControlHostRemote()->GetWritableDirectory(&result);
return result.AsUTF8Unsafe();
}
void TestRunnerBindings::SetFilePathForMockFileDialog(const std::string& path) {
if (frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetFilePathForMockFileDialog(
base::FilePath::FromUTF8Unsafe(path));
}
void TestRunnerBindings::SetMockSpellCheckerEnabled(bool enabled) {
if (!frame_) {
return;
}
spell_check_->SetEnabled(enabled);
}
void TestRunnerBindings::SetSpellCheckResolvedCallback(
v8::Local<v8::Function> callback) {
if (!frame_) {
return;
}
spell_check_->SetSpellCheckResolvedCallback(callback);
}
void TestRunnerBindings::RemoveSpellCheckResolvedCallback() {
if (!frame_) {
return;
}
spell_check_->RemoveSpellCheckResolvedCallback();
}
v8::Local<v8::Value>
TestRunnerBindings::EvaluateScriptInIsolatedWorldAndReturnValue(
int world_id,
const std::string& script) {
if (!frame_ || world_id <= 0 || world_id >= (1 << 29)) {
return {};
}
blink::WebScriptSource source(blink::WebString::FromUTF8(script));
return GetWebFrame()->ExecuteScriptInIsolatedWorldAndReturnValue(
world_id, source, blink::BackForwardCacheAware::kAllow);
}
void TestRunnerBindings::EvaluateScriptInIsolatedWorld(
int world_id,
const std::string& script) {
if (!frame_ || world_id <= 0 || world_id >= (1 << 29)) {
return;
}
blink::WebScriptSource source(blink::WebString::FromUTF8(script));
GetWebFrame()->ExecuteScriptInIsolatedWorld(
world_id, source, blink::BackForwardCacheAware::kAllow);
}
void TestRunnerBindings::EvaluateScriptInOwnTask(
const std::string& script,
const std::string& url,
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
blink::WebScriptSource source(blink::WebString::FromUTF8(script),
blink::WebURL(GURL(url)));
GetWebFrame()
->GetTaskRunner(blink::TaskType::kInternalTest)
->PostTask(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<TestRunnerBindings> weak_this,
blink::WebScriptSource source, base::OnceClosure closure) {
if (!weak_this || !weak_this->frame_) {
return;
}
weak_this->GetWebFrame()->ExecuteScript(source);
std::move(closure).Run();
},
weak_ptr_factory_.GetWeakPtr(), std::move(source),
WrapV8Closure(v8_callback)));
}
void TestRunnerBindings::SetIsolatedWorldInfo(
int world_id,
v8::Local<v8::Value> security_origin,
v8::Local<v8::Value> content_security_policy) {
if (!frame_) {
return;
}
if (world_id <= content::ISOLATED_WORLD_ID_GLOBAL ||
blink::IsEqualOrExceedEmbedderWorldIdLimit(world_id)) {
return;
}
if (!security_origin->IsString() && !security_origin->IsNull()) {
return;
}
if (!content_security_policy->IsString() &&
!content_security_policy->IsNull()) {
return;
}
// If |content_security_policy| is specified, |security_origin| must also be
// specified.
if (content_security_policy->IsString() && security_origin->IsNull()) {
return;
}
blink::WebLocalFrame* web_frame = GetWebFrame();
blink::WebIsolatedWorldInfo info;
if (security_origin->IsString()) {
info.security_origin = blink::WebSecurityOrigin::CreateFromString(
web_test_string_util::V8StringToWebString(
web_frame->GetAgentGroupScheduler()->Isolate(),
security_origin.As<v8::String>()));
}
if (content_security_policy->IsString()) {
info.content_security_policy = web_test_string_util::V8StringToWebString(
web_frame->GetAgentGroupScheduler()->Isolate(),
content_security_policy.As<v8::String>());
}
// Clear the document->isolated world CSP mapping.
GetWebFrame()->ClearIsolatedWorldCSPForTesting(world_id);
blink::SetIsolatedWorldInfo(world_id, info);
}
void TestRunnerBindings::AddOriginAccessAllowListEntry(
const std::string& source_origin,
const std::string& destination_protocol,
const std::string& destination_host,
bool allow_destination_subdomains) {
if (!frame_) {
return;
}
// Non-standard schemes should be added to the scheme registeries to use
// for the origin access whitelisting.
GURL url(source_origin);
DCHECK(url.is_valid());
DCHECK(url.has_scheme());
DCHECK(url.has_host());
runner_->AddOriginAccessAllowListEntry(source_origin, destination_protocol,
destination_host,
allow_destination_subdomains);
}
void TestRunnerBindings::InsertStyleSheet(const std::string& source_code) {
if (!frame_) {
return;
}
GetWebFrame()->GetDocument().InsertStyleSheet(
blink::WebString::FromUTF8(source_code));
}
bool TestRunnerBindings::FindString(
const std::string& search_text,
const std::vector<std::string>& options_array) {
if (!frame_) {
return false;
}
bool match_case = true;
bool forward = true;
bool new_session = false;
bool wrap_around = false;
bool async = false;
for (const auto& option : options_array) {
if (option == "CaseInsensitive")
match_case = false;
else if (option == "Backwards")
forward = false;
else if (option == "StartInSelection")
new_session = true;
else if (option == "WrapAround")
wrap_around = true;
else if (option == "Async")
async = true;
}
const bool find_result = GetWebFrame()->FindForTesting(
0, blink::WebString::FromUTF8(search_text), match_case, forward,
new_session, false /* force */, wrap_around, async);
return find_result;
}
std::string TestRunnerBindings::SelectionAsMarkup() {
if (!frame_) {
return {};
}
return GetWebFrame()->SelectionAsMarkup().Utf8();
}
void TestRunnerBindings::SetTextSubpixelPositioning(bool value) {
if (!frame_) {
return;
}
runner_->SetTextSubpixelPositioning(value);
}
void TestRunnerBindings::SetTrustTokenKeyCommitments(
const std::string& raw_commitments,
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetTrustTokenKeyCommitments(
raw_commitments, WrapV8Closure(std::move(v8_callback)));
}
void TestRunnerBindings::SetMainWindowHidden(bool hidden) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetMainWindowHidden(hidden);
}
void TestRunnerBindings::SetFrameWindowHidden(bool hidden) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetFrameWindowHidden(
frame_->GetWebFrame()->GetLocalFrameToken(), hidden);
}
void TestRunnerBindings::SetWindowRect(const gin::Dictionary& bounds) {
if (!frame_) {
return;
}
gfx::Rect rect = frame_->GetLocalRootWebFrameWidget()->WindowRect();
// https://www.w3.org/TR/webdriver2/#set-window-rect
int x, y, width, height;
if (const_cast<gin::Dictionary&>(bounds).Get("x", &x) &&
const_cast<gin::Dictionary&>(bounds).Get("y", &y)) {
rect.set_origin({x, y});
}
if (const_cast<gin::Dictionary&>(bounds).Get("width", &width) &&
const_cast<gin::Dictionary&>(bounds).Get("height", &height)) {
rect.set_size({width, height});
}
GetWebFrame()->View()->SetWindowRectSynchronouslyForTesting(rect);
}
void TestRunnerBindings::SetTextDirection(const std::string& direction_name) {
if (!frame_) {
return;
}
// Map a direction name to a base::i18n::TextDirection value.
base::i18n::TextDirection direction;
if (direction_name == "auto") {
direction = base::i18n::TextDirection::UNKNOWN_DIRECTION;
} else if (direction_name == "rtl") {
direction = base::i18n::TextDirection::RIGHT_TO_LEFT;
} else if (direction_name == "ltr") {
direction = base::i18n::TextDirection::LEFT_TO_RIGHT;
} else {
return;
}
GetWebFrame()->SetTextDirectionForTesting(direction);
}
void TestRunnerBindings::EnableAutoResizeMode(int min_width,
int min_height,
int max_width,
int max_height) {
if (!frame_) {
return;
}
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
if (max_width <= 0 || max_height <= 0) {
return;
}
gfx::Size min_size(min_width, min_height);
gfx::Size max_size(max_width, max_height);
frame_->GetWebTestControlHostRemote()->EnableAutoResize(min_size, max_size);
}
void TestRunnerBindings::DisableAutoResizeMode(int new_width, int new_height) {
if (!frame_) {
return;
}
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
if (new_width <= 0 || new_height <= 0) {
return;
}
gfx::Size new_size(new_width, new_height);
frame_->GetWebTestControlHostRemote()->DisableAutoResize(new_size);
}
void TestRunnerBindings::SetMockScreenOrientation(
const std::string& orientation) {
if (!frame_) {
return;
}
runner_->SetMockScreenOrientation(orientation, *frame_);
}
void TestRunnerBindings::DisableMockScreenOrientation() {
if (!frame_) {
return;
}
runner_->DisableMockScreenOrientation(GetWebFrame()->View());
}
void TestRunnerBindings::SetDisallowedSubresourcePathSuffixes(
std::vector<std::string> suffixes,
bool block_subresources) {
if (!frame_) {
return;
}
GetWebFrame()->GetDocumentLoader()->SetSubresourceFilter(
new FakeSubresourceFilter(std::move(suffixes), block_subresources));
}
void TestRunnerBindings::SetPopupBlockingEnabled(bool block_popups) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetPopupBlockingEnabled(block_popups);
}
void TestRunnerBindings::SetJavaScriptCanAccessClipboard(bool can_access) {
if (!frame_) {
return;
}
// WebPreferences aren't propagated between frame tree fragments, so only
// allow this in the main frame.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
prefs_.java_script_can_access_clipboard = can_access;
runner_->OnTestPreferencesChanged(prefs_, *frame_);
}
void TestRunnerBindings::SetAllowFileAccessFromFileURLs(bool allow) {
if (!frame_) {
return;
}
// WebPreferences aren't propagated between frame tree fragments, so only
// allow this in the main frame.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
prefs_.allow_file_access_from_file_urls = allow;
runner_->OnTestPreferencesChanged(prefs_, *frame_);
}
void TestRunnerBindings::OverridePreference(gin::Arguments* args) {
if (!frame_) {
return;
}
if (args->Length() != 2) {
args->ThrowTypeError("overridePreference expects 2 arguments");
return;
}
std::string key;
if (!args->GetNext(&key)) {
args->ThrowError();
return;
}
if (key == "WebKitDefaultFontSize") {
ConvertAndSet(args, &prefs_.default_font_size);
} else if (key == "WebKitMinimumFontSize") {
ConvertAndSet(args, &prefs_.minimum_font_size);
} else if (key == "WebKitDefaultTextEncodingName") {
ConvertAndSet(args, &prefs_.default_text_encoding_name);
} else if (key == "WebKitJavaScriptEnabled") {
ConvertAndSet(args, &prefs_.java_script_enabled);
} else if (key == "WebKitSupportsMultipleWindows") {
ConvertAndSet(args, &prefs_.supports_multiple_windows);
} else if (key == "WebKitDisplayImagesKey") {
ConvertAndSet(args, &prefs_.loads_images_automatically);
} else if (key == "WebKitPluginsEnabled") {
ConvertAndSet(args, &prefs_.plugins_enabled);
} else if (key == "WebKitTabToLinksPreferenceKey") {
ConvertAndSet(args, &prefs_.tabs_to_links);
} else if (key == "WebKitCSSGridLayoutEnabled") {
ConvertAndSet(args, &prefs_.experimental_css_grid_layout_enabled);
} else if (key == "WebKitHyperlinkAuditingEnabled") {
ConvertAndSet(args, &prefs_.hyperlink_auditing_enabled);
} else if (key == "WebKitEnableCaretBrowsing") {
ConvertAndSet(args, &prefs_.caret_browsing_enabled);
} else if (key == "WebKitAllowRunningInsecureContent") {
ConvertAndSet(args, &prefs_.allow_running_of_insecure_content);
} else if (key == "WebKitDisableReadingFromCanvas") {
ConvertAndSet(args, &prefs_.disable_reading_from_canvas);
} else if (key == "WebKitStrictMixedContentChecking") {
ConvertAndSet(args, &prefs_.strict_mixed_content_checking);
} else if (key == "WebKitStrictPowerfulFeatureRestrictions") {
ConvertAndSet(args, &prefs_.strict_powerful_feature_restrictions);
} else if (key == "WebKitWebSecurityEnabled") {
ConvertAndSet(args, &prefs_.web_security_enabled);
} else if (key == "WebKitSpatialNavigationEnabled") {
ConvertAndSet(args, &prefs_.spatial_navigation_enabled);
} else {
args->ThrowTypeError("Invalid name for preference: " + key);
}
runner_->OnTestPreferencesChanged(prefs_, *frame_);
}
void TestRunnerBindings::SetAcceptLanguages(
const std::string& accept_languages) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetAcceptLanguages(accept_languages);
}
void TestRunnerBindings::SetPluginsEnabled(bool enabled) {
if (!frame_) {
return;
}
// WebPreferences aren't propagated between frame tree fragments, so only
// allow this in the main frame.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
prefs_.plugins_enabled = enabled;
runner_->OnTestPreferencesChanged(prefs_, *frame_);
}
void TestRunnerBindings::DumpEditingCallbacks() {
if (!frame_) {
return;
}
runner_->DumpEditingCallbacks(*frame_);
}
void TestRunnerBindings::DumpAsMarkup() {
if (!frame_) {
return;
}
runner_->DumpAsMarkup(*frame_);
}
void TestRunnerBindings::DumpAsText() {
if (!frame_) {
return;
}
runner_->DumpAsText(*frame_);
}
void TestRunnerBindings::DumpAsTextWithPixelResults() {
if (!frame_) {
return;
}
runner_->DumpAsTextWithPixelResults(*frame_);
}
void TestRunnerBindings::DumpAsLayout() {
if (!frame_) {
return;
}
runner_->DumpAsLayout(*frame_);
}
void TestRunnerBindings::DumpAsLayoutWithPixelResults() {
if (!frame_) {
return;
}
runner_->DumpAsLayoutWithPixelResults(*frame_);
}
void TestRunnerBindings::DumpChildFrames() {
if (!frame_) {
return;
}
runner_->DumpChildFrames(*frame_);
}
void TestRunnerBindings::DumpIconChanges() {
if (!frame_) {
return;
}
runner_->DumpIconChanges(*frame_);
}
void TestRunnerBindings::SetAudioData(const gin::ArrayBufferView& view) {
if (!frame_) {
return;
}
runner_->SetAudioData(view);
}
void TestRunnerBindings::DumpFrameLoadCallbacks() {
if (!frame_) {
return;
}
runner_->DumpFrameLoadCallbacks(*frame_);
}
void TestRunnerBindings::DumpPingLoaderCallbacks() {
if (!frame_) {
return;
}
runner_->DumpPingLoaderCallbacks(*frame_);
}
void TestRunnerBindings::DumpUserGestureInFrameLoadCallbacks() {
if (!frame_) {
return;
}
runner_->DumpUserGestureInFrameLoadCallbacks(*frame_);
}
void TestRunnerBindings::DumpTitleChanges() {
if (!frame_) {
return;
}
runner_->DumpTitleChanges(*frame_);
}
void TestRunnerBindings::SetCaretBrowsingEnabled() {
if (!frame_) {
return;
}
blink::WebView* web_view = GetWebFrame()->View();
web_view->GetSettings()->SetCaretBrowsingEnabled(true);
}
void TestRunnerBindings::SetStorageAllowed(bool allowed) {
if (!frame_) {
return;
}
runner_->SetStorageAllowed(allowed, *frame_);
}
void TestRunnerBindings::SetPluginsAllowed(bool allowed) {
if (!frame_) {
return;
}
// This only modifies the local process, and is used to verify behaviour based
// on settings, but does not test propagation of settings across renderers.
blink::WebView* web_view = GetWebFrame()->View();
web_view->GetSettings()->SetPluginsEnabled(allowed);
}
void TestRunnerBindings::SetAllowRunningOfInsecureContent(bool allowed) {
if (!frame_) {
return;
}
runner_->SetAllowRunningOfInsecureContent(allowed, *frame_);
}
void TestRunnerBindings::DumpPermissionClientCallbacks() {
if (!frame_) {
return;
}
runner_->DumpPermissionClientCallbacks(*frame_);
}
void TestRunnerBindings::DumpBackForwardList() {
if (!frame_) {
return;
}
runner_->DumpBackForwardList();
}
void TestRunnerBindings::DumpSelectionRect() {
if (!frame_) {
return;
}
runner_->DumpSelectionRect(*frame_);
}
void TestRunnerBindings::SetPrinting() {
if (!frame_) {
return;
}
runner_->SetPrinting(*frame_);
}
void TestRunnerBindings::SetPrintingForFrame(const std::string& frame_name) {
if (!frame_) {
return;
}
runner_->SetPrintingForFrame(frame_name, *frame_);
}
void TestRunnerBindings::SetPrintingSize(int width, int height) {
if (!frame_) {
return;
}
runner_->SetPrintingSize(width, height, *frame_);
}
void TestRunnerBindings::SetPrintingMargin(int margin) {
if (!frame_) {
return;
}
runner_->SetPrintingMargin(margin, *frame_);
}
void TestRunnerBindings::SetShouldCenterAndShrinkToFitPaper(bool b) {
if (!frame_) {
return;
}
runner_->SetShouldCenterAndShrinkToFitPaper(b);
}
void TestRunnerBindings::SetPrintingScaleFactor(float factor) {
if (!frame_) {
return;
}
runner_->SetPrintingScaleFactor(factor);
}
void TestRunnerBindings::ClearTrustTokenState(
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->ClearTrustTokenState(
WrapV8Closure(std::move(v8_callback)));
}
void TestRunnerBindings::SetShouldGeneratePixelResults(bool value) {
if (!frame_) {
return;
}
runner_->SetShouldGeneratePixelResults(value, *frame_);
}
void TestRunnerBindings::SetShouldStayOnPageAfterHandlingBeforeUnload(
bool value) {
if (!frame_) {
return;
}
runner_->SetShouldStayOnPageAfterHandlingBeforeUnload(value, *frame_);
}
void TestRunnerBindings::SetWillSendRequestClearHeader(
const std::string& header) {
if (!frame_) {
return;
}
runner_->SetWillSendRequestClearHeader(header);
}
void TestRunnerBindings::SetWillSendRequestClearReferrer() {
if (!frame_) {
return;
}
runner_->SetWillSendRequestClearReferrer();
}
void TestRunnerBindings::WaitUntilExternalURLLoad() {
if (!frame_) {
return;
}
runner_->WaitUntilExternalURLLoad(*frame_);
}
void TestRunnerBindings::DumpDragImage() {
if (!frame_) {
return;
}
runner_->DumpDragImage(*frame_);
}
void TestRunnerBindings::DumpNavigationPolicy() {
if (!frame_) {
return;
}
runner_->DumpNavigationPolicy(*frame_);
}
void TestRunnerBindings::ClearAllDatabases() {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->ClearAllDatabases();
}
void TestRunnerBindings::SetDatabaseQuota(int quota) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetDatabaseQuota(quota);
}
void TestRunnerBindings::SetBlockThirdPartyCookies(bool block) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->BlockThirdPartyCookies(block);
}
void TestRunnerBindings::SimulateBrowserWindowFocus(bool value) {
if (!frame_) {
return;
}
// This simulates the browser focusing or unfocusing the window,
// but does so only for this renderer process. Other frame tree
// fragments in other processes do not hear about the change. To
// do so the focus change would need to go through window.focus()
// and then watch for the focus event or do a round trip to the
// browser.
// TODO(danakj): This does not appear to do the same thing as the
// browser does, because actually moving focus causes different test
// results in tests such as editing/selection/4975120.html with the
// inner frame not getting its caret back.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
runner_->FocusWindow(frame_, value);
}
std::string TestRunnerBindings::PathToLocalResource(const std::string& path) {
if (!frame_) {
return {};
}
return RewriteFileURLToLocalResource(path).GetString().Utf8();
}
void TestRunnerBindings::SetBackingScaleFactor(
double value,
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
// Limit backing scale factor to something low - 15x. Without
// this limit, arbitrarily large values can be used, which can lead to
// crashes and other problems. Examples of problems:
// gfx::Size::GetCheckedArea crashes with a size which overflows int;
// GLES2DecoderImpl::TexStorageImpl fails with "dimensions out of range"; GL
// ERROR :GL_OUT_OF_MEMORY. See https://crbug.com/899482 or
// https://crbug.com/900271
double limited_value = fmin(15, value);
frame_->GetLocalRootWebFrameWidget()->SetDeviceScaleFactorForTesting(
limited_value);
v8::Isolate* isolate = GetWebFrame()->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
WrapV8Closure(std::move(v8_callback)).Run();
}
void TestRunnerBindings::SetColorProfile(const std::string& name,
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
gfx::ColorSpace color_space;
if (name == "genericRGB") {
color_space = gfx::ICCProfileForTestingGenericRGB().GetColorSpace();
} else if (name == "sRGB") {
color_space = gfx::ColorSpace::CreateSRGB();
} else if (name == "colorSpin") {
color_space = gfx::ICCProfileForTestingColorSpin().GetColorSpace();
} else if (name == "adobeRGB") {
color_space = gfx::ICCProfileForTestingAdobeRGB().GetColorSpace();
}
GetWebFrame()->View()->SetDeviceColorSpaceForTesting(color_space);
WrapV8Closure(std::move(v8_callback)).Run();
}
void TestRunnerBindings::SetBluetoothFakeAdapter(
const std::string& adapter_name,
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
runner_->GetBluetoothFakeAdapterSetter().Set(
adapter_name, WrapV8Closure(std::move(v8_callback)));
}
void TestRunnerBindings::SetBluetoothManualChooser(bool enable) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetBluetoothManualChooser(enable);
}
static void GetBluetoothManualChooserEventsReply(
base::WeakPtr<TestRunnerBindings> test_runner,
blink::WebLocalFrame* frame,
BoundV8Callback callback,
const std::vector<std::string>& events) {
if (!test_runner) // This guards the validity of the |frame|.
return;
v8::Isolate* isolate = frame->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
// gin::TryConvertToV8() requires a v8::Context.
v8::Local<v8::Context> context = frame->MainWorldScriptContext();
CHECK(!context.IsEmpty());
v8::Context::Scope context_scope(context);
v8::Local<v8::Value> arg;
bool converted = gin::TryConvertToV8(isolate, events, &arg);
CHECK(converted);
std::move(callback).Run(v8::LocalVector<v8::Value>(isolate, {
arg,
}));
}
void TestRunnerBindings::GetBluetoothManualChooserEvents(
v8::Local<v8::Function> callback) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->GetBluetoothManualChooserEvents(
base::BindOnce(&GetBluetoothManualChooserEventsReply,
weak_ptr_factory_.GetWeakPtr(), GetWebFrame(),
WrapV8Callback(std::move(callback))));
}
void TestRunnerBindings::SetBrowserHandlesFocus(bool enable) {
if (!frame_) {
return;
}
blink::SetBrowserCanHandleFocusForWebTest(enable);
}
void TestRunnerBindings::SendBluetoothManualChooserEvent(
const std::string& event,
const std::string& argument) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SendBluetoothManualChooserEvent(
event, argument);
}
void TestRunnerBindings::SetPOSIXLocale(const std::string& locale) {
if (!frame_) {
return;
}
setlocale(LC_ALL, locale.c_str());
// Number to string conversions require C locale, regardless of what
// all the other subsystems are set to.
setlocale(LC_NUMERIC, "C");
}
void TestRunnerBindings::SimulateWebNotificationClick(gin::Arguments* args) {
if (!frame_) {
return;
}
DCHECK_GE(args->Length(), 1);
std::string title;
int action_index = std::numeric_limits<int32_t>::min();
std::optional<std::u16string> reply;
if (!args->GetNext(&title)) {
args->ThrowError();
return;
}
// Optional |action_index| argument.
if (args->Length() >= 2) {
if (!args->GetNext(&action_index)) {
args->ThrowError();
return;
}
}
// Optional |reply| argument.
if (args->Length() >= 3) {
std::string reply_string;
if (!args->GetNext(&reply_string)) {
args->ThrowError();
return;
}
reply = base::UTF8ToUTF16(reply_string);
}
frame_->GetWebTestControlHostRemote()->SimulateWebNotificationClick(
title, action_index, reply);
}
void TestRunnerBindings::SimulateWebNotificationClose(const std::string& title,
bool by_user) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SimulateWebNotificationClose(title,
by_user);
}
void TestRunnerBindings::SimulateWebContentIndexDelete(const std::string& id) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SimulateWebContentIndexDelete(id);
}
void TestRunnerBindings::SetHighlightAds() {
if (!frame_) {
return;
}
blink::WebView* web_view = GetWebFrame()->View();
web_view->GetSettings()->SetHighlightAds(true);
}
void TestRunnerBindings::AddWebPageOverlay() {
if (!frame_) {
return;
}
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame())
return;
frame_->GetLocalRootWebFrameWidget()->SetMainFrameOverlayColor(SK_ColorCYAN);
}
void TestRunnerBindings::RemoveWebPageOverlay() {
if (!frame_) {
return;
}
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame())
return;
frame_->GetLocalRootWebFrameWidget()->SetMainFrameOverlayColor(
SK_ColorTRANSPARENT);
}
void TestRunnerBindings::UpdateAllLifecyclePhasesAndComposite() {
if (!frame_) {
return;
}
frame_->GetLocalRootFrameWidgetTestHelper()
->UpdateAllLifecyclePhasesAndComposite(base::DoNothing());
}
void TestRunnerBindings::UpdateAllLifecyclePhasesAndCompositeThen(
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
frame_->GetLocalRootFrameWidgetTestHelper()
->UpdateAllLifecyclePhasesAndComposite(
WrapV8Closure(std::move(v8_callback)));
}
void TestRunnerBindings::SetAnimationRequiresRaster(bool do_raster) {
if (!frame_) {
return;
}
runner_->SetAnimationRequiresRaster(do_raster);
}
static void GetManifestReply(v8::Isolate* isolate,
BoundV8Callback callback,
const blink::WebURL& manifest_url) {
std::move(callback).Run(NoV8Args(isolate));
}
void TestRunnerBindings::GetManifestThen(v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
v8::Isolate* isolate = GetWebFrame()->GetAgentGroupScheduler()->Isolate();
blink::WebManifestManager::RequestManifestForTesting(
GetWebFrame(), base::BindOnce(GetManifestReply, isolate,
WrapV8Callback(std::move(v8_callback))));
}
#if BUILDFLAG(ENABLE_PRINTING)
void TestRunnerBindings::CapturePrintingPixelsThen(
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
blink::WebLocalFrame* frame = GetWebFrame();
SkBitmap bitmap = runner_->PrintFrameToBitmap(frame);
v8::Isolate* isolate = frame->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
// ConvertBitmapToV8() requires a v8::Context.
v8::Local<v8::Context> context = frame->MainWorldScriptContext();
CHECK(!context.IsEmpty());
v8::Context::Scope context_scope(context);
WrapV8Callback(std::move(v8_callback))
.Run(ConvertBitmapToV8(isolate, context_scope, bitmap));
}
#endif // BUILDFLAG(ENABLE_PRINTING)
void TestRunnerBindings::CheckForLeakedWindows() {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->CheckForLeakedWindows();
}
void TestRunnerBindings::CopyImageThen(int x,
int y,
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
mojo::Remote<blink::mojom::ClipboardHost> remote_clipboard;
frame_->GetBrowserInterfaceBroker()->GetInterface(
remote_clipboard.BindNewPipeAndPassReceiver());
blink::ClipboardSequenceNumberToken sequence_number_before;
CHECK(remote_clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste,
&sequence_number_before));
GetWebFrame()->CopyImageAtForTesting(gfx::Point(x, y));
auto sequence_number_after = sequence_number_before;
while (sequence_number_before.value() == sequence_number_after.value()) {
// TODO(crbug.com/40588468): Ideally we would CHECK here that the mojo call
// succeeded, but this crashes under some circumstances (crbug.com/1232810).
remote_clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste,
&sequence_number_after);
}
mojo_base::BigBuffer png_data;
remote_clipboard->ReadPng(ui::ClipboardBuffer::kCopyPaste, &png_data);
SkBitmap bitmap;
gfx::PNGCodec::Decode(png_data.data(), png_data.size(), &bitmap);
blink::WebLocalFrame* web_frame = GetWebFrame();
v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
CHECK(!context.IsEmpty());
v8::Context::Scope context_scope(context);
WrapV8Callback(std::move(v8_callback))
.Run(ConvertBitmapToV8(isolate, context_scope, std::move(bitmap)));
}
void TestRunnerBindings::DropPointerLock() {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->DropPointerLock();
}
void TestRunnerBindings::SetPointerLockWillFail() {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetPointerLockWillFail();
}
void TestRunnerBindings::SetPointerLockWillRespondAsynchronously() {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()
->SetPointerLockWillRespondAsynchronously();
}
void TestRunnerBindings::AllowPointerLock() {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->AllowPointerLock();
}
void TestRunnerBindings::SetCustomTextOutput(const std::string& output) {
if (!frame_) {
return;
}
runner_->SetCustomTextOutput(output, *frame_);
}
void TestRunnerBindings::SetPermission(const std::string& name,
const std::string& value,
const std::string& origin,
const std::string& embedding_origin) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->SetPermission(
name, blink::ToPermissionStatus(value), GURL(origin),
GURL(embedding_origin));
}
static void DispatchBeforeInstallPromptEventReply(v8::Isolate* isolate,
BoundV8Callback callback,
bool cancelled) {
v8::HandleScope handle_scope(isolate);
std::move(callback).Run(v8::LocalVector<v8::Value>(
isolate, {
v8::Boolean::New(isolate, cancelled),
}));
}
void TestRunnerBindings::DispatchBeforeInstallPromptEvent(
const std::vector<std::string>& event_platforms,
v8::Local<v8::Function> v8_callback) {
if (!frame_) {
return;
}
app_banner_service_ = std::make_unique<AppBannerService>();
frame_->BindLocalInterface(blink::mojom::AppBannerController::Name_,
app_banner_service_->controller()
.BindNewPipeAndPassReceiver()
.PassPipe());
app_banner_service_->SendBannerPromptRequest(
event_platforms,
base::BindOnce(
&DispatchBeforeInstallPromptEventReply,
base::Unretained(GetWebFrame()->GetAgentGroupScheduler()->Isolate()),
WrapV8Callback(std::move(v8_callback))));
}
void TestRunnerBindings::ResolveBeforeInstallPromptPromise(
const std::string& platform) {
if (!frame_) {
return;
}
if (app_banner_service_) {
app_banner_service_->ResolvePromise(platform);
app_banner_service_.reset();
}
}
std::string TestRunnerBindings::PlatformName() {
if (!frame_) {
return {};
}
return runner_->platform_name_;
}
void TestRunnerBindings::TextZoomIn() {
if (!frame_) {
return;
}
// This may only be run from the main frame, as the user modifies this at the
// top level.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame())
return;
// TODO(danakj): This should be an async call through the browser process, but
// note this is an AndroidWebView feature which is not part of the content (or
// content_shell) APIs.
blink::WebFrameWidget* widget = frame_->GetLocalRootWebFrameWidget();
widget->SetTextZoomFactor(widget->TextZoomFactor() * 1.2f);
}
void TestRunnerBindings::TextZoomOut() {
if (!frame_) {
return;
}
// This may only be run from the main frame, as the user modifies this at the
// top level.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
// TODO(danakj): This should be an async call through the browser process, but
// note this is an AndroidWebView feature which is not part of the content (or
// content_shell) APIs.
blink::WebFrameWidget* widget = frame_->GetLocalRootWebFrameWidget();
widget->SetTextZoomFactor(widget->TextZoomFactor() / 1.2f);
}
void TestRunnerBindings::ZoomPageIn() {
if (!frame_) {
return;
}
// This may only be run from the main frame, as the user modifies this at the
// top level.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
blink::WebFrameWidget* web_frame_widget =
frame_->GetLocalRootWebFrameWidget();
// TODO(danakj): This should be an async call through the browser process.
// JS can wait for `matchMedia("screen and (min-resolution: 2dppx)").matches`
// for the operation to complete, if it can tell which number to use in
// min-resolution.
web_frame_widget->SetZoomLevelForTesting(web_frame_widget->GetZoomLevel() +
1);
}
void TestRunnerBindings::ZoomPageOut() {
if (!frame_) {
return;
}
// This may only be run from the main frame, as the user modifies this at the
// top level.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame())
return;
blink::WebFrameWidget* web_frame_widget =
frame_->GetLocalRootWebFrameWidget();
// TODO(danakj): This should be an async call through the browser process.
// JS can wait for `matchMedia("screen and (min-resolution: 2dppx)").matches`
// for the operation to complete, if it can tell which number to use in
// min-resolution.
web_frame_widget->SetZoomLevelForTesting(web_frame_widget->GetZoomLevel() -
1);
}
void TestRunnerBindings::SetPageZoomFactor(double zoom_factor) {
if (!frame_) {
return;
}
// This may only be run from the main frame, as the user modifies this at the
// top level.
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!frame_->IsMainFrame()) {
return;
}
// TODO(danakj): This should be an async call through the browser process.
// JS can wait for `matchMedia("screen and (min-resolution: 2dppx)").matches`
// for the operation to complete, if it can tell which number to use in
// min-resolution.
frame_->GetLocalRootWebFrameWidget()->SetZoomLevelForTesting(
blink::ZoomFactorToZoomLevel(zoom_factor));
}
std::string TestRunnerBindings::TooltipText() {
if (!frame_) {
return {};
}
blink::WebString tooltip_text =
frame_->GetLocalRootWebFrameWidget()->GetLastToolTipTextForTesting();
return tooltip_text.Utf8();
}
int TestRunnerBindings::WebHistoryItemCount() {
if (!frame_) {
return 0;
}
// Returns the length of the session history of this `blink::WebView`. Note
// that this only coincides with the actual length of the session history if
// this `blink::WebView` is the currently active `blink::WebView` of a
// WebContents.
return frame_->GetWebFrame()->View()->HistoryBackListCount() +
frame_->GetWebFrame()->View()->HistoryForwardListCount() + 1;
}
void TestRunnerBindings::ForceNextWebGLContextCreationToFail() {
if (!frame_) {
return;
}
blink::ForceNextWebGLContextCreationToFailForTest();
}
void TestRunnerBindings::FocusDevtoolsSecondaryWindow() {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->FocusDevtoolsSecondaryWindow();
}
void TestRunnerBindings::ForceNextDrawingBufferCreationToFail() {
if (!frame_) {
return;
}
blink::ForceNextDrawingBufferCreationToFailForTest();
}
void TestRunnerBindings::DisableAutomaticDragDrop() {
if (!frame_) {
return;
}
runner_->DisableAutomaticDragDrop(*frame_);
}
void TestRunnerBindings::GoToOffset(int offset) {
if (!frame_) {
return;
}
frame_->GetWebTestControlHostRemote()->GoToOffset(offset);
}
void TestRunnerBindings::SetRphRegistrationMode(gin::Arguments* args) {
if (!frame_) {
return;
}
if (args->Length() != 1) {
args->ThrowTypeError("setRphRegistrationMode expects 1 argument");
return;
}
std::string arg;
if (!args->GetNext(&arg)) {
args->ThrowError();
return;
}
auto mode = mojom::WebTestControlHost::AutoResponseMode::kNone;
if (arg == "autoAccept") {
mode = mojom::WebTestControlHost::AutoResponseMode::kAutoAccept;
} else if (arg == "autoReject") {
mode = mojom::WebTestControlHost::AutoResponseMode::kAutoReject;
} else if (arg != "none") {
args->ThrowTypeError(
"setRphRegistrationMode called with an invalid 'mode' argument");
return;
}
frame_->GetWebTestControlHostRemote()->SetRegisterProtocolHandlerMode(mode);
}
void TestRunnerBindings::NotImplemented(const gin::Arguments& args) {}
// This class helps track active main windows and when the `blink::WebView` is
// destroyed it will remove it from TestRunner's list.
class TestRunner::MainWindowTracker : public blink::WebViewObserver {
public:
MainWindowTracker(blink::WebView* view, TestRunner* test_runner)
: blink::WebViewObserver(view), test_runner_(test_runner) {}
void OnDestruct() override {
std::erase_if(test_runner_->main_windows_, base::MatchesUniquePtr(this));
}
private:
const raw_ptr<TestRunner> test_runner_;
};
TestRunner::WorkQueue::WorkQueue(TestRunner* controller)
: controller_(controller) {}
void TestRunner::WorkQueue::Reset() {
// Set values in a TrackedDictionary |states_| to avoid accessing missing
// values.
set_frozen(false);
set_has_items(false);
states_.ResetChangeTracking();
set_loading(true);
}
void TestRunner::WorkQueue::AddWork(mojom::WorkItemPtr work_item,
WebFrameTestProxy& source) {
if (is_frozen())
return;
source.GetWebTestControlHostRemote()->WorkItemAdded(std::move(work_item));
set_has_items(true);
OnStatesChanged(source);
}
void TestRunner::WorkQueue::RequestWork(WebFrameTestProxy& source) {
source.GetWebTestControlHostRemote()->RequestWorkItem();
}
void TestRunner::WorkQueue::ProcessWorkItem(mojom::WorkItemPtr work_item,
WebFrameTestProxy& source) {
// Watch for loading finishing inside ProcessWorkItemInternal().
set_loading(true);
bool started_load = ProcessWorkItemInternal(std::move(work_item), source);
if (started_load) {
// If a load started, and didn't complete inside of
// ProcessWorkItemInternal(), then mark the load as running.
if (loading_)
controller_->frame_will_start_load_ = true;
// Wait for an ongoing load to complete before requesting the next WorkItem.
return;
}
RequestWork(source);
}
bool TestRunner::WorkQueue::ProcessWorkItemInternal(
mojom::WorkItemPtr work_item,
WebFrameTestProxy& source) {
switch (work_item->which()) {
case mojom::WorkItem::Tag::kBackForward: {
mojom::WorkItemBackForwardPtr& item_back_forward =
work_item->get_back_forward();
source.GetWebTestControlHostRemote()->GoToOffset(
item_back_forward->distance);
return true; // TODO(danakj): Did it really start a navigation?
}
case mojom::WorkItem::Tag::kLoadingScript: {
mojom::WorkItemLoadingScriptPtr& item_loading_script =
work_item->get_loading_script();
WebFrameTestProxy* main_frame =
controller_->FindInProcessMainWindowMainFrame();
DCHECK(main_frame);
main_frame->GetWebFrame()->ExecuteScript(blink::WebScriptSource(
blink::WebString::FromUTF8(item_loading_script->script)));
return true; // TODO(danakj): Did it really start a navigation?
}
case mojom::WorkItem::Tag::kNonLoadingScript: {
mojom::WorkItemNonLoadingScriptPtr& item_non_loading_script =
work_item->get_non_loading_script();
WebFrameTestProxy* main_frame =
controller_->FindInProcessMainWindowMainFrame();
DCHECK(main_frame);
main_frame->GetWebFrame()->ExecuteScript(blink::WebScriptSource(
blink::WebString::FromUTF8(item_non_loading_script->script)));
return false;
}
case mojom::WorkItem::Tag::kLoad: {
mojom::WorkItemLoadPtr& item_load = work_item->get_load();
source.GetWebTestControlHostRemote()->LoadURLForFrame(
GURL(item_load->url), item_load->target);
return true; // TODO(danakj): Did it really start a navigation?
}
case mojom::WorkItem::Tag::kReload:
source.GetWebTestControlHostRemote()->Reload();
return true;
}
NOTREACHED_IN_MIGRATION();
return false;
}
void TestRunner::WorkQueue::ReplicateStates(const base::Value::Dict& values,
WebFrameTestProxy& source) {
states_.ApplyUntrackedChanges(values);
if (!has_items())
controller_->FinishTestIfReady(source);
}
void TestRunner::WorkQueue::OnStatesChanged(WebFrameTestProxy& source) {
if (states_.changed_values().empty())
return;
source.GetWebTestControlHostRemote()->WorkQueueStatesChanged(
states_.changed_values().Clone());
states_.ResetChangeTracking();
}
TestRunner::TestRunner() : work_queue_(this) {
// NOTE: please don't put feature specific enable flags here,
// instead add them to runtime_enabled_features.json5.
//
// Stores state to be restored after each test.
blink::WebTestingSupport::SaveRuntimeFeatures();
Reset();
}
TestRunner::~TestRunner() = default;
void TestRunner::Install(WebFrameTestProxy* frame,
SpellCheckClient* spell_check) {
bool is_main_test_window = IsFrameInMainWindow(frame->GetWebFrame());
TestRunnerBindings::Install(this, frame, spell_check,
IsWebPlatformTestsMode(), is_main_test_window);
fake_screen_orientation_impl_.OverrideAssociatedInterfaceProviderForFrame(
frame->GetWebFrame());
gamepad_controller_.Install(frame);
frame->GetWebFrame()->View()->SetScreenOrientationOverrideForTesting(
fake_screen_orientation_impl_.CurrentOrientationType());
}
void TestRunner::Reset() {
loading_frames_.clear();
web_test_runtime_flags_.Reset();
fake_screen_orientation_impl_.ResetData();
gamepad_controller_.Reset();
drag_image_.reset();
blink::WebTestingSupport::ResetRuntimeFeatures();
blink::WebCache::Clear();
blink::WebSecurityPolicy::ClearOriginAccessList();
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
blink::WebFontRenderStyle::SetSubpixelPositioning(false);
#endif
blink::ResetDomainRelaxationForTest();
blink::SetBrowserCanHandleFocusForWebTest(false);
setlocale(LC_ALL, "");
setlocale(LC_NUMERIC, "C");
dump_as_audio_ = false;
dump_back_forward_list_ = false;
test_repaint_ = false;
sweep_horizontally_ = false;
animation_requires_raster_ = false;
main_frame_loaded_ = false;
frame_will_start_load_ = false;
did_notify_done_ = false;
http_headers_to_clear_.clear();
clear_referrer_ = false;
platform_name_ = "chromium";
weak_factory_.InvalidateWeakPtrs();
work_queue_.Reset();
}
void TestRunner::ResetWebView(blink::WebView* web_view) {
web_view->SetTabKeyCyclesThroughElements(true);
web_view->GetSettings()->SetHighlightAds(false);
web_view->GetSettings()->SetCaretBrowsingEnabled(false);
web_view->DisableAutoResizeForTesting(gfx::Size());
web_view->SetScreenOrientationOverrideForTesting(
fake_screen_orientation_impl_.CurrentOrientationType());
}
void TestRunner::ResetWebFrameWidget(blink::WebFrameWidget* web_frame_widget) {
web_frame_widget->SetDeviceScaleFactorForTesting(0);
web_frame_widget->ReleaseMouseLockAndPointerCaptureForTesting();
// These things are only modified/valid for the main frame's widget.
if (!web_frame_widget->LocalRoot()->Parent()) {
web_frame_widget->ResetZoomLevelForTesting();
web_frame_widget->SetMainFrameOverlayColor(SK_ColorTRANSPARENT);
web_frame_widget->SetTextZoomFactor(1);
}
}
void TestRunner::SetTestIsRunning(bool running) {
test_is_running_ = running;
}
bool TestRunner::ShouldDumpSelectionRect() const {
return web_test_runtime_flags_.dump_selection_rect();
}
bool TestRunner::ShouldDumpEditingCallbacks() const {
return web_test_runtime_flags_.dump_editting_callbacks();
}
void TestRunner::SetShouldDumpAsLayout(bool value, WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_as_layout(value);
OnWebTestRuntimeFlagsChanged(source);
}
bool TestRunner::ShouldDumpAsCustomText() const {
return web_test_runtime_flags_.has_custom_text_output();
}
std::string TestRunner::CustomDumpText() const {
return web_test_runtime_flags_.custom_text_output();
}
void TestRunner::SetCustomTextOutput(const std::string& text,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_custom_text_output(text);
web_test_runtime_flags_.set_has_custom_text_output(true);
OnWebTestRuntimeFlagsChanged(source);
}
bool TestRunner::ShouldGeneratePixelResults() {
return web_test_runtime_flags_.generate_pixel_results();
}
TextResultType TestRunner::ShouldGenerateTextResults() {
if (web_test_runtime_flags_.dump_as_text()) {
return TextResultType::kText;
} else if (web_test_runtime_flags_.dump_as_markup()) {
DCHECK(!web_test_runtime_flags_.is_printing());
return TextResultType::kMarkup;
} else if (web_test_runtime_flags_.dump_as_layout()) {
if (web_test_runtime_flags_.is_printing())
return TextResultType::kLayoutAsPrinting;
return TextResultType::kLayout;
}
return TextResultType::kEmpty;
}
bool TestRunner::ShouldStayOnPageAfterHandlingBeforeUnload() const {
return web_test_runtime_flags_.stay_on_page_after_handling_before_unload();
}
void TestRunner::SetShouldGeneratePixelResults(bool value,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_generate_pixel_results(value);
OnWebTestRuntimeFlagsChanged(source);
}
bool TestRunner::ShouldDumpAsAudio() const {
return dump_as_audio_;
}
const std::vector<uint8_t>& TestRunner::GetAudioData() const {
return audio_data_;
}
bool TestRunner::IsRecursiveLayoutDumpRequested() {
return web_test_runtime_flags_.dump_child_frames();
}
bool TestRunner::CanDumpPixelsFromRenderer() const {
return web_test_runtime_flags_.dump_drag_image() ||
web_test_runtime_flags_.is_printing();
}
bool TestRunner::IsPrinting() const {
return web_test_runtime_flags_.is_printing();
}
#if BUILDFLAG(ENABLE_PRINTING)
gfx::Size TestRunner::GetPrintingPageSize(blink::WebLocalFrame* frame) const {
const int printing_width = web_test_runtime_flags_.printing_width();
const int printing_height = web_test_runtime_flags_.printing_height();
if (printing_width > 0 && printing_height > 0) {
return gfx::Size(printing_width, printing_height);
}
blink::WebFrameWidget* widget = frame->LocalRoot()->FrameWidget();
widget->UpdateAllLifecyclePhases(blink::DocumentUpdateReason::kTest);
return widget->Size();
}
int TestRunner::GetPrintingMargin() const {
return web_test_runtime_flags_.printing_margin();
}
static std::string GetPageRangesStringFromMetadata(
blink::WebLocalFrame* frame) {
blink::WebElementCollection meta_iter =
frame->GetDocument().GetElementsByHTMLTagName("meta");
std::string result = "-";
if (!meta_iter.IsNull()) {
for (blink::WebElement meta = meta_iter.FirstItem(); !meta.IsNull();
meta = meta_iter.NextItem()) {
if (meta.GetAttribute("name") == "reftest-pages") {
blink::WebString pages = meta.GetAttribute("content");
if (!pages.IsNull()) {
result = pages.Ascii();
}
break; // We only take the ranges from the first tag.
}
}
}
return result;
}
printing::PageRanges TestRunner::GetPrintingPageRanges(
blink::WebLocalFrame* frame) const {
const std::string page_ranges_string = GetPageRangesStringFromMetadata(frame);
const std::vector<std::string_view> range_strings =
base::SplitStringPiece(page_ranges_string, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
printing::PageRanges result;
for (std::string_view range_string : range_strings) {
// The format for each range is "<int> | <int>? - <int>?" where the page
// numbers are 1-indexed.
const std::vector<std::string_view> page_strings = base::SplitStringPiece(
range_string, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
bool invalid = false;
if (page_strings.size() == 1) {
uint32_t page;
if (base::StringToUint(range_string, &page)) {
result.push_back(printing::PageRange{.from = page - 1, .to = page - 1});
} else {
invalid = true;
}
} else if (page_strings.size() > 2) {
invalid = true;
} else {
std::array<uint32_t, 2> page_nums{0, printing::PageRange::kMaxPage};
for (const int i : {0, 1}) {
if (!page_strings[i].empty()) {
if (base::StringToUint(page_strings[i], &page_nums[i])) {
--page_nums[i]; // Change 1-indexing to 0-indexing.
} else {
invalid = true;
break;
}
}
}
if (!invalid) {
result.push_back(
printing::PageRange{.from = page_nums[0], .to = page_nums[1]});
}
}
if (invalid) {
DLOG(WARNING) << "Invalid page range \"" << range_string << "\".\n";
}
}
printing::PageRange::Normalize(result);
return result;
}
SkBitmap TestRunner::PrintFrameToBitmap(blink::WebLocalFrame* frame) {
// Page size and margins are in CSS pixels.
auto print_params =
blink::WebPrintParams(gfx::SizeF(GetPrintingPageSize(frame)));
int default_margin = GetPrintingMargin();
print_params.default_page_description.margin_top = default_margin;
print_params.default_page_description.margin_right = default_margin;
print_params.default_page_description.margin_bottom = default_margin;
print_params.default_page_description.margin_left = default_margin;
print_params.scale_factor = printing_scale_factor_;
print_params.print_scaling_option =
should_center_and_shrink_to_fit_paper_
? printing::mojom::PrintScalingOption::kCenterShrinkToFitPaper
: printing::mojom::PrintScalingOption::kSourceSize;
auto* frame_widget = frame->LocalRoot()->FrameWidget();
frame_widget->UpdateAllLifecyclePhases(blink::DocumentUpdateReason::kTest);
uint32_t page_count = frame->PrintBegin(print_params, blink::WebNode());
const printing::PageRanges& page_ranges = GetPrintingPageRanges(frame);
blink::WebVector<uint32_t> pages(
printing::PageNumber::GetPages(page_ranges, page_count));
gfx::Size spool_size = frame->SpoolSizeInPixelsForTesting(pages);
bool is_opaque = false;
SkBitmap bitmap;
if (!bitmap.tryAllocN32Pixels(spool_size.width(), spool_size.height(),
is_opaque)) {
LOG(ERROR) << "Failed to create bitmap width=" << spool_size.width()
<< " height=" << spool_size.height();
return SkBitmap();
}
printing::MetafileSkia metafile(printing::mojom::SkiaDocumentType::kMSKP,
printing::PrintSettings::NewCookie());
cc::SkiaPaintCanvas canvas(bitmap);
canvas.SetPrintingMetafile(&metafile);
frame->PrintPagesForTesting(&canvas, spool_size, &pages);
frame->PrintEnd();
return bitmap;
}
#endif // BUILDFLAG(ENABLE_PRINTING)
SkBitmap TestRunner::DumpPixelsInRenderer(blink::WebLocalFrame* main_frame) {
DCHECK(!main_frame->Parent());
DCHECK(CanDumpPixelsFromRenderer());
if (web_test_runtime_flags_.dump_drag_image()) {
if (!drag_image_.isNull())
return drag_image_;
// This means the test called dumpDragImage but did not initiate a drag.
// Return a blank image so that the test fails.
SkBitmap bitmap;
bitmap.allocN32Pixels(1, 1);
bitmap.eraseColor(0);
return bitmap;
}
#if BUILDFLAG(ENABLE_PRINTING)
blink::WebLocalFrame* target_frame = main_frame;
std::string frame_name = web_test_runtime_flags_.printing_frame();
if (!frame_name.empty()) {
blink::WebFrame* frame_to_print =
main_frame->FindFrameByName(blink::WebString::FromUTF8(frame_name));
if (frame_to_print && frame_to_print->IsWebLocalFrame())
target_frame = frame_to_print->ToWebLocalFrame();
}
return PrintFrameToBitmap(target_frame);
#else
NOTREACHED_IN_MIGRATION();
return SkBitmap();
#endif
}
void TestRunner::ReplicateWebTestRuntimeFlagsChanges(
const base::Value::Dict& changed_values) {
if (!test_is_running_)
return;
web_test_runtime_flags_.tracked_dictionary().ApplyUntrackedChanges(
changed_values);
}
bool TestRunner::HasCustomTextDump(std::string* custom_text_dump) const {
if (ShouldDumpAsCustomText()) {
*custom_text_dump = CustomDumpText();
return true;
}
return false;
}
bool TestRunner::ShouldDumpFrameLoadCallbacks() const {
return test_is_running_ &&
web_test_runtime_flags_.dump_frame_load_callbacks();
}
void TestRunner::SetShouldDumpFrameLoadCallbacks(bool value,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_frame_load_callbacks(value);
OnWebTestRuntimeFlagsChanged(source);
}
bool TestRunner::ShouldDumpPingLoaderCallbacks() const {
return test_is_running_ &&
web_test_runtime_flags_.dump_ping_loader_callbacks();
}
bool TestRunner::ShouldDumpUserGestureInFrameLoadCallbacks() const {
return test_is_running_ &&
web_test_runtime_flags_.dump_user_gesture_in_frame_load_callbacks();
}
bool TestRunner::ShouldDumpTitleChanges() const {
return web_test_runtime_flags_.dump_title_changes();
}
bool TestRunner::ShouldDumpIconChanges() const {
return web_test_runtime_flags_.dump_icon_changes();
}
bool TestRunner::ShouldDumpBackForwardList() const {
return dump_back_forward_list_;
}
bool TestRunner::ShouldWaitUntilExternalURLLoad() const {
return web_test_runtime_flags_.wait_until_external_url_load();
}
const std::set<std::string>* TestRunner::HttpHeadersToClear() const {
return &http_headers_to_clear_;
}
bool TestRunner::ClearReferrer() const {
return clear_referrer_;
}
void TestRunner::AddLoadingFrame(blink::WebLocalFrame* frame) {
// Don't track loading the about:blank between tests
if (!test_is_running_)
return;
if (loading_frames_.empty()) {
// Don't do anything if another renderer process is already tracking the
// loading frames.
if (web_test_runtime_flags_.have_loading_frame())
return;
web_test_runtime_flags_.set_have_loading_frame(true);
auto* frame_proxy =
static_cast<WebFrameTestProxy*>(RenderFrame::FromWebFrame(frame));
OnWebTestRuntimeFlagsChanged(*frame_proxy);
}
loading_frames_.push_back(frame);
frame_will_start_load_ = false;
}
void TestRunner::RemoveLoadingFrame(blink::WebLocalFrame* frame) {
// We don't track frames that were started between tests.
if (!base::Contains(loading_frames_, frame))
return;
// We had a DCHECK checking
// web_test_runtime_flags_.have_loading_frame() here, but that led to
// flakiness due to inconsistent state management across renderers.
// See https://crbug.com/1100223 for details.
std::erase(loading_frames_, frame);
if (!loading_frames_.empty())
return;
auto* frame_proxy =
static_cast<WebFrameTestProxy*>(RenderFrame::FromWebFrame(frame));
web_test_runtime_flags_.set_have_loading_frame(false);
// Loads in between tests should not propel us into thinking that we're now
// inside the test. |main_frame_loaded_| set below is used to signal that the
// test has definitely started executing.
if (!test_is_running_)
return;
main_frame_loaded_ = true;
OnWebTestRuntimeFlagsChanged(*frame_proxy);
// No more new work after the first complete load.
work_queue_.set_frozen(true);
work_queue_.OnStatesChanged(*frame_proxy);
// Inform the work queue that any load it started is done, in case it is
// still inside ProcessWorkItem().
work_queue_.set_loading(false);
// testRunner.waitUntilDone() will pause the work queue if it is being used by
// the test, until testRunner.notifyDone() is called. However this can only be
// done once.
if (!web_test_runtime_flags_.wait_until_done() || did_notify_done_)
work_queue_.RequestWork(*frame_proxy);
}
void TestRunner::OnFrameDeactivated(WebFrameTestProxy& frame) {
if (!test_is_running_)
return;
DCHECK(frame.IsMainFrame());
RemoveMainFrame(frame);
if (frame.GetWebFrame()->IsLoading()) {
RemoveLoadingFrame(frame.GetWebFrame());
}
}
void TestRunner::OnFrameReactivated(WebFrameTestProxy& frame) {
if (!test_is_running_)
return;
DCHECK(frame.IsMainFrame());
DCHECK(!frame.GetWebFrame()->GetDocument().IsPrerendering());
if (frame.GetWebFrame()->IsLoading()) {
AddLoadingFrame(frame.GetWebFrame());
}
// A WorkQueueItem that navigates reports that it will start a load, but when
// a frame comes from the back/forward cache, it is already loaded so
// AddLoadingFrame() will not occur. This informs the system that the load is
// complete, or will in fact not start so that the TestRunner does not wait
// for this frame to end the test. At this point the frame has already had a
// chance to run script and insert further WorkQueueItems or other state that
// would delay ending the test, if it wished to.
frame_will_start_load_ = false;
AddMainFrame(frame);
if (IsFrameInMainWindow(frame.GetWebFrame())) {
work_queue_.RequestWork(frame);
}
}
void TestRunner::FinishTestIfReady(blink::WebLocalFrame& source) {
FinishTestIfReady(
*static_cast<WebFrameTestProxy*>(RenderFrame::FromWebFrame(&source)));
}
void TestRunner::FinishTestIfReady(WebFrameTestProxy& source) {
if (!test_is_running_) {
return;
}
// We don't end the test before the main frame has had a chance to load. This
// is used to ensure the main frame has had a chance to start loading. If the
// test calls testRunner.notifyDone() then we also know it has begun loading.
if (!main_frame_loaded_ && !did_notify_done_) {
return;
}
// While loading any frames, we do not end the test.
// The |frame_will_start_load_| bool is used for when the work queue has
// started a load, but it is not in |loading_frames_| yet as there is some
// time between them. We also have to check |loading_frames_| for once the
// loading is started, and because the test may start a load in other ways
// besides the work queue.
if (frame_will_start_load_ || !loading_frames_.empty()) {
return;
}
// If there are tasks in the queue still, we must wait for them before
// finishing the test.
if (work_queue_.has_items()) {
return;
}
// If waiting for testRunner.notifyDone() then we can not end the test.
if (web_test_runtime_flags_.wait_until_done() && !did_notify_done_) {
return;
}
FinishTest(source);
}
void TestRunner::TestFinishedFromSecondaryRenderer(WebFrameTestProxy& source) {
NotifyDone(source);
}
void TestRunner::ResetRendererAfterWebTest() {
WebFrameTestProxy* main_frame = FindInProcessMainWindowMainFrame();
// When the about:blank navigation happens in a new process, the new
// WebFrameTestProxy is not designated to be the "MainWindowMainFrame" one
// yet. It will be tracked later after receiving the SetTestConfiguration IPC.
if (main_frame)
main_frame->Reset();
Reset();
}
void TestRunner::AddMainFrame(WebFrameTestProxy& frame) {
main_frames_.insert(&frame);
}
void TestRunner::RemoveMainFrame(WebFrameTestProxy& frame) {
main_frames_.erase(&frame);
}
void TestRunner::PolicyDelegateDone(WebFrameTestProxy& source) {
DCHECK(web_test_runtime_flags_.wait_until_done());
FinishTest(source);
}
bool TestRunner::PolicyDelegateEnabled() const {
return web_test_runtime_flags_.policy_delegate_enabled();
}
bool TestRunner::PolicyDelegateIsPermissive() const {
return web_test_runtime_flags_.policy_delegate_is_permissive();
}
bool TestRunner::PolicyDelegateShouldNotifyDone() const {
return web_test_runtime_flags_.policy_delegate_should_notify_done();
}
void TestRunner::SetDragImage(const SkBitmap& drag_image) {
if (web_test_runtime_flags_.dump_drag_image()) {
if (drag_image_.isNull())
drag_image_ = drag_image;
}
}
bool TestRunner::ShouldDumpNavigationPolicy() const {
return web_test_runtime_flags_.dump_navigation_policy();
}
WebFrameTestProxy* TestRunner::FindInProcessMainWindowMainFrame() {
for (WebFrameTestProxy* main_frame : main_frames_) {
// Prerendering frames are marked as being in the main window but
// expect the active main frame from this method.
if (main_frame->GetWebFrame()->GetDocument().IsPrerendering()) {
continue;
}
if (IsFrameInMainWindow(main_frame->GetWebFrame()))
return main_frame;
}
return nullptr;
}
void TestRunner::WaitUntilDone(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_wait_until_done(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::NotifyDone(WebFrameTestProxy& source) {
if (!web_test_runtime_flags_.wait_until_done()) {
return;
}
if (did_notify_done_) {
return;
}
// Mark that the test has asked the test to end when the rest of our stopping
// conditions are met. Then check if we can end the test.
did_notify_done_ = true;
FinishTestIfReady(source);
}
void TestRunner::QueueBackNavigation(int how_far_back,
WebFrameTestProxy& source) {
work_queue_.AddWork(mojom::WorkItem::NewBackForward(
mojom::WorkItemBackForward::New(-how_far_back)),
source);
}
void TestRunner::QueueForwardNavigation(int how_far_forward,
WebFrameTestProxy& source) {
work_queue_.AddWork(mojom::WorkItem::NewBackForward(
mojom::WorkItemBackForward::New(how_far_forward)),
source);
}
void TestRunner::QueueReload(WebFrameTestProxy& source) {
work_queue_.AddWork(mojom::WorkItem::NewReload(mojom::WorkItemReload::New()),
source);
}
void TestRunner::QueueLoadingScript(const std::string& script,
WebFrameTestProxy& source) {
work_queue_.AddWork(mojom::WorkItem::NewLoadingScript(
mojom::WorkItemLoadingScript::New(script)),
source);
}
void TestRunner::QueueNonLoadingScript(const std::string& script,
WebFrameTestProxy& source) {
work_queue_.AddWork(mojom::WorkItem::NewNonLoadingScript(
mojom::WorkItemNonLoadingScript::New(script)),
source);
}
void TestRunner::QueueLoad(const GURL& current_url,
const std::string& relative_url,
const std::string& target,
WebFrameTestProxy& source) {
GURL full_url = current_url.Resolve(relative_url);
work_queue_.AddWork(mojom::WorkItem::NewLoad(
mojom::WorkItemLoad::New(full_url.spec(), target)),
source);
}
void TestRunner::ProcessWorkItem(mojom::WorkItemPtr work_item,
WebFrameTestProxy& source) {
work_queue_.ProcessWorkItem(std::move(work_item), source);
}
void TestRunner::ReplicateWorkQueueStates(const base::Value::Dict& values,
WebFrameTestProxy& source) {
if (!test_is_running_)
return;
work_queue_.ReplicateStates(values, source);
}
bool TestRunner::IsFrameInMainWindow(blink::WebLocalFrame* frame) {
blink::WebView* view = frame->View();
for (auto& window : main_windows_) {
if (window->GetWebView() == view)
return true;
}
return false;
}
void TestRunner::SetMainWindowAndTestConfiguration(
blink::WebLocalFrame* frame,
mojom::WebTestRunTestConfigurationPtr config) {
blink::WebView* view = frame->View();
// Add |view| into the main window collection if it isn't there already.
if (!IsFrameInMainWindow(frame)) {
main_windows_.push_back(std::make_unique<MainWindowTracker>(view, this));
}
// This may be called for a local root in the same process as another local
// root, in which case we just keep the original config, which should match.
if (test_is_running_)
return;
test_config_ = std::move(*config);
SetTestIsRunning(true);
std::string spec = GURL(test_config_.test_url).spec();
size_t path_start = spec.rfind("web_tests/");
if (path_start != std::string::npos)
spec = spec.substr(path_start);
bool is_devtools_test =
spec.find("/devtools/") != std::string::npos ||
spec.find("/inspector-protocol/") != std::string::npos;
auto* source =
static_cast<WebFrameTestProxy*>(RenderFrame::FromWebFrame(frame));
if (is_devtools_test)
SetDumpConsoleMessages(false, *source);
// In protocol mode (see TestInfo::protocol_mode), we dump layout only when
// requested by the test. In non-protocol mode, we dump layout by default
// because the layout may be the only interesting thing to the user while
// we don't dump non-human-readable binary data. In non-protocol mode, we
// still generate pixel results (though don't dump them) to let the renderer
// execute the same code regardless of the protocol mode, e.g. for ease of
// debugging a web test issue.
if (!test_config_.protocol_mode)
SetShouldDumpAsLayout(true, *source);
bool wpt_printing_test = test_config_.wpt_print_mode;
// For http/tests/loading/, which is served via httpd and becomes /loading/.
if (spec.find("/loading/") != std::string::npos)
SetShouldDumpFrameLoadCallbacks(true, *source);
if (IsWebPlatformTest(spec)) {
SetIsWebPlatformTestsMode(*source);
if (spec.find("/print/") != std::string::npos ||
spec.find("-print.html") != std::string::npos) {
wpt_printing_test = true;
}
}
if (wpt_printing_test) {
SetPrinting(*source);
view->GetSettings()->SetShouldPrintBackgrounds(true);
SetPrintingSize(kWPTPrintWidth, kWPTPrintHeight, *source);
SetPrintingMargin(kWPTPrintMargins, *source);
}
view->GetSettings()->SetV8CacheOptions(
is_devtools_test ? blink::mojom::V8CacheOptions::kNone
: blink::mojom::V8CacheOptions::kDefault);
}
blink::WebString TestRunner::GetAbsoluteWebStringFromUTF8Path(
const std::string& utf8_path) {
DCHECK(test_is_running_);
base::FilePath path = base::FilePath::FromUTF8Unsafe(utf8_path);
if (!path.IsAbsolute()) {
GURL base_url =
net::FilePathToFileURL(test_config_.current_working_directory.Append(
FILE_PATH_LITERAL("foo")));
net::FileURLToFilePath(base_url.Resolve(utf8_path), &path);
}
return blink::FilePathToWebString(path);
}
const mojom::WebTestRunTestConfiguration& TestRunner::TestConfig() const {
DCHECK(test_is_running_);
return test_config_;
}
void TestRunner::OnTestPreferencesChanged(const TestPreferences& test_prefs,
WebFrameTestProxy& frame) {
blink::WebView* web_view = frame.GetWebFrame()->View();
blink::web_pref::WebPreferences web_prefs = web_view->GetWebPreferences();
// Turns the TestPreferences into WebPreferences.
ExportWebTestSpecificPreferences(test_prefs, &web_prefs);
web_view->SetWebPreferences(web_prefs);
frame.GetWebTestControlHostRemote()->OverridePreferences(web_prefs);
}
void TestRunner::SetCustomPolicyDelegate(gin::Arguments* args,
WebFrameTestProxy& source) {
bool value;
args->GetNext(&value);
web_test_runtime_flags_.set_policy_delegate_enabled(value);
if (!args->PeekNext().IsEmpty() && args->PeekNext()->IsBoolean()) {
args->GetNext(&value);
web_test_runtime_flags_.set_policy_delegate_is_permissive(value);
}
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::WaitForPolicyDelegate(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_policy_delegate_enabled(true);
web_test_runtime_flags_.set_policy_delegate_should_notify_done(true);
web_test_runtime_flags_.set_wait_until_done(true);
OnWebTestRuntimeFlagsChanged(source);
}
int TestRunner::InProcessWindowCount() {
return main_frames_.size();
}
void TestRunner::AddOriginAccessAllowListEntry(
const std::string& source_origin,
const std::string& destination_protocol,
const std::string& destination_host,
bool allow_destination_subdomains) {
blink::WebURL url((GURL(source_origin)));
if (!url.IsValid())
return;
blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(
url, blink::WebString::FromUTF8(destination_protocol),
blink::WebString::FromUTF8(destination_host), /*destination_port=*/0,
allow_destination_subdomains
? network::mojom::CorsDomainMatchMode::kAllowSubdomains
: network::mojom::CorsDomainMatchMode::kDisallowSubdomains,
network::mojom::CorsPortMatchMode::kAllowAnyPort,
network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority);
}
void TestRunner::SetTextSubpixelPositioning(bool value) {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
// Since FontConfig doesn't provide a variable to control subpixel
// positioning, we'll fall back to setting it globally for all fonts.
blink::WebFontRenderStyle::SetSubpixelPositioning(value);
#endif
}
void TestRunner::SetMockScreenOrientation(const std::string& orientation_str,
WebFrameTestProxy& frame) {
display::mojom::ScreenOrientation orientation;
if (orientation_str == "portrait-primary") {
orientation = display::mojom::ScreenOrientation::kPortraitPrimary;
} else if (orientation_str == "portrait-secondary") {
orientation = display::mojom::ScreenOrientation::kPortraitSecondary;
} else if (orientation_str == "landscape-primary") {
orientation = display::mojom::ScreenOrientation::kLandscapePrimary;
} else {
DCHECK_EQ("landscape-secondary", orientation_str);
orientation = display::mojom::ScreenOrientation::kLandscapeSecondary;
}
bool changed = fake_screen_orientation_impl_.UpdateDeviceOrientation(
frame.GetWebView(), orientation);
if (changed) {
frame.GetWebTestControlHostRemote()->SimulateScreenOrientationChanged();
}
}
void TestRunner::DisableMockScreenOrientation(blink::WebView* view) {
fake_screen_orientation_impl_.SetDisabled(view, true);
}
void TestRunner::DumpEditingCallbacks(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_editting_callbacks(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpAsMarkup(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_as_markup(true);
web_test_runtime_flags_.set_generate_pixel_results(false);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpAsText(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_as_text(true);
web_test_runtime_flags_.set_generate_pixel_results(false);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpAsTextWithPixelResults(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_as_text(true);
web_test_runtime_flags_.set_generate_pixel_results(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpAsLayout(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_as_layout(true);
web_test_runtime_flags_.set_generate_pixel_results(false);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpAsLayoutWithPixelResults(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_as_layout(true);
web_test_runtime_flags_.set_generate_pixel_results(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpChildFrames(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_child_frames(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpIconChanges(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_icon_changes(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetAudioData(const gin::ArrayBufferView& view) {
uint8_t* bytes = static_cast<uint8_t*>(view.bytes());
audio_data_.resize(view.num_bytes());
std::copy(bytes, bytes + view.num_bytes(), audio_data_.begin());
dump_as_audio_ = true;
}
void TestRunner::DumpFrameLoadCallbacks(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_frame_load_callbacks(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpPingLoaderCallbacks(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_ping_loader_callbacks(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpUserGestureInFrameLoadCallbacks(
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_user_gesture_in_frame_load_callbacks(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpTitleChanges(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_title_changes(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetStorageAllowed(bool allowed, WebFrameTestProxy& source) {
web_test_runtime_flags_.set_storage_allowed(allowed);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetAllowRunningOfInsecureContent(bool allowed,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_running_insecure_content_allowed(allowed);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpPermissionClientCallbacks(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_web_content_settings_client_callbacks(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpBackForwardList() {
dump_back_forward_list_ = true;
}
void TestRunner::DumpSelectionRect(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_selection_rect(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetPrinting(WebFrameTestProxy& source) {
SetPrintingForFrame("", source);
}
void TestRunner::SetPrintingForFrame(const std::string& frame_name,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_printing_frame(frame_name);
web_test_runtime_flags_.set_is_printing(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetPrintingSize(int width,
int height,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_printing_width(width);
web_test_runtime_flags_.set_printing_height(height);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetPrintingMargin(int size, WebFrameTestProxy& source) {
web_test_runtime_flags_.set_printing_margin(size);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetShouldStayOnPageAfterHandlingBeforeUnload(
bool value,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_stay_on_page_after_handling_before_unload(value);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetWillSendRequestClearHeader(const std::string& header) {
if (!header.empty())
http_headers_to_clear_.insert(header);
}
void TestRunner::SetWillSendRequestClearReferrer() {
clear_referrer_ = true;
}
void TestRunner::WaitUntilExternalURLLoad(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_wait_until_external_url_load(true);
web_test_runtime_flags_.set_wait_until_done(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpDragImage(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_drag_image(true);
DumpAsTextWithPixelResults(source);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::DumpNavigationPolicy(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_navigation_policy(true);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetDumpConsoleMessages(bool value, WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_console_messages(value);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetIsWebPlatformTestsMode(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_is_web_platform_tests_mode(true);
OnWebTestRuntimeFlagsChanged(source);
}
bool TestRunner::IsWebPlatformTestsMode() const {
return web_test_runtime_flags_.is_web_platform_tests_mode();
}
void TestRunner::SetDumpJavaScriptDialogs(bool value,
WebFrameTestProxy& source) {
web_test_runtime_flags_.set_dump_javascript_dialogs(value);
OnWebTestRuntimeFlagsChanged(source);
}
void TestRunner::SetEffectiveConnectionType(
blink::WebEffectiveConnectionType connection_type) {
effective_connection_type_ = connection_type;
}
bool TestRunner::ShouldDumpConsoleMessages() const {
// Once TestFinished() is entered, we don't want additional log lines to
// be printed while we collect the renderer-side test results, so we check
// |test_is_running_| here as well.
return test_is_running_ && web_test_runtime_flags_.dump_console_messages();
}
void TestRunner::PrintMessage(const std::string& message,
WebFrameTestProxy& source) {
source.GetWebTestControlHostRemote()->PrintMessage(message);
}
blink::WebString TestRunner::RegisterIsolatedFileSystem(
const std::vector<base::FilePath>& file_paths,
WebFrameTestProxy& source) {
std::string filesystem_id;
source.GetWebTestControlHostRemote()->RegisterIsolatedFileSystem(
file_paths, &filesystem_id);
return blink::WebString::FromUTF8(filesystem_id);
}
void TestRunner::FocusWindow(RenderFrame* main_frame, bool focus) {
// Early out instead of CHECK() to avoid poking the fuzzer bear.
if (!main_frame->IsMainFrame())
return;
auto* frame_proxy = static_cast<WebFrameTestProxy*>(main_frame);
blink::WebFrameWidget* widget = frame_proxy->GetLocalRootWebFrameWidget();
// Web tests get multiple windows in one renderer by doing same-site
// window.open() calls (or about:blank). They want to be able to move focus
// between those windows synchronously in the renderer, which is what we
// do here. We only allow it to focus main frames however, for simplicitly.
if (!focus) {
// This path simulates losing focus on the window, without moving it to
// another window.
if (widget->HasFocus()) {
auto* web_view = frame_proxy->GetWebFrame()->View();
// TODO(dtapuska): We should call the exact IPC the browser
// calls. ie. WebFrameWidgetImpl::SetActive but that isn't
// exposed outside of blink.
web_view->SetIsActive(false);
widget->SetFocus(false);
}
return;
}
// Find the currently focused window, and remove its focus.
for (WebFrameTestProxy* other_main_frame : main_frames_) {
if (other_main_frame != main_frame) {
blink::WebFrameWidget* other_widget =
other_main_frame->GetLocalRootWebFrameWidget();
if (other_widget->HasFocus()) {
auto* other_web_view = other_main_frame->GetWebFrame()->View();
// TODO(dtapuska): We should call the exact IPC the browser
// calls. ie. WebFrameWidgetImpl::SetActive but that isn't
// exposed outside of blink.
other_web_view->SetIsActive(false);
other_widget->SetFocus(false);
}
}
}
if (!widget->HasFocus()) {
widget->SetFocus(true);
}
}
void TestRunner::SetAnimationRequiresRaster(bool do_raster) {
animation_requires_raster_ = do_raster;
}
void TestRunner::OnWebTestRuntimeFlagsChanged(WebFrameTestProxy& source) {
// Ignore changes that happen before we got the initial, accumulated
// web flag changes in SetTestConfiguration().
if (!test_is_running_)
return;
if (web_test_runtime_flags_.tracked_dictionary().changed_values().empty())
return;
source.GetWebTestControlHostRemote()->WebTestRuntimeFlagsChanged(
web_test_runtime_flags_.tracked_dictionary().changed_values().Clone());
web_test_runtime_flags_.tracked_dictionary().ResetChangeTracking();
}
void TestRunner::FinishTest(WebFrameTestProxy& source) {
WebFrameTestProxy* main_frame = FindInProcessMainWindowMainFrame();
// When there are no more frames loading, and the test hasn't asked to wait
// for NotifyDone(), then we normally conclude the test. However if this
// TestRunner is attached to a swapped out frame tree - that is, the main
// frame is in another frame tree - then finishing here would be premature
// for the main frame where the test is running. If |did_notify_done_| is
// true then we *were* waiting for NotifyDone() and it has already happened,
// so we want to proceed as if the NotifyDone() is happening now.
//
// Ideally, the main frame would wait for loading frames in its frame tree
// as well as any secondary renderers, but it does not know about secondary
// renderers. So in this case the test should finish when frames finish
// loading in the primary renderer, and we don't finish the test from a
// secondary renderer unless it is asked for explicitly via NotifyDone.
//
// This will bounce through the browser to the renderer process hosting the
// main window's main frame. There it will come back to this method, but hit
// the other path.
if (!main_frame) {
if (did_notify_done_) {
source.GetWebTestControlHostRemote()->TestFinishedInSecondaryRenderer();
}
return;
}
// Avoid a situation where TestFinished is called twice, because
// of a racey test where multiple renderers call notifyDone(), or a test that
// calls notifyDone() more than once.
if (!test_is_running_) {
return;
}
test_is_running_ = false;
// Now we know that we're in the main frame, we should generate dump results.
// Clean out the lifecycle if needed before capturing the web tree
// dump and pixels from the compositor.
auto* web_frame = main_frame->GetWebFrame();
web_frame->FrameWidget()->PrepareForFinalLifecyclUpdateForTesting();
web_frame->FrameWidget()->UpdateAllLifecyclePhases(
blink::DocumentUpdateReason::kTest);
// Initialize a new dump results object which we will populate in the calls
// below.
auto dump_result = mojom::WebTestRendererDumpResult::New();
bool browser_should_dump_back_forward_list = ShouldDumpBackForwardList();
bool browser_should_dump_pixels = false;
if (ShouldDumpAsAudio()) {
TRACE_EVENT0("shell", "TestRunner::CaptureLocalAudioDump");
dump_result->audio = GetAudioData();
} else {
TextResultType text_result_type = ShouldGenerateTextResults();
bool pixel_result = ShouldGeneratePixelResults();
std::string spec = GURL(test_config_.test_url).spec();
size_t path_start = spec.rfind("web_tests/");
if (path_start != std::string::npos)
spec = spec.substr(path_start);
std::string mime_type =
web_frame->GetDocumentLoader()->GetWebResponse().MimeType().Utf8();
// In a text/plain document, and in a dumpAsText/ subdirectory, we generate
// text results no matter what the test may previously have requested.
if (mime_type == "text/plain" ||
spec.find("/dumpAsText/") != std::string::npos) {
text_result_type = TextResultType::kText;
pixel_result = false;
}
// If possible we grab the layout dump locally because a round trip through
// the browser would give javascript a chance to run and change the layout.
// We only go to the browser if we can not do it locally, because we want to
// dump more than just the local main frame. Those tests must be written to
// not modify layout after signalling the test is finished.
//
// The CustomTextDump always takes precedence if it's been specified by the
// test.
std::string custom_text_dump;
if (HasCustomTextDump(&custom_text_dump)) {
dump_result->layout = custom_text_dump + "\n";
} else if (!IsRecursiveLayoutDumpRequested()) {
TRACE_EVENT0("shell", "TestRunner::CaptureLocalLayoutDump");
dump_result->layout = DumpLayoutAsString(web_frame, text_result_type);
}
if (pixel_result) {
if (CanDumpPixelsFromRenderer()) {
TRACE_EVENT0("shell", "TestRunner::CaptureLocalPixelsDump");
SkBitmap actual = DumpPixelsInRenderer(web_frame);
DCHECK_GT(actual.info().width(), 0);
DCHECK_GT(actual.info().height(), 0);
base::MD5Digest digest;
auto bytes = base::span(static_cast<const uint8_t*>(actual.getPixels()),
actual.computeByteSize());
base::MD5Sum(bytes, &digest);
dump_result->actual_pixel_hash = base::MD5DigestToBase16(digest);
if (dump_result->actual_pixel_hash != test_config_.expected_pixel_hash)
dump_result->pixels = std::move(actual);
} else {
browser_should_dump_pixels = true;
if (ShouldDumpSelectionRect()) {
TRACE_EVENT0("shell", "TestRunner::CaptureLocalSelectionRect");
dump_result->selection_rect =
web_frame->GetSelectionBoundsRectForTesting();
}
}
}
}
// Informs the browser that the test is done, passing along any test results
// that have been generated locally. The browser may collect further results
// from this and other renderer processes before moving on to the next test.
source.GetWebTestControlHostRemote()->InitiateCaptureDump(
std::move(dump_result), browser_should_dump_back_forward_list,
browser_should_dump_pixels);
}
mojom::WebTestBluetoothFakeAdapterSetter&
TestRunner::GetBluetoothFakeAdapterSetter() {
if (!bluetooth_fake_adapter_setter_) {
RenderThread::Get()->BindHostReceiver(
bluetooth_fake_adapter_setter_.BindNewPipeAndPassReceiver());
bluetooth_fake_adapter_setter_.set_disconnect_handler(base::BindOnce(
&TestRunner::HandleBluetoothFakeAdapterSetterDisconnected,
base::Unretained(this)));
}
return *bluetooth_fake_adapter_setter_;
}
void TestRunner::HandleBluetoothFakeAdapterSetterDisconnected() {
bluetooth_fake_adapter_setter_.reset();
}
void TestRunner::DisableAutomaticDragDrop(WebFrameTestProxy& source) {
web_test_runtime_flags_.set_auto_drag_drop_enabled(false);
OnWebTestRuntimeFlagsChanged(source);
}
bool TestRunner::AutomaticDragDropEnabled() {
return web_test_runtime_flags_.auto_drag_drop_enabled();
}
const WebTestRuntimeFlags& TestRunner::GetFlags() {
return web_test_runtime_flags_;
}
} // namespace content