Commit 6bcda465 authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

[content-service] Allow custom navigation handling

Adds a |suppress_navigations| option to NavigableContentsParams,
allowing clients to block navigations from occurring as a result of e.g.
user gesture or scripting behavior. When enabled, suppressed navigations
instead trigger a message on the NavigableContentsClient interface and a
corresponding event on any attached NavigableContentsObservers.

Bug: 854367
Change-Id: I85b4a0ca970f83a4d0b27e56843f99841434c483
Reviewed-on: https://chromium-review.googlesource.com/c/1269444
Commit-Queue: Ken Rockot <rockot@chromium.org>
Reviewed-by: default avatarCamille Lamy <clamy@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599748}
parent 085ecdf2
...@@ -52,8 +52,8 @@ class ContentServiceBrowserTest : public ContentBrowserTest { ...@@ -52,8 +52,8 @@ class ContentServiceBrowserTest : public ContentBrowserTest {
class StopLoadingObserver : public content::NavigableContentsObserver { class StopLoadingObserver : public content::NavigableContentsObserver {
public: public:
StopLoadingObserver() {} StopLoadingObserver() = default;
~StopLoadingObserver() override {} ~StopLoadingObserver() override = default;
void CallOnNextStopLoading(base::OnceClosure callback) { void CallOnNextStopLoading(base::OnceClosure callback) {
callback_ = std::move(callback); callback_ = std::move(callback);
...@@ -73,8 +73,8 @@ class StopLoadingObserver : public content::NavigableContentsObserver { ...@@ -73,8 +73,8 @@ class StopLoadingObserver : public content::NavigableContentsObserver {
class NavigationObserver : public content::NavigableContentsObserver { class NavigationObserver : public content::NavigableContentsObserver {
public: public:
NavigationObserver() {} NavigationObserver() = default;
~NavigationObserver() override {} ~NavigationObserver() override = default;
void CallOnNextNavigation(base::OnceClosure callback) { void CallOnNextNavigation(base::OnceClosure callback) {
callback_ = std::move(callback); callback_ = std::move(callback);
...@@ -102,6 +102,35 @@ class NavigationObserver : public content::NavigableContentsObserver { ...@@ -102,6 +102,35 @@ class NavigationObserver : public content::NavigableContentsObserver {
DISALLOW_COPY_AND_ASSIGN(NavigationObserver); DISALLOW_COPY_AND_ASSIGN(NavigationObserver);
}; };
class NavigationSuppressedObserver : public content::NavigableContentsObserver {
public:
NavigationSuppressedObserver() = default;
~NavigationSuppressedObserver() override = default;
void CallOnNextNavigationSuppression(base::OnceClosure callback) {
callback_ = std::move(callback);
}
size_t navigations_suppressed() const { return navigations_suppressed_; }
const GURL& last_url() const { return last_url_; }
private:
void DidSuppressNavigation(const GURL& url,
WindowOpenDisposition disposition,
bool from_user_gesture) override {
++navigations_suppressed_;
last_url_ = url;
if (callback_)
std::move(callback_).Run();
}
base::OnceClosure callback_;
size_t navigations_suppressed_ = 0;
GURL last_url_;
DISALLOW_COPY_AND_ASSIGN(NavigationSuppressedObserver);
};
// Verifies that the embedded Content Service is reachable. Does a basic // Verifies that the embedded Content Service is reachable. Does a basic
// end-to-end sanity check to also verify that a NavigableContents is backed by // end-to-end sanity check to also verify that a NavigableContents is backed by
// a WebContents instance in the browser. // a WebContents instance in the browser.
...@@ -137,5 +166,57 @@ IN_PROC_BROWSER_TEST_F(ContentServiceBrowserTest, DidFinishNavigation) { ...@@ -137,5 +166,57 @@ IN_PROC_BROWSER_TEST_F(ContentServiceBrowserTest, DidFinishNavigation) {
EXPECT_EQ(kTestUrl, observer.last_url()); EXPECT_EQ(kTestUrl, observer.last_url());
} }
IN_PROC_BROWSER_TEST_F(ContentServiceBrowserTest, SuppressNavigations) {
auto params = mojom::NavigableContentsParams::New();
params->suppress_navigations = true;
auto contents =
std::make_unique<NavigableContents>(GetFactory(), std::move(params));
NavigationObserver navigation_observer;
NavigationSuppressedObserver suppressed_observer;
base::RunLoop suppressed_loop;
suppressed_observer.CallOnNextNavigationSuppression(
suppressed_loop.QuitClosure());
contents->AddObserver(&navigation_observer);
contents->AddObserver(&suppressed_observer);
// This URL is expected to elicit an automatic navigation on load, which
// should be suppressed because |suppress_navigations| is |true|.
const GURL kTestUrl =
embedded_test_server()->GetURL("/navigate_on_load.html");
contents->Navigate(kTestUrl);
EXPECT_EQ(0u, navigation_observer.navigations_finished());
EXPECT_EQ(0u, suppressed_observer.navigations_suppressed());
suppressed_loop.Run();
EXPECT_EQ(1u, navigation_observer.navigations_finished());
EXPECT_EQ(1u, suppressed_observer.navigations_suppressed());
EXPECT_EQ(kTestUrl, navigation_observer.last_url());
EXPECT_EQ(embedded_test_server()->GetURL("/hello.html"),
suppressed_observer.last_url());
// Now force a navigation on the same contents. It should complete and we can
// verify that only two navigations (the initial explicit navigation above,
// and the one we do here) were completed. This effectively confirms that the
// scripted navigation to "/hello.html" was fully suppressed.
const GURL kTestUrl2 = embedded_test_server()->GetURL("/title1.html");
contents->Navigate(kTestUrl2);
base::RunLoop navigation_loop;
navigation_observer.CallOnNextNavigation(navigation_loop.QuitClosure());
navigation_loop.Run();
contents->RemoveObserver(&navigation_observer);
contents->RemoveObserver(&suppressed_observer);
EXPECT_EQ(2u, navigation_observer.navigations_finished());
EXPECT_EQ(kTestUrl2, navigation_observer.last_url());
}
} // namespace } // namespace
} // namespace content } // namespace content
...@@ -6,10 +6,12 @@ ...@@ -6,10 +6,12 @@
#include "base/macros.h" #include "base/macros.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
#include "content/public/common/renderer_preferences.h"
#include "services/content/navigable_contents_delegate.h" #include "services/content/navigable_contents_delegate.h"
#include "services/content/service.h" #include "services/content/service.h"
...@@ -33,6 +35,13 @@ class NavigableContentsDelegateImpl : public content::NavigableContentsDelegate, ...@@ -33,6 +35,13 @@ class NavigableContentsDelegateImpl : public content::NavigableContentsDelegate,
web_contents_ = WebContents::Create(create_params); web_contents_ = WebContents::Create(create_params);
WebContentsObserver::Observe(web_contents_.get()); WebContentsObserver::Observe(web_contents_.get());
web_contents_->SetDelegate(this); web_contents_->SetDelegate(this);
content::RendererPreferences* renderer_prefs =
web_contents_->GetMutableRendererPrefs();
renderer_prefs->can_accept_load_drops = false;
renderer_prefs->browser_handles_all_top_level_requests =
params.suppress_navigations;
web_contents_->GetRenderViewHost()->SyncRendererPrefs();
} }
~NavigableContentsDelegateImpl() override { ~NavigableContentsDelegateImpl() override {
...@@ -55,6 +64,13 @@ class NavigableContentsDelegateImpl : public content::NavigableContentsDelegate, ...@@ -55,6 +64,13 @@ class NavigableContentsDelegateImpl : public content::NavigableContentsDelegate,
} }
// WebContentsDelegate: // WebContentsDelegate:
WebContents* OpenURLFromTab(WebContents* source,
const OpenURLParams& params) override {
client_->DidSuppressNavigation(params.url, params.disposition,
params.user_gesture);
return nullptr;
}
void ResizeDueToAutoResize(WebContents* web_contents, void ResizeDueToAutoResize(WebContents* web_contents,
const gfx::Size& new_size) override { const gfx::Size& new_size) override {
DCHECK_EQ(web_contents, web_contents_.get()); DCHECK_EQ(web_contents, web_contents_.get());
......
<html>
<head>
<title>Navigate On Load</title>
<script type="text/javascript">
window.onload = () => {
location.href = '/hello.html';
};
</script>
</head>
<body>
</body>
</html>
...@@ -32,6 +32,7 @@ component("cpp") { ...@@ -32,6 +32,7 @@ component("cpp") {
"//base", "//base",
"//net", "//net",
"//services/content/public/mojom", "//services/content/public/mojom",
"//ui/base",
"//ui/gfx:native_widget_types", "//ui/gfx:native_widget_types",
"//ui/gfx/geometry", "//ui/gfx/geometry",
"//url", "//url",
...@@ -39,10 +40,7 @@ component("cpp") { ...@@ -39,10 +40,7 @@ component("cpp") {
deps = [] deps = []
if (toolkit_views) { if (toolkit_views) {
deps += [ deps += [ "//ui/views" ]
"//ui/base",
"//ui/views",
]
if (enable_remote_navigable_contents_view) { if (enable_remote_navigable_contents_view) {
deps += [ deps += [
......
...@@ -72,6 +72,13 @@ void NavigableContents::DidAutoResizeView(const gfx::Size& new_size) { ...@@ -72,6 +72,13 @@ void NavigableContents::DidAutoResizeView(const gfx::Size& new_size) {
observer.DidAutoResizeView(new_size); observer.DidAutoResizeView(new_size);
} }
void NavigableContents::DidSuppressNavigation(const GURL& url,
WindowOpenDisposition disposition,
bool from_user_gesture) {
for (auto& observer : observers_)
observer.DidSuppressNavigation(url, disposition, from_user_gesture);
}
void NavigableContents::OnEmbedTokenReceived( void NavigableContents::OnEmbedTokenReceived(
const base::UnguessableToken& token) { const base::UnguessableToken& token) {
DCHECK(view_); DCHECK(view_);
......
...@@ -61,6 +61,9 @@ class COMPONENT_EXPORT(CONTENT_SERVICE_CPP) NavigableContents ...@@ -61,6 +61,9 @@ class COMPONENT_EXPORT(CONTENT_SERVICE_CPP) NavigableContents
const scoped_refptr<net::HttpResponseHeaders>& response_headers) override; const scoped_refptr<net::HttpResponseHeaders>& response_headers) override;
void DidStopLoading() override; void DidStopLoading() override;
void DidAutoResizeView(const gfx::Size& new_size) override; void DidAutoResizeView(const gfx::Size& new_size) override;
void DidSuppressNavigation(const GURL& url,
WindowOpenDisposition disposition,
bool from_user_gesture) override;
void OnEmbedTokenReceived(const base::UnguessableToken& token); void OnEmbedTokenReceived(const base::UnguessableToken& token);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/component_export.h" #include "base/component_export.h"
#include "base/observer_list_types.h" #include "base/observer_list_types.h"
#include "net/http/http_response_headers.h" #include "net/http/http_response_headers.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -23,6 +24,9 @@ class COMPONENT_EXPORT(CONTENT_SERVICE_CPP) NavigableContentsObserver ...@@ -23,6 +24,9 @@ class COMPONENT_EXPORT(CONTENT_SERVICE_CPP) NavigableContentsObserver
const net::HttpResponseHeaders* response_headers) {} const net::HttpResponseHeaders* response_headers) {}
virtual void DidStopLoading() {} virtual void DidStopLoading() {}
virtual void DidAutoResizeView(const gfx::Size& new_size) {} virtual void DidAutoResizeView(const gfx::Size& new_size) {}
virtual void DidSuppressNavigation(const GURL& url,
WindowOpenDisposition disposition,
bool from_user_gesture) {}
}; };
} // namespace content } // namespace content
......
...@@ -20,6 +20,7 @@ mojom_component("mojom") { ...@@ -20,6 +20,7 @@ mojom_component("mojom") {
public_deps = [ public_deps = [
"//mojo/public/mojom/base", "//mojo/public/mojom/base",
"//services/network/public/mojom:websocket_mojom", "//services/network/public/mojom:websocket_mojom",
"//ui/base/mojo",
"//ui/gfx/geometry/mojo", "//ui/gfx/geometry/mojo",
"//url/mojom:url_mojom_gurl", "//url/mojom:url_mojom_gurl",
] ]
......
...@@ -6,6 +6,7 @@ module content.mojom; ...@@ -6,6 +6,7 @@ module content.mojom;
import "mojo/public/mojom/base/unguessable_token.mojom"; import "mojo/public/mojom/base/unguessable_token.mojom";
import "services/network/public/mojom/network_param.mojom"; import "services/network/public/mojom/network_param.mojom";
import "ui/base/mojo/window_open_disposition.mojom";
import "ui/gfx/geometry/mojo/geometry.mojom"; import "ui/gfx/geometry/mojo/geometry.mojom";
import "url/mojom/url.mojom"; import "url/mojom/url.mojom";
...@@ -60,4 +61,13 @@ interface NavigableContentsClient { ...@@ -60,4 +61,13 @@ interface NavigableContentsClient {
// creating the corresponding NavigableContents. The client may use this as a // creating the corresponding NavigableContents. The client may use this as a
// signal to, e.g., resize a UI element containing the content view. // signal to, e.g., resize a UI element containing the content view.
DidAutoResizeView(gfx.mojom.Size new_size); DidAutoResizeView(gfx.mojom.Size new_size);
// Notifies the client that a navigation was attempted by the contents (e.g.
// by user gesture or script behavior), but it was suppressed because
// |NavigableContentsParams.suppress_navigations| was set to |true| when the
// NavigableContents was created. See that flag's documentation for details
// regarding which types of navigations it can affect.
DidSuppressNavigation(url.mojom.Url url,
ui.mojom.WindowOpenDisposition disposition,
bool from_user_gesture);
}; };
...@@ -12,6 +12,15 @@ struct NavigableContentsParams { ...@@ -12,6 +12,15 @@ struct NavigableContentsParams {
// |true|, the corresponding NavigableContentsClient will receive // |true|, the corresponding NavigableContentsClient will receive
// |DidAutoResizeView()| notifications whenever such resizing happens. // |DidAutoResizeView()| notifications whenever such resizing happens.
bool enable_view_auto_resize = false; bool enable_view_auto_resize = false;
// Indicates that the client wants to control how navigation requests are
// handled within the created NavigableContents. Any attempt to navigate the
// NavigableContents by any means other than an explicit call to
// |NavigableContents.Navigate()| -- for example, link clicks or scripted
// location changes -- will be suppressed and will instead result in a
// |DidSuppressNavigation()| message being sent to the corresponding
// NavigableContentsClient.
bool suppress_navigations = false;
}; };
// NavigableContentsFactory is the primary interface through which a new // NavigableContentsFactory is the primary interface through which a new
......
...@@ -37,6 +37,9 @@ class TestNavigableContentsClient : public mojom::NavigableContentsClient { ...@@ -37,6 +37,9 @@ class TestNavigableContentsClient : public mojom::NavigableContentsClient {
response_headers) override {} response_headers) override {}
void DidStopLoading() override {} void DidStopLoading() override {}
void DidAutoResizeView(const gfx::Size& new_size) override {} void DidAutoResizeView(const gfx::Size& new_size) override {}
void DidSuppressNavigation(const GURL& url,
WindowOpenDisposition disposition,
bool from_user_gesture) override {}
DISALLOW_COPY_AND_ASSIGN(TestNavigableContentsClient); DISALLOW_COPY_AND_ASSIGN(TestNavigableContentsClient);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment