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.
5 #include "chrome/browser/extensions/api/tabs/tabs_event_router.h"
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_tab_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_iterator.h"
17 #include "chrome/browser/ui/browser_list.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/common/extensions/extension_constants.h"
20 #include "content/public/browser/favicon_status.h"
21 #include "content/public/browser/navigation_controller.h"
22 #include "content/public/browser/navigation_entry.h"
23 #include "content/public/browser/notification_service.h"
24 #include "content/public/browser/notification_types.h"
25 #include "content/public/browser/web_contents.h"
27 using base::DictionaryValue
;
28 using base::ListValue
;
29 using base::FundamentalValue
;
30 using content::NavigationController
;
31 using content::WebContents
;
33 namespace extensions
{
37 namespace tabs
= api::tabs
;
39 void WillDispatchTabUpdatedEvent(
40 WebContents
* contents
,
41 const base::DictionaryValue
* changed_properties
,
42 content::BrowserContext
* context
,
43 const Extension
* extension
,
44 base::ListValue
* event_args
) {
45 // Overwrite the second argument with the appropriate properties dictionary,
46 // depending on extension permissions.
47 base::DictionaryValue
* properties_value
= changed_properties
->DeepCopy();
48 ExtensionTabUtil::ScrubTabValueForExtension(contents
,
51 event_args
->Set(1, properties_value
);
53 // Overwrite the third arg with our tab value as seen by this extension.
54 event_args
->Set(2, ExtensionTabUtil::CreateTabValue(contents
, extension
));
59 TabsEventRouter::TabEntry::TabEntry() : complete_waiting_on_load_(false),
63 base::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())
71 // Send "complete" state change.
72 complete_waiting_on_load_
= false;
73 base::DictionaryValue
* changed_properties
= new base::DictionaryValue();
74 changed_properties
->SetString(tabs_constants::kStatusKey
,
75 tabs_constants::kStatusValueComplete
);
76 return changed_properties
;
79 base::DictionaryValue
* TabsEventRouter::TabEntry::DidNavigate(
80 const WebContents
* contents
) {
81 // Send "loading" state change.
82 complete_waiting_on_load_
= true;
83 base::DictionaryValue
* changed_properties
= new base::DictionaryValue();
84 changed_properties
->SetString(tabs_constants::kStatusKey
,
85 tabs_constants::kStatusValueLoading
);
87 if (contents
->GetURL() != url_
) {
88 url_
= contents
->GetURL();
89 changed_properties
->SetString(tabs_constants::kUrlKey
, url_
.spec());
92 return changed_properties
;
95 TabsEventRouter::TabsEventRouter(Profile
* profile
) : profile_(profile
) {
96 DCHECK(!profile
->IsOffTheRecord());
98 BrowserList::AddObserver(this);
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
);
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();
117 TabsEventRouter::~TabsEventRouter() {
118 BrowserList::RemoveObserver(this);
121 void TabsEventRouter::OnBrowserAdded(Browser
* browser
) {
122 RegisterForBrowserNotifications(browser
);
125 void TabsEventRouter::RegisterForBrowserNotifications(Browser
* browser
) {
126 if (!profile_
->IsSameProfile(browser
->profile()))
128 // Start listening to TabStripModel events for this browser.
129 TabStripModel
* tab_strip
= browser
->tab_strip_model();
130 tab_strip
->AddObserver(this);
132 for (int i
= 0; i
< tab_strip
->count(); ++i
) {
133 RegisterForTabNotifications(tab_strip
->GetWebContentsAt(i
));
137 void TabsEventRouter::RegisterForTabNotifications(WebContents
* contents
) {
139 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
140 content::Source
<NavigationController
>(&contents
->GetController()));
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
));
149 registrar_
.Add(this, chrome::NOTIFICATION_FAVICON_UPDATED
,
150 content::Source
<WebContents
>(contents
));
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
));
162 void TabsEventRouter::OnBrowserRemoved(Browser
* browser
) {
163 if (!profile_
->IsSameProfile(browser
->profile()))
166 // Stop listening to TabStripModel events for this browser.
167 browser
->tab_strip_model()->RemoveObserver(this);
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
);
178 static void WillDispatchTabCreatedEvent(WebContents
* contents
,
180 content::BrowserContext
* context
,
181 const Extension
* extension
,
182 base::ListValue
* event_args
) {
183 base::DictionaryValue
* tab_value
= ExtensionTabUtil::CreateTabValue(
184 contents
, extension
);
186 event_args
->Append(tab_value
);
187 tab_value
->SetBoolean(tabs_constants::kSelectedKey
, active
);
190 void TabsEventRouter::TabCreatedAt(WebContents
* contents
,
193 Profile
* profile
= Profile::FromBrowserContext(contents
->GetBrowserContext());
194 scoped_ptr
<base::ListValue
> args(new base::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 EventRouter::Get(profile
)->BroadcastEvent(event
.Pass());
202 RegisterForTabNotifications(contents
);
205 void TabsEventRouter::TabInsertedAt(WebContents
* contents
,
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();
213 TabCreatedAt(contents
, index
, active
);
217 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
218 args
->Append(new FundamentalValue(tab_id
));
220 base::DictionaryValue
* object_args
= new base::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
);
228 Profile
* profile
= Profile::FromBrowserContext(contents
->GetBrowserContext());
229 DispatchEvent(profile
, tabs::OnAttached::kEventName
, args
.Pass(),
230 EventRouter::USER_GESTURE_UNKNOWN
);
233 void TabsEventRouter::TabDetachedAt(WebContents
* contents
, int index
) {
234 if (!GetTabEntry(contents
)) {
235 // The tab was removed. Don't send detach event.
239 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
241 new FundamentalValue(ExtensionTabUtil::GetTabId(contents
)));
243 base::DictionaryValue
* object_args
= new base::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
);
251 Profile
* profile
= Profile::FromBrowserContext(contents
->GetBrowserContext());
252 DispatchEvent(profile
,
253 tabs::OnDetached::kEventName
,
255 EventRouter::USER_GESTURE_UNKNOWN
);
258 void TabsEventRouter::TabClosingAt(TabStripModel
* tab_strip_model
,
259 WebContents
* contents
,
261 int tab_id
= ExtensionTabUtil::GetTabId(contents
);
263 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
264 args
->Append(new FundamentalValue(tab_id
));
266 base::DictionaryValue
* object_args
= new base::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
);
273 Profile
* profile
= Profile::FromBrowserContext(contents
->GetBrowserContext());
274 DispatchEvent(profile
,
275 tabs::OnRemoved::kEventName
,
277 EventRouter::USER_GESTURE_UNKNOWN
);
279 int removed_count
= tab_entries_
.erase(tab_id
);
280 DCHECK_GT(removed_count
, 0);
282 UnregisterForTabNotifications(contents
);
285 void TabsEventRouter::ActiveTabChanged(WebContents
* old_contents
,
286 WebContents
* new_contents
,
289 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
290 int tab_id
= ExtensionTabUtil::GetTabId(new_contents
);
291 args
->Append(new FundamentalValue(tab_id
));
293 base::DictionaryValue
* object_args
= new base::DictionaryValue();
294 object_args
->Set(tabs_constants::kWindowIdKey
,
295 new FundamentalValue(
296 ExtensionTabUtil::GetWindowIdOfTab(new_contents
)));
297 args
->Append(object_args
);
299 // The onActivated event replaced onActiveChanged and onSelectionChanged. The
300 // deprecated events take two arguments: tabId, {windowId}.
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
<base::ListValue
>(args
->DeepCopy()),
311 DispatchEvent(profile
,
312 tabs::OnActiveChanged::kEventName
,
313 scoped_ptr
<base::ListValue
>(args
->DeepCopy()),
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
);
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
<base::ListValue
> all_tabs(new base::ListValue
);
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
);
335 int tab_id
= ExtensionTabUtil::GetTabId(contents
);
336 all_tabs
->Append(new FundamentalValue(tab_id
));
339 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
340 scoped_ptr
<base::DictionaryValue
> select_info(new base::DictionaryValue
);
343 tabs_constants::kWindowIdKey
,
344 new FundamentalValue(
345 ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model
)));
347 select_info
->Set(tabs_constants::kTabIdsKey
, all_tabs
.release());
348 args
->Append(select_info
.release());
350 // The onHighlighted event replaced onHighlightChanged.
351 Profile
* profile
= tab_strip_model
->profile();
352 DispatchEvent(profile
,
353 tabs::OnHighlightChanged::kEventName
,
354 scoped_ptr
<base::ListValue
>(args
->DeepCopy()),
355 EventRouter::USER_GESTURE_UNKNOWN
);
356 DispatchEvent(profile
,
357 tabs::OnHighlighted::kEventName
,
359 EventRouter::USER_GESTURE_UNKNOWN
);
362 void TabsEventRouter::TabMoved(WebContents
* contents
,
365 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
367 new FundamentalValue(ExtensionTabUtil::GetTabId(contents
)));
369 base::DictionaryValue
* object_args
= new base::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
);
379 Profile
* profile
= Profile::FromBrowserContext(contents
->GetBrowserContext());
380 DispatchEvent(profile
,
381 tabs::OnMoved::kEventName
,
383 EventRouter::USER_GESTURE_UNKNOWN
);
386 void TabsEventRouter::TabUpdated(WebContents
* contents
, bool did_navigate
) {
387 TabEntry
* entry
= GetTabEntry(contents
);
388 scoped_ptr
<base::DictionaryValue
> changed_properties
;
393 changed_properties
.reset(entry
->DidNavigate(contents
));
395 changed_properties
.reset(entry
->UpdateLoadState(contents
));
397 if (changed_properties
)
398 DispatchTabUpdatedEvent(contents
, changed_properties
.Pass());
401 void TabsEventRouter::FaviconUrlUpdated(WebContents
* contents
) {
402 content::NavigationEntry
* entry
=
403 contents
->GetController().GetVisibleEntry();
404 if (!entry
|| !entry
->GetFavicon().valid
)
406 scoped_ptr
<base::DictionaryValue
> changed_properties(
407 new base::DictionaryValue
);
408 changed_properties
->SetString(
409 tabs_constants::kFaviconUrlKey
,
410 entry
->GetFavicon().url
.possibly_invalid_spec());
411 DispatchTabUpdatedEvent(contents
, changed_properties
.Pass());
414 void TabsEventRouter::DispatchEvent(
416 const std::string
& event_name
,
417 scoped_ptr
<base::ListValue
> args
,
418 EventRouter::UserGestureState user_gesture
) {
419 EventRouter
* event_router
= EventRouter::Get(profile
);
420 if (!profile_
->IsSameProfile(profile
) || !event_router
)
423 scoped_ptr
<Event
> event(new Event(event_name
, args
.Pass()));
424 event
->restrict_to_browser_context
= profile
;
425 event
->user_gesture
= user_gesture
;
426 event_router
->BroadcastEvent(event
.Pass());
429 void TabsEventRouter::DispatchSimpleBrowserEvent(
430 Profile
* profile
, const int window_id
, const std::string
& event_name
) {
431 if (!profile_
->IsSameProfile(profile
))
434 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
435 args
->Append(new FundamentalValue(window_id
));
437 DispatchEvent(profile
,
440 EventRouter::USER_GESTURE_UNKNOWN
);
443 void TabsEventRouter::DispatchTabUpdatedEvent(
444 WebContents
* contents
,
445 scoped_ptr
<base::DictionaryValue
> changed_properties
) {
446 DCHECK(changed_properties
);
449 // The state of the tab (as seen from the extension point of view) has
450 // changed. Send a notification to the extension.
451 scoped_ptr
<base::ListValue
> args_base(new base::ListValue
);
453 // First arg: The id of the tab that changed.
454 args_base
->AppendInteger(ExtensionTabUtil::GetTabId(contents
));
456 // Second arg: An object containing the changes to the tab state. Filled in
457 // by WillDispatchTabUpdatedEvent as a copy of changed_properties, if the
458 // extension has the tabs permission.
460 // Third arg: An object containing the state of the tab. Filled in by
461 // WillDispatchTabUpdatedEvent.
462 Profile
* profile
= Profile::FromBrowserContext(contents
->GetBrowserContext());
464 scoped_ptr
<Event
> event(
465 new Event(tabs::OnUpdated::kEventName
, args_base
.Pass()));
466 event
->restrict_to_browser_context
= profile
;
467 event
->user_gesture
= EventRouter::USER_GESTURE_NOT_ENABLED
;
468 event
->will_dispatch_callback
=
469 base::Bind(&WillDispatchTabUpdatedEvent
,
471 changed_properties
.get());
472 EventRouter::Get(profile
)->BroadcastEvent(event
.Pass());
475 TabsEventRouter::TabEntry
* TabsEventRouter::GetTabEntry(
476 const WebContents
* contents
) {
477 int tab_id
= ExtensionTabUtil::GetTabId(contents
);
478 std::map
<int, TabEntry
>::iterator i
= tab_entries_
.find(tab_id
);
479 if (tab_entries_
.end() == i
)
484 void TabsEventRouter::Observe(int type
,
485 const content::NotificationSource
& source
,
486 const content::NotificationDetails
& details
) {
487 if (type
== content::NOTIFICATION_NAV_ENTRY_COMMITTED
) {
488 NavigationController
* source_controller
=
489 content::Source
<NavigationController
>(source
).ptr();
490 TabUpdated(source_controller
->GetWebContents(), true);
491 } else if (type
== content::NOTIFICATION_WEB_CONTENTS_DESTROYED
) {
492 // Tab was destroyed after being detached (without being re-attached).
493 WebContents
* contents
= content::Source
<WebContents
>(source
).ptr();
494 registrar_
.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
495 content::Source
<NavigationController
>(&contents
->GetController()));
496 registrar_
.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
497 content::Source
<WebContents
>(contents
));
498 registrar_
.Remove(this, chrome::NOTIFICATION_FAVICON_UPDATED
,
499 content::Source
<WebContents
>(contents
));
500 } else if (type
== chrome::NOTIFICATION_FAVICON_UPDATED
) {
501 bool icon_url_changed
= *content::Details
<bool>(details
).ptr();
502 if (icon_url_changed
)
503 FaviconUrlUpdated(content::Source
<WebContents
>(source
).ptr());
509 void TabsEventRouter::TabChangedAt(WebContents
* contents
,
511 TabChangeType change_type
) {
512 TabUpdated(contents
, false);
515 void TabsEventRouter::TabReplacedAt(TabStripModel
* tab_strip_model
,
516 WebContents
* old_contents
,
517 WebContents
* new_contents
,
519 // Notify listeners that the next tabs closing or being added are due to
520 // WebContents being swapped.
521 const int new_tab_id
= ExtensionTabUtil::GetTabId(new_contents
);
522 const int old_tab_id
= ExtensionTabUtil::GetTabId(old_contents
);
523 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
524 args
->Append(new FundamentalValue(new_tab_id
));
525 args
->Append(new FundamentalValue(old_tab_id
));
527 DispatchEvent(Profile::FromBrowserContext(new_contents
->GetBrowserContext()),
528 tabs::OnReplaced::kEventName
,
530 EventRouter::USER_GESTURE_UNKNOWN
);
532 // Update tab_entries_.
533 const int removed_count
= tab_entries_
.erase(old_tab_id
);
534 DCHECK_GT(removed_count
, 0);
535 UnregisterForTabNotifications(old_contents
);
537 if (!GetTabEntry(new_contents
)) {
538 tab_entries_
[new_tab_id
] = TabEntry();
539 RegisterForTabNotifications(new_contents
);
543 void TabsEventRouter::TabPinnedStateChanged(WebContents
* contents
, int index
) {
544 TabStripModel
* tab_strip
= NULL
;
547 if (ExtensionTabUtil::GetTabStripModel(contents
, &tab_strip
, &tab_index
)) {
548 scoped_ptr
<base::DictionaryValue
> changed_properties(
549 new base::DictionaryValue());
550 changed_properties
->SetBoolean(tabs_constants::kPinnedKey
,
551 tab_strip
->IsTabPinned(tab_index
));
552 DispatchTabUpdatedEvent(contents
, changed_properties
.Pass());
556 } // namespace extensions