DEPRECATED Pattern sample
Just don't use that.
The bigest change in new window management system is to deprecate the usage of module pattern. In my opinion, the main reason to use module pattern is to enable private variables. But this is a strong requirement, and the drawback it brings to us is much more than the value of private variables and functions.
The main problem is the private variables/method inside module pattern are not that easy to test. Yes, you could still work hard to figure out all the code path to trigger the inner function, but that is in-efficient and a simple function in object literal saves your time.
The initial purpose of this change is described in the comment.
As above, it's some kind of mozBrowser API wrapper class.
In the very beginning, the main purpose is to create a mozbrowser iframe and load the URL of the app into the iframe.
But after the system grows, plenty of features are added,
- open/close transiton on the iframe, switching app.
- modal dialog from window.alert()
- more mozbrowser events to represent the state of the app. An object to represent the state of each iframe is necessary. Moreover:
- DOM element create/destroy per instance
- Event handler per instance
- Diverged object type by inheriting the base class and override the method.
We are using
Object.create to create the different window now.
Remember to reassign the constructor after
otherwise the reference to
this.constructor will point to the super class.
A mixin is another object/class which is "dumped" into current class. The object could be standalone, swapped in the future, or be dumped into another class.
The way how we dump is simple: iterate the mixin object and append it to the target prototype.
If you look at the Mixin Pattern sample closer, you will find browser_mixin.js is also a sample of proxy pattern. What is a proxy? It creates one more layer to access the mozBrowserAPI: setVisible, getScreenshot, reload... on the mozBrowser iframe. The mediator of AppWindow should not call these API directly but encouraged to call the proxy. The proxy will protect the API at certain degree.
This pattern might be a most used one but people won't they are using it. Facade encapsulate the detail inside a function and the user of the function don't need to know the detail.
When we want to bring an app from background to foreground,
we do the following:
- Attach a repaint event listener to the browser iframe and remove the screenshot layer after repaints.
- Call browser API to bring the iframe to foreground.
- Remove the
aria-hiddenattribute from the DOM element to make sure the screen reader see this.
The caller of setVisible(true) don't need to know the detail but just use it.
Here is one another example using facade pattern.
Knowing an AppWindow is ready to perform the opening animation is complex and full of business logic:
- If it's never loaded or is protected by screenshot overlay, callback right away.
- If it's loaded once, try to trigger the repaint before we open it; otherwise the user will notice the screen blinks.
- Create a timer to protect this operation. Infinite wait is not tolerable.
The user of the
ready()don't need to worry about the logic but just give the callback function.
Exactly, there's no strict factory pattern.
Whenever gecko/platform asks us to open a new window, we will get an event.
AppWindowFactory will instantiate a proper AppWindow instance from the event detail,
or bypass the event to the proper AppWindow's
Although HomescreenWindow is instantiable, we still want to get the same instance each time we need it. HomescreenLauncher provides an interface to query the instance.
Finite State Machine Pattern
I am not using a general state machine but having a specific transition state machine to deal with the open/close request on the AppWindow object.
A regular transition could be defined as 4 states: closed, opening, opened, closing.
What we care:
- State change needs to tell others.
- We are using animationend event to switch from opening state to opened state; and from closing state to closed state.
- But sometimes, we will lose the animationend event. We cannot wait infinitely in opening state and in closing state. We need to create timeout event to force the state change.
- We are bypassing the opening and closing states if the open/close animation is requested as 'immediate'.
AppWindowManager is the mediator of all
All AppWindow don’t need to know each other when they are trying to do UI operation.
If the operation affects the other classes, they should publish a request. Mediator will check for them.
A real use case is, when an instance requests to open, we need to animate current displayed instance. But according to the rule "module should not know each other's state", we(AppWindow) are not supposed to do close operation to other instance and we don't even know who is currently opened instance.
AppWindowManager(AppWindowMediator) should deal with the open request and make sure the current opened instance is closing.
The observer pattern is highly used to prevent tight coupling. However, we are having a more complex event machenism right now. Let's step by step look into it.
We are using DOM events to realize the publish/subscribe pattern right now.
The subscriber don't need to know the DOM element to attach the event listener because the event will be bubbling to the
The publisher is encourged to have an event prefix to represent itself. Usually it's the class name.
Also note the event detail including the whole object so we could access any information we want in the subscriber, even calling the method.
2 phase observer
AppWindow is not only a simple module but also plays as a mediator of all sub modules inside it.
The event processing is indeed including two phases:
- Private event phase - module inside the AppWindow will notice the private event.
- Public event phase - module outside the AppWindow will notice the public event after all private events are resolved.
Let's go through the sample.
_opened subcriber is handled before
This is to ensure the UI consistency:
- When we want to tell others something happens, we need to settle everything corresponding to the event before going public.
- When something happens but it's not public enough, use
broadcast()to tell the modules which are managed by us. It would be dispatched on
- The underline before private event is a convension. Internally we don't need a class-like prefix.
To understand the two phase observer, let's treat AppWindow as the local government, and the mediator of AppWindow is the central government.
The central government will know something happens in local government in the long run, but the local government is responsible for manipulating the events at first. In the local government there are many different departments which have different responsibilities. Some of them might be interested in the same thing but has different behavior when they know the event happens.
Mediator as Observer
For some honored reason, AppWindow is playing the role of Mediator as its opened window now.
That is to say, if an app is using
window.open(), the newly created AppWindow instance is managed by the caller instance.
But note we are using DOM events to pass the events, if we don't do something, the observer of the outer instance will be invoked if the inner instance triggers some event.
What we will do? Amend the subsribe/publish function() is the solution.
Mediator could stop the event propagation if necessary.
Live pattern sample
Honestly I don't know this is a pattern before I see it.
We are using the command pattern in the realization of finite state machine.
When we want to transite the state from A to B by event Z,
we will trigger 3 functions:
- Leaving A State
- Handle Z Event
- Entering B State
Using switch or if-else is something really bad especially if you are having more states and more events. So we are using the same semantic to invoke the function:
The centralized event handler in AppWindow uses the same idea to enhance handleEvent interface.