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",