summaryrefslogtreecommitdiffstats
path: root/widget/android/nsWindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/android/nsWindow.cpp3252
1 files changed, 3252 insertions, 0 deletions
diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp
new file mode 100644
index 0000000000..0c9fa562a4
--- /dev/null
+++ b/widget/android/nsWindow.cpp
@@ -0,0 +1,3252 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*-
+ * vim: set sw=2 ts=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/. */
+
+#include <algorithm>
+#include <atomic>
+#include <android/log.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <math.h>
+#include <queue>
+#include <type_traits>
+#include <unistd.h>
+
+#include "AndroidGraphics.h"
+#include "AndroidBridge.h"
+#include "AndroidBridgeUtilities.h"
+#include "AndroidCompositorWidget.h"
+#include "AndroidContentController.h"
+#include "AndroidUiThread.h"
+#include "AndroidView.h"
+#include "gfxContext.h"
+#include "GeckoEditableSupport.h"
+#include "GeckoViewOutputStream.h"
+#include "GeckoViewSupport.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "JavaBuiltins.h"
+#include "JavaExceptions.h"
+#include "KeyEvent.h"
+#include "MotionEvent.h"
+#include "ScopedGLHelpers.h"
+#include "ScreenHelperAndroid.h"
+#include "TouchResampler.h"
+#include "WidgetUtils.h"
+#include "WindowRenderer.h"
+
+#include "mozilla/EventForwards.h"
+#include "nsAppShell.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsGkAtoms.h"
+#include "nsGfxCIID.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsLayoutUtils.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsUserIdleService.h"
+#include "nsViewManager.h"
+#include "nsWidgetsCID.h"
+#include "nsWindow.h"
+
+#include "nsIWidgetListener.h"
+#include "nsIWindowWatcher.h"
+#include "nsIAppWindow.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_android.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
+#include "mozilla/a11y/SessionAccessibility.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowserHost.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/Swizzle.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/ipc/Shmem.h"
+#include "mozilla/java/EventDispatcherWrappers.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/GeckoEditableChildWrappers.h"
+#include "mozilla/java/GeckoResultWrappers.h"
+#include "mozilla/java/GeckoSessionNatives.h"
+#include "mozilla/java/GeckoSystemStateListenerWrappers.h"
+#include "mozilla/java/PanZoomControllerNatives.h"
+#include "mozilla/java/SessionAccessibilityWrappers.h"
+#include "mozilla/java/SurfaceControlManagerWrappers.h"
+#include "mozilla/jni/NativesInlines.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/CompositorSession.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/UiCompositorControllerChild.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/widget/AndroidVsync.h"
+#include "mozilla/widget/Screen.h"
+
+#define GVS_LOG(...) MOZ_LOG(sGVSupportLog, LogLevel::Warning, (__VA_ARGS__))
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::ipc;
+
+using mozilla::dom::ContentChild;
+using mozilla::dom::ContentParent;
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::Matrix;
+using mozilla::gfx::SurfaceFormat;
+using mozilla::java::GeckoSession;
+using mozilla::java::sdk::IllegalStateException;
+using GeckoPrintException = GeckoSession::GeckoPrintException;
+static mozilla::LazyLogModule sGVSupportLog("GeckoViewSupport");
+
+// All the toplevel windows that have been created; these are in
+// stacking order, so the window at gTopLevelWindows[0] is the topmost
+// one.
+static nsTArray<nsWindow*> gTopLevelWindows;
+
+static bool sFailedToCreateGLContext = false;
+
+// Multitouch swipe thresholds in inches
+static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
+static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
+
+static const double kTouchResampleVsyncAdjustMs = 5.0;
+
+static const int32_t INPUT_RESULT_UNHANDLED =
+ java::PanZoomController::INPUT_RESULT_UNHANDLED;
+static const int32_t INPUT_RESULT_HANDLED =
+ java::PanZoomController::INPUT_RESULT_HANDLED;
+static const int32_t INPUT_RESULT_HANDLED_CONTENT =
+ java::PanZoomController::INPUT_RESULT_HANDLED_CONTENT;
+static const int32_t INPUT_RESULT_IGNORED =
+ java::PanZoomController::INPUT_RESULT_IGNORED;
+
+static const nsCString::size_type MAX_TOPLEVEL_DATA_URI_LEN = 2 * 1024 * 1024;
+
+// Unique ID given to each widget, to identify it for the
+// CompositorSurfaceManager.
+static std::atomic<int32_t> sWidgetId{0};
+
+namespace {
+template <class Instance, class Impl>
+std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value ==
+ jni::detail::NativePtrType::REFPTR,
+ void>
+CallAttachNative(Instance aInstance, Impl* aImpl) {
+ Impl::AttachNative(aInstance, RefPtr<Impl>(aImpl).get());
+}
+
+template <class Instance, class Impl>
+std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value ==
+ jni::detail::NativePtrType::OWNING,
+ void>
+CallAttachNative(Instance aInstance, Impl* aImpl) {
+ Impl::AttachNative(aInstance, UniquePtr<Impl>(aImpl));
+}
+
+template <class Lambda>
+bool DispatchToUiThread(const char* aName, Lambda&& aLambda) {
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(NS_NewRunnableFunction(aName, std::move(aLambda)));
+ return true;
+ }
+ return false;
+}
+} // namespace
+
+namespace mozilla {
+namespace widget {
+
+using WindowPtr = jni::NativeWeakPtr<GeckoViewSupport>;
+
+/**
+ * PanZoomController handles its native calls on the UI thread, so make
+ * it separate from GeckoViewSupport.
+ */
+class NPZCSupport final
+ : public java::PanZoomController::NativeProvider::Natives<NPZCSupport> {
+ WindowPtr mWindow;
+ java::PanZoomController::NativeProvider::WeakRef mNPZC;
+
+ // Stores the returnResult of each pending motion event between
+ // HandleMotionEvent and FinishHandlingMotionEvent.
+ std::queue<std::pair<uint64_t, java::GeckoResult::GlobalRef>>
+ mPendingMotionEventReturnResults;
+
+ RefPtr<AndroidVsync> mAndroidVsync;
+ TouchResampler mTouchResampler;
+ int mPreviousButtons = 0;
+ bool mListeningToVsync = false;
+
+ // Only true if mAndroidVsync is non-null and the resampling pref is set.
+ bool mTouchResamplingEnabled = false;
+
+ template <typename Lambda>
+ class InputEvent final : public nsAppShell::Event {
+ java::PanZoomController::NativeProvider::GlobalRef mNPZC;
+ Lambda mLambda;
+
+ public:
+ InputEvent(const NPZCSupport* aNPZCSupport, Lambda&& aLambda)
+ : mNPZC(aNPZCSupport->mNPZC), mLambda(std::move(aLambda)) {}
+
+ void Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ const auto npzcSupportWeak = GetNative(
+ java::PanZoomController::NativeProvider::LocalRef(env, mNPZC));
+ if (!npzcSupportWeak) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ auto acc = npzcSupportWeak->Access();
+ if (!acc) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ auto win = acc->mWindow.Access();
+ if (!win) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ nsWindow* const window = win->GetNsWindow();
+ if (!window) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ window->UserActivity();
+ return mLambda(window);
+ }
+
+ bool IsUIEvent() const override { return true; }
+ };
+
+ class MOZ_HEAP_CLASS Observer final : public AndroidVsync::Observer {
+ public:
+ static Observer* Create(jni::NativeWeakPtr<NPZCSupport>&& aNPZCSupport) {
+ return new Observer(std::move(aNPZCSupport));
+ }
+
+ private:
+ // Private constructor, part of a strategy to make sure
+ // we're only able to create these on the heap.
+ explicit Observer(jni::NativeWeakPtr<NPZCSupport>&& aNPZCSupport)
+ : mNPZCSupport(std::move(aNPZCSupport)) {}
+
+ void OnVsync(const TimeStamp& aTimeStamp) override {
+ auto accessor = mNPZCSupport.Access();
+
+ if (!accessor) {
+ return;
+ }
+
+ accessor->mTouchResampler.NotifyFrame(
+ aTimeStamp -
+ TimeDuration::FromMilliseconds(kTouchResampleVsyncAdjustMs));
+ accessor->ConsumeMotionEventsFromResampler();
+ }
+
+ void Dispose() override { delete this; }
+
+ jni::NativeWeakPtr<NPZCSupport> mNPZCSupport;
+ };
+
+ Observer* mObserver = nullptr;
+
+ template <typename Lambda>
+ void PostInputEvent(Lambda&& aLambda) {
+ // Use priority queue for input events.
+ nsAppShell::PostEvent(
+ MakeUnique<InputEvent<Lambda>>(this, std::move(aLambda)));
+ }
+
+ public:
+ typedef java::PanZoomController::NativeProvider::Natives<NPZCSupport> Base;
+
+ NPZCSupport(WindowPtr aWindow,
+ const java::PanZoomController::NativeProvider::LocalRef& aNPZC)
+ : mWindow(aWindow), mNPZC(aNPZC) {
+#if defined(DEBUG)
+ auto win(mWindow.Access());
+ MOZ_ASSERT(!!win);
+#endif // defined(DEBUG)
+
+ // Use vsync for touch resampling on API level 19 and above.
+ // See gfxAndroidPlatform::CreateGlobalHardwareVsyncSource() for comparison.
+ if (jni::GetAPIVersion() >= 19) {
+ mAndroidVsync = AndroidVsync::GetInstance();
+ }
+ }
+
+ ~NPZCSupport() {
+ if (mListeningToVsync) {
+ MOZ_RELEASE_ASSERT(mAndroidVsync);
+ mAndroidVsync->UnregisterObserver(mObserver, AndroidVsync::INPUT);
+ mListeningToVsync = false;
+ }
+ }
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ RefPtr<Runnable> disposer = aDisposer;
+ // There are several considerations when shutting down NPZC. 1) The
+ // Gecko thread may destroy NPZC at any time when nsWindow closes. 2)
+ // There may be pending events on the Gecko thread when NPZC is
+ // destroyed. 3) mWindow may not be available when the pending event
+ // runs. 4) The UI thread may destroy NPZC at any time when GeckoView
+ // is destroyed. 5) The UI thread may destroy NPZC at the same time as
+ // Gecko thread trying to destroy NPZC. 6) There may be pending calls
+ // on the UI thread when NPZC is destroyed. 7) mWindow may have been
+ // cleared on the Gecko thread when the pending call happens on the UI
+ // thread.
+ //
+ // 1) happens through OnWeakNonIntrusiveDetach, which first notifies the UI
+ // thread through Destroy; Destroy then calls DisposeNative, which
+ // finally disposes the native instance back on the Gecko thread. Using
+ // Destroy to indirectly call DisposeNative here also solves 5), by
+ // making everything go through the UI thread, avoiding contention.
+ //
+ // 2) and 3) are solved by clearing mWindow, which signals to the
+ // pending event that we had shut down. In that case the event bails
+ // and does not touch mWindow.
+ //
+ // 4) happens through DisposeNative directly.
+ //
+ // 6) is solved by keeping a destroyed flag in the Java NPZC instance,
+ // and only make a pending call if the destroyed flag is not set.
+ //
+ // 7) is solved by taking a lock whenever mWindow is modified on the
+ // Gecko thread or accessed on the UI thread. That way, we don't
+ // release mWindow until the UI thread is done using it, thus avoiding
+ // the race condition.
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ auto npzc = java::PanZoomController::NativeProvider::GlobalRef(mNPZC);
+ if (!npzc) {
+ return;
+ }
+
+ uiThread->Dispatch(
+ NS_NewRunnableFunction("NPZCSupport::OnWeakNonIntrusiveDetach",
+ [npzc, disposer = std::move(disposer)] {
+ npzc->SetAttached(false);
+ disposer->Run();
+ }));
+ }
+ }
+
+ const java::PanZoomController::NativeProvider::Ref& GetJavaNPZC() const {
+ return mNPZC;
+ }
+
+ public:
+ void SetIsLongpressEnabled(bool aIsLongpressEnabled) {
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (controller) {
+ controller->SetLongTapEnabled(aIsLongpressEnabled);
+ }
+ }
+
+ int32_t HandleScrollEvent(int64_t aTime, int32_t aMetaState, float aX,
+ float aY, float aHScroll, float aVScroll) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (!controller) {
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ if (StaticPrefs::ui_scrolling_negate_wheel_scroll()) {
+ aHScroll = -aHScroll;
+ aVScroll = -aVScroll;
+ }
+
+ ScrollWheelInput input(
+ nsWindow::GetEventTimeStamp(aTime), nsWindow::GetModifiers(aMetaState),
+ ScrollWheelInput::SCROLLMODE_SMOOTH,
+ ScrollWheelInput::SCROLLDELTA_PIXEL, origin, aHScroll, aVScroll, false,
+ // XXX Do we need to support auto-dir scrolling
+ // for Android widgets with a wheel device?
+ // Currently, I just leave it unimplemented. If
+ // we need to implement it, what's the extra work
+ // to do?
+ WheelDeltaAdjustmentStrategy::eNone);
+
+ APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
+ if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
+ return INPUT_RESULT_IGNORED;
+ }
+
+ PostInputEvent([input = std::move(input), result](nsWindow* window) {
+ WidgetWheelEvent wheelEvent = input.ToWidgetEvent(window);
+ window->ProcessUntransformedAPZEvent(&wheelEvent, result);
+ });
+
+ switch (result.GetStatus()) {
+ case nsEventStatus_eIgnore:
+ return INPUT_RESULT_UNHANDLED;
+ case nsEventStatus_eConsumeDoDefault:
+ return result.GetHandledResult()->IsHandledByRoot()
+ ? INPUT_RESULT_HANDLED
+ : INPUT_RESULT_HANDLED_CONTENT;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
+ return INPUT_RESULT_UNHANDLED;
+ }
+ }
+
+ private:
+ static MouseInput::ButtonType GetButtonType(int button) {
+ MouseInput::ButtonType result = MouseInput::NONE;
+
+ switch (button) {
+ case java::sdk::MotionEvent::BUTTON_PRIMARY:
+ result = MouseInput::PRIMARY_BUTTON;
+ break;
+ case java::sdk::MotionEvent::BUTTON_SECONDARY:
+ result = MouseInput::SECONDARY_BUTTON;
+ break;
+ case java::sdk::MotionEvent::BUTTON_TERTIARY:
+ result = MouseInput::MIDDLE_BUTTON;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ static int16_t ConvertButtons(int buttons) {
+ int16_t result = 0;
+
+ if (buttons & java::sdk::MotionEvent::BUTTON_PRIMARY) {
+ result |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_SECONDARY) {
+ result |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_TERTIARY) {
+ result |= MouseButtonsFlag::eMiddleFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_BACK) {
+ result |= MouseButtonsFlag::e4thFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_FORWARD) {
+ result |= MouseButtonsFlag::e5thFlag;
+ }
+
+ return result;
+ }
+
+ static int32_t ConvertAPZHandledPlace(APZHandledPlace aHandledPlace) {
+ switch (aHandledPlace) {
+ case APZHandledPlace::Unhandled:
+ return INPUT_RESULT_UNHANDLED;
+ case APZHandledPlace::HandledByRoot:
+ return INPUT_RESULT_HANDLED;
+ case APZHandledPlace::HandledByContent:
+ return INPUT_RESULT_HANDLED_CONTENT;
+ case APZHandledPlace::Invalid:
+ MOZ_ASSERT_UNREACHABLE("The handled result should NOT be Invalid");
+ return INPUT_RESULT_UNHANDLED;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown handled result");
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ static int32_t ConvertSideBits(SideBits aSideBits) {
+ int32_t ret = java::PanZoomController::SCROLLABLE_FLAG_NONE;
+ if (aSideBits & SideBits::eTop) {
+ ret |= java::PanZoomController::SCROLLABLE_FLAG_TOP;
+ }
+ if (aSideBits & SideBits::eRight) {
+ ret |= java::PanZoomController::SCROLLABLE_FLAG_RIGHT;
+ }
+ if (aSideBits & SideBits::eBottom) {
+ ret |= java::PanZoomController::SCROLLABLE_FLAG_BOTTOM;
+ }
+ if (aSideBits & SideBits::eLeft) {
+ ret |= java::PanZoomController::SCROLLABLE_FLAG_LEFT;
+ }
+ return ret;
+ }
+
+ static int32_t ConvertScrollDirections(
+ layers::ScrollDirections aScrollDirections) {
+ int32_t ret = java::PanZoomController::OVERSCROLL_FLAG_NONE;
+ if (aScrollDirections.contains(layers::HorizontalScrollDirection)) {
+ ret |= java::PanZoomController::OVERSCROLL_FLAG_HORIZONTAL;
+ }
+ if (aScrollDirections.contains(layers::VerticalScrollDirection)) {
+ ret |= java::PanZoomController::OVERSCROLL_FLAG_VERTICAL;
+ }
+ return ret;
+ }
+
+ static java::PanZoomController::InputResultDetail::LocalRef
+ ConvertAPZHandledResult(const APZHandledResult& aHandledResult) {
+ return java::PanZoomController::InputResultDetail::New(
+ ConvertAPZHandledPlace(aHandledResult.mPlace),
+ ConvertSideBits(aHandledResult.mScrollableDirections),
+ ConvertScrollDirections(aHandledResult.mOverscrollDirections));
+ }
+
+ public:
+ int32_t HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState,
+ float aX, float aY, int buttons) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (!controller) {
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ MouseInput::MouseType mouseType = MouseInput::MOUSE_NONE;
+ MouseInput::ButtonType buttonType = MouseInput::NONE;
+ switch (aAction) {
+ case java::sdk::MotionEvent::ACTION_DOWN:
+ mouseType = MouseInput::MOUSE_DOWN;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case java::sdk::MotionEvent::ACTION_UP:
+ mouseType = MouseInput::MOUSE_UP;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case java::sdk::MotionEvent::ACTION_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case java::sdk::MotionEvent::ACTION_HOVER_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case java::sdk::MotionEvent::ACTION_HOVER_ENTER:
+ mouseType = MouseInput::MOUSE_WIDGET_ENTER;
+ break;
+ case java::sdk::MotionEvent::ACTION_HOVER_EXIT:
+ mouseType = MouseInput::MOUSE_WIDGET_EXIT;
+ break;
+ default:
+ break;
+ }
+
+ if (mouseType == MouseInput::MOUSE_NONE) {
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ MouseInput input(
+ mouseType, buttonType, MouseEvent_Binding::MOZ_SOURCE_MOUSE,
+ ConvertButtons(buttons), origin, nsWindow::GetEventTimeStamp(aTime),
+ nsWindow::GetModifiers(aMetaState));
+
+ APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
+ if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
+ return INPUT_RESULT_IGNORED;
+ }
+
+ PostInputEvent([input = std::move(input), result](nsWindow* window) {
+ WidgetMouseEvent mouseEvent = input.ToWidgetEvent(window);
+ window->ProcessUntransformedAPZEvent(&mouseEvent, result);
+ });
+
+ switch (result.GetStatus()) {
+ case nsEventStatus_eIgnore:
+ return INPUT_RESULT_UNHANDLED;
+ case nsEventStatus_eConsumeDoDefault:
+ return result.GetHandledResult()->IsHandledByRoot()
+ ? INPUT_RESULT_HANDLED
+ : INPUT_RESULT_HANDLED_CONTENT;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
+ return INPUT_RESULT_UNHANDLED;
+ }
+ }
+
+ // Convert MotionEvent touch radius and orientation into the format required
+ // by w3c touchevents.
+ // toolMajor and toolMinor span a rectangle that's oriented as per
+ // aOrientation, centered around the touch point.
+ static std::pair<float, ScreenSize> ConvertOrientationAndRadius(
+ float aOrientation, float aToolMajor, float aToolMinor) {
+ float angle = aOrientation * 180.0f / M_PI;
+ // w3c touchevents spec does not allow orientations == 90
+ // this shifts it to -90, which will be shifted to zero below
+ if (angle >= 90.0) {
+ angle -= 180.0f;
+ }
+
+ // w3c touchevent radii are given with an orientation between 0 and
+ // 90. The radii are found by removing the orientation and
+ // measuring the x and y radii of the resulting ellipse. For
+ // Android orientations >= 0 and < 90, use the y radius as the
+ // major radius, and x as the minor radius. However, for an
+ // orientation < 0, we have to shift the orientation by adding 90,
+ // and reverse which radius is major and minor.
+ ScreenSize radius;
+ if (angle < 0.0f) {
+ angle += 90.0f;
+ radius =
+ ScreenSize(int32_t(aToolMajor / 2.0f), int32_t(aToolMinor / 2.0f));
+ } else {
+ radius =
+ ScreenSize(int32_t(aToolMinor / 2.0f), int32_t(aToolMajor / 2.0f));
+ }
+
+ return std::make_pair(angle, radius);
+ }
+
+ void HandleMotionEvent(
+ const java::PanZoomController::NativeProvider::LocalRef& aInstance,
+ jni::Object::Param aEventData, float aScreenX, float aScreenY,
+ jni::Object::Param aResult) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ auto returnResult = java::GeckoResult::Ref::From(aResult);
+ auto eventData =
+ java::PanZoomController::MotionEventData::Ref::From(aEventData);
+ nsTArray<int32_t> pointerId(eventData->PointerId()->GetElements());
+ size_t pointerCount = pointerId.Length();
+ MultiTouchInput::MultiTouchType type;
+ size_t startIndex = 0;
+ size_t endIndex = pointerCount;
+
+ switch (eventData->Action()) {
+ case java::sdk::MotionEvent::ACTION_DOWN:
+ case java::sdk::MotionEvent::ACTION_POINTER_DOWN:
+ type = MultiTouchInput::MULTITOUCH_START;
+ break;
+ case java::sdk::MotionEvent::ACTION_MOVE:
+ type = MultiTouchInput::MULTITOUCH_MOVE;
+ break;
+ case java::sdk::MotionEvent::ACTION_UP:
+ case java::sdk::MotionEvent::ACTION_POINTER_UP:
+ // for pointer-up events we only want the data from
+ // the one pointer that went up
+ type = MultiTouchInput::MULTITOUCH_END;
+ startIndex = eventData->ActionIndex();
+ endIndex = startIndex + 1;
+ break;
+ case java::sdk::MotionEvent::ACTION_OUTSIDE:
+ case java::sdk::MotionEvent::ACTION_CANCEL:
+ type = MultiTouchInput::MULTITOUCH_CANCEL;
+ break;
+ default:
+ if (returnResult) {
+ returnResult->Complete(
+ java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
+ }
+ return;
+ }
+
+ MultiTouchInput input(type, eventData->Time(),
+ nsWindow::GetEventTimeStamp(eventData->Time()), 0);
+ input.modifiers = nsWindow::GetModifiers(eventData->MetaState());
+ input.mTouches.SetCapacity(endIndex - startIndex);
+ input.mScreenOffset =
+ ExternalIntPoint(int32_t(floorf(aScreenX)), int32_t(floorf(aScreenY)));
+
+ size_t historySize = eventData->HistorySize();
+ nsTArray<int64_t> historicalTime(
+ eventData->HistoricalTime()->GetElements());
+ MOZ_RELEASE_ASSERT(historicalTime.Length() == historySize);
+
+ // Each of these is |historySize| sets of |pointerCount| values.
+ size_t historicalDataCount = historySize * pointerCount;
+ nsTArray<float> historicalX(eventData->HistoricalX()->GetElements());
+ nsTArray<float> historicalY(eventData->HistoricalY()->GetElements());
+ nsTArray<float> historicalOrientation(
+ eventData->HistoricalOrientation()->GetElements());
+ nsTArray<float> historicalPressure(
+ eventData->HistoricalPressure()->GetElements());
+ nsTArray<float> historicalToolMajor(
+ eventData->HistoricalToolMajor()->GetElements());
+ nsTArray<float> historicalToolMinor(
+ eventData->HistoricalToolMinor()->GetElements());
+
+ MOZ_RELEASE_ASSERT(historicalX.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalY.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalOrientation.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalPressure.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalToolMajor.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalToolMinor.Length() == historicalDataCount);
+
+ // Each of these is |pointerCount| values.
+ nsTArray<float> x(eventData->X()->GetElements());
+ nsTArray<float> y(eventData->Y()->GetElements());
+ nsTArray<float> orientation(eventData->Orientation()->GetElements());
+ nsTArray<float> pressure(eventData->Pressure()->GetElements());
+ nsTArray<float> toolMajor(eventData->ToolMajor()->GetElements());
+ nsTArray<float> toolMinor(eventData->ToolMinor()->GetElements());
+
+ MOZ_ASSERT(x.Length() == pointerCount);
+ MOZ_ASSERT(y.Length() == pointerCount);
+ MOZ_ASSERT(orientation.Length() == pointerCount);
+ MOZ_ASSERT(pressure.Length() == pointerCount);
+ MOZ_ASSERT(toolMajor.Length() == pointerCount);
+ MOZ_ASSERT(toolMinor.Length() == pointerCount);
+
+ for (size_t i = startIndex; i < endIndex; i++) {
+ auto [orien, radius] = ConvertOrientationAndRadius(
+ orientation[i], toolMajor[i], toolMinor[i]);
+
+ ScreenIntPoint point(int32_t(floorf(x[i])), int32_t(floorf(y[i])));
+ SingleTouchData singleTouchData(pointerId[i], point, radius, orien,
+ pressure[i]);
+
+ for (size_t historyIndex = 0; historyIndex < historySize;
+ historyIndex++) {
+ size_t historicalI = historyIndex * pointerCount + i;
+ auto [historicalAngle, historicalRadius] = ConvertOrientationAndRadius(
+ historicalOrientation[historicalI],
+ historicalToolMajor[historicalI], historicalToolMinor[historicalI]);
+ ScreenIntPoint historicalPoint(
+ int32_t(floorf(historicalX[historicalI])),
+ int32_t(floorf(historicalY[historicalI])));
+ singleTouchData.mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ nsWindow::GetEventTimeStamp(historicalTime[historyIndex]),
+ historicalPoint,
+ {}, // mLocalScreenPoint will be computed later by APZ
+ historicalRadius,
+ historicalAngle,
+ historicalPressure[historicalI]});
+ }
+
+ input.mTouches.AppendElement(singleTouchData);
+ }
+
+ if (mAndroidVsync &&
+ eventData->Action() == java::sdk::MotionEvent::ACTION_DOWN) {
+ // Query pref value at the beginning of a touch gesture so that we don't
+ // leave events stuck in the resampler after a pref flip.
+ mTouchResamplingEnabled = StaticPrefs::android_touch_resampling_enabled();
+ }
+
+ if (!mTouchResamplingEnabled) {
+ FinishHandlingMotionEvent(std::move(input),
+ java::GeckoResult::LocalRef(returnResult));
+ return;
+ }
+
+ uint64_t eventId = mTouchResampler.ProcessEvent(std::move(input));
+ mPendingMotionEventReturnResults.push(
+ {eventId, java::GeckoResult::GlobalRef(returnResult)});
+
+ RegisterOrUnregisterForVsync(mTouchResampler.InTouchingState());
+ ConsumeMotionEventsFromResampler();
+ }
+
+ void RegisterOrUnregisterForVsync(bool aNeedVsync) {
+ MOZ_RELEASE_ASSERT(mAndroidVsync);
+ if (aNeedVsync && !mListeningToVsync) {
+ MOZ_ASSERT(!mObserver);
+ auto win = mWindow.Access();
+ if (!win) {
+ return;
+ }
+ RefPtr<nsWindow> gkWindow = win->GetNsWindow();
+ if (!gkWindow) {
+ return;
+ }
+ MutexAutoLock lock(gkWindow->GetDestroyMutex());
+ if (gkWindow->Destroyed()) {
+ return;
+ }
+ jni::NativeWeakPtr<NPZCSupport> weakPtrToThis =
+ gkWindow->GetNPZCSupportWeakPtr();
+ mObserver = Observer::Create(std::move(weakPtrToThis));
+ mAndroidVsync->RegisterObserver(mObserver, AndroidVsync::INPUT);
+ } else if (!aNeedVsync && mListeningToVsync) {
+ mAndroidVsync->UnregisterObserver(mObserver, AndroidVsync::INPUT);
+ mObserver = nullptr;
+ }
+ mListeningToVsync = aNeedVsync;
+ }
+
+ void ConsumeMotionEventsFromResampler() {
+ auto outgoing = mTouchResampler.ConsumeOutgoingEvents();
+ while (!outgoing.empty()) {
+ auto outgoingEvent = std::move(outgoing.front());
+ outgoing.pop();
+ java::GeckoResult::GlobalRef returnResult;
+ if (outgoingEvent.mEventId) {
+ // Look up the GeckoResult for this event.
+ // The outgoing events from the resampler are in the same order as the
+ // original events, and no event IDs are skipped.
+ MOZ_RELEASE_ASSERT(!mPendingMotionEventReturnResults.empty());
+ auto pair = mPendingMotionEventReturnResults.front();
+ mPendingMotionEventReturnResults.pop();
+ MOZ_RELEASE_ASSERT(pair.first == *outgoingEvent.mEventId);
+ returnResult = pair.second;
+ }
+ FinishHandlingMotionEvent(std::move(outgoingEvent.mEvent),
+ java::GeckoResult::LocalRef(returnResult));
+ }
+ }
+
+ void FinishHandlingMotionEvent(MultiTouchInput&& aInput,
+ java::GeckoResult::LocalRef&& aReturnResult) {
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (!controller) {
+ if (aReturnResult) {
+ aReturnResult->Complete(java::PanZoomController::InputResultDetail::New(
+ INPUT_RESULT_UNHANDLED,
+ java::PanZoomController::SCROLLABLE_FLAG_NONE,
+ java::PanZoomController::OVERSCROLL_FLAG_NONE));
+ }
+ return;
+ }
+
+ APZInputBridge::InputBlockCallback callback;
+ if (aReturnResult) {
+ callback = [aReturnResult = java::GeckoResult::GlobalRef(aReturnResult)](
+ uint64_t aInputBlockId,
+ const APZHandledResult& aHandledResult) {
+ aReturnResult->Complete(ConvertAPZHandledResult(aHandledResult));
+ };
+ }
+ APZEventResult result = controller->InputBridge()->ReceiveInputEvent(
+ aInput, std::move(callback));
+
+ if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
+ if (aReturnResult) {
+ if (result.GetHandledResult() != Nothing()) {
+ aReturnResult->Complete(
+ ConvertAPZHandledResult(result.GetHandledResult().value()));
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "nsEventStatus_eConsumeNoDefault should involve a valid "
+ "APZHandledResult");
+ aReturnResult->Complete(
+ java::PanZoomController::InputResultDetail::New(
+ INPUT_RESULT_IGNORED,
+ java::PanZoomController::SCROLLABLE_FLAG_NONE,
+ java::PanZoomController::OVERSCROLL_FLAG_NONE));
+ }
+ }
+ return;
+ }
+
+ // Dispatch APZ input event on Gecko thread.
+ PostInputEvent([input = std::move(aInput), result](nsWindow* window) {
+ WidgetTouchEvent touchEvent = input.ToWidgetEvent(window);
+ window->ProcessUntransformedAPZEvent(&touchEvent, result);
+ window->DispatchHitTest(touchEvent);
+ });
+
+ if (aReturnResult && result.GetHandledResult() != Nothing()) {
+ MOZ_ASSERT(result.GetStatus() == nsEventStatus_eConsumeDoDefault ||
+ result.GetStatus() == nsEventStatus_eIgnore);
+ aReturnResult->Complete(
+ ConvertAPZHandledResult(result.GetHandledResult().value()));
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(AndroidView, nsIAndroidEventDispatcher, nsIAndroidView)
+
+nsresult AndroidView::GetInitData(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOut) {
+ if (!mInitData) {
+ aOut.setNull();
+ return NS_OK;
+ }
+
+ return widget::EventDispatcher::UnboxBundle(aCx, mInitData, aOut);
+}
+
+/**
+ * Compositor has some unique requirements for its native calls, so make it
+ * separate from GeckoViewSupport.
+ */
+class LayerViewSupport final
+ : public GeckoSession::Compositor::Natives<LayerViewSupport> {
+ WindowPtr mWindow;
+ GeckoSession::Compositor::WeakRef mCompositor;
+ Atomic<bool, ReleaseAcquire> mCompositorPaused;
+ java::sdk::Surface::GlobalRef mSurface;
+ java::sdk::SurfaceControl::GlobalRef mSurfaceControl;
+ int32_t mX;
+ int32_t mY;
+ int32_t mWidth;
+ int32_t mHeight;
+ // Used to communicate with the gecko compositor from the UI thread.
+ // Set in NotifyCompositorCreated and cleared in
+ // NotifyCompositorSessionLost.
+ RefPtr<UiCompositorControllerChild> mUiCompositorControllerChild;
+ // Whether we have requested a new Surface from the GeckoSession.
+ bool mRequestedNewSurface = false;
+
+ Maybe<uint32_t> mDefaultClearColor;
+
+ struct CaptureRequest {
+ explicit CaptureRequest() : mResult(nullptr) {}
+ explicit CaptureRequest(java::GeckoResult::GlobalRef aResult,
+ java::sdk::Bitmap::GlobalRef aBitmap,
+ const ScreenRect& aSource,
+ const IntSize& aOutputSize)
+ : mResult(aResult),
+ mBitmap(aBitmap),
+ mSource(aSource),
+ mOutputSize(aOutputSize) {}
+
+ // where to send the pixels
+ java::GeckoResult::GlobalRef mResult;
+
+ // where to store the pixels
+ java::sdk::Bitmap::GlobalRef mBitmap;
+
+ ScreenRect mSource;
+
+ IntSize mOutputSize;
+ };
+ std::queue<CaptureRequest> mCapturePixelsResults;
+
+ // In order to use Event::HasSameTypeAs in PostTo(), we cannot make
+ // LayerViewEvent a template because each template instantiation is
+ // a different type. So implement LayerViewEvent as a ProxyEvent.
+ class LayerViewEvent final : public nsAppShell::ProxyEvent {
+ using Event = nsAppShell::Event;
+
+ public:
+ static UniquePtr<Event> MakeEvent(UniquePtr<Event>&& event) {
+ return MakeUnique<LayerViewEvent>(std::move(event));
+ }
+
+ explicit LayerViewEvent(UniquePtr<Event>&& event)
+ : nsAppShell::ProxyEvent(std::move(event)) {}
+
+ void PostTo(LinkedList<Event>& queue) override {
+ // Give priority to compositor events, but keep in order with
+ // existing compositor events.
+ nsAppShell::Event* event = queue.getFirst();
+ while (event && event->HasSameTypeAs(this)) {
+ event = event->getNext();
+ }
+ if (event) {
+ event->setPrevious(this);
+ } else {
+ queue.insertBack(this);
+ }
+ }
+ };
+
+ public:
+ typedef GeckoSession::Compositor::Natives<LayerViewSupport> Base;
+
+ LayerViewSupport(WindowPtr aWindow,
+ const GeckoSession::Compositor::LocalRef& aInstance)
+ : mWindow(aWindow), mCompositor(aInstance), mCompositorPaused(true) {
+#if defined(DEBUG)
+ auto win(mWindow.Access());
+ MOZ_ASSERT(!!win);
+#endif // defined(DEBUG)
+ }
+
+ ~LayerViewSupport() {}
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ RefPtr<Runnable> disposer = aDisposer;
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ GeckoSession::Compositor::GlobalRef compositor(mCompositor);
+ if (!compositor) {
+ return;
+ }
+
+ uiThread->Dispatch(NS_NewRunnableFunction(
+ "LayerViewSupport::OnWeakNonIntrusiveDetach",
+ [compositor, disposer = std::move(disposer),
+ results = &mCapturePixelsResults, window = mWindow]() mutable {
+ if (auto accWindow = window.Access()) {
+ while (!results->empty()) {
+ auto aResult =
+ java::GeckoResult::LocalRef(results->front().mResult);
+ if (aResult) {
+ aResult->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "The compositor has detached from the session")
+ .Cast<jni::Throwable>());
+ }
+ results->pop();
+ }
+ }
+
+ compositor->OnCompositorDetached();
+ disposer->Run();
+ }));
+ }
+ }
+
+ const GeckoSession::Compositor::Ref& GetJavaCompositor() const {
+ return mCompositor;
+ }
+
+ bool CompositorPaused() const { return mCompositorPaused; }
+
+ /// Called from the main thread whenever the compositor has been
+ /// (re)initialized.
+ void NotifyCompositorCreated(
+ RefPtr<UiCompositorControllerChild> aUiCompositorControllerChild) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ mUiCompositorControllerChild = aUiCompositorControllerChild;
+
+ if (mDefaultClearColor) {
+ mUiCompositorControllerChild->SetDefaultClearColor(*mDefaultClearColor);
+ }
+
+ if (!mCompositorPaused) {
+ // If we are using SurfaceControl but mSurface is null, that means the
+ // previous surface was destroyed along with the the previous
+ // compositor, and we need to create a new one.
+ if (mSurfaceControl && !mSurface) {
+ mSurface = java::SurfaceControlManager::GetInstance()->GetChildSurface(
+ mSurfaceControl, mWidth, mHeight);
+ }
+
+ if (auto window{mWindow.Access()}) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ mUiCompositorControllerChild->OnCompositorSurfaceChanged(
+ gkWindow->mWidgetId, mSurface);
+ }
+ }
+
+ bool resumed = mUiCompositorControllerChild->ResumeAndResize(
+ mX, mY, mWidth, mHeight);
+ if (!resumed) {
+ gfxCriticalNote
+ << "Failed to resume compositor from NotifyCompositorCreated";
+ RequestNewSurface();
+ }
+ }
+ }
+
+ /// Called from the main thread whenever the compositor has been destroyed.
+ void NotifyCompositorSessionLost() {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ mUiCompositorControllerChild = nullptr;
+
+ if (mSurfaceControl) {
+ // If we are using SurfaceControl then we must set the Surface to null
+ // here to ensure we create a new one when the new compositor is
+ // created.
+ mSurface = nullptr;
+ }
+
+ if (auto window = mWindow.Access()) {
+ while (!mCapturePixelsResults.empty()) {
+ auto result =
+ java::GeckoResult::LocalRef(mCapturePixelsResults.front().mResult);
+ if (result) {
+ result->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "Compositor session lost during screen pixels request")
+ .Cast<jni::Throwable>());
+ }
+ mCapturePixelsResults.pop();
+ }
+ }
+ }
+
+ java::sdk::Surface::Param GetSurface() { return mSurface; }
+
+ private:
+ already_AddRefed<DataSourceSurface> FlipScreenPixels(
+ Shmem& aMem, const ScreenIntSize& aInSize, const ScreenRect& aInRegion,
+ const IntSize& aOutSize) {
+ RefPtr<gfx::DataSourceSurface> image =
+ gfx::Factory::CreateWrappingDataSourceSurface(
+ aMem.get<uint8_t>(),
+ StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aInSize.width),
+ IntSize(aInSize.width, aInSize.height), SurfaceFormat::B8G8R8A8);
+ RefPtr<gfx::DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ aOutSize, SurfaceFormat::B8G8R8A8);
+ if (!drawTarget) {
+ return nullptr;
+ }
+
+ drawTarget->SetTransform(Matrix::Scaling(1.0, -1.0) *
+ Matrix::Translation(0, aOutSize.height));
+
+ gfx::Rect srcRect(aInRegion.x,
+ (aInSize.height - aInRegion.height) - aInRegion.y,
+ aInRegion.width, aInRegion.height);
+ gfx::Rect destRect(0, 0, aOutSize.width, aOutSize.height);
+ drawTarget->DrawSurface(image, destRect, srcRect);
+
+ RefPtr<gfx::SourceSurface> snapshot = drawTarget->Snapshot();
+ RefPtr<gfx::DataSourceSurface> data = snapshot->GetDataSurface();
+ return data.forget();
+ }
+
+ /**
+ * Compositor methods
+ */
+ public:
+ void AttachNPZC(jni::Object::Param aNPZC) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aNPZC);
+
+ auto locked(mWindow.Access());
+ if (!locked) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = locked->GetNsWindow();
+
+ // We can have this situation if we get two GeckoViewSupport::Transfer()
+ // called before the first AttachNPZC() gets here. Just detach the current
+ // instance since that's what happens in GeckoViewSupport::Transfer() as
+ // well.
+ gkWindow->mNPZCSupport.Detach();
+
+ auto npzc = java::PanZoomController::NativeProvider::LocalRef(
+ jni::GetGeckoThreadEnv(),
+ java::PanZoomController::NativeProvider::Ref::From(aNPZC));
+ gkWindow->mNPZCSupport =
+ jni::NativeWeakPtrHolder<NPZCSupport>::Attach(npzc, mWindow, npzc);
+
+ DispatchToUiThread(
+ "LayerViewSupport::AttachNPZC",
+ [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc)] {
+ npzc->SetAttached(true);
+ });
+ }
+
+ void OnBoundsChanged(int32_t aLeft, int32_t aTop, int32_t aWidth,
+ int32_t aHeight) {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto acc = mWindow.Access();
+ if (!acc) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (!gkWindow) {
+ return;
+ }
+
+ gkWindow->Resize(aLeft, aTop, aWidth, aHeight, /* repaint */ false);
+ }
+
+ void NotifyMemoryPressure() {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto acc = mWindow.Access();
+ if (!acc) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (!gkWindow || !gkWindow->mCompositorBridgeChild) {
+ return;
+ }
+
+ gkWindow->mCompositorBridgeChild->SendNotifyMemoryPressure();
+ }
+
+ void SetDynamicToolbarMaxHeight(int32_t aHeight) {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto acc = mWindow.Access();
+ if (!acc) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (!gkWindow) {
+ return;
+ }
+
+ gkWindow->UpdateDynamicToolbarMaxHeight(ScreenIntCoord(aHeight));
+ }
+
+ void SyncPauseCompositor() {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ mCompositorPaused = true;
+
+ if (mUiCompositorControllerChild) {
+ mUiCompositorControllerChild->Pause();
+
+ mSurface = nullptr;
+ mSurfaceControl = nullptr;
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ mUiCompositorControllerChild->OnCompositorSurfaceChanged(
+ gkWindow->mWidgetId, nullptr);
+ }
+ }
+ }
+
+ if (auto lock{mWindow.Access()}) {
+ while (!mCapturePixelsResults.empty()) {
+ auto result =
+ java::GeckoResult::LocalRef(mCapturePixelsResults.front().mResult);
+ if (result) {
+ result->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "The compositor has detached from the session")
+ .Cast<jni::Throwable>());
+ }
+ mCapturePixelsResults.pop();
+ }
+ }
+ }
+
+ void SyncResumeCompositor() {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ if (mUiCompositorControllerChild) {
+ mCompositorPaused = false;
+ bool resumed = mUiCompositorControllerChild->Resume();
+ if (!resumed) {
+ gfxCriticalNote
+ << "Failed to resume compositor from SyncResumeCompositor";
+ RequestNewSurface();
+ }
+ }
+ }
+
+ void SyncResumeResizeCompositor(
+ const GeckoSession::Compositor::LocalRef& aObj, int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight, jni::Object::Param aSurface,
+ jni::Object::Param aSurfaceControl) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ mX = aX;
+ mY = aY;
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mSurfaceControl =
+ java::sdk::SurfaceControl::GlobalRef::From(aSurfaceControl);
+ if (mSurfaceControl) {
+ // When using SurfaceControl, we create a child Surface to render in to
+ // rather than rendering directly in to the Surface provided by the
+ // application. This allows us to work around a bug on some versions of
+ // Android when recovering from a GPU process crash.
+ mSurface = java::SurfaceControlManager::GetInstance()->GetChildSurface(
+ mSurfaceControl, mWidth, mHeight);
+ } else {
+ mSurface = java::sdk::Surface::GlobalRef::From(aSurface);
+ }
+
+ if (mUiCompositorControllerChild) {
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ // Send new Surface to GPU process, if one exists.
+ mUiCompositorControllerChild->OnCompositorSurfaceChanged(
+ gkWindow->mWidgetId, mSurface);
+ }
+ }
+
+ bool resumed = mUiCompositorControllerChild->ResumeAndResize(
+ aX, aY, aWidth, aHeight);
+ if (!resumed) {
+ gfxCriticalNote
+ << "Failed to resume compositor from SyncResumeResizeCompositor";
+ // Only request a new Surface if this SyncResumeAndResize call is not
+ // response to a previous request, otherwise we will get stuck in an
+ // infinite loop.
+ if (!mRequestedNewSurface) {
+ RequestNewSurface();
+ }
+ return;
+ }
+ }
+
+ mRequestedNewSurface = false;
+
+ mCompositorPaused = false;
+
+ class OnResumedEvent : public nsAppShell::Event {
+ GeckoSession::Compositor::GlobalRef mCompositor;
+
+ public:
+ explicit OnResumedEvent(GeckoSession::Compositor::GlobalRef&& aCompositor)
+ : mCompositor(std::move(aCompositor)) {}
+
+ void Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ const auto lvsHolder =
+ GetNative(GeckoSession::Compositor::LocalRef(env, mCompositor));
+
+ if (!lvsHolder) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ auto lvs(lvsHolder->Access());
+ if (!lvs) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ auto win = lvs->mWindow.Access();
+ if (!win) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ // When we get here, the compositor has already been told to
+ // resume. This means it's now safe for layer updates to occur.
+ // Since we might have prevented one or more draw events from
+ // occurring while the compositor was paused, we need to
+ // schedule a draw event now.
+ if (!lvs->mCompositorPaused) {
+ nsWindow* const gkWindow = win->GetNsWindow();
+ if (gkWindow) {
+ gkWindow->RedrawAll();
+ }
+ }
+ }
+ };
+
+ // Use priority queue for timing-sensitive event.
+ nsAppShell::PostEvent(
+ MakeUnique<LayerViewEvent>(MakeUnique<OnResumedEvent>(aObj)));
+ }
+
+ void RequestNewSurface() {
+ if (const auto& compositor = GetJavaCompositor()) {
+ mRequestedNewSurface = true;
+ if (mSurfaceControl) {
+ java::SurfaceControlManager::GetInstance()->RemoveSurface(
+ mSurfaceControl);
+ }
+ compositor->RequestNewSurface();
+ }
+ }
+
+ mozilla::jni::Object::LocalRef GetMagnifiableSurface() {
+ return mozilla::jni::Object::LocalRef::From(GetSurface());
+ }
+
+ void SyncInvalidateAndScheduleComposite() {
+ if (!mUiCompositorControllerChild) {
+ return;
+ }
+
+ if (AndroidBridge::IsJavaUiThread()) {
+ mUiCompositorControllerChild->InvalidateAndRender();
+ return;
+ }
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(NewRunnableMethod<>(
+ "LayerViewSupport::InvalidateAndRender",
+ mUiCompositorControllerChild,
+ &UiCompositorControllerChild::InvalidateAndRender),
+ nsIThread::DISPATCH_NORMAL);
+ }
+ }
+
+ void SetMaxToolbarHeight(int32_t aHeight) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ if (mUiCompositorControllerChild) {
+ mUiCompositorControllerChild->SetMaxToolbarHeight(aHeight);
+ }
+ }
+
+ void SetFixedBottomOffset(int32_t aOffset) {
+ if (auto acc{mWindow.Access()}) {
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (gkWindow) {
+ gkWindow->UpdateDynamicToolbarOffset(ScreenIntCoord(aOffset));
+ }
+ }
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(NS_NewRunnableFunction(
+ "LayerViewSupport::SetFixedBottomOffset", [this, offset = aOffset] {
+ if (mUiCompositorControllerChild) {
+ mUiCompositorControllerChild->SetFixedBottomOffset(offset);
+ }
+ }));
+ }
+ }
+
+ void SendToolbarAnimatorMessage(int32_t aMessage) {
+ if (!mUiCompositorControllerChild) {
+ return;
+ }
+
+ if (AndroidBridge::IsJavaUiThread()) {
+ mUiCompositorControllerChild->ToolbarAnimatorMessageFromUI(aMessage);
+ return;
+ }
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(
+ NewRunnableMethod<int32_t>(
+ "LayerViewSupport::ToolbarAnimatorMessageFromUI",
+ mUiCompositorControllerChild,
+ &UiCompositorControllerChild::ToolbarAnimatorMessageFromUI,
+ aMessage),
+ nsIThread::DISPATCH_NORMAL);
+ }
+ }
+
+ void RecvToolbarAnimatorMessage(int32_t aMessage) {
+ auto compositor = GeckoSession::Compositor::LocalRef(mCompositor);
+ if (compositor) {
+ compositor->RecvToolbarAnimatorMessage(aMessage);
+ }
+ }
+
+ void SetDefaultClearColor(int32_t aColor) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ mDefaultClearColor = Some((uint32_t)aColor);
+ if (mUiCompositorControllerChild) {
+ mUiCompositorControllerChild->SetDefaultClearColor((uint32_t)aColor);
+ }
+ }
+
+ void RequestScreenPixels(jni::Object::Param aResult,
+ jni::Object::Param aTarget, int32_t aXOffset,
+ int32_t aYOffset, int32_t aSrcWidth,
+ int32_t aSrcHeight, int32_t aOutWidth,
+ int32_t aOutHeight) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ auto result = java::GeckoResult::LocalRef(aResult);
+
+ if (!mUiCompositorControllerChild) {
+ if (result) {
+ if (auto window = mWindow.Access()) {
+ result->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "Compositor session lost prior to screen pixels request")
+ .Cast<jni::Throwable>());
+ }
+ }
+ return;
+ }
+
+ int size = 0;
+ if (auto window = mWindow.Access()) {
+ mCapturePixelsResults.push(CaptureRequest(
+ java::GeckoResult::GlobalRef(result),
+ java::sdk::Bitmap::GlobalRef(java::sdk::Bitmap::LocalRef(aTarget)),
+ ScreenRect(aXOffset, aYOffset, aSrcWidth, aSrcHeight),
+ IntSize(aOutWidth, aOutHeight)));
+ size = mCapturePixelsResults.size();
+ }
+
+ if (size == 1) {
+ mUiCompositorControllerChild->RequestScreenPixels();
+ }
+ }
+
+ void RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize,
+ bool aNeedsYFlip) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ CaptureRequest request;
+ java::GeckoResult::LocalRef result = nullptr;
+ java::sdk::Bitmap::LocalRef bitmap = nullptr;
+ if (auto window = mWindow.Access()) {
+ // The result might have been already rejected if the compositor was
+ // detached from the session
+ if (!mCapturePixelsResults.empty()) {
+ request = mCapturePixelsResults.front();
+ result = java::GeckoResult::LocalRef(request.mResult);
+ bitmap = java::sdk::Bitmap::LocalRef(request.mBitmap);
+ mCapturePixelsResults.pop();
+ }
+ }
+
+ if (result) {
+ if (bitmap) {
+ RefPtr<DataSourceSurface> surf;
+ if (aNeedsYFlip) {
+ surf = FlipScreenPixels(aMem, aSize, request.mSource,
+ request.mOutputSize);
+ } else {
+ surf = gfx::Factory::CreateWrappingDataSourceSurface(
+ aMem.get<uint8_t>(),
+ StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aSize.width),
+ IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8);
+ }
+ if (surf) {
+ DataSourceSurface::ScopedMap smap(surf, DataSourceSurface::READ);
+ auto pixels = mozilla::jni::ByteBuffer::New(
+ reinterpret_cast<int8_t*>(smap.GetData()),
+ smap.GetStride() * request.mOutputSize.height);
+ bitmap->CopyPixelsFromBuffer(pixels);
+ result->Complete(bitmap);
+ } else {
+ result->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "Failed to create flipped snapshot surface (probably out "
+ "of memory)")
+ .Cast<jni::Throwable>());
+ }
+ } else {
+ result->CompleteExceptionally(java::sdk::IllegalArgumentException::New(
+ "No target bitmap argument provided")
+ .Cast<jni::Throwable>());
+ }
+ }
+
+ // Pixels have been copied, so Dealloc Shmem
+ if (mUiCompositorControllerChild) {
+ mUiCompositorControllerChild->DeallocPixelBuffer(aMem);
+
+ if (auto window = mWindow.Access()) {
+ if (!mCapturePixelsResults.empty()) {
+ mUiCompositorControllerChild->RequestScreenPixels();
+ }
+ }
+ }
+ }
+
+ void EnableLayerUpdateNotifications(bool aEnable) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (mUiCompositorControllerChild) {
+ mUiCompositorControllerChild->EnableLayerUpdateNotifications(aEnable);
+ }
+ }
+
+ void OnSafeAreaInsetsChanged(int32_t aTop, int32_t aRight, int32_t aBottom,
+ int32_t aLeft) {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto win(mWindow.Access());
+ if (!win) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = win->GetNsWindow();
+ if (!gkWindow) {
+ return;
+ }
+
+ ScreenIntMargin safeAreaInsets(aTop, aRight, aBottom, aLeft);
+ gkWindow->UpdateSafeAreaInsets(safeAreaInsets);
+ }
+};
+
+GeckoViewSupport::~GeckoViewSupport() {
+ if (mWindow) {
+ mWindow->DetachNatives();
+ }
+}
+
+/* static */
+void GeckoViewSupport::Open(
+ const jni::Class::LocalRef& aCls, GeckoSession::Window::Param aWindow,
+ jni::Object::Param aQueue, jni::Object::Param aCompositor,
+ jni::Object::Param aDispatcher, jni::Object::Param aSessionAccessibility,
+ jni::Object::Param aInitData, jni::String::Param aId,
+ jni::String::Param aChromeURI, bool aPrivateMode) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ AUTO_PROFILER_LABEL("mozilla::widget::GeckoViewSupport::Open", OTHER);
+
+ // We'll need gfxPlatform to be initialized to create a compositor later.
+ // Might as well do that now so that the GPU process launch can get a head
+ // start.
+ gfxPlatform::GetPlatform();
+
+ nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ MOZ_RELEASE_ASSERT(ww);
+
+ nsAutoCString url;
+ if (aChromeURI) {
+ url = aChromeURI->ToCString();
+ } else {
+ nsresult rv = Preferences::GetCString("toolkit.defaultChromeURI", url);
+ if (NS_FAILED(rv)) {
+ url = "chrome://geckoview/content/geckoview.xhtml"_ns;
+ }
+ }
+
+ // Prepare an nsIAndroidView to pass as argument to the window.
+ RefPtr<AndroidView> androidView = new AndroidView();
+ androidView->mEventDispatcher->Attach(
+ java::EventDispatcher::Ref::From(aDispatcher), nullptr);
+ androidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
+
+ nsAutoCString chromeFlags("chrome,dialog=0,remote,resizable,scrollbars");
+ if (aPrivateMode) {
+ chromeFlags += ",private";
+ }
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ ww->OpenWindow(nullptr, url, nsDependentCString(aId->ToCString().get()),
+ chromeFlags, androidView, getter_AddRefs(domWindow));
+ MOZ_RELEASE_ASSERT(domWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> pdomWindow = nsPIDOMWindowOuter::From(domWindow);
+ const RefPtr<nsWindow> window = nsWindow::From(pdomWindow);
+ MOZ_ASSERT(window);
+
+ // Attach a new GeckoView support object to the new window.
+ GeckoSession::Window::LocalRef sessionWindow(aCls.Env(), aWindow);
+ auto weakGeckoViewSupport =
+ jni::NativeWeakPtrHolder<GeckoViewSupport>::Attach(
+ sessionWindow, window, sessionWindow, pdomWindow);
+
+ window->mGeckoViewSupport = weakGeckoViewSupport;
+ window->mAndroidView = androidView;
+
+ // Attach other session support objects.
+ { // Scope for gvsAccess
+ auto gvsAccess = weakGeckoViewSupport.Access();
+ MOZ_ASSERT(gvsAccess);
+
+ gvsAccess->Transfer(sessionWindow, aQueue, aCompositor, aDispatcher,
+ aSessionAccessibility, aInitData);
+ }
+
+ if (window->mWidgetListener) {
+ nsCOMPtr<nsIAppWindow> appWindow(window->mWidgetListener->GetAppWindow());
+ if (appWindow) {
+ // Our window is not intrinsically sized, so tell AppWindow to
+ // not set a size for us.
+ appWindow->SetIntrinsicallySized(false);
+ }
+ }
+}
+
+void GeckoViewSupport::Close() {
+ if (mWindow) {
+ if (mWindow->mAndroidView) {
+ mWindow->mAndroidView->mEventDispatcher->Detach();
+ }
+ mWindow = nullptr;
+ }
+
+ if (!mDOMWindow) {
+ return;
+ }
+
+ mDOMWindow->ForceClose();
+ mDOMWindow = nullptr;
+ mGeckoViewWindow = nullptr;
+}
+
+void GeckoViewSupport::Transfer(const GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aQueue,
+ jni::Object::Param aCompositor,
+ jni::Object::Param aDispatcher,
+ jni::Object::Param aSessionAccessibility,
+ jni::Object::Param aInitData) {
+ mWindow->mNPZCSupport.Detach();
+
+ auto compositor = GeckoSession::Compositor::LocalRef(
+ inst.Env(), GeckoSession::Compositor::Ref::From(aCompositor));
+
+ bool attachLvs;
+ { // Scope for lvsAccess
+ auto lvsAccess{mWindow->mLayerViewSupport.Access()};
+ // If we do not yet have mLayerViewSupport, or if the compositor has
+ // changed, then we must attach a new one.
+ attachLvs = !lvsAccess || lvsAccess->GetJavaCompositor() != compositor;
+ }
+
+ if (attachLvs) {
+ mWindow->mLayerViewSupport =
+ jni::NativeWeakPtrHolder<LayerViewSupport>::Attach(
+ compositor, mWindow->mGeckoViewSupport, compositor);
+
+ if (RefPtr<UiCompositorControllerChild> uiCompositorController =
+ mWindow->GetUiCompositorControllerChild()) {
+ DispatchToUiThread(
+ "LayerViewSupport::NotifyCompositorCreated",
+ [lvs = mWindow->mLayerViewSupport, uiCompositorController] {
+ if (auto lvsAccess{lvs.Access()}) {
+ lvsAccess->NotifyCompositorCreated(uiCompositorController);
+ }
+ });
+ }
+ }
+
+ MOZ_ASSERT(mWindow->mAndroidView);
+ mWindow->mAndroidView->mEventDispatcher->Attach(
+ java::EventDispatcher::Ref::From(aDispatcher), mDOMWindow);
+
+ RefPtr<jni::DetachPromise> promise = mWindow->mSessionAccessibility.Detach();
+ if (aSessionAccessibility) {
+ // SessionAccessibility's JNI object isn't released immediately, it uses
+ // recycled object, we have to wait for released object completely.
+ auto sa = java::SessionAccessibility::NativeProvider::LocalRef(
+ aSessionAccessibility);
+ promise->Then(
+ GetMainThreadSerialEventTarget(),
+ "GeckoViewSupprt::Transfer::SessionAccessibility",
+ [inst = GeckoSession::Window::GlobalRef(inst),
+ sa = java::SessionAccessibility::NativeProvider::GlobalRef(sa),
+ window = mWindow, gvs = mWindow->mGeckoViewSupport](
+ const mozilla::jni::DetachPromise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(aValue.IsResolve());
+ if (window->Destroyed()) {
+ return;
+ }
+
+ MOZ_ASSERT(!window->mSessionAccessibility.IsAttached());
+ if (auto gvsAccess{gvs.Access()}) {
+ gvsAccess->AttachAccessibility(inst, sa);
+ }
+ });
+ }
+
+ if (mIsReady) {
+ // We're in a transfer; update init-data and notify JS code.
+ mWindow->mAndroidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
+ OnReady(aQueue);
+ mWindow->mAndroidView->mEventDispatcher->Dispatch(
+ u"GeckoView:UpdateInitData");
+ }
+
+ DispatchToUiThread("GeckoViewSupport::Transfer",
+ [compositor = GeckoSession::Compositor::GlobalRef(
+ compositor)] { compositor->OnCompositorAttached(); });
+}
+
+void GeckoViewSupport::AttachEditable(
+ const GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aEditableParent) {
+ if (auto win{mWindow->mEditableSupport.Access()}) {
+ win->TransferParent(aEditableParent);
+ } else {
+ auto editableChild = java::GeckoEditableChild::New(aEditableParent,
+ /* default */ true);
+ mWindow->mEditableSupport =
+ jni::NativeWeakPtrHolder<GeckoEditableSupport>::Attach(
+ editableChild, mWindow->mGeckoViewSupport, editableChild);
+ }
+
+ mWindow->mEditableParent = aEditableParent;
+}
+
+void GeckoViewSupport::AttachAccessibility(
+ const GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aSessionAccessibility) {
+ java::SessionAccessibility::NativeProvider::LocalRef sessionAccessibility(
+ inst.Env());
+ sessionAccessibility = java::SessionAccessibility::NativeProvider::Ref::From(
+ aSessionAccessibility);
+
+ mWindow->mSessionAccessibility =
+ jni::NativeWeakPtrHolder<a11y::SessionAccessibility>::Attach(
+ sessionAccessibility, mWindow->mGeckoViewSupport,
+ sessionAccessibility);
+}
+
+auto GeckoViewSupport::OnLoadRequest(mozilla::jni::String::Param aUri,
+ int32_t aWindowType, int32_t aFlags,
+ mozilla::jni::String::Param aTriggeringUri,
+ bool aHasUserGesture,
+ bool aIsTopLevel) const
+ -> java::GeckoResult::LocalRef {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return nullptr;
+ }
+ return window->OnLoadRequest(aUri, aWindowType, aFlags, aTriggeringUri,
+ aHasUserGesture, aIsTopLevel);
+}
+
+void GeckoViewSupport::OnShowDynamicToolbar() const {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return;
+ }
+
+ window->OnShowDynamicToolbar();
+}
+
+void GeckoViewSupport::OnReady(jni::Object::Param aQueue) {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return;
+ }
+ window->OnReady(aQueue);
+ mIsReady = true;
+}
+
+void GeckoViewSupport::PassExternalResponse(
+ java::WebResponse::Param aResponse) {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return;
+ }
+
+ auto response = java::WebResponse::GlobalRef(aResponse);
+
+ DispatchToUiThread("GeckoViewSupport::PassExternalResponse",
+ [window = java::GeckoSession::Window::GlobalRef(window),
+ response] { window->PassExternalWebResponse(response); });
+}
+
+RefPtr<CanonicalBrowsingContext>
+GeckoViewSupport::GetContentCanonicalBrowsingContext() {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = mDOMWindow->GetTreeOwner();
+ if (!treeOwner) {
+ return nullptr;
+ }
+ RefPtr<BrowsingContext> bc;
+ nsresult rv = treeOwner->GetPrimaryContentBrowsingContext(getter_AddRefs(bc));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !bc) {
+ return nullptr;
+ }
+ return bc->Canonical();
+}
+
+void GeckoViewSupport::CreatePdf(
+ jni::LocalRef<mozilla::java::GeckoResult> aGeckoResult,
+ RefPtr<dom::CanonicalBrowsingContext> aCbc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const auto pdfErrorMsg = "Could not save this page as PDF.";
+ auto stream = java::GeckoInputStream::New(nullptr);
+ RefPtr<GeckoViewOutputStream> streamListener =
+ new GeckoViewOutputStream(stream);
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (!printSettingsService) {
+ aGeckoResult->CompleteExceptionally(
+ GeckoPrintException::New(
+ GeckoPrintException::ERROR_PRINT_SETTINGS_SERVICE_NOT_AVAILABLE)
+ .Cast<jni::Throwable>());
+ GVS_LOG("Could not create print settings service.");
+ return;
+ }
+
+ nsCOMPtr<nsIPrintSettings> printSettings;
+ nsresult rv = printSettingsService->CreateNewPrintSettings(
+ getter_AddRefs(printSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aGeckoResult->CompleteExceptionally(
+ GeckoPrintException::New(
+ GeckoPrintException::ERROR_UNABLE_TO_CREATE_PRINT_SETTINGS)
+ .Cast<jni::Throwable>());
+ GVS_LOG("Could not create print settings.");
+ return;
+ }
+
+ printSettings->SetPrinterName(u"Mozilla Save to PDF"_ns);
+ printSettings->SetOutputDestination(
+ nsIPrintSettings::kOutputDestinationStream);
+ printSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
+ printSettings->SetOutputStream(streamListener);
+ printSettings->SetPrintSilent(true);
+
+ RefPtr<CanonicalBrowsingContext::PrintPromise> print =
+ aCbc->Print(printSettings);
+
+ aGeckoResult->Complete(stream);
+ print->Then(
+ mozilla::GetCurrentSerialEventTarget(), __func__,
+ [result = java::GeckoResult::GlobalRef(aGeckoResult), stream,
+ pdfErrorMsg](
+ const CanonicalBrowsingContext::PrintPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ GVS_LOG("Could not print. %s", pdfErrorMsg);
+ stream->WriteError();
+ }
+ });
+}
+
+void GeckoViewSupport::PrintToPdf(
+ const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aResult) {
+ auto geckoResult = java::GeckoResult::Ref::From(aResult);
+ RefPtr<CanonicalBrowsingContext> cbc = GetContentCanonicalBrowsingContext();
+ if (!cbc) {
+ geckoResult->CompleteExceptionally(
+ GeckoPrintException::New(
+ GeckoPrintException::
+ ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT)
+ .Cast<jni::Throwable>());
+ GVS_LOG("Could not retrieve content canonical browsing context.");
+ return;
+ }
+ CreatePdf(geckoResult, cbc);
+}
+
+void GeckoViewSupport::PrintToPdf(
+ const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aResult, int64_t aBcId) {
+ auto geckoResult = java::GeckoResult::Ref::From(aResult);
+
+ RefPtr<CanonicalBrowsingContext> cbc = CanonicalBrowsingContext::Get(aBcId);
+ if (!cbc) {
+ geckoResult->CompleteExceptionally(
+ GeckoPrintException::New(
+ GeckoPrintException::
+ ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT)
+ .Cast<jni::Throwable>());
+ GVS_LOG("Could not retrieve content canonical browsing context by ID.");
+ return;
+ }
+ CreatePdf(geckoResult, cbc);
+}
+} // namespace widget
+} // namespace mozilla
+
+void nsWindow::InitNatives() {
+ jni::InitConversionStatics();
+ mozilla::widget::GeckoViewSupport::Base::Init();
+ mozilla::widget::LayerViewSupport::Init();
+ mozilla::widget::NPZCSupport::Init();
+
+ mozilla::widget::GeckoEditableSupport::Init();
+ a11y::SessionAccessibility::Init();
+}
+
+void nsWindow::DetachNatives() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mEditableSupport.Detach();
+ mNPZCSupport.Detach();
+ mLayerViewSupport.Detach();
+ mSessionAccessibility.Detach();
+}
+
+/* static */
+already_AddRefed<nsWindow> nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) {
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aDOMWindow);
+ return From(widget);
+}
+
+/* static */
+already_AddRefed<nsWindow> nsWindow::From(nsIWidget* aWidget) {
+ // `widget` may be one of several different types in the parent
+ // process, including the Android nsWindow, PuppetWidget, etc. To
+ // ensure that the cast to the Android nsWindow is valid, we check that the
+ // widget is a top-level window and that its NS_NATIVE_WIDGET value is
+ // non-null, which is not the case for non-native widgets like
+ // PuppetWidget.
+ if (aWidget && aWidget->GetWindowType() == WindowType::TopLevel &&
+ aWidget->GetNativeData(NS_NATIVE_WIDGET) == aWidget) {
+ RefPtr<nsWindow> window = static_cast<nsWindow*>(aWidget);
+ return window.forget();
+ }
+ return nullptr;
+}
+
+nsWindow* nsWindow::TopWindow() {
+ if (!gTopLevelWindows.IsEmpty()) return gTopLevelWindows[0];
+ return nullptr;
+}
+
+void nsWindow::LogWindow(nsWindow* win, int index, int indent) {
+#if defined(DEBUG) || defined(FORCE_ALOG)
+ char spaces[] = " ";
+ spaces[indent < 20 ? indent : 20] = 0;
+ ALOG("%s [% 2d] 0x%p [parent 0x%p] [% 3d,% 3dx% 3d,% 3d] vis %d type %d",
+ spaces, index, win, win->mParent, win->mBounds.x, win->mBounds.y,
+ win->mBounds.width, win->mBounds.height, win->mIsVisible,
+ int(win->mWindowType));
+#endif
+}
+
+void nsWindow::DumpWindows() { DumpWindows(gTopLevelWindows); }
+
+void nsWindow::DumpWindows(const nsTArray<nsWindow*>& wins, int indent) {
+ for (uint32_t i = 0; i < wins.Length(); ++i) {
+ nsWindow* w = wins[i];
+ LogWindow(w, i, indent);
+ DumpWindows(w->mChildren, indent + 1);
+ }
+}
+
+nsWindow::nsWindow()
+ : mWidgetId(++sWidgetId),
+ mIsVisible(false),
+ mParent(nullptr),
+ mDynamicToolbarMaxHeight(0),
+ mSizeMode(nsSizeMode_Normal),
+ mIsFullScreen(false),
+ mCompositorWidgetDelegate(nullptr),
+ mDestroyMutex("nsWindow::mDestroyMutex") {}
+
+nsWindow::~nsWindow() {
+ gTopLevelWindows.RemoveElement(this);
+ ALOG("nsWindow %p destructor", (void*)this);
+ // The mCompositorSession should have been cleaned up in nsWindow::Destroy()
+ // DestroyLayerManager() will call DestroyCompositor() which will crash if
+ // called from nsBaseWidget destructor. See Bug 1392705
+ MOZ_ASSERT(!mCompositorSession);
+}
+
+bool nsWindow::IsTopLevel() {
+ return mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog ||
+ mWindowType == WindowType::Invisible;
+}
+
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ InitData* aInitData) {
+ ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent,
+ aRect.x, aRect.y, aRect.width, aRect.height);
+
+ nsWindow* parent = (nsWindow*)aParent;
+ if (aNativeParent) {
+ if (parent) {
+ ALOG(
+ "Ignoring native parent on Android window [%p], "
+ "since parent was specified (%p %p)",
+ (void*)this, (void*)aNativeParent, (void*)aParent);
+ } else {
+ parent = (nsWindow*)aNativeParent;
+ }
+ }
+
+ // A default size of 1x1 confuses MobileViewportManager, so
+ // use 0x0 instead. This is also a little more fitting since
+ // we don't yet have a surface yet (and therefore a valid size)
+ // and 0x0 is usually recognized as invalid.
+ LayoutDeviceIntRect rect = aRect;
+ if (aRect.width == 1 && aRect.height == 1) {
+ rect.width = 0;
+ rect.height = 0;
+ }
+
+ mBounds = rect;
+ SetSizeConstraints(SizeConstraints());
+
+ BaseCreate(nullptr, aInitData);
+
+ NS_ASSERTION(IsTopLevel() || parent,
+ "non-top-level window doesn't have a parent!");
+
+ if (IsTopLevel()) {
+ gTopLevelWindows.AppendElement(this);
+
+ } else if (parent) {
+ parent->mChildren.AppendElement(this);
+ mParent = parent;
+ }
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+
+ return NS_OK;
+}
+
+void nsWindow::Destroy() {
+ MutexAutoLock lock(mDestroyMutex);
+
+ nsBaseWidget::mOnDestroyCalled = true;
+
+ // Disassociate our native object from GeckoView.
+ mGeckoViewSupport.Detach();
+
+ // Stuff below may release the last ref to this
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ while (mChildren.Length()) {
+ // why do we still have children?
+ ALOG("### Warning: Destroying window %p and reparenting child %p to null!",
+ (void*)this, (void*)mChildren[0]);
+ mChildren[0]->SetParent(nullptr);
+ }
+
+ // Ensure the compositor has been shutdown before this nsWindow is potentially
+ // deleted
+ nsBaseWidget::DestroyCompositor();
+
+ nsBaseWidget::Destroy();
+
+ if (IsTopLevel()) gTopLevelWindows.RemoveElement(this);
+
+ SetParent(nullptr);
+
+ nsBaseWidget::OnDestroy();
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+}
+
+mozilla::widget::EventDispatcher* nsWindow::GetEventDispatcher() const {
+ if (mAndroidView) {
+ return mAndroidView->mEventDispatcher;
+ }
+ return nullptr;
+}
+
+void nsWindow::RedrawAll() {
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->RequestRepaint();
+ } else if (mWidgetListener) {
+ mWidgetListener->RequestRepaint();
+ }
+}
+
+RefPtr<UiCompositorControllerChild> nsWindow::GetUiCompositorControllerChild() {
+ return mCompositorSession
+ ? mCompositorSession->GetUiCompositorControllerChild()
+ : nullptr;
+}
+
+mozilla::layers::LayersId nsWindow::GetRootLayerId() const {
+ return mCompositorSession ? mCompositorSession->RootLayerTreeId()
+ : mozilla::layers::LayersId{0};
+}
+
+void nsWindow::OnGeckoViewReady() {
+ auto acc(mGeckoViewSupport.Access());
+ if (!acc) {
+ return;
+ }
+
+ acc->OnReady();
+}
+
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ if ((nsIWidget*)mParent == aNewParent) return;
+
+ // If we had a parent before, remove ourselves from its list of
+ // children.
+ if (mParent) mParent->mChildren.RemoveElement(this);
+
+ mParent = (nsWindow*)aNewParent;
+
+ if (mParent) mParent->mChildren.AppendElement(this);
+
+ // if we are now in the toplevel window's hierarchy, schedule a redraw
+ if (FindTopLevel() == nsWindow::TopWindow()) RedrawAll();
+}
+
+nsIWidget* nsWindow::GetParent() { return mParent; }
+
+RefPtr<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest(
+ nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
+ nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
+ bool aIsTopLevel) {
+ auto geckoViewSupport(mGeckoViewSupport.Access());
+ if (!geckoViewSupport) {
+ return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__);
+ }
+
+ nsAutoCString spec, triggeringSpec;
+ if (aUri) {
+ aUri->GetDisplaySpec(spec);
+ if (aIsTopLevel && mozilla::net::SchemeIsData(aUri) &&
+ spec.Length() > MAX_TOPLEVEL_DATA_URI_LEN) {
+ return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__);
+ }
+ }
+
+ bool isNullPrincipal = false;
+ if (aTriggeringPrincipal) {
+ aTriggeringPrincipal->GetIsNullPrincipal(&isNullPrincipal);
+
+ if (!isNullPrincipal) {
+ nsCOMPtr<nsIURI> triggeringUri;
+ BasePrincipal::Cast(aTriggeringPrincipal)
+ ->GetURI(getter_AddRefs(triggeringUri));
+ if (triggeringUri) {
+ triggeringUri->GetDisplaySpec(triggeringSpec);
+ }
+ }
+ }
+
+ auto geckoResult = geckoViewSupport->OnLoadRequest(
+ spec.get(), aWindowType, aFlags,
+ isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture,
+ aIsTopLevel);
+ return geckoResult
+ ? MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
+ : nullptr;
+}
+
+void nsWindow::OnUpdateSessionStore(mozilla::jni::Object::Param aBundle) {
+ auto geckoViewSupport(mGeckoViewSupport.Access());
+ if (!geckoViewSupport) {
+ return;
+ }
+
+ geckoViewSupport->OnUpdateSessionStore(aBundle);
+}
+
+float nsWindow::GetDPI() {
+ float dpi = 160.0f;
+
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() {
+ double scale = 1.0f;
+
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetContentsScaleFactor(&scale);
+ }
+
+ return scale;
+}
+
+void nsWindow::Show(bool aState) {
+ ALOG("nsWindow[%p]::Show %d", (void*)this, aState);
+
+ if (mWindowType == WindowType::Invisible) {
+ ALOG("trying to show invisible window! ignoring..");
+ return;
+ }
+
+ if (aState == mIsVisible) return;
+
+ mIsVisible = aState;
+
+ if (IsTopLevel()) {
+ // XXX should we bring this to the front when it's shown,
+ // if it's a toplevel widget?
+
+ // XXX we should synthesize a eMouseExitFromWidget (for old top
+ // window)/eMouseEnterIntoWidget (for new top window) since we need
+ // to pretend that the top window always has focus. Not sure
+ // if Show() is the right place to do this, though.
+
+ if (aState) {
+ // It just became visible, so bring it to the front.
+ BringToFront();
+
+ } else if (nsWindow::TopWindow() == this) {
+ // find the next visible window to show
+ unsigned int i;
+ for (i = 1; i < gTopLevelWindows.Length(); i++) {
+ nsWindow* win = gTopLevelWindows[i];
+ if (!win->mIsVisible) continue;
+
+ win->BringToFront();
+ break;
+ }
+ }
+ } else if (FindTopLevel() == nsWindow::TopWindow()) {
+ RedrawAll();
+ }
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+}
+
+bool nsWindow::IsVisible() const { return mIsVisible; }
+
+void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
+ ALOG("nsWindow[%p]::ConstrainPosition [%d %d]", (void*)this, aPoint.x.value,
+ aPoint.y.value);
+
+ // Constrain toplevel windows; children we don't care about
+ if (IsTopLevel()) {
+ aPoint = DesktopIntPoint();
+ }
+}
+
+void nsWindow::Move(double aX, double aY) {
+ if (IsTopLevel()) return;
+
+ Resize(aX, aY, mBounds.width, mBounds.height, true);
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ Resize(mBounds.x, mBounds.y, aWidth, aHeight, aRepaint);
+}
+
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ ALOG("nsWindow[%p]::Resize [%f %f %f %f] (repaint %d)", (void*)this, aX, aY,
+ aWidth, aHeight, aRepaint);
+
+ LayoutDeviceIntRect oldBounds = mBounds;
+
+ mBounds.x = NSToIntRound(aX);
+ mBounds.y = NSToIntRound(aY);
+ mBounds.width = NSToIntRound(aWidth);
+ mBounds.height = NSToIntRound(aHeight);
+
+ ConstrainSize(&mBounds.width, &mBounds.height);
+
+ bool needPositionDispatch = mBounds.TopLeft() != oldBounds.TopLeft();
+ bool needSizeDispatch = mBounds.Size() != oldBounds.Size();
+
+ if (needSizeDispatch) {
+ OnSizeChanged(mBounds.Size().ToUnknownSize());
+ }
+
+ if (needPositionDispatch) {
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+ }
+
+ // Should we skip honoring aRepaint here?
+ if (aRepaint && FindTopLevel() == nsWindow::TopWindow()) RedrawAll();
+}
+
+void nsWindow::SetZIndex(int32_t aZIndex) {
+ ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex);
+}
+
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ if (aMode == mSizeMode) {
+ return;
+ }
+
+ mSizeMode = aMode;
+
+ switch (aMode) {
+ case nsSizeMode_Minimized:
+ java::GeckoAppShell::MoveTaskToBack();
+ break;
+ case nsSizeMode_Fullscreen:
+ MakeFullScreen(true);
+ break;
+ default:
+ break;
+ }
+}
+
+void nsWindow::Enable(bool aState) {
+ ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState);
+}
+
+bool nsWindow::IsEnabled() const { return true; }
+
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {}
+
+nsWindow* nsWindow::FindTopLevel() {
+ nsWindow* toplevel = this;
+ while (toplevel) {
+ if (toplevel->IsTopLevel()) return toplevel;
+
+ toplevel = toplevel->mParent;
+ }
+
+ ALOG(
+ "nsWindow::FindTopLevel(): couldn't find a toplevel or dialog window in "
+ "this [%p] widget's hierarchy!",
+ (void*)this);
+ return this;
+}
+
+void nsWindow::SetFocus(Raise, mozilla::dom::CallerType aCallerType) {
+ FindTopLevel()->BringToFront();
+}
+
+void nsWindow::BringToFront() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // If the window to be raised is the same as the currently raised one,
+ // do nothing. We need to check the focus manager as well, as the first
+ // window that is created will be first in the window list but won't yet
+ // be focused.
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && fm->GetActiveWindow() && FindTopLevel() == nsWindow::TopWindow()) {
+ return;
+ }
+
+ if (!IsTopLevel()) {
+ FindTopLevel()->BringToFront();
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(this);
+
+ nsWindow* oldTop = nullptr;
+ if (!gTopLevelWindows.IsEmpty()) {
+ oldTop = gTopLevelWindows[0];
+ }
+
+ gTopLevelWindows.RemoveElement(this);
+ gTopLevelWindows.InsertElementAt(0, this);
+
+ if (oldTop) {
+ nsIWidgetListener* listener = oldTop->GetWidgetListener();
+ if (listener) {
+ listener->WindowDeactivated();
+ }
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowActivated();
+ }
+
+ RedrawAll();
+}
+
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
+}
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ LayoutDeviceIntPoint p(0, 0);
+
+ for (nsWindow* w = this; !!w; w = w->mParent) {
+ p.x += w->mBounds.x;
+ p.y += w->mBounds.y;
+
+ if (w->IsTopLevel()) {
+ break;
+ }
+ }
+ return p;
+}
+
+nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+ aStatus = DispatchEvent(aEvent);
+ return NS_OK;
+}
+
+nsEventStatus nsWindow::DispatchEvent(WidgetGUIEvent* aEvent) {
+ if (mAttachedWidgetListener) {
+ return mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ } else if (mWidgetListener) {
+ return mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+ return nsEventStatus_eIgnore;
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
+ if (!mAndroidView) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mIsFullScreen = aFullScreen;
+ mAndroidView->mEventDispatcher->Dispatch(
+ aFullScreen ? u"GeckoView:FullScreenEnter" : u"GeckoView:FullScreenExit");
+
+ nsIWidgetListener* listener = GetWidgetListener();
+ if (listener) {
+ mSizeMode = mIsFullScreen ? nsSizeMode_Fullscreen : nsSizeMode_Normal;
+ listener->SizeModeChanged(mSizeMode);
+ }
+ return NS_OK;
+}
+
+mozilla::WindowRenderer* nsWindow::GetWindowRenderer() {
+ if (!mWindowRenderer) {
+ CreateLayerManager();
+ }
+
+ return mWindowRenderer;
+}
+
+void nsWindow::CreateLayerManager() {
+ if (mWindowRenderer) {
+ return;
+ }
+
+ nsWindow* topLevelWindow = FindTopLevel();
+ if (!topLevelWindow || topLevelWindow->mWindowType == WindowType::Invisible) {
+ // don't create a layer manager for an invisible top-level window
+ return;
+ }
+
+ // Ensure that gfxPlatform is initialized first.
+ gfxPlatform::GetPlatform();
+
+ if (ShouldUseOffMainThreadCompositing()) {
+ LayoutDeviceIntRect rect = GetBounds();
+ CreateCompositor(rect.Width(), rect.Height());
+ if (mWindowRenderer) {
+ if (mLayerViewSupport.IsAttached()) {
+ DispatchToUiThread(
+ "LayerViewSupport::NotifyCompositorCreated",
+ [lvs = mLayerViewSupport,
+ uiCompositorController = GetUiCompositorControllerChild()] {
+ if (auto lvsAccess{lvs.Access()}) {
+ lvsAccess->NotifyCompositorCreated(uiCompositorController);
+ }
+ });
+ }
+
+ return;
+ }
+
+ // If we get here, then off main thread compositing failed to initialize.
+ sFailedToCreateGLContext = true;
+ }
+
+ if (!ComputeShouldAccelerate() || sFailedToCreateGLContext) {
+ printf_stderr(" -- creating basic, not accelerated\n");
+ mWindowRenderer = CreateFallbackRenderer();
+ }
+}
+
+void nsWindow::NotifyCompositorSessionLost(
+ mozilla::layers::CompositorSession* aSession) {
+ nsBaseWidget::NotifyCompositorSessionLost(aSession);
+
+ DispatchToUiThread("nsWindow::NotifyCompositorSessionLost",
+ [lvs = mLayerViewSupport] {
+ if (auto lvsAccess{lvs.Access()}) {
+ lvsAccess->NotifyCompositorSessionLost();
+ }
+ });
+
+ RedrawAll();
+}
+
+void nsWindow::ShowDynamicToolbar() {
+ auto acc(mGeckoViewSupport.Access());
+ if (!acc) {
+ return;
+ }
+
+ acc->OnShowDynamicToolbar();
+}
+
+void GeckoViewSupport::OnUpdateSessionStore(
+ mozilla::jni::Object::Param aBundle) {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return;
+ }
+
+ window->OnUpdateSessionStore(aBundle);
+}
+
+void nsWindow::OnSizeChanged(const gfx::IntSize& aSize) {
+ ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width,
+ aSize.height);
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(
+ LayoutDeviceIntSize::FromUnknownSize(aSize));
+ }
+}
+
+void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
+ if (aPoint) {
+ event.mRefPoint = *aPoint;
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+}
+
+void nsWindow::UpdateOverscrollVelocity(const float aX, const float aY) {
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ const auto& compositor = lvs->GetJavaCompositor();
+ if (AndroidBridge::IsJavaUiThread()) {
+ compositor->UpdateOverscrollVelocity(aX, aY);
+ return;
+ }
+
+ DispatchToUiThread(
+ "nsWindow::UpdateOverscrollVelocity",
+ [compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] {
+ compositor->UpdateOverscrollVelocity(aX, aY);
+ });
+ }
+}
+
+void nsWindow::UpdateOverscrollOffset(const float aX, const float aY) {
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ const auto& compositor = lvs->GetJavaCompositor();
+ if (AndroidBridge::IsJavaUiThread()) {
+ compositor->UpdateOverscrollOffset(aX, aY);
+ return;
+ }
+
+ DispatchToUiThread(
+ "nsWindow::UpdateOverscrollOffset",
+ [compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] {
+ compositor->UpdateOverscrollOffset(aX, aY);
+ });
+ }
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ // used by GLContextProviderEGL, nullptr is EGL_DEFAULT_DISPLAY
+ case NS_NATIVE_WIDGET:
+ return (void*)this;
+
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // We assume that there is only one context per process on Android
+ return NS_ONLY_ONE_NATIVE_IME_CONTEXT;
+ }
+
+ case NS_JAVA_SURFACE:
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ return lvs->GetSurface().Get();
+ }
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
+void nsWindow::DispatchHitTest(const WidgetTouchEvent& aEvent) {
+ if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() == 1) {
+ // Since touch events don't get retargeted by PositionedEventTargeting.cpp
+ // code, we dispatch a dummy mouse event that *does* get retargeted.
+ // Front-end code can use this to activate the highlight element in case
+ // this touchstart is the start of a tap.
+ WidgetMouseEvent hittest(true, eMouseHitTest, this,
+ WidgetMouseEvent::eReal);
+ hittest.mRefPoint = aEvent.mTouches[0]->mRefPoint;
+ hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+ nsEventStatus status;
+ DispatchEvent(&hittest, status);
+ }
+}
+
+void nsWindow::PassExternalResponse(java::WebResponse::Param aResponse) {
+ auto acc(mGeckoViewSupport.Access());
+ if (!acc) {
+ return;
+ }
+
+ acc->PassExternalResponse(aResponse);
+}
+
+mozilla::Modifiers nsWindow::GetModifiers(int32_t metaState) {
+ using mozilla::java::sdk::KeyEvent;
+ return (metaState & KeyEvent::META_ALT_MASK ? MODIFIER_ALT : 0) |
+ (metaState & KeyEvent::META_SHIFT_MASK ? MODIFIER_SHIFT : 0) |
+ (metaState & KeyEvent::META_CTRL_MASK ? MODIFIER_CONTROL : 0) |
+ (metaState & KeyEvent::META_META_MASK ? MODIFIER_META : 0) |
+ (metaState & KeyEvent::META_FUNCTION_ON ? MODIFIER_FN : 0) |
+ (metaState & KeyEvent::META_CAPS_LOCK_ON ? MODIFIER_CAPSLOCK : 0) |
+ (metaState & KeyEvent::META_NUM_LOCK_ON ? MODIFIER_NUMLOCK : 0) |
+ (metaState & KeyEvent::META_SCROLL_LOCK_ON ? MODIFIER_SCROLLLOCK : 0);
+}
+
+TimeStamp nsWindow::GetEventTimeStamp(int64_t aEventTime) {
+ // Android's event time is SystemClock.uptimeMillis that is counted in ms
+ // since OS was booted.
+ // (https://developer.android.com/reference/android/os/SystemClock.html)
+ // and this SystemClock.uptimeMillis uses SYSTEM_TIME_MONOTONIC.
+ // Our posix implemententaion of TimeStamp::Now uses SYSTEM_TIME_MONOTONIC
+ // too. Due to same implementation, we can use this via FromSystemTime.
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime);
+ return TimeStamp::FromSystemTime(tick);
+}
+
+void nsWindow::UserActivity() {
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
+ }
+
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+
+ if (FindTopLevel() != nsWindow::TopWindow()) {
+ BringToFront();
+ }
+}
+
+RefPtr<mozilla::a11y::SessionAccessibility>
+nsWindow::GetSessionAccessibility() {
+ auto acc(mSessionAccessibility.Access());
+ if (!acc) {
+ return nullptr;
+ }
+
+ return acc.AsRefPtr();
+}
+
+TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ auto acc(top->mEditableSupport.Access());
+ if (!acc) {
+ // Non-GeckoView windows don't support IME operations.
+ return nullptr;
+ }
+
+ nsCOMPtr<TextEventDispatcherListener> ptr;
+ if (NS_FAILED(acc->QueryInterface(NS_GET_IID(TextEventDispatcherListener),
+ getter_AddRefs(ptr)))) {
+ return nullptr;
+ }
+
+ return ptr.get();
+}
+
+void nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ auto acc(top->mEditableSupport.Access());
+ if (!acc) {
+ // Non-GeckoView windows don't support IME operations.
+ return;
+ }
+
+ // We are using an IME event later to notify Java, and the IME event
+ // will be processed by the top window. Therefore, to ensure the
+ // IME event uses the correct mInputContext, we need to let the top
+ // window process SetInputContext
+ acc->SetInputContext(aContext, aAction);
+}
+
+InputContext nsWindow::GetInputContext() {
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ auto acc(top->mEditableSupport.Access());
+ if (!acc) {
+ // Non-GeckoView windows don't support IME operations.
+ return InputContext();
+ }
+
+ // We let the top window process SetInputContext,
+ // so we should let it process GetInputContext as well.
+ return acc->GetInputContext();
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ int eventType;
+ switch (aPointerState) {
+ case TOUCH_CONTACT:
+ // This could be a ACTION_DOWN or ACTION_MOVE depending on the
+ // existing state; it is mapped to the right thing in Java.
+ eventType = java::sdk::MotionEvent::ACTION_POINTER_DOWN;
+ break;
+ case TOUCH_REMOVE:
+ // This could be turned into a ACTION_UP in Java
+ eventType = java::sdk::MotionEvent::ACTION_POINTER_UP;
+ break;
+ case TOUCH_CANCEL:
+ eventType = java::sdk::MotionEvent::ACTION_CANCEL;
+ break;
+ case TOUCH_HOVER: // not supported for now
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mNPZCSupport.IsAttached());
+ auto npzcSup(mNPZCSupport.Access());
+ MOZ_ASSERT(!!npzcSup);
+
+ const auto& npzc = npzcSup->GetJavaNPZC();
+ const auto& bounds = FindTopLevel()->mBounds;
+ aPoint.x -= bounds.x;
+ aPoint.y -= bounds.y;
+
+ DispatchToUiThread(
+ "nsWindow::SynthesizeNativeTouchPoint",
+ [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc),
+ aPointerId, eventType, aPoint, aPointerPressure, aPointerOrientation] {
+ npzc->SynthesizeNativeTouchPoint(aPointerId, eventType, aPoint.x,
+ aPoint.y, aPointerPressure,
+ aPointerOrientation);
+ });
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(
+ LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
+ MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
+ nsIObserver* aObserver) {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ MOZ_ASSERT(mNPZCSupport.IsAttached());
+ auto npzcSup(mNPZCSupport.Access());
+ MOZ_ASSERT(!!npzcSup);
+
+ const auto& npzc = npzcSup->GetJavaNPZC();
+ const auto& bounds = FindTopLevel()->mBounds;
+ aPoint.x -= bounds.x;
+ aPoint.y -= bounds.y;
+
+ int32_t nativeMessage;
+ switch (aNativeMessage) {
+ case NativeMouseMessage::ButtonDown:
+ nativeMessage = java::sdk::MotionEvent::ACTION_POINTER_DOWN;
+ break;
+ case NativeMouseMessage::ButtonUp:
+ nativeMessage = java::sdk::MotionEvent::ACTION_POINTER_UP;
+ break;
+ case NativeMouseMessage::Move:
+ nativeMessage = java::sdk::MotionEvent::ACTION_HOVER_MOVE;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Android");
+ return NS_ERROR_INVALID_ARG;
+ }
+ int32_t button = 0;
+ if (aNativeMessage != NativeMouseMessage::ButtonUp) {
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ button = java::sdk::MotionEvent::BUTTON_PRIMARY;
+ break;
+ case MouseButton::eMiddle:
+ button = java::sdk::MotionEvent::BUTTON_TERTIARY;
+ break;
+ case MouseButton::eSecondary:
+ button = java::sdk::MotionEvent::BUTTON_SECONDARY;
+ break;
+ case MouseButton::eX1:
+ button = java::sdk::MotionEvent::BUTTON_BACK;
+ break;
+ case MouseButton::eX2:
+ button = java::sdk::MotionEvent::BUTTON_FORWARD;
+ break;
+ default:
+ if (aNativeMessage == NativeMouseMessage::ButtonDown) {
+ MOZ_ASSERT_UNREACHABLE("Non supported mouse button type on Android");
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ }
+ }
+
+ // TODO (bug 1693237): Handle aModifierFlags.
+ DispatchToUiThread(
+ "nsWindow::SynthesizeNativeMouseEvent",
+ [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc),
+ nativeMessage, aPoint, button] {
+ npzc->SynthesizeNativeMouseEvent(nativeMessage, aPoint.x, aPoint.y,
+ button);
+ });
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) {
+ return SynthesizeNativeMouseEvent(
+ aPoint, NativeMouseMessage::Move, MouseButton::eNotPressed,
+ nsIWidget::Modifiers::NO_MODIFIERS, aObserver);
+}
+
+void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ if (delegate) {
+ mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
+ MOZ_ASSERT(mCompositorWidgetDelegate,
+ "nsWindow::SetCompositorWidgetDelegate called with a "
+ "non-PlatformCompositorWidgetDelegate");
+ } else {
+ mCompositorWidgetDelegate = nullptr;
+ }
+}
+
+void nsWindow::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ *aInitData = mozilla::widget::AndroidCompositorWidgetInitData(
+ mWidgetId, GetClientSize());
+}
+
+bool nsWindow::WidgetPaintsBackground() {
+ return StaticPrefs::android_widget_paints_background();
+}
+
+bool nsWindow::NeedsPaint() {
+ auto lvs(mLayerViewSupport.Access());
+ if (!lvs || lvs->CompositorPaused() || !GetWindowRenderer()) {
+ return false;
+ }
+
+ return nsIWidget::NeedsPaint();
+}
+
+void nsWindow::ConfigureAPZControllerThread() {
+ nsCOMPtr<nsISerialEventTarget> thread = mozilla::GetAndroidUiThread();
+ APZThreadUtils::SetControllerThread(thread);
+}
+
+already_AddRefed<GeckoContentController>
+nsWindow::CreateRootContentController() {
+ RefPtr<GeckoContentController> controller =
+ new AndroidContentController(this, mAPZEventState, mAPZC);
+ return controller.forget();
+}
+
+uint32_t nsWindow::GetMaxTouchPoints() const {
+ return java::GeckoAppShell::GetMaxTouchPoints();
+}
+
+void nsWindow::UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints) {
+ nsBaseWidget::UpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
+}
+
+CompositorBridgeChild* nsWindow::GetCompositorBridgeChild() const {
+ return mCompositorSession ? mCompositorSession->GetCompositorBridgeChild()
+ : nullptr;
+}
+
+void nsWindow::SetContentDocumentDisplayed(bool aDisplayed) {
+ mContentDocumentDisplayed = aDisplayed;
+}
+
+bool nsWindow::IsContentDocumentDisplayed() {
+ return mContentDocumentDisplayed;
+}
+
+void nsWindow::RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ lvs->RecvToolbarAnimatorMessage(aMessage);
+ }
+}
+
+void nsWindow::UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset,
+ const CSSToScreenScale& aZoom) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ const auto& compositor = lvs->GetJavaCompositor();
+ mContentDocumentDisplayed = true;
+ compositor->UpdateRootFrameMetrics(aScrollOffset.x, aScrollOffset.y,
+ aZoom.scale);
+ }
+}
+
+void nsWindow::RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize,
+ bool aNeedsYFlip) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ lvs->RecvScreenPixels(std::move(aMem), aSize, aNeedsYFlip);
+ }
+}
+
+void nsWindow::UpdateDynamicToolbarMaxHeight(ScreenIntCoord aHeight) {
+ if (mDynamicToolbarMaxHeight == aHeight) {
+ return;
+ }
+
+ mDynamicToolbarMaxHeight = aHeight;
+
+ if (mWidgetListener) {
+ mWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
+ }
+}
+
+void nsWindow::UpdateDynamicToolbarOffset(ScreenIntCoord aOffset) {
+ if (mWidgetListener) {
+ mWidgetListener->DynamicToolbarOffsetChanged(aOffset);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->DynamicToolbarOffsetChanged(aOffset);
+ }
+}
+
+ScreenIntMargin nsWindow::GetSafeAreaInsets() const { return mSafeAreaInsets; }
+
+void nsWindow::UpdateSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets) {
+ mSafeAreaInsets = aSafeAreaInsets;
+
+ if (mWidgetListener) {
+ mWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets);
+ }
+}
+
+jni::NativeWeakPtr<NPZCSupport> nsWindow::GetNPZCSupportWeakPtr() {
+ return mNPZCSupport;
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+static already_AddRefed<DataSourceSurface> GetCursorImage(
+ const nsIWidget::Cursor& aCursor, mozilla::CSSToLayoutDeviceScale aScale) {
+ if (!aCursor.IsCustom()) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> destDataSurface;
+
+ nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
+ // prevent DoS attacks
+ if (size.width > 128 || size.height > 128) {
+ return nullptr;
+ }
+
+ RefPtr<gfx::SourceSurface> surface = aCursor.mContainer->GetFrameAtSize(
+ size * aScale.scale, imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ if (NS_WARN_IF(!surface)) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> srcDataSurface = surface->GetDataSurface();
+ if (NS_WARN_IF(!srcDataSurface)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap sourceMap(srcDataSurface,
+ DataSourceSurface::READ);
+
+ destDataSurface = gfx::Factory::CreateDataSourceSurfaceWithStride(
+ srcDataSurface->GetSize(), SurfaceFormat::R8G8B8A8,
+ sourceMap.GetStride());
+ if (NS_WARN_IF(!destDataSurface)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap destMap(destDataSurface,
+ DataSourceSurface::READ_WRITE);
+
+ SwizzleData(sourceMap.GetData(), sourceMap.GetStride(), surface->GetFormat(),
+ destMap.GetData(), destMap.GetStride(), SurfaceFormat::R8G8B8A8,
+ destDataSurface->GetSize());
+
+ return destDataSurface.forget();
+}
+
+static int32_t GetCursorType(nsCursor aCursor) {
+ // When our minimal requirement of SDK version is 25+,
+ // we should replace with JNI auto-generator.
+ switch (aCursor) {
+ case eCursor_standard:
+ // android.view.PointerIcon.TYPE_ARROW
+ return 0x3e8;
+ case eCursor_wait:
+ // android.view.PointerIcon.TYPE_WAIT
+ return 0x3ec;
+ case eCursor_select:
+ // android.view.PointerIcon.TYPE_TEXT;
+ return 0x3f0;
+ case eCursor_hyperlink:
+ // android.view.PointerIcon.TYPE_HAND
+ return 0x3ea;
+ case eCursor_n_resize:
+ case eCursor_s_resize:
+ case eCursor_ns_resize:
+ case eCursor_row_resize:
+ // android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW
+ return 0x3f7;
+ case eCursor_w_resize:
+ case eCursor_e_resize:
+ case eCursor_ew_resize:
+ case eCursor_col_resize:
+ // android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW
+ return 0x3f6;
+ case eCursor_nw_resize:
+ case eCursor_se_resize:
+ case eCursor_nwse_resize:
+ // android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
+ return 0x3f9;
+ case eCursor_ne_resize:
+ case eCursor_sw_resize:
+ case eCursor_nesw_resize:
+ // android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
+ return 0x3f8;
+ case eCursor_crosshair:
+ // android.view.PointerIcon.TYPE_CROSSHAIR
+ return 0x3ef;
+ case eCursor_move:
+ // android.view.PointerIcon.TYPE_ARROW
+ return 0x3e8;
+ case eCursor_help:
+ // android.view.PointerIcon.TYPE_HELP
+ return 0x3eb;
+ case eCursor_copy:
+ // android.view.PointerIcon.TYPE_COPY
+ return 0x3f3;
+ case eCursor_alias:
+ // android.view.PointerIcon.TYPE_ALIAS
+ return 0x3f2;
+ case eCursor_context_menu:
+ // android.view.PointerIcon.TYPE_CONTEXT_MENU
+ return 0x3e9;
+ case eCursor_cell:
+ // android.view.PointerIcon.TYPE_CELL
+ return 0x3ee;
+ case eCursor_grab:
+ // android.view.PointerIcon.TYPE_GRAB
+ return 0x3fc;
+ case eCursor_grabbing:
+ // android.view.PointerIcon.TYPE_GRABBING
+ return 0x3fd;
+ case eCursor_spinning:
+ // android.view.PointerIcon.TYPE_WAIT
+ return 0x3ec;
+ case eCursor_zoom_in:
+ // android.view.PointerIcon.TYPE_ZOOM_IN
+ return 0x3fa;
+ case eCursor_zoom_out:
+ // android.view.PointerIcon.TYPE_ZOOM_OUT
+ return 0x3fb;
+ case eCursor_not_allowed:
+ // android.view.PointerIcon.TYPE_NO_DROP:
+ return 0x3f4;
+ case eCursor_no_drop:
+ // android.view.PointerIcon.TYPE_NO_DROP:
+ return 0x3f4;
+ case eCursor_vertical_text:
+ // android.view.PointerIcon.TYPE_VERTICAL_TEXT
+ return 0x3f1;
+ case eCursor_all_scroll:
+ // android.view.PointerIcon.TYPE_ALL_SCROLL
+ return 0x3f5;
+ case eCursor_none:
+ // android.view.PointerIcon.TYPE_NULL
+ return 0;
+ default:
+ NS_WARNING_ASSERTION(aCursor, "Invalid cursor type");
+ // android.view.PointerIcon.TYPE_ARROW
+ return 0x3e8;
+ }
+}
+
+void nsWindow::SetCursor(const Cursor& aCursor) {
+ if (mozilla::jni::GetAPIVersion() < 24) {
+ return;
+ }
+
+ // Only change cursor if it's actually been changed
+ if (!mUpdateCursor && mCursor == aCursor) {
+ return;
+ }
+
+ mUpdateCursor = false;
+ mCursor = aCursor;
+
+ int32_t type = 0;
+ RefPtr<DataSourceSurface> destDataSurface =
+ GetCursorImage(aCursor, GetDefaultScale());
+ if (!destDataSurface) {
+ type = GetCursorType(aCursor.mDefaultCursor);
+ }
+
+ if (mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ const auto& compositor = lvs->GetJavaCompositor();
+
+ DispatchToUiThread(
+ "nsWindow::SetCursor",
+ [compositor = GeckoSession::Compositor::GlobalRef(compositor), type,
+ destDataSurface = std::move(destDataSurface),
+ hotspotX = aCursor.mHotspotX, hotspotY = aCursor.mHotspotY] {
+ java::sdk::Bitmap::LocalRef bitmap;
+ if (destDataSurface) {
+ DataSourceSurface::ScopedMap destMap(destDataSurface,
+ DataSourceSurface::READ);
+ auto pixels = mozilla::jni::ByteBuffer::New(
+ reinterpret_cast<int8_t*>(destMap.GetData()),
+ destMap.GetStride() * destDataSurface->GetSize().height);
+ bitmap = java::sdk::Bitmap::CreateBitmap(
+ destDataSurface->GetSize().width,
+ destDataSurface->GetSize().height,
+ java::sdk::Bitmap::Config::ARGB_8888());
+ bitmap->CopyPixelsFromBuffer(pixels);
+ }
+ compositor->SetPointerIcon(type, bitmap, hotspotX, hotspotY);
+ });
+ }
+}