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 {
class StopLoadingObserver : public content::NavigableContentsObserver {
public:
StopLoadingObserver() {}
~StopLoadingObserver() override {}
StopLoadingObserver() = default;
~StopLoadingObserver() override = default;
void CallOnNextStopLoading(base::OnceClosure callback) {
callback_ = std::move(callback);
......@@ -73,8 +73,8 @@ class StopLoadingObserver : public content::NavigableContentsObserver {
class NavigationObserver : public content::NavigableContentsObserver {
public:
NavigationObserver() {}
~NavigationObserver() override {}
NavigationObserver() = default;
~NavigationObserver() override = default;
void CallOnNextNavigation(base::OnceClosure callback) {
callback_ = std::move(callback);
......@@ -102,6 +102,35 @@ class NavigationObserver : public content::NavigableContentsObserver {
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
// end-to-end sanity check to also verify that a NavigableContents is backed by
// a WebContents instance in the browser.
......@@ -137,5 +166,57 @@ IN_PROC_BROWSER_TEST_F(ContentServiceBrowserTest, DidFinishNavigation) {
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 content
......@@ -6,10 +6,12 @@
#include "base/macros.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/web_contents.h"
#include "content/public/browser/web_contents_delegate.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/service.h"
......@@ -33,6 +35,13 @@ class NavigableContentsDelegateImpl : public content::NavigableContentsDelegate,
web_contents_ = WebContents::Create(create_params);
WebContentsObserver::Observe(web_contents_.get());
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 {
......@@ -55,6 +64,13 @@ class NavigableContentsDelegateImpl : public content::NavigableContentsDelegate,
}
// 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,
const gfx::Size& new_size) override {
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") {
"//base",
"//net",
"//services/content/public/mojom",
"//ui/base",
"//ui/gfx:native_widget_types",
"//ui/gfx/geometry",
"//url",
......@@ -39,10 +40,7 @@ component("cpp") {
deps = []
if (toolkit_views) {
deps += [
"//ui/base",
"//ui/views",
]
deps += [ "//ui/views" ]
if (enable_remote_navigable_contents_view) {
deps += [
......
......@@ -72,6 +72,13 @@ void NavigableContents::DidAutoResizeView(const gfx::Size& 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(
const base::UnguessableToken& token) {
DCHECK(view_);
......
......@@ -61,6 +61,9 @@ class COMPONENT_EXPORT(CONTENT_SERVICE_CPP) NavigableContents
const scoped_refptr<net::HttpResponseHeaders>& response_headers) override;
void DidStopLoading() 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);
......
......@@ -8,6 +8,7 @@
#include "base/component_export.h"
#include "base/observer_list_types.h"
#include "net/http/http_response_headers.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
......@@ -23,6 +24,9 @@ class COMPONENT_EXPORT(CONTENT_SERVICE_CPP) NavigableContentsObserver
const net::HttpResponseHeaders* response_headers) {}
virtual void DidStopLoading() {}
virtual void DidAutoResizeView(const gfx::Size& new_size) {}
virtual void DidSuppressNavigation(const GURL& url,
WindowOpenDisposition disposition,
bool from_user_gesture) {}
};
} // namespace content
......
......@@ -20,6 +20,7 @@ mojom_component("mojom") {
public_deps = [
"//mojo/public/mojom/base",
"//services/network/public/mojom:websocket_mojom",
"//ui/base/mojo",
"//ui/gfx/geometry/mojo",
"//url/mojom:url_mojom_gurl",
]
......
......@@ -6,6 +6,7 @@ module content.mojom;
import "mojo/public/mojom/base/unguessable_token.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 "url/mojom/url.mojom";
......@@ -60,4 +61,13 @@ interface NavigableContentsClient {
// creating the corresponding NavigableContents. The client may use this as a
// signal to, e.g., resize a UI element containing the content view.
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 {
// |true|, the corresponding NavigableContentsClient will receive
// |DidAutoResizeView()| notifications whenever such resizing happens.
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
......
......@@ -37,6 +37,9 @@ class TestNavigableContentsClient : public mojom::NavigableContentsClient {
response_headers) override {}
void DidStopLoading() 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);
};
......
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