Bug 1514547 - Timing token to allow external protocol URLs are blocked in iframes without user-interaction, r=smaug

This commit is contained in:
Andrea Marchesini 2019-01-24 20:05:03 +01:00
parent 0216704229
commit 89a0cf9c7e
8 changed files with 96 additions and 2 deletions

View File

@ -9654,6 +9654,9 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
// events
if (PopupBlocker::GetPopupControlState() <= PopupBlocker::openBlocked) {
popupBlocked = !PopupBlocker::TryUsePopupOpeningToken();
} else if (mIsActive &&
PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe()) {
popupBlocked = false;
} else {
nsCOMPtr<nsINode> loadingNode =
mScriptGlobal->AsOuter()->GetFrameElementInternal();

View File

@ -800,6 +800,17 @@ constexpr auto kSkipSelfHosted = JS::SavedFrameSelfHosted::Exclude;
return PopupBlocker::IsPopupOpeningTokenUnused();
}
/* static */ double ChromeUtils::LastExternalProtocolIframeAllowed(
GlobalObject& aGlobal) {
TimeStamp when = PopupBlocker::WhenLastExternalProtocolIframeAllowed();
if (when.IsNull()) {
return 0;
}
TimeDuration duration = TimeStamp::Now() - when;
return duration.ToMilliseconds();
}
/* static */ void ChromeUtils::RegisterWindowActor(
const GlobalObject& aGlobal, const nsAString& aName,
const WindowActorOptions& aOptions, ErrorResult& aRv) {

View File

@ -178,6 +178,8 @@ class ChromeUtils {
static bool IsPopupTokenUnused(GlobalObject& aGlobal);
static double LastExternalProtocolIframeAllowed(GlobalObject& aGlobal);
static void RegisterWindowActor(const GlobalObject& aGlobal,
const nsAString& aName,
const WindowActorOptions& aOptions,

View File

@ -7,7 +7,9 @@
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TimeStamp.h"
#include "nsXULPopupManager.h"
#include "nsIPermissionManager.h"
@ -22,6 +24,8 @@ static PopupBlocker::PopupControlState sPopupControlState =
PopupBlocker::openAbused;
static uint32_t sPopupStatePusherCount = 0;
static TimeStamp sLastAllowedExternalProtocolIFrameTimeStamp;
// This token is by default set to false. When a popup/filePicker is shown, it
// is set to true.
static bool sUnusedPopupToken = false;
@ -379,6 +383,27 @@ PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState(
Preferences::UnregisterCallback(OnPrefChange, "dom.popup_allowed_events");
}
/* static */ bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() {
TimeStamp now = TimeStamp::Now();
if (sLastAllowedExternalProtocolIFrameTimeStamp.IsNull()) {
sLastAllowedExternalProtocolIFrameTimeStamp = now;
return true;
}
if ((now - sLastAllowedExternalProtocolIFrameTimeStamp).ToSeconds() <
(StaticPrefs::dom_delay_block_external_protocol_in_iframes())) {
return false;
}
sLastAllowedExternalProtocolIFrameTimeStamp = now;
return true;
}
/* static */ TimeStamp PopupBlocker::WhenLastExternalProtocolIframeAllowed() {
return sLastAllowedExternalProtocolIFrameTimeStamp;
}
} // namespace dom
} // namespace mozilla

View File

@ -53,6 +53,12 @@ class PopupBlocker final {
static PopupBlocker::PopupControlState GetEventPopupControlState(
WidgetEvent* aEvent, Event* aDOMEvent = nullptr);
// Returns if a external protocol iframe is allowed.
static bool ConsumeTimerTokenForExternalProtocolIframe();
// Returns when the last external protocol iframe has been allowed.
static TimeStamp WhenLastExternalProtocolIframeAllowed();
static void Initialize();
static void Shutdown();
};

View File

@ -386,6 +386,12 @@ partial namespace ChromeUtils {
[ChromeOnly]
boolean isPopupTokenUnused();
/**
* Milliseconds from the last iframe loading an external protocol.
*/
[ChromeOnly]
double lastExternalProtocolIframeAllowed();
[ChromeOnly, Throws]
void registerWindowActor(DOMString aName, WindowActorOptions aOptions);
};

View File

@ -10,7 +10,25 @@
<div id='foo'><a href='#'>Click here to test this issue</a></div>
<script>
function next() {
function test_noUserInteraction() {
is(ChromeUtils.getPopupControlState(), "openAbused", "No user-interaction means: abuse state");
ok(!ChromeUtils.isPopupTokenUnused(), "Popup token has not been used yet");
is(ChromeUtils.lastExternalProtocolIframeAllowed(), 0, "No iframe loaded before this test!");
for (let i = 0; i < 10; ++i) {
let ifr = document.createElement('iframe');
ifr.src = "foo+bar:all_good";
document.body.appendChild(ifr);
is(ChromeUtils.getPopupControlState(), "openAbused", "No user-interaction means: abuse state");
ok(!ChromeUtils.isPopupTokenUnused(), "Popup token has been used!");
ok(ChromeUtils.lastExternalProtocolIframeAllowed() != 0, "We have 1 iframe loaded");
}
next();
}
function test_userInteraction() {
let foo = document.getElementById('foo');
foo.addEventListener('click', _ => {
is(ChromeUtils.getPopupControlState(), "openAllowed", "Click events allow popups");
@ -25,7 +43,7 @@ function next() {
ok(ChromeUtils.isPopupTokenUnused(), "Popup token has been used!");
}
SimpleTest.finish();
next();
}, {once: true});
@ -34,6 +52,21 @@ function next() {
}, 0);
}
let tests = [
test_noUserInteraction,
test_userInteraction,
];
function next() {
if (tests.length == 0) {
SimpleTest.finish();
return;
}
let test = tests.shift();
SimpleTest.executeSoon(test);
}
SpecialPowers.pushPrefEnv({'set': [
['dom.block_external_protocol_in_iframes', true],
]}, next);

View File

@ -469,6 +469,14 @@ VARCACHE_PREF(
)
#undef PREF_VALUE
// Any how many seconds we allow external protocol URLs in iframe when not in
// single events
VARCACHE_PREF(
"dom.delay.block_external_protocol_in_iframes",
dom_delay_block_external_protocol_in_iframes,
uint32_t, 10 // in seconds
)
// Block multiple window.open() per single event.
VARCACHE_PREF(
"dom.block_multiple_popups",