summaryrefslogtreecommitdiffstats
path: root/b2g/components/Frames.jsm
blob: 0eb00cb4cb7b2b7a72faf3cb31a8d1bfcbc6a08a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* 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';

this.EXPORTED_SYMBOLS = ['Frames'];

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/SystemAppProxy.jsm');

const listeners = [];

const Observer = {
  // Save a map of (MessageManager => Frame) to be able to dispatch
  // the FrameDestroyed event with a frame reference.
  _frames: new Map(),

  // Also save current number of iframes opened by app
  _apps: new Map(),

  start: function () {
    Services.obs.addObserver(this, 'remote-browser-shown', false);
    Services.obs.addObserver(this, 'inprocess-browser-shown', false);
    Services.obs.addObserver(this, 'message-manager-close', false);

    SystemAppProxy.getFrames().forEach(frame => {
      let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
      this._frames.set(mm, frame);
      let mozapp = frame.getAttribute('mozapp');
      if (mozapp) {
        this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1);
      }
    });
  },

  stop: function () {
    Services.obs.removeObserver(this, 'remote-browser-shown');
    Services.obs.removeObserver(this, 'inprocess-browser-shown');
    Services.obs.removeObserver(this, 'message-manager-close');
    this._frames.clear();
    this._apps.clear();
  },

  observe: function (subject, topic, data) {
    switch(topic) {

      // Listen for frame creation in OOP (device) as well as in parent process (b2g desktop)
      case 'remote-browser-shown':
      case 'inprocess-browser-shown':
        let frameLoader = subject;

        // get a ref to the app <iframe>
        frameLoader.QueryInterface(Ci.nsIFrameLoader);
        let frame = frameLoader.ownerElement;
        let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
        this.onMessageManagerCreated(mm, frame);
        break;

      // Every time an iframe is destroyed, its message manager also is
      case 'message-manager-close':
        this.onMessageManagerDestroyed(subject);
        break;
    }
  },

  onMessageManagerCreated: function (mm, frame) {
    this._frames.set(mm, frame);

    let isFirstAppFrame = null;
    let mozapp = frame.getAttribute('mozapp');
    if (mozapp) {
      let count = (this._apps.get(mozapp) || 0) + 1;
      this._apps.set(mozapp, count);
      isFirstAppFrame = (count === 1);
    }

    listeners.forEach(function (listener) {
      try {
        listener.onFrameCreated(frame, isFirstAppFrame);
      } catch(e) {
        dump('Exception while calling Frames.jsm listener:' + e + '\n' +
             e.stack + '\n');
      }
    });
  },

  onMessageManagerDestroyed: function (mm) {
    let frame = this._frames.get(mm);
    if (!frame) {
      // We received an event for an unknown message manager
      return;
    }

    this._frames.delete(mm);

    let isLastAppFrame = null;
    let mozapp = frame.getAttribute('mozapp');
    if (mozapp) {
      let count = (this._apps.get(mozapp) || 0) - 1;
      this._apps.set(mozapp, count);
      isLastAppFrame = (count === 0);
    }

    listeners.forEach(function (listener) {
      try {
        listener.onFrameDestroyed(frame, isLastAppFrame);
      } catch(e) {
        dump('Exception while calling Frames.jsm listener:' + e + '\n' +
             e.stack + '\n');
      }
    });
  }

};

var Frames = this.Frames = {

  list: () => SystemAppProxy.getFrames(),

  addObserver: function (listener) {
    if (listeners.indexOf(listener) !== -1) {
      return;
    }

    listeners.push(listener);
    if (listeners.length == 1) {
      Observer.start();
    }
  },

  removeObserver: function (listener) {
    let idx = listeners.indexOf(listener);
    if (idx !== -1) {
      listeners.splice(idx, 1);
    }
    if (listeners.length === 0) {
      Observer.stop();
    }
  }

};