Bug 1305237 Expose frameAncestors to webextensions draft
authorevilpies@gmail.com
Wed, 16 Aug 2017 15:51:03 -0700
changeset 648521 f920cf2d2d80e8fb664d8db3f94a4bb57ce7b093
parent 648520 e691e2dc6c9cd7b63773651d3cfc5b4e1559ec72
child 648522 15280db6ea8bea1538f7d4f49c7bac77efb53f9e
push id74777
push usermixedpuppy@gmail.com
push dateThu, 17 Aug 2017 21:31:50 +0000
bugs1305237
milestone57.0a1
Bug 1305237 Expose frameAncestors to webextensions MozReview-Commit-ID: D5L6pJA1rT5
toolkit/components/extensions/ext-webRequest.js
toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html
toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html
toolkit/modules/addons/WebRequest.jsm
toolkit/modules/addons/WebRequestContent.js
toolkit/modules/tests/browser/browser_WebRequest.js
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -64,17 +64,17 @@ function WebRequestEventManager(context,
         data2.fromCache = !!data.fromCache;
       }
 
       if ("ip" in data) {
         data2.ip = data.ip;
       }
 
       let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
-                      "requestBody", "scheme", "realm", "isProxy", "challenger"];
+                      "requestBody", "scheme", "realm", "isProxy", "challenger", "frameAncestors"];
       for (let opt of optional) {
         if (opt in data) {
           data2[opt] = data[opt];
         }
       }
 
       return fire.sync(data2);
     };
--- a/toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html
+++ b/toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html
@@ -9,10 +9,11 @@
 <script>
 "use strict";
 
 let req = new XMLHttpRequest();
 req.open("GET", "/xhr_resource_2");
 req.send();
 </script>
 <img src="file_image_bad.png#2"/>
+<iframe src="data:text/plain,webRequestTest"/>
 </body>
 </html>
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html
@@ -64,39 +64,59 @@ let expected = {
     type: "sub_frame",
   },
   "file_image_bad.png#2": {
     type: "image",
   },
   "xhr_resource_2": {
     type: "xmlhttprequest",
   },
+  // Last frame tests content policy frame ancestors.
+  "plain,webRequestTest": { // filename logic in checkDetails makes this weird.
+    type: "sub_frame",
+  },
 };
 
