Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / extension_action / extension_action_api.cc
1 // Copyright (c) 2012 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/extension_action/extension_action_api.h"
6
7 #include "base/lazy_instance.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/values.h"
10 #include "chrome/browser/extensions/active_script_controller.h"
11 #include "chrome/browser/extensions/extension_action_manager.h"
12 #include "chrome/browser/extensions/extension_tab_util.h"
13 #include "chrome/browser/extensions/extension_toolbar_model.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/session_tab_helper.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_finder.h"
19 #include "chrome/browser/ui/browser_window.h"
20 #include "chrome/browser/ui/location_bar/location_bar.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/common/extensions/api/extension_action/action_info.h"
23 #include "chrome/common/render_messages.h"
24 #include "content/public/browser/notification_service.h"
25 #include "extensions/browser/event_router.h"
26 #include "extensions/browser/extension_function_registry.h"
27 #include "extensions/browser/extension_host.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/image_util.h"
30 #include "extensions/browser/notification_types.h"
31 #include "extensions/common/error_utils.h"
32 #include "extensions/common/feature_switch.h"
33 #include "ui/gfx/image/image.h"
34 #include "ui/gfx/image/image_skia.h"
35
36 using content::WebContents;
37
38 namespace extensions {
39
40 namespace {
41
42 // Whether the browser action is visible in the toolbar.
43 const char kBrowserActionVisible[] = "browser_action_visible";
44
45 // Errors.
46 const char kNoExtensionActionError[] =
47     "This extension has no action specified.";
48 const char kNoTabError[] = "No tab with id: *.";
49 const char kOpenPopupError[] =
50     "Failed to show popup either because there is an existing popup or another "
51     "error occurred.";
52
53 }  // namespace
54
55 //
56 // ExtensionActionAPI::Observer
57 //
58
59 void ExtensionActionAPI::Observer::OnExtensionActionUpdated(
60     ExtensionAction* extension_action,
61     content::WebContents* web_contents,
62     content::BrowserContext* browser_context) {
63 }
64
65 void ExtensionActionAPI::Observer::OnPageActionsUpdated(
66     content::WebContents* web_contents) {
67 }
68
69 void ExtensionActionAPI::Observer::OnExtensionActionAPIShuttingDown() {
70 }
71
72 ExtensionActionAPI::Observer::~Observer() {
73 }
74
75 //
76 // ExtensionActionAPI
77 //
78
79 static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI> >
80     g_factory = LAZY_INSTANCE_INITIALIZER;
81
82 ExtensionActionAPI::ExtensionActionAPI(content::BrowserContext* context)
83     : browser_context_(context) {
84   ExtensionFunctionRegistry* registry =
85       ExtensionFunctionRegistry::GetInstance();
86
87   // Browser Actions
88   registry->RegisterFunction<BrowserActionSetIconFunction>();
89   registry->RegisterFunction<BrowserActionSetTitleFunction>();
90   registry->RegisterFunction<BrowserActionSetBadgeTextFunction>();
91   registry->RegisterFunction<BrowserActionSetBadgeBackgroundColorFunction>();
92   registry->RegisterFunction<BrowserActionSetPopupFunction>();
93   registry->RegisterFunction<BrowserActionGetTitleFunction>();
94   registry->RegisterFunction<BrowserActionGetBadgeTextFunction>();
95   registry->RegisterFunction<BrowserActionGetBadgeBackgroundColorFunction>();
96   registry->RegisterFunction<BrowserActionGetPopupFunction>();
97   registry->RegisterFunction<BrowserActionEnableFunction>();
98   registry->RegisterFunction<BrowserActionDisableFunction>();
99   registry->RegisterFunction<BrowserActionOpenPopupFunction>();
100
101   // Page Actions
102   registry->RegisterFunction<PageActionShowFunction>();
103   registry->RegisterFunction<PageActionHideFunction>();
104   registry->RegisterFunction<PageActionSetIconFunction>();
105   registry->RegisterFunction<PageActionSetTitleFunction>();
106   registry->RegisterFunction<PageActionSetPopupFunction>();
107   registry->RegisterFunction<PageActionGetTitleFunction>();
108   registry->RegisterFunction<PageActionGetPopupFunction>();
109 }
110
111 ExtensionActionAPI::~ExtensionActionAPI() {
112 }
113
114 // static
115 BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
116 ExtensionActionAPI::GetFactoryInstance() {
117   return g_factory.Pointer();
118 }
119
120 // static
121 ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
122   return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
123 }
124
125 // static
126 bool ExtensionActionAPI::GetBrowserActionVisibility(
127     const ExtensionPrefs* prefs,
128     const std::string& extension_id) {
129   bool visible = false;
130   if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
131                                           kBrowserActionVisible,
132                                           &visible)) {
133     return true;
134   }
135   return visible;
136 }
137
138 // static
139 void ExtensionActionAPI::SetBrowserActionVisibility(
140     ExtensionPrefs* prefs,
141     const std::string& extension_id,
142     bool visible) {
143   if (GetBrowserActionVisibility(prefs, extension_id) == visible)
144     return;
145
146   prefs->UpdateExtensionPref(extension_id,
147                              kBrowserActionVisible,
148                              new base::FundamentalValue(visible));
149   content::NotificationService::current()->Notify(
150       NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
151       content::Source<ExtensionPrefs>(prefs),
152       content::Details<const std::string>(&extension_id));
153 }
154
155 void ExtensionActionAPI::AddObserver(Observer* observer) {
156   observers_.AddObserver(observer);
157 }
158
159 void ExtensionActionAPI::RemoveObserver(Observer* observer) {
160   observers_.RemoveObserver(observer);
161 }
162
163 ExtensionAction::ShowAction ExtensionActionAPI::ExecuteExtensionAction(
164     const Extension* extension,
165     Browser* browser,
166     bool grant_active_tab_permissions) {
167   content::WebContents* web_contents =
168       browser->tab_strip_model()->GetActiveWebContents();
169   if (!web_contents)
170     return ExtensionAction::ACTION_NONE;
171
172   int tab_id = SessionTabHelper::IdForTab(web_contents);
173
174   ActiveScriptController* active_script_controller =
175       ActiveScriptController::GetForWebContents(web_contents);
176   bool has_pending_scripts = false;
177   if (active_script_controller &&
178       active_script_controller->WantsToRun(extension)) {
179     has_pending_scripts = true;
180   }
181
182   // Grant active tab if appropriate.
183   if (grant_active_tab_permissions) {
184     TabHelper::FromWebContents(web_contents)->active_tab_permission_granter()->
185         GrantIfRequested(extension);
186   }
187
188   // If this was a request to run a script, it will have been run once active
189   // tab was granted. Return without executing the action, since we should only
190   // run pending scripts OR the extension action, not both.
191   if (has_pending_scripts)
192     return ExtensionAction::ACTION_NONE;
193
194   ExtensionAction* extension_action =
195       ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
196           *extension);
197
198   // Anything that gets here should have a page or browser action.
199   DCHECK(extension_action);
200   if (!extension_action->GetIsVisible(tab_id))
201     return ExtensionAction::ACTION_NONE;
202
203   if (extension_action->HasPopup(tab_id))
204     return ExtensionAction::ACTION_SHOW_POPUP;
205
206   ExtensionActionExecuted(*extension_action, web_contents);
207   return ExtensionAction::ACTION_NONE;
208 }
209
210 bool ExtensionActionAPI::ShowExtensionActionPopup(
211     const Extension* extension,
212     Browser* browser,
213     bool grant_active_tab_permissions) {
214   ExtensionAction* extension_action =
215       ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
216           *extension);
217   if (!extension_action)
218     return false;
219
220   if (extension_action->action_type() == ActionInfo::TYPE_PAGE &&
221       !FeatureSwitch::extension_action_redesign()->IsEnabled()) {
222     // We show page actions in the location bar unless the new toolbar is
223     // enabled.
224     return browser->window()->GetLocationBar()->ShowPageActionPopup(
225         extension, grant_active_tab_permissions);
226   } else {
227     return ExtensionToolbarModel::Get(browser->profile())->
228         ShowExtensionActionPopup(
229             extension, browser, grant_active_tab_permissions);
230   }
231 }
232
233 bool ExtensionActionAPI::ExtensionWantsToRun(
234     const Extension* extension, content::WebContents* web_contents) {
235   // An extension wants to act if it has a visible page action on the given
236   // page...
237   ExtensionAction* page_action =
238       ExtensionActionManager::Get(browser_context_)->GetPageAction(*extension);
239   if (page_action &&
240       page_action->GetIsVisible(SessionTabHelper::IdForTab(web_contents)))
241     return true;
242
243   // ... Or if it has pending scripts that need approval for execution.
244   ActiveScriptController* active_script_controller =
245       ActiveScriptController::GetForWebContents(web_contents);
246   if (active_script_controller &&
247       active_script_controller->WantsToRun(extension))
248     return true;
249
250   return false;
251 }
252
253 void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action,
254                                       content::WebContents* web_contents,
255                                       content::BrowserContext* context) {
256   FOR_EACH_OBSERVER(
257       Observer,
258       observers_,
259       OnExtensionActionUpdated(extension_action, web_contents, context));
260
261   if (extension_action->action_type() == ActionInfo::TYPE_PAGE)
262     NotifyPageActionsChanged(web_contents);
263 }
264
265 void ExtensionActionAPI::ClearAllValuesForTab(
266     content::WebContents* web_contents) {
267   DCHECK(web_contents);
268   int tab_id = SessionTabHelper::IdForTab(web_contents);
269   content::BrowserContext* browser_context = web_contents->GetBrowserContext();
270   const ExtensionSet& enabled_extensions =
271       ExtensionRegistry::Get(browser_context_)->enabled_extensions();
272   ExtensionActionManager* action_manager =
273       ExtensionActionManager::Get(browser_context_);
274
275   for (ExtensionSet::const_iterator iter = enabled_extensions.begin();
276        iter != enabled_extensions.end(); ++iter) {
277     ExtensionAction* extension_action =
278         action_manager->GetExtensionAction(**iter);
279     if (extension_action) {
280       extension_action->ClearAllValuesForTab(tab_id);
281       NotifyChange(extension_action, web_contents, browser_context);
282     }
283   }
284 }
285
286 void ExtensionActionAPI::DispatchEventToExtension(
287     content::BrowserContext* context,
288     const std::string& extension_id,
289     const std::string& event_name,
290     scoped_ptr<base::ListValue> event_args) {
291   if (!EventRouter::Get(context))
292     return;
293
294   scoped_ptr<Event> event(new Event(event_name, event_args.Pass()));
295   event->restrict_to_browser_context = context;
296   event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
297   EventRouter::Get(context)
298       ->DispatchEventToExtension(extension_id, event.Pass());
299 }
300
301 void ExtensionActionAPI::ExtensionActionExecuted(
302     const ExtensionAction& extension_action,
303     WebContents* web_contents) {
304   const char* event_name = NULL;
305   switch (extension_action.action_type()) {
306     case ActionInfo::TYPE_BROWSER:
307       event_name = "browserAction.onClicked";
308       break;
309     case ActionInfo::TYPE_PAGE:
310       event_name = "pageAction.onClicked";
311       break;
312     case ActionInfo::TYPE_SYSTEM_INDICATOR:
313       // The System Indicator handles its own clicks.
314       break;
315   }
316
317   if (event_name) {
318     scoped_ptr<base::ListValue> args(new base::ListValue());
319     base::DictionaryValue* tab_value =
320         ExtensionTabUtil::CreateTabValue(web_contents);
321     args->Append(tab_value);
322
323     DispatchEventToExtension(
324         web_contents->GetBrowserContext(),
325         extension_action.extension_id(),
326         event_name,
327         args.Pass());
328   }
329 }
330
331 void ExtensionActionAPI::NotifyPageActionsChanged(
332     content::WebContents* web_contents) {
333   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
334   if (!browser)
335     return;
336   LocationBar* location_bar =
337       browser->window() ? browser->window()->GetLocationBar() : NULL;
338   if (!location_bar)
339     return;
340   location_bar->UpdatePageActions();
341
342   FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents));
343 }
344
345 void ExtensionActionAPI::Shutdown() {
346   FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown());
347 }
348
349 //
350 // ExtensionActionFunction
351 //
352
353 ExtensionActionFunction::ExtensionActionFunction()
354     : details_(NULL),
355       tab_id_(ExtensionAction::kDefaultTabId),
356       contents_(NULL),
357       extension_action_(NULL) {
358 }
359
360 ExtensionActionFunction::~ExtensionActionFunction() {
361 }
362
363 bool ExtensionActionFunction::RunSync() {
364   ExtensionActionManager* manager = ExtensionActionManager::Get(GetProfile());
365   if (StartsWithASCII(name(), "systemIndicator.", false)) {
366     extension_action_ = manager->GetSystemIndicator(*extension());
367   } else {
368     extension_action_ = manager->GetBrowserAction(*extension());
369     if (!extension_action_) {
370       extension_action_ = manager->GetPageAction(*extension());
371     }
372   }
373   if (!extension_action_) {
374     // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
375     // exist for extensions that don't have one declared. This should come as
376     // part of the Feature system.
377     error_ = kNoExtensionActionError;
378     return false;
379   }
380
381   // Populates the tab_id_ and details_ members.
382   EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
383
384   // Find the WebContents that contains this tab id if one is required.
385   if (tab_id_ != ExtensionAction::kDefaultTabId) {
386     ExtensionTabUtil::GetTabById(tab_id_,
387                                  GetProfile(),
388                                  include_incognito(),
389                                  NULL,
390                                  NULL,
391                                  &contents_,
392                                  NULL);
393     if (!contents_) {
394       error_ = ErrorUtils::FormatErrorMessage(
395           kNoTabError, base::IntToString(tab_id_));
396       return false;
397     }
398   } else {
399     // Only browser actions and system indicators have a default tabId.
400     ActionInfo::Type action_type = extension_action_->action_type();
401     EXTENSION_FUNCTION_VALIDATE(
402         action_type == ActionInfo::TYPE_BROWSER ||
403         action_type == ActionInfo::TYPE_SYSTEM_INDICATOR);
404   }
405   return RunExtensionAction();
406 }
407
408 bool ExtensionActionFunction::ExtractDataFromArguments() {
409   // There may or may not be details (depends on the function).
410   // The tabId might appear in details (if it exists), as the first
411   // argument besides the action type (depends on the function), or be omitted
412   // entirely.
413   base::Value* first_arg = NULL;
414   if (!args_->Get(0, &first_arg))
415     return true;
416
417   switch (first_arg->GetType()) {
418     case base::Value::TYPE_INTEGER:
419       CHECK(first_arg->GetAsInteger(&tab_id_));
420       break;
421
422     case base::Value::TYPE_DICTIONARY: {
423       // Found the details argument.
424       details_ = static_cast<base::DictionaryValue*>(first_arg);
425       // Still need to check for the tabId within details.
426       base::Value* tab_id_value = NULL;
427       if (details_->Get("tabId", &tab_id_value)) {
428         switch (tab_id_value->GetType()) {
429           case base::Value::TYPE_NULL:
430             // OK; tabId is optional, leave it default.
431             return true;
432           case base::Value::TYPE_INTEGER:
433             CHECK(tab_id_value->GetAsInteger(&tab_id_));
434             return true;
435           default:
436             // Boom.
437             return false;
438         }
439       }
440       // Not found; tabId is optional, leave it default.
441       break;
442     }
443
444     case base::Value::TYPE_NULL:
445       // The tabId might be an optional argument.
446       break;
447
448     default:
449       return false;
450   }
451
452   return true;
453 }
454
455 void ExtensionActionFunction::NotifyChange() {
456   ExtensionActionAPI::Get(GetProfile())->NotifyChange(
457       extension_action_, contents_, GetProfile());
458 }
459
460 bool ExtensionActionFunction::SetVisible(bool visible) {
461   if (extension_action_->GetIsVisible(tab_id_) == visible)
462     return true;
463   extension_action_->SetIsVisible(tab_id_, visible);
464   NotifyChange();
465   return true;
466 }
467
468 bool ExtensionActionShowFunction::RunExtensionAction() {
469   return SetVisible(true);
470 }
471
472 bool ExtensionActionHideFunction::RunExtensionAction() {
473   return SetVisible(false);
474 }
475
476 bool ExtensionActionSetIconFunction::RunExtensionAction() {
477   EXTENSION_FUNCTION_VALIDATE(details_);
478
479   // setIcon can take a variant argument: either a dictionary of canvas
480   // ImageData, or an icon index.
481   base::DictionaryValue* canvas_set = NULL;
482   int icon_index;
483   if (details_->GetDictionary("imageData", &canvas_set)) {
484     gfx::ImageSkia icon;
485
486     EXTENSION_FUNCTION_VALIDATE(
487         ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon));
488
489     extension_action_->SetIcon(tab_id_, gfx::Image(icon));
490   } else if (details_->GetInteger("iconIndex", &icon_index)) {
491     // Obsolete argument: ignore it.
492     return true;
493   } else {
494     EXTENSION_FUNCTION_VALIDATE(false);
495   }
496   NotifyChange();
497   return true;
498 }
499
500 bool ExtensionActionSetTitleFunction::RunExtensionAction() {
501   EXTENSION_FUNCTION_VALIDATE(details_);
502   std::string title;
503   EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
504   extension_action_->SetTitle(tab_id_, title);
505   NotifyChange();
506   return true;
507 }
508
509 bool ExtensionActionSetPopupFunction::RunExtensionAction() {
510   EXTENSION_FUNCTION_VALIDATE(details_);
511   std::string popup_string;
512   EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
513
514   GURL popup_url;
515   if (!popup_string.empty())
516     popup_url = extension()->GetResourceURL(popup_string);
517
518   extension_action_->SetPopupUrl(tab_id_, popup_url);
519   NotifyChange();
520   return true;
521 }
522
523 bool ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
524   EXTENSION_FUNCTION_VALIDATE(details_);
525   std::string badge_text;
526   EXTENSION_FUNCTION_VALIDATE(details_->GetString("text", &badge_text));
527   extension_action_->SetBadgeText(tab_id_, badge_text);
528   NotifyChange();
529   return true;
530 }
531
532 bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
533   EXTENSION_FUNCTION_VALIDATE(details_);
534   base::Value* color_value = NULL;
535   EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
536   SkColor color = 0;
537   if (color_value->IsType(base::Value::TYPE_LIST)) {
538     base::ListValue* list = NULL;
539     EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
540     EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
541
542     int color_array[4] = {0};
543     for (size_t i = 0; i < arraysize(color_array); ++i) {
544       EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
545     }
546
547     color = SkColorSetARGB(color_array[3], color_array[0],
548                            color_array[1], color_array[2]);
549   } else if (color_value->IsType(base::Value::TYPE_STRING)) {
550     std::string color_string;
551     EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
552     if (!image_util::ParseCSSColorString(color_string, &color))
553       return false;
554   }
555
556   extension_action_->SetBadgeBackgroundColor(tab_id_, color);
557   NotifyChange();
558   return true;
559 }
560
561 bool ExtensionActionGetTitleFunction::RunExtensionAction() {
562   SetResult(new base::StringValue(extension_action_->GetTitle(tab_id_)));
563   return true;
564 }
565
566 bool ExtensionActionGetPopupFunction::RunExtensionAction() {
567   SetResult(
568       new base::StringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
569   return true;
570 }
571
572 bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
573   SetResult(new base::StringValue(extension_action_->GetBadgeText(tab_id_)));
574   return true;
575 }
576
577 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
578   base::ListValue* list = new base::ListValue();
579   SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
580   list->Append(
581       new base::FundamentalValue(static_cast<int>(SkColorGetR(color))));
582   list->Append(
583       new base::FundamentalValue(static_cast<int>(SkColorGetG(color))));
584   list->Append(
585       new base::FundamentalValue(static_cast<int>(SkColorGetB(color))));
586   list->Append(
587       new base::FundamentalValue(static_cast<int>(SkColorGetA(color))));
588   SetResult(list);
589   return true;
590 }
591
592 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
593     : response_sent_(false) {
594 }
595
596 bool BrowserActionOpenPopupFunction::RunAsync() {
597   // We only allow the popup in the active window.
598   Browser* browser = chrome::FindLastActiveWithProfile(
599                          GetProfile(), chrome::GetActiveDesktop());
600
601   // If there's no active browser, or the Toolbar isn't visible, abort.
602   // Otherwise, try to open a popup in the active browser.
603   // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
604   // fixed.
605   if (!browser ||
606       !browser->window()->IsActive() ||
607       !browser->window()->IsToolbarVisible() ||
608       !ExtensionActionAPI::Get(GetProfile())->ShowExtensionActionPopup(
609           extension_.get(), browser, false)) {
610     error_ = kOpenPopupError;
611     return false;
612   }
613
614   registrar_.Add(this,
615                  NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
616                  content::Source<Profile>(GetProfile()));
617
618   // Set a timeout for waiting for the notification that the popup is loaded.
619   // Waiting is required so that the popup view can be retrieved by the custom
620   // bindings for the response callback. It's also needed to keep this function
621   // instance around until a notification is observed.
622   base::MessageLoopForUI::current()->PostDelayedTask(
623       FROM_HERE,
624       base::Bind(&BrowserActionOpenPopupFunction::OpenPopupTimedOut, this),
625       base::TimeDelta::FromSeconds(10));
626   return true;
627 }
628
629 void BrowserActionOpenPopupFunction::OpenPopupTimedOut() {
630   if (response_sent_)
631     return;
632
633   DVLOG(1) << "chrome.browserAction.openPopup did not show a popup.";
634   error_ = kOpenPopupError;
635   SendResponse(false);
636   response_sent_ = true;
637 }
638
639 void BrowserActionOpenPopupFunction::Observe(
640     int type,
641     const content::NotificationSource& source,
642     const content::NotificationDetails& details) {
643   DCHECK_EQ(NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, type);
644   if (response_sent_)
645     return;
646
647   ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
648   if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP ||
649       host->extension()->id() != extension_->id())
650     return;
651
652   SendResponse(true);
653   response_sent_ = true;
654   registrar_.RemoveAll();
655 }
656
657 }  // namespace extensions