diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /widget/android/nsWindow.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream/115.8.0esr.tar.xz firefox-esr-upstream/115.8.0esr.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | widget/android/nsWindow.cpp | 3252 |
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); + }); + } +} |