Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1d76f6e
fix(browser): address benchmark-discovered protocol and rendering fai…
Sep 17, 2026
c3cb521
test(parser): remove unnecessary mutable binding
Sep 17, 2026
610e333
fix(layout): exclude collapsed line-end spaces from overflow
Sep 17, 2026
17ec7dc
fix(network): project redirected request metadata
Sep 20, 2026
725b81f
fix(browser): address activation and document review regressions
Sep 20, 2026
98fcf16
Fix WebDriver activation boundaries and share network cleanup rules
Sep 20, 2026
4d33077
fix(layout): invalidate adopted stylesheet geometry
Sep 20, 2026
a92847d
test(cdp): declare redirect expectations explicitly
Sep 20, 2026
2715f79
test(curl): wait for proxy fixture connections
Sep 20, 2026
df44de5
fix(forms): preserve detached document property semantics
Sep 20, 2026
7b0bb9f
fix(review): preserve cache lifetime and validation contracts
Sep 21, 2026
569b6eb
fix(browser): preserve layout and navigation invariants
lanyue-llk Sep 24, 2026
bcff719
fix(parser): omit doctype from text documents
lanyue-llk Sep 24, 2026
ff5e9cd
fix(rebase): preserve explicit layout publication
lanyue-llk Sep 25, 2026
ad6dee5
fix(protocol): complete inspector rendering and navigation contracts
Sep 22, 2026
be2afd5
test(worker): use renderer stack budget for child message test
lanyue-llk Sep 22, 2026
0511ee0
fix(protocol): honor overlay payload and session lifecycle
lanyue-llk Sep 22, 2026
8ebed1f
fix: preserve per-target style state and bound vision blur memory
lanyue-llk Sep 22, 2026
9201a57
fix(emulation): compose context style defaults with target overrides
lanyue-llk Sep 24, 2026
4db9be8
fix(ci): retain Stylo baseline and use page-root emulation
lanyue-llk Sep 24, 2026
afe8782
test(cdp): keep style defaults within one frontend owner
lanyue-llk Sep 24, 2026
d3f9a66
Revert "fix(emulation): compose context style defaults with target ov…
lanyue-llk Sep 24, 2026
c03c493
Adapt screenshot emulation to rebased layout contract
lanyue-llk Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/actions/use-ci-release/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ runs:
using: composite
steps:
- name: Download release artifact
id: download
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: moli-release-${{ inputs.side }}
path: target/ci-artifacts/${{ inputs.side }}
- name: Check downloaded archive completeness
id: archive
continue-on-error: true
shell: bash
env:
RELEASE_SIDE: ${{ inputs.side }}
run: gzip -t "target/ci-artifacts/$RELEASE_SIDE/moli-release-$RELEASE_SIDE.tar.gz"
- name: Retry incomplete release download
if: steps.download.outcome != 'success' || steps.archive.outcome != 'success'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: moli-release-${{ inputs.side }}
Expand Down
3 changes: 3 additions & 0 deletions .github/scripts/unpack-ci-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ esac
release_name="moli-release-$side"
archive="target/ci-artifacts/$side/$release_name.tar.gz"
test -f "$archive"
# A download action can terminate with a partial extracted archive. Reject it
# before touching the executable directory, including after the bounded retry.
gzip -t "$archive"
mkdir -p target/ci-bin
tar -xzf "$archive" -C target/ci-bin
(cd "target/ci-bin/$release_name" && sha256sum --check SHA256SUMS)
Expand Down
60 changes: 60 additions & 0 deletions .github/scripts/unpack-ci-release.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';

const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const { createHash } = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const test = require('node:test');

const script = path.join(__dirname, 'unpack-ci-release.sh');

