blob: c505efde61445692978f44bc420d8fa8a554d56c [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_TEST_UTILS_H_
#define CONTENT_PUBLIC_TEST_TEST_UTILS_H_
#include <memory>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/threading/thread_checker.h"
#include "build/build_config.h"
#include "content/public/browser/browser_child_process_observer.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/blink/public/common/fetch/fetch_api_request_headers_map.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-forward.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom-forward.h"
#if BUILDFLAG(IS_ANDROID)
#include <jni.h>
#endif
namespace base {
class CommandLine;
} // namespace base
// A collection of functions designed for use with unit and browser tests.
namespace blink {
class NoiseToken;
} // namespace blink
namespace content {
class RenderFrameHost;
// Create an blink::mojom::FetchAPIRequestPtr with given fields.
blink::mojom::FetchAPIRequestPtr CreateFetchAPIRequest(
const GURL& url,
const std::string& method,
const blink::FetchAPIRequestHeadersMap& headers,
blink::mojom::ReferrerPtr referrer,
bool is_reload);
// Deprecated: Use RunLoop::Run(). Use RunLoop::Type::kNestableTasksAllowed to
// force nesting in browser tests.
void RunMessageLoop();
// Turns on nestable tasks, runs all pending tasks in the message loop, then
// resets nestable tasks to what they were originally. Can only be called from
// the UI thread. Only use this instead of RunLoop::RunUntilIdle() to work
// around cases where a task keeps reposting itself and prevents the loop from
// going idle.
// TODO(gab): Assess whether this API is really needed. If you find yourself
// needing this, post a comment on https://crbug.com/824431.
void RunAllPendingInMessageLoop();
// Deprecated: For BrowserThread::IO use
// BrowserTaskEnvironment::RunIOThreadUntilIdle. For the main thread use
// RunLoop. In non-unit-tests use RunLoop::QuitClosure to observe async events
// rather than flushing entire threads.
void RunAllPendingInMessageLoop(BrowserThread::ID thread_id);
// Runs all tasks on the current thread and ThreadPool threads until idle.
// Note: Prefer BrowserTaskEnvironment::RunUntilIdle() in unit tests.
void RunAllTasksUntilIdle();
// Get task to quit the given RunLoop. It allows a few generations of pending
// tasks to run as opposed to run_loop->QuitClosure().
// Prefer RunLoop::RunUntilIdle() to this.
// TODO(gab): Assess the need for this API (see comment on
// RunAllPendingInMessageLoop() above).
base::OnceClosure GetDeferredQuitTaskForRunLoop(base::RunLoop* run_loop);
// Returns true if all sites are isolated. Typically used to bail from a test
// that is incompatible with --site-per-process.
bool AreAllSitesIsolatedForTesting();
// Returns true if |origin| is currently isolated with respect to the
// BrowsingInstance of |site_instance|. This is only relevant for
// OriginAgentCluster isolation, and not other types of origin isolation.
// Note: this only indicates logcial OriginAgentCluster isolation, and says
// nothing about process-isolation (RequiresOriginKeyedProcess).
bool IsOriginAgentClusterEnabledForOrigin(SiteInstance* site_instance,
const url::Origin& origin);
// Returns true if the process model only allows a SiteInstance to contain
// a single site.
// TODO(crbug.com/390571607, yangsharon): Rename this function and use for
// default SiteInstanceGroups.
bool AreStrictSiteInstancesEnabled();
// Returns true if a test needs to register an origin for isolation to ensure
// that navigations, for that origin, are placed in a dedicated process. Some
// process model modes allow sites to share a process if they are not isolated.
// This helper indicates when such a mode is in use and indicates the test must
// register an isolated origin to ensure the origin gets placed in its own
// process.
bool IsIsolatedOriginRequiredToGuaranteeDedicatedProcess();
// Appends --site-per-process to the command line, enabling tests to exercise
// site isolation and cross-process iframes. This must be called early in
// the test; the flag will be read on the first real navigation.
void IsolateAllSitesForTesting(base::CommandLine* command_line);
// Whether same-site navigations might result in a change of RenderFrameHosts -
// this will happen when ProactivelySwapBrowsingInstance, RenderDocument or
// back-forward cache is enabled on same-site main frame navigations. Note that
// even if this returns true, not all same-site main frame navigations will
// result in a change of RenderFrameHosts, e.g. if RenderDocument is disabled
// but BFCache is enabled, this will return true but only same-site navigations
// from pages that are BFCache-eligible will result in a RenderFrameHost change.
bool CanSameSiteMainFrameNavigationsChangeRenderFrameHosts();
// Whether same-site navigations will result in a change of RenderFrameHosts,
// which will happen when RenderDocument is enabled. Due to the various levels
// of the feature, the result may differ depending on whether the
// RenderFrameHost is a main/local root/non-local-root frame.
bool WillSameSiteNavigationChangeRenderFrameHosts(bool is_main_frame,
bool is_local_root = true);
// Whether same-site navigations might result in a change of SiteInstances -
// this will happen when ProactivelySwapBrowsingInstance or back-forward cache
// is enabled on same-site main frame navigations.
// Note that unlike WillSameSiteNavigationChangeRenderFrameHosts()
// above, this will not be true when RenderDocument for main-frame is enabled.
bool CanSameSiteMainFrameNavigationsChangeSiteInstances();
// Returns true if navigation queueing is fully enabled, where we will queue new
// navigations that happen when there is an existing pending commit navigation.
bool IsNavigationQueueingEnabled();
// Makes sure that navigations that start in |rfh| won't result in a proactive
// BrowsingInstance swap (note they might still result in a normal
// BrowsingInstance swap, e.g. in the case of cross-site navigations).
void DisableProactiveBrowsingInstanceSwapFor(RenderFrameHost* rfh);
// Returns a GURL constructed from the WebUI scheme and the given host.
GURL GetWebUIURL(const std::string& host);
// Returns a string constructed from the WebUI scheme and the given host.
std::string GetWebUIURLString(const std::string& host);
// Creates a WebContents and attaches it as an inner WebContents, replacing
// |rfh| in the frame tree. |rfh| should not be a main frame (in a browser test,
// it should be an <iframe>). Delegate interfaces are mocked out.
//
// Returns a pointer to the inner WebContents, which is now owned by the outer
// WebContents. The caller should be careful when retaining the pointer, as the
// inner WebContents will be deleted if the frame it's attached to goes away.
WebContents* CreateAndAttachInnerContents(RenderFrameHost* rfh);
// Spins a run loop until IsDocumentOnLoadCompletedInPrimaryMainFrame() is true.
void AwaitDocumentOnLoadCompleted(WebContents* web_contents);
// Sets the focused frame of `web_contents` to the `rfh` for tests that rely on
// the focused frame not being null.
void FocusWebContentsOnFrame(WebContents* web_contents, RenderFrameHost* rfh);
// Gets the CanvasNoiseToken value from the `page` for tests that rely on the
// CanvasNoiseToken value and do not reside in //content/browser.
std::optional<blink::NoiseToken> GetCanvasNoiseTokenForPage(const Page& page);
// Helper class to Run and Quit the message loop. Run and Quit can only happen
// once per instance. Make a new instance for each use. Calling Quit after Run
// has returned is safe and has no effect.
// Note that by default Quit does not quit immediately. If that is not what you
// really need, pass QuitMode::IMMEDIATE in the constructor.
//
// DEPRECATED. Consider using base::RunLoop, in most cases MessageLoopRunner is
// not needed. If you need to defer quitting the loop, use
// RunLoop::RunUntilIdle() and if you really think you need deferred quit (can't
// reach idle, please post details in a comment on https://crbug.com/668707).
class MessageLoopRunner : public base::RefCountedThreadSafe<MessageLoopRunner> {
public:
enum class QuitMode {
// Message loop stops after finishing the current task.
IMMEDIATE,
// Several generations of posted tasks are executed before stopping.
DEFERRED,
};
explicit MessageLoopRunner(QuitMode mode = QuitMode::DEFERRED);
MessageLoopRunner(const MessageLoopRunner&) = delete;
MessageLoopRunner& operator=(const MessageLoopRunner&) = delete;
// Run the current MessageLoop unless the quit closure
// has already been called.
void Run();
// Quit the matching call to Run (nested MessageLoops are unaffected).
void Quit();
// Hand this closure off to code that uses callbacks to notify completion.
// Example:
// scoped_refptr<MessageLoopRunner> runner = new MessageLoopRunner;
// kick_off_some_api(runner->QuitClosure());
// runner->Run();
base::OnceClosure QuitClosure();
bool loop_running() const { return loop_running_; }
private:
friend class base::RefCountedThreadSafe<MessageLoopRunner>;
~MessageLoopRunner();
QuitMode quit_mode_;
// True when the message loop is running.
bool loop_running_ = false;
// True after closure returned by |QuitClosure| has been called.
bool quit_closure_called_ = false;
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
base::ThreadChecker thread_checker_;
};
// Helper to wait for loading to stop on a WebContents.
//
// This helper class exists to avoid the following common pattern in tests:
// PerformAction()
// WaitForCompletionNotification()
// The pattern leads to flakiness as there is a window between PerformAction
// returning and the observers getting registered, where a notification will be
// missed.
//
// Rather, one can do this:
// LoadStopObserver signal(web_contents)
// PerformAction()
// signal.Wait()
class LoadStopObserver : public WebContentsObserver {
public:
explicit LoadStopObserver(WebContents* web_contents);
// Wait until at least one load stop has been observed. Return immediately if
// one has been observed since construction.
void Wait();
// WebContentsObserver
void DidStopLoading() override;
private:
bool seen_ = false;
base::RunLoop run_loop_;
};
// Unit tests can use code which runs in the utility process by having it run on
// an in-process utility thread. This eliminates having two code paths in
// production code to deal with unit tests, and also helps with the binary
// separation on Windows since chrome.dll doesn't need to call into Blink code
// for some utility code to handle the single process case.
// Include this class as a member variable in your test harness if you take
// advantage of this functionality to ensure that the in-process utility thread
// is torn down correctly. See http://crbug.com/316919 for more information.
// Note: this class should be declared after the BrowserTaskEnvironment and
// ShadowingAtExitManager (if it exists) as it will need to be run before they
// are torn down.
class InProcessUtilityThreadHelper : public BrowserChildProcessObserver {
public:
InProcessUtilityThreadHelper();
InProcessUtilityThreadHelper(const InProcessUtilityThreadHelper&) = delete;
InProcessUtilityThreadHelper& operator=(const InProcessUtilityThreadHelper&) =
delete;
~InProcessUtilityThreadHelper() override;
private:
void JoinAllUtilityThreads();
void CheckHasRunningChildProcess();
void BrowserChildProcessHostDisconnected(
const ChildProcessData& data) override;
std::optional<base::RunLoop> run_loop_;
};
// This observer keeps tracks of whether a given RenderFrameHost has received
// WebContentsObserver::RenderFrameDeleted.
class RenderFrameDeletedObserver : public WebContentsObserver {
public:
// |rfh| should not already be deleted.
explicit RenderFrameDeletedObserver(RenderFrameHost* rfh);
RenderFrameDeletedObserver(const RenderFrameDeletedObserver&) = delete;
RenderFrameDeletedObserver& operator=(const RenderFrameDeletedObserver&) =
delete;
~RenderFrameDeletedObserver() override;
// Overridden WebContentsObserver methods.
void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
// TODO(crbug.com/40204325): Add [[nodiscard]]
// Returns true if the frame was deleted before the timeout.
bool WaitUntilDeleted();
bool deleted() const;
private:
// We cannot keep a pointer because if the RenderFrameHost is not in the
// created state when this class is initialized, then RenderFrameDeleted might
// not be called when it is destroyed.
GlobalRenderFrameHostId rfh_id_;
std::unique_ptr<base::RunLoop> runner_;
};
// This class holds a RenderFrameHost*, providing safe access to it for testing.
// If the RFH is destroyed, it can no longer be accessed. Attempting to access
// it via dereference will cause a DCHECK failure.
//
// For convenience, it also wraps a RenderFrameDeletedObserver and provides
// access to |deleted| and |WaitForDeleted|. Note, deletion of the RenderFrame
// does not always correspond to destruction of the RenderFrameHost, see
// the comments on |RenderFrameDeletedObserver|).
class RenderFrameHostWrapper {
public:
explicit RenderFrameHostWrapper(RenderFrameHost* rfh);
~RenderFrameHostWrapper();
RenderFrameHostWrapper(RenderFrameHostWrapper&&);
// Returns the pointer or nullptr if the RFH has already been destroyed.
RenderFrameHost* get() const;
// Returns true if RenderFrameHost has been destroyed.
bool IsDestroyed() const;
// See RenderFrameDeletedObserver for notes on the difference between
// RenderFrame being deleted and RenderFrameHost being destroyed.
// Returns true if the frame was deleted before the timeout.
[[nodiscard]] bool WaitUntilRenderFrameDeleted() const;
bool IsRenderFrameDeleted() const;
// Pointerish operators. Feel free to add more if you need them.
RenderFrameHost& operator*() const;
RenderFrameHost* operator->() const;
explicit operator bool() const { return get() != nullptr; }
private:
const GlobalRenderFrameHostId rfh_id_;
// It's tempting to just inherit but RenderFrameDeletedObserver is not
// movable because it is a WebContentsObserver.
std::unique_ptr<RenderFrameDeletedObserver> deleted_observer_;
};
// Watches a WebContents. Can be used to block until it is destroyed or just
// merely report if it was destroyed.
class WebContentsDestroyedWatcher : public WebContentsObserver {
public:
explicit WebContentsDestroyedWatcher(WebContents* web_contents);
WebContentsDestroyedWatcher(const WebContentsDestroyedWatcher&) = delete;
WebContentsDestroyedWatcher& operator=(const WebContentsDestroyedWatcher&) =
delete;
~WebContentsDestroyedWatcher() override;
// Waits until the WebContents is destroyed.
void Wait();
// Returns whether the WebContents was destroyed.
bool IsDestroyed() { return destroyed_; }
private:
// Overridden WebContentsObserver methods.
void WebContentsDestroyed() override;
base::RunLoop run_loop_;
bool destroyed_ = false;
};
// Watches a web contents for page scales.
class TestPageScaleObserver : public WebContentsObserver {
public:
explicit TestPageScaleObserver(WebContents* web_contents);
TestPageScaleObserver(const TestPageScaleObserver&) = delete;
TestPageScaleObserver& operator=(const TestPageScaleObserver&) = delete;
~TestPageScaleObserver() override;
float WaitForPageScaleUpdate();
private:
void OnPageScaleFactorChanged(float page_scale_factor) override;
base::OnceClosure done_callback_;
bool seen_page_scale_change_ = false;
float last_scale_ = 0.f;
};
class EffectiveURLContentBrowserClientHelper {
public:
explicit EffectiveURLContentBrowserClientHelper(
bool requires_dedicated_process = false);
~EffectiveURLContentBrowserClientHelper();
void AddTranslation(const GURL& url_to_modify, const GURL& url_to_return);
std::optional<GURL> GetEffectiveURL(const GURL& url);
bool DoesSiteRequireDedicatedProcess(BrowserContext* browser_context,
const GURL& effective_site_url);
private:
// A map of original URLs to effective URLs.
std::map<GURL, GURL> urls_to_modify_;
const bool requires_dedicated_process_;
};
// A custom ContentBrowserClient that simulates GetEffectiveURL() translation
// for one or more URL pairs. |requires_dedicated_process| indicates whether
// the client should indicate that each registered URL requires a dedicated
// process. Passing |false| for it will rely on default behavior computed in
// SiteInstanceImpl::DoesSiteRequireDedicatedProcess().
//
// Do not use this in browser tests. Instead use
// EffectiveURLContentBrowserTestContentBrowserClient.
class EffectiveURLContentBrowserClient : public ContentBrowserClient {
public:
explicit EffectiveURLContentBrowserClient(bool requires_dedicated_process);
EffectiveURLContentBrowserClient(const GURL& url_to_modify,
const GURL& url_to_return,
bool requires_dedicated_process);
EffectiveURLContentBrowserClient(const EffectiveURLContentBrowserClient&) =
delete;
EffectiveURLContentBrowserClient& operator=(
const EffectiveURLContentBrowserClient&) = delete;
~EffectiveURLContentBrowserClient() override;
// Adds effective URL translation from |url_to_modify| to |url_to_return|.
void AddTranslation(const GURL& url_to_modify, const GURL& url_to_return);
private:
std::optional<GURL> GetEffectiveURL(BrowserContext* browser_context,
const GURL& url) override;
bool DoesSiteRequireDedicatedProcess(BrowserContext* browser_context,
const GURL& effective_site_url) override;
EffectiveURLContentBrowserClientHelper helper_;
};
// Wrapper around `SetBrowserClientForTesting()` that ensures the
// previous content browser client is restored upon destruction. This is
// unnecessary in browser tests. In browser tests subclass
// ContentBrowserTestContentBrowserClient and it will take care of this for you.
class ScopedContentBrowserClientSetting final {
public:
explicit ScopedContentBrowserClientSetting(ContentBrowserClient* new_client);
~ScopedContentBrowserClientSetting();
ScopedContentBrowserClientSetting(const ScopedContentBrowserClientSetting&) =
delete;
ScopedContentBrowserClientSetting(ScopedContentBrowserClientSetting&&) =
delete;
ScopedContentBrowserClientSetting& operator=(
const ScopedContentBrowserClientSetting&) = delete;
ScopedContentBrowserClientSetting& operator=(
ScopedContentBrowserClientSetting&&) = delete;
private:
const raw_ptr<ContentBrowserClient> old_client_;
};
// Blocks the current execution until the frame submitted via the browser's
// compositor is presented on the screen.
void WaitForBrowserCompositorFramePresented(WebContents* web_contents);
// Forces the browser to submit a compositor frame, even if nothing has changed
// in the viewport. Use `WaitForBrowserCompositorFramePresented()` to wait for
// the frame's presentation.
void ForceNewCompositorFrameFromBrowser(WebContents* web_contents);
} // namespace content
#endif // CONTENT_PUBLIC_TEST_TEST_UTILS_H_