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;
mod player;
mod prelude;
mod property_map;
pub mod property_map;
pub mod shape_utils;
pub mod string_utils;
pub mod tag_utils;

View File

@ -6,6 +6,7 @@ use crate::backend::navigator::OwnedFuture;
use crate::context::{ActionQueue, ActionType};
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
use crate::player::{Player, NEWEST_PLAYER_VERSION};
use crate::property_map::PropertyMap;
use crate::tag_utils::SwfMovie;
use crate::vminterface::Instantiator;
use crate::xml::XMLNode;
@ -121,6 +122,7 @@ impl<'gc> LoadManager<'gc> {
player: Weak<Mutex<Player>>,
fetch: OwnedFuture<Vec<u8>, Error>,
url: String,
parameters: PropertyMap<String>,
) -> OwnedFuture<(), Error> {
let loader = Loader::RootMovie { self_handle: None };
let handle = self.add_loader(loader);
@ -128,7 +130,7 @@ impl<'gc> LoadManager<'gc> {
let loader = self.get_loader_mut(handle).unwrap();
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.
@ -358,6 +360,7 @@ impl<'gc> Loader<'gc> {
player: Weak<Mutex<Player>>,
fetch: OwnedFuture<Vec<u8>, Error>,
mut url: String,
parameters: PropertyMap<String>,
) -> OwnedFuture<(), Error> {
let _handle = match self {
Loader::RootMovie { self_handle, .. } => {
@ -383,7 +386,10 @@ impl<'gc> Loader<'gc> {
let data = (fetch.await)
.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));
Ok(())

View File

@ -2,7 +2,7 @@ use crate::avm1::activation::{Activation, ActivationIdentifier};
use crate::avm1::debug::VariableDumper;
use crate::avm1::globals::system::SystemProperties;
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::backend::input::{InputBackend, MouseCursor};
use crate::backend::locale::LocaleBackend;
@ -19,6 +19,7 @@ use crate::external::{ExternalInterface, ExternalInterfaceProvider};
use crate::library::Library;
use crate::loader::LoadManager;
use crate::prelude::*;
use crate::property_map::PropertyMap;
use crate::tag_utils::SwfMovie;
use crate::transform::TransformStack;
use crate::vminterface::Instantiator;
@ -312,13 +313,14 @@ impl Player {
///
/// This should not be called if a root movie fetch has already been kicked
/// 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| {
let fetch = context.navigator.fetch(movie_url, RequestOptions::get());
let process = context.load_manager.load_root_movie(
context.player.clone().unwrap(),
fetch,
movie_url.to_string(),
parameters,
);
context.navigator.spawn_future(process);
@ -351,7 +353,21 @@ impl Player {
MovieClip::from_movie(context.gc_context, context.swf.clone()).into();
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, "");
context.levels.insert(0, root);

View File

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

View File

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

View File

@ -17,7 +17,11 @@ module.exports = class RuffleEmbed extends RufflePlayer {
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() {
@ -34,9 +38,12 @@ module.exports = class RuffleEmbed extends RufflePlayer {
attributeChangedCallback(name, oldValue, newValue) {
super.attributeChangedCallback(name, oldValue, newValue);
console.log(name + " " + oldValue + " " + newValue);
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;
}
let parameters = RuffleObject.find_case_insensitive(
this.params,
"flashvars",
RuffleObject.find_case_insensitive(
this.attributes,
"flashvars",
null
)
);
if (url) {
this.allow_script_access =
allowScriptAccess &&
@ -41,7 +51,7 @@ module.exports = class RuffleObject extends RufflePlayer {
new URL(url, window.location.href).origin));
//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+)?(%)?)/;
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 {
constructor(...args) {
let self = super(...args);
@ -152,15 +169,18 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
* being loaded, or any errors that happen loading it.
*
* @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...
try {
if (this.isConnected && !this.is_unused_fallback_object()) {
console.log("Loading SWF file " + url);
await this.ensure_fresh_instance();
this.instance.stream_from(url);
this.instance.stream_from(url, sanitize_parameters(parameters));
if (this.play_button) {
this.play_button.style.display = "block";
@ -205,14 +225,20 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
* the movie being loaded.
*
* @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 {
if (this.isConnected && !this.is_unused_fallback_object()) {
console.log("Got SWF data");
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.");
if (this.play_button) {

View File

@ -24,6 +24,7 @@ use ruffle_core::events::MouseWheelDelta;
use ruffle_core::external::{
ExternalInterfaceMethod, ExternalInterfaceProvider, Value as ExternalValue, Value,
};
use ruffle_core::property_map::PropertyMap;
use ruffle_core::tag_utils::SwfMovie;
use ruffle_core::PlayerEvent;
use ruffle_web_common::JsResult;
@ -124,22 +125,32 @@ impl Ruffle {
/// Stream an arbitrary movie file from (presumably) the Internet.
///
/// 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| {
let instances = instances.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.
///
/// 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 mut data = vec![0; swf_data.length() as usize];
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| {
@ -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(())
}