diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index 387d9ef754c..2e564528050 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -154,6 +154,7 @@ static bool sFailedToCreateGLContext = false; static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4; static const double SWIPE_MIN_DISTANCE_INCHES = 0.6; +static Modifiers GetModifiers(int32_t metaState); class nsWindow::GeckoViewSupport final : public GeckoView::Window::Natives @@ -245,17 +246,12 @@ public: , mIMESelectionChanged(false) , mIMETextChangedDuringFlush(false) { - Reattach(aInstance); + Base::AttachNative(aInstance, this); EditableBase::AttachNative(mEditable, this); } ~GeckoViewSupport(); - void Reattach(const GeckoView::Window::LocalRef& aInstance) - { - Base::AttachNative(aInstance, this); - } - using Base::DisposeNative; using EditableBase::DisposeNative; @@ -367,6 +363,282 @@ public: void OnImeUpdateComposition(int32_t aStart, int32_t aEnd); }; +/** + * NativePanZoomController handles its native calls on the UI thread, so make + * it separate from GeckoViewSupport. + */ +class nsWindow::NPZCSupport final + : public NativePanZoomController::Natives +{ + nsWindow* mWindow; + // Lock for keeping mWindow alive when accessed off of the Gecko thread. + Mutex mWindowLock; + NativePanZoomController::GlobalRef mNPZC; + +public: + typedef NativePanZoomController::Natives Base; + + NPZCSupport(nsWindow* aWindow, + const NativePanZoomController::LocalRef& aNPZC) + : mWindow(aWindow) + , mWindowLock("NPZCSupport") + , mNPZC(aNPZC) + { + if (mWindow->mNPZCSupport) { + mWindow->mNPZCSupport->DetachFromWindow(); + } + mWindow->mNPZCSupport = this; + } + + ~NPZCSupport() + {} + + using Base::AttachNative; + + void DetachFromWindow() + { + // 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 DetachFromWindow, 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. DetachFromWindow is not + // called. + // + // 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. + + typedef NativePanZoomController::GlobalRef NPZCRef; + auto callDestroy = [] (const NPZCRef& npzc) { + npzc->Destroy(); + }; + + NativePanZoomController::GlobalRef npzc = mNPZC; + AndroidBridge::Bridge()->PostTaskToUiThread(NewRunnableFunction( + static_cast(callDestroy), + mozilla::Move(npzc)), 0); + + // Signal to any pending calls on either Gecko or UI thread that NPZC + // is being destroyed. + MutexAutoLock lock(mWindowLock); + mWindow->mNPZCSupport = nullptr; + mWindow = nullptr; + } + +private: + void DisposeOnGeckoThread() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mWindow && mWindow->mNPZCSupport == this) { + mWindow->mNPZCSupport = nullptr; + } + // Base::DisposeNative implicitly deletes 'this'. + Base::DisposeNative(NativePanZoomController::LocalRef( + jni::GetGeckoThreadEnv(), mNPZC)); + } + +public: + void DisposeNative() + { + // Capturing 'this' is okay because 'this' is owned by the Java + // instance, it is alive until this lambda is run, and we make sure to + // only call DisposeNative once. + nsAppShell::PostEvent([this] { + DisposeOnGeckoThread(); + }); + } + + void AbortAnimation() + { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + MutexAutoLock lock(mWindowLock); + if (!mWindow) { + // We already shut down. + return; + } + + RefPtr controller = mWindow->mAPZC; + RefPtr compositor = mWindow->mCompositorParent; + if (controller && compositor) { + // TODO: Pass in correct values for presShellId and viewId. + controller->CancelAnimation(ScrollableLayerGuid( + compositor->RootLayerTreeId(), 0, 0)); + } + } + + void SetIsLongpressEnabled(bool aIsLongpressEnabled) + { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + APZCTreeManager::SetLongTapEnabled(aIsLongpressEnabled); + } + + bool HandleMotionEvent(const NativePanZoomController::LocalRef& aInstance, + int32_t aAction, int32_t aActionIndex, + int64_t aTime, int32_t aMetaState, + jni::IntArray::Param aPointerId, + jni::FloatArray::Param aX, + jni::FloatArray::Param aY, + jni::FloatArray::Param aOrientation, + jni::FloatArray::Param aPressure, + jni::FloatArray::Param aToolMajor, + jni::FloatArray::Param aToolMinor) + { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + MutexAutoLock lock(mWindowLock); + if (!mWindow) { + // We already shut down. + return false; + } + + RefPtr controller = mWindow->mAPZC; + if (!controller) { + return false; + } + + nsTArray pointerId(aPointerId); + MultiTouchInput::MultiTouchType type; + size_t startIndex = 0; + size_t endIndex = pointerId.Length(); + + switch (aAction) { + case AndroidMotionEvent::ACTION_DOWN: + case AndroidMotionEvent::ACTION_POINTER_DOWN: + type = MultiTouchInput::MULTITOUCH_START; + break; + case AndroidMotionEvent::ACTION_MOVE: + type = MultiTouchInput::MULTITOUCH_MOVE; + break; + case AndroidMotionEvent::ACTION_UP: + case AndroidMotionEvent::ACTION_POINTER_UP: + // for pointer-up events we only want the data from + // the one pointer that went up + type = MultiTouchInput::MULTITOUCH_END; + startIndex = aActionIndex; + endIndex = aActionIndex + 1; + break; + case AndroidMotionEvent::ACTION_OUTSIDE: + case AndroidMotionEvent::ACTION_CANCEL: + type = MultiTouchInput::MULTITOUCH_CANCEL; + break; + default: + return false; + } + + MultiTouchInput input(type, aTime, TimeStamp(), 0); + input.modifiers = GetModifiers(aMetaState); + input.mTouches.SetCapacity(endIndex - startIndex); + + nsTArray x(aX); + nsTArray y(aY); + nsTArray orientation(aOrientation); + nsTArray pressure(aPressure); + nsTArray toolMajor(aToolMajor); + nsTArray toolMinor(aToolMinor); + + MOZ_ASSERT(pointerId.Length() == x.Length()); + MOZ_ASSERT(pointerId.Length() == y.Length()); + MOZ_ASSERT(pointerId.Length() == orientation.Length()); + MOZ_ASSERT(pointerId.Length() == pressure.Length()); + MOZ_ASSERT(pointerId.Length() == toolMajor.Length()); + MOZ_ASSERT(pointerId.Length() == toolMinor.Length()); + + const nsIntPoint& offset = + mWindow->WidgetToScreenOffset().ToUnknownPoint(); + + for (size_t i = startIndex; i < endIndex; i++) { + + float orien = orientation[i] * 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 (orien >= 90.0) { + orien -= 180.0f; + } + + nsIntPoint point = nsIntPoint(int32_t(floorf(x[i])), + int32_t(floorf(y[i]))) - offset; + + // 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. + gfx::Size radius; + if (orien < 0.0f) { + orien += 90.0f; + radius = gfx::Size(int32_t(toolMajor[i] / 2.0f), + int32_t(toolMinor[i] / 2.0f)); + } else { + radius = gfx::Size(int32_t(toolMinor[i] / 2.0f), + int32_t(toolMajor[i] / 2.0f)); + } + + input.mTouches.AppendElement(SingleTouchData( + pointerId[i], ScreenIntPoint::FromUnknownPoint(point), + ScreenSize::FromUnknownSize(radius), orien, pressure[i])); + } + + ScrollableLayerGuid guid; + uint64_t blockId; + nsEventStatus status = + controller->ReceiveInputEvent(input, &guid, &blockId); + + if (status == nsEventStatus_eConsumeNoDefault) { + return true; + } + + // Dispatch APZ input event on Gecko thread. + NativePanZoomController::GlobalRef npzc = mNPZC; + nsAppShell::PostEvent([npzc, input, guid, blockId, status] { + MOZ_ASSERT(NS_IsMainThread()); + + JNIEnv* const env = jni::GetGeckoThreadEnv(); + NPZCSupport* npzcSupport = GetNative( + NativePanZoomController::LocalRef(env, npzc)); + + if (!npzcSupport || !npzcSupport->mWindow) { + // We already shut down. + env->ExceptionClear(); + return; + } + + nsWindow* const window = npzcSupport->mWindow; + window->UserActivity(); + WidgetTouchEvent touchEvent = input.ToWidgetTouchEvent(window); + window->ProcessUntransformedAPZEvent(&touchEvent, guid, + blockId, status); + window->DispatchHitTest(touchEvent); + }); + return true; + } +}; + /** * GLController has some unique requirements for its native calls, so make it * separate from GeckoViewSupport. @@ -465,7 +737,7 @@ public: , mGLController(aInstance) , mCompositorPaused(true) { - Reattach(aInstance); + Base::AttachNative(aInstance, this); } ~GLControllerSupport() @@ -473,11 +745,6 @@ public: mGLController->Destroy(); } - void Reattach(const GLController::LocalRef& aInstance) - { - Base::AttachNative(aInstance, this); - } - const GeckoLayerClient::Ref& GetLayerClient() const { return mLayerClient; @@ -711,6 +978,7 @@ nsWindow::InitNatives() nsWindow::GeckoViewSupport::Base::Init(); nsWindow::GeckoViewSupport::EditableBase::Init(); nsWindow::GLControllerSupport::Init(); + nsWindow::NPZCSupport::Init(); } nsWindow* @@ -752,6 +1020,7 @@ nsWindow::DumpWindows(const nsTArray& wins, int indent) } nsWindow::nsWindow() : + mNPZCSupport(nullptr), mIsVisible(false), mParent(nullptr), mAwaitingFullScreen(false), diff --git a/widget/android/nsWindow.h b/widget/android/nsWindow.h index 782baccd386..27b083f60de 100644 --- a/widget/android/nsWindow.h +++ b/widget/android/nsWindow.h @@ -57,6 +57,11 @@ private: // Object that implements native GLController calls. mozilla::UniquePtr mGLControllerSupport; + class NPZCSupport; + // Object that implements native NativePanZoomController calls. + // Owned by the Java NativePanZoomController instance. + NPZCSupport* mNPZCSupport; + public: static void OnGlobalAndroidEvent(mozilla::AndroidGeckoEvent *ae); static mozilla::gfx::IntSize GetAndroidScreenBounds();