From e17234b0e40356273e1e2fc95b03c694b25a0a04 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Thu, 10 Sep 2020 15:19:34 +0200 Subject: [PATCH] Add Ephemeral storage support for localStorage and sessionStorage Ephemeral storage persists DOM storage in third-party frames in a partitioned storage area that is unique to each top-level frame eTLD. The storage area is persisted until no tabs from the top-level eTLD exist. This change also adds a runtime flag for ephemeral storage, which is only useful if third-party storage is activated. A future change will selectively enable third-party storage when the flag is active. --- browser/BUILD.gn | 1 + browser/brave_tab_helpers.cc | 7 + browser/ephemeral_storage/BUILD.gn | 35 ++ .../ephemeral_storage_browsertest.cc | 302 ++++++++++++++++++ .../ephemeral_storage_tab_helper.cc | 122 +++++++ .../ephemeral_storage_tab_helper.h | 52 +++ chromium_src/chrome/browser/about_flags.cc | 7 +- .../chrome/browser/flag_descriptions.cc | 3 + .../chrome/browser/flag_descriptions.h | 2 + .../core/common/cookie_settings_base.cc | 4 +- .../content/browser/browser_context.cc | 41 +++ .../content/public/browser/browser_context.h | 30 ++ .../third_party/blink/common/features.cc | 13 + .../blink/public/common/features.h | 24 ++ .../storage/brave_dom_window_storage.h | 23 +- .../modules/storage/dom_window_storage.cc | 235 +++++++++++++- test/BUILD.gn | 1 + test/data/ephemeral_storage.html | 5 + 18 files changed, 885 insertions(+), 22 deletions(-) create mode 100644 browser/ephemeral_storage/BUILD.gn create mode 100644 browser/ephemeral_storage/ephemeral_storage_browsertest.cc create mode 100644 browser/ephemeral_storage/ephemeral_storage_tab_helper.cc create mode 100644 browser/ephemeral_storage/ephemeral_storage_tab_helper.h create mode 100644 chromium_src/content/browser/browser_context.cc create mode 100644 chromium_src/content/public/browser/browser_context.h create mode 100644 chromium_src/third_party/blink/common/features.cc create mode 100644 chromium_src/third_party/blink/public/common/features.h create mode 100644 test/data/ephemeral_storage.html diff --git a/browser/BUILD.gn b/browser/BUILD.gn index 51adb6821545..b78e29349695 100644 --- a/browser/BUILD.gn +++ b/browser/BUILD.gn @@ -114,6 +114,7 @@ source_set("browser_process") { "browsing_data", "content_settings", "download", + "ephemeral_storage", "farbling", "net", "ntp_background_images", diff --git a/browser/brave_tab_helpers.cc b/browser/brave_tab_helpers.cc index 7711d296bc33..fa9064623711 100644 --- a/browser/brave_tab_helpers.cc +++ b/browser/brave_tab_helpers.cc @@ -8,6 +8,7 @@ #include "base/command_line.h" #include "base/feature_list.h" #include "brave/browser/brave_stats/brave_stats_tab_helper.h" +#include "brave/browser/ephemeral_storage/ephemeral_storage_tab_helper.h" #include "brave/browser/farbling/farbling_tab_helper.h" #include "brave/browser/profiles/profile_util.h" #include "brave/browser/ui/bookmark/brave_bookmark_tab_helper.h" @@ -22,6 +23,7 @@ #include "brave/components/speedreader/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" #include "content/public/browser/web_contents.h" +#include "third_party/blink/public/common/features.h" #include "third_party/widevine/cdm/buildflags.h" #if BUILDFLAG(ENABLE_GREASELION) @@ -131,6 +133,11 @@ void AttachTabHelpers(content::WebContents* web_contents) { FarblingTabHelper::CreateForWebContents(web_contents); brave_stats::BraveStatsTabHelper::CreateForWebContents(web_contents); + + if (base::FeatureList::IsEnabled(blink::features::kBraveEphemeralStorage)) { + ephemeral_storage::EphemeralStorageTabHelper::CreateForWebContents( + web_contents); + } } } // namespace brave diff --git a/browser/ephemeral_storage/BUILD.gn b/browser/ephemeral_storage/BUILD.gn new file mode 100644 index 000000000000..9c36f970dea5 --- /dev/null +++ b/browser/ephemeral_storage/BUILD.gn @@ -0,0 +1,35 @@ +source_set("ephemeral_storage") { + sources = [ + "ephemeral_storage_tab_helper.cc", + "ephemeral_storage_tab_helper.h", + ] + + deps = [ + "//base", + "//chrome/browser/ui", + "//content/public/browser", + "//net", + "//third_party/blink/public/common", + ] +} + +if (!is_android) { + source_set("ephemeral_storage_tests") { + testonly = true + sources = [ "ephemeral_storage_browsertest.cc" ] + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + deps = [ + "//base", + "//brave/components/brave_shields/browser:browser", + "//brave/components/brave_shields/common:common", + "//chrome/browser", + "//chrome/browser/ui", + "//chrome/test:test_support_ui", + "//content/public/common", + "//content/test:test_support", + "//services/network:test_support", + "//third_party/blink/public/common", + "//url", + ] + } +} diff --git a/browser/ephemeral_storage/ephemeral_storage_browsertest.cc b/browser/ephemeral_storage/ephemeral_storage_browsertest.cc new file mode 100644 index 000000000000..02b16539f3f7 --- /dev/null +++ b/browser/ephemeral_storage/ephemeral_storage_browsertest.cc @@ -0,0 +1,302 @@ +/* Copyright (c) 2020 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +#include "base/path_service.h" +#include "brave/common/brave_paths.h" +#include "brave/components/brave_shields/browser/brave_shields_util.h" +#include "brave/components/brave_shields/common/brave_shield_constants.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.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 "components/network_session_configurator/common/network_switches.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/test_navigation_observer.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/default_handlers.h" +#include "third_party/blink/public/common/features.h" +#include "url/gurl.h" + +using content::RenderFrameHost; +using content::WebContents; +using net::test_server::EmbeddedTestServer; + +namespace { + +enum StorageType { Session, Local }; + +const char* ToString(StorageType storage_type) { + switch (storage_type) { + case StorageType::Session: + return "session"; + case StorageType::Local: + return "local"; + } +} + +void SetStorageValueInFrame(RenderFrameHost* host, + std::string value, + StorageType storage_type) { + std::string script = + base::StringPrintf("%sStorage.setItem('storage_key', '%s');", + ToString(storage_type), value.c_str()); + ASSERT_TRUE(content::ExecuteScript(host, script)); +} + +content::EvalJsResult GetStorageValueInFrame(RenderFrameHost* host, + StorageType storage_type) { + std::string script = base::StringPrintf("%sStorage.getItem('storage_key');", + ToString(storage_type)); + return content::EvalJs(host, script); +} + +} // namespace + +class EphemeralStorageBrowserTest : public InProcessBrowserTest { + public: + EphemeralStorageBrowserTest() + : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { + scoped_feature_list_.InitAndEnableFeature( + blink::features::kBraveEphemeralStorage); + } + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + + host_resolver()->AddRule("*", "127.0.0.1"); + + brave::RegisterPathProvider(); + base::FilePath test_data_dir; + base::PathService::Get(brave::DIR_TEST_DATA, &test_data_dir); + + https_server_.ServeFilesFromDirectory(test_data_dir); + https_server_.AddDefaultHandlers(GetChromeTestDataDir()); + content::SetupCrossSiteRedirector(&https_server_); + + ASSERT_TRUE(https_server_.Start()); + a_site_ephemeral_storage_url_ = + https_server_.GetURL("a.com", "/ephemeral_storage.html"); + b_site_ephemeral_storage_url_ = + https_server_.GetURL("b.com", "/ephemeral_storage.html"); + c_site_ephemeral_storage_url_ = + https_server_.GetURL("c.com", "/ephemeral_storage.html"); + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + InProcessBrowserTest::SetUpCommandLine(command_line); + + // This is needed to load pages from "domain.com" without an interstitial. + command_line->AppendSwitch(switches::kIgnoreCertificateErrors); + } + + void AllowAllCookies() { + auto* content_settings = + HostContentSettingsMapFactory::GetForProfile(browser()->profile()); + brave_shields::SetCookieControlType( + content_settings, brave_shields::ControlType::ALLOW, GURL()); + } + + void SetValuesInFrames(WebContents* web_contents, + std::string storage_value, + std::string cookie_value) { + auto set_values_in_frame = [&](RenderFrameHost* frame) { + SetStorageValueInFrame(frame, storage_value, StorageType::Local); + SetStorageValueInFrame(frame, storage_value, StorageType::Session); + }; + + RenderFrameHost* main_frame = web_contents->GetMainFrame(); + set_values_in_frame(main_frame); + set_values_in_frame(content::ChildFrameAt(main_frame, 0)); + set_values_in_frame(content::ChildFrameAt(main_frame, 1)); + } + + struct ValuesFromFrame { + content::EvalJsResult local_storage; + content::EvalJsResult session_storage; + }; + + ValuesFromFrame GetValuesFromFrame(RenderFrameHost* frame) { + return { + GetStorageValueInFrame(frame, StorageType::Local), + GetStorageValueInFrame(frame, StorageType::Session), + }; + } + + struct ValuesFromFrames { + ValuesFromFrame main_frame; + ValuesFromFrame iframe_1; + ValuesFromFrame iframe_2; + }; + + ValuesFromFrames GetValuesFromFrames(WebContents* web_contents) { + RenderFrameHost* main_frame = web_contents->GetMainFrame(); + return { + GetValuesFromFrame(main_frame), + GetValuesFromFrame(content::ChildFrameAt(main_frame, 0)), + GetValuesFromFrame(content::ChildFrameAt(main_frame, 1)), + }; + } + + WebContents* LoadURLInNewTab(GURL url) { + ui_test_utils::AllBrowserTabAddedWaiter add_tab; + ui_test_utils::NavigateToURLWithDisposition( + browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + return add_tab.Wait(); + } + + protected: + net::test_server::EmbeddedTestServer https_server_; + base::test::ScopedFeatureList scoped_feature_list_; + GURL a_site_ephemeral_storage_url_; + GURL b_site_ephemeral_storage_url_; + GURL c_site_ephemeral_storage_url_; + + private: + DISALLOW_COPY_AND_ASSIGN(EphemeralStorageBrowserTest); +}; + +IN_PROC_BROWSER_TEST_F(EphemeralStorageBrowserTest, StorageIsPartitioned) { + AllowAllCookies(); + + WebContents* first_party_tab = LoadURLInNewTab(b_site_ephemeral_storage_url_); + WebContents* site_a_tab1 = LoadURLInNewTab(a_site_ephemeral_storage_url_); + WebContents* site_a_tab2 = LoadURLInNewTab(a_site_ephemeral_storage_url_); + WebContents* site_c_tab = LoadURLInNewTab(c_site_ephemeral_storage_url_); + + EXPECT_EQ(browser()->tab_strip_model()->count(), 5); + + // We set a value in the page where all the frames are first-party. + SetValuesInFrames(first_party_tab, "b.com - first party", "from=b.com"); + + // The page this tab is loaded via a.com and has two b.com third-party + // iframes. The third-party iframes should have ephemeral storage. That means + // that their values should be shared by third-party b.com iframes loaded from + // a.com. + SetValuesInFrames(site_a_tab1, "a.com", "from=a.com"); + ValuesFromFrames site_a_tab1_values = GetValuesFromFrames(site_a_tab1); + EXPECT_EQ("a.com", site_a_tab1_values.main_frame.local_storage); + EXPECT_EQ("a.com", site_a_tab1_values.iframe_1.local_storage); + EXPECT_EQ("a.com", site_a_tab1_values.iframe_2.local_storage); + + EXPECT_EQ("a.com", site_a_tab1_values.main_frame.session_storage); + EXPECT_EQ("a.com", site_a_tab1_values.iframe_1.session_storage); + EXPECT_EQ("a.com", site_a_tab1_values.iframe_2.session_storage); + + // The second tab is loaded on the same domain, so should see the same + // storage for the third-party iframes. + ValuesFromFrames site_a_tab2_values = GetValuesFromFrames(site_a_tab2); + EXPECT_EQ("a.com", site_a_tab2_values.main_frame.local_storage); + EXPECT_EQ("a.com", site_a_tab2_values.iframe_1.local_storage); + EXPECT_EQ("a.com", site_a_tab2_values.iframe_2.local_storage); + + EXPECT_EQ(nullptr, site_a_tab2_values.main_frame.session_storage); + EXPECT_EQ(nullptr, site_a_tab2_values.iframe_1.session_storage); + EXPECT_EQ(nullptr, site_a_tab2_values.iframe_2.session_storage); + + // The storage in the first-party iframes should still reflect the + // original value that was written in the non-ephemeral storage area. + ValuesFromFrames first_party_values = GetValuesFromFrames(first_party_tab); + EXPECT_EQ("b.com - first party", first_party_values.main_frame.local_storage); + EXPECT_EQ("b.com - first party", first_party_values.iframe_1.local_storage); + EXPECT_EQ("b.com - first party", first_party_values.iframe_2.local_storage); + + EXPECT_EQ("b.com - first party", + first_party_values.main_frame.session_storage); + EXPECT_EQ("b.com - first party", first_party_values.iframe_1.session_storage); + EXPECT_EQ("b.com - first party", first_party_values.iframe_2.session_storage); + + // Even though this page loads b.com iframes as third-party iframes, the TLD + // differs, so it should get an entirely different ephemeral storage area. + ValuesFromFrames site_c_tab_values = GetValuesFromFrames(site_c_tab); + EXPECT_EQ(nullptr, site_c_tab_values.main_frame.local_storage); + EXPECT_EQ(nullptr, site_c_tab_values.iframe_1.local_storage); + EXPECT_EQ(nullptr, site_c_tab_values.iframe_2.local_storage); + + EXPECT_EQ(nullptr, site_c_tab_values.main_frame.session_storage); + EXPECT_EQ(nullptr, site_c_tab_values.iframe_1.session_storage); + EXPECT_EQ(nullptr, site_c_tab_values.iframe_2.session_storage); +} + +IN_PROC_BROWSER_TEST_F(EphemeralStorageBrowserTest, + NavigatingClearsEphemeralStorage) { + AllowAllCookies(); + + ui_test_utils::NavigateToURL(browser(), a_site_ephemeral_storage_url_); + auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); + + SetValuesInFrames(web_contents, "a.com value", "from=a.com"); + + ValuesFromFrames values_before = GetValuesFromFrames(web_contents); + EXPECT_EQ("a.com value", values_before.main_frame.local_storage); + EXPECT_EQ("a.com value", values_before.iframe_1.local_storage); + EXPECT_EQ("a.com value", values_before.iframe_2.local_storage); + + EXPECT_EQ("a.com value", values_before.main_frame.session_storage); + EXPECT_EQ("a.com value", values_before.iframe_1.session_storage); + EXPECT_EQ("a.com value", values_before.iframe_2.session_storage); + + // Navigate away and then navigate back to the original site. + ui_test_utils::NavigateToURL(browser(), b_site_ephemeral_storage_url_); + ui_test_utils::NavigateToURL(browser(), a_site_ephemeral_storage_url_); + + ValuesFromFrames values_after = GetValuesFromFrames(web_contents); + EXPECT_EQ("a.com value", values_after.main_frame.local_storage); + EXPECT_EQ(nullptr, values_after.iframe_1.local_storage); + EXPECT_EQ(nullptr, values_after.iframe_2.local_storage); + + EXPECT_EQ("a.com value", values_after.main_frame.session_storage); + EXPECT_EQ(nullptr, values_after.iframe_1.session_storage); + EXPECT_EQ(nullptr, values_after.iframe_2.session_storage); +} + +IN_PROC_BROWSER_TEST_F(EphemeralStorageBrowserTest, + ClosingTabClearsEphemeralStorage) { + AllowAllCookies(); + + WebContents* site_a_tab = LoadURLInNewTab(a_site_ephemeral_storage_url_); + EXPECT_EQ(browser()->tab_strip_model()->count(), 2); + + SetValuesInFrames(site_a_tab, "a.com value", "from=a.com"); + + ValuesFromFrames values_before = GetValuesFromFrames(site_a_tab); + EXPECT_EQ("a.com value", values_before.main_frame.local_storage); + EXPECT_EQ("a.com value", values_before.iframe_1.local_storage); + EXPECT_EQ("a.com value", values_before.iframe_2.local_storage); + + EXPECT_EQ("a.com value", values_before.main_frame.session_storage); + EXPECT_EQ("a.com value", values_before.iframe_1.session_storage); + EXPECT_EQ("a.com value", values_before.iframe_2.session_storage); + + // Close the new tab which we set ephemeral storage value in. This should + // clear the ephemeral storage since this is the last tab which has a.com as + // an eTLD. + int tab_index = + browser()->tab_strip_model()->GetIndexOfWebContents(site_a_tab); + bool was_closed = browser()->tab_strip_model()->CloseWebContentsAt( + tab_index, TabStripModel::CloseTypes::CLOSE_NONE); + EXPECT_TRUE(was_closed); + + // Navigate the main tab to the same site. + ui_test_utils::NavigateToURL(browser(), a_site_ephemeral_storage_url_); + auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); + + // Closing the tab earlier should have cleared the ephemeral storage area. + ValuesFromFrames values_after = GetValuesFromFrames(web_contents); + EXPECT_EQ("a.com value", values_after.main_frame.local_storage); + EXPECT_EQ(nullptr, values_after.iframe_1.local_storage); + EXPECT_EQ(nullptr, values_after.iframe_2.local_storage); + + EXPECT_EQ(nullptr, values_after.main_frame.session_storage); + EXPECT_EQ(nullptr, values_after.iframe_1.session_storage); + EXPECT_EQ(nullptr, values_after.iframe_2.session_storage); +} diff --git a/browser/ephemeral_storage/ephemeral_storage_tab_helper.cc b/browser/ephemeral_storage/ephemeral_storage_tab_helper.cc new file mode 100644 index 000000000000..a25cff6414e3 --- /dev/null +++ b/browser/ephemeral_storage/ephemeral_storage_tab_helper.cc @@ -0,0 +1,122 @@ +/* Copyright (c) 2020 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ephemeral_storage/ephemeral_storage_tab_helper.h" + +#include +#include + +#include "base/feature_list.h" +#include "base/hash/md5.h" +#include "base/no_destructor.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/session_storage_namespace.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" +#include "third_party/blink/public/common/features.h" + +using content::BrowserContext; +using content::NavigationHandle; +using content::SessionStorageNamespace; +using content::WebContents; + +namespace ephemeral_storage { + +namespace { + +std::string URLToStorageDomain(const GURL& url) { + std::string domain = net::registry_controlled_domains::GetDomainAndRegistry( + url, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); + + // GetDomainAndRegistry might return an empty string if this host is an IP + // address or a file URL. + if (domain.empty()) + domain = url::Origin::Create(url.GetOrigin()).Serialize(); + + return domain; +} + +// Session storage ids are expected to be 36 character long GUID strings. Since +// we are constructing our own ids, we convert our string into a 32 character +// hash and then use that make up our own GUID-like string. Because of the way +// we are constructing the string we should never collide with a real GUID and +// we only need to worry about hash collisions, which are unlikely. +std::string StringToSessionStorageId(const std::string& string, + const std::string& suffix) { + std::string hash = base::MD5String(string + suffix) + "____"; + DCHECK_EQ(hash.size(), 36u); + return hash; +} + +} // namespace + +EphemeralStorageTabHelper::EphemeralStorageTabHelper(WebContents* web_contents) + : WebContentsObserver(web_contents) { + DCHECK(base::FeatureList::IsEnabled(blink::features::kBraveEphemeralStorage)); + + // The URL might not be empty if this is a restored WebContents, for instance. + // In that case we want to make sure it has valid ephemeral storage. + const GURL& url = web_contents->GetLastCommittedURL(); + if (!url.is_empty()) + CreateEphemeralStorageAreasForDomainAndURL(URLToStorageDomain(url), url); +} + +EphemeralStorageTabHelper::~EphemeralStorageTabHelper() {} + +void EphemeralStorageTabHelper::ReadyToCommitNavigation( + NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInMainFrame()) + return; + if (navigation_handle->IsSameDocument()) + return; + + const GURL& new_url = navigation_handle->GetURL(); + std::string new_domain = URLToStorageDomain(new_url); + std::string previous_domain = + URLToStorageDomain(web_contents()->GetLastCommittedURL()); + if (new_domain == previous_domain) + return; + + CreateEphemeralStorageAreasForDomainAndURL(new_domain, new_url); +} + +void EphemeralStorageTabHelper::CreateEphemeralStorageAreasForDomainAndURL( + std::string new_domain, + const GURL& new_url) { + auto* browser_context = web_contents()->GetBrowserContext(); + auto site_instance = + content::SiteInstance::CreateForURL(browser_context, new_url); + auto* partition = + BrowserContext::GetStoragePartition(browser_context, site_instance.get()); + + // This will fetch a session storage namespace for this storage partition + // and storage domain. If another tab helper is already using the same + // namespace, this will just give us a new reference. When the last tab helper + // drops the reference, the namespace should be deleted. + std::string local_partition_id = + StringToSessionStorageId(new_domain, "/ephemeral-local-storage"); + local_storage_namespace_ = + content::CreateSessionStorageNamespace(partition, local_partition_id); + + // Session storage is always per-tab and never per-TLD, so we always delete + // and recreate the session storage when switching domains. + // + // We need to explicitly release the storage namespace before recreating a + // new one in order to make sure that we remove the final reference and free + // it. + session_storage_namespace_.reset(); + + std::string session_partition_id = StringToSessionStorageId( + content::GetSessionStorageNamespaceId(web_contents()), + "/ephemeral-session-storage"); + session_storage_namespace_ = + content::CreateSessionStorageNamespace(partition, session_partition_id); +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(EphemeralStorageTabHelper) + +} // namespace ephemeral_storage diff --git a/browser/ephemeral_storage/ephemeral_storage_tab_helper.h b/browser/ephemeral_storage/ephemeral_storage_tab_helper.h new file mode 100644 index 000000000000..b694254ce5e4 --- /dev/null +++ b/browser/ephemeral_storage/ephemeral_storage_tab_helper.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2020 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_EPHEMERAL_STORAGE_EPHEMERAL_STORAGE_TAB_HELPER_H_ +#define BRAVE_BROWSER_EPHEMERAL_STORAGE_EPHEMERAL_STORAGE_TAB_HELPER_H_ + +#include +#include + +#include "content/public/browser/session_storage_namespace.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace content { +class WebContents; +class BrowserContext; +} // namespace content + +namespace ephemeral_storage { + +// The EphemeralStorageTabHelper manages ephemeral storage for a WebContents. +// Ephemeral storage is a partitioned storage area only used by third-party +// iframes. This storage is partitioned based on the origin of the TLD +// of the main frame. When no more tabs are open with a particular origin, +// this storage is cleared. +class EphemeralStorageTabHelper + : public content::WebContentsObserver, + public content::WebContentsUserData { + public: + explicit EphemeralStorageTabHelper(content::WebContents* web_contents); + ~EphemeralStorageTabHelper() override; + + protected: + void ReadyToCommitNavigation( + content::NavigationHandle* navigation_handle) override; + + private: + void CreateEphemeralStorageAreasForDomainAndURL(std::string new_domain, + const GURL& new_url); + + friend class content::WebContentsUserData; + scoped_refptr local_storage_namespace_; + scoped_refptr session_storage_namespace_; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +} // namespace ephemeral_storage + +#endif // BRAVE_BROWSER_EPHEMERAL_STORAGE_EPHEMERAL_STORAGE_TAB_HELPER_H_ diff --git a/chromium_src/chrome/browser/about_flags.cc b/chromium_src/chrome/browser/about_flags.cc index 1e5355bfa7b2..5d9ceb8091b4 100644 --- a/chromium_src/chrome/browser/about_flags.cc +++ b/chromium_src/chrome/browser/about_flags.cc @@ -17,6 +17,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "components/prefs/pref_service.h" +#include "third_party/blink/public/common/features.h" using brave_shields::features::kBraveAdblockCosmeticFiltering; using ntp_background_images::features::kBraveNTPBrandedWallpaper; @@ -84,7 +85,11 @@ using ntp_background_images::features::kBraveNTPSuperReferralWallpaper; flag_descriptions::kBraveSuperReferralName, \ flag_descriptions::kBraveSuperReferralDescription, \ flags_ui::kOsMac | flags_ui::kOsWin | flags_ui::kOsAndroid, \ - FEATURE_VALUE_TYPE(kBraveNTPSuperReferralWallpaper)}, + FEATURE_VALUE_TYPE(kBraveNTPSuperReferralWallpaper)}, \ + {"brave-ephemeral-storage", \ + flag_descriptions::kBraveEphemeralStorageName, \ + flag_descriptions::kBraveEphemeralStorageDescription, kOsAll, \ + FEATURE_VALUE_TYPE(blink::features::kBraveEphemeralStorage)}, #define SetFeatureEntryEnabled SetFeatureEntryEnabled_ChromiumImpl #include "../../../../chrome/browser/about_flags.cc" // NOLINT diff --git a/chromium_src/chrome/browser/flag_descriptions.cc b/chromium_src/chrome/browser/flag_descriptions.cc index 617830272f3e..48d8d29f0c3d 100644 --- a/chromium_src/chrome/browser/flag_descriptions.cc +++ b/chromium_src/chrome/browser/flag_descriptions.cc @@ -37,4 +37,7 @@ const char kBraveIpfsDescription[] = const char kBraveSuperReferralName[] = "Enable Brave Super Referral"; const char kBraveSuperReferralDescription[] = "Use custom theme for Brave Super Referral"; +const char kBraveEphemeralStorageName[] = "Enable Ephemeral Storage"; +const char kBraveEphemeralStorageDescription[] = + "Use ephemeral storage for third-party frames"; } // namespace flag_descriptions diff --git a/chromium_src/chrome/browser/flag_descriptions.h b/chromium_src/chrome/browser/flag_descriptions.h index 51258c20b6ce..0dadf94b4907 100644 --- a/chromium_src/chrome/browser/flag_descriptions.h +++ b/chromium_src/chrome/browser/flag_descriptions.h @@ -27,6 +27,8 @@ extern const char kBraveIpfsName[]; extern const char kBraveIpfsDescription[]; extern const char kBraveSuperReferralName[]; extern const char kBraveSuperReferralDescription[]; +extern const char kBraveEphemeralStorageName[]; +extern const char kBraveEphemeralStorageDescription[]; } // namespace flag_descriptions #endif // BRAVE_CHROMIUM_SRC_CHROME_BROWSER_FLAG_DESCRIPTIONS_H_ diff --git a/chromium_src/components/content_settings/core/common/cookie_settings_base.cc b/chromium_src/components/content_settings/core/common/cookie_settings_base.cc index ea1ad0f37d7c..2a65049633a9 100644 --- a/chromium_src/components/content_settings/core/common/cookie_settings_base.cc +++ b/chromium_src/components/content_settings/core/common/cookie_settings_base.cc @@ -5,11 +5,13 @@ #include "components/content_settings/core/common/cookie_settings_base.h" +#include "base/feature_list.h" #include "base/no_destructor.h" #include "base/optional.h" -#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "components/content_settings/core/common/content_settings.h" #include "components/content_settings/core/common/content_settings_pattern.h" +#include "components/content_settings/core/common/features.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "url/gurl.h" #include "url/origin.h" diff --git a/chromium_src/content/browser/browser_context.cc b/chromium_src/content/browser/browser_context.cc new file mode 100644 index 000000000000..fd0d77e18203 --- /dev/null +++ b/chromium_src/content/browser/browser_context.cc @@ -0,0 +1,41 @@ +/* Copyright (c) 2020 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "content/public/browser/browser_context.h" + +#include + +#include "base/memory/ref_counted.h" +#include "content/browser/dom_storage/dom_storage_context_wrapper.h" +#include "content/browser/dom_storage/session_storage_namespace_impl.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/session_storage_namespace.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" + +namespace content { + +scoped_refptr CreateSessionStorageNamespace( + content::StoragePartition* partition, + const std::string& namespace_id) { + content::DOMStorageContextWrapper* context_wrapper = + static_cast( + partition->GetDOMStorageContext()); + + return content::SessionStorageNamespaceImpl::Create(context_wrapper, + namespace_id); +} + +std::string GetSessionStorageNamespaceId(WebContents* web_contents) { + return web_contents->GetRenderViewHost() + ->GetDelegate() + ->GetSessionStorageNamespace(web_contents->GetSiteInstance()) + ->id(); +} + +} // namespace content + +#include "../../../../content/browser/browser_context.cc" diff --git a/chromium_src/content/public/browser/browser_context.h b/chromium_src/content/public/browser/browser_context.h new file mode 100644 index 000000000000..535f8d529969 --- /dev/null +++ b/chromium_src/content/public/browser/browser_context.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2020 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_CONTENT_PUBLIC_BROWSER_BROWSER_CONTEXT_H_ +#define BRAVE_CHROMIUM_SRC_CONTENT_PUBLIC_BROWSER_BROWSER_CONTEXT_H_ + +#include "../../../../../content/public/browser/browser_context.h" + +#include + +#include "base/memory/ref_counted.h" +#include "content/common/content_export.h" + +namespace content { + +class WebContents; +class SessionStorageNamespace; +class StoragePartition; + +CONTENT_EXPORT scoped_refptr +CreateSessionStorageNamespace(content::StoragePartition* partition, + const std::string& namespace_id); + +CONTENT_EXPORT std::string GetSessionStorageNamespaceId(WebContents*); + +} // namespace content + +#endif // BRAVE_CHROMIUM_SRC_CONTENT_PUBLIC_BROWSER_BROWSER_CONTEXT_H_ diff --git a/chromium_src/third_party/blink/common/features.cc b/chromium_src/third_party/blink/common/features.cc new file mode 100644 index 000000000000..f7dad536f8ee --- /dev/null +++ b/chromium_src/third_party/blink/common/features.cc @@ -0,0 +1,13 @@ +/* Copyright (c) 2020 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "../../../../../../../third_party/blink/common/features.cc" + +namespace blink { +namespace features { +const base::Feature kBraveEphemeralStorage{"EphemeralStorage", + base::FEATURE_DISABLED_BY_DEFAULT}; +} // namespace features +} // namespace blink diff --git a/chromium_src/third_party/blink/public/common/features.h b/chromium_src/third_party/blink/public/common/features.h new file mode 100644 index 000000000000..4c19a76945c4 --- /dev/null +++ b/chromium_src/third_party/blink/public/common/features.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2020 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_PUBLIC_COMMON_FEATURES_H_ +#define BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_PUBLIC_COMMON_FEATURES_H_ + +#include "base/feature_list.h" +#include "third_party/blink/public/common/common_export.h" + +namespace blink { +namespace features { + +BLINK_COMMON_EXPORT extern const base::Feature kBraveEphemeralStorage; + +} // namespace features +} // namespace blink + +#include "../../../../../../third_party/blink/public/common/features.h" + +#undef BRAVE_FEATURES_H + +#endif // BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_PUBLIC_COMMON_FEATURES_H_ diff --git a/chromium_src/third_party/blink/renderer/modules/storage/brave_dom_window_storage.h b/chromium_src/third_party/blink/renderer/modules/storage/brave_dom_window_storage.h index ac794e391360..8dd246028b7a 100644 --- a/chromium_src/third_party/blink/renderer/modules/storage/brave_dom_window_storage.h +++ b/chromium_src/third_party/blink/renderer/modules/storage/brave_dom_window_storage.h @@ -6,20 +6,37 @@ #ifndef BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_MODULES_STORAGE_BRAVE_DOM_WINDOW_STORAGE_H_ #define BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_MODULES_STORAGE_BRAVE_DOM_WINDOW_STORAGE_H_ +#include "third_party/blink/renderer/platform/supplementable.h" + namespace blink { class ExceptionState; class LocalDOMWindow; class StorageArea; -class BraveDOMWindowStorage { +class BraveDOMWindowStorage final + : public GarbageCollected, + public Supplement { public: + static const char kSupplementName[]; + + static BraveDOMWindowStorage& From(LocalDOMWindow&); static StorageArea* sessionStorage(LocalDOMWindow&, ExceptionState&); static StorageArea* localStorage(LocalDOMWindow&, ExceptionState&); + StorageArea* sessionStorage(ExceptionState&); + StorageArea* localStorage(ExceptionState&); + + explicit BraveDOMWindowStorage(LocalDOMWindow&); + + void Trace(Visitor*) const override; + private: - BraveDOMWindowStorage(); - ~BraveDOMWindowStorage(); + StorageArea* ephemeralSessionStorage(); + StorageArea* ephemeralLocalStorage(); + + mutable Member ephemeral_session_storage_; + mutable Member ephemeral_local_storage_; }; } // namespace blink diff --git a/chromium_src/third_party/blink/renderer/modules/storage/dom_window_storage.cc b/chromium_src/third_party/blink/renderer/modules/storage/dom_window_storage.cc index 485b313c9ee3..a45aed40b3aa 100644 --- a/chromium_src/third_party/blink/renderer/modules/storage/dom_window_storage.cc +++ b/chromium_src/third_party/blink/renderer/modules/storage/dom_window_storage.cc @@ -4,14 +4,29 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "../../../../../../../third_party/blink/renderer/modules/storage/dom_window_storage.cc" -#include "third_party/blink/renderer/modules/storage/brave_dom_window_storage.h" +#include "third_party/blink/public/common/dom_storage/session_storage_namespace_id.h" +#include "third_party/blink/public/common/features.h" +#include "third_party/blink/public/web/web_view_client.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/exported/web_view_impl.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/modules/storage/brave_dom_window_storage.h" namespace blink { namespace { +// This replicates the conversion of a string into a session storage namespace +// id that is found in the implementation of EphemeralStorageTabHelper. +String StringToSessionStorageId(const String& string, + const std::string& suffix) { + std::string hash = + base::MD5String(std::string(string.Utf8()) + suffix) + "____"; + DCHECK_EQ(hash.length(), kSessionStorageNamespaceIdLength); + return String(hash.c_str()); +} + // If storage is null and there was an exception then clear the exception unless // it was caused by CanAccessSessionStorage for the document security origin // (sandbox, data urls, etc...) @@ -19,9 +34,7 @@ void MaybeClearAccessDeniedException(StorageArea* storage, const LocalDOMWindow& window, ExceptionState* exception_state) { if (!storage && exception_state->HadException()) { - LocalDOMWindow* dom_window = window.GetFrame()->DomWindow(); - - if (!dom_window->GetSecurityOrigin()->CanAccessSessionStorage()) + if (!window.GetSecurityOrigin()->CanAccessSessionStorage()) return; // clear the access denied exception for better webcompat @@ -31,25 +44,213 @@ void MaybeClearAccessDeniedException(StorageArea* storage, } // namespace -BraveDOMWindowStorage::BraveDOMWindowStorage() {} -BraveDOMWindowStorage::~BraveDOMWindowStorage() {} +// EphemeralStorageNamespace manage ephemeral storage namespaces for a +// particular Page object. The namespaces are instantiated on the Page lazily, +// as soon as a third-party frame needs ephemeral storage. They are then shared +// by all third-party frames that are embedded in this Page. +// +// These namespaces are created in the browser process ahead of time. We ensure +// that we are using the same namespace by using a common naming scheme. +class EphemeralStorageNamespaces + : public GarbageCollected, + public Supplement { + public: + EphemeralStorageNamespaces(StorageController* controller, + const String& session_storage_id, + const String& local_storage_id); + virtual ~EphemeralStorageNamespaces() = default; + + static const char kSupplementName[]; + static EphemeralStorageNamespaces* From(Page* page, LocalDOMWindow* window); + + StorageNamespace* session_storage() { return session_storage_.Get(); } + StorageNamespace* local_storage() { return local_storage_.Get(); } + void Trace(Visitor* visitor) const override; + + private: + Member session_storage_; + Member local_storage_; +}; + +const char EphemeralStorageNamespaces::kSupplementName[] = + "EphemeralStorageNamespaces"; + +EphemeralStorageNamespaces::EphemeralStorageNamespaces( + StorageController* controller, + const String& session_storage_id, + const String& local_storage_id) + : session_storage_( + MakeGarbageCollected(controller, + session_storage_id)), + local_storage_(MakeGarbageCollected(controller, + local_storage_id)) { +} + +void EphemeralStorageNamespaces::Trace(Visitor* visitor) const { + visitor->Trace(session_storage_); + visitor->Trace(local_storage_); + Supplement::Trace(visitor); +} // static -StorageArea* BraveDOMWindowStorage::sessionStorage(LocalDOMWindow& window, - ExceptionState& exception_state) { - auto* storage = - DOMWindowStorage::From(window).sessionStorage(exception_state); - MaybeClearAccessDeniedException(storage, window, &exception_state); - return storage; +EphemeralStorageNamespaces* EphemeralStorageNamespaces::From( + Page* page, + LocalDOMWindow* window) { + DCHECK(window); + if (!page) + return nullptr; + + EphemeralStorageNamespaces* supplement = + Supplement::From(page); + if (supplement) + return supplement; + + auto* web_frame = WebLocalFrameImpl::FromFrame(window->GetFrame()); + WebViewImpl* webview = web_frame->ViewImpl(); + if (!webview || !webview->Client()) + return nullptr; + String session_storage_id = StringToSessionStorageId( + String::FromUTF8(webview->Client()->GetSessionStorageNamespaceId()), + "/ephemeral-session-storage"); + + auto* security_origin = + page->MainFrame()->GetSecurityContext()->GetSecurityOrigin(); + String domain = security_origin->RegistrableDomain(); + // RegistrableDomain might return an empty string if this host is an IP + // address or a file URL. + if (domain.IsEmpty()) { + url::Origin origin = url::Origin::Create(GURL(window->Url()).GetOrigin()); + domain = String::FromUTF8(origin.Serialize()); + } + + String local_storage_id = + StringToSessionStorageId(domain, "/ephemeral-local-storage"); + supplement = MakeGarbageCollected( + StorageController::GetInstance(), session_storage_id, local_storage_id); + + ProvideTo(*page, supplement); + return supplement; +} + +// static +const char BraveDOMWindowStorage::kSupplementName[] = "BraveDOMWindowStorage"; + +// static +BraveDOMWindowStorage& BraveDOMWindowStorage::From(LocalDOMWindow& window) { + BraveDOMWindowStorage* supplement = + Supplement::From(window); + if (!supplement) { + supplement = MakeGarbageCollected(window); + ProvideTo(window, supplement); + } + return *supplement; +} + +// static +StorageArea* BraveDOMWindowStorage::sessionStorage( + LocalDOMWindow& window, + ExceptionState& exception_state) { + return From(window).sessionStorage(exception_state); } // static -StorageArea* BraveDOMWindowStorage::localStorage(LocalDOMWindow& window, - ExceptionState& exception_state) { +StorageArea* BraveDOMWindowStorage::localStorage( + LocalDOMWindow& window, + ExceptionState& exception_state) { + return From(window).localStorage(exception_state); +} + +BraveDOMWindowStorage::BraveDOMWindowStorage(LocalDOMWindow& window) + : Supplement(window) {} + +StorageArea* BraveDOMWindowStorage::sessionStorage( + ExceptionState& exception_state) { + LocalDOMWindow* window = GetSupplementable(); auto* storage = - DOMWindowStorage::From(window).localStorage(exception_state); - MaybeClearAccessDeniedException(storage, window, &exception_state); - return storage; + DOMWindowStorage::From(*window).sessionStorage(exception_state); + + MaybeClearAccessDeniedException(storage, *window, &exception_state); + if (!base::FeatureList::IsEnabled(features::kBraveEphemeralStorage)) + return storage; + + if (!window->IsCrossSiteSubframe()) + return storage; + + // If we were not able to create non-ephemeral session storage for this + // window, then don't attempt to create an ephemeral version. + if (!storage) + return nullptr; + + return ephemeralSessionStorage(); +} + +StorageArea* BraveDOMWindowStorage::ephemeralSessionStorage() { + if (ephemeral_session_storage_) + return ephemeral_session_storage_; + + LocalDOMWindow* window = GetSupplementable(); + Page* page = window->GetFrame()->GetDocument()->GetPage(); + EphemeralStorageNamespaces* namespaces = + EphemeralStorageNamespaces::From(page, window); + if (!namespaces) + return nullptr; + + auto storage_area = + namespaces->session_storage()->GetCachedArea(window->GetSecurityOrigin()); + + ephemeral_session_storage_ = + StorageArea::Create(window->GetFrame(), std::move(storage_area), + StorageArea::StorageType::kSessionStorage); + return ephemeral_session_storage_; +} + +StorageArea* BraveDOMWindowStorage::localStorage( + ExceptionState& exception_state) { + LocalDOMWindow* window = GetSupplementable(); + auto* storage = DOMWindowStorage::From(*window).localStorage(exception_state); + + MaybeClearAccessDeniedException(storage, *window, &exception_state); + if (!base::FeatureList::IsEnabled(features::kBraveEphemeralStorage)) + return storage; + + if (!window->IsCrossSiteSubframe()) + return storage; + + // If we were not able to create non-ephemeral localStorage for this Window, + // then don't attempt to create an ephemeral version. + if (!storage) + return nullptr; + + return ephemeralLocalStorage(); +} + +StorageArea* BraveDOMWindowStorage::ephemeralLocalStorage() { + if (ephemeral_local_storage_) + return ephemeral_local_storage_; + + LocalDOMWindow* window = GetSupplementable(); + Page* page = window->GetFrame()->GetDocument()->GetPage(); + EphemeralStorageNamespaces* namespaces = + EphemeralStorageNamespaces::From(page, window); + if (!namespaces) + return nullptr; + + auto storage_area = + namespaces->local_storage()->GetCachedArea(window->GetSecurityOrigin()); + + // Ephemeral localStorage never persists stored data, which is also how + // sessionStorage works. Due to this, when opening up a new ephemeral + // localStorage area, we use the sessionStorage infrastructure. + ephemeral_local_storage_ = + StorageArea::Create(window->GetFrame(), std::move(storage_area), + StorageArea::StorageType::kSessionStorage); + return ephemeral_local_storage_; +} + +void BraveDOMWindowStorage::Trace(Visitor* visitor) const { + visitor->Trace(ephemeral_session_storage_); + visitor->Trace(ephemeral_local_storage_); + Supplement::Trace(visitor); } } // namespace blink diff --git a/test/BUILD.gn b/test/BUILD.gn index 30851349afce..c16a8155c716 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -865,6 +865,7 @@ if (!is_android) { "//brave/browser/browsing_data", "//brave/browser:browser_process", "//brave/browser/devtools", + "//brave/browser/ephemeral_storage:ephemeral_storage_tests", "//brave/browser/extensions", "//brave/browser/net", "//brave/browser/profiles", diff --git a/test/data/ephemeral_storage.html b/test/data/ephemeral_storage.html new file mode 100644 index 000000000000..b50fb59b2c37 --- /dev/null +++ b/test/data/ephemeral_storage.html @@ -0,0 +1,5 @@ +ephemeral storage test + + + +