diff --git a/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URL.java b/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URL.java index 285febb9813..e89250bf16c 100644 --- a/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URL.java +++ b/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URL.java @@ -21,8 +21,11 @@ import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF78; import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass; @@ -48,7 +51,7 @@ @JsxClass public class URL extends SimpleScriptable { - private java.net.URL url_; + private URIBuilder url_; /** * Creates an instance. @@ -73,15 +76,16 @@ public URL(final String url, final Object base) { try { if (StringUtils.isBlank(baseStr)) { - url_ = UrlUtils.toUrlUnsafe(url); + url_ = new URIBuilder(url); } else { final java.net.URL baseUrl = UrlUtils.toUrlUnsafe(baseStr); - url_ = new java.net.URL(baseUrl, url); + url_ = new URIBuilder(new java.net.URL(baseUrl, url).toURI()); } checkRemoveRedundantPort(); + url_.build().toURL(); //validate } - catch (final MalformedURLException e) { + catch (final URISyntaxException | MalformedURLException e) { throw ScriptRuntime.typeError(e.toString()); } } @@ -121,7 +125,7 @@ public String getHash() { if (url_ == null) { return null; } - final String ref = url_.getRef(); + final String ref = url_.getFragment(); return ref == null ? "" : "#" + ref; } @@ -130,7 +134,7 @@ public void setHash(final String fragment) throws MalformedURLException { if (url_ == null) { return; } - url_ = UrlUtils.getUrlWithNewRef(url_, StringUtils.isEmpty(fragment) ? null : fragment); + url_.setFragment(StringUtils.isEmpty(fragment) ? null : fragment); } /** @@ -179,29 +183,26 @@ public void setHost(final String host) throws MalformedURLException { // back to string } - url_ = UrlUtils.getUrlWithNewHost(url_, newHost); + url_.setHost(newHost); final String newPort = StringUtils.substringAfter(host, ':'); if (StringUtils.isNotBlank(newHost)) { try { - url_ = UrlUtils.getUrlWithNewHostAndPort(url_, newHost, Integer.parseInt(newPort)); + url_.setPort(Integer.parseInt(newPort)); } catch (final Exception e) { // back to string } } - else { - url_ = UrlUtils.getUrlWithNewHost(url_, newHost); - } checkRemoveRedundantPort(); } /** Removes port if it can be deduced from protocol */ - private void checkRemoveRedundantPort() throws MalformedURLException { - if (("https".equals(url_.getProtocol()) && url_.getPort() == 443) - || ("http".equals(url_.getProtocol()) && url_.getPort() == 80)) { - url_ = UrlUtils.getUrlWithNewPort(url_, -1); + private void checkRemoveRedundantPort() { + if (("https".equals(url_.getScheme()) && url_.getPort() == 443) + || ("http".equals(url_.getScheme()) && url_.getPort() == 80)) { + url_.setPort(-1); } } @@ -222,11 +223,11 @@ public String getHostname() { public void setHostname(final String hostname) throws MalformedURLException { if (getBrowserVersion().hasFeature(JS_ANCHOR_HOSTNAME_IGNORE_BLANK)) { if (!StringUtils.isBlank(hostname)) { - url_ = UrlUtils.getUrlWithNewHost(url_, hostname); + url_.setHost(hostname); } } else if (!StringUtils.isEmpty(hostname)) { - url_ = UrlUtils.getUrlWithNewHost(url_, hostname); + url_.setHost(hostname); } } @@ -243,12 +244,12 @@ public String getHref() { } @JsxSetter - public void setHref(final String href) throws MalformedURLException { + public void setHref(final String href) throws URISyntaxException { if (url_ == null) { return; } - url_ = UrlUtils.toUrlUnsafe(href); + url_ = new URIBuilder(href); checkRemoveRedundantPort(); } @@ -261,7 +262,7 @@ public Object getOrigin() { return null; } - return url_.getProtocol() + "://" + url_.getHost(); + return url_.getScheme() + "://" + url_.getHost(); } /** @@ -273,7 +274,7 @@ public URLSearchParams getSearchParams() { return null; } - final URLSearchParams searchParams = new URLSearchParams(url_.getQuery()); + final URLSearchParams searchParams = new URLSearchParams(url_); searchParams.setParentScope(getParentScope()); searchParams.setPrototype(getPrototype(searchParams.getClass())); return searchParams; @@ -298,8 +299,9 @@ public void setPassword(final String password) throws MalformedURLException { if (url_ == null) { return; } - - url_ = UrlUtils.getUrlWithNewUserPassword(url_, password.isEmpty() ? null : password); + url_.setUserInfo(UrlUtils.getUserInfoWithNewUserPassword( + url_.getUserInfo(), + password.isEmpty() ? null : password)); } /** @@ -312,7 +314,7 @@ public String getPathname() { } final String path = url_.getPath(); - return path.isEmpty() ? "/" : path; + return StringUtils.isBlank(path) ? "/" : path; } @JsxSetter @@ -321,7 +323,7 @@ public void setPathname(final String path) throws MalformedURLException { return; } - url_ = UrlUtils.getUrlWithNewPath(url_, path.startsWith("/") ? path : "/" + path); + url_.setPath(path.startsWith("/") ? path : "/" + path); } /** @@ -344,7 +346,7 @@ public void setPort(final String port) throws MalformedURLException { return; } final int portInt = port.isEmpty() ? -1 : Integer.parseInt(port); - url_ = UrlUtils.getUrlWithNewPort(url_, portInt); + url_.setPort(portInt); checkRemoveRedundantPort(); } @@ -356,7 +358,7 @@ public String getProtocol() { if (url_ == null) { return null; } - final String protocol = url_.getProtocol(); + final String protocol = url_.getScheme(); return protocol.isEmpty() ? "" : (protocol + ":"); } @@ -366,13 +368,8 @@ public void setProtocol(final String protocol) throws MalformedURLException { return; } - try { - url_ = UrlUtils.getUrlWithNewProtocol(url_, protocol); - checkRemoveRedundantPort(); - } - catch (final MalformedURLException e) { - // ignore - } + url_.setScheme(protocol.toLowerCase()); + checkRemoveRedundantPort(); } /** @@ -383,8 +380,12 @@ public String getSearch() { if (url_ == null) { return null; } - final String search = url_.getQuery(); - return search == null ? "" : "?" + search; + final String search = url_.getQueryParams().stream() + .map(pair -> pair.getValue() != null + ? String.format("%s=%s", pair.getName(), pair.getValue()) + : pair.getName()) + .collect(Collectors.joining("&")); + return StringUtils.isBlank(search) ? "" : "?" + search; } @JsxSetter @@ -404,7 +405,7 @@ else if (search.charAt(0) == '?') { query = search; } - url_ = UrlUtils.getUrlWithNewQuery(url_, query); + url_.setQuery(query); } /** @@ -429,7 +430,7 @@ public void setUsername(final String username) throws MalformedURLException { if (url_ == null) { return; } - url_ = UrlUtils.getUrlWithNewUserName(url_, username.isEmpty() ? null : username); + url_.setUserInfo(UrlUtils.getUserInfoWithNewUserName(url_.getUserInfo(), username)); } /** @@ -445,9 +446,9 @@ public Object getDefaultValue(final Class hint) { } if (StringUtils.isEmpty(url_.getPath())) { - return url_.toExternalForm() + "/"; + return url_.toString() + "/"; } - return url_.toExternalForm(); + return url_.toString(); } /** @@ -467,12 +468,11 @@ public String toJSON() { public String jsToString() { if (StringUtils.isEmpty(url_.getPath())) { try { - return UrlUtils.getUrlWithNewPath(url_, "/").toExternalForm(); - } - catch (final MalformedURLException e) { - return url_.toExternalForm(); + return UrlUtils.getUrlWithNewPath(url_.build().toURL(), "/").toExternalForm(); + } catch (final URISyntaxException | MalformedURLException e) { + return url_.toString(); } } - return url_.toExternalForm(); + return url_.toString(); } } diff --git a/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParams.java b/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParams.java index e3202f3502c..45a25111e05 100644 --- a/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParams.java +++ b/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParams.java @@ -20,13 +20,17 @@ import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF78; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URIBuilder; import org.apache.http.client.utils.URLEncodedUtils; import com.gargoylesoftware.htmlunit.FormEncodingType; @@ -35,7 +39,6 @@ import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction; -import com.gargoylesoftware.htmlunit.util.NameValuePair; import net.sourceforge.htmlunit.corejs.javascript.Context; import net.sourceforge.htmlunit.corejs.javascript.ES6Iterator; @@ -106,12 +109,17 @@ protected Object nextValue(final Context cx, final Scriptable scope) { } } - private final List params_ = new ArrayList<>(); + private final URIBuilder url; /** * Constructs a new instance. */ public URLSearchParams() { + try { + this.url = new URIBuilder("http://dummy.com"); + } catch (URISyntaxException e) { + throw new IllegalStateException("Unexpected"); + } } /** @@ -120,6 +128,7 @@ public URLSearchParams() { */ @JsxConstructor public URLSearchParams(final Object params) { + this(); // TODO: Pass in a sequence // new URLSearchParams([["foo", 1],["bar", 2]]); @@ -130,23 +139,30 @@ public URLSearchParams(final Object params) { return; } - splitQuery(Context.toString(params)); + List nameValuePairs = splitQuery(Context.toString(params)); + url.setParameters(nameValuePairs); + } + + public URLSearchParams(final URIBuilder url){ + this.url = url; } - private void splitQuery(String params) { + private List splitQuery(String params) { + List result= new ArrayList<>(); params = StringUtils.stripStart(params, "?"); if (StringUtils.isEmpty(params)) { - return; + return result; } // TODO: encoding final String[] parts = StringUtils.split(params, '&'); for (final String part : parts) { - params_.add(splitQueryParameter(part)); + result.add(splitQueryParameter(part)); } + return result; } - private static NameValuePair splitQueryParameter(final String singleParam) { + private static com.gargoylesoftware.htmlunit.util.NameValuePair splitQueryParameter(final String singleParam) { final int idx = singleParam.indexOf('='); if (idx > -1) { final String key = singleParam.substring(0, idx); @@ -154,13 +170,17 @@ private static NameValuePair splitQueryParameter(final String singleParam) { if (idx < singleParam.length()) { value = singleParam.substring(idx + 1); } - return new NameValuePair(key, value); + return new com.gargoylesoftware.htmlunit.util.NameValuePair(key, value); } final String key = singleParam; final String value = ""; - return new NameValuePair(key, value); + return new com.gargoylesoftware.htmlunit.util.NameValuePair(key, value); } + private List getParams() { + return url.getQueryParams(); + } + /** * The append() method of the URLSearchParams interface appends a specified * key/value pair as a new search parameter. @@ -170,7 +190,7 @@ private static NameValuePair splitQueryParameter(final String singleParam) { */ @JsxFunction public void append(final String name, final String value) { - params_.add(new NameValuePair(name, value)); + url.addParameter(name, value); } /** @@ -182,13 +202,15 @@ public void append(final String name, final String value) { @JsxFunction @Override public void delete(final String name) { - final Iterator iter = params_.iterator(); + List params = url.getQueryParams(); + final Iterator iter = params.iterator(); while (iter.hasNext()) { final NameValuePair entry = iter.next(); if (entry.getName().equals(name)) { iter.remove(); } } + url.setParameters(params); } /** @@ -200,7 +222,7 @@ public void delete(final String name) { */ @JsxFunction public String get(final String name) { - for (final NameValuePair param : params_) { + for (final NameValuePair param : getParams()) { if (param.getName().equals(name)) { return param.getValue(); } @@ -217,8 +239,8 @@ public String get(final String name) { */ @JsxFunction public Scriptable getAll(final String name) { - final List result = new ArrayList<>(params_.size()); - for (final NameValuePair param : params_) { + final List result = new ArrayList<>(getParams().size()); + for (final NameValuePair param : getParams()) { if (param.getName().equals(name)) { result.add(param.getValue()); } @@ -238,14 +260,15 @@ public Scriptable getAll(final String name) { */ @JsxFunction public void set(final String name, final String value) { - boolean change = true; - final ListIterator iter = params_.listIterator(); + List params = url.getQueryParams(); + boolean found = false; + final ListIterator iter = params.listIterator(); while (iter.hasNext()) { final NameValuePair entry = iter.next(); if (entry.getName().equals(name)) { - if (change) { - iter.set(new NameValuePair(name, value)); - change = false; + if (!found) { + iter.set(new com.gargoylesoftware.htmlunit.util.NameValuePair(name, value)); + found = true; } else { iter.remove(); @@ -253,7 +276,9 @@ public void set(final String name, final String value) { } } - if (change) { + if (found) { + url.setParameters(params); + } else { append(name, value); } } @@ -267,7 +292,7 @@ public void set(final String name, final String value) { */ @JsxFunction public boolean has(final String name) { - for (final NameValuePair param : params_) { + for (final NameValuePair param : getParams()) { if (param.getName().equals(name)) { return true; } @@ -286,11 +311,11 @@ public boolean has(final String name) { public Object entries() { if (getBrowserVersion().hasFeature(JS_URL_SEARCH_PARMS_ITERATOR_SIMPLE_NAME)) { return new NativeParamsIterator(getParentScope(), - "Iterator", NativeParamsIterator.Type.BOTH, params_.iterator()); + "Iterator", NativeParamsIterator.Type.BOTH, getParams().iterator()); } return new NativeParamsIterator(getParentScope(), - "URLSearchParams Iterator", NativeParamsIterator.Type.BOTH, params_.iterator()); + "URLSearchParams Iterator", NativeParamsIterator.Type.BOTH, getParams().iterator()); } /** @@ -303,11 +328,11 @@ public Object entries() { public Object keys() { if (getBrowserVersion().hasFeature(JS_URL_SEARCH_PARMS_ITERATOR_SIMPLE_NAME)) { return new NativeParamsIterator(getParentScope(), - "Iterator", NativeParamsIterator.Type.KEYS, params_.iterator()); + "Iterator", NativeParamsIterator.Type.KEYS, getParams().iterator()); } return new NativeParamsIterator(getParentScope(), - "URLSearchParams Iterator", NativeParamsIterator.Type.KEYS, params_.iterator()); + "URLSearchParams Iterator", NativeParamsIterator.Type.KEYS, getParams().iterator()); } /** @@ -320,11 +345,11 @@ public Object keys() { public Object values() { if (getBrowserVersion().hasFeature(JS_URL_SEARCH_PARMS_ITERATOR_SIMPLE_NAME)) { return new NativeParamsIterator(getParentScope(), - "Iterator", NativeParamsIterator.Type.VALUES, params_.iterator()); + "Iterator", NativeParamsIterator.Type.VALUES, getParams().iterator()); } return new NativeParamsIterator(getParentScope(), - "URLSearchParams Iterator", NativeParamsIterator.Type.VALUES, params_.iterator()); + "URLSearchParams Iterator", NativeParamsIterator.Type.VALUES, getParams().iterator()); } /** @@ -333,7 +358,15 @@ public Object values() { */ @JsxFunction(functionName = "toString") public String jsToString() { - return URLEncodedUtils.format(NameValuePair.toHttpClient(params_), "UTF-8"); + List params = getParams().stream() + .map(p -> { + if (p.getValue() != null) { + return p; + } else { + return new com.gargoylesoftware.htmlunit.util.NameValuePair(p.getName(), ""); + } + }).collect(Collectors.toList()); + return URLEncodedUtils.format(params, "UTF-8"); } /** @@ -355,10 +388,10 @@ public void fillRequest(final WebRequest webRequest) { webRequest.setRequestBody(null); webRequest.setEncodingType(FormEncodingType.URL_ENCODED); - if (params_.size() > 0) { - final List params = new ArrayList(); - for (final NameValuePair entry : params_) { - params.add(new NameValuePair(entry.getName(), entry.getValue())); + if (getParams().size() > 0) { + final List params = new ArrayList<>(); + for (final NameValuePair entry : getParams()) { + params.add(new com.gargoylesoftware.htmlunit.util.NameValuePair(entry.getName(), entry.getValue())); } webRequest.setRequestParameters(params); } diff --git a/src/main/java/com/gargoylesoftware/htmlunit/util/NameValuePair.java b/src/main/java/com/gargoylesoftware/htmlunit/util/NameValuePair.java index 5c3b6a15087..b1e19023b2d 100644 --- a/src/main/java/com/gargoylesoftware/htmlunit/util/NameValuePair.java +++ b/src/main/java/com/gargoylesoftware/htmlunit/util/NameValuePair.java @@ -28,7 +28,7 @@ * @author Nicolas Belisle * @author Ronald Brill */ -public class NameValuePair implements Serializable { +public class NameValuePair implements org.apache.http.NameValuePair, Serializable { /** The name. */ private final String name_; @@ -46,6 +46,11 @@ public NameValuePair(final String name, final String value) { value_ = value; } + public NameValuePair(final org.apache.http.NameValuePair httpPair) { + name_ = httpPair.getName(); + value_ = httpPair.getValue(); + } + /** * Returns the name. * @return the name diff --git a/src/main/java/com/gargoylesoftware/htmlunit/util/UrlUtils.java b/src/main/java/com/gargoylesoftware/htmlunit/util/UrlUtils.java index 88ed9e0105d..1c65c2f73a7 100644 --- a/src/main/java/com/gargoylesoftware/htmlunit/util/UrlUtils.java +++ b/src/main/java/com/gargoylesoftware/htmlunit/util/UrlUtils.java @@ -529,18 +529,27 @@ public static URL getUrlWithProtocolAndAuthority(final URL u) throws MalformedUR * @throws MalformedURLException if there is a problem creating the new URL */ public static URL getUrlWithNewUserName(final URL u, final String newUserName) throws MalformedURLException { + String newUserInfo = getUserInfoWithNewUserName(u.getUserInfo(), newUserName); + return createNewUrl(u.getProtocol(), newUserInfo, + u.getHost(), u.getPort(), u.getPath(), u.getRef(), u.getQuery()); + } + + /** + * Creates and returns a new UserInfo identical to the specified UserInfo but with a changed user name. + * @param userInfo the UserInfo on which to base the returned URL + * @param newUserName the new user name or null to remove it + * @return a new UserInfo identical to the specified UserInfo, only user name updated, or null if empty UserInfo + */ + public static String getUserInfoWithNewUserName(final String userInfo, final String newUserName){ String newUserInfo = newUserName == null ? "" : newUserName; - final String userInfo = u.getUserInfo(); if (org.apache.commons.lang3.StringUtils.isNotBlank(userInfo)) { final int colonIdx = userInfo.indexOf(':'); if (colonIdx > -1) { newUserInfo = newUserInfo + userInfo.substring(colonIdx); } } - return createNewUrl(u.getProtocol(), newUserInfo.isEmpty() ? null : newUserInfo, - u.getHost(), u.getPort(), u.getPath(), u.getRef(), u.getQuery()); + return newUserInfo.isEmpty() ? null : newUserInfo; } - /** * Creates and returns a new URL identical to the specified URL but with a changed user password. * @param u the URL on which to base the returned URL @@ -550,8 +559,19 @@ public static URL getUrlWithNewUserName(final URL u, final String newUserName) t */ public static URL getUrlWithNewUserPassword(final URL u, final String newUserPassword) throws MalformedURLException { + String newUserInfo = getUserInfoWithNewUserPassword(u.getUserInfo(), newUserPassword); + return createNewUrl(u.getProtocol(), newUserInfo, + u.getHost(), u.getPort(), u.getPath(), u.getRef(), u.getQuery()); + } + + /** + * Creates and returns a new UserInfo identical to the specified UserInfo but with a changed user password. + * @param userInfo the UserInfo on which to base the returned UserInfo + * @param newUserPassword the new user password or null to remove it + * @return a new UserInfo identical to the specified URL, only user name updated, or null if empty UserInfo + */ + public static String getUserInfoWithNewUserPassword(final String userInfo, final String newUserPassword) { String newUserInfo = newUserPassword == null ? "" : (':' + newUserPassword); - final String userInfo = u.getUserInfo(); if (org.apache.commons.lang3.StringUtils.isNotBlank(userInfo)) { final int colonIdx = userInfo.indexOf(':'); if (colonIdx > -1) { @@ -561,8 +581,7 @@ public static URL getUrlWithNewUserPassword(final URL u, final String newUserPas newUserInfo = userInfo + newUserInfo; } } - return createNewUrl(u.getProtocol(), newUserInfo.isEmpty() ? null : newUserInfo, - u.getHost(), u.getPort(), u.getPath(), u.getRef(), u.getQuery()); + return newUserInfo.isEmpty() ? null : newUserInfo; } /** diff --git a/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParamsTest.java b/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParamsTest.java index 76879e8c08e..e43a0fe0edf 100644 --- a/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParamsTest.java +++ b/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLSearchParamsTest.java @@ -371,6 +371,11 @@ public void getAll() throws Exception { */ @Test @Alerts(DEFAULT = {"key1=val1&key2=val2&key2=val3&key4=val4", + "key1=new1&key2=val2&key2=val3&key4=val4", + "key1=new1&key2=new2&key4=val4", + "key1=new1&key2=new2&key4=val4&key3=undefined", + "key1=new1&key2=new2&key4=null&key3=undefined", + "key1=val1&key2=val2&key2=val3&key4=val4", "key1=new1&key2=val2&key2=val3&key4=val4", "key1=new1&key2=new2&key4=val4", "key1=new1&key2=new2&key4=val4&key3=undefined", @@ -437,6 +442,17 @@ public void setFromUrl() throws Exception { + " log(param);\n" + " param.set('key4', null);\n" + " log(param);\n" + + " param = new URL('http://test.com/p?key1=val1&key2=val2&key2=val3').searchParams;\n" + + " param.set('key4', 'val4');\n" + + " log(param);\n" + + " param.set('key1', 'new1');\n" + + " log(param);\n" + + " param.set('key2', 'new2');\n" + + " log(param);\n" + + " param.set('key3', undefined);\n" + + " log(param);\n" + + " param.set('key4', null);\n" + + " log(param);\n" + " }\n" + " }\n" + " \n" diff --git a/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLTest.java b/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLTest.java index e2eea52f6ff..583acc7ec54 100644 --- a/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLTest.java +++ b/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/URLTest.java @@ -185,7 +185,7 @@ public void createObjectURL() throws Exception { * @throws Exception if an error occurs */ @Test - @Alerts(DEFAULT = {"", "a=u&x="}, + @Alerts(DEFAULT = {"", "a=u&x=", "a=b&x=", "x=", "x=&b=c"}, IE = {}) public void searchParams() throws Exception { final String html = @@ -199,6 +199,13 @@ public void searchParams() throws Exception { + " log(u.searchParams);\n" + " u = new URL('http://developer.mozilla.org/en-US/docs?a=u&x');\n" + " log(u.searchParams);\n" + + " var params = u.searchParams;\n" + + " params.set('a','b');\n" + + " log(params);\n" + + " params.delete('a');\n" + + " log(params);\n" + + " params.append('b', 'c');\n" + + " log(params);\n" + " }\n" + " }\n" + " \n"