Implement 'URLSearchParams'

This patch implements the 'URLSearchParams' interface (https://url.spec.whatwg.org/#interface-urlsearchparams), extracted
from Sigbjorn's excellent https://codereview.chromium.org/143313002/.
The bits of that patch which integrate 'URLSearchParams' with 'URL' and
'URLUtils' are dependent on Node moving to Oilpan, and will be added in
a subsequent patch.

Intent to Implement and Ship: https://groups.google.com/a/chromium.org/d/msg/blink-dev/grHZDbldP04/JdsoQ169AQAJ

BUG=303152

Review URL: https://codereview.chromium.org/1442643008

Cr-Commit-Position: refs/heads/master@{#360086}
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/resources/urlsearchparams-worker.js b/third_party/WebKit/LayoutTests/fast/domurl/resources/urlsearchparams-worker.js
new file mode 100644
index 0000000..999ddfc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/resources/urlsearchparams-worker.js
@@ -0,0 +1,14 @@
+importScripts("../../../resources/testharness.js")
+
+test(function() {
+    assert_true('URLSearchParams' in self);
+    assert_true('toString' in URLSearchParams.prototype);
+    assert_true('append' in URLSearchParams.prototype);
+    assert_true('delete' in URLSearchParams.prototype);
+    assert_true('get' in URLSearchParams.prototype);
+    assert_true('getAll' in URLSearchParams.prototype);
+    assert_true('has' in URLSearchParams.prototype);
+    assert_true('set' in URLSearchParams.prototype);
+}, 'URLSearchParams interface');
+
+done();
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-append.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-append.html
new file mode 100644
index 0000000..74071f1a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-append.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-append">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+test(function() {
+    var params = new URLSearchParams();
+    params.append('a', 'b');
+    assert_equals(params + '', 'a=b');
+    params.append('a', 'b');
+    assert_equals(params + '', 'a=b&a=b');
+    params.append('a', 'c');
+    assert_equals(params + '', 'a=b&a=b&a=c');
+}, 'Append same name');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('', '');
+    assert_equals(params + '', '=');
+    params.append('', '');
+    assert_equals(params + '', '=&=');
+}, 'Append empty strings');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append(null, null);
+    assert_equals(params + '', 'null=null');
+    params.append(null, null);
+    assert_equals(params + '', 'null=null&null=null');
+}, 'Append null');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('first', 1);
+    params.append('second', 2);
+    params.append('third', '');
+    params.append('first', 10);
+    assert_true(params.has('first'), 'Search params object has name "first"');
+    assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"');
+    assert_equals(params.get('second'), '2', 'Search params object has name "second" with value "2"');
+    assert_equals(params.get('third'), '', 'Search params object has name "third" with value ""');
+    params.append('first', 10);
+    assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"');
+}, 'Append multiple');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-constructor.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-constructor.html
new file mode 100644
index 0000000..33a446e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-constructor.html
@@ -0,0 +1,163 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#urlsearchparams">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+function assert_type_error(f, msg) {
+    assert_throws(TypeError(), f, msg);
+}
+
+// Consider upstreaming something kinda like this.
+function assert_iteration_equals(iteration, expectation) {
+    var actual = [...iteration];
+    assert_equals(actual.length, expectation.length);
+    for (var i = 0; i < actual.length; i++) {
+        assert_equals(typeof actual[i], typeof expectation[i]);
+        if (actual[i] instanceof Array)
+            assert_array_equals(actual[i], expectation[i]);
+        else
+            assert_equals(actual[i], expectation[i]);
+    }
+}
+
+test(function() {
+    var params = new URLSearchParams();
+    assert_equals(params + '', '');
+    params = new URLSearchParams('');
+    assert_equals(params + '', '');
+    params = new URLSearchParams('a=b');
+    assert_equals(params + '', 'a=b');
+    params = new URLSearchParams(params);
+    assert_equals(params + '', 'a=b');
+}, 'Basic URLSearchParams construction');
+
+test(function() {
+    assert_type_error(function () { URLSearchParams(); }, 'Failed to construct \'URLSearchParams\': Please use the \'new\' operator, this DOM object constructor cannot be called as a function.');
+
+    var params = new URLSearchParams('');
+    assert_not_equals(params, null, 'constructor returned non-null value.');
+    assert_equals(params.__proto__, URLSearchParams.prototype, 'expected URLSearchParams.prototype as prototype.');
+    params = new URLSearchParams({});
+    assert_equals(params + '', '%5Bobject+Object%5D=');
+}, 'URLSearchParams constructor, empty.');
+
+test(function() {
+    var params = new URLSearchParams('a=b');
+    assert_not_equals(params, null, 'constructor returned non-null value.');
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    assert_false(params.has('b'), 'Search params object has not got name "b"');
+
+    var params = new URLSearchParams('a=b&c');
+    assert_not_equals(params, null, 'constructor returned non-null value.');
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    assert_true(params.has('c'), 'Search params object has name "c"');
+
+    var params = new URLSearchParams('&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8');
+    assert_not_equals(params, null, 'constructor returned non-null value.');
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    assert_true(params.has('a b'), 'Search params object has name "a b"');
+    assert_true(params.has(' '), 'Search params object has name " "');
+    assert_false(params.has('c'), 'Search params object did not have the name "c"');
+    assert_true(params.has(' c'), 'Search params object has name " c"');
+    assert_true(params.has('møø'), 'Search params object has name "møø"');
+}, 'URLSearchParams constructor, string.');
+
+test(function() {
+    var seed = new URLSearchParams('a=b&c=d');
+    var params = new URLSearchParams(seed);
+    assert_not_equals(params, null, 'constructor returned non-null value.');
+    assert_iteration_equals(params, [ ['a', 'b' ], [ 'c', 'd' ] ]);
+    // The name-value pairs are copied when created; later updates
+    // should not be observable.
+    seed.append('e', 'f');
+    assert_false(params.has('e'));
+    params.append('g', 'h');
+    assert_false(seed.has('g'));
+}, 'URLSearchParams constructor, object.');
+
+test(function() {
+    var params = new URLSearchParams('a=b+c');
+    assert_equals(params.get('a'), 'b c');
+    params = new URLSearchParams('a+b=c');
+    assert_equals(params.get('a b'), 'c');
+}, 'Parse +');
+
+test(function() {
+    var params = new URLSearchParams('a=b%2Bc');
+    assert_equals(params.get('a'), 'b+c');
+    params = new URLSearchParams('a%2Bb=c');
+    assert_equals(params.get('a+b'), 'c');
+}, 'Parse %2B');
+
+test(function() {
+    var params = new URLSearchParams('a=b c');
+    assert_equals(params.get('a'), 'b c');
+    params = new URLSearchParams('a b=c');
+    assert_equals(params.get('a b'), 'c');
+}, 'Parse space');
+
+test(function() {
+    var params = new URLSearchParams('a=b%20c');
+    assert_equals(params.get('a'), 'b c');
+    params = new URLSearchParams('a%20b=c');
+    assert_equals(params.get('a b'), 'c');
+}, 'Parse %20');
+
+test(function() {
+    var params = new URLSearchParams('a=b\0c');
+    assert_equals(params.get('a'), 'b\0c');
+    params = new URLSearchParams('a\0b=c');
+    assert_equals(params.get('a\0b'), 'c');
+}, 'Parse \\0');
+
+test(function() {
+    var params = new URLSearchParams('a=b%00c');
+    assert_equals(params.get('a'), 'b\0c');
+    params = new URLSearchParams('a%00b=c');
+    assert_equals(params.get('a\0b'), 'c');
+}, 'Parse %00');
+
+test(function() {
+    var params = new URLSearchParams('a=b\u2384');
+    assert_equals(params.get('a'), 'b\u2384');
+    params = new URLSearchParams('a\u2384b=c');
+    assert_equals(params.get('a\u2384b'), 'c');
+}, 'Parse \u2384');  // Unicode Character 'COMPOSITION SYMBOL' (U+2384)
+
+test(function() {
+    var params = new URLSearchParams('a=b%e2%8e%84');
+    assert_equals(params.get('a'), 'b\u2384');
+    params = new URLSearchParams('a%e2%8e%84b=c');
+    assert_equals(params.get('a\u2384b'), 'c');
+}, 'Parse %e2%8e%84');  // Unicode Character 'COMPOSITION SYMBOL' (U+2384)
+
+test(function() {
+    var params = new URLSearchParams('a=b\uD83D\uDCA9c');
+    assert_equals(params.get('a'), 'b\uD83D\uDCA9c');
+    params = new URLSearchParams('a\uD83D\uDCA9b=c');
+    assert_equals(params.get('a\uD83D\uDCA9b'), 'c');
+}, 'Parse \uD83D\uDCA9');  // Unicode Character 'PILE OF POO' (U+1F4A9)
+
+test(function() {
+    var params = new URLSearchParams('a=b%f0%9f%92%a9c');
+    assert_equals(params.get('a'), 'b\uD83D\uDCA9c');
+    params = new URLSearchParams('a%f0%9f%92%a9b=c');
+    assert_equals(params.get('a\uD83D\uDCA9b'), 'c');
+}, 'Parse %f0%9f%92%a9');  // Unicode Character 'PILE OF POO' (U+1F4A9)
+
+test(function() {
+    var params = new URLSearchParams('=');
+    assert_equals(params.toString(), '=');
+}, 'Parse =');
+
+test(function() {
+    var params = new URLSearchParams('foobar=a\nb');
+    assert_equals(params.toString(), 'foobar=a%0Ab');
+}, 'Parse \\n');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-delete.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-delete.html
new file mode 100644
index 0000000..0ffae60
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-delete.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-delete">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+test(function() {
+    var params = new URLSearchParams('a=b&c=d');
+    params.delete('a');
+    assert_equals(params + '', 'c=d');
+    params = new URLSearchParams('a=a&b=b&a=a&c=c');
+    params.delete('a');
+    assert_equals(params + '', 'b=b&c=c');
+    params = new URLSearchParams('a=a&=&b=b&c=c');
+    params.delete('');
+    assert_equals(params + '', 'a=a&b=b&c=c');
+    params = new URLSearchParams('a=a&null=null&b=b');
+    params.delete(null);
+    assert_equals(params + '', 'a=a&b=b');
+    params = new URLSearchParams('a=a&undefined=undefined&b=b');
+    params.delete(undefined);
+    assert_equals(params + '', 'a=a&b=b');
+}, 'Delete basics');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('first', 1);
+    assert_true(params.has('first'), 'Search params object has name "first"');
+    assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"');
+    params.delete('first');
+    assert_false(params.has('first'), 'Search params object has no "first" name');
+    params.append('first', 1);
+    params.append('first', 10);
+    params.delete('first');
+    assert_false(params.has('first'), 'Search params object has no "first" name');
+}, 'Deleting appended multiple');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-get.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-get.html
new file mode 100644
index 0000000..91ef209a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-get.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-get">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+test(function() {
+    var params = new URLSearchParams('a=b&c=d');
+    assert_equals(params.get('a'), 'b');
+    assert_equals(params.get('c'), 'd');
+    assert_equals(params.get('e'), null);
+    params = new URLSearchParams('a=b&c=d&a=e');
+    assert_equals(params.get('a'), 'b');
+    params = new URLSearchParams('=b&c=d');
+    assert_equals(params.get(''), 'b');
+    params = new URLSearchParams('a=&c=d&a=e');
+    assert_equals(params.get('a'), '');
+}, 'Get basics');
+
+test(function() {
+    var params = new URLSearchParams('first=second&third&&');
+    assert_true(params != null, 'constructor returned non-null value.');
+    assert_true(params.has('first'), 'Search params object has name "first"');
+    assert_equals(params.get('first'), 'second', 'Search params object has name "first" with value "second"');
+    assert_equals(params.get('third'), '', 'Search params object has name "third" with the empty value.');
+    assert_equals(params.get('fourth'), null, 'Search params object has no "fourth" name and value.');
+}, 'More get() basics');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-getall.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-getall.html
new file mode 100644
index 0000000..4454e2b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-getall.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-getAll">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+test(function() {
+    var params = new URLSearchParams('a=b&c=d');
+    assert_array_equals(params.getAll('a'), ['b']);
+    assert_array_equals(params.getAll('c'), ['d']);
+    assert_array_equals(params.getAll('e'), []);
+    params = new URLSearchParams('a=b&c=d&a=e');
+    assert_array_equals(params.getAll('a'), ['b', 'e']);
+    params = new URLSearchParams('=b&c=d');
+    assert_array_equals(params.getAll(''), ['b']);
+    params = new URLSearchParams('a=&c=d&a=e');
+    assert_array_equals(params.getAll('a'), ['', 'e']);
+}, 'getAll() basics');
+
+test(function() {
+    var params = new URLSearchParams('a=1&a=2&a=3&a');
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    var matches = params.getAll('a');
+    assert_true(matches && matches.length == 4, 'Search params object has values for name "a"');
+    assert_array_equals(matches, ['1', '2', '3', ''], 'Search params object has expected name "a" values');
+    params.set('a', 'one');
+    assert_equals(params.get('a'), 'one', 'Search params object has name "a" with value "one"');
+    var matches = params.getAll('a');
+    assert_true(matches && matches.length == 1, 'Search params object has values for name "a"');
+    assert_array_equals(matches, ['one'], 'Search params object has expected name "a" values');
+}, 'getAll() multiples');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-has.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-has.html
new file mode 100644
index 0000000..e020253
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-has.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-has">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+test(function() {
+    var params = new URLSearchParams('a=b&c=d');
+    assert_true(params.has('a'));
+    assert_true(params.has('c'));
+    assert_false(params.has('e'));
+    params = new URLSearchParams('a=b&c=d&a=e');
+    assert_true(params.has('a'));
+    params = new URLSearchParams('=b&c=d');
+    assert_true(params.has(''));
+    params = new URLSearchParams('null=a');
+    assert_true(params.has(null));
+}, 'has() basics');
+
+test(function() {
+    var params = new URLSearchParams('a=b&c=d&&');
+    params.append('first', 1);
+    params.append('first', 2);
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    assert_true(params.has('c'), 'Search params object has name "c"');
+    assert_true(params.has('first'), 'Search params object has name "first"');
+    assert_false(params.has('d'), 'Search params object has no name "d"');
+    params.delete('first');
+    assert_false(params.has('first'), 'Search params object has no name "first"');
+}, 'has() following delete()');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-iterable.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-iterable.html
new file mode 100644
index 0000000..3e7b0f0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-iterable.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+var expectedValues = {
+    'a': '1',
+    'b': '2',
+    'c': '3'
+};
+
+var params = new URLSearchParams();
+params.append('a', '1');
+params.append('b', '2');
+params.append('c', '3');
+
+test(function() {
+    for (var param of params) {
+        var key = param[0];
+        var value = param[1];
+        assert_true(key in expectedValues);
+        assert_equals(params.get(key), expectedValues[key]);
+        assert_equals(value, expectedValues[key]);
+    }
+}, 'for...of Iteration');
+
+test(function() {
+    for (var key of params.keys()) {
+        assert_true(key in expectedValues);
+        assert_equals(params.get(key), expectedValues[key]);
+    }
+}, 'keys');
+
+test(function() {
+    var expectedKeys = {};
+    for (var key in expectedValues)
+        expectedKeys[expectedValues[key]] = key;
+
+    for (var value of params.values())
+        assert_true(value in expectedKeys);
+}, 'values');
+
+test(function () {
+    for (var param of params.entries()) {
+        var key = param[0];
+        var value = param[1];
+        assert_true(key in expectedValues);
+        assert_equals(params.get(key), expectedValues[key]);
+        assert_equals(value, expectedValues[key]);
+    }
+}, 'entries');
+
+test(function () {
+    params.forEach(function (value, key, paramsObject) {
+        assert_true(key in expectedValues);
+        assert_equals(params.get(key), expectedValues[key]);
+        assert_equals(value, expectedValues[key]);
+        assert_equals(paramsObject, params);
+    });
+}, 'forEach');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-set.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-set.html
new file mode 100644
index 0000000..bb9b8a1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-set.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-set">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+test(function() {
+    var params = new URLSearchParams('a=b&c=d');
+    params.set('a', 'B');
+    assert_equals(params + '', 'a=B&c=d');
+    params = new URLSearchParams('a=b&c=d&a=e');
+    params.set('a', 'B');
+    assert_equals(params + '', 'a=B&c=d')
+    params.set('e', 'f');
+    assert_equals(params + '', 'a=B&c=d&e=f')
+}, 'set() basics');
+
+test(function() {
+    var params = new URLSearchParams('a=1&a=2&a=3');
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    assert_equals(params.get('a'), '1', 'Search params object has name "a" with value "1"');
+    params.set('first', 4);
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    assert_equals(params.get('a'), '1', 'Search params object has name "a" with value "1"');
+    assert_equals(params + '', 'a=1&a=2&a=3&first=4');
+    params.set('a', 4);
+    assert_true(params.has('a'), 'Search params object has name "a"');
+    assert_equals(params.get('a'), '4', 'Search params object has name "a" with value "4"');
+    assert_equals(params + '', 'a=4&first=4');
+}, 'set() replaces multiple values');
+</script>
+</head>
+</html>
+
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-stringifier-expected.txt b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-stringifier-expected.txt
new file mode 100644
index 0000000..397c97b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-stringifier-expected.txt
@@ -0,0 +1,270 @@
+This is a testharness.js-based test.
+PASS Serialize space 
+PASS Serialize empty value 
+PASS Serialize empty name 
+PASS Serialize empty name and value 
+PASS Serialize + 
+PASS Serialize = 
+PASS Serialize & 
+PASS Serialize U+0000 -> '\0' 
+PASS Serialize U+0001 -> '' 
+PASS Serialize U+0002 -> '' 
+PASS Serialize U+0003 -> '' 
+PASS Serialize U+0004 -> '' 
+PASS Serialize U+0005 -> '' 
+PASS Serialize U+0006 -> '' 
+PASS Serialize U+0007 -> '' 
+PASS Serialize U+0008 -> '' 
+PASS Serialize U+0009 -> '	' 
+PASS Serialize U+000A -> '
+' 
+PASS Serialize U+000B -> '' 
+PASS Serialize U+000C -> '' 
+PASS Serialize U+000D -> '\r' 
+PASS Serialize U+000E -> '' 
+PASS Serialize U+000F -> '' 
+PASS Serialize U+0010 -> '' 
+PASS Serialize U+0011 -> '' 
+PASS Serialize U+0012 -> '' 
+PASS Serialize U+0013 -> '' 
+PASS Serialize U+0014 -> '' 
+PASS Serialize U+0015 -> '' 
+PASS Serialize U+0016 -> '' 
+PASS Serialize U+0017 -> '' 
+PASS Serialize U+0018 -> '' 
+PASS Serialize U+0019 -> '' 
+PASS Serialize U+001A -> '' 
+PASS Serialize U+001B -> '' 
+PASS Serialize U+001C -> '' 
+PASS Serialize U+001D -> '' 
+PASS Serialize U+001E -> '' 
+PASS Serialize U+001F -> '' 
+PASS Serialize U+0020 -> ' ' 
+FAIL Serialize U+0021 -> '!' assert_equals: expected "33=%21" but got "33=!"
+PASS Serialize U+0022 -> '"' 
+PASS Serialize U+0023 -> '#' 
+PASS Serialize U+0024 -> '$' 
+PASS Serialize U+0025 -> '%' 
+PASS Serialize U+0026 -> '&' 
+PASS Serialize U+0027 -> ''' 
+FAIL Serialize U+0028 -> '(' assert_equals: expected "40=%28" but got "40=("
+FAIL Serialize U+0029 -> ')' assert_equals: expected "41=%29" but got "41=)"
+PASS Serialize U+002A -> '*' 
+PASS Serialize U+002B -> '+' 
+PASS Serialize U+002C -> ',' 
+PASS Serialize U+002D -> '-' 
+PASS Serialize U+002E -> '.' 
+FAIL Serialize U+002F -> '/' assert_equals: expected "47=%2F" but got "47=/"
+PASS Serialize U+0030 -> '0' 
+PASS Serialize U+0031 -> '1' 
+PASS Serialize U+0032 -> '2' 
+PASS Serialize U+0033 -> '3' 
+PASS Serialize U+0034 -> '4' 
+PASS Serialize U+0035 -> '5' 
+PASS Serialize U+0036 -> '6' 
+PASS Serialize U+0037 -> '7' 
+PASS Serialize U+0038 -> '8' 
+PASS Serialize U+0039 -> '9' 
+PASS Serialize U+003A -> ':' 
+PASS Serialize U+003B -> ';' 
+PASS Serialize U+003C -> '<' 
+PASS Serialize U+003D -> '=' 
+PASS Serialize U+003E -> '>' 
+PASS Serialize U+003F -> '?' 
+PASS Serialize U+0040 -> '@' 
+PASS Serialize U+0041 -> 'A' 
+PASS Serialize U+0042 -> 'B' 
+PASS Serialize U+0043 -> 'C' 
+PASS Serialize U+0044 -> 'D' 
+PASS Serialize U+0045 -> 'E' 
+PASS Serialize U+0046 -> 'F' 
+PASS Serialize U+0047 -> 'G' 
+PASS Serialize U+0048 -> 'H' 
+PASS Serialize U+0049 -> 'I' 
+PASS Serialize U+004A -> 'J' 
+PASS Serialize U+004B -> 'K' 
+PASS Serialize U+004C -> 'L' 
+PASS Serialize U+004D -> 'M' 
+PASS Serialize U+004E -> 'N' 
+PASS Serialize U+004F -> 'O' 
+PASS Serialize U+0050 -> 'P' 
+PASS Serialize U+0051 -> 'Q' 
+PASS Serialize U+0052 -> 'R' 
+PASS Serialize U+0053 -> 'S' 
+PASS Serialize U+0054 -> 'T' 
+PASS Serialize U+0055 -> 'U' 
+PASS Serialize U+0056 -> 'V' 
+PASS Serialize U+0057 -> 'W' 
+PASS Serialize U+0058 -> 'X' 
+PASS Serialize U+0059 -> 'Y' 
+PASS Serialize U+005A -> 'Z' 
+PASS Serialize U+005B -> '[' 
+PASS Serialize U+005C -> '\' 
+PASS Serialize U+005D -> ']' 
+PASS Serialize U+005E -> '^' 
+PASS Serialize U+005F -> '_' 
+PASS Serialize U+0060 -> '`' 
+PASS Serialize U+0061 -> 'a' 
+PASS Serialize U+0062 -> 'b' 
+PASS Serialize U+0063 -> 'c' 
+PASS Serialize U+0064 -> 'd' 
+PASS Serialize U+0065 -> 'e' 
+PASS Serialize U+0066 -> 'f' 
+PASS Serialize U+0067 -> 'g' 
+PASS Serialize U+0068 -> 'h' 
+PASS Serialize U+0069 -> 'i' 
+PASS Serialize U+006A -> 'j' 
+PASS Serialize U+006B -> 'k' 
+PASS Serialize U+006C -> 'l' 
+PASS Serialize U+006D -> 'm' 
+PASS Serialize U+006E -> 'n' 
+PASS Serialize U+006F -> 'o' 
+PASS Serialize U+0070 -> 'p' 
+PASS Serialize U+0071 -> 'q' 
+PASS Serialize U+0072 -> 'r' 
+PASS Serialize U+0073 -> 's' 
+PASS Serialize U+0074 -> 't' 
+PASS Serialize U+0075 -> 'u' 
+PASS Serialize U+0076 -> 'v' 
+PASS Serialize U+0077 -> 'w' 
+PASS Serialize U+0078 -> 'x' 
+PASS Serialize U+0079 -> 'y' 
+PASS Serialize U+007A -> 'z' 
+PASS Serialize U+007B -> '{' 
+PASS Serialize U+007C -> '|' 
+PASS Serialize U+007D -> '}' 
+FAIL Serialize U+007E -> '~' assert_equals: expected "126=%7E" but got "126=~"
+PASS Serialize U+007F -> '' 
+PASS Serialize U+0080 -> '€' 
+PASS Serialize U+0081 -> '' 
+PASS Serialize U+0082 -> '‚' 
+PASS Serialize U+0083 -> 'ƒ' 
+PASS Serialize U+0084 -> '„' 
+PASS Serialize U+0085 -> '…' 
+PASS Serialize U+0086 -> '†' 
+PASS Serialize U+0087 -> '‡' 
+PASS Serialize U+0088 -> 'ˆ' 
+PASS Serialize U+0089 -> '‰' 
+PASS Serialize U+008A -> 'Š' 
+PASS Serialize U+008B -> '‹' 
+PASS Serialize U+008C -> 'Œ' 
+PASS Serialize U+008D -> '' 
+PASS Serialize U+008E -> 'Ž' 
+PASS Serialize U+008F -> '' 
+PASS Serialize U+0090 -> '' 
+PASS Serialize U+0091 -> '‘' 
+PASS Serialize U+0092 -> '’' 
+PASS Serialize U+0093 -> '“' 
+PASS Serialize U+0094 -> '”' 
+PASS Serialize U+0095 -> '•' 
+PASS Serialize U+0096 -> '–' 
+PASS Serialize U+0097 -> '—' 
+PASS Serialize U+0098 -> '˜' 
+PASS Serialize U+0099 -> '™' 
+PASS Serialize U+009A -> 'š' 
+PASS Serialize U+009B -> '›' 
+PASS Serialize U+009C -> 'œ' 
+PASS Serialize U+009D -> '' 
+PASS Serialize U+009E -> 'ž' 
+PASS Serialize U+009F -> 'Ÿ' 
+PASS Serialize U+00A0 -> ' ' 
+PASS Serialize U+00A1 -> '¡' 
+PASS Serialize U+00A2 -> '¢' 
+PASS Serialize U+00A3 -> '£' 
+PASS Serialize U+00A4 -> '¤' 
+PASS Serialize U+00A5 -> '¥' 
+PASS Serialize U+00A6 -> '¦' 
+PASS Serialize U+00A7 -> '§' 
+PASS Serialize U+00A8 -> '¨' 
+PASS Serialize U+00A9 -> '©' 
+PASS Serialize U+00AA -> 'ª' 
+PASS Serialize U+00AB -> '«' 
+PASS Serialize U+00AC -> '¬' 
+PASS Serialize U+00AD -> '­' 
+PASS Serialize U+00AE -> '®' 
+PASS Serialize U+00AF -> '¯' 
+PASS Serialize U+00B0 -> '°' 
+PASS Serialize U+00B1 -> '±' 
+PASS Serialize U+00B2 -> '²' 
+PASS Serialize U+00B3 -> '³' 
+PASS Serialize U+00B4 -> '´' 
+PASS Serialize U+00B5 -> 'µ' 
+PASS Serialize U+00B6 -> '¶' 
+PASS Serialize U+00B7 -> '·' 
+PASS Serialize U+00B8 -> '¸' 
+PASS Serialize U+00B9 -> '¹' 
+PASS Serialize U+00BA -> 'º' 
+PASS Serialize U+00BB -> '»' 
+PASS Serialize U+00BC -> '¼' 
+PASS Serialize U+00BD -> '½' 
+PASS Serialize U+00BE -> '¾' 
+PASS Serialize U+00BF -> '¿' 
+PASS Serialize U+00C0 -> 'À' 
+PASS Serialize U+00C1 -> 'Á' 
+PASS Serialize U+00C2 -> 'Â' 
+PASS Serialize U+00C3 -> 'Ã' 
+PASS Serialize U+00C4 -> 'Ä' 
+PASS Serialize U+00C5 -> 'Å' 
+PASS Serialize U+00C6 -> 'Æ' 
+PASS Serialize U+00C7 -> 'Ç' 
+PASS Serialize U+00C8 -> 'È' 
+PASS Serialize U+00C9 -> 'É' 
+PASS Serialize U+00CA -> 'Ê' 
+PASS Serialize U+00CB -> 'Ë' 
+PASS Serialize U+00CC -> 'Ì' 
+PASS Serialize U+00CD -> 'Í' 
+PASS Serialize U+00CE -> 'Î' 
+PASS Serialize U+00CF -> 'Ï' 
+PASS Serialize U+00D0 -> 'Ð' 
+PASS Serialize U+00D1 -> 'Ñ' 
+PASS Serialize U+00D2 -> 'Ò' 
+PASS Serialize U+00D3 -> 'Ó' 
+PASS Serialize U+00D4 -> 'Ô' 
+PASS Serialize U+00D5 -> 'Õ' 
+PASS Serialize U+00D6 -> 'Ö' 
+PASS Serialize U+00D7 -> '×' 
+PASS Serialize U+00D8 -> 'Ø' 
+PASS Serialize U+00D9 -> 'Ù' 
+PASS Serialize U+00DA -> 'Ú' 
+PASS Serialize U+00DB -> 'Û' 
+PASS Serialize U+00DC -> 'Ü' 
+PASS Serialize U+00DD -> 'Ý' 
+PASS Serialize U+00DE -> 'Þ' 
+PASS Serialize U+00DF -> 'ß' 
+PASS Serialize U+00E0 -> 'à' 
+PASS Serialize U+00E1 -> 'á' 
+PASS Serialize U+00E2 -> 'â' 
+PASS Serialize U+00E3 -> 'ã' 
+PASS Serialize U+00E4 -> 'ä' 
+PASS Serialize U+00E5 -> 'å' 
+PASS Serialize U+00E6 -> 'æ' 
+PASS Serialize U+00E7 -> 'ç' 
+PASS Serialize U+00E8 -> 'è' 
+PASS Serialize U+00E9 -> 'é' 
+PASS Serialize U+00EA -> 'ê' 
+PASS Serialize U+00EB -> 'ë' 
+PASS Serialize U+00EC -> 'ì' 
+PASS Serialize U+00ED -> 'í' 
+PASS Serialize U+00EE -> 'î' 
+PASS Serialize U+00EF -> 'ï' 
+PASS Serialize U+00F0 -> 'ð' 
+PASS Serialize U+00F1 -> 'ñ' 
+PASS Serialize U+00F2 -> 'ò' 
+PASS Serialize U+00F3 -> 'ó' 
+PASS Serialize U+00F4 -> 'ô' 
+PASS Serialize U+00F5 -> 'õ' 
+PASS Serialize U+00F6 -> 'ö' 
+PASS Serialize U+00F7 -> '÷' 
+PASS Serialize U+00F8 -> 'ø' 
+PASS Serialize U+00F9 -> 'ù' 
+PASS Serialize U+00FA -> 'ú' 
+PASS Serialize U+00FB -> 'û' 
+PASS Serialize U+00FC -> 'ü' 
+PASS Serialize U+00FD -> 'ý' 
+PASS Serialize U+00FE -> 'þ' 
+PASS Serialize % 
+PASS Serialize \0 
+PASS Serialize 💩 
+PASS URLSearchParams.toString 
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-stringifier.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-stringifier.html
new file mode 100644
index 0000000..495a1ac3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-stringifier.html
@@ -0,0 +1,146 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-set">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/testharness-extras.js"></script>
+<script>
+test(function() {
+    var params = new URLSearchParams();
+    params.append('a', 'b c');
+    assert_equals(params + '', 'a=b+c');
+    params.delete('a');
+    params.append('a b', 'c');
+    assert_equals(params + '', 'a+b=c');
+}, 'Serialize space');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('a', '');
+    assert_equals(params + '', 'a=');
+    params.append('a', '');
+    assert_equals(params + '', 'a=&a=');
+    params.append('', 'b');
+    assert_equals(params + '', 'a=&a=&=b');
+    params.append('', '');
+    assert_equals(params + '', 'a=&a=&=b&=');
+    params.append('', '');
+    assert_equals(params + '', 'a=&a=&=b&=&=');
+}, 'Serialize empty value');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('', 'b');
+    assert_equals(params + '', '=b');
+    params.append('', 'b');
+    assert_equals(params + '', '=b&=b');
+}, 'Serialize empty name');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('', '');
+    assert_equals(params + '', '=');
+    params.append('', '');
+    assert_equals(params + '', '=&=');
+}, 'Serialize empty name and value');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('a', 'b+c');
+    assert_equals(params + '', 'a=b%2Bc');
+    params.delete('a');
+    params.append('a+b', 'c');
+    assert_equals(params + '', 'a%2Bb=c');
+}, 'Serialize +');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('=', 'a');
+    assert_equals(params + '', '%3D=a');
+    params.append('b', '=');
+    assert_equals(params + '', '%3D=a&b=%3D');
+}, 'Serialize =');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('&', 'a');
+    assert_equals(params + '', '%26=a');
+    params.append('b', '&');
+    assert_equals(params + '', '%26=a&b=%26');
+}, 'Serialize &');
+
+function intToHex(i) {
+    return (i<0x10 ? '0':'') + i.toString(16).toUpperCase();
+}
+
+function urlEncodedSerialize(n) {
+  if (n === 0x20) {
+    return "\x2B";
+  }
+
+  if (n === 0x2A || n === 0x2D || n === 0x2E ||
+      (0x30 <= n && n <= 0x39) || (0x41 <= n && n <= 0x5A) ||
+      n === 0x5F || (0x61 <= n && n <= 0x7A)) {
+    return String.fromCharCode(n);
+  }
+  var bytes = (new TextEncoder()).encode(String.fromCharCode(n));
+  var ret = "";
+  for (var i = 0; i < bytes.length; i++) {
+      ret += "%" + intToHex(bytes[i]);
+  }
+  return ret;
+}
+
+for (var i = 0x00; i < 0xFF; i++) {
+    // Not all bytes can appear in valid UTF-8, so some bytes aren't covered.
+    // TODO(mkwst): We fail to properly encode a few bytes: https://crbug.com/557063
+    test(function() {
+        var params = new URLSearchParams();
+        params.append('' + i, String.fromCodePoint(i));
+        assert_equals(params + '', '' + i + '=' + urlEncodedSerialize(i));
+    }, "Serialize U+00" + intToHex(i) + " -> '" + String.fromCodePoint(i) + "'");
+}
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('a', 'b%c');
+    assert_equals(params + '', 'a=b%25c');
+    params.delete('a');
+    params.append('a%b', 'c');
+    assert_equals(params + '', 'a%25b=c');
+}, 'Serialize %');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('a', 'b\0c');
+    assert_equals(params + '', 'a=b%00c');
+    params.delete('a');
+    params.append('a\0b', 'c');
+    assert_equals(params + '', 'a%00b=c');
+}, 'Serialize \\0');
+
+test(function() {
+    var params = new URLSearchParams();
+    params.append('a', 'b\uD83D\uDCA9c');
+    assert_equals(params + '', 'a=b%F0%9F%92%A9c');
+    params.delete('a');
+    params.append('a\uD83D\uDCA9b', 'c');
+    assert_equals(params + '', 'a%F0%9F%92%A9b=c');
+}, 'Serialize \uD83D\uDCA9');  // Unicode Character 'PILE OF POO' (U+1F4A9)
+
+test(function() {
+    var params;
+    params = new URLSearchParams('a=b&c=d&&e&&');
+    assert_equals(params.toString(), 'a=b&c=d&e=');
+    params = new URLSearchParams('a = b &a=b&c=d%20');
+    assert_equals(params.toString(), 'a+=+b+&a=b&c=d+');
+    // The lone '=' _does_ survive the roundtrip.
+    params = new URLSearchParams('a=&a=b');
+    assert_equals(params.toString(), 'a=&a=b');
+}, 'URLSearchParams.toString');
+</script>
+</head>
+</html>
+
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-worker.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-worker.html
new file mode 100644
index 0000000..5fb8474
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams-worker.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#interface-urlsearchparams">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker("./resources/urlsearchparams-worker.js"));
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams.html b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams.html
new file mode 100644
index 0000000..80861f51
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/domurl/urlsearchparams.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf8">
+<link rel="help" href="https://url.spec.whatwg.org/#interface-urlsearchparams">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+test(function() {
+    assert_true('URLSearchParams' in window);
+    assert_true('toString' in URLSearchParams.prototype);
+    assert_true('append' in URLSearchParams.prototype);
+    assert_true('delete' in URLSearchParams.prototype);
+    assert_true('get' in URLSearchParams.prototype);
+    assert_true('getAll' in URLSearchParams.prototype);
+    assert_true('has' in URLSearchParams.prototype);
+    assert_true('set' in URLSearchParams.prototype);
+}, 'URLSearchParams interface');
+</script>
+</head>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index f6ff90d..54605ce 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -629,6 +629,19 @@
     setter protocol
     setter search
     setter username
+interface URLSearchParams
+    method append
+    method constructor
+    method delete
+    method entries
+    method forEach
+    method get
+    method getAll
+    method has
+    method keys
+    method set
+    method toString
+    method values
 interface WebSocket : EventTarget
     attribute CLOSED
     attribute CLOSING
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 48d165e..36dedba4 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -528,6 +528,19 @@
     setter protocol
     setter search
     setter username
+interface URLSearchParams
+    method append
+    method constructor
+    method delete
+    method entries
+    method forEach
+    method get
+    method getAll
+    method has
+    method keys
+    method set
+    method toString
+    method values
 interface WebSocket : EventTarget
     attribute CLOSED
     attribute CLOSING
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 886ce38..bb0e7c2 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -514,6 +514,19 @@
 [Worker]     setter protocol
 [Worker]     setter search
 [Worker]     setter username
+[Worker] interface URLSearchParams
+[Worker]     method append
+[Worker]     method constructor
+[Worker]     method delete
+[Worker]     method entries
+[Worker]     method forEach
+[Worker]     method get
+[Worker]     method getAll
+[Worker]     method has
+[Worker]     method keys
+[Worker]     method set
+[Worker]     method toString
+[Worker]     method values
 [Worker] interface WebSocket : EventTarget
 [Worker]     attribute CLOSED
 [Worker]     attribute CLOSING
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt
index ba13e8c..4311a8b 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -4750,6 +4750,19 @@
     setter protocol
     setter search
     setter username
+interface URLSearchParams
+    method append
+    method constructor
+    method delete
+    method entries
+    method forEach
+    method get
+    method getAll
+    method has
+    method keys
+    method set
+    method toString
+    method values
 interface VTTCue : TextTrackCue
     getter align
     getter line
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index 9af80ab..d53d411 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -506,6 +506,19 @@
 [Worker]     setter protocol
 [Worker]     setter search
 [Worker]     setter username
+[Worker] interface URLSearchParams
+[Worker]     method append
+[Worker]     method constructor
+[Worker]     method delete
+[Worker]     method entries
+[Worker]     method forEach
+[Worker]     method get
+[Worker]     method getAll
+[Worker]     method has
+[Worker]     method keys
+[Worker]     method set
+[Worker]     method toString
+[Worker]     method values
 [Worker] interface WebSocket : EventTarget
 [Worker]     attribute CLOSED
 [Worker]     attribute CLOSING
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index b71358c..843ac62 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -579,6 +579,19 @@
 [Worker]     setter protocol
 [Worker]     setter search
 [Worker]     setter username
+[Worker] interface URLSearchParams
+[Worker]     method append
+[Worker]     method constructor
+[Worker]     method delete
+[Worker]     method entries
+[Worker]     method forEach
+[Worker]     method get
+[Worker]     method getAll
+[Worker]     method has
+[Worker]     method keys
+[Worker]     method set
+[Worker]     method toString
+[Worker]     method values
 [Worker] interface WebSocket : EventTarget
 [Worker]     attribute CLOSED
 [Worker]     attribute CLOSING
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index 1f0a02b..33088f5 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -5340,6 +5340,19 @@
     setter protocol
     setter search
     setter username
+interface URLSearchParams
+    method append
+    method constructor
+    method delete
+    method entries
+    method forEach
+    method get
+    method getAll
+    method has
+    method keys
+    method set
+    method toString
+    method values
 interface USBAlternateInterface
     getter alternateSetting
     getter endpoints
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
index 01ec856..c0d05bc 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -571,6 +571,19 @@
 [Worker]     setter protocol
 [Worker]     setter search
 [Worker]     setter username
+[Worker] interface URLSearchParams
+[Worker]     method append
+[Worker]     method constructor
+[Worker]     method delete
+[Worker]     method entries
+[Worker]     method forEach
+[Worker]     method get
+[Worker]     method getAll
+[Worker]     method has
+[Worker]     method keys
+[Worker]     method set
+[Worker]     method toString
+[Worker]     method values
 [Worker] interface WebSocket : EventTarget
 [Worker]     attribute CLOSED
 [Worker]     attribute CLOSING
diff --git a/third_party/WebKit/Source/core/core.gypi b/third_party/WebKit/Source/core/core.gypi
index 6578811..c1551df 100644
--- a/third_party/WebKit/Source/core/core.gypi
+++ b/third_party/WebKit/Source/core/core.gypi
@@ -96,6 +96,7 @@
             'dom/Uint32Array.idl',
             'dom/Uint8Array.idl',
             'dom/Uint8ClampedArray.idl',
+            'dom/URLSearchParams.idl',
             'dom/XMLDocument.idl',
             'dom/shadow/ShadowRoot.idl',
             'editing/Selection.idl',
@@ -2501,6 +2502,8 @@
             'dom/TreeShared.h',
             'dom/TreeWalker.cpp',
             'dom/TreeWalker.h',
+            'dom/URLSearchParams.cpp',
+            'dom/URLSearchParams.h',
             'dom/UserActionElementSet.cpp',
             'dom/UserActionElementSet.h',
             'dom/ViewportDescription.cpp',
diff --git a/third_party/WebKit/Source/core/dom/URLSearchParams.cpp b/third_party/WebKit/Source/core/dom/URLSearchParams.cpp
new file mode 100644
index 0000000..141aab1
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/URLSearchParams.cpp
@@ -0,0 +1,188 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "config.h"
+#include "core/dom/URLSearchParams.h"
+
+#include "platform/weborigin/KURL.h"
+#include "wtf/text/StringBuilder.h"
+#include "wtf/text/TextEncoding.h"
+
+namespace blink {
+
+namespace {
+
+class URLSearchParamsIterationSource final : public PairIterable<String, String>::IterationSource {
+public:
+    URLSearchParamsIterationSource(Vector<std::pair<String, String>> params) : m_params(params), m_current(0) { }
+
+    bool next(ScriptState*, String& key, String& value, ExceptionState&) override
+    {
+        if (m_current >= m_params.size())
+            return false;
+
+        key = m_params[m_current].first;
+        value = m_params[m_current].second;
+        m_current++;
+        return true;
+    }
+
+private:
+    Vector<std::pair<String, String>> m_params;
+    size_t m_current;
+};
+
+} // namespace
+
+URLSearchParams* URLSearchParams::create(const URLSearchParamsInit& init)
+{
+    if (init.isUSVString())
+        return new URLSearchParams(init.getAsUSVString());
+    if (init.isURLSearchParams())
+        return new URLSearchParams(init.getAsURLSearchParams());
+
+    ASSERT(init.isNull());
+    return new URLSearchParams(String());
+}
+
+URLSearchParams::URLSearchParams(const String& queryString)
+{
+    if (!queryString.isEmpty())
+        setInput(queryString);
+}
+
+URLSearchParams::URLSearchParams(URLSearchParams* searchParams)
+{
+    ASSERT(searchParams);
+    m_params = searchParams->m_params;
+}
+
+URLSearchParams::~URLSearchParams()
+{
+}
+
+static String decodeString(String input)
+{
+    return decodeURLEscapeSequences(input.replace('+', ' '));
+}
+
+void URLSearchParams::setInput(const String& queryString)
+{
+    ASSERT(m_params.isEmpty());
+    size_t start = 0;
+    size_t queryStringLength = queryString.length();
+    while (start < queryStringLength) {
+        size_t nameStart = start;
+        size_t nameValueEnd = queryString.find('&', start);
+        if (nameValueEnd == kNotFound)
+            nameValueEnd = queryStringLength;
+        if (nameValueEnd > start) {
+            size_t endOfName = queryString.find('=', start);
+            if (endOfName == kNotFound || endOfName > nameValueEnd)
+                endOfName = nameValueEnd;
+            String name = decodeString(queryString.substring(nameStart, endOfName - nameStart));
+            String value;
+            if (endOfName != nameValueEnd)
+                value = decodeString(queryString.substring(endOfName + 1, nameValueEnd - endOfName - 1));
+            if (value.isNull())
+                value = "";
+            m_params.append(std::make_pair(name, value));
+        }
+        start = nameValueEnd + 1;
+    }
+}
+static String encodeString(const String& input)
+{
+    return encodeWithURLEscapeSequences(input).replace("%20", "+");
+}
+
+String URLSearchParams::toString() const
+{
+    StringBuilder result;
+    for (size_t i = 0; i < m_params.size(); ++i) {
+        if (i)
+            result.append('&');
+        result.append(encodeString(m_params[i].first));
+        result.append('=');
+        result.append(encodeString(m_params[i].second));
+    }
+    return result.toString();
+}
+
+void URLSearchParams::append(const String& name, const String& value)
+{
+    m_params.append(std::make_pair(name, value));
+}
+
+void URLSearchParams::deleteAllWithName(const String& name)
+{
+    for (size_t i = 0; i < m_params.size();) {
+        if (m_params[i].first == name)
+            m_params.remove(i);
+        else
+            i++;
+    }
+}
+
+String URLSearchParams::get(const String& name) const
+{
+    for (const auto& param : m_params) {
+        if (param.first == name)
+            return param.second;
+    }
+    return String();
+}
+
+Vector<String> URLSearchParams::getAll(const String& name) const
+{
+    Vector<String> result;
+    for (const auto& param : m_params) {
+        if (param.first == name)
+            result.append(param.second);
+    }
+    return result;
+}
+
+bool URLSearchParams::has(const String& name) const
+{
+    for (const auto& param : m_params) {
+        if (param.first == name)
+            return true;
+    }
+    return false;
+}
+
+void URLSearchParams::set(const String& name, const String& value)
+{
+    bool foundMatch = false;
+    for (size_t i = 0; i < m_params.size();) {
+        // If there are any name-value whose name is 'name', set
+        // the value of the first such name-value pair to 'value'
+        // and remove the others.
+        if (m_params[i].first == name) {
+            if (!foundMatch) {
+                m_params[i++].second = value;
+                foundMatch = true;
+            } else {
+                m_params.remove(i);
+            }
+        } else {
+            i++;
+        }
+    }
+    // Otherwise, append a new name-value pair to the list.
+    if (!foundMatch)
+        append(name, value);
+}
+
+DEFINE_TRACE(URLSearchParams)
+{
+}
+
+PairIterable<String, String>::IterationSource* URLSearchParams::startIteration(ScriptState*, ExceptionState&)
+{
+    return new URLSearchParamsIterationSource(m_params);
+}
+
+} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/URLSearchParams.h b/third_party/WebKit/Source/core/dom/URLSearchParams.h
new file mode 100644
index 0000000..7eb3dfb
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/URLSearchParams.h
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef URLSearchParams_h
+#define URLSearchParams_h
+
+#include "bindings/core/v8/Iterable.h"
+#include "bindings/core/v8/ScriptWrappable.h"
+#include "bindings/core/v8/UnionTypesCore.h"
+#include "platform/heap/Handle.h"
+#include "wtf/Forward.h"
+#include "wtf/text/WTFString.h"
+#include <utility>
+
+namespace blink {
+
+class ExceptionState;
+
+typedef USVStringOrURLSearchParams URLSearchParamsInit;
+
+class CORE_EXPORT URLSearchParams final : public GarbageCollectedFinalized<URLSearchParams>, public ScriptWrappable, public PairIterable<String, String> {
+    DEFINE_WRAPPERTYPEINFO();
+
+public:
+    // TODO(mkwst): We should support integration with URLUtils, as explored in
+    // https://codereview.chromium.org/143313002/. That approach is totally
+    // reasonable, but relies on Node switching to Oilpan. Sigbjorn assures me
+    // that this will happen Real Soon Now(tm).
+    static URLSearchParams* create(const URLSearchParamsInit&);
+
+    // TODO(mkwst): ScriptWrappable doesn't have a destructor with Oilpan, so this
+    // won't need to be virtual once that's the default.
+    virtual ~URLSearchParams();
+
+    // URLSearchParams interface methods
+    String toString() const;
+    void append(const String& name, const String& value);
+    void deleteAllWithName(const String&);
+    String get(const String&) const;
+    Vector<String> getAll(const String&) const;
+    bool has(const String&) const;
+    void set(const String& name, const String& value);
+    void setInput(const String&);
+
+    DECLARE_TRACE();
+
+private:
+    explicit URLSearchParams(const String&);
+    explicit URLSearchParams(URLSearchParams*);
+    Vector<std::pair<String, String>> m_params;
+
+    IterationSource* startIteration(ScriptState*, ExceptionState&) override;
+};
+
+} // namespace blink
+
+#endif // URLSearchParams_h
diff --git a/third_party/WebKit/Source/core/dom/URLSearchParams.idl b/third_party/WebKit/Source/core/dom/URLSearchParams.idl
new file mode 100644
index 0000000..409542c
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/URLSearchParams.idl
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://url.spec.whatwg.org/#interface-urlsearchparams
+
+[
+    Constructor(optional (USVString or URLSearchParams) init = ""),
+    GarbageCollected,
+    Exposed=(Window,Worker)
+] interface URLSearchParams {
+    void append(USVString name, USVString value);
+    [ImplementedAs=deleteAllWithName] void delete(USVString name);
+    USVString? get(USVString name);
+    sequence<USVString> getAll(USVString name);
+    boolean has(USVString name);
+    void set(USVString name, USVString value);
+    
+    iterable<USVString, USVString>;
+    stringifier;
+};