| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/shell/renderer/layout_test/blink_test_runner.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <clocale> |
| #include <cmath> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/debug/debugger.h" |
| #include "base/files/file_path.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/md5.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/plugins/renderer/plugin_placeholder.h" |
| #include "content/common/content_switches_internal.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/service_names.mojom.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/common/web_preferences.h" |
| #include "content/public/renderer/media_stream_utils.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "content/public/renderer/render_view.h" |
| #include "content/public/renderer/render_view_visitor.h" |
| #include "content/public/renderer/renderer_gamepad_provider.h" |
| #include "content/public/test/layouttest_support.h" |
| #include "content/shell/common/layout_test/layout_test_messages.h" |
| #include "content/shell/common/shell_messages.h" |
| #include "content/shell/common/shell_switches.h" |
| #include "content/shell/renderer/layout_test/blink_test_helpers.h" |
| #include "content/shell/renderer/layout_test/layout_test_render_thread_observer.h" |
| #include "content/shell/renderer/layout_test/leak_detector.h" |
| #include "content/shell/test_runner/app_banner_service.h" |
| #include "content/shell/test_runner/gamepad_controller.h" |
| #include "content/shell/test_runner/layout_and_paint_async_then.h" |
| #include "content/shell/test_runner/pixel_dump.h" |
| #include "content/shell/test_runner/web_test_interfaces.h" |
| #include "content/shell/test_runner/web_test_runner.h" |
| #include "content/shell/test_runner/web_view_test_proxy.h" |
| #include "media/base/audio_capturer_source.h" |
| #include "media/base/audio_parameters.h" |
| #include "media/capture/video_capturer_source.h" |
| #include "media/media_features.h" |
| #include "net/base/filename_util.h" |
| #include "net/base/net_errors.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "services/service_manager/public/cpp/interface_registry.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "third_party/WebKit/public/platform/FilePathConversion.h" |
| #include "third_party/WebKit/public/platform/Platform.h" |
| #include "third_party/WebKit/public/platform/WebInputEvent.h" |
| #include "third_party/WebKit/public/platform/WebPoint.h" |
| #include "third_party/WebKit/public/platform/WebRect.h" |
| #include "third_party/WebKit/public/platform/WebSize.h" |
| #include "third_party/WebKit/public/platform/WebString.h" |
| #include "third_party/WebKit/public/platform/WebThread.h" |
| #include "third_party/WebKit/public/platform/WebTraceLocation.h" |
| #include "third_party/WebKit/public/platform/WebURL.h" |
| #include "third_party/WebKit/public/platform/WebURLError.h" |
| #include "third_party/WebKit/public/platform/WebURLRequest.h" |
| #include "third_party/WebKit/public/platform/WebURLResponse.h" |
| #include "third_party/WebKit/public/platform/modules/app_banner/app_banner.mojom.h" |
| #include "third_party/WebKit/public/web/WebArrayBufferView.h" |
| #include "third_party/WebKit/public/web/WebContextMenuData.h" |
| #include "third_party/WebKit/public/web/WebDataSource.h" |
| #include "third_party/WebKit/public/web/WebDevToolsAgent.h" |
| #include "third_party/WebKit/public/web/WebDocument.h" |
| #include "third_party/WebKit/public/web/WebElement.h" |
| #include "third_party/WebKit/public/web/WebFrame.h" |
| #include "third_party/WebKit/public/web/WebFrameWidget.h" |
| #include "third_party/WebKit/public/web/WebHistoryItem.h" |
| #include "third_party/WebKit/public/web/WebKit.h" |
| #include "third_party/WebKit/public/web/WebLeakDetector.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| #include "third_party/WebKit/public/web/WebScriptSource.h" |
| #include "third_party/WebKit/public/web/WebTestingSupport.h" |
| #include "third_party/WebKit/public/web/WebView.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/icc_profile.h" |
| |
| using blink::Platform; |
| using blink::WebArrayBufferView; |
| using blink::WebContextMenuData; |
| using blink::WebDevToolsAgent; |
| using device::MotionData; |
| using device::OrientationData; |
| using blink::WebElement; |
| using blink::WebLocalFrame; |
| using blink::WebHistoryItem; |
| using blink::WebFrame; |
| using blink::WebLocalFrame; |
| using blink::WebPoint; |
| using blink::WebRect; |
| using blink::WebScriptSource; |
| using blink::WebSize; |
| using blink::WebString; |
| using blink::WebURL; |
| using blink::WebURLError; |
| using blink::WebURLRequest; |
| using blink::WebTestingSupport; |
| using blink::WebTraceLocation; |
| using blink::WebThread; |
| using blink::WebVector; |
| using blink::WebView; |
| |
| namespace content { |
| |
| namespace { |
| |
| class SyncNavigationStateVisitor : public RenderViewVisitor { |
| public: |
| SyncNavigationStateVisitor() {} |
| ~SyncNavigationStateVisitor() override {} |
| |
| bool Visit(RenderView* render_view) override { |
| SyncNavigationState(render_view); |
| return true; |
| } |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SyncNavigationStateVisitor); |
| }; |
| |
| class UseSynchronousResizeModeVisitor : public RenderViewVisitor { |
| public: |
| explicit UseSynchronousResizeModeVisitor(bool enable) : enable_(enable) {} |
| ~UseSynchronousResizeModeVisitor() override {} |
| |
| bool Visit(RenderView* render_view) override { |
| UseSynchronousResizeMode(render_view, enable_); |
| return true; |
| } |
| |
| private: |
| bool enable_; |
| }; |
| |
| class MockGamepadProvider : public RendererGamepadProvider { |
| public: |
| explicit MockGamepadProvider(test_runner::GamepadController* controller) |
| : RendererGamepadProvider(nullptr), controller_(controller) {} |
| ~MockGamepadProvider() override { |
| StopIfObserving(); |
| } |
| |
| // RendererGamepadProvider implementation. |
| void SampleGamepads(blink::WebGamepads& gamepads) override { |
| controller_->SampleGamepads(gamepads); |
| } |
| void Start(blink::WebPlatformEventListener* listener) override { |
| controller_->SetListener(static_cast<blink::WebGamepadListener*>(listener)); |
| RendererGamepadProvider::Start(listener); |
| } |
| void SendStartMessage() override {} |
| void SendStopMessage() override {} |
| |
| private: |
| std::unique_ptr<test_runner::GamepadController> controller_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockGamepadProvider); |
| }; |
| |
| class MockVideoCapturerSource : public media::VideoCapturerSource { |
| public: |
| MockVideoCapturerSource() = default; |
| ~MockVideoCapturerSource() override {} |
| |
| void GetCurrentSupportedFormats( |
| int max_requested_width, |
| int max_requested_height, |
| double max_requested_frame_rate, |
| const VideoCaptureDeviceFormatsCB& callback) override { |
| const int supported_width = 640; |
| const int supported_height = 480; |
| const float supported_framerate = 60.0; |
| callback.Run(media::VideoCaptureFormats( |
| 1, media::VideoCaptureFormat( |
| gfx::Size(supported_width, supported_height), |
| supported_framerate, media::PIXEL_FORMAT_I420))); |
| } |
| void StartCapture(const media::VideoCaptureParams& params, |
| const VideoCaptureDeliverFrameCB& new_frame_callback, |
| const RunningCallback& running_callback) override { |
| running_callback.Run(true); |
| } |
| void StopCapture() override {} |
| }; |
| |
| class MockAudioCapturerSource : public media::AudioCapturerSource { |
| public: |
| MockAudioCapturerSource() = default; |
| |
| void Initialize(const media::AudioParameters& params, |
| CaptureCallback* callback, |
| int session_id) override {} |
| void Start() override {} |
| void Stop() override {} |
| void SetVolume(double volume) override {} |
| void SetAutomaticGainControl(bool enable) override {} |
| |
| protected: |
| ~MockAudioCapturerSource() override {} |
| }; |
| |
| // Tests in csswg-test use absolute path links such as |
| // <script src="/resources/testharness.js">. |
| // Because we load the tests as local files, such links don't work. |
| // This function fixes this issue by rewriting file: URLs which were produced |
| // from such links so that they point actual files in LayoutTests/resources/. |
| // |
| // Note that this isn't applied to external/wpt because tests in external/wpt |
| // are accessed via http. |
| WebURL RewriteAbsolutePathInCsswgTest(const std::string& utf8_url) { |
| const char kFileScheme[] = "file:///"; |
| const int kFileSchemeLen = arraysize(kFileScheme) - 1; |
| if (utf8_url.compare(0, kFileSchemeLen, kFileScheme, kFileSchemeLen) != 0) |
| return WebURL(); |
| if (utf8_url.find("/LayoutTests/") != std::string::npos) |
| return WebURL(); |
| #if defined(OS_WIN) |
| // +3 for a drive letter, :, and /. |
| const int kFileSchemeAndDriveLen = kFileSchemeLen + 3; |
| if (utf8_url.size() <= kFileSchemeAndDriveLen) |
| return WebURL(); |
| std::string path = utf8_url.substr(kFileSchemeAndDriveLen); |
| #else |
| std::string path = utf8_url.substr(kFileSchemeLen); |
| #endif |
| base::FilePath new_path = |
| LayoutTestRenderThreadObserver::GetInstance() |
| ->webkit_source_dir() |
| .Append(FILE_PATH_LITERAL("LayoutTests/")) |
| .AppendASCII(path); |
| return WebURL(net::FilePathToFileURL(new_path)); |
| } |
| |
| } // namespace |
| |
| BlinkTestRunner::BlinkTestRunner(RenderView* render_view) |
| : RenderViewObserver(render_view), |
| RenderViewObserverTracker<BlinkTestRunner>(render_view), |
| is_main_window_(false), |
| focus_on_next_commit_(false), |
| leak_detector_(new LeakDetector(this)) { |
| } |
| |
| BlinkTestRunner::~BlinkTestRunner() { |
| } |
| |
| // WebTestDelegate ----------------------------------------------------------- |
| |
| void BlinkTestRunner::ClearEditCommand() { |
| render_view()->ClearEditCommands(); |
| } |
| |
| void BlinkTestRunner::SetEditCommand(const std::string& name, |
| const std::string& value) { |
| render_view()->SetEditCommandForNextKeyEvent(name, value); |
| } |
| |
| void BlinkTestRunner::SetGamepadProvider( |
| test_runner::GamepadController* controller) { |
| std::unique_ptr<MockGamepadProvider> provider( |
| new MockGamepadProvider(controller)); |
| SetMockGamepadProvider(std::move(provider)); |
| } |
| |
| void BlinkTestRunner::SetDeviceLightData(const double data) { |
| SetMockDeviceLightData(data); |
| } |
| |
| void BlinkTestRunner::SetDeviceMotionData(const MotionData& data) { |
| SetMockDeviceMotionData(data); |
| } |
| |
| void BlinkTestRunner::SetDeviceOrientationData(const OrientationData& data) { |
| SetMockDeviceOrientationData(data); |
| } |
| |
| void BlinkTestRunner::PrintMessageToStderr(const std::string& message) { |
| Send(new ShellViewHostMsg_PrintMessageToStderr(routing_id(), message)); |
| } |
| |
| void BlinkTestRunner::PrintMessage(const std::string& message) { |
| Send(new ShellViewHostMsg_PrintMessage(routing_id(), message)); |
| } |
| |
| void BlinkTestRunner::PostTask(const base::Closure& task) { |
| Platform::current()->currentThread()->getSingleThreadTaskRunner()->PostTask( |
| FROM_HERE, task); |
| } |
| |
| void BlinkTestRunner::PostDelayedTask(const base::Closure& task, long long ms) { |
| Platform::current() |
| ->currentThread() |
| ->getSingleThreadTaskRunner() |
| ->PostDelayedTask(FROM_HERE, task, base::TimeDelta::FromMilliseconds(ms)); |
| } |
| |
| WebString BlinkTestRunner::RegisterIsolatedFileSystem( |
| const blink::WebVector<blink::WebString>& absolute_filenames) { |
| std::vector<base::FilePath> files; |
| for (size_t i = 0; i < absolute_filenames.size(); ++i) |
| files.push_back(blink::WebStringToFilePath(absolute_filenames[i])); |
| std::string filesystem_id; |
| Send(new LayoutTestHostMsg_RegisterIsolatedFileSystem( |
| routing_id(), files, &filesystem_id)); |
| return WebString::fromUTF8(filesystem_id); |
| } |
| |
| long long BlinkTestRunner::GetCurrentTimeInMillisecond() { |
| return base::TimeDelta(base::Time::Now() - |
| base::Time::UnixEpoch()).ToInternalValue() / |
| base::Time::kMicrosecondsPerMillisecond; |
| } |
| |
| WebString BlinkTestRunner::GetAbsoluteWebStringFromUTF8Path( |
| const std::string& utf8_path) { |
| 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); |
| } |
| |
| WebURL BlinkTestRunner::LocalFileToDataURL(const WebURL& file_url) { |
| base::FilePath local_path; |
| if (!net::FileURLToFilePath(file_url, &local_path)) |
| return WebURL(); |
| |
| std::string contents; |
| Send(new LayoutTestHostMsg_ReadFileToString( |
| routing_id(), local_path, &contents)); |
| |
| std::string contents_base64; |
| base::Base64Encode(contents, &contents_base64); |
| |
| const char data_url_prefix[] = "data:text/css:charset=utf-8;base64,"; |
| return WebURL(GURL(data_url_prefix + contents_base64)); |
| } |
| |
| WebURL BlinkTestRunner::RewriteLayoutTestsURL(const std::string& utf8_url, |
| bool is_wpt_mode) { |
| if (is_wpt_mode) { |
| WebURL rewritten_url = RewriteAbsolutePathInCsswgTest(utf8_url); |
| if (!rewritten_url.isEmpty()) |
| return rewritten_url; |
| return WebURL(GURL(utf8_url)); |
| } |
| |
| const char kGenPrefix[] = "file:///gen/"; |
| const int kGenPrefixLen = arraysize(kGenPrefix) - 1; |
| |
| // Map "file:///gen/" to "file://<build directory>/gen/". |
| if (!utf8_url.compare(0, kGenPrefixLen, kGenPrefix, kGenPrefixLen)) { |
| base::FilePath gen_directory_path = |
| test_config_->build_directory.Append(FILE_PATH_LITERAL("gen/")); |
| std::string new_url = std::string("file://") + |
| gen_directory_path.AsUTF8Unsafe() + |
| utf8_url.substr(kGenPrefixLen); |
| return WebURL(GURL(new_url)); |
| } |
| |
| const char kPrefix[] = "file:///tmp/LayoutTests/"; |
| const int kPrefixLen = arraysize(kPrefix) - 1; |
| |
| if (utf8_url.compare(0, kPrefixLen, kPrefix, kPrefixLen)) |
| return WebURL(GURL(utf8_url)); |
| |
| base::FilePath replace_path = |
| LayoutTestRenderThreadObserver::GetInstance()->webkit_source_dir() |
| .Append(FILE_PATH_LITERAL("LayoutTests/")); |
| std::string utf8_path = replace_path.AsUTF8Unsafe(); |
| std::string new_url = |
| std::string("file://") + utf8_path + utf8_url.substr(kPrefixLen); |
| return WebURL(GURL(new_url)); |
| } |
| |
| test_runner::TestPreferences* BlinkTestRunner::Preferences() { |
| return &prefs_; |
| } |
| |
| void BlinkTestRunner::ApplyPreferences() { |
| WebPreferences prefs = render_view()->GetWebkitPreferences(); |
| ExportLayoutTestSpecificPreferences(prefs_, &prefs); |
| render_view()->SetWebkitPreferences(prefs); |
| Send(new ShellViewHostMsg_OverridePreferences(routing_id(), prefs)); |
| } |
| |
| std::string BlinkTestRunner::makeURLErrorDescription(const WebURLError& error) { |
| std::string domain = error.domain.utf8(); |
| int code = error.reason; |
| |
| if (domain == net::kErrorDomain) { |
| domain = "NSURLErrorDomain"; |
| switch (error.reason) { |
| case net::ERR_ABORTED: |
| code = -999; // NSURLErrorCancelled |
| break; |
| case net::ERR_UNSAFE_PORT: |
| // Our unsafe port checking happens at the network stack level, but we |
| // make this translation here to match the behavior of stock WebKit. |
| domain = "WebKitErrorDomain"; |
| code = 103; |
| break; |
| case net::ERR_ADDRESS_INVALID: |
| case net::ERR_ADDRESS_UNREACHABLE: |
| case net::ERR_NETWORK_ACCESS_DENIED: |
| code = -1004; // NSURLErrorCannotConnectToHost |
| break; |
| } |
| } else { |
| DLOG(WARNING) << "Unknown error domain"; |
| } |
| |
| return base::StringPrintf("<NSError domain %s, code %d, failing URL \"%s\">", |
| domain.c_str(), code, error.unreachableURL.string().utf8().data()); |
| } |
| |
| void BlinkTestRunner::UseUnfortunateSynchronousResizeMode(bool enable) { |
| UseSynchronousResizeModeVisitor visitor(enable); |
| RenderView::ForEach(&visitor); |
| } |
| |
| void BlinkTestRunner::EnableAutoResizeMode(const WebSize& min_size, |
| const WebSize& max_size) { |
| content::EnableAutoResizeMode(render_view(), min_size, max_size); |
| } |
| |
| void BlinkTestRunner::DisableAutoResizeMode(const WebSize& new_size) { |
| content::DisableAutoResizeMode(render_view(), new_size); |
| if (!new_size.isEmpty()) |
| ForceResizeRenderView(render_view(), new_size); |
| } |
| |
| void BlinkTestRunner::ClearDevToolsLocalStorage() { |
| Send(new ShellViewHostMsg_ClearDevToolsLocalStorage(routing_id())); |
| } |
| |
| void BlinkTestRunner::ShowDevTools(const std::string& settings, |
| const std::string& frontend_url) { |
| Send(new ShellViewHostMsg_ShowDevTools( |
| routing_id(), settings, frontend_url)); |
| } |
| |
| void BlinkTestRunner::CloseDevTools() { |
| Send(new ShellViewHostMsg_CloseDevTools(routing_id())); |
| WebDevToolsAgent* agent = |
| render_view()->GetMainRenderFrame()->GetWebFrame()->devToolsAgent(); |
| if (agent) |
| agent->detach(); |
| } |
| |
| void BlinkTestRunner::EvaluateInWebInspector(int call_id, |
| const std::string& script) { |
| Send(new ShellViewHostMsg_EvaluateInDevTools( |
| routing_id(), call_id, script)); |
| } |
| |
| std::string BlinkTestRunner::EvaluateInWebInspectorOverlay( |
| const std::string& script) { |
| WebDevToolsAgent* agent = |
| render_view()->GetMainRenderFrame()->GetWebFrame()->devToolsAgent(); |
| if (!agent) |
| return std::string(); |
| |
| return agent->evaluateInWebInspectorOverlay( |
| WebString::fromUTF8(script)).utf8(); |
| } |
| |
| void BlinkTestRunner::ClearAllDatabases() { |
| Send(new LayoutTestHostMsg_ClearAllDatabases(routing_id())); |
| } |
| |
| void BlinkTestRunner::SetDatabaseQuota(int quota) { |
| Send(new LayoutTestHostMsg_SetDatabaseQuota(routing_id(), quota)); |
| } |
| |
| void BlinkTestRunner::SimulateWebNotificationClick( |
| const std::string& title, |
| int action_index, |
| const base::NullableString16& reply) { |
| Send(new LayoutTestHostMsg_SimulateWebNotificationClick(routing_id(), title, |
| action_index, reply)); |
| } |
| |
| void BlinkTestRunner::SimulateWebNotificationClose(const std::string& title, |
| bool by_user) { |
| Send(new LayoutTestHostMsg_SimulateWebNotificationClose(routing_id(), title, |
| by_user)); |
| } |
| |
| void BlinkTestRunner::SetDeviceScaleFactor(float factor) { |
| content::SetDeviceScaleFactor(render_view(), factor); |
| } |
| |
| float BlinkTestRunner::GetWindowToViewportScale() { |
| return content::GetWindowToViewportScale(render_view()); |
| } |
| |
| std::unique_ptr<blink::WebInputEvent> |
| BlinkTestRunner::TransformScreenToWidgetCoordinates( |
| test_runner::WebWidgetTestProxyBase* web_widget_test_proxy_base, |
| const blink::WebInputEvent& event) { |
| return content::TransformScreenToWidgetCoordinates(web_widget_test_proxy_base, |
| event); |
| } |
| |
| test_runner::WebWidgetTestProxyBase* BlinkTestRunner::GetWebWidgetTestProxyBase( |
| blink::WebLocalFrame* frame) { |
| return content::GetWebWidgetTestProxyBase(frame); |
| } |
| |
| void BlinkTestRunner::EnableUseZoomForDSF() { |
| base::CommandLine::ForCurrentProcess()-> |
| AppendSwitch(switches::kEnableUseZoomForDSF); |
| } |
| |
| bool BlinkTestRunner::IsUseZoomForDSFEnabled() { |
| return content::IsUseZoomForDSFEnabled(); |
| } |
| |
| void BlinkTestRunner::SetDeviceColorProfile(const std::string& name) { |
| content::SetDeviceColorProfile(render_view(), GetTestingICCProfile(name)); |
| } |
| |
| void BlinkTestRunner::SetBluetoothFakeAdapter(const std::string& adapter_name, |
| const base::Closure& callback) { |
| GetBluetoothFakeAdapterSetter().Set(adapter_name, callback); |
| } |
| |
| void BlinkTestRunner::SetBluetoothManualChooser(bool enable) { |
| Send(new ShellViewHostMsg_SetBluetoothManualChooser(routing_id(), enable)); |
| } |
| |
| void BlinkTestRunner::GetBluetoothManualChooserEvents( |
| const base::Callback<void(const std::vector<std::string>&)>& callback) { |
| get_bluetooth_events_callbacks_.push_back(callback); |
| Send(new ShellViewHostMsg_GetBluetoothManualChooserEvents(routing_id())); |
| } |
| |
| void BlinkTestRunner::SendBluetoothManualChooserEvent( |
| const std::string& event, |
| const std::string& argument) { |
| Send(new ShellViewHostMsg_SendBluetoothManualChooserEvent(routing_id(), event, |
| argument)); |
| } |
| |
| void BlinkTestRunner::SetFocus(blink::WebView* web_view, bool focus) { |
| RenderView* render_view = RenderView::FromWebView(web_view); |
| if (render_view) // Check whether |web_view| has been already closed. |
| SetFocusAndActivate(render_view, focus); |
| } |
| |
| void BlinkTestRunner::SetBlockThirdPartyCookies(bool block) { |
| Send(new LayoutTestHostMsg_BlockThirdPartyCookies(routing_id(), block)); |
| } |
| |
| std::string BlinkTestRunner::PathToLocalResource(const std::string& resource) { |
| #if defined(OS_WIN) |
| if (base::StartsWith(resource, "/tmp/", base::CompareCase::SENSITIVE)) { |
| // We want a temp file. |
| GURL base_url = net::FilePathToFileURL(test_config_->temp_path); |
| return base_url.Resolve(resource.substr(sizeof("/tmp/") - 1)).spec(); |
| } |
| #endif |
| |
| // Some layout tests use file://// which we resolve as a UNC path. Normalize |
| // them to just file:///. |
| std::string result = resource; |
| static const size_t kFileLen = sizeof("file:///") - 1; |
| while (base::StartsWith(base::ToLowerASCII(result), "file:////", |
| base::CompareCase::SENSITIVE)) { |
| result = result.substr(0, kFileLen) + result.substr(kFileLen + 1); |
| } |
| return RewriteLayoutTestsURL(result, false /* is_wpt_mode */).string().utf8(); |
| } |
| |
| void BlinkTestRunner::SetLocale(const std::string& locale) { |
| setlocale(LC_ALL, locale.c_str()); |
| } |
| |
| void BlinkTestRunner::OnLayoutTestRuntimeFlagsChanged( |
| const base::DictionaryValue& changed_values) { |
| // Ignore changes that happen before we got the initial, accumulated |
| // layout flag changes in ShellViewMsg_ReplicateTestConfiguration. |
| if (!is_main_window_) |
| return; |
| |
| RenderThread::Get()->Send( |
| new LayoutTestHostMsg_LayoutTestRuntimeFlagsChanged(changed_values)); |
| } |
| |
| void BlinkTestRunner::TestFinished() { |
| if (!is_main_window_ || !render_view()->GetMainRenderFrame()) { |
| RenderThread::Get()->Send( |
| new LayoutTestHostMsg_TestFinishedInSecondaryRenderer()); |
| return; |
| } |
| test_runner::WebTestInterfaces* interfaces = |
| LayoutTestRenderThreadObserver::GetInstance()->test_interfaces(); |
| interfaces->SetTestIsRunning(false); |
| if (interfaces->TestRunner()->ShouldDumpBackForwardList()) { |
| SyncNavigationStateVisitor visitor; |
| RenderView::ForEach(&visitor); |
| Send(new ShellViewHostMsg_CaptureSessionHistory(routing_id())); |
| } else { |
| // clean out the lifecycle if needed before capturing the layout tree |
| // dump and pixels from the compositor. |
| render_view()->GetWebView()->mainFrame()->toWebLocalFrame() |
| ->frameWidget()->updateAllLifecyclePhases(); |
| CaptureDump(); |
| } |
| } |
| |
| void BlinkTestRunner::CloseRemainingWindows() { |
| Send(new ShellViewHostMsg_CloseRemainingWindows(routing_id())); |
| } |
| |
| void BlinkTestRunner::DeleteAllCookies() { |
| Send(new LayoutTestHostMsg_DeleteAllCookies(routing_id())); |
| } |
| |
| int BlinkTestRunner::NavigationEntryCount() { |
| return GetLocalSessionHistoryLength(render_view()); |
| } |
| |
| void BlinkTestRunner::GoToOffset(int offset) { |
| Send(new ShellViewHostMsg_GoToOffset(routing_id(), offset)); |
| } |
| |
| void BlinkTestRunner::Reload() { |
| Send(new ShellViewHostMsg_Reload(routing_id())); |
| } |
| |
| void BlinkTestRunner::LoadURLForFrame(const WebURL& url, |
| const std::string& frame_name) { |
| Send(new ShellViewHostMsg_LoadURLForFrame( |
| routing_id(), url, frame_name)); |
| } |
| |
| bool BlinkTestRunner::AllowExternalPages() { |
| return test_config_->allow_external_pages; |
| } |
| |
| std::string BlinkTestRunner::DumpHistoryForWindow(blink::WebView* web_view) { |
| size_t pos = 0; |
| std::vector<int>::iterator id; |
| for (id = routing_ids_.begin(); id != routing_ids_.end(); ++id, ++pos) { |
| RenderView* render_view = RenderView::FromRoutingID(*id); |
| if (!render_view) { |
| NOTREACHED(); |
| continue; |
| } |
| if (render_view->GetWebView() == web_view) |
| break; |
| } |
| |
| if (id == routing_ids_.end()) { |
| NOTREACHED(); |
| return std::string(); |
| } |
| return DumpBackForwardList(session_histories_[pos], |
| current_entry_indexes_[pos]); |
| } |
| |
| void BlinkTestRunner::FetchManifest( |
| blink::WebView* view, |
| const GURL& url, |
| const base::Callback<void(const blink::WebURLResponse& response, |
| const std::string& data)>& callback) { |
| ::content::FetchManifest(view, url, callback); |
| } |
| |
| void BlinkTestRunner::SetPermission(const std::string& name, |
| const std::string& value, |
| const GURL& origin, |
| const GURL& embedding_origin) { |
| blink::mojom::PermissionStatus status; |
| if (value == "granted") { |
| status = blink::mojom::PermissionStatus::GRANTED; |
| } else if (value == "prompt") { |
| status = blink::mojom::PermissionStatus::ASK; |
| } else if (value == "denied") { |
| status = blink::mojom::PermissionStatus::DENIED; |
| } else { |
| NOTREACHED(); |
| status = blink::mojom::PermissionStatus::DENIED; |
| } |
| |
| Send(new LayoutTestHostMsg_SetPermission( |
| routing_id(), name, status, origin, embedding_origin)); |
| } |
| |
| void BlinkTestRunner::ResetPermissions() { |
| Send(new LayoutTestHostMsg_ResetPermissions(routing_id())); |
| } |
| |
| cc::SharedBitmapManager* BlinkTestRunner::GetSharedBitmapManager() { |
| return RenderThread::Get()->GetSharedBitmapManager(); |
| } |
| |
| void BlinkTestRunner::DispatchBeforeInstallPromptEvent( |
| const std::vector<std::string>& event_platforms, |
| const base::Callback<void(bool)>& callback) { |
| app_banner_service_.reset(new test_runner::AppBannerService()); |
| |
| service_manager::InterfaceRegistry::TestApi test_api( |
| render_view()->GetMainRenderFrame()->GetInterfaceRegistry()); |
| test_api.GetLocalInterface( |
| mojo::MakeRequest(&app_banner_service_->controller())); |
| |
| app_banner_service_->SendBannerPromptRequest(event_platforms, callback); |
| } |
| |
| void BlinkTestRunner::ResolveBeforeInstallPromptPromise( |
| const std::string& platform) { |
| if (app_banner_service_) { |
| app_banner_service_->ResolvePromise(platform); |
| app_banner_service_.reset(nullptr); |
| } |
| } |
| |
| blink::WebPlugin* BlinkTestRunner::CreatePluginPlaceholder( |
| blink::WebLocalFrame* frame, const blink::WebPluginParams& params) { |
| if (params.mimeType != "application/x-plugin-placeholder-test") |
| return nullptr; |
| |
| plugins::PluginPlaceholder* placeholder = |
| new plugins::PluginPlaceholder(render_view()->GetMainRenderFrame(), frame, |
| params, "<div>Test content</div>"); |
| return placeholder->plugin(); |
| } |
| |
| float BlinkTestRunner::GetDeviceScaleFactor() const { |
| return render_view()->GetDeviceScaleFactor(); |
| } |
| |
| void BlinkTestRunner::RunIdleTasks(const base::Closure& callback) { |
| SchedulerRunIdleTasks(callback); |
| } |
| |
| void BlinkTestRunner::ForceTextInputStateUpdate(WebFrame* frame) { |
| ForceTextInputStateUpdateForRenderFrame(RenderFrame::FromWebFrame(frame)); |
| } |
| |
| bool BlinkTestRunner::IsNavigationInitiatedByRenderer( |
| const WebURLRequest& request) { |
| return content::IsNavigationInitiatedByRenderer(request); |
| } |
| |
| bool BlinkTestRunner::AddMediaStreamVideoSourceAndTrack( |
| blink::WebMediaStream* stream) { |
| DCHECK(stream); |
| #if BUILDFLAG(ENABLE_WEBRTC) |
| return AddVideoTrackToMediaStream(base::MakeUnique<MockVideoCapturerSource>(), |
| false, // is_remote |
| stream); |
| #else |
| return false; |
| #endif |
| } |
| |
| bool BlinkTestRunner::AddMediaStreamAudioSourceAndTrack( |
| blink::WebMediaStream* stream) { |
| DCHECK(stream); |
| #if BUILDFLAG(ENABLE_WEBRTC) |
| return AddAudioTrackToMediaStream( |
| make_scoped_refptr(new MockAudioCapturerSource()), |
| 48000, // sample rate |
| media::CHANNEL_LAYOUT_STEREO, |
| 480, // sample frames per buffer |
| false, // is_remote |
| stream); |
| #else |
| return false; |
| #endif |
| } |
| |
| // RenderViewObserver -------------------------------------------------------- |
| |
| void BlinkTestRunner::DidClearWindowObject(WebLocalFrame* frame) { |
| WebTestingSupport::injectInternalsObject(frame); |
| } |
| |
| bool BlinkTestRunner::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(BlinkTestRunner, message) |
| IPC_MESSAGE_HANDLER(ShellViewMsg_SessionHistory, OnSessionHistory) |
| IPC_MESSAGE_HANDLER(ShellViewMsg_Reset, OnReset) |
| IPC_MESSAGE_HANDLER(ShellViewMsg_TestFinishedInSecondaryRenderer, |
| OnTestFinishedInSecondaryRenderer) |
| IPC_MESSAGE_HANDLER(ShellViewMsg_TryLeakDetection, OnTryLeakDetection) |
| IPC_MESSAGE_HANDLER(ShellViewMsg_ReplyBluetoothManualChooserEvents, |
| OnReplyBluetoothManualChooserEvents) |
| IPC_MESSAGE_HANDLER(ShellViewMsg_LayoutDumpCompleted, OnLayoutDumpCompleted) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| return handled; |
| } |
| |
| void BlinkTestRunner::Navigate(const GURL& url) { |
| focus_on_next_commit_ = true; |
| } |
| |
| void BlinkTestRunner::DidCommitProvisionalLoad(WebLocalFrame* frame, |
| bool is_new_navigation) { |
| if (!focus_on_next_commit_) |
| return; |
| focus_on_next_commit_ = false; |
| render_view()->GetWebView()->setFocusedFrame(frame); |
| } |
| |
| void BlinkTestRunner::DidFailProvisionalLoad(WebLocalFrame* frame, |
| const WebURLError& error) { |
| focus_on_next_commit_ = false; |
| } |
| |
| // Public methods - ----------------------------------------------------------- |
| |
| void BlinkTestRunner::Reset(bool for_new_test) { |
| prefs_.Reset(); |
| routing_ids_.clear(); |
| session_histories_.clear(); |
| current_entry_indexes_.clear(); |
| |
| render_view()->ClearEditCommands(); |
| if (for_new_test) { |
| if (render_view()->GetWebView()->mainFrame()->isWebLocalFrame()) |
| render_view()->GetWebView()->mainFrame()->setName(WebString()); |
| render_view()->GetWebView()->mainFrame()->clearOpener(); |
| } |
| |
| // Resetting the internals object also overrides the WebPreferences, so we |
| // have to sync them to WebKit again. |
| if (render_view()->GetWebView()->mainFrame()->isWebLocalFrame()) { |
| WebTestingSupport::resetInternalsObject( |
| render_view()->GetWebView()->mainFrame()->toWebLocalFrame()); |
| render_view()->SetWebkitPreferences(render_view()->GetWebkitPreferences()); |
| } |
| } |
| |
| // Private methods ----------------------------------------------------------- |
| |
| void BlinkTestRunner::CaptureDump() { |
| test_runner::WebTestInterfaces* interfaces = |
| LayoutTestRenderThreadObserver::GetInstance()->test_interfaces(); |
| TRACE_EVENT0("shell", "BlinkTestRunner::CaptureDump"); |
| |
| if (interfaces->TestRunner()->ShouldDumpAsAudio()) { |
| std::vector<unsigned char> vector_data; |
| interfaces->TestRunner()->GetAudioData(&vector_data); |
| Send(new ShellViewHostMsg_AudioDump(routing_id(), vector_data)); |
| CaptureDumpContinued(); |
| return; |
| } |
| |
| std::string custom_text_dump; |
| if (interfaces->TestRunner()->HasCustomTextDump(&custom_text_dump)) { |
| Send(new ShellViewHostMsg_TextDump(routing_id(), custom_text_dump + "\n")); |
| CaptureDumpContinued(); |
| return; |
| } |
| |
| if (!interfaces->TestRunner()->IsRecursiveLayoutDumpRequested()) { |
| std::string layout_dump = interfaces->TestRunner()->DumpLayout( |
| render_view()->GetMainRenderFrame()->GetWebFrame()); |
| OnLayoutDumpCompleted(std::move(layout_dump)); |
| return; |
| } |
| |
| Send(new ShellViewHostMsg_InitiateLayoutDump(routing_id())); |
| // OnLayoutDumpCompleted will be eventually called by an IPC from the browser. |
| } |
| |
| void BlinkTestRunner::OnLayoutDumpCompleted(std::string completed_layout_dump) { |
| test_runner::WebTestInterfaces* interfaces = |
| LayoutTestRenderThreadObserver::GetInstance()->test_interfaces(); |
| if (interfaces->TestRunner()->ShouldDumpBackForwardList()) { |
| for (WebView* web_view : interfaces->GetWindowList()) |
| completed_layout_dump.append(DumpHistoryForWindow(web_view)); |
| } |
| |
| Send(new ShellViewHostMsg_TextDump(routing_id(), |
| std::move(completed_layout_dump))); |
| |
| CaptureDumpContinued(); |
| } |
| |
| void BlinkTestRunner::CaptureDumpContinued() { |
| test_runner::WebTestInterfaces* interfaces = |
| LayoutTestRenderThreadObserver::GetInstance()->test_interfaces(); |
| if (test_config_->enable_pixel_dumping && |
| interfaces->TestRunner()->ShouldGeneratePixelResults() && |
| !interfaces->TestRunner()->ShouldDumpAsAudio()) { |
| CHECK(render_view()->GetWebView()->isAcceleratedCompositingActive()); |
| interfaces->TestRunner()->DumpPixelsAsync( |
| render_view()->GetWebView(), |
| base::Bind(&BlinkTestRunner::OnPixelsDumpCompleted, |
| base::Unretained(this))); |
| return; |
| } |
| |
| CaptureDumpComplete(); |
| } |
| |
| void BlinkTestRunner::OnPixelsDumpCompleted(const SkBitmap& snapshot) { |
| DCHECK_NE(0, snapshot.info().width()); |
| DCHECK_NE(0, snapshot.info().height()); |
| |
| SkAutoLockPixels snapshot_lock(snapshot); |
| // The snapshot arrives from the GPU process via shared memory. Because MSan |
| // can't track initializedness across processes, we must assure it that the |
| // pixels are in fact initialized. |
| MSAN_UNPOISON(snapshot.getPixels(), snapshot.getSize()); |
| base::MD5Digest digest; |
| base::MD5Sum(snapshot.getPixels(), snapshot.getSize(), &digest); |
| std::string actual_pixel_hash = base::MD5DigestToBase16(digest); |
| |
| if (actual_pixel_hash == test_config_->expected_pixel_hash) { |
| SkBitmap empty_image; |
| Send(new ShellViewHostMsg_ImageDump( |
| routing_id(), actual_pixel_hash, empty_image)); |
| } else { |
| Send(new ShellViewHostMsg_ImageDump( |
| routing_id(), actual_pixel_hash, snapshot)); |
| } |
| |
| CaptureDumpComplete(); |
| } |
| |
| void BlinkTestRunner::CaptureDumpComplete() { |
| render_view()->GetWebView()->mainFrame()->stopLoading(); |
| |
| Send(new ShellViewHostMsg_TestFinished(routing_id())); |
| } |
| |
| mojom::LayoutTestBluetoothFakeAdapterSetter& |
| BlinkTestRunner::GetBluetoothFakeAdapterSetter() { |
| if (!bluetooth_fake_adapter_setter_) { |
| RenderThread::Get()->GetConnector()->BindInterface( |
| mojom::kBrowserServiceName, |
| mojo::MakeRequest(&bluetooth_fake_adapter_setter_)); |
| } |
| return *bluetooth_fake_adapter_setter_; |
| } |
| |
| void BlinkTestRunner::OnSetupSecondaryRenderer() { |
| DCHECK(!is_main_window_); |
| |
| test_runner::WebTestInterfaces* interfaces = |
| LayoutTestRenderThreadObserver::GetInstance()->test_interfaces(); |
| interfaces->SetTestIsRunning(true); |
| interfaces->ConfigureForTestWithURL(GURL(), false); |
| ForceResizeRenderView(render_view(), WebSize(800, 600)); |
| } |
| |
| void BlinkTestRunner::OnReplicateTestConfiguration( |
| mojom::ShellTestConfigurationPtr params) { |
| test_runner::WebTestInterfaces* interfaces = |
| LayoutTestRenderThreadObserver::GetInstance()->test_interfaces(); |
| |
| test_config_ = params.Clone(); |
| |
| is_main_window_ = true; |
| interfaces->SetMainView(render_view()->GetWebView()); |
| |
| interfaces->SetTestIsRunning(true); |
| interfaces->ConfigureForTestWithURL(params->test_url, |
| params->enable_pixel_dumping); |
| } |
| |
| void BlinkTestRunner::OnSetTestConfiguration( |
| mojom::ShellTestConfigurationPtr params) { |
| mojom::ShellTestConfigurationPtr local_params = params.Clone(); |
| OnReplicateTestConfiguration(std::move(params)); |
| |
| ForceResizeRenderView(render_view(), |
| WebSize(local_params->initial_size.width(), |
| local_params->initial_size.height())); |
| LayoutTestRenderThreadObserver::GetInstance() |
| ->test_interfaces() |
| ->TestRunner() |
| ->SetFocus(render_view()->GetWebView(), true); |
| } |
| |
| void BlinkTestRunner::OnSessionHistory( |
| const std::vector<int>& routing_ids, |
| const std::vector<std::vector<PageState>>& session_histories, |
| const std::vector<unsigned>& current_entry_indexes) { |
| routing_ids_ = routing_ids; |
| session_histories_ = session_histories; |
| current_entry_indexes_ = current_entry_indexes; |
| CaptureDump(); |
| } |
| |
| void BlinkTestRunner::OnReset() { |
| LayoutTestRenderThreadObserver::GetInstance()->test_interfaces()->ResetAll(); |
| Reset(true /* for_new_test */); |
| // Navigating to about:blank will make sure that no new loads are initiated |
| // by the renderer. |
| WebURLRequest request = WebURLRequest(GURL(url::kAboutBlankURL)); |
| render_view()->GetWebView()->mainFrame()->loadRequest(request); |
| Send(new ShellViewHostMsg_ResetDone(routing_id())); |
| } |
| |
| void BlinkTestRunner::OnTestFinishedInSecondaryRenderer() { |
| DCHECK(is_main_window_ && render_view()->GetMainRenderFrame()); |
| TestFinished(); |
| } |
| |
| void BlinkTestRunner::OnTryLeakDetection() { |
| blink::WebFrame* main_frame = |
| render_view()->GetWebView()->mainFrame(); |
| DCHECK_EQ(GURL(url::kAboutBlankURL), GURL(main_frame->document().url())); |
| DCHECK(!main_frame->isLoading()); |
| |
| leak_detector_->TryLeakDetection(main_frame); |
| } |
| |
| void BlinkTestRunner::OnReplyBluetoothManualChooserEvents( |
| const std::vector<std::string>& events) { |
| DCHECK(!get_bluetooth_events_callbacks_.empty()); |
| base::Callback<void(const std::vector<std::string>&)> callback = |
| get_bluetooth_events_callbacks_.front(); |
| get_bluetooth_events_callbacks_.pop_front(); |
| callback.Run(events); |
| } |
| |
| void BlinkTestRunner::ReportLeakDetectionResult( |
| const LeakDetectionResult& report) { |
| Send(new ShellViewHostMsg_LeakDetectionDone(routing_id(), report)); |
| } |
| |
| void BlinkTestRunner::OnDestruct() { |
| delete this; |
| } |
| |
| } // namespace content |