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::openAbused
;
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 abuse
= PopupBlocker::openBlocked
;
162 switch (aEvent
->mMessage
) {
164 if (PopupAllowedForEvent("select")) {
165 abuse
= PopupBlocker::openControlled
;
169 if (PopupAllowedForEvent("change")) {
170 abuse
= PopupBlocker::openControlled
;
178 case eEditorInputEventClass
:
179 // For this following event only allow popups if it's triggered
180 // while handling user input. See
181 // UserActivation::IsUserInteractionEvent() for details.
182 if (UserActivation::IsHandlingUserInput()) {
183 abuse
= PopupBlocker::openBlocked
;
184 switch (aEvent
->mMessage
) {
186 if (PopupAllowedForEvent("input")) {
187 abuse
= PopupBlocker::openControlled
;
195 case eInputEventClass
:
196 // For this following event only allow popups if it's triggered
197 // while handling user input. See
198 // UserActivation::IsUserInteractionEvent() for details.
199 if (UserActivation::IsHandlingUserInput()) {
200 abuse
= PopupBlocker::openBlocked
;
201 switch (aEvent
->mMessage
) {
203 if (PopupAllowedForEvent("change")) {
204 abuse
= PopupBlocker::openControlled
;
208 abuse
= PopupBlocker::openControlled
;
215 case eKeyboardEventClass
:
216 if (aEvent
->IsTrusted()) {
217 abuse
= PopupBlocker::openBlocked
;
218 uint32_t key
= aEvent
->AsKeyboardEvent()->mKeyCode
;
219 switch (aEvent
->mMessage
) {
221 // return key on focused button. see note at eMouseClick.
222 if (key
== NS_VK_RETURN
) {
223 abuse
= PopupBlocker::openAllowed
;
224 } else if (PopupAllowedForEvent("keypress")) {
225 abuse
= PopupBlocker::openControlled
;
229 // space key on focused button. see note at eMouseClick.
230 if (key
== NS_VK_SPACE
) {
231 abuse
= PopupBlocker::openAllowed
;
232 } else if (PopupAllowedForEvent("keyup")) {
233 abuse
= PopupBlocker::openControlled
;
237 if (PopupAllowedForEvent("keydown")) {
238 abuse
= PopupBlocker::openControlled
;
246 case eTouchEventClass
:
247 if (aEvent
->IsTrusted()) {
248 abuse
= PopupBlocker::openBlocked
;
249 switch (aEvent
->mMessage
) {
251 if (PopupAllowedForEvent("touchstart")) {
252 abuse
= PopupBlocker::openControlled
;
256 if (PopupAllowedForEvent("touchend")) {
257 abuse
= PopupBlocker::openControlled
;
265 case eMouseEventClass
:
266 if (aEvent
->IsTrusted()) {
267 // Let's ignore MouseButton::eSecondary because that is handled as
269 if (aEvent
->AsMouseEvent()->mButton
== MouseButton::ePrimary
||
270 aEvent
->AsMouseEvent()->mButton
== MouseButton::eMiddle
) {
271 abuse
= PopupBlocker::openBlocked
;
272 switch (aEvent
->mMessage
) {
274 if (PopupAllowedForEvent("mouseup")) {
275 abuse
= PopupBlocker::openControlled
;
279 if (PopupAllowedForEvent("mousedown")) {
280 abuse
= PopupBlocker::openControlled
;
284 /* Click events get special treatment because of their
285 historical status as a more legitimate event handler. If
286 click popups are enabled in the prefs, clear the popup
287 status completely. */
288 if (PopupAllowedForEvent("click")) {
289 abuse
= PopupBlocker::openAllowed
;
292 case eMouseDoubleClick
:
293 if (PopupAllowedForEvent("dblclick")) {
294 abuse
= PopupBlocker::openControlled
;
300 } else if (aEvent
->mMessage
== eMouseAuxClick
) {
302 // There's not a strong reason to ignore other events (eg eMouseUp)
303 // for non-primary clicks as far as we know, so we could add them if
304 // it becomes a compat issue
305 if (PopupAllowedForEvent("auxclick")) {
306 abuse
= PopupBlocker::openControlled
;
308 abuse
= PopupBlocker::openBlocked
;
312 switch (aEvent
->mMessage
) {
314 if (PopupAllowedForEvent("contextmenu")) {
315 abuse
= PopupBlocker::openControlled
;
317 abuse
= PopupBlocker::openBlocked
;
325 case ePointerEventClass
:
326 if (aEvent
->IsTrusted() &&
327 (aEvent
->AsPointerEvent()->mButton
== MouseButton::ePrimary
||
328 aEvent
->AsPointerEvent()->mButton
== MouseButton::eMiddle
)) {
329 switch (aEvent
->mMessage
) {
331 if (PopupAllowedForEvent("pointerup")) {
332 abuse
= PopupBlocker::openControlled
;
336 if (PopupAllowedForEvent("pointerdown")) {
337 abuse
= PopupBlocker::openControlled
;
345 case eFormEventClass
:
346 // For these following events only allow popups if they're
347 // triggered while handling user input. See
348 // UserActivation::IsUserInteractionEvent() for details.
349 if (UserActivation::IsHandlingUserInput()) {
350 abuse
= PopupBlocker::openBlocked
;
351 switch (aEvent
->mMessage
) {
353 if (PopupAllowedForEvent("submit")) {
354 abuse
= PopupBlocker::openControlled
;
358 if (PopupAllowedForEvent("reset")) {
359 abuse
= PopupBlocker::openControlled
;
375 void PopupBlocker::Initialize() {
376 DebugOnly
<nsresult
> rv
=
377 Preferences::RegisterCallback(OnPrefChange
, "dom.popup_allowed_events");
378 MOZ_ASSERT(NS_SUCCEEDED(rv
),
379 "Failed to observe \"dom.popup_allowed_events\"");
383 void PopupBlocker::Shutdown() {
384 MOZ_ASSERT(sOpenPopupSpamCount
== 0);
386 if (sPopupAllowedEvents
) {
387 free(sPopupAllowedEvents
);
390 Preferences::UnregisterCallback(OnPrefChange
, "dom.popup_allowed_events");
394 bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() {
395 if (!StaticPrefs::dom_delay_block_external_protocol_in_iframes_enabled()) {
399 TimeStamp now
= TimeStamp::Now();
401 if (sLastAllowedExternalProtocolIFrameTimeStamp
.IsNull()) {
402 sLastAllowedExternalProtocolIFrameTimeStamp
= now
;
406 if ((now
- sLastAllowedExternalProtocolIFrameTimeStamp
).ToSeconds() <
407 StaticPrefs::dom_delay_block_external_protocol_in_iframes()) {
411 sLastAllowedExternalProtocolIFrameTimeStamp
= now
;
416 TimeStamp
PopupBlocker::WhenLastExternalProtocolIframeAllowed() {
417 return sLastAllowedExternalProtocolIFrameTimeStamp
;
421 void PopupBlocker::ResetLastExternalProtocolIframeAllowed() {
422 sLastAllowedExternalProtocolIFrameTimeStamp
= TimeStamp();
426 void PopupBlocker::RegisterOpenPopupSpam() { sOpenPopupSpamCount
++; }
429 void PopupBlocker::UnregisterOpenPopupSpam() {
430 MOZ_ASSERT(sOpenPopupSpamCount
);
431 sOpenPopupSpamCount
--;
435 uint32_t PopupBlocker::GetOpenPopupSpamCount() { return sOpenPopupSpamCount
; }
437 } // namespace mozilla::dom
439 AutoPopupStatePusherInternal::AutoPopupStatePusherInternal(
440 mozilla::dom::PopupBlocker::PopupControlState aState
, bool aForce
)
442 mozilla::dom::PopupBlocker::PushPopupControlState(aState
, aForce
)) {
443 mozilla::dom::PopupBlocker::PopupStatePusherCreated();
446 AutoPopupStatePusherInternal::~AutoPopupStatePusherInternal() {
447 mozilla::dom::PopupBlocker::PopPopupControlState(mOldState
);
448 mozilla::dom::PopupBlocker::PopupStatePusherDestroyed();