mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1680721 - Grant a single iframe without user interaction to top windows. r=smaug,johannh
Only for top windows because for nested iframes they could get around this without being noticed by reloading themselves which is not great. Differential Revision: https://phabricator.services.mozilla.com/D98775
This commit is contained in:
parent
fe574be463
commit
4fd5d13610
@ -94,6 +94,10 @@ WindowContext* WindowContext::TopWindowContext() {
|
||||
|
||||
bool WindowContext::IsTop() const { return mBrowsingContext->IsTop(); }
|
||||
|
||||
bool WindowContext::SameOriginWithTop() const {
|
||||
return mBrowsingContext->SameOriginWithTop();
|
||||
}
|
||||
|
||||
nsIGlobalObject* WindowContext::GetParentObject() const {
|
||||
return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
|
||||
}
|
||||
|
@ -112,6 +112,8 @@ class WindowContext : public nsISupports, public nsWrapperCache {
|
||||
WindowContext* GetParentWindowContext();
|
||||
WindowContext* TopWindowContext();
|
||||
|
||||
bool SameOriginWithTop() const;
|
||||
|
||||
bool IsTop() const;
|
||||
|
||||
Span<RefPtr<BrowsingContext>> Children() { return mChildren; }
|
||||
|
@ -9943,14 +9943,30 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
|
||||
mBrowsingContext->GetParentWindowContext();
|
||||
MOZ_ASSERT(parentContext);
|
||||
const bool popupBlocked = [&] {
|
||||
const bool active = mBrowsingContext->GetIsActive();
|
||||
|
||||
// For same-origin-with-top windows, we grant a single free popup
|
||||
// without user activation, see bug 1680721.
|
||||
//
|
||||
// We consume the flag now even if there's no user activation.
|
||||
const bool hasFreePass = [&] {
|
||||
if (!active || !parentContext->SameOriginWithTop()) {
|
||||
return false;
|
||||
}
|
||||
nsGlobalWindowInner* win =
|
||||
parentContext->TopWindowContext()->GetInnerWindow();
|
||||
return win && win->TryOpenExternalProtocolIframe();
|
||||
}();
|
||||
|
||||
if (parentContext->ConsumeTransientUserGestureActivation()) {
|
||||
// If the user has interacted with the page, consume it.
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(emilio): Can we remove this check? It seems like what prompted
|
||||
// this code (bug 1514547) should be covered by transient user
|
||||
// activation, see bug 1514547.
|
||||
if (mBrowsingContext->GetIsActive() &&
|
||||
if (active &&
|
||||
PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe()) {
|
||||
return false;
|
||||
}
|
||||
@ -9959,6 +9975,10 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasFreePass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}();
|
||||
|
||||
|
@ -926,6 +926,7 @@ nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow,
|
||||
mWasCurrentInnerWindow(false),
|
||||
mHasSeenGamepadInput(false),
|
||||
mHintedWasLoading(false),
|
||||
mHasOpenedExternalProtocolFrame(false),
|
||||
mSuspendDepth(0),
|
||||
mFreezeDepth(0),
|
||||
#ifdef DEBUG
|
||||
|
@ -1255,9 +1255,16 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
|
||||
// Hint to the JS engine whether we are currently loading.
|
||||
void HintIsLoading(bool aIsLoading);
|
||||
|
||||
public:
|
||||
mozilla::dom::ContentMediaController* GetContentMediaController();
|
||||
|
||||
bool TryOpenExternalProtocolIframe() {
|
||||
if (mHasOpenedExternalProtocolFrame) {
|
||||
return false;
|
||||
}
|
||||
mHasOpenedExternalProtocolFrame = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<mozilla::dom::ContentMediaController> mContentMediaController;
|
||||
|
||||
@ -1325,6 +1332,10 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
|
||||
// Whether we told the JS engine that we were in pageload.
|
||||
bool mHintedWasLoading : 1;
|
||||
|
||||
// Whether this window has opened an external-protocol iframe without user
|
||||
// activation once already. Only relevant for top windows.
|
||||
bool mHasOpenedExternalProtocolFrame : 1;
|
||||
|
||||
nsCheapSet<nsUint32HashKey> mGamepadIndexSet;
|
||||
nsRefPtrHashtable<nsGenericHashKey<mozilla::dom::GamepadHandle>,
|
||||
mozilla::dom::Gamepad>
|
||||
|
@ -42,6 +42,9 @@ support-files =
|
||||
[browser_protocol_ask_dialog.js]
|
||||
support-files =
|
||||
file_nested_protocol_request.html
|
||||
[browser_first_prompt_not_blocked_without_user_interaction.js]
|
||||
support-files =
|
||||
file_external_protocol_iframe.html
|
||||
[browser_protocol_ask_dialog_permission.js]
|
||||
[browser_protocolhandler_loop.js]
|
||||
[browser_remember_download_option.js]
|
||||
|
@ -0,0 +1,70 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PATH = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"https://example.com"
|
||||
);
|
||||
|
||||
add_task(setupMailHandler);
|
||||
|
||||
add_task(async function test_open_without_user_interaction() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.disable_open_during_load", true],
|
||||
["dom.block_external_protocol_in_iframes", true],
|
||||
["dom.delay.block_external_protocol_in_iframes.enabled", false],
|
||||
],
|
||||
});
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
|
||||
let dialogWindowPromise = waitForProtocolAppChooserDialog(
|
||||
tab.linkedBrowser,
|
||||
true
|
||||
);
|
||||
|
||||
BrowserTestUtils.loadURI(
|
||||
tab.linkedBrowser,
|
||||
TEST_PATH + "file_external_protocol_iframe.html"
|
||||
);
|
||||
|
||||
let dialog = await dialogWindowPromise;
|
||||
ok(dialog, "Should show the dialog even without user interaction");
|
||||
|
||||
let dialogClosedPromise = waitForProtocolAppChooserDialog(
|
||||
tab.linkedBrowser,
|
||||
false
|
||||
);
|
||||
|
||||
// Adding another iframe without user interaction should be blocked.
|
||||
let blockedWarning = new Promise(resolve => {
|
||||
Services.console.registerListener(function onMessage(msg) {
|
||||
let { message, logLevel } = msg;
|
||||
if (logLevel != Ci.nsIConsoleMessage.warn) {
|
||||
return;
|
||||
}
|
||||
if (!message.includes("Iframe with external protocol was blocked")) {
|
||||
return;
|
||||
}
|
||||
Services.console.unregisterListener(onMessage);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
info("Adding another frame without user interaction");
|
||||
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
|
||||
let frame = content.document.createElement("iframe");
|
||||
frame.src = "mailto:foo@baz.com";
|
||||
content.document.body.appendChild(frame);
|
||||
});
|
||||
|
||||
await blockedWarning;
|
||||
|
||||
info("Removing tab to close the dialog.");
|
||||
gBrowser.removeTab(tab);
|
||||
await dialogClosedPromise;
|
||||
});
|
@ -3,15 +3,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import(
|
||||
"resource://testing-common/HandlerServiceTestUtils.jsm",
|
||||
this
|
||||
);
|
||||
|
||||
let gHandlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
|
||||
Ci.nsIHandlerService
|
||||
);
|
||||
|
||||
const TEST_PATH = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"https://example.com"
|
||||
@ -20,56 +11,7 @@ const TEST_PATH = getRootDirectory(gTestPath).replace(
|
||||
const CONTENT_HANDLING_URL =
|
||||
"chrome://mozapps/content/handling/appChooser.xhtml";
|
||||
|
||||
let gOldMailHandlers = [];
|
||||
|
||||
add_task(async function setup() {
|
||||
let mailHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
|
||||
|
||||
// Remove extant web handlers because they have icons that
|
||||
// we fetch from the web, which isn't allowed in tests.
|
||||
let handlers = mailHandlerInfo.possibleApplicationHandlers;
|
||||
for (let i = handlers.Count() - 1; i >= 0; i--) {
|
||||
try {
|
||||
let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
|
||||
gOldMailHandlers.push(handler);
|
||||
// If we get here, this is a web handler app. Remove it:
|
||||
handlers.removeElementAt(i);
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
mailHandlerInfo.alwaysAskBeforeHandling = previousHandling;
|
||||
gHandlerService.store(mailHandlerInfo);
|
||||
});
|
||||
});
|
||||
add_task(setupMailHandler);
|
||||
|
||||
/**
|
||||
* Check that if we open the protocol handler dialog from a subframe, we close
|
||||
|
@ -0,0 +1 @@
|
||||
<iframe src="mailto:foo@bar.com"></iframe>
|
@ -1,4 +1,7 @@
|
||||
var { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
|
||||
var { HandlerServiceTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/HandlerServiceTestUtils.jsm"
|
||||
);
|
||||
|
||||
var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
||||
var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
|
||||
@ -195,3 +198,53 @@ async function promiseDownloadFinished(list) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupMailHandler() {
|
||||
let mailHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
|
||||
let gOldMailHandlers = [];
|
||||
|
||||
// Remove extant web handlers because they have icons that
|
||||
// we fetch from the web, which isn't allowed in tests.
|
||||
let handlers = mailHandlerInfo.possibleApplicationHandlers;
|
||||
for (let i = handlers.Count() - 1; i >= 0; i--) {
|
||||
try {
|
||||
let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
|
||||
gOldMailHandlers.push(handler);
|
||||
// If we get here, this is a web handler app. Remove it:
|
||||
handlers.removeElementAt(i);
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
gHandlerSvc.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);
|
||||
}
|
||||
mailHandlerInfo.alwaysAskBeforeHandling = previousHandling;
|
||||
gHandlerSvc.store(mailHandlerInfo);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user