function fixture(t) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'moli-ci-release-test-'));
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const packageDir = path.join(root, 'package', 'moli-release-head');
const archive = path.join(root, 'target', 'ci-artifacts', 'head', 'moli-release-head.tar.gz');
fs.mkdirSync(packageDir, { recursive: true });
fs.mkdirSync(path.dirname(archive), { recursive: true });
const binary = Buffer.from('test release binary');
fs.writeFileSync(path.join(packageDir, 'moli'), binary);
fs.writeFileSync(path.join(packageDir, 'revision.txt'), 'expected-revision\n');
fs.writeFileSync(path.join(packageDir, 'SHA256SUMS'),
`${createHash('sha256').update(binary).digest('hex')} moli\n`);
function pack() {
const result = spawnSync('tar', ['-czf', archive, '-C', path.join(root, 'package'), 'moli-release-head']);
assert.equal(result.status, 0, String(result.stderr));
}
pack();
return { root, archive, packageDir, pack };
}

function unpack(root, revision = 'expected-revision') {
return spawnSync('bash', [script, 'head', revision], { cwd: root, encoding: 'utf8' });
}

test('valid archive passes payload and exact revision verification', (t) => {
const { root } = fixture(t);
const result = unpack(root);
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.readFileSync(path.join(root, 'target/ci-bin/moli-release-head/moli'), 'utf8'), 'test release binary');
});

test('truncated download cannot write a partial executable; redownload recovers', (t) => {
const { root, archive } = fixture(t);
const complete = fs.readFileSync(archive);
fs.writeFileSync(archive, complete.subarray(0, complete.length - 16));
assert.notEqual(unpack(root).status, 0);
assert.equal(fs.existsSync(path.join(root, 'target/ci-bin')), false);
fs.writeFileSync(archive, complete);
assert.equal(unpack(root).status, 0);
});

