| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 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; | |
| 14 | |
| 15 this.node = $('templates').querySelector('.download').cloneNode(true); | |
| 16 | |
| 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'); | |
| 38 | |
| 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'); | |
| 48 | |
| 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 } | |
| 62 | |
| 63 /** Progress meter constants. */ | |
| 64 ItemView.Progress = { | |
| 65 /** @const {number} */ | |
| 66 START_ANGLE: -0.5 * Math.PI, | |
| 67 /** @const {number} */ | |
| 68 SIDE: 48, | |
| 69 }; | |
| 70 | |
| 71 /** @const {number} */ | |
| 72 ItemView.Progress.HALF = ItemView.Progress.SIDE / 2; | |
| 73 | |
| 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 } | |
| 84 | |
| 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(); | |
| 99 | |
| 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 }); | |
| 106 | |
| 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 }; | |
| 121 | |
| 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|. | |
| 127 | |
| 128 this.node.classList.toggle('otr', data.otr); | |
| 129 | |
| 130 var dangerText = this.getDangerText_(data); | |
| 131 this.dangerous_.hidden = !dangerText; | |
| 132 this.safe_.hidden = !!dangerText; | |
| 133 | |
| 134 this.ensureTextIs_(this.since_, data.since_string); | |
| 135 this.ensureTextIs_(this.date_, data.date_string); | |
| 136 | |
| 137 if (dangerText) { | |
| 138 this.ensureTextIs_(this.description_, dangerText); | |
| 139 | |
| 140 var dangerType = data.danger_type; | |
| 141 var dangerousFile = dangerType == downloads.DangerType.DANGEROUS_FILE; | |
| 142 this.description_.classList.toggle('malware', !dangerousFile); | |
| 143 | |
| 144 var idr = dangerousFile ? 'IDR_WARNING' : 'IDR_SAFEBROWSING_WARNING'; | |
| 145 var iconUrl = 'chrome://theme/' + idr; | |
| 146 this.iconLoader_.loadScaledIcon(this.dangerImg_, iconUrl); | |
| 147 | |
| 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; | |
| 153 | |
| 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); | |
| 160 | |
| 161 /** @const */ var isInProgress = | |
| 162 data.state == downloads.States.IN_PROGRESS; | |
| 163 this.node.classList.toggle('in-progress', isInProgress); | |
| 164 | |
| 165 /** @const */ var completelyOnDisk = | |
| 166 data.state == downloads.States.COMPLETE && | |
| 167 !data.file_externally_removed; | |
| 168 | |
| 169 this.fileLink_.href = data.url; | |
| 170 this.ensureTextIs_(this.fileLink_, data.file_name); | |
| 171 this.fileLink_.hidden = !completelyOnDisk; | |
| 172 | |
| 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; | |
| 178 | |
| 179 this.show_.hidden = !completelyOnDisk; | |
| 180 | |
| 181 this.retry_.href = data.url; | |
| 182 this.retry_.hidden = !data.retry; | |
| 183 | |
| 184 this.pause_.hidden = !isInProgress; | |
| 185 | |
| 186 this.resume_.hidden = !data.resume; | |
| 187 | |
| 188 /** @const */ var isPaused = data.state == downloads.States.PAUSED; | |
| 189 /** @const */ var showCancel = isPaused || isInProgress; | |
| 190 this.cancel_.hidden = !showCancel; | |
| 191 | |
| 192 this.safeRemove_.hidden = showCancel || | |
| 193 !loadTimeData.getBoolean('allowDeletingHistory'); | |
| 194 | |
| 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 } | |
| 204 | |
| 205 this.ensureTextIs_(this.srcUrl_, data.url); | |
| 206 this.srcUrl_.href = data.url; | |
| 207 this.ensureTextIs_(this.status_, this.getStatusText_(data)); | |
| 208 | |
| 209 this.foregroundProgress_.hidden = !isInProgress; | |
| 210 this.backgroundProgress_.hidden = !isInProgress; | |
| 211 | |
| 212 if (isInProgress) { | |
| 213 this.foregroundProgress_.width = ItemView.Progress.width; | |
| 214 this.foregroundProgress_.height = ItemView.Progress.height; | |
| 215 | |
| 216 if (!this.progressContext_) { | |
| 217 /** @private */ | |
| 218 this.progressContext_ = /** @type !CanvasRenderingContext2D */( | |
| 219 this.foregroundProgress_.getContext('2d')); | |
| 220 } | |
| 221 | |
| 222 var foregroundImage = ItemView.getForegroundProgressImage(); | |
| 223 | |
| 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); | |
| 233 | |
| 234 this.progressContext_.globalCompositeOperation = 'destination-in'; | |
| 235 this.progressContext_.beginPath(); | |
| 236 this.progressContext_.moveTo(ItemView.Progress.centerX, | |
| 237 ItemView.Progress.centerY); | |
| 238 | |
| 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); | |
| 247 | |
| 248 this.progressContext_.lineTo(ItemView.Progress.centerX, | |
| 249 ItemView.Progress.centerY); | |
| 250 this.progressContext_.fill(); | |
| 251 this.progressContext_.closePath(); | |
| 252 } | |
| 253 } | |
| 254 }, | |
| 255 | |
| 256 destroy: function() { | |
| 257 if (this.node.parentNode) | |
| 258 this.node.parentNode.removeChild(this.node); | |
| 259 }, | |
| 260 | |
| 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 }, | |
| 269 | |
| 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 }, | |
| 280 | |
| 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 }, | |
| 303 | |
| 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 }, | |
| 329 | |
| 330 /** | |
| 331 * @private | |
| 332 * @param {Event} e | |
| 333 */ | |
| 334 onSafeDragstart_: function(e) { | |
| 335 e.preventDefault(); | |
| 336 chrome.send('drag', [this.id_]); | |
| 337 }, | |
| 338 | |
| 339 /** | |
| 340 * @param {Event} e | |
| 341 * @private | |
| 342 */ | |
| 343 onFileLinkClick_: function(e) { | |
| 344 e.preventDefault(); | |
| 345 chrome.send('openFile', [this.id_]); | |
| 346 }, | |
| 347 | |
| 348 /** @private */ | |
| 349 onShowClick_: function() { | |
| 350 chrome.send('show', [this.id_]); | |
| 351 }, | |
| 352 | |
| 353 /** @private */ | |
| 354 onPauseClick_: function() { | |
| 355 chrome.send('pause', [this.id_]); | |
| 356 }, | |
| 357 | |
| 358 /** @private */ | |
| 359 onResumeClick_: function() { | |
| 360 chrome.send('resume', [this.id_]); | |
| 361 }, | |
| 362 | |
| 363 /** @private */ | |
| 364 onSafeRemoveClick_: function() { | |
| 365 chrome.send('remove', [this.id_]); | |
| 366 }, | |
| 367 | |
| 368 /** @private */ | |
| 369 onCancelClick_: function() { | |
| 370 chrome.send('cancel', [this.id_]); | |
| 371 }, | |
| 372 | |
| 373 /** @private */ | |
| 374 onRestoreClick_: function() { | |
| 375 this.onSaveClick_(); | |
| 376 }, | |
| 377 | |
| 378 /** @private */ | |
| 379 onSaveClick_: function() { | |
| 380 chrome.send('saveDangerous', [this.id_]); | |
| 381 }, | |
| 382 | |
| 383 /** @private */ | |
| 384 onDangerRemoveClick_: function() { | |
| 385 this.onDiscardClick_(); | |
| 386 }, | |
| 387 | |
| 388 /** @private */ | |
| 389 onDiscardClick_: function() { | |
| 390 chrome.send('discardDangerous', [this.id_]); | |
| 391 }, | |
| 392 }; | |
| 393 | |
| 394 return {ItemView: ItemView}; | |
| 395 }); | |
| OLD | NEW |