Bug 1305237 Expose frameAncestors to webextensions
MozReview-Commit-ID: D5L6pJA1rT5
--- 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;
}