Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(974)

Unified Diff: content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java

Issue 22272006: Add support for multiple asynchronous browser startups. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Updated tests to use assert messages. Cleaned some of them up a bit. Rebased. Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
diff --git a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
new file mode 100644
index 0000000000000000000000000000000000000000..e6b3a6757a98298cd91f9da0681c4d76d8d0c527
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
@@ -0,0 +1,235 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.common.ProcessInitException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class controls how C++ browser main loop is started and ensures it happens only once.
+ *
+ * It supports kicking off the startup sequence in an asynchronous way. Startup can be called as
+ * many times as needed (for instance, multiple activities for the same application), but the
+ * browser process will still only be initialized once. All requests to start the browser will
+ * always get their callback executed; if the browser process has already been started, the callback
+ * is called immediately, else it is called when initialization is complete.
+ *
+ * All communication with this class must happen on the main thread.
+ *
+ * This is a singleton, and stores a reference to the application context.
+ */
+@JNINamespace("content")
+public class BrowserStartupController {
+
+ public interface StartupCallback {
+ void onSuccess(boolean alreadyStarted);
+ void onFailure();
+ }
+
+ private static final String TAG = "BrowserStartupController";
+
+ // Helper constants for {@link StartupCallback#onSuccess}.
+ private static final boolean ALREADY_STARTED = true;
+ private static final boolean NOT_ALREADY_STARTED = false;
+
+ // Helper constants for {@link #executeEnqueuedCallbacks(int, boolean)}.
+ @VisibleForTesting
+ static final int STARTUP_SUCCESS = -1;
+ @VisibleForTesting
+ static final int STARTUP_FAILURE = 1;
+
+ private static BrowserStartupController sInstance;
+
+ private static boolean sBrowserMayStartAsynchronously = false;
+
+ private static void setAsynchronousStartupConfig() {
+ sBrowserMayStartAsynchronously = true;
+ }
+
+ @CalledByNative
+ private static boolean browserMayStartAsynchonously() {
+ return sBrowserMayStartAsynchronously;
+ }
+
+ @VisibleForTesting
+ @CalledByNative
+ static void browserStartupComplete(int result) {
+ if (sInstance != null) {
+ sInstance.executeEnqueuedCallbacks(result, NOT_ALREADY_STARTED);
+ }
+ }
+
+ // A list of callbacks that should be called when the async startup of the browser process is
+ // complete.
+ private final List<StartupCallback> mAsyncStartupCallbacks;
+
+ // The context is set on creation, but the reference is cleared after the browser process
+ // initialization has been started, since it is not needed anymore. This is to ensure the
+ // context is not leaked.
+ private Context mContext;
+
+ // Whether the async startup of the browser process has started.
+ private boolean mHasStartedInitializingBrowserProcess;
+
+ // Whether the async startup of the browser process is complete.
+ private boolean mAsyncStartupDone;
+
+ // This field is set after startup has been completed based on whether the startup was a success
+ // or not. It is used when later requests to startup come in that happen after the initial set
+ // of enqueued callbacks have been executed.
+ private boolean mStartupSuccess;
+
+ BrowserStartupController(Context context) {
+ mContext = context;
+ mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
+ }
+
+ public static BrowserStartupController get(Context context) {
+ assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
+ ThreadUtils.assertOnUiThread();
+ if (sInstance == null) {
+ sInstance = new BrowserStartupController(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ static BrowserStartupController overrideInstanceForTest(BrowserStartupController controller) {
+ if (sInstance == null) {
+ sInstance = controller;
+ }
+ return sInstance;
+ }
+
+ /**
+ * Start the browser process asynchronously. This will set up a queue of UI thread tasks to
+ * initialize the browser process.
+ * <p/>
+ * Note that this can only be called on the UI thread.
+ *
+ * @param callback the callback to be called when browser startup is complete.
+ */
+ public void startBrowserProcessesAsync(final StartupCallback callback) {
+ assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
+ if (mAsyncStartupDone) {
+ // Browser process initialization has already been completed, so we can immediately post
+ // the callback.
+ postStartupCompleted(callback);
+ return;
+ }
+
+ // Browser process has not been fully started yet, so we defer executing the callback.
+ mAsyncStartupCallbacks.add(callback);
+
+ if (!mHasStartedInitializingBrowserProcess) {
+ // This is the first time we have been asked to start the browser process. We set the
+ // flag that indicates that we have kicked off starting the browser process.
+ mHasStartedInitializingBrowserProcess = true;
+
+ enableAsynchronousStartup();
+
+ // Try to initialize the Android browser process.
+ tryToInitializeBrowserProcess();
+ }
+ }
+
+ private void tryToInitializeBrowserProcess() {
+ try {
+ assert mContext != null;
+ boolean wasAlreadyInitialized = initializeAndroidBrowserProcess();
+ // The context is not needed anymore, so clear the member field to not leak.
+ mContext = null;
+ if (wasAlreadyInitialized) {
+ // Something has already initialized the browser process before we got to setup the
+ // async startup. This means that we will never get a callback, so manually call
+ // them now, and just assume that the startup was successful.
+ Log.w(TAG, "Browser process was initialized without BrowserStartupController");
+ enqueueCallbackExecution(STARTUP_SUCCESS, ALREADY_STARTED);
+ }
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "Unable to start browser process.", e);
+ // ProcessInitException could mean one of two things:
+ // 1) The LibraryLoader failed.
+ // 2) ContentMain failed to start.
+ // It is unclear whether the browser tasks have already been started, and in case they
+ // have not, post a message to execute all the callbacks. Whichever call to
+ // executeEnqueuedCallbacks comes first will trigger the callbacks, but since the list
+ // of callbacks is then cleared, they will only be called once.
+ enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
+ }
+ }
+
+ public void addStartupCompletedObserver(StartupCallback callback) {
+ ThreadUtils.assertOnUiThread();
+ if (mAsyncStartupDone)
+ postStartupCompleted(callback);
+ else
+ mAsyncStartupCallbacks.add(callback);
+ }
+
+ private void executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted) {
+ assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread.";
+ mAsyncStartupDone = true;
+ for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) {
+ if (startupResult > 0) {
+ asyncStartupCallback.onFailure();
+ } else {
+ mStartupSuccess = true;
+ asyncStartupCallback.onSuccess(alreadyStarted);
+ }
+ }
+ // We don't want to hold on to any objects after we do not need them anymore.
+ mAsyncStartupCallbacks.clear();
+ }
+
+ private void enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted) {
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ executeEnqueuedCallbacks(startupFailure, alreadyStarted);
+ }
+ });
+ }
+
+ private void postStartupCompleted(final StartupCallback callback) {
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mStartupSuccess)
+ callback.onSuccess(ALREADY_STARTED);
+ else
+ callback.onFailure();
+ }
+ });
+ }
+
+ /**
+ * Ensure that the browser process will be asynchronously started up. This also ensures that we
+ * get a call to {@link #browserStartupComplete} when the browser startup is complete.
+ */
+ @VisibleForTesting
+ void enableAsynchronousStartup() {
+ setAsynchronousStartupConfig();
+ }
+
+ /**
+ * @return whether the process was already initialized, so native was not instructed to start.
+ */
+ @VisibleForTesting
+ boolean initializeAndroidBrowserProcess() throws ProcessInitException {
+ return !AndroidBrowserProcess.init(mContext, AndroidBrowserProcess.MAX_RENDERERS_LIMIT);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698