Bug 1310483 - Implement nsIURIWithQuery for having query part in simple URI. r=valentin, a=jcristau
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 14 Nov 2016 13:04:33 +0100
changeset 353425 a7d6c64a21d66ad653e820406aeb142b9c8402ed
parent 353424 bc05fa599f8c52fad1c8e5641fd48504ccc1dc39
child 353426 1b0e83800dc6432e697272d8d84c92049c47ca37
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin, jcristau
bugs1310483
milestone52.0a2
Bug 1310483 - Implement nsIURIWithQuery for having query part in simple URI. r=valentin, a=jcristau
dom/url/URL.cpp
dom/url/tests/test_url.html
ipc/glue/URIParams.ipdlh
modules/libjar/nsJARURI.cpp
modules/libjar/nsJARURI.h
netwerk/base/moz.build
netwerk/base/nsIURIWithQuery.idl
netwerk/base/nsIURL.idl
netwerk/base/nsSimpleURI.cpp
netwerk/base/nsSimpleURI.h
netwerk/base/nsStandardURL.cpp
netwerk/base/nsStandardURL.h
testing/marionette/navigate.js
testing/web-platform/meta/url/url-constructor.html.ini
--- a/dom/url/URL.cpp
+++ b/dom/url/URL.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/dom/URLBinding.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/ipc/nsIRemoteBlob.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "nsContentUtils.h"
 #include "nsEscape.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsIIOService.h"
+#include "nsIURIWithQuery.h"
 #include "nsIURL.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
@@ -516,65 +517,67 @@ URLMainThread::SetPort(const nsAString& 
   mURI->SetPort(port);
 }
 
 void
 URLMainThread::GetPathname(nsAString& aPathname, ErrorResult& aRv) const
 {
   aPathname.Truncate();
 
-  nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
-  if (!url) {
-    nsAutoCString path;
-    nsresult rv = mURI->GetPath(path);
-    if (NS_FAILED(rv)){
-      // Do not throw!  Not having a valid URI or URL should result in an empty
-      // string.
-      return;
+  // Do not throw!  Not having a valid URI or URL should result in an empty
+  // string.
+
+  nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(mURI));
+  if (url) {
+    nsAutoCString file;
+    nsresult rv = url->GetFilePath(file);
+    if (NS_SUCCEEDED(rv)) {
+      CopyUTF8toUTF16(file, aPathname);
     }
 
-    CopyUTF8toUTF16(path, aPathname);
     return;
   }
 
-  nsAutoCString file;
-  nsresult rv = url->GetFilePath(file);
+  nsAutoCString path;
+  nsresult rv = mURI->GetPath(path);
   if (NS_SUCCEEDED(rv)) {
-    CopyUTF8toUTF16(file, aPathname);
+    CopyUTF8toUTF16(path, aPathname);
   }
 }
 
 void
 URLMainThread::SetPathname(const nsAString& aPathname, ErrorResult& aRv)
 {
-  nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
-  if (!url) {
-    // Ignore failures to be compatible with NS4.
+  // Do not throw!
+
+  nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(mURI));
+  if (url) {
+    url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname));
     return;
   }
-
-  url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname));
 }
 
 void
 URLMainThread::GetSearch(nsAString& aSearch, ErrorResult& aRv) const
 {
   aSearch.Truncate();
 
-  nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
-  if (!url) {
-    // Do not throw!  Not having a valid URI or URL should result in an empty
-    // string.
-    return;
-  }
+  // Do not throw!  Not having a valid URI or URL should result in an empty
+  // string.
 
   nsAutoCString search;
-  nsresult rv = url->GetQuery(search);
-  if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
-    CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, aSearch);
+  nsresult rv;
+
+  nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(mURI));
+  if (url) {
+    rv = url->GetQuery(search);
+    if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
+      CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, aSearch);
+    }
+    return;
   }
 }
 
 void
 URLMainThread::GetHash(nsAString& aHash, ErrorResult& aRv) const
 {
   aHash.Truncate();
 
@@ -593,23 +596,23 @@ void
 URLMainThread::SetHash(const nsAString& aHash, ErrorResult& aRv)
 {
   mURI->SetRef(NS_ConvertUTF16toUTF8(aHash));
 }
 
 void
 URLMainThread::SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv)
 {
-  nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
-  if (!url) {
-    // Ignore failures to be compatible with NS4.
+  // Ignore failures to be compatible with NS4.
+
+  nsCOMPtr<nsIURIWithQuery> uriWithQuery(do_QueryInterface(mURI));
+  if (uriWithQuery) {
+    uriWithQuery->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
     return;
   }
-
-  url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
 }
 
 } // anonymous namespace
 
 ///////////////////////////////////////////////////////////////////////////////
 // URL for Workers
 ///////////////////////////////////////////////////////////////////////////////
 
--- a/dom/url/tests/test_url.html
+++ b/dom/url/tests/test_url.html
@@ -212,16 +212,26 @@
 
     { url: 'about:blank',
       base: undefined,
       error: false,
       protocol: 'about:',
       pathname: 'blank',
       skip_setters: false,
     },
+
+    { url: 'foo:bar?what#yeah',
+      base: undefined,
+      error: false,
+      protocol: 'foo:',
+      pathname: 'bar',
+      search: '?what',
+      hash: '#yeah',
+      skip_setters: false,
+    },
   ];
 
   while(tests.length) {
     var test = tests.shift();
 
     var error = false;
     var url;
     try {
@@ -382,10 +392,45 @@
     is(url.href, "ftp://tmp/test");
 
     url = new URL("ftp:\\\\tmp\\test", base);
     is(url.href, "ftp://tmp/test");
 
     url = new URL("scheme://tmp\\test", base);
     is(url.href, "scheme://tmp\\test");
   </script>
+
+  <script>
+    var url = new URL("scheme:path/to/file?query#hash");
+    is(url.href, "scheme:path/to/file?query#hash");
+    is(url.pathname, "path/to/file");
+    is(url.search, "?query");
+    is(url.hash, "#hash");
+
+    // pathname cannot be overwritten.
+    url.pathname = "new/path?newquery#newhash";
+    is(url.href, "scheme:path/to/file?query#hash");
+
+    url.search = "?newquery#newhash";
+    is(url.href, "scheme:path/to/file?newquery%23newhash#hash");
+
+    // nulls get encoded, whitespace gets stripped
+    url = new URL("scheme:pa\0\nth/to/fi\0\nle?qu\0\nery#ha\0\nsh");
+    is(url.href, "scheme:pa%00th/to/fi%00le?qu%00ery#ha%00sh");
+
+    url.search = "new\0\nquery";
+    is(url.href, "scheme:pa%00th/to/fi%00le?new%00%0Aquery#ha%00sh");
+    url.hash = "new\0\nhash";
+    is(url.href, "scheme:pa%00th/to/fi%00le?new%00%0Aquery#new%00%0Ahash");
+
+    url = new URL("scheme:path#hash");
+    is(url.href, "scheme:path#hash");
+    url.search = "query";
+    is(url.href, "scheme:path?query#hash");
+    url.hash = "";
+    is(url.href, "scheme:path?query");
+    url.hash = "newhash";
+    is(url.href, "scheme:path?query#newhash");
+    url.search = "";
+    is(url.href, "scheme:path#newhash");
+  </script>
 </body>
 </html>
--- a/ipc/glue/URIParams.ipdlh
+++ b/ipc/glue/URIParams.ipdlh
@@ -10,16 +10,17 @@ include PBackgroundSharedTypes;
 namespace mozilla {
 namespace ipc {
 
 struct SimpleURIParams
 {
   nsCString scheme;
   nsCString path;
   nsCString ref;
+  nsCString query;
   bool isMutable;
 };
 
 struct StandardURLSegment
 {
   uint32_t position;
   int32_t length;
 };
--- a/modules/libjar/nsJARURI.cpp
+++ b/modules/libjar/nsJARURI.cpp
@@ -36,16 +36,17 @@ nsJARURI::~nsJARURI()
 }
 
 // XXX Why is this threadsafe?
 NS_IMPL_ADDREF(nsJARURI)
 NS_IMPL_RELEASE(nsJARURI)
 NS_INTERFACE_MAP_BEGIN(nsJARURI)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJARURI)
   NS_INTERFACE_MAP_ENTRY(nsIURI)
+  NS_INTERFACE_MAP_ENTRY(nsIURIWithQuery)
   NS_INTERFACE_MAP_ENTRY(nsIURL)
   NS_INTERFACE_MAP_ENTRY(nsIJARURI)
   NS_INTERFACE_MAP_ENTRY(nsISerializable)
   NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
   NS_INTERFACE_MAP_ENTRY(nsINestedURI)
   NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI)
   // see nsJARURI::Equals
   if (aIID.Equals(NS_GET_IID(nsJARURI)))
--- a/modules/libjar/nsJARURI.h
+++ b/modules/libjar/nsJARURI.h
@@ -36,16 +36,17 @@ class nsJARURI final : public nsIJARURI,
                        public nsISerializable,
                        public nsIClassInfo,
                        public nsINestedURI,
                        public nsIIPCSerializableURI
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIURI
+    NS_DECL_NSIURIWITHQUERY
     NS_DECL_NSIURL
     NS_DECL_NSIJARURI
     NS_DECL_NSISERIALIZABLE
     NS_DECL_NSICLASSINFO
     NS_DECL_NSINESTEDURI
     NS_DECL_NSIIPCSERIALIZABLEURI
 
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_JARURI_IMPL_CID)
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -127,16 +127,17 @@ XPIDL_SOURCES += [
     'nsIUDPSocket.idl',
     'nsIUnicharStreamLoader.idl',
     'nsIUploadChannel.idl',
     'nsIUploadChannel2.idl',
     'nsIURI.idl',
     'nsIURIClassifier.idl',
     'nsIURIWithBlobImpl.idl',
     'nsIURIWithPrincipal.idl',
+    'nsIURIWithQuery.idl',
     'nsIURL.idl',
     'nsIURLParser.idl',
     'nsPILoadGroupInternal.idl',
     'nsPISocketTransportService.idl',
 ]
 
 if CONFIG['MOZ_TOOLKIT_SEARCH']:
     XPIDL_SOURCES += [
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIURIWithQuery.idl
@@ -0,0 +1,30 @@
+/* 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 "nsIURI.idl"
+
+/**
+ * nsIURIWithQuery is implemented by URIs which have a query parameter.
+ * This is useful for the URL API.
+ */
+[scriptable, uuid(367510ee-8556-435a-8f99-b5fd357e08cc)]
+interface nsIURIWithQuery : nsIURI
+{
+    /**
+     * Returns a path including the directory and file portions of a
+     * URL.  For example, the filePath of "http://host/foo/bar.html#baz"
+     * is "/foo/bar.html".
+     *
+     * Some characters may be escaped.
+     */
+    attribute AUTF8String filePath;
+
+    /**
+     * Returns the query portion (the part after the "?") of the URL.
+     * If there isn't one, an empty string is returned.
+     *
+     * Some characters may be escaped.
+     */
+    attribute AUTF8String query;
+};
--- a/netwerk/base/nsIURL.idl
+++ b/netwerk/base/nsIURL.idl
@@ -1,53 +1,40 @@
 /* -*- Mode: C++; tab-width: 2; 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 "nsIURI.idl"
+#include "nsIURIWithQuery.idl"
 
 /**
  * The nsIURL interface provides convenience methods that further
  * break down the path portion of nsIURI:
  *
  * http://host/directory/fileBaseName.fileExtension?query
  * http://host/directory/fileBaseName.fileExtension#ref
  *            \          \                       /
  *             \          -----------------------
  *              \                   |          /
  *               \               fileName     /
  *                ----------------------------
  *                            |
  *                        filePath
  */
 [scriptable, uuid(86adcd89-0b70-47a2-b0fe-5bb2c5f37e31)]
-interface nsIURL : nsIURI
+interface nsIURL : nsIURIWithQuery
 {
     /*************************************************************************
      * The URL path is broken down into the following principal components:
-     */
-
-    /**
-     * Returns a path including the directory and file portions of a
-     * URL.  For example, the filePath of "http://host/foo/bar.html#baz"
-     * is "/foo/bar.html".
      *
-     * Some characters may be escaped.
+     * attribute AUTF8String filePath;
+     * attribute AUTF8String query;
+     *
+     * These are inherited from nsIURIWithQuery.
      */
-    attribute AUTF8String filePath;
-
-    /**
-     * Returns the query portion (the part after the "?") of the URL.
-     * If there isn't one, an empty string is returned.
-     *
-     * Some characters may be escaped.
-     */
-    attribute AUTF8String query;
-
 
     /*************************************************************************
      * The URL filepath is broken down into the following sub-components:
      */
 
     /**
      * Returns the directory portion of a URL.  If the URL denotes a path to a
      * directory and not a file, e.g. http://host/foo/bar/, then the Directory
--- a/netwerk/base/nsSimpleURI.cpp
+++ b/netwerk/base/nsSimpleURI.cpp
@@ -30,30 +30,31 @@ namespace net {
 static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
                      NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
 static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID);
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsSimpleURI methods:
 
 nsSimpleURI::nsSimpleURI()
-    : mMutable(true),
-      mIsRefValid(false)
+    : mMutable(true)
+    , mIsRefValid(false)
+    , mIsQueryValid(false)
 {
 }
 
 nsSimpleURI::~nsSimpleURI()
 {
 }
 
 NS_IMPL_ADDREF(nsSimpleURI)
 NS_IMPL_RELEASE(nsSimpleURI)
 NS_INTERFACE_TABLE_HEAD(nsSimpleURI)
-NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsISerializable, nsIClassInfo,
-                    nsIMutable, nsIIPCSerializableURI)
+NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsIURIWithQuery, nsISerializable,
+                   nsIClassInfo, nsIMutable, nsIIPCSerializableURI)
 NS_INTERFACE_TABLE_TO_MAP_SEGUE
   if (aIID.Equals(kThisSimpleURIImplementationCID))
     foundInterface = static_cast<nsIURI*>(this);
   else
   NS_INTERFACE_MAP_ENTRY(nsISizeOf)
 NS_INTERFACE_MAP_END
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -82,16 +83,28 @@ nsSimpleURI::Read(nsIObjectInputStream* 
 
     if (isRefValid) {
         rv = aStream->ReadCString(mRef);
         if (NS_FAILED(rv)) return rv;
     } else {
         mRef.Truncate(); // invariant: mRef should be empty when it's not valid
     }
 
+    bool isQueryValid;
+    rv = aStream->ReadBoolean(&isQueryValid);
+    if (NS_FAILED(rv)) return rv;
+    mIsQueryValid = isQueryValid;
+
+    if (isQueryValid) {
+        rv = aStream->ReadCString(mQuery);
+        if (NS_FAILED(rv)) return rv;
+    } else {
+        mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+    }
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::Write(nsIObjectOutputStream* aStream)
 {
     nsresult rv;
 
@@ -107,35 +120,50 @@ nsSimpleURI::Write(nsIObjectOutputStream
     rv = aStream->WriteBoolean(mIsRefValid);
     if (NS_FAILED(rv)) return rv;
 
     if (mIsRefValid) {
         rv = aStream->WriteStringZ(mRef.get());
         if (NS_FAILED(rv)) return rv;
     }
 
+    rv = aStream->WriteBoolean(mIsQueryValid);
+    if (NS_FAILED(rv)) return rv;
+
+    if (mIsQueryValid) {
+        rv = aStream->WriteStringZ(mQuery.get());
+        if (NS_FAILED(rv)) return rv;
+    }
+
     return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIIPCSerializableURI methods:
 
 void
 nsSimpleURI::Serialize(URIParams& aParams)
 {
     SimpleURIParams params;
 
     params.scheme() = mScheme;
     params.path() = mPath;
+
     if (mIsRefValid) {
       params.ref() = mRef;
-    }
-    else {
+    } else {
       params.ref().SetIsVoid(true);
     }
+
+    if (mIsQueryValid) {
+      params.query() = mQuery;
+    } else {
+      params.query().SetIsVoid(true);
+    }
+
     params.isMutable() = mMutable;
 
     aParams = params;
 }
 
 bool
 nsSimpleURI::Deserialize(const URIParams& aParams)
 {
@@ -143,24 +171,33 @@ nsSimpleURI::Deserialize(const URIParams
         NS_ERROR("Received unknown parameters from the other process!");
         return false;
     }
 
     const SimpleURIParams& params = aParams.get_SimpleURIParams();
 
     mScheme = params.scheme();
     mPath = params.path();
+
     if (params.ref().IsVoid()) {
         mRef.Truncate();
         mIsRefValid = false;
-    }
-    else {
+    } else {
         mRef = params.ref();
         mIsRefValid = true;
     }
+
+    if (params.query().IsVoid()) {
+        mQuery.Truncate();
+        mIsQueryValid = false;
+    } else {
+        mQuery = params.query();
+        mIsQueryValid = true;
+    }
+
     mMutable = params.isMutable();
 
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIURI methods:
 
@@ -168,47 +205,60 @@ NS_IMETHODIMP
 nsSimpleURI::GetSpec(nsACString &result)
 {
     if (!result.Assign(mScheme, fallible) ||
         !result.Append(NS_LITERAL_CSTRING(":"), fallible) ||
         !result.Append(mPath, fallible)) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
+    if (mIsQueryValid) {
+        if (!result.Append(NS_LITERAL_CSTRING("?"), fallible) ||
+            !result.Append(mQuery, fallible)) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+    } else {
+        MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+    }
+
     if (mIsRefValid) {
         if (!result.Append(NS_LITERAL_CSTRING("#"), fallible) ||
             !result.Append(mRef, fallible)) {
             return NS_ERROR_OUT_OF_MEMORY;
         }
     } else {
         MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
     }
+
     return NS_OK;
 }
 
 // result may contain unescaped UTF-8 characters
 NS_IMETHODIMP
 nsSimpleURI::GetSpecIgnoringRef(nsACString &result)
 {
     result = mScheme + NS_LITERAL_CSTRING(":") + mPath;
+    if (mIsQueryValid) {
+        result += NS_LITERAL_CSTRING("?") + mQuery;
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetHasRef(bool *result)
 {
     *result = mIsRefValid;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetSpec(const nsACString &aSpec)
 {
     NS_ENSURE_STATE(mMutable);
-    
+
     // filter out unexpected chars "\r\n\t" if necessary
     nsAutoCString filteredSpec;
     net_FilterURIString(aSpec, filteredSpec);
 
     // nsSimpleURI currently restricts the charset to US-ASCII
     nsAutoCString spec;
     nsresult rv = NS_EscapeURL(filteredSpec, esc_OnlyNonASCII, spec, fallible);
     if (NS_FAILED(rv)) {
@@ -219,17 +269,17 @@ nsSimpleURI::SetSpec(const nsACString &a
     if (colonPos < 0 || !net_IsValidScheme(spec.get(), colonPos))
         return NS_ERROR_MALFORMED_URI;
 
     mScheme.Truncate();
     DebugOnly<int32_t> n = spec.Left(mScheme, colonPos);
     NS_ASSERTION(n == colonPos, "Left failed");
     ToLowerCase(mScheme);
 
-    // This sets both mPath and mRef.
+    // This sets mPath, mQuery and mRef.
     return SetPath(Substring(spec, colonPos + 1));
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetScheme(nsACString &result)
 {
     result = mScheme;
     return NS_OK;
@@ -275,48 +325,48 @@ nsSimpleURI::GetUsername(nsACString &res
 {
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetUsername(const nsACString &userName)
 {
     NS_ENSURE_STATE(mMutable);
-    
+
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetPassword(nsACString &result)
 {
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetPassword(const nsACString &password)
 {
     NS_ENSURE_STATE(mMutable);
-    
+
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetHostPort(nsACString &result)
 {
     // Note: Audit all callers before changing this to return an empty
     // string -- CAPS and UI code may depend on this throwing.
     // Note: If this is changed, change GetAsciiHostPort as well.
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetHostPort(const nsACString &result)
 {
     NS_ENSURE_STATE(mMutable);
-    
+
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetHostAndPort(const nsACString &result)
 {
     NS_ENSURE_STATE(mMutable);
 
@@ -330,100 +380,141 @@ nsSimpleURI::GetHost(nsACString &result)
     // string -- CAPS and UI code depend on this throwing.
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetHost(const nsACString &host)
 {
     NS_ENSURE_STATE(mMutable);
-    
+
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetPort(int32_t *result)
 {
     // Note: Audit all callers before changing this to return an empty
     // string -- CAPS and UI code may depend on this throwing.
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetPort(int32_t port)
 {
     NS_ENSURE_STATE(mMutable);
-    
+
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetPath(nsACString &result)
 {
     result = mPath;
+    if (mIsQueryValid) {
+        result += NS_LITERAL_CSTRING("?") + mQuery;
+    }
     if (mIsRefValid) {
         result += NS_LITERAL_CSTRING("#") + mRef;
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSimpleURI::SetPath(const nsACString &path)
+nsSimpleURI::SetPath(const nsACString &aPath)
 {
     NS_ENSURE_STATE(mMutable);
-    
+
+    nsAutoCString path(aPath);
+    int32_t queryPos = path.FindChar('?');
     int32_t hashPos = path.FindChar('#');
-    if (hashPos < 0) {
-        mIsRefValid = false;
-        mRef.Truncate(); // invariant: mRef should be empty when it's not valid
-        if (!mPath.Assign(path, fallible)) {
-            return NS_ERROR_OUT_OF_MEMORY;
-        }
-        return NS_OK;
+
+    if (queryPos != kNotFound && hashPos != kNotFound && hashPos < queryPos) {
+        queryPos = kNotFound;
+    }
+
+    nsAutoCString query;
+    if (queryPos != kNotFound) {
+        query.Assign(Substring(path, queryPos));
+        path.Truncate(queryPos);
     }
 
-    mPath = StringHead(path, hashPos);
-    return SetRef(Substring(path, uint32_t(hashPos)));
+    nsAutoCString hash;
+    if (hashPos != kNotFound) {
+        if (query.IsEmpty()) {
+            hash.Assign(Substring(path, hashPos));
+            path.Truncate(hashPos);
+        } else {
+            // We have to search the hash character in the query
+            hashPos = query.FindChar('#');
+            hash.Assign(Substring(query, hashPos));
+            query.Truncate(hashPos);
+        }
+    }
+
+    mIsQueryValid = false;
+    mQuery.Truncate();
+
+    mIsRefValid = false;
+    mRef.Truncate();
+
+    // The path
+    if (!mPath.Assign(path, fallible)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    nsresult rv = SetQuery(query);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
+    return SetRef(hash);
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetRef(nsACString &result)
 {
     if (!mIsRefValid) {
-      MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
-      result.Truncate();
+        MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+        result.Truncate();
     } else {
-      result = mRef;
+        result = mRef;
     }
 
     return NS_OK;
 }
 
 // NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
 // string (and will result in .spec and .path having a terminal #).
 NS_IMETHODIMP
 nsSimpleURI::SetRef(const nsACString &aRef)
 {
     NS_ENSURE_STATE(mMutable);
 
-    if (aRef.IsEmpty()) {
-      // Empty string means to remove ref completely.
-      mIsRefValid = false;
-      mRef.Truncate(); // invariant: mRef should be empty when it's not valid
-      return NS_OK;
+    nsAutoCString ref;
+    nsresult rv = NS_EscapeURL(aRef, esc_OnlyNonASCII, ref, fallible);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
+    if (ref.IsEmpty()) {
+        // Empty string means to remove ref completely.
+        mIsRefValid = false;
+        mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+        return NS_OK;
     }
 
     mIsRefValid = true;
 
     // Gracefully skip initial hash
-    if (aRef[0] == '#') {
-        mRef = Substring(aRef, 1);
+    if (ref[0] == '#') {
+        mRef = Substring(ref, 1);
     } else {
-        mRef = aRef;
+        mRef = ref;
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::Equals(nsIURI* other, bool *result)
 {
@@ -457,16 +548,21 @@ nsSimpleURI::EqualsInternal(nsIURI* othe
 }
 
 bool
 nsSimpleURI::EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode)
 {
     bool result = (mScheme == otherUri->mScheme &&
                    mPath   == otherUri->mPath);
 
+    if (result) {
+        result = (mIsQueryValid == otherUri->mIsQueryValid &&
+                  (!mIsQueryValid || mQuery == otherUri->mQuery));
+    }
+
     if (result && refHandlingMode == eHonorRef) {
         result = (mIsRefValid == otherUri->mIsRefValid &&
                   (!mIsRefValid || mRef == otherUri->mRef));
     }
 
     return result;
 }
 
@@ -537,22 +633,27 @@ nsSimpleURI::CloneInternal(nsSimpleURI::
     if (!url)
         return NS_ERROR_OUT_OF_MEMORY;
 
     // Note: |url| may well have mMutable false at this point, so
     // don't call any setter methods.
     url->mScheme = mScheme;
     url->mPath = mPath;
 
+    url->mIsQueryValid = mIsQueryValid;
+    if (url->mIsQueryValid) {
+      url->mQuery = mQuery;
+    }
+
     url.forget(result);
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSimpleURI::Resolve(const nsACString &relativePath, nsACString &result) 
+nsSimpleURI::Resolve(const nsACString &relativePath, nsACString &result)
 {
     result = relativePath;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::GetAsciiSpec(nsACString &result)
 {
@@ -583,66 +684,66 @@ nsSimpleURI::GetOriginCharset(nsACString
     result.Truncate();
     return NS_OK;
 }
 
 //----------------------------------------------------------------------------
 // nsSimpleURI::nsIClassInfo
 //----------------------------------------------------------------------------
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsSimpleURI::GetInterfaces(uint32_t *count, nsIID * **array)
 {
     *count = 0;
     *array = nullptr;
     return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsSimpleURI::GetScriptableHelper(nsIXPCScriptable **_retval)
 {
     *_retval = nullptr;
     return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsSimpleURI::GetContractID(char * *aContractID)
 {
     // Make sure to modify any subclasses as needed if this ever
     // changes.
     *aContractID = nullptr;
     return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsSimpleURI::GetClassDescription(char * *aClassDescription)
 {
     *aClassDescription = nullptr;
     return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsSimpleURI::GetClassID(nsCID * *aClassID)
 {
     // Make sure to modify any subclasses as needed if this ever
     // changes to not call the virtual GetClassIDNoAlloc.
     *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
     if (!*aClassID)
         return NS_ERROR_OUT_OF_MEMORY;
     return GetClassIDNoAlloc(*aClassID);
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsSimpleURI::GetFlags(uint32_t *aFlags)
 {
     *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
     return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsSimpleURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
 {
     *aClassIDNoAlloc = kSimpleURICID;
     return NS_OK;
 }
 
 //----------------------------------------------------------------------------
 // nsSimpleURI::nsISimpleURI
@@ -662,23 +763,83 @@ nsSimpleURI::SetMutable(bool value)
     mMutable = value;
     return NS_OK;
 }
 
 //----------------------------------------------------------------------------
 // nsSimpleURI::nsISizeOf
 //----------------------------------------------------------------------------
 
-size_t 
+size_t
 nsSimpleURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
          mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+         mQuery.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
          mRef.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
 }
 
 size_t
 nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
 }
 
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsIURIWithQuery
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSimpleURI::GetFilePath(nsACString& aFilePath)
+{
+    aFilePath = mPath;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetFilePath(const nsACString& aFilePath)
+{
+    return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetQuery(nsACString& aQuery)
+{
+    if (!mIsQueryValid) {
+        MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+        aQuery.Truncate();
+    } else {
+        aQuery = mQuery;
+    }
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetQuery(const nsACString& aQuery)
+{
+    NS_ENSURE_STATE(mMutable);
+
+    nsAutoCString query;
+    nsresult rv = NS_EscapeURL(aQuery, esc_Query, query, fallible);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
+    if (query.IsEmpty()) {
+        // Empty string means to remove ref completely.
+        mIsQueryValid = false;
+        mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+        return NS_OK;
+    }
+
+    mIsQueryValid = true;
+
+    // Gracefully skip initial hash
+    if (query[0] == '?') {
+        mQuery = Substring(query, 1);
+    } else {
+        mQuery = query;
+    }
+
+    return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/base/nsSimpleURI.h
+++ b/netwerk/base/nsSimpleURI.h
@@ -3,16 +3,17 @@
  * 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/. */
 
 #ifndef nsSimpleURI_h__
 #define nsSimpleURI_h__
 
 #include "mozilla/MemoryReporting.h"
 #include "nsIURI.h"
+#include "nsIURIWithQuery.h"
 #include "nsISerializable.h"
 #include "nsString.h"
 #include "nsIClassInfo.h"
 #include "nsIMutable.h"
 #include "nsISizeOf.h"
 #include "nsIIPCSerializableURI.h"
 
 namespace mozilla {
@@ -21,29 +22,31 @@ namespace net {
 #define NS_THIS_SIMPLEURI_IMPLEMENTATION_CID         \
 { /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */         \
     0x0b9bb0c2,                                      \
     0xfee6,                                          \
     0x470b,                                          \
     {0xb9, 0xb9, 0x9f, 0xd9, 0x46, 0x2b, 0x5e, 0x19} \
 }
 
-class nsSimpleURI : public nsIURI,
-                    public nsISerializable,
-                    public nsIClassInfo,
-                    public nsIMutable,
-                    public nsISizeOf,
-                    public nsIIPCSerializableURI
+class nsSimpleURI
+    : public nsIURIWithQuery
+    , public nsISerializable
+    , public nsIClassInfo
+    , public nsIMutable
+    , public nsISizeOf
+    , public nsIIPCSerializableURI
 {
 protected:
     virtual ~nsSimpleURI();
 
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIURI
+    NS_DECL_NSIURIWITHQUERY
     NS_DECL_NSISERIALIZABLE
     NS_DECL_NSICLASSINFO
     NS_DECL_NSIMUTABLE
     NS_DECL_NSIIPCSERIALIZABLEURI
 
     // nsSimpleURI methods:
 
     nsSimpleURI();
@@ -90,16 +93,18 @@ protected:
     // Helper to share code between Clone methods.
     virtual nsresult CloneInternal(RefHandlingEnum refHandlingMode,
                                    const nsACString &newRef,
                                    nsIURI** clone);
     
     nsCString mScheme;
     nsCString mPath; // NOTE: mPath does not include ref, as an optimization
     nsCString mRef;  // so that URIs with different refs can share string data.
+    nsCString mQuery;  // so that URLs with different querys can share string data.
     bool mMutable;
     bool mIsRefValid; // To distinguish between empty-ref and no-ref.
+    bool mIsQueryValid; // To distinguish between empty-query and no-query.
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsSimpleURI_h__
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -1186,16 +1186,17 @@ SHIFT_FROM_LAST(ShiftFromRef, mRef)
 //----------------------------------------------------------------------------
 
 NS_IMPL_ADDREF(nsStandardURL)
 NS_IMPL_RELEASE(nsStandardURL)
 
 NS_INTERFACE_MAP_BEGIN(nsStandardURL)
     NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
     NS_INTERFACE_MAP_ENTRY(nsIURI)
+    NS_INTERFACE_MAP_ENTRY(nsIURIWithQuery)
     NS_INTERFACE_MAP_ENTRY(nsIURL)
     NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
     NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
     NS_INTERFACE_MAP_ENTRY(nsISerializable)
     NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
     NS_INTERFACE_MAP_ENTRY(nsIMutable)
     NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI)
     NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
--- a/netwerk/base/nsStandardURL.h
+++ b/netwerk/base/nsStandardURL.h
@@ -49,16 +49,17 @@ class nsStandardURL : public nsIFileURL
                     , public nsISensitiveInfoHiddenURI
 {
 protected:
     virtual ~nsStandardURL();
 
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIURI
+    NS_DECL_NSIURIWITHQUERY
     NS_DECL_NSIURL
     NS_DECL_NSIFILEURL
     NS_DECL_NSISTANDARDURL
     NS_DECL_NSISERIALIZABLE
     NS_DECL_NSICLASSINFO
     NS_DECL_NSIMUTABLE
     NS_DECL_NSIIPCSERIALIZABLEURI
     NS_DECL_NSISENSITIVEINFOHIDDENURI
--- a/testing/marionette/navigate.js
+++ b/testing/marionette/navigate.js
@@ -97,28 +97,23 @@ navigate.isLoadEventExpected = function 
 navigate.IdempotentURL = function (o) {
   let url = new URL(o);
 
   let hash = url.hash;
   if (hash == "" && url.href[url.href.length - 1] == "#") {
     hash = "#";
   }
 
-  let pathname = url.pathname;
-  if (url.protocol == "data:" && hash != "") {
-    pathname = pathname.substring(0, pathname.length - hash.length);
-  }
-
   return {
     hash: hash,
     host: url.host,
     hostname: url.hostname,
     href: url.href,
     origin: url.origin,
     password: url.password,
-    pathname: pathname,
+    pathname: url.pathname,
     port: url.port,
     protocol: url.protocol,
     search: url.search,
     searchParams: url.searchParams,
     username: url.username,
   };
 };
--- a/testing/web-platform/meta/url/url-constructor.html.ini
+++ b/testing/web-platform/meta/url/url-constructor.html.ini
@@ -37,19 +37,16 @@
     expected: FAIL
 
   [Parsing: <data:example.com/> against <http://example.org/foo/bar>]
     expected: FAIL
 
   [Parsing: <#β> against <http://example.org/foo/bar>]
     expected: FAIL
 
-  [Parsing: <data:text/html,test#test> against <http://example.org/foo/bar>]
-    expected: FAIL
-
   [Parsing: <file:c:\\foo\\bar.html> against <file:///tmp/mock/path>]
     expected: FAIL
 
   [Parsing: <  File:c|////foo\\bar.html> against <file:///tmp/mock/path>]
     expected: FAIL
 
   [Parsing: <C|/foo/bar> against <file:///tmp/mock/path>]
     expected: FAIL
@@ -148,31 +145,16 @@
     expected: FAIL
 
   [Parsing: <http://192.168.0.257> against <http://other.com/>]
     expected: FAIL
 
   [Parsing: <http://%3g%78%63%30%2e%30%32%35%30%2E.01> against <http://other.com/>]
     expected: FAIL
 
-  [Parsing: <#> against <test:test>]
-    expected: FAIL
-
-  [Parsing: <#x> against <mailto:x@x.com>]
-    expected: FAIL
-
-  [Parsing: <#x> against <data:,>]
-    expected: FAIL
-
-  [Parsing: <#x> against <about:blank>]
-    expected: FAIL
-
-  [Parsing: <#> against <test:test?test>]
-    expected: FAIL
-
   [Parsing: <i> against <sc:/pa/pa>]
     expected: FAIL
 
   [Parsing: <i> against <sc://ho/pa>]
     expected: FAIL
 
   [Parsing: <i> against <sc:///pa/pa>]
     expected: FAIL
@@ -199,25 +181,16 @@
     expected: FAIL
 
   [Parsing: <?i> against <sc://ho/pa>]
     expected: FAIL
 
   [Parsing: <?i> against <sc:///pa/pa>]
     expected: FAIL
 
-  [Parsing: <#i> against <sc:sd>]
-    expected: FAIL
-
-  [Parsing: <#i> against <sc:sd/sd>]
-    expected: FAIL
-
-  [Parsing: <#i> against <sc:/pa/pa>]
-    expected: FAIL
-
   [Parsing: <#i> against <sc://ho/pa>]
     expected: FAIL
 
   [Parsing: <#i> against <sc:///pa/pa>]
     expected: FAIL
 
   [Parsing: <about:/../> against <about:blank>]
     expected: FAIL