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 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 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 371void 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 393void 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 409void 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 433ExtensionBrowserEventRouter::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 442void 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 468void ExtensionBrowserEventRouter::TabChangedAt(TabContentsWrapper* contents, 469 int index, 470 TabChangeType change_type) { 471 TabUpdated(contents->tab_contents(), false); 472} 473 474void 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 483void 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 498void ExtensionBrowserEventRouter::TabStripEmpty() {} 499 500void 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 522void 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 540void 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