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
|
/// This is necessary to support form submission from Flash via a couple of
|
||||||
/// legacy methods, such as the `ActionGetURL2` opcode or `getURL` function.
|
/// legacy methods, such as the `ActionGetURL2` opcode or `getURL` function.
|
||||||
///
|
///
|
||||||
/// WARNING: This does not support user defined virtual properties!
|
/// WARNING: This does not support user defined virtual properties!
|
||||||
pub fn locals_into_form_values(
|
pub fn object_into_form_values(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
object: Object<'gc>,
|
||||||
) -> HashMap<String, String> {
|
) -> HashMap<String, String> {
|
||||||
let mut form_values = HashMap::new();
|
let mut form_values = HashMap::new();
|
||||||
let scope = self.scope_cell();
|
let keys = object.get_keys(self);
|
||||||
let locals = scope.read().locals_cell();
|
|
||||||
let keys = locals.get_keys(self);
|
|
||||||
|
|
||||||
for k in keys {
|
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?
|
//TODO: What happens if an error occurs inside a virtual property?
|
||||||
form_values.insert(
|
form_values.insert(
|
||||||
|
@ -2520,17 +2519,18 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
||||||
form_values
|
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.
|
/// form data in the request body or URL.
|
||||||
pub fn locals_into_request_options<'b>(
|
pub fn object_into_request_options<'b>(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
object: Object<'gc>,
|
||||||
url: Cow<'b, str>,
|
url: Cow<'b, str>,
|
||||||
method: Option<NavigationMethod>,
|
method: Option<NavigationMethod>,
|
||||||
) -> (Cow<'b, str>, RequestOptions) {
|
) -> (Cow<'b, str>, RequestOptions) {
|
||||||
match method {
|
match method {
|
||||||
Some(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())
|
let qstring = form_urlencoded::Serializer::new(String::new())
|
||||||
.extend_pairs(vars.iter())
|
.extend_pairs(vars.iter())
|
||||||
.finish();
|
.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.
|
/// 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
|
/// 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;
|
pub(crate) mod error;
|
||||||
mod function;
|
mod function;
|
||||||
mod key;
|
mod key;
|
||||||
|
mod load_vars;
|
||||||
mod math;
|
mod math;
|
||||||
mod matrix;
|
mod matrix;
|
||||||
pub(crate) mod mouse;
|
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 number_proto: Object<'gc> = number::create_proto(gc_context, object_proto, function_proto);
|
||||||
let boolean_proto: Object<'gc> =
|
let boolean_proto: Object<'gc> =
|
||||||
boolean::create_proto(gc_context, object_proto, function_proto);
|
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 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 point_proto: Object<'gc> = point::create_proto(gc_context, object_proto, function_proto);
|
||||||
let rectangle_proto: Object<'gc> =
|
let rectangle_proto: Object<'gc> =
|
||||||
|
@ -288,6 +291,12 @@ pub fn create_globals<'gc>(
|
||||||
Some(function_proto),
|
Some(function_proto),
|
||||||
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(
|
let movie_clip = FunctionObject::function(
|
||||||
gc_context,
|
gc_context,
|
||||||
Executable::Native(movie_clip::constructor),
|
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, "Error", error.into(), EnumSet::empty());
|
||||||
globals.define_value(gc_context, "Object", object.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, "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, "MovieClip", movie_clip.into(), EnumSet::empty());
|
||||||
globals.define_value(
|
globals.define_value(
|
||||||
gc_context,
|
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::activation::{Activation, ActivationIdentifier};
|
||||||
use crate::avm1::{AvmString, Object, TObject, Value};
|
use crate::avm1::{AvmString, Object, TObject, Value};
|
||||||
use crate::backend::navigator::OwnedFuture;
|
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::display_object::{DisplayObject, MorphShape, TDisplayObject};
|
||||||
use crate::player::{Player, NEWEST_PLAYER_VERSION};
|
use crate::player::{Player, NEWEST_PLAYER_VERSION};
|
||||||
use crate::tag_utils::SwfMovie;
|
use crate::tag_utils::SwfMovie;
|
||||||
|
@ -28,6 +28,9 @@ pub enum Error {
|
||||||
#[error("Non-form loader spawned as form loader")]
|
#[error("Non-form loader spawned as form loader")]
|
||||||
NotFormLoader,
|
NotFormLoader,
|
||||||
|
|
||||||
|
#[error("Non-load vars loader spawned as load vars loader")]
|
||||||
|
NotLoadVarsLoader,
|
||||||
|
|
||||||
#[error("Non-XML loader spawned as XML loader")]
|
#[error("Non-XML loader spawned as XML loader")]
|
||||||
NotXmlLoader,
|
NotXmlLoader,
|
||||||
|
|
||||||
|
@ -46,6 +49,16 @@ pub enum Error {
|
||||||
Avm1Error(String),
|
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 {
|
impl From<crate::avm1::error::Error<'_>> for Error {
|
||||||
fn from(error: crate::avm1::error::Error<'_>) -> Self {
|
fn from(error: crate::avm1::error::Error<'_>) -> Self {
|
||||||
Error::Avm1Error(error.to_string())
|
Error::Avm1Error(error.to_string())
|
||||||
|
@ -163,6 +176,27 @@ impl<'gc> LoadManager<'gc> {
|
||||||
loader.form_loader(player, fetch)
|
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.
|
/// Kick off an XML data load into an XML node.
|
||||||
///
|
///
|
||||||
/// Returns the loader's async process, which you will need to spawn.
|
/// Returns the loader's async process, which you will need to spawn.
|
||||||
|
@ -227,6 +261,15 @@ pub enum Loader<'gc> {
|
||||||
target_object: Object<'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.
|
/// Loader that is loading XML data into an XML tree.
|
||||||
XML {
|
XML {
|
||||||
/// The handle to refer to this loader instance.
|
/// The handle to refer to this loader instance.
|
||||||
|
@ -257,6 +300,7 @@ unsafe impl<'gc> Collect for Loader<'gc> {
|
||||||
target_broadcaster.trace(cc);
|
target_broadcaster.trace(cc);
|
||||||
}
|
}
|
||||||
Loader::Form { target_object, .. } => target_object.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),
|
Loader::XML { target_node, .. } => target_node.trace(cc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,6 +315,7 @@ impl<'gc> Loader<'gc> {
|
||||||
match self {
|
match self {
|
||||||
Loader::Movie { self_handle, .. } => *self_handle = Some(handle),
|
Loader::Movie { self_handle, .. } => *self_handle = Some(handle),
|
||||||
Loader::Form { 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),
|
Loader::XML { self_handle, .. } => *self_handle = Some(handle),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,12 +512,13 @@ impl<'gc> Loader<'gc> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let data = fetch.await?;
|
let data = fetch.await?;
|
||||||
|
|
||||||
|
// Fire the load handler.
|
||||||
player.lock().unwrap().update(|avm1, _avm2, uc| {
|
player.lock().unwrap().update(|avm1, _avm2, uc| {
|
||||||
let loader = uc.load_manager.get_loader(handle);
|
let loader = uc.load_manager.get_loader(handle);
|
||||||
let that = match loader {
|
let that = match loader {
|
||||||
Some(Loader::Form { target_object, .. }) => *target_object,
|
Some(&Loader::Form { target_object, .. }) => target_object,
|
||||||
None => return Err(Error::Cancelled),
|
None => return Err(Error::Cancelled),
|
||||||
_ => return Err(Error::NotMovieLoader),
|
_ => return Err(Error::NotFormLoader),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut activation = Activation::from_nothing(
|
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.
|
/// Event handler morally equivalent to `onLoad` on a movie clip.
|
||||||
///
|
///
|
||||||
/// Returns `true` if the loader has completed and should be removed.
|
/// Returns `true` if the loader has completed and should be removed.
|
||||||
|
|
Loading…
Reference in New Issue