From 89a0cf9c7ef1d31a6b5792b0f725977823b88ab3 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Thu, 24 Jan 2019 20:05:03 +0100 Subject: [PATCH] Bug 1514547 - Timing token to allow external protocol URLs are blocked in iframes without user-interaction, r=smaug --- docshell/base/nsDocShell.cpp | 3 ++ dom/base/ChromeUtils.cpp | 11 ++++++ dom/base/ChromeUtils.h | 2 + dom/base/PopupBlocker.cpp | 25 +++++++++++++ dom/base/PopupBlocker.h | 6 +++ dom/chrome-webidl/ChromeUtils.webidl | 6 +++ .../test/test_external_protocol_iframe.html | 37 ++++++++++++++++++- modules/libpref/init/StaticPrefList.h | 8 ++++ 8 files changed, 96 insertions(+), 2 deletions(-) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 4f73b0b7c813..7bac1c734cae 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -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 loadingNode = mScriptGlobal->AsOuter()->GetFrameElementInternal(); diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index 0c01dfbe21f7..46c7fc30377c 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -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) { diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index ae96b55bd65e..f112c425a7c5 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -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, diff --git a/dom/base/PopupBlocker.cpp b/dom/base/PopupBlocker.cpp index a18cfbbe3a2d..7a0bcfd3ed0e 100644 --- a/dom/base/PopupBlocker.cpp +++ b/dom/base/PopupBlocker.cpp @@ -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 diff --git a/dom/base/PopupBlocker.h b/dom/base/PopupBlocker.h index 0db91ddff9e1..1e7e486b35d5 100644 --- a/dom/base/PopupBlocker.h +++ b/dom/base/PopupBlocker.h @@ -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(); }; diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl index 83957632ba6d..5c5a3de31811 100644 --- a/dom/chrome-webidl/ChromeUtils.webidl +++ b/dom/chrome-webidl/ChromeUtils.webidl @@ -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); }; diff --git a/dom/html/test/test_external_protocol_iframe.html b/dom/html/test/test_external_protocol_iframe.html index 8c67edc792b7..b2daf5283c92 100644 --- a/dom/html/test/test_external_protocol_iframe.html +++ b/dom/html/test/test_external_protocol_iframe.html @@ -10,7 +10,25 @@