tor-browser

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

MIDIPermissionRequest.cpp (7237B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/MIDIPermissionRequest.h"
      8 
      9 #include "mozilla/BasePrincipal.h"
     10 #include "mozilla/Preferences.h"
     11 #include "mozilla/RandomNum.h"
     12 #include "mozilla/StaticPrefs_dom.h"
     13 #include "mozilla/dom/Document.h"
     14 #include "mozilla/dom/MIDIAccessManager.h"
     15 #include "mozilla/dom/MIDIOptionsBinding.h"
     16 #include "mozilla/ipc/BackgroundChild.h"
     17 #include "mozilla/ipc/PBackgroundChild.h"
     18 #include "nsContentUtils.h"
     19 #include "nsIGlobalObject.h"
     20 
     21 //-------------------------------------------------
     22 // MIDI Permission Requests
     23 //-------------------------------------------------
     24 
     25 using namespace mozilla::dom;
     26 
     27 NS_IMPL_CYCLE_COLLECTION_INHERITED(MIDIPermissionRequest,
     28                                   ContentPermissionRequestBase, mPromise)
     29 
     30 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(MIDIPermissionRequest,
     31                                                   ContentPermissionRequestBase,
     32                                                   nsIRunnable)
     33 
     34 NS_IMPL_ADDREF_INHERITED(MIDIPermissionRequest, ContentPermissionRequestBase)
     35 NS_IMPL_RELEASE_INHERITED(MIDIPermissionRequest, ContentPermissionRequestBase)
     36 
     37 MIDIPermissionRequest::MIDIPermissionRequest(nsPIDOMWindowInner* aWindow,
     38                                             Promise* aPromise,
     39                                             const MIDIOptions& aOptions)
     40    : ContentPermissionRequestBase(
     41          aWindow->GetDoc()->NodePrincipal(), aWindow,
     42          ""_ns,  // We check prefs in a custom way here
     43          "midi"_ns),
     44      mPromise(aPromise),
     45      mNeedsSysex(aOptions.mSysex) {
     46  MOZ_ASSERT(aWindow);
     47  MOZ_ASSERT(aPromise, "aPromise should not be null!");
     48  MOZ_ASSERT(aWindow->GetDoc());
     49  mPrincipal = aWindow->GetDoc()->NodePrincipal();
     50  MOZ_ASSERT(mPrincipal);
     51 }
     52 
     53 NS_IMETHODIMP
     54 MIDIPermissionRequest::GetTypes(nsIArray** aTypes) {
     55  NS_ENSURE_ARG_POINTER(aTypes);
     56  nsTArray<nsString> options;
     57 
     58  // The previous implementation made no differences between midi and
     59  // midi-sysex. The check on the SitePermsAddonProvider pref should be removed
     60  // at the same time as the old implementation.
     61  if (mNeedsSysex || !StaticPrefs::dom_sitepermsaddon_provider_enabled()) {
     62    options.AppendElement(u"sysex"_ns);
     63  }
     64  return nsContentPermissionUtils::CreatePermissionArray(mType, options,
     65                                                         aTypes);
     66 }
     67 
     68 NS_IMETHODIMP
     69 MIDIPermissionRequest::Cancel() {
     70  mCancelTimer = nullptr;
     71  mPromise->MaybeRejectWithSecurityError(
     72      "WebMIDI requires a site permission add-on to activate");
     73  return NS_OK;
     74 }
     75 
     76 NS_IMETHODIMP
     77 MIDIPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
     78  MOZ_ASSERT(aChoices.isUndefined());
     79  MIDIAccessManager* mgr = MIDIAccessManager::Get();
     80  mgr->CreateMIDIAccess(mWindow, mNeedsSysex, mPromise);
     81  return NS_OK;
     82 }
     83 
     84 NS_IMETHODIMP
     85 MIDIPermissionRequest::Run() {
     86  // If the testing flag is true, skip dialog
     87  if (Preferences::GetBool("midi.prompt.testing", false)) {
     88    bool allow =
     89        Preferences::GetBool("media.navigator.permission.disabled", false);
     90    if (allow) {
     91      Allow(JS::UndefinedHandleValue);
     92    } else {
     93      Cancel();
     94    }
     95    return NS_OK;
     96  }
     97 
     98  nsCString permName = "midi"_ns;
     99  // The previous implementation made no differences between midi and
    100  // midi-sysex. The check on the SitePermsAddonProvider pref should be removed
    101  // at the same time as the old implementation.
    102  if (mNeedsSysex || !StaticPrefs::dom_sitepermsaddon_provider_enabled()) {
    103    permName.Append("-sysex");
    104  }
    105 
    106  // First, check for an explicit allow/deny. Note that we want to support
    107  // granting a permission on the base domain and then using it on a subdomain,
    108  // which is why we use the non-"Exact" variants of these APIs. See bug
    109  // 1757218.
    110  if (nsContentUtils::IsSitePermAllow(mPrincipal, permName)) {
    111    Allow(JS::UndefinedHandleValue);
    112    return NS_OK;
    113  }
    114 
    115  if (nsContentUtils::IsSitePermDeny(mPrincipal, permName)) {
    116    CancelWithRandomizedDelay();
    117    return NS_OK;
    118  }
    119 
    120  // If the add-on is not installed, and sitepermsaddon provider not enabled,
    121  // auto-deny (except for localhost).
    122  if (StaticPrefs::dom_webmidi_gated() &&
    123      !StaticPrefs::dom_sitepermsaddon_provider_enabled() &&
    124      !nsContentUtils::HasSitePerm(mPrincipal, permName) &&
    125      !mPrincipal->GetIsLoopbackHost()) {
    126    CancelWithRandomizedDelay();
    127    return NS_OK;
    128  }
    129 
    130  // If sitepermsaddon provider is enabled and user denied install,
    131  // auto-deny (except for localhost, where we use a regular permission flow).
    132  if (StaticPrefs::dom_sitepermsaddon_provider_enabled() &&
    133      nsContentUtils::IsSitePermDeny(mPrincipal, "install"_ns) &&
    134      !mPrincipal->GetIsLoopbackHost()) {
    135    CancelWithRandomizedDelay();
    136    return NS_OK;
    137  }
    138 
    139  // Before we bother the user with a prompt, see if they have any devices. If
    140  // they don't, just report denial.
    141  MOZ_ASSERT(NS_IsMainThread());
    142  mozilla::ipc::PBackgroundChild* actor =
    143      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
    144  if (NS_WARN_IF(!actor)) {
    145    return NS_ERROR_FAILURE;
    146  }
    147  RefPtr<MIDIPermissionRequest> self = this;
    148  actor->SendHasMIDIDevice(
    149      [=](bool aHasDevices) {
    150        MOZ_ASSERT(NS_IsMainThread());
    151 
    152        if (aHasDevices) {
    153          self->DoPrompt();
    154        } else {
    155          nsContentUtils::ReportToConsoleNonLocalized(
    156              u"Silently denying site request for MIDI access because no devices were detected. You may need to restart your browser after connecting a new device."_ns,
    157              nsIScriptError::infoFlag, "WebMIDI"_ns, mWindow->GetDoc());
    158          self->CancelWithRandomizedDelay();
    159        }
    160      },
    161      [=](auto) { self->CancelWithRandomizedDelay(); });
    162 
    163  return NS_OK;
    164 }
    165 
    166 // If the user has no MIDI devices, we automatically deny the request. To
    167 // prevent sites from using timing attack to discern the existence of MIDI
    168 // devices, we instrument silent denials with a randomized delay between 3
    169 // and 13 seconds, which is intended to model the time the user might spend
    170 // considering a prompt before denying it.
    171 //
    172 // Note that we set the random component of the delay to zero in automation
    173 // to avoid unnecessarily increasing test end-to-end time.
    174 void MIDIPermissionRequest::CancelWithRandomizedDelay() {
    175  MOZ_ASSERT(NS_IsMainThread());
    176  uint32_t baseDelayMS = 3 * 1000;
    177  uint32_t randomDelayMS =
    178      xpc::IsInAutomation() ? 0 : RandomUint64OrDie() % (10 * 1000);
    179  auto delay = TimeDuration::FromMilliseconds(baseDelayMS + randomDelayMS);
    180  RefPtr<MIDIPermissionRequest> self = this;
    181  NS_NewTimerWithCallback(
    182      getter_AddRefs(mCancelTimer), [=](auto) { self->Cancel(); }, delay,
    183      nsITimer::TYPE_ONE_SHOT,
    184      "MIDIPermissionRequest::CancelWithRandomizedDelay"_ns);
    185 }
    186 
    187 nsresult MIDIPermissionRequest::DoPrompt() {
    188  if (NS_FAILED(nsContentPermissionUtils::AskPermission(this, mWindow))) {
    189    Cancel();
    190    return NS_ERROR_FAILURE;
    191  }
    192  return NS_OK;
    193 }