Assume we are implementing a module which has its own localization files (.properties)

import '../scss/index.scss';
import '../localization/my-module.en-US.properties'

We need to add a new file loader in webpack.config.js

  module: {
    loaders: [
      {
          test: /\.(ttf|eot|png|svg|woff(2)?)(\?[a-z0-9]+)?$/,
          loader: 'file-loader',
      },
      {
          test: /\.properties$/,
          loader: 'file-loader?name=[name].[ext]',
      }
    ]
  }

Then we will get the same file name in output folder (/dist), and so far we can include that in index.html because mozL10n needs to parse the document.head to know about the localization files.

<link rel="localization" href="dist/my-module.{locale}.properties">

Further improvement

Once we import a new module which is having localization files, we need to add all the localization files into index.html. It seems redundant. We should make webpack be able to generate total properties just like ExtractTextPlugin.

Prerequisites

I am designing a transition component which only cares about open/close method from its parent and update the class + data-transition-state on its own element. Let's call it XWindow, and its controller is XWindowManager

XWindow

var XWindow = React.createClass({
    getInitialState: function() {
    return {
        transition: 'closed',
      animation: ''
    }
  },
  onAnimationEnd: function() {
    this.setState({
        transition: this.state.transition === 'closing' ? 'closed' : 'opened';
    });
  },
  componentDidMount: function() {
    this.getDOMNode().addEventListener('animationend', this.onAnimationEnd.bind(this), false);
  },
  open: function(animation) {
    this.setState({
        transition: 'opening',
      animation: animation
    });
  },
  close: function(animation) {
    this.setState({
        transition: 'closing',
      animation: animation
    });
  },
  render: function() {
    return <div className={"x-window " + this.state.animation} data-transition-state={this.state.transition}>
                     {this.props.children}
                 </div>
  }
});

XWindowManager:

var XWindowManager = React.createClass({
    getInitialState: function() {
    return {
        windows: []
    }
  },
  openXWindow: function(content) {
    var contents = this.state.windows;
    contents.push(content);
    this.setState({
        content: contents
    });
  },
  render: function() {
    return <div className={"x-window " + this.state.animation} data-transition-state={this.state.transition}>
                     {this.props.children}
                 </div>
  }
});

App

var app = React.render(<XWindowManager />, document.body);
app.openXWindow(<Inbox />); // Inbox, Thread was defined somewhere.

app.openXWindow(<Thread />);

How to avoid re-rendering Inbox when Thread is opened?

Usally, whenever the top most component is updated by setState, every sub component will be updated, too. React won't perform the real DOM operation if it founds the newly Virtual DOM is the same as the existing Virtual DOM. However, I just don't want any sub component to even compare the Virtual DOM nor to compare the next state and the current state. It's really unneccessary.

How about to tell the children please don't update during the transition?

This just solves part of the problem. Whenever a XWindow is closed and then opened again, the subcomponent of the XWindow will be updated.
Also, it makes the implementation of the child component become dirty: needs to know the parent's transition state to do the update instead of only its own state.

Use CSSTransitionGroup in React.addon

React has a CSSTransitionGroup component to help us to do the transition; however, it does not solve this problem as well. Every children in CSSTransitionGroup will be asked to update once a new children is added.

Final simple answer: Create a Gap Component Between Transition Component and Content!

The solution I come up with is pretty simple and make thing really clear:
to implement a gap component which will always not update.

var ShadowWindow = React.createClass({
    shouldComponentUpdate: function() {
    return false;
  },
  render: function() {
    return <div className="shadow-window">
                     {this.props.children}
                 </div>
  }
});

Then we just need to insert the gap component between XWindow and the content:

// Modify XWindow

  render: function() {
    return <div className={"x-window " + this.state.animation} data-transition-state={this.state.transition}>
                     <ShadowWindow>{this.props.children}</ShadowWindow>
              </div>
  }

Now the XWindow and the content is totally separated. No matter how we transition the XWindow, the content won't be asked to update anymore. It could live with its own state.

Hope this helps :)

Prerequisite

Module start might be asycnchronous

Each module has an initing phase, and the phase might be asynchoronous because you want to do something asynchronously. For example, a module may need to read some configuration from db to set up the environment; we will say this module is not ready yet if it's still in the init phase.

