diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index a23dc7819e406..58431ccf17dfc 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -245,6 +245,8 @@ impl EditTextData<'_> { } impl<'gc> EditText<'gc> { + const ANY_NEWLINE: [char; 2] = ['\n', '\r']; + // This seems to be OS-independent const INPUT_NEWLINE: char = '\r'; @@ -1711,7 +1713,7 @@ impl<'gc> EditText<'gc> { let is_selectable = self.is_selectable(); match control_code { TextControlCode::Enter => { - self.text_input(Self::INPUT_NEWLINE, context); + self.text_input(Self::INPUT_NEWLINE.to_string(), context); } TextControlCode::MoveLeft | TextControlCode::MoveLeftWord @@ -1779,30 +1781,7 @@ impl<'gc> EditText<'gc> { break 'paste; } - let text = self.0.read().restrict.filter_allowed(&text); - let text = WString::from_utf8(&text); - let mut text = text.as_wstr(); - - if text.len() > self.available_chars() && self.available_chars() > 0 { - text = &text[0..self.available_chars()]; - } - - if text.len() <= self.available_chars() { - self.replace_text(selection.start(), selection.end(), text, context); - let new_pos = selection.start() + text.len(); - if is_selectable { - self.set_selection( - Some(TextSelection::for_position(new_pos)), - context.gc(), - ); - } else { - self.set_selection( - Some(TextSelection::for_position(self.text().len())), - context.gc(), - ); - } - changed = true; - } + self.text_input(text, context); } TextControlCode::Cut => { let text = &self.text()[selection.start()..selection.end()]; @@ -1979,28 +1958,31 @@ impl<'gc> EditText<'gc> { self.text().get(pos).unwrap_or(0) == '\n' as u16 } - pub fn text_input(self, character: char, context: &mut UpdateContext<'gc>) { - if self.0.read().flags.contains(EditTextFlag::READ_ONLY) - || (character.is_control() && character != Self::INPUT_NEWLINE) - || self.available_chars() == 0 - { + pub fn text_input(self, text: String, context: &mut UpdateContext<'gc>) { + if self.0.read().flags.contains(EditTextFlag::READ_ONLY) || self.available_chars() == 0 { return; } - if !self.is_multiline() && character == Self::INPUT_NEWLINE { + let text = if self.is_multiline() { + text + } else { + text.replace(&Self::ANY_NEWLINE[..], "") + }; + + if text.is_empty() { return; } + let text = WString::from_utf8(&text); + let Some(selection) = self.selection() else { return; }; - let Some(character) = self.0.read().restrict.to_allowed(character) else { - return; - }; + let filtered_text = self.0.read().restrict.filter_allowed(&text); if let Avm2Value::Object(target) = self.object2() { - let character_string = AvmString::new_utf8(context.gc(), character.to_string()); + let character_string = AvmString::new(context.gc(), text); let mut activation = Avm2Activation::from_nothing(context); let text_evt = Avm2EventObject::text_event( @@ -2017,13 +1999,13 @@ impl<'gc> EditText<'gc> { } } - self.replace_text( - selection.start(), - selection.end(), - &WString::from_char(character), - context, - ); - let new_pos = selection.start() + character.len_utf8(); + let mut text = filtered_text.as_wstr(); + if text.len() > self.available_chars() { + text = &text[0..self.available_chars()]; + } + + self.replace_text(selection.start(), selection.end(), text, context); + let new_pos = selection.start() + text.len(); self.set_selection(Some(TextSelection::for_position(new_pos)), context.gc()); let mut activation = Avm1Activation::from_nothing( @@ -3477,11 +3459,11 @@ impl EditTextRestrict { } } - pub fn filter_allowed(&self, text: &str) -> String { - let mut filtered = String::with_capacity(text.len()); + pub fn filter_allowed(&self, text: &WStr) -> WString { + let mut filtered = WString::with_capacity(text.len(), text.is_wide()); for c in text.chars() { - if let Some(c) = self.to_allowed(c) { - filtered.push(c); + if let Some(c) = c.ok().and_then(|c| self.to_allowed(c)) { + filtered.push_char(c); } } filtered diff --git a/core/src/player.rs b/core/src/player.rs index 45b1926452274..6c3d72efb98e5 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -1299,7 +1299,7 @@ impl Player { if !key_press_handled { if let Some(text) = context.focus_tracker.get_as_edit_text() { if let InputEvent::TextInput { codepoint } = &event { - text.text_input(*codepoint, context); + text.text_input((*codepoint).to_string(), context); } if let InputEvent::TextControl { code } = &event { text.text_control_input(*code, context); diff --git a/tests/tests/swfs/avm2/edittext_paste_events/Test.as b/tests/tests/swfs/avm2/edittext_paste_events/Test.as new file mode 100644 index 0000000000000..56735608f77ed --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_events/Test.as @@ -0,0 +1,40 @@ +package { +import flash.display.*; +import flash.text.*; +import flash.events.*; + +public class Test extends MovieClip { + public function Test() { + var tf = new TextField(); + tf.type = "input"; + tf.border = true; + tf.x = 10; + tf.y = 10; + tf.width = 200; + tf.height = 50; + addChild(tf); + + tf.addEventListener("textInput", function(evt:TextEvent):void { + trace("input " + evt.text + ", " + tf.text); + }); + + tf.addEventListener("change", function(evt:Event):void { + trace("change " + tf.text); + }); + + stage.addEventListener("keyDown", function(evt:KeyboardEvent):void { + if (evt.keyCode >= 0 && evt.keyCode <= 256) { + trace("key down " + evt.keyCode + ", " + tf.text); + } + }); + + stage.addEventListener("keyUp", function(evt:KeyboardEvent):void { + if (evt.keyCode >= 0 && evt.keyCode <= 256) { + trace("key up " + evt.keyCode + ", " + tf.text); + } + }); + + stage.focus = tf; + } +} +} diff --git a/tests/tests/swfs/avm2/edittext_paste_events/input.json b/tests/tests/swfs/avm2/edittext_paste_events/input.json new file mode 100644 index 0000000000000..5622e34c60e73 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_events/input.json @@ -0,0 +1,10 @@ +[ + { "type": "SetClipboardText", "text": "Test" }, + { "type": "KeyDown", "key": "Control" }, + { "type": "TextControl", "code": "Paste" }, + { "type": "KeyUp", "key": "Control" }, + { "type": "KeyDown", "key": { "Char": "x" } }, + { "type": "TextInput", "codepoint": "x" }, + { "type": "KeyUp", "key": { "Char": "x" } }, + { "type": "Wait" } +] diff --git a/tests/tests/swfs/avm2/edittext_paste_events/output.txt b/tests/tests/swfs/avm2/edittext_paste_events/output.txt new file mode 100644 index 0000000000000..c8b18ad5e479d --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_events/output.txt @@ -0,0 +1,8 @@ +key down 17, +input Test, +change Test +key up 17, Test +key down 88, Test +input x, Test +change Testx +key up 88, Testx diff --git a/tests/tests/swfs/avm2/edittext_paste_events/test.swf b/tests/tests/swfs/avm2/edittext_paste_events/test.swf new file mode 100644 index 0000000000000..5a0404ee02aa0 Binary files /dev/null and b/tests/tests/swfs/avm2/edittext_paste_events/test.swf differ diff --git a/tests/tests/swfs/avm2/edittext_paste_events/test.toml b/tests/tests/swfs/avm2/edittext_paste_events/test.toml new file mode 100644 index 0000000000000..cf6123969a1d6 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_events/test.toml @@ -0,0 +1 @@ +num_ticks = 1 diff --git a/tests/tests/swfs/avm2/edittext_paste_maxchars/Test.as b/tests/tests/swfs/avm2/edittext_paste_maxchars/Test.as new file mode 100644 index 0000000000000..97d105462871e --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_maxchars/Test.as @@ -0,0 +1,43 @@ +package { +import flash.display.*; +import flash.text.*; +import flash.events.*; + +public class Test extends MovieClip { + public function Test() { + var tf = new TextField(); + tf.type = "input"; + tf.border = true; + tf.x = 10; + tf.y = 10; + tf.width = 200; + tf.height = 50; + tf.maxChars = 5; + tf.text = "xxxxx"; + addChild(tf); + + tf.addEventListener("textInput", function(evt:TextEvent):void { + trace("input " + evt.text + ", " + tf.text); + }); + + tf.addEventListener("change", function(evt:Event):void { + trace("change " + tf.text); + }); + + stage.addEventListener("keyDown", function(evt:KeyboardEvent):void { + if (evt.keyCode >= 0 && evt.keyCode <= 256) { + trace("key down " + evt.keyCode + ", " + tf.text); + } + }); + + stage.addEventListener("keyUp", function(evt:KeyboardEvent):void { + if (evt.keyCode >= 0 && evt.keyCode <= 256) { + trace("key up " + evt.keyCode + ", " + tf.text); + } + }); + + stage.focus = tf; + tf.setSelection(3, 5); + } +} +} diff --git a/tests/tests/swfs/avm2/edittext_paste_maxchars/input.json b/tests/tests/swfs/avm2/edittext_paste_maxchars/input.json new file mode 100644 index 0000000000000..c251ace490451 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_maxchars/input.json @@ -0,0 +1,7 @@ +[ + { "type": "SetClipboardText", "text": "a\nb" }, + { "type": "KeyDown", "key": "Control" }, + { "type": "TextControl", "code": "Paste" }, + { "type": "KeyUp", "key": "Control" }, + { "type": "Wait" } +] diff --git a/tests/tests/swfs/avm2/edittext_paste_maxchars/output.txt b/tests/tests/swfs/avm2/edittext_paste_maxchars/output.txt new file mode 100644 index 0000000000000..9f15bf4444dcc --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_maxchars/output.txt @@ -0,0 +1,4 @@ +key down 17, xxxxx +input ab, xxxxx +change xxxab +key up 17, xxxab diff --git a/tests/tests/swfs/avm2/edittext_paste_maxchars/test.swf b/tests/tests/swfs/avm2/edittext_paste_maxchars/test.swf new file mode 100644 index 0000000000000..06becab207f13 Binary files /dev/null and b/tests/tests/swfs/avm2/edittext_paste_maxchars/test.swf differ diff --git a/tests/tests/swfs/avm2/edittext_paste_maxchars/test.toml b/tests/tests/swfs/avm2/edittext_paste_maxchars/test.toml new file mode 100644 index 0000000000000..cf6123969a1d6 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_maxchars/test.toml @@ -0,0 +1 @@ +num_ticks = 1 diff --git a/tests/tests/swfs/avm2/edittext_paste_restrict/Test.as b/tests/tests/swfs/avm2/edittext_paste_restrict/Test.as new file mode 100644 index 0000000000000..3e38786eba697 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_restrict/Test.as @@ -0,0 +1,43 @@ +package { +import flash.display.*; +import flash.text.*; +import flash.events.*; + +public class Test extends MovieClip { + public function Test() { + var tf = new TextField(); + tf.type = "input"; + tf.border = true; + tf.x = 10; + tf.y = 10; + tf.width = 200; + tf.height = 50; + tf.restrict = "y"; + tf.text = "xxxxx"; + addChild(tf); + + tf.addEventListener("textInput", function(evt:TextEvent):void { + trace("input " + evt.text + ", " + tf.text); + }); + + tf.addEventListener("change", function(evt:Event):void { + trace("change " + tf.text); + }); + + stage.addEventListener("keyDown", function(evt:KeyboardEvent):void { + if (evt.keyCode >= 0 && evt.keyCode <= 256) { + trace("key down " + evt.keyCode + ", " + tf.text); + } + }); + + stage.addEventListener("keyUp", function(evt:KeyboardEvent):void { + if (evt.keyCode >= 0 && evt.keyCode <= 256) { + trace("key up " + evt.keyCode + ", " + tf.text); + } + }); + + stage.focus = tf; + tf.setSelection(3, 5); + } +} +} diff --git a/tests/tests/swfs/avm2/edittext_paste_restrict/input.json b/tests/tests/swfs/avm2/edittext_paste_restrict/input.json new file mode 100644 index 0000000000000..74ff0f356a4e4 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_restrict/input.json @@ -0,0 +1,17 @@ +[ + { "type": "SetClipboardText", "text": "abc" }, + { "type": "KeyDown", "key": "Control" }, + { "type": "TextControl", "code": "Paste" }, + { "type": "KeyUp", "key": "Control" }, + { "type": "SetClipboardText", "text": "ayb" }, + { "type": "KeyDown", "key": "Control" }, + { "type": "TextControl", "code": "Paste" }, + { "type": "KeyUp", "key": "Control" }, + { "type": "KeyDown", "key": { "Char": "x" } }, + { "type": "TextInput", "codepoint": "x" }, + { "type": "KeyUp", "key": { "Char": "x" } }, + { "type": "KeyDown", "key": { "Char": "y" } }, + { "type": "TextInput", "codepoint": "y" }, + { "type": "KeyUp", "key": { "Char": "y" } }, + { "type": "Wait" } +] diff --git a/tests/tests/swfs/avm2/edittext_paste_restrict/output.txt b/tests/tests/swfs/avm2/edittext_paste_restrict/output.txt new file mode 100644 index 0000000000000..60ec69d6f2e1a --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_restrict/output.txt @@ -0,0 +1,16 @@ +key down 17, xxxxx +input abc, xxxxx +change xxx +key up 17, xxx +key down 17, xxx +input ayb, xxx +change xxxy +key up 17, xxxy +key down 88, xxxy +input x, xxxy +change xxxy +key up 88, xxxy +key down 89, xxxy +input y, xxxy +change xxxyy +key up 89, xxxyy diff --git a/tests/tests/swfs/avm2/edittext_paste_restrict/test.swf b/tests/tests/swfs/avm2/edittext_paste_restrict/test.swf new file mode 100644 index 0000000000000..7b6c02f5d5657 Binary files /dev/null and b/tests/tests/swfs/avm2/edittext_paste_restrict/test.swf differ diff --git a/tests/tests/swfs/avm2/edittext_paste_restrict/test.toml b/tests/tests/swfs/avm2/edittext_paste_restrict/test.toml new file mode 100644 index 0000000000000..cf6123969a1d6 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_paste_restrict/test.toml @@ -0,0 +1 @@ +num_ticks = 1 diff --git a/tests/tests/swfs/avm2/edittext_restrict_events/Test.as b/tests/tests/swfs/avm2/edittext_restrict_events/Test.as new file mode 100644 index 0000000000000..7779e5c933149 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_restrict_events/Test.as @@ -0,0 +1,40 @@ +package { + import flash.display.Sprite; + import flash.display.Stage; + import flash.events.Event; + import flash.events.KeyboardEvent; + import flash.events.TextEvent; + import flash.ui.Keyboard; + import flash.text.TextField; + + public class Test extends Sprite { + public function Test() { + var text = new TextField(); + text.border = true; + text.width = 200; + text.height = 20; + text.type = "input"; + text.restrict = "x"; + text.maxChars = 5; + addChild(text); + + stage.focus = text; + stage.addEventListener("keyDown", function(event:KeyboardEvent):void { + if (event.keyCode < 256) { + trace("key down: " + event.keyCode + ", " + text.text); + } + }); + stage.addEventListener("keyUp", function(event:KeyboardEvent):void { + if (event.keyCode < 256) { + trace("key up: " + event.keyCode + ", " + text.text); + } + }); + text.addEventListener("textInput", function(event:TextEvent):void { + trace("text input: " + event.text + ", " + text.text); + }); + text.addEventListener("change", function(event:*):void { + trace("changed: " + text.text); + }); + } + } +} diff --git a/tests/tests/swfs/avm2/edittext_restrict_events/input.json b/tests/tests/swfs/avm2/edittext_restrict_events/input.json new file mode 100644 index 0000000000000..fb44d7b9ff845 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_restrict_events/input.json @@ -0,0 +1,21 @@ +[ + { "type": "SetClipboardText", "text": "text" }, + { "type": "KeyDown", "key": "Control" }, + { "type": "TextControl", "code": "Paste" }, + { "type": "KeyUp", "key": "Control" }, + { "type": "KeyDown", "key": { "Char": "y" } }, + { "type": "TextInput", "codepoint": "y" }, + { "type": "KeyUp", "key": { "Char": "y" } }, + { "type": "KeyDown", "key": { "Char": "x" } }, + { "type": "TextInput", "codepoint": "x" }, + { "type": "KeyUp", "key": { "Char": "x" } }, + { "type": "KeyDown", "key": "Shift" }, + { "type": "KeyDown", "key": { "Char": "X" } }, + { "type": "TextInput", "codepoint": "X" }, + { "type": "KeyUp", "key": { "Char": "X" } }, + { "type": "KeyUp", "key": "Shift" }, + { "type": "SetClipboardText", "text": "xxxxx" }, + { "type": "KeyDown", "key": "Control" }, + { "type": "TextControl", "code": "Paste" }, + { "type": "KeyUp", "key": "Control" } +] diff --git a/tests/tests/swfs/avm2/edittext_restrict_events/output.txt b/tests/tests/swfs/avm2/edittext_restrict_events/output.txt new file mode 100644 index 0000000000000..f06b15252dce2 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_restrict_events/output.txt @@ -0,0 +1,22 @@ +key down: 17, +text input: text, +changed: x +key up: 17, x +key down: 89, x +text input: y, x +changed: x +key up: 89, x +key down: 88, x +text input: x, x +changed: xx +key up: 88, xx +key down: 16, xx +key down: 88, xx +text input: X, xx +changed: xxx +key up: 88, xxx +key up: 16, xxx +key down: 17, xxx +text input: xxxxx, xxx +changed: xxxxx +key up: 17, xxxxx diff --git a/tests/tests/swfs/avm2/edittext_restrict_events/test.swf b/tests/tests/swfs/avm2/edittext_restrict_events/test.swf new file mode 100644 index 0000000000000..ebe06229bcd0a Binary files /dev/null and b/tests/tests/swfs/avm2/edittext_restrict_events/test.swf differ diff --git a/tests/tests/swfs/avm2/edittext_restrict_events/test.toml b/tests/tests/swfs/avm2/edittext_restrict_events/test.toml new file mode 100644 index 0000000000000..cf6123969a1d6 --- /dev/null +++ b/tests/tests/swfs/avm2/edittext_restrict_events/test.toml @@ -0,0 +1 @@ +num_ticks = 1