Skip to content

Commit 8d8a760

Browse files
Aaron1011Herschel
authored andcommitted
avm2: Partially implement URLLoader and related classes
This PR implements the `URLLoader` class, allowing AVM2 scripts to load data from a URL. This requires several other related classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be implemented as well. Currently implemented: * Fetching from URLs using the 'navigator' backend * The `text` and `binary` data formats (which store data in a `String` or `ByteArray` respectively) * The `open`, `complete`, and `ioError` events * The `bytesLoaded`, `bytesTotal`, and `data` properties Not yet implemented: * The HTTP and security events * All of the properties of `IOError` * The properties on `URLRequest` (besides `url`) * The "variables" data format This should be enough to get some basic uses of `URLLoader` working (e.g. simple GET requests to a particular website). Note that in Flash's `playerglobal`, the `URLLoader` class is just a think wrapper around the more general `URLStream`. However, implementing `URLStream` will require changes to `Navigator`` to support notifications when data arrives in the stream. When that happens, we should be able to re-use a large amount of the code in this PR.
1 parent 7130c6c commit 8d8a760

File tree

17 files changed

+583
-10
lines changed

17 files changed

+583
-10
lines changed

core/src/avm2.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ macro_rules! avm_debug {
2323

2424
pub mod activation;
2525
mod array;
26-
mod bytearray;
26+
pub mod bytearray;
2727
mod class;
2828
mod domain;
2929
mod events;
3030
mod function;
3131
mod globals;
3232
mod method;
33-
mod names;
33+
pub mod names;
3434
pub mod object;
3535
mod property;
3636
mod property_map;

core/src/avm2/events.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ pub enum EventData<'gc> {
101101
button_down: bool,
102102
delta: i32,
103103
},
104+
// FIXME - define properties from 'ErrorEvent' and 'TextEvent'
105+
IOError {
106+
// FIXME - this should be inherited in some way from
107+
// the (currently not declared) `TextEvent`
108+
text: AvmString<'gc>,
109+
},
104110
}
105111

106112
impl<'gc> EventData<'gc> {

core/src/avm2/globals.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub struct SystemPrototypes<'gc> {
8888
pub nativemenu: Object<'gc>,
8989
pub contextmenu: Object<'gc>,
9090
pub mouseevent: Object<'gc>,
91+
pub ioerrorevent: Object<'gc>,
9192
}
9293

9394
impl<'gc> SystemPrototypes<'gc> {
@@ -150,6 +151,7 @@ impl<'gc> SystemPrototypes<'gc> {
150151
nativemenu: empty,
151152
contextmenu: empty,
152153
mouseevent: empty,
154+
ioerrorevent: empty,
153155
}
154156
}
155157
}
@@ -202,6 +204,7 @@ pub struct SystemClasses<'gc> {
202204
pub nativemenu: ClassObject<'gc>,
203205
pub contextmenu: ClassObject<'gc>,
204206
pub mouseevent: ClassObject<'gc>,
207+
pub ioerrorevent: ClassObject<'gc>,
205208
}
206209

207210
impl<'gc> SystemClasses<'gc> {
@@ -264,6 +267,7 @@ impl<'gc> SystemClasses<'gc> {
264267
nativemenu: object,
265268
contextmenu: object,
266269
mouseevent: object,
270+
ioerrorevent: object,
267271
}
268272
}
269273
}
@@ -576,11 +580,12 @@ pub fn load_player_globals<'gc>(
576580
flash::events::mouseevent::create_class(mc),
577581
script
578582
);
579-
class(
583+
avm2_system_class!(
584+
ioerrorevent,
580585
activation,
581586
flash::events::ioerrorevent::create_class(mc),
582-
script,
583-
)?;
587+
script
588+
);
584589
class(
585590
activation,
586591
flash::events::contextmenuevent::create_class(mc),
@@ -927,6 +932,12 @@ pub fn load_player_globals<'gc>(
927932
flash::net::object_encoding::create_class(mc),
928933
script,
929934
)?;
935+
class(activation, flash::net::url_loader::create_class(mc), script)?;
936+
class(
937+
activation,
938+
flash::net::url_loader_data_format::create_class(mc),
939+
script,
940+
)?;
930941
class(
931942
activation,
932943
flash::net::url_request::create_class(mc),

core/src/avm2/globals/flash/events/ioerrorevent.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use crate::avm2::activation::Activation;
22
use crate::avm2::class::{Class, ClassAttributes};
3+
use crate::avm2::events::EventData;
34
use crate::avm2::method::Method;
5+
use crate::avm2::method::NativeMethodImpl;
46
use crate::avm2::names::{Namespace, QName};
57
use crate::avm2::object::Object;
8+
use crate::avm2::object::TObject;
69
use crate::avm2::value::Value;
710
use crate::avm2::Error;
811
use gc_arena::{GcCell, MutationContext};
@@ -28,6 +31,24 @@ pub fn class_init<'gc>(
2831
Ok(Value::Undefined)
2932
}
3033

