Bug 1311815 - Check host permissions for webRequest listeners draft 1330340-host-perms
authorTomislav Jovanovic <tomica@gmail.com>
Thu, 16 Feb 2017 14:56:35 +0100
changeset 485277 fac44a7e78518ffc5692185e37e0f95eb635d2e7
parent 485276 d280a7a149bba92ecdb59b9de0a0336c336de889
child 545978 a9bda25ca62d7f5cbe16a0857dd672d094ba71df
push id45691
push userbmo:tomica@gmail.com
push dateThu, 16 Feb 2017 14:08:59 +0000
bugs1311815
milestone54.0a1
Bug 1311815 - Check host permissions for webRequest listeners MozReview-Commit-ID: 9bcb4p7tP2u
toolkit/components/extensions/ext-webRequest.js
toolkit/components/extensions/schemas/web_request.json
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/file_image_great.png
toolkit/components/extensions/test/mochitest/file_with_images.html
toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_host_permissions.html
toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html
toolkit/modules/addons/WebRequest.jsm
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -23,31 +23,44 @@ function WebRequestEventManager(context,
   let register = (fire, filter, info) => {
     let listener = data => {
       // Prevent listening in on requests originating from system principal to
       // prevent tinkering with OCSP, app and addon updates, etc.
       if (data.isSystemPrincipal) {
         return;
       }
 
+      // Check hosts permissions for both the resource being requested,
+      const hosts = context.extension.whiteListedHosts;
+      if (!hosts.matchesIgnoringPath(NetUtil.newURI(data.url))) {
+        return;
+      }
+      // and the origin that is loading the resource.
+      const origin = data.documentUrl;
+      const own = origin && origin.startsWith(context.extension.getURL());
+      if (origin && !own && !hosts.matchesIgnoringPath(NetUtil.newURI(origin))) {
+        return;
+      }
+
       let browserData = {tabId: -1, windowId: -1};
       if (data.browser) {
         browserData = tabTracker.getBrowserData(data.browser);
       }
       if (filter.tabId != null && browserData.tabId != filter.tabId) {
         return;
       }
       if (filter.windowId != null && browserData.windowId != filter.windowId) {
         return;
       }
 
       let data2 = {
         requestId: data.requestId,
         url: data.url,
         originUrl: data.originUrl,
+        documentUrl: data.documentUrl,
         method: data.method,
         tabId: browserData.tabId,
         type: data.type,
         timeStamp: Date.now(),
         frameId: data.type == "main_frame" ? 0 : ExtensionManagement.getFrameId(data.windowId),
         parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
       };
 
--- a/toolkit/components/extensions/schemas/web_request.json
+++ b/toolkit/components/extensions/schemas/web_request.json
@@ -212,16 +212,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "requestBody": {
                 "type": "object",
                 "optional": true,
                 "description": "Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'.",
                 "properties": {
                   "error": {"type": "string", "optional": true, "description": "Errors when obtaining request body data."},
                   "formData": {
                     "type": "object",
@@ -278,16 +280,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."}
             }
           }
         ],
         "extraParameters": [
@@ -321,16 +325,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that have been sent out with this request."}
             }
           }
         ],
         "extraParameters": [
@@ -359,16 +365,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."},
               "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}
              }
           }
@@ -404,16 +412,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "scheme": {"type": "string", "description": "The authentication scheme, e.g. Basic or Digest."},
               "realm": {"type": "string", "description": "The authentication realm provided by the server, if there is one.", "optional": true},
               "challenger": {"type": "object", "description": "The server requesting authentication.", "properties": {"host": {"type": "string"}, "port": {"type": "integer"}}},
               "isProxy": {"type": "boolean", "description": "True for Proxy-Authenticate, false for WWW-Authenticate."},
               "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
@@ -461,16 +471,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
               "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
               "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
@@ -503,16 +515,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
               "redirectUrl": {"type": "string", "description": "The new URL."},
               "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this redirect."},
@@ -546,16 +560,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
               "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
               "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
@@ -588,16 +604,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
+              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "error": {"type": "string", "description": "The error description. This string is <em>not</em> guaranteed to remain backwards compatible between releases. You must not parse and act based upon its content."}
             }
           }
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -1,15 +1,17 @@
 [DEFAULT]
 support-files =
   chrome_head.js
   chrome_cleanup_script.js
   head.js
   head_cookies.js
+  file_image_great.png
   file_sample.html
+  file_with_images.html
   webrequest_chromeworker.js
   webrequest_test.jsm
   oauth.html
   redirect_auto.sjs
 tags = webextensions
 
 [test_chrome_ext_background_debug_global.html]
 skip-if = (os == 'android') # android doesn't have devtools
@@ -29,9 +31,10 @@ skip-if = os != "mac" && os != "linux"
 [test_ext_jsversion.html]
 [test_ext_schema.html]
 [test_chrome_ext_storage_cleanup.html]
 [test_chrome_ext_idle.html]
 [test_chrome_ext_identity.html]
 skip-if = os == 'android' # unsupported.
 [test_chrome_ext_downloads_saveAs.html]
 [test_chrome_ext_webrequest_background_events.html]
+[test_chrome_ext_webrequest_host_permissions.html]
 [test_chrome_ext_trackingprotection.html]
