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
This commit is contained in:
Jim Chen 2018-11-06 00:12:06 -05:00
parent 2944534e33
commit 8a599aa552
7 changed files with 131 additions and 64 deletions

View File

@ -18,8 +18,8 @@ interface IGeckoEditableParent {
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);

View File

@ -70,17 +70,16 @@ public final class GeckoEditableChild extends JNIObject implements IGeckoEditabl
}
}
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) {
@ -91,6 +90,13 @@ public final class GeckoEditableChild extends JNIObject implements IGeckoEditabl
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 {
@ -175,7 +181,8 @@ public final class GeckoEditableChild extends JNIObject implements IGeckoEditabl
}
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);
}

View File

@ -1292,7 +1292,7 @@ import android.view.inputmethod.EditorInfo;
}
@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.
@ -1304,9 +1304,14 @@ import android.view.inputmethod.EditorInfo;
"\", 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

View File

@ -16,6 +16,7 @@
#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>
@ -1425,27 +1426,34 @@ GeckoEditableSupport::SetInputContext(const InputContext& aContext,
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
@ -1456,5 +1464,57 @@ GeckoEditableSupport::GetInputContext()
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

View File

@ -21,6 +21,10 @@ namespace mozilla {
class TextComposition;
namespace dom {
class TabChild;
}
namespace widget {
class GeckoEditableSupport final
@ -128,6 +132,8 @@ class GeckoEditableSupport final
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>
@ -162,6 +168,8 @@ public:
std::move(aCall)));
}
static void SetOnTabChild(dom::TabChild* aTabChild);
// Constructor for main process GeckoEditableChild.
GeckoEditableSupport(nsWindow::NativePtr<GeckoEditableSupport>* aPtr,
nsWindow* aWindow,
@ -215,6 +223,18 @@ public:
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);

View File

@ -22,7 +22,6 @@
#include "nsIDOMWakeLockListener.h"
#include "nsIPowerManagerService.h"
#include "nsISpeculativeConnect.h"
#include "nsITabChild.h"
#include "nsIURIFixup.h"
#include "nsCategoryManagerUtils.h"
#include "nsCDefaultURIFixup.h"
@ -553,6 +552,7 @@ nsAppShell::Init()
obsServ->AddObserver(this, "chrome-document-loaded", false);
} else {
obsServ->AddObserver(this, "content-document-global-created", false);
obsServ->AddObserver(this, "geckoview-content-global-transferred", false);
}
}
@ -678,39 +678,15 @@ nsAppShell::Observe(nsISupports* aSubject,
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::GeckoEditableSupport::SetOnTabChild(
domWidget->GetOwningTabChild());
widget::TextEventDispatcherListener* listener =
widget->GetNativeTextEventDispatcherListener();
if (listener && listener !=
static_cast<widget::TextEventDispatcherListener*>(widget)) {
// We already set a listener before.
return NS_OK;
}
// 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) {

View File

@ -1363,15 +1363,14 @@ 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;
}