blob: b8e3903a915369e9dd8cf721a06b3bcd04bae9ff [file] [log] [blame]
// Copyright 2012 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_PUBLIC_TEST_BROWSER_TEST_UTILS_H_
#define CONTENT_PUBLIC_TEST_BROWSER_TEST_UTILS_H_
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/template_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/types/strong_alias.h"
#include "base/types/to_address.h"
#include "build/build_config.h"
#include "cc/test/pixel_test_utils.h"
#include "content/public/browser/commit_deferring_condition.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_frame_metadata_provider.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_media_capture_id.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/common/page_type.h"
#include "content/public/test/test_utils.h"
#include "ipc/message_filter.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/base/load_flags.h"
#include "net/cookies/cookie_options.h"
#include "net/cookies/cookie_partition_key_collection.h"
#include "services/network/public/mojom/cookie_manager.mojom-forward.h"
#include "storage/common/file_system/file_system_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/blob/blob_utils.h"
#include "third_party/blink/public/common/context_menu_data/untrustworthy_context_menu_params.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
#include "third_party/blink/public/mojom/frame/user_activation_update_types.mojom.h"
#include "third_party/blink/public/mojom/keyboard_lock/keyboard_lock.mojom-shared.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom-shared.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/display/display_switches.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/types/event_type.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_handle.h"
#endif
namespace gfx {
class Point;
} // namespace gfx
namespace net {
class CanonicalCookie;
namespace test_server {
class EmbeddedTestServer;
} // namespace test_server
// TODO(svaldez): Remove typedef once EmbeddedTestServer has been migrated
// out of net::test_server.
using test_server::EmbeddedTestServer;
} // namespace net
namespace ui {
class AXPlatformNodeDelegate;
class AXTreeID;
} // namespace ui
#if BUILDFLAG(IS_WIN)
namespace Microsoft {
namespace WRL {
template <typename>
class ComPtr;
} // namespace WRL
} // namespace Microsoft
typedef int PROPERTYID;
#endif
// A collections of functions designed for use with content_browsertests and
// browser_tests.
// TO BE CLEAR: any function here must work against both binaries. If it only
// works with browser_tests, it should be in chrome\test\base\ui_test_utils.h.
// If it only works with content_browsertests, it should be in
// content\test\content_browser_test_utils.h.
namespace blink {
class StorageKey;
namespace mojom {
class FrameWidget;
} // namespace mojom
} // namespace blink
namespace storage {
class BlobUrlRegistry;
}
namespace content {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
class MockCapturedSurfaceController;
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
#if defined(USE_AURA)
class SelectionBoundsWaiter;
#endif // defined(USE_AURA)
class BrowserContext;
class FileSystemAccessPermissionContext;
class FrameTreeNode;
class NavigationHandle;
class NavigationRequest;
class RenderFrameMetadataProviderImpl;
class RenderFrameProxyHost;
class RenderWidgetHost;
class RenderWidgetHostImpl;
class RenderWidgetHostView;
class ScopedAllowRendererCrashes;
class ToRenderFrameHost;
class WebContents;
// This encapsulates the pattern of waiting for an event and returning whether
// that event was received from `Wait`. This makes it easy to do the right thing
// in Wait, i.e. return with `[[nodiscard]]`.
class WaiterHelper {
public:
// Wait until OnEvent is called. Will return true if ended by OnEvent or false
// if ended for some other reason (e.g. timeout).
[[nodiscard]] bool Wait();
// Steps the waiting.
void OnEvent();
private:
[[nodiscard]] bool WaitInternal();
base::RunLoop run_loop_;
bool event_received_ = false;
};
// Navigates |web_contents| to |url|, blocking until the navigation finishes.
// Returns true if the page was loaded successfully and the last committed URL
// matches |url|. This is a browser-initiated navigation that simulates a user
// typing |url| into the address bar.
[[nodiscard]] bool NavigateToURL(WebContents* web_contents, const GURL& url);
// Same as above, but takes in an additional URL, |expected_commit_url|, to
// which the navigation should eventually commit. This is useful for cases
// like redirects, where navigation starts on one URL but ends up committing a
// different URL. This function will return true if navigating to |url|
// results in a successful commit to |expected_commit_url|.
[[nodiscard]] bool NavigateToURL(WebContents* web_contents,
const GURL& url,
const GURL& expected_commit_url);
// Navigates |web_contents| to |url|, blocking until the given number of
// navigations finishes. If |ignore_uncommitted_navigations| is true, then an
// aborted navigation also counts toward |number_of_navigations| being complete.
void NavigateToURLBlockUntilNavigationsComplete(
WebContents* web_contents,
const GURL& url,
int number_of_navigations,
bool ignore_uncommitted_navigations = true);
// Same, but allows specifying the full LoadURLParams instead of just the URL.
void NavigateToURLBlockUntilNavigationsComplete(
WebContents* web_contents,
const NavigationController::LoadURLParams& params,
int number_of_navigations,
bool ignore_uncommitted_navigations = true);
// Helpers for performing renderer-initiated navigations. Note these helpers are
// only suitable when the navigation is expected to at least start successfully,
// i.e. result in a `DidStartNavigation()` notification.
//
// To write a test where the renderer itself blocks or cancels the navigation,
// use `ExecJs()` or `EvalJs()`, e.g.:
//
// EXPECT_THAT(ExecJs("try { location = ''; } catch (e) { e.message; }")
// .ExtractString(),
// HasSubStr("..."));
//
// To write a test where the navigation results in a bad message kill, use
// `ExecuteScriptAsync()` + `RenderProcessHostBadMojoMessageWaiter`, e.g.:
//
// ExecuteScriptAsync(rfh, JsReplace("location = $1", "/title2.html"));
// RenderProcessHostBadMojoMessageWaiter kill_waiter(rfh->GetProcess());
// EXPECT_THAT(kill_waiter.Wait(), Optional(HasSubstr("...")));
// Perform a renderer-initiated navigation of the frame |adapter| to |url|,
// blocking until the navigation finishes. The navigation is done by assigning
// location.href in the frame |adapter|. Returns true if the page was loaded
// successfully and the last committed URL matches |url|.
[[nodiscard]] bool NavigateToURLFromRenderer(const ToRenderFrameHost& adapter,
const GURL& url);
// Similar to above but takes in an additional URL, |expected_commit_url|, to
// which the navigation should eventually commit. (See the browser-initiated
// counterpart for more details).
[[nodiscard]] bool NavigateToURLFromRenderer(const ToRenderFrameHost& adapter,
const GURL& url,
const GURL& expected_commit_url);
// Similar to the two helpers above, but perform the renderer-initiated
// navigation without a user gesture.
[[nodiscard]] bool NavigateToURLFromRendererWithoutUserGesture(
const ToRenderFrameHost& adapter,
const GURL& url);
[[nodiscard]] bool NavigateToURLFromRendererWithoutUserGesture(
const ToRenderFrameHost& adapter,
const GURL& url,
const GURL& expected_commit_url);
// Perform a renderer-initiated navigation of the frame `adapter` to `url`.
// Returns true if the navigation started successfully and false otherwise.
[[nodiscard]] bool BeginNavigateToURLFromRenderer(
const ToRenderFrameHost& adapter,
const GURL& url);
// Navigate a frame with ID |iframe_id| to |url|, blocking until the navigation
// finishes. Uses a renderer-initiated navigation from script code in the
// main frame.
//
// This method does not trigger a user activation before the navigation. If
// necessary, a user activation can be triggered right before calling this
// method, e.g. by calling |ExecJs(frame_tree_node, "")|.
bool NavigateIframeToURL(WebContents* web_contents,
std::string_view iframe_id,
const GURL& url);
// Similar to |NavigateIframeToURL()| but returns as soon as the navigation is
// initiated.
bool BeginNavigateIframeToURL(WebContents* web_contents,
std::string_view iframe_id,
const GURL& url);
// Generate a URL for a file path including a query string.
GURL GetFileUrlWithQuery(const base::FilePath& path,
std::string_view query_string);
// Checks whether the page type of the last committed navigation entry matches
// |page_type|.
bool IsLastCommittedEntryOfPageType(WebContents* web_contents,
content::PageType page_type);
// Waits for |web_contents| to stop loading. If |web_contents| is not loading
// returns immediately. Tests should use WaitForLoadStop instead and check that
// last navigation succeeds, and this function should only be used if the
// navigation leads to web_contents being destroyed.
void WaitForLoadStopWithoutSuccessCheck(WebContents* web_contents);
// Waits for |web_contents| to stop loading. If |web_contents| is not loading
// returns immediately. Returns true if the last navigation succeeded (resulted
// in a committed navigation entry of type PAGE_TYPE_NORMAL).
// TODO(alexmos): tests that use this function to wait for successful
// navigations should be refactored to do EXPECT_TRUE(WaitForLoadStop()).
bool WaitForLoadStop(WebContents* web_contents);
// If a test uses a beforeunload dialog, it must be prepared to avoid flakes.
// This function collects everything that needs to be done, except for user
// activation which is triggered only when |trigger_user_activation| is true.
// Note that beforeunload dialog attempts are ignored unless the frame has
// received a user activation.
void PrepContentsForBeforeUnloadTest(WebContents* web_contents,
bool trigger_user_activation = true);
#if defined(USE_AURA) || BUILDFLAG(IS_ANDROID)
// If WebContent's view is currently being resized, this will wait for the ack
// from the renderer that the resize is complete and for the
// WindowEventDispatcher to release the pointer moves. If there's no resize in
// progress, the method will return right away.
void WaitForResizeComplete(WebContents* web_contents);
#endif // defined(USE_AURA) || BUILDFLAG(IS_ANDROID)
// Allows tests to set the last committed origin of |render_frame_host|, to
// simulate a scenario that might happen with a compromised renderer or might
// not otherwise be possible.
void OverrideLastCommittedOrigin(RenderFrameHost* render_frame_host,
const url::Origin& origin);
// Causes the specified web_contents to crash. Blocks until it is crashed.
void CrashTab(WebContents* web_contents);
// Sets up a commit interceptor to alter commits for |target_url| to change
// their commit URL to |new_url| and origin to |new_origin|. This will happen
// for all commits in |web_contents|.
void PwnCommitIPC(WebContents* web_contents,
const GURL& target_url,
const GURL& new_url,
const url::Origin& new_origin);
// Test helper to check the content-internal CanCommitURL() method on
// ChildProcessSecurityPolicy, determining whether a particular process is
// allowed to commit a navigation to `url`.
bool CanCommitURLForTesting(int child_id, const GURL& url);
// Causes the specified web_contents to issue an OnUnresponsiveRenderer event
// to its observers.
void SimulateUnresponsiveRenderer(WebContents* web_contents,
RenderWidgetHost* widget);
// Simulates clicking at the center of the given tab asynchronously; modifiers
// may contain bits from WebInputEvent::Modifiers. Sends the event through
// RenderWidgetHostInputEventRouter and thus can target OOPIFs. If an OOPIF is
// the intended target, ensure that its hit test data is available for routing,
// using `WaitForHitTestData`, first.
void SimulateMouseClick(WebContents* web_contents,
int modifiers,
blink::WebMouseEvent::Button button);
// Simulates clicking at the point |point| of the given tab asynchronously;
// modifiers may contain bits from WebInputEvent::Modifiers. Sends the event
// through RenderWidgetHostInputEventRouter and thus can target OOPIFs. If an
// OOPIF is the intended target, ensure that its hit test data is available for
// routing, using `WaitForHitTestData`, first.
void SimulateMouseClickAt(WebContents* web_contents,
int modifiers,
blink::WebMouseEvent::Button button,
const gfx::Point& point);
// Retrieves the center coordinates of the element with id |id|.
// ATTENTION: When using these coordinates to simulate a click or tap make sure
// that the viewport is not zoomed as the coordinates returned by this method
// are relative to the page not the viewport. In particular for Android make
// sure the page has the meta tag
// <meta name="viewport" content="width=device-width,minimum-scale=1">
// TODO(crbug.com/40177926): Make the Simulate* methods more user
// friendly by taking zooming into account.
gfx::PointF GetCenterCoordinatesOfElementWithId(
const ToRenderFrameHost& adapter,
std::string_view id);
// Retrieves the center coordinates of the element with id |id| and simulates a
// mouse click there using SimulateMouseClickAt().
void SimulateMouseClickOrTapElementWithId(content::WebContents* web_contents,
std::string_view id);
// Simulates asynchronously a mouse enter/move/leave event. The mouse event is
// routed through RenderWidgetHostInputEventRouter and thus can target OOPIFs.
void SimulateMouseEvent(WebContents* web_contents,
blink::WebInputEvent::Type type,
const gfx::Point& point);
void SimulateMouseEvent(WebContents* web_contents,
blink::WebInputEvent::Type type,
blink::WebMouseEvent::Button button,
const gfx::Point& point);
// Simulate a mouse wheel event.
void SimulateMouseWheelEvent(WebContents* web_contents,
const gfx::Point& point,
const gfx::Vector2d& delta,
const blink::WebMouseWheelEvent::Phase phase);
#if !BUILDFLAG(IS_MAC)
// Simulate a mouse wheel event with the ctrl modifier set.
void SimulateMouseWheelCtrlZoomEvent(RenderWidgetHost* render_widget_host,
const gfx::Point& point,
bool zoom_in,
blink::WebMouseWheelEvent::Phase phase);
void SimulateTouchscreenPinch(WebContents* web_contents,
const gfx::PointF& anchor,
float scale_change,
base::OnceClosure on_complete);
#endif // !BUILDFLAG(IS_MAC)
// Sends a GesturePinch Begin/Update/End sequence.
void SimulateGesturePinchSequence(RenderWidgetHost* render_widget_host,
const gfx::Point& point,
float scale,
blink::WebGestureDevice source_device);
void SimulateGesturePinchSequence(WebContents* web_contents,
const gfx::Point& point,
float scale,
blink::WebGestureDevice source_device);
// Sends a simple, three-event (Begin/Update/End) gesture scroll.
void SimulateGestureScrollSequence(RenderWidgetHost* render_widget_host,
const gfx::Point& point,
const gfx::Vector2dF& delta);
void SimulateGestureScrollSequence(WebContents* web_contents,
const gfx::Point& point,
const gfx::Vector2dF& delta);
void SimulateGestureEvent(RenderWidgetHost* render_widget_host,
const blink::WebGestureEvent& gesture_event,
const ui::LatencyInfo& latency);
void SimulateGestureEvent(WebContents* web_contents,
const blink::WebGestureEvent& gesture_event,
const ui::LatencyInfo& latency);
// Taps the screen at |point|, using gesture Tap or TapDown.
void SimulateTapAt(WebContents* web_contents, const gfx::Point& point);
void SimulateTapDownAt(WebContents* web_contents, const gfx::Point& point);
// A helper function for SimulateTap(Down)At.
void SimulateTouchGestureAt(WebContents* web_contents,
const gfx::Point& point,
blink::WebInputEvent::Type type);
#if defined(USE_AURA)
// Generates a TouchEvent of |event_type| at |point|.
void SimulateTouchEventAt(WebContents* web_contents,
ui::EventType event_type,
const gfx::Point& point);
void SimulateLongTapAt(WebContents* web_contents, const gfx::Point& point);
// Can be used to wait for the caret bounds associated with `web_contents` to
// have non-zero size.
class NonZeroCaretSizeWaiter {
public:
explicit NonZeroCaretSizeWaiter(WebContents* web_contents);
NonZeroCaretSizeWaiter(const NonZeroCaretSizeWaiter&) = delete;
NonZeroCaretSizeWaiter& operator=(const NonZeroCaretSizeWaiter&) = delete;
~NonZeroCaretSizeWaiter();
void Wait();
private:
std::unique_ptr<SelectionBoundsWaiter> selection_bounds_waiter_;
};
// Can be used to wait for an update to the caret bounds associated with
// `web_contents`.
class CaretBoundsUpdateWaiter {
public:
explicit CaretBoundsUpdateWaiter(WebContents* web_contents);
CaretBoundsUpdateWaiter(const CaretBoundsUpdateWaiter&) = delete;
CaretBoundsUpdateWaiter& operator=(const CaretBoundsUpdateWaiter&) = delete;
~CaretBoundsUpdateWaiter();
void Wait();
private:
std::unique_ptr<SelectionBoundsWaiter> selection_bounds_waiter_;
};
// Can be used to wait for updates to the bounding box (i.e. the rectangle
// enclosing the selection region) associated with `web_contents`.
class BoundingBoxUpdateWaiter {
public:
explicit BoundingBoxUpdateWaiter(WebContents* web_contents);
BoundingBoxUpdateWaiter(const BoundingBoxUpdateWaiter&) = delete;
BoundingBoxUpdateWaiter& operator=(const BoundingBoxUpdateWaiter&) = delete;
~BoundingBoxUpdateWaiter();
void Wait();
private:
std::unique_ptr<SelectionBoundsWaiter> selection_bounds_waiter_;
};
#endif
// Taps the screen with modifires at |point|.
void SimulateTapWithModifiersAt(WebContents* web_contents,
unsigned Modifiers,
const gfx::Point& point);
// Sends a key press asynchronously.
// |key| specifies the UIEvents (aka: DOM4Events) value of the key.
// |code| specifies the UIEvents (aka: DOM4Events) value of the physical key.
// |key_code| alone is good enough for scenarios that only need the char
// value represented by a key event and not the physical key on the keyboard
// or the keyboard layout.
// If set to true, the modifiers |control|, |shift|, |alt|, and |command| are
// pressed down first before the key event, and released after.
void SimulateKeyPress(WebContents* web_contents,
ui::DomKey key,
ui::DomCode code,
ui::KeyboardCode key_code,
bool control,
bool shift,
bool alt,
bool command);
// Like SimulateKeyPress(), but does not send the char (AKA keypress) event.
// This is useful for arrow keys and other key presses that do not generate
// characters.
void SimulateKeyPressWithoutChar(WebContents* web_contents,
ui::DomKey key,
ui::DomCode code,
ui::KeyboardCode key_code,
bool control,
bool shift,
bool alt,
bool command);
// Reset touch action for the embedder of a BrowserPluginGuest.
void ResetTouchAction(RenderWidgetHost* host);
// Spins a run loop until effects of previously forwarded input are fully
// realized.
void RunUntilInputProcessed(RenderWidgetHost* host);
// Returns a string representation of a given |referrer_policy|. This is used to
// setup <meta name=referrer> tags in documents used for referrer-policy-based
// tests. The value `no-meta` indicates no tag should be created.
std::string ReferrerPolicyToString(
network::mojom::ReferrerPolicy referrer_policy);
// For testing, bind FakeFrameWidget to a RenderWidgetHost associated
// with a given RenderFrameHost
mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget>
BindFakeFrameWidgetInterfaces(RenderFrameHost* frame);
// Set |active| state for a RenderWidgetHost associated with a given
// RenderFrameHost
void SimulateActiveStateForWidget(RenderFrameHost* frame, bool active);
// Return the value set for VisitedLinkSalt in the navigation's commit_params.
std::optional<uint64_t> GetVisitedLinkSaltForNavigation(
NavigationHandle* navigation_handle);
// Holds down modifier keys for the duration of its lifetime and releases them
// upon destruction. This allows simulating multiple input events without
// simulating modifier key releases in between.
class ScopedSimulateModifierKeyPress {
public:
ScopedSimulateModifierKeyPress(WebContents* web_contents,
bool control,
bool shift,
bool alt,
bool command);
ScopedSimulateModifierKeyPress(const ScopedSimulateModifierKeyPress&) =
delete;
ScopedSimulateModifierKeyPress& operator=(
const ScopedSimulateModifierKeyPress&) = delete;
~ScopedSimulateModifierKeyPress();
// Similar to SimulateMouseClickAt().
void MouseClickAt(int additional_modifiers,
blink::WebMouseEvent::Button button,
const gfx::Point& point);
// Similar to SimulateKeyPress().
void KeyPress(ui::DomKey key, ui::DomCode code, ui::KeyboardCode key_code);
// Similar to SimulateKeyPressWithoutChar().
void KeyPressWithoutChar(ui::DomKey key,
ui::DomCode code,
ui::KeyboardCode key_code);
private:
const raw_ptr<WebContents> web_contents_;
int modifiers_;
const bool control_;
const bool shift_;
const bool alt_;
const bool command_;
};
// Method to check what devices we have on the system.
bool IsWebcamAvailableOnSystem(WebContents* web_contents);
// Allow ExecJs/EvalJs methods to target either a WebContents or a
// RenderFrameHost. Targeting a WebContents means executing the script in the
// RenderFrameHost returned by WebContents::GetPrimaryMainFrame(), which is the
// main frame. Pass a specific RenderFrameHost to target it. Embedders may
// declare additional ConvertToRenderFrameHost functions for convenience.
class ToRenderFrameHost {
public:
template <typename Ptr>
requires requires(Ptr p) { base::to_address(p); }
// NOLINTNEXTLINE(google-explicit-constructor)
ToRenderFrameHost(Ptr frame_convertible_value)
: render_frame_host_(ConvertToRenderFrameHost(
base::to_address(frame_convertible_value))) {}
// Extract the underlying frame.
RenderFrameHost* render_frame_host() const { return render_frame_host_; }
private:
raw_ptr<RenderFrameHost, DanglingUntriaged> render_frame_host_;
};
RenderFrameHost* ConvertToRenderFrameHost(RenderFrameHost* render_view_host);
RenderFrameHost* ConvertToRenderFrameHost(WebContents* web_contents);
// Executes the passed |script| in the specified frame with a user gesture,
// without waiting for |script| completion.
//
// See also:
// - ExecJs (if you want to wait for script completion, or detect syntax errors)
// - EvalJs (if you want to retrieve a value)
// - DOMMessageQueue (to manually wait for domAutomationController.send(...))
void ExecuteScriptAsync(const ToRenderFrameHost& adapter,
std::string_view script);
// Same as `content::ExecuteScriptAsync()`, but doesn't send a user gesture to
// the renderer.
void ExecuteScriptAsyncWithoutUserGesture(const ToRenderFrameHost& adapter,
std::string_view script);
// JsLiteralHelper is a helper class that determines what types are legal to
// pass to StringifyJsLiteral. Legal types include int, string,
// std::string_view, char*, bool, double, GURL, url::Origin, and base::Value&&.
template <typename T>
struct JsLiteralHelper {
// This generic version enables passing any type from which base::Value can be
// instantiated. This covers int, string, double, bool, base::Value&&, etc.
template <typename U>
static base::Value Convert(U&& arg) {
return base::Value(std::forward<U>(arg));
}
static base::Value Convert(const base::Value& value) { return value.Clone(); }
};
// Specialization allowing GURL to be passed to StringifyJsLiteral.
template <>
struct JsLiteralHelper<GURL> {
static base::Value Convert(const GURL& url) {
return base::Value(url.spec());
}
};
// Specialization allowing url::Origin to be passed to StringifyJsLiteral.
template <>
struct JsLiteralHelper<url::Origin> {
static base::Value Convert(const url::Origin& url) {
return base::Value(url.Serialize());
}
};
// Construct a list-type base::Value from a mix of arguments.
//
// Each |arg| can be any type explicitly convertible to base::Value
// (including int/string/std::string_view/char*/double/bool), or any type that
// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
// string literals. |args| can be a mix of different types.
template <typename... Args>
base::Value ListValueOf(Args&&... args) {
base::Value::List values;
(values.Append(JsLiteralHelper<std::remove_cvref_t<Args>>::Convert(
std::forward<Args>(args))),
...);
return base::Value(std::move(values));
}
// Replaces $1, $2, $3, ..., $9 in |script_template| with JS literal values
// constructed from |args|, similar to base::ReplaceStringPlaceholders. Note
// that $10 and above aren't allowed.
//
// Unlike StringPrintf or manual concatenation, this version will properly
// escape string content, even if it contains slashes or quotation marks.
//
// Each |arg| can be any type explicitly convertible to base::Value
// (including int/string/std::string_view/char*/double/bool), or any type that
// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
// string literals. |args| can be a mix of different types.
//
// Example 1:
//
// GURL page_url("http://example.com");
// EXPECT_TRUE(ExecJs(
// shell(), JsReplace("window.open($1, '_blank');", page_url)));
//
// $1 is replaced with a double-quoted JS string literal:
// "http://example.com". Note that quotes around $1 are not required.
//
// Example 2:
//
// bool forced_reload = true;
// EXPECT_TRUE(ExecJs(
// shell(), JsReplace("window.location.reload($1);", forced_reload)));
//
// This becomes "window.location.reload(true);" -- because bool values are
// supported by base::Value. Numbers, lists, and dicts also work.
template <typename... Args>
std::string JsReplace(std::string_view script_template, Args&&... args) {
base::Value::List values =
ListValueOf(std::forward<Args>(args)...).TakeList();
std::vector<std::string> replacements(values.size());
for (size_t i = 0; i < values.size(); ++i) {
CHECK(base::JSONWriter::Write(values[i], &replacements[i]));
}
return base::ReplaceStringPlaceholders(script_template, replacements,
nullptr);
}
// The return value of EvalJs. Captures the value (or the error) arising from
// script execution. When used with gtest assertions, EvalJsResult generally
// behaves like its wrapped value.
//
// An EvalJsResult can be consumed in two ways:
//
// (1) [preferred] Pass it directly to an EXPECT_EQ() macro. It has
// overloaded operator== against std::string, bool, int, double,
// nullptr_t, and base::Value. This will produce readable assertion
// failures if there is a type mismatch, or if an exception was thrown --
// errors are never equal to anything.
//
// For boolean results, note that EXPECT_TRUE(..) and EXPECT_FALSE()
// won't compile; use EXPECT_EQ(true, ...) instead. This is intentional,
// since EXPECT_TRUE() could be read ambiguously as either "expect
// successful execution", "expect truthy value of any type", or "expect
// boolean value 'true'".
//
// (2) [use when necessary] Extract the underlying value of an expected type,
// by calling ExtractString(), ExtractInt(), etc. This will produce a
// CHECK failure if the execution didn't result in the appropriate type
// of result, or if an exception was thrown.
struct EvalJsResult {
const base::Value value; // Value; if things went well.
const std::string error; // Error; if things went badly.
// Creates an EvalJs result. If |error| is non-empty, |value| will be
// ignored.
EvalJsResult(base::Value value, std::string_view error);
// Copy ctor.
EvalJsResult(const EvalJsResult& value);
// Matchers for successful & unsuccessful runs.
static auto IsOk() {
return testing::Field(&EvalJsResult::error, testing::Eq(""));
}
static auto IsError() { return testing::Not(IsOk()); }
// Extract a result value of the requested type, or die trying.
//
// If there was an error, or if returned value is of a different type, these
// will fail with a CHECK. Use Extract methods only when accessing the
// result value is necessary; prefer operator== and EXPECT_EQ() instead:
// they don't CHECK, and give better error messages.
[[nodiscard]] const std::string& ExtractString() const;
[[nodiscard]] int ExtractInt() const;
[[nodiscard]] bool ExtractBool() const;
[[nodiscard]] double ExtractDouble() const;
[[nodiscard]] base::Value ExtractList() const;
};
// Enables EvalJsResult to be used directly in ASSERT/EXPECT macros:
//
// ASSERT_EQ("ab", EvalJs(rfh, "'a' + 'b'"))
// ASSERT_EQ(2, EvalJs(rfh, "1 + 1"))
// ASSERT_EQ(nullptr, EvalJs(rfh, "var a = 1 + 1"))
//
// Error values never return true for any comparison operator.
template <typename T>
bool operator==(const T& a, const EvalJsResult& b) {
return b.error.empty() && (JsLiteralHelper<T>::Convert(a) == b.value);
}
template <typename T>
bool operator==(const EvalJsResult& a, const T& b) {
return b == a;
}
template <typename T>
bool operator!=(const T& a, const EvalJsResult& b) {
return b.error.empty() && (JsLiteralHelper<T>::Convert(a) != b.value);
}
template <typename T>
bool operator!=(const EvalJsResult& a, const T& b) {
return b != a;
}
template <typename T>
bool operator>=(const T& a, const EvalJsResult& b) {
return b.error.empty() && (JsLiteralHelper<T>::Convert(a) >= b.value);
}
template <typename T>
bool operator>=(const EvalJsResult& a, const T& b) {
return b < a;
}
template <typename T>
bool operator<=(const T& a, const EvalJsResult& b) {
return b.error.empty() && (JsLiteralHelper<T>::Convert(a) <= b.value);
}
template <typename T>
bool operator<=(const EvalJsResult& a, const T& b) {
return b > a;
}
template <typename T>
bool operator<(const T& a, const EvalJsResult& b) {
return b.error.empty() && (JsLiteralHelper<T>::Convert(a) < b.value);
}
template <typename T>
bool operator<(const EvalJsResult& a, const T& b) {
return b >= a;
}
template <typename T>
bool operator>(const T& a, const EvalJsResult& b) {
return b.error.empty() && (JsLiteralHelper<T>::Convert(a) > b.value);
}
template <typename T>
bool operator>(const EvalJsResult& a, const T& b) {
return b <= a;
}
inline bool operator==(std::nullptr_t a, const EvalJsResult& b) {
return b.error.empty() && (base::Value() == b.value);
}
template <typename T>
inline bool operator==(const EvalJsResult& a, std::nullptr_t b) {
return nullptr == a;
}
// Provides informative failure messages when the result of EvalJs() is
// used in a failing ASSERT_EQ or EXPECT_EQ.
std::ostream& operator<<(std::ostream& os, const EvalJsResult& bar);
enum EvalJsOptions {
EXECUTE_SCRIPT_DEFAULT_OPTIONS = 0,
// By default, EvalJs runs with a user gesture. This bit flag disables
// that.
EXECUTE_SCRIPT_NO_USER_GESTURE = (1 << 0),
// By default, when the script passed to EvalJs evaluates to a Promise, the
// execution continues until the Promise resolves, and the resolved value is
// returned. Setting this bit disables such Promise resolution.
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES = (1 << 1),
};
// EvalJs() -- run |script| in |execution_target| and return its value or error.
//
// Example simple usage:
//
// EXPECT_EQ("https://abcd.com", EvalJs(render_frame_host, "self.origin"));
// EXPECT_EQ(5, EvalJs(render_frame_host, "history.length"));
// EXPECT_EQ(false, EvalJs(render_frame_host, "history.length > 5"));
//
// The result value of |script| is its "statement completion value" -- the same
// semantics used by Javascript's own eval() function. If |script|
// raises exceptions, or is syntactically invalid, an error is captured instead,
// including a full stack trace.
//
// The return value of EvalJs() may be used directly in EXPECT_EQ()
// macros, and compared for against std::string, int, or any other type for
// which base::Value has a constructor. If an error was thrown by the script,
// any comparison operators will always return false.
//
// If |script|'s captured completion value is a Promise, this function blocks
// until the Promise is resolved. This enables a usage pattern where |script|
// may call an async function, and use the await keyword to wait for
// events to fire. For example:
//
// EXPECT_EQ(200, EvalJs(rfh, "(async () => { var resp = (await fetch(url));"
// " return resp.status; })()");
//
// In the above example, the immediately-invoked function expression results in
// a Promise (that's what async functions do); EvalJs will continue blocking
// until the Promise resolves, which happens when the async function returns
// the HTTP status code -- which is expected, in this case, to be 200.
//
// - If possible, pass the result of EvalJs() into the second argument of an
// EXPECT_EQ macro. This will trigger failure (and a nice message) if an
// error occurs.
// - JS exceptions are reliably captured and will appear as C++ assertion
// failures.
// - JS stack traces arising from exceptions are annotated with the
// corresponding source code; this also appears in C++ assertion failures.
// - Delayed response is supported via Promises and JS async/await.
// - |script| doesn't need to call domAutomationController.send directly.
// - When a script doesn't produce a result, it's likely an assertion
// failure rather than a hang. Doesn't get confused by crosstalk with
// callers of domAutomationController.send() -- EvalJs does not rely on
// domAutomationController.
// - Lists, dicts, null values, etc. can be returned as base::Values.
// - |after_script_invoke| is an optional callback which will be invoked after
// script execution has started in the renderer but before the RunLoop is
// blocked on the result.
//
// It is guaranteed that EvalJs works even when the target frame is frozen.
[[nodiscard]] EvalJsResult EvalJs(
const ToRenderFrameHost& execution_target,
std::string_view script,
int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS,
int32_t world_id = ISOLATED_WORLD_ID_GLOBAL,
base::OnceClosure after_script_invoke = base::DoNothing());
// Like EvalJs(), but runs |raf_script| inside a requestAnimationFrame handler,
// and runs |script| after the rendering update has completed. By the time
// this method returns, any IPCs sent from the renderer process to the browser
// process during the lifecycle update should already have been received and
// processed by the browser.
[[nodiscard]] EvalJsResult EvalJsAfterLifecycleUpdate(
const ToRenderFrameHost& execution_target,
std::string_view raf_script,
std::string_view script,
int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS,
int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
// Run a script exactly the same as EvalJs(), but ignore the resulting value.
//
// Returns AssertionSuccess() if |script| ran successfully, and
// AssertionFailure() if |script| contained a syntax error or threw an
// exception.
//
// As with EvalJs(), if the script passed evaluates to a Promise, this waits
// until it resolves (by default).
[[nodiscard]] ::testing::AssertionResult ExecJs(
const ToRenderFrameHost& execution_target,
std::string_view script,
int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS,
int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
// Walks the frame tree of the specified `page`, also descending into any inner
// frame-trees (e.g. GuestView), and returns the sole frame that matches the
// specified predicate function. Returns nullptr if no frame matches.
// EXPECTs that at most one frame matches.
RenderFrameHost* FrameMatchingPredicateOrNullptr(
Page& page,
base::RepeatingCallback<bool(RenderFrameHost*)> predicate);
// Same like FrameMatchingPredicateOrNullptr(), but EXPECTs that exactly one
// frame matches.
RenderFrameHost* FrameMatchingPredicate(
Page& page,
base::RepeatingCallback<bool(RenderFrameHost*)> predicate);
// Predicates for use with FrameMatchingPredicate[OrNullPtr]().
bool FrameMatchesName(std::string_view name, RenderFrameHost* frame);
bool FrameIsChildOfMainFrame(RenderFrameHost* frame);
bool FrameHasSourceUrl(const GURL& url, RenderFrameHost* frame);
// Finds the child frame at the specified |index| for |adapter| and returns its
// RenderFrameHost. Returns nullptr if such child frame does not exist.
RenderFrameHost* ChildFrameAt(const ToRenderFrameHost& adapter, size_t index);
// Returns true if |frame| has origin-keyed process isolation due to the
// OriginAgentCluster header.
bool HasOriginKeyedProcess(RenderFrameHost* frame);
// Returns true if `frame` has a sandboxed SiteInstance.
bool HasSandboxedSiteInstance(RenderFrameHost* frame);
// Returns the frames visited by |RenderFrameHost::ForEachRenderFrameHost| in
// the same order.
std::vector<RenderFrameHost*> CollectAllRenderFrameHosts(
RenderFrameHost* starting_rfh);
// Returns the frames visited by |RenderFrameHost::ForEachRenderFrameHost| on
// |page|'s main document in the same order.
std::vector<RenderFrameHost*> CollectAllRenderFrameHosts(Page& page);
// Returns the frames visited by |WebContents::ForEachRenderFrameHost| in
// the same order.
std::vector<RenderFrameHost*> CollectAllRenderFrameHosts(
WebContents* web_contents);
// Returns all WebContents. Note that this includes WebContents from any
// BrowserContext.
std::vector<WebContents*> GetAllWebContents();
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Executes the WebUI resource tests. Injects the test runner script prior to
// executing the tests.
//
// Returns true if tests ran successfully, false otherwise.
bool ExecuteWebUIResourceTest(WebContents* web_contents);
#endif
// Returns the serialized cookie string for the given url. Uses an inclusive
// SameSiteCookieContext by default, which gets cookies regardless of their
// SameSite attribute.
std::string GetCookies(
BrowserContext* browser_context,
const GURL& url,
net::CookieOptions::SameSiteCookieContext context =
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKeyCollection key_collection =
net::CookiePartitionKeyCollection::ContainsAll());
// Returns the canonical cookies for the given url.
std::vector<net::CanonicalCookie> GetCanonicalCookies(
BrowserContext* browser_context,
const GURL& url,
net::CookiePartitionKeyCollection key_collection =
net::CookiePartitionKeyCollection::ContainsAll());
// Sets a cookie for the given url. Uses inclusive SameSiteCookieContext by
// default, which gets cookies regardless of their SameSite attribute. The
// cookie is unpartitioned by default. Returns true on success.
bool SetCookie(BrowserContext* browser_context,
const GURL& url,
const std::string& value,
net::CookieOptions::SameSiteCookieContext context =
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey* cookie_partition_key = nullptr);
// Deletes cookies matching the provided filter. Returns the number of cookies
// that were deleted.
uint32_t DeleteCookies(BrowserContext* browser_context,
network::mojom::CookieDeletionFilter filter);
// Fetches the histograms data from other processes.
//
// This function should be called after a child process has logged the
// histogram/metric being tested, to ensure that base::HistogramTester sees all
// the data from the child process.
//
// The caller should ensure that there is no race between 1) the call to
// FetchHistogramsFromChildProcesses() and 2) the child process shutting down.
// See also https://crbug.com/1246137 which describes how this is a test-only
// problem.
void FetchHistogramsFromChildProcesses();
// Registers a request handler which redirects to a different host, based
// on the request path. The format of the path should be
// "/cross-site/hostname/rest/of/path" to redirect the request to
// "<scheme>://hostname:<port>/rest/of/path", where <scheme> and <port>
// are the values for the instance of EmbeddedTestServer.
//
// By default, redirection will be done using HTTP 302 response, but in some
// cases (e.g. to preserve HTTP method and POST body across redirects as
// prescribed by https://tools.ietf.org/html/rfc7231#section-6.4.7) a test might
// want to use HTTP 307 response instead. This can be accomplished by replacing
// "/cross-site/" URL substring above with "/cross-site-307/".
//
// |embedded_test_server| should not be running when passing it to this function
// because adding the request handler won't be thread safe.
void SetupCrossSiteRedirector(net::EmbeddedTestServer* embedded_test_server);
// Sets the access permission context in FileSystemAccessManagerImpl.
void SetFileSystemAccessPermissionContext(
BrowserContext* browser_context,
FileSystemAccessPermissionContext* permission_context);
// Waits until all resources have loaded in the given RenderFrameHost.
[[nodiscard]] bool WaitForRenderFrameReady(RenderFrameHost* rfh);
// Wait until the focused accessible node changes in any WebContents.
void WaitForAccessibilityFocusChange();
// Retrieve information about the node that's focused in the accessibility tree.
ui::AXNodeData GetFocusedAccessibilityNodeInfo(WebContents* web_contents);
// This is intended to be a robust way to assert that the accessibility
// tree eventually gets into the correct state, without worrying about
// the exact ordering of events received while getting there. Blocks
// until any change happens to the accessibility tree.
void WaitForAccessibilityTreeToChange(WebContents* web_contents);
// Searches the accessibility tree to see if any node's accessible name
// is equal to the given name. If not, repeatedly calls
// WaitForAccessibilityTreeToChange, above, and then checks again.
// Keeps looping until the text is found (or the test times out).
void WaitForAccessibilityTreeToContainNodeWithName(WebContents* web_contents,
std::string_view name);
// Get a snapshot of a web page's accessibility tree.
ui::AXTreeUpdate GetAccessibilityTreeSnapshot(WebContents* web_contents);
// Get a snapshot of an accessibility tree given a `tree_id`.
ui::AXTreeUpdate GetAccessibilityTreeSnapshotFromId(
const ui::AXTreeID& tree_id);
// Returns the root accessibility node for the given WebContents.
ui::AXPlatformNodeDelegate* GetRootAccessibilityNode(WebContents* web_contents);
// Finds an accessibility node matching the given criteria.
struct FindAccessibilityNodeCriteria {
FindAccessibilityNodeCriteria();
~FindAccessibilityNodeCriteria();
std::optional<ax::mojom::Role> role;
std::optional<std::string> name;
};
ui::AXPlatformNodeDelegate* FindAccessibilityNode(
WebContents* web_contents,
const FindAccessibilityNodeCriteria& criteria);
ui::AXPlatformNodeDelegate* FindAccessibilityNodeInSubtree(
ui::AXPlatformNodeDelegate* node,
const FindAccessibilityNodeCriteria& criteria);
#if BUILDFLAG(IS_WIN)
// Retrieve the specified interface from an accessibility node.
template <typename T>
Microsoft::WRL::ComPtr<T> QueryInterfaceFromNode(
ui::AXPlatformNodeDelegate* node);
// Call GetPropertyValue with the given UIA property id with variant type
// VT_ARRAY | VT_UNKNOWN on the target browser accessibility node to retrieve
// an array of automation elements, then validate the name property of the
// automation elements with the expected names.
void UiaGetPropertyValueVtArrayVtUnknownValidate(
PROPERTYID property_id,
ui::AXPlatformNodeDelegate* target_node,
const std::vector<std::string>& expected_names);
#endif
// Returns the RenderWidgetHost that holds the keyboard lock.
RenderWidgetHost* GetKeyboardLockWidget(WebContents* web_contents);
// Returns the RenderWidgetHost that holds the mouse lock.
RenderWidgetHost* GetMouseLockWidget(WebContents* web_contents);
// Allows tests to drive keyboard lock functionality without requiring access
// to the RenderWidgetHostImpl header or setting up an HTTP test server.
// |codes| represents the set of keys to lock. If |codes| has no value, then
// all keys will be considered locked. If |codes| has a value, then at least
// one key must be specified.
void RequestKeyboardLock(
WebContents* web_contents,
std::optional<base::flat_set<ui::DomCode>> codes,
base::OnceCallback<void(blink::mojom::KeyboardLockRequestResult)> callback);
void CancelKeyboardLock(WebContents* web_contents);
// Returns the screen orientation provider that's been set via
// WebContents::SetScreenOrientationDelegate(). May return null.
ScreenOrientationDelegate* GetScreenOrientationDelegate();
// Returns all the RenderWidgetHostViews inside the |web_contents| that are
// registered in the RenderWidgetHostInputEventRouter.
std::vector<RenderWidgetHostView*> GetInputEventRouterRenderWidgetHostViews(
WebContents* web_contents);
// Returns the focused RenderWidgetHost.
RenderWidgetHost* GetFocusedRenderWidgetHost(WebContents* web_contents);
// Returns whether or not the RenderWidgetHost thinks it is focused.
bool IsRenderWidgetHostFocused(const RenderWidgetHost*);
// Returns the focused WebContents.
WebContents* GetFocusedWebContents(WebContents* web_contents);
// Watches title changes on a WebContents, blocking until an expected title is
// set.
class TitleWatcher : public WebContentsObserver {
public:
// |web_contents| must be non-NULL and needs to stay alive for the
// entire lifetime of |this|. |expected_title| is the title that |this|
// will wait for.
TitleWatcher(WebContents* web_contents, const std::u16string& expected_title);
TitleWatcher(const TitleWatcher&) = delete;
TitleWatcher& operator=(const TitleWatcher&) = delete;
~TitleWatcher() override;
// Adds another title to watch for.
void AlsoWaitForTitle(const std::u16string& expected_title);
// Waits until the title matches either expected_title or one of the titles
// added with AlsoWaitForTitle. Returns the value of the most recently
// observed matching title.
[[nodiscard]] const std::u16string& WaitAndGetTitle();
private:
// Overridden WebContentsObserver methods.
void DidStopLoading() override;
void TitleWasSet(NavigationEntry* entry) override;
void TestTitle();
std::vector<std::u16string> expected_titles_;
base::RunLoop run_loop_;
// The most recently observed expected title, if any.
std::u16string observed_title_;
};
// Watches a RenderProcessHost and waits for a specified lifecycle event.
class RenderProcessHostWatcher : public RenderProcessHostObserver {
public:
enum WatchType {
WATCH_FOR_PROCESS_READY,
WATCH_FOR_PROCESS_EXIT,
WATCH_FOR_HOST_DESTRUCTION
};
RenderProcessHostWatcher(RenderProcessHost* render_process_host,
WatchType type);
// Waits for the renderer process that contains the specified web contents.
RenderProcessHostWatcher(WebContents* web_contents, WatchType type);
RenderProcessHostWatcher(const RenderProcessHostWatcher&) = delete;
RenderProcessHostWatcher& operator=(const RenderProcessHostWatcher&) = delete;
~RenderProcessHostWatcher() override;
// Waits until the expected event is triggered. This may only be called once.
void Wait();
// Returns true if a renderer process exited cleanly (without hitting
// RenderProcessExited with an abnormal TerminationStatus). This should be
// called after Wait().
bool did_exit_normally() { return did_exit_normally_; }
private:
// Quit the run loop and clean up.
void QuitRunLoop();
// Overridden RenderProcessHost::LifecycleObserver methods.
void RenderProcessReady(RenderProcessHost* host) override;
void RenderProcessExited(RenderProcessHost* host,
const ChildProcessTerminationInfo& info) override;
void RenderProcessHostDestroyed(RenderProcessHost* host) override;
base::ScopedObservation<RenderProcessHost, RenderProcessHostObserver>
observation_{this};
WatchType type_;
bool did_exit_normally_;
std::unique_ptr<ScopedAllowRendererCrashes> allow_renderer_crashes_;
base::RunLoop run_loop_;
base::OnceClosure quit_closure_;
};
// Implementation helper for:
// *) content-internal content::RenderProcessHostBadIpcMessageWaiter
// (declared in //content/test/content_browser_test_utils_internal.h)
// *) content-public content::RenderProcessHostBadMojoMessageWaiter
// (declared below)
// *) maybe in the future: similar helpers for chrome-layer BadMessageReason
class RenderProcessHostKillWaiter {
public:
// |uma_name| is the name of the histogram from which the |bad_message_reason|
// can be extracted.
RenderProcessHostKillWaiter(RenderProcessHost* render_process_host,
std::string_view uma_name);
RenderProcessHostKillWaiter(const RenderProcessHostKillWaiter&) = delete;
RenderProcessHostKillWaiter& operator=(const RenderProcessHostKillWaiter&) =
delete;
// Waits until the renderer process exits. Extracts and returns the bad
// message reason that should be logged in the |uma_name_| histogram.
// Returns |std::nullopt| if the renderer exited normally or didn't log
// the |uma_name_| histogram.
[[nodiscard]] std::optional<int> Wait();
private:
RenderProcessHostWatcher exit_watcher_;
base::HistogramTester histogram_tester_;
std::string uma_name_;
};
// Helps tests to wait until the given renderer process is terminated because of
// a bad/invalid mojo message.
//
// Example usage:
// RenderProcessHostBadMojoMessageWaiter kill_waiter(render_process_host);
// ... test code that triggers a renderer kill ...
// EXPECT_EQ("expected error message", kill_waiter.Wait());
class RenderProcessHostBadMojoMessageWaiter {
public:
explicit RenderProcessHostBadMojoMessageWaiter(
RenderProcessHost* render_process_host);
~RenderProcessHostBadMojoMessageWaiter();
RenderProcessHostBadMojoMessageWaiter(
const RenderProcessHostBadMojoMessageWaiter&) = delete;
RenderProcessHostBadMojoMessageWaiter& operator=(
const RenderProcessHostBadMojoMessageWaiter&) = delete;
// Waits until |render_process_host| from the constructor is terminated
// because of a bad/invalid mojo message and returns the associated error
// string. Returns std::nullopt if the process was terminated for an
// unrelated reason.
[[nodiscard]] std::optional<std::string> Wait();
private:
void OnBadMojoMessage(int render_process_id, const std::string& error);
int monitored_render_process_id_;
std::optional<std::string> observed_mojo_error_;
RenderProcessHostKillWaiter kill_waiter_;
};
// Watches for responses from the DOMAutomationController and keeps them in a
// queue. Useful for waiting for a message to be received.
class DOMMessageQueue {
public:
// Constructs a DOMMessageQueue and begins listening for messages from the
// DOMAutomationController. Do not construct this until the browser has
// started.
// NOTE: Use one of the below constructors if observing messages for a single
// WebContents instance.
DOMMessageQueue();
// Same as the default constructor, but only listens for messages
// sent from a particular |web_contents|.
explicit DOMMessageQueue(WebContents* web_contents);
// Same as the constructor with a WebContents, but observes the
// RenderFrameHost deletion.
explicit DOMMessageQueue(RenderFrameHost* render_frame_host);
DOMMessageQueue(const DOMMessageQueue&) = delete;
DOMMessageQueue& operator=(const DOMMessageQueue&) = delete;
~DOMMessageQueue();
// Removes all messages in the message queue.
void ClearQueue();
// Wait for the next message to arrive. |message| will be set to the next
// message. Returns true on success.
[[nodiscard]] bool WaitForMessage(std::string* message);
// Facilitates asynchronous use of the DOMMessageQueue. The given callback
// will be called once a message was put into the queue (or when there is a
// renderer crash).
// NOTE: This is incompatible with the DOMMessageQueue(RenderFrameHost*) ctor.
void SetOnMessageAvailableCallback(base::OnceClosure callback);
// If there is a message in the queue, then copies it to |message| and returns
// true. Otherwise (if the queue is empty), returns false.
[[nodiscard]] bool PopMessage(std::string* message);
// Returns true if there are currently any messages in the queue.
bool HasMessages();
private:
class MessageObserver;
friend class MessageObserver;
void OnDomMessageReceived(const std::string& message);
void PrimaryMainFrameRenderProcessGone(base::TerminationStatus status);
void RenderFrameDeleted(RenderFrameHost* render_frame_host);
void OnWebContentsCreated(WebContents* contents);
void OnBackingWebContentsDestroyed(MessageObserver* observer);
std::set<std::unique_ptr<MessageObserver>> observers_;
base::CallbackListSubscription web_contents_creation_subscription_;
base::queue<std::string> message_queue_;
base::OnceClosure callback_;
bool renderer_crashed_ = false;
raw_ptr<RenderFrameHost, DanglingUntriaged> render_frame_host_ = nullptr;
};
// Used to wait for a new WebContents to be created. Instantiate this object
// before the operation that will create the window.
class WebContentsAddedObserver {
public:
WebContentsAddedObserver();
WebContentsAddedObserver(const WebContentsAddedObserver&) = delete;
WebContentsAddedObserver& operator=(const WebContentsAddedObserver&) = delete;
~WebContentsAddedObserver();
// Will run a message loop to wait for the new window if it hasn't been
// created since the constructor
WebContents* GetWebContents();
private:
void WebContentsCreated(WebContents* web_contents);
base::CallbackListSubscription creation_subscription_;
raw_ptr<WebContents, AcrossTasksDanglingUntriaged> web_contents_ = nullptr;
base::OnceClosure quit_closure_;
};
// Request a new frame be drawn, returns false if request fails.
bool RequestFrame(WebContents* web_contents);
// This class is intended to synchronize upon the submission of compositor
// frames from the renderer to the display compositor.
//
// This class enables observation of the provided
// RenderFrameMetadataProvider. Which notifies this of every
// subsequent frame submission. Observation ends upon the destruction of this
// class.
//
// Calling Wait will block the browser ui thread until the next time the
// renderer submits a frame.
//
// Tests interested in the associated RenderFrameMetadata will find it cached
// in the RenderFrameMetadataProvider.
class RenderFrameSubmissionObserver
: public RenderFrameMetadataProvider::Observer {
public:
explicit RenderFrameSubmissionObserver(
RenderFrameMetadataProviderImpl* render_frame_metadata_provider);
explicit RenderFrameSubmissionObserver(FrameTreeNode* node);
explicit RenderFrameSubmissionObserver(WebContents* web_contents);
explicit RenderFrameSubmissionObserver(RenderFrameHost* rfh);
~RenderFrameSubmissionObserver() override;
// Resets the current |render_frame_count|;
void ResetCounter() { render_frame_count_ = 0; }
// Blocks the browser ui thread until the next OnRenderFrameSubmission.
void WaitForAnyFrameSubmission();
// Blocks the browser ui thread until the next
// OnRenderFrameMetadataChangedAfterActivation.
void WaitForMetadataChange();
// Blocks the browser ui thread until RenderFrameMetadata arrives with
// page scale factor matching |expected_page_scale_factor|.
void WaitForPageScaleFactor(float expected_page_scale_factor,
const float tolerance);
// Blocks the browser ui thread until RenderFrameMetadata arrives with
// external page scale factor matching |expected_external_page_scale_factor|.
void WaitForExternalPageScaleFactor(float expected_external_page_scale_factor,
const float tolerance);
// Blocks the browser ui thread until RenderFrameMetadata arrives where its
// scroll offset matches |expected_offset|.
void WaitForScrollOffset(const gfx::PointF& expected_offset);
// Blocks the browser ui thread until RenderFrameMetadata arrives where its
// scroll offset at top matches |expected_scroll_offset_at_top|.
void WaitForScrollOffsetAtTop(bool expected_scroll_offset_at_top);
const cc::RenderFrameMetadata& LastRenderFrameMetadata() const;
// Returns the number of frames submitted since the observer's creation.
int render_frame_count() const { return render_frame_count_; }
// Runs |closure| the next time metadata changes.
void NotifyOnNextMetadataChange(base::OnceClosure closure);
private:
// Exits |run_loop_| unblocking the UI thread. Execution will resume in Wait.
void Quit();
// Blocks the browser ui thread.
void Wait();
// RenderFrameMetadataProvider::Observer
void OnRenderFrameMetadataChangedBeforeActivation(
const cc::RenderFrameMetadata& metadata) override;
void OnRenderFrameMetadataChangedAfterActivation(
base::TimeTicks activation_time) override;
void OnRenderFrameSubmission() override;
void OnLocalSurfaceIdChanged(
const cc::RenderFrameMetadata& metadata) override;
// If true then the next OnRenderFrameSubmission will cancel the blocking
// |run_loop_| otherwise the blocking will continue until the next
// OnRenderFrameMetadataChangedAfterActivation.
bool break_on_any_frame_ = false;
raw_ptr<RenderFrameMetadataProviderImpl> render_frame_metadata_provider_ =
nullptr;
base::OnceClosure quit_closure_;
// If non-null, run when metadata changes.
base::OnceClosure metadata_change_closure_;
int render_frame_count_ = 0;
};
// This class is intended to synchronize the renderer main thread, renderer impl
// thread and the browser main thread.
//
// This is accomplished by sending an IPC to RenderWidget, then blocking until
// the ACK is received and processed.
//
// The ACK is sent from compositor thread, when the CompositorFrame is submited
// to the display compositor
// TODO(danakj): This class seems to provide the same as
// RenderFrameSubmissionObserver, consider using that instead.
class MainThreadFrameObserver {
public:
explicit MainThreadFrameObserver(RenderWidgetHost* render_widget_host);
MainThreadFrameObserver(const MainThreadFrameObserver&) = delete;
MainThreadFrameObserver& operator=(const MainThreadFrameObserver&) = delete;
~MainThreadFrameObserver();
// Synchronizes the browser main thread with the renderer main thread and impl
// thread.
void Wait();
private:
void Quit(bool);
// This dangling raw_ptr occurred in:
// browser_tests: All/NavigationPageLoadMetricsBrowserTest.FirstInputDelay/1
// https://ci.chromium.org/ui/p/chromium/builders/try/win-rel/180209/test-results?q=ExactID%3Aninja%3A%2F%2Fchrome%2Ftest%3Abrowser_tests%2FNavigationPageLoadMetricsBrowserTest.FirstInputDelay%2FAll.1+VHash%3Abdbee181b3e0309b
raw_ptr<RenderWidgetHost, FlakyDanglingUntriaged> render_widget_host_;
base::OnceClosure quit_closure_;
int routing_id_;
};
// Watches for an input msg to be consumed.
class InputMsgWatcher : public RenderWidgetHost::InputEventObserver {
public:
InputMsgWatcher(RenderWidgetHost* render_widget_host,
blink::WebInputEvent::Type type);
InputMsgWatcher(const InputMsgWatcher&) = delete;
InputMsgWatcher& operator=(const InputMsgWatcher&) = delete;
~InputMsgWatcher() override;
bool HasReceivedAck() const;
// Wait until ack message occurs, returning the ack result from
// the message.
blink::mojom::InputEventResultState WaitForAck();
// Wait for the ack if it hasn't been received, if it has been
// received return the result immediately.
blink::mojom::InputEventResultState GetAckStateWaitIfNecessary();
blink::mojom::InputEventResultSource last_event_ack_source() const {
return ack_source_;
}
private:
// Overridden InputEventObserver methods.
void OnInputEventAck(blink::mojom::InputEventResultSource source,
blink::mojom::InputEventResultState state,
const blink::WebInputEvent&) override;
raw_ptr<RenderWidgetHost> render_widget_host_;
blink::WebInputEvent::Type wait_for_type_;
blink::mojom::InputEventResultState ack_result_;
blink::mojom::InputEventResultSource ack_source_;
base::OnceClosure quit_closure_;
};
// Used to wait for a desired input event ack.
class InputEventAckWaiter : public RenderWidgetHost::InputEventObserver {
public:
// A function determining if a given |event| and its ack are what we're
// waiting for.
using InputEventAckPredicate =
base::RepeatingCallback<bool(blink::mojom::InputEventResultSource source,
blink::mojom::InputEventResultState state,
const blink::WebInputEvent& event)>;
// Wait for an event satisfying |predicate|.
InputEventAckWaiter(RenderWidgetHost* render_widget_host,
InputEventAckPredicate predicate);
// Wait for any event of the given |type|.
InputEventAckWaiter(RenderWidgetHost* render_widget_host,
blink::WebInputEvent::Type type);
InputEventAckWaiter(const InputEventAckWaiter&) = delete;
InputEventAckWaiter& operator=(const InputEventAckWaiter&) = delete;
~InputEventAckWaiter() override;
void Wait();
void Reset();
// RenderWidgetHost::InputEventObserver:
void OnInputEventAck(blink::mojom::InputEventResultSource source,
blink::mojom::InputEventResultState state,
const blink::WebInputEvent& event) override;
private:
base::WeakPtr<RenderWidgetHostImpl> render_widget_host_;
InputEventAckPredicate predicate_;
bool event_received_ = false;
base::OnceClosure quit_closure_;
};
// Sets up a ui::TestClipboard for use in browser tests. On Windows,
// clipboard is handled on the IO thread, BrowserTestClipboardScope
// hops messages onto the right thread.
class BrowserTestClipboardScope {
public:
// Sets up a ui::TestClipboard.
BrowserTestClipboardScope();
BrowserTestClipboardScope(const BrowserTestClipboardScope&) = delete;
BrowserTestClipboardScope& operator=(const BrowserTestClipboardScope&) =
delete;
// Tears down the clipboard.
~BrowserTestClipboardScope();
// Puts text/rtf |rtf| on the clipboard.
void SetRtf(const std::string& rtf);
// Puts plain text |text| on the clipboard.
void SetText(const std::string& text);
// Gets plain text from the clipboard, if any.
void GetText(std::string* text);
};
// This observer is used to wait for its owner Frame to become focused.
class FrameFocusedObserver {
public:
explicit FrameFocusedObserver(RenderFrameHost* owner_host);
FrameFocusedObserver(const FrameFocusedObserver&) = delete;
FrameFocusedObserver& operator=(const FrameFocusedObserver&) = delete;
~FrameFocusedObserver();
void Wait();
private:
// Private impl struct which hides non public types including FrameTreeNode.
class FrameTreeNodeObserverImpl;
// FrameTreeNode::Observer
std::unique_ptr<FrameTreeNodeObserverImpl> impl_;
};
// This observer is used to wait for its owner FrameTreeNode to become deleted.
class FrameDeletedObserver {
public:
explicit FrameDeletedObserver(RenderFrameHost* owner_host);
FrameDeletedObserver(const FrameDeletedObserver&) = delete;
FrameDeletedObserver& operator=(const FrameDeletedObserver&) = delete;
~FrameDeletedObserver();
void Wait();
bool IsDeleted() const;
int GetFrameTreeNodeId() const;
private:
// Private impl struct which hides non public types including FrameTreeNode.
class FrameTreeNodeObserverImpl;
// FrameTreeNode::Observer
std::unique_ptr<FrameTreeNodeObserverImpl> impl_;
};
// This class can be used to pause and resume navigations, based on a URL
// match. Note that it only keeps track of one navigation at a time.
// Navigations are paused automatically before hitting the network, and are
// resumed automatically if a Wait method is called for a future event.
//
// Note: This class is one time use only! After it successfully tracks a
// navigation it will ignore all subsequent navigations. Explicitly create
// multiple instances of this class if you want to pause multiple navigations.
//
// Note2: This class cannot be used with page activating navigations (e.g.
// BFCache, prerendering, etc.) as the activation doesn't run
// NavigationThrottles. Use TestActivationManager in these cases instead.
class TestNavigationManager : public WebContentsObserver {
public:
// Monitors any frame in WebContents.
TestNavigationManager(WebContents* web_contents, const GURL& url);
TestNavigationManager(const TestNavigationManager&) = delete;
TestNavigationManager& operator=(const TestNavigationManager&) = delete;
~TestNavigationManager() override;
// Waits until the first yield point after DidStartNavigation. Unlike
// WaitForRequestStart, this can be used to wait for a pause in cases where a
// test expects a NavigationThrottle to defer in WillStartRequest. In cases
// where throttles run and none defer, this will break at the same time as
// WaitForRequestStart. Note: since we won't know which throttle deferred,
// don't use ResumeNavigation() after this call since it assumes we paused
// from the TestNavigationManagerThrottle. Returns false if the waiting was
// terminated before reaching DidStartNavigation (e.g. timeout).
bool WaitForFirstYieldAfterDidStartNavigation();
// Waits until the navigation request is ready to be sent to the network
// stack. This will wait until all NavigationThrottles have proceeded through
// WillStartRequest. Returns false if the request was aborted before starting.
[[nodiscard]] bool WaitForRequestStart();
// Waits until the navigation response's headers have been received. This
// will wait until all NavigationThrottles have proceeded through
// WillProcessResponse. Returns false if the request was aborted before
// getting a response.
[[nodiscard]] bool WaitForResponse();
// Waits until the navigation has been finished. Will automatically resume
// navigations paused before this point. Returns false if the waiting was
// terminated before reaching DidStartNavigation (e.g. timeout).
[[nodiscard]] bool WaitForNavigationFinished();
// Resume the navigation.
// * Called after |WaitForRequestStart|, it causes the request to be sent.
// * Called after |WaitForResponse|, it causes the response to be committed.
void ResumeNavigation();
// Returns the NavigationHandle associated with the navigation. It is non-null
// only in between DidStartNavigation(...) and DidFinishNavigation(...).
NavigationHandle* GetNavigationHandle();
// Whether the navigation successfully committed.
bool was_committed() const { return was_committed_; }
// Whether the navigation successfully committed and was not an error page.
bool was_successful() const { return was_successful_; }
// Allows nestable tasks when running a message loop in the Wait* functions.
// This is useful for utilizing this class from within another message loop.
void AllowNestableTasks();
// Write a representation of this class into trace.
void WriteIntoTrace(perfetto::TracedValue ctx) const;
protected:
// Derived classes can override if they want to filter out navigations. This
// is called from DidStartNavigation.
virtual bool ShouldMonitorNavigation(NavigationHandle* handle);
private:
enum class NavigationState {
INITIAL = 0,
WILL_START = 1,
STARTED = 2,
RESPONSE = 3,
FINISHED = 4,
};
// WebContentsObserver:
void DidStartNavigation(NavigationHandle* handle) override;
void DidFinishNavigation(NavigationHandle* handle) override;
// Called when the NavigationThrottle pauses the navigation in
// WillStartRequest.
void OnWillStartRequest();
// Called when the NavigationThrottle pauses the navigation in
// WillProcessResponse.
void OnWillProcessResponse();
// Waits for the desired state. Returns false if the desired state cannot be
// reached (eg the navigation finishes before reaching this state).
[[nodiscard]] bool WaitForDesiredState();
// Called when the state of the navigation has changed. This will either stop
// the message loop if the state specified by the user has been reached, or
// resume the navigation if it hasn't been reached yet.
void OnNavigationStateChanged();
void ResumeIfPaused();
const GURL url_;
// RAW_PTR_EXCLUSION: Incompatible with tracing (TRACE_EVENT*),
// perfetto::TracedDictionary::Add and gmock/EXPECT_THAT.
RAW_PTR_EXCLUSION NavigationRequest* request_ = nullptr;
bool navigation_paused_ = false;
NavigationState current_state_ = NavigationState::INITIAL;
NavigationState desired_state_ = NavigationState::WILL_START;
bool was_committed_ = false;
bool was_successful_ = false;
base::OnceClosure quit_closure_;
base::RunLoop::Type message_loop_type_ = base::RunLoop::Type::kDefault;
base::WeakPtrFactory<TestNavigationManager> weak_factory_{this};
};
// Like TestNavigationManager but for page activating navigations like
// Back-Forward Cache restores and prerendering activations. This can be used
// to pause and resume the navigation at certain points during an activation.
// Note that only a single navigation will be tracked by this manager. When
// this object is live, a matching navigation will be automatically paused at
// kBeforeChecks and resumed automatically when a Wait method is called.
class TestActivationManager : public WebContentsObserver {
public:
TestActivationManager(WebContents* web_contents, const GURL& url);
TestActivationManager(const TestActivationManager&) = delete;
TestActivationManager& operator=(const TestActivationManager&) = delete;
~TestActivationManager() override;
// Waits until the navigation is about to start running
// CommitDeferringConditions. i.e. before any (non-test)
// CommitDeferringConditions have run.
[[nodiscard]] bool WaitForBeforeChecks();
// Waits until the navigation has finished running CommitDeferringConditions.
// i.e. all (non-test) CommitDeferringConditions have completed. Resuming
// from here will post a task that will synchronously commit the navigation.
[[nodiscard]] bool WaitForAfterChecks();
// Waits until the navigation has completed or been deleted.
void WaitForNavigationFinished();
// If the manager paused the navigation, this method can be used to proceed
// to the next state.
void ResumeActivation();
// Returns the navigation handle being observed by this
// TestActivationManager. This will be null until a navigation matching the
// given URL starts running its CommitDeferringConditions. It'll also be
// cleared when the navigation finishes.
NavigationHandle* GetNavigationHandle();
// Sets the callback that is called after all commit deferring conditions run.
void SetCallbackCalledAfterActivationIsReady(base::OnceClosure callback);
// Whether the navigation successfully committed.
bool was_committed() const { return was_committed_; }
// Whether the navigation successfully committed and was not an error page.
bool was_successful() const { return was_successful_; }
// Whether the navigation finished successfully as a page activation. This
// can be false in the case that the activation failed and the navigation
// continues as a regular, non-activating, navigation.
bool was_activated() const { return was_activated_; }
// Returns true if the navigation is paused by the TestActivationManager.
bool is_paused() const { return !resume_callback_.is_null(); }
private:
enum class ActivationState {
kInitial,
kBeforeChecks,
kAfterChecks,
kFinished
};
CommitDeferringCondition::Result FirstConditionCallback(
CommitDeferringCondition& condition,
base::OnceClosure resume_callback);
CommitDeferringCondition::Result LastConditionCallback(
CommitDeferringCondition& condition,
base::OnceClosure resume_callback);
// WebContentsObserver:
void DidFinishNavigation(NavigationHandle* handle) override;
bool WaitForDesiredState();
void StopWaitingIfNeeded();
// The URL this manager is watching for. Navigations to a URL that do not
// match this will be ignored.
const GURL url_;
// Set when a matching navigation reaches kBeforeChecks and cleared when the
// navigation is deleted/finished.
raw_ptr<NavigationRequest> request_ = nullptr;
// If the navigation is paused in the first or last CommitDeferringCondition
// (i.e. the one installed by this manager for testing), this will be the
// closure used to resume the navigation. Note: invoking resume_callback_
// will start running further conditions, it doesn't just quit a RunLoop like
// `quit_closure_`.
base::OnceClosure resume_callback_;
// When the manager is blocked waiting on a state (e.g.
// WaitForNavigationFinished), this closure will quit the waiting runloop so
// that the manager client can proceed.
base::OnceClosure quit_closure_;
// These are RAII helpers that will install a testing
// CommitDeferringCondition before (first_condition_inserter_) and after
// (last_condition_inserter_) all other CommitDeferringConditions that
// TestActivationManager will use to pause the navigation.
class ConditionInserter;
std::unique_ptr<ConditionInserter> first_condition_inserter_;
std::unique_ptr<ConditionInserter> last_condition_inserter_;
ActivationState current_state_ = ActivationState::kInitial;
ActivationState desired_state_ = ActivationState::kBeforeChecks;
// Prevents the TestActivationManager from attaching to more than one
// matching activation.
bool is_tracking_activation_ = false;
bool was_committed_ = false;
bool was_successful_ = false;
bool was_activated_ = false;
// Callback to be called in the last condition callback after all commit
// deferring conditions run.
base::OnceClosure callback_in_last_condition;
base::WeakPtrFactory<TestActivationManager> weak_factory_{this};
};
class NavigationHandleCommitObserver : public content::WebContentsObserver {
public:
NavigationHandleCommitObserver(content::WebContents* web_contents,
const GURL& url);
bool has_committed() const { return has_committed_; }
bool was_same_document() const { return was_same_document_; }
bool was_renderer_initiated() const { return was_renderer_initiated_; }
blink::mojom::NavigationType navigation_type() const {
return navigation_type_;
}
private:
void DidFinishNavigation(content::NavigationHandle* handle) override;
const GURL url_;
bool has_committed_ = false;
bool was_same_document_ = false;
bool was_renderer_initiated_ = false;
blink::mojom::NavigationType navigation_type_ =
blink::mojom::NavigationType::RELOAD;
};
// A test utility that monitors console messages sent to a WebContents. This
// can be used to wait for a message that matches a specific filter, an
// arbitrary message, or monitor all messages sent to the WebContents' console.
class WebContentsConsoleObserver : public WebContentsObserver {
public:
struct Message {
raw_ptr<RenderFrameHost, AcrossTasksDanglingUntriaged> source_frame;
blink::mojom::ConsoleMessageLevel log_level;
std::u16string message;
int32_t line_no;
std::u16string source_id;
};
// A filter to apply to incoming console messages to determine whether to
// record them. The filter should return `true` if the observer should record
// the message, and stop waiting (if it was waiting).
using Filter = base::RepeatingCallback<bool(const Message& message)>;
explicit WebContentsConsoleObserver(content::WebContents* web_contents);
~WebContentsConsoleObserver() override;
// Waits for a message to come in that matches the set filter, if any. If no
// filter is set, waits for the first message that comes in.
[[nodiscard]] bool Wait();
// Sets a custom filter to be used while waiting for a message, allowing
// more custom filtering (e.g. based on source).
void SetFilter(Filter filter);
// A convenience method to just match the message against a string pattern.
void SetPattern(std::string pattern);
// A helper method to return the string content (in UTF8) of the message at
// the given |index|. This will cause a test failure if there is no such
// message.
std::string GetMessageAt(size_t index) const;
const std::vector<Message>& messages() const { return messages_; }
private:
// WebContentsObserver:
void OnDidAddMessageToConsole(
RenderFrameHost* source_frame,
blink::mojom::ConsoleMessageLevel log_level,
const std::u16string& message,
int32_t line_no,
const std::u16string& source_id,
const std::optional<std::u16string>& untrusted_stack_trace) override;
Filter filter_;
std::string pattern_;
WaiterHelper waiter_helper_;
std::vector<Message> messages_;
};
// A helper class to get DevTools inspector log messages (e.g. network errors).
class DevToolsInspectorLogWatcher : public DevToolsAgentHostClient {
public:
explicit DevToolsInspectorLogWatcher(WebContents* web_contents);
~DevToolsInspectorLogWatcher() override;
void FlushAndStopWatching();
std::string last_message() { return last_message_; }
GURL last_url() { return last_url_; }
// DevToolsAgentHostClient:
void DispatchProtocolMessage(DevToolsAgentHost* host,
base::span<const uint8_t> message) override;
void AgentHostClosed(DevToolsAgentHost* host) override;
private:
scoped_refptr<DevToolsAgentHost> host_;
base::RunLoop run_loop_enable_log_;
base::RunLoop run_loop_disable_log_;
std::string last_message_;
GURL last_url_;
};
// Static methods that simulates Mojo methods as if they were called by a
// renderer. Used to simulate a compromised renderer.
class PwnMessageHelper {
public:
PwnMessageHelper(const PwnMessageHelper&) = delete;
PwnMessageHelper& operator=(const PwnMessageHelper&) = delete;
// Calls Create method in FileSystemHost Mojo interface.
static void FileSystemCreate(RenderProcessHost* process,
int request_id,
GURL path,
bool exclusive,
bool is_directory,
bool recursive,
const blink::StorageKey& storage_key);
// Calls Write method in FileSystemHost Mojo interface.
static void FileSystemWrite(RenderProcessHost* process,
int request_id,
GURL file_path,
std::string blob_uuid,
int64_t position,
const blink::StorageKey& storage_key);
// Calls OpenURL method in FrameHost Mojo interface.
static void OpenURL(RenderFrameHost* render_frame_host, const GURL& url);
private:
PwnMessageHelper(); // Not instantiable.
};
#if defined(USE_AURA)
// Tests that a |render_widget_host_view| stores a stale content when its frame
// gets evicted. |render_widget_host_view| has to be a RenderWidgetHostViewAura.
void VerifyStaleContentOnFrameEviction(
RenderWidgetHostView* render_widget_host_view);
#endif // defined(USE_AURA)
// Helper class to interpose on Blob URL registrations, replacing the URL
// contained in incoming registration requests with the specified URL.
class BlobURLStoreInterceptor
: public blink::mojom::BlobURLStoreInterceptorForTesting {
public:
static void InterceptDeprecated(
GURL target_url,
mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>
receiver);
static void Intercept(GURL target_url,
storage::BlobUrlRegistry* registry,
mojo::ReceiverId receiver_id);
~BlobURLStoreInterceptor() override;
blink::mojom::BlobURLStore* GetForwardingInterface() override;
void Register(
mojo::PendingRemote<blink::mojom::Blob> blob,
const GURL& url,
// TODO(https://crbug.com/1224926): Remove these once experiment is over.
const base::UnguessableToken& unsafe_agent_cluster_id,
const std::optional<net::SchemefulSite>& unsafe_top_level_site,
RegisterCallback callback) override;
private:
explicit BlobURLStoreInterceptor(GURL target_url);
std::unique_ptr<blink::mojom::BlobURLStore> url_store_;
GURL target_url_;
};
// Load the given |url| with |network_context| and return the |net::Error| code.
//
// This overload simulates loading through a URLLoaderFactory created for a
// Browser process.
int LoadBasicRequest(network::mojom::NetworkContext* network_context,
const GURL& url,
int load_flags = net::LOAD_NORMAL);
// Load the given |url| via URLLoaderFactory created by |frame|. Return the
// |net::Error| code.
//
// This overload simulates loading through a URLLoaderFactory created for a
// Renderer process (the factory is driven from the Test/Browser process, but
// has the same properties as factories vended to the Renderer process that
// hosts the |frame|).
int LoadBasicRequest(RenderFrameHost* frame, const GURL& url);
// Ensures that all StoragePartitions for the given BrowserContext have their
// cookies flushed to disk.
void EnsureCookiesFlushed(BrowserContext* browser_context);
// Performs a simple auto-resize flow and ensures that the embedder gets a
// single response messages back from the guest, with the expected values.
bool TestGuestAutoresize(WebContents* embedder_web_contents,
RenderFrameHost* guest_main_frame);
// This class allows monitoring of mouse events received by a specific
// RenderWidgetHost.
class RenderWidgetHostMouseEventMonitor {
public:
explicit RenderWidgetHostMouseEventMonitor(RenderWidgetHost* host);
RenderWidgetHostMouseEventMonitor(const RenderWidgetHostMouseEventMonitor&) =
delete;
RenderWidgetHostMouseEventMonitor& operator=(
const RenderWidgetHostMouseEventMonitor&) = delete;
~RenderWidgetHostMouseEventMonitor();
bool EventWasReceived() const { return event_received_; }
void ResetEventReceived() { event_received_ = false; }
const blink::WebMouseEvent& event() const { return event_; }
private:
bool MouseEventCallback(const blink::WebMouseEvent& event) {
event_received_ = true;
event_ = event;
return false;
}
RenderWidgetHost::MouseEventCallback mouse_callback_;
raw_ptr<RenderWidgetHost> host_;
bool event_received_;
blink::WebMouseEvent event_;
};
// Helper class to track and allow waiting for navigation start events.
class DidStartNavigationObserver : public WebContentsObserver {
public:
explicit DidStartNavigationObserver(WebContents* web_contents);
DidStartNavigationObserver(const DidStartNavigationObserver&) = delete;
DidStartNavigationObserver& operator=(const DidStartNavigationObserver&) =
delete;
~DidStartNavigationObserver() override;
void Wait() { run_loop_.Run(); }
bool observed() { return observed_; }
// If the navigation was observed and is still not finished yet, this returns
// its handle, otherwise it returns nullptr.
NavigationHandle* navigation_handle() { return navigation_handle_; }
// WebContentsObserver override:
void DidStartNavigation(NavigationHandle* navigation_handle) override;
void DidFinishNavigation(NavigationHandle* navigation_handle) override;
private:
bool observed_ = false;
base::RunLoop run_loop_;
raw_ptr<NavigationHandle> navigation_handle_ = nullptr;
};
// Tracks the creation of RenderFrameProxyHosts that have
// CrossProcessFrameConnectors, and records the initial (post-construction)
// device scale factor in the CrossProcessFrameConnector.
class ProxyDSFObserver {
public:
ProxyDSFObserver();
~ProxyDSFObserver();
// Waits until a single RenderFrameProxyHost with a CrossProcessFrameConnector
// has been created. If a creation occurs before this function is called, it
// returns immediately.
void WaitForOneProxyHostCreation();
size_t num_creations() { return proxy_host_created_dsf_.size(); }
float get_proxy_host_dsf(unsigned index) {
return proxy_host_created_dsf_[index];
}
private:
void OnCreation(RenderFrameProxyHost* rfph);
// Make this a vector, just in case we encounter multiple creations prior to
// calling WaitForOneProxyHostCreation(). That way we can confirm we're
// getting the device scale factor we expect.
// Note: We can modify the vector to collect a void* id for each
// RenderFrameProxyHost if we want to expand to cases where multiple creations
// must be observed.
std::vector<float> proxy_host_created_dsf_;
std::unique_ptr<base::RunLoop> runner_;
};
// Helper used by the wrapper class below. Compares the output of the given
// |web_contents| to the PNG file at |expected_path| across the region defined
// by |snapshot_size| and returns true if the images are equivalent. Uses a
// ManhattanDistancePixelComparator which allows some small differences. If
// the flag switches::kRebaselinePixelTests (--rebaseline-pixel-tests) is set,
// this function will (over)write the reference file with the produced output.
bool CompareWebContentsOutputToReference(
WebContents* web_contents,
const base::FilePath& expected_path,
const gfx::Size& snapshot_size,
const cc::PixelComparator& comparator =
cc::ManhattanDistancePixelComparator());
typedef base::OnceCallback<void(RenderFrameHost*, RenderFrameHost*)>
RenderFrameHostChangedCallback;
// Runs callback at RenderFrameHostChanged time. On cross-RFH navigations, this
// will run the callback after the new RenderFrameHost committed and is set as
// the current RenderFrameHost, etc. but before the old RenderFrameHost gets
// unloaded.
class RenderFrameHostChangedCallbackRunner : public WebContentsObserver {
public:
explicit RenderFrameHostChangedCallbackRunner(
WebContents* content,
RenderFrameHostChangedCallback callback);
RenderFrameHostChangedCallbackRunner(
const RenderFrameHostChangedCallbackRunner&) = delete;
RenderFrameHostChangedCallbackRunner& operator=(
const RenderFrameHostChangedCallbackRunner&) = delete;
~RenderFrameHostChangedCallbackRunner() override;
private:
void RenderFrameHostChanged(RenderFrameHost* old_host,
RenderFrameHost* new_host) override;
RenderFrameHostChangedCallback callback_;
};
// Calls |callback| whenever a navigation finishes in |render_frame_host|.
class DidFinishNavigationObserver : public WebContentsObserver {
public:
DidFinishNavigationObserver(
WebContents* web_contents,
base::RepeatingCallback<void(NavigationHandle*)> callback);
DidFinishNavigationObserver(
RenderFrameHost* render_frame_host,
base::RepeatingCallback<void(NavigationHandle*)> callback);
~DidFinishNavigationObserver() override;
DidFinishNavigationObserver(const DidFinishNavigationObserver&) = delete;
DidFinishNavigationObserver& operator=(const DidFinishNavigationObserver&) =
delete;
protected:
// WebContentsObserver:
void DidFinishNavigation(NavigationHandle* navigation_handle) override;
private:
base::RepeatingCallback<void(NavigationHandle*)> callback_;
};
// Wait for a new WebContents to be created, and for it to finish navigation.
// It will detect WebContents creation after construction, even if it's before
// Wait() is called. The intended pattern is:
//
// CreateAndLoadWebContentsObserver observer;
// ...Do something that creates one WebContents and causes it to navigate...
// observer.Wait();
class CreateAndLoadWebContentsObserver {
public:
// Used to wait for the given number of WebContents to be created. The load of
// the last expected WebContents is awaited.
explicit CreateAndLoadWebContentsObserver(int num_expected_contents = 1);
~CreateAndLoadWebContentsObserver();
// Wait for the last expected WebContents to finish loading. The test will
// fail if an additional WebContents creation is observed before `Wait()`
// completes.
WebContents* Wait();
private:
void OnWebContentsCreated(WebContents* web_contents);
// Unregister for WebContents creation callbacks if we are registered. May
// be called multiple times.
void UnregisterIfNeeded();
std::optional<LoadStopObserver> load_stop_observer_;
base::CallbackListSubscription creation_subscription_;
raw_ptr<WebContents, DanglingUntriaged> web_contents_ = nullptr;
base::OnceClosure contents_creation_quit_closure_;
const int num_expected_contents_;
int num_new_contents_seen_ = 0;
};
// Waits for the given number of calls to
// WebContentsObserver::OnCookiesAccessed.
class CookieChangeObserver : public content::WebContentsObserver {
public:
explicit CookieChangeObserver(content::WebContents* web_contents,
int num_expected_calls = 1);
~CookieChangeObserver() override;
void Wait();
int num_read_seen() const { return num_read_seen_; }
int num_write_seen() const { return num_write_seen_; }
private:
void OnCookiesAccessed(content::RenderFrameHost* render_frame_host,
const content::CookieAccessDetails& details) override;
void OnCookiesAccessed(content::NavigationHandle* navigation,
const content::CookieAccessDetails& details) override;
void OnCookieAccessed(const content::CookieAccessDetails& details);
base::RunLoop run_loop_;
int num_seen_ = 0;
int num_expected_calls_;
int num_read_seen_ = 0;
int num_write_seen_ = 0;
};
[[nodiscard]] base::CallbackListSubscription
RegisterWebContentsCreationCallback(
base::RepeatingCallback<void(WebContents*)> callback);
// Functions to traverse history and wait until the traversal completes, even if
// the loading is stopped halfway (e.g. if a BackForwardCache entry is evicted
// during the restoration, causing the old NavigationRequest to be reset and a
// new NavigationRequest to be restarted). These are wrappers around the
// same-named methods of the `NavigationController`.
[[nodiscard]] bool HistoryGoToIndex(WebContents* wc, int index);
[[nodiscard]] bool HistoryGoToOffset(WebContents* wc, int offset);
[[nodiscard]] bool HistoryGoBack(WebContents* wc);
[[nodiscard]] bool HistoryGoForward(WebContents* wc);
#if BUILDFLAG(IS_MAC)
// Grant native windows the ability to activate, allowing them to become key
// and/or main. This can be useful to enable when the process hosting the window
// is a standalone executable without an Info.plist.
bool EnableNativeWindowActivation();
#endif // BUILDFLAG(IS_MAC)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Set the global factory for CapturedSurfaceController objects.
void SetCapturedSurfaceControllerFactoryForTesting(
base::RepeatingCallback<std::unique_ptr<MockCapturedSurfaceController>(
GlobalRenderFrameHostId,
WebContentsMediaCaptureId)> factory);
#endif // !BUILDFLAG(IS_ANDROID)
} // namespace content
#endif // CONTENT_PUBLIC_TEST_BROWSER_TEST_UTILS_H_