diff --git a/resources/dist/index.js b/resources/dist/index.js index 047f708ea..b15a549d7 100644 Binary files a/resources/dist/index.js and b/resources/dist/index.js differ diff --git a/resources/dist/index.js.map.json b/resources/dist/index.js.map.json index 7de713c7c..5f430adb2 100644 Binary files a/resources/dist/index.js.map.json and b/resources/dist/index.js.map.json differ 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 )