new file mode 100644
index 0000000000000000000000000000000000000000..769c636340e11f9d2a0b7eb6a84d574dd9563f0c
GIT binary patch
literal 580
zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)=5M`RCwBA
z{Qv(y10?_;fS8au@87>?@9gaSt*os4-<6T^zln*-e<1(Y>eZ{a-@A8D7MlS80mJ}u
z0SKQtbH>fs*!aH-1H=C_K)ecw?*efe5W7I}s#U9Y!PLVrKmdV>nKNfT1{(H16si$K
zmjm&CV+al6D=8`c7X*o+82}JK4A3z64>JJB_`e(E3S)?2<zN>X{|^lf1sei(+1<Me
zFarPr2&}oIqvIC?)OL^o?-&p^!wh-{#k-;2f_VoZfS{%bLi}zFF<>UtAY{W<LBj?n
zz6$CsfB=HI;P*^44eyZn#$YcBg1rF~2L~`P&;bI75#$0;v@zVf$It;Z4d`qJS0Dzu
zh@l)BQx!mb7Roj*FK2kaXAgtR*|Q9LfP8=e0=od{pY0$UI-sVXf!f>w#jt?wfcn3@
zy!=0d5|Evi_8%aC7~Z{m#}1AKK|~<_M{=g1px}QcP=ErR57Iaj7$e5OumVLr$n^jL
z1djz!sJcLHd57c@W2lWFi_p^m2m=HVoB>kgf@C|$pqa*ykjAAMgaHBwo)>`0c=vmt
z+g3yQpuk*x7A(#H^u|wInF%0(FiZq_r2`t3pnwGh6fWCA7$ATcDb3CR0R{jJCzQv)
SYsoAC0000<MNUMnLSTYrIq9PS
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/file_with_images.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<body>
+  <img src="https://example.com/tests/toolkit/components/extensions/test/mochitest/file_image_good.png">
+  <img src="http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest/file_image_great.png">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_host_permissions.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<html>
+<head>
+  <title>Test webRequest checks host permissions</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script src="head.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+
+<script type="text/javascript">
+"use strict";
+
+add_task(function* test_webRequest_host_permissions() {
+  function background() {
+    function png(details) {
+      browser.test.sendMessage("png", details.url);
+    }
+    browser.webRequest.onBeforeRequest.addListener(png, {urls: ["*://*/*.png"]});
+    browser.test.sendMessage("ready");
+  }
+
+  const all = ExtensionTestUtils.loadExtension({background, manifest: {permissions: ["webRequest", "<all_urls>"]}});
+  const example = ExtensionTestUtils.loadExtension({background, manifest: {permissions: ["webRequest", "https://example.com/"]}});
+  const mochi_test = ExtensionTestUtils.loadExtension({background, manifest: {permissions: ["webRequest", "http://mochi.test/"]}});
+
+  yield all.startup();
+  yield example.startup();
+  yield mochi_test.startup();
+
+  yield all.awaitMessage("ready");
+  yield example.awaitMessage("ready");
+  yield mochi_test.awaitMessage("ready");
+
+  const win1 = window.open("https://example.com/chrome/toolkit/components/extensions/test/mochitest/file_with_images.html");
+  ok((yield all.awaitMessage("png")).endsWith("good.png"), "<all_urls> permission gets to see good.png");
+  ok((yield example.awaitMessage("png")).endsWith("good.png"), "example permission sees same-origin example.com image");
+  ok((yield all.awaitMessage("png")).endsWith("great.png"), "<all_urls> permission also sees great.png");
+
+  // Clear the in-memory image cache, it can prevent listeners from receiving events.
+  const imgTools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(SpecialPowers.Ci.imgITools);
+  imgTools.getImgCacheForDocument(win1.document).clearCache(false);
+  win1.close();
+
+  const win2 = window.open("http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest/file_with_images.html");
+  ok((yield all.awaitMessage("png")).endsWith("good.png"), "<all_urls> permission gets to see good.png");
+  ok((yield mochi_test.awaitMessage("png")).endsWith("great.png"), "mochi.test permission sees same-origin mochi.test image");
+  ok((yield all.awaitMessage("png")).endsWith("great.png"), "<all_urls> permission also sees great.png");
+  win2.close();
+
+  yield all.unload();
+  yield example.unload();
+  yield mochi_test.unload();
+});
+
+</script>
+</body>
+</html>
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html
@@ -14,16 +14,17 @@
 "use strict";
 
 add_task(function* test_suspend() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: [
         "webRequest",
         "webRequestBlocking",
+        "http://mochi.test/",
       ],
     },
 
     background() {
       browser.webRequest.onBeforeSendHeaders.addListener(
         details => {
           // Make sure that returning undefined or a promise that resolves to
           // undefined does not break later handlers.
@@ -86,16 +87,18 @@ add_task(function* test_error_resume() {
     Services.obs.addObserver(observer, "http-on-modify-request", false);
   });
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: [
         "webRequest",
         "webRequestBlocking",
+        "http://example.com/",
+        "http://mochi.test/",
       ],
     },
 
     background() {
       browser.webRequest.onBeforeSendHeaders.addListener(
         details => {
           browser.test.log(`onBeforeSendHeaders({url: ${details.url}})`);
 
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -750,16 +750,20 @@ HttpObserverManager = {
       parentWindowId: 0,
     };
 
     if (loadInfo) {
       let originPrincipal = loadInfo.triggeringPrincipal;
       if (originPrincipal.URI) {
         data.originUrl = originPrincipal.URI.spec;
       }
+      let docPrincipal = loadInfo.loadingPrincipal;
+      if (docPrincipal && docPrincipal.URI) {
+        data.documentUrl = docPrincipal.URI.spec;
+      }
 
       // If there is no loadingPrincipal, check that the request is not going to
       // inherit a system principal.  triggeringPrincipal is the context that
       // initiated the load, but is not necessarily the principal that the
       // request results in, only rely on that if no other principal is available.
       let {isSystemPrincipal} = Services.scriptSecurityManager;
       let isTopLevel = !loadInfo.loadingPrincipal && !!data.browser;
       data.isSystemPrincipal = !isTopLevel &&