--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -41,17 +41,27 @@ import android.support.annotation.Nullab
import android.support.annotation.NonNull;
import android.util.Log;
public class GeckoSession extends LayerSession
implements Parcelable {
private static final String LOGTAG = "GeckoSession";
private static final boolean DEBUG = false;
- /* package */ enum State implements NativeQueue.State {
+ // Type of changes given to onWindowChanged.
+ // Window has been cleared due to the session being closed.
+ private static final int WINDOW_CLOSE = 0;
+ // Window has been set due to the session being opened.
+ private static final int WINDOW_OPEN = 1; // Window has been opened.
+ // Window has been cleared due to the session being transferred to another session.
+ private static final int WINDOW_TRANSFER_OUT = 2; // Window has been transfer.
+ // Window has been set due to another session being transferred to this one.
+ private static final int WINDOW_TRANSFER_IN = 3;
+
+ private enum State implements NativeQueue.State {
INITIAL(0),
READY(1);
private final int mRank;
private State(int rank) {
mRank = rank;
}
@@ -304,16 +314,23 @@ public class GeckoSession extends LayerS
delegate.onMediaPermissionRequest(
GeckoSession.this, message.getString("uri"),
videos, audios, new PermissionCallback("media", callback));
}
}
};
+ /* package */ int handlersCount;
+
+ private final GeckoSessionHandler<?>[] mSessionHandlers = new GeckoSessionHandler<?>[] {
+ mContentHandler, mNavigationHandler, mProgressHandler, mScrollHandler,
+ mTrackingProtectionHandler, mPermissionHandler
+ };
+
private static class PermissionCallback implements
PermissionDelegate.Callback, PermissionDelegate.MediaCallback {
private final String mType;
private EventCallback mCallback;
public PermissionCallback(final String type, final EventCallback callback) {
mType = type;
@@ -397,64 +414,79 @@ public class GeckoSession extends LayerS
@WrapForJNI(dispatchTo = "proxy")
public static native void open(Window instance, Compositor compositor,
EventDispatcher dispatcher,
GeckoBundle settings, String chromeUri,
int screenId, boolean privateMode, String id);
@Override // JNIObject
- protected void disposeNative() {
- // Detach ourselves from the binder as well, to prevent this window from being
- // read from any parcels.
- asBinder().attachInterface(null, Window.class.getName());
-
- // Reset our queue, so we don't end up with queued calls on a disposed object.
- synchronized (this) {
- mNativeQueue.reset(State.INITIAL);
- }
-
+ public void disposeNative() {
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
nativeDisposeNative();
} else {
GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
this, "nativeDisposeNative");
}
}
@WrapForJNI(dispatchTo = "proxy", stubName = "DisposeNative")
private native void nativeDisposeNative();
- @WrapForJNI(dispatchTo = "proxy")
- public native void close();
+ public void close() {
+ // Reset our queue, so we don't end up with queued calls on a disposed object.
+ synchronized (this) {
+ if (mNativeQueue == null) {
+ // Already closed elsewhere.
+ return;
+ }
+ mNativeQueue.reset(State.INITIAL);
+ mNativeQueue = null;
+ }
+
+ // Detach ourselves from the binder as well, to prevent this window from being
+ // read from any parcels.
+ asBinder().attachInterface(null, Window.class.getName());
+
+ if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+ nativeClose();
+ } else {
+ GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
+ this, "nativeClose");
+ }
+ }
+
+ @WrapForJNI(dispatchTo = "proxy", stubName = "Close")
+ private native void nativeClose();
@WrapForJNI(dispatchTo = "proxy")
public native void transfer(Compositor compositor, EventDispatcher dispatcher,
GeckoBundle settings);
@WrapForJNI(calledFrom = "gecko")
private synchronized void onTransfer(final EventDispatcher dispatcher) {
final NativeQueue nativeQueue = dispatcher.getNativeQueue();
- if (mNativeQueue != nativeQueue) {
+ if (mNativeQueue != null && mNativeQueue != nativeQueue) {
// Set new queue to the same state as the old queue,
// then return the old queue to its initial state if applicable,
// because the old queue is no longer the active queue.
nativeQueue.setState(mNativeQueue.getState());
mNativeQueue.reset(State.INITIAL);
mNativeQueue = nativeQueue;
}
}
@WrapForJNI(dispatchTo = "proxy")
public native void attachEditable(IGeckoEditableParent parent,
GeckoEditableChild child);
@WrapForJNI(calledFrom = "gecko")
private synchronized void onReady() {
- if (mNativeQueue.checkAndSetState(State.INITIAL, State.READY)) {
+ if (mNativeQueue != null &&
+ mNativeQueue.checkAndSetState(State.INITIAL, State.READY)) {
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
" - chrome startup finished");
}
}
}
private class Listener implements BundleEventListener {
/* package */ void registerListeners() {
@@ -481,47 +513,63 @@ public class GeckoSession extends LayerS
public GeckoSession() {
this(null);
}
public GeckoSession(final GeckoSessionSettings settings) {
mSettings = new GeckoSessionSettings(settings, this);
mListener.registerListeners();
+
+ if (BuildConfig.DEBUG && handlersCount != mSessionHandlers.length) {
+ throw new AssertionError("Add new handler to handlers list");
+ }
}
private void transferFrom(final Window window, final GeckoSessionSettings settings,
final String id) {
if (isOpen()) {
throw new IllegalStateException("Session is open");
}
+ if (window != null) {
+ onWindowChanged(WINDOW_TRANSFER_IN, /* inProgress */ true);
+ }
+
mWindow = window;
mSettings = new GeckoSessionSettings(settings, this);
mId = id;
if (mWindow != null) {
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
mWindow.transfer(mCompositor, mEventDispatcher, mSettings.asBundle());
} else {
GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
mWindow, "transfer",
Compositor.class, mCompositor,
EventDispatcher.class, mEventDispatcher,
GeckoBundle.class, mSettings.asBundle());
}
+
+ onWindowChanged(WINDOW_TRANSFER_IN, /* inProgress */ false);
}
-
- onWindowChanged();
}
/* package */ void transferFrom(final GeckoSession session) {
+ final boolean changing = (session.mWindow != null);
+ if (changing) {
+ session.onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ true);
+ }
+
transferFrom(session.mWindow, session.mSettings, session.mId);
session.mWindow = null;
- session.onWindowChanged();
+
+ if (changing) {
+ session.onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ false);
+ }
}
@Override // Parcelable
public int describeContents() {
return 0;
}
@Override // Parcelable
@@ -633,32 +681,34 @@ public class GeckoSession extends LayerS
private void openWindow() {
final String chromeUri = mSettings.getString(GeckoSessionSettings.CHROME_URI);
final int screenId = mSettings.getInt(GeckoSessionSettings.SCREEN_ID);
final boolean isPrivate = mSettings.getBoolean(GeckoSessionSettings.USE_PRIVATE_MODE);
mWindow = new Window(mNativeQueue);
+ onWindowChanged(WINDOW_OPEN, /* inProgress */ true);
+
if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
Window.open(mWindow, mCompositor, mEventDispatcher,
mSettings.asBundle(), chromeUri, screenId, isPrivate, mId);
} else {
GeckoThread.queueNativeCallUntil(
GeckoThread.State.PROFILE_READY,
Window.class, "open",
Window.class, mWindow,
Compositor.class, mCompositor,
EventDispatcher.class, mEventDispatcher,
GeckoBundle.class, mSettings.asBundle(),
String.class, chromeUri,
screenId, isPrivate, mId);
}
- onWindowChanged();
+ onWindowChanged(WINDOW_OPEN, /* inProgress */ false);
}
/**
* Closes the session.
*
* This frees the underlying Gecko objects and unloads the current page. The session may be
* reopened later, but page state is not restored. Call this when you are finished using
* a GeckoSession instance.
@@ -666,31 +716,37 @@ public class GeckoSession extends LayerS
public void close() {
ThreadUtils.assertOnUiThread();
if (!isOpen()) {
Log.w(LOGTAG, "Attempted to close a GeckoSession that was already closed.");
return;
}
- if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- mWindow.close();
- } else {
- GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
- mWindow, "close");
+ onWindowChanged(WINDOW_CLOSE, /* inProgress */ true);
+
+ mWindow.close();
+ mWindow.disposeNative();
+ mWindow = null;
+
+ onWindowChanged(WINDOW_CLOSE, /* inProgress */ false);
+ }
+
+ private void onWindowChanged(int change, boolean inProgress) {
+ if ((change == WINDOW_OPEN || change == WINDOW_TRANSFER_IN) && !inProgress) {
+ mTextInput.onWindowChanged(mWindow);
}
- mWindow.disposeNative();
- mWindow = null;
- onWindowChanged();
- }
-
- private void onWindowChanged() {
- if (mWindow != null) {
- mTextInput.onWindowChanged(mWindow);
+ if (change == WINDOW_CLOSE) {
+ // Detach when window is closing, and reattach immediately after window is closed.
+ // We reattach immediate after closing because we want any actions performed while the
+ // session is closed to be properly queued, until the session is open again.
+ for (final GeckoSessionHandler<?> handler : mSessionHandlers) {
+ handler.setSessionIsReady(getEventDispatcher(), !inProgress);
+ }
}
}
/**
* Get the TextInputController instance for this session.
*
* @return TextInputController instance.
*/
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionHandler.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionHandler.java
@@ -5,17 +5,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.geckoview;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.geckoview.GeckoSession;
import android.util.Log;
/* package */ abstract class GeckoSessionHandler<Delegate>
implements BundleEventListener {
private static final String LOGTAG = "GeckoSessionHandler";
private static final boolean DEBUG = false;
@@ -31,71 +30,86 @@ import android.util.Log;
final String[] events) {
this(module, session, events, /* alwaysListen */ false);
}
/* package */ GeckoSessionHandler(final String module,
final GeckoSession session,
final String[] events,
final boolean alwaysListen) {
+ session.handlersCount++;
+
mAlwaysListen = alwaysListen;
mModuleName = module;
mEvents = events;
if (alwaysListen) {
register(session.getEventDispatcher());
+ setSessionIsReady(session.getEventDispatcher(), /* ready */ true);
}
}
public Delegate getDelegate() {
return mDelegate;
}
public void setDelegate(final Delegate delegate, final GeckoSession session) {
final EventDispatcher eventDispatcher = session.getEventDispatcher();
if (mDelegate == delegate) {
return;
}
- if (!mAlwaysListen && mDelegate != null) {
+ final boolean unsettingOldDelegate = mDelegate != null &&
+ delegate == null;
+ final boolean settingNewDelegate = mDelegate == null &&
+ delegate != null;
+
+ if (!mAlwaysListen && unsettingOldDelegate) {
unregister(eventDispatcher);
}
mDelegate = delegate;
- if (!mAlwaysListen && mDelegate != null) {
+ if (!mAlwaysListen && settingNewDelegate) {
register(eventDispatcher);
}
}
private void unregister(final EventDispatcher eventDispatcher) {
- final GeckoBundle msg = new GeckoBundle(1);
- msg.putString("module", mModuleName);
- eventDispatcher.dispatch("GeckoView:Unregister", msg);
+ setSessionIsReady(eventDispatcher, /* ready */ false);
eventDispatcher.unregisterUiThreadListener(this, mEvents);
}
private void register(final EventDispatcher eventDispatcher) {
+ eventDispatcher.registerUiThreadListener(this, mEvents);
+ setSessionIsReady(eventDispatcher, /* ready */ true);
+ }
+
+ public void setSessionIsReady(final EventDispatcher eventDispatcher, final boolean ready) {
+ if (!mAlwaysListen && mDelegate == null) {
+ return;
+ }
+
final GeckoBundle msg = new GeckoBundle(1);
msg.putString("module", mModuleName);
- eventDispatcher.dispatch("GeckoView:Register", msg);
- eventDispatcher.registerUiThreadListener(this, mEvents);
+ eventDispatcher.dispatch(ready ? "GeckoView:Register"
+ : "GeckoView:Unregister", msg);
}
@Override
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
if (DEBUG) {
Log.d(LOGTAG, mModuleName + " handleMessage: event = " + event);
}
if (mDelegate != null) {
handleMessage(mDelegate, event, message, callback);
} else {
- callback.sendError("No listener registered");
+ callback.sendError("No delegate registered");
}
}
protected abstract void handleMessage(final Delegate delegate,
final String event,
final GeckoBundle message,
final EventCallback callback);
}