Home | History | Annotate | Download | only in tabs
      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
    586