forked from mirrors/gecko-dev
Currently the state could be openAbused or openBlocked, the reason for the difference is for bug 918780 where we would like to fix a issue of opening input dialog might trigger popup blocker if allowed list is empty. But after bug 1678389, opening input dialog now checks the transient user activation instead, we don't need this difference anymore. Differential Revision: https://phabricator.services.mozilla.com/D127158
438 lines
13 KiB
C++
438 lines
13 KiB
C++
/* -*- 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();
|
|
}
|