PDA

View Full Version : Stage and sandboxes.



wvxvw
December 3rd, 2009, 05:58 AM
I would like to know your opinion on such feature request:
Make stage property of DisplayObject deprecated and remove it in the future versions of ActionScript.
May sound controversial, but, please read further.

In majority of cases, when one SWF fails to unload from another SWF the problem is within stage being accessible to the loaded SWF. Often times loaded SWF would make a hard reference to it, which would then mean staying forever in player's memory.
This concern has been expressed long ago by Grant Skinner, but there was no effective solution to the problem. The unloadAndStop() added in FP10 doesn't solve this problem, it is in fact not much different from stop() + unload(). However, the problem remains... You cannot safely load external content into your SWF. All the sandboxes and application domain system is worth nothing since once you get the reference to the stage you can do almost everything in loading SWF.

We only need a reference to the stage in FP9 / FP10 when trying to imitate onReleaseOutside AS2 handler. I.e. one common-case scenario, where stage is indispensable is when you need to add handler to mouseUp event.

Now, to sandboxing. There should be some mechanism built into player to let the player decide which sandbox is allowed to get the reference to the stage, and which is not. So that for example loaded SWF couldn't set stage.align to what it wants unless the first SWF loaded into player explicitly permits it to the certain domain.
Otherwise what happens is like this: You have an application that loads flash banners, and your application sets stage's align to top-left, then, some rookie banner sets it to the middle... The controls in your application go hirewire... and you simply cannot prevent it from happening...

Solution: setting stage properties fails if the sandboxed content is not allowed to access stage. Stage does not exist as a property by default, it is passed as an argument to the entry point of your application. This is done prior to initialization of the rest of your code, so, if the argument is null, you would know you are sandboxed and not allowed to call stage's methods and properties.
Another possible solution is to have Stage.available property and / or Stage.stage properties. Same way, if the Stage.stage is null or Stage.available is false you cannot change align / displayMode / displayState etc...
Mouse events: Make Mouse an EventDispatcher, overload addEventListener() in a way it always adds a weak reference, no matter what.
GetObjectsUnderPoint() method should return only objects within the sandbox your SWF has permissions to access. Likewise Stage.focus would return null if focus is out of reach in terms of sandbox.

This won't solve all the loading issues as there are still timers and local connections to deal with, but will greatly improve the security overall IMO.

I'm yet to think over the content of the FR, so, haven't posted it yet, and, would be happy to know your opinion on this matter!

TheCanadian
December 3rd, 2009, 02:31 PM
I'm to dumb to answer this, but do you really think AS4 will be released with FP11?

wvxvw
December 3rd, 2009, 03:38 PM
Ha, nope, I don't have any such info :) I just put a the next major version number. I would imagine, that player version will iterate more often then of the language judging from the past, but that's just a guess :)

And, oh, I see how my question maybe looks like a stream of consciousness :D
I'll try to make it shorter:
ExternalInterface and NativeApplication both follow similar pattern, so, why Stage, which is for the SWF running in the embedded player is almost the same as NativeApplication is implemented differently? Property suggests multiplicity, however, there isn't situation, when there is more then one stage... On the other hand, Stage is a core of many security problems, which, in part prevent developers from loading external content.
Imagine this:

public class LoadedSWF extends Sprite {
...
private function addedToStageHandler(event:Event):void
{
super.stage.align = StageAlign.BOTTOM_RIGHT;
}
Now, you have loaded this SWF, but, before you did so, your Stage.align setting was StageAlign.TOP_LEFT. Once the misbehaving SWF is loaded all your visual controls go wondering across the screen :) Even worse - there is nothing you can do to prevent this.

IQAndreas
December 3rd, 2009, 07:34 PM
Here are my thoughts (the abbreviated version), but they may be a bit off topic to the point you are trying to make, so feel free to remove or ignore this post...


