Merge mozilla-central to mozilla-inbound
authorEd Morley <bmo@edmorley.co.uk>
Wed, 18 Jan 2012 11:37:02 +0000
changeset 86018 2813a0ddc819fb89f977bb8d7636e74a10248001
parent 86017 9519fed510ee714348ebb4bbdafb22d7d5814240 (current diff)
parent 86004 7538f4d4697c67c34ce17cb567a35133eb4cb1bc (diff)
child 86019 d2f36b2d2c3bc1f4fa87dc07d4e499340d5671ab
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone12.0a1
Merge mozilla-central to mozilla-inbound
--- a/browser/base/content/syncSetup.xul
+++ b/browser/base/content/syncSetup.xul
@@ -403,16 +403,20 @@
                  accesskey="&syncComputerName.accesskey;"
                  control="syncComputerName"/>
           <textbox id="syncComputerName" flex="1"
                    onchange="gSyncUtils.changeName(this)"/>
         </row>
         <row>
           <label value="&syncMy.label;" />
           <vbox>
+            <checkbox label="&engine.addons.label;"
+                      accesskey="&engine.addons.accesskey;"
+                      id="engine.addons"
+                      checked="true"/>
             <checkbox label="&engine.bookmarks.label;"
                       accesskey="&engine.bookmarks.accesskey;"
                       id="engine.bookmarks"
                       checked="true"/>
             <checkbox label="&engine.passwords.label;"
                       accesskey="&engine.passwords.accesskey;"
                       id="engine.passwords"
                       checked="true"/>
@@ -423,20 +427,16 @@
             <checkbox label="&engine.history.label;"
                       accesskey="&engine.history.accesskey;"
                       id="engine.history"
                       checked="true"/>
             <checkbox label="&engine.tabs.label;"
                       accesskey="&engine.tabs.accesskey;"
                       id="engine.tabs"
                       checked="true"/>
-            <checkbox label="&engine.addons.label;"
-                      accesskey="&engine.addons.accesskey;"
-                      id="engine.addons"
-                      checked="true"/>
           </vbox>
         </row>
       </rows>
     </grid>
     </groupbox>
 
     <groupbox id="mergeOptions">
       <radiogroup id="mergeChoiceRadio" pack="start">
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -52,22 +52,22 @@
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:html="http://www.w3.org/1999/xhtml">
 
   <prefpane id="paneSync"
             helpTopic="prefs-weave"
             onpaneload="gSyncPane.init()">
 
     <preferences>
+      <preference id="engine.addons"    name="services.sync.engine.addons"    type="bool"/>
       <preference id="engine.bookmarks" name="services.sync.engine.bookmarks" type="bool"/>
       <preference id="engine.history"   name="services.sync.engine.history"   type="bool"/>
       <preference id="engine.tabs"      name="services.sync.engine.tabs"      type="bool"/>
       <preference id="engine.prefs"     name="services.sync.engine.prefs"     type="bool"/>
       <preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
-      <preference id="engine.addons"    name="services.sync.engine.addons"    type="bool"/>
     </preferences>
 
 
     <script type="application/javascript"
             src="chrome://browser/content/preferences/sync.js"/>
     <script type="application/javascript"
             src="chrome://browser/content/syncUtils.js"/>
 
@@ -124,16 +124,21 @@
             </hbox>
 
             <vbox>
               <label value="&syncMy.label;" />
               <richlistbox id="syncEnginesList"
                            orient="vertical"
                            onselect="if (this.selectedCount) this.clearSelection();">
                 <richlistitem>
+                  <checkbox label="&engine.addons.label;"
+                            accesskey="&engine.addons.accesskey;"
+                            preference="engine.addons"/>
+                </richlistitem>
+                <richlistitem>
                   <checkbox label="&engine.bookmarks.label;"
                             accesskey="&engine.bookmarks.accesskey;"
                             preference="engine.bookmarks"/>
                 </richlistitem>
                 <richlistitem>
                   <checkbox label="&engine.passwords.label;"
                             accesskey="&engine.passwords.accesskey;"
                             preference="engine.passwords"/>
@@ -148,21 +153,16 @@
                             accesskey="&engine.history.accesskey;"
                             preference="engine.history"/>
                 </richlistitem>
                 <richlistitem>
                   <checkbox label="&engine.tabs.label;"
                             accesskey="&engine.tabs.accesskey;"
                             preference="engine.tabs"/>
                 </richlistitem>
-                <richlistitem>
-                  <checkbox label="&engine.addons.label;"
-                            accesskey="&engine.addons.accesskey;"
-                            preference="engine.addons"/>
-                </richlistitem>
               </richlistbox>
             </vbox>
           </groupbox>
 
           <groupbox class="syncGroupBox">
             <grid>
               <columns>
                 <column/>
--- a/layout/base/tests/bug389321-1-ref.html
+++ b/layout/base/tests/bug389321-1-ref.html
@@ -1,15 +1,17 @@
 <!DOCTYPE HTML><html><head>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
 </head>
 <body>
 <span contenteditable id="t" style="border: 1px dashed green; min-height: 2px; padding-right: 20px;"> </span></body>
 <script>
-  // Only focus the span to put the caret at its beginning
   var sel = window.getSelection();
   sel.removeAllRanges();
 
+  // Focus the span to put the caret at its beginning.
   var area = document.getElementById('t');
   area.focus();
+
+  // Do nothing else.
 </script>
 </body>
 </html>
--- a/layout/base/tests/bug389321-1.html
+++ b/layout/base/tests/bug389321-1.html
@@ -1,18 +1,19 @@
 <!DOCTYPE HTML><html><head>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
 </head>
 <body>
 <span contenteditable id="t" style="border: 1px dashed green; min-height: 2px; padding-right: 20px;"> </span></body>
 <script>
-  // Enter a character in the span and delete it
   var sel = window.getSelection();
   sel.removeAllRanges();
 
+  // Focus the span to put the caret at its beginning.
   var area = document.getElementById('t');
   area.focus();
 
-  sendKey("W"); // enter a character
+  // Enter a character in the span then delete it.
+  sendChar("W");
   sendKey("BACK_SPACE");
 </script>
 </body>
 </html>
--- a/services/crypto/component/Makefile.in
+++ b/services/crypto/component/Makefile.in
@@ -38,16 +38,18 @@
 
 DEPTH     = ../../..
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
+FAIL_ON_WARNINGS := 1
+
 MODULE = services-crypto
 XPIDL_MODULE = services-crypto-component
 
 XPIDLSRCS = \
   nsISyncJPAKE.idl \
   $(NULL)
 
 LIBRARY_NAME   = services-crypto
