Home | History | Annotate | Download | only in web_applications
      1 // Copyright 2014 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/web_applications/update_shortcut_worker_win.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/bind_helpers.h"
     11 #include "base/files/file_util.h"
     12 #include "base/path_service.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/win/shortcut.h"
     15 #include "base/win/windows_version.h"
     16 #include "chrome/browser/chrome_notification_types.h"
     17 #include "chrome/browser/extensions/tab_helper.h"
     18 #include "chrome/browser/favicon/favicon_tab_helper.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/browser/web_applications/web_app.h"
     21 #include "chrome/browser/web_applications/web_app_win.h"
     22 #include "components/favicon_base/select_favicon_frames.h"
     23 #include "content/public/browser/browser_thread.h"
     24 #include "content/public/browser/notification_details.h"
     25 #include "content/public/browser/notification_source.h"
     26 #include "content/public/browser/web_contents.h"
     27 #include "ui/gfx/icon_util.h"
     28 #include "url/gurl.h"
     29 
     30 using content::BrowserThread;
     31 using content::NavigationController;
     32 using content::WebContents;
     33 
     34 namespace web_app {
     35 
     36 UpdateShortcutWorker::UpdateShortcutWorker(WebContents* web_contents)
     37     : web_contents_(web_contents),
     38       profile_path_(Profile::FromBrowserContext(
     39           web_contents->GetBrowserContext())->GetPath()) {
     40   extensions::TabHelper* extensions_tab_helper =
     41       extensions::TabHelper::FromWebContents(web_contents);
     42   web_app::GetShortcutInfoForTab(web_contents_, &shortcut_info_);
     43   web_app::GetIconsInfo(extensions_tab_helper->web_app_info(),
     44                         &unprocessed_icons_);
     45   file_name_ = web_app::internals::GetSanitizedFileName(shortcut_info_.title);
     46 
     47   registrar_.Add(
     48       this,
     49       chrome::NOTIFICATION_TAB_CLOSING,
     50       content::Source<NavigationController>(&web_contents->GetController()));
     51 }
     52 
     53 void UpdateShortcutWorker::Run() {
     54   // Starting by downloading app icon.
     55   DownloadIcon();
     56 }
     57 
     58 void UpdateShortcutWorker::Observe(
     59     int type,
     60     const content::NotificationSource& source,
     61     const content::NotificationDetails& details) {
     62   if (type == chrome::NOTIFICATION_TAB_CLOSING &&
     63       content::Source<NavigationController>(source).ptr() ==
     64         &web_contents_->GetController()) {
     65     // Underlying tab is closing.
     66     web_contents_ = NULL;
     67   }
     68 }
     69 
     70 void UpdateShortcutWorker::DownloadIcon() {
     71   // FetchIcon must run on UI thread because it relies on WebContents
     72   // to download the icon.
     73   DCHECK_CURRENTLY_ON(BrowserThread::UI);
     74 
     75   if (web_contents_ == NULL) {
     76     DeleteMe();  // We are done if underlying WebContents is gone.
     77     return;
     78   }
     79 
     80   if (unprocessed_icons_.empty()) {
     81     // No app icon. Just use the favicon from WebContents.
     82     UpdateShortcuts();
     83     return;
     84   }
     85 
     86   int preferred_size = std::max(unprocessed_icons_.back().width,
     87                                 unprocessed_icons_.back().height);
     88   web_contents_->DownloadImage(
     89       unprocessed_icons_.back().url,
     90       true,  // favicon
     91       0,  // no maximum size
     92       base::Bind(&UpdateShortcutWorker::DidDownloadFavicon,
     93                  base::Unretained(this),
     94                  preferred_size));
     95   unprocessed_icons_.pop_back();
     96 }
     97 
     98 void UpdateShortcutWorker::DidDownloadFavicon(
     99     int requested_size,
    100     int id,
    101     int http_status_code,
    102     const GURL& image_url,
    103     const std::vector<SkBitmap>& bitmaps,
    104     const std::vector<gfx::Size>& original_sizes) {
    105   std::vector<int> requested_sizes_in_pixel;
    106   requested_sizes_in_pixel.push_back(requested_size);
    107 
    108   std::vector<size_t> closest_indices;
    109   SelectFaviconFrameIndices(
    110       original_sizes, requested_sizes_in_pixel, &closest_indices, NULL);
    111 
    112   SkBitmap bitmap;
    113   if (!bitmaps.empty()) {
    114      size_t closest_index = closest_indices[0];
    115      bitmap = bitmaps[closest_index];
    116   }
    117 
    118   if (!bitmap.isNull()) {
    119     // Update icon with download image and update shortcut.
    120     shortcut_info_.favicon.Add(gfx::Image::CreateFrom1xBitmap(bitmap));
    121     extensions::TabHelper* extensions_tab_helper =
    122         extensions::TabHelper::FromWebContents(web_contents_);
    123     extensions_tab_helper->SetAppIcon(bitmap);
    124     UpdateShortcuts();
    125   } else {
    126     // Try the next icon otherwise.
    127     DownloadIcon();
    128   }
    129 }
    130 
    131 void UpdateShortcutWorker::CheckExistingShortcuts() {
    132   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    133 
    134   // Locations to check to shortcut_paths.
    135   struct {
    136     int location_id;
    137     const wchar_t* sub_dir;
    138   } locations[] = {
    139     {
    140       base::DIR_USER_DESKTOP,
    141       NULL
    142     }, {
    143       base::DIR_START_MENU,
    144       NULL
    145     }, {
    146       // For Win7, create_in_quick_launch_bar means pinning to taskbar.
    147       base::DIR_APP_DATA,
    148       (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
    149           L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar" :
    150           L"Microsoft\\Internet Explorer\\Quick Launch"
    151     }
    152   };
    153 
    154   for (int i = 0; i < arraysize(locations); ++i) {
    155     base::FilePath path;
    156     if (!PathService::Get(locations[i].location_id, &path)) {
    157       NOTREACHED();
    158       continue;
    159     }
    160 
    161     if (locations[i].sub_dir != NULL)
    162       path = path.Append(locations[i].sub_dir);
    163 
    164     base::FilePath shortcut_file = path.Append(file_name_).
    165         ReplaceExtension(FILE_PATH_LITERAL(".lnk"));
    166     if (base::PathExists(shortcut_file)) {
    167       shortcut_files_.push_back(shortcut_file);
    168     }
    169   }
    170 }
    171 
    172 void UpdateShortcutWorker::UpdateShortcuts() {
    173   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    174       base::Bind(&UpdateShortcutWorker::UpdateShortcutsOnFileThread,
    175                  base::Unretained(this)));
    176 }
    177 
    178 void UpdateShortcutWorker::UpdateShortcutsOnFileThread() {
    179   DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    180 
    181   base::FilePath web_app_path = web_app::GetWebAppDataDirectory(
    182       profile_path_, shortcut_info_.extension_id, shortcut_info_.url);
    183 
    184   // Ensure web_app_path exists. web_app_path could be missing for a legacy
    185   // shortcut created by Gears.
    186   if (!base::PathExists(web_app_path) &&
    187       !base::CreateDirectory(web_app_path)) {
    188     NOTREACHED();
    189     return;
    190   }
    191 
    192   base::FilePath icon_file =
    193       web_app::internals::GetIconFilePath(web_app_path, shortcut_info_.title);
    194   web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info_.favicon);
    195 
    196   // Update existing shortcuts' description, icon and app id.
    197   CheckExistingShortcuts();
    198   if (!shortcut_files_.empty()) {
    199     // Generates app id from web app url and profile path.
    200     base::string16 app_id = ShellIntegration::GetAppModelIdForProfile(
    201         base::UTF8ToWide(
    202             web_app::GenerateApplicationNameFromURL(shortcut_info_.url)),
    203         profile_path_);
    204 
    205     // Sanitize description
    206     if (shortcut_info_.description.length() >= MAX_PATH)
    207       shortcut_info_.description.resize(MAX_PATH - 1);
    208 
    209     for (size_t i = 0; i < shortcut_files_.size(); ++i) {
    210       base::win::ShortcutProperties shortcut_properties;
    211       shortcut_properties.set_target(shortcut_files_[i]);
    212       shortcut_properties.set_description(shortcut_info_.description);
    213       shortcut_properties.set_icon(icon_file, 0);
    214       shortcut_properties.set_app_id(app_id);
    215       base::win::CreateOrUpdateShortcutLink(
    216           shortcut_files_[i], shortcut_properties,
    217           base::win::SHORTCUT_UPDATE_EXISTING);
    218     }
    219   }
    220 
    221   OnShortcutsUpdated(true);
    222 }
    223 
    224 void UpdateShortcutWorker::OnShortcutsUpdated(bool) {
    225   DeleteMe();  // We are done.
    226 }
    227 
    228 void UpdateShortcutWorker::DeleteMe() {
    229   if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    230     DeleteMeOnUIThread();
    231   } else {
    232     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    233       base::Bind(&UpdateShortcutWorker::DeleteMeOnUIThread,
    234                  base::Unretained(this)));
    235   }
    236 }
    237 
    238 void UpdateShortcutWorker::DeleteMeOnUIThread() {
    239   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    240   delete this;
    241 }
    242 
    243 }  // namespace web_app
    244