core: Implement LocalConnection
This commit is contained in:
parent
0a2528b4f9
commit
4d12e0e5b4
|
@ -36,7 +36,7 @@ pub(crate) mod glow_filter;
|
|||
pub(crate) mod gradient_filter;
|
||||
mod key;
|
||||
mod load_vars;
|
||||
mod local_connection;
|
||||
pub(crate) mod local_connection;
|
||||
mod math;
|
||||
mod matrix;
|
||||
pub(crate) mod mouse;
|
||||
|
|
|
@ -2,14 +2,139 @@
|
|||
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::error::Error;
|
||||
use crate::avm1::globals::shared_object::{deserialize_value, serialize};
|
||||
use crate::avm1::object::TObject;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{Object, ScriptObject, Value};
|
||||
use crate::context::GcContext;
|
||||
use crate::avm1::{
|
||||
ActivationIdentifier, ExecutionReason, NativeObject, Object, ScriptObject, Value,
|
||||
};
|
||||
use crate::context::{GcContext, UpdateContext};
|
||||
use crate::display_object::TDisplayObject;
|
||||
use crate::local_connection::{LocalConnectionHandle, LocalConnections};
|
||||
use crate::string::AvmString;
|
||||
use flash_lso::types::Value as AmfValue;
|
||||
use gc_arena::{Collect, Gc};
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Debug, Collect)]
|
||||
#[collect(require_static)]
|
||||
struct LocalConnectionData {
|
||||
handle: RefCell<Option<LocalConnectionHandle>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct LocalConnection<'gc>(Gc<'gc, LocalConnectionData>);
|
||||
|
||||
impl<'gc> LocalConnection<'gc> {
|
||||
pub fn cast(value: Value<'gc>) -> Option<Self> {
|
||||
if let Value::Object(object) = value {
|
||||
if let NativeObject::LocalConnection(local_connection) = object.native() {
|
||||
return Some(local_connection);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.0.handle.borrow().is_some()
|
||||
}
|
||||
|
||||
pub fn connect(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
name: AvmString<'gc>,
|
||||
this: Object<'gc>,
|
||||
) -> bool {
|
||||
if self.is_connected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let connection_handle = activation.context.local_connections.connect(
|
||||
&LocalConnections::get_domain(activation.context.swf.url()),
|
||||
this,
|
||||
&name,
|
||||
);
|
||||
let result = connection_handle.is_some();
|
||||
*self.0.handle.borrow_mut() = connection_handle;
|
||||
result
|
||||
}
|
||||
|
||||
pub fn disconnect(&self, activation: &mut Activation<'_, 'gc>) {
|
||||
if let Some(conn_handle) = self.0.handle.take() {
|
||||
activation.context.local_connections.close(conn_handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_status(
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
status: &'static str,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
let Some(root_clip) = context.stage.root_clip() else {
|
||||
tracing::warn!("Ignored LocalConnection callback as there's no root movie");
|
||||
return Ok(());
|
||||
};
|
||||
let mut activation = Activation::from_nothing(
|
||||
context.reborrow(),
|
||||
ActivationIdentifier::root("[LocalConnection onStatus]"),
|
||||
root_clip,
|
||||
);
|
||||
let constructor = activation.context.avm1.prototypes().object_constructor;
|
||||
let event = constructor
|
||||
.construct(&mut activation, &[])?
|
||||
.coerce_to_object(&mut activation);
|
||||
event.set("level", status.into(), &mut activation)?;
|
||||
this.call_method(
|
||||
"onStatus".into(),
|
||||
&[event.into()],
|
||||
&mut activation,
|
||||
ExecutionReason::Special,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_method(
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
method_name: AvmString<'gc>,
|
||||
amf_arguments: Vec<AmfValue>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
let Some(root_clip) = context.stage.root_clip() else {
|
||||
tracing::warn!("Ignored LocalConnection callback as there's no root movie");
|
||||
return Ok(());
|
||||
};
|
||||
let mut activation = Activation::from_nothing(
|
||||
context.reborrow(),
|
||||
ActivationIdentifier::root("[LocalConnection call]"),
|
||||
root_clip,
|
||||
);
|
||||
let mut args = Vec::with_capacity(amf_arguments.len());
|
||||
for arg in amf_arguments {
|
||||
let reader = flash_lso::read::Reader::default();
|
||||
let value = deserialize_value(
|
||||
&mut activation,
|
||||
&arg,
|
||||
&reader.amf0_decoder,
|
||||
&mut Default::default(),
|
||||
);
|
||||
args.push(value);
|
||||
}
|
||||
this.call_method(
|
||||
method_name,
|
||||
&args,
|
||||
&mut activation,
|
||||
ExecutionReason::Special,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||
"domain" => method(domain; DONT_DELETE | READ_ONLY);
|
||||
"domain" => method(domain; DONT_DELETE | DONT_ENUM);
|
||||
"connect" => method(connect; DONT_DELETE | DONT_ENUM);
|
||||
"close" => method(close; DONT_DELETE | DONT_ENUM);
|
||||
"send" => method(send; DONT_DELETE | DONT_ENUM);
|
||||
};
|
||||
|
||||
pub fn domain<'gc>(
|
||||
|
@ -18,29 +143,104 @@ pub fn domain<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let movie = activation.base_clip().movie();
|
||||
let domain = LocalConnections::get_domain(movie.url());
|
||||
|
||||
let domain = if let Ok(url) = url::Url::parse(movie.url()) {
|
||||
if url.scheme() == "file" {
|
||||
"localhost".into()
|
||||
} else if let Some(domain) = url.domain() {
|
||||
AvmString::new_utf8(activation.context.gc_context, domain)
|
||||
} else {
|
||||
// no domain?
|
||||
"localhost".into()
|
||||
}
|
||||
} else {
|
||||
tracing::error!("LocalConnection::domain: Unable to parse movie URL");
|
||||
return Ok(Value::Null);
|
||||
};
|
||||
|
||||
Ok(Value::String(domain))
|
||||
Ok(Value::String(AvmString::new_utf8(
|
||||
activation.context.gc_context,
|
||||
domain,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn constructor<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc>,
|
||||
pub fn connect<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let Some(Value::String(connection_name)) = args.get(0) else {
|
||||
// This is deliberately not a coercion, Flash tests the type
|
||||
return Ok(false.into());
|
||||
};
|
||||
if connection_name.is_empty() || connection_name.contains(b':') {
|
||||
return Ok(false.into());
|
||||
}
|
||||
|
||||
if let Some(local_connection) = LocalConnection::cast(this.into()) {
|
||||
return Ok(local_connection
|
||||
.connect(activation, *connection_name, this)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn send<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let Some(Value::String(connection_name)) = args.get(0) else {
|
||||
// This is deliberately not a coercion, Flash tests the type
|
||||
return Ok(false.into());
|
||||
};
|
||||
let Some(Value::String(method_name)) = args.get(1) else {
|
||||
// This is deliberately not a coercion, Flash tests the type
|
||||
return Ok(false.into());
|
||||
};
|
||||
|
||||
if connection_name.is_empty() || method_name.is_empty() {
|
||||
return Ok(false.into());
|
||||
}
|
||||
|
||||
if method_name == b"send"
|
||||
|| method_name == b"connect"
|
||||
|| method_name == b"close"
|
||||
|| method_name == b"allowDomain"
|
||||
|| method_name == b"allowInsecureDomain"
|
||||
|| method_name == b"domain"
|
||||
{
|
||||
return Ok(false.into());
|
||||
}
|
||||
|
||||
let mut amf_arguments = Vec::with_capacity(args.len() - 2);
|
||||
for arg in &args[2..] {
|
||||
amf_arguments.push(serialize(activation, *arg));
|
||||
}
|
||||
|
||||
activation.context.local_connections.send(
|
||||
&LocalConnections::get_domain(activation.context.swf.url()),
|
||||
this,
|
||||
*connection_name,
|
||||
*method_name,
|
||||
amf_arguments,
|
||||
);
|
||||
Ok(true.into())
|
||||
}
|
||||
|
||||
pub fn close<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(local_connection) = LocalConnection::cast(this.into()) {
|
||||
local_connection.disconnect(activation);
|
||||
}
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn constructor<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
this.set_native(
|
||||
activation.gc(),
|
||||
NativeObject::LocalConnection(LocalConnection(Gc::new(
|
||||
activation.gc(),
|
||||
LocalConnectionData {
|
||||
handle: RefCell::new(None),
|
||||
},
|
||||
))),
|
||||
);
|
||||
Ok(this.into())
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::avm1::globals::drop_shadow_filter::DropShadowFilter;
|
|||
use crate::avm1::globals::file_reference::FileReferenceObject;
|
||||
use crate::avm1::globals::glow_filter::GlowFilter;
|
||||
use crate::avm1::globals::gradient_filter::GradientFilter;
|
||||
use crate::avm1::globals::local_connection::LocalConnection;
|
||||
use crate::avm1::globals::netconnection::NetConnection;
|
||||
use crate::avm1::globals::shared_object::SharedObject;
|
||||
use crate::avm1::globals::transform::TransformObject;
|
||||
|
@ -66,6 +67,7 @@ pub enum NativeObject<'gc> {
|
|||
XmlSocket(XmlSocket<'gc>),
|
||||
FileReference(FileReferenceObject<'gc>),
|
||||
NetConnection(NetConnection<'gc>),
|
||||
LocalConnection(LocalConnection<'gc>),
|
||||
}
|
||||
|
||||
/// Represents an object that can be directly interacted with by the AVM
|
||||
|
|
|
@ -55,22 +55,27 @@ pub fn serialize_value<'gc>(
|
|||
// Don't serialize properties from the vtable (we don't want a 'length' field)
|
||||
recursive_serialize(activation, o, &mut values, None, amf_version, object_table)
|
||||
.unwrap();
|
||||
|
||||
let mut dense = vec![];
|
||||
let mut sparse = vec![];
|
||||
// ActionScript `Array`s can have non-number properties, and these properties
|
||||
// are confirmed and tested to also be serialized, so do not limit the values
|
||||
// iterated over by the length of the internal array data.
|
||||
for (i, elem) in values.into_iter().enumerate() {
|
||||
if elem.name == i.to_string() {
|
||||
dense.push(elem.value.clone());
|
||||
} else {
|
||||
sparse.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
let len = o.as_array_storage().unwrap().length() as u32;
|
||||
Some(AmfValue::ECMAArray(dense, sparse, len))
|
||||
|
||||
if amf_version == AMFVersion::AMF3 {
|
||||
let mut dense = vec![];
|
||||
let mut sparse = vec![];
|
||||
// ActionScript `Array`s can have non-number properties, and these properties
|
||||
// are confirmed and tested to also be serialized, so do not limit the values
|
||||
// iterated over by the length of the internal array data.
|
||||
for (i, elem) in values.into_iter().enumerate() {
|
||||
if elem.name == i.to_string() {
|
||||
dense.push(elem.value.clone());
|
||||
} else {
|
||||
sparse.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
Some(AmfValue::ECMAArray(dense, sparse, len))
|
||||
} else {
|
||||
// TODO: is this right?
|
||||
Some(AmfValue::ECMAArray(vec![], values, len))
|
||||
}
|
||||
} else if let Some(vec) = o.as_vector_storage() {
|
||||
let val_type = vec.value_type();
|
||||
if val_type == Some(activation.avm2().classes().int.inner_class_definition()) {
|
||||
|
|
|
@ -524,6 +524,23 @@ pub fn make_error_2037<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc>
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
pub fn make_error_2085<'gc>(activation: &mut Activation<'_, 'gc>, param_name: &str) -> Error<'gc> {
|
||||
let err = argument_error(
|
||||
activation,
|
||||
&format!(
|
||||
"Error #2085: Parameter {} must be non-empty string.",
|
||||
param_name
|
||||
),
|
||||
2007,
|
||||
);
|
||||
match err {
|
||||
Ok(err) => Error::AvmError(err),
|
||||
Err(err) => err,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
pub fn make_error_2097<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> {
|
||||
|
|
|
@ -165,6 +165,7 @@ pub struct SystemClasses<'gc> {
|
|||
pub netstatusevent: ClassObject<'gc>,
|
||||
pub shaderfilter: ClassObject<'gc>,
|
||||
pub statusevent: ClassObject<'gc>,
|
||||
pub asyncerrorevent: ClassObject<'gc>,
|
||||
pub contextmenuevent: ClassObject<'gc>,
|
||||
pub filereference: ClassObject<'gc>,
|
||||
pub filefilter: ClassObject<'gc>,
|
||||
|
@ -293,6 +294,7 @@ impl<'gc> SystemClasses<'gc> {
|
|||
netstatusevent: object,
|
||||
shaderfilter: object,
|
||||
statusevent: object,
|
||||
asyncerrorevent: object,
|
||||
contextmenuevent: object,
|
||||
filereference: object,
|
||||
filefilter: object,
|
||||
|
@ -804,6 +806,7 @@ fn load_playerglobal<'gc>(
|
|||
("flash.events", "UncaughtErrorEvents", uncaughterrorevents),
|
||||
("flash.events", "NetStatusEvent", netstatusevent),
|
||||
("flash.events", "StatusEvent", statusevent),
|
||||
("flash.events", "AsyncErrorEvent", asyncerrorevent),
|
||||
("flash.events", "ContextMenuEvent", contextmenuevent),
|
||||
("flash.events", "FocusEvent", focusevent),
|
||||
("flash.geom", "Matrix", matrix),
|
||||
|
|
|
@ -25,21 +25,7 @@ package flash.net {
|
|||
|
||||
public native function connect(connectionName:String):void;
|
||||
|
||||
public function send(connectionName: String, methodName: String, ... arguments):void {
|
||||
if (connectionName === null) {
|
||||
throw new TypeError("Error #2007: Parameter connectionName must be non-null.", 2007);
|
||||
}
|
||||
if (methodName === null) {
|
||||
throw new TypeError("Error #2007: Parameter methodName must be non-null.", 2007);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.send_internal(connectionName, methodName, arguments);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private native function send_internal(connectionName: String, methodName: String, args: Array):void;
|
||||
public native function send(connectionName: String, methodName: String, ... arguments):void;
|
||||
|
||||
public function allowDomain(... domains): void {
|
||||
stub_method("flash.net.LocalConnection", "allowDomain");
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use crate::avm2::error::{argument_error, make_error_2007};
|
||||
use crate::avm2::amf::serialize_value;
|
||||
use crate::avm2::error::{argument_error, make_error_2004, make_error_2085, Error2004Type};
|
||||
use crate::avm2::object::TObject;
|
||||
use crate::avm2::parameters::ParametersExt;
|
||||
use crate::avm2::{Activation, Avm2, Error, Object, Value};
|
||||
use crate::avm2::{Activation, Error, Object, Value};
|
||||
use crate::string::AvmString;
|
||||
|
||||
use crate::avm2_stub_method;
|
||||
use flash_lso::types::{AMFVersion, Value as AmfValue};
|
||||
|
||||
pub use crate::avm2::object::local_connection_allocator;
|
||||
use crate::local_connection::LocalConnections;
|
||||
|
||||
/// Implements `domain` getter
|
||||
pub fn get_domain<'gc>(
|
||||
|
@ -15,60 +16,56 @@ pub fn get_domain<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let movie = &activation.context.swf;
|
||||
let domain = LocalConnections::get_domain(movie.url());
|
||||
|
||||
let domain = if let Ok(url) = url::Url::parse(movie.url()) {
|
||||
if url.scheme() == "file" {
|
||||
"localhost".into()
|
||||
} else if let Some(domain) = url.domain() {
|
||||
AvmString::new_utf8(activation.context.gc_context, domain)
|
||||
} else {
|
||||
// no domain?
|
||||
"localhost".into()
|
||||
}
|
||||
} else {
|
||||
tracing::error!("LocalConnection::domain: Unable to parse movie URL");
|
||||
return Ok(Value::Null);
|
||||
};
|
||||
|
||||
Ok(Value::String(domain))
|
||||
Ok(Value::String(AvmString::new_utf8(
|
||||
activation.context.gc_context,
|
||||
domain,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Implements `LocalConnection.send`
|
||||
pub fn send_internal<'gc>(
|
||||
pub fn send<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// Already null-checked by the AS wrapper `LocalConnection.send`
|
||||
let connection_name = args.get_value(0);
|
||||
let connection_name = args.get_string_non_null(activation, 0, "connectionName")?;
|
||||
let method_name = args.get_string_non_null(activation, 1, "methodName")?;
|
||||
|
||||
let connection_name = connection_name.coerce_to_string(activation)?;
|
||||
|
||||
let event_name = if activation
|
||||
.context
|
||||
.local_connections
|
||||
.all_by_name(connection_name)
|
||||
.is_empty()
|
||||
if connection_name.is_empty() {
|
||||
return Err(make_error_2085(activation, "connectionName"));
|
||||
}
|
||||
if method_name.is_empty() {
|
||||
return Err(make_error_2085(activation, "methodName"));
|
||||
}
|
||||
if &method_name == b"send"
|
||||
|| &method_name == b"connect"
|
||||
|| &method_name == b"close"
|
||||
|| &method_name == b"allowDomain"
|
||||
|| &method_name == b"allowInsecureDomain"
|
||||
|| &method_name == b"domain"
|
||||
{
|
||||
"error"
|
||||
} else {
|
||||
avm2_stub_method!(activation, "flash.net.LocalConnection", "send");
|
||||
return Err(make_error_2004(activation, Error2004Type::ArgumentError));
|
||||
}
|
||||
|
||||
"status"
|
||||
};
|
||||
let mut amf_arguments = Vec::with_capacity(args.len() - 2);
|
||||
for arg in &args[2..] {
|
||||
amf_arguments.push(
|
||||
serialize_value(activation, *arg, AMFVersion::AMF0, &mut Default::default())
|
||||
.unwrap_or(AmfValue::Undefined),
|
||||
);
|
||||
}
|
||||
|
||||
let event = activation.avm2().classes().statusevent.construct(
|
||||
activation,
|
||||
&[
|
||||
"status".into(),
|
||||
false.into(),
|
||||
false.into(),
|
||||
Value::Null,
|
||||
event_name.into(),
|
||||
],
|
||||
)?;
|
||||
|
||||
Avm2::dispatch_event(&mut activation.context, event, this);
|
||||
if let Some(local_connection) = this.as_local_connection_object() {
|
||||
activation.context.local_connections.send(
|
||||
&LocalConnections::get_domain(activation.context.swf.url()),
|
||||
(activation.domain(), local_connection),
|
||||
connection_name,
|
||||
method_name,
|
||||
amf_arguments,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
@ -79,22 +76,24 @@ pub fn connect<'gc>(
|
|||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let connection_name = args.get_value(0);
|
||||
if matches!(connection_name, Value::Null) {
|
||||
return Err(make_error_2007(activation, "connectionName"));
|
||||
};
|
||||
let connection_name = args.get_string_non_null(activation, 0, "connectionName")?;
|
||||
if connection_name.is_empty() {
|
||||
return Err(make_error_2085(activation, "connectionName"));
|
||||
}
|
||||
if connection_name.contains(b':') {
|
||||
return Err(make_error_2004(activation, Error2004Type::ArgumentError));
|
||||
}
|
||||
|
||||
if let Some(local_connection) = this.as_local_connection_object() {
|
||||
if local_connection.is_connected() {
|
||||
if !local_connection.connect(activation, connection_name) {
|
||||
// This triggers both if this object is already connected, OR there's something else taking the name
|
||||
// (The error message is misleading, in that case!)
|
||||
return Err(Error::AvmError(argument_error(
|
||||
activation,
|
||||
"Error #2082: Connect failed because the object is already connected.",
|
||||
2082,
|
||||
)?));
|
||||
}
|
||||
|
||||
let connection_name = connection_name.coerce_to_string(activation)?;
|
||||
local_connection.connect(activation, connection_name);
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
use crate::avm2::activation::Activation;
|
||||
use crate::avm2::amf::deserialize_value;
|
||||
use crate::avm2::object::script_object::ScriptObjectData;
|
||||
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Error;
|
||||
use crate::local_connection::{LocalConnection, LocalConnectionHandle};
|
||||
use crate::avm2::{Avm2, Domain, Error};
|
||||
use crate::context::UpdateContext;
|
||||
use crate::local_connection::{LocalConnectionHandle, LocalConnections};
|
||||
use crate::string::AvmString;
|
||||
use core::fmt;
|
||||
use flash_lso::types::Value as AmfValue;
|
||||
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
|
||||
use std::cell::{Ref, RefMut};
|
||||
|
||||
|
@ -42,7 +45,7 @@ impl fmt::Debug for LocalConnectionObject<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Collect)]
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct LocalConnectionObjectData<'gc> {
|
||||
/// Base script object
|
||||
|
@ -57,30 +60,91 @@ impl<'gc> LocalConnectionObject<'gc> {
|
|||
self.0.read().connection_handle.is_some()
|
||||
}
|
||||
|
||||
pub fn connection_handle(&self) -> Option<LocalConnectionHandle> {
|
||||
self.0.read().connection_handle
|
||||
}
|
||||
pub fn connect(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) -> bool {
|
||||
if self.is_connected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn connect(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) {
|
||||
assert!(!self.is_connected());
|
||||
|
||||
let connection_handle = activation
|
||||
.context
|
||||
.local_connections
|
||||
.insert(LocalConnection::new(*self, name));
|
||||
let connection_handle = activation.context.local_connections.connect(
|
||||
&LocalConnections::get_domain(activation.context.swf.url()),
|
||||
(activation.domain(), *self),
|
||||
&name,
|
||||
);
|
||||
let result = connection_handle.is_some();
|
||||
self.0
|
||||
.write(activation.context.gc_context)
|
||||
.connection_handle = Some(connection_handle);
|
||||
.connection_handle = connection_handle;
|
||||
result
|
||||
}
|
||||
|
||||
pub fn disconnect(&self, activation: &mut Activation<'_, 'gc>) {
|
||||
if let Some(conn_handle) = self.0.read().connection_handle {
|
||||
activation.context.local_connections.remove(conn_handle);
|
||||
if let Some(conn_handle) = self
|
||||
.0
|
||||
.write(activation.context.gc_context)
|
||||
.connection_handle
|
||||
.take()
|
||||
{
|
||||
activation.context.local_connections.close(conn_handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_status(&self, context: &mut UpdateContext<'_, 'gc>, status: &'static str) {
|
||||
let mut activation = Activation::from_nothing(context.reborrow());
|
||||
if let Ok(event) = activation.avm2().classes().statusevent.construct(
|
||||
&mut activation,
|
||||
&[
|
||||
"status".into(),
|
||||
false.into(),
|
||||
false.into(),
|
||||
Value::Null,
|
||||
status.into(),
|
||||
],
|
||||
) {
|
||||
Avm2::dispatch_event(&mut activation.context, event, (*self).into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_method(
|
||||
&self,
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
domain: Domain<'gc>,
|
||||
method_name: AvmString<'gc>,
|
||||
amf_arguments: Vec<AmfValue>,
|
||||
) {
|
||||
let mut activation = Activation::from_domain(context.reborrow(), domain);
|
||||
let mut arguments = Vec::with_capacity(amf_arguments.len());
|
||||
|
||||
for argument in amf_arguments {
|
||||
arguments
|
||||
.push(deserialize_value(&mut activation, &argument).unwrap_or(Value::Undefined));
|
||||
}
|
||||
|
||||
self.0
|
||||
.write(activation.context.gc_context)
|
||||
.connection_handle = None;
|
||||
if let Ok(client) = self
|
||||
.get_public_property("client", &mut activation)
|
||||
.and_then(|v| v.coerce_to_object(&mut activation))
|
||||
{
|
||||
if let Err(e) = client.call_public_property(method_name, &arguments, &mut activation) {
|
||||
match e {
|
||||
Error::AvmError(error) => {
|
||||
if let Ok(event) = activation.avm2().classes().asyncerrorevent.construct(
|
||||
&mut activation,
|
||||
&[
|
||||
"asyncError".into(),
|
||||
false.into(),
|
||||
false.into(),
|
||||
error,
|
||||
error,
|
||||
],
|
||||
) {
|
||||
Avm2::dispatch_event(&mut activation.context, event, (*self).into());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
tracing::error!("Unhandled error dispatching AVM2 LocalConnection method call to '{method_name}': {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,55 +1,130 @@
|
|||
use crate::avm1::globals::local_connection::LocalConnection as Avm1LocalConnectionObject;
|
||||
use crate::avm1::Object as Avm1Object;
|
||||
use crate::avm2::object::LocalConnectionObject;
|
||||
use crate::avm2::Domain as Avm2Domain;
|
||||
use crate::context::UpdateContext;
|
||||
use crate::string::AvmString;
|
||||
use flash_lso::types::Value as AmfValue;
|
||||
use fnv::FnvHashMap;
|
||||
use gc_arena::Collect;
|
||||
use slotmap::{new_key_type, SlotMap};
|
||||
use ruffle_wstr::{WStr, WString};
|
||||
use std::borrow::Cow;
|
||||
|
||||
new_key_type! {
|
||||
pub struct LocalConnectionHandle;
|
||||
}
|
||||
|
||||
#[derive(Collect)]
|
||||
#[derive(Clone, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub enum LocalConnectionKind<'gc> {
|
||||
Avm2(LocalConnectionObject<'gc>),
|
||||
Avm2(Avm2Domain<'gc>, LocalConnectionObject<'gc>),
|
||||
Avm1(Avm1Object<'gc>),
|
||||
}
|
||||
|
||||
impl<'gc> From<LocalConnectionObject<'gc>> for LocalConnectionKind<'gc> {
|
||||
fn from(obj: LocalConnectionObject<'gc>) -> Self {
|
||||
Self::Avm2(obj)
|
||||
impl<'gc> From<(Avm2Domain<'gc>, LocalConnectionObject<'gc>)> for LocalConnectionKind<'gc> {
|
||||
fn from(obj: (Avm2Domain<'gc>, LocalConnectionObject<'gc>)) -> Self {
|
||||
Self::Avm2(obj.0, obj.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct LocalConnection<'gc> {
|
||||
object: LocalConnectionKind<'gc>,
|
||||
|
||||
connection_name: AvmString<'gc>,
|
||||
impl<'gc> From<Avm1Object<'gc>> for LocalConnectionKind<'gc> {
|
||||
fn from(obj: Avm1Object<'gc>) -> Self {
|
||||
Self::Avm1(obj)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> LocalConnection<'gc> {
|
||||
pub fn new(
|
||||
object: impl Into<LocalConnectionKind<'gc>>,
|
||||
connection_name: AvmString<'gc>,
|
||||
) -> Self {
|
||||
Self {
|
||||
object: object.into(),
|
||||
connection_name,
|
||||
impl<'gc> LocalConnectionKind<'gc> {
|
||||
pub fn send_status(&self, context: &mut UpdateContext<'_, 'gc>, status: &'static str) {
|
||||
match self {
|
||||
LocalConnectionKind::Avm2(_domain, object) => {
|
||||
object.send_status(context, status);
|
||||
}
|
||||
LocalConnectionKind::Avm1(object) => {
|
||||
if let Err(e) = Avm1LocalConnectionObject::send_status(context, *object, status) {
|
||||
tracing::error!("Unhandled AVM1 error during LocalConnection onStatus: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_method(
|
||||
&self,
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
method_name: AvmString<'gc>,
|
||||
arguments: Vec<AmfValue>,
|
||||
) {
|
||||
match self {
|
||||
LocalConnectionKind::Avm2(domain, object) => {
|
||||
object.run_method(context, *domain, method_name, arguments);
|
||||
}
|
||||
LocalConnectionKind::Avm1(object) => {
|
||||
if let Err(e) =
|
||||
Avm1LocalConnectionObject::run_method(context, *object, method_name, arguments)
|
||||
{
|
||||
tracing::error!("Unhandled AVM1 error during LocalConnection onStatus: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages the collection of local connections.
|
||||
pub struct LocalConnections<'gc> {
|
||||
connections: SlotMap<LocalConnectionHandle, LocalConnection<'gc>>,
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct QueuedMessage<'gc> {
|
||||
source: LocalConnectionKind<'gc>,
|
||||
kind: QueuedMessageKind<'gc>,
|
||||
}
|
||||
|
||||
unsafe impl<'gc> Collect for LocalConnections<'gc> {
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub enum QueuedMessageKind<'gc> {
|
||||
Failure,
|
||||
Message {
|
||||
#[collect(require_static)]
|
||||
connection_name: WString,
|
||||
method_name: AvmString<'gc>,
|
||||
#[collect(require_static)]
|
||||
arguments: Vec<AmfValue>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'gc> QueuedMessageKind<'gc> {
|
||||
pub fn deliver(self, source: LocalConnectionKind<'gc>, context: &mut UpdateContext<'_, 'gc>) {
|
||||
match self {
|
||||
QueuedMessageKind::Failure => {
|
||||
source.send_status(context, "error");
|
||||
}
|
||||
QueuedMessageKind::Message {
|
||||
connection_name,
|
||||
method_name,
|
||||
arguments,
|
||||
} => {
|
||||
if let Some(receiver) = context.local_connections.find_listener(&connection_name) {
|
||||
source.send_status(context, "status");
|
||||
receiver.run_method(context, method_name, arguments);
|
||||
} else {
|
||||
source.send_status(context, "error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque handle to an actively listening LocalConnection.
|
||||
/// Owning this handle represents ownership of a LocalConnection;
|
||||
/// However, a LocalConnection must be manually closed, you can't just Drop this handle.
|
||||
#[derive(Debug)]
|
||||
pub struct LocalConnectionHandle(WString);
|
||||
|
||||
/// Manages the collection of local connections.
|
||||
pub struct LocalConnections<'gc> {
|
||||
connections: FnvHashMap<WString, LocalConnectionKind<'gc>>,
|
||||
messages: Vec<QueuedMessage<'gc>>,
|
||||
}
|
||||
|
||||
unsafe impl Collect for LocalConnections<'_> {
|
||||
fn trace(&self, cc: &gc_arena::Collection) {
|
||||
for (_, connection) in self.connections.iter() {
|
||||
connection.trace(cc)
|
||||
for (_, v) in self.connections.iter() {
|
||||
v.trace(cc);
|
||||
}
|
||||
for m in self.messages.iter() {
|
||||
m.trace(cc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,26 +132,108 @@ unsafe impl<'gc> Collect for LocalConnections<'gc> {
|
|||
impl<'gc> LocalConnections<'gc> {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
connections: SlotMap::with_key(),
|
||||
connections: Default::default(),
|
||||
messages: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, connection: LocalConnection<'gc>) -> LocalConnectionHandle {
|
||||
self.connections.insert(connection)
|
||||
pub fn connect<C: Into<LocalConnectionKind<'gc>>>(
|
||||
&mut self,
|
||||
domain: &str,
|
||||
connection: C,
|
||||
name: &WStr,
|
||||
) -> Option<LocalConnectionHandle> {
|
||||
let key = if name.starts_with(b'_') {
|
||||
name.to_ascii_lowercase()
|
||||
} else {
|
||||
let mut key = WString::from_utf8(Self::get_superdomain(domain));
|
||||
key.push_char(':');
|
||||
key.push_str(name);
|
||||
key.make_ascii_lowercase();
|
||||
key
|
||||
};
|
||||
|
||||
if self.connections.contains_key(&key) {
|
||||
None
|
||||
} else {
|
||||
self.connections.insert(key.to_owned(), connection.into());
|
||||
Some(LocalConnectionHandle(key.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, handle: LocalConnectionHandle) {
|
||||
self.connections.remove(handle);
|
||||
pub fn close(&mut self, handle: LocalConnectionHandle) {
|
||||
self.connections.remove(&handle.0);
|
||||
}
|
||||
|
||||
pub fn all_by_name(&self, requested_name: AvmString<'gc>) -> Vec<&LocalConnection<'gc>> {
|
||||
let mut conns = Vec::new();
|
||||
for (_, connection) in self.connections.iter() {
|
||||
if connection.connection_name == requested_name {
|
||||
conns.push(connection);
|
||||
pub fn send<C: Into<LocalConnectionKind<'gc>>>(
|
||||
&mut self,
|
||||
domain: &str,
|
||||
source: C,
|
||||
connection_name: AvmString<'gc>,
|
||||
method_name: AvmString<'gc>,
|
||||
arguments: Vec<AmfValue>,
|
||||
) {
|
||||
// There's two checks for "is connected":
|
||||
// 1 - At `send()` time, if there's no connections, just immediately queue up a failure
|
||||
// 2 - At `update_connections()` time, if the connection couldn't be found, queue up a failure
|
||||
// Even if one becomes available between send and update, it won't be used
|
||||
// Similarly, if one becomes unavailable between send and update, it'll error
|
||||
// If something *else* takes its place between send and update, it'll use that instead
|
||||
|
||||
let mut connection_name = connection_name.to_ascii_lowercase();
|
||||
if !connection_name.contains(b':') && !connection_name.starts_with(b'_') {
|
||||
let mut result = WString::from_utf8(Self::get_superdomain(domain));
|
||||
result.push_char(':');
|
||||
result.push_str(&connection_name);
|
||||
connection_name = result;
|
||||
}
|
||||
|
||||
let kind = if self.find_listener(&connection_name).is_some() {
|
||||
QueuedMessageKind::Message {
|
||||
connection_name,
|
||||
method_name,
|
||||
arguments,
|
||||
}
|
||||
} else {
|
||||
QueuedMessageKind::Failure
|
||||
};
|
||||
self.messages.push(QueuedMessage {
|
||||
source: source.into(),
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
fn find_listener(&self, name: &WStr) -> Option<LocalConnectionKind<'gc>> {
|
||||
self.connections.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn update_connections(context: &mut UpdateContext<'_, 'gc>) {
|
||||
if context.local_connections.messages.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
conns
|
||||
for message in std::mem::take(&mut context.local_connections.messages) {
|
||||
message.kind.deliver(message.source, context);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_domain(url: &str) -> Cow<'static, str> {
|
||||
if let Ok(url) = url::Url::parse(url) {
|
||||
if url.scheme() == "file" {
|
||||
Cow::Borrowed("localhost")
|
||||
} else if let Some(domain) = url.domain() {
|
||||
Cow::Owned(domain.to_owned())
|
||||
} else {
|
||||
// no domain?
|
||||
Cow::Borrowed("localhost")
|
||||
}
|
||||
} else {
|
||||
tracing::error!("LocalConnection: Unable to parse movie URL: {url}");
|
||||
return Cow::Borrowed("unknown"); // this is surely an error but it'll hopefully highlight this case in issues for us
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_superdomain(domain: &str) -> &str {
|
||||
domain.rsplit_once('.').map(|(_, b)| b).unwrap_or(domain)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1715,6 +1715,7 @@ impl Player {
|
|||
run_all_phases_avm2(context);
|
||||
Avm1::run_frame(context);
|
||||
AudioManager::update_sounds(context);
|
||||
LocalConnections::update_connections(context);
|
||||
|
||||
// Only run the current list of callbacks - any callbacks added during callback execution
|
||||
// will be run at the end of the *next* frame.
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
class CustomLocalConnection extends LocalConnection {
|
||||
function test() {
|
||||
trace("custom.test was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s"));
|
||||
if (arguments.length > 0) {
|
||||
trace(" " + repr(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
function throwAnError() {
|
||||
trace("custom.throwAnError was called");
|
||||
//throw "aah!"; // [NA] this crashes every Flash Player I've tried
|
||||
//throw {}; // [NA] this causes an error when constructing the AsyncErrorEvent
|
||||
//throw new Error("aaah!");
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,111 @@
|
|||
package {
|
||||
|
||||
import flash.display.MovieClip;
|
||||
import flash.net.LocalConnection;
|
||||
import flash.utils.getQualifiedClassName;
|
||||
|
||||
|
||||
public class Child extends MovieClip {
|
||||
var lc: LocalConnection = new LocalConnection();
|
||||
|
||||
public function Child() {
|
||||
lc.connect("avm2_child");
|
||||
lc.client = {};
|
||||
lc.client.test = function() {
|
||||
trace("avm2_child.test was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s"));
|
||||
if (arguments.length > 0) {
|
||||
trace(" " + repr(arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getObjectId(needle: Object, haystack: Array): String {
|
||||
for (var i = 0; i < haystack.length; i++) {
|
||||
if (haystack[i] === needle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function repr(value: *, indent: String = " ", seenObjects: Array = null) {
|
||||
if (seenObjects == null) {
|
||||
seenObjects = [];
|
||||
}
|
||||
if (value === lc) {
|
||||
return "lc";
|
||||
}
|
||||
|
||||
if (value === undefined || value === null || value === true || value === false || value is Number) {
|
||||
return String(value);
|
||||
} else if (value is String) {
|
||||
return escapeString(value);
|
||||
} else {
|
||||
var existingId = getObjectId(value, seenObjects);
|
||||
if (existingId != null) {
|
||||
return "*" + existingId;
|
||||
}
|
||||
existingId = seenObjects.length;
|
||||
seenObjects.push(value);
|
||||
if (value is Array) {
|
||||
if (value.length == 0) {
|
||||
return "*" + existingId + " []";
|
||||
} else {
|
||||
var result = "*" + existingId + " [\n";
|
||||
var nextIndent = indent + " ";
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
result += nextIndent + repr(value[i], nextIndent, seenObjects) + "\n";
|
||||
}
|
||||
return result + indent + "]";
|
||||
}
|
||||
} else {
|
||||
var keys = [];
|
||||
for (var key in value) {
|
||||
keys.push(key);
|
||||
}
|
||||
keys.sort();
|
||||
|
||||
var result = "*" + existingId + " " + getQualifiedClassName(value) + " {";
|
||||
|
||||
if (keys.length == 0) {
|
||||
return result + "}";
|
||||
} else {
|
||||
result += "\n";
|
||||
var nextIndent = indent + " ";
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result += nextIndent + keys[i] + " = " + repr(value[keys[i]], nextIndent, seenObjects) + "\n";
|
||||
}
|
||||
return result + indent + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function escapeString(input: String): String {
|
||||
var output:String = "\"";
|
||||
for (var i:int = 0; i < input.length; i++) {
|
||||
var char:String = input.charAt(i);
|
||||
switch (char) {
|
||||
case "\\":
|
||||
output += "\\\\";
|
||||
break;
|
||||
case "\"":
|
||||
output += "\\\"";
|
||||
break;
|
||||
case "\n":
|
||||
output += "\\n";
|
||||
break;
|
||||
case "\r":
|
||||
output += "\\r";
|
||||
break;
|
||||
case "\t":
|
||||
output += "\\t";
|
||||
break;
|
||||
default:
|
||||
output += char;
|
||||
}
|
||||
}
|
||||
return output + "\"";
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,580 @@
|
|||
|
||||
-- start test: A message to nowhere! --
|
||||
|
||||
sender.send("nowhere", "test", *0 []): true
|
||||
|
||||
-- end frame: A message to nowhere! --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: A message to nowhere! --
|
||||
|
||||
|
||||
-- start test: Both receivers try to connect to the same channel --
|
||||
|
||||
receiver.connect("channel"): true
|
||||
custom.connect("channel"): false
|
||||
|
||||
-- end frame: Both receivers try to connect to the same channel --
|
||||
|
||||
|
||||
-- end test: Both receivers try to connect to the same channel --
|
||||
|
||||
|
||||
-- start test: A message to an unimplemented function --
|
||||
|
||||
sender.send("channel", "unimplemented", *0 []): true
|
||||
|
||||
-- end frame: A message to an unimplemented function --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
|
||||
-- end test: A message to an unimplemented function --
|
||||
|
||||
|
||||
-- start test: Receiver tries to connect elsewhere, but can't --
|
||||
|
||||
receiver.connect("elsewhere"): false
|
||||
|
||||
-- end frame: Receiver tries to connect elsewhere, but can't --
|
||||
|
||||
|
||||
-- end test: Receiver tries to connect elsewhere, but can't --
|
||||
|
||||
|
||||
-- start test: Receiver actually connects elsewhere, and custom is allowed to connect to channel --
|
||||
|
||||
receiver.close()
|
||||
receiver.connect("elsewhere"): true
|
||||
custom.connect("channel"): true
|
||||
|
||||
-- end frame: Receiver actually connects elsewhere, and custom is allowed to connect to channel --
|
||||
|
||||
|
||||
-- end test: Receiver actually connects elsewhere, and custom is allowed to connect to channel --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'channel' --
|
||||
|
||||
sender.send("channel", "test", *0 []): true
|
||||
|
||||
-- end frame: Sender calls test() on 'channel' --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
custom.test was called with 0 argument
|
||||
|
||||
-- end test: Sender calls test() on 'channel' --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'channel'... after the listener is gone --
|
||||
|
||||
custom.close()
|
||||
sender.send("channel", "test", *0 []): true
|
||||
|
||||
-- end frame: Sender calls test() on 'channel'... after the listener is gone --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: Sender calls test() on 'channel'... after the listener is gone --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'elsewhere'... immediately before the listener is gone --
|
||||
|
||||
sender.send("elsewhere", "test", *0 []): true
|
||||
receiver.close()
|
||||
|
||||
-- end frame: Sender calls test() on 'elsewhere'... immediately before the listener is gone --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: Sender calls test() on 'elsewhere'... immediately before the listener is gone --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'channel'... before the listener connects --
|
||||
|
||||
sender.send("channel", "test", *0 []): true
|
||||
custom.connect("channel"): true
|
||||
|
||||
-- end frame: Sender calls test() on 'channel'... before the listener connects --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: Sender calls test() on 'channel'... before the listener connects --
|
||||
|
||||
|
||||
-- start test: Sending to a channel that gets reassigned before end-of-frame --
|
||||
|
||||
sender.send("channel", "test", *0 []): true
|
||||
custom.close()
|
||||
receiver.connect("channel"): true
|
||||
|
||||
-- end frame: Sending to a channel that gets reassigned before end-of-frame --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
receiver.test was called with 0 argument
|
||||
|
||||
-- end test: Sending to a channel that gets reassigned before end-of-frame --
|
||||
|
||||
|
||||
-- start test: Channels reconnect and receive --
|
||||
|
||||
custom.close()
|
||||
receiver.close()
|
||||
receiver.connect("elsewhere"): true
|
||||
sender.send("channel", "test", *0 []): true
|
||||
sender.send("elsewhere", "test", *0 []): true
|
||||
custom.connect("channel"): true
|
||||
|
||||
-- end frame: Channels reconnect and receive --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
receiver.test was called with 0 argument
|
||||
|
||||
-- end test: Channels reconnect and receive --
|
||||
|
||||
|
||||
-- start test: A connected listener can also send --
|
||||
|
||||
receiver.send("channel", "test", *0 []): true
|
||||
receiver.send("elsewhere", "test", *0 []): true
|
||||
|
||||
-- end frame: A connected listener can also send --
|
||||
|
||||
receiver.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
custom.test was called with 0 argument
|
||||
receiver.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
receiver.test was called with 0 argument
|
||||
|
||||
-- end test: A connected listener can also send --
|
||||
|
||||
|
||||
-- start test: A listener throws an error --
|
||||
|
||||
sender.send("channel", "throwAnError", *0 []): true
|
||||
|
||||
-- end frame: A listener throws an error --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
custom.throwAnError was called
|
||||
|
||||
-- end test: A listener throws an error --
|
||||
|
||||
|
||||
-- start test: Close something's that's already closed --
|
||||
|
||||
|
||||
-- end frame: Close something's that's already closed --
|
||||
|
||||
|
||||
-- end test: Close something's that's already closed --
|
||||
|
||||
|
||||
-- start test: Send to funky channel names --
|
||||
|
||||
sender.send(null, "test", *0 []): false
|
||||
sender.send(0, "test", *0 []): false
|
||||
sender.send("", "test", *0 []): false
|
||||
sender.send(" ??? ", "test", *0 []): true
|
||||
|
||||
-- end frame: Send to funky channel names --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: Send to funky channel names --
|
||||
|
||||
|
||||
-- start test: Send to funky methods --
|
||||
|
||||
sender.send("channel", null, *0 []): false
|
||||
sender.send("channel", 0, *0 []): false
|
||||
sender.send("channel", "", *0 []): false
|
||||
sender.send("channel", " ??? ", *0 []): true
|
||||
|
||||
-- end frame: Send to funky methods --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
|
||||
-- end test: Send to funky methods --
|
||||
|
||||
|
||||
-- start test: Connect to funky names --
|
||||
|
||||
sender.connect(null): false
|
||||
sender.close()
|
||||
sender.connect(0): false
|
||||
sender.close()
|
||||
sender.connect(""): false
|
||||
sender.close()
|
||||
sender.connect(" ??? "): true
|
||||
sender.close()
|
||||
|
||||
-- end frame: Connect to funky names --
|
||||
|
||||
|
||||
-- end test: Connect to funky names --
|
||||
|
||||
|
||||
-- start test: Connect to something with a prefix --
|
||||
|
||||
sender.connect("localhost:something"): false
|
||||
sender.close()
|
||||
|
||||
-- end frame: Connect to something with a prefix --
|
||||
|
||||
|
||||
-- end test: Connect to something with a prefix --
|
||||
|
||||
|
||||
-- start test: Send to protected methods --
|
||||
|
||||
sender.send("channel", "send", *0 []): false
|
||||
sender.send("channel", "connect", *0 []): false
|
||||
sender.send("channel", "close", *0 []): false
|
||||
sender.send("channel", "allowDomain", *0 []): false
|
||||
sender.send("channel", "allowInsecureDomain", *0 []): false
|
||||
sender.send("channel", "domain", *0 []): false
|
||||
|
||||
-- end frame: Send to protected methods --
|
||||
|
||||
|
||||
-- end test: Send to protected methods --
|
||||
|
||||
|
||||
-- start test: Arguments are sent --
|
||||
|
||||
sender.send("elsewhere", "test", *0 [
|
||||
1
|
||||
"two"
|
||||
*1 Object {
|
||||
value = 3
|
||||
}
|
||||
]): true
|
||||
|
||||
-- end frame: Arguments are sent --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
receiver.test was called with 3 arguments
|
||||
*0 [
|
||||
1
|
||||
"two"
|
||||
*1 Object {
|
||||
value = 3
|
||||
}
|
||||
]
|
||||
|
||||
-- end test: Arguments are sent --
|
||||
|
||||
|
||||
-- start test: Explicit host prefix --
|
||||
|
||||
sender.send("localhost:channel", "test", *0 []): true
|
||||
sender.send("notlocalhost:elsewhere", "test", *0 []): true
|
||||
|
||||
-- end frame: Explicit host prefix --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
custom.test was called with 0 argument
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: Explicit host prefix --
|
||||
|
||||
|
||||
-- start test: Underscores in names --
|
||||
|
||||
custom.close()
|
||||
custom.connect("_channel"): true
|
||||
sender.send("_channel", "test", *0 []): true
|
||||
|
||||
-- end frame: Underscores in names --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
custom.test was called with 0 argument
|
||||
|
||||
-- end test: Underscores in names --
|
||||
|
||||
|
||||
-- start test: Underscores in name doesn't allow a prefix --
|
||||
|
||||
sender.send("localhost:channel", "test", *0 []): true
|
||||
sender.send("localhost:_channel", "test", *0 []): true
|
||||
|
||||
-- end frame: Underscores in name doesn't allow a prefix --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: Underscores in name doesn't allow a prefix --
|
||||
|
||||
|
||||
-- start test: Case sensitivity --
|
||||
|
||||
sender.send("ELSEWhere", "test", *0 []): true
|
||||
sender.send("LOCalHOST:ElseWhere", "test", *0 []): true
|
||||
|
||||
-- end frame: Case sensitivity --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
receiver.test was called with 0 argument
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
receiver.test was called with 0 argument
|
||||
|
||||
-- end test: Case sensitivity --
|
||||
|
||||
|
||||
-- start test: Calling an AVM2 movie --
|
||||
|
||||
sender.send("avm2_child", "test", *0 []): true
|
||||
|
||||
-- end frame: Calling an AVM2 movie --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
|
||||
-- end test: Calling an AVM2 movie --
|
||||
|
||||
|
||||
-- start test: Calling an AVM1 movie --
|
||||
|
||||
sender.send("avm1_child", "test", *0 []): true
|
||||
|
||||
-- end frame: Calling an AVM1 movie --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
avm1_child.test was called with 0 argument
|
||||
|
||||
-- end test: Calling an AVM1 movie --
|
||||
|
||||
|
||||
-- start test: Argument translations: primitives --
|
||||
|
||||
sender.send("avm1_child", "test", *0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]): true
|
||||
sender.send("avm2_child", "test", *0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]): true
|
||||
sender.send("_channel", "test", *0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]): true
|
||||
|
||||
-- end frame: Argument translations: primitives --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
avm1_child.test was called with 7 arguments
|
||||
*0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
custom.test was called with 7 arguments
|
||||
*0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]
|
||||
|
||||
-- end test: Argument translations: primitives --
|
||||
|
||||
|
||||
-- start test: Argument translations: simple object --
|
||||
|
||||
sender.send("avm1_child", "test", *0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]): true
|
||||
sender.send("avm2_child", "test", *0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]): true
|
||||
sender.send("_channel", "test", *0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]): true
|
||||
|
||||
-- end frame: Argument translations: simple object --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
avm1_child.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 object {
|
||||
nested = *2 object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "error"
|
||||
}
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
custom.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
-- end test: Argument translations: simple object --
|
||||
|
||||
|
||||
-- start test: AVM1 movie throws an error --
|
||||
|
||||
sender.send("avm1_child", "throwAnError", *0 []): true
|
||||
|
||||
-- end frame: AVM1 movie throws an error --
|
||||
|
||||
sender.onStatus was called
|
||||
*0 Object {
|
||||
level = "status"
|
||||
}
|
||||
avm1_child.throwAnError was called
|
||||
|
||||
-- end test: AVM1 movie throws an error --
|
||||
|
||||
Finished after 117 frames
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
num_ticks = 300 # Test may finish in less, but it'll `fscommand:exit` when it's done.
|
|
@ -0,0 +1,8 @@
|
|||
/// Enumeration
|
||||
|
||||
/// Known Properties
|
||||
domain: DONT_ENUM | DONT_DELETE
|
||||
connect: DONT_ENUM | DONT_DELETE
|
||||
close: DONT_ENUM | DONT_DELETE
|
||||
isPerUser NOT FOUND
|
||||
send: DONT_ENUM | DONT_DELETE
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
num_frames = 1
|
|
@ -0,0 +1,26 @@
|
|||
package {
|
||||
import flash.net.LocalConnection;
|
||||
|
||||
public class CustomLocalConnection extends LocalConnection {
|
||||
private var main: Test;
|
||||
|
||||
public function CustomLocalConnection(main: Test) {
|
||||
super();
|
||||
this.main = main;
|
||||
}
|
||||
|
||||
public function test() {
|
||||
trace("custom.test was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s"));
|
||||
if (arguments.length > 0) {
|
||||
trace(" " + main.repr(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
public function throwAnError() {
|
||||
trace("custom.throwAnError was called");
|
||||
//throw "aah!"; // [NA] this crashes every Flash Player I've tried
|
||||
//throw {}; // [NA] this causes an error when constructing the AsyncErrorEvent
|
||||
//throw new Error("aaah!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
package {
|
||||
|
||||
import flash.display.MovieClip;
|
||||
import flash.net.LocalConnection;
|
||||
import flash.utils.getQualifiedClassName;
|
||||
import flash.events.AsyncErrorEvent;
|
||||
import flash.events.Event;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.events.StatusEvent;
|
||||
import flash.system.fscommand;
|
||||
import flash.net.URLRequest;
|
||||
import flash.display.Loader;
|
||||
|
||||
|
||||
public class Test extends MovieClip {
|
||||
// Just a safe value, to make things easier...
|
||||
// Sometimes Flash isn't super consistant about things being done on the same frame
|
||||
const TICKS_PER_TEST: uint = 3;
|
||||
|
||||
var receiver: LocalConnection = new LocalConnection();
|
||||
var sender: LocalConnection = new LocalConnection();
|
||||
var custom: LocalConnection;
|
||||
var recvObject: Object = {};
|
||||
var tests: Array = [];
|
||||
var currentTest = null;
|
||||
var frameNum: uint = 0;
|
||||
var totalFrameNum: uint = 0;
|
||||
var funkyNames = [null, 0, "", " ??? "];
|
||||
var protectedFunctions = ["send", "connect", "close", "allowDomain", "allowInsecureDomain", "domain"];
|
||||
|
||||
public function Test() {
|
||||
custom = new CustomLocalConnection(this);
|
||||
|
||||
loadMovie("avm2child/child.swf");
|
||||
loadMovie("avm1child/child.swf");
|
||||
|
||||
trace("LocalConnection.isSupported: " + repr(LocalConnection.isSupported));
|
||||
trace("");
|
||||
|
||||
recvObject.test = createTestFunction("recvObject.test");
|
||||
|
||||
setupEvents(receiver);
|
||||
setupEvents(sender);
|
||||
setupEvents(custom);
|
||||
|
||||
addTest("A message to nowhere!", function() {
|
||||
send(sender, "nowhere", "test");
|
||||
});
|
||||
|
||||
addTest("Both receivers try to connect to the same channel", function() {
|
||||
connect(receiver, "channel");
|
||||
connect(custom, "channel");
|
||||
});
|
||||
|
||||
addTest("A message to an unimplemented function", function() {
|
||||
send(sender, "channel", "unimplemented");
|
||||
});
|
||||
|
||||
addTest("Receiver tries to connect elsewhere, but can't", function() {
|
||||
connect(receiver, "elsewhere");
|
||||
});
|
||||
|
||||
addTest("Receiver actually connects elsewhere, and custom is allowed to connect to channel", function() {
|
||||
close(receiver);
|
||||
connect(receiver, "elsewhere");
|
||||
connect(custom, "channel");
|
||||
});
|
||||
|
||||
addTest("Sender calls test() on 'channel'", function() {
|
||||
send(sender, "channel", "test");
|
||||
});
|
||||
|
||||
addTest("Client is used", function() {
|
||||
receiver.client = recvObject;
|
||||
send(sender, "elsewhere", "test");
|
||||
});
|
||||
|
||||
addTest("Sender calls test() on 'channel'... after the listener is gone", function() {
|
||||
close(custom);
|
||||
send(sender, "channel", "test");
|
||||
});
|
||||
|
||||
addTest("Sender calls test() on 'elsewhere'... immediately before the listener is gone", function() {
|
||||
send(sender, "elsewhere", "test");
|
||||
close(receiver);
|
||||
});
|
||||
|
||||
addTest("Sender calls test() on 'channel'... before the listener connects", function() {
|
||||
send(sender, "channel", "test");
|
||||
connect(custom, "channel");
|
||||
});
|
||||
|
||||
addTest("Sending to a channel that gets reassigned before end-of-frame", function() {
|
||||
send(sender, "channel", "test");
|
||||
close(custom);
|
||||
connect(receiver, "channel");
|
||||
});
|
||||
|
||||
addTest("Channels reconnect and receive", function() {
|
||||
close(custom);
|
||||
close(receiver);
|
||||
connect(receiver, "elsewhere");
|
||||
send(sender, "channel", "test");
|
||||
send(sender, "elsewhere", "test");
|
||||
connect(custom, "channel");
|
||||
});
|
||||
|
||||
addTest("A connected listener can also send", function() {
|
||||
send(receiver, "channel", "test");
|
||||
send(receiver, "elsewhere", "test");
|
||||
});
|
||||
|
||||
addTest("A listener throws an error", function() {
|
||||
// Fun fact: you can crash Flash Player if the thing thrown isn't an object
|
||||
send(sender, "channel", "throwAnError");
|
||||
});
|
||||
|
||||
addTest("Close something's that's already closed", function() {
|
||||
sender.close();
|
||||
});
|
||||
|
||||
addTest("Send to funky channel names", function() {
|
||||
for (var i = 0; i < funkyNames.length; i++) {
|
||||
send(sender, funkyNames[i], "test");
|
||||
}
|
||||
});
|
||||
|
||||
addTest("Send to funky methods", function() {
|
||||
for (var i = 0; i < funkyNames.length; i++) {
|
||||
send(sender, "channel", funkyNames[i]);
|
||||
}
|
||||
});
|
||||
|
||||
addTest("Connect to funky names", function() {
|
||||
for (var i = 0; i < funkyNames.length; i++) {
|
||||
connect(sender, funkyNames[i]);
|
||||
close(sender);
|
||||
}
|
||||
});
|
||||
|
||||
addTest("Connect to something with a prefix", function() {
|
||||
connect(sender, "localhost:something");
|
||||
close(sender);
|
||||
});
|
||||
|
||||
addTest("Send to protected methods", function() {
|
||||
for (var i = 0; i < protectedFunctions.length; i++) {
|
||||
send(sender, "channel", protectedFunctions[i]);
|
||||
}
|
||||
});
|
||||
|
||||
addTest("Arguments are sent", function() {
|
||||
send(sender, "elsewhere", "test", 1, "two", {value: 3}, [4, 5]);
|
||||
});
|
||||
|
||||
addTest("Explicit host prefix", function() {
|
||||
send(sender, "localhost:channel", "test");
|
||||
send(sender, "notlocalhost:elsewhere", "test");
|
||||
});
|
||||
|
||||
addTest("Underscores in names", function() {
|
||||
close(custom);
|
||||
connect(custom, "_channel");
|
||||
send(sender, "_channel", "test");
|
||||
});
|
||||
|
||||
addTest("Underscores in name doesn't allow a prefix", function() {
|
||||
send(sender, "localhost:channel", "test");
|
||||
send(sender, "localhost:_channel", "test");
|
||||
});
|
||||
|
||||
addTest("Case sensitivity", function() {
|
||||
send(sender, "ELSEWhere", "test");
|
||||
send(sender, "LOCalHOST:ElseWhere", "test");
|
||||
});
|
||||
|
||||
addTest("Calling an AVM2 movie", function() {
|
||||
send(sender, "avm2_child", "test");
|
||||
});
|
||||
|
||||
addTest("Calling an AVM1 movie", function() {
|
||||
send(sender, "avm1_child", "test");
|
||||
});
|
||||
|
||||
addTest("Argument translations: primitives", function() {
|
||||
sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", 1, 1.2, true, false, "string", null, undefined);
|
||||
});
|
||||
|
||||
addTest("Argument translations: simple array", function() {
|
||||
sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", [1,2, "three", 4.5, NaN, Infinity]);
|
||||
});
|
||||
|
||||
addTest("Argument translations: simple object", function() {
|
||||
sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", {"nested": {"numbers": [1,2], "string": "hello"}});
|
||||
});
|
||||
|
||||
// [NA] broken in ruffle at time of writing
|
||||
//addTest("Argument translations: self referential object", function() {
|
||||
// var obj = {};
|
||||
// obj.self = obj;
|
||||
// obj.nested = {root: obj};
|
||||
// sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", obj);
|
||||
//});
|
||||
|
||||
//addTest("Argument translations: self referential array", function() {
|
||||
// var array = [];
|
||||
// array.push(array);
|
||||
// sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", array);
|
||||
//});
|
||||
|
||||
//addTest("Argument translations: vector", function() {
|
||||
//var vector = new Vector.<String>();
|
||||
//vector.push("hello");
|
||||
//vector.push("world");
|
||||
//sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", vector);
|
||||
//});
|
||||
|
||||
addTest("AVM1 movie throws an error", function() {
|
||||
send(sender, "avm1_child", "throwAnError");
|
||||
});
|
||||
|
||||
addEventListener(Event.ENTER_FRAME, onEnterFrame);
|
||||
}
|
||||
|
||||
function loadMovie(path: String) {
|
||||
var loader = new Loader();
|
||||
loader.load(new URLRequest(path));
|
||||
addChild(loader);
|
||||
}
|
||||
|
||||
function onEnterFrame(event: Event) {
|
||||
totalFrameNum++;
|
||||
|
||||
if (frameNum == TICKS_PER_TEST) {
|
||||
trace("");
|
||||
trace("-- end test: " + currentTest[0] + " --");
|
||||
trace("");
|
||||
frameNum = 0;
|
||||
return; // Allow any end-of-frame cleanup before next test
|
||||
}
|
||||
if (frameNum == 0) {
|
||||
currentTest = tests.shift();
|
||||
if (currentTest != null) {
|
||||
trace("");
|
||||
trace("-- start test: " + currentTest[0] + " --");
|
||||
trace("");
|
||||
try {
|
||||
currentTest[1]();
|
||||
} catch (e) {
|
||||
trace("! test stopped with error: " + e);
|
||||
}
|
||||
trace("");
|
||||
trace("-- end frame: " + currentTest[0] + " --");
|
||||
trace("");
|
||||
}
|
||||
}
|
||||
if (currentTest == null) {
|
||||
trace("Finished after " + totalFrameNum + " frames");
|
||||
fscommand("exit");
|
||||
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
|
||||
return;
|
||||
}
|
||||
frameNum++;
|
||||
}
|
||||
|
||||
function connect(lc: LocalConnection, name: String) {
|
||||
var doing = repr(lc) + ".connect(" + repr(name) + ")";
|
||||
try {
|
||||
lc.connect(name);
|
||||
trace(doing);
|
||||
} catch (e) {
|
||||
trace(doing + ": ! " + e);
|
||||
}
|
||||
}
|
||||
|
||||
function send(lc: LocalConnection, connectionName: String, methodName: String, ...args) {
|
||||
var doing = repr(lc) + ".send(" + repr(connectionName) + ", " + repr(methodName) + ", " + repr(args) + ")";
|
||||
try {
|
||||
args.unshift(methodName);
|
||||
args.unshift(connectionName);
|
||||
lc.send.apply(lc, args);
|
||||
trace(doing);
|
||||
} catch (e) {
|
||||
trace(doing + ": ! " + e);
|
||||
}
|
||||
}
|
||||
|
||||
function sendToMany(lc: LocalConnection, connectionNames: Array, methodName: String, ...args) {
|
||||
args.unshift(methodName);
|
||||
args.unshift("");
|
||||
args.unshift(lc);
|
||||
for (var i = 0; i < connectionNames.length; i++) {
|
||||
args[1] = connectionNames[i];
|
||||
send.apply(null, args);
|
||||
}
|
||||
}
|
||||
|
||||
function close(lc: LocalConnection) {
|
||||
var doing = repr(lc) + ".close()";
|
||||
try {
|
||||
lc.close();
|
||||
trace(doing);
|
||||
} catch (e) {
|
||||
trace(doing + ": ! " + e);
|
||||
}
|
||||
}
|
||||
|
||||
function addTest(name: String, fn: Function) {
|
||||
tests.push([name, fn]);
|
||||
}
|
||||
|
||||
function createTestFunction(name: String) {
|
||||
return function() {
|
||||
trace(name + " was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s"));
|
||||
if (arguments.length > 0) {
|
||||
trace(" " + repr(arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupEvents(lc: LocalConnection) {
|
||||
var name: String = repr(lc);
|
||||
lc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, function(event: AsyncErrorEvent) {
|
||||
trace(name + " received event AsyncErrorEvent.ASYNC_ERROR");
|
||||
trace(" bubbles: " + repr(event.bubbles));
|
||||
trace(" cancelable: " + repr(event.cancelable));
|
||||
trace(" error: " + event.error);
|
||||
trace(" currentTarget: " + repr(event.currentTarget));
|
||||
trace(" target: " + repr(event.target));
|
||||
trace("");
|
||||
});
|
||||
lc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(event: SecurityErrorEvent) {
|
||||
trace(name + " received event SecurityErrorEvent.SECURITY_ERROR");
|
||||
trace(" bubbles: " + repr(event.bubbles));
|
||||
trace(" cancelable: " + repr(event.cancelable));
|
||||
trace(" text: " + repr(event.text));
|
||||
trace(" currentTarget: " + repr(event.currentTarget));
|
||||
trace(" target: " + repr(event.target));
|
||||
trace("");
|
||||
});
|
||||
lc.addEventListener(StatusEvent.STATUS, function(event: StatusEvent) {
|
||||
trace(name + " received event StatusEvent.STATUS");
|
||||
trace(" bubbles: " + repr(event.bubbles));
|
||||
trace(" cancelable: " + repr(event.cancelable));
|
||||
trace(" code: " + repr(event.code));
|
||||
trace(" currentTarget: " + repr(event.currentTarget));
|
||||
trace(" level: " + repr(event.level));
|
||||
trace(" target: " + repr(event.target));
|
||||
trace("");
|
||||
});
|
||||
}
|
||||
|
||||
private function getObjectId(needle: Object, haystack: Array): String {
|
||||
for (var i = 0; i < haystack.length; i++) {
|
||||
if (haystack[i] === needle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function repr(value: *, indent: String = " ", seenObjects: Array = null) {
|
||||
if (seenObjects == null) {
|
||||
seenObjects = [];
|
||||
}
|
||||
if (value === receiver) {
|
||||
return "receiver";
|
||||
} else if (value === sender) {
|
||||
return "sender";
|
||||
} else if (value === recvObject) {
|
||||
return "recvObject";
|
||||
} else if (value === custom) {
|
||||
return "custom";
|
||||
}
|
||||
|
||||
if (value === undefined || value === null || value === true || value === false || value is Number) {
|
||||
return String(value);
|
||||
} else if (value is String) {
|
||||
return escapeString(value);
|
||||
} else {
|
||||
var existingId = getObjectId(value, seenObjects);
|
||||
if (existingId != null) {
|
||||
return "*" + existingId;
|
||||
}
|
||||
existingId = seenObjects.length;
|
||||
seenObjects.push(value);
|
||||
if (value is Array) {
|
||||
if (value.length == 0) {
|
||||
return "*" + existingId + " []";
|
||||
} else {
|
||||
var result = "*" + existingId + " [\n";
|
||||
var nextIndent = indent + " ";
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
result += nextIndent + repr(value[i], nextIndent, seenObjects) + "\n";
|
||||
}
|
||||
return result + indent + "]";
|
||||
}
|
||||
} else {
|
||||
var keys = [];
|
||||
for (var key in value) {
|
||||
keys.push(key);
|
||||
}
|
||||
keys.sort();
|
||||
|
||||
var result = "*" + existingId + " " + getQualifiedClassName(value) + " {";
|
||||
|
||||
if (keys.length == 0) {
|
||||
return result + "}";
|
||||
} else {
|
||||
result += "\n";
|
||||
var nextIndent = indent + " ";
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result += nextIndent + keys[i] + " = " + repr(value[keys[i]], nextIndent, seenObjects) + "\n";
|
||||
}
|
||||
return result + indent + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function escapeString(input: String): String {
|
||||
var output:String = "\"";
|
||||
for (var i:int = 0; i < input.length; i++) {
|
||||
var char:String = input.charAt(i);
|
||||
switch (char) {
|
||||
case "\\":
|
||||
output += "\\\\";
|
||||
break;
|
||||
case "\"":
|
||||
output += "\\\"";
|
||||
break;
|
||||
case "\n":
|
||||
output += "\\n";
|
||||
break;
|
||||
case "\r":
|
||||
output += "\\r";
|
||||
break;
|
||||
case "\t":
|
||||
output += "\\t";
|
||||
break;
|
||||
default:
|
||||
output += char;
|
||||
}
|
||||
}
|
||||
return output + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,111 @@
|
|||
package {
|
||||
|
||||
import flash.display.MovieClip;
|
||||
import flash.net.LocalConnection;
|
||||
import flash.utils.getQualifiedClassName;
|
||||
|
||||
|
||||
public class Child extends MovieClip {
|
||||
var lc: LocalConnection = new LocalConnection();
|
||||
|
||||
public function Child() {
|
||||
lc.connect("avm2_child");
|
||||
lc.client = {};
|
||||
lc.client.test = function() {
|
||||
trace("avm2_child.test was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s"));
|
||||
if (arguments.length > 0) {
|
||||
trace(" " + repr(arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getObjectId(needle: Object, haystack: Array): String {
|
||||
for (var i = 0; i < haystack.length; i++) {
|
||||
if (haystack[i] === needle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function repr(value: *, indent: String = " ", seenObjects: Array = null) {
|
||||
if (seenObjects == null) {
|
||||
seenObjects = [];
|
||||
}
|
||||
if (value === lc) {
|
||||
return "lc";
|
||||
}
|
||||
|
||||
if (value === undefined || value === null || value === true || value === false || value is Number) {
|
||||
return String(value);
|
||||
} else if (value is String) {
|
||||
return escapeString(value);
|
||||
} else {
|
||||
var existingId = getObjectId(value, seenObjects);
|
||||
if (existingId != null) {
|
||||
return "*" + existingId;
|
||||
}
|
||||
existingId = seenObjects.length;
|
||||
seenObjects.push(value);
|
||||
if (value is Array) {
|
||||
if (value.length == 0) {
|
||||
return "*" + existingId + " []";
|
||||
} else {
|
||||
var result = "*" + existingId + " [\n";
|
||||
var nextIndent = indent + " ";
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
result += nextIndent + repr(value[i], nextIndent, seenObjects) + "\n";
|
||||
}
|
||||
return result + indent + "]";
|
||||
}
|
||||
} else {
|
||||
var keys = [];
|
||||
for (var key in value) {
|
||||
keys.push(key);
|
||||
}
|
||||
keys.sort();
|
||||
|
||||
var result = "*" + existingId + " " + getQualifiedClassName(value) + " {";
|
||||
|
||||
if (keys.length == 0) {
|
||||
return result + "}";
|
||||
} else {
|
||||
result += "\n";
|
||||
var nextIndent = indent + " ";
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result += nextIndent + keys[i] + " = " + repr(value[keys[i]], nextIndent, seenObjects) + "\n";
|
||||
}
|
||||
return result + indent + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function escapeString(input: String): String {
|
||||
var output:String = "\"";
|
||||
for (var i:int = 0; i < input.length; i++) {
|
||||
var char:String = input.charAt(i);
|
||||
switch (char) {
|
||||
case "\\":
|
||||
output += "\\\\";
|
||||
break;
|
||||
case "\"":
|
||||
output += "\\\"";
|
||||
break;
|
||||
case "\n":
|
||||
output += "\\n";
|
||||
break;
|
||||
case "\r":
|
||||
output += "\\r";
|
||||
break;
|
||||
case "\t":
|
||||
output += "\\t";
|
||||
break;
|
||||
default:
|
||||
output += char;
|
||||
}
|
||||
}
|
||||
return output + "\"";
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,890 @@
|
|||
LocalConnection.isSupported: true
|
||||
|
||||
|
||||
-- start test: A message to nowhere! --
|
||||
|
||||
sender.send("nowhere", "test", *0 [])
|
||||
|
||||
-- end frame: A message to nowhere! --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
|
||||
-- end test: A message to nowhere! --
|
||||
|
||||
|
||||
-- start test: Both receivers try to connect to the same channel --
|
||||
|
||||
receiver.connect("channel")
|
||||
custom.connect("channel"): ! ArgumentError: Error #2082: Connect failed because the object is already connected.
|
||||
|
||||
-- end frame: Both receivers try to connect to the same channel --
|
||||
|
||||
|
||||
-- end test: Both receivers try to connect to the same channel --
|
||||
|
||||
|
||||
-- start test: A message to an unimplemented function --
|
||||
|
||||
sender.send("channel", "unimplemented", *0 [])
|
||||
|
||||
-- end frame: A message to an unimplemented function --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
receiver received event AsyncErrorEvent.ASYNC_ERROR
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
error: ReferenceError: Error #1069: Property unimplemented not found on flash.net.LocalConnection and there is no default value.
|
||||
currentTarget: receiver
|
||||
target: receiver
|
||||
|
||||
|
||||
-- end test: A message to an unimplemented function --
|
||||
|
||||
|
||||
-- start test: Receiver tries to connect elsewhere, but can't --
|
||||
|
||||
receiver.connect("elsewhere"): ! ArgumentError: Error #2082: Connect failed because the object is already connected.
|
||||
|
||||
-- end frame: Receiver tries to connect elsewhere, but can't --
|
||||
|
||||
|
||||
-- end test: Receiver tries to connect elsewhere, but can't --
|
||||
|
||||
|
||||
-- start test: Receiver actually connects elsewhere, and custom is allowed to connect to channel --
|
||||
|
||||
receiver.close()
|
||||
receiver.connect("elsewhere")
|
||||
custom.connect("channel")
|
||||
|
||||
-- end frame: Receiver actually connects elsewhere, and custom is allowed to connect to channel --
|
||||
|
||||
|
||||
-- end test: Receiver actually connects elsewhere, and custom is allowed to connect to channel --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'channel' --
|
||||
|
||||
sender.send("channel", "test", *0 [])
|
||||
|
||||
-- end frame: Sender calls test() on 'channel' --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom.test was called with 0 argument
|
||||
|
||||
-- end test: Sender calls test() on 'channel' --
|
||||
|
||||
|
||||
-- start test: Client is used --
|
||||
|
||||
sender.send("elsewhere", "test", *0 [])
|
||||
|
||||
-- end frame: Client is used --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
recvObject.test was called with 0 argument
|
||||
|
||||
-- end test: Client is used --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'channel'... after the listener is gone --
|
||||
|
||||
custom.close()
|
||||
sender.send("channel", "test", *0 [])
|
||||
|
||||
-- end frame: Sender calls test() on 'channel'... after the listener is gone --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
|
||||
-- end test: Sender calls test() on 'channel'... after the listener is gone --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'elsewhere'... immediately before the listener is gone --
|
||||
|
||||
sender.send("elsewhere", "test", *0 [])
|
||||
receiver.close()
|
||||
|
||||
-- end frame: Sender calls test() on 'elsewhere'... immediately before the listener is gone --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
|
||||
-- end test: Sender calls test() on 'elsewhere'... immediately before the listener is gone --
|
||||
|
||||
|
||||
-- start test: Sender calls test() on 'channel'... before the listener connects --
|
||||
|
||||
sender.send("channel", "test", *0 [])
|
||||
custom.connect("channel")
|
||||
|
||||
-- end frame: Sender calls test() on 'channel'... before the listener connects --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
|
||||
-- end test: Sender calls test() on 'channel'... before the listener connects --
|
||||
|
||||
|
||||
-- start test: Sending to a channel that gets reassigned before end-of-frame --
|
||||
|
||||
sender.send("channel", "test", *0 [])
|
||||
custom.close()
|
||||
receiver.connect("channel")
|
||||
|
||||
-- end frame: Sending to a channel that gets reassigned before end-of-frame --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
recvObject.test was called with 0 argument
|
||||
|
||||
-- end test: Sending to a channel that gets reassigned before end-of-frame --
|
||||
|
||||
|
||||
-- start test: Channels reconnect and receive --
|
||||
|
||||
custom.close(): ! ArgumentError: Error #2083: Close failed because the object is not connected.
|
||||
receiver.close()
|
||||
receiver.connect("elsewhere")
|
||||
sender.send("channel", "test", *0 [])
|
||||
sender.send("elsewhere", "test", *0 [])
|
||||
custom.connect("channel")
|
||||
|
||||
-- end frame: Channels reconnect and receive --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
recvObject.test was called with 0 argument
|
||||
|
||||
-- end test: Channels reconnect and receive --
|
||||
|
||||
|
||||
-- start test: A connected listener can also send --
|
||||
|
||||
receiver.send("channel", "test", *0 [])
|
||||
receiver.send("elsewhere", "test", *0 [])
|
||||
|
||||
-- end frame: A connected listener can also send --
|
||||
|
||||
receiver received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: receiver
|
||||
level: "status"
|
||||
target: receiver
|
||||
|
||||
custom.test was called with 0 argument
|
||||
receiver received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: receiver
|
||||
level: "status"
|
||||
target: receiver
|
||||
|
||||
recvObject.test was called with 0 argument
|
||||
|
||||
-- end test: A connected listener can also send --
|
||||
|
||||
|
||||
-- start test: A listener throws an error --
|
||||
|
||||
sender.send("channel", "throwAnError", *0 [])
|
||||
|
||||
-- end frame: A listener throws an error --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom.throwAnError was called
|
||||
|
||||
-- end test: A listener throws an error --
|
||||
|
||||
|
||||
-- start test: Close something's that's already closed --
|
||||
|
||||
! test stopped with error: ArgumentError: Error #2083: Close failed because the object is not connected.
|
||||
|
||||
-- end frame: Close something's that's already closed --
|
||||
|
||||
|
||||
-- end test: Close something's that's already closed --
|
||||
|
||||
|
||||
-- start test: Send to funky channel names --
|
||||
|
||||
sender.send(null, "test", *0 []): ! TypeError: Error #2007: Parameter connectionName must be non-null.
|
||||
sender.send("0", "test", *0 [])
|
||||
sender.send("", "test", *0 []): ! ArgumentError: Error #2085: Parameter connectionName must be non-empty string.
|
||||
sender.send(" ??? ", "test", *0 [])
|
||||
|
||||
-- end frame: Send to funky channel names --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
|
||||
-- end test: Send to funky channel names --
|
||||
|
||||
|
||||
-- start test: Send to funky methods --
|
||||
|
||||
sender.send("channel", null, *0 []): ! TypeError: Error #2007: Parameter methodName must be non-null.
|
||||
sender.send("channel", "0", *0 [])
|
||||
sender.send("channel", "", *0 []): ! ArgumentError: Error #2085: Parameter methodName must be non-empty string.
|
||||
sender.send("channel", " ??? ", *0 [])
|
||||
|
||||
-- end frame: Send to funky methods --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom received event AsyncErrorEvent.ASYNC_ERROR
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
error: ReferenceError: Error #1069: Property 0 not found on CustomLocalConnection and there is no default value.
|
||||
currentTarget: custom
|
||||
target: custom
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom received event AsyncErrorEvent.ASYNC_ERROR
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
error: ReferenceError: Error #1069: Property ??? not found on CustomLocalConnection and there is no default value.
|
||||
currentTarget: custom
|
||||
target: custom
|
||||
|
||||
|
||||
-- end test: Send to funky methods --
|
||||
|
||||
|
||||
-- start test: Connect to funky names --
|
||||
|
||||
sender.connect(null): ! TypeError: Error #2007: Parameter connectionName must be non-null.
|
||||
sender.close(): ! ArgumentError: Error #2083: Close failed because the object is not connected.
|
||||
sender.connect("0")
|
||||
sender.close()
|
||||
sender.connect(""): ! ArgumentError: Error #2085: Parameter connectionName must be non-empty string.
|
||||
sender.close(): ! ArgumentError: Error #2083: Close failed because the object is not connected.
|
||||
sender.connect(" ??? ")
|
||||
sender.close()
|
||||
|
||||
-- end frame: Connect to funky names --
|
||||
|
||||
|
||||
-- end test: Connect to funky names --
|
||||
|
||||
|
||||
-- start test: Connect to something with a prefix --
|
||||
|
||||
sender.connect("localhost:something"): ! ArgumentError: Error #2004: One of the parameters is invalid.
|
||||
sender.close(): ! ArgumentError: Error #2083: Close failed because the object is not connected.
|
||||
|
||||
-- end frame: Connect to something with a prefix --
|
||||
|
||||
|
||||
-- end test: Connect to something with a prefix --
|
||||
|
||||
|
||||
-- start test: Send to protected methods --
|
||||
|
||||
sender.send("channel", "send", *0 []): ! ArgumentError: Error #2004: One of the parameters is invalid.
|
||||
sender.send("channel", "connect", *0 []): ! ArgumentError: Error #2004: One of the parameters is invalid.
|
||||
sender.send("channel", "close", *0 []): ! ArgumentError: Error #2004: One of the parameters is invalid.
|
||||
sender.send("channel", "allowDomain", *0 []): ! ArgumentError: Error #2004: One of the parameters is invalid.
|
||||
sender.send("channel", "allowInsecureDomain", *0 []): ! ArgumentError: Error #2004: One of the parameters is invalid.
|
||||
sender.send("channel", "domain", *0 []): ! ArgumentError: Error #2004: One of the parameters is invalid.
|
||||
|
||||
-- end frame: Send to protected methods --
|
||||
|
||||
|
||||
-- end test: Send to protected methods --
|
||||
|
||||
|
||||
-- start test: Arguments are sent --
|
||||
|
||||
sender.send("elsewhere", "test", *0 [
|
||||
1
|
||||
"two"
|
||||
*1 Object {
|
||||
value = 3
|
||||
}
|
||||
*2 [
|
||||
4
|
||||
5
|
||||
]
|
||||
])
|
||||
|
||||
-- end frame: Arguments are sent --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
recvObject.test was called with 4 arguments
|
||||
*0 [
|
||||
1
|
||||
"two"
|
||||
*1 Object {
|
||||
value = 3
|
||||
}
|
||||
*2 [
|
||||
4
|
||||
5
|
||||
]
|
||||
]
|
||||
|
||||
-- end test: Arguments are sent --
|
||||
|
||||
|
||||
-- start test: Explicit host prefix --
|
||||
|
||||
sender.send("localhost:channel", "test", *0 [])
|
||||
sender.send("notlocalhost:elsewhere", "test", *0 [])
|
||||
|
||||
-- end frame: Explicit host prefix --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom.test was called with 0 argument
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
|
||||
-- end test: Explicit host prefix --
|
||||
|
||||
|
||||
-- start test: Underscores in names --
|
||||
|
||||
custom.close()
|
||||
custom.connect("_channel")
|
||||
sender.send("_channel", "test", *0 [])
|
||||
|
||||
-- end frame: Underscores in names --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom.test was called with 0 argument
|
||||
|
||||
-- end test: Underscores in names --
|
||||
|
||||
|
||||
-- start test: Underscores in name doesn't allow a prefix --
|
||||
|
||||
sender.send("localhost:channel", "test", *0 [])
|
||||
sender.send("localhost:_channel", "test", *0 [])
|
||||
|
||||
-- end frame: Underscores in name doesn't allow a prefix --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "error"
|
||||
target: sender
|
||||
|
||||
|
||||
-- end test: Underscores in name doesn't allow a prefix --
|
||||
|
||||
|
||||
-- start test: Case sensitivity --
|
||||
|
||||
sender.send("ELSEWhere", "test", *0 [])
|
||||
sender.send("LOCalHOST:ElseWhere", "test", *0 [])
|
||||
|
||||
-- end frame: Case sensitivity --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
recvObject.test was called with 0 argument
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
recvObject.test was called with 0 argument
|
||||
|
||||
-- end test: Case sensitivity --
|
||||
|
||||
|
||||
-- start test: Calling an AVM2 movie --
|
||||
|
||||
sender.send("avm2_child", "test", *0 [])
|
||||
|
||||
-- end frame: Calling an AVM2 movie --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm2_child.test was called with 0 argument
|
||||
|
||||
-- end test: Calling an AVM2 movie --
|
||||
|
||||
|
||||
-- start test: Calling an AVM1 movie --
|
||||
|
||||
sender.send("avm1_child", "test", *0 [])
|
||||
|
||||
-- end frame: Calling an AVM1 movie --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm1_child.test was called with 0 argument
|
||||
|
||||
-- end test: Calling an AVM1 movie --
|
||||
|
||||
|
||||
-- start test: Argument translations: primitives --
|
||||
|
||||
sender.send("avm1_child", "test", *0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
])
|
||||
sender.send("avm2_child", "test", *0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
])
|
||||
sender.send("_channel", "test", *0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
])
|
||||
|
||||
-- end frame: Argument translations: primitives --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm1_child.test was called with 7 arguments
|
||||
*0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm2_child.test was called with 7 arguments
|
||||
*0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom.test was called with 7 arguments
|
||||
*0 [
|
||||
1
|
||||
1.2
|
||||
true
|
||||
false
|
||||
"string"
|
||||
null
|
||||
undefined
|
||||
]
|
||||
|
||||
-- end test: Argument translations: primitives --
|
||||
|
||||
|
||||
-- start test: Argument translations: simple array --
|
||||
|
||||
sender.send("avm1_child", "test", *0 [
|
||||
*1 [
|
||||
1
|
||||
2
|
||||
"three"
|
||||
4.5
|
||||
NaN
|
||||
Infinity
|
||||
]
|
||||
])
|
||||
sender.send("avm2_child", "test", *0 [
|
||||
*1 [
|
||||
1
|
||||
2
|
||||
"three"
|
||||
4.5
|
||||
NaN
|
||||
Infinity
|
||||
]
|
||||
])
|
||||
sender.send("_channel", "test", *0 [
|
||||
*1 [
|
||||
1
|
||||
2
|
||||
"three"
|
||||
4.5
|
||||
NaN
|
||||
Infinity
|
||||
]
|
||||
])
|
||||
|
||||
-- end frame: Argument translations: simple array --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm1_child.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 [
|
||||
1
|
||||
2
|
||||
"three"
|
||||
4.5
|
||||
NaN
|
||||
Infinity
|
||||
]
|
||||
]
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm2_child.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 [
|
||||
1
|
||||
2
|
||||
"three"
|
||||
4.5
|
||||
NaN
|
||||
Infinity
|
||||
]
|
||||
]
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 [
|
||||
1
|
||||
2
|
||||
"three"
|
||||
4.5
|
||||
NaN
|
||||
Infinity
|
||||
]
|
||||
]
|
||||
|
||||
-- end test: Argument translations: simple array --
|
||||
|
||||
|
||||
-- start test: Argument translations: simple object --
|
||||
|
||||
sender.send("avm1_child", "test", *0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
])
|
||||
sender.send("avm2_child", "test", *0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
])
|
||||
sender.send("_channel", "test", *0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
-- end frame: Argument translations: simple object --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm1_child.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 object {
|
||||
nested = *2 object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm2_child.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
custom.test was called with 1 arguments
|
||||
*0 [
|
||||
*1 Object {
|
||||
nested = *2 Object {
|
||||
numbers = *3 [
|
||||
1
|
||||
2
|
||||
]
|
||||
string = "hello"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
-- end test: Argument translations: simple object --
|
||||
|
||||
|
||||
-- start test: AVM1 movie throws an error --
|
||||
|
||||
sender.send("avm1_child", "throwAnError", *0 [])
|
||||
|
||||
-- end frame: AVM1 movie throws an error --
|
||||
|
||||
sender received event StatusEvent.STATUS
|
||||
bubbles: false
|
||||
cancelable: false
|
||||
code: null
|
||||
currentTarget: sender
|
||||
level: "status"
|
||||
target: sender
|
||||
|
||||
avm1_child.throwAnError was called
|
||||
|
||||
-- end test: AVM1 movie throws an error --
|
||||
|
||||
Finished after 125 frames
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
num_ticks = 300 # Test may finish in less, but it'll `fscommand:exit` when it's done.
|
Loading…
Reference in New Issue