diff options
17 files changed, 667 insertions, 77 deletions
diff --git a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java index 98df0c1..b53c80d 100644 --- a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java +++ b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java @@ -16,7 +16,7 @@ import org.chromium.base.MemoryPressureListener; import org.chromium.chrome.browser.DevToolsServer; import org.chromium.content.browser.ActivityContentVideoViewClient; import org.chromium.content.browser.AndroidBrowserProcess; -import org.chromium.content.browser.ContentVideoView; +import org.chromium.content.browser.BrowserStartupConfig; import org.chromium.content.browser.ContentVideoViewClient; import org.chromium.content.browser.ContentView; import org.chromium.content.browser.ContentViewClient; @@ -51,19 +51,39 @@ public class ChromiumTestShellActivity extends ChromiumActivity { private DevToolsServer mDevToolsServer; @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!CommandLine.isInitialized()) CommandLine.initFromFile(COMMAND_LINE_FILE); waitForDebuggerIfNeeded(); DeviceUtils.addDeviceSpecificUserAgentSwitch(this); + + BrowserStartupConfig.setAsync(new BrowserStartupConfig.StartupCallback() { + @Override + public void run(int startupResult) { + if (startupResult > 0) { + // TODO: Show error message. + Log.e(TAG, "Chromium browser process initialization failed"); + finish(); + } else { + finishInitialization(savedInstanceState); + } + } + }); + try { - AndroidBrowserProcess.init(this, AndroidBrowserProcess.MAX_RENDERERS_LIMIT); + if (!AndroidBrowserProcess.init(this, AndroidBrowserProcess.MAX_RENDERERS_LIMIT)) { + // Process was already running, finish initialization now. + finishInitialization(savedInstanceState); + } } catch (ProcessInitException e) { Log.e(TAG, "Chromium browser process initialization failed", e); finish(); } + } + + private void finishInitialization(final Bundle savedInstanceState) { setContentView(R.layout.testshell_activity); mTabManager = (TabManager) findViewById(R.id.tab_manager); String startupUrl = getUrlFromIntent(getIntent()); @@ -161,6 +181,7 @@ public class ChromiumTestShellActivity extends ChromiumActivity { /** * Creates a {@link TestShellTab} with a URL specified by {@code url}. + * * @param url The URL the new {@link TestShellTab} should start with. */ public void createTab(String url) { diff --git a/chrome/browser/chrome_browser_main.h b/chrome/browser/chrome_browser_main.h index e7cac1d..10c1d8e 100644 --- a/chrome/browser/chrome_browser_main.h +++ b/chrome/browser/chrome_browser_main.h @@ -17,6 +17,7 @@ #include "chrome/browser/ui/startup/startup_browser_creator.h" #include "content/public/browser/browser_main_parts.h" #include "content/public/browser/render_view_host.h" +#include "content/public/common/main_function_params.h" class ActiveTabTracker; class BrowserProcessImpl; @@ -41,10 +42,6 @@ namespace chrome_browser_metrics { class TrackingSynchronizer; } -namespace content { -struct MainFunctionParams; -} - namespace performance_monitor { class StartupTimer; } @@ -134,7 +131,7 @@ class ChromeBrowserMainParts : public content::BrowserMainParts { // Members initialized on construction --------------------------------------- - const content::MainFunctionParams& parameters_; + const content::MainFunctionParams parameters_; const CommandLine& parsed_command_line_; int result_code_; diff --git a/content/browser/android/browser_jni_registrar.cc b/content/browser/android/browser_jni_registrar.cc index 25e0c3b..35da3af 100644 --- a/content/browser/android/browser_jni_registrar.cc +++ b/content/browser/android/browser_jni_registrar.cc @@ -9,6 +9,7 @@ #include "content/browser/accessibility/browser_accessibility_android.h" #include "content/browser/accessibility/browser_accessibility_manager_android.h" #include "content/browser/android/android_browser_process.h" +#include "content/browser/android/browser_startup_config.h" #include "content/browser/android/child_process_launcher_android.h" #include "content/browser/android/content_settings.h" #include "content/browser/android/content_video_view.h" @@ -36,37 +37,37 @@ using content::SurfaceTexturePeerBrowserImpl; namespace { base::android::RegistrationMethod kContentRegisteredMethods[] = { - { "AndroidLocationApiAdapter", - content::AndroidLocationApiAdapter::RegisterGeolocationService }, - { "AndroidBrowserProcess", content::RegisterAndroidBrowserProcess }, - { "BrowserAccessibilityManager", - content::RegisterBrowserAccessibilityManager }, - { "ChildProcessLauncher", content::RegisterChildProcessLauncher }, - { "ContentSettings", content::ContentSettings::RegisterContentSettings }, - { "ContentViewRenderView", - content::ContentViewRenderView::RegisterContentViewRenderView }, - { "ContentVideoView", content::ContentVideoView::RegisterContentVideoView }, - { "ContentViewCore", content::RegisterContentViewCore }, - { "DataFetcherImplAndroid", content::DataFetcherImplAndroid::Register }, - { "DateTimePickerAndroid", content::RegisterDateTimeChooserAndroid }, - { "DownloadControllerAndroidImpl", - content::DownloadControllerAndroidImpl::RegisterDownloadController }, - { "InterstitialPageDelegateAndroid", - content::InterstitialPageDelegateAndroid - ::RegisterInterstitialPageDelegateAndroid }, - { "MediaResourceGetterImpl", - content::MediaResourceGetterImpl::RegisterMediaResourceGetter }, - { "LoadUrlParams", content::RegisterLoadUrlParams }, - { "PowerSaveBlock", content::RegisterPowerSaveBlocker }, - { "RegisterImeAdapter", content::RegisterImeAdapter }, - { "SpeechRecognizerImplAndroid", - content::SpeechRecognizerImplAndroid::RegisterSpeechRecognizer }, - { "TouchPoint", content::RegisterTouchPoint }, - { "TracingIntentHandler", content::RegisterTracingIntentHandler }, - { "VibrationMessageFilter", content::VibrationMessageFilter::Register }, - { "WebContentsObserverAndroid", content::RegisterWebContentsObserverAndroid }, - { "WebViewStatics", content::RegisterWebViewStatics }, -}; + {"AndroidLocationApiAdapter", + content::AndroidLocationApiAdapter::RegisterGeolocationService}, + {"AndroidBrowserProcess", content::RegisterAndroidBrowserProcess}, + {"BrowserAccessibilityManager", + content::RegisterBrowserAccessibilityManager}, + {"BrowserStartupConfiguration", content::RegisterBrowserStartupConfig}, + {"ChildProcessLauncher", content::RegisterChildProcessLauncher}, + {"ContentSettings", content::ContentSettings::RegisterContentSettings}, + {"ContentViewRenderView", + content::ContentViewRenderView::RegisterContentViewRenderView}, + {"ContentVideoView", content::ContentVideoView::RegisterContentVideoView}, + {"ContentViewCore", content::RegisterContentViewCore}, + {"DataFetcherImplAndroid", content::DataFetcherImplAndroid::Register}, + {"DateTimePickerAndroid", content::RegisterDateTimeChooserAndroid}, + {"DownloadControllerAndroidImpl", + content::DownloadControllerAndroidImpl::RegisterDownloadController}, + {"InterstitialPageDelegateAndroid", + content::InterstitialPageDelegateAndroid:: + RegisterInterstitialPageDelegateAndroid}, + {"MediaResourceGetterImpl", + content::MediaResourceGetterImpl::RegisterMediaResourceGetter}, + {"LoadUrlParams", content::RegisterLoadUrlParams}, + {"PowerSaveBlock", content::RegisterPowerSaveBlocker}, + {"RegisterImeAdapter", content::RegisterImeAdapter}, + {"SpeechRecognizerImplAndroid", + content::SpeechRecognizerImplAndroid::RegisterSpeechRecognizer}, + {"TouchPoint", content::RegisterTouchPoint}, + {"TracingIntentHandler", content::RegisterTracingIntentHandler}, + {"VibrationMessageFilter", content::VibrationMessageFilter::Register}, + {"WebContentsObserverAndroid", content::RegisterWebContentsObserverAndroid}, + {"WebViewStatics", content::RegisterWebViewStatics}, }; } // namespace diff --git a/content/browser/android/browser_startup_config.cc b/content/browser/android/browser_startup_config.cc new file mode 100644 index 0000000..3e74208 --- /dev/null +++ b/content/browser/android/browser_startup_config.cc @@ -0,0 +1,25 @@ +// 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. + +#include "content/browser/android/browser_startup_config.h" + +#include "base/android/jni_android.h" +#include "jni/BrowserStartupConfig_jni.h" + +namespace content { + +bool BrowserMayStartAsynchronously() { + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_BrowserStartupConfig_browserMayStartAsynchonously(env); +} + +void BrowserStartupComplete(int result) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_BrowserStartupConfig_browserStartupComplete(env, result); +} + +bool RegisterBrowserStartupConfig(JNIEnv* env) { + return RegisterNativesImpl(env); +} +} // namespace content diff --git a/content/browser/android/browser_startup_config.h b/content/browser/android/browser_startup_config.h new file mode 100644 index 0000000..95575f2 --- /dev/null +++ b/content/browser/android/browser_startup_config.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef CONTENT_BROWSER_BROWSER_STARTUP_CONFIGURATION_H_ +#define CONTENT_BROWSER_BROWSER_STARTUP_CONFIGURATION_H_ + +#include <jni.h> + +namespace content { + +bool BrowserMayStartAsynchronously(); +void BrowserStartupComplete(int result); + +bool RegisterBrowserStartupConfig(JNIEnv* env); + +} // namespace content +#endif // CONTENT_BROWSER_BROWSER_STARTUP_CONFIGURATION_H_ diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index cc45bc2..9e02cc4 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -34,6 +34,7 @@ #include "content/browser/renderer_host/media/audio_mirroring_manager.h" #include "content/browser/renderer_host/media/media_stream_manager.h" #include "content/browser/speech/speech_recognition_manager_impl.h" +#include "content/browser/startup_task_runner.h" #include "content/browser/tracing/trace_controller_impl.h" #include "content/browser/webui/content_web_ui_controller_factory.h" #include "content/browser/webui/url_data_manager.h" @@ -60,6 +61,7 @@ #if defined(OS_ANDROID) #include "base/android/jni_android.h" +#include "content/browser/android/browser_startup_config.h" #include "content/browser/android/surface_texture_peer_browser_impl.h" #endif @@ -299,7 +301,8 @@ BrowserMainLoop* BrowserMainLoop::GetInstance() { BrowserMainLoop::BrowserMainLoop(const MainFunctionParams& parameters) : parameters_(parameters), parsed_command_line_(parameters.command_line), - result_code_(RESULT_CODE_NORMAL_EXIT) { + result_code_(RESULT_CODE_NORMAL_EXIT), + created_threads_(false) { DCHECK(!g_current_browser_main_loop); g_current_browser_main_loop = this; } @@ -483,8 +486,7 @@ void BrowserMainLoop::MainMessageLoopStart() { #endif } -void BrowserMainLoop::CreateThreads() { - TRACE_EVENT0("startup", "BrowserMainLoop::CreateThreads") +int BrowserMainLoop::PreCreateThreads() { if (parts_) { TRACE_EVENT0("startup", @@ -509,9 +511,44 @@ void BrowserMainLoop::CreateThreads() { if (parsed_command_line_.HasSwitch(switches::kSingleProcess)) RenderProcessHost::SetRunRendererInProcess(true); #endif + return result_code_; +} - if (result_code_ > 0) - return; +void BrowserMainLoop::CreateStartupTasks() { + TRACE_EVENT0("startup", "BrowserMainLoop::CreateStartupTasks") + +#if defined(OS_ANDROID) + scoped_refptr<StartupTaskRunner> task_runner = + new StartupTaskRunner(BrowserMayStartAsynchronously(), + base::Bind(&BrowserStartupComplete), + base::MessageLoop::current()->message_loop_proxy()); +#else + scoped_refptr<StartupTaskRunner> task_runner = + new StartupTaskRunner(false, + base::Callback<void(int)>(), + base::MessageLoop::current()->message_loop_proxy()); +#endif + StartupTask pre_create_threads = + base::Bind(&BrowserMainLoop::PreCreateThreads, base::Unretained(this)); + task_runner->AddTask(pre_create_threads); + + StartupTask create_threads = + base::Bind(&BrowserMainLoop::CreateThreads, base::Unretained(this)); + task_runner->AddTask(create_threads); + + StartupTask browser_thread_started = base::Bind( + &BrowserMainLoop::BrowserThreadsStarted, base::Unretained(this)); + task_runner->AddTask(browser_thread_started); + + StartupTask pre_main_message_loop_run = base::Bind( + &BrowserMainLoop::PreMainMessageLoopRun, base::Unretained(this)); + task_runner->AddTask(pre_main_message_loop_run); + + task_runner->StartRunningTasks(); +} + +int BrowserMainLoop::CreateThreads() { + TRACE_EVENT0("startup", "BrowserMainLoop::CreateThreads"); base::Thread::Options default_options; base::Thread::Options io_message_loop_options; @@ -596,14 +633,11 @@ void BrowserMainLoop::CreateThreads() { TRACE_EVENT_END0("startup", "BrowserMainLoop::CreateThreads:start"); } + created_threads_ = true; + return result_code_; +} -#if !defined(OS_IOS) - indexed_db_thread_.reset(new base::Thread("IndexedDB")); - indexed_db_thread_->Start(); -#endif - - BrowserThreadsStarted(); - +int BrowserMainLoop::PreMainMessageLoopRun() { if (parts_) { TRACE_EVENT0("startup", "BrowserMainLoop::CreateThreads:PreMainMessageLoopRun"); @@ -614,6 +648,7 @@ void BrowserMainLoop::CreateThreads() { // Do not allow disk IO from the UI thread. base::ThreadRestrictions::SetIOAllowed(false); base::ThreadRestrictions::DisallowWaiting(); + return result_code_; } void BrowserMainLoop::RunMainMessageLoopParts() { @@ -630,6 +665,11 @@ void BrowserMainLoop::RunMainMessageLoopParts() { } void BrowserMainLoop::ShutdownThreadsAndCleanUp() { + + if (!created_threads_) { + // Called early, nothing to do + return; + } // Teardown may start in PostMainMessageLoopRun, and during teardown we // need to be able to perform IO. base::ThreadRestrictions::SetIOAllowed(true); @@ -765,8 +805,14 @@ void BrowserMainLoop::InitializeMainThread() { new BrowserThreadImpl(BrowserThread::UI, base::MessageLoop::current())); } -void BrowserMainLoop::BrowserThreadsStarted() { +int BrowserMainLoop::BrowserThreadsStarted() { TRACE_EVENT0("startup", "BrowserMainLoop::BrowserThreadsStarted") + +#if !defined(OS_IOS) + indexed_db_thread_.reset(new base::Thread("IndexedDB")); + indexed_db_thread_->Start(); +#endif + #if defined(OS_ANDROID) // Up the priority of anything that touches with display tasks // (this thread is UI thread, and io_thread_ is for IPCs). @@ -844,6 +890,7 @@ void BrowserMainLoop::BrowserThreadsStarted() { CAUSE_FOR_GPU_LAUNCH_BROWSER_STARTUP)); } #endif // !defined(OS_IOS) + return result_code_; } void BrowserMainLoop::InitializeToolkit() { diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h index 5c3608b..7b2879f 100644 --- a/content/browser/browser_main_loop.h +++ b/content/browser/browser_main_loop.h @@ -6,8 +6,10 @@ #define CONTENT_BROWSER_BROWSER_MAIN_LOOP_H_ #include "base/basictypes.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "content/browser/browser_process_sub_thread.h" +#include "content/public/browser/browser_main_runner.h" class CommandLine; @@ -65,8 +67,8 @@ class CONTENT_EXPORT BrowserMainLoop { void InitializeToolkit(); void MainMessageLoopStart(); - // Create all secondary threads. - void CreateThreads(); + // Create the tasks we need to complete startup. + void CreateStartupTasks(); // Perform the default message loop run logic. void RunMainMessageLoopParts(); @@ -94,8 +96,16 @@ class CONTENT_EXPORT BrowserMainLoop { void InitializeMainThread(); + // Called just before creating the threads + int PreCreateThreads(); + + // Create all secondary threads. + int CreateThreads(); + // Called right after the browser threads have been started. - void BrowserThreadsStarted(); + int BrowserThreadsStarted(); + + int PreMainMessageLoopRun(); void MainMessageLoopRun(); @@ -103,6 +113,8 @@ class CONTENT_EXPORT BrowserMainLoop { const MainFunctionParams& parameters_; const CommandLine& parsed_command_line_; int result_code_; + // True if the non-UI threads were created. + bool created_threads_; // Members initialized in |MainMessageLoopStart()| --------------------------- scoped_ptr<base::MessageLoop> main_message_loop_; diff --git a/content/browser/browser_main_runner.cc b/content/browser/browser_main_runner.cc index f50832a..cca1466 100644 --- a/content/browser/browser_main_runner.cc +++ b/content/browser/browser_main_runner.cc @@ -29,11 +29,7 @@ namespace content { class BrowserMainRunnerImpl : public BrowserMainRunner { public: - BrowserMainRunnerImpl() - : is_initialized_(false), - is_shutdown_(false), - created_threads_(false) { - } + BrowserMainRunnerImpl() : is_initialized_(false), is_shutdown_(false) {} virtual ~BrowserMainRunnerImpl() { if (is_initialized_ && !is_shutdown_) @@ -102,11 +98,10 @@ class BrowserMainRunnerImpl : public BrowserMainRunner { #endif ui::InitializeInputMethod(); - main_loop_->CreateThreads(); + main_loop_->CreateStartupTasks(); int result_code = main_loop_->GetResultCode(); if (result_code > 0) return result_code; - created_threads_ = true; // Return -1 to indicate no early termination. return -1; @@ -124,8 +119,7 @@ class BrowserMainRunnerImpl : public BrowserMainRunner { DCHECK(!is_shutdown_); g_exited_main_message_loop = true; - if (created_threads_) - main_loop_->ShutdownThreadsAndCleanUp(); + main_loop_->ShutdownThreadsAndCleanUp(); ui::ShutdownInputMethod(); #if defined(OS_WIN) @@ -146,9 +140,6 @@ class BrowserMainRunnerImpl : public BrowserMainRunner { // True if the runner has been shut down. bool is_shutdown_; - // True if the non-UI threads were created. - bool created_threads_; - scoped_ptr<NotificationServiceImpl> notification_service_; scoped_ptr<BrowserMainLoop> main_loop_; #if defined(OS_WIN) diff --git a/content/browser/startup_task_runner.cc b/content/browser/startup_task_runner.cc new file mode 100644 index 0000000..a7e730e --- /dev/null +++ b/content/browser/startup_task_runner.cc @@ -0,0 +1,63 @@ +// 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. + +#include "content/browser/startup_task_runner.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" + +namespace content { + +StartupTaskRunner::StartupTaskRunner( + bool browser_may_start_asynchronously, + base::Callback<void(int)> const startup_complete_callback, + scoped_refptr<base::SingleThreadTaskRunner> proxy) + : asynchronous_startup_(browser_may_start_asynchronously), + startup_complete_callback_(startup_complete_callback), + proxy_(proxy) {} + +void StartupTaskRunner::AddTask(StartupTask& callback) { + task_list_.push_back(callback); +} + +void StartupTaskRunner::StartRunningTasks() { + DCHECK(proxy_); + int result = 0; + if (asynchronous_startup_ && !task_list_.empty()) { + const base::Closure next_task = + base::Bind(&StartupTaskRunner::WrappedTask, this); + proxy_->PostNonNestableTask(FROM_HERE, next_task); + } else { + for (std::list<StartupTask>::iterator it = task_list_.begin(); + it != task_list_.end(); + it++) { + result = it->Run(); + if (result > 0) { + break; + } + } + if (!startup_complete_callback_.is_null()) { + startup_complete_callback_.Run(result); + } + } +} + +void StartupTaskRunner::WrappedTask() { + int result = task_list_.front().Run(); + task_list_.pop_front(); + if (result > 0 || task_list_.empty()) { + if (!startup_complete_callback_.is_null()) { + startup_complete_callback_.Run(result); + } + } else { + const base::Closure next_task = + base::Bind(&StartupTaskRunner::WrappedTask, this); + proxy_->PostNonNestableTask(FROM_HERE, next_task); + } +} + +StartupTaskRunner::~StartupTaskRunner() {} + +} // namespace content diff --git a/content/browser/startup_task_runner.h b/content/browser/startup_task_runner.h new file mode 100644 index 0000000..5f954ed --- /dev/null +++ b/content/browser/startup_task_runner.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef CONTENT_BROWSER_STARTUP_TASK_RUNNER_H_ +#define CONTENT_BROWSER_STARTUP_TASK_RUNNER_H_ + +#include <list> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" + +#include "build/build_config.h" + +#include "content/public/browser/browser_main_runner.h" + +namespace content { + +// A startup task is a void function returning the status on completion. +// a status of > 0 indicates a failure, and that no further startup tasks should +// be run. +typedef base::Callback<int(void)> StartupTask; + +// This class runs startup tasks. The tasks are either run immediately inline, +// or are queued one at a time on the UI thread's message loop. If the events +// are queued, UI events that are received during startup will be acted upon +// between startup tasks. The motivation for this is that, on targets where the +// UI is already started, it allows us to keep the UI responsive during startup. +// +// Note that this differs from a SingleThreadedTaskRunner in that there may be +// no opportunity to handle UI events between the tasks of a +// SingleThreadedTaskRunner. + +class CONTENT_EXPORT StartupTaskRunner + : public base::RefCounted<StartupTaskRunner> { + + public: + // Constructor: Note that |startup_complete_callback| is optional. If it is + // not null it will be called once all the startup tasks have run. + StartupTaskRunner(bool browser_may_start_asynchronously, + base::Callback<void(int)> startup_complete_callback, + scoped_refptr<base::SingleThreadTaskRunner> proxy); + + // Add a task to the queue of startup tasks to be run. + virtual void AddTask(StartupTask& callback); + + // Start running the tasks. + virtual void StartRunningTasks(); + + private: + friend class base::RefCounted<StartupTaskRunner>; + virtual ~StartupTaskRunner(); + + std::list<StartupTask> task_list_; + void WrappedTask(); + + const bool asynchronous_startup_; + base::Callback<void(int)> startup_complete_callback_; + scoped_refptr<base::SingleThreadTaskRunner> proxy_; + + DISALLOW_COPY_AND_ASSIGN(StartupTaskRunner); +}; + +} // namespace content +#endif // CONTENT_BROWSER_STARTUP_TASK_RUNNER_H_ diff --git a/content/browser/startup_task_runner_unittest.cc b/content/browser/startup_task_runner_unittest.cc new file mode 100644 index 0000000..2efa79f --- /dev/null +++ b/content/browser/startup_task_runner_unittest.cc @@ -0,0 +1,281 @@ +// 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. + +#include "content/browser/startup_task_runner.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/run_loop.h" +#include "base/task_runner.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { +namespace { + +using base::Closure; +using testing::_; +using testing::Assign; +using testing::Invoke; +using testing::WithArg; + +bool observer_called = false; +int observer_result; +base::Closure task; + +// I couldn't get gMock's SaveArg to compile, hence had to save the argument +// this way +bool SaveTaskArg(const Closure& arg) { + task = arg; + return true; +} + +void Observer(int result) { + observer_called = true; + observer_result = result; +} + +class StartupTaskRunnerTest : public testing::Test { + public: + + virtual void SetUp() { + last_task_ = 0; + observer_called = false; + } + + int Task1() { + last_task_ = 1; + return 0; + } + + int Task2() { + last_task_ = 2; + return 0; + } + + int FailingTask() { + // Task returning failure + last_task_ = 3; + return 1; + } + + int GetLastTask() { return last_task_; } + + private: + + int last_task_; +}; + +// We can't use the real message loop, even if we want to, since doing so on +// Android requires a complex Java infrastructure. The test would have to built +// as a content_shell test; but content_shell startup invokes the class we are +// trying to test. +// +// The mocks are not directly in TaskRunnerProxy because reference counted +// objects seem to confuse the mocking framework + +class MockTaskRunner { + public: + MOCK_METHOD3( + PostDelayedTask, + bool(const tracked_objects::Location&, const Closure&, base::TimeDelta)); + MOCK_METHOD3( + PostNonNestableDelayedTask, + bool(const tracked_objects::Location&, const Closure&, base::TimeDelta)); +}; + +class TaskRunnerProxy : public base::SingleThreadTaskRunner { + public: + TaskRunnerProxy(MockTaskRunner* mock) : mock_(mock) {} + virtual bool RunsTasksOnCurrentThread() const OVERRIDE { return true; } + virtual bool PostDelayedTask(const tracked_objects::Location& location, + const Closure& closure, + base::TimeDelta delta) OVERRIDE { + return mock_->PostDelayedTask(location, closure, delta); + } + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& location, + const Closure& closure, + base::TimeDelta delta) OVERRIDE { + return mock_->PostNonNestableDelayedTask(location, closure, delta); + } + + private: + MockTaskRunner* mock_; + virtual ~TaskRunnerProxy() {} +}; + +TEST_F(StartupTaskRunnerTest, SynchronousExecution) { + MockTaskRunner mock_runner; + scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); + + EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); + EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0); + + scoped_refptr<StartupTaskRunner> runner = + new StartupTaskRunner(false, base::Bind(&Observer), proxy); + + StartupTask task1 = + base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this)); + runner->AddTask(task1); + EXPECT_EQ(GetLastTask(), 0); + StartupTask task2 = + base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); + runner->AddTask(task2); + + // Nothing should run until we tell them to. + EXPECT_EQ(GetLastTask(), 0); + runner->StartRunningTasks(); + + // On an immediate StartupTaskRunner the tasks should now all have run. + EXPECT_EQ(GetLastTask(), 2); + + EXPECT_TRUE(observer_called); + EXPECT_EQ(observer_result, 0); +} + +TEST_F(StartupTaskRunnerTest, NullObserver) { + MockTaskRunner mock_runner; + scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); + + EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); + EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0); + + scoped_refptr<StartupTaskRunner> runner = + new StartupTaskRunner(false, base::Callback<void(int)>(), proxy); + + StartupTask task1 = + base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this)); + runner->AddTask(task1); + EXPECT_EQ(GetLastTask(), 0); + StartupTask task2 = + base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); + runner->AddTask(task2); + + // Nothing should run until we tell them to. + EXPECT_EQ(GetLastTask(), 0); + runner->StartRunningTasks(); + + // On an immediate StartupTaskRunner the tasks should now all have run. + EXPECT_EQ(GetLastTask(), 2); + + EXPECT_FALSE(observer_called); +} + +TEST_F(StartupTaskRunnerTest, SynchronousExecutionFailedTask) { + MockTaskRunner mock_runner; + scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); + + EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); + EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0); + + scoped_refptr<StartupTaskRunner> runner = + new StartupTaskRunner(false, base::Bind(&Observer), proxy); + + StartupTask task3 = + base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this)); + runner->AddTask(task3); + EXPECT_EQ(GetLastTask(), 0); + StartupTask task2 = + base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); + runner->AddTask(task2); + + // Nothing should run until we tell them to. + EXPECT_EQ(GetLastTask(), 0); + runner->StartRunningTasks(); + + // Only the first task should have run, since it failed + EXPECT_EQ(GetLastTask(), 3); + + EXPECT_TRUE(observer_called); + EXPECT_EQ(observer_result, 1); +} + +TEST_F(StartupTaskRunnerTest, AsynchronousExecution) { + + MockTaskRunner mock_runner; + scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); + + EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); + EXPECT_CALL( + mock_runner, + PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0))) + .Times(testing::Between(2, 3)) + .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg))); + + scoped_refptr<StartupTaskRunner> runner = + new StartupTaskRunner(true, base::Bind(&Observer), proxy); + + StartupTask task1 = + base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this)); + runner->AddTask(task1); + StartupTask task2 = + base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); + runner->AddTask(task2); + + // Nothing should run until we tell them to. + EXPECT_EQ(GetLastTask(), 0); + runner->StartRunningTasks(); + + // No tasks should have run yet, since we the message loop hasn't run. + EXPECT_EQ(GetLastTask(), 0); + + // Fake the actual message loop. Each time a task is run a new task should + // be added to the queue, hence updating "task". The loop should actually run + // at most 3 times (once for each task plus possibly once for the observer), + // the "4" is a backstop. + for (int i = 0; i < 4 && !observer_called; i++) { + task.Run(); + EXPECT_EQ(i + 1, GetLastTask()); + } + EXPECT_TRUE(observer_called); + EXPECT_EQ(observer_result, 0); +} + +TEST_F(StartupTaskRunnerTest, AsynchronousExecutionFailedTask) { + + MockTaskRunner mock_runner; + scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); + + EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); + EXPECT_CALL( + mock_runner, + PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0))) + .Times(testing::Between(1, 2)) + .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg))); + + scoped_refptr<StartupTaskRunner> runner = + new StartupTaskRunner(true, base::Bind(&Observer), proxy); + + StartupTask task3 = + base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this)); + runner->AddTask(task3); + StartupTask task2 = + base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); + runner->AddTask(task2); + + // Nothing should run until we tell them to. + EXPECT_EQ(GetLastTask(), 0); + runner->StartRunningTasks(); + + // No tasks should have run yet, since we the message loop hasn't run. + EXPECT_EQ(GetLastTask(), 0); + + // Fake the actual message loop. Each time a task is run a new task should + // be added to the queue, hence updating "task". The loop should actually run + // at most twice (once for the failed task plus possibly once for the + // observer), the "4" is a backstop. + for (int i = 0; i < 4 && !observer_called; i++) { + task.Run(); + } + EXPECT_EQ(GetLastTask(), 3); + + EXPECT_TRUE(observer_called); + EXPECT_EQ(observer_result, 1); +} +} // namespace +} // namespace content diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 6d6581d..d7120cb 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -237,6 +237,8 @@ 'browser/android/browser_jni_registrar.h', 'browser/android/browser_media_player_manager.cc', 'browser/android/browser_media_player_manager.h', + 'browser/android/browser_startup_config.cc', + 'browser/android/browser_startup_config.h', 'browser/android/child_process_launcher_android.cc', 'browser/android/child_process_launcher_android.h', 'browser/android/content_settings.cc', @@ -1046,6 +1048,8 @@ 'browser/ssl/ssl_policy.h', 'browser/ssl/ssl_request_info.cc', 'browser/ssl/ssl_request_info.h', + 'browser/startup_task_runner.cc', + 'browser/startup_task_runner.h', 'browser/streams/stream.cc', 'browser/streams/stream.h', 'browser/streams/stream_context.cc', diff --git a/content/content_jni.gypi b/content/content_jni.gypi index fe10d80..0ea4ad4 100644 --- a/content/content_jni.gypi +++ b/content/content_jni.gypi @@ -13,6 +13,7 @@ 'public/android/java/src/org/chromium/content/app/LibraryLoader.java', 'public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java', 'public/android/java/src/org/chromium/content/browser/AndroidBrowserProcess.java', + 'public/android/java/src/org/chromium/content/browser/BrowserStartupConfig.java', 'public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java', 'public/android/java/src/org/chromium/content/browser/ContentSettings.java', 'public/android/java/src/org/chromium/content/browser/ContentVideoView.java', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 1425e81..b5c41e5 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -369,6 +369,7 @@ 'browser/speech/google_streaming_remote_engine_unittest.cc', 'browser/speech/speech_recognizer_impl_unittest.cc', 'browser/ssl/ssl_host_state_unittest.cc', + 'browser/startup_task_runner_unittest.cc', 'browser/storage_partition_impl_unittest.cc', 'browser/storage_partition_impl_map_unittest.cc', 'browser/streams/stream_unittest.cc', diff --git a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupConfig.java b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupConfig.java new file mode 100644 index 0000000..f784c7a --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupConfig.java @@ -0,0 +1,43 @@ +// 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 org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +/** + * This class controls how C++ browser main loop is run. + */ +@JNINamespace("content") +public class BrowserStartupConfig { + public interface StartupCallback { + void run(int startupResult); + } + + private static boolean sBrowserMayStartAsynchronously = false; + private static StartupCallback sBrowserStartupCompleteCallback = null; + + @CalledByNative + private static boolean browserMayStartAsynchonously() { + return sBrowserMayStartAsynchronously; + } + + @CalledByNative + private static void browserStartupComplete(int result) { + if(sBrowserStartupCompleteCallback != null) { + sBrowserStartupCompleteCallback.run(result); + } + } + + /** + * Set browser to start asynchronously. May only be called before contentMain.start(). If it + * has been called then contentMain.start() will queue up a series of UI tasks to complete + * browser initialization. + * @param browserStartupCompleteCallback If not null called when browser startup is complete. + */ + public static void setAsync(StartupCallback browserStartupCompleteCallback) { + sBrowserMayStartAsynchronously = true; + sBrowserStartupCompleteCallback = browserStartupCompleteCallback; + } +} diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java index 9e37370..e24835b 100644 --- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java +++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java @@ -18,7 +18,7 @@ import org.chromium.base.MemoryPressureListener; import org.chromium.content.app.LibraryLoader; import org.chromium.content.browser.ActivityContentVideoViewClient; import org.chromium.content.browser.AndroidBrowserProcess; -import org.chromium.content.browser.ContentVideoView; +import org.chromium.content.browser.BrowserStartupConfig; import org.chromium.content.browser.ContentVideoViewClient; import org.chromium.content.browser.ContentView; import org.chromium.content.browser.ContentViewClient; @@ -92,26 +92,45 @@ public class ContentShellActivity extends ChromiumActivity { if (!TextUtils.isEmpty(startupUrl)) { mShellManager.setStartupUrl(Shell.sanitizeUrl(startupUrl)); } + + BrowserStartupConfig.setAsync(new BrowserStartupConfig.StartupCallback() { + + @Override + public void run(int startupResult) { + if (startupResult > 0) { + // TODO: Show error message. + Log.e(TAG, "ContentView initialization failed."); + finish(); + } else { + finishInitialization(); + } + } + }); + if (!AndroidBrowserProcess.init(this, AndroidBrowserProcess.MAX_RENDERERS_LIMIT)) { String shellUrl = ShellManager.DEFAULT_SHELL_URL; if (savedInstanceState != null - && savedInstanceState.containsKey(ACTIVE_SHELL_URL_KEY)) { + && savedInstanceState.containsKey(ACTIVE_SHELL_URL_KEY)) { shellUrl = savedInstanceState.getString(ACTIVE_SHELL_URL_KEY); } mShellManager.launchShell(shellUrl); + finishInitialization(); } - getActiveContentView().setContentViewClient(new ContentViewClient() { - @Override - public ContentVideoViewClient getContentVideoViewClient() { - return new ActivityContentVideoViewClient(ContentShellActivity.this); - } - }); } catch (ProcessInitException e) { Log.e(TAG, "ContentView initialization failed.", e); finish(); } } + private void finishInitialization() { + getActiveContentView().setContentViewClient(new ContentViewClient() { + @Override + public ContentVideoViewClient getContentVideoViewClient() { + return new ActivityContentVideoViewClient(ContentShellActivity.this); + } + }); + } + @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); diff --git a/content/shell/shell_browser_main_parts.h b/content/shell/shell_browser_main_parts.h index 8ba4c1f..25a4449 100644 --- a/content/shell/shell_browser_main_parts.h +++ b/content/shell/shell_browser_main_parts.h @@ -8,6 +8,7 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "content/public/browser/browser_main_parts.h" +#include "content/public/common/main_function_params.h" namespace base { class Thread; @@ -22,7 +23,6 @@ namespace content { class ShellBrowserContext; class ShellDevToolsDelegate; class ShellPluginServiceFilter; -struct MainFunctionParams; class ShellBrowserMainParts : public BrowserMainParts { public: @@ -54,7 +54,7 @@ class ShellBrowserMainParts : public BrowserMainParts { scoped_ptr<ShellBrowserContext> off_the_record_browser_context_; // For running content_browsertests. - const MainFunctionParams& parameters_; + const MainFunctionParams parameters_; bool run_message_loop_; scoped_ptr<ShellDevToolsDelegate> devtools_delegate_; |