prop('href')/prop('src') ignore <base href> and skip area/base/script/embed/track/input
Describe the bug
prop('href') and prop('src') diverge from the DOM in two ways:
An in-document
<base href>is ignored. The HTML spec resolves URL-reflecting attributes against the document base URL, which is the first<base href>in the document when present, and the document URL otherwise. Cheerio always resolves againstoptions.baseURI, so every scraped URL on a page that uses<base href>comes out pointing at the wrong path.Only 6 of the 13 elements that reflect these attributes are resolved. Today
hrefis resolved foraandlink, andsrcforimg,iframe,audio,videoandsource(attributes.ts). The DOM also reflectshrefonareaandbase, andsrconscript,embed,trackandinput. For those elementsprop()silently returns the raw relative attribute instead of a resolved URL, which is easy to miss because the same call works for<a>.
Steps to reproduce
import { load } from 'cheerio';
const html = `
<head><base href="/sub/dir/"></head>
<body>
<a href="p.html">a</a>
<area href="p.html">
<script src="j.js"></script>
</body>`;
const $ = load(html, { baseURI: 'https://example.com/base/page.html' });
$('a').prop('href'); // 'https://example.com/base/p.html' <- ignores <base>
$('area').prop('href'); // 'p.html' <- not resolved at all
$('script').prop('src'); // 'j.js' <- not resolved at allSame document in jsdom (which follows the spec here):
import { JSDOM } from 'jsdom';
const d = new JSDOM(html, { url: 'https://example.com/base/page.html' }).window.document;
d.querySelector('a').href; // 'https://example.com/sub/dir/p.html'
d.querySelector('area').href; // 'https://example.com/sub/dir/p.html'
d.querySelector('script').src; // 'https://example.com/sub/dir/j.js'Expected behavior
prop('href') / prop('src') should match the DOM:
- resolve against the first
<base href>in the document when there is one, falling back tooptions.baseURI(a<base>'s ownhrefresolves against the document URL, not itself); - resolve
hreffora,area,base,linkandsrcforaudio,embed,iframe,img,input,script,source,track,video; - keep ignoring
<base>in XML mode, where the element carries no HTML semantics.
Invalid or missing <base href> values should be ignored rather than throwing, and the first
<base href> wins when a document contains several, as in the spec.
Environment
- cheerio version:
main(f8f3393) and 1.2.0 - Node.js: 26
- verified against jsdom 28 for DOM parity
Happy to open a PR — I have the fix and tests ready, following CONTRIBUTING (issue first).
Source: cheeriojs/cheerio