Index: content/browser/frame_host/data_url_navigation_browsertest.cc |
diff --git a/content/browser/frame_host/data_url_navigation_browsertest.cc b/content/browser/frame_host/data_url_navigation_browsertest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f2fac1ffa329e20645ba0ec9c4c02f319276461b |
--- /dev/null |
+++ b/content/browser/frame_host/data_url_navigation_browsertest.cc |
@@ -0,0 +1,959 @@ |
+// 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/command_line.h" |
+#include "base/files/file_util.h" |
+#include "base/macros.h" |
+#include "base/path_service.h" |
+#include "base/strings/pattern.h" |
+#include "base/strings/utf_string_conversions.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/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 "net/base/escape.h" |
+#include "net/dns/mock_host_resolver.h" |
+#include "net/test/embedded_test_server/embedded_test_server.h" |
+#include "ppapi/features/features.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 a data URL |
+// navigation is blocked. |
+const char kDataUrlBlockedPattern[] = |
+ "Not allowed to top-level navigate to resource:*"; |
+ |
+// The message printed by the data URL when it successfully navigates. |
+const char kDataUrlSuccessfulMessage[] = "NAVIGATION_SUCCESSFUL"; |
+ |
+// A "Hello World" PDF encoded as a data URL. Source of this PDF: |
+// ------------------------- |
+// %PDF-1.7 |
+// 1 0 obj << /Type /Page /Parent 3 0 R /Resources 5 0 R /Contents 2 0 R >> |
+// endobj |
+// 2 0 obj << /Length 51 >> |
+// stream BT |
+// /F1 12 Tf |
+// 1 0 0 1 100 20 Tm |
+// (Hello World)Tj |
+// ET |
+// endstream |
+// endobj |
+// 3 0 obj << /Type /Pages /Kids [ 1 0 R ] /Count 1 /MediaBox [ 0 0 300 50] >> |
+// endobj |
+// 4 0 obj << /Type /Font /Subtype /Type1 /Name /F1 /BaseFont/Arial >> |
+// endobj |
+// 5 0 obj << /ProcSet[/PDF/Text] /Font <</F1 4 0 R >> >> |
+// endobj |
+// 6 0 obj << /Type /Catalog /Pages 3 0 R >> |
+// endobj |
+// trailer << /Root 6 0 R >> |
+// ------------------------- |
+const char kPdfUrl[] = |
+ "data:application/pdf;base64,JVBERi0xLjcKMSAwIG9iaiA8PCAvVHlwZSAvUGFnZSAvUG" |
+ "FyZW50IDMgMCBSIC9SZXNvdXJjZXMgNSAwIFIgL0NvbnRlbnRzIDIgMCBSID4+CmVuZG9iagoy" |
+ "IDAgb2JqIDw8IC9MZW5ndGggNTEgPj4KIHN0cmVhbSBCVAogL0YxIDEyIFRmCiAxIDAgMCAxID" |
+ "EwMCAyMCBUbQogKEhlbGxvIFdvcmxkKVRqCiBFVAogZW5kc3RyZWFtCmVuZG9iagozIDAgb2Jq" |
+ "IDw8IC9UeXBlIC9QYWdlcyAvS2lkcyBbIDEgMCBSIF0gL0NvdW50IDEgL01lZGlhQm94IFsgMC" |
+ "AwIDMwMCA1MF0gPj4KZW5kb2JqCjQgMCBvYmogPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5" |
+ "cGUxIC9OYW1lIC9GMSAvQmFzZUZvbnQvQXJpYWwgPj4KZW5kb2JqCjUgMCBvYmogPDwgL1Byb2" |
+ "NTZXRbL1BERi9UZXh0XSAvRm9udCA8PC9GMSA0IDAgUiA+PiA+PgplbmRvYmoKNiAwIG9iaiA8" |
+ "PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgPj4KZW5kb2JqCnRyYWlsZXIgPDwgL1Jvb3" |
+ "QgNiAwIFIgPj4K"; |
+ |
+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 DataURLWarningConsoleObserverDelegate : public WebContentsDelegate { |
+ public: |
+ DataURLWarningConsoleObserverDelegate( |
+ WebContents* web_contents, |
+ ExpectedNavigationStatus expected_navigation_status) |
+ : web_contents_(web_contents), |
+ success_filter_(expected_navigation_status == NAVIGATION_ALLOWED |
+ ? kDataUrlSuccessfulMessage |
+ : kDataUrlBlockedPattern), |
+ fail_filter_(expected_navigation_status == NAVIGATION_ALLOWED |
+ ? kDataUrlBlockedPattern |
+ : kDataUrlSuccessfulMessage), |
+ message_loop_runner_(new MessageLoopRunner()), |
+ saw_failure_message_(false) {} |
+ ~DataURLWarningConsoleObserverDelegate() override {} |
+ |
+ void Wait() { message_loop_runner_->Run(); } |
+ |
+ // 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_)) { |
+ saw_failure_message_ = true; |
+ message_loop_runner_->Quit(); |
+ } |
+ if (base::MatchPattern(ascii_message, success_filter_)) { |
+ message_loop_runner_->Quit(); |
+ } |
+ return false; |
+ } |
+ |
+ // Returns true if the observer encountered a message that matches |
+ // |fail_filter_|. |
+ bool saw_failure_message() const { return saw_failure_message_; } |
+ |
+ private: |
+ WebContents* web_contents_; |
+ const std::string success_filter_; |
+ const std::string fail_filter_; |
+ scoped_refptr<MessageLoopRunner> message_loop_runner_; |
+ bool saw_failure_message_; |
+}; |
+ |
+enum TestNavigationType { |
+ NO_PLZNAVIGATE, |
+ PLZNAVIGATE, |
+}; |
+ |
+enum TestSitePerProcessType { NO_SITE_PER_PROCESS, SITE_PER_PROCESS }; |
+ |
+#if BUILDFLAG(ENABLE_PLUGINS) |
+// This class registers a fake PDF plugin handler so that data URL 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 DataUrlNavigationBrowserTest |
+ : public ContentBrowserTest, |
+ public testing::WithParamInterface< |
+ std::tuple<TestNavigationType, TestSitePerProcessType>> { |
+ public: |
+#if BUILDFLAG(ENABLE_PLUGINS) |
+ DataUrlNavigationBrowserTest() |
+ : scoped_plugin_register_(PluginService::GetInstance()) {} |
+#else |
+ DataUrlNavigationBrowserTest() {} |
+#endif // BUILDFLAG(ENABLE_PLUGINS) |
+ |
+ protected: |
+ void SetUpCommandLine(base::CommandLine* command_line) override { |
+ if (IsPlzNavigateTest()) { |
+ command_line->AppendSwitch(switches::kEnableBrowserSideNavigation); |
+ } |
+ if (IsSitePerProcessTest()) { |
+ IsolateAllSitesForTesting(command_line); |
+ } |
+ } |
+ |
+ void SetUpOnMainThread() override { |
+ host_resolver()->AddRule("*", "127.0.0.1"); |
+ ASSERT_TRUE(embedded_test_server()->Start()); |
+ |
+ base::FilePath path; |
+ ASSERT_TRUE(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); |
+ } |
+ |
+ // Adds an iframe to |rfh| pointing to |url|. |
+ void AddIFrame(RenderFrameHost* rfh, const GURL& url) { |
+ const std::string javascript = base::StringPrintf( |
+ "f = document.createElement('iframe'); f.src = '%s';" |
+ "document.body.appendChild(f);", |
+ url.spec().c_str()); |
+ bool iframe_loaded = false; |
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(rfh, javascript, &iframe_loaded)); |
+ EXPECT_TRUE(iframe_loaded); |
+ } |
+ |
+ // Runs |javascript| on the first child frame and checks for a navigation. |
+ void TestNavigationFromFrame( |
+ const std::string& javascript, |
+ ExpectedNavigationStatus expected_navigation_status) { |
+ RenderFrameHost* child = |
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); |
+ ASSERT_TRUE(child); |
+ if (IsSitePerProcessTest()) { |
+ ASSERT_TRUE(child->IsCrossProcessSubframe()); |
+ } |
+ ExecuteScriptAndCheckNavigation(child, 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 (IsSitePerProcessTest()) { |
+ ASSERT_TRUE(child->IsCrossProcessSubframe()); |
+ } |
+ ExecuteScriptAndCheckNavigationDownload(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& javascript, |
+ ExpectedNavigationStatus expected_navigation_status) { |
+ RenderFrameHost* child = |
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); |
+ ASSERT_TRUE(child); |
+ if (IsSitePerProcessTest()) { |
+ ASSERT_TRUE(child->IsCrossProcessSubframe()); |
+ } |
+ ExecuteScriptAndCheckPDFNavigation(child, javascript, |
+ expected_navigation_status); |
+ } |
+ |
+ // Same as TestNavigationFromFrame, but instead of navigating, the child frame |
+ // tries to open a new window with a data URL. |
+ void TestWindowOpenFromFrame( |
+ const std::string& javascript, |
+ ExpectedNavigationStatus expected_navigation_status) { |
+ RenderFrameHost* child = |
+ ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0); |
+ if (IsSitePerProcessTest()) { |
+ ASSERT_TRUE(child->IsCrossProcessSubframe()); |
+ } |
+ ExecuteScriptAndCheckWindowOpen(child, 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 kDataUrlSuccessfulMessage. |
+ void ExecuteScriptAndCheckNavigation( |
+ RenderFrameHost* rfh, |
+ const std::string& javascript, |
+ ExpectedNavigationStatus expected_navigation_status) { |
+ const GURL original_url(shell()->web_contents()->GetLastCommittedURL()); |
+ const std::string expected_message; |
+ |
+ DataURLWarningConsoleObserverDelegate console_delegate( |
+ shell()->web_contents(), expected_navigation_status); |
+ shell()->web_contents()->SetDelegate(&console_delegate); |
+ |
+ TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
+ EXPECT_TRUE(ExecuteScript(rfh, javascript)); |
+ console_delegate.Wait(); |
+ EXPECT_FALSE(console_delegate.saw_failure_message()); |
+ shell()->web_contents()->SetDelegate(nullptr); |
+ |
+ switch (expected_navigation_status) { |
+ case NAVIGATION_ALLOWED: |
+ navigation_observer.Wait(); |
+ // The new page should have a data URL. |
+ EXPECT_TRUE( |
+ shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme)); |
+ EXPECT_TRUE(shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL() |
+ .SchemeIs(url::kDataScheme)); |
+ EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs( |
+ url::kDataScheme)); |
+ EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); |
+ break; |
+ |
+ case NAVIGATION_BLOCKED: |
+ // Original page shouldn't navigate away. |
+ EXPECT_EQ(original_url, shell()->web_contents()->GetURL()); |
+ EXPECT_EQ(original_url, shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL()); |
+ EXPECT_FALSE(navigation_observer.last_navigation_succeeded()); |
+ break; |
+ |
+ default: |
+ NOTREACHED(); |
+ } |
+ } |
+ |
+ // 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& 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() |
+ : kDataUrlBlockedPattern; |
+ |
+ 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(), 1); |
+ 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 a data URL. |
+ EXPECT_TRUE( |
+ shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme)); |
+ EXPECT_TRUE(shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL() |
+ .SchemeIs(url::kDataScheme)); |
+ EXPECT_TRUE(navigation_observer.last_navigation_url().SchemeIs( |
+ url::kDataScheme)); |
+ EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); |
+ break; |
+ |
+ case NAVIGATION_BLOCKED: |
+ // Original page shouldn't navigate away. |
+ EXPECT_EQ(original_url, shell()->web_contents()->GetURL()); |
+ EXPECT_EQ(original_url, shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL()); |
+ 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& 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( |
+ url::kDataScheme)); |
+ 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; |
+ EXPECT_TRUE(ExecuteScript(rfh, javascript)); |
+ Shell* new_shell = new_shell_observer.GetShell(); |
+ |
+ DownloadManager* download_manager = BrowserContext::GetDownloadManager( |
+ new_shell->web_contents()->GetBrowserContext()); |
+ DownloadTestObserverTerminal download_observer( |
+ download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); |
+ |
+ WaitForLoadStop(new_shell->web_contents()); |
+ download_observer.WaitForFinished(); |
+ |
+ EXPECT_TRUE(new_shell->web_contents()->GetURL().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()->GetURL()); |
+ EXPECT_EQ(original_url, shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL()); |
+ } |
+ |
+ // Executes |javascript| on |rfh| and waits for a download to be started. |
+ void ExecuteScriptAndCheckNavigationDownload(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()->GetURL()); |
+ EXPECT_EQ(original_url, shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL()); |
+ } |
+ |
+ bool IsPlzNavigateTest() const { |
+ return std::get<0>(GetParam()) == PLZNAVIGATE; |
+ } |
+ |
+ bool IsSitePerProcessTest() const { |
+ return std::get<1>(GetParam()) == SITE_PER_PROCESS; |
+ } |
+ |
+ // data URL form of the file at content/test/data/data_url_navigations.html |
+ GURL data_url() const { return data_url_; } |
+ |
+ private: |
+#if BUILDFLAG(ENABLE_PLUGINS) |
+ ScopedPluginRegister scoped_plugin_register_; |
+#endif // BUILDFLAG(ENABLE_PLUGINS) |
+ |
+ GURL data_url_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DataUrlNavigationBrowserTest); |
+}; |
+ |
+INSTANTIATE_TEST_CASE_P( |
+ , |
+ DataUrlNavigationBrowserTest, |
+ ::testing::Combine(::testing::Values(NO_PLZNAVIGATE, PLZNAVIGATE), |
+ ::testing::Values(NO_SITE_PER_PROCESS, |
+ SITE_PER_PROCESS))); |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// data URLs with HTML mimetype |
+// |
+// Tests that a direct navigation to a data URL doesn't show a console warning |
+// and is not blocked. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, BrowserInitiated_Allow) { |
+ DataURLWarningConsoleObserverDelegate console_delegate( |
+ shell()->web_contents(), NAVIGATION_ALLOWED); |
+ shell()->web_contents()->SetDelegate(&console_delegate); |
+ |
+ NavigateToURL(shell(), GURL("data:text/" |
+ "html,<html><script>console.log('NAVIGATION_" |
+ "SUCCESSFUL');</script>")); |
+ console_delegate.Wait(); |
+ shell()->web_contents()->SetDelegate(nullptr); |
+ |
+ EXPECT_TRUE(shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme)); |
+ EXPECT_TRUE(shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL() |
+ .SchemeIs(url::kDataScheme)); |
+} |
+ |
+// Tests that a content initiated navigation to a data URL is blocked. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, HTML_Navigation_Block) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckNavigation( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('navigate-top-frame-to-html').click()", |
+ NAVIGATION_BLOCKED); |
+} |
+ |
+// Test that a window.open to a data URL with HTML mime type is blocked. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, HTML_WindowOpen_Block) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckWindowOpen( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('window-open-html').click()", |
+ NAVIGATION_BLOCKED); |
+} |
+ |
+// Test that a form post to a data URL with HTML mime type is blocked. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, HTML_FormPost_Block) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckNavigation( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('form-post-to-html').click()", |
+ NAVIGATION_BLOCKED); |
+} |
+ |
+// Tests that navigating the main frame to a data URL with HTML mimetype from a |
+// subframe is blocked. |
+// TODO(meacer): Enable for all cases once crbug.com/651895 is fixed. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ HTML_NavigationFromFrame_Block) { |
+ // This test fails and is disabled in site-per-process + no plznavigate mode. |
+ // request->originDocument is null in FrameLoader::prepareForRequest, |
+ // allowing the navigation by default. |
+ if (!IsSitePerProcessTest() || IsPlzNavigateTest()) { |
+ 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")); |
+ |
+ TestNavigationFromFrame( |
+ "document.getElementById('navigate-top-frame-to-html').click()", |
+ NAVIGATION_BLOCKED); |
+ } |
+} |
+ |
+// Tests that opening a new data URL window from a subframe is blocked. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ HTML_WindowOpenFromFrame_Block) { |
+ 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")); |
+ |
+ TestWindowOpenFromFrame("document.getElementById('window-open-html').click()", |
+ NAVIGATION_BLOCKED); |
+} |
+ |
+// Tests that navigation to a data URL is allowed if the top frame is already a |
+// data URL. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ HTML_Navigation_DataToData_Allow) { |
+ NavigateToURL(shell(), data_url()); |
+ ExecuteScriptAndCheckNavigation( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('navigate-top-frame-to-html').click()", |
+ NAVIGATION_ALLOWED); |
+} |
+ |
+// Test that a form post to a data URL with HTML mime type is allowed if the top |
+// frame is already a data URL. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ HTML_FormPost_DataToData_Allow) { |
+ NavigateToURL(shell(), data_url()); |
+ ExecuteScriptAndCheckNavigation( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('form-post-to-html').click()", |
+ NAVIGATION_ALLOWED); |
+} |
+ |
+// Tests that navigating the top frame to a data URL with HTML mimetype is |
+// allowed if the top frame is already a data URL. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ HTML_NavigationFromFrame_TopFrameIsDataURL_Allow) { |
+ const GURL top_url( |
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>", |
+ embedded_test_server() |
+ ->GetURL("/data_url_navigations.html") |
+ .spec() |
+ .c_str())); |
+ NavigateToURL(shell(), top_url); |
+ |
+ TestNavigationFromFrame( |
+ "document.getElementById('navigate-top-frame-to-html').click()", |
+ NAVIGATION_ALLOWED); |
+} |
+ |
+// Tests that opening a new window with a data URL with HTML mimetype is allowed |
+// if the top frame already is a data URL. |
+// TODO(meacer): Disabled for PlzNavigate. DataUrlNavigationThrottler doesn't |
+// know about the initiator window and fails to allow. |
+// Enable for all cases once crbug.com/651895 is fixed. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ HTML_WindowOpenFromFrame_TopFrameIsDataURL_Allow) { |
+ if (!IsPlzNavigateTest()) { |
+ const GURL top_url( |
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>", |
+ embedded_test_server() |
+ ->GetURL("/data_url_navigations.html") |
+ .spec() |
+ .c_str())); |
+ NavigateToURL(shell(), top_url); |
+ |
+ TestWindowOpenFromFrame( |
+ "document.getElementById('window-open-html').click()", |
+ NAVIGATION_ALLOWED); |
+ } |
+} |
+//////////////////////////////////////////////////////////////////////////////// |
+// data URLs with octet-stream mimetype (binary) |
+// |
+// Test that window.open to a data URL results in an allowed download if the URL |
+// has a binary mime type. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ OctetStream_WindowOpen_Download) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckWindowOpenDownload( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('window-open-octetstream').click()"); |
+} |
+ |
+// Test that a navigation to a data URL results in a download if the URL has a |
+// binary mime type. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ OctetStream_Navigation_Download) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckNavigationDownload( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('navigate-top-frame-to-octetstream').click()"); |
+} |
+ |
+// Test that a form post to a data URL results in a download if the URL has a |
+// binary mime type. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ OctetStream_FormPost_Download) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckNavigationDownload( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('form-post-to-octetstream').click()"); |
+} |
+ |
+// Tests that navigating the main frame from a subframe results in a download |
+// if the URL has a binary mimetype. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ OctetStream_NavigationFromFrame_Download) { |
+ 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()"); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// data URLs with unknown mimetype |
+// |
+// Test that window.open to a data URL results in an allowed download if the URL |
+// has an unknown mime type. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ UnknownMimeType_WindowOpen_Download) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckWindowOpenDownload( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('window-open-unknown-mimetype').click()"); |
+} |
+ |
+// Test that a navigation to a data URL results in a download if the URL has an |
+// unknown mime type. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ UnknownMimeType_Navigation_Download) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckNavigationDownload( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('navigate-top-" |
+ "frame-to-unknown-mimetype').click()"); |
+} |
+ |
+// Test that a form post to a data URL results in a download if the URL has an |
+// unknown mime type. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ UnknownMimeType_FormPost_Download) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckNavigationDownload( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('form-post-to-unknown-mimetype').click()"); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// data URLs with PDF mimetype |
+// |
+// Tests that a direct navigation to a data URL with PDF mime type is allowed. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ PDF_DirectNavigation_Allow) { |
+ TestNavigationObserver observer(shell()->web_contents()); |
+ NavigateToURL(shell(), GURL(kPdfUrl)); |
+ EXPECT_EQ(GURL(kPdfUrl), observer.last_navigation_url()); |
+ EXPECT_TRUE(observer.last_navigation_succeeded()); |
+ EXPECT_TRUE(shell()->web_contents()->GetURL().SchemeIs(url::kDataScheme)); |
+ EXPECT_TRUE(shell() |
+ ->web_contents() |
+ ->GetController() |
+ .GetLastCommittedEntry() |
+ ->GetURL() |
+ .SchemeIs(url::kDataScheme)); |
+} |
+ |
+// Tests that a window.open to a data URL is blocked if the data URL has a |
+// mime type that will be handled by a plugin (PDF in this case). |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, PDF_WindowOpen_Block) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckWindowOpen( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('window-open-pdf').click()", NAVIGATION_BLOCKED); |
+} |
+ |
+// Test that a navigation to a data URL is blocked if the data URL has a mime |
+// type that will be handled by a plugin (PDF in this case). |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, PDF_Navigation_Block) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckPDFNavigation( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('navigate-top-frame-to-pdf').click()", |
+ NAVIGATION_BLOCKED); |
+} |
+ |
+// Test that a navigation to a data URL is blocked if the data URL has a mime |
+// type that will be handled by a plugin (PDF in this case). |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, PDF_FormPost_Block) { |
+ NavigateToURL(shell(), |
+ embedded_test_server()->GetURL("/data_url_navigations.html")); |
+ ExecuteScriptAndCheckPDFNavigation( |
+ shell()->web_contents()->GetMainFrame(), |
+ "document.getElementById('form-post-to-pdf').click()", |
+ NAVIGATION_BLOCKED); |
+} |
+ |
+// Tests that navigating the main frame to a data URL with PDF mimetype from a |
+// subframe is blocked. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ PDF_NavigationFromFrame_Block) { |
+ 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")); |
+ |
+ TestPDFNavigationFromFrame( |
+ "document.getElementById('navigate-top-frame-to-pdf').click()", |
+ NAVIGATION_BLOCKED); |
+} |
+ |
+// Tests that opening a window with a data URL with PDF mimetype from a |
+// subframe is blocked. |
+// TODO(meacer): Disabled because PDF navigations are handled by |
+// DataURLNavigationThrottler, and DataURLNavigationThrottler |
+// doesn't yet know anything about the opener of the window. |
+// Blocked on crbug.com/651895. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ DISABLED_PDF_WindowOpenFromFrame_Block) { |
+ 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")); |
+ |
+ TestPDFNavigationFromFrame( |
+ "document.getElementById('window-open-pdf').click()", NAVIGATION_BLOCKED); |
+} |
+ |
+// Tests that navigating the top frame to a data URL with PDF mimetype from a |
+// subframe is allowed if the top frame is already a data URL. |
+IN_PROC_BROWSER_TEST_P(DataUrlNavigationBrowserTest, |
+ PDF_NavigationFromFrame_TopFrameIsDataURL_Allow) { |
+ const GURL top_url( |
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>", |
+ embedded_test_server() |
+ ->GetURL("/data_url_navigations.html") |
+ .spec() |
+ .c_str())); |
+ NavigateToURL(shell(), top_url); |
+ |
+ TestPDFNavigationFromFrame( |
+ "document.getElementById('navigate-top-frame-to-pdf').click()", |
+ NAVIGATION_ALLOWED); |
+} |
+ |
+// Tests that opening a window with a data URL with PDF mimetype from a |
+// subframe is allowed if the top frame is already a data URL. |
+// TODO(meacer): Disabled because PDF navigations are handled by |
+// DataURLNavigationThrottler, and DataURLNavigationThrottler |
+// doesn't yet know anything about the opener of the window. |
+// Blocked on crbug.com/651895. |
+IN_PROC_BROWSER_TEST_P( |
+ DataUrlNavigationBrowserTest, |
+ DISABLED_PDF_WindowOpenFromFrame_TopFrameIsDataURL_Allow) { |
+ const GURL top_url( |
+ base::StringPrintf("data:text/html, <iframe src='%s'></iframe>", |
+ embedded_test_server() |
+ ->GetURL("/data_url_navigations.html") |
+ .spec() |
+ .c_str())); |
+ NavigateToURL(shell(), top_url); |
+ |
+ TestWindowOpenFromFrame("document.getElementById('window-open-pdf').click()", |
+ NAVIGATION_ALLOWED); |
+} |
+ |
+typedef ContentBrowserTest DataUrlXSSBrowserTest; |
+ |
+// This was previously a layout test called |
+// xss-DENIED-from-javascript-url-window-open.html. It's moved here now that |
+// pages can't open windows with data URLs. This test runs the same code on a |
+// data URL, as data URLs can open windows with data URLs. |
+IN_PROC_BROWSER_TEST_F(DataUrlXSSBrowserTest, DenyXSSFromDataUrlWindowOpen) { |
+ const char kMainPage[] = |
+ "data:text/html," |
+ "<script>" |
+ " window.addEventListener('message', function() {" |
+ " window.domAutomationController.send(" |
+ " document.getElementById('accessMe')" |
+ " .textContent.startsWith('PASS'));" |
+ " });" |
+ "</script>" |
+ "<body>" |
+ "<p id='accessMe'>PASS: Access from a window opened with a data: URL was " |
+ "denied.</p>" |
+ "</body>"; |
+ |
+ // Opens a window, attempts to modify the opener's DOM and notifies the opener |
+ // after the attempt. |
+ const char kWindowOpen[] = |
+ "window.open('data:text/html," |
+ "<script>" |
+ " try {" |
+ " opener.document.getElementById(`accessMe`).innerHTML = " |
+ " `FAIL: Access from a window opened with a data: URL was allowed!`;" |
+ " } catch (e) {}" |
+ " window.opener.postMessage(`done`, `*`);" |
+ "</script>', '', '_blank')"; |
+ NavigateToURL(shell(), GURL(kMainPage)); |
+ bool pass = false; |
+ ASSERT_TRUE( |
+ ExecuteScriptAndExtractBool(shell()->web_contents(), kWindowOpen, &pass)); |
+ EXPECT_TRUE(pass); |
+} |
+ |
+// Similar to DenyXSSToDataUrlInOtherWindow, but this time the opener tries |
+// to modify the opened data URL. Used to be a layout test called |
+// xss-DENIED-to-data-url-window-open.html. |
+IN_PROC_BROWSER_TEST_F(DataUrlXSSBrowserTest, DenyXSSToDataUrlWindowOpen) { |
+ const char kMainPage[] = |
+ "data:text/html," |
+ "<script>" |
+ " var openedWindow = null;" |
+ " window.addEventListener('message', function(evt) {" |
+ " if (evt.data == 'try-accessing-window') {" |
+ " try {" |
+ " openedWindow.document.getElementById('accessMe').innerHTML = " |
+ " 'FAIL: Access to a window opened with a data: URL was " |
+ "allowed.';" |
+ " } catch (e) {}" |
+ " openedWindow.postMessage('done', '*');" |
+ " }" |
+ " if (evt.data == 'test-succeed')" |
+ " window.domAutomationController.send(true);" |
+ " if (evt.data == 'test-fail')" |
+ " window.domAutomationController.send(false);" |
+ " });" |
+ "</script>"; |
+ |
+ // Opens a window. Once loaded, the window sends a message asking the opener |
+ // to access its (the window's) DOM. The opener attempts to change the |
+ // window's DOM and then asks the window to notify whether its DOM changed. |
+ const char kWindowOpen[] = |
+ "openedWindow = window.open('data:text/html," |
+ "<body>" |
+ " <p id=accessMe>PASS: Access from a window opened with a data: URL" |
+ " was denied.</p>" |
+ "<script>" |
+ " window.addEventListener(`message`, function() {" |
+ " if (document.getElementById(`accessMe`)" |
+ " .textContent.startsWith(`PASS`)) {" |
+ " window.opener.postMessage(`test-succeed`, `*`);" |
+ " } else {" |
+ " window.opener.postMessage(`test-fail`, `*`);" |
+ " }" |
+ " });" |
+ " window.onload = function() {" |
+ " window.opener.postMessage(`try-accessing-window`, `*`);" |
+ " }" |
+ "</scr' + 'ipt>" |
+ "</body>');"; |
+ NavigateToURL(shell(), GURL(kMainPage)); |
+ bool pass = false; |
+ ASSERT_TRUE( |
+ ExecuteScriptAndExtractBool(shell()->web_contents(), kWindowOpen, &pass)); |
+ EXPECT_TRUE(pass); |
+} |
+ |
+} // content |