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 }