Bug 1433958 - Change code that sets nsIURI.query to use nsIURIMutator draft
authorValentin Gosu <valentin.gosu@gmail.com>
Mon, 26 Feb 2018 20:43:46 +0100
changeset 759895 bb2938bdaa42a1d9331e80edd48289e020cceff0
parent 759894 29c33956375b85823763bef839dd1c6dd4a85c5e
child 759896 5fa03cdaac29c772964ee1a8aa2ae4550a7d7bed
push id100504
push uservalentin.gosu@gmail.com
push dateMon, 26 Feb 2018 19:44:44 +0000
bugs1433958
milestone60.0a1
Bug 1433958 - Change code that sets nsIURI.query to use nsIURIMutator MozReview-Commit-ID: JKW8IsaFY10
browser/base/content/browser-safebrowsing.js
browser/base/content/content.js
dom/cache/DBAction.cpp
dom/indexedDB/ActorsParent.cpp
dom/url/URLMainThread.cpp
dom/url/URLWorker.cpp
modules/libjar/nsJARURI.cpp
netwerk/protocol/file/nsFileChannel.cpp
netwerk/protocol/file/nsFileProtocolHandler.cpp
netwerk/protocol/file/nsFileProtocolHandler.h
netwerk/protocol/file/nsIFileProtocolHandler.idl
netwerk/test/unit/test_URIs.js
netwerk/test/unit/test_URIs2.js
netwerk/test/unit/test_standardurl.js
services/sync/modules/addonutils.js
services/sync/modules/record.js
services/sync/tests/unit/test_resource.js
toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm
--- a/browser/base/content/browser-safebrowsing.js
+++ b/browser/base/content/browser-safebrowsing.js
@@ -53,16 +53,18 @@ var gSafeBrowsing = {
    */
   getReportURL(name, info) {
     let reportInfo = info;
     if (!reportInfo) {
       let pageUri = gBrowser.currentURI.clone();
 
       // Remove the query to avoid including potentially sensitive data
       if (pageUri instanceof Ci.nsIURL) {
-        pageUri.query = "";
+        pageUri = pageUri.mutate()
+                         .setQuery("")
+                         .finalize();
       }
 
       reportInfo = { uri: pageUri.asciiSpec };
     }
     return SafeBrowsing.getReportURL(name, reportInfo);
   }
 };
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -103,17 +103,19 @@ function getSiteBlockedErrorDetails(docS
                             QueryInterface(Ci.nsIClassifiedChannel);
     if (classifiedChannel) {
       let httpChannel = docShell.failedChannel.QueryInterface(Ci.nsIHttpChannel);
 
       let reportUri = httpChannel.URI.clone();
 
       // Remove the query to avoid leaking sensitive data
       if (reportUri instanceof Ci.nsIURL) {
-        reportUri.query = "";
+        reportUri = reportUri.mutate()
+                             .setQuery("")
+                             .finalize();
       }
 
       blockedInfo = { list: classifiedChannel.matchedList,
                       provider: classifiedChannel.matchedProvider,
                       uri: reportUri.asciiSpec };
     }
   }
   return blockedInfo;
--- a/dom/cache/DBAction.cpp
+++ b/dom/cache/DBAction.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/net/nsFileProtocolHandler.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageService.h"
 #include "mozStorageCID.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
+#include "nsIURIMutator.h"
 #include "nsIFileURL.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 using mozilla::dom::quota::AssertIsOnIOThread;
