| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.content.browser; |
| |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.StrictMode; |
| import android.support.annotation.IntDef; |
| |
| import org.chromium.base.BuildInfo; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.Log; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.base.library_loader.LibraryLoader; |
| import org.chromium.base.library_loader.LibraryProcessType; |
| import org.chromium.base.library_loader.LoaderErrors; |
| import org.chromium.base.library_loader.ProcessInitException; |
| import org.chromium.base.task.PostTask; |
| import org.chromium.content.app.ContentMain; |
| import org.chromium.content_public.browser.BrowserStartupController; |
| import org.chromium.content_public.browser.UiThreadTaskTraits; |
| import org.chromium.ui.resources.ResourceExtractor; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Implementation of {@link BrowserStartupController}. |
| * This is a singleton, and stores a reference to the application context. |
| */ |
| @JNINamespace("content") |
| public class BrowserStartupControllerImpl implements BrowserStartupController { |
| private static final String TAG = "cr.BrowserStartup"; |
| |
| // Helper constants for {@link #executeEnqueuedCallbacks(int, boolean)}. |
| @VisibleForTesting |
| static final int STARTUP_SUCCESS = -1; |
| @VisibleForTesting |
| static final int STARTUP_FAILURE = 1; |
| |
| @IntDef({BROWSER_START_TYPE_FULL_BROWSER, BROWSER_START_TYPE_SERVICE_MANAGER_ONLY}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface BrowserStartType {} |
| private static final int BROWSER_START_TYPE_FULL_BROWSER = 0; |
| private static final int BROWSER_START_TYPE_SERVICE_MANAGER_ONLY = 1; |
| |
| private static BrowserStartupControllerImpl sInstance; |
| |
| private static boolean sShouldStartGpuProcessOnBrowserStartup; |
| |
| private static void setShouldStartGpuProcessOnBrowserStartup(boolean enable) { |
| sShouldStartGpuProcessOnBrowserStartup = enable; |
| } |
| |
| @VisibleForTesting |
| @CalledByNative |
| static void browserStartupComplete(int result) { |
| if (sInstance != null) { |
| sInstance.executeEnqueuedCallbacks(result); |
| } |
| } |
| |
| @CalledByNative |
| static void serviceManagerStartupComplete() { |
| if (sInstance != null) { |
| sInstance.serviceManagerStarted(); |
| } |
| } |
| |
| @CalledByNative |
| static boolean shouldStartGpuProcessOnBrowserStartup() { |
| return sShouldStartGpuProcessOnBrowserStartup; |
| } |
| |
| // A list of callbacks that should be called when the async startup of the browser process is |
| // complete. |
| private final List<StartupCallback> mAsyncStartupCallbacks; |
| |
| // A list of callbacks that should be called when the ServiceManager is started. These callbacks |
| // will be called once all the ongoing requests to start ServiceManager or full browser process |
| // are completed. For example, if there is no outstanding request to start full browser process, |
| // the callbacks will be executed once ServiceManager starts. On the other hand, the callbacks |
| // will be defered until full browser starts. |
| private final List<StartupCallback> mServiceManagerCallbacks; |
| |
| // Whether the async startup of the browser process has started. |
| private boolean mHasStartedInitializingBrowserProcess; |
| |
| // Whether tasks that occur after resource extraction have been completed. |
| private boolean mPostResourceExtractionTasksCompleted; |
| |
| private boolean mHasCalledContentStart; |
| |
| // Whether the async startup of the browser process is complete. |
| private boolean mFullBrowserStartupDone; |
| |
| // 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 |
| // of enqueued callbacks have been executed. |
| private boolean mStartupSuccess; |
| |
| private int mLibraryProcessType; |
| |
| // Browser start up type. If the type is |BROWSER_START_TYPE_SERVICE_MANAGER_ONLY|, start up |
| // will be paused after ServiceManager is launched. Additional request to launch the full |
| // browser process is needed to fully complete the startup process. Callbacks will executed |
| // once the browser is fully started, or when the ServiceManager is started and there is no |
| // outstanding requests to start the full browser. |
| @BrowserStartType |
| private int mCurrentBrowserStartType = BROWSER_START_TYPE_FULL_BROWSER; |
| |
| // If the app is only started with the ServiceManager, whether it needs to launch full browser |
| // funcionalities now. |
| private boolean mLaunchFullBrowserAfterServiceManagerStart; |
| |
| // Whether ServiceManager is started. |
| private boolean mServiceManagerStarted; |
| |
| private TracingControllerAndroidImpl mTracingController; |
| |
| BrowserStartupControllerImpl(int libraryProcessType) { |
| mAsyncStartupCallbacks = new ArrayList<>(); |
| mServiceManagerCallbacks = new ArrayList<>(); |
| mLibraryProcessType = libraryProcessType; |
| if (BuildInfo.isDebugAndroid()) { |
| // Only set up the tracing broadcast receiver on debug builds of the OS. Normal tracing |
| // should use the DevTools API. |
| PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() { |
| @Override |
| public void run() { |
| addStartupCompletedObserver(new StartupCallback() { |
| @Override |
| public void onSuccess() { |
| assert mTracingController == null; |
| Context context = ContextUtils.getApplicationContext(); |
| mTracingController = new TracingControllerAndroidImpl(context); |
| mTracingController.registerReceiver(context); |
| } |
| |
| @Override |
| public void onFailure() { |
| // Startup failed. |
| } |
| }); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Get BrowserStartupController instance, create a new one if no existing. |
| * |
| * @param libraryProcessType the type of process the shared library is loaded. it must be |
| * LibraryProcessType.PROCESS_BROWSER or |
| * LibraryProcessType.PROCESS_WEBVIEW. |
| * @return BrowserStartupController instance. |
| */ |
| public static BrowserStartupController get(int libraryProcessType) { |
| assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread."; |
| ThreadUtils.assertOnUiThread(); |
| if (sInstance == null) { |
| assert LibraryProcessType.PROCESS_BROWSER == libraryProcessType |
| || LibraryProcessType.PROCESS_WEBVIEW == libraryProcessType; |
| sInstance = new BrowserStartupControllerImpl(libraryProcessType); |
| } |
| assert sInstance.mLibraryProcessType == libraryProcessType : "Wrong process type"; |
| return sInstance; |
| } |
| |
| @VisibleForTesting |
| public static void overrideInstanceForTest(BrowserStartupController controller) { |
| sInstance = (BrowserStartupControllerImpl) controller; |
| } |
| |
| @Override |
| public void startBrowserProcessesAsync(boolean startGpuProcess, boolean startServiceManagerOnly, |
| final StartupCallback callback) throws ProcessInitException { |
| assert ThreadUtils.runningOnUiThread() : "Tried to start the browser on the wrong thread."; |
| ServicificationStartupUma.getInstance().record(ServicificationStartupUma.getStartupMode( |
| mFullBrowserStartupDone, mServiceManagerStarted, startServiceManagerOnly)); |
| |
| if (mFullBrowserStartupDone || (startServiceManagerOnly && mServiceManagerStarted)) { |
| // Browser process initialization has already been completed, so we can immediately post |
| // the callback. |
| postStartupCompleted(callback); |
| return; |
| } |
| |
| // Browser process has not been fully started yet, so we defer executing the callback. |
| if (startServiceManagerOnly) { |
| mServiceManagerCallbacks.add(callback); |
| } else { |
| mAsyncStartupCallbacks.add(callback); |
| } |
| // If the browser process is launched with ServiceManager only, we need to relaunch the full |
| // process in serviceManagerStarted() if such a request was received. |
| mLaunchFullBrowserAfterServiceManagerStart |= |
| (mCurrentBrowserStartType == BROWSER_START_TYPE_SERVICE_MANAGER_ONLY) |
| && !startServiceManagerOnly; |
| if (!mHasStartedInitializingBrowserProcess) { |
| // This is the first time we have been asked to start the browser process. We set the |
| // flag that indicates that we have kicked off starting the browser process. |
| mHasStartedInitializingBrowserProcess = true; |
| |
| setShouldStartGpuProcessOnBrowserStartup(startGpuProcess); |
| |
| prepareToStartBrowserProcess(false, new Runnable() { |
| @Override |
| public void run() { |
| ThreadUtils.assertOnUiThread(); |
| if (mHasCalledContentStart) return; |
| mCurrentBrowserStartType = startServiceManagerOnly |
| ? BROWSER_START_TYPE_SERVICE_MANAGER_ONLY |
| : BROWSER_START_TYPE_FULL_BROWSER; |
| if (contentStart() > 0) { |
| // Failed. The callbacks may not have run, so run them. |
| enqueueCallbackExecution(STARTUP_FAILURE); |
| } |
| } |
| }); |
| } else if (mServiceManagerStarted && mLaunchFullBrowserAfterServiceManagerStart) { |
| // If we missed the serviceManagerStarted() call, launch the full browser now if needed. |
| // Otherwise, serviceManagerStarted() will handle the full browser launch. |
| mCurrentBrowserStartType = BROWSER_START_TYPE_FULL_BROWSER; |
| if (contentStart() > 0) enqueueCallbackExecution(STARTUP_FAILURE); |
| } |
| } |
| |
| @Override |
| public void startBrowserProcessesSync(boolean singleProcess) throws ProcessInitException { |
| ServicificationStartupUma.getInstance().record( |
| ServicificationStartupUma.getStartupMode(mFullBrowserStartupDone, |
| mServiceManagerStarted, false /* startServiceManagerOnly */)); |
| |
| // If already started skip to checking the result |
| if (!mFullBrowserStartupDone) { |
| if (!mHasStartedInitializingBrowserProcess || !mPostResourceExtractionTasksCompleted) { |
| prepareToStartBrowserProcess(singleProcess, null); |
| } |
| |
| boolean startedSuccessfully = true; |
| if (!mHasCalledContentStart) { |
| mCurrentBrowserStartType = BROWSER_START_TYPE_FULL_BROWSER; |
| if (contentStart() > 0) { |
| // Failed. The callbacks may not have run, so run them. |
| enqueueCallbackExecution(STARTUP_FAILURE); |
| startedSuccessfully = false; |
| } |
| } else if (mCurrentBrowserStartType == BROWSER_START_TYPE_SERVICE_MANAGER_ONLY) { |
| mCurrentBrowserStartType = BROWSER_START_TYPE_FULL_BROWSER; |
| if (contentStart() > 0) { |
| enqueueCallbackExecution(STARTUP_FAILURE); |
| startedSuccessfully = false; |
| } |
| } |
| if (startedSuccessfully) { |
| flushStartupTasks(); |
| } |
| } |
| |
| // Startup should now be complete |
| assert mFullBrowserStartupDone; |
| if (!mStartupSuccess) { |
| throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_STARTUP_FAILED); |
| } |
| } |
| |
| /** |
| * Start the browser process by calling ContentMain.start(). |
| */ |
| int contentStart() { |
| boolean startServiceManagerOnly = |
| mCurrentBrowserStartType == BROWSER_START_TYPE_SERVICE_MANAGER_ONLY; |
| int result = contentMainStart(startServiceManagerOnly); |
| mHasCalledContentStart = true; |
| // No need to launch the full browser again if we are launching full browser now. |
| if (!startServiceManagerOnly) mLaunchFullBrowserAfterServiceManagerStart = false; |
| return result; |
| } |
| |
| /** |
| * Wrap ContentMain.start() for testing. |
| */ |
| @VisibleForTesting |
| int contentMainStart(boolean startServiceManagerOnly) { |
| return ContentMain.start(startServiceManagerOnly); |
| } |
| |
| @VisibleForTesting |
| void flushStartupTasks() { |
| nativeFlushStartupTasks(); |
| } |
| |
| @Override |
| public boolean isStartupSuccessfullyCompleted() { |
| ThreadUtils.assertOnUiThread(); |
| return mFullBrowserStartupDone && mStartupSuccess; |
| } |
| |
| @Override |
| public void addStartupCompletedObserver(StartupCallback callback) { |
| ThreadUtils.assertOnUiThread(); |
| if (mFullBrowserStartupDone) { |
| postStartupCompleted(callback); |
| } else { |
| mAsyncStartupCallbacks.add(callback); |
| } |
| } |
| |
| /** |
| * Called when ServiceManager is launched. |
| */ |
| private void serviceManagerStarted() { |
| mServiceManagerStarted = true; |
| if (mLaunchFullBrowserAfterServiceManagerStart) { |
| // If startFullBrowser() fails, execute the callbacks right away. Otherwise, |
| // callbacks will be deferred until browser startup completes. |
| mCurrentBrowserStartType = BROWSER_START_TYPE_FULL_BROWSER; |
| if (contentStart() > 0) enqueueCallbackExecution(STARTUP_FAILURE); |
| return; |
| } |
| |
| if (mCurrentBrowserStartType == BROWSER_START_TYPE_SERVICE_MANAGER_ONLY) { |
| executeServiceManagerCallbacks(STARTUP_SUCCESS); |
| } |
| recordStartupUma(); |
| } |
| |
| private void executeEnqueuedCallbacks(int startupResult) { |
| assert ThreadUtils.runningOnUiThread() : "Callback from browser startup from wrong thread."; |
| mFullBrowserStartupDone = true; |
| mStartupSuccess = (startupResult <= 0); |
| for (StartupCallback asyncStartupCallback : mAsyncStartupCallbacks) { |
| if (mStartupSuccess) { |
| asyncStartupCallback.onSuccess(); |
| } else { |
| asyncStartupCallback.onFailure(); |
| } |
| } |
| // We don't want to hold on to any objects after we do not need them anymore. |
| mAsyncStartupCallbacks.clear(); |
| |
| executeServiceManagerCallbacks(startupResult); |
| } |
| |
| private void executeServiceManagerCallbacks(int startupResult) { |
| mStartupSuccess = (startupResult <= 0); |
| for (StartupCallback serviceMangerCallback : mServiceManagerCallbacks) { |
| if (mStartupSuccess) { |
| serviceMangerCallback.onSuccess(); |
| } else { |
| serviceMangerCallback.onFailure(); |
| } |
| } |
| mServiceManagerCallbacks.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) { |
| new Handler().post(new Runnable() { |
| @Override |
| public void run() { |
| executeEnqueuedCallbacks(startupFailure); |
| } |
| }); |
| } |
| |
| private void postStartupCompleted(final StartupCallback callback) { |
| new Handler().post(new Runnable() { |
| @Override |
| public void run() { |
| if (mStartupSuccess) { |
| callback.onSuccess(); |
| } else { |
| callback.onFailure(); |
| } |
| } |
| }); |
| } |
| |
| @VisibleForTesting |
| void prepareToStartBrowserProcess(final boolean singleProcess, |
| final Runnable completionCallback) throws ProcessInitException { |
| Log.i(TAG, "Initializing chromium process, singleProcess=%b", singleProcess); |
| |
| // This strictmode exception is to cover the case where the browser process is being started |
| // asynchronously but not in the main browser flow. The main browser flow will trigger |
| // library loading earlier and this will be a no-op, but in the other cases this will need |
| // to block on loading libraries. |
| // This applies to tests and ManageSpaceActivity, which can be launched from Settings. |
| StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
| try { |
| // 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.getInstance().ensureInitialized(mLibraryProcessType); |
| } finally { |
| StrictMode.setThreadPolicy(oldPolicy); |
| } |
| |
| Runnable postResourceExtraction = new Runnable() { |
| @Override |
| public void run() { |
| if (!mPostResourceExtractionTasksCompleted) { |
| // TODO(yfriedman): Remove dependency on a command line flag for this. |
| DeviceUtilsImpl.addDeviceSpecificUserAgentSwitch(); |
| nativeSetCommandLineFlags(singleProcess); |
| mPostResourceExtractionTasksCompleted = true; |
| } |
| |
| if (completionCallback != null) completionCallback.run(); |
| } |
| }; |
| |
| if (completionCallback == null) { |
| // If no continuation callback is specified, then force the resource extraction |
| // to complete. |
| ResourceExtractor.get().waitForCompletion(); |
| postResourceExtraction.run(); |
| } else { |
| ResourceExtractor.get().addCompletionCallback(postResourceExtraction); |
| } |
| } |
| |
| /** |
| * Can be overridden by testing. |
| */ |
| @VisibleForTesting |
| void recordStartupUma() { |
| ServicificationStartupUma.getInstance().commit(); |
| } |
| |
| /** |
| * Initialization needed for tests. Mainly used by content browsertests. |
| */ |
| @Override |
| public void initChromiumBrowserProcessForTests() { |
| ResourceExtractor resourceExtractor = ResourceExtractor.get(); |
| resourceExtractor.startExtractingResources(); |
| resourceExtractor.waitForCompletion(); |
| nativeSetCommandLineFlags(false); |
| } |
| |
| private static native void nativeSetCommandLineFlags(boolean singleProcess); |
| |
| // 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 void nativeFlushStartupTasks(); |
| } |