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:
Aaron Hill 2024-01-17 18:39:14 -05:00
parent 173efbb77a
commit 8dbcfe26f9
22 changed files with 496 additions and 131 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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(),

View File

@ -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,
);

View File

@ -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);
}
});

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -0,0 +1 @@
num_ticks = 4

View File

@ -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);
}
}
}

View File

@ -0,0 +1 @@
This is some data

View File

@ -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.

View File

@ -0,0 +1 @@
num_frames = 2

View File

@ -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);
}
}
}
}

View File

@ -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.