This is currently somewhat buggy, `homestuck_02791.swf` stops at 12% for some reason. I tried handing it both compressed and uncompressed lengths with no luck.
This requires estimating a count of bytes loaded of the original compressed stream, even though we only have uncompressed byte totals at this point. I instead rescale the uncompressed bytes by the compressed count to roughly estimate what would be the `bytesLoaded` had we been actually streaming bytes in and preloading them synchronously.
Backends that need synchronous preload behavior now explicitly ask for it as follows:
* `tests` - repeatedly call `preload` in a loop with an exhausted execution limit to stress-test the chunked preload
* `exporter`, `scanner` - synchronous/unlimited preload to match prior behavior
These may change in the future.
Actions are abstract; here we're using it to count bytes loaded (as a proxy for execution time). AVM code could potentially be adapted to count operations run instead.
An "empty clip" is any clip created by the `new` or `new_with_avm2` function, intended for dynamically-created movie clips with no association to a movie symbol.
The preload frame counting logic starts from one and continues to the end of the file, which results in a completely preloaded movie having one more frame "loaded" than there is in the file. This fixes that.
As usual, also bump its helper crates (`js-sys`, `web-sys` and
`wasm-bindgen-futures`) to the latest versions.
Due to https://github.com/rustwasm/wasm-bindgen/pull/3031, use the
`serde-wasm-bindgen` crate as a replacement to the deprecated
`JsValue::from_serde` function.
Previously, we would only use mergeAlpha if alphaBitmapData
and alphaPoint. However, mergeAlpha can be used even when
those parameters are null.
Some of the AVM1 argument handling was also incorrect - I've fixed
it, and extended the existing test with the output-based test
added for AVM2.
In several cases, the current code seems preferable to the
code required by `clippy::bool_to_int_with_if`. Let's suppress
this for now to get the build passing, and decide later if this
is something that we want to enable.
Previously, we would always use a transparent background,
even if the BitmapData is not transparent. This would normally
be corrected on the next frame when we copied the pixels to the
CPU. However, if an SWF ran `BitmapData.draw` on every frame,
this would never be corrected.
Our AVM2 `SharedObject` support is now *almost* equivalent
to our avm1 `SharedObject` support. We implement serialization
and deserialization for primitives, arrays, and `Object` instances
with local properties. We also implement serialization for `Date`,
but not `Xml` (since our AVM2 `Xml` class is just a stub at the moment).
This is enough to make 'This is the only level too' save level
progress to disk.
Currently, we always serialize to AMF3. When we implement
the `defaultObjectEncoding` and `objectEncoding`, we'll need
to adjust this.
An AVM2 movie can repeatedly remove and add a DisplayObject from/to
a parent. This was causing SolarMax to stop working after advancing
to the next level.
We now check if a BitmapData has been disposed by checking
for a zero width or height (which cannot happen otherwise).
As a result, we no longer need the 'disposed' field on the AVM1
BitmapData object.
* avm2: Implement `BitmapData.draw` for `wgpu` backend
This method requires us to have the ability to render directly to a
texture. Fortunately, the `wgpu` backend already supports this in
the form of `TextureTarget`. However, the rendering code required
some refactoring in order to avoid creating duplicate `wgpu` resources.
The current implementation blocks on copying the pixels back
from the GPU to the CPU, so that we can immediately set them in
the Ruffle `BitmapData`. This is likely very inefficient, but will
work for a first implementation.
In the future, we could explore allowing the CPU image data and GPU
texture to be out of sync, and only synchronized when explicitly
necessary (e.g. on `getPixel` or `setPixel` calls).
* Rename `with_offscreen_backend` to `render_offscreen` and use Bitmap
* Don't panic when backend doesn't implement `render_offscreen`
We already have `&mut self` available whenever we write to it,
and we never made use of the `Clone` impl. As far as I can tell,
we don't have any unimplemented features that would require a `GcCell`.
The stub implementation was breaking code that relied on being
able to set a value for 'mask' and then retrieve it
(which used to work on a dynamic class like `MovieClip`).
Change `set_matrix` and `set_color_transform` to accept owned structs,
instead of references. This allows callers that already have an owned
struct to pass it directly, thus saving an unnecessary borrow + clone.
This also aligns with other methods, such as `set_sound_transform`,
which currently accepts an owned struct.
The existing `Object` enum representation is problematic for inherited
native objects, since "regular" `ScriptObject`s cannot be turned into
native objects, but rather a completely new native object needs to be
created. `TObject::create_bare_object` is an attempt to aid this
situation, but it works only for `ActionExtends` inheritance, and not
when the user manually wires up `prototype`/`__proto__` (#701).
In Flash, it seems like derived constructors initially have a "regular"
`this` object. But once the `super()` constructor is invoked, the same
`this` object becomes a native object.
To allow this in Ruffle, introduce a new `NativeObject` enum, and
store it as a member in `ScriptObject`. For a start, move `TextFormatObject`
from the `Object` enum to `NativeObject`. The plan is to gradually
move all `Object` enum variants to `NativeObject`, except for `ScriptObject`.
For now, I've left 'dropTarget' unimplemented - unlike in
AVM1, the drop target can be non-interactive objects like `Shape`,
so we'll need additional refactoring to implement it.
This allows 'This is the only level too' to be playable
It was only used to make structs `#[derive(gc_arena::Collect)]`, and
generally it doesn't make much sense that `render` needs to be GC-aware.
So instead annotate `render` fields in `core` with `#[collect(require_static)]`.
This brings us closer to matching the Flash Player
enumeration behavior. Unfortunately, the precise enumeration
order for ScriptObject properties depends on the precise
order in the internal avmplus hashmap. This order is deterministic,
but adding/removing a property effectively randomizes it. Hopefully
there aren't any SWFS that depend on the *exact* order.
Handle removed clips inline in `Avm1::run_frame`, such that
`DisplayObject::prev_avm1_clip` is no longer used. Thus it can be
removed in a follow-up commit.
When we run a 'goto' where the initial and target frame are the same,
we need to skip triggering any sounds in the target frame.
Some games like 'This is the only level too' rely on this behavior:
they repeatedly run 'movieClip.gotoAndStop(current_frame_id)',
where 'current_frame_id' is the id of a frame that starts playing
a sound. Without this change, the sound will restart every frame
intead of playing exactly once.
We previously used 'coerce_to_object', which produced
an error with `Value::Null`. Instead, we can just ues
`value.as_type_of`, which will correctly handle `null`
The 'charCode' and 'keyCode' properties are now implemented
on `KeyboardEvent`
The input injection code we use does not support keyboard events,
so we can't yet write a regression test for this. However,
both 'You need to burn the rope' and 'This is the Only Level TOO'
now properly handle keyboard events with this PR.
* avm2: Implement call stack
* avm2: Class traits should have a special prefix
* avm2: Stack tracebacks should also contain error message
* avm2: Move method naming to Executable
* avm2: Handle getter and setter methods in tracebacks
* chore: Formatting
* chore: Add comments
* avm2: Make full_name write to a string, instead of creating a new one
* core: Make GcArena publicly accessible
* core: Add Deref impl for Either type
* desktop: Add AVM2 call stack to panic message
* avm2: Prefix native methods with a `/`
* chore: Appease clippy
* avm2: Check if method actually contains bytecode instead of unwrapping
* web: Add AVM2 stack trace to panic message
* chore: Formatting
* chore: Clippy
* avm2: Fix stack traces for free standing functions
* core: Remove global data from context
* core: Rename GcGlobalData to GcCallstack
* core: Introduce StaticCallstack, make GcArena private again
Co-authored-by: Adrian Wielgosik <4729533+adrian17@users.noreply.github.com>
Declare `NaN`, `Infinity` and `undefined` in ActionScript, similarly
to how `avmplus` does in its `actionscript.lang.as`.
Note that `null` is only removed, without an ActionScript declaration,
as it seems like `avmplus` neither declares it. Probably `null` is
only usable as a compile-time constant.
Extract `swf::Reader::read_do_abc()` which, as the name suggests,
reads a `DoAbc` tag, and use it before calling to `Avm2::load_abc`.
Finally, introduce `DoAbcFlag` using `bitflags`.
This greatly simplifies the ABC loading code.
This is in keeping with the whole idea of a "recursive frame": gotos run the entire frame lifecycle on the target clip, including broadcasts for `frameConstructed` and `exitFrame`.
We still retain the queue system as events are fired at removal time, and those events can trigger more gotos. If such a goto happens, AS3 code will hit a clip still in the old state rather than an inconsistent one. I don't have test coverage for this exact scenario just yet.
The rationale for the catch-up logic is as follows:
* We must always enter-frame and construct objects, even if those respective display events haven't happened yet.
* Display objects created in event handlers still need to run catchup phases, otherwise they will tag-stream desync
* Frame scripts are never triggered by catchup phases
* `exit_frame` is not a catchup phase as it is *only* an event broadcast currently
Normally a function closures also closes around its base clip.
If the base clip is removed, and then the function is executed, the
base clip then defaults to `this`.
However, Ruffle was incorrectly using the wrong base clip when
loading the `_root` and `_parent` registers in this case.
Fixes#5645.
This PR implements the `Loader.load` method, as well as
the associated `LoaderInfo` properties and events.
We can now load in an external AVM2 SWf: it will be added
as a child of `Loader` object, and will render properly
to the screen.
Limitations:
* The only supported `URLRequest` property is `url`
* `LoaderContext` is not supported at all - we always use the default
behavior
* Only `Loader.load` is implemented - we do not yet support unloading.
* We fire a plain 'Event' for the 'progress' event, instead of using
the (not yet implemented) 'ProgressEvent' class
The main changes in this PR are:
* The AVM2 `Loader` class now has an associated display object,
`LoaderDisplay`. This is basically a stub, and just renders
its single child (if it exists).
* `LoaderStream::Stage` is renamed to `LoaderStream::NotYetLoaded`.
This is used for both the `Stage` and an 'uninitialized'
`Loader.contentLoaderInfo`. In both cases, certain properties throw
errors, while others return actual values.
* The rust `Loader` manager now handles both AVM1 and AVM2 movie loads.
Previously, the viewport height and width were stored in
both `Stage` and the `RenderBackend`. Any changes to the viewport
dimensions (e.g. due to window resizing) needed to be updated in both
places to keep our handling of the viewport consistent.
This PR adds a new `ViewportDimensions` type, which holds the
width, height, and scale factor. It is stored inside the
`RenderBackend` impl, and is retrieved using the newly added
method `RenderBackend.get_viewport_dimensions`. After a `Player`
has been constructed, any code that needes access to the viewport
dimensions will ultimate go through this method.
Unfortunately, `Stage` needs to use the viewport dimensions
in `build_matrices`. Therefore, any code modifying the viewport
dimensions should go through `player.set_viewport_dimensions`,
which ensures that the stage matrices are rebuilt after the render
backend is updated.
When doing mouse picking, interactive children were considered
before all non-interactives, which could cause an `_droptarget` to
be set to an underlying movieclip even if a shape occluded it.
Now consider all children in render order so that the top-most
shape will capture the mouse input.
If we try to go to a frame that doesn't exist, or hasn't been loaded yet, we will stop on the last available frame, but skip any tags that would have run there. This is technically a desync, but it hasn't caused any problems so far as any further timeline interaction would trigger a rewind (which isn't affected by desyncs).
Of course, now that we're actually testing the tag stream position it *does* cause problems. We actually have to fix up the position to be correct even though it will never be used (hopefully). It may be prudent to do this outside of the `timeline_debug` feature as well in the future.
Each render backend keeps track of a stack of BlenModes,
which are pushed and popped by 'core' as we render objects
in the displaay tree. For now, I've just implemented BlendMode.ADD,
which maps directly onto blend mode supported by each backend.
All other blend modes (besides 'NORMAL') will produce a warning
when we try to render using them. This may produce a very large amount
of log output, but it's simpler than emitting each warning only once,
and will help to point developers in the right direction when they
get otherwise inexplicable rendering issues (due to a blend mode
not being implemented).
The wgpu implementation is by far the most complicated, as we need
to construct a `RenderPipeline` for each possible
`(BlendMode, MaskState)`. I haven't been able to find any documentation
about the maximum supported number of (simultaneous) WebGPU render
pipelines - if this becomes an issue, we may need to register them
on-demand when a particular blend mode is requested.
This PR implements the 'DisplayObject.transform' getters/setters,
and most of the getters/setters in the `Transform` class
From testing in FP, it appears that each call to the
'DisplayObject.transform' property produces a new
'Transform' instance, which is permanently tied to the
owner 'DisplayObject'. All of the getters/setters in
`Transform` operate directly on owner `DisplayObject`.
However, note that the `Matrix` and `ColorTransform`
valuse *produced* the getter are plain ActionScript objects,
and have no further tie to the `DisplayObject`.
Using the `DisplayObject.transform` setter results in
values being *copied* from the input `Transform` object.
The input object retains its original owner `DisplayObject`.
Not implemented:
* Transform.concatenatedColorTransform
* Transform.pixelBounds
When a DisplayObject is not a descendant of the stage,
the `concatenatedMatrix` property produces a bizarre matrix:
a scale matrix that the depends on the global state quality.
Any DisplayObject that *is* a descendant of the stage has
a `concatenatedMatrix` that does not depend on the stage quality.
I'm not sure why the behavior occurs - for now, I just manually
mimic the values prdduced by FP. However, these values may indicate
that we need to do some internal scaling based on stage quality values,
and then 'undo' this in certain circumstances when constructing
an ActionScript matrix.
Unfortunately, some of the computed 'concatenatedMatrix' values
are off by f32::EPSILON. This is likely due to us storing some
internal values in pixels rather than twips (the rounding introduced
by round-trip twips conversions could cause this slight difference0.
For now, I've opted to mark these tests as 'approximate'.
To support this, I've extended our test framework to support providing
a regex that matches floating-point values in the output. This allows
us to print out 'Matrix.toString()' and still perform approximate
comparisons between strings of the format
'(a=0, b=0, c=0, d=0, tx=0, ty=0)'
This is the last stub needed for Wonderputt to reach the
main game screen.
As far as I know, ActionScript cannot observe a frame being rendered,
so implementing this method isn't actually necessary for correctness.
The benefit of implementing this would be to make certain animations
appear smoother, since we'll render changes to the scene without
needing to wait for the next frame. However, actually rendering
*immediately* after the event would require some refactoring -
we have a `&mut UpdateContext` while running timers, but we'd need
to bail out and obtain a `&mut Player`.
Many of the class property defintiions were wrong -
instance methods were defined as class properties,
and class properties were defined as instance properties.
This allows Wonderputt to get further (it deliberately assigns
'null' to 'URLRequest.data'). We throw an exception for any other
value, to prevent confusing errors caused by attempting an
unexpected request to a web server with a missig body.
We currently lack the ability to preserve the original
`Value<'gc>` in the error, so we're forced to stringify the error.
This means that only typeless 'catch' blocks will work properly -
however, they're the only kind of 'catch' block that we currently
implement. Implementing support for typed 'catch' blocks will naturally
allow us to preserve the original 'Value<'gc>' in the 'throw'
implementation, since we'll need to switch to a custom `Error<'gc>`
type.
* avm2: Include class name in ScriptObject debug
Currently, the `ScriptObject` debug impl is almost useless -
while you determine if two printed objects are the same
by comparing the pointer value, you'll have no idea what
kind of object it actually is.
This PR now formats the `ScriptObject` output as a struct,
printing a (fake) "class" field containing the class name.
Before/after:
```
[ERROR ruffle_core::avm2::activation] AVM2 error: Cannot coerce Object(ScriptObject(ScriptObject(GcCell(Gc { ptr: 0x55f863936db8 })))) to an QName { ns: Private("Test.as$38"), name: "Second" }
[ERROR ruffle_core::avm2::activation] AVM2 error: Cannot coerce Object(ScriptObject(ScriptObject { class: "Object", ptr: 0x55ee0ad161e0 })) to an QName { ns: Private("Test.as$38"), name: "Second" }
```
Getting access to the class name from a `Debug` impl is tricky:
Developers can (and should be able to) insert logging statements
whereever they want, so any `GcCell` may be mutably borrowed.
Panics in debug impls are extremely frustrating to deal with,
so I've ensured that we only use `try_borrow` at each step.
If any of the attempted borrows fail, we print out an error message
in the "class_name" field, but we're still able to print the
rest of the `ScriptObject`.
Additionally, we have no access to a `MutationContext`, so we
cannot allocate a new `AvmString`. To get around this,
I've created a new method `QName::to_qualified_name_no_mc`,
which uses an `Either` to return a `WString` instead of allocating
an `AvmString`. This is more cumbersome to work with than the
nrmal `QName::to_qualified_name`, so we'll only want to use
it when we have no other choice.
An exception thrown by one event handler shoud not prevent other event
handlers from running on this same event. Some SWFs like Wonderputt
depend on this behavior, as they have buggy event handlers that throw
errors.
Calling `get_trait` copies the returned `Property`, so the caching
we performed in `PropertyClass` was never actually getting used.
Instead, we now store our `PropertyClass` values in a `Vec`
indexed by slot id. `set_property` and `init_property` now perform
coercions by going through the `VTable,` which writes the updated
`PropertyClass` back into the array.
FP allows code like
`class Foo { static var INSTANCE: Foo = new Foo(); }`
However, this breaks our current property type coercion setup -
we cannot resolve the type `Foo` when setting the property `INSTANCE`,
since `Foo` is still being constructed.
Fortunately, we can perform this 'coercion' by just checking if
the object's class name and domain match the type name and domain
of the property.
This returns the approximate interval that the audio backend
updates the sound position information. This is used for syncing
animation to embedded "stream" audio tracks, and fixes some
stuttering in cases where the syncing was being too strict.
Previously, we would create a fresh `LoaderInfo` object each
time the `loaderInfo` property was accessed. However, users can
add event handlers to a `LoaderInfo`, so we need to create and
store exactly one `LoaderInfo` object per movie (and stage).
To verify that we're correctly handling the storage of `LoaderInfo`,
I've implemented firing the "init" event. This required a new
`on_frame_exit` hook, so that we can properly fire the "init"
event after the "exitFrame" for the initial frame but before
the "enterFrame" of the next frame.
The current 'setInterval/setTimeout' implementation is
moved to 'core/src/timers.rs', and now works with both
AVM1 and AVM2 objects. The `flash.utils.Timer` class is implemented
mostly in ActionScript, with minimal modifications to the actual
Ruffle timer code.
* avm2: implement string replace where pattern is string and replacement is a function
* * removed unnecessary vec!
* fixed "no newline at the end of file"
This commit adds support for combining instance allocators with
ActionScript playerglobal class definitions. This is activated
by defining the metadata `[Ruffle(InstanceAllocator = "true")]`
on the ActionScript class definition.
The implementation of this feature is very similar to native methods:
`build_playerglobal` checks for the metadata described above,
and defines a const `NATIVE_INSTANCE_ALLOCATOR_TABLE` mapping
class ids to function pointers.
To demonstrate this feature, I've converted `Event` to ActionScript
(keeping the existing instance allocator function).
I've also converted `ActivityEvent` and `ContextMenuEvent` to
`ActionScript`, to demonstrate how this simplifies inheritance.
In a future PR, we can convert the remaining events to ActionScript,
and remove the `EventData` enum entirely.
Unfortunately, `flex-sdk`'s `asc.jar` compiler strips out all metadata
when the `-optimize` option is passed. As a result, I forked
`flex-sdk` and disabled this behavior:
https://github.com/ruffle-rs/flex-sdk/releases/tag/ruffle-1.0.0
The modified `asc.jar` (built from the forked repository)
is included in this PR, and replaces the our previous 'asc.jar'
downloaded from the official Flex SDK release.
* Change metadata to `[Ruffle(InstanceAllocator)]`
* Strip out metadata before saving bytecode
* avm2: implement string.replace(...) with fn, for now regex only.
* string - added path for replacing regex with fn (replacing string
with fn is still unimplemented)
* regex - factored out common replace logic for when replacement is
a string and when it is a function
* added tests
* Addressed review comments
* removed tinkering cruft; formatting
* addressed review comments
This commit adds support for marking methods as `native`
in ActionScript classes defined in playerglobal. The
`build_playerglobal` now checks for native methods, and
generates Rust code linking them to a corresponding Rust
function definition in the codebase.
To test this functionality, I've reimplemented several
functions as native methods (and moved related code to
pure ActionScript).
* avm2: implement string.split for regex
* Compressed the testing for regexp and unwrapping thereof
* * Moved the split logic into the regex object
* Factored out a method for utf-16 matching
* Added tests
* formatting
* * replaced manual counting with storage.length()
* clippy cleanup
* Address review comments
* fix import path for WString
* remove redundant variable in return statement
* error passing via '?' instead of unwrap()
Though https://github.com/rust-lang/rust-clippy/pull/8355 has been
merged, it seems to still report false-positives on nightly channel.
For now just fix the instances reported by stable clippy, and keep
`needless_borrow` allowed.
The register index was not being increment when preloading the
`super` register which would cause issues when multiple registers
were preloaded.
Fixes#7338.
Alongside comment wordings, fix handling of non-`u8` characters by
replacing `as u8` conversions with `u8::try_from()`, that doesn't
wrap around, but rather fails gracefully.
Based on the work in #6717, plus additional adaptions mentioned in
https://github.com/gfx-rs/wgpu/blob/master/CHANGELOG.md#wgpu-013-2022-06-30,
and more not-mentioned but required changes.
Also bump `wasm-bindgen` to `0.2.81` (along with its helper crates), as
required by the new `wgpu` version.
Note that I don't fully understand some of the required changes, notably:
* `wgpu::PresentMode::Mailbox` no longer works on my machine (Windows 11) -
The `wgpu` documentation says that `wgpu::PresentMode::Fifo` is the
only guaranteed to be supported, so I switched over to it instead.
* `self.staging_belt.recall()` doesn't return a `Future` anymore -
I assume it became synchronous so I simply removed the `executor`
from there.
Properties can be declared with a type
(e.g. `var foo:MyClass = new MyClass();`). When
`set_property`/`init_property` is invoked for that property,
the VM will attempt to coerce the value to the provided type,
throwing an error if this fails. This can have observable behavior
consequences - if a property has type `integer`, for example, then
storing a floating point `Number` to that property will cause the
value to be coerced to an integer. Some SWFs (e.g. 'Solarmax') rely
on this behavior in order to implicitly coerce a floating point value
that's later used for array indexing.
This PR implements property type coercions in Ruffle. There are several
important considerations:
* The class lookup for property types needs to be done lazily, since
we can have a cycle between two classes (e.g. `var prop1:Class2;`
and `var prop2:Class1` in two different classes).
* The class lookup uses special rules (different from
`resolve_definition`), and does *not* use `ScopeStack/`ScopeTree`
This means that a private class can specified as a property name -
the lookup will succeed without using a scope, even though
`flash.utils.getDefinitionByName` would fail with the same name
* The specialized 'Vector' classes (e.g "Vector$int") can be used
as property types, even though they cannot be lookup up normally.
Some Ruffle class definitions were previously using nonexistent
classes as property types (e.g. "BareObject") - these are fixed
in this PR.
- Handle the case where both preload aud suppress flags are
set for the same variable;
- Remove `arguments` field in `Activation`; instead use a normal
local definition;
- When `suppress_this` is set, inherit the `this` value from parent
activation. (This isn't entirely correct, as FP's `this` is mutable
and seems to be part of the scope chain, but this would require a
larger refactoring)
Currently, all three render backends hold on texture-related
resources indefinitely (`register_bitmap` pushes to a `Vec`,
and never removes anything). As a result, the resources used
by the render backend (which may include GPU memory) will grow
over time, even if the corresponding `BitmapData` has been deallocated.
This commit adds a new `unregister_bitmap` method, which is called from
`BitmapData.dispose`. All render backs are changed to now use an
`FnvHashMap<BitmapHandle, _>` instead of a `Vec`, allowing us to
remove individual entries.
Currently, we only call `unregister_bitmap in response to
`BitmapData.dispose` - when `BitmapData` is freed by the
garbage collector, `unregister_bitmap` is *not* called.
This will be addressed in a future PR.
I've kept the rust `flash.geom` module, even though it's now empty,
since we'll need to add things like `flash.geom.Transform` native
methods in the future.
* AVM2: Implement escape()
* chore: Fix formatting
* avm2: Escape resolves non strings to null and use push to append
* chore: Fix nits
* avm2: Escape should coerce objects, add early returns
Previously all the `.as` files compiled into `playerglobal.swf`
were detected automatically using `walkdir`. While this might be
convenient, it can cause unexpected results when untracked `.as`
files exist. So instead, introduce two entry points - `stubs.as`
and `globals.as`, which include all stub ActionScript definitions,
and actual class implementations, respectively. This also simplifies
the `playerglobal.swf` build script a bit.
`flash.geom.Rectangle` is a good candidate, since it doesn't have
any native function, and it depends only on `flash.geom.Point`,
which was already been ported to ActionScript in #7071.
This PR adds support for building a custom `playerglobal.swf`, which can be used
to implement builtin Flash classes in ActionScript. This file is embedded into Ruffle
using `include_bytes!`, and loaded during initialization.
As an example, the `Point` class is reimplemented
in ActionScript, and `flash.text.AntiAliasType` is added.
The ActionScript compilation process is performed by `core/build.rs`.
See that file, along with `core/src/avm2/globals/README.md`, for
more details.
The `render_list` for a container always contains all of the children
under both AVM1 and AVM2 - howver, the depth_list may not contain
some children under AVM2.
When we're not performing some AVM1-specific operation
(e.g. `getInstanceAtDepth`, or dumping out AVM1 variables),
we should be using the render list.
This stubs out BlurFilter, adds properties to GlowFilter,
and make the getter for DisplayObject.filters return
an empty array instead of Undefined.
This is all of the filter-related code that 'Solarmax'
needs in order to reach the main screen (combined with
other unrelated changes I have yet to submit)
Previously, there was an off-by-one bug in `get_enumerant_name`,
which caused us to produce a spurious 'null' as a key.
However, the 'dictionary_foreach' test only checked that certain
keys were present, so the presence of an additional key didn't break
the test.
This commit makes Dictionary enumerants behave in the same way as
Array enumerants - all of the object-specific enumerants
(in this case, the non-primitive dicitonar keys) come first,
followed by 'base' enumerants from ScriptObject (in this case,
primtive/String keys). Additionally, `setPropertyIsEnumerable` is now
ignored for `Dictionary`, consistent with Flash's behavior.
The `dictionary_foreach` test is updated to print out all of
the keys when inspecting the dictionary. Since the enumeration
order is unstable (under both Flash and Ruffle) due to the dependency
on pointer hashing, the test sorts the keys before printing them.
This ensures that we get stable output which is consistent between
Ruffle and Flash.
Flash Player allows this, and returns the path to the root SWF file.
The test only checks that the returned path contains 'test.swf',
to avoid depending on a platform-specific path.
The loop to search for a `non_hole` was missing
a `break;`, so it would actually find the *first*
non-hole, rather than than the last. This was not caught
by the test, since there was only one "real" element
in the array (the other one was set on 'Array.prototype')
Since all `RenderBackend::register_bitmap_*` implementations are
identical now, move them to the default implementation of `RenderBackend`.
Also, turn `RenderBackend::register_bitmap_raw` into `RenderBackend::register_bitmap`,
which accepts a single `Bitmap` parameter.
Replace direct instatiations of `swf::Matrix` where only `tx` and
`ty` are specified, and other fields are default.
This results in a slightly more shorter, readable code.
A method called with `super` is always an instance method,
so we should be using `instance_scope` for consistency with
`call_property`. This fixes a bug where a method cannot
access static class members (via `getlex`) when called bia
`super.method()`
In several places, we read some data from a tag, and then pass
the original tag length to `resize_to_reader`. This is incorrect -
the provided length is used an an offset from the current position
in the reader, so we will extend past the end of the current tag if
we've already read some bytes.
In practice, this doesn't appear to cause any problems - AVM bytecode
has internal length fields, which end up ensuring that we will never
try to read past where the slice *should* end. However, if a `DoAbc`
tag is the last tag in the file, then we'll end up trying to use
`resize_to_reader` with an offset past the end of the movie.
This commit subtracts the number of already-read bytes from `tag_len`,
to ensure that we always construct a correctly-sized `SwfSlice`
Store just the XML declaration string itself, rather than the attributes
it consists of. Then simply return it in ActionScript's `XML.xmlDecl`
property, without using `quick_xml::Writer` at all. This also matches
Flash behavior by capturing the XML declaration as-is, preserving
whitespaces, quotes, casing etc.
Prior to this commit, executing frame scripts on a movie that doesn't have them for this frame, or didn't advance to a new frame, would cause the the movie to ignore all gotos until the next time it ran a frame script.
This PR implements the `URLLoader` class, allowing AVM2 scripts
to load data from a URL. This requires several other related
classes (`URLLoaderDataFormat`, `URLRequest`, `IOError`) to be
implemented as well.
Currently implemented:
* Fetching from URLs using the 'navigator' backend
* The `text` and `binary` data formats (which store data
in a `String` or `ByteArray` respectively)
* The `open`, `complete`, and `ioError` events
* The `bytesLoaded`, `bytesTotal`, and `data` properties
Not yet implemented:
* The HTTP and security events
* All of the properties of `IOError`
* The properties on `URLRequest` (besides `url`)
* The "variables" data format
This should be enough to get some basic uses of `URLLoader` working
(e.g. simple GET requests to a particular website).
Note that in Flash's `playerglobal`, the `URLLoader` class is just
a think wrapper around the more general `URLStream`. However,
implementing `URLStream` will require changes to `Navigator``
to support notifications when data arrives in the stream. When
that happens, we should be able to re-use a large amount of the
code in this PR.
Guarantee bitmap data after decoding has the expected length for
the given width+height+format. This eases the burden from backends
to worry about this. Pad or truncate the data if it doesn't have
the expected size.
Due to various quirks of how timeline-initiated symbols interact with AVM2, it is possible for user code to gain a reference to uninitialized objects (especially `SimpleButton` which doesn't initialize until after `exitFrame`). It is still possible to attach event handlers to them that fire correctly, and movies expect to be able to.
Internal function calls such as `toString` and event handlers
always used the base clip from the function itself, so add an
`ExecutionReason::InternalCall` to signify this and pass it along
to `call_method`.
Fix various scope behavior when executing a function:
* For function calls in SWFv6+, functions are proper closures and
close over the SWF version, base clip, and scope of their
defining function.
* Function calls in SWFv5 are not closures, and use the version,
base clip, and scope of `this` when the function is called.
Fixes#5502.
Testing under Flash shows that methods can be considered 'unchecked'
(allowing them to be called with more arguments than declared
parameters) even if they have a declared return type.
This is relied on by SteamBirds, which registers an event handler
which takes 0 parameters and an explicitly declared return type
This was recently added in https://github.com/rust-lang/rust/pull/96150
Some of the `__version` rules are currently unused, but I assume
that we still want to keep them for potential future use.
This module is now mostly empty, so move the items up to `globals`.
`getDepth` was the only shared method, so declare this property
inline in each display object type. `Video` was also incorrectly
declaring `getDepth`.
Remove `_root`, `_parent` and `_global` from `MovieClip.prototype`.
Instead, these are "magic" properties similar to `_x` and `_y`.
Add `StageObject::resolve_path_property` to handle these, alongside
the `_levelN` property.
Fixes#768.
The remaining caller was `SwfMovie::from_path`, which is now changed
to be simpler, and a little stricter (panics if `Url::from_file_path`
fails, though it shouldn't happen with canonicalized paths).
Remove the `swf_version` parameter from `Activation` constructors,
because this was incorrectly using the global or root SWF version
most times.
Instead, grab the SWF version for the activation directly from the
base clip.
Use a bare `ScriptObject` instead. This matches Flash's behavior in
many aspects:
* Attribute values can now store arbitrary AVM1 values, rather than
just strings. These are coerced to string lazily on demand (e.g.
when coercing the whole XML node to a string).
* Attributes iteration order is the same as their definition order,
not sorted by their name.
Also fix some bugs in `XmlNode::lookup_namespace_uri` (renamed from
`lookup_uri_for_namespace`):
* Iterate attributes by their definition order, so the first matching
one is returned.
* The empty prefix matches every attribute that starts with "xmlns"
(with/without a colon).
And inline `XmlNode::lookup_namespace_for_uri` into `get_prefix_for_namespace`,
and fix some bugs in there as well:
* Iterate attributes by their definition order, so the first matching
one is returned.
* Match any attribute that start with "xmlns" (with/without a colon).
The colors in a DefineBitsJPEG3 tag should be premultiplied alpha,
but in some SWFs they are incorrectly not premultiplied. Flash
Player clamps the color values to the alpha in this case to allow
these images to work more as expected.
Fixes#6893.
The resolved URL only used by `NavigatorBackend::fetch`. So simply
inline `NavigatorBackend::resolve_relative_url` into `NavigatorBackend::fetch`,
per implementation.
* Mimic Flash's quicksort algorithm, rather than using Rust's
`sort_unstable_by`.
* Rename `flags` to `options`, as they are named in AS2 reference.
* Organize different sorting options using 3 simple functions:
`sort_compare`, `sort_compare_custom` and `sort_on_compare`.
Those methods are:
* `Value.coerce_to_receiver`: coerce to object for receivers
* `Value.as_callable`: as_object for callables
* `Activation.superclass_object`: get superclass of currently-called object
* `Activation.resolve_class`: resolve scope value, unwrap `ClassObject`, and error out if either step fails
This accounts for ~80% of coercion-related errors in `Activation`.
PNGs with 16-bit samples were not being normalized to 8-bit, screwing up their
appearance.
Add the `STRIP_16` transformation using `Transformations::normalize_to_color8()`,
which is equivalent to `Transformations::EXPAND | Transformations::STRIP_16`.
Also warn in case `png` outputs a color type other than `Rgb` and `Rgba`.
Due to the `EXPAND` transformation, this may be only `Grayscale` or
`GrayscaleAlpha`. `Indexed` is converted to either `Rgb` or `Rgba`.
Fixes#6662.
* Remove LineStyle::color, instead using fill_style with
FillStyle::Color to indicate solid color.
* Store `flags` in the struct instead of separate bools/values.
* Add getters/setters for ease of use.
* Add builder-style methods for setting LineStyle properties.
* Fix misnamed ALLOW_CLOSE flag to NO_CLOSE.
This feature stopped building with the bump to symphonia 0.5, which
added a `Sync` bound to its traits.
* Add `Sync` bounds to our own internal audio traits to match.
* nellymoser::Decoder was also tweaked to add a Sync bound.
* Lock the nellymoser dependency to a specific git commit.
Unify the previous 3 creation methods to a single `XmlNode::new`.
This allows supporting arbitrary `nodeType` values passed to the
`XMLNode` constructor.
Integer math was used when calculating a sound's start/endpoints,
because it was assumed that the sound sample rate was always an
even divisor of 44100Hz. However, some third party tools can embed
MP3s with other samples rates, such as #6569 which has a 16000Hz
MP3. This could also occur for dynamically loaded MP3s. This
results in the sound starting at an incorrect position.
Use floating point math to ensure the correct position is
caluclated.
Fixes#6569.
Remove the preload step that would pre-create the shapes for each
morph shape ratio on SWF load. Instead, lazily crate the shapes
when they are needed.
Loading unknown data is not considered an error on Flash; Both
`onLoadProgress` and `onLoadComplete` events are dispatched. But,
`onLoadProgress` reports 0 bytes loaded.
This re-uses the logic we have for handling AVM1's `ExternalInterface`.
For now, serialization/deserialization of non-array objects is
left unimplemented.
When decoding PNG/GIF data, convert the image to premultiplied
alpha so that it plays nicely with the renderer.
This applies to both dynamically loaded images and to PNG/GIF data
embedded inside a DefineBitsJPEG tag. This is in contrast to
DefineBitsLossless and DefineBitsJPEG tags w/ alpha, which are
already premultiplied in the SWF.
Also remove unnecessary clamps now that Rust defines casts as
saturating.
Fixes#6559.
These are *not* methods, because we cannot borrow both the update context and the loader at the same time. At least not without making loaders free-standing objects (`GcCell`s), which I don't want to do yet.
I (Michael R. Welsh) assign to Ruffle LLC all rights, title, and
interest to copyrights of my personal contributions to Ruffle,
effective March 26, 2022.
The difference between element nodes and text nodes is very minor.
So instead of representing them by two distinct enum members, make
`XmlNodeData` a single unified struct that can represent both. A new
`node_type` field is introduced, in order to still distinguish
between element and text nodes. Also, Ruffle made some incorrect
assumptions, which are now corrected, including:
* Nodes can have any arbitrary `u8` type. This is resolved by the
introduction of the `node_type` field which is a `u8`.
* Text nodes can have children. This is resolved by simply not checking
for text nodes in `append_child` etc.
Make it a thin abstraction layer over either the `futures` or `wasm-bindgen-futures`
crates, as already done in `render/wgpu/src/uniform_buffer.rs`,
instead of a hand-made single-thread executor.
Ideally this would also be usable on desktop, but I didn't manage to
get `LocalPool` working with `winit` (it needs to post a task to the
`EventLoopProxy` as a wake procedure).
In both `FrameLabel` and `Scene`, we define multiple
'public property / private slot' pairs.
The public property has a getter which delegates to the private
property. There is no setter for the property, ensuring that
the private slot can only be modified from within Ruffle itself.
This PR adds a macro `define_indirect_properties` to abstract over
this pattern. Currently, it only supports the read-only property
pattern - however, it could be extended in the future to generate
a setter that invokes a caller-provided callback function.
This needs to be a macro (rather than a method) so that we can
generate a function with the property name hard-coded into it.
Using a closure that references an upvar will not work, since
`Method::from_builtin` requires a function pointer.
Document roots (a.k.a. `XML` objects) are very similar to regular
element nodes (a.k.a. `XMLNode` objects). The primary difference is
that `XML` objects return `null` for their `nodeName`. But this can
be changed too; Setting `xml.nodeName = "someName"` will make `XML`
objects behave much like `XMLNode`. Moreover, many checks in Ruffle
that refuse to operate on document roots were wrong, and actually
these should be accepted as normal element nodes.
Besides the functional corrections, this also simplifies the code.
This also slightly changes behavior: Previously `SwfMovie::from_data`
errors were propagated as `FetchError` and `display_root_movie_download_failed_message`
was called, offering the user to sidestep CORS by opening the SWF
in a new tab. But that wouldn't help, obviously, because no network
error is involved. Now, these errors are propagated as `InvalidSwf`,
and `display_root_movie_download_failed_message` is not called.
`core` already depends on the `instant` crate which abstracts
`std::instant::Instant` and polyfills it on Web. Use it to replace
`NavigatorBackend::time_since_launch` in order to make `NavigatorBackend`
a little smaller and more simple.
Previously there were 3 implementations of `LocaleBackend`:
`DesktopLocaleBackend`, `WebLocaleBackend` and `NullLocaleBackend`.
While `DesktopLocaleBackend`, `WebLocaleBackend` were identical,
`NullLocaleBackend` always returned a fixed date/time for tests
determinism.
Unify them in a single file, and use `cfg!(test)` and a new dedicated
`deterministic` feature to decide whether to mock date/time or not.
This should not cause any behavioral changes.
Resolving `_levelN` had some inconsistencies with Flash:
1. `_flash` can be a prefix too.
2. The level ID parsing cannot fail; non-digit characters are ignored,
the value wraps around at `i32::MAX`, and negative values are valid.
This logic is relevant also for the `GetUrl` and `GetUrl2` opcodes.
For now only add TODOs for this.
This PR adds the following class stubs:
* 'flash.display.Loader'
* 'flash.net.URLRequest'
* 'flash.ui.Keyboard'
* 'flash.utils.Timer'
These are needed for 'This is the Only Level TOO' (though we'll need
actual implementations to get this game past the loading screen).
Previously, `isFinite()` with no arguments on SWF<7 incorrectly
returned `true`, as `undefined` coerced to `f64` is `0.0`, which
is finite. Fix this by not defaulting to `undefined`, similarly to
the `isNaN()` implementation.
Instead of returning a `Result` which is anyway always handled with
a `log::warn!()`, simply `log::warn!()` in place of errors. This
removes the last 3 remaining `Error` enum members besides `InvalidXml`.
The 2 existing usages of `remove_node` always operated on a child and
its parent: One iterates over all of its children and removes each one,
and the other explicitly grabs the parent of a given node.
As a simplification, `remove_node` operates only on a child node,
without the need for the parent node in addition; it grabs the parent
from the child by itself. As such, it's non-failable.
Text nodes are guaranteed to not be parents, as `adopt_child`
refuses to adopt children into them. So instead of returning an
`Err(Error::TextNodeCantHaveChildren)` in case of a text node parent,
mark those code paths as `unreachable!()`. This makes `orphan_child`
non-failable.
The `json` crate seems unmaintained, and recently also causes compile
errors with stable Rust 1.59.0. On the other hand, `serde_json` is
very maintained and more popular.
However, from some reason a cyclic package dependency has introduced
by this change. For now use a workaround from: https://github.com/tkaitchuck/aHash/issues/95#issuecomment-903560879
This is basically a revert of 61298b2be3.
`SharedObject`s used to be saved as JSON in Ruffle, but since #4238
they're saved in AMF to match Flash's behavior. The legacy JSON
deserialization remained for backwards-compatibility, but from what it
seems, it has never worked; cd1cde1708
changed `LocalStorageBackend` to store base64-encoded strings instead
of plain ones. Therefore, Ruffle attempts to base64-decode old JSON
data, and unsurprisingly fails.
In addition, this removes 1 out of 2 usages of the unmaintained `json`
crate, which recently also causes compile errors with stable Rust 1.59.0.
The only use of `last_parse_error` was in the `XML.prototype.status`
property, where it was converted into a number. Avoid storing it by
storing just the number.
Revert some of e50aea864b for an even
better approach - Remove `XmlNodeObject::empty_node` entirely by
making `XmlNodeObject::from_xml_node` a suitable alternative. That is,
being able to accept a custom `proto` like before.
Also, make it return an `XmlNodeObject` instead of an `Object`, and
add a few `.into()` where needed.
* Don't use `quick_xml::Writer` for formatting the XML, being much
more simple.
* Return `WString` instead of `String`, reducing `to_utf8_lossy()`
calls except when the string needs to be escaped (attribute values
and text contents).
As `XmlDocument` and `XmlObject` had 1-to-1 relation, and `XmlDocument`
is already tightly coupled with AVM1, there's no good reason for them
being separate objects.
This brings us one step closer towards an XML implementation hosted
completely in AVM1.
A future PR will merge `XmlNode` into `XmlNodeObject` in a similar
manner.
Instead of storing shared pointers to `Avm1ConstructorRegistry` in
`MovieLibrary`, access the `PropertyMap` directly, without an extra
abstraction.
Also, move the constructor registries to `Avm1`, for better
encapsulation.
Instead call `XmlNode::script_object`, which internally calls
`XmlNode::introduce_script_object`. This is a preparation for changing
the signature of `XmlNodeObject::from_xml_node`.
Currently it is not directly possible to configure lints for the
entire workspace via TOML, which forced us to repeat `#![allow]`
blocks in each crate.
embark pointed out this workaround to configure lints at the
workspace level via RUSTFLAGS:
https://github.com/EmbarkStudios/rust-ecosystem/issues/22#issuecomment-947011395
Remove the common `#![allow]` blocks and switch to this method for
global lint config.
Temporarily allow `needless_borrow` lint, buggy pending this fix:
https://github.com/rust-lang/rust-clippy/pull/8355
Previously we called `toString` when concatenating a string to an
Object. However, Flash actually has more complex behavior, usually
calling both `valueOf` and `toString`. This is loosely based on
ToPrimitive/DefaultValue with no type hint in the ECMAScript spec.
* Call `valueOf`.
* If the result isn't a primitive, call `toString`.
* If the result still isn't primitive, return `"[type Object]"`.
* For Date objects in SWFv6 and higher, call `toString`.
* If the result isn't a primitive, call `toString` (AVM1 bug?)
* If it still isn't primitive, return `"[type Object]"`.
This also rearranges some things about how we construct events, because `MouseEvent` has different defaults from `Event`. When we finally support parameter metadata on methods we should remove that code.
We also remove the `value_of` code on `EventObject` as that was a mistake. Events don't do anything special in there and I misinterpreted the test results the first time around.
This requires adding another notion of mouse-release events to `ClipEvent`. We now have four:
* `MouseUp` - the mouse was released, any object on the render list can handle this event ("anycast" event)
* `MouseUpInside` - the mouse was released inside this display object, only the mouse-picked target of the event can handle it
* `Release` - the mouse was released inside the last clicked display object
* `ReleaseOutside` - the mouse was released outside the last clicked display object
For those keeping score at home, in AVM2, the valid progression of events is either...
* On the same object, `mouseDown`, `mouseUp`, and `click`
* On one object, `mouseDown`, then some mouse movement that takes the cursor out of the first object, then on another object `mouseUp`, and then finally the first object gets `releaseOutside`.
`MouseDown`/`MouseUp` are effectively broadcasts; they hit every movie clip that can accept them until one of them has a handler for it. AVM2 instead wants events that only apply to specific mouse-picked display objects, which means we need to use the Player-tracked events `Press`, `Release`, and `ReleaseOutside`. The only problem is that we also need to emit a `mouseUp` event on both `Release` and `ReleaseOutside`.
SWFv5 always calls `Object.valueOf` at least once and sometimes
twice in the Equals2 op, even when comparing two Objects.
For example, `Object(1) == Object(1)` is true in SWFv5 but false
in SWFv6.
Consolidate several cases and fix some issues:
* Object-to-primitive comparison always goes through `valueOf`.
* `Object(undefined) == undefined` is true; this will coerce
to a bare object with no `valueOf`, resulting in
`undefined==undefined`.
* `{valueOf:function() { return NaN; }} == NaN` is true.
When creating a scope for a closure, any `with` scopes were being
filtered out, but this was incorrect; `with` scopes are still on
the scope chain when the function is called.
Flash ignores mismatched end tags (i.e. end tags with a missing/different
corresponding start tag). `quick-xml` checks end tag mismatches by
default, but it cannot recover after encountering one.
Commit 7e20543578 already disabled
`quick-xml`'s check, but that caused mismatched `Event::End` to be
handled, which may empty `format_stack` and later panic on
`format_stack.last().unwrap()`.
Thus, check for mismatched end tags ourselves, in a similar manner
of `quick-xml`, but in a recoverable way.
* Have `DefineFunction` and `DefineFunction2` go through the same
code path by implementing `From<DefineFunction>` for
`DefineFunction2`.
* Change `register` to a `Option<NonZeroU8>` for size optimization.
* Add `function::Param` to store param info instead of a tuple.
Use a struct for all variants of `avm1::Action`.
This makes the style more consistent instead of using a mix of
struct and tuple variants, and allows the data to be easily passed
around.
Handle strings, numbers and DisplayObject targets (not just MovieClips).
To support non-MovieClip targets, turn `clip.as_movie_clip().unwrap()`
to `if let Some(mc) = clip.as_movie_clip()` in `Loader`.
`onLoadInit` is queued after all `DoAction`s of the loaded clips.
That is, if clip1, clip2, clip3 are loaded in the same frame
(in this order), then actions will be executed as follows:
* `DoAction` of clip3
* `DoAction` of clip2
* `DoAction` of clip1
* `onLoadInit` of clip3
* `onLoadInit` of clip2
* `onLoadInit` of clip1
Previously, those were incorrectly executed as follows:
* `DoAction` of clip3
* `onLoadInit` of clip3
* `DoAction` of clip2
* `onLoadInit` of clip2
* `DoAction` of clip1
* `onLoadInit` of clip1
An MP3 "stream" sound can sometimes have frames without a
SoundStreamBlock tag, despite the SWF spec saying there should
at least be a tag with 0 samples on each frame. Ruffle would
stop the sound in this case, but the Flash Player may or may not
stop thje sound in the audio depending on the number of "empty"
frames. This could cause the audio to stutter as it continuously
stopped and restarted.
Handle this by keeping track of how many samples we've encountered
in MP3 blocks, and deducting the amount of samples consumed by each
timeline frame. Stop the sound if we run out of samples, as opposed
to when we hit a frame without a SoundStreamBlock.
Fixes#3817.
The first argument of all events is the target MovieClip. It was
incorrect.
Also, `onLoadComplete` accepts an additional `httpStatus` argument.
Stub it to 0.
Remove unnecessary calls to `introduce_loader_handle`, which are
dominated by `add_loader` that already calls it. As a result, `add_loader`
remained the only function to call `introduce_loader_handle`, so inline
it there.
Since they are identical (they both load the URL as a string, then
fire the `onHTTPStatus` and `onData` events). In fact, AVM1's
`XML.prototype.load` and `LoadVars.prototype.load` functions are
both defined as `ASnative(301, 0)`, so they invoke the same native
code under the hood.
The path starting position was not being set correctly after a
moveTo command, which could cause stray strokes to appear in the
drawing.
Fixes#5598, #5768, #5957.
`XmlNode::is_as2_compatible` returns `false` for `XmlNodeData::DocType`
nodes, which means they are not included in string representations of
XML documents, and they cannot be traversed using the DOM methods.
So don't create those when parsing an XML from string, but still
store the `DOCTYPE` declaration string on the `XmlDocument`, which
is accissible through the `.docTypeDecl` property.
`XmlNode::is_as2_compatible` returns `false` for `XmlNodeData::Comment`
nodes, which means they are not included in string representations of
XML documents, and they cannot be traversed using the DOM methods.
So simply don't create those when parsing an XML from string.
Now that `replace_with_str` is defined in `XmlDocument`, there is
no need anymore for a separate function that handles nodes which have
document-wide implications.
Previously Ruffle's AVM1 runtime incorrectly permitted calling `XML`
functions on `XMLNode` objects. For example:
```as
var xml = new XML("<a><b></b></a>");
trace(XML.prototype.createElement.call(xml.firstChild, "aaa")); // traces "undefined" in Flash, but "<aaa />" in Ruffle before this commit.
```
Disallow this by using the newly-reintroduced `XmlObject` for `XML` objects
(rather than `XmlNodeObject` that represents also `XMLNode` object), and check
for it in all `XML` builtins.
* avm2: Implement JSON.parse
* avm2: Add AvmSerializer for serializing AVM values to JSON
* avm2: Add support for replacer objects
* avm2: use *const ObjectPtr for object stack
* avm2: Add support for space parameter is JSON.stringify
* avm2: Refactor AvmSerializer design
* avm2: Restrict spaces to a maximum of 10
* avm2: Refactor map_value
* tests: Add JSON.parse test
* chore: Appease clippy
* avm2: Check if value is undefined before inserting
* tests: Add test for JSON.stringify
* tests: Improve JSON.stringify test
* chore: Replace map_or with explicit match statements
* chore: Use QName::dynamic_name
* avm2: Use Object<'gc> instead of ObjectPtr
* chore: Use explicit match in deserialize_value
* Rebase fixes
Co-authored-by: Adrian Wielgosik <adrian.wielgosik@gmail.com>
`Function.prototype` doesn't have its own `toString` method, but
rather inherts it from `Object.prototype`. So remove `Function.prototype.toString`
and move its logic to `Object.prototype.toString`.
`quick-xml` returns an `Err` for invalid attributes (e.g. unquoted).
Handle such errors by ignoring the HTML completely and return an
empty string instead, as Flash does.
Fix#5789.
The comparsion `start_change.move_to != end_change.move_to` doesn't
make sense, because even if they are identical, the positions need
to be updated.
Fix#2886.
This method has an odd flaw that we don't emulate yet. Actually, two:
1. Precision limits that are specific to the chosen radix
2. Occasional and intermittent corruption in the resulting 0 padding; usually manifesting as `x`, `W`, or `°` characters
The first could be emulated, but I've chosen not to... because the second thing listed not only isn't really possible to emulate, but actively prohibits approx-testing the results. So I'm marking the test as ignored and hoping no movies actually rely on the precision limits in `toString`.
Flash preserves spaces before and after text.
But since now `quick-xml` might emit empty `Text` events, those need
to be explicitly ignored in order to retain the same text format across
tags.
This is a little tricky, because we have to map the utf8 indices
returned by the regex engine to utf16 indices usable by Ruffle.
To limit the impact on performance, the regex, the string we're
currently matching on, and the last known (utf8, utf16) positions
are cached, avoiding extra utf8 conversions in common use cases
where a single string is repeatedly searched with increasing
`lastIndex`.
This generally means that methods are more efficient, as we
don't need to encode to UTF16 on-the-fly to have correct indices.
This also fix some bugs:
- charCode now properly handle surrogate pairs
- calling lastIndexOf with the empty pattern and an OoB index now
properly returns the string length
Still missing is AVM2's String.match
These types represent an UCS2 string (UTF-16 with unpaired surrogates).
The string is stored either as a sequence of u8s (Bytes) or u16s (Wide);
the type of string is tracked by setting the high bit of the length of
its fat pointer.
The fix in #5218 wasn't sufficient; 30-bit arithmetic should be used
along all the way when calculating an effective sound transform.
For example, a sound transform composited by volumes `-0x80000000` and
`25` should end up as effectively 0, whereas previously it would have
been calculated as `-0x80000000 * 25 / 100 = -0x20000000`, which is a
30-bit integer that hasn't been truncated.
Fixes#5655.
This makes `DesktopUiBackend::is_key_down` a simple one-line check.
Also unify the handling code of `ElementState::Pressed` and `ElementState::Released`,
which had a lot of common code.
This fully reverts commit 2119ce9.
Seems like Flash does handle "br" tags, but ignores them under some
unknown circumstances (e.g. setting `htmlText` in AVM1).
For now handle "br" tags unconditionally.
This also necessitated removing the `impl_display_object` family of macros, as you cannot name a field of a field in a macro expression. I tried. So instead I've reverted to standard default method inheritance, in the same way we did with AVM2 objects.
`handle_clip_event` is now a default trait method that calls three methods in order:
* `filter_clip_event`, to determine which events that either this object or it's children may handle
* `propagate_to_children`, to check if any children of this object want to handle an event. (This also includes AVM2 button states, which are not technically "children" in the usual sense...)
* `event_dispatch`, which does the actual "object reacts to an event" bit if no child handles the object.
These roughly correspond to phases of existing event-handling objects pre-`InteractiveObject`.
Since `TextFormat.color` should preserve the alpha value, and the AVM1
getter/setter no longer reset it, we need to reset it in other places:
* `TextField.textColor` setter.
* `EditText` SWF tag handling.
* HTML parsing.
And to set it explicitly to 255 in `LayoutBox::as_renderable_text`.
`TextFormat` objects differ from regular objects in that
`TextField.setTextFormat` and `TextField.setNewTextFormat` accept
only the former, and ignore the latter.
Also, `TextFormat.prototype` has native accessors that coerce the
values on get/set.
For example, `tellTarget("bogus!!!") { var n = 2; gotoAndPlay(n); }`
should cause the _root clip to go to frame 2.
Fixes the character freezing in Steppenwolf 3-1 mentioned in #4585.
`tellTarget` to other clips should fail if the current base clip
has been removed. All properties of the clip return `undefined`
at this point, so traversing the target path should fail.
Fixes soft-lock in Steppenwolf 2-1 as mentioned in #4452.
Use prototype depths instead. Most calls passed `base_proto = None`,
which is equivalent to `depth = 0`, and is now the default.
The few other cases were adapted to use `Executable::exec` directly,
where `depth` can be specified manually.
The changes in #5498 caused the samples from the initial MP3 frame
to be skipped. This was noticable in:
https://www.newgrounds.com/portal/view/1
This change properly removes the redundant `num_samples` variable
and considers the samples from the initial MP3 frame.
MP3 data in an SWF can be of a different sample rate than
indicated in the SWF tag, so grab the sample rate from the MP3
decoder instead of passing it in from the SWF tag.
Also, a general clean up of the MP3 decoders.
Fixes#335.
Fix various issues with `SoundChannel`:
* Change `avm2::Object::as_sound_instance` to `as_sound_channel`.
* Cache sound position in `SoundChannelObject`.
* `SoundInfo::in_sample` is in units of 44100Hz.
* Clamp `num_loops` to 1.
* Change `AudioBackend::get_sound_position` to return `f64` to
match `AudioBackend::get_sound_duration`.
* Wire up `AudioBackend::get_sound_position` to `Sound.position`.
* Remove unimplmeneted warning from `Sound.position`.
This is a temporary hack to calculate depth from `base_proto` and
`this`, so that changing `this.__proto__` will affect `super`.
In the future, `depth` should be passed instead of `base_proto`.
In case the method is found directly on `this` (normally it shouldn't
because it's usually defined on `this.__proto__`), it seems like the
`super` object behaves identically as-if the method was found on
the object's prototype.
Previously constructions had `base_proto` set to the newly-created
object (`this`). However this doesn't match the `base_proto` of method
calls, which is `this.__proto__` (or more precisely where the function
is found on the prototype chain). This caused wrong behavior when using
the `super` object from within constructors.
Change `base_proto` in that case to be `this.__proto__`, which aligns
with method calls.
In order to keep things working, `SuperObject::call` needs to look-up
one level less than before.
An alternative can be changing `base_proto` for method calls instead,
but that seems to be harder because this would require `search_prototype`
to return the before-last visited object in the prototype chain.
When a function is defined, the base clip is stored in the function
object, and used when the function is called in SWFv6+. This
affects the target clip for GotoFrame and other actions.
However, if that base clip no longer exists when the function is
called, the base clip should default to the `this` display object.
Previously Ruffle would still use the previously unloaded base clip.
If a movieclip calls a seek operation like `gotoAndStop` on
itself while executing a frame script, this goto is not executed
until the frame script has completed. In contrast, calling a goto
on other clips will execute the goto immediately.
Slots and const traits should initialize to an appropriate value
for their type if no default is specified. Remove the `Option`
from `default_value`, and always set it to an appropriate null-ish
value (`0` for ints, `NaN` for `Number`, `null` for objects, etc.)
Gated behind the "vp6" feature, enabled by default.
Utilizing a heavily stripped-down version of the NihAV project,
retaining only the VP6 decoder, relicensed under MIT.
Including VP6WithAlpha decoding, proper FrameDependency reporting,
and cropping the unwanted encoded pixels on the right/bottom manually.
* `try_actions` -> `try_body`
* `catch` -> `catch_body`
* `finally` -> `finally_body`
This aligns with the names used in SWF19, and is more consistent.
This might create templatized functions leading to unnecessary code bloat.
So instead use just `Value<'gc>` parameters and add `.into()` in callers
where needed.
* avm2: Properly make all classes an instance of `Class`.
Also, does this technically mean that `Class` is a metaclass?
* avm2: Remove `Function::from_method_and_proto` as it will no longer be needed
* avm2: Ensure builtin classes are also instances of `Class`.
This requires tying a veritable gordian knot of classes; everything needs to be allocated up-front, linked together, and then properly initialized later on. This necessitated splitting the whole class construction process up into three steps:
1. Allocation via `from_class_partial`, which does everything that can be done without any other classes
2. Weaving via `link_prototype` and `link_type`, which links all of the allocated parts together correctly. This also includes initializing `SystemClasses` and `SystemPrototypes`.
3. Initialization via `into_finished_class`, which must be done *after* the weave has finished.
Once complete you have core classes that are all instances of `Class`, along with prototypes that have their usual legacy quirks.
Note that this does *not* make prototypes instances of their class. We do need to do that, but doing so breaks ES3 legacy support. This is because we currently only work with bound methods, but need to be able to call unbound methods in `callproperty`.
* tests: Add a test for all core classes' instance-of relationships
This also changes the `bitmapdata_constr` test slightly to use a different starting value. Our premultiplied alpha calculations generate slightly different values from Flash Player which trips the test.
Attempting to do so results in borrow-mut panics. Furthermore, it's unnecessary; the handle provided to this function is already derived from the bitmap data in this way.
`get_local` is basically equivalent to `get_local_stored` that also
handles virtual getters. So instead handle virtual getters in
`search_prototype`. This allows to inline the `get_local_sub` helper
methods into the implementations of `get_local_stored`.
The remaining usages of `get_local` were not exactly correct as they
all should ignore virtual getters. This change solves this as well
by using `get_local_stored` that ignores virtual getters.
It seems like Flash stores sound transform values in 30-bit unsigned integers:
* Negative values are equivalent to their absolute value.
* Specifically, 0x40000000, -0x40000000 and -0x80000000 are equivalent to zero.
Pull out the audio mixing code from desktop and add it to core.
This will allow other backends to use it (such as the web audio
backend) to get consistent audio across all platforms.
This one was rather tough to test, as I actually can't generate ABCs in Animate CC that reference these classes. I instead had to modify a compiled SWF to open the package-internal namespace that these pre-specialized classes exist in.
In AVM2, the `*` (any) type is represented as `null`.
This requires also changing the parameters on classes and objects to be nullable, too.
We do *not* represent `null` as a particular value on the underyling vector storage object, however. We instead change it to `Object`, as that's the root of all other types.
This may require revision if non-`Object`-extending types appear in the future.