/* -*- 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 #include #include #include #include #include #include #include #include #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 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 sWidgetId{0}; namespace { template std::enable_if_t::value == jni::detail::NativePtrType::REFPTR, void> CallAttachNative(Instance aInstance, Impl* aImpl) { Impl::AttachNative(aInstance, RefPtr(aImpl).get()); } template std::enable_if_t::value == jni::detail::NativePtrType::OWNING, void> CallAttachNative(Instance aInstance, Impl* aImpl) { Impl::AttachNative(aInstance, UniquePtr(aImpl)); } template bool DispatchToUiThread(const char* aName, Lambda&& aLambda) { if (RefPtr uiThread = GetAndroidUiThread()) { uiThread->Dispatch(NS_NewRunnableFunction(aName, std::move(aLambda))); return true; } return false; } } // namespace namespace mozilla { namespace widget { using WindowPtr = jni::NativeWeakPtr; /** * PanZoomController handles its native calls on the UI thread, so make * it separate from GeckoViewSupport. */ class NPZCSupport final : public java::PanZoomController::NativeProvider::Natives { WindowPtr mWindow; java::PanZoomController::NativeProvider::WeakRef mNPZC; // Stores the returnResult of each pending motion event between // HandleMotionEvent and FinishHandlingMotionEvent. std::queue> mPendingMotionEventReturnResults; RefPtr 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 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&& 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&& 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 mNPZCSupport; }; Observer* mObserver = nullptr; template void PostInputEvent(Lambda&& aLambda) { // Use priority queue for input events. nsAppShell::PostEvent( MakeUnique>(this, std::move(aLambda))); } public: typedef java::PanZoomController::NativeProvider::Natives 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 aDisposer) { RefPtr 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 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 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 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 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 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 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 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 historicalX(eventData->HistoricalX()->GetElements()); nsTArray historicalY(eventData->HistoricalY()->GetElements()); nsTArray historicalOrientation( eventData->HistoricalOrientation()->GetElements()); nsTArray historicalPressure( eventData->HistoricalPressure()->GetElements()); nsTArray historicalToolMajor( eventData->HistoricalToolMajor()->GetElements()); nsTArray 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 x(eventData->X()->GetElements()); nsTArray y(eventData->Y()->GetElements()); nsTArray orientation(eventData->Orientation()->GetElements()); nsTArray pressure(eventData->Pressure()->GetElements()); nsTArray toolMajor(eventData->ToolMajor()->GetElements()); nsTArray 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 gkWindow = win->GetNsWindow(); if (!gkWindow) { return; } MutexAutoLock lock(gkWindow->GetDestroyMutex()); if (gkWindow->Destroyed()) { return; } jni::NativeWeakPtr 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 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 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 { WindowPtr mWindow; GeckoSession::Compositor::WeakRef mCompositor; Atomic 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 mUiCompositorControllerChild; // Whether we have requested a new Surface from the GeckoSession. bool mRequestedNewSurface = false; Maybe 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 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 MakeEvent(UniquePtr&& event) { return MakeUnique(std::move(event)); } explicit LayerViewEvent(UniquePtr&& event) : nsAppShell::ProxyEvent(std::move(event)) {} void PostTo(LinkedList& 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 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 aDisposer) { RefPtr disposer = aDisposer; if (RefPtr 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()); } 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 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()); } mCapturePixelsResults.pop(); } } } java::sdk::Surface::Param GetSurface() { return mSurface; } private: already_AddRefed FlipScreenPixels( Shmem& aMem, const ScreenIntSize& aInSize, const ScreenRect& aInRegion, const IntSize& aOutSize) { RefPtr image = gfx::Factory::CreateWrappingDataSourceSurface( aMem.get(), StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aInSize.width), IntSize(aInSize.width, aInSize.height), SurfaceFormat::B8G8R8A8); RefPtr 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 snapshot = drawTarget->Snapshot(); RefPtr 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::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()); } 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(MakeUnique(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 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 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 uiThread = GetAndroidUiThread()) { uiThread->Dispatch( NewRunnableMethod( "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()); } } 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 surf; if (aNeedsYFlip) { surf = FlipScreenPixels(aMem, aSize, request.mSource, request.mOutputSize); } else { surf = gfx::Factory::CreateWrappingDataSourceSurface( aMem.get(), 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(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()); } } else { result->CompleteExceptionally(java::sdk::IllegalArgumentException::New( "No target bitmap argument provided") .Cast()); } } // 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 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 = 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 domWindow; ww->OpenWindow(nullptr, url, nsDependentCString(aId->ToCString().get()), chromeFlags, androidView, getter_AddRefs(domWindow)); MOZ_RELEASE_ASSERT(domWindow); nsCOMPtr pdomWindow = nsPIDOMWindowOuter::From(domWindow); const RefPtr 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::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 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::Attach( compositor, mWindow->mGeckoViewSupport, compositor); if (RefPtr 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 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::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::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 GeckoViewSupport::GetContentCanonicalBrowsingContext() { nsCOMPtr treeOwner = mDOMWindow->GetTreeOwner(); if (!treeOwner) { return nullptr; } RefPtr 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 aGeckoResult, RefPtr aCbc) { MOZ_ASSERT(NS_IsMainThread()); const auto pdfErrorMsg = "Could not save this page as PDF."; auto stream = java::GeckoInputStream::New(nullptr); RefPtr streamListener = new GeckoViewOutputStream(stream); nsCOMPtr printSettingsService = do_GetService("@mozilla.org/gfx/printsettings-service;1"); if (!printSettingsService) { aGeckoResult->CompleteExceptionally( GeckoPrintException::New( GeckoPrintException::ERROR_PRINT_SETTINGS_SERVICE_NOT_AVAILABLE) .Cast()); GVS_LOG("Could not create print settings service."); return; } nsCOMPtr 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()); 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 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 cbc = GetContentCanonicalBrowsingContext(); if (!cbc) { geckoResult->CompleteExceptionally( GeckoPrintException::New( GeckoPrintException:: ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT) .Cast()); 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 cbc = CanonicalBrowsingContext::Get(aBcId); if (!cbc) { geckoResult->CompleteExceptionally( GeckoPrintException::New( GeckoPrintException:: ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT) .Cast()); 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::From(nsPIDOMWindowOuter* aDOMWindow) { nsCOMPtr widget = WidgetUtils::DOMWindowToWidget(aDOMWindow); return From(widget); } /* static */ already_AddRefed 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 window = static_cast(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& 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 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 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> nsWindow::OnLoadRequest( nsIURI* aUri, int32_t aWindowType, int32_t aFlags, nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture, bool aIsTopLevel) { auto geckoViewSupport(mGeckoViewSupport.Access()); if (!geckoViewSupport) { return MozPromise::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::CreateAndResolve(false, __func__); } } bool isNullPrincipal = false; if (aTriggeringPrincipal) { aTriggeringPrincipal->GetIsNullPrincipal(&isNullPrincipal); if (!isNullPrincipal) { nsCOMPtr 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::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 screen = GetWidgetScreen(); if (screen) { screen->GetDpi(&dpi); } return dpi; } double nsWindow::GetDefaultScaleInternal() { double scale = 1.0f; nsCOMPtr 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 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::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::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::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 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 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 thread = mozilla::GetAndroidUiThread(); APZThreadUtils::SetControllerThread(thread); } already_AddRefed nsWindow::CreateRootContentController() { RefPtr 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& 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::Accessor lvs{ mLayerViewSupport.Access()}) { lvs->RecvToolbarAnimatorMessage(aMessage); } } void nsWindow::UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset, const CSSToScreenScale& aZoom) { MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); if (::mozilla::jni::NativeWeakPtr::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::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 nsWindow::GetNPZCSupportWeakPtr() { return mNPZCSupport; } already_AddRefed nsIWidget::CreateTopLevelWindow() { nsCOMPtr window = new nsWindow(); return window.forget(); } already_AddRefed nsIWidget::CreateChildWindow() { nsCOMPtr window = new nsWindow(); return window.forget(); } static already_AddRefed GetCursorImage( const nsIWidget::Cursor& aCursor, mozilla::CSSToLayoutDeviceScale aScale) { if (!aCursor.IsCustom()) { return nullptr; } RefPtr destDataSurface; nsIntSize size = nsIWidget::CustomCursorSize(aCursor); // prevent DoS attacks if (size.width > 128 || size.height > 128) { return nullptr; } RefPtr 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 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 destDataSurface = GetCursorImage(aCursor, GetDefaultScale()); if (!destDataSurface) { type = GetCursorType(aCursor.mDefaultCursor); } if (mozilla::jni::NativeWeakPtr::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(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); }); } }