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 // Use single-process mode that runs the renderer on a separate thread in
98 // the main application.
99 public static final int MAX_RENDERERS_SINGLE_PROCESS = 0;
101 // Cap on the maximum number of renderer processes that can be requested.
102 // This is currently set to account for:
103 // 13: The maximum number of sandboxed processes we have available
104 // - 1: The regular New Tab Page
105 // - 1: The incognito New Tab Page
106 // - 1: A regular incognito tab
107 // - 1: Safety buffer (http://crbug.com/251279)
108 public static final int MAX_RENDERERS_LIMIT =
109 ChildProcessLauncher.MAX_REGISTERED_SANDBOXED_SERVICES - 4;
111 // This field is set after startup has been completed based on whether the startup was a success
112 // or not. It is used when later requests to startup come in that happen after the initial set
113 // of enqueued callbacks have been executed.
114 private boolean mStartupSuccess;
116 BrowserStartupController(Context context) {
118 mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
121 public static BrowserStartupController get(Context context) {
122 assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
123 ThreadUtils.assertOnUiThread();
124 if (sInstance == null) {
125 sInstance = new BrowserStartupController(context.getApplicationContext());
131 static BrowserStartupController overrideInstanceForTest(BrowserStartupController controller) {
132 if (sInstance == null) {
133 sInstance = controller;
139 * Start the browser process asynchronously. This will set up a queue of UI thread tasks to
140 * initialize the browser process.
142 * Note that this can only be called on the UI thread.
144 * @param callback the callback to be called when browser startup is complete.
146 public void startBrowserProcessesAsync(final StartupCallback callback)
147 throws ProcessInitException {
148 assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
150 // Browser process initialization has already been completed, so we can immediately post
152 postStartupCompleted(callback);
156 // Browser process has not been fully started yet, so we defer executing the callback.
157 mAsyncStartupCallbacks.add(callback);
159 if (!mHasStartedInitializingBrowserProcess) {
160 // This is the first time we have been asked to start the browser process. We set the
161 // flag that indicates that we have kicked off starting the browser process.
162 mHasStartedInitializingBrowserProcess = true;
164 prepareToStartBrowserProcess(MAX_RENDERERS_LIMIT);
166 setAsynchronousStartup(true);
167 if (contentStart() > 0) {
168 // Failed. The callbacks may not have run, so run them.
169 enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
175 * Start the browser process synchronously. If the browser is already being started
176 * asynchronously then complete startup synchronously
179 * Note that this can only be called on the UI thread.
181 * @param maxRenderers The maximum number of renderer processes the browser may
182 * create. Zero for single process mode.
183 * @throws ProcessInitException
185 public void startBrowserProcessesSync(int maxRenderers) throws ProcessInitException {
186 // If already started skip to checking the result
188 if (!mHasStartedInitializingBrowserProcess) {
189 prepareToStartBrowserProcess(maxRenderers);
192 setAsynchronousStartup(false);
193 if (contentStart() > 0) {
194 // Failed. The callbacks may not have run, so run them.
195 enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
199 // Startup should now be complete
201 if (!mStartupSuccess) {
202 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_STARTUP_FAILED);
207 * Wrap ContentMain.start() for testing.
211 return ContentMain.start();
214 public void addStartupCompletedObserver(StartupCallback callback) {
215 ThreadUtils.assertOnUiThread();
217 postStartupCompleted(callback);
219 mAsyncStartupCallbacks.add(callback);
223 private void executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted) {
224 assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread.";
226 mStartupSuccess = (startupResult <= 0);
227 for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) {
228 if (mStartupSuccess) {
229 asyncStartupCallback.onSuccess(alreadyStarted);
231 asyncStartupCallback.onFailure();
234 // We don't want to hold on to any objects after we do not need them anymore.
235 mAsyncStartupCallbacks.clear();
238 // Queue the callbacks to run. Since running the callbacks clears the list it is safe to call
239 // this more than once.
240 private void enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted) {
241 new Handler().post(new Runnable() {
244 executeEnqueuedCallbacks(startupFailure, alreadyStarted);
249 private void postStartupCompleted(final StartupCallback callback) {
250 new Handler().post(new Runnable() {
253 if (mStartupSuccess) {
254 callback.onSuccess(ALREADY_STARTED);
256 callback.onFailure();
263 void prepareToStartBrowserProcess(int maxRendererProcesses) throws ProcessInitException {
264 Log.i(TAG, "Initializing chromium process, renderers=" + maxRendererProcesses);
266 // Normally Main.java will have kicked this off asynchronously for Chrome. But other
267 // ContentView apps like tests also need them so we make sure we've extracted resources
268 // here. We can still make it a little async (wait until the library is loaded).
269 ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
270 resourceExtractor.startExtractingResources();
272 // Normally Main.java will have already loaded the library asynchronously, we only need
273 // to load it here if we arrived via another flow, e.g. bookmark access & sync setup.
274 LibraryLoader.ensureInitialized();
276 // TODO(yfriedman): Remove dependency on a command line flag for this.
277 DeviceUtils.addDeviceSpecificUserAgentSwitch(mContext);
279 Context appContext = mContext.getApplicationContext();
280 // Now we really need to have the resources ready.
281 resourceExtractor.waitForCompletion();
283 nativeSetCommandLineFlags(maxRendererProcesses,
284 nativeIsPluginEnabled() ? getPlugins() : null);
285 ContentMain.initApplicationContext(appContext);
289 * Initialization needed for tests. Mainly used by content browsertests.
291 public void initChromiumBrowserProcessForTests() {
292 ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
293 resourceExtractor.startExtractingResources();
294 resourceExtractor.waitForCompletion();
296 // Having a single renderer should be sufficient for tests. We can't have more than
297 // MAX_RENDERERS_LIMIT.
298 nativeSetCommandLineFlags(1 /* maxRenderers */, null);
301 private String getPlugins() {
302 return PepperPluginManager.getPlugins(mContext);
305 private static native void nativeSetCommandLineFlags(int maxRenderProcesses,
306 String pluginDescriptor);
308 // Is this an official build of Chrome? Only native code knows for sure. Official build
309 // knowledge is needed very early in process startup.
310 private static native boolean nativeIsOfficialBuild();
312 private static native boolean nativeIsPluginEnabled();