diff --git a/src/avm1/context.ts b/src/avm1/context.ts index 51d2dbe8f..96267019c 100644 --- a/src/avm1/context.ts +++ b/src/avm1/context.ts @@ -34,7 +34,9 @@ module Shumway.AVM1 { } export interface IAVM1RuntimeUtils { + hasProperty(obj, name); getProperty(obj, name); + setProperty(obj, name, value); } export class AVM1Context { diff --git a/src/avm1/flash.d.ts b/src/avm1/flash.d.ts index ff82e56d1..4e11f5253 100644 --- a/src/avm1/flash.d.ts +++ b/src/avm1/flash.d.ts @@ -51,6 +51,7 @@ declare module Shumway.AVM2.AS.flash { _mouseDown: boolean; _children: DisplayObject []; _depth: number; + _symbol: DisplaySymbol; getBounds(obj: DisplayObject): flash.geom.Rectangle; play(); stop(); @@ -129,9 +130,7 @@ declare module Shumway.AVM2.AS.flash { constructor(); } class SimpleButton extends DisplayObject { - _symbol: { - data: {buttonActions: Shumway.Timeline.AVM1ButtonAction[]} - } + _symbol: ButtonSymbol; } class Stage extends DisplayObject { @@ -144,8 +143,12 @@ declare module Shumway.AVM2.AS.flash { stageHeight: number; } - class BitmapSymbol {} - class SpriteSymbol { + class DisplaySymbol {} + class BitmapSymbol extends DisplaySymbol {} + class ButtonSymbol extends DisplaySymbol { + data: { buttonActions: Shumway.Timeline.AVM1ButtonAction[] } + } + class SpriteSymbol extends DisplaySymbol { avm1Name: string; avm1SymbolClass; } @@ -276,10 +279,14 @@ declare module Shumway.AVM2.AS.flash { textWidth: number; textHeight: number; defaultTextFormat: TextFormat; + _symbol: TextSymbol; } class TextFormat extends ASNative { constructor(...args); } + class TextSymbol extends display.DisplaySymbol { + variableName: string; + } } module ui { class ContextMenu extends ASNative {} diff --git a/src/avm1/interpreter.ts b/src/avm1/interpreter.ts index df1dd9b82..aec7569f9 100644 --- a/src/avm1/interpreter.ts +++ b/src/avm1/interpreter.ts @@ -197,10 +197,27 @@ module Shumway.AVM1 { this.errorsIgnored = 0; this.deferScriptExecution = true; this.pendingScripts = []; + + var context = this; this.utils = { + hasProperty(obj, name) { + var result: boolean; + context.enterContext(function () { + result = as2HasProperty(obj, name); + }, obj); + return result; + }, getProperty(obj, name) { - var resolved = avm1ResolveProperty(obj, name, false); - return resolved ? resolved.link.asGetPublicProperty(name) : undefined; + var result; + context.enterContext(function () { + result = as2GetProperty(obj, name); + }, obj); + return result; + }, + setProperty(obj, name, value) { + context.enterContext(function () { + as2SetProperty(obj, name, value); + }, obj); } }; } @@ -554,6 +571,11 @@ module Shumway.AVM1 { // versions 6 and below ignore identifier case if (isNumeric(name) || as2GetCurrentSwfVersion() > 6) { + if (normalize) { + __resolvePropertyResult.link = obj; + __resolvePropertyResult.name = name; + return __resolvePropertyResult; + } return null; } @@ -596,18 +618,26 @@ module Shumway.AVM1 { return null; } + function as2ResolveProperty(obj, name: string, normalize: boolean): string { + var resolved = avm1ResolveProperty(obj, name, normalize); + return resolved ? resolved.name : null; + } + function as2HasProperty(obj, name: string): boolean { return !!avm1ResolveProperty(obj, name, false); } - function as2ResolveProperty(obj, name: string, normalize: boolean): string { - var result = avm1ResolveProperty(obj, name, normalize); - return result ? result.name : null; + function as2GetProperty(obj, name: string): any { + var resolved = avm1ResolveProperty(obj, name, false); + return resolved ? resolved.link.asGetPublicProperty(resolved.name) : undefined; } - function as2GetProperty(obj, name: string): any { - var result = avm1ResolveProperty(obj, name, false); - return result ? result.link.asGetPublicProperty(name) : undefined; + function as2SetProperty(obj, name: string, value: any): any { + var resolved = avm1ResolveProperty(obj, name, true); + if (!resolved) { + return; // probably obj is undefined or null + } + resolved.link.asSetPublicProperty(resolved.name, value); } function as2CastError(ex) { diff --git a/src/avm1/lib/AVM1TextField.ts b/src/avm1/lib/AVM1TextField.ts index e71f9adda..744cae0b7 100644 --- a/src/avm1/lib/AVM1TextField.ts +++ b/src/avm1/lib/AVM1TextField.ts @@ -39,13 +39,20 @@ module Shumway.AVM1.Lib { return wrapped; } - _variable: string; + private _variable: string; + private _exitFrameHandler: (event: flash.events.Event) => void; - public initAVM1Instance(as2Object: flash.text.TextField, context: AVM1Context) { - super.initAVM1Instance(as2Object, context); + + public initAVM1Instance(as3Object: flash.text.TextField, context: AVM1Context) { + super.initAVM1Instance(as3Object, context); this._variable = ''; + this._exitFrameHandler = null; initDefaultListeners(this); + + if (as3Object._symbol) { + this.variable = as3Object._symbol.variableName || ''; + } } public get _alpha() { @@ -351,14 +358,33 @@ module Shumway.AVM1.Lib { if (name === this._variable) { return; } - this._variable = name; var instance = this.as3Object; - var hasPath = name.indexOf('.') >= 0 || name.indexOf(':') >= 0; + if (this._exitFrameHandler && !name) { + instance.removeEventListener('exitFrame', this._exitFrameHandler); + this._exitFrameHandler = null; + } + this._variable = name; + if (!this._exitFrameHandler && name) { + this._exitFrameHandler = this._onAS3ObjectExitFrame.bind(this); + instance.addEventListener('exitFrame', this._exitFrameHandler); + } + } + + private _onAS3ObjectExitFrame() { + this._syncTextFieldValue(this.as3Object, this._variable); + } + + private _syncTextFieldValue(instance, name) { var clip; + var hasPath = name.indexOf('.') >= 0 || name.indexOf(':') >= 0; + var avm1ContextUtils = this.context.utils; if (hasPath) { var targetPath = name.split(/[.:\/]/g); name = targetPath.pop(); if (targetPath[0] == '_root' || targetPath[0] === '') { + if (instance.root === null) { + return; // text field is not part of the stage yet + } clip = getAVM1Object(instance.root, this.context); targetPath.shift(); if (targetPath[0] === '') { @@ -369,20 +395,20 @@ module Shumway.AVM1.Lib { } while (targetPath.length > 0) { var childName = targetPath.shift(); - clip = clip.asGetPublicProperty(childName) || clip[childName]; + clip = avm1ContextUtils.getProperty(clip, childName); if (!clip) { - throw new Error('Cannot find ' + childName + ' variable'); + return; // cannot find child clip } } } else { clip = getAVM1Object(instance._parent, this.context); } - if (!clip.asHasProperty(undefined, name, 0)) { - clip.asSetPublicProperty(name, instance.text); + // Sets default values as defined in SWF if this property was not found. + if (!avm1ContextUtils.hasProperty(clip, name)) { + avm1ContextUtils.setProperty(clip, name, instance.text); } - instance.addEventListener('advanceFrame', function () { - instance.text = '' + clip.asGetPublicProperty(name); - }); + + instance.text = '' + avm1ContextUtils.getProperty(clip, name); } public get _visible() { diff --git a/test/swfs/avm1/text-bind/text-bind-1.fla b/test/swfs/avm1/text-bind/text-bind-1.fla new file mode 100644 index 000000000..6f89fec76 Binary files /dev/null and b/test/swfs/avm1/text-bind/text-bind-1.fla differ diff --git a/test/swfs/avm1/text-bind/text-bind-1.swf b/test/swfs/avm1/text-bind/text-bind-1.swf new file mode 100644 index 000000000..a82337951 Binary files /dev/null and b/test/swfs/avm1/text-bind/text-bind-1.swf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 1de931c3e..85abbe982 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -447,6 +447,11 @@ "swf": "/examples/textfield/align.swf", "type": "eq" }, + { "id": "text-bind-ref", + "frames": [3], + "swf": "swfs/avm1/text-bind/text-bind-1.swf", + "type": "eq" + }, { "id": "as3-loader", "stas": "swfs/trace.stas", "filenames": [