diff options
author | nasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-08 08:31:53 +0000 |
---|---|---|
committer | nasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-08 08:31:53 +0000 |
commit | 1bc2831891813f9cf9191c7d5deeccf246eff672 (patch) | |
tree | f4eb830fe3b63f5a891e802cbfa74e6616a62ce6 /chrome | |
parent | eea2e67034a223c74ce0e8c0517f6066cc1ee143 (diff) | |
download | chromium_src-1bc2831891813f9cf9191c7d5deeccf246eff672.zip chromium_src-1bc2831891813f9cf9191c7d5deeccf246eff672.tar.gz chromium_src-1bc2831891813f9cf9191c7d5deeccf246eff672.tar.bz2 |
Webview tag creation should be using storage partitions.
BUG=145500,149726
Review URL: https://chromiumcodereview.appspot.com/11234032
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@166639 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
11 files changed, 518 insertions, 149 deletions
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 585a8a2..758eb51 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -103,6 +103,7 @@ #include "content/public/common/content_descriptors.h" #include "grit/generated_resources.h" #include "grit/ui_resources.h" +#include "net/base/escape.h" #include "net/base/ssl_cert_request_info.h" #include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_options.h" @@ -472,61 +473,79 @@ content::WebContentsView* return NULL; } -std::string ChromeContentBrowserClient::GetStoragePartitionIdForChildProcess( - content::BrowserContext* browser_context, - int child_process_id) { - const Extension* extension = NULL; - Profile* profile = Profile::FromBrowserContext(browser_context); - ExtensionService* extension_service = - extensions::ExtensionSystem::Get(profile)->extension_service(); - if (extension_service) { - std::set<std::string> extension_ids = - extension_service->process_map()-> - GetExtensionsInProcess(child_process_id); - if (!extension_ids.empty()) - // Since All the apps in a process share the same storage partition, - // we can pick any of them to retrieve the storage partition id. - extension = - extension_service->extensions()->GetByID(*(extension_ids.begin())); - } - return GetStoragePartitionIdForExtension(browser_context, extension); -} - std::string ChromeContentBrowserClient::GetStoragePartitionIdForSite( content::BrowserContext* browser_context, const GURL& site) { - const Extension* extension = NULL; - Profile* profile = Profile::FromBrowserContext(browser_context); - ExtensionService* extension_service = - extensions::ExtensionSystem::Get(profile)->extension_service(); - if (extension_service) { - extension = extension_service->extensions()-> - GetExtensionOrAppByURL(ExtensionURLInfo(site)); - } + std::string partition_id; + + // The partition ID for webview guest processes is the string value of its + // SiteInstance URL - "chrome-guest://app_id/persist?partition". + if (site.SchemeIs(chrome::kGuestScheme)) + partition_id = site.spec(); - return GetStoragePartitionIdForExtension(browser_context, extension); + DCHECK(IsValidStoragePartitionId(browser_context,partition_id)); + return partition_id; } bool ChromeContentBrowserClient::IsValidStoragePartitionId( content::BrowserContext* browser_context, const std::string& partition_id) { - // The default ID is empty which is always allowed. + // The default ID is empty and is always valid. if (partition_id.empty()) return true; - // If it isn't empty, then it must belong to an extension of some sort. Parse - // out the extension ID and make sure it is still installed. + return GURL(partition_id).is_valid(); +} + +void ChromeContentBrowserClient::GetStoragePartitionConfigForSite( + content::BrowserContext* browser_context, + const GURL& site, + std::string* partition_domain, + std::string* partition_name, + bool* in_memory) { + // For the webview tag, we create special guest processes, which host the + // tag content separately from the main application that embeds the tag. + // A webview tag can specify both the partition name and whether the storage + // for that partition should be persisted. Each tag gets a SiteInstance with + // a specially formatted URL, based on the application it is hosted by and + // the partition requested by it. The format for that URL is: + // chrome-guest://partition_domain/persist?partition_name + if (site.SchemeIs(chrome::kGuestScheme)) { + // Since guest URLs are only used for packaged apps, there must be an app + // id in the URL. + CHECK(site.has_host()); + *partition_domain = site.host(); + // Since persistence is optional, the path must either be empty or the + // literal string. + *in_memory = (site.path() != "/persist"); + // The partition name is user supplied value, which we have encoded when the + // URL was created, so it needs to be decoded. + *partition_name = net::UnescapeURLComponent(site.query(), + net::UnescapeRule::NORMAL); + return; + } + + const Extension* extension = NULL; Profile* profile = Profile::FromBrowserContext(browser_context); ExtensionService* extension_service = extensions::ExtensionSystem::Get(profile)->extension_service(); - if (!extension_service) { - // No extension service means no storage partitions in Chrome. - return false; + if (extension_service) { + extension = extension_service->extensions()-> + GetExtensionOrAppByURL(ExtensionURLInfo(site)); + if (extension && extension->is_storage_isolated()) { + // Extensions which have storage isolation enabled (e.g., apps), use + // the extension id as the |partition_domain|. + *partition_domain = extension->id(); + partition_name->clear(); + *in_memory = false; + return; + } } - // See if we can find an extension. The |partition_id| is the extension ID so - // no parsing needed to be done. - return extension_service->GetExtensionById(partition_id, false) != NULL; + // All other cases use the default, browser-wide, storage partition. + partition_domain->clear(); + partition_name->clear(); + *in_memory = false; } content::WebContentsViewDelegate* @@ -1611,10 +1630,16 @@ void ChromeContentBrowserClient::OverrideWebkitPrefs( chrome::ViewType view_type = chrome::GetViewType(web_contents); ExtensionService* service = profile->GetExtensionService(); if (service) { - const Extension* extension = service->extensions()->GetByID( - rvh->GetSiteInstance()->GetSiteURL().host()); - extension_webkit_preferences::SetPreferences( - extension, view_type, web_prefs); + const GURL& url = rvh->GetSiteInstance()->GetSiteURL(); + const Extension* extension = service->extensions()->GetByID(url.host()); + // Ensure that we are only granting extension preferences to URLs with + // the correct scheme. Without this check, chrome-guest:// schemes used by + // webview tags as well as hosts that happen to match the id of an + // installed extension would get the wrong preferences. + if (url.SchemeIs(chrome::kExtensionScheme)) { + extension_webkit_preferences::SetPreferences( + extension, view_type, web_prefs); + } } if (content::IsForceCompositingModeEnabled()) @@ -1892,23 +1917,4 @@ void ChromeContentBrowserClient::SetApplicationLocaleOnIOThread( io_thread_application_locale_ = locale; } -std::string ChromeContentBrowserClient::GetStoragePartitionIdForExtension( - content::BrowserContext* browser_context, const Extension* extension) { - // In chrome, we use the extension ID as the partition ID. This works well - // because the extension ID fits the partition ID pattern and currently only - // apps can designate that storage should be isolated. - // - // If |extension| is NULL, then the default, empty string, partition id is - // used. - std::string partition_id; - if (extension && extension->is_storage_isolated()) { - partition_id = extension->id(); - } - - // Enforce that IsValidStoragePartitionId() implementation stays in sync. - DCHECK(IsValidStoragePartitionId(browser_context, partition_id)); - return partition_id; -} - - } // namespace chrome diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h index 9a8cd77..78b741c 100644 --- a/chrome/browser/chrome_content_browser_client.h +++ b/chrome/browser/chrome_content_browser_client.h @@ -43,15 +43,18 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { content::WebContents* web_contents, content::RenderViewHostDelegateView** render_view_host_delegate_view) OVERRIDE; - virtual std::string GetStoragePartitionIdForChildProcess( - content::BrowserContext* browser_context, - int child_process_id) OVERRIDE; virtual std::string GetStoragePartitionIdForSite( content::BrowserContext* browser_context, const GURL& site) OVERRIDE; virtual bool IsValidStoragePartitionId( content::BrowserContext* browser_context, const std::string& partition_id) OVERRIDE; + virtual void GetStoragePartitionConfigForSite( + content::BrowserContext* browser_context, + const GURL& site, + std::string* partition_domain, + std::string* partition_name, + bool* in_memory) OVERRIDE; virtual content::WebContentsViewDelegate* GetWebContentsViewDelegate( content::WebContents* web_contents) OVERRIDE; virtual void RenderViewHostCreated( @@ -237,12 +240,6 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { // Sets io_thread_application_locale_ to the given value. void SetApplicationLocaleOnIOThread(const std::string& locale); - // Helper function for getting the storage partition id from an Extension - // object. - std::string GetStoragePartitionIdForExtension( - content::BrowserContext* browser_context, - const extensions::Extension* extension); - #if defined(OS_ANDROID) void InitCrashDumpManager(); diff --git a/chrome/browser/extensions/isolated_app_browsertest.cc b/chrome/browser/extensions/isolated_app_browsertest.cc index 266d5a4..5d42e9f 100644 --- a/chrome/browser/extensions/isolated_app_browsertest.cc +++ b/chrome/browser/extensions/isolated_app_browsertest.cc @@ -367,7 +367,12 @@ IN_PROC_BROWSER_TEST_F(IsolatedAppTest, IsolatedAppProcessModel) { chrome::GetWebContentsAt(browser(), 1)->GetRenderProcessHost()->GetID()); } -IN_PROC_BROWSER_TEST_F(IsolatedAppTest, SessionStorage) { +// This test no longer passes, since we don't properly isolate sessionStorage +// for isolated apps. This was broken as part of the changes for storage +// partition support for webview tags. +// TODO(nasko): If isolated apps is no longer developed, this test should be +// removed. http://crbug.com/159932 +IN_PROC_BROWSER_TEST_F(IsolatedAppTest, DISABLED_SessionStorage) { host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(test_server()->Start()); diff --git a/chrome/browser/extensions/web_view_browsertest.cc b/chrome/browser/extensions/web_view_browsertest.cc index d8ac13e..759017b 100644 --- a/chrome/browser/extensions/web_view_browsertest.cc +++ b/chrome/browser/extensions/web_view_browsertest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/utf_string_conversions.h" #include "chrome/browser/automation/automation_util.h" #include "chrome/browser/extensions/platform_app_browsertest_util.h" #include "chrome/browser/ui/browser_tabstrip.h" @@ -24,6 +25,107 @@ class WebViewTest : public extensions::PlatformAppBrowserTest { #endif ui::DisableTestCompositor(); } + + // This method is responsible for initializing a packaged app, which contains + // multiple webview tags. The tags have different partition identifiers and + // their WebContent objects are returned as output. The method also verifies + // the expected process allocation and storage partition assignment. + // The |navigate_to_url| parameter is used to navigate the main browser + // window. + void NavigateAndOpenAppForIsolation( + GURL navigate_to_url, + content::WebContents** default_tag_contents1, + content::WebContents** default_tag_contents2, + content::WebContents** named_partition_contents1, + content::WebContents** named_partition_contents2) { + GURL::Replacements replace_host; + std::string host_str("localhost"); // Must stay in scope with replace_host. + replace_host.SetHostStr(host_str); + + navigate_to_url = navigate_to_url.ReplaceComponents(replace_host); + + GURL tag_url1 = test_server()->GetURL( + "files/extensions/platform_apps/web_view_isolation/cookie.html"); + tag_url1 = tag_url1.ReplaceComponents(replace_host); + GURL tag_url2 = test_server()->GetURL( + "files/extensions/platform_apps/web_view_isolation/cookie2.html"); + tag_url2 = tag_url2.ReplaceComponents(replace_host); + GURL tag_url3 = test_server()->GetURL( + "files/extensions/platform_apps/web_view_isolation/storage1.html"); + tag_url3 = tag_url3.ReplaceComponents(replace_host); + GURL tag_url4 = test_server()->GetURL( + "files/extensions/platform_apps/web_view_isolation/storage2.html"); + tag_url4 = tag_url4.ReplaceComponents(replace_host); + + ui_test_utils::NavigateToURLWithDisposition( + browser(), navigate_to_url, CURRENT_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + ui_test_utils::UrlLoadObserver observer1( + tag_url1, content::NotificationService::AllSources()); + ui_test_utils::UrlLoadObserver observer2( + tag_url2, content::NotificationService::AllSources()); + ui_test_utils::UrlLoadObserver observer3( + tag_url3, content::NotificationService::AllSources()); + ui_test_utils::UrlLoadObserver observer4( + tag_url4, content::NotificationService::AllSources()); + LoadAndLaunchPlatformApp("web_view_isolation"); + observer1.Wait(); + observer2.Wait(); + observer3.Wait(); + observer4.Wait(); + + content::Source<content::NavigationController> source1 = observer1.source(); + EXPECT_TRUE(source1->GetWebContents()->GetRenderProcessHost()->IsGuest()); + content::Source<content::NavigationController> source2 = observer2.source(); + EXPECT_TRUE(source2->GetWebContents()->GetRenderProcessHost()->IsGuest()); + content::Source<content::NavigationController> source3 = observer3.source(); + EXPECT_TRUE(source3->GetWebContents()->GetRenderProcessHost()->IsGuest()); + content::Source<content::NavigationController> source4 = observer4.source(); + EXPECT_TRUE(source4->GetWebContents()->GetRenderProcessHost()->IsGuest()); + + // Tags with the same storage partition are not yet combined in the same + // process. Check this until http://crbug.com/138296 is fixed. + EXPECT_NE(source1->GetWebContents()->GetRenderProcessHost()->GetID(), + source2->GetWebContents()->GetRenderProcessHost()->GetID()); + + // Check that the storage partitions of the first two tags match and are + // different than the other two. + EXPECT_EQ( + source1->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition(), + source2->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition()); + EXPECT_EQ( + source3->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition(), + source4->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition()); + EXPECT_NE( + source1->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition(), + source3->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition()); + + *default_tag_contents1 = source1->GetWebContents(); + *default_tag_contents2 = source2->GetWebContents(); + *named_partition_contents1 = source3->GetWebContents(); + *named_partition_contents2 = source4->GetWebContents(); + } + + void ExecuteScriptWaitForTitle(content::WebContents* web_contents, + const char* script, + const char* title) { + std::wstring js_script = ASCIIToWide(script); + string16 expected_title(ASCIIToUTF16(title)); + string16 error_title(ASCIIToUTF16("error")); + + content::TitleWatcher title_watcher(web_contents, expected_title); + title_watcher.AlsoWaitForTitle(error_title); + EXPECT_TRUE(content::ExecuteJavaScript(web_contents->GetRenderViewHost(), + std::wstring(), js_script)); + EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); + } }; IN_PROC_BROWSER_TEST_F(WebViewTest, Shim) { @@ -35,7 +137,11 @@ IN_PROC_BROWSER_TEST_F(WebViewTest, ShimSrcAttribute) { << message_; } -IN_PROC_BROWSER_TEST_F(WebViewTest, Isolation) { +// This tests cookie isolation for packaged apps with webview tags. It navigates +// the main browser window to a page that sets a cookie and loads an app with +// multiple webview tags. Each tag sets a cookie and the test checks the proper +// storage isolation is enforced. +IN_PROC_BROWSER_TEST_F(WebViewTest, CookieIsolation) { ASSERT_TRUE(StartTestServer()); const std::wstring kExpire = L"var expire = new Date(Date.now() + 24 * 60 * 60 * 1000);"; @@ -53,48 +159,28 @@ IN_PROC_BROWSER_TEST_F(WebViewTest, Isolation) { GURL set_cookie_url = test_server()->GetURL( "files/extensions/platform_apps/isolation/set_cookie.html"); set_cookie_url = set_cookie_url.ReplaceComponents(replace_host); - GURL tag_url1 = test_server()->GetURL( - "files/extensions/platform_apps/web_view_isolation/cookie.html"); - tag_url1 = tag_url1.ReplaceComponents(replace_host); - GURL tag_url2 = test_server()->GetURL( - "files/extensions/platform_apps/web_view_isolation/cookie2.html"); - tag_url2 = tag_url2.ReplaceComponents(replace_host); - - // Load a (non-app) page under the "localhost" origin that sets a cookie. - ui_test_utils::NavigateToURLWithDisposition( - browser(), set_cookie_url, - CURRENT_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - // Make sure the cookie is set. - int cookie_size; - std::string cookie_value; - automation_util::GetCookies(set_cookie_url, - chrome::GetWebContentsAt(browser(), 0), - &cookie_size, &cookie_value); - EXPECT_EQ("testCookie=1", cookie_value); - ui_test_utils::UrlLoadObserver observer1( - tag_url1, content::NotificationService::AllSources()); - ui_test_utils::UrlLoadObserver observer2( - tag_url2, content::NotificationService::AllSources()); - LoadAndLaunchPlatformApp("web_view_isolation"); - observer1.Wait(); - observer2.Wait(); - - content::Source<content::NavigationController> source1 = observer1.source(); - EXPECT_TRUE(source1->GetWebContents()->GetRenderProcessHost()->IsGuest()); - content::Source<content::NavigationController> source2 = observer2.source(); - EXPECT_TRUE(source2->GetWebContents()->GetRenderProcessHost()->IsGuest()); - EXPECT_NE(source1->GetWebContents()->GetRenderProcessHost()->GetID(), - source2->GetWebContents()->GetRenderProcessHost()->GetID()); + // The first two partitions will be used to set cookies and ensure they are + // shared. The named partition is used to ensure that cookies are isolated + // between partitions within the same app. + content::WebContents* cookie_contents1; + content::WebContents* cookie_contents2; + content::WebContents* named_partition_contents1; + content::WebContents* named_partition_contents2; + + NavigateAndOpenAppForIsolation(set_cookie_url, &cookie_contents1, + &cookie_contents2, &named_partition_contents1, + &named_partition_contents2); EXPECT_TRUE(content::ExecuteJavaScript( - source1->GetWebContents()->GetRenderViewHost(), std::wstring(), - cookie_script1)); + cookie_contents1->GetRenderViewHost(), std::wstring(), cookie_script1)); EXPECT_TRUE(content::ExecuteJavaScript( - source2->GetWebContents()->GetRenderViewHost(), std::wstring(), - cookie_script2)); + cookie_contents2->GetRenderViewHost(), std::wstring(), cookie_script2)); - // Test the regular browser context to ensure we still have only one cookie. + int cookie_size; + std::string cookie_value; + + // Test the regular browser context to ensure we have only one cookie. automation_util::GetCookies(GURL("http://localhost"), chrome::GetWebContentsAt(browser(), 0), &cookie_size, &cookie_value); @@ -105,13 +191,198 @@ IN_PROC_BROWSER_TEST_F(WebViewTest, Isolation) { // ensure we have properly set the cookies and we have both cookies in both // tags. automation_util::GetCookies(GURL("http://localhost"), - source1->GetWebContents(), + cookie_contents1, &cookie_size, &cookie_value); EXPECT_EQ("guest1=true; guest2=true", cookie_value); automation_util::GetCookies(GURL("http://localhost"), - source2->GetWebContents(), + cookie_contents2, &cookie_size, &cookie_value); EXPECT_EQ("guest1=true; guest2=true", cookie_value); + + // The third tag should not have any cookies as it is in a separate partition. + automation_util::GetCookies(GURL("http://localhost"), + named_partition_contents1, + &cookie_size, &cookie_value); + EXPECT_EQ("", cookie_value); + + CloseShellWindowsAndWaitForAppToExit(); +} + +// This tests DOM storage isolation for packaged apps with webview tags. It +// loads an app with multiple webview tags and each tag sets DOM storage +// entries, which the test checks to ensure proper storage isolation is +// enforced. +IN_PROC_BROWSER_TEST_F(WebViewTest, DOMStorageIsolation) { + ASSERT_TRUE(StartTestServer()); + GURL regular_url = test_server()->GetURL("files/title1.html"); + + std::string output; + std::wstring get_local_storage(L"window.domAutomationController.send(" + L"window.localStorage.getItem('foo') || 'badval')"); + std::wstring get_session_storage(L"window.domAutomationController.send(" + L"window.sessionStorage.getItem('bar') || 'badval')"); + + content::WebContents* default_tag_contents1; + content::WebContents* default_tag_contents2; + content::WebContents* storage_contents1; + content::WebContents* storage_contents2; + + NavigateAndOpenAppForIsolation(regular_url, &default_tag_contents1, + &default_tag_contents2, &storage_contents1, + &storage_contents2); + + // Initialize the storage for the first of the two tags that share a storage + // partition. + EXPECT_TRUE(content::ExecuteJavaScript( + storage_contents1->GetRenderViewHost(), std::wstring(), + L"initDomStorage('page1')")); + + // Let's test that the expected values are present in the first tag, as they + // will be overwritten once we call the initDomStorage on the second tag. + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents1->GetRenderViewHost(), std::wstring(), + get_local_storage.c_str(), &output)); + EXPECT_STREQ("local-page1", output.c_str()); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents1->GetRenderViewHost(), std::wstring(), + get_session_storage.c_str(), &output)); + EXPECT_STREQ("session-page1", output.c_str()); + + // Now, init the storage in the second tag in the same storage partition, + // which will overwrite the shared localStorage. + EXPECT_TRUE(content::ExecuteJavaScript( + storage_contents2->GetRenderViewHost(), std::wstring(), + L"initDomStorage('page2')")); + + // The localStorage value now should reflect the one written through the + // second tag. + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents1->GetRenderViewHost(), std::wstring(), + get_local_storage.c_str(), &output)); + EXPECT_STREQ("local-page2", output.c_str()); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents2->GetRenderViewHost(), std::wstring(), + get_local_storage.c_str(), &output)); + EXPECT_STREQ("local-page2", output.c_str()); + + // Session storage is not shared though, as each webview tag has separate + // instance, even if they are in the same storage partition. + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents1->GetRenderViewHost(), std::wstring(), + get_session_storage.c_str(), &output)); + EXPECT_STREQ("session-page1", output.c_str()); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents2->GetRenderViewHost(), std::wstring(), + get_session_storage.c_str(), &output)); + EXPECT_STREQ("session-page2", output.c_str()); + + // Also, let's check that the main browser and another tag that doesn't share + // the same partition don't have those values stored. + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + chrome::GetWebContentsAt(browser(), 0)->GetRenderViewHost(), + std::wstring(), get_local_storage.c_str(), &output)); + EXPECT_STREQ("badval", output.c_str()); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + chrome::GetWebContentsAt(browser(), 0)->GetRenderViewHost(), + std::wstring(), get_session_storage.c_str(), &output)); + EXPECT_STREQ("badval", output.c_str()); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + default_tag_contents1->GetRenderViewHost(), std::wstring(), + get_local_storage.c_str(), &output)); + EXPECT_STREQ("badval", output.c_str()); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + default_tag_contents1->GetRenderViewHost(), std::wstring(), + get_session_storage.c_str(), &output)); + EXPECT_STREQ("badval", output.c_str()); + + CloseShellWindowsAndWaitForAppToExit(); +} + +// This tests IndexedDB isolation for packaged apps with webview tags. It loads +// an app with multiple webview tags and each tag creates an IndexedDB record, +// which the test checks to ensure proper storage isolation is enforced. +IN_PROC_BROWSER_TEST_F(WebViewTest, IndexedDBIsolation) { + ASSERT_TRUE(StartTestServer()); + GURL regular_url = test_server()->GetURL("files/title1.html"); + + content::WebContents* default_tag_contents1; + content::WebContents* default_tag_contents2; + content::WebContents* storage_contents1; + content::WebContents* storage_contents2; + + NavigateAndOpenAppForIsolation(regular_url, &default_tag_contents1, + &default_tag_contents2, &storage_contents1, + &storage_contents2); + + // Initialize the storage for the first of the two tags that share a storage + // partition. + ExecuteScriptWaitForTitle(storage_contents1, "initIDB()", "idb created"); + ExecuteScriptWaitForTitle(storage_contents1, "addItemIDB(7, 'page1')", + "addItemIDB complete"); + ExecuteScriptWaitForTitle(storage_contents1, "readItemIDB(7)", + "readItemIDB complete"); + + std::string output; + std::wstring get_value( + L"window.domAutomationController.send(getValueIDB() || 'badval')"); + + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents1->GetRenderViewHost(), std::wstring(), + get_value.c_str(), &output)); + EXPECT_STREQ("page1", output.c_str()); + + // Initialize the db in the second tag. + ExecuteScriptWaitForTitle(storage_contents2, "initIDB()", "idb open"); + + // Since we share a partition, reading the value should return the existing + // one. + ExecuteScriptWaitForTitle(storage_contents2, "readItemIDB(7)", + "readItemIDB complete"); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents2->GetRenderViewHost(), std::wstring(), + get_value.c_str(), &output)); + EXPECT_STREQ("page1", output.c_str()); + + // Now write through the second tag and read it back. + ExecuteScriptWaitForTitle(storage_contents2, "addItemIDB(7, 'page2')", + "addItemIDB complete"); + ExecuteScriptWaitForTitle(storage_contents2, "readItemIDB(7)", + "readItemIDB complete"); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents2->GetRenderViewHost(), std::wstring(), + get_value.c_str(), &output)); + EXPECT_STREQ("page2", output.c_str()); + + // Reset the document title, otherwise the next call will not see a change and + // will hang waiting for it. + EXPECT_TRUE(content::ExecuteJavaScript( + storage_contents1->GetRenderViewHost(), std::wstring(), + L"document.title = 'foo'")); + + // Read through the first tag to ensure we have the second value. + ExecuteScriptWaitForTitle(storage_contents1, "readItemIDB(7)", + "readItemIDB complete"); + EXPECT_TRUE(ExecuteJavaScriptAndExtractString( + storage_contents1->GetRenderViewHost(), std::wstring(), + get_value.c_str(), &output)); + EXPECT_STREQ("page2", output.c_str()); + + // Now, let's confirm there is no database in the main browser and another + // tag that doesn't share the same partition. Due to the IndexedDB API design, + // open will succeed, but the version will be 1, since it creates the database + // if it is not found. The two tags use database version 3, so we avoid + // ambiguity. + const char* script = + "indexedDB.open('isolation').onsuccess = function(e) {" + " if (e.target.result.version == 1)" + " document.title = 'db not found';" + " else " + " document.title = 'error';" + "}"; + ExecuteScriptWaitForTitle(chrome::GetWebContentsAt(browser(), 0), + script, "db not found"); + ExecuteScriptWaitForTitle(default_tag_contents1, script, "db not found"); + CloseShellWindowsAndWaitForAppToExit(); } diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc index 5edbb68..7411d64 100644 --- a/chrome/browser/profiles/profile_impl.cc +++ b/chrome/browser/profiles/profile_impl.cc @@ -782,18 +782,8 @@ net::URLRequestContextGetter* ProfileImpl::GetRequestContextForRenderProcess( int renderer_child_id) { content::RenderProcessHost* rph = content::RenderProcessHost::FromID( renderer_child_id); - content::StoragePartition* storage_partition = rph->GetStoragePartition(); - - // TODO(nasko): Remove this conditional, once webview tag creates a proper - // storage partition. - if (rph->IsGuest()) { - // For guest processes, we only allow in-memory partitions for now, so - // hardcode the parameter here. - return GetRequestContextForStoragePartition( - storage_partition->GetPath(), true); - } - return storage_partition->GetURLRequestContext(); + return rph->GetStoragePartition()->GetURLRequestContext(); } net::URLRequestContextGetter* ProfileImpl::GetMediaRequestContext() { @@ -808,15 +798,6 @@ ProfileImpl::GetMediaRequestContextForRenderProcess( renderer_child_id); content::StoragePartition* storage_partition = rph->GetStoragePartition(); - // TODO(nasko): Remove this conditional, once webview tag creates a proper - // storage partition. - if (rph->IsGuest()) { - // For guest processes, we only allow in-memory partitions for now, so - // hardcode the parameter here. - return GetMediaRequestContextForStoragePartition( - storage_partition->GetPath(), true); - } - return storage_partition->GetMediaURLRequestContext(); } diff --git a/chrome/browser/profiles/profile_impl_io_data.cc b/chrome/browser/profiles/profile_impl_io_data.cc index 65e8822..35377cb 100644 --- a/chrome/browser/profiles/profile_impl_io_data.cc +++ b/chrome/browser/profiles/profile_impl_io_data.cc @@ -188,9 +188,9 @@ ProfileImplIOData::Handle::GetIsolatedAppRequestContextGetter( const FilePath& partition_path, bool in_memory) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - // TODO(nasko): Check that the partition_path is not the same as the - // base profile path. We expect isolated partition, which will never go - // to the default profile path. + // Check that the partition_path is not the same as the base profile path. We + // expect isolated partition, which will never go to the default profile path. + CHECK(partition_path != profile_->GetPath()); LazyInitialize(); // Keep a map of request context getters, one per requested storage partition. @@ -220,10 +220,7 @@ ProfileImplIOData::Handle::GetIsolatedMediaRequestContextGetter( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // We must have a non-default path, or this will act like the default media // context. - // - // TODO(nasko): Check that the partition_path is not the same as the - // base profile path. We expect isolated partition, which will never go - // to the default profile path. + CHECK(partition_path != profile_->GetPath()); LazyInitialize(); // Keep a map of request context getters, one per requested storage partition. diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc index bfc8bec..fdb8bbb 100644 --- a/chrome/test/base/testing_profile.cc +++ b/chrome/test/base/testing_profile.cc @@ -612,16 +612,7 @@ net::URLRequestContextGetter* TestingProfile::GetRequestContextForRenderProcess( int renderer_child_id) { content::RenderProcessHost* rph = content::RenderProcessHost::FromID( renderer_child_id); - content::StoragePartition* storage_partition = rph->GetStoragePartition(); - - // TODO(nasko): Remove this conditional, once webview tag creates a proper - // storage partition. - if (rph->IsGuest()) { - return GetRequestContextForStoragePartition( - storage_partition->GetPath(), true); - } - - return storage_partition->GetURLRequestContext(); + return rph->GetStoragePartition()->GetURLRequestContext(); } void TestingProfile::CreateRequestContext() { diff --git a/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js b/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js index 16f1b2d..ad95ea9 100644 --- a/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js +++ b/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js @@ -7,10 +7,19 @@ chrome.test.getConfig(function(config) { '/files/extensions/platform_apps/web_view_isolation/cookie.html'; var url2 = 'http://localhost:' + config.testServer.port + '/files/extensions/platform_apps/web_view_isolation/cookie2.html'; + var url3 = 'http://localhost:' + config.testServer.port + + '/files/extensions/platform_apps/web_view_isolation/storage1.html'; + var url4 = 'http://localhost:' + config.testServer.port + + '/files/extensions/platform_apps/web_view_isolation/storage2.html'; var node = document.getElementById('web_view_container'); - node.innerHTML = "<object id='webview' src=" + url + + node.innerHTML = + "<object id='webview' src=" + url + " type='application/browser-plugin' width=500 height=550></object>" + "<object id='webview2' src=" + url2 + + " type='application/browser-plugin' width=500 height=550></object>" + + "<object id='webview2' partition='partition1' src=" + url3 + + " type='application/browser-plugin' width=500 height=550></object>" + + "<object id='webview2' partition='partition1' src=" + url4 + " type='application/browser-plugin' width=500 height=550></object>"; chrome.test.sendMessage('Launched'); }); diff --git a/chrome/test/data/extensions/platform_apps/web_view_isolation/storage.js b/chrome/test/data/extensions/platform_apps/web_view_isolation/storage.js new file mode 100644 index 0000000..a9bc99e --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/web_view_isolation/storage.js @@ -0,0 +1,86 @@ +// Copyright (c) 2012 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. + +// This method initializes the two types of DOM storage. +function initDomStorage(value) { + window.localStorage.setItem('foo', 'local-' + value); + window.sessionStorage.setItem('bar', 'session-' + value); +} + +// The code below is used for testing IndexedDB isolation. +// The test uses three basic operations -- open, read, write -- to verify proper +// isolation across webview tags with different storage partitions. +// Each of the basic functions below sets document.title to a specific text, +// which the main browser test is waiting for. This is needed because all +// the functions get their results through callbacks and cannot return the +// values directly. +var isolation = {}; +window.indexedDB = window.indexedDB || window.webkitIndexedDB; + +isolation.db = null; +isolation.onerror = function(e) { + document.title = "error"; +}; + +// This method opens the database and creates the objectStore if it doesn't +// exist. It sets the document.title to a string referring to which +// operation has been performed - open vs create. +function initIDB() { + var request = indexedDB.open("isolation"); + request.onsuccess = function(e) { + var v = 3; + isolation.db = e.target.result; + if (v != isolation.db.version) { + var setVrequest = isolation.db.setVersion(v); + setVrequest.onerror = isolation.onerror; + setVrequest.onsuccess = function(e) { + var store = isolation.db.createObjectStore( + "partitions", {keyPath: "id"}); + e.target.transaction.oncomplete = function() { + document.title = "idb created"; + } + }; + } else { + document.title = "idb open"; + } + }; + request.onerror = isolation.onerror; +} + +// This method adds a |value| to the database identified by |id|. +function addItemIDB(id, value) { + var trans = isolation.db.transaction(["partitions"], "readwrite"); + var store = trans.objectStore("partitions"); + var data = { "partition": value, "id": id }; + + var request = store.put(data); + request.onsuccess = function(e) { + document.title = "addItemIDB complete"; + }; + request.onerror = isolation.onerror; +}; + +var storedValue = null; + +// This method reads an item from the database, identified by |id|. Since +// the value cannot be returned directly, it is saved into the global +// "storedValue" variable, which is then read through getValueIDB(). +function readItemIDB(id) { + storedValue = null; + var trans = isolation.db.transaction(["partitions"], "readwrite"); + var store = trans.objectStore("partitions"); + + var request = store.get(id); + request.onsuccess = function(e) { + if (!!e.target.result != false) { + storedValue = request.result.partition; + } + document.title = "readItemIDB complete"; + }; + request.onerror = isolation.onerror; +} + +function getValueIDB() { + return storedValue; +} diff --git a/chrome/test/data/extensions/platform_apps/web_view_isolation/storage1.html b/chrome/test/data/extensions/platform_apps/web_view_isolation/storage1.html new file mode 100644 index 0000000..8443a27 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/web_view_isolation/storage1.html @@ -0,0 +1,13 @@ +<!-- + * Copyright (c) 2012 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. +--> +<html> +<head> +<script src="storage.js"></script> +</head> +<body> +Empty page 1, used for testing storage isolation. +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/web_view_isolation/storage2.html b/chrome/test/data/extensions/platform_apps/web_view_isolation/storage2.html new file mode 100644 index 0000000..d12b17c --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/web_view_isolation/storage2.html @@ -0,0 +1,13 @@ +<!-- + * Copyright (c) 2012 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. +--> +<html> +<head> +<script src="storage.js"></script> +</head> +<body> +Empty page 2, used for testing storage isolation. +</body> +</html> |