Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* vim: sw=2 ts=2 sts=2 expandtab filetype=javascript |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | this.EXPORTED_SYMBOLS = [ "DownloadUtils" ]; |
michael@0 | 7 | |
michael@0 | 8 | /** |
michael@0 | 9 | * This module provides the DownloadUtils object which contains useful methods |
michael@0 | 10 | * for downloads such as displaying file sizes, transfer times, and download |
michael@0 | 11 | * locations. |
michael@0 | 12 | * |
michael@0 | 13 | * List of methods: |
michael@0 | 14 | * |
michael@0 | 15 | * [string status, double newLast] |
michael@0 | 16 | * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes, |
michael@0 | 17 | * [optional] double aSpeed, [optional] double aLastSec) |
michael@0 | 18 | * |
michael@0 | 19 | * string progress |
michael@0 | 20 | * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes) |
michael@0 | 21 | * |
michael@0 | 22 | * [string timeLeft, double newLast] |
michael@0 | 23 | * getTimeLeft(double aSeconds, [optional] double aLastSec) |
michael@0 | 24 | * |
michael@0 | 25 | * [string dateCompact, string dateComplete] |
michael@0 | 26 | * getReadableDates(Date aDate, [optional] Date aNow) |
michael@0 | 27 | * |
michael@0 | 28 | * [string displayHost, string fullHost] |
michael@0 | 29 | * getURIHost(string aURIString) |
michael@0 | 30 | * |
michael@0 | 31 | * [string convertedBytes, string units] |
michael@0 | 32 | * convertByteUnits(int aBytes) |
michael@0 | 33 | * |
michael@0 | 34 | * [int time, string units, int subTime, string subUnits] |
michael@0 | 35 | * convertTimeUnits(double aSecs) |
michael@0 | 36 | */ |
michael@0 | 37 | |
michael@0 | 38 | const Cc = Components.classes; |
michael@0 | 39 | const Ci = Components.interfaces; |
michael@0 | 40 | const Cu = Components.utils; |
michael@0 | 41 | |
michael@0 | 42 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 43 | |
michael@0 | 44 | XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", |
michael@0 | 45 | "resource://gre/modules/PluralForm.jsm"); |
michael@0 | 46 | |
michael@0 | 47 | this.__defineGetter__("gDecimalSymbol", function() { |
michael@0 | 48 | delete this.gDecimalSymbol; |
michael@0 | 49 | return this.gDecimalSymbol = Number(5.4).toLocaleString().match(/\D/); |
michael@0 | 50 | }); |
michael@0 | 51 | |
michael@0 | 52 | const kDownloadProperties = |
michael@0 | 53 | "chrome://mozapps/locale/downloads/downloads.properties"; |
michael@0 | 54 | |
michael@0 | 55 | let gStr = { |
michael@0 | 56 | statusFormat: "statusFormat3", |
michael@0 | 57 | statusFormatInfiniteRate: "statusFormatInfiniteRate", |
michael@0 | 58 | statusFormatNoRate: "statusFormatNoRate", |
michael@0 | 59 | transferSameUnits: "transferSameUnits2", |
michael@0 | 60 | transferDiffUnits: "transferDiffUnits2", |
michael@0 | 61 | transferNoTotal: "transferNoTotal2", |
michael@0 | 62 | timePair: "timePair2", |
michael@0 | 63 | timeLeftSingle: "timeLeftSingle2", |
michael@0 | 64 | timeLeftDouble: "timeLeftDouble2", |
michael@0 | 65 | timeFewSeconds: "timeFewSeconds", |
michael@0 | 66 | timeUnknown: "timeUnknown", |
michael@0 | 67 | monthDate: "monthDate2", |
michael@0 | 68 | yesterday: "yesterday", |
michael@0 | 69 | doneScheme: "doneScheme2", |
michael@0 | 70 | doneFileScheme: "doneFileScheme", |
michael@0 | 71 | units: ["bytes", "kilobyte", "megabyte", "gigabyte"], |
michael@0 | 72 | // Update timeSize in convertTimeUnits if changing the length of this array |
michael@0 | 73 | timeUnits: ["seconds", "minutes", "hours", "days"], |
michael@0 | 74 | infiniteRate: "infiniteRate", |
michael@0 | 75 | }; |
michael@0 | 76 | |
michael@0 | 77 | // This lazily initializes the string bundle upon first use. |
michael@0 | 78 | this.__defineGetter__("gBundle", function() { |
michael@0 | 79 | delete gBundle; |
michael@0 | 80 | return this.gBundle = Cc["@mozilla.org/intl/stringbundle;1"]. |
michael@0 | 81 | getService(Ci.nsIStringBundleService). |
michael@0 | 82 | createBundle(kDownloadProperties); |
michael@0 | 83 | }); |
michael@0 | 84 | |
michael@0 | 85 | // Keep track of at most this many second/lastSec pairs so that multiple calls |
michael@0 | 86 | // to getTimeLeft produce the same time left |
michael@0 | 87 | const kCachedLastMaxSize = 10; |
michael@0 | 88 | let gCachedLast = []; |
michael@0 | 89 | |
michael@0 | 90 | this.DownloadUtils = { |
michael@0 | 91 | /** |
michael@0 | 92 | * Generate a full status string for a download given its current progress, |
michael@0 | 93 | * total size, speed, last time remaining |
michael@0 | 94 | * |
michael@0 | 95 | * @param aCurrBytes |
michael@0 | 96 | * Number of bytes transferred so far |
michael@0 | 97 | * @param [optional] aMaxBytes |
michael@0 | 98 | * Total number of bytes or -1 for unknown |
michael@0 | 99 | * @param [optional] aSpeed |
michael@0 | 100 | * Current transfer rate in bytes/sec or -1 for unknown |
michael@0 | 101 | * @param [optional] aLastSec |
michael@0 | 102 | * Last time remaining in seconds or Infinity for unknown |
michael@0 | 103 | * @return A pair: [download status text, new value of "last seconds"] |
michael@0 | 104 | */ |
michael@0 | 105 | getDownloadStatus: function DU_getDownloadStatus(aCurrBytes, aMaxBytes, |
michael@0 | 106 | aSpeed, aLastSec) |
michael@0 | 107 | { |
michael@0 | 108 | let [transfer, timeLeft, newLast, normalizedSpeed] |
michael@0 | 109 | = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec); |
michael@0 | 110 | |
michael@0 | 111 | let [rate, unit] = DownloadUtils.convertByteUnits(normalizedSpeed); |
michael@0 | 112 | |
michael@0 | 113 | let status; |
michael@0 | 114 | if (rate === "Infinity") { |
michael@0 | 115 | // Infinity download speed doesn't make sense. Show a localized phrase instead. |
michael@0 | 116 | let params = [transfer, gBundle.GetStringFromName(gStr.infiniteRate), timeLeft]; |
michael@0 | 117 | status = gBundle.formatStringFromName(gStr.statusFormatInfiniteRate, params, |
michael@0 | 118 | params.length); |
michael@0 | 119 | } |
michael@0 | 120 | else { |
michael@0 | 121 | let params = [transfer, rate, unit, timeLeft]; |
michael@0 | 122 | status = gBundle.formatStringFromName(gStr.statusFormat, params, |
michael@0 | 123 | params.length); |
michael@0 | 124 | } |
michael@0 | 125 | return [status, newLast]; |
michael@0 | 126 | }, |
michael@0 | 127 | |
michael@0 | 128 | /** |
michael@0 | 129 | * Generate a status string for a download given its current progress, |
michael@0 | 130 | * total size, speed, last time remaining. The status string contains the |
michael@0 | 131 | * time remaining, as well as the total bytes downloaded. Unlike |
michael@0 | 132 | * getDownloadStatus, it does not include the rate of download. |
michael@0 | 133 | * |
michael@0 | 134 | * @param aCurrBytes |
michael@0 | 135 | * Number of bytes transferred so far |
michael@0 | 136 | * @param [optional] aMaxBytes |
michael@0 | 137 | * Total number of bytes or -1 for unknown |
michael@0 | 138 | * @param [optional] aSpeed |
michael@0 | 139 | * Current transfer rate in bytes/sec or -1 for unknown |
michael@0 | 140 | * @param [optional] aLastSec |
michael@0 | 141 | * Last time remaining in seconds or Infinity for unknown |
michael@0 | 142 | * @return A pair: [download status text, new value of "last seconds"] |
michael@0 | 143 | */ |
michael@0 | 144 | getDownloadStatusNoRate: |
michael@0 | 145 | function DU_getDownloadStatusNoRate(aCurrBytes, aMaxBytes, aSpeed, |
michael@0 | 146 | aLastSec) |
michael@0 | 147 | { |
michael@0 | 148 | let [transfer, timeLeft, newLast] |
michael@0 | 149 | = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec); |
michael@0 | 150 | |
michael@0 | 151 | let params = [transfer, timeLeft]; |
michael@0 | 152 | let status = gBundle.formatStringFromName(gStr.statusFormatNoRate, params, |
michael@0 | 153 | params.length); |
michael@0 | 154 | return [status, newLast]; |
michael@0 | 155 | }, |
michael@0 | 156 | |
michael@0 | 157 | /** |
michael@0 | 158 | * Helper function that returns a transfer string, a time remaining string, |
michael@0 | 159 | * and a new value of "last seconds". |
michael@0 | 160 | * @param aCurrBytes |
michael@0 | 161 | * Number of bytes transferred so far |
michael@0 | 162 | * @param [optional] aMaxBytes |
michael@0 | 163 | * Total number of bytes or -1 for unknown |
michael@0 | 164 | * @param [optional] aSpeed |
michael@0 | 165 | * Current transfer rate in bytes/sec or -1 for unknown |
michael@0 | 166 | * @param [optional] aLastSec |
michael@0 | 167 | * Last time remaining in seconds or Infinity for unknown |
michael@0 | 168 | * @return A triple: [amount transferred string, time remaining string, |
michael@0 | 169 | * new value of "last seconds"] |
michael@0 | 170 | */ |
michael@0 | 171 | _deriveTransferRate: function DU__deriveTransferRate(aCurrBytes, |
michael@0 | 172 | aMaxBytes, aSpeed, |
michael@0 | 173 | aLastSec) |
michael@0 | 174 | { |
michael@0 | 175 | if (aMaxBytes == null) |
michael@0 | 176 | aMaxBytes = -1; |
michael@0 | 177 | if (aSpeed == null) |
michael@0 | 178 | aSpeed = -1; |
michael@0 | 179 | if (aLastSec == null) |
michael@0 | 180 | aLastSec = Infinity; |
michael@0 | 181 | |
michael@0 | 182 | // Calculate the time remaining if we have valid values |
michael@0 | 183 | let seconds = (aSpeed > 0) && (aMaxBytes > 0) ? |
michael@0 | 184 | (aMaxBytes - aCurrBytes) / aSpeed : -1; |
michael@0 | 185 | |
michael@0 | 186 | let transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes); |
michael@0 | 187 | let [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec); |
michael@0 | 188 | return [transfer, timeLeft, newLast, aSpeed]; |
michael@0 | 189 | }, |
michael@0 | 190 | |
michael@0 | 191 | /** |
michael@0 | 192 | * Generate the transfer progress string to show the current and total byte |
michael@0 | 193 | * size. Byte units will be as large as possible and the same units for |
michael@0 | 194 | * current and max will be suppressed for the former. |
michael@0 | 195 | * |
michael@0 | 196 | * @param aCurrBytes |
michael@0 | 197 | * Number of bytes transferred so far |
michael@0 | 198 | * @param [optional] aMaxBytes |
michael@0 | 199 | * Total number of bytes or -1 for unknown |
michael@0 | 200 | * @return The transfer progress text |
michael@0 | 201 | */ |
michael@0 | 202 | getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes) |
michael@0 | 203 | { |
michael@0 | 204 | if (aMaxBytes == null) |
michael@0 | 205 | aMaxBytes = -1; |
michael@0 | 206 | |
michael@0 | 207 | let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes); |
michael@0 | 208 | let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes); |
michael@0 | 209 | |
michael@0 | 210 | // Figure out which byte progress string to display |
michael@0 | 211 | let name, values; |
michael@0 | 212 | if (aMaxBytes < 0) { |
michael@0 | 213 | name = gStr.transferNoTotal; |
michael@0 | 214 | values = [ |
michael@0 | 215 | progress, |
michael@0 | 216 | progressUnits, |
michael@0 | 217 | ]; |
michael@0 | 218 | } else if (progressUnits == totalUnits) { |
michael@0 | 219 | name = gStr.transferSameUnits; |
michael@0 | 220 | values = [ |
michael@0 | 221 | progress, |
michael@0 | 222 | total, |
michael@0 | 223 | totalUnits, |
michael@0 | 224 | ]; |
michael@0 | 225 | } else { |
michael@0 | 226 | name = gStr.transferDiffUnits; |
michael@0 | 227 | values = [ |
michael@0 | 228 | progress, |
michael@0 | 229 | progressUnits, |
michael@0 | 230 | total, |
michael@0 | 231 | totalUnits, |
michael@0 | 232 | ]; |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | return gBundle.formatStringFromName(name, values, values.length); |
michael@0 | 236 | }, |
michael@0 | 237 | |
michael@0 | 238 | /** |
michael@0 | 239 | * Generate a "time left" string given an estimate on the time left and the |
michael@0 | 240 | * last time. The extra time is used to give a better estimate on the time to |
michael@0 | 241 | * show. Both the time values are doubles instead of integers to help get |
michael@0 | 242 | * sub-second accuracy for current and future estimates. |
michael@0 | 243 | * |
michael@0 | 244 | * @param aSeconds |
michael@0 | 245 | * Current estimate on number of seconds left for the download |
michael@0 | 246 | * @param [optional] aLastSec |
michael@0 | 247 | * Last time remaining in seconds or Infinity for unknown |
michael@0 | 248 | * @return A pair: [time left text, new value of "last seconds"] |
michael@0 | 249 | */ |
michael@0 | 250 | getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec) |
michael@0 | 251 | { |
michael@0 | 252 | if (aLastSec == null) |
michael@0 | 253 | aLastSec = Infinity; |
michael@0 | 254 | |
michael@0 | 255 | if (aSeconds < 0) |
michael@0 | 256 | return [gBundle.GetStringFromName(gStr.timeUnknown), aLastSec]; |
michael@0 | 257 | |
michael@0 | 258 | // Try to find a cached lastSec for the given second |
michael@0 | 259 | aLastSec = gCachedLast.reduce(function(aResult, aItem) |
michael@0 | 260 | aItem[0] == aSeconds ? aItem[1] : aResult, aLastSec); |
michael@0 | 261 | |
michael@0 | 262 | // Add the current second/lastSec pair unless we have too many |
michael@0 | 263 | gCachedLast.push([aSeconds, aLastSec]); |
michael@0 | 264 | if (gCachedLast.length > kCachedLastMaxSize) |
michael@0 | 265 | gCachedLast.shift(); |
michael@0 | 266 | |
michael@0 | 267 | // Apply smoothing only if the new time isn't a huge change -- e.g., if the |
michael@0 | 268 | // new time is more than half the previous time; this is useful for |
michael@0 | 269 | // downloads that start/resume slowly |
michael@0 | 270 | if (aSeconds > aLastSec / 2) { |
michael@0 | 271 | // Apply hysteresis to favor downward over upward swings |
michael@0 | 272 | // 30% of down and 10% of up (exponential smoothing) |
michael@0 | 273 | let (diff = aSeconds - aLastSec) { |
michael@0 | 274 | aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff; |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | // If the new time is similar, reuse something close to the last seconds, |
michael@0 | 278 | // but subtract a little to provide forward progress |
michael@0 | 279 | let diff = aSeconds - aLastSec; |
michael@0 | 280 | let diffPct = diff / aLastSec * 100; |
michael@0 | 281 | if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5) |
michael@0 | 282 | aSeconds = aLastSec - (diff < 0 ? .4 : .2); |
michael@0 | 283 | } |
michael@0 | 284 | |
michael@0 | 285 | // Decide what text to show for the time |
michael@0 | 286 | let timeLeft; |
michael@0 | 287 | if (aSeconds < 4) { |
michael@0 | 288 | // Be friendly in the last few seconds |
michael@0 | 289 | timeLeft = gBundle.GetStringFromName(gStr.timeFewSeconds); |
michael@0 | 290 | } else { |
michael@0 | 291 | // Convert the seconds into its two largest units to display |
michael@0 | 292 | let [time1, unit1, time2, unit2] = |
michael@0 | 293 | DownloadUtils.convertTimeUnits(aSeconds); |
michael@0 | 294 | |
michael@0 | 295 | let pair1 = |
michael@0 | 296 | gBundle.formatStringFromName(gStr.timePair, [time1, unit1], 2); |
michael@0 | 297 | let pair2 = |
michael@0 | 298 | gBundle.formatStringFromName(gStr.timePair, [time2, unit2], 2); |
michael@0 | 299 | |
michael@0 | 300 | // Only show minutes for under 1 hour unless there's a few minutes left; |
michael@0 | 301 | // or the second pair is 0. |
michael@0 | 302 | if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) { |
michael@0 | 303 | timeLeft = gBundle.formatStringFromName(gStr.timeLeftSingle, |
michael@0 | 304 | [pair1], 1); |
michael@0 | 305 | } else { |
michael@0 | 306 | // We've got 2 pairs of times to display |
michael@0 | 307 | timeLeft = gBundle.formatStringFromName(gStr.timeLeftDouble, |
michael@0 | 308 | [pair1, pair2], 2); |
michael@0 | 309 | } |
michael@0 | 310 | } |
michael@0 | 311 | |
michael@0 | 312 | return [timeLeft, aSeconds]; |
michael@0 | 313 | }, |
michael@0 | 314 | |
michael@0 | 315 | /** |
michael@0 | 316 | * Converts a Date object to two readable formats, one compact, one complete. |
michael@0 | 317 | * The compact format is relative to the current date, and is not an accurate |
michael@0 | 318 | * representation. For example, only the time is displayed for today. The |
michael@0 | 319 | * complete format always includes both the date and the time, excluding the |
michael@0 | 320 | * seconds, and is often shown when hovering the cursor over the compact |
michael@0 | 321 | * representation. |
michael@0 | 322 | * |
michael@0 | 323 | * @param aDate |
michael@0 | 324 | * Date object representing the date and time to format. It is assumed |
michael@0 | 325 | * that this value represents a past date. |
michael@0 | 326 | * @param [optional] aNow |
michael@0 | 327 | * Date object representing the current date and time. The real date |
michael@0 | 328 | * and time of invocation is used if this parameter is omitted. |
michael@0 | 329 | * @return A pair: [compact text, complete text] |
michael@0 | 330 | */ |
michael@0 | 331 | getReadableDates: function DU_getReadableDates(aDate, aNow) |
michael@0 | 332 | { |
michael@0 | 333 | if (!aNow) { |
michael@0 | 334 | aNow = new Date(); |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"] |
michael@0 | 338 | .getService(Ci.nsIScriptableDateFormat); |
michael@0 | 339 | |
michael@0 | 340 | // Figure out when today begins |
michael@0 | 341 | let today = new Date(aNow.getFullYear(), aNow.getMonth(), aNow.getDate()); |
michael@0 | 342 | |
michael@0 | 343 | // Figure out if the time is from today, yesterday, this week, etc. |
michael@0 | 344 | let dateTimeCompact; |
michael@0 | 345 | if (aDate >= today) { |
michael@0 | 346 | // After today started, show the time |
michael@0 | 347 | dateTimeCompact = dts.FormatTime("", |
michael@0 | 348 | dts.timeFormatNoSeconds, |
michael@0 | 349 | aDate.getHours(), |
michael@0 | 350 | aDate.getMinutes(), |
michael@0 | 351 | 0); |
michael@0 | 352 | } else if (today - aDate < (24 * 60 * 60 * 1000)) { |
michael@0 | 353 | // After yesterday started, show yesterday |
michael@0 | 354 | dateTimeCompact = gBundle.GetStringFromName(gStr.yesterday); |
michael@0 | 355 | } else if (today - aDate < (6 * 24 * 60 * 60 * 1000)) { |
michael@0 | 356 | // After last week started, show day of week |
michael@0 | 357 | dateTimeCompact = aDate.toLocaleFormat("%A"); |
michael@0 | 358 | } else { |
michael@0 | 359 | // Show month/day |
michael@0 | 360 | let month = aDate.toLocaleFormat("%B"); |
michael@0 | 361 | // Remove leading 0 by converting the date string to a number |
michael@0 | 362 | let date = Number(aDate.toLocaleFormat("%d")); |
michael@0 | 363 | dateTimeCompact = gBundle.formatStringFromName(gStr.monthDate, [month, date], 2); |
michael@0 | 364 | } |
michael@0 | 365 | |
michael@0 | 366 | let dateTimeFull = dts.FormatDateTime("", |
michael@0 | 367 | dts.dateFormatLong, |
michael@0 | 368 | dts.timeFormatNoSeconds, |
michael@0 | 369 | aDate.getFullYear(), |
michael@0 | 370 | aDate.getMonth() + 1, |
michael@0 | 371 | aDate.getDate(), |
michael@0 | 372 | aDate.getHours(), |
michael@0 | 373 | aDate.getMinutes(), |
michael@0 | 374 | 0); |
michael@0 | 375 | |
michael@0 | 376 | return [dateTimeCompact, dateTimeFull]; |
michael@0 | 377 | }, |
michael@0 | 378 | |
michael@0 | 379 | /** |
michael@0 | 380 | * Get the appropriate display host string for a URI string depending on if |
michael@0 | 381 | * the URI has an eTLD + 1, is an IP address, a local file, or other protocol |
michael@0 | 382 | * |
michael@0 | 383 | * @param aURIString |
michael@0 | 384 | * The URI string to try getting an eTLD + 1, etc. |
michael@0 | 385 | * @return A pair: [display host for the URI string, full host name] |
michael@0 | 386 | */ |
michael@0 | 387 | getURIHost: function DU_getURIHost(aURIString) |
michael@0 | 388 | { |
michael@0 | 389 | let ioService = Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 390 | getService(Ci.nsIIOService); |
michael@0 | 391 | let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]. |
michael@0 | 392 | getService(Ci.nsIEffectiveTLDService); |
michael@0 | 393 | let idnService = Cc["@mozilla.org/network/idn-service;1"]. |
michael@0 | 394 | getService(Ci.nsIIDNService); |
michael@0 | 395 | |
michael@0 | 396 | // Get a URI that knows about its components |
michael@0 | 397 | let uri = ioService.newURI(aURIString, null, null); |
michael@0 | 398 | |
michael@0 | 399 | // Get the inner-most uri for schemes like jar: |
michael@0 | 400 | if (uri instanceof Ci.nsINestedURI) |
michael@0 | 401 | uri = uri.innermostURI; |
michael@0 | 402 | |
michael@0 | 403 | let fullHost; |
michael@0 | 404 | try { |
michael@0 | 405 | // Get the full host name; some special URIs fail (data: jar:) |
michael@0 | 406 | fullHost = uri.host; |
michael@0 | 407 | } catch (e) { |
michael@0 | 408 | fullHost = ""; |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | let displayHost; |
michael@0 | 412 | try { |
michael@0 | 413 | // This might fail if it's an IP address or doesn't have more than 1 part |
michael@0 | 414 | let baseDomain = eTLDService.getBaseDomain(uri); |
michael@0 | 415 | |
michael@0 | 416 | // Convert base domain for display; ignore the isAscii out param |
michael@0 | 417 | displayHost = idnService.convertToDisplayIDN(baseDomain, {}); |
michael@0 | 418 | } catch (e) { |
michael@0 | 419 | // Default to the host name |
michael@0 | 420 | displayHost = fullHost; |
michael@0 | 421 | } |
michael@0 | 422 | |
michael@0 | 423 | // Check if we need to show something else for the host |
michael@0 | 424 | if (uri.scheme == "file") { |
michael@0 | 425 | // Display special text for file protocol |
michael@0 | 426 | displayHost = gBundle.GetStringFromName(gStr.doneFileScheme); |
michael@0 | 427 | fullHost = displayHost; |
michael@0 | 428 | } else if (displayHost.length == 0) { |
michael@0 | 429 | // Got nothing; show the scheme (data: about: moz-icon:) |
michael@0 | 430 | displayHost = |
michael@0 | 431 | gBundle.formatStringFromName(gStr.doneScheme, [uri.scheme], 1); |
michael@0 | 432 | fullHost = displayHost; |
michael@0 | 433 | } else if (uri.port != -1) { |
michael@0 | 434 | // Tack on the port if it's not the default port |
michael@0 | 435 | let port = ":" + uri.port; |
michael@0 | 436 | displayHost += port; |
michael@0 | 437 | fullHost += port; |
michael@0 | 438 | } |
michael@0 | 439 | |
michael@0 | 440 | return [displayHost, fullHost]; |
michael@0 | 441 | }, |
michael@0 | 442 | |
michael@0 | 443 | /** |
michael@0 | 444 | * Converts a number of bytes to the appropriate unit that results in an |
michael@0 | 445 | * internationalized number that needs fewer than 4 digits. |
michael@0 | 446 | * |
michael@0 | 447 | * @param aBytes |
michael@0 | 448 | * Number of bytes to convert |
michael@0 | 449 | * @return A pair: [new value with 3 sig. figs., its unit] |
michael@0 | 450 | */ |
michael@0 | 451 | convertByteUnits: function DU_convertByteUnits(aBytes) |
michael@0 | 452 | { |
michael@0 | 453 | let unitIndex = 0; |
michael@0 | 454 | |
michael@0 | 455 | // Convert to next unit if it needs 4 digits (after rounding), but only if |
michael@0 | 456 | // we know the name of the next unit |
michael@0 | 457 | while ((aBytes >= 999.5) && (unitIndex < gStr.units.length - 1)) { |
michael@0 | 458 | aBytes /= 1024; |
michael@0 | 459 | unitIndex++; |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | // Get rid of insignificant bits by truncating to 1 or 0 decimal points |
michael@0 | 463 | // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235 |
michael@0 | 464 | // added in bug 462064: (unitIndex != 0) makes sure that no decimal digit for bytes appears when aBytes < 100 |
michael@0 | 465 | aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) && (unitIndex != 0) ? 1 : 0); |
michael@0 | 466 | |
michael@0 | 467 | if (gDecimalSymbol != ".") |
michael@0 | 468 | aBytes = aBytes.replace(".", gDecimalSymbol); |
michael@0 | 469 | return [aBytes, gBundle.GetStringFromName(gStr.units[unitIndex])]; |
michael@0 | 470 | }, |
michael@0 | 471 | |
michael@0 | 472 | /** |
michael@0 | 473 | * Converts a number of seconds to the two largest units. Time values are |
michael@0 | 474 | * whole numbers, and units have the correct plural/singular form. |
michael@0 | 475 | * |
michael@0 | 476 | * @param aSecs |
michael@0 | 477 | * Seconds to convert into the appropriate 2 units |
michael@0 | 478 | * @return 4-item array [first value, its unit, second value, its unit] |
michael@0 | 479 | */ |
michael@0 | 480 | convertTimeUnits: function DU_convertTimeUnits(aSecs) |
michael@0 | 481 | { |
michael@0 | 482 | // These are the maximum values for seconds, minutes, hours corresponding |
michael@0 | 483 | // with gStr.timeUnits without the last item |
michael@0 | 484 | let timeSize = [60, 60, 24]; |
michael@0 | 485 | |
michael@0 | 486 | let time = aSecs; |
michael@0 | 487 | let scale = 1; |
michael@0 | 488 | let unitIndex = 0; |
michael@0 | 489 | |
michael@0 | 490 | // Keep converting to the next unit while we have units left and the |
michael@0 | 491 | // current one isn't the largest unit possible |
michael@0 | 492 | while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) { |
michael@0 | 493 | time /= timeSize[unitIndex]; |
michael@0 | 494 | scale *= timeSize[unitIndex]; |
michael@0 | 495 | unitIndex++; |
michael@0 | 496 | } |
michael@0 | 497 | |
michael@0 | 498 | let value = convertTimeUnitsValue(time); |
michael@0 | 499 | let units = convertTimeUnitsUnits(value, unitIndex); |
michael@0 | 500 | |
michael@0 | 501 | let extra = aSecs - value * scale; |
michael@0 | 502 | let nextIndex = unitIndex - 1; |
michael@0 | 503 | |
michael@0 | 504 | // Convert the extra time to the next largest unit |
michael@0 | 505 | for (let index = 0; index < nextIndex; index++) |
michael@0 | 506 | extra /= timeSize[index]; |
michael@0 | 507 | |
michael@0 | 508 | let value2 = convertTimeUnitsValue(extra); |
michael@0 | 509 | let units2 = convertTimeUnitsUnits(value2, nextIndex); |
michael@0 | 510 | |
michael@0 | 511 | return [value, units, value2, units2]; |
michael@0 | 512 | }, |
michael@0 | 513 | }; |
michael@0 | 514 | |
michael@0 | 515 | /** |
michael@0 | 516 | * Private helper for convertTimeUnits that gets the display value of a time |
michael@0 | 517 | * |
michael@0 | 518 | * @param aTime |
michael@0 | 519 | * Time value for display |
michael@0 | 520 | * @return An integer value for the time rounded down |
michael@0 | 521 | */ |
michael@0 | 522 | function convertTimeUnitsValue(aTime) |
michael@0 | 523 | { |
michael@0 | 524 | return Math.floor(aTime); |
michael@0 | 525 | } |
michael@0 | 526 | |
michael@0 | 527 | /** |
michael@0 | 528 | * Private helper for convertTimeUnits that gets the display units of a time |
michael@0 | 529 | * |
michael@0 | 530 | * @param aTime |
michael@0 | 531 | * Time value for display |
michael@0 | 532 | * @param aIndex |
michael@0 | 533 | * Index into gStr.timeUnits for the appropriate unit |
michael@0 | 534 | * @return The appropriate plural form of the unit for the time |
michael@0 | 535 | */ |
michael@0 | 536 | function convertTimeUnitsUnits(aTime, aIndex) |
michael@0 | 537 | { |
michael@0 | 538 | // Negative index would be an invalid unit, so just give empty |
michael@0 | 539 | if (aIndex < 0) |
michael@0 | 540 | return ""; |
michael@0 | 541 | |
michael@0 | 542 | return PluralForm.get(aTime, gBundle.GetStringFromName(gStr.timeUnits[aIndex])); |
michael@0 | 543 | } |
michael@0 | 544 | |
michael@0 | 545 | /** |
michael@0 | 546 | * Private helper function to log errors to the error console and command line |
michael@0 | 547 | * |
michael@0 | 548 | * @param aMsg |
michael@0 | 549 | * Error message to log or an array of strings to concat |
michael@0 | 550 | */ |
michael@0 | 551 | function log(aMsg) |
michael@0 | 552 | { |
michael@0 | 553 | let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg); |
michael@0 | 554 | Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService). |
michael@0 | 555 | logStringMessage(msg); |
michael@0 | 556 | dump(msg + "\n"); |
michael@0 | 557 | } |