diff --git a/static/xref/script.js b/static/xref/script.js
index 6c143ace..7696a39a 100644
--- a/static/xref/script.js
+++ b/static/xref/script.js
@@ -144,6 +144,10 @@ function renderResults(entries, query) {
return;
}
+ // Detect overloaded IDL entries that would produce identical citations.
+ // Build a set of "uri|forContext" pairs for entries that need disambiguation.
+ const overloadedPairs = detectOverloadedEntries(entries, term);
+
let html = '';
for (const entry of entries) {
// Use the canonical matched term when available (case-insensitive fallback
@@ -153,7 +157,7 @@ function renderResults(entries, query) {
const link = new URL(entry.uri, specInfo.url).href;
const title = escapeHTML(specInfo.title);
const cite = metadata.types.idl.has(entry.type)
- ? howToCiteIDL(citeTerm, entry)
+ ? howToCiteIDL(citeTerm, entry, overloadedPairs)
: metadata.types.markup.has(entry.type)
? howToCiteMarkup(citeTerm, entry)
: metadata.types.css.has(entry.type) ||
@@ -172,26 +176,124 @@ function renderResults(entries, query) {
output.innerHTML = html;
}
-function howToCiteIDL(term, entry) {
+/**
+ * Detects IDL entries that would produce identical citations (overloaded
+ * methods/constructors). Returns a Set of "uri|forContext" keys for entries
+ * that need disambiguation. Entries without a `for` list use an empty string
+ * as the forContext part.
+ */
+function detectOverloadedEntries(entries, term) {
+ const citationGroups = new Map();
+ for (const entry of entries) {
+ if (!metadata.types.idl.has(entry.type)) continue;
+ const specKey = entry.spec || '';
+ const statusKey = entry.status || '';
+ const forList = entry.for || [];
+ // Group per individual rendered citation (one per `f`), so overlapping
+ // `for` contexts across entries are correctly detected as ambiguous.
+ if (forList.length > 0) {
+ for (const f of forList) {
+ const key = `${f}|${term}|${specKey}|${statusKey}`;
+ const group = citationGroups.get(key) ?? [];
+ if (!group.length) citationGroups.set(key, group);
+ group.push({ entry, f });
+ }
+ } else {
+ const key = `|${term}|${specKey}|${statusKey}`;
+ const group = citationGroups.get(key) ?? [];
+ if (!group.length) citationGroups.set(key, group);
+ group.push({ entry, f: '' });
+ }
+ }
+ // Build a Set of "uri|forContext" strings for ambiguous (entry, f) pairs.
+ const overloadedPairs = new Set();
+ for (const group of citationGroups.values()) {
+ if (group.length > 1) {
+ for (const { entry, f } of group) {
+ overloadedPairs.add(`${entry.uri}|${f}`);
+ }
+ }
+ }
+ return overloadedPairs;
+}
+
+function howToCiteIDL(term, entry, overloadedPairs = null) {
const { type, for: forList } = entry;
const safeTerm = escapeHTML(term);
if (forList) {
return forList
.map(f => {
const safeF = escapeHTML(f);
- const termPart = type === 'enum-value' ? `"${safeTerm}"` : safeTerm;
- return `{{${safeF}/${safeTerm ? termPart : '""'}}}`;
+ let displayTerm = type === 'enum-value' ? `"${safeTerm}"` : safeTerm;
+ if (overloadedPairs?.has(`${entry.uri}|${f}`)) {
+ const hint = extractOverloadHint(entry.uri, f, term);
+ if (hint) {
+ displayTerm = displayTerm.replace('()', `(${escapeHTML(hint)})`);
+ }
+ }
+ return `{{${safeF}/${displayTerm ? displayTerm : '""'}}}`;
})
.join('
');
}
+ let cite;
switch (type) {
case 'exception':
if (!exceptionExceptions.has(term)) {
- return `{{"${safeTerm}"}}`;
+ cite = `{{"${safeTerm}"}}`;
+ break;
}
default:
- return `{{${safeTerm}}}`;
+ cite = `{{${safeTerm}}}`;
}
+ if (overloadedPairs?.has(`${entry.uri}|`)) {
+ const hint = extractOverloadHint(entry.uri, null, term);
+ if (hint) {
+ cite = cite.replace('()', `(${escapeHTML(hint)})`);
+ }
+ }
+ return cite;
+}
+
+/**
+ * Extracts a human-readable overload hint from a URI fragment.
+ *
+ * URI fragments for WebIDL definitions follow the pattern:
+ * #dom-----...
+ *
+ * For example:
+ * #dom-window-postmessage-message-targetorigin-transfer
+ * #dom-window-postmessage-message-options
+ *
+ * This function strips the known prefix (interface + method) and returns
+ * the remaining parts as a parameter list, e.g. "message, targetorigin, transfer".
+ */
+function extractOverloadHint(uri, forContext, term) {
+ if (!uri) return '';
+ const hash = uri.includes('#') ? uri.split('#')[1] : uri;
+ if (!hash) return '';
+
+ const originalParts = hash.split('-');
+ const lowerParts = hash.toLowerCase().split('-');
+
+ // Build the prefix to strip: typically "dom", interface, method
+ const prefixParts = ['dom'];
+ if (forContext) {
+ prefixParts.push(...forContext.toLowerCase().split('-'));
+ }
+ const cleanTerm = term.replace(/\(.*\)$/, '').toLowerCase();
+ if (cleanTerm) {
+ prefixParts.push(...cleanTerm.split('-'));
+ }
+
+ const matches =
+ prefixParts.length <= lowerParts.length &&
+ prefixParts.every((p, i) => lowerParts[i] === p);
+
+ if (matches && prefixParts.length < lowerParts.length) {
+ return originalParts.slice(prefixParts.length).join(', ');
+ }
+
+ return '';
}
function howToCiteMarkup(term, entry) {