34+
/// Implements `text`'s getter.
35+
// FIXME - we should define the ancestor class `TextEvent`
36+
// and declare this getter there
37+
pub fn text<'gc>(
38+
_activation: &mut Activation<'_, 'gc, '_>,
39+
this: Option<Object<'gc>>,
40+
_args: &[Value<'gc>],
41+
) -> Result<Value<'gc>, Error> {
42+
if let Some(this) = this {
43+
if let Some(evt) = this.as_event() {
44+
if let EventData::IOError { text } = evt.event_data() {
45+
return Ok(Value::String(*text));
46+
}
47+
}
48+
}
49+
Ok(Value::Undefined)
50+
}
51+
3152
/// Construct `IOErrorEvent`'s class.
3253
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
3354
let class = Class::new(
@@ -47,5 +68,12 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
4768

4869
write.define_public_constant_string_class_traits(CONSTANTS);
4970

71+
const PUBLIC_INSTANCE_PROPERTIES: &[(
72+
&str,
73+
Option<NativeMethodImpl>,
74+
Option<NativeMethodImpl>,
75+
)] = &[("text", Some(text), None)];
76+
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
77+
5078
class
5179
}

core/src/avm2/globals/flash/net.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
33
pub mod object_encoding;
44
pub mod sharedobject;
5+
pub mod url_loader;
6+
pub mod url_loader_data_format;
57
pub mod url_request;
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//! `flash.net.URLLoader` builtin/prototype
2+
3+
use crate::avm2::activation::Activation;
4+
use crate::avm2::class::Class;
5+
use crate::avm2::method::{Method, NativeMethodImpl};
6+
use crate::avm2::names::{Namespace, QName};
7+
use crate::avm2::object::TObject;
8+
use crate::avm2::value::Value;
9+
use crate::avm2::{Error, Object};
10+
use crate::backend::navigator::RequestOptions;
11+
use crate::loader::DataFormat;
12+
use crate::string::AvmString;
13+
use gc_arena::{GcCell, MutationContext};
14+
15+
/// Implements `flash.net.URLLoader`'s class constructor.
16+
pub fn class_init<'gc>(
17+
_activation: &mut Activation<'_, 'gc, '_>,
18+
_this: Option<Object<'gc>>,
19+
_args: &[Value<'gc>],
20+
) -> Result<Value<'gc>, Error> {
21+
Ok(Value::Undefined)
22+
}
23+
24+
/// Implements `flash.net.URLLoader`'s instance constructor.
25+
pub fn instance_init<'gc>(
26+
activation: &mut Activation<'_, 'gc, '_>,
27+
this: Option<Object<'gc>>,
28+
args: &[Value<'gc>],
29+
) -> Result<Value<'gc>, Error> {
30+
if let Some(mut this) = this {
31+
activation.super_init(this, &[])?;
32+
this.set_property(
33+
&QName::new(Namespace::public(), "dataFormat").into(),
34+
"text".into(),
35+
activation,
36+
)?;
37+
this.set_property(
38+
&QName::new(Namespace::public(), "data").into(),
39+
Value::Undefined,
40+
activation,
41+
)?;
42+
43+
if let Some(request) = args.get(0) {
44+
if request != &Value::Null {
45+
load(activation, Some(this), args)?;
46+
}
47+
}
48+
}
49+
Ok(Value::Undefined)
50+
}
51+
52+
pub fn bytes_loaded<'gc>(
53+
activation: &mut Activation<'_, 'gc, '_>,
54+
this: Option<Object<'gc>>,
55+
args: &[Value<'gc>],
56+
) -> Result<Value<'gc>, Error> {
57+
// For now, just use `bytes_total`. The `bytesLoaded` value
58+
// should really update as the download progresses, instead
59+
// of jumping at completion from 0 to the total length
60+
log::warn!("URLLoader.bytesLoaded - not yet implemented");
61+
bytes_total(activation, this, args)
62+
}
63+
64+
pub fn bytes_total<'gc>(
65+
activation: &mut Activation<'_, 'gc, '_>,
66+
this: Option<Object<'gc>>,
67+
_args: &[Value<'gc>],
68+
) -> Result<Value<'gc>, Error> {
69+
if let Some(this) = this {
70+
let data =
71+
this.get_property(&QName::new(Namespace::public(), "data").into(), activation)?;
72+
73+
if let Value::Object(data) = data {
74+
// `bytesTotal` should be 0 while the download is in progress
75+
// (the `data` property is only set after the download is completed)
76+
if let Some(array) = data.as_bytearray() {
77+
return Ok(array.len().into());
78+
} else {
79+
return Err(format!("Unexpected value for `data` property: {:?}", data).into());
80+
}
81+
} else if let Value::String(data) = data {
82+
return Ok(data.len().into());
83+
}
84+
return Ok(0.into());
85+
}
86+
Ok(Value::Undefined)
87+
}
88+
89+
pub fn load<'gc>(
90+
activation: &mut Activation<'_, 'gc, '_>,
91+
this: Option<Object<'gc>>,
92+
args: &[Value<'gc>],
93+
) -> Result<Value<'gc>, Error> {
94+
if let Some(this) = this {
95+
let request = match args.get(0) {
96+
Some(Value::Object(request)) => request,
97+
// This should never actually happen
98+
_ => return Ok(Value::Undefined),
99+
};
100+
101+
let data_format = this
102+
.get_property(
103+
&QName::new(Namespace::public(), "dataFormat").into(),
104+
activation,
105+
)?
106+
.coerce_to_string(activation)?;
107+
108+
let data_format = if data_format == AvmString::from("binary") {
109+
DataFormat::Binary
110+
} else if data_format == AvmString::from("text") {
111+
DataFormat::Text
112+
} else if data_format == AvmString::from("variables") {
113+
DataFormat::Variables
114+
} else {
115+
return Err(format!("Unknown data format: {}", data_format).into());
116+
};
117+
118+
return spawn_fetch(activation, this, request, data_format);
119+
}
120+
Ok(Value::Undefined)
121+
}
122+
123+
fn spawn_fetch<'gc>(
124+
activation: &mut Activation<'_, 'gc, '_>,
125+
loader_object: Object<'gc>,
126+
url_request: &Object<'gc>,
127+
data_format: DataFormat,
128+
) -> Result<Value<'gc>, Error> {
129+
let url = url_request
130+
.get_property(&QName::new(Namespace::public(), "url").into(), activation)?
131+
.coerce_to_string(activation)?;
132+
133+
let url = url.to_utf8_lossy();
134+
135+
let future = activation.context.load_manager.load_data_into_url_loader(
136+
activation.context.player.clone(),
137+
loader_object,
138+
&url,
139+
// FIXME - get these from the `URLRequest`
140+
RequestOptions::get(),
141+
data_format,
142+
);
143+
activation.context.navigator.spawn_future(future);
144+
Ok(Value::Undefined)
145+
}
146+
147+
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
148+
let class = Class::new(
149+
QName::new(Namespace::package("flash.net"), "URLLoader"),
150+
Some(QName::new(Namespace::package("flash.events"), "EventDispatcher").into()),
151+
Method::from_builtin(instance_init, "<URLLoader instance initializer>", mc),
152+
Method::from_builtin(class_init, "<URLLoader class initializer>", mc),
153+
mc,
154+
);
155+
156+
let mut write = class.write(mc);
157+
158+
const PUBLIC_INSTANCE_PROPERTIES: &[(
159+
&str,
160+
Option<NativeMethodImpl>,
161+
Option<NativeMethodImpl>,
162+
)] = &[
163+
("bytesLoaded", Some(bytes_loaded), None),
164+
("bytesTotal", Some(bytes_total), None),
165+
];
166+
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
167+
168+
const PUBLIC_INSTANCE_SLOTS: &[(&str, &str, &str)] = &[("data", "", "Object")];
169+
write.define_public_slot_instance_traits(PUBLIC_INSTANCE_SLOTS);
170+
171+
const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[("load", load)];
172+
write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS);
173+
174+
class
175+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//! `flash.net.URLLoaderDataFormat` builtin/prototype
2+
3+
use crate::avm2::activation::Activation;
4+
use crate::avm2::class::{Class, ClassAttributes};
5+
use crate::avm2::method::Method;
6+
use crate::avm2::names::{Namespace, QName};
7+
use crate::avm2::object::Object;
8+
use crate::avm2::value::Value;
9+
use crate::avm2::Error;
10+
use gc_arena::{GcCell, MutationContext};
11+
12+
/// Implements `flash.net.URLLoaderDataFormat`'s instance constructor.
13+
pub fn instance_init<'gc>(
14+
_activation: &mut Activation<'_, 'gc, '_>,
15+
_this: Option<Object<'gc>>,
16+
_args: &[Value<'gc>],
17+
) -> Result<Value<'gc>, Error> {
18+
Ok(Value::Undefined)
19+
}
20+
21+
/// Implements `flash.net.URLLoaderDataFormat`'s class constructor.
22+
pub fn class_init<'gc>(
23+
_activation: &mut Activation<'_, 'gc, '_>,
24+
_this: Option<Object<'gc>>,
25+
_args: &[Value<'gc>],
26+
) -> Result<Value<'gc>, Error> {
27+
Ok(Value::Undefined)
28+
}
29+
30+
/// Construct `URLLoaderDataFormat`'s class.
31+
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
32+
let class = Class::new(
33+
QName::new(Namespace::package("flash.net"), "URLLoaderDataFormat"),
34+
Some(QName::new(Namespace::public(), "Object").into()),
35+
Method::from_builtin(
36+
instance_init,
37+
"<URLLoaderDataFormat instance initializer>",
38+
mc,
39+
),
40+
Method::from_builtin(class_init, "<URLLoaderDataFormat class initializer>", mc),
41+
mc,
42+
);
43+
44+
let mut write = class.write(mc);
45+
46+
write.set_attributes(ClassAttributes::SEALED | ClassAttributes::FINAL);
47+
48+
const CONSTANTS: &[(&str, &str)] = &[
49+
("BINARY", "binary"),
50+
("TEXT", "text"),
51+
("VARIABLES", "variables"),
52+
];
53+
54+
write.define_public_constant_string_class_traits(CONSTANTS);
55+
class
56+
}

0 commit comments

Comments
 (0)