core: First pass at image loading through `loadMovie` et all

This commit is contained in:
David Wendt 2022-02-12 21:00:53 -05:00 committed by Mike Welsh
parent 4ad0253076
commit e1d18be7fa
1 changed files with 126 additions and 44 deletions

View File

@ -4,8 +4,11 @@ use crate::avm1::activation::{Activation, ActivationIdentifier};
use crate::avm1::{Avm1, Object, TObject, Value};
use crate::avm2::{Activation as Avm2Activation, Domain as Avm2Domain};
use crate::backend::navigator::{OwnedFuture, RequestOptions};
use crate::backend::render::{determine_jpeg_tag_format, JpegTagFormat};
use crate::context::{ActionQueue, ActionType};
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
use crate::display_object::{
Bitmap, DisplayObject, MorphShape, TDisplayObject, TDisplayObjectContainer,
};
use crate::player::{Player, NEWEST_PLAYER_VERSION};
use crate::string::AvmString;
use crate::tag_utils::SwfMovie;
@ -14,11 +17,45 @@ use encoding_rs::UTF_8;
use gc_arena::{Collect, CollectionContext};
use generational_arena::{Arena, Index};
use std::sync::{Arc, Mutex, Weak};
use swf::read::read_compression_type;
use thiserror::Error;
use url::form_urlencoded;
pub type Handle = Index;
/// Enumeration of all content types that `Loader` can handle.
///
/// This is a superset of `JpegTagFormat`.
#[derive(PartialEq)]
pub enum ContentType {
Swf,
Jpeg,
Png,
Gif,
Unknown,
}
impl From<JpegTagFormat> for ContentType {
fn from(jtf: JpegTagFormat) -> Self {
match jtf {
JpegTagFormat::Jpeg => Self::Jpeg,
JpegTagFormat::Png => Self::Png,
JpegTagFormat::Gif => Self::Gif,
JpegTagFormat::Unknown => Self::Unknown,
}
}
}
impl ContentType {
fn sniff(data: &[u8]) -> ContentType {
if read_compression_type(data).is_ok() {
ContentType::Swf
} else {
determine_jpeg_tag_format(data).into()
}
}
}
#[derive(Error, Debug)]
pub enum Error {
#[error("Load cancelled")]
@ -42,6 +79,9 @@ pub enum Error {
#[error("Invalid SWF")]
InvalidSwf(#[from] crate::tag_utils::Error),
#[error("Invalid data")]
InvalidData,
// TODO: We can't support lifetimes on this error object yet (or we'll need some backends inside
// the GC arena). We're losing info here. How do we fix that?
#[error("Error running avm1 script: {0}")]
@ -389,12 +429,15 @@ impl<'gc> Loader<'gc> {
})?;
if let Ok(data) = fetch.await {
let movie = Arc::new(SwfMovie::from_data(
&data,
Some(url.into_owned()),
loader_url,
)?);
if replacing_root_movie {
let sniffed_type = ContentType::sniff(&data);
let length = data.len();
//TODO: Does replacing the root movie with an image require any
//special work?
if replacing_root_movie && sniffed_type == ContentType::Swf {
let movie =
SwfMovie::from_data(&data, Some(url.into_owned()), loader_url.clone())?;
let movie = Arc::new(movie);
player.lock().unwrap().set_root_movie(movie);
return Ok(());
}
@ -410,46 +453,83 @@ impl<'gc> Loader<'gc> {
_ => unreachable!(),
};
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
let parent_domain = activation.avm2().global_domain();
let domain = Avm2Domain::movie_domain(&mut activation, parent_domain);
uc.library
.library_for_movie_mut(movie.clone())
.set_avm2_domain(domain);
match sniffed_type {
ContentType::Swf => {
let movie = Arc::new(SwfMovie::from_data(
&data,
Some(url.into_owned()),
loader_url,
)?);
if let Some(broadcaster) = broadcaster {
Avm1::run_stack_frame_for_method(
clip,
broadcaster,
NEWEST_PLAYER_VERSION,
uc,
"broadcastMessage".into(),
&[
"onLoadProgress".into(),
clip.object(),
data.len().into(),
data.len().into(),
],
);
}
if let Some(mut mc) = clip.as_movie_clip() {
mc.replace_with_movie(uc.gc_context, Some(movie.clone()));
mc.post_instantiation(uc, None, Instantiator::Movie, false);
let mut morph_shapes = fnv::FnvHashMap::default();
mc.preload(uc, &mut morph_shapes);
// Finalize morph shapes.
for (id, static_data) in morph_shapes {
let morph_shape = MorphShape::new(uc.gc_context, static_data);
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
let parent_domain = activation.avm2().global_domain();
let domain = Avm2Domain::movie_domain(&mut activation, parent_domain);
uc.library
.library_for_movie_mut(movie.clone())
.register_character(
id,
crate::character::Character::MorphShape(morph_shape),
.set_avm2_domain(domain);
if let Some(broadcaster) = broadcaster {
Avm1::run_stack_frame_for_method(
clip,
broadcaster,
NEWEST_PLAYER_VERSION,
uc,
"broadcastMessage".into(),
&[
"onLoadProgress".into(),
clip.object(),
data.len().into(),
data.len().into(),
],
);
}
if let Some(mut mc) = clip.as_movie_clip() {
mc.replace_with_movie(uc.gc_context, Some(movie.clone()));
mc.post_instantiation(uc, None, Instantiator::Movie, false);
let mut morph_shapes = fnv::FnvHashMap::default();
mc.preload(uc, &mut morph_shapes);
// Finalize morph shapes.
for (id, static_data) in morph_shapes {
let morph_shape = MorphShape::new(uc.gc_context, static_data);
uc.library
.library_for_movie_mut(movie.clone())
.register_character(
id,
crate::character::Character::MorphShape(morph_shape),
);
}
}
}
ContentType::Gif | ContentType::Jpeg | ContentType::Png => {
let bitmap = uc.renderer.register_bitmap_jpeg_2(&data)?;
let bitmap_obj =
Bitmap::new(uc, 0, bitmap.handle, bitmap.width, bitmap.height);
if let Some(broadcaster) = broadcaster {
Avm1::run_stack_frame_for_method(
clip,
broadcaster,
NEWEST_PLAYER_VERSION,
uc,
"broadcastMessage".into(),
&[
"onLoadProgress".into(),
clip.object(),
length.into(),
length.into(),
],
);
}
if let Some(mc) = clip.as_movie_clip() {
//TODO: Somehow replace with Bitmap?
mc.replace_at_depth(uc, bitmap_obj.into(), 1);
}
}
ContentType::Unknown => return Err(Error::InvalidData),
}
if let Some(broadcaster) = broadcaster {
@ -471,7 +551,7 @@ impl<'gc> Loader<'gc> {
};
Ok(())
})
})?; //TODO: content sniffing errors need to be reported somehow
} else {
//TODO: Inspect the fetch error.
//This requires cooperation from the backend to send abstract
@ -511,8 +591,10 @@ impl<'gc> Loader<'gc> {
};
Ok(())
})
})?;
}
Ok(())
})
}