Service is playing the role of message queuing between modules

Servicejs
https://github.com/alivedise/servicejs

We call this 'Durable subscriber' in the messaging pattern.
That is, event dispatching will be lost if the subscriber is not ready when the dispatcher loads before the subscriber. To avoid this, we need a third module to queue the request of the dispatcher before the subscriber is able to register to the requests.

How to know the application is fully loaded if we decide to delay something?

A rough idea here is let every module to notify the centralized controller when it starts.

var A = {
    start: function() {
    Service.request('notifyStarted');
  };
};
A.start();

But this does not make sense because we need to maintain a list of all the modules in the centralized controller.

Using Promise

var A = {
    start: function() {
    return this.asyncOperationWhoWillReturnPromise();
  }
};
A.start().then(() => {
    console.log('A is done');
});

A promise chain of start operations

A module may have some sub modules to load and start in the init phase. If we connect all the start function using Promise, we could have a simple start function call from the application root and simply knows when all the sub modules are started.

var App = {
    start: function() {
    return lazyLoadModule(['A', 'B', 'C']).then(() => {
      return Promise.all([
        this.subModuleA.start(),
        this.subModuleB.start(),
        this.subModuleC.start()
      ]);
    });
  }
};
App.start().then(() => {
    console.log('Cool, everything is done.');
});

Tricky: Define critical modules and side modules but keep the start promise

For reason of performance we don't want everything to be loaded together in startup. That is to say, we need to figure out what's the critical module and what's the side modules.

var App = {
    start: function() {
    return lazyLoadModule(['A', 'B', 'C']).then(() => {
      return Promise.all([
        this.subModuleA.start(),
        this.subModuleB.start(),
        this.subModuleC.start(),
        this.loadWhenIdle('D', 'E', 'F');
      ]);
    });
  },
  loadWhenIdle: function(modules) {
    return Service.request('schedule').then(() => {
        return lazyLoadModule(modules);
    }).then(() => {
        return modules.map(function(module) {
        return module.start();
      }, this);
    });
  }
};
App.start().then(() => {
    console.log('Cool, everything is done.');
});

The loadWhenIdle function call will not being resolved once the consumer of 'schedule' Service tell it now is time to do your operation. So now we need a new module to schedule the non critical operations. It should know the timing that all critical modules are loaded and started, and then execute the queued requests.

var CriticalLeafModule = {
  start: function() {
    // I am the last important module in the tree

    Service.request('notifyVisuallyLoaded');
  }
};
var Scheduler = {
    start: function() {
    Service.register('notifyVisuallyLoaded', this);
    Service.register('schedule', this);
  },
  schedule: function() {
    return new Promise(function(resolve) {
      if (!this.visuallyLoaded) {
        this.queuedTasks.push({
            resolve: resolve
        });
      } else {
        resolve();
      }
    });
  },
  notifyVisuallyLoaded: function() {
    this.visuallyLoaded = true;
    this.queuedTasks.forEach(function(task) {
        task.resolve();
    });
    this.queuedTasks = [];
  }
};

Real case: System startup timeline

I am working on a non-trivial patch to refine the system app launch time(See https://bugzilla.mozilla.org/show_bug.cgi?id=1094759) now; according to raptor, it's improving launch time by 8~10secs.

Demo

https://www.youtube.com/watch?v=4ZiqaA-VKjM
(Left: with patch; Right: current master.
From the progress bar, you could see the left one is about 12sec faster than the right one.)

Let's see what we did in the past

  1. Whenever a new feature in system is done, find somewhere in the header to insert the new scripts.
  2. If it needs an entry point, put it somewhere in bootstrap.js
  3. Leave it if the ordering of the module loaded does not cause error.
  4. If there is an error? Re-arrange the scripts until the error disappears.

The result is the scripts being added grows unbelievably long and the device boot time is slower and slower.

To improve this

  1. Find out the critical launch path - what is necessary to boot into homescreen or FTU or lockscreen?
  2. Leave every module else as optional and find out a good timing to load them after entering homescreen.
  3. Build dependency tree to make sure dynamic load won't cause race.

Critical launch path

The first app to be launched on a phone is either 'First Time Usage' or 'Homescreen'. To decide which to launch, we need to read several asynchronous values:

  • deviceinfo.os
  • deviceinfo.previous_os
  • ftu.manifestURL
  • homescreen.manifestURL from settings db
  • ftu.enabled from async storage

In the past most of the value fetching is done in FtuLauncher module; however, it's running at the same time as other non-critical scripts are busy running.


To refine this, we removed most scripts from the header, and only let one module named for launcher to get these values at the startup, to make sure all of the boot preference are done before the real bootup procedure.

Dependency Tree Architecture

Core

After launcher decides what to do, it would launch the system core.

The task of system core is simple: it will launch every subsystem which is responsible to certain hardware API. Every subsystem has a core, too, to load all its dependency modules to make sure we control the hardware well.

Hardware subsystems

The hardware subsystem is expected to be independent running from all other concurrent subsystems. Each subsystem has a core as well, which plays as a "smart" loader and decides the priority to load modules, also sometimes cooperate the child modules if necessary.

Example: MobileConnection Subsystem

Window Management Subsystem

This subsystem is playing the most important role of the whole operation system; as you could imagine, it's including most modules and expected to be most complex.
For detail of window management development, see slides here
https://docs.google.com/presentation/d/1nx9k5Gdetnt7dqoX2iehfDXeM7H3be3dMnMYAwZjzRY/edit?usp=sharing

Cooperate subsystems

Service

Each subsystem is expected to be independent from other subsystems.
However, sometimes we still need to query or request other modules across subsystems;
'Service' is created to serve as the mediator between all modules who needs other's help.

Service.query('isHeadsetConnected'); // undefined

Service.registerState('isHeadsetConnected', SoundManager);
SoundManager.isHeadsetConnected = true;
Service.query('isHeadsetConnected'); // true
// Launcher

Service.request('FtuLauncher.launch', ftuManifest);
// FtuLauncher

Service.register('launch', this);
// When being registrated, the queue request would be procceeded right away.
Hierarchy management

Hierarchy Manager is designed to resolve these problems:

  • Some UI events are "top most only"
  • When topmost UI are changed, we need to inform other UI for that.
  • Some UI events are "radiolucent" Before we could lazily load every modules, the biggest problem is several UI components are listening to some UI events by the ordering of DOM event registration.
    // certain_ui.js who needs home button event
    
    start: function() {
      window.addEventListener('home', function(evt) {
        evt.stopImmediatePropagation();
      });
    }
    
    The problem is this kind of event priorization implementation needs to start the module event registration in certain ordering. This makes lazy load difficult because the ordering of loading scripts are not guranteed.

The proper fix is to have a mediator to deal with all UI events priority according to a predefined hierarchy map. With that, an UI module don't need to know the existence of other UI modules, but instead it would be noticed once there is a higher priority one activated/deactivated.

// Lazy loaded module A

Service.request('registerHierarchy'); // join hierarchy competition

this.publish('activated');
// Lazy loaded module B

Service.request('registerHierarchy');
this.publish('activated');

// Assume UI priority B > A:

// A.setHierarchy(false); will be called by HierarchyManager to notify it to do the

// blur() set DOM element's aria-hidden to true because it loses the top most view.

One key point is, without knowing each other, an UI module could be safely started/stopped to join the hierarchy competition, which also means it's safe to remove unwanted UI module or add new UI module somedays, or for certain device type.

BaseModule

During the design of the new architecure, I found that we almost have the same pattern of the behavior of a module:

  1. debugging method
  2. settings observer
  3. event publish/subscribe
  4. start/stop mechanism
  5. rendering if it's an UI module

To group all these patterns together, BaseModule is designed to support them by configuration on the constructor. Each part could be swapped out if necessary. For example, event publish/subscribe is ready to be switched to pubsub library or any other event registration library if necessary.

Settings Observer

SettingsCore is implemented to replace all navigator.mozSettings libraries in the current system to provide a more generic interface to observe the settings. Moreover, an settings should be only observed by a certain settings manager module, and it should notify others when it observes the settings is changed instead of letting every module to observe the same settings at the same time. It's similar to put a cache between settings db and the observer to make sure the getter is getting the value without doing the asynchorous operation each time it needs the value.

Event pub/sub

Event is an important part to decouple modules; currently we are using DOM events to dispatch the event and mostly on the window object. With a centralized event interface it's possible to switch the DOM event into any other 3rd party event library. This is especially important if we are migrating the UI-less modules into ServiceWorker because we cannot access DOM there.

Start/stop mechanism

The ideal world is everything could be started automatically and don't need to care others; however, in real world we need to make sure something is ready before we really init a module.
The paring start/stop's work is to

  1. Register/un-register pre-defined events in start/stop
  2. Observe/de-observe pre-defined settings db keys in start/stop
  3. load+start/stop predefined child modules in start/stop
  4. (optional) Render DOM elements if necessary

Future work: supprting multi-device

To achieve the goal: 'one system to serve multiple devices', there are two solutions

  1. Build time solution: define build flags and inject/generate modules according to the config.
    make DEVICE=phone
    make DEVICE=tv
    
  2. Run time solution:
    if (Service.query('deviceConfig') === 'tv') {
      LazyLoader.load('tv/WindowManager');
    }
    
    However, it might be insufficient to only use 1. for the second display case
    To support the remote display which might has different dimension, we still need to have the ability to load a different UI set at run time.

Ideally, the goal is to split UI-less modules and UI modules; each core in the subsystem is expected to load the UI-less part then the UI part per device type or the request from the remote display; it's still vague at this point, but IMO we have two ways to deal with multiple devices:

  1. Configurable module WindowManager might be a good example. In the current design, it's possible to change only LayoutManager to adaptor multiple windows. (There was an old side project of mine to implement multiple windows, see https://github.com/alivedise/gaia/commit/beee95717b5af037ca60100352e38b0889b2ced8)
  2. Centralized render method per configs If everyone is using BaseUI/BaseModule, it's possible to have the render method and the view method being managed by a ViewManager will will load view for phone or view for tablet accordingly.

Conclusion

  1. Making each module less dependent from others if possible.
  2. Only parent is responsible to load/start/stop child in the timing being designed.
  3. The launching timing of non-blocking modules should not affect each other. A.k.a., the ordering does not matter and everything will work if any new module is started/stopped. Requests before an module which provides the service would be queued until it is started.
  4. Generic interface for UI-less module(BaseModule) and UI module(BaseUI).

Module Pattern

DEPRECATED Pattern sample

https://github.com/mozilla-b2g/gaia/blob/v1.3/apps/system/js/window_manager.js

Pattern detail

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.

Creation Pattern

Pattern sample

https://github.com/mozilla-b2g/gaia/blob/53649e7abba84b24bd2b9a391ef92ab7029d675f/apps/system/js/app_window.js#L42

Pattern detail

The initial purpose of this change is described in the comment.

  /**
   * AppWindow creates, contains, manages a
   * [mozbrowser](https://developer.mozilla.org/en-US/docs/WebAPI/Browser)
   * iframe. AppWindow is directly managed by AppWindowManager,
   * by call resize(), open(), close() on AppWindow.
   *
   * Basically AppWindow would manipulate all mozbrowser events
   * fired from the mozbrowser iframe by itself and show relevant UI.
   */

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.

  window.addEventListener('launchapp', function(evt) {
    var iframe = document.createElement('iframe');
    iframe.src = evt.detail.url;
    document.body.append(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.

var AppWindow = function() {
};
AppWindow.prototype.open = function() {
};

var HomescreenWindow = function() {
};
HomescreenWindow.prototype = Object.create(AppWindow);
HomescreenWindow.prototype.constructor = HomescreenWindow;

Remember to reassign the constructor after Object.create,
otherwise the reference to this.constructor will point to the super class.

Mixin Pattern

Pattern sample

https://github.com/mozilla-b2g/gaia/blob/1de61ebc86e0c8a381f7e169fe01b764a6e03c9e/apps/system/js/browser_mixin.js

Pattern detail

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.

// browser_mixin.js, a standalone file.

var BrowserMixin = {
  a: 0,
  b: function() {}
};

// app_window.js

var AppWindow = function() {
};
AppWindow.addMixin = function(mixin) {
  for (var k in mixin) {
    AppWindow.prototype[k] = mixin[k];
  }
};
AppWindow.addMixin(BrowserMixin);

Proxy Pattern

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.

var BrowserMixin = {
  reload: function() {
    if (!this.browser.element) {
      return;
    }
    this.browser.element.reload();
  }
};

Facade Pattern

Pattern sample

https://github.com/mozilla-b2g/gaia/blob/53649e7abba84b24bd2b9a391ef92ab7029d675f/apps/system/js/app_window.js#L217

Pattern detail

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-hidden attribute 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.

var AppWindow = function() {
};
AppWindow.prototype.ready = function(callback) {
  this.debug('requesting to open');
  if (!this.loaded || this._screenshotOverlayState == 'screenshot') {
    this.debug('loaded yet');
    setTimeout(callback);
    return;
  } else {
    var invoked = false;
    this.waitForNextPaint(function() {
      if (invoked) {
        return;
      }
      invoked = true;
      setTimeout(callback);
    });
    if (this.isHomescreen) {
      this.setVisible(true);
      return;
    }
    this.tryWaitForFullRepaint(function() {
      if (invoked) {
        return;
      }
      invoked = true;
      setTimeout(callback);
    });
  }
};

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.

Factory Pattern

Pattern sample

https://github.com/mozilla-b2g/gaia/blob/ea96ccb102b67e6cd09903271999e30d3e4334f0/apps/system/js/app_window_factory.js

Pattern detail

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 childWindowFactory.

Singleton Pattern

Pattern sample

https://github.com/mozilla-b2g/gaia/blob/ea96ccb102b67e6cd09903271999e30d3e4334f0/apps/system/js/homescreen_launcher.js#L230

Pattern detail

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

Pattern sample

https://github.com/mozilla-b2g/gaia/blob/ea96ccb102b67e6cd09903271999e30d3e4334f0/apps/system/js/app_transition_controller.js

Pattern detail

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'.

Mediator Pattern

AppWindowManager is the mediator of all AppWindow instances.
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.

var AppWindow = function() {
  this.request('open'); // Let's visit observer later.

  // The instance is requesting to open by passing itself to the mediator.

};

var AppWindowMediator = function() {
  window.addEventListener('apprequestopen', this);
};
AppWindowMediator.prototype.handleEvent = function(evt) {
  // Mediator plays the role as 'permission check'

  if (this.readyToOpenANewApp) {
    evt.detail.open();
  } else {
    // wait until we are ready and do the open operation.

  }
};
var a = new AppWindow();

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.

Observer pattern

Pattern sample

https://github.com/mozilla-b2g/gaia/blob/ea96ccb102b67e6cd09903271999e30d3e4334f0/apps/system/js/app_window.js#L1189

Pattern detail

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.

Simple Observer

We are using DOM events to realize the publish/subscribe pattern right now.

// publisher

var AppWindow = function() {
  this.element = document.createElement('div');
  document.body.appendChild(this.element);
  this.publish('created');
};
AppWindow.prototype.PREFIX = 'app';
AppWindow.prototype.publish = function(name) {
  this.element.dispatchEvent(new CustomEvent(this.PREFIX + name, {
    detail: this
  }));
};

// subscriber

window.addEventListener('appcreated', function(evt) {
  console.log(evt.detail);
});

The subscriber don't need to know the DOM element to attach the event listener because the event will be bubbling to the window.
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.

// sub_module.js

var SubModule = function(app) {
  this.app = app;
  this.app.element.addEventListener('_opened', function() {
    console.log(Date.now());
  });
};

// app_window.js

var AppWindow = function() {
  this.element = document.createElement('div');
  document.body.appendChild(this.element);
  this.subModuleA = new SubModule(this);
  this.publish('created');
  // Perform the opening animation...

  this.publish('opened');
};

AppWindow.prototype.PREFIX = 'app';
AppWindow.prototype.publish = function(name) {
  this.broadcast(name);
  this.element.dispatchEvent(new CustomEvent(this.PREFIX + name, {
    detail: this
  }));
};
AppWindow.prototype.broadcast = function(name) {
  this.element.dispatchEvent(new CustomEvent('_' + name, {
    detail: this,
    bubble: false
  }));
};

// somewhere.js

window.addEventListener('appopened', function() {
  console.log(Date.now());
});

The _opened subcriber is handled before appopened subsriber.
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 this.element.
  • 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.

// local government

function ModuleA(app) {
  this.app.addEventListener('_onfire', function() {
    // pull water

  });
}
function ModuleB(app) {
  this.app.addEventListener('_onfire', function() {
    // rescue

  });
}
var app = new AppWindow();
var departmentA = new ModuleA(app);
var departmentB = new ModuleB(app);

// central government

window.addEventListener('apponfire', function() {
  // do something..

});

app.publish('onfire');
// The _onfire observer in moduleA and moduleB

// will be invoked before the global observer knows.
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.
https://github.com/mozilla-b2g/gaia/blob/ea96ccb102b67e6cd09903271999e30d3e4334f0/apps/system/js/app_window.js#L990

Command Pattern

Live pattern sample

https://github.com/mozilla-b2g/gaia/blob/ea96ccb102b67e6cd09903271999e30d3e4334f0/apps/system/js/app_transition_controller.js#L109

Pattern detail

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:

  this['_leave_' + previousState](evt);
  this['_handle_' + evt]();
  this['_enter_' + currentState](evt);

The centralized event handler in AppWindow uses the same idea to enhance handleEvent interface.
(https://github.com/mozilla-b2g/gaia/blob/ea96ccb102b67e6cd09903271999e30d3e4334f0/apps/system/js/app_window.js#L994)

一直以來在儲存一個實體化的物件時,我習慣用 array。

// Add

var instances = [];
var a = new A();
instances.push(A);
// Remove

/* 對陣列來說增加實體當然沒有問題,不過在移除時的 code 會長這樣 */
arr.splice(arr.indexOf(instanceToRemove), 1);
/* 可讀性不高 */

同事提醒我有 Map/WeakMap 可以用 key-value 來存物件,

// Add

var instances = new Map();
var a = new A();
instances.set(a, a);
// Remove

instance.delete(a);
/* It's pretty simple now */

Map on MDN
http://mdn.io/map

var spy = sinon.spy(window, 'MyApp');
// Test if MyApp is instantiated

assert.isTrue(spy.calledWithNew());
// Get the instance

console.log(spy.getCall(0).returnValue);

I got my geeksphone revolution last week.

Its default operating system is android, you need to use its tool to flash FirefoxOS.
From Settings->About device->Install another OS.
Then you will get a v1.3 custom geeksphone build.

I'd like to flash it into lastest gaia, but after doing make reset-gaia I found that adb doesn't work anymore even I turned on the settings.

After try & error I think it's because the settings name is changed from v1.3 to v1.5 for adb enabling.
In v1.5, the settings name to enable adb is debugger.remote-mode
http://mxr.mozilla.org/mozilla-central/source/b2g/chrome/content/settings.js#456
But in v1.3, the name is devtools.debugger.remote-enabled.
The codebase of geeksphone is updated so we cannot use adb anymore.

A workaround is adding this settings in gaia/build/config/custom-settings.json

diff --git a/build/config/common-settings.json b/build/config/common-settings.json
index bda8436..0decaa3 100644
--- a/build/config/common-settings.json
+++ b/build/config/common-settings.json
@@ -206,5 +206,6 @@
    "wifi.disabled_by_wakelock": false,
    "wifi.notification": false,
    "wifi.connect_via_settings": false,
-   "wap.push.enabled": true
+   "wap.push.enabled": true,
+   "devtools.debugger.remote-enabled": true
 }

and then do make reset-gaia you will have a build with adb enabled.

Happy hacking!
I hope I could do some dogfooding from now on to improve some more.

Useful links

Geeksphone gaia
https://github.com/gp-b2g/gp-revolution-gaia
Geeksphone gecko
https://github.com/gp-b2g/gp-revolution-gecko
Geeksphone gecko diff
https://pastebin.mozilla.org/4712755

今天遇到的問題,什麼時候該用 stopPropagation 而什麼時候該用 stopImmediatePropagation ?

stopImmediatePropagation

https://developer.mozilla.org/en-US/docs/Web/API/event.stopImmediatePropagation

stopPropagation

https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation

簡單而言,stopPropagation 會防止上一層以上的元素聽到事件,stopImmedaitePropagation 有同樣效果再加上「在這個 addEventListener 之後註冊的 event handler 都不會被執行。」