UNPKG

@awayfl/avm1

Version:

Virtual machine for executing AS1 and AS2 code

482 lines (481 loc) 19.1 kB
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 };