Support the removal of only session cookies or persistent cookies
through the <webview> cleardata API.
BUG=687082
Review-Url: https://codereview.chromium.org/2700473003
Cr-Commit-Position: refs/heads/master@{#451119}
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 2698c53..b14a789 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -2405,9 +2405,23 @@
IN_PROC_BROWSER_TEST_P(WebViewTest, ClearData) {
ASSERT_TRUE(StartEmbeddedTestServer()); // For serving guest pages.
- ASSERT_TRUE(RunPlatformAppTestWithArg(
- "platform_apps/web_view/common", "cleardata"))
- << message_;
+ ASSERT_TRUE(
+ RunPlatformAppTestWithArg("platform_apps/web_view/common", "cleardata"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_P(WebViewTest, ClearSessionCookies) {
+ ASSERT_TRUE(StartEmbeddedTestServer()); // For serving guest pages.
+ ASSERT_TRUE(RunPlatformAppTestWithArg("platform_apps/web_view/common",
+ "cleardata_session"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_P(WebViewTest, ClearPersistentCookies) {
+ ASSERT_TRUE(StartEmbeddedTestServer()); // For serving guest pages.
+ ASSERT_TRUE(RunPlatformAppTestWithArg("platform_apps/web_view/common",
+ "cleardata_persistent"))
+ << message_;
}
// Regression test for https://crbug.com/615429.
diff --git a/chrome/common/extensions/api/webview_tag.json b/chrome/common/extensions/api/webview_tag.json
index 0263954..7692c7a 100644
--- a/chrome/common/extensions/api/webview_tag.json
+++ b/chrome/common/extensions/api/webview_tag.json
@@ -32,6 +32,8 @@
"appcache": { "type": "boolean", "optional": true, "description": "Websites' appcaches." },
"cache": { "type": "boolean", "optional": true, "description": "Since Chrome 43.<br>The browser's cache. Note: when removing data, this clears the entire cache; it is not limited to the range you specify." },
"cookies": { "type": "boolean", "optional": true, "description": "The partition's cookies." },
+ "sessionCookies": { "type": "boolean", "optional": true, "description": "The partition's session cookies." },
+ "persistentCookies": { "type": "boolean", "optional": true, "description": "The partition's persistent cookies." },
"fileSystems": { "type": "boolean", "optional": true, "description": "Websites' filesystems." },
"indexedDB": { "type": "boolean", "optional": true, "description": "Websites' IndexedDB data." },
"localStorage": { "type": "boolean", "optional": true, "description": "Websites' local storage data." },
diff --git a/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_persistent/bootstrap.js b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_persistent/bootstrap.js
new file mode 100644
index 0000000..bdb7fdb
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_persistent/bootstrap.js
@@ -0,0 +1,69 @@
+// Copyright 2017 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.
+
+config.IS_CHROME_TEST = true;
+// Guest served from TestServer.
+config.IS_JS_ONLY_GUEST = false;
+config.TEST_DIR = 'cleardata_persistent';
+
+var clearDataTests = {};
+
+// step1. Ask guest to load load session (bar) and persistent (foo) cookies.
+// step2. Guest responds saying it has added cookies.
+// embedder clears persistent cookie data of the guest via clearData API.
+// step3. Ask guest for cookies that were set in step1.
+// step4. Guest responds with cookie values, embedder verifies persistent cookie
+// is unset but session cookie is still set.
+
+var run = function() {
+ var container = document.createElement('div');
+ container.id = 'webview-tag-container';
+ document.body.appendChild(container);
+
+ chrome.test.getConfig(function(chromeConfig) {
+ window.console.log('getConfig: ' + chromeConfig);
+ utils.setUp(chromeConfig, config);
+ embedder.loadGuest(function() {
+ chrome.test.runTests([
+ clearDataTests.testCookies
+ ]);
+ }, function(data) {
+ var handled = true;
+ switch (data[0]) {
+ case 'step2.cookies-added':
+ window.console.log('embedder, on message: ' + data[0]);
+ var onDataCleared = function() {
+ window.console.log('embedder.onDataCleared');
+ embedder.webview.contentWindow.postMessage(
+ JSON.stringify(['step3.get-cookies', 'foo', 'bar']), '*');
+ };
+ embedder.webview.clearData(
+ { 'since': 1 }, { 'persistentCookies': true },
+ onDataCleared);
+ break;
+ case 'step4.got-cookies':
+ window.console.log('embedder, on message: ' + data[0]);
+ var cookies = data[1];
+ // fooValue was a persistent cookie, which should be gone.
+ chrome.test.assertEq([null, 'barValue'], cookies);
+ chrome.test.succeed();
+ break;
+ default:
+ handled = false;
+ break;
+ }
+ return handled;
+ });
+ });
+};
+
+// Tests.
+clearDataTests.testCookies = function testCookies() {
+ window.console.log('clearDataTests.testCookies');
+ embedder.webview.contentWindow.postMessage(
+ JSON.stringify(['step1.add-cookies']), '*');
+};
+
+// Run test(s).
+run();
diff --git a/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_persistent/guest.html b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_persistent/guest.html
new file mode 100644
index 0000000..06b3449
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_persistent/guest.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<!--
+ * Copyright 2017 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 type="text/javascript">
+ // A guest that stores and deletes cookies.
+ // Note that the embedder has to initiate a postMessage first so that
+ // the guest has a reference to the embedder's window.
+
+ // The window reference of the embedder to send post message reply.
+ var embedderWindowChannel = null;
+
+ // A value that uniquely identifies the guest sending the messages to the
+ // embedder.
+ var channelId = 0;
+ var notifyEmbedder = function (msgArray) {
+ var msg = msgArray.concat([channelId]);
+ embedderWindowChannel.postMessage(JSON.stringify(msg), '*');
+ };
+
+ var SPLIT_RE_ = /\s*;\s*/;
+ var setCookie = function(name, value) { // Just a random future time.
+ var futureDate = new Date((+new Date) + 10000 * 1000);
+ document.cookie =
+ name + '=' + value + ';expires=' + futureDate.toUTCString();
+ };
+ var setSessionCookie = function (name, value) { // Session cookie.
+ document.cookie = name + '=' + value;
+ };
+ var getCookie = function (name) {
+ var nameEq = name + '=';
+ var parts = (document.cookie || '').split(SPLIT_RE_);
+ for (var i = 0; i < parts.length; ++i) {
+ var part = parts[i];
+ if (part.startsWith(nameEq)) {
+ return part.substr(nameEq.length);
+ }
+ if (part == name) {
+ return '';
+ }
+ }
+ return undefined;
+ };
+
+ var addCookies = function() {
+ window.console.log('setCookie: foo = fooValue');
+ setCookie('foo', 'fooValue');
+ window.console.log('setSessionCookie: bar = barValue');
+ setSessionCookie('bar', 'barValue');
+ notifyEmbedder(['step2.cookies-added']);
+ };
+
+ var onPostMessageReceived = function(e) {
+ embedderWindowChannel = e.source;
+ var data = JSON.parse(e.data);
+ if (data[0] == 'create-channel') {
+ window.console.log('guest: create-channel');
+ channelId = data[1];
+ notifyEmbedder(['channel-created']);
+ return;
+ }
+
+ window.console.log('guest.onPostMessageReceived: ' + data[0]);
+ // Tests.
+ // These logs trigger event listeners in the embedder.
+ switch (data[0]) {
+ case 'step1.add-cookies':
+ window.console.log('guest.' + data[0]);
+ addCookies();
+ break;
+ case 'step3.get-cookies':
+ window.console.log('guest.' + data[0]);
+ var retValues = ['step4.got-cookies'];
+ var cookieValues = [];
+ for (var i = 1; i < data.length; ++i) {
+ cookieValues.push(getCookie(data[i]));
+ }
+ retValues.push(cookieValues);
+ notifyEmbedder(retValues);
+ break;
+ default:
+ break;
+ }
+ };
+ window.addEventListener('message', onPostMessageReceived, false);
+ </script>
+ </head>
+ <body>
+ <div>Guest that stores and retrieves certain cookies.</div>
+ </body>
+</html>
diff --git a/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_session/bootstrap.js b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_session/bootstrap.js
new file mode 100644
index 0000000..11c7ce27b
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_session/bootstrap.js
@@ -0,0 +1,69 @@
+// Copyright 2017 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.
+
+config.IS_CHROME_TEST = true;
+// Guest served from TestServer.
+config.IS_JS_ONLY_GUEST = false;
+config.TEST_DIR = 'cleardata_session';
+
+var clearDataTests = {};
+
+// step1. Ask guest to load load session (bar) and persistent (foo) cookies.
+// step2. Guest responds saying it has added cookies.
+// embedder clears session cookie data of the guest via clearData API.
+// step3. Ask guest for cookies that were set in step1.
+// step4. Guest responds with cookie values, embedder verifies session cookie is
+// unset but persistent cookie is still set.
+
+var run = function() {
+ var container = document.createElement('div');
+ container.id = 'webview-tag-container';
+ document.body.appendChild(container);
+
+ chrome.test.getConfig(function(chromeConfig) {
+ window.console.log('getConfig: ' + chromeConfig);
+ utils.setUp(chromeConfig, config);
+ embedder.loadGuest(function() {
+ chrome.test.runTests([
+ clearDataTests.testCookies
+ ]);
+ }, function(data) {
+ var handled = true;
+ switch (data[0]) {
+ case 'step2.cookies-added':
+ window.console.log('embedder, on message: ' + data[0]);
+ var onDataCleared = function() {
+ window.console.log('embedder.onDataCleared');
+ embedder.webview.contentWindow.postMessage(
+ JSON.stringify(['step3.get-cookies', 'foo', 'bar']), '*');
+ };
+ embedder.webview.clearData(
+ { 'since': 1 }, { 'sessionCookies': true },
+ onDataCleared);
+ break;
+ case 'step4.got-cookies':
+ window.console.log('embedder, on message: ' + data[0]);
+ var cookies = data[1];
+ // barValue was a session cookie, which should be gone.
+ chrome.test.assertEq(['fooValue', null], cookies);
+ chrome.test.succeed();
+ break;
+ default:
+ handled = false;
+ break;
+ }
+ return handled;
+ });
+ });
+};
+
+// Tests.
+clearDataTests.testCookies = function testCookies() {
+ window.console.log('clearDataTests.testCookies');
+ embedder.webview.contentWindow.postMessage(
+ JSON.stringify(['step1.add-cookies']), '*');
+};
+
+// Run test(s).
+run();
diff --git a/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_session/guest.html b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_session/guest.html
new file mode 100644
index 0000000..37d2be0
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/web_view/common/cleardata_session/guest.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<!--
+ * Copyright 2017 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 type="text/javascript">
+ // A guest that stores and deletes cookies.
+ // Note that the embedder has to initiate a postMessage first so that
+ // the guest has a reference to the embedder's window.
+
+ // The window reference of the embedder to send post message reply.
+ var embedderWindowChannel = null;
+
+ // A value that uniquely identifies the guest sending the messages to the
+ // embedder.
+ var channelId = 0;
+ var notifyEmbedder = function(msgArray) {
+ var msg = msgArray.concat([channelId]);
+ embedderWindowChannel.postMessage(JSON.stringify(msg), '*');
+ };
+
+ var SPLIT_RE_ = /\s*;\s*/;
+ var setCookie = function(name, value) { // Just a random future time.
+ var futureDate = new Date((+new Date) + 10000 * 1000);
+ document.cookie =
+ name + '=' + value + ';expires=' + futureDate.toUTCString();
+ };
+ var setSessionCookie = function (name, value) { // Session cookie.
+ document.cookie = name + '=' + value;
+ };
+ var getCookie = function (name) {
+ var nameEq = name + '=';
+ var parts = (document.cookie || '').split(SPLIT_RE_);
+ for (var i = 0; i < parts.length; ++i) {
+ var part = parts[i];
+ if (part.startsWith(nameEq)) {
+ return part.substr(nameEq.length);
+ }
+ if (part == name) {
+ return '';
+ }
+ }
+ return undefined;
+ };
+
+ var addCookies = function() {
+ window.console.log('setCookie: foo = fooValue');
+ setCookie('foo', 'fooValue');
+ window.console.log('setSessionCookie: bar = barValue');
+ setSessionCookie('bar', 'barValue');
+ notifyEmbedder(['step2.cookies-added']);
+ };
+
+ var onPostMessageReceived = function(e) {
+ embedderWindowChannel = e.source;
+ var data = JSON.parse(e.data);
+ if (data[0] == 'create-channel') {
+ window.console.log('guest: create-channel');
+ channelId = data[1];
+ notifyEmbedder(['channel-created']);
+ return;
+ }
+
+ window.console.log('guest.onPostMessageReceived: ' + data[0]);
+ // Tests.
+ // These logs trigger event listeners in the embedder.
+ switch (data[0]) {
+ case 'step1.add-cookies':
+ window.console.log('guest.' + data[0]);
+ addCookies();
+ break;
+ case 'step3.get-cookies':
+ window.console.log('guest.' + data[0]);
+ var retValues = ['step4.got-cookies'];
+ var cookieValues = [];
+ for (var i = 1; i < data.length; ++i) {
+ cookieValues.push(getCookie(data[i]));
+ }
+ retValues.push(cookieValues);
+ notifyEmbedder(retValues);
+ break;
+ default:
+ break;
+ }
+ };
+ window.addEventListener('message', onPostMessageReceived, false);
+ </script>
+ </head>
+ <body>
+ <div>Guest that stores and retrieves certain cookies.</div>
+ </body>
+</html>
diff --git a/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc b/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
index 179e68e..c12db754 100644
--- a/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
+++ b/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
@@ -44,6 +44,8 @@
const char kAppCacheKey[] = "appcache";
const char kCacheKey[] = "cache";
const char kCookiesKey[] = "cookies";
+const char kSessionCookiesKey[] = "sessionCookies";
+const char kPersistentCookiesKey[] = "persistentCookies";
const char kFileSystemsKey[] = "fileSystems";
const char kIndexedDBKey[] = "indexedDB";
const char kLocalStorageKey[] = "localStorage";
@@ -61,6 +63,10 @@
return webview::WEB_VIEW_REMOVE_DATA_MASK_APPCACHE;
if (strcmp(key, kCacheKey) == 0)
return webview::WEB_VIEW_REMOVE_DATA_MASK_CACHE;
+ if (strcmp(key, kSessionCookiesKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_SESSION_COOKIES;
+ if (strcmp(key, kPersistentCookiesKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_PERSISTENT_COOKIES;
if (strcmp(key, kCookiesKey) == 0)
return webview::WEB_VIEW_REMOVE_DATA_MASK_COOKIES;
if (strcmp(key, kFileSystemsKey) == 0)
diff --git a/extensions/browser/guest_view/web_view/web_view_constants.cc b/extensions/browser/guest_view/web_view/web_view_constants.cc
index 9ed1ed5..4c2af5b 100644
--- a/extensions/browser/guest_view/web_view/web_view_constants.cc
+++ b/extensions/browser/guest_view/web_view/web_view_constants.cc
@@ -138,6 +138,8 @@
const uint32_t WEB_VIEW_REMOVE_DATA_MASK_INDEXEDDB = 1 << 4;
const uint32_t WEB_VIEW_REMOVE_DATA_MASK_LOCAL_STORAGE = 1 << 5;
const uint32_t WEB_VIEW_REMOVE_DATA_MASK_WEBSQL = 1 << 6;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_SESSION_COOKIES = 1 << 7;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_PERSISTENT_COOKIES = 1 << 8;
// Other.
const char kWebViewContentScriptManagerKeyName[] =
diff --git a/extensions/browser/guest_view/web_view/web_view_constants.h b/extensions/browser/guest_view/web_view/web_view_constants.h
index 6b82c19..bfef2e5 100644
--- a/extensions/browser/guest_view/web_view/web_view_constants.h
+++ b/extensions/browser/guest_view/web_view/web_view_constants.h
@@ -148,6 +148,8 @@
extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_INDEXEDDB;
extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_LOCAL_STORAGE;
extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_WEBSQL;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_SESSION_COOKIES;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_PERSISTENT_COOKIES;
// Other.
extern const char kWebViewContentScriptManagerKeyName[];
diff --git a/extensions/browser/guest_view/web_view/web_view_guest.cc b/extensions/browser/guest_view/web_view/web_view_guest.cc
index 57150cd9..6973ebb0 100644
--- a/extensions/browser/guest_view/web_view/web_view_guest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_guest.cc
@@ -62,6 +62,7 @@
#include "ipc/ipc_message_macros.h"
#include "net/base/escape.h"
#include "net/base/net_errors.h"
+#include "net/cookies/canonical_cookie.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "url/url_constants.h"
@@ -87,8 +88,12 @@
uint32_t mask = 0;
if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_APPCACHE)
mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE;
- if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_COOKIES)
+ if (web_view_removal_mask &
+ (webview::WEB_VIEW_REMOVE_DATA_MASK_COOKIES |
+ webview::WEB_VIEW_REMOVE_DATA_MASK_SESSION_COOKIES |
+ webview::WEB_VIEW_REMOVE_DATA_MASK_PERSISTENT_COOKIES)) {
mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
+ }
if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_FILE_SYSTEMS)
mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_INDEXEDDB)
@@ -425,15 +430,41 @@
callback.Run();
return;
}
+
+ content::StoragePartition::CookieMatcherFunction cookie_matcher;
+
+ bool remove_session_cookies =
+ !!(removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_SESSION_COOKIES);
+ bool remove_persistent_cookies =
+ !!(removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_PERSISTENT_COOKIES);
+ bool remove_all_cookies =
+ (!!(removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_COOKIES)) ||
+ (remove_session_cookies && remove_persistent_cookies);
+
+ // Leaving the cookie_matcher unset will cause all cookies to be purged.
+ if (!remove_all_cookies) {
+ if (remove_session_cookies) {
+ cookie_matcher =
+ base::Bind([](const net::CanonicalCookie& cookie) -> bool {
+ return !cookie.IsPersistent();
+ });
+ } else if (remove_persistent_cookies) {
+ cookie_matcher =
+ base::Bind([](const net::CanonicalCookie& cookie) -> bool {
+ return cookie.IsPersistent();
+ });
+ }
+ }
+
content::StoragePartition* partition =
content::BrowserContext::GetStoragePartition(
web_contents()->GetBrowserContext(),
web_contents()->GetSiteInstance());
partition->ClearData(
storage_partition_removal_mask,
- content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, GURL(),
- content::StoragePartition::OriginMatcherFunction(), remove_since,
- base::Time::Now(), callback);
+ content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
+ content::StoragePartition::OriginMatcherFunction(), cookie_matcher,
+ remove_since, base::Time::Now(), callback);
}
void WebViewGuest::GuestViewDidStopLoading() {
diff --git a/extensions/common/api/web_view_internal.json b/extensions/common/api/web_view_internal.json
index 94a6eb9..2030dfe 100644
--- a/extensions/common/api/web_view_internal.json
+++ b/extensions/common/api/web_view_internal.json
@@ -23,7 +23,17 @@
"cookies": {
"type": "boolean",
"optional": true,
- "description": "The browser's cookies."
+ "description": "The Websites' cookies. This will remove both session and persistent cookies"
+ },
+ "sessionCookies": {
+ "type": "boolean",
+ "optional": true,
+ "description": "The Websites' session cookies."
+ },
+ "persistentCookies": {
+ "type": "boolean",
+ "optional": true,
+ "description": "The Websites' persistent cookies."
},
"fileSystems": {
"type": "boolean",