| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a3abc42 commit 925a464
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,19 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common.js'); | ||
| 3 | + | ||
| 4 | + const bench = common.createBenchmark(main, { | ||
| 5 | + type: ['URL', 'URLSearchParams'], | ||
| 6 | + n: [1e3, 1e6], | ||
| 7 | + }); | ||
| 8 | + | ||
| 9 | + function main({ type, n }) { | ||
| 10 | + const params = type === 'URL' ? | ||
| 11 | + new URL('https://nodejs.org').searchParams : | ||
| 12 | + new URLSearchParams(); | ||
| 13 | + | ||
| 14 | + bench.start(); | ||
| 15 | + for (let i = 0; i < n; i++) { | ||
| 16 | + params.append('test', i); | ||
| 17 | + } | ||
| 18 | + bench.end(n); | ||
| 19 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,29 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common.js'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + | ||
| 5 | + const bench = common.createBenchmark(main, { | ||
| 6 | + searchParams: ['true', 'false'], | ||
| 7 | + property: ['pathname', 'search', 'hash'], | ||
| 8 | + n: [1e6], | ||
| 9 | + }); | ||
| 10 | + | ||
| 11 | + function getMethod(url, property) { | ||
| 12 | + if (property === 'pathname') return (x) => url.pathname = `/${x}`; | ||
| 13 | + if (property === 'search') return (x) => url.search = `?${x}`; | ||
| 14 | + if (property === 'hash') return (x) => url.hash = `#${x}`; | ||
| 15 | + throw new Error(`Unsupported property "${property}"`); | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + function main({ searchParams, property, n }) { | ||
| 19 | + const url = new URL('https://nodejs.org'); | ||
| 20 | + if (searchParams === 'true') assert(url.searchParams); | ||
| 21 | + | ||
| 22 | + const method = getMethod(url, property); | ||
| 23 | + | ||
| 24 | + bench.start(); | ||
| 25 | + for (let i = 0; i < n; i++) { | ||
| 26 | + method(i); | ||
| 27 | + } | ||
| 28 | + bench.end(n); | ||
| 29 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -206,6 +206,7 @@ class URLContext { | |||
| 206 | 206 | } | |
| 207 | 207 | } | |
| 208 | 208 | ||
| 209 | + let setURLSearchParamsModified; | ||
| 209 | 210 | let setURLSearchParamsContext; | |
| 210 | 211 | let getURLSearchParamsList; | |
| 211 | 212 | let setURLSearchParams; | |
@@ -475,8 +476,9 @@ class URLSearchParams { | |||
| 475 | 476 | name = StringPrototypeToWellFormed(`${name}`); | |
| 476 | 477 | value = StringPrototypeToWellFormed(`${value}`); | |
| 477 | 478 | ArrayPrototypePush(this.#searchParams, name, value); | |
| 479 | + | ||
| 478 | 480 | if (this.#context) { | |
| 479 | - this.#context.search = this.toString(); | ||
| 481 | + setURLSearchParamsModified(this.#context); | ||
| 480 | 482 | } | |
| 481 | 483 | } | |
| 482 | 484 | ||
@@ -509,8 +511,9 @@ class URLSearchParams { | |||
| 509 | 511 | } | |
| 510 | 512 | } | |
| 511 | 513 | } | |
| 514 | + | ||
| 512 | 515 | if (this.#context) { | |
| 513 | - this.#context.search = this.toString(); | ||
| 516 | + setURLSearchParamsModified(this.#context); | ||
| 514 | 517 | } | |
| 515 | 518 | } | |
| 516 | 519 | ||
@@ -615,7 +618,7 @@ class URLSearchParams { | |||
| 615 | 618 | } | |
| 616 | 619 | ||
| 617 | 620 | if (this.#context) { | |
| 618 | - this.#context.search = this.toString(); | ||
| 621 | + setURLSearchParamsModified(this.#context); | ||
| 619 | 622 | } | |
| 620 | 623 | } | |
| 621 | 624 | ||
@@ -664,7 +667,7 @@ class URLSearchParams { | |||
| 664 | 667 | } | |
| 665 | 668 | ||
| 666 | 669 | if (this.#context) { | |
| 667 | - this.#context.search = this.toString(); | ||
| 670 | + setURLSearchParamsModified(this.#context); | ||
| 668 | 671 | } | |
| 669 | 672 | } | |
| 670 | 673 | ||
@@ -769,6 +772,20 @@ function isURL(self) { | |||
| 769 | 772 | class URL { | |
| 770 | 773 | #context = new URLContext(); | |
| 771 | 774 | #searchParams; | |
| 775 | + #searchParamsModified; | ||
| 776 | + | ||
| 777 | + static { | ||
| 778 | + setURLSearchParamsModified = (obj) => { | ||
| 779 | + // When URLSearchParams changes, we lazily update URL on the next read/write for performance. | ||
| 780 | + obj.#searchParamsModified = true; | ||
| 781 | + | ||
| 782 | + // If URL has an existing search, remove it without cascading back to URLSearchParams. | ||
| 783 | + // Do this to avoid any internal confusion about whether URLSearchParams or URL is up-to-date. | ||
| 784 | + if (obj.#context.hasSearch) { | ||
| 785 | + obj.#updateContext(bindingUrl.update(obj.#context.href, updateActions.kSearch, '')); | ||
| 786 | + } | ||
| 787 | + }; | ||
| 788 | + } | ||
| 772 | 789 | ||
| 773 | 790 | constructor(input, base = undefined) { | |
| 774 | 791 | markTransferMode(this, false, false); | |
@@ -814,7 +831,37 @@ class URL { | |||
| 814 | 831 | return `${constructor.name} ${inspect(obj, opts)}`; | |
| 815 | 832 | } | |
| 816 | 833 | ||
| 817 | - #updateContext(href) { | ||
| 834 | + #getSearchFromContext() { | ||
| 835 | + if (!this.#context.hasSearch) return ''; | ||
| 836 | + let endsAt = this.#context.href.length; | ||
| 837 | + if (this.#context.hasHash) endsAt = this.#context.hash_start; | ||
| 838 | + if (endsAt - this.#context.search_start <= 1) return ''; | ||
| 839 | + return StringPrototypeSlice(this.#context.href, this.#context.search_start, endsAt); | ||
| 840 | + } | ||
| 841 | + | ||
| 842 | + #getSearchFromParams() { | ||
| 843 | + if (!this.#searchParams?.size) return ''; | ||
| 844 | + return `?${this.#searchParams}`; | ||
| 845 | + } | ||
| 846 | + | ||
| 847 | + #ensureSearchParamsUpdated() { | ||
| 848 | + // URL is updated lazily to greatly improve performance when URLSearchParams is updated repeatedly. | ||
| 849 | + // If URLSearchParams has been modified, reflect that back into URL, without cascading back. | ||
| 850 | + if (this.#searchParamsModified) { | ||
| 851 | + this.#searchParamsModified = false; | ||
| 852 | + this.#updateContext(bindingUrl.update(this.#context.href, updateActions.kSearch, this.#getSearchFromParams())); | ||
| 853 | + } | ||
| 854 | + } | ||
| 855 | + | ||
| 856 | + /** | ||
| 857 | + * Update the internal context state for URL. | ||
| 858 | + * @param {string} href New href string from `bindingUrl.update`. | ||
| 859 | + * @param {boolean} [shouldUpdateSearchParams] If the update has potential to update search params (href/search). | ||
| 860 | + */ | ||
| 861 | + #updateContext(href, shouldUpdateSearchParams = false) { | ||
| 862 | + const previousSearch = shouldUpdateSearchParams && this.#searchParams && | ||
| 863 | + (this.#searchParamsModified ? this.#getSearchFromParams() : this.#getSearchFromContext()); | ||
| 864 | + | ||
| 818 | 865 | this.#context.href = href; | |
| 819 | 866 | ||
| 820 | 867 | const { | |
@@ -840,27 +887,39 @@ class URL { | |||
| 840 | 887 | this.#context.scheme_type = scheme_type; | |
| 841 | 888 | ||
| 842 | 889 | if (this.#searchParams) { | |
| 843 | - if (this.#context.hasSearch) { | ||
| 844 | - setURLSearchParams(this.#searchParams, this.search); | ||
| 845 | - } else { | ||
| 846 | - setURLSearchParams(this.#searchParams, undefined); | ||
| 890 | + // If the search string has updated, URL becomes the source of truth, and we update URLSearchParams. | ||
| 891 | + // Only do this when we're expecting it to have changed, otherwise a change to hash etc. | ||
| 892 | + // would incorrectly compare the URLSearchParams state to the empty URL search state. | ||
| 893 | + if (shouldUpdateSearchParams) { | ||
| 894 | + const currentSearch = this.#getSearchFromContext(); | ||
| 895 | + if (previousSearch !== currentSearch) { | ||
| 896 | + setURLSearchParams(this.#searchParams, currentSearch); | ||
| 897 | + this.#searchParamsModified = false; | ||
| 898 | + } | ||
| 847 | 899 | } | |
| 900 | + | ||
| 901 | + // If we have a URLSearchParams, ensure that URL is up-to-date with any modification to it. | ||
| 902 | + this.#ensureSearchParamsUpdated(); | ||
| 848 | 903 | } | |
| 849 | 904 | } | |
| 850 | 905 | ||
| 851 | 906 | toString() { | |
| 907 | + // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync. | ||
| 908 | + this.#ensureSearchParamsUpdated(); | ||
| 852 | 909 | return this.#context.href; | |
| 853 | 910 | } | |
| 854 | 911 | ||
| 855 | 912 | get href() { | |
| 913 | + // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync. | ||
| 914 | + this.#ensureSearchParamsUpdated(); | ||
| 856 | 915 | return this.#context.href; | |
| 857 | 916 | } | |
| 858 | 917 | ||
| 859 | 918 | set href(value) { | |
| 860 | 919 | value = `${value}`; | |
| 861 | 920 | const href = bindingUrl.update(this.#context.href, updateActions.kHref, value); | |
| 862 | 921 | if (!href) { throw new ERR_INVALID_URL(value); } | |
| 863 | - this.#updateContext(href); | ||
| 922 | + this.#updateContext(href, true); | ||
| 864 | 923 | } | |
| 865 | 924 | ||
| 866 | 925 | // readonly | |
@@ -1002,26 +1061,25 @@ class URL { | |||
| 1002 | 1061 | } | |
| 1003 | 1062 | ||
| 1004 | 1063 | get search() { | |
| 1005 | - if (!this.#context.hasSearch) { return ''; } | ||
| 1006 | - let endsAt = this.#context.href.length; | ||
| 1007 | - if (this.#context.hasHash) { endsAt = this.#context.hash_start; } | ||
| 1008 | - if (endsAt - this.#context.search_start <= 1) { return ''; } | ||
| 1009 | - return StringPrototypeSlice(this.#context.href, this.#context.search_start, endsAt); | ||
| 1064 | + // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync. | ||
| 1065 | + this.#ensureSearchParamsUpdated(); | ||
| 1066 | + return this.#getSearchFromContext(); | ||
| 1010 | 1067 | } | |
| 1011 | 1068 | ||
| 1012 | 1069 | set search(value) { | |
| 1013 | 1070 | const href = bindingUrl.update(this.#context.href, updateActions.kSearch, StringPrototypeToWellFormed(`${value}`)); | |
| 1014 | 1071 | if (href) { | |
| 1015 | - this.#updateContext(href); | ||
| 1072 | + this.#updateContext(href, true); | ||
| 1016 | 1073 | } | |
| 1017 | 1074 | } | |
| 1018 | 1075 | ||
| 1019 | 1076 | // readonly | |
| 1020 | 1077 | get searchParams() { | |
| 1021 | 1078 | // Create URLSearchParams on demand to greatly improve the URL performance. | |
| 1022 | 1079 | if (this.#searchParams == null) { | |
| 1023 | - this.#searchParams = new URLSearchParams(this.search); | ||
| 1080 | + this.#searchParams = new URLSearchParams(this.#getSearchFromContext()); | ||
| 1024 | 1081 | setURLSearchParamsContext(this.#searchParams, this); | |
| 1082 | + this.#searchParamsModified = false; | ||
| 1025 | 1083 | } | |
| 1026 | 1084 | return this.#searchParams; | |
| 1027 | 1085 | } | |
@@ -1041,6 +1099,8 @@ class URL { | |||
| 1041 | 1099 | } | |
| 1042 | 1100 | ||
| 1043 | 1101 | toJSON() { | |
| 1102 | + // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync. | ||
| 1103 | + this.#ensureSearchParamsUpdated(); | ||
| 1044 | 1104 | return this.#context.href; | |
| 1045 | 1105 | } | |
| 1046 | 1106 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,6 +43,42 @@ assert.strictEqual(sp.toString(), serialized); | |||
| 43 | 43 | ||
| 44 | 44 | assert.strictEqual(m.search, `?${serialized}`); | |
| 45 | 45 | ||
| 46 | + sp.delete('a'); | ||
| 47 | + values.forEach((i) => sp.append('a', i)); | ||
| 48 | + assert.strictEqual(m.href, `http://example.org/?${serialized}`); | ||
| 49 | + | ||
| 50 | + sp.delete('a'); | ||
| 51 | + values.forEach((i) => sp.append('a', i)); | ||
| 52 | + assert.strictEqual(m.toString(), `http://example.org/?${serialized}`); | ||
| 53 | + | ||
| 54 | + sp.delete('a'); | ||
| 55 | + values.forEach((i) => sp.append('a', i)); | ||
| 56 | + assert.strictEqual(m.toJSON(), `http://example.org/?${serialized}`); | ||
| 57 | + | ||
| 58 | + sp.delete('a'); | ||
| 59 | + values.forEach((i) => sp.append('a', i)); | ||
| 60 | + m.href = 'http://example.org'; | ||
| 61 | + assert.strictEqual(m.href, 'http://example.org/'); | ||
| 62 | + assert.strictEqual(sp.size, 0); | ||
| 63 | + | ||
| 64 | + sp.delete('a'); | ||
| 65 | + values.forEach((i) => sp.append('a', i)); | ||
| 66 | + m.search = ''; | ||
| 67 | + assert.strictEqual(m.href, 'http://example.org/'); | ||
| 68 | + assert.strictEqual(sp.size, 0); | ||
| 69 | + | ||
| 70 | + sp.delete('a'); | ||
| 71 | + values.forEach((i) => sp.append('a', i)); | ||
| 72 | + m.pathname = '/test'; | ||
| 73 | + assert.strictEqual(m.href, `http://example.org/test?${serialized}`); | ||
| 74 | + m.pathname = ''; | ||
| 75 | + | ||
| 76 | + sp.delete('a'); | ||
| 77 | + values.forEach((i) => sp.append('a', i)); | ||
| 78 | + m.hash = '#test'; | ||
| 79 | + assert.strictEqual(m.href, `http://example.org/?${serialized}#test`); | ||
| 80 | + m.hash = ''; | ||
| 81 | + | ||
| 46 | 82 | assert.strictEqual(sp[Symbol.iterator], sp.entries); | |
| 47 | 83 | ||
| 48 | 84 | let key, val; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,20 +11,33 @@ const assert = require('assert'); | |||
| 11 | 11 | ].forEach((i) => { | |
| 12 | 12 | assert.throws(() => Reflect.apply(URL.prototype[i], [], {}), { | |
| 13 | 13 | name: 'TypeError', | |
| 14 | - message: /Cannot read private member/, | ||
| 14 | + message: /Receiver must be an instance of class/, | ||
| 15 | 15 | }); | |
| 16 | 16 | }); | |
| 17 | 17 | ||
| 18 | 18 | [ | |
| 19 | 19 | 'href', | |
| 20 | + 'search', | ||
| 21 | + ].forEach((i) => { | ||
| 22 | + assert.throws(() => Reflect.get(URL.prototype, i, {}), { | ||
| 23 | + name: 'TypeError', | ||
| 24 | + message: /Receiver must be an instance of class/, | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + assert.throws(() => Reflect.set(URL.prototype, i, null, {}), { | ||
| 28 | + name: 'TypeError', | ||
| 29 | + message: /Cannot read private member/, | ||
| 30 | + }); | ||
| 31 | + }); | ||
| 32 | + | ||
| 33 | + [ | ||
| 20 | 34 | 'protocol', | |
| 21 | 35 | 'username', | |
| 22 | 36 | 'password', | |
| 23 | 37 | 'host', | |
| 24 | 38 | 'hostname', | |
| 25 | 39 | 'port', | |
| 26 | 40 | 'pathname', | |
| 27 | - 'search', | ||
| 28 | 41 | 'hash', | |
| 29 | 42 | ].forEach((i) => { | |
| 30 | 43 | assert.throws(() => Reflect.get(URL.prototype, i, {}), { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments