Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / tabs / tabs_event_router.cc
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
27 using base::DictionaryValue;
28 using base::ListValue;
29 using base::FundamentalValue;
30 using content::NavigationController;
31 using content::WebContents;
32
33 namespace extensions {
34
35 namespace {
36
37 namespace tabs = api::tabs;
38
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,
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
59 TabsEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false),
60                                         url_() {
61 }
62
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())
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
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);
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
95 TabsEventRouter::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
117 TabsEventRouter::~TabsEventRouter() {
118   BrowserList::RemoveObserver(this);
119 }
120
121 void TabsEventRouter::OnBrowserAdded(Browser* browser) {
122   RegisterForBrowserNotifications(browser);
123 }
124
125 void 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
137 void 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   ZoomController::FromWebContents(contents)->AddObserver(this);
153 }
154
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));
162
163   ZoomController::FromWebContents(contents)->RemoveObserver(this);
164 }
165
166 void TabsEventRouter::OnBrowserRemoved(Browser* browser) {
167   if (!profile_->IsSameProfile(browser->profile()))
168     return;
169
170   // Stop listening to TabStripModel events for this browser.
171   browser->tab_strip_model()->RemoveObserver(this);
172 }
173
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);
179   }
180 }
181
182 static void WillDispatchTabCreatedEvent(WebContents* contents,
183                                         bool active,
184                                         content::BrowserContext* context,
185                                         const Extension* extension,
186                                         base::ListValue* event_args) {
187   base::DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue(
188       contents, extension);
189   event_args->Clear();
190   event_args->Append(tab_value);
191   tab_value->SetBoolean(tabs_constants::kSelectedKey, active);
192 }
193
194 void TabsEventRouter::TabCreatedAt(WebContents* contents,
195                                    int index,
196                                    bool active) {
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());
205
206   RegisterForTabNotifications(contents);
207 }
208
209 void TabsEventRouter::TabInsertedAt(WebContents* contents,
210                                     int index,
211                                     bool active) {
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();
216
217     TabCreatedAt(contents, index, active);
218     return;
219   }
220
221   scoped_ptr<base::ListValue> args(new base::ListValue);
222   args->Append(new FundamentalValue(tab_id));
223
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);
231
232   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
233   DispatchEvent(profile, tabs::OnAttached::kEventName, args.Pass(),
234                 EventRouter::USER_GESTURE_UNKNOWN);
235 }
236
237 void TabsEventRouter::TabDetachedAt(WebContents* contents, int index) {
238   if (!GetTabEntry(contents)) {
239     // The tab was removed. Don't send detach event.
240     return;
241   }
242
243   scoped_ptr<base::ListValue> args(new base::ListValue);
244   args->Append(
245       new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
246
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);
254
255   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
256   DispatchEvent(profile,
257                 tabs::OnDetached::kEventName,
258                 args.Pass(),
259                 EventRouter::USER_GESTURE_UNKNOWN);
260 }
261
262 void TabsEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
263                                    WebContents* contents,
264                                    int index) {
265   int tab_id = ExtensionTabUtil::GetTabId(contents);
266
267   scoped_ptr<base::ListValue> args(new base::ListValue);
268   args->Append(new FundamentalValue(tab_id));
269
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);
276
277   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
278   DispatchEvent(profile,
279                 tabs::OnRemoved::kEventName,
280                 args.Pass(),
281                 EventRouter::USER_GESTURE_UNKNOWN);
282
283   int removed_count = tab_entries_.erase(tab_id);
284   DCHECK_GT(removed_count, 0);
285
286   UnregisterForTabNotifications(contents);
287 }
288
289 void TabsEventRouter::ActiveTabChanged(WebContents* old_contents,
290                                        WebContents* new_contents,
291                                        int index,
292                                        int reason) {
293   scoped_ptr<base::ListValue> args(new base::ListValue);
294   int tab_id = ExtensionTabUtil::GetTabId(new_contents);
295   args->Append(new FundamentalValue(tab_id));
296
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);
302
303   // The onActivated event replaced onActiveChanged and onSelectionChanged. The
304   // deprecated events take two arguments: tabId, {windowId}.
305   Profile* profile =
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()),
314                 gesture);
315   DispatchEvent(profile,
316                 tabs::OnActiveChanged::kEventName,
317                 scoped_ptr<base::ListValue>(args->DeepCopy()),
318                 gesture);
319
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);
325 }
326
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);
333
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);
337     if (!contents)
338       break;
339     int tab_id = ExtensionTabUtil::GetTabId(contents);
340     all_tabs->Append(new FundamentalValue(tab_id));
341   }
342
343   scoped_ptr<base::ListValue> args(new base::ListValue);
344   scoped_ptr<base::DictionaryValue> select_info(new base::DictionaryValue);
345
346   select_info->Set(
347       tabs_constants::kWindowIdKey,
348       new FundamentalValue(
349           ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model)));
350
351   select_info->Set(tabs_constants::kTabIdsKey, all_tabs.release());
352   args->Append(select_info.release());
353
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,
362                 args.Pass(),
363                 EventRouter::USER_GESTURE_UNKNOWN);
364 }
365
366 void TabsEventRouter::TabMoved(WebContents* contents,
367                                int from_index,
368                                int to_index) {
369   scoped_ptr<base::ListValue> args(new base::ListValue);
370   args->Append(
371       new FundamentalValue(ExtensionTabUtil::GetTabId(contents)));
372
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);
382
383   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
384   DispatchEvent(profile,
385                 tabs::OnMoved::kEventName,
386                 args.Pass(),
387                 EventRouter::USER_GESTURE_UNKNOWN);
388 }
389
390 void TabsEventRouter::TabUpdated(WebContents* contents, bool did_navigate) {
391   TabEntry* entry = GetTabEntry(contents);
392   scoped_ptr<base::DictionaryValue> changed_properties;
393
394   CHECK(entry);
395
396   if (did_navigate)
397     changed_properties.reset(entry->DidNavigate(contents));
398   else
399     changed_properties.reset(entry->UpdateLoadState(contents));
400
401   if (changed_properties)
402     DispatchTabUpdatedEvent(contents, changed_properties.Pass());
403 }
404
405 void TabsEventRouter::FaviconUrlUpdated(WebContents* contents) {
406     content::NavigationEntry* entry =
407         contents->GetController().GetVisibleEntry();
408     if (!entry || !entry->GetFavicon().valid)
409       return;
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());
416 }
417
418 void TabsEventRouter::DispatchEvent(
419     Profile* profile,
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)
425     return;
426
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());
431 }
432
433 void TabsEventRouter::DispatchSimpleBrowserEvent(
434     Profile* profile, const int window_id, const std::string& event_name) {
435   if (!profile_->IsSameProfile(profile))
436     return;
437
438   scoped_ptr<base::ListValue> args(new base::ListValue);
439   args->Append(new FundamentalValue(window_id));
440
441   DispatchEvent(profile,
442                 event_name,
443                 args.Pass(),
444                 EventRouter::USER_GESTURE_UNKNOWN);
445 }
446
447 void TabsEventRouter::DispatchTabUpdatedEvent(
448     WebContents* contents,
449     scoped_ptr<base::DictionaryValue> changed_properties) {
450   DCHECK(changed_properties);
451   DCHECK(contents);
452
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);
456
457   // First arg: The id of the tab that changed.
458   args_base->AppendInteger(ExtensionTabUtil::GetTabId(contents));
459
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.
463
464   // Third arg: An object containing the state of the tab. Filled in by
465   // WillDispatchTabUpdatedEvent.
466   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
467
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,
474                  contents,
475                  changed_properties.get());
476   EventRouter::Get(profile)->BroadcastEvent(event.Pass());
477 }
478
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)
483     return NULL;
484   return &i->second;
485 }
486
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());
507   } else {
508     NOTREACHED();
509   }
510 }
511
512 void TabsEventRouter::TabChangedAt(WebContents* contents,
513                                    int index,
514                                    TabChangeType change_type) {
515   TabUpdated(contents, false);
516 }
517
518 void TabsEventRouter::TabReplacedAt(TabStripModel* tab_strip_model,
519                                     WebContents* old_contents,
520                                     WebContents* new_contents,
521                                     int index) {
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));
529
530   DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()),
531                 tabs::OnReplaced::kEventName,
532                 args.Pass(),
533                 EventRouter::USER_GESTURE_UNKNOWN);
534
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);
539
540   if (!GetTabEntry(new_contents)) {
541     tab_entries_[new_tab_id] = TabEntry();
542     RegisterForTabNotifications(new_contents);
543   }
544 }
545
546 void TabsEventRouter::TabPinnedStateChanged(WebContents* contents, int index) {
547   TabStripModel* tab_strip = NULL;
548   int tab_index;
549
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());
556   }
557 }
558
559 void TabsEventRouter::OnZoomChanged(
560     const ZoomController::ZoomChangedEventData& data) {
561   DCHECK(data.web_contents);
562   int tab_id = ExtensionTabUtil::GetTabId(data.web_contents);
563   if (tab_id < 0)
564     return;
565
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);
575
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);
583 }
584
585 }  // namespace extensions