зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1650162 - disjoint external URI loading protection should deal with invisible iframes, r=mattwoodrow
The aim of the code we're modifying here is to block things in one browsingcontext tree from opening external links in another browsingcontext tree (and causing the external protocol dialog to show up for that tab/window) -- except if the other browsingcontext into which something is being loaded is same-origin. Unfortunately the pre-patch code assumed that it would find currentWindowGlobal objects for each browsingcontext, and it turns out that's not guaranteed, especially in the case of hidden iframes, which turn out to be quite commonly used for external protocol launches. This patch fixes this by continuing to move towards the root of the browsingcontext tree even if there's no currentWindowGlobal (though logically speaking, this should only be necessary for the first iteration of the loop, it seems easier to just always check this). It also adds a test for this behaviour working. Differential Revision: https://phabricator.services.mozilla.com/D83015
This commit is contained in:
Родитель
60239c6b26
Коммит
d409469275
|
@ -984,16 +984,25 @@ nsExternalHelperAppService::LoadURI(nsIURI* aURI,
|
|||
|
||||
// Also allow this load if the target is a toplevel BC and contains a
|
||||
// non-web-controlled about:blank document
|
||||
if (bc->IsTop() && !bc->HadOriginalOpener()) {
|
||||
if (bc->IsTop() && !bc->HadOriginalOpener() && wgp) {
|
||||
RefPtr<nsIURI> uri = wgp->GetDocumentURI();
|
||||
foundAccessibleFrame =
|
||||
uri && uri->GetSpecOrDefault().EqualsLiteral("about:blank");
|
||||
}
|
||||
|
||||
while (wgp && !foundAccessibleFrame) {
|
||||
foundAccessibleFrame =
|
||||
aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal());
|
||||
wgp = wgp->GetParentWindowContext();
|
||||
while (!foundAccessibleFrame) {
|
||||
if (wgp) {
|
||||
foundAccessibleFrame =
|
||||
aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal());
|
||||
}
|
||||
// We have to get the parent via the bc, because there may not
|
||||
// be a window global for the innermost bc; see bug 1650162.
|
||||
BrowsingContext* parent = bc->GetParent();
|
||||
if (!parent) {
|
||||
break;
|
||||
}
|
||||
bc = parent;
|
||||
wgp = parent->Canonical()->GetCurrentWindowGlobal();
|
||||
}
|
||||
if (!foundAccessibleFrame) {
|
||||
return NS_OK; // deny the load.
|
||||
|
|
|
@ -38,10 +38,30 @@ add_task(async function setup() {
|
|||
|
||||
let previousHandling = mailHandlerInfo.alwaysAskBeforeHandling;
|
||||
mailHandlerInfo.alwaysAskBeforeHandling = true;
|
||||
|
||||
// Create a dummy web mail handler so we always know the mailto: protocol.
|
||||
// Without this, the test fails on VMs without a default mailto: handler,
|
||||
// because no dialog is ever shown, as we ignore subframe navigations to
|
||||
// protocols that cannot be handled.
|
||||
let dummy = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
|
||||
Ci.nsIWebHandlerApp
|
||||
);
|
||||
dummy.name = "Handler 1";
|
||||
dummy.uriTemplate = "https://example.com/first/%s";
|
||||
mailHandlerInfo.possibleApplicationHandlers.appendElement(dummy);
|
||||
|
||||
gHandlerService.store(mailHandlerInfo);
|
||||
registerCleanupFunction(() => {
|
||||
// Re-add the original protocol handlers:
|
||||
let mailHandlers = mailHandlerInfo.possibleApplicationHandlers;
|
||||
for (let i = handlers.Count() - 1; i >= 0; i--) {
|
||||
try {
|
||||
// See if this is a web handler. If it is, it'll throw, otherwise,
|
||||
// we will remove it.
|
||||
mailHandlers.queryElementAt(i, Ci.nsIWebHandlerApp);
|
||||
mailHandlers.removeElementAt(i);
|
||||
} catch (ex) {}
|
||||
}
|
||||
for (let h of gOldMailHandlers) {
|
||||
mailHandlers.appendElement(h);
|
||||
}
|
||||
|
@ -224,3 +244,97 @@ add_task(async function test_multiple_dialogs() {
|
|||
await dialogClosedPromise;
|
||||
ok(dialog.closed, "The dialog should have been closed again.");
|
||||
});
|
||||
|
||||
/**
|
||||
* Check that navigating invisible frames to external-proto URLs
|
||||
* is handled correctly.
|
||||
*/
|
||||
add_task(async function invisible_iframes() {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"https://example.com/"
|
||||
);
|
||||
|
||||
// Ensure we notice the dialog opening:
|
||||
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], function() {
|
||||
let frame = content.document.createElement("iframe");
|
||||
frame.style.display = "none";
|
||||
frame.src = "mailto:help@example.com";
|
||||
content.document.body.append(frame);
|
||||
});
|
||||
let dialog = await dialogWindowPromise;
|
||||
|
||||
is(
|
||||
dialog.document.location.href,
|
||||
CONTENT_HANDLING_URL,
|
||||
"Dialog opens as expected for invisible iframe"
|
||||
);
|
||||
// Close the dialog:
|
||||
let dialogClosedPromise = BrowserTestUtils.domWindowClosed(dialog);
|
||||
dialog.close();
|
||||
await dialogClosedPromise;
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check that nested iframes are handled correctly.
|
||||
*/
|
||||
add_task(async function nested_iframes() {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"https://example.com/"
|
||||
);
|
||||
|
||||
// Ensure we notice the dialog opening:
|
||||
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
|
||||
let innerLoaded = BrowserTestUtils.browserLoaded(
|
||||
tab.linkedBrowser,
|
||||
true,
|
||||
"https://example.org/"
|
||||
);
|
||||
info("Constructing top frame");
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], function() {
|
||||
let frame = content.document.createElement("iframe");
|
||||
frame.src = "https://example.org/"; // cross-origin frame.
|
||||
content.document.body.prepend(frame);
|
||||
|
||||
content.eval(
|
||||
`window.addEventListener("message", e => e.source.location = "mailto:help@example.com");`
|
||||
);
|
||||
});
|
||||
|
||||
await innerLoaded;
|
||||
let parentBC = tab.linkedBrowser.browsingContext;
|
||||
|
||||
info("Creating innermost frame");
|
||||
await SpecialPowers.spawn(parentBC.children[0], [], async function() {
|
||||
let innerFrame = content.document.createElement("iframe");
|
||||
let frameLoaded = ContentTaskUtils.waitForEvent(innerFrame, "load", true);
|
||||
content.document.body.prepend(innerFrame);
|
||||
await frameLoaded;
|
||||
});
|
||||
|
||||
info("Posting event from innermost frame");
|
||||
await SpecialPowers.spawn(
|
||||
parentBC.children[0].children[0],
|
||||
[],
|
||||
async function() {
|
||||
// Top browsing context needs reference to the innermost, which is cross origin.
|
||||
content.eval("top.postMessage('hello', '*')");
|
||||
}
|
||||
);
|
||||
|
||||
let dialog = await dialogWindowPromise;
|
||||
|
||||
is(
|
||||
dialog.document.location.href,
|
||||
CONTENT_HANDLING_URL,
|
||||
"Dialog opens as expected for deeply nested cross-origin iframe"
|
||||
);
|
||||
// Close the dialog:
|
||||
let dialogClosedPromise = BrowserTestUtils.domWindowClosed(dialog);
|
||||
dialog.close();
|
||||
await dialogClosedPromise;
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче