View Full Version : Replace Class Method
inanimate
August 20th, 2009, 09:01 PM
Hello. Is it possible to override/replace a method of a class in AS3? When I say "replace", I don't mean subclass it and override it. I mean I literally want to replace the existing method with my own.
For instance, AS3 has the built-in HTMLLoader class which has a load method. I want to say, in essence:
HTMLLoader.load = function() { trace("Overridden"); };
Therefore, when *anybody* makes a new HTMLLoader object and calls the load method, it will call my method instead.
I've tried a couple things, and so far I've gotten this far:
HTMLLoader.prototype.load = function() { trace("Overridden") };
And I compile it with -as3=false -es=true -strict=false. The problem is it doesn't override the already-existing load method. It looks in the class first before looking in the prototype, thus making it not work.
There must be a way. Even if I can't override it, can I proxy it like a decorator in Python or a macro in LISP? (So mine gets called then it gets called.)
Thanks.
Krilnon
August 20th, 2009, 11:19 PM
I'm fairly sure that you can't do that with the methods (or properties in general) of built-in classes in a SWF that is already running, with the exception of 'core' classes defined by the ES4 draft specification. I've tried to do the same thing before and I met with no success.
For example:
var override:Function = function(){
trace('override!');
}
// MovieClip and Sprite are non-ES4 classes
MovieClip.prototype.addChild = Sprite.prototype.addChild = override;
new Sprite().addChild(new Sprite()); // nope
new MovieClip().addChild(new Sprite()); // nope
// ES4-spec classes are okay
Function.prototype.call = override;
void function(){}.call(); // override!
Number.prototype.toFixed = override;
(5).toFixed(); // override!
Namespace.prototype.valueOf = override;
AS3.valueOf(); // override!
Something similar happens when you define your own class:
Q.prototype.f = override;
new Q().f();
// elsewhere:
package {
dynamic public class Q {
public function f(){
trace('trait method');
}
prototype.f = function(){
trace('proto');
}
}
}
The reason that this works for the core/ES-spec classes is that their methods were all defined in the AS3 namespace instead of the usual public namespace that the rest of the built-in methods use. That way, they could also let you use the prototype versions and still be in compliance with the ES4 draft.
If you're loading in a SWF that uses some non-built-in classes, you can use your SWF's class definitions to override the ones given in the external one, as long as the fully-qualified names of the classes are the same (using some ApplicationDomain trickery (although it's not really trickery per se)).
If you're loading in a SWF and you don't mind giving up the content access permissions that you get when you load a SWF directly from a different server, you can replace all references to a given built-in class with a reference to a dynamically defined class of your own that extends (or just uses) one of the built-in classes. I say 'can', but I haven't actually seen an implementation of this anywhere despite its technical feasibility.
There must be a way. Even if I can't override it, can I proxy it like a decorator in Python or a macro in LISP? (So mine gets called then it gets called.)
There is a Proxy class, but unfortunately you need to subclass it to use it. As a result, you can't use any Proxy features with built-in class instances, since a) you can't have a class that is a subclass of both Proxy and another built-in class and b) instances of a given built-in class can't be somehow routed through Proxy during construction in order to give them special behavior.
Sorry for the disappointing news, I wish it weren't so. :-/
inanimate
August 21st, 2009, 02:23 AM
Ah... That is so lame... Where's AS2 when you need it...
The reason that this works for the core/ES-spec classes is that their methods were all defined in the AS3 namespace instead of the usual public namespace that the rest of the built-in methods use. That way, they could also let you use the prototype versions and still be in compliance with the ES4 draft.
Interesting. So there is no way to get at the trait property and override something in there?
If you're loading in a SWF and you don't mind giving up the content access permissions that you get when you load a SWF directly from a different server, you can replace all references to a given built-in class with a reference to a dynamically defined class of your own that extends (or just uses) one of the built-in classes. I say 'can', but I haven't actually seen an implementation of this anywhere despite its technical feasibility.
This sounds interesting, but I'm not sure I understand. So if you're using a built-in class A, say, you're saying you replace it with a custom "myA" that extends/uses A? If so, how does this have to do with loading an SWF from a different server? (And given that, I can't really replace instantiations of A with myA, otherwise I wouldn't need to override the method in the first place... I would have just used a different class.)
There is a Proxy class, but unfortunately you need to subclass it to use it. As a result, you can't use any Proxy features with built-in class instances, since a) you can't have a class that is a subclass of both Proxy and another built-in class and b) instances of a given built-in class can't be somehow routed through Proxy during construction in order to give them special behavior.
Sorry for the disappointing news, I wish it weren't so. :-/
Yeah, I saw Proxy, but once I saw you had to subclass it I basically disregarded it.
Thank you for the thorough explanation. Too bad...
Gnoll
August 21st, 2009, 02:41 AM
Can't you just extend HTMLLoader then override that method?
protected function load(request:URLRequest):void
{
super().load(request);
trace("overriden");
}
or is it private...
Gnoll
Edit. missed that you said you didn't want to subclass it, but that is the whole concept of overriding in programming generally :P
If you have your own class with public var func:Function you should be able to do func = function() { } or something but I can't think of a case in which it would work sooo great. :P
Felixz
August 21st, 2009, 06:27 AM
or use interfaces
inanimate
August 21st, 2009, 12:49 PM
Edit. missed that you said you didn't want to subclass it, but that is the whole concept of overriding in programming generally :P
Right... That's why I titled it "Replace Class Method" instead of "Override Class Method", because after a couple days of Googling on this topic, I realized that "overriding" was not what I wanted =).
If you have your own class with public var func:Function you should be able to do func = function() { } or something but I can't think of a case in which it would work sooo great. :P
Yeah, unfortunately I don't have access to the class (more on that below).
or use interfaces
Not sure how that would work? The class (whose method I'm trying to replace) doesn't implement my interface, and even if they did, they implement the body of the method which I need to change.
Here's the actual scenario, and maybe that will stir up some new ideas: There are a couple built-in classes in AS3--mainly the flash.net classes and other ways of communicating through the network--that I need to get at/proxy. I do not have access to the code in which they're used, which includes in AS3 itself (other classes using these net classes) and other SWF files that I can't touch and know nothing about. That said, I need to basically be the "gatekeeper" and make them go through me instead of going directly through the network, hence needing to replace the methods they would have used.
Now, there may be an entirely different way to proxy network traffic specifically (although I've found that AIR/Flash itself doesn't support its own proxy settings), and I'd love to know how, but that said, I think there are still some other methods that don't have to do with networking that I'll have to replace too.
Any ideas? Thanks for your help.
senocular
August 21st, 2009, 01:07 PM
Krilnon's pretty much on track.
The only thing I could think of that might possibly work, is that if you defined your own variation of these classes within the flash.* package namespace that overrides the native definitions. IF that even worked, it would then be a mess to try to maintain functional parity with that actual class, especially if it has hooks into native player code (and assuming you need it). At that point you'd probably have to set this up in a separate application domain and use some cross-domain scripting to access an instance in a non-polluted domain with an instance of the respective class there to get that functionality - if even possible at that point.
But in general, the answer is no. ActionScript doesn't technically support what you're asking for and anything that would make it work would probably be a monstrous hack.
inanimate
August 21st, 2009, 01:50 PM
Well I guess that about sums it up then...
That package idea is pretty hilarious... I wonder if it would work, but you're absolutely right that it'd be a nightmare to maintain, especially if you had to make requests to get the un-tainted objects from somewhere else to use their functionality.
I miss prototypes =(.
ominds
May 27th, 2011, 12:04 AM
Hello All,
I need your collective knowledge to solve this one. We're the developers of the FlashFirebug extension:
https://addons.mozilla.org/en-US/firefox/addon/161670/
This is the most downloaded flash debugging extension on Mozilla. What we do is inject an SWF on the page that loads the existing SWFs and communicate with our extension. Our main feature is displaying the DisplayList and allowing users to modify properties of nodes they select.
When a node is ADDED/REMOVED to the DisplayList in the SWF, the relevant events launch and we send a signal to the extension to update the tree. The problem happens on methods that modify the DisplayList but do not dispatch events. Namely:
setChildIndex, swapChildren, swapChildrenAt
How can we detect these calls to update our tree? So far, we've found
http://www.as3commons.org/as3-commons-bytecode/proxy.html
But we don't know if it works on built-in classes and if it does, it seems to fire the interceptor function asynchronously, meaning that while the interceptor function is running, the ADDED event could fire and update the tree making the swap call send a reference to incorrect indexes (the interceptor should block any node add/remove functions).
We're also looking at this paragraph by "krilnon"
If you're loading in a SWF and you don't mind giving up the content access permissions that you get when you load a SWF directly from a different server, you can replace all references to a given built-in class with a reference to a dynamically defined class of your own that extends (or just uses) one of the built-in classes. I say 'can', but I haven't actually seen an implementation of this anywhere despite its technical feasibility.
But don't really understand how this could be done. Looked for articles everywhere. So my question is, has anyone been able to accomplish this? Either via replacing the functions or intercepting them?
Thanks!
Krilnon
May 27th, 2011, 02:52 AM
It's mostly just a matter of putting "guards" in the bytecode and SWF tags at the right places. It's easy but inefficient to put them everywhere, and more complicated but more efficient to put them only where they're needed.
For example, it's easy to spot this sort of instantiation statically:
// really AS3
var s:Sprite = new Sprite
…which will result in some bytecode like this:
findpropstrict flash.display::Sprite
constructprop flash.display::Sprite, 0
coerce flash.display::Sprite
setlocal_1
But it's harder to spot this:
var str:String = String.fromCharCode(105)
var ns:Namespace = new Namespace('flash.ut' + str + 'ls')
var s = new (ns::['getDef' + str + 'nitionByName']('flash.display.Spr' + str + 'te'))
throw s
getlex (public)::String
pushbyte 105
callproperty [namespace]::"http://adobe.com/AS3/2006/builtin"::fromCharCode, 1
coerce_s
setlocal_1
findpropstrict (public)::Namespace
pushstring "flash.ut"
getlocal_1
add
pushstring "ls"
add
constructprop (public)::Namespace, 1
coerce (public)::Namespace
setlocal_2
getlocal_2
coerce (public)::Namespace
pushstring "getDef"
getlocal_1
add
pushstring "nitionByName"
add
findpropstrict [Multiname RTQNameLate]
dup
setlocal 4
getlocal_2
coerce (public)::Namespace
pushstring "getDef"
getlocal_1
add
pushstring "nitionByName"
add
getproperty [Multiname RTQNameLate]
getlocal 4
pushstring "flash.display.Spr"
getlocal_1
add
pushstring "te"
add
call 1
kill 4
construct 0
coerce_a
setlocal_3
getlocal_3
throw
Obviously my example is contrived, but there really are cases where you end up at a construct instruction without statically knowing anything about the class being constructed (especially if charCode was generated by a user's keypress or something). The nice thing about these cases is that they don't actually come up all that often in regular code, so it's not too inefficient to throw guards around these very dynamic cases. It doesn't really matter that there's a bunch of junk above the construct invocation, except that you would need to track the values on the operand stack to capture what's being passed to it. (BTW, the reason that you're guarding construction is so that you could check for anything that's a DisplayObjectContainer.)
In one case that I've implemented for real, I would wrap the first argument of the easy-to-find calls to the URLRequest constructor with an arbitrary function call of my own, so if you called new URLRequest('google.com') it would instead perhaps be rewritten to new URLRequest('goojje.cn'). At a bytecode level it would look something like this (I had this diagram handy):
http://temp.reclipse.net/rewrite2.png
In your case, where you want to know when certain DisplayObjectContainer methods are called, you wouldn't wrap the arguments, you would wrap the construction. Abstractly, you'd probably want new Sprite to turn into new (getInstrumentedDisplayObjectContainerSubclassFor( Sprite)). For cases where people used built-in classes, the generated subclasses could simply be tossed back as results and they'd still behave as intended by the original authors. For user-defined classes, you could either do the same thing by generating subclasses of those classes, or you could just stuff your own class into their class hierarchy somewhere and then not have to worry about the overhead of wrapping statically-detectable instantiations of those classes. That's related to the tradeoff I mentioned at the beginning of my post.
I haven't used AS3Commmons Bytecode because it didn't exist back when I wanted something like it, so I wrote my own similar library which is much less polished (which should be evident given the other thread that's floating around here tonight), but functional enough to do what I wanted it to do. That and I decided never to use their library when I saw the line var proxyFactory:IProxyFactory = new ProxyFactory() in their examples. (kidding (-: ) However, their "interceptors" look like almost what you're looking for. The only potential problem that I noticed is that they wrap all of their example instantiations with a proxy, like so: proxyFactory.createProxy(Order) as Order. Since you don't have access to the source files in your extension, that's not a comparable situation. However, I'm fairly sure it's doable with their library overall; I just haven't looked into it in detail because I have my own code to worry about.
You might have noticed that I mentioned SWF tags near the top. Another consideration that you'd want to take into account would be the display list elements that are generated by the authoring IDE, and not via (ActionScript) code. Unsurprisingly, you would also want to be concerned with the classes associated with timeline symbols, etc. The strategy is basically the same, it's just another place to guard that has a format other than bytecode that you would need to be able to parse. I believe there are at least one or two libraries in AS3 which deal with SWF tags…
You mentioned not being able to find too many examples of this being done. I think there are several reasons why this is so. First, not too many developers want or need the general capability to modify compiled SWFs in ActionScript. Of course, there are some, so that's why there are several libraries which let you do so. However, what you're asking about is of interest to an even smaller subset of developers. If there is someone else who has already done it, it's pretty likely that they either didn't tell anyone about it or publicized it on a very tiny corner of the internet. Finally, it usually takes a question like yours in order to prompt someone to make an example file or small tutorial about the process of doing something…
ominds
May 30th, 2011, 10:31 AM
We tried mixing loom, it worked on intercepting all non-built-in classes and methods (by injecting a call inside the methods). But when we tried it on built-in classes it failed. The reason, obviously, is that the classes are located in the player, not in the SWF itself.
Catching references to the DisplayObjectContainer Class came to mind (as you suggested), but what about the inheritance inside the player itself? There are not less than 20-30 classes that inherit from the DisplayObjectContainer (or its subclasses) that are all built-in (inside the player). So we can't modify their references to DisplayObjectContainer (because the inheritance is inside the player) and we would have to override each and every one of these classes.
There's got to be some other way to do this. Any suggestions are welcome!
a tadster
May 30th, 2011, 09:02 PM
@ominds
Whatever you guys are doing is messing with the global settings SharedObject that gets put on a system (from Adobe) whenever the global settings manager is used.
Your plugin totally screws up any ExternalInterface usage, and perhaps also the actual global security settings. (which is very bad) The only solution is to erase the global settings SO! Not even uninstalling your plugin solves the problem.. I tried to say this on your site and was immediately marked as a spammer! Well, I just said the bad part first before finding the solution, you never let me post the solution...
Anyway, there it is. You guys need to seriously re-work you software, otherwise, it's pretty neat.
ominds
May 31st, 2011, 08:14 AM
Hey Tadster,
As I specified in my email to you, it's an automated spam detection system. We have the problem you specified on our Radar and will make sure to fix it in our upcoming release. As to your proposed solution, feel free to reply to my email with it.
But going back to the topic, we're still facing the same problem. We can't find a way to detect changes in object index in the DisplayList and we're quite stuck on that. All solutions we have in mind are either bad in performance or provide bad user experience. We highly appreciate suggestions and preferably, working code!
Powered by vBulletin® Version 4.1.10 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.