--- a/services/sync/modules/resource.js
+++ b/services/sync/modules/resource.js
@@ -239,25 +239,25 @@ AsyncResource.prototype = {
     let channel = Services.io.newChannel(this.spec, null, null)
                           .QueryInterface(Ci.nsIRequest)
                           .QueryInterface(Ci.nsIHttpChannel);
 
     // Always validate the cache:
     channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
     channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 
-    // Setup a callback to handle bad HTTPS certificates.
-    channel.notificationCallbacks = new BadCertListener();
+    // Setup a callback to handle channel notifications.
+    channel.notificationCallbacks = new ChannelNotificationListener();
 
     // Compose a UA string fragment from the various available identifiers.
     if (Svc.Prefs.get("sendVersionInfo", true)) {
       let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop");
       channel.setRequestHeader("user-agent", ua, false);
     }
-    
+
     // Avoid calling the authorizer more than once.
     let headers = this.headers;
     for (let key in headers) {
       if (key == 'authorization')
         this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
       else
         this._log.trace("HTTP Header " + key + ": " + headers[key]);
       channel.setRequestHeader(key, headers[key], false);
@@ -515,17 +515,24 @@ function ChannelListener(onComplete, onP
   this._log = logger;
   this._timeout = timeout;
   this.delayAbort();
 }
 ChannelListener.prototype = {
 
   onStartRequest: function Channel_onStartRequest(channel) {
     this._log.trace("onStartRequest called for channel " + channel + ".");
-    channel.QueryInterface(Ci.nsIHttpChannel);
+
+    try {
+      channel.QueryInterface(Ci.nsIHttpChannel);
+    } catch (ex) {
+      this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
+      channel.cancel(Cr.NS_BINDING_ABORTED);
+      return;
+    }
 
     // Save the latest server timestamp when possible.
     try {
       AsyncResource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0;
     }
     catch(ex) {}
 
     this._log.trace("onStartRequest: " + channel.requestMethod + " " +
@@ -533,39 +540,58 @@ ChannelListener.prototype = {
     this._data = '';
     this.delayAbort();
   },
 
   onStopRequest: function Channel_onStopRequest(channel, context, status) {
     // Clear the abort timer now that the channel is done.
     this.abortTimer.clear();
 
+    if (!this._onComplete) {
+      this._log.error("Unexpected error: _onComplete not defined in onStopRequest.");
+      this._onProgress = null;
+      return;
+    }
+
+    try {
+      channel.QueryInterface(Ci.nsIHttpChannel);
+    } catch (ex) {
+      this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
+
+      this._onComplete(ex, this._data, channel);
+      this._onComplete = this._onProgress = null;
+      return;
+    }
+
     let statusSuccess = Components.isSuccessCode(status);
     let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
     this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " +
                     "isSuccessCode(" + status + ")? " + statusSuccess);
 
     if (this._data == '') {
       this._data = null;
     }
 
     // Pass back the failure code and stop execution. Use Components.Exception()
     // instead of Error() so the exception is QI-able and can be passed across
     // XPCOM borders while preserving the status code.
     if (!statusSuccess) {
       let message = Components.Exception("", status).name;
       let error   = Components.Exception(message, status);
+
       this._onComplete(error, undefined, channel);
+      this._onComplete = this._onProgress = null;
       return;
     }
 
     this._log.trace("Channel: flags = " + channel.loadFlags +
                     ", URI = " + uri +
                     ", HTTP success? " + channel.requestSucceeded);
     this._onComplete(null, this._data, channel);
+    this._onComplete = this._onProgress = null;
   },
 
   onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {
     let siStream = Cc["@mozilla.org/scriptableinputstream;1"].
       createInstance(Ci.nsIScriptableInputStream);
     siStream.init(stream);
     try {
       this._data += siStream.read(count);
@@ -595,45 +621,54 @@ ChannelListener.prototype = {
     Utils.namedTimer(this.abortRequest, this._timeout, this, "abortTimer");
   },
 
   abortRequest: function abortRequest() {
     // Ignore any callbacks if we happen to get any now
     this.onStopRequest = function() {};
     let error = Components.Exception("Aborting due to channel inactivity.",
                                      Cr.NS_ERROR_NET_TIMEOUT);
+    if (!this._onComplete) {
+      this._log.error("Unexpected error: _onComplete not defined in " +
+                      "abortRequest.");
+      return;
+    }
     this._onComplete(error);
   }
 };
 
-// = BadCertListener =
-//
-// We use this listener to ignore bad HTTPS
-// certificates and continue a request on a network
-// channel. Probably not a very smart thing to do,
-// but greatly simplifies debugging and is just very
-// convenient.
-function BadCertListener() {
+/**
+ * This class handles channel notification events.
+ *
+ * An instance of this class is bound to each created channel.
+ */
+function ChannelNotificationListener() {
 }
-BadCertListener.prototype = {
+ChannelNotificationListener.prototype = {
   getInterface: function(aIID) {
     return this.QueryInterface(aIID);
   },
 
   QueryInterface: function(aIID) {
-    if (aIID.equals(Components.interfaces.nsIBadCertListener2) ||
-        aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
-        aIID.equals(Components.interfaces.nsISupports))
+    if (aIID.equals(Ci.nsIBadCertListener2) ||
+        aIID.equals(Ci.nsIInterfaceRequestor) ||
+        aIID.equals(Ci.nsISupports) ||
+        aIID.equals(Ci.nsIChannelEventSink))
       return this;
 
-    throw Components.results.NS_ERROR_NO_INTERFACE;
+    throw Cr.NS_ERROR_NO_INTERFACE;
   },
 
   notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) {
-    // Silently ignore?
     let log = Log4Moz.repository.getLogger("Sync.CertListener");
-    log.level =
-      Log4Moz.Level[Svc.Prefs.get("log.logger.network.resources")];
-    log.debug("Invalid HTTPS certificate encountered, ignoring!");
+    log.warn("Invalid HTTPS certificate encountered!");
+
+    // This suppresses the UI warning only. The request is still cancelled.
+    return true;
+  },
 
-    return true;
+  asyncOnChannelRedirect:
+    function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+
+    // We let all redirects proceed.
+    callback.onRedirectVerifyCallback(Cr.NS_OK);
   }
 };
--- a/services/sync/modules/rest.js
+++ b/services/sync/modules/rest.js
@@ -121,17 +121,18 @@ function RESTRequest(uri) {
     Log4Moz.Level[Svc.Prefs.get("log.logger.network.resources")];
 }
 RESTRequest.prototype = {
 
   _logName: "Sync.RESTRequest",
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIBadCertListener2,
-    Ci.nsIInterfaceRequestor
+    Ci.nsIInterfaceRequestor,
+    Ci.nsIChannelEventSink
   ]),
 
   /*** Public API: ***/
 
   /**
    * URI for the request (an nsIURI object).
    */
   uri: null,
@@ -359,32 +360,43 @@ RESTRequest.prototype = {
 
   /**
    * Abort the request based on a timeout.
    */
   abortTimeout: function abortTimeout() {
     this.abort();
     let error = Components.Exception("Aborting due to channel inactivity.",
                                      Cr.NS_ERROR_NET_TIMEOUT);
+    if (!this.onComplete) {
+      this._log.error("Unexpected error: onComplete not defined in " +
+                      "abortTimeout.")
+      return;
+    }
     this.onComplete(error);
   },
 
   /*** nsIStreamListener ***/
 
   onStartRequest: function onStartRequest(channel) {
-    // Update the channel in case we got redirected.
-    this.channel = channel;
-
     if (this.status == this.ABORTED) {
       this._log.trace("Not proceeding with onStartRequest, request was aborted.");
       return;
     }
+
+    try {
+      channel.QueryInterface(Ci.nsIHttpChannel);
+    } catch (ex) {
+      this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
+      this.status = this.ABORTED;
+      channel.cancel(Cr.NS_BINDING_ABORTED);
+      return;
+    }
+
     this.status = this.IN_PROGRESS;
 
-    channel.QueryInterface(Ci.nsIHttpChannel);
     this._log.trace("onStartRequest: " + channel.requestMethod + " " +
                     channel.URI.spec);
 
     // Create a response object and fill it with some data.
     let response = this.response = new RESTResponse();
     response.request = this;
     response.body = "";
 
@@ -392,36 +404,48 @@ RESTRequest.prototype = {
     // onDataAvailable() gets called.
     this._inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
                           .createInstance(Ci.nsIScriptableInputStream);
 
     this.delayTimeout();
   },
 
   onStopRequest: function onStopRequest(channel, context, statusCode) {
-    // Update the channel in case we got redirected.
-    this.channel = channel;
-
     if (this.timeoutTimer) {
       // Clear the abort timer now that the channel is done.
       this.timeoutTimer.clear();
     }
 
     // We don't want to do anything for a request that's already been aborted.
     if (this.status == this.ABORTED) {
       this._log.trace("Not proceeding with onStopRequest, request was aborted.");
       return;
     }
+
+    try {
+      channel.QueryInterface(Ci.nsIHttpChannel);
+    } catch (ex) {
+      this._log.error("Unexpected error: channel not nsIHttpChannel!");
+      this.status = this.ABORTED;
+      return;
+    }
     this.status = this.COMPLETED;
 
     let statusSuccess = Components.isSuccessCode(statusCode);
     let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
     this._log.trace("Channel for " + channel.requestMethod + " " + uri +
                     " returned status code " + statusCode);
 
+    if (!this.onComplete) {
+      this._log.error("Unexpected error: onComplete not defined in " +
+                      "abortRequest.");
+      this.onProgress = null;
+      return;
+    }
+
     // Throw the failure code and stop execution.  Use Components.Exception()
     // instead of Error() so the exception is QI-able and can be passed across
     // XPCOM borders while preserving the status code.
     if (!statusSuccess) {
       let message = Components.Exception("", statusCode).name;
       let error = Components.Exception(message, statusCode);
       this.onComplete(error);
       this.onComplete = this.onProgress = null;
@@ -454,16 +478,24 @@ RESTRequest.prototype = {
 
     try {
       this.onProgress();
     } catch (ex) {
       this._log.warn("Got exception calling onProgress handler, aborting " +
                      this.method + " " + req.URI.spec);
       this._log.debug("Exception: " + Utils.exceptionStr(ex));
       this.abort();
+
+      if (!this.onComplete) {
+        this._log.error("Unexpected error: onComplete not defined in " +
+                        "onDataAvailable.");
+        this.onProgress = null;
+        return;
+      }
+
       this.onComplete(ex);
       this.onComplete = this.onProgress = null;
       return;
     }
 
     this.delayTimeout();
   },
 
@@ -475,16 +507,34 @@ RESTRequest.prototype = {
 
   /*** nsIBadCertListener2 ***/
 
   notifyCertProblem: function notifyCertProblem(socketInfo, sslStatus, targetHost) {
     this._log.warn("Invalid HTTPS certificate encountered!");
     // Suppress invalid HTTPS certificate warnings in the UI.
     // (The request will still fail.)
     return true;
+  },
+
+  /*** nsIChannelEventSink ***/
+  asyncOnChannelRedirect:
+    function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+
+    try {
+      newChannel.QueryInterface(Ci.nsIHttpChannel);
+    } catch (ex) {
+      this._log.error("Unexpected error: channel not nsIHttpChannel!");
+      callback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE);
+      return;
+    }
+
+    this.channel = newChannel;
+
+    // We let all redirects proceed.
+    callback.onRedirectVerifyCallback(Cr.NS_OK);
   }
 };
 
 
 /**
  * Response object for a RESTRequest. This will be created automatically by
  * the RESTRequest.
  */
--- a/services/sync/tests/unit/head_http_server.js
+++ b/services/sync/tests/unit/head_http_server.js
@@ -18,19 +18,19 @@ function return_timestamp(request, respo
   }
   let body = "" + timestamp;
   response.setHeader("X-Weave-Timestamp", body);
   response.setStatusLine(request.httpVersion, 200, "OK");
   response.bodyOutputStream.write(body, body.length);
   return timestamp;
 }
 
-function httpd_setup (handlers) {
+function httpd_setup (handlers, port) {
+  let port   = port || 8080;
   let server = new nsHttpServer();
-  let port   = 8080;
   for (let path in handlers) {
     server.registerPathHandler(path, handlers[path]);
   }
   try {
     server.start(port);
   } catch (ex) {
     _("==========================================");
     _("Got exception starting HTTP server on port " + port);
--- a/services/sync/tests/unit/test_resource_async.js
+++ b/services/sync/tests/unit/test_resource_async.js
@@ -140,16 +140,23 @@ function server_headers(metadata, respon
   for each (let header in header_names) {
     headers[header] = metadata.getHeader(header);
   }
   let body = JSON.stringify(headers);
   response.setStatusLine(metadata.httpVersion, 200, "OK");
   response.bodyOutputStream.write(body, body.length);
 }
 
+function server_redirect(metadata, response) {
+  let body = "Redirecting";
+  response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
+  response.setHeader("Location", "http://localhost:8081/resource");
+  response.bodyOutputStream.write(body, body.length);
+}
+
 let quotaValue;
 Observers.add("weave:service:quota:remaining",
               function (subject) { quotaValue = subject; });
 
 let server;
 
 function run_test() {
   logger = Log4Moz.repository.getLogger('Test');
@@ -162,17 +169,18 @@ function run_test() {
     "/upload": server_upload,
     "/delete": server_delete,
     "/json": server_json,
     "/timestamp": server_timestamp,
     "/headers": server_headers,
     "/backoff": server_backoff,
     "/pac2": server_pac,
     "/quota-notice": server_quota_notice,
-    "/quota-error": server_quota_error
+    "/quota-error": server_quota_error,
+    "/redirect": server_redirect
   });
 
   Svc.Prefs.set("network.numRetries", 1); // speed up test
   run_next_test();
 }
 
 // This apparently has to come first in order for our PAC URL to be hit.
 // Don't put any other HTTP requests earlier in the file!
@@ -653,11 +661,36 @@ add_test(function test_uri_construction(
   let uri1 = Utils.makeURL("http://foo/" + query);
   let uri2 = Utils.makeURL("http://foo/");
   uri2.query = query;
   do_check_eq(uri1.query, uri2.query);
 
   run_next_test();
 });
 
+add_test(function test_new_channel() {
+  _("Ensure a redirect to a new channel is handled properly.");
+
+  let resourceRequested = false;
+  function resourceHandler(metadata, response) {
+    resourceRequested = true;
+
+    let body = "Test";
+    response.setHeader("Content-Type", "text/plain");
+    response.bodyOutputStream.write(body, body.length);
+  }
+  let server2 = httpd_setup({"/resource": resourceHandler}, 8081);
+
+  let request = new AsyncResource("http://localhost:8080/redirect");
+  request.get(function onRequest(error, content) {
+    do_check_null(error);
+    do_check_true(resourceRequested);
+    do_check_eq(200, content.status);
+    do_check_true("content-type" in content.headers);
+    do_check_eq("text/plain", content.headers["content-type"]);
+
+    server2.stop(run_next_test);
+  });
+});
+
 add_test(function tear_down() {
   server.stop(run_next_test);
 });
--- a/services/sync/tests/unit/test_restrequest.js
+++ b/services/sync/tests/unit/test_restrequest.js
@@ -615,8 +615,49 @@ add_test(function test_exception_in_onPr
   };
   request.get(function (error) {
     do_check_eq(error, "ReferenceError: it is not defined");
     do_check_eq(this.status, this.ABORTED);
 
     server.stop(run_next_test);
   });
 });
+
+add_test(function test_new_channel() {
+  _("Ensure a redirect to a new channel is handled properly.");
+
+  let redirectRequested = false;
+  function redirectHandler(metadata, response) {
+    redirectRequested = true;
+
+    let body = "Redirecting";
+    response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
+    response.setHeader("Location", "http://localhost:8081/resource");
+    response.bodyOutputStream.write(body, body.length);
+  }
+
+  let resourceRequested = false;
+  function resourceHandler(metadata, response) {
+    resourceRequested = true;
+
+    let body = "Test";
+    response.setHeader("Content-Type", "text/plain");
+    response.bodyOutputStream.write(body, body.length);
+  }
+  let server1 = httpd_setup({"/redirect": redirectHandler}, 8080);
+  let server2 = httpd_setup({"/resource": resourceHandler}, 8081);
+
+  function advance() {
+    server1.stop(function () {
+      server2.stop(run_next_test);
+    });
+  }
+
+  let request = new RESTRequest("http://localhost:8080/redirect");
+  request.get(function onComplete(error) {
+    let response = this.response;
+
+    do_check_eq(200, response.status);
+    do_check_eq("Test", response.body);
+
+    advance();
+  });
+});
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -1730,17 +1730,19 @@ GCGraphBuilder::NoteRoot(PRUint32 langID
 {
     NS_ASSERTION(root, "Don't add a null root!");
 
     if (langID > nsIProgrammingLanguage::MAX || !mRuntimes[langID]) {
         Fault("adding root for unregistered language", root);
         return;
     }
 
-    AddNode(root, participant, langID);
+    if (!participant->CanSkipThis(root)) {
+        AddNode(root, participant, langID);
+    }
 }
 
 NS_IMETHODIMP_(void)
 GCGraphBuilder::DescribeRefCountedNode(nsrefcnt refCount, size_t objSz,
                                        const char *objName)
 {
     if (refCount == 0)
         Fault("zero refcount", mCurrPi);
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -129,60 +129,26 @@ protected:
     nsCycleCollectionTraversalCallback() : mFlags(0) {}
 
     PRUint32 mFlags;
 };
 
 class NS_NO_VTABLE nsCycleCollectionParticipant
 {
 public:
+    nsCycleCollectionParticipant() : mMightSkip(false) {}
+    nsCycleCollectionParticipant(bool aSkip) : mMightSkip(aSkip) {} 
+    
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_CYCLECOLLECTIONPARTICIPANT_IID)
 
     NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb) = 0;
 
     NS_IMETHOD Root(void *p) = 0;
     NS_IMETHOD Unlink(void *p) = 0;
     NS_IMETHOD Unroot(void *p) = 0;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsCycleCollectionParticipant, 
-                              NS_CYCLECOLLECTIONPARTICIPANT_IID)
-
-#undef IMETHOD_VISIBILITY
-#define IMETHOD_VISIBILITY NS_COM_GLUE
-
-typedef void
-(* TraceCallback)(PRUint32 langID, void *p, const char *name, void *closure);
-
-class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant
-{
-public:
-    NS_IMETHOD_(void) Trace(void *p, TraceCallback cb, void *closure) = 0;
-    void NS_COM_GLUE TraverseScriptObjects(void *p,
-                                        nsCycleCollectionTraversalCallback &cb);
-};
-
-class NS_COM_GLUE nsXPCOMCycleCollectionParticipant
-    : public nsScriptObjectTracer
-{
-public:
-    nsXPCOMCycleCollectionParticipant() : mMightSkip(false) {}
-    nsXPCOMCycleCollectionParticipant(bool aSkip) : mMightSkip(aSkip) {}
-
-    NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb);
-
-    NS_IMETHOD Root(void *p);
-    NS_IMETHOD Unlink(void *p);
-    NS_IMETHOD Unroot(void *p);
-
-    NS_IMETHOD_(void) Trace(void *p, TraceCallback cb, void *closure);
-
-    NS_IMETHOD_(void) UnmarkPurple(nsISupports *p);
-
-    bool CheckForRightISupports(nsISupports *s);
 
     // If CanSkip returns true, p is removed from the purple buffer during
     // a call to nsCycleCollector_forgetSkippable().
     // Note, calling CanSkip may remove objects from the purple buffer!
     bool CanSkip(void *p)
     {
         return mMightSkip ? CanSkipReal(p) : false;
     }
@@ -218,16 +184,58 @@ protected:
         NS_ASSERTION(false, "Forgot to implement CanSkipThisReal?");
         return false;
     }
 
 private:
     bool mMightSkip;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(nsCycleCollectionParticipant, 
+                              NS_CYCLECOLLECTIONPARTICIPANT_IID)
+
+#undef IMETHOD_VISIBILITY
+#define IMETHOD_VISIBILITY NS_COM_GLUE
+
+typedef void
+(* TraceCallback)(PRUint32 langID, void *p, const char *name, void *closure);
+
+class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant
+{
+public:
+    nsScriptObjectTracer() : nsCycleCollectionParticipant(false) {}
+    nsScriptObjectTracer(bool aSkip) : nsCycleCollectionParticipant(aSkip) {}
+
+    NS_IMETHOD_(void) Trace(void *p, TraceCallback cb, void *closure) = 0;
+    void NS_COM_GLUE TraverseScriptObjects(void *p,
+                                        nsCycleCollectionTraversalCallback &cb);
+};
+
+class NS_COM_GLUE nsXPCOMCycleCollectionParticipant
+    : public nsScriptObjectTracer
+{
+public:
+    nsXPCOMCycleCollectionParticipant()
+    : nsScriptObjectTracer(false) {}
+    nsXPCOMCycleCollectionParticipant(bool aSkip)
+    : nsScriptObjectTracer(aSkip) {}
+
+    NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb);
+
+    NS_IMETHOD Root(void *p);
+    NS_IMETHOD Unlink(void *p);
+    NS_IMETHOD Unroot(void *p);
+
+    NS_IMETHOD_(void) Trace(void *p, TraceCallback cb, void *closure);
+
+    NS_IMETHOD_(void) UnmarkPurple(nsISupports *p);
+
+    bool CheckForRightISupports(nsISupports *s);
+};
+
 #undef IMETHOD_VISIBILITY
 #define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN
 
 ///////////////////////////////////////////////////////////////////////////////
 // Helpers for implementing a QI to nsXPCOMCycleCollectionParticipant
 ///////////////////////////////////////////////////////////////////////////////
 
 #define NS_CYCLE_COLLECTION_INNERCLASS                                         \