michael@0: /* -*- Mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "mozilla/MiscEvents.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/TouchEvents.h" michael@0: michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "mozilla/unused.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/layers/RenderTrace.h" michael@0: #include michael@0: michael@0: using mozilla::dom::ContentParent; michael@0: using mozilla::dom::ContentChild; michael@0: using mozilla::unused; michael@0: michael@0: #include "nsAppShell.h" michael@0: #include "nsIdleService.h" michael@0: #include "nsWindow.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsIWidgetListener.h" michael@0: #include "nsViewManager.h" michael@0: michael@0: #include "nsRenderingContext.h" michael@0: #include "nsIDOMSimpleGestureEvent.h" michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsGfxCIID.h" michael@0: michael@0: #include "gfxImageSurface.h" michael@0: #include "gfxContext.h" michael@0: michael@0: #include "Layers.h" michael@0: #include "mozilla/layers/LayerManagerComposite.h" michael@0: #include "mozilla/layers/AsyncCompositionManager.h" michael@0: #include "mozilla/layers/APZCTreeManager.h" michael@0: #include "GLContext.h" michael@0: #include "GLContextProvider.h" michael@0: #include "ScopedGLHelpers.h" michael@0: #include "mozilla/layers/CompositorOGL.h" michael@0: michael@0: #include "nsTArray.h" michael@0: michael@0: #include "AndroidBridge.h" michael@0: #include "AndroidBridgeUtilities.h" michael@0: #include "android_npapi.h" michael@0: michael@0: #include "imgIEncoder.h" michael@0: michael@0: #include "nsString.h" michael@0: #include "GeckoProfiler.h" // For PROFILER_LABEL michael@0: #include "nsIXULRuntime.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::widget; michael@0: using namespace mozilla::layers; michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget) michael@0: michael@0: // The dimensions of the current android view michael@0: static gfxIntSize gAndroidBounds = gfxIntSize(0, 0); michael@0: static gfxIntSize gAndroidScreenBounds; michael@0: michael@0: #include "mozilla/layers/AsyncPanZoomController.h" michael@0: #include "mozilla/layers/CompositorChild.h" michael@0: #include "mozilla/layers/CompositorParent.h" michael@0: #include "mozilla/layers/LayerTransactionParent.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: class ContentCreationNotifier; michael@0: static StaticRefPtr gContentCreationNotifier; michael@0: michael@0: // A helper class to send updates when content processes michael@0: // are created. Currently an update for the screen size is sent. michael@0: class ContentCreationNotifier MOZ_FINAL : public nsIObserver michael@0: { michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!strcmp(aTopic, "ipc:content-created")) { michael@0: nsCOMPtr cpo = do_QueryInterface(aSubject); michael@0: ContentParent* cp = static_cast(cpo.get()); michael@0: unused << cp->SendScreenSizeChanged(gAndroidScreenBounds); michael@0: } else if (!strcmp(aTopic, "xpcom-shutdown")) { michael@0: nsCOMPtr michael@0: obs(do_GetService("@mozilla.org/observer-service;1")); michael@0: if (obs) { michael@0: obs->RemoveObserver(static_cast(this), michael@0: "xpcom-shutdown"); michael@0: obs->RemoveObserver(static_cast(this), michael@0: "ipc:content-created"); michael@0: } michael@0: gContentCreationNotifier = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ContentCreationNotifier, michael@0: nsIObserver) michael@0: michael@0: static bool gMenu; michael@0: static bool gMenuConsumed; michael@0: michael@0: // All the toplevel windows that have been created; these are in michael@0: // stacking order, so the window at gAndroidBounds[0] is the topmost michael@0: // one. michael@0: static nsTArray gTopLevelWindows; michael@0: michael@0: static bool sFailedToCreateGLContext = false; michael@0: michael@0: // Multitouch swipe thresholds in inches michael@0: static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4; michael@0: static const double SWIPE_MIN_DISTANCE_INCHES = 0.6; michael@0: michael@0: nsWindow* michael@0: nsWindow::TopWindow() michael@0: { michael@0: if (!gTopLevelWindows.IsEmpty()) michael@0: return gTopLevelWindows[0]; michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsWindow::LogWindow(nsWindow *win, int index, int indent) michael@0: { michael@0: #if defined(DEBUG) || defined(FORCE_ALOG) michael@0: char spaces[] = " "; michael@0: spaces[indent < 20 ? indent : 20] = 0; michael@0: ALOG("%s [% 2d] 0x%08x [parent 0x%08x] [% 3d,% 3dx% 3d,% 3d] vis %d type %d", michael@0: spaces, index, (intptr_t)win, (intptr_t)win->mParent, michael@0: win->mBounds.x, win->mBounds.y, michael@0: win->mBounds.width, win->mBounds.height, michael@0: win->mIsVisible, win->mWindowType); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: nsWindow::DumpWindows() michael@0: { michael@0: DumpWindows(gTopLevelWindows); michael@0: } michael@0: michael@0: void michael@0: nsWindow::DumpWindows(const nsTArray& wins, int indent) michael@0: { michael@0: for (uint32_t i = 0; i < wins.Length(); ++i) { michael@0: nsWindow *w = wins[i]; michael@0: LogWindow(w, i, indent); michael@0: DumpWindows(w->mChildren, indent+1); michael@0: } michael@0: } michael@0: michael@0: nsWindow::nsWindow() : michael@0: mIsVisible(false), michael@0: mParent(nullptr), michael@0: mFocus(nullptr), michael@0: mIMEComposing(false), michael@0: mIMEMaskSelectionUpdate(false), michael@0: mIMEMaskTextUpdate(false), michael@0: mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet michael@0: mIMERanges(new TextRangeArray()), michael@0: mIMEUpdatingContext(false), michael@0: mIMESelectionChanged(false) michael@0: { michael@0: } michael@0: michael@0: nsWindow::~nsWindow() michael@0: { michael@0: gTopLevelWindows.RemoveElement(this); michael@0: nsWindow *top = FindTopLevel(); michael@0: if (top->mFocus == this) michael@0: top->mFocus = nullptr; michael@0: ALOG("nsWindow %p destructor", (void*)this); michael@0: if (mLayerManager == sLayerManager) { michael@0: // If this window was the one that created the global OMTC layer manager michael@0: // and compositor, then we should null those out. michael@0: SetCompositor(nullptr, nullptr, nullptr); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsWindow::IsTopLevel() michael@0: { michael@0: return mWindowType == eWindowType_toplevel || michael@0: mWindowType == eWindowType_dialog || michael@0: mWindowType == eWindowType_invisible; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Create(nsIWidget *aParent, michael@0: nsNativeWidget aNativeParent, michael@0: const nsIntRect &aRect, michael@0: nsDeviceContext *aContext, michael@0: nsWidgetInitData *aInitData) michael@0: { michael@0: ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent, aRect.x, aRect.y, aRect.width, aRect.height); michael@0: nsWindow *parent = (nsWindow*) aParent; michael@0: if (aNativeParent) { michael@0: if (parent) { michael@0: ALOG("Ignoring native parent on Android window [%p], since parent was specified (%p %p)", (void*)this, (void*)aNativeParent, (void*)aParent); michael@0: } else { michael@0: parent = (nsWindow*) aNativeParent; michael@0: } michael@0: } michael@0: michael@0: mBounds = aRect; michael@0: michael@0: // for toplevel windows, bounds are fixed to full screen size michael@0: if (!parent) { michael@0: mBounds.x = 0; michael@0: mBounds.y = 0; michael@0: mBounds.width = gAndroidBounds.width; michael@0: mBounds.height = gAndroidBounds.height; michael@0: } michael@0: michael@0: BaseCreate(nullptr, mBounds, aContext, aInitData); michael@0: michael@0: NS_ASSERTION(IsTopLevel() || parent, "non top level windowdoesn't have a parent!"); michael@0: michael@0: if (IsTopLevel()) { michael@0: gTopLevelWindows.AppendElement(this); michael@0: } michael@0: michael@0: if (parent) { michael@0: parent->mChildren.AppendElement(this); michael@0: mParent = parent; michael@0: } michael@0: michael@0: #ifdef DEBUG_ANDROID_WIDGET michael@0: DumpWindows(); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Destroy(void) michael@0: { michael@0: nsBaseWidget::mOnDestroyCalled = true; michael@0: michael@0: while (mChildren.Length()) { michael@0: // why do we still have children? michael@0: ALOG("### Warning: Destroying window %p and reparenting child %p to null!", (void*)this, (void*)mChildren[0]); michael@0: mChildren[0]->SetParent(nullptr); michael@0: } michael@0: michael@0: if (IsTopLevel()) michael@0: gTopLevelWindows.RemoveElement(this); michael@0: michael@0: SetParent(nullptr); michael@0: michael@0: nsBaseWidget::OnDestroy(); michael@0: michael@0: #ifdef DEBUG_ANDROID_WIDGET michael@0: DumpWindows(); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::ConfigureChildren(const nsTArray& config) michael@0: { michael@0: for (uint32_t i = 0; i < config.Length(); ++i) { michael@0: nsWindow *childWin = (nsWindow*) config[i].mChild; michael@0: childWin->Resize(config[i].mBounds.x, michael@0: config[i].mBounds.y, michael@0: config[i].mBounds.width, michael@0: config[i].mBounds.height, michael@0: false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsWindow::RedrawAll() michael@0: { michael@0: if (mFocus && mFocus->mWidgetListener) { michael@0: mFocus->mWidgetListener->RequestRepaint(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::SetParent(nsIWidget *aNewParent) michael@0: { michael@0: if ((nsIWidget*)mParent == aNewParent) michael@0: return NS_OK; michael@0: michael@0: // If we had a parent before, remove ourselves from its list of michael@0: // children. michael@0: if (mParent) michael@0: mParent->mChildren.RemoveElement(this); michael@0: michael@0: mParent = (nsWindow*)aNewParent; michael@0: michael@0: if (mParent) michael@0: mParent->mChildren.AppendElement(this); michael@0: michael@0: // if we are now in the toplevel window's hierarchy, schedule a redraw michael@0: if (FindTopLevel() == nsWindow::TopWindow()) michael@0: RedrawAll(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::ReparentNativeWidget(nsIWidget *aNewParent) michael@0: { michael@0: NS_PRECONDITION(aNewParent, ""); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIWidget* michael@0: nsWindow::GetParent() michael@0: { michael@0: return mParent; michael@0: } michael@0: michael@0: float michael@0: nsWindow::GetDPI() michael@0: { michael@0: if (AndroidBridge::Bridge()) michael@0: return AndroidBridge::Bridge()->GetDPI(); michael@0: return 160.0f; michael@0: } michael@0: michael@0: double michael@0: nsWindow::GetDefaultScaleInternal() michael@0: { michael@0: static double density = 0.0; michael@0: michael@0: if (density != 0.0) { michael@0: return density; michael@0: } michael@0: michael@0: density = mozilla::widget::android::GeckoAppShell::GetDensity(); michael@0: michael@0: if (!density) { michael@0: density = 1.0; michael@0: } michael@0: michael@0: return density; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Show(bool aState) michael@0: { michael@0: ALOG("nsWindow[%p]::Show %d", (void*)this, aState); michael@0: michael@0: if (mWindowType == eWindowType_invisible) { michael@0: ALOG("trying to show invisible window! ignoring.."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (aState == mIsVisible) michael@0: return NS_OK; michael@0: michael@0: mIsVisible = aState; michael@0: michael@0: if (IsTopLevel()) { michael@0: // XXX should we bring this to the front when it's shown, michael@0: // if it's a toplevel widget? michael@0: michael@0: // XXX we should synthesize a NS_MOUSE_EXIT (for old top michael@0: // window)/NS_MOUSE_ENTER (for new top window) since we need michael@0: // to pretend that the top window always has focus. Not sure michael@0: // if Show() is the right place to do this, though. michael@0: michael@0: if (aState) { michael@0: // It just became visible, so send a resize update if necessary michael@0: // and bring it to the front. michael@0: Resize(0, 0, gAndroidBounds.width, gAndroidBounds.height, false); michael@0: BringToFront(); michael@0: } else if (nsWindow::TopWindow() == this) { michael@0: // find the next visible window to show michael@0: unsigned int i; michael@0: for (i = 1; i < gTopLevelWindows.Length(); i++) { michael@0: nsWindow *win = gTopLevelWindows[i]; michael@0: if (!win->mIsVisible) michael@0: continue; michael@0: michael@0: win->BringToFront(); michael@0: break; michael@0: } michael@0: } michael@0: } else if (FindTopLevel() == nsWindow::TopWindow()) { michael@0: RedrawAll(); michael@0: } michael@0: michael@0: #ifdef DEBUG_ANDROID_WIDGET michael@0: DumpWindows(); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::SetModal(bool aState) michael@0: { michael@0: ALOG("nsWindow[%p]::SetModal %d ignored", (void*)this, aState); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsWindow::IsVisible() const michael@0: { michael@0: return mIsVisible; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::ConstrainPosition(bool aAllowSlop, michael@0: int32_t *aX, michael@0: int32_t *aY) michael@0: { michael@0: ALOG("nsWindow[%p]::ConstrainPosition %d [%d %d]", (void*)this, aAllowSlop, *aX, *aY); michael@0: michael@0: // constrain toplevel windows; children we don't care about michael@0: if (IsTopLevel()) { michael@0: *aX = 0; michael@0: *aY = 0; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Move(double aX, michael@0: double aY) michael@0: { michael@0: if (IsTopLevel()) michael@0: return NS_OK; michael@0: michael@0: return Resize(aX, michael@0: aY, michael@0: mBounds.width, michael@0: mBounds.height, michael@0: true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Resize(double aWidth, michael@0: double aHeight, michael@0: bool aRepaint) michael@0: { michael@0: return Resize(mBounds.x, michael@0: mBounds.y, michael@0: aWidth, michael@0: aHeight, michael@0: aRepaint); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Resize(double aX, michael@0: double aY, michael@0: double aWidth, michael@0: double aHeight, michael@0: bool aRepaint) michael@0: { michael@0: ALOG("nsWindow[%p]::Resize [%f %f %f %f] (repaint %d)", (void*)this, aX, aY, aWidth, aHeight, aRepaint); michael@0: michael@0: bool needSizeDispatch = aWidth != mBounds.width || aHeight != mBounds.height; michael@0: michael@0: mBounds.x = NSToIntRound(aX); michael@0: mBounds.y = NSToIntRound(aY); michael@0: mBounds.width = NSToIntRound(aWidth); michael@0: mBounds.height = NSToIntRound(aHeight); michael@0: michael@0: if (needSizeDispatch) michael@0: OnSizeChanged(gfxIntSize(aWidth, aHeight)); michael@0: michael@0: // Should we skip honoring aRepaint here? michael@0: if (aRepaint && FindTopLevel() == nsWindow::TopWindow()) michael@0: RedrawAll(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsWindow::SetZIndex(int32_t aZIndex) michael@0: { michael@0: ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, michael@0: nsIWidget *aWidget, michael@0: bool aActivate) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::SetSizeMode(int32_t aMode) michael@0: { michael@0: switch (aMode) { michael@0: case nsSizeMode_Minimized: michael@0: mozilla::widget::android::GeckoAppShell::MoveTaskToBack(); michael@0: break; michael@0: case nsSizeMode_Fullscreen: michael@0: MakeFullScreen(true); michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Enable(bool aState) michael@0: { michael@0: ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsWindow::IsEnabled() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::Invalidate(const nsIntRect &aRect) michael@0: { michael@0: AndroidGeckoEvent *event = AndroidGeckoEvent::MakeDrawEvent(aRect); michael@0: nsAppShell::gAppShell->PostEvent(event); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsWindow* michael@0: nsWindow::FindTopLevel() michael@0: { michael@0: nsWindow *toplevel = this; michael@0: while (toplevel) { michael@0: if (toplevel->IsTopLevel()) michael@0: return toplevel; michael@0: michael@0: toplevel = toplevel->mParent; michael@0: } michael@0: michael@0: ALOG("nsWindow::FindTopLevel(): couldn't find a toplevel or dialog window in this [%p] widget's hierarchy!", (void*)this); michael@0: return this; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::SetFocus(bool aRaise) michael@0: { michael@0: if (!aRaise) { michael@0: ALOG("nsWindow::SetFocus: can't set focus without raising, ignoring aRaise = false!"); michael@0: } michael@0: michael@0: nsWindow *top = FindTopLevel(); michael@0: top->mFocus = this; michael@0: top->BringToFront(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsWindow::BringToFront() michael@0: { michael@0: // If the window to be raised is the same as the currently raised one, michael@0: // do nothing. We need to check the focus manager as well, as the first michael@0: // window that is created will be first in the window list but won't yet michael@0: // be focused. michael@0: nsCOMPtr fm = do_GetService(FOCUSMANAGER_CONTRACTID); michael@0: nsCOMPtr existingTopWindow; michael@0: fm->GetActiveWindow(getter_AddRefs(existingTopWindow)); michael@0: if (existingTopWindow && FindTopLevel() == nsWindow::TopWindow()) michael@0: return; michael@0: michael@0: if (!IsTopLevel()) { michael@0: FindTopLevel()->BringToFront(); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: nsWindow *oldTop = nullptr; michael@0: nsWindow *newTop = this; michael@0: if (!gTopLevelWindows.IsEmpty()) michael@0: oldTop = gTopLevelWindows[0]; michael@0: michael@0: gTopLevelWindows.RemoveElement(this); michael@0: gTopLevelWindows.InsertElementAt(0, this); michael@0: michael@0: if (oldTop) { michael@0: nsIWidgetListener* listener = oldTop->GetWidgetListener(); michael@0: if (listener) { michael@0: listener->WindowDeactivated(); michael@0: } michael@0: } michael@0: michael@0: if (Destroyed()) { michael@0: // somehow the deactivate event handler destroyed this window. michael@0: // try to recover by grabbing the next window in line and activating michael@0: // that instead michael@0: if (gTopLevelWindows.IsEmpty()) michael@0: return; michael@0: newTop = gTopLevelWindows[0]; michael@0: } michael@0: michael@0: if (mWidgetListener) { michael@0: mWidgetListener->WindowActivated(); michael@0: } michael@0: michael@0: // force a window resize michael@0: nsAppShell::gAppShell->ResendLastResizeEvent(newTop); michael@0: RedrawAll(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::GetScreenBounds(nsIntRect &aRect) michael@0: { michael@0: nsIntPoint p = WidgetToScreenOffset(); michael@0: michael@0: aRect.x = p.x; michael@0: aRect.y = p.y; michael@0: aRect.width = mBounds.width; michael@0: aRect.height = mBounds.height; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIntPoint michael@0: nsWindow::WidgetToScreenOffset() michael@0: { michael@0: nsIntPoint p(0, 0); michael@0: nsWindow *w = this; michael@0: michael@0: while (w && !w->IsTopLevel()) { michael@0: p.x += w->mBounds.x; michael@0: p.y += w->mBounds.y; michael@0: michael@0: w = w->mParent; michael@0: } michael@0: michael@0: return p; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::DispatchEvent(WidgetGUIEvent* aEvent, michael@0: nsEventStatus &aStatus) michael@0: { michael@0: aStatus = DispatchEvent(aEvent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsEventStatus michael@0: nsWindow::DispatchEvent(WidgetGUIEvent* aEvent) michael@0: { michael@0: if (mWidgetListener) { michael@0: nsEventStatus status = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents); michael@0: michael@0: switch (aEvent->message) { michael@0: case NS_COMPOSITION_START: michael@0: MOZ_ASSERT(!mIMEComposing); michael@0: mIMEComposing = true; michael@0: break; michael@0: case NS_COMPOSITION_END: michael@0: MOZ_ASSERT(mIMEComposing); michael@0: mIMEComposing = false; michael@0: mIMEComposingText.Truncate(); michael@0: break; michael@0: case NS_TEXT_TEXT: michael@0: MOZ_ASSERT(mIMEComposing); michael@0: mIMEComposingText = aEvent->AsTextEvent()->theText; michael@0: break; michael@0: } michael@0: return status; michael@0: } michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::MakeFullScreen(bool aFullScreen) michael@0: { michael@0: mozilla::widget::android::GeckoAppShell::SetFullScreen(aFullScreen); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::SetWindowClass(const nsAString& xulWinType) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mozilla::layers::LayerManager* michael@0: nsWindow::GetLayerManager(PLayerTransactionChild*, LayersBackend, LayerManagerPersistence, michael@0: bool* aAllowRetaining) michael@0: { michael@0: if (aAllowRetaining) { michael@0: *aAllowRetaining = true; michael@0: } michael@0: if (mLayerManager) { michael@0: return mLayerManager; michael@0: } michael@0: // for OMTC allow use of the single layer manager/compositor michael@0: // shared across all windows michael@0: if (ShouldUseOffMainThreadCompositing()) { michael@0: return sLayerManager; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsWindow::CreateLayerManager(int aCompositorWidth, int aCompositorHeight) michael@0: { michael@0: if (mLayerManager) { michael@0: return; michael@0: } michael@0: michael@0: nsWindow *topLevelWindow = FindTopLevel(); michael@0: if (!topLevelWindow || topLevelWindow->mWindowType == eWindowType_invisible) { michael@0: // don't create a layer manager for an invisible top-level window michael@0: return; michael@0: } michael@0: michael@0: mUseLayersAcceleration = ComputeShouldAccelerate(mUseLayersAcceleration); michael@0: michael@0: if (ShouldUseOffMainThreadCompositing()) { michael@0: if (sLayerManager) { michael@0: return; michael@0: } michael@0: CreateCompositor(aCompositorWidth, aCompositorHeight); michael@0: if (mLayerManager) { michael@0: // for OMTC create a single layer manager and compositor that will be michael@0: // used for all windows. michael@0: SetCompositor(mLayerManager, mCompositorParent, mCompositorChild); michael@0: sCompositorPaused = false; michael@0: return; michael@0: } michael@0: michael@0: // If we get here, then off main thread compositing failed to initialize. michael@0: sFailedToCreateGLContext = true; michael@0: } michael@0: michael@0: if (!mUseLayersAcceleration || sFailedToCreateGLContext) { michael@0: printf_stderr(" -- creating basic, not accelerated\n"); michael@0: mLayerManager = CreateBasicLayerManager(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae) michael@0: { michael@0: nsWindow *win = TopWindow(); michael@0: if (!win) michael@0: return; michael@0: michael@0: switch (ae->Type()) { michael@0: case AndroidGeckoEvent::FORCED_RESIZE: michael@0: win->mBounds.width = 0; michael@0: win->mBounds.height = 0; michael@0: // also resize the children michael@0: for (uint32_t i = 0; i < win->mChildren.Length(); i++) { michael@0: win->mChildren[i]->mBounds.width = 0; michael@0: win->mChildren[i]->mBounds.height = 0; michael@0: } michael@0: case AndroidGeckoEvent::SIZE_CHANGED: { michael@0: const nsTArray& points = ae->Points(); michael@0: NS_ASSERTION(points.Length() == 2, "Size changed does not have enough coordinates"); michael@0: michael@0: int nw = points[0].x; michael@0: int nh = points[0].y; michael@0: michael@0: if (ae->Type() == AndroidGeckoEvent::FORCED_RESIZE || nw != gAndroidBounds.width || michael@0: nh != gAndroidBounds.height) { michael@0: gAndroidBounds.width = nw; michael@0: gAndroidBounds.height = nh; michael@0: michael@0: // tell all the windows about the new size michael@0: for (size_t i = 0; i < gTopLevelWindows.Length(); ++i) { michael@0: if (gTopLevelWindows[i]->mIsVisible) michael@0: gTopLevelWindows[i]->Resize(gAndroidBounds.width, michael@0: gAndroidBounds.height, michael@0: false); michael@0: } michael@0: } michael@0: michael@0: int newScreenWidth = points[1].x; michael@0: int newScreenHeight = points[1].y; michael@0: michael@0: if (newScreenWidth == gAndroidScreenBounds.width && michael@0: newScreenHeight == gAndroidScreenBounds.height) michael@0: break; michael@0: michael@0: gAndroidScreenBounds.width = newScreenWidth; michael@0: gAndroidScreenBounds.height = newScreenHeight; michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default || michael@0: !BrowserTabsRemote()) { michael@0: break; michael@0: } michael@0: michael@0: // Tell the content process the new screen size. michael@0: nsTArray cplist; michael@0: ContentParent::GetAll(cplist); michael@0: for (uint32_t i = 0; i < cplist.Length(); ++i) michael@0: unused << cplist[i]->SendScreenSizeChanged(gAndroidScreenBounds); michael@0: michael@0: if (gContentCreationNotifier) michael@0: break; michael@0: michael@0: // If the content process is not created yet, wait until it's michael@0: // created and then tell it the screen size. michael@0: nsCOMPtr obs = do_GetService("@mozilla.org/observer-service;1"); michael@0: if (!obs) michael@0: break; michael@0: michael@0: nsRefPtr notifier = new ContentCreationNotifier; michael@0: if (NS_SUCCEEDED(obs->AddObserver(notifier, "ipc:content-created", false))) { michael@0: if (NS_SUCCEEDED(obs->AddObserver(notifier, "xpcom-shutdown", false))) michael@0: gContentCreationNotifier = notifier; michael@0: else michael@0: obs->RemoveObserver(notifier, "ipc:content-created"); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case AndroidGeckoEvent::MOTION_EVENT: { michael@0: win->UserActivity(); michael@0: if (!gTopLevelWindows.IsEmpty()) { michael@0: nsIntPoint pt(0,0); michael@0: const nsTArray& points = ae->Points(); michael@0: if (points.Length() > 0) { michael@0: pt = points[0]; michael@0: } michael@0: pt.x = clamped(pt.x, 0, std::max(gAndroidBounds.width - 1, 0)); michael@0: pt.y = clamped(pt.y, 0, std::max(gAndroidBounds.height - 1, 0)); michael@0: nsWindow *target = win->FindWindowForPoint(pt); michael@0: #if 0 michael@0: ALOG("MOTION_EVENT %f,%f -> %p (visible: %d children: %d)", pt.x, pt.y, (void*)target, michael@0: target ? target->mIsVisible : 0, michael@0: target ? target->mChildren.Length() : 0); michael@0: michael@0: DumpWindows(); michael@0: #endif michael@0: if (target) { michael@0: bool preventDefaultActions = target->OnMultitouchEvent(ae); michael@0: if (!preventDefaultActions && ae->Count() < 2) michael@0: target->OnMouseEvent(ae); michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case AndroidGeckoEvent::NATIVE_GESTURE_EVENT: { michael@0: nsIntPoint pt(0,0); michael@0: const nsTArray& points = ae->Points(); michael@0: if (points.Length() > 0) { michael@0: pt = points[0]; michael@0: } michael@0: pt.x = clamped(pt.x, 0, std::max(gAndroidBounds.width - 1, 0)); michael@0: pt.y = clamped(pt.y, 0, std::max(gAndroidBounds.height - 1, 0)); michael@0: nsWindow *target = win->FindWindowForPoint(pt); michael@0: michael@0: target->OnNativeGestureEvent(ae); michael@0: break; michael@0: } michael@0: michael@0: case AndroidGeckoEvent::KEY_EVENT: michael@0: win->UserActivity(); michael@0: if (win->mFocus) michael@0: win->mFocus->OnKeyEvent(ae); michael@0: break; michael@0: michael@0: case AndroidGeckoEvent::DRAW: michael@0: layers::renderTraceEventStart("Global draw start", "414141"); michael@0: win->OnDraw(ae); michael@0: layers::renderTraceEventEnd("414141"); michael@0: break; michael@0: michael@0: case AndroidGeckoEvent::IME_EVENT: michael@0: win->UserActivity(); michael@0: if (win->mFocus) { michael@0: win->mFocus->OnIMEEvent(ae); michael@0: } else { michael@0: NS_WARNING("Sending unexpected IME event to top window"); michael@0: win->OnIMEEvent(ae); michael@0: } michael@0: break; michael@0: michael@0: case AndroidGeckoEvent::IME_KEY_EVENT: michael@0: // Keys synthesized by Java IME code are saved in the mIMEKeyEvents michael@0: // array until the next IME_REPLACE_TEXT event, at which point michael@0: // these keys are dispatched in sequence. michael@0: if (win->mFocus) { michael@0: win->mFocus->mIMEKeyEvents.AppendElement(*ae); michael@0: } michael@0: break; michael@0: michael@0: case AndroidGeckoEvent::COMPOSITOR_CREATE: michael@0: win->CreateLayerManager(ae->Width(), ae->Height()); michael@0: break; michael@0: michael@0: case AndroidGeckoEvent::COMPOSITOR_PAUSE: michael@0: // The compositor gets paused when the app is about to go into the michael@0: // background. While the compositor is paused, we need to ensure that michael@0: // no layer tree updates (from draw events) occur, since the compositor michael@0: // cannot make a GL context current in order to process updates. michael@0: if (sCompositorChild) { michael@0: sCompositorChild->SendPause(); michael@0: } michael@0: sCompositorPaused = true; michael@0: break; michael@0: michael@0: case AndroidGeckoEvent::COMPOSITOR_RESUME: michael@0: // When we receive this, the compositor has already been told to michael@0: // resume. (It turns out that waiting till we reach here to tell michael@0: // the compositor to resume takes too long, resulting in a black michael@0: // flash.) This means it's now safe for layer updates to occur. michael@0: // Since we might have prevented one or more draw events from michael@0: // occurring while the compositor was paused, we need to schedule michael@0: // a draw event now. michael@0: if (!sCompositorPaused) { michael@0: win->RedrawAll(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnAndroidEvent(AndroidGeckoEvent *ae) michael@0: { michael@0: switch (ae->Type()) { michael@0: case AndroidGeckoEvent::DRAW: michael@0: OnDraw(ae); michael@0: break; michael@0: michael@0: default: michael@0: ALOG("Window got targetted android event type %d, but didn't handle!", ae->Type()); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsWindow::DrawTo(gfxASurface *targetSurface) michael@0: { michael@0: nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height); michael@0: return DrawTo(targetSurface, boundsRect); michael@0: } michael@0: michael@0: bool michael@0: nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect) michael@0: { michael@0: mozilla::layers::RenderTraceScope trace("DrawTo", "717171"); michael@0: if (!mIsVisible || !mWidgetListener || !GetLayerManager(nullptr)) michael@0: return false; michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height); michael@0: michael@0: // Figure out if any of our children cover this widget completely michael@0: int32_t coveringChildIndex = -1; michael@0: for (uint32_t i = 0; i < mChildren.Length(); ++i) { michael@0: if (mChildren[i]->mBounds.IsEmpty()) michael@0: continue; michael@0: michael@0: if (mChildren[i]->mBounds.Contains(boundsRect)) { michael@0: coveringChildIndex = int32_t(i); michael@0: } michael@0: } michael@0: michael@0: // If we have no covering child, then we need to render this. michael@0: if (coveringChildIndex == -1) { michael@0: nsIntRegion region = invalidRect; michael@0: michael@0: mWidgetListener->WillPaintWindow(this); michael@0: michael@0: switch (GetLayerManager(nullptr)->GetBackendType()) { michael@0: case mozilla::layers::LayersBackend::LAYERS_BASIC: { michael@0: michael@0: nsRefPtr ctx = new gfxContext(targetSurface); michael@0: michael@0: { michael@0: mozilla::layers::RenderTraceScope trace2("Basic DrawTo", "727272"); michael@0: AutoLayerManagerSetup michael@0: setupLayerManager(this, ctx, mozilla::layers::BufferMode::BUFFER_NONE); michael@0: michael@0: mWidgetListener->PaintWindow(this, region); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case mozilla::layers::LayersBackend::LAYERS_CLIENT: { michael@0: mWidgetListener->PaintWindow(this, region); michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: NS_ERROR("Invalid layer manager"); michael@0: } michael@0: michael@0: mWidgetListener->DidPaintWindow(); michael@0: michael@0: // We had no covering child, so make sure we draw all the children, michael@0: // starting from index 0. michael@0: coveringChildIndex = 0; michael@0: } michael@0: michael@0: gfxPoint offset; michael@0: michael@0: if (targetSurface) michael@0: offset = targetSurface->GetDeviceOffset(); michael@0: michael@0: for (uint32_t i = coveringChildIndex; i < mChildren.Length(); ++i) { michael@0: if (mChildren[i]->mBounds.IsEmpty() || michael@0: !mChildren[i]->mBounds.Intersects(boundsRect)) { michael@0: continue; michael@0: } michael@0: michael@0: if (targetSurface) michael@0: targetSurface->SetDeviceOffset(offset + gfxPoint(mChildren[i]->mBounds.x, michael@0: mChildren[i]->mBounds.y)); michael@0: michael@0: bool ok = mChildren[i]->DrawTo(targetSurface, invalidRect); michael@0: michael@0: if (!ok) { michael@0: ALOG("nsWindow[%p]::DrawTo child %d[%p] returned FALSE!", (void*) this, i, (void*)mChildren[i]); michael@0: } michael@0: } michael@0: michael@0: if (targetSurface) michael@0: targetSurface->SetDeviceOffset(offset); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnDraw(AndroidGeckoEvent *ae) michael@0: { michael@0: if (!IsTopLevel()) { michael@0: ALOG("##### redraw for window %p, which is not a toplevel window -- sending to toplevel!", (void*) this); michael@0: DumpWindows(); michael@0: return; michael@0: } michael@0: michael@0: if (!mIsVisible) { michael@0: ALOG("##### redraw for window %p, which is not visible -- ignoring!", (void*) this); michael@0: DumpWindows(); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: AutoLocalJNIFrame jniFrame; michael@0: michael@0: // We're paused, or we haven't been given a window-size yet, so do nothing michael@0: if (sCompositorPaused || gAndroidBounds.width <= 0 || gAndroidBounds.height <= 0) { michael@0: return; michael@0: } michael@0: michael@0: int bytesPerPixel = 2; michael@0: gfxImageFormat format = gfxImageFormat::RGB16_565; michael@0: if (AndroidBridge::Bridge()->GetScreenDepth() == 24) { michael@0: bytesPerPixel = 4; michael@0: format = gfxImageFormat::RGB24; michael@0: } michael@0: michael@0: layers::renderTraceEventStart("Get surface", "424545"); michael@0: static unsigned char bits2[32 * 32 * 4]; michael@0: nsRefPtr targetSurface = michael@0: new gfxImageSurface(bits2, gfxIntSize(32, 32), 32 * bytesPerPixel, format); michael@0: layers::renderTraceEventEnd("Get surface", "424545"); michael@0: michael@0: layers::renderTraceEventStart("Widget draw to", "434646"); michael@0: if (targetSurface->CairoStatus()) { michael@0: ALOG("### Failed to create a valid surface from the bitmap"); michael@0: } else { michael@0: DrawTo(targetSurface, ae->Rect()); michael@0: } michael@0: layers::renderTraceEventEnd("Widget draw to", "434646"); michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnSizeChanged(const gfxIntSize& aSize) michael@0: { michael@0: ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width, aSize.height); michael@0: michael@0: mBounds.width = aSize.width; michael@0: mBounds.height = aSize.height; michael@0: michael@0: if (mWidgetListener) { michael@0: mWidgetListener->WindowResized(this, aSize.width, aSize.height); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsWindow::InitEvent(WidgetGUIEvent& event, nsIntPoint* aPoint) michael@0: { michael@0: if (aPoint) { michael@0: event.refPoint.x = aPoint->x; michael@0: event.refPoint.y = aPoint->y; michael@0: } else { michael@0: event.refPoint.x = 0; michael@0: event.refPoint.y = 0; michael@0: } michael@0: michael@0: event.time = PR_Now() / 1000; michael@0: } michael@0: michael@0: gfxIntSize michael@0: nsWindow::GetAndroidScreenBounds() michael@0: { michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: return ContentChild::GetSingleton()->GetScreenSize(); michael@0: } michael@0: return gAndroidScreenBounds; michael@0: } michael@0: michael@0: void * michael@0: nsWindow::GetNativeData(uint32_t aDataType) michael@0: { michael@0: switch (aDataType) { michael@0: // used by GLContextProviderEGL, nullptr is EGL_DEFAULT_DISPLAY michael@0: case NS_NATIVE_DISPLAY: michael@0: return nullptr; michael@0: michael@0: case NS_NATIVE_WIDGET: michael@0: return (void *) this; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnMouseEvent(AndroidGeckoEvent *ae) michael@0: { michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: WidgetMouseEvent event = ae->MakeMouseEvent(this); michael@0: if (event.message == NS_EVENT_NULL) { michael@0: // invalid event type, abort michael@0: return; michael@0: } michael@0: michael@0: // XXX add the double-click handling logic here michael@0: DispatchEvent(&event); michael@0: } michael@0: michael@0: bool nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae) michael@0: { michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: // End any composition in progress in case the touch event listener michael@0: // modifies the input field value (see bug 856155) michael@0: RemoveIMEComposition(); michael@0: michael@0: // This is set to true once we have called SetPreventPanning() exactly michael@0: // once for a given sequence of touch events. It is reset on the start michael@0: // of the next sequence. michael@0: static bool sDefaultPreventedNotified = false; michael@0: static bool sLastWasDownEvent = false; michael@0: michael@0: bool preventDefaultActions = false; michael@0: bool isDownEvent = false; michael@0: michael@0: WidgetTouchEvent event = ae->MakeTouchEvent(this); michael@0: if (event.message != NS_EVENT_NULL) { michael@0: nsEventStatus status; michael@0: DispatchEvent(&event, status); michael@0: // We check mMultipleActionsPrevented because that's what michael@0: // sets when someone starts dragging the thumb. It doesn't set the status michael@0: // because it doesn't want to prevent the code that gives the input focus michael@0: // from running. michael@0: preventDefaultActions = (status == nsEventStatus_eConsumeNoDefault || michael@0: event.mFlags.mMultipleActionsPrevented); michael@0: isDownEvent = (event.message == NS_TOUCH_START); michael@0: } michael@0: michael@0: // if the last event we got was a down event, then by now we know for sure whether michael@0: // this block has been default-prevented or not. if we haven't already sent the michael@0: // notification for this block, do so now. michael@0: if (sLastWasDownEvent && !sDefaultPreventedNotified) { michael@0: // if this event is a down event, that means it's the start of a new block, and the michael@0: // previous block should not be default-prevented michael@0: bool defaultPrevented = isDownEvent ? false : preventDefaultActions; michael@0: mozilla::widget::android::GeckoAppShell::NotifyDefaultPrevented(defaultPrevented); michael@0: sDefaultPreventedNotified = true; michael@0: } michael@0: michael@0: // now, if this event is a down event, then we might already know that it has been michael@0: // default-prevented. if so, we send the notification right away; otherwise we wait michael@0: // for the next event. michael@0: if (isDownEvent) { michael@0: if (preventDefaultActions) { michael@0: mozilla::widget::android::GeckoAppShell::NotifyDefaultPrevented(true); michael@0: sDefaultPreventedNotified = true; michael@0: } else { michael@0: sDefaultPreventedNotified = false; michael@0: } michael@0: } michael@0: sLastWasDownEvent = isDownEvent; michael@0: michael@0: return preventDefaultActions; michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnNativeGestureEvent(AndroidGeckoEvent *ae) michael@0: { michael@0: nsIntPoint pt(ae->Points()[0].x, michael@0: ae->Points()[0].y); michael@0: double delta = ae->X(); michael@0: int msg = 0; michael@0: michael@0: switch (ae->Action()) { michael@0: case AndroidMotionEvent::ACTION_MAGNIFY_START: michael@0: msg = NS_SIMPLE_GESTURE_MAGNIFY_START; michael@0: mStartDist = delta; michael@0: mLastDist = delta; michael@0: break; michael@0: case AndroidMotionEvent::ACTION_MAGNIFY: michael@0: msg = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE; michael@0: delta -= mLastDist; michael@0: mLastDist += delta; michael@0: break; michael@0: case AndroidMotionEvent::ACTION_MAGNIFY_END: michael@0: msg = NS_SIMPLE_GESTURE_MAGNIFY; michael@0: delta -= mStartDist; michael@0: break; michael@0: default: michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: DispatchGestureEvent(msg, 0, delta, pt, ae->Time()); michael@0: } michael@0: michael@0: void michael@0: nsWindow::DispatchGestureEvent(uint32_t msg, uint32_t direction, double delta, michael@0: const nsIntPoint &refPoint, uint64_t time) michael@0: { michael@0: WidgetSimpleGestureEvent event(true, msg, this); michael@0: michael@0: event.direction = direction; michael@0: event.delta = delta; michael@0: event.modifiers = 0; michael@0: event.time = time; michael@0: event.refPoint = LayoutDeviceIntPoint::FromUntyped(refPoint); michael@0: michael@0: DispatchEvent(&event); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsWindow::DispatchMotionEvent(WidgetInputEvent &event, AndroidGeckoEvent *ae, michael@0: const nsIntPoint &refPoint) michael@0: { michael@0: nsIntPoint offset = WidgetToScreenOffset(); michael@0: michael@0: event.modifiers = ae->DOMModifiers(); michael@0: event.time = ae->Time(); michael@0: michael@0: // XXX possibly bound the range of event.refPoint here. michael@0: // some code may get confused. michael@0: event.refPoint = LayoutDeviceIntPoint::FromUntyped(refPoint - offset); michael@0: michael@0: DispatchEvent(&event); michael@0: } michael@0: michael@0: static unsigned int ConvertAndroidKeyCodeToDOMKeyCode(int androidKeyCode) michael@0: { michael@0: // Special-case alphanumeric keycodes because they are most common. michael@0: if (androidKeyCode >= AKEYCODE_A && michael@0: androidKeyCode <= AKEYCODE_Z) { michael@0: return androidKeyCode - AKEYCODE_A + NS_VK_A; michael@0: } michael@0: michael@0: if (androidKeyCode >= AKEYCODE_0 && michael@0: androidKeyCode <= AKEYCODE_9) { michael@0: return androidKeyCode - AKEYCODE_0 + NS_VK_0; michael@0: } michael@0: michael@0: switch (androidKeyCode) { michael@0: // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3) michael@0: case AKEYCODE_BACK: return NS_VK_ESCAPE; michael@0: // KEYCODE_CALL (5) ... KEYCODE_POUND (18) michael@0: case AKEYCODE_DPAD_UP: return NS_VK_UP; michael@0: case AKEYCODE_DPAD_DOWN: return NS_VK_DOWN; michael@0: case AKEYCODE_DPAD_LEFT: return NS_VK_LEFT; michael@0: case AKEYCODE_DPAD_RIGHT: return NS_VK_RIGHT; michael@0: case AKEYCODE_DPAD_CENTER: return NS_VK_RETURN; michael@0: case AKEYCODE_VOLUME_UP: return NS_VK_VOLUME_UP; michael@0: case AKEYCODE_VOLUME_DOWN: return NS_VK_VOLUME_DOWN; michael@0: // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54) michael@0: case AKEYCODE_COMMA: return NS_VK_COMMA; michael@0: case AKEYCODE_PERIOD: return NS_VK_PERIOD; michael@0: case AKEYCODE_ALT_LEFT: return NS_VK_ALT; michael@0: case AKEYCODE_ALT_RIGHT: return NS_VK_ALT; michael@0: case AKEYCODE_SHIFT_LEFT: return NS_VK_SHIFT; michael@0: case AKEYCODE_SHIFT_RIGHT: return NS_VK_SHIFT; michael@0: case AKEYCODE_TAB: return NS_VK_TAB; michael@0: case AKEYCODE_SPACE: return NS_VK_SPACE; michael@0: // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65) michael@0: case AKEYCODE_ENTER: return NS_VK_RETURN; michael@0: case AKEYCODE_DEL: return NS_VK_BACK; // Backspace michael@0: case AKEYCODE_GRAVE: return NS_VK_BACK_QUOTE; michael@0: // KEYCODE_MINUS (69) michael@0: case AKEYCODE_EQUALS: return NS_VK_EQUALS; michael@0: case AKEYCODE_LEFT_BRACKET: return NS_VK_OPEN_BRACKET; michael@0: case AKEYCODE_RIGHT_BRACKET: return NS_VK_CLOSE_BRACKET; michael@0: case AKEYCODE_BACKSLASH: return NS_VK_BACK_SLASH; michael@0: case AKEYCODE_SEMICOLON: return NS_VK_SEMICOLON; michael@0: // KEYCODE_APOSTROPHE (75) michael@0: case AKEYCODE_SLASH: return NS_VK_SLASH; michael@0: // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90) michael@0: case AKEYCODE_MUTE: return NS_VK_VOLUME_MUTE; michael@0: case AKEYCODE_PAGE_UP: return NS_VK_PAGE_UP; michael@0: case AKEYCODE_PAGE_DOWN: return NS_VK_PAGE_DOWN; michael@0: // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110) michael@0: case AKEYCODE_ESCAPE: return NS_VK_ESCAPE; michael@0: case AKEYCODE_FORWARD_DEL: return NS_VK_DELETE; michael@0: case AKEYCODE_CTRL_LEFT: return NS_VK_CONTROL; michael@0: case AKEYCODE_CTRL_RIGHT: return NS_VK_CONTROL; michael@0: case AKEYCODE_CAPS_LOCK: return NS_VK_CAPS_LOCK; michael@0: case AKEYCODE_SCROLL_LOCK: return NS_VK_SCROLL_LOCK; michael@0: // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119) michael@0: case AKEYCODE_SYSRQ: return NS_VK_PRINTSCREEN; michael@0: case AKEYCODE_BREAK: return NS_VK_PAUSE; michael@0: case AKEYCODE_MOVE_HOME: return NS_VK_HOME; michael@0: case AKEYCODE_MOVE_END: return NS_VK_END; michael@0: case AKEYCODE_INSERT: return NS_VK_INSERT; michael@0: // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130) michael@0: case AKEYCODE_F1: return NS_VK_F1; michael@0: case AKEYCODE_F2: return NS_VK_F2; michael@0: case AKEYCODE_F3: return NS_VK_F3; michael@0: case AKEYCODE_F4: return NS_VK_F4; michael@0: case AKEYCODE_F5: return NS_VK_F5; michael@0: case AKEYCODE_F6: return NS_VK_F6; michael@0: case AKEYCODE_F7: return NS_VK_F7; michael@0: case AKEYCODE_F8: return NS_VK_F8; michael@0: case AKEYCODE_F9: return NS_VK_F9; michael@0: case AKEYCODE_F10: return NS_VK_F10; michael@0: case AKEYCODE_F11: return NS_VK_F11; michael@0: case AKEYCODE_F12: return NS_VK_F12; michael@0: case AKEYCODE_NUM_LOCK: return NS_VK_NUM_LOCK; michael@0: case AKEYCODE_NUMPAD_0: return NS_VK_NUMPAD0; michael@0: case AKEYCODE_NUMPAD_1: return NS_VK_NUMPAD1; michael@0: case AKEYCODE_NUMPAD_2: return NS_VK_NUMPAD2; michael@0: case AKEYCODE_NUMPAD_3: return NS_VK_NUMPAD3; michael@0: case AKEYCODE_NUMPAD_4: return NS_VK_NUMPAD4; michael@0: case AKEYCODE_NUMPAD_5: return NS_VK_NUMPAD5; michael@0: case AKEYCODE_NUMPAD_6: return NS_VK_NUMPAD6; michael@0: case AKEYCODE_NUMPAD_7: return NS_VK_NUMPAD7; michael@0: case AKEYCODE_NUMPAD_8: return NS_VK_NUMPAD8; michael@0: case AKEYCODE_NUMPAD_9: return NS_VK_NUMPAD9; michael@0: case AKEYCODE_NUMPAD_DIVIDE: return NS_VK_DIVIDE; michael@0: case AKEYCODE_NUMPAD_MULTIPLY: return NS_VK_MULTIPLY; michael@0: case AKEYCODE_NUMPAD_SUBTRACT: return NS_VK_SUBTRACT; michael@0: case AKEYCODE_NUMPAD_ADD: return NS_VK_ADD; michael@0: case AKEYCODE_NUMPAD_DOT: return NS_VK_DECIMAL; michael@0: case AKEYCODE_NUMPAD_COMMA: return NS_VK_SEPARATOR; michael@0: case AKEYCODE_NUMPAD_ENTER: return NS_VK_RETURN; michael@0: case AKEYCODE_NUMPAD_EQUALS: return NS_VK_EQUALS; michael@0: // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210) michael@0: michael@0: // Needs to confirm the behavior. If the key switches the open state michael@0: // of Japanese IME (or switches input character between Hiragana and michael@0: // Roman numeric characters), then, it might be better to use michael@0: // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows. michael@0: case AKEYCODE_ZENKAKU_HANKAKU: return 0; michael@0: case AKEYCODE_EISU: return NS_VK_EISU; michael@0: case AKEYCODE_MUHENKAN: return NS_VK_NONCONVERT; michael@0: case AKEYCODE_HENKAN: return NS_VK_CONVERT; michael@0: case AKEYCODE_KATAKANA_HIRAGANA: return 0; michael@0: case AKEYCODE_YEN: return NS_VK_BACK_SLASH; // Same as other platforms. michael@0: case AKEYCODE_RO: return NS_VK_BACK_SLASH; // Same as other platforms. michael@0: case AKEYCODE_KANA: return NS_VK_KANA; michael@0: case AKEYCODE_ASSIST: return NS_VK_HELP; michael@0: michael@0: // the A key is the action key for gamepad devices. michael@0: case AKEYCODE_BUTTON_A: return NS_VK_RETURN; michael@0: michael@0: default: michael@0: ALOG("ConvertAndroidKeyCodeToDOMKeyCode: " michael@0: "No DOM keycode for Android keycode %d", androidKeyCode); michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: static KeyNameIndex michael@0: ConvertAndroidKeyCodeToKeyNameIndex(AndroidGeckoEvent& aAndroidGeckoEvent) michael@0: { michael@0: int keyCode = aAndroidGeckoEvent.KeyCode(); michael@0: // Special-case alphanumeric keycodes because they are most common. michael@0: if (keyCode >= AKEYCODE_A && keyCode <= AKEYCODE_Z) { michael@0: return KEY_NAME_INDEX_USE_STRING; michael@0: } michael@0: michael@0: if (keyCode >= AKEYCODE_0 && keyCode <= AKEYCODE_9) { michael@0: return KEY_NAME_INDEX_USE_STRING; michael@0: } michael@0: michael@0: switch (keyCode) { michael@0: michael@0: #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ michael@0: case aNativeKey: return aKeyNameIndex; michael@0: michael@0: #include "NativeKeyToDOMKeyName.h" michael@0: michael@0: #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX michael@0: michael@0: // KEYCODE_0 (7) ... KEYCODE_9 (16) michael@0: case AKEYCODE_STAR: // '*' key michael@0: case AKEYCODE_POUND: // '#' key michael@0: michael@0: // KEYCODE_A (29) ... KEYCODE_Z (54) michael@0: michael@0: case AKEYCODE_COMMA: // ',' key michael@0: case AKEYCODE_PERIOD: // '.' key michael@0: case AKEYCODE_SPACE: michael@0: case AKEYCODE_GRAVE: // '`' key michael@0: case AKEYCODE_MINUS: // '-' key michael@0: case AKEYCODE_EQUALS: // '=' key michael@0: case AKEYCODE_LEFT_BRACKET: // '[' key michael@0: case AKEYCODE_RIGHT_BRACKET: // ']' key michael@0: case AKEYCODE_BACKSLASH: // '\' key michael@0: case AKEYCODE_SEMICOLON: // ';' key michael@0: case AKEYCODE_APOSTROPHE: // ''' key michael@0: case AKEYCODE_SLASH: // '/' key michael@0: case AKEYCODE_AT: // '@' key michael@0: case AKEYCODE_PLUS: // '+' key michael@0: michael@0: case AKEYCODE_NUMPAD_0: michael@0: case AKEYCODE_NUMPAD_1: michael@0: case AKEYCODE_NUMPAD_2: michael@0: case AKEYCODE_NUMPAD_3: michael@0: case AKEYCODE_NUMPAD_4: michael@0: case AKEYCODE_NUMPAD_5: michael@0: case AKEYCODE_NUMPAD_6: michael@0: case AKEYCODE_NUMPAD_7: michael@0: case AKEYCODE_NUMPAD_8: michael@0: case AKEYCODE_NUMPAD_9: michael@0: case AKEYCODE_NUMPAD_DIVIDE: michael@0: case AKEYCODE_NUMPAD_MULTIPLY: michael@0: case AKEYCODE_NUMPAD_SUBTRACT: michael@0: case AKEYCODE_NUMPAD_ADD: michael@0: case AKEYCODE_NUMPAD_DOT: michael@0: case AKEYCODE_NUMPAD_COMMA: michael@0: case AKEYCODE_NUMPAD_EQUALS: michael@0: case AKEYCODE_NUMPAD_LEFT_PAREN: michael@0: case AKEYCODE_NUMPAD_RIGHT_PAREN: michael@0: michael@0: case AKEYCODE_YEN: // yen sign key michael@0: case AKEYCODE_RO: // Japanese Ro key michael@0: return KEY_NAME_INDEX_USE_STRING; michael@0: michael@0: case AKEYCODE_SOFT_LEFT: michael@0: case AKEYCODE_SOFT_RIGHT: michael@0: case AKEYCODE_CALL: michael@0: case AKEYCODE_ENDCALL: michael@0: case AKEYCODE_SYM: // Symbol modifier michael@0: case AKEYCODE_NUM: // XXX Not sure michael@0: case AKEYCODE_HEADSETHOOK: michael@0: case AKEYCODE_FOCUS: michael@0: case AKEYCODE_NOTIFICATION: // XXX Not sure michael@0: case AKEYCODE_PICTSYMBOLS: michael@0: michael@0: case AKEYCODE_BUTTON_A: michael@0: case AKEYCODE_BUTTON_B: michael@0: case AKEYCODE_BUTTON_C: michael@0: case AKEYCODE_BUTTON_X: michael@0: case AKEYCODE_BUTTON_Y: michael@0: case AKEYCODE_BUTTON_Z: michael@0: case AKEYCODE_BUTTON_L1: michael@0: case AKEYCODE_BUTTON_R1: michael@0: case AKEYCODE_BUTTON_L2: michael@0: case AKEYCODE_BUTTON_R2: michael@0: case AKEYCODE_BUTTON_THUMBL: michael@0: case AKEYCODE_BUTTON_THUMBR: michael@0: case AKEYCODE_BUTTON_START: michael@0: case AKEYCODE_BUTTON_SELECT: michael@0: case AKEYCODE_BUTTON_MODE: michael@0: michael@0: case AKEYCODE_MUTE: // mutes the microphone michael@0: case AKEYCODE_MEDIA_CLOSE: michael@0: michael@0: case AKEYCODE_ZOOM_IN: michael@0: case AKEYCODE_ZOOM_OUT: michael@0: case AKEYCODE_DVR: michael@0: case AKEYCODE_TV_POWER: michael@0: case AKEYCODE_TV_INPUT: michael@0: case AKEYCODE_STB_POWER: michael@0: case AKEYCODE_STB_INPUT: michael@0: case AKEYCODE_AVR_POWER: michael@0: case AKEYCODE_AVR_INPUT: michael@0: michael@0: case AKEYCODE_BUTTON_1: michael@0: case AKEYCODE_BUTTON_2: michael@0: case AKEYCODE_BUTTON_3: michael@0: case AKEYCODE_BUTTON_4: michael@0: case AKEYCODE_BUTTON_5: michael@0: case AKEYCODE_BUTTON_6: michael@0: case AKEYCODE_BUTTON_7: michael@0: case AKEYCODE_BUTTON_8: michael@0: case AKEYCODE_BUTTON_9: michael@0: case AKEYCODE_BUTTON_10: michael@0: case AKEYCODE_BUTTON_11: michael@0: case AKEYCODE_BUTTON_12: michael@0: case AKEYCODE_BUTTON_13: michael@0: case AKEYCODE_BUTTON_14: michael@0: case AKEYCODE_BUTTON_15: michael@0: case AKEYCODE_BUTTON_16: michael@0: michael@0: case AKEYCODE_LANGUAGE_SWITCH: michael@0: case AKEYCODE_MANNER_MODE: michael@0: case AKEYCODE_3D_MODE: michael@0: case AKEYCODE_CONTACTS: michael@0: case AKEYCODE_CALENDAR: michael@0: case AKEYCODE_MUSIC: michael@0: case AKEYCODE_CALCULATOR: michael@0: michael@0: case AKEYCODE_ZENKAKU_HANKAKU: michael@0: case AKEYCODE_KATAKANA_HIRAGANA: michael@0: return KEY_NAME_INDEX_Unidentified; michael@0: michael@0: case AKEYCODE_UNKNOWN: michael@0: MOZ_ASSERT( michael@0: aAndroidGeckoEvent.Action() != AKEY_EVENT_ACTION_MULTIPLE, michael@0: "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!"); michael@0: // It's actually an unknown key if the action isn't ACTION_MULTIPLE. michael@0: // However, it might cause text input. So, let's check the value. michael@0: return aAndroidGeckoEvent.DOMPrintableKeyValue() ? michael@0: KEY_NAME_INDEX_USE_STRING : KEY_NAME_INDEX_Unidentified; michael@0: michael@0: default: michael@0: ALOG("ConvertAndroidKeyCodeToKeyNameIndex: " michael@0: "No DOM key name index for Android keycode %d", keyCode); michael@0: return KEY_NAME_INDEX_Unidentified; michael@0: } michael@0: } michael@0: michael@0: static void InitPluginEvent(ANPEvent* pluginEvent, ANPKeyActions keyAction, michael@0: AndroidGeckoEvent& key) michael@0: { michael@0: int androidKeyCode = key.KeyCode(); michael@0: uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(androidKeyCode); michael@0: michael@0: int modifiers = 0; michael@0: if (key.IsAltPressed()) michael@0: modifiers |= kAlt_ANPKeyModifier; michael@0: if (key.IsShiftPressed()) michael@0: modifiers |= kShift_ANPKeyModifier; michael@0: michael@0: pluginEvent->inSize = sizeof(ANPEvent); michael@0: pluginEvent->eventType = kKey_ANPEventType; michael@0: pluginEvent->data.key.action = keyAction; michael@0: pluginEvent->data.key.nativeCode = androidKeyCode; michael@0: pluginEvent->data.key.virtualCode = domKeyCode; michael@0: pluginEvent->data.key.unichar = key.UnicodeChar(); michael@0: pluginEvent->data.key.modifiers = modifiers; michael@0: pluginEvent->data.key.repeatCount = key.RepeatCount(); michael@0: } michael@0: michael@0: void michael@0: nsWindow::InitKeyEvent(WidgetKeyboardEvent& event, AndroidGeckoEvent& key, michael@0: ANPEvent* pluginEvent) michael@0: { michael@0: event.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(key); michael@0: if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { michael@0: int keyValue = key.DOMPrintableKeyValue(); michael@0: if (keyValue) { michael@0: event.mKeyValue = static_cast(keyValue); michael@0: } michael@0: } michael@0: uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(key.KeyCode()); michael@0: michael@0: if (event.message == NS_KEY_PRESS) { michael@0: // Android gives us \n, so filter out some control characters. michael@0: int charCode = key.UnicodeChar(); michael@0: if (!charCode) { michael@0: charCode = key.BaseUnicodeChar(); michael@0: } michael@0: event.isChar = (charCode >= ' '); michael@0: event.charCode = event.isChar ? charCode : 0; michael@0: event.keyCode = (event.charCode > 0) ? 0 : domKeyCode; michael@0: event.pluginEvent = nullptr; michael@0: } else { michael@0: #ifdef DEBUG michael@0: if (event.message != NS_KEY_DOWN && event.message != NS_KEY_UP) { michael@0: ALOG("InitKeyEvent: unexpected event.message %d", event.message); michael@0: } michael@0: #endif // DEBUG michael@0: michael@0: // Flash will want a pluginEvent for keydown and keyup events. michael@0: ANPKeyActions action = event.message == NS_KEY_DOWN michael@0: ? kDown_ANPKeyAction michael@0: : kUp_ANPKeyAction; michael@0: InitPluginEvent(pluginEvent, action, key); michael@0: michael@0: event.isChar = false; michael@0: event.charCode = 0; michael@0: event.keyCode = domKeyCode; michael@0: event.pluginEvent = pluginEvent; michael@0: } michael@0: michael@0: event.modifiers = key.DOMModifiers(); michael@0: if (gMenu) { michael@0: event.modifiers |= MODIFIER_CONTROL; michael@0: } michael@0: // For keypress, if the unicode char already has modifiers applied, we michael@0: // don't specify extra modifiers. If UnicodeChar() != BaseUnicodeChar() michael@0: // it means UnicodeChar() already has modifiers applied. michael@0: // Note that on Android 4.x, Alt modifier isn't set when the key input michael@0: // causes text input even while right Alt key is pressed. However, this michael@0: // is necessary for Android 2.3 compatibility. michael@0: if (event.message == NS_KEY_PRESS && michael@0: key.UnicodeChar() && key.UnicodeChar() != key.BaseUnicodeChar()) { michael@0: event.modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL | MODIFIER_META); michael@0: } michael@0: michael@0: event.mIsRepeat = michael@0: (event.message == NS_KEY_DOWN || event.message == NS_KEY_PRESS) && michael@0: (!!(key.Flags() & AKEY_EVENT_FLAG_LONG_PRESS) || !!key.RepeatCount()); michael@0: event.location = key.DomKeyLocation(); michael@0: event.time = key.Time(); michael@0: michael@0: if (gMenu) michael@0: gMenuConsumed = true; michael@0: } michael@0: michael@0: void michael@0: nsWindow::HandleSpecialKey(AndroidGeckoEvent *ae) michael@0: { michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: nsCOMPtr command; michael@0: bool isDown = ae->Action() == AKEY_EVENT_ACTION_DOWN; michael@0: bool isLongPress = !!(ae->Flags() & AKEY_EVENT_FLAG_LONG_PRESS); michael@0: bool doCommand = false; michael@0: uint32_t keyCode = ae->KeyCode(); michael@0: michael@0: if (isDown) { michael@0: switch (keyCode) { michael@0: case AKEYCODE_BACK: michael@0: if (isLongPress) { michael@0: command = nsGkAtoms::Clear; michael@0: doCommand = true; michael@0: } michael@0: break; michael@0: case AKEYCODE_MENU: michael@0: gMenu = true; michael@0: gMenuConsumed = isLongPress; michael@0: break; michael@0: } michael@0: } else { michael@0: switch (keyCode) { michael@0: case AKEYCODE_BACK: { michael@0: // XXX Where is the keydown event for this?? michael@0: WidgetKeyboardEvent pressEvent(true, NS_KEY_PRESS, this); michael@0: ANPEvent pluginEvent; michael@0: InitKeyEvent(pressEvent, *ae, &pluginEvent); michael@0: DispatchEvent(&pressEvent); michael@0: return; michael@0: } michael@0: case AKEYCODE_MENU: michael@0: gMenu = false; michael@0: if (!gMenuConsumed) { michael@0: command = nsGkAtoms::Menu; michael@0: doCommand = true; michael@0: } michael@0: break; michael@0: case AKEYCODE_SEARCH: michael@0: command = nsGkAtoms::Search; michael@0: doCommand = true; michael@0: break; michael@0: default: michael@0: ALOG("Unknown special key code!"); michael@0: return; michael@0: } michael@0: } michael@0: if (doCommand) { michael@0: WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, command, this); michael@0: InitEvent(event); michael@0: DispatchEvent(&event); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnKeyEvent(AndroidGeckoEvent *ae) michael@0: { michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: RemoveIMEComposition(); michael@0: uint32_t msg; michael@0: switch (ae->Action()) { michael@0: case AKEY_EVENT_ACTION_DOWN: michael@0: msg = NS_KEY_DOWN; michael@0: break; michael@0: case AKEY_EVENT_ACTION_UP: michael@0: msg = NS_KEY_UP; michael@0: break; michael@0: case AKEY_EVENT_ACTION_MULTIPLE: michael@0: // Keys with multiple action are handled in Java, michael@0: // and we should never see one here michael@0: MOZ_CRASH("Cannot handle key with multiple action"); michael@0: default: michael@0: ALOG("Unknown key action event!"); michael@0: return; michael@0: } michael@0: michael@0: bool firePress = ae->Action() == AKEY_EVENT_ACTION_DOWN; michael@0: switch (ae->KeyCode()) { michael@0: case AKEYCODE_SHIFT_LEFT: michael@0: case AKEYCODE_SHIFT_RIGHT: michael@0: case AKEYCODE_ALT_LEFT: michael@0: case AKEYCODE_ALT_RIGHT: michael@0: case AKEYCODE_CTRL_LEFT: michael@0: case AKEYCODE_CTRL_RIGHT: michael@0: firePress = false; michael@0: break; michael@0: case AKEYCODE_BACK: michael@0: case AKEYCODE_MENU: michael@0: case AKEYCODE_SEARCH: michael@0: HandleSpecialKey(ae); michael@0: return; michael@0: } michael@0: michael@0: nsEventStatus status; michael@0: WidgetKeyboardEvent event(true, msg, this); michael@0: ANPEvent pluginEvent; michael@0: InitKeyEvent(event, *ae, &pluginEvent); michael@0: DispatchEvent(&event, status); michael@0: michael@0: if (Destroyed()) michael@0: return; michael@0: if (!firePress || status == nsEventStatus_eConsumeNoDefault) { michael@0: return; michael@0: } michael@0: michael@0: WidgetKeyboardEvent pressEvent(true, NS_KEY_PRESS, this); michael@0: InitKeyEvent(pressEvent, *ae, &pluginEvent); michael@0: #ifdef DEBUG_ANDROID_WIDGET michael@0: __android_log_print(ANDROID_LOG_INFO, "Gecko", "Dispatching key pressEvent with keyCode %d charCode %d shift %d alt %d sym/ctrl %d metamask %d", pressEvent.keyCode, pressEvent.charCode, pressEvent.IsShift(), pressEvent.IsAlt(), pressEvent.IsControl(), ae->MetaState()); michael@0: #endif michael@0: DispatchEvent(&pressEvent); michael@0: } michael@0: michael@0: #ifdef DEBUG_ANDROID_IME michael@0: #define ALOGIME(args...) ALOG(args) michael@0: #else michael@0: #define ALOGIME(args...) ((void)0) michael@0: #endif michael@0: michael@0: static nscolor michael@0: ConvertAndroidColor(uint32_t argb) michael@0: { michael@0: return NS_RGBA((argb & 0x00ff0000) >> 16, michael@0: (argb & 0x0000ff00) >> 8, michael@0: (argb & 0x000000ff), michael@0: (argb & 0xff000000) >> 24); michael@0: } michael@0: michael@0: class AutoIMEMask { michael@0: private: michael@0: bool mOldMask, *mMask; michael@0: public: michael@0: AutoIMEMask(bool &mask) : mOldMask(mask), mMask(&mask) { michael@0: mask = true; michael@0: } michael@0: ~AutoIMEMask() { michael@0: *mMask = mOldMask; michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: Remove the composition but leave the text content as-is michael@0: */ michael@0: void michael@0: nsWindow::RemoveIMEComposition() michael@0: { michael@0: // Remove composition on Gecko side michael@0: if (!mIMEComposing) michael@0: return; michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: AutoIMEMask selMask(mIMEMaskSelectionUpdate); michael@0: AutoIMEMask textMask(mIMEMaskTextUpdate); michael@0: michael@0: WidgetTextEvent textEvent(true, NS_TEXT_TEXT, this); michael@0: InitEvent(textEvent, nullptr); michael@0: textEvent.theText = mIMEComposingText; michael@0: DispatchEvent(&textEvent); michael@0: michael@0: WidgetCompositionEvent event(true, NS_COMPOSITION_END, this); michael@0: InitEvent(event, nullptr); michael@0: DispatchEvent(&event); michael@0: } michael@0: michael@0: void michael@0: nsWindow::OnIMEEvent(AndroidGeckoEvent *ae) michael@0: { michael@0: MOZ_ASSERT(!mIMEMaskTextUpdate); michael@0: MOZ_ASSERT(!mIMEMaskSelectionUpdate); michael@0: /* michael@0: Rules for managing IME between Gecko and Java: michael@0: michael@0: * Gecko controls the text content, and Java shadows the Gecko text michael@0: through text updates michael@0: * Java controls the selection, and Gecko shadows the Java selection michael@0: through set selection events michael@0: * Java controls the composition, and Gecko shadows the Java michael@0: composition through update composition events michael@0: */ michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: if (ae->Action() == AndroidGeckoEvent::IME_ACKNOWLEDGE_FOCUS) { michael@0: MOZ_ASSERT(mIMEMaskEventsCount > 0); michael@0: mIMEMaskEventsCount--; michael@0: if (!mIMEMaskEventsCount) { michael@0: // The focusing handshake sequence is complete, and Java is waiting michael@0: // on Gecko. Now we can notify Java of the newly focused content michael@0: mIMETextChanges.Clear(); michael@0: mIMESelectionChanged = false; michael@0: // NotifyIMEOfTextChange also notifies selection michael@0: // Use 'INT32_MAX / 2' here because subsequent text changes might michael@0: // combine with this text change, and overflow might occur if michael@0: // we just use INT32_MAX michael@0: IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE); michael@0: notification.mTextChangeData.mOldEndOffset = michael@0: notification.mTextChangeData.mNewEndOffset = INT32_MAX / 2; michael@0: NotifyIMEOfTextChange(notification); michael@0: FlushIMEChanges(); michael@0: } michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT); michael@0: return; michael@0: } else if (ae->Action() == AndroidGeckoEvent::IME_UPDATE_CONTEXT) { michael@0: mozilla::widget::android::GeckoAppShell::NotifyIMEContext(mInputContext.mIMEState.mEnabled, michael@0: mInputContext.mHTMLInputType, michael@0: mInputContext.mHTMLInputInputmode, michael@0: mInputContext.mActionHint); michael@0: mIMEUpdatingContext = false; michael@0: return; michael@0: } michael@0: if (mIMEMaskEventsCount > 0) { michael@0: // Still reply to events, but don't do anything else michael@0: if (ae->Action() == AndroidGeckoEvent::IME_SYNCHRONIZE || michael@0: ae->Action() == AndroidGeckoEvent::IME_REPLACE_TEXT) { michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT); michael@0: } michael@0: return; michael@0: } michael@0: switch (ae->Action()) { michael@0: case AndroidGeckoEvent::IME_FLUSH_CHANGES: michael@0: { michael@0: FlushIMEChanges(); michael@0: } michael@0: break; michael@0: case AndroidGeckoEvent::IME_SYNCHRONIZE: michael@0: { michael@0: FlushIMEChanges(); michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT); michael@0: } michael@0: break; michael@0: case AndroidGeckoEvent::IME_REPLACE_TEXT: michael@0: { michael@0: /* michael@0: Replace text in Gecko thread from ae->Start() to ae->End() michael@0: with the string ae->Characters() michael@0: michael@0: Selection updates are masked so the result of our temporary michael@0: selection event is not passed on to Java michael@0: michael@0: Text updates are passed on, so the Java text can shadow the michael@0: Gecko text michael@0: */ michael@0: AutoIMEMask selMask(mIMEMaskSelectionUpdate); michael@0: RemoveIMEComposition(); michael@0: { michael@0: WidgetSelectionEvent event(true, NS_SELECTION_SET, this); michael@0: InitEvent(event, nullptr); michael@0: event.mOffset = uint32_t(ae->Start()); michael@0: event.mLength = uint32_t(ae->End() - ae->Start()); michael@0: event.mExpandToClusterBoundary = false; michael@0: DispatchEvent(&event); michael@0: } michael@0: michael@0: if (!mIMEKeyEvents.IsEmpty()) { michael@0: for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) { michael@0: OnKeyEvent(&mIMEKeyEvents[i]); michael@0: } michael@0: mIMEKeyEvents.Clear(); michael@0: FlushIMEChanges(); michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT); michael@0: break; michael@0: } michael@0: michael@0: { michael@0: WidgetCompositionEvent event(true, NS_COMPOSITION_START, this); michael@0: InitEvent(event, nullptr); michael@0: DispatchEvent(&event); michael@0: } michael@0: { michael@0: WidgetCompositionEvent event(true, NS_COMPOSITION_UPDATE, this); michael@0: InitEvent(event, nullptr); michael@0: event.data = ae->Characters(); michael@0: DispatchEvent(&event); michael@0: } michael@0: { michael@0: WidgetTextEvent event(true, NS_TEXT_TEXT, this); michael@0: InitEvent(event, nullptr); michael@0: event.theText = ae->Characters(); michael@0: DispatchEvent(&event); michael@0: } michael@0: { michael@0: WidgetCompositionEvent event(true, NS_COMPOSITION_END, this); michael@0: InitEvent(event, nullptr); michael@0: event.data = ae->Characters(); michael@0: DispatchEvent(&event); michael@0: } michael@0: FlushIMEChanges(); michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT); michael@0: } michael@0: break; michael@0: case AndroidGeckoEvent::IME_SET_SELECTION: michael@0: { michael@0: /* michael@0: Set Gecko selection to ae->Start() to ae->End() michael@0: michael@0: Selection updates are masked to prevent Java from being michael@0: notified of the new selection michael@0: */ michael@0: AutoIMEMask selMask(mIMEMaskSelectionUpdate); michael@0: RemoveIMEComposition(); michael@0: WidgetSelectionEvent selEvent(true, NS_SELECTION_SET, this); michael@0: InitEvent(selEvent, nullptr); michael@0: michael@0: int32_t start = ae->Start(), end = ae->End(); michael@0: michael@0: if (start < 0 || end < 0) { michael@0: WidgetQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, michael@0: this); michael@0: InitEvent(event, nullptr); michael@0: DispatchEvent(&event); michael@0: MOZ_ASSERT(event.mSucceeded && !event.mWasAsync); michael@0: michael@0: if (start < 0) michael@0: start = int32_t(event.GetSelectionStart()); michael@0: if (end < 0) michael@0: end = int32_t(event.GetSelectionEnd()); michael@0: } michael@0: michael@0: selEvent.mOffset = std::min(start, end); michael@0: selEvent.mLength = std::max(start, end) - selEvent.mOffset; michael@0: selEvent.mReversed = start > end; michael@0: selEvent.mExpandToClusterBoundary = false; michael@0: michael@0: DispatchEvent(&selEvent); michael@0: michael@0: // Notify SelectionHandler of final caret position michael@0: // Required after IME hide via 'Back' button michael@0: AndroidGeckoEvent* broadcastEvent = AndroidGeckoEvent::MakeBroadcastEvent( michael@0: NS_LITERAL_CSTRING("TextSelection:UpdateCaretPos"), michael@0: NS_LITERAL_CSTRING("")); michael@0: nsAppShell::gAppShell->PostEvent(broadcastEvent); michael@0: } michael@0: break; michael@0: case AndroidGeckoEvent::IME_ADD_COMPOSITION_RANGE: michael@0: { michael@0: TextRange range; michael@0: range.mStartOffset = ae->Start(); michael@0: range.mEndOffset = ae->End(); michael@0: range.mRangeType = ae->RangeType(); michael@0: range.mRangeStyle.mDefinedStyles = ae->RangeStyles(); michael@0: range.mRangeStyle.mLineStyle = ae->RangeLineStyle(); michael@0: range.mRangeStyle.mIsBoldLine = ae->RangeBoldLine(); michael@0: range.mRangeStyle.mForegroundColor = michael@0: ConvertAndroidColor(uint32_t(ae->RangeForeColor())); michael@0: range.mRangeStyle.mBackgroundColor = michael@0: ConvertAndroidColor(uint32_t(ae->RangeBackColor())); michael@0: range.mRangeStyle.mUnderlineColor = michael@0: ConvertAndroidColor(uint32_t(ae->RangeLineColor())); michael@0: mIMERanges->AppendElement(range); michael@0: } michael@0: break; michael@0: case AndroidGeckoEvent::IME_UPDATE_COMPOSITION: michael@0: { michael@0: /* michael@0: Update the composition from ae->Start() to ae->End() using michael@0: information from added ranges. This is only used for michael@0: visual indication and does not affect the text content. michael@0: Only the offsets are specified and not the text content michael@0: to eliminate the possibility of this event altering the michael@0: text content unintentionally. michael@0: michael@0: Selection and text updates are masked so the result of michael@0: temporary events are not passed on to Java michael@0: */ michael@0: AutoIMEMask selMask(mIMEMaskSelectionUpdate); michael@0: AutoIMEMask textMask(mIMEMaskTextUpdate); michael@0: RemoveIMEComposition(); michael@0: michael@0: WidgetTextEvent event(true, NS_TEXT_TEXT, this); michael@0: InitEvent(event, nullptr); michael@0: michael@0: event.mRanges = new TextRangeArray(); michael@0: mIMERanges.swap(event.mRanges); michael@0: michael@0: { michael@0: WidgetSelectionEvent event(true, NS_SELECTION_SET, this); michael@0: InitEvent(event, nullptr); michael@0: event.mOffset = uint32_t(ae->Start()); michael@0: event.mLength = uint32_t(ae->End() - ae->Start()); michael@0: event.mExpandToClusterBoundary = false; michael@0: DispatchEvent(&event); michael@0: } michael@0: { michael@0: WidgetQueryContentEvent queryEvent(true, michael@0: NS_QUERY_SELECTED_TEXT, michael@0: this); michael@0: InitEvent(queryEvent, nullptr); michael@0: DispatchEvent(&queryEvent); michael@0: MOZ_ASSERT(queryEvent.mSucceeded && !queryEvent.mWasAsync); michael@0: event.theText = queryEvent.mReply.mString; michael@0: } michael@0: { michael@0: WidgetCompositionEvent event(true, NS_COMPOSITION_START, this); michael@0: InitEvent(event, nullptr); michael@0: DispatchEvent(&event); michael@0: } michael@0: { michael@0: WidgetCompositionEvent compositionUpdate(true, michael@0: NS_COMPOSITION_UPDATE, michael@0: this); michael@0: InitEvent(compositionUpdate, nullptr); michael@0: compositionUpdate.data = event.theText; michael@0: DispatchEvent(&compositionUpdate); michael@0: } michael@0: michael@0: #ifdef DEBUG_ANDROID_IME michael@0: const NS_ConvertUTF16toUTF8 theText8(event.theText); michael@0: const char* text = theText8.get(); michael@0: ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u", michael@0: text, event.theText.Length(), event.mRanges->Length()); michael@0: #endif // DEBUG_ANDROID_IME michael@0: michael@0: DispatchEvent(&event); michael@0: michael@0: // Notify SelectionHandler of final caret position michael@0: // Required in cases of keyboards providing autoCorrections michael@0: AndroidGeckoEvent* broadcastEvent = AndroidGeckoEvent::MakeBroadcastEvent( michael@0: NS_LITERAL_CSTRING("TextSelection:UpdateCaretPos"), michael@0: NS_LITERAL_CSTRING("")); michael@0: nsAppShell::gAppShell->PostEvent(broadcastEvent); michael@0: } michael@0: break; michael@0: case AndroidGeckoEvent::IME_REMOVE_COMPOSITION: michael@0: { michael@0: /* michael@0: * Remove any previous composition. This is only used for michael@0: * visual indication and does not affect the text content. michael@0: * michael@0: * Selection and text updates are masked so the result of michael@0: * temporary events are not passed on to Java michael@0: */ michael@0: AutoIMEMask selMask(mIMEMaskSelectionUpdate); michael@0: AutoIMEMask textMask(mIMEMaskTextUpdate); michael@0: RemoveIMEComposition(); michael@0: mIMERanges->Clear(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: nsWindow * michael@0: nsWindow::FindWindowForPoint(const nsIntPoint& pt) michael@0: { michael@0: if (!mBounds.Contains(pt)) michael@0: return nullptr; michael@0: michael@0: // children mBounds are relative to their parent michael@0: nsIntPoint childPoint(pt.x - mBounds.x, pt.y - mBounds.y); michael@0: michael@0: for (uint32_t i = 0; i < mChildren.Length(); ++i) { michael@0: if (mChildren[i]->mBounds.Contains(childPoint)) michael@0: return mChildren[i]->FindWindowForPoint(childPoint); michael@0: } michael@0: michael@0: return this; michael@0: } michael@0: michael@0: void michael@0: nsWindow::UserActivity() michael@0: { michael@0: if (!mIdleService) { michael@0: mIdleService = do_GetService("@mozilla.org/widget/idleservice;1"); michael@0: } michael@0: michael@0: if (mIdleService) { michael@0: mIdleService->ResetIdleTimeOut(0); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsWindow::NotifyIME(const IMENotification& aIMENotification) michael@0: { michael@0: switch (aIMENotification.mMessage) { michael@0: case REQUEST_TO_COMMIT_COMPOSITION: michael@0: //ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION: s=%d", aState); michael@0: RemoveIMEComposition(); michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); michael@0: return NS_OK; michael@0: case REQUEST_TO_CANCEL_COMPOSITION: michael@0: ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION"); michael@0: michael@0: // Cancel composition on Gecko side michael@0: if (mIMEComposing) { michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: WidgetCompositionEvent updateEvent(true, NS_COMPOSITION_UPDATE, michael@0: this); michael@0: InitEvent(updateEvent, nullptr); michael@0: DispatchEvent(&updateEvent); michael@0: michael@0: WidgetTextEvent textEvent(true, NS_TEXT_TEXT, this); michael@0: InitEvent(textEvent, nullptr); michael@0: DispatchEvent(&textEvent); michael@0: michael@0: WidgetCompositionEvent compEvent(true, NS_COMPOSITION_END, michael@0: this); michael@0: InitEvent(compEvent, nullptr); michael@0: DispatchEvent(&compEvent); michael@0: } michael@0: michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(REQUEST_TO_CANCEL_COMPOSITION); michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_FOCUS: michael@0: ALOGIME("IME: NOTIFY_IME_OF_FOCUS"); michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(NOTIFY_IME_OF_FOCUS); michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_BLUR: michael@0: ALOGIME("IME: NOTIFY_IME_OF_BLUR"); michael@0: michael@0: // Mask events because we lost focus. On the next focus event, michael@0: // Gecko will notify Java, and Java will send an acknowledge focus michael@0: // event back to Gecko. That is where we unmask event handling michael@0: mIMEMaskEventsCount++; michael@0: mIMEComposing = false; michael@0: mIMEComposingText.Truncate(); michael@0: michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(NOTIFY_IME_OF_BLUR); michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_SELECTION_CHANGE: michael@0: if (mIMEMaskSelectionUpdate) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE"); michael@0: michael@0: PostFlushIMEChanges(); michael@0: mIMESelectionChanged = true; michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_TEXT_CHANGE: michael@0: return NotifyIMEOfTextChange(aIMENotification); michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsWindow::SetInputContext(const InputContext& aContext, michael@0: const InputContextAction& aAction) michael@0: { michael@0: nsWindow *top = TopWindow(); michael@0: if (top && top->mFocus && this != top->mFocus) { michael@0: // We are using an IME event later to notify Java, and the IME event michael@0: // will be processed by the focused window. Therefore, to ensure the michael@0: // IME event uses the correct mInputContext, we need to let the focused michael@0: // window process SetInputContext michael@0: top->mFocus->SetInputContext(aContext, aAction); michael@0: return; michael@0: } michael@0: michael@0: ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X", michael@0: aContext.mIMEState.mEnabled, aContext.mIMEState.mOpen, michael@0: aAction.mCause, aAction.mFocusChange); michael@0: michael@0: mInputContext = aContext; michael@0: michael@0: // Ensure that opening the virtual keyboard is allowed for this specific michael@0: // InputContext depending on the content.ime.strict.policy pref michael@0: if (aContext.mIMEState.mEnabled != IMEState::DISABLED && michael@0: aContext.mIMEState.mEnabled != IMEState::PLUGIN && michael@0: Preferences::GetBool("content.ime.strict_policy", false) && michael@0: !aAction.ContentGotFocusByTrustedCause() && michael@0: !aAction.UserMightRequestOpenVKB()) { michael@0: return; michael@0: } michael@0: michael@0: IMEState::Enabled enabled = aContext.mIMEState.mEnabled; michael@0: michael@0: // Only show the virtual keyboard for plugins if mOpen is set appropriately. michael@0: // This avoids showing it whenever a plugin is focused. Bug 747492 michael@0: if (aContext.mIMEState.mEnabled == IMEState::PLUGIN && michael@0: aContext.mIMEState.mOpen != IMEState::OPEN) { michael@0: enabled = IMEState::DISABLED; michael@0: } michael@0: michael@0: mInputContext.mIMEState.mEnabled = enabled; michael@0: michael@0: if (enabled == IMEState::ENABLED && aAction.UserMightRequestOpenVKB()) { michael@0: // Don't reset keyboard when we should simply open the vkb michael@0: mozilla::widget::android::GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_OPEN_VKB); michael@0: return; michael@0: } michael@0: michael@0: if (mIMEUpdatingContext) { michael@0: return; michael@0: } michael@0: AndroidGeckoEvent *event = AndroidGeckoEvent::MakeIMEEvent( michael@0: AndroidGeckoEvent::IME_UPDATE_CONTEXT); michael@0: nsAppShell::gAppShell->PostEvent(event); michael@0: mIMEUpdatingContext = true; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(InputContext) michael@0: nsWindow::GetInputContext() michael@0: { michael@0: nsWindow *top = TopWindow(); michael@0: if (top && top->mFocus && this != top->mFocus) { michael@0: // We let the focused window process SetInputContext, michael@0: // so we should let it process GetInputContext as well. michael@0: return top->mFocus->GetInputContext(); michael@0: } michael@0: InputContext context = mInputContext; michael@0: context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; michael@0: // We assume that there is only one context per process on Android michael@0: context.mNativeIMEContext = nullptr; michael@0: return context; michael@0: } michael@0: michael@0: void michael@0: nsWindow::PostFlushIMEChanges() michael@0: { michael@0: if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) { michael@0: // Already posted michael@0: return; michael@0: } michael@0: AndroidGeckoEvent *event = AndroidGeckoEvent::MakeIMEEvent( michael@0: AndroidGeckoEvent::IME_FLUSH_CHANGES); michael@0: nsAppShell::gAppShell->PostEvent(event); michael@0: } michael@0: michael@0: void michael@0: nsWindow::FlushIMEChanges() michael@0: { michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: for (uint32_t i = 0; i < mIMETextChanges.Length(); i++) { michael@0: IMEChange &change = mIMETextChanges[i]; michael@0: michael@0: WidgetQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, this); michael@0: InitEvent(event, nullptr); michael@0: event.InitForQueryTextContent(change.mStart, michael@0: change.mNewEnd - change.mStart); michael@0: DispatchEvent(&event); michael@0: if (!event.mSucceeded) michael@0: return; michael@0: michael@0: mozilla::widget::android::GeckoAppShell::NotifyIMEChange(event.mReply.mString, michael@0: change.mStart, michael@0: change.mOldEnd, michael@0: change.mNewEnd); michael@0: } michael@0: mIMETextChanges.Clear(); michael@0: michael@0: if (mIMESelectionChanged) { michael@0: WidgetQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, this); michael@0: InitEvent(event, nullptr); michael@0: michael@0: DispatchEvent(&event); michael@0: if (!event.mSucceeded) michael@0: return; michael@0: michael@0: mozilla::widget::android::GeckoAppShell::NotifyIMEChange(EmptyString(), michael@0: (int32_t) event.GetSelectionStart(), michael@0: (int32_t) event.GetSelectionEnd(), -1); michael@0: mIMESelectionChanged = false; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsWindow::NotifyIMEOfTextChange(const IMENotification& aIMENotification) michael@0: { michael@0: MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE, michael@0: "NotifyIMEOfTextChange() is called with invaild notification"); michael@0: michael@0: if (mIMEMaskTextUpdate) michael@0: return NS_OK; michael@0: michael@0: ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d", michael@0: aIMENotification.mTextChangeData.mStartOffset, michael@0: aIMENotification.mTextChangeData.mOldEndOffset, michael@0: aIMENotification.mTextChangeData.mNewEndOffset); michael@0: michael@0: /* Make sure Java's selection is up-to-date */ michael@0: mIMESelectionChanged = false; michael@0: NotifyIME(NOTIFY_IME_OF_SELECTION_CHANGE); michael@0: PostFlushIMEChanges(); michael@0: michael@0: mIMETextChanges.AppendElement(IMEChange(aIMENotification)); michael@0: // Now that we added a new range we need to go back and michael@0: // update all the ranges before that. michael@0: // Ranges that have offsets which follow this new range michael@0: // need to be updated to reflect new offsets michael@0: int32_t delta = aIMENotification.mTextChangeData.AdditionalLength(); michael@0: for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) { michael@0: IMEChange &previousChange = mIMETextChanges[i]; michael@0: if (previousChange.mStart > michael@0: static_cast( michael@0: aIMENotification.mTextChangeData.mOldEndOffset)) { michael@0: previousChange.mStart += delta; michael@0: previousChange.mOldEnd += delta; michael@0: previousChange.mNewEnd += delta; michael@0: } michael@0: } michael@0: michael@0: // Now go through all ranges to merge any ranges that are connected michael@0: // srcIndex is the index of the range to merge from michael@0: // dstIndex is the index of the range to potentially merge into michael@0: int32_t srcIndex = mIMETextChanges.Length() - 1; michael@0: int32_t dstIndex = srcIndex; michael@0: michael@0: while (--dstIndex >= 0) { michael@0: IMEChange &src = mIMETextChanges[srcIndex]; michael@0: IMEChange &dst = mIMETextChanges[dstIndex]; michael@0: // When merging a more recent change into an older michael@0: // change, we need to compare recent change's (start, oldEnd) michael@0: // range to the older change's (start, newEnd) michael@0: if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) { michael@0: // No overlap between ranges michael@0: continue; michael@0: } michael@0: // When merging two ranges, there are generally four posibilities: michael@0: // [----(----]----), (----[----]----), michael@0: // [----(----)----], (----[----)----] michael@0: // where [----] is the first range and (----) is the second range michael@0: // As seen above, the start of the merged range is always the lesser michael@0: // of the two start offsets. OldEnd and NewEnd then need to be michael@0: // adjusted separately depending on the case. In any case, the change michael@0: // in text length of the merged range should be the sum of text length michael@0: // changes of the two original ranges, i.e., michael@0: // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2 michael@0: dst.mStart = std::min(dst.mStart, src.mStart); michael@0: if (src.mOldEnd < dst.mNewEnd) { michael@0: // New range overlaps or is within previous range; merge michael@0: dst.mNewEnd += src.mNewEnd - src.mOldEnd; michael@0: } else { // src.mOldEnd >= dst.mNewEnd michael@0: // New range overlaps previous range; merge michael@0: dst.mOldEnd += src.mOldEnd - dst.mNewEnd; michael@0: dst.mNewEnd = src.mNewEnd; michael@0: } michael@0: // src merged to dst; delete src. michael@0: mIMETextChanges.RemoveElementAt(srcIndex); michael@0: // Any ranges that we skip over between src and dst are not mergeable michael@0: // so we can safely continue the merge starting at dst michael@0: srcIndex = dstIndex; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIMEUpdatePreference michael@0: nsWindow::GetIMEUpdatePreference() michael@0: { michael@0: return nsIMEUpdatePreference( michael@0: nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE | michael@0: nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE); michael@0: } michael@0: michael@0: void michael@0: nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager, nsIntRect aRect) michael@0: { michael@0: JNIEnv *env = GetJNIForThread(); michael@0: michael@0: AutoLocalJNIFrame jniFrame(env); michael@0: michael@0: mozilla::widget::android::GeckoLayerClient* client = AndroidBridge::Bridge()->GetLayerClient(); michael@0: if (!client || client->isNull()) { michael@0: ALOG_BRIDGE("Exceptional Exit: %s", __PRETTY_FUNCTION__); michael@0: return; michael@0: } michael@0: michael@0: jobject frameObj = client->CreateFrame(); michael@0: if (!frameObj) { michael@0: NS_WARNING("Warning: unable to obtain a LayerRenderer frame; aborting window underlay draw"); michael@0: return; michael@0: } michael@0: michael@0: mLayerRendererFrame.Init(env, frameObj); michael@0: if (!WidgetPaintsBackground()) { michael@0: return; michael@0: } michael@0: michael@0: gl::GLContext* gl = static_cast(aManager->GetCompositor())->gl(); michael@0: gl::ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST); michael@0: gl::ScopedScissorRect scopedScissorRectState(gl); michael@0: michael@0: client->ActivateProgram(); michael@0: if (!mLayerRendererFrame.BeginDrawing(&jniFrame)) return; michael@0: if (!mLayerRendererFrame.DrawBackground(&jniFrame)) return; michael@0: client->DeactivateProgram(); // redundant, but in case somebody adds code after this... michael@0: } michael@0: michael@0: void michael@0: nsWindow::DrawWindowOverlay(LayerManagerComposite* aManager, nsIntRect aRect) michael@0: { michael@0: PROFILER_LABEL("nsWindow", "DrawWindowOverlay"); michael@0: JNIEnv *env = GetJNIForThread(); michael@0: michael@0: AutoLocalJNIFrame jniFrame(env); michael@0: michael@0: if (mLayerRendererFrame.isNull()) { michael@0: NS_WARNING("Warning: do not have a LayerRenderer frame; aborting window overlay draw"); michael@0: return; michael@0: } michael@0: michael@0: mozilla::widget::android::GeckoLayerClient* client = AndroidBridge::Bridge()->GetLayerClient(); michael@0: michael@0: gl::GLContext* gl = static_cast(aManager->GetCompositor())->gl(); michael@0: gl::ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST); michael@0: gl::ScopedScissorRect scopedScissorRectState(gl); michael@0: michael@0: client->ActivateProgram(); michael@0: if (!mLayerRendererFrame.DrawForeground(&jniFrame)) return; michael@0: if (!mLayerRendererFrame.EndDrawing(&jniFrame)) return; michael@0: client->DeactivateProgram(); michael@0: mLayerRendererFrame.Dispose(env); michael@0: } michael@0: michael@0: // off-main-thread compositor fields and functions michael@0: michael@0: StaticRefPtr nsWindow::sApzcTreeManager; michael@0: StaticRefPtr nsWindow::sLayerManager; michael@0: StaticRefPtr nsWindow::sCompositorParent; michael@0: StaticRefPtr nsWindow::sCompositorChild; michael@0: bool nsWindow::sCompositorPaused = true; michael@0: michael@0: void michael@0: nsWindow::SetCompositor(mozilla::layers::LayerManager* aLayerManager, michael@0: mozilla::layers::CompositorParent* aCompositorParent, michael@0: mozilla::layers::CompositorChild* aCompositorChild) michael@0: { michael@0: sLayerManager = aLayerManager; michael@0: sCompositorParent = aCompositorParent; michael@0: sCompositorChild = aCompositorChild; michael@0: } michael@0: michael@0: void michael@0: nsWindow::ScheduleComposite() michael@0: { michael@0: if (sCompositorParent) { michael@0: sCompositorParent->ScheduleRenderOnCompositorThread(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsWindow::ScheduleResumeComposition(int width, int height) michael@0: { michael@0: if (sCompositorParent && sCompositorParent->ScheduleResumeOnCompositorThread(width, height)) { michael@0: sCompositorPaused = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsWindow::ForceIsFirstPaint() michael@0: { michael@0: if (sCompositorParent) { michael@0: sCompositorParent->ForceIsFirstPaint(); michael@0: } michael@0: } michael@0: michael@0: float michael@0: nsWindow::ComputeRenderIntegrity() michael@0: { michael@0: if (sCompositorParent) { michael@0: return sCompositorParent->ComputeRenderIntegrity(); michael@0: } michael@0: michael@0: return 1.f; michael@0: } michael@0: michael@0: bool michael@0: nsWindow::WidgetPaintsBackground() michael@0: { michael@0: static bool sWidgetPaintsBackground = true; michael@0: static bool sWidgetPaintsBackgroundPrefCached = false; michael@0: michael@0: if (!sWidgetPaintsBackgroundPrefCached) { michael@0: sWidgetPaintsBackgroundPrefCached = true; michael@0: mozilla::Preferences::AddBoolVarCache(&sWidgetPaintsBackground, michael@0: "android.widget_paints_background", michael@0: true); michael@0: } michael@0: michael@0: return sWidgetPaintsBackground; michael@0: } michael@0: michael@0: bool michael@0: nsWindow::NeedsPaint() michael@0: { michael@0: if (sCompositorPaused || FindTopLevel() != nsWindow::TopWindow() || !GetLayerManager(nullptr)) { michael@0: return false; michael@0: } michael@0: return nsIWidget::NeedsPaint(); michael@0: } michael@0: michael@0: CompositorParent* michael@0: nsWindow::NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight) michael@0: { michael@0: return new CompositorParent(this, true, aSurfaceWidth, aSurfaceHeight); michael@0: } michael@0: michael@0: mozilla::layers::APZCTreeManager* michael@0: nsWindow::GetAPZCTreeManager() michael@0: { michael@0: if (!sApzcTreeManager) { michael@0: CompositorParent* compositor = sCompositorParent; michael@0: if (!compositor) { michael@0: return nullptr; michael@0: } michael@0: uint64_t rootLayerTreeId = compositor->RootLayerTreeId(); michael@0: CompositorParent::SetControllerForLayerTree(rootLayerTreeId, AndroidBridge::Bridge()); michael@0: sApzcTreeManager = CompositorParent::GetAPZCTreeManager(rootLayerTreeId); michael@0: } michael@0: return sApzcTreeManager; michael@0: } michael@0: michael@0: uint64_t michael@0: nsWindow::RootLayerTreeId() michael@0: { michael@0: MOZ_ASSERT(sCompositorParent); michael@0: return sCompositorParent->RootLayerTreeId(); michael@0: }