avm1: Implement LoadVars
This commit is contained in:
parent
74cb8609c1
commit
1709e76409
|
@ -2488,23 +2488,22 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert the current locals pool into a set of form values.
|
||||
/// Convert the enumerable properties of an object into a set of form values.
|
||||
///
|
||||
/// This is necessary to support form submission from Flash via a couple of
|
||||
/// legacy methods, such as the `ActionGetURL2` opcode or `getURL` function.
|
||||
///
|
||||
/// WARNING: This does not support user defined virtual properties!
|
||||
pub fn locals_into_form_values(
|
||||
pub fn object_into_form_values(
|
||||
&mut self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
object: Object<'gc>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut form_values = HashMap::new();
|
||||
let scope = self.scope_cell();
|
||||
let locals = scope.read().locals_cell();
|
||||
let keys = locals.get_keys(self);
|
||||
let keys = object.get_keys(self);
|
||||
|
||||
for k in keys {
|
||||
let v = locals.get(&k, self, context);
|
||||
let v = object.get(&k, self, context);
|
||||
|
||||
//TODO: What happens if an error occurs inside a virtual property?
|
||||
form_values.insert(
|
||||
|
@ -2520,17 +2519,18 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
|||
form_values
|
||||
}
|
||||
|
||||
/// Construct request options for a fetch operation that may send locals as
|
||||
/// Construct request options for a fetch operation that may sends object properties as
|
||||
/// form data in the request body or URL.
|
||||
pub fn locals_into_request_options<'b>(
|
||||
pub fn object_into_request_options<'b>(
|
||||
&mut self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
object: Object<'gc>,
|
||||
url: Cow<'b, str>,
|
||||
method: Option<NavigationMethod>,
|
||||
) -> (Cow<'b, str>, RequestOptions) {
|
||||
match method {
|
||||
Some(method) => {
|
||||
let vars = self.locals_into_form_values(context);
|
||||
let vars = self.object_into_form_values(context, object);
|
||||
let qstring = form_urlencoded::Serializer::new(String::new())
|
||||
.extend_pairs(vars.iter())
|
||||
.finish();
|
||||
|
@ -2557,6 +2557,34 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert the current locals pool into a set of form values.
|
||||
///
|
||||
/// This is necessary to support form submission from Flash via a couple of
|
||||
/// legacy methods, such as the `ActionGetURL2` opcode or `getURL` function.
|
||||
///
|
||||
/// WARNING: This does not support user defined virtual properties!
|
||||
pub fn locals_into_form_values(
|
||||
&mut self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> HashMap<String, String> {
|
||||
let scope = self.scope_cell();
|
||||
let locals = scope.read().locals_cell();
|
||||
self.object_into_form_values(context, locals)
|
||||
}
|
||||
|
||||
/// Construct request options for a fetch operation that may send locals as
|
||||
/// form data in the request body or URL.
|
||||
pub fn locals_into_request_options<'b>(
|
||||
&mut self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
url: Cow<'b, str>,
|
||||
method: Option<NavigationMethod>,
|
||||
) -> (Cow<'b, str>, RequestOptions) {
|
||||
let scope = self.scope_cell();
|
||||
let locals = scope.read().locals_cell();
|
||||
self.object_into_request_options(context, locals, url, method)
|
||||
}
|
||||
|
||||
/// Resolves a target value to a display object, relative to a starting display object.
|
||||
///
|
||||
/// This is used by any action/function with a parameter that can be either
|
||||
|
|
|
@ -20,6 +20,7 @@ pub(crate) mod display_object;
|
|||
pub(crate) mod error;
|
||||
mod function;
|
||||
mod key;
|
||||
mod load_vars;
|
||||
mod math;
|
||||
mod matrix;
|
||||
pub(crate) mod mouse;
|
||||
|
@ -250,6 +251,8 @@ pub fn create_globals<'gc>(
|
|||
let number_proto: Object<'gc> = number::create_proto(gc_context, object_proto, function_proto);
|
||||
let boolean_proto: Object<'gc> =
|
||||
boolean::create_proto(gc_context, object_proto, function_proto);
|
||||
let load_vars_proto: Object<'gc> =
|
||||
load_vars::create_proto(gc_context, object_proto, function_proto);
|
||||
let matrix_proto: Object<'gc> = matrix::create_proto(gc_context, object_proto, function_proto);
|
||||
let point_proto: Object<'gc> = point::create_proto(gc_context, object_proto, function_proto);
|
||||
let rectangle_proto: Object<'gc> =
|
||||
|
@ -288,6 +291,12 @@ pub fn create_globals<'gc>(
|
|||
Some(function_proto),
|
||||
Some(function_proto),
|
||||
);
|
||||
let load_vars = FunctionObject::function(
|
||||
gc_context,
|
||||
Executable::Native(load_vars::constructor),
|
||||
Some(function_proto),
|
||||
Some(load_vars_proto),
|
||||
);
|
||||
let movie_clip = FunctionObject::function(
|
||||
gc_context,
|
||||
Executable::Native(movie_clip::constructor),
|
||||
|
@ -371,6 +380,7 @@ pub fn create_globals<'gc>(
|
|||
globals.define_value(gc_context, "Error", error.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "Object", object.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "Function", function.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "LoadVars", load_vars.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "MovieClip", movie_clip.into(), EnumSet::empty());
|
||||
globals.define_value(
|
||||
gc_context,
|
||||
|
|
|
@ -0,0 +1,383 @@
|
|||
//! AVM1 LoadVars object
|
||||
//! TODO: bytesLoaded, bytesTotal, contentType, addRequestHeader
|
||||
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::error::Error;
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, UpdateContext, Value};
|
||||
use crate::backend::navigator::{NavigationMethod, RequestOptions};
|
||||
use gc_arena::MutationContext;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Implements `LoadVars`
|
||||
pub fn constructor<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc>,
|
||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// No-op constructor
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn create_proto<'gc>(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proto: Object<'gc>,
|
||||
fn_proto: Object<'gc>,
|
||||
) -> Object<'gc> {
|
||||
use Attribute::*;
|
||||
|
||||
let mut object = ScriptObject::object(gc_context, Some(proto));
|
||||
|
||||
object.force_set_function(
|
||||
"load",
|
||||
load,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"send",
|
||||
send,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"sendAndLoad",
|
||||
send_and_load,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"decode",
|
||||
decode,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"getBytesLoaded",
|
||||
get_bytes_loaded,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"getBytesTotal",
|
||||
get_bytes_total,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"toString",
|
||||
to_string,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.define_value(
|
||||
gc_context,
|
||||
"contentType",
|
||||
"application/x-www-form-url-encoded".into(),
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"onLoad",
|
||||
on_load,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"onData",
|
||||
on_data,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.force_set_function(
|
||||
"addRequestHeader",
|
||||
add_request_header,
|
||||
gc_context,
|
||||
DontDelete | DontEnum | ReadOnly,
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
object.into()
|
||||
}
|
||||
|
||||
fn add_request_header<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc>,
|
||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
log::warn!("LoadVars.addRequestHeader: Unimplemented");
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
fn decode<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// Spec says added in SWF 7, but not version gated.
|
||||
// Decode the query string into properties on this object.
|
||||
if let Some(data) = args.get(0) {
|
||||
let data = data.coerce_to_string(activation, context)?;
|
||||
for (k, v) in url::form_urlencoded::parse(data.as_bytes()) {
|
||||
this.set(
|
||||
&k,
|
||||
crate::avm1::AvmString::new(context.gc_context, v.into_owned()).into(),
|
||||
activation,
|
||||
context,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
fn get_bytes_loaded<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// Forwards to undocumented property on the object.
|
||||
this.get("_bytesLoaded", activation, context)
|
||||
}
|
||||
|
||||
fn get_bytes_total<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// Forwards to undocumented property on the object.
|
||||
this.get("_bytesTotal", activation, context)
|
||||
}
|
||||
|
||||
fn load<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let url = match args.get(0) {
|
||||
Some(val) => val.coerce_to_string(activation, context)?,
|
||||
None => return Ok(false.into()),
|
||||
};
|
||||
|
||||
spawn_load_var_fetch(activation, context, this, &url, None)?;
|
||||
|
||||
Ok(true.into())
|
||||
}
|
||||
|
||||
fn on_data<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// Default implementation forwards to decode and onLoad.
|
||||
let success = match args.get(0) {
|
||||
None | Some(Value::Undefined) | Some(Value::Null) => false,
|
||||
Some(val) => {
|
||||
this.call_method(&"decode", &[val.clone()], activation, context)?;
|
||||
this.set("loaded", true.into(), activation, context)?;
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
this.call_method(&"onLoad", &[success.into()], activation, context)?;
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
fn on_load<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc>,
|
||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// Default implementation: no-op?
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
fn send<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// `send` navigates the browser to a URL with the given query parameter.
|
||||
let url = match args.get(0) {
|
||||
Some(url) => url.coerce_to_string(activation, context)?,
|
||||
None => return Ok(false.into()),
|
||||
};
|
||||
|
||||
let window = match args.get(1) {
|
||||
Some(v) => Some(v.coerce_to_string(activation, context)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let method_name = args
|
||||
.get(1)
|
||||
.unwrap_or(&Value::Undefined)
|
||||
.coerce_to_string(activation, context)?;
|
||||
let method = NavigationMethod::from_method_str(&method_name).unwrap_or(NavigationMethod::POST);
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut form_values = HashMap::new();
|
||||
let keys = this.get_keys(activation);
|
||||
|
||||
for k in keys {
|
||||
let v = this.get(&k, activation, context);
|
||||
|
||||
form_values.insert(
|
||||
k,
|
||||
v.ok()
|
||||
.unwrap_or_else(|| Value::Undefined)
|
||||
.coerce_to_string(activation, context)
|
||||
.unwrap_or_else(|_| "undefined".into())
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(window) = window {
|
||||
context.navigator.navigate_to_url(
|
||||
url.to_string(),
|
||||
Some(window.to_string()),
|
||||
Some((method, form_values)),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(true.into())
|
||||
}
|
||||
|
||||
fn send_and_load<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let url_val = args.get(0).cloned().unwrap_or(Value::Undefined);
|
||||
let url = url_val.coerce_to_string(activation, context)?;
|
||||
let target = match args.get(1) {
|
||||
Some(&Value::Object(o)) => o,
|
||||
_ => return Ok(false.into()),
|
||||
};
|
||||
|
||||
let method_name = args
|
||||
.get(2)
|
||||
.unwrap_or(&Value::Undefined)
|
||||
.coerce_to_string(activation, context)?;
|
||||
let method = NavigationMethod::from_method_str(&method_name).unwrap_or(NavigationMethod::POST);
|
||||
|
||||
spawn_load_var_fetch(activation, context, target, &url, Some((this, method)))?;
|
||||
|
||||
Ok(true.into())
|
||||
}
|
||||
|
||||
fn to_string<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut form_values = HashMap::new();
|
||||
let keys = this.get_keys(activation);
|
||||
|
||||
for k in keys {
|
||||
let v = this.get(&k, activation, context);
|
||||
|
||||
//TODO: What happens if an error occurs inside a virtual property?
|
||||
form_values.insert(
|
||||
k,
|
||||
v.ok()
|
||||
.unwrap_or_else(|| Value::Undefined)
|
||||
.coerce_to_string(activation, context)
|
||||
.unwrap_or_else(|_| "undefined".into())
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let query_string = url::form_urlencoded::Serializer::new(String::new())
|
||||
.extend_pairs(form_values.iter())
|
||||
.finish();
|
||||
|
||||
Ok(crate::avm1::AvmString::new(context.gc_context, query_string).into())
|
||||
}
|
||||
|
||||
fn spawn_load_var_fetch<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
loader_object: Object<'gc>,
|
||||
url: &AvmString,
|
||||
send_object: Option<(Object<'gc>, NavigationMethod)>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let (url, request_options) = if let Some((send_object, method)) = send_object {
|
||||
// Send properties from `send_object`.
|
||||
activation.object_into_request_options(
|
||||
context,
|
||||
send_object,
|
||||
Cow::Borrowed(&url),
|
||||
Some(method),
|
||||
)
|
||||
} else {
|
||||
// Not sending any parameters.
|
||||
(Cow::Borrowed(url.as_str()), RequestOptions::get())
|
||||
};
|
||||
|
||||
let fetch = context.navigator.fetch(&url, request_options);
|
||||
let process = context.load_manager.load_form_into_load_vars(
|
||||
context.player.clone().unwrap(),
|
||||
loader_object,
|
||||
fetch,
|
||||
);
|
||||
|
||||
// Create hidden properties on object.
|
||||
if !loader_object.has_property(activation, context, "_bytesLoaded") {
|
||||
loader_object.define_value(
|
||||
context.gc_context,
|
||||
"_bytesLoaded",
|
||||
0.into(),
|
||||
Attribute::DontDelete | Attribute::DontEnum,
|
||||
);
|
||||
} else {
|
||||
loader_object.set("_bytesLoaded", 0.into(), activation, context)?;
|
||||
}
|
||||
|
||||
if !loader_object.has_property(activation, context, "loaded") {
|
||||
loader_object.define_value(
|
||||
context.gc_context,
|
||||
"loaded",
|
||||
false.into(),
|
||||
Attribute::DontDelete | Attribute::DontEnum,
|
||||
);
|
||||
} else {
|
||||
loader_object.set("loaded", false.into(), activation, context)?;
|
||||
}
|
||||
|
||||
context.navigator.spawn_future(process);
|
||||
|
||||
Ok(true.into())
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
use crate::avm1::activation::{Activation, ActivationIdentifier};
|
||||
use crate::avm1::{AvmString, Object, TObject, Value};
|
||||
use crate::backend::navigator::OwnedFuture;
|
||||
use crate::context::{ActionQueue, ActionType};
|
||||
use crate::context::{ActionQueue, ActionType, UpdateContext};
|
||||
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
|
||||
use crate::player::{Player, NEWEST_PLAYER_VERSION};
|
||||
use crate::tag_utils::SwfMovie;
|
||||
|
@ -28,6 +28,9 @@ pub enum Error {
|
|||
#[error("Non-form loader spawned as form loader")]
|
||||
NotFormLoader,
|
||||
|
||||
#[error("Non-load vars loader spawned as load vars loader")]
|
||||
NotLoadVarsLoader,
|
||||
|
||||
#[error("Non-XML loader spawned as XML loader")]
|
||||
NotXmlLoader,
|
||||
|
||||
|
@ -46,6 +49,16 @@ pub enum Error {
|
|||
Avm1Error(String),
|
||||
}
|
||||
|
||||
pub type FormLoadHandler<'gc> = fn(
|
||||
&mut Activation<'_, 'gc>,
|
||||
&mut UpdateContext<'_, 'gc, '_>,
|
||||
Object<'gc>,
|
||||
data: &[u8],
|
||||
) -> Result<(), Error>;
|
||||
|
||||
pub type FormErrorHandler<'gc> =
|
||||
fn(&mut Activation<'_, 'gc>, &mut UpdateContext<'_, 'gc, '_>, Object<'gc>) -> Result<(), Error>;
|
||||
|
||||
impl From<crate::avm1::error::Error<'_>> for Error {
|
||||
fn from(error: crate::avm1::error::Error<'_>) -> Self {
|
||||
Error::Avm1Error(error.to_string())
|
||||
|
@ -163,6 +176,27 @@ impl<'gc> LoadManager<'gc> {
|
|||
loader.form_loader(player, fetch)
|
||||
}
|
||||
|
||||
/// Kick off a form data load into an AVM1 object.
|
||||
///
|
||||
/// Returns the loader's async process, which you will need to spawn.
|
||||
pub fn load_form_into_load_vars(
|
||||
&mut self,
|
||||
player: Weak<Mutex<Player>>,
|
||||
target_object: Object<'gc>,
|
||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||
) -> OwnedFuture<(), Error> {
|
||||
let loader = Loader::LoadVars {
|
||||
self_handle: None,
|
||||
target_object,
|
||||
};
|
||||
let handle = self.add_loader(loader);
|
||||
|
||||
let loader = self.get_loader_mut(handle).unwrap();
|
||||
loader.introduce_loader_handle(handle);
|
||||
|
||||
loader.load_vars_loader(player, fetch)
|
||||
}
|
||||
|
||||
/// Kick off an XML data load into an XML node.
|
||||
///
|
||||
/// Returns the loader's async process, which you will need to spawn.
|
||||
|
@ -227,6 +261,15 @@ pub enum Loader<'gc> {
|
|||
target_object: Object<'gc>,
|
||||
},
|
||||
|
||||
/// Loader that is loading form data into an AVM1 LoadVars object.
|
||||
LoadVars {
|
||||
/// The handle to refer to this loader instance.
|
||||
self_handle: Option<Handle>,
|
||||
|
||||
/// The target AVM1 object to load form data into.
|
||||
target_object: Object<'gc>,
|
||||
},
|
||||
|
||||
/// Loader that is loading XML data into an XML tree.
|
||||
XML {
|
||||
/// The handle to refer to this loader instance.
|
||||
|
@ -257,6 +300,7 @@ unsafe impl<'gc> Collect for Loader<'gc> {
|
|||
target_broadcaster.trace(cc);
|
||||
}
|
||||
Loader::Form { target_object, .. } => target_object.trace(cc),
|
||||
Loader::LoadVars { target_object, .. } => target_object.trace(cc),
|
||||
Loader::XML { target_node, .. } => target_node.trace(cc),
|
||||
}
|
||||
}
|
||||
|
@ -271,6 +315,7 @@ impl<'gc> Loader<'gc> {
|
|||
match self {
|
||||
Loader::Movie { self_handle, .. } => *self_handle = Some(handle),
|
||||
Loader::Form { self_handle, .. } => *self_handle = Some(handle),
|
||||
Loader::LoadVars { self_handle, .. } => *self_handle = Some(handle),
|
||||
Loader::XML { self_handle, .. } => *self_handle = Some(handle),
|
||||
}
|
||||
}
|
||||
|
@ -467,12 +512,13 @@ impl<'gc> Loader<'gc> {
|
|||
Box::pin(async move {
|
||||
let data = fetch.await?;
|
||||
|
||||
// Fire the load handler.
|
||||
player.lock().unwrap().update(|avm1, _avm2, uc| {
|
||||
let loader = uc.load_manager.get_loader(handle);
|
||||
let that = match loader {
|
||||
Some(Loader::Form { target_object, .. }) => *target_object,
|
||||
Some(&Loader::Form { target_object, .. }) => target_object,
|
||||
None => return Err(Error::Cancelled),
|
||||
_ => return Err(Error::NotMovieLoader),
|
||||
_ => return Err(Error::NotFormLoader),
|
||||
};
|
||||
|
||||
let mut activation = Activation::from_nothing(
|
||||
|
@ -498,6 +544,70 @@ impl<'gc> Loader<'gc> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Creates a future for a LoadVars load call.
|
||||
pub fn load_vars_loader(
|
||||
&mut self,
|
||||
player: Weak<Mutex<Player>>,
|
||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||
) -> OwnedFuture<(), Error> {
|
||||
let handle = match self {
|
||||
Loader::LoadVars { self_handle, .. } => {
|
||||
self_handle.expect("Loader not self-introduced")
|
||||
}
|
||||
_ => return Box::pin(async { Err(Error::NotLoadVarsLoader) }),
|
||||
};
|
||||
|
||||
let player = player
|
||||
.upgrade()
|
||||
.expect("Could not upgrade weak reference to player");
|
||||
|
||||
Box::pin(async move {
|
||||
let data = fetch.await;
|
||||
|
||||
// Fire the load handler.
|
||||
player.lock().unwrap().update(|avm1, _avm2, uc| {
|
||||
let loader = uc.load_manager.get_loader(handle);
|
||||
let that = match loader {
|
||||
Some(&Loader::LoadVars { target_object, .. }) => target_object,
|
||||
None => return Err(Error::Cancelled),
|
||||
_ => return Err(Error::NotLoadVarsLoader),
|
||||
};
|
||||
|
||||
let mut activation = Activation::from_nothing(
|
||||
avm1,
|
||||
ActivationIdentifier::root("[Form Loader]"),
|
||||
uc.swf.version(),
|
||||
avm1.global_object_cell(),
|
||||
uc.gc_context,
|
||||
*uc.levels.get(&0).unwrap(),
|
||||
);
|
||||
|
||||
match data {
|
||||
Ok(data) => {
|
||||
// Fire the onData method with the loaded string.
|
||||
let string_data =
|
||||
AvmString::new(uc.gc_context, String::from_utf8_lossy(&data));
|
||||
let _ =
|
||||
that.call_method("onData", &[string_data.into()], &mut activation, uc);
|
||||
}
|
||||
Err(_) => {
|
||||
// TODO: Log "Error opening URL" trace similar to the Flash Player?
|
||||
// Simulate 404 HTTP status. This should probably be fired elsewhere
|
||||
// because a failed local load doesn't fire a 404.
|
||||
let _ =
|
||||
that.call_method("onHTTPStatus", &[404.into()], &mut activation, uc);
|
||||
|
||||
// Fire the onData method with no data to indicate an unsuccessful load.
|
||||
let _ =
|
||||
that.call_method("onData", &[Value::Undefined], &mut activation, uc);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Event handler morally equivalent to `onLoad` on a movie clip.
|
||||
///
|
||||
/// Returns `true` if the loader has completed and should be removed.
|
||||
|
|
Loading…
Reference in New Issue