core: Add flashvars support - #312

This commit is contained in:
Nathan Adams 2020-10-11 20:35:28 +02:00 committed by Mike Welsh
parent a6b952e44e
commit 6d9155477c
9 changed files with 135 additions and 21 deletions

View File

@ -30,7 +30,7 @@ mod library;
pub mod loader; pub mod loader;
mod player; mod player;
mod prelude; mod prelude;
mod property_map; pub mod property_map;
pub mod shape_utils; pub mod shape_utils;
pub mod string_utils; pub mod string_utils;
pub mod tag_utils; pub mod tag_utils;

View File

@ -6,6 +6,7 @@ use crate::backend::navigator::OwnedFuture;
use crate::context::{ActionQueue, ActionType}; use crate::context::{ActionQueue, ActionType};
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject}; use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
use crate::player::{Player, NEWEST_PLAYER_VERSION}; use crate::player::{Player, NEWEST_PLAYER_VERSION};
use crate::property_map::PropertyMap;
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
use crate::vminterface::Instantiator; use crate::vminterface::Instantiator;
use crate::xml::XMLNode; use crate::xml::XMLNode;
@ -121,6 +122,7 @@ impl<'gc> LoadManager<'gc> {
player: Weak<Mutex<Player>>, player: Weak<Mutex<Player>>,
fetch: OwnedFuture<Vec<u8>, Error>, fetch: OwnedFuture<Vec<u8>, Error>,
url: String, url: String,
parameters: PropertyMap<String>,
) -> OwnedFuture<(), Error> { ) -> OwnedFuture<(), Error> {
let loader = Loader::RootMovie { self_handle: None }; let loader = Loader::RootMovie { self_handle: None };
let handle = self.add_loader(loader); let handle = self.add_loader(loader);
@ -128,7 +130,7 @@ impl<'gc> LoadManager<'gc> {
let loader = self.get_loader_mut(handle).unwrap(); let loader = self.get_loader_mut(handle).unwrap();
loader.introduce_loader_handle(handle); loader.introduce_loader_handle(handle);
loader.root_movie_loader(player, fetch, url) loader.root_movie_loader(player, fetch, url, parameters)
} }
/// Kick off a movie clip load. /// Kick off a movie clip load.
@ -358,6 +360,7 @@ impl<'gc> Loader<'gc> {
player: Weak<Mutex<Player>>, player: Weak<Mutex<Player>>,
fetch: OwnedFuture<Vec<u8>, Error>, fetch: OwnedFuture<Vec<u8>, Error>,
mut url: String, mut url: String,
parameters: PropertyMap<String>,
) -> OwnedFuture<(), Error> { ) -> OwnedFuture<(), Error> {
let _handle = match self { let _handle = match self {
Loader::RootMovie { self_handle, .. } => { Loader::RootMovie { self_handle, .. } => {
@ -383,7 +386,10 @@ impl<'gc> Loader<'gc> {
let data = (fetch.await) let data = (fetch.await)
.and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?))); .and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?)));
if let Ok((_length, movie)) = data { if let Ok((_length, mut movie)) = data {
for (key, value) in parameters.iter() {
movie.parameters_mut().insert(key, value.to_owned(), false);
}
player.lock().unwrap().set_root_movie(Arc::new(movie)); player.lock().unwrap().set_root_movie(Arc::new(movie));
Ok(()) Ok(())

View File

@ -2,7 +2,7 @@ use crate::avm1::activation::{Activation, ActivationIdentifier};
use crate::avm1::debug::VariableDumper; use crate::avm1::debug::VariableDumper;
use crate::avm1::globals::system::SystemProperties; use crate::avm1::globals::system::SystemProperties;
use crate::avm1::object::Object; use crate::avm1::object::Object;
use crate::avm1::{Avm1, AvmString, TObject, Timers, Value}; use crate::avm1::{Avm1, AvmString, ScriptObject, TObject, Timers, Value};
use crate::avm2::Avm2; use crate::avm2::Avm2;
use crate::backend::input::{InputBackend, MouseCursor}; use crate::backend::input::{InputBackend, MouseCursor};
use crate::backend::locale::LocaleBackend; use crate::backend::locale::LocaleBackend;
@ -19,6 +19,7 @@ use crate::external::{ExternalInterface, ExternalInterfaceProvider};
use crate::library::Library; use crate::library::Library;
use crate::loader::LoadManager; use crate::loader::LoadManager;
use crate::prelude::*; use crate::prelude::*;
use crate::property_map::PropertyMap;
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
use crate::transform::TransformStack; use crate::transform::TransformStack;
use crate::vminterface::Instantiator; use crate::vminterface::Instantiator;
@ -312,13 +313,14 @@ impl Player {
/// ///
/// This should not be called if a root movie fetch has already been kicked /// This should not be called if a root movie fetch has already been kicked
/// off. /// off.
pub fn fetch_root_movie(&mut self, movie_url: &str) { pub fn fetch_root_movie(&mut self, movie_url: &str, parameters: PropertyMap<String>) {
self.mutate_with_update_context(|context| { self.mutate_with_update_context(|context| {
let fetch = context.navigator.fetch(movie_url, RequestOptions::get()); let fetch = context.navigator.fetch(movie_url, RequestOptions::get());
let process = context.load_manager.load_root_movie( let process = context.load_manager.load_root_movie(
context.player.clone().unwrap(), context.player.clone().unwrap(),
fetch, fetch,
movie_url.to_string(), movie_url.to_string(),
parameters,
); );
context.navigator.spawn_future(process); context.navigator.spawn_future(process);
@ -351,7 +353,21 @@ impl Player {
MovieClip::from_movie(context.gc_context, context.swf.clone()).into(); MovieClip::from_movie(context.gc_context, context.swf.clone()).into();
root.set_depth(context.gc_context, 0); root.set_depth(context.gc_context, 0);
root.post_instantiation(context, root, None, Instantiator::Movie, false); let flashvars = if !context.swf.parameters().is_empty() {
let object = ScriptObject::object(context.gc_context, None);
for (key, value) in context.swf.parameters().iter() {
object.define_value(
context.gc_context,
key,
AvmString::new(context.gc_context, value).into(),
EnumSet::empty(),
);
}
Some(object.into())
} else {
None
};
root.post_instantiation(context, root, flashvars, Instantiator::Movie, false);
root.set_name(context.gc_context, ""); root.set_name(context.gc_context, "");
context.levels.insert(0, root); context.levels.insert(0, root);

View File

@ -13,7 +13,7 @@ use std::hash::{Hash, Hasher};
type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>; type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;
/// A map from property names to values. /// A map from property names to values.
#[derive(Debug)] #[derive(Default, Clone, Debug)]
pub struct PropertyMap<V>(FnvIndexMap<PropertyName, V>); pub struct PropertyMap<V>(FnvIndexMap<PropertyName, V>);
impl<V> PropertyMap<V> { impl<V> PropertyMap<V> {
@ -107,6 +107,10 @@ impl<V> PropertyMap<V> {
self.0.shift_remove(&CaseInsensitiveStr(key)) self.0.shift_remove(&CaseInsensitiveStr(key))
} }
} }
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
} }
unsafe impl<V: Collect> Collect for PropertyMap<V> { unsafe impl<V: Collect> Collect for PropertyMap<V> {

View File

@ -1,4 +1,5 @@
use crate::backend::navigator::url_from_relative_path; use crate::backend::navigator::url_from_relative_path;
use crate::property_map::PropertyMap;
use gc_arena::Collect; use gc_arena::Collect;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
@ -21,6 +22,9 @@ pub struct SwfMovie {
/// The URL the SWF was downloaded from. /// The URL the SWF was downloaded from.
url: Option<String>, url: Option<String>,
/// Any parameters provided when loading this movie (also known as 'flashvars')
parameters: PropertyMap<String>,
} }
impl SwfMovie { impl SwfMovie {
@ -36,6 +40,7 @@ impl SwfMovie {
}, },
data: vec![], data: vec![],
url: None, url: None,
parameters: PropertyMap::new(),
} }
} }
@ -49,6 +54,7 @@ impl SwfMovie {
header: self.header.clone(), header: self.header.clone(),
data, data,
url: source.url.clone(), url: source.url.clone(),
parameters: source.parameters.clone(),
} }
} }
@ -90,7 +96,12 @@ impl SwfMovie {
data data
}; };
Ok(Self { header, data, url }) Ok(Self {
header,
data,
url,
parameters: PropertyMap::new(),
})
} }
pub fn header(&self) -> &Header { pub fn header(&self) -> &Header {
@ -118,6 +129,14 @@ impl SwfMovie {
pub fn url(&self) -> Option<&str> { pub fn url(&self) -> Option<&str> {
self.url.as_deref() self.url.as_deref()
} }
pub fn parameters(&self) -> &PropertyMap<String> {
&self.parameters
}
pub fn parameters_mut(&mut self) -> &mut PropertyMap<String> {
&mut self.parameters
}
} }
/// A shared-ownership reference to some portion of an SWF datastream. /// A shared-ownership reference to some portion of an SWF datastream.

View File

@ -17,7 +17,11 @@ module.exports = class RuffleEmbed extends RufflePlayer {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.stream_swf_url(this.attributes.src.value); let parameters = null;
if (this.attributes.flashvars) {
parameters = this.attributes.flashvars.value;
}
this.stream_swf_url(this.attributes.src.value, parameters);
} }
get src() { get src() {
@ -34,9 +38,12 @@ module.exports = class RuffleEmbed extends RufflePlayer {
attributeChangedCallback(name, oldValue, newValue) { attributeChangedCallback(name, oldValue, newValue) {
super.attributeChangedCallback(name, oldValue, newValue); super.attributeChangedCallback(name, oldValue, newValue);
console.log(name + " " + oldValue + " " + newValue);
if (this.isConnected && name === "src") { if (this.isConnected && name === "src") {
this.stream_swf_url(this.attributes.src.value); let parameters = null;
if (this.attributes.flashvars) {
parameters = this.attributes.flashvars.value;
}
this.stream_swf_url(this.attributes.src.value, parameters);
} }
} }

View File

@ -32,6 +32,16 @@ module.exports = class RuffleObject extends RufflePlayer {
url = this.params.movie; url = this.params.movie;
} }
let parameters = RuffleObject.find_case_insensitive(
this.params,
"flashvars",
RuffleObject.find_case_insensitive(
this.attributes,
"flashvars",
null
)
);
if (url) { if (url) {
this.allow_script_access = this.allow_script_access =
allowScriptAccess && allowScriptAccess &&
@ -41,7 +51,7 @@ module.exports = class RuffleObject extends RufflePlayer {
new URL(url, window.location.href).origin)); new URL(url, window.location.href).origin));
//Kick off the SWF download. //Kick off the SWF download.
this.stream_swf_url(url); this.stream_swf_url(url, parameters);
} }
} }