@@ -192,31 +193,32 @@ OpenDBConnection(const QuotaInfo& aQuota
   // Use our default file:// protocol handler directly to construct the database
   // URL.  This avoids any problems if a plugin registers a custom file://
   // handler.  If such a custom handler used javascript, then we would have a
   // bad time running off the main thread here.
   RefPtr<nsFileProtocolHandler> handler = new nsFileProtocolHandler();
   rv = handler->Init();
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  nsCOMPtr<nsIURI> uri;
-  rv = handler->NewFileURI(dbFile, getter_AddRefs(uri));
+  nsCOMPtr<nsIURIMutator> mutator;
+  rv = handler->NewFileURIMutator(dbFile, getter_AddRefs(mutator));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  nsCOMPtr<nsIFileURL> dbFileUrl = do_QueryInterface(uri);
-  if (NS_WARN_IF(!dbFileUrl)) { return NS_ERROR_UNEXPECTED; }
+  nsCOMPtr<nsIFileURL> dbFileUrl;
 
   nsAutoCString type;
   PersistenceTypeToText(PERSISTENCE_TYPE_DEFAULT, type);
 
-  rv = dbFileUrl->SetQuery(
+  rv = NS_MutateURI(mutator)
+         .SetQuery(
     NS_LITERAL_CSTRING("persistenceType=") + type +
     NS_LITERAL_CSTRING("&group=") + aQuotaInfo.mGroup +
     NS_LITERAL_CSTRING("&origin=") + aQuotaInfo.mOrigin +
-    NS_LITERAL_CSTRING("&cache=private"));
+    NS_LITERAL_CSTRING("&cache=private"))
+         .Finalize(dbFileUrl);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!ss)) { return NS_ERROR_UNEXPECTED; }
 
   rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
   if (rv == NS_ERROR_FILE_CORRUPTED) {
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -89,16 +89,17 @@
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupports.h"
 #include "nsISupportsImpl.h"
 #include "nsISupportsPriority.h"
 #include "nsIThread.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
+#include "nsIURIMutator.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsQueryObject.h"
 #include "nsRefPtrHashtable.h"
 #include "nsStreamUtils.h"
 #include "nsString.h"
 #include "nsStringStream.h"
 #include "nsThreadPool.h"
@@ -4194,40 +4195,41 @@ GetDatabaseFileURL(nsIFile* aDatabaseFil
   }
 
   nsCOMPtr<nsIFileProtocolHandler> fileHandler(
     do_QueryInterface(protocolHandler, &rv));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsCOMPtr<nsIURI> uri;
-  rv = fileHandler->NewFileURI(aDatabaseFile, getter_AddRefs(uri));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
-  MOZ_ASSERT(fileUrl);
+  nsCOMPtr<nsIURIMutator> mutator;
+  rv = fileHandler->NewFileURIMutator(aDatabaseFile, getter_AddRefs(mutator));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIFileURL> fileUrl;
 
   nsAutoCString type;
   PersistenceTypeToText(aPersistenceType, type);
 
   nsAutoCString telemetryFilenameClause;
   if (aTelemetryId) {
     telemetryFilenameClause.AssignLiteral("&telemetryFilename=indexedDB-");
     telemetryFilenameClause.AppendInt(aTelemetryId);
     telemetryFilenameClause.AppendLiteral(".sqlite");
   }
 
-  rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type +
-                         NS_LITERAL_CSTRING("&group=") + aGroup +
-                         NS_LITERAL_CSTRING("&origin=") + aOrigin +
-                         NS_LITERAL_CSTRING("&cache=private") +
-                         telemetryFilenameClause);
+  rv = NS_MutateURI(mutator)
+         .SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type +
+                   NS_LITERAL_CSTRING("&group=") + aGroup +
+                   NS_LITERAL_CSTRING("&origin=") + aOrigin +
+                   NS_LITERAL_CSTRING("&cache=private") +
+                   telemetryFilenameClause)
+         .Finalize(fileUrl);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   fileUrl.forget(aResult);
   return NS_OK;
 }
 
--- a/dom/url/URLMainThread.cpp
+++ b/dom/url/URLMainThread.cpp
@@ -451,17 +451,19 @@ URLMainThread::SetHash(const nsAString& 
               .Finalize(mURI);
 }
 
 void
 URLMainThread::SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv)
 {
   // Ignore failures to be compatible with NS4.
 
-  mURI->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
+  Unused << NS_MutateURI(mURI)
+              .SetQuery(NS_ConvertUTF16toUTF8(aSearch))
+              .Finalize(mURI);
 }
 
 nsIURI*
 URLMainThread::GetURI() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mURI;
 }
--- a/dom/url/URLWorker.cpp
+++ b/dom/url/URLWorker.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "URLWorker.h"
 
 #include "mozilla/dom/Blob.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/dom/WorkerScope.h"
