• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.google.common.annotations.VisibleForTesting;
12 
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;
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 {
onSuccess(boolean alreadyStarted)44         void onSuccess(boolean alreadyStarted);
onFailure()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 
setAsynchronousStartup(boolean enable)64     private static void setAsynchronousStartup(boolean enable) {
65         sBrowserMayStartAsynchronously = enable;
66     }
67 
68     @VisibleForTesting
69     @CalledByNative
browserMayStartAsynchonously()70     static boolean browserMayStartAsynchonously() {
71         return sBrowserMayStartAsynchronously;
72     }
73 
74     @VisibleForTesting
75     @CalledByNative
browserStartupComplete(int result)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     // 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;
100 
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;
110 
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;
115 
BrowserStartupController(Context context)116     BrowserStartupController(Context context) {
117         mContext = context;
118         mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
119     }
120 
get(Context context)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());
126         }
127         return sInstance;
128     }
129 
130     @VisibleForTesting
overrideInstanceForTest(BrowserStartupController controller)131     static BrowserStartupController overrideInstanceForTest(BrowserStartupController controller) {
132         if (sInstance == null) {
133             sInstance = controller;
134         }
135         return sInstance;
136     }
137 
138     /**
139      * Start the browser process asynchronously. This will set up a queue of UI thread tasks to
140      * initialize the browser process.
141      * <p/>
142      * Note that this can only be called on the UI thread.
143      *
144      * @param callback the callback to be called when browser startup is complete.
145      */
startBrowserProcessesAsync(final StartupCallback callback)146     public void startBrowserProcessesAsync(final StartupCallback callback)
147             throws ProcessInitException {
148         assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
149         if (mStartupDone) {
150             // Browser process initialization has already been completed, so we can immediately post
151             // the callback.
152             postStartupCompleted(callback);
153             return;
154         }
155 
156         // Browser process has not been fully started yet, so we defer executing the callback.
157         mAsyncStartupCallbacks.add(callback);
158 
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;
163 
164             prepareToStartBrowserProcess(MAX_RENDERERS_LIMIT);
165 
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);
170             }
171         }
172     }
173 
174     /**
175      * Start the browser process synchronously. If the browser is already being started
176      * asynchronously then complete startup synchronously
177      *
178      * <p/>
179      * Note that this can only be called on the UI thread.
180      *
181      * @param maxRenderers The maximum number of renderer processes the browser may
182      *                      create. Zero for single process mode.
183      * @throws ProcessInitException
184      */
startBrowserProcessesSync(int maxRenderers)185     public void startBrowserProcessesSync(int maxRenderers) throws ProcessInitException {
186         // If already started skip to checking the result
187         if (!mStartupDone) {
188             if (!mHasStartedInitializingBrowserProcess) {
189                 prepareToStartBrowserProcess(maxRenderers);
190             }
191 
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);
196             }
197         }
198 
199         // Startup should now be complete
200         assert mStartupDone;
201         if (!mStartupSuccess) {
202             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_STARTUP_FAILED);
203         }
204     }
205 
206     /**
207      * Wrap ContentMain.start() for testing.
208      */
209     @VisibleForTesting
contentStart()210     int contentStart() {
211         return ContentMain.start();
212     }
213 
addStartupCompletedObserver(StartupCallback callback)214     public void addStartupCompletedObserver(StartupCallback callback) {
215         ThreadUtils.assertOnUiThread();
216         if (mStartupDone) {
217             postStartupCompleted(callback);
218         } else {
219             mAsyncStartupCallbacks.add(callback);
220         }
221     }
222 
executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted)223     private void executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted) {
224         assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread.";
225         mStartupDone = true;
226         mStartupSuccess = (startupResult <= 0);
227         for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) {
228             if (mStartupSuccess) {
229                 asyncStartupCallback.onSuccess(alreadyStarted);
230             } else {
231                 asyncStartupCallback.onFailure();
232             }
233         }
234         // We don't want to hold on to any objects after we do not need them anymore.
235         mAsyncStartupCallbacks.clear();
236     }
237 
238     // Queue the callbacks to run. Since running the callbacks clears the list it is safe to call
239     // this more than once.
enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted)240     private void enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted) {
241         new Handler().post(new Runnable() {
242             @Override
243             public void run() {
244                 executeEnqueuedCallbacks(startupFailure, alreadyStarted);
245             }
246         });
247     }
248 
postStartupCompleted(final StartupCallback callback)249     private void postStartupCompleted(final StartupCallback callback) {
250         new Handler().post(new Runnable() {
251             @Override
252             public void run() {
253                 if (mStartupSuccess) {
254                     callback.onSuccess(ALREADY_STARTED);
255                 } else {
256                     callback.onFailure();
257                 }
258             }
259         });
260     }
261 
262     @VisibleForTesting
prepareToStartBrowserProcess(int maxRendererProcesses)263     void prepareToStartBrowserProcess(int maxRendererProcesses) throws ProcessInitException {
264         Log.i(TAG, "Initializing chromium process, renderers=" + maxRendererProcesses);
265 
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();
271 
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(mContext, true);
275 
276         // TODO(yfriedman): Remove dependency on a command line flag for this.
277         DeviceUtils.addDeviceSpecificUserAgentSwitch(mContext);
278 
279         Context appContext = mContext.getApplicationContext();
280         // Now we really need to have the resources ready.
281         resourceExtractor.waitForCompletion();
282 
283         nativeSetCommandLineFlags(maxRendererProcesses,
284                 nativeIsPluginEnabled() ? getPlugins() : null);
285         ContentMain.initApplicationContext(appContext);
286     }
287 
288     /**
289      * Initialization needed for tests. Mainly used by content browsertests.
290      */
initChromiumBrowserProcessForTests()291     public void initChromiumBrowserProcessForTests() {
292         ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
293         resourceExtractor.startExtractingResources();
294         resourceExtractor.waitForCompletion();
295 
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);
299     }
300 
getPlugins()301     private String getPlugins() {
302         return PepperPluginManager.getPlugins(mContext);
303     }
304 
nativeSetCommandLineFlags(int maxRenderProcesses, String pluginDescriptor)305     private static native void nativeSetCommandLineFlags(int maxRenderProcesses,
306             String pluginDescriptor);
307 
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.
nativeIsOfficialBuild()310     private static native boolean nativeIsOfficialBuild();
311 
nativeIsPluginEnabled()312     private static native boolean nativeIsPluginEnabled();
313 }
314