@awayfl/avm1
Version:
Virtual machine for executing AS1 and AS2 code
482 lines (481 loc) • 19.1 kB
JavaScript
import { __extends } from "tslib";
import { alToString, alIsName, alIsFunction } from '../runtime';
import { release, Debug } from '@awayfl/swf-loader';
import { AVM1PropertyDescriptor } from './AVM1PropertyDescriptor';
/**
* Base class for object instances we prefer to not inherit Object.prototype properties.
*/
var NullPrototypeObject = /** @class */ (function () {
function NullPrototypeObject() {
}
return NullPrototypeObject;
}());
export { NullPrototypeObject };
var DEBUG_PROPERTY_PREFIX = '$Bg';
/**
* Base class for the ActionScript AVM1 object.
*/
var AVM1Object = /** @class */ (function (_super) {
__extends(AVM1Object, _super);
function AVM1Object(avm1Context) {
var _this = _super.call(this) || this;
_this.initialDepth = 0;
_this.scriptRefsToChilds = {};
// mark that object is GHOST, FP not allow assign/get/call props in this mode, instanceOf always is false
_this._isGhost = false;
_this._avm1Context = avm1Context;
_this._ownProperties = Object.create(null);
_this.scriptRefsToChilds = {};
_this._prototype = null;
_this._blockedByScript = false;
_this._ctBlockedByScript = false;
_this._visibilityByScript = false;
var self = _this;
// Using IAVM1Callable here to avoid circular calls between AVM1Object and
// AVM1Function during constructions.
// TODO do we need to support __proto__ for all SWF versions?
var getter = { alCall: function (thisArg, args) { return self.alPrototype; } };
var setter = { alCall: function (thisArg, args) { self.alPrototype = args[0]; } };
var desc = new AVM1PropertyDescriptor(128 /* AVM1PropertyFlags.ACCESSOR */ |
2 /* AVM1PropertyFlags.DONT_DELETE */ |
1 /* AVM1PropertyFlags.DONT_ENUM */, null, getter, setter);
_this.alSetOwnProperty('__proto__', desc);
return _this;
}
Object.defineProperty(AVM1Object.prototype, "isGhost", {
get: function () {
return this._isGhost;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AVM1Object.prototype, "eventObserver", {
get: function () {
return this._eventObserver;
},
set: function (value) {
this._eventObserver = value;
},
enumerable: false,
configurable: true
});
/**
* Move object to ghost mode, we can't recover back from this mode, all props and methods will be undef
*/
AVM1Object.prototype.makeGhost = function () {
// remove all props that was assigned in runtime
// require for batch3/DarkValentine
// moved this into this condition. required for chickClick level-button issue
this.deleteOwnProperties();
// drop prototype, instanceOf always will false
this.alPut('__proto__', null);
this._isGhost = true;
};
AVM1Object.prototype.dispose = function () {
};
AVM1Object.prototype.updateFilters = function (newFilters) {
/*let filter: IFilter;
for (let f = 0; f < newFilters.length; f++) {
filter = newFilters[f];
}*/
// console.warn('[AVM1Object] update_filters not implemented');
};
AVM1Object.prototype.isBlockedByScript = function () {
return this._blockedByScript;
};
AVM1Object.prototype.isColorTransformByScript = function () {
return this._ctBlockedByScript;
};
AVM1Object.prototype.isVisibilityByScript = function () {
return this._visibilityByScript;
};
AVM1Object.prototype.initAdapter = function () {
};
AVM1Object.prototype.freeFromScript = function () {
this.protoTypeChanged = false;
this._blockedByScript = false;
this._ctBlockedByScript = false;
this._visibilityByScript = false;
};
AVM1Object.prototype.clone = function () {
var newAVM1Object = new AVM1Object(this._avm1Context);
return newAVM1Object;
};
Object.defineProperty(AVM1Object.prototype, "context", {
get: function () {
return this._avm1Context;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AVM1Object.prototype, "alPrototype", {
get: function () {
return this._prototype;
},
set: function (v) {
// checking for circular references
var p = v;
while (p) {
if (p === this) {
return; // possible loop in __proto__ chain is found
}
p = p.alPrototype;
}
// TODO recursive chain check
this._prototype = v;
},
enumerable: false,
configurable: true
});
AVM1Object.prototype.alGetPrototypeProperty = function () {
return this.alGet('prototype');
};
// TODO shall we add mode for readonly/native flags of the prototype property?
AVM1Object.prototype.alSetOwnPrototypeProperty = function (v) {
this.alSetOwnProperty('prototype', new AVM1PropertyDescriptor(64 /* AVM1PropertyFlags.DATA */ |
1 /* AVM1PropertyFlags.DONT_ENUM */, v));
};
AVM1Object.prototype.alGetConstructorProperty = function () {
return this.alGet('__constructor__');
};
AVM1Object.prototype.alSetOwnConstructorProperty = function (v) {
this.alSetOwnProperty('__constructor__', new AVM1PropertyDescriptor(64 /* AVM1PropertyFlags.DATA */ |
1 /* AVM1PropertyFlags.DONT_ENUM */, v));
};
AVM1Object.prototype._debugEscapeProperty = function (p) {
var context = this.context;
var name = alToString(context, p);
if (!context.isPropertyCaseSensitive) {
name = name.toLowerCase();
}
return DEBUG_PROPERTY_PREFIX + name;
};
AVM1Object.prototype.alGetOwnProperty = function (name) {
if (this._isGhost) {
return null;
}
if (typeof name === 'string' && !this.context.isPropertyCaseSensitive) {
name = name.toLowerCase();
}
release || Debug.assert(alIsName(this.context, name));
// TODO __resolve
return this._ownProperties[name];
};
AVM1Object.prototype.alSetOwnProperty = function (propName, desc) {
if (this._isGhost) {
return;
}
var name = this.context.normalizeName(propName);
if (!desc.originalName && !this.context.isPropertyCaseSensitive) {
desc.originalName = propName;
}
if (!release) {
Debug.assert(desc instanceof AVM1PropertyDescriptor);
// Ensure that a descriptor isn't used multiple times. If it were, we couldn't update
// values in-place.
Debug.assert(!desc['owningObject'] || desc['owningObject'] === this);
desc['owningObject'] = this;
// adding data property on the main object for convenience of debugging.
if ((desc.flags & 64 /* AVM1PropertyFlags.DATA */) &&
!(desc.flags & 1 /* AVM1PropertyFlags.DONT_ENUM */)) {
Object.defineProperty(this, this._debugEscapeProperty(name), { value: desc.value, enumerable: true, configurable: true });
}
}
this._ownProperties[name] = desc;
};
AVM1Object.prototype.alHasOwnProperty = function (propName) {
if (this._isGhost) {
return false;
}
var name = this.context.normalizeName(propName);
return !!this._ownProperties[name];
};
AVM1Object.prototype.alDeleteOwnProperty = function (propName) {
var name = this.context.normalizeName(propName);
delete this._ownProperties[name];
if (!release) {
delete this[this._debugEscapeProperty(propName)];
}
};
AVM1Object.prototype.deleteOwnProperties = function () {
var allProps = this.alGetOwnPropertiesKeys();
for (var i = 0; i < allProps.length; i++) {
this.alDeleteOwnProperty(allProps[i]);
}
};
AVM1Object.prototype.alGetOwnPropertiesKeys = function () {
var keys = [];
if (this._isGhost) {
return keys;
}
var desc;
if (!this.context.isPropertyCaseSensitive) {
for (var name_1 in this._ownProperties) {
desc = this._ownProperties[name_1];
release || Debug.assert('originalName' in desc);
if (!(desc.flags & 1 /* AVM1PropertyFlags.DONT_ENUM */)) {
keys.push(desc.originalName);
}
}
}
else {
for (var name_2 in this._ownProperties) {
desc = this._ownProperties[name_2];
if (!(desc.flags & 1 /* AVM1PropertyFlags.DONT_ENUM */)) {
keys.push(name_2);
}
}
}
return keys;
};
AVM1Object.prototype.alGetProperty = function (propName) {
if (this._isGhost) {
return null;
}
var desc = this.alGetOwnProperty(propName);
if (desc) {
return desc;
}
if (!this._prototype) {
return undefined;
}
return this._prototype.alGetProperty(propName);
};
AVM1Object.prototype.alGet = function (propName) {
if (this._isGhost) {
return void 0;
}
var name = this.context.normalizeName(propName);
var desc = this.alGetProperty(name);
if (!desc) {
return void 0;
}
if ((desc.flags & 64 /* AVM1PropertyFlags.DATA */)) {
var val = desc.value;
// redurant, XML should return value direct
// for xml nodes we need to return the nodeValue
// https://developer.mozilla.org/ru/docs/Web/API/Node/nodeType
// if (val && (val.nodeType == 2 /* Attr */ || val.nodeType == 3 /* Text */ || val.nodeValue)) {
// return desc.value.nodeValue;
// }
return val;
}
release || Debug.assert(!!(desc.flags & 128 /* AVM1PropertyFlags.ACCESSOR */));
var getter = desc.get;
return getter ? getter.alCall(this) : void 0;
};
AVM1Object.prototype.alCanPut = function (propName) {
if (this._isGhost) {
return false;
}
var desc = this.alGetOwnProperty(propName);
if (desc) {
if ((desc.flags & 128 /* AVM1PropertyFlags.ACCESSOR */)) {
return !!desc.set;
}
else {
return !(desc.flags & 4 /* AVM1PropertyFlags.READ_ONLY */);
}
}
var proto = this._prototype;
if (!proto) {
return true;
}
return proto.alCanPut(propName);
};
AVM1Object.prototype.alPut = function (propName, value) {
if (this._isGhost) {
return;
}
// Perform all lookups with the canonicalized name, but keep the original name around to
// pass it to `alSetOwnProperty`, which stores it on the descriptor.
var originalName = propName;
propName = this.context.normalizeName(propName);
// stupid hack to make sure we can update references to objects in cases when the timeline changes the objects
// if a new object is registered for the same name, we can use the "scriptRefsToChilds"
// to update all references to the old object with the new one
if (value && typeof value === 'object'
&& value.avmType === 'symbol'
&& propName != 'this' && propName != '_parent'
&& !value.dynamicallyCreated) {
if (value.adaptee && value.adaptee.parent
&& value.adaptee.parent.adapter
&& value.adaptee.parent.adapter.scriptRefsToChilds) {
value.adaptee.parent.adapter.scriptRefsToChilds[value.adaptee.name] = { obj: this, name: propName };
}
}
if (!this.alCanPut(propName)) {
return;
}
var ownDesc = this.alGetOwnProperty(propName);
if (ownDesc && (ownDesc.flags & 64 /* AVM1PropertyFlags.DATA */)) {
if (ownDesc.watcher) {
value = ownDesc.watcher.callback.alCall(this, [ownDesc.watcher.name, ownDesc.value, value, ownDesc.watcher.userData]);
}
// Real properties (i.e., not things like "_root" on MovieClips) can be updated in-place.
if (propName in this._ownProperties) {
ownDesc.value = value;
}
else {
this.alSetOwnProperty(originalName, new AVM1PropertyDescriptor(ownDesc.flags, value));
}
return;
}
if (typeof value === 'undefined'
&& (propName == '_x' || propName == '_y' || propName == '_xscale' || propName == '_yscale' || propName == '_width' || propName == '_height')) {
// certain props do not allow their value to be set to "undefined", so we exit here
// todo: there might be more props that do not allow "undefined"
return;
}
var desc = this.alGetProperty(propName);
if (desc && (desc.flags & 128 /* AVM1PropertyFlags.ACCESSOR */)) {
if (desc.watcher) {
var oldValue = desc.get ? desc.get.alCall(this) : undefined;
value = desc.watcher.callback.alCall(this, [desc.watcher.name, oldValue, value, desc.watcher.userData]);
}
var setter = desc.set;
release || Debug.assert(setter);
setter.alCall(this, [value]);
}
else {
if (desc && desc.watcher) {
release || Debug.assert(desc.flags & 64 /* AVM1PropertyFlags.DATA */);
value = desc.watcher.callback.alCall(this, [desc.watcher.name, desc.value, value, desc.watcher.userData]);
}
if (value && value.isTextVar) {
value = value.value;
var newDesc = new AVM1PropertyDescriptor(desc ? desc.flags : 64 /* AVM1PropertyFlags.DATA */, value);
newDesc.isTextVar = true;
this.alSetOwnProperty(originalName, newDesc);
}
else {
var newDesc = new AVM1PropertyDescriptor(desc ? desc.flags : 64 /* AVM1PropertyFlags.DATA */, value);
this.alSetOwnProperty(originalName, newDesc);
}
}
};
AVM1Object.prototype.alHasProperty = function (p) {
if (this._isGhost) {
return false;
}
var desc = this.alGetProperty(p);
return !!desc;
};
AVM1Object.prototype.alDeleteProperty = function (propName) {
var desc = this.alGetOwnProperty(propName);
if (!desc) {
return true;
}
if ((desc.flags & 2 /* AVM1PropertyFlags.DONT_DELETE */)) {
return false;
}
this.alDeleteOwnProperty(propName);
return true;
};
AVM1Object.prototype.alAddPropertyWatcher = function (propName, callback, userData) {
if (this._isGhost) {
return false;
}
// TODO verify/test this functionality to match ActionScript
var desc = this.alGetProperty(propName);
if (!desc) {
return false;
}
desc.watcher = {
name: propName,
callback: callback,
userData: userData
};
return true;
};
AVM1Object.prototype.alRemotePropertyWatcher = function (p) {
var desc = this.alGetProperty(p);
if (!desc || !desc.watcher) {
return false;
}
desc.watcher = undefined;
return true;
};
AVM1Object.prototype.alDefaultValue = function (hint) {
if (hint === void 0) { hint = 0 /* AVM1DefaultValueHint.NUMBER */; }
if (hint === 1 /* AVM1DefaultValueHint.STRING */) {
var toString_1 = this.alGet(this.context.normalizeName('toString'));
if (alIsFunction(toString_1)) {
return toString_1.alCall(this);
}
var valueOf = this.alGet(this.context.normalizeName('valueOf'));
if (alIsFunction(valueOf)) {
return valueOf.alCall(this);
}
}
else {
release || Debug.assert(hint === 0 /* AVM1DefaultValueHint.NUMBER */);
var valueOf = this.alGet(this.context.normalizeName('valueOf'));
if (alIsFunction(valueOf)) {
return valueOf.alCall(this);
}
var toString_2 = this.alGet(this.context.normalizeName('toString'));
if (alIsFunction(toString_2)) {
return toString_2.alCall(this);
}
}
// TODO is this a default?
return this;
};
AVM1Object.prototype.alGetKeys = function () {
if (this._isGhost) {
return [];
}
var ownKeys = this.alGetOwnPropertiesKeys();
var proto = this._prototype;
if (!proto) {
return ownKeys;
}
var otherKeys = proto.alGetKeys();
if (ownKeys.length === 0) {
return otherKeys;
}
// Merging two keys sets
// TODO check if we shall worry about __proto__ usage here
var context = this.context;
var k;
// If the context is case-insensitive, names only differing in their casing overwrite each
// other. Iterating over the keys returns the first original, case-preserved key that was
// ever used for the property, though.
if (!context.isPropertyCaseSensitive) {
var keyLists = [ownKeys, otherKeys];
var canonicalKeysMap = Object.create(null);
var keys = [];
var keyList = void 0;
var key = void 0;
var canonicalKey = void 0;
for (k = 0; k < keyLists.length; k++) {
keyList = keyLists[k];
for (var i = 0; i < keyList.length; i++) {
key = keyList[i];
canonicalKey = context.normalizeName(key);
if (canonicalKeysMap[canonicalKey]) {
continue;
}
canonicalKeysMap[canonicalKey] = true;
keys.push(key);
}
}
return keys;
}
else {
var processed = Object.create(null);
var keyLength1 = ownKeys.length;
for (k = 0; k < keyLength1; k++) {
processed[ownKeys[k]] = true;
}
var keyLength2 = otherKeys.length;
for (k = 0; k < keyLength2; k++) {
processed[otherKeys[k]] = true;
}
return Object.getOwnPropertyNames(processed);
}
};
return AVM1Object;
}(NullPrototypeObject));
export { AVM1Object };