1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/extensions/extension_test_notification_observer.h" 6 7 #include "base/callback_list.h" 8 #include "chrome/browser/extensions/extension_service.h" 9 #include "chrome/browser/profiles/profile_manager.h" 10 #include "chrome/browser/ui/browser.h" 11 #include "chrome/browser/ui/browser_window.h" 12 #include "content/public/browser/notification_registrar.h" 13 #include "content/public/browser/notification_service.h" 14 #include "content/public/browser/render_view_host.h" 15 #include "content/public/test/test_utils.h" 16 #include "extensions/browser/extension_system.h" 17 #include "extensions/browser/process_manager.h" 18 #include "extensions/common/extension.h" 19 20 using extensions::Extension; 21 22 namespace { 23 24 bool HasExtensionPageActionCountReachedTarget(LocationBarTesting* location_bar, 25 int target_page_action_count) { 26 VLOG(1) << "Number of page actions: " << location_bar->PageActionCount(); 27 return location_bar->PageActionCount() == target_page_action_count; 28 } 29 30 bool HasExtensionPageActionVisibilityReachedTarget( 31 LocationBarTesting* location_bar, 32 int target_visible_page_action_count) { 33 VLOG(1) << "Number of visible page actions: " 34 << location_bar->PageActionVisibleCount(); 35 return location_bar->PageActionVisibleCount() == 36 target_visible_page_action_count; 37 } 38 39 bool HaveAllExtensionRenderViewHostsFinishedLoading( 40 extensions::ProcessManager* manager) { 41 extensions::ProcessManager::ViewSet all_views = manager->GetAllViews(); 42 for (extensions::ProcessManager::ViewSet::const_iterator iter = 43 all_views.begin(); 44 iter != all_views.end(); ++iter) { 45 if ((*iter)->IsLoading()) 46 return false; 47 } 48 return true; 49 } 50 51 class NotificationSet : public content::NotificationObserver { 52 public: 53 void Add(int type, const content::NotificationSource& source); 54 void Add(int type); 55 56 // Notified any time an Add()ed notification is received. 57 // The details of the notification are dropped. 58 base::CallbackList<void()>& callback_list() { 59 return callback_list_; 60 } 61 62 private: 63 // content::NotificationObserver: 64 virtual void Observe(int type, 65 const content::NotificationSource& source, 66 const content::NotificationDetails& details) OVERRIDE; 67 68 content::NotificationRegistrar notification_registrar_; 69 base::CallbackList<void()> callback_list_; 70 }; 71 72 void NotificationSet::Add( 73 int type, 74 const content::NotificationSource& source) { 75 notification_registrar_.Add(this, type, source); 76 } 77 78 void NotificationSet::Add(int type) { 79 Add(type, content::NotificationService::AllSources()); 80 } 81 82 void NotificationSet::Observe( 83 int type, 84 const content::NotificationSource& source, 85 const content::NotificationDetails& details) { 86 callback_list_.Notify(); 87 } 88 89 void MaybeQuit(content::MessageLoopRunner* runner, 90 const base::Callback<bool(void)>& condition) { 91 if (condition.Run()) 92 runner->Quit(); 93 } 94 95 void WaitForCondition( 96 const base::Callback<bool(void)>& condition, 97 NotificationSet* notification_set) { 98 if (condition.Run()) 99 return; 100 101 scoped_refptr<content::MessageLoopRunner> runner( 102 new content::MessageLoopRunner); 103 scoped_ptr<base::CallbackList<void()>::Subscription> subscription = 104 notification_set->callback_list().Add( 105 base::Bind(&MaybeQuit, base::Unretained(runner.get()), condition)); 106 runner->Run(); 107 } 108 109 void WaitForCondition( 110 const base::Callback<bool(void)>& condition, 111 int type) { 112 NotificationSet notification_set; 113 notification_set.Add(type); 114 WaitForCondition(condition, ¬ification_set); 115 } 116 117 } // namespace 118 119 ExtensionTestNotificationObserver::ExtensionTestNotificationObserver( 120 Browser* browser) 121 : browser_(browser), 122 profile_(NULL), 123 extension_installs_observed_(0), 124 extension_load_errors_observed_(0), 125 crx_installers_done_observed_(0) { 126 } 127 128 ExtensionTestNotificationObserver::~ExtensionTestNotificationObserver() {} 129 130 Profile* ExtensionTestNotificationObserver::GetProfile() { 131 if (!profile_) { 132 if (browser_) 133 profile_ = browser_->profile(); 134 else 135 profile_ = ProfileManager::GetActiveUserProfile(); 136 } 137 return profile_; 138 } 139 140 void ExtensionTestNotificationObserver::WaitForNotification( 141 int notification_type) { 142 // TODO(bauerb): Using a WindowedNotificationObserver like this can break 143 // easily, if the notification we're waiting for is sent before this method. 144 // Change it so that the WindowedNotificationObserver is constructed earlier. 145 content::NotificationRegistrar registrar; 146 registrar.Add( 147 this, notification_type, content::NotificationService::AllSources()); 148 content::WindowedNotificationObserver( 149 notification_type, content::NotificationService::AllSources()).Wait(); 150 } 151 152 bool ExtensionTestNotificationObserver::WaitForPageActionCountChangeTo( 153 int count) { 154 LocationBarTesting* location_bar = 155 browser_->window()->GetLocationBar()->GetLocationBarForTesting(); 156 WaitForCondition( 157 base::Bind( 158 &HasExtensionPageActionCountReachedTarget, location_bar, count), 159 chrome::NOTIFICATION_EXTENSION_PAGE_ACTION_COUNT_CHANGED); 160 return true; 161 } 162 163 bool ExtensionTestNotificationObserver::WaitForPageActionVisibilityChangeTo( 164 int count) { 165 LocationBarTesting* location_bar = 166 browser_->window()->GetLocationBar()->GetLocationBarForTesting(); 167 WaitForCondition( 168 base::Bind( 169 &HasExtensionPageActionVisibilityReachedTarget, location_bar, count), 170 chrome::NOTIFICATION_EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED); 171 return true; 172 } 173 174 bool ExtensionTestNotificationObserver::WaitForExtensionViewsToLoad() { 175 extensions::ProcessManager* manager = 176 extensions::ExtensionSystem::Get(GetProfile())->process_manager(); 177 NotificationSet notification_set; 178 notification_set.Add(content::NOTIFICATION_WEB_CONTENTS_DESTROYED); 179 notification_set.Add(content::NOTIFICATION_LOAD_STOP); 180 WaitForCondition( 181 base::Bind(&HaveAllExtensionRenderViewHostsFinishedLoading, manager), 182 ¬ification_set); 183 return true; 184 } 185 186 bool ExtensionTestNotificationObserver::WaitForExtensionInstall() { 187 int before = extension_installs_observed_; 188 WaitForNotification(chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED); 189 return extension_installs_observed_ == (before + 1); 190 } 191 192 bool ExtensionTestNotificationObserver::WaitForExtensionInstallError() { 193 int before = extension_installs_observed_; 194 content::WindowedNotificationObserver( 195 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 196 content::NotificationService::AllSources()).Wait(); 197 return extension_installs_observed_ == before; 198 } 199 200 void ExtensionTestNotificationObserver::WaitForExtensionLoad() { 201 WaitForNotification(chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED); 202 } 203 204 void ExtensionTestNotificationObserver::WaitForExtensionAndViewLoad() { 205 this->WaitForExtensionLoad(); 206 WaitForExtensionViewsToLoad(); 207 } 208 209 bool ExtensionTestNotificationObserver::WaitForExtensionLoadError() { 210 int before = extension_load_errors_observed_; 211 WaitForNotification(chrome::NOTIFICATION_EXTENSION_LOAD_ERROR); 212 return extension_load_errors_observed_ != before; 213 } 214 215 bool ExtensionTestNotificationObserver::WaitForExtensionCrash( 216 const std::string& extension_id) { 217 ExtensionService* service = extensions::ExtensionSystem::Get( 218 GetProfile())->extension_service(); 219 220 if (!service->GetExtensionById(extension_id, true)) { 221 // The extension is already unloaded, presumably due to a crash. 222 return true; 223 } 224 content::WindowedNotificationObserver( 225 chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED, 226 content::NotificationService::AllSources()).Wait(); 227 return (service->GetExtensionById(extension_id, true) == NULL); 228 } 229 230 bool ExtensionTestNotificationObserver::WaitForCrxInstallerDone() { 231 int before = crx_installers_done_observed_; 232 WaitForNotification(chrome::NOTIFICATION_CRX_INSTALLER_DONE); 233 return crx_installers_done_observed_ == (before + 1); 234 } 235 236 void ExtensionTestNotificationObserver::Watch( 237 int type, 238 const content::NotificationSource& source) { 239 CHECK(!observer_); 240 observer_.reset(new content::WindowedNotificationObserver(type, source)); 241 registrar_.Add(this, type, source); 242 } 243 244 void ExtensionTestNotificationObserver::Wait() { 245 observer_->Wait(); 246 247 registrar_.RemoveAll(); 248 observer_.reset(); 249 } 250 251 void ExtensionTestNotificationObserver::Observe( 252 int type, 253 const content::NotificationSource& source, 254 const content::NotificationDetails& details) { 255 switch (type) { 256 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: 257 last_loaded_extension_id_ = 258 content::Details<const Extension>(details).ptr()->id(); 259 VLOG(1) << "Got EXTENSION_LOADED notification."; 260 break; 261 262 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: 263 VLOG(1) << "Got CRX_INSTALLER_DONE notification."; 264 { 265 const Extension* extension = 266 content::Details<const Extension>(details).ptr(); 267 if (extension) 268 last_loaded_extension_id_ = extension->id(); 269 else 270 last_loaded_extension_id_.clear(); 271 } 272 ++crx_installers_done_observed_; 273 break; 274 275 case chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED: 276 VLOG(1) << "Got EXTENSION_INSTALLED notification."; 277 ++extension_installs_observed_; 278 break; 279 280 case chrome::NOTIFICATION_EXTENSION_LOAD_ERROR: 281 VLOG(1) << "Got EXTENSION_LOAD_ERROR notification."; 282 ++extension_load_errors_observed_; 283 break; 284 285 default: 286 NOTREACHED(); 287 break; 288 } 289 } 290