+#include "mozilla/Unused.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsStandardURL.h"
 #include "nsURLHelper.h"
 
 namespace mozilla {
 
 using net::nsStandardURL;
 
@@ -1163,17 +1164,19 @@ URLWorker::SetHash(const nsAString& aHas
   MOZ_ASSERT(!runnable->Failed());
 }
 
 void
 URLWorker::SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv)
 {
   if (mStdURL) {
     // URLMainThread ignores failures here.
-    mStdURL->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
+    Unused << NS_MutateURI(mStdURL)
+                .SetQuery(NS_ConvertUTF16toUTF8(aSearch))
+                .Finalize(mStdURL);
     return;
   }
 
   MOZ_ASSERT(mURLProxy);
   RefPtr<SetterRunnable> runnable =
     new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterSearch,
                        aSearch, mURLProxy);
 
--- a/modules/libjar/nsJARURI.cpp
+++ b/modules/libjar/nsJARURI.cpp
@@ -673,17 +673,19 @@ NS_IMETHODIMP
 nsJARURI::GetQuery(nsACString& query)
 {
     return mJAREntry->GetQuery(query);
 }
 
 NS_IMETHODIMP
 nsJARURI::SetQuery(const nsACString& query)
 {
-    return mJAREntry->SetQuery(query);
+    return NS_MutateURI(mJAREntry)
+             .SetQuery(query)
+             .Finalize(mJAREntry);
 }
 
 NS_IMETHODIMP
 nsJARURI::SetQueryWithEncoding(const nsACString& query,
                                const Encoding* encoding)
 {
     return NS_MutateURI(mJAREntry)
              .SetQueryWithEncoding(query, encoding)
--- a/netwerk/protocol/file/nsFileChannel.cpp
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -18,21 +18,24 @@
 #include "nsIFileStreams.h"
 #include "nsFileProtocolHandler.h"
 #include "nsProxyRelease.h"
 #include "nsAutoPtr.h"
 #include "nsIContentPolicy.h"
 #include "nsContentUtils.h"
 
 #include "nsIFileURL.h"
+#include "nsIURIMutator.h"
 #include "nsIFile.h"
 #include "nsIMIMEService.h"
 #include "prio.h"
 #include <algorithm>
 
+#include "mozilla/Unused.h"
+
 using namespace mozilla;
 using namespace mozilla::net;
 
 //-----------------------------------------------------------------------------
 
 class nsFileCopyEvent : public Runnable {
 public:
   nsFileCopyEvent(nsIOutputStream* dest, nsIInputStream* source, int64_t len)
@@ -296,17 +299,19 @@ nsFileChannel::Init()
 #endif
       NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(targetURI),
                                  resolvedFile, nullptr))) {
     // Make an effort to match up the query strings.
     nsCOMPtr<nsIURL> origURL = do_QueryInterface(mFileURI);
     nsCOMPtr<nsIURL> targetURL = do_QueryInterface(targetURI);
     nsAutoCString queryString;
     if (origURL && targetURL && NS_SUCCEEDED(origURL->GetQuery(queryString))) {
-      targetURL->SetQuery(queryString);
+      Unused << NS_MutateURI(targetURI)
+                  .SetQuery(queryString)
+                  .Finalize(targetURI);
     }
 
     SetURI(targetURI);
     SetOriginalURI(mFileURI);
     mLoadInfo->SetResultPrincipalURI(targetURI);
   } else {
     SetURI(mFileURI);
   }
--- a/netwerk/protocol/file/nsFileProtocolHandler.cpp
+++ b/netwerk/protocol/file/nsFileProtocolHandler.cpp
@@ -252,16 +252,44 @@ nsFileProtocolHandler::NewFileURI(nsIFil
     // charset by the SetFile method.
     rv = url->SetFile(file);
     if (NS_FAILED(rv)) return rv;
 
     return CallQueryInterface(url, result);
 }
 
 NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURIMutator(nsIFile *aFile, nsIURIMutator **aResult)
+{
+    NS_ENSURE_ARG_POINTER(aFile);
+    nsresult rv;
+
+    nsCOMPtr<nsIURI> url = new nsStandardURL(true);
+    nsCOMPtr<nsIURIMutator> mutator;
+    rv = url->Mutate(getter_AddRefs(mutator));
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+    nsCOMPtr<nsIFileURLMutator> fileMutator = do_QueryInterface(mutator, &rv);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
+    // NOTE: the origin charset is assigned the value of the platform
+    // charset by the SetFile method.
+    rv = fileMutator->SetFile(aFile);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
+    mutator.forget(aResult);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsFileProtocolHandler::GetURLSpecFromFile(nsIFile *file, nsACString &result)
 {
     NS_ENSURE_ARG_POINTER(file);
     return net_GetURLSpecFromFile(file, result);
 }
 
 NS_IMETHODIMP
 nsFileProtocolHandler::GetURLSpecFromActualFile(nsIFile *file,
--- a/netwerk/protocol/file/nsFileProtocolHandler.h
+++ b/netwerk/protocol/file/nsFileProtocolHandler.h
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsFileProtocolHandler_h__
 #define nsFileProtocolHandler_h__
 
 #include "nsIFileProtocolHandler.h"
 #include "nsWeakReference.h"
 
+class nsIURIMutator;
+
 class nsFileProtocolHandler : public nsIFileProtocolHandler
                             , public nsSupportsWeakReference
 {
     virtual ~nsFileProtocolHandler() {}
 
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIPROTOCOLHANDLER
--- a/netwerk/protocol/file/nsIFileProtocolHandler.idl
+++ b/netwerk/protocol/file/nsIFileProtocolHandler.idl
@@ -1,29 +1,40 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "nsIProtocolHandler.idl"
 
 interface nsIFile;
+interface nsIURIMutator;
 
 [scriptable, uuid(1fb25bd5-4354-4dcd-8d97-621b7b3ed2e4)]
 interface nsIFileProtocolHandler : nsIProtocolHandler
 {
     /**
      * This method constructs a new file URI 
      *
      * @param aFile nsIFile
      * @return reference to a new nsIURI object
      */
     nsIURI newFileURI(in nsIFile aFile);
 
     /**
+     * This method constructs a new file URI, and returns a URI mutator
+     * that has not yet been finalized, allowing the URI to be changed without
+     * being cloned.
+     *
+     * @param aFile nsIFile
+     * @return reference to a new nsIURIMutator object
+     */
+    nsIURIMutator newFileURIMutator(in nsIFile file);
+
+    /**
      * Converts the nsIFile to the corresponding URL string.  NOTE: under
      * some platforms this is a lossy conversion (e.g., Mac Carbon build).
      * If the nsIFile is a local file, then the result will be a file://
      * URL string.
      *
      * The resulting string may contain URL-escaped characters.
      * NOTE: Callers should use getURLSpecFromActualFile or
      * getURLSpecFromDirFile if possible, for performance reasons.
--- a/netwerk/test/unit/test_URIs.js
+++ b/netwerk/test/unit/test_URIs.js
@@ -522,17 +522,17 @@ function do_test_mutate_ref(aTest, aSuff
 // Tests that normally-mutable properties can't be modified on
 // special URIs that are known to be immutable.
 function do_test_immutable(aTest) {
   Assert.ok(aTest.immutable);
 
   var URI = NetUtil.newURI(aTest.spec);
   // All the non-readonly attributes on nsIURI.idl:
   var propertiesToCheck = ["spec", "scheme",
-                           "host", "port", "query"];
+                           "host", "port"];
 
   propertiesToCheck.forEach(function(aProperty) {
     var threw = false;
     try {
       URI[aProperty] = "anothervalue";
     } catch(e) {
       threw = true;
     }
--- a/netwerk/test/unit/test_URIs2.js
+++ b/netwerk/test/unit/test_URIs2.js
@@ -623,17 +623,17 @@ function do_test_mutate_ref(aTest, aSuff
 // Tests that normally-mutable properties can't be modified on
 // special URIs that are known to be immutable.
 function do_test_immutable(aTest) {
   Assert.ok(aTest.immutable);
 
   var URI = NetUtil.newURI(aTest.spec);
   // All the non-readonly attributes on nsIURI.idl:
   var propertiesToCheck = ["scheme",
-                           "host", "port", "query"];
+                           "host", "port"];
 
   propertiesToCheck.forEach(function(aProperty) {
     var threw = false;
     try {
       URI[aProperty] = "anothervalue";
     } catch(e) {
       threw = true;
     }
--- a/netwerk/test/unit/test_standardurl.js
+++ b/netwerk/test/unit/test_standardurl.js
@@ -77,26 +77,26 @@ add_test(function test_setQuery()
      /* And one that's nonempty but shorter than "foo" */
      ["http://example.com/?f#bar", "http://example.com/?foo#bar"],
      ["http://example.com/?f", "http://example.com/?foo"],
     ].map(pairToURLs);
 
   for (var [provided, target] of pairs) {
     symmetricEquality(false, provided, target);
 
-    provided.query = "foo";
+    provided = provided.mutate().setQuery("foo").finalize().QueryInterface(Ci.nsIURL);
 
     Assert.equal(provided.spec, target.spec);
     symmetricEquality(true, provided, target);
   }
 
   [provided, target] =
     ["http://example.com/#", "http://example.com/?foo#bar"].map(stringToURL);
   symmetricEquality(false, provided, target);
-  provided.query = "foo";
+  provided = provided.mutate().setQuery("foo").finalize().QueryInterface(Ci.nsIURL);
   symmetricEquality(false, provided, target);
 
   var newProvided = Components.classes["@mozilla.org/network/io-service;1"]
                               .getService(Components.interfaces.nsIIOService)
                               .newURI("#bar", null, provided)
                               .QueryInterface(Components.interfaces.nsIURL);
 
   Assert.equal(newProvided.spec, target.spec);
@@ -297,32 +297,32 @@ add_test(function test_hugeStringThrows(
 {
   let prefs = Cc["@mozilla.org/preferences-service;1"]
                 .getService(Ci.nsIPrefService);
   let maxLen = prefs.getIntPref("network.standard-url.max-length");
   let url = stringToURL("http://test:test@example.com");
 
   let hugeString = new Array(maxLen + 1).fill("a").join("");
   let properties = ["scheme",
-                    "host",
-                    "query"];
+                    "host"];
   for (let prop of properties) {
     Assert.throws(() => url[prop] = hugeString,
                   /NS_ERROR_MALFORMED_URI/,
                   `Passing a huge string to "${prop}" should throw`);
   }
 
   let setters = [
     { method: "setSpec", qi: Ci.nsIURIMutator },
     { method: "setUsername", qi: Ci.nsIURIMutator },
     { method: "setPassword", qi: Ci.nsIURIMutator },
     { method: "setFilePath", qi: Ci.nsIURIMutator },
     { method: "setHostPort", qi: Ci.nsIURIMutator },
     { method: "setUserPass", qi: Ci.nsIURIMutator },
     { method: "setPathQueryRef", qi: Ci.nsIURIMutator },
+    { method: "setQuery", qi: Ci.nsIURIMutator },
     { method: "setRef", qi: Ci.nsIURIMutator },
     { method: "setFileName", qi: Ci.nsIURLMutator },
     { method: "setFileExtension", qi: Ci.nsIURLMutator },
     { method: "setFileBaseName", qi: Ci.nsIURLMutator },
   ];
 
   for (let prop of setters) {
     Assert.throws(() => url = url.mutate().QueryInterface(prop.qi)[prop.method](hugeString).finalize(),
@@ -337,17 +337,17 @@ add_test(function test_filterWhitespace(
 {
   var url = stringToURL(" \r\n\th\nt\rt\tp://ex\r\n\tample.com/path\r\n\t/\r\n\tto the/fil\r\n\te.e\r\n\txt?que\r\n\try#ha\r\n\tsh \r\n\t ");
   Assert.equal(url.spec, "http://example.com/path/to%20the/file.ext?query#hash");
 
   // These setters should escape \r\n\t, not filter them.
   var url = stringToURL("http://test.com/path?query#hash");
   url = url.mutate().setFilePath("pa\r\n\tth").finalize();
   Assert.equal(url.spec, "http://test.com/pa%0D%0A%09th?query#hash");
-  url.query = "qu\r\n\tery";
+  url = url.mutate().setQuery("qu\r\n\tery").finalize();
   Assert.equal(url.spec, "http://test.com/pa%0D%0A%09th?qu%0D%0A%09ery#hash");
   url = url.mutate().setRef("ha\r\n\tsh").finalize();
   Assert.equal(url.spec, "http://test.com/pa%0D%0A%09th?qu%0D%0A%09ery#ha%0D%0A%09sh");
   url = url.mutate().QueryInterface(Ci.nsIURLMutator).setFileName("fi\r\n\tle.name").finalize();
   Assert.equal(url.spec, "http://test.com/fi%0D%0A%09le.name?qu%0D%0A%09ery#ha%0D%0A%09sh");
 
   run_next_test();
 });
@@ -406,17 +406,17 @@ add_test(function test_encode_C0_and_spa
     var url = stringToURL("http://example.com/pa" + String.fromCharCode(i) + "th?qu" + String.fromCharCode(i) +"ery#ha" + String.fromCharCode(i) + "sh");
     Assert.equal(url.spec, "http://example.com/pa%" + toHex(i) + "th?qu%" + toHex(i) + "ery#ha%" + toHex(i) + "sh");
   }
 
   // Additionally, we need to check the setters.
   var url = stringToURL("http://example.com/path?query#hash");
   url = url.mutate().setFilePath("pa\0th").finalize();
   Assert.equal(url.spec, "http://example.com/pa%00th?query#hash");
-  url.query = "qu\0ery";
+  url = url.mutate().setQuery("qu\0ery").finalize();
   Assert.equal(url.spec, "http://example.com/pa%00th?qu%00ery#hash");
   url = url.mutate().setRef("ha\0sh").finalize();
   Assert.equal(url.spec, "http://example.com/pa%00th?qu%00ery#ha%00sh");
   url = url.mutate().QueryInterface(Ci.nsIURLMutator).setFileName("fi\0le.name").finalize();
   Assert.equal(url.spec, "http://example.com/fi%00le.name?qu%00ery#ha%00sh");
 
   run_next_test();
 });
--- a/services/sync/modules/addonutils.js
+++ b/services/sync/modules/addonutils.js
@@ -267,17 +267,19 @@ AddonUtilsInternal.prototype = {
         function rewrite(param) {
 
         if (param.indexOf("src=") == 0) {
           return "src=sync";
         }
         return param;
       });
 
-      addon.sourceURI.query = params.join("&");
+      addon.sourceURI = addon.sourceURI.mutate()
+                                       .setQuery(params.join("&"))
+                                       .finalize();
     }
 
     if (!toInstall.length) {
       return ourResult;
     }
 
     const installPromises = [];
     // Start all the installs asynchronously. They will report back to us
--- a/services/sync/modules/record.js
+++ b/services/sync/modules/record.js
@@ -636,17 +636,19 @@ Collection.prototype = {
     }
     if (this._commit) {
       args.push("commit=true");
     }
     if (this._offset) {
       args.push("offset=" + encodeURIComponent(this._offset));
     }
 
-    this.uri.query = (args.length > 0) ? "?" + args.join("&") : "";
+    this.uri = this.uri.mutate()
+                       .setQuery((args.length > 0) ? "?" + args.join("&") : "")
+                       .finalize();
   },
 
   // get full items
   get full() { return this._full; },
   set full(value) {
     this._full = value;
     this._rebuildURL();
   },
--- a/services/sync/tests/unit/test_resource.js
+++ b/services/sync/tests/unit/test_resource.js
@@ -507,17 +507,20 @@ add_test(function test_uri_construction(
   args.push("sort=" + 1234);
 
   let query = "?" + args.join("&");
 
   let uri1 = CommonUtils.makeURI("http://foo/" + query)
                   .QueryInterface(Ci.nsIURL);
   let uri2 = CommonUtils.makeURI("http://foo/")
                   .QueryInterface(Ci.nsIURL);
-  uri2.query = query;
+  uri2 = uri2.mutate()
+             .setQuery(query)
+             .finalize()
+             .QueryInterface(Ci.nsIURL);
   Assert.equal(uri1.query, uri2.query);
 
   run_next_test();
 });
 
 /**
  * End of tests that rely on a single HTTP server.
  * All tests after this point must begin and end their own.
--- a/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm
+++ b/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm
@@ -68,17 +68,19 @@ var ImageCropper = {
     let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height;
     let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]);
 
     // If we have a local file that is not in progress, return it.
     if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) {
       let fileURI = Services.io.newFileURI(croppedFile);
 
       // Copy the query part to avoid wrong caching.
-      fileURI.QueryInterface(Ci.nsIURL).query = uri.query;
+      fileURI = fileURI.mutate()
+                       .setQuery(uri.query)
+                       .finalize();
       return fileURI.spec;
     }
 
     // Crop the given image in the background.
     this._crop(uri, croppedFile, aScreen, aOrigin);
 
     // Return the original image while we're waiting for the cropped version
     // to be written to disk.