| // Copyright 2014 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 "chrome/browser/chromeos/power/renderer_freezer.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/run_loop.h" |
| #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h" |
| #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/test_extension_system.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/fake_power_manager_client.h" |
| #include "chromeos/dbus/power_manager/suspend.pb.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/test/mock_render_process_host.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "extensions/browser/notification_types.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/browser/process_map.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/value_builder.h" |
| #include "testing/gtest/include/gtest/gtest-death-test.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| // Class that delegates used in testing can inherit from to record calls that |
| // are made by the code being tested. |
| class ActionRecorder { |
| public: |
| ActionRecorder() {} |
| virtual ~ActionRecorder() {} |
| |
| // Returns a comma-separated string describing the actions that were |
| // requested since the previous call to GetActions() (i.e. results are |
| // non-repeatable). |
| std::string GetActions() { |
| std::string actions = actions_; |
| actions_.clear(); |
| return actions; |
| } |
| |
| protected: |
| // Appends |new_action| to |actions_|, using a comma as a separator if |
| // other actions are already listed. |
| void AppendAction(const std::string& new_action) { |
| if (!actions_.empty()) |
| actions_ += ","; |
| actions_ += new_action; |
| } |
| |
| private: |
| // Comma-separated list of actions that have been performed. |
| std::string actions_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ActionRecorder); |
| }; |
| |
| // Actions that can be returned by TestDelegate::GetActions(). |
| const char kSetShouldFreezeRenderer[] = "set_should_freeze_renderer"; |
| const char kSetShouldNotFreezeRenderer[] = "set_should_not_freeze_renderer"; |
| const char kFreezeRenderers[] = "freeze_renderers"; |
| const char kThawRenderers[] = "thaw_renderers"; |
| const char kNoActions[] = ""; |
| |
| // Test implementation of RendererFreezer::Delegate that records the actions it |
| // was asked to perform. |
| class TestDelegate : public RendererFreezer::Delegate, public ActionRecorder { |
| public: |
| TestDelegate() |
| : can_freeze_renderers_(true), |
| thaw_renderers_result_(true) {} |
| |
| ~TestDelegate() override {} |
| |
| // RendererFreezer::Delegate overrides. |
| void SetShouldFreezeRenderer(base::ProcessHandle handle, |
| bool frozen) override { |
| AppendAction(frozen ? kSetShouldFreezeRenderer |
| : kSetShouldNotFreezeRenderer); |
| } |
| void FreezeRenderers() override { |
| AppendAction(kFreezeRenderers); |
| } |
| void ThawRenderers(ResultCallback callback) override { |
| AppendAction(kThawRenderers); |
| |
| callback.Run(thaw_renderers_result_); |
| } |
| void CheckCanFreezeRenderers(ResultCallback callback) override { |
| callback.Run(can_freeze_renderers_); |
| } |
| |
| void set_thaw_renderers_result(bool result) { |
| thaw_renderers_result_ = result; |
| } |
| |
| // Sets whether the delegate is capable of freezing renderers. This also |
| // changes |freeze_renderers_result_| and |thaw_renderers_result_|. |
| void set_can_freeze_renderers(bool can_freeze) { |
| can_freeze_renderers_ = can_freeze; |
| |
| thaw_renderers_result_ = can_freeze; |
| } |
| |
| private: |
| bool can_freeze_renderers_; |
| bool thaw_renderers_result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestDelegate); |
| }; |
| |
| } // namespace |
| |
| class RendererFreezerTest : public testing::Test { |
| public: |
| RendererFreezerTest() |
| : power_manager_client_(new FakePowerManagerClient()), |
| test_delegate_(new TestDelegate()) { |
| DBusThreadManager::GetSetterForTesting()->SetPowerManagerClient( |
| std::unique_ptr<PowerManagerClient>(power_manager_client_)); |
| } |
| |
| ~RendererFreezerTest() override { |
| renderer_freezer_.reset(); |
| |
| DBusThreadManager::Shutdown(); |
| } |
| |
| protected: |
| void Init() { |
| renderer_freezer_.reset(new RendererFreezer( |
| std::unique_ptr<RendererFreezer::Delegate>(test_delegate_))); |
| } |
| |
| // Owned by DBusThreadManager. |
| FakePowerManagerClient* power_manager_client_; |
| |
| // Owned by |renderer_freezer_|. |
| TestDelegate* test_delegate_; |
| std::unique_ptr<RendererFreezer> renderer_freezer_; |
| |
| private: |
| content::TestBrowserThreadBundle browser_thread_bundle_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RendererFreezerTest); |
| }; |
| |
| // Tests that the RendererFreezer freezes renderers on suspend and thaws them on |
| // resume. |
| TEST_F(RendererFreezerTest, SuspendResume) { |
| Init(); |
| |
| power_manager_client_->SendSuspendImminent( |
| power_manager::SuspendImminent_Reason_OTHER); |
| EXPECT_EQ(kFreezeRenderers, test_delegate_->GetActions()); |
| |
| // The renderers should be thawed when we resume. |
| power_manager_client_->SendSuspendDone(); |
| EXPECT_EQ(kThawRenderers, test_delegate_->GetActions()); |
| } |
| |
| // Tests that the renderer freezer does nothing if the delegate cannot freeze |
| // renderers. |
| TEST_F(RendererFreezerTest, DelegateCannotFreezeRenderers) { |
| test_delegate_->set_can_freeze_renderers(false); |
| Init(); |
| |
| // Nothing happens on suspend. |
| power_manager_client_->SendSuspendImminent( |
| power_manager::SuspendImminent_Reason_OTHER); |
| EXPECT_EQ(kNoActions, test_delegate_->GetActions()); |
| |
| // Nothing happens on resume. |
| power_manager_client_->SendSuspendDone(); |
| EXPECT_EQ(kNoActions, test_delegate_->GetActions()); |
| } |
| |
| #if defined(GTEST_HAS_DEATH_TEST) |
| // Tests that the RendererFreezer crashes the browser if the freezing operation |
| // was successful but the thawing operation failed. |
| TEST_F(RendererFreezerTest, ErrorThawingRenderers) { |
| // The "threadsafe" style of death test re-executes the unit test binary, |
| // which in turn re-initializes some global state leading to failed CHECKs. |
| // Instead, we use the "fast" style here to prevent re-initialization. |
| ::testing::FLAGS_gtest_death_test_style = "fast"; |
| Init(); |
| test_delegate_->set_thaw_renderers_result(false); |
| |
| power_manager_client_->SendSuspendImminent( |
| power_manager::SuspendImminent_Reason_OTHER); |
| EXPECT_EQ(kFreezeRenderers, test_delegate_->GetActions()); |
| |
| EXPECT_DEATH(power_manager_client_->SendSuspendDone(), "Unable to thaw"); |
| } |
| #endif // GTEST_HAS_DEATH_TEST |
| |
| class RendererFreezerTestWithExtensions : public RendererFreezerTest { |
| public: |
| RendererFreezerTestWithExtensions() {} |
| ~RendererFreezerTestWithExtensions() override {} |
| |
| // testing::Test overrides. |
| void SetUp() override { |
| RendererFreezerTest::SetUp(); |
| |
| profile_manager_.reset( |
| new TestingProfileManager(TestingBrowserProcess::GetGlobal())); |
| |
| // Must be called from testing::Test::SetUp. |
| EXPECT_TRUE(profile_manager_->SetUp()); |
| |
| profile_ = profile_manager_->CreateTestingProfile("RendererFreezerTest"); |
| |
| extensions::TestExtensionSystem* extension_system = |
| static_cast<extensions::TestExtensionSystem*>( |
| extensions::ExtensionSystem::Get(profile_)); |
| extension_system->CreateExtensionService( |
| base::CommandLine::ForCurrentProcess(), |
| base::FilePath() /* install_directory */, |
| false /* autoupdate_enabled*/); |
| } |
| void TearDown() override { |
| extensions::ExtensionSystem::Get(profile_)->Shutdown(); |
| |
| profile_ = NULL; |
| |
| profile_manager_->DeleteAllTestingProfiles(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| profile_manager_.reset(); |
| |
| RendererFreezerTest::TearDown(); |
| } |
| |
| protected: |
| void CreateRenderProcessForExtension(extensions::Extension* extension) { |
| std::unique_ptr<content::MockRenderProcessHostFactory> rph_factory( |
| new content::MockRenderProcessHostFactory()); |
| scoped_refptr<content::SiteInstance> site_instance( |
| extensions::ProcessManager::Get(profile_)->GetSiteInstanceForURL( |
| extensions::BackgroundInfo::GetBackgroundURL(extension))); |
| std::unique_ptr<content::RenderProcessHost> rph( |
| rph_factory->CreateRenderProcessHost(profile_, site_instance.get())); |
| |
| // Fake that the RenderProcessHost is hosting the gcm app. |
| extensions::ProcessMap::Get(profile_) |
| ->Insert(extension->id(), rph->GetID(), site_instance->GetId()); |
| |
| // Send the notification that the RenderProcessHost has been created. |
| content::NotificationService::current()->Notify( |
| content::NOTIFICATION_RENDERER_PROCESS_CREATED, |
| content::Source<content::RenderProcessHost>(rph.get()), |
| content::NotificationService::NoDetails()); |
| } |
| |
| // Owned by |profile_manager_|. |
| TestingProfile* profile_; |
| std::unique_ptr<TestingProfileManager> profile_manager_; |
| |
| private: |
| // Chrome OS needs the CrosSettings test helper. |
| chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RendererFreezerTestWithExtensions); |
| }; |
| |
| // Tests that the RendererFreezer freezes renderers that are not hosting |
| // GCM extensions. |
| TEST_F(RendererFreezerTestWithExtensions, FreezesNonExtensionRenderers) { |
| Init(); |
| |
| // Create the mock RenderProcessHost. |
| std::unique_ptr<content::MockRenderProcessHostFactory> rph_factory( |
| new content::MockRenderProcessHostFactory()); |
| scoped_refptr<content::SiteInstance> site_instance( |
| content::SiteInstance::Create(profile_)); |
| std::unique_ptr<content::RenderProcessHost> rph( |
| rph_factory->CreateRenderProcessHost(profile_, site_instance.get())); |
| |
| // Send the notification that the RenderProcessHost has been created. |
| content::NotificationService::current()->Notify( |
| content::NOTIFICATION_RENDERER_PROCESS_CREATED, |
| content::Source<content::RenderProcessHost>(rph.get()), |
| content::NotificationService::NoDetails()); |
| |
| EXPECT_EQ(kSetShouldFreezeRenderer, test_delegate_->GetActions()); |
| } |
| |
| // Tests that the RendererFreezer does not freeze renderers that are hosting |
| // extensions that use GCM. |
| TEST_F(RendererFreezerTestWithExtensions, DoesNotFreezeGcmExtensionRenderers) { |
| Init(); |
| |
| // First build the GCM extension. |
| scoped_refptr<extensions::Extension> gcm_app = |
| extensions::ExtensionBuilder() |
| .SetManifest( |
| extensions::DictionaryBuilder() |
| .Set("name", "GCM App") |
| .Set("version", "1.0.0") |
| .Set("manifest_version", 2) |
| .Set("app", |
| extensions::DictionaryBuilder() |
| .Set("background", |
| extensions::DictionaryBuilder() |
| .Set("scripts", extensions::ListBuilder() |
| .Append("background.js") |
| .Build()) |
| .Build()) |
| .Build()) |
| .Set("permissions", |
| extensions::ListBuilder().Append("gcm").Build()) |
| .Build()) |
| .Build(); |
| |
| // Now install it and give it a renderer. |
| extensions::ExtensionSystem::Get(profile_) |
| ->extension_service() |
| ->AddExtension(gcm_app.get()); |
| CreateRenderProcessForExtension(gcm_app.get()); |
| |
| EXPECT_EQ(kSetShouldNotFreezeRenderer, test_delegate_->GetActions()); |
| } |
| |
| // Tests that the RendererFreezer freezes renderers that are hosting extensions |
| // that do not use GCM. |
| TEST_F(RendererFreezerTestWithExtensions, FreezesNonGcmExtensionRenderers) { |
| Init(); |
| |
| // First build the extension. |
| scoped_refptr<extensions::Extension> background_app = |
| extensions::ExtensionBuilder() |
| .SetManifest( |
| extensions::DictionaryBuilder() |
| .Set("name", "Background App") |
| .Set("version", "1.0.0") |
| .Set("manifest_version", 2) |
| .Set("app", |
| extensions::DictionaryBuilder() |
| .Set("background", |
| extensions::DictionaryBuilder() |
| .Set("scripts", extensions::ListBuilder() |
| .Append("background.js") |
| .Build()) |
| .Build()) |
| .Build()) |
| .Build()) |
| .Build(); |
| |
| // Now install it and give it a renderer. |
| extensions::ExtensionSystem::Get(profile_) |
| ->extension_service() |
| ->AddExtension(background_app.get()); |
| CreateRenderProcessForExtension(background_app.get()); |
| |
| EXPECT_EQ(kSetShouldFreezeRenderer, test_delegate_->GetActions()); |
| } |
| |
| } // namespace chromeos |