1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Components.h"
8 #include "mozilla/dom/PopupBlocker.h"
9 #include "mozilla/dom/UserActivation.h"
10 #include "mozilla/BasePrincipal.h"
11 #include "mozilla/MouseEvents.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/StaticPrefs_dom.h"
14 #include "mozilla/TextEvents.h"
15 #include "mozilla/TimeStamp.h"
16 #include "nsXULPopupManager.h"
17 #include "nsIPermissionManager.h"
19 namespace mozilla::dom
{
23 static char* sPopupAllowedEvents
;
25 static PopupBlocker::PopupControlState sPopupControlState
=
26 PopupBlocker::openAbused
;
27 static uint32_t sPopupStatePusherCount
= 0;
29 static TimeStamp sLastAllowedExternalProtocolIFrameTimeStamp
;
31 static uint32_t sOpenPopupSpamCount
= 0;
33 void PopupAllowedEventsChanged() {
34 if (sPopupAllowedEvents
) {
35 free(sPopupAllowedEvents
);
39 Preferences::GetCString("dom.popup_allowed_events", str
);
41 // We'll want to do this even if str is empty to avoid looking up
42 // this pref all the time if it's not set.
43 sPopupAllowedEvents
= ToNewCString(str
);
46 // return true if eventName is contained within events, delimited by
48 bool PopupAllowedForEvent(const char* eventName
) {
49 if (!sPopupAllowedEvents
) {
50 PopupAllowedEventsChanged();
52 if (!sPopupAllowedEvents
) {
57 nsDependentCString
events(sPopupAllowedEvents
);
59 nsCString::const_iterator start
, end
;
60 nsCString::const_iterator
startiter(events
.BeginReading(start
));
61 events
.EndReading(end
);
63 while (startiter
!= end
) {
64 nsCString::const_iterator
enditer(end
);
66 if (!FindInReadable(nsDependentCString(eventName
), startiter
, enditer
))
69 // the match is surrounded by spaces, or at a string boundary
70 if ((startiter
== start
|| *--startiter
== ' ') &&
71 (enditer
== end
|| *enditer
== ' ')) {
75 // Move on and see if there are other matches. (The delimitation
76 // requirement makes it pointless to begin the next search before
77 // the end of the invalid match just found.)
85 void OnPrefChange(const char* aPrefName
, void*) {
86 nsDependentCString
prefName(aPrefName
);
87 if (prefName
.EqualsLiteral("dom.popup_allowed_events")) {
88 PopupAllowedEventsChanged();
95 PopupBlocker::PopupControlState
PopupBlocker::PushPopupControlState(
96 PopupBlocker::PopupControlState aState
, bool aForce
) {
97 MOZ_ASSERT(NS_IsMainThread());
98 PopupBlocker::PopupControlState old
= sPopupControlState
;
99 if (aState
< old
|| aForce
) {
100 sPopupControlState
= aState
;
106 void PopupBlocker::PopPopupControlState(
107 PopupBlocker::PopupControlState aState
) {
108 MOZ_ASSERT(NS_IsMainThread());
109 sPopupControlState
= aState
;
112 /* static */ PopupBlocker::PopupControlState
113 PopupBlocker::GetPopupControlState() {
114 return sPopupControlState
;
118 uint32_t PopupBlocker::GetPopupPermission(nsIPrincipal
* aPrincipal
) {
119 uint32_t permit
= nsIPermissionManager::UNKNOWN_ACTION
;
120 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
121 components::PermissionManager::Service();
123 if (permissionManager
) {
124 permissionManager
->TestPermissionFromPrincipal(aPrincipal
, "popup"_ns
,
132 void PopupBlocker::PopupStatePusherCreated() { ++sPopupStatePusherCount
; }
135 void PopupBlocker::PopupStatePusherDestroyed() {
136 MOZ_ASSERT(sPopupStatePusherCount
);
137 --sPopupStatePusherCount
;
141 PopupBlocker::PopupControlState
PopupBlocker::GetEventPopupControlState(
142 WidgetEvent
* aEvent
, Event
* aDOMEvent
) {
143 // generally if an event handler is running, new windows are disallowed.
144 // check for exceptions:
145 PopupBlocker::PopupControlState abuse
= PopupBlocker::openBlocked
;
147 if (aDOMEvent
&& aDOMEvent
->GetWantsPopupControlCheck()) {
149 aDOMEvent
->GetType(type
);
150 if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type
).get())) {
151 return PopupBlocker::openAllowed
;
155 switch (aEvent
->mClass
) {
156 case eBasicEventClass
:
157 // For these following events only allow popups if they're
158 // triggered while handling user input. See
159 // UserActivation::IsUserInteractionEvent() for details.
160 if (UserActivation::IsHandlingUserInput()) {
161 switch (aEvent
->mMessage
) {
163 if (PopupAllowedForEvent("select")) {
164 abuse
= PopupBlocker::openControlled
;
168 if (PopupAllowedForEvent("change")) {
169 abuse
= PopupBlocker::openControlled
;
177 case eEditorInputEventClass
:
178 // For this following event only allow popups if it's triggered
179 // while handling user input. See
180 // UserActivation::IsUserInteractionEvent() for details.
181 if (UserActivation::IsHandlingUserInput()) {
182 switch (aEvent
->mMessage
) {
184 if (PopupAllowedForEvent("input")) {
185 abuse
= PopupBlocker::openControlled
;
193 case eInputEventClass
:
194 // For this following event only allow popups if it's triggered
195 // while handling user input. See
196 // UserActivation::IsUserInteractionEvent() for details.
197 if (UserActivation::IsHandlingUserInput()) {
198 switch (aEvent
->mMessage
) {
200 if (PopupAllowedForEvent("change")) {
201 abuse
= PopupBlocker::openControlled
;
205 abuse
= PopupBlocker::openControlled
;
212 case eKeyboardEventClass
:
213 if (aEvent
->IsTrusted()) {
214 uint32_t key
= aEvent
->AsKeyboardEvent()->mKeyCode
;
215 switch (aEvent
->mMessage
) {
217 // return key on focused button. see note at eMouseClick.
218 if (key
== NS_VK_RETURN
) {
219 abuse
= PopupBlocker::openAllowed
;
220 } else if (PopupAllowedForEvent("keypress")) {
221 abuse
= PopupBlocker::openControlled
;
225 // space key on focused button. see note at eMouseClick.
226 if (key
== NS_VK_SPACE
) {
227 abuse
= PopupBlocker::openAllowed
;
228 } else if (PopupAllowedForEvent("keyup")) {
229 abuse
= PopupBlocker::openControlled
;
233 if (PopupAllowedForEvent("keydown")) {
234 abuse
= PopupBlocker::openControlled
;
242 case eTouchEventClass
:
243 if (aEvent
->IsTrusted()) {
244 switch (aEvent
->mMessage
) {
246 if (PopupAllowedForEvent("touchstart")) {
247 abuse
= PopupBlocker::openControlled
;
251 if (PopupAllowedForEvent("touchend")) {
252 abuse
= PopupBlocker::openControlled
;
260 case eMouseEventClass
:
261 if (aEvent
->IsTrusted()) {
262 // Let's ignore MouseButton::eSecondary because that is handled as
264 if (aEvent
->AsMouseEvent()->mButton
== MouseButton::ePrimary
||
265 aEvent
->AsMouseEvent()->mButton
== MouseButton::eMiddle
) {
266 switch (aEvent
->mMessage
) {
268 if (PopupAllowedForEvent("mouseup")) {
269 abuse
= PopupBlocker::openControlled
;
273 if (PopupAllowedForEvent("mousedown")) {
274 abuse
= PopupBlocker::openControlled
;
278 /* Click events get special treatment because of their
279 historical status as a more legitimate event handler. If
280 click popups are enabled in the prefs, clear the popup
281 status completely. */
282 if (PopupAllowedForEvent("click")) {
283 abuse
= PopupBlocker::openAllowed
;
286 case eMouseDoubleClick
:
287 if (PopupAllowedForEvent("dblclick")) {
288 abuse
= PopupBlocker::openControlled
;
294 } else if (aEvent
->mMessage
== eMouseAuxClick
) {
296 // There's not a strong reason to ignore other events (eg eMouseUp)
297 // for non-primary clicks as far as we know, so we could add them if
298 // it becomes a compat issue
299 if (PopupAllowedForEvent("auxclick")) {
300 abuse
= PopupBlocker::openControlled
;
304 switch (aEvent
->mMessage
) {
306 if (PopupAllowedForEvent("contextmenu")) {
307 abuse
= PopupBlocker::openControlled
;
315 case ePointerEventClass
:
316 if (aEvent
->IsTrusted() &&
317 (aEvent
->AsPointerEvent()->mButton
== MouseButton::ePrimary
||
318 aEvent
->AsPointerEvent()->mButton
== MouseButton::eMiddle
)) {
319 switch (aEvent
->mMessage
) {
321 if (PopupAllowedForEvent("pointerup")) {
322 abuse
= PopupBlocker::openControlled
;
326 if (PopupAllowedForEvent("pointerdown")) {
327 abuse
= PopupBlocker::openControlled
;
335 case eFormEventClass
:
336 // For these following events only allow popups if they're
337 // triggered while handling user input. See
338 // UserActivation::IsUserInteractionEvent() for details.
339 if (UserActivation::IsHandlingUserInput()) {
340 switch (aEvent
->mMessage
) {
342 if (PopupAllowedForEvent("submit")) {
343 abuse
= PopupBlocker::openControlled
;
347 if (PopupAllowedForEvent("reset")) {
348 abuse
= PopupBlocker::openControlled
;
364 void PopupBlocker::Initialize() {
365 DebugOnly
<nsresult
> rv
=
366 Preferences::RegisterCallback(OnPrefChange
, "dom.popup_allowed_events");
367 MOZ_ASSERT(NS_SUCCEEDED(rv
),
368 "Failed to observe \"dom.popup_allowed_events\"");
372 void PopupBlocker::Shutdown() {
373 MOZ_ASSERT(sOpenPopupSpamCount
== 0);
375 if (sPopupAllowedEvents
) {
376 free(sPopupAllowedEvents
);
379 Preferences::UnregisterCallback(OnPrefChange
, "dom.popup_allowed_events");
383 bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() {
384 if (!StaticPrefs::dom_delay_block_external_protocol_in_iframes_enabled()) {
388 TimeStamp now
= TimeStamp::Now();
390 if (sLastAllowedExternalProtocolIFrameTimeStamp
.IsNull()) {
391 sLastAllowedExternalProtocolIFrameTimeStamp
= now
;
395 if ((now
- sLastAllowedExternalProtocolIFrameTimeStamp
).ToSeconds() <
396 StaticPrefs::dom_delay_block_external_protocol_in_iframes()) {
400 sLastAllowedExternalProtocolIFrameTimeStamp
= now
;
405 TimeStamp
PopupBlocker::WhenLastExternalProtocolIframeAllowed() {
406 return sLastAllowedExternalProtocolIFrameTimeStamp
;
410 void PopupBlocker::ResetLastExternalProtocolIframeAllowed() {
411 sLastAllowedExternalProtocolIFrameTimeStamp
= TimeStamp();
415 void PopupBlocker::RegisterOpenPopupSpam() { sOpenPopupSpamCount
++; }
418 void PopupBlocker::UnregisterOpenPopupSpam() {
419 MOZ_ASSERT(sOpenPopupSpamCount
);
420 sOpenPopupSpamCount
--;
424 uint32_t PopupBlocker::GetOpenPopupSpamCount() { return sOpenPopupSpamCount
; }
426 } // namespace mozilla::dom
428 AutoPopupStatePusherInternal::AutoPopupStatePusherInternal(
429 mozilla::dom::PopupBlocker::PopupControlState aState
, bool aForce
)
431 mozilla::dom::PopupBlocker::PushPopupControlState(aState
, aForce
)) {
432 mozilla::dom::PopupBlocker::PopupStatePusherCreated();
435 AutoPopupStatePusherInternal::~AutoPopupStatePusherInternal() {
436 mozilla::dom::PopupBlocker::PopPopupControlState(mOldState
);
437 mozilla::dom::PopupBlocker::PopupStatePusherDestroyed();