Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / downloads / item_view.js
blobf91d3fb1c086a76d47583a83395de1d9ff3393e5
1 // Copyright 2015 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.
5 cr.define('downloads', function() {
6   /**
7    * Creates and updates the DOM representation for a download.
8    * @param {!downloads.ThrottledIconLoader} iconLoader
9    * @constructor
10    */
11   function ItemView(iconLoader) {
12     /** @private {!downloads.ThrottledIconLoader} */
13     this.iconLoader_ = iconLoader;
15     this.node = $('templates').querySelector('.download').cloneNode(true);
17     this.safe_ = this.queryRequired_('.safe');
18     this.since_ = this.queryRequired_('.since');
19     this.dateContainer = this.queryRequired_('.date-container');
20     this.date_ = this.queryRequired_('.date');
21     this.save_ = this.queryRequired_('.save');
22     this.backgroundProgress_ = this.queryRequired_('.progress.background');
23     this.foregroundProgress_ = /** @type !HTMLCanvasElement */(
24         this.queryRequired_('canvas.progress'));
25     this.safeImg_ = /** @type !HTMLImageElement */(
26         this.queryRequired_('.safe img'));
27     this.fileName_ = this.queryRequired_('span.name');
28     this.fileLink_ = this.queryRequired_('[is="action-link"].name');
29     this.status_ = this.queryRequired_('.status');
30     this.srcUrl_ = this.queryRequired_('.src-url');
31     this.show_ = this.queryRequired_('.show');
32     this.retry_ = this.queryRequired_('.retry');
33     this.pause_ = this.queryRequired_('.pause');
34     this.resume_ = this.queryRequired_('.resume');
35     this.safeRemove_ = this.queryRequired_('.safe .remove');
36     this.cancel_ = this.queryRequired_('.cancel');
37     this.controlledBy_ = this.queryRequired_('.controlled-by');
39     this.dangerous_ = this.queryRequired_('.dangerous');
40     this.dangerImg_ = /** @type {!HTMLImageElement} */(
41         this.queryRequired_('.dangerous img'));
42     this.description_ = this.queryRequired_('.description');
43     this.malwareControls_ = this.queryRequired_('.dangerous .controls');
44     this.restore_ = this.queryRequired_('.restore');
45     this.dangerRemove_ = this.queryRequired_('.dangerous .remove');
46     this.save_ = this.queryRequired_('.save');
47     this.discard_ = this.queryRequired_('.discard');
49     // Event handlers (bound once on creation).
50     this.safe_.ondragstart = this.onSafeDragstart_.bind(this);
51     this.fileLink_.onclick = this.onFileLinkClick_.bind(this);
52     this.show_.onclick = this.onShowClick_.bind(this);
53     this.pause_.onclick = this.onPauseClick_.bind(this);
54     this.resume_.onclick = this.onResumeClick_.bind(this);
55     this.safeRemove_.onclick = this.onSafeRemoveClick_.bind(this);
56     this.cancel_.onclick = this.onCancelClick_.bind(this);
57     this.restore_.onclick = this.onRestoreClick_.bind(this);
58     this.save_.onclick = this.onSaveClick_.bind(this);
59     this.dangerRemove_.onclick = this.onDangerRemoveClick_.bind(this);
60     this.discard_.onclick = this.onDiscardClick_.bind(this);
61   }
63   /** Progress meter constants. */
64   ItemView.Progress = {
65     /** @const {number} */
66     START_ANGLE: -0.5 * Math.PI,
67     /** @const {number} */
68     SIDE: 48,
69   };
71   /** @const {number} */
72   ItemView.Progress.HALF = ItemView.Progress.SIDE / 2;
74   ItemView.computeDownloadProgress = function() {
75     /**
76      * @param {number} a Some float.
77      * @param {number} b Some float.
78      * @param {number=} opt_pct Percent of min(a,b).
79      * @return {boolean} true if a is within opt_pct percent of b.
80      */
81     function floatEq(a, b, opt_pct) {
82       return Math.abs(a - b) < (Math.min(a, b) * (opt_pct || 1.0) / 100.0);
83     }
85     if (floatEq(ItemView.Progress.scale, window.devicePixelRatio)) {
86       // Zooming in or out multiple times then typing Ctrl+0 resets the zoom
87       // level directly to 1x, which fires the matchMedia event multiple times.
88       return;
89     }
90     var Progress = ItemView.Progress;
91     Progress.scale = window.devicePixelRatio;
92     Progress.width = Progress.SIDE * Progress.scale;
93     Progress.height = Progress.SIDE * Progress.scale;
94     Progress.radius = Progress.HALF * Progress.scale;
95     Progress.centerX = Progress.HALF * Progress.scale;
96     Progress.centerY = Progress.HALF * Progress.scale;
97   };
98   ItemView.computeDownloadProgress();
100   // Listens for when device-pixel-ratio changes between any zoom level.
101   [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].
102       forEach(function(scale) {
103     var media = '(-webkit-min-device-pixel-ratio:' + scale + ')';
104     window.matchMedia(media).addListener(ItemView.computeDownloadProgress);
105   });
107   /**
108    * @return {!HTMLImageElement} The correct <img> to show when an item is
109    *     progressing in the foreground.
110    */
111   ItemView.getForegroundProgressImage = function() {
112     var x = window.devicePixelRatio >= 2 ? '2x' : '1x';
113     ItemView.foregroundImages_ = ItemView.foregroundImages_ || {};
114     if (!ItemView.foregroundImages_[x]) {
115       ItemView.foregroundImages_[x] = new Image;
116       var IMAGE_URL = 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32';
117       ItemView.foregroundImages_[x].src = IMAGE_URL + '@' + x;
118     }
119     return ItemView.foregroundImages_[x];
120   };
122   ItemView.prototype = {
123     /** @param {!downloads.Data} data */
124     update: function(data) {
125       assert(!this.id_ || data.id == this.id_);
126       this.id_ = data.id;  // This is the only thing saved from |data|.
128       this.node.classList.toggle('otr', data.otr);
130       var dangerText = this.getDangerText_(data);
131       this.dangerous_.hidden = !dangerText;
132       this.safe_.hidden = !!dangerText;
134       this.ensureTextIs_(this.since_, data.since_string);
135       this.ensureTextIs_(this.date_, data.date_string);
137       if (dangerText) {
138         this.ensureTextIs_(this.description_, dangerText);
140         var dangerType = data.danger_type;
141         var dangerousFile = dangerType == downloads.DangerType.DANGEROUS_FILE;
142         this.description_.classList.toggle('malware', !dangerousFile);
144         var idr = dangerousFile ? 'IDR_WARNING' : 'IDR_SAFEBROWSING_WARNING';
145         var iconUrl = 'chrome://theme/' + idr;
146         this.iconLoader_.loadScaledIcon(this.dangerImg_, iconUrl);
148         var showMalwareControls =
149             dangerType == downloads.DangerType.DANGEROUS_CONTENT ||
150             dangerType == downloads.DangerType.DANGEROUS_HOST ||
151             dangerType == downloads.DangerType.DANGEROUS_URL ||
152             dangerType == downloads.DangerType.POTENTIALLY_UNWANTED;
154         this.malwareControls_.hidden = !showMalwareControls;
155         this.discard_.hidden = showMalwareControls;
156         this.save_.hidden = showMalwareControls;
157       } else {
158         var iconUrl = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
159         this.iconLoader_.loadScaledIcon(this.safeImg_, iconUrl);
161         /** @const */ var isInProgress =
162             data.state == downloads.States.IN_PROGRESS;
163         this.node.classList.toggle('in-progress', isInProgress);
165         /** @const */ var completelyOnDisk =
166             data.state == downloads.States.COMPLETE &&
167             !data.file_externally_removed;
169         this.fileLink_.href = data.url;
170         this.ensureTextIs_(this.fileLink_, data.file_name);
171         this.fileLink_.hidden = !completelyOnDisk;
173         /** @const */ var isInterrupted =
174             data.state == downloads.States.INTERRUPTED;
175         this.fileName_.classList.toggle('interrupted', isInterrupted);
176         this.ensureTextIs_(this.fileName_, data.file_name);
177         this.fileName_.hidden = completelyOnDisk;
179         this.show_.hidden = !completelyOnDisk;
181         this.retry_.href = data.url;
182         this.retry_.hidden = !data.retry;
184         this.pause_.hidden = !isInProgress;
186         this.resume_.hidden = !data.resume;
188         /** @const */ var isPaused = data.state == downloads.States.PAUSED;
189         /** @const */ var showCancel = isPaused || isInProgress;
190         this.cancel_.hidden = !showCancel;
192         this.safeRemove_.hidden = showCancel ||
193             !loadTimeData.getBoolean('allowDeletingHistory');
195         /** @const */ var controlledByExtension = data.by_ext_id &&
196                                                   data.by_ext_name;
197         this.controlledBy_.hidden = !controlledByExtension;
198         if (controlledByExtension) {
199           var link = this.controlledBy_.querySelector('a');
200           link.href = 'chrome://extensions#' + data.by_ext_id;
201           link.setAttribute('focus-type', 'controlled-by');
202           link.textContent = data.by_ext_name;
203         }
205         this.ensureTextIs_(this.srcUrl_, data.url);
206         this.srcUrl_.href = data.url;
207         this.ensureTextIs_(this.status_, this.getStatusText_(data));
209         this.foregroundProgress_.hidden = !isInProgress;
210         this.backgroundProgress_.hidden = !isInProgress;
212         if (isInProgress) {
213           this.foregroundProgress_.width = ItemView.Progress.width;
214           this.foregroundProgress_.height = ItemView.Progress.height;
216           if (!this.progressContext_) {
217             /** @private */
218             this.progressContext_ = /** @type !CanvasRenderingContext2D */(
219                 this.foregroundProgress_.getContext('2d'));
220           }
222           var foregroundImage = ItemView.getForegroundProgressImage();
224           // Draw a pie-slice for the progress.
225           this.progressContext_.globalCompositeOperation = 'copy';
226           this.progressContext_.drawImage(
227               foregroundImage,
228               0, 0,  // sx, sy
229               foregroundImage.width,
230               foregroundImage.height,
231               0, 0,  // x, y
232               ItemView.Progress.width, ItemView.Progress.height);
234           this.progressContext_.globalCompositeOperation = 'destination-in';
235           this.progressContext_.beginPath();
236           this.progressContext_.moveTo(ItemView.Progress.centerX,
237                                        ItemView.Progress.centerY);
239           // Draw an arc CW for both RTL and LTR. http://crbug.com/13215
240           this.progressContext_.arc(
241               ItemView.Progress.centerX,
242               ItemView.Progress.centerY,
243               ItemView.Progress.radius,
244               ItemView.Progress.START_ANGLE,
245               ItemView.Progress.START_ANGLE + Math.PI * 0.02 * data.percent,
246               false);
248           this.progressContext_.lineTo(ItemView.Progress.centerX,
249                                        ItemView.Progress.centerY);
250           this.progressContext_.fill();
251           this.progressContext_.closePath();
252         }
253       }
254     },
256     destroy: function() {
257       if (this.node.parentNode)
258         this.node.parentNode.removeChild(this.node);
259     },
261     /**
262      * @param {string} selector A CSS selector (e.g. '.class-name').
263      * @return {!Element} The element found by querying for |selector|.
264      * @private
265      */
266     queryRequired_: function(selector) {
267       return assert(this.node.querySelector(selector));
268     },
270     /**
271      * Overwrite |el|'s textContent if it differs from |text|.
272      * @param {!Element} el
273      * @param {string} text
274      * @private
275      */
276     ensureTextIs_: function(el, text) {
277       if (el.textContent != text)
278         el.textContent = text;
279     },
281     /**
282      * @param {!downloads.Data} data
283      * @return {string} Text describing the danger of a download. Empty if not
284      *     dangerous.
285      */
286     getDangerText_: function(data) {
287       switch (data.danger_type) {
288         case downloads.DangerType.DANGEROUS_FILE:
289           return loadTimeData.getStringF('dangerFileDesc', data.file_name);
290         case downloads.DangerType.DANGEROUS_URL:
291           return loadTimeData.getString('dangerUrlDesc');
292         case downloads.DangerType.DANGEROUS_CONTENT:  // Fall through.
293         case downloads.DangerType.DANGEROUS_HOST:
294           return loadTimeData.getStringF('dangerContentDesc', data.file_name);
295         case downloads.DangerType.UNCOMMON_CONTENT:
296           return loadTimeData.getStringF('dangerUncommonDesc', data.file_name);
297         case downloads.DangerType.POTENTIALLY_UNWANTED:
298           return loadTimeData.getStringF('dangerSettingsDesc', data.file_name);
299         default:
300           return '';
301       }
302     },
304     /**
305      * @param {!downloads.Data} data
306      * @return {string} User-visible status update text.
307      * @private
308      */
309     getStatusText_: function(data) {
310       switch (data.state) {
311         case downloads.States.IN_PROGRESS:
312         case downloads.States.PAUSED:  // Fallthrough.
313           assert(typeof data.progress_status_text == 'string');
314           return data.progress_status_text;
315         case downloads.States.CANCELLED:
316           return loadTimeData.getString('statusCancelled');
317         case downloads.States.DANGEROUS:
318           break;  // Intentionally hit assertNotReached(); at bottom.
319         case downloads.States.INTERRUPTED:
320           assert(typeof data.last_reason_text == 'string');
321           return data.last_reason_text;
322         case downloads.States.COMPLETE:
323           return data.file_externally_removed ?
324               loadTimeData.getString('statusRemoved') : '';
325       }
326       assertNotReached();
327       return '';
328     },
330     /**
331      * @private
332      * @param {Event} e
333      */
334     onSafeDragstart_: function(e) {
335       e.preventDefault();
336       chrome.send('drag', [this.id_]);
337     },
339     /**
340      * @param {Event} e
341      * @private
342      */
343     onFileLinkClick_: function(e) {
344       e.preventDefault();
345       chrome.send('openFile', [this.id_]);
346     },
348     /** @private */
349     onShowClick_: function() {
350       chrome.send('show', [this.id_]);
351     },
353     /** @private */
354     onPauseClick_: function() {
355       chrome.send('pause', [this.id_]);
356     },
358     /** @private */
359     onResumeClick_: function() {
360       chrome.send('resume', [this.id_]);
361     },
363     /** @private */
364     onSafeRemoveClick_: function() {
365       chrome.send('remove', [this.id_]);
366     },
368     /** @private */
369     onCancelClick_: function() {
370       chrome.send('cancel', [this.id_]);
371     },
373     /** @private */
374     onRestoreClick_: function() {
375       this.onSaveClick_();
376     },
378     /** @private */
379     onSaveClick_: function() {
380       chrome.send('saveDangerous', [this.id_]);
381     },
383     /** @private */
384     onDangerRemoveClick_: function() {
385       this.onDiscardClick_();
386     },
388     /** @private */
389     onDiscardClick_: function() {
390       chrome.send('discardDangerous', [this.id_]);
391     },
392   };
394   return {ItemView: ItemView};