Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / api / tabs / tabs_event_router.cc
blob74a12c99fd671f29742ba823ca74fe382316d891
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/command_line.h"
8 #include "base/json/json_writer.h"
9 #include "base/values.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
12 #include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
13 #include "chrome/browser/extensions/api/tabs/windows_event_router.h"
14 #include "chrome/browser/extensions/extension_tab_util.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_iterator.h"
18 #include "chrome/browser/ui/browser_list.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/common/extensions/extension_constants.h"
22 #include "components/favicon/content/content_favicon_driver.h"
23 #include "content/public/browser/favicon_status.h"
24 #include "content/public/browser/navigation_controller.h"
25 #include "content/public/browser/navigation_entry.h"
26 #include "content/public/browser/notification_service.h"
27 #include "content/public/browser/notification_types.h"
28 #include "content/public/browser/web_contents.h"
30 using base::DictionaryValue;
31 using base::ListValue;
32 using base::FundamentalValue;
33 using content::NavigationController;
34 using content::WebContents;
35 using ui_zoom::ZoomController;
37 namespace extensions {
39 namespace {
41 namespace tabs = api::tabs;
43 bool WillDispatchTabUpdatedEvent(
44 WebContents* contents,
45 const base::DictionaryValue* changed_properties,
46 content::BrowserContext* context,
47 const Extension* extension,
48 Event* event,
49 const base::DictionaryValue* listener_filter) {
50 // Overwrite the second argument with the appropriate properties dictionary,
51 // depending on extension permissions.
52 base::DictionaryValue* properties_value = changed_properties->DeepCopy();
53 ExtensionTabUtil::ScrubTabValueForExtension(contents,
54 extension,
55 properties_value);
56 event->event_args->Set(1, properties_value);
58 // Overwrite the third arg with our tab value as seen by this extension.
59 event->event_args->Set(2,
60 ExtensionTabUtil::CreateTabValue(contents, extension));
61 return true;
64 } // namespace
66 TabsEventRouter::TabEntry::TabEntry(content::WebContents* contents)
67 : contents_(contents),
68 complete_waiting_on_load_(false),
69 was_audible_(contents->WasRecentlyAudible()),
70 was_muted_(contents->IsAudioMuted()) {
73 scoped_ptr<base::DictionaryValue> TabsEventRouter::TabEntry::UpdateLoadState() {
74 // The tab may go in & out of loading (for instance if iframes navigate).
75 // We only want to respond to the first change from loading to !loading after
76 // the NAV_ENTRY_COMMITTED was fired.
77 scoped_ptr<base::DictionaryValue> changed_properties(
78 new base::DictionaryValue());
79 if (!complete_waiting_on_load_ || contents_->IsLoading()) {
80 return changed_properties.Pass();
83 // Send "complete" state change.
84 complete_waiting_on_load_ = false;
85 changed_properties->SetString(tabs_constants::kStatusKey,
86 tabs_constants::kStatusValueComplete);
87 return changed_properties.Pass();
90 scoped_ptr<base::DictionaryValue> TabsEventRouter::TabEntry::DidNavigate() {
91 // Send "loading" state change.
92 complete_waiting_on_load_ = true;
93 scoped_ptr<base::DictionaryValue> changed_properties(
94 new base::DictionaryValue());
95 changed_properties->SetString(tabs_constants::kStatusKey,
96 tabs_constants::kStatusValueLoading);
98 if (contents_->GetURL() != url_) {
99 url_ = contents_->GetURL();
100 changed_properties->SetString(tabs_constants::kUrlKey, url_.spec());
103 return changed_properties.Pass();
106 bool TabsEventRouter::TabEntry::SetAudible(bool new_val) {
107 if (was_audible_ == new_val)
108 return false;
109 was_audible_ = new_val;
110 return true;
113 bool TabsEventRouter::TabEntry::SetMuted(bool new_val) {
114 if (was_muted_ == new_val)
115 return false;
116 was_muted_ = new_val;
117 return true;
120 TabsEventRouter::TabsEventRouter(Profile* profile)
121 : profile_(profile), favicon_scoped_observer_(this) {
122 DCHECK(!profile->IsOffTheRecord());
124 BrowserList::AddObserver(this);
126 // Init() can happen after the browser is running, so catch up with any
127 // windows that already exist.
128 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
129 RegisterForBrowserNotifications(*it);
131 // Also catch up our internal bookkeeping of tab entries.
132 Browser* browser = *it;
133 if (ExtensionTabUtil::BrowserSupportsTabs(browser)) {
134 for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
135 WebContents* contents = browser->tab_strip_model()->GetWebContentsAt(i);
136 int tab_id = ExtensionTabUtil::GetTabId(contents);
137 tab_entries_[tab_id] = make_linked_ptr(new TabEntry(contents));
143 TabsEventRouter::~TabsEventRouter() {
144 BrowserList::RemoveObserver(this);
147 void TabsEventRouter::OnBrowserAdded(Browser* browser) {
148 RegisterForBrowserNotifications(browser);
151 void TabsEventRouter::RegisterForBrowserNotifications(Browser* browser) {
152 if (!profile_->IsSameProfile(browser->profile()) ||
153 !ExtensionTabUtil::BrowserSupportsTabs(browser))
154 return;
155 // Start listening to TabStripModel events for this browser.
156 TabStripModel* tab_strip = browser->tab_strip_model();
157 tab_strip->AddObserver(this);
159 for (int i = 0; i < tab_strip->count(); ++i) {
160 RegisterForTabNotifications(tab_strip->GetWebContentsAt(i));
164 void TabsEventRouter::RegisterForTabNotifications(WebContents* contents) {
165 registrar_.Add(
166 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
167 content::Source<NavigationController>(&contents->GetController()));
169 // Observing NOTIFICATION_WEB_CONTENTS_DESTROYED is necessary because it's
170 // possible for tabs to be created, detached and then destroyed without
171 // ever having been re-attached and closed. This happens in the case of
172 // a devtools WebContents that is opened in window, docked, then closed.
173 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
174 content::Source<WebContents>(contents));
176 favicon_scoped_observer_.Add(
177 favicon::ContentFaviconDriver::FromWebContents(contents));
179 ZoomController::FromWebContents(contents)->AddObserver(this);
182 void TabsEventRouter::UnregisterForTabNotifications(WebContents* contents) {
183 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
184 content::Source<NavigationController>(&contents->GetController()));
185 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
186 content::Source<WebContents>(contents));
187 favicon_scoped_observer_.Remove(
188 favicon::ContentFaviconDriver::FromWebContents(contents));
190 ZoomController::FromWebContents(contents)->RemoveObserver(this);
193 void TabsEventRouter::OnBrowserRemoved(Browser* browser) {
194 if (!profile_->IsSameProfile(browser->profile()))
195 return;
197 // Stop listening to TabStripModel events for this browser.
198 browser->tab_strip_model()->RemoveObserver(this);
201 void TabsEventRouter::OnBrowserSetLastActive(Browser* browser) {
202 TabsWindowsAPI* tabs_window_api = TabsWindowsAPI::Get(profile_);
203 if (tabs_window_api) {
204 tabs_window_api->windows_event_router()->OnActiveWindowChanged(
205 browser ? browser->extension_window_controller() : NULL);
209 static bool WillDispatchTabCreatedEvent(
210 WebContents* contents,
211 bool active,
212 content::BrowserContext* context,
213 const Extension* extension,
214 Event* event,
215 const base::DictionaryValue* listener_filter) {
216 base::DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue(
217 contents, extension);
218 event->event_args->Clear();
219 event->event_args->Append(tab_value);
220 tab_value->SetBoolean(tabs_constants::kSelectedKey, active);
221 return true;
224 void TabsEventRouter::TabCreatedAt(WebContents* contents,
225 int index,
226 bool active) {
227 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
228 scoped_ptr<base::ListValue> args(new base::ListValue);
229 scoped_ptr<Event> event(new Event(events::TABS_ON_CREATED,
230 tabs::OnCreated::kEventName, args.Pass()));
231 event->restrict_to_browser_context = profile;
232 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
233 event->will_dispatch_callback =
234 base::Bind(&WillDispatchTabCreatedEvent, contents, active);
235 EventRouter::Get(profile)->BroadcastEvent(event.Pass());
237 RegisterForTabNotifications(contents);
240 void TabsEventRouter::TabInsertedAt(WebContents* contents,
241 int index,
242 bool active) {
243 // If tab is new, send created event.
244 int tab_id = ExtensionTabUtil::GetTabId(contents);
245 if (GetTabEntry(contents).get() == NULL) {
246 tab_entries_[tab_id] = make_linked_ptr(new TabEntry(contents));
248 TabCreatedAt(contents, index, active);
249 return;
252 scoped_ptr<base::ListValue> args(new base::ListValue);
253 args->Append(new FundamentalValue(tab_id));
255 base::DictionaryValue* object_args = new base::DictionaryValue();
256 object_args->Set(tabs_constants::kNewWindowIdKey,
257 new FundamentalValue(
258 ExtensionTabUtil::GetWindowIdOfTab(contents)));
259 object_args->Set(tabs_constants::kNewPositionKey,
260 new FundamentalValue(index));
261 args->Append(object_args);
263 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
264 DispatchEvent(profile, events::TABS_ON_ATTACHED, tabs::OnAttached::kEventName,
265 args.Pass(), EventRouter::USER_GESTURE_UNKNOWN);
268 void TabsEventRouter::TabDetachedAt(WebContents* contents, int index) {
269 if (GetTabEntry(contents).get() == NULL) {
270 // The tab was removed. Don't send detach event.
271 return;
274 scoped_ptr<base::ListValue> args(new base::ListValue);
275 args->Append(
276 new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
278 base::DictionaryValue* object_args = new base::DictionaryValue();
279 object_args->Set(tabs_constants::kOldWindowIdKey,
280 new FundamentalValue(
281 ExtensionTabUtil::GetWindowIdOfTab(contents)));
282 object_args->Set(tabs_constants::kOldPositionKey,
283 new FundamentalValue(index));
284 args->Append(object_args);
286 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
287 DispatchEvent(profile, events::TABS_ON_DETACHED, tabs::OnDetached::kEventName,
288 args.Pass(), EventRouter::USER_GESTURE_UNKNOWN);
291 void TabsEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
292 WebContents* contents,
293 int index) {
294 int tab_id = ExtensionTabUtil::GetTabId(contents);
296 scoped_ptr<base::ListValue> args(new base::ListValue);
297 args->Append(new FundamentalValue(tab_id));
299 base::DictionaryValue* object_args = new base::DictionaryValue();
300 object_args->SetInteger(tabs_constants::kWindowIdKey,
301 ExtensionTabUtil::GetWindowIdOfTab(contents));
302 object_args->SetBoolean(tabs_constants::kWindowClosing,
303 tab_strip_model->closing_all());
304 args->Append(object_args);
306 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
307 DispatchEvent(profile, events::TABS_ON_REMOVED, tabs::OnRemoved::kEventName,
308 args.Pass(), EventRouter::USER_GESTURE_UNKNOWN);
310 int removed_count = tab_entries_.erase(tab_id);
311 DCHECK_GT(removed_count, 0);
313 UnregisterForTabNotifications(contents);
316 void TabsEventRouter::ActiveTabChanged(WebContents* old_contents,
317 WebContents* new_contents,
318 int index,
319 int reason) {
320 scoped_ptr<base::ListValue> args(new base::ListValue);
321 int tab_id = ExtensionTabUtil::GetTabId(new_contents);
322 args->Append(new FundamentalValue(tab_id));
324 base::DictionaryValue* object_args = new base::DictionaryValue();
325 object_args->Set(tabs_constants::kWindowIdKey,
326 new FundamentalValue(
327 ExtensionTabUtil::GetWindowIdOfTab(new_contents)));
328 args->Append(object_args);
330 // The onActivated event replaced onActiveChanged and onSelectionChanged. The
331 // deprecated events take two arguments: tabId, {windowId}.
332 Profile* profile =
333 Profile::FromBrowserContext(new_contents->GetBrowserContext());
334 EventRouter::UserGestureState gesture =
335 reason & CHANGE_REASON_USER_GESTURE
336 ? EventRouter::USER_GESTURE_ENABLED
337 : EventRouter::USER_GESTURE_NOT_ENABLED;
338 DispatchEvent(profile, events::TABS_ON_SELECTION_CHANGED,
339 tabs::OnSelectionChanged::kEventName,
340 scoped_ptr<base::ListValue>(args->DeepCopy()), gesture);
341 DispatchEvent(profile, events::TABS_ON_ACTIVE_CHANGED,
342 tabs::OnActiveChanged::kEventName,
343 scoped_ptr<base::ListValue>(args->DeepCopy()), gesture);
345 // The onActivated event takes one argument: {windowId, tabId}.
346 args->Remove(0, NULL);
347 object_args->Set(tabs_constants::kTabIdKey,
348 new FundamentalValue(tab_id));
349 DispatchEvent(profile, events::TABS_ON_ACTIVATED,
350 tabs::OnActivated::kEventName, args.Pass(), gesture);
353 void TabsEventRouter::TabSelectionChanged(
354 TabStripModel* tab_strip_model,
355 const ui::ListSelectionModel& old_model) {
356 ui::ListSelectionModel::SelectedIndices new_selection =
357 tab_strip_model->selection_model().selected_indices();
358 scoped_ptr<base::ListValue> all_tabs(new base::ListValue);
360 for (size_t i = 0; i < new_selection.size(); ++i) {
361 int index = new_selection[i];
362 WebContents* contents = tab_strip_model->GetWebContentsAt(index);
363 if (!contents)
364 break;
365 int tab_id = ExtensionTabUtil::GetTabId(contents);
366 all_tabs->Append(new FundamentalValue(tab_id));
369 scoped_ptr<base::ListValue> args(new base::ListValue);
370 scoped_ptr<base::DictionaryValue> select_info(new base::DictionaryValue);
372 select_info->Set(
373 tabs_constants::kWindowIdKey,
374 new FundamentalValue(
375 ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model)));
377 select_info->Set(tabs_constants::kTabIdsKey, all_tabs.release());
378 args->Append(select_info.release());
380 // The onHighlighted event replaced onHighlightChanged.
381 Profile* profile = tab_strip_model->profile();
382 DispatchEvent(profile, events::TABS_ON_HIGHLIGHT_CHANGED,
383 tabs::OnHighlightChanged::kEventName,
384 scoped_ptr<base::ListValue>(args->DeepCopy()),
385 EventRouter::USER_GESTURE_UNKNOWN);
386 DispatchEvent(profile, events::TABS_ON_HIGHLIGHTED,
387 tabs::OnHighlighted::kEventName, args.Pass(),
388 EventRouter::USER_GESTURE_UNKNOWN);
391 void TabsEventRouter::TabMoved(WebContents* contents,
392 int from_index,
393 int to_index) {
394 scoped_ptr<base::ListValue> args(new base::ListValue);
395 args->Append(
396 new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
398 base::DictionaryValue* object_args = new base::DictionaryValue();
399 object_args->Set(tabs_constants::kWindowIdKey,
400 new FundamentalValue(
401 ExtensionTabUtil::GetWindowIdOfTab(contents)));
402 object_args->Set(tabs_constants::kFromIndexKey,
403 new FundamentalValue(from_index));
404 object_args->Set(tabs_constants::kToIndexKey,
405 new FundamentalValue(to_index));
406 args->Append(object_args);
408 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
409 DispatchEvent(profile, events::TABS_ON_MOVED, tabs::OnMoved::kEventName,
410 args.Pass(), EventRouter::USER_GESTURE_UNKNOWN);
413 void TabsEventRouter::TabUpdated(
414 linked_ptr<TabEntry> entry,
415 scoped_ptr<base::DictionaryValue> changed_properties) {
416 CHECK(entry->web_contents());
418 bool audible = entry->web_contents()->WasRecentlyAudible();
419 if (entry->SetAudible(audible)) {
420 changed_properties->SetBoolean(tabs_constants::kAudibleKey, audible);
423 bool muted = entry->web_contents()->IsAudioMuted();
424 if (entry->SetMuted(muted)) {
425 changed_properties->Set(
426 tabs_constants::kMutedInfoKey,
427 ExtensionTabUtil::CreateMutedInfo(entry->web_contents()).Pass());
430 if (!changed_properties->empty()) {
431 DispatchTabUpdatedEvent(entry->web_contents(), changed_properties.Pass());
435 void TabsEventRouter::FaviconUrlUpdated(WebContents* contents) {
436 content::NavigationEntry* entry =
437 contents->GetController().GetVisibleEntry();
438 if (!entry || !entry->GetFavicon().valid)
439 return;
440 scoped_ptr<base::DictionaryValue> changed_properties(
441 new base::DictionaryValue);
442 changed_properties->SetString(
443 tabs_constants::kFaviconUrlKey,
444 entry->GetFavicon().url.possibly_invalid_spec());
445 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
448 void TabsEventRouter::DispatchEvent(
449 Profile* profile,
450 events::HistogramValue histogram_value,
451 const std::string& event_name,
452 scoped_ptr<base::ListValue> args,
453 EventRouter::UserGestureState user_gesture) {
454 EventRouter* event_router = EventRouter::Get(profile);
455 if (!profile_->IsSameProfile(profile) || !event_router)
456 return;
458 scoped_ptr<Event> event(new Event(histogram_value, event_name, args.Pass()));
459 event->restrict_to_browser_context = profile;
460 event->user_gesture = user_gesture;
461 event_router->BroadcastEvent(event.Pass());
464 void TabsEventRouter::DispatchTabUpdatedEvent(
465 WebContents* contents,
466 scoped_ptr<base::DictionaryValue> changed_properties) {
467 DCHECK(changed_properties);
468 DCHECK(contents);
470 // The state of the tab (as seen from the extension point of view) has
471 // changed. Send a notification to the extension.
472 scoped_ptr<base::ListValue> args_base(new base::ListValue);
474 // First arg: The id of the tab that changed.
475 args_base->AppendInteger(ExtensionTabUtil::GetTabId(contents));
477 // Second arg: An object containing the changes to the tab state. Filled in
478 // by WillDispatchTabUpdatedEvent as a copy of changed_properties, if the
479 // extension has the tabs permission.
481 // Third arg: An object containing the state of the tab. Filled in by
482 // WillDispatchTabUpdatedEvent.
483 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
485 scoped_ptr<Event> event(new Event(
486 events::TABS_ON_UPDATED, tabs::OnUpdated::kEventName, args_base.Pass()));
487 event->restrict_to_browser_context = profile;
488 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED;
489 event->will_dispatch_callback =
490 base::Bind(&WillDispatchTabUpdatedEvent,
491 contents,
492 changed_properties.get());
493 EventRouter::Get(profile)->BroadcastEvent(event.Pass());
496 linked_ptr<TabsEventRouter::TabEntry> TabsEventRouter::GetTabEntry(
497 WebContents* contents) {
498 int tab_id = ExtensionTabUtil::GetTabId(contents);
500 TabEntryMap::iterator i = tab_entries_.find(tab_id);
501 if (tab_entries_.end() == i)
502 return linked_ptr<TabEntry>(NULL);
503 return i->second;
506 void TabsEventRouter::Observe(int type,
507 const content::NotificationSource& source,
508 const content::NotificationDetails& details) {
509 if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) {
510 NavigationController* source_controller =
511 content::Source<NavigationController>(source).ptr();
512 linked_ptr<TabEntry> entry =
513 GetTabEntry(source_controller->GetWebContents());
514 CHECK(entry.get());
515 TabUpdated(entry, (entry.get())->DidNavigate());
516 } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
517 // Tab was destroyed after being detached (without being re-attached).
518 WebContents* contents = content::Source<WebContents>(source).ptr();
519 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
520 content::Source<NavigationController>(&contents->GetController()));
521 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
522 content::Source<WebContents>(contents));
523 favicon_scoped_observer_.Remove(
524 favicon::ContentFaviconDriver::FromWebContents(contents));
525 } else {
526 NOTREACHED();
530 void TabsEventRouter::TabChangedAt(WebContents* contents,
531 int index,
532 TabChangeType change_type) {
533 linked_ptr<TabEntry> entry = GetTabEntry(contents);
534 CHECK(entry.get());
535 TabUpdated(entry, (entry.get())->UpdateLoadState());
538 void TabsEventRouter::TabReplacedAt(TabStripModel* tab_strip_model,
539 WebContents* old_contents,
540 WebContents* new_contents,
541 int index) {
542 // Notify listeners that the next tabs closing or being added are due to
543 // WebContents being swapped.
544 const int new_tab_id = ExtensionTabUtil::GetTabId(new_contents);
545 const int old_tab_id = ExtensionTabUtil::GetTabId(old_contents);
546 scoped_ptr<base::ListValue> args(new base::ListValue);
547 args->Append(new FundamentalValue(new_tab_id));
548 args->Append(new FundamentalValue(old_tab_id));
550 DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()),
551 events::TABS_ON_REPLACED, tabs::OnReplaced::kEventName,
552 args.Pass(), EventRouter::USER_GESTURE_UNKNOWN);
554 // Update tab_entries_.
555 const int removed_count = tab_entries_.erase(old_tab_id);
556 DCHECK_GT(removed_count, 0);
557 UnregisterForTabNotifications(old_contents);
559 if (GetTabEntry(new_contents).get() == NULL) {
560 tab_entries_[new_tab_id] = make_linked_ptr(new TabEntry(new_contents));
561 RegisterForTabNotifications(new_contents);
565 void TabsEventRouter::TabPinnedStateChanged(WebContents* contents, int index) {
566 TabStripModel* tab_strip = NULL;
567 int tab_index;
569 if (ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index)) {
570 scoped_ptr<base::DictionaryValue> changed_properties(
571 new base::DictionaryValue());
572 changed_properties->SetBoolean(tabs_constants::kPinnedKey,
573 tab_strip->IsTabPinned(tab_index));
574 DispatchTabUpdatedEvent(contents, changed_properties.Pass());
578 void TabsEventRouter::OnZoomChanged(
579 const ZoomController::ZoomChangedEventData& data) {
580 DCHECK(data.web_contents);
581 int tab_id = ExtensionTabUtil::GetTabId(data.web_contents);
582 if (tab_id < 0)
583 return;
585 // Prepare the zoom change information.
586 api::tabs::OnZoomChange::ZoomChangeInfo zoom_change_info;
587 zoom_change_info.tab_id = tab_id;
588 zoom_change_info.old_zoom_factor =
589 content::ZoomLevelToZoomFactor(data.old_zoom_level);
590 zoom_change_info.new_zoom_factor =
591 content::ZoomLevelToZoomFactor(data.new_zoom_level);
592 ZoomModeToZoomSettings(data.zoom_mode,
593 &zoom_change_info.zoom_settings);
595 // Dispatch the |onZoomChange| event.
596 Profile* profile = Profile::FromBrowserContext(
597 data.web_contents->GetBrowserContext());
598 DispatchEvent(profile, events::TABS_ON_ZOOM_CHANGE,
599 tabs::OnZoomChange::kEventName,
600 api::tabs::OnZoomChange::Create(zoom_change_info),
601 EventRouter::USER_GESTURE_UNKNOWN);
604 void TabsEventRouter::OnFaviconAvailable(const gfx::Image& image) {
607 void TabsEventRouter::OnFaviconUpdated(favicon::FaviconDriver* favicon_driver,
608 bool icon_url_changed) {
609 if (icon_url_changed) {
610 favicon::ContentFaviconDriver* content_favicon_driver =
611 static_cast<favicon::ContentFaviconDriver*>(favicon_driver);
612 FaviconUrlUpdated(content_favicon_driver->web_contents());
616 } // namespace extensions