extension_browser_event_router.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 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/tab_contents/navigation_entry.h" 15#include "chrome/browser/tab_contents/tab_contents.h" 16#include "chrome/browser/tabs/tab_strip_model.h" 17#include "chrome/browser/ui/browser.h" 18#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 19#include "chrome/common/extensions/extension_constants.h" 20#include "chrome/common/extensions/extension.h" 21#include "chrome/common/notification_service.h" 22 23namespace events = extension_event_names; 24namespace tab_keys = extension_tabs_module_constants; 25namespace page_action_keys = extension_page_actions_module_constants; 26 27ExtensionBrowserEventRouter::TabEntry::TabEntry() 28 : complete_waiting_on_load_(false), 29 url_() { 30} 31 32DictionaryValue* 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 48DictionaryValue* 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 64static 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 73static 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 83static 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 98static 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 110void 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 145ExtensionBrowserEventRouter::ExtensionBrowserEventRouter(Profile* profile) 146 : initialized_(false), 147 focused_window_id_(extension_misc::kUnknownWindowId), 148 profile_(profile) { 149 DCHECK(!profile->IsOffTheRecord()); 150} 151 152ExtensionBrowserEventRouter::~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 161void ExtensionBrowserEventRouter::OnBrowserAdded(const Browser* browser) { 162 RegisterForBrowserNotifications(browser); 163} 164 165void 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 182void 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 195void 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 203void 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 216void 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) 229void 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) 236void ExtensionBrowserEventRouter::ActiveWindowChanged( 237 GdkWindow* active_window) { 238 if (!active_window) 239 OnBrowserSetLastActive(NULL); 240} 241#endif 242 243void 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 262void ExtensionBrowserEventRouter::TabCreatedAt(TabContents* contents, 263 int index, 264 bool foreground) { 265 DispatchEventWithTab(contents->profile(), "", events::kOnTabCreated, 266 contents); 267 268 RegisterForTabNotifications(contents); 269} 270 271void 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 299void 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 323void 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 347void ExtensionBrowserEventRouter::TabSelectedAt( 348 TabContentsWrapper* old_contents, 349 TabContentsWrapper* new_contents, 350 int index, 351 bool user_gesture) { 352 ListValue args; 353 args.Append(Value::CreateIntegerValue( 354 ExtensionTabUtil::GetTabId(new_contents->tab_contents()))); 355 356 DictionaryValue* object_args = new DictionaryValue(); 357 object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( 358 ExtensionTabUtil::GetWindowIdOfTab(new_contents->tab_contents()))); 359 args.Append(object_args); 360 361 std::string json_args; 362 base::JSONWriter::Write(&args, false, &json_args); 363 364 DispatchEvent(new_contents->profile(), events::kOnTabSelectionChanged, 365 json_args); 366} 367 368void ExtensionBrowserEventRouter::TabMoved(TabContentsWrapper* contents, 369 int from_index, 370 int to_index) { 371 ListValue args; 372 args.Append(Value::CreateIntegerValue( 373 ExtensionTabUtil::GetTabId(contents->tab_contents()))); 374 375 DictionaryValue* object_args = new DictionaryValue(); 376 object_args->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( 377 ExtensionTabUtil::GetWindowIdOfTab(contents->tab_contents()))); 378 object_args->Set(tab_keys::kFromIndexKey, Value::CreateIntegerValue( 379 from_index)); 380 object_args->Set(tab_keys::kToIndexKey, Value::CreateIntegerValue( 381 to_index)); 382 args.Append(object_args); 383 384 std::string json_args; 385 base::JSONWriter::Write(&args, false, &json_args); 386 387 DispatchEvent(contents->profile(), events::kOnTabMoved, json_args); 388} 389 390void ExtensionBrowserEventRouter::TabUpdated(TabContents* contents, 391 bool did_navigate) { 392 TabEntry* entry = GetTabEntry(contents); 393 DictionaryValue* changed_properties = NULL; 394 395 DCHECK(entry); 396 397 if (did_navigate) 398 changed_properties = entry->DidNavigate(contents); 399 else 400 changed_properties = entry->UpdateLoadState(contents); 401 402 if (changed_properties) 403 DispatchTabUpdatedEvent(contents, changed_properties); 404} 405 406void ExtensionBrowserEventRouter::DispatchTabUpdatedEvent( 407 TabContents* contents, DictionaryValue* changed_properties) { 408 DCHECK(changed_properties); 409 DCHECK(contents); 410 411 // The state of the tab (as seen from the extension point of view) has 412 // changed. Send a notification to the extension. 413 ListValue args; 414 415 // First arg: The id of the tab that changed. 416 args.Append(Value::CreateIntegerValue(ExtensionTabUtil::GetTabId(contents))); 417 418 // Second arg: An object containing the changes to the tab state. 419 args.Append(changed_properties); 420 421 // Third arg: An object containing the state of the tab. 422 args.Append(ExtensionTabUtil::CreateTabValue(contents)); 423 424 std::string json_args; 425 base::JSONWriter::Write(&args, false, &json_args); 426 427 DispatchEvent(contents->profile(), events::kOnTabUpdated, json_args); 428} 429 430ExtensionBrowserEventRouter::TabEntry* ExtensionBrowserEventRouter::GetTabEntry( 431 const TabContents* contents) { 432 int tab_id = ExtensionTabUtil::GetTabId(contents); 433 std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id); 434 if (tab_entries_.end() == i) 435 return NULL; 436 return &i->second; 437} 438 439void ExtensionBrowserEventRouter::Observe(NotificationType type, 440 const NotificationSource& source, 441 const NotificationDetails& details) { 442 if (type == NotificationType::NAV_ENTRY_COMMITTED) { 443 NavigationController* source_controller = 444 Source<NavigationController>(source).ptr(); 445 TabUpdated(source_controller->tab_contents(), true); 446 } else if (type == NotificationType::TAB_CONTENTS_DESTROYED) { 447 // Tab was destroyed after being detached (without being re-attached). 448 TabContents* contents = Source<TabContents>(source).ptr(); 449 registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED, 450 Source<NavigationController>(&contents->controller())); 451 registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED, 452 Source<TabContents>(contents)); 453 } else if (type == NotificationType::BROWSER_WINDOW_READY) { 454 const Browser* browser = Source<const Browser>(source).ptr(); 455 OnBrowserWindowReady(browser); 456#if defined(OS_MACOSX) 457 } else if (type == NotificationType::NO_KEY_WINDOW) { 458 OnBrowserSetLastActive(NULL); 459#endif 460 } else { 461 NOTREACHED(); 462 } 463} 464 465void ExtensionBrowserEventRouter::TabChangedAt(TabContentsWrapper* contents, 466 int index, 467 TabChangeType change_type) { 468 TabUpdated(contents->tab_contents(), false); 469} 470 471void ExtensionBrowserEventRouter::TabReplacedAt( 472 TabStripModel* tab_strip_model, 473 TabContentsWrapper* old_contents, 474 TabContentsWrapper* new_contents, 475 int index) { 476 TabClosingAt(tab_strip_model, old_contents, index); 477 TabInsertedAt(new_contents, index, 478 tab_strip_model->selected_index() == index); 479} 480 481void ExtensionBrowserEventRouter::TabPinnedStateChanged( 482 TabContentsWrapper* contents, 483 int index) { 484 TabStripModel* tab_strip = NULL; 485 int tab_index; 486 487 if (ExtensionTabUtil::GetTabStripModel( 488 contents->tab_contents(), &tab_strip, &tab_index)) { 489 DictionaryValue* changed_properties = new DictionaryValue(); 490 changed_properties->SetBoolean(tab_keys::kPinnedKey, 491 tab_strip->IsTabPinned(tab_index)); 492 DispatchTabUpdatedEvent(contents->tab_contents(), changed_properties); 493 } 494} 495 496void ExtensionBrowserEventRouter::TabStripEmpty() {} 497 498void ExtensionBrowserEventRouter::DispatchOldPageActionEvent( 499 Profile* profile, 500 const std::string& extension_id, 501 const std::string& page_action_id, 502 int tab_id, 503 const std::string& url, 504 int button) { 505 ListValue args; 506 args.Append(Value::CreateStringValue(page_action_id)); 507 508 DictionaryValue* data = new DictionaryValue(); 509 data->Set(tab_keys::kTabIdKey, Value::CreateIntegerValue(tab_id)); 510 data->Set(tab_keys::kTabUrlKey, Value::CreateStringValue(url)); 511 data->Set(page_action_keys::kButtonKey, Value::CreateIntegerValue(button)); 512 args.Append(data); 513 514 std::string json_args; 515 base::JSONWriter::Write(&args, false, &json_args); 516 517 DispatchEventToExtension(profile, extension_id, "pageActions", json_args); 518} 519 520void ExtensionBrowserEventRouter::PageActionExecuted( 521 Profile* profile, 522 const std::string& extension_id, 523 const std::string& page_action_id, 524 int tab_id, 525 const std::string& url, 526 int button) { 527 DispatchOldPageActionEvent(profile, extension_id, page_action_id, tab_id, url, 528 button); 529 TabContentsWrapper* tab_contents = NULL; 530 if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(), 531 NULL, NULL, &tab_contents, NULL)) { 532 return; 533 } 534 DispatchEventWithTab(profile, extension_id, "pageAction.onClicked", 535 tab_contents->tab_contents()); 536} 537 538void ExtensionBrowserEventRouter::BrowserActionExecuted( 539 Profile* profile, const std::string& extension_id, Browser* browser) { 540 TabContentsWrapper* tab_contents = NULL; 541 int tab_id = 0; 542 if (!ExtensionTabUtil::GetDefaultTab(browser, &tab_contents, &tab_id)) 543 return; 544 DispatchEventWithTab(profile, extension_id, "browserAction.onClicked", 545 tab_contents->tab_contents()); 546} 547