From 7a9e8fd6221515b48f45ba29d3f102eb899f1e33 Mon Sep 17 00:00:00 2001 From: Jon Robson Date: Fri, 15 Nov 2024 09:05:46 -0800 Subject: [PATCH] Stop using deprecated mediawiki.Uri module Use native URI instead. Bug: T380079 Change-Id: I7648562c0262076f6dc06f619ef39c106ea7b30f --- resources/dist/index.js | Bin 40933 -> 40969 bytes resources/dist/index.js.map.json | Bin 202229 -> 202770 bytes src/title.js | 28 +++++++++----- tests/node-qunit/title.test.js | 61 +++---------------------------- webpack.config.js | 4 +- 5 files changed, 26 insertions(+), 67 deletions(-) diff --git a/resources/dist/index.js b/resources/dist/index.js index 047f708eaac258a32b11338d4087125b522b05b0..b15a549d71b7eae91951ce2c92f9ea925586a591 100644 GIT binary patch delta 340 zcmX|+u}cFn6vnwuih~G(gP=!;Grjgc zlW?1h{b&3O{0B@6cKYCb-}~P0{e0J+=e5r@8g$Eb`m{S_c|rRl6+5X_c!vPz2HeP~ za9F<6Rxqr7>DKjar+wqCBIYg@$l=_XNv@cJ$8hO1Qtd6@K(vvGRR76UTT#e9QbcI$ z6wjbMnC{&xVu71jw7B7!ZAf-u44;s`(HYX1q@WTfsNa+Lm@y`!NE5*=sypdYsN*O? zhG~+3Ta7S<)I$EiO~}jn=}uWqPtNR;vkggeJU|>17(?t?2Hc4S3#N-_46{+xgIM>V o)4saY*+45$4%u>lcC_KoigSGI2MAXlX2$)Czh6 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 i;dA>fu-*0=A_ zJw9`0=FH5Q`DT3i{U`qO-V^V?eAeZWr_L_9`&06Rvt3v9ZuHfiE>}&S_)%chDrj-W z;@yIlWGrQ;HJyn**92p_mS)WGoy55~j^82%YMc>!s$Az_WZ8KwgRjuC_-5%_5nxI( zC=C3T3lD5V`T|uf~avH=1c4iue2vccDht+w`=lD92BnwhP%QGf__v3Gj z_m0!8IiU=jQ5OW#P!+Gl$Pp2pmHS?+@iaNOKqe(Bu%4{;mK1lIOe@f_VV7{P#oJR@ zx|~8bA@m**S*{{~{o2Hqh^X_HBGmb0LjLbHb4w%vcUVSia>ke`?bmm2DKBY@j4i6r z=1h6(b+4zB#bvP-VPT!BrfeQck=^lNO3Ub#RE;YJR&Zo&={mR9)sy?qd0({WAem0| zuy}bZ8Sgv6&kdZFF!;q@U)$_YIl?%j>H07Q1Uw4(|N9T9BD)Qs= zdq=xqLlm-Yr6CiV(TXft;dMEF;qca>LyQtpEc2dx`NF}G5UCRL;etGO;RRRV?k_KF zcRkx#pji9W!zU+;%9Th_e*BYN(Hbccvw~>ps?xaFBt~c?$5NzxT4iZFw~wgG+m#Y#JM79$wn{R41ck0%ufK_vFsYdtIhHdU?jv0a^=XTrUM#2dF;ymXT8%fGYn&4#0n26{|vtzTzO%& zZ;-02vWaDoomE^{=RpG!q9H8j7-4{UpB#q`dgF$$zxeo(^ze(7LpljyBNW}!k0nEd zVr;SISnMb31Rh>z85WYUtNqPXd?=dSq#=KMb@y}r2G`>8$<#m$;c+6Inr}O47%5%8 zwmsxqB3sjbwJb(wDZU6zni)m-KR@48;8zF=1+}J@Gbb$r9|I`5@Oyv~R6 zy|ta29V4w+G$S9Z9o;%eX*!@gl_!tNqA&Nn@xwzwr}jF$vPk~rb;Kq$1h98_cY1|) zUm}HJ9+3CnIO=b~!w&X6F$q|Rv$&5CGZ@Q6u+>?RGfG4RM#CFd=7NCGZ(SdCRb}q_ z_(*NNsSf1#u0OMTS>aRBuL=tYO})X(y$YkV0eScO{-?@7A?R9GsGMxee_rozLVm$Y zKuhUOUw%OCoEgJ3TAi>^cSVBjo7dtpbm}zux8)LiJ(*N3y+HXlKtuWSTa#PrGfpvr)Z%S9`gZKe6_ubl;#wHW z>f5u02z3E5??XahL@>Nqafpcq)CLbYp>l!X1z+JzCg6+H!a+IkK3KtWT_98tL-+5!+tLrg@C8pq3y z7R&;$`b2l>vCiWvMf%Eq3{(~ZjWuE!3;(YW%%~#k+K?R`@)ttZa~uPfH%L)_i|)Z(D0VaPQe)YUj(% zqG^pC8#}#k|93wTe|+%D>3cUHxVzt;Y*)v%@k*;>nfYcBKR1|)-E!Wtift3Wiv3~% P%OeH%nqT|UoiG0%XTNt~ diff --git a/src/title.js b/src/title.js index 9a7804e46..d294621c7 100644 --- a/src/title.js +++ b/src/title.js @@ -26,27 +26,37 @@ function isOwnPageAnchorLink( el ) { * @return {string|undefined} */ export function getTitle( href, config ) { - // Skip every URI that mw.Uri cannot parse + // Skip every URL that cannot be parsed let linkHref; try { - linkHref = new mw.Uri( href ); + linkHref = new URL( href ); } catch ( e ) { - return undefined; + // treat as relative URI + try { + linkHref = new URL( href, location.origin ); + } catch ( errRelative ) { + // could not be parsed. + return undefined; + } } // External links - if ( linkHref.host !== location.hostname ) { + if ( linkHref.hostname !== location.hostname ) { return undefined; } - const queryLength = Object.keys( linkHref.query ).length; + const searchParams = linkHref.searchParams; + // Note that we use Array.from and length rather than `size` as Selenium browser tests fail + // with `size` being undefined. `size` property is relatively new (April 2023) + // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/size + const queryLength = Array.from( searchParams ).length; let title; // No query params (pretty URL) if ( !queryLength ) { const pattern = mw.util.escapeRegExp( config.get( 'wgArticlePath' ) ).replace( '\\$1', '([^?#]+)' ), // eslint-disable-next-line security/detect-non-literal-regexp - matches = new RegExp( pattern ).exec( linkHref.path ); + matches = new RegExp( pattern ).exec( linkHref.pathname ); // We can't be sure decodeURIComponent() is able to parse every possible match try { @@ -54,12 +64,12 @@ export function getTitle( href, config ) { } catch ( e ) { // Will return undefined below } - } else if ( queryLength === 1 && 'title' in linkHref.query ) { + } else if ( queryLength === 1 && searchParams.has( 'title' ) ) { // URL is not pretty, but only has a `title` parameter - title = linkHref.query.title; + title = searchParams.get( 'title' ); } - return title ? `${ title }${ linkHref.fragment ? `#${ linkHref.fragment }` : '' }` : undefined; + return title ? `${ title }${ linkHref.hash ? linkHref.hash : '' }` : undefined; } /** diff --git a/tests/node-qunit/title.test.js b/tests/node-qunit/title.test.js index cf503614e..4dc7acec8 100644 --- a/tests/node-qunit/title.test.js +++ b/tests/node-qunit/title.test.js @@ -5,31 +5,24 @@ QUnit.module( 'title#getTitle', { this.config = new Map(); this.config.set( 'wgArticlePath', '/wiki/$1' ); - this.location = global.location = { hostname: 'en.wikipedia.org' }; - + this.location = global.location = { + origin: 'https://en.wikipedia.org', + hostname: 'en.wikipedia.org' + }; mw.util = { escapeRegExp: this.sandbox.spy( ( str ) => { return str.replace( /([\\{}()|.?*+\-^$[\]])/g, '\\$1' ); } ) }; - - mw.Uri = this.sandbox.stub().throws( 'UNIMPLEMENTED' ); }, afterEach() { global.location = null; mw.util = null; - mw.Uri = null; } } ); QUnit.test( 'it should return the title of a url with a title query param', function ( assert ) { const href = '/w/index.php?title=Foo'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - query: { - title: 'Foo' - } - } ); assert.strictEqual( getTitle( href, this.config ), @@ -40,11 +33,6 @@ QUnit.test( 'it should return the title of a url with a title query param', func QUnit.test( 'it should return the title of a pretty url if it conforms wgArticlePath', function ( assert ) { const href = '/wiki/Foo'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - path: href, - query: {} - } ); assert.strictEqual( getTitle( href, this.config ), @@ -55,11 +43,6 @@ QUnit.test( 'it should return the title of a pretty url if it conforms wgArticle QUnit.test( 'it should return the title of a pretty url properly decoded', function ( assert ) { const href = '/wiki/%E6%B8%AC%E8%A9%A6'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - path: href, - query: {} - } ); assert.strictEqual( getTitle( href, this.config ), @@ -70,12 +53,6 @@ QUnit.test( 'it should return the title of a pretty url properly decoded', funct QUnit.test( 'it should accept urls with fragments', function ( assert ) { let href = '/wiki/Example_1#footnote_1'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - path: href, - query: {}, - fragment: 'footnote_1' - } ); assert.strictEqual( getTitle( href, this.config ), @@ -84,11 +61,6 @@ QUnit.test( 'it should accept urls with fragments', function ( assert ) { ); href = '/w/index.php?title=Example_2#footnote_2'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - query: { title: 'Example_2' }, - fragment: 'footnote_2' - } ); assert.strictEqual( getTitle( href, this.config ), @@ -99,20 +71,12 @@ QUnit.test( 'it should accept urls with fragments', function ( assert ) { QUnit.test( 'it should skip pretty urls with invalid % encoded characters', function ( assert ) { const href = '/wiki/100%'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - path: href, - query: {} - } ); assert.strictEqual( getTitle( href, this.config ), undefined ); } ); -QUnit.test( 'it should skip urls that mw.Uri cannot parse', function ( assert ) { +QUnit.test( 'it should skip urls that URL cannot parse', function ( assert ) { const href = 'javascript:void(0);'; // eslint-disable-line no-script-url - mw.Uri.withArgs( href ).throws( - new Error( 'Cannot parse' ) - ); assert.strictEqual( getTitle( href, this.config ), @@ -123,11 +87,6 @@ QUnit.test( 'it should skip urls that mw.Uri cannot parse', function ( assert ) QUnit.test( 'it should skip urls that are external', function ( assert ) { const href = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; - mw.Uri.withArgs( href ).returns( { - host: 'www.youtube.com', - path: '/watch', - query: { v: 'dQw4w9WgXcQ' } - } ); assert.strictEqual( getTitle( href, this.config ), @@ -139,11 +98,6 @@ QUnit.test( 'it should skip urls that are external', function ( assert ) { QUnit.test( 'it should skip urls not on article path without one title query param', function ( assert ) { // No params let href = '/Foo'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - path: '/Foo', - query: {} - } ); assert.strictEqual( getTitle( href, this.config ), @@ -153,11 +107,6 @@ QUnit.test( 'it should skip urls not on article path without one title query par // Multiple query params href = '/Foo?a=1&title=Foo'; - mw.Uri.withArgs( href ).returns( { - host: this.location.hostname, - path: '/Foo', - query: { a: 1, title: 'Foo' } - } ); assert.strictEqual( getTitle( href, this.config ), diff --git a/webpack.config.js b/webpack.config.js index 280036ce5..f9d16e382 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -121,8 +121,8 @@ module.exports = ( env, argv ) => ( { // Minified uncompressed size limits for chunks / assets and entrypoints. Keep these numbers // up-to-date and rounded to the nearest 10th of a kibibyte so that code sizing costs are // well understood. Related to bundlesize minified, gzipped compressed file size tests. - maxAssetSize: 40.0 * 1024, - maxEntrypointSize: 40.0 * 1024, + maxAssetSize: 40.1 * 1024, + maxEntrypointSize: 40.1 * 1024, // The default filter excludes map files but we rename ours. assetFilter: ( filename ) => !filename.endsWith( srcMapExt )