CONTAINER CLASS
I say, stop making the stage a DisplayObjectContainer. Instead of dispatching errors whenever trying to set common display object properties to the stage, just remove those properties at all. Also, prevent children from being added to or removed directly from the stage. It is bad practice. Also, since there pretty much will only ever be one stage (bear with me, I'll explain), make Stage a static class which cannot be initialized directly. Stage "will only ever have one child" (but I believe thinking of it as having children should be stopped), which I believe should not be able to be accessed at all through the class.

The Stage class is considered to be the actual player window or browser window. It only has a handful of properties along the lines of (italicized if they do not currently exist):


align
alpha (this might be bad, since then the SWF can be used maliciously to be larger than the site without anyone knowing it, and capture any outside mouse and keyboard events, and since SWFs are designed to be as "safe" as possible on the user preventing attacks, this might be limited to when compiling the SWF as a projector)
backgroundColor
displayState
hasOSFocus
width (note, not content width, but what was previously stageWidth)
height (note, not content height, but what was previously stageHeight)
scaleMode
frameRate (unsure of weather or not they belong here)
quality (unsure of weather or not they belong here)
totalRunTime (like getTimer(), unsure of weather or not they belong here)

And a few properties I believe SWFs that are exported to Projector Files should have access to:


hideBlueBar (whatever that grabbable bar is named again, by default blue in Windows XP. This should be hideable, as is possible in Visual Studio.net)
grabArea (once again, if I knew the name of the bar, it can be used. Basically this allows people to move the SWF around when the mouse is down on it, and stops when the mouse is released. Perhaps developers are creating a media player, and want to be able to grab on the leftmost side instead of the top bar)
minimized
maximized
showInTaskbar (again, security issues)
x (possibly readonly)
y (possibly readonly)
desktopSize (possibly readonly)


onClose
The class dispatches the same events, fullScreen, mouseLeave, and resize, and perhaps even an "onClose" event which many have requested for oh so long. This event is cancelable in projector files to show prompts first, but when compiled as a SWF, this event is dispatched, and there is enough time for split seconds of code to be run before the computer kills the SWF. Once the onClose has started, there is no longer access to the stage or mouse, and sound or the display content can no longer play, and is invisible to the user. This time should instead be used for closing network connections and saving any outstanding sharedObjects.


Perhaps it would be better to rename this class Container, since this is the actual window the SWF is running in. EDIT: I realize now, a hundred lines later, that this was a poor name, however, instead of digging through all my writing, I'll just leave it.


STAGED CLASS (here, staged is a statement, and not a class name)
When compiling a project, you can name one class "staged", meaning this is the main class that shows up and starts when the SWF is run. This replaces the stage, and can be any type of class.
public staged class MainTimeline extends Sprite
This class is considered "God", and is similar to the Document Class. It is the parent of all, and is just required to extend any type of DisplayObject. (It doesn't have to be a container at all for unique purposes. You could have the staged class extend Button or something. Of course, this limits you to not being able to add children in the traditional way, but developers might have their reasons for not extending a DisplayObjectContainer descendant)

Perhaps this class completely takes the roll of the stage, so DisplayObjects on the display list still have a "stage" property which points to this class. I would call the property "main" or "root" or something more fitting, as stage might confuse users with the old system. However, I believe this is a bad idea, and since classes have access to most properties they will ever need, there should be no reason why they need a reference to this class, especially if they aren't even on the display list.

This class is not static, but can be initialized, which is what is done when it is externally loaded into another class containing a "staged" class. (more on that later)


ACCESSING THE CONTAINER CLASS
Since "Container" is static, all classes can at any time read from it, weather or not they are on the display list. This means that previously difficult to reach properties like "stageWidth" and "stageHeight" will now be readily available.

It is possible (and might be a good idea if) only the staged class is allowed to set properties (such as align, scaleMode, etc). This ensures the properties don't conflict and are set in 8 different locations by accident.

If implemented, it would be quite simple to catch these errors while typing, or at least at compile time. The only time the error wouldn't appear immediately and would instead need to be caught at runtime once it reaches that line is if the user is tricky, and hopefully they never try to do this. It definitely wouldn't happen accidentally:
var contRef:Class = Container;
contRef["width"] = 600;


CATCHING EVENTS

Mouse events: Make Mouse an EventDispatcher, overload addEventListener() in a way it always adds a weak reference, no matter what.Perfect. I was originally thinking of a "Global" class which catches and dispatches all bubbling events, but separating the global events into their own class is even better. And then if you want to listen for events to specific display objects and not global, you add listeners directly to them like normal.

Same thing for a KeyBoard class would be really beneficial. Then you could have the option of adding event listeners like this instead:
KeyBoard.addEventListener(KeyBoard.RIGHT, movePlayer);
KeyBoard.addEventListener(KeyBoard.P, pauseGame);

I'm actually working on a class where you can add event listeners like that, but it's going pretty slow...


LOADING EXTERNAL STAGED CLASSES
Ah, here is the pride and glory of this setup, where everything shines. I believe this is actually quite similar to how Flash runs right now, though this handles the stage a little differently. By default, whenever a staged class is initiated, it is considered to run as if it were inside of a little box, with all classes it creates running inside of it. (I believe that's what's considered now a separate "Application Domain"). This external SWF (or even class) also has it's own separate Container class (meaning the stage size the external class reads might vary from the actual stage size), as well as separate Mouse and KeyBoard classes, which only capture events which happen inside of that little box.

By default, if one of these SWFs are loaded externally, it is very limited in what it can do by changing the Container class properties such as going fullscreen etc. or changing the size. Whenever a external staged class tries to modify a Container property, an event is dispatched (and possibly bubbled in order to reach the topmost staged class or something. Or perhaps the event is directly dispatched by the topmost Container class) which describes what the external class is trying to do.

The topmost staged class can then listen to this event, and may choose to grant this request by manually applying the changes to the container class.
To make an example, the external class here is allowed to go to full screen, but not allowed to set the stage align:

//a SWFLoader is like a Loader, but has been optimized for staged classes
var externalStagedClass:SWFLoader = new SWFLoader(urlRequest, useGlobalApplicationDomain:false, startImmediately:true});
this.addChild(externalStagedClass);
externalStagedClass.addEventListener(PropertyEvent .CHANGE, onPropChange);
function onPropChange(event:PropertyEvent):void
{
if (event.property == "fullScreen")
{
Container.fullScreen = event.value;
}
else if (event.property == "align")
{ trace("The SWF tried setting the stage alignment. I don't feel like it."); }
else
{ trace("Miscellanious property was set, so it was ignored"); }
}

Even if the staged class is not put on the display list immediately, it still thinks it's on the display list, because it has it's own display list and runs independently of the SWF it is being loaded into. It is possible to load the SWF first, and then start it manually once it's visible on the main display list by setting the startImmediately parameter to false, and manually using the start() command. Note that a new Application Domain and all information is not crated until AFTER the start command has been run.


Note that if you initialize a staged class into your project (somewhere inside of another staged class), weather or not you loaded it in dynamically or crated it using it's original AS file, it will always create it's own little box, and use it's own static properties.

However, it is possible to open an external SWFs and use staged classes, but treat them as if they were any regular old DisplayObject, not creating a new Application Domain for it. This would be accomplished by passing a parameter into the loader, or perhaps setting a property on the class or something. That part still needs a little working out.

Be warned, a poorly designed staged class might not appreciate or take into account that it might no longer be the topmost class, and might no longer have direct control over the Container class, however, it will still always have access to the stage dimensions and alignment and will be able to catch global events without any problems. The class will still dispatch events whenever it tries to set Container properties, though. It might also be a good idea if all staged classes contain a readonly Boolean property "isTopmost".



Have I made myself clear on what I am thinking? I'll gladly explain or think through any parts that are still unclear, about 40% of this I thought about while writing...



Anything I haven't thought of that won't work, or anything that needs revisions? Sorry to hijack your thread, wvxvw, I can move this elsewhere... :ponder: I wonder if anyone has enough time on their hands to read through all this. I guess "abbreviated version" no longer applies. I got carried away.

sekasi
December 4th, 2009, 12:40 AM
hasOsFocus and totalRunTime already exists, btw. Just not quite like that. ;p

IQAndreas
December 4th, 2009, 03:56 AM
hasOsFocus and totalRunTime already exists, btw. Just not quite like that. ;p
Well, as far as I know, it dispatches an event whenever it's lost focus, and when it regains foucs, but is there any exact property that lets you know the current focus at any given time?

And as for totalRunTime, the reason it is separated (or copied) onto the Container class is to emphasize and reinforce that staged classes will have their own instance of it, meaning totalRunTime will be different for it than the SWF that is doing the loading.

You can still use getTimer, but I believe that should be separated off as more of an application property.

wvxvw
December 4th, 2009, 06:25 AM
AFAIK, this functionality is being worked on, since there is a remark on LoaderContext class allowBytecodeExecution, which says that it's going to change some time, possibly soon.
I think that in loading scenario there should exist a possibility to describe the permissions for the loaded SWF in more peculiar way, then it is done right now. I.e. instead of having single


Security.allowDomain("domain");

which will either grant or deny all the permissions at once, probably make it


Security.alowDomain("domain",
SecurityOptions.ALLOW_FULLSCREEN | // defaults to the parent settings
SecurityOptions.ALLOW_DISPLAY_LIST_ACCESS | // true by default
SecurityOptions.ALLOW_BYTECODE_ACCESS | // true by default
SecurityOptions.ALLOW_CROSSDOMAIN_REUSE | // false by default
SecurityOptions.ROUTE_EXCEPTIONS | // true if global error handler exists
SecurityOptions.ROUTE_EXTERNAL_INTERFACE_CALLS // false by default
);
Where:

SecurityOptions.ALLOW_FULLSCREEN would be the same as allowFullScreen of the <object/> tag.
SecurityOptions.ALLOW_DISPLAY_LIST_ACCESS would either grant or deny permissions to traverse parent's display list, adding and removing objects to / from it etc.
SecurityOptions.ALLOW_BYTECODE_ACCESS would allow the foreign SWF access to assembly of the loading SWF.
SecurityOptions.ALLOW_CROSSDOMAIN_REUSE would allow the loaded SWF to enjoy the same permissions already obtained through loading crossdomain policy files by the loading SWF (I'm blur on this issue right now, really not sure on what happens if the loaded SWF will try to access API that already negotiated with the loaded SWF and granted permission, I assume, that if it's not the same application domain, then by default the loaded SWF would need to negotiate on it's own).
SecurityOptions.ROUTE_EXCEPTIONS would mean that the unhandled exception that happen in loaded SWF would be routed into the global error handler of the loading SWF (along with error events).
SecurityOptions.ROUTE_EXTERNAL_INTERFACE_CALLS would mean that the loading SWF will act as an external part of ExternalInterface for the loaded SWF - this will help to resolve situations when loaded SWF overrides callbacks added to the loading SWF for example.

Well, just another though...

* Otherwise developers are forced to load SWFs into the same application domain if they need normal plugin functionality from the loaded SWF... which is definitely insecure when dealing with unknown foreign SWFs...