Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avm2: Implement tabEnabled, tabIndex, and tabChildren #16050

Merged
merged 15 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 16 additions & 17 deletions core/src/avm1/globals/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::avm1::ArrayObject;
use crate::avm1::{globals, Object, ScriptObject, TObject, Value};
use crate::avm1_stub;
use crate::context::GcContext;
use crate::display_object::{Avm1Button, TDisplayObject};
use crate::display_object::{Avm1Button, TDisplayObject, TInteractiveObject};
use crate::string::AvmString;

macro_rules! button_getter {
Expand Down Expand Up @@ -180,8 +180,8 @@ fn tab_index<'gc>(
this: Avm1Button<'gc>,
_activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(index) = this.tab_index_value() {
Ok(index.into())
if let Some(index) = this.as_interactive().and_then(|this| this.tab_index()) {
Ok(Value::Number(index as f64))
} else {
Ok(Value::Undefined)
}
Expand All @@ -192,19 +192,18 @@ fn set_tab_index<'gc>(
activation: &mut Activation<'_, 'gc>,
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
match value {
Value::Undefined | Value::Null => {
this.set_tab_index_value(&mut activation.context, None);
}
Value::Bool(_) | Value::Number(_) => {
// FIXME This coercion is not perfect, as it wraps
// instead of falling back to MIN, as FP does
let i32_value = value.coerce_to_i32(activation)?;
this.set_tab_index_value(&mut activation.context, Some(i32_value));
}
_ => {
this.set_tab_index_value(&mut activation.context, Some(i32::MIN));
}
};
if let Some(this) = this.as_interactive() {
let value = match value {
Value::Undefined | Value::Null => None,
Value::Bool(_) | Value::Number(_) => {
// FIXME This coercion is not perfect, as it wraps
// instead of falling back to MIN, as FP does
let i32_value = value.coerce_to_i32(activation)?;
Some(i32_value)
}
_ => Some(i32::MIN),
};
this.set_tab_index(&mut activation.context, value);
}
Ok(())
}
33 changes: 16 additions & 17 deletions core/src/avm1/globals/movie_clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::avm1::property_decl::{define_properties_on, Declaration};
use crate::avm1::{self, ArrayObject, Object, ScriptObject, TObject, Value};
use crate::backend::navigator::NavigationMethod;
use crate::context::{GcContext, UpdateContext};
use crate::display_object::{Bitmap, EditText, MovieClip};
use crate::display_object::{Bitmap, EditText, MovieClip, TInteractiveObject};
use crate::ecma_conversions::f64_to_wrapping_i32;
use crate::prelude::*;
use crate::string::AvmString;
Expand Down Expand Up @@ -1828,8 +1828,8 @@ fn tab_index<'gc>(
this: MovieClip<'gc>,
_activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(index) = this.tab_index_value() {
Ok(index.into())
if let Some(index) = this.as_interactive().and_then(|this| this.tab_index()) {
Ok(Value::Number(index as f64))
} else {
Ok(Value::Undefined)
}
Expand All @@ -1840,19 +1840,18 @@ fn set_tab_index<'gc>(
activation: &mut Activation<'_, 'gc>,
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
match value {
Value::Undefined | Value::Null => {
this.set_tab_index_value(&mut activation.context, None);
}
Value::Bool(_) | Value::Number(_) => {
// FIXME This coercion is not perfect, as it wraps
// instead of falling back to MIN, as FP does
let i32_value = value.coerce_to_i32(activation)?;
this.set_tab_index_value(&mut activation.context, Some(i32_value));
}
_ => {
this.set_tab_index_value(&mut activation.context, Some(i32::MIN));
}
};
if let Some(this) = this.as_interactive() {
let value = match value {
Value::Undefined | Value::Null => None,
Value::Bool(_) | Value::Number(_) => {
// FIXME This coercion is not perfect, as it wraps
// instead of falling back to MIN, as FP does
let i32_value = value.coerce_to_i32(activation)?;
Some(i32_value)
}
_ => Some(i32::MIN),
};
this.set_tab_index(&mut activation.context, value);
}
Ok(())
}
30 changes: 18 additions & 12 deletions core/src/avm1/globals/text_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use crate::avm1::object::NativeObject;
use crate::avm1::property_decl::{define_properties_on, Declaration};
use crate::avm1::{globals, ArrayObject, Object, ScriptObject, TObject, Value};
use crate::context::GcContext;
use crate::display_object::{AutoSizeMode, EditText, TDisplayObject, TextSelection};
use crate::display_object::{
AutoSizeMode, EditText, TDisplayObject, TInteractiveObject, TextSelection,
};
use crate::font::round_down_to_pixel;
use crate::html::TextFormat;
use crate::string::{AvmString, WStr};
Expand Down Expand Up @@ -919,8 +921,8 @@ pub fn tab_index<'gc>(
this: EditText<'gc>,
_activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(index) = this.tab_index_value() {
Ok(index.into())
if let Some(index) = this.as_interactive().and_then(|this| this.tab_index()) {
Ok(Value::Number(index as u32 as f64))
} else {
Ok(Value::Undefined)
}
Expand All @@ -931,14 +933,18 @@ pub fn set_tab_index<'gc>(
activation: &mut Activation<'_, 'gc>,
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
match value {
Value::Undefined | Value::Null => {
this.set_tab_index_value(&mut activation.context, None);
}
_ => {
let u32_value = value.coerce_to_u32(activation)?;
this.set_tab_index_value(&mut activation.context, Some(u32_value));
}
};
if let Some(this) = this.as_interactive() {
let value = match value {
Value::Undefined | Value::Null => None,
_ => {
// `tabIndex` is u32 in TextField, compared to i32 in Button and MovieClip,
// but that is only a data representation difference,
// as both are interpreted as i32.
let u32_value = value.coerce_to_u32(activation)?;
Some(u32_value as i32)
}
};
this.set_tab_index(&mut activation.context, value);
}
Ok(())
}
17 changes: 17 additions & 0 deletions core/src/avm2/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,23 @@ pub fn make_error_2008<'gc>(activation: &mut Activation<'_, 'gc>, param_name: &s
}
}

#[inline(never)]
#[cold]
pub fn make_error_2027<'gc>(activation: &mut Activation<'_, 'gc>, value: i32) -> Error<'gc> {
let err = range_error(
activation,
&format!(
"Error #2027: Parameter tabIndex must be a non-negative number; got {}.",
value
),
2027,
);
match err {
Ok(err) => Error::AvmError(err),
Err(err) => err,
}
}

#[inline(never)]
#[cold]
pub fn make_error_2037<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> {
Expand Down
3 changes: 2 additions & 1 deletion core/src/avm2/globals/flash/display/Stage.as
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ package flash.display {
}

override public function set tabChildren(value:Boolean):void {
super.tabChildren = value;
// Docs say that this operation throws IllegalOperationError,
// but in reality this call is just ignored.
}

override public function set tabEnabled(value:Boolean):void {
Expand Down
35 changes: 19 additions & 16 deletions core/src/avm2/globals/flash/display/display_object_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use crate::avm2::object::{Object, TObject};
use crate::avm2::parameters::ParametersExt;
use crate::avm2::value::Value;
use crate::avm2::{ArrayObject, ArrayStorage, Error};
use crate::avm2_stub_method;
use crate::context::UpdateContext;
use crate::display_object::HitTestOptions;
use crate::display_object::{DisplayObject, TDisplayObject, TDisplayObjectContainer};
use crate::{avm2_stub_getter, avm2_stub_method, avm2_stub_setter};
use std::cmp::min;

/// Implements `flash.display.DisplayObjectContainer`'s native instance constructor.
Expand Down Expand Up @@ -637,28 +637,31 @@ pub fn set_mouse_children<'gc>(

pub fn get_tab_children<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_getter!(
activation,
"flash.display.DisplayObjectContainer",
"tabChildren"
);

Ok(true.into())
if let Some(obj) = this
.as_display_object()
.and_then(|this| this.as_container())
{
Ok(Value::Bool(obj.is_tab_children(&mut activation.context)))
} else {
Ok(Value::Undefined)
}
}

pub fn set_tab_children<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_setter!(
activation,
"flash.display.DisplayObjectContainer",
"tabChildren"
);
if let Some(obj) = this
.as_display_object()
.and_then(|this| this.as_container())
{
let value = args.get_bool(0);
obj.set_tab_children(&mut activation.context, value);
}

Ok(Value::Undefined)
}
50 changes: 35 additions & 15 deletions core/src/avm2/globals/flash/display/interactive_object.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! `flash.display.InteractiveObject` builtin/prototype

