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
23 namespace events = extension_event_names;
24 namespace tab_keys = extension_tabs_module_constants;
25 namespace page_action_keys = extension_page_actions_module_constants;
26
TabEntry()27 ExtensionBrowserEventRouter::TabEntry::TabEntry()
28 : complete_waiting_on_load_(false),
29 url_() {
30 }
31
UpdateLoadState(const TabContents * contents)32 DictionaryValue* 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
DidNavigate(const TabContents * contents)48 DictionaryValue* 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
DispatchEvent(Profile * profile,const char * event_name,const std::string & json_args)64 static 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
DispatchEventToExtension(Profile * profile,const std::string & extension_id,const char * event_name,const std::string & json_args)73 static 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
DispatchEventWithTab(Profile * profile,const std::string & extension_id,const char * event_name,const TabContents * tab_contents)83 static 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
DispatchSimpleBrowserEvent(Profile * profile,const int window_id,const char * event_name)98 static 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
Init()110 void 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
ExtensionBrowserEventRouter(Profile * profile)145 ExtensionBrowserEventRouter::ExtensionBrowserEventRouter(Profile* profile)
146 : initialized_(false),
147 focused_window_id_(extension_misc::kUnknownWindowId),
148 profile_(profile) {
149 DCHECK(!profile->IsOffTheRecord());
150 }
151
~ExtensionBrowserEventRouter()152 ExtensionBrowserEventRouter::~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
OnBrowserAdded(const Browser * browser)161 void ExtensionBrowserEventRouter::OnBrowserAdded(const Browser* browser) {
162 RegisterForBrowserNotifications(browser);
163 }
164
RegisterForBrowserNotifications(const Browser * browser)165 void 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
RegisterForTabNotifications(TabContents * contents)182 void 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
UnregisterForTabNotifications(TabContents * contents)195 void 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
OnBrowserWindowReady(const Browser * browser)203 void 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
OnBrowserRemoved(const Browser * browser)216 void 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)
NativeFocusWillChange(gfx::NativeView focused_before,gfx::NativeView focused_now)229 void 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)
ActiveWindowChanged(GdkWindow * active_window)236 void ExtensionBrowserEventRouter::ActiveWindowChanged(
237 GdkWindow* active_window) {
238 if (!active_window)
239 OnBrowserSetLastActive(NULL);
240 }
241 #endif
242
OnBrowserSetLastActive(const Browser * browser)243 void 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
TabCreatedAt(TabContents * contents,int index,bool foreground)262 void ExtensionBrowserEventRouter::TabCreatedAt(TabContents* contents,
263 int index,
264 bool foreground) {
265 DispatchEventWithTab(contents->profile(), "", events::kOnTabCreated,
266 contents);
267
268 RegisterForTabNotifications(contents);
269 }
270
TabInsertedAt(TabContentsWrapper * contents,int index,bool foreground)271 void 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
TabDetachedAt(TabContentsWrapper * contents,int index)299 void 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
TabClosingAt(TabStripModel * tab_strip_model,TabContentsWrapper * contents,int index)323 void 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
TabSelectedAt(TabContentsWrapper * old_contents,TabContentsWrapper * new_contents,int index,bool user_gesture)347 void 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
TabMoved(TabContentsWrapper * contents,int from_index,int to_index)371 void 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
TabUpdated(TabContents * contents,bool did_navigate)393 void 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
DispatchTabUpdatedEvent(TabContents * contents,DictionaryValue * changed_properties)409 void 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
GetTabEntry(const TabContents * contents)433 ExtensionBrowserEventRouter::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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)442 void 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
TabChangedAt(TabContentsWrapper * contents,int index,TabChangeType change_type)468 void ExtensionBrowserEventRouter::TabChangedAt(TabContentsWrapper* contents,
469 int index,
470 TabChangeType change_type) {
471 TabUpdated(contents->tab_contents(), false);
472 }
473
TabReplacedAt(TabStripModel * tab_strip_model,TabContentsWrapper * old_contents,TabContentsWrapper * new_contents,int index)474 void 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
TabPinnedStateChanged(TabContentsWrapper * contents,int index)483 void 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
TabStripEmpty()498 void ExtensionBrowserEventRouter::TabStripEmpty() {}
499
DispatchOldPageActionEvent(Profile * profile,const std::string & extension_id,const std::string & page_action_id,int tab_id,const std::string & url,int button)500 void 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
PageActionExecuted(Profile * profile,const std::string & extension_id,const std::string & page_action_id,int tab_id,const std::string & url,int button)522 void 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
BrowserActionExecuted(Profile * profile,const std::string & extension_id,Browser * browser)540 void 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