| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chrome_content_browser_client.h" |
| #include "chrome/browser/notifications/notification_permission_context.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_mock_cert_verifier.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| |
| namespace { |
| |
| const char kTestFilePath[] = |
| "/notifications/notification_permission_checker.html"; |
| const char kTesterHost[] = "notification.com"; |
| const char kIsolatedEmbedderHost[] = "isolated.com"; |
| const char kEmbedderHost[] = "normal.com"; |
| |
| // A ChromeContentBrowserClient that returns a non-default |
| // StoragePartitionConfig for the given Origin. |
| class StoragePartitioningChromeContentBrowserClient |
| : public ChromeContentBrowserClient { |
| public: |
| explicit StoragePartitioningChromeContentBrowserClient( |
| const std::string& partitioned_host) |
| : partitioned_host_(partitioned_host) {} |
| |
| ~StoragePartitioningChromeContentBrowserClient() override = default; |
| |
| content::StoragePartitionConfig GetStoragePartitionConfigForSite( |
| content::BrowserContext* browser_context, |
| const GURL& site) override { |
| if (site.host() == partitioned_host_) { |
| return content::StoragePartitionConfig::Create( |
| browser_context, partitioned_host_, /*partition_name=*/"", |
| /*in_memory=*/false); |
| } |
| return ChromeContentBrowserClient::GetStoragePartitionConfigForSite( |
| browser_context, site); |
| } |
| |
| private: |
| std::string partitioned_host_; |
| }; |
| |
| } // namespace |
| |
| class NotificationPermissionBrowserTest : public InProcessBrowserTest { |
| public: |
| NotificationPermissionBrowserTest() |
| : partitioning_client_(kIsolatedEmbedderHost) {} |
| |
| NotificationPermissionBrowserTest(const NotificationPermissionBrowserTest&) = |
| delete; |
| NotificationPermissionBrowserTest& operator=( |
| const NotificationPermissionBrowserTest&) = delete; |
| ~NotificationPermissionBrowserTest() override { |
| CHECK_EQ(&partitioning_client_, |
| SetBrowserClientForTesting(original_client_)); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK); |
| |
| https_server_ = std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server_->ServeFilesFromSourceDirectory(server_root_); |
| EXPECT_TRUE(https_server_->Start()); |
| |
| original_client_ = SetBrowserClientForTesting(&partitioning_client_); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| mock_cert_verifier_.SetUpCommandLine(command_line); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| mock_cert_verifier_.SetUpInProcessBrowserTestFixture(); |
| } |
| |
| void TearDownInProcessBrowserTestFixture() override { |
| mock_cert_verifier_.TearDownInProcessBrowserTestFixture(); |
| } |
| |
| protected: |
| void GrantNotificationPermissionForTest(const GURL& url) const { |
| NotificationPermissionContext::UpdatePermission( |
| browser()->profile(), url.DeprecatedGetOriginAsURL(), |
| CONTENT_SETTING_ALLOW); |
| } |
| |
| GURL TesterUrl() const { |
| return https_server_->GetURL(kTesterHost, kTestFilePath); |
| } |
| |
| GURL IsolatedEmbedderUrl() const { |
| return https_server_->GetURL(kIsolatedEmbedderHost, kTestFilePath); |
| } |
| |
| GURL EmbedderUrl() const { |
| return https_server_->GetURL(kEmbedderHost, kTestFilePath); |
| } |
| |
| content::WebContents* GetActiveWebContents() const { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| content::RenderFrameHost* CreateChildIframe( |
| content::RenderFrameHost* parent_rfh, |
| const GURL& iframe_src) { |
| // For now assume this is the only child iframe. |
| EXPECT_FALSE(ChildFrameAt(parent_rfh, 0)); |
| |
| EXPECT_EQ("iframe loaded", |
| EvalJs(parent_rfh, content::JsReplace(R"( |
| new Promise((resolve, reject) => { |
| const iframe = document.createElement('iframe'); |
| iframe.id = 'child_iframe'; |
| iframe.src = $1; |
| iframe.onload = _ => { resolve('iframe loaded') }; |
| iframe.onerror = e => { reject(e) }; |
| document.body.appendChild(iframe); |
| }))", |
| iframe_src))); |
| |
| content::RenderFrameHost* iframe = ChildFrameAt(parent_rfh, 0); |
| EXPECT_TRUE(iframe); |
| EXPECT_EQ(iframe_src, iframe->GetLastCommittedURL()); |
| return iframe; |
| } |
| |
| private: |
| const base::FilePath server_root_{FILE_PATH_LITERAL("chrome/test/data")}; |
| std::unique_ptr<net::EmbeddedTestServer> https_server_; |
| content::ContentMockCertVerifier mock_cert_verifier_; |
| |
| raw_ptr<content::ContentBrowserClient> original_client_ = nullptr; |
| StoragePartitioningChromeContentBrowserClient partitioning_client_; |
| }; |
| |
| // Tests that notification permissions aren't delegated to an embedded frame |
| // as other permissions are. If 'example.com' was granted notification |
| // permissions by the user when it was a top-level frame, then it retains that |
| // permission when iframed in another page, regardless of the other page's |
| // permission status. |
| IN_PROC_BROWSER_TEST_F(NotificationPermissionBrowserTest, |
| PermissionNotDelegated) { |
| GrantNotificationPermissionForTest(TesterUrl()); |
| |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), TesterUrl())); |
| content::RenderFrameHost* main_frame = |
| GetActiveWebContents()->GetPrimaryMainFrame(); |
| EXPECT_EQ("granted", EvalJs(main_frame, "getNotificationPermission()")); |
| EXPECT_EQ("granted", |
| EvalJs(main_frame, "getServiceWorkerNotificationPermission()")); |
| |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), EmbedderUrl())); |
| main_frame = GetActiveWebContents()->GetPrimaryMainFrame(); |
| EXPECT_EQ("default", EvalJs(main_frame, "getNotificationPermission()")); |
| |
| content::RenderFrameHost* iframe = CreateChildIframe(main_frame, TesterUrl()); |
| EXPECT_EQ("granted", EvalJs(iframe, "getNotificationPermission()")); |
| EXPECT_EQ("granted", |
| EvalJs(iframe, "getServiceWorkerNotificationPermission()")); |
| EXPECT_EQ("granted", EvalJs(iframe, "getPushPermission()")); |
| EXPECT_EQ("granted", EvalJs(iframe, "getServiceWorkerPushPermission()")); |
| } |
| |
| // Tests that iframes not using their normal StoragePartition don't have |
| // notification permission, even if they would have permission outside of an |
| // isolated app. |
| IN_PROC_BROWSER_TEST_F(NotificationPermissionBrowserTest, |
| IframesInNonDefaultPartitionDontGetPermission) { |
| GrantNotificationPermissionForTest(TesterUrl()); |
| |
| // Verify that TesterUrl() has notification/push permission. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), TesterUrl())); |
| content::RenderFrameHost* main_frame = |
| GetActiveWebContents()->GetPrimaryMainFrame(); |
| EXPECT_EQ("granted", EvalJs(main_frame, "getNotificationPermission()")); |
| EXPECT_EQ("granted", |
| EvalJs(main_frame, "getServiceWorkerNotificationPermission()")); |
| EXPECT_EQ("granted", EvalJs(main_frame, "queryNotificationPermission()")); |
| EXPECT_EQ("granted", |
| EvalJs(main_frame, "queryServiceWorkerNotificationPermission()")); |
| EXPECT_EQ("granted", EvalJs(main_frame, "getPushPermission()")); |
| EXPECT_EQ("granted", EvalJs(main_frame, "getServiceWorkerPushPermission()")); |
| |
| // Load a site that uses a dedicated StoragePartition and verify that it has |
| // default notification/push permissions. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), IsolatedEmbedderUrl())); |
| main_frame = GetActiveWebContents()->GetPrimaryMainFrame(); |
| EXPECT_EQ("default", EvalJs(main_frame, "getNotificationPermission()")); |
| EXPECT_EQ("denied", |
| EvalJs(main_frame, "getServiceWorkerNotificationPermission()")); |
| EXPECT_EQ("prompt", EvalJs(main_frame, "queryNotificationPermission()")); |
| EXPECT_EQ("prompt", |
| EvalJs(main_frame, "queryServiceWorkerNotificationPermission()")); |
| EXPECT_EQ("prompt", EvalJs(main_frame, "getPushPermission()")); |
| EXPECT_EQ("prompt", EvalJs(main_frame, "getServiceWorkerPushPermission()")); |
| |
| // Load TesterUrl() in an iframe inside the dedicated StoragePartition page. |
| // Even though TesterUrl() has notification/push permission when in a main |
| // frame, it shouldn't when it's embedded in a different StoragePartition. |
| content::RenderFrameHost* iframe = CreateChildIframe(main_frame, TesterUrl()); |
| EXPECT_EQ("denied", EvalJs(iframe, "getNotificationPermission()")); |
| EXPECT_EQ("denied", |
| EvalJs(iframe, "getServiceWorkerNotificationPermission()")); |
| EXPECT_EQ("denied", EvalJs(iframe, "queryNotificationPermission()")); |
| EXPECT_EQ("denied", |
| EvalJs(iframe, "queryServiceWorkerNotificationPermission()")); |
| EXPECT_EQ("denied", EvalJs(iframe, "requestNotificationPermission()")); |
| EXPECT_EQ("denied", EvalJs(iframe, "getPushPermission()")); |
| EXPECT_EQ("denied", EvalJs(iframe, "getServiceWorkerPushPermission()")); |
| EXPECT_EQ( |
| "a JavaScript error: \"NotAllowedError: " |
| "Registration failed - permission denied\"\n", |
| EvalJs(iframe, "requestPushPermission()").error); |
| } |