tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PopupBlocker.cpp (13730B)


      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/. */
      6 
      7 #include "mozilla/dom/PopupBlocker.h"
      8 
      9 #include "mozilla/BasePrincipal.h"
     10 #include "mozilla/Components.h"
     11 #include "mozilla/EventForwards.h"
     12 #include "mozilla/MouseEvents.h"
     13 #include "mozilla/Preferences.h"
     14 #include "mozilla/StaticPrefs_dom.h"
     15 #include "mozilla/TextEvents.h"
     16 #include "mozilla/TimeStamp.h"
     17 #include "mozilla/dom/UserActivation.h"
     18 #include "nsIPermissionManager.h"
     19 #include "nsXULPopupManager.h"
     20 
     21 namespace mozilla::dom {
     22 
     23 namespace {
     24 
     25 static char* sPopupAllowedEvents;
     26 
     27 static PopupBlocker::PopupControlState sPopupControlState =
     28    PopupBlocker::openAbused;
     29 static uint32_t sPopupStatePusherCount = 0;
     30 
     31 static TimeStamp sLastAllowedExternalProtocolIFrameTimeStamp;
     32 
     33 static uint32_t sOpenPopupSpamCount = 0;
     34 
     35 void PopupAllowedEventsChanged() {
     36  if (sPopupAllowedEvents) {
     37    free(sPopupAllowedEvents);
     38  }
     39 
     40  nsAutoCString str;
     41  Preferences::GetCString("dom.popup_allowed_events", str);
     42 
     43  // We'll want to do this even if str is empty to avoid looking up
     44  // this pref all the time if it's not set.
     45  sPopupAllowedEvents = ToNewCString(str);
     46 }
     47 
     48 // return true if eventName is contained within events, delimited by
     49 // spaces
     50 bool PopupAllowedForEvent(const char* eventName) {
     51  if (!sPopupAllowedEvents) {
     52    PopupAllowedEventsChanged();
     53 
     54    if (!sPopupAllowedEvents) {
     55      return false;
     56    }
     57  }
     58 
     59  nsDependentCString events(sPopupAllowedEvents);
     60 
     61  nsCString::const_iterator start, end;
     62  nsCString::const_iterator startiter(events.BeginReading(start));
     63  events.EndReading(end);
     64 
     65  while (startiter != end) {
     66    nsCString::const_iterator enditer(end);
     67 
     68    if (!FindInReadable(nsDependentCString(eventName), startiter, enditer))
     69      return false;
     70 
     71    // the match is surrounded by spaces, or at a string boundary
     72    if ((startiter == start || *--startiter == ' ') &&
     73        (enditer == end || *enditer == ' ')) {
     74      return true;
     75    }
     76 
     77    // Move on and see if there are other matches. (The delimitation
     78    // requirement makes it pointless to begin the next search before
     79    // the end of the invalid match just found.)
     80    startiter = enditer;
     81  }
     82 
     83  return false;
     84 }
     85 
     86 // static
     87 void OnPrefChange(const char* aPrefName, void*) {
     88  nsDependentCString prefName(aPrefName);
     89  if (prefName.EqualsLiteral("dom.popup_allowed_events")) {
     90    PopupAllowedEventsChanged();
     91  }
     92 }
     93 
     94 }  // namespace
     95 
     96 /* static */
     97 PopupBlocker::PopupControlState PopupBlocker::PushPopupControlState(
     98    PopupBlocker::PopupControlState aState, bool aForce) {
     99  MOZ_ASSERT(NS_IsMainThread());
    100  PopupBlocker::PopupControlState old = sPopupControlState;
    101  if (aState < old || aForce) {
    102    sPopupControlState = aState;
    103  }
    104  return old;
    105 }
    106 
    107 /* static */
    108 void PopupBlocker::PopPopupControlState(
    109    PopupBlocker::PopupControlState aState) {
    110  MOZ_ASSERT(NS_IsMainThread());
    111  sPopupControlState = aState;
    112 }
    113 
    114 /* static */ PopupBlocker::PopupControlState
    115 PopupBlocker::GetPopupControlState() {
    116  return sPopupControlState;
    117 }
    118 
    119 /* static */
    120 uint32_t PopupBlocker::GetPopupPermission(nsIPrincipal* aPrincipal) {
    121  uint32_t permit = nsIPermissionManager::UNKNOWN_ACTION;
    122  nsCOMPtr<nsIPermissionManager> permissionManager =
    123      components::PermissionManager::Service();
    124 
    125  if (permissionManager) {
    126    permissionManager->TestPermissionFromPrincipal(aPrincipal, "popup"_ns,
    127                                                   &permit);
    128  }
    129 
    130  return permit;
    131 }
    132 
    133 /* static */
    134 void PopupBlocker::PopupStatePusherCreated() { ++sPopupStatePusherCount; }
    135 
    136 /* static */
    137 void PopupBlocker::PopupStatePusherDestroyed() {
    138  MOZ_ASSERT(sPopupStatePusherCount);
    139  --sPopupStatePusherCount;
    140 }
    141 
    142 // static
    143 PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState(
    144    WidgetEvent* aEvent, Event* aDOMEvent) {
    145  // generally if an event handler is running, new windows are disallowed.
    146  // check for exceptions:
    147  PopupBlocker::PopupControlState abuse = PopupBlocker::openBlocked;
    148 
    149  if (aDOMEvent && aDOMEvent->GetWantsPopupControlCheck()) {
    150    nsAutoString type;
    151    aDOMEvent->GetType(type);
    152    if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) {
    153      return PopupBlocker::openAllowed;
    154    }
    155  }
    156 
    157  switch (aEvent->mClass) {
    158    case eBasicEventClass:
    159      // For these following events only allow popups if they're
    160      // triggered while handling user input. See
    161      // UserActivation::IsUserInteractionEvent() for details.
    162      if (UserActivation::IsHandlingUserInput()) {
    163        switch (aEvent->mMessage) {
    164          case eFormSelect:
    165            if (PopupAllowedForEvent("select")) {
    166              abuse = PopupBlocker::openControlled;
    167            }
    168            break;
    169          case eFormChange:
    170            if (PopupAllowedForEvent("change")) {
    171              abuse = PopupBlocker::openControlled;
    172            }
    173            break;
    174          default:
    175            break;
    176        }
    177      }
    178      break;
    179    case eEditorInputEventClass:
    180      // For this following event only allow popups if it's triggered
    181      // while handling user input. See
    182      // UserActivation::IsUserInteractionEvent() for details.
    183      if (UserActivation::IsHandlingUserInput()) {
    184        switch (aEvent->mMessage) {
    185          case eEditorInput:
    186            if (PopupAllowedForEvent("input")) {
    187              abuse = PopupBlocker::openControlled;
    188            }
    189            break;
    190          default:
    191            break;
    192        }
    193      }
    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        switch (aEvent->mMessage) {
    201          case eFormChange:
    202            if (PopupAllowedForEvent("change")) {
    203              abuse = PopupBlocker::openControlled;
    204            }
    205            break;
    206          case eUnidentifiedEvent:
    207            if (aEvent->mSpecifiedEventType == nsGkAtoms::oncommand) {
    208              abuse = PopupBlocker::openControlled;
    209            }
    210            break;
    211          default:
    212            break;
    213        }
    214      }
    215      break;
    216    case eKeyboardEventClass:
    217      if (aEvent->IsTrusted()) {
    218        uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode;
    219        switch (aEvent->mMessage) {
    220          case eKeyPress:
    221            // return key on focused button. see note at ePointerClick.
    222            if (key == NS_VK_RETURN) {
    223              abuse = PopupBlocker::openAllowed;
    224            } else if (PopupAllowedForEvent("keypress")) {
    225              abuse = PopupBlocker::openControlled;
    226            }
    227            break;
    228          case eKeyUp:
    229            // space key on focused button. see note at ePointerClick.
    230            if (key == NS_VK_SPACE) {
    231              abuse = PopupBlocker::openAllowed;
    232            } else if (PopupAllowedForEvent("keyup")) {
    233              abuse = PopupBlocker::openControlled;
    234            }
    235            break;
    236          case eKeyDown:
    237            if (PopupAllowedForEvent("keydown")) {
    238              abuse = PopupBlocker::openControlled;
    239            }
    240            break;
    241          default:
    242            break;
    243        }
    244      }
    245      break;
    246    case eTouchEventClass:
    247      if (aEvent->IsTrusted()) {
    248        switch (aEvent->mMessage) {
    249          case eTouchStart:
    250            if (PopupAllowedForEvent("touchstart")) {
    251              abuse = PopupBlocker::openControlled;
    252            }
    253            break;
    254          case eTouchEnd:
    255            if (PopupAllowedForEvent("touchend")) {
    256              abuse = PopupBlocker::openControlled;
    257            }
    258            break;
    259          default:
    260            break;
    261        }
    262      }
    263      break;
    264    case eMouseEventClass:
    265      if (aEvent->IsTrusted()) {
    266        // Let's ignore MouseButton::eSecondary because that is handled as
    267        // context menu.
    268        if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary ||
    269            aEvent->AsMouseEvent()->mButton == MouseButton::eMiddle) {
    270          switch (aEvent->mMessage) {
    271            case eMouseUp:
    272              if (PopupAllowedForEvent("mouseup")) {
    273                abuse = PopupBlocker::openControlled;
    274              }
    275              break;
    276            case eMouseDown:
    277              if (PopupAllowedForEvent("mousedown")) {
    278                abuse = PopupBlocker::openControlled;
    279              }
    280              break;
    281            case eMouseDoubleClick:
    282              if (PopupAllowedForEvent("dblclick")) {
    283                abuse = PopupBlocker::openControlled;
    284              }
    285              break;
    286            default:
    287              break;
    288          }
    289        }
    290      }
    291      break;
    292    case ePointerEventClass:
    293      if (aEvent->IsTrusted()) {
    294        if ((aEvent->AsPointerEvent()->mButton == MouseButton::ePrimary ||
    295             aEvent->AsPointerEvent()->mButton == MouseButton::eMiddle)) {
    296          switch (aEvent->mMessage) {
    297            case ePointerClick:
    298              /* Click events get special treatment because of their
    299                 historical status as a more legitimate event handler. If
    300                 click popups are enabled in the prefs, clear the popup
    301                 status completely. */
    302              if (PopupAllowedForEvent("click")) {
    303                abuse = PopupBlocker::openAllowed;
    304              }
    305              break;
    306            case ePointerUp:
    307              if (PopupAllowedForEvent("pointerup")) {
    308                abuse = PopupBlocker::openControlled;
    309              }
    310              break;
    311            case ePointerDown:
    312              if (PopupAllowedForEvent("pointerdown")) {
    313                abuse = PopupBlocker::openControlled;
    314              }
    315              break;
    316            default:
    317              break;
    318          }
    319        }
    320        // XXX Why don't we handle auxclick if the button is the middle button?
    321        else if (aEvent->mMessage == ePointerAuxClick) {
    322          // Not MouseButton::ePrimary:
    323          // There's not a strong reason to ignore other events (eg eMouseUp)
    324          // for non-primary clicks as far as we know, so we could add them if
    325          // it becomes a compat issue
    326          if (PopupAllowedForEvent("auxclick")) {
    327            abuse = PopupBlocker::openControlled;
    328          }
    329        }
    330 
    331        // XXX However, `contextmenu` event is handled even if the button is the
    332        // primary button which may occur if the context menu is opened by
    333        // keyboard.
    334        if (aEvent->mMessage == eContextMenu) {
    335          if (PopupAllowedForEvent("contextmenu")) {
    336            abuse = PopupBlocker::openControlled;
    337          }
    338        }
    339      }
    340      break;
    341    case eFormEventClass:
    342      // For these following events only allow popups if they're
    343      // triggered while handling user input. See
    344      // UserActivation::IsUserInteractionEvent() for details.
    345      if (UserActivation::IsHandlingUserInput()) {
    346        switch (aEvent->mMessage) {
    347          case eFormSubmit:
    348            if (PopupAllowedForEvent("submit")) {
    349              abuse = PopupBlocker::openControlled;
    350            }
    351            break;
    352          case eFormReset:
    353            if (PopupAllowedForEvent("reset")) {
    354              abuse = PopupBlocker::openControlled;
    355            }
    356            break;
    357          default:
    358            break;
    359        }
    360      }
    361      break;
    362    default:
    363      break;
    364  }
    365 
    366  return abuse;
    367 }
    368 
    369 /* static */
    370 void PopupBlocker::Initialize() {
    371  DebugOnly<nsresult> rv =
    372      Preferences::RegisterCallback(OnPrefChange, "dom.popup_allowed_events");
    373  MOZ_ASSERT(NS_SUCCEEDED(rv),
    374             "Failed to observe \"dom.popup_allowed_events\"");
    375 }
    376 
    377 /* static */
    378 void PopupBlocker::Shutdown() {
    379  MOZ_ASSERT(sOpenPopupSpamCount == 0);
    380 
    381  if (sPopupAllowedEvents) {
    382    free(sPopupAllowedEvents);
    383  }
    384 
    385  Preferences::UnregisterCallback(OnPrefChange, "dom.popup_allowed_events");
    386 }
    387 
    388 /* static */
    389 bool PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe() {
    390  if (!StaticPrefs::dom_delay_block_external_protocol_in_iframes_enabled()) {
    391    return false;
    392  }
    393 
    394  TimeStamp now = TimeStamp::Now();
    395 
    396  if (sLastAllowedExternalProtocolIFrameTimeStamp.IsNull()) {
    397    sLastAllowedExternalProtocolIFrameTimeStamp = now;
    398    return true;
    399  }
    400 
    401  if ((now - sLastAllowedExternalProtocolIFrameTimeStamp).ToSeconds() <
    402      StaticPrefs::dom_delay_block_external_protocol_in_iframes()) {
    403    return false;
    404  }
    405 
    406  sLastAllowedExternalProtocolIFrameTimeStamp = now;
    407  return true;
    408 }
    409 
    410 /* static */
    411 TimeStamp PopupBlocker::WhenLastExternalProtocolIframeAllowed() {
    412  return sLastAllowedExternalProtocolIFrameTimeStamp;
    413 }
    414 
    415 /* static */
    416 void PopupBlocker::ResetLastExternalProtocolIframeAllowed() {
    417  sLastAllowedExternalProtocolIFrameTimeStamp = TimeStamp();
    418 }
    419 
    420 /* static */
    421 void PopupBlocker::RegisterOpenPopupSpam() { sOpenPopupSpamCount++; }
    422 
    423 /* static */
    424 void PopupBlocker::UnregisterOpenPopupSpam() {
    425  MOZ_ASSERT(sOpenPopupSpamCount);
    426  sOpenPopupSpamCount--;
    427 }
    428 
    429 /* static */
    430 uint32_t PopupBlocker::GetOpenPopupSpamCount() { return sOpenPopupSpamCount; }
    431 
    432 }  // namespace mozilla::dom
    433 
    434 AutoPopupStatePusherInternal::AutoPopupStatePusherInternal(
    435    mozilla::dom::PopupBlocker::PopupControlState aState, bool aForce)
    436    : mOldState(
    437          mozilla::dom::PopupBlocker::PushPopupControlState(aState, aForce)) {
    438  mozilla::dom::PopupBlocker::PopupStatePusherCreated();
    439 }
    440 
    441 AutoPopupStatePusherInternal::~AutoPopupStatePusherInternal() {
    442  mozilla::dom::PopupBlocker::PopPopupControlState(mOldState);
    443  mozilla::dom::PopupBlocker::PopupStatePusherDestroyed();
    444 }