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 <stddef.h>
8 #include <memory>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/lazy_instance.h"
13 #include "base/location.h"
14 #include "base/metrics/histogram_functions.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/threading/thread_task_runner_handle.h"
20 #include "base/values.h"
21 #include "chrome/browser/extensions/extension_action_runner.h"
22 #include "chrome/browser/extensions/extension_tab_util.h"
23 #include "chrome/browser/extensions/extension_ui_util.h"
24 #include "chrome/browser/extensions/tab_helper.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_finder.h"
28 #include "chrome/browser/ui/browser_window.h"
29 #include "chrome/browser/ui/extensions/extensions_container.h"
30 #include "chrome/browser/ui/tabs/tab_strip_model.h"
31 #include "components/sessions/content/session_tab_helper.h"
32 #include "content/public/browser/notification_service.h"
33 #include "extensions/browser/api/declarative_net_request/constants.h"
34 #include "extensions/browser/event_router.h"
35 #include "extensions/browser/extension_action_manager.h"
36 #include "extensions/browser/extension_host.h"
37 #include "extensions/browser/extension_prefs.h"
38 #include "extensions/browser/extension_registry.h"
39 #include "extensions/browser/extension_util.h"
40 #include "extensions/browser/notification_types.h"
41 #include "extensions/common/api/extension_action/action_info.h"
42 #include "extensions/common/error_utils.h"
43 #include "extensions/common/feature_switch.h"
44 #include "extensions/common/image_util.h"
45 #include "ui/gfx/image/image.h"
46 #include "ui/gfx/image/image_skia.h"
47 
48 using content::WebContents;
49 
50 namespace extensions {
51 
52 namespace {
53 
54 // Errors.
55 const char kNoExtensionActionError[] =
56     "This extension has no action specified.";
57 const char kNoTabError[] = "No tab with id: *.";
58 const char kOpenPopupError[] =
59     "Failed to show popup either because there is an existing popup or another "
60     "error occurred.";
61 const char kInvalidColorError[] =
62     "The color specification could not be parsed.";
63 
64 bool g_report_error_for_invisible_icon = false;
65 
66 }  // namespace
67 
68 //
69 // ExtensionActionAPI::Observer
70 //
71 
OnExtensionActionUpdated(ExtensionAction * extension_action,content::WebContents * web_contents,content::BrowserContext * browser_context)72 void ExtensionActionAPI::Observer::OnExtensionActionUpdated(
73     ExtensionAction* extension_action,
74     content::WebContents* web_contents,
75     content::BrowserContext* browser_context) {
76 }
77 
OnExtensionActionAPIShuttingDown()78 void ExtensionActionAPI::Observer::OnExtensionActionAPIShuttingDown() {
79 }
80 
~Observer()81 ExtensionActionAPI::Observer::~Observer() {
82 }
83 
84 //
85 // ExtensionActionAPI
86 //
87 
88 static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI>>::
89     DestructorAtExit g_extension_action_api_factory = LAZY_INSTANCE_INITIALIZER;
90 
ExtensionActionAPI(content::BrowserContext * context)91 ExtensionActionAPI::ExtensionActionAPI(content::BrowserContext* context)
92     : browser_context_(context), extension_prefs_(nullptr) {}
93 
~ExtensionActionAPI()94 ExtensionActionAPI::~ExtensionActionAPI() {
95 }
96 
97 // static
98 BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
GetFactoryInstance()99 ExtensionActionAPI::GetFactoryInstance() {
100   return g_extension_action_api_factory.Pointer();
101 }
102 
103 // static
Get(content::BrowserContext * context)104 ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
105   return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
106 }
107 
AddObserver(Observer * observer)108 void ExtensionActionAPI::AddObserver(Observer* observer) {
109   observers_.AddObserver(observer);
110 }
111 
RemoveObserver(Observer * observer)112 void ExtensionActionAPI::RemoveObserver(Observer* observer) {
113   observers_.RemoveObserver(observer);
114 }
115 
ShowExtensionActionPopupForAPICall(const Extension * extension,Browser * browser)116 bool ExtensionActionAPI::ShowExtensionActionPopupForAPICall(
117     const Extension* extension,
118     Browser* browser) {
119   ExtensionAction* extension_action =
120       ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
121           *extension);
122   if (!extension_action)
123     return false;
124 
125   // Don't support showing action popups in a popup window.
126   if (!browser->SupportsWindowFeature(Browser::FEATURE_TOOLBAR))
127     return false;
128 
129   ExtensionsContainer* extensions_container =
130       browser->window()->GetExtensionsContainer();
131   // The ExtensionsContainer could be null if, e.g., this is a popup window with
132   // no toolbar.
133   return extensions_container &&
134          extensions_container->ShowToolbarActionPopupForAPICall(
135              extension->id());
136 }
137 
NotifyChange(ExtensionAction * extension_action,content::WebContents * web_contents,content::BrowserContext * context)138 void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action,
139                                       content::WebContents* web_contents,
140                                       content::BrowserContext* context) {
141   for (auto& observer : observers_)
142     observer.OnExtensionActionUpdated(extension_action, web_contents, context);
143 }
144 
DispatchExtensionActionClicked(const ExtensionAction & extension_action,WebContents * web_contents,const Extension * extension)145 void ExtensionActionAPI::DispatchExtensionActionClicked(
146     const ExtensionAction& extension_action,
147     WebContents* web_contents,
148     const Extension* extension) {
149   events::HistogramValue histogram_value = events::UNKNOWN;
150   const char* event_name = NULL;
151   switch (extension_action.action_type()) {
152     case ActionInfo::TYPE_ACTION:
153       histogram_value = events::ACTION_ON_CLICKED;
154       event_name = "action.onClicked";
155       break;
156     case ActionInfo::TYPE_BROWSER:
157       histogram_value = events::BROWSER_ACTION_ON_CLICKED;
158       event_name = "browserAction.onClicked";
159       break;
160     case ActionInfo::TYPE_PAGE:
161       histogram_value = events::PAGE_ACTION_ON_CLICKED;
162       event_name = "pageAction.onClicked";
163       break;
164   }
165 
166   if (event_name) {
167     std::unique_ptr<base::ListValue> args(new base::ListValue());
168     // The action APIs (browserAction, pageAction, action) are only available
169     // to blessed extension contexts. As such, we deterministically know that
170     // the right context type here is blessed.
171     constexpr Feature::Context context_type =
172         Feature::BLESSED_EXTENSION_CONTEXT;
173     ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
174         ExtensionTabUtil::GetScrubTabBehavior(extension, context_type,
175                                               web_contents);
176     args->Append(ExtensionTabUtil::CreateTabObject(
177                      web_contents, scrub_tab_behavior, extension)
178                      ->ToValue());
179 
180     DispatchEventToExtension(web_contents->GetBrowserContext(),
181                              extension_action.extension_id(), histogram_value,
182                              event_name, std::move(args));
183   }
184 }
185 
ClearAllValuesForTab(content::WebContents * web_contents)186 void ExtensionActionAPI::ClearAllValuesForTab(
187     content::WebContents* web_contents) {
188   DCHECK(web_contents);
189   const SessionID tab_id = sessions::SessionTabHelper::IdForTab(web_contents);
190   content::BrowserContext* browser_context = web_contents->GetBrowserContext();
191   const ExtensionSet& enabled_extensions =
192       ExtensionRegistry::Get(browser_context_)->enabled_extensions();
193   ExtensionActionManager* action_manager =
194       ExtensionActionManager::Get(browser_context_);
195 
196   for (ExtensionSet::const_iterator iter = enabled_extensions.begin();
197        iter != enabled_extensions.end(); ++iter) {
198     ExtensionAction* extension_action =
199         action_manager->GetExtensionAction(**iter);
200     if (extension_action) {
201       extension_action->ClearAllValuesForTab(tab_id.id());
202       NotifyChange(extension_action, web_contents, browser_context);
203     }
204   }
205 }
206 
GetExtensionPrefs()207 ExtensionPrefs* ExtensionActionAPI::GetExtensionPrefs() {
208   // This lazy initialization is more than just an optimization, because it
209   // allows tests to associate a new ExtensionPrefs with the browser context
210   // before we access it.
211   if (!extension_prefs_)
212     extension_prefs_ = ExtensionPrefs::Get(browser_context_);
213   return extension_prefs_;
214 }
215 
DispatchEventToExtension(content::BrowserContext * context,const std::string & extension_id,events::HistogramValue histogram_value,const std::string & event_name,std::unique_ptr<base::ListValue> event_args)216 void ExtensionActionAPI::DispatchEventToExtension(
217     content::BrowserContext* context,
218     const std::string& extension_id,
219     events::HistogramValue histogram_value,
220     const std::string& event_name,
221     std::unique_ptr<base::ListValue> event_args) {
222   if (!EventRouter::Get(context))
223     return;
224 
225   auto event = std::make_unique<Event>(histogram_value, event_name,
226                                        std::move(event_args), context);
227   event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
228   EventRouter::Get(context)
229       ->DispatchEventToExtension(extension_id, std::move(event));
230 }
231 
Shutdown()232 void ExtensionActionAPI::Shutdown() {
233   for (auto& observer : observers_)
234     observer.OnExtensionActionAPIShuttingDown();
235 }
236 
237 //
238 // ExtensionActionFunction
239 //
240 
ExtensionActionFunction()241 ExtensionActionFunction::ExtensionActionFunction()
242     : details_(nullptr),
243       tab_id_(ExtensionAction::kDefaultTabId),
244       contents_(nullptr),
245       extension_action_(nullptr) {}
246 
~ExtensionActionFunction()247 ExtensionActionFunction::~ExtensionActionFunction() {
248 }
249 
Run()250 ExtensionFunction::ResponseAction ExtensionActionFunction::Run() {
251   ExtensionActionManager* manager =
252       ExtensionActionManager::Get(browser_context());
253   extension_action_ = manager->GetExtensionAction(*extension());
254   if (!extension_action_) {
255     // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
256     // exist for extensions that don't have one declared. This should come as
257     // part of the Feature system.
258     return RespondNow(Error(kNoExtensionActionError));
259   }
260 
261   // Populates the tab_id_ and details_ members.
262   EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
263 
264   // Find the WebContents that contains this tab id if one is required.
265   if (tab_id_ != ExtensionAction::kDefaultTabId) {
266     ExtensionTabUtil::GetTabById(tab_id_, browser_context(),
267                                  include_incognito_information(), &contents_);
268     if (!contents_)
269       return RespondNow(Error(kNoTabError, base::NumberToString(tab_id_)));
270   } else {
271     // Page actions do not have a default tabId.
272     EXTENSION_FUNCTION_VALIDATE(extension_action_->action_type() !=
273                                 ActionInfo::TYPE_PAGE);
274   }
275   return RunExtensionAction();
276 }
277 
ExtractDataFromArguments()278 bool ExtensionActionFunction::ExtractDataFromArguments() {
279   // There may or may not be details (depends on the function).
280   // The tabId might appear in details (if it exists), as the first
281   // argument besides the action type (depends on the function), or be omitted
282   // entirely.
283   base::Value* first_arg = NULL;
284   if (!args_->Get(0, &first_arg))
285     return true;
286 
287   switch (first_arg->type()) {
288     case base::Value::Type::INTEGER:
289       CHECK(first_arg->GetAsInteger(&tab_id_));
290       break;
291 
292     case base::Value::Type::DICTIONARY: {
293       // Found the details argument.
294       details_ = static_cast<base::DictionaryValue*>(first_arg);
295       // Still need to check for the tabId within details.
296       base::Value* tab_id_value = NULL;
297       if (details_->Get("tabId", &tab_id_value)) {
298         switch (tab_id_value->type()) {
299           case base::Value::Type::NONE:
300             // OK; tabId is optional, leave it default.
301             return true;
302           case base::Value::Type::INTEGER:
303             CHECK(tab_id_value->GetAsInteger(&tab_id_));
304             return true;
305           default:
306             // Boom.
307             return false;
308         }
309       }
310       // Not found; tabId is optional, leave it default.
311       break;
312     }
313 
314     case base::Value::Type::NONE:
315       // The tabId might be an optional argument.
316       break;
317 
318     default:
319       return false;
320   }
321 
322   return true;
323 }
324 
NotifyChange()325 void ExtensionActionFunction::NotifyChange() {
326   ExtensionActionAPI::Get(browser_context())
327       ->NotifyChange(extension_action_, contents_, browser_context());
328 }
329 
SetVisible(bool visible)330 void ExtensionActionFunction::SetVisible(bool visible) {
331   if (extension_action_->GetIsVisible(tab_id_) == visible)
332     return;
333   extension_action_->SetIsVisible(tab_id_, visible);
334   NotifyChange();
335 }
336 
337 ExtensionFunction::ResponseAction
RunExtensionAction()338 ExtensionActionShowFunction::RunExtensionAction() {
339   SetVisible(true);
340   return RespondNow(NoArguments());
341 }
342 
343 ExtensionFunction::ResponseAction
RunExtensionAction()344 ExtensionActionHideFunction::RunExtensionAction() {
345   SetVisible(false);
346   return RespondNow(NoArguments());
347 }
348 
349 // static
SetReportErrorForInvisibleIconForTesting(bool value)350 void ExtensionActionSetIconFunction::SetReportErrorForInvisibleIconForTesting(
351     bool value) {
352   g_report_error_for_invisible_icon = value;
353 }
354 
355 ExtensionFunction::ResponseAction
RunExtensionAction()356 ExtensionActionSetIconFunction::RunExtensionAction() {
357   // TODO(devlin): Temporary logging to track down https://crbug.com/1087948.
358   // Remove this (and the redundant `if (!x) { VALIDATE(x); }`) checks after
359   // the bug is fixed.
360   // Don't reorder or remove values.
361   enum class FailureType {
362     kFailedToParseDetails = 0,
363     kFailedToDecodeCanvas = 1,
364     kFailedToUnpickleCanvas = 2,
365     kNoImageDataOrIconIndex = 3,
366     kMaxValue = kNoImageDataOrIconIndex,
367   };
368 
369   auto log_set_icon_failure = [](FailureType type) {
370     base::UmaHistogramEnumeration("Extensions.ActionSetIconFailureType", type);
371   };
372 
373   if (!details_) {
374     log_set_icon_failure(FailureType::kFailedToParseDetails);
375     EXTENSION_FUNCTION_VALIDATE(details_);
376   }
377 
378   // setIcon can take a variant argument: either a dictionary of canvas
379   // ImageData, or an icon index.
380   base::DictionaryValue* canvas_set = NULL;
381   int icon_index;
382   if (details_->GetDictionary("imageData", &canvas_set)) {
383     gfx::ImageSkia icon;
384 
385     ExtensionAction::IconParseResult parse_result =
386         ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon);
387 
388     if (parse_result != ExtensionAction::IconParseResult::kSuccess) {
389       switch (parse_result) {
390         case ExtensionAction::IconParseResult::kDecodeFailure:
391           log_set_icon_failure(FailureType::kFailedToDecodeCanvas);
392           break;
393         case ExtensionAction::IconParseResult::kUnpickleFailure:
394           log_set_icon_failure(FailureType::kFailedToUnpickleCanvas);
395           break;
396         case ExtensionAction::IconParseResult::kSuccess:
397           NOTREACHED();
398           break;
399       }
400       EXTENSION_FUNCTION_VALIDATE(false);
401     }
402 
403     if (icon.isNull())
404       return RespondNow(Error("Icon invalid."));
405 
406     gfx::Image icon_image(icon);
407     const SkBitmap bitmap = icon_image.AsBitmap();
408     const bool is_visible = image_util::IsIconSufficientlyVisible(bitmap);
409     UMA_HISTOGRAM_BOOLEAN("Extensions.DynamicExtensionActionIconWasVisible",
410                           is_visible);
411     const bool is_visible_rendered =
412         extensions::ui_util::IsRenderedIconSufficientlyVisibleForBrowserContext(
413             bitmap, browser_context());
414     UMA_HISTOGRAM_BOOLEAN(
415         "Extensions.DynamicExtensionActionIconWasVisibleRendered",
416         is_visible_rendered);
417 
418     if (!is_visible && g_report_error_for_invisible_icon)
419       return RespondNow(Error("Icon not sufficiently visible."));
420 
421     extension_action_->SetIcon(tab_id_, icon_image);
422   } else if (details_->GetInteger("iconIndex", &icon_index)) {
423     // Obsolete argument: ignore it.
424     return RespondNow(NoArguments());
425   } else {
426     log_set_icon_failure(FailureType::kNoImageDataOrIconIndex);
427     EXTENSION_FUNCTION_VALIDATE(false);
428   }
429   NotifyChange();
430   return RespondNow(NoArguments());
431 }
432 
433 ExtensionFunction::ResponseAction
RunExtensionAction()434 ExtensionActionSetTitleFunction::RunExtensionAction() {
435   EXTENSION_FUNCTION_VALIDATE(details_);
436   std::string title;
437   EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
438   extension_action_->SetTitle(tab_id_, title);
439   NotifyChange();
440   return RespondNow(NoArguments());
441 }
442 
443 ExtensionFunction::ResponseAction
RunExtensionAction()444 ExtensionActionSetPopupFunction::RunExtensionAction() {
445   EXTENSION_FUNCTION_VALIDATE(details_);
446   std::string popup_string;
447   EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
448 
449   GURL popup_url;
450   if (!popup_string.empty())
451     popup_url = extension()->GetResourceURL(popup_string);
452 
453   extension_action_->SetPopupUrl(tab_id_, popup_url);
454   NotifyChange();
455   return RespondNow(NoArguments());
456 }
457 
458 ExtensionFunction::ResponseAction
RunExtensionAction()459 ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
460   EXTENSION_FUNCTION_VALIDATE(details_);
461 
462   std::string badge_text;
463   if (details_->GetString("text", &badge_text))
464     extension_action_->SetBadgeText(tab_id_, badge_text);
465   else
466     extension_action_->ClearBadgeText(tab_id_);
467 
468   NotifyChange();
469   return RespondNow(NoArguments());
470 }
471 
472 ExtensionFunction::ResponseAction
RunExtensionAction()473 ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
474   EXTENSION_FUNCTION_VALIDATE(details_);
475   base::Value* color_value = NULL;
476   EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
477   SkColor color = 0;
478   if (color_value->is_list()) {
479     base::ListValue* list = NULL;
480     EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
481     EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
482 
483     int color_array[4] = {0};
484     for (size_t i = 0; i < base::size(color_array); ++i) {
485       EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
486     }
487 
488     color = SkColorSetARGB(color_array[3], color_array[0],
489                            color_array[1], color_array[2]);
490   } else if (color_value->is_string()) {
491     std::string color_string;
492     EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
493     if (!image_util::ParseCssColorString(color_string, &color))
494       return RespondNow(Error(kInvalidColorError));
495   }
496 
497   extension_action_->SetBadgeBackgroundColor(tab_id_, color);
498   NotifyChange();
499   return RespondNow(NoArguments());
500 }
501 
502 ExtensionFunction::ResponseAction
RunExtensionAction()503 ExtensionActionGetTitleFunction::RunExtensionAction() {
504   return RespondNow(
505       OneArgument(base::Value(extension_action_->GetTitle(tab_id_))));
506 }
507 
508 ExtensionFunction::ResponseAction
RunExtensionAction()509 ExtensionActionGetPopupFunction::RunExtensionAction() {
510   return RespondNow(
511       OneArgument(base::Value(extension_action_->GetPopupUrl(tab_id_).spec())));
512 }
513 
514 ExtensionFunction::ResponseAction
RunExtensionAction()515 ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
516   // Return a placeholder value if the extension has enabled using
517   // declarativeNetRequest action count as badge text and the badge count shown
518   // for this tab is the number of actions matched.
519   std::string badge_text =
520       extension_action_->UseDNRActionCountAsBadgeText(tab_id_)
521           ? declarative_net_request::kActionCountPlaceholderBadgeText
522           : extension_action_->GetExplicitlySetBadgeText(tab_id_);
523 
524   // TODO(crbug.com/990224): Document this behavior once
525   // chrome.declarativeNetRequest.setExtensionActionOptions is promoted to beta
526   // from trunk.
527   return RespondNow(OneArgument(base::Value(std::move(badge_text))));
528 }
529 
530 ExtensionFunction::ResponseAction
RunExtensionAction()531 ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
532   std::unique_ptr<base::ListValue> list(new base::ListValue());
533   SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
534   list->AppendInteger(static_cast<int>(SkColorGetR(color)));
535   list->AppendInteger(static_cast<int>(SkColorGetG(color)));
536   list->AppendInteger(static_cast<int>(SkColorGetB(color)));
537   list->AppendInteger(static_cast<int>(SkColorGetA(color)));
538   return RespondNow(
539       OneArgument(base::Value::FromUniquePtrValue(std::move(list))));
540 }
541 
542 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction() = default;
543 
Run()544 ExtensionFunction::ResponseAction BrowserActionOpenPopupFunction::Run() {
545   // We only allow the popup in the active window.
546   Profile* profile = Profile::FromBrowserContext(browser_context());
547   Browser* browser = chrome::FindLastActiveWithProfile(profile);
548   // It's possible that the last active browser actually corresponds to the
549   // associated incognito profile, and this won't be returned by
550   // FindLastActiveWithProfile. If the browser we found isn't active and the
551   // extension can operate incognito, then check the last active incognito, too.
552   if ((!browser || !browser->window()->IsActive()) &&
553       util::IsIncognitoEnabled(extension()->id(), profile) &&
554       profile->HasPrimaryOTRProfile()) {
555     browser =
556         chrome::FindLastActiveWithProfile(profile->GetPrimaryOTRProfile());
557   }
558 
559   // If there's no active browser, or the Toolbar isn't visible, abort.
560   // Otherwise, try to open a popup in the active browser.
561   // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
562   // fixed.
563   if (!browser || !browser->window()->IsActive() ||
564       !browser->window()->IsToolbarVisible() ||
565       !ExtensionActionAPI::Get(profile)->ShowExtensionActionPopupForAPICall(
566           extension_.get(), browser)) {
567     return RespondNow(Error(kOpenPopupError));
568   }
569 
570   // Even if this is for an incognito window, we want to use the normal profile.
571   // If the extension is spanning, then extension hosts are created with the
572   // original profile, and if it's split, then we know the api call came from
573   // the right profile.
574   registrar_.Add(this, NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD,
575                  content::Source<Profile>(profile));
576 
577   // Set a timeout for waiting for the notification that the popup is loaded.
578   // Waiting is required so that the popup view can be retrieved by the custom
579   // bindings for the response callback. It's also needed to keep this function
580   // instance around until a notification is observed.
581   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
582       FROM_HERE,
583       base::BindOnce(&BrowserActionOpenPopupFunction::OpenPopupTimedOut, this),
584       base::TimeDelta::FromSeconds(10));
585   return RespondLater();
586 }
587 
OpenPopupTimedOut()588 void BrowserActionOpenPopupFunction::OpenPopupTimedOut() {
589   if (did_respond())
590     return;
591 
592   DVLOG(1) << "chrome.browserAction.openPopup did not show a popup.";
593   Respond(Error(kOpenPopupError));
594 }
595 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)596 void BrowserActionOpenPopupFunction::Observe(
597     int type,
598     const content::NotificationSource& source,
599     const content::NotificationDetails& details) {
600   DCHECK_EQ(NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD, type);
601   if (did_respond())
602     return;
603 
604   ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
605   if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP ||
606       host->extension()->id() != extension_->id())
607     return;
608 
609   Respond(NoArguments());
610   registrar_.RemoveAll();
611 }
612 
613 }  // namespace extensions
614