• Home
  • History
  • Annotate
  • 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 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 {
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     // 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 
BrowserStartupController(Context context)102     BrowserStartupController(Context context) {
103         mContext = context;
104         mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
105     }
106 
get(Context context)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
overrideInstanceForTest(BrowserStartupController controller)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      */
startBrowserProcessesAsync(final StartupCallback callback)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      */
startBrowserProcessesSync(boolean singleProcess)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
contentStart()196     int contentStart() {
197         return ContentMain.start();
198     }
199 
addStartupCompletedObserver(StartupCallback callback)200     public void addStartupCompletedObserver(StartupCallback callback) {
201         ThreadUtils.assertOnUiThread();
202         if (mStartupDone) {
203             postStartupCompleted(callback);
204         } else {
205             mAsyncStartupCallbacks.add(callback);
206         }
207     }
208 
executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted)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.
enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted)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 
postStartupCompleted(final StartupCallback callback)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
prepareToStartBrowserProcess(boolean singleProcess)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      */
initChromiumBrowserProcessForTests()276     public void initChromiumBrowserProcessForTests() {
277         ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
278         resourceExtractor.startExtractingResources();
279         resourceExtractor.waitForCompletion();
280         nativeSetCommandLineFlags(false, null);
281     }
282 
getPlugins()283     private String getPlugins() {
284         return PepperPluginManager.getPlugins(mContext);
285     }
286 
nativeSetCommandLineFlags( boolean singleProcess, String pluginDescriptor)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.
nativeIsOfficialBuild()292     private static native boolean nativeIsOfficialBuild();
293 
nativeIsPluginEnabled()294     private static native boolean nativeIsPluginEnabled();
295 }
296