View File

@ -10,6 +10,23 @@ exports.FLASH_ACTIVEX_CLASSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
const DIMENSION_REGEX = /^\s*(\d+(\.\d+)?(%)?)/; const DIMENSION_REGEX = /^\s*(\d+(\.\d+)?(%)?)/;
function sanitize_parameters(parameters) {
if (parameters === null || parameters === undefined) {
return {};
}
if (!(parameters instanceof URLSearchParams)) {
parameters = new URLSearchParams(parameters);
}
const output = {};
for (const [key, value] of parameters) {
// Every value must be type of string
output[key] = value.toString();
}
return output;
}
exports.RufflePlayer = class RufflePlayer extends HTMLElement { exports.RufflePlayer = class RufflePlayer extends HTMLElement {
constructor(...args) { constructor(...args) {
let self = super(...args); let self = super(...args);
@ -152,15 +169,18 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
* being loaded, or any errors that happen loading it. * being loaded, or any errors that happen loading it.
* *
* @param {String} url The URL to stream. * @param {String} url The URL to stream.
* @param {URLSearchParams|String|Object} [parameters] The parameters (also known as "flashvars") to load the movie with.
* If it's a string, it will be decoded into an object.
* If it's an object, every key and value must be a String.
*/ */
async stream_swf_url(url) { async stream_swf_url(url, parameters) {
//TODO: Actually stream files... //TODO: Actually stream files...
try { try {
if (this.isConnected && !this.is_unused_fallback_object()) { if (this.isConnected && !this.is_unused_fallback_object()) {
console.log("Loading SWF file " + url); console.log("Loading SWF file " + url);
await this.ensure_fresh_instance(); await this.ensure_fresh_instance();
this.instance.stream_from(url); this.instance.stream_from(url, sanitize_parameters(parameters));
if (this.play_button) { if (this.play_button) {
this.play_button.style.display = "block"; this.play_button.style.display = "block";
@ -205,14 +225,20 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
* the movie being loaded. * the movie being loaded.
* *
* @param {String} url The URL to stream. * @param {String} url The URL to stream.
* @param {URLSearchParams|String|Object} [parameters] The parameters (also known as "flashvars") to load the movie with.
* If it's a string, it will be decoded into an object.
* If it's an object, every key and value must be a String.
*/ */
async play_swf_data(data) { async play_swf_data(data, parameters) {
try { try {
if (this.isConnected && !this.is_unused_fallback_object()) { if (this.isConnected && !this.is_unused_fallback_object()) {
console.log("Got SWF data"); console.log("Got SWF data");
await this.ensure_fresh_instance(); await this.ensure_fresh_instance();
this.instance.load_data(new Uint8Array(data)); this.instance.load_data(
new Uint8Array(data),
sanitize_parameters(parameters)
);
console.log("New Ruffle instance created."); console.log("New Ruffle instance created.");
if (this.play_button) { if (this.play_button) {

View File

@ -24,6 +24,7 @@ use ruffle_core::events::MouseWheelDelta;
use ruffle_core::external::{ use ruffle_core::external::{
ExternalInterfaceMethod, ExternalInterfaceProvider, Value as ExternalValue, Value, ExternalInterfaceMethod, ExternalInterfaceProvider, Value as ExternalValue, Value,
}; };
use ruffle_core::property_map::PropertyMap;
use ruffle_core::tag_utils::SwfMovie; use ruffle_core::tag_utils::SwfMovie;
use ruffle_core::PlayerEvent; use ruffle_core::PlayerEvent;
use ruffle_web_common::JsResult; use ruffle_web_common::JsResult;
@ -124,22 +125,32 @@ impl Ruffle {
/// Stream an arbitrary movie file from (presumably) the Internet. /// Stream an arbitrary movie file from (presumably) the Internet.
/// ///
/// This method should only be called once per player. /// This method should only be called once per player.
pub fn stream_from(&mut self, movie_url: &str) { pub fn stream_from(&mut self, movie_url: &str, parameters: &JsValue) -> Result<(), JsValue> {
INSTANCES.with(|instances| { INSTANCES.with(|instances| {
let instances = instances.borrow(); let instances = instances.borrow();
let instance = instances.get(self.0).unwrap().borrow(); let instance = instances.get(self.0).unwrap().borrow();
instance.core.lock().unwrap().fetch_root_movie(movie_url); let mut parameters_to_load = PropertyMap::new();
}); populate_movie_parameters(&parameters, &mut parameters_to_load)?;
instance
.core
.lock()
.unwrap()
.fetch_root_movie(movie_url, parameters_to_load);
Ok(())
})
} }
/// Play an arbitrary movie on this instance. /// Play an arbitrary movie on this instance.
/// ///
/// This method should only be called once per player. /// This method should only be called once per player.
pub fn load_data(&mut self, swf_data: Uint8Array) -> Result<(), JsValue> { pub fn load_data(&mut self, swf_data: Uint8Array, parameters: &JsValue) -> Result<(), JsValue> {
let movie = Arc::new({ let movie = Arc::new({
let mut data = vec![0; swf_data.length() as usize]; let mut data = vec![0; swf_data.length() as usize];
swf_data.copy_to(&mut data[..]); swf_data.copy_to(&mut data[..]);
SwfMovie::from_data(&data, None).map_err(|e| format!("Error loading movie: {}", e))? let mut movie = SwfMovie::from_data(&data, None)
.map_err(|e| format!("Error loading movie: {}", e))?;
populate_movie_parameters(&parameters, movie.parameters_mut())?;
movie
}); });
INSTANCES.with(|instances| { INSTANCES.with(|instances| {
@ -899,3 +910,18 @@ pub fn set_panic_handler() {
})); }));
}); });
} }
fn populate_movie_parameters(
input: &JsValue,
output: &mut PropertyMap<String>,
) -> Result<(), JsValue> {
let keys = js_sys::Reflect::own_keys(input)?;
for key in keys.values() {
let key = key?;
let value = js_sys::Reflect::get(input, &key)?;
if let (Some(key), Some(value)) = (key.as_string(), value.as_string()) {
output.insert(&key, value, false);
}
}
Ok(())
}