diff options
Diffstat (limited to 'dom/base/PopupBlocker.cpp')
-rw-r--r-- | dom/base/PopupBlocker.cpp | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/dom/base/PopupBlocker.cpp b/dom/base/PopupBlocker.cpp new file mode 100644 index 0000000000..7b4112d0ea --- /dev/null +++ b/dom/base/PopupBlocker.cpp @@ -0,0 +1,438 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Components.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TimeStamp.h" +#include "nsXULPopupManager.h" +#include "nsIPermissionManager.h" + +namespace mozilla::dom { + +namespace { + +static char* sPopupAllowedEvents; + +static PopupBlocker::PopupControlState sPopupControlState = + PopupBlocker::openAbused; +static uint32_t sPopupStatePusherCount = 0; + +static TimeStamp sLastAllowedExternalProtocolIFrameTimeStamp; + +static uint32_t sOpenPopupSpamCount = 0; + +void PopupAllowedEventsChanged() { + if (sPopupAllowedEvents) { + free(sPopupAllowedEvents); + } + + nsAutoCString str; + Preferences::GetCString("dom.popup_allowed_events", str); + + // We'll want to do this even if str is empty to avoid looking up + // this pref all the time if it's not set. + sPopupAllowedEvents = ToNewCString(str); +} + +// return true if eventName is contained within events, delimited by +// spaces +bool PopupAllowedForEvent(const char* eventName) { + if (!sPopupAllowedEvents) { + PopupAllowedEventsChanged(); + + if (!sPopupAllowedEvents) { + return false; + } + } + + nsDependentCString events(sPopupAllowedEvents); + + nsCString::const_iterator start, end; + nsCString::const_iterator startiter(events.BeginReading(start)); + events.EndReading(end); + + while (startiter != end) { + nsCString::const_iterator enditer(end); + + if (!FindInReadable(nsDependentCString(eventName), startiter, enditer)) + return false; + + // the match is surrounded by spaces, or at a string boundary + if ((startiter == start || *--startiter == ' ') && + (enditer == end || *enditer == ' ')) { + return true; + } + + // Move on and see if there are other matches. (The delimitation + // requirement makes it pointless to begin the next search before + // the end of the invalid match just found.) + startiter = enditer; + } + + return false; +} + +// static +void OnPrefChange(const char* aPrefName, void*) { + nsDependentCString prefName(aPrefName); + if (prefName.EqualsLiteral("dom.popup_allowed_events")) { + PopupAllowedEventsChanged(); + } +} + +} // namespace + +/* static */ +PopupBlocker::PopupControlState PopupBlocker::PushPopupControlState( + PopupBlocker::PopupControlState aState, bool aForce) { + MOZ_ASSERT(NS_IsMainThread()); + PopupBlocker::PopupControlState old = sPopupControlState; + if (aState < old || aForce) { + sPopupControlState = aState; + } + return old; +} + +/* static */ +void PopupBlocker::PopPopupControlState( + PopupBlocker::PopupControlState aState) { + MOZ_ASSERT(NS_IsMainThread()); + sPopupControlState = aState; +} + +/* static */ PopupBlocker::PopupControlState +PopupBlocker::GetPopupControlState() { + return sPopupControlState; +} + +/* static */ +uint32_t PopupBlocker::GetPopupPermission(nsIPrincipal* aPrincipal) { + uint32_t permit = nsIPermissionManager::UNKNOWN_ACTION; + nsCOMPtr<nsIPermissionManager> permissionManager = + components::PermissionManager::Service(); + + if (permissionManager) { + permissionManager->TestPermissionFromPrincipal(aPrincipal, "popup"_ns, + &permit); + } + + return permit; +} + +/* static */ +void PopupBlocker::PopupStatePusherCreated() { ++sPopupStatePusherCount; } + +/* static */ +void PopupBlocker::PopupStatePusherDestroyed() { + MOZ_ASSERT(sPopupStatePusherCount); + --sPopupStatePusherCount; +} + +// static +PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState( + WidgetEvent* aEvent, Event* aDOMEvent) { + // generally if an event handler is running, new windows are disallowed. + // check for exceptions: + PopupBlocker::PopupControlState abuse = PopupBlocker::openBlocked; + + if (aDOMEvent && aDOMEvent->GetWantsPopupControlCheck()) { + nsAutoString type; + aDOMEvent->GetType(type); + if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) { + return PopupBlocker::openAllowed; + } + } + + switch (aEvent->mClass) { + case eBasicEventClass: + // For these following events only allow popups if they're + // triggered while handling user input. See + // UserActivation::IsUserInteractionEvent() for details. + if (UserActivation::IsHandlingUserInput()) { + switch (aEvent->mMessage) { + case eFormSelect: + if (PopupAllowedForEvent("select")) { + abuse = PopupBlocker::openControlled; + } + break; + case eFormChange: + if (PopupAllowedForEvent("change")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } + break; + case eEditorInputEventClass: + // For this following event only allow popups if it's triggered + // while handling user input. See + // UserActivation::IsUserInteractionEvent() for details. + if (UserActivation::IsHandlingUserInput()) { + switch (aEvent->mMessage) { + case eEditorInput: + if (PopupAllowedForEvent("input")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } + break; + case eInputEventClass: + // For this following event only allow popups if it's triggered + // while handling user input. See + // UserActivation::IsUserInteractionEvent() for details. + if (UserActivation::IsHandlingUserInput()) { + switch (aEvent->mMessage) { + case eFormChange: + if (PopupAllowedForEvent("change")) { + abuse = PopupBlocker::openControlled; + } + break; + case eXULCommand: + abuse = PopupBlocker::openControlled; + break; + default: + break; + } + } + break; + case eKeyboardEventClass: + if (aEvent->IsTrusted()) { + uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode; + switch (aEvent->mMessage) { + case eKeyPress: + // return key on focused button. see note at eMouseClick. + if (key == NS_VK_RETURN) { + abuse = PopupBlocker::openAllowed; + } else if (PopupAllowedForEvent("keypress")) { + abuse = PopupBlocker::openControlled; + } + break; + case eKeyUp: + // space key on focused button. see note at eMouseClick. + if (key == NS_VK_SPACE) { + abuse = PopupBlocker::openAllowed; + } else if (PopupAllowedForEvent("keyup")) { + abuse = PopupBlocker::openControlled; + } + break; + case eKeyDown: + if (PopupAllowedForEvent("keydown")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } + break; + case eTouchEventClass: + if (aEvent->IsTrusted()) { + switch (aEvent->mMessage) { + case eTouchStart: + if (PopupAllowedForEvent("touchstart")) { + abuse = PopupBlocker::openControlled; + } + break; + case eTouchEnd: + if (PopupAllowedForEvent("touchend")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } + break; + case eMouseEventClass: + if (aEvent->IsTrusted()) { + // Let's ignore MouseButton::eSecondary because that is handled as + // context menu. + if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary || + aEvent->AsMouseEvent()->mButton == MouseButton::eMiddle) { + switch (aEvent->mMessage) { + case eMouseUp: + if (PopupAllowedForEvent("mouseup")) { + abuse = PopupBlocker::openControlled; + } + break; + case eMouseDown: + if (PopupAllowedForEvent("mousedown")) { + abuse = PopupBlocker::openControlled; + } + break; + case eMouseClick: + /* Click events get special treatment because of their + historical status as a more legitimate event handler. If + click popups are enabled in the prefs, clear the popup + status completely. */ + if (PopupAllowedForEvent("click")) { + abuse = PopupBlocker::openAllowed; + } + break; + case eMouseDoubleClick: + if (PopupAllowedForEvent("dblclick")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } else if (aEvent->mMessage == eMouseAuxClick) { + // Not eLeftButton + // There's not a strong reason to ignore other events (eg eMouseUp) + // for non-primary clicks as far as we know, so we could add them if + // it becomes a compat issue + if (PopupAllowedForEvent("auxclick")) { + abuse = PopupBlocker::openControlled; + } + } + + switch (aEvent->mMessage) { + case eContextMenu: + if (PopupAllowedForEvent("contextmenu")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } + break; + case ePointerEventClass: + if (aEvent->IsTrusted() && + (aEvent->AsPointerEvent()->mButton == MouseButton::ePrimary || + aEvent->AsPointerEvent()->mButton == MouseButton::eMiddle)) { + switch (aEvent->mMessage) { + case ePointerUp: + if (PopupAllowedForEvent("pointerup")) { + abuse = PopupBlocker::openControlled; + } + break; + case ePointerDown: + if (PopupAllowedForEvent("pointerdown")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } + break; + case eFormEventClass: + // For these following events only allow popups if they're + // triggered while handling user input. See + // UserActivation::IsUserInteractionEvent() for details. + if (UserActivation::IsHandlingUserInput()) { + switch (aEvent->mMessage) { + case eFormSubmit: + if (PopupAllowedForEvent("submit")) { + abuse = PopupBlocker::openControlled; + } + break; + case eFormReset: + if (PopupAllowedForEvent("reset")) { + abuse = PopupBlocker::openControlled; + } + break; + default: + break; + } + } + break; + default: + break; + } + + return abuse; +} + +/* static */ +void PopupBlocker::Initialize() { + DebugOnly<nsresult> rv = + Preferences::RegisterCallback(OnPrefChange, "dom.popup_allowed_events"); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"dom.popup_allowed_events\""); +} + +/* static */ +void PopupBlocker::Shutdown() { + MOZ_ASSERT(sOpenPopupSpamCount == 0); + + if (sPopupAllowedEvents) { + free(sPopupAllowedEvents); + } + + Preferences::UnregisterCallback(OnPrefChange, "dom.popup_allowed_events"); +} + +/* static */ +bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() { + if (!StaticPrefs::dom_delay_block_external_protocol_in_iframes_enabled()) { + return false; + } + + 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; +} + +/* static */ +void PopupBlocker::ResetLastExternalProtocolIframeAllowed() { + sLastAllowedExternalProtocolIFrameTimeStamp = TimeStamp(); +} + +/* static */ +void PopupBlocker::RegisterOpenPopupSpam() { sOpenPopupSpamCount++; } + +/* static */ +void PopupBlocker::UnregisterOpenPopupSpam() { + MOZ_ASSERT(sOpenPopupSpamCount); + sOpenPopupSpamCount--; +} + +/* static */ +uint32_t PopupBlocker::GetOpenPopupSpamCount() { return sOpenPopupSpamCount; } + +} // namespace mozilla::dom + +AutoPopupStatePusherInternal::AutoPopupStatePusherInternal( + mozilla::dom::PopupBlocker::PopupControlState aState, bool aForce) + : mOldState( + mozilla::dom::PopupBlocker::PushPopupControlState(aState, aForce)) { + mozilla::dom::PopupBlocker::PopupStatePusherCreated(); +} + +AutoPopupStatePusherInternal::~AutoPopupStatePusherInternal() { + mozilla::dom::PopupBlocker::PopPopupControlState(mOldState); + mozilla::dom::PopupBlocker::PopupStatePusherDestroyed(); +} |