Home | History | Annotate | Download | only in browser
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.content.browser;
      6 
      7 import android.content.Context;
      8 import android.os.Handler;
      9 import android.util.Log;
     10 
     11 import org.chromium.base.CalledByNative;
     12 import org.chromium.base.JNINamespace;
     13 import org.chromium.base.ResourceExtractor;
     14 import org.chromium.base.ThreadUtils;
     15 import org.chromium.base.VisibleForTesting;
     16 import org.chromium.base.library_loader.LibraryLoader;
     17 import org.chromium.base.library_loader.LoaderErrors;
     18 import org.chromium.base.library_loader.ProcessInitException;
     19 import org.chromium.content.app.ContentMain;
     20 
     21 import java.util.ArrayList;
     22 import java.util.List;
     23 
     24 /**
     25  * This class controls how C++ browser main loop is started and ensures it happens only once.
     26  *
     27  * It supports kicking off the startup sequence in an asynchronous way. Startup can be called as
     28  * many times as needed (for instance, multiple activities for the same application), but the
     29  * browser process will still only be initialized once. All requests to start the browser will
     30  * always get their callback executed; if the browser process has already been started, the callback
     31  * is called immediately, else it is called when initialization is complete.
     32  *
     33  * All communication with this class must happen on the main thread.
     34  *
     35  * This is a singleton, and stores a reference to the application context.
     36  */
     37 @JNINamespace("content")
     38 public class BrowserStartupController {
     39 
     40     /**
     41      * This provides the interface to the callbacks for successful or failed startup
     42      */
     43     public interface StartupCallback {
     44         void onSuccess(boolean alreadyStarted);
     45         void onFailure();
     46     }
     47 
     48     private static final String TAG = "BrowserStartupController";
     49 
     50     // Helper constants for {@link StartupCallback#onSuccess}.
     51     private static final boolean ALREADY_STARTED = true;
     52     private static final boolean NOT_ALREADY_STARTED = false;
     53 
     54     // Helper constants for {@link #executeEnqueuedCallbacks(int, boolean)}.
     55     @VisibleForTesting
     56     static final int STARTUP_SUCCESS = -1;
     57     @VisibleForTesting
     58     static final int STARTUP_FAILURE = 1;
     59 
     60     private static BrowserStartupController sInstance;
     61 
     62     private static boolean sBrowserMayStartAsynchronously = false;
     63 
     64     private static void setAsynchronousStartup(boolean enable) {
     65         sBrowserMayStartAsynchronously = enable;
     66     }
     67 
     68     @VisibleForTesting
     69     @CalledByNative
     70     static boolean browserMayStartAsynchonously() {
     71         return sBrowserMayStartAsynchronously;
     72     }
     73 
     74     @VisibleForTesting
     75     @CalledByNative
     76     static void browserStartupComplete(int result) {
     77         if (sInstance != null) {
     78             sInstance.executeEnqueuedCallbacks(result, NOT_ALREADY_STARTED);
     79         }
     80     }
     81 
     82     // A list of callbacks that should be called when the async startup of the browser process is
     83     // complete.
     84     private final List<StartupCallback> mAsyncStartupCallbacks;
     85 
     86     // The context is set on creation, but the reference is cleared after the browser process
     87     // initialization has been started, since it is not needed anymore. This is to ensure the
     88     // context is not leaked.
     89     private final Context mContext;
     90 
     91     // Whether the async startup of the browser process has started.
     92     private boolean mHasStartedInitializingBrowserProcess;
     93 
     94     // Whether the async startup of the browser process is complete.
     95     private boolean mStartupDone;
     96 
     97     // This field is set after startup has been completed based on whether the startup was a success
     98     // or not. It is used when later requests to startup come in that happen after the initial set
     99     // of enqueued callbacks have been executed.
    100     private boolean mStartupSuccess;
    101 
    102     BrowserStartupController(Context context) {
    103         mContext = context;
    104         mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
    105     }
    106 
    107     public static BrowserStartupController get(Context context) {
    108         assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
    109         ThreadUtils.assertOnUiThread();
    110         if (sInstance == null) {
    111             sInstance = new BrowserStartupController(context.getApplicationContext());
    112         }
    113         return sInstance;
    114     }
    115 
    116     @VisibleForTesting
    117     static BrowserStartupController overrideInstanceForTest(BrowserStartupController controller) {
    118         if (sInstance == null) {
    119             sInstance = controller;
    120         }
    121         return sInstance;
    122     }
    123 
    124     /**
    125      * Start the browser process asynchronously. This will set up a queue of UI thread tasks to
    126      * initialize the browser process.
    127      * <p/>
    128      * Note that this can only be called on the UI thread.
    129      *
    130      * @param callback the callback to be called when browser startup is complete.
    131      */
    132     public void startBrowserProcessesAsync(final StartupCallback callback)
    133             throws ProcessInitException {
    134         assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
    135         if (mStartupDone) {
    136             // Browser process initialization has already been completed, so we can immediately post
    137             // the callback.
    138             postStartupCompleted(callback);
    139             return;
    140         }
    141 
    142         // Browser process has not been fully started yet, so we defer executing the callback.
    143         mAsyncStartupCallbacks.add(callback);
    144 
    145         if (!mHasStartedInitializingBrowserProcess) {
    146             // This is the first time we have been asked to start the browser process. We set the
    147             // flag that indicates that we have kicked off starting the browser process.
    148             mHasStartedInitializingBrowserProcess = true;
    149 
    150             prepareToStartBrowserProcess(false);
    151 
    152             setAsynchronousStartup(true);
    153             if (contentStart() > 0) {
    154                 // Failed. The callbacks may not have run, so run them.
    155                 enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
    156             }
    157         }
    158     }
    159 
    160     /**
    161      * Start the browser process synchronously. If the browser is already being started
    162      * asynchronously then complete startup synchronously
    163      *
    164      * <p/>
    165      * Note that this can only be called on the UI thread.
    166      *
    167      * @param singleProcess true iff the browser should run single-process, ie. keep renderers in
    168      *                      the browser process
    169      * @throws ProcessInitException
    170      */
    171     public void startBrowserProcessesSync(boolean singleProcess) throws ProcessInitException {
    172         // If already started skip to checking the result
    173         if (!mStartupDone) {
    174             if (!mHasStartedInitializingBrowserProcess) {
    175                 prepareToStartBrowserProcess(singleProcess);
    176             }
    177 
    178             setAsynchronousStartup(false);
    179             if (contentStart() > 0) {
    180                 // Failed. The callbacks may not have run, so run them.
    181                 enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
    182             }
    183         }
    184 
    185         // Startup should now be complete
    186         assert mStartupDone;
    187         if (!mStartupSuccess) {
    188             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_STARTUP_FAILED);
    189         }
    190     }
    191 
    192     /**
    193      * Wrap ContentMain.start() for testing.
    194      */
    195     @VisibleForTesting
    196     int contentStart() {
    197         return ContentMain.start();
    198     }
    199 
    200     public void addStartupCompletedObserver(StartupCallback callback) {
    201         ThreadUtils.assertOnUiThread();
    202         if (mStartupDone) {
    203             postStartupCompleted(callback);
    204         } else {
    205             mAsyncStartupCallbacks.add(callback);
    206         }
    207     }
    208 
    209     private void executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted) {
    210         assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread.";
    211         mStartupDone = true;
    212         mStartupSuccess = (startupResult <= 0);
    213         for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) {
    214             if (mStartupSuccess) {
    215                 asyncStartupCallback.onSuccess(alreadyStarted);
    216             } else {
    217                 asyncStartupCallback.onFailure();
    218             }
    219         }
    220         // We don't want to hold on to any objects after we do not need them anymore.
    221         mAsyncStartupCallbacks.clear();
    222     }
    223 
    224     // Queue the callbacks to run. Since running the callbacks clears the list it is safe to call
    225     // this more than once.
    226     private void enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted) {
    227         new Handler().post(new Runnable() {
    228             @Override
    229             public void run() {
    230                 executeEnqueuedCallbacks(startupFailure, alreadyStarted);
    231             }
    232         });
    233     }
    234 
    235     private void postStartupCompleted(final StartupCallback callback) {
    236         new Handler().post(new Runnable() {
    237             @Override
    238             public void run() {
    239                 if (mStartupSuccess) {
    240                     callback.onSuccess(ALREADY_STARTED);
    241                 } else {
    242                     callback.onFailure();
    243                 }
    244             }
    245         });
    246     }
    247 
    248     @VisibleForTesting
    249     void prepareToStartBrowserProcess(boolean singleProcess) throws ProcessInitException {
    250         Log.i(TAG, "Initializing chromium process, singleProcess=" + singleProcess);
    251 
    252         // Normally Main.java will have kicked this off asynchronously for Chrome. But other
    253         // ContentView apps like tests also need them so we make sure we've extracted resources
    254         // here. We can still make it a little async (wait until the library is loaded).
    255         ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
    256         resourceExtractor.startExtractingResources();
    257 
    258         // Normally Main.java will have already loaded the library asynchronously, we only need
    259         // to load it here if we arrived via another flow, e.g. bookmark access & sync setup.
    260         LibraryLoader.ensureInitialized(mContext, true);
    261 
    262         // TODO(yfriedman): Remove dependency on a command line flag for this.
    263         DeviceUtils.addDeviceSpecificUserAgentSwitch(mContext);
    264 
    265         Context appContext = mContext.getApplicationContext();
    266         // Now we really need to have the resources ready.
    267         resourceExtractor.waitForCompletion();
    268 
    269         nativeSetCommandLineFlags(singleProcess, nativeIsPluginEnabled() ? getPlugins() : null);
    270         ContentMain.initApplicationContext(appContext);
    271     }
    272 
    273     /**
    274      * Initialization needed for tests. Mainly used by content browsertests.
    275      */
    276     public void initChromiumBrowserProcessForTests() {
    277         ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
    278         resourceExtractor.startExtractingResources();
    279         resourceExtractor.waitForCompletion();
    280         nativeSetCommandLineFlags(false, null);
    281     }
    282 
    283     private String getPlugins() {
    284         return PepperPluginManager.getPlugins(mContext);
    285     }
    286 
    287     private static native void nativeSetCommandLineFlags(
    288             boolean singleProcess, String pluginDescriptor);
    289 
    290     // Is this an official build of Chrome? Only native code knows for sure. Official build
    291     // knowledge is needed very early in process startup.
    292     private static native boolean nativeIsOfficialBuild();
    293 
    294     private static native boolean nativeIsPluginEnabled();
    295 }
    296