Bug 1697647 - Add screen orientation lock api r=ipc-reviewers,mccr8,agi,smaug,jonalmeida

Previously, the screenOrientation.lock API was for Fennec and not supported for Fenix and multi-process use. The overall idea is to now allow apps to use the API through a delegate and make asynchronous calls to LockDeviceOrientation. This required replacing the existing code that returned a default false bool to calls that perform the requested orientation change and instead return a promise that contained either an allow or deny value.

Returning a promise instead of a bool involved changing the API calls from the C++ side to Java. The new general control flow of screenOrientation lock follows: an app calls C++ ScreenOrientation.lock() which eventually dispatches LockOrientationTask to resolve the pending orientation promise. Hal.cpp sends an IPC call to the content process and RecvLockScreenOrientation retrieves the current instance of geckoRuntime and calls the java side LockScreenOrientation. Apps must create a delegate and override onOrientationLock to set the requested orientation. In geckoview's testing, this is done with the android API setRequestedOrientation. Once a device orientation change has been triggered, native OnOrientationChange calls to NotifyScreenConfigurationChange, which notifies all observers and dispatches a change event to resolve the pending orientation promise.

Testing:
I used a demo on the GeckoView Example (https://usefulangle.com/demos/105/screen.html) to test locking to landscape orientation. This required a change to the GVE to show the app from recreating the whole thing on orientation change. In the example AndroidManifest xml file, `orientation` prevents restart when orientation changes.

The Junit/Kotlin tests were to verify that the expected orientation delegate was called with the expected new orientation value, in an orientation change, if the new orientation was the same as the current, and if the pre-lock conditions such as being fullscreen were not met.

A static preference `dom.screenorientation.allow-lock` was added to the dom group, since it affects the ui dom) and is currently turned off. C++ can access it through its mirrored variable dom_screenorientation_allow_lock (same name but with underscores). The junit tests turn the preference on and test the lock feature.

Reference:
Orientation constant values:
    C++
        1 ScreenOrientation_PortraitPrimary); - vertical with button at bottom
        2 ScreenOrientation_PortraitSecondary); - vertical with button at top
        4 ScreenOrientation_LandscapePrimary); - horizational w button right
        8 ScreenOrientation_LandscapeSecondary); - horization button left
        16 ScreenOrientation_Default);
    Java
        1 GeckoScreenOrientation.ScreenOrientation.PORTRAIT_PRIMARY.value
        2 GeckoScreenOrientation.ScreenOrientation.PORTRAIT_SECONDARY.value
        4 GeckoScreenOrientation.ScreenOrientation.LANDSCAPE_PRIMARY.value
        8 GeckoScreenOrientation.ScreenOrientation.LANDSCAPE_SECONDARY.value

    Java public API
        0 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
        1 Activitynfo.SCREEN_ORIENTATION_PORTRAIT

    Android
        1 ORIENTATION_PORTRAIT
        2 ORIENTATION_LANDSCAPE

Differential Revision: https://phabricator.services.mozilla.com/D129427
This commit is contained in:
Cathy Lu 2021-12-03 23:49:25 +00:00
Родитель daea1328b7
Коммит 3e9924d513
24 изменённых файлов: 433 добавлений и 132 удалений

Просмотреть файл

@ -18,6 +18,7 @@
#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;
@ -158,8 +159,11 @@ bool ScreenOrientation::LockOrientationTask::OrientationLockContains(
NS_IMETHODIMP
ScreenOrientation::LockOrientationTask::Run() {
// Step to lock the orientation as defined in the spec.
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
@ -182,19 +186,29 @@ ScreenOrientation::LockOrientationTask::Run() {
return NS_OK;
}
ErrorResult rv;
bool result = mScreenOrientation->LockDeviceOrientation(mOrientationLock,
mIsFullscreen, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
RefPtr<MozPromise<bool, bool, false>> lockOrientationPromise =
mScreenOrientation->LockDeviceOrientation(mOrientationLock,
mIsFullscreen);
if (NS_WARN_IF(!result)) {
if (NS_WARN_IF(!lockOrientationPromise)) {
mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
mDocument->ClearOrientationPendingPromise();
return NS_OK;
}
lockOrientationPromise->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}](
const mozilla::MozPromise<bool, bool, false>::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::eScreenOrientation_Default &&
@ -304,6 +318,12 @@ already_AddRefed<Promise> ScreenOrientation::LockInternal(
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);
@ -342,11 +362,10 @@ already_AddRefed<Promise> ScreenOrientation::LockInternal(
#endif
}
bool ScreenOrientation::LockDeviceOrientation(
hal::ScreenOrientation aOrientation, bool aIsFullscreen, ErrorResult& aRv) {
RefPtr<MozPromise<bool, bool, false>> ScreenOrientation::LockDeviceOrientation(
hal::ScreenOrientation aOrientation, bool aIsFullscreen) {
if (!GetOwner()) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return false;
return MozPromise<bool, bool, false>::CreateAndReject(false, __func__);
}
nsCOMPtr<EventTarget> target = GetOwner()->GetDoc();
@ -355,11 +374,7 @@ bool ScreenOrientation::LockDeviceOrientation(
// This needs to be done before LockScreenOrientation call to make sure
// the locking can be unlocked.
if (aIsFullscreen && !target) {
return false;
}
if (NS_WARN_IF(!hal::LockScreenOrientation(aOrientation))) {
return false;
return MozPromise<bool, bool, false>::CreateAndReject(false, __func__);
}
// We are fullscreen and lock has been accepted.
@ -368,15 +383,21 @@ bool ScreenOrientation::LockDeviceOrientation(
mFullscreenListener = new FullscreenEventListener();
}
aRv = target->AddSystemEventListener(u"fullscreenchange"_ns,
mFullscreenListener,
/* useCapture = */ true);
if (NS_WARN_IF(aRv.Failed())) {
return false;
nsresult rv = target->AddSystemEventListener(u"fullscreenchange"_ns,
mFullscreenListener,
/* aUseCapture = */ true);
if (NS_WARN_IF(NS_FAILED(rv))) {
return MozPromise<bool, bool, false>::CreateAndReject(false, __func__);
}
}
return true;
RefPtr<MozPromise<bool, bool, false>> halPromise =
hal::LockScreenOrientation(aOrientation);
if (halPromise == nullptr) {
return MozPromise<bool, bool, false>::CreateAndReject(false, __func__);
}
return halPromise;
}
void ScreenOrientation::Unlock(ErrorResult& aRv) {
@ -521,7 +542,7 @@ void ScreenOrientation::Notify(const hal::ScreenConfiguration& aConfiguration) {
if (doc->Hidden() && !mVisibleListener) {
mVisibleListener = new VisibleEventListener();
rv = doc->AddSystemEventListener(u"visibilitychange"_ns, mVisibleListener,
/* useCapture = */ true);
/* aUseCapture = */ true);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddSystemEventListener failed");
return;
}
@ -541,8 +562,14 @@ void ScreenOrientation::UpdateActiveOrientationLock(
if (aOrientation == hal::eScreenOrientation_None) {
hal::UnlockScreenOrientation();
} else {
DebugOnly<bool> ok = hal::LockScreenOrientation(aOrientation);
NS_WARNING_ASSERTION(ok, "hal::LockScreenOrientation failed");
hal::LockScreenOrientation(aOrientation)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[](const mozilla::MozPromise<bool, bool,
false>::ResolveOrRejectValue& aValue) {
NS_WARNING_ASSERTION(aValue.IsResolve(),
"hal::LockScreenOrientation failed");
});
}
}

Просмотреть файл

@ -11,6 +11,7 @@
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/ScreenOrientationBinding.h"
#include "mozilla/HalScreenConfiguration.h"
#include "mozilla/MozPromise.h"
class nsScreen;
@ -74,8 +75,8 @@ class ScreenOrientation final
// This method calls into the HAL to lock the device and sets
// up listeners for full screen change.
bool LockDeviceOrientation(hal::ScreenOrientation aOrientation,
bool aIsFullscreen, ErrorResult& aRv);
RefPtr<MozPromise<bool, bool, false>> LockDeviceOrientation(
hal::ScreenOrientation aOrientation, bool aIsFullscreen);
// This method calls in to the HAL to unlock the device and removes
// full screen change listener.

Просмотреть файл

@ -195,27 +195,6 @@ void nsScreen::GetMozOrientation(nsString& aOrientation,
}
}
static void UpdateDocShellOrientationLock(nsPIDOMWindowInner* aWindow,
hal::ScreenOrientation aOrientation) {
if (!aWindow) {
return;
}
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
if (!docShell) {
return;
}
RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
bc = bc ? bc->Top() : nullptr;
if (!bc) {
return;
}
// Setting orientation lock on a discarded context has no effect.
Unused << bc->SetOrientationLock(aOrientation);
}
bool nsScreen::MozLockOrientation(const nsAString& aOrientation,
ErrorResult& aRv) {
nsString orientation(aOrientation);
@ -227,62 +206,13 @@ bool nsScreen::MozLockOrientation(const nsAString& aOrientation,
return MozLockOrientation(orientations, aRv);
}
// This function is deprecated, use ScreenOrientation API instead.
bool nsScreen::MozLockOrientation(const Sequence<nsString>& aOrientations,
ErrorResult& aRv) {
if (ShouldResistFingerprinting()) {
return false;
}
hal::ScreenOrientation orientation = hal::eScreenOrientation_None;
for (uint32_t i = 0; i < aOrientations.Length(); ++i) {
const nsString& item = aOrientations[i];
if (item.EqualsLiteral("portrait")) {
orientation |= hal::eScreenOrientation_PortraitPrimary |
hal::eScreenOrientation_PortraitSecondary;
} else if (item.EqualsLiteral("portrait-primary")) {
orientation |= hal::eScreenOrientation_PortraitPrimary;
} else if (item.EqualsLiteral("portrait-secondary")) {
orientation |= hal::eScreenOrientation_PortraitSecondary;
} else if (item.EqualsLiteral("landscape")) {
orientation |= hal::eScreenOrientation_LandscapePrimary |
hal::eScreenOrientation_LandscapeSecondary;
} else if (item.EqualsLiteral("landscape-primary")) {
orientation |= hal::eScreenOrientation_LandscapePrimary;
} else if (item.EqualsLiteral("landscape-secondary")) {
orientation |= hal::eScreenOrientation_LandscapeSecondary;
} else if (item.EqualsLiteral("default")) {
orientation |= hal::eScreenOrientation_Default;
} else {
// If we don't recognize the token, we should just return 'false'
// without throwing.
return false;
}
}
switch (mScreenOrientation->GetLockOrientationPermission(false)) {
case ScreenOrientation::LOCK_DENIED:
return false;
case ScreenOrientation::LOCK_ALLOWED:
UpdateDocShellOrientationLock(GetOwner(), orientation);
return mScreenOrientation->LockDeviceOrientation(orientation, false, aRv);
case ScreenOrientation::FULLSCREEN_LOCK_ALLOWED:
UpdateDocShellOrientationLock(GetOwner(), orientation);
return mScreenOrientation->LockDeviceOrientation(orientation, true, aRv);
}
// This is only for compilers that don't understand that the previous switch
// will always return.
MOZ_CRASH("unexpected lock orientation permission value");
return false;
}
void nsScreen::MozUnlockOrientation() {
if (ShouldResistFingerprinting()) {
return;
}
UpdateDocShellOrientationLock(GetOwner(), hal::eScreenOrientation_None);
mScreenOrientation->UnlockDeviceOrientation();
}
void nsScreen::MozUnlockOrientation() {}
/* virtual */
JSObject* nsScreen::WrapObject(JSContext* aCx,

Просмотреть файл

@ -407,9 +407,10 @@ void NotifyScreenConfigurationChange(
ScreenConfigurationObservers()->BroadcastCachedInformation();
}
bool LockScreenOrientation(const ScreenOrientation& aOrientation) {
RefPtr<mozilla::MozPromise<bool, bool, false>> LockScreenOrientation(
const ScreenOrientation& aOrientation) {
AssertMainThread();
RETURN_PROXY_IF_SANDBOXED(LockScreenOrientation(aOrientation), false);
RETURN_PROXY_IF_SANDBOXED(LockScreenOrientation(aOrientation), nullptr);
}
void UnlockScreenOrientation() {

Просмотреть файл

@ -17,6 +17,7 @@
#include "mozilla/HalWakeLockInformation.h"
#include "mozilla/HalTypes.h"
#include "mozilla/Types.h"
#include "mozilla/MozPromise.h"
/*
* Hal.h contains the public Hal API.
@ -232,10 +233,10 @@ void NotifyScreenConfigurationChange(
/**
* Lock the screen orientation to the specific orientation.
* @return Whether the lock has been accepted.
* @return A promise indicating that the screen orientation has been locked.
*/
[[nodiscard]] bool LockScreenOrientation(
const hal::ScreenOrientation& aOrientation);
[[nodiscard]] RefPtr<mozilla::MozPromise<bool, bool, false>>
LockScreenOrientation(const hal::ScreenOrientation& aOrientation);
/**
* Unlock the screen orientation.

Просмотреть файл

@ -9,6 +9,7 @@
#include "AndroidBridge.h"
#include "mozilla/dom/network/Constants.h"
#include "mozilla/java/GeckoAppShellWrappers.h"
#include "mozilla/java/GeckoRuntimeWrappers.h"
#include "nsIScreenManager.h"
#include "nsPIDOMWindow.h"
#include "nsServiceManagerUtils.h"
@ -123,8 +124,40 @@ void GetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) {
orientation, angle, colorDepth, pixelDepth);
}
bool LockScreenOrientation(const hal::ScreenOrientation& aOrientation) {
return false;
RefPtr<MozPromise<bool, bool, false>> LockScreenOrientation(
const hal::ScreenOrientation& aOrientation) {
// Force the default orientation to be portrait-primary.
hal::ScreenOrientation orientation =
aOrientation == eScreenOrientation_Default
? eScreenOrientation_PortraitPrimary
: aOrientation;
switch (orientation) {
// The Android backend only supports these orientations.
case eScreenOrientation_PortraitPrimary:
case eScreenOrientation_PortraitSecondary:
case eScreenOrientation_PortraitPrimary |
eScreenOrientation_PortraitSecondary:
case eScreenOrientation_LandscapePrimary:
case eScreenOrientation_LandscapeSecondary:
case eScreenOrientation_LandscapePrimary |
eScreenOrientation_LandscapeSecondary:
case eScreenOrientation_Default: {
java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance();
if (runtime != NULL) {
auto result = runtime->LockScreenOrientation(orientation);
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
return geckoResult
? MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
: MozPromise<bool, bool, false>::CreateAndReject(false,
__func__);
} else {
return MozPromise<bool, bool, false>::CreateAndReject(false, __func__);
}
}
default:
return nullptr;
}
}
void UnlockScreenOrientation() {}

Просмотреть файл

@ -16,8 +16,10 @@ void GetCurrentScreenConfiguration(
fallback::GetCurrentScreenConfiguration(aScreenConfiguration);
}
bool LockScreenOrientation(const hal::ScreenOrientation& aOrientation) {
return false;
RefPtr<mozilla::MozPromise<bool, bool, false>> LockScreenOrientation(
const hal::ScreenOrientation& aOrientation) {
return mozilla::MozPromise<bool, bool, false>::CreateAndReject(false,
__func__);
}
void UnlockScreenOrientation() {}

Просмотреть файл

@ -91,7 +91,7 @@ parent:
async EnableScreenConfigurationNotifications();
async DisableScreenConfigurationNotifications();
sync LockScreenOrientation(ScreenOrientation aOrientation)
async LockScreenOrientation(ScreenOrientation aOrientation)
returns (bool allowed);
async UnlockScreenOrientation();

Просмотреть файл

@ -82,10 +82,21 @@ void GetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) {
fallback::GetCurrentScreenConfiguration(aScreenConfiguration);
}
bool LockScreenOrientation(const hal::ScreenOrientation& aOrientation) {
bool allowed;
Hal()->SendLockScreenOrientation(aOrientation, &allowed);
return allowed;
RefPtr<mozilla::MozPromise<bool, bool, false>> LockScreenOrientation(
const hal::ScreenOrientation& aOrientation) {
return Hal()
->SendLockScreenOrientation(aOrientation)
->Then(
GetCurrentSerialEventTarget(), __func__,
[=](const mozilla::MozPromise<bool, ipc::ResponseRejectReason,
true>::ResolveOrRejectValue& aValue) {
if (aValue.IsResolve()) {
return mozilla::MozPromise<bool, bool, false>::CreateAndResolve(
true, __func__);
}
return mozilla::MozPromise<bool, bool, false>::CreateAndReject(
false, __func__);
});
}
void UnlockScreenOrientation() { Hal()->SendUnlockScreenOrientation(); }
@ -233,12 +244,23 @@ class HalParent : public PHalParent,
}
virtual mozilla::ipc::IPCResult RecvLockScreenOrientation(
const ScreenOrientation& aOrientation, bool* aAllowed) override {
const ScreenOrientation& aOrientation,
LockScreenOrientationResolver&& aResolve) override {
// FIXME/bug 777980: unprivileged content may only lock
// orientation while fullscreen. We should check whether the
// request comes from an actor in a process that might be
// fullscreen. We don't have that information currently.
*aAllowed = hal::LockScreenOrientation(aOrientation);
hal::LockScreenOrientation(aOrientation)
->Then(GetMainThreadSerialEventTarget(), __func__,
[aResolve](const mozilla::MozPromise<
bool, bool, false>::ResolveOrRejectValue& aValue) {
if (aValue.IsResolve()) {
aResolve(aValue.ResolveValue());
} else {
aResolve(false);
}
});
return IPC_OK();
}

Просмотреть файл

@ -982,8 +982,6 @@ description = legacy sync IPC - please add detailed description
description = legacy sync IPC - please add detailed description
[PHal::GetWakeLockInfo]
description = legacy sync IPC - please add detailed description
[PHal::LockScreenOrientation]
description = legacy sync IPC - please add detailed description
[PPrinting::SavePrintSettings]
description = legacy sync IPC - please add detailed description
[PHandlerService::FillHandlerInfo]

Просмотреть файл

@ -82,6 +82,7 @@ import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
import org.mozilla.geckoview.Image;
import org.mozilla.geckoview.MediaSession;
import org.mozilla.geckoview.OrientationController;
import org.mozilla.geckoview.OverscrollEdgeEffect;
import org.mozilla.geckoview.PanZoomController;
import org.mozilla.geckoview.ProfilerController;
@ -607,6 +608,7 @@ package org.mozilla.geckoview {
method @NonNull public synchronized GeckoResult<Boolean> cancel();
method public synchronized void complete(@Nullable T);
method public synchronized void completeExceptionally(@NonNull Throwable);
method public void completeFrom(@Nullable GeckoResult<T>);
method @AnyThread @NonNull public static GeckoResult<AllowOrDeny> deny();
method @NonNull public <U> GeckoResult<U> exceptionally(@NonNull GeckoResult.OnExceptionListener<U>);
method @NonNull public GeckoResult<Void> finally_(@NonNull Runnable);
@ -662,6 +664,7 @@ package org.mozilla.geckoview {
method @NonNull @UiThread public ContentBlockingController getContentBlockingController();
method @NonNull @UiThread public static synchronized GeckoRuntime getDefault(@NonNull Context);
method @Nullable @UiThread public GeckoRuntime.Delegate getDelegate();
method @NonNull @UiThread public OrientationController getOrientationController();
method @NonNull @UiThread public ProfilerController getProfilerController();
method @Nullable @UiThread public GeckoRuntime.ServiceWorkerDelegate getServiceWorkerDelegate();
method @AnyThread @NonNull public GeckoRuntimeSettings getSettings();
@ -1538,6 +1541,16 @@ package org.mozilla.geckoview {
field public final double position;
}
public class OrientationController {
method @Nullable @UiThread public OrientationController.OrientationDelegate getDelegate();
method @UiThread public void setDelegate(@Nullable OrientationController.OrientationDelegate);
}
@UiThread public static interface OrientationController.OrientationDelegate {
method @Nullable default public GeckoResult<AllowOrDeny> onOrientationLock(@NonNull int);
method @Nullable default public void onOrientationUnlock();
}
@UiThread public final class OverscrollEdgeEffect {
method public void draw(@NonNull Canvas);
method @Nullable public Runnable getInvalidationCallback();

Просмотреть файл

@ -22,6 +22,7 @@ import org.json.JSONObject
import org.junit.Assume.assumeThat
import org.junit.Rule
import org.junit.rules.ErrorCollector
import org.junit.rules.RuleChain
import kotlin.reflect.KClass
@ -117,7 +118,11 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
const val TEST_PORT = GeckoSessionTestRule.TEST_PORT
}
@get:Rule val sessionRule = GeckoSessionTestRule()
val sessionRule = GeckoSessionTestRule()
// Override this to include more `evaluate` rules in the chain
@get:Rule
open val rules = RuleChain.outerRule(sessionRule)
@get:Rule var temporaryProfile = TemporaryProfileRule()

Просмотреть файл

@ -0,0 +1,103 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.geckoview.test
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import androidx.test.filters.MediumTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import org.hamcrest.Matchers.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.mozilla.geckoview.*
import org.mozilla.geckoview.GeckoSession.ContentDelegate
import org.mozilla.geckoview.OrientationController
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
@RunWith(AndroidJUnit4::class)
@MediumTest
class OrientationDelegateTest : BaseSessionTest() {
val activityRule = ActivityTestRule(GeckoViewTestActivity::class.java, false, true)
@get:Rule
override val rules = RuleChain.outerRule(activityRule).around(sessionRule)
private fun goFullscreen() {
sessionRule.setPrefsUntilTestEnd(mapOf("full-screen-api.allow-trusted-requests-only" to false))
mainSession.loadTestPath(FULLSCREEN_PATH)
mainSession.waitForPageStop()
val promise = mainSession.evaluatePromiseJS("document.querySelector('#fullscreen').requestFullscreen()")
sessionRule.waitUntilCalled(object : ContentDelegate {
@AssertCalled(count = 1)
override fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
assertThat("Div went fullscreen", fullScreen, equalTo(true))
}
})
promise.value
}
@Test fun orientationLockedAlready() {
sessionRule.setPrefsUntilTestEnd(mapOf("dom.screenorientation.allow-lock" to true))
goFullscreen()
// Lock to the current orientation
if (activityRule.activity.resources.configuration.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
sessionRule.delegateUntilTestEnd(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT))
val promise = mainSession.evaluatePromiseJS("screen.orientation.lock('portrait-primary')")
sessionRule.waitUntilCalled(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT))
sessionRule.runtime.orientationChanged(Configuration.ORIENTATION_PORTRAIT)
promise.value
} else if (activityRule.activity.resources.configuration.orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
sessionRule.delegateUntilTestEnd(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE))
val promise = mainSession.evaluatePromiseJS("screen.orientation.lock('landscape-primary')")
sessionRule.waitUntilCalled(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE))
sessionRule.runtime.orientationChanged(Configuration.ORIENTATION_LANDSCAPE)
promise.value
}
}
@Test(expected = GeckoSessionTestRule.RejectedPromiseException::class)
fun orientationLockNoFullscreen() {
// Verify if fullscreen pre-lock conditions are not met, a rejected promise is returned.
sessionRule.setPrefsUntilTestEnd(mapOf("dom.screenorientation.allow-lock" to true))
mainSession.loadTestPath(FULLSCREEN_PATH)
mainSession.waitForPageStop()
sessionRule.delegateUntilTestEnd(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE))
mainSession.evaluateJS("screen.orientation.lock('landscape-primary')")
}
@Test fun orientationLock() {
sessionRule.setPrefsUntilTestEnd(mapOf("dom.screenorientation.allow-lock" to true))
goFullscreen()
// If the orientation is landscape, lock to portrait and wait for delegate. If portrait, lock to landscape instead.
if (activityRule.activity.resources.configuration.orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
sessionRule.delegateUntilTestEnd(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT))
val promise = mainSession.evaluatePromiseJS("screen.orientation.lock('portrait-primary')")
sessionRule.waitUntilCalled(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT))
sessionRule.runtime.orientationChanged(Configuration.ORIENTATION_PORTRAIT)
promise.value
} else if (activityRule.activity.resources.configuration.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
sessionRule.delegateUntilTestEnd(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE))
val promise = mainSession.evaluatePromiseJS("screen.orientation.lock('landscape-primary')")
sessionRule.waitUntilCalled(TestOrientationDelegate(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE))
sessionRule.runtime.orientationChanged(Configuration.ORIENTATION_LANDSCAPE)
promise.value
}
}
inner class TestOrientationDelegate(private val expectedOrientation : Int) : OrientationController.OrientationDelegate {
override fun onOrientationLock(aOrientation: Int): GeckoResult<AllowOrDeny>? {
assertThat("The orientation value is as expected", aOrientation, equalTo(expectedOrientation))
activityRule.activity.requestedOrientation = aOrientation
return GeckoResult.allow()
}
}
}

Просмотреть файл

@ -78,6 +78,7 @@ import org.mozilla.geckoview.GeckoSession.SelectionActionDelegate;
import org.mozilla.geckoview.GeckoSession.TextInputDelegate;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.MediaSession;
import org.mozilla.geckoview.OrientationController;
import org.mozilla.geckoview.RuntimeTelemetry;
import org.mozilla.geckoview.SessionTextInput;
import org.mozilla.geckoview.WebExtension;
@ -769,6 +770,7 @@ public class GeckoSessionTestRule implements TestRule {
DEFAULT_RUNTIME_DELEGATES.add(Autocomplete.StorageDelegate.class);
DEFAULT_RUNTIME_DELEGATES.add(ActivityDelegate.class);
DEFAULT_RUNTIME_DELEGATES.add(GeckoRuntime.Delegate.class);
DEFAULT_RUNTIME_DELEGATES.add(OrientationController.OrientationDelegate.class);
DEFAULT_RUNTIME_DELEGATES.add(ServiceWorkerDelegate.class);
DEFAULT_RUNTIME_DELEGATES.add(WebNotificationDelegate.class);
DEFAULT_RUNTIME_DELEGATES.add(WebExtensionController.PromptDelegate.class);
@ -795,6 +797,7 @@ public class GeckoSessionTestRule implements TestRule {
ActivityDelegate,
Autocomplete.StorageDelegate,
GeckoRuntime.Delegate,
OrientationController.OrientationDelegate,
ServiceWorkerDelegate,
WebExtensionController.PromptDelegate,
WebNotificationDelegate,
@ -978,6 +981,10 @@ public class GeckoSessionTestRule implements TestRule {
runtime.setActivityDelegate((ActivityDelegate) delegate);
} else if (cls == GeckoRuntime.Delegate.class) {
runtime.setDelegate((GeckoRuntime.Delegate) delegate);
} else if (cls == OrientationController.OrientationDelegate.class) {
runtime
.getOrientationController()
.setDelegate((OrientationController.OrientationDelegate) delegate);
} else if (cls == ServiceWorkerDelegate.class) {
runtime.setServiceWorkerDelegate((ServiceWorkerDelegate) delegate);
} else if (cls == WebNotificationDelegate.class) {
@ -1001,6 +1008,8 @@ public class GeckoSessionTestRule implements TestRule {
return runtime.getActivityDelegate();
} else if (cls == GeckoRuntime.Delegate.class) {
return runtime.getDelegate();
} else if (cls == OrientationController.OrientationDelegate.class) {
return runtime.getOrientationController().getDelegate();
} else if (cls == ServiceWorkerDelegate.class) {
return runtime.getServiceWorkerDelegate();
} else if (cls == WebNotificationDelegate.class) {

Просмотреть файл

@ -1180,13 +1180,7 @@ public class GeckoAppShell {
@WrapForJNI(calledFrom = "gecko")
private static void enableNetworkNotifications() {
ThreadUtils.runOnUiThread(
new Runnable() {
@Override
public void run() {
GeckoNetworkManager.getInstance().enableNotifications();
}
});
ThreadUtils.runOnUiThread(() -> GeckoNetworkManager.getInstance().enableNotifications());
}
@WrapForJNI(calledFrom = "gecko")

Просмотреть файл

@ -5,6 +5,9 @@
package org.mozilla.gecko;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import android.content.Context;
import android.content.res.Configuration;
import android.util.Log;
@ -225,7 +228,7 @@ public class GeckoScreenOrientation {
private ScreenOrientation getScreenOrientation(
final int aAndroidOrientation, final int aRotation) {
final boolean isPrimary = aRotation == Surface.ROTATION_0 || aRotation == Surface.ROTATION_90;
if (aAndroidOrientation == Configuration.ORIENTATION_PORTRAIT) {
if (aAndroidOrientation == ORIENTATION_PORTRAIT) {
if (isPrimary) {
// Non-rotated portrait device or landscape device rotated
// to primary portrait mode counter-clockwise.
@ -233,7 +236,7 @@ public class GeckoScreenOrientation {
}
return ScreenOrientation.PORTRAIT_SECONDARY;
}
if (aAndroidOrientation == Configuration.ORIENTATION_LANDSCAPE) {
if (aAndroidOrientation == ORIENTATION_LANDSCAPE) {
if (isPrimary) {
// Non-rotated landscape device or portrait device rotated
// to primary landscape mode counter-clockwise.

Просмотреть файл

@ -797,7 +797,7 @@ public class GeckoResult<T> {
*
* @param other The result that this result should mirror
*/
private void completeFrom(final GeckoResult<T> other) {
public void completeFrom(final @Nullable GeckoResult<T> other) {
if (other == null) {
complete(null);
return;

Просмотреть файл

@ -12,6 +12,7 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
@ -39,6 +40,7 @@ import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoNetworkManager;
import org.mozilla.gecko.GeckoScreenOrientation;
import org.mozilla.gecko.GeckoScreenOrientation.ScreenOrientation;
import org.mozilla.gecko.GeckoSystemStateListener;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.annotation.WrapForJNI;
@ -169,6 +171,7 @@ public final class GeckoRuntime implements Parcelable {
private ServiceWorkerDelegate mServiceWorkerDelegate;
private WebNotificationDelegate mNotificationDelegate;
private ActivityDelegate mActivityDelegate;
private OrientationController mOrientationController;
private StorageController mStorageController;
private final WebExtensionController mWebExtensionController;
private WebPushController mPushController;
@ -792,6 +795,68 @@ public final class GeckoRuntime implements Parcelable {
GeckoScreenOrientation.getInstance().update(newOrientation);
}
/**
* Get the orientation controller for this runtime. The orientation controller can be used to
* manage changes to and locking of the screen orientation.
*
* @return The {@link OrientationController} for this instance.
*/
@UiThread
public @NonNull OrientationController getOrientationController() {
ThreadUtils.assertOnUiThread();
if (mOrientationController == null) {
mOrientationController = new OrientationController();
}
return mOrientationController;
}
/**
* Converts GeckoScreenOrientation to ActivityInfo orientation
*
* @return A {@link ActivityInfo} orientation.
*/
@AnyThread
private int toAndroidOrientation(final int geckoOrientation) {
if (geckoOrientation == ScreenOrientation.PORTRAIT_PRIMARY.value) {
return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else if (geckoOrientation == ScreenOrientation.PORTRAIT_SECONDARY.value) {
return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
} else if (geckoOrientation == ScreenOrientation.LANDSCAPE_PRIMARY.value) {
return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
} else if (geckoOrientation == ScreenOrientation.LANDSCAPE_SECONDARY.value) {
return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
}
return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
}
/**
* Lock screen orientation using OrientationController's onOrientationLock.
*
* @return A {@link GeckoResult} that resolves an orientation lock.
*/
@WrapForJNI(calledFrom = "gecko")
private @NonNull GeckoResult<Boolean> lockScreenOrientation(final int aOrientation) {
final GeckoResult<Boolean> res = new GeckoResult<>();
ThreadUtils.runOnUiThread(
() -> {
final OrientationController.OrientationDelegate delegate =
getOrientationController().getDelegate();
if (delegate == null) {
res.complete(false);
} else {
final GeckoResult<AllowOrDeny> response =
delegate.onOrientationLock(toAndroidOrientation(aOrientation));
if (response == null) {
res.complete(false);
} else {
res.completeFrom(response.map(v -> v == AllowOrDeny.ALLOW));
}
}
});
return res;
}
/**
* Get the storage controller for this runtime. The storage controller can be used to manage
* persistent storage data accumulated by {@link GeckoSession}.

Просмотреть файл

@ -0,0 +1,60 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* vim: ts=4 sw=4 expandtab:
* 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/. */
package org.mozilla.geckoview;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import org.mozilla.gecko.util.ThreadUtils;
public class OrientationController {
private OrientationDelegate mDelegate;
OrientationController() {}
/**
* Sets the {@link OrientationDelegate} for this instance.
*
* @param delegate The {@link OrientationDelegate} instance.
*/
@UiThread
public void setDelegate(final @Nullable OrientationDelegate delegate) {
ThreadUtils.assertOnUiThread();
mDelegate = delegate;
}
/**
* Gets the {@link OrientationDelegate} for this instance.
*
* @return delegate The {@link OrientationDelegate} instance.
*/
@UiThread
@Nullable
public OrientationDelegate getDelegate() {
ThreadUtils.assertOnUiThread();
return mDelegate;
}
/** This delegate will be called whenever an orientation lock is called. */
@UiThread
public interface OrientationDelegate {
/**
* Called whenever the orientation should be locked.
*
* @param aOrientation The desired orientation such as ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
* @return A {@link GeckoResult} which resolves to a {@link AllowOrDeny}
*/
@Nullable
default GeckoResult<AllowOrDeny> onOrientationLock(@NonNull final int aOrientation) {
return null;
}
/** Called whenever the orientation should be unlocked. */
@Nullable
default void onOrientationUnlock() {}
}
}

Просмотреть файл

@ -26,11 +26,13 @@ exclude: true
HTML content as plain text.
- Removed deprecated Content Blocking APIs.
([bug 1743706]({{bugzilla}}1743706)).
- Added [`OrientationController`][96.5] to allow GeckoView to handle orientation locking.
[96.1]: {{javadoc_uri}}/Autocomplete.StorageDelegate.html#onLoginFetch--
[96.2]: {{javadoc_uri}}/GeckoResult.html#finally_-java.lang.Runnable-
[96.3]: {{javadoc_uri}}/WebExtension.InstallException.ErrorCodes.html#ERROR_INVALID_DOMAIN-
[96.4]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.Selection.html#pasteAsPlainText--
[96.5]: {{javadoc_uri}}/OrientationController.html
## v95
- Added [`GeckoSession.ContentDelegate.onPointerIconChange()`][95.1] to notify
@ -1091,4 +1093,4 @@ to allow adding gecko profiler markers.
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: f8a31bdc9d6debf2e34c1a231d13bfaad76a08aa
[api-version]: 2e5d064d5675b5e38eeb25595a11906ed8d43e1a

Просмотреть файл

@ -22,6 +22,7 @@
android:required="false"/>
<activity
android:configChanges="keyboardHidden|orientation|screenSize"
android:name=".GeckoViewActivity"
android:label="GeckoView Example"
android:launchMode="singleTop"

Просмотреть файл

@ -81,6 +81,7 @@ import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
import org.mozilla.geckoview.GeckoWebExecutor;
import org.mozilla.geckoview.Image;
import org.mozilla.geckoview.OrientationController;
import org.mozilla.geckoview.RuntimeTelemetry;
import org.mozilla.geckoview.SlowScriptResponse;
import org.mozilla.geckoview.WebExtension;
@ -776,6 +777,7 @@ public class GeckoViewActivity extends AppCompatActivity
sGeckoRuntime.getWebExtensionController().setDebuggerDelegate(sExtensionManager);
sGeckoRuntime.setAutocompleteStorageDelegate(new ExampleAutocompleteStorageDelegate());
sGeckoRuntime.getOrientationController().setDelegate(new ExampleOrientationDelegate());
// `getSystemService` call requires API level 23
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
@ -1077,7 +1079,7 @@ public class GeckoViewActivity extends AppCompatActivity
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
if (savedInstanceState != null && mGeckoView.getSession() != null) {
mTabSessionManager.setCurrentSession((TabSession) mGeckoView.getSession());
sGeckoRuntime.getWebExtensionController().setTabActive(mGeckoView.getSession(), true);
} else {
@ -1596,6 +1598,14 @@ public class GeckoViewActivity extends AppCompatActivity
}
}
private class ExampleOrientationDelegate implements OrientationController.OrientationDelegate {
@Override
public GeckoResult<AllowOrDeny> onOrientationLock(@NonNull int aOrientation) {
setRequestedOrientation(aOrientation);
return GeckoResult.allow();
}
}
private class ExampleContentDelegate implements GeckoSession.ContentDelegate {
@Override
public void onTitleChange(GeckoSession session, String title) {

Просмотреть файл

@ -22,6 +22,7 @@ import org.mozilla.geckoview.GeckoRuntimeSettings;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
import org.mozilla.geckoview.OrientationController;
import org.mozilla.geckoview.WebExtension;
import org.mozilla.geckoview.WebExtensionController;
import org.mozilla.geckoview.WebRequestError;
@ -84,6 +85,10 @@ public class TestRunnerActivity extends Activity {
return sRuntime.getWebExtensionController();
}
private static OrientationController orientationController() {
return sRuntime.getOrientationController();
}
// Keeps track of all sessions for this test runner. The top session in the deque is the
// current active session for extension purposes.
private ArrayDeque<GeckoSession> mOwnedSessions = new ArrayDeque<>();
@ -413,6 +418,16 @@ public class TestRunnerActivity extends Activity {
});
}
orientationController()
.setDelegate(
new OrientationController.OrientationDelegate() {
@Override
public GeckoResult<AllowOrDeny> onOrientationLock(int aOrientation) {
setRequestedOrientation(aOrientation);
return GeckoResult.allow();
}
});
mSession = createSession(/* active */ true);
webExtensionController().setTabActive(mOwnedSessions.peek(), true);
mSession.open(sRuntime);

Просмотреть файл

@ -3109,6 +3109,12 @@
value: true
mirror: always
# Enable Screen Orientation lock
- name: dom.screenorientation.allow-lock
type: bool
value: false
mirror: always
# Whether to enable the JavaScript start-up cache. This causes one of the first
# execution to record the bytecode of the JavaScript function used, and save it
# in the existing cache entry. On the following loads of the same script, the