// Copyright (c) 2011 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/extension_browser_event_router.h" #include "base/json/json_writer.h" #include "base/values.h" #include "chrome/browser/extensions/extension_event_names.h" #include "chrome/browser/extensions/extension_event_router.h" #include "chrome/browser/extensions/extension_page_actions_module_constants.h" #include "chrome/browser/extensions/extension_tabs_module_constants.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "content/browser/tab_contents/navigation_entry.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/notification_service.h" namespace events = extension_event_names; namespace tab_keys = extension_tabs_module_constants; namespace page_action_keys = extension_page_actions_module_constants; ExtensionBrowserEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false), url_() { } DictionaryValue* ExtensionBrowserEventRouter::TabEntry::UpdateLoadState( const TabContents* 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->is_loading()) 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* ExtensionBrowserEventRouter::TabEntry::DidNavigate( const TabContents* 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; } static void DispatchEvent(Profile* profile, const char* event_name, const std::string& json_args) { if (profile->GetExtensionEventRouter()) { profile->GetExtensionEventRouter()->DispatchEventToRenderers( event_name, json_args, profile, GURL()); } } static void DispatchEventToExtension(Profile* profile, const std::string& extension_id, const char* event_name, const std::string& json_args) { if (profile->GetExtensionEventRouter()) { profile->GetExtensionEventRouter()->DispatchEventToExtension( extension_id, event_name, json_args, profile, GURL()); } } static void DispatchEventWithTab(Profile* profile, const std::string& extension_id, const char* event_name, const TabContents* tab_contents) { ListValue args; args.Append(ExtensionTabUtil::CreateTabValue(tab_contents)); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); if (!extension_id.empty()) { DispatchEventToExtension(profile, extension_id, event_name, json_args); } else { DispatchEvent(profile, event_name, json_args); } } static void DispatchSimpleBrowserEvent(Profile* profile, const int window_id, const char* event_name) { ListValue args; args.Append(Value::CreateIntegerValue(window_id)); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(profile, event_name, json_args); } void ExtensionBrowserEventRouter::Init() { if (initialized_) return; BrowserList::AddObserver(this); #if defined(TOOLKIT_VIEWS) views::FocusManager::GetWidgetFocusManager()->AddFocusChangeListener(this); #elif defined(TOOLKIT_GTK) ui::ActiveWindowWatcherX::AddObserver(this); #elif defined(OS_MACOSX) // Needed for when no suitable window can be passed to an extension as the // currently focused window. registrar_.Add(this, NotificationType::NO_KEY_WINDOW, NotificationService::AllSources()); #endif // 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->tabstrip_model()) { for (int i = 0; i < browser->tabstrip_model()->count(); ++i) { TabContents* contents = browser->GetTabContentsAt(i); int tab_id = ExtensionTabUtil::GetTabId(contents); tab_entries_[tab_id] = TabEntry(); } } } initialized_ = true; } ExtensionBrowserEventRouter::ExtensionBrowserEventRouter(Profile* profile) : initialized_(false), focused_window_id_(extension_misc::kUnknownWindowId), profile_(profile) { DCHECK(!profile->IsOffTheRecord()); } ExtensionBrowserEventRouter::~ExtensionBrowserEventRouter() { BrowserList::RemoveObserver(this); #if defined(TOOLKIT_VIEWS) views::FocusManager::GetWidgetFocusManager()->RemoveFocusChangeListener(this); #elif defined(TOOLKIT_GTK) ui::ActiveWindowWatcherX::RemoveObserver(this); #endif } void ExtensionBrowserEventRouter::OnBrowserAdded(const Browser* browser) { RegisterForBrowserNotifications(browser); } void ExtensionBrowserEventRouter::RegisterForBrowserNotifications( const Browser* browser) { // Start listening to TabStripModel events for this browser. browser->tabstrip_model()->AddObserver(this); // If this is a new window, it isn't ready at this point, so we register to be // notified when it is. If this is an existing window, this is a no-op that we // just do to reduce code complexity. registrar_.Add(this, NotificationType::BROWSER_WINDOW_READY, Source<const Browser>(browser)); if (browser->tabstrip_model()) { for (int i = 0; i < browser->tabstrip_model()->count(); ++i) RegisterForTabNotifications(browser->GetTabContentsAt(i)); } } void ExtensionBrowserEventRouter::RegisterForTabNotifications( TabContents* contents) { registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, Source<NavigationController>(&contents->controller())); // Observing TAB_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 TabContents that is opened in window, docked, then closed. registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, Source<TabContents>(contents)); } void ExtensionBrowserEventRouter::UnregisterForTabNotifications( TabContents* contents) { registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED, Source<NavigationController>(&contents->controller())); registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED, Source<TabContents>(contents)); } void ExtensionBrowserEventRouter::OnBrowserWindowReady(const Browser* browser) { ListValue args; DictionaryValue* window_dictionary = ExtensionTabUtil::CreateWindowValue( browser, false); args.Append(window_dictionary); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(browser->profile(), events::kOnWindowCreated, json_args); } void ExtensionBrowserEventRouter::OnBrowserRemoved(const Browser* browser) { // Stop listening to TabStripModel events for this browser. browser->tabstrip_model()->RemoveObserver(this); registrar_.Remove(this, NotificationType::BROWSER_WINDOW_READY, Source<const Browser>(browser)); DispatchSimpleBrowserEvent(browser->profile(), ExtensionTabUtil::GetWindowId(browser), events::kOnWindowRemoved); } #if defined(TOOLKIT_VIEWS) void ExtensionBrowserEventRouter::NativeFocusWillChange( gfx::NativeView focused_before, gfx::NativeView focused_now) { if (!focused_now) OnBrowserSetLastActive(NULL); } #elif defined(TOOLKIT_GTK) void ExtensionBrowserEventRouter::ActiveWindowChanged( GdkWindow* active_window) { if (!active_window) OnBrowserSetLastActive(NULL); } #endif void ExtensionBrowserEventRouter::OnBrowserSetLastActive( const Browser* browser) { int window_id = extension_misc::kUnknownWindowId; if (browser) window_id = ExtensionTabUtil::GetWindowId(browser); if (focused_window_id_ == window_id) return; focused_window_id_ = window_id; // Note: because we use the default profile when |browser| is NULL, it means // that all extensions hear about the event regardless of whether the browser // that lost focus was OTR or if the extension is OTR-enabled. // See crbug.com/46610. DispatchSimpleBrowserEvent(browser ? browser->profile() : profile_, focused_window_id_, events::kOnWindowFocusedChanged); } void ExtensionBrowserEventRouter::TabCreatedAt(TabContents* contents, int index, bool foreground) { DispatchEventWithTab(contents->profile(), "", events::kOnTabCreated, contents); RegisterForTabNotifications(contents); } void ExtensionBrowserEventRouter::TabInsertedAt(TabContentsWrapper* contents, int index, bool foreground) { // If tab is new, send created event. int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents()); if (!GetTabEntry(contents->tab_contents())) { tab_entries_[tab_id] = TabEntry(); TabCreatedAt(contents->tab_contents(), index, foreground); return; } ListValue args; args.Append(Value::CreateIntegerValue(tab_id)); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kNewWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents()))); object_args->Set(tab_keys::kNewPositionKey, Value::CreateIntegerValue( index)); args.Append(object_args); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(contents->profile(), events::kOnTabAttached, json_args); } void ExtensionBrowserEventRouter::TabDetachedAt(TabContentsWrapper* contents, int index) { if (!GetTabEntry(contents->tab_contents())) { // The tab was removed. Don't send detach event. return; } ListValue args; args.Append(Value::CreateIntegerValue( ExtensionTabUtil::GetTabId(contents->tab_contents()))); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kOldWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents()))); object_args->Set(tab_keys::kOldPositionKey, Value::CreateIntegerValue( index)); args.Append(object_args); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(contents->profile(), events::kOnTabDetached, json_args); } void ExtensionBrowserEventRouter::TabClosingAt(TabStripModel* tab_strip_model, TabContentsWrapper* contents, int index) { int tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents()); ListValue args; 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); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(contents->profile(), events::kOnTabRemoved, json_args); int removed_count = tab_entries_.erase(tab_id); DCHECK_GT(removed_count, 0); UnregisterForTabNotifications(contents->tab_contents()); } void ExtensionBrowserEventRouter::TabSelectedAt( TabContentsWrapper* old_contents, TabContentsWrapper* new_contents, int index, bool user_gesture) { if (old_contents == new_contents) return; ListValue args; args.Append(Value::CreateIntegerValue( ExtensionTabUtil::GetTabId(new_contents->tab_contents()))); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(new_contents->tab_contents()))); args.Append(object_args); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(new_contents->profile(), events::kOnTabSelectionChanged, json_args); } void ExtensionBrowserEventRouter::TabMoved(TabContentsWrapper* contents, int from_index, int to_index) { ListValue args; args.Append(Value::CreateIntegerValue( ExtensionTabUtil::GetTabId(contents->tab_contents()))); DictionaryValue* object_args = new DictionaryValue(); object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( ExtensionTabUtil::GetWindowIdOfTab(contents->tab_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); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(contents->profile(), events::kOnTabMoved, json_args); } void ExtensionBrowserEventRouter::TabUpdated(TabContents* 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 ExtensionBrowserEventRouter::DispatchTabUpdatedEvent( TabContents* 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. ListValue args; // First arg: The id of the tab that changed. args.Append(Value::CreateIntegerValue(ExtensionTabUtil::GetTabId(contents))); // Second arg: An object containing the changes to the tab state. args.Append(changed_properties); // Third arg: An object containing the state of the tab. args.Append(ExtensionTabUtil::CreateTabValue(contents)); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(contents->profile(), events::kOnTabUpdated, json_args); } ExtensionBrowserEventRouter::TabEntry* ExtensionBrowserEventRouter::GetTabEntry( const TabContents* contents) { int tab_id = ExtensionTabUtil::GetTabId(contents); std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id); if (tab_entries_.end() == i) return NULL; return &i->second; } void ExtensionBrowserEventRouter::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::NAV_ENTRY_COMMITTED) { NavigationController* source_controller = Source<NavigationController>(source).ptr(); TabUpdated(source_controller->tab_contents(), true); } else if (type == NotificationType::TAB_CONTENTS_DESTROYED) { // Tab was destroyed after being detached (without being re-attached). TabContents* contents = Source<TabContents>(source).ptr(); registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED, Source<NavigationController>(&contents->controller())); registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED, Source<TabContents>(contents)); } else if (type == NotificationType::BROWSER_WINDOW_READY) { const Browser* browser = Source<const Browser>(source).ptr(); OnBrowserWindowReady(browser); #if defined(OS_MACOSX) } else if (type == NotificationType::NO_KEY_WINDOW) { OnBrowserSetLastActive(NULL); #endif } else { NOTREACHED(); } } void ExtensionBrowserEventRouter::TabChangedAt(TabContentsWrapper* contents, int index, TabChangeType change_type) { TabUpdated(contents->tab_contents(), false); } void ExtensionBrowserEventRouter::TabReplacedAt( TabStripModel* tab_strip_model, TabContentsWrapper* old_contents, TabContentsWrapper* new_contents, int index) { TabClosingAt(tab_strip_model, old_contents, index); TabInsertedAt(new_contents, index, tab_strip_model->active_index() == index); } void ExtensionBrowserEventRouter::TabPinnedStateChanged( TabContentsWrapper* contents, int index) { TabStripModel* tab_strip = NULL; int tab_index; if (ExtensionTabUtil::GetTabStripModel( contents->tab_contents(), &tab_strip, &tab_index)) { DictionaryValue* changed_properties = new DictionaryValue(); changed_properties->SetBoolean(tab_keys::kPinnedKey, tab_strip->IsTabPinned(tab_index)); DispatchTabUpdatedEvent(contents->tab_contents(), changed_properties); } } void ExtensionBrowserEventRouter::TabStripEmpty() {} void ExtensionBrowserEventRouter::DispatchOldPageActionEvent( Profile* profile, const std::string& extension_id, const std::string& page_action_id, int tab_id, const std::string& url, int button) { ListValue args; 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); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEventToExtension(profile, extension_id, "pageActions", json_args); } void ExtensionBrowserEventRouter::PageActionExecuted( Profile* profile, const std::string& extension_id, const std::string& page_action_id, int tab_id, const std::string& url, int button) { DispatchOldPageActionEvent(profile, extension_id, page_action_id, tab_id, url, button); TabContentsWrapper* tab_contents = NULL; if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(), NULL, NULL, &tab_contents, NULL)) { return; } DispatchEventWithTab(profile, extension_id, "pageAction.onClicked", tab_contents->tab_contents()); } void ExtensionBrowserEventRouter::BrowserActionExecuted( Profile* profile, const std::string& extension_id, Browser* browser) { TabContentsWrapper* tab_contents = NULL; int tab_id = 0; if (!ExtensionTabUtil::GetDefaultTab(browser, &tab_contents, &tab_id)) return; DispatchEventWithTab(profile, extension_id, "browserAction.onClicked", tab_contents->tab_contents()); }