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 "base/logging.h"
      8 #include "base/strings/string_util.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/extensions/active_script_controller.h"
     12 #include "chrome/browser/extensions/activity_log/activity_log.h"
     13 #include "chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h"
     14 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
     15 #include "chrome/browser/extensions/api/webstore/webstore_api.h"
     16 #include "chrome/browser/extensions/bookmark_app_helper.h"
     17 #include "chrome/browser/extensions/error_console/error_console.h"
     18 #include "chrome/browser/extensions/extension_tab_util.h"
     19 #include "chrome/browser/extensions/extension_util.h"
     20 #include "chrome/browser/extensions/location_bar_controller.h"
     21 #include "chrome/browser/extensions/webstore_inline_installer.h"
     22 #include "chrome/browser/extensions/webstore_inline_installer_factory.h"
     23 #include "chrome/browser/profiles/profile.h"
     24 #include "chrome/browser/sessions/session_tab_helper.h"
     25 #include "chrome/browser/shell_integration.h"
     26 #include "chrome/browser/ui/browser_commands.h"
     27 #include "chrome/browser/ui/browser_dialogs.h"
     28 #include "chrome/browser/ui/browser_finder.h"
     29 #include "chrome/browser/ui/browser_window.h"
     30 #include "chrome/browser/ui/host_desktop.h"
     31 #include "chrome/browser/web_applications/web_app.h"
     32 #include "chrome/common/extensions/chrome_extension_messages.h"
     33 #include "chrome/common/extensions/extension_constants.h"
     34 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     35 #include "chrome/common/render_messages.h"
     36 #include "chrome/common/url_constants.h"
     37 #include "content/public/browser/invalidate_type.h"
     38 #include "content/public/browser/navigation_controller.h"
     39 #include "content/public/browser/navigation_details.h"
     40 #include "content/public/browser/navigation_entry.h"
     41 #include "content/public/browser/notification_service.h"
     42 #include "content/public/browser/notification_source.h"
     43 #include "content/public/browser/notification_types.h"
     44 #include "content/public/browser/render_process_host.h"
     45 #include "content/public/browser/render_view_host.h"
     46 #include "content/public/browser/web_contents.h"
     47 #include "content/public/common/frame_navigate_params.h"
     48 #include "extensions/browser/api/declarative/rules_registry_service.h"
     49 #include "extensions/browser/extension_error.h"
     50 #include "extensions/browser/extension_registry.h"
     51 #include "extensions/browser/extension_system.h"
     52 #include "extensions/browser/image_loader.h"
     53 #include "extensions/common/constants.h"
     54 #include "extensions/common/extension.h"
     55 #include "extensions/common/extension_icon_set.h"
     56 #include "extensions/common/extension_messages.h"
     57 #include "extensions/common/extension_resource.h"
     58 #include "extensions/common/extension_urls.h"
     59 #include "extensions/common/feature_switch.h"
     60 #include "extensions/common/manifest_handlers/icons_handler.h"
     61 
     62 #if defined(OS_CHROMEOS)
     63 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
     64 #endif
     65 
     66 #if defined(OS_WIN)
     67 #include "chrome/browser/web_applications/web_app_win.h"
     68 #endif
     69 
     70 using content::NavigationController;
     71 using content::NavigationEntry;
     72 using content::RenderViewHost;
     73 using content::WebContents;
     74 
     75 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
     76 
     77 namespace extensions {
     78 
     79 TabHelper::TabHelper(content::WebContents* web_contents)
     80     : content::WebContentsObserver(web_contents),
     81       extension_app_(NULL),
     82       extension_function_dispatcher_(
     83           Profile::FromBrowserContext(web_contents->GetBrowserContext()),
     84           this),
     85       pending_web_app_action_(NONE),
     86       last_committed_page_id_(-1),
     87       update_shortcut_on_load_complete_(false),
     88       script_executor_(
     89           new ScriptExecutor(web_contents, &script_execution_observers_)),
     90       location_bar_controller_(new LocationBarController(web_contents)),
     91       active_script_controller_(new ActiveScriptController(web_contents)),
     92       webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()),
     93       image_loader_ptr_factory_(this) {
     94   // The ActiveTabPermissionManager requires a session ID; ensure this
     95   // WebContents has one.
     96   SessionTabHelper::CreateForWebContents(web_contents);
     97   if (web_contents->GetRenderViewHost())
     98     SetTabId(web_contents->GetRenderViewHost());
     99   active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
    100       web_contents,
    101       SessionTabHelper::IdForTab(web_contents),
    102       Profile::FromBrowserContext(web_contents->GetBrowserContext())));
    103 
    104   // If more classes need to listen to global content script activity, then
    105   // a separate routing class with an observer interface should be written.
    106   profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
    107 
    108   AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
    109 
    110   registrar_.Add(this,
    111                  content::NOTIFICATION_LOAD_STOP,
    112                  content::Source<NavigationController>(
    113                      &web_contents->GetController()));
    114 }
    115 
    116 TabHelper::~TabHelper() {
    117   RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
    118 }
    119 
    120 void TabHelper::CreateApplicationShortcuts() {
    121   DCHECK(CanCreateApplicationShortcuts());
    122   // Start fetching web app info for CreateApplicationShortcut dialog and show
    123   // the dialog when the data is available in OnDidGetApplicationInfo.
    124   GetApplicationInfo(CREATE_SHORTCUT);
    125 }
    126 
    127 void TabHelper::CreateHostedAppFromWebContents() {
    128   DCHECK(CanCreateBookmarkApp());
    129   // Start fetching web app info for CreateApplicationShortcut dialog and show
    130   // the dialog when the data is available in OnDidGetApplicationInfo.
    131   GetApplicationInfo(CREATE_HOSTED_APP);
    132 }
    133 
    134 bool TabHelper::CanCreateApplicationShortcuts() const {
    135 #if defined(OS_MACOSX)
    136   return false;
    137 #else
    138   return web_app::IsValidUrl(web_contents()->GetURL()) &&
    139       pending_web_app_action_ == NONE;
    140 #endif
    141 }
    142 
    143 bool TabHelper::CanCreateBookmarkApp() const {
    144 #if defined(OS_MACOSX)
    145   return false;
    146 #else
    147   return IsValidBookmarkAppUrl(web_contents()->GetURL()) &&
    148          pending_web_app_action_ == NONE;
    149 #endif
    150 }
    151 
    152 void TabHelper::AddScriptExecutionObserver(ScriptExecutionObserver* observer) {
    153   script_execution_observers_.AddObserver(observer);
    154 }
    155 
    156 void TabHelper::RemoveScriptExecutionObserver(
    157     ScriptExecutionObserver* observer) {
    158   script_execution_observers_.RemoveObserver(observer);
    159 }
    160 
    161 void TabHelper::SetExtensionApp(const Extension* extension) {
    162   DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
    163   if (extension_app_ == extension)
    164     return;
    165 
    166   extension_app_ = extension;
    167 
    168   UpdateExtensionAppIcon(extension_app_);
    169 
    170   content::NotificationService::current()->Notify(
    171       chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
    172       content::Source<TabHelper>(this),
    173       content::NotificationService::NoDetails());
    174 }
    175 
    176 void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
    177   const Extension* extension = GetExtension(extension_app_id);
    178   if (extension)
    179     SetExtensionApp(extension);
    180 }
    181 
    182 void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
    183   const Extension* extension = GetExtension(extension_app_id);
    184   if (extension)
    185     UpdateExtensionAppIcon(extension);
    186 }
    187 
    188 SkBitmap* TabHelper::GetExtensionAppIcon() {
    189   if (extension_app_icon_.empty())
    190     return NULL;
    191 
    192   return &extension_app_icon_;
    193 }
    194 
    195 void TabHelper::FinishCreateBookmarkApp(
    196     const Extension* extension,
    197     const WebApplicationInfo& web_app_info) {
    198   pending_web_app_action_ = NONE;
    199 
    200   // There was an error with downloading the icons or installing the app.
    201   if (!extension)
    202     return;
    203 
    204 #if defined(OS_CHROMEOS)
    205   ChromeLauncherController::instance()->PinAppWithID(extension->id());
    206 #endif
    207 
    208   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
    209   if (browser) {
    210     browser->window()->ShowBookmarkAppBubble(web_app_info, extension->id());
    211   }
    212 }
    213 
    214 void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
    215   SetTabId(render_view_host);
    216 }
    217 
    218 void TabHelper::DidNavigateMainFrame(
    219     const content::LoadCommittedDetails& details,
    220     const content::FrameNavigateParams& params) {
    221   if (ExtensionSystem::Get(profile_)->extension_service() &&
    222       RulesRegistryService::Get(profile_)) {
    223     RulesRegistryService::Get(profile_)->content_rules_registry()->
    224         DidNavigateMainFrame(web_contents(), details, params);
    225   }
    226 
    227   content::BrowserContext* context = web_contents()->GetBrowserContext();
    228   ExtensionRegistry* registry = ExtensionRegistry::Get(context);
    229   const ExtensionSet& enabled_extensions = registry->enabled_extensions();
    230 
    231   if (util::IsStreamlinedHostedAppsEnabled()) {
    232     Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
    233     if (browser && browser->is_app()) {
    234       SetExtensionApp(registry->GetExtensionById(
    235           web_app::GetExtensionIdFromApplicationName(browser->app_name()),
    236           ExtensionRegistry::EVERYTHING));
    237     } else {
    238       UpdateExtensionAppIcon(
    239           enabled_extensions.GetExtensionOrAppByURL(params.url));
    240     }
    241   } else {
    242     UpdateExtensionAppIcon(
    243         enabled_extensions.GetExtensionOrAppByURL(params.url));
    244   }
    245 
    246   if (!details.is_in_page)
    247     ExtensionActionAPI::Get(context)->ClearAllValuesForTab(web_contents());
    248 }
    249 
    250 bool TabHelper::OnMessageReceived(const IPC::Message& message) {
    251   bool handled = true;
    252   IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
    253     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo,
    254                         OnDidGetWebApplicationInfo)
    255     IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
    256                         OnInlineWebstoreInstall)
    257     IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
    258                         OnGetAppInstallState);
    259     IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
    260     IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
    261                         OnContentScriptsExecuting)
    262     IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
    263                         OnWatchedPageChange)
    264     IPC_MESSAGE_UNHANDLED(handled = false)
    265   IPC_END_MESSAGE_MAP()
    266   return handled;
    267 }
    268 
    269 bool TabHelper::OnMessageReceived(const IPC::Message& message,
    270                                   content::RenderFrameHost* render_frame_host) {
    271   bool handled = true;
    272   IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
    273     IPC_MESSAGE_HANDLER(ExtensionHostMsg_DetailedConsoleMessageAdded,
    274                         OnDetailedConsoleMessageAdded)
    275     IPC_MESSAGE_UNHANDLED(handled = false)
    276   IPC_END_MESSAGE_MAP()
    277   return handled;
    278 }
    279 
    280 void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
    281                                          WebContents* new_web_contents) {
    282   // When the WebContents that this is attached to is cloned, give the new clone
    283   // a TabHelper and copy state over.
    284   CreateForWebContents(new_web_contents);
    285   TabHelper* new_helper = FromWebContents(new_web_contents);
    286 
    287   new_helper->SetExtensionApp(extension_app());
    288   new_helper->extension_app_icon_ = extension_app_icon_;
    289 }
    290 
    291 void TabHelper::OnDidGetWebApplicationInfo(const WebApplicationInfo& info) {
    292 #if !defined(OS_MACOSX)
    293   web_app_info_ = info;
    294 
    295   NavigationEntry* entry =
    296       web_contents()->GetController().GetLastCommittedEntry();
    297   if (!entry || last_committed_page_id_ != entry->GetPageID())
    298     return;
    299   last_committed_page_id_ = -1;
    300 
    301   switch (pending_web_app_action_) {
    302     case CREATE_SHORTCUT: {
    303       chrome::ShowCreateWebAppShortcutsDialog(
    304           web_contents()->GetTopLevelNativeWindow(),
    305           web_contents());
    306       break;
    307     }
    308     case CREATE_HOSTED_APP: {
    309       if (web_app_info_.app_url.is_empty())
    310         web_app_info_.app_url = web_contents()->GetURL();
    311 
    312       if (web_app_info_.title.empty())
    313         web_app_info_.title = web_contents()->GetTitle();
    314       if (web_app_info_.title.empty())
    315         web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec());
    316 
    317       bookmark_app_helper_.reset(new BookmarkAppHelper(
    318           ExtensionSystem::Get(profile_)->extension_service(),
    319           web_app_info_, web_contents()));
    320       bookmark_app_helper_->Create(base::Bind(
    321           &TabHelper::FinishCreateBookmarkApp, base::Unretained(this)));
    322       break;
    323     }
    324     case UPDATE_SHORTCUT: {
    325       web_app::UpdateShortcutForTabContents(web_contents());
    326       break;
    327     }
    328     default:
    329       NOTREACHED();
    330       break;
    331   }
    332 
    333   // The hosted app action will be cleared once the installation completes or
    334   // fails.
    335   if (pending_web_app_action_ != CREATE_HOSTED_APP)
    336     pending_web_app_action_ = NONE;
    337 #endif
    338 }
    339 
    340 void TabHelper::OnInlineWebstoreInstall(int install_id,
    341                                         int return_route_id,
    342                                         const std::string& webstore_item_id,
    343                                         const GURL& requestor_url,
    344                                         int listeners_mask) {
    345   // Check that the listener is reasonable. We should never get anything other
    346   // than an install stage listener, a download listener, or both.
    347   if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
    348                           api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0) {
    349     NOTREACHED();
    350     return;
    351   }
    352   // Inform the Webstore API that an inline install is happening, in case the
    353   // page requested status updates.
    354   Profile* profile =
    355       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    356   WebstoreAPI::Get(profile)->OnInlineInstallStart(
    357       return_route_id, this, webstore_item_id, listeners_mask);
    358 
    359   WebstoreStandaloneInstaller::Callback callback =
    360       base::Bind(&TabHelper::OnInlineInstallComplete,
    361                  base::Unretained(this),
    362                  install_id,
    363                  return_route_id);
    364   scoped_refptr<WebstoreInlineInstaller> installer(
    365       webstore_inline_installer_factory_->CreateInstaller(
    366           web_contents(),
    367           webstore_item_id,
    368           requestor_url,
    369           callback));
    370   installer->BeginInstall();
    371 }
    372 
    373 void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
    374                                      int return_route_id,
    375                                      int callback_id) {
    376   ExtensionRegistry* registry =
    377       ExtensionRegistry::Get(web_contents()->GetBrowserContext());
    378   const ExtensionSet& extensions = registry->enabled_extensions();
    379   const ExtensionSet& disabled_extensions = registry->disabled_extensions();
    380 
    381   std::string state;
    382   if (extensions.GetHostedAppByURL(requestor_url))
    383     state = extension_misc::kAppStateInstalled;
    384   else if (disabled_extensions.GetHostedAppByURL(requestor_url))
    385     state = extension_misc::kAppStateDisabled;
    386   else
    387     state = extension_misc::kAppStateNotInstalled;
    388 
    389   Send(new ExtensionMsg_GetAppInstallStateResponse(
    390       return_route_id, state, callback_id));
    391 }
    392 
    393 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
    394   extension_function_dispatcher_.Dispatch(request,
    395                                           web_contents()->GetRenderViewHost());
    396 }
    397 
    398 void TabHelper::OnContentScriptsExecuting(
    399     const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
    400     const GURL& on_url) {
    401   FOR_EACH_OBSERVER(
    402       ScriptExecutionObserver,
    403       script_execution_observers_,
    404       OnScriptsExecuted(web_contents(), executing_scripts_map, on_url));
    405 }
    406 
    407 void TabHelper::OnWatchedPageChange(
    408     const std::vector<std::string>& css_selectors) {
    409   if (ExtensionSystem::Get(profile_)->extension_service() &&
    410       RulesRegistryService::Get(profile_)) {
    411     RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
    412         web_contents(), css_selectors);
    413   }
    414 }
    415 
    416 void TabHelper::OnDetailedConsoleMessageAdded(
    417     const base::string16& message,
    418     const base::string16& source,
    419     const StackTrace& stack_trace,
    420     int32 severity_level) {
    421   if (IsSourceFromAnExtension(source)) {
    422     content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
    423     ErrorConsole::Get(profile_)->ReportError(
    424         scoped_ptr<ExtensionError>(new RuntimeError(
    425             extension_app_ ? extension_app_->id() : std::string(),
    426             profile_->IsOffTheRecord(),
    427             source,
    428             message,
    429             stack_trace,
    430             web_contents() ?
    431                 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
    432             static_cast<logging::LogSeverity>(severity_level),
    433             rvh->GetRoutingID(),
    434             rvh->GetProcess()->GetID())));
    435   }
    436 }
    437 
    438 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
    439   if (extension_app_id.empty())
    440     return NULL;
    441 
    442   content::BrowserContext* context = web_contents()->GetBrowserContext();
    443   return ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
    444       extension_app_id);
    445 }
    446 
    447 void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
    448   extension_app_icon_.reset();
    449   // Ensure previously enqueued callbacks are ignored.
    450   image_loader_ptr_factory_.InvalidateWeakPtrs();
    451 
    452   // Enqueue OnImageLoaded callback.
    453   if (extension) {
    454     Profile* profile =
    455         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    456     ImageLoader* loader = ImageLoader::Get(profile);
    457     loader->LoadImageAsync(
    458         extension,
    459         IconsInfo::GetIconResource(extension,
    460                                    extension_misc::EXTENSION_ICON_SMALL,
    461                                    ExtensionIconSet::MATCH_BIGGER),
    462         gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
    463                   extension_misc::EXTENSION_ICON_SMALL),
    464         base::Bind(&TabHelper::OnImageLoaded,
    465                    image_loader_ptr_factory_.GetWeakPtr()));
    466   }
    467 }
    468 
    469 void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
    470   extension_app_icon_ = app_icon;
    471   web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
    472 }
    473 
    474 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
    475     WebstoreInlineInstallerFactory* factory) {
    476   webstore_inline_installer_factory_.reset(factory);
    477 }
    478 
    479 void TabHelper::OnImageLoaded(const gfx::Image& image) {
    480   if (!image.IsEmpty()) {
    481     extension_app_icon_ = *image.ToSkBitmap();
    482     web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
    483   }
    484 }
    485 
    486 WindowController* TabHelper::GetExtensionWindowController() const  {
    487   return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
    488 }
    489 
    490 void TabHelper::OnInlineInstallComplete(int install_id,
    491                                         int return_route_id,
    492                                         bool success,
    493                                         const std::string& error,
    494                                         webstore_install::Result result) {
    495   Send(new ExtensionMsg_InlineWebstoreInstallResponse(
    496       return_route_id,
    497       install_id,
    498       success,
    499       success ? std::string() : error,
    500       result));
    501 }
    502 
    503 WebContents* TabHelper::GetAssociatedWebContents() const {
    504   return web_contents();
    505 }
    506 
    507 void TabHelper::GetApplicationInfo(WebAppAction action) {
    508   NavigationEntry* entry =
    509       web_contents()->GetController().GetLastCommittedEntry();
    510   if (!entry)
    511     return;
    512 
    513   pending_web_app_action_ = action;
    514   last_committed_page_id_ = entry->GetPageID();
    515 
    516   Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
    517 }
    518 
    519 void TabHelper::Observe(int type,
    520                         const content::NotificationSource& source,
    521                         const content::NotificationDetails& details) {
    522   DCHECK_EQ(content::NOTIFICATION_LOAD_STOP, type);
    523   const NavigationController& controller =
    524       *content::Source<NavigationController>(source).ptr();
    525   DCHECK_EQ(controller.GetWebContents(), web_contents());
    526 
    527   if (update_shortcut_on_load_complete_) {
    528     update_shortcut_on_load_complete_ = false;
    529     // Schedule a shortcut update when web application info is available if
    530     // last committed entry is not NULL. Last committed entry could be NULL
    531     // when an interstitial page is injected (e.g. bad https certificate,
    532     // malware site etc). When this happens, we abort the shortcut update.
    533     if (controller.GetLastCommittedEntry())
    534       GetApplicationInfo(UPDATE_SHORTCUT);
    535   }
    536 }
    537 
    538 void TabHelper::SetTabId(RenderViewHost* render_view_host) {
    539   render_view_host->Send(
    540       new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
    541                                 SessionTabHelper::IdForTab(web_contents())));
    542 }
    543 
    544 }  // namespace extensions
    545