fune/dom/base/ScreenOrientation.cpp
Emilio Cobos Álvarez 28290f66db Bug 1754858 - Simplify screen orientation API implementation. r=smaug,m_kato,geckoview-reviewers
Make the ScreenOrientation part of the screen struct, as it should. Stop
using HAL to propagate just screen orientation updates, use the more
general screen manager.

Instead of HAL observers, add a simple observer service notification,
and clean a bunch of the code.

This will simplify bug 1754802 a bit, and is generally simpler.
Shouldn't change behavior. I've tested the events and some common
orientation locking use cases like Youtube, and they behave the same.

Differential Revision: https://phabricator.services.mozilla.com/D138477
2022-02-15 20:22:54 +00:00

690 lines
22 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 "nsGlobalWindow.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() {
UnlockDeviceOrientation();
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;
nsCOMPtr<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;
using LockOrientationPromise = MozPromise<bool, bool, false>;
bool ScreenOrientation::LockOrientationTask::OrientationLockContains(
OrientationType aOrientationType) {
return bool(mOrientationLock & OrientationTypeToInternal(aOrientationType));
}
NS_IMETHODIMP
ScreenOrientation::LockOrientationTask::Run() {
if (!mPromise) {
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;
}
RefPtr<LockOrientationPromise> lockOrientationPromise =
mScreenOrientation->LockDeviceOrientation(mOrientationLock,
mIsFullscreen);
if (NS_WARN_IF(!lockOrientationPromise)) {
mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
mDocument->ClearOrientationPendingPromise();
return NS_OK;
}
lockOrientationPromise->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}](
const LockOrientationPromise::ResolveOrRejectValue& aValue) {
if (aValue.IsResolve()) {
return NS_OK;
}
self->mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
self->mDocument->ClearOrientationPendingPromise();
return NS_OK;
});
BrowsingContext* bc = mDocument->GetBrowsingContext();
if (OrientationLockContains(bc->GetCurrentOrientationType()) ||
(mOrientationLock == hal::ScreenOrientation::Default &&
bc->GetCurrentOrientationAngle() == 0)) {
// Orientation lock will not cause an orientation change.
mPromise->MaybeResolveWithUndefined();
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);
}
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();
}
}
}
});
}
already_AddRefed<Promise> ScreenOrientation::LockInternal(
hal::ScreenOrientation aOrientation, ErrorResult& aRv) {
// Steps to apply an orientation lock as defined in spec.
Document* doc = GetResponsibleDocument();
if (NS_WARN_IF(!doc)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
if (NS_WARN_IF(!owner)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsCOMPtr<nsIDocShell> docShell = owner->GetDocShell();
if (NS_WARN_IF(!docShell)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
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 !defined(MOZ_WIDGET_ANDROID)
// 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();
}
LockPermission perm = GetLockOrientationPermission(true);
if (perm == LOCK_DENIED) {
p->MaybeReject(NS_ERROR_DOM_SECURITY_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();
}
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();
#endif
}
RefPtr<LockOrientationPromise> ScreenOrientation::LockDeviceOrientation(
hal::ScreenOrientation aOrientation, bool aIsFullscreen) {
if (!GetOwner()) {
return LockOrientationPromise::CreateAndReject(false, __func__);
}
nsCOMPtr<EventTarget> target = GetOwner()->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 LockOrientationPromise::CreateAndReject(false, __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 LockOrientationPromise::CreateAndReject(false, __func__);
}
}
RefPtr<LockOrientationPromise> halPromise =
hal::LockScreenOrientation(aOrientation);
if (!halPromise) {
return LockOrientationPromise::CreateAndReject(false, __func__);
}
mTriedToLockDeviceOrientation = true;
return halPromise;
}
void ScreenOrientation::Unlock(ErrorResult& aRv) {
RefPtr<Promise> p = LockInternal(hal::ScreenOrientation::None, aRv);
}
void ScreenOrientation::UnlockDeviceOrientation() {
if (mTriedToLockDeviceOrientation) {
hal::UnlockScreenOrientation();
mTriedToLockDeviceOrientation = false;
}
if (!mFullscreenListener || !GetOwner()) {
mFullscreenListener = nullptr;
return;
}
// Remove event listener in case of fullscreen lock.
if (nsCOMPtr<EventTarget> target = GetOwner()->GetDoc()) {
target->RemoveSystemEventListener(u"fullscreenchange"_ns,
mFullscreenListener,
/* useCapture */ true);
}
mFullscreenListener = nullptr;
}
OrientationType ScreenOrientation::DeviceType(CallerType aCallerType) const {
return nsContentUtils::ResistFingerprinting(aCallerType)
? OrientationType::Landscape_primary
: mType;
}
uint16_t ScreenOrientation::DeviceAngle(CallerType aCallerType) const {
return nsContentUtils::ResistFingerprinting(aCallerType) ? 0 : mAngle;
}
OrientationType ScreenOrientation::GetType(CallerType aCallerType,
ErrorResult& aRv) const {
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
return OrientationType::Landscape_primary;
}
Document* doc = GetResponsibleDocument();
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
if (!bc) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return OrientationType::Portrait_primary;
}
return bc->GetCurrentOrientationType();
}
uint16_t ScreenOrientation::GetAngle(CallerType aCallerType,
ErrorResult& aRv) const {
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
return 0;
}
Document* doc = GetResponsibleDocument();
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
if (!bc) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return 0;
}
return bc->GetCurrentOrientationAngle();
}
ScreenOrientation::LockPermission
ScreenOrientation::GetLockOrientationPermission(bool aCheckSandbox) const {
nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
if (!owner) {
return LOCK_DENIED;
}
// Chrome can always lock the screen orientation.
if (owner->GetBrowsingContext()->IsChrome()) {
return LOCK_ALLOWED;
}
nsCOMPtr<Document> doc = owner->GetDoc();
if (!doc || doc->Hidden()) {
return LOCK_DENIED;
}
// Sandboxed without "allow-orientation-lock"
if (aCheckSandbox && doc->GetSandboxFlags() & SANDBOXED_ORIENTATION_LOCK) {
return LOCK_DENIED;
}
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 doc->Fullscreen() ? FULLSCREEN_LOCK_ALLOWED : LOCK_DENIED;
}
Document* ScreenOrientation::GetResponsibleDocument() const {
nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
if (!owner) {
return nullptr;
}
return owner->GetDoc();
}
void ScreenOrientation::MaybeChanged() {
if (ShouldResistFingerprinting()) {
return;
}
Document* doc = GetResponsibleDocument();
BrowsingContext* bc = doc ? doc->GetBrowsingContext() : nullptr;
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");
nsCOMPtr<nsIRunnable> runnable = DispatchChangeEventAndResolvePromise();
rv = NS_DispatchToMainThread(runnable);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
}
}
void ScreenOrientation::UpdateActiveOrientationLock(
hal::ScreenOrientation aOrientation) {
if (aOrientation == hal::ScreenOrientation::None) {
hal::UnlockScreenOrientation();
} else {
hal::LockScreenOrientation(aOrientation)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[](const mozilla::MozPromise<bool, bool,
false>::ResolveOrRejectValue& aValue) {
NS_WARNING_ASSERTION(aValue.IsResolve(),
"hal::LockScreenOrientation failed");
});
}
}
nsCOMPtr<nsIRunnable>
ScreenOrientation::DispatchChangeEventAndResolvePromise() {
RefPtr<Document> doc = GetResponsibleDocument();
RefPtr<ScreenOrientation> self = this;
return NS_NewRunnableFunction(
"dom::ScreenOrientation::DispatchChangeEvent", [self, doc]() {
DebugOnly<nsresult> rv = self->DispatchTrustedEvent(u"change"_ns);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
if (doc) {
Promise* pendingPromise = doc->GetOrientationPendingPromise();
if (pendingPromise) {
pendingPromise->MaybeResolveWithUndefined();
doc->ClearOrientationPendingPromise();
}
}
});
}
JSObject* ScreenOrientation::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return ScreenOrientation_Binding::Wrap(aCx, this, aGivenProto);
}
bool ScreenOrientation::ShouldResistFingerprinting() const {
if (nsContentUtils::ShouldResistFingerprinting()) {
bool resist = false;
if (nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner()) {
resist = nsContentUtils::ShouldResistFingerprinting(owner->GetDocShell());
}
return resist;
}
return false;
}
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;
}
ErrorResult rv;
nsScreen* screen = win->GetScreen(rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
MOZ_ASSERT(screen);
ScreenOrientation* orientation = 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();
rv = NS_DispatchToMainThread(runnable);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
}
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;
}
hal::UnlockScreenOrientation();
target->RemoveSystemEventListener(u"fullscreenchange"_ns, this, true);
return NS_OK;
}