Home | History | Annotate | Download | only in extension_action
      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 void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action,
    234                                       content::WebContents* web_contents,
    235                                       content::BrowserContext* context) {
    236   FOR_EACH_OBSERVER(
    237       Observer,
    238       observers_,
    239       OnExtensionActionUpdated(extension_action, web_contents, context));
    240 
    241   if (extension_action->action_type() == ActionInfo::TYPE_PAGE)
    242     NotifyPageActionsChanged(web_contents);
    243 }
    244 
    245 void ExtensionActionAPI::ClearAllValuesForTab(
    246     content::WebContents* web_contents) {
    247   DCHECK(web_contents);
    248   int tab_id = SessionTabHelper::IdForTab(web_contents);
    249   content::BrowserContext* browser_context = web_contents->GetBrowserContext();
    250   const ExtensionSet& enabled_extensions =
    251       ExtensionRegistry::Get(browser_context_)->enabled_extensions();
    252   ExtensionActionManager* action_manager =
    253       ExtensionActionManager::Get(browser_context_);
    254 
    255   for (ExtensionSet::const_iterator iter = enabled_extensions.begin();
    256        iter != enabled_extensions.end(); ++iter) {
    257     ExtensionAction* extension_action =
    258         action_manager->GetBrowserAction(*iter->get());
    259     if (!extension_action)
    260       extension_action = action_manager->GetPageAction(*iter->get());
    261     if (extension_action) {
    262       extension_action->ClearAllValuesForTab(tab_id);
    263       NotifyChange(extension_action, web_contents, browser_context);
    264     }
    265   }
    266 }
    267 
    268 void ExtensionActionAPI::DispatchEventToExtension(
    269     content::BrowserContext* context,
    270     const std::string& extension_id,
    271     const std::string& event_name,
    272     scoped_ptr<base::ListValue> event_args) {
    273   if (!EventRouter::Get(context))
    274     return;
    275 
    276   scoped_ptr<Event> event(new Event(event_name, event_args.Pass()));
    277   event->restrict_to_browser_context = context;
    278   event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
    279   EventRouter::Get(context)
    280       ->DispatchEventToExtension(extension_id, event.Pass());
    281 }
    282 
    283 void ExtensionActionAPI::ExtensionActionExecuted(
    284     const ExtensionAction& extension_action,
    285     WebContents* web_contents) {
    286   const char* event_name = NULL;
    287   switch (extension_action.action_type()) {
    288     case ActionInfo::TYPE_BROWSER:
    289       event_name = "browserAction.onClicked";
    290       break;
    291     case ActionInfo::TYPE_PAGE:
    292       event_name = "pageAction.onClicked";
    293       break;
    294     case ActionInfo::TYPE_SYSTEM_INDICATOR:
    295       // The System Indicator handles its own clicks.
    296       break;
    297   }
    298 
    299   if (event_name) {
    300     scoped_ptr<base::ListValue> args(new base::ListValue());
    301     base::DictionaryValue* tab_value =
    302         ExtensionTabUtil::CreateTabValue(web_contents);
    303     args->Append(tab_value);
    304 
    305     DispatchEventToExtension(
    306         web_contents->GetBrowserContext(),
    307         extension_action.extension_id(),
    308         event_name,
    309         args.Pass());
    310   }
    311 }
    312 
    313 void ExtensionActionAPI::NotifyPageActionsChanged(
    314     content::WebContents* web_contents) {
    315   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
    316   if (!browser)
    317     return;
    318   LocationBar* location_bar =
    319       browser->window() ? browser->window()->GetLocationBar() : NULL;
    320   if (!location_bar)
    321     return;
    322   location_bar->UpdatePageActions();
    323 
    324   FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents));
    325 }
    326 
    327 void ExtensionActionAPI::Shutdown() {
    328   FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown());
    329 }
    330 
    331 //
    332 // ExtensionActionFunction
    333 //
    334 
    335 ExtensionActionFunction::ExtensionActionFunction()
    336     : details_(NULL),
    337       tab_id_(ExtensionAction::kDefaultTabId),
    338       contents_(NULL),
    339       extension_action_(NULL) {
    340 }
    341 
    342 ExtensionActionFunction::~ExtensionActionFunction() {
    343 }
    344 
    345 bool ExtensionActionFunction::RunSync() {
    346   ExtensionActionManager* manager = ExtensionActionManager::Get(GetProfile());
    347   if (StartsWithASCII(name(), "systemIndicator.", false)) {
    348     extension_action_ = manager->GetSystemIndicator(*extension());
    349   } else {
    350     extension_action_ = manager->GetBrowserAction(*extension());
    351     if (!extension_action_) {
    352       extension_action_ = manager->GetPageAction(*extension());
    353     }
    354   }
    355   if (!extension_action_) {
    356     // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
    357     // exist for extensions that don't have one declared. This should come as
    358     // part of the Feature system.
    359     error_ = kNoExtensionActionError;
    360     return false;
    361   }
    362 
    363   // Populates the tab_id_ and details_ members.
    364   EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
    365 
    366   // Find the WebContents that contains this tab id if one is required.
    367   if (tab_id_ != ExtensionAction::kDefaultTabId) {
    368     ExtensionTabUtil::GetTabById(tab_id_,
    369                                  GetProfile(),
    370                                  include_incognito(),
    371                                  NULL,
    372                                  NULL,
    373                                  &contents_,
    374                                  NULL);
    375     if (!contents_) {
    376       error_ = ErrorUtils::FormatErrorMessage(
    377           kNoTabError, base::IntToString(tab_id_));
    378       return false;
    379     }
    380   } else {
    381     // Only browser actions and system indicators have a default tabId.
    382     ActionInfo::Type action_type = extension_action_->action_type();
    383     EXTENSION_FUNCTION_VALIDATE(
    384         action_type == ActionInfo::TYPE_BROWSER ||
    385         action_type == ActionInfo::TYPE_SYSTEM_INDICATOR);
    386   }
    387   return RunExtensionAction();
    388 }
    389 
    390 bool ExtensionActionFunction::ExtractDataFromArguments() {
    391   // There may or may not be details (depends on the function).
    392   // The tabId might appear in details (if it exists), as the first
    393   // argument besides the action type (depends on the function), or be omitted
    394   // entirely.
    395   base::Value* first_arg = NULL;
    396   if (!args_->Get(0, &first_arg))
    397     return true;
    398 
    399   switch (first_arg->GetType()) {
    400     case base::Value::TYPE_INTEGER:
    401       CHECK(first_arg->GetAsInteger(&tab_id_));
    402       break;
    403 
    404     case base::Value::TYPE_DICTIONARY: {
    405       // Found the details argument.
    406       details_ = static_cast<base::DictionaryValue*>(first_arg);
    407       // Still need to check for the tabId within details.
    408       base::Value* tab_id_value = NULL;
    409       if (details_->Get("tabId", &tab_id_value)) {
    410         switch (tab_id_value->GetType()) {
    411           case base::Value::TYPE_NULL:
    412             // OK; tabId is optional, leave it default.
    413             return true;
    414           case base::Value::TYPE_INTEGER:
    415             CHECK(tab_id_value->GetAsInteger(&tab_id_));
    416             return true;
    417           default:
    418             // Boom.
    419             return false;
    420         }
    421       }
    422       // Not found; tabId is optional, leave it default.
    423       break;
    424     }
    425 
    426     case base::Value::TYPE_NULL:
    427       // The tabId might be an optional argument.
    428       break;
    429 
    430     default:
    431       return false;
    432   }
    433 
    434   return true;
    435 }
    436 
    437 void ExtensionActionFunction::NotifyChange() {
    438   ExtensionActionAPI::Get(GetProfile())->NotifyChange(
    439       extension_action_, contents_, GetProfile());
    440 }
    441 
    442 bool ExtensionActionFunction::SetVisible(bool visible) {
    443   if (extension_action_->GetIsVisible(tab_id_) == visible)
    444     return true;
    445   extension_action_->SetIsVisible(tab_id_, visible);
    446   NotifyChange();
    447   return true;
    448 }
    449 
    450 bool ExtensionActionShowFunction::RunExtensionAction() {
    451   return SetVisible(true);
    452 }
    453 
    454 bool ExtensionActionHideFunction::RunExtensionAction() {
    455   return SetVisible(false);
    456 }
    457 
    458 bool ExtensionActionSetIconFunction::RunExtensionAction() {
    459   EXTENSION_FUNCTION_VALIDATE(details_);
    460 
    461   // setIcon can take a variant argument: either a dictionary of canvas
    462   // ImageData, or an icon index.
    463   base::DictionaryValue* canvas_set = NULL;
    464   int icon_index;
    465   if (details_->GetDictionary("imageData", &canvas_set)) {
    466     gfx::ImageSkia icon;
    467 
    468     EXTENSION_FUNCTION_VALIDATE(
    469         ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon));
    470 
    471     extension_action_->SetIcon(tab_id_, gfx::Image(icon));
    472   } else if (details_->GetInteger("iconIndex", &icon_index)) {
    473     // Obsolete argument: ignore it.
    474     return true;
    475   } else {
    476     EXTENSION_FUNCTION_VALIDATE(false);
    477   }
    478   NotifyChange();
    479   return true;
    480 }
    481 
    482 bool ExtensionActionSetTitleFunction::RunExtensionAction() {
    483   EXTENSION_FUNCTION_VALIDATE(details_);
    484   std::string title;
    485   EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
    486   extension_action_->SetTitle(tab_id_, title);
    487   NotifyChange();
    488   return true;
    489 }
    490 
    491 bool ExtensionActionSetPopupFunction::RunExtensionAction() {
    492   EXTENSION_FUNCTION_VALIDATE(details_);
    493   std::string popup_string;
    494   EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
    495 
    496   GURL popup_url;
    497   if (!popup_string.empty())
    498     popup_url = extension()->GetResourceURL(popup_string);
    499 
    500   extension_action_->SetPopupUrl(tab_id_, popup_url);
    501   NotifyChange();
    502   return true;
    503 }
    504 
    505 bool ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
    506   EXTENSION_FUNCTION_VALIDATE(details_);
    507   std::string badge_text;
    508   EXTENSION_FUNCTION_VALIDATE(details_->GetString("text", &badge_text));
    509   extension_action_->SetBadgeText(tab_id_, badge_text);
    510   NotifyChange();
    511   return true;
    512 }
    513 
    514 bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
    515   EXTENSION_FUNCTION_VALIDATE(details_);
    516   base::Value* color_value = NULL;
    517   EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
    518   SkColor color = 0;
    519   if (color_value->IsType(base::Value::TYPE_LIST)) {
    520     base::ListValue* list = NULL;
    521     EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
    522     EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
    523 
    524     int color_array[4] = {0};
    525     for (size_t i = 0; i < arraysize(color_array); ++i) {
    526       EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
    527     }
    528 
    529     color = SkColorSetARGB(color_array[3], color_array[0],
    530                            color_array[1], color_array[2]);
    531   } else if (color_value->IsType(base::Value::TYPE_STRING)) {
    532     std::string color_string;
    533     EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
    534     if (!image_util::ParseCSSColorString(color_string, &color))
    535       return false;
    536   }
    537 
    538   extension_action_->SetBadgeBackgroundColor(tab_id_, color);
    539   NotifyChange();
    540   return true;
    541 }
    542 
    543 bool ExtensionActionGetTitleFunction::RunExtensionAction() {
    544   SetResult(new base::StringValue(extension_action_->GetTitle(tab_id_)));
    545   return true;
    546 }
    547 
    548 bool ExtensionActionGetPopupFunction::RunExtensionAction() {
    549   SetResult(
    550       new base::StringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
    551   return true;
    552 }
    553 
    554 bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
    555   SetResult(new base::StringValue(extension_action_->GetBadgeText(tab_id_)));
    556   return true;
    557 }
    558 
    559 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
    560   base::ListValue* list = new base::ListValue();
    561   SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
    562   list->Append(
    563       new base::FundamentalValue(static_cast<int>(SkColorGetR(color))));
    564   list->Append(
    565       new base::FundamentalValue(static_cast<int>(SkColorGetG(color))));
    566   list->Append(
    567       new base::FundamentalValue(static_cast<int>(SkColorGetB(color))));
    568   list->Append(
    569       new base::FundamentalValue(static_cast<int>(SkColorGetA(color))));
    570   SetResult(list);
    571   return true;
    572 }
    573 
    574 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
    575     : response_sent_(false) {
    576 }
    577 
    578 bool BrowserActionOpenPopupFunction::RunAsync() {
    579   // We only allow the popup in the active window.
    580   Browser* browser = chrome::FindLastActiveWithProfile(
    581                          GetProfile(), chrome::GetActiveDesktop());
    582 
    583   // If there's no active browser, or the Toolbar isn't visible, abort.
    584   // Otherwise, try to open a popup in the active browser.
    585   // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
    586   // fixed.
    587   if (!browser ||
    588       !browser->window()->IsActive() ||
    589       !browser->window()->IsToolbarVisible() ||
    590       !ExtensionActionAPI::Get(GetProfile())->ShowExtensionActionPopup(
    591           extension_.get(), browser, false)) {
    592     error_ = kOpenPopupError;
    593     return false;
    594   }
    595 
    596   registrar_.Add(this,
    597                  NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
    598                  content::Source<Profile>(GetProfile()));
    599 
    600   // Set a timeout for waiting for the notification that the popup is loaded.
    601   // Waiting is required so that the popup view can be retrieved by the custom
    602   // bindings for the response callback. It's also needed to keep this function
    603   // instance around until a notification is observed.
    604   base::MessageLoopForUI::current()->PostDelayedTask(
    605       FROM_HERE,
    606       base::Bind(&BrowserActionOpenPopupFunction::OpenPopupTimedOut, this),
    607       base::TimeDelta::FromSeconds(10));
    608   return true;
    609 }
    610 
    611 void BrowserActionOpenPopupFunction::OpenPopupTimedOut() {
    612   if (response_sent_)
    613     return;
    614 
    615   DVLOG(1) << "chrome.browserAction.openPopup did not show a popup.";
    616   error_ = kOpenPopupError;
    617   SendResponse(false);
    618   response_sent_ = true;
    619 }
    620 
    621 void BrowserActionOpenPopupFunction::Observe(
    622     int type,
    623     const content::NotificationSource& source,
    624     const content::NotificationDetails& details) {
    625   DCHECK_EQ(NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, type);
    626   if (response_sent_)
    627     return;
    628 
    629   ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
    630   if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP ||
    631       host->extension()->id() != extension_->id())
    632     return;
    633 
    634   SendResponse(true);
    635   response_sent_ = true;
    636   registrar_.RemoveAll();
    637 }
    638 
    639 }  // namespace extensions
    640