OLD | NEW |
---|---|
(Empty) | |
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 cr.define('downloads', function() { | |
6 /** @const */ var Item = downloads.Item; | |
7 | |
8 /** | |
9 * Creates and updates the DOM representation for a download. | |
10 * @constructor | |
11 */ | |
12 function ItemView() { | |
13 this.node = $('templates').querySelector('.download').cloneNode(true); | |
14 | |
15 this.safe_ = this.queryRequired_('.safe'); | |
16 this.since_ = this.queryRequired_('.since'); | |
17 this.dateContainer = this.queryRequired_('.date-container'); | |
18 this.date_ = this.queryRequired_('.date'); | |
19 this.save_ = this.queryRequired_('.save'); | |
20 this.backgroundProgress_ = this.queryRequired_('.progress.background'); | |
21 this.foregroundProgress_ = /** @type !HTMLCanvasElement */( | |
22 this.queryRequired_('canvas.progress')); | |
23 this.safeImg_ = /** @type !HTMLImageElement */( | |
24 this.queryRequired_('.safe img')); | |
25 this.fileName_ = this.queryRequired_('span.name'); | |
26 this.fileLink = this.queryRequired_('[is="action-link"].name'); | |
27 this.status_ = this.queryRequired_('.status'); | |
28 this.srcUrl = this.queryRequired_('.src-url'); | |
29 this.show = this.queryRequired_('.show'); | |
30 this.retry = this.queryRequired_('.retry'); | |
31 this.pause = this.queryRequired_('.pause'); | |
32 this.resume = this.queryRequired_('.resume'); | |
33 this.safeRemove = this.queryRequired_('.safe .remove'); | |
34 this.cancel = this.queryRequired_('.cancel'); | |
35 this.controlledBy = this.queryRequired_('.controlled-by'); | |
36 | |
37 this.dangerous_ = this.queryRequired_('.dangerous'); | |
38 this.dangerImg_ = /** @type {!HTMLImageElement} */( | |
39 this.queryRequired_('.dangerous img')); | |
40 this.description_ = this.queryRequired_('.description'); | |
41 this.malwareControls_ = this.queryRequired_('.dangerous .controls'); | |
42 this.restore = this.queryRequired_('.restore'); | |
43 this.dangerRemove = this.queryRequired_('.dangerous .remove'); | |
44 this.save = this.queryRequired_('.save'); | |
45 this.discard = this.queryRequired_('.discard'); | |
46 | |
47 // Event handlers (bound once on creation). | |
48 this.safe_.ondragstart = this.onSafeDragstart_.bind(this); | |
49 this.fileLink.onclick = this.onFileLinkClick_.bind(this); | |
50 this.show.onclick = this.onShowClick_.bind(this); | |
51 this.pause.onclick = this.onPauseClick_.bind(this); | |
52 this.resume.onclick = this.onResumeClick_.bind(this); | |
53 this.safeRemove.onclick = this.onSafeRemoveClick_.bind(this); | |
54 this.cancel.onclick = this.onCancelClick_.bind(this); | |
55 this.restore.onclick = this.onRestoreClick_.bind(this); | |
56 this.save.onclick = this.onSaveClick_.bind(this); | |
57 this.dangerRemove.onclick = this.onDangerRemoveClick_.bind(this); | |
58 this.discard.onclick = this.onDiscardClick_.bind(this); | |
59 } | |
60 | |
61 /** Progress meter constants. */ | |
62 ItemView.Progress = { | |
63 /** @const {number} */ | |
64 START_ANGLE: -0.5 * Math.PI, | |
65 /** @const {number} */ | |
66 SIDE: 48, | |
67 }; | |
68 | |
69 /** @const {number} */ | |
70 ItemView.Progress.HALF = ItemView.Progress.SIDE / 2; | |
71 | |
72 ItemView.computeDownloadProgress = function() { | |
73 /** | |
74 * @param {number} a Some float. | |
75 * @param {number} b Some float. | |
76 * @param {number=} opt_pct Percent of min(a,b). | |
77 * @return {boolean} true if a is within opt_pct percent of b. | |
78 */ | |
79 function floatEq(a, b, opt_pct) { | |
80 return Math.abs(a - b) < (Math.min(a, b) * (opt_pct || 1.0) / 100.0); | |
81 } | |
82 | |
83 if (floatEq(ItemView.Progress.scale, window.devicePixelRatio)) { | |
84 // Zooming in or out multiple times then typing Ctrl+0 resets the zoom | |
85 // level directly to 1x, which fires the matchMedia event multiple times. | |
86 return; | |
87 } | |
88 var Progress = ItemView.Progress; | |
89 Progress.scale = window.devicePixelRatio; | |
90 Progress.width = Progress.SIDE * Progress.scale; | |
91 Progress.height = Progress.SIDE * Progress.scale; | |
92 Progress.radius = Progress.HALF * Progress.scale; | |
93 Progress.centerX = Progress.HALF * Progress.scale; | |
94 Progress.centerY = Progress.HALF * Progress.scale; | |
95 }; | |
96 ItemView.computeDownloadProgress(); | |
97 | |
98 // Listens for when device-pixel-ratio changes between any zoom level. | |
99 [0.3, 0.4, 0.6, 0.7, 0.8, 0.95, 1.05, 1.2, 1.4, 1.6, 1.9, 2.2, 2.7, 3.5, 4.5]. | |
100 forEach(function(scale) { | |
101 var media = '(-webkit-min-device-pixel-ratio:' + scale + ')'; | |
102 window.matchMedia(media).addListener(ItemView.computeDownloadProgress); | |
103 }); | |
104 | |
105 /** | |
106 * @return {!HTMLImageElement} The correct <img> to show when an item is | |
107 * progressing in the foreground. | |
108 */ | |
109 ItemView.getForegroundProgressImage = function() { | |
110 var x = window.devicePixelRatio >= 2 ? '2x' : '1x'; | |
111 ItemView.foregroundImages_ = ItemView.foregroundImages_ || {}; | |
112 if (!ItemView.foregroundImages_[x]) { | |
113 ItemView.foregroundImages_[x] = new Image; | |
114 var IMAGE_URL = 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32'; | |
115 ItemView.foregroundImages_[x].src = IMAGE_URL + '@' + x; | |
116 } | |
117 return ItemView.foregroundImages_[x]; | |
118 }; | |
119 | |
120 /** @private {Array<{img: HTMLImageElement, url: string}>} */ | |
121 ItemView.iconsToLoad_ = []; | |
122 | |
123 /** | |
124 * Load the provided |url| into |img.src| after appending ?scale=. | |
125 * @param {!HTMLImageElement} img An <img> to show the loaded image in. | |
126 * @param {string} url A remote image URL to load. | |
127 */ | |
128 ItemView.loadScaledIcon = function(img, url) { | |
129 var scale = '?scale=' + window.devicePixelRatio + 'x'; | |
130 ItemView.iconsToLoad_.push({img: img, url: url + scale}); | |
131 ItemView.loadNextIcon_(); | |
132 }; | |
133 | |
134 /** @private */ | |
135 ItemView.loadNextIcon_ = function() { | |
136 if (ItemView.isIconLoading_) | |
137 return; | |
138 | |
139 ItemView.isIconLoading_ = true; | |
140 | |
141 while (ItemView.iconsToLoad_.length) { | |
142 var request = ItemView.iconsToLoad_.shift(); | |
143 var img = request.img; | |
144 | |
145 if (img.src == request.url) | |
asanka
2015/03/10 04:15:51
This is probably OK, but Blink does (or didn't) gu
Dan Beam
2015/03/10 05:41:02
unlikely to be problematic in our case, but I see
| |
146 continue; | |
147 | |
148 img.onabort = img.onerror = img.onload = function() { | |
149 ItemView.isIconLoading_ = false; | |
150 ItemView.loadNextIcon_(); | |
151 }; | |
152 | |
153 img.src = request.url; | |
154 return; | |
155 } | |
156 | |
157 // If we reached here, there's no more work to do. | |
158 ItemView.isIconLoading_ = false; | |
159 }; | |
160 | |
161 ItemView.prototype = { | |
162 /** @param {!downloads.Data} data */ | |
163 update: function(data) { | |
asanka
2015/03/10 04:15:51
Shall we break this up?
Dan Beam
2015/03/10 05:41:02
the reason I didn't is because when there's a sepa
asanka
2015/03/11 19:57:49
Ok
| |
164 assert(!this.id_ || data.id == this.id_); | |
165 this.id_ = data.id; // This is the only thing saved from |data|. | |
166 | |
167 this.node.classList.toggle('otr', data.otr); | |
168 | |
169 var dangerText = this.getDangerText_(data); | |
170 this.dangerous_.hidden = !dangerText; | |
171 this.safe_.hidden = !!dangerText; | |
172 | |
173 if (dangerText) { | |
174 this.ensureTextIs_(this.description_, dangerText); | |
175 | |
176 var dangerousFile = data.danger_type == Item.DangerType.DANGEROUS_FILE; | |
177 this.description_.classList.toggle('malware', !dangerousFile); | |
178 | |
179 var idr = dangerousFile ? 'IDR_WARNING' : 'IDR_SAFEBROWSING_WARNING'; | |
180 ItemView.loadScaledIcon(this.dangerImg_, 'chrome://theme/' + idr); | |
181 | |
182 var showMalwareControls = | |
183 data.danger_type == Item.DangerType.DANGEROUS_CONTENT || | |
184 data.danger_type == Item.DangerType.DANGEROUS_HOST || | |
185 data.danger_type == Item.DangerType.DANGEROUS_URL || | |
186 data.danger_type == Item.DangerType.POTENTIALLY_UNWANTED; | |
187 | |
188 this.malwareControls_.hidden = !showMalwareControls; | |
189 this.discard.hidden = showMalwareControls; | |
190 this.save.hidden = showMalwareControls; | |
191 } else { | |
192 var path = encodeURIComponent(data.file_path); | |
193 ItemView.loadScaledIcon(this.safeImg_, 'chrome://fileicon/' + path); | |
194 | |
195 /** @const */ var isInProgress = data.state == Item.States.IN_PROGRESS; | |
196 this.node.classList.toggle('in-progress', isInProgress); | |
197 | |
198 /** @const */ var completelyOnDisk = | |
199 data.state == Item.States.COMPLETE && !data.file_externally_removed; | |
200 | |
201 this.fileLink.href = data.url; | |
202 this.ensureTextIs_(this.fileLink, data.file_name); | |
203 this.fileLink.hidden = !completelyOnDisk; | |
204 | |
205 var isInterrupted = data.state == Item.States.INTERRUPTED; | |
asanka
2015/03/10 04:15:51
@const for consistency
Dan Beam
2015/03/10 05:41:02
Done.
| |
206 this.fileName_.classList.toggle('interrupted', isInterrupted); | |
207 this.ensureTextIs_(this.fileName_, data.file_name); | |
208 this.fileName_.hidden = completelyOnDisk; | |
209 | |
210 this.show.hidden = !completelyOnDisk; | |
211 | |
212 this.retry.href = data.url; | |
213 this.retry.hidden = !data.retry; | |
214 | |
215 this.pause.hidden = !isInProgress; | |
216 | |
217 this.resume.hidden = !data.resume; | |
218 | |
219 /** @const */ var isPaused = data.state == Item.States.PAUSED; | |
220 /** @const */ var showCancel = isPaused || isInProgress; | |
221 this.cancel.hidden = !showCancel; | |
222 | |
223 this.safeRemove.hidden = showCancel || | |
224 !loadTimeData.getBoolean('allow_deleting_history'); | |
225 | |
226 var controlledByExtension = data.by_ext_id && data.by_ext_name; | |
asanka
2015/03/10 04:15:51
@const
Dan Beam
2015/03/10 05:41:02
Done.
| |
227 this.controlledBy.hidden = !controlledByExtension; | |
228 if (controlledByExtension) { | |
229 var url = 'chrome://extensions#' + data.by_ext_id; | |
230 this.controlledBy.innerHTML = loadTimeData.getStringF( | |
231 'control_by_extension', url, data.by_ext_name); | |
232 } | |
233 | |
234 this.ensureTextIs_(this.since_, data.since_string); | |
235 this.ensureTextIs_(this.date_, data.date_string); | |
236 this.ensureTextIs_(this.srcUrl, data.url); | |
237 this.srcUrl.href = data.url; | |
238 this.ensureTextIs_(this.status_, this.getStatusText_(data)); | |
239 | |
240 this.foregroundProgress_.hidden = !isInProgress; | |
241 this.backgroundProgress_.hidden = !isInProgress; | |
242 | |
243 if (isInProgress) { | |
244 this.foregroundProgress_.width = ItemView.Progress.width; | |
245 this.foregroundProgress_.height = ItemView.Progress.height; | |
246 | |
247 if (!this.progressContext_) { | |
248 /** @private */ | |
249 this.progressContext_ = /** @type !CanvasRenderingContext2D */( | |
250 this.foregroundProgress_.getContext('2d')); | |
251 } | |
252 | |
253 var foregroundImage = ItemView.getForegroundProgressImage(); | |
254 | |
255 // Draw a pie-slice for the progress. | |
256 this.progressContext_.globalCompositeOperation = 'copy'; | |
257 this.progressContext_.drawImage( | |
258 foregroundImage, | |
259 0, 0, // sx, sy | |
260 foregroundImage.width, | |
261 foregroundImage.height, | |
262 0, 0, // x, y | |
263 ItemView.Progress.width, ItemView.Progress.height); | |
264 | |
265 this.progressContext_.globalCompositeOperation = 'destination-in'; | |
266 this.progressContext_.beginPath(); | |
267 this.progressContext_.moveTo(ItemView.Progress.centerX, | |
268 ItemView.Progress.centerY); | |
269 | |
270 // Draw an arc CW for both RTL and LTR. http://crbug.com/13215 | |
271 this.progressContext_.arc( | |
272 ItemView.Progress.centerX, | |
273 ItemView.Progress.centerY, | |
274 ItemView.Progress.radius, | |
275 ItemView.Progress.START_ANGLE, | |
276 ItemView.Progress.START_ANGLE + Math.PI * 0.02 * data.percent, | |
277 false); | |
278 | |
279 this.progressContext_.lineTo(ItemView.Progress.centerX, | |
280 ItemView.Progress.centerY); | |
281 this.progressContext_.fill(); | |
282 this.progressContext_.closePath(); | |
283 } | |
284 } | |
285 }, | |
286 | |
287 destroy: function() { | |
288 if (this.node.parentNode) | |
289 this.node.parentNode.removeChild(this.node); | |
290 }, | |
291 | |
292 /** | |
293 * @param {string} selector A CSS selector (e.g. '.class-name'). | |
294 * @return {!Element} The element found by querying for |selector|. | |
295 * @private | |
296 */ | |
297 queryRequired_: function(selector) { | |
298 return assert(this.node.querySelector(selector)); | |
299 }, | |
300 | |
301 /** | |
302 * Overwrite |el|'s textContent if it differs from |text|. | |
303 * @param {!Element} el | |
304 * @param {string} text | |
305 * @private | |
306 */ | |
307 ensureTextIs_: function(el, text) { | |
308 if (el.textContent != text) | |
309 el.textContent = text; | |
310 }, | |
311 | |
312 /** | |
313 * @param {!downloads.Data} data | |
314 * @return {string} Text describing the danger of a download. Empty if not | |
315 * dangerous. | |
316 */ | |
317 getDangerText_: function(data) { | |
318 switch (data.danger_type) { | |
319 case Item.DangerType.DANGEROUS_FILE: | |
320 return loadTimeData.getStringF('danger_file_desc', data.file_name); | |
321 case Item.DangerType.DANGEROUS_URL: | |
322 return loadTimeData.getString('danger_url_desc'); | |
323 case Item.DangerType.DANGEROUS_CONTENT: // Fall through. | |
324 case Item.DangerType.DANGEROUS_HOST: | |
325 return loadTimeData.getStringF('danger_content_desc', data.file_name); | |
326 case Item.DangerType.UNCOMMON_CONTENT: | |
327 return loadTimeData.getStringF('danger_uncommon_desc', | |
328 data.file_name); | |
329 case Item.DangerType.POTENTIALLY_UNWANTED: | |
330 return loadTimeData.getStringF('danger_settings_desc', | |
331 data.file_name); | |
332 default: | |
333 return ''; | |
334 } | |
335 }, | |
336 | |
337 /** | |
338 * @param {!downloads.Data} data | |
339 * @return {string} User-visible status update text. | |
340 * @private | |
341 */ | |
342 getStatusText_: function(data) { | |
343 switch (data.state) { | |
344 case Item.States.IN_PROGRESS: | |
345 return assert(data.progress_status_text); | |
346 case Item.States.CANCELLED: | |
347 return loadTimeData.getString('status_cancelled'); | |
348 case Item.States.PAUSED: | |
349 return loadTimeData.getString('status_paused'); | |
asanka
2015/03/10 04:15:51
For paused downloads, we should still display the
Dan Beam
2015/03/10 05:41:02
chrome://downloads currently shows "Paused": http:
asanka
2015/03/11 19:57:49
I'd like to change the behavior since it's one of
| |
350 case Item.States.DANGEROUS: | |
asanka
2015/03/10 04:15:51
should be a notreached.
Dan Beam
2015/03/10 05:41:02
copied from here, but I assume the code has change
| |
351 var desc = data.danger_type == Item.DangerType.DANGEROUS_FILE ? | |
352 'danger_file_desc' : 'danger_url_desc'; | |
353 return loadTimeData.getString(desc); | |
354 case Item.States.INTERRUPTED: | |
355 return assert(data.last_reason_text); | |
356 case Item.States.COMPLETE: | |
357 return data.file_externally_removed ? | |
358 loadTimeData.getString('status_removed') : ''; | |
359 } | |
360 assertNotReached(); | |
361 return ''; | |
362 }, | |
363 | |
364 /** | |
365 * @private | |
366 * @param {Event} e | |
367 */ | |
368 onSafeDragstart_: function(e) { | |
369 e.preventDefault(); | |
370 chrome.send('drag', [this.id_]); | |
371 }, | |
372 | |
373 /** | |
374 * @param {Event} e | |
375 * @private | |
376 */ | |
377 onFileLinkClick_: function(e) { | |
378 e.preventDefault(); | |
379 chrome.send('openFile', [this.id_]); | |
380 }, | |
381 | |
382 /** @private */ | |
383 onShowClick_: function() { | |
384 chrome.send('show', [this.id_]); | |
385 }, | |
386 | |
387 /** @private */ | |
388 onPauseClick_: function() { | |
389 chrome.send('pause', [this.id_]); | |
390 }, | |
391 | |
392 /** @private */ | |
393 onResumeClick_: function() { | |
394 chrome.send('resume', [this.id_]); | |
395 }, | |
396 | |
397 /** @private */ | |
398 onSafeRemoveClick_: function() { | |
399 chrome.send('remove', [this.id_]); | |
400 }, | |
401 | |
402 /** @private */ | |
403 onCancelClick_: function() { | |
404 chrome.send('cancel', [this.id_]); | |
405 }, | |
406 | |
407 /** @private */ | |
408 onRestoreClick_: function() { | |
409 this.onSaveClick_(); | |
410 }, | |
411 | |
412 /** @private */ | |
413 onSaveClick_: function() { | |
414 chrome.send('saveDangerous', [this.id_]); | |
415 }, | |
416 | |
417 /** @private */ | |
418 onDangerRemoveClick_: function() { | |
419 this.onDiscardClick_(); | |
420 }, | |
421 | |
422 /** @private */ | |
423 onDiscardClick_: function() { | |
424 chrome.send('discardDangerous', [this.id_]); | |
425 }, | |
426 }; | |
427 | |
428 return {ItemView: ItemView}; | |
429 }); | |
OLD | NEW |