Bug 1728955: part 3) Add logging to `nsBaseClipboard`. r=masayuki
[gecko.git] / dom / base / PopupBlocker.cpp
blob6ef9113892cb4931d0a2a6be92678e3975ff6eab
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 {
21 namespace {
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);
38 nsAutoCString str;
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
47 // spaces
48 bool PopupAllowedForEvent(const char* eventName) {
49 if (!sPopupAllowedEvents) {
50 PopupAllowedEventsChanged();
52 if (!sPopupAllowedEvents) {
53 return false;
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))
67 return false;
69 // the match is surrounded by spaces, or at a string boundary
70 if ((startiter == start || *--startiter == ' ') &&
71 (enditer == end || *enditer == ' ')) {
72 return true;
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.)
78 startiter = enditer;
81 return false;
84 // static
85 void OnPrefChange(const char* aPrefName, void*) {
86 nsDependentCString prefName(aPrefName);
87 if (prefName.EqualsLiteral("dom.popup_allowed_events")) {
88 PopupAllowedEventsChanged();
92 } // namespace
94 /* static */
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;
102 return old;
105 /* static */
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;
117 /* static */
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,
125 &permit);
128 return permit;
131 /* static */
132 void PopupBlocker::PopupStatePusherCreated() { ++sPopupStatePusherCount; }
134 /* static */
135 void PopupBlocker::PopupStatePusherDestroyed() {
136 MOZ_ASSERT(sPopupStatePusherCount);
137 --sPopupStatePusherCount;
140 // static
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()) {
148 nsAutoString type;
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) {
163 case eFormSelect:
164 if (PopupAllowedForEvent("select")) {
165 abuse = PopupBlocker::openControlled;
167 break;
168 case eFormChange:
169 if (PopupAllowedForEvent("change")) {
170 abuse = PopupBlocker::openControlled;
172 break;
173 default:
174 break;
177 break;
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) {
185 case eEditorInput:
186 if (PopupAllowedForEvent("input")) {
187 abuse = PopupBlocker::openControlled;
189 break;
190 default:
191 break;
194 break;
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) {
202 case eFormChange:
203 if (PopupAllowedForEvent("change")) {
204 abuse = PopupBlocker::openControlled;
206 break;
207 case eXULCommand:
208 abuse = PopupBlocker::openControlled;
209 break;
210 default:
211 break;
214 break;
215 case eKeyboardEventClass:
216 if (aEvent->IsTrusted()) {
217 abuse = PopupBlocker::openBlocked;
218 uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode;
219 switch (aEvent->mMessage) {
220 case eKeyPress:
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;
227 break;
228 case eKeyUp:
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;
235 break;
236 case eKeyDown:
237 if (PopupAllowedForEvent("keydown")) {
238 abuse = PopupBlocker::openControlled;
240 break;
241 default:
242 break;
245 break;
246 case eTouchEventClass:
247 if (aEvent->IsTrusted()) {
248 abuse = PopupBlocker::openBlocked;
249 switch (aEvent->mMessage) {
250 case eTouchStart:
251 if (PopupAllowedForEvent("touchstart")) {
252 abuse = PopupBlocker::openControlled;
254 break;
255 case eTouchEnd:
256 if (PopupAllowedForEvent("touchend")) {
257 abuse = PopupBlocker::openControlled;
259 break;
260 default:
261 break;
264 break;
265 case eMouseEventClass:
266 if (aEvent->IsTrusted()) {
267 // Let's ignore MouseButton::eSecondary because that is handled as
268 // context menu.
269 if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary ||
270 aEvent->AsMouseEvent()->mButton == MouseButton::eMiddle) {
271 abuse = PopupBlocker::openBlocked;
272 switch (aEvent->mMessage) {
273 case eMouseUp:
274 if (PopupAllowedForEvent("mouseup")) {
275 abuse = PopupBlocker::openControlled;
277 break;
278 case eMouseDown:
279 if (PopupAllowedForEvent("mousedown")) {
280 abuse = PopupBlocker::openControlled;
282 break;
283 case eMouseClick:
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;
291 break;
292 case eMouseDoubleClick:
293 if (PopupAllowedForEvent("dblclick")) {
294 abuse = PopupBlocker::openControlled;
296 break;
297 default:
298 break;
300 } else if (aEvent->mMessage == eMouseAuxClick) {
301 // Not eLeftButton
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;
307 } else {
308 abuse = PopupBlocker::openBlocked;
312 switch (aEvent->mMessage) {
313 case eContextMenu:
314 if (PopupAllowedForEvent("contextmenu")) {
315 abuse = PopupBlocker::openControlled;
316 } else {
317 abuse = PopupBlocker::openBlocked;
319 break;
320 default:
321 break;
324 break;
325 case ePointerEventClass:
326 if (aEvent->IsTrusted() &&
327 (aEvent->AsPointerEvent()->mButton == MouseButton::ePrimary ||
328 aEvent->AsPointerEvent()->mButton == MouseButton::eMiddle)) {
329 switch (aEvent->mMessage) {
330 case ePointerUp:
331 if (PopupAllowedForEvent("pointerup")) {
332 abuse = PopupBlocker::openControlled;
334 break;
335 case ePointerDown:
336 if (PopupAllowedForEvent("pointerdown")) {
337 abuse = PopupBlocker::openControlled;
339 break;
340 default:
341 break;
344 break;
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) {
352 case eFormSubmit:
353 if (PopupAllowedForEvent("submit")) {
354 abuse = PopupBlocker::openControlled;
356 break;
357 case eFormReset:
358 if (PopupAllowedForEvent("reset")) {
359 abuse = PopupBlocker::openControlled;
361 break;
362 default:
363 break;
366 break;
367 default:
368 break;
371 return abuse;
374 /* static */
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\"");
382 /* static */
383 void PopupBlocker::Shutdown() {
384 MOZ_ASSERT(sOpenPopupSpamCount == 0);
386 if (sPopupAllowedEvents) {
387 free(sPopupAllowedEvents);
390 Preferences::UnregisterCallback(OnPrefChange, "dom.popup_allowed_events");
393 /* static */
394 bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() {
395 if (!StaticPrefs::dom_delay_block_external_protocol_in_iframes_enabled()) {
396 return false;
399 TimeStamp now = TimeStamp::Now();
401 if (sLastAllowedExternalProtocolIFrameTimeStamp.IsNull()) {
402 sLastAllowedExternalProtocolIFrameTimeStamp = now;
403 return true;
406 if ((now - sLastAllowedExternalProtocolIFrameTimeStamp).ToSeconds() <
407 StaticPrefs::dom_delay_block_external_protocol_in_iframes()) {
408 return false;
411 sLastAllowedExternalProtocolIFrameTimeStamp = now;
412 return true;
415 /* static */
416 TimeStamp PopupBlocker::WhenLastExternalProtocolIframeAllowed() {
417 return sLastAllowedExternalProtocolIFrameTimeStamp;
420 /* static */
421 void PopupBlocker::ResetLastExternalProtocolIframeAllowed() {
422 sLastAllowedExternalProtocolIFrameTimeStamp = TimeStamp();
425 /* static */
426 void PopupBlocker::RegisterOpenPopupSpam() { sOpenPopupSpamCount++; }
428 /* static */
429 void PopupBlocker::UnregisterOpenPopupSpam() {
430 MOZ_ASSERT(sOpenPopupSpamCount);
431 sOpenPopupSpamCount--;
434 /* static */
435 uint32_t PopupBlocker::GetOpenPopupSpamCount() { return sOpenPopupSpamCount; }
437 } // namespace mozilla::dom
439 AutoPopupStatePusherInternal::AutoPopupStatePusherInternal(
440 mozilla::dom::PopupBlocker::PopupControlState aState, bool aForce)
441 : mOldState(
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();