core: Avoid borrow panic when text is removed (fix #1580)

This commit is contained in:
Mike Welsh 2020-11-19 17:42:41 -08:00
parent b492a9c379
commit 2d463629e2
1 changed files with 101 additions and 78 deletions

View File

@ -946,13 +946,17 @@ impl<'gc> MovieClip<'gc> {
child: DisplayObject<'gc>, child: DisplayObject<'gc>,
depth: Depth, depth: Depth,
) { ) {
let mut parent = self.0.write(context.gc_context); let prev_child = self
.0
let prev_child = parent.children.insert(depth, child); .write(context.gc_context)
.children
.insert(depth, child);
if let Some(prev_child) = prev_child { if let Some(prev_child) = prev_child {
parent.remove_child_from_exec_list(context, prev_child); self.remove_child_from_exec_list(context, prev_child);
} }
parent.add_child_to_exec_list(context.gc_context, child); self.0
.write(context.gc_context)
.add_child_to_exec_list(context.gc_context, child);
child.set_parent(context.gc_context, Some((*self).into())); child.set_parent(context.gc_context, Some((*self).into()));
child.set_place_frame(context.gc_context, 0); child.set_place_frame(context.gc_context, 0);
child.set_depth(context.gc_context, depth); child.set_depth(context.gc_context, depth);
@ -968,9 +972,13 @@ impl<'gc> MovieClip<'gc> {
child.parent().unwrap(), child.parent().unwrap(),
(*self).into() (*self).into()
)); ));
let mut parent = self.0.write(context.gc_context); let child = self
if let Some(child) = parent.children.remove(&child.depth()) { .0
parent.remove_child_from_exec_list(context, child); .write(context.gc_context)
.children
.remove(&child.depth());
if let Some(child) = child {
self.remove_child_from_exec_list(context, child);
} }
} }
@ -1180,12 +1188,17 @@ impl<'gc> MovieClip<'gc> {
// Remove previous child from children list, // Remove previous child from children list,
// and add new child onto front of the list. // and add new child onto front of the list.
let prev_child = { let prev_child = {
let mut mc = self.0.write(context.gc_context); let prev_child = self
let prev_child = mc.children.insert(depth, child); .0
.write(context.gc_context)
.children
.insert(depth, child);
if let Some(prev_child) = prev_child { if let Some(prev_child) = prev_child {
mc.remove_child_from_exec_list(context, prev_child); self.remove_child_from_exec_list(context, prev_child);
} }
mc.add_child_to_exec_list(context.gc_context, child); self.0
.write(context.gc_context)
.add_child_to_exec_list(context.gc_context, child);
prev_child prev_child
}; };
{ {
@ -1258,9 +1271,8 @@ impl<'gc> MovieClip<'gc> {
}) })
.collect(); .collect();
for (depth, child) in children { for (depth, child) in children {
let mut mc = self.0.write(context.gc_context); self.0.write(context.gc_context).children.remove(&depth);
mc.children.remove(&depth); self.remove_child_from_exec_list(context, child);
mc.remove_child_from_exec_list(context, child);
} }
true true
} else { } else {
@ -1288,7 +1300,6 @@ impl<'gc> MovieClip<'gc> {
self.0.write(context.gc_context).current_frame += 1; self.0.write(context.gc_context).current_frame += 1;
frame_pos = reader.get_inner().position(); frame_pos = reader.get_inner().position();
let mut mc = self.0.write(context.gc_context);
let version = reader.version(); let version = reader.version();
use swf::TagCode; use swf::TagCode;
let tag_callback = |reader: &mut SwfStream<&[u8]>, tag_code, tag_len| { let tag_callback = |reader: &mut SwfStream<&[u8]>, tag_code, tag_len| {
@ -1299,6 +1310,8 @@ impl<'gc> MovieClip<'gc> {
match tag_code { match tag_code {
TagCode::PlaceObject => { TagCode::PlaceObject => {
index += 1; index += 1;
let mut mc = self.0.write(context.gc_context);
mc.goto_place_object( mc.goto_place_object(
reader, reader,
tag_len, tag_len,
@ -1310,6 +1323,8 @@ impl<'gc> MovieClip<'gc> {
} }
TagCode::PlaceObject2 => { TagCode::PlaceObject2 => {
index += 1; index += 1;
let mut mc = self.0.write(context.gc_context);
mc.goto_place_object( mc.goto_place_object(
reader, reader,
tag_len, tag_len,
@ -1321,6 +1336,8 @@ impl<'gc> MovieClip<'gc> {
} }
TagCode::PlaceObject3 => { TagCode::PlaceObject3 => {
index += 1; index += 1;
let mut mc = self.0.write(context.gc_context);
mc.goto_place_object( mc.goto_place_object(
reader, reader,
tag_len, tag_len,
@ -1332,6 +1349,8 @@ impl<'gc> MovieClip<'gc> {
} }
TagCode::PlaceObject4 => { TagCode::PlaceObject4 => {
index += 1; index += 1;
let mut mc = self.0.write(context.gc_context);
mc.goto_place_object( mc.goto_place_object(
reader, reader,
tag_len, tag_len,
@ -1342,10 +1361,10 @@ impl<'gc> MovieClip<'gc> {
) )
} }
TagCode::RemoveObject => { TagCode::RemoveObject => {
mc.goto_remove_object(reader, 1, context, &mut goto_commands, is_rewind) self.goto_remove_object(reader, 1, context, &mut goto_commands, is_rewind)
} }
TagCode::RemoveObject2 => { TagCode::RemoveObject2 => {
mc.goto_remove_object(reader, 2, context, &mut goto_commands, is_rewind) self.goto_remove_object(reader, 2, context, &mut goto_commands, is_rewind)
} }
_ => Ok(()), _ => Ok(()),
} }
@ -1630,6 +1649,64 @@ impl<'gc> MovieClip<'gc> {
pub fn set_focusable(self, focusable: bool, context: &mut UpdateContext<'_, 'gc, '_>) { pub fn set_focusable(self, focusable: bool, context: &mut UpdateContext<'_, 'gc, '_>) {
self.0.write(context.gc_context).is_focusable = focusable; self.0.write(context.gc_context).is_focusable = focusable;
} }
/// Removes a child from the execution list.
/// This does not affect the render list.
fn remove_child_from_exec_list(
self,
context: &mut UpdateContext<'_, 'gc, '_>,
child: DisplayObject<'gc>,
) {
// Remove from children linked list.
let prev = child.prev_sibling();
let next = child.next_sibling();
if let Some(prev) = prev {
prev.set_next_sibling(context.gc_context, next);
}
if let Some(next) = next {
next.set_prev_sibling(context.gc_context, prev);
}
if let Some(head) = self.first_child() {
if DisplayObject::ptr_eq(head, child) {
self.set_first_child(context.gc_context, next);
}
}
// Flag child as removed.
child.unload(context);
}
/// Handle a RemoveObject tag when running a goto action.
#[inline]
fn goto_remove_object<'a>(
self,
reader: &mut SwfStream<&'a [u8]>,
version: u8,
context: &mut UpdateContext<'_, 'gc, '_>,
goto_commands: &mut Vec<GotoPlaceObject>,
is_rewind: bool,
) -> DecodeResult {
let remove_object = if version == 1 {
reader.read_remove_object_1()
} else {
reader.read_remove_object_2()
}?;
let depth = Depth::from(remove_object.depth);
if let Some(i) = goto_commands.iter().position(|o| o.depth() == depth) {
goto_commands.swap_remove(i);
}
if !is_rewind {
// For fast-forwards, if this tag were to remove an object
// that existed before the goto, then we can remove that child right away.
// Don't do this for rewinds, because they conceptually
// start from an empty display list, and we also want to examine
// the old children to decide if they persist (place_frame <= goto_frame).
let child = self.0.write(context.gc_context).children.remove(&depth);
if let Some(child) = child {
self.remove_child_from_exec_list(context, child);
}
}
Ok(())
}
} }
impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
@ -1979,30 +2056,6 @@ impl<'gc> MovieClipData<'gc> {
} }
self.set_first_child(gc_context, Some(child)); self.set_first_child(gc_context, Some(child));
} }
/// Removes a child from the execution list.
/// This does not affect the render list.
fn remove_child_from_exec_list(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
child: DisplayObject<'gc>,
) {
// Remove from children linked list.
let prev = child.prev_sibling();
let next = child.next_sibling();
if let Some(prev) = prev {
prev.set_next_sibling(context.gc_context, next);
}
if let Some(next) = next {
next.set_prev_sibling(context.gc_context, prev);
}
if let Some(head) = self.first_child() {
if DisplayObject::ptr_eq(head, child) {
self.set_first_child(context.gc_context, next);
}
}
// Flag child as removed.
child.unload(context);
}
/// Handles a PlaceObject tag when running a goto action. /// Handles a PlaceObject tag when running a goto action.
#[inline] #[inline]
@ -2034,39 +2087,6 @@ impl<'gc> MovieClipData<'gc> {
Ok(()) Ok(())
} }
/// Handle a RemoveObject tag when running a goto action.
#[inline]
fn goto_remove_object<'a>(
&mut self,
reader: &mut SwfStream<&'a [u8]>,
version: u8,
context: &mut UpdateContext<'_, 'gc, '_>,
goto_commands: &mut Vec<GotoPlaceObject>,
is_rewind: bool,
) -> DecodeResult {
let remove_object = if version == 1 {
reader.read_remove_object_1()
} else {
reader.read_remove_object_2()
}?;
let depth = Depth::from(remove_object.depth);
if let Some(i) = goto_commands.iter().position(|o| o.depth() == depth) {
goto_commands.swap_remove(i);
}
if !is_rewind {
// For fast-forwards, if this tag were to remove an object
// that existed before the goto, then we can remove that child right away.
// Don't do this for rewinds, because they conceptually
// start from an empty display list, and we also want to examine
// the old children to decide if they persist (place_frame <= goto_frame).
let child = self.children.remove(&depth);
if let Some(child) = child {
self.remove_child_from_exec_list(context, child);
}
}
Ok(())
}
/// Run all actions for the given clip event. /// Run all actions for the given clip event.
fn run_clip_event( fn run_clip_event(
&self, &self,
@ -2916,10 +2936,13 @@ impl<'gc, 'a> MovieClip<'gc> {
} else { } else {
reader.read_remove_object_2() reader.read_remove_object_2()
}?; }?;
let mut mc = self.0.write(context.gc_context); let child = self
let child = mc.children.remove(&remove_object.depth.into()); .0
.write(context.gc_context)
.children
.remove(&remove_object.depth.into());
if let Some(child) = child { if let Some(child) = child {
mc.remove_child_from_exec_list(context, child); self.remove_child_from_exec_list(context, child);
} }
Ok(()) Ok(())
} }