index.html (519 lines of code) (raw):

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="color-scheme" content="light dark"> <title>Mozilla Standards Positions</title> <link rel="preload" as="image" href="asset/Mozilla-2024.svg"> <link rel="preload" as="image" href="asset/WHATWG.svg"> <link rel="preload" as="image" href="asset/W3C.svg"> <link rel="preload" as="image" href="asset/W3C-CG.svg"> <link rel="preload" as="image" href="asset/IETF.svg"> <link rel="preload" as="image" href="asset/Ecma.png"> <link rel="preload" as="image" href="asset/Unicode.svg"> <link rel="preload" as="image" href="asset/Proposal.svg"> <link rel="preload" as="image" href="asset/Other.svg"> <link rel="preload" as="image" href="asset/bugzilla.png"> <link rel="preload" as="image" href="asset/MDN.svg"> <link rel="icon" href="asset/mozilla-icon.png"> <style> /* light scheme colors */ :root { --root-color: #333; --root-bg-color: #fff; --header-bg-color: #eee; --h1-color: black; --highlight-bg-color: #ffffd7; --highlight-color: inherit; --link-color: #2268a4; --row-hover-bg-color: #f5f5f5; --positive-color: white; --positive-bg-color: green; --negative-color: white; --negative-bg-color: maroon; --neutral-color: inherit; --neutral-bg-color: transparent; --defer-color: white; --defer-bg-color: #666; --table-border-color: silver; --position-border-color: gray; --input-bg-color: white; --input-color: black; /* under consideration uses the same colors as defer */ } /* dark scheme colors */ @media (prefers-color-scheme: dark) { :root { --root-color: #eee; --root-bg-color: #222; --header-bg-color: black; --highlight-bg-color: #440; --h1-color: white; --link-color: #6fb6f5; --row-hover-bg-color: black; --table-border-color: #444; --position-border-color: black; --positive-bg-color: #00a000; --positive-color: black; --negative-bg-color: #c65050; --negative-color: black; --defer-bg-color: #999; --defer-color: black; --input-bg-color: #171717; --input-color: white; } h1 img { filter: invert(1); } table img { background-color: white; border-radius: 16px; } } html { font-family: system-ui, sans-serif; color: var(--root-color); background-color: var(--root-bg-color); line-height: 1.5; scrollbar-gutter: stable; scroll-padding-top: calc(2.5em + 1px); } html.loading header ~ * { visibility: hidden; } body { margin: 2em; } header { background-color: var(--header-bg-color); margin: 0 auto 2em; padding: 3em; max-width: 66em; } h1 { font-size: min(12vw, 4em); margin: 0; color: var(--h1-color); line-height: 1; } h1 img { max-height: 0.83333333em; width: auto; } mark { background-color: var(--highlight-bg-color); color: var(--highlight-color); padding: 0.5em; margin: -0.5em; line-height: 2; box-decoration-break: clone; } :any-link { color: var(--link-color); padding: 0.25rem 0; } table :any-link { text-decoration: none; } table :any-link:hover { text-decoration: underline; } search { margin: 0 auto 2em; padding-block: 3em; padding-inline: min(3%, 3em); max-width: 66em; } search label { display: flex; gap: 1em; } input[name="search"] { width: 100%; margin-right: min(5%, 5em); border: 1px solid gray; border-radius: 3px; background-color: var(--input-bg-color); color: var(--input-color); } table { border-collapse: collapse; table-layout: fixed; width: 100%; margin: 0 auto 2em; max-width: 72em; min-width: 48em; } thead { position: sticky; top: 0; background-color: var(--root-bg-color); z-index: 2; } th { text-align: left; box-shadow: var(--table-border-color) 0 1px 0; white-space: nowrap; } td.specification { word-wrap: break-word; } td.concerns, td.topics { font-size: 0.75em; padding-bottom: 0.85em; text-wrap: balance; } td.venues, td.more-info { word-spacing: 8px; padding-top: 0.1em; padding-bottom: 0.1em; font-size: 1.5em; } td.more-info :any-link[title="Discussion"] { font-size: 0.75rem; vertical-align: 0.2rem; } td.more-info :any-link:not([title="Discussion"]) { padding: 0; text-decoration: none; } td.link :any-link { padding: 0.25rem; } th.concerns { width: 7em; } th.position { width: 4.25em; } th.topics { width: 3.5em; } th.venues { width: 4em; } th.more-info { width: 9.5em; } th, td { padding: 0.5rem; } th.link { width: 3em; text-align: center; } td.link { padding-left: 2.5em; } tr.details > td { padding-top: 0; padding-bottom: 0; border-top: none; } summary { position: relative; white-space: pre; top: calc(-2em - 1px); width: 0.9em; padding-left: 0.45em; overflow: hidden; cursor: default; user-select: none; } details:not([open]) { height: 0; } details dl { margin-top: -1em; grid-template-columns: 6em auto; } details dt { font-weight: bold; } dl { display: grid; grid-template-columns: max-content auto; row-gap: 1em; max-width: calc(100vw - 4em) } dt { text-align: right; } dd { margin-left: 1em; padding-left: 1px; } code { font-family: monospace, monospace; font-size-adjust: 0.5; } td { border-top: 1px solid var(--table-border-color); vertical-align: bottom; } tbody > tr:hover, tbody > tr:hover + tr.details, tbody > tr:has( + :where(tr.details:hover)) { background-color: var(--row-hover-bg-color); } tbody > tr:target, tbody > tr:target + tr.details { background-color: var(--highlight-bg-color); color: var(--highlight-color); } table img { width: auto; height: 24px; vertical-align: -2px; } span.position { border-radius: 0.6em; padding: 0.25em 0.5em; vertical-align: 1px; font-size: 0.75em; border: 1px solid var(--position-border-color); white-space: nowrap; } span.positive { background-color: var(--positive-bg-color); color: var(--positive-color); } span.negative { background-color: var(--negative-bg-color); color: var(--negative-color); } span.neutral { border-color: currentColor; } span.defer, span.under-consideration { background-color: var(--defer-bg-color); color: var(--defer-color); } /* sorting */ th.sortable { padding-top: 0; padding-bottom: 0; } th button { appearance: none; padding: 0.25em 0; font: inherit; display: inline; background-color: transparent; border: none; cursor: default; user-select: none; } th button::after { font-size: 0.8125em; content: " ▼"; opacity: 0.3; } th button:hover::after, th button:focus::after { opacity: 0.6 } th[aria-sort] button::after { opacity: 1; } th[aria-sort="descending"] button::after { content: " ▲"; } </style> <script> // Avoid a flash of empty table (FOET) document.documentElement.classList.add('loading'); setTimeout(() => { document.documentElement.classList.remove('loading'); }, 1000); </script> <meta name="description" content="This page tracks Mozilla's positions on open Web and Web-related specifications submitted to standards bodies like the IETF, W3C, WHATWG, and Ecma TC39."> </head> <body> <header> <h1><img src="asset/Mozilla-2024.svg" alt="Mozilla" width="240" height="50"> Standards Positions</h1> <p>This page tracks Mozilla's positions on open Web and Web-related specifications submitted to standards bodies like the <a href="https://www.ietf.org/">IETF</a>, <a href="https://www.w3.org/">W3C</a>, <a href="https://whatwg.org/">WHATWG</a>, and <a href="https://tc39.github.io/">Ecma TC39</a>.</p> <p>Please remember, this isn't a commitment to implement or participate; <strong>it's just what we think right now</strong>. See <a href="https://groups.google.com/a/mozilla.org/g/dev-platform/">dev-platform</a> to find out what we're implementing.</p> <p><mark>Want Mozilla's position on a specification? <a href="https://github.com/mozilla/standards-positions/">Find out more</a>.</mark></p> </header> <p class="noscript">Enable JavaScript to view the Mozilla standards positions, or see the <a href="https://github.com/mozilla/standards-positions/issues?q=is%3Aissue">issues in mozilla/standards-positions</a> on GitHub.</p> <search> <p><label>Search <input name="search"></label></p> </search> <table> <thead> <tr> <th class="link">Link</th> <th class="specification sortable">Specification</th> <th class="position sortable">Position</th> <th class="concerns sortable">Concerns</th> <th class="topics sortable">Topics</th> <th class="venues sortable">Venues</th> <th class="more-info sortable">More info</th> </tr> </thead> <tbody> </tbody> </table> <h2>Legend</h2> <p>The possible positions are:</p> <dl> <dt><span class="position positive">positive</span></dt><dd>Mozilla regards this work as a potential improvement to the web.</dd> <dt><span class="position neutral">neutral</span></dt><dd>Mozilla is not convinced of the merits of this work, but does not see any significant negative potential.</dd> <dt><span class="position negative">negative</span></dt><dd>Mozilla believes that pursuing this work in its current form would not be good for the web.</dd> <dt><span class="position defer">defer</span></dt><dd>Mozilla takes no position on this work.</dd> <dt><span class="position under-consideration">under consideration</span></dt><dd>Mozilla has not taken a position on this work and is gathering more information.</dd> </dl> <script> function getData() { // We need to delay the load event for URL fragments and text fragments to work reliably, // so use <iframe> instead of fetch(). const iframe = document.createElement('iframe'); iframe.hidden = true; iframe.src = "merged-data.json"; const p = new Promise(resolve => { iframe.onload = e => { const text = iframe.contentDocument.body.textContent; const data = JSON.parse(text); iframe.remove(); resolve(data); }; }); document.body.append(iframe); return p; } function expandInline(markdown) { const fragment = document.createDocumentFragment(); const codePattern = /`([^`]+)`/; while (true) { const match = codePattern.exec(markdown); if (!match) { // No more code segments, just append remaining text (if any) if (markdown) { fragment.appendChild(document.createTextNode(markdown)); } break; } const textBefore = markdown.slice(0, match.index); if (textBefore) { fragment.appendChild(document.createTextNode(textBefore)); } // Create a <code> element for the matched segment const codeElement = document.createElement('code'); codeElement.textContent = match[1]; fragment.appendChild(codeElement); // Slice off what we've already processed markdown = markdown.slice(match.index + match[0].length); } return fragment; } function expandLinks(markdown) { const fragment = document.createDocumentFragment(); const linkPattern = /\[([^\]]+)\]\(((?:#|https:\/\/)[^\)]+)\)/; while (true) { const match = linkPattern.exec(markdown); if (!match) { // No more links, parse the remaining text as inline if (markdown) { fragment.appendChild(expandInline(markdown)); } break; } // Text before the link const textBefore = markdown.slice(0, match.index); if (textBefore) { fragment.appendChild(expandInline(textBefore)); } // Create the <a> element for the matched link const linkElement = document.createElement('a'); linkElement.href = match[2]; // The link text itself can contain inline code linkElement.appendChild(expandInline(match[1])); fragment.appendChild(linkElement); // Slice the processed portion off markdown = markdown.slice(match.index + match[0].length); } return fragment; } function cell(nodes, className, colSpan) { const td = document.createElement('td'); if (nodes) { if (!Array.isArray(nodes)) { nodes = [nodes]; } td.append(...nodes); } if (className) { td.className = className; } if (colSpan) { td.colSpan = colSpan; } return td; } function link(href, child, helpText) { const a = document.createElement('a'); if (href) { a.setAttribute('href', href); } a.append(child); if (helpText) { a.setAttribute('aria-label', helpText); a.title = helpText; } return a; } function img(src, width, height, alt, title) { const img = document.createElement('img'); img.src = src; img.width = width; img.height = height; img.alt = alt; if (title) { img.title = title; } return img; } function positionSpan(position) { if (!position) { return null; } const span = document.createElement('span'); span.className = `position ${position.replace(/ /g, '-')}`; span.textContent = position; return span; } function dlItem(term, value) { const dt = document.createElement('dt'); dt.textContent = term; const dd = document.createElement('dd'); dd.append(expandLinks(value)); return [dt, dd]; } function details(description, rationale, describedById) { if (!description && !rationale) { return null; } const details = document.createElement('details'); const summary = document.createElement('summary'); summary.textContent = 'Details'; summary.setAttribute('aria-describedby', describedById); details.append(summary); const dl = document.createElement('dl'); details.append(dl); if (description) { dl.append(...dlItem('Description', description)); } if (rationale) { dl.append(...dlItem('Rationale', rationale)); } return details; } const imageMapping = { 'WHATWG': ['WHATWG.svg', 32, 32], 'W3C': ['W3C.svg', 47, 32], 'W3C CG': ['W3C-CG.svg', 32, 32], 'IETF': ['IETF.svg', 60, 32], 'Ecma': ['Ecma.png', 32, 32], 'Unicode': ['Unicode.svg', 32, 32], 'Proposal': ['Proposal.svg', 32, 32], 'Other': ['Other.svg', 32, 32], }; function venuesImages(venues) { const rv = []; for (const venue of venues) { if (rv.length > 0) { rv.push(" "); } const [filename, width, height] = imageMapping[venue]; rv.push(img(`asset/${filename}`, width, height, venue, venue)); } return rv; } function row(issueNum, { id, url, explainer, title, concerns, position, topics, venues, caniuse, bug, mdn, webkit, description, rationale }) { const tr = document.createElement('tr'); const rv = [tr]; // Link let selfId = ''; if (id) { selfId += id; } else { selfId += issueNum; } tr.id = selfId; tr.append(cell(link(`#${selfId}`, '🔗', 'Self-link'), 'link')); // Specification if (!url) { url = explainer; } const specLink = link(url, expandInline(title)); specLink.id = `speclink-${selfId}`; tr.append(cell(specLink, 'specification')); // Position tr.append(cell(positionSpan(position), 'positions')); tr.dataset.position = position; // Concerns tr.append(cell(concerns?.join(', '), 'concerns')); // Topics tr.append(cell(topics?.join(', '), 'topics')); tr.dataset.topics = topics?.join(' '); // Venues tr.append(cell(venuesImages(venues), 'venues')); tr.dataset.venues = venues?.join(' '); // More info const infoLinks = []; infoLinks.push(link(`https://github.com/mozilla/standards-positions/issues/${issueNum}`, `#${issueNum}`, 'Discussion')); if (bug) { infoLinks.push(" ", link(bug, img('asset/bugzilla.png', 32, 32, 'bug'), 'Mozilla bug')); } if (mdn) { infoLinks.push(" ", link(mdn, img('asset/MDN.svg', 32, 32, 'MDN'), 'MDN')); } if (caniuse) { infoLinks.push(" ", link(caniuse, '📊', 'Can I use')); } tr.append(cell(infoLinks, 'more-info')); // Details if (description || rationale) { const detailsTr = document.createElement('tr'); detailsTr.className = 'details'; detailsTr.append(cell(details(description, rationale, specLink.id), null, 7)); rv.push(detailsTr); } return rv; } // Expand details for :target function expandDetailsForTarget(id) { const tr = document.getElementById(id); if (!tr) { return; } if (tr.nextElementSibling.className === "details") { const details = tr.nextElementSibling.firstChild.firstChild; details.open = true; } } // Filter/search function filterTable(searchText) { const tbody = document.querySelector('tbody'); for (const tr of tbody.children) { if (tr.classList.contains('details')) { continue; } const show = shouldShowRow(searchText, tr); tr.hidden = !show; const nextTr = tr.nextElementSibling; if (nextTr.classList.contains('details')) { nextTr.hidden = !show; } } } function shouldShowRow(searchText, tr) { const specTitle = tr.firstChild.nextSibling.textContent; const position = tr.dataset.position; const concerns = tr.firstChild.nextSibling.nextSibling.nextSibling.textContent; const topics = tr.dataset.topics; const venues = tr.dataset.venues; const issueNum = tr.lastChild.firstChild.textContent; const allText = [specTitle, position, concerns, topics, venues, issueNum].filter(Boolean).join(' '); const links = tr.querySelectorAll('a[href]'); return allText.toLowerCase().includes(searchText.toLowerCase()) || [...links].find(a => a.getAttribute('href').includes(searchText)); } function setupSearch() { const searchInput = document.querySelector('input[name="search"]'); searchInput.oninput = e => filterTable(searchInput.value); if (searchInput.value) { filterTable(searchInput.value); } } // Sorting function setupSorting() { const table = document.querySelector("table"); const headers = table.querySelectorAll("th"); headers.forEach((header, columnIndex) => { if (!header.classList.contains('sortable')) { return; } const button = document.createElement("button"); // "more-info" is the column that starts out being sorted; in reverse order. const isInitDescending = header.classList.contains("more-info"); const sortNumerically = isInitDescending; // Coincidentally, this is the only numeric column. button.setAttribute("aria-label", `Sort by ${header.textContent}`); button.textContent = header.textContent; header.setAttribute("aria-label", header.textContent); header.textContent = ''; header.append(button); if (isInitDescending) { header.setAttribute("aria-sort", "descending"); } button.onclick = () => { const sortAscending = header.getAttribute("aria-sort") !== "ascending"; sortTable(table, columnIndex, sortAscending, sortNumerically); headers.forEach((h) => h.removeAttribute("aria-sort")); header.setAttribute("aria-sort", sortAscending ? "ascending" : "descending"); }; }); } function sortTable(table, columnIndex, ascending, sortNumerically) { const tbody = table.querySelector("tbody"); const rows = Array.from(tbody.querySelectorAll("tr[id]")); // Don't include .details rows rows.sort((rowA, rowB) => { const cellA = rowA.cells[columnIndex]; const cellB = rowB.cells[columnIndex]; let a = cellA.firstChild instanceof HTMLImageElement ? cellA.firstChild.alt : cellA.textContent; let b = cellB.firstChild instanceof HTMLImageElement ? cellB.firstChild.alt : cellB.textContent; // Handle empty cells: Always place them at the end if (a === "" || b === "") { if (a === "" && b === "") { return 0; } return a === "" ? 1 : -1; } [a, b] = ascending ? [a, b] : [b, a]; return a.localeCompare(b, undefined, { numeric: sortNumerically }); }); for (const row of rows) { const detailsRow = row.nextElementSibling?.className === 'details' ? row.nextElementSibling : null; tbody.append(row); if (detailsRow) { tbody.append(detailsRow); } } } async function init() { const data = await getData(); const tbody = document.querySelector('tbody'); for (const issueNum in data) { const item = data[issueNum]; // Remove the below if check to include issues without a position. if (item.position) { // Newest issue first tbody.prepend(...row(issueNum, item)); } } if (location.hash) { expandDetailsForTarget(location.hash.substr(1)); } onhashchange = e => { expandDetailsForTarget(e.newURL.split('#')[1]); }; setupSearch(); setupSorting(); document.querySelector('.noscript').remove(); document.documentElement.classList.remove('loading'); } init(); </script> </body> </html>