mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
fix(cssom): preserve keyframe mutation error semantics
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body
|
||||
data-ok=""
|
||||
data-insert-0=""
|
||||
data-insert-1=""
|
||||
data-length-after-insert=""
|
||||
data-first-rule=""
|
||||
data-second-rule=""
|
||||
data-length-after-delete=""
|
||||
data-remaining-rule=""
|
||||
data-group-insert-0=""
|
||||
data-group-insert-1=""
|
||||
data-group-length-after-insert=""
|
||||
data-group-first-parent=""
|
||||
data-group-first-sheet=""
|
||||
data-group-css-text-after-insert=""
|
||||
data-group-length-after-delete=""
|
||||
data-group-css-text-after-delete=""
|
||||
data-group-css-text-after-style=""
|
||||
data-keyframes-rule-brand=""
|
||||
data-keyframes-length=""
|
||||
data-keyframe-rule-brand=""
|
||||
data-keyframe-find=""
|
||||
data-keyframe-parent=""
|
||||
data-keyframe-sheet=""
|
||||
data-keyframes-after-append=""
|
||||
data-keyframes-after-keytext=""
|
||||
data-keyframes-after-style=""
|
||||
data-keyframes-after-csstext=""
|
||||
data-keyframe-type-after-csstext=""
|
||||
data-keyframes-after-delete=""
|
||||
data-keyframes-missing=""
|
||||
data-keyframe-invalid-error=""
|
||||
data-keyframe-invalid-preserved=""
|
||||
data-fontface-css-text-after-style=""
|
||||
>
|
||||
<script>
|
||||
const body = document.body;
|
||||
let ok = true;
|
||||
try {
|
||||
const sheet = new CSSStyleSheet();
|
||||
body.setAttribute('data-insert-0', String(sheet.insertRule('div { color: red; }', 0)));
|
||||
body.setAttribute('data-insert-1', String(sheet.insertRule('span { color: blue; }', 1)));
|
||||
body.setAttribute('data-length-after-insert', String(sheet.cssRules.length));
|
||||
body.setAttribute('data-first-rule', sheet.cssRules[0]?.cssText ?? '');
|
||||
body.setAttribute('data-second-rule', sheet.cssRules[1]?.cssText ?? '');
|
||||
sheet.deleteRule(1);
|
||||
body.setAttribute('data-length-after-delete', String(sheet.cssRules.length));
|
||||
body.setAttribute('data-remaining-rule', sheet.cssRules[0]?.cssText ?? '');
|
||||
|
||||
const groupSheet = new CSSStyleSheet();
|
||||
groupSheet.replaceSync('@media screen { div { color: red; } }');
|
||||
const media = groupSheet.cssRules[0];
|
||||
body.setAttribute('data-group-insert-0', String(media.insertRule('span { color: blue; }', 1)));
|
||||
body.setAttribute('data-group-insert-1', String(media.insertRule('main { display: block; }', 0)));
|
||||
body.setAttribute('data-group-length-after-insert', String(media.cssRules.length));
|
||||
body.setAttribute('data-group-first-parent', String(media.cssRules[0].parentRule === media));
|
||||
body.setAttribute('data-group-first-sheet', String(media.cssRules[0].parentStyleSheet === groupSheet));
|
||||
body.setAttribute('data-group-css-text-after-insert', media.cssText);
|
||||
media.deleteRule(2);
|
||||
body.setAttribute('data-group-length-after-delete', String(media.cssRules.length));
|
||||
body.setAttribute('data-group-css-text-after-delete', media.cssText);
|
||||
media.cssRules[0].style.color = 'green';
|
||||
body.setAttribute('data-group-css-text-after-style', media.cssText);
|
||||
|
||||
const keyframesSheet = new CSSStyleSheet();
|
||||
keyframesSheet.replaceSync('@keyframes fade { from { opacity: 0; } to { opacity: 1; } }');
|
||||
const keyframes = keyframesSheet.cssRules[0];
|
||||
body.setAttribute('data-keyframes-rule-brand', String(keyframes instanceof CSSKeyframesRule));
|
||||
body.setAttribute('data-keyframes-length', String(keyframes.cssRules.length));
|
||||
const fromRule = keyframes.findRule('0%');
|
||||
body.setAttribute('data-keyframe-rule-brand', String(fromRule instanceof CSSKeyframeRule));
|
||||
body.setAttribute('data-keyframe-find', fromRule?.keyText ?? '');
|
||||
body.setAttribute('data-keyframe-parent', String(fromRule.parentRule === keyframes));
|
||||
body.setAttribute('data-keyframe-sheet', String(fromRule.parentStyleSheet === keyframesSheet));
|
||||
keyframes.appendRule('50% { opacity: 0.5; transform: scale(1); }');
|
||||
body.setAttribute('data-keyframes-after-append', keyframes.cssText);
|
||||
const midRule = keyframes.findRule('50%');
|
||||
midRule.keyText = '75%';
|
||||
body.setAttribute('data-keyframes-after-keytext', keyframes.cssText);
|
||||
midRule.style.opacity = '0.75';
|
||||
body.setAttribute('data-keyframes-after-style', keyframes.cssText);
|
||||
midRule.cssText = '80% { opacity: 0.8; transform: scale(1); }';
|
||||
body.setAttribute('data-keyframes-after-csstext', keyframes.cssText);
|
||||
body.setAttribute('data-keyframe-type-after-csstext', String(midRule.type));
|
||||
let invalidKeyTextError;
|
||||
try {
|
||||
midRule.keyText = 'body';
|
||||
} catch (error) {
|
||||
invalidKeyTextError = error;
|
||||
}
|
||||
if (!(invalidKeyTextError instanceof DOMException) ||
|
||||
invalidKeyTextError.name !== 'SyntaxError' || invalidKeyTextError.code !== 12) {
|
||||
throw new Error('Invalid keyText must throw DOM SyntaxError');
|
||||
}
|
||||
body.setAttribute('data-keyframe-invalid-error', invalidKeyTextError.name);
|
||||
body.setAttribute('data-keyframe-invalid-preserved', midRule.keyText);
|
||||
keyframes.deleteRule('80%');
|
||||
body.setAttribute('data-keyframes-after-delete', keyframes.cssText);
|
||||
body.setAttribute('data-keyframes-missing', String(keyframes.findRule('80%') === null));
|
||||
|
||||
const fontSheet = new CSSStyleSheet();
|
||||
fontSheet.replaceSync('@font-face { font-family: Test; src: url(test.woff2); }');
|
||||
const fontRule = fontSheet.cssRules[0];
|
||||
fontRule.style.fontStyle = 'italic';
|
||||
body.setAttribute('data-fontface-css-text-after-style', fontRule.cssText);
|
||||
} catch (error) {
|
||||
ok = false;
|
||||
}
|
||||
body.setAttribute('data-ok', String(ok));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4156,7 +4156,7 @@ async fn chrome_cssstylesheet_rule_mutation_keeps_cssrules_list_operable_subset(
|
||||
let browser = Browser::new(AppConfig::default())?;
|
||||
|
||||
let page = browser
|
||||
.fetch(&server.url("/compat/chrome-cssstylesheet-rule-mutation"))
|
||||
.fetch(&server.url("/compat/chrome-cssstylesheet-rule-mutation-v2"))
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
@@ -4276,6 +4276,12 @@ async fn chrome_cssstylesheet_rule_mutation_keeps_cssrules_list_operable_subset(
|
||||
.unwrap()
|
||||
.contains("data-keyframe-type-after-csstext=\"8\"")
|
||||
);
|
||||
assert!(
|
||||
page.serialize_html_async()
|
||||
.await
|
||||
.unwrap()
|
||||
.contains("data-keyframe-invalid-error=\"SyntaxError\"")
|
||||
);
|
||||
assert!(
|
||||
page.serialize_html_async()
|
||||
.await
|
||||
|
||||
@@ -482,35 +482,34 @@ pub(crate) fn css_keyframes_rule_append_rule_callback<'s>(
|
||||
let attached_rule = parent_style_sheet.and_then(|sheet| {
|
||||
css_rule_attached_native_path(scope, this, sheet).map(|path| (sheet, path))
|
||||
});
|
||||
if let Some((sheet, path)) = attached_rule {
|
||||
if let Err(error) = apply_live_stylesheet_keyframe_rule_insert_mutation(
|
||||
let result = if let Some((sheet, path)) = attached_rule {
|
||||
apply_live_stylesheet_keyframe_rule_insert_mutation(
|
||||
scope,
|
||||
rules,
|
||||
&parsed.rule,
|
||||
index,
|
||||
sheet,
|
||||
&path,
|
||||
) {
|
||||
throw_insert_rule_error(scope, error);
|
||||
return;
|
||||
)
|
||||
} else {
|
||||
let result = apply_detached_keyframe_rule_insert_mutation(
|
||||
scope,
|
||||
this,
|
||||
rules,
|
||||
&parsed.rule,
|
||||
index,
|
||||
parent_style_sheet,
|
||||
);
|
||||
if result.is_ok() {
|
||||
sync_css_keyframes_rule_css_text_from_rules(scope, this, rules);
|
||||
}
|
||||
rv.set_undefined();
|
||||
return;
|
||||
result
|
||||
};
|
||||
match result {
|
||||
// Unlike CSSStyleSheet.insertRule, appendRule ignores invalid CSS.
|
||||
Ok(()) | Err(CssRuleInsertError::Syntax) => rv.set_undefined(),
|
||||
Err(error) => throw_insert_rule_error(scope, error),
|
||||
}
|
||||
|
||||
if let Err(error) = apply_detached_keyframe_rule_insert_mutation(
|
||||
scope,
|
||||
this,
|
||||
rules,
|
||||
&parsed.rule,
|
||||
index,
|
||||
parent_style_sheet,
|
||||
) {
|
||||
throw_insert_rule_error(scope, error);
|
||||
return;
|
||||
}
|
||||
sync_css_keyframes_rule_css_text_from_rules(scope, this, rules);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(crate) fn css_keyframes_rule_delete_rule_callback<'s>(
|
||||
|
||||
@@ -2195,11 +2195,13 @@ pub(crate) fn css_keyframe_rule_key_text_setter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Some(parsed) = webidl::parse_args::<CssKeyframeRuleKeyTextArgs>(scope, &args) else {
|
||||
let Some(key_text) =
|
||||
cssom_dom_string_property_value(scope, args.get(0), "CSSKeyframeRule", "keyText")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(key_text) = normalize_keyframe_selector_text_with_stylo(&parsed.key_text) else {
|
||||
rv.set_undefined();
|
||||
let Some(key_text) = normalize_keyframe_selector_text_with_stylo(&key_text) else {
|
||||
throw_dom_exception_value(scope, "Invalid keyframe selector.", "SyntaxError");
|
||||
return;
|
||||
};
|
||||
let this = css_rule_receiver(scope, &args);
|
||||
|
||||
@@ -876,13 +876,6 @@ pub struct CssKeyframesRuleFindRuleArgs {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "CSSKeyframeRule.keyText")]
|
||||
pub struct CssKeyframeRuleKeyTextArgs {
|
||||
#[webidl(required)]
|
||||
pub key_text: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "CSSStyleSheet.replace")]
|
||||
pub struct CssStyleSheetReplaceArgs {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
((bindings, values, label) => {
|
||||
const checks=[];
|
||||
const check=(name,fn)=>{try{const result=fn();checks.push({name:label+': '+name,passed:result===true,detail:result===true?null:String(result)});}catch(error){checks.push({name:label+': '+name,passed:false,detail:String(error)});}};
|
||||
const setter=Object.getOwnPropertyDescriptor(bindings.CSSKeyframeRule.prototype,'keyText').set;
|
||||
const append=bindings.CSSKeyframesRule.prototype.appendRule;
|
||||
const find=bindings.CSSKeyframesRule.prototype.findRule;
|
||||
const remove=bindings.CSSKeyframesRule.prototype.deleteRule;
|
||||
const syntaxError=error=>error instanceof bindings.DOMException && error.name==='SyntaxError' && error.code===12 && !(error instanceof bindings.SyntaxError);
|
||||
for(const mode of ['attached','constructed','detached']) {
|
||||
let owner;
|
||||
const sheet=mode==='attached' ? (()=>{owner=values.document.createElement('style');owner.textContent='@keyframes original {from {left:0px} to {left:100px}}';values.document.head.appendChild(owner);return owner.sheet;})() : new values.CSSStyleSheet();
|
||||
if(mode!=='attached')sheet.replaceSync('@keyframes original {from {left:0px} to {left:100px}}');
|
||||
const frames=sheet.cssRules[0],list=frames.cssRules,frame=list[0],declaration=frame.style;
|
||||
if(mode==='detached')sheet.deleteRule(0);
|
||||
const snapshot=()=>JSON.stringify([frame.keyText,frame.cssText,declaration.cssText,frames.cssText,frames.length,sheet.cssRules.length]);
|
||||
const initial=snapshot();
|
||||
const unchanged=()=>snapshot()===initial && frames.cssRules===list && list[0]===frame && frame.style===declaration;
|
||||
try {
|
||||
for(const [index,value] of ['', ' ', 'body', '-1%', '101%', '0', '0px', 'from,', 'from to', '0%, broken', '50% {}', '@keyframes k{}', '50%;', 'calc(50px)', undefined, null, false, 0, 1n, {}].entries()) {
|
||||
check(mode+' invalid keyText '+index+' throws DOM SyntaxError',()=>{let error;try{setter.call(frame,value);}catch(caught){error=caught;}return syntaxError(error);});
|
||||
check(mode+' invalid keyText '+index+' preserves state and identities',unchanged);
|
||||
}
|
||||
check(mode+' omitted setter value converts undefined',()=>{let error;try{setter.call(frame);}catch(caught){error=caught;}return syntaxError(error) && unchanged();});
|
||||
check(mode+' symbol keyText throws TypeError',()=>{let error;try{setter.call(frame,Symbol('selector'));}catch(caught){error=caught;}return error instanceof bindings.TypeError && unchanged();});
|
||||
check(mode+' keyText conversion exception identity',()=>{const sentinel=new values.Error('keyText');let calls=0,error;try{setter.call(frame,{[Symbol.toPrimitive](hint){calls++;if(hint!=='string')throw Error(hint);throw sentinel;}});}catch(caught){error=caught;}return error===sentinel && calls===1 && unchanged();});
|
||||
for(const [index,value] of ['', ' ', 'body {}', '-1% {}', '101% {}', '50px {}', '50% trailing {}', 'from, {}', '@media all {}', '50%;', 'from {} to {}', '0%, bad {}', undefined, null, false, 0, 1n, {}].entries()) {
|
||||
check(mode+' invalid appendRule '+index+' returns undefined',()=>append.call(frames,value)===undefined);
|
||||
check(mode+' invalid appendRule '+index+' preserves state and identities',unchanged);
|
||||
}
|
||||
check(mode+' appendRule missing argument throws TypeError',()=>{let error;try{append.call(frames);}catch(caught){error=caught;}return error instanceof bindings.TypeError && unchanged();});
|
||||
check(mode+' appendRule symbol throws TypeError',()=>{let error;try{append.call(frames,Symbol('rule'));}catch(caught){error=caught;}return error instanceof bindings.TypeError && unchanged();});
|
||||
check(mode+' appendRule conversion exception identity',()=>{const sentinel=new values.Error('appendRule');let calls=0,error;try{append.call(frames,{toString(){calls++;throw sentinel;}});}catch(caught){error=caught;}return error===sentinel && calls===1 && unchanged();});
|
||||
check(mode+' receiver validation precedes mutation conversion',()=>{let calls=0,error;try{setter.call(frames,{toString(){calls++;return '50%';}});}catch(caught){error=caught;}return error instanceof bindings.TypeError && calls===0 && unchanged();});
|
||||
check(mode+' valid keyText converts once and normalizes',()=>{let calls=0;const result=setter.call(frame,{[Symbol.toPrimitive](hint){calls++;if(hint!=='string')throw Error(hint);return ' 25% , to ';}});return result===undefined && calls===1 && frame.keyText==='25%, 100%' && frame.style===declaration && list[0]===frame && find.call(frames,'25%, to')===frame;});
|
||||
check(mode+' valid appendRule converts once and appends',()=>{let calls=0;const result=append.call(frames,{toString(){calls++;return '75% {left:75px}';}});return result===undefined && calls===1 && frames.length===3 && list[2]===find.call(frames,'75%') && list[2].style.left==='75px';});
|
||||
const last=list[2];
|
||||
check(mode+' duplicate selector returns last rule',()=>{append.call(frames,'75% {left:76px}');return frames.length===4 && find.call(frames,'75%')===list[3] && list[2]===last;});
|
||||
check(mode+' deleteRule deletes last duplicate only',()=>{remove.call(frames,'75%');return frames.length===3 && find.call(frames,'75%')===last;});
|
||||
check(mode+' selector list ordering stays significant',()=>find.call(frames,'100%, 25%')===null && find.call(frames,'25%, 100%')===frame);
|
||||
check(mode+' invalid findRule and deleteRule do not mutate',()=>{const before=snapshot();const result=find.call(frames,'body');remove.call(frames,'body');return result===null && snapshot()===before && frames.cssRules===list;});
|
||||
} finally {owner?.remove();}
|
||||
}
|
||||
return checks;
|
||||
})
|
||||
@@ -0,0 +1,93 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn css_keyframes_mutations_use_cssom_error_semantics_across_realms_and_rule_lifetimes() {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://css-keyframes.test/");
|
||||
vm.eval("document.body.innerHTML='<iframe id=child></iframe>'")
|
||||
.unwrap();
|
||||
let source = format!(
|
||||
"(() => {{const probe={};const realms=[globalThis,child.contentWindow];globalThis.mutationChecks=[];for(let a=0;a<2;a++)for(let b=0;b<2;b++)mutationChecks.push(...probe(realms[a],realms[b],a+'-'+b));return JSON.stringify(mutationChecks.filter(row=>!row.passed));}})()",
|
||||
include_str!("css_keyframes_mutations.js")
|
||||
);
|
||||
assert_eq!(vm.eval(&source).unwrap(), "[]");
|
||||
assert_eq!(vm.eval("mutationChecks.length").unwrap(), "1068");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn css_keyframes_mutations_accept_native_proxies_and_preserve_intrinsic_exception_realms() {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://css-keyframes.test/");
|
||||
vm.eval(r#"
|
||||
document.body.innerHTML='<iframe id=child></iframe>';
|
||||
globalThis.other=child.contentWindow;
|
||||
const sheet=new other.CSSStyleSheet();sheet.replaceSync('@keyframes native {from {left:0px}}');
|
||||
globalThis.keyframes=sheet.cssRules[0];globalThis.frame=keyframes[0];
|
||||
"#).unwrap();
|
||||
let context_ptr = &vm.page_default_runtime.context as *const _;
|
||||
vm.with_context_scope_by_ptr_and_checkpoint_for_test(context_ptr, |scope, _| {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
for (name, proxy_name) in [("keyframes", "keyframesProxy"), ("frame", "frameProxy")] {
|
||||
let key = crate::util::v8str(scope, name);
|
||||
let target =
|
||||
v8::Local::<v8::Object>::try_from(global.get(scope, key.into()).unwrap()).unwrap();
|
||||
let handler = crate::util::new_null_prototype_object(scope);
|
||||
let proxy = v8::Proxy::new(scope, target, handler).unwrap();
|
||||
moli_webapi_declare::register_web_api_proxy(scope, proxy).unwrap();
|
||||
let key = crate::util::v8str(scope, proxy_name);
|
||||
global.create_data_property(scope, key.into(), proxy.into());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(vm.eval(r#"
|
||||
(() => {
|
||||
const assert=(value,message)=>{if(!value)throw Error(message);};
|
||||
const realms=[globalThis,other],exceptions=realms.map(realm=>realm.DOMException);
|
||||
const descriptors=realms.map(realm=>Object.getOwnPropertyDescriptor(realm,'DOMException'));
|
||||
let constructorReads=0;
|
||||
try {
|
||||
for(const realm of realms)Object.defineProperty(realm,'DOMException',{configurable:true,get(){constructorReads++;throw Error('public constructor');}});
|
||||
for(let index=0;index<realms.length;index++) {
|
||||
const realm=realms[index],setter=Object.getOwnPropertyDescriptor(realm.CSSKeyframeRule.prototype,'keyText').set;
|
||||
for(const supplied of [true,false]) {
|
||||
let error;try{if(supplied)setter.call(frameProxy,'body');else setter.call(frameProxy);}catch(caught){error=caught;}
|
||||
assert(error instanceof exceptions[index] && !(error instanceof exceptions[1-index]) && error.name==='SyntaxError' && error.code===12,'native SyntaxError uses callee intrinsic');
|
||||
}
|
||||
assert(frame.keyText==='0%' && keyframes[0]===frame,'invalid setter preserves backing rule');
|
||||
assert(realm.CSSKeyframesRule.prototype.appendRule.call(keyframesProxy,'body {}')===undefined && keyframes.length===1,'invalid native append ignored');
|
||||
setter.call(frameProxy,'50%');assert(frame.keyText==='50%','valid native selector update');
|
||||
realm.CSSKeyframesRule.prototype.appendRule.call(keyframesProxy,'to {left:100px}');
|
||||
assert(keyframes.length===2 && keyframes[1].keyText==='100%','valid native append');
|
||||
realm.CSSKeyframesRule.prototype.deleteRule.call(keyframesProxy,'to');setter.call(frameProxy,'from');
|
||||
}
|
||||
return constructorReads===0;
|
||||
} finally {for(let index=0;index<realms.length;index++)Object.defineProperty(realms[index],'DOMException',descriptors[index]);}
|
||||
})()
|
||||
"#).unwrap(), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn css_keyframes_mutations_invalid_input_preserves_native_cascade_and_animation_identity() {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://css-keyframes.test/");
|
||||
vm.eval(r#"
|
||||
const style=document.createElement('style');style.textContent='@keyframes stable {from {left:0px} to {left:100px}}';document.head.appendChild(style);
|
||||
globalThis.keyframes=style.sheet.cssRules[0];globalThis.frame=keyframes[0];
|
||||
globalThis.item=document.createElement('div');document.body.appendChild(item);item.style.animation='stable 100s';
|
||||
globalThis.animation=item.getAnimations()[0];
|
||||
"#).unwrap();
|
||||
crate::live_stylesheet::reset_live_stylesheet_mutation_metrics_for_test();
|
||||
assert_eq!(vm.eval(r#"
|
||||
(() => {
|
||||
for(let index=0;index<20;index++) {
|
||||
let error;try{frame.keyText='body';}catch(caught){error=caught;}
|
||||
if(!(error instanceof DOMException) || error.name!=='SyntaxError')throw Error('invalid selector');
|
||||
if(keyframes.appendRule('body {}')!==undefined)throw Error('invalid append');
|
||||
}
|
||||
return frame.keyText==='0%' && keyframes.length===2 && keyframes[0]===frame && item.getAnimations()[0]===animation;
|
||||
})()
|
||||
"#).unwrap(), "true");
|
||||
let metrics = crate::live_stylesheet::live_stylesheet_mutation_metrics_for_test();
|
||||
assert_eq!(metrics.native_keyframe_mutations, 0);
|
||||
assert_eq!(metrics.native_rule_value_mutations, 0);
|
||||
assert_eq!(metrics.native_top_level_mutations, 0);
|
||||
assert_eq!(metrics.native_nested_mutations, 0);
|
||||
}
|
||||
+5
-1
@@ -460,7 +460,11 @@ fn css_keyframe_key_text_uses_stylo_selector_helpers() {
|
||||
keyframes.cssText,
|
||||
].join('/');
|
||||
|
||||
frame.keyText = 'body';
|
||||
let invalidError;
|
||||
try { frame.keyText = 'body'; } catch (error) { invalidError = error; }
|
||||
if (!(invalidError instanceof DOMException) || invalidError.name !== 'SyntaxError') {
|
||||
throw Error('Invalid keyText must throw DOM SyntaxError');
|
||||
}
|
||||
const afterInvalid = [
|
||||
frame.keyText,
|
||||
keyframes.findRule('body') === null,
|
||||
|
||||
@@ -2244,6 +2244,7 @@ mod cross_origin_symbol_fallback;
|
||||
mod cross_origin_window_indexes;
|
||||
mod cross_origin_window_names;
|
||||
mod css_animation_interfaces;
|
||||
mod css_keyframes_mutations;
|
||||
mod css_keyframes_receivers;
|
||||
mod device_events;
|
||||
mod payment_events;
|
||||
|
||||
@@ -431,6 +431,9 @@ const CHROME_OVERFLOW_PROPERTY_HTML: &str =
|
||||
include_str!("../../moli-core/tests/fixtures/runtime/chrome_overflow_property.html");
|
||||
const CHROME_CSSSTYLESHEET_RULE_MUTATION_HTML: &str =
|
||||
include_str!("../../moli-core/tests/fixtures/runtime/chrome_cssstylesheet_rule_mutation.html");
|
||||
const CHROME_CSSSTYLESHEET_RULE_MUTATION_V2_HTML: &str = include_str!(
|
||||
"../../moli-core/tests/fixtures/runtime/chrome_cssstylesheet_rule_mutation_v2.html"
|
||||
);
|
||||
const CHROME_DELETE_RULE_NO_CRASH_HTML: &str =
|
||||
include_str!("../../moli-core/tests/fixtures/runtime/chrome_delete_rule_no_crash.html");
|
||||
const CHROME_IMPORTANT_JS_OVERRIDE_HTML: &str =
|
||||
|
||||
@@ -1496,6 +1496,10 @@ pub(super) async fn chrome_cssstylesheet_rule_mutation_page() -> Html<&'static s
|
||||
Html(CHROME_CSSSTYLESHEET_RULE_MUTATION_HTML)
|
||||
}
|
||||
|
||||
pub(super) async fn chrome_cssstylesheet_rule_mutation_v2_page() -> Html<&'static str> {
|
||||
Html(CHROME_CSSSTYLESHEET_RULE_MUTATION_V2_HTML)
|
||||
}
|
||||
|
||||
pub(super) async fn chrome_delete_rule_no_crash_page() -> Html<&'static str> {
|
||||
Html(CHROME_DELETE_RULE_NO_CRASH_HTML)
|
||||
}
|
||||
|
||||
@@ -683,6 +683,10 @@ pub(super) fn build_router() -> Router {
|
||||
"/compat/chrome-cssstylesheet-rule-mutation",
|
||||
get(chrome_cssstylesheet_rule_mutation_page),
|
||||
)
|
||||
.route(
|
||||
"/compat/chrome-cssstylesheet-rule-mutation-v2",
|
||||
get(chrome_cssstylesheet_rule_mutation_v2_page),
|
||||
)
|
||||
.route(
|
||||
"/compat/chrome-delete-rule-no-crash",
|
||||
get(chrome_delete_rule_no_crash_page),
|
||||
|
||||
Reference in New Issue
Block a user