// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/browser_event_router.h" #include "base/json/json_writer.h" #include "base/values.h" #include "chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h" #include "chrome/browser/extensions/event_names.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/window_controller.h" #include "chrome/browser/extensions/window_event_router.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/tab_contents/tab_contents.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" namespace events = extensions::event_names; namespace tab_keys = extensions::tabs_constants; namespace page_action_keys = extension_page_actions_api_constants; using content::NavigationController; using content::WebContents; namespace extensions { BrowserEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false), url_() { } DictionaryValue* BrowserEventRouter::TabEntry::UpdateLoadState( const WebContents* contents) { // The tab may go in & out of loading (for instance if iframes navigate). // We only want to respond to the first change from loading to !loading after // the NAV_ENTRY_COMMITTED was fired. if (!complete_waiting_on_load_ || contents->IsLoading()) return NULL; // Send "complete" state change. complete_waiting_on_load_ = false; DictionaryValue* changed_properties = new DictionaryValue(); changed_properties->SetString(tab_keys::kStatusKey, tab_keys::kStatusValueComplete); return changed_properties; } DictionaryValue* BrowserEventRouter::TabEntry::DidNavigate( const WebContents* contents) { // Send "loading" state change. complete_waiting_on_load_ = true; DictionaryValue* changed_properties = new DictionaryValue(); changed_properties->SetString(tab_keys::kStatusKey, tab_keys::kStatusValueLoading); if (contents->GetURL() != url_) { url_ = contents->GetURL(); changed_properties->SetString(tab_keys::kUrlKey, url_.spec()); } return changed_properties; } void BrowserEventRouter::Init() { if (initialized_) return; BrowserList::AddObserver(this); // Init() can happen after the browser is running, so catch up with any // windows that already exist. for (BrowserList::const_iterator iter = BrowserList::begin(); iter != BrowserList::end(); ++iter) { RegisterForBrowserNotifications(*iter); // Also catch up our internal bookkeeping of tab entries. Browser* browser = *iter; if (browser->tab_strip_model()) { for (int i = 0; i < browser->tab_strip_model()->count(); ++i) { WebContents* contents = chrome::GetWebContentsAt(browser, i); int tab_id = ExtensionTabUtil::GetTabId(contents); tab_entries_[tab_id] = TabEntry(); } } } initialized_ = true; } BrowserEventRouter::BrowserEventRouter(Profile* profile) : initialized_(false), profile_(profile) { DCHECK(!profile->IsOffTheRecord()); } BrowserEventRouter::~BrowserEventRouter() { BrowserList::RemoveObserver(this); } void BrowserEventRouter::OnBrowserAdded(Browser* browser) { RegisterForBrowserNotifications(browser); } void BrowserEventRouter::RegisterForBrowserNotifications(Browser* browser) { if (!profile_->IsSameProfile(browser->profile())) return; // Start listening to TabStripModel events for this browser. browser->tab_strip_model()->AddObserver(this); for (int i = 0; i < browser->tab_strip_model()->count(); ++i) { RegisterForTabNotifications(chrome::GetWebContentsAt(browser, i)); } } void BrowserEventRouter::RegisterForTabNotifications(WebContents* contents) { registrar_.Add( this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, content::Source(&contents->GetController())); // Observing NOTIFICATION_WEB_CONTENTS_DESTROYED is necessary because it's // possible for tabs to be created, detached and then destroyed without // ever having been re-attached and closed. This happens in the case of // a devtools WebContents that is opened in window, docked, then closed. registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::Source(contents)); } void BrowserEventRouter::UnregisterForTabNotifications(WebContents* contents) { registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, content::Source(&contents->GetController())); registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::Source(contents)); } void BrowserEventRouter::OnBrowserRemoved(Browser* browser) { if (!profile_->IsSameProfile(browser->profile())) return; // Stop listening to TabStripModel events for this browser. browser->tab_strip_model()->RemoveObserver(this); } void BrowserEventRouter::OnBrowserSetLastActive(Browser* browser) { ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->extension_service(); if (service) { service->window_event_router()->OnActiveWindowChanged( browser ? browser->extension_window_controller() : NULL); } } void BrowserEventRouter::TabCreatedAt(WebContents* contents, int index, bool active) { Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); const EventListenerMap::ListenerList& listeners( ExtensionSystem::Get(profile)->event_router()-> listeners().GetEventListenersByName(events::kOnTabCreated)); for (EventListenerMap::ListenerList::const_iterator it = listeners.begin(); it != listeners.end(); ++it) { scoped_ptr args(new ListValue()); DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue( contents, profile->GetExtensionService()->extensions()->GetByID( (*it)->extension_id)); args->Append(tab_value); tab_value->SetBoolean(tab_keys::kSelectedKey, active); DispatchEventToExtension(profile, (*it)->extension_id, events::kOnTabCreated, args.Pass(), EventRouter::USER_GESTURE_NOT_ENABLED); } RegisterForTabNotifications(contents); } void BrowserEventRouter::TabInsertedAt(TabContents* contents, int index, bool active) { // If tab is new, send created event. int tab_id = ExtensionTabUtil::GetTabId(contents->web_contents()); if (!GetTabEntry(contents->web_contents())) { tab_entries_[tab_id] = TabEntry(); TabCreatedAt(contents->web_contents(), index, active); return; } scoped_ptr args(new ListValue()); args->Append(Value::CreateIntegerValue(tab_id)); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kNewWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(contents->web_contents()))); object_args->Set(tab_keys::kNewPositionKey, Value::CreateIntegerValue( index)); args->Append(object_args); DispatchEvent(contents->profile(), events::kOnTabAttached, args.Pass(), EventRouter::USER_GESTURE_UNKNOWN); } void BrowserEventRouter::TabDetachedAt(TabContents* contents, int index) { if (!GetTabEntry(contents->web_contents())) { // The tab was removed. Don't send detach event. return; } scoped_ptr args(new ListValue()); args->Append(Value::CreateIntegerValue( ExtensionTabUtil::GetTabId(contents->web_contents()))); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kOldWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(contents->web_contents()))); object_args->Set(tab_keys::kOldPositionKey, Value::CreateIntegerValue( index)); args->Append(object_args); DispatchEvent(contents->profile(), events::kOnTabDetached, args.Pass(), EventRouter::USER_GESTURE_UNKNOWN); } void BrowserEventRouter::TabClosingAt(TabStripModel* tab_strip_model, TabContents* contents, int index) { int tab_id = ExtensionTabUtil::GetTabId(contents->web_contents()); scoped_ptr args(new ListValue()); args->Append(Value::CreateIntegerValue(tab_id)); DictionaryValue* object_args = new DictionaryValue(); object_args->SetBoolean(tab_keys::kWindowClosing, tab_strip_model->closing_all()); args->Append(object_args); DispatchEvent(contents->profile(), events::kOnTabRemoved, args.Pass(), EventRouter::USER_GESTURE_UNKNOWN); int removed_count = tab_entries_.erase(tab_id); DCHECK_GT(removed_count, 0); UnregisterForTabNotifications(contents->web_contents()); } void BrowserEventRouter::ActiveTabChanged(TabContents* old_contents, TabContents* new_contents, int index, bool user_gesture) { scoped_ptr args(new ListValue()); int tab_id = ExtensionTabUtil::GetTabId(new_contents->web_contents()); args->Append(Value::CreateIntegerValue(tab_id)); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(new_contents->web_contents()))); args->Append(object_args); // The onActivated event replaced onActiveChanged and onSelectionChanged. The // deprecated events take two arguments: tabId, {windowId}. Profile* profile = new_contents->profile(); EventRouter::UserGestureState gesture = user_gesture ? EventRouter::USER_GESTURE_ENABLED : EventRouter::USER_GESTURE_NOT_ENABLED; DispatchEvent(profile, events::kOnTabSelectionChanged, scoped_ptr(args->DeepCopy()), gesture); DispatchEvent(profile, events::kOnTabActiveChanged, scoped_ptr(args->DeepCopy()), gesture); // The onActivated event takes one argument: {windowId, tabId}. args->Remove(0, NULL); object_args->Set(tab_keys::kTabIdKey, Value::CreateIntegerValue(tab_id)); DispatchEvent(profile, events::kOnTabActivated, args.Pass(), gesture); } void BrowserEventRouter::TabSelectionChanged( TabStripModel* tab_strip_model, const TabStripSelectionModel& old_model) { TabStripSelectionModel::SelectedIndices new_selection = tab_strip_model->selection_model().selected_indices(); ListValue* all = new ListValue(); for (size_t i = 0; i < new_selection.size(); ++i) { int index = new_selection[i]; WebContents* contents = tab_strip_model->GetWebContentsAt(index); if (!contents) break; int tab_id = ExtensionTabUtil::GetTabId(contents); all->Append(Value::CreateIntegerValue(tab_id)); } scoped_ptr args(new ListValue()); DictionaryValue* select_info = new DictionaryValue(); select_info->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model))); select_info->Set(tab_keys::kTabIdsKey, all); args->Append(select_info); // The onHighlighted event replaced onHighlightChanged. Profile* profile = tab_strip_model->profile(); DispatchEvent(profile, events::kOnTabHighlightChanged, scoped_ptr(args->DeepCopy()), EventRouter::USER_GESTURE_UNKNOWN); DispatchEvent(profile, events::kOnTabHighlighted, args.Pass(), EventRouter::USER_GESTURE_UNKNOWN); } void BrowserEventRouter::TabMoved(TabContents* contents, int from_index, int to_index) { scoped_ptr args(new ListValue()); args->Append(Value::CreateIntegerValue( ExtensionTabUtil::GetTabId(contents->web_contents()))); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(contents->web_contents()))); object_args->Set(tab_keys::kFromIndexKey, Value::CreateIntegerValue( from_index)); object_args->Set(tab_keys::kToIndexKey, Value::CreateIntegerValue( to_index)); args->Append(object_args); DispatchEvent(contents->profile(), events::kOnTabMoved, args.Pass(), EventRouter::USER_GESTURE_UNKNOWN); } void BrowserEventRouter::TabUpdated(WebContents* contents, bool did_navigate) { TabEntry* entry = GetTabEntry(contents); DictionaryValue* changed_properties = NULL; DCHECK(entry); if (did_navigate) changed_properties = entry->DidNavigate(contents); else changed_properties = entry->UpdateLoadState(contents); if (changed_properties) DispatchTabUpdatedEvent(contents, changed_properties); } void BrowserEventRouter::DispatchEvent( Profile* profile, const char* event_name, scoped_ptr args, EventRouter::UserGestureState user_gesture) { if (!profile_->IsSameProfile(profile) || !extensions::ExtensionSystem::Get(profile)->event_router()) return; extensions::ExtensionSystem::Get(profile)->event_router()-> DispatchEventToRenderers(event_name, args.Pass(), profile, GURL(), user_gesture); } void BrowserEventRouter::DispatchEventToExtension( Profile* profile, const std::string& extension_id, const char* event_name, scoped_ptr event_args, EventRouter::UserGestureState user_gesture) { if (!profile_->IsSameProfile(profile) || !extensions::ExtensionSystem::Get(profile)->event_router()) return; extensions::ExtensionSystem::Get(profile)->event_router()-> DispatchEventToExtension(extension_id, event_name, event_args.Pass(), profile, GURL(), user_gesture); } void BrowserEventRouter::DispatchEventsAcrossIncognito( Profile* profile, const char* event_name, scoped_ptr event_args, scoped_ptr cross_incognito_args) { if (!profile_->IsSameProfile(profile) || !extensions::ExtensionSystem::Get(profile)->event_router()) return; extensions::ExtensionSystem::Get(profile)->event_router()-> DispatchEventsToRenderersAcrossIncognito(event_name, event_args.Pass(), profile, cross_incognito_args.Pass(), GURL()); } void BrowserEventRouter::DispatchSimpleBrowserEvent( Profile* profile, const int window_id, const char* event_name) { if (!profile_->IsSameProfile(profile)) return; scoped_ptr args(new ListValue()); args->Append(Value::CreateIntegerValue(window_id)); DispatchEvent(profile, event_name, args.Pass(), EventRouter::USER_GESTURE_UNKNOWN); } void BrowserEventRouter::DispatchTabUpdatedEvent( WebContents* contents, DictionaryValue* changed_properties) { DCHECK(changed_properties); DCHECK(contents); // The state of the tab (as seen from the extension point of view) has // changed. Send a notification to the extension. scoped_ptr args_base(new ListValue()); // First arg: The id of the tab that changed. args_base->AppendInteger(ExtensionTabUtil::GetTabId(contents)); // Second arg: An object containing the changes to the tab state. args_base->Append(changed_properties); // Third arg: An object containing the state of the tab. Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); const EventListenerMap::ListenerList& listeners( ExtensionSystem::Get(profile)->event_router()-> listeners().GetEventListenersByName(events::kOnTabUpdated)); for (EventListenerMap::ListenerList::const_iterator it = listeners.begin(); it != listeners.end(); ++it) { scoped_ptr args(args_base->DeepCopy()); DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue( contents, profile->GetExtensionService()->extensions()->GetByID( (*it)->extension_id)); args->Append(tab_value); DispatchEventToExtension(profile, (*it)->extension_id, events::kOnTabUpdated, args.Pass(), EventRouter::USER_GESTURE_UNKNOWN); } } BrowserEventRouter::TabEntry* BrowserEventRouter::GetTabEntry( const WebContents* contents) { int tab_id = ExtensionTabUtil::GetTabId(contents); std::map::iterator i = tab_entries_.find(tab_id); if (tab_entries_.end() == i) return NULL; return &i->second; } void BrowserEventRouter::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) { NavigationController* source_controller = content::Source(source).ptr(); TabUpdated(source_controller->GetWebContents(), true); } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { // Tab was destroyed after being detached (without being re-attached). WebContents* contents = content::Source(source).ptr(); registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, content::Source(&contents->GetController())); registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::Source(contents)); } else { NOTREACHED(); } } void BrowserEventRouter::TabChangedAt(TabContents* contents, int index, TabChangeType change_type) { TabUpdated(contents->web_contents(), false); } void BrowserEventRouter::TabReplacedAt(TabStripModel* tab_strip_model, TabContents* old_contents, TabContents* new_contents, int index) { TabClosingAt(tab_strip_model, old_contents, index); TabInsertedAt(new_contents, index, tab_strip_model->active_index() == index); } void BrowserEventRouter::TabPinnedStateChanged(TabContents* contents, int index) { TabStripModel* tab_strip = NULL; int tab_index; if (ExtensionTabUtil::GetTabStripModel( contents->web_contents(), &tab_strip, &tab_index)) { DictionaryValue* changed_properties = new DictionaryValue(); changed_properties->SetBoolean(tab_keys::kPinnedKey, tab_strip->IsTabPinned(tab_index)); DispatchTabUpdatedEvent(contents->web_contents(), changed_properties); } } void BrowserEventRouter::TabStripEmpty() {} void BrowserEventRouter::DispatchOldPageActionEvent( Profile* profile, const std::string& extension_id, const std::string& page_action_id, int tab_id, const std::string& url, int button) { scoped_ptr args(new ListValue()); args->Append(Value::CreateStringValue(page_action_id)); DictionaryValue* data = new DictionaryValue(); data->Set(tab_keys::kTabIdKey, Value::CreateIntegerValue(tab_id)); data->Set(tab_keys::kTabUrlKey, Value::CreateStringValue(url)); data->Set(page_action_keys::kButtonKey, Value::CreateIntegerValue(button)); args->Append(data); DispatchEventToExtension(profile, extension_id, "pageActions", args.Pass(), EventRouter::USER_GESTURE_ENABLED); } void BrowserEventRouter::BrowserActionExecuted( const ExtensionAction& browser_action, Browser* browser) { Profile* profile = browser->profile(); WebContents* web_contents = NULL; int tab_id = 0; if (!ExtensionTabUtil::GetDefaultTab(browser, &web_contents, &tab_id)) return; ExtensionActionExecuted(profile, browser_action, web_contents); } void BrowserEventRouter::PageActionExecuted(Profile* profile, const ExtensionAction& page_action, int tab_id, const std::string& url, int button) { DispatchOldPageActionEvent(profile, page_action.extension_id(), page_action.id(), tab_id, url, button); WebContents* web_contents = NULL; if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(), NULL, NULL, &web_contents, NULL)) { return; } ExtensionActionExecuted(profile, page_action, web_contents); } void BrowserEventRouter::ScriptBadgeExecuted( Profile* profile, const ExtensionAction& script_badge, int tab_id) { WebContents* web_contents = NULL; if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(), NULL, NULL, &web_contents, NULL)) { return; } ExtensionActionExecuted(profile, script_badge, web_contents); } void BrowserEventRouter::CommandExecuted(Profile* profile, const std::string& extension_id, const std::string& command) { scoped_ptr args(new ListValue()); args->Append(Value::CreateStringValue(command)); DispatchEventToExtension(profile, extension_id, "commands.onCommand", args.Pass(), EventRouter::USER_GESTURE_ENABLED); } void BrowserEventRouter::ExtensionActionExecuted( Profile* profile, const ExtensionAction& extension_action, WebContents* web_contents) { const char* event_name = NULL; switch (extension_action.action_type()) { case Extension::ActionInfo::TYPE_BROWSER: event_name = "browserAction.onClicked"; break; case Extension::ActionInfo::TYPE_PAGE: event_name = "pageAction.onClicked"; break; case Extension::ActionInfo::TYPE_SCRIPT_BADGE: event_name = "scriptBadge.onClicked"; break; } if (event_name) { scoped_ptr args(new ListValue()); DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue( web_contents, ExtensionTabUtil::INCLUDE_PRIVACY_SENSITIVE_FIELDS); args->Append(tab_value); DispatchEventToExtension(profile, extension_action.extension_id(), event_name, args.Pass(), EventRouter::USER_GESTURE_ENABLED); } } } // namespace extensions