blob: ac8066f453c6eb3ffa3b9a151ebd5dbe53831614 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_WEB_TEST_BROWSER_WEB_TEST_CONTROL_HOST_H_
#define CONTENT_WEB_TEST_BROWSER_WEB_TEST_CONTROL_HOST_H_
#include <map>
#include <memory>
#include <ostream>
#include <queue>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/cancelable_callback.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "base/sequence_checker.h"
#include "base/synchronization/lock.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/public/browser/bluetooth_chooser.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/gpu_data_manager_observer.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/web_test/browser/leak_detector.h"
#include "content/web_test/browser/web_test_tracing_controller.h"
#include "content/web_test/common/web_test.mojom.h"
#include "content/web_test/common/web_test_runtime_flags.h"
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/lcp_critical_path_predictor/lcp_critical_path_predictor.mojom.h"
#include "ui/gfx/geometry/size.h"
class SkBitmap;
namespace content {
class DevToolsProtocolTestBindings;
class RenderFrameHost;
class Shell;
class WebTestBluetoothChooserFactory;
class WebTestDevToolsBindings;
struct TestInfo;
class WebTestResultPrinter {
public:
WebTestResultPrinter(std::ostream* output, std::ostream* error);
WebTestResultPrinter(const WebTestResultPrinter&) = delete;
WebTestResultPrinter& operator=(const WebTestResultPrinter&) = delete;
~WebTestResultPrinter() = default;
void reset() { state_ = DURING_TEST; }
bool output_finished() const { return state_ == AFTER_TEST; }
void set_capture_text_only(bool capture_text_only) {
capture_text_only_ = capture_text_only;
}
void set_encode_binary_data(bool encode_binary_data) {
encode_binary_data_ = encode_binary_data;
}
void PrintTextHeader();
void PrintTextBlock(const std::string& block);
void PrintTextFooter();
void PrintImageHeader(const std::string& actual_hash,
const std::string& expected_hash);
void PrintImageBlock(const std::vector<unsigned char>& png_image);
void PrintImageFooter();
void PrintAudioHeader();
void PrintAudioBlock(const std::vector<unsigned char>& audio_data);
void PrintAudioFooter();
void AddMessageToStderr(const std::string& message);
void AddMessage(const std::string& message);
void AddMessageRaw(const std::string& message);
void AddErrorMessage(const std::string& message);
void CloseStderr();
void StartStateDump();
private:
enum State {
DURING_TEST,
DURING_STATE_DUMP,
IN_TEXT_BLOCK,
IN_AUDIO_BLOCK,
IN_IMAGE_BLOCK,
AFTER_TEST
};
void PrintEncodedBinaryData(const std::vector<unsigned char>& data);
const raw_ptr<std::ostream> output_;
const raw_ptr<std::ostream> error_;
State state_ = DURING_TEST;
bool capture_text_only_ = false;
bool encode_binary_data_ = false;
};
class WebTestControlHost : public WebContentsObserver,
public RenderProcessHostObserver,
public GpuDataManagerObserver,
public mojom::WebTestControlHost,
public mojom::NonAssociatedWebTestControlHost {
public:
static WebTestControlHost* Get();
WebTestControlHost();
~WebTestControlHost() override;
WebTestControlHost(const WebTestControlHost&) = delete;
WebTestControlHost& operator=(const WebTestControlHost&) = delete;
void PrepareForWebTest(const TestInfo& test_info);
void ResetBrowserAfterWebTest();
// Allows WebTestControlHost to track all WebContents created by tests, either
// by Javascript or by C++ code in the browser.
void DidCreateOrAttachWebContents(WebContents* web_contents);
void SetTempPath(const base::FilePath& temp_path);
void OverrideWebkitPrefs(blink::web_pref::WebPreferences* prefs);
void OpenURL(const GURL& url);
bool IsMainWindow(WebContents* web_contents) const;
std::unique_ptr<BluetoothChooser> RunBluetoothChooser(
RenderFrameHost* frame,
const BluetoothChooser::EventHandler& event_handler);
void RequestPointerLock(WebContents* web_contents);
WebTestResultPrinter* printer() { return printer_.get(); }
void set_printer(WebTestResultPrinter* printer) { printer_.reset(printer); }
// WebTestControlHost implementation.
void PrintMessageToStderr(const std::string& message) override;
void DevToolsProcessCrashed();
// Called when a renderer wants to bind a connection to the
// WebTestControlHost.
void BindWebTestControlHostForRenderer(
int render_process_id,
mojo::PendingAssociatedReceiver<mojom::WebTestControlHost> receiver);
void BindNonAssociatedWebTestControlHost(
mojo::PendingReceiver<mojom::NonAssociatedWebTestControlHost> receiver);
const WebTestRuntimeFlags& web_test_runtime_flags() const {
return web_test_runtime_flags_;
}
private:
enum TestPhase { BETWEEN_TESTS, DURING_TEST, CLEAN_UP };
// Node structure to construct a RenderFrameHost tree.
struct Node {
explicit Node(RenderFrameHost* host);
~Node();
Node(Node&& other);
Node& operator=(Node&& other);
raw_ptr<RenderFrameHost, AcrossTasksDanglingUntriaged> render_frame_host =
nullptr;
GlobalRenderFrameHostId render_frame_host_id;
std::vector<raw_ptr<Node, VectorExperimental>> children;
};
class WebTestWindowObserver;
// WebContentsObserver implementation.
void PluginCrashed(const base::FilePath& plugin_path,
base::ProcessId plugin_pid) override;
void TitleWasSet(NavigationEntry* entry) override;
void DidFailLoad(RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code) override;
void WebContentsDestroyed() override;
void DidUpdateFaviconURL(
RenderFrameHost* render_frame_host,
const std::vector<blink::mojom::FaviconURLPtr>& candidates) override;
void RenderFrameHostChanged(RenderFrameHost* old_host,
RenderFrameHost* new_host) override;
void RenderViewDeleted(RenderViewHost* render_view_host) override;
void DidStartNavigation(NavigationHandle* navigation_handle) override;
void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override;
void DidFinishNavigation(NavigationHandle* navigation) override;
// RenderProcessHostObserver implementation.
void RenderProcessHostDestroyed(
RenderProcessHost* render_process_host) override;
void RenderProcessExited(RenderProcessHost* render_process_host,
const ChildProcessTerminationInfo& info) override;
// GpuDataManagerObserver implementation.
void OnGpuProcessCrashed() override;
// WebTestControlHost implementation.
void InitiateCaptureDump(
mojom::WebTestRendererDumpResultPtr renderer_dump_result,
bool capture_navigation_history,
bool capture_pixels) override;
void TestFinishedInSecondaryRenderer() override;
void PrintMessage(const std::string& message) override;
void Reload() override;
void OverridePreferences(
const blink::web_pref::WebPreferences& web_preferences) override;
void SetMainWindowHidden(bool hidden) override;
void SetFrameWindowHidden(const blink::LocalFrameToken& frame_token,
bool hidden) override;
void CheckForLeakedWindows() override;
void GoToOffset(int offset) override;
void SendBluetoothManualChooserEvent(const std::string& event,
const std::string& argument) override;
void SetBluetoothManualChooser(bool enable) override;
void GetBluetoothManualChooserEvents(
GetBluetoothManualChooserEventsCallback reply) override;
void SetPopupBlockingEnabled(bool block_popups) override;
void LoadURLForFrame(const GURL& url, const std::string& frame_name) override;
void SimulateScreenOrientationChanged() override;
void SetPermission(const std::string& name,
blink::mojom::PermissionStatus status,
const GURL& origin,
const GURL& embedding_origin) override;
void BlockThirdPartyCookies(bool block) override;
void GetWritableDirectory(GetWritableDirectoryCallback reply) override;
void SetFilePathForMockFileDialog(const base::FilePath& path) override;
void FocusDevtoolsSecondaryWindow() override;
void SetTrustTokenKeyCommitments(const std::string& raw_commitments,
base::OnceClosure callback) override;
void ClearTrustTokenState(base::OnceClosure callback) override;
void SetDatabaseQuota(int32_t quota) override;
void ClearAllDatabases() override;
void SimulateWebNotificationClick(
const std::string& title,
int32_t action_index,
const std::optional<std::u16string>& reply) override;
void SimulateWebNotificationClose(const std::string& title,
bool by_user) override;
void SimulateWebContentIndexDelete(const std::string& id) override;
void WebTestRuntimeFlagsChanged(
base::Value::Dict changed_web_test_runtime_flags) override;
void RegisterIsolatedFileSystem(
const std::vector<base::FilePath>& file_paths,
RegisterIsolatedFileSystemCallback callback) override;
void DropPointerLock() override;
void SetPointerLockWillFail() override;
void SetPointerLockWillRespondAsynchronously() override;
void AllowPointerLock() override;
void WorkItemAdded(mojom::WorkItemPtr work_item) override;
void RequestWorkItem() override;
void WorkQueueStatesChanged(
base::Value::Dict changed_work_queue_states) override;
void SetAcceptLanguages(const std::string& accept_languages) override;
void EnableAutoResize(const gfx::Size& min_size,
const gfx::Size& max_size) override;
void DisableAutoResize(const gfx::Size& new_size) override;
void SetLCPPNavigationHint(
blink::mojom::LCPCriticalPathPredictorNavigationTimeHintPtr hint)
override;
// Sets the Protocol Handler Registry in automation mode to avoid the
// permission prompt in tests.
void SetRegisterProtocolHandlerMode(
mojom::WebTestControlHost::AutoResponseMode mode) override;
void DiscardMainWindow();
void FlushInputAndStartTest(WeakDocumentPtr rfh);
// Closes all windows opened by the test. This is every window but the main
// window, since it is created by the test harness and reused between tests.
void CloseTestOpenedWindows();
// Closes all windows, including the main window.
void CloseAllWindows();
// Makes sure that the potentially new renderer associated with |frame| is 1)
// initialized for the test, 2) kept up to date wrt test flags and 3)
// monitored for crashes.
void HandleNewRenderFrameHost(RenderFrameHost* frame);
// Message handlers.
void OnAudioDump(const std::vector<unsigned char>& audio_dump);
void OnImageDump(const std::string& actual_pixel_hash, const SkBitmap& image);
void OnTextDump(const std::string& dump);
void OnDumpFrameLayoutResponse(int frame_tree_node_id,
const std::string& dump);
void OnTestFinished();
void OnCaptureSessionHistory();
void OnLeakDetectionDone(int pid,
const LeakDetector::LeakDetectionReport& report);
// In between two tests, do some cleanup. Navigate to about:blank and
// request blink to reset states.
//
// Note: If the current document had COOP:same-origin defined, the navigation
// to about:blank will have to happen in a different browsing context group.
// The process used might be a different one.
void PrepareRendererForNextWebTest();
void PrepareRendererForNextWebTestDone();
void OnPixelDumpCaptured(const SkBitmap& snapshot);
void ReportResults();
void EnqueueSurfaceCopyRequest();
// Returns the WebContents associated with `frame_token`. Must only be called
// when processing messages received on the mojom::WebTestControlHost
// interface from renderer processes. `frame_token` must correspond to a valid
// RenderFrameHost.
// The return value can not be null.
WebContents* GetWebContentsFromCurrentContext(
const blink::LocalFrameToken& frame_token);
mojo::AssociatedRemote<mojom::WebTestRenderFrame>&
GetWebTestRenderFrameRemote(RenderFrameHost* frame);
void HandleWebTestRenderFrameRemoteError(const GlobalRenderFrameHostId& key);
// CompositeAllFramesThen() first builds a frame tree based on
// frame->GetParent(). Then, it builds a queue of frames in depth-first order,
// so that compositing happens from the leaves up. Finally,
// CompositeNodeQueueThen() is used to composite one frame at a time,
// asynchronously, continuing on to the next frame once each composite
// finishes. Once all nodes have been composited, the final callback is run.
// Each call to CompositeWithRaster() is an asynchronous Mojo call, to avoid
// reentrancy problems.
void CompositeAllFramesThen(base::OnceCallback<void()> callback);
Node* BuildFrameTree(WebContents* web_contents);
void CompositeNodeQueueThen(base::OnceCallback<void()> callback);
void BuildDepthFirstQueue(Node* node);
#if BUILDFLAG(IS_MAC)
// Bypasses system APIs to force a resize on the RenderWidgetHostView when in
// headless web tests.
static void PlatformResizeWindowMac(Shell* shell, const gfx::Size& size);
#endif
static WebTestControlHost* instance_;
std::unique_ptr<WebTestResultPrinter> printer_;
base::FilePath current_working_directory_;
base::FilePath temp_path_;
raw_ptr<Shell> main_window_ = nullptr;
raw_ptr<Shell, DanglingUntriaged> secondary_window_ = nullptr;
std::unique_ptr<WebTestDevToolsBindings> devtools_bindings_;
std::unique_ptr<DevToolsProtocolTestBindings>
devtools_protocol_test_bindings_;
// What phase of running an individual test we are currently in.
TestPhase test_phase_ = BETWEEN_TESTS;
// Per test config.
std::string expected_pixel_hash_;
GURL test_url_;
bool wpt_print_mode_;
bool protocol_mode_ = false;
// Stores the default test-adapted WebPreferences which is then used to fully
// reset the main window's preferences if and when it is reused.
blink::web_pref::WebPreferences default_prefs_;
std::string default_accept_languages_;
// True if the WebPreferences of newly created RenderViewHost should be
// overridden with prefs_.
bool should_override_prefs_ = false;
blink::web_pref::WebPreferences prefs_;
// When populated, simulate a LCPP backend by sending this hint data along
// navigations (typically reload of the same page).
// This is set by the LCPP web_tests via
// NonAssociatedWebTestControlHost::SetLCPPNavigationHint mojom interface.
// This is reset before switching to the next test page.
std::optional<blink::mojom::LCPCriticalPathPredictorNavigationTimeHint>
lcpp_hint_;
bool crash_when_leak_found_ = false;
std::unique_ptr<LeakDetector> leak_detector_;
std::unique_ptr<WebTestBluetoothChooserFactory> bluetooth_chooser_factory_;
// Observe windows opened by tests.
base::flat_map<WebContents*, std::unique_ptr<WebTestWindowObserver>>
test_opened_window_observers_;
// Renderer processes are observed to detect crashes.
base::ScopedMultiSourceObservation<RenderProcessHost,
RenderProcessHostObserver>
render_process_host_observations_{this};
std::set<raw_ptr<RenderProcessHost, SetExperimental>>
all_observed_render_process_hosts_;
std::set<raw_ptr<RenderProcessHost, SetExperimental>>
main_window_render_process_hosts_;
std::set<raw_ptr<RenderViewHost, SetExperimental>>
main_window_render_view_hosts_;
// Changes reported by WebTestRuntimeFlagsChanged() that have accumulated
// since PrepareForWebTest (i.e. changes that need to be sent to a fresh
// renderer created while test is in progress).
base::Value::Dict accumulated_web_test_runtime_flags_changes_;
// A snasphot of the current runtime flags.
WebTestRuntimeFlags web_test_runtime_flags_;
// Work items to be processed in the TestRunner on the renderer process
// that hosts the main window's main frame.
base::circular_deque<mojom::WorkItemPtr> work_queue_;
// Properties of the work queue.
base::Value::Dict work_queue_states_;
mojom::WebTestRendererDumpResultPtr renderer_dump_result_;
std::string navigation_history_dump_;
std::optional<SkBitmap> pixel_dump_;
std::optional<std::string> layout_dump_;
std::string actual_pixel_hash_;
// By default a test that opens other windows will have them closed at the end
// of the test before checking for leaks. It may specify that it has closed
// any windows it opened, and thus look for leaks from them with this flag.
bool check_for_leaked_windows_ = false;
bool waiting_for_pixel_results_ = false;
int waiting_for_layout_dumps_ = 0;
// Map from frame_tree_node_id into frame-specific dumps while collecting
// text dumps from all frames, before stitching them together.
std::map<int, std::string> frame_to_layout_dump_map_;
std::vector<std::unique_ptr<Node>> composite_all_frames_node_storage_;
std::queue<raw_ptr<Node, CtnExperimental>> composite_all_frames_node_queue_;
// Map from one frame to one mojo pipe.
std::map<GlobalRenderFrameHostId,
mojo::AssociatedRemote<mojom::WebTestRenderFrame>>
web_test_render_frame_map_;
// The set of bindings that receive messages on the mojom::WebTestControlHost
// interface from renderer processes. There should be one per renderer
// process, and we store it with the |render_process_id| attached to it
// so that we can tell where a given message came from if needed.
mojo::AssociatedReceiverSet<mojom::WebTestControlHost,
int /*render_process_id*/>
receiver_bindings_;
mojo::ReceiverSet<mojom::NonAssociatedWebTestControlHost>
non_associated_receiver_bindings_;
base::ScopedTempDir writable_directory_for_tests_;
std::optional<WebTestTracingController> tracing_controller_;
enum class NextPointerLockAction {
kWillSucceed,
kTestWillRespond,
kWillFail,
};
NextPointerLockAction next_pointer_lock_action_ =
NextPointerLockAction::kWillSucceed;
// When navigating to a new web test, the control host blocks the renderer
// from starting the test while some initialization to a known state occurs
// (e.g. flushing synthetic input events associated with navigation).
//
// It does so by resetting this bit to `true` at the end of each web test.
// When this bit is set, the next ReadyToCommitNavigation seen will call
// BlockTestUntilStart in the renderer to block the renderer parser,
// preventing the test from running. When that navigation finishes in
// DidFinishNavigation, this bit is turned off and this class performs
// initialization. Once the initialization is complete, StartTest is run in
// the renderer, unblocking the parser.
bool next_non_blank_nav_is_new_test_ = true;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<WebTestControlHost> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_WEB_TEST_BROWSER_WEB_TEST_CONTROL_HOST_H_