Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 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/extension_browser_event_router.h"
      6 
      7 #include "base/json/json_writer.h"
      8 #include "base/values.h"
      9 #include "chrome/browser/extensions/extension_event_names.h"
     10 #include "chrome/browser/extensions/extension_event_router.h"
     11 #include "chrome/browser/extensions/extension_page_actions_module_constants.h"
     12 #include "chrome/browser/extensions/extension_tabs_module_constants.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/tabs/tab_strip_model.h"
     15 #include "chrome/browser/ui/browser.h"
     16 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     17 #include "chrome/common/extensions/extension.h"
     18 #include "chrome/common/extensions/extension_constants.h"
     19 #include "content/browser/tab_contents/navigation_entry.h"
     20 #include "content/browser/tab_contents/tab_contents.h"
     21 #include "content/common/notification_service.h"
     22 
     23 namespace events = extension_event_names;
     24 namespace tab_keys = extension_tabs_module_constants;
     25 namespace page_action_keys = extension_page_actions_module_constants;
     26 
     27 ExtensionBrowserEventRouter::TabEntry::TabEntry()
     28     : complete_waiting_on_load_(false),
     29       url_() {
     30 }
     31 
     32 DictionaryValue* ExtensionBrowserEventRouter::TabEntry::UpdateLoadState(
     33     const TabContents* contents) {
     34   // The tab may go in & out of loading (for instance if iframes navigate).
     35   // We only want to respond to the first change from loading to !loading after
     36   // the NAV_ENTRY_COMMITTED was fired.
     37   if (!complete_waiting_on_load_ || contents->is_loading())
     38     return NULL;
     39 
     40   // Send "complete" state change.
     41   complete_waiting_on_load_ = false;
     42   DictionaryValue* changed_properties = new DictionaryValue();
     43   changed_properties->SetString(tab_keys::kStatusKey,
     44       tab_keys::kStatusValueComplete);
     45   return changed_properties;
     46 }
     47 
     48 DictionaryValue* ExtensionBrowserEventRouter::TabEntry::DidNavigate(
     49     const TabContents* contents) {
     50   // Send "loading" state change.
     51   complete_waiting_on_load_ = true;
     52   DictionaryValue* changed_properties = new DictionaryValue();
     53   changed_properties->SetString(tab_keys::kStatusKey,
     54       tab_keys::kStatusValueLoading);
     55 
     56   if (contents->GetURL() != url_) {
     57     url_ = contents->GetURL();
     58     changed_properties->SetString(tab_keys::kUrlKey, url_.spec());
     59   }
     60 
     61   return changed_properties;
     62 }
     63 
     64 static void DispatchEvent(Profile* profile,
     65                           const char* event_name,
     66                           const std::string& json_args) {
     67   if (profile->GetExtensionEventRouter()) {
     68     profile->GetExtensionEventRouter()->DispatchEventToRenderers(
     69         event_name, json_args, profile, GURL());
     70   }
     71 }
     72 
     73 static void DispatchEventToExtension(Profile* profile,
     74                                      const std::string& extension_id,
     75                                      const char* event_name,
     76                                      const std::string& json_args) {
     77   if (profile->GetExtensionEventRouter()) {
     78     profile->GetExtensionEventRouter()->DispatchEventToExtension(
     79         extension_id, event_name, json_args, profile, GURL());
     80   }
     81 }
     82 
     83 static void DispatchEventWithTab(Profile* profile,
     84                                  const std::string& extension_id,
     85                                  const char* event_name,
     86                                  const TabContents* tab_contents) {
     87   ListValue args;
     88   args.Append(ExtensionTabUtil::CreateTabValue(tab_contents));
     89   std::string json_args;
     90   base::JSONWriter::Write(&args, false, &json_args);
     91   if (!extension_id.empty()) {
     92     DispatchEventToExtension(profile, extension_id, event_name, json_args);
     93   } else {
     94     DispatchEvent(profile, event_name, json_args);
     95   }
     96 }
     97 
     98 static void DispatchSimpleBrowserEvent(Profile* profile,
     99                                        const int window_id,
    100                                        const char* event_name) {
    101   ListValue args;
    102   args.Append(Value::CreateIntegerValue(window_id));
    103 
    104   std::string json_args;
    105   base::JSONWriter::Write(&args, false, &json_args);
    106 
    107   DispatchEvent(profile, event_name, json_args);
    108 }
    109 
    110 void ExtensionBrowserEventRouter::Init() {
    111   if (initialized_)
    112     return;
    113   BrowserList::AddObserver(this);
    114 #if defined(TOOLKIT_VIEWS)
    115   views::FocusManager::GetWidgetFocusManager()->AddFocusChangeListener(this);
    116 #elif defined(TOOLKIT_GTK)
    117   ui::ActiveWindowWatcherX::AddObserver(this);
    118 #elif defined(OS_MACOSX)
    119   // Needed for when no suitable window can be passed to an extension as the
    120   // currently focused window.
    121   registrar_.Add(this, NotificationType::NO_KEY_WINDOW,
    122                  NotificationService::AllSources());
    123 #endif
    124 
    125   // Init() can happen after the browser is running, so catch up with any
    126   // windows that already exist.
    127   for (BrowserList::const_iterator iter = BrowserList::begin();
    128        iter != BrowserList::end(); ++iter) {
    129     RegisterForBrowserNotifications(*iter);
    130 
    131     // Also catch up our internal bookkeeping of tab entries.
    132     Browser* browser = *iter;
    133     if (browser->tabstrip_model()) {
    134       for (int i = 0; i < browser->tabstrip_model()->count(); ++i) {
    135         TabContents* contents = browser->GetTabContentsAt(i);
    136         int tab_id = ExtensionTabUtil::GetTabId(contents);
    137         tab_entries_[tab_id] = TabEntry();
    138       }
    139     }
    140   }
    141 
    142   initialized_ = true;
    143 }
    144 
    145 ExtensionBrowserEventRouter::ExtensionBrowserEventRouter(Profile* profile)
    146     : initialized_(false),
    147       focused_window_id_(extension_misc::kUnknownWindowId),
    148       profile_(profile) {
    149   DCHECK(!profile->IsOffTheRecord());
    150 }
    151 
    152 ExtensionBrowserEventRouter::~ExtensionBrowserEventRouter() {
    153   BrowserList::RemoveObserver(this);
    154 #if defined(TOOLKIT_VIEWS)
    155   views::FocusManager::GetWidgetFocusManager()->RemoveFocusChangeListener(this);
    156 #elif defined(TOOLKIT_GTK)
    157   ui::ActiveWindowWatcherX::RemoveObserver(this);
    158 #endif
    159 }
    160 
    161 void ExtensionBrowserEventRouter::OnBrowserAdded(const Browser* browser) {
    162   RegisterForBrowserNotifications(browser);
    163 }
    164 
    165 void ExtensionBrowserEventRouter::RegisterForBrowserNotifications(
    166     const Browser* browser) {
    167   // Start listening to TabStripModel events for this browser.
    168   browser->tabstrip_model()->AddObserver(this);
    169 
    170   // If this is a new window, it isn't ready at this point, so we register to be
    171   // notified when it is. If this is an existing window, this is a no-op that we
    172   // just do to reduce code complexity.
    173   registrar_.Add(this, NotificationType::BROWSER_WINDOW_READY,
    174       Source<const Browser>(browser));
    175 
    176   if (browser->tabstrip_model()) {
    177     for (int i = 0; i < browser->tabstrip_model()->count(); ++i)
    178       RegisterForTabNotifications(browser->GetTabContentsAt(i));
    179   }
    180 }
    181 
    182 void ExtensionBrowserEventRouter::RegisterForTabNotifications(
    183     TabContents* contents) {
    184   registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
    185                  Source<NavigationController>(&contents->controller()));
    186 
    187   // Observing TAB_CONTENTS_DESTROYED is necessary because it's
    188   // possible for tabs to be created, detached and then destroyed without
    189   // ever having been re-attached and closed. This happens in the case of
    190   // a devtools TabContents that is opened in window, docked, then closed.
    191   registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
    192                  Source<TabContents>(contents));
    193 }
    194 
    195 void ExtensionBrowserEventRouter::UnregisterForTabNotifications(
    196     TabContents* contents) {
    197   registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
    198       Source<NavigationController>(&contents->controller()));
    199   registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
    200       Source<TabContents>(contents));
    201 }
    202 
    203 void ExtensionBrowserEventRouter::OnBrowserWindowReady(const Browser* browser) {
    204   ListValue args;
    205 
    206   DictionaryValue* window_dictionary = ExtensionTabUtil::CreateWindowValue(
    207       browser, false);
    208   args.Append(window_dictionary);
    209 
    210   std::string json_args;
    211   base::JSONWriter::Write(&args, false, &json_args);
    212 
    213   DispatchEvent(browser->profile(), events::kOnWindowCreated, json_args);
    214 }
    215 
    216 void ExtensionBrowserEventRouter::OnBrowserRemoved(const Browser* browser) {
    217   // Stop listening to TabStripModel events for this browser.
    218   browser->tabstrip_model()->RemoveObserver(this);
    219 
    220   registrar_.Remove(this, NotificationType::BROWSER_WINDOW_READY,
    221       Source<const Browser>(browser));
    222 
    223   DispatchSimpleBrowserEvent(browser->profile(),
    224                              ExtensionTabUtil::GetWindowId(browser),
    225                              events::kOnWindowRemoved);
    226 }
    227 
    228 #if defined(TOOLKIT_VIEWS)
    229 void ExtensionBrowserEventRouter::NativeFocusWillChange(
    230     gfx::NativeView focused_before,
    231     gfx::NativeView focused_now) {
    232   if (!focused_now)
    233     OnBrowserSetLastActive(NULL);
    234 }
    235 #elif defined(TOOLKIT_GTK)
    236 void ExtensionBrowserEventRouter::ActiveWindowChanged(
    237     GdkWindow* active_window) {
    238   if (!active_window)
    239     OnBrowserSetLastActive(NULL);
    240 }
    241 #endif
    242 
    243 void ExtensionBrowserEventRouter::OnBrowserSetLastActive(
    244     const Browser* browser) {
    245   int window_id = extension_misc::kUnknownWindowId;
    246   if (browser)
    247     window_id = ExtensionTabUtil::GetWindowId(browser);
    248 
    249   if (focused_window_id_ == window_id)
    250     return;
    251 
    252   focused_window_id_ = window_id;
    253   // Note: because we use the default profile when |browser| is NULL, it means
    254   // that all extensions hear about the event regardless of whether the browser
    255   // that lost focus was OTR or if the extension is OTR-enabled.
    256   // See crbug.com/46610.
    257   DispatchSimpleBrowserEvent(browser ? browser->profile() : profile_,
    258                              focused_window_id_,
    259                              events::kOnWindowFocusedChanged);
    260 }
    261 
    262 void ExtensionBrowserEventRouter::TabCreatedAt(TabContents* contents,
    263                                                int index,
    264                                                bool foreground) {
    265   DispatchEventWithTab(contents->profile(), "", events::kOnTabCreated,
    266                        contents);
    267 
    268   RegisterForTabNotifications(contents);
    269 }
    270 
    271 void ExtensionBrowserEventRouter::TabInsertedAt(TabContentsWrapper* contents,
    272                                                 int index,
    273                                                 bool foreground) {
    274   // If tab is new, send created event.
    275   int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents());
    276   if (!GetTabEntry(contents->tab_contents())) {
    277     tab_entries_[tab_id] = TabEntry();
    278 
    279     TabCreatedAt(contents->tab_contents(), index, foreground);
    280     return;
    281   }
    282 
    283   ListValue args;
    284   args.Append(Value::CreateIntegerValue(tab_id));
    285 
    286   DictionaryValue* object_args = new DictionaryValue();
    287   object_args->Set(tab_keys::kNewWindowIdKey, Value::CreateIntegerValue(
    288       ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
    289   object_args->Set(tab_keys::kNewPositionKey, Value::CreateIntegerValue(
    290       index));
    291   args.Append(object_args);
    292 
    293   std::string json_args;
    294   base::JSONWriter::Write(&args, false, &json_args);
    295 
    296   DispatchEvent(contents->profile(), events::kOnTabAttached, json_args);
    297 }
    298 
    299 void ExtensionBrowserEventRouter::TabDetachedAt(TabContentsWrapper* contents,
    300                                                 int index) {
    301   if (!GetTabEntry(contents->tab_contents())) {
    302     // The tab was removed. Don't send detach event.
    303     return;
    304   }
    305 
    306   ListValue args;
    307   args.Append(Value::CreateIntegerValue(
    308       ExtensionTabUtil::GetTabId(contents->tab_contents())));
    309 
    310   DictionaryValue* object_args = new DictionaryValue();
    311   object_args->Set(tab_keys::kOldWindowIdKey, Value::CreateIntegerValue(
    312       ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
    313   object_args->Set(tab_keys::kOldPositionKey, Value::CreateIntegerValue(
    314       index));
    315   args.Append(object_args);
    316 
    317   std::string json_args;
    318   base::JSONWriter::Write(&args, false, &json_args);
    319 
    320   DispatchEvent(contents->profile(), events::kOnTabDetached, json_args);
    321 }
    322 
    323 void ExtensionBrowserEventRouter::TabClosingAt(TabStripModel* tab_strip_model,
    324                                                TabContentsWrapper* contents,
    325                                                int index) {
    326   int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents());
    327 
    328   ListValue args;
    329   args.Append(Value::CreateIntegerValue(tab_id));
    330 
    331   DictionaryValue* object_args = new DictionaryValue();
    332   object_args->SetBoolean(tab_keys::kWindowClosing,
    333                           tab_strip_model->closing_all());
    334   args.Append(object_args);
    335 
    336   std::string json_args;
    337   base::JSONWriter::Write(&args, false, &json_args);
    338 
    339   DispatchEvent(contents->profile(), events::kOnTabRemoved, json_args);
    340 
    341   int removed_count = tab_entries_.erase(tab_id);
    342   DCHECK_GT(removed_count, 0);
    343 
    344   UnregisterForTabNotifications(contents->tab_contents());
    345 }
    346 
    347 void ExtensionBrowserEventRouter::TabSelectedAt(
    348     TabContentsWrapper* old_contents,
    349     TabContentsWrapper* new_contents,
    350     int index,
    351     bool user_gesture) {
    352   if (old_contents == new_contents)
    353     return;
    354 
    355   ListValue args;
    356   args.Append(Value::CreateIntegerValue(
    357       ExtensionTabUtil::GetTabId(new_contents->tab_contents())));
    358 
    359   DictionaryValue* object_args = new DictionaryValue();
    360   object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue(
    361       ExtensionTabUtil::GetWindowIdOfTab(new_contents->tab_contents())));
    362   args.Append(object_args);
    363 
    364   std::string json_args;
    365   base::JSONWriter::Write(&args, false, &json_args);
    366 
    367   DispatchEvent(new_contents->profile(), events::kOnTabSelectionChanged,
    368                 json_args);
    369 }
    370 
    371 void ExtensionBrowserEventRouter::TabMoved(TabContentsWrapper* contents,
    372                                            int from_index,
    373                                            int to_index) {
    374   ListValue args;
    375   args.Append(Value::CreateIntegerValue(
    376       ExtensionTabUtil::GetTabId(contents->tab_contents())));
    377 
    378   DictionaryValue* object_args = new DictionaryValue();
    379   object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue(
    380       ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents())));
    381   object_args->Set(tab_keys::kFromIndexKey, Value::CreateIntegerValue(
    382       from_index));
    383   object_args->Set(tab_keys::kToIndexKey, Value::CreateIntegerValue(
    384       to_index));
    385   args.Append(object_args);
    386 
    387   std::string json_args;
    388   base::JSONWriter::Write(&args, false, &json_args);
    389 
    390   DispatchEvent(contents->profile(), events::kOnTabMoved, json_args);
    391 }
    392 
    393 void ExtensionBrowserEventRouter::TabUpdated(TabContents* contents,
    394                                              bool did_navigate) {
    395   TabEntry* entry = GetTabEntry(contents);
    396   DictionaryValue* changed_properties = NULL;
    397 
    398   DCHECK(entry);
    399 
    400   if (did_navigate)
    401     changed_properties = entry->DidNavigate(contents);
    402   else
    403     changed_properties = entry->UpdateLoadState(contents);
    404 
    405   if (changed_properties)
    406     DispatchTabUpdatedEvent(contents, changed_properties);
    407 }
    408 
    409 void ExtensionBrowserEventRouter::DispatchTabUpdatedEvent(
    410     TabContents* contents, DictionaryValue* changed_properties) {
    411   DCHECK(changed_properties);
    412   DCHECK(contents);
    413 
    414   // The state of the tab (as seen from the extension point of view) has
    415   // changed.  Send a notification to the extension.
    416   ListValue args;
    417 
    418   // First arg: The id of the tab that changed.
    419   args.Append(Value::CreateIntegerValue(ExtensionTabUtil::GetTabId(contents)));
    420 
    421   // Second arg: An object containing the changes to the tab state.
    422   args.Append(changed_properties);
    423 
    424   // Third arg: An object containing the state of the tab.
    425   args.Append(ExtensionTabUtil::CreateTabValue(contents));
    426 
    427   std::string json_args;
    428   base::JSONWriter::Write(&args, false, &json_args);
    429 
    430   DispatchEvent(contents->profile(), events::kOnTabUpdated, json_args);
    431 }
    432 
    433 ExtensionBrowserEventRouter::TabEntry* ExtensionBrowserEventRouter::GetTabEntry(
    434     const TabContents* contents) {
    435   int tab_id = ExtensionTabUtil::GetTabId(contents);
    436   std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id);
    437   if (tab_entries_.end() == i)
    438     return NULL;
    439   return &i->second;
    440 }
    441 
    442 void ExtensionBrowserEventRouter::Observe(NotificationType type,
    443                                           const NotificationSource& source,
    444                                           const NotificationDetails& details) {
    445   if (type == NotificationType::NAV_ENTRY_COMMITTED) {
    446     NavigationController* source_controller =
    447         Source<NavigationController>(source).ptr();
    448     TabUpdated(source_controller->tab_contents(), true);
    449   } else if (type == NotificationType::TAB_CONTENTS_DESTROYED) {
    450     // Tab was destroyed after being detached (without being re-attached).
    451     TabContents* contents = Source<TabContents>(source).ptr();
    452     registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
    453         Source<NavigationController>(&contents->controller()));
    454     registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
    455         Source<TabContents>(contents));
    456   } else if (type == NotificationType::BROWSER_WINDOW_READY) {
    457     const Browser* browser = Source<const Browser>(source).ptr();
    458     OnBrowserWindowReady(browser);
    459 #if defined(OS_MACOSX)
    460   } else if (type == NotificationType::NO_KEY_WINDOW) {
    461     OnBrowserSetLastActive(NULL);
    462 #endif
    463   } else {
    464     NOTREACHED();
    465   }
    466 }
    467 
    468 void ExtensionBrowserEventRouter::TabChangedAt(TabContentsWrapper* contents,
    469                                                int index,
    470                                                TabChangeType change_type) {
    471   TabUpdated(contents->tab_contents(), false);
    472 }
    473 
    474 void ExtensionBrowserEventRouter::TabReplacedAt(
    475     TabStripModel* tab_strip_model,
    476     TabContentsWrapper* old_contents,
    477     TabContentsWrapper* new_contents,
    478     int index) {
    479   TabClosingAt(tab_strip_model, old_contents, index);
    480   TabInsertedAt(new_contents, index, tab_strip_model->active_index() == index);
    481 }
    482 
    483 void ExtensionBrowserEventRouter::TabPinnedStateChanged(
    484     TabContentsWrapper* contents,
    485     int index) {
    486   TabStripModel* tab_strip = NULL;
    487   int tab_index;
    488 
    489   if (ExtensionTabUtil::GetTabStripModel(
    490         contents->tab_contents(), &tab_strip, &tab_index)) {
    491     DictionaryValue* changed_properties = new DictionaryValue();
    492     changed_properties->SetBoolean(tab_keys::kPinnedKey,
    493                                    tab_strip->IsTabPinned(tab_index));
    494     DispatchTabUpdatedEvent(contents->tab_contents(), changed_properties);
    495   }
    496 }
    497 
    498 void ExtensionBrowserEventRouter::TabStripEmpty() {}
    499 
    500 void ExtensionBrowserEventRouter::DispatchOldPageActionEvent(
    501     Profile* profile,
    502     const std::string& extension_id,
    503     const std::string& page_action_id,
    504     int tab_id,
    505     const std::string& url,
    506     int button) {
    507   ListValue args;
    508   args.Append(Value::CreateStringValue(page_action_id));
    509 
    510   DictionaryValue* data = new DictionaryValue();
    511   data->Set(tab_keys::kTabIdKey, Value::CreateIntegerValue(tab_id));
    512   data->Set(tab_keys::kTabUrlKey, Value::CreateStringValue(url));
    513   data->Set(page_action_keys::kButtonKey, Value::CreateIntegerValue(button));
    514   args.Append(data);
    515 
    516   std::string json_args;
    517   base::JSONWriter::Write(&args, false, &json_args);
    518 
    519   DispatchEventToExtension(profile, extension_id, "pageActions", json_args);
    520 }
    521 
    522 void ExtensionBrowserEventRouter::PageActionExecuted(
    523     Profile* profile,
    524     const std::string& extension_id,
    525     const std::string& page_action_id,
    526     int tab_id,
    527     const std::string& url,
    528     int button) {
    529   DispatchOldPageActionEvent(profile, extension_id, page_action_id, tab_id, url,
    530                              button);
    531   TabContentsWrapper* tab_contents = NULL;
    532   if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(),
    533                                     NULL, NULL, &tab_contents, NULL)) {
    534     return;
    535   }
    536   DispatchEventWithTab(profile, extension_id, "pageAction.onClicked",
    537                        tab_contents->tab_contents());
    538 }
    539 
    540 void ExtensionBrowserEventRouter::BrowserActionExecuted(
    541     Profile* profile, const std::string& extension_id, Browser* browser) {
    542   TabContentsWrapper* tab_contents = NULL;
    543   int tab_id = 0;
    544   if (!ExtensionTabUtil::GetDefaultTab(browser, &tab_contents, &tab_id))
    545     return;
    546   DispatchEventWithTab(profile, extension_id, "browserAction.onClicked",
    547                        tab_contents->tab_contents());
    548 }
    549