Bug 1499429 - 3. Transfer to new GeckoEditableParent during session transfer; r=esawin
authorJim Chen <nchen@mozilla.com>
Tue, 06 Nov 2018 00:12:06 -0500
changeset 444484 7688a1109239930cc5baab5bb11b51facc7816d1
parent 444483 4d2e501b020db20ffa117960b595d23f94de26b3
child 444485 578c92bc890e44eddcc8bfd7adf003f3a115ec51
push id109600
push usernchen@mozilla.com
push dateTue, 06 Nov 2018 05:12:42 +0000
treeherdermozilla-inbound@ff32e0590061 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin
bugs1499429
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1499429 - 3. Transfer to new GeckoEditableParent during session transfer; r=esawin During a session transfer, update existing GeckoEditableChild instances in the parent and child processes to use the new GeckoEditableParent instance that corresponds to the new session. If the GeckoEditableChild has focus, take additional steps to make sure the GeckoEditableParent receives current input context and focus information. Differential Revision: https://phabricator.services.mozilla.com/D8996
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/IGeckoEditableParent.aidl
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
widget/android/GeckoEditableSupport.cpp
widget/android/GeckoEditableSupport.h
widget/android/nsAppShell.cpp
widget/android/nsWindow.cpp
--- a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/IGeckoEditableParent.aidl
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/IGeckoEditableParent.aidl
@@ -13,18 +13,18 @@ import org.mozilla.gecko.IGeckoEditableC
 interface IGeckoEditableParent {
     // Set the default child to forward events to, when there is no focused child.
     void setDefaultChild(IGeckoEditableChild child);
 
     // Notify an IME event of a type defined in GeckoEditableListener.
     void notifyIME(IGeckoEditableChild child, int type);
 
     // Notify a change in editor state or type.
-    void notifyIMEContext(int state, String typeHint, String modeHint, String actionHint,
-                          int flags);
+    void notifyIMEContext(IBinder token, int state, String typeHint, String modeHint,
+                          String actionHint, int flags);
 
     // Notify a change in editor selection.
     void onSelectionChange(IBinder token, int start, int end);
 
     // Notify a change in editor text.
     void onTextChange(IBinder token, in CharSequence text,
                       int start, int unboundedOldEnd);
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java
@@ -65,37 +65,43 @@ public final class GeckoEditableChild ex
         }
 
         @Override // IGeckoEditableChild
         public void onImeRequestCursorUpdates(int requestMode) {
             GeckoEditableChild.this.onImeRequestCursorUpdates(requestMode);
         }
     }
 
-    private final IGeckoEditableParent mEditableParent;
     private final IGeckoEditableChild mEditableChild;
     private final boolean mIsDefault;
 
+    private IGeckoEditableParent mEditableParent;
     private int mCurrentTextLength; // Used by Gecko thread
 
     @WrapForJNI(calledFrom = "gecko")
     private GeckoEditableChild(final IGeckoEditableParent editableParent,
                                final boolean isDefault) {
         mIsDefault = isDefault;
-        mEditableParent = editableParent;
 
         final IBinder binder = editableParent.asBinder();
         if (binder.queryLocalInterface(IGeckoEditableParent.class.getName()) != null) {
             // IGeckoEditableParent is local; i.e. we're in the main process.
             mEditableChild = this;
         } else {
             // IGeckoEditableParent is remote; i.e. we're in a content process.
             mEditableChild = new RemoteChild();
         }
 
+        setParent(editableParent);
+    }
+
+    @WrapForJNI(calledFrom = "gecko")
+    private void setParent(final IGeckoEditableParent editableParent) {
+        mEditableParent = editableParent;
+
         if (mIsDefault) {
             // Tell the parent we're the default child.
             try {
                 editableParent.setDefaultChild(mEditableChild);
             } catch (final RemoteException e) {
                 Log.e(LOGTAG, "Failed to set default child", e);
             }
         }
@@ -170,17 +176,18 @@ public final class GeckoEditableChild ex
         if (DEBUG) {
             ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "notifyIMEContext(" + state + ", \"" +
                           typeHint + "\", \"" + modeHint + "\", \"" + actionHint +
                           "\", 0x" + Integer.toHexString(flags) + ")");
         }
 
         try {
-            mEditableParent.notifyIMEContext(state, typeHint, modeHint, actionHint, flags);
+            mEditableParent.notifyIMEContext(mEditableChild.asBinder(), state, typeHint,
+                                             modeHint, actionHint, flags);
         } catch (final RemoteException e) {
             Log.e(LOGTAG, "Remote call failed", e);
         }
     }
 
     @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore")
     private void onSelectionChange(final int start, final int end) throws RemoteException {
         if (DEBUG) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
@@ -1287,31 +1287,36 @@ import android.view.inputmethod.EditorIn
         }
 
         if (mListener != null) {
             mListener.notifyIME(type);
         }
     }
 
     @Override // IGeckoEditableParent
