// 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 #include #include "base/command_line.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.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 "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( scoped_ptr(power_manager_client_)); } ~RendererFreezerTest() override { renderer_freezer_.reset(); DBusThreadManager::Shutdown(); } protected: void Init() { renderer_freezer_.reset(new RendererFreezer( scoped_ptr(test_delegate_))); } // Owned by DBusThreadManager. FakePowerManagerClient* power_manager_client_; // Owned by |renderer_freezer_|. TestDelegate* test_delegate_; scoped_ptr 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(); 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(); 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(); 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::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) { scoped_ptr rph_factory( new content::MockRenderProcessHostFactory()); scoped_refptr site_instance( extensions::ProcessManager::Get(profile_)->GetSiteInstanceForURL( extensions::BackgroundInfo::GetBackgroundURL(extension))); scoped_ptr 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(rph.get()), content::NotificationService::NoDetails()); } // Owned by |profile_manager_|. TestingProfile* profile_; scoped_ptr profile_manager_; private: // Chrome OS needs extra services to run in the following order. chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; chromeos::ScopedTestCrosSettings test_cros_settings_; chromeos::ScopedTestUserManager test_user_manager_; 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. scoped_ptr rph_factory( new content::MockRenderProcessHostFactory()); scoped_refptr site_instance( content::SiteInstance::Create(profile_)); scoped_ptr 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(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 gcm_app = extensions::ExtensionBuilder() .SetManifest(std::move( extensions::DictionaryBuilder() .Set("name", "GCM App") .Set("version", "1.0.0") .Set("manifest_version", 2) .Set("app", std::move(extensions::DictionaryBuilder().Set( "background", std::move(extensions::DictionaryBuilder().Set( "scripts", std::move(extensions::ListBuilder().Append( "background.js"))))))) .Set("permissions", std::move(extensions::ListBuilder().Append("gcm"))))) .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 background_app = extensions::ExtensionBuilder() .SetManifest(std::move( extensions::DictionaryBuilder() .Set("name", "Background App") .Set("version", "1.0.0") .Set("manifest_version", 2) .Set("app", std::move(extensions::DictionaryBuilder().Set( "background", std::move(extensions::DictionaryBuilder().Set( "scripts", std::move(extensions::ListBuilder().Append( "background.js"))))))))) .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