-let subframeId, parentId;
 function checkDetails(details) {
   let url = new URL(details.url);
   let filename = url.pathname.split("/").pop();
   let expect = expected[filename];
   is(expect.type, details.type, `${details.type} type matches`);
+  if (details.parentFrameId == -1) {
+    is(details.frameAncestors, undefined, "no ancestors for main_frame requests");
+  } else if (details.parentFrameId == 0) {
+    is(details.frameAncestors.length, 1, "one ancestors for sub_frame requests");
+    is(details.frameAncestors[0].frameId, 0, "ancestor is always zero");
+  } else {
+    ok(details.frameAncestors.length > 0, "have multiple ancestors for deep subframe requests");
+    is(details.frameAncestors[0].frameId, details.parentFrameId, "first ancestor matches request.parentFrameId");
+    is(details.frameAncestors[details.frameAncestors.length - 1].frameId, 0, "last ancestor is always zero");
+  }
   if (expect.toplevel) {
-    is(0, details.frameId, "expect load at top level");
-    is(-1, details.parentFrameId, "expect top level frame to have no parent");
+    is(details.frameId, 0, "expect load at top level");
+    is(details.parentFrameId, -1, "expect top level frame to have no parent");
   } else if (details.type == "sub_frame") {
     ok(details.frameId > 0, "expect sub_frame to load into a new frame");
     if (expect.toplevelParent) {
-      is(0, details.parentFrameId, "expect sub_frame to have top level parent");
+      is(details.parentFrameId, 0, "expect sub_frame to have top level parent");
+      is(details.frameAncestors.length, 1, "one ancestor for top sub_frame request");
     } else {
       ok(details.parentFrameId > 0, "expect sub_frame to have parent");
+      ok(details.frameAncestors.length > 1, "sub_frame has ancestors");
     }
-    subframeId = details.frameId;
-    parentId = details.parentFrameId;
+    // save for a later test
+    expect.subframeId = details.frameId;
+    expect.parentId = details.parentFrameId;
   } else {
-    is(subframeId, details.frameId, "expect load in subframe");
-    is(parentId, details.parentFrameId, "expect subframe parent");
+    // get the parent frame.
+    let purl = new URL(details.documentUrl);
+    let pfilename = purl.pathname.split("/").pop();
+    let parent = expected[pfilename];
+    is(details.frameId, parent.subframeId, "expect load in subframe");
+    is(details.parentFrameId, parent.parentId, "expect subframe parent");
   }
 }
 
 add_task(async function test_webRequest_main_frame() {
   // Clear the image cache, since it gets in the way otherwise.
   let imgTools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(SpecialPowers.Ci.imgITools);
   let cache = imgTools.getImgCacheForDocument(document);
   cache.clearCache(false);
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -782,16 +782,28 @@ HttpObserverManager = {
       if (originPrincipal.URI) {
         data.originUrl = originPrincipal.URI.spec;
       }
       let docPrincipal = loadInfo.loadingPrincipal;
       if (docPrincipal && docPrincipal.URI) {
         data.documentUrl = docPrincipal.URI.spec;
       }
 
+      let frameAncestors = [];
+      let ancestors = loadInfo.getAncestors();
+      let ancestorWindowIDs = loadInfo.getAncestorsOuterWindowIDs();
+      for (let i = 0; i < ancestors.length; i++) {
+        const principal = ancestors[i];
+        const url = principal.URI ? principal.URI.spec : null;
+        frameAncestors.push({
+          url,
+          frameId: ancestorWindowIDs[i],
+        });
+      }
+
       // 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 &&
                                isSystemPrincipal(loadInfo.loadingPrincipal ||
@@ -805,27 +817,39 @@ HttpObserverManager = {
         // have a non-zero frameOuterWindowID.  For a sub_frame, outerWindowID
         // points at the frames parent.  The parent frame is the main_frame if
         // outerWindowID == parentOuterWindowID, in which case set parentWindowId
         // to zero.
         Object.assign(data, {
           windowId: loadInfo.frameOuterWindowID,
           parentWindowId: loadInfo.outerWindowID == loadInfo.parentOuterWindowID ? 0 : loadInfo.outerWindowID,
         });
+
+        frameAncestors.unshift({
+          url: data.documentUrl,
+          frameId: data.parentWindowId,
+        });
       } else if (loadInfo.outerWindowID != loadInfo.parentOuterWindowID) {
         // This is a non-frame (e.g. script, image, etc) request within a
         // sub_frame.  We have to check parentOuterWindowID against the browser
         // to see if it is the main_frame in which case the parenteWindowId
         // available to the caller must be set to zero.
         let parentMainFrame = data.browser && data.browser.outerWindowID == loadInfo.parentOuterWindowID;
         Object.assign(data, {
           windowId: loadInfo.outerWindowID,
           parentWindowId: parentMainFrame ? 0 : loadInfo.parentOuterWindowID,
         });
       }
+      // In a request in some subframe, we're not going to know the original
+      // windowId of the top level request, but it is always the last entry
+      // in the chain.  Just set it to zero here.
+      if (frameAncestors.length > 0) {
+        frameAncestors[frameAncestors.length - 1].frameId = 0;
+        data.frameAncestors = frameAncestors;
+      }
     }
 
     if (channel instanceof Ci.nsIHttpChannelInternal) {
       try {
         data.ip = channel.remoteAddress;
       } catch (e) {
         // The remoteAddress getter throws if the address is unavailable,
         // but ip is an optional property so just ignore the exception.
--- a/toolkit/modules/addons/WebRequestContent.js
+++ b/toolkit/modules/addons/WebRequestContent.js
@@ -115,16 +115,17 @@ var ContentPolicy = {
     }
 
     if (!ids.length) {
       return Ci.nsIContentPolicy.ACCEPT;
     }
 
     let windowId = 0;
     let parentWindowId = -1;
+    let frameAncestors = [];
     let mm = Services.cpmm;
 
     function getWindowId(window) {
       return window.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils)
         .outerWindowID;
     }
 
@@ -149,16 +150,32 @@ var ContentPolicy = {
           doc = node;
         }
         window = doc.defaultView;
       }
 
       windowId = getWindowId(window);
       if (window.parent !== window) {
         parentWindowId = getWindowId(window.parent);
+
+        let frame = window.parent;
+        do {
+          frameAncestors.push({
+            url: frame.document.documentURIObject.spec,
+            frameId: getWindowId(frame),
+          });
+
+          if (frame.parent === frame) {
+            // Set the last frameId to zero for top level frame.
+            frameAncestors[frameAncestors.length - 1].frameId = 0;
+            break;
+          }
+
+          frame = frame.parent;
+        } while (frame);
       }
 
       let ir = window.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIDocShell)
                      .QueryInterface(Ci.nsIInterfaceRequestor);
       try {
         // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
         mm = ir.getInterface(Ci.nsIContentFrameMessageManager);
@@ -169,16 +186,19 @@ var ContentPolicy = {
       }
     }
 
     let data = {ids,
                 url,
                 type: WebRequestCommon.typeForPolicyType(policyType),
                 windowId,
                 parentWindowId};
+    if (frameAncestors.length > 0) {
+      data.frameAncestors = frameAncestors;
+    }
     if (requestOrigin) {
       data.originUrl = requestOrigin.spec;
     }
     if (block) {
       let rval = mm.sendSyncMessage("WebRequest:ShouldLoad", data);
       if (rval.length == 1 && rval[0].cancel) {
         return Ci.nsIContentPolicy.REJECT;
       }
--- a/toolkit/modules/tests/browser/browser_WebRequest.js
+++ b/toolkit/modules/tests/browser/browser_WebRequest.js
@@ -39,16 +39,21 @@ function onBeforeRequest(details) {
     is(details.browser, expected_browser, "correct <browser> element");
     checkType(details);
 
     windowIDs.set(details.url, details.windowId);
     if (details.url.indexOf("page2") != -1) {
       let page1id = windowIDs.get(URL);
       ok(details.windowId != page1id, "sub-frame gets its own window ID");
       is(details.parentWindowId, page1id, "parent window id is correct");
+
+      is(details.frameAncestors.length, 1, "correctly has only one ancestor");
+      let ancestor = details.frameAncestors[0];
+      ok(ancestor.url.includes("page1"), "parent window url seems correct");
+      is(ancestor.windowId, page1id, "parent window id is correct");
     }
   }
   if (details.url.indexOf("_bad.") != -1) {
     return {cancel: true};
   }
   return undefined;
 }