`orientation.unlock()` has to process the common safety checks [*1], then it throws an exception by common safety checks. But since old spec doesn't throw an exception, we don't throw it by `unlock()` now. So we should throw it even if calling unlock(). `lock-sandboxed-iframe.html` has a bug that it doesn't consider whether unlock throws an exception. So we have to fix it to implement the common safety check for unlock(). On macOS's native fullscreen implementation, fullscreen transition is asynchronous, so even if the promise of `requestFullscreen` is resolved, the window isn't still activated yet, visibility state may be still hidden. (This will be visible state soon). So I would like to disable native fullscreen on screen-orientation tests for this situation. *1 https://w3c.github.io/screen-orientation/#unlock-method Differential Revision: https://phabricator.services.mozilla.com/D241820
971 lines
32 KiB
C++
971 lines
32 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "ScreenOrientation.h"
|
|
#include "nsIDocShell.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsSandboxFlags.h"
|
|
#include "nsScreen.h"
|
|
|
|
#include "mozilla/DOMEventTargetHelper.h"
|
|
#include "mozilla/Hal.h"
|
|
#include "mozilla/Preferences.h"
|
|
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/dom/Event.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/StaticPrefs_browser.h"
|
|
#include "nsContentUtils.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(ScreenOrientation, DOMEventTargetHelper,
|
|
mScreen);
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScreenOrientation)
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(ScreenOrientation, DOMEventTargetHelper)
|
|
NS_IMPL_RELEASE_INHERITED(ScreenOrientation, DOMEventTargetHelper)
|
|
|
|
static OrientationType InternalOrientationToType(
|
|
hal::ScreenOrientation aOrientation) {
|
|
switch (aOrientation) {
|
|
case hal::ScreenOrientation::PortraitPrimary:
|
|
return OrientationType::Portrait_primary;
|
|
case hal::ScreenOrientation::PortraitSecondary:
|
|
return OrientationType::Portrait_secondary;
|
|
case hal::ScreenOrientation::LandscapePrimary:
|
|
return OrientationType::Landscape_primary;
|
|
case hal::ScreenOrientation::LandscapeSecondary:
|
|
return OrientationType::Landscape_secondary;
|
|
default:
|
|
MOZ_CRASH("Bad aOrientation value");
|
|
}
|
|
}
|
|
|
|
static hal::ScreenOrientation OrientationTypeToInternal(
|
|
OrientationType aOrientation) {
|
|
switch (aOrientation) {
|
|
case OrientationType::Portrait_primary:
|
|
return hal::ScreenOrientation::PortraitPrimary;
|
|
case OrientationType::Portrait_secondary:
|
|
return hal::ScreenOrientation::PortraitSecondary;
|
|
case OrientationType::Landscape_primary:
|
|
return hal::ScreenOrientation::LandscapePrimary;
|
|
case OrientationType::Landscape_secondary:
|
|
return hal::ScreenOrientation::LandscapeSecondary;
|
|
default:
|
|
MOZ_CRASH("Bad aOrientation value");
|
|
}
|
|
}
|
|
|
|
ScreenOrientation::ScreenOrientation(nsPIDOMWindowInner* aWindow,
|
|
nsScreen* aScreen)
|
|
: DOMEventTargetHelper(aWindow), mScreen(aScreen) {
|
|
MOZ_ASSERT(aWindow);
|
|
MOZ_ASSERT(aScreen);
|
|
|
|
mAngle = aScreen->GetOrientationAngle();
|
|
mType = InternalOrientationToType(aScreen->GetOrientationType());
|
|
|
|
Document* doc = GetResponsibleDocument();
|
|
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
|
|
if (bc && !bc->IsDiscarded() && !bc->InRDMPane()) {
|
|
MOZ_ALWAYS_SUCCEEDS(bc->SetCurrentOrientation(mType, mAngle));
|
|
}
|
|
}
|
|
|
|
ScreenOrientation::~ScreenOrientation() {
|
|
if (mTriedToLockDeviceOrientation) {
|
|
UnlockDeviceOrientation();
|
|
} else {
|
|
CleanupFullscreenListener();
|
|
}
|
|
|
|
MOZ_ASSERT(!mFullscreenListener);
|
|
}
|
|
|
|
class ScreenOrientation::FullscreenEventListener final
|
|
: public nsIDOMEventListener {
|
|
~FullscreenEventListener() = default;
|
|
|
|
public:
|
|
FullscreenEventListener() = default;
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIDOMEVENTLISTENER
|
|
};
|
|
|
|
class ScreenOrientation::VisibleEventListener final
|
|
: public nsIDOMEventListener {
|
|
~VisibleEventListener() = default;
|
|
|
|
public:
|
|
VisibleEventListener() = default;
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIDOMEVENTLISTENER
|
|
};
|
|
|
|
class ScreenOrientation::LockOrientationTask final : public nsIRunnable {
|
|
~LockOrientationTask();
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIRUNNABLE
|
|
|
|
LockOrientationTask(ScreenOrientation* aScreenOrientation, Promise* aPromise,
|
|
hal::ScreenOrientation aOrientationLock,
|
|
Document* aDocument, bool aIsFullscreen);
|
|
|
|
protected:
|
|
bool OrientationLockContains(OrientationType aOrientationType);
|
|
|
|
RefPtr<ScreenOrientation> mScreenOrientation;
|
|
RefPtr<Promise> mPromise;
|
|
hal::ScreenOrientation mOrientationLock;
|
|
WeakPtr<Document> mDocument;
|
|
bool mIsFullscreen;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ScreenOrientation::LockOrientationTask, nsIRunnable)
|
|
|
|
ScreenOrientation::LockOrientationTask::LockOrientationTask(
|
|
ScreenOrientation* aScreenOrientation, Promise* aPromise,
|
|
hal::ScreenOrientation aOrientationLock, Document* aDocument,
|
|
bool aIsFullscreen)
|
|
: mScreenOrientation(aScreenOrientation),
|
|
mPromise(aPromise),
|
|
mOrientationLock(aOrientationLock),
|
|
mDocument(aDocument),
|
|
mIsFullscreen(aIsFullscreen) {
|
|
MOZ_ASSERT(aScreenOrientation);
|
|
MOZ_ASSERT(aPromise);
|
|
MOZ_ASSERT(aDocument);
|
|
}
|
|
|
|
ScreenOrientation::LockOrientationTask::~LockOrientationTask() = default;
|
|
|
|
bool ScreenOrientation::LockOrientationTask::OrientationLockContains(
|
|
OrientationType aOrientationType) {
|
|
return bool(mOrientationLock & OrientationTypeToInternal(aOrientationType));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ScreenOrientation::LockOrientationTask::Run() {
|
|
if (!mPromise) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mDocument) {
|
|
mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> owner = mScreenOrientation->GetOwnerWindow();
|
|
if (!owner || !owner->IsFullyActive()) {
|
|
mPromise->MaybeRejectWithAbortError("The document is not fully active.");
|
|
return NS_OK;
|
|
}
|
|
|
|
// Step to lock the orientation as defined in the spec.
|
|
if (mDocument->GetOrientationPendingPromise() != mPromise) {
|
|
// The document's pending promise is not associated with this task
|
|
// to lock orientation. There has since been another request to
|
|
// lock orientation, thus we don't need to do anything. Old promise
|
|
// should be been rejected.
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mDocument->Hidden()) {
|
|
// Active orientation lock is not the document's orientation lock.
|
|
mPromise->MaybeResolveWithUndefined();
|
|
mDocument->ClearOrientationPendingPromise();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mOrientationLock == hal::ScreenOrientation::None) {
|
|
mScreenOrientation->UnlockDeviceOrientation();
|
|
mPromise->MaybeResolveWithUndefined();
|
|
mDocument->ClearOrientationPendingPromise();
|
|
return NS_OK;
|
|
}
|
|
|
|
BrowsingContext* bc = mDocument->GetBrowsingContext();
|
|
if (!bc) {
|
|
mPromise->MaybeResolveWithUndefined();
|
|
mDocument->ClearOrientationPendingPromise();
|
|
return NS_OK;
|
|
}
|
|
|
|
OrientationType previousOrientationType = bc->GetCurrentOrientationType();
|
|
mScreenOrientation->LockDeviceOrientation(mOrientationLock, mIsFullscreen)
|
|
->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, previousOrientationType](
|
|
const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) {
|
|
if (self->mPromise->State() != Promise::PromiseState::Pending) {
|
|
// mPromise is already resolved or rejected by
|
|
// DispatchChangeEventAndResolvePromise() or
|
|
// AbortInProcessOrientationPromises().
|
|
return;
|
|
}
|
|
|
|
if (!self->mDocument) {
|
|
self->mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
|
return;
|
|
}
|
|
|
|
if (self->mDocument->GetOrientationPendingPromise() !=
|
|
self->mPromise) {
|
|
// mPromise is old promise now and document has new promise by
|
|
// later `orientation.lock` call. Old promise is already rejected
|
|
// by AbortInProcessOrientationPromises()
|
|
return;
|
|
}
|
|
if (aValue.IsResolve()) {
|
|
// LockDeviceOrientation won't change orientation, so change
|
|
// event isn't fired.
|
|
if (BrowsingContext* bc = self->mDocument->GetBrowsingContext()) {
|
|
OrientationType currentOrientationType =
|
|
bc->GetCurrentOrientationType();
|
|
if ((previousOrientationType == currentOrientationType &&
|
|
self->OrientationLockContains(currentOrientationType)) ||
|
|
(self->mOrientationLock ==
|
|
hal::ScreenOrientation::Default &&
|
|
bc->GetCurrentOrientationAngle() == 0)) {
|
|
// Orientation lock will not cause an orientation change, so
|
|
// we need to manually resolve the promise here.
|
|
self->mPromise->MaybeResolveWithUndefined();
|
|
self->mDocument->ClearOrientationPendingPromise();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
self->mPromise->MaybeReject(aValue.RejectValue());
|
|
self->mDocument->ClearOrientationPendingPromise();
|
|
});
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<Promise> ScreenOrientation::Lock(
|
|
OrientationLockType aOrientation, ErrorResult& aRv) {
|
|
hal::ScreenOrientation orientation = hal::ScreenOrientation::None;
|
|
|
|
switch (aOrientation) {
|
|
case OrientationLockType::Any:
|
|
orientation = hal::ScreenOrientation::PortraitPrimary |
|
|
hal::ScreenOrientation::PortraitSecondary |
|
|
hal::ScreenOrientation::LandscapePrimary |
|
|
hal::ScreenOrientation::LandscapeSecondary;
|
|
break;
|
|
case OrientationLockType::Natural:
|
|
orientation |= hal::ScreenOrientation::Default;
|
|
break;
|
|
case OrientationLockType::Landscape:
|
|
orientation = hal::ScreenOrientation::LandscapePrimary |
|
|
hal::ScreenOrientation::LandscapeSecondary;
|
|
break;
|
|
case OrientationLockType::Portrait:
|
|
orientation = hal::ScreenOrientation::PortraitPrimary |
|
|
hal::ScreenOrientation::PortraitSecondary;
|
|
break;
|
|
case OrientationLockType::Portrait_primary:
|
|
orientation = hal::ScreenOrientation::PortraitPrimary;
|
|
break;
|
|
case OrientationLockType::Portrait_secondary:
|
|
orientation = hal::ScreenOrientation::PortraitSecondary;
|
|
break;
|
|
case OrientationLockType::Landscape_primary:
|
|
orientation = hal::ScreenOrientation::LandscapePrimary;
|
|
break;
|
|
case OrientationLockType::Landscape_secondary:
|
|
orientation = hal::ScreenOrientation::LandscapeSecondary;
|
|
break;
|
|
default:
|
|
NS_WARNING("Unexpected orientation type");
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return nullptr;
|
|
}
|
|
|
|
return LockInternal(orientation, aRv);
|
|
}
|
|
|
|
// Wait for document entered fullscreen.
|
|
class FullscreenWaitListener final : public nsIDOMEventListener {
|
|
private:
|
|
~FullscreenWaitListener() = default;
|
|
|
|
public:
|
|
FullscreenWaitListener() = default;
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
// When we have pending fullscreen request, we will wait for the completion or
|
|
// cancel of it.
|
|
RefPtr<GenericPromise> Promise(Document* aDocument) {
|
|
if (aDocument->Fullscreen()) {
|
|
return GenericPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
if (NS_FAILED(InstallEventListener(aDocument))) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
MOZ_ASSERT(aDocument->HasPendingFullscreenRequests());
|
|
return mHolder.Ensure(__func__);
|
|
}
|
|
|
|
NS_IMETHODIMP HandleEvent(Event* aEvent) override {
|
|
nsAutoString eventType;
|
|
aEvent->GetType(eventType);
|
|
|
|
if (eventType.EqualsLiteral("pagehide")) {
|
|
mHolder.Reject(NS_ERROR_FAILURE, __func__);
|
|
CleanupEventListener();
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(eventType.EqualsLiteral("fullscreenchange") ||
|
|
eventType.EqualsLiteral("fullscreenerror") ||
|
|
eventType.EqualsLiteral("pagehide"));
|
|
if (mDocument->Fullscreen()) {
|
|
mHolder.Resolve(true, __func__);
|
|
} else {
|
|
mHolder.Reject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
CleanupEventListener();
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsresult InstallEventListener(Document* aDoc) {
|
|
if (mDocument) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mDocument = aDoc;
|
|
nsresult rv = aDoc->AddSystemEventListener(u"fullscreenchange"_ns, this,
|
|
/* aUseCapture = */ true);
|
|
if (NS_FAILED(rv)) {
|
|
CleanupEventListener();
|
|
return rv;
|
|
}
|
|
|
|
rv = aDoc->AddSystemEventListener(u"fullscreenerror"_ns, this,
|
|
/* aUseCapture = */ true);
|
|
if (NS_FAILED(rv)) {
|
|
CleanupEventListener();
|
|
return rv;
|
|
}
|
|
|
|
nsPIDOMWindowOuter* window = aDoc->GetWindow();
|
|
nsCOMPtr<EventTarget> target = do_QueryInterface(window);
|
|
if (!target) {
|
|
CleanupEventListener();
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
rv = target->AddSystemEventListener(u"pagehide"_ns, this,
|
|
/* aUseCapture = */ true,
|
|
/* aWantsUntrusted = */ false);
|
|
if (NS_FAILED(rv)) {
|
|
CleanupEventListener();
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void CleanupEventListener() {
|
|
if (!mDocument) {
|
|
return;
|
|
}
|
|
RefPtr<FullscreenWaitListener> kungFuDeathGrip(this);
|
|
mDocument->RemoveSystemEventListener(u"fullscreenchange"_ns, this, true);
|
|
mDocument->RemoveSystemEventListener(u"fullscreenerror"_ns, this, true);
|
|
nsPIDOMWindowOuter* window = mDocument->GetWindow();
|
|
nsCOMPtr<EventTarget> target = do_QueryInterface(window);
|
|
if (target) {
|
|
target->RemoveSystemEventListener(u"pagehide"_ns, this, true);
|
|
}
|
|
mDocument = nullptr;
|
|
}
|
|
|
|
MozPromiseHolder<GenericPromise> mHolder;
|
|
RefPtr<Document> mDocument;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(FullscreenWaitListener, nsIDOMEventListener)
|
|
|
|
void ScreenOrientation::AbortInProcessOrientationPromises(
|
|
BrowsingContext* aBrowsingContext) {
|
|
MOZ_ASSERT(aBrowsingContext);
|
|
|
|
aBrowsingContext = aBrowsingContext->Top();
|
|
aBrowsingContext->PreOrderWalk([](BrowsingContext* aContext) {
|
|
nsIDocShell* docShell = aContext->GetDocShell();
|
|
if (docShell) {
|
|
Document* doc = docShell->GetDocument();
|
|
if (doc) {
|
|
Promise* promise = doc->GetOrientationPendingPromise();
|
|
if (promise) {
|
|
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
|
doc->ClearOrientationPendingPromise();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// https://w3c.github.io/screen-orientation/#dfn-common-safety-checks.
|
|
|
|
// static
|
|
bool ScreenOrientation::CommonSafetyChecks(nsPIDOMWindowInner* aOwner,
|
|
Document* aDocument,
|
|
ErrorResult& aRv) {
|
|
MOZ_ASSERT(aOwner);
|
|
MOZ_ASSERT(aDocument);
|
|
|
|
// Chrome can always lock the screen orientation.
|
|
if (aOwner->GetBrowsingContext()->IsChrome()) {
|
|
return true;
|
|
}
|
|
|
|
// 5.4.1.
|
|
// If document is not fully active, throw an "InvalidStateError" DOMException.
|
|
if (!aOwner->IsFullyActive()) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return false;
|
|
}
|
|
|
|
// 5.4.2.
|
|
// If document has the sandboxed orientation lock browsing context flag set,
|
|
// throw "SecurityError" DOMException.
|
|
if (aDocument->GetSandboxFlags() & SANDBOXED_ORIENTATION_LOCK) {
|
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
|
return false;
|
|
}
|
|
|
|
// 5.4.3.
|
|
// If document's visibility state is "hidden", throw "SecurityError"
|
|
// DOMException.
|
|
if (aDocument->Hidden()) {
|
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
already_AddRefed<Promise> ScreenOrientation::LockInternal(
|
|
hal::ScreenOrientation aOrientation, ErrorResult& aRv) {
|
|
// Steps to apply an orientation lock as defined in spec.
|
|
|
|
// Step 1.
|
|
// Let document be this's relevant global object's associated Document.
|
|
|
|
Document* doc = GetResponsibleDocument();
|
|
if (NS_WARN_IF(!doc)) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> owner = GetOwnerWindow();
|
|
if (NS_WARN_IF(!owner)) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = owner->GetDocShell();
|
|
if (NS_WARN_IF(!docShell)) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(owner);
|
|
MOZ_ASSERT(go);
|
|
RefPtr<Promise> p = Promise::Create(go, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!CommonSafetyChecks(owner, doc, aRv)) {
|
|
if (aOrientation == hal::ScreenOrientation::None) {
|
|
// When unlock, throws a DOM exception.
|
|
return nullptr;
|
|
}
|
|
p->MaybeReject(aRv.StealNSResult());
|
|
return p.forget();
|
|
}
|
|
|
|
// If document doesn't meet the pre-lock conditions, or locking would be a
|
|
// security risk, return a promise rejected with a "SecurityError"
|
|
// DOMException and abort these steps.
|
|
|
|
LockPermission perm = GetLockOrientationPermission(owner, doc);
|
|
if (perm == LOCK_DENIED) {
|
|
p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
|
return p.forget();
|
|
}
|
|
|
|
// Step 4.
|
|
// If the user agent does not support locking the screen orientation to
|
|
// orientation, return a promise rejected with a "NotSupportedError"
|
|
// DOMException and abort these steps.
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID) && !defined(XP_WIN)
|
|
// User agent does not support locking the screen orientation.
|
|
p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
|
return p.forget();
|
|
#else
|
|
// Bypass locking screen orientation if preference is false
|
|
if (!StaticPrefs::dom_screenorientation_allow_lock()) {
|
|
p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
|
return p.forget();
|
|
}
|
|
|
|
RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
|
|
bc = bc ? bc->Top() : nullptr;
|
|
if (!bc) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return nullptr;
|
|
}
|
|
|
|
bc->SetOrientationLock(aOrientation, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
AbortInProcessOrientationPromises(bc);
|
|
dom::ContentChild::GetSingleton()->SendAbortOtherOrientationPendingPromises(
|
|
bc);
|
|
|
|
if (!doc->SetOrientationPendingPromise(p)) {
|
|
p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
|
return p.forget();
|
|
}
|
|
|
|
if (perm == LOCK_ALLOWED || doc->Fullscreen()) {
|
|
nsCOMPtr<nsIRunnable> lockOrientationTask = new LockOrientationTask(
|
|
this, p, aOrientation, doc, perm == FULLSCREEN_LOCK_ALLOWED);
|
|
aRv = NS_DispatchToMainThread(lockOrientationTask);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return p.forget();
|
|
}
|
|
|
|
MOZ_ASSERT(perm == FULLSCREEN_LOCK_ALLOWED);
|
|
|
|
// Full screen state is pending. We have to wait for the completion.
|
|
RefPtr<FullscreenWaitListener> listener = new FullscreenWaitListener();
|
|
RefPtr<Promise> promise = p;
|
|
listener->Promise(doc)->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, promise = std::move(promise), aOrientation,
|
|
document =
|
|
RefPtr{doc}](const GenericPromise::ResolveOrRejectValue& aValue) {
|
|
if (aValue.IsResolve()) {
|
|
nsCOMPtr<nsIRunnable> lockOrientationTask = new LockOrientationTask(
|
|
self, promise, aOrientation, document, true);
|
|
nsresult rv = NS_DispatchToMainThread(lockOrientationTask);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return;
|
|
}
|
|
}
|
|
// Pending full screen request is canceled or causes an error.
|
|
if (document->GetOrientationPendingPromise() != promise) {
|
|
// The document's pending promise is not associated with
|
|
// this promise.
|
|
return;
|
|
}
|
|
// pre-lock conditions aren't matched.
|
|
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
|
document->ClearOrientationPendingPromise();
|
|
});
|
|
|
|
return p.forget();
|
|
#endif
|
|
}
|
|
|
|
RefPtr<GenericNonExclusivePromise> ScreenOrientation::LockDeviceOrientation(
|
|
hal::ScreenOrientation aOrientation, bool aIsFullscreen) {
|
|
if (!GetOwnerWindow()) {
|
|
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
|
|
__func__);
|
|
}
|
|
|
|
nsCOMPtr<EventTarget> target = GetOwnerWindow()->GetDoc();
|
|
// We need to register a listener so we learn when we leave fullscreen
|
|
// and when we will have to unlock the screen.
|
|
// This needs to be done before LockScreenOrientation call to make sure
|
|
// the locking can be unlocked.
|
|
if (aIsFullscreen && !target) {
|
|
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
|
|
__func__);
|
|
}
|
|
|
|
// We are fullscreen and lock has been accepted.
|
|
if (aIsFullscreen) {
|
|
if (!mFullscreenListener) {
|
|
mFullscreenListener = new FullscreenEventListener();
|
|
}
|
|
|
|
nsresult rv = target->AddSystemEventListener(u"fullscreenchange"_ns,
|
|
mFullscreenListener,
|
|
/* aUseCapture = */ true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR,
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
mTriedToLockDeviceOrientation = true;
|
|
return hal::LockScreenOrientation(aOrientation);
|
|
}
|
|
|
|
void ScreenOrientation::Unlock(ErrorResult& aRv) {
|
|
if (RefPtr<Promise> p = LockInternal(hal::ScreenOrientation::None, aRv)) {
|
|
// Don't end up reporting unhandled promise rejection since
|
|
// screen.orientation.unlock doesn't return promise.
|
|
MOZ_ALWAYS_TRUE(p->SetAnyPromiseIsHandled());
|
|
}
|
|
}
|
|
|
|
void ScreenOrientation::UnlockDeviceOrientation() {
|
|
hal::UnlockScreenOrientation();
|
|
CleanupFullscreenListener();
|
|
}
|
|
|
|
void ScreenOrientation::CleanupFullscreenListener() {
|
|
if (!mFullscreenListener || !GetOwnerWindow()) {
|
|
mFullscreenListener = nullptr;
|
|
return;
|
|
}
|
|
|
|
// Remove event listener in case of fullscreen lock.
|
|
if (nsCOMPtr<EventTarget> target = GetOwnerWindow()->GetDoc()) {
|
|
target->RemoveSystemEventListener(u"fullscreenchange"_ns,
|
|
mFullscreenListener,
|
|
/* useCapture */ true);
|
|
}
|
|
|
|
mFullscreenListener = nullptr;
|
|
}
|
|
|
|
OrientationType ScreenOrientation::DeviceType(CallerType aCallerType) const {
|
|
if (nsContentUtils::ShouldResistFingerprinting(
|
|
aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
|
|
Document* doc = GetResponsibleDocument();
|
|
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
|
|
if (!bc) {
|
|
return nsRFPService::GetDefaultOrientationType();
|
|
}
|
|
CSSIntSize size = bc->GetTopInnerSizeForRFP();
|
|
return nsRFPService::ViewportSizeToOrientationType(size.width, size.height);
|
|
}
|
|
return mType;
|
|
}
|
|
|
|
uint16_t ScreenOrientation::DeviceAngle(CallerType aCallerType) const {
|
|
if (nsContentUtils::ShouldResistFingerprinting(
|
|
aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
|
|
Document* doc = GetResponsibleDocument();
|
|
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
|
|
if (!bc) {
|
|
return 0;
|
|
}
|
|
CSSIntSize size = bc->GetTopInnerSizeForRFP();
|
|
return nsRFPService::ViewportSizeToAngle(size.width, size.height);
|
|
}
|
|
return mAngle;
|
|
}
|
|
|
|
OrientationType ScreenOrientation::GetType(CallerType aCallerType,
|
|
ErrorResult& aRv) const {
|
|
Document* doc = GetResponsibleDocument();
|
|
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
|
|
if (!bc) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return OrientationType::Portrait_primary;
|
|
}
|
|
|
|
OrientationType orientation = bc->GetCurrentOrientationType();
|
|
if (nsContentUtils::ShouldResistFingerprinting(
|
|
aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
|
|
CSSIntSize size = bc->GetTopInnerSizeForRFP();
|
|
return nsRFPService::ViewportSizeToOrientationType(size.width, size.height);
|
|
}
|
|
return orientation;
|
|
}
|
|
|
|
uint16_t ScreenOrientation::GetAngle(CallerType aCallerType,
|
|
ErrorResult& aRv) const {
|
|
Document* doc = GetResponsibleDocument();
|
|
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
|
|
if (!bc) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return 0;
|
|
}
|
|
|
|
uint16_t angle = static_cast<uint16_t>(bc->GetCurrentOrientationAngle());
|
|
if (nsContentUtils::ShouldResistFingerprinting(
|
|
aCallerType, GetOwnerGlobal(), RFPTarget::ScreenOrientation)) {
|
|
CSSIntSize size = bc->GetTopInnerSizeForRFP();
|
|
return nsRFPService::ViewportSizeToAngle(size.width, size.height);
|
|
}
|
|
return angle;
|
|
}
|
|
|
|
// static
|
|
ScreenOrientation::LockPermission
|
|
ScreenOrientation::GetLockOrientationPermission(nsPIDOMWindowInner* aOwner,
|
|
Document* aDocument) {
|
|
MOZ_ASSERT(aOwner);
|
|
MOZ_ASSERT(aDocument);
|
|
|
|
// Chrome can always lock the screen orientation.
|
|
if (aOwner->GetBrowsingContext()->IsChrome()) {
|
|
return LOCK_ALLOWED;
|
|
}
|
|
|
|
if (Preferences::GetBool(
|
|
"dom.screenorientation.testing.non_fullscreen_lock_allow", false)) {
|
|
return LOCK_ALLOWED;
|
|
}
|
|
|
|
// Other content must be fullscreen in order to lock orientation.
|
|
return aDocument->Fullscreen() || aDocument->HasPendingFullscreenRequests()
|
|
? FULLSCREEN_LOCK_ALLOWED
|
|
: LOCK_DENIED;
|
|
}
|
|
|
|
Document* ScreenOrientation::GetResponsibleDocument() const {
|
|
nsCOMPtr<nsPIDOMWindowInner> owner = GetOwnerWindow();
|
|
if (!owner) {
|
|
return nullptr;
|
|
}
|
|
|
|
return owner->GetDoc();
|
|
}
|
|
|
|
void ScreenOrientation::MaybeChanged() {
|
|
Document* doc = GetResponsibleDocument();
|
|
if (!doc || doc->ShouldResistFingerprinting(RFPTarget::ScreenOrientation)) {
|
|
return;
|
|
}
|
|
|
|
BrowsingContext* bc = doc->GetBrowsingContext();
|
|
if (!bc) {
|
|
return;
|
|
}
|
|
|
|
hal::ScreenOrientation orientation = mScreen->GetOrientationType();
|
|
if (orientation != hal::ScreenOrientation::PortraitPrimary &&
|
|
orientation != hal::ScreenOrientation::PortraitSecondary &&
|
|
orientation != hal::ScreenOrientation::LandscapePrimary &&
|
|
orientation != hal::ScreenOrientation::LandscapeSecondary) {
|
|
// The platform may notify of some other values from
|
|
// an orientation lock, but we only care about real
|
|
// changes to screen orientation which result in one of
|
|
// the values we care about.
|
|
return;
|
|
}
|
|
|
|
OrientationType previousOrientation = mType;
|
|
mAngle = mScreen->GetOrientationAngle();
|
|
mType = InternalOrientationToType(orientation);
|
|
|
|
DebugOnly<nsresult> rv;
|
|
if (mScreen && mType != previousOrientation) {
|
|
// Use of mozorientationchange is deprecated.
|
|
rv = mScreen->DispatchTrustedEvent(u"mozorientationchange"_ns);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
|
|
}
|
|
|
|
if (doc->Hidden() && !mVisibleListener) {
|
|
mVisibleListener = new VisibleEventListener();
|
|
rv = doc->AddSystemEventListener(u"visibilitychange"_ns, mVisibleListener,
|
|
/* aUseCapture = */ true);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddSystemEventListener failed");
|
|
return;
|
|
}
|
|
|
|
if (mType != bc->GetCurrentOrientationType()) {
|
|
rv = bc->SetCurrentOrientation(mType, mAngle);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetCurrentOrientation failed");
|
|
|
|
// change event has to be dispatched by descendantDocs.
|
|
// Looking for top level browsing context that has screen in process.
|
|
// If parent document has screen, we don't dispatch it at this time.
|
|
// change event will be dispatched by parent's
|
|
// ScreenOrientation::MaybeChanged.
|
|
BrowsingContext* rootBc = bc;
|
|
bool dispatchChangeEvent = true;
|
|
while (rootBc->GetParent()) {
|
|
rootBc = rootBc->GetParent();
|
|
if (Document* doc = rootBc->GetExtantDocument()) {
|
|
if (auto* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow())) {
|
|
if (win->HasScreen()) {
|
|
// Parent of browsing context has screen object. Child shouldn't
|
|
// dispatch change event.
|
|
dispatchChangeEvent = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (dispatchChangeEvent) {
|
|
DispatchChangeEventToChildren(rootBc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScreenOrientation::UpdateActiveOrientationLock(
|
|
hal::ScreenOrientation aOrientation) {
|
|
if (aOrientation == hal::ScreenOrientation::None) {
|
|
hal::UnlockScreenOrientation();
|
|
} else {
|
|
hal::LockScreenOrientation(aOrientation)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[](const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) {
|
|
NS_WARNING_ASSERTION(aValue.IsResolve(),
|
|
"hal::LockScreenOrientation failed");
|
|
});
|
|
}
|
|
}
|
|
|
|
// static
|
|
void ScreenOrientation::DispatchChangeEventToChildren(
|
|
BrowsingContext* aBrowsingContext) {
|
|
// XXX(m_kato):
|
|
// If crossing process, child process's document might receive change event
|
|
// before parent process is received.
|
|
aBrowsingContext->PreOrderWalk([](BrowsingContext* aContext) {
|
|
if (Document* doc = aContext->GetExtantDocument()) {
|
|
if (auto* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow())) {
|
|
if (win->HasScreen()) {
|
|
ScreenOrientation* orientation = win->Screen()->Orientation();
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
orientation->DispatchChangeEventAndResolvePromise();
|
|
DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"NS_DispatchToMainThread failed");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable>
|
|
ScreenOrientation::DispatchChangeEventAndResolvePromise() {
|
|
RefPtr<Document> doc = GetResponsibleDocument();
|
|
RefPtr<ScreenOrientation> self = this;
|
|
return NS_NewRunnableFunction(
|
|
"dom::ScreenOrientation::DispatchChangeEvent", [self, doc]() {
|
|
RefPtr<Promise> pendingPromise;
|
|
if (doc) {
|
|
pendingPromise = doc->GetOrientationPendingPromise();
|
|
if (pendingPromise) {
|
|
doc->ClearOrientationPendingPromise();
|
|
}
|
|
}
|
|
DebugOnly<nsresult> rv = self->DispatchTrustedEvent(u"change"_ns);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
|
|
if (pendingPromise) {
|
|
pendingPromise->MaybeResolveWithUndefined();
|
|
}
|
|
});
|
|
}
|
|
|
|
JSObject* ScreenOrientation::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return ScreenOrientation_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ScreenOrientation::VisibleEventListener, nsIDOMEventListener)
|
|
|
|
NS_IMETHODIMP
|
|
ScreenOrientation::VisibleEventListener::HandleEvent(Event* aEvent) {
|
|
// Document may have become visible, if the page is visible, run the steps
|
|
// following the "now visible algorithm" as specified.
|
|
MOZ_ASSERT(aEvent->GetCurrentTarget());
|
|
nsCOMPtr<nsINode> eventTargetNode =
|
|
nsINode::FromEventTarget(aEvent->GetCurrentTarget());
|
|
if (!eventTargetNode || !eventTargetNode->IsDocument() ||
|
|
eventTargetNode->AsDocument()->Hidden()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<Document> doc = eventTargetNode->AsDocument();
|
|
auto* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow());
|
|
if (!win) {
|
|
return NS_OK;
|
|
}
|
|
|
|
ScreenOrientation* orientation = win->Screen()->Orientation();
|
|
MOZ_ASSERT(orientation);
|
|
|
|
doc->RemoveSystemEventListener(u"visibilitychange"_ns, this, true);
|
|
|
|
BrowsingContext* bc = doc->GetBrowsingContext();
|
|
if (bc && bc->GetCurrentOrientationType() !=
|
|
orientation->DeviceType(CallerType::System)) {
|
|
nsresult result =
|
|
bc->SetCurrentOrientation(orientation->DeviceType(CallerType::System),
|
|
orientation->DeviceAngle(CallerType::System));
|
|
NS_ENSURE_SUCCESS(result, result);
|
|
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
orientation->DispatchChangeEventAndResolvePromise();
|
|
MOZ_TRY(NS_DispatchToMainThread(runnable));
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ScreenOrientation::FullscreenEventListener,
|
|
nsIDOMEventListener)
|
|
|
|
NS_IMETHODIMP
|
|
ScreenOrientation::FullscreenEventListener::HandleEvent(Event* aEvent) {
|
|
#ifdef DEBUG
|
|
nsAutoString eventType;
|
|
aEvent->GetType(eventType);
|
|
|
|
MOZ_ASSERT(eventType.EqualsLiteral("fullscreenchange"));
|
|
#endif
|
|
|
|
EventTarget* target = aEvent->GetCurrentTarget();
|
|
MOZ_ASSERT(target);
|
|
MOZ_ASSERT(target->IsNode());
|
|
RefPtr<Document> doc = nsINode::FromEventTarget(target)->AsDocument();
|
|
MOZ_ASSERT(doc);
|
|
|
|
// We have to make sure that the event we got is the event sent when
|
|
// fullscreen is disabled because we could get one when fullscreen
|
|
// got enabled if the lock call is done at the same moment.
|
|
if (doc->Fullscreen()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
BrowsingContext* bc = doc->GetBrowsingContext();
|
|
bc = bc ? bc->Top() : nullptr;
|
|
if (bc) {
|
|
bc->SetOrientationLock(hal::ScreenOrientation::None, IgnoreErrors());
|
|
}
|
|
|
|
hal::UnlockScreenOrientation();
|
|
|
|
target->RemoveSystemEventListener(u"fullscreenchange"_ns, this, true);
|
|
return NS_OK;
|
|
}
|