-    public void notifyIMEContext(final int state, final String typeHint,
+    public void notifyIMEContext(final IBinder token, final int state, final String typeHint,
                                  final String modeHint, final String actionHint,
                                  final int flags) {
         // On Gecko or binder thread.
         if (DEBUG) {
             Log.d(LOGTAG, "notifyIMEContext(" +
                           getConstantName(SessionTextInput.EditableListener.class,
                                           "IME_STATE_", state) +
                           ", \"" + typeHint + "\", \"" + modeHint + "\", \"" + actionHint +
                           "\", 0x" + Integer.toHexString(flags) + ")");
         }
 
-        // Don't check token for notifyIMEContext, because the calls all come
-        // from the parent process.
-        ThreadUtils.assertOnGeckoThread();
+        // Regular notifyIMEContext calls all come from the parent process (with the default child),
+        // so always allow calls from there. We can get additional notifyIMEContext calls during
+        // a session transfer; calls in those cases can come from child processes, and we must
+        // perform a token check in that situation.
+        if (token != mDefaultChild.asBinder() &&
+            !binderCheckToken(token, /* allowNull */ false)) {
+            return;
+        }
 
         mIcPostHandler.post(new Runnable() {
             @Override
             public void run() {
                 icNotifyIMEContext(state, typeHint, modeHint, actionHint, flags);
             }
         });
     }
--- a/widget/android/GeckoEditableSupport.cpp
+++ b/widget/android/GeckoEditableSupport.cpp
@@ -11,16 +11,17 @@
 #include "PuppetWidget.h"
 #include "nsIContent.h"
 
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEventDispatcherListener.h"
 #include "mozilla/TextEvents.h"
+#include "mozilla/dom/TabChild.h"
 
 #include <android/api-level.h>
 #include <android/input.h>
 #include <android/log.h>
 
 #ifdef DEBUG_ANDROID_IME
 #define ALOGIME(args...) __android_log_print(ANDROID_LOG_INFO, \
                                              "GeckoEditableSupport" , ## args)
@@ -1420,41 +1421,100 @@ GeckoEditableSupport::SetInputContext(co
 
     if (mInputContext.mIMEState.mEnabled != IMEState::DISABLED &&
         aAction.UserMightRequestOpenVKB()) {
         // Don't reset keyboard when we should simply open the vkb
         mEditable->NotifyIME(EditableListener::NOTIFY_IME_OPEN_VKB);
         return;
     }
 
-    const bool inPrivateBrowsing = mInputContext.mInPrivateBrowsing;
+    // Post an event to keep calls in order relative to NotifyIME.
+    nsAppShell::PostEvent([this, self = RefPtr<GeckoEditableSupport>(this),
+                           context = mInputContext, action = aAction] {
+        nsCOMPtr<nsIWidget> widget = GetWidget();
+
+        if (!widget || widget->Destroyed()) {
+            return;
+        }
+        NotifyIMEContext(context, action);
+    });
+}
+
+void
+GeckoEditableSupport::NotifyIMEContext(const InputContext& aContext,
+                                       const InputContextAction& aAction)
+{
+    const bool inPrivateBrowsing = aContext.mInPrivateBrowsing;
     const bool isUserAction =
             aAction.IsHandlingUserInput() || aContext.mHasHandledUserInput;
     const int32_t flags =
             (inPrivateBrowsing ? EditableListener::IME_FLAG_PRIVATE_BROWSING : 0) |
             (isUserAction ? EditableListener::IME_FLAG_USER_ACTION : 0);
 
-    // Post an event to keep calls in order relative to NotifyIME.
-    nsAppShell::PostEvent([this, self = RefPtr<GeckoEditableSupport>(this),
-                           flags, context = mInputContext] {
-        nsCOMPtr<nsIWidget> widget = GetWidget();
-
-        if (!widget || widget->Destroyed()) {
-            return;
-        }
-        mEditable->NotifyIMEContext(context.mIMEState.mEnabled,
-                                    context.mHTMLInputType,
-                                    context.mHTMLInputInputmode,
-                                    context.mActionHint,
-                                    flags);
-    });
+    mEditable->NotifyIMEContext(aContext.mIMEState.mEnabled,
+                                aContext.mHTMLInputType,
+                                aContext.mHTMLInputInputmode,
+                                aContext.mActionHint,
+                                flags);
 }
 
 InputContext
 GeckoEditableSupport::GetInputContext()
 {
     InputContext context = mInputContext;
     context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
     return context;
 }
 
+void
+GeckoEditableSupport::SetOnTabChild(dom::TabChild* aTabChild)
+{
+    MOZ_ASSERT(!XRE_IsParentProcess());
+    NS_ENSURE_TRUE_VOID(aTabChild);
+
+    const dom::ContentChild* const contentChild =
+            dom::ContentChild::GetSingleton();
+    RefPtr<widget::PuppetWidget> widget(aTabChild->WebWidget());
+    NS_ENSURE_TRUE_VOID(contentChild && widget);
+
+    // Get the content/tab ID in order to get the correct
+    // IGeckoEditableParent object, which GeckoEditableChild uses to
+    // communicate with the parent process.
+    const uint64_t contentId = contentChild->GetID();
+    const uint64_t tabId = aTabChild->GetTabId();
+    NS_ENSURE_TRUE_VOID(contentId && tabId);
+
+    auto editableParent = java::GeckoServiceChildProcess::GetEditableParent(
+            contentId, tabId);
+    NS_ENSURE_TRUE_VOID(editableParent);
+
+    RefPtr<widget::TextEventDispatcherListener> listener =
+            widget->GetNativeTextEventDispatcherListener();
+
+    if (!listener || listener.get() ==
+            static_cast<widget::TextEventDispatcherListener*>(widget)) {
+        // We need to set a new listener.
+        auto editableChild = java::GeckoEditableChild::New(editableParent,
+                                                           /* default */ false);
+        RefPtr<widget::GeckoEditableSupport> editableSupport =
+                new widget::GeckoEditableSupport(editableChild);
+
+        // Tell PuppetWidget to use our listener for IME operations.
+        widget->SetNativeTextEventDispatcherListener(editableSupport);
+        return;
+    }
+
+    // We need to update the existing listener to use the new parent.
+
+    // We expect the existing TextEventDispatcherListener to be a
+    // GeckoEditableSupport object, so we perform a sanity check to make
+    // sure, by comparing their respective vtable pointers.
+    RefPtr<widget::GeckoEditableSupport> dummy =
+            new widget::GeckoEditableSupport(/* child */ nullptr);
+    NS_ENSURE_TRUE_VOID(*reinterpret_cast<const uintptr_t*>(listener.get()) ==
+            *reinterpret_cast<const uintptr_t*>(dummy.get()));
+
+    static_cast<widget::GeckoEditableSupport*>(
+            listener.get())->TransferParent(editableParent);
+}
+
 } // namespace widget
 } // namespace mozilla
