| // Copyright 2016 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 "extensions/test/extension_test_notification_observer.h" |
| |
| #include "base/bind.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_registrar.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/notification_types.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/common/extension.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // A callback that returns true if the condition has been met and takes no |
| // arguments. |
| using ConditionCallback = base::Callback<bool(void)>; |
| |
| const Extension* GetNonTerminatedExtensions(const std::string& id, |
| content::BrowserContext* context) { |
| return ExtensionRegistry::Get(context)->GetExtensionById( |
| id, ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::TERMINATED); |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ExtensionTestNotificationObserver::NotificationSet |
| |
| ExtensionTestNotificationObserver::NotificationSet::NotificationSet() |
| : process_manager_observer_(this) {} |
| ExtensionTestNotificationObserver::NotificationSet::~NotificationSet() {} |
| |
| void ExtensionTestNotificationObserver::NotificationSet::Add( |
| int type, |
| const content::NotificationSource& source) { |
| notification_registrar_.Add(this, type, source); |
| } |
| |
| void ExtensionTestNotificationObserver::NotificationSet::Add(int type) { |
| Add(type, content::NotificationService::AllSources()); |
| } |
| |
| void ExtensionTestNotificationObserver::NotificationSet:: |
| AddExtensionFrameUnregistration(ProcessManager* manager) { |
| process_manager_observer_.Add(manager); |
| } |
| |
| void ExtensionTestNotificationObserver::NotificationSet::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| callback_list_.Notify(); |
| } |
| |
| void ExtensionTestNotificationObserver::NotificationSet:: |
| OnExtensionFrameUnregistered(const std::string& extension_id, |
| content::RenderFrameHost* render_frame_host) { |
| callback_list_.Notify(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ExtensionTestNotificationObserver |
| |
| ExtensionTestNotificationObserver::ExtensionTestNotificationObserver( |
| content::BrowserContext* context) |
| : context_(context), |
| extension_installs_observed_(0), |
| extension_load_errors_observed_(0), |
| crx_installers_done_observed_(0), |
| registry_observer_(this) { |
| if (context_) |
| registry_observer_.Add(ExtensionRegistry::Get(context_)); |
| } |
| |
| ExtensionTestNotificationObserver::~ExtensionTestNotificationObserver() {} |
| |
| void ExtensionTestNotificationObserver::WaitForNotification( |
| int notification_type) { |
| // TODO(bauerb): Using a WindowedNotificationObserver like this can break |
| // easily, if the notification we're waiting for is sent before this method. |
| // Change it so that the WindowedNotificationObserver is constructed earlier. |
| content::NotificationRegistrar registrar; |
| registrar.Add(this, notification_type, |
| content::NotificationService::AllSources()); |
| content::WindowedNotificationObserver( |
| notification_type, content::NotificationService::AllSources()) |
| .Wait(); |
| } |
| |
| bool ExtensionTestNotificationObserver::WaitForExtensionInstallError() { |
| int before = extension_installs_observed_; |
| content::WindowedNotificationObserver( |
| NOTIFICATION_EXTENSION_INSTALL_ERROR, |
| content::NotificationService::AllSources()) |
| .Wait(); |
| return extension_installs_observed_ == before; |
| } |
| |
| bool ExtensionTestNotificationObserver::WaitForExtensionLoadError() { |
| int before = extension_load_errors_observed_; |
| WaitForNotification(NOTIFICATION_EXTENSION_LOAD_ERROR); |
| return extension_load_errors_observed_ != before; |
| } |
| |
| bool ExtensionTestNotificationObserver::WaitForExtensionCrash( |
| const std::string& extension_id) { |
| if (!GetNonTerminatedExtensions(extension_id, context_)) { |
| // The extension is already unloaded, presumably due to a crash. |
| return true; |
| } |
| |
| content::WindowedNotificationObserver( |
| NOTIFICATION_EXTENSION_PROCESS_TERMINATED, |
| content::NotificationService::AllSources()) |
| .Wait(); |
| // GetNonTerminatedExtensions consults ExtensionRegistry which gets updated |
| // asynchronously in a task posted when |
| // NOTIFICATION_EXTENSION_PROCESS_TERMINATED is handled, so let this task run. |
| base::RunLoop().RunUntilIdle(); |
| return (GetNonTerminatedExtensions(extension_id, context_) == NULL); |
| } |
| |
| bool ExtensionTestNotificationObserver::WaitForCrxInstallerDone() { |
| int before = crx_installers_done_observed_; |
| WaitForNotification(NOTIFICATION_CRX_INSTALLER_DONE); |
| return crx_installers_done_observed_ == before + 1 && |
| !last_loaded_extension_id_.empty(); |
| } |
| |
| void ExtensionTestNotificationObserver::Watch( |
| int type, |
| const content::NotificationSource& source) { |
| CHECK(!observer_); |
| observer_.reset(new content::WindowedNotificationObserver(type, source)); |
| registrar_.Add(this, type, source); |
| } |
| |
| void ExtensionTestNotificationObserver::Wait() { |
| observer_->Wait(); |
| |
| registrar_.RemoveAll(); |
| observer_.reset(); |
| } |
| |
| void ExtensionTestNotificationObserver::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| switch (type) { |
| case NOTIFICATION_CRX_INSTALLER_DONE: |
| VLOG(1) << "Got CRX_INSTALLER_DONE notification."; |
| { |
| const Extension* extension = |
| content::Details<const Extension>(details).ptr(); |
| if (extension) |
| last_loaded_extension_id_ = extension->id(); |
| else |
| last_loaded_extension_id_.clear(); |
| } |
| ++crx_installers_done_observed_; |
| break; |
| |
| case NOTIFICATION_EXTENSION_LOAD_ERROR: |
| VLOG(1) << "Got EXTENSION_LOAD_ERROR notification."; |
| ++extension_load_errors_observed_; |
| break; |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void ExtensionTestNotificationObserver::OnExtensionLoaded( |
| content::BrowserContext* browser_context, |
| const Extension* extension) { |
| last_loaded_extension_id_ = extension->id(); |
| VLOG(1) << "Got EXTENSION_LOADED notification."; |
| } |
| |
| void ExtensionTestNotificationObserver::OnShutdown( |
| ExtensionRegistry* registry) { |
| registry_observer_.RemoveAll(); |
| } |
| |
| void ExtensionTestNotificationObserver::WaitForCondition( |
| const ConditionCallback& condition, |
| NotificationSet* notification_set) { |
| if (condition.Run()) |
| return; |
| condition_ = condition; |
| |
| base::RunLoop run_loop; |
| quit_closure_ = run_loop.QuitClosure(); |
| |
| std::unique_ptr<base::CallbackList<void()>::Subscription> subscription; |
| if (notification_set) { |
| subscription = notification_set->callback_list().Add(base::Bind( |
| &ExtensionTestNotificationObserver::MaybeQuit, base::Unretained(this))); |
| } |
| run_loop.Run(); |
| |
| condition_.Reset(); |
| quit_closure_.Reset(); |
| } |
| |
| void ExtensionTestNotificationObserver::MaybeQuit() { |
| if (condition_.Run()) |
| quit_closure_.Run(); |
| } |
| |
| } // namespace extensions |