Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(477)

Side by Side Diff: chrome/browser/resources/downloads/item_view.js

Issue 977473002: downloads: break downloads.js into more classes/files. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: remove NOTREACHED for testing code Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698