Bug 1305237 Expose frameAncestors to webextensions, r=bz,kmag
MozReview-Commit-ID: 8gvEiqJEsP3
This commit is contained in:
@@ -286,6 +286,18 @@ interface ChannelWrapper : EventTarget {
|
||||
[Cached, Pure]
|
||||
readonly attribute nsISupports? browserElement;
|
||||
|
||||
/**
|
||||
* Returns an array of objects that combine the url and frameId from the
|
||||
* ancestorPrincipals and ancestorOuterWindowIDs on loadInfo.
|
||||
* The immediate parent is the first entry, the last entry is always the top
|
||||
* level frame. It will be an empty list for toplevel window loads and
|
||||
* non-subdocument resource loads within a toplevel window. For the latter,
|
||||
* originURL will provide information on what window is doing the load. It
|
||||
* will be null if the request is not associated with a window (e.g. XHR with
|
||||
* mozBackgroundRequest = true).
|
||||
*/
|
||||
[Cached, Frozen, GetterThrows, Pure]
|
||||
readonly attribute sequence<MozFrameAncestorInfo>? frameAncestors;
|
||||
|
||||
/**
|
||||
* For HTTP requests, returns an array of request headers which will be, or
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
let req = new XMLHttpRequest();
|
||||
req.open("GET", "/xhr_sandboxed");
|
||||
req.send();
|
||||
|
||||
let sandbox = document.createElement("iframe");
|
||||
sandbox.setAttribute("sandbox", "allow-scripts");
|
||||
sandbox.setAttribute("src", "file_simple_sandboxed_subframe.html");
|
||||
document.documentElement.appendChild(sandbox);
|
||||
</script>
|
||||
<img src="file_image_great.png"/>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -18,6 +18,7 @@ sandbox.setAttribute("sandbox", "allow-scripts");
|
||||
sandbox.setAttribute("src", "file_simple_sandboxed_frame.html");
|
||||
document.documentElement.appendChild(sandbox);
|
||||
</script>
|
||||
<img src="file_image_bad.png#2"/>
|
||||
<img src="file_image_redirect.png"/>
|
||||
<iframe src="data:text/plain,webRequestTest"/>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -40,6 +40,7 @@ support-files =
|
||||
file_remote_frame.html
|
||||
file_sample.html
|
||||
file_simple_sandboxed_frame.html
|
||||
file_simple_sandboxed_subframe.html
|
||||
file_simple_xhr.html
|
||||
file_simple_xhr_frame.html
|
||||
file_simple_xhr_frame2.html
|
||||
|
||||
@@ -45,49 +45,108 @@ let expected = {
|
||||
"file_image_good.png": {
|
||||
type: "image",
|
||||
toplevel: true,
|
||||
origin: "file_simple_xhr.html",
|
||||
},
|
||||
"example.txt": {
|
||||
type: "xmlhttprequest",
|
||||
toplevel: true,
|
||||
origin: "file_simple_xhr.html",
|
||||
},
|
||||
// sub frames will have the origin and first ancestor is the
|
||||
// parent document
|
||||
"file_simple_xhr_frame.html": {
|
||||
type: "sub_frame",
|
||||
toplevelParent: true,
|
||||
origin: "file_simple_xhr.html",
|
||||
parent: "file_simple_xhr.html",
|
||||
},
|
||||
// a resource in a sub frame will have origin of the subframe,
|
||||
// but the ancestor chain starts with the parent document
|
||||
"xhr_resource": {
|
||||
type: "xmlhttprequest",
|
||||
origin: "file_simple_xhr_frame.html",
|
||||
parent: "file_simple_xhr.html",
|
||||
},
|
||||
"file_image_bad.png": {
|
||||
type: "image",
|
||||
},
|
||||
"xhr_resource": {
|
||||
type: "xmlhttprequest",
|
||||
depth: 2,
|
||||
origin: "file_simple_xhr_frame.html",
|
||||
parent: "file_simple_xhr.html",
|
||||
},
|
||||
"file_simple_xhr_frame2.html": {
|
||||
type: "sub_frame",
|
||||
depth: 2,
|
||||
origin: "file_simple_xhr_frame.html",
|
||||
parent: "file_simple_xhr_frame.html",
|
||||
},
|
||||
"file_image_bad.png#2": {
|
||||
"file_image_redirect.png": {
|
||||
type: "image",
|
||||
depth: 2,
|
||||
origin: "file_simple_xhr_frame2.html",
|
||||
parent: "file_simple_xhr_frame.html",
|
||||
},
|
||||
"xhr_resource_2": {
|
||||
type: "xmlhttprequest",
|
||||
depth: 2,
|
||||
origin: "file_simple_xhr_frame2.html",
|
||||
parent: "file_simple_xhr_frame.html",
|
||||
},
|
||||
// This is loaded in a sandbox iframe.
|
||||
// Last frame tests content policy frame ancestors.
|
||||
"webRequestTest": {
|
||||
type: "sub_frame",
|
||||
depth: 3,
|
||||
origin: "file_simple_xhr_frame2.html",
|
||||
parent: "file_simple_xhr_frame2.html",
|
||||
},
|
||||
// This is loaded in a sandbox iframe. originUrl is not availabe for that,
|
||||
// and requests within a sandboxed iframe will additionally have an empty
|
||||
// url on their immediate parent/ancestor.
|
||||
"file_simple_sandboxed_frame.html": {
|
||||
type: "sub_frame",
|
||||
depth: 3,
|
||||
parent: "file_simple_xhr_frame2.html",
|
||||
},
|
||||
"xhr_sandboxed": {
|
||||
type: "xmlhttprequest",
|
||||
sandboxed: true,
|
||||
depth: 3,
|
||||
parent: "",
|
||||
},
|
||||
"file_image_great.png": {
|
||||
type: "image",
|
||||
sandboxed: true,
|
||||
depth: 3,
|
||||
parent: "",
|
||||
},
|
||||
"file_simple_sandboxed_subframe.html": {
|
||||
type: "sub_frame",
|
||||
depth: 4,
|
||||
parent: "",
|
||||
},
|
||||
};
|
||||
|
||||
function checkDetails(details) {
|
||||
let url = new URL(details.url);
|
||||
let filename = url.pathname.split("/").pop();
|
||||
let filename = url.pathname.split(url.protocol == "data:" ? "," : "/").pop();
|
||||
let expect = expected[filename];
|
||||
is(expect.type, details.type, `${details.type} type matches`);
|
||||
if (details.parentFrameId == -1) {
|
||||
is(details.frameAncestors.length, 0, "no ancestors for main_frame requests");
|
||||
} else if (details.parentFrameId == 0) {
|
||||
is(details.frameAncestors.length, 1, "one ancestors for sub_frame requests");
|
||||
} else {
|
||||
ok(details.frameAncestors.length > 1, "have multiple ancestors for deep subframe requests");
|
||||
is(details.frameAncestors.length, expect.depth, "have multiple ancestors for deep subframe requests");
|
||||
}
|
||||
if (details.parentFrameId > -1) {
|
||||
ok(!expect.origin || details.originUrl.includes(expect.origin), "origin url is correct");
|
||||
is(details.frameAncestors[0].frameId, details.parentFrameId, "first ancestor matches request.parentFrameId");
|
||||
ok(details.frameAncestors[0].url.includes(expect.parent), "ancestor parent page correct");
|
||||
is(details.frameAncestors[details.frameAncestors.length - 1].frameId, 0, "last ancestor is always zero");
|
||||
// All our tests should be somewhere within the frame that we set topframe in the query string. That
|
||||
// frame will always be the last ancestor.
|
||||
ok(details.frameAncestors[details.frameAncestors.length - 1].url.includes("topframe=true"), "last ancestor is always topframe");
|
||||
}
|
||||
if (expect.toplevel) {
|
||||
is(details.frameId, 0, "expect load at top level");
|
||||
is(details.parentFrameId, -1, "expect top level frame to have no parent");
|
||||
@@ -95,8 +154,10 @@ function checkDetails(details) {
|
||||
ok(details.frameId > 0, "expect sub_frame to load into a new frame");
|
||||
if (expect.toplevelParent) {
|
||||
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");
|
||||
}
|
||||
expect.subframeId = details.frameId;
|
||||
expect.parentId = details.parentFrameId;
|
||||
@@ -121,7 +182,7 @@ add_task(async function test_webRequest_main_frame() {
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
|
||||
let a = addLink(`file_simple_xhr.html?nocache=${Math.random()}`);
|
||||
let a = addLink(`file_simple_xhr.html?topframe=true&nocache=${Math.random()}`);
|
||||
a.click();
|
||||
|
||||
for (let i = 0; i < Object.keys(expected).length; i++) {
|
||||
@@ -133,7 +194,6 @@ add_task(async function test_webRequest_main_frame() {
|
||||
await closed;
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -569,6 +569,60 @@ ChannelWrapper::ParentWindowId() const
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
ChannelWrapper::GetFrameAncestors(dom::Nullable<nsTArray<dom::MozFrameAncestorInfo>>& aFrameAncestors, ErrorResult& aRv) const
|
||||
{
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo();
|
||||
if (!loadInfo || WindowId(loadInfo) == 0) {
|
||||
aFrameAncestors.SetNull();
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = GetFrameAncestors(loadInfo, aFrameAncestors.SetValue());
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
ChannelWrapper::GetFrameAncestors(nsILoadInfo* aLoadInfo, nsTArray<dom::MozFrameAncestorInfo>& aFrameAncestors) const
|
||||
{
|
||||
const nsTArray<nsCOMPtr<nsIPrincipal>>& ancestorPrincipals = aLoadInfo->AncestorPrincipals();
|
||||
const nsTArray<uint64_t>& ancestorOuterWindowIDs = aLoadInfo->AncestorOuterWindowIDs();
|
||||
uint32_t size = ancestorPrincipals.Length();
|
||||
MOZ_DIAGNOSTIC_ASSERT(size == ancestorOuterWindowIDs.Length());
|
||||
if (size != ancestorOuterWindowIDs.Length()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
bool subFrame = aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_SUBDOCUMENT;
|
||||
if (!aFrameAncestors.SetCapacity(subFrame ? size : size + 1, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// The immediate parent is always the first element in the ancestor arrays, however
|
||||
// SUBDOCUMENTs do not have their immediate parent included, so we inject it here.
|
||||
// This will force wrapper.parentWindowId == wrapper.frameAncestors[0].frameId to
|
||||
// always be true. All ather requests already match this way.
|
||||
if (subFrame) {
|
||||
auto ancestor = aFrameAncestors.AppendElement();
|
||||
GetDocumentURL(ancestor->mUrl);
|
||||
ancestor->mFrameId = ParentWindowId();
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < size; ++i) {
|
||||
auto ancestor = aFrameAncestors.AppendElement();
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
MOZ_TRY(ancestorPrincipals[i]->GetURI(getter_AddRefs(uri)));
|
||||
if (!uri) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
MOZ_TRY(uri->GetSpec(ancestor->mUrl));
|
||||
ancestor->mFrameId = NormalizeWindowID(aLoadInfo, ancestorOuterWindowIDs[i]);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Response filtering
|
||||
*****************************************************************************/
|
||||
@@ -721,7 +775,7 @@ ChannelWrapper::FinalURI() const
|
||||
if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
|
||||
NS_GetFinalChannelURI(chan, getter_AddRefs(uri));
|
||||
}
|
||||
return uri.forget();;
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -190,6 +190,8 @@ public:
|
||||
|
||||
int64_t ParentWindowId() const;
|
||||
|
||||
void GetFrameAncestors(dom::Nullable<nsTArray<dom::MozFrameAncestorInfo>>& aFrameAncestors, ErrorResult& aRv) const;
|
||||
|
||||
bool IsSystemLoad() const;
|
||||
|
||||
void GetOriginURL(nsCString& aRetVal) const;
|
||||
@@ -262,6 +264,8 @@ private:
|
||||
|
||||
uint64_t WindowId(nsILoadInfo* aLoadInfo) const;
|
||||
|
||||
nsresult GetFrameAncestors(nsILoadInfo* aLoadInfo, nsTArray<dom::MozFrameAncestorInfo>& aFrameAncestors) const;
|
||||
|
||||
static uint64_t GetNextId()
|
||||
{
|
||||
static uint64_t sNextId = 1;
|
||||
|
||||
@@ -171,7 +171,7 @@ const MAYBE_CACHED_EVENTS = new Set([
|
||||
|
||||
const OPTIONAL_PROPERTIES = [
|
||||
"requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
|
||||
"requestBody", "scheme", "realm", "isProxy", "challenger", "proxyInfo", "ip",
|
||||
"requestBody", "scheme", "realm", "isProxy", "challenger", "proxyInfo", "ip", "frameAncestors",
|
||||
];
|
||||
|
||||
function serializeRequestData(eventName) {
|
||||
|
||||
@@ -118,6 +118,7 @@ var ContentPolicy = {
|
||||
|
||||
let windowId = 0;
|
||||
let parentWindowId = -1;
|
||||
let frameAncestors = [];
|
||||
let mm = Services.cpmm;
|
||||
|
||||
function getWindowId(window) {
|
||||
@@ -152,6 +153,18 @@ var ContentPolicy = {
|
||||
windowId = getWindowId(window);
|
||||
if (window.parent !== window) {
|
||||
parentWindowId = getWindowId(window.parent);
|
||||
|
||||
for (let frame = window.parent; ; frame = frame.parent) {
|
||||
frameAncestors.push({
|
||||
url: frame.document.documentURIObject.spec,
|
||||
frameId: getWindowId(frame),
|
||||
});
|
||||
if (frame === frame.parent) {
|
||||
// Set the last frameId to zero for top level frame.
|
||||
frameAncestors[frameAncestors.length - 1].frameId = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ir = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
@@ -172,6 +185,9 @@ var ContentPolicy = {
|
||||
type: WebRequestCommon.typeForPolicyType(policyType),
|
||||
windowId,
|
||||
parentWindowId};
|
||||
if (frameAncestors.length > 0) {
|
||||
data.frameAncestors = frameAncestors;
|
||||
}
|
||||
if (requestOrigin) {
|
||||
data.originUrl = requestOrigin.spec;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ support-files = file_FinderSample.html
|
||||
[browser_WebNavigation.js]
|
||||
skip-if = true # Superseded by WebExtension tests
|
||||
[browser_WebRequest.js]
|
||||
[browser_WebRequest_ancestors.js]
|
||||
[browser_WebRequest_cookies.js]
|
||||
[browser_WebRequest_filtering.js]
|
||||
[browser_PageMetadata.js]
|
||||
|
||||
@@ -44,6 +44,11 @@ function onBeforeRequest(details) {
|
||||
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.frameId, page1id, "parent window id is correct");
|
||||
}
|
||||
}
|
||||
if (details.url.indexOf("_bad.") != -1) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
|
||||
var { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.importGlobalProperties(["XMLHttpRequest"]);
|
||||
|
||||
var {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {});
|
||||
var {PromiseUtils} = Cu.import("resource://gre/modules/PromiseUtils.jsm", {});
|
||||
|
||||
add_task(async function test_ancestors_exist() {
|
||||
let deferred = PromiseUtils.defer();
|
||||
function onBeforeRequest(details) {
|
||||
info(`onBeforeRequest ${details.url}`);
|
||||
ok(typeof details.frameAncestors === "object", `ancestors exists [${typeof details.frameAncestors}]`);
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
WebRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: new MatchPatternSet(["http://mochi.test/test/*"])}, ["blocking"]);
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/test/");
|
||||
await deferred.promise;
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
|
||||
WebRequest.onBeforeRequest.removeListener(onBeforeRequest);
|
||||
});
|
||||
|
||||
add_task(async function test_ancestors_null() {
|
||||
let deferred = PromiseUtils.defer();
|
||||
function onBeforeRequest(details) {
|
||||
info(`onBeforeRequest ${details.url}`);
|
||||
ok(details.frameAncestors === undefined, "ancestors do not exist");
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
WebRequest.onBeforeRequest.addListener(onBeforeRequest, null, ["blocking"]);
|
||||
|
||||
function fetch(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.mozBackgroundRequest = true;
|
||||
xhr.open("GET", url);
|
||||
xhr.onload = () => { resolve(xhr.responseText); };
|
||||
xhr.onerror = () => { reject(xhr.status); };
|
||||
// use a different contextId to avoid auth cache.
|
||||
xhr.setOriginAttributes({userContextId: 1});
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
await fetch("http://mochi.test:8888/test/");
|
||||
await deferred.promise;
|
||||
|
||||
WebRequest.onBeforeRequest.removeListener(onBeforeRequest);
|
||||
});
|
||||
Reference in New Issue
Block a user