Add these methods that will explicilty coerce a value to an int,
following the wrapping behavior in the ECMAScript specs (ToInt32,
ToUInt32, ToUInt16).
This also fixed an off-by-one error for negative numbers in the
previous implementation.
These will call `valueOf` if necessary. AVM code that requires an
integer will probably use one of these (`coerce_to_i32` usually).
When setting a variable in a function-local scope, if that variable
has not been defined in the function scope, it should be defined in
the executing movieclip's scope. Previously it would get defined
in the function's scope. Changed Scope::overwrite to Scope::set,
and modified the behavior to stop traversing and define the value
when it hits a movie clip Target scope.
Also, modified With scopes to properly add onto the end of the scope
chain.
I'm not entirely sure how to test this one - the list of errors that Flash kicks out for XML and the list of errors that `quick_xml` kicks out don't line up at all; so I just ensured that any error is a negative number (currently the one for OOM errors) and stuck whatever errors *did* match up together.
Consequently I don't know entirely *how* to write tests for this.
`idMap` is a strange property; it's only populated with nodes which had a given `id` *at the time of parsing*, and said nodes continue to be referenced even if the node is removed from the document. I have yet to find a way by which nodes can be deleted from `idMap`.
It also takes expandos, so this has to be a new retained object on the XML document. I originally considered not creating *another* `Object` impl and populating a regular `ScriptObject` with nodes, but that meant we couldn't lazy-instantiate their AVM1 side counterparts. Boo. :/
There is a bug in `quick_xml` - or at least, I *think* it's a bug - where the `unescaped` method of `BytesStart` yields a bunch of attributes if you have slightly invalid crap in your tag like `xmlns:`. To work around it, I turned off unescaping; we're instead using `name` and ignoring unescaping. This will probably fail somewhere.
I can't write proper tests for this because our underlying XML parsing technology doesn't let us do what AS2 XML does: just copy the first xml tag out of the document and into a string. No, seriously, anything at the start of the parsed XML that starts with `<?xml` and ends with `?>` gets copied into Flash's moral equivalent of `xml::Document` as a string. We parse the whole thing, which is wrong, so we'll need to additionally retain the original xmldecl string in order to pass the test I wrote for this.
Fixes a bug where new XML("<node />").childNode[0].parentNode did NOT refer to the overall document object, but to a phantom text node.
This is because the swap operation used to construct an XMLObject's node in-place was happening AFTER parsing, which means that referents already existed to the temporary XMLNode created by XMLObject::new. swap is not to be called after tree structure has been created; it does not update referents to the swapped nodes.
In the future I should examine the implications of explicitly reconstructing already existing nodes, e.g. through XML.apply(some_xml). Right now, the existing node will be swapped with a new one, and two nodes will exist pointing to the same script object, which is a huge problem with our overall design. We should, at the very least, disassociate swapped nodes from their script object, just in case they still have referents.
Ideally, we wouldn't have to swap nodes, but to avoid a swap, I'd have to instead have a second layer of indirection just to hold a rewritable pointer that every XMLObject points to. This isn't really worth it unless I HAVE to do it, so I'm not going to do it.
ActionAnd, ActionOr, and ActionNot were incorrectly comparing
to 0. This only works for SWF<4. Now they all go through the
Value::as_bool method to handle version specific behavior.
Value::from_bool_v1 was also renamed to Value::from_bool.
Functions now store their base clip (the code that contains the
executing bytecode). This is because `GotoFrame` and other actions
will execute on the clip the bytecode exists on, not on `this`.
(Note that `this.gotoAndStop` uses `GetMember` actions, which
worked correctly).
`Activation` now stores `target_clip`, and `Avm1::target_clip` and
`target_clip_or_root` grab this from the current stack frame.
Renamed `start_clip` to `base_clip` to match Flash conventions.
Removed `active_clip` as this was superfluous. Now you can use
`Avm1::target_clip_or_root`.
`UpdateContext` no longer contains `target_clip` etc.
This is a very rough stub out of Stage.width and height to get
basic V-cams to start functioning.
TODO: Implement the different stage scaling modes. We will probably
want to add a "Stage" display object to handle this.
This requires some subclassing nonsense to be able to smuggle a self-reference into `SuperObject`s. When successfully smuggled, all calls to `call` will be invoked with the `super` object as `this`. This allows constructor chaining to work.
Note that not all `Object` trait methods are implemented on `SuperObject`, so things like `delete this.x` in super constructors will randomly fail. This should be fixed.
We implement `super` by way of a new `Object` impl which wraps arbitrary objects with a modified prototype chain. Specifically, the lowest layer of the prototype chain is omitted. This new `SuperObject` script is composable: a chain of two `SuperObject`s will go two levels of inheritance upwards while still maintaining non-prototype property access.
Add DisplayObject::slash_path to get the Flash 4-style slash path
to the clip. This fixes the tellTarget regression test and removes
the superfluous `target_path` from `UpdateContext`.
This was discovered almost by accident: @Dinnerbone noticed that `_global == null`, and surmised that `valueOf` was the culprit. However, this doesn't really make sense: `_global` is a bare object, so it shouldn't have a `valueOf` (and in practice, it doesn't).
The ultimate cause of such an odd comparison is as such:
1. Flash coerces the `_global` object to a numerical primitive by calling `valueOf`.
2. `_global.valueOf` is undefined. Flash handles calls to any uncallable value by literally just having it return `undefined`. In other words, all values are implicitly callable as empty functions.
3. `undefined` is then compared to `null`. These two values *are* equal under abstract equality (`==`). Hence, `_global == null`.
For comparison, modern ECMAScript engines throw errors on calls to uncallable values; and won't attempt to use an invalid `valueOf` to coerce objects. So none of this applies to, say, standard JavaScript in your browser.
In Flash, this also at least claims to halt ActionScript execution on the movie. No such implementation of AVM poisoning currently exists in Ruffle, primarily because it's unclear what gets poisoned and implementing some of these options isn't yet possible:
1. AVM (e.g. all movies) - we would need to make the AVM fail silently in this case. This is the most straightforward way to poison the movie, but I'm not sure if this is how Flash actually does it, or if it poisons...
2. Movie - the current structure of movies is incompatible with adding arbitrary data to them. We need to merge `moviefetch` in before we can attach data to loaded movies.
3. MovieClip - this would also be implementable but has problems. How do child MovieClips know that their parent has been poisoned, or vice versa? What if a movie clip is loaded from one movie and moved into another?
As a result, I have decided to hold off on implementing recursion poisoning until I know where it's supposed to go and how to implement that.
While I don't expect every host object to implement it correctly, this also gets rid of a lot of unnecessary `unwrap` calls that would allow a poorly-written Flash file to kill the interpreter.
Note that host objects that do so will *not* have access to their standard representation from within member functions - you will need to extend the interface to accomodate for them. This is due to long-standing limitations with type IDs and downcasting with types that bear lifetimes - it's entirely an unsafe operation and exposing such a facility to safe Rust is unsound. However, this will at least let us separate out several things from ScriptObject that don't need to be there for the time being.
`Object::function` now returns a pre-allocated function object. You may supply it an explicit prototype to have it linked into the function object (which is why we have to return a cell).
The previous behavior had an oversight: if you tried to set a variable with the same name as an in-scope property, it would always try to overwrite that property. This can fail silently and doesn't match with Flash Player behavior. Now, an attempt to overwrite a read-only property is instead correctly rejected so that it can be defined in local scope.
This type explicitly signals if an immediate value is to be returned, if a value is to be returned on the stack, or if no return value is to be generated. Holders of a `ReturnValue` can also use `and_then` to schedule a `StackContinuation` to be executed when and if that value is ready.
`StackContinuations` now yield `ReturnValues` as well, so they have a moderate level of composability. For example, if you need to get a property from an object and push it on the stack, you can return the result of calling `get` directly and the machinery ensures it eventually gets there.
This involved yet another macro, `and_then!`, to avoid a ridiculous amount of duplicate code. It calls a continuation whenever it's value is ready, even if the value resolved on the Rust stack.
`locals_into_form_values` does not currently support this. It skips any property that does not resolve on the Rust stack. Future work is required to resolve this.
This involves the use of a "stack continuation" system. Due to previous lifetime issues with using closures directly (see `8ea6c6234dba925ec5fbc61502627fb62b05916c`), we instead use a macro that constructs a `Collect`able type holding the things the continuation needs to continue working with. The syntax is largely similar to Rust closures but with the addition of an explicit list of bound variables, all of which must be `Collect`.
Gotos now goto the specified frames immediately as opposed to
queuing. Actions on the new frame will still be queued,
and are executed after any current actions are completed.
This has the side effect of letting us remove the `Option` on register_count since setting this to `0` is equivalent now. Furthermore, we can skip an allocation if a function requests no registers.
On SWF5, the SWF version of the callee depends on it's this parameter. Calling it as a function rather than a method downgrades the callee. SWF6+ use the callee's inherent SWF version and do not allow changing the SWF version like this.
* Remove clone calls from Copy objects
* Used Iterator::cloned() instead of manually cloning
* Pass swf::Function into AvmFunction2::new()
* Use action_clone_sprite
1. We no longer implicitly return Undefined unless we're specifically returning from a function (this also keeps actions from filling the stack with Undefined)
2. With scopes are now always inserted behind the current set of locals rather than overriding them
3. `ActionSubtract` now subtracts (instead of adds)