@awayfl/avm1
Version:
Virtual machine for executing AS1 and AS2 code
382 lines (381 loc) • 13.1 kB
JavaScript
/*
* Copyright 2015 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { __extends } from "tslib";
import { AVM1ArrayNative, AVM1BooleanNative, AVM1NumberNative, AVM1StringNative } from './natives';
import { AVM1Object } from './runtime/AVM1Object';
import { AVM1Function } from './runtime/AVM1Function';
import { AVM1PropertyDescriptor } from './runtime/AVM1PropertyDescriptor';
import { Debug, release, isNullOrUndefined, isIndex } from '@awayfl/swf-loader';
/**
* Base class for ActionScript functions with native JavaScript implementation.
*/
var AVM1NativeFunction = /** @class */ (function (_super) {
__extends(AVM1NativeFunction, _super);
/**
* @param {IAVM1Context} context
* @param {Function} fn The native function for regular calling.
* @param {Function} ctor The native function for construction.
*/
function AVM1NativeFunction(context, fn, ctor) {
var _this = _super.call(this, context) || this;
_this._fn = fn;
if (ctor) {
_this._ctor = ctor;
}
return _this;
}
AVM1NativeFunction.prototype.alConstruct = function (args) {
if (!this._ctor) {
throw new Error('not a constructor');
}
/* eslint-disable-next-line prefer-spread */
return this._ctor.apply(this, args);
};
AVM1NativeFunction.prototype.alCall = function (thisArg, args) {
if (!this._fn) {
throw new Error('not callable');
}
return this._fn.apply(thisArg, args);
};
return AVM1NativeFunction;
}(AVM1Function));
export { AVM1NativeFunction };
/**
* Base class the is used for the interpreter.
* See {AVM1InterpretedFunction} implementation
*/
var AVM1EvalFunction = /** @class */ (function (_super) {
__extends(AVM1EvalFunction, _super);
function AVM1EvalFunction(context) {
var _this = _super.call(this, context) || this;
var proto = new AVM1Object(context);
proto.alPrototype = context.builtins.Object.alGetPrototypeProperty();
proto.alSetOwnProperty('constructor', new AVM1PropertyDescriptor(64 /* AVM1PropertyFlags.DATA */ |
1 /* AVM1PropertyFlags.DONT_ENUM */ |
2 /* AVM1PropertyFlags.DONT_DELETE */));
_this.alSetOwnPrototypeProperty(proto);
return _this;
}
AVM1EvalFunction.prototype.alConstruct = function (args) {
var obj = new AVM1Object(this.context);
var objPrototype = this.alGetPrototypeProperty();
if (!(objPrototype instanceof AVM1Object)) {
objPrototype = this.context.builtins.Object.alGetPrototypeProperty();
}
obj.alPrototype = objPrototype;
obj.alSetOwnConstructorProperty(this);
var result = this.alCall(obj, args);
return result instanceof AVM1Object ? result : obj;
};
return AVM1EvalFunction;
}(AVM1Function));
export { AVM1EvalFunction };
// TODO create classes for the ActionScript errors.
function AVM1TypeError(msg) {
}
AVM1TypeError.prototype = Object.create(Error.prototype);
export function alToPrimitive(context, v, preferredType) {
var t = typeof v;
if (t === 'string' || t === 'number' || t === 'undefined') {
return v;
}
if (!(v instanceof AVM1Object)) {
return v;
}
var obj = v;
return preferredType !== undefined ? obj.alDefaultValue(preferredType) : obj.alDefaultValue();
}
export function bToRuntimeBool(context, v) {
var is5 = context.swfVersion >= 5;
//const is7 = context.swfVersion >= 7;
if (is5) {
return !!v;
}
return +v;
}
export function alToBoolean(context, v) {
var is7 = context.swfVersion >= 7;
switch (typeof v) {
case 'undefined':
return false;
case 'object':
return v !== null;
case 'boolean':
return v;
case 'string': {
if (is7) {
// In files published for Flash Player 7 and later, the result is true if the string has a length
// greater than 0; the value is false for an empty string.
return !!v;
}
// In files published for Flash Player 6 and earlier, the string is first converted to a number.
// The value is true if the number is not 0, otherwise the return value is false.
return !isNaN(+v) && (+v) !== 0;
}
case 'number':
return !!v;
default:
release || Debug.assert(false);
}
}
export function alToNumber(context, v) {
if (typeof v === 'object' && v !== null) {
v = alToPrimitive(context, v, 0 /* AVM1DefaultValueHint.NUMBER */);
}
switch (typeof v) {
case 'undefined':
return context.swfVersion >= 7 ? NaN : 0;
case 'object':
if (v === null) {
return context.swfVersion >= 7 ? NaN : 0;
}
// for xml nodes we want to get the nodeValue here
if (typeof v.nodeValue !== 'undefined')
return parseFloat(v.nodeValue);
return context.swfVersion >= 5 ? NaN : 0;
case 'boolean':
return v ? 1 : 0;
case 'number':
return v;
case 'string':
if (v === '' && context.swfVersion < 5) {
return 0;
}
if (v === '') {
return NaN;
}
return +v;
default:
release || Debug.assert(false);
}
}
export function alToInteger(context, v) {
var n = alToNumber(context, v);
if (isNaN(n)) {
return 0;
}
if (n === 0 || n === Number.POSITIVE_INFINITY || n === Number.NEGATIVE_INFINITY) {
return n;
}
return n < 0 ? Math.ceil(n) : Math.floor(n);
}
export function alToInt32(context, v) {
var n = alToNumber(context, v);
return Math.floor(n);
}
export function alToString(context, v) {
if (typeof v === 'object' && v !== null) {
v = alToPrimitive(context, v, 1 /* AVM1DefaultValueHint.STRING */);
}
switch (typeof v) {
case 'undefined':
return context.swfVersion >= 7 ? 'undefined' : '';
case 'object':
if (v === null) {
return 'null';
}
if (v && v instanceof Array) {
var outputStr = '';
for (var i = 0; i < v.length; i++) {
outputStr += alToString(context, v[i]);
outputStr += (i === v.length - 1) ? '' : ',';
}
return outputStr;
}
return '[type ' + alGetObjectClass(v) + ']';
case 'boolean':
return v ? 'true' : 'false';
case 'number': {
// int
if ((v | 0) === v) {
return '' + v;
}
if (isFinite(v)) {
// https://esbench.com/bench/5f888a98b4632100a7dcd403
var e = Math.floor(Math.log10(Math.abs(+v)));
if (Math.abs(e) < 14) {
var p = Math.pow(10, 14 - e);
v = Math.round(v * p) / p;
}
}
return '' + v;
}
case 'string':
return v;
default:
release || Debug.assert(false);
}
}
export function alIsName(context, v) {
return typeof v === 'number' ||
typeof v === 'string' &&
(!context.isPropertyCaseSensitive || v === v.toLowerCase());
}
export function alToObject(context, v) {
switch (typeof v) {
case 'undefined':
throw new AVM1TypeError();
case 'object':
if (v === null) {
throw new AVM1TypeError();
}
// TODO verify if all objects here are inherited from AVM1Object
if (Array.isArray(v)) {
return new AVM1ArrayNative(context, v);
}
return v;
case 'boolean':
return new AVM1BooleanNative(context, v);
case 'number':
return new AVM1NumberNative(context, v);
case 'string':
return new AVM1StringNative(context, v);
default:
release || Debug.assert(false);
}
}
export function alNewObject(context) {
var obj = new AVM1Object(context);
obj.alPrototype = context.builtins.Object.alGetPrototypeProperty();
obj.alSetOwnConstructorProperty(context.builtins.Object);
return obj;
}
export function alGetObjectClass(obj) {
if (obj instanceof AVM1Function) {
return 'Function';
}
// TODO more cases
return 'Object';
}
/**
* Non-standard string coercion function roughly matching the behavior of AVM2's axCoerceString.
*
* This is useful when dealing with AVM2 objects in the implementation of AVM1 builtins: they
* frequently expect either a string or `null`, but not `undefined`.
*/
export function alCoerceString(context, x) {
if (x instanceof AVM1Object)
return alToString(context, x);
if (typeof x === 'string')
return x;
if (x == undefined)
return null;
return x + '';
}
export function alCoerceNumber(context, x) {
if (isNullOrUndefined(x)) {
return undefined;
}
return alToNumber(context, x);
}
export function alIsIndex(context, p) {
if (p instanceof AVM1Object) {
return isIndex(alToString(context, p));
}
return isIndex(p);
}
export function alForEachProperty(obj, fn, thisArg) {
obj.alGetKeys().forEach(fn, thisArg);
}
export function alIsFunction(obj) {
return obj instanceof AVM1Function;
}
export function alCallProperty(obj, p, args) {
var callable = obj.alGet(p);
callable.alCall(obj, args);
}
export function alInstanceOf(context, obj, cls) {
if (!(obj instanceof AVM1Object)) {
return false;
}
if (!(cls instanceof AVM1Object)) {
return false;
}
var proto = cls.alGetPrototypeProperty();
for (var i = obj; i; i = i.alPrototype) {
if (i === proto) {
return true;
}
}
return false;
}
export function alIsArray(context, v) {
return alInstanceOf(context, v, context.builtins.Array);
}
export function alIsArrayLike(context, v) {
if (!(v instanceof AVM1Object)) {
return false;
}
var length = alToInteger(context, v.alGet('length'));
if (isNaN(length) || length < 0 || length >= 4294967296) {
return false;
}
return true;
}
/* eslint-disable-next-line max-len */
export function alIterateArray(context, arr, fn, thisArg) {
if (thisArg === void 0) { thisArg = null; }
var length = alToInteger(context, arr.alGet('length'));
if (isNaN(length) || length >= 4294967296) {
return;
}
for (var i = 0; i < length; i++) {
fn.call(thisArg, arr.alGet(i), i);
}
}
export function alIsString(context, v) {
return typeof v === 'string';
}
export function alDefineObjectProperties(obj, descriptors) {
var context = obj.context;
Object.getOwnPropertyNames(descriptors).forEach(function (name) {
var desc = descriptors[name];
var value, getter, setter;
var flags = 0;
if (typeof desc === 'object') {
if (desc.get || desc.set) {
getter = desc.get ? new AVM1NativeFunction(context, desc.get) : undefined;
setter = desc.set ? new AVM1NativeFunction(context, desc.set) : undefined;
flags |= 128 /* AVM1PropertyFlags.ACCESSOR */;
}
else {
value = desc.value;
if (typeof value === 'function') {
value = new AVM1NativeFunction(context, value);
}
flags |= 64 /* AVM1PropertyFlags.DATA */;
if (!desc.writable) {
flags |= 4 /* AVM1PropertyFlags.READ_ONLY */;
}
}
if (!desc.enumerable) {
flags |= 1 /* AVM1PropertyFlags.DONT_ENUM */;
}
if (!desc.configurable) {
flags |= 2 /* AVM1PropertyFlags.DONT_DELETE */;
}
}
else {
value = desc;
if (typeof value === 'function') {
value = new AVM1NativeFunction(context, value);
}
flags |= 64 /* AVM1PropertyFlags.DATA */ | 2 /* AVM1PropertyFlags.DONT_DELETE */ |
1 /* AVM1PropertyFlags.DONT_ENUM */ | 4 /* AVM1PropertyFlags.READ_ONLY */;
}
obj.alSetOwnProperty(name, new AVM1PropertyDescriptor(flags, value, getter, setter));
});
}