| // Copyright 2015 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 <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/time/time.h" |
| #include "content/browser/webui/content_web_ui_controller_factory.h" |
| #include "content/public/browser/child_process_security_policy.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui_data_source.h" |
| #include "content/public/browser/web_ui_message_handler.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/public/test/web_ui_browsertest_util.h" |
| #include "content/shell/browser/shell.h" |
| #include "third_party/blink/public/common/input/web_mouse_event.h" |
| #include "third_party/blink/public/common/input/web_mouse_wheel_event.h" |
| #include "ui/events/base_event_utils.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using WebUIImplBrowserTest = ContentBrowserTest; |
| |
| class TestWebUIMessageHandler : public WebUIMessageHandler { |
| public: |
| void RegisterMessages() override { |
| web_ui()->RegisterMessageCallback( |
| "messageRequiringGesture", |
| base::BindRepeating(&TestWebUIMessageHandler::OnMessageRequiringGesture, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "notifyFinish", |
| base::BindRepeating(&TestWebUIMessageHandler::OnNotifyFinish, |
| base::Unretained(this))); |
| } |
| |
| void set_finish_closure(base::RepeatingClosure closure) { |
| finish_closure_ = std::move(closure); |
| } |
| |
| int message_requiring_gesture_count() const { |
| return message_requiring_gesture_count_; |
| } |
| |
| private: |
| void OnMessageRequiringGesture(const base::ListValue* args) { |
| ++message_requiring_gesture_count_; |
| } |
| |
| void OnNotifyFinish(const base::ListValue* args) { |
| if (finish_closure_) |
| finish_closure_.Run(); |
| } |
| |
| int message_requiring_gesture_count_ = 0; |
| base::RepeatingClosure finish_closure_; |
| }; |
| |
| class WebUIRequiringGestureBrowserTest : public ContentBrowserTest { |
| public: |
| WebUIRequiringGestureBrowserTest() { |
| clock_.SetNowTicks(base::TimeTicks::Now()); |
| ui::SetEventTickClockForTesting(&clock_); |
| } |
| |
| ~WebUIRequiringGestureBrowserTest() override { |
| ui::SetEventTickClockForTesting(nullptr); |
| } |
| |
| void SetUpOnMainThread() override { |
| ASSERT_TRUE(NavigateToURL(web_contents(), GetWebUIURL(kChromeUIGpuHost))); |
| test_handler_ = new TestWebUIMessageHandler(); |
| web_contents()->GetWebUI()->AddMessageHandler( |
| base::WrapUnique(test_handler_)); |
| } |
| |
| protected: |
| void SendMessageAndWaitForFinish() { |
| main_rfh()->ExecuteJavaScriptForTests( |
| base::ASCIIToUTF16("chrome.send('messageRequiringGesture');" |
| "chrome.send('notifyFinish');"), |
| base::NullCallback()); |
| base::RunLoop run_loop; |
| test_handler()->set_finish_closure(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| void AdvanceClock(base::TimeDelta delta) { clock_.Advance(delta); } |
| |
| WebContents* web_contents() { return shell()->web_contents(); } |
| RenderFrameHost* main_rfh() { return web_contents()->GetMainFrame(); } |
| |
| TestWebUIMessageHandler* test_handler() { return test_handler_; } |
| |
| private: |
| base::SimpleTestTickClock clock_; |
| |
| // Owned by the WebUI associated with the WebContents. |
| TestWebUIMessageHandler* test_handler_ = nullptr; |
| }; |
| |
| } // namespace |
| |
| // Tests that navigating between WebUIs of different types results in |
| // SiteInstance swap when running in process-per-tab process model. |
| IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, ForceSwapOnDifferenteWebUITypes) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kProcessPerTab); |
| WebContents* web_contents = shell()->web_contents(); |
| |
| const GURL web_ui_url(GetWebUIURL(kChromeUIHistogramHost)); |
| EXPECT_TRUE(ContentWebUIControllerFactory::GetInstance()->UseWebUIForURL( |
| web_contents->GetBrowserContext(), web_ui_url)); |
| ASSERT_TRUE(NavigateToURL(web_contents, web_ui_url)); |
| EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( |
| web_contents->GetMainFrame()->GetProcess()->GetID())); |
| |
| // Capture the SiteInstance before navigating for later comparison. |
| scoped_refptr<SiteInstance> orig_site_instance( |
| web_contents->GetSiteInstance()); |
| int32_t orig_browsing_instance_id = |
| orig_site_instance->GetBrowsingInstanceId(); |
| |
| // Navigate to a different WebUI type and ensure that the SiteInstance |
| // has changed and the new process also has WebUI bindings. |
| const GURL web_ui_url2(GetWebUIURL(kChromeUIGpuHost)); |
| EXPECT_TRUE(ContentWebUIControllerFactory::GetInstance()->UseWebUIForURL( |
| web_contents->GetBrowserContext(), web_ui_url2)); |
| ASSERT_TRUE(NavigateToURL(web_contents, web_ui_url2)); |
| auto* new_site_instance = web_contents->GetSiteInstance(); |
| EXPECT_NE(orig_site_instance, new_site_instance); |
| EXPECT_NE(orig_browsing_instance_id, |
| new_site_instance->GetBrowsingInstanceId()); |
| EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( |
| web_contents->GetMainFrame()->GetProcess()->GetID())); |
| } |
| |
| // Tests that a WebUI page will use a separate SiteInstance when we navigated to |
| // it from the initial blank page. |
| IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, |
| ForceBrowsingInstanceSwapOnFirstNavigation) { |
| WebContents* web_contents = shell()->web_contents(); |
| scoped_refptr<SiteInstance> orig_site_instance( |
| web_contents->GetSiteInstance()); |
| // Navigate from the initial blank page to the WebUI URL. |
| const GURL web_ui_url(GetWebUIURL(kChromeUIHistogramHost)); |
| EXPECT_TRUE(ContentWebUIControllerFactory::GetInstance()->UseWebUIForURL( |
| web_contents->GetBrowserContext(), web_ui_url)); |
| ASSERT_TRUE(NavigateToURL(web_contents, web_ui_url)); |
| |
| EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( |
| web_contents->GetMainFrame()->GetProcess()->GetID())); |
| auto* new_site_instance = web_contents->GetSiteInstance(); |
| EXPECT_NE(orig_site_instance, new_site_instance); |
| EXPECT_FALSE(orig_site_instance->IsRelatedSiteInstance(new_site_instance)); |
| } |
| |
| // Tests that navigating from chrome:// to chrome-untrusted:// results in |
| // SiteInstance swap. |
| IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, ForceSwapOnFromChromeToUntrusted) { |
| WebContents* web_contents = shell()->web_contents(); |
| AddUntrustedDataSource(web_contents->GetBrowserContext(), "test-host"); |
| |
| const GURL web_ui_url(GetWebUIURL(kChromeUIHistogramHost)); |
| EXPECT_TRUE(ContentWebUIControllerFactory::GetInstance()->UseWebUIForURL( |
| web_contents->GetBrowserContext(), web_ui_url)); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, web_ui_url)); |
| EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( |
| web_contents->GetMainFrame()->GetProcess()->GetID())); |
| |
| // Capture the SiteInstance before navigating for later comparison. |
| scoped_refptr<SiteInstance> orig_site_instance( |
| web_contents->GetSiteInstance()); |
| int32_t orig_browsing_instance_id = |
| orig_site_instance->GetBrowsingInstanceId(); |
| |
| // Navigate to chrome-untrusted:// and ensure that the SiteInstance |
| // has changed and the new process has no WebUI bindings. |
| ASSERT_TRUE(NavigateToURL(web_contents, |
| GetChromeUntrustedUIURL("test-host/title1.html"))); |
| auto* new_site_instance = web_contents->GetSiteInstance(); |
| EXPECT_NE(orig_site_instance, new_site_instance); |
| EXPECT_NE(orig_browsing_instance_id, |
| new_site_instance->GetBrowsingInstanceId()); |
| EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( |
| web_contents->GetMainFrame()->GetProcess()->GetID())); |
| } |
| |
| // Tests that navigating from chrome-untrusted:// to chrome:// results in |
| // SiteInstance swap. |
| IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, ForceSwapOnFromUntrustedToChrome) { |
| WebContents* web_contents = shell()->web_contents(); |
| AddUntrustedDataSource(web_contents->GetBrowserContext(), "test-host"); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, |
| GetChromeUntrustedUIURL("test-host/title1.html"))); |
| EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( |
| web_contents->GetMainFrame()->GetProcess()->GetID())); |
| |
| // Capture the SiteInstance before navigating for later comparison. |
| scoped_refptr<SiteInstance> orig_site_instance( |
| web_contents->GetSiteInstance()); |
| int32_t orig_browsing_instance_id = |
| orig_site_instance->GetBrowsingInstanceId(); |
| |
| // Navigate to a WebUI and ensure that the SiteInstance has changed and the |
| // new process has WebUI bindings. |
| const GURL web_ui_url(GetWebUIURL(kChromeUIHistogramHost)); |
| EXPECT_TRUE(ContentWebUIControllerFactory::GetInstance()->UseWebUIForURL( |
| web_contents->GetBrowserContext(), web_ui_url)); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, web_ui_url)); |
| auto* new_site_instance = web_contents->GetSiteInstance(); |
| EXPECT_NE(orig_site_instance, new_site_instance); |
| EXPECT_NE(orig_browsing_instance_id, |
| new_site_instance->GetBrowsingInstanceId()); |
| EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( |
| web_contents->GetMainFrame()->GetProcess()->GetID())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, SameDocumentNavigationsAndReload) { |
| auto* web_contents = shell()->web_contents(); |
| ASSERT_TRUE(NavigateToURL(web_contents, GetWebUIURL(kChromeUIHistogramHost))); |
| |
| WebUIMessageHandler* test_handler = new TestWebUIMessageHandler; |
| web_contents->GetWebUI()->AddMessageHandler(base::WrapUnique(test_handler)); |
| test_handler->AllowJavascriptForTesting(); |
| |
| // Push onto window.history. Back should now be an in-page navigation. |
| ASSERT_TRUE(ExecuteScript(web_contents, |
| "window.history.pushState({}, '', 'foo.html')")); |
| shell()->GoBackOrForward(-1); |
| EXPECT_TRUE(WaitForLoadStop(web_contents)); |
| |
| // Test handler should still have JavaScript allowed after in-page navigation. |
| EXPECT_TRUE(test_handler->IsJavascriptAllowed()); |
| |
| shell()->Reload(); |
| EXPECT_TRUE(WaitForLoadStop(web_contents)); |
| |
| // Verify that after a reload, the test handler has been disallowed. |
| EXPECT_FALSE(test_handler->IsJavascriptAllowed()); |
| } |
| |
| // A WebUI message that should require a user gesture is ignored if there is no |
| // recent input event. |
| IN_PROC_BROWSER_TEST_F(WebUIRequiringGestureBrowserTest, |
| MessageRequiringGestureIgnoredIfNoGesture) { |
| SendMessageAndWaitForFinish(); |
| EXPECT_EQ(0, test_handler()->message_requiring_gesture_count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebUIRequiringGestureBrowserTest, |
| MessageRequiringGestureIgnoresRendererOnlyGesture) { |
| // Note: this doesn't use SendMessageAndWaitForFinish() since this test needs |
| // to use a test-only helper to instantiate a scoped user gesture in the |
| // renderer. |
| main_rfh()->ExecuteJavaScriptWithUserGestureForTests( |
| base::ASCIIToUTF16("chrome.send('messageRequiringGesture');" |
| "chrome.send('notifyFinish');")); |
| base::RunLoop run_loop; |
| test_handler()->set_finish_closure(run_loop.QuitClosure()); |
| run_loop.Run(); |
| EXPECT_EQ(0, test_handler()->message_requiring_gesture_count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebUIRequiringGestureBrowserTest, |
| MessageRequiringGestureIgnoresNonInteractiveEvents) { |
| // Mouse enter / mouse move / mouse leave should not be considered input |
| // events that interact with the page. |
| content::SimulateMouseEvent(web_contents(), |
| blink::WebInputEvent::Type::kMouseEnter, |
| gfx::Point(50, 50)); |
| content::SimulateMouseEvent(web_contents(), |
| blink::WebInputEvent::Type::kMouseMove, |
| gfx::Point(50, 50)); |
| content::SimulateMouseEvent(web_contents(), |
| blink::WebInputEvent::Type::kMouseLeave, |
| gfx::Point(50, 50)); |
| // Nor should mouse wheel. |
| content::SimulateMouseWheelEvent(web_contents(), gfx::Point(50, 50), |
| gfx::Vector2d(0, 100), |
| blink::WebMouseWheelEvent::kPhaseBegan); |
| SendMessageAndWaitForFinish(); |
| EXPECT_EQ(0, test_handler()->message_requiring_gesture_count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebUIRequiringGestureBrowserTest, |
| MessageRequiringGestureAllowedWithInteractiveEvent) { |
| // Simulate a click at Now. |
| content::SimulateMouseClick(web_contents(), 0, |
| blink::WebMouseEvent::Button::kLeft); |
| |
| // Now+0 should be allowed. |
| SendMessageAndWaitForFinish(); |
| EXPECT_EQ(1, test_handler()->message_requiring_gesture_count()); |
| |
| // Now+5 seconds should be allowed. |
| AdvanceClock(base::TimeDelta::FromSeconds(5)); |
| SendMessageAndWaitForFinish(); |
| EXPECT_EQ(2, test_handler()->message_requiring_gesture_count()); |
| |
| // Anything after that should be disallowed though. |
| AdvanceClock(base::TimeDelta::FromMicroseconds(1)); |
| SendMessageAndWaitForFinish(); |
| EXPECT_EQ(2, test_handler()->message_requiring_gesture_count()); |
| } |
| |
| // Verify that we can successfully navigate to a chrome-untrusted:// URL. |
| IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, UntrustedSchemeLoads) { |
| auto* web_contents = shell()->web_contents(); |
| AddUntrustedDataSource(web_contents->GetBrowserContext(), "test-host"); |
| |
| const GURL untrusted_url(GetChromeUntrustedUIURL("test-host/title2.html")); |
| EXPECT_TRUE(NavigateToURL(web_contents, untrusted_url)); |
| EXPECT_EQ(base::ASCIIToUTF16("Title Of Awesomeness"), |
| web_contents->GetTitle()); |
| } |
| |
| class WebUIRequestSchemesTest : public ContentBrowserTest { |
| public: |
| WebUIRequestSchemesTest() { |
| WebUIControllerFactory::RegisterFactory(&factory_); |
| } |
| |
| ~WebUIRequestSchemesTest() override { |
| WebUIControllerFactory::UnregisterFactoryForTesting(&factory_); |
| } |
| |
| WebUIRequestSchemesTest(const WebUIRequestSchemesTest&) = delete; |
| |
| WebUIRequestSchemesTest& operator=(const WebUIRequestSchemesTest&) = delete; |
| |
| TestWebUIControllerFactory* factory() { return &factory_; } |
| |
| private: |
| TestWebUIControllerFactory factory_; |
| }; |
| |
| // Verify that by default WebUI's child process security policy can request |
| // default schemes such as chrome. |
| // |
| // ChildProcessSecurityPolicy::CanRequestURL() always returns true for the |
| // following schemes, but in practice there are other checks that stop WebUIs |
| // from accessing these schemes. |
| IN_PROC_BROWSER_TEST_F(WebUIRequestSchemesTest, DefaultSchemesCanBeRequested) { |
| auto* web_contents = shell()->web_contents(); |
| |
| std::string host_and_path = "test-host/title2.html"; |
| const GURL chrome_url(GetWebUIURL(host_and_path)); |
| GURL url; |
| |
| std::vector<std::string> requestable_schemes = { |
| // WebSafe Schemes: |
| "feed", url::kHttpScheme, url::kHttpsScheme, url::kFtpScheme, |
| url::kDataScheme, url::kWsScheme, url::kWssScheme, |
| // Default added as requestable schemes: |
| url::kFileScheme, kChromeUIScheme}; |
| |
| std::vector<std::string> unrequestable_schemes = { |
| kChromeDevToolsScheme, url::kBlobScheme, kChromeUIUntrustedScheme, |
| base::StrCat({url::kFileSystemScheme, ":", kChromeUIUntrustedScheme})}; |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, chrome_url)); |
| |
| for (const auto& requestable_scheme : requestable_schemes) { |
| url = GURL(base::StrCat( |
| {requestable_scheme, url::kStandardSchemeSeparator, host_and_path})); |
| EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->CanRequestURL( |
| web_contents->GetMainFrame()->GetProcess()->GetID(), url)); |
| } |
| |
| for (const auto& unrequestable_scheme : unrequestable_schemes) { |
| url = GURL(base::StrCat( |
| {unrequestable_scheme, url::kStandardSchemeSeparator, host_and_path})); |
| EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->CanRequestURL( |
| web_contents->GetMainFrame()->GetProcess()->GetID(), url)); |
| } |
| } |
| |
| // Verify that we can successfully allow non-default URL schemes to |
| // be requested by the WebUI's child process security policy. |
| IN_PROC_BROWSER_TEST_F(WebUIRequestSchemesTest, |
| AllowAdditionalSchemesToBeRequested) { |
| auto* web_contents = shell()->web_contents(); |
| |
| std::string host_and_path = "test-host/title2.html"; |
| GURL url; |
| |
| // All URLs with a web safe scheme, or with a scheme not |
| // handled by ContentBrowserClient are requestable. All other schemes are |
| // not requestable. |
| std::vector<std::string> requestable_schemes = { |
| // WebSafe schemes: |
| "feed", |
| url::kHttpScheme, |
| url::kHttpsScheme, |
| url::kFtpScheme, |
| url::kDataScheme, |
| url::kWsScheme, |
| url::kWssScheme, |
| // Default added as requestable schemes: |
| "file", |
| kChromeUIScheme, |
| // Schemes given requestable access: |
| kChromeUIUntrustedScheme, |
| base::StrCat({url::kFileSystemScheme, ":", kChromeUIUntrustedScheme}), |
| }; |
| std::vector<std::string> unrequestable_schemes = { |
| kChromeDevToolsScheme, url::kBlobScheme, |
| base::StrCat({url::kFileSystemScheme, ":", kChromeDevToolsScheme})}; |
| |
| const GURL chrome_ui_url = GetWebUIURL(base::StrCat( |
| {host_and_path, "?requestableschemes=", kChromeUIUntrustedScheme, ",", |
| url::kWsScheme})); |
| |
| ASSERT_TRUE(NavigateToURL(web_contents, chrome_ui_url)); |
| |
| for (const auto& requestable_scheme : requestable_schemes) { |
| url = GURL(base::StrCat( |
| {requestable_scheme, url::kStandardSchemeSeparator, host_and_path})); |
| EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->CanRequestURL( |
| web_contents->GetMainFrame()->GetProcess()->GetID(), url)); |
| } |
| |
| for (const auto& unrequestable_scheme : unrequestable_schemes) { |
| url = GURL(base::StrCat( |
| {unrequestable_scheme, url::kStandardSchemeSeparator, host_and_path})); |
| EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->CanRequestURL( |
| web_contents->GetMainFrame()->GetProcess()->GetID(), url)); |
| } |
| } |
| |
| } // namespace content |