Bug 1504470: Switch to using a storage stream for caching favicon data. r=mak
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 28 Nov 2018 12:42:51 -0800
changeset 505465 4f3b16bba67cceea884be0ac9764bc2f8187ea41
parent 505464 e4712449ba4303cef134ba0b3f1bea13fbd50c4a
child 505466 a8824f8d86c969f386003b6fa4c5cf7bb3fda5ec
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1504470
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1504470: Switch to using a storage stream for caching favicon data. r=mak Differential Revision: https://phabricator.services.mozilla.com/D13361
browser/modules/FaviconLoader.jsm
--- a/browser/modules/FaviconLoader.jsm
+++ b/browser/modules/FaviconLoader.jsm
@@ -11,18 +11,25 @@ ChromeUtils.import("resource://gre/modul
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["Blob", "FileReader"]);
 
 ChromeUtils.defineModuleGetter(this, "DeferredTask",
   "resource://gre/modules/DeferredTask.jsm");
 ChromeUtils.defineModuleGetter(this, "PromiseUtils",
   "resource://gre/modules/PromiseUtils.jsm");
 
+const STREAM_SEGMENT_SIZE = 4096;
+const PR_UINT32_MAX = 0xffffffff;
+
 const BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1",
                                                  "nsIBinaryInputStream", "setInputStream");
+const StorageStream = Components.Constructor("@mozilla.org/storagestream;1",
+                                             "nsIStorageStream", "init");
+const BufferedOutputStream = Components.Constructor("@mozilla.org/network/buffered-output-stream;1",
+                                                    "nsIBufferedOutputStream", "init");
 
 const SIZES_TELEMETRY_ENUM = {
   NO_SIZES: 0,
   ANY: 1,
   DIMENSION: 2,
   INVALID: 3,
 };
 
@@ -60,17 +67,16 @@ function promiseBlobAsOctets(blob) {
     });
     reader.addEventListener("error", reject);
     reader.readAsBinaryString(blob);
   });
 }
 
 class FaviconLoad {
   constructor(iconInfo) {
-    this.buffers = [];
     this.icon = iconInfo;
 
     this.channel = Services.io.newChannelFromURI2(
       iconInfo.iconUri,
       iconInfo.node,
       iconInfo.node.nodePrincipal,
       iconInfo.node.nodePrincipal,
       (Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
@@ -89,21 +95,29 @@ class FaviconLoad {
     if (Services.prefs.getBoolPref("network.http.tailing.enabled", true) &&
         this.channel instanceof Ci.nsIClassOfService) {
       this.channel.addClassFlags(Ci.nsIClassOfService.Tail | Ci.nsIClassOfService.Throttleable);
     }
   }
 
   load() {
     this._deferred = PromiseUtils.defer();
-    // Clear the channel reference when we succeed or fail.
-    this._deferred.promise.then(
-      () => this.channel = null,
-      () => this.channel = null
-    );
+
+    // Clear the references when we succeed or fail.
+    let cleanup = () => {
+      this.channel = null;
+      this.dataBuffer = null;
+      this.stream = null;
+    };
+    this._deferred.promise.then(cleanup, cleanup);
+
+    this.dataBuffer = new StorageStream(STREAM_SEGMENT_SIZE, PR_UINT32_MAX);
+
+    // storage streams do not implement writeFrom so wrap it with a buffered stream.
+    this.stream = new BufferedOutputStream(this.dataBuffer.getOutputStream(0), STREAM_SEGMENT_SIZE * 2);
 
     try {
       this.channel.asyncOpen2(this);
     } catch (e) {
       this._deferred.reject(e);
     }
 
     return this._deferred.promise;
@@ -116,20 +130,17 @@ class FaviconLoad {
 
     this.channel.cancel(Cr.NS_BINDING_ABORTED);
   }
 
   onStartRequest(request, context) {
   }
 
   onDataAvailable(request, context, inputStream, offset, count) {
-    let stream = new BinaryInputStream(inputStream);
-    let buffer = new ArrayBuffer(count);
-    stream.readArrayBuffer(buffer.byteLength, buffer);
-    this.buffers.push(new Uint8Array(buffer));
+    this.stream.writeFrom(inputStream, count);
   }
 
   asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
     if (oldChannel == this.channel) {
       this.channel = newChannel;
     }
 
     callback.onRedirectVerifyCallback(Cr.NS_OK);
@@ -137,16 +148,19 @@ class FaviconLoad {
 
   async onStopRequest(request, context, statusCode) {
     if (request != this.channel) {
       // Indicates that a redirect has occurred. We don't care about the result
       // of the original channel.
       return;
     }
 
+    this.stream.close();
+    this.stream = null;
+
     if (!Components.isSuccessCode(statusCode)) {
       if (statusCode == Cr.NS_BINDING_ABORTED) {
         this._deferred.reject(Components.Exception(`Favicon load from ${this.icon.iconUri.spec} was cancelled.`, statusCode));
       } else {
         this._deferred.reject(Components.Exception(`Favicon at "${this.icon.iconUri.spec}" failed to load.`, statusCode));
       }
       return;
     }
@@ -168,18 +182,22 @@ class FaviconLoad {
       try {
         expiration = Math.min(this.channel.cacheTokenExpirationTime * 1000, expiration);
       } catch (e) {
         // Ignore failures to get the expiration time.
       }
     }
 
     try {
+      let stream = new BinaryInputStream(this.dataBuffer.newInputStream(0));
+      let buffer = new ArrayBuffer(this.dataBuffer.length);
+      stream.readArrayBuffer(buffer.byteLength, buffer);
+
       let type = this.channel.contentType;
-      let blob = new Blob(this.buffers, { type });
+      let blob = new Blob([buffer], { type });
 
       if (type != "image/svg+xml") {
         let octets = await promiseBlobAsOctets(blob);
         let sniffer = Cc["@mozilla.org/image/loader;1"].
                       createInstance(Ci.nsIContentSniffer);
         type = sniffer.getMIMETypeFromContent(this.channel, octets, octets.length);
 
         if (!type) {