tor-browser

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

ScreenOrientation.cpp (34827B)


      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 "ScreenOrientation.h"
      8 
      9 #include "mozilla/DOMEventTargetHelper.h"
     10 #include "mozilla/Hal.h"
     11 #include "mozilla/Preferences.h"
     12 #include "mozilla/StaticPrefs_browser.h"
     13 #include "mozilla/dom/ContentChild.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "mozilla/dom/Event.h"
     16 #include "mozilla/dom/Promise.h"
     17 #include "nsContentUtils.h"
     18 #include "nsGlobalWindowInner.h"
     19 #include "nsIDocShell.h"
     20 #include "nsSandboxFlags.h"
     21 #include "nsScreen.h"
     22 
     23 using namespace mozilla;
     24 using namespace mozilla::dom;
     25 
     26 NS_IMPL_CYCLE_COLLECTION_INHERITED(ScreenOrientation, DOMEventTargetHelper,
     27                                   mScreen);
     28 
     29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScreenOrientation)
     30 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
     31 
     32 NS_IMPL_ADDREF_INHERITED(ScreenOrientation, DOMEventTargetHelper)
     33 NS_IMPL_RELEASE_INHERITED(ScreenOrientation, DOMEventTargetHelper)
     34 
     35 static OrientationType InternalOrientationToType(
     36    hal::ScreenOrientation aOrientation) {
     37  switch (aOrientation) {
     38    case hal::ScreenOrientation::PortraitPrimary:
     39      return OrientationType::Portrait_primary;
     40    case hal::ScreenOrientation::PortraitSecondary:
     41      return OrientationType::Portrait_secondary;
     42    case hal::ScreenOrientation::LandscapePrimary:
     43      return OrientationType::Landscape_primary;
     44    case hal::ScreenOrientation::LandscapeSecondary:
     45      return OrientationType::Landscape_secondary;
     46    default:
     47      MOZ_CRASH("Bad aOrientation value");
     48  }
     49 }
     50 
     51 static hal::ScreenOrientation OrientationTypeToInternal(
     52    OrientationType aOrientation) {
     53  switch (aOrientation) {
     54    case OrientationType::Portrait_primary:
     55      return hal::ScreenOrientation::PortraitPrimary;
     56    case OrientationType::Portrait_secondary:
     57      return hal::ScreenOrientation::PortraitSecondary;
     58    case OrientationType::Landscape_primary:
     59      return hal::ScreenOrientation::LandscapePrimary;
     60    case OrientationType::Landscape_secondary:
     61      return hal::ScreenOrientation::LandscapeSecondary;
     62    default:
     63      MOZ_CRASH("Bad aOrientation value");
     64  }
     65 }
     66 
     67 ScreenOrientation::ScreenOrientation(nsPIDOMWindowInner* aWindow,
     68                                     nsScreen* aScreen)
     69    : DOMEventTargetHelper(aWindow), mScreen(aScreen) {
     70  MOZ_ASSERT(aWindow);
     71  MOZ_ASSERT(aScreen);
     72 
     73  mAngle = aScreen->GetOrientationAngle();
     74  mType = InternalOrientationToType(aScreen->GetOrientationType());
     75 
     76  Document* doc = GetResponsibleDocument();
     77  BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
     78  if (bc && !bc->IsDiscarded() && !bc->HasOrientationOverride()) {
     79    MOZ_ALWAYS_SUCCEEDS(bc->SetCurrentOrientation(mType, mAngle));
     80  } else if (bc && !bc->IsTop() && bc->HasOrientationOverride()) {
     81    // Resync the override for newly created iframes.
     82    BrowsingContext* topBC = bc->Top();
     83    MOZ_ALWAYS_SUCCEEDS(
     84        bc->SetOrientationOverride(topBC->GetCurrentOrientationType(),
     85                                   topBC->GetCurrentOrientationAngle()));
     86  }
     87 }
     88 
     89 ScreenOrientation::~ScreenOrientation() {
     90  if (mTriedToLockDeviceOrientation) {
     91    UnlockDeviceOrientation();
     92  } else {
     93    CleanupFullscreenListener();
     94  }
     95 
     96  MOZ_ASSERT(!mFullscreenListener);
     97 }
     98 
     99 class ScreenOrientation::FullscreenEventListener final
    100    : public nsIDOMEventListener {
    101  ~FullscreenEventListener() = default;
    102 
    103 public:
    104  FullscreenEventListener() = default;
    105 
    106  NS_DECL_ISUPPORTS
    107  NS_DECL_NSIDOMEVENTLISTENER
    108 };
    109 
    110 class ScreenOrientation::VisibleEventListener final
    111    : public nsIDOMEventListener {
    112  ~VisibleEventListener() = default;
    113 
    114 public:
    115  VisibleEventListener() = default;
    116 
    117  NS_DECL_ISUPPORTS
    118  NS_DECL_NSIDOMEVENTLISTENER
    119 };
    120 
    121 class ScreenOrientation::LockOrientationTask final : public nsIRunnable {
    122  ~LockOrientationTask();
    123 
    124 public:
    125  NS_DECL_ISUPPORTS
    126  NS_DECL_NSIRUNNABLE
    127 
    128  LockOrientationTask(ScreenOrientation* aScreenOrientation, Promise* aPromise,
    129                      hal::ScreenOrientation aOrientationLock,
    130                      Document* aDocument, bool aIsFullscreen);
    131 
    132 protected:
    133  bool OrientationLockContains(OrientationType aOrientationType);
    134 
    135  RefPtr<ScreenOrientation> mScreenOrientation;
    136  RefPtr<Promise> mPromise;
    137  hal::ScreenOrientation mOrientationLock;
    138  WeakPtr<Document> mDocument;
    139  bool mIsFullscreen;
    140 };
    141 
    142 NS_IMPL_ISUPPORTS(ScreenOrientation::LockOrientationTask, nsIRunnable)
    143 
    144 ScreenOrientation::LockOrientationTask::LockOrientationTask(
    145    ScreenOrientation* aScreenOrientation, Promise* aPromise,
    146    hal::ScreenOrientation aOrientationLock, Document* aDocument,
    147    bool aIsFullscreen)
    148    : mScreenOrientation(aScreenOrientation),
    149      mPromise(aPromise),
    150      mOrientationLock(aOrientationLock),
    151      mDocument(aDocument),
    152      mIsFullscreen(aIsFullscreen) {
    153  MOZ_ASSERT(aScreenOrientation);
    154  MOZ_ASSERT(aPromise);
    155  MOZ_ASSERT(aDocument);
    156 }
    157 
    158 ScreenOrientation::LockOrientationTask::~LockOrientationTask() = default;
    159 
    160 bool ScreenOrientation::LockOrientationTask::OrientationLockContains(
    161    OrientationType aOrientationType) {
    162  return bool(mOrientationLock & OrientationTypeToInternal(aOrientationType));
    163 }
    164 
    165 NS_IMETHODIMP
    166 ScreenOrientation::LockOrientationTask::Run() {
    167  if (!mPromise) {
    168    return NS_OK;
    169  }
    170 
    171  if (!mDocument) {
    172    mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    173    return NS_OK;
    174  }
    175 
    176  nsCOMPtr<nsPIDOMWindowInner> owner = mScreenOrientation->GetOwnerWindow();
    177  if (!owner || !owner->IsFullyActive()) {
    178    mPromise->MaybeRejectWithAbortError("The document is not fully active.");
    179    return NS_OK;
    180  }
    181 
    182  // Step to lock the orientation as defined in the spec.
    183  if (mDocument->GetOrientationPendingPromise() != mPromise) {
    184    // The document's pending promise is not associated with this task
    185    // to lock orientation. There has since been another request to
    186    // lock orientation, thus we don't need to do anything. Old promise
    187    // should be been rejected.
    188    return NS_OK;
    189  }
    190 
    191  if (mDocument->Hidden()) {
    192    // Active orientation lock is not the document's orientation lock.
    193    mPromise->MaybeResolveWithUndefined();
    194    mDocument->ClearOrientationPendingPromise();
    195    return NS_OK;
    196  }
    197 
    198  if (mOrientationLock == hal::ScreenOrientation::None) {
    199    mScreenOrientation->UnlockDeviceOrientation();
    200    mPromise->MaybeResolveWithUndefined();
    201    mDocument->ClearOrientationPendingPromise();
    202    return NS_OK;
    203  }
    204 
    205  BrowsingContext* bc = mDocument->GetBrowsingContext();
    206  if (!bc) {
    207    mPromise->MaybeResolveWithUndefined();
    208    mDocument->ClearOrientationPendingPromise();
    209    return NS_OK;
    210  }
    211 
    212  OrientationType previousOrientationType = bc->GetCurrentOrientationType();
    213  mScreenOrientation->LockDeviceOrientation(mOrientationLock, mIsFullscreen)
    214      ->Then(
    215          GetCurrentSerialEventTarget(), __func__,
    216          [self = RefPtr{this}, previousOrientationType](
    217              const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) {
    218            if (self->mPromise->State() != Promise::PromiseState::Pending) {
    219              // mPromise is already resolved or rejected by
    220              // DispatchChangeEventAndResolvePromise() or
    221              // AbortInProcessOrientationPromises().
    222              return;
    223            }
    224 
    225            if (aValue.IsReject()) {
    226              // Since current device doesn't support lock orientation or
    227              // causes something device error, we should throw it, instead of
    228              // abort.
    229              self->mPromise->MaybeReject(aValue.RejectValue());
    230              self->mDocument->ClearOrientationPendingPromise();
    231              return;
    232            }
    233 
    234            if (!self->mDocument || !self->mDocument->IsFullyActive()) {
    235              // Pending promise in document will be clear during destroying
    236              // document.
    237              self->mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    238              // Since orientation.lock is failed, but system side might be
    239              // successful, reset orientation lock.
    240              if (self->mDocument) {
    241                BrowsingContext* bc = self->mDocument->GetBrowsingContext();
    242                bc = bc ? bc->Top() : nullptr;
    243                if (bc) {
    244                  bc->SetOrientationLock(hal::ScreenOrientation::None,
    245                                         IgnoreErrors());
    246                  self->mScreenOrientation->UnlockDeviceOrientation();
    247                }
    248              }
    249 
    250              return;
    251            }
    252 
    253            if (self->mDocument->GetOrientationPendingPromise() !=
    254                self->mPromise) {
    255              // mPromise is old promise now and document has new promise by
    256              // later `orientation.lock` call. Old promise is already rejected
    257              // by AbortInProcessOrientationPromises()
    258              return;
    259            }
    260 
    261            // LockDeviceOrientation won't change orientation, so change
    262            // event isn't fired.
    263            if (BrowsingContext* bc = self->mDocument->GetBrowsingContext()) {
    264              OrientationType currentOrientationType =
    265                  bc->GetCurrentOrientationType();
    266              if ((previousOrientationType == currentOrientationType &&
    267                   self->OrientationLockContains(currentOrientationType)) ||
    268                  (self->mOrientationLock == hal::ScreenOrientation::Default &&
    269                   bc->GetCurrentOrientationAngle() == 0)) {
    270                // Orientation lock will not cause an orientation change, so
    271                // we need to manually resolve the promise here.
    272                self->mPromise->MaybeResolveWithUndefined();
    273                self->mDocument->ClearOrientationPendingPromise();
    274              }
    275            }
    276          });
    277 
    278  return NS_OK;
    279 }
    280 
    281 already_AddRefed<Promise> ScreenOrientation::Lock(
    282    OrientationLockType aOrientation, ErrorResult& aRv) {
    283  hal::ScreenOrientation orientation = hal::ScreenOrientation::None;
    284 
    285  switch (aOrientation) {
    286    case OrientationLockType::Any:
    287      orientation = hal::ScreenOrientation::PortraitPrimary |
    288                    hal::ScreenOrientation::PortraitSecondary |
    289                    hal::ScreenOrientation::LandscapePrimary |
    290                    hal::ScreenOrientation::LandscapeSecondary;
    291      break;
    292    case OrientationLockType::Natural:
    293      orientation |= hal::ScreenOrientation::Default;
    294      break;
    295    case OrientationLockType::Landscape:
    296      orientation = hal::ScreenOrientation::LandscapePrimary |
    297                    hal::ScreenOrientation::LandscapeSecondary;
    298      break;
    299    case OrientationLockType::Portrait:
    300      orientation = hal::ScreenOrientation::PortraitPrimary |
    301                    hal::ScreenOrientation::PortraitSecondary;
    302      break;
    303    case OrientationLockType::Portrait_primary:
    304      orientation = hal::ScreenOrientation::PortraitPrimary;
    305      break;
    306    case OrientationLockType::Portrait_secondary:
    307      orientation = hal::ScreenOrientation::PortraitSecondary;
    308      break;
    309    case OrientationLockType::Landscape_primary:
    310      orientation = hal::ScreenOrientation::LandscapePrimary;
    311      break;
    312    case OrientationLockType::Landscape_secondary:
    313      orientation = hal::ScreenOrientation::LandscapeSecondary;
    314      break;
    315    default:
    316      NS_WARNING("Unexpected orientation type");
    317      aRv.Throw(NS_ERROR_UNEXPECTED);
    318      return nullptr;
    319  }
    320 
    321  return LockInternal(orientation, aRv);
    322 }
    323 
    324 // Wait for document entered fullscreen.
    325 class FullscreenWaitListener final : public nsIDOMEventListener {
    326 private:
    327  ~FullscreenWaitListener() = default;
    328 
    329 public:
    330  FullscreenWaitListener() = default;
    331 
    332  NS_DECL_ISUPPORTS
    333 
    334  // When we have pending fullscreen request, we will wait for the completion or
    335  // cancel of it.
    336  RefPtr<GenericPromise> Promise(Document* aDocument) {
    337    if (aDocument->Fullscreen()) {
    338      return GenericPromise::CreateAndResolve(true, __func__);
    339    }
    340 
    341    if (NS_FAILED(InstallEventListener(aDocument))) {
    342      return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
    343    }
    344 
    345    MOZ_ASSERT(aDocument->HasPendingFullscreenRequests());
    346    return mHolder.Ensure(__func__);
    347  }
    348 
    349  NS_IMETHODIMP HandleEvent(Event* aEvent) override {
    350    nsAutoString eventType;
    351    aEvent->GetType(eventType);
    352 
    353    if (eventType.EqualsLiteral("pagehide")) {
    354      mHolder.Reject(NS_ERROR_FAILURE, __func__);
    355      CleanupEventListener();
    356      return NS_OK;
    357    }
    358 
    359    MOZ_ASSERT(eventType.EqualsLiteral("fullscreenchange") ||
    360               eventType.EqualsLiteral("fullscreenerror") ||
    361               eventType.EqualsLiteral("pagehide"));
    362    if (mDocument->Fullscreen()) {
    363      mHolder.Resolve(true, __func__);
    364    } else {
    365      mHolder.Reject(NS_ERROR_FAILURE, __func__);
    366    }
    367    CleanupEventListener();
    368    return NS_OK;
    369  }
    370 
    371 private:
    372  nsresult InstallEventListener(Document* aDoc) {
    373    if (mDocument) {
    374      return NS_OK;
    375    }
    376 
    377    mDocument = aDoc;
    378    nsresult rv = aDoc->AddSystemEventListener(u"fullscreenchange"_ns, this,
    379                                               /* aUseCapture = */ true);
    380    if (NS_FAILED(rv)) {
    381      CleanupEventListener();
    382      return rv;
    383    }
    384 
    385    rv = aDoc->AddSystemEventListener(u"fullscreenerror"_ns, this,
    386                                      /* aUseCapture = */ true);
    387    if (NS_FAILED(rv)) {
    388      CleanupEventListener();
    389      return rv;
    390    }
    391 
    392    nsPIDOMWindowOuter* window = aDoc->GetWindow();
    393    nsCOMPtr<EventTarget> target = do_QueryInterface(window);
    394    if (!target) {
    395      CleanupEventListener();
    396      return NS_ERROR_FAILURE;
    397    }
    398    rv = target->AddSystemEventListener(u"pagehide"_ns, this,
    399                                        /* aUseCapture = */ true,
    400                                        /* aWantsUntrusted = */ false);
    401    if (NS_FAILED(rv)) {
    402      CleanupEventListener();
    403      return rv;
    404    }
    405 
    406    return NS_OK;
    407  }
    408 
    409  void CleanupEventListener() {
    410    if (!mDocument) {
    411      return;
    412    }
    413    RefPtr<FullscreenWaitListener> kungFuDeathGrip(this);
    414    mDocument->RemoveSystemEventListener(u"fullscreenchange"_ns, this, true);
    415    mDocument->RemoveSystemEventListener(u"fullscreenerror"_ns, this, true);
    416    nsPIDOMWindowOuter* window = mDocument->GetWindow();
    417    nsCOMPtr<EventTarget> target = do_QueryInterface(window);
    418    if (target) {
    419      target->RemoveSystemEventListener(u"pagehide"_ns, this, true);
    420    }
    421    mDocument = nullptr;
    422  }
    423 
    424  MozPromiseHolder<GenericPromise> mHolder;
    425  RefPtr<Document> mDocument;
    426 };
    427 
    428 NS_IMPL_ISUPPORTS(FullscreenWaitListener, nsIDOMEventListener)
    429 
    430 void ScreenOrientation::AbortInProcessOrientationPromises(
    431    BrowsingContext* aBrowsingContext) {
    432  MOZ_ASSERT(aBrowsingContext);
    433 
    434  aBrowsingContext = aBrowsingContext->Top();
    435  aBrowsingContext->PreOrderWalk([](BrowsingContext* aContext) {
    436    nsIDocShell* docShell = aContext->GetDocShell();
    437    if (docShell) {
    438      Document* doc = docShell->GetDocument();
    439      if (doc) {
    440        Promise* promise = doc->GetOrientationPendingPromise();
    441        if (promise) {
    442          promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    443          doc->ClearOrientationPendingPromise();
    444        }
    445      }
    446    }
    447  });
    448 }
    449 
    450 // https://w3c.github.io/screen-orientation/#dfn-common-safety-checks.
    451 
    452 // static
    453 bool ScreenOrientation::CommonSafetyChecks(nsPIDOMWindowInner* aOwner,
    454                                           Document* aDocument,
    455                                           ErrorResult& aRv) {
    456  MOZ_ASSERT(aOwner);
    457  MOZ_ASSERT(aDocument);
    458 
    459  // Chrome can always lock the screen orientation.
    460  if (aOwner->GetBrowsingContext()->IsChrome()) {
    461    return true;
    462  }
    463 
    464  // 5.4.1.
    465  // If document is not fully active, throw an "InvalidStateError" DOMException.
    466  if (!aOwner->IsFullyActive()) {
    467    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    468    return false;
    469  }
    470 
    471  // 5.4.2.
    472  // If document has the sandboxed orientation lock browsing context flag set,
    473  // throw "SecurityError" DOMException.
    474  if (aDocument->GetSandboxFlags() & SANDBOXED_ORIENTATION_LOCK) {
    475    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    476    return false;
    477  }
    478 
    479  // 5.4.3.
    480  // If document's visibility state is "hidden", throw "SecurityError"
    481  // DOMException.
    482  if (aDocument->Hidden()) {
    483    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    484    return false;
    485  }
    486 
    487  return true;
    488 }
    489 
    490 already_AddRefed<Promise> ScreenOrientation::LockInternal(
    491    hal::ScreenOrientation aOrientation, ErrorResult& aRv) {
    492  // Steps to apply an orientation lock as defined in spec.
    493 
    494  // Step 1.
    495  // Let document be this's relevant global object's associated Document.
    496 
    497  Document* doc = GetResponsibleDocument();
    498  if (NS_WARN_IF(!doc)) {
    499    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    500    return nullptr;
    501  }
    502 
    503  nsCOMPtr<nsPIDOMWindowInner> owner = GetOwnerWindow();
    504  if (NS_WARN_IF(!owner)) {
    505    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    506    return nullptr;
    507  }
    508 
    509  nsCOMPtr<nsIDocShell> docShell = owner->GetDocShell();
    510  if (NS_WARN_IF(!docShell)) {
    511    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    512    return nullptr;
    513  }
    514 
    515  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(owner);
    516  MOZ_ASSERT(go);
    517  RefPtr<Promise> p = Promise::Create(go, aRv);
    518  if (NS_WARN_IF(aRv.Failed())) {
    519    return nullptr;
    520  }
    521 
    522  if (!CommonSafetyChecks(owner, doc, aRv)) {
    523    if (aOrientation == hal::ScreenOrientation::None) {
    524      // When unlock, throws a DOM exception.
    525      return nullptr;
    526    }
    527    p->MaybeReject(aRv.StealNSResult());
    528    return p.forget();
    529  }
    530 
    531  // If document doesn't meet the pre-lock conditions, or locking would be a
    532  // security risk, return a promise rejected with a "SecurityError"
    533  // DOMException and abort these steps.
    534 
    535  LockPermission perm = GetLockOrientationPermission(owner, doc);
    536  if (perm == LOCK_DENIED) {
    537    p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    538    return p.forget();
    539  }
    540 
    541  // Step 4.
    542  // If the user agent does not support locking the screen orientation to
    543  // orientation, return a promise rejected with a "NotSupportedError"
    544  // DOMException and abort these steps.
    545 
    546 #if !defined(MOZ_WIDGET_ANDROID) && !defined(XP_WIN)
    547  // User agent does not support locking the screen orientation.
    548  p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    549  return p.forget();
    550 #else
    551  // Bypass locking screen orientation if preference is false
    552  if (!StaticPrefs::dom_screenorientation_allow_lock()) {
    553    p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    554    return p.forget();
    555  }
    556 
    557  RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
    558  bc = bc ? bc->Top() : nullptr;
    559  if (!bc) {
    560    aRv.Throw(NS_ERROR_UNEXPECTED);
    561    return nullptr;
    562  }
    563 
    564  bc->SetOrientationLock(aOrientation, aRv);
    565  if (aRv.Failed()) {
    566    return nullptr;
    567  }
    568 
    569  AbortInProcessOrientationPromises(bc);
    570  dom::ContentChild::GetSingleton()->SendAbortOtherOrientationPendingPromises(
    571      bc);
    572 
    573  if (!doc->SetOrientationPendingPromise(p)) {
    574    p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    575    return p.forget();
    576  }
    577 
    578  if (perm == LOCK_ALLOWED || doc->Fullscreen()) {
    579    nsCOMPtr<nsIRunnable> lockOrientationTask = new LockOrientationTask(
    580        this, p, aOrientation, doc, perm == FULLSCREEN_LOCK_ALLOWED);
    581    aRv = NS_DispatchToMainThread(lockOrientationTask);
    582    if (NS_WARN_IF(aRv.Failed())) {
    583      return nullptr;
    584    }
    585 
    586    return p.forget();
    587  }
    588 
    589  MOZ_ASSERT(perm == FULLSCREEN_LOCK_ALLOWED);
    590 
    591  // Full screen state is pending. We have to wait for the completion.
    592  RefPtr<FullscreenWaitListener> listener = new FullscreenWaitListener();
    593  RefPtr<Promise> promise = p;
    594  listener->Promise(doc)->Then(
    595      GetMainThreadSerialEventTarget(), __func__,
    596      [self = RefPtr{this}, promise = std::move(promise), aOrientation,
    597       document =
    598           RefPtr{doc}](const GenericPromise::ResolveOrRejectValue& aValue) {
    599        if (aValue.IsResolve()) {
    600          nsCOMPtr<nsIRunnable> lockOrientationTask = new LockOrientationTask(
    601              self, promise, aOrientation, document, true);
    602          nsresult rv = NS_DispatchToMainThread(lockOrientationTask);
    603          if (NS_SUCCEEDED(rv)) {
    604            return;
    605          }
    606        }
    607        // Pending full screen request is canceled or causes an error.
    608        if (document->GetOrientationPendingPromise() != promise) {
    609          // The document's pending promise is not associated with
    610          // this promise.
    611          return;
    612        }
    613        // pre-lock conditions aren't matched.
    614        promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    615        document->ClearOrientationPendingPromise();
    616      });
    617 
    618  return p.forget();
    619 #endif
    620 }
    621 
    622 RefPtr<GenericNonExclusivePromise> ScreenOrientation::LockDeviceOrientation(
    623    hal::ScreenOrientation aOrientation, bool aIsFullscreen) {
    624  if (!GetOwnerWindow()) {
    625    return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
    626                                                       __func__);
    627  }
    628 
    629  nsCOMPtr<EventTarget> target = GetOwnerWindow()->GetDoc();
    630  // We need to register a listener so we learn when we leave fullscreen
    631  // and when we will have to unlock the screen.
    632  // This needs to be done before LockScreenOrientation call to make sure
    633  // the locking can be unlocked.
    634  if (aIsFullscreen && !target) {
    635    return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
    636                                                       __func__);
    637  }
    638 
    639  // We are fullscreen and lock has been accepted.
    640  if (aIsFullscreen) {
    641    if (!mFullscreenListener) {
    642      mFullscreenListener = new FullscreenEventListener();
    643    }
    644 
    645    nsresult rv = target->AddSystemEventListener(u"fullscreenchange"_ns,
    646                                                 mFullscreenListener,
    647                                                 /* aUseCapture = */ true);
    648    if (NS_WARN_IF(NS_FAILED(rv))) {
    649      return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
    650                                                         __func__);
    651    }
    652  }
    653 
    654  mTriedToLockDeviceOrientation = true;
    655  return hal::LockScreenOrientation(aOrientation);
    656 }
    657 
    658 void ScreenOrientation::Unlock(ErrorResult& aRv) {
    659  if (RefPtr<Promise> p = LockInternal(hal::ScreenOrientation::None, aRv)) {
    660    // Don't end up reporting unhandled promise rejection since
    661    // screen.orientation.unlock doesn't return promise.
    662    MOZ_ALWAYS_TRUE(p->SetAnyPromiseIsHandled());
    663  }
    664 }
    665 
    666 void ScreenOrientation::UnlockDeviceOrientation() {
    667  hal::UnlockScreenOrientation();
    668  CleanupFullscreenListener();
    669 }
    670 
    671 void ScreenOrientation::CleanupFullscreenListener() {
    672  if (!mFullscreenListener || !GetOwnerWindow()) {
    673    mFullscreenListener = nullptr;
    674    return;
    675  }
    676 
    677  // Remove event listener in case of fullscreen lock.
    678  if (nsCOMPtr<EventTarget> target = GetOwnerWindow()->GetDoc()) {
    679    target->RemoveSystemEventListener(u"fullscreenchange"_ns,
    680                                      mFullscreenListener,
    681                                      /* useCapture */ true);
    682  }
    683 
    684  mFullscreenListener = nullptr;
    685 }
    686 
    687 OrientationType ScreenOrientation::DeviceType(CallerType aCallerType) const {
    688  if (nsContentUtils::ShouldResistFingerprinting(
    689          aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
    690    Document* doc = GetResponsibleDocument();
    691    BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
    692    if (!bc) {
    693      return nsRFPService::GetDefaultOrientationType();
    694    }
    695    CSSIntSize size = bc->GetTopInnerSizeForRFP();
    696    return nsRFPService::ViewportSizeToOrientationType(size.width, size.height);
    697  }
    698  return mType;
    699 }
    700 
    701 uint16_t ScreenOrientation::DeviceAngle(CallerType aCallerType) const {
    702  if (nsContentUtils::ShouldResistFingerprinting(
    703          aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
    704    Document* doc = GetResponsibleDocument();
    705    BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
    706    if (!bc) {
    707      return 0;
    708    }
    709    CSSIntSize size = bc->GetTopInnerSizeForRFP();
    710    return nsRFPService::ViewportSizeToAngle(size.width, size.height);
    711  }
    712  return mAngle;
    713 }
    714 
    715 OrientationType ScreenOrientation::GetType(CallerType aCallerType,
    716                                           ErrorResult& aRv) const {
    717  Document* doc = GetResponsibleDocument();
    718  BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
    719  if (!bc) {
    720    aRv.Throw(NS_ERROR_UNEXPECTED);
    721    return OrientationType::Portrait_primary;
    722  }
    723 
    724  OrientationType orientation = bc->GetCurrentOrientationType();
    725  if (nsContentUtils::ShouldResistFingerprinting(
    726          aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
    727    CSSIntSize size = bc->GetTopInnerSizeForRFP();
    728    return nsRFPService::ViewportSizeToOrientationType(size.width, size.height);
    729  }
    730  return orientation;
    731 }
    732 
    733 uint16_t ScreenOrientation::GetAngle(CallerType aCallerType,
    734                                     ErrorResult& aRv) const {
    735  Document* doc = GetResponsibleDocument();
    736  BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
    737  if (!bc) {
    738    aRv.Throw(NS_ERROR_UNEXPECTED);
    739    return 0;
    740  }
    741 
    742  uint16_t angle = static_cast<uint16_t>(bc->GetCurrentOrientationAngle());
    743  if (nsContentUtils::ShouldResistFingerprinting(
    744          aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
    745    CSSIntSize size = bc->GetTopInnerSizeForRFP();
    746    return nsRFPService::ViewportSizeToAngle(size.width, size.height);
    747  }
    748  return angle;
    749 }
    750 
    751 // static
    752 ScreenOrientation::LockPermission
    753 ScreenOrientation::GetLockOrientationPermission(nsPIDOMWindowInner* aOwner,
    754                                                Document* aDocument) {
    755  MOZ_ASSERT(aOwner);
    756  MOZ_ASSERT(aDocument);
    757 
    758  // Chrome can always lock the screen orientation.
    759  if (aOwner->GetBrowsingContext()->IsChrome()) {
    760    return LOCK_ALLOWED;
    761  }
    762 
    763  if (Preferences::GetBool(
    764          "dom.screenorientation.testing.non_fullscreen_lock_allow", false)) {
    765    return LOCK_ALLOWED;
    766  }
    767 
    768  // Other content must be fullscreen in order to lock orientation.
    769  return aDocument->Fullscreen() || aDocument->HasPendingFullscreenRequests()
    770             ? FULLSCREEN_LOCK_ALLOWED
    771             : LOCK_DENIED;
    772 }
    773 
    774 Document* ScreenOrientation::GetResponsibleDocument() const {
    775  nsCOMPtr<nsPIDOMWindowInner> owner = GetOwnerWindow();
    776  if (!owner) {
    777    return nullptr;
    778  }
    779 
    780  return owner->GetDoc();
    781 }
    782 
    783 void ScreenOrientation::MaybeChanged() {
    784  Document* doc = GetResponsibleDocument();
    785  if (!doc || doc->ShouldResistFingerprinting(RFPTarget::ScreenOrientation)) {
    786    return;
    787  }
    788 
    789  BrowsingContext* bc = doc->GetBrowsingContext();
    790  if (!bc) {
    791    return;
    792  }
    793 
    794  hal::ScreenOrientation orientation = mScreen->GetOrientationType();
    795  if (orientation != hal::ScreenOrientation::PortraitPrimary &&
    796      orientation != hal::ScreenOrientation::PortraitSecondary &&
    797      orientation != hal::ScreenOrientation::LandscapePrimary &&
    798      orientation != hal::ScreenOrientation::LandscapeSecondary) {
    799    // The platform may notify of some other values from
    800    // an orientation lock, but we only care about real
    801    // changes to screen orientation which result in one of
    802    // the values we care about.
    803    return;
    804  }
    805 
    806  OrientationType previousOrientation = mType;
    807  mAngle = mScreen->GetOrientationAngle();
    808  mType = InternalOrientationToType(orientation);
    809 
    810  DebugOnly<nsresult> rv;
    811  if (mScreen && mType != previousOrientation) {
    812    // Use of mozorientationchange is deprecated.
    813    rv = mScreen->DispatchTrustedEvent(u"mozorientationchange"_ns);
    814    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
    815  }
    816 
    817  if (doc->Hidden() && !mVisibleListener) {
    818    mVisibleListener = new VisibleEventListener();
    819    rv = doc->AddSystemEventListener(u"visibilitychange"_ns, mVisibleListener,
    820                                     /* aUseCapture = */ true);
    821    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddSystemEventListener failed");
    822    return;
    823  }
    824 
    825  if (mType != bc->GetCurrentOrientationType()) {
    826    rv = bc->SetCurrentOrientation(mType, mAngle);
    827    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetCurrentOrientation failed");
    828 
    829    MaybeDispatchChangeEvent(bc);
    830  }
    831 }
    832 
    833 void ScreenOrientation::MaybeDispatchChangeEvent(
    834    BrowsingContext* aBrowsingContext) {
    835  // change event has to be dispatched by descendantDocs.
    836  // Looking for top level browsing context that has screen in process.
    837  // If parent document has screen, we don't dispatch it at this time.
    838  // change event will be dispatched by parent's
    839  // ScreenOrientation::MaybeChanged.
    840  BrowsingContext* rootBc = aBrowsingContext;
    841  bool dispatchChangeEvent = true;
    842  while (rootBc->GetParent()) {
    843    rootBc = rootBc->GetParent();
    844    if (Document* doc = rootBc->GetExtantDocument()) {
    845      if (auto* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow())) {
    846        if (win->HasScreen()) {
    847          // Parent of browsing context has screen object. Child shouldn't
    848          // dispatch change event.
    849          dispatchChangeEvent = false;
    850          break;
    851        }
    852      }
    853    }
    854  }
    855  if (dispatchChangeEvent) {
    856    DispatchChangeEventToChildren(rootBc);
    857  }
    858 }
    859 
    860 void ScreenOrientation::MaybeDispatchEventsForOverride(
    861    BrowsingContext* aBrowsingContext, bool aOldHasOrientationOverride,
    862    bool aOverrideIsDifferentThanDevice) {
    863  Document* doc = aBrowsingContext->GetExtantDocument();
    864  nsCOMPtr<nsPIDOMWindowOuter> outerWindow = doc->GetWindow();
    865 
    866  // Send the event if the orientation was already overriden or different
    867  // from device metrics or the override was reset and it is different from
    868  // device metrics.
    869  if ((aBrowsingContext->HasOrientationOverride() &&
    870       (aOldHasOrientationOverride || aOverrideIsDifferentThanDevice)) ||
    871      (!aBrowsingContext->HasOrientationOverride() &&
    872       aOldHasOrientationOverride && aOverrideIsDifferentThanDevice)) {
    873    outerWindow->DispatchCustomEvent(u"orientationchange"_ns);
    874    MaybeDispatchChangeEvent(aBrowsingContext);
    875  }
    876 }
    877 
    878 void ScreenOrientation::UpdateActiveOrientationLock(
    879    hal::ScreenOrientation aOrientation) {
    880  if (aOrientation == hal::ScreenOrientation::None) {
    881    hal::UnlockScreenOrientation();
    882  } else {
    883    hal::LockScreenOrientation(aOrientation)
    884        ->Then(
    885            GetMainThreadSerialEventTarget(), __func__,
    886            [](const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) {
    887              NS_WARNING_ASSERTION(aValue.IsResolve(),
    888                                   "hal::LockScreenOrientation failed");
    889            });
    890  }
    891 }
    892 
    893 // static
    894 void ScreenOrientation::DispatchChangeEventToChildren(
    895    BrowsingContext* aBrowsingContext) {
    896  // XXX(m_kato):
    897  // If crossing process, child process's document might receive change event
    898  // before parent process is received.
    899  aBrowsingContext->PreOrderWalk([](BrowsingContext* aContext) {
    900    if (Document* doc = aContext->GetExtantDocument()) {
    901      if (auto* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow())) {
    902        if (win->HasScreen()) {
    903          ScreenOrientation* orientation = win->Screen()->Orientation();
    904          nsCOMPtr<nsIRunnable> runnable =
    905              orientation->DispatchChangeEventAndResolvePromise();
    906          DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
    907          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    908                               "NS_DispatchToMainThread failed");
    909        }
    910      }
    911    }
    912  });
    913 }
    914 
    915 nsCOMPtr<nsIRunnable>
    916 ScreenOrientation::DispatchChangeEventAndResolvePromise() {
    917  RefPtr<Document> doc = GetResponsibleDocument();
    918  RefPtr<ScreenOrientation> self = this;
    919  return NS_NewRunnableFunction(
    920      "dom::ScreenOrientation::DispatchChangeEvent", [self, doc]() {
    921        RefPtr<Promise> pendingPromise;
    922        if (doc) {
    923          pendingPromise = doc->GetOrientationPendingPromise();
    924          if (pendingPromise) {
    925            doc->ClearOrientationPendingPromise();
    926          }
    927        }
    928        DebugOnly<nsresult> rv = self->DispatchTrustedEvent(u"change"_ns);
    929        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
    930        if (pendingPromise) {
    931          pendingPromise->MaybeResolveWithUndefined();
    932        }
    933      });
    934 }
    935 
    936 JSObject* ScreenOrientation::WrapObject(JSContext* aCx,
    937                                        JS::Handle<JSObject*> aGivenProto) {
    938  return ScreenOrientation_Binding::Wrap(aCx, this, aGivenProto);
    939 }
    940 
    941 NS_IMPL_ISUPPORTS(ScreenOrientation::VisibleEventListener, nsIDOMEventListener)
    942 
    943 NS_IMETHODIMP
    944 ScreenOrientation::VisibleEventListener::HandleEvent(Event* aEvent) {
    945  // Document may have become visible, if the page is visible, run the steps
    946  // following the "now visible algorithm" as specified.
    947  MOZ_ASSERT(aEvent->GetCurrentTarget());
    948  nsCOMPtr<nsINode> eventTargetNode =
    949      nsINode::FromEventTarget(aEvent->GetCurrentTarget());
    950  if (!eventTargetNode || !eventTargetNode->IsDocument() ||
    951      eventTargetNode->AsDocument()->Hidden()) {
    952    return NS_OK;
    953  }
    954 
    955  RefPtr<Document> doc = eventTargetNode->AsDocument();
    956  auto* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow());
    957  if (!win) {
    958    return NS_OK;
    959  }
    960 
    961  ScreenOrientation* orientation = win->Screen()->Orientation();
    962  MOZ_ASSERT(orientation);
    963 
    964  doc->RemoveSystemEventListener(u"visibilitychange"_ns, this, true);
    965 
    966  BrowsingContext* bc = doc->GetBrowsingContext();
    967  if (bc && bc->GetCurrentOrientationType() !=
    968                orientation->DeviceType(CallerType::System)) {
    969    nsresult result =
    970        bc->SetCurrentOrientation(orientation->DeviceType(CallerType::System),
    971                                  orientation->DeviceAngle(CallerType::System));
    972    NS_ENSURE_SUCCESS(result, result);
    973 
    974    nsCOMPtr<nsIRunnable> runnable =
    975        orientation->DispatchChangeEventAndResolvePromise();
    976    MOZ_TRY(NS_DispatchToMainThread(runnable));
    977  }
    978  return NS_OK;
    979 }
    980 
    981 NS_IMPL_ISUPPORTS(ScreenOrientation::FullscreenEventListener,
    982                  nsIDOMEventListener)
    983 
    984 NS_IMETHODIMP
    985 ScreenOrientation::FullscreenEventListener::HandleEvent(Event* aEvent) {
    986 #ifdef DEBUG
    987  nsAutoString eventType;
    988  aEvent->GetType(eventType);
    989 
    990  MOZ_ASSERT(eventType.EqualsLiteral("fullscreenchange"));
    991 #endif
    992 
    993  EventTarget* target = aEvent->GetCurrentTarget();
    994  MOZ_ASSERT(target);
    995  MOZ_ASSERT(target->IsNode());
    996  RefPtr<Document> doc = nsINode::FromEventTarget(target)->AsDocument();
    997  MOZ_ASSERT(doc);
    998 
    999  // We have to make sure that the event we got is the event sent when
   1000  // fullscreen is disabled because we could get one when fullscreen
   1001  // got enabled if the lock call is done at the same moment.
   1002  if (doc->Fullscreen()) {
   1003    return NS_OK;
   1004  }
   1005 
   1006  BrowsingContext* bc = doc->GetBrowsingContext();
   1007  bc = bc ? bc->Top() : nullptr;
   1008  if (bc) {
   1009    bc->SetOrientationLock(hal::ScreenOrientation::None, IgnoreErrors());
   1010  }
   1011 
   1012  hal::UnlockScreenOrientation();
   1013 
   1014  target->RemoveSystemEventListener(u"fullscreenchange"_ns, this, true);
   1015  return NS_OK;
   1016 }