Skip to content

Introduce a variant of url.searchParams that operates according to URL rules instead of <form> rules #491

Open
@domenic

Description

@domenic

Problem

See background in #18 and #478.

URLSearchParams was designed, not to hold URL query data, but instead to hold application/x-www-form-urlencoded data, i.e. the data that is sent to a server when submitting a HTML <form>.

Unfortunately, it was misnamed URLSearchParams instead of ApplicationXWWWFormURLEncodedParams. And, even more unfortunately, a property named searchParams was added to the URL class, which is an instance of the URLSearchParams class. Any attempts to use the searchParams class will give misleading information about the URL. And any attempts to manipulate it will change the contents of your URL's query string in unintended ways, converting values from a query string serialization (of the type produced by the URL parser) into an application/x-www-form-urlencoded serialization.

Some examples of how url.searchParams does not allow faithful introspection into the URL record:

const urlA = new URL('http://localhost:9999/segment?foo=bar/baz? boo');
const urlB = new URL('http://localhost:9999/segment?foo=bar%2Fbaz%3F%20boo');

// Not equal:
console.log(urlA.href); // "http://localhost:9999/segment?foo=bar/baz?%20boo"
console.log(urlB.href); // "http://localhost:9999/segment?foo=bar%2Fbaz%3F%20boo"

console.log(urlA.search); // "?foo=bar/baz?%20boo"
console.log(urlB.search); // "?foo=bar%2Fbaz%3F%20boo"

// Equal:
console.log(urlA.searchParams.get("foo")); // "bar/baz? boo"
console.log(urlB.searchParams.get("foo")); // "bar/baz? boo"

// Equal, but both different from search:
console.log(urlA.searchParams.toString()); // "foo=bar%2Fbaz%3F+boo"
console.log(urlB.searchParams.toString()); // "foo=bar%2Fbaz%3F+boo"

Some examples of how using url.searchParams for mutation will cause unintended changes to your URL record:

const url = new URL('http://httpbin.org/anything?a=~');

console.log(url.href);   // "http://httpbin.org/anything?a=~"
console.log(url.search); // "?a=~"

// This should be a no-op, but it is not:
url.searchParams.set("a", url.searchParams.get("a"));

console.log(url.href); // "http://httpbin.org/anything?a=%7E"
const url = new URL('http://httpbin.org/anything?a=~');

console.log(url.href);   // "http://httpbin.org/anything?a=~"
console.log(url.search); // "?a=~"

// This should not change the value of a, but it does:
url.searchParams.set("b", "d");

console.log(url.href); // "http://httpbin.org/anything?a=%7E&b=d"
const url = new URL('http://httpbin.org/anything?a=b c');

console.log(url.href);   // "http://httpbin.org/anything?a=b%20c"
console.log(url.search); // "?a=b%20c"

// This should be a no-op (sorting a single-element set), but it is not:
url.searchParams.sort();

console.log(url.href); // "http://httpbin.org/anything?a=b+c"

Solution

In #478 (comment) I proposed four solutions to this problem. In response, @ricea (Chromium) and @achristensen07 (WebKit) indicated they were "in favor of maintaining the status quo". I interpret this as meaning that any changes to either the URL query string parser/serializer, or the application/x-www-form-urlencoded parser/serializer, or the URLSearchParams class and url.searchParams member, are not on the table.

Given these constraints, it seems the only thing we could do is propose a new non-breaking addition to the API. As such, I propose a URLQueryParams class and a corresponding url.queryParams member, which are identical to URLSearchParams and url.searchParams, except that they use the URL parsing/serialization rules instead of the application/x-www-form-urlencoded rules. (Alternate names include url.realSearchParams or url.searchParams2.)

With that added, we could effectively deprecate url.searchParams (i.e., state loudly in the spec and MDN that using it will give unreliable results and mess up your URLs), and note that URLSearchParams is useful for representing <form> serialization, but not useful for manipulating URL search parameters.

(Optionally, we might want to define url.query / location.query / workerLocation.query as aliases for the corresponding .search properties, to fully align on the "query" naming and obsolete the "search" naming. But that's separable.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions