Home | History | Annotate | Download | only in extensions
      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/tab_helper.h"
      6 
      7 #include "chrome/browser/chrome_notification_types.h"
      8 #include "chrome/browser/extensions/activity_log/activity_log.h"
      9 #include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
     10 #include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h"
     11 #include "chrome/browser/extensions/crx_installer.h"
     12 #include "chrome/browser/extensions/extension_action.h"
     13 #include "chrome/browser/extensions/extension_action_manager.h"
     14 #include "chrome/browser/extensions/extension_service.h"
     15 #include "chrome/browser/extensions/extension_system.h"
     16 #include "chrome/browser/extensions/extension_tab_util.h"
     17 #include "chrome/browser/extensions/image_loader.h"
     18 #include "chrome/browser/extensions/page_action_controller.h"
     19 #include "chrome/browser/extensions/script_badge_controller.h"
     20 #include "chrome/browser/extensions/script_bubble_controller.h"
     21 #include "chrome/browser/extensions/script_executor.h"
     22 #include "chrome/browser/extensions/webstore_inline_installer.h"
     23 #include "chrome/browser/profiles/profile.h"
     24 #include "chrome/browser/sessions/session_id.h"
     25 #include "chrome/browser/sessions/session_tab_helper.h"
     26 #include "chrome/browser/ui/browser_dialogs.h"
     27 #include "chrome/browser/ui/web_applications/web_app_ui.h"
     28 #include "chrome/browser/web_applications/web_app.h"
     29 #include "chrome/common/extensions/extension.h"
     30 #include "chrome/common/extensions/extension_constants.h"
     31 #include "chrome/common/extensions/extension_icon_set.h"
     32 #include "chrome/common/extensions/extension_messages.h"
     33 #include "chrome/common/extensions/feature_switch.h"
     34 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     35 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
     36 #include "content/public/browser/invalidate_type.h"
     37 #include "content/public/browser/navigation_controller.h"
     38 #include "content/public/browser/navigation_details.h"
     39 #include "content/public/browser/navigation_entry.h"
     40 #include "content/public/browser/notification_service.h"
     41 #include "content/public/browser/notification_source.h"
     42 #include "content/public/browser/notification_types.h"
     43 #include "content/public/browser/render_process_host.h"
     44 #include "content/public/browser/render_view_host.h"
     45 #include "content/public/browser/render_widget_host_view.h"
     46 #include "content/public/browser/web_contents.h"
     47 #include "content/public/browser/web_contents_view.h"
     48 #include "extensions/common/extension_resource.h"
     49 #include "ui/gfx/image/image.h"
     50 
     51 using content::NavigationController;
     52 using content::NavigationEntry;
     53 using content::RenderViewHost;
     54 using content::WebContents;
     55 
     56 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
     57 
     58 namespace {
     59 
     60 const char kPermissionError[] = "permission_error";
     61 
     62 }  // namespace
     63 
     64 namespace extensions {
     65 
     66 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver(
     67     TabHelper* tab_helper)
     68     : tab_helper_(tab_helper) {
     69   tab_helper_->AddScriptExecutionObserver(this);
     70 }
     71 
     72 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver()
     73     : tab_helper_(NULL) {
     74 }
     75 
     76 TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() {
     77   if (tab_helper_)
     78     tab_helper_->RemoveScriptExecutionObserver(this);
     79 }
     80 
     81 TabHelper::TabHelper(content::WebContents* web_contents)
     82     : content::WebContentsObserver(web_contents),
     83       extension_app_(NULL),
     84       extension_function_dispatcher_(
     85           Profile::FromBrowserContext(web_contents->GetBrowserContext()), this),
     86       pending_web_app_action_(NONE),
     87       script_executor_(new ScriptExecutor(web_contents,
     88                                           &script_execution_observers_)),
     89       image_loader_ptr_factory_(this) {
     90   // The ActiveTabPermissionManager requires a session ID; ensure this
     91   // WebContents has one.
     92   SessionTabHelper::CreateForWebContents(web_contents);
     93   if (web_contents->GetRenderViewHost())
     94     SetTabId(web_contents->GetRenderViewHost());
     95   active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
     96       web_contents,
     97       SessionID::IdForTab(web_contents),
     98       Profile::FromBrowserContext(web_contents->GetBrowserContext())));
     99   if (FeatureSwitch::script_badges()->IsEnabled()) {
    100     location_bar_controller_.reset(
    101         new ScriptBadgeController(web_contents, this));
    102   } else {
    103     location_bar_controller_.reset(
    104         new PageActionController(web_contents));
    105   }
    106 
    107   if (FeatureSwitch::script_bubble()->IsEnabled()) {
    108     script_bubble_controller_.reset(
    109         new ScriptBubbleController(web_contents, this));
    110   }
    111 
    112 
    113   // If more classes need to listen to global content script activity, then
    114   // a separate routing class with an observer interface should be written.
    115   profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
    116   AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
    117 
    118   registrar_.Add(this,
    119                  content::NOTIFICATION_LOAD_STOP,
    120                  content::Source<NavigationController>(
    121                      &web_contents->GetController()));
    122 
    123   registrar_.Add(this,
    124                  chrome::NOTIFICATION_EXTENSION_UNLOADED,
    125                  content::NotificationService::AllSources());
    126 }
    127 
    128 TabHelper::~TabHelper() {
    129   RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
    130 }
    131 
    132 void TabHelper::CreateApplicationShortcuts() {
    133   DCHECK(CanCreateApplicationShortcuts());
    134   NavigationEntry* entry =
    135       web_contents()->GetController().GetLastCommittedEntry();
    136   if (!entry)
    137     return;
    138 
    139   pending_web_app_action_ = CREATE_SHORTCUT;
    140 
    141   // Start fetching web app info for CreateApplicationShortcut dialog and show
    142   // the dialog when the data is available in OnDidGetApplicationInfo.
    143   GetApplicationInfo(entry->GetPageID());
    144 }
    145 
    146 bool TabHelper::CanCreateApplicationShortcuts() const {
    147 #if defined(OS_MACOSX)
    148   return false;
    149 #else
    150   return web_app::IsValidUrl(web_contents()->GetURL()) &&
    151       pending_web_app_action_ == NONE;
    152 #endif
    153 }
    154 
    155 void TabHelper::SetExtensionApp(const Extension* extension) {
    156   DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
    157   extension_app_ = extension;
    158 
    159   UpdateExtensionAppIcon(extension_app_);
    160 
    161   content::NotificationService::current()->Notify(
    162       chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
    163       content::Source<TabHelper>(this),
    164       content::NotificationService::NoDetails());
    165 }
    166 
    167 void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
    168   const Extension* extension = GetExtension(extension_app_id);
    169   if (extension)
    170     SetExtensionApp(extension);
    171 }
    172 
    173 void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
    174   const Extension* extension = GetExtension(extension_app_id);
    175   if (extension)
    176     UpdateExtensionAppIcon(extension);
    177 }
    178 
    179 SkBitmap* TabHelper::GetExtensionAppIcon() {
    180   if (extension_app_icon_.empty())
    181     return NULL;
    182 
    183   return &extension_app_icon_;
    184 }
    185 
    186 void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
    187   SetTabId(render_view_host);
    188 }
    189 
    190 void TabHelper::DidNavigateMainFrame(
    191     const content::LoadCommittedDetails& details,
    192     const content::FrameNavigateParams& params) {
    193 #if defined(ENABLE_EXTENSIONS)
    194   if (ExtensionSystem::Get(profile_)->extension_service() &&
    195       RulesRegistryService::Get(profile_)) {
    196     RulesRegistryService::Get(profile_)->content_rules_registry()->
    197         DidNavigateMainFrame(web_contents(), details, params);
    198   }
    199 #endif  // defined(ENABLE_EXTENSIONS)
    200 
    201   if (details.is_in_page)
    202     return;
    203 
    204   Profile* profile =
    205       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    206   ExtensionService* service = profile->GetExtensionService();
    207   if (!service)
    208     return;
    209 
    210   ExtensionActionManager* extension_action_manager =
    211       ExtensionActionManager::Get(profile);
    212   for (ExtensionSet::const_iterator it = service->extensions()->begin();
    213        it != service->extensions()->end(); ++it) {
    214     ExtensionAction* browser_action =
    215         extension_action_manager->GetBrowserAction(*it->get());
    216     if (browser_action) {
    217       browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents()));
    218       content::NotificationService::current()->Notify(
    219           chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
    220           content::Source<ExtensionAction>(browser_action),
    221           content::NotificationService::NoDetails());
    222     }
    223   }
    224 }
    225 
    226 bool TabHelper::OnMessageReceived(const IPC::Message& message) {
    227   bool handled = true;
    228   IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
    229     IPC_MESSAGE_HANDLER(ExtensionHostMsg_DidGetApplicationInfo,
    230                         OnDidGetApplicationInfo)
    231     IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
    232                         OnInlineWebstoreInstall)
    233     IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
    234                         OnGetAppInstallState);
    235     IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
    236     IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
    237                         OnContentScriptsExecuting)
    238     IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
    239                         OnWatchedPageChange)
    240     IPC_MESSAGE_UNHANDLED(handled = false)
    241   IPC_END_MESSAGE_MAP()
    242   return handled;
    243 }
    244 
    245 void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
    246                                          WebContents* new_web_contents) {
    247   // When the WebContents that this is attached to is cloned, give the new clone
    248   // a TabHelper and copy state over.
    249   CreateForWebContents(new_web_contents);
    250   TabHelper* new_helper = FromWebContents(new_web_contents);
    251 
    252   new_helper->SetExtensionApp(extension_app());
    253   new_helper->extension_app_icon_ = extension_app_icon_;
    254 }
    255 
    256 
    257 void TabHelper::OnDidGetApplicationInfo(int32 page_id,
    258                                         const WebApplicationInfo& info) {
    259   // Android does not implement BrowserWindow.
    260 #if !defined(OS_MACOSX) && !defined(OS_ANDROID)
    261   web_app_info_ = info;
    262 
    263   NavigationEntry* entry =
    264       web_contents()->GetController().GetLastCommittedEntry();
    265   if (!entry || (entry->GetPageID() != page_id))
    266     return;
    267 
    268   switch (pending_web_app_action_) {
    269     case CREATE_SHORTCUT: {
    270       chrome::ShowCreateWebAppShortcutsDialog(
    271           web_contents()->GetView()->GetTopLevelNativeWindow(),
    272           web_contents());
    273       break;
    274     }
    275     case UPDATE_SHORTCUT: {
    276       web_app::UpdateShortcutForTabContents(web_contents());
    277       break;
    278     }
    279     default:
    280       NOTREACHED();
    281       break;
    282   }
    283 
    284   pending_web_app_action_ = NONE;
    285 #endif
    286 }
    287 
    288 void TabHelper::OnInlineWebstoreInstall(
    289     int install_id,
    290     int return_route_id,
    291     const std::string& webstore_item_id,
    292     const GURL& requestor_url) {
    293   WebstoreStandaloneInstaller::Callback callback =
    294       base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this),
    295                  install_id, return_route_id);
    296   scoped_refptr<WebstoreInlineInstaller> installer(
    297       new WebstoreInlineInstaller(
    298           web_contents(),
    299           webstore_item_id,
    300           requestor_url,
    301           callback));
    302   installer->BeginInstall();
    303 }
    304 
    305 void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
    306                                      int return_route_id,
    307                                      int callback_id) {
    308   Profile* profile =
    309       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    310   ExtensionService* extension_service = profile->GetExtensionService();
    311   const ExtensionSet* extensions = extension_service->extensions();
    312   const ExtensionSet* disabled = extension_service->disabled_extensions();
    313 
    314   std::string state;
    315   if (extensions->GetHostedAppByURL(requestor_url))
    316     state = extension_misc::kAppStateInstalled;
    317   else if (disabled->GetHostedAppByURL(requestor_url))
    318     state = extension_misc::kAppStateDisabled;
    319   else
    320     state = extension_misc::kAppStateNotInstalled;
    321 
    322   Send(new ExtensionMsg_GetAppInstallStateResponse(
    323       return_route_id, state, callback_id));
    324 }
    325 
    326 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
    327   extension_function_dispatcher_.Dispatch(request,
    328                                           web_contents()->GetRenderViewHost());
    329 }
    330 
    331 void TabHelper::OnContentScriptsExecuting(
    332     const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
    333     int32 on_page_id,
    334     const GURL& on_url) {
    335   FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_,
    336                     OnScriptsExecuted(web_contents(),
    337                                       executing_scripts_map,
    338                                       on_page_id,
    339                                       on_url));
    340 }
    341 
    342 void TabHelper::OnWatchedPageChange(
    343     const std::vector<std::string>& css_selectors) {
    344 #if defined(ENABLE_EXTENSIONS)
    345   if (ExtensionSystem::Get(profile_)->extension_service() &&
    346       RulesRegistryService::Get(profile_)) {
    347     RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
    348         web_contents(), css_selectors);
    349   }
    350 #endif  // defined(ENABLE_EXTENSIONS)
    351 }
    352 
    353 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
    354   if (extension_app_id.empty())
    355     return NULL;
    356 
    357   Profile* profile =
    358       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    359   ExtensionService* extension_service = profile->GetExtensionService();
    360   if (!extension_service || !extension_service->is_ready())
    361     return NULL;
    362 
    363   const Extension* extension =
    364       extension_service->GetExtensionById(extension_app_id, false);
    365   return extension;
    366 }
    367 
    368 void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
    369   extension_app_icon_.reset();
    370   // Ensure previously enqueued callbacks are ignored.
    371   image_loader_ptr_factory_.InvalidateWeakPtrs();
    372 
    373   // Enqueue OnImageLoaded callback.
    374   if (extension) {
    375     Profile* profile =
    376         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    377     extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile);
    378     loader->LoadImageAsync(
    379         extension,
    380         IconsInfo::GetIconResource(extension,
    381                                    extension_misc::EXTENSION_ICON_SMALLISH,
    382                                    ExtensionIconSet::MATCH_EXACTLY),
    383         gfx::Size(extension_misc::EXTENSION_ICON_SMALLISH,
    384                   extension_misc::EXTENSION_ICON_SMALLISH),
    385         base::Bind(&TabHelper::OnImageLoaded,
    386                    image_loader_ptr_factory_.GetWeakPtr()));
    387   }
    388 }
    389 
    390 void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
    391   extension_app_icon_ = app_icon;
    392   web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
    393 }
    394 
    395 void TabHelper::OnImageLoaded(const gfx::Image& image) {
    396   if (!image.IsEmpty()) {
    397     extension_app_icon_ = *image.ToSkBitmap();
    398     web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
    399   }
    400 }
    401 
    402 WindowController* TabHelper::GetExtensionWindowController() const  {
    403   return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
    404 }
    405 
    406 void TabHelper::OnInlineInstallComplete(int install_id,
    407                                         int return_route_id,
    408                                         bool success,
    409                                         const std::string& error) {
    410   Send(new ExtensionMsg_InlineWebstoreInstallResponse(
    411       return_route_id, install_id, success, success ? std::string() : error));
    412 }
    413 
    414 WebContents* TabHelper::GetAssociatedWebContents() const {
    415   return web_contents();
    416 }
    417 
    418 void TabHelper::GetApplicationInfo(int32 page_id) {
    419   Send(new ExtensionMsg_GetApplicationInfo(routing_id(), page_id));
    420 }
    421 
    422 void TabHelper::Observe(int type,
    423                         const content::NotificationSource& source,
    424                         const content::NotificationDetails& details) {
    425   switch (type) {
    426     case content::NOTIFICATION_LOAD_STOP: {
    427       const NavigationController& controller =
    428           *content::Source<NavigationController>(source).ptr();
    429       DCHECK_EQ(controller.GetWebContents(), web_contents());
    430 
    431       if (pending_web_app_action_ == UPDATE_SHORTCUT) {
    432         // Schedule a shortcut update when web application info is available if
    433         // last committed entry is not NULL. Last committed entry could be NULL
    434         // when an interstitial page is injected (e.g. bad https certificate,
    435         // malware site etc). When this happens, we abort the shortcut update.
    436         NavigationEntry* entry = controller.GetLastCommittedEntry();
    437         if (entry)
    438           GetApplicationInfo(entry->GetPageID());
    439         else
    440           pending_web_app_action_ = NONE;
    441       }
    442       break;
    443     }
    444 
    445     case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
    446       if (script_bubble_controller_) {
    447         script_bubble_controller_->OnExtensionUnloaded(
    448             content::Details<extensions::UnloadedExtensionInfo>(
    449                 details)->extension->id());
    450         break;
    451       }
    452     }
    453   }
    454 }
    455 
    456 void TabHelper::SetTabId(RenderViewHost* render_view_host) {
    457   render_view_host->Send(
    458       new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
    459                                 SessionID::IdForTab(web_contents())));
    460 }
    461 
    462 }  // namespace extensions
    463