summaryrefslogtreecommitdiff
path: root/widget/android/nsWindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/android/nsWindow.cpp')
-rw-r--r--widget/android/nsWindow.cpp3638
1 files changed, 3638 insertions, 0 deletions
diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp
new file mode 100644
index 0000000000..9423a4a269
--- /dev/null
+++ b/widget/android/nsWindow.cpp
@@ -0,0 +1,3638 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * vim: set sw=4 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 <android/log.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <math.h>
+#include <unistd.h>
+
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/WeakPtr.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/layers/RenderTrace.h"
+#include <algorithm>
+
+using mozilla::dom::ContentParent;
+using mozilla::dom::ContentChild;
+using mozilla::Unused;
+
+#include "nsWindow.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIObserverService.h"
+#include "nsISelection.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWidgetListener.h"
+#include "nsIWindowWatcher.h"
+#include "nsIXULWindow.h"
+
+#include "nsAppShell.h"
+#include "nsFocusManager.h"
+#include "nsIdleService.h"
+#include "nsLayoutUtils.h"
+#include "nsViewManager.h"
+
+#include "WidgetUtils.h"
+
+#include "nsIDOMSimpleGestureEvent.h"
+
+#include "nsGkAtoms.h"
+#include "nsWidgetsCID.h"
+#include "nsGfxCIID.h"
+
+#include "gfxContext.h"
+
+#include "Layers.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/AsyncCompositionManager.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "ScopedGLHelpers.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "AndroidContentController.h"
+
+#include "nsTArray.h"
+
+#include "AndroidBridge.h"
+#include "AndroidBridgeUtilities.h"
+#include "android_npapi.h"
+#include "FennecJNINatives.h"
+#include "GeneratedJNINatives.h"
+#include "KeyEvent.h"
+#include "MotionEvent.h"
+
+#include "imgIEncoder.h"
+
+#include "nsString.h"
+#include "GeckoProfiler.h" // For PROFILER_LABEL
+#include "nsIXULRuntime.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::java;
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorSession.h"
+#include "mozilla/layers/LayerTransactionParent.h"
+#include "mozilla/Services.h"
+#include "nsThreadUtils.h"
+
+// All the toplevel windows that have been created; these are in
+// stacking order, so the window at gTopLevelWindows[0] is the topmost
+// one.
+static nsTArray<nsWindow*> gTopLevelWindows;
+
+static bool sFailedToCreateGLContext = false;
+
+// Multitouch swipe thresholds in inches
+static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
+static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
+
+// Sync with GeckoEditableView class
+static const int IME_MONITOR_CURSOR_ONE_SHOT = 1;
+static const int IME_MONITOR_CURSOR_START_MONITOR = 2;
+static const int IME_MONITOR_CURSOR_END_MONITOR = 3;
+
+static Modifiers GetModifiers(int32_t metaState);
+
+template<typename Lambda, bool IsStatic, typename InstanceType, class Impl>
+class nsWindow::WindowEvent : public nsAppShell::LambdaEvent<Lambda>
+{
+ typedef nsAppShell::Event Event;
+ typedef nsAppShell::LambdaEvent<Lambda> Base;
+
+ bool IsStaleCall()
+ {
+ if (IsStatic) {
+ // Static calls are never stale.
+ return false;
+ }
+
+ JNIEnv* const env = mozilla::jni::GetEnvForThread();
+
+ const auto natives = reinterpret_cast<mozilla::WeakPtr<Impl>*>(
+ jni::GetNativeHandle(env, mInstance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+
+ // The call is stale if the nsWindow has been destroyed on the
+ // Gecko side, but the Java object is still attached to it through
+ // a weak pointer. Stale calls should be discarded. Note that it's
+ // an error if natives is nullptr here; we return false but the
+ // native call will throw an error.
+ return natives && !natives->get();
+ }
+
+ const InstanceType mInstance;
+ const Event::Type mEventType;
+
+public:
+ WindowEvent(Lambda&& aLambda,
+ InstanceType&& aInstance,
+ Event::Type aEventType = Event::Type::kGeneralActivity)
+ : Base(mozilla::Move(aLambda))
+ , mInstance(mozilla::Move(aInstance))
+ , mEventType(aEventType)
+ {}
+
+ WindowEvent(Lambda&& aLambda,
+ Event::Type aEventType = Event::Type::kGeneralActivity)
+ : Base(mozilla::Move(aLambda))
+ , mInstance(Base::lambda.GetThisArg())
+ , mEventType(aEventType)
+ {}
+
+ void Run() override
+ {
+ if (!IsStaleCall()) {
+ return Base::Run();
+ }
+ }
+
+ Event::Type ActivityType() const override
+ {
+ return mEventType;
+ }
+};
+
+template<class Impl>
+template<class Instance, typename... Args> void
+nsWindow::NativePtr<Impl>::Attach(Instance aInstance, nsWindow* aWindow,
+ Args&&... aArgs)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mPtr && !mImpl);
+
+ auto impl = mozilla::MakeUnique<Impl>(
+ this, aWindow, mozilla::Forward<Args>(aArgs)...);
+ mImpl = impl.get();
+
+ Impl::AttachNative(aInstance, mozilla::Move(impl));
+}
+
+template<class Impl> void
+nsWindow::NativePtr<Impl>::Detach()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPtr && mImpl);
+
+ mImpl->OnDetach();
+ {
+ Locked implLock(*this);
+ mImpl = nullptr;
+ }
+
+ typename WindowPtr<Impl>::Locked lock(*mPtr);
+ mPtr->mWindow = nullptr;
+ mPtr->mPtr = nullptr;
+ mPtr = nullptr;
+}
+
+template<class Impl>
+class nsWindow::NativePtr<Impl>::Locked final : private MutexAutoLock
+{
+ Impl* const mImpl;
+
+public:
+ Locked(NativePtr<Impl>& aPtr)
+ : MutexAutoLock(aPtr.mImplLock)
+ , mImpl(aPtr.mImpl)
+ {}
+
+ operator Impl*() const { return mImpl; }
+ Impl* operator->() const { return mImpl; }
+};
+
+template<class Impl>
+class nsWindow::WindowPtr final
+{
+ friend NativePtr<Impl>;
+
+ NativePtr<Impl>* mPtr;
+ nsWindow* mWindow;
+ Mutex mWindowLock;
+
+public:
+ class Locked final : private MutexAutoLock
+ {
+ nsWindow* const mWindow;
+
+ public:
+ Locked(WindowPtr<Impl>& aPtr)
+ : MutexAutoLock(aPtr.mWindowLock)
+ , mWindow(aPtr.mWindow)
+ {}
+
+ operator nsWindow*() const { return mWindow; }
+ nsWindow* operator->() const { return mWindow; }
+ };
+
+ WindowPtr(NativePtr<Impl>* aPtr, nsWindow* aWindow)
+ : mPtr(aPtr)
+ , mWindow(aWindow)
+ , mWindowLock(NativePtr<Impl>::sName)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mPtr->mPtr = this;
+ }
+
+ ~WindowPtr()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mPtr) {
+ return;
+ }
+ mPtr->mPtr = nullptr;
+ mPtr->mImpl = nullptr;
+ }
+
+ operator nsWindow*() const
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mWindow;
+ }
+
+ nsWindow* operator->() const { return operator nsWindow*(); }
+};
+
+
+class nsWindow::GeckoViewSupport final
+ : public GeckoView::Window::Natives<GeckoViewSupport>
+ , public GeckoEditable::Natives<GeckoViewSupport>
+ , public SupportsWeakPtr<GeckoViewSupport>
+{
+ nsWindow& window;
+
+public:
+ typedef GeckoView::Window::Natives<GeckoViewSupport> Base;
+ typedef GeckoEditable::Natives<GeckoViewSupport> EditableBase;
+
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(GeckoViewSupport);
+
+ template<typename Functor>
+ static void OnNativeCall(Functor&& aCall)
+ {
+ if (aCall.IsTarget(&Open) && NS_IsMainThread()) {
+ // Gecko state probably just switched to PROFILE_READY, and the
+ // event loop is not running yet. Skip the event loop here so we
+ // can get a head start on opening our window.
+ return aCall();
+ }
+
+ const nsAppShell::Event::Type eventType =
+ aCall.IsTarget(&GeckoViewSupport::OnKeyEvent) ||
+ aCall.IsTarget(&GeckoViewSupport::OnImeReplaceText) ||
+ aCall.IsTarget(&GeckoViewSupport::OnImeUpdateComposition) ?
+ nsAppShell::Event::Type::kUIActivity :
+ nsAppShell::Event::Type::kGeneralActivity;
+
+ nsAppShell::PostEvent(mozilla::MakeUnique<WindowEvent<Functor>>(
+ mozilla::Move(aCall), eventType));
+ }
+
+ GeckoViewSupport(nsWindow* aWindow,
+ const GeckoView::Window::LocalRef& aInstance,
+ GeckoView::Param aView)
+ : window(*aWindow)
+ , mEditable(GeckoEditable::New(aView))
+ , mIMERanges(new TextRangeArray())
+ , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
+ , mIMEUpdatingContext(false)
+ , mIMESelectionChanged(false)
+ , mIMETextChangedDuringFlush(false)
+ , mIMEMonitorCursor(false)
+ {
+ Base::AttachNative(aInstance, this);
+ EditableBase::AttachNative(mEditable, this);
+ }
+
+ ~GeckoViewSupport();
+
+ using Base::DisposeNative;
+ using EditableBase::DisposeNative;
+
+ /**
+ * GeckoView methods
+ */
+private:
+ nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow;
+
+public:
+ // Create and attach a window.
+ static void Open(const jni::Class::LocalRef& aCls,
+ GeckoView::Window::Param aWindow,
+ GeckoView::Param aView, jni::Object::Param aCompositor,
+ jni::String::Param aChromeURI,
+ int32_t screenId);
+
+ // Close and destroy the nsWindow.
+ void Close();
+
+ // Reattach this nsWindow to a new GeckoView.
+ void Reattach(const GeckoView::Window::LocalRef& inst,
+ GeckoView::Param aView, jni::Object::Param aCompositor);
+
+ void LoadUri(jni::String::Param aUri, int32_t aFlags);
+
+ /**
+ * GeckoEditable methods
+ */
+private:
+ /*
+ Rules for managing IME between Gecko and Java:
+
+ * Gecko controls the text content, and Java shadows the Gecko text
+ through text updates
+ * Gecko and Java maintain separate selections, and synchronize when
+ needed through selection updates and set-selection events
+ * Java controls the composition, and Gecko shadows the Java
+ composition through update composition events
+ */
+
+ struct IMETextChange final {
+ int32_t mStart, mOldEnd, mNewEnd;
+
+ IMETextChange() :
+ mStart(-1), mOldEnd(-1), mNewEnd(-1) {}
+
+ IMETextChange(const IMENotification& aIMENotification)
+ : mStart(aIMENotification.mTextChangeData.mStartOffset)
+ , mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset)
+ , mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset)
+ {
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
+ "IMETextChange initialized with wrong notification");
+ MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(),
+ "The text change notification isn't initialized");
+ MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(),
+ "The text change notification is out of range");
+ }
+
+ bool IsEmpty() const { return mStart < 0; }
+ };
+
+ // GeckoEditable instance used by this nsWindow;
+ java::GeckoEditable::GlobalRef mEditable;
+ AutoTArray<mozilla::UniquePtr<mozilla::WidgetEvent>, 8> mIMEKeyEvents;
+ AutoTArray<IMETextChange, 4> mIMETextChanges;
+ InputContext mInputContext;
+ RefPtr<mozilla::TextRangeArray> mIMERanges;
+ int32_t mIMEMaskEventsCount; // Mask events when > 0.
+ bool mIMEUpdatingContext;
+ bool mIMESelectionChanged;
+ bool mIMETextChangedDuringFlush;
+ bool mIMEMonitorCursor;
+
+ void SendIMEDummyKeyEvents();
+ void AddIMETextChange(const IMETextChange& aChange);
+
+ enum FlushChangesFlag {
+ // Not retrying.
+ FLUSH_FLAG_NONE,
+ // Retrying due to IME text changes during flush.
+ FLUSH_FLAG_RETRY,
+ // Retrying due to IME sync exceptions during flush.
+ FLUSH_FLAG_RECOVER
+ };
+ void PostFlushIMEChanges();
+ void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void AsyncNotifyIME(int32_t aNotification);
+ void UpdateCompositionRects();
+
+public:
+ bool NotifyIME(const IMENotification& aIMENotification);
+ void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+ InputContext GetInputContext();
+
+ // RAII helper class that automatically sends an event reply through
+ // OnImeSynchronize, as required by events like OnImeReplaceText.
+ class AutoIMESynchronize {
+ GeckoViewSupport* const mGVS;
+ public:
+ AutoIMESynchronize(GeckoViewSupport* gvs) : mGVS(gvs) {}
+ ~AutoIMESynchronize() { mGVS->OnImeSynchronize(); }
+ };
+
+ // Handle an Android KeyEvent.
+ void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
+ int32_t aMetaState, int64_t aTime, int32_t aUnicodeChar,
+ int32_t aBaseUnicodeChar, int32_t aDomPrintableKeyValue,
+ int32_t aRepeatCount, int32_t aFlags,
+ bool aIsSynthesizedImeKey, jni::Object::Param originalEvent);
+
+ // Synchronize Gecko thread with the InputConnection thread.
+ void OnImeSynchronize();
+
+ // Replace a range of text with new text.
+ void OnImeReplaceText(int32_t aStart, int32_t aEnd,
+ jni::String::Param aText);
+
+ // Add styling for a range within the active composition.
+ void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd,
+ int32_t aRangeType, int32_t aRangeStyle, int32_t aRangeLineStyle,
+ bool aRangeBoldLine, int32_t aRangeForeColor,
+ int32_t aRangeBackColor, int32_t aRangeLineColor);
+
+ // Update styling for the active composition using previous-added ranges.
+ void OnImeUpdateComposition(int32_t aStart, int32_t aEnd);
+
+ // Set cursor mode whether IME requests
+ void OnImeRequestCursorUpdates(int aRequestMode);
+};
+
+/**
+ * NativePanZoomController handles its native calls on the UI thread, so make
+ * it separate from GeckoViewSupport.
+ */
+class nsWindow::NPZCSupport final
+ : public NativePanZoomController::Natives<NPZCSupport>
+{
+ using LockedWindowPtr = WindowPtr<NPZCSupport>::Locked;
+
+ WindowPtr<NPZCSupport> mWindow;
+ NativePanZoomController::GlobalRef mNPZC;
+ int mPreviousButtons;
+
+public:
+ typedef NativePanZoomController::Natives<NPZCSupport> Base;
+
+ NPZCSupport(NativePtr<NPZCSupport>* aPtr, nsWindow* aWindow,
+ const NativePanZoomController::LocalRef& aNPZC)
+ : mWindow(aPtr, aWindow)
+ , mNPZC(aNPZC)
+ , mPreviousButtons(0)
+ {}
+
+ ~NPZCSupport()
+ {}
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnDetach()
+ {
+ // 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 OnDetach, 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. OnDetach 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<void(*)(const NPZCRef&)>(callDestroy),
+ mozilla::Move(npzc)), 0);
+ }
+
+public:
+ void AdjustScrollForSurfaceShift(float aX, float aY)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (controller) {
+ controller->AdjustScrollForSurfaceShift(
+ ScreenPoint(aX, aY));
+ }
+ }
+
+ void SetIsLongpressEnabled(bool aIsLongpressEnabled)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (controller) {
+ controller->SetLongTapEnabled(aIsLongpressEnabled);
+ }
+ }
+
+ bool HandleScrollEvent(int64_t aTime, int32_t aMetaState,
+ float aX, float aY,
+ float aHScroll, float aVScroll)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (!controller) {
+ return false;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ ScrollWheelInput input(aTime, TimeStamp::Now(), GetModifiers(aMetaState),
+ ScrollWheelInput::SCROLLMODE_SMOOTH,
+ ScrollWheelInput::SCROLLDELTA_PIXEL,
+ origin,
+ aHScroll, aVScroll,
+ false);
+
+ ScrollableLayerGuid guid;
+ uint64_t blockId;
+ nsEventStatus status = controller->ReceiveInputEvent(input, &guid, &blockId);
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return true;
+ }
+
+ 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();
+ WidgetWheelEvent wheelEvent = input.ToWidgetWheelEvent(window);
+ window->ProcessUntransformedAPZEvent(&wheelEvent, guid,
+ blockId, status);
+ });
+
+ return true;
+ }
+
+private:
+ static MouseInput::ButtonType GetButtonType(int button)
+ {
+ MouseInput::ButtonType result = MouseInput::NONE;
+
+ switch (button) {
+ case java::sdk::MotionEvent::BUTTON_PRIMARY:
+ result = MouseInput::LEFT_BUTTON;
+ break;
+ case java::sdk::MotionEvent::BUTTON_SECONDARY:
+ result = MouseInput::RIGHT_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 |= WidgetMouseEventBase::eLeftButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_SECONDARY) {
+ result |= WidgetMouseEventBase::eRightButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_TERTIARY) {
+ result |= WidgetMouseEventBase::eMiddleButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_BACK) {
+ result |= WidgetMouseEventBase::e4thButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_FORWARD) {
+ result |= WidgetMouseEventBase::e5thButtonFlag;
+ }
+
+ return result;
+ }
+
+public:
+ bool HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState,
+ float aX, float aY, int buttons)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (!controller) {
+ return false;
+ }
+
+ MouseInput::MouseType mouseType = MouseInput::MOUSE_NONE;
+ MouseInput::ButtonType buttonType = MouseInput::NONE;
+ switch (aAction) {
+ case AndroidMotionEvent::ACTION_DOWN:
+ mouseType = MouseInput::MOUSE_DOWN;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case AndroidMotionEvent::ACTION_UP:
+ mouseType = MouseInput::MOUSE_UP;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case AndroidMotionEvent::ACTION_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case AndroidMotionEvent::ACTION_HOVER_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case AndroidMotionEvent::ACTION_HOVER_ENTER:
+ mouseType = MouseInput::MOUSE_WIDGET_ENTER;
+ break;
+ case AndroidMotionEvent::ACTION_HOVER_EXIT:
+ mouseType = MouseInput::MOUSE_WIDGET_EXIT;
+ break;
+ default:
+ break;
+ }
+
+ if (mouseType == MouseInput::MOUSE_NONE) {
+ return false;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ MouseInput input(mouseType, buttonType, nsIDOMMouseEvent::MOZ_SOURCE_MOUSE, ConvertButtons(buttons), origin, aTime, TimeStamp(), GetModifiers(aMetaState));
+
+ ScrollableLayerGuid guid;
+ uint64_t blockId;
+ nsEventStatus status = controller->ReceiveInputEvent(input, &guid, &blockId);
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return true;
+ }
+
+ 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();
+ WidgetMouseEvent mouseEvent = input.ToWidgetMouseEvent(window);
+ window->ProcessUntransformedAPZEvent(&mouseEvent, guid,
+ blockId, status);
+ });
+
+ return true;
+ }
+
+ 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());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (!controller) {
+ return false;
+ }
+
+ nsTArray<int32_t> pointerId(aPointerId->GetElements());
+ MultiTouchInput::MultiTouchType type;
+ size_t startIndex = 0;
+ size_t endIndex = pointerId.Length();
+
+ switch (aAction) {
+ case sdk::MotionEvent::ACTION_DOWN:
+ case sdk::MotionEvent::ACTION_POINTER_DOWN:
+ type = MultiTouchInput::MULTITOUCH_START;
+ break;
+ case sdk::MotionEvent::ACTION_MOVE:
+ type = MultiTouchInput::MULTITOUCH_MOVE;
+ break;
+ case sdk::MotionEvent::ACTION_UP:
+ case 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 = aActionIndex;
+ endIndex = aActionIndex + 1;
+ break;
+ case sdk::MotionEvent::ACTION_OUTSIDE:
+ case sdk::MotionEvent::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<float> x(aX->GetElements());
+ nsTArray<float> y(aY->GetElements());
+ nsTArray<float> orientation(aOrientation->GetElements());
+ nsTArray<float> pressure(aPressure->GetElements());
+ nsTArray<float> toolMajor(aToolMajor->GetElements());
+ nsTArray<float> toolMinor(aToolMinor->GetElements());
+
+ 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());
+
+ 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])));
+
+ // 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;
+ }
+
+ void HandleMotionEventVelocity(int64_t aTime, float aSpeedY)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (controller) {
+ controller->ProcessTouchVelocity((uint32_t)aTime, aSpeedY);
+ }
+ }
+
+ void UpdateOverscrollVelocity(const float x, const float y)
+ {
+ mNPZC->UpdateOverscrollVelocity(x, y);
+ }
+
+ void UpdateOverscrollOffset(const float x, const float y)
+ {
+ mNPZC->UpdateOverscrollOffset(x, y);
+ }
+
+ void SetScrollingRootContent(const bool isRootContent)
+ {
+ mNPZC->SetScrollingRootContent(isRootContent);
+ }
+
+ void SetSelectionDragState(const bool aState)
+ {
+ mNPZC->OnSelectionDragState(aState);
+ }
+};
+
+template<> const char
+nsWindow::NativePtr<nsWindow::NPZCSupport>::sName[] = "NPZCSupport";
+
+/**
+ * Compositor has some unique requirements for its native calls, so make it
+ * separate from GeckoViewSupport.
+ */
+class nsWindow::LayerViewSupport final
+ : public LayerView::Compositor::Natives<LayerViewSupport>
+{
+ using LockedWindowPtr = WindowPtr<LayerViewSupport>::Locked;
+
+ WindowPtr<LayerViewSupport> mWindow;
+ LayerView::Compositor::GlobalRef mCompositor;
+ GeckoLayerClient::GlobalRef mLayerClient;
+ Atomic<bool, ReleaseAcquire> mCompositorPaused;
+ jni::Object::GlobalRef mSurface;
+
+ // In order to use Event::HasSameTypeAs in PostTo(), we cannot make
+ // LayerViewEvent a template because each template instantiation is
+ // a different type. So implement LayerViewEvent as a ProxyEvent.
+ class LayerViewEvent final : public nsAppShell::ProxyEvent
+ {
+ using Event = nsAppShell::Event;
+
+ public:
+ static UniquePtr<Event> MakeEvent(UniquePtr<Event>&& event)
+ {
+ return MakeUnique<LayerViewEvent>(mozilla::Move(event));
+ }
+
+ LayerViewEvent(UniquePtr<Event>&& event)
+ : nsAppShell::ProxyEvent(mozilla::Move(event))
+ {}
+
+ void PostTo(LinkedList<Event>& queue) override
+ {
+ // Give priority to compositor events, but keep in order with
+ // existing compositor events.
+ nsAppShell::Event* event = queue.getFirst();
+ while (event && event->HasSameTypeAs(this)) {
+ event = event->getNext();
+ }
+ if (event) {
+ event->setPrevious(this);
+ } else {
+ queue.insertBack(this);
+ }
+ }
+ };
+
+public:
+ typedef LayerView::Compositor::Natives<LayerViewSupport> Base;
+
+ template<class Functor>
+ static void OnNativeCall(Functor&& aCall)
+ {
+ if (aCall.IsTarget(&LayerViewSupport::CreateCompositor)) {
+ // This call is blocking.
+ nsAppShell::SyncRunEvent(nsAppShell::LambdaEvent<Functor>(
+ mozilla::Move(aCall)), &LayerViewEvent::MakeEvent);
+ return;
+ }
+ }
+
+ static LayerViewSupport*
+ FromNative(const LayerView::Compositor::LocalRef& instance)
+ {
+ return GetNative(instance);
+ }
+
+ LayerViewSupport(NativePtr<LayerViewSupport>* aPtr, nsWindow* aWindow,
+ const LayerView::Compositor::LocalRef& aInstance)
+ : mWindow(aPtr, aWindow)
+ , mCompositor(aInstance)
+ , mCompositorPaused(true)
+ {}
+
+ ~LayerViewSupport()
+ {}
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnDetach()
+ {
+ mCompositor->Destroy();
+ }
+
+ const GeckoLayerClient::Ref& GetLayerClient() const
+ {
+ return mLayerClient;
+ }
+
+ bool CompositorPaused() const
+ {
+ return mCompositorPaused;
+ }
+
+ jni::Object::Param GetSurface()
+ {
+ return mSurface;
+ }
+
+private:
+ void OnResumedCompositor()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // When we receive this, the compositor has already been told to
+ // resume. (It turns out that waiting till we reach here to tell
+ // the compositor to resume takes too long, resulting in a black
+ // flash.) 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 (!mCompositorPaused) {
+ mWindow->RedrawAll();
+ }
+ }
+
+ /**
+ * Compositor methods
+ */
+public:
+ void AttachToJava(jni::Object::Param aClient, jni::Object::Param aNPZC)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mWindow) {
+ return; // Already shut down.
+ }
+
+ const auto& layerClient = GeckoLayerClient::Ref::From(aClient);
+
+ // If resetting is true, Android destroyed our GeckoApp activity and we
+ // had to recreate it, but all the Gecko-side things were not
+ // destroyed. We therefore need to link up the new java objects to
+ // Gecko, and that's what we do here.
+ const bool resetting = !!mLayerClient;
+ mLayerClient = layerClient;
+
+ MOZ_ASSERT(aNPZC);
+ auto npzc = NativePanZoomController::LocalRef(
+ jni::GetGeckoThreadEnv(),
+ NativePanZoomController::Ref::From(aNPZC));
+ mWindow->mNPZCSupport.Attach(npzc, mWindow, npzc);
+
+ layerClient->OnGeckoReady();
+
+ if (resetting) {
+ // Since we are re-linking the new java objects to Gecko, we need
+ // to get the viewport from the compositor (since the Java copy was
+ // thrown away) and we do that by setting the first-paint flag.
+ if (RefPtr<CompositorBridgeParent> bridge = mWindow->GetCompositorBridgeParent()) {
+ bridge->ForceIsFirstPaint();
+ }
+ }
+ }
+
+ void OnSizeChanged(int32_t aWindowWidth, int32_t aWindowHeight,
+ int32_t aScreenWidth, int32_t aScreenHeight)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mWindow) {
+ return; // Already shut down.
+ }
+
+ if (aWindowWidth != mWindow->mBounds.width ||
+ aWindowHeight != mWindow->mBounds.height) {
+
+ mWindow->Resize(aWindowWidth, aWindowHeight, /* repaint */ false);
+ }
+ }
+
+ void CreateCompositor(int32_t aWidth, int32_t aHeight,
+ jni::Object::Param aSurface)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mWindow);
+
+ mSurface = aSurface;
+ mWindow->CreateLayerManager(aWidth, aHeight);
+
+ mCompositorPaused = false;
+ OnResumedCompositor();
+ }
+
+ void SyncPauseCompositor()
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ if (bridge) {
+ mCompositorPaused = true;
+ bridge->SchedulePauseOnCompositorThread();
+ }
+ }
+
+ void SyncResumeCompositor()
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ if (bridge && bridge->ScheduleResumeOnCompositorThread()) {
+ mCompositorPaused = false;
+ }
+ }
+
+ void SyncResumeResizeCompositor(const LayerView::Compositor::LocalRef& aObj,
+ int32_t aWidth, int32_t aHeight,
+ jni::Object::Param aSurface)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ mSurface = aSurface;
+
+ if (!bridge || !bridge->ScheduleResumeOnCompositorThread(aWidth,
+ aHeight)) {
+ return;
+ }
+
+ mCompositorPaused = false;
+
+ class OnResumedEvent : public nsAppShell::Event
+ {
+ LayerView::Compositor::GlobalRef mCompositor;
+
+ public:
+ OnResumedEvent(LayerView::Compositor::GlobalRef&& aCompositor)
+ : mCompositor(mozilla::Move(aCompositor))
+ {}
+
+ void Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ LayerViewSupport* const lvs = GetNative(
+ LayerView::Compositor::LocalRef(env, mCompositor));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+
+ lvs->OnResumedCompositor();
+ }
+ };
+
+ nsAppShell::PostEvent(MakeUnique<LayerViewEvent>(
+ MakeUnique<OnResumedEvent>(aObj)));
+ }
+
+ void SyncInvalidateAndScheduleComposite()
+ {
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ if (bridge) {
+ bridge->InvalidateOnCompositorThread();
+ bridge->ScheduleRenderOnCompositorThread();
+ }
+ }
+};
+
+template<> const char
+nsWindow::NativePtr<nsWindow::LayerViewSupport>::sName[] = "LayerViewSupport";
+
+/* PresentationMediaPlayerManager native calls access inner nsWindow functionality so PMPMSupport is a child class of nsWindow */
+class nsWindow::PMPMSupport final
+ : public PresentationMediaPlayerManager::Natives<PMPMSupport>
+{
+ PMPMSupport() = delete;
+
+ static LayerViewSupport* GetLayerViewSupport(jni::Object::Param aView)
+ {
+ const auto& layerView = LayerView::Ref::From(aView);
+
+ LayerView::Compositor::LocalRef compositor = layerView->GetCompositor();
+ if (!layerView->CompositorCreated() || !compositor) {
+ return nullptr;
+ }
+
+ LayerViewSupport* const lvs = LayerViewSupport::FromNative(compositor);
+ if (!lvs) {
+ // There is a pending exception whenever FromNative returns nullptr.
+ compositor.Env()->ExceptionClear();
+ }
+ return lvs;
+ }
+
+public:
+ static ANativeWindow* sWindow;
+ static EGLSurface sSurface;
+
+ static void InvalidateAndScheduleComposite(jni::Object::Param aView)
+ {
+ LayerViewSupport* const lvs = GetLayerViewSupport(aView);
+ if (lvs) {
+ lvs->SyncInvalidateAndScheduleComposite();
+ }
+ }
+
+ static void AddPresentationSurface(const jni::Class::LocalRef& aCls,
+ jni::Object::Param aView,
+ jni::Object::Param aSurface)
+ {
+ RemovePresentationSurface();
+
+ LayerViewSupport* const lvs = GetLayerViewSupport(aView);
+ if (!lvs) {
+ return;
+ }
+
+ ANativeWindow* const window = ANativeWindow_fromSurface(
+ aCls.Env(), aSurface.Get());
+ if (!window) {
+ return;
+ }
+
+ sWindow = window;
+
+ const bool wasAlreadyPaused = lvs->CompositorPaused();
+ if (!wasAlreadyPaused) {
+ lvs->SyncPauseCompositor();
+ }
+
+ if (sSurface) {
+ // Destroy the EGL surface! The compositor is paused so it should
+ // be okay to destroy the surface here.
+ mozilla::gl::GLContextProvider::DestroyEGLSurface(sSurface);
+ sSurface = nullptr;
+ }
+
+ if (!wasAlreadyPaused) {
+ lvs->SyncResumeCompositor();
+ }
+
+ lvs->SyncInvalidateAndScheduleComposite();
+ }
+
+ static void RemovePresentationSurface()
+ {
+ if (sWindow) {
+ ANativeWindow_release(sWindow);
+ sWindow = nullptr;
+ }
+ }
+};
+
+ANativeWindow* nsWindow::PMPMSupport::sWindow;
+EGLSurface nsWindow::PMPMSupport::sSurface;
+
+
+nsWindow::GeckoViewSupport::~GeckoViewSupport()
+{
+ // Disassociate our GeckoEditable instance with our native object.
+ // OnDestroy will call disposeNative after any pending native calls have
+ // been made.
+ MOZ_ASSERT(mEditable);
+ mEditable->OnViewChange(nullptr);
+
+ if (window.mNPZCSupport) {
+ window.mNPZCSupport.Detach();
+ }
+
+ if (window.mLayerViewSupport) {
+ window.mLayerViewSupport.Detach();
+ }
+}
+
+/* static */ void
+nsWindow::GeckoViewSupport::Open(const jni::Class::LocalRef& aCls,
+ GeckoView::Window::Param aWindow,
+ GeckoView::Param aView,
+ jni::Object::Param aCompositor,
+ jni::String::Param aChromeURI,
+ int32_t aScreenId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PROFILER_LABEL("nsWindow", "GeckoViewSupport::Open",
+ js::ProfileEntry::Category::OTHER);
+
+ nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ MOZ_RELEASE_ASSERT(ww);
+
+ nsAdoptingCString url;
+ if (aChromeURI) {
+ url = aChromeURI->ToCString();
+ } else {
+ url = Preferences::GetCString("toolkit.defaultChromeURI");
+ if (!url) {
+ url = NS_LITERAL_CSTRING("chrome://browser/content/browser.xul");
+ }
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ ww->OpenWindow(nullptr, url, nullptr, "chrome,dialog=0,resizable,scrollbars=yes",
+ nullptr, getter_AddRefs(domWindow));
+ MOZ_RELEASE_ASSERT(domWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> pdomWindow =
+ nsPIDOMWindowOuter::From(domWindow);
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(pdomWindow);
+ MOZ_ASSERT(widget);
+
+ const auto window = static_cast<nsWindow*>(widget.get());
+ window->SetScreenId(aScreenId);
+
+ // Attach a new GeckoView support object to the new window.
+ window->mGeckoViewSupport = mozilla::MakeUnique<GeckoViewSupport>(
+ window, GeckoView::Window::LocalRef(aCls.Env(), aWindow), aView);
+
+ window->mGeckoViewSupport->mDOMWindow = pdomWindow;
+
+ // Attach the Compositor to the new window.
+ auto compositor = LayerView::Compositor::LocalRef(
+ aCls.Env(), LayerView::Compositor::Ref::From(aCompositor));
+ window->mLayerViewSupport.Attach(compositor, window, compositor);
+
+ if (window->mWidgetListener) {
+ nsCOMPtr<nsIXULWindow> xulWindow(
+ window->mWidgetListener->GetXULWindow());
+ if (xulWindow) {
+ // Our window is not intrinsically sized, so tell nsXULWindow to
+ // not set a size for us.
+ xulWindow->SetIntrinsicallySized(false);
+ }
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::Close()
+{
+ if (!mDOMWindow) {
+ return;
+ }
+
+ mDOMWindow->ForceClose();
+ mDOMWindow = nullptr;
+}
+
+void
+nsWindow::GeckoViewSupport::Reattach(const GeckoView::Window::LocalRef& inst,
+ GeckoView::Param aView,
+ jni::Object::Param aCompositor)
+{
+ // Associate our previous GeckoEditable with the new GeckoView.
+ mEditable->OnViewChange(aView);
+
+ // mNPZCSupport might have already been detached through the Java side calling
+ // NativePanZoomController.destroy().
+ if (window.mNPZCSupport) {
+ window.mNPZCSupport.Detach();
+ }
+
+ MOZ_ASSERT(window.mLayerViewSupport);
+ window.mLayerViewSupport.Detach();
+
+ auto compositor = LayerView::Compositor::LocalRef(
+ inst.Env(), LayerView::Compositor::Ref::From(aCompositor));
+ window.mLayerViewSupport.Attach(compositor, &window, compositor);
+ compositor->Reattach();
+}
+
+void
+nsWindow::GeckoViewSupport::LoadUri(jni::String::Param aUri, int32_t aFlags)
+{
+ if (!mDOMWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = nsAppShell::ResolveURI(aUri->ToCString());
+ if (NS_WARN_IF(!uri)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(mDOMWindow);
+ nsCOMPtr<nsIBrowserDOMWindow> browserWin;
+
+ if (NS_WARN_IF(!chromeWin) || NS_WARN_IF(NS_FAILED(
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(browserWin))))) {
+ return;
+ }
+
+ const int flags = aFlags == GeckoView::LOAD_NEW_TAB ?
+ nsIBrowserDOMWindow::OPEN_NEWTAB :
+ aFlags == GeckoView::LOAD_SWITCH_TAB ?
+ nsIBrowserDOMWindow::OPEN_SWITCHTAB :
+ nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+ nsCOMPtr<mozIDOMWindowProxy> newWin;
+
+ if (NS_FAILED(browserWin->OpenURI(
+ uri, nullptr, flags, nsIBrowserDOMWindow::OPEN_EXTERNAL,
+ getter_AddRefs(newWin)))) {
+ NS_WARNING("Failed to open URI");
+ }
+}
+
+void
+nsWindow::InitNatives()
+{
+ nsWindow::GeckoViewSupport::Base::Init();
+ nsWindow::GeckoViewSupport::EditableBase::Init();
+ nsWindow::LayerViewSupport::Init();
+ nsWindow::NPZCSupport::Init();
+ if (jni::IsFennec()) {
+ nsWindow::PMPMSupport::Init();
+ }
+}
+
+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%08x [parent 0x%08x] [% 3d,% 3dx% 3d,% 3d] vis %d type %d",
+ spaces, index, (intptr_t)win, (intptr_t)win->mParent,
+ win->mBounds.x, win->mBounds.y,
+ win->mBounds.width, win->mBounds.height,
+ win->mIsVisible, win->mWindowType);
+#endif
+}
+
+void
+nsWindow::DumpWindows()
+{
+ DumpWindows(gTopLevelWindows);
+}
+
+void
+nsWindow::DumpWindows(const nsTArray<nsWindow*>& wins, int indent)
+{
+ for (uint32_t i = 0; i < wins.Length(); ++i) {
+ nsWindow *w = wins[i];
+ LogWindow(w, i, indent);
+ DumpWindows(w->mChildren, indent+1);
+ }
+}
+
+nsWindow::nsWindow() :
+ mScreenId(0), // Use 0 (primary screen) as the default value.
+ mIsVisible(false),
+ mParent(nullptr),
+ mAwaitingFullScreen(false),
+ mIsFullScreen(false)
+{
+}
+
+nsWindow::~nsWindow()
+{
+ gTopLevelWindows.RemoveElement(this);
+ ALOG("nsWindow %p destructor", (void*)this);
+}
+
+bool
+nsWindow::IsTopLevel()
+{
+ return mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_invisible;
+}
+
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* 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;
+ }
+ }
+
+ mBounds = aRect;
+
+ 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()
+{
+ nsBaseWidget::mOnDestroyCalled = true;
+
+ if (mGeckoViewSupport) {
+ // Disassociate our native object with GeckoView.
+ mGeckoViewSupport = nullptr;
+ }
+
+ // Stuff below may release the last ref to this
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ while (mChildren.Length()) {
+ // why do we still have children?
+ ALOG("### Warning: Destroying window %p and reparenting child %p to null!", (void*)this, (void*)mChildren[0]);
+ mChildren[0]->SetParent(nullptr);
+ }
+
+ nsBaseWidget::Destroy();
+
+ if (IsTopLevel())
+ gTopLevelWindows.RemoveElement(this);
+
+ SetParent(nullptr);
+
+ nsBaseWidget::OnDestroy();
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+}
+
+NS_IMETHODIMP
+nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config)
+{
+ for (uint32_t i = 0; i < config.Length(); ++i) {
+ nsWindow *childWin = (nsWindow*) config[i].mChild.get();
+ childWin->Resize(config[i].mBounds.x,
+ config[i].mBounds.y,
+ config[i].mBounds.width,
+ config[i].mBounds.height,
+ false);
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::RedrawAll()
+{
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->RequestRepaint();
+ } else if (mWidgetListener) {
+ mWidgetListener->RequestRepaint();
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::SetParent(nsIWidget *aNewParent)
+{
+ if ((nsIWidget*)mParent == aNewParent)
+ return NS_OK;
+
+ // 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();
+
+ return NS_OK;
+}
+
+nsIWidget*
+nsWindow::GetParent()
+{
+ return mParent;
+}
+
+float
+nsWindow::GetDPI()
+{
+ if (AndroidBridge::Bridge())
+ return AndroidBridge::Bridge()->GetDPI();
+ return 160.0f;
+}
+
+double
+nsWindow::GetDefaultScaleInternal()
+{
+
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ MOZ_ASSERT(screen);
+ RefPtr<nsScreenAndroid> screenAndroid = (nsScreenAndroid*) screen.get();
+ return screenAndroid->GetDensity();
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ ALOG("nsWindow[%p]::Show %d", (void*)this, aState);
+
+ if (mWindowType == eWindowType_invisible) {
+ ALOG("trying to show invisible window! ignoring..");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aState == mIsVisible)
+ return NS_OK;
+
+ 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
+
+ return NS_OK;
+}
+
+bool
+nsWindow::IsVisible() const
+{
+ return mIsVisible;
+}
+
+void
+nsWindow::ConstrainPosition(bool aAllowSlop,
+ int32_t *aX,
+ int32_t *aY)
+{
+ ALOG("nsWindow[%p]::ConstrainPosition %d [%d %d]", (void*)this, aAllowSlop, *aX, *aY);
+
+ // constrain toplevel windows; children we don't care about
+ if (IsTopLevel()) {
+ *aX = 0;
+ *aY = 0;
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Move(double aX,
+ double aY)
+{
+ if (IsTopLevel())
+ return NS_OK;
+
+ return Resize(aX,
+ aY,
+ mBounds.width,
+ mBounds.height,
+ true);
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ return Resize(mBounds.x,
+ mBounds.y,
+ aWidth,
+ aHeight,
+ aRepaint);
+}
+
+NS_IMETHODIMP
+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);
+
+ bool needSizeDispatch = aWidth != mBounds.width || aHeight != mBounds.height;
+
+ mBounds.x = NSToIntRound(aX);
+ mBounds.y = NSToIntRound(aY);
+ mBounds.width = NSToIntRound(aWidth);
+ mBounds.height = NSToIntRound(aHeight);
+
+ if (needSizeDispatch) {
+ OnSizeChanged(gfx::IntSize::Truncate(aWidth, aHeight));
+ }
+
+ // Should we skip honoring aRepaint here?
+ if (aRepaint && FindTopLevel() == nsWindow::TopWindow())
+ RedrawAll();
+
+ nsIWidgetListener* listener = GetWidgetListener();
+ if (mAwaitingFullScreen && listener) {
+ listener->FullscreenChanged(mIsFullScreen);
+ mAwaitingFullScreen = false;
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::SetZIndex(int32_t aZIndex)
+{
+ ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex);
+}
+
+void
+nsWindow::SetSizeMode(nsSizeMode aMode)
+{
+ switch (aMode) {
+ case nsSizeMode_Minimized:
+ GeckoAppShell::MoveTaskToBack();
+ break;
+ case nsSizeMode_Fullscreen:
+ MakeFullScreen(true);
+ break;
+ default:
+ break;
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Enable(bool aState)
+{
+ ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState);
+ return NS_OK;
+}
+
+bool
+nsWindow::IsEnabled() const
+{
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ return NS_OK;
+}
+
+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;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ nsWindow *top = FindTopLevel();
+ top->BringToFront();
+
+ return NS_OK;
+}
+
+void
+nsWindow::BringToFront()
+{
+ // 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.
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ nsCOMPtr<mozIDOMWindowProxy> existingTopWindow;
+ fm->GetActiveWindow(getter_AddRefs(existingTopWindow));
+ if (existingTopWindow && FindTopLevel() == nsWindow::TopWindow())
+ return;
+
+ if (!IsTopLevel()) {
+ FindTopLevel()->BringToFront();
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(this);
+
+ nsWindow *oldTop = nullptr;
+ if (!gTopLevelWindows.IsEmpty()) {
+ oldTop = gTopLevelWindows[0];
+ }
+
+ gTopLevelWindows.RemoveElement(this);
+ gTopLevelWindows.InsertElementAt(0, this);
+
+ if (oldTop) {
+ nsIWidgetListener* listener = oldTop->GetWidgetListener();
+ if (listener) {
+ listener->WindowDeactivated();
+ }
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowActivated();
+ }
+
+ RedrawAll();
+}
+
+LayoutDeviceIntRect
+nsWindow::GetScreenBounds()
+{
+ return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
+}
+
+LayoutDeviceIntPoint
+nsWindow::WidgetToScreenOffset()
+{
+ LayoutDeviceIntPoint p(0, 0);
+ nsWindow *w = this;
+
+ while (w && !w->IsTopLevel()) {
+ p.x += w->mBounds.x;
+ p.y += w->mBounds.y;
+
+ w = w->mParent;
+ }
+
+ return p;
+}
+
+NS_IMETHODIMP
+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, nsIScreen*)
+{
+ mIsFullScreen = aFullScreen;
+ mAwaitingFullScreen = true;
+ GeckoAppShell::SetFullScreen(aFullScreen);
+ return NS_OK;
+}
+
+mozilla::layers::LayerManager*
+nsWindow::GetLayerManager(PLayerTransactionChild*, LayersBackend, LayerManagerPersistence)
+{
+ if (mLayerManager) {
+ return mLayerManager;
+ }
+ return nullptr;
+}
+
+void
+nsWindow::CreateLayerManager(int aCompositorWidth, int aCompositorHeight)
+{
+ if (mLayerManager) {
+ return;
+ }
+
+ nsWindow *topLevelWindow = FindTopLevel();
+ if (!topLevelWindow || topLevelWindow->mWindowType == eWindowType_invisible) {
+ // don't create a layer manager for an invisible top-level window
+ return;
+ }
+
+ // Ensure that gfxPlatform is initialized first.
+ gfxPlatform::GetPlatform();
+
+ if (ShouldUseOffMainThreadCompositing()) {
+ CreateCompositor(aCompositorWidth, aCompositorHeight);
+ if (mLayerManager) {
+ 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");
+ mLayerManager = CreateBasicLayerManager();
+ }
+}
+
+void
+nsWindow::OnSizeChanged(const gfx::IntSize& aSize)
+{
+ ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width, aSize.height);
+
+ mBounds.width = aSize.width;
+ mBounds.height = aSize.height;
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+}
+
+void
+nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint)
+{
+ if (aPoint) {
+ event.mRefPoint = *aPoint;
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+
+ event.mTime = PR_Now() / 1000;
+}
+
+void
+nsWindow::UpdateOverscrollVelocity(const float aX, const float aY)
+{
+ if (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->UpdateOverscrollVelocity(aX, aY);
+ }
+}
+
+void
+nsWindow::UpdateOverscrollOffset(const float aX, const float aY)
+{
+ if (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->UpdateOverscrollOffset(aX, aY);
+ }
+}
+
+void
+nsWindow::SetScrollingRootContent(const bool isRootContent)
+{
+ // On Android, the Controller thread and UI thread are the same.
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread(), "nsWindow::SetScrollingRootContent must be called from the controller thread");
+
+ if (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->SetScrollingRootContent(isRootContent);
+ }
+}
+
+void
+nsWindow::SetSelectionDragState(bool aState)
+{
+ if (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->SetSelectionDragState(aState);
+ }
+}
+
+void *
+nsWindow::GetNativeData(uint32_t aDataType)
+{
+ switch (aDataType) {
+ // used by GLContextProviderEGL, nullptr is EGL_DEFAULT_DISPLAY
+ case NS_NATIVE_DISPLAY:
+ return nullptr;
+
+ 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 (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ return lvs->GetSurface().Get();
+ }
+ return nullptr;
+
+ case NS_PRESENTATION_WINDOW:
+ return PMPMSupport::sWindow;
+
+ case NS_PRESENTATION_SURFACE:
+ return PMPMSupport::sSurface;
+ }
+
+ return nullptr;
+}
+
+void
+nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ switch (aDataType) {
+ case NS_PRESENTATION_SURFACE:
+ PMPMSupport::sSurface = reinterpret_cast<EGLSurface>(aVal);
+ break;
+ }
+}
+
+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 on Fennec, we dispatch a dummy mouse event that *does* get
+ // retargeted. The Fennec browser.js code can use this to activate the
+ // highlight element in case the this touchstart is the start of a tap.
+ WidgetMouseEvent hittest(true, eMouseHitTest, this,
+ WidgetMouseEvent::eReal);
+ hittest.mRefPoint = aEvent.mTouches[0]->mRefPoint;
+ hittest.mIgnoreRootScrollFrame = true;
+ hittest.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+ nsEventStatus status;
+ DispatchEvent(&hittest, status);
+
+ if (mAPZEventState && hittest.hitCluster) {
+ mAPZEventState->ProcessClusterHit();
+ }
+ }
+}
+
+static unsigned int ConvertAndroidKeyCodeToDOMKeyCode(int androidKeyCode)
+{
+ // Special-case alphanumeric keycodes because they are most common.
+ if (androidKeyCode >= AKEYCODE_A &&
+ androidKeyCode <= AKEYCODE_Z) {
+ return androidKeyCode - AKEYCODE_A + NS_VK_A;
+ }
+
+ if (androidKeyCode >= AKEYCODE_0 &&
+ androidKeyCode <= AKEYCODE_9) {
+ return androidKeyCode - AKEYCODE_0 + NS_VK_0;
+ }
+
+ switch (androidKeyCode) {
+ // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3)
+ case AKEYCODE_BACK: return NS_VK_ESCAPE;
+ // KEYCODE_CALL (5) ... KEYCODE_POUND (18)
+ case AKEYCODE_DPAD_UP: return NS_VK_UP;
+ case AKEYCODE_DPAD_DOWN: return NS_VK_DOWN;
+ case AKEYCODE_DPAD_LEFT: return NS_VK_LEFT;
+ case AKEYCODE_DPAD_RIGHT: return NS_VK_RIGHT;
+ case AKEYCODE_DPAD_CENTER: return NS_VK_RETURN;
+ case AKEYCODE_VOLUME_UP: return NS_VK_VOLUME_UP;
+ case AKEYCODE_VOLUME_DOWN: return NS_VK_VOLUME_DOWN;
+ // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54)
+ case AKEYCODE_COMMA: return NS_VK_COMMA;
+ case AKEYCODE_PERIOD: return NS_VK_PERIOD;
+ case AKEYCODE_ALT_LEFT: return NS_VK_ALT;
+ case AKEYCODE_ALT_RIGHT: return NS_VK_ALT;
+ case AKEYCODE_SHIFT_LEFT: return NS_VK_SHIFT;
+ case AKEYCODE_SHIFT_RIGHT: return NS_VK_SHIFT;
+ case AKEYCODE_TAB: return NS_VK_TAB;
+ case AKEYCODE_SPACE: return NS_VK_SPACE;
+ // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65)
+ case AKEYCODE_ENTER: return NS_VK_RETURN;
+ case AKEYCODE_DEL: return NS_VK_BACK; // Backspace
+ case AKEYCODE_GRAVE: return NS_VK_BACK_QUOTE;
+ // KEYCODE_MINUS (69)
+ case AKEYCODE_EQUALS: return NS_VK_EQUALS;
+ case AKEYCODE_LEFT_BRACKET: return NS_VK_OPEN_BRACKET;
+ case AKEYCODE_RIGHT_BRACKET: return NS_VK_CLOSE_BRACKET;
+ case AKEYCODE_BACKSLASH: return NS_VK_BACK_SLASH;
+ case AKEYCODE_SEMICOLON: return NS_VK_SEMICOLON;
+ // KEYCODE_APOSTROPHE (75)
+ case AKEYCODE_SLASH: return NS_VK_SLASH;
+ // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90)
+ case AKEYCODE_MUTE: return NS_VK_VOLUME_MUTE;
+ case AKEYCODE_PAGE_UP: return NS_VK_PAGE_UP;
+ case AKEYCODE_PAGE_DOWN: return NS_VK_PAGE_DOWN;
+ // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110)
+ case AKEYCODE_ESCAPE: return NS_VK_ESCAPE;
+ case AKEYCODE_FORWARD_DEL: return NS_VK_DELETE;
+ case AKEYCODE_CTRL_LEFT: return NS_VK_CONTROL;
+ case AKEYCODE_CTRL_RIGHT: return NS_VK_CONTROL;
+ case AKEYCODE_CAPS_LOCK: return NS_VK_CAPS_LOCK;
+ case AKEYCODE_SCROLL_LOCK: return NS_VK_SCROLL_LOCK;
+ // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119)
+ case AKEYCODE_SYSRQ: return NS_VK_PRINTSCREEN;
+ case AKEYCODE_BREAK: return NS_VK_PAUSE;
+ case AKEYCODE_MOVE_HOME: return NS_VK_HOME;
+ case AKEYCODE_MOVE_END: return NS_VK_END;
+ case AKEYCODE_INSERT: return NS_VK_INSERT;
+ // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130)
+ case AKEYCODE_F1: return NS_VK_F1;
+ case AKEYCODE_F2: return NS_VK_F2;
+ case AKEYCODE_F3: return NS_VK_F3;
+ case AKEYCODE_F4: return NS_VK_F4;
+ case AKEYCODE_F5: return NS_VK_F5;
+ case AKEYCODE_F6: return NS_VK_F6;
+ case AKEYCODE_F7: return NS_VK_F7;
+ case AKEYCODE_F8: return NS_VK_F8;
+ case AKEYCODE_F9: return NS_VK_F9;
+ case AKEYCODE_F10: return NS_VK_F10;
+ case AKEYCODE_F11: return NS_VK_F11;
+ case AKEYCODE_F12: return NS_VK_F12;
+ case AKEYCODE_NUM_LOCK: return NS_VK_NUM_LOCK;
+ case AKEYCODE_NUMPAD_0: return NS_VK_NUMPAD0;
+ case AKEYCODE_NUMPAD_1: return NS_VK_NUMPAD1;
+ case AKEYCODE_NUMPAD_2: return NS_VK_NUMPAD2;
+ case AKEYCODE_NUMPAD_3: return NS_VK_NUMPAD3;
+ case AKEYCODE_NUMPAD_4: return NS_VK_NUMPAD4;
+ case AKEYCODE_NUMPAD_5: return NS_VK_NUMPAD5;
+ case AKEYCODE_NUMPAD_6: return NS_VK_NUMPAD6;
+ case AKEYCODE_NUMPAD_7: return NS_VK_NUMPAD7;
+ case AKEYCODE_NUMPAD_8: return NS_VK_NUMPAD8;
+ case AKEYCODE_NUMPAD_9: return NS_VK_NUMPAD9;
+ case AKEYCODE_NUMPAD_DIVIDE: return NS_VK_DIVIDE;
+ case AKEYCODE_NUMPAD_MULTIPLY: return NS_VK_MULTIPLY;
+ case AKEYCODE_NUMPAD_SUBTRACT: return NS_VK_SUBTRACT;
+ case AKEYCODE_NUMPAD_ADD: return NS_VK_ADD;
+ case AKEYCODE_NUMPAD_DOT: return NS_VK_DECIMAL;
+ case AKEYCODE_NUMPAD_COMMA: return NS_VK_SEPARATOR;
+ case AKEYCODE_NUMPAD_ENTER: return NS_VK_RETURN;
+ case AKEYCODE_NUMPAD_EQUALS: return NS_VK_EQUALS;
+ // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210)
+
+ // Needs to confirm the behavior. If the key switches the open state
+ // of Japanese IME (or switches input character between Hiragana and
+ // Roman numeric characters), then, it might be better to use
+ // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows.
+ case AKEYCODE_ZENKAKU_HANKAKU: return 0;
+ case AKEYCODE_EISU: return NS_VK_EISU;
+ case AKEYCODE_MUHENKAN: return NS_VK_NONCONVERT;
+ case AKEYCODE_HENKAN: return NS_VK_CONVERT;
+ case AKEYCODE_KATAKANA_HIRAGANA: return 0;
+ case AKEYCODE_YEN: return NS_VK_BACK_SLASH; // Same as other platforms.
+ case AKEYCODE_RO: return NS_VK_BACK_SLASH; // Same as other platforms.
+ case AKEYCODE_KANA: return NS_VK_KANA;
+ case AKEYCODE_ASSIST: return NS_VK_HELP;
+
+ // the A key is the action key for gamepad devices.
+ case AKEYCODE_BUTTON_A: return NS_VK_RETURN;
+
+ default:
+ ALOG("ConvertAndroidKeyCodeToDOMKeyCode: "
+ "No DOM keycode for Android keycode %d", androidKeyCode);
+ return 0;
+ }
+}
+
+static KeyNameIndex
+ConvertAndroidKeyCodeToKeyNameIndex(int keyCode, int action,
+ int domPrintableKeyValue)
+{
+ // Special-case alphanumeric keycodes because they are most common.
+ if (keyCode >= AKEYCODE_A && keyCode <= AKEYCODE_Z) {
+ return KEY_NAME_INDEX_USE_STRING;
+ }
+
+ if (keyCode >= AKEYCODE_0 && keyCode <= AKEYCODE_9) {
+ return KEY_NAME_INDEX_USE_STRING;
+ }
+
+ switch (keyCode) {
+
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ // KEYCODE_0 (7) ... KEYCODE_9 (16)
+ case AKEYCODE_STAR: // '*' key
+ case AKEYCODE_POUND: // '#' key
+
+ // KEYCODE_A (29) ... KEYCODE_Z (54)
+
+ case AKEYCODE_COMMA: // ',' key
+ case AKEYCODE_PERIOD: // '.' key
+ case AKEYCODE_SPACE:
+ case AKEYCODE_GRAVE: // '`' key
+ case AKEYCODE_MINUS: // '-' key
+ case AKEYCODE_EQUALS: // '=' key
+ case AKEYCODE_LEFT_BRACKET: // '[' key
+ case AKEYCODE_RIGHT_BRACKET: // ']' key
+ case AKEYCODE_BACKSLASH: // '\' key
+ case AKEYCODE_SEMICOLON: // ';' key
+ case AKEYCODE_APOSTROPHE: // ''' key
+ case AKEYCODE_SLASH: // '/' key
+ case AKEYCODE_AT: // '@' key
+ case AKEYCODE_PLUS: // '+' key
+
+ case AKEYCODE_NUMPAD_0:
+ case AKEYCODE_NUMPAD_1:
+ case AKEYCODE_NUMPAD_2:
+ case AKEYCODE_NUMPAD_3:
+ case AKEYCODE_NUMPAD_4:
+ case AKEYCODE_NUMPAD_5:
+ case AKEYCODE_NUMPAD_6:
+ case AKEYCODE_NUMPAD_7:
+ case AKEYCODE_NUMPAD_8:
+ case AKEYCODE_NUMPAD_9:
+ case AKEYCODE_NUMPAD_DIVIDE:
+ case AKEYCODE_NUMPAD_MULTIPLY:
+ case AKEYCODE_NUMPAD_SUBTRACT:
+ case AKEYCODE_NUMPAD_ADD:
+ case AKEYCODE_NUMPAD_DOT:
+ case AKEYCODE_NUMPAD_COMMA:
+ case AKEYCODE_NUMPAD_EQUALS:
+ case AKEYCODE_NUMPAD_LEFT_PAREN:
+ case AKEYCODE_NUMPAD_RIGHT_PAREN:
+
+ case AKEYCODE_YEN: // yen sign key
+ case AKEYCODE_RO: // Japanese Ro key
+ return KEY_NAME_INDEX_USE_STRING;
+
+ case AKEYCODE_ENDCALL:
+ case AKEYCODE_NUM: // XXX Not sure
+ case AKEYCODE_HEADSETHOOK:
+ case AKEYCODE_NOTIFICATION: // XXX Not sure
+ case AKEYCODE_PICTSYMBOLS:
+
+ case AKEYCODE_BUTTON_A:
+ case AKEYCODE_BUTTON_B:
+ case AKEYCODE_BUTTON_C:
+ case AKEYCODE_BUTTON_X:
+ case AKEYCODE_BUTTON_Y:
+ case AKEYCODE_BUTTON_Z:
+ case AKEYCODE_BUTTON_L1:
+ case AKEYCODE_BUTTON_R1:
+ case AKEYCODE_BUTTON_L2:
+ case AKEYCODE_BUTTON_R2:
+ case AKEYCODE_BUTTON_THUMBL:
+ case AKEYCODE_BUTTON_THUMBR:
+ case AKEYCODE_BUTTON_START:
+ case AKEYCODE_BUTTON_SELECT:
+ case AKEYCODE_BUTTON_MODE:
+
+ case AKEYCODE_MUTE: // mutes the microphone
+ case AKEYCODE_MEDIA_CLOSE:
+
+ case AKEYCODE_DVR:
+
+ case AKEYCODE_BUTTON_1:
+ case AKEYCODE_BUTTON_2:
+ case AKEYCODE_BUTTON_3:
+ case AKEYCODE_BUTTON_4:
+ case AKEYCODE_BUTTON_5:
+ case AKEYCODE_BUTTON_6:
+ case AKEYCODE_BUTTON_7:
+ case AKEYCODE_BUTTON_8:
+ case AKEYCODE_BUTTON_9:
+ case AKEYCODE_BUTTON_10:
+ case AKEYCODE_BUTTON_11:
+ case AKEYCODE_BUTTON_12:
+ case AKEYCODE_BUTTON_13:
+ case AKEYCODE_BUTTON_14:
+ case AKEYCODE_BUTTON_15:
+ case AKEYCODE_BUTTON_16:
+
+ case AKEYCODE_MANNER_MODE:
+ case AKEYCODE_3D_MODE:
+ case AKEYCODE_CONTACTS:
+ return KEY_NAME_INDEX_Unidentified;
+
+ case AKEYCODE_UNKNOWN:
+ MOZ_ASSERT(
+ action != AKEY_EVENT_ACTION_MULTIPLE,
+ "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!");
+ // It's actually an unknown key if the action isn't ACTION_MULTIPLE.
+ // However, it might cause text input. So, let's check the value.
+ return domPrintableKeyValue ?
+ KEY_NAME_INDEX_USE_STRING : KEY_NAME_INDEX_Unidentified;
+
+ default:
+ ALOG("ConvertAndroidKeyCodeToKeyNameIndex: "
+ "No DOM key name index for Android keycode %d", keyCode);
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+static CodeNameIndex
+ConvertAndroidScanCodeToCodeNameIndex(int scanCode)
+{
+ switch (scanCode) {
+
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+static bool
+IsModifierKey(int32_t keyCode)
+{
+ using mozilla::java::sdk::KeyEvent;
+ return keyCode == KeyEvent::KEYCODE_ALT_LEFT ||
+ keyCode == KeyEvent::KEYCODE_ALT_RIGHT ||
+ keyCode == KeyEvent::KEYCODE_SHIFT_LEFT ||
+ keyCode == KeyEvent::KEYCODE_SHIFT_RIGHT ||
+ keyCode == KeyEvent::KEYCODE_CTRL_LEFT ||
+ keyCode == KeyEvent::KEYCODE_CTRL_RIGHT ||
+ keyCode == KeyEvent::KEYCODE_META_LEFT ||
+ keyCode == KeyEvent::KEYCODE_META_RIGHT;
+}
+
+static Modifiers
+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);
+}
+
+static void
+InitKeyEvent(WidgetKeyboardEvent& event,
+ int32_t action, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, int64_t time, int32_t unicodeChar,
+ int32_t baseUnicodeChar, int32_t domPrintableKeyValue,
+ int32_t repeatCount, int32_t flags)
+{
+ const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(keyCode);
+ const int32_t charCode = unicodeChar ? unicodeChar : baseUnicodeChar;
+
+ event.mModifiers = GetModifiers(metaState);
+
+ if (event.mMessage == eKeyPress) {
+ // Android gives us \n, so filter out some control characters.
+ event.mIsChar = (charCode >= ' ');
+ event.mCharCode = event.mIsChar ? charCode : 0;
+ event.mKeyCode = event.mIsChar ? 0 : domKeyCode;
+ event.mPluginEvent.Clear();
+
+ // For keypress, if the unicode char already has modifiers applied, we
+ // don't specify extra modifiers. If UnicodeChar() != BaseUnicodeChar()
+ // it means UnicodeChar() already has modifiers applied.
+ // Note that on Android 4.x, Alt modifier isn't set when the key input
+ // causes text input even while right Alt key is pressed. However,
+ // this is necessary for Android 2.3 compatibility.
+ if (unicodeChar && unicodeChar != baseUnicodeChar) {
+ event.mModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL
+ | MODIFIER_META);
+ }
+
+ } else {
+ event.mIsChar = false;
+ event.mCharCode = 0;
+ event.mKeyCode = domKeyCode;
+
+ ANPEvent pluginEvent;
+ pluginEvent.inSize = sizeof(pluginEvent);
+ pluginEvent.eventType = kKey_ANPEventType;
+ pluginEvent.data.key.action = event.mMessage == eKeyDown
+ ? kDown_ANPKeyAction : kUp_ANPKeyAction;
+ pluginEvent.data.key.nativeCode = keyCode;
+ pluginEvent.data.key.virtualCode = domKeyCode;
+ pluginEvent.data.key.unichar = charCode;
+ pluginEvent.data.key.modifiers =
+ (metaState & sdk::KeyEvent::META_SHIFT_MASK
+ ? kShift_ANPKeyModifier : 0) |
+ (metaState & sdk::KeyEvent::META_ALT_MASK
+ ? kAlt_ANPKeyModifier : 0);
+ pluginEvent.data.key.repeatCount = repeatCount;
+ event.mPluginEvent.Copy(pluginEvent);
+ }
+
+ event.mIsRepeat =
+ (event.mMessage == eKeyDown || event.mMessage == eKeyPress) &&
+ ((flags & sdk::KeyEvent::FLAG_LONG_PRESS) || repeatCount);
+
+ event.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(
+ keyCode, action, domPrintableKeyValue);
+ event.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(scanCode);
+
+ if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ domPrintableKeyValue) {
+ event.mKeyValue = char16_t(domPrintableKeyValue);
+ }
+
+ event.mLocation =
+ WidgetKeyboardEvent::ComputeLocationFromCodeValue(event.mCodeNameIndex);
+ event.mTime = time;
+}
+
+void
+nsWindow::GeckoViewSupport::OnKeyEvent(int32_t aAction, int32_t aKeyCode,
+ int32_t aScanCode, int32_t aMetaState, int64_t aTime,
+ int32_t aUnicodeChar, int32_t aBaseUnicodeChar,
+ int32_t aDomPrintableKeyValue, int32_t aRepeatCount, int32_t aFlags,
+ bool aIsSynthesizedImeKey, jni::Object::Param originalEvent)
+{
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+ if (!aIsSynthesizedImeKey) {
+ window.UserActivity();
+ window.RemoveIMEComposition();
+ }
+
+ EventMessage msg;
+ if (aAction == sdk::KeyEvent::ACTION_DOWN) {
+ msg = eKeyDown;
+ } else if (aAction == sdk::KeyEvent::ACTION_UP) {
+ msg = eKeyUp;
+ } else if (aAction == sdk::KeyEvent::ACTION_MULTIPLE) {
+ // Keys with multiple action are handled in Java,
+ // and we should never see one here
+ MOZ_CRASH("Cannot handle key with multiple action");
+ } else {
+ ALOG("Unknown key action event!");
+ return;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetKeyboardEvent event(true, msg, &window);
+ window.InitEvent(event, nullptr);
+ InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+ aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+ aRepeatCount, aFlags);
+
+ if (aIsSynthesizedImeKey) {
+ // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
+ // array until the next IME_REPLACE_TEXT event, at which point
+ // these keys are dispatched in sequence.
+ mIMEKeyEvents.AppendElement(
+ mozilla::UniquePtr<WidgetEvent>(event.Duplicate()));
+
+ } else {
+ window.DispatchEvent(&event, status);
+
+ if (window.Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
+ // Skip default processing.
+ return;
+ }
+
+ mEditable->OnDefaultKeyEvent(originalEvent);
+ }
+
+ if (msg != eKeyDown || IsModifierKey(aKeyCode)) {
+ // Skip sending key press event.
+ return;
+ }
+
+ WidgetKeyboardEvent pressEvent(true, eKeyPress, &window);
+ window.InitEvent(pressEvent, nullptr);
+ InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+ aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+ aRepeatCount, aFlags);
+
+ if (aIsSynthesizedImeKey) {
+ mIMEKeyEvents.AppendElement(
+ mozilla::UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
+ } else {
+ window.DispatchEvent(&pressEvent, status);
+ }
+}
+
+#ifdef DEBUG_ANDROID_IME
+#define ALOGIME(args...) ALOG(args)
+#else
+#define ALOGIME(args...) ((void)0)
+#endif
+
+static nscolor
+ConvertAndroidColor(uint32_t aArgb)
+{
+ return NS_RGBA((aArgb & 0x00ff0000) >> 16,
+ (aArgb & 0x0000ff00) >> 8,
+ (aArgb & 0x000000ff),
+ (aArgb & 0xff000000) >> 24);
+}
+
+/*
+ * Get the current composition object, if any.
+ */
+RefPtr<mozilla::TextComposition>
+nsWindow::GetIMEComposition()
+{
+ MOZ_ASSERT(this == FindTopLevel());
+ return mozilla::IMEStateManager::GetTextCompositionFor(this);
+}
+
+/*
+ Remove the composition but leave the text content as-is
+*/
+void
+nsWindow::RemoveIMEComposition(RemoveIMECompositionFlag aFlag)
+{
+ // Remove composition on Gecko side
+ const RefPtr<mozilla::TextComposition> composition(GetIMEComposition());
+ if (!composition) {
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(this);
+
+ WidgetCompositionEvent compositionCommitEvent(
+ true, eCompositionCommit, this);
+ if (aFlag == COMMIT_IME_COMPOSITION) {
+ compositionCommitEvent.mMessage = eCompositionCommitAsIs;
+ }
+ InitEvent(compositionCommitEvent, nullptr);
+ DispatchEvent(&compositionCommitEvent);
+}
+
+/*
+ * Send dummy key events for pages that are unaware of input events,
+ * to provide web compatibility for pages that depend on key events.
+ * Our dummy key events have 0 as the keycode.
+ */
+void
+nsWindow::GeckoViewSupport::SendIMEDummyKeyEvents()
+{
+ WidgetKeyboardEvent downEvent(true, eKeyDown, &window);
+ window.InitEvent(downEvent, nullptr);
+ MOZ_ASSERT(downEvent.mKeyCode == 0);
+ window.DispatchEvent(&downEvent);
+
+ WidgetKeyboardEvent upEvent(true, eKeyUp, &window);
+ window.InitEvent(upEvent, nullptr);
+ MOZ_ASSERT(upEvent.mKeyCode == 0);
+ window.DispatchEvent(&upEvent);
+}
+
+void
+nsWindow::GeckoViewSupport::AddIMETextChange(const IMETextChange& aChange)
+{
+ mIMETextChanges.AppendElement(aChange);
+
+ // We may not be in the middle of flushing,
+ // in which case this flag is meaningless.
+ mIMETextChangedDuringFlush = true;
+
+ // Now that we added a new range we need to go back and
+ // update all the ranges before that.
+ // Ranges that have offsets which follow this new range
+ // need to be updated to reflect new offsets
+ const int32_t delta = aChange.mNewEnd - aChange.mOldEnd;
+ for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) {
+ IMETextChange& previousChange = mIMETextChanges[i];
+ if (previousChange.mStart > aChange.mOldEnd) {
+ previousChange.mStart += delta;
+ previousChange.mOldEnd += delta;
+ previousChange.mNewEnd += delta;
+ }
+ }
+
+ // Now go through all ranges to merge any ranges that are connected
+ // srcIndex is the index of the range to merge from
+ // dstIndex is the index of the range to potentially merge into
+ int32_t srcIndex = mIMETextChanges.Length() - 1;
+ int32_t dstIndex = srcIndex;
+
+ while (--dstIndex >= 0) {
+ IMETextChange& src = mIMETextChanges[srcIndex];
+ IMETextChange& dst = mIMETextChanges[dstIndex];
+ // When merging a more recent change into an older
+ // change, we need to compare recent change's (start, oldEnd)
+ // range to the older change's (start, newEnd)
+ if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) {
+ // No overlap between ranges
+ continue;
+ }
+ // When merging two ranges, there are generally four posibilities:
+ // [----(----]----), (----[----]----),
+ // [----(----)----], (----[----)----]
+ // where [----] is the first range and (----) is the second range
+ // As seen above, the start of the merged range is always the lesser
+ // of the two start offsets. OldEnd and NewEnd then need to be
+ // adjusted separately depending on the case. In any case, the change
+ // in text length of the merged range should be the sum of text length
+ // changes of the two original ranges, i.e.,
+ // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2
+ dst.mStart = std::min(dst.mStart, src.mStart);
+ if (src.mOldEnd < dst.mNewEnd) {
+ // New range overlaps or is within previous range; merge
+ dst.mNewEnd += src.mNewEnd - src.mOldEnd;
+ } else { // src.mOldEnd >= dst.mNewEnd
+ // New range overlaps previous range; merge
+ dst.mOldEnd += src.mOldEnd - dst.mNewEnd;
+ dst.mNewEnd = src.mNewEnd;
+ }
+ // src merged to dst; delete src.
+ mIMETextChanges.RemoveElementAt(srcIndex);
+ // Any ranges that we skip over between src and dst are not mergeable
+ // so we can safely continue the merge starting at dst
+ srcIndex = dstIndex;
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::PostFlushIMEChanges()
+{
+ if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
+ // Already posted
+ return;
+ }
+
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+
+ nsAppShell::PostEvent([this, window] {
+ if (!window->Destroyed()) {
+ FlushIMEChanges();
+ }
+ });
+}
+
+void
+nsWindow::GeckoViewSupport::FlushIMEChanges(FlushChangesFlag aFlags)
+{
+ // Only send change notifications if we are *not* masking events,
+ // i.e. if we have a focused editor,
+ NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount);
+
+ nsCOMPtr<nsISelection> imeSelection;
+ nsCOMPtr<nsIContent> imeRoot;
+
+ // If we are receiving notifications, we must have selection/root content.
+ MOZ_ALWAYS_SUCCEEDS(IMEStateManager::GetFocusSelectionAndRoot(
+ getter_AddRefs(imeSelection), getter_AddRefs(imeRoot)));
+
+ // Make sure we still have a valid selection/root. We can potentially get
+ // a stale selection/root if the editor becomes hidden, for example.
+ NS_ENSURE_TRUE_VOID(imeRoot->IsInComposedDoc());
+
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+ window.UserActivity();
+
+ struct TextRecord {
+ nsString text;
+ int32_t start;
+ int32_t oldEnd;
+ int32_t newEnd;
+ };
+ AutoTArray<TextRecord, 4> textTransaction;
+ if (mIMETextChanges.Length() > textTransaction.Capacity()) {
+ textTransaction.SetCapacity(mIMETextChanges.Length());
+ }
+
+ mIMETextChangedDuringFlush = false;
+
+ auto shouldAbort = [=] () -> bool {
+ if (!mIMETextChangedDuringFlush) {
+ return false;
+ }
+ // A query event could have triggered more text changes to come in, as
+ // indicated by our flag. If that happens, try flushing IME changes
+ // again.
+ if (aFlags == FLUSH_FLAG_NONE) {
+ FlushIMEChanges(FLUSH_FLAG_RETRY);
+ } else {
+ // Don't retry if already retrying, to avoid infinite loops.
+ __android_log_print(ANDROID_LOG_WARN, "GeckoViewSupport",
+ "Already retrying IME flush");
+ }
+ return true;
+ };
+
+ for (const IMETextChange &change : mIMETextChanges) {
+ if (change.mStart == change.mOldEnd &&
+ change.mStart == change.mNewEnd) {
+ continue;
+ }
+
+ WidgetQueryContentEvent event(true, eQueryTextContent, &window);
+
+ if (change.mNewEnd != change.mStart) {
+ window.InitEvent(event, nullptr);
+ event.InitForQueryTextContent(change.mStart,
+ change.mNewEnd - change.mStart);
+ window.DispatchEvent(&event);
+ NS_ENSURE_TRUE_VOID(event.mSucceeded);
+ NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+ }
+
+ if (shouldAbort()) {
+ return;
+ }
+
+ textTransaction.AppendElement(
+ TextRecord{event.mReply.mString, change.mStart,
+ change.mOldEnd, change.mNewEnd});
+ }
+
+ int32_t selStart = -1;
+ int32_t selEnd = -1;
+
+ if (mIMESelectionChanged) {
+ WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+
+ NS_ENSURE_TRUE_VOID(event.mSucceeded);
+ NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+
+ if (shouldAbort()) {
+ return;
+ }
+
+ selStart = int32_t(event.GetSelectionStart());
+ selEnd = int32_t(event.GetSelectionEnd());
+ }
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ auto flushOnException = [=] () -> bool {
+ if (!env->ExceptionCheck()) {
+ return false;
+ }
+ if (aFlags != FLUSH_FLAG_RECOVER) {
+ // First time seeing an exception; try flushing text.
+ env->ExceptionClear();
+ __android_log_print(ANDROID_LOG_WARN, "GeckoViewSupport",
+ "Recovering from IME exception");
+ FlushIMEText(FLUSH_FLAG_RECOVER);
+ } else {
+ // Give up because we've already tried.
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ }
+ return true;
+ };
+
+ // Commit the text change and selection change transaction.
+ mIMETextChanges.Clear();
+
+ for (const TextRecord& record : textTransaction) {
+ mEditable->OnTextChange(record.text, record.start,
+ record.oldEnd, record.newEnd);
+ if (flushOnException()) {
+ return;
+ }
+ }
+
+ if (mIMESelectionChanged) {
+ mIMESelectionChanged = false;
+ mEditable->OnSelectionChange(selStart, selEnd);
+ flushOnException();
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::FlushIMEText(FlushChangesFlag aFlags)
+{
+ // Notify Java of the newly focused content
+ mIMETextChanges.Clear();
+ mIMESelectionChanged = true;
+
+ // Use 'INT32_MAX / 2' here because subsequent text changes might combine
+ // with this text change, and overflow might occur if we just use
+ // INT32_MAX.
+ IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
+ notification.mTextChangeData.mStartOffset = 0;
+ notification.mTextChangeData.mRemovedEndOffset = INT32_MAX / 2;
+ notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
+ NotifyIME(notification);
+
+ FlushIMEChanges(aFlags);
+}
+
+static jni::ObjectArray::LocalRef
+ConvertRectArrayToJavaRectFArray(JNIEnv* aJNIEnv, const nsTArray<LayoutDeviceIntRect>& aRects, const LayoutDeviceIntPoint& aOffset, const CSSToLayoutDeviceScale aScale)
+{
+ size_t length = aRects.Length();
+ jobjectArray rects = aJNIEnv->NewObjectArray(length, sdk::RectF::Context().ClassRef(), nullptr);
+ auto rectsRef = jni::ObjectArray::LocalRef::Adopt(aJNIEnv, rects);
+ for (size_t i = 0; i < length; i++) {
+ sdk::RectF::LocalRef rect(aJNIEnv);
+ LayoutDeviceIntRect tmp = aRects[i] + aOffset;
+ sdk::RectF::New(tmp.x / aScale.scale, tmp.y / aScale.scale,
+ (tmp.x + tmp.width) / aScale.scale,
+ (tmp.y + tmp.height) / aScale.scale,
+ &rect);
+ rectsRef->SetElement(i, rect);
+ }
+ return rectsRef;
+}
+
+void
+nsWindow::GeckoViewSupport::UpdateCompositionRects()
+{
+ const auto composition(window.GetIMEComposition());
+ if (NS_WARN_IF(!composition)) {
+ return;
+ }
+
+ uint32_t offset = composition->NativeOffsetOfStartComposition();
+ WidgetQueryContentEvent textRects(true, eQueryTextRectArray, &window);
+ textRects.InitForQueryTextRectArray(offset, composition->String().Length());
+ window.DispatchEvent(&textRects);
+
+ auto rects =
+ ConvertRectArrayToJavaRectFArray(jni::GetGeckoThreadEnv(),
+ textRects.mReply.mRectArray,
+ window.WidgetToScreenOffset(),
+ window.GetDefaultScale());
+
+ mEditable->UpdateCompositionRects(rects);
+}
+
+void
+nsWindow::GeckoViewSupport::AsyncNotifyIME(int32_t aNotification)
+{
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+
+ nsAppShell::PostEvent([this, window, aNotification] {
+ if (mIMEMaskEventsCount) {
+ return;
+ }
+
+ mEditable->NotifyIME(aNotification);
+ });
+}
+
+bool
+nsWindow::GeckoViewSupport::NotifyIME(const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(mEditable);
+
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION: {
+ ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
+
+ window.RemoveIMEComposition();
+
+ AsyncNotifyIME(GeckoEditableListener::
+ NOTIFY_IME_TO_COMMIT_COMPOSITION);
+ return true;
+ }
+
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
+
+ window.RemoveIMEComposition(CANCEL_IME_COMPOSITION);
+
+ AsyncNotifyIME(GeckoEditableListener::
+ NOTIFY_IME_TO_CANCEL_COMPOSITION);
+ return true;
+ }
+
+ case NOTIFY_IME_OF_FOCUS: {
+ ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+
+ // Post an event because we have to flush the text before sending a
+ // focus event, and we may not be able to flush text during the
+ // NotifyIME call.
+ nsAppShell::PostEvent([this, window] {
+ --mIMEMaskEventsCount;
+ if (mIMEMaskEventsCount || window->Destroyed()) {
+ return;
+ }
+
+ FlushIMEText();
+
+ // IME will call requestCursorUpdates after getting context.
+ // So reset cursor update mode before getting context.
+ mIMEMonitorCursor = false;
+
+ MOZ_ASSERT(mEditable);
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_FOCUS);
+ });
+ return true;
+ }
+
+ case NOTIFY_IME_OF_BLUR: {
+ ALOGIME("IME: NOTIFY_IME_OF_BLUR");
+
+ if (!mIMEMaskEventsCount) {
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_BLUR);
+ }
+
+ // Mask events because we lost focus. Unmask on the next focus.
+ mIMEMaskEventsCount++;
+ return true;
+ }
+
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE");
+
+ PostFlushIMEChanges();
+ mIMESelectionChanged = true;
+ return true;
+ }
+
+ case NOTIFY_IME_OF_TEXT_CHANGE: {
+ ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d",
+ aIMENotification.mTextChangeData.mStartOffset,
+ aIMENotification.mTextChangeData.mRemovedEndOffset,
+ aIMENotification.mTextChangeData.mAddedEndOffset);
+
+ /* Make sure Java's selection is up-to-date */
+ PostFlushIMEChanges();
+ mIMESelectionChanged = true;
+ AddIMETextChange(IMETextChange(aIMENotification));
+ return true;
+ }
+
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
+ ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
+
+ // Hardware keyboard support requires each string rect.
+ if (AndroidBridge::Bridge() && AndroidBridge::Bridge()->GetAPIVersion() >= 21 && mIMEMonitorCursor) {
+ UpdateCompositionRects();
+ }
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ MOZ_ASSERT(mEditable);
+
+ ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X",
+ aContext.mIMEState.mEnabled, aContext.mIMEState.mOpen,
+ aAction.mCause, aAction.mFocusChange);
+
+ // Ensure that opening the virtual keyboard is allowed for this specific
+ // InputContext depending on the content.ime.strict.policy pref
+ if (aContext.mIMEState.mEnabled != IMEState::DISABLED &&
+ aContext.mIMEState.mEnabled != IMEState::PLUGIN &&
+ Preferences::GetBool("content.ime.strict_policy", false) &&
+ !aAction.ContentGotFocusByTrustedCause() &&
+ !aAction.UserMightRequestOpenVKB()) {
+ return;
+ }
+
+ IMEState::Enabled enabled = aContext.mIMEState.mEnabled;
+
+ // Only show the virtual keyboard for plugins if mOpen is set appropriately.
+ // This avoids showing it whenever a plugin is focused. Bug 747492
+ if (aContext.mIMEState.mEnabled == IMEState::PLUGIN &&
+ aContext.mIMEState.mOpen != IMEState::OPEN) {
+ enabled = IMEState::DISABLED;
+ }
+
+ mInputContext = aContext;
+ mInputContext.mIMEState.mEnabled = enabled;
+
+ if (enabled == IMEState::ENABLED && aAction.UserMightRequestOpenVKB()) {
+ // Don't reset keyboard when we should simply open the vkb
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OPEN_VKB);
+ return;
+ }
+
+ if (mIMEUpdatingContext) {
+ return;
+ }
+
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+ mIMEUpdatingContext = true;
+
+ nsAppShell::PostEvent([this, window] {
+ mIMEUpdatingContext = false;
+ if (window->Destroyed()) {
+ return;
+ }
+ MOZ_ASSERT(mEditable);
+ mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
+ mInputContext.mHTMLInputType,
+ mInputContext.mHTMLInputInputmode,
+ mInputContext.mActionHint);
+ });
+}
+
+InputContext
+nsWindow::GeckoViewSupport::GetInputContext()
+{
+ InputContext context = mInputContext;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ return context;
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeSynchronize()
+{
+ if (!mIMEMaskEventsCount) {
+ FlushIMEChanges();
+ }
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeReplaceText(int32_t aStart, int32_t aEnd,
+ jni::String::Param aText)
+{
+ AutoIMESynchronize as(this);
+
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused; still reply to events, but don't do anything else.
+ return;
+ }
+
+ /*
+ Replace text in Gecko thread from aStart to aEnd with the string text.
+ */
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+ nsString string(aText->ToString());
+
+ const auto composition(window.GetIMEComposition());
+ MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+ const bool composing = !mIMERanges->IsEmpty();
+
+ if (!mIMEKeyEvents.IsEmpty() || !composition ||
+ uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+ uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+ composition->String().Length())
+ {
+ // Only start a new composition if we have key events,
+ // if we don't have an existing composition, or
+ // the replaced text does not match our composition.
+ window.RemoveIMEComposition();
+
+ {
+ // Use text selection to set target postion(s) for
+ // insert, or replace, of text.
+ WidgetSelectionEvent event(true, eSetSelection, &window);
+ window.InitEvent(event, nullptr);
+ event.mOffset = uint32_t(aStart);
+ event.mLength = uint32_t(aEnd - aStart);
+ event.mExpandToClusterBoundary = false;
+ event.mReason = nsISelectionListener::IME_REASON;
+ window.DispatchEvent(&event);
+ }
+
+ if (!mIMEKeyEvents.IsEmpty()) {
+ nsEventStatus status;
+ for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
+ const auto event = static_cast<WidgetGUIEvent*>(
+ mIMEKeyEvents[i].get());
+ if (event->mMessage == eKeyPress &&
+ status == nsEventStatus_eConsumeNoDefault) {
+ MOZ_ASSERT(i > 0 &&
+ mIMEKeyEvents[i - 1]->mMessage == eKeyDown);
+ // The previous key down event resulted in eConsumeNoDefault
+ // so we should not dispatch the current key press event.
+ continue;
+ }
+ // widget for duplicated events is initially nullptr.
+ event->mWidget = &window;
+ window.DispatchEvent(event, status);
+ }
+ mIMEKeyEvents.Clear();
+ return;
+ }
+
+ if (aStart != aEnd) {
+ // Perform a deletion first.
+ WidgetContentCommandEvent event(
+ true, eContentCommandDelete, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+ }
+
+ // Start a composition if we're not just performing a deletion.
+ if (composing || !string.IsEmpty()) {
+ WidgetCompositionEvent event(true, eCompositionStart, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+ }
+
+ } else if (composition->String().Equals(string)) {
+ /* If the new text is the same as the existing composition text,
+ * the NS_COMPOSITION_CHANGE event does not generate a text
+ * change notification. However, the Java side still expects
+ * one, so we manually generate a notification. */
+ IMETextChange dummyChange;
+ dummyChange.mStart = aStart;
+ dummyChange.mOldEnd = dummyChange.mNewEnd = aEnd;
+ AddIMETextChange(dummyChange);
+ }
+
+ // Check composition again because previous events may have destroyed our
+ // composition; in which case we should just skip the next event.
+ if (window.GetIMEComposition()) {
+ WidgetCompositionEvent event(true, eCompositionChange, &window);
+ window.InitEvent(event, nullptr);
+ event.mData = string;
+
+ if (composing) {
+ event.mRanges = new TextRangeArray();
+ mIMERanges.swap(event.mRanges);
+ } else {
+ event.mMessage = eCompositionCommit;
+ }
+
+ window.DispatchEvent(&event);
+
+ } else if (composing) {
+ // Ensure IME ranges are empty.
+ mIMERanges->Clear();
+ }
+
+ if (mInputContext.mMayBeIMEUnaware) {
+ SendIMEDummyKeyEvents();
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeAddCompositionRange(
+ int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle,
+ int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor,
+ int32_t aRangeBackColor, int32_t aRangeLineColor)
+{
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return;
+ }
+
+ TextRange range;
+ range.mStartOffset = aStart;
+ range.mEndOffset = aEnd;
+ range.mRangeType = ToTextRangeType(aRangeType);
+ range.mRangeStyle.mDefinedStyles = aRangeStyle;
+ range.mRangeStyle.mLineStyle = aRangeLineStyle;
+ range.mRangeStyle.mIsBoldLine = aRangeBoldLine;
+ range.mRangeStyle.mForegroundColor =
+ ConvertAndroidColor(uint32_t(aRangeForeColor));
+ range.mRangeStyle.mBackgroundColor =
+ ConvertAndroidColor(uint32_t(aRangeBackColor));
+ range.mRangeStyle.mUnderlineColor =
+ ConvertAndroidColor(uint32_t(aRangeLineColor));
+ mIMERanges->AppendElement(range);
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd)
+{
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+
+ // A composition with no ranges means we want to set the selection.
+ if (mIMERanges->IsEmpty()) {
+ MOZ_ASSERT(aStart >= 0 && aEnd >= 0);
+ window.RemoveIMEComposition();
+
+ WidgetSelectionEvent selEvent(true, eSetSelection, &window);
+ window.InitEvent(selEvent, nullptr);
+
+ selEvent.mOffset = std::min(aStart, aEnd);
+ selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
+ selEvent.mReversed = aStart > aEnd;
+ selEvent.mExpandToClusterBoundary = false;
+
+ window.DispatchEvent(&selEvent);
+ return;
+ }
+
+ /*
+ Update the composition from aStart to aEnd using
+ information from added ranges. This is only used for
+ visual indication and does not affect the text content.
+ Only the offsets are specified and not the text content
+ to eliminate the possibility of this event altering the
+ text content unintentionally.
+ */
+ const auto composition(window.GetIMEComposition());
+ MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+ WidgetCompositionEvent event(true, eCompositionChange, &window);
+ window.InitEvent(event, nullptr);
+
+ event.mRanges = new TextRangeArray();
+ mIMERanges.swap(event.mRanges);
+
+ if (!composition ||
+ uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+ uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+ composition->String().Length())
+ {
+ // Only start new composition if we don't have an existing one,
+ // or if the existing composition doesn't match the new one.
+ window.RemoveIMEComposition();
+
+ {
+ WidgetSelectionEvent event(true, eSetSelection, &window);
+ window.InitEvent(event, nullptr);
+ event.mOffset = uint32_t(aStart);
+ event.mLength = uint32_t(aEnd - aStart);
+ event.mExpandToClusterBoundary = false;
+ event.mReason = nsISelectionListener::IME_REASON;
+ window.DispatchEvent(&event);
+ }
+
+ {
+ WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
+ &window);
+ window.InitEvent(queryEvent, nullptr);
+ window.DispatchEvent(&queryEvent);
+ MOZ_ASSERT(queryEvent.mSucceeded);
+ event.mData = queryEvent.mReply.mString;
+ }
+
+ {
+ WidgetCompositionEvent event(true, eCompositionStart, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+ }
+
+ } else {
+ // If the new composition matches the existing composition,
+ // reuse the old composition.
+ event.mData = composition->String();
+ }
+
+#ifdef DEBUG_ANDROID_IME
+ const NS_ConvertUTF16toUTF8 data(event.mData);
+ const char* text = data.get();
+ ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
+ text, event.mData.Length(), event.mRanges->Length());
+#endif // DEBUG_ANDROID_IME
+
+ // Previous events may have destroyed our composition; bail in that case.
+ if (window.GetIMEComposition()) {
+ window.DispatchEvent(&event);
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeRequestCursorUpdates(int aRequestMode)
+{
+ if (aRequestMode == IME_MONITOR_CURSOR_ONE_SHOT) {
+ UpdateCompositionRects();
+ return;
+ }
+
+ mIMEMonitorCursor = (aRequestMode == IME_MONITOR_CURSOR_START_MONITOR);
+}
+
+void
+nsWindow::UserActivity()
+{
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
+ }
+
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+nsresult
+nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(this == FindTopLevel());
+
+ if (!mGeckoViewSupport) {
+ // Non-GeckoView windows don't support IME operations.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mGeckoViewSupport->NotifyIME(aIMENotification)) {
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ if (!top->mGeckoViewSupport) {
+ // 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
+ top->mGeckoViewSupport->SetInputContext(aContext, aAction);
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ if (!top->mGeckoViewSupport) {
+ // 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 top->mGeckoViewSupport->GetInputContext();
+}
+
+nsIMEUpdatePreference
+nsWindow::GetIMEUpdatePreference()
+{
+ // While a plugin has focus, nsWindow for Android doesn't need any
+ // notifications.
+ if (GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
+ return nsIMEUpdatePreference();
+ }
+ return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
+}
+
+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 = sdk::MotionEvent::ACTION_POINTER_DOWN;
+ break;
+ case TOUCH_REMOVE:
+ // This could be turned into a ACTION_UP in Java
+ eventType = sdk::MotionEvent::ACTION_POINTER_UP;
+ break;
+ case TOUCH_CANCEL:
+ eventType = sdk::MotionEvent::ACTION_CANCEL;
+ break;
+ case TOUCH_HOVER: // not supported for now
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mLayerViewSupport);
+ GeckoLayerClient::LocalRef client = mLayerViewSupport->GetLayerClient();
+ client->SynthesizeNativeTouchPoint(aPointerId, eventType,
+ aPoint.x, aPoint.y, aPointerPressure, aPointerOrientation);
+
+ return NS_OK;
+}
+
+nsresult
+nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver)
+{
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ MOZ_ASSERT(mLayerViewSupport);
+ GeckoLayerClient::LocalRef client = mLayerViewSupport->GetLayerClient();
+ client->SynthesizeNativeMouseEvent(aNativeMessage, aPoint.x, aPoint.y);
+
+ return NS_OK;
+}
+
+nsresult
+nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver)
+{
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ MOZ_ASSERT(mLayerViewSupport);
+ GeckoLayerClient::LocalRef client = mLayerViewSupport->GetLayerClient();
+ client->SynthesizeNativeMouseEvent(sdk::MotionEvent::ACTION_HOVER_MOVE, aPoint.x, aPoint.y);
+
+ return NS_OK;
+}
+
+bool
+nsWindow::PreRender(WidgetRenderingContext* aContext)
+{
+ if (Destroyed()) {
+ return true;
+ }
+
+ layers::Compositor* compositor = aContext->mCompositor;
+
+ GeckoLayerClient::LocalRef client;
+
+ if (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ client = lvs->GetLayerClient();
+ }
+
+ if (compositor && client) {
+ // Android Color is ARGB which is apparently unusual.
+ compositor->SetDefaultClearColor(gfx::Color::UnusualFromARGB((uint32_t)client->ClearColor()));
+ }
+
+ return true;
+}
+void
+nsWindow::DrawWindowUnderlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ if (Destroyed()) {
+ return;
+ }
+
+ GeckoLayerClient::LocalRef client;
+
+ if (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ client = lvs->GetLayerClient();
+ }
+
+ if (!client) {
+ return;
+ }
+
+ LayerRenderer::Frame::LocalRef frame = client->CreateFrame();
+ mLayerRendererFrame = frame;
+ if (NS_WARN_IF(!mLayerRendererFrame)) {
+ return;
+ }
+
+ if (!WidgetPaintsBackground()) {
+ return;
+ }
+
+ frame->BeginDrawing();
+}
+
+void
+nsWindow::DrawWindowOverlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ PROFILER_LABEL("nsWindow", "DrawWindowOverlay",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ if (Destroyed() || NS_WARN_IF(!mLayerRendererFrame)) {
+ return;
+ }
+
+ mLayerRendererFrame->EndDrawing();
+ mLayerRendererFrame = nullptr;
+}
+
+bool
+nsWindow::WidgetPaintsBackground()
+{
+ static bool sWidgetPaintsBackground = true;
+ static bool sWidgetPaintsBackgroundPrefCached = false;
+
+ if (!sWidgetPaintsBackgroundPrefCached) {
+ sWidgetPaintsBackgroundPrefCached = true;
+ mozilla::Preferences::AddBoolVarCache(&sWidgetPaintsBackground,
+ "android.widget_paints_background",
+ true);
+ }
+
+ return sWidgetPaintsBackground;
+}
+
+bool
+nsWindow::NeedsPaint()
+{
+ if (!mLayerViewSupport || mLayerViewSupport->CompositorPaused() ||
+ // FindTopLevel() != nsWindow::TopWindow() ||
+ !GetLayerManager(nullptr)) {
+ return false;
+ }
+ return nsIWidget::NeedsPaint();
+}
+
+void
+nsWindow::ConfigureAPZControllerThread()
+{
+ APZThreadUtils::SetControllerThread(nullptr);
+}
+
+already_AddRefed<GeckoContentController>
+nsWindow::CreateRootContentController()
+{
+ RefPtr<GeckoContentController> controller = new AndroidContentController(this, mAPZEventState, mAPZC);
+ return controller.forget();
+}
+
+uint32_t
+nsWindow::GetMaxTouchPoints() const
+{
+ return GeckoAppShell::GetMaxTouchPoints();
+}
+
+void
+nsWindow::UpdateZoomConstraints(const uint32_t& aPresShellId,
+ const FrameMetrics::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints)
+{
+ nsBaseWidget::UpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
+}
+
+CompositorBridgeParent*
+nsWindow::GetCompositorBridgeParent() const
+{
+ return mCompositorSession ? mCompositorSession->GetInProcessBridge() : nullptr;
+}
+
+already_AddRefed<nsIScreen>
+nsWindow::GetWidgetScreen()
+{
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ MOZ_ASSERT(screenMgr, "Failed to get nsIScreenManager");
+
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForId(mScreenId, getter_AddRefs(screen));
+
+ return screen.forget();
+}
+
+jni::DependentRef<java::GeckoLayerClient>
+nsWindow::GetLayerClient()
+{
+ if (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ return lvs->GetLayerClient().Get();
+ }
+ return nullptr;
+}