From 7309e457d5efd07eed89495fccd87feedac05ba9 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Mon, 13 May 2024 01:49:00 +0200 Subject: [PATCH] avm2: Transform css properties from with-dashes to snakeCase --- .../avm2/globals/flash/text/style_sheet.rs | 4 +-- core/src/html.rs | 2 +- core/src/html/stylesheet.rs | 26 +++++++++++++++++- tests/tests/swfs/avm2/stylesheet/Test.as | 10 +++++++ tests/tests/swfs/avm2/stylesheet/output.txt | 8 ++++++ tests/tests/swfs/avm2/stylesheet/test.swf | Bin 3234 -> 3311 bytes wstr/src/common.rs | 12 ++++++++ wstr/src/ops.rs | 17 ++++++++++++ 8 files changed, 75 insertions(+), 4 deletions(-) diff --git a/core/src/avm2/globals/flash/text/style_sheet.rs b/core/src/avm2/globals/flash/text/style_sheet.rs index 28c5c4f3d..b0804ab7f 100644 --- a/core/src/avm2/globals/flash/text/style_sheet.rs +++ b/core/src/avm2/globals/flash/text/style_sheet.rs @@ -1,6 +1,6 @@ use crate::avm2::parameters::ParametersExt; use crate::avm2::{Activation, Error, Object, TObject, Value}; -use crate::html::CssStream; +use crate::html::{transform_dashes_to_camel_case, CssStream}; use crate::string::AvmString; use ruffle_wstr::{WStr, WString}; @@ -25,7 +25,7 @@ pub fn inner_parse_css<'gc>( .construct(activation, &[])?; for (key, value) in properties.into_iter() { object.set_public_property( - AvmString::new(activation.gc(), key), + AvmString::new(activation.gc(), transform_dashes_to_camel_case(key)), Value::String(AvmString::new(activation.gc(), value)), activation, )?; diff --git a/core/src/html.rs b/core/src/html.rs index 5a73b030b..0e1c7a6a6 100644 --- a/core/src/html.rs +++ b/core/src/html.rs @@ -8,7 +8,7 @@ mod text_format; pub use dimensions::BoxBounds; pub use dimensions::Position; pub use layout::{LayoutBox, LayoutContent, LayoutMetrics}; -pub use stylesheet::CssStream; +pub use stylesheet::{transform_dashes_to_camel_case, CssStream}; pub use text_format::{FormatSpans, TextDisplay, TextFormat, TextSpan}; mod stylesheet; diff --git a/core/src/html/stylesheet.rs b/core/src/html/stylesheet.rs index 78d83d516..21443bebd 100644 --- a/core/src/html/stylesheet.rs +++ b/core/src/html/stylesheet.rs @@ -1,5 +1,6 @@ use fnv::FnvHashMap; -use ruffle_wstr::WStr; +use ruffle_wstr::{WStr, WString}; +use std::borrow::Cow; pub type CssProperties<'a> = FnvHashMap<&'a WStr, &'a WStr>; @@ -209,6 +210,29 @@ impl<'a> CssStream<'a> { } } +pub fn transform_dashes_to_camel_case(input: &WStr) -> Cow { + if !input.contains(b'-') { + return Cow::Borrowed(input); + } + let mut result = WString::with_capacity(input.len(), input.is_wide()); + + let mut make_upper = false; + let mut pos = 0; + while let Some(char) = input.get(pos) { + match char { + 0x002D if !make_upper => make_upper = true, // - as u16, can't use `as` in arm + _ if make_upper => { + make_upper = false; + result.push_str(&input[pos..=pos].to_ascii_uppercase()) + } + _ => result.push(char), + } + pos += 1; + } + + Cow::Owned(result) +} + // More exhaustive tests live inside avm2 stylesheet swf test // These are just some useful ones extracted out #[cfg(test)] diff --git a/tests/tests/swfs/avm2/stylesheet/Test.as b/tests/tests/swfs/avm2/stylesheet/Test.as index a60fd5150..b69f56273 100644 --- a/tests/tests/swfs/avm2/stylesheet/Test.as +++ b/tests/tests/swfs/avm2/stylesheet/Test.as @@ -186,6 +186,16 @@ "}"); }); + test("parseCSS: name transformation", styleSheet, function() { + styleSheet.clear(); + styleSheet.parseCSS("key {" + + "name-with-dashes: value" + + " name-with-dashes-and-spaces : value;" + + "this--one--has--two--dashes: value;" + + "UPPER-DASHES: value;" + + "}"); + }); + styleSheet = new AlwaysRedStyleSheet(); test("transform", styleSheet, function() { styleSheet.setStyle("anything", "blue"); diff --git a/tests/tests/swfs/avm2/stylesheet/output.txt b/tests/tests/swfs/avm2/stylesheet/output.txt index f98fda1a5..e5dcac336 100644 --- a/tests/tests/swfs/avm2/stylesheet/output.txt +++ b/tests/tests/swfs/avm2/stylesheet/output.txt @@ -189,6 +189,14 @@ styleSheet.getStyle("key") = { } styleSheet.getStyle("nonexistant") = {} +/// parseCSS: name transformation +styleSheet.getStyle("key") = { + "UPPERDASHES" = string "value" + "nameWithDashes" = string "value name-with-dashes-and-spaces : value" + "this-one-has-two-dashes" = string "value" +} +styleSheet.getStyle("nonexistant") = {} + /// transform styleSheet.getStyle("anything") = {} styleSheet.getStyle("nonexistant") = {} diff --git a/tests/tests/swfs/avm2/stylesheet/test.swf b/tests/tests/swfs/avm2/stylesheet/test.swf index 291437d54b19b86cfee3fe90743de5ec0117334c..96f01b3fa13acb2b1e97fd46a13c311fd0e57239 100644 GIT binary patch literal 3311 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 literal 3234 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 Ugpl~BC;OKqdKbX|0Cd7VY7$Rw=l}o! diff --git a/wstr/src/common.rs b/wstr/src/common.rs index f3385ca6e..674608f2f 100644 --- a/wstr/src/common.rs +++ b/wstr/src/common.rs @@ -346,6 +346,18 @@ impl WStr { super::ops::str_make_ascii_lowercase(self) } + /// Returns a new string with all ASCII characters mapped to their uppercase equivalent. + #[inline] + pub fn to_ascii_uppercase(&self) -> WString { + super::ops::str_to_ascii_uppercase(self) + } + + /// Converts this string to its ASCII uppercase equivalent in-place. + #[inline] + pub fn make_ascii_uppercase(&mut self) { + super::ops::str_make_ascii_uppercase(self) + } + /// Analogue of [`str::replace`]. #[inline] pub fn replace<'a, P: Pattern<'a>>(&'a self, pattern: P, with: &WStr) -> WString { diff --git a/wstr/src/ops.rs b/wstr/src/ops.rs index 7c36e1a3c..472263aa8 100644 --- a/wstr/src/ops.rs +++ b/wstr/src/ops.rs @@ -215,6 +215,23 @@ pub fn str_make_ascii_lowercase(s: &mut WStr) { } } +pub fn str_to_ascii_uppercase(s: &WStr) -> WString { + map_latin1_chars(s, |c| c.to_ascii_uppercase()) +} + +pub fn str_make_ascii_uppercase(s: &mut WStr) { + match s.units_mut() { + Units::Bytes(us) => us.make_ascii_uppercase(), + Units::Wide(us) => { + for c in us { + if let Ok(b) = u8::try_from(*c) { + *c = b.to_ascii_uppercase().into(); + } + } + } + } +} + pub fn str_is_latin1(s: &WStr) -> bool { match s.units() { Units::Bytes(_) => true,