Skip to content

Commit 71ee329

Browse files
committed
Support for URL and URLSearchParams
1 parent e6fcdf9 commit 71ee329

File tree

7 files changed

+871
-2
lines changed

7 files changed

+871
-2
lines changed

crates/wasm-rquickjs/skeleton/Cargo.toml_

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ encoding_rs = "0.8.35"
2626
futures = { version = "0.3.31", features = [] }
2727
futures-concurrency = "7.6.3"
2828
pin-project = "1.1.10"
29+
url = "2.5.7"
2930
wasi = "0.12.1+wasi-0.2.0"
3031
wasi-async-runtime = "0.1.2"
3132
wit-bindgen-rt = { version = "0.42.1", features = ["bitflags"] }

crates/wasm-rquickjs/skeleton/src/builtin/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod ieee754;
2020
mod process;
2121
mod streams;
2222
mod timeout;
23+
mod url;
2324
mod util;
2425

2526
pub fn add_module_resolvers(
@@ -49,6 +50,8 @@ pub fn add_module_resolvers(
4950
.with_module("__wasm_rquickjs_builtin/process_native")
5051
.with_module("node:process")
5152
.with_module("process")
53+
.with_module("__wasm_rquickjs_builtin/url_native")
54+
.with_module("__wasm_rquickjs_builtin/url")
5255
}
5356

5457
pub fn module_loader() -> (
@@ -77,7 +80,8 @@ pub fn module_loader() -> (
7780
.with_module(
7881
"__wasm_rquickjs_builtin/process_native",
7982
process::js_native_module,
80-
),
83+
)
84+
.with_module("__wasm_rquickjs_builtin/url_native", url::js_native_module),
8185
rquickjs::loader::BuiltinLoader::default()
8286
.with_module("__wasm_rquickjs_builtin/console", console::CONSOLE_JS)
8387
.with_module("__wasm_rquickjs_builtin/timeout", timeout::TIMEOUT_JS)
@@ -95,7 +99,8 @@ pub fn module_loader() -> (
9599
.with_module("node:fs", fs::FS_JS)
96100
.with_module("fs", fs::FS_JS)
97101
.with_module("node:process", process::PROCESS_JS)
98-
.with_module("process", process::PROCESS_JS),
102+
.with_module("process", process::PROCESS_JS)
103+
.with_module("__wasm_rquickjs_builtin/url", url::URL_JS),
99104
)
100105
}
101106

@@ -106,6 +111,7 @@ pub fn wire_builtins() -> String {
106111
writeln!(result, "{}", http::WIRE_JS).unwrap();
107112
writeln!(result, "{}", streams::WIRE_JS).unwrap();
108113
writeln!(result, "{}", encoding::WIRE_JS).unwrap();
114+
writeln!(result, "{}", url::WIRE_JS).unwrap();
109115

110116
result
111117
}
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
import * as urlNative from '__wasm_rquickjs_builtin/url_native';
2+
3+
// Based on https://github.com/jerrybendy/url-search-params-polyfill
4+
/**!
5+
* url-search-params-polyfill
6+
*
7+
* @author Jerry Bendy (https://github.com/jerrybendy)
8+
* @licence MIT
9+
*/
10+
11+
const __URLSearchParams__ = "__URLSearchParams__";
12+
13+
/**
14+
* Make a URLSearchParams instance
15+
*
16+
* @param {object|string|URLSearchParams} search
17+
* @constructor
18+
*/
19+
function URLSearchParamsPolyfill(search) {
20+
search = search || "";
21+
22+
// support construct object with another URLSearchParams instance
23+
if (search instanceof URLSearchParams || search instanceof URLSearchParamsPolyfill) {
24+
search = search.toString();
25+
}
26+
this [__URLSearchParams__] = parseToDict(search);
27+
}
28+
29+
const prototype = URLSearchParamsPolyfill.prototype;
30+
31+
/**
32+
* Appends a specified key/value pair as a new search parameter.
33+
*
34+
* @param {string} name
35+
* @param {string} value
36+
*/
37+
prototype.append = function (name, value) {
38+
appendTo(this [__URLSearchParams__], name, value);
39+
};
40+
41+
/**
42+
* Deletes the given search parameter, and its associated value,
43+
* from the list of all search parameters.
44+
*
45+
* @param {string} name
46+
*/
47+
prototype['delete'] = function (name) {
48+
delete this [__URLSearchParams__] [name];
49+
};
50+
51+
/**
52+
* Returns the first value associated to the given search parameter.
53+
*
54+
* @param {string} name
55+
* @returns {string|null}
56+
*/
57+
prototype.get = function (name) {
58+
var dict = this [__URLSearchParams__];
59+
return this.has(name) ? dict[name][0] : null;
60+
};
61+
62+
/**
63+
* Returns all the values association with a given search parameter.
64+
*
65+
* @param {string} name
66+
* @returns {Array}
67+
*/
68+
prototype.getAll = function (name) {
69+
var dict = this [__URLSearchParams__];
70+
return this.has(name) ? dict [name].slice(0) : [];
71+
};
72+
73+
/**
74+
* Returns a Boolean indicating if such a search parameter exists.
75+
*
76+
* @param {string} name
77+
* @returns {boolean}
78+
*/
79+
prototype.has = function (name, value) {
80+
if (hasOwnProperty(this [__URLSearchParams__], name)) {
81+
if (value === undefined) {
82+
return true;
83+
} else {
84+
var dict = this [__URLSearchParams__];
85+
for (var i = 0; i < dict[name].length; i++) {
86+
if (dict[name][i] === value) {
87+
return true;
88+
}
89+
}
90+
return false;
91+
}
92+
} else {
93+
return false;
94+
}
95+
};
96+
97+
/**
98+
* Sets the value associated to a given search parameter to
99+
* the given value. If there were several values, delete the
100+
* others.
101+
*
102+
* @param {string} name
103+
* @param {string} value
104+
*/
105+
prototype.set = function set(name, value) {
106+
this [__URLSearchParams__][name] = ['' + value];
107+
};
108+
109+
/**
110+
* Returns a string containing a query string suitable for use in a URL.
111+
*
112+
* @returns {string}
113+
*/
114+
prototype.toString = function () {
115+
var dict = this[__URLSearchParams__], query = [], i, key, name, value;
116+
for (key in dict) {
117+
name = encode(key);
118+
for (i = 0, value = dict[key]; i < value.length; i++) {
119+
query.push(name + '=' + encode(value[i]));
120+
}
121+
}
122+
return query.join('&');
123+
};
124+
125+
export const URLSearchParams = URLSearchParamsPolyfill;
126+
127+
var USPProto = URLSearchParams.prototype;
128+
129+
USPProto.polyfill = true;
130+
131+
// Fix #54, `toString.call(new URLSearchParams)` will return correct value when Proxy not used
132+
USPProto[Symbol.toStringTag] = 'URLSearchParams';
133+
134+
/**
135+
*
136+
* @param {function} callback
137+
* @param {object} thisArg
138+
*/
139+
if (!('forEach' in USPProto)) {
140+
USPProto.forEach = function (callback, thisArg) {
141+
var dict = parseToDict(this.toString());
142+
Object.getOwnPropertyNames(dict).forEach(function (name) {
143+
dict[name].forEach(function (value) {
144+
callback.call(thisArg, value, name, this);
145+
}, this);
146+
}, this);
147+
};
148+
}
149+
150+
/**
151+
* Sort all name-value pairs
152+
*/
153+
if (!('sort' in USPProto)) {
154+
USPProto.sort = function () {
155+
var dict = parseToDict(this.toString()), keys = [], k, i, j;
156+
for (k in dict) {
157+
keys.push(k);
158+
}
159+
keys.sort();
160+
161+
for (i = 0; i < keys.length; i++) {
162+
this['delete'](keys[i]);
163+
}
164+
for (i = 0; i < keys.length; i++) {
165+
var key = keys[i], values = dict[key];
166+
for (j = 0; j < values.length; j++) {
167+
this.append(key, values[j]);
168+
}
169+
}
170+
};
171+
}
172+
173+
/**
174+
* Returns an iterator allowing to go through all keys of
175+
* the key/value pairs contained in this object.
176+
*
177+
* @returns {function}
178+
*/
179+
if (!('keys' in USPProto)) {
180+
USPProto.keys = function () {
181+
var items = [];
182+
this.forEach(function (item, name) {
183+
items.push(name);
184+
});
185+
return makeIterator(items);
186+
};
187+
}
188+
189+
/**
190+
* Returns an iterator allowing to go through all values of
191+
* the key/value pairs contained in this object.
192+
*
193+
* @returns {function}
194+
*/
195+
if (!('values' in USPProto)) {
196+
USPProto.values = function () {
197+
var items = [];
198+
this.forEach(function (item) {
199+
items.push(item);
200+
});
201+
return makeIterator(items);
202+
};
203+
}
204+
205+
/**
206+
* Returns an iterator allowing to go through all key/value
207+
* pairs contained in this object.
208+
*
209+
* @returns {function}
210+
*/
211+
if (!('entries' in USPProto)) {
212+
USPProto.entries = function () {
213+
var items = [];
214+
this.forEach(function (item, name) {
215+
items.push([name, item]);
216+
});
217+
return makeIterator(items);
218+
};
219+
}
220+
221+
USPProto[Symbol.iterator] = USPProto[Symbol.iterator] || USPProto.entries;
222+
223+
if (!('size' in USPProto)) {
224+
Object.defineProperty(USPProto, 'size', {
225+
get: function () {
226+
var dict = parseToDict(this.toString())
227+
if (USPProto === this) {
228+
throw new TypeError('Illegal invocation at URLSearchParams.invokeGetter')
229+
}
230+
return Object.keys(dict).reduce(function (prev, cur) {
231+
return prev + dict[cur].length;
232+
}, 0);
233+
}
234+
});
235+
}
236+
237+
function encode(str) {
238+
var replace = {
239+
'!': '%21',
240+
"'": '%27',
241+
'(': '%28',
242+
')': '%29',
243+
'~': '%7E',
244+
'%20': '+',
245+
'%00': '\x00'
246+
};
247+
return encodeURIComponent(str).replace(/[!'\(\)~]|%20|%00/g, function (match) {
248+
return replace[match];
249+
});
250+
}
251+
252+
function decode(str) {
253+
return str
254+
.replace(/[ +]/g, '%20')
255+
.replace(/(%[a-f0-9]{2})+/ig, function (match) {
256+
return decodeURIComponent(match);
257+
});
258+
}
259+
260+
function makeIterator(arr) {
261+
var iterator = {
262+
next: function () {
263+
var value = arr.shift();
264+
return {done: value === undefined, value: value};
265+
}
266+
};
267+
268+
iterator[Symbol.iterator] = function () {
269+
return iterator;
270+
};
271+
272+
return iterator;
273+
}
274+
275+
function parseToDict(search) {
276+
var dict = {};
277+
278+
if (typeof search === "object") {
279+
// if `search` is an array, treat it as a sequence
280+
if (isArray(search)) {
281+
for (var i = 0; i < search.length; i++) {
282+
var item = search[i];
283+
if (isArray(item) && item.length === 2) {
284+
appendTo(dict, item[0], item[1]);
285+
} else {
286+
throw new TypeError("Failed to construct 'URLSearchParams': Sequence initializer must only contain pair elements");
287+
}
288+
}
289+
290+
} else {
291+
for (var key in search) {
292+
if (search.hasOwnProperty(key)) {
293+
appendTo(dict, key, search[key]);
294+
}
295+
}
296+
}
297+
298+
} else {
299+
// remove first '?'
300+
if (search.indexOf("?") === 0) {
301+
search = search.slice(1);
302+
}
303+
304+
var pairs = search.split("&");
305+
for (var j = 0; j < pairs.length; j++) {
306+
var value = pairs [j],
307+
index = value.indexOf('=');
308+
309+
if (-1 < index) {
310+
appendTo(dict, decode(value.slice(0, index)), decode(value.slice(index + 1)));
311+
312+
} else {
313+
if (value) {
314+
appendTo(dict, decode(value), '');
315+
}
316+
}
317+
}
318+
}
319+
320+
return dict;
321+
}
322+
323+
function appendTo(dict, name, value) {
324+
var val = typeof value === 'string' ? value : (
325+
value !== null && value !== undefined && typeof value.toString === 'function' ? value.toString() : JSON.stringify(value)
326+
);
327+
328+
// #47 Prevent using `hasOwnProperty` as a property name
329+
if (hasOwnProperty(dict, name)) {
330+
dict[name].push(val);
331+
} else {
332+
dict[name] = [val];
333+
}
334+
}
335+
336+
function isArray(val) {
337+
return !!val && '[object Array]' === Object.prototype.toString.call(val);
338+
}
339+
340+
function hasOwnProperty(obj, prop) {
341+
return Object.prototype.hasOwnProperty.call(obj, prop);
342+
}
343+

0 commit comments

Comments
 (0)