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
{
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
,
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
,
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
));
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
)
109 was_audible_
= new_val
;
113 bool TabsEventRouter::TabEntry::SetMuted(bool new_val
) {
114 if (was_muted_
== new_val
)
116 was_muted_
= new_val
;
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
))
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
) {
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()))
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
,
212 content::BrowserContext
* context
,
213 const Extension
* extension
,
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
);
224 void TabsEventRouter::TabCreatedAt(WebContents
* contents
,
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
,
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
);
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.
274 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
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
,
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
,
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}.
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
);
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
);
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
,
394 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
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
)
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(
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
)
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
);
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
,
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
);
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());
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
));
530 void TabsEventRouter::TabChangedAt(WebContents
* contents
,
532 TabChangeType change_type
) {
533 linked_ptr
<TabEntry
> entry
= GetTabEntry(contents
);
535 TabUpdated(entry
, (entry
.get())->UpdateLoadState());
538 void TabsEventRouter::TabReplacedAt(TabStripModel
* tab_strip_model
,
539 WebContents
* old_contents
,
540 WebContents
* new_contents
,
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
;
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
);
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