Bug 1433958 - Change code that sets nsIURI.query to use nsIURIMutator r=mayhemer
authorValentin Gosu <valentin.gosu@gmail.com>
Mon, 26 Feb 2018 20:43:46 +0100
changeset 405496 2827f08f7b5327a530f8c5c9dbbd6cc14da6c2da
parent 405495 d39e5d03e44688a2900eee3a77bc8b248a8e76b1
child 405497 91477a3eabdfdc53f1c942ce61f1f754cb03d240
push id100235
push useraiakab@mozilla.com
push dateTue, 27 Feb 2018 10:06:18 +0000
treeherdermozilla-inbound@b9dc720c70b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer
bugs1433958
milestone60.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 1433958 - Change code that sets nsIURI.query to use nsIURIMutator r=mayhemer 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.