| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/public/test/test_utils.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/base_switches.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/current_thread.h" |
| #include "base/task/sequence_manager/sequence_manager.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/task_observer.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/font_access/font_enumeration_cache.h" |
| #include "content/browser/origin_agent_cluster_isolation_state.h" |
| #include "content/browser/renderer_host/render_frame_host_delegate.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/site_info.h" |
| #include "content/browser/site_instance_impl.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/public/browser/browser_child_process_host_iterator.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/process_type.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/test_launcher.h" |
| #include "testing/gtest/include/gtest/gtest.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.h" |
| #include "url/url_util.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Number of times to repost a Quit task so that the MessageLoop finishes up |
| // pending tasks and tasks posted by those pending tasks without risking the |
| // potential hang behavior of MessageLoop::QuitWhenIdle. |
| // The criteria for choosing this number: it should be high enough to make the |
| // quit act like QuitWhenIdle, while taking into account that any page which is |
| // animating may be rendering another frame for each quit deferral. For an |
| // animating page, the potential delay to quitting the RunLoop would be |
| // kNumQuitDeferrals * frame_render_time. Some perf tests run slow, such as |
| // 200ms/frame. |
| constexpr int kNumQuitDeferrals = 10; |
| |
| void DeferredQuitRunLoop(base::OnceClosure quit_task, int num_quit_deferrals) { |
| if (num_quit_deferrals <= 0) { |
| std::move(quit_task).Run(); |
| } else { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&DeferredQuitRunLoop, std::move(quit_task), |
| num_quit_deferrals - 1)); |
| } |
| } |
| |
| // Monitors if any task is processed by the message loop. |
| class TaskObserver : public base::TaskObserver { |
| public: |
| TaskObserver() = default; |
| |
| TaskObserver(const TaskObserver&) = delete; |
| TaskObserver& operator=(const TaskObserver&) = delete; |
| |
| ~TaskObserver() override = default; |
| |
| // TaskObserver overrides. |
| void WillProcessTask(const base::PendingTask& pending_task, |
| bool was_blocked_or_low_priority) override {} |
| void DidProcessTask(const base::PendingTask& pending_task) override { |
| if (base::EndsWith(pending_task.posted_from.file_name(), "base/run_loop.cc", |
| base::CompareCase::SENSITIVE)) { |
| // Don't consider RunLoop internal tasks (i.e. QuitClosure() reposted by |
| // ProxyToTaskRunner() or RunLoop timeouts) as actual work. |
| return; |
| } |
| processed_ = true; |
| } |
| |
| // Returns true if any task was processed. |
| bool processed() const { return processed_; } |
| |
| private: |
| bool processed_ = false; |
| }; |
| |
| // Adapter that makes a WindowedNotificationObserver::ConditionTestCallback from |
| // a WindowedNotificationObserver::ConditionTestCallbackWithoutSourceAndDetails |
| // by ignoring the notification source and details. |
| bool IgnoreSourceAndDetails( |
| WindowedNotificationObserver::ConditionTestCallbackWithoutSourceAndDetails |
| callback, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| return std::move(callback).Run(); |
| } |
| |
| } // namespace |
| |
| blink::mojom::FetchAPIRequestPtr CreateFetchAPIRequest( |
| const GURL& url, |
| const std::string& method, |
| const blink::FetchAPIRequestHeadersMap& headers, |
| blink::mojom::ReferrerPtr referrer, |
| bool is_reload) { |
| auto request = blink::mojom::FetchAPIRequest::New(); |
| request->url = url; |
| request->method = method; |
| request->headers = headers; |
| request->referrer = std::move(referrer); |
| request->is_reload = is_reload; |
| return request; |
| } |
| |
| void RunMessageLoop() { |
| base::RunLoop run_loop; |
| RunThisRunLoop(&run_loop); |
| } |
| |
| void RunThisRunLoop(base::RunLoop* run_loop) { |
| base::CurrentThread::ScopedNestableTaskAllower allow; |
| run_loop->Run(); |
| } |
| |
| void RunAllPendingInMessageLoop() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RunAllPendingInMessageLoop(BrowserThread::UI); |
| } |
| |
| void RunAllPendingInMessageLoop(BrowserThread::ID thread_id) { |
| // See comment for |kNumQuitDeferrals| for why this is needed. |
| for (int i = 0; i <= kNumQuitDeferrals; ++i) { |
| BrowserThread::RunAllPendingTasksOnThreadForTesting(thread_id); |
| } |
| } |
| |
| void RunAllTasksUntilIdle() { |
| while (true) { |
| // Setup a task observer to determine if MessageLoop tasks run in the |
| // current loop iteration and loop in case the MessageLoop posts tasks to |
| // the Task Scheduler after the initial flush. |
| TaskObserver task_observer; |
| base::CurrentThread::Get()->AddTaskObserver(&task_observer); |
| |
| // This must use RunLoop::Type::kNestableTasksAllowed in case this |
| // RunAllTasksUntilIdle() call is nested inside an existing Run(). Without |
| // it, the QuitWhenIdleClosure() below would never run if it's posted from |
| // another thread (i.e.. by run_loop.cc's ProxyToTaskRunner). |
| base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); |
| |
| base::ThreadPoolInstance::Get()->FlushAsyncForTesting( |
| run_loop.QuitWhenIdleClosure()); |
| |
| run_loop.Run(); |
| |
| base::CurrentThread::Get()->RemoveTaskObserver(&task_observer); |
| |
| if (!task_observer.processed()) |
| break; |
| } |
| } |
| |
| base::OnceClosure GetDeferredQuitTaskForRunLoop(base::RunLoop* run_loop) { |
| return base::BindOnce(&DeferredQuitRunLoop, run_loop->QuitClosure(), |
| kNumQuitDeferrals); |
| } |
| |
| base::Value ExecuteScriptAndGetValue(RenderFrameHost* render_frame_host, |
| const std::string& script) { |
| base::RunLoop run_loop; |
| base::Value result; |
| |
| render_frame_host->ExecuteJavaScriptForTests( |
| base::UTF8ToUTF16(script), |
| base::BindOnce( |
| [](base::OnceClosure quit_closure, base::Value* out_result, |
| base::Value value) { |
| *out_result = std::move(value); |
| std::move(quit_closure).Run(); |
| }, |
| run_loop.QuitWhenIdleClosure(), &result)); |
| run_loop.Run(); |
| |
| return result; |
| } |
| |
| bool AreAllSitesIsolatedForTesting() { |
| return SiteIsolationPolicy::UseDedicatedProcessesForAllSites(); |
| } |
| |
| bool IsOriginAgentClusterEnabledForOrigin(SiteInstance* site_instance, |
| const url::Origin& origin) { |
| OriginAgentClusterIsolationState origin_requests_isolation( |
| OriginAgentClusterIsolationState::CreateNonIsolated()); |
| |
| return static_cast<ChildProcessSecurityPolicyImpl*>( |
| ChildProcessSecurityPolicy::GetInstance()) |
| ->DetermineOriginAgentClusterIsolation( |
| static_cast<SiteInstanceImpl*>(site_instance)->GetIsolationContext(), |
| origin, origin_requests_isolation) |
| .is_origin_agent_cluster(); |
| } |
| |
| bool AreDefaultSiteInstancesEnabled() { |
| return !AreAllSitesIsolatedForTesting() && |
| base::FeatureList::IsEnabled( |
| features::kProcessSharingWithDefaultSiteInstances); |
| } |
| |
| bool AreStrictSiteInstancesEnabled() { |
| return AreAllSitesIsolatedForTesting() || |
| base::FeatureList::IsEnabled( |
| features::kProcessSharingWithStrictSiteInstances); |
| } |
| |
| bool IsIsolatedOriginRequiredToGuaranteeDedicatedProcess() { |
| return AreDefaultSiteInstancesEnabled() || |
| base::FeatureList::IsEnabled( |
| features::kProcessSharingWithStrictSiteInstances); |
| } |
| |
| void IsolateAllSitesForTesting(base::CommandLine* command_line) { |
| command_line->AppendSwitch(switches::kSitePerProcess); |
| } |
| |
| bool CanSameSiteMainFrameNavigationsChangeRenderFrameHosts() { |
| // TODO(crbug.com/936696): Also return true when RenderDocument for main frame |
| // is enabled. |
| return CanSameSiteMainFrameNavigationsChangeSiteInstances(); |
| } |
| |
| bool CanSameSiteMainFrameNavigationsChangeSiteInstances() { |
| return IsProactivelySwapBrowsingInstanceOnSameSiteNavigationEnabled() || |
| IsSameSiteBackForwardCacheEnabled(); |
| } |
| |
| void DisableProactiveBrowsingInstanceSwapFor(RenderFrameHost* rfh) { |
| if (!CanSameSiteMainFrameNavigationsChangeSiteInstances()) |
| return; |
| // If the RFH is not a primary main frame, navigations on it will never result |
| // in a proactive BrowsingInstance swap, so we shouldn't call this function on |
| // subframes. |
| DCHECK(rfh->IsInPrimaryMainFrame()); |
| static_cast<RenderFrameHostImpl*>(rfh) |
| ->DisableProactiveBrowsingInstanceSwapForTesting(); |
| } |
| |
| GURL GetWebUIURL(const std::string& host) { |
| return GURL(GetWebUIURLString(host)); |
| } |
| |
| std::string GetWebUIURLString(const std::string& host) { |
| return std::string(content::kChromeUIScheme) + url::kStandardSchemeSeparator + |
| host; |
| } |
| |
| WebContents* CreateAndAttachInnerContents(RenderFrameHost* rfh) { |
| auto* outer_contents = WebContents::FromRenderFrameHost(rfh); |
| if (!outer_contents) |
| return nullptr; |
| |
| WebContents::CreateParams inner_params(outer_contents->GetBrowserContext()); |
| |
| std::unique_ptr<WebContents> inner_contents_ptr = |
| WebContents::Create(inner_params); |
| |
| // Attach. |inner_contents| becomes owned by |outer_contents|. |
| WebContents* inner_contents = inner_contents_ptr.get(); |
| outer_contents->AttachInnerWebContents( |
| std::move(inner_contents_ptr), rfh, |
| /*remote_frame=*/mojo::NullAssociatedRemote(), |
| /*remote_frame_host_receiver=*/mojo::NullAssociatedReceiver(), |
| /*is_full_page=*/false); |
| |
| return inner_contents; |
| } |
| |
| void AwaitDocumentOnLoadCompleted(WebContents* web_contents) { |
| class Awaiter : public WebContentsObserver { |
| public: |
| explicit Awaiter(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| observed_( |
| web_contents->IsDocumentOnLoadCompletedInPrimaryMainFrame()) {} |
| |
| Awaiter(const Awaiter&) = delete; |
| Awaiter& operator=(const Awaiter&) = delete; |
| |
| ~Awaiter() override = default; |
| |
| void Await() { |
| if (!observed_) |
| run_loop_.Run(); |
| DCHECK(web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame()); |
| } |
| |
| // WebContentsObserver: |
| void DocumentOnLoadCompletedInPrimaryMainFrame() override { |
| observed_ = true; |
| if (run_loop_.running()) |
| run_loop_.Quit(); |
| } |
| |
| private: |
| bool observed_ = false; |
| base::RunLoop run_loop_; |
| }; |
| |
| Awaiter(web_contents).Await(); |
| } |
| |
| MessageLoopRunner::MessageLoopRunner(QuitMode quit_mode) |
| : quit_mode_(quit_mode) {} |
| |
| MessageLoopRunner::~MessageLoopRunner() = default; |
| |
| void MessageLoopRunner::Run() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // Do not run the message loop if our quit closure has already been called. |
| // This helps in scenarios where the closure has a chance to run before |
| // we Run explicitly. |
| if (quit_closure_called_) |
| return; |
| |
| loop_running_ = true; |
| RunThisRunLoop(&run_loop_); |
| } |
| |
| base::OnceClosure MessageLoopRunner::QuitClosure() { |
| return base::BindOnce(&MessageLoopRunner::Quit, this); |
| } |
| |
| void MessageLoopRunner::Quit() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| quit_closure_called_ = true; |
| |
| // Only run the quit task if we are running the message loop. |
| if (loop_running_) { |
| switch (quit_mode_) { |
| case QuitMode::DEFERRED: |
| DeferredQuitRunLoop(run_loop_.QuitClosure(), kNumQuitDeferrals); |
| break; |
| case QuitMode::IMMEDIATE: |
| run_loop_.Quit(); |
| break; |
| } |
| loop_running_ = false; |
| } |
| } |
| |
| WindowedNotificationObserver::WindowedNotificationObserver( |
| int notification_type, |
| const NotificationSource& source) |
| : source_(NotificationService::AllSources()) { |
| AddNotificationType(notification_type, source); |
| } |
| |
| WindowedNotificationObserver::WindowedNotificationObserver( |
| int notification_type, |
| ConditionTestCallback callback) |
| : callback_(std::move(callback)), |
| source_(NotificationService::AllSources()) { |
| AddNotificationType(notification_type, source_); |
| } |
| |
| WindowedNotificationObserver::WindowedNotificationObserver( |
| int notification_type, |
| ConditionTestCallbackWithoutSourceAndDetails callback) |
| : callback_( |
| base::BindRepeating(&IgnoreSourceAndDetails, std::move(callback))), |
| source_(NotificationService::AllSources()) { |
| registrar_.Add(this, notification_type, source_); |
| } |
| |
| WindowedNotificationObserver::~WindowedNotificationObserver() = default; |
| |
| void WindowedNotificationObserver::AddNotificationType( |
| int notification_type, |
| const NotificationSource& source) { |
| registrar_.Add(this, notification_type, source); |
| } |
| |
| void WindowedNotificationObserver::Wait() { |
| if (!seen_) |
| run_loop_.Run(); |
| EXPECT_TRUE(seen_); |
| } |
| |
| void WindowedNotificationObserver::Observe(int type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| source_ = source; |
| details_ = details; |
| if (!callback_.is_null() && !callback_.Run(source, details)) |
| return; |
| |
| seen_ = true; |
| run_loop_.Quit(); |
| } |
| |
| LoadStopObserver::LoadStopObserver(WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| void LoadStopObserver::Wait() { |
| if (!seen_) |
| run_loop_.Run(); |
| |
| EXPECT_TRUE(seen_); |
| } |
| |
| void LoadStopObserver::DidStopLoading() { |
| seen_ = true; |
| run_loop_.Quit(); |
| } |
| |
| InProcessUtilityThreadHelper::InProcessUtilityThreadHelper() { |
| RenderProcessHost::SetRunRendererInProcess(true); |
| } |
| |
| InProcessUtilityThreadHelper::~InProcessUtilityThreadHelper() { |
| JoinAllUtilityThreads(); |
| RenderProcessHost::SetRunRendererInProcess(false); |
| } |
| |
| void InProcessUtilityThreadHelper::JoinAllUtilityThreads() { |
| ASSERT_FALSE(run_loop_); |
| run_loop_.emplace(); |
| BrowserChildProcessObserver::Add(this); |
| CheckHasRunningChildProcess(); |
| run_loop_->Run(); |
| run_loop_.reset(); |
| BrowserChildProcessObserver::Remove(this); |
| } |
| |
| void InProcessUtilityThreadHelper::CheckHasRunningChildProcess() { |
| ASSERT_TRUE(run_loop_); |
| |
| auto check_has_running_child_process_on_io = |
| [](base::OnceClosure quit_closure) { |
| BrowserChildProcessHostIterator it; |
| // If not Done(), we have some running child processes and need to wait. |
| if (it.Done()) |
| std::move(quit_closure).Run(); |
| }; |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(check_has_running_child_process_on_io, |
| run_loop_->QuitClosure())); |
| } |
| |
| void InProcessUtilityThreadHelper::BrowserChildProcessHostDisconnected( |
| const ChildProcessData& data) { |
| CheckHasRunningChildProcess(); |
| } |
| |
| RenderFrameDeletedObserver::RenderFrameDeletedObserver(RenderFrameHost* rfh) |
| : WebContentsObserver(WebContents::FromRenderFrameHost(rfh)), |
| rfh_id_(rfh->GetGlobalId()) { |
| DCHECK(rfh); |
| } |
| |
| RenderFrameDeletedObserver::~RenderFrameDeletedObserver() = default; |
| |
| void RenderFrameDeletedObserver::RenderFrameDeleted( |
| RenderFrameHost* render_frame_host) { |
| if (render_frame_host->GetGlobalId() == rfh_id_) { |
| rfh_id_ = GlobalRenderFrameHostId(); |
| |
| if (runner_.get()) |
| runner_->Quit(); |
| } |
| } |
| |
| bool RenderFrameDeletedObserver::deleted() const { |
| return rfh_id_ == GlobalRenderFrameHostId(); |
| } |
| |
| bool RenderFrameDeletedObserver::WaitUntilDeleted() { |
| if (deleted()) |
| return true; |
| |
| runner_ = std::make_unique<base::RunLoop>(); |
| runner_->Run(); |
| runner_.reset(); |
| return deleted(); |
| } |
| |
| RenderFrameHostWrapper::RenderFrameHostWrapper(RenderFrameHost* rfh) |
| : rfh_id_(rfh->GetGlobalId()), |
| deleted_observer_(std::make_unique<RenderFrameDeletedObserver>(rfh)) {} |
| |
| RenderFrameHostWrapper::RenderFrameHostWrapper(RenderFrameHostWrapper&& rfhft) = |
| default; |
| RenderFrameHostWrapper::~RenderFrameHostWrapper() = default; |
| |
| RenderFrameHost* RenderFrameHostWrapper::get() const { |
| return RenderFrameHost::FromID(rfh_id_); |
| } |
| |
| bool RenderFrameHostWrapper::IsDestroyed() const { |
| return get() == nullptr; |
| } |
| |
| // See RenderFrameDeletedObserver for notes on the difference between |
| // RenderFrame being deleted and RenderFrameHost being destroyed. |
| bool RenderFrameHostWrapper::WaitUntilRenderFrameDeleted() { |
| return deleted_observer_->WaitUntilDeleted(); |
| } |
| |
| bool RenderFrameHostWrapper::IsRenderFrameDeleted() const { |
| return deleted_observer_->deleted(); |
| } |
| |
| RenderFrameHost& RenderFrameHostWrapper::operator*() const { |
| DCHECK(get()); |
| return *get(); |
| } |
| |
| RenderFrameHost* RenderFrameHostWrapper::operator->() const { |
| DCHECK(get()); |
| return get(); |
| } |
| |
| WebContentsDestroyedWatcher::WebContentsDestroyedWatcher( |
| WebContents* web_contents) |
| : WebContentsObserver(web_contents) { |
| EXPECT_TRUE(web_contents != nullptr); |
| } |
| |
| WebContentsDestroyedWatcher::~WebContentsDestroyedWatcher() = default; |
| |
| void WebContentsDestroyedWatcher::Wait() { |
| run_loop_.Run(); |
| } |
| |
| void WebContentsDestroyedWatcher::WebContentsDestroyed() { |
| destroyed_ = true; |
| run_loop_.Quit(); |
| } |
| |
| TestPageScaleObserver::TestPageScaleObserver(WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| TestPageScaleObserver::~TestPageScaleObserver() = default; |
| |
| void TestPageScaleObserver::OnPageScaleFactorChanged(float page_scale_factor) { |
| last_scale_ = page_scale_factor; |
| seen_page_scale_change_ = true; |
| if (done_callback_) |
| std::move(done_callback_).Run(); |
| } |
| |
| float TestPageScaleObserver::WaitForPageScaleUpdate() { |
| if (!seen_page_scale_change_) { |
| base::RunLoop run_loop; |
| done_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| seen_page_scale_change_ = false; |
| return last_scale_; |
| } |
| |
| EffectiveURLContentBrowserClient::EffectiveURLContentBrowserClient( |
| bool requires_dedicated_process) |
| : requires_dedicated_process_(requires_dedicated_process) {} |
| |
| EffectiveURLContentBrowserClient::EffectiveURLContentBrowserClient( |
| const GURL& url_to_modify, |
| const GURL& url_to_return, |
| bool requires_dedicated_process) |
| : requires_dedicated_process_(requires_dedicated_process) { |
| AddTranslation(url_to_modify, url_to_return); |
| } |
| |
| EffectiveURLContentBrowserClient::~EffectiveURLContentBrowserClient() = default; |
| |
| void EffectiveURLContentBrowserClient::AddTranslation( |
| const GURL& url_to_modify, |
| const GURL& url_to_return) { |
| urls_to_modify_[url_to_modify] = url_to_return; |
| } |
| |
| GURL EffectiveURLContentBrowserClient::GetEffectiveURL( |
| BrowserContext* browser_context, |
| const GURL& url) { |
| auto it = urls_to_modify_.find(url); |
| if (it != urls_to_modify_.end()) |
| return it->second; |
| return url; |
| } |
| |
| bool EffectiveURLContentBrowserClient::DoesSiteRequireDedicatedProcess( |
| BrowserContext* browser_context, |
| const GURL& effective_site_url) { |
| if (!requires_dedicated_process_) |
| return false; |
| |
| for (const auto& pair : urls_to_modify_) { |
| auto site_info = SiteInfo::CreateForTesting( |
| IsolationContext(browser_context), pair.first); |
| if (site_info.site_url() == effective_site_url) |
| return true; |
| } |
| return false; |
| } |
| |
| ScopedContentBrowserClientSetting::ScopedContentBrowserClientSetting( |
| ContentBrowserClient* new_client) |
| : old_client_(SetBrowserClientForTesting(new_client)) {} |
| |
| ScopedContentBrowserClientSetting::~ScopedContentBrowserClientSetting() { |
| SetBrowserClientForTesting(old_client_); |
| } |
| |
| } // namespace content |