1// Copyright (c) 2011 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_browser_event_router.h"
6
7#include "base/json/json_writer.h"
8#include "base/values.h"
9#include "chrome/browser/extensions/extension_event_names.h"
10#include "chrome/browser/extensions/extension_event_router.h"
11#include "chrome/browser/extensions/extension_page_actions_module_constants.h"
12#include "chrome/browser/extensions/extension_tabs_module_constants.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/tabs/tab_strip_model.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/common/extensions/extension_constants.h"
19#include "content/browser/tab_contents/navigation_entry.h"
20#include "content/browser/tab_contents/tab_contents.h"
21#include "content/common/notification_service.h"
22
23namespace events = extension_event_names;
24namespace tab_keys = extension_tabs_module_constants;
25namespace page_action_keys = extension_page_actions_module_constants;
26
27ExtensionBrowserEventRouter::TabEntry::TabEntry()
28    : complete_waiting_on_load_(false),
29      url_() {
30}
31
32DictionaryValue* ExtensionBrowserEventRouter::TabEntry::UpdateLoadState(
33    const TabContents* contents) {
34  // The tab may go in & out of loading (for instance if iframes navigate).
35  // We only want to respond to the first change from loading to !loading after
36  // the NAV_ENTRY_COMMITTED was fired.
37  if (!complete_waiting_on_load_ || contents->is_loading())
38    return NULL;
39
40  // Send "complete" state change.
41  complete_waiting_on_load_ = false;
42  DictionaryValue* changed_properties = new DictionaryValue();
43  changed_properties->SetString(tab_keys::kStatusKey,
44      tab_keys::kStatusValueComplete);
45  return changed_properties;
46}
47
48DictionaryValue* ExtensionBrowserEventRouter::TabEntry::DidNavigate(
49    const TabContents* contents) {
50  // Send "loading" state change.
51  complete_waiting_on_load_ = true;
52  DictionaryValue* changed_properties = new DictionaryValue();
53  changed_properties->SetString(tab_keys::kStatusKey,
54      tab_keys::kStatusValueLoading);
55
56  if (contents->GetURL() != url_) {
57    url_ = contents->GetURL();
58    changed_properties->SetString(tab_keys::kUrlKey, url_.spec());
59  }
60
61  return changed_properties;
62}
63
64static void DispatchEvent(Profile* profile,
65                          const char* event_name,
66                          const std::string& json_args) {
67  if (profile->GetExtensionEventRouter()) {
68    profile->GetExtensionEventRouter()->DispatchEventToRenderers(
69        event_name, json_args, profile, GURL());
70  }
71}
72
73static void DispatchEventToExtension(Profile* profile,
74                                     const std::string& extension_id,
75                                     const char* event_name,
76                                     const std::string& json_args) {
77  if (profile->GetExtensionEventRouter()) {
78    profile->GetExtensionEventRouter()->DispatchEventToExtension(
79        extension_id, event_name, json_args, profile, GURL());
80  }
81}
82
83static void DispatchEventWithTab(Profile* profile,
84                                 const std::string& extension_id,
85                                 const char* event_name,
86                                 const TabContents* tab_contents) {
87  ListValue args;
88  args.Append(ExtensionTabUtil::CreateTabValue(tab_contents));
89  std::string json_args;
90  base::JSONWriter::Write(&args, false, &json_args);
91  if (!extension_id.empty()) {
92    DispatchEventToExtension(profile, extension_id, event_name, json_args);
93  } else {
94    DispatchEvent(profile, event_name, json_args);
95  }
96}
97
98static void DispatchSimpleBrowserEvent(Profile* profile,
99                                       const int window_id,
100                                       const char* event_name) {
101  ListValue args;
102  args.Append(Value::CreateIntegerValue(window_id));
103
104  std::string json_args;
105  base::JSONWriter::Write(&args, false, &json_args);
106
107  DispatchEvent(profile, event_name, json_args);
108}
109
110void ExtensionBrowserEventRouter::Init() {
111  if (initialized_)
112    return;
113  BrowserList::AddObserver(this);
114#if defined(TOOLKIT_VIEWS)
115  views::FocusManager::GetWidgetFocusManager()->AddFocusChangeListener(this);
116#elif defined(TOOLKIT_GTK)
117  ui::ActiveWindowWatcherX::AddObserver(this);
118#elif defined(OS_MACOSX)
119  // Needed for when no suitable window can be passed to an extension as the
120  // currently focused window.
121  registrar_.Add(this, NotificationType::NO_KEY_WINDOW,
122                 NotificationService::AllSources());
123#endif
124
125  // Init() can happen after the browser is running, so catch up with any
126  // windows that already exist.
127  for (BrowserList::const_iterator iter = BrowserList::begin();
128       iter != BrowserList::end(); ++iter) {
129    RegisterForBrowserNotifications(*iter);
130
131    // Also catch up our internal bookkeeping of tab entries.
132    Browser* browser = *iter;
133    if (browser->tabstrip_model()) {
134      for (int i = 0; i < browser->tabstrip_model()->count(); ++i) {
135        TabContents* contents = browser->GetTabContentsAt(i);
136        int tab_id = ExtensionTabUtil::GetTabId(contents);
137        tab_entries_[tab_id] = TabEntry();
138      }
139    }
140  }
141
142  initialized_ = true;
143}
144
145ExtensionBrowserEventRouter::ExtensionBrowserEventRouter(Profile* profile)
146    : initialized_(false),
147      focused_window_id_(extension_misc::kUnknownWindowId),
148      profile_(profile) {
149  DCHECK(!profile->IsOffTheRecord());
150}
151
152ExtensionBrowserEventRouter::~ExtensionBrowserEventRouter() {
153  BrowserList::RemoveObserver(this);
154#if defined(TOOLKIT_VIEWS)
155  views::FocusManager::GetWidgetFocusManager()->RemoveFocusChangeListener(this);
156#elif defined(TOOLKIT_GTK)
157  ui::ActiveWindowWatcherX::RemoveObserver(this);
158#endif
159}
160
161void ExtensionBrowserEventRouter::OnBrowserAdded(const Browser* browser) {
162  RegisterForBrowserNotifications(browser);
163}
164
165void ExtensionBrowserEventRouter::RegisterForBrowserNotifications(
166    const Browser* browser) {
167  // Start listening to TabStripModel events for this browser.
168  browser->tabstrip_model()->AddObserver(this);
169
170  // If this is a new window, it isn't ready at this point, so we register to be
171  // notified when it is. If this is an existing window, this is a no-op that we
172  // just do to reduce code complexity.
173  registrar_.Add(this, NotificationType::BROWSER_WINDOW_READY,
174      Source<const Browser>(browser));
175
176  if (browser->tabstrip_model()) {
177    for (int i = 0; i < browser->tabstrip_model()->count(); ++i)
178      RegisterForTabNotifications(browser->GetTabContentsAt(i));
179  }
180}
181
182void ExtensionBrowserEventRouter::RegisterForTabNotifications(
183    TabContents* contents) {
184  registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
185                 Source<NavigationController>(&contents->controller()));
186
187  // Observing TAB_CONTENTS_DESTROYED is necessary because it's
188  // possible for tabs to be created, detached and then destroyed without
189  // ever having been re-attached and closed. This happens in the case of
190  // a devtools TabContents that is opened in window, docked, then closed.
191  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
192                 Source<TabContents>(contents));
193}
194
195void ExtensionBrowserEventRouter::UnregisterForTabNotifications(
196    TabContents* contents) {
197  registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
198      Source<NavigationController>(&contents->controller()));
199  registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
200      Source<TabContents>(contents));
201}
202
203void ExtensionBrowserEventRouter::OnBrowserWindowReady(const Browser* browser) {
204  ListValue args;
205
206  DictionaryValue* window_dictionary = ExtensionTabUtil::CreateWindowValue(
207      browser, false);
208  args.Append(window_dictionary);
209
210  std::string json_args;
211  base::JSONWriter::Write(&args, false, &json_args);
212
213  DispatchEvent(browser->profile(), events::kOnWindowCreated, json_args);
214}
215
216void ExtensionBrowserEventRouter::OnBrowserRemoved(const Browser* browser) {
217  // Stop listening to TabStripModel events for this browser.
218  browser->tabstrip_model()->RemoveObserver(this);
219
220  registrar_.Remove(this, NotificationType::BROWSER_WINDOW_READY,
221      Source<const Browser>(browser));
222
223  DispatchSimpleBrowserEvent(browser->profile(),
224                             ExtensionTabUtil::GetWindowId(browser),
225                             events::kOnWindowRemoved);
226}
227
228#if defined(TOOLKIT_VIEWS)
229void ExtensionBrowserEventRouter::NativeFocusWillChange(
230    gfx::NativeView focused_before,
231    gfx::NativeView focused_now) {
232  if (!focused_now)
233    OnBrowserSetLastActive(NULL);
234}
235#elif defined(TOOLKIT_GTK)
236void ExtensionBrowserEventRouter::ActiveWindowChanged(
237    GdkWindow* active_window) {
238  if (!active_window)
239    OnBrowserSetLastActive(NULL);
240}
241#endif
242
243void ExtensionBrowserEventRouter::OnBrowserSetLastActive(
244    const Browser* browser) {
245  int window_id = extension_misc::kUnknownWindowId;
246  if (browser)
247    window_id = ExtensionTabUtil::GetWindowId(browser);
248
249  if (focused_window_id_ == window_id)
250    return;
251
252  focused_window_id_ = window_id;
253  // Note: because we use the default profile when |browser| is NULL, it means
254  // that all extensions hear about the event regardless of whether the browser
255  // that lost focus was OTR or if the extension is OTR-enabled.
256  // See crbug.com/46610.
257  DispatchSimpleBrowserEvent(browser ? browser->profile() : profile_,
258                             focused_window_id_,
259                             events::kOnWindowFocusedChanged);
260}
261
262void ExtensionBrowserEventRouter::TabCreatedAt(TabContents* contents,
263                                               int index,
264                                               bool foreground) {
265  DispatchEventWithTab(contents->profile(), "", events::kOnTabCreated,
266                       contents);
267
268  RegisterForTabNotifications(contents);
269}
270
271void ExtensionBrowserEventRouter::TabInsertedAt(TabContentsWrapper* contents,
272                                                int index,
273                                                bool foreground) {
274  // If tab is new, send created event.
275  int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents());
276  if (!GetTabEntry(contents->tab_contents())) {
277    tab_entries_[tab_id] = TabEntry();
278
279    TabCreatedAt(contents->tab_contents(), index, foreground);
280    return;
281  }
282
283  ListValue args;
284  args.Append(Value::CreateIntegerValue(tab_id));
285
286  DictionaryValue* object_args = new DictionaryValue();
287  object_args->Set(tab_keys::kNewWindowIdKey, Value::CreateIntegerValue(
288      ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
289  object_args->Set(tab_keys::kNewPositionKey, Value::CreateIntegerValue(
290      index));
291  args.Append(object_args);
292
293  std::string json_args;
294  base::JSONWriter::Write(&args, false, &json_args);
295
296  DispatchEvent(contents->profile(), events::kOnTabAttached, json_args);
297}
298
299void ExtensionBrowserEventRouter::TabDetachedAt(TabContentsWrapper* contents,
300                                                int index) {
301  if (!GetTabEntry(contents->tab_contents())) {
302    // The tab was removed. Don't send detach event.
303    return;
304  }
305
306  ListValue args;
307  args.Append(Value::CreateIntegerValue(
308      ExtensionTabUtil::GetTabId(contents->tab_contents())));
309
310  DictionaryValue* object_args = new DictionaryValue();
311  object_args->Set(tab_keys::kOldWindowIdKey, Value::CreateIntegerValue(
312      ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
313  object_args->Set(tab_keys::kOldPositionKey, Value::CreateIntegerValue(
314      index));
315  args.Append(object_args);
316
317  std::string json_args;
318  base::JSONWriter::Write(&args, false, &json_args);
319
320  DispatchEvent(contents->profile(), events::kOnTabDetached, json_args);
321}
322
323void ExtensionBrowserEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
324                                               TabContentsWrapper* contents,
325                                               int index) {
326  int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents());
327
328  ListValue args;
329  args.Append(Value::CreateIntegerValue(tab_id));
330
331  DictionaryValue* object_args = new DictionaryValue();
332  object_args->SetBoolean(tab_keys::kWindowClosing,
333                          tab_strip_model->closing_all());
334  args.Append(object_args);
335
336  std::string json_args;
337  base::JSONWriter::Write(&args, false, &json_args);
338
339  DispatchEvent(contents->profile(), events::kOnTabRemoved, json_args);
340
341  int removed_count = tab_entries_.erase(tab_id);
342  DCHECK_GT(removed_count, 0);
343
344  UnregisterForTabNotifications(contents->tab_contents());
345}
346
347void ExtensionBrowserEventRouter::TabSelectedAt(
348    TabContentsWrapper* old_contents,
349    TabContentsWrapper* new_contents,
350    int index,
351    bool user_gesture) {
352  if (old_contents == new_contents)
353    return;
354
355  ListValue args;
356  args.Append(Value::CreateIntegerValue(
357      ExtensionTabUtil::GetTabId(new_contents->tab_contents())));
358
359  DictionaryValue* object_args = new DictionaryValue();
360  object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue(
361      ExtensionTabUtil::GetWindowIdOfTab(new_contents->tab_contents())));
362  args.Append(object_args);
363
364  std::string json_args;
365  base::JSONWriter::Write(&args, false, &json_args);
366
367  DispatchEvent(new_contents->profile(), events::kOnTabSelectionChanged,
368                json_args);
369}
370
371void ExtensionBrowserEventRouter::TabMoved(TabContentsWrapper* contents,
372                                           int from_index,
373                                           int to_index) {
374  ListValue args;
375  args.Append(Value::CreateIntegerValue(
376      ExtensionTabUtil::GetTabId(contents->tab_contents())));
377
378  DictionaryValue* object_args = new DictionaryValue();
379  object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue(
380      ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
381  object_args->Set(tab_keys::kFromIndexKey, Value::CreateIntegerValue(
382      from_index));
383  object_args->Set(tab_keys::kToIndexKey, Value::CreateIntegerValue(
384      to_index));
385  args.Append(object_args);
386
387  std::string json_args;
388  base::JSONWriter::Write(&args, false, &json_args);
389
390  DispatchEvent(contents->profile(), events::kOnTabMoved, json_args);
391}
392
393void ExtensionBrowserEventRouter::TabUpdated(TabContents* contents,
394                                             bool did_navigate) {
395  TabEntry* entry = GetTabEntry(contents);
396  DictionaryValue* changed_properties = NULL;
397
398  DCHECK(entry);
399
400  if (did_navigate)
401    changed_properties = entry->DidNavigate(contents);
402  else
403    changed_properties = entry->UpdateLoadState(contents);
404
405  if (changed_properties)
406    DispatchTabUpdatedEvent(contents, changed_properties);
407}
408
409void ExtensionBrowserEventRouter::DispatchTabUpdatedEvent(
410    TabContents* contents, DictionaryValue* changed_properties) {
411  DCHECK(changed_properties);
412  DCHECK(contents);
413
414  // The state of the tab (as seen from the extension point of view) has
415  // changed.  Send a notification to the extension.
416  ListValue args;
417
418  // First arg: The id of the tab that changed.
419  args.Append(Value::CreateIntegerValue(ExtensionTabUtil::GetTabId(contents)));
420
421  // Second arg: An object containing the changes to the tab state.
422  args.Append(changed_properties);
423
424  // Third arg: An object containing the state of the tab.
425  args.Append(ExtensionTabUtil::CreateTabValue(contents));
426
427  std::string json_args;
428  base::JSONWriter::Write(&args, false, &json_args);
429
430  DispatchEvent(contents->profile(), events::kOnTabUpdated, json_args);
431}
432
433ExtensionBrowserEventRouter::TabEntry* ExtensionBrowserEventRouter::GetTabEntry(
434    const TabContents* contents) {
435  int tab_id = ExtensionTabUtil::GetTabId(contents);
436  std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id);
437  if (tab_entries_.end() == i)
438    return NULL;
439  return &i->second;
440}
441
442void ExtensionBrowserEventRouter::Observe(NotificationType type,
443                                          const NotificationSource& source,
444                                          const NotificationDetails& details) {
445  if (type == NotificationType::NAV_ENTRY_COMMITTED) {
446    NavigationController* source_controller =
447        Source<NavigationController>(source).ptr();
448    TabUpdated(source_controller->tab_contents(), true);
449  } else if (type == NotificationType::TAB_CONTENTS_DESTROYED) {
450    // Tab was destroyed after being detached (without being re-attached).
451    TabContents* contents = Source<TabContents>(source).ptr();
452    registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
453        Source<NavigationController>(&contents->controller()));
454    registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
455        Source<TabContents>(contents));
456  } else if (type == NotificationType::BROWSER_WINDOW_READY) {
457    const Browser* browser = Source<const Browser>(source).ptr();
458    OnBrowserWindowReady(browser);
459#if defined(OS_MACOSX)
460  } else if (type == NotificationType::NO_KEY_WINDOW) {
461    OnBrowserSetLastActive(NULL);
462#endif
463  } else {
464    NOTREACHED();
465  }
466}
467
468void ExtensionBrowserEventRouter::TabChangedAt(TabContentsWrapper* contents,
469                                               int index,
470                                               TabChangeType change_type) {
471  TabUpdated(contents->tab_contents(), false);
472}
473
474void ExtensionBrowserEventRouter::TabReplacedAt(
475    TabStripModel* tab_strip_model,
476    TabContentsWrapper* old_contents,
477    TabContentsWrapper* new_contents,
478    int index) {
479  TabClosingAt(tab_strip_model, old_contents, index);
480  TabInsertedAt(new_contents, index, tab_strip_model->active_index() == index);
481}
482
483void ExtensionBrowserEventRouter::TabPinnedStateChanged(
484    TabContentsWrapper* contents,
485    int index) {
486  TabStripModel* tab_strip = NULL;
487  int tab_index;
488
489  if (ExtensionTabUtil::GetTabStripModel(
490        contents->tab_contents(), &tab_strip, &tab_index)) {
491    DictionaryValue* changed_properties = new DictionaryValue();
492    changed_properties->SetBoolean(tab_keys::kPinnedKey,
493                                   tab_strip->IsTabPinned(tab_index));
494    DispatchTabUpdatedEvent(contents->tab_contents(), changed_properties);
495  }
496}
497
498void ExtensionBrowserEventRouter::TabStripEmpty() {}
499
500void ExtensionBrowserEventRouter::DispatchOldPageActionEvent(
501    Profile* profile,
502    const std::string& extension_id,
503    const std::string& page_action_id,
504    int tab_id,
505    const std::string& url,
506    int button) {
507  ListValue args;
508  args.Append(Value::CreateStringValue(page_action_id));
509
510  DictionaryValue* data = new DictionaryValue();
511  data->Set(tab_keys::kTabIdKey, Value::CreateIntegerValue(tab_id));
512  data->Set(tab_keys::kTabUrlKey, Value::CreateStringValue(url));
513  data->Set(page_action_keys::kButtonKey, Value::CreateIntegerValue(button));
514  args.Append(data);
515
516  std::string json_args;
517  base::JSONWriter::Write(&args, false, &json_args);
518
519  DispatchEventToExtension(profile, extension_id, "pageActions", json_args);
520}
521
522void ExtensionBrowserEventRouter::PageActionExecuted(
523    Profile* profile,
524    const std::string& extension_id,
525    const std::string& page_action_id,
526    int tab_id,
527    const std::string& url,
528    int button) {
529  DispatchOldPageActionEvent(profile, extension_id, page_action_id, tab_id, url,
530                             button);
531  TabContentsWrapper* tab_contents = NULL;
532  if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(),
533                                    NULL, NULL, &tab_contents, NULL)) {
534    return;
535  }
536  DispatchEventWithTab(profile, extension_id, "pageAction.onClicked",
537                       tab_contents->tab_contents());
538}
539
540void ExtensionBrowserEventRouter::BrowserActionExecuted(
541    Profile* profile, const std::string& extension_id, Browser* browser) {
542  TabContentsWrapper* tab_contents = NULL;
543  int tab_id = 0;
544  if (!ExtensionTabUtil::GetDefaultTab(browser, &tab_contents, &tab_id))
545    return;
546  DispatchEventWithTab(profile, extension_id, "browserAction.onClicked",
547                       tab_contents->tab_contents());
548}
549