Home | History | Annotate | Download | only in web_applications
      1 // Copyright (c) 2011 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/ui/web_applications/web_app_ui.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/path_service.h"
      9 #include "base/task.h"
     10 #include "base/win/windows_version.h"
     11 #include "chrome/browser/extensions/extension_tab_helper.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/web_applications/web_app.h"
     14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     15 #include "chrome/common/chrome_paths.h"
     16 #include "content/browser/browser_thread.h"
     17 #include "content/browser/tab_contents/tab_contents.h"
     18 #include "content/common/notification_registrar.h"
     19 
     20 #if defined(OS_LINUX)
     21 #include "base/environment.h"
     22 #endif  // defined(OS_LINUX)
     23 
     24 #if defined(OS_WIN)
     25 #include "content/common/notification_details.h"
     26 #include "content/common/notification_source.h"
     27 #endif  // defined(OS_WIN)
     28 
     29 namespace {
     30 
     31 #if defined(OS_WIN)
     32 // UpdateShortcutWorker holds all context data needed for update shortcut.
     33 // It schedules a pre-update check to find all shortcuts that needs to be
     34 // updated. If there are such shortcuts, it schedules icon download and
     35 // update them when icons are downloaded. It observes TAB_CLOSING notification
     36 // and cancels all the work when the underlying tab is closing.
     37 class UpdateShortcutWorker : public NotificationObserver {
     38  public:
     39   explicit UpdateShortcutWorker(TabContentsWrapper* tab_contents);
     40 
     41   void Run();
     42 
     43  private:
     44   // Overridden from NotificationObserver:
     45   virtual void Observe(NotificationType type,
     46                        const NotificationSource& source,
     47                        const NotificationDetails& details);
     48 
     49   // Downloads icon via TabContents.
     50   void DownloadIcon();
     51 
     52   // Callback when icon downloaded.
     53   void OnIconDownloaded(int download_id, bool errored, const SkBitmap& image);
     54 
     55   // Checks if shortcuts exists on desktop, start menu and quick launch.
     56   void CheckExistingShortcuts();
     57 
     58   // Update shortcut files and icons.
     59   void UpdateShortcuts();
     60   void UpdateShortcutsOnFileThread();
     61 
     62   // Callback after shortcuts are updated.
     63   void OnShortcutsUpdated(bool);
     64 
     65   // Deletes the worker on UI thread where it gets created.
     66   void DeleteMe();
     67   void DeleteMeOnUIThread();
     68 
     69   NotificationRegistrar registrar_;
     70 
     71   // Underlying TabContentsWrapper whose shortcuts will be updated.
     72   TabContentsWrapper* tab_contents_;
     73 
     74   // Icons info from tab_contents_'s web app data.
     75   web_app::IconInfoList unprocessed_icons_;
     76 
     77   // Cached shortcut data from the tab_contents_.
     78   ShellIntegration::ShortcutInfo shortcut_info_;
     79 
     80   // Our copy of profile path.
     81   FilePath profile_path_;
     82 
     83   // File name of shortcut/ico file based on app title.
     84   FilePath file_name_;
     85 
     86   // Existing shortcuts.
     87   std::vector<FilePath> shortcut_files_;
     88 
     89   DISALLOW_COPY_AND_ASSIGN(UpdateShortcutWorker);
     90 };
     91 
     92 UpdateShortcutWorker::UpdateShortcutWorker(TabContentsWrapper* tab_contents)
     93     : tab_contents_(tab_contents),
     94       profile_path_(tab_contents->profile()->GetPath()) {
     95   web_app::GetShortcutInfoForTab(tab_contents_, &shortcut_info_);
     96   web_app::GetIconsInfo(tab_contents_->extension_tab_helper()->web_app_info(),
     97                         &unprocessed_icons_);
     98   file_name_ = web_app::internals::GetSanitizedFileName(shortcut_info_.title);
     99 
    100   registrar_.Add(this, NotificationType::TAB_CLOSING,
    101                  Source<NavigationController>(&tab_contents_->controller()));
    102 }
    103 
    104 void UpdateShortcutWorker::Run() {
    105   // Starting by downloading app icon.
    106   DownloadIcon();
    107 }
    108 
    109 void UpdateShortcutWorker::Observe(NotificationType type,
    110                                    const NotificationSource& source,
    111                                    const NotificationDetails& details) {
    112   if (type == NotificationType::TAB_CLOSING &&
    113       Source<NavigationController>(source).ptr() ==
    114         &tab_contents_->controller()) {
    115     // Underlying tab is closing.
    116     tab_contents_ = NULL;
    117   }
    118 }
    119 
    120 void UpdateShortcutWorker::DownloadIcon() {
    121   // FetchIcon must run on UI thread because it relies on TabContents
    122   // to download the icon.
    123   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    124 
    125   if (tab_contents_ == NULL) {
    126     DeleteMe();  // We are done if underlying TabContents is gone.
    127     return;
    128   }
    129 
    130   if (unprocessed_icons_.empty()) {
    131     // No app icon. Just use the favicon from TabContents.
    132     UpdateShortcuts();
    133     return;
    134   }
    135 
    136   tab_contents_->tab_contents()->favicon_helper().DownloadImage(
    137       unprocessed_icons_.back().url,
    138       std::max(unprocessed_icons_.back().width,
    139                unprocessed_icons_.back().height),
    140       history::FAVICON,
    141       NewCallback(this, &UpdateShortcutWorker::OnIconDownloaded));
    142   unprocessed_icons_.pop_back();
    143 }
    144 
    145 void UpdateShortcutWorker::OnIconDownloaded(int download_id,
    146                                             bool errored,
    147                                             const SkBitmap& image) {
    148   if (tab_contents_ == NULL) {
    149     DeleteMe();  // We are done if underlying TabContents is gone.
    150     return;
    151   }
    152 
    153   if (!errored && !image.isNull()) {
    154     // Update icon with download image and update shortcut.
    155     shortcut_info_.favicon = image;
    156     tab_contents_->extension_tab_helper()->SetAppIcon(image);
    157     UpdateShortcuts();
    158   } else {
    159     // Try the next icon otherwise.
    160     DownloadIcon();
    161   }
    162 }
    163 
    164 void UpdateShortcutWorker::CheckExistingShortcuts() {
    165   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    166 
    167   // Locations to check to shortcut_paths.
    168   struct {
    169     bool& use_this_location;
    170     int location_id;
    171     const wchar_t* sub_dir;
    172   } locations[] = {
    173     {
    174       shortcut_info_.create_on_desktop,
    175       chrome::DIR_USER_DESKTOP,
    176       NULL
    177     }, {
    178       shortcut_info_.create_in_applications_menu,
    179       base::DIR_START_MENU,
    180       NULL
    181     }, {
    182       shortcut_info_.create_in_quick_launch_bar,
    183       // For Win7, create_in_quick_launch_bar means pinning to taskbar.
    184       base::DIR_APP_DATA,
    185       (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
    186           L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar" :
    187           L"Microsoft\\Internet Explorer\\Quick Launch"
    188     }
    189   };
    190 
    191   for (int i = 0; i < arraysize(locations); ++i) {
    192     locations[i].use_this_location = false;
    193 
    194     FilePath path;
    195     if (!PathService::Get(locations[i].location_id, &path)) {
    196       NOTREACHED();
    197       continue;
    198     }
    199 
    200     if (locations[i].sub_dir != NULL)
    201       path = path.Append(locations[i].sub_dir);
    202 
    203     FilePath shortcut_file = path.Append(file_name_).
    204         ReplaceExtension(FILE_PATH_LITERAL(".lnk"));
    205     if (file_util::PathExists(shortcut_file)) {
    206       locations[i].use_this_location = true;
    207       shortcut_files_.push_back(shortcut_file);
    208     }
    209   }
    210 }
    211 
    212 void UpdateShortcutWorker::UpdateShortcuts() {
    213   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    214       NewRunnableMethod(this,
    215       &UpdateShortcutWorker::UpdateShortcutsOnFileThread));
    216 }
    217 
    218 void UpdateShortcutWorker::UpdateShortcutsOnFileThread() {
    219   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    220 
    221   FilePath web_app_path = web_app::internals::GetWebAppDataDirectory(
    222       web_app::GetDataDir(profile_path_), shortcut_info_);
    223 
    224   // Ensure web_app_path exists. web_app_path could be missing for a legacy
    225   // shortcut created by Gears.
    226   if (!file_util::PathExists(web_app_path) &&
    227       !file_util::CreateDirectory(web_app_path)) {
    228     NOTREACHED();
    229     return;
    230   }
    231 
    232   FilePath icon_file = web_app_path.Append(file_name_).ReplaceExtension(
    233       FILE_PATH_LITERAL(".ico"));
    234   web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info_.favicon);
    235 
    236   // Update existing shortcuts' description, icon and app id.
    237   CheckExistingShortcuts();
    238   if (!shortcut_files_.empty()) {
    239     // Generates app id from web app url and profile path.
    240     std::wstring app_id = ShellIntegration::GetAppId(
    241         UTF8ToWide(web_app::GenerateApplicationNameFromURL(shortcut_info_.url)),
    242         profile_path_);
    243 
    244     // Sanitize description
    245     if (shortcut_info_.description.length() >= MAX_PATH)
    246       shortcut_info_.description.resize(MAX_PATH - 1);
    247 
    248     for (size_t i = 0; i < shortcut_files_.size(); ++i) {
    249       file_util::UpdateShortcutLink(NULL,
    250           shortcut_files_[i].value().c_str(),
    251           NULL,
    252           NULL,
    253           shortcut_info_.description.c_str(),
    254           icon_file.value().c_str(),
    255           0,
    256           app_id.c_str());
    257     }
    258   }
    259 
    260   OnShortcutsUpdated(true);
    261 }
    262 
    263 void UpdateShortcutWorker::OnShortcutsUpdated(bool) {
    264   DeleteMe();  // We are done.
    265 }
    266 
    267 void UpdateShortcutWorker::DeleteMe() {
    268   if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    269     DeleteMeOnUIThread();
    270   } else {
    271     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    272       NewRunnableMethod(this, &UpdateShortcutWorker::DeleteMeOnUIThread));
    273   }
    274 }
    275 
    276 void UpdateShortcutWorker::DeleteMeOnUIThread() {
    277   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    278   delete this;
    279 }
    280 #endif  // defined(OS_WIN)
    281 
    282 }  // namespace
    283 
    284 #if defined(OS_WIN)
    285 // Allows UpdateShortcutWorker without adding refcounting. UpdateShortcutWorker
    286 // manages its own life time and will delete itself when it's done.
    287 DISABLE_RUNNABLE_METHOD_REFCOUNT(UpdateShortcutWorker);
    288 #endif  // defined(OS_WIN)
    289 
    290 namespace web_app {
    291 
    292 void GetShortcutInfoForTab(TabContentsWrapper* tab_contents_wrapper,
    293                            ShellIntegration::ShortcutInfo* info) {
    294   DCHECK(info);  // Must provide a valid info.
    295   const TabContents* tab_contents = tab_contents_wrapper->tab_contents();
    296 
    297   const WebApplicationInfo& app_info =
    298       tab_contents_wrapper->extension_tab_helper()->web_app_info();
    299 
    300   info->url = app_info.app_url.is_empty() ? tab_contents->GetURL() :
    301                                             app_info.app_url;
    302   info->title = app_info.title.empty() ?
    303       (tab_contents->GetTitle().empty() ? UTF8ToUTF16(info->url.spec()) :
    304                                           tab_contents->GetTitle()) :
    305       app_info.title;
    306   info->description = app_info.description;
    307   info->favicon = tab_contents->GetFavicon();
    308 }
    309 
    310 void UpdateShortcutForTabContents(TabContentsWrapper* tab_contents) {
    311 #if defined(OS_WIN)
    312   // UpdateShortcutWorker will delete itself when it's done.
    313   UpdateShortcutWorker* worker = new UpdateShortcutWorker(tab_contents);
    314   worker->Run();
    315 #endif  // defined(OS_WIN)
    316 }
    317 
    318 }  // namespace web_app
    319