toolkit/mozapps/downloads/DownloadUtils.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial