| Index: chrome/browser/extensions/web_view_browsertest.cc
|
| diff --git a/chrome/browser/extensions/web_view_browsertest.cc b/chrome/browser/extensions/web_view_browsertest.cc
|
| index d8ac13eee4b5701ac68af6e4059d940b1260109e..759017b8cc55a7decb446c5cdf422aedd91a1c59 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();
|
| }
|
|
|