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"
27 using base::DictionaryValue;
28 using base::ListValue;
29 using base::FundamentalValue;
30 using content::NavigationController;
31 using content::WebContents;
33 namespace extensions {
37 namespace tabs = api::tabs;
39 void 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,
51 event_args->Set(1, properties_value);
53 // Overwrite the third arg with our tab value as seen by this extension.
54 event_args->Set(2, ExtensionTabUtil::CreateTabValue(contents, extension));
59 TabsEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false),
63 base::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())
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;
79 base::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);
87 if (contents->GetURL() != url_) {
88 url_ = contents->GetURL();
89 changed_properties->SetString(tabs_constants::kUrlKey, url_.spec());
92 return changed_properties;
95 TabsEventRouter::TabsEventRouter(Profile* profile) : profile_(profile) {
96 DCHECK(!profile->IsOffTheRecord());
98 BrowserList::AddObserver(this);
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);
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();
117 TabsEventRouter::~TabsEventRouter() {
118 BrowserList::RemoveObserver(this);
121 void TabsEventRouter::OnBrowserAdded(Browser* browser) {
122 RegisterForBrowserNotifications(browser);
125 void TabsEventRouter::RegisterForBrowserNotifications(Browser* browser) {
126 if (!profile_->IsSameProfile(browser->profile()))
128 // Start listening to TabStripModel events for this browser.
129 TabStripModel* tab_strip = browser->tab_strip_model();
130 tab_strip->AddObserver(this);
132 for (int i = 0; i < tab_strip->count(); ++i) {
133 RegisterForTabNotifications(tab_strip->GetWebContentsAt(i));
137 void TabsEventRouter::RegisterForTabNotifications(WebContents* contents) {
139 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
140 content::Source<NavigationController>(&contents->GetController()));
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));
149 registrar_.Add(this, chrome::NOTIFICATION_FAVICON_UPDATED,
150 content::Source<WebContents>(contents));
152 ZoomController::FromWebContents(contents)->AddObserver(this);
155 void TabsEventRouter::UnregisterForTabNotifications(WebContents* contents) {
156 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
157 content::Source<NavigationController>(&contents->GetController()));
158 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
159 content::Source<WebContents>(contents));
160 registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED,
161 content::Source<WebContents>(contents));
163 ZoomController::FromWebContents(contents)->RemoveObserver(this);
166 void TabsEventRouter::OnBrowserRemoved(Browser* browser) {
167 if (!profile_->IsSameProfile(browser->profile()))
170 // Stop listening to TabStripModel events for this browser.
171 browser->tab_strip_model()->RemoveObserver(this);
174 void TabsEventRouter::OnBrowserSetLastActive(Browser* browser) {
175 TabsWindowsAPI* tabs_window_api = TabsWindowsAPI::Get(profile_);
176 if (tabs_window_api) {
177 tabs_window_api->windows_event_router()->OnActiveWindowChanged(
178 browser ? browser->extension_window_controller() : NULL);
182 static void WillDispatchTabCreatedEvent(WebContents* contents,
184 content::BrowserContext* context,
185 const Extension* extension,
186 base::ListValue* event_args) {
187 base::DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue(
188 contents, extension);
190 event_args->Append(tab_value);
191 tab_value->SetBoolean(tabs_constants::kSelectedKey, active);
194 void TabsEventRouter::TabCreatedAt(WebContents* contents,
197 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
198 scoped_ptr<base::ListValue> args(new base::ListValue);
199 scoped_ptr<Event> event(new Event(tabs::OnCreated::kEventName, args.Pass()));
200 event->restrict_to_browser_context = profile;
201 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
202 event->will_dispatch_callback =
203 base::Bind(&WillDispatchTabCreatedEvent, contents, active);
204 EventRouter::Get(profile)->BroadcastEvent(event.Pass());
206 RegisterForTabNotifications(contents);
209 void TabsEventRouter::TabInsertedAt(WebContents* contents,
212 // If tab is new, send created event.
213 int tab_id = ExtensionTabUtil::GetTabId(contents);
214 if (!GetTabEntry(contents)) {
215 tab_entries_[tab_id] = TabEntry();
217 TabCreatedAt(contents, index, active);
221 scoped_ptr<base::ListValue> args(new base::ListValue);
222 args->Append(new FundamentalValue(tab_id));
224 base::DictionaryValue* object_args = new base::DictionaryValue();
225 object_args->Set(tabs_constants::kNewWindowIdKey,
226 new FundamentalValue(
227 ExtensionTabUtil::GetWindowIdOfTab(contents)));
228 object_args->Set(tabs_constants::kNewPositionKey,
229 new FundamentalValue(index));
230 args->Append(object_args);
232 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
233 DispatchEvent(profile, tabs::OnAttached::kEventName, args.Pass(),
234 EventRouter::USER_GESTURE_UNKNOWN);
237 void TabsEventRouter::TabDetachedAt(WebContents* contents, int index) {
238 if (!GetTabEntry(contents)) {
239 // The tab was removed. Don't send detach event.
243 scoped_ptr<base::ListValue> args(new base::ListValue);
245 new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
247 base::DictionaryValue* object_args = new base::DictionaryValue();
248 object_args->Set(tabs_constants::kOldWindowIdKey,
249 new FundamentalValue(
250 ExtensionTabUtil::GetWindowIdOfTab(contents)));
251 object_args->Set(tabs_constants::kOldPositionKey,
252 new FundamentalValue(index));
253 args->Append(object_args);
255 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
256 DispatchEvent(profile,
257 tabs::OnDetached::kEventName,
259 EventRouter::USER_GESTURE_UNKNOWN);
262 void TabsEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
263 WebContents* contents,
265 int tab_id = ExtensionTabUtil::GetTabId(contents);
267 scoped_ptr<base::ListValue> args(new base::ListValue);
268 args->Append(new FundamentalValue(tab_id));
270 base::DictionaryValue* object_args = new base::DictionaryValue();
271 object_args->SetInteger(tabs_constants::kWindowIdKey,
272 ExtensionTabUtil::GetWindowIdOfTab(contents));
273 object_args->SetBoolean(tabs_constants::kWindowClosing,
274 tab_strip_model->closing_all());
275 args->Append(object_args);
277 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
278 DispatchEvent(profile,
279 tabs::OnRemoved::kEventName,
281 EventRouter::USER_GESTURE_UNKNOWN);
283 int removed_count = tab_entries_.erase(tab_id);
284 DCHECK_GT(removed_count, 0);
286 UnregisterForTabNotifications(contents);
289 void TabsEventRouter::ActiveTabChanged(WebContents* old_contents,
290 WebContents* new_contents,
293 scoped_ptr<base::ListValue> args(new base::ListValue);
294 int tab_id = ExtensionTabUtil::GetTabId(new_contents);
295 args->Append(new FundamentalValue(tab_id));
297 base::DictionaryValue* object_args = new base::DictionaryValue();
298 object_args->Set(tabs_constants::kWindowIdKey,
299 new FundamentalValue(
300 ExtensionTabUtil::GetWindowIdOfTab(new_contents)));
301 args->Append(object_args);
303 // The onActivated event replaced onActiveChanged and onSelectionChanged. The
304 // deprecated events take two arguments: tabId, {windowId}.
306 Profile::FromBrowserContext(new_contents->GetBrowserContext());
307 EventRouter::UserGestureState gesture =
308 reason & CHANGE_REASON_USER_GESTURE
309 ? EventRouter::USER_GESTURE_ENABLED
310 : EventRouter::USER_GESTURE_NOT_ENABLED;
311 DispatchEvent(profile,
312 tabs::OnSelectionChanged::kEventName,
313 scoped_ptr<base::ListValue>(args->DeepCopy()),
315 DispatchEvent(profile,
316 tabs::OnActiveChanged::kEventName,
317 scoped_ptr<base::ListValue>(args->DeepCopy()),
320 // The onActivated event takes one argument: {windowId, tabId}.
321 args->Remove(0, NULL);
322 object_args->Set(tabs_constants::kTabIdKey,
323 new FundamentalValue(tab_id));
324 DispatchEvent(profile, tabs::OnActivated::kEventName, args.Pass(), gesture);
327 void TabsEventRouter::TabSelectionChanged(
328 TabStripModel* tab_strip_model,
329 const ui::ListSelectionModel& old_model) {
330 ui::ListSelectionModel::SelectedIndices new_selection =
331 tab_strip_model->selection_model().selected_indices();
332 scoped_ptr<base::ListValue> all_tabs(new base::ListValue);
334 for (size_t i = 0; i < new_selection.size(); ++i) {
335 int index = new_selection[i];
336 WebContents* contents = tab_strip_model->GetWebContentsAt(index);
339 int tab_id = ExtensionTabUtil::GetTabId(contents);
340 all_tabs->Append(new FundamentalValue(tab_id));
343 scoped_ptr<base::ListValue> args(new base::ListValue);
344 scoped_ptr<base::DictionaryValue> select_info(new base::DictionaryValue);
347 tabs_constants::kWindowIdKey,
348 new FundamentalValue(
349 ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model)));
351 select_info->Set(tabs_constants::kTabIdsKey, all_tabs.release());
352 args->Append(select_info.release());
354 // The onHighlighted event replaced onHighlightChanged.
355 Profile* profile = tab_strip_model->profile();
356 DispatchEvent(profile,
357 tabs::OnHighlightChanged::kEventName,
358 scoped_ptr<base::ListValue>(args->DeepCopy()),
359 EventRouter::USER_GESTURE_UNKNOWN);
360 DispatchEvent(profile,
361 tabs::OnHighlighted::kEventName,
363 EventRouter::USER_GESTURE_UNKNOWN);
366 void TabsEventRouter::TabMoved(WebContents* contents,
369 scoped_ptr<base::ListValue> args(new base::ListValue);
371 new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
373 base::DictionaryValue* object_args = new base::DictionaryValue();
374 object_args->Set(tabs_constants::kWindowIdKey,
375 new FundamentalValue(
376 ExtensionTabUtil::GetWindowIdOfTab(contents)));
377 object_args->Set(tabs_constants::kFromIndexKey,
378 new FundamentalValue(from_index));
379 object_args->Set(tabs_constants::kToIndexKey,
380 new FundamentalValue(to_index));
381 args->Append(object_args);
383 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
384 DispatchEvent(profile,
385 tabs::OnMoved::kEventName,
387 EventRouter::USER_GESTURE_UNKNOWN);
390 void TabsEventRouter::TabUpdated(WebContents* contents, bool did_navigate) {
391 TabEntry* entry = GetTabEntry(contents);
392 scoped_ptr<base::DictionaryValue> changed_properties;
397 changed_properties.reset(entry->DidNavigate(contents));
399 changed_properties.reset(entry->UpdateLoadState(contents));
401 if (changed_properties)
402 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
405 void TabsEventRouter::FaviconUrlUpdated(WebContents* contents) {
406 content::NavigationEntry* entry =
407 contents->GetController().GetVisibleEntry();
408 if (!entry || !entry->GetFavicon().valid)
410 scoped_ptr<base::DictionaryValue> changed_properties(
411 new base::DictionaryValue);
412 changed_properties->SetString(
413 tabs_constants::kFaviconUrlKey,
414 entry->GetFavicon().url.possibly_invalid_spec());
415 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
418 void TabsEventRouter::DispatchEvent(
420 const std::string& event_name,
421 scoped_ptr<base::ListValue> args,
422 EventRouter::UserGestureState user_gesture) {
423 EventRouter* event_router = EventRouter::Get(profile);
424 if (!profile_->IsSameProfile(profile) || !event_router)
427 scoped_ptr<Event> event(new Event(event_name, args.Pass()));
428 event->restrict_to_browser_context = profile;
429 event->user_gesture = user_gesture;
430 event_router->BroadcastEvent(event.Pass());
433 void TabsEventRouter::DispatchSimpleBrowserEvent(
434 Profile* profile, const int window_id, const std::string& event_name) {
435 if (!profile_->IsSameProfile(profile))
438 scoped_ptr<base::ListValue> args(new base::ListValue);
439 args->Append(new FundamentalValue(window_id));
441 DispatchEvent(profile,
444 EventRouter::USER_GESTURE_UNKNOWN);
447 void TabsEventRouter::DispatchTabUpdatedEvent(
448 WebContents* contents,
449 scoped_ptr<base::DictionaryValue> changed_properties) {
450 DCHECK(changed_properties);
453 // The state of the tab (as seen from the extension point of view) has
454 // changed. Send a notification to the extension.
455 scoped_ptr<base::ListValue> args_base(new base::ListValue);
457 // First arg: The id of the tab that changed.
458 args_base->AppendInteger(ExtensionTabUtil::GetTabId(contents));
460 // Second arg: An object containing the changes to the tab state. Filled in
461 // by WillDispatchTabUpdatedEvent as a copy of changed_properties, if the
462 // extension has the tabs permission.
464 // Third arg: An object containing the state of the tab. Filled in by
465 // WillDispatchTabUpdatedEvent.
466 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
468 scoped_ptr<Event> event(
469 new Event(tabs::OnUpdated::kEventName, args_base.Pass()));
470 event->restrict_to_browser_context = profile;
471 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
472 event->will_dispatch_callback =
473 base::Bind(&WillDispatchTabUpdatedEvent,
475 changed_properties.get());
476 EventRouter::Get(profile)->BroadcastEvent(event.Pass());
479 TabsEventRouter::TabEntry* TabsEventRouter::GetTabEntry(WebContents* contents) {
480 int tab_id = ExtensionTabUtil::GetTabId(contents);
481 std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id);
482 if (tab_entries_.end() == i)
487 void TabsEventRouter::Observe(int type,
488 const content::NotificationSource& source,
489 const content::NotificationDetails& details) {
490 if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) {
491 NavigationController* source_controller =
492 content::Source<NavigationController>(source).ptr();
493 TabUpdated(source_controller->GetWebContents(), true);
494 } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
495 // Tab was destroyed after being detached (without being re-attached).
496 WebContents* contents = content::Source<WebContents>(source).ptr();
497 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
498 content::Source<NavigationController>(&contents->GetController()));
499 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
500 content::Source<WebContents>(contents));
501 registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED,
502 content::Source<WebContents>(contents));
503 } else if (type == chrome::NOTIFICATION_FAVICON_UPDATED) {
504 bool icon_url_changed = *content::Details<bool>(details).ptr();
505 if (icon_url_changed)
506 FaviconUrlUpdated(content::Source<WebContents>(source).ptr());
512 void TabsEventRouter::TabChangedAt(WebContents* contents,
514 TabChangeType change_type) {
515 TabUpdated(contents, false);
518 void TabsEventRouter::TabReplacedAt(TabStripModel* tab_strip_model,
519 WebContents* old_contents,
520 WebContents* new_contents,
522 // Notify listeners that the next tabs closing or being added are due to
523 // WebContents being swapped.
524 const int new_tab_id = ExtensionTabUtil::GetTabId(new_contents);
525 const int old_tab_id = ExtensionTabUtil::GetTabId(old_contents);
526 scoped_ptr<base::ListValue> args(new base::ListValue);
527 args->Append(new FundamentalValue(new_tab_id));
528 args->Append(new FundamentalValue(old_tab_id));
530 DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()),
531 tabs::OnReplaced::kEventName,
533 EventRouter::USER_GESTURE_UNKNOWN);
535 // Update tab_entries_.
536 const int removed_count = tab_entries_.erase(old_tab_id);
537 DCHECK_GT(removed_count, 0);
538 UnregisterForTabNotifications(old_contents);
540 if (!GetTabEntry(new_contents)) {
541 tab_entries_[new_tab_id] = TabEntry();
542 RegisterForTabNotifications(new_contents);
546 void TabsEventRouter::TabPinnedStateChanged(WebContents* contents, int index) {
547 TabStripModel* tab_strip = NULL;
550 if (ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index)) {
551 scoped_ptr<base::DictionaryValue> changed_properties(
552 new base::DictionaryValue());
553 changed_properties->SetBoolean(tabs_constants::kPinnedKey,
554 tab_strip->IsTabPinned(tab_index));
555 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
559 void TabsEventRouter::OnZoomChanged(
560 const ZoomController::ZoomChangedEventData& data) {
561 DCHECK(data.web_contents);
562 int tab_id = ExtensionTabUtil::GetTabId(data.web_contents);
566 // Prepare the zoom change information.
567 api::tabs::OnZoomChange::ZoomChangeInfo zoom_change_info;
568 zoom_change_info.tab_id = tab_id;
569 zoom_change_info.old_zoom_factor =
570 content::ZoomLevelToZoomFactor(data.old_zoom_level);
571 zoom_change_info.new_zoom_factor =
572 content::ZoomLevelToZoomFactor(data.new_zoom_level);
573 ZoomModeToZoomSettings(data.zoom_mode,
574 &zoom_change_info.zoom_settings);
576 // Dispatch the |onZoomChange| event.
577 Profile* profile = Profile::FromBrowserContext(
578 data.web_contents->GetBrowserContext());
579 DispatchEvent(profile,
580 tabs::OnZoomChange::kEventName,
581 api::tabs::OnZoomChange::Create(zoom_change_info),
582 EventRouter::USER_GESTURE_UNKNOWN);
585 } // namespace extensions