Bug 1529497 - Remove `promise` and `defer` usage in `devtools/shared/DevToolsUtils.js`; r=yulia.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Wed, 27 Feb 2019 13:28:16 +0000
changeset 519319 6f356dac426598f80c6ab9d6b4bf983ac8a7f4a1
parent 519318 980be6fb9210b7a29683d58c90e5b30145b0279e
child 519320 d17134399e22a0a320abcd764518847270017bd1
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyulia
bugs1529497
milestone67.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 1529497 - Remove `promise` and `defer` usage in `devtools/shared/DevToolsUtils.js`; r=yulia. The `yieldingEach` is moved into the only file that is using it, in canvas debugger, and is not migrated as the panel will be removed in a few months. Differential Revision: https://phabricator.services.mozilla.com/D20641
devtools/client/canvasdebugger/snapshotslist.js
devtools/shared/DevToolsUtils.js
--- a/devtools/client/canvasdebugger/snapshotslist.js
+++ b/devtools/client/canvasdebugger/snapshotslist.js
@@ -1,15 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* import-globals-from canvasdebugger.js */
 /* globals window, document */
 "use strict";
 
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+
 /**
  * Functions handling the recorded animation frame snapshots UI.
  */
 var SnapshotsListView = extend(WidgetMethods, {
   /**
    * Initialization function, called when the tool is started.
    */
   initialize: function() {
@@ -416,34 +419,34 @@ var SnapshotsListView = extend(WidgetMet
         thumbnails: [],
         screenshot: null,
       };
       const functionCalls = snapshotItem.attachment.calls;
       const thumbnails = snapshotItem.attachment.thumbnails;
       const screenshot = snapshotItem.attachment.screenshot;
 
       // Prepare all the function calls for serialization.
-      await DevToolsUtils.yieldingEach(functionCalls, (call, i) => {
+      await yieldingEach(functionCalls, (call, i) => {
         const { type, name, file, line, timestamp, argsPreview, callerPreview } = call;
         return call.getDetails().then(({ stack }) => {
           data.calls[i] = {
             type: type,
             name: name,
             file: file,
             line: line,
             stack: stack,
             timestamp: timestamp,
             argsPreview: argsPreview,
             callerPreview: callerPreview,
           };
         });
       });
 
       // Prepare all the thumbnails for serialization.
-      await DevToolsUtils.yieldingEach(thumbnails, (thumbnail, i) => {
+      await yieldingEach(thumbnails, (thumbnail, i) => {
         const { index, width, height, flipped, pixels } = thumbnail;
         data.thumbnails.push({ index, width, height, flipped, pixels });
       });
 
       // Prepare the screenshot for serialization.
       const { index, width, height, flipped, pixels } = screenshot;
       data.screenshot = { index, width, height, flipped, pixels };
 
@@ -490,8 +493,57 @@ var SnapshotsListView = extend(WidgetMet
 
 function showNotification(toolbox, name, message) {
   const notificationBox = toolbox.getNotificationBox();
   const notification = notificationBox.getNotificationWithValue(name);
   if (!notification) {
     notificationBox.appendNotification(message, name, "", notificationBox.PRIORITY_WARNING_HIGH);
   }
 }
+
+/**
+ * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
+ * very large arrays by yielding to the browser and continuing execution on the
+ * next tick.
+ *
+ * @param Array array
+ *        The array being iterated over.
+ * @param Function fn
+ *        The function called on each item in the array. If a promise is
+ *        returned by this function, iterating over the array will be paused
+ *        until the respective promise is resolved.
+ * @returns Promise
+ *          A promise that is resolved once the whole array has been iterated
+ *          over, and all promises returned by the fn callback are resolved.
+ */
+function yieldingEach(array, fn) {
+  const deferred = defer();
+
+  let i = 0;
+  const len = array.length;
+  const outstanding = [deferred.promise];
+
+  (function loop() {
+    const start = Date.now();
+
+    while (i < len) {
+      // Don't block the main thread for longer than 16 ms at a time. To
+      // maintain 60fps, you have to render every frame in at least 16ms; we
+      // aren't including time spent in non-JS here, but this is Good
+      // Enough(tm).
+      if (Date.now() - start > 16) {
+        DevToolsUtils.executeSoon(loop);
+        return;
+      }
+
+      try {
+        outstanding.push(fn(array[i], i++));
+      } catch (e) {
+        deferred.reject(e);
+        return;
+      }
+    }
+
+    deferred.resolve();
+  }());
+
+  return promise.all(outstanding);
+}
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -5,18 +5,16 @@
 /* globals setImmediate, rpc */
 
 "use strict";
 
 /* General utilities used throughout devtools. */
 
 var { Ci, Cu, components } = require("chrome");
 var Services = require("Services");
-var promise = require("promise");
-var defer = require("devtools/shared/defer");
 var flags = require("./flags");
 var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
 
 loader.lazyRequireGetter(this, "FileUtils",
                          "resource://gre/modules/FileUtils.jsm", true);
 
 // Using this name lets the eslint plugin know about lazy defines in
 // this file.
@@ -69,82 +67,31 @@ exports.executeSoon = function(fn) {
 
 /**
  * Waits for the next tick in the event loop.
  *
  * @return Promise
  *         A promise that is resolved after the next tick in the event loop.
  */
 exports.waitForTick = function() {
-  const deferred = defer();
-  exports.executeSoon(deferred.resolve);
-  return deferred.promise;
+  return new Promise(resolve => {
+    exports.executeSoon(resolve);
+  });
 };
 
 /**
  * Waits for the specified amount of time to pass.
  *
  * @param number delay
  *        The amount of time to wait, in milliseconds.
  * @return Promise
  *         A promise that is resolved after the specified amount of time passes.
  */
 exports.waitForTime = function(delay) {
-  const deferred = defer();
-  setTimeout(deferred.resolve, delay);
-  return deferred.promise;
-};
-
-/**
- * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
- * very large arrays by yielding to the browser and continuing execution on the
- * next tick.
- *
- * @param Array array
- *        The array being iterated over.
- * @param Function fn
- *        The function called on each item in the array. If a promise is
- *        returned by this function, iterating over the array will be paused
- *        until the respective promise is resolved.
- * @returns Promise
- *          A promise that is resolved once the whole array has been iterated
- *          over, and all promises returned by the fn callback are resolved.
- */
-exports.yieldingEach = function(array, fn) {
-  const deferred = defer();
-
-  let i = 0;
-  const len = array.length;
-  const outstanding = [deferred.promise];
-
-  (function loop() {
-    const start = Date.now();
-
-    while (i < len) {
-      // Don't block the main thread for longer than 16 ms at a time. To
-      // maintain 60fps, you have to render every frame in at least 16ms; we
-      // aren't including time spent in non-JS here, but this is Good
-      // Enough(tm).
-      if (Date.now() - start > 16) {
-        exports.executeSoon(loop);
-        return;
-      }
-
-      try {
-        outstanding.push(fn(array[i], i++));
-      } catch (e) {
-        deferred.reject(e);
-        return;
-      }
-    }
-
-    deferred.resolve();
-  }());
-
-  return promise.all(outstanding);
+  return new Promise(resolve => setTimeout(resolve, delay));
 };
 
 /**
  * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
  * allows the lazy getter to be defined on a prototype and work correctly with
  * instances.
  *
  * @param Object object
@@ -557,140 +504,140 @@ DevToolsUtils.defineLazyGetter(this, "Ne
  * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
  */
 function mainThreadFetch(urlIn, aOptions = { loadFromCache: true,
                                              policy: Ci.nsIContentPolicy.TYPE_OTHER,
                                              window: null,
                                              charset: null,
                                              principal: null,
                                              cacheKey: 0 }) {
-  // Create a channel.
-  const url = urlIn.split(" -> ").pop();
-  let channel;
-  try {
-    channel = newChannelForURL(url, aOptions);
-  } catch (ex) {
-    return promise.reject(ex);
-  }
-
-  // Set the channel options.
-  channel.loadFlags = aOptions.loadFromCache
-    ? channel.LOAD_FROM_CACHE
-    : channel.LOAD_BYPASS_CACHE;
-
-  // When loading from cache, the cacheKey allows us to target a specific
-  // SHEntry and offer ways to restore POST requests from cache.
-  if (aOptions.loadFromCache &&
-      aOptions.cacheKey != 0 && channel instanceof Ci.nsICacheInfoChannel) {
-    channel.cacheKey = aOptions.cacheKey;
-  }
-
-  if (aOptions.window) {
-    // Respect private browsing.
-    channel.loadGroup = aOptions.window.docShell
-                          .QueryInterface(Ci.nsIDocumentLoader)
-                          .loadGroup;
-  }
-
-  const deferred = defer();
-  const onResponse = (stream, status, request) => {
-    if (!components.isSuccessCode(status)) {
-      deferred.reject(new Error(`Failed to fetch ${url}. Code ${status}.`));
+  return new Promise((resolve, reject) =>{
+    // Create a channel.
+    const url = urlIn.split(" -> ").pop();
+    let channel;
+    try {
+      channel = newChannelForURL(url, aOptions);
+    } catch (ex) {
+      reject(ex);
       return;
     }
 
-    try {
-      // We cannot use NetUtil to do the charset conversion as if charset
-      // information is not available and our default guess is wrong the method
-      // might fail and we lose the stream data. This means we can't fall back
-      // to using the locale default encoding (bug 1181345).
+    // Set the channel options.
+    channel.loadFlags = aOptions.loadFromCache
+      ? channel.LOAD_FROM_CACHE
+      : channel.LOAD_BYPASS_CACHE;
 
-      // Read and decode the data according to the locale default encoding.
-      const available = stream.available();
-      let source = NetUtil.readInputStreamToString(stream, available);
-      stream.close();
+    // When loading from cache, the cacheKey allows us to target a specific
+    // SHEntry and offer ways to restore POST requests from cache.
+    if (aOptions.loadFromCache &&
+        aOptions.cacheKey != 0 && channel instanceof Ci.nsICacheInfoChannel) {
+      channel.cacheKey = aOptions.cacheKey;
+    }
 
-      // We do our own BOM sniffing here because there's no convenient
-      // implementation of the "decode" algorithm
-      // (https://encoding.spec.whatwg.org/#decode) exposed to JS.
-      let bomCharset = null;
-      if (available >= 3 && source.codePointAt(0) == 0xef &&
-          source.codePointAt(1) == 0xbb && source.codePointAt(2) == 0xbf) {
-        bomCharset = "UTF-8";
-        source = source.slice(3);
-      } else if (available >= 2 && source.codePointAt(0) == 0xfe &&
-                 source.codePointAt(1) == 0xff) {
-        bomCharset = "UTF-16BE";
-        source = source.slice(2);
-      } else if (available >= 2 && source.codePointAt(0) == 0xff &&
-                 source.codePointAt(1) == 0xfe) {
-        bomCharset = "UTF-16LE";
-        source = source.slice(2);
+    if (aOptions.window) {
+      // Respect private browsing.
+      channel.loadGroup = aOptions.window.docShell
+                            .QueryInterface(Ci.nsIDocumentLoader)
+                            .loadGroup;
+    }
+
+    const onResponse = (stream, status, request) => {
+      if (!components.isSuccessCode(status)) {
+        reject(new Error(`Failed to fetch ${url}. Code ${status}.`));
+        return;
       }
 
-      // If the channel or the caller has correct charset information, the
-      // content will be decoded correctly. If we have to fall back to UTF-8 and
-      // the guess is wrong, the conversion fails and convertToUnicode returns
-      // the input unmodified. Essentially we try to decode the data as UTF-8
-      // and if that fails, we use the locale specific default encoding. This is
-      // the best we can do if the source does not provide charset info.
-      let charset = bomCharset;
-      if (!charset) {
-        try {
-          charset = channel.contentCharset;
-        } catch (e) {
-          // Accessing `contentCharset` on content served by a service worker in
-          // non-e10s may throw.
+      try {
+        // We cannot use NetUtil to do the charset conversion as if charset
+        // information is not available and our default guess is wrong the method
+        // might fail and we lose the stream data. This means we can't fall back
+        // to using the locale default encoding (bug 1181345).
+
+        // Read and decode the data according to the locale default encoding.
+        const available = stream.available();
+        let source = NetUtil.readInputStreamToString(stream, available);
+        stream.close();
+
+        // We do our own BOM sniffing here because there's no convenient
+        // implementation of the "decode" algorithm
+        // (https://encoding.spec.whatwg.org/#decode) exposed to JS.
+        let bomCharset = null;
+        if (available >= 3 && source.codePointAt(0) == 0xef &&
+            source.codePointAt(1) == 0xbb && source.codePointAt(2) == 0xbf) {
+          bomCharset = "UTF-8";
+          source = source.slice(3);
+        } else if (available >= 2 && source.codePointAt(0) == 0xfe &&
+                  source.codePointAt(1) == 0xff) {
+          bomCharset = "UTF-16BE";
+          source = source.slice(2);
+        } else if (available >= 2 && source.codePointAt(0) == 0xff &&
+                  source.codePointAt(1) == 0xfe) {
+          bomCharset = "UTF-16LE";
+          source = source.slice(2);
+        }
+
+        // If the channel or the caller has correct charset information, the
+        // content will be decoded correctly. If we have to fall back to UTF-8 and
+        // the guess is wrong, the conversion fails and convertToUnicode returns
+        // the input unmodified. Essentially we try to decode the data as UTF-8
+        // and if that fails, we use the locale specific default encoding. This is
+        // the best we can do if the source does not provide charset info.
+        let charset = bomCharset;
+        if (!charset) {
+          try {
+            charset = channel.contentCharset;
+          } catch (e) {
+            // Accessing `contentCharset` on content served by a service worker in
+            // non-e10s may throw.
+          }
+        }
+        if (!charset) {
+          charset = aOptions.charset || "UTF-8";
+        }
+        const unicodeSource = NetworkHelper.convertToUnicode(source, charset);
+
+        resolve({
+          content: unicodeSource,
+          contentType: request.contentType,
+        });
+      } catch (ex) {
+        const uri = request.originalURI;
+        if (ex.name === "NS_BASE_STREAM_CLOSED" && uri instanceof Ci.nsIFileURL) {
+          // Empty files cause NS_BASE_STREAM_CLOSED exception. Use OS.File to
+          // differentiate between empty files and other errors (bug 1170864).
+          // This can be removed when bug 982654 is fixed.
+
+          uri.QueryInterface(Ci.nsIFileURL);
+          const result = OS.File.read(uri.file.path).then(bytes => {
+            // Convert the bytearray to a String.
+            const decoder = new TextDecoder();
+            const content = decoder.decode(bytes);
+
+            // We can't detect the contentType without opening a channel
+            // and that failed already. This is the best we can do here.
+            return {
+              content,
+              contentType: "text/plain",
+            };
+          });
+
+          resolve(result);
+        } else {
+          reject(ex);
         }
       }
-      if (!charset) {
-        charset = aOptions.charset || "UTF-8";
-      }
-      const unicodeSource = NetworkHelper.convertToUnicode(source, charset);
-
-      deferred.resolve({
-        content: unicodeSource,
-        contentType: request.contentType,
-      });
-    } catch (ex) {
-      const uri = request.originalURI;
-      if (ex.name === "NS_BASE_STREAM_CLOSED" && uri instanceof Ci.nsIFileURL) {
-        // Empty files cause NS_BASE_STREAM_CLOSED exception. Use OS.File to
-        // differentiate between empty files and other errors (bug 1170864).
-        // This can be removed when bug 982654 is fixed.
-
-        uri.QueryInterface(Ci.nsIFileURL);
-        const result = OS.File.read(uri.file.path).then(bytes => {
-          // Convert the bytearray to a String.
-          const decoder = new TextDecoder();
-          const content = decoder.decode(bytes);
+    };
 
-          // We can't detect the contentType without opening a channel
-          // and that failed already. This is the best we can do here.
-          return {
-            content,
-            contentType: "text/plain",
-          };
-        });
-
-        deferred.resolve(result);
-      } else {
-        deferred.reject(ex);
-      }
+    // Open the channel
+    try {
+      NetUtil.asyncFetch(channel, onResponse);
+    } catch (ex) {
+      reject(ex);
     }
-  };
-
-  // Open the channel
-  try {
-    NetUtil.asyncFetch(channel, onResponse);
-  } catch (ex) {
-    return promise.reject(ex);
-  }
-
-  return deferred.promise;
+  });
 }
 
 /**
  * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
  *
  * @param {String} url - The URL to open a channel for.
  * @param {Object} options - The options object passed to @method fetch.
  * @return {nsIChannel} - The newly created channel. Throws on failure.