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