use crate::avm2::activation::Activation;
use crate::avm2::error::make_error_2027;
use crate::avm2::object::{Object, TObject};
use crate::avm2::parameters::ParametersExt;
use crate::avm2::value::Value;
Expand Down Expand Up @@ -120,40 +121,59 @@ pub fn set_context_menu<'gc>(

pub fn get_tab_enabled<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_getter!(activation, "flash.display.InteractiveObject", "tabEnabled");

Ok(false.into())
if let Some(obj) = this.as_display_object().and_then(|o| o.as_interactive()) {
Ok(Value::Bool(obj.tab_enabled(&mut activation.context)))
} else {
Ok(Value::Undefined)
}
}

pub fn set_tab_enabled<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_setter!(activation, "flash.display.InteractiveObject", "tabIndex");
if let Some(obj) = this
.as_display_object()
.and_then(|this| this.as_interactive())
{
let value = args.get_bool(0);
obj.set_tab_enabled(&mut activation.context, value);
}

Ok(Value::Undefined)
}

pub fn get_tab_index<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_getter!(activation, "flash.display.InteractiveObject", "tabIndex");

Ok((-1).into())
if let Some(obj) = this
.as_display_object()
.and_then(|this| this.as_interactive())
{
Ok(Value::Number(obj.tab_index().unwrap_or(-1) as f64))
} else {
Ok(Value::Undefined)
}
}

pub fn set_tab_index<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_setter!(activation, "flash.display.InteractiveObject", "tabIndex");
if let Some(obj) = this.as_display_object().and_then(|o| o.as_interactive()) {
let value = args.get_i32(activation, 0)?;
if value < 0 {
return Err(make_error_2027(activation, value));
}
obj.set_tab_index(&mut activation.context, Some(value));
}

Ok(Value::Undefined)
}
Expand Down
22 changes: 1 addition & 21 deletions core/src/display_object/avm1_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ pub struct Avm1ButtonData<'gc> {
object: Lock<Option<Object<'gc>>>,
initialized: Cell<bool>,
has_focus: Cell<bool>,
// TODO Consider moving this to InteractiveObject along with
// TextField's and MovieClip's tab indices after AVM2 analysis
tab_index: Cell<Option<i32>>,
}

#[derive(Clone, Collect)]
Expand Down Expand Up @@ -102,7 +99,6 @@ impl<'gc> Avm1Button<'gc> {
ButtonTracking::Push
}),
has_focus: Cell::new(false),
tab_index: Cell::new(None),
},
))
}
Expand Down Expand Up @@ -245,18 +241,6 @@ impl<'gc> Avm1Button<'gc> {
fn use_hand_cursor(self, context: &mut UpdateContext<'_, 'gc>) -> bool {
self.get_boolean_property(context, "useHandCursor", true)
}

/// Get the value of `tabIndex` used in AS.
///
/// Do not confuse it with `tab_index`, which returns the value used for ordering.
pub fn tab_index_value(&self) -> Option<i32> {
self.0.tab_index.get()
}

/// Set the value of `tabIndex` used in AS.
pub fn set_tab_index_value(&self, _context: &mut UpdateContext<'_, 'gc>, value: Option<i32>) {
self.0.tab_index.set(value)
}
}

impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> {
Expand Down Expand Up @@ -634,13 +618,9 @@ impl<'gc> TInteractiveObject<'gc> for Avm1Button<'gc> {
self.call_focus_handler(context, focused, other);
}

fn is_tabbable(&self, context: &mut UpdateContext<'_, 'gc>) -> bool {
fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool {
self.get_avm1_boolean_property(context, "tabEnabled", |_| true)
}

fn tab_index(&self) -> Option<i64> {
self.0.tab_index.get().map(|i| i as i64)
}
}

impl<'gc> Avm1ButtonData<'gc> {
Expand Down
Loading