Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / BrowserStartupController.java
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 {
44         void onSuccess(boolean alreadyStarted);
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
64     private static void setAsynchronousStartup(boolean enable) {
65         sBrowserMayStartAsynchronously = enable;
66     }
67
68     @VisibleForTesting
69     @CalledByNative
70     static boolean browserMayStartAsynchonously() {
71         return sBrowserMayStartAsynchronously;
72     }
73
74     @VisibleForTesting
75     @CalledByNative
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
102     BrowserStartupController(Context context) {
103         mContext = context;
104         mAsyncStartupCallbacks = new ArrayList<StartupCallback>();
105     }
106
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
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      */
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      */
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
196     int contentStart() {
197         return ContentMain.start();
198     }
199
200     public void addStartupCompletedObserver(StartupCallback callback) {
201         ThreadUtils.assertOnUiThread();
202         if (mStartupDone) {
203             postStartupCompleted(callback);
204         } else {
205             mAsyncStartupCallbacks.add(callback);
206         }
207     }
208
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.
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
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
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      */
276     public void initChromiumBrowserProcessForTests() {
277         ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
278         resourceExtractor.startExtractingResources();
279         resourceExtractor.waitForCompletion();
280         nativeSetCommandLineFlags(false, null);
281     }
282
283     private String getPlugins() {
284         return PepperPluginManager.getPlugins(mContext);
285     }
286
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.
292     private static native boolean nativeIsOfficialBuild();
293
294     private static native boolean nativeIsPluginEnabled();
295 }