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.
5 #include "chrome/browser/extensions/api/tabs/tabs_event_router.h"
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 #include "extensions/browser/extension_system.h"
28 using base::DictionaryValue;
29 using base::ListValue;
30 using base::FundamentalValue;
31 using content::NavigationController;
32 using content::WebContents;
34 namespace extensions {
38 namespace tabs = api::tabs;
40 void WillDispatchTabUpdatedEvent(
41 WebContents* contents,
42 const base::DictionaryValue* changed_properties,
43 content::BrowserContext* context,
44 const Extension* extension,
45 base::ListValue* event_args) {
46 // Overwrite the second argument with the appropriate properties dictionary,
47 // depending on extension permissions.
48 base::DictionaryValue* properties_value = changed_properties->DeepCopy();
49 ExtensionTabUtil::ScrubTabValueForExtension(contents,
52 event_args->Set(1, properties_value);
54 // Overwrite the third arg with our tab value as seen by this extension.
55 event_args->Set(2, ExtensionTabUtil::CreateTabValue(contents, extension));
60 TabsEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false),
64 base::DictionaryValue* TabsEventRouter::TabEntry::UpdateLoadState(
65 const WebContents* contents) {
66 // The tab may go in & out of loading (for instance if iframes navigate).
67 // We only want to respond to the first change from loading to !loading after
68 // the NAV_ENTRY_COMMITTED was fired.
69 if (!complete_waiting_on_load_ || contents->IsLoading())
72 // Send "complete" state change.
73 complete_waiting_on_load_ = false;
74 base::DictionaryValue* changed_properties = new base::DictionaryValue();
75 changed_properties->SetString(tabs_constants::kStatusKey,
76 tabs_constants::kStatusValueComplete);
77 return changed_properties;
80 base::DictionaryValue* TabsEventRouter::TabEntry::DidNavigate(
81 const WebContents* contents) {
82 // Send "loading" state change.
83 complete_waiting_on_load_ = true;
84 base::DictionaryValue* changed_properties = new base::DictionaryValue();
85 changed_properties->SetString(tabs_constants::kStatusKey,
86 tabs_constants::kStatusValueLoading);
88 if (contents->GetURL() != url_) {
89 url_ = contents->GetURL();
90 changed_properties->SetString(tabs_constants::kUrlKey, url_.spec());
93 return changed_properties;
96 TabsEventRouter::TabsEventRouter(Profile* profile) : profile_(profile) {
97 DCHECK(!profile->IsOffTheRecord());
99 BrowserList::AddObserver(this);
101 // Init() can happen after the browser is running, so catch up with any
102 // windows that already exist.
103 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
104 RegisterForBrowserNotifications(*it);
106 // Also catch up our internal bookkeeping of tab entries.
107 Browser* browser = *it;
108 if (browser->tab_strip_model()) {
109 for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
110 WebContents* contents = browser->tab_strip_model()->GetWebContentsAt(i);
111 int tab_id = ExtensionTabUtil::GetTabId(contents);
112 tab_entries_[tab_id] = TabEntry();
118 TabsEventRouter::~TabsEventRouter() {
119 BrowserList::RemoveObserver(this);
122 void TabsEventRouter::OnBrowserAdded(Browser* browser) {
123 RegisterForBrowserNotifications(browser);
126 void TabsEventRouter::RegisterForBrowserNotifications(Browser* browser) {
127 if (!profile_->IsSameProfile(browser->profile()))
129 // Start listening to TabStripModel events for this browser.
130 TabStripModel* tab_strip = browser->tab_strip_model();
131 tab_strip->AddObserver(this);
133 for (int i = 0; i < tab_strip->count(); ++i) {
134 RegisterForTabNotifications(tab_strip->GetWebContentsAt(i));
138 void TabsEventRouter::RegisterForTabNotifications(WebContents* contents) {
140 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
141 content::Source<NavigationController>(&contents->GetController()));
143 // Observing NOTIFICATION_WEB_CONTENTS_DESTROYED is necessary because it's
144 // possible for tabs to be created, detached and then destroyed without
145 // ever having been re-attached and closed. This happens in the case of
146 // a devtools WebContents that is opened in window, docked, then closed.
147 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
148 content::Source<WebContents>(contents));
150 registrar_.Add(this, chrome::NOTIFICATION_FAVICON_UPDATED,
151 content::Source<WebContents>(contents));
154 void TabsEventRouter::UnregisterForTabNotifications(WebContents* contents) {
155 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
156 content::Source<NavigationController>(&contents->GetController()));
157 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
158 content::Source<WebContents>(contents));
159 registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED,
160 content::Source<WebContents>(contents));
163 void TabsEventRouter::OnBrowserRemoved(Browser* browser) {
164 if (!profile_->IsSameProfile(browser->profile()))
167 // Stop listening to TabStripModel events for this browser.
168 browser->tab_strip_model()->RemoveObserver(this);
171 void TabsEventRouter::OnBrowserSetLastActive(Browser* browser) {
172 TabsWindowsAPI* tabs_window_api = TabsWindowsAPI::Get(profile_);
173 if (tabs_window_api) {
174 tabs_window_api->windows_event_router()->OnActiveWindowChanged(
175 browser ? browser->extension_window_controller() : NULL);
179 static void WillDispatchTabCreatedEvent(WebContents* contents,
181 content::BrowserContext* context,
182 const Extension* extension,
183 base::ListValue* event_args) {
184 base::DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue(
185 contents, extension);
187 event_args->Append(tab_value);
188 tab_value->SetBoolean(tabs_constants::kSelectedKey, active);
191 void TabsEventRouter::TabCreatedAt(WebContents* contents,
194 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
195 scoped_ptr<base::ListValue> args(new base::ListValue);
196 scoped_ptr<Event> event(new Event(tabs::OnCreated::kEventName, args.Pass()));
197 event->restrict_to_browser_context = profile;
198 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
199 event->will_dispatch_callback =
200 base::Bind(&WillDispatchTabCreatedEvent, contents, active);
201 ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(event.Pass());
203 RegisterForTabNotifications(contents);
206 void TabsEventRouter::TabInsertedAt(WebContents* contents,
209 // If tab is new, send created event.
210 int tab_id = ExtensionTabUtil::GetTabId(contents);
211 if (!GetTabEntry(contents)) {
212 tab_entries_[tab_id] = TabEntry();
214 TabCreatedAt(contents, index, active);
218 scoped_ptr<base::ListValue> args(new base::ListValue);
219 args->Append(new FundamentalValue(tab_id));
221 base::DictionaryValue* object_args = new base::DictionaryValue();
222 object_args->Set(tabs_constants::kNewWindowIdKey,
223 new FundamentalValue(
224 ExtensionTabUtil::GetWindowIdOfTab(contents)));
225 object_args->Set(tabs_constants::kNewPositionKey,
226 new FundamentalValue(index));
227 args->Append(object_args);
229 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
230 DispatchEvent(profile, tabs::OnAttached::kEventName, args.Pass(),
231 EventRouter::USER_GESTURE_UNKNOWN);
234 void TabsEventRouter::TabDetachedAt(WebContents* contents, int index) {
235 if (!GetTabEntry(contents)) {
236 // The tab was removed. Don't send detach event.
240 scoped_ptr<base::ListValue> args(new base::ListValue);
242 new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
244 base::DictionaryValue* object_args = new base::DictionaryValue();
245 object_args->Set(tabs_constants::kOldWindowIdKey,
246 new FundamentalValue(
247 ExtensionTabUtil::GetWindowIdOfTab(contents)));
248 object_args->Set(tabs_constants::kOldPositionKey,
249 new FundamentalValue(index));
250 args->Append(object_args);
252 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
253 DispatchEvent(profile,
254 tabs::OnDetached::kEventName,
256 EventRouter::USER_GESTURE_UNKNOWN);
259 void TabsEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
260 WebContents* contents,
262 int tab_id = ExtensionTabUtil::GetTabId(contents);
264 scoped_ptr<base::ListValue> args(new base::ListValue);
265 args->Append(new FundamentalValue(tab_id));
267 base::DictionaryValue* object_args = new base::DictionaryValue();
268 object_args->SetInteger(tabs_constants::kWindowIdKey,
269 ExtensionTabUtil::GetWindowIdOfTab(contents));
270 object_args->SetBoolean(tabs_constants::kWindowClosing,
271 tab_strip_model->closing_all());
272 args->Append(object_args);
274 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
275 DispatchEvent(profile,
276 tabs::OnRemoved::kEventName,
278 EventRouter::USER_GESTURE_UNKNOWN);
280 int removed_count = tab_entries_.erase(tab_id);
281 DCHECK_GT(removed_count, 0);
283 UnregisterForTabNotifications(contents);
286 void TabsEventRouter::ActiveTabChanged(WebContents* old_contents,
287 WebContents* new_contents,
290 scoped_ptr<base::ListValue> args(new base::ListValue);
291 int tab_id = ExtensionTabUtil::GetTabId(new_contents);
292 args->Append(new FundamentalValue(tab_id));
294 base::DictionaryValue* object_args = new base::DictionaryValue();
295 object_args->Set(tabs_constants::kWindowIdKey,
296 new FundamentalValue(
297 ExtensionTabUtil::GetWindowIdOfTab(new_contents)));
298 args->Append(object_args);
300 // The onActivated event replaced onActiveChanged and onSelectionChanged. The
301 // deprecated events take two arguments: tabId, {windowId}.
303 Profile::FromBrowserContext(new_contents->GetBrowserContext());
304 EventRouter::UserGestureState gesture =
305 reason & CHANGE_REASON_USER_GESTURE
306 ? EventRouter::USER_GESTURE_ENABLED
307 : EventRouter::USER_GESTURE_NOT_ENABLED;
308 DispatchEvent(profile,
309 tabs::OnSelectionChanged::kEventName,
310 scoped_ptr<base::ListValue>(args->DeepCopy()),
312 DispatchEvent(profile,
313 tabs::OnActiveChanged::kEventName,
314 scoped_ptr<base::ListValue>(args->DeepCopy()),
317 // The onActivated event takes one argument: {windowId, tabId}.
318 args->Remove(0, NULL);
319 object_args->Set(tabs_constants::kTabIdKey,
320 new FundamentalValue(tab_id));
321 DispatchEvent(profile, tabs::OnActivated::kEventName, args.Pass(), gesture);
324 void TabsEventRouter::TabSelectionChanged(
325 TabStripModel* tab_strip_model,
326 const ui::ListSelectionModel& old_model) {
327 ui::ListSelectionModel::SelectedIndices new_selection =
328 tab_strip_model->selection_model().selected_indices();
329 scoped_ptr<base::ListValue> all_tabs(new base::ListValue);
331 for (size_t i = 0; i < new_selection.size(); ++i) {
332 int index = new_selection[i];
333 WebContents* contents = tab_strip_model->GetWebContentsAt(index);
336 int tab_id = ExtensionTabUtil::GetTabId(contents);
337 all_tabs->Append(new FundamentalValue(tab_id));
340 scoped_ptr<base::ListValue> args(new base::ListValue);
341 scoped_ptr<base::DictionaryValue> select_info(new base::DictionaryValue);
344 tabs_constants::kWindowIdKey,
345 new FundamentalValue(
346 ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model)));
348 select_info->Set(tabs_constants::kTabIdsKey, all_tabs.release());
349 args->Append(select_info.release());
351 // The onHighlighted event replaced onHighlightChanged.
352 Profile* profile = tab_strip_model->profile();
353 DispatchEvent(profile,
354 tabs::OnHighlightChanged::kEventName,
355 scoped_ptr<base::ListValue>(args->DeepCopy()),
356 EventRouter::USER_GESTURE_UNKNOWN);
357 DispatchEvent(profile,
358 tabs::OnHighlighted::kEventName,
360 EventRouter::USER_GESTURE_UNKNOWN);
363 void TabsEventRouter::TabMoved(WebContents* contents,
366 scoped_ptr<base::ListValue> args(new base::ListValue);
368 new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
370 base::DictionaryValue* object_args = new base::DictionaryValue();
371 object_args->Set(tabs_constants::kWindowIdKey,
372 new FundamentalValue(
373 ExtensionTabUtil::GetWindowIdOfTab(contents)));
374 object_args->Set(tabs_constants::kFromIndexKey,
375 new FundamentalValue(from_index));
376 object_args->Set(tabs_constants::kToIndexKey,
377 new FundamentalValue(to_index));
378 args->Append(object_args);
380 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
381 DispatchEvent(profile,
382 tabs::OnMoved::kEventName,
384 EventRouter::USER_GESTURE_UNKNOWN);
387 void TabsEventRouter::TabUpdated(WebContents* contents, bool did_navigate) {
388 TabEntry* entry = GetTabEntry(contents);
389 scoped_ptr<base::DictionaryValue> changed_properties;
394 changed_properties.reset(entry->DidNavigate(contents));
396 changed_properties.reset(entry->UpdateLoadState(contents));
398 if (changed_properties)
399 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
402 void TabsEventRouter::FaviconUrlUpdated(WebContents* contents) {
403 content::NavigationEntry* entry =
404 contents->GetController().GetVisibleEntry();
405 if (!entry || !entry->GetFavicon().valid)
407 scoped_ptr<base::DictionaryValue> changed_properties(
408 new base::DictionaryValue);
409 changed_properties->SetString(
410 tabs_constants::kFaviconUrlKey,
411 entry->GetFavicon().url.possibly_invalid_spec());
412 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
415 void TabsEventRouter::DispatchEvent(
417 const std::string& event_name,
418 scoped_ptr<base::ListValue> args,
419 EventRouter::UserGestureState user_gesture) {
420 if (!profile_->IsSameProfile(profile) ||
421 !ExtensionSystem::Get(profile)->event_router())
424 scoped_ptr<Event> event(new Event(event_name, args.Pass()));
425 event->restrict_to_browser_context = profile;
426 event->user_gesture = user_gesture;
427 ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(event.Pass());
430 void TabsEventRouter::DispatchSimpleBrowserEvent(
431 Profile* profile, const int window_id, const std::string& event_name) {
432 if (!profile_->IsSameProfile(profile))
435 scoped_ptr<base::ListValue> args(new base::ListValue);
436 args->Append(new FundamentalValue(window_id));
438 DispatchEvent(profile,
441 EventRouter::USER_GESTURE_UNKNOWN);
444 void TabsEventRouter::DispatchTabUpdatedEvent(
445 WebContents* contents,
446 scoped_ptr<base::DictionaryValue> changed_properties) {
447 DCHECK(changed_properties);
450 // The state of the tab (as seen from the extension point of view) has
451 // changed. Send a notification to the extension.
452 scoped_ptr<base::ListValue> args_base(new base::ListValue);
454 // First arg: The id of the tab that changed.
455 args_base->AppendInteger(ExtensionTabUtil::GetTabId(contents));
457 // Second arg: An object containing the changes to the tab state. Filled in
458 // by WillDispatchTabUpdatedEvent as a copy of changed_properties, if the
459 // extension has the tabs permission.
461 // Third arg: An object containing the state of the tab. Filled in by
462 // WillDispatchTabUpdatedEvent.
463 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
465 scoped_ptr<Event> event(
466 new Event(tabs::OnUpdated::kEventName, args_base.Pass()));
467 event->restrict_to_browser_context = profile;
468 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
469 event->will_dispatch_callback =
470 base::Bind(&WillDispatchTabUpdatedEvent,
472 changed_properties.get());
473 ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(event.Pass());
476 TabsEventRouter::TabEntry* TabsEventRouter::GetTabEntry(
477 const WebContents* contents) {
478 int tab_id = ExtensionTabUtil::GetTabId(contents);
479 std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id);
480 if (tab_entries_.end() == i)
485 void TabsEventRouter::Observe(int type,
486 const content::NotificationSource& source,
487 const content::NotificationDetails& details) {
488 if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) {
489 NavigationController* source_controller =
490 content::Source<NavigationController>(source).ptr();
491 TabUpdated(source_controller->GetWebContents(), true);
492 } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
493 // Tab was destroyed after being detached (without being re-attached).
494 WebContents* contents = content::Source<WebContents>(source).ptr();
495 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
496 content::Source<NavigationController>(&contents->GetController()));
497 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
498 content::Source<WebContents>(contents));
499 registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED,
500 content::Source<WebContents>(contents));
501 } else if (type == chrome::NOTIFICATION_FAVICON_UPDATED) {
502 bool icon_url_changed = *content::Details<bool>(details).ptr();
503 if (icon_url_changed)
504 FaviconUrlUpdated(content::Source<WebContents>(source).ptr());
510 void TabsEventRouter::TabChangedAt(WebContents* contents,
512 TabChangeType change_type) {
513 TabUpdated(contents, false);
516 void TabsEventRouter::TabReplacedAt(TabStripModel* tab_strip_model,
517 WebContents* old_contents,
518 WebContents* new_contents,
520 // Notify listeners that the next tabs closing or being added are due to
521 // WebContents being swapped.
522 const int new_tab_id = ExtensionTabUtil::GetTabId(new_contents);
523 const int old_tab_id = ExtensionTabUtil::GetTabId(old_contents);
524 scoped_ptr<base::ListValue> args(new base::ListValue);
525 args->Append(new FundamentalValue(new_tab_id));
526 args->Append(new FundamentalValue(old_tab_id));
528 DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()),
529 tabs::OnReplaced::kEventName,
531 EventRouter::USER_GESTURE_UNKNOWN);
533 // Update tab_entries_.
534 const int removed_count = tab_entries_.erase(old_tab_id);
535 DCHECK_GT(removed_count, 0);
536 UnregisterForTabNotifications(old_contents);
538 if (!GetTabEntry(new_contents)) {
539 tab_entries_[new_tab_id] = TabEntry();
540 RegisterForTabNotifications(new_contents);
544 void TabsEventRouter::TabPinnedStateChanged(WebContents* contents, int index) {
545 TabStripModel* tab_strip = NULL;
548 if (ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index)) {
549 scoped_ptr<base::DictionaryValue> changed_properties(
550 new base::DictionaryValue());
551 changed_properties->SetBoolean(tabs_constants::kPinnedKey,
552 tab_strip->IsTabPinned(tab_index));
553 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
557 } // namespace extensions