sync: update from private repo (2336949a)
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
oss-sync
2026-09-17 11:29:31 +00:00
parent 28f74c4e37
commit 57702b4f34
2139 changed files with 321270 additions and 17494 deletions
@@ -0,0 +1,167 @@
// @vitest-environment jsdom
import { describe, it, expect } from 'vitest';
import { buildDumpHtmlScript } from './browser.js';
/**
* dumpHtml がページ内で走らせる文字列を jsdom の上で実際に評価して、
* 生の HTML に入力欄の中身が載らないことを見る(issue #1236 レビュー)。
*
* 入力した文字列を `value` **属性**へ書き戻すページ(React の制御された
* input など)では、打ち込んだ値がそのまま HTML に現れる。ここはその形を
* 直接置いて確かめる。ブラウザを通す経路の確認は
* browser.recording-secret.e2e.test.ts(http サーバ + 本物の Chromium)。
*/
function dump(html: string, selector?: string, depth = 3): string {
document.body.innerHTML = html;
return (0, eval)(buildDumpHtmlScript(selector, depth)) as string;
}
describe('buildDumpHtmlScript — value 属性に残った中身', () => {
it('パスワード欄の value 属性を伏せる', () => {
const out = dump('<input id="pw" type="password" name="password" value="hunter2-topsecret">');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('ふつうの欄の value 属性はそのまま残す(伏せ過ぎの回帰)', () => {
const out = dump('<input id="u" type="text" name="username" value="alice-visible">');
expect(out).toContain('value="alice-visible"');
expect(out).not.toContain('[masked]');
});
it('選んだ要素そのものが入力欄でも伏せる(根は querySelectorAll に出ない)', () => {
const out = dump(
'<input id="pw" type="password" name="password" value="hunter2-topsecret">',
'#pw',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('名札だけが手掛かりの欄も伏せる(「表示」で伏せ字を解いた欄)', () => {
const out = dump('<input id=":r3:" type="text" aria-label="パスワード" value="hunter2-topsecret">');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('`<label for>` が付いただけの欄も伏せる', () => {
const out = dump(
'<label for=":r3:">パスワード</label><input id=":r3:" type="text" value="hunter2-topsecret">',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('名札が取得範囲の外にあっても伏せる(複製の中だけを探さないこと)', () => {
// 取るのは `#field` だけ。名札はその外側にあるので、複製を作ってから
// 名札を探す実装では辿れず、パスワードが生の HTML に残る。
const out = dump(
'<label for=":r3:">パスワード</label>' +
'<div id="field"><input id=":r3:" type="text" value="hunter2-topsecret"></div>',
'#field',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('包んでいる `<label>` が取得範囲の外でも伏せる', () => {
// 取るのは `#field` だけ。複製は文書から切り離されるので、複製側で
// 祖先を辿ると `<label>` に届かず、パスワードが生の HTML に残る。
const out = dump(
'<label>パスワード<div id="field">' +
'<input id=":r7:" type="text" value="hunter2-topsecret"></div></label>',
'#field',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('入れ子の `<label>` に包まれた欄も伏せる', () => {
const out = dump('<label>パスワード<input id=":r4:" type="text" value="hunter2-topsecret"></label>');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('aria-labelledby で名札を指している欄も伏せる', () => {
const out = dump(
'<span id="lbl">パスワード</span>' +
'<input id=":r5:" type="text" aria-labelledby="lbl" value="hunter2-topsecret">',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('離れた名札がふつうの語なら伏せない(伏せ過ぎの回帰)', () => {
const out = dump(
'<label for=":r6:">Shipping address</label><input id=":r6:" type="text" value="tokyo-visible">',
);
expect(out).toContain('value="tokyo-visible"');
expect(out).not.toContain('[masked]');
});
it('autocomplete=one-time-code の欄も伏せる', () => {
const out = dump('<input id="c" type="text" autocomplete="one-time-code" value="482913">');
expect(out).not.toContain('482913');
expect(out).toContain('value="[masked]"');
});
it('textarea は中身(要素のテキスト)も伏せる', () => {
const out = dump('<textarea name="secretNote">hunter2-topsecret</textarea>');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('[masked]');
});
it('伏せる欄とふつうの欄が並んでいても、伏せるのは片方だけ', () => {
const out = dump(
'<form><input name="username" value="alice-visible">' +
'<input name="password" type="password" value="hunter2-topsecret"></form>',
);
expect(out).toContain('value="alice-visible"');
expect(out).toContain('value="[masked]"');
expect(out).not.toContain('hunter2-topsecret');
});
it('複製で印の数がずれたら、印の無い欄まで含めて全部伏せる', () => {
// 複製したときに中身が増える独自要素を置く。`cloneNode(true)` は複製した
// 独自要素をその場で昇格させるので、構築子がもう一度走って複製側だけ欄が
// 増える(issue #1236 レビュー 3 巡目の原因そのもの)。
//
// ここでは構築子が「文書の中の欄を複製して自分に足す」形にしてある。
// 印は複製に乗って運ばれるので、構築子が新しい欄を作るだけなら数はずれ
// ない(印の付いた欄は元も複製も 1 つずつ)。ずれるのは、印を付けた**後**
// に走る構築子が印付きの欄を複製したとき——複製側にだけ印が 1 つ増える。
class DupField extends HTMLElement {
constructor() {
super();
const proto = document.getElementById('proto');
if (proto) this.appendChild(proto.cloneNode(true));
}
}
if (!customElements.get('dup-field')) customElements.define('dup-field', DupField);
const out = dump(
'<div id="field">' +
'<input id="proto" type="password" name="password" value="hunter2-topsecret">' +
'<dup-field></dup-field>' +
'<input name="username" value="alice-visible">' +
'</div>',
'#field',
5,
);
// 数が合わない=どれが秘密か分からない。通す側ではなく全部伏せる側へ倒す。
// 印の付いていなかった利用者名の欄まで伏せられていることが要(合っている
// ときは「伏せるのは片方だけ」で、この欄は素通しになる)。
expect(out).not.toContain('hunter2-topsecret');
expect(out).not.toContain('alice-visible');
expect(out).toContain('value="[masked]"');
// 印は書き出しに残さない。
expect(out).not.toContain('data-ao-mask');
});
it('切り詰めや除去はこれまでどおり効く', () => {
const out = dump('<div><script>var a=1</script><p><b><i>deep</i></b></p></div>', 'div', 1);
expect(out).not.toContain('var a=1');
expect(out).toContain('truncated');
});
});