• 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.content.app.ContentMain;
17 import org.chromium.content.app.LibraryLoader;
18 import org.chromium.content.common.ProcessInitException;
19 import org.chromium.content.common.ResultCodes;
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     public interface StartupCallback {
onSuccess(boolean alreadyStarted)41         void onSuccess(boolean alreadyStarted);
onFailure()42         void onFailure();
43     }
44 
45     private static final String TAG = "BrowserStartupController";
46 
47     // Helper constants for {@link StartupCallback#onSuccess}.
48     private static final boolean ALREADY_STARTED = true;
49     private static final boolean NOT_ALREADY_STARTED = false;
50 
51     // Helper constants for {@link #executeEnqueuedCallbacks(int, boolean)}.
52     @VisibleForTesting
53     static final int STARTUP_SUCCESS = -1;
54     @VisibleForTesting
55     static final int STARTUP_FAILURE = 1;
56 
57     private static BrowserStartupController sInstance;
58 
59     private static boolean sBrowserMayStartAsynchronously = false;
60 
setAsynchronousStartup(boolean enable)61     private static void setAsynchronousStartup(boolean enable) {
62         sBrowserMayStartAsynchronously = enable;
63     }
64 
65     @VisibleForTesting
66     @CalledByNative
browserMayStartAsynchonously()67     static boolean browserMayStartAsynchonously() {
68         return sBrowserMayStartAsynchronously;
69     }
70 
71     @VisibleForTesting
72     @CalledByNative
browserStartupComplete(int result)73     static void browserStartupComplete(int result) {
74         if (sInstance != null) {
75             sInstance.executeEnqueuedCallbacks(result, NOT_ALREADY_STARTED);
76         }
77     }
78 
79     // A list of callbacks that should be called when the async startup of the browser process is
80     // complete.
81     private final List<StartupCallback> mAsyncStartupCallbacks;
82 
83     // The context is set on creation, but the reference is cleared after the browser process
84     // initialization has been started, since it is not needed anymore. This is to ensure the
85     // context is not leaked.
86     private final Context mContext;
87 
88     // Whether the async startup of the browser process has started.
89     private boolean mHasStartedInitializingBrowserProcess;
90 
91     // Whether the async startup of the browser process is complete.
92     private boolean mStartupDone;
93 
94     // Use single-process mode that runs the renderer on a separate thread in
95     // the main application.
96     public static final int MAX_RENDERERS_SINGLE_PROCESS = 0;
97 
98     // Cap on the maximum number of renderer processes that can be requested.
99     // This is currently set to account for:
100     //  13: The maximum number of sandboxed processes we have available
101     // - 1: The regular New Tab Page
102     // - 1: The incognito New Tab Page
103     // - 1: A regular incognito tab
104     // - 1: Safety buffer (http://crbug.com/251279)
105     public static final int MAX_RENDERERS_LIMIT =
106             ChildProcessLauncher.MAX_REGISTERED_SANDBOXED_SERVICES - 4;
107 
108     // This field is set after startup has been completed based on whether the startup was a success
109     // or not. It is used when later requests to startup come in that happen after the initial set
110     // of enqueued callbacks have been executed.
111     private boolean mStartupSuccess;
112 
BrowserStartupController(Context context)113     BrowserStartupController(Context context) {
114         mContext = context;
115         mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
116     }
117 
get(Context context)118     public static BrowserStartupController get(Context context) {
119         assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
120         ThreadUtils.assertOnUiThread();
121         if (sInstance == null) {
122             sInstance = new BrowserStartupController(context.getApplicationContext());
123         }
124         return sInstance;
125     }
126 
127     @VisibleForTesting
overrideInstanceForTest(BrowserStartupController controller)128     static BrowserStartupController overrideInstanceForTest(BrowserStartupController controller) {
129         if (sInstance == null) {
130             sInstance = controller;
131         }
132         return sInstance;
133     }
134 
135     /**
136      * Start the browser process asynchronously. This will set up a queue of UI thread tasks to
137      * initialize the browser process.
138      * <p/>
139      * Note that this can only be called on the UI thread.
140      *
141      * @param callback the callback to be called when browser startup is complete.
142      */
startBrowserProcessesAsync(final StartupCallback callback)143     public void startBrowserProcessesAsync(final StartupCallback callback)
144             throws ProcessInitException {
145         assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
146         if (mStartupDone) {
147             // Browser process initialization has already been completed, so we can immediately post
148             // the callback.
149             postStartupCompleted(callback);
150             return;
151         }
152 
153         // Browser process has not been fully started yet, so we defer executing the callback.
154         mAsyncStartupCallbacks.add(callback);
155 
156         if (!mHasStartedInitializingBrowserProcess) {
157             // This is the first time we have been asked to start the browser process. We set the
158             // flag that indicates that we have kicked off starting the browser process.
159             mHasStartedInitializingBrowserProcess = true;
160 
161             prepareToStartBrowserProcess(MAX_RENDERERS_LIMIT);
162 
163             setAsynchronousStartup(true);
164             if (contentStart() > 0) {
165                 // Failed. The callbacks may not have run, so run them.
166                 enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
167             }
168         }
169     }
170 
171     /**
172      * Start the browser process synchronously. If the browser is already being started
173      * asynchronously then complete startup synchronously
174      *
175      * <p/>
176      * Note that this can only be called on the UI thread.
177      *
178      * @param maxRenderers The maximum number of renderer processes the browser may
179      *                      create. Zero for single process mode.
180      * @throws ProcessInitException
181      */
startBrowserProcessesSync(int maxRenderers)182     public void startBrowserProcessesSync(int maxRenderers) throws ProcessInitException {
183         // If already started skip to checking the result
184         if (!mStartupDone) {
185             if (!mHasStartedInitializingBrowserProcess) {
186                 prepareToStartBrowserProcess(maxRenderers);
187             }
188 
189             setAsynchronousStartup(false);
190             if (contentStart() > 0) {
191                 // Failed. The callbacks may not have run, so run them.
192                 enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
193             }
194         }
195 
196         // Startup should now be complete
197         assert mStartupDone;
198         if (!mStartupSuccess) {
199             throw new ProcessInitException(ResultCodes.RESULT_CODE_NATIVE_STARTUP_FAILED);
200         }
201     }
202 
203     /**
204      * Wrap ContentMain.start() for testing.
205      */
206     @VisibleForTesting
contentStart()207     int contentStart() {
208         return ContentMain.start();
209     }
210 
addStartupCompletedObserver(StartupCallback callback)211     public void addStartupCompletedObserver(StartupCallback callback) {
212         ThreadUtils.assertOnUiThread();
213         if (mStartupDone) {
214             postStartupCompleted(callback);
215         } else {
216             mAsyncStartupCallbacks.add(callback);
217         }
218     }
219 
executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted)220     private void executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted) {
221         assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread.";
222         mStartupDone = true;
223         mStartupSuccess = (startupResult <= 0);
224         for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) {
225             if (mStartupSuccess) {
226                 asyncStartupCallback.onSuccess(alreadyStarted);
227             } else {
228                 asyncStartupCallback.onFailure();
229             }
230         }
231         // We don't want to hold on to any objects after we do not need them anymore.
232         mAsyncStartupCallbacks.clear();
233     }
234 
235     // Queue the callbacks to run. Since running the callbacks clears the list it is safe to call
236     // this more than once.
enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted)237     private void enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted) {
238         new Handler().post(new Runnable() {
239             @Override
240             public void run() {
241                 executeEnqueuedCallbacks(startupFailure, alreadyStarted);
242             }
243         });
244     }
245 
postStartupCompleted(final StartupCallback callback)246     private void postStartupCompleted(final StartupCallback callback) {
247         new Handler().post(new Runnable() {
248             @Override
249             public void run() {
250                 if (mStartupSuccess) {
251                     callback.onSuccess(ALREADY_STARTED);
252                 } else {
253                     callback.onFailure();
254                 }
255             }
256         });
257     }
258 
259     @VisibleForTesting
prepareToStartBrowserProcess(int maxRendererProcesses)260     void prepareToStartBrowserProcess(int maxRendererProcesses) throws ProcessInitException {
261         Log.i(TAG, "Initializing chromium process, renderers=" + maxRendererProcesses);
262 
263         // Normally Main.java will have kicked this off asynchronously for Chrome. But other
264         // ContentView apps like tests also need them so we make sure we've extracted resources
265         // here. We can still make it a little async (wait until the library is loaded).
266         ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
267         resourceExtractor.startExtractingResources();
268 
269         // Normally Main.java will have already loaded the library asynchronously, we only need
270         // to load it here if we arrived via another flow, e.g. bookmark access & sync setup.
271         LibraryLoader.ensureInitialized();
272 
273         // TODO(yfriedman): Remove dependency on a command line flag for this.
274         DeviceUtils.addDeviceSpecificUserAgentSwitch(mContext);
275 
276         Context appContext = mContext.getApplicationContext();
277         // Now we really need to have the resources ready.
278         resourceExtractor.waitForCompletion();
279 
280         nativeSetCommandLineFlags(maxRendererProcesses,
281                 nativeIsPluginEnabled() ? getPlugins() : null);
282         ContentMain.initApplicationContext(appContext);
283     }
284 
285     /**
286      * Initialization needed for tests. Mainly used by content browsertests.
287      */
initChromiumBrowserProcessForTests()288     public void initChromiumBrowserProcessForTests() {
289         ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
290         resourceExtractor.startExtractingResources();
291         resourceExtractor.waitForCompletion();
292 
293         // Having a single renderer should be sufficient for tests. We can't have more than
294         // MAX_RENDERERS_LIMIT.
295         nativeSetCommandLineFlags(1 /* maxRenderers */, null);
296     }
297 
getPlugins()298     private String getPlugins() {
299         return PepperPluginManager.getPlugins(mContext);
300     }
301 
nativeSetCommandLineFlags(int maxRenderProcesses, String pluginDescriptor)302     private static native void nativeSetCommandLineFlags(int maxRenderProcesses,
303             String pluginDescriptor);
304 
305     // Is this an official build of Chrome? Only native code knows for sure. Official build
306     // knowledge is needed very early in process startup.
nativeIsOfficialBuild()307     private static native boolean nativeIsOfficialBuild();
308 
nativeIsPluginEnabled()309     private static native boolean nativeIsPluginEnabled();
310 }
311