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.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.os.Handler;
9 import android.util.Log;
11 import com.google.common.annotations.VisibleForTesting;
13 import org.chromium.base.CalledByNative;
14 import org.chromium.base.JNINamespace;
15 import org.chromium.base.ThreadUtils;
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;
21 import java.util.ArrayList;
22 import java.util.List;
25 * This class controls how C++ browser main loop is started and ensures it happens only once.
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.
33 * All communication with this class must happen on the main thread.
35 * This is a singleton, and stores a reference to the application context.
37 @JNINamespace("content")
38 public class BrowserStartupController {
41 * This provides the interface to the callbacks for successful or failed startup
43 public interface StartupCallback {
44 void onSuccess(boolean alreadyStarted);
48 private static final String TAG = "BrowserStartupController";
50 // Helper constants for {@link StartupCallback#onSuccess}.
51 private static final boolean ALREADY_STARTED = true;
52 private static final boolean NOT_ALREADY_STARTED = false;
54 // Helper constants for {@link #executeEnqueuedCallbacks(int, boolean)}.
56 static final int STARTUP_SUCCESS = -1;
58 static final int STARTUP_FAILURE = 1;
60 private static BrowserStartupController sInstance;
62 private static boolean sBrowserMayStartAsynchronously = false;
64 private static void setAsynchronousStartup(boolean enable) {
65 sBrowserMayStartAsynchronously = enable;
70 static boolean browserMayStartAsynchonously() {
71 return sBrowserMayStartAsynchronously;
76 static void browserStartupComplete(int result) {
77 if (sInstance != null) {
78 sInstance.executeEnqueuedCallbacks(result, NOT_ALREADY_STARTED);
82 // A list of callbacks that should be called when the async startup of the browser process is
84 private final List<StartupCallback> mAsyncStartupCallbacks;
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;
91 // Whether the async startup of the browser process has started.
92 private boolean mHasStartedInitializingBrowserProcess;
94 // Whether the async startup of the browser process is complete.
95 private boolean mStartupDone;
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;
102 BrowserStartupController(Context context) {
104 mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
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());
117 static BrowserStartupController overrideInstanceForTest(BrowserStartupController controller) {
118 if (sInstance == null) {
119 sInstance = controller;
125 * Start the browser process asynchronously. This will set up a queue of UI thread tasks to
126 * initialize the browser process.
128 * Note that this can only be called on the UI thread.
130 * @param callback the callback to be called when browser startup is complete.
132 public void startBrowserProcessesAsync(final StartupCallback callback)
133 throws ProcessInitException {
134 assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
136 // Browser process initialization has already been completed, so we can immediately post
138 postStartupCompleted(callback);
142 // Browser process has not been fully started yet, so we defer executing the callback.
143 mAsyncStartupCallbacks.add(callback);
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;
150 prepareToStartBrowserProcess(false);
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);
161 * Start the browser process synchronously. If the browser is already being started
162 * asynchronously then complete startup synchronously
165 * Note that this can only be called on the UI thread.
167 * @param singleProcess true iff the browser should run single-process, ie. keep renderers in
168 * the browser process
169 * @throws ProcessInitException
171 public void startBrowserProcessesSync(boolean singleProcess) throws ProcessInitException {
172 // If already started skip to checking the result
174 if (!mHasStartedInitializingBrowserProcess) {
175 prepareToStartBrowserProcess(singleProcess);
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);
185 // Startup should now be complete
187 if (!mStartupSuccess) {
188 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_STARTUP_FAILED);
193 * Wrap ContentMain.start() for testing.
197 return ContentMain.start();
200 public void addStartupCompletedObserver(StartupCallback callback) {
201 ThreadUtils.assertOnUiThread();
203 postStartupCompleted(callback);
205 mAsyncStartupCallbacks.add(callback);
209 private void executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted) {
210 assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread.";
212 mStartupSuccess = (startupResult <= 0);
213 for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) {
214 if (mStartupSuccess) {
215 asyncStartupCallback.onSuccess(alreadyStarted);
217 asyncStartupCallback.onFailure();
220 // We don't want to hold on to any objects after we do not need them anymore.
221 mAsyncStartupCallbacks.clear();
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() {
230 executeEnqueuedCallbacks(startupFailure, alreadyStarted);
235 private void postStartupCompleted(final StartupCallback callback) {
236 new Handler().post(new Runnable() {
239 if (mStartupSuccess) {
240 callback.onSuccess(ALREADY_STARTED);
242 callback.onFailure();
249 void prepareToStartBrowserProcess(boolean singleProcess) throws ProcessInitException {
250 Log.i(TAG, "Initializing chromium process, singleProcess=" + singleProcess);
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();
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);
262 // TODO(yfriedman): Remove dependency on a command line flag for this.
263 DeviceUtils.addDeviceSpecificUserAgentSwitch(mContext);
265 Context appContext = mContext.getApplicationContext();
266 // Now we really need to have the resources ready.
267 resourceExtractor.waitForCompletion();
269 nativeSetCommandLineFlags(singleProcess, nativeIsPluginEnabled() ? getPlugins() : null);
270 ContentMain.initApplicationContext(appContext);
274 * Initialization needed for tests. Mainly used by content browsertests.
276 public void initChromiumBrowserProcessForTests() {
277 ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
278 resourceExtractor.startExtractingResources();
279 resourceExtractor.waitForCompletion();
280 nativeSetCommandLineFlags(false, null);
283 private String getPlugins() {
284 return PepperPluginManager.getPlugins(mContext);
287 private static native void nativeSetCommandLineFlags(
288 boolean singleProcess, String pluginDescriptor);
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();
294 private static native boolean nativeIsPluginEnabled();