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