--- a/widget/android/GeckoEditableSupport.h
+++ b/widget/android/GeckoEditableSupport.h
@@ -16,16 +16,20 @@
 #include "mozilla/UniquePtr.h"
 
 class nsWindow;
 
 namespace mozilla {
 
 class TextComposition;
 
+namespace dom {
+class TabChild;
+}
+
 namespace widget {
 
 class GeckoEditableSupport final
     : public TextEventDispatcherListener
     , public java::GeckoEditableChild::Natives<GeckoEditableSupport>
 {
     /*
         Rules for managing IME between Gecko and Java:
@@ -123,16 +127,18 @@ class GeckoEditableSupport final
     void SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg);
     void AddIMETextChange(const IMETextChange& aChange);
     void PostFlushIMEChanges();
     void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
     void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
     void AsyncNotifyIME(int32_t aNotification);
     void UpdateCompositionRects();
     bool DoReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText);
+    void NotifyIMEContext(const InputContext& aContext,
+                          const InputContextAction& aAction);
 
 public:
     template<typename Functor>
     static void OnNativeCall(Functor&& aCall)
     {
         struct IMEEvent : nsAppShell::LambdaEvent<Functor>
         {
             explicit IMEEvent(Functor&& l) : nsAppShell::LambdaEvent<Functor>(std::move(l)) {}
@@ -157,16 +163,18 @@ public:
                 }
                 nsAppShell::LambdaEvent<Functor>::Run();
             }
         };
         nsAppShell::PostEvent(mozilla::MakeUnique<IMEEvent>(
                 std::move(aCall)));
     }
 
+    static void SetOnTabChild(dom::TabChild* aTabChild);
+
     // Constructor for main process GeckoEditableChild.
     GeckoEditableSupport(nsWindow::NativePtr<GeckoEditableSupport>* aPtr,
                          nsWindow* aWindow,
                          java::GeckoEditableChild::Param aEditableChild)
         : mIsRemote(!aWindow)
         , mWindow(aPtr, aWindow)
         , mEditable(aEditableChild)
         , mEditableAttached(!mIsRemote)
@@ -210,16 +218,28 @@ public:
     InputContext GetInputContext();
 
     // GeckoEditableChild methods
     using EditableBase::AttachNative;
     using EditableBase::DisposeNative;
 
     const java::GeckoEditableChild::Ref& GetJavaEditable() { return mEditable; }
 
+    void TransferParent(jni::Object::Param aEditableParent) {
+        mEditable->SetParent(aEditableParent);
+
+        // If we are already focused, make sure the new parent has our token
+        // and focus information, so it can accept additional calls from us.
+        if (mIMEFocusCount > 0) {
+            mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN);
+            NotifyIMEContext(mInputContext, InputContextAction());
+            mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS);
+        }
+    }
+
     void OnDetach(already_AddRefed<Runnable> aDisposer)
     {
         RefPtr<GeckoEditableSupport> self(this);
         nsAppShell::PostEvent([this, self,
                                disposer = RefPtr<Runnable>(aDisposer)] {
             mEditableAttached = false;
             disposer->Run();
         });
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -17,17 +17,16 @@
 #include "nsIObserverService.h"
 #include "nsIAppStartup.h"
 #include "nsIGeolocationProvider.h"
 #include "nsCacheService.h"
 #include "nsIDOMEventListener.h"
 #include "nsIDOMWakeLockListener.h"
 #include "nsIPowerManagerService.h"
 #include "nsISpeculativeConnect.h"
-#include "nsITabChild.h"
 #include "nsIURIFixup.h"
 #include "nsCategoryManagerUtils.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsToolkitCompsCID.h"
 #include "nsGeoPosition.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Telemetry.h"
@@ -548,16 +547,17 @@ nsAppShell::Init()
         obsServ->AddObserver(this, "quit-application", false);
         obsServ->AddObserver(this, "quit-application-granted", false);
         obsServ->AddObserver(this, "xpcom-shutdown", false);
 
         if (XRE_IsParentProcess()) {
             obsServ->AddObserver(this, "chrome-document-loaded", false);
         } else {
             obsServ->AddObserver(this, "content-document-global-created", false);
+            obsServ->AddObserver(this, "geckoview-content-global-transferred", false);
         }
     }
 
     if (sPowerManagerService)
         sPowerManagerService->AddWakeLockListener(sWakeLockListener);
 
     Preferences::AddStrongObservers(this, kObservedPrefs);
     mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true);
@@ -673,49 +673,25 @@ nsAppShell::Observe(nsISupports* aSubjec
         MOZ_ASSERT(!XRE_IsParentProcess());
 
         nsCOMPtr<mozIDOMWindowProxy> domWindow = do_QueryInterface(aSubject);
         MOZ_ASSERT(domWindow);
         nsCOMPtr<nsIWidget> domWidget = widget::WidgetUtils::DOMWindowToWidget(
                 nsPIDOMWindowOuter::From(domWindow));
         NS_ENSURE_TRUE(domWidget, NS_OK);
 
-        dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
-        dom::TabChild* tabChild = domWidget->GetOwningTabChild();
-        RefPtr<widget::PuppetWidget> widget(tabChild->WebWidget());
-        NS_ENSURE_TRUE(contentChild && tabChild && widget, NS_OK);
-
-        widget::TextEventDispatcherListener* listener =
-                widget->GetNativeTextEventDispatcherListener();
-        if (listener && listener !=
-                static_cast<widget::TextEventDispatcherListener*>(widget)) {
-            // We already set a listener before.
-            return NS_OK;
-        }
+        widget::GeckoEditableSupport::SetOnTabChild(
+                domWidget->GetOwningTabChild());
 
-        // Get the content/tab ID in order to get the correct
-        // IGeckoEditableParent object, which GeckoEditableChild uses to
-        // communicate with the parent process.
-        const uint64_t contentId = contentChild->GetID();
-        const uint64_t tabId = tabChild->GetTabId();
-        NS_ENSURE_TRUE(contentId && tabId, NS_OK);
-
-        auto editableParent = java::GeckoServiceChildProcess::GetEditableParent(
-                contentId, tabId);
-        NS_ENSURE_TRUE(editableParent, NS_OK);
-
-        auto editableChild = java::GeckoEditableChild::New(editableParent,
-                                                           /* default */ false);
-        NS_ENSURE_TRUE(editableChild, NS_OK);
-
-        RefPtr<widget::GeckoEditableSupport> editableSupport =
-                new widget::GeckoEditableSupport(editableChild);
-
-        // Tell PuppetWidget to use our listener for IME operations.
-        widget->SetNativeTextEventDispatcherListener(editableSupport);
+    } else if (!strcmp(aTopic, "geckoview-content-global-transferred")) {
+        // We're transferring to a new GeckoEditableParent, so notify the
+        // existing GeckoEditableChild instance associated with the docshell.
+        nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aSubject);
+        widget::GeckoEditableSupport::SetOnTabChild(
+                dom::TabChild::GetFrom(docShell));
     }
 
     if (removeObserver) {
         nsCOMPtr<nsIObserverService> obsServ =
             mozilla::services::GetObserverService();
         if (obsServ) {
             obsServ->RemoveObserver(this, aTopic);
         }
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1358,25 +1358,24 @@ nsWindow::GeckoViewSupport::Transfer(con
                 compositor->OnCompositorAttached();
             });
 }
 
 void
 nsWindow::GeckoViewSupport::AttachEditable(const GeckoSession::Window::LocalRef& inst,
                                            jni::Object::Param aEditableParent)
 {
-    auto editableChild = java::GeckoEditableChild::New(aEditableParent,
-                                                       /* default */ true);
-
-    if (window.mEditableSupport) {
-        window.mEditableSupport.Detach(
-                window.mEditableSupport->GetJavaEditable());
+    if (!window.mEditableSupport) {
+        auto editableChild = java::GeckoEditableChild::New(aEditableParent,
+                                                           /* default */ true);
+        window.mEditableSupport.Attach(editableChild, &window, editableChild);
+    } else {
+        window.mEditableSupport->TransferParent(aEditableParent);
     }
 
-    window.mEditableSupport.Attach(editableChild, &window, editableChild);
     window.mEditableParent = aEditableParent;
 }
 
 void
 nsWindow::GeckoViewSupport::AttachAccessibility(const GeckoSession::Window::LocalRef& inst,
                                                 jni::Object::Param aSessionAccessibility)
 {
     MOZ_ASSERT(!window.mSessionAccessibility);