Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / base / PopupBlocker.cpp
blob7b4112d0ea204854509eb246995fc1b17965345b
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::openBlocked;
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 switch (aEvent->mMessage) {
162 case eFormSelect:
163 if (PopupAllowedForEvent("select")) {
164 abuse = PopupBlocker::openControlled;
166 break;
167 case eFormChange:
168 if (PopupAllowedForEvent("change")) {
169 abuse = PopupBlocker::openControlled;
171 break;
172 default:
173 break;
176 break;
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) {
183 case eEditorInput:
184 if (PopupAllowedForEvent("input")) {
185 abuse = PopupBlocker::openControlled;
187 break;
188 default:
189 break;
192 break;
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) {
199 case eFormChange:
200 if (PopupAllowedForEvent("change")) {
201 abuse = PopupBlocker::openControlled;
203 break;
204 case eXULCommand:
205 abuse = PopupBlocker::openControlled;
206 break;
207 default:
208 break;
211 break;
212 case eKeyboardEventClass:
213 if (aEvent->IsTrusted()) {
214 uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode;
215 switch (aEvent->mMessage) {
216 case eKeyPress:
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;
223 break;
224 case eKeyUp:
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;
231 break;
232 case eKeyDown:
233 if (PopupAllowedForEvent("keydown")) {
234 abuse = PopupBlocker::openControlled;
236 break;
237 default:
238 break;
241 break;
242 case eTouchEventClass:
243 if (aEvent->IsTrusted()) {
244 switch (aEvent->mMessage) {
245 case eTouchStart:
246 if (PopupAllowedForEvent("touchstart")) {
247 abuse = PopupBlocker::openControlled;
249 break;
250 case eTouchEnd:
251 if (PopupAllowedForEvent("touchend")) {
252 abuse = PopupBlocker::openControlled;
254 break;
255 default:
256 break;
259 break;
260 case eMouseEventClass:
261 if (aEvent->IsTrusted()) {
262 // Let's ignore MouseButton::eSecondary because that is handled as
263 // context menu.
264 if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary ||
265 aEvent->AsMouseEvent()->mButton == MouseButton::eMiddle) {
266 switch (aEvent->mMessage) {
267 case eMouseUp:
268 if (PopupAllowedForEvent("mouseup")) {
269 abuse = PopupBlocker::openControlled;
271 break;
272 case eMouseDown:
273 if (PopupAllowedForEvent("mousedown")) {
274 abuse = PopupBlocker::openControlled;
276 break;
277 case eMouseClick:
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;
285 break;
286 case eMouseDoubleClick:
287 if (PopupAllowedForEvent("dblclick")) {
288 abuse = PopupBlocker::openControlled;
290 break;
291 default:
292 break;
294 } else if (aEvent->mMessage == eMouseAuxClick) {
295 // Not eLeftButton
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) {
305 case eContextMenu:
306 if (PopupAllowedForEvent("contextmenu")) {
307 abuse = PopupBlocker::openControlled;
309 break;
310 default:
311 break;
314 break;
315 case ePointerEventClass:
316 if (aEvent->IsTrusted() &&
317 (aEvent->AsPointerEvent()->mButton == MouseButton::ePrimary ||
318 aEvent->AsPointerEvent()->mButton == MouseButton::eMiddle)) {
319 switch (aEvent->mMessage) {
320 case ePointerUp:
321 if (PopupAllowedForEvent("pointerup")) {
322 abuse = PopupBlocker::openControlled;
324 break;
325 case ePointerDown:
326 if (PopupAllowedForEvent("pointerdown")) {
327 abuse = PopupBlocker::openControlled;
329 break;
330 default:
331 break;
334 break;
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) {
341 case eFormSubmit:
342 if (PopupAllowedForEvent("submit")) {
343 abuse = PopupBlocker::openControlled;
345 break;
346 case eFormReset:
347 if (PopupAllowedForEvent("reset")) {
348 abuse = PopupBlocker::openControlled;
350 break;
351 default:
352 break;
355 break;
356 default:
357 break;
360 return abuse;
363 /* static */
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\"");
371 /* static */
372 void PopupBlocker::Shutdown() {
373 MOZ_ASSERT(sOpenPopupSpamCount == 0);
375 if (sPopupAllowedEvents) {
376 free(sPopupAllowedEvents);
379 Preferences::UnregisterCallback(OnPrefChange, "dom.popup_allowed_events");
382 /* static */
383 bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() {
384 if (!StaticPrefs::dom_delay_block_external_protocol_in_iframes_enabled()) {
385 return false;
388 TimeStamp now = TimeStamp::Now();
390 if (sLastAllowedExternalProtocolIFrameTimeStamp.IsNull()) {
391 sLastAllowedExternalProtocolIFrameTimeStamp = now;
392 return true;
395 if ((now - sLastAllowedExternalProtocolIFrameTimeStamp).ToSeconds() <
396 StaticPrefs::dom_delay_block_external_protocol_in_iframes()) {
397 return false;
400 sLastAllowedExternalProtocolIFrameTimeStamp = now;
401 return true;
404 /* static */
405 TimeStamp PopupBlocker::WhenLastExternalProtocolIframeAllowed() {
406 return sLastAllowedExternalProtocolIFrameTimeStamp;
409 /* static */
410 void PopupBlocker::ResetLastExternalProtocolIframeAllowed() {
411 sLastAllowedExternalProtocolIFrameTimeStamp = TimeStamp();
414 /* static */
415 void PopupBlocker::RegisterOpenPopupSpam() { sOpenPopupSpamCount++; }
417 /* static */
418 void PopupBlocker::UnregisterOpenPopupSpam() {
419 MOZ_ASSERT(sOpenPopupSpamCount);
420 sOpenPopupSpamCount--;
423 /* static */
424 uint32_t PopupBlocker::GetOpenPopupSpamCount() { return sOpenPopupSpamCount; }
426 } // namespace mozilla::dom
428 AutoPopupStatePusherInternal::AutoPopupStatePusherInternal(
429 mozilla::dom::PopupBlocker::PopupControlState aState, bool aForce)
430 : mOldState(
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();