1 // Copyright 2013 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/api/tabs/tabs_event_router.h" 6 7 #include "base/json/json_writer.h" 8 #include "base/values.h" 9 #include "chrome/browser/chrome_notification_types.h" 10 #include "chrome/browser/extensions/api/tabs/tabs_constants.h" 11 #include "chrome/browser/extensions/api/tabs/tabs_windows_api.h" 12 #include "chrome/browser/extensions/api/tabs/windows_event_router.h" 13 #include "chrome/browser/extensions/extension_system.h" 14 #include "chrome/browser/extensions/extension_tab_util.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/ui/browser.h" 17 #include "chrome/browser/ui/browser_iterator.h" 18 #include "chrome/browser/ui/browser_list.h" 19 #include "chrome/browser/ui/tabs/tab_strip_model.h" 20 #include "chrome/common/extensions/extension_constants.h" 21 #include "content/public/browser/favicon_status.h" 22 #include "content/public/browser/navigation_controller.h" 23 #include "content/public/browser/navigation_entry.h" 24 #include "content/public/browser/notification_service.h" 25 #include "content/public/browser/notification_types.h" 26 #include "content/public/browser/web_contents.h" 27 28 using base::DictionaryValue; 29 using base::ListValue; 30 using base::FundamentalValue; 31 using content::NavigationController; 32 using content::WebContents; 33 34 namespace extensions { 35 36 namespace { 37 38 namespace tabs = api::tabs; 39 40 void WillDispatchTabUpdatedEvent(WebContents* contents, 41 const DictionaryValue* changed_properties, 42 content::BrowserContext* context, 43 const Extension* extension, 44 ListValue* event_args) { 45 // Overwrite the second argument with the appropriate properties dictionary, 46 // depending on extension permissions. 47 DictionaryValue* properties_value = changed_properties->DeepCopy(); 48 ExtensionTabUtil::ScrubTabValueForExtension(contents, 49 extension, 50 properties_value); 51 event_args->Set(1, properties_value); 52 53 // Overwrite the third arg with our tab value as seen by this extension. 54 event_args->Set(2, ExtensionTabUtil::CreateTabValue(contents, extension)); 55 } 56 57 } // namespace 58 59 TabsEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false), 60 url_() { 61 } 62 63 DictionaryValue* TabsEventRouter::TabEntry::UpdateLoadState( 64 const WebContents* contents) { 65 // The tab may go in & out of loading (for instance if iframes navigate). 66 // We only want to respond to the first change from loading to !loading after 67 // the NAV_ENTRY_COMMITTED was fired. 68 if (!complete_waiting_on_load_ || contents->IsLoading()) 69 return NULL; 70 71 // Send "complete" state change. 72 complete_waiting_on_load_ = false; 73 DictionaryValue* changed_properties = new DictionaryValue(); 74 changed_properties->SetString(tabs_constants::kStatusKey, 75 tabs_constants::kStatusValueComplete); 76 return changed_properties; 77 } 78 79 DictionaryValue* TabsEventRouter::TabEntry::DidNavigate( 80 const WebContents* contents) { 81 // Send "loading" state change. 82 complete_waiting_on_load_ = true; 83 DictionaryValue* changed_properties = new DictionaryValue(); 84 changed_properties->SetString(tabs_constants::kStatusKey, 85 tabs_constants::kStatusValueLoading); 86 87 if (contents->GetURL() != url_) { 88 url_ = contents->GetURL(); 89 changed_properties->SetString(tabs_constants::kUrlKey, url_.spec()); 90 } 91 92 return changed_properties; 93 } 94 95 TabsEventRouter::TabsEventRouter(Profile* profile) : profile_(profile) { 96 DCHECK(!profile->IsOffTheRecord()); 97 98 BrowserList::AddObserver(this); 99 100 // Init() can happen after the browser is running, so catch up with any 101 // windows that already exist. 102 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 103 RegisterForBrowserNotifications(*it); 104 105 // Also catch up our internal bookkeeping of tab entries. 106 Browser* browser = *it; 107 if (browser->tab_strip_model()) { 108 for (int i = 0; i < browser->tab_strip_model()->count(); ++i) { 109 WebContents* contents = browser->tab_strip_model()->GetWebContentsAt(i); 110 int tab_id = ExtensionTabUtil::GetTabId(contents); 111 tab_entries_[tab_id] = TabEntry(); 112 } 113 } 114 } 115 } 116 117 TabsEventRouter::~TabsEventRouter() { 118 BrowserList::RemoveObserver(this); 119 } 120 121 void TabsEventRouter::OnBrowserAdded(Browser* browser) { 122 RegisterForBrowserNotifications(browser); 123 } 124 125 void TabsEventRouter::RegisterForBrowserNotifications(Browser* browser) { 126 if (!profile_->IsSameProfile(browser->profile())) 127 return; 128 // Start listening to TabStripModel events for this browser. 129 TabStripModel* tab_strip = browser->tab_strip_model(); 130 tab_strip->AddObserver(this); 131 132 for (int i = 0; i < tab_strip->count(); ++i) { 133 RegisterForTabNotifications(tab_strip->GetWebContentsAt(i)); 134 } 135 } 136 137 void TabsEventRouter::RegisterForTabNotifications(WebContents* contents) { 138 registrar_.Add( 139 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 140 content::Source<NavigationController>(&contents->GetController())); 141 142 // Observing NOTIFICATION_WEB_CONTENTS_DESTROYED is necessary because it's 143 // possible for tabs to be created, detached and then destroyed without 144 // ever having been re-attached and closed. This happens in the case of 145 // a devtools WebContents that is opened in window, docked, then closed. 146 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 147 content::Source<WebContents>(contents)); 148 149 registrar_.Add(this, chrome::NOTIFICATION_FAVICON_UPDATED, 150 content::Source<WebContents>(contents)); 151 } 152 153 void TabsEventRouter::UnregisterForTabNotifications(WebContents* contents) { 154 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 155 content::Source<NavigationController>(&contents->GetController())); 156 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 157 content::Source<WebContents>(contents)); 158 registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED, 159 content::Source<WebContents>(contents)); 160 } 161 162 void TabsEventRouter::OnBrowserRemoved(Browser* browser) { 163 if (!profile_->IsSameProfile(browser->profile())) 164 return; 165 166 // Stop listening to TabStripModel events for this browser. 167 browser->tab_strip_model()->RemoveObserver(this); 168 } 169 170 void TabsEventRouter::OnBrowserSetLastActive(Browser* browser) { 171 TabsWindowsAPI* tabs_window_api = TabsWindowsAPI::Get(profile_); 172 if (tabs_window_api) { 173 tabs_window_api->windows_event_router()->OnActiveWindowChanged( 174 browser ? browser->extension_window_controller() : NULL); 175 } 176 } 177 178 static void WillDispatchTabCreatedEvent(WebContents* contents, 179 bool active, 180 content::BrowserContext* context, 181 const Extension* extension, 182 ListValue* event_args) { 183 DictionaryValue* tab_value = ExtensionTabUtil::CreateTabValue( 184 contents, extension); 185 event_args->Clear(); 186 event_args->Append(tab_value); 187 tab_value->SetBoolean(tabs_constants::kSelectedKey, active); 188 } 189 190 void TabsEventRouter::TabCreatedAt(WebContents* contents, 191 int index, 192 bool active) { 193 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); 194 scoped_ptr<ListValue> args(new ListValue); 195 scoped_ptr<Event> event(new Event(tabs::OnCreated::kEventName, args.Pass())); 196 event->restrict_to_browser_context = profile; 197 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED; 198 event->will_dispatch_callback = 199 base::Bind(&WillDispatchTabCreatedEvent, contents, active); 200 ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(event.Pass()); 201 202 RegisterForTabNotifications(contents); 203 } 204 205 void TabsEventRouter::TabInsertedAt(WebContents* contents, 206 int index, 207 bool active) { 208 // If tab is new, send created event. 209 int tab_id = ExtensionTabUtil::GetTabId(contents); 210 if (!GetTabEntry(contents)) { 211 tab_entries_[tab_id] = TabEntry(); 212 213 TabCreatedAt(contents, index, active); 214 return; 215 } 216 217 scoped_ptr<ListValue> args(new ListValue); 218 args->Append(new FundamentalValue(tab_id)); 219 220 DictionaryValue* object_args = new DictionaryValue(); 221 object_args->Set(tabs_constants::kNewWindowIdKey, 222 new FundamentalValue( 223 ExtensionTabUtil::GetWindowIdOfTab(contents))); 224 object_args->Set(tabs_constants::kNewPositionKey, 225 new FundamentalValue(index)); 226 args->Append(object_args); 227 228 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); 229 DispatchEvent(profile, tabs::OnAttached::kEventName, args.Pass(), 230 EventRouter::USER_GESTURE_UNKNOWN); 231 } 232 233 void TabsEventRouter::TabDetachedAt(WebContents* contents, int index) { 234 if (!GetTabEntry(contents)) { 235 // The tab was removed. Don't send detach event. 236 return; 237 } 238 239 scoped_ptr<ListValue> args(new ListValue); 240 args->Append( 241 new FundamentalValue(ExtensionTabUtil::GetTabId(contents))); 242 243 DictionaryValue* object_args = new DictionaryValue(); 244 object_args->Set(tabs_constants::kOldWindowIdKey, 245 new FundamentalValue( 246 ExtensionTabUtil::GetWindowIdOfTab(contents))); 247 object_args->Set(tabs_constants::kOldPositionKey, 248 new FundamentalValue(index)); 249 args->Append(object_args); 250 251 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); 252 DispatchEvent(profile, 253 tabs::OnDetached::kEventName, 254 args.Pass(), 255 EventRouter::USER_GESTURE_UNKNOWN); 256 } 257 258 void TabsEventRouter::TabClosingAt(TabStripModel* tab_strip_model, 259 WebContents* contents, 260 int index) { 261 int tab_id = ExtensionTabUtil::GetTabId(contents); 262 263 scoped_ptr<ListValue> args(new ListValue); 264 args->Append(new FundamentalValue(tab_id)); 265 266 DictionaryValue* object_args = new DictionaryValue(); 267 object_args->SetInteger(tabs_constants::kWindowIdKey, 268 ExtensionTabUtil::GetWindowIdOfTab(contents)); 269 object_args->SetBoolean(tabs_constants::kWindowClosing, 270 tab_strip_model->closing_all()); 271 args->Append(object_args); 272 273 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); 274 DispatchEvent(profile, 275 tabs::OnRemoved::kEventName, 276 args.Pass(), 277 EventRouter::USER_GESTURE_UNKNOWN); 278 279 int removed_count = tab_entries_.erase(tab_id); 280 DCHECK_GT(removed_count, 0); 281 282 UnregisterForTabNotifications(contents); 283 } 284 285 void TabsEventRouter::ActiveTabChanged(WebContents* old_contents, 286 WebContents* new_contents, 287 int index, 288 int reason) { 289 scoped_ptr<ListValue> args(new ListValue); 290 int tab_id = ExtensionTabUtil::GetTabId(new_contents); 291 args->Append(new FundamentalValue(tab_id)); 292 293 DictionaryValue* object_args = new DictionaryValue(); 294 object_args->Set(tabs_constants::kWindowIdKey, 295 new FundamentalValue( 296 ExtensionTabUtil::GetWindowIdOfTab(new_contents))); 297 args->Append(object_args); 298 299 // The onActivated event replaced onActiveChanged and onSelectionChanged. The 300 // deprecated events take two arguments: tabId, {windowId}. 301 Profile* profile = 302 Profile::FromBrowserContext(new_contents->GetBrowserContext()); 303 EventRouter::UserGestureState gesture = 304 reason & CHANGE_REASON_USER_GESTURE 305 ? EventRouter::USER_GESTURE_ENABLED 306 : EventRouter::USER_GESTURE_NOT_ENABLED; 307 DispatchEvent(profile, 308 tabs::OnSelectionChanged::kEventName, 309 scoped_ptr<ListValue>(args->DeepCopy()), 310 gesture); 311 DispatchEvent(profile, 312 tabs::OnActiveChanged::kEventName, 313 scoped_ptr<ListValue>(args->DeepCopy()), 314 gesture); 315 316 // The onActivated event takes one argument: {windowId, tabId}. 317 args->Remove(0, NULL); 318 object_args->Set(tabs_constants::kTabIdKey, 319 new FundamentalValue(tab_id)); 320 DispatchEvent(profile, tabs::OnActivated::kEventName, args.Pass(), gesture); 321 } 322 323 void TabsEventRouter::TabSelectionChanged( 324 TabStripModel* tab_strip_model, 325 const ui::ListSelectionModel& old_model) { 326 ui::ListSelectionModel::SelectedIndices new_selection = 327 tab_strip_model->selection_model().selected_indices(); 328 scoped_ptr<ListValue> all_tabs(new ListValue); 329 330 for (size_t i = 0; i < new_selection.size(); ++i) { 331 int index = new_selection[i]; 332 WebContents* contents = tab_strip_model->GetWebContentsAt(index); 333 if (!contents) 334 break; 335 int tab_id = ExtensionTabUtil::GetTabId(contents); 336 all_tabs->Append(new FundamentalValue(tab_id)); 337 } 338 339 scoped_ptr<ListValue> args(new ListValue); 340 scoped_ptr<DictionaryValue> select_info(new DictionaryValue); 341 342 select_info->Set( 343 tabs_constants::kWindowIdKey, 344 new FundamentalValue( 345 ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model))); 346 347 select_info->Set(tabs_constants::kTabIdsKey, all_tabs.release()); 348 args->Append(select_info.release()); 349 350 // The onHighlighted event replaced onHighlightChanged. 351 Profile* profile = tab_strip_model->profile(); 352 DispatchEvent(profile, 353 tabs::OnHighlightChanged::kEventName, 354 scoped_ptr<ListValue>(args->DeepCopy()), 355 EventRouter::USER_GESTURE_UNKNOWN); 356 DispatchEvent(profile, 357 tabs::OnHighlighted::kEventName, 358 args.Pass(), 359 EventRouter::USER_GESTURE_UNKNOWN); 360 } 361 362 void TabsEventRouter::TabMoved(WebContents* contents, 363 int from_index, 364 int to_index) { 365 scoped_ptr<ListValue> args(new ListValue); 366 args->Append( 367 new FundamentalValue(ExtensionTabUtil::GetTabId(contents))); 368 369 DictionaryValue* object_args = new DictionaryValue(); 370 object_args->Set(tabs_constants::kWindowIdKey, 371 new FundamentalValue( 372 ExtensionTabUtil::GetWindowIdOfTab(contents))); 373 object_args->Set(tabs_constants::kFromIndexKey, 374 new FundamentalValue(from_index)); 375 object_args->Set(tabs_constants::kToIndexKey, 376 new FundamentalValue(to_index)); 377 args->Append(object_args); 378 379 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); 380 DispatchEvent(profile, 381 tabs::OnMoved::kEventName, 382 args.Pass(), 383 EventRouter::USER_GESTURE_UNKNOWN); 384 } 385 386 void TabsEventRouter::TabUpdated(WebContents* contents, bool did_navigate) { 387 TabEntry* entry = GetTabEntry(contents); 388 scoped_ptr<DictionaryValue> changed_properties; 389 390 CHECK(entry); 391 392 if (did_navigate) 393 changed_properties.reset(entry->DidNavigate(contents)); 394 else 395 changed_properties.reset(entry->UpdateLoadState(contents)); 396 397 if (changed_properties) 398 DispatchTabUpdatedEvent(contents, changed_properties.Pass()); 399 } 400 401 void TabsEventRouter::FaviconUrlUpdated(WebContents* contents) { 402 content::NavigationEntry* entry = 403 contents->GetController().GetVisibleEntry(); 404 if (!entry || !entry->GetFavicon().valid) 405 return; 406 scoped_ptr<DictionaryValue> changed_properties(new DictionaryValue); 407 changed_properties->SetString( 408 tabs_constants::kFaviconUrlKey, 409 entry->GetFavicon().url.possibly_invalid_spec()); 410 DispatchTabUpdatedEvent(contents, changed_properties.Pass()); 411 } 412 413 void TabsEventRouter::DispatchEvent( 414 Profile* profile, 415 const std::string& event_name, 416 scoped_ptr<ListValue> args, 417 EventRouter::UserGestureState user_gesture) { 418 if (!profile_->IsSameProfile(profile) || 419 !ExtensionSystem::Get(profile)->event_router()) 420 return; 421 422 scoped_ptr<Event> event(new Event(event_name, args.Pass())); 423 event->restrict_to_browser_context = profile; 424 event->user_gesture = user_gesture; 425 ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(event.Pass()); 426 } 427 428 void TabsEventRouter::DispatchSimpleBrowserEvent( 429 Profile* profile, const int window_id, const std::string& event_name) { 430 if (!profile_->IsSameProfile(profile)) 431 return; 432 433 scoped_ptr<ListValue> args(new ListValue); 434 args->Append(new FundamentalValue(window_id)); 435 436 DispatchEvent(profile, 437 event_name, 438 args.Pass(), 439 EventRouter::USER_GESTURE_UNKNOWN); 440 } 441 442 void TabsEventRouter::DispatchTabUpdatedEvent( 443 WebContents* contents, scoped_ptr<DictionaryValue> changed_properties) { 444 DCHECK(changed_properties); 445 DCHECK(contents); 446 447 // The state of the tab (as seen from the extension point of view) has 448 // changed. Send a notification to the extension. 449 scoped_ptr<ListValue> args_base(new ListValue); 450 451 // First arg: The id of the tab that changed. 452 args_base->AppendInteger(ExtensionTabUtil::GetTabId(contents)); 453 454 // Second arg: An object containing the changes to the tab state. Filled in 455 // by WillDispatchTabUpdatedEvent as a copy of changed_properties, if the 456 // extension has the tabs permission. 457 458 // Third arg: An object containing the state of the tab. Filled in by 459 // WillDispatchTabUpdatedEvent. 460 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); 461 462 scoped_ptr<Event> event( 463 new Event(tabs::OnUpdated::kEventName, args_base.Pass())); 464 event->restrict_to_browser_context = profile; 465 event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED; 466 event->will_dispatch_callback = 467 base::Bind(&WillDispatchTabUpdatedEvent, 468 contents, 469 changed_properties.get()); 470 ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(event.Pass()); 471 } 472 473 TabsEventRouter::TabEntry* TabsEventRouter::GetTabEntry( 474 const WebContents* contents) { 475 int tab_id = ExtensionTabUtil::GetTabId(contents); 476 std::map<int, TabEntry>::iterator i = tab_entries_.find(tab_id); 477 if (tab_entries_.end() == i) 478 return NULL; 479 return &i->second; 480 } 481 482 void TabsEventRouter::Observe(int type, 483 const content::NotificationSource& source, 484 const content::NotificationDetails& details) { 485 if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) { 486 NavigationController* source_controller = 487 content::Source<NavigationController>(source).ptr(); 488 TabUpdated(source_controller->GetWebContents(), true); 489 } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { 490 // Tab was destroyed after being detached (without being re-attached). 491 WebContents* contents = content::Source<WebContents>(source).ptr(); 492 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 493 content::Source<NavigationController>(&contents->GetController())); 494 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 495 content::Source<WebContents>(contents)); 496 registrar_.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED, 497 content::Source<WebContents>(contents)); 498 } else if (type == chrome::NOTIFICATION_FAVICON_UPDATED) { 499 bool icon_url_changed = *content::Details<bool>(details).ptr(); 500 if (icon_url_changed) 501 FaviconUrlUpdated(content::Source<WebContents>(source).ptr()); 502 } else { 503 NOTREACHED(); 504 } 505 } 506 507 void TabsEventRouter::TabChangedAt(WebContents* contents, 508 int index, 509 TabChangeType change_type) { 510 TabUpdated(contents, false); 511 } 512 513 void TabsEventRouter::TabReplacedAt(TabStripModel* tab_strip_model, 514 WebContents* old_contents, 515 WebContents* new_contents, 516 int index) { 517 // Notify listeners that the next tabs closing or being added are due to 518 // WebContents being swapped. 519 const int new_tab_id = ExtensionTabUtil::GetTabId(new_contents); 520 const int old_tab_id = ExtensionTabUtil::GetTabId(old_contents); 521 scoped_ptr<ListValue> args(new ListValue); 522 args->Append(new FundamentalValue(new_tab_id)); 523 args->Append(new FundamentalValue(old_tab_id)); 524 525 DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()), 526 tabs::OnReplaced::kEventName, 527 args.Pass(), 528 EventRouter::USER_GESTURE_UNKNOWN); 529 530 // Update tab_entries_. 531 const int removed_count = tab_entries_.erase(old_tab_id); 532 DCHECK_GT(removed_count, 0); 533 UnregisterForTabNotifications(old_contents); 534 535 if (!GetTabEntry(new_contents)) { 536 tab_entries_[new_tab_id] = TabEntry(); 537 RegisterForTabNotifications(new_contents); 538 } 539 } 540 541 void TabsEventRouter::TabPinnedStateChanged(WebContents* contents, int index) { 542 TabStripModel* tab_strip = NULL; 543 int tab_index; 544 545 if (ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index)) { 546 scoped_ptr<DictionaryValue> changed_properties(new DictionaryValue()); 547 changed_properties->SetBoolean(tabs_constants::kPinnedKey, 548 tab_strip->IsTabPinned(tab_index)); 549 DispatchTabUpdatedEvent(contents, changed_properties.Pass()); 550 } 551 } 552 553 } // namespace extensions 554