tabs_event_router.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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/api/tabs/tabs_event_router.h"
6
7#include "base/json/json_writer.h"
8#include "base/values.h"
9#include "chrome/browser/chrome_notification_types.h"
10#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
11#include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
12#include "chrome/browser/extensions/api/tabs/windows_event_router.h"
13#include "chrome/browser/extensions/extension_tab_util.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/browser_iterator.h"
17#include "chrome/browser/ui/browser_list.h"
18#include "chrome/browser/ui/tabs/tab_strip_model.h"
19#include "chrome/common/extensions/extension_constants.h"
20#include "content/public/browser/favicon_status.h"
21#include "content/public/browser/navigation_controller.h"
22#include "content/public/browser/navigation_entry.h"
23#include "content/public/browser/notification_service.h"
24#include "content/public/browser/notification_types.h"
25#include "content/public/browser/web_contents.h"
26
27using base::DictionaryValue;
28using base::ListValue;
29using base::FundamentalValue;
30using content::NavigationController;
31using content::WebContents;
32
33namespace extensions {
34
35namespace {
36
37namespace tabs = api::tabs;
38
39void WillDispatchTabUpdatedEvent(
40    WebContents* contents,
41    const base::DictionaryValue* changed_properties,
42    content::BrowserContext* context,
43    const Extension* extension,
44    base::ListValue* event_args) {
45  // Overwrite the second argument with the appropriate properties dictionary,
46  // depending on extension permissions.
47  base::DictionaryValue* properties_value = changed_properties->DeepCopy();
48  ExtensionTabUtil::ScrubTabValueForExtension(contents,
49                                              extension,
50                                              properties_value);
51  event_args->Set(1, properties_value);
52
53  // Overwrite the third arg with our tab value as seen by this extension.
54  event_args->Set(2, ExtensionTabUtil::CreateTabValue(contents, extension));
55}
56
57}  // namespace
58
59TabsEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false),
60                                        url_() {
61}
62
63base::DictionaryValue* TabsEventRouter::TabEntry::UpdateLoadState(
64    const WebContents* contents) {
65  // The tab may go in & out of loading (for instance if iframes navigate).
66  // We only want to respond to the first change from loading to !loading after
67  // the NAV_ENTRY_COMMITTED was fired.
68  if (!complete_waiting_on_load_ || contents->IsLoading())
69    return NULL;
70
71  // Send "complete" state change.
72  complete_waiting_on_load_ = false;
73  base::DictionaryValue* changed_properties = new base::DictionaryValue();
74  changed_properties->SetString(tabs_constants::kStatusKey,
75                                tabs_constants::kStatusValueComplete);
76  return changed_properties;
77}
78
79base::DictionaryValue* TabsEventRouter::TabEntry::DidNavigate(
80    const WebContents* contents) {
81  // Send "loading" state change.
82  complete_waiting_on_load_ = true;
83  base::DictionaryValue* changed_properties = new base::DictionaryValue();
84  changed_properties->SetString(tabs_constants::kStatusKey,
85                                tabs_constants::kStatusValueLoading);
86
87  if (contents->GetURL() != url_) {
88    url_ = contents->GetURL();
89    changed_properties->SetString(tabs_constants::kUrlKey, url_.spec());
90  }
91
92  return changed_properties;
93}
94
95TabsEventRouter::TabsEventRouter(Profile* profile) : profile_(profile) {
96  DCHECK(!profile->IsOffTheRecord());
97
98  BrowserList::AddObserver(this);
99
100  // Init() can happen after the browser is running, so catch up with any
101  // windows that already exist.
102  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
103    RegisterForBrowserNotifications(*it);
104
105    // Also catch up our internal bookkeeping of tab entries.
106    Browser* browser = *it;
107    if (browser->tab_strip_model()) {
108      for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
109        WebContents* contents = browser->tab_strip_model()->GetWebContentsAt(i);
110        int tab_id = ExtensionTabUtil::GetTabId(contents);
111        tab_entries_[tab_id] = TabEntry();
112      }
113    }
114  }
115}
116
117TabsEventRouter::~TabsEventRouter() {
118  BrowserList::RemoveObserver(this);
119}
120
121void TabsEventRouter::OnBrowserAdded(Browser* browser) {
122  RegisterForBrowserNotifications(browser);
123}
124
125void TabsEventRouter::RegisterForBrowserNotifications(Browser* browser) {
126  if (!profile_->IsSameProfile(browser->profile()))
127    return;
128  // Start listening to TabStripModel events for this browser.
129  TabStripModel* tab_strip = browser->tab_strip_model();
130  tab_strip->AddObserver(this);
131
132  for (int i = 0; i < tab_strip->count(); ++i) {
133    RegisterForTabNotifications(tab_strip->GetWebContentsAt(i));
134  }
135}
136
137void TabsEventRouter::RegisterForTabNotifications(WebContents* contents) {
138  registrar_.Add(
139      this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
140      content::Source<NavigationController>(&contents->GetController()));
141
142  // Observing NOTIFICATION_WEB_CONTENTS_DESTROYED is necessary because it's
143  // possible for tabs to be created, detached and then destroyed without
144  // ever having been re-attached and closed. This happens in the case of
145  // a devtools WebContents that is opened in window, docked, then closed.
146  registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
147                 content::Source<WebContents>(contents));
148
149  registrar_.Add(this, chrome::NOTIFICATION_FAVICON_UPDATED,
150                 content::Source<WebContents>(contents));
151}
152
153void TabsEventRouter::UnregisterForTabNotifications(WebContents* contents) {
154  registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
155      content::Source<NavigationController>(&contents->GetController()));
156  registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
157      content::Source<WebContents>(contents));
158  registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED,
159      content::Source<WebContents>(contents));
160}
161
162void TabsEventRouter::OnBrowserRemoved(Browser* browser) {
163  if (!profile_->IsSameProfile(browser->profile()))
164    return;
165
166  // Stop listening to TabStripModel events for this browser.
167  browser->tab_strip_model()->RemoveObserver(this);
168}
169
170void TabsEventRouter::OnBrowserSetLastActive(Browser* browser) {
171  TabsWindowsAPI* tabs_window_api = TabsWindowsAPI::Get(profile_);
172  if (tabs_window_api) {
173    tabs_window_api->windows_event_router()->OnActiveWindowChanged(
174        browser ? browser->extension_window_controller() : NULL);
175  }
176}
177
178static void WillDispatchTabCreatedEvent(WebContents* contents,
179                                        bool active,
180                                        content::BrowserContext* context,
181                                        const Extension* extension,
182                                        base::ListValue* event_args) {
183  base::DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue(
184      contents, extension);
185  event_args->Clear();
186  event_args->Append(tab_value);
187  tab_value->SetBoolean(tabs_constants::kSelectedKey, active);
188}
189
190void TabsEventRouter::TabCreatedAt(WebContents* contents,
191                                   int index,
192                                   bool active) {
193  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
194  scoped_ptr<base::ListValue> args(new base::ListValue);
195  scoped_ptr<Event> event(new Event(tabs::OnCreated::kEventName, args.Pass()));
196  event->restrict_to_browser_context = profile;
197  event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
198  event->will_dispatch_callback =
199      base::Bind(&WillDispatchTabCreatedEvent, contents, active);
200  EventRouter::Get(profile)->BroadcastEvent(event.Pass());
201
202  RegisterForTabNotifications(contents);
203}
204
205void TabsEventRouter::TabInsertedAt(WebContents* contents,
206                                       int index,
207                                       bool active) {
208  // If tab is new, send created event.
209  int tab_id = ExtensionTabUtil::GetTabId(contents);
210  if (!GetTabEntry(contents)) {
211    tab_entries_[tab_id] = TabEntry();
212
213    TabCreatedAt(contents, index, active);
214    return;
215  }
216
217  scoped_ptr<base::ListValue> args(new base::ListValue);
218  args->Append(new FundamentalValue(tab_id));
219
220  base::DictionaryValue* object_args = new base::DictionaryValue();
221  object_args->Set(tabs_constants::kNewWindowIdKey,
222                   new FundamentalValue(
223                       ExtensionTabUtil::GetWindowIdOfTab(contents)));
224  object_args->Set(tabs_constants::kNewPositionKey,
225                   new FundamentalValue(index));
226  args->Append(object_args);
227
228  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
229  DispatchEvent(profile, tabs::OnAttached::kEventName, args.Pass(),
230                EventRouter::USER_GESTURE_UNKNOWN);
231}
232
233void TabsEventRouter::TabDetachedAt(WebContents* contents, int index) {
234  if (!GetTabEntry(contents)) {
235    // The tab was removed. Don't send detach event.
236    return;
237  }
238
239  scoped_ptr<base::ListValue> args(new base::ListValue);
240  args->Append(
241      new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
242
243  base::DictionaryValue* object_args = new base::DictionaryValue();
244  object_args->Set(tabs_constants::kOldWindowIdKey,
245                   new FundamentalValue(
246                       ExtensionTabUtil::GetWindowIdOfTab(contents)));
247  object_args->Set(tabs_constants::kOldPositionKey,
248                   new FundamentalValue(index));
249  args->Append(object_args);
250
251  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
252  DispatchEvent(profile,
253                tabs::OnDetached::kEventName,
254                args.Pass(),
255                EventRouter::USER_GESTURE_UNKNOWN);
256}
257
258void TabsEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
259                                   WebContents* contents,
260                                   int index) {
261  int tab_id = ExtensionTabUtil::GetTabId(contents);
262
263  scoped_ptr<base::ListValue> args(new base::ListValue);
264  args->Append(new FundamentalValue(tab_id));
265
266  base::DictionaryValue* object_args = new base::DictionaryValue();
267  object_args->SetInteger(tabs_constants::kWindowIdKey,
268                          ExtensionTabUtil::GetWindowIdOfTab(contents));
269  object_args->SetBoolean(tabs_constants::kWindowClosing,
270                          tab_strip_model->closing_all());
271  args->Append(object_args);
272
273  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
274  DispatchEvent(profile,
275                tabs::OnRemoved::kEventName,
276                args.Pass(),
277                EventRouter::USER_GESTURE_UNKNOWN);
278
279  int removed_count = tab_entries_.erase(tab_id);
280  DCHECK_GT(removed_count, 0);
281
282  UnregisterForTabNotifications(contents);
283}
284
285void TabsEventRouter::ActiveTabChanged(WebContents* old_contents,
286                                       WebContents* new_contents,
287                                       int index,
288                                       int reason) {
289  scoped_ptr<base::ListValue> args(new base::ListValue);
290  int tab_id = ExtensionTabUtil::GetTabId(new_contents);
291  args->Append(new FundamentalValue(tab_id));
292
293  base::DictionaryValue* object_args = new base::DictionaryValue();
294  object_args->Set(tabs_constants::kWindowIdKey,
295                   new FundamentalValue(
296                       ExtensionTabUtil::GetWindowIdOfTab(new_contents)));
297  args->Append(object_args);
298
299  // The onActivated event replaced onActiveChanged and onSelectionChanged. The
300  // deprecated events take two arguments: tabId, {windowId}.
301  Profile* profile =
302      Profile::FromBrowserContext(new_contents->GetBrowserContext());
303  EventRouter::UserGestureState gesture =
304      reason & CHANGE_REASON_USER_GESTURE
305      ? EventRouter::USER_GESTURE_ENABLED
306      : EventRouter::USER_GESTURE_NOT_ENABLED;
307  DispatchEvent(profile,
308                tabs::OnSelectionChanged::kEventName,
309                scoped_ptr<base::ListValue>(args->DeepCopy()),
310                gesture);
311  DispatchEvent(profile,
312                tabs::OnActiveChanged::kEventName,
313                scoped_ptr<base::ListValue>(args->DeepCopy()),
314                gesture);
315
316  // The onActivated event takes one argument: {windowId, tabId}.
317  args->Remove(0, NULL);
318  object_args->Set(tabs_constants::kTabIdKey,
319                   new FundamentalValue(tab_id));
320  DispatchEvent(profile, tabs::OnActivated::kEventName, args.Pass(), gesture);
321}
322
323void TabsEventRouter::TabSelectionChanged(
324    TabStripModel* tab_strip_model,
325    const ui::ListSelectionModel& old_model) {
326  ui::ListSelectionModel::SelectedIndices new_selection =
327      tab_strip_model->selection_model().selected_indices();
328  scoped_ptr<base::ListValue> all_tabs(new base::ListValue);
329
330  for (size_t i = 0; i < new_selection.size(); ++i) {
331    int index = new_selection[i];
332    WebContents* contents = tab_strip_model->GetWebContentsAt(index);
333    if (!contents)
334      break;
335    int tab_id = ExtensionTabUtil::GetTabId(contents);
336    all_tabs->Append(new FundamentalValue(tab_id));
337  }
338
339  scoped_ptr<base::ListValue> args(new base::ListValue);
340  scoped_ptr<base::DictionaryValue> select_info(new base::DictionaryValue);
341
342  select_info->Set(
343      tabs_constants::kWindowIdKey,
344      new FundamentalValue(
345          ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model)));
346
347  select_info->Set(tabs_constants::kTabIdsKey, all_tabs.release());
348  args->Append(select_info.release());
349
350  // The onHighlighted event replaced onHighlightChanged.
351  Profile* profile = tab_strip_model->profile();
352  DispatchEvent(profile,
353                tabs::OnHighlightChanged::kEventName,
354                scoped_ptr<base::ListValue>(args->DeepCopy()),
355                EventRouter::USER_GESTURE_UNKNOWN);
356  DispatchEvent(profile,
357                tabs::OnHighlighted::kEventName,
358                args.Pass(),
359                EventRouter::USER_GESTURE_UNKNOWN);
360}
361
362void TabsEventRouter::TabMoved(WebContents* contents,
363                               int from_index,
364                               int to_index) {
365  scoped_ptr<base::ListValue> args(new base::ListValue);
366  args->Append(
367      new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
368
369  base::DictionaryValue* object_args = new base::DictionaryValue();
370  object_args->Set(tabs_constants::kWindowIdKey,
371                   new FundamentalValue(
372                       ExtensionTabUtil::GetWindowIdOfTab(contents)));
373  object_args->Set(tabs_constants::kFromIndexKey,
374                   new FundamentalValue(from_index));
375  object_args->Set(tabs_constants::kToIndexKey,
376                   new FundamentalValue(to_index));
377  args->Append(object_args);
378
379  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
380  DispatchEvent(profile,
381                tabs::OnMoved::kEventName,
382                args.Pass(),
383                EventRouter::USER_GESTURE_UNKNOWN);
384}
385
386void TabsEventRouter::TabUpdated(WebContents* contents, bool did_navigate) {
387  TabEntry* entry = GetTabEntry(contents);
388  scoped_ptr<base::DictionaryValue> changed_properties;
389
390  CHECK(entry);
391
392  if (did_navigate)
393    changed_properties.reset(entry->DidNavigate(contents));
394  else
395    changed_properties.reset(entry->UpdateLoadState(contents));
396
397  if (changed_properties)
398    DispatchTabUpdatedEvent(contents, changed_properties.Pass());
399}
400
401void TabsEventRouter::FaviconUrlUpdated(WebContents* contents) {
402    content::NavigationEntry* entry =
403        contents->GetController().GetVisibleEntry();
404    if (!entry || !entry->GetFavicon().valid)
405      return;
406    scoped_ptr<base::DictionaryValue> changed_properties(
407        new base::DictionaryValue);
408    changed_properties->SetString(
409        tabs_constants::kFaviconUrlKey,
410        entry->GetFavicon().url.possibly_invalid_spec());
411    DispatchTabUpdatedEvent(contents, changed_properties.Pass());
412}
413
414void TabsEventRouter::DispatchEvent(
415    Profile* profile,
416    const std::string& event_name,
417    scoped_ptr<base::ListValue> args,
418    EventRouter::UserGestureState user_gesture) {
419  EventRouter* event_router = EventRouter::Get(profile);
420  if (!profile_->IsSameProfile(profile) || !event_router)
421    return;
422
423  scoped_ptr<Event> event(new Event(event_name, args.Pass()));
424  event->restrict_to_browser_context = profile;
425  event->user_gesture = user_gesture;
426  event_router->BroadcastEvent(event.Pass());
427}
428
429void TabsEventRouter::DispatchSimpleBrowserEvent(
430    Profile* profile, const int window_id, const std::string& event_name) {
431  if (!profile_->IsSameProfile(profile))
432    return;
433
434  scoped_ptr<base::ListValue> args(new base::ListValue);
435  args->Append(new FundamentalValue(window_id));
436
437  DispatchEvent(profile,
438                event_name,
439                args.Pass(),
440                EventRouter::USER_GESTURE_UNKNOWN);
441}
442
443void TabsEventRouter::DispatchTabUpdatedEvent(
444    WebContents* contents,
445    scoped_ptr<base::DictionaryValue> changed_properties) {
446  DCHECK(changed_properties);
447  DCHECK(contents);
448
449  // The state of the tab (as seen from the extension point of view) has
450  // changed.  Send a notification to the extension.
451  scoped_ptr<base::ListValue> args_base(new base::ListValue);
452
453  // First arg: The id of the tab that changed.
454  args_base->AppendInteger(ExtensionTabUtil::GetTabId(contents));
455
456  // Second arg: An object containing the changes to the tab state.  Filled in
457  // by WillDispatchTabUpdatedEvent as a copy of changed_properties, if the
458  // extension has the tabs permission.
459
460  // Third arg: An object containing the state of the tab. Filled in by
461  // WillDispatchTabUpdatedEvent.
462  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
463
464  scoped_ptr<Event> event(
465      new Event(tabs::OnUpdated::kEventName, args_base.Pass()));
466  event->restrict_to_browser_context = profile;
467  event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
468  event->will_dispatch_callback =
469      base::Bind(&WillDispatchTabUpdatedEvent,
470                 contents,
471                 changed_properties.get());
472  EventRouter::Get(profile)->BroadcastEvent(event.Pass());
473}
474
475TabsEventRouter::TabEntry* TabsEventRouter::GetTabEntry(
476    const WebContents* contents) {
477  int tab_id = ExtensionTabUtil::GetTabId(contents);
478  std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id);
479  if (tab_entries_.end() == i)
480    return NULL;
481  return &i->second;
482}
483
484void TabsEventRouter::Observe(int type,
485                              const content::NotificationSource& source,
486                              const content::NotificationDetails& details) {
487  if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) {
488    NavigationController* source_controller =
489        content::Source<NavigationController>(source).ptr();
490    TabUpdated(source_controller->GetWebContents(), true);
491  } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
492    // Tab was destroyed after being detached (without being re-attached).
493    WebContents* contents = content::Source<WebContents>(source).ptr();
494    registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
495        content::Source<NavigationController>(&contents->GetController()));
496    registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
497        content::Source<WebContents>(contents));
498    registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED,
499        content::Source<WebContents>(contents));
500  } else if (type == chrome::NOTIFICATION_FAVICON_UPDATED) {
501    bool icon_url_changed = *content::Details<bool>(details).ptr();
502    if (icon_url_changed)
503      FaviconUrlUpdated(content::Source<WebContents>(source).ptr());
504  } else {
505    NOTREACHED();
506  }
507}
508
509void TabsEventRouter::TabChangedAt(WebContents* contents,
510                                   int index,
511                                   TabChangeType change_type) {
512  TabUpdated(contents, false);
513}
514
515void TabsEventRouter::TabReplacedAt(TabStripModel* tab_strip_model,
516                                    WebContents* old_contents,
517                                    WebContents* new_contents,
518                                    int index) {
519  // Notify listeners that the next tabs closing or being added are due to
520  // WebContents being swapped.
521  const int new_tab_id = ExtensionTabUtil::GetTabId(new_contents);
522  const int old_tab_id = ExtensionTabUtil::GetTabId(old_contents);
523  scoped_ptr<base::ListValue> args(new base::ListValue);
524  args->Append(new FundamentalValue(new_tab_id));
525  args->Append(new FundamentalValue(old_tab_id));
526
527  DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()),
528                tabs::OnReplaced::kEventName,
529                args.Pass(),
530                EventRouter::USER_GESTURE_UNKNOWN);
531
532  // Update tab_entries_.
533  const int removed_count = tab_entries_.erase(old_tab_id);
534  DCHECK_GT(removed_count, 0);
535  UnregisterForTabNotifications(old_contents);
536
537  if (!GetTabEntry(new_contents)) {
538    tab_entries_[new_tab_id] = TabEntry();
539    RegisterForTabNotifications(new_contents);
540  }
541}
542
543void TabsEventRouter::TabPinnedStateChanged(WebContents* contents, int index) {
544  TabStripModel* tab_strip = NULL;
545  int tab_index;
546
547  if (ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index)) {
548    scoped_ptr<base::DictionaryValue> changed_properties(
549        new base::DictionaryValue());
550    changed_properties->SetBoolean(tabs_constants::kPinnedKey,
551                                   tab_strip->IsTabPinned(tab_index));
552    DispatchTabUpdatedEvent(contents, changed_properties.Pass());
553  }
554}
555
556}  // namespace extensions
557