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