Hide requests in an extension from other extensions

BUG=510802

Review URL: https://codereview.chromium.org/1267183003

Cr-Commit-Position: refs/heads/master@{#341939}
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index c9bdf6e..7589d09 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -322,3 +322,68 @@
   EXPECT_TRUE(listener2.WaitUntilSatisfied());
   EXPECT_TRUE(listener_incognito2.WaitUntilSatisfied());
 }
+
+IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ExtensionRequests) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  ExtensionTestMessageListener listener_main1("web_request_status1", true);
+  ExtensionTestMessageListener listener_main2("web_request_status2", true);
+
+  ExtensionTestMessageListener listener_app("app_done", false);
+  ExtensionTestMessageListener listener_extension("extension_done", true);
+
+  // Set up webRequest listener
+  ASSERT_TRUE(LoadExtension(
+          test_data_dir_.AppendASCII("webrequest_extensions/main")));
+  EXPECT_TRUE(listener_main1.WaitUntilSatisfied());
+  EXPECT_TRUE(listener_main2.WaitUntilSatisfied());
+
+  // Perform some network activity in an app and another extension.
+  ASSERT_TRUE(LoadExtension(
+          test_data_dir_.AppendASCII("webrequest_extensions/app")));
+  ASSERT_TRUE(LoadExtension(
+          test_data_dir_.AppendASCII("webrequest_extensions/extension")));
+
+  EXPECT_TRUE(listener_app.WaitUntilSatisfied());
+  EXPECT_TRUE(listener_extension.WaitUntilSatisfied());
+
+  // Load a page, a content script will ping us when it is ready.
+  ExtensionTestMessageListener listener_pageready("contentscript_ready", true);
+  ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL(
+          "/extensions/test_file.html?match_webrequest_test"));
+
+  // The extension and app-generated requests should not have triggered any
+  // webRequest event filtered by type 'xmlhttprequest'.
+  // (check this here instead of before the navigation, in case the webRequest
+  // event routing is slow for some reason).
+  ExtensionTestMessageListener listener_result(false);
+  listener_main1.Reply("");
+  EXPECT_TRUE(listener_result.WaitUntilSatisfied());
+  EXPECT_EQ("Did not intercept any requests.", listener_result.message());
+
+  // Proceed with the final tests: Let the content script fire a request.
+  EXPECT_TRUE(listener_pageready.WaitUntilSatisfied());
+  listener_pageready.Reply("");
+
+  ExtensionTestMessageListener listener_contentscript("contentscript_done",
+                                                      true);
+  ExtensionTestMessageListener listener_framescript("framescript_done", false);
+  EXPECT_TRUE(listener_contentscript.WaitUntilSatisfied());
+  listener_contentscript.Reply("");
+  EXPECT_TRUE(listener_framescript.WaitUntilSatisfied());
+
+  // Collect the visited URLs. The content script and subframe does not run in
+  // the extension's process, so the requests should be visible to the main
+  // extension.
+  listener_result.Reset();
+  listener_main2.Reply("");
+  EXPECT_TRUE(listener_result.WaitUntilSatisfied());
+  if (content::AreAllSitesIsolatedForTesting()) {
+    // With --site-per-process, the extension frame does run in the extension's
+    // process.
+    EXPECT_EQ("Intercepted requests: ?contentscript",
+              listener_result.message());
+  } else {
+    EXPECT_EQ("Intercepted requests: ?contentscript, ?framescript",
+              listener_result.message());
+  }
+}
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/app/background.js b/chrome/test/data/extensions/api_test/webrequest_extensions/app/background.js
new file mode 100644
index 0000000..5a4ed86
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/app/background.js
@@ -0,0 +1,17 @@
+// Copyright 2015 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.
+
+chrome.test.getConfig(function(config) {
+  var baseUrl = 'http://example.com:' + config.testServer.port;
+
+  var x = new XMLHttpRequest();
+  x.open('GET', baseUrl + '/extensions/test_file.txt?app');
+  x.onloadend = function() {
+    // Just a sanity check to ensure that the server is running.
+    // The test does not change the response.
+    chrome.test.assertEq('Hello!', x.responseText);
+    chrome.test.sendMessage('app_done');
+  };
+  x.send();
+});
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/app/manifest.json b/chrome/test/data/extensions/api_test/webrequest_extensions/app/manifest.json
new file mode 100644
index 0000000..e9d303e
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/app/manifest.json
@@ -0,0 +1,11 @@
+{
+  "name": "App",
+  "version": "1",
+  "manifest_version": 2,
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  "permissions": ["*://*/*"]
+}
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/extension/background.js b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/background.js
new file mode 100644
index 0000000..af5082abf
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/background.js
@@ -0,0 +1,18 @@
+// Copyright 2015 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.
+
+chrome.test.getConfig(function(config) {
+  var baseUrl = 'http://example.com:' + config.testServer.port;
+
+  var x = new XMLHttpRequest();
+  x.open('GET', baseUrl + '/extensions/test_file.txt?extension');
+  x.onloadend = function() {
+    // Just a sanity check to ensure that the server is running.
+    // The test does not change the response.
+    chrome.test.assertEq('Hello!', x.responseText);
+
+    chrome.test.sendMessage('extension_done');
+  };
+  x.send();
+});
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/extension/contentscript.js b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/contentscript.js
new file mode 100644
index 0000000..9c3ba8b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/contentscript.js
@@ -0,0 +1,21 @@
+// Copyright 2015 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.
+
+// location.origin will be equivalent to 'http://example.com:PORT';
+var baseUrl = location.origin;
+
+chrome.test.sendMessage('contentscript_ready', function() {
+  var x = new XMLHttpRequest();
+  x.open('GET', baseUrl + '/extensions/test_file.txt?contentscript');
+  x.onloadend = function() {
+    chrome.test.assertEq('Hello!', x.responseText);
+
+    chrome.test.sendMessage('contentscript_done', function() {
+      var frame = document.createElement('iframe');
+      frame.src = chrome.runtime.getURL('frame.html');
+      document.body.appendChild(frame);
+    });
+  };
+  x.send();
+});
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/extension/frame.html b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/frame.html
new file mode 100644
index 0000000..f9ed90c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/frame.html
@@ -0,0 +1,6 @@
+<!--
+ * Copyright 2015 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.
+-->
+<script src="framescript.js"></script>
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/extension/framescript.js b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/framescript.js
new file mode 100644
index 0000000..f3761279
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/framescript.js
@@ -0,0 +1,16 @@
+// Copyright 2015 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.
+
+// |baseUrl| will be equivalent to 'http://example.com:PORT';
+var baseUrl = location.ancestorOrigins[0];
+
+var x = new XMLHttpRequest();
+x.open('GET', baseUrl + '/extensions/test_file.txt?framescript');
+x.onloadend = function() {
+  // Sanity check.
+  chrome.test.assertEq('Hello!', x.responseText);
+
+  chrome.test.sendMessage('framescript_done');
+};
+x.send();
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/extension/manifest.json b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/manifest.json
new file mode 100644
index 0000000..28cb603
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/extension/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "Extension",
+  "version": "1",
+  "manifest_version": 2,
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": true
+  },
+  "content_scripts": [{
+    "js": ["contentscript.js"],
+    "matches": ["*://*/*match_webrequest_test*"]
+  }],
+  "web_accessible_resources": ["frame.html"],
+  "permissions": ["*://*/*"]
+}
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/main/background.js b/chrome/test/data/extensions/api_test/webrequest_extensions/main/background.js
new file mode 100644
index 0000000..40fcd03
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/main/background.js
@@ -0,0 +1,25 @@
+// Copyright 2015 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.
+
+var requests = [];
+chrome.webRequest.onBeforeRequest.addListener(function(details) {
+  // Show the query string of the request. If this is absent for some
+  // reason (probably an error), show the full URL.
+  requests.push(new URL(details.url).search || details.url);
+}, {
+  types: ['xmlhttprequest'],
+  urls: ['*://*/*']
+});
+
+chrome.test.sendMessage('web_request_status1', echoRequestStatus);
+chrome.test.sendMessage('web_request_status2', echoRequestStatus);
+
+function echoRequestStatus() {
+  if (requests.length === 0) {
+    chrome.test.sendMessage('Did not intercept any requests.');
+  } else {
+    chrome.test.sendMessage('Intercepted requests: ' + requests.join(', '));
+  }
+  requests.length = 0;
+}
diff --git a/chrome/test/data/extensions/api_test/webrequest_extensions/main/manifest.json b/chrome/test/data/extensions/api_test/webrequest_extensions/main/manifest.json
new file mode 100644
index 0000000..e102cfe
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webrequest_extensions/main/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "Eavesdropping extension",
+  "version": "1",
+  "manifest_version": 2,
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": true
+  },
+  "permissions": [
+    "webRequest",
+    "<all_urls>"
+  ]
+}
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 801e58d..04a2bed 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -416,7 +416,7 @@
                                      details.browser_context,
                                      details.extension_id,
                                      details.event_name,
-                                     0 /* embedder_process_id */,
+                                     0 /* embedder_process_id (ignored) */,
                                      0 /* web_view_instance_id */));
 }
 
@@ -445,12 +445,21 @@
     if (sub_event_name != that.sub_event_name)
       return sub_event_name < that.sub_event_name;
 
-    if (embedder_process_id != that.embedder_process_id)
-      return embedder_process_id < that.embedder_process_id;
-
     if (web_view_instance_id != that.web_view_instance_id)
       return web_view_instance_id < that.web_view_instance_id;
 
+    if (web_view_instance_id == 0) {
+      // Do not filter by process ID for non-webviews, because this comparator
+      // is also used to find and remove an event listener when an extension is
+      // unloaded. At this point, the event listener cannot be mapped back to
+      // the original process, so 0 is used instead of the actual process ID.
+      DCHECK(embedder_process_id == 0 || that.embedder_process_id == 0);
+      return false;
+    }
+
+    if (embedder_process_id != that.embedder_process_id)
+      return embedder_process_id < that.embedder_process_id;
+
     return false;
   }
 
@@ -1238,6 +1247,8 @@
     const std::string& sub_event_name,
     uint64 request_id,
     EventResponse* response) {
+  // TODO(robwu): Does this also work with webviews? operator< (used by find)
+  // takes the webview ID into account, which is not set on |listener|.
   EventListener listener;
   listener.extension_id = extension_id;
   listener.sub_event_name = sub_event_name;
@@ -1470,6 +1481,12 @@
          it->web_view_instance_id != web_view_info.instance_id))
       continue;
 
+    // Filter requests from other extensions / apps. This does not work for
+    // content scripts, or extension pages in non-extension processes.
+    if (is_request_from_extension &&
+        it->embedder_process_id != render_process_host_id)
+      continue;
+
     if (!it->filter.urls.is_empty() && !it->filter.urls.MatchesURL(url))
       continue;
     if (web_request_event_router_delegate_ &&
@@ -2190,9 +2207,7 @@
   EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(5, &web_view_instance_id));
 
   base::WeakPtr<IOThreadExtensionMessageFilter> ipc_sender = ipc_sender_weak();
-  int embedder_process_id =
-      ipc_sender.get() && web_view_instance_id > 0 ?
-          ipc_sender->render_process_id() : 0;
+  int embedder_process_id = ipc_sender ? ipc_sender->render_process_id() : 0;
 
   const Extension* extension =
       extension_info_map()->extensions().GetByID(extension_id_safe());