| 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);
|
| + }
|
| +}
|
|
|