test('complete downloads still reject wrong revisions and altered payloads', (t) => {
const { root, packageDir, pack } = fixture(t);
assert.notEqual(unpack(root, 'wrong-revision').status, 0);
fs.writeFileSync(path.join(packageDir, 'moli'), 'tampered payload');
pack();
assert.notEqual(unpack(root).status, 0);
});
23 changes: 12 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ curl-sys = { git = "https://github.com/lexmount/curl-rust", rev = "a0ea59f5ca1b1
cookie = { git = "https://github.com/ldm0/cookie-rs", branch = "priority" }
v8 = { path = "vendor/v8-152.2.0" }
deno_v8 = { path = "vendor/deno_v8-0.3.0" }
selectors = { git = "https://github.com/lexmount/stylo", branch = "moli" }
servo_arc = { git = "https://github.com/lexmount/stylo", branch = "moli" }
stylo = { git = "https://github.com/lexmount/stylo", branch = "moli" }
stylo_atoms = { git = "https://github.com/lexmount/stylo", branch = "moli" }
stylo_dom = { git = "https://github.com/lexmount/stylo", branch = "moli" }
stylo_static_prefs = { git = "https://github.com/lexmount/stylo", branch = "moli" }
stylo_traits = { git = "https://github.com/lexmount/stylo", branch = "moli" }
selectors = { git = "https://github.com/lanyue-llk/stylo", rev = "3d44ed30808997861946a3f4b84a20318c4d451c" }
servo_arc = { git = "https://github.com/lanyue-llk/stylo", rev = "3d44ed30808997861946a3f4b84a20318c4d451c" }
stylo = { git = "https://github.com/lanyue-llk/stylo", rev = "3d44ed30808997861946a3f4b84a20318c4d451c" }
stylo_atoms = { git = "https://github.com/lanyue-llk/stylo", rev = "3d44ed30808997861946a3f4b84a20318c4d451c" }
stylo_dom = { git = "https://github.com/lanyue-llk/stylo", rev = "3d44ed30808997861946a3f4b84a20318c4d451c" }
stylo_static_prefs = { git = "https://github.com/lanyue-llk/stylo", rev = "3d44ed30808997861946a3f4b84a20318c4d451c" }
stylo_traits = { git = "https://github.com/lanyue-llk/stylo", rev = "3d44ed30808997861946a3f4b84a20318c4d451c" }
taffy = { git = "https://github.com/ldm0/taffy", rev = "645bf5a49df607a8bff34ec90c5343901d04f10e" }

[workspace.lints.clippy]
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ Layout is an on-demand snapshot rather than continuously maintained state. The
first geometry request (a cold start) builds a working layout tree from the
current DOM/style, freezes its canonical geometry into an immutable,
DOM-independent `FrozenLayoutTree`, and retains only that latest tree. Ordinary
geometry reads may reuse it even if the page has changed. Screenshots always
geometry reads reuse it while layout inputs are unchanged. Input changes mark
the retained tree dirty without running layout; the next geometry request
rebuilds it once from the current inputs. Screenshots always
rebuild and replace the frozen tree. Each screencast subscription remembers
only an opaque visual-state token: an unchanged token suppresses the poll,
while a changed token triggers one fresh frame. Paint results are never reused.
Expand Down
2 changes: 1 addition & 1 deletion docs/README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Rechenintensive Browser-Operationen sind in Moli standardmäßig deaktiviert und
| `--image`, `--font`, `--audio`, `--video`, `--media`, `--text-track` | Eine bestimmte optionale Ressourcengruppe einzeln aktivieren |
| `--profile-dir`, `--http-cache-dir`, `--cookie-file` | Persistenz gezielt aktivieren, je nach Bedarf des Workloads |

Das Layoutergebnis ist ein bei Bedarf erzeugter Snapshot, kein dauerhaft gepflegter Zustand: Die erste Geometrieanfrage (Kaltstart) baut aus dem aktuellen DOM/Stil einen temporären Arbeitsbaum auf und friert dessen kanonische Geometrie in einen unveränderlichen, vom DOM unabhängigen `FrozenLayoutTree` ein — nur dieser jeweils neueste Baum wird vorgehalten. Normale Geometrieabfragen können ihn auch nach Seitenänderungen weiterverwenden; Screenshots und Screencasts dagegen bauen jedes Mal neu auf, ersetzen den eingefrorenen Baum und greifen nie auf alte Zeichenergebnisse zurück.
Das Layoutergebnis ist ein bei Bedarf erzeugter Snapshot, kein dauerhaft gepflegter Zustand: Die erste Geometrieanfrage (Kaltstart) baut aus dem aktuellen DOM/Stil einen temporären Arbeitsbaum auf und friert dessen kanonische Geometrie in einen unveränderlichen, vom DOM unabhängigen `FrozenLayoutTree` ein — nur dieser jeweils neueste Baum wird vorgehalten. Normale Geometrieabfragen verwenden ihn wieder, solange die Layouteingaben unverändert sind. Änderungen markieren den Baum als veraltet, ohne sofort ein Layout auszuführen; erst die nächste Geometrieanfrage baut ihn neu auf. Screenshots und Screencasts dagegen bauen jedes Mal neu auf, ersetzen den eingefrorenen Baum und greifen nie auf alte Zeichenergebnisse zurück.

## Architektur

Expand Down
2 changes: 1 addition & 1 deletion docs/README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ En Moli, las operaciones caras del navegador hay que activarlas explícitamente:
| `--image`, `--font`, `--audio`, `--video`, `--media`, `--text-track` | Activa por separado una familia concreta de recursos opcionales |
| `--profile-dir`, `--http-cache-dir`, `--cookie-file` | Activa de forma selectiva la persistencia que necesite tu carga de trabajo |

El resultado del layout es una instantánea tomada bajo demanda, no un estado que se mantiene todo el tiempo: la primera petición de geometría (cold start) construye un árbol de trabajo temporal a partir del DOM y los estilos actuales, congela su geometría canónica en un `FrozenLayoutTree` inmutable e independiente del DOM, y se queda solo con ese árbol, el más reciente. Las lecturas de geometría normales pueden reutilizarlo aunque la página haya cambiado entretanto. Las capturas de pantalla siempre reconstruyen y sustituyen el árbol congelado. Cada suscripción de screencast solo conserva un token opaco del estado visual: un token sin cambios evita el trabajo, mientras que un cambio genera un frame nuevo. Los resultados de pintado nunca se reutilizan.
El resultado del layout es una instantánea tomada bajo demanda, no un estado que se mantiene todo el tiempo: la primera petición de geometría (cold start) construye un árbol de trabajo temporal a partir del DOM y los estilos actuales, congela su geometría canónica en un `FrozenLayoutTree` inmutable e independiente del DOM, y se queda solo con ese árbol, el más reciente. Las lecturas de geometría normales lo reutilizan mientras no cambien las entradas del layout. Los cambios marcan el árbol como desactualizado sin calcular el layout de inmediato; la siguiente petición de geometría lo reconstruye una sola vez. Las capturas de pantalla siempre reconstruyen y sustituyen el árbol congelado. Cada suscripción de screencast solo conserva un token opaco del estado visual: un token sin cambios evita el trabajo, mientras que un cambio genera un frame nuevo. Los resultados de pintado nunca se reutilizan.

## Arquitectura

Expand Down
2 changes: 1 addition & 1 deletion docs/README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Dans Moli, les opérations coûteuses du navigateur ne sont jamais activées par
| `--image`, `--font`, `--audio`, `--video`, `--media`, `--text-track` | Active une famille spécifique de ressources optionnelles |
| `--profile-dir`, `--http-cache-dir`, `--cookie-file` | Active, au cas par cas, la persistance dont la charge de travail a besoin |

Le résultat de la mise en page est un instantané produit à la demande, pas un état maintenu en continu : la première requête de géométrie (« à froid ») construit un arbre de travail temporaire à partir du DOM et des styles courants, fige sa géométrie canonique dans un `FrozenLayoutTree` immuable et indépendant du DOM, puis ne conserve que ce dernier. Les lectures de géométrie ultérieures peuvent réutiliser cet arbre figé, même si la page a changé entre-temps. Les captures d'écran reconstruisent toujours et remplacent l'arbre figé. Chaque abonnement de screencast ne mémorise qu'un jeton opaque d'état visuel : un jeton inchangé supprime le travail, tandis qu'un changement produit une image fraîche. Les résultats de rendu ne sont jamais réutilisés.
Le résultat de la mise en page est un instantané produit à la demande, pas un état maintenu en continu : la première requête de géométrie (« à froid ») construit un arbre de travail temporaire à partir du DOM et des styles courants, fige sa géométrie canonique dans un `FrozenLayoutTree` immuable et indépendant du DOM, puis ne conserve que ce dernier. Les lectures de géométrie réutilisent cet arbre tant que les données de mise en page restent inchangées. Une modification marque l’arbre comme périmé sans recalcul immédiat ; la prochaine requête de géométrie le reconstruit une seule fois. Les captures d'écran reconstruisent toujours et remplacent l'arbre figé. Chaque abonnement de screencast ne mémorise qu'un jeton opaque d'état visuel : un jeton inchangé supprime le travail, tandis qu'un changement produit une image fraîche. Les résultats de rendu ne sont jamais réutilisés.

## Architecture

Expand Down
2 changes: 1 addition & 1 deletion docs/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Moli では、処理負荷の高い機能は明示的に有効にした場合だ
| `--image`、`--font`、`--audio`、`--video`、`--media`、`--text-track` | 指定した種類のデータを取得します。 |
| `--profile-dir`、`--http-cache-dir`、`--cookie-file` | 必要な保存機能だけを選んで有効にします。 |

レイアウト結果は常に更新される状態ではなく、要求を受けた時点の状態です。最初に位置・寸法を要求されたときは、現在の DOM とスタイルから一時的な作業用レイアウト木を構築し、その確定済みジオメトリを DOM から独立した不変の `FrozenLayoutTree` に凍結して、最新の木だけを保持します。その後はページが変化していても通常の位置・寸法取得で古い木を再利用する場合があります。一方、画面画像と連続画面配信は毎回再構築して凍結木を置き換え、古い描画結果を再利用しません。
レイアウト結果は常に更新される状態ではなく、要求を受けた時点の状態です。最初に位置・寸法を要求されたときは、現在の DOM とスタイルから一時的な作業用レイアウト木を構築し、その確定済みジオメトリを DOM から独立した不変の `FrozenLayoutTree` に凍結して、最新の木だけを保持します。レイアウトの入力が変わらない間は、通常の位置・寸法取得でこの木を再利用します。入力が変わってもすぐにはレイアウトを計算せず、木を古い状態としてマークし、次の位置・寸法要求で一度だけ再構築します。一方、画面画像と連続画面配信は毎回再構築して凍結木を置き換え、古い描画結果を再利用しません。

## 仕組み

Expand Down
Loading