core: Display image load when using Loader.loadBytes
The handling of images in Loader.loaderBytes is similar to the handling of SWFs - some of the data is exposed immediately following the 'Loader.loadBytes' call, but the DisplayObject isn't loaded until later.
This commit is contained in:
parent
173efbb77a
commit
8dbcfe26f9
|
@ -6,9 +6,9 @@ use crate::avm2::error::error;
|
|||
use crate::avm2::object::{DomainObject, LoaderStream, Object, TObject};
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::{AvmString, Error};
|
||||
use crate::avm2_stub_getter;
|
||||
use crate::display_object::TDisplayObject;
|
||||
use crate::loader::ContentType;
|
||||
use crate::{avm2_stub_getter, avm2_stub_method};
|
||||
use swf::{write_swf, Compression};
|
||||
|
||||
pub use crate::avm2::object::loader_info_allocator;
|
||||
|
@ -118,23 +118,26 @@ pub fn get_bytes_loaded<'gc>(
|
|||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(loader_stream) = this
|
||||
.as_loader_info_object()
|
||||
.and_then(|o| o.as_loader_stream())
|
||||
{
|
||||
match &*loader_stream {
|
||||
LoaderStream::NotYetLoaded(_, None, _) => return Ok(0.into()),
|
||||
LoaderStream::Swf(_, root) | LoaderStream::NotYetLoaded(_, Some(root), _) => {
|
||||
return Ok(root
|
||||
.as_movie_clip()
|
||||
.map(|mc| mc.compressed_loaded_bytes())
|
||||
.unwrap_or_default()
|
||||
.into())
|
||||
let loader_info = this.as_loader_info_object().unwrap();
|
||||
let loader_stream = loader_info.as_loader_stream().unwrap();
|
||||
match &*loader_stream {
|
||||
LoaderStream::NotYetLoaded(swf, None, _) => {
|
||||
if loader_info.errored() {
|
||||
return Ok(swf.compressed_len().into());
|
||||
}
|
||||
};
|
||||
Ok(0.into())
|
||||
}
|
||||
LoaderStream::Swf(swf, root) | LoaderStream::NotYetLoaded(swf, Some(root), _) => {
|
||||
if root.as_bitmap().is_some() {
|
||||
return Ok(swf.compressed_len().into());
|
||||
}
|
||||
Ok(root
|
||||
.as_movie_clip()
|
||||
.map(|mc| mc.compressed_loaded_bytes())
|
||||
.unwrap_or_default()
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// `content` getter
|
||||
|
@ -389,63 +392,77 @@ pub fn get_bytes<'gc>(
|
|||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(loader_stream) = this
|
||||
.as_loader_info_object()
|
||||
.and_then(|o| o.as_loader_stream())
|
||||
{
|
||||
let root = match &*loader_stream {
|
||||
LoaderStream::NotYetLoaded(_, None, _) => {
|
||||
// If we haven't even started loading yet (we have no root clip),
|
||||
// then return null. FIXME - we should probably store the ByteArray
|
||||
// in a field, and initialize it when we start loading.
|
||||
return Ok(Value::Null);
|
||||
let loader_info = this.as_loader_info_object().unwrap();
|
||||
let loader_stream = loader_info.as_loader_stream().unwrap();
|
||||
let (root, dobj) = match &*loader_stream {
|
||||
LoaderStream::NotYetLoaded(_, None, _) => {
|
||||
if loader_info.errored() {
|
||||
return Ok(activation
|
||||
.context
|
||||
.avm2
|
||||
.classes()
|
||||
.bytearray
|
||||
.construct(activation, &[])?
|
||||
.into());
|
||||
}
|
||||
LoaderStream::NotYetLoaded(swf, Some(_), _) => swf,
|
||||
LoaderStream::Swf(root, _) => root,
|
||||
};
|
||||
|
||||
let ba_class = activation.context.avm2.classes().bytearray;
|
||||
let ba = ba_class.construct(activation, &[])?;
|
||||
|
||||
if root.data().is_empty() {
|
||||
return Ok(ba.into());
|
||||
// If we haven't even started loading yet (we have no root clip),
|
||||
// then return null. FIXME - we should probably store the ByteArray
|
||||
// in a field, and initialize it when we start loading.
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
LoaderStream::NotYetLoaded(swf, Some(dobj), _) => (swf, dobj),
|
||||
LoaderStream::Swf(root, dobj) => (root, dobj),
|
||||
};
|
||||
|
||||
let mut ba_write = ba.as_bytearray_mut(activation.context.gc_context).unwrap();
|
||||
|
||||
// First, write a fake header corresponding to an
|
||||
// uncompressed SWF
|
||||
let mut header = root.header().swf_header().clone();
|
||||
header.compression = Compression::None;
|
||||
|
||||
write_swf(&header, &[], &mut *ba_write).unwrap();
|
||||
|
||||
// `swf` always writes an implicit end tag, let's cut that
|
||||
// off. We scroll back 2 bytes before writing the actual
|
||||
// datastream as it is guaranteed to at least be as long as
|
||||
// the implicit end tag we want to get rid of.
|
||||
let correct_header_length = ba_write.len() - 2;
|
||||
ba_write.set_position(correct_header_length);
|
||||
ba_write
|
||||
.write_bytes(root.data())
|
||||
.map_err(|e| e.to_avm(activation))?;
|
||||
|
||||
// `swf` wrote the wrong length (since we wrote the data
|
||||
// ourselves), so we need to overwrite it ourselves.
|
||||
ba_write.set_position(4);
|
||||
ba_write.set_endian(Endian::Little);
|
||||
ba_write
|
||||
.write_unsigned_int((root.data().len() + correct_header_length) as u32)
|
||||
.map_err(|e| e.to_avm(activation))?;
|
||||
|
||||
// Finally, reset the array to the correct state.
|
||||
ba_write.set_position(0);
|
||||
ba_write.set_endian(Endian::Big);
|
||||
let ba_class = activation.context.avm2.classes().bytearray;
|
||||
let ba = ba_class.construct(activation, &[])?;
|
||||
|
||||
if root.data().is_empty() {
|
||||
return Ok(ba.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
if dobj.as_bitmap().is_some() {
|
||||
// TODO - we need to construct a fake SWF that contains a 'Define' tag for the image data.
|
||||
avm2_stub_method!(
|
||||
activation,
|
||||
"flash.display.LoaderInfo",
|
||||
"bytes",
|
||||
"with image"
|
||||
);
|
||||
}
|
||||
|
||||
let mut ba_write = ba.as_bytearray_mut(activation.context.gc_context).unwrap();
|
||||
|
||||
// First, write a fake header corresponding to an
|
||||
// uncompressed SWF
|
||||
let mut header = root.header().swf_header().clone();
|
||||
header.compression = Compression::None;
|
||||
|
||||
write_swf(&header, &[], &mut *ba_write).unwrap();
|
||||
|
||||
// `swf` always writes an implicit end tag, let's cut that
|
||||
// off. We scroll back 2 bytes before writing the actual
|
||||
// datastream as it is guaranteed to at least be as long as
|
||||
// the implicit end tag we want to get rid of.
|
||||
let correct_header_length = ba_write.len() - 2;
|
||||
ba_write.set_position(correct_header_length);
|
||||
ba_write
|
||||
.write_bytes(root.data())
|
||||
.map_err(|e| e.to_avm(activation))?;
|
||||
|
||||
// `swf` wrote the wrong length (since we wrote the data
|
||||
// ourselves), so we need to overwrite it ourselves.
|
||||
ba_write.set_position(4);
|
||||
ba_write.set_endian(Endian::Little);
|
||||
ba_write
|
||||
.write_unsigned_int((root.data().len() + correct_header_length) as u32)
|
||||
.map_err(|e| e.to_avm(activation))?;
|
||||
|
||||
// Finally, reset the array to the correct state.
|
||||
ba_write.set_position(0);
|
||||
ba_write.set_endian(Endian::Big);
|
||||
|
||||
Ok(ba.into())
|
||||
}
|
||||
|
||||
/// `loader` getter
|
||||
|
|
|
@ -57,6 +57,15 @@ pub enum LoaderStream<'gc> {
|
|||
Swf(Arc<SwfMovie>, DisplayObject<'gc>),
|
||||
}
|
||||
|
||||
impl<'gc> LoaderStream<'gc> {
|
||||
pub fn movie(&self) -> &Arc<SwfMovie> {
|
||||
match self {
|
||||
LoaderStream::NotYetLoaded(movie, _, _) => movie,
|
||||
LoaderStream::Swf(movie, _) => movie,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An Object which represents a loadable object, such as a SWF movie or image
|
||||
/// resource.
|
||||
#[derive(Collect, Clone, Copy)]
|
||||
|
@ -103,6 +112,8 @@ pub struct LoaderInfoObjectData<'gc> {
|
|||
|
||||
#[collect(require_static)]
|
||||
content_type: ContentType,
|
||||
|
||||
errored: bool,
|
||||
}
|
||||
|
||||
impl<'gc> LoaderInfoObject<'gc> {
|
||||
|
@ -139,6 +150,7 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
.construct(activation, &[])?,
|
||||
cached_avm1movie: None,
|
||||
content_type: ContentType::Swf,
|
||||
errored: false,
|
||||
},
|
||||
))
|
||||
.into();
|
||||
|
@ -185,6 +197,7 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
.construct(activation, &[])?,
|
||||
cached_avm1movie: None,
|
||||
content_type: ContentType::Unknown,
|
||||
errored: false,
|
||||
},
|
||||
))
|
||||
.into();
|
||||
|
@ -218,6 +231,14 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_errored(&self, val: bool, mc: &Mutation<'gc>) {
|
||||
self.0.write(mc).errored = val;
|
||||
}
|
||||
|
||||
pub fn errored(&self) -> bool {
|
||||
self.0.read().errored
|
||||
}
|
||||
|
||||
pub fn fire_init_and_complete_events(
|
||||
&self,
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
|
@ -321,6 +342,7 @@ impl<'gc> LoaderInfoObject<'gc> {
|
|||
let empty_swf = Arc::new(SwfMovie::empty(activation.context.swf.version()));
|
||||
let loader_stream = LoaderStream::NotYetLoaded(empty_swf, None, false);
|
||||
self.set_loader_stream(loader_stream, activation.context.gc_context);
|
||||
self.set_errored(false, activation.context.gc_context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ use crate::loader::LoadManager;
|
|||
use crate::local_connection::LocalConnections;
|
||||
use crate::net_connection::NetConnections;
|
||||
use crate::player::Player;
|
||||
use crate::player::PostFrameCallback;
|
||||
use crate::prelude::*;
|
||||
use crate::socket::Sockets;
|
||||
use crate::streams::StreamManager;
|
||||
|
@ -252,7 +253,7 @@ pub struct UpdateContext<'a, 'gc> {
|
|||
/// These functions are run at the end of each frame execution.
|
||||
/// Currently, this is just used for handling `Loader.loadBytes`
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub post_frame_callbacks: &'a mut Vec<Box<dyn FnOnce(&mut UpdateContext<'_, '_>)>>,
|
||||
pub post_frame_callbacks: &'a mut Vec<PostFrameCallback<'gc>>,
|
||||
}
|
||||
|
||||
/// Convenience methods for controlling audio.
|
||||
|
|
|
@ -348,6 +348,9 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
|
|||
.expect("can't throw from post_instantiation -_-");
|
||||
self.0.write(mc).avm2_object = Some(bitmap.into());
|
||||
|
||||
// Use a dummy BitmapData when calling the constructor on the user subclass
|
||||
// - the constructor should see an invalid BitmapData before calling 'super',
|
||||
// even if it's linked to an image.
|
||||
let bitmap_data_obj = Avm2BitmapDataObject::from_bitmap_data_internal(
|
||||
&mut activation,
|
||||
BitmapDataWrapper::dummy(mc),
|
||||
|
@ -355,6 +358,14 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
|
|||
)
|
||||
.expect("can't throw from post_instantiation -_-");
|
||||
|
||||
if bitmap_data_obj.as_bitmap_data().unwrap().disposed() {
|
||||
// Set the real bitmapdata, in case this Bitmap was constructed from a Loader
|
||||
// (it will have real data that doesn't come from a linked class)
|
||||
bitmap_data_obj.init_bitmap_data(
|
||||
activation.context.gc_context,
|
||||
self.bitmap_data_wrapper(),
|
||||
);
|
||||
}
|
||||
self.set_bitmap_data(
|
||||
&mut activation.context,
|
||||
bitmap_data_obj.as_bitmap_data().unwrap(),
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::avm1::{ExecutionReason, NativeObject};
|
|||
use crate::avm1::{Object, SoundObject, TObject, Value};
|
||||
use crate::avm2::bytearray::ByteArrayStorage;
|
||||
use crate::avm2::object::{
|
||||
BitmapDataObject, ByteArrayObject, EventObject as Avm2EventObject, LoaderStream, TObject as _,
|
||||
ByteArrayObject, EventObject as Avm2EventObject, LoaderStream, TObject as _,
|
||||
};
|
||||
use crate::avm2::{
|
||||
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, Object as Avm2Object,
|
||||
|
@ -18,12 +18,12 @@ use crate::bitmap::bitmap_data::Color;
|
|||
use crate::bitmap::bitmap_data::{BitmapData, BitmapDataWrapper};
|
||||
use crate::context::{ActionQueue, ActionType, UpdateContext};
|
||||
use crate::display_object::{
|
||||
DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer, TInteractiveObject,
|
||||
Bitmap, DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer, TInteractiveObject,
|
||||
};
|
||||
use crate::events::ClipEvent;
|
||||
use crate::frame_lifecycle::catchup_display_object_to_frame;
|
||||
use crate::limits::ExecutionLimit;
|
||||
use crate::player::Player;
|
||||
use crate::player::{Player, PostFrameCallback};
|
||||
use crate::streams::NetStream;
|
||||
use crate::string::AvmString;
|
||||
use crate::tag_utils::SwfMovie;
|
||||
|
@ -1688,9 +1688,9 @@ impl<'gc> Loader<'gc> {
|
|||
.as_loader_info_object()
|
||||
.unwrap()
|
||||
.set_content_type(sniffed_type, activation.context.gc_context);
|
||||
let fake_movie = Arc::new(SwfMovie::empty_fake_compressed_len(
|
||||
let fake_movie = Arc::new(SwfMovie::fake_with_compressed_len(
|
||||
activation.context.swf.version(),
|
||||
length,
|
||||
data.len(),
|
||||
));
|
||||
|
||||
// Expose 'bytesTotal' (via the fake movie) during the first 'progress' event,
|
||||
|
@ -1764,23 +1764,16 @@ impl<'gc> Loader<'gc> {
|
|||
mc.compressed_loaded_bytes() as usize,
|
||||
mc.compressed_total_bytes() as usize,
|
||||
)?;
|
||||
let cb: Box<dyn FnOnce(&mut UpdateContext<'_, '_>)> = Box::new(move |uc| {
|
||||
let target_clip = match uc.load_manager.get_loader(handle) {
|
||||
Some(Loader::Movie { target_clip, .. }) => *target_clip,
|
||||
None => return,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if let Err(e) = Loader::movie_loader_complete(
|
||||
handle,
|
||||
uc,
|
||||
Some(target_clip),
|
||||
0,
|
||||
false,
|
||||
) {
|
||||
tracing::error!("Error finishing loading of Loader.loadBytes movie {target_clip:?}: {e:?}");
|
||||
}
|
||||
uc.post_frame_callbacks.push(PostFrameCallback {
|
||||
callback: Box::new(move |uc, dobj: DisplayObject<'_>| {
|
||||
if let Err(e) =
|
||||
Loader::movie_loader_complete(handle, uc, Some(dobj), 0, false)
|
||||
{
|
||||
tracing::error!("Error finishing loading of Loader.loadBytes movie {dobj:?}: {e:?}");
|
||||
}
|
||||
}),
|
||||
data: clip,
|
||||
});
|
||||
uc.post_frame_callbacks.push(cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1798,7 +1791,10 @@ impl<'gc> Loader<'gc> {
|
|||
return Ok(());
|
||||
}
|
||||
ContentType::Gif | ContentType::Jpeg | ContentType::Png => {
|
||||
let library = activation.context.library.library_for_movie_mut(movie);
|
||||
let library = activation
|
||||
.context
|
||||
.library
|
||||
.library_for_movie_mut(movie.clone());
|
||||
|
||||
library.set_avm2_domain(domain);
|
||||
|
||||
|
@ -1807,38 +1803,92 @@ impl<'gc> Loader<'gc> {
|
|||
let bitmap = ruffle_render::utils::decode_define_bits_jpeg(data, None)?;
|
||||
|
||||
let transparency = true;
|
||||
let bitmap_data = BitmapData::new_with_pixels(
|
||||
bitmap.width(),
|
||||
bitmap.height(),
|
||||
transparency,
|
||||
bitmap.as_colors().map(Color::from).collect(),
|
||||
let bitmap_data = BitmapDataWrapper::new(GcCell::new(
|
||||
activation.context.gc_context,
|
||||
BitmapData::new_with_pixels(
|
||||
bitmap.width(),
|
||||
bitmap.height(),
|
||||
transparency,
|
||||
bitmap.as_colors().map(Color::from).collect(),
|
||||
),
|
||||
));
|
||||
let bitmap_dobj = Bitmap::new_with_bitmap_data(
|
||||
activation.context.gc_context,
|
||||
0,
|
||||
bitmap_data,
|
||||
false,
|
||||
&activation.caller_movie_or_root(),
|
||||
);
|
||||
let bitmapdata_wrapper =
|
||||
BitmapDataWrapper::new(GcCell::new(activation.context.gc_context, bitmap_data));
|
||||
let bitmapdata_class = activation.context.avm2.classes().bitmapdata;
|
||||
let bitmapdata_avm2 = BitmapDataObject::from_bitmap_data_internal(
|
||||
&mut activation,
|
||||
bitmapdata_wrapper,
|
||||
bitmapdata_class,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bitmap_avm2 = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.bitmap
|
||||
.construct(&mut activation, &[bitmapdata_avm2.into()])
|
||||
.unwrap();
|
||||
let bitmap_obj = bitmap_avm2.as_display_object().unwrap();
|
||||
if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data {
|
||||
let fake_movie = Arc::new(SwfMovie::fake_with_compressed_len(
|
||||
activation.context.swf.version(),
|
||||
data.len(),
|
||||
));
|
||||
|
||||
loader_info
|
||||
.as_loader_info_object()
|
||||
.unwrap()
|
||||
.set_loader_stream(
|
||||
LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj.into()), false),
|
||||
activation.context.gc_context,
|
||||
);
|
||||
}
|
||||
|
||||
Loader::movie_loader_progress(handle, &mut activation.context, length, length)?;
|
||||
Loader::movie_loader_complete(
|
||||
handle,
|
||||
&mut activation.context,
|
||||
Some(bitmap_obj),
|
||||
status,
|
||||
redirected,
|
||||
)?;
|
||||
|
||||
if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data {
|
||||
let fake_movie = Arc::new(SwfMovie::fake_with_compressed_data(
|
||||
activation.context.swf.version(),
|
||||
data.to_vec(),
|
||||
));
|
||||
|
||||
loader_info
|
||||
.as_loader_info_object()
|
||||
.unwrap()
|
||||
.set_loader_stream(
|
||||
LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj.into()), false),
|
||||
activation.context.gc_context,
|
||||
);
|
||||
}
|
||||
|
||||
if from_bytes {
|
||||
// Note - flash player seems to delay this for *two* frames for some reason
|
||||
uc.post_frame_callbacks.push(PostFrameCallback {
|
||||
callback: Box::new(move |uc, bitmap_obj| {
|
||||
uc.post_frame_callbacks.push(PostFrameCallback {
|
||||
callback: Box::new(move |uc, bitmap_obj| {
|
||||
bitmap_obj.post_instantiation(uc, None, Instantiator::Movie, true);
|
||||
if let Err(e) = Loader::movie_loader_complete(
|
||||
handle,
|
||||
uc,
|
||||
Some(bitmap_obj),
|
||||
status,
|
||||
redirected,
|
||||
) {
|
||||
tracing::error!("Error finishing loading of Loader.loadBytes image {bitmap_obj:?}: {e:?}");
|
||||
}
|
||||
}),
|
||||
data: bitmap_obj,
|
||||
})
|
||||
}),
|
||||
data: bitmap_dobj.into(),
|
||||
});
|
||||
} else {
|
||||
bitmap_dobj.post_instantiation(
|
||||
&mut activation.context,
|
||||
None,
|
||||
Instantiator::Movie,
|
||||
true,
|
||||
);
|
||||
Loader::movie_loader_complete(
|
||||
handle,
|
||||
&mut activation.context,
|
||||
Some(bitmap_dobj.into()),
|
||||
status,
|
||||
redirected,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
ContentType::Unknown => {
|
||||
match vm_data {
|
||||
|
@ -1858,20 +1908,35 @@ impl<'gc> Loader<'gc> {
|
|||
redirected,
|
||||
)?;
|
||||
}
|
||||
MovieLoaderVMData::Avm2 { .. } => {
|
||||
MovieLoaderVMData::Avm2 { loader_info, .. } => {
|
||||
let fake_movie = Arc::new(SwfMovie::fake_with_compressed_len(
|
||||
activation.context.swf.version(),
|
||||
data.len(),
|
||||
));
|
||||
|
||||
let loader_info = loader_info.as_loader_info_object().unwrap();
|
||||
loader_info.set_errored(true, activation.context.gc_context);
|
||||
|
||||
loader_info.set_loader_stream(
|
||||
LoaderStream::NotYetLoaded(fake_movie, None, false),
|
||||
activation.context.gc_context,
|
||||
);
|
||||
|
||||
Loader::movie_loader_progress(
|
||||
handle,
|
||||
&mut activation.context,
|
||||
length,
|
||||
length,
|
||||
)?;
|
||||
let mut error = "Error #2124: Loaded file is an unknown type.".to_string();
|
||||
if !from_bytes {
|
||||
error += &format!(" URL: {url}");
|
||||
}
|
||||
|
||||
Loader::movie_loader_error(
|
||||
handle,
|
||||
uc,
|
||||
AvmString::new_utf8(
|
||||
uc.gc_context,
|
||||
&format!("Error #2124: Loaded file is an unknown type. URL: {url}"),
|
||||
),
|
||||
AvmString::new_utf8(uc.gc_context, error),
|
||||
status,
|
||||
redirected,
|
||||
url,
|
||||
|
@ -2097,8 +2162,9 @@ impl<'gc> Loader<'gc> {
|
|||
// in `MovieClip.on_exit_frame`
|
||||
MovieLoaderVMData::Avm2 { loader_info, .. } => {
|
||||
let loader_info_obj = loader_info.as_loader_info_object().unwrap();
|
||||
let current_movie = { loader_info_obj.as_loader_stream().unwrap().movie().clone() };
|
||||
loader_info_obj.set_loader_stream(
|
||||
LoaderStream::Swf(target_clip.as_movie_clip().unwrap().movie(), dobj.unwrap()),
|
||||
LoaderStream::Swf(current_movie, dobj.unwrap()),
|
||||
uc.gc_context,
|
||||
);
|
||||
|
||||
|
|
|
@ -177,9 +177,16 @@ struct GcRootData<'gc> {
|
|||
/// Dynamic root for allowing handles to GC objects to exist outside of the GC.
|
||||
dynamic_root: DynamicRootSet<'gc>,
|
||||
|
||||
post_frame_callbacks: Vec<PostFrameCallback<'gc>>,
|
||||
}
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct PostFrameCallback<'gc> {
|
||||
#[collect(require_static)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
post_frame_callbacks: Vec<Box<dyn FnOnce(&mut UpdateContext<'_, '_>) + 'static>>,
|
||||
pub callback: Box<dyn for<'b> FnOnce(&mut UpdateContext<'_, 'b>, DisplayObject<'b>) + 'static>,
|
||||
pub data: DisplayObject<'gc>,
|
||||
}
|
||||
|
||||
impl<'gc> GcRootData<'gc> {
|
||||
|
@ -208,7 +215,7 @@ impl<'gc> GcRootData<'gc> {
|
|||
&mut Sockets<'gc>,
|
||||
&mut NetConnections<'gc>,
|
||||
&mut LocalConnections<'gc>,
|
||||
&mut Vec<Box<dyn FnOnce(&mut UpdateContext<'_, '_>) + 'static>>,
|
||||
&mut Vec<PostFrameCallback<'gc>>,
|
||||
DynamicRootSet<'gc>,
|
||||
) {
|
||||
(
|
||||
|
@ -1569,8 +1576,8 @@ impl Player {
|
|||
|
||||
// Only run the current list of callbacks - any callbacks added during callback execution
|
||||
// will be run at the end of the *next* frame.
|
||||
for callback in std::mem::take(context.post_frame_callbacks) {
|
||||
callback(context);
|
||||
for cb in std::mem::take(context.post_frame_callbacks) {
|
||||
(cb.callback)(context, cb.data);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -91,15 +91,30 @@ impl SwfMovie {
|
|||
/// This is used by `Loader` when firing an initial `progress` event:
|
||||
/// `LoaderInfo.bytesTotal` is set to the actual value, but no data is available,
|
||||
/// and `LoaderInfo.parameters` is empty.
|
||||
pub fn empty_fake_compressed_len(swf_version: u8, compressed_len: usize) -> Self {
|
||||
pub fn fake_with_compressed_len(swf_version: u8, compressed_len: usize) -> Self {
|
||||
Self {
|
||||
header: HeaderExt::default_with_swf_version(swf_version),
|
||||
data: vec![],
|
||||
compressed_len,
|
||||
data: Vec::new(),
|
||||
url: "file:///".into(),
|
||||
loader_url: None,
|
||||
parameters: Vec::new(),
|
||||
encoding: swf::UTF_8,
|
||||
is_movie: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `fake_with_compressed_len`, but uses actual data.
|
||||
/// This is used when loading a Bitmap to expose the underlying content
|
||||
pub fn fake_with_compressed_data(swf_version: u8, compressed_data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
header: HeaderExt::default_with_swf_version(swf_version),
|
||||
compressed_len: compressed_data.len(),
|
||||
data: compressed_data,
|
||||
url: "file:///".into(),
|
||||
loader_url: None,
|
||||
parameters: Vec::new(),
|
||||
encoding: swf::UTF_8,
|
||||
compressed_len,
|
||||
is_movie: false,
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,121 @@
|
|||
package {
|
||||
import flash.display.Stage;
|
||||
import flash.display.Loader;
|
||||
import flash.display.Loader;
|
||||
import flash.net.URLRequest;
|
||||
import flash.errors.IllegalOperationError;
|
||||
import flash.display.Sprite;
|
||||
import flash.events.Event;
|
||||
import flash.events.ProgressEvent;
|
||||
import flash.events.HTTPStatusEvent;
|
||||
import flash.display.MovieClip;
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
public class Main extends MovieClip {
|
||||
|
||||
[Embed(source="5000x5000.png", mimeType="application/octet-stream")]
|
||||
private static var LOADABLE_IMAGE_BYTES:Class;
|
||||
private var loader: Loader;
|
||||
|
||||
public function Main() {
|
||||
this.setupLoader();
|
||||
trace("Calling super() in Main()");
|
||||
super();
|
||||
trace("Called super() in Main()");
|
||||
|
||||
var self = this;
|
||||
|
||||
this.addEventListener(Event.ENTER_FRAME, function(e) {
|
||||
// FIXME - re-enable this when the timing of 'content' being
|
||||
// set in Ruffle matches Flash Player
|
||||
//trace("enterFrame in Test: this.loader.content = " + self.loader.content);
|
||||
});
|
||||
|
||||
this.addEventListener(Event.EXIT_FRAME, function(e) {
|
||||
trace("exitFrame in Test");
|
||||
});
|
||||
}
|
||||
|
||||
private function dumpParams(obj: Object) {
|
||||
var out = []
|
||||
for (var key in obj) {
|
||||
out.push(key + " = " + obj[key]);
|
||||
}
|
||||
out.sort();
|
||||
trace("Parameters: (len=" + out.length + ")");
|
||||
trace(out);
|
||||
}
|
||||
|
||||
private function dumpLoader(loader: Loader) {
|
||||
trace("loader.content = " + loader.content);
|
||||
trace("loader.contentLoaderInfo.content = " + loader.contentLoaderInfo.content);
|
||||
trace("loader.contentLoaderInfo.bytesLoaded = " + loader.contentLoaderInfo.bytesLoaded);
|
||||
trace("loader.contentLoaderInfo.bytesTotal = " + loader.contentLoaderInfo.bytesTotal);
|
||||
//trace("loader.contentLoaderInfo.bytes?.length = " + (loader.contentLoaderInfo.bytes ? loader.contentLoaderInfo.bytes.length : null));
|
||||
trace("loader.contentLoaderInfo.url = " + loader.contentLoaderInfo.url);
|
||||
trace("loader.contentLoaderInfo.parameters = " + loader.contentLoaderInfo.parameters);
|
||||
}
|
||||
|
||||
function setupLoader() {
|
||||
this.loader = new Loader();
|
||||
this.addChild(loader);
|
||||
this.dumpLoader(loader);
|
||||
|
||||
|
||||
function dump(event:Event) {
|
||||
var url = loader.contentLoaderInfo.url;
|
||||
if (url) {
|
||||
// This truncates the path to 'file:///' to make the output
|
||||
// reproducible across deifferent machines
|
||||
url = url.substr(0, 8);
|
||||
}
|
||||
trace("Event " + event + ": "
|
||||
+ "loader.numChildren = " + loader.numChildren
|
||||
+ ", loader.content = " + loader.content
|
||||
+ ", loader.contentLoaderInfo.bytesLoaded = " + loader.contentLoaderInfo.bytesLoaded
|
||||
+ ", loader.contentLoaderInfo.bytesTotal = " + loader.contentLoaderInfo.bytesTotal
|
||||
// TODO - enable this when we correctly construct a fake SWF for the image
|
||||
//+ ", loader.contentLoaderInfo.bytes.length = " + loader.contentLoaderInfo.bytes.length
|
||||
+ ", loader.contentLoaderInfo.url = " + url);
|
||||
}
|
||||
|
||||
loader.contentLoaderInfo.addEventListener(Event.OPEN, function(e) {
|
||||
dump(e);
|
||||
});
|
||||
|
||||
loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, function(e) {
|
||||
// FIXME - the 'bytesLoaded' and 'bytesTotal' values printed here are wrong,
|
||||
// as they are not properly implemented in Ruffle. Once the implementation is fixed,
|
||||
// the output of this test will change.
|
||||
dump(e);
|
||||
});
|
||||
|
||||
loader.contentLoaderInfo.addEventListener(Event.INIT, function(e) {
|
||||
dump(e);
|
||||
});
|
||||
|
||||
loader.contentLoaderInfo.addEventListener(HTTPStatusEvent.HTTP_STATUS, function(e) {
|
||||
dump(e);
|
||||
});
|
||||
|
||||
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e) {
|
||||
dump(e);
|
||||
});
|
||||
|
||||
loader.loadBytes(ByteArray(new LOADABLE_IMAGE_BYTES()));
|
||||
trace("Directly after load:");
|
||||
this.dumpLoader(loader);
|
||||
|
||||
// Enable this to print and save the fake SWF
|
||||
/*var bytes = loader.contentLoaderInfo.bytes;
|
||||
new FileReference().save(bytes);
|
||||
var readBack = [];
|
||||
for (var i = 0; i < 64; i++) {
|
||||
readBack.push(bytes[i]);;
|
||||
}
|
||||
trace(readBack);*/
|
||||
|
||||
return loader;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
loader.content = null
|
||||
loader.contentLoaderInfo.content = null
|
||||
loader.contentLoaderInfo.bytesLoaded = 0
|
||||
loader.contentLoaderInfo.bytesTotal = 0
|
||||
loader.contentLoaderInfo.url = null
|
||||
loader.contentLoaderInfo.parameters = [object Object]
|
||||
Event [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=0 bytesTotal=28417]: loader.numChildren = 0, loader.content = null, loader.contentLoaderInfo.bytesLoaded = 0, loader.contentLoaderInfo.bytesTotal = 28417, loader.contentLoaderInfo.url = null
|
||||
Event [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=28417 bytesTotal=28417]: loader.numChildren = 0, loader.content = null, loader.contentLoaderInfo.bytesLoaded = 28417, loader.contentLoaderInfo.bytesTotal = 28417, loader.contentLoaderInfo.url = null
|
||||
Directly after load:
|
||||
loader.content = null
|
||||
loader.contentLoaderInfo.content = null
|
||||
loader.contentLoaderInfo.bytesLoaded = 28417
|
||||
loader.contentLoaderInfo.bytesTotal = 28417
|
||||
loader.contentLoaderInfo.url = null
|
||||
loader.contentLoaderInfo.parameters = [object Object]
|
||||
Calling super() in Main()
|
||||
Called super() in Main()
|
||||
exitFrame in Test
|
||||
exitFrame in Test
|
||||
Event [Event type="init" bubbles=false cancelable=false eventPhase=2]: loader.numChildren = 1, loader.content = [object Bitmap], loader.contentLoaderInfo.bytesLoaded = 28417, loader.contentLoaderInfo.bytesTotal = 28417, loader.contentLoaderInfo.url = file:///
|
||||
Event [Event type="complete" bubbles=false cancelable=false eventPhase=2]: loader.numChildren = 1, loader.content = [object Bitmap], loader.contentLoaderInfo.bytesLoaded = 28417, loader.contentLoaderInfo.bytesTotal = 28417, loader.contentLoaderInfo.url = file:///
|
||||
exitFrame in Test
|
||||
exitFrame in Test
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
num_ticks = 4
|
|
@ -0,0 +1,47 @@
|
|||
package {
|
||||
import flash.display.Loader;
|
||||
import flash.events.Event;
|
||||
import flash.events.ProgressEvent;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.net.URLRequest;
|
||||
import flash.display.MovieClip;
|
||||
|
||||
public class Test {
|
||||
[Embed(source = "data.txt", mimeType="application/octet-stream")]
|
||||
public static var DATA: Class;
|
||||
|
||||
public function Test(main: MovieClip) {
|
||||
var loader = new Loader();
|
||||
loader.contentLoaderInfo.addEventListener(Event.OPEN, function(e) {
|
||||
printEvent(Event.OPEN, e, loader);
|
||||
});
|
||||
loader.contentLoaderInfo.addEventListener(Event.INIT, function(e) {
|
||||
printEvent(Event.INIT, e, loader);
|
||||
});
|
||||
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, function(e) {
|
||||
printEvent(IOErrorEvent.IO_ERROR, e, loader);
|
||||
});
|
||||
loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, function(e) {
|
||||
printEvent(ProgressEvent.PROGRESS, e, loader);
|
||||
});
|
||||
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e) {
|
||||
printEvent(Event.COMPLETE, e, loader);
|
||||
});
|
||||
main.addChild(loader);
|
||||
trace("Calling loadBytes");
|
||||
loader.loadBytes(new DATA());
|
||||
trace("Immediately after loadBytes:");
|
||||
trace("loader.contentLoaderInfo.bytesLoaded = " + loader.contentLoaderInfo.bytesLoaded);
|
||||
trace("loader.contentLoaderInfo.bytesTotal = " + loader.contentLoaderInfo.bytesTotal);
|
||||
trace("loader.contentLoaderInfo.bytes.length = " + loader.contentLoaderInfo.bytes.length);
|
||||
}
|
||||
|
||||
private function printEvent(name: String, event: Event, loader: Loader) {
|
||||
var eventString = event.toString();
|
||||
trace("Event: " + name + " event: " + eventString);
|
||||
// FIXME - print 'bytesLoaded' and 'bytesTotal' when Ruffle properly matches Flash Player
|
||||
trace("Content: " + loader.content);
|
||||
trace("Bytes length: " + loader.contentLoaderInfo.bytes.length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
This is some data
|
|
@ -0,0 +1,14 @@
|
|||
Calling loadBytes
|
||||
Event: progress event: [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=0 bytesTotal=20]
|
||||
Content: null
|
||||
Bytes length: 0
|
||||
Event: progress event: [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=20 bytesTotal=20]
|
||||
Content: null
|
||||
Bytes length: 0
|
||||
Event: ioError event: [IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2124: Loaded file is an unknown type."]
|
||||
Content: null
|
||||
Bytes length: 0
|
||||
Immediately after loadBytes:
|
||||
loader.contentLoaderInfo.bytesLoaded = 20
|
||||
loader.contentLoaderInfo.bytesTotal = 20
|
||||
loader.contentLoaderInfo.bytes.length = 0
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
num_frames = 2
|
|
@ -36,9 +36,15 @@
|
|||
eventString = eventString.substr(0, index) + "file:///[[RUFFLE PATH]]";
|
||||
}
|
||||
trace("Event: " + name + " event: " + eventString);
|
||||
// FIXME - print 'bytesLoaded' and 'bytesTotal' when Ruffle properly matches Flash Player
|
||||
trace("Content: " + loader.content);
|
||||
trace("Bytes length: " + loader.contentLoaderInfo.bytes.length);
|
||||
trace("loader.contentLoaderInfo.bytesLoaded = " + loader.contentLoaderInfo.bytesLoaded);
|
||||
trace("loader.contentLoaderInfo.bytesTotal = " + loader.contentLoaderInfo.bytesTotal);
|
||||
try {
|
||||
trace("loader.contentLoaderInfo.frameRate = " + loader.contentLoaderInfo.frameRate);
|
||||
} catch (e) {
|
||||
trace("Caught error: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,24 @@
|
|||
Event: open event: [Event type="open" bubbles=false cancelable=false eventPhase=2]
|
||||
Content: null
|
||||
Bytes length: 0
|
||||
loader.contentLoaderInfo.bytesLoaded = 0
|
||||
loader.contentLoaderInfo.bytesTotal = 0
|
||||
Caught error: Error: Error #2099: The loading object is not sufficiently loaded to provide this information.
|
||||
Event: progress event: [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=0 bytesTotal=20]
|
||||
Content: null
|
||||
Bytes length: 0
|
||||
loader.contentLoaderInfo.bytesLoaded = 0
|
||||
loader.contentLoaderInfo.bytesTotal = 20
|
||||
Caught error: Error: Error #2099: The loading object is not sufficiently loaded to provide this information.
|
||||
Event: progress event: [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=20 bytesTotal=20]
|
||||
Content: null
|
||||
Bytes length: 0
|
||||
loader.contentLoaderInfo.bytesLoaded = 20
|
||||
loader.contentLoaderInfo.bytesTotal = 20
|
||||
Caught error: Error: Error #2099: The loading object is not sufficiently loaded to provide this information.
|
||||
Event: ioError event: [IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2124: Loaded file is an unknown type. URL: file:///[[RUFFLE PATH]]
|
||||
Content: null
|
||||
Bytes length: 0
|
||||
loader.contentLoaderInfo.bytesLoaded = 20
|
||||
loader.contentLoaderInfo.bytesTotal = 20
|
||||
Caught error: Error: Error #2099: The loading object is not sufficiently loaded to provide this information.
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue