Bug 887836 - URLQuery object, r=khuey
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 12 Dec 2013 19:30:10 +0000
changeset 160256 0e3beadca4ba0ae060e452eaeb443e01f79ca140
parent 160255 638f72f30d5e2203d8a9450d10836c3bb9863938
child 160257 058037e3baf4a6907cc5c6eab613d8456dd8eaeb
push id25827
push userkwierso@gmail.com
push dateFri, 13 Dec 2013 03:13:04 +0000
treeherdermozilla-central@1bc33fa19b24 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs887836
milestone29.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 887836 - URLQuery object, r=khuey
dom/base/URLSearchParams.cpp
dom/base/URLSearchParams.h
dom/base/moz.build
dom/base/test/mochitest.ini
dom/base/test/test_urlSearchParams.html
dom/bindings/Bindings.conf
dom/webidl/URLSearchParams.webidl
dom/webidl/URLUtils.webidl
dom/webidl/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/base/URLSearchParams.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "URLSearchParams.h"
+#include "mozilla/dom/URLSearchParamsBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+URLSearchParams::URLSearchParams()
+{
+}
+
+URLSearchParams::~URLSearchParams()
+{
+  DeleteAll();
+}
+
+JSObject*
+URLSearchParams::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return URLSearchParamsBinding::Wrap(aCx, aScope, this);
+}
+
+/* static */ already_AddRefed<URLSearchParams>
+URLSearchParams::Constructor(const GlobalObject& aGlobal,
+                             const nsAString& aInit,
+                             ErrorResult& aRv)
+{
+  nsRefPtr<URLSearchParams> sp = new URLSearchParams();
+  sp->ParseInput(aInit);
+  return sp.forget();
+}
+
+/* static */ already_AddRefed<URLSearchParams>
+URLSearchParams::Constructor(const GlobalObject& aGlobal,
+                             URLSearchParams& aInit,
+                             ErrorResult& aRv)
+{
+  nsRefPtr<URLSearchParams> sp = new URLSearchParams();
+  aInit.mSearchParams.EnumerateRead(CopyEnumerator, sp);
+  return sp.forget();
+}
+
+void
+URLSearchParams::ParseInput(const nsAString& aInput)
+{
+  nsAString::const_iterator start, end;
+  aInput.BeginReading(start);
+  aInput.EndReading(end);
+  nsAString::const_iterator iter(start);
+
+  while (start != end) {
+    nsAutoString string;
+
+    if (FindCharInReadable('&', iter, end)) {
+      string.Assign(Substring(start, iter));
+      start = ++iter;
+    } else {
+      string.Assign(Substring(start, end));
+      start = end;
+    }
+
+    if (string.IsEmpty()) {
+      continue;
+    }
+
+    nsAString::const_iterator eqStart, eqEnd;
+    string.BeginReading(eqStart);
+    string.EndReading(eqEnd);
+    nsAString::const_iterator eqIter(eqStart);
+
+    nsAutoString name;
+    nsAutoString value;
+
+    if (FindCharInReadable('=', eqIter, eqEnd)) {
+      name.Assign(Substring(eqStart, eqIter));
+
+      ++eqIter;
+      value.Assign(Substring(eqIter, eqEnd));
+    } else {
+      name.Assign(string);
+    }
+
+    nsAutoString decodedName;
+    DecodeString(name, decodedName);
+
+    nsAutoString decodedValue;
+    DecodeString(value, decodedValue);
+
+    Append(decodedName, decodedValue);
+  }
+}
+
+void
+URLSearchParams::DecodeString(const nsAString& aInput, nsAString& aOutput)
+{
+  nsAString::const_iterator start, end;
+  aInput.BeginReading(start);
+  aInput.EndReading(end);
+
+  while (start != end) {
+    // replace '+' with U+0020
+    if (*start == '+') {
+      aOutput.Append(' ');
+      ++start;
+      continue;
+    }
+
+    // Percent decode algorithm
+    if (*start == '%') {
+      nsAString::const_iterator first(start);
+      ++first;
+
+      nsAString::const_iterator second(first);
+      ++second;
+
+#define ASCII_HEX_DIGIT( x )    \
+  ((x >= 0x41 && x <= 0x46) ||  \
+   (x >= 0x61 && x <= 0x66) ||  \
+   (x >= 0x30 && x <= 0x39))
+
+#define HEX_DIGIT( x )              \
+   (*x >= 0x30 && *x <= 0x39        \
+     ? *x - 0x30                    \
+     : (*x >= 0x41 && *x <= 0x46    \
+        ? *x - 0x37                 \
+        : *x - 0x57))
+
+      if (first != end && second != end &&
+          ASCII_HEX_DIGIT(*first) && ASCII_HEX_DIGIT(*second)) {
+        aOutput.Append(HEX_DIGIT(first) * 16 + HEX_DIGIT(second));
+        start = ++second;
+        continue;
+
+      } else {
+        aOutput.Append('%');
+        ++start;
+        continue;
+      }
+    }
+
+    aOutput.Append(*start);
+    ++start;
+  }
+}
+
+/* static */ PLDHashOperator
+URLSearchParams::CopyEnumerator(const nsAString& aName,
+                                nsTArray<nsString>* aArray,
+                                void *userData)
+{
+  URLSearchParams* aSearchParams = static_cast<URLSearchParams*>(userData);
+
+  nsTArray<nsString>* newArray = new nsTArray<nsString>();
+  newArray->AppendElements(*aArray);
+
+  aSearchParams->mSearchParams.Put(aName, newArray);
+  return PL_DHASH_NEXT;
+}
+
+void
+URLSearchParams::Get(const nsAString& aName, nsString& aRetval)
+{
+  nsTArray<nsString>* array;
+  if (!mSearchParams.Get(aName, &array)) {
+    aRetval.Truncate();
+    return;
+  }
+
+  aRetval.Assign(array->ElementAt(0));
+}
+
+void
+URLSearchParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
+{
+  nsTArray<nsString>* array;
+  if (!mSearchParams.Get(aName, &array)) {
+    return;
+  }
+
+  aRetval.AppendElements(*array);
+}
+
+void
+URLSearchParams::Set(const nsAString& aName, const nsAString& aValue)
+{
+  nsTArray<nsString>* array;
+  if (!mSearchParams.Get(aName, &array)) {
+    array = new nsTArray<nsString>();
+    array->AppendElement(aValue);
+    mSearchParams.Put(aName, array);
+  } else {
+    array->ElementAt(0) = aValue;
+  }
+}
+
+void
+URLSearchParams::Append(const nsAString& aName, const nsAString& aValue)
+{
+  nsTArray<nsString>* array;
+  if (!mSearchParams.Get(aName, &array)) {
+    array = new nsTArray<nsString>();
+    mSearchParams.Put(aName, array);
+  }
+
+  array->AppendElement(aValue);
+}
+
+bool
+URLSearchParams::Has(const nsAString& aName)
+{
+  return mSearchParams.Get(aName, nullptr);
+}
+
+void
+URLSearchParams::Delete(const nsAString& aName)
+{
+  nsTArray<nsString>* array;
+  if (!mSearchParams.Get(aName, &array)) {
+    return;
+  }
+
+  mSearchParams.Remove(aName);
+}
+
+void
+URLSearchParams::DeleteAll()
+{
+  mSearchParams.Clear();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/URLSearchParams.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_URLSearchParams_h
+#define mozilla_dom_URLSearchParams_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ErrorResult.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+
+namespace mozilla {
+namespace dom {
+
+class URLSearchParams MOZ_FINAL
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(URL)
+
+  URLSearchParams();
+  ~URLSearchParams();
+
+  // WebIDL methods
+  nsISupports* GetParentObject() const
+  {
+    return nullptr;
+  }
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope);
+
+  static already_AddRefed<URLSearchParams>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aInit,
+              ErrorResult& aRv);
+
+  static already_AddRefed<URLSearchParams>
+  Constructor(const GlobalObject& aGlobal, URLSearchParams& aInit,
+              ErrorResult& aRv);
+
+  void Get(const nsAString& aName, nsString& aRetval);
+
+  void GetAll(const nsAString& aName, nsTArray<nsString >& aRetval);
+
+  void Set(const nsAString& aName, const nsAString& aValue);
+
+  void Append(const nsAString& aName, const nsAString& aValue);
+
+  bool Has(const nsAString& aName);
+
+  void Delete(const nsAString& aName);
+
+  uint32_t Size() const
+  {
+    return mSearchParams.Count();
+  }
+
+private:
+  void ParseInput(const nsAString& aInput);
+
+  void DeleteAll();
+
+  void DecodeString(const nsAString& aInput, nsAString& aOutput);
+
+  static PLDHashOperator
+  CopyEnumerator(const nsAString& aName, nsTArray<nsString>* aArray,
+                 void *userData);
+
+  nsClassHashtable<nsStringHashKey, nsTArray<nsString>> mSearchParams;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_URLSearchParams_h */
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -57,16 +57,17 @@ EXPORTS.mozilla.dom += [
     'MessageChannel.h',
     'MessagePort.h',
     'MessagePortList.h',
     'Navigator.h',
     'ScreenOrientation.h',
     'ScriptSettings.h',
     'StructuredCloneTags.h',
     'URL.h',
+    'URLSearchParams.h',
 ]
 
 UNIFIED_SOURCES += [
     'BarProps.cpp',
     'CompositionStringSynthesizer.cpp',
     'Crypto.cpp',
     'DOMCursor.cpp',
     'DOMError.cpp',
@@ -92,16 +93,17 @@ UNIFIED_SOURCES += [
     'nsScreen.cpp',
     'nsScriptNameSpaceManager.cpp',
     'nsStructuredCloneContainer.cpp',
     'nsWindowMemoryReporter.cpp',
     'nsWindowRoot.cpp',
     'nsWrapperCache.cpp',
     'ScriptSettings.cpp',
     'URL.cpp',
+    'URLSearchParams.cpp',
     'WindowNamedPropertiesHandler.cpp',
 ]
 
 # these files couldn't be in UNIFIED_SOURCES for now for reasons given below:
 SOURCES += [
     # this file doesn't like windows.h
     'MessagePort.cpp',
     # this file doesn't like windows.h
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -39,8 +39,9 @@ support-files =
 [test_window_enumeration.html]
 [test_window_extensible.html]
 [test_window_indexing.html]
 [test_writable-replaceable.html]
 [test_urlExceptions.html]
 [test_openDialogChromeOnly.html]
 [test_messagemanager_targetchain.html]
 [test_url_empty_port.html]
+[test_urlSearchParams.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_urlSearchParams.html
@@ -0,0 +1,141 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=887836
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 887836</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887836">Mozilla Bug 887836</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <iframe name="x" id="x"></iframe>
+  <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+  <script type="application/javascript">
+
+  /** Test for Bug 887836 **/
+  ok("URLSearchParams" in window, "window.URLSearchParams exists");
+
+  function testSimpleURLSearchParams() {
+    var u = new URLSearchParams();
+    ok(u, "URLSearchParams created");
+    is(u.has('foo'), false, 'URLSearchParams.has(foo)');
+    is(u.get('foo'), '', 'URLSearchParams.get(foo)');
+    is(u.getAll('foo').length, 0, 'URLSearchParams.getAll(foo)');
+    is(u.size, 0, 'URLSearchParams.size()');
+
+    u.append('foo', 'bar');
+    is(u.has('foo'), true, 'URLSearchParams.has(foo)');
+    is(u.get('foo'), 'bar', 'URLSearchParams.get(foo)');
+    is(u.getAll('foo').length, 1, 'URLSearchParams.getAll(foo)');
+    is(u.size, 1, 'URLSearchParams.size()');
+
+    u.set('foo', 'bar2');
+    is(u.get('foo'), 'bar2', 'URLSearchParams.get(foo)');
+    is(u.getAll('foo').length, 1, 'URLSearchParams.getAll(foo)');
+    is(u.size, 1, 'URLSearchParams.size()');
+
+    u.delete('foo');
+    is(u.size, 0, 'URLSearchParams.size()');
+
+    runTest();
+  }
+
+  function testCopyURLSearchParams() {
+    var u = new URLSearchParams();
+    ok(u, "URLSearchParams created");
+    u.append('foo', 'bar');
+    is(u.size, 1, "u.size()");
+
+    var uu = new URLSearchParams(u);
+    is(uu.size, 1, "uu.size()");
+    is(uu.get('foo'), 'bar', 'uu.get()');
+
+    u.append('foo', 'bar2');
+    is(u.getAll('foo').length, 2, "u.getAll()");
+    is(uu.getAll('foo').length, 1, "uu.getAll()");
+
+    runTest();
+  }
+
+  function testParserURLSearchParams() {
+    var checks = [
+      { input: '', data: {} },
+      { input: 'a', data: { 'a' : [''] } },
+      { input: 'a=b', data: { 'a' : ['b'] } },
+      { input: 'a=', data: { 'a' : [''] } },
+      { input: '=b', data: { '' : ['b'] } },
+      { input: '&', data: {} },
+      { input: '&a', data: { 'a' : [''] } },
+      { input: 'a&', data: { 'a' : [''] } },
+      { input: 'a&a', data: { 'a' : ['', ''] } },
+      { input: 'a&b&c', data: { 'a' : [''], 'b' : [''], 'c' : [''] } },
+      { input: 'a=b&c=d', data: { 'a' : ['b'], 'c' : ['d'] } },
+      { input: 'a=b&c=d&', data: { 'a' : ['b'], 'c' : ['d'] } },
+      { input: '&&&a=b&&&&c=d&', data: { 'a' : ['b'], 'c' : ['d'] } },
+      { input: 'a=a&a=b&a=c', data: { 'a' : ['a', 'b', 'c'] } },
+      { input: 'a==a', data: { 'a' : ['=a'] } },
+      { input: 'a=a+b+c+d', data: { 'a' : ['a b c d'] } },
+      { input: '%=a', data: { '%' : ['a'] } },
+      { input: '%a=a', data: { '%a' : ['a'] } },
+      { input: '%a_=a', data: { '%a_' : ['a'] } },
+      { input: '%61=a', data: { 'a' : ['a'] } },
+      { input: '%=a', data: { '%' : ['a'] } },
+      { input: '%a=a', data: { '%a' : ['a'] } },
+      { input: '%a_=a', data: { '%a_' : ['a'] } },
+      { input: '%61=a', data: { 'a' : ['a'] } },
+      { input: '%61+%4d%4D=', data: { 'a MM' : [''] } },
+    ];
+
+    for (var i = 0; i < checks.length; ++i) {
+      var u = new URLSearchParams(checks[i].input);
+
+      var count = 0;
+      for (var key in checks[i].data) {
+        ++count;
+        ok(u.has(key), "key " + key + " found");
+
+        var all = u.getAll(key);
+        is(all.length, checks[i].data[key].length, "same number of elements");
+
+        for (var k = 0; k < all.length; ++k) {
+          is(all[k], checks[i].data[key][k], "value matches");
+        }
+      }
+
+      is(u.size, count, "size matches");
+    }
+
+    runTest();
+  }
+
+  var tests = [
+    testSimpleURLSearchParams,
+    testCopyURLSearchParams,
+    testParserURLSearchParams
+  ];
+
+  function runTest() {
+    if (!tests.length) {
+      SimpleTest.finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  runTest();
+
+  </script>
+</body>
+</html>
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1285,16 +1285,21 @@ DOMInterfaces = {
     'wrapperCache': False,
 },
 {
     'workers': True,
     'wrapperCache': False,
     'nativeOwnership': 'owned',
 }],
 
+'URLSearchParams' : {
+    'wrapperCache' : False,
+    'nativeOwnership': 'refcounted',
+},
+
 'VTTCue': {
     'nativeType': 'mozilla::dom::TextTrackCue'
 },
 
 'VTTRegion': {
   'nativeType': 'mozilla::dom::TextTrackRegion',
 },
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/URLSearchParams.webidl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * http://url.spec.whatwg.org/#urlsearchparams
+ *
+ * To the extent possible under law, the editors have waived all copyright
+ * and related or neighboring rights to this work. In addition, as of 17
+ * February 2013, the editors have made this specification available under
+ * the Open Web Foundation Agreement Version 1.0, which is available at
+ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.
+ */
+
+[Constructor(optional DOMString init = ""),
+ Constructor(URLSearchParams init)]
+interface URLSearchParams {
+  DOMString? get(DOMString name);
+  sequence<DOMString> getAll(DOMString name);
+  void set(DOMString name, DOMString value);
+  void append(DOMString name, DOMString value);
+  boolean has(DOMString name);
+  void delete(DOMString name);
+  readonly attribute unsigned long size;
+};
--- a/dom/webidl/URLUtils.webidl
+++ b/dom/webidl/URLUtils.webidl
@@ -22,12 +22,12 @@ interface URLUtils {
            attribute DOMString protocol;
            attribute DOMString username;
            attribute DOMString password;
            attribute DOMString host;
            attribute DOMString hostname;
            attribute DOMString port;
            attribute DOMString pathname;
            attribute DOMString search;
-           // attribute URLQuery? query;
+           // attribute URLSearchParams? searchParams;
            attribute DOMString hash;
 };
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -402,16 +402,17 @@ WEBIDL_FILES = [
     'TouchEvent.webidl',
     'TouchList.webidl',
     'TransitionEvent.webidl',
     'TreeColumns.webidl',
     'TreeWalker.webidl',
     'UIEvent.webidl',
     'UndoManager.webidl',
     'URL.webidl',
+    'URLSearchParams.webidl',
     'URLUtils.webidl',
     'URLUtilsReadOnly.webidl',
     'ValidityState.webidl',
     'VideoPlaybackQuality.webidl',
     'VideoStreamTrack.webidl',
     'VTTCue.webidl',
     'VTTRegion.webidl',
     'VTTRegionList.webidl',