summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authornasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-25 23:01:06 +0000
committernasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-25 23:01:06 +0000
commit893558b0bc3f65c412c552e1c248c68c62df9b1b (patch)
treeb45836fe6949cf7f5c87b85f7df0c6d164f99c9f /content
parentb98c8b5d4fe34cb8a0cf961af55f60673b86e96d (diff)
downloadchromium_src-893558b0bc3f65c412c552e1c248c68c62df9b1b.zip
chromium_src-893558b0bc3f65c412c552e1c248c68c62df9b1b.tar.gz
chromium_src-893558b0bc3f65c412c552e1c248c68c62df9b1b.tar.bz2
Perform navigation policy check on UI thread for --site-per-process.
This CL adds a NavigateFrameToURL method, which will allow browser tests to navigate specific (sub)frames and more precise out-of-process iframes tests can be written. It also changes how cross-site transition is done for --site-per-process. It used to depend on the Referer header, but if there is a navigation to data URL, it will be empty and there will always be a transfer to new renderer. I'm changing it to do the check on the UI thread and for now to be a very simplistic one - is the URL in the same SiteInstance as the current renderer. BUG=357747 Review URL: https://codereview.chromium.org/248963007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@266267 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/browser/frame_host/interstitial_page_navigator_impl.cc8
-rw-r--r--content/browser/frame_host/interstitial_page_navigator_impl.h4
-rw-r--r--content/browser/frame_host/navigator.cc4
-rw-r--r--content/browser/frame_host/navigator.h3
-rw-r--r--content/browser/frame_host/navigator_impl.cc4
-rw-r--r--content/browser/frame_host/navigator_impl.h1
-rw-r--r--content/browser/loader/cross_site_resource_handler.cc85
-rw-r--r--content/browser/loader/cross_site_resource_handler.h14
-rw-r--r--content/browser/site_per_process_browsertest.cc39
-rw-r--r--content/content_tests.gypi4
-rw-r--r--content/test/content_browser_test_utils_internal.cc23
-rw-r--r--content/test/content_browser_test_utils_internal.h25
-rw-r--r--content/test/test_frame_navigation_observer.cc80
-rw-r--r--content/test/test_frame_navigation_observer.h75
14 files changed, 341 insertions, 28 deletions
diff --git a/content/browser/frame_host/interstitial_page_navigator_impl.cc b/content/browser/frame_host/interstitial_page_navigator_impl.cc
index 82f40af..fc45950 100644
--- a/content/browser/frame_host/interstitial_page_navigator_impl.cc
+++ b/content/browser/frame_host/interstitial_page_navigator_impl.cc
@@ -5,6 +5,7 @@
#include "content/browser/frame_host/interstitial_page_navigator_impl.h"
#include "content/browser/frame_host/interstitial_page_impl.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
#include "content/browser/frame_host/navigator_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
@@ -13,7 +14,12 @@ namespace content {
InterstitialPageNavigatorImpl::InterstitialPageNavigatorImpl(
InterstitialPageImpl* interstitial,
NavigationControllerImpl* navigation_controller)
- : interstitial_(interstitial) {}
+ : interstitial_(interstitial),
+ controller_(navigation_controller) {}
+
+NavigationController* InterstitialPageNavigatorImpl::GetController() {
+ return controller_;
+}
void InterstitialPageNavigatorImpl::DidNavigate(
RenderFrameHostImpl* render_frame_host,
diff --git a/content/browser/frame_host/interstitial_page_navigator_impl.h b/content/browser/frame_host/interstitial_page_navigator_impl.h
index 2b57f7a..7077546 100644
--- a/content/browser/frame_host/interstitial_page_navigator_impl.h
+++ b/content/browser/frame_host/interstitial_page_navigator_impl.h
@@ -22,6 +22,7 @@ class CONTENT_EXPORT InterstitialPageNavigatorImpl : public Navigator {
InterstitialPageImpl* interstitial,
NavigationControllerImpl* navigation_controller);
+ virtual NavigationController* GetController() OVERRIDE;
virtual void DidNavigate(
RenderFrameHostImpl* render_frame_host,
const FrameHostMsg_DidCommitProvisionalLoad_Params&
@@ -34,6 +35,9 @@ class CONTENT_EXPORT InterstitialPageNavigatorImpl : public Navigator {
// Non owned pointer.
InterstitialPageImpl* interstitial_;
+ // The NavigationController associated with this navigator.
+ NavigationControllerImpl* controller_;
+
DISALLOW_COPY_AND_ASSIGN(InterstitialPageNavigatorImpl);
};
diff --git a/content/browser/frame_host/navigator.cc b/content/browser/frame_host/navigator.cc
index 6b51a91..3d5eb8f9 100644
--- a/content/browser/frame_host/navigator.cc
+++ b/content/browser/frame_host/navigator.cc
@@ -8,6 +8,10 @@
namespace content {
+NavigationController* Navigator::GetController() {
+ return NULL;
+}
+
bool Navigator::NavigateToPendingEntry(
RenderFrameHostImpl* render_frame_host,
NavigationController::ReloadType reload_type) {
diff --git a/content/browser/frame_host/navigator.h b/content/browser/frame_host/navigator.h
index 5d7b904..b229ffb 100644
--- a/content/browser/frame_host/navigator.h
+++ b/content/browser/frame_host/navigator.h
@@ -34,6 +34,9 @@ class RenderFrameHostImpl;
// from WebContentsImpl to this interface.
class CONTENT_EXPORT Navigator : public base::RefCounted<Navigator> {
public:
+ // Returns the NavigationController associated with this Navigator.
+ virtual NavigationController* GetController();
+
// The RenderFrameHostImpl started a provisional load.
virtual void DidStartProvisionalLoad(RenderFrameHostImpl* render_frame_host,
int parent_routing_id,
diff --git a/content/browser/frame_host/navigator_impl.cc b/content/browser/frame_host/navigator_impl.cc
index 832c58e..64d2421 100644
--- a/content/browser/frame_host/navigator_impl.cc
+++ b/content/browser/frame_host/navigator_impl.cc
@@ -137,6 +137,10 @@ NavigatorImpl::NavigatorImpl(
delegate_(delegate) {
}
+NavigationController* NavigatorImpl::GetController() {
+ return controller_;
+}
+
void NavigatorImpl::DidStartProvisionalLoad(
RenderFrameHostImpl* render_frame_host,
int parent_routing_id,
diff --git a/content/browser/frame_host/navigator_impl.h b/content/browser/frame_host/navigator_impl.h
index 47c3f37..1aa9a60 100644
--- a/content/browser/frame_host/navigator_impl.h
+++ b/content/browser/frame_host/navigator_impl.h
@@ -24,6 +24,7 @@ class CONTENT_EXPORT NavigatorImpl : public Navigator {
NavigatorDelegate* delegate);
// Navigator implementation.
+ virtual NavigationController* GetController() OVERRIDE;
virtual void DidStartProvisionalLoad(RenderFrameHostImpl* render_frame_host,
int parent_routing_id,
const GURL& url) OVERRIDE;
diff --git a/content/browser/loader/cross_site_resource_handler.cc b/content/browser/loader/cross_site_resource_handler.cc
index 8e56397..bf3f0bd 100644
--- a/content/browser/loader/cross_site_resource_handler.cc
+++ b/content/browser/loader/cross_site_resource_handler.cc
@@ -84,6 +84,20 @@ void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) {
}
}
+bool CheckNavigationPolicyOnUI(GURL url, int process_id, int render_frame_id) {
+ RenderFrameHostImpl* rfh =
+ RenderFrameHostImpl::FromID(process_id, render_frame_id);
+ if (!rfh)
+ return false;
+
+ // TODO(nasko): This check is very simplistic and is used temporarily only
+ // for --site-per-process. It should be updated to match the check performed
+ // by RenderFrameHostManager::UpdateRendererStateForNavigate.
+ return !SiteInstance::IsSameWebSite(
+ rfh->GetSiteInstance()->GetBrowserContext(),
+ rfh->GetSiteInstance()->GetSiteURL(), url);
+}
+
} // namespace
CrossSiteResourceHandler::CrossSiteResourceHandler(
@@ -93,7 +107,8 @@ CrossSiteResourceHandler::CrossSiteResourceHandler(
has_started_response_(false),
in_cross_site_transition_(false),
completed_during_transition_(false),
- did_defer_(false) {
+ did_defer_(false),
+ weak_ptr_factory_(this) {
}
CrossSiteResourceHandler::~CrossSiteResourceHandler() {
@@ -134,24 +149,14 @@ bool CrossSiteResourceHandler::OnResponseStarted(
info->GetContext(), request()->original_url(), request()->url());
// When the --site-per-process flag is passed, we transfer processes for
- // cross-site subframe navigations.
- if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess)) {
- GURL referrer(request()->referrer());
- // We skip this for WebUI processes for now, since pages like the NTP host
- // cross-site WebUI iframes but don't have referrers.
- bool is_webui_process = ChildProcessSecurityPolicyImpl::GetInstance()->
- HasWebUIBindings(info->GetChildID());
-
- // TODO(creis): This shouldn't rely on the referrer to determine the parent
- // frame's URL. This also doesn't work for hosted apps, due to passing NULL
- // to IsSameWebSite. It should be possible to always send the navigation to
- // the UI thread to make a policy decision, which could let us eliminate the
- // renderer-side check in RenderViewImpl::decidePolicyForNavigation as well.
- if (info->GetResourceType() == ResourceType::SUB_FRAME &&
- !is_webui_process &&
- !SiteInstance::IsSameWebSite(NULL, request()->url(), referrer)) {
- should_transfer = true;
- }
+ // cross-site navigations. This is skipped if a transfer is already required
+ // or for WebUI processes for now, since pages like the NTP host multiple
+ // cross-site WebUI iframes.
+ if (!should_transfer &&
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess) &&
+ !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
+ info->GetChildID())) {
+ return DeferForNavigationPolicyCheck(info, response, defer);
}
bool swap_needed = should_transfer ||
@@ -190,6 +195,15 @@ bool CrossSiteResourceHandler::OnResponseStarted(
return true;
}
+void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
+ if (is_transfer) {
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+ StartCrossSiteTransition(info->GetRequestID(), response_, is_transfer);
+ } else {
+ ResumeResponse();
+ }
+}
+
bool CrossSiteResourceHandler::OnReadCompleted(int request_id,
int bytes_read,
bool* defer) {
@@ -238,7 +252,6 @@ void CrossSiteResourceHandler::OnResponseCompleted(
// WebContentsImpl to swap in the new renderer and destroy the old one.
void CrossSiteResourceHandler::ResumeResponse() {
DCHECK(request());
- DCHECK(in_cross_site_transition_);
in_cross_site_transition_ = false;
ResourceRequestInfoImpl* info = GetRequestInfo();
@@ -326,6 +339,38 @@ void CrossSiteResourceHandler::StartCrossSiteTransition(
info->should_replace_current_entry())));
}
+bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
+ ResourceRequestInfoImpl* info,
+ ResourceResponse* response,
+ bool* defer) {
+ // Store the response_ object internally, since the navigation is deferred
+ // regardless of whether it will be a transfer or not.
+ response_ = response;
+
+ // Always defer the navigation to the UI thread to make a policy decision.
+ // It will send the result back to the IO thread to either resume or
+ // transfer it to a new renderer.
+ // TODO(nasko): If the UI thread result is that transfer is required, the
+ // IO thread will defer to the UI thread again through
+ // StartCrossSiteTransition. This is unnecessary and the policy check on the
+ // UI thread should be refactored to avoid the extra hop.
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&CheckNavigationPolicyOnUI,
+ request()->url(),
+ info->GetChildID(),
+ info->GetRenderFrameID()),
+ base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ // Defer loading until it is known whether the navigation will transfer
+ // to a new process or continue in the existing one.
+ *defer = true;
+ OnDidDefer();
+ return true;
+}
+
void CrossSiteResourceHandler::ResumeIfDeferred() {
if (did_defer_) {
request()->LogUnblocked();
diff --git a/content/browser/loader/cross_site_resource_handler.h b/content/browser/loader/cross_site_resource_handler.h
index 9f487a6..df6c7a3 100644
--- a/content/browser/loader/cross_site_resource_handler.h
+++ b/content/browser/loader/cross_site_resource_handler.h
@@ -6,6 +6,7 @@
#define CONTENT_BROWSER_LOADER_CROSS_SITE_RESOURCE_HANDLER_H_
#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
#include "content/browser/loader/layered_resource_handler.h"
#include "content/common/content_export.h"
#include "net/url_request/url_request_status.h"
@@ -59,6 +60,13 @@ class CrossSiteResourceHandler : public LayeredResourceHandler {
ResourceResponse* response,
bool should_transfer);
+ // Defer the navigation to the UI thread to check whether transfer is required
+ // or not. Currently only used in --site-per-process.
+ bool DeferForNavigationPolicyCheck(ResourceRequestInfoImpl* info,
+ ResourceResponse* response,
+ bool* defer);
+
+ void ResumeOrTransfer(bool is_transfer);
void ResumeIfDeferred();
// Called when about to defer a request. Sets |did_defer_| and logs the
@@ -73,6 +81,12 @@ class CrossSiteResourceHandler : public LayeredResourceHandler {
std::string completed_security_info_;
scoped_refptr<ResourceResponse> response_;
+ // TODO(nasko): WeakPtr is needed in --site-per-process, since all navigations
+ // are deferred to the UI thread and come back to IO thread via
+ // PostTaskAndReplyWithResult. If a transfer is needed, it goes back to the UI
+ // thread. This can be removed once the code is changed to only do one hop.
+ base::WeakPtrFactory<CrossSiteResourceHandler> weak_ptr_factory_;
+
DISALLOW_COPY_AND_ASSIGN(CrossSiteResourceHandler);
};
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 9a47b4b..5c3be86 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -17,6 +17,7 @@
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
+#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "url/gurl.h"
@@ -203,13 +204,16 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) {
GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
NavigateToURL(shell(), main_url);
- StartFrameAtDataURL();
+ // It is safe to obtain the root frame tree node here, as it doesn't change.
+ FrameTreeNode* root =
+ static_cast<WebContentsImpl*>(shell()->web_contents())->
+ GetFrameTree()->root();
SitePerProcessWebContentsObserver observer(shell()->web_contents());
// Load same-site page into iframe.
GURL http_url(test_server()->GetURL("files/title1.html"));
- EXPECT_TRUE(NavigateIframeToURL(shell(), http_url, "test"));
+ NavigateFrameToURL(root->child_at(0), http_url);
EXPECT_EQ(http_url, observer.navigation_url());
EXPECT_TRUE(observer.navigation_succeeded());
@@ -221,22 +225,43 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) {
GURL cross_site_url(test_server()->GetURL("files/title2.html"));
replace_host.SetHostStr(foo_com);
cross_site_url = cross_site_url.ReplaceComponents(replace_host);
- EXPECT_TRUE(NavigateIframeToURL(shell(), cross_site_url, "test"));
+ NavigateFrameToURL(root->child_at(0), cross_site_url);
EXPECT_EQ(cross_site_url, observer.navigation_url());
EXPECT_TRUE(observer.navigation_succeeded());
// Ensure that we have created a new process for the subframe.
- FrameTreeNode* root =
- static_cast<WebContentsImpl*>(shell()->web_contents())->
- GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
+ SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance();
+ RenderViewHost* rvh = child->current_frame_host()->render_view_host();
+ RenderProcessHost* rph = child->current_frame_host()->GetProcess();
+ EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh);
+ EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance);
+ EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph);
+
+ // Load another cross-site page into the same iframe.
+ cross_site_url = test_server()->GetURL("files/title3.html");
+ std::string bar_com("bar.com");
+ replace_host.SetHostStr(bar_com);
+ cross_site_url = cross_site_url.ReplaceComponents(replace_host);
+ NavigateFrameToURL(root->child_at(0), cross_site_url);
+ EXPECT_EQ(cross_site_url, observer.navigation_url());
+ EXPECT_TRUE(observer.navigation_succeeded());
+
+ // Check again that a new process is created and is different from the
+ // top level one and the previous one.
+ ASSERT_EQ(1U, root->child_count());
+ child = root->child_at(0);
EXPECT_NE(shell()->web_contents()->GetRenderViewHost(),
child->current_frame_host()->render_view_host());
+ EXPECT_NE(rvh, child->current_frame_host()->render_view_host());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
- child->current_frame_host()->render_view_host()->GetSiteInstance());
+ child->current_frame_host()->GetSiteInstance());
+ EXPECT_NE(site_instance,
+ child->current_frame_host()->GetSiteInstance());
EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(),
child->current_frame_host()->GetProcess());
+ EXPECT_NE(rph, child->current_frame_host()->GetProcess());
}
// Crash a subframe and ensures its children are cleared from the FrameTree.
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index c14b98c..344f690 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -154,6 +154,8 @@
'gpu/gpu_idirect3d9_mock_win.h',
'test/appcache_test_helper.cc',
'test/appcache_test_helper.h',
+ 'test/content_browser_test_utils_internal.cc',
+ 'test/content_browser_test_utils_internal.h',
'test/content_test_suite.cc',
'test/content_test_suite.h',
'test/mock_google_streaming_server.cc',
@@ -183,6 +185,8 @@
'test/test_content_browser_client.h',
'test/test_content_client.cc',
'test/test_content_client.h',
+ 'test/test_frame_navigation_observer.cc',
+ 'test/test_frame_navigation_observer.h',
'test/test_render_frame_host.cc',
'test/test_render_frame_host.h',
'test/test_render_frame_host_factory.cc',
diff --git a/content/test/content_browser_test_utils_internal.cc b/content/test/content_browser_test_utils_internal.cc
new file mode 100644
index 0000000..be503b7
--- /dev/null
+++ b/content/test/content_browser_test_utils_internal.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 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/test/content_browser_test_utils_internal.h"
+
+#include "content/browser/frame_host/frame_tree_node.h"
+#include "content/browser/frame_host/navigator.h"
+#include "content/test/test_frame_navigation_observer.h"
+#include "url/gurl.h"
+
+namespace content {
+
+void NavigateFrameToURL(FrameTreeNode* node, const GURL& url) {
+ TestFrameNavigationObserver observer(node);
+ NavigationController::LoadURLParams params(url);
+ params.transition_type = PageTransitionFromInt(PAGE_TRANSITION_LINK);
+ params.frame_tree_node_id = node->frame_tree_node_id();
+ node->navigator()->GetController()->LoadURLWithParams(params);
+ observer.Wait();
+}
+
+} // namespace content
diff --git a/content/test/content_browser_test_utils_internal.h b/content/test/content_browser_test_utils_internal.h
new file mode 100644
index 0000000..a27aea5
--- /dev/null
+++ b/content/test/content_browser_test_utils_internal.h
@@ -0,0 +1,25 @@
+// Copyright 2014 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.
+
+#ifndef CONTENT_TEST_CONTENT_BROWSER_TEST_UTILS_INTERNAL_H_
+#define CONTENT_TEST_CONTENT_BROWSER_TEST_UTILS_INTERNAL_H_
+
+// A collections of functions designed for use with content_shell based browser
+// tests internal to the content/ module.
+// Note: If a function here also works with browser_tests, it should be in
+// the content public API.
+
+class GURL;
+
+namespace content {
+
+class FrameTreeNode;
+
+// Navigates the frame represented by |node| to |url|, blocking until the
+// navigation finishes.
+void NavigateFrameToURL(FrameTreeNode* node, const GURL& url);
+
+} // namespace content
+
+#endif // CONTENT_TEST_CONTENT_BROWSER_TEST_UTILS_INTERNAL_H_
diff --git a/content/test/test_frame_navigation_observer.cc b/content/test/test_frame_navigation_observer.cc
new file mode 100644
index 0000000..83eb093
--- /dev/null
+++ b/content/test/test_frame_navigation_observer.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 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/test/test_frame_navigation_observer.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+TestFrameNavigationObserver::TestFrameNavigationObserver(
+ FrameTreeNode* node,
+ int number_of_navigations)
+ : WebContentsObserver(
+ node->current_frame_host()->delegate()->GetAsWebContents()),
+ frame_tree_node_id_(node->frame_tree_node_id()),
+ navigation_started_(false),
+ navigations_completed_(0),
+ number_of_navigations_(number_of_navigations),
+ message_loop_runner_(new MessageLoopRunner) {
+}
+
+TestFrameNavigationObserver::TestFrameNavigationObserver(
+ FrameTreeNode* node)
+ : WebContentsObserver(
+ node->current_frame_host()->delegate()->GetAsWebContents()),
+ frame_tree_node_id_(node->frame_tree_node_id()),
+ navigation_started_(false),
+ navigations_completed_(0),
+ number_of_navigations_(1),
+ message_loop_runner_(new MessageLoopRunner) {
+}
+
+TestFrameNavigationObserver::~TestFrameNavigationObserver() {
+}
+
+void TestFrameNavigationObserver::Wait() {
+ message_loop_runner_->Run();
+}
+
+void TestFrameNavigationObserver::DidStartProvisionalLoadForFrame(
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc,
+ RenderViewHost* render_view_host) {
+ RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(
+ render_view_host->GetProcess()->GetID(), frame_id);
+ if (!rfh)
+ return;
+
+ if (rfh->frame_tree_node()->frame_tree_node_id() == frame_tree_node_id_)
+ navigation_started_ = true;
+}
+
+void TestFrameNavigationObserver::DidNavigateAnyFrame(
+ const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) {
+ if (!navigation_started_)
+ return;
+
+ ++navigations_completed_;
+ if (navigations_completed_ == number_of_navigations_) {
+ navigation_started_ = false;
+ message_loop_runner_->Quit();
+ }
+}
+
+} // namespace content
diff --git a/content/test/test_frame_navigation_observer.h b/content/test/test_frame_navigation_observer.h
new file mode 100644
index 0000000..4cccb34
--- /dev/null
+++ b/content/test/test_frame_navigation_observer.h
@@ -0,0 +1,75 @@
+// Copyright 2014 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.
+
+#ifndef CONTENT_TEST_TEST_FRAME_NAVIGATION_OBSERVER_H_
+#define CONTENT_TEST_TEST_FRAME_NAVIGATION_OBSERVER_H_
+
+#include <set>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/test/test_utils.h"
+
+class GURL;
+
+namespace content {
+class FrameTreeNode;
+class RenderFrameHostImpl;
+class WebContents;
+struct LoadCommittedDetails;
+
+// For content_browsertests, which run on the UI thread, run a second
+// MessageLoop and quit when the navigation in a specific frame completes
+// loading.
+class TestFrameNavigationObserver : public WebContentsObserver {
+ public:
+ // Create and register a new TestFrameNavigationObserver which will track
+ // navigations performed in the specified |node| of the frame tree.
+ TestFrameNavigationObserver(FrameTreeNode* node, int number_of_navigations);
+ // As above but waits for one navigation.
+ explicit TestFrameNavigationObserver(FrameTreeNode* node);
+
+ virtual ~TestFrameNavigationObserver();
+
+ // Runs a nested message loop and blocks until the expected number of
+ // navigations are complete.
+ void Wait();
+
+ private:
+ // WebContentsObserver
+ virtual void DidStartProvisionalLoadForFrame(
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc,
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidNavigateAnyFrame(
+ const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) OVERRIDE;
+
+ // The id of the FrameTreeNode in which navigations are peformed.
+ int frame_tree_node_id_;
+
+ // If true the navigation has started.
+ bool navigation_started_;
+
+ // The number of navigations that have been completed.
+ int navigations_completed_;
+
+ // The number of navigations to wait for.
+ int number_of_navigations_;
+
+ // The MessageLoopRunner used to spin the message loop.
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestFrameNavigationObserver);
+};
+
+} // namespace content
+
+#endif // CONTENT_TEST_TEST_FRAME_NAVIGATION_OBSERVER_H_