avm1: don't use &str for activation's path & variable logic

This commit is contained in:
Moulins 2021-09-22 16:51:46 +02:00 committed by kmeisthax
parent 1d9d7e6942
commit 8863b54db0
6 changed files with 83 additions and 114 deletions

View File

@ -11,7 +11,7 @@ use crate::backend::navigator::{NavigationMethod, RequestOptions};
use crate::context::UpdateContext; use crate::context::UpdateContext;
use crate::display_object::{DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer}; use crate::display_object::{DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer};
use crate::ecma_conversions::f64_to_wrapping_u32; use crate::ecma_conversions::f64_to_wrapping_u32;
use crate::string::{AvmString, BorrowWStr, WString}; use crate::string::{AvmString, BorrowWStr, WStr, WString};
use crate::tag_utils::SwfSlice; use crate::tag_utils::SwfSlice;
use crate::vminterface::Instantiator; use crate::vminterface::Instantiator;
use crate::{avm_error, avm_warn}; use crate::{avm_error, avm_warn};
@ -557,7 +557,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Action::SetMember => self.action_set_member(), Action::SetMember => self.action_set_member(),
Action::SetProperty => self.action_set_property(), Action::SetProperty => self.action_set_property(),
Action::SetTarget(target) => { Action::SetTarget(target) => {
self.action_set_target(&target.to_str_lossy(self.encoding())) let target = WString::from_utf8_owned(target.to_string_lossy(self.encoding()));
self.action_set_target(target.borrow())
} }
Action::SetTarget2 => self.action_set_target2(), Action::SetTarget2 => self.action_set_target2(),
Action::SetVariable => self.action_set_variable(), Action::SetVariable => self.action_set_variable(),
@ -759,12 +760,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} else { } else {
// An optional path to a MovieClip and a frame #/label, such as "/clip:framelabel". // An optional path to a MovieClip and a frame #/label, such as "/clip:framelabel".
let frame_path = arg.coerce_to_string(self)?; let frame_path = arg.coerce_to_string(self)?;
if let Some((clip, frame)) = self.resolve_variable_path(target, &frame_path)? { if let Some((clip, frame)) = self.resolve_variable_path(target, frame_path.borrow())? {
if let Some(clip) = clip.as_display_object().and_then(|o| o.as_movie_clip()) { if let Some(clip) = clip.as_display_object().and_then(|o| o.as_movie_clip()) {
let frame = frame.to_utf8_lossy(); // TODO: avoid this UTF8 conversion.
if let Ok(frame) = frame.parse().map(f64_to_wrapping_u32) { if let Ok(frame) = frame.parse().map(f64_to_wrapping_u32) {
// First try to parse as a frame number. // First try to parse as a frame number.
call_frame = Some((clip, frame)); call_frame = Some((clip, frame));
} else if let Some(frame) = clip.frame_label_to_number(frame) { } else if let Some(frame) = clip.frame_label_to_number(&frame) {
// Otherwise, it's a frame label. // Otherwise, it's a frame label.
call_frame = Some((clip, frame.into())); call_frame = Some((clip, frame.into()));
} }
@ -1908,7 +1910,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Ok(FrameControl::Continue) Ok(FrameControl::Continue)
} }
fn action_set_target(&mut self, target: &str) -> Result<FrameControl<'gc>, Error<'gc>> { fn action_set_target(&mut self, target: WStr<'_>) -> Result<FrameControl<'gc>, Error<'gc>> {
let base_clip = self.base_clip(); let base_clip = self.base_clip();
let new_target_clip; let new_target_clip;
let root = base_clip.avm1_root(&self.context)?; let root = base_clip.avm1_root(&self.context)?;
@ -1925,13 +1927,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} else { } else {
avm_warn!(self, "SetTarget failed: {} not found", target); avm_warn!(self, "SetTarget failed: {} not found", target);
// TODO: Emulate AVM1 trace error message. // TODO: Emulate AVM1 trace error message.
let path = if base_clip.removed() { None } else { Some(base_clip.path()) };
let message = format!( let message = format!(
"Target not found: Target=\"{}\" Base=\"{}\"", "Target not found: Target=\"{}\" Base=\"{}\"",
target, target,
if base_clip.removed() { match &path {
"?".to_string() Some(p) => p.borrow(),
} else { None => WStr::from_units(b"?"),
base_clip.path()
} }
); );
self.context.log.avm_trace(&message); self.context.log.avm_trace(&message);
@ -1966,7 +1968,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
match target { match target {
Value::String(target) => { Value::String(target) => {
return self.action_set_target(&target); return self.action_set_target(target.borrow());
} }
Value::Undefined => { Value::Undefined => {
// Reset. // Reset.
@ -1980,12 +1982,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} else { } else {
// Other objects get coerced to string. // Other objects get coerced to string.
let target = target.coerce_to_string(self)?; let target = target.coerce_to_string(self)?;
return self.action_set_target(&target); return self.action_set_target(target.borrow());
} }
} }
_ => { _ => {
let target = target.coerce_to_string(self)?; let target = target.coerce_to_string(self)?;
return self.action_set_target(&target); return self.action_set_target(target.borrow());
} }
}; };
@ -2151,7 +2153,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let param = self.context.avm1.pop().coerce_to_object(self); let param = self.context.avm1.pop().coerce_to_object(self);
let result = if let Some(display_object) = param.as_display_object() { let result = if let Some(display_object) = param.as_display_object() {
let path = display_object.path(); let path = display_object.path();
AvmString::new(self.context.gc_context, path).into() AvmString::new_ucs2(self.context.gc_context, path).into()
} else { } else {
Value::Undefined Value::Undefined
}; };
@ -2504,7 +2506,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let root = start.avm1_root(&self.context)?; let root = start.avm1_root(&self.context)?;
let start = start.object().coerce_to_object(self); let start = start.object().coerce_to_object(self);
Ok(self Ok(self
.resolve_target_path(root, start, &path, false)? .resolve_target_path(root, start, path.borrow(), false)?
.and_then(|o| o.as_display_object())) .and_then(|o| o.as_display_object()))
} }
@ -2521,8 +2523,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
&mut self, &mut self,
root: DisplayObject<'gc>, root: DisplayObject<'gc>,
start: Object<'gc>, start: Object<'gc>,
// TODO(moulins): replace by Str<'_> once the API is good enough. mut path: WStr<'_>,
path: &str,
mut first_element: bool, mut first_element: bool,
) -> Result<Option<Object<'gc>>, Error<'gc>> { ) -> Result<Option<Object<'gc>>, Error<'gc>> {
// Empty path resolves immediately to start clip. // Empty path resolves immediately to start clip.
@ -2532,9 +2533,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Starting / means an absolute path starting from root. // Starting / means an absolute path starting from root.
// (`/bar` means `_root.bar`) // (`/bar` means `_root.bar`)
let mut path = path.as_bytes(); let (mut object, mut is_slash_path) = if path.starts_with(b'/') {
let (mut object, mut is_slash_path) = if path[0] == b'/' { path = path.slice(1..);
path = &path[1..];
(root.object().coerce_to_object(self), true) (root.object().coerce_to_object(self), true)
} else { } else {
(start, false) (start, false)
@ -2546,17 +2546,19 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
while !path.is_empty() { while !path.is_empty() {
// Skip any number of leading : // Skip any number of leading :
// `foo`, `:foo`, and `:::foo` are all the same // `foo`, `:foo`, and `:::foo` are all the same
while path.get(0) == Some(&b':') { path = path.trim_start_matches(b':');
path = &path[1..];
}
let val = if let b".." | b"../" | b"..:" = &path[..std::cmp::min(path.len(), 3)] { let prefix = path.slice(..path.len().min(3));
let val = if prefix == b".."
|| prefix == b"../"
|| prefix == b"..:"
{
// Check for .. // Check for ..
// SWF-4 style _parent // SWF-4 style _parent
if path.get(2) == Some(&b'/') { if path.try_get(2) == Some(u16::from(b'/')) {
is_slash_path = true; is_slash_path = true;
} }
path = path.get(3..).unwrap_or(&[]); path = path.try_slice(3..).unwrap_or_default();
if let Some(parent) = object.as_display_object().and_then(|o| o.avm1_parent()) { if let Some(parent) = object.as_display_object().and_then(|o| o.avm1_parent()) {
parent.object() parent.object()
} else { } else {
@ -2571,10 +2573,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// TODO: SWF4 is probably more restrictive. // TODO: SWF4 is probably more restrictive.
let mut pos = 0; let mut pos = 0;
while pos < path.len() { while pos < path.len() {
match path[pos] { match u8::try_from(path.get(pos)) {
b':' => break, Ok(b':') => break,
b'.' if !is_slash_path => break, Ok(b'.') if !is_slash_path => break,
b'/' => { Ok(b'/') => {
is_slash_path = true; is_slash_path = true;
break; break;
} }
@ -2584,15 +2586,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} }
// Slice out the identifier and step the cursor past the delimiter. // Slice out the identifier and step the cursor past the delimiter.
let ident = &path[..pos]; let name = path.slice(..pos);
path = path.get(pos + 1..).unwrap_or(&[]); path = path.try_slice(pos + 1..).unwrap_or_default();
// Guaranteed to be valid UTF-8. if first_element && name == b"this" {
let name = unsafe { std::str::from_utf8_unchecked(ident) };
if first_element && name == "this" {
self.this_cell().into() self.this_cell().into()
} else if first_element && name == "_root" { } else if first_element && name == b"_root" {
self.root_object()? self.root_object()?
} else { } else {
// Get the value from the object. // Get the value from the object.
@ -2601,13 +2600,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Some(child) = object if let Some(child) = object
.as_display_object() .as_display_object()
.and_then(|o| o.as_container()) .and_then(|o| o.as_container())
.and_then(|o| { .and_then(|o| o.child_by_name(name, case_sensitive))
o.child_by_name(WString::from_utf8(name).borrow(), case_sensitive)
})
{ {
child.object() child.object()
} else { } else {
let name = AvmString::new(self.context.gc_context, name); let name = AvmString::new_ucs2(self.context.gc_context, name.into());
object.get(name, self).unwrap() object.get(name, self).unwrap()
} }
} }
@ -2634,29 +2631,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
pub fn resolve_variable_path<'s>( pub fn resolve_variable_path<'s>(
&mut self, &mut self,
start: DisplayObject<'gc>, start: DisplayObject<'gc>,
// TODO(moulins): replace by Str<'_> once the API is good enough. path: WStr<'s>,
path: &'s str, ) -> Result<Option<(Object<'gc>, WStr<'s>)>, Error<'gc>> {
) -> Result<Option<(Object<'gc>, &'s str)>, Error<'gc>> {
// Find the right-most : or . in the path. // Find the right-most : or . in the path.
// If we have one, we must resolve as a target path. // If we have one, we must resolve as a target path.
// We also check for a / to skip some unnecessary work later. if let Some(separator) = path.rfind(b":.".as_ref()) {
let mut has_slash = false;
let mut var_iter = path.as_bytes().rsplitn(2, |c| match c {
b':' | b'.' => true,
b'/' => {
has_slash = true;
false
}
_ => false,
});
let b = var_iter.next();
let a = var_iter.next();
if let (Some(path), Some(var_name)) = (a, b) {
// We have a . or :, so this is a path to an object plus a variable name. // We have a . or :, so this is a path to an object plus a variable name.
// We resolve it directly on the targeted object. // We resolve it directly on the targeted object.
let path = unsafe { std::str::from_utf8_unchecked(path) }; let (path, var_name) = (path.slice(..separator), path.slice(separator + 1..));
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
let mut current_scope = Some(self.scope_cell()); let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope { while let Some(scope) = current_scope {
@ -2708,24 +2690,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Find the right-most : or . in the path. // Find the right-most : or . in the path.
// If we have one, we must resolve as a target path. // If we have one, we must resolve as a target path.
// We also check for a / to skip some unnecessary work later. if let Some(separator) = path.rfind(b":.".as_ref()) {
let mut has_slash = false;
let mut var_iter = path.as_bytes().rsplitn(2, |c| match c {
b':' | b'.' => true,
b'/' => {
has_slash = true;
false
}
_ => false,
});
let b = var_iter.next();
let a = var_iter.next();
if let (Some(path), Some(var_name)) = (a, b) {
// We have a . or :, so this is a path to an object plus a variable name. // We have a . or :, so this is a path to an object plus a variable name.
// We resolve it directly on the targeted object. // We resolve it directly on the targeted object.
let path = unsafe { std::str::from_utf8_unchecked(path) }; let (path, var_name) = (path.slice(..separator), path.slice(separator + 1..));
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
let mut current_scope = Some(self.scope_cell()); let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope { while let Some(scope) = current_scope {
@ -2733,7 +2701,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Some(object) = if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)? self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
{ {
let var_name = AvmString::new(self.context.gc_context, var_name); let var_name = AvmString::new_ucs2(self.context.gc_context, var_name.into());
if object.has_property(self, var_name) { if object.has_property(self, var_name) {
return Ok(CallableValue::Callable(object, object.get(var_name, self)?)); return Ok(CallableValue::Callable(object, object.get(var_name, self)?));
} }
@ -2745,14 +2713,16 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} }
// If it doesn't have a trailing variable, it can still be a slash path. // If it doesn't have a trailing variable, it can still be a slash path.
// We can skip this step if we didn't find a slash above. if path.contains(b'/') {
if has_slash {
let mut current_scope = Some(self.scope_cell()); let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope { while let Some(scope) = current_scope {
let avm1_root = start.avm1_root(&self.context)?; let avm1_root = start.avm1_root(&self.context)?;
if let Some(object) = if let Some(object) = self.resolve_target_path(
self.resolve_target_path(avm1_root, *scope.read().locals(), &path, false)? avm1_root,
{ *scope.read().locals(),
path.borrow(),
false,
)? {
return Ok(CallableValue::UnCallable(object.into())); return Ok(CallableValue::UnCallable(object.into()));
} }
current_scope = scope.read().parent_cell(); current_scope = scope.read().parent_cell();
@ -2801,16 +2771,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Find the right-most : or . in the path. // Find the right-most : or . in the path.
// If we have one, we must resolve as a target path. // If we have one, we must resolve as a target path.
let mut var_iter = path.as_bytes().rsplitn(2, |&c| c == b':' || c == b'.'); let separator = path.rfind(b":.".as_ref());
let b = var_iter.next();
let a = var_iter.next();
if let (Some(path), Some(var_name)) = (a, b) { if let Some(sep) = separator {
// We have a . or :, so this is a path to an object plus a variable name. // We have a . or :, so this is a path to an object plus a variable name.
// We resolve it directly on the targeted object. // We resolve it directly on the targeted object.
let path = unsafe { std::str::from_utf8_unchecked(path) }; let (path, var_name) = (path.slice(..sep), path.slice(sep + 1..));
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
let var_name = AvmString::new(self.context.gc_context, var_name);
let mut current_scope = Some(self.scope_cell()); let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope { while let Some(scope) = current_scope {
@ -2818,6 +2784,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Some(object) = if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)? self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
{ {
let var_name = AvmString::new_ucs2(self.context.gc_context, var_name.into());
object.set(var_name, value, self)?; object.set(var_name, value, self)?;
return Ok(()); return Ok(());
} }

View File

@ -89,7 +89,7 @@ pub fn to_string<'gc>(
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
if let Some(display_object) = this.as_display_object() { if let Some(display_object) = this.as_display_object() {
Ok(AvmString::new(activation.context.gc_context, display_object.path()).into()) Ok(AvmString::new_ucs2(activation.context.gc_context, display_object.path()).into())
} else { } else {
Ok(Value::Undefined) Ok(Value::Undefined)
} }

View File

@ -1004,7 +1004,7 @@ pub fn goto_frame<'gc>(
// This can direct other clips than the one this method was called on! // This can direct other clips than the one this method was called on!
let frame_path = val.coerce_to_string(activation)?; let frame_path = val.coerce_to_string(activation)?;
if let Some((clip, frame)) = if let Some((clip, frame)) =
activation.resolve_variable_path(movie_clip.into(), &frame_path)? activation.resolve_variable_path(movie_clip.into(), frame_path.borrow())?
{ {
if let Some(clip) = clip.as_display_object().and_then(|o| o.as_movie_clip()) { if let Some(clip) = clip.as_display_object().and_then(|o| o.as_movie_clip()) {
// TODO(moulins): we need WStr::parse for avoiding allocation here. // TODO(moulins): we need WStr::parse for avoiding allocation here.

View File

@ -782,7 +782,7 @@ fn set_rotation<'gc>(
} }
fn target<'gc>(activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>) -> Value<'gc> { fn target<'gc>(activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>) -> Value<'gc> {
AvmString::new(activation.context.gc_context, this.slash_path()).into() AvmString::new_ucs2(activation.context.gc_context, this.slash_path()).into()
} }
fn frames_loaded<'gc>( fn frames_loaded<'gc>(
@ -794,8 +794,8 @@ fn frames_loaded<'gc>(
.map_or(Value::Undefined, Value::from) .map_or(Value::Undefined, Value::from)
} }
fn name<'gc>(activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>) -> Value<'gc> { fn name<'gc>(_activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>) -> Value<'gc> {
AvmString::new(activation.context.gc_context, this.name().to_string()).into() this.name().into()
} }
fn set_name<'gc>( fn set_name<'gc>(
@ -817,7 +817,7 @@ fn drop_target<'gc>(
.map_or_else( .map_or_else(
|| "".into(), || "".into(),
|drop_target| { |drop_target| {
AvmString::new(activation.context.gc_context, drop_target.slash_path()).into() AvmString::new_ucs2(activation.context.gc_context, drop_target.slash_path()).into()
}, },
) )
} }

View File

@ -10,7 +10,7 @@ use crate::context::{RenderContext, UpdateContext};
use crate::drawing::Drawing; use crate::drawing::Drawing;
use crate::player::NEWEST_PLAYER_VERSION; use crate::player::NEWEST_PLAYER_VERSION;
use crate::prelude::*; use crate::prelude::*;
use crate::string::AvmString; use crate::string::{AvmString, BorrowWStr, WString};
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
use crate::transform::Transform; use crate::transform::Transform;
use crate::types::{Degrees, Percent}; use crate::types::{Degrees, Percent};
@ -789,34 +789,34 @@ pub trait TDisplayObject<'gc>:
} }
/// Returns the dot-syntax path to this display object, e.g. `_level0.foo.clip` /// Returns the dot-syntax path to this display object, e.g. `_level0.foo.clip`
fn path(&self) -> String { fn path(&self) -> WString {
if let Some(parent) = self.avm1_parent() { if let Some(parent) = self.avm1_parent() {
let mut path = parent.path(); let mut path = parent.path();
path.push('.'); path.push_byte(b'.');
path.push_str(&*self.name()); path.push_str(self.name().borrow());
path path
} else { } else {
format!("_level{}", self.depth()) WString::from_utf8_owned(format!("_level{}", self.depth()))
} }
} }
/// Returns the Flash 4 slash-syntax path to this display object, e.g. `/foo/clip`. /// Returns the Flash 4 slash-syntax path to this display object, e.g. `/foo/clip`.
/// Returned by the `_target` property in AVM1. /// Returned by the `_target` property in AVM1.
fn slash_path(&self) -> String { fn slash_path(&self) -> WString {
fn build_slash_path(object: DisplayObject<'_>) -> String { fn build_slash_path(object: DisplayObject<'_>) -> WString {
if let Some(parent) = object.avm1_parent() { if let Some(parent) = object.avm1_parent() {
let mut path = build_slash_path(parent); let mut path = build_slash_path(parent);
path.push('/'); path.push_byte(b'/');
path.push_str(&*object.name()); path.push_str(object.name().borrow());
path path
} else { } else {
let level = object.depth(); let level = object.depth();
if level == 0 { if level == 0 {
// _level0 does not append its name in slash syntax. // _level0 does not append its name in slash syntax.
String::new() WString::new()
} else { } else {
// Other levels do append their name. // Other levels do append their name.
format!("_level{}", level) WString::from_utf8_owned(format!("_level{}", level))
} }
} }
} }
@ -825,7 +825,7 @@ pub trait TDisplayObject<'gc>:
build_slash_path((*self).into()) build_slash_path((*self).into())
} else { } else {
// _target of _level0 should just be '/'. // _target of _level0 should just be '/'.
'/'.to_string() WString::from_unit(b'/'.into())
} }
} }

View File

@ -20,7 +20,7 @@ use crate::font::{round_down_to_pixel, Glyph, TextRenderSettings};
use crate::html::{BoxBounds, FormatSpans, LayoutBox, LayoutContent, TextFormat}; use crate::html::{BoxBounds, FormatSpans, LayoutBox, LayoutContent, TextFormat};
use crate::prelude::*; use crate::prelude::*;
use crate::shape_utils::DrawCommand; use crate::shape_utils::DrawCommand;
use crate::string::{utils as string_utils, AvmString}; use crate::string::{utils as string_utils, AvmString, BorrowWStr, WString};
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
use crate::transform::Transform; use crate::transform::Transform;
use crate::vminterface::{AvmObject, AvmType, Instantiator}; use crate::vminterface::{AvmObject, AvmType, Instantiator};
@ -1047,7 +1047,7 @@ impl<'gc> EditText<'gc> {
// Avoid double-borrows by copying the string. // Avoid double-borrows by copying the string.
// TODO: Can we avoid this somehow? Maybe when we have a better string type. // TODO: Can we avoid this somehow? Maybe when we have a better string type.
let variable = (*var_path).to_string(); let variable_path = WString::from_utf8(&var_path);
drop(var_path); drop(var_path);
let parent = self.avm1_parent().unwrap(); let parent = self.avm1_parent().unwrap();
@ -1058,9 +1058,10 @@ impl<'gc> EditText<'gc> {
activation.context.swf.version(), activation.context.swf.version(),
|activation| { |activation| {
if let Ok(Some((object, property))) = if let Ok(Some((object, property))) =
activation.resolve_variable_path(parent, &variable) activation.resolve_variable_path(parent, variable_path.borrow())
{ {
let property = AvmString::new(activation.context.gc_context, property); let property =
AvmString::new_ucs2(activation.context.gc_context, property.into());
// If this text field was just created, we immediately propagate the text to the variable (or vice versa). // If this text field was just created, we immediately propagate the text to the variable (or vice versa).
if set_initial_value { if set_initial_value {
@ -1124,11 +1125,11 @@ impl<'gc> EditText<'gc> {
if let Some(variable) = self.variable() { if let Some(variable) = self.variable() {
// Avoid double-borrows by copying the string. // Avoid double-borrows by copying the string.
// TODO: Can we avoid this somehow? Maybe when we have a better string type. // TODO: Can we avoid this somehow? Maybe when we have a better string type.
let variable_path = variable.to_string(); let variable_path = WString::from_utf8(&variable);
drop(variable); drop(variable);
if let Ok(Some((object, property))) = if let Ok(Some((object, property))) = activation
activation.resolve_variable_path(self.avm1_parent().unwrap(), &variable_path) .resolve_variable_path(self.avm1_parent().unwrap(), variable_path.borrow())
{ {
let html_text = self.html_text(&mut activation.context); let html_text = self.html_text(&mut activation.context);
@ -1139,7 +1140,8 @@ impl<'gc> EditText<'gc> {
self.avm1_parent().unwrap(), self.avm1_parent().unwrap(),
activation.context.swf.version(), activation.context.swf.version(),
|activation| { |activation| {
let property = AvmString::new(activation.context.gc_context, property); let property =
AvmString::new_ucs2(activation.context.gc_context, property.into());
let _ = object.set( let _ = object.set(
property, property,
AvmString::new(activation.context.gc_context, html_text).into(), AvmString::new(activation.context.gc_context, html_text).into(),