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