Bug 1125618 - Revise NetUtil.jsm API for creating channels and fetching data while providing load info. r=sicking
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Wed, 11 Feb 2015 17:37:05 +0000
changeset 228639 e6b4bb4cc3937023534874a41d41eb1bdb94d3db
parent 228638 e1035da2a8ddaa5bf6691294b17f69ea23f90a13
child 228640 33e8777ce33f5942da70e824125c4d4b6960542c
push id28266
push userryanvm@gmail.com
push dateWed, 11 Feb 2015 21:21:01 +0000
treeherdermozilla-central@08420ee384ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs1125618
milestone38.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 1125618 - Revise NetUtil.jsm API for creating channels and fetching data while providing load info. r=sicking
netwerk/base/NetUtil.jsm
netwerk/test/unit/test_NetUtil.js
--- a/netwerk/base/NetUtil.jsm
+++ b/netwerk/base/NetUtil.jsm
@@ -17,16 +17,19 @@ this.EXPORTED_SYMBOLS = [
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 const PR_UINT32_MAX = 0xffffffff;
 
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 ////////////////////////////////////////////////////////////////////////////////
 //// NetUtil Object
 
 this.NetUtil = {
     /**
      * Function to perform simple async copying from aSource (an input stream)
      * to aSink (an output stream).  The copy will happen on some background
      * thread.  Both streams will be closed when the copy completes.
@@ -76,32 +79,34 @@ this.NetUtil = {
         }
 
         // start the copying
         copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
         return copier;
     },
 
     /**
-     * Asynchronously opens a source and fetches the response.  A source can be
-     * an nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream.  The
-     * provided callback will get an input stream containing the response, the
-     * result code, and a reference to the request.
+     * Asynchronously opens a source and fetches the response.  While the fetch
+     * is asynchronous, I/O may happen on the main thread.  When reading from
+     * a local file, prefer using "OS.File" methods instead.
      *
      * @param aSource
-     *        The nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream
-     *        to open.
+     *        This argument can be one of the following:
+     *         - An options object that will be passed to NetUtil.newChannel.
+     *         - An existing nsIChannel.
+     *         - An existing nsIInputStream.
+     *        Using an nsIURI, nsIFile, or string spec directly is deprecated.
      * @param aCallback
      *        The callback function that will be notified upon completion.  It
-     *        will get two arguments:
+     *        will get these arguments:
      *        1) An nsIInputStream containing the data from aSource, if any.
      *        2) The status code from opening the source.
      *        3) Reference to the nsIRequest.
      */
-    asyncFetch: function NetUtil_asyncOpen(aSource, aCallback)
+    asyncFetch: function NetUtil_asyncFetch(aSource, aCallback)
     {
         if (!aSource || !aCallback) {
             let exception = new Components.Exception(
                 "Must have a source and a callback",
                 Cr.NS_ERROR_INVALID_ARG,
                 Components.stack.caller
             );
             throw exception;
@@ -149,41 +154,17 @@ this.NetUtil = {
                 aSource,
                 e
             );
             throw exception;
         }
     },
 
     /**
-     * Asynchronously opens a source and fetches the response.  A source can be
-     * an nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream.  The
-     * provided callback will get an input stream containing the response, the
-     * result code, and a reference to the request.
-     *
-     * Please note, if aSource is an instance of an nsIChannel, then
-     * aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, aSecurityFlags,
-     * aContentPolicyType must be "undefined".
-     *
-     * @param aSource
-     *        The nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream
-     *        to open.
-     * @param aCallback
-     *        The callback function that will be notified upon completion.  It
-     *        will get two arguments:
-     *        1) An nsIInputStream containing the data from aSource, if any.
-     *        2) The status code from opening the source.
-     *        3) Reference to the nsIRequest.
-     * @param aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal
-     *        aSecurityFlags, aContentPolicyType
-     *        See param description in NetUtil_newChannel2.
-     *
-     * Note: As an interim we have asyncFetch as well as asyncFetch2.
-     *       Once Bug 1087720 (which converts all js callers to use
-     *       asyncFetch2) lands, we can remove asyncFetch completely.
+     * @deprecated Use asyncFecth({ ...options... }, callback) instead.
      */
     asyncFetch2: function NetUtil_asyncFetch2(aSource,
                                               aCallback,
                                               aLoadingNode,
                                               aLoadingPrincipal,
                                               aTriggeringPrincipal,
                                               aSecurityFlags,
                                               aContentPolicyType)
@@ -298,116 +279,204 @@ this.NetUtil = {
         if (aTarget instanceof Ci.nsIFile) {
             return this.ioService.newFileURI(aTarget);
         }
 
         return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI);
     },
 
     /**
-     * Constructs a new channel for the given spec, character set, and base URI,
-     * or nsIURI, or nsIFile.
+     * Constructs a new channel for the given source.
+     *
+     * Keep in mind that URIs coming from a webpage should *never* use the
+     * systemPrincipal as the loadingPrincipal.
      *
      * @param aWhatToLoad
-     *        The string spec for the desired URI, an nsIURI, or an nsIFile.
-     * @param aOriginCharset [optional]
+     *        This argument used to be a string spec for the desired URI, an
+     *        nsIURI, or an nsIFile.  Now it should be an options object with
+     *        the following properties:
+     *        {
+     *          uri:
+     *            The full URI spec string or nsIURI to create the channel for.
+     *            Note that this cannot be an nsIFile and you cannot specify a
+     *            non-default charset or base URI.  Call NetUtil.newURI first if
+     *            you need to construct an URI using those options.
+     *          loadingNode:
+     *            The loadingDocument of the channel.
+     *            The element or document where the result of this request will
+     *            be used.  This is the document/element that will get access to
+     *            the result of this request.  For example for an image load,
+     *            it's the document in which the image will be loaded.  And for
+     *            a CSS stylesheet it's the document whose rendering will be
+     *            affected by the stylesheet.
+     *            If possible, pass in the element which is performing the load.
+     *            But if the load is coming from a JS API (such as
+     *            XMLHttpRequest) or if the load might be coalesced across
+     *            multiple elements (such as for <img>) then pass in the
+     *            Document node instead.
+     *            For loads that are not related to any document, such as loads
+     *            coming from addons or internal browser features, omit this
+     *            property and specify a loadingPrincipal or
+     *            loadUsingSystemPrincipal instead.
+     *          loadingPrincipal:
+     *            The loadingPrincipal of the channel.
+     *            The principal of the document where the result of this request
+     *            will be used.
+     *            This is generally the principal of the loadingNode.  However
+     *            for loads where loadingNode is omitted this argument still
+     *            needs to be passed.  For example for loads from a WebWorker,
+     *            pass the principal of that worker.  For loads from an addon or
+     *            from internal browser features, pass the system principal.
+     *            This principal should almost always be the system principal if
+     *            loadingNode is omitted, in which case you can use the
+     *            useSystemPrincipal property.  The only exception to this is
+     *            for loads from WebWorkers since they don't have any nodes to
+     *            be passed as loadingNode.
+     *            Please note, loadingPrincipal is *not* the principal of the
+     *            resource being loaded, but rather the principal of the context
+     *            where the resource will be used.
+     *          loadUsingSystemPrincipal:
+     *            Set this to true to use the system principal as
+     *            loadingPrincipal.  This must be omitted if loadingPrincipal or
+     *            loadingNode are present.
+     *            This should be used with care as it skips security checks.
+     *          triggeringPrincipal:
+     *            The triggeringPrincipal of the load.
+     *            The triggeringPrincipal is the principal of the resource that
+     *            caused this particular URL to be loaded.
+     *            Most likely the triggeringPrincipal and the loadingPrincipal
+     *            are identical, in which case the triggeringPrincipal can be
+     *            left out.  In some cases the loadingPrincipal and the
+     *            triggeringPrincipal are different however, e.g. a stylesheet
+     *            may import a subresource.  In that case the principal of the
+     *            stylesheet which contains the import command is the
+     *            triggeringPrincipal, and the principal of the document whose
+     *            rendering is affected is the loadingPrincipal.
+     *          securityFlags:
+     *            The securityFlags of the channel.
+     *            Any of the securityflags defined in nsILoadInfo.idl.
+     *          contentPolicyType:
+     *            The contentPolicyType of the channel.
+     *            Any of the content types defined in nsIContentPolicy.idl.
+     *        }
+     * @param aOriginCharset [deprecated]
      *        The character set for the URI.  Only used if aWhatToLoad is a
-     *        string.
-     * @param aBaseURI [optional]
-     *        The base URI for the spec.  Only used if aWhatToLoad is a string.
+     *        string, which is a deprecated API.  Must be undefined otherwise.
+     *        Use NetUtil.newURI if you need to use this option.
+     * @param aBaseURI [deprecated]
+     *        The base URI for the spec.  Only used if aWhatToLoad is a string,
+     *        which is a deprecated API.  Must be undefined otherwise.  Use
+     *        NetUtil.newURI if you need to use this option.
      *
      * @return an nsIChannel object.
      */
     newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset,
                                             aBaseURI)
     {
-        if (!aWhatToLoad) {
-            let exception = new Components.Exception(
-                "Must have a non-null string spec, nsIURI, or nsIFile object",
+        // Check for the deprecated API first.
+        if (typeof aWhatToLoad == "string" ||
+            (aWhatToLoad instanceof Ci.nsIFile) ||
+            (aWhatToLoad instanceof Ci.nsIURI)) {
+
+            let uri = (aWhatToLoad instanceof Ci.nsIURI)
+                      ? aWhatToLoad
+                      : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI);
+
+            return this.ioService.newChannelFromURI(uri);
+        }
+
+        // We are using the updated API, that requires only the options object.
+        if (typeof aWhatToLoad != "object" ||
+            aOriginCharset !== undefined ||
+            aBaseURI !== undefined) {
+
+            throw new Components.Exception(
+                "newChannel requires a single object argument",
+                Cr.NS_ERROR_INVALID_ARG,
+                Components.stack.caller
+            );
+        }
+
+        let { uri,
+              loadingNode,
+              loadingPrincipal,
+              loadUsingSystemPrincipal,
+              triggeringPrincipal,
+              securityFlags,
+              contentPolicyType } = aWhatToLoad;
+
+        if (!uri) {
+            throw new Components.Exception(
+                "newChannel requires the 'uri' property on the options object.",
                 Cr.NS_ERROR_INVALID_ARG,
                 Components.stack.caller
             );
-            throw exception;
+        }
+
+        if (typeof uri == "string") {
+            uri = this.newURI(uri);
+        }
+
+        if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
+            throw new Components.Exception(
+                "newChannel requires at least one of the 'loadingNode'," +
+                " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
+                " properties on the options object.",
+                Cr.NS_ERROR_INVALID_ARG,
+                Components.stack.caller
+            );
         }
 
-        let uri = aWhatToLoad;
-        if (!(aWhatToLoad instanceof Ci.nsIURI)) {
-            // We either have a string or an nsIFile that we'll need a URI for.
-            uri = this.newURI(aWhatToLoad, aOriginCharset, aBaseURI);
+        if (loadUsingSystemPrincipal === true) {
+            if (loadingNode || loadingPrincipal) {
+                throw new Components.Exception(
+                    "newChannel does not accept 'loadUsingSystemPrincipal'" +
+                    " if the 'loadingNode' or 'loadingPrincipal' properties" +
+                    " are present on the options object.",
+                    Cr.NS_ERROR_INVALID_ARG,
+                    Components.stack.caller
+                );
+            }
+            loadingPrincipal = Services.scriptSecurityManager
+                                       .getSystemPrincipal();
+        } else if (loadUsingSystemPrincipal !== undefined) {
+            throw new Components.Exception(
+                "newChannel requires the 'loadUsingSystemPrincipal'" +
+                " property on the options object to be 'true' or 'undefined'.",
+                Cr.NS_ERROR_INVALID_ARG,
+                Components.stack.caller
+            );
         }
 
-        return this.ioService.newChannelFromURI(uri);
+        if (securityFlags === undefined) {
+            securityFlags = Ci.nsILoadInfo.SEC_NORMAL;
+        }
+
+        if (contentPolicyType === undefined) {
+            if (!loadUsingSystemPrincipal) {
+                throw new Components.Exception(
+                    "newChannel requires the 'contentPolicyType' property on" +
+                    " the options object unless loading from system principal.",
+                    Cr.NS_ERROR_INVALID_ARG,
+                    Components.stack.caller
+                );
+            }
+            contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
+        }
+
+        return this.ioService.newChannelFromURI2(uri,
+                                                 loadingNode || null,
+                                                 loadingPrincipal || null,
+                                                 triggeringPrincipal || null,
+                                                 securityFlags,
+                                                 contentPolicyType);
     },
 
     /**
-     * Constructs a new channel for the given spec, character set, and base URI,
-     * or nsIURI, or nsIFile.
-     *
-     * @param aWhatToLoad
-     *        The string spec for the desired URI, an nsIURI, or an nsIFile.
-     * @param aOriginCharset
-     *        The character set for the URI.  Only used if aWhatToLoad is a
-     *        string.
-     * @param aBaseURI
-     *        The base URI for the spec.  Only used if aWhatToLoad is a string.
-     * @param aLoadingNode
-     *        The loadingDocument of the channel.
-     *        The element or document where the result of this request will be
-     *        used. This is the document/element that will get access to the
-     *        result of this request. For example for an image load, it's the
-     *        document in which the image will be loaded. And for a CSS
-     *        stylesheet it's the document whose rendering will be affected by
-     *        the stylesheet.
-     *        If possible, pass in the element which is performing the load. But
-     *        if the load is coming from a JS API (such as XMLHttpRequest) or if
-     *        the load might be coalesced across multiple elements (such as
-     *        for <img>) then pass in the Document node instead.
-     *        For loads that are not related to any document, such as loads coming
-     *        from addons or internal browser features, use null here.
-     * @param aLoadingPrincipal
-     *        The loadingPrincipal of the channel.
-     *        The principal of the document where the result of this request will
-     *        be used.
-     *        This is generally the principal of the aLoadingNode. However for
-     *        loads where aLoadingNode is null this argument still needs to be
-     *        passed. For example for loads from a WebWorker, pass the principal
-     *        of that worker. For loads from an addon or from internal browser
-     *        features, pass the system principal.
-     *        This principal should almost always be the system principal if
-     *        aLoadingNode is null. The only exception to this is for loads
-     *        from WebWorkers since they don't have any nodes to be passed as
-     *        aLoadingNode.
-     *        Please note, aLoadingPrincipal is *not* the principal of the
-     *        resource being loaded. But rather the principal of the context
-     *        where the resource will be used.
-     * @param aTriggeringPrincipal
-     *        The triggeringPrincipal of the load.
-     *        The triggeringPrincipal is the principal of the resource that caused
-     *        this particular URL to be loaded.
-     *        Most likely the triggeringPrincipal and the loadingPrincipal are
-     *        identical, in which case the triggeringPrincipal can be left out.
-     *        In some cases the loadingPrincipal and the triggeringPrincipal are
-     *        different however, e.g. a stylesheet may import a subresource. In
-     *        that case the principal of the stylesheet which contains the
-     *        import command is the triggeringPrincipal, and the principal of
-     *        the document whose rendering is affected is the loadingPrincipal.
-     * @param aSecurityFlags
-     *        The securityFlags of the channel.
-     *        Any of the securityflags defined in nsILoadInfo.idl
-     * @param aContentPolicyType
-     *        The contentPolicyType of the channel.
-     *        Any of the content types defined in nsIContentPolicy.idl
-     * @return an nsIChannel object.
-     *
-     * Keep in mind that URIs coming from a webpage should *never* use the
-     * systemPrincipal as the loadingPrincipal.
-     *
-     * Note: As an interim we have newChannel as well as newChannel2.
-     *       Once Bug 1087720 (which converts all js callers to use
-     *       newChannel2) lands, we can remove newChannel completely.
+     * @deprecated Use newChannel({ ...options... }) instead.
      */
     newChannel2: function NetUtil_newChannel2(aWhatToLoad,
                                               aOriginCharset,
                                               aBaseURI,
                                               aLoadingNode,
                                               aLoadingPrincipal,
                                               aTriggeringPrincipal,
                                               aSecurityFlags,
@@ -480,17 +549,17 @@ this.NetUtil = {
             );
             throw exception;
         }
 
         if (aOptions && "charset" in aOptions) {
           let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].
                     createInstance(Ci.nsIConverterInputStream);
           try {
-            // When replacement is set, the character that is unknown sequence 
+            // When replacement is set, the character that is unknown sequence
             // replaces with aOptions.replacement character.
             if (!("replacement" in aOptions)) {
               // aOptions.replacement isn't set.
               // If input stream has unknown sequences for aOptions.charset,
               // throw NS_ERROR_ILLEGAL_INPUT.
               aOptions.replacement = 0;
             }
 
@@ -528,17 +597,8 @@ this.NetUtil = {
      */
     get ioService()
     {
         delete this.ioService;
         return this.ioService = Cc["@mozilla.org/network/io-service;1"].
                                 getService(Ci.nsIIOService);
     },
 };
-
-////////////////////////////////////////////////////////////////////////////////
-//// Initialization
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-// Define our lazy getters.
-XPCOMUtils.defineLazyServiceGetter(this, "ioUtil", "@mozilla.org/io-util;1",
-                                   "nsIIOUtil");
--- a/netwerk/test/unit/test_NetUtil.js
+++ b/netwerk/test/unit/test_NetUtil.js
@@ -256,30 +256,30 @@ function test_ioService()
 {
   do_check_true(NetUtil.ioService instanceof Ci.nsIIOService);
   run_next_test();
 }
 
 function test_asyncFetch_no_channel()
 {
   try {
-    NetUtil.asyncFetch2(null, function() { });
+    NetUtil.asyncFetch(null, function() { });
     do_throw("should throw!");
   }
   catch (e) {
     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   }
 
   run_next_test();
 }
 
 function test_asyncFetch_no_callback()
 {
   try {
-    NetUtil.asyncFetch2({ });
+    NetUtil.asyncFetch({ });
     do_throw("should throw!");
   }
   catch (e) {
     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   }
 
   run_next_test();
 }
@@ -293,29 +293,23 @@ function test_asyncFetch_with_nsIChannel
   server.registerPathHandler("/test", function(aRequest, aResponse) {
     aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
     aResponse.setHeader("Content-Type", "text/plain", false);
     aResponse.write(TEST_DATA);
   });
   server.start(-1);
 
   // Create our channel.
-  let channel = NetUtil.ioService.
-                newChannel2("http://localhost:" +
-                            server.identity.primaryPort + "/test",
-                            null,
-                            null,
-                            null,      // aLoadingNode
-                            Services.scriptSecurityManager.getSystemPrincipal(),
-                            null,      // aTriggeringPrincipal
-                            Ci.nsILoadInfo.SEC_NORMAL,
-                            Ci.nsIContentPolicy.TYPE_OTHER);
+  let channel = NetUtil.newChannel({
+    uri: "http://localhost:" + server.identity.primaryPort + "/test",
+    loadUsingSystemPrincipal: true,
+  });
 
   // Open our channel asynchronously.
-  NetUtil.asyncFetch2(channel, function(aInputStream, aResult) {
+  NetUtil.asyncFetch(channel, function(aInputStream, aResult) {
     // Check that we had success.
     do_check_true(Components.isSuccessCode(aResult));
 
     // Check that we got the right data.
     do_check_eq(aInputStream.available(), TEST_DATA.length);
     let is = Cc["@mozilla.org/scriptableinputstream;1"].
              createInstance(Ci.nsIScriptableInputStream);
     is.init(aInputStream);
@@ -339,17 +333,20 @@ function test_asyncFetch_with_nsIURI()
   });
   server.start(-1);
 
   // Create our URI.
   let uri = NetUtil.newURI("http://localhost:" +
                            server.identity.primaryPort + "/test");
 
   // Open our URI asynchronously.
-  NetUtil.asyncFetch2(uri, function(aInputStream, aResult) {
+  NetUtil.asyncFetch({
+    uri,
+    loadUsingSystemPrincipal: true,
+  }, function(aInputStream, aResult) {
     // Check that we had success.
     do_check_true(Components.isSuccessCode(aResult));
 
     // Check that we got the right data.
     do_check_eq(aInputStream.available(), TEST_DATA.length);
     let is = Cc["@mozilla.org/scriptableinputstream;1"].
              createInstance(Ci.nsIScriptableInputStream);
     is.init(aInputStream);
@@ -374,19 +371,20 @@ function test_asyncFetch_with_string()
   server.registerPathHandler("/test", function(aRequest, aResponse) {
     aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
     aResponse.setHeader("Content-Type", "text/plain", false);
     aResponse.write(TEST_DATA);
   });
   server.start(-1);
 
   // Open our location asynchronously.
-  NetUtil.asyncFetch2("http://localhost:" +
-                     server.identity.primaryPort + "/test",
-                     function(aInputStream, aResult) {
+  NetUtil.asyncFetch({
+    uri: "http://localhost:" + server.identity.primaryPort + "/test",
+    loadUsingSystemPrincipal: true,
+  }, function(aInputStream, aResult) {
     // Check that we had success.
     do_check_true(Components.isSuccessCode(aResult));
 
     // Check that we got the right data.
     do_check_eq(aInputStream.available(), TEST_DATA.length);
     let is = Cc["@mozilla.org/scriptableinputstream;1"].
              createInstance(Ci.nsIScriptableInputStream);
     is.init(aInputStream);
@@ -418,17 +416,21 @@ function test_asyncFetch_with_nsIFile()
                 createInstance(Ci.nsIFileOutputStream);
   ostream.init(file, -1, -1, 0);
   ostream.write(TEST_DATA, TEST_DATA.length);
 
   // Sanity check to make sure the data was written.
   do_check_eq(TEST_DATA, getFileContents(file));
 
   // Open our file asynchronously.
-  NetUtil.asyncFetch2(file, function(aInputStream, aResult) {
+  // Note that this causes main-tread I/O and should be avoided in production.
+  NetUtil.asyncFetch({
+    uri: NetUtil.newURI(file),
+    loadUsingSystemPrincipal: true,
+  }, function(aInputStream, aResult) {
     // Check that we had success.
     do_check_true(Components.isSuccessCode(aResult));
 
     // Check that we got the right data.
     do_check_eq(aInputStream.available(), TEST_DATA.length);
     let is = Cc["@mozilla.org/scriptableinputstream;1"].
              createInstance(Ci.nsIScriptableInputStream);
     is.init(aInputStream);
@@ -447,17 +449,17 @@ function test_asyncFetch_with_nsIFile()
 function test_asyncFetch_with_nsIInputString()
 {
   const TEST_DATA = "this is a test string";
   let istream = Cc["@mozilla.org/io/string-input-stream;1"].
                 createInstance(Ci.nsIStringInputStream);
   istream.setData(TEST_DATA, TEST_DATA.length);
 
   // Read the input stream asynchronously.
-  NetUtil.asyncFetch2(istream, function(aInputStream, aResult) {
+  NetUtil.asyncFetch(istream, function(aInputStream, aResult) {
     // Check that we had success.
     do_check_true(Components.isSuccessCode(aResult));
 
     // Check that we got the right data.
     do_check_eq(aInputStream.available(), TEST_DATA.length);
     do_check_eq(NetUtil.readInputStreamToString(aInputStream, TEST_DATA.length),
                 TEST_DATA);
 
@@ -468,28 +470,23 @@ function test_asyncFetch_with_nsIInputSt
   null,      // aTriggeringPrincipal
   Ci.nsILoadInfo.SEC_NORMAL,
   Ci.nsIContentPolicy.TYPE_OTHER);
 }
 
 function test_asyncFetch_does_not_block()
 {
   // Create our channel that has no data.
-  let channel = NetUtil.ioService.
-                newChannel2("data:text/plain,",
-                            null,
-                            null,
-                            null,      // aLoadingNode
-                            Services.scriptSecurityManager.getSystemPrincipal(),
-                            null,      // aTriggeringPrincipal
-                            Ci.nsILoadInfo.SEC_NORMAL,
-                            Ci.nsIContentPolicy.TYPE_OTHER);
+  let channel = NetUtil.newChannel({
+    uri: "data:text/plain,",
+    loadUsingSystemPrincipal: true,
+  });
 
   // Open our channel asynchronously.
-  NetUtil.asyncFetch2(channel, function(aInputStream, aResult) {
+  NetUtil.asyncFetch(channel, function(aInputStream, aResult) {
     // Check that we had success.
     do_check_true(Components.isSuccessCode(aResult));
 
     // Check that reading a byte throws that the stream was closed (as opposed
     // saying it would block).
     let is = Cc["@mozilla.org/scriptableinputstream;1"].
              createInstance(Ci.nsIScriptableInputStream);
     is.init(aInputStream);
@@ -503,17 +500,17 @@ function test_asyncFetch_does_not_block(
 
     run_next_test();
   });
 }
 
 function test_newChannel_no_specifier()
 {
   try {
-    NetUtil.newChannel2();
+    NetUtil.newChannel();
     do_throw("should throw!");
   }
   catch (e) {
     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   }
 
   run_next_test();
 }
@@ -528,24 +525,17 @@ function test_newChannel_with_string()
   let iosChannel = ios.newChannel2(TEST_SPEC,
                                    null,
                                    null,
                                    null,      // aLoadingNode
                                    Services.scriptSecurityManager.getSystemPrincipal(),
                                    null,      // aTriggeringPrincipal
                                    Ci.nsILoadInfo.SEC_NORMAL,
                                    Ci.nsIContentPolicy.TYPE_OTHER);
-  let NetUtilChannel = NetUtil.newChannel2(TEST_SPEC,
-                                           null,
-                                           null,
-                                           null,      // aLoadingNode
-                                           Services.scriptSecurityManager.getSystemPrincipal(),
-                                           null,      // aTriggeringPrincipal
-                                           Ci.nsILoadInfo.SEC_NORMAL,
-                                           Ci.nsIContentPolicy.TYPE_OTHER);
+  let NetUtilChannel = NetUtil.newChannel(TEST_SPEC);
   do_check_true(iosChannel.URI.equals(NetUtilChannel.URI));
 
   run_next_test();
 }
 
 function test_newChannel_with_nsIURI()
 {
   const TEST_SPEC = "http://mozilla.org";
@@ -554,24 +544,17 @@ function test_newChannel_with_nsIURI()
   // the channel the utility method creates.
   let uri = NetUtil.newURI(TEST_SPEC);
   let iosChannel = NetUtil.ioService.newChannelFromURI2(uri,
                                                         null,      // aLoadingNode
                                                         Services.scriptSecurityManager.getSystemPrincipal(),
                                                         null,      // aTriggeringPrincipal
                                                         Ci.nsILoadInfo.SEC_NORMAL,
                                                         Ci.nsIContentPolicy.TYPE_OTHER);
-  let NetUtilChannel = NetUtil.newChannel2(uri,
-                                           null,
-                                           null,
-                                           null,      // aLoadingNode
-                                           Services.scriptSecurityManager.getSystemPrincipal(),
-                                           null,      // aTriggeringPrincipal
-                                           Ci.nsILoadInfo.SEC_NORMAL,
-                                           Ci.nsIContentPolicy.TYPE_OTHER);
+  let NetUtilChannel = NetUtil.newChannel(uri);
   do_check_true(iosChannel.URI.equals(NetUtilChannel.URI));
 
   run_next_test();
 }
 
 function test_newChannel_with_nsIFile()
 {
   let file = Cc["@mozilla.org/file/directory_service;1"].
@@ -583,29 +566,93 @@ function test_newChannel_with_nsIFile()
   // the channel the utility method creates.
   let uri = NetUtil.newURI(file);
   let iosChannel = NetUtil.ioService.newChannelFromURI2(uri,
                                                         null,      // aLoadingNode
                                                         Services.scriptSecurityManager.getSystemPrincipal(),
                                                         null,      // aTriggeringPrincipal
                                                         Ci.nsILoadInfo.SEC_NORMAL,
                                                         Ci.nsIContentPolicy.TYPE_OTHER);
-  let NetUtilChannel = NetUtil.newChannel2(uri,
-                                           null,
-                                           null,
-                                           null,      // aLoadingNode
-                                           Services.scriptSecurityManager.getSystemPrincipal(),
-                                           null,      // aTriggeringPrincipal
-                                           Ci.nsILoadInfo.SEC_NORMAL,
-                                           Ci.nsIContentPolicy.TYPE_OTHER);
+  let NetUtilChannel = NetUtil.newChannel(file);
   do_check_true(iosChannel.URI.equals(NetUtilChannel.URI));
 
   run_next_test();
 }
 
+function test_newChannel_with_options()
+{
+  let uri = "data:text/plain,";
+
+  let iosChannel = NetUtil.ioService.newChannelFromURI2(NetUtil.newURI(uri),
+                                                        null,      // aLoadingNode
+                                                        Services.scriptSecurityManager.getSystemPrincipal(),
+                                                        null,      // aTriggeringPrincipal
+                                                        Ci.nsILoadInfo.SEC_NORMAL,
+                                                        Ci.nsIContentPolicy.TYPE_OTHER);
+
+  function checkEqualToIOSChannel(channel) {
+    do_check_true(iosChannel.URI.equals(channel.URI));  
+  }
+
+  checkEqualToIOSChannel(NetUtil.newChannel({
+    uri,
+    loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+  }));
+
+  checkEqualToIOSChannel(NetUtil.newChannel({
+    uri,
+    loadUsingSystemPrincipal: true,
+  }));
+
+  run_next_test();
+}
+
+function test_newChannel_with_wrong_options()
+{
+  let uri = "data:text/plain,";
+  let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+  Assert.throws(() => {
+    NetUtil.newChannel({ uri, loadUsingSystemPrincipal: true }, null, null);
+  }, /requires a single object argument/);
+
+  Assert.throws(() => {
+    NetUtil.newChannel({});
+  }, /requires the 'uri' property/);
+
+  Assert.throws(() => {
+    NetUtil.newChannel({ uri });
+  }, /requires at least one of the 'loadingNode'/);
+
+  Assert.throws(() => {
+    NetUtil.newChannel({
+      uri,
+      loadingPrincipal: systemPrincipal,
+    });
+  }, /requires the 'contentPolicyType'/);
+
+  Assert.throws(() => {
+    NetUtil.newChannel({
+      uri,
+      loadUsingSystemPrincipal: systemPrincipal,
+    });
+  }, /to be 'true' or 'undefined'/);
+
+  Assert.throws(() => {
+    NetUtil.newChannel({
+      uri,
+      loadingPrincipal: systemPrincipal,
+      loadUsingSystemPrincipal: true,
+    });
+  }, /does not accept 'loadUsingSystemPrincipal'/);
+
+  run_next_test();
+}
+
 function test_readInputStreamToString()
 {
   const TEST_DATA = "this is a test string\0 with an embedded null";
   let istream = Cc["@mozilla.org/io/string-input-stream;1"].
                 createInstance(Ci.nsISupportsCString);
   istream.data = TEST_DATA;
 
   do_check_eq(NetUtil.readInputStreamToString(istream, TEST_DATA.length),
@@ -751,16 +798,18 @@ function test_readInputStreamToString_in
   test_asyncFetch_with_string,
   test_asyncFetch_with_nsIFile,
   test_asyncFetch_with_nsIInputString,
   test_asyncFetch_does_not_block,
   test_newChannel_no_specifier,
   test_newChannel_with_string,
   test_newChannel_with_nsIURI,
   test_newChannel_with_nsIFile,
+  test_newChannel_with_options,
+  test_newChannel_with_wrong_options,
   test_readInputStreamToString,
   test_readInputStreamToString_no_input_stream,
   test_readInputStreamToString_no_bytes_arg,
   test_readInputStreamToString_blocking_stream,
   test_readInputStreamToString_too_many_bytes,
   test_readInputStreamToString_with_charset,
   test_readInputStreamToString_invalid_sequence,
 ].forEach(add_test);