| Index: content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
|
| index e6b3a6757a98298cd91f9da0681c4d76d8d0c527..b72d60d38a773c08177e797bb2df0e2eeddd8063 100644
|
| --- a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
|
| @@ -13,6 +13,8 @@ import com.google.common.annotations.VisibleForTesting;
|
| import org.chromium.base.CalledByNative;
|
| import org.chromium.base.JNINamespace;
|
| import org.chromium.base.ThreadUtils;
|
| +import org.chromium.content.app.ContentMain;
|
| +import org.chromium.content.app.LibraryLoader;
|
| import org.chromium.content.common.ProcessInitException;
|
|
|
| import java.util.ArrayList;
|
| @@ -55,12 +57,13 @@ public class BrowserStartupController {
|
|
|
| private static boolean sBrowserMayStartAsynchronously = false;
|
|
|
| - private static void setAsynchronousStartupConfig() {
|
| - sBrowserMayStartAsynchronously = true;
|
| + private static void setAsynchronousStartup(boolean enable) {
|
| + sBrowserMayStartAsynchronously = enable;
|
| }
|
|
|
| + @VisibleForTesting
|
| @CalledByNative
|
| - private static boolean browserMayStartAsynchonously() {
|
| + static boolean browserMayStartAsynchonously() {
|
| return sBrowserMayStartAsynchronously;
|
| }
|
|
|
| @@ -79,13 +82,27 @@ public class BrowserStartupController {
|
| // The context is set on creation, but the reference is cleared after the browser process
|
| // initialization has been started, since it is not needed anymore. This is to ensure the
|
| // context is not leaked.
|
| - private Context mContext;
|
| + private final Context mContext;
|
|
|
| // Whether the async startup of the browser process has started.
|
| private boolean mHasStartedInitializingBrowserProcess;
|
|
|
| // Whether the async startup of the browser process is complete.
|
| - private boolean mAsyncStartupDone;
|
| + private boolean mStartupDone;
|
| +
|
| + // Use single-process mode that runs the renderer on a separate thread in
|
| + // the main application.
|
| + public static final int MAX_RENDERERS_SINGLE_PROCESS = 0;
|
| +
|
| + // Cap on the maximum number of renderer processes that can be requested.
|
| + // This is currently set to account for:
|
| + // 13: The maximum number of sandboxed processes we have available
|
| + // - 1: The regular New Tab Page
|
| + // - 1: The incognito New Tab Page
|
| + // - 1: A regular incognito tab
|
| + // - 1: Safety buffer (http://crbug.com/251279)
|
| + public static final int MAX_RENDERERS_LIMIT =
|
| + ChildProcessLauncher.MAX_REGISTERED_SANDBOXED_SERVICES - 4;
|
|
|
| // This field is set after startup has been completed based on whether the startup was a success
|
| // or not. It is used when later requests to startup come in that happen after the initial set
|
| @@ -124,7 +141,7 @@ public class BrowserStartupController {
|
| */
|
| public void startBrowserProcessesAsync(final StartupCallback callback) {
|
| assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread.";
|
| - if (mAsyncStartupDone) {
|
| + if (mStartupDone) {
|
| // Browser process initialization has already been completed, so we can immediately post
|
| // the callback.
|
| postStartupCompleted(callback);
|
| @@ -139,62 +156,93 @@ public class BrowserStartupController {
|
| // flag that indicates that we have kicked off starting the browser process.
|
| mHasStartedInitializingBrowserProcess = true;
|
|
|
| - enableAsynchronousStartup();
|
| + if (!tryPrepareToStartBrowserProcess(MAX_RENDERERS_LIMIT)) return;
|
|
|
| - // Try to initialize the Android browser process.
|
| - tryToInitializeBrowserProcess();
|
| + setAsynchronousStartup(true);
|
| + if (contentStart() > 0) {
|
| + // Failed. The callbacks may not have run, so run them.
|
| + enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
|
| + }
|
| }
|
| }
|
|
|
| - private void tryToInitializeBrowserProcess() {
|
| + private boolean tryPrepareToStartBrowserProcess(int maxRenderers) {
|
| + // Make sure that everything is in place to initialize the Android browser process.
|
| try {
|
| - assert mContext != null;
|
| - boolean wasAlreadyInitialized = initializeAndroidBrowserProcess();
|
| - // The context is not needed anymore, so clear the member field to not leak.
|
| - mContext = null;
|
| - if (wasAlreadyInitialized) {
|
| - // Something has already initialized the browser process before we got to setup the
|
| - // async startup. This means that we will never get a callback, so manually call
|
| - // them now, and just assume that the startup was successful.
|
| - Log.w(TAG, "Browser process was initialized without BrowserStartupController");
|
| - enqueueCallbackExecution(STARTUP_SUCCESS, ALREADY_STARTED);
|
| - }
|
| + prepareToStartBrowserProcess(maxRenderers);
|
| + return true;
|
| } catch (ProcessInitException e) {
|
| - Log.e(TAG, "Unable to start browser process.", e);
|
| - // ProcessInitException could mean one of two things:
|
| - // 1) The LibraryLoader failed.
|
| - // 2) ContentMain failed to start.
|
| - // It is unclear whether the browser tasks have already been started, and in case they
|
| - // have not, post a message to execute all the callbacks. Whichever call to
|
| - // executeEnqueuedCallbacks comes first will trigger the callbacks, but since the list
|
| - // of callbacks is then cleared, they will only be called once.
|
| + Log.e(TAG, "Unable to load native library.", e);
|
| enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
|
| + return false;
|
| }
|
| }
|
|
|
| + /**
|
| + * Start the browser process synchronously. If the browser is already being started
|
| + * asynchronously then complete startup synchronously
|
| + *
|
| + * <p/>
|
| + * Note that this can only be called on the UI thread.
|
| + *
|
| + * @param max_renderers The maximum number of renderer processes the browser may
|
| + * create. Zero for single process mode.
|
| + * @return true if successfully started, false otherwise.
|
| + */
|
| + public boolean startBrowserProcessesSync(int maxRenderers) {
|
| + if (mStartupDone) {
|
| + // Nothing to do
|
| + return mStartupSuccess;
|
| + }
|
| + if (!mHasStartedInitializingBrowserProcess) {
|
| + if (!tryPrepareToStartBrowserProcess(maxRenderers)) return false;
|
| + }
|
| +
|
| + setAsynchronousStartup(false);
|
| + if (contentStart() > 0) {
|
| + // Failed. The callbacks may not have run, so run them.
|
| + enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
|
| + }
|
| +
|
| + // Startup should now be complete
|
| + assert mStartupDone;
|
| + return mStartupSuccess;
|
| + }
|
| +
|
| + /**
|
| + * Wrap ContentMain.start() for testing.
|
| + */
|
| + @VisibleForTesting
|
| + int contentStart() {
|
| + return ContentMain.start();
|
| + }
|
| +
|
| public void addStartupCompletedObserver(StartupCallback callback) {
|
| ThreadUtils.assertOnUiThread();
|
| - if (mAsyncStartupDone)
|
| + if (mStartupDone) {
|
| postStartupCompleted(callback);
|
| - else
|
| + } else {
|
| mAsyncStartupCallbacks.add(callback);
|
| + }
|
| }
|
|
|
| private void executeEnqueuedCallbacks(int startupResult, boolean alreadyStarted) {
|
| assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread.";
|
| - mAsyncStartupDone = true;
|
| + mStartupDone = true;
|
| + mStartupSuccess = (startupResult <= 0);
|
| for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) {
|
| - if (startupResult > 0) {
|
| - asyncStartupCallback.onFailure();
|
| - } else {
|
| - mStartupSuccess = true;
|
| + if (mStartupSuccess) {
|
| asyncStartupCallback.onSuccess(alreadyStarted);
|
| + } else {
|
| + asyncStartupCallback.onFailure();
|
| }
|
| }
|
| // We don't want to hold on to any objects after we do not need them anymore.
|
| mAsyncStartupCallbacks.clear();
|
| }
|
|
|
| + // Queue the callbacks to run. Since running the callbacks clears the list it is safe to call
|
| + // this more than once.
|
| private void enqueueCallbackExecution(final int startupFailure, final boolean alreadyStarted) {
|
| new Handler().post(new Runnable() {
|
| @Override
|
| @@ -208,28 +256,64 @@ public class BrowserStartupController {
|
| new Handler().post(new Runnable() {
|
| @Override
|
| public void run() {
|
| - if (mStartupSuccess)
|
| + if (mStartupSuccess) {
|
| callback.onSuccess(ALREADY_STARTED);
|
| - else
|
| + } else {
|
| callback.onFailure();
|
| + }
|
| }
|
| });
|
| }
|
|
|
| - /**
|
| - * Ensure that the browser process will be asynchronously started up. This also ensures that we
|
| - * get a call to {@link #browserStartupComplete} when the browser startup is complete.
|
| - */
|
| @VisibleForTesting
|
| - void enableAsynchronousStartup() {
|
| - setAsynchronousStartupConfig();
|
| + void prepareToStartBrowserProcess(int maxRendererProcesses) throws ProcessInitException {
|
| + Log.i(TAG, "Initializing chromium process, renderers=" + maxRendererProcesses);
|
| +
|
| + // Normally Main.java will have kicked this off asynchronously for Chrome. But other
|
| + // ContentView apps like tests also need them so we make sure we've extracted resources
|
| + // here. We can still make it a little async (wait until the library is loaded).
|
| + ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
|
| + resourceExtractor.startExtractingResources();
|
| +
|
| + // Normally Main.java will have already loaded the library asynchronously, we only need
|
| + // to load it here if we arrived via another flow, e.g. bookmark access & sync setup.
|
| + LibraryLoader.ensureInitialized();
|
| +
|
| + // TODO(yfriedman): Remove dependency on a command line flag for this.
|
| + DeviceUtils.addDeviceSpecificUserAgentSwitch(mContext);
|
| +
|
| + Context appContext = mContext.getApplicationContext();
|
| + // Now we really need to have the resources ready.
|
| + resourceExtractor.waitForCompletion();
|
| +
|
| + nativeSetCommandLineFlags(maxRendererProcesses,
|
| + nativeIsPluginEnabled() ? getPlugins() : null);
|
| + ContentMain.initApplicationContext(appContext);
|
| }
|
|
|
| /**
|
| - * @return whether the process was already initialized, so native was not instructed to start.
|
| + * Initialization needed for tests. Mainly used by content browsertests.
|
| */
|
| - @VisibleForTesting
|
| - boolean initializeAndroidBrowserProcess() throws ProcessInitException {
|
| - return !AndroidBrowserProcess.init(mContext, AndroidBrowserProcess.MAX_RENDERERS_LIMIT);
|
| + public void initChromiumBrowserProcessForTests() {
|
| + ResourceExtractor resourceExtractor = ResourceExtractor.get(mContext);
|
| + resourceExtractor.startExtractingResources();
|
| + resourceExtractor.waitForCompletion();
|
| +
|
| + // Having a single renderer should be sufficient for tests. We can't have more than
|
| + // MAX_RENDERERS_LIMIT.
|
| + nativeSetCommandLineFlags(1 /* maxRenderers */, null);
|
| }
|
| +
|
| + private String getPlugins() {
|
| + return PepperPluginManager.getPlugins(mContext);
|
| + }
|
| +
|
| + private static native void nativeSetCommandLineFlags(int maxRenderProcesses,
|
| + String pluginDescriptor);
|
| +
|
| + // Is this an official build of Chrome? Only native code knows for sure. Official build
|
| + // knowledge is needed very early in process startup.
|
| + private static native boolean nativeIsOfficialBuild();
|
| +
|
| + private static native boolean nativeIsPluginEnabled();
|
| }
|
|
|