diff --git a/test/wpt/README.md b/test/wpt/README.md index a0db61d..683a193 100644 --- a/test/wpt/README.md +++ b/test/wpt/README.md @@ -152,16 +152,18 @@ unrelated failed unit probe does not block that comparison. ## Current adapter scope -* Standards-mode HTML, `html`/`body`/`div`/`span`/`section`, IDs/classes, inline - declarations, embedded and linked stylesheets. ASCII/degree text and all +* Standards-mode HTML, `html`/`body`/`div`/`span`/`section`/`strong`/`aside`/`article`, + the unknown elements `flexbox`/`grid`/`container`/`item`, IDs/classes, inline + declarations, embedded and linked stylesheets. ASCII, degree and ellipsis (U+2026) text and all source whitespace are transported to the native layout engine. Comments do not interrupt a contiguous CSS text sequence. * 800 × 600 CSS pixels, DPR 1: the WPT viewport, not the physical board viewport. Pixel storage and rasterization use the simulator's embedded RGB565 path. * HTML body margins and root text color are installed at Gea's default-style priority, below author styles. The display list paints onto a white viewport. - Paragraph display and `1em` block margins are registered as UA element rules - by the test bridge. The shared cascade keeps them below author rules, + Paragraph display and `1em` block margins, bold `strong`, and inline display + for the unknown elements (HTML makes them `HTMLUnknownElement`) are registered + as UA element rules by the test bridge. The shared cascade keeps them below author rules, including universal selectors; the engine does not install HTML defaults. Selector lists are split for the native one-selector-per-rule API. Simple element/class selectors use their corresponding native registration APIs. @@ -179,10 +181,15 @@ unrelated failed unit probe does not block that comparison. a fresh tree and an initial layout/paint. This first version has no animation or mutation lifecycle and does not exercise the TSX compiler/app loader. * Scripts, further HTML defaults and display modes, text outside - the native ASCII/degree repertoire, XHTML, image assets, + the ASCII/degree/ellipsis repertoire, XHTML, image assets, at-rules other than basic TrueType/WOFF1 `@font-face`, `!important`, quirks mode, compound HTML/body selectors and unsupported HTML are explicit skips. These are adapter limitations, not evidence that the engine cannot support them. +* `display` declarations are transported for `block`, `flex`, `grid`, `none`, + `flow-root`, `list-item`, `inline`, `inline-block`, `inline-flex` and + `inline-grid`; any other value is + a skip. Whatever the engine does not implement for these values (such as + list markers) shows up as a failure, not a skip. * Apart from that disclosed color normalization, CSS properties and values are sent unchanged to the engine. The imported corpus includes interactions with features such as floats, flex-flow and order. A failure @@ -386,6 +393,10 @@ The default serif font is pinned upstream Gentium Plus. Its WOFF1 wrapper is decoded losslessly into SFNT tables, with bounds and checksums validated. Independent tests compare every decoded table with its source and verify the complete SFNT checksum. Pinned Ahem is also available as an installed test font. +Documents that name the generic `monospace` family also load DejaVu Sans Mono +2.35 from `test/wpt/fonts`, the rig's own font directory (upstream WPT pins no +monospace font). It is unmodified, pinned by SHA-256 in `fonts.mjs`, and +distributed under the Bitstream Vera license in `fonts/LICENSE-DejaVu.txt`. Font variants, general font matching/shaping, paragraph defaults, additional display modes and other character repertoires still need work. diff --git a/test/wpt/adapter.mjs b/test/wpt/adapter.mjs index 7fe26b8..1fdb6d2 100644 --- a/test/wpt/adapter.mjs +++ b/test/wpt/adapter.mjs @@ -3,7 +3,7 @@ import { parse } from 'parse5'; import postcss from 'postcss'; import valueParser from 'postcss-value-parser'; import { normalizeNamedColors } from './named-colors.mjs'; -import { defaultFont, ahemFont } from './fonts.mjs'; +import { defaultFont, ahemFont, monospaceFont } from './fonts.mjs'; export class Unsupported extends Error {} @@ -53,16 +53,17 @@ export function parseDocument(source, name, { readResource } = {}) { const dom = parse(source); if (dom.mode !== 'no-quirks') throw new Unsupported('Quirks-mode document'); const rules = [], references = [], specs = [], normalizations = [], fonts = [], properties = new Set(); - let title = name, fuzzy = null, count = 0; + let title = name, fuzzy = null, count = 0, monospace = false; function declarations(nodes) { return nodes.filter(n => n.type !== 'comment').map(n => { if (n.type !== 'decl') throw new Unsupported('Nested CSS or at-rule'); if (/url\s*\(/i.test(n.value)) throw new Unsupported('CSS resource/font loading'); if (n.important) throw new Unsupported('!important needs priority-preserving declaration transport'); - if (n.prop.toLowerCase() === 'display' && !/^(block|flex|grid|none)$/i.test(n.value)) throw new Unsupported('Only block/flex/grid/none display declarations are supported by this adapter'); + if (n.prop.toLowerCase() === 'display' && !/^(block|flex|grid|none|flow-root|list-item|inline|inline-block|inline-flex|inline-grid)$/i.test(n.value)) throw new Unsupported('Only block/flex/grid/none/flow-root/list-item/inline/inline-block/inline-flex/inline-grid display declarations are supported by this adapter'); if (n.prop.toLowerCase() === 'content') throw new Unsupported('Generated text content'); if (/^(animation|transition)(-|$)/i.test(n.prop)) throw new Unsupported('Animation/transition timing is not implemented by this static adapter'); properties.add(n.prop); + if (/^font(-family)?$/i.test(n.prop) && /\bmonospace\b/i.test(n.value)) monospace = true; const value = normalizeNamedColors(n.prop, n.value); if (value !== n.value) normalizations.push({ property: n.prop, from: n.value, to: value }); return [n.prop, value]; @@ -153,12 +154,12 @@ export function parseDocument(source, name, { readResource } = {}) { function element(n) { if (n.nodeName === '#comment') return null; if (n.nodeName === '#text') { - if (/[^\t\r\n\f\x20-\x7e\u00b0]/u.test(n.value)) throw new Unsupported('Text codepoint outside native runtime font repertoire'); + if (/[^\t\r\n\f\x20-\x7e\u00b0\u2026]/u.test(n.value)) throw new Unsupported('Text codepoint outside native runtime font repertoire'); if (++count > 450) throw new Unsupported('Document exceeds adapter node limit'); return { tag: '#text', text: n.value, attributes: [], inline: [], children: [] }; } if (n.tagName === 'head') return null; - if (!['html', 'body', 'div', 'span', 'section', 'p', 'br'].includes(n.tagName)) throw new Unsupported(`HTML element <${n.tagName}>`); + if (!['html', 'body', 'div', 'span', 'section', 'p', 'br', 'strong', 'aside', 'article', 'flexbox', 'grid', 'container', 'item'].includes(n.tagName)) throw new Unsupported(`HTML element <${n.tagName}>`); if (n.namespaceURI !== 'http://www.w3.org/1999/xhtml') throw new Unsupported('Non-HTML namespace'); if (++count > 450) throw new Unsupported('Document exceeds adapter node limit'); const attributes = [], inline = []; @@ -179,6 +180,7 @@ export function parseDocument(source, name, { readResource } = {}) { const tree = element(dom.childNodes.find(n => n.tagName === 'html')); if (!fonts.some(font => font.family.toLowerCase() === defaultFont.family)) fonts.unshift(defaultFont); if (!fonts.some(font => font.family.toLowerCase() === 'ahem')) fonts.push(ahemFont); + if (monospace && !fonts.some(font => font.family.toLowerCase() === 'monospace')) fonts.push(monospaceFont); return { title, tree, rules, fonts, references, specs, fuzzy, normalizations, properties: [...properties].sort() }; } diff --git a/test/wpt/adapter.test.mjs b/test/wpt/adapter.test.mjs index 54bcfb5..d8e6680 100644 --- a/test/wpt/adapter.test.mjs +++ b/test/wpt/adapter.test.mjs @@ -60,7 +60,7 @@ test('passes original selectors, ordered duplicate declarations and inline value }); test('fails closed on unsupported document semantics instead of weakening the test', () => { - for (const body of ['', '
', '', '', '', '', '', '', '']) { + for (const body of ['', '
', '', '', '', '', '', '', '']) { assert.throws(() => parseDocument(`${body}`, 'test.html'), Unsupported, body); } assert.throws(() => parseDocument('
', 'test.html'), /Quirks/); diff --git a/test/wpt/bridge.cpp b/test/wpt/bridge.cpp index 7fa9161..f0b3e5f 100644 --- a/test/wpt/bridge.cpp +++ b/test/wpt/bridge.cpp @@ -55,6 +55,11 @@ EMSCRIPTEN_KEEPALIVE int wpt_init(int width, int height) { sheet.registerUserAgentElementRule("p", "display", "block"); sheet.registerUserAgentElementRule("p", "margin-top", "1em"); sheet.registerUserAgentElementRule("p", "margin-bottom", "1em"); + sheet.registerUserAgentElementRule("aside", "display", "block"); + sheet.registerUserAgentElementRule("article", "display", "block"); + sheet.registerUserAgentElementRule("strong", "font-weight", "bold"); + // Elements HTML does not define are HTMLUnknownElement: inline unless styled. + for (const char *tag : {"flexbox", "grid", "container", "item"}) sheet.registerUserAgentElementRule(tag, "display", "inline"); return 1; } EMSCRIPTEN_KEEPALIVE int wpt_font(const char *family, const std::uint8_t *bytes, int length) { diff --git a/test/wpt/build.mjs b/test/wpt/build.mjs index a6d83af..714836d 100644 --- a/test/wpt/build.mjs +++ b/test/wpt/build.mjs @@ -43,5 +43,5 @@ for (let i = 0; i < sources.length; i++) { } fs.writeFileSync(stamp, identity); run(compiler, ['-O1', ...objects, '--no-entry', '-sALLOW_MEMORY_GROWTH=1', '-sMODULARIZE=1', '-sEXPORT_ES6=1', '-sENVIRONMENT=node', '-sEXPORTED_FUNCTIONS=["_malloc","_free"]', '-sEXPORTED_RUNTIME_METHODS=["ccall","HEAPU8"]', '-o', `${out}/wpt-renderer.mjs`]); -fs.writeFileSync(`${out}/wpt-build.json`, JSON.stringify({ packages: Object.fromEntries(Object.entries(env).filter(([k]) => ['GEA_CORE', 'GEA_HOST_DIR', 'GEA_ENGINE_DIR', 'GEA_ELEMENTS_DIR', 'GEA_GEAOS_PACKAGE_DIR'].includes(k))), compiler: run(compiler, ['--version'], true), flags, profile: 'simulator RGB565, CSS DPR 1, static HTML subset with pinned fonts and ASCII/degree text' }, null, 2)); +fs.writeFileSync(`${out}/wpt-build.json`, JSON.stringify({ packages: Object.fromEntries(Object.entries(env).filter(([k]) => ['GEA_CORE', 'GEA_HOST_DIR', 'GEA_ENGINE_DIR', 'GEA_ELEMENTS_DIR', 'GEA_GEAOS_PACKAGE_DIR'].includes(k))), compiler: run(compiler, ['--version'], true), flags, profile: 'simulator RGB565, CSS DPR 1, static HTML subset with pinned fonts and ASCII/degree/ellipsis text' }, null, 2)); console.log('Built dist/wpt-renderer.mjs using simulator display and shared framework sources.'); diff --git a/test/wpt/fonts.mjs b/test/wpt/fonts.mjs index 1fcd3da..46077b2 100644 --- a/test/wpt/fonts.mjs +++ b/test/wpt/fonts.mjs @@ -3,6 +3,13 @@ import { inflateSync } from 'node:zlib'; export const defaultFont = { family: 'serif', path: 'fonts/GentiumPlus-R.woff' }; export const ahemFont = { family: 'Ahem', path: 'fonts/Ahem.ttf' }; +// Rig-owned fonts live in test/wpt/fonts, outside the pinned upstream corpus, +// each pinned by its SHA-256. The CSS monospace family is DejaVu Sans Mono 2.35, +// unmodified, under the Bitstream Vera license in fonts/LICENSE-DejaVu.txt. +export const monospaceFont = { + family: 'monospace', path: 'DejaVuSansMono.ttf', rig: true, + sha256: '602ec86b8948cfcd956482fe64f94c36c867770149ef2f791d4613f443bcecb3', +}; const padded = n => Math.ceil(n / 4) * 4; function checksum(bytes) { let sum = 0; diff --git a/test/wpt/fonts/DejaVuSansMono.ttf b/test/wpt/fonts/DejaVuSansMono.ttf new file mode 100644 index 0000000..278cd78 Binary files /dev/null and b/test/wpt/fonts/DejaVuSansMono.ttf differ diff --git a/test/wpt/fonts/LICENSE-DejaVu.txt b/test/wpt/fonts/LICENSE-DejaVu.txt new file mode 100644 index 0000000..254e2cc --- /dev/null +++ b/test/wpt/fonts/LICENSE-DejaVu.txt @@ -0,0 +1,99 @@ +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. +Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + +Bitstream Vera Fonts Copyright +------------------------------ + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute the +Font Software, including without limitation the rights to use, copy, merge, +publish, distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to the +following conditions: + +The above copyright and trademark notices and this permission notice shall +be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional glyphs or characters may be added to the Fonts, only if the fonts +are renamed to names not containing either the words "Bitstream" or the word +"Vera". + +This License becomes null and void to the extent applicable to Fonts or Font +Software that has been modified and is distributed under the "Bitstream +Vera" names. + +The Font Software may be sold as part of a larger software package but no +copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING +ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +------------------------------ + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and +associated documentation files (the "Font Software"), to reproduce +and distribute the modifications to the Bitstream Vera Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to +the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Tavmjong Bah" or the word "Arev". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Tavmjong Bah Arev" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $ diff --git a/test/wpt/named-colors.mjs b/test/wpt/named-colors.mjs index 8806433..acc07ef 100644 --- a/test/wpt/named-colors.mjs +++ b/test/wpt/named-colors.mjs @@ -159,7 +159,7 @@ const colorProperties = new Set([ 'color', 'background', 'background-color', 'background-image', 'box-shadow', 'text-shadow', 'outline', 'outline-color', 'text-decoration', 'text-decoration-color', 'column-rule', 'column-rule-color', - 'fill', 'stroke', 'caret-color', 'accent-color', + 'fill', 'stroke', 'caret-color', 'accent-color', 'border-image', 'border-image-source', ]); // Only declaration values in color-bearing properties are eligible. Tokenizing diff --git a/test/wpt/run.mjs b/test/wpt/run.mjs index 6d52160..71dd68e 100644 --- a/test/wpt/run.mjs +++ b/test/wpt/run.mjs @@ -55,11 +55,17 @@ function readBytes(name) { return bytes; } function read(name) { return readBytes(name).toString('utf8'); } +function fontBytes(font) { + if (!font.rig) return readBytes(font.path); + const bytes = fs.readFileSync(path.join(root, 'fonts', font.path)); + if (createHash('sha256').update(bytes).digest('hex') !== font.sha256) throw new Error(`Rig font modified: ${font.path}`); + return bytes; +} // Check every imported source before running anything; missing or altered // fixtures are infrastructure errors, never skips or newly accepted baselines. for (const name of Object.keys(manifest.files)) readBytes(name); function render(document, actions = []) { - const fonts = (document.fonts || []).map(font => ({ family: font.family, bytes: sfntFont(readBytes(font.path)).toString('base64') })); + const fonts = (document.fonts || []).map(font => ({ family: font.family, bytes: sfntFont(fontBytes(font)).toString('base64') })); const r = spawnSync(process.execPath, [`${root}/render.mjs`], { input: JSON.stringify({ document, viewport: manifest.viewport, fonts, interactions: actions }), encoding: 'utf8', timeout: 30000, maxBuffer: 16 * 1024 * 1024,