summaryrefslogtreecommitdiff
path: root/chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc')
-rw-r--r--chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc1418
1 files changed, 1418 insertions, 0 deletions
diff --git a/chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc b/chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc
new file mode 100644
index 00000000000..f25ed52f307
--- /dev/null
+++ b/chromium/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc
@@ -0,0 +1,1418 @@
+// Copyright 2017 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 "base/base64.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "build/buildflag.h"
+#include "content/browser/site_per_process_browsertest.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_download_manager_delegate.h"
+#include "net/base/escape.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "content/public/browser/plugin_service.h"
+#include "content/public/common/webplugininfo.h"
+#endif
+
+namespace content {
+
+namespace {
+
+// The pattern to catch messages printed by the browser when navigation to a
+// URL is blocked.
+const char kNavigationBlockedMessage[] =
+ "Not allowed to navigate top frame to %s URL:*";
+
+// The message printed by the data or filesystem URL when it successfully
+// navigates.
+const char kNavigationSuccessfulMessage[] = "NAVIGATION_SUCCESSFUL";
+
+// A "Hello World" pdf.
+const char kPDF[] =
+ "%PDF-1.7\n"
+ "1 0 obj << /Type /Page /Parent 3 0 R /Resources 5 0 R /Contents 2 0 R >>\n"
+ "endobj\n"
+ "2 0 obj << /Length 51 >>\n"
+ " stream BT\n"
+ " /F1 12 Tf\n"
+ " 1 0 0 1 100 20 Tm\n"
+ " (Hello World)Tj\n"
+ " ET\n"
+ " endstream\n"
+ "endobj\n"
+ "3 0 obj << /Type /Pages /Kids [ 1 0 R ] /Count 1 /MediaBox [ 0 0 300 50] "
+ ">>\n"
+ "endobj\n"
+ "4 0 obj << /Type /Font /Subtype /Type1 /Name /F1 /BaseFont/Arial >>\n"
+ "endobj\n"
+ "5 0 obj << /ProcSet[/PDF/Text] /Font <</F1 4 0 R >> >>\n"
+ "endobj\n"
+ "6 0 obj << /Type /Catalog /Pages 3 0 R >>\n"
+ "endobj\n"
+ "trailer << /Root 6 0 R >>\n";
+
+enum ExpectedNavigationStatus { NAVIGATION_BLOCKED, NAVIGATION_ALLOWED };
+
+// This class is similar to ConsoleObserverDelegate in that it listens and waits
+// for specific console messages. The difference from ConsoleObserverDelegate is
+// that this class immediately stops waiting if it sees a message matching
+// fail_pattern, instead of waiting for a message matching success_pattern.
+class BlockedURLWarningConsoleObserverDelegate : public WebContentsDelegate {
+ public:
+ enum Status {
+ NO_MESSAGE,
+ SAW_SUCCESS_MESSAGE,
+ SAW_FAILURE_MESSAGE,
+ };
+ BlockedURLWarningConsoleObserverDelegate(WebContents* web_contents,
+ const std::string& success_filter,
+ const std::string& fail_filter)
+ : web_contents_(web_contents),
+ success_filter_(success_filter),
+ fail_filter_(fail_filter),
+ status_(NO_MESSAGE) {}
+
+ ~BlockedURLWarningConsoleObserverDelegate() override {}
+
+ // WebContentsDelegate method:
+ bool DidAddMessageToConsole(WebContents* source,
+ int32_t level,
+ const base::string16& message,
+ int32_t line_no,
+ const base::string16& source_id) override {
+ DCHECK(source == web_contents_);
+ const std::string ascii_message = base::UTF16ToASCII(message);
+ if (base::MatchPattern(ascii_message, fail_filter_)) {
+ status_ = SAW_FAILURE_MESSAGE;
+ run_loop_.Quit();
+ }
+ if (base::MatchPattern(ascii_message, success_filter_)) {
+ status_ = SAW_SUCCESS_MESSAGE;
+ run_loop_.Quit();
+ }
+ return false;
+ }
+
+ void Wait() { run_loop_.Run(); }
+
+ Status status() const { return status_; }
+
+ private:
+ WebContents* web_contents_;
+ const std::string success_filter_;
+ const std::string fail_filter_;
+ base::RunLoop run_loop_;
+ Status status_;
+};
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+// This class registers a fake PDF plugin handler so that navigations with a PDF
+// mime type end up with a navigation and don't simply download the file.
+class ScopedPluginRegister {
+ public:
+ ScopedPluginRegister(content::PluginService* plugin_service)
+ : plugin_service_(plugin_service) {
+ const char kPluginName[] = "PDF";
+ const char kPdfMimeType[] = "application/pdf";
+ const char kPdfFileType[] = "pdf";
+ WebPluginInfo plugin_info;
+ plugin_info.type = WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS;
+ plugin_info.name = base::ASCIIToUTF16(kPluginName);
+ plugin_info.mime_types.push_back(
+ WebPluginMimeType(kPdfMimeType, kPdfFileType, std::string()));
+ plugin_service_->RegisterInternalPlugin(plugin_info, false);
+ plugin_service_->RefreshPlugins();
+ }
+
+ ~ScopedPluginRegister() {
+ std::vector<WebPluginInfo> plugins;
+ plugin_service_->GetInternalPlugins(&plugins);
+ EXPECT_EQ(1u, plugins.size());
+ plugin_service_->UnregisterInternalPlugin(plugins[0].path);
+ plugin_service_->RefreshPlugins();
+
+ plugins.clear();
+ plugin_service_->GetInternalPlugins(&plugins);
+ EXPECT_TRUE(plugins.empty());
+ }
+
+ private:
+ content::PluginService* plugin_service_;
+};
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+} // namespace
+
+class BlockedSchemeNavigationBrowserTest
+ : public ContentBrowserTest,
+ public testing::WithParamInterface<const char*> {
+ public:
+#if BUILDFLAG(ENABLE_PLUGINS)
+ BlockedSchemeNavigationBrowserTest()
+ : scoped_plugin_register_(PluginService::GetInstance()) {}
+#else
+ BlockedSchemeNavigationBrowserTest() {}
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+ protected:
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ base::FilePath path;
+ ASSERT_TRUE(base::PathService::Get(content::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("data_url_navigations.html");
+ ASSERT_TRUE(base::PathExists(path));
+
+ std::string contents;
+ ASSERT_TRUE(base::ReadFileToString(path, &contents));
+ data_url_ = GURL(std::string("data:text/html,") + contents);
+
+ ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
+ ShellDownloadManagerDelegate* delegate =
+ static_cast<ShellDownloadManagerDelegate*>(
+ shell()
+ ->web_contents()
+ ->GetBrowserContext()
+ ->GetDownloadManagerDelegate());
+ delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
+ }
+
+ void Navigate(const GURL& url) {
+ content::DOMMessageQueue message_queue;
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+ std::string message;
+ while (message_queue.WaitForMessage(&message)) {
+ if (message == "\"READY\"")
+ break;
+ }
+ }
+
+ // Creates a filesystem: URL on the current origin.
+ GURL CreateFileSystemUrl(const std::string& filename,
+ const std::string& content,
+ const std::string& mime_type) {
+ const char kCreateFilesystemUrlScript[] =
+ "var contents = `%s`;"
+ "webkitRequestFileSystem(window.TEMPORARY, 1024, fs => {"
+ " fs.root.getFile('%s', {create: true}, entry => {"
+ " entry.createWriter(w => {"
+ " w.write(new Blob([contents], {type: '%s'}));"
+ " w.onwrite = function(evt) {"
+ " domAutomationController.send(entry.toURL());"
+ " }"
+ " });"
+ " });"
+ "});";
+ std::string filesystem_url_string;
+ EXPECT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents()->GetMainFrame(),
+ base::StringPrintf(kCreateFilesystemUrlScript, content.c_str(),
+ filename.c_str(), mime_type.c_str()),
+ &filesystem_url_string));
+ GURL filesystem_url(filesystem_url_string);
+ EXPECT_TRUE(filesystem_url.is_valid());
+ EXPECT_TRUE(filesystem_url.SchemeIsFileSystem());
+ return filesystem_url;
+ }
+
+ bool IsDataURLTest() const {
+ return std::string(url::kDataScheme) == GetParam();
+ }
+
+ GURL CreateEmptyURLWithBlockedScheme() {
+ return CreateURLWithBlockedScheme("empty.html", "<html></html>",
+ "text/html");
+ }
+
+ GURL CreateURLWithBlockedScheme(const std::string& filename,
+ const std::string& content,
+ const std::string& mimetype) {
+ if (IsDataURLTest()) {
+ return GURL(
+ base::StringPrintf("data:%s,%s", mimetype.c_str(), content.c_str()));
+ }
+ // We need an origin to create a filesystem URL on, so navigate to one.
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("a.com", "/simple_page.html"));
+ return CreateFileSystemUrl(filename, content, mimetype);
+ }
+
+ GURL GetTestURL() {
+ return embedded_test_server()->GetURL(
+ base::StringPrintf("/%s_url_navigations.html", GetParam()));
+ }
+
+ // Adds an iframe to |rfh| pointing to |url|.
+ void AddIFrame(RenderFrameHost* rfh, const GURL& url) {
+ content::DOMMessageQueue message_queue;
+ const std::string javascript = base::StringPrintf(
+ "f = document.createElement('iframe'); f.src = '%s';"
+ "document.body.appendChild(f);",
+ url.spec().c_str());
+ TestNavigationObserver observer(shell()->web_contents());
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ observer.Wait();
+ std::string message;
+ while (message_queue.WaitForMessage(&message)) {
+ if (message == "\"READY\"")
+ break;
+ }
+ }
+
+ // Runs |javascript| on the first child frame and checks for a navigation.
+ void TestNavigationFromFrame(
+ const std::string& scheme,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckNavigation(shell(), child, scheme, javascript,
+ expected_navigation_status);
+ }
+
+ // Runs |javascript| on the first child frame and expects a download to occur.
+ void TestDownloadFromFrame(const std::string& javascript) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckDownload(child, javascript);
+ }
+
+ // Runs |javascript| on the first child frame and checks for a navigation to
+ // the PDF file pointed by the test case.
+ void TestPDFNavigationFromFrame(
+ const std::string& scheme,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckPDFNavigation(child, scheme, javascript,
+ expected_navigation_status);
+ }
+
+ // Same as TestNavigationFromFrame, but instead of navigating, the child frame
+ // tries to open a new window with a blocked URL (data or filesystem)
+ void TestWindowOpenFromFrame(
+ const std::string& scheme,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckWindowOpen(child, scheme, javascript,
+ expected_navigation_status);
+ }
+
+ // Executes |javascript| on |rfh| and waits for a console message based on
+ // |expected_navigation_status|.
+ // - Blocked navigations should print kDataUrlBlockedPattern.
+ // - Allowed navigations should print kNavigationSuccessfulMessage.
+ void ExecuteScriptAndCheckNavigation(
+ Shell* shell,
+ RenderFrameHost* rfh,
+ const std::string& scheme,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ if (expected_navigation_status == NAVIGATION_ALLOWED)
+ ExecuteScriptAndCheckNavigationAllowed(shell, rfh, javascript, scheme);
+ else
+ ExecuteScriptAndCheckNavigationBlocked(shell, rfh, javascript, scheme);
+ }
+
+ protected:
+ // Similar to ExecuteScriptAndCheckNavigation(), but doesn't wait for a
+ // console message if the navigation is expected to be allowed (this is
+ // because PDF files can't print to the console).
+ void ExecuteScriptAndCheckPDFNavigation(
+ RenderFrameHost* rfh,
+ const std::string& scheme,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+
+ const std::string expected_message =
+ (expected_navigation_status == NAVIGATION_ALLOWED)
+ ? std::string()
+ : base::StringPrintf(kNavigationBlockedMessage, scheme.c_str());
+
+ std::unique_ptr<ConsoleObserverDelegate> console_delegate;
+ if (!expected_message.empty()) {
+ console_delegate.reset(new ConsoleObserverDelegate(
+ shell()->web_contents(), expected_message));
+ shell()->web_contents()->SetDelegate(console_delegate.get());
+ }
+
+ TestNavigationObserver navigation_observer(shell()->web_contents());
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+
+ if (console_delegate) {
+ console_delegate->Wait();
+ shell()->web_contents()->SetDelegate(nullptr);
+ }
+
+ switch (expected_navigation_status) {
+ case NAVIGATION_ALLOWED:
+ navigation_observer.Wait();
+ // The new page should have the expected scheme.
+ EXPECT_TRUE(
+ shell()->web_contents()->GetLastCommittedURL().SchemeIs(scheme));
+ EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs(scheme));
+ EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+ break;
+
+ case NAVIGATION_BLOCKED:
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+ EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ }
+
+ // Executes |javascript| on |rfh| and waits for a new window to be opened.
+ // Does not check for console messages (it's currently not possible to
+ // concurrently wait for a new shell to be created and a console message to be
+ // printed on that new shell).
+ void ExecuteScriptAndCheckWindowOpen(
+ RenderFrameHost* rfh,
+ const std::string& scheme,
+ const std::string& javascript,
+ ExpectedNavigationStatus expected_navigation_status) {
+ ShellAddedObserver new_shell_observer;
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+
+ Shell* new_shell = new_shell_observer.GetShell();
+ WaitForLoadStop(new_shell->web_contents());
+
+ switch (expected_navigation_status) {
+ case NAVIGATION_ALLOWED:
+ EXPECT_TRUE(
+ new_shell->web_contents()->GetLastCommittedURL().SchemeIs(scheme));
+ break;
+
+ case NAVIGATION_BLOCKED:
+ EXPECT_TRUE(
+ new_shell->web_contents()->GetLastCommittedURL().is_empty());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ }
+
+ // Executes |javascript| on |rfh| and waits for a download to be started by
+ // a window.open call.
+ void ExecuteScriptAndCheckWindowOpenDownload(RenderFrameHost* rfh,
+ const std::string& javascript) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+ ShellAddedObserver new_shell_observer;
+ DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+ shell()->web_contents()->GetBrowserContext());
+
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ DownloadTestObserverTerminal download_observer(
+ download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+
+ WaitForLoadStop(new_shell->web_contents());
+ // If no download happens, this will timeout.
+ download_observer.WaitForFinished();
+
+ EXPECT_TRUE(
+ new_shell->web_contents()->GetLastCommittedURL().spec().empty());
+ // No navigation should commit.
+ EXPECT_FALSE(
+ new_shell->web_contents()->GetController().GetLastCommittedEntry());
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+ }
+
+ // Executes |javascript| on |rfh| and waits for a download to be started.
+ void ExecuteScriptAndCheckDownload(RenderFrameHost* rfh,
+ const std::string& javascript) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+ DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+ shell()->web_contents()->GetBrowserContext());
+ DownloadTestObserverTerminal download_observer(
+ download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ // If no download happens, this will timeout.
+ download_observer.WaitForFinished();
+
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+ }
+
+ // Initiates a browser initiated navigation to |url| and waits for a download
+ // to be started.
+ void NavigateAndCheckDownload(const GURL& url) {
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL());
+ DownloadManager* download_manager = BrowserContext::GetDownloadManager(
+ shell()->web_contents()->GetBrowserContext());
+ DownloadTestObserverTerminal download_observer(
+ download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+ NavigateToURL(shell(), url);
+
+ // If no download happens, this will timeout.
+ download_observer.WaitForFinished();
+
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
+ }
+
+ // data URL form of the file at content/test/data/data_url_navigations.html
+ GURL data_url() const { return data_url_; }
+
+ std::string GetNavigationBlockedMessage() const {
+ return base::StringPrintf(kNavigationBlockedMessage, GetParam());
+ }
+
+ private:
+ // Executes |javascript| on |rfh| and waits for a console message that
+ // indicates the navigation has completed. |scheme| is the scheme being
+ // tested.
+ static void ExecuteScriptAndCheckNavigationAllowed(
+ Shell* shell,
+ RenderFrameHost* rfh,
+ const std::string& javascript,
+ const std::string& scheme) {
+ // Should see success message, should never see blocked message.
+ const std::string blocked_message =
+ base::StringPrintf(kNavigationBlockedMessage, scheme.c_str());
+ BlockedURLWarningConsoleObserverDelegate console_delegate(
+ shell->web_contents(), kNavigationSuccessfulMessage, blocked_message);
+ shell->web_contents()->SetDelegate(&console_delegate);
+
+ TestNavigationObserver navigation_observer(shell->web_contents());
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ console_delegate.Wait();
+ EXPECT_EQ(BlockedURLWarningConsoleObserverDelegate::SAW_SUCCESS_MESSAGE,
+ console_delegate.status());
+ shell->web_contents()->SetDelegate(nullptr);
+ navigation_observer.Wait();
+
+ // The new page should have the expected scheme.
+ EXPECT_EQ(navigation_observer.last_navigation_url(),
+ shell->web_contents()->GetLastCommittedURL());
+ EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
+ }
+
+ // Similar to ExecuteScriptAndCheckNavigationAllowed. Executes |javascript| on
+ // |rfh| and waits for a console message that indicates the navigation has
+ // been blocked. |scheme| is the scheme being tested.
+ static void ExecuteScriptAndCheckNavigationBlocked(
+ Shell* shell,
+ RenderFrameHost* rfh,
+ const std::string& javascript,
+ const std::string& scheme) {
+ const GURL original_url(shell->web_contents()->GetLastCommittedURL());
+
+ // Should see blocked message, should never see success message.
+ const std::string blocked_message =
+ base::StringPrintf(kNavigationBlockedMessage, scheme.c_str());
+ BlockedURLWarningConsoleObserverDelegate console_delegate(
+ shell->web_contents(), kNavigationSuccessfulMessage, blocked_message);
+ shell->web_contents()->SetDelegate(&console_delegate);
+
+ TestNavigationObserver navigation_observer(shell->web_contents());
+ EXPECT_TRUE(ExecuteScript(rfh, javascript));
+ console_delegate.Wait();
+ EXPECT_EQ(BlockedURLWarningConsoleObserverDelegate::SAW_FAILURE_MESSAGE,
+ console_delegate.status());
+ shell->web_contents()->SetDelegate(nullptr);
+
+ // Original page shouldn't navigate away.
+ EXPECT_EQ(original_url, shell->web_contents()->GetLastCommittedURL());
+ EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
+ }
+
+ base::ScopedTempDir downloads_directory_;
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+ ScopedPluginRegister scoped_plugin_register_;
+#endif // BUILDFLAG(ENABLE_PLUGINS)
+
+ GURL data_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockedSchemeNavigationBrowserTest);
+};
+
+INSTANTIATE_TEST_CASE_P(,
+ BlockedSchemeNavigationBrowserTest,
+ ::testing::Values(url::kDataScheme,
+ url::kFileSystemScheme));
+
+////////////////////////////////////////////////////////////////////////////////
+// Blocked schemes with HTML mimetype
+//
+// Tests that a browser initiated navigation to a blocked scheme doesn't show a
+// console warning and is not blocked.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ BrowserInitiated_Allow) {
+ const GURL kUrl(CreateURLWithBlockedScheme(
+ "test.html",
+ "<html><script>console.log('NAVIGATION_SUCCESSFUL');</script></html>",
+ "text/html"));
+ if (IsDataURLTest()) {
+ BlockedURLWarningConsoleObserverDelegate console_delegate(
+ shell()->web_contents(), kNavigationSuccessfulMessage,
+ GetNavigationBlockedMessage());
+
+ shell()->web_contents()->SetDelegate(&console_delegate);
+ EXPECT_TRUE(NavigateToURL(shell(), kUrl));
+ console_delegate.Wait();
+ shell()->web_contents()->SetDelegate(nullptr);
+ EXPECT_TRUE(
+ shell()->web_contents()->GetLastCommittedURL().SchemeIs(GetParam()));
+
+ } else {
+ // Navigate to a.com and create a filesystem URL on it.
+ // For filesystem: tests we create a new shell and navigate that shell to
+ // the filesystem: URL created above. Navigating the a tab away from the
+ // original page may clear all filesystem: URLs associated with that origin,
+ // so we keep the origin around in the original shell.
+ ShellAddedObserver new_shell_observer;
+ EXPECT_TRUE(
+ ExecuteScript(shell()->web_contents(), "window.open('about:blank');"));
+ Shell* new_shell = new_shell_observer.GetShell();
+ WaitForLoadStop(new_shell->web_contents());
+
+ BlockedURLWarningConsoleObserverDelegate console_delegate(
+ new_shell->web_contents(), kNavigationSuccessfulMessage,
+ GetNavigationBlockedMessage());
+ new_shell->web_contents()->SetDelegate(&console_delegate);
+ EXPECT_TRUE(NavigateToURL(new_shell, kUrl));
+
+ console_delegate.Wait();
+ new_shell->web_contents()->SetDelegate(nullptr);
+ EXPECT_TRUE(
+ new_shell->web_contents()->GetLastCommittedURL().SchemeIs(GetParam()));
+ }
+}
+
+// Tests that a content initiated navigation to a blocked scheme is blocked.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ HTML_Navigation_Block) {
+ Navigate(GetTestURL());
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+class DataUrlNavigationBrowserTestWithFeatureFlag
+ : public BlockedSchemeNavigationBrowserTest {
+ public:
+ DataUrlNavigationBrowserTestWithFeatureFlag() {
+ scoped_feature_list_.InitAndEnableFeature(
+ features::kAllowContentInitiatedDataUrlNavigations);
+ }
+ ~DataUrlNavigationBrowserTestWithFeatureFlag() override {}
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataUrlNavigationBrowserTestWithFeatureFlag);
+};
+
+// Tests that a content initiated navigation to a data URL is allowed if
+// blocking is disabled with a feature flag.
+IN_PROC_BROWSER_TEST_F(DataUrlNavigationBrowserTestWithFeatureFlag,
+ HTML_Navigation_Allow_FeatureFlag) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("/data_url_navigations.html")));
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), url::kDataScheme,
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_ALLOWED);
+}
+
+// Tests that a window.open to a blocked scheme with HTML mime type is blocked.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ HTML_WindowOpen_Block) {
+ Navigate(GetTestURL());
+ ExecuteScriptAndCheckWindowOpen(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('window-open-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that a form post to a blocked scheme with HTML mime type is blocked.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ HTML_FormPost_Block) {
+ Navigate(GetTestURL());
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('form-post-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that clicking <a download> link downloads the URL even with a blocked
+// scheme.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, HTML_Download) {
+ Navigate(GetTestURL());
+ ExecuteScriptAndCheckDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('download-link').click()");
+}
+
+// Tests that navigating the main frame to a blocked scheme with HTML mimetype
+// from a subframe is blocked.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ HTML_NavigationFromFrame_Block) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL(
+ "b.com", base::StringPrintf("/%s_url_navigations.html", GetParam())));
+
+ TestNavigationFromFrame(
+ GetParam(),
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a new window with a blocked scheme from a subframe is
+// blocked.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ HTML_WindowOpenFromFrame_Block) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL(
+ "b.com", base::StringPrintf("/%s_url_navigations.html", GetParam())));
+
+ TestWindowOpenFromFrame(GetParam(),
+ "document.getElementById('window-open-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that navigation to a blocked scheme is blocked even if the top frame is
+// already has a blocked scheme.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ HTML_Navigation_SameScheme_Block) {
+ if (IsDataURLTest()) {
+ EXPECT_TRUE(NavigateToURL(shell(), data_url()));
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), url::kDataScheme,
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+ } else {
+ // We need an origin to create a filesystem URL on, so navigate to one.
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ const GURL kFilesystemURL1(
+ CreateFileSystemUrl("empty1.html", "empty1", "text/html"));
+ const GURL kFilesystemURL2(
+ CreateFileSystemUrl("empty2.html", "empty2", "text/html"));
+
+ // Create a new shell and navigate that shell to the filesystem: URL created
+ // above. Navigating the a tab away from the
+ // original page may clear all filesystem: URLs associated with that origin,
+ // so we keep the origin around in the original shell.
+ ShellAddedObserver new_shell_observer;
+ EXPECT_TRUE(
+ ExecuteScript(shell()->web_contents(), "window.open('about:blank');"));
+ Shell* new_shell = new_shell_observer.GetShell();
+ WaitForLoadStop(new_shell->web_contents());
+
+ EXPECT_TRUE(NavigateToURL(new_shell, kFilesystemURL1));
+ ExecuteScriptAndCheckNavigation(
+ new_shell, new_shell->web_contents()->GetMainFrame(),
+ url::kFileSystemScheme,
+ base::StringPrintf("window.location='%s';",
+ kFilesystemURL2.spec().c_str()),
+ NAVIGATION_BLOCKED);
+ }
+}
+
+// Tests that a form post to a blocked scheme with HTML mime type is blocked
+// even if the top frame is already a blocked scheme.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ HTML_FormPost_SameScheme_Block) {
+ if (IsDataURLTest()) {
+ EXPECT_TRUE(NavigateToURL(shell(), data_url()));
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), url::kDataScheme,
+ "document.getElementById('form-post-to-html').click()",
+ NAVIGATION_BLOCKED);
+ } else {
+ // We need an origin to create a filesystem URL on, so navigate to one.
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ const GURL kFilesystemURL1(
+ CreateFileSystemUrl("target.html", "form target", "text/html"));
+ const GURL kFilesystemURL2(CreateFileSystemUrl(
+ "form.html",
+ base::StringPrintf("<html><form id=f method=post action='%s'><input "
+ "type=submit "
+ "onclick=document.getElementById('f').click() "
+ "id=btn-submit></form></html>",
+ kFilesystemURL1.spec().c_str()),
+ "text/html"));
+
+ // Create a new shell and navigate that shell to the filesystem: URL created
+ // above. Navigating the a tab away from the
+ // original page may clear all filesystem: URLs associated with that origin,
+ // so we keep the origin around in the original shell.
+ ShellAddedObserver new_shell_observer;
+ // TODO(crbug/811558): about:blank might commit without needing to wait.
+ // Remove the wait.
+ EXPECT_TRUE(
+ ExecuteScript(shell()->web_contents(), "window.open('about:blank');"));
+ Shell* new_shell = new_shell_observer.GetShell();
+ WaitForLoadStop(new_shell->web_contents());
+
+ EXPECT_TRUE(NavigateToURL(new_shell, kFilesystemURL2));
+ ExecuteScriptAndCheckNavigation(
+ new_shell, new_shell->web_contents()->GetMainFrame(),
+ url::kFileSystemScheme, "document.getElementById('btn-submit').click()",
+ NAVIGATION_BLOCKED);
+ }
+}
+
+// Tests that navigating the top frame to a blocked scheme with HTML mimetype is
+// blocked even if the top frame already has a blocked scheme.
+IN_PROC_BROWSER_TEST_P(
+ BlockedSchemeNavigationBrowserTest,
+ HTML_NavigationFromFrame_TopFrameHasBlockedScheme_Block) {
+ EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme()));
+ AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL());
+
+ TestNavigationFromFrame(
+ GetParam(),
+ "document.getElementById('navigate-top-frame-to-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests that opening a new window with a blocked scheme with HTML mimetype is
+// blocked even if the top frame already has a blocked scheme.
+IN_PROC_BROWSER_TEST_P(
+ BlockedSchemeNavigationBrowserTest,
+ HTML_WindowOpenFromFrame_TopFrameHasBlockedScheme_Block) {
+ // Create an empty URL with a blocked scheme, navigate to it, and add a frame.
+ EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme()));
+ AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL());
+
+ TestWindowOpenFromFrame(GetParam(),
+ "document.getElementById('window-open-html').click()",
+ NAVIGATION_BLOCKED);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Blocked schemes with octet-stream mimetype (binary)
+//
+// Test direct navigations to a binary mime types.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ OctetStream_BrowserInitiated) {
+ const GURL kUrl(CreateURLWithBlockedScheme("test.html", "test",
+ "application/octet-stream"));
+
+ if (IsDataURLTest()) {
+ // Navigations to data URLs with unknown mime types should end up as
+ // downloads.
+ NavigateAndCheckDownload(kUrl);
+ } else {
+ // Navigations to filesystem URLs never end up as downloads.
+ EXPECT_TRUE(NavigateToURL(shell(), kUrl));
+ EXPECT_EQ(kUrl, shell()->web_contents()->GetLastCommittedURL());
+ }
+}
+
+#if defined(OS_ANDROID)
+// Flaky on android: https://crbug.com/734563
+#define MAYBE_DataUrl_OctetStream_WindowOpen \
+ DISABLED_DataUrl_OctetStream_WindowOpen
+#else
+#define MAYBE_DataUrl_OctetStream_WindowOpen DataUrl_OctetStream_WindowOpen
+#endif
+
+// Test window.open to a data URL with binary mimetype.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ DataUrl_OctetStream_WindowOpen) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/data_url_navigations.html")));
+ // Navigations to data URLs with unknown mime types should end up as
+ // downloads.
+ ExecuteScriptAndCheckWindowOpenDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('window-open-octetstream').click()");
+}
+
+// Test window.open to a filesystem URL with binary mimetype.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ FilesystemUrl_OctetStream_WindowOpen) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/filesystem_url_navigations.html")));
+ // Navigations to filesystem URLs never end up as downloads.
+ ExecuteScriptAndCheckWindowOpen(
+ shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme,
+ "document.getElementById('window-open-octetstream').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Test navigation to a data URL with binary mimetype.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ DataUrl_OctetStream_Navigation) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/data_url_navigations.html")));
+ // Navigations to data URLs with unknown mime types should end up as
+ // downloads.
+ ExecuteScriptAndCheckDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-to-octetstream').click()");
+}
+
+// Test navigation to a filesystem URL with binary mimetype.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ FilesystemUrl_OctetStream_Navigation) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/filesystem_url_navigations.html")));
+ // Navigations to filesystem URLs never end up as downloads.
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme,
+ "document.getElementById('navigate-top-frame-to-octetstream').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Test form post to a data URL with binary mimetype.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ DataUrl_OctetStream_FormPost) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/data_url_navigations.html")));
+ // Form posts to data URLs with unknown mime types should end up as
+ // downloads.
+ ExecuteScriptAndCheckDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-octetstream').click()");
+}
+
+// Test form post to a filesystem URL with binary mimetype.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ FilesystemUrl_OctetStream_FormPost) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/filesystem_url_navigations.html")));
+ // Navigations to filesystem URLs never end up as downloads.
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme,
+ "document.getElementById('form-post-to-octetstream').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Tests navigation of the main frame to a data URL with a binary mimetype
+// from a subframe. These navigations should end up as downloads.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ DataUrl_OctetStream_NavigationFromFrame) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+ TestDownloadFromFrame(
+ "document.getElementById('navigate-top-frame-to-octetstream').click()");
+}
+
+// Tests navigation of the main frame to a filesystem URL with a binary mimetype
+// from a subframe. Navigations to filesystem URLs never end up as downloads.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ FilesystemUrl_OctetStream_NavigationFromFrame) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com",
+ "/filesystem_url_navigations.html"));
+
+ TestNavigationFromFrame(
+ url::kFileSystemScheme,
+ "document.getElementById('navigate-top-frame-to-octetstream').click()",
+ NAVIGATION_BLOCKED);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// URLs with unknown mimetype
+//
+// Test direct navigation to an unknown mime type.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ UnknownMimeType_BrowserInitiated_Download) {
+ const GURL kUrl(
+ CreateURLWithBlockedScheme("test.html", "test", "unknown/mimetype"));
+
+ if (IsDataURLTest()) {
+ // Navigations to data URLs with unknown mime types should end up as
+ // downloads.
+ NavigateAndCheckDownload(kUrl);
+ } else {
+ // Navigations to filesystem URLs never end up as downloads.
+ EXPECT_TRUE(NavigateToURL(shell(), kUrl));
+ EXPECT_EQ(kUrl, shell()->web_contents()->GetLastCommittedURL());
+ }
+}
+
+#if defined(OS_ANDROID)
+// Flaky on android: https://crbug.com/734563
+#define MAYBE_UnknownMimeType_WindowOpen DISABLED_UnknownMimeType_WindowOpen
+#else
+#define MAYBE_UnknownMimeType_WindowOpen UnknownMimeType_WindowOpen
+#endif
+
+// Test window.open to a blocked scheme with an unknown mime type.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ MAYBE_UnknownMimeType_WindowOpen) {
+ Navigate(GetTestURL());
+ if (IsDataURLTest()) {
+ // Navigations to data URLs with unknown mime types should end up as
+ // downloads.
+ ExecuteScriptAndCheckWindowOpenDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('window-open-unknown-mimetype').click()");
+ } else {
+ // Navigations to filesystem URLs never end up as downloads.
+ ExecuteScriptAndCheckWindowOpen(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('window-open-unknown-mimetype').click()",
+ NAVIGATION_BLOCKED);
+ }
+}
+
+// Test navigation to a data URL with an unknown mime type.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ DataUrl_UnknownMimeType_Navigation) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/data_url_navigations.html")));
+ // Navigations to data URLs with unknown mime types should end up as
+ // downloads.
+ ExecuteScriptAndCheckDownload(shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-"
+ "to-unknown-mimetype').click()");
+}
+
+// Test navigation to a filesystem URL with an unknown mime type.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ FilesystemUrl_UnknownMimeType_Navigation) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/filesystem_url_navigations.html")));
+ // Navigations to filesystem URLs never end up as downloads.
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme,
+ "document.getElementById('navigate-top-frame-to-unknown-mimetype')."
+ "click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Test form post to a data URL with an unknown mime type.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ DataUrl_UnknownMimeType_FormPost) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/data_url_navigations.html")));
+ // Form posts to data URLs with unknown mime types should end up as
+ // downloads.
+ ExecuteScriptAndCheckDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-unknown-mimetype').click()");
+}
+
+// Test form post to a filesystem URL with an unknown mime type.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ FilesystemUrl_UnknownMimeType_FormPost) {
+ Navigate(embedded_test_server()->GetURL(
+ base::StringPrintf("/filesystem_url_navigations.html")));
+ // Navigations to filesystem URLs never end up as downloads.
+ ExecuteScriptAndCheckNavigation(
+ shell(), shell()->web_contents()->GetMainFrame(), url::kFileSystemScheme,
+ "document.getElementById('form-post-to-unknown-mimetype').click()",
+ NAVIGATION_BLOCKED);
+}
+
+// Test navigation of the main frame to a data URL with an unknown mimetype from
+// a subframe. These navigations should end up as downloads.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ DataUrl_UnknownMimeType_NavigationFromFrame) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com", "/data_url_navigations.html"));
+
+ TestDownloadFromFrame(
+ "document.getElementById('navigate-top-frame-to-unknown-mimetype')."
+ "click()");
+}
+
+// Test navigation of the main frame to a filesystem URL with an unknown
+// mimetype from a subframe. Navigations to filesystem URLs don't end up as
+// downloads.
+IN_PROC_BROWSER_TEST_F(BlockedSchemeNavigationBrowserTest,
+ FilesystemUrl_UnknownMimeType_NavigationFromFrame) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL("b.com",
+ "/filesystem_url_navigations.html"));
+
+ TestNavigationFromFrame(url::kFileSystemScheme,
+ "document.getElementById('navigate-top-frame-to-"
+ "unknown-mimetype').click()",
+ NAVIGATION_BLOCKED);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// URLs with PDF mimetype
+//
+// Tests that a browser initiated navigation to a blocked scheme URL with PDF
+// mime type is allowed, or initiates a download on Android.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ PDF_BrowserInitiatedNavigation_Allow) {
+ std::string pdf_base64;
+ base::Base64Encode(kPDF, &pdf_base64);
+ const GURL kPDFUrl(CreateURLWithBlockedScheme(
+ "test.pdf", IsDataURLTest() ? pdf_base64 : kPDF, "application/pdf"));
+
+#if !defined(OS_ANDROID)
+ TestNavigationObserver observer(shell()->web_contents());
+ EXPECT_TRUE(NavigateToURL(shell(), kPDFUrl));
+ EXPECT_EQ(kPDFUrl, observer.last_navigation_url());
+ EXPECT_TRUE(observer.last_navigation_succeeded());
+ EXPECT_TRUE(
+ shell()->web_contents()->GetLastCommittedURL().SchemeIs(GetParam()));
+#else
+ NavigateAndCheckDownload(kPDFUrl);
+#endif
+}
+
+// Tests that a window.open to a blocked scheme is blocked if the URL has a
+// mime type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ PDF_WindowOpen_Block) {
+ Navigate(GetTestURL());
+
+#if !defined(OS_ANDROID)
+ ExecuteScriptAndCheckWindowOpen(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('window-open-pdf').click()", NAVIGATION_BLOCKED);
+#else
+ if (IsDataURLTest()) {
+ // On Android, data URL PDFs are downloaded upon navigation.
+ ExecuteScriptAndCheckDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('window-open-pdf').click()");
+ } else {
+ // On Android, filesystem PDF URLs are navigated and should be blocked.
+ ExecuteScriptAndCheckWindowOpen(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('window-open-pdf').click()",
+ NAVIGATION_BLOCKED);
+ }
+#endif
+}
+
+// Test that a navigation to a blocked scheme URL is blocked if the URL has a
+// mime type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ PDF_Navigation_Block) {
+ Navigate(GetTestURL());
+
+#if !defined(OS_ANDROID)
+ ExecuteScriptAndCheckPDFNavigation(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+#else
+ if (IsDataURLTest()) {
+ // On Android, data URL PDFs are downloaded upon navigation.
+ ExecuteScriptAndCheckDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()");
+ } else {
+ // On Android, filesystem PDF URLs are navigated and should be blocked.
+ ExecuteScriptAndCheckPDFNavigation(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+ }
+#endif
+}
+
+// Test that a form post to a blocked scheme is blocked if the URL has a mime
+// type that will be handled by a plugin (PDF in this case).
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest, PDF_FormPost_Block) {
+ Navigate(GetTestURL());
+
+#if !defined(OS_ANDROID)
+ ExecuteScriptAndCheckPDFNavigation(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('form-post-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+#else
+ if (IsDataURLTest()) {
+ // On Android, data URL PDFs are downloaded upon navigation.
+ ExecuteScriptAndCheckDownload(
+ shell()->web_contents()->GetMainFrame(),
+ "document.getElementById('form-post-to-pdf').click()");
+ } else {
+ // On Android, filesystem PDF URLs are navigated and should be blocked.
+ ExecuteScriptAndCheckPDFNavigation(
+ shell()->web_contents()->GetMainFrame(), GetParam(),
+ "document.getElementById('form-post-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+ }
+#endif
+}
+
+// Tests that navigating the main frame to a blocked scheme with PDF mimetype
+// from a subframe is blocked, or is downloaded on Android.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ PDF_NavigationFromFrame_Block) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(
+ shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL(
+ "b.com", base::StringPrintf("/%s_url_navigations.html", GetParam())));
+
+#if !defined(OS_ANDROID)
+ TestPDFNavigationFromFrame(
+ GetParam(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+#else
+ if (IsDataURLTest()) {
+ // On Android, data URL PDFs are downloaded upon navigation.
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckDownload(
+ child, "document.getElementById('navigate-top-frame-to-pdf').click()");
+ } else {
+ // On Android, filesystem PDF URLs are navigated and should be blocked.
+ TestPDFNavigationFromFrame(
+ GetParam(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+ }
+#endif
+}
+
+// Tests that opening a window with a blocked scheme with PDF mimetype from a
+// subframe is blocked, or is downloaded on Android.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ PDF_WindowOpenFromFrame_Block) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html")));
+ AddIFrame(shell()->web_contents()->GetMainFrame(),
+ embedded_test_server()->GetURL(
+ base::StringPrintf("/%s_url_navigations.html", GetParam())));
+
+#if !defined(OS_ANDROID)
+ TestWindowOpenFromFrame(GetParam(),
+ "document.getElementById('window-open-pdf').click()",
+ NAVIGATION_BLOCKED);
+#else
+ if (IsDataURLTest()) {
+ // On Android, data URL PDFs are downloaded upon navigation.
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckDownload(
+ child, "document.getElementById('window-open-pdf').click()");
+ } else {
+ // On Android, filesystem PDF URLs are navigated and should be blocked.
+ TestWindowOpenFromFrame(
+ GetParam(), "document.getElementById('window-open-pdf').click()",
+ NAVIGATION_BLOCKED);
+ }
+#endif
+}
+
+// Tests that navigating the top frame to a blocked scheme with PDF mimetype
+// from a subframe is blocked even if the top frame already has a blocked
+// scheme.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ PDF_NavigationFromFrame_TopFrameHasBlockedScheme_Block) {
+ EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme()));
+ AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL());
+
+#if !defined(OS_ANDROID)
+ TestPDFNavigationFromFrame(
+ GetParam(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+#else
+ if (IsDataURLTest()) {
+ // On Android, data URL PDFs are downloaded upon navigation.
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckDownload(
+ child, "document.getElementById('navigate-top-frame-to-pdf').click()");
+ } else {
+ // On Android, filesystem PDF URLs are navigated and should be blocked.
+ TestPDFNavigationFromFrame(
+ GetParam(),
+ "document.getElementById('navigate-top-frame-to-pdf').click()",
+ NAVIGATION_BLOCKED);
+ }
+#endif
+}
+
+// Tests that opening a window with a blocked scheme with PDF mimetype from a
+// subframe is blocked even if the top frame already has a blocked scheme.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ PDF_WindowOpenFromFrame_TopFrameHasBlockedScheme_Block) {
+ EXPECT_TRUE(NavigateToURL(shell(), CreateEmptyURLWithBlockedScheme()));
+ AddIFrame(shell()->web_contents()->GetMainFrame(), GetTestURL());
+
+#if !defined(OS_ANDROID)
+ TestWindowOpenFromFrame(GetParam(),
+ "document.getElementById('window-open-pdf').click()",
+ NAVIGATION_BLOCKED);
+#else
+ if (IsDataURLTest()) {
+ // On Android, data URL PDFs are downloaded upon navigation.
+ RenderFrameHost* child =
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
+ ASSERT_TRUE(child);
+ if (AreAllSitesIsolatedForTesting()) {
+ ASSERT_TRUE(child->IsCrossProcessSubframe());
+ }
+ ExecuteScriptAndCheckDownload(
+ child, "document.getElementById('window-open-pdf').click()");
+ } else {
+ // On Android, filesystem PDF URLs are navigated to and should be blocked.
+ TestWindowOpenFromFrame(
+ GetParam(), "document.getElementById('window-open-pdf').click()",
+ NAVIGATION_BLOCKED);
+ }
+#endif
+}
+
+// Test case to verify that redirects to blocked schemes are properly
+// disallowed, even when invoked through history navigations. See
+// https://crbug.com/723796.
+IN_PROC_BROWSER_TEST_P(BlockedSchemeNavigationBrowserTest,
+ WindowOpenRedirectAndBack) {
+ Navigate(GetTestURL());
+
+ // This test will need to navigate the newly opened window.
+ ShellAddedObserver new_shell_observer;
+ EXPECT_TRUE(
+ ExecuteScript(shell()->web_contents(),
+ "document.getElementById('window-open-redirect').click()"));
+ Shell* new_shell = new_shell_observer.GetShell();
+ NavigationController* controller =
+ &new_shell->web_contents()->GetController();
+ WaitForLoadStop(new_shell->web_contents());
+
+ // The window.open() should have resulted in an error page. The blocked
+ // URL should be in both the actual and the virtual URL.
+ {
+ EXPECT_EQ(0, controller->GetLastCommittedEntryIndex());
+ NavigationEntry* entry = controller->GetLastCommittedEntry();
+ EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
+ EXPECT_FALSE(entry->GetURL().SchemeIs(url::kDataScheme));
+ EXPECT_FALSE(entry->GetURL().SchemeIs(url::kFileSystemScheme));
+ EXPECT_TRUE(base::StartsWith(
+ entry->GetURL().spec(),
+ embedded_test_server()->GetURL("/server-redirect?").spec(),
+ base::CompareCase::SENSITIVE));
+ EXPECT_EQ(entry->GetURL(), entry->GetVirtualURL());
+ }
+
+ // Navigate forward and then go back to ensure the navigation to data: or
+ // filesystem: URL is blocked. Use a browser-initiated back navigation,
+ // equivalent to user pressing the back button.
+ EXPECT_TRUE(
+ NavigateToURL(new_shell, embedded_test_server()->GetURL("/title1.html")));
+ EXPECT_EQ(1, controller->GetLastCommittedEntryIndex());
+ {
+ TestNavigationObserver observer(new_shell->web_contents());
+ controller->GoBack();
+ observer.Wait();
+
+ NavigationEntry* entry = controller->GetLastCommittedEntry();
+ EXPECT_EQ(0, controller->GetLastCommittedEntryIndex());
+ EXPECT_FALSE(entry->GetURL().SchemeIs(url::kDataScheme));
+ EXPECT_FALSE(entry->GetURL().SchemeIs(url::kFileSystemScheme));
+ EXPECT_TRUE(base::StartsWith(
+ entry->GetURL().spec(),
+ embedded_test_server()->GetURL("/server-redirect?").spec(),
+ base::CompareCase::SENSITIVE));
+ EXPECT_EQ(entry->GetURL(), entry->GetVirtualURL());
+ }
+
+ // Do another new navigation, but then use JavaScript to navigate back,
+ // equivalent to document executing JS.
+ EXPECT_TRUE(
+ NavigateToURL(new_shell, embedded_test_server()->GetURL("/title1.html")));
+ EXPECT_EQ(1, controller->GetLastCommittedEntryIndex());
+ {
+ TestNavigationObserver observer(new_shell->web_contents());
+ EXPECT_TRUE(ExecuteScript(new_shell, "history.go(-1)"));
+ observer.Wait();
+
+ NavigationEntry* entry = controller->GetLastCommittedEntry();
+ EXPECT_EQ(0, controller->GetLastCommittedEntryIndex());
+ EXPECT_FALSE(entry->GetURL().SchemeIs(url::kDataScheme));
+ EXPECT_FALSE(entry->GetURL().SchemeIs(url::kFileSystemScheme));
+ EXPECT_TRUE(base::StartsWith(
+ entry->GetURL().spec(),
+ embedded_test_server()->GetURL("/server-redirect?").spec(),
+ base::CompareCase::SENSITIVE));
+ EXPECT_EQ(entry->GetURL(), entry->GetVirtualURL());
+ }
+}
+
+} // namespace content