Bug 1235123 - Part 1 - Make SystemAppProxy can handle multiple system app, r=schien
authorKuoE0 <kuoe0.tw@gmail.com>
Thu, 28 Apr 2016 15:18:03 +0800
changeset 295179 e3245a8dfacdefc22a21261c32e5129afc725dfb
parent 295178 c2da65057928abc7fb07c061c0581b743a7baec1
child 295180 2389fb0248dbb8184edfef0e06add6fe76bdaba2
push id75838
push usergachen@mozilla.com
push dateThu, 28 Apr 2016 07:19:49 +0000
treeherdermozilla-inbound@50ec1d8e5504 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersschien
bugs1235123
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1235123 - Part 1 - Make SystemAppProxy can handle multiple system app, r=schien
b2g/components/SystemAppProxy.jsm
--- a/b2g/components/SystemAppProxy.jsm
+++ b/b2g/components/SystemAppProxy.jsm
@@ -1,218 +1,377 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 
 this.EXPORTED_SYMBOLS = ['SystemAppProxy'];
 
+const kMainSystemAppId = 'main';
+
 var SystemAppProxy = {
-  _frame: null,
-  _isLoaded: false,
-  _isReady: false,
+  _frameInfoMap: new Map(),
   _pendingLoadedEvents: [],
   _pendingReadyEvents: [],
   _pendingListeners: [],
 
-  // To call when a new system app iframe is created
-  registerFrame: function (frame) {
-    this._isReady = false;
-    this._frame = frame;
+  // To call when a main system app iframe is created
+  // Only used for main system app.
+  registerFrame: function systemApp_registerFrame(frame) {
+    this.registerFrameWithId(kMainSystemAppId, frame);
+  },
+
+  // To call when a new system(-remote) app iframe is created with ID
+  registerFrameWithId: function systemApp_registerFrameWithId(frameId,
+                                                              frame) {
+    // - Frame ID of main system app is predefined as 'main'.
+    // - Frame ID of system-remote app is defined themselves.
+    //
+    // frameInfo = {
+    //    isReady: ...,
+    //    isLoaded: ...,
+    //    frame: ...
+    // }
+
+    let frameInfo = { frameId: frameId,
+                      isReady: false,
+                      isLoaded: false,
+                      frame: frame };
 
-    // Register all DOM event listeners added before we got a ref to the app iframe
+    this._frameInfoMap.set(frameId, frameInfo);
+
+    // Register all DOM event listeners added before we got a ref to
+    // this system app iframe.
     this._pendingListeners
-        .forEach((args) =>
-                 this.addEventListener.apply(this, args));
-    this._pendingListeners = [];
+        .forEach(args => {
+          if (args[0] === frameInfo.frameId) {
+            this.addEventListenerWithId.apply(this, args);
+          }
+        });
+    // Removed registered event listeners.
+    this._pendingListeners =
+      this._pendingListeners
+          .filter(args => { return args[0] != frameInfo.frameId; });
+  },
+
+  unregisterFrameWithId: function systemApp_unregisterFrameWithId(frameId) {
+    this._frameInfoMap.delete(frameId);
+    // remove all pending event listener to the deleted system(-remote) app
+    this._pendingListeners = this._pendingListeners.filter(
+      args => { return args[0] != frameId; });
+    this._pendingReadyEvents = this._pendingReadyEvents.filter(
+        ([evtFrameId]) => { return evtFrameId != frameId });
+    this._pendingLoadedEvents = this._pendingLoadedEvents.filter(
+        ([evtFrameId]) => { return evtFrameId != frameId });
   },
 
-  // Get the system app frame
-  getFrame: function () {
-    return this._frame;
+  // Get the main system app frame
+  _getMainSystemAppInfo: function systemApp_getMainSystemAppInfo() {
+    return this._frameInfoMap.get(kMainSystemAppId);
+  },
+
+  // Get the main system app frame
+  // Only used for the main system app.
+  getFrame: function systemApp_getFrame() {
+    return this.getFrameWithId(kMainSystemAppId);
+  },
+
+  // Get the frame of the specific system app
+  getFrameWithId: function systemApp_getFrameWithId(frameId) {
+    let frameInfo = this._frameInfoMap.get(frameId);
+
+    if (!frameInfo) {
+      throw new Error('no frame ID is ' + frameId);
+    }
+    if (!frameInfo.frame) {
+      throw new Error('no content window');
+    }
+    return frameInfo.frame;
   },
 
-  // To call when the load event of the System app document is triggered.
+  // To call when the load event of the main system app document is triggered.
   // i.e. everything that is not lazily loaded are run and done.
-  setIsLoaded: function () {
-    if (this._isLoaded) {
-      Cu.reportError('SystemApp has already been declared as being loaded.');
+  // Only used for the main system app.
+  setIsLoaded: function systemApp_setIsLoaded() {
+    this.setIsLoadedWithId(kMainSystemAppId);
+  },
+
+  // To call when the load event of the specific system app document is
+  // triggered. i.e. everything that is not lazily loaded are run and done.
+  setIsLoadedWithId: function systemApp_setIsLoadedWithId(frameId) {
+    let frameInfo = this._frameInfoMap.get(frameId);
+    if (!frameInfo) {
+      throw new Error('no frame ID is ' + frameId);
     }
-    this._isLoaded = true;
+
+    if (frameInfo.isLoaded) {
+      if (frameInfo.frameId === kMainSystemAppId) {
+        Cu.reportError('SystemApp has already been declared as being loaded.');
+      }
+      else {
+        Cu.reportError('SystemRemoteApp (ID: ' + frameInfo.frameId + ') ' +
+                       'has already been declared as being loaded.');
+      }
+    }
+
+    frameInfo.isLoaded = true;
 
     // Dispatch all events being queued while the system app was still loading
     this._pendingLoadedEvents
-        .forEach(([type, details]) =>
-                 this._sendCustomEvent(type, details, true));
-    this._pendingLoadedEvents = [];
+        .forEach(([evtFrameId, evtType, evtDetails]) => {
+          if (evtFrameId === frameInfo.frameId) {
+            this.sendCustomEventWithId(evtFrameId, evtType, evtDetails, true);
+          }
+        });
+    // Remove sent events.
+    this._pendingLoadedEvents =
+      this._pendingLoadedEvents
+          .filter(([evtFrameId]) => { return evtFrameId != frameInfo.frameId });
   },
 
-  // To call when it is ready to receive events
+  // To call when the main system app is ready to receive events
   // i.e. when system-message-listener-ready mozContentEvent is sent.
-  setIsReady: function () {
-    if (!this._isLoaded) {
+  // Only used for the main system app.
+  setIsReady: function systemApp_setIsReady() {
+    this.setIsReadyWithId(kMainSystemAppId);
+  },
+
+  // To call when the specific system(-remote) app is ready to receive events
+  // i.e. when system-message-listener-ready mozContentEvent is sent.
+  setIsReadyWithId: function systemApp_setIsReadyWithId(frameId) {
+    let frameInfo = this._frameInfoMap.get(frameId);
+    if (!frameInfo) {
+      throw new Error('no frame ID is ' + frameId);
+    }
+
+    if (!frameInfo.isLoaded) {
       Cu.reportError('SystemApp.setIsLoaded() should be called before setIsReady().');
     }
 
-    if (this._isReady) {
+    if (frameInfo.isReady) {
       Cu.reportError('SystemApp has already been declared as being ready.');
     }
-    this._isReady = true;
+
+    frameInfo.isReady = true;
 
     // Dispatch all events being queued while the system app was still not ready
     this._pendingReadyEvents
-        .forEach(([type, details]) =>
-                 this._sendCustomEvent(type, details));
-    this._pendingReadyEvents = [];
+        .forEach(([evtFrameId, evtType, evtDetails]) => {
+          if (evtFrameId === frameInfo.frameId) {
+            this.sendCustomEventWithId(evtFrameId, evtType, evtDetails);
+          }
+        });
+
+    // Remove sent events.
+    this._pendingReadyEvents =
+      this._pendingReadyEvents
+          .filter(([evtFrameId]) => { return evtFrameId != frameInfo.frameId });
   },
 
   /*
-   * Common way to send an event to the system app.
+   * Common way to send an event to the main system app.
+   * Only used for the main system app.
    *
    * // In gecko code:
    *   SystemAppProxy.sendCustomEvent('foo', { data: 'bar' });
    * // In system app:
    *   window.addEventListener('foo', function (event) {
    *     event.details == 'bar'
    *   });
    *
    *   @param type      The custom event type.
    *   @param details   The event details.
    *   @param noPending Set to true to emit this event even before the system
    *                    app is ready.
    *                    Event is always pending if the app is not loaded yet.
+   *   @param target    The element who dispatch this event.
    *
    *   @returns event?  Dispatched event, or null if the event is pending.
    */
   _sendCustomEvent: function systemApp_sendCustomEvent(type,
                                                        details,
                                                        noPending,
                                                        target) {
-    let content = this._frame ? this._frame.contentWindow : null;
+    let args = Array.prototype.slice.call(arguments);
+    return this.sendCustomEventWithId
+               .apply(this, [kMainSystemAppId].concat(args));
+  },
 
+  /*
+   * Common way to send an event to the specific system app.
+   *
+   * // In gecko code (send custom event from main system app):
+   *   SystemAppProxy.sendCustomEventWithId('main', 'foo', { data: 'bar' });
+   * // In system app:
+   *   window.addEventListener('foo', function (event) {
+   *     event.details == 'bar'
+   *   });
+   *
+   *   @param frameId   Specify the system(-remote) app who dispatch this event.
+   *   @param type      The custom event type.
+   *   @param details   The event details.
+   *   @param noPending Set to true to emit this event even before the system
+   *                    app is ready.
+   *                    Event is always pending if the app is not loaded yet.
+   *   @param target    The element who dispatch this event.
+   *
+   *   @returns event?  Dispatched event, or null if the event is pending.
+   */
+  sendCustomEventWithId: function systemApp_sendCustomEventWithId(frameId,
+                                                                  type,
+                                                                  details,
+                                                                  noPending,
+                                                                  target) {
+    let frameInfo = this._frameInfoMap.get(frameId);
+    let content = (frameInfo && frameInfo.frame) ?
+                  frameInfo.frame.contentWindow : null;
     // If the system app isn't loaded yet,
     // queue events until someone calls setIsLoaded
-    if (!content || !this._isLoaded) {
+    if (!content || !(frameInfo && frameInfo.isLoaded)) {
       if (noPending) {
-        this._pendingLoadedEvents.push([type, details]);
+        this._pendingLoadedEvents.push([frameId, type, details]);
       } else {
-        this._pendingReadyEvents.push([type, details]);
+        this._pendingReadyEvents.push([frameId, type, details]);
       }
-
       return null;
     }
 
     // If the system app isn't ready yet,
     // queue events until someone calls setIsReady
-    if (!this._isReady && !noPending) {
-      this._pendingReadyEvents.push([type, details]);
+    if (!(frameInfo && frameInfo.isReady) && !noPending) {
+      this._pendingReadyEvents.push([frameId, type, details]);
       return null;
     }
 
     let event = content.document.createEvent('CustomEvent');
 
     let payload;
     // If the root object already has __exposedProps__,
     // we consider the caller already wrapped (correctly) the object.
     if ('__exposedProps__' in details) {
       payload = details;
     } else {
       payload = details ? Cu.cloneInto(details, content) : {};
     }
 
-    if ((target || content) === this._frame.contentWindow) {
-      dump('XXX FIXME : Dispatch a ' + type + ': ' + details.type + "\n");
+    if ((target || content) === frameInfo.frame.contentWindow) {
+      dump('XXX FIXME : Dispatch a ' + type + ': ' + details.type + '\n');
     }
 
     event.initCustomEvent(type, true, false, payload);
     (target || content).dispatchEvent(event);
 
     return event;
   },
 
   // Now deprecated, use sendCustomEvent with a custom event name
   dispatchEvent: function systemApp_dispatchEvent(details, target) {
     return this._sendCustomEvent('mozChromeEvent', details, false, target);
   },
 
   dispatchKeyboardEvent: function systemApp_dispatchKeyboardEvent(type, details) {
     try {
-      let content = this._frame ? this._frame.contentWindow : null;
+      let frameInfo = this._getMainSystemAppInfo();
+      let content = (frameInfo && frameInfo.frame) ? frameInfo.frame.contentWindow
+                                                   : null;
       if (!content) {
-        throw new Error("no content window");
+        throw new Error('no content window');
       }
-
       // If we don't already have a TextInputProcessor, create one now
       if (!this.TIP) {
-        this.TIP = Cc["@mozilla.org/text-input-processor;1"]
+        this.TIP = Cc['@mozilla.org/text-input-processor;1']
           .createInstance(Ci.nsITextInputProcessor);
         if (!this.TIP) {
-          throw new Error("failed to create textInputProcessor");
+          throw new Error('failed to create textInputProcessor');
         }
       }
 
       if (!this.TIP.beginInputTransactionForTests(content)) {
         this.TIP = null;
-        throw new Error("beginInputTransaction failed");
+        throw new Error('beginInputTransaction failed');
       }
 
-      let e = new content.KeyboardEvent("", { key: details.key, });
+      let e = new content.KeyboardEvent('', { key: details.key, });
 
       if (type === 'keydown') {
         this.TIP.keydown(e);
       }
       else if (type === 'keyup') {
         this.TIP.keyup(e);
       }
       else {
-        throw new Error("unexpected event type: " + type);
+        throw new Error('unexpected event type: ' + type);
       }
     }
     catch(e) {
-      dump("dispatchKeyboardEvent: " + e + "\n");
+      dump('dispatchKeyboardEvent: ' + e + '\n');
     }
   },
 
-  // Listen for dom events on the system app
+  // Listen for dom events on the main system app
   addEventListener: function systemApp_addEventListener() {
-    let content = this._frame ? this._frame.contentWindow : null;
-    if (!content) {
+    let args = Array.prototype.slice.call(arguments);
+    this.addEventListenerWithId.apply(this, [kMainSystemAppId].concat(args));
+  },
+
+  // Listen for dom events on the specific system app
+  addEventListenerWithId: function systemApp_addEventListenerWithId(frameId,
+                                                                    ...args) {
+    let frameInfo = this._frameInfoMap.get(frameId);
+
+    if (!frameInfo) {
       this._pendingListeners.push(arguments);
       return false;
     }
 
-    content.addEventListener.apply(content, arguments);
+    let content = frameInfo.frame.contentWindow;
+    content.addEventListener.apply(content, args);
     return true;
   },
 
+  // remove the event listener from the main system app
   removeEventListener: function systemApp_removeEventListener(name, listener) {
-    let content = this._frame ? this._frame.contentWindow : null;
-    if (content) {
-      content.removeEventListener.apply(content, arguments);
-    } else {
+    this.removeEventListenerWithId.apply(this, [kMainSystemAppId, name, listener]);
+  },
+
+  // remove the event listener from the specific system app
+  removeEventListenerWithId: function systemApp_removeEventListenerWithId(frameId,
+                                                                          name,
+                                                                          listener) {
+    let frameInfo = this._frameInfoMap.get(frameId);
+
+    if (frameInfo) {
+      let content = frameInfo.frame.contentWindow;
+      content.removeEventListener.apply(content, [name, listener]);
+    }
+    else {
       this._pendingListeners = this._pendingListeners.filter(
         args => {
-          return args[0] != name || args[1] != listener;
+          return args[0] != frameId || args[1] != name || args[2] != listener;
         });
     }
   },
 
-  getFrames: function systemApp_getFrames() {
-    let systemAppFrame = this._frame;
-    if (!systemAppFrame) {
-      return [];
+  // Get all frame in system app
+  getFrames: function systemApp_getFrames(frameId) {
+    let frameList = [];
+
+    for (let frameId of this._frameInfoMap.keys()) {
+      let frameInfo = this._frameInfoMap.get(frameId);
+      let systemAppFrame = frameInfo.frame;
+      let subFrames = systemAppFrame.contentDocument.querySelectorAll('iframe');
+      frameList.push(systemAppFrame);
+      for (let i = 0; i < subFrames.length; ++i) {
+        frameList.push(subFrames[i]);
+      }
     }
-    let list = [systemAppFrame];
-    let frames = systemAppFrame.contentDocument.querySelectorAll('iframe');
-    for (let i = 0; i < frames.length; i++) {
-      list.push(frames[i]);
-    }
-    return list;
+    return frameList;
   }
 };
 this.SystemAppProxy = SystemAppProxy;
-