diff --git a/lib/internal/url.js b/lib/internal/url.js
index e48811a9c3ca61..4fd7702367e8a6 100644
--- a/lib/internal/url.js
+++ b/lib/internal/url.js
@@ -257,8 +257,262 @@ class URLSearchParams {
     }
     return `${this.constructor.name} {}`;
   }
+
+  append(name, value) {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+    if (arguments.length < 2) {
+      throw new ERR_MISSING_ARGS('name', 'value');
+    }
+
+    name = toUSVString(name);
+    value = toUSVString(value);
+    ArrayPrototypePush(this[searchParams], name, value);
+    update(this[context], this);
+  }
+
+  delete(name) {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+    if (arguments.length < 1) {
+      throw new ERR_MISSING_ARGS('name');
+    }
+
+    const list = this[searchParams];
+    name = toUSVString(name);
+    for (let i = 0; i < list.length;) {
+      const cur = list[i];
+      if (cur === name) {
+        list.splice(i, 2);
+      } else {
+        i += 2;
+      }
+    }
+    update(this[context], this);
+  }
+
+  get(name) {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+    if (arguments.length < 1) {
+      throw new ERR_MISSING_ARGS('name');
+    }
+
+    const list = this[searchParams];
+    name = toUSVString(name);
+    for (let i = 0; i < list.length; i += 2) {
+      if (list[i] === name) {
+        return list[i + 1];
+      }
+    }
+    return null;
+  }
+
+  getAll(name) {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+    if (arguments.length < 1) {
+      throw new ERR_MISSING_ARGS('name');
+    }
+
+    const list = this[searchParams];
+    const values = [];
+    name = toUSVString(name);
+    for (let i = 0; i < list.length; i += 2) {
+      if (list[i] === name) {
+        values.push(list[i + 1]);
+      }
+    }
+    return values;
+  }
+
+  has(name) {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+    if (arguments.length < 1) {
+      throw new ERR_MISSING_ARGS('name');
+    }
+
+    const list = this[searchParams];
+    name = toUSVString(name);
+    for (let i = 0; i < list.length; i += 2) {
+      if (list[i] === name) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  set(name, value) {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+    if (arguments.length < 2) {
+      throw new ERR_MISSING_ARGS('name', 'value');
+    }
+
+    const list = this[searchParams];
+    name = toUSVString(name);
+    value = toUSVString(value);
+
+    // If there are any name-value pairs whose name is `name`, in `list`, set
+    // the value of the first such name-value pair to `value` and remove the
+    // others.
+    let found = false;
+    for (let i = 0; i < list.length;) {
+      const cur = list[i];
+      if (cur === name) {
+        if (!found) {
+          list[i + 1] = value;
+          found = true;
+          i += 2;
+        } else {
+          list.splice(i, 2);
+        }
+      } else {
+        i += 2;
+      }
+    }
+
+    // Otherwise, append a new name-value pair whose name is `name` and value
+    // is `value`, to `list`.
+    if (!found) {
+      ArrayPrototypePush(list, name, value);
+    }
+
+    update(this[context], this);
+  }
+
+  sort() {
+    const a = this[searchParams];
+    const len = a.length;
+
+    if (len <= 2) {
+      // Nothing needs to be done.
+    } else if (len < 100) {
+      // 100 is found through testing.
+      // Simple stable in-place insertion sort
+      // Derived from v8/src/js/array.js
+      for (let i = 2; i < len; i += 2) {
+        const curKey = a[i];
+        const curVal = a[i + 1];
+        let j;
+        for (j = i - 2; j >= 0; j -= 2) {
+          if (a[j] > curKey) {
+            a[j + 2] = a[j];
+            a[j + 3] = a[j + 1];
+          } else {
+            break;
+          }
+        }
+        a[j + 2] = curKey;
+        a[j + 3] = curVal;
+      }
+    } else {
+      // Bottom-up iterative stable merge sort
+      const lBuffer = new Array(len);
+      const rBuffer = new Array(len);
+      for (let step = 2; step < len; step *= 2) {
+        for (let start = 0; start < len - 2; start += 2 * step) {
+          const mid = start + step;
+          let end = mid + step;
+          end = end < len ? end : len;
+          if (mid > end)
+            continue;
+          merge(a, start, mid, end, lBuffer, rBuffer);
+        }
+      }
+    }
+
+    update(this[context], this);
+  }
+
+  // https://heycam.github.io/webidl/#es-iterators
+  // Define entries here rather than [Symbol.iterator] as the function name
+  // must be set to `entries`.
+  entries() {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+
+    return createSearchParamsIterator(this, 'key+value');
+  }
+
+  forEach(callback, thisArg = undefined) {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+    validateCallback(callback);
+
+    let list = this[searchParams];
+
+    let i = 0;
+    while (i < list.length) {
+      const key = list[i];
+      const value = list[i + 1];
+      callback.call(thisArg, value, key, this);
+      // In case the URL object's `search` is updated
+      list = this[searchParams];
+      i += 2;
+    }
+  }
+
+  // https://heycam.github.io/webidl/#es-iterable
+  keys() {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+
+    return createSearchParamsIterator(this, 'key');
+  }
+
+  values() {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+
+    return createSearchParamsIterator(this, 'value');
+  }
+
+  // https://heycam.github.io/webidl/#es-stringifier
+  // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
+  toString() {
+    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
+      throw new ERR_INVALID_THIS('URLSearchParams');
+    }
+
+    return serializeParams(this[searchParams]);
+  }
 }
 
+ObjectDefineProperties(URLSearchParams.prototype, {
+  append: { enumerable: true },
+  delete: { enumerable: true },
+  get: { enumerable: true },
+  getAll: { enumerable: true },
+  has: { enumerable: true },
+  set: { enumerable: true },
+  sort: { enumerable: true },
+  entries: { enumerable: true },
+  forEach: { enumerable: true },
+  keys: { enumerable: true },
+  values: { enumerable: true },
+  toString: { enumerable: true },
+  [SymbolToStringTag]: { configurable: true, value: 'URLSearchParams' },
+
+  // https://heycam.github.io/webidl/#es-iterable-entries
+  [SymbolIterator]: {
+    configurable: true,
+    writable: true,
+    value: URLSearchParams.prototype.entries,
+  },
+});
+
 function onParseComplete(flags, protocol, username, password,
                          host, port, path, query, fragment) {
   const ctx = this[context];
@@ -408,319 +662,293 @@ class URL {
 
     return `${constructor.name} ${inspect(obj, opts)}`;
   }
-}
 
-ObjectDefineProperties(URL.prototype, {
-  [kFormat]: {
-    enumerable: false,
-    configurable: false,
-    // eslint-disable-next-line func-name-matching
-    value: function format(options) {
-      if (options)
-        validateObject(options, 'options');
-
-      options = {
-        fragment: true,
-        unicode: false,
-        search: true,
-        auth: true,
-        ...options
-      };
-      const ctx = this[context];
-      // https://url.spec.whatwg.org/#url-serializing
-      let ret = ctx.scheme;
-      if (ctx.host !== null) {
-        ret += '//';
-        const has_username = ctx.username !== '';
-        const has_password = ctx.password !== '';
-        if (options.auth && (has_username || has_password)) {
-          if (has_username)
-            ret += ctx.username;
-          if (has_password)
-            ret += `:${ctx.password}`;
-          ret += '@';
-        }
-        ret += options.unicode ?
-          domainToUnicode(ctx.host) : ctx.host;
-        if (ctx.port !== null)
-          ret += `:${ctx.port}`;
-      }
-      if (this[cannotBeBase]) {
-        ret += ctx.path[0];
-      } else {
-        if (ctx.host === null && ctx.path.length > 1 && ctx.path[0] === '') {
-          ret += '/.';
-        }
-        if (ctx.path.length) {
-          ret += '/' + ArrayPrototypeJoin(ctx.path, '/');
-        }
-      }
-      if (options.search && ctx.query !== null)
-        ret += `?${ctx.query}`;
-      if (options.fragment && ctx.fragment !== null)
-        ret += `#${ctx.fragment}`;
-      return ret;
-    }
-  },
-  [SymbolToStringTag]: {
-    configurable: true,
-    value: 'URL'
-  },
-  toString: {
-    // https://heycam.github.io/webidl/#es-stringifier
-    writable: true,
-    enumerable: true,
-    configurable: true,
-    // eslint-disable-next-line func-name-matching
-    value: function toString() {
-      return this[kFormat]({});
-    }
-  },
-  href: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      return this[kFormat]({});
-    },
-    set(input) {
-      // toUSVString is not needed.
-      input = `${input}`;
-      parse(input, -1, undefined, undefined,
-            FunctionPrototypeBind(onParseComplete, this), onParseError);
-    }
-  },
-  origin: {  // readonly
-    enumerable: true,
-    configurable: true,
-    get() {
-      // Refs: https://url.spec.whatwg.org/#concept-url-origin
-      const ctx = this[context];
-      switch (ctx.scheme) {
-        case 'blob:':
-          if (ctx.path.length > 0) {
-            try {
-              return (new URL(ctx.path[0])).origin;
-            } catch {
-              // Fall through... do nothing
-            }
-          }
-          return kOpaqueOrigin;
-        case 'ftp:':
-        case 'http:':
-        case 'https:':
-        case 'ws:':
-        case 'wss:':
-          return serializeTupleOrigin(ctx.scheme, ctx.host, ctx.port);
+  [kFormat](options) {
+    if (options)
+      validateObject(options, 'options');
+
+    options = {
+      fragment: true,
+      unicode: false,
+      search: true,
+      auth: true,
+      ...options
+    };
+    const ctx = this[context];
+    // https://url.spec.whatwg.org/#url-serializing
+    let ret = ctx.scheme;
+    if (ctx.host !== null) {
+      ret += '//';
+      const has_username = ctx.username !== '';
+      const has_password = ctx.password !== '';
+      if (options.auth && (has_username || has_password)) {
+        if (has_username)
+          ret += ctx.username;
+        if (has_password)
+          ret += `:${ctx.password}`;
+        ret += '@';
       }
-      return kOpaqueOrigin;
-    }
-  },
-  protocol: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      return this[context].scheme;
-    },
-    set(scheme) {
-      // toUSVString is not needed.
-      scheme = `${scheme}`;
-      if (scheme.length === 0)
-        return;
-      const ctx = this[context];
-      parse(scheme, kSchemeStart, null, ctx,
-            FunctionPrototypeBind(onParseProtocolComplete, this));
+      ret += options.unicode ?
+        domainToUnicode(ctx.host) : ctx.host;
+      if (ctx.port !== null)
+        ret += `:${ctx.port}`;
     }
-  },
-  username: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      return this[context].username;
-    },
-    set(username) {
-      // toUSVString is not needed.
-      username = `${username}`;
-      if (this[cannotHaveUsernamePasswordPort])
-        return;
-      const ctx = this[context];
-      if (username === '') {
-        ctx.username = '';
-        ctx.flags &= ~URL_FLAGS_HAS_USERNAME;
-        return;
+    if (this[cannotBeBase]) {
+      ret += ctx.path[0];
+    } else {
+      if (ctx.host === null && ctx.path.length > 1 && ctx.path[0] === '') {
+        ret += '/.';
       }
-      ctx.username = encodeAuth(username);
-      ctx.flags |= URL_FLAGS_HAS_USERNAME;
-    }
-  },
-  password: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      return this[context].password;
-    },
-    set(password) {
-      // toUSVString is not needed.
-      password = `${password}`;
-      if (this[cannotHaveUsernamePasswordPort])
-        return;
-      const ctx = this[context];
-      if (password === '') {
-        ctx.password = '';
-        ctx.flags &= ~URL_FLAGS_HAS_PASSWORD;
-        return;
+      if (ctx.path.length) {
+        ret += '/' + ArrayPrototypeJoin(ctx.path, '/');
       }
-      ctx.password = encodeAuth(password);
-      ctx.flags |= URL_FLAGS_HAS_PASSWORD;
     }
-  },
-  host: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      const ctx = this[context];
-      let ret = ctx.host || '';
-      if (ctx.port !== null)
-        ret += `:${ctx.port}`;
-      return ret;
-    },
-    set(host) {
-      const ctx = this[context];
-      // toUSVString is not needed.
-      host = `${host}`;
-      if (this[cannotBeBase]) {
-        // Cannot set the host if cannot-be-base is set
-        return;
-      }
-      parse(host, kHost, null, ctx,
-            FunctionPrototypeBind(onParseHostComplete, this));
+    if (options.search && ctx.query !== null)
+      ret += `?${ctx.query}`;
+    if (options.fragment && ctx.fragment !== null)
+      ret += `#${ctx.fragment}`;
+    return ret;
+  }
+
+  // https://heycam.github.io/webidl/#es-stringifier
+  toString() {
+    return this[kFormat]({});
+  }
+
+  get href() {
+    return this[kFormat]({});
+  }
+
+  set href(input) {
+    // toUSVString is not needed.
+    input = `${input}`;
+    parse(input, -1, undefined, undefined,
+          FunctionPrototypeBind(onParseComplete, this), onParseError);
+  }
+
+  // readonly
+  get origin() {
+    // Refs: https://url.spec.whatwg.org/#concept-url-origin
+    const ctx = this[context];
+    switch (ctx.scheme) {
+      case 'blob:':
+        if (ctx.path.length > 0) {
+          try {
+            return (new URL(ctx.path[0])).origin;
+          } catch {
+            // Fall through... do nothing
+          }
+        }
+        return kOpaqueOrigin;
+      case 'ftp:':
+      case 'http:':
+      case 'https:':
+      case 'ws:':
+      case 'wss:':
+        return serializeTupleOrigin(ctx.scheme, ctx.host, ctx.port);
     }
-  },
-  hostname: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      return this[context].host || '';
-    },
-    set(host) {
-      const ctx = this[context];
-      // toUSVString is not needed.
-      host = `${host}`;
-      if (this[cannotBeBase]) {
-        // Cannot set the host if cannot-be-base is set
-        return;
-      }
-      parse(host, kHostname, null, ctx, onParseHostnameComplete.bind(this));
+    return kOpaqueOrigin;
+  }
+
+  get protocol() {
+    return this[context].scheme;
+  }
+
+  set protocol(scheme) {
+    // toUSVString is not needed.
+    scheme = `${scheme}`;
+    if (scheme.length === 0)
+      return;
+    const ctx = this[context];
+    parse(scheme, kSchemeStart, null, ctx,
+          FunctionPrototypeBind(onParseProtocolComplete, this));
+  }
+
+  get username() {
+    return this[context].username;
+  }
+
+  set username(username) {
+    // toUSVString is not needed.
+    username = `${username}`;
+    if (this[cannotHaveUsernamePasswordPort])
+      return;
+    const ctx = this[context];
+    if (username === '') {
+      ctx.username = '';
+      ctx.flags &= ~URL_FLAGS_HAS_USERNAME;
+      return;
     }
-  },
-  port: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      const port = this[context].port;
-      return port === null ? '' : String(port);
-    },
-    set(port) {
-      // toUSVString is not needed.
-      port = `${port}`;
-      if (this[cannotHaveUsernamePasswordPort])
-        return;
-      const ctx = this[context];
-      if (port === '') {
-        ctx.port = null;
-        return;
-      }
-      parse(port, kPort, null, ctx,
-            FunctionPrototypeBind(onParsePortComplete, this));
+    ctx.username = encodeAuth(username);
+    ctx.flags |= URL_FLAGS_HAS_USERNAME;
+  }
+
+  get password() {
+    return this[context].password;
+  }
+
+  set password(password) {
+    // toUSVString is not needed.
+    password = `${password}`;
+    if (this[cannotHaveUsernamePasswordPort])
+      return;
+    const ctx = this[context];
+    if (password === '') {
+      ctx.password = '';
+      ctx.flags &= ~URL_FLAGS_HAS_PASSWORD;
+      return;
     }
-  },
-  pathname: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      const ctx = this[context];
-      if (this[cannotBeBase])
-        return ctx.path[0];
-      if (ctx.path.length === 0)
-        return '';
-      return `/${ArrayPrototypeJoin(ctx.path, '/')}`;
-    },
-    set(path) {
-      // toUSVString is not needed.
-      path = `${path}`;
-      if (this[cannotBeBase])
-        return;
-      parse(path, kPathStart, null, this[context],
-            onParsePathComplete.bind(this));
+    ctx.password = encodeAuth(password);
+    ctx.flags |= URL_FLAGS_HAS_PASSWORD;
+  }
+
+  get host() {
+    const ctx = this[context];
+    let ret = ctx.host || '';
+    if (ctx.port !== null)
+      ret += `:${ctx.port}`;
+    return ret;
+  }
+
+  set host(host) {
+    const ctx = this[context];
+    // toUSVString is not needed.
+    host = `${host}`;
+    if (this[cannotBeBase]) {
+      // Cannot set the host if cannot-be-base is set
+      return;
     }
-  },
-  search: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      const { query } = this[context];
-      if (query === null || query === '')
-        return '';
-      return `?${query}`;
-    },
-    set(search) {
-      const ctx = this[context];
-      search = toUSVString(search);
-      if (search === '') {
-        ctx.query = null;
-        ctx.flags &= ~URL_FLAGS_HAS_QUERY;
-      } else {
-        if (search[0] === '?') search = StringPrototypeSlice(search, 1);
-        ctx.query = '';
-        ctx.flags |= URL_FLAGS_HAS_QUERY;
-        if (search) {
-          parse(search, kQuery, null, ctx,
-                FunctionPrototypeBind(onParseSearchComplete, this));
-        }
-      }
-      initSearchParams(this[searchParams], search);
+    parse(host, kHost, null, ctx,
+          FunctionPrototypeBind(onParseHostComplete, this));
+  }
+
+  get hostname() {
+    return this[context].host || '';
+  }
+
+  set hostname(host) {
+    const ctx = this[context];
+    // toUSVString is not needed.
+    host = `${host}`;
+    if (this[cannotBeBase]) {
+      // Cannot set the host if cannot-be-base is set
+      return;
     }
-  },
-  searchParams: {  // readonly
-    enumerable: true,
-    configurable: true,
-    get() {
-      return this[searchParams];
+    parse(host, kHostname, null, ctx, onParseHostnameComplete.bind(this));
+  }
+
+  get port() {
+    const port = this[context].port;
+    return port === null ? '' : String(port);
+  }
+
+  set port(port) {
+    // toUSVString is not needed.
+    port = `${port}`;
+    if (this[cannotHaveUsernamePasswordPort])
+      return;
+    const ctx = this[context];
+    if (port === '') {
+      ctx.port = null;
+      return;
     }
-  },
-  hash: {
-    enumerable: true,
-    configurable: true,
-    get() {
-      const { fragment } = this[context];
-      if (fragment === null || fragment === '')
-        return '';
-      return `#${fragment}`;
-    },
-    set(hash) {
-      const ctx = this[context];
-      // toUSVString is not needed.
-      hash = `${hash}`;
-      if (!hash) {
-        ctx.fragment = null;
-        ctx.flags &= ~URL_FLAGS_HAS_FRAGMENT;
-        return;
+    parse(port, kPort, null, ctx,
+          FunctionPrototypeBind(onParsePortComplete, this));
+  }
+
+  get pathname() {
+    const ctx = this[context];
+    if (this[cannotBeBase])
+      return ctx.path[0];
+    if (ctx.path.length === 0)
+      return '';
+    return `/${ArrayPrototypeJoin(ctx.path, '/')}`;
+  }
+
+  set pathname(path) {
+    // toUSVString is not needed.
+    path = `${path}`;
+    if (this[cannotBeBase])
+      return;
+    parse(path, kPathStart, null, this[context],
+          onParsePathComplete.bind(this));
+  }
+
+  get search() {
+    const { query } = this[context];
+    if (query === null || query === '')
+      return '';
+    return `?${query}`;
+  }
+
+  set search(search) {
+    const ctx = this[context];
+    search = toUSVString(search);
+    if (search === '') {
+      ctx.query = null;
+      ctx.flags &= ~URL_FLAGS_HAS_QUERY;
+    } else {
+      if (search[0] === '?') search = StringPrototypeSlice(search, 1);
+      ctx.query = '';
+      ctx.flags |= URL_FLAGS_HAS_QUERY;
+      if (search) {
+        parse(search, kQuery, null, ctx,
+              FunctionPrototypeBind(onParseSearchComplete, this));
       }
-      if (hash[0] === '#') hash = StringPrototypeSlice(hash, 1);
-      ctx.fragment = '';
-      ctx.flags |= URL_FLAGS_HAS_FRAGMENT;
-      parse(hash, kFragment, null, ctx,
-            FunctionPrototypeBind(onParseHashComplete, this));
     }
-  },
-  toJSON: {
-    writable: true,
-    enumerable: true,
-    configurable: true,
-    // eslint-disable-next-line func-name-matching
-    value: function toJSON() {
-      return this[kFormat]({});
+    initSearchParams(this[searchParams], search);
+  }
+
+  // readonly
+  get searchParams() {
+    return this[searchParams];
+  }
+
+  get hash() {
+    const { fragment } = this[context];
+    if (fragment === null || fragment === '')
+      return '';
+    return `#${fragment}`;
+  }
+
+  set hash(hash) {
+    const ctx = this[context];
+    // toUSVString is not needed.
+    hash = `${hash}`;
+    if (!hash) {
+      ctx.fragment = null;
+      ctx.flags &= ~URL_FLAGS_HAS_FRAGMENT;
+      return;
     }
+    if (hash[0] === '#') hash = StringPrototypeSlice(hash, 1);
+    ctx.fragment = '';
+    ctx.flags |= URL_FLAGS_HAS_FRAGMENT;
+    parse(hash, kFragment, null, ctx,
+          FunctionPrototypeBind(onParseHashComplete, this));
+  }
+
+  toJSON() {
+    return this[kFormat]({});
   }
+}
+
+ObjectDefineProperties(URL.prototype, {
+  [kFormat]: { configurable: false, writable: false },
+  [SymbolToStringTag]: { configurable: true, value: 'URL' },
+  toString: { enumerable: true },
+  href: { enumerable: true },
+  origin: { enumerable: true },
+  protocol: { enumerable: true },
+  username: { enumerable: true },
+  password: { enumerable: true },
+  host: { enumerable: true },
+  hostname: { enumerable: true },
+  port: { enumerable: true },
+  pathname: { enumerable: true },
+  search: { enumerable: true },
+  searchParams: { enumerable: true },
+  hash: { enumerable: true },
+  toJSON: { enumerable: true },
 });
 
 function update(url, params) {
@@ -944,246 +1172,6 @@ function merge(out, start, mid, end, lBuffer, rBuffer) {
     out[o++] = rBuffer[r++];
 }
 
-defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
-  append(name, value) {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-    if (arguments.length < 2) {
-      throw new ERR_MISSING_ARGS('name', 'value');
-    }
-
-    name = toUSVString(name);
-    value = toUSVString(value);
-    ArrayPrototypePush(this[searchParams], name, value);
-    update(this[context], this);
-  },
-
-  delete(name) {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-    if (arguments.length < 1) {
-      throw new ERR_MISSING_ARGS('name');
-    }
-
-    const list = this[searchParams];
-    name = toUSVString(name);
-    for (let i = 0; i < list.length;) {
-      const cur = list[i];
-      if (cur === name) {
-        list.splice(i, 2);
-      } else {
-        i += 2;
-      }
-    }
-    update(this[context], this);
-  },
-
-  get(name) {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-    if (arguments.length < 1) {
-      throw new ERR_MISSING_ARGS('name');
-    }
-
-    const list = this[searchParams];
-    name = toUSVString(name);
-    for (let i = 0; i < list.length; i += 2) {
-      if (list[i] === name) {
-        return list[i + 1];
-      }
-    }
-    return null;
-  },
-
-  getAll(name) {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-    if (arguments.length < 1) {
-      throw new ERR_MISSING_ARGS('name');
-    }
-
-    const list = this[searchParams];
-    const values = [];
-    name = toUSVString(name);
-    for (let i = 0; i < list.length; i += 2) {
-      if (list[i] === name) {
-        values.push(list[i + 1]);
-      }
-    }
-    return values;
-  },
-
-  has(name) {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-    if (arguments.length < 1) {
-      throw new ERR_MISSING_ARGS('name');
-    }
-
-    const list = this[searchParams];
-    name = toUSVString(name);
-    for (let i = 0; i < list.length; i += 2) {
-      if (list[i] === name) {
-        return true;
-      }
-    }
-    return false;
-  },
-
-  set(name, value) {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-    if (arguments.length < 2) {
-      throw new ERR_MISSING_ARGS('name', 'value');
-    }
-
-    const list = this[searchParams];
-    name = toUSVString(name);
-    value = toUSVString(value);
-
-    // If there are any name-value pairs whose name is `name`, in `list`, set
-    // the value of the first such name-value pair to `value` and remove the
-    // others.
-    let found = false;
-    for (let i = 0; i < list.length;) {
-      const cur = list[i];
-      if (cur === name) {
-        if (!found) {
-          list[i + 1] = value;
-          found = true;
-          i += 2;
-        } else {
-          list.splice(i, 2);
-        }
-      } else {
-        i += 2;
-      }
-    }
-
-    // Otherwise, append a new name-value pair whose name is `name` and value
-    // is `value`, to `list`.
-    if (!found) {
-      ArrayPrototypePush(list, name, value);
-    }
-
-    update(this[context], this);
-  },
-
-  sort() {
-    const a = this[searchParams];
-    const len = a.length;
-
-    if (len <= 2) {
-      // Nothing needs to be done.
-    } else if (len < 100) {
-      // 100 is found through testing.
-      // Simple stable in-place insertion sort
-      // Derived from v8/src/js/array.js
-      for (let i = 2; i < len; i += 2) {
-        const curKey = a[i];
-        const curVal = a[i + 1];
-        let j;
-        for (j = i - 2; j >= 0; j -= 2) {
-          if (a[j] > curKey) {
-            a[j + 2] = a[j];
-            a[j + 3] = a[j + 1];
-          } else {
-            break;
-          }
-        }
-        a[j + 2] = curKey;
-        a[j + 3] = curVal;
-      }
-    } else {
-      // Bottom-up iterative stable merge sort
-      const lBuffer = new Array(len);
-      const rBuffer = new Array(len);
-      for (let step = 2; step < len; step *= 2) {
-        for (let start = 0; start < len - 2; start += 2 * step) {
-          const mid = start + step;
-          let end = mid + step;
-          end = end < len ? end : len;
-          if (mid > end)
-            continue;
-          merge(a, start, mid, end, lBuffer, rBuffer);
-        }
-      }
-    }
-
-    update(this[context], this);
-  },
-
-  // https://heycam.github.io/webidl/#es-iterators
-  // Define entries here rather than [Symbol.iterator] as the function name
-  // must be set to `entries`.
-  entries() {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-
-    return createSearchParamsIterator(this, 'key+value');
-  },
-
-  forEach(callback, thisArg = undefined) {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-    validateCallback(callback);
-
-    let list = this[searchParams];
-
-    let i = 0;
-    while (i < list.length) {
-      const key = list[i];
-      const value = list[i + 1];
-      callback.call(thisArg, value, key, this);
-      // In case the URL object's `search` is updated
-      list = this[searchParams];
-      i += 2;
-    }
-  },
-
-  // https://heycam.github.io/webidl/#es-iterable
-  keys() {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-
-    return createSearchParamsIterator(this, 'key');
-  },
-
-  values() {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-
-    return createSearchParamsIterator(this, 'value');
-  },
-
-  // https://heycam.github.io/webidl/#es-stringifier
-  // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
-  toString() {
-    if (!this || !this[searchParams] || this[searchParams][searchParams]) {
-      throw new ERR_INVALID_THIS('URLSearchParams');
-    }
-
-    return serializeParams(this[searchParams]);
-  }
-});
-
-// https://heycam.github.io/webidl/#es-iterable-entries
-ObjectDefineProperty(URLSearchParams.prototype, SymbolIterator, {
-  writable: true,
-  configurable: true,
-  value: URLSearchParams.prototype.entries
-});
-
 // https://heycam.github.io/webidl/#dfn-default-iterator-object
 function createSearchParamsIterator(target, kind) {
   const iterator = ObjectCreate(URLSearchParamsIteratorPrototype);
diff --git a/test/parallel/test-whatwg-url-properties.js b/test/parallel/test-whatwg-url-properties.js
new file mode 100644
index 00000000000000..a387b0eb753e1a
--- /dev/null
+++ b/test/parallel/test-whatwg-url-properties.js
@@ -0,0 +1,100 @@
+'use strict';
+require('../common');
+const assert = require('assert');
+const { URL, URLSearchParams } = require('url');
+
+[
+  { name: 'toString' },
+  { name: 'toJSON' },
+  { name: Symbol.for('nodejs.util.inspect.custom') },
+].forEach(({ name }) => {
+  testMethod(URL.prototype, name);
+});
+
+[
+  { name: 'href' },
+  { name: 'protocol' },
+  { name: 'username' },
+  { name: 'password' },
+  { name: 'host' },
+  { name: 'hostname' },
+  { name: 'port' },
+  { name: 'pathname' },
+  { name: 'search' },
+  { name: 'hash' },
+  { name: 'origin', readonly: true },
+  { name: 'searchParams', readonly: true },
+].forEach(({ name, readonly = false }) => {
+  testAccessor(URL.prototype, name, readonly);
+});
+
+[
+  { name: 'append' },
+  { name: 'delete' },
+  { name: 'get' },
+  { name: 'getAll' },
+  { name: 'has' },
+  { name: 'set' },
+  { name: 'sort' },
+  { name: 'entries' },
+  { name: 'forEach' },
+  { name: 'keys' },
+  { name: 'values' },
+  { name: 'toString' },
+  { name: Symbol.iterator, methodName: 'entries' },
+  { name: Symbol.for('nodejs.util.inspect.custom') },
+].forEach(({ name, methodName }) => {
+  testMethod(URLSearchParams.prototype, name, methodName);
+});
+
+function stringifyName(name) {
+  if (typeof name === 'symbol') {
+    const { description } = name;
+    if (description === undefined) {
+      return '';
+    }
+    return `[${description}]`;
+  }
+
+  return name;
+}
+
+function testMethod(target, name, methodName = stringifyName(name)) {
+  const desc = Object.getOwnPropertyDescriptor(target, name);
+  assert.notStrictEqual(desc, undefined);
+  assert.strictEqual(desc.enumerable, typeof name === 'string');
+
+  const { value } = desc;
+  assert.strictEqual(typeof value, 'function');
+  assert.strictEqual(value.name, methodName);
+  assert.strictEqual(
+    Object.prototype.hasOwnProperty.call(value, 'prototype'),
+    false,
+  );
+}
+
+function testAccessor(target, name, readonly = false) {
+  const desc = Object.getOwnPropertyDescriptor(target, name);
+  assert.notStrictEqual(desc, undefined);
+  assert.strictEqual(desc.enumerable, typeof name === 'string');
+
+  const methodName = stringifyName(name);
+  const { get, set } = desc;
+  assert.strictEqual(typeof get, 'function');
+  assert.strictEqual(get.name, `get ${methodName}`);
+  assert.strictEqual(
+    Object.prototype.hasOwnProperty.call(get, 'prototype'),
+    false,
+  );
+
+  if (readonly) {
+    assert.strictEqual(set, undefined);
+  } else {
+    assert.strictEqual(typeof set, 'function');
+    assert.strictEqual(set.name, `set ${methodName}`);
+    assert.strictEqual(
+      Object.prototype.hasOwnProperty.call(set, 'prototype'),
+      false,
+    );
+  }
+}