summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/tabs/observer.js
blob: 4e935cd628ca38061889e4a61952ceecea794558 (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
/* 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';

module.metadata = {
  "stability": "unstable"
};

const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { Class } = require("../core/heritage");
const { getActiveTab, getTabs } = require("./utils");
const { browserWindowIterator } = require("../deprecated/window-utils");
const { isBrowser, windows, getMostRecentBrowserWindow } = require("../window/utils");
const { observer: windowObserver } = require("../windows/observer");
const { when } = require("../system/unload");

const EVENTS = {
  "TabOpen": "open",
  "TabClose": "close",
  "TabSelect": "select",
  "TabMove": "move",
  "TabPinned": "pinned",
  "TabUnpinned": "unpinned"
};

const selectedTab = Symbol("observer/state/selectedTab");

// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
  implements: [EventTarget, DOMEventAssembler],
  initialize() {
    this[selectedTab] = null;
    // Currently Gecko does not dispatch any event on the previously selected
    // tab before / after "TabSelect" is dispatched. In order to work around this
    // limitation we keep track of selected tab and emit "deactivate" event with
    // that before emitting "activate" on selected tab.
    this.on("select", tab => {
      const selected = this[selectedTab];
      if (selected !== tab) {
        if (selected) {
          emit(this, 'deactivate', selected);
        }

        if (tab) {
          this[selectedTab] = tab;
          emit(this, 'activate', this[selectedTab]);
        }
      }
    });


    // We also observe opening / closing windows in order to add / remove it's
    // containers to the observed list.
    windowObserver.on("open", chromeWindow => {
      if (isBrowser(chromeWindow)) {
        this.observe(chromeWindow);
      }
    });

    windowObserver.on("close", chromeWindow => {
      if (isBrowser(chromeWindow)) {
        // Bug 751546: Emit `deactivate` event on window close immediatly
        // Otherwise we are going to face "dead object" exception on `select` event
        if (getActiveTab(chromeWindow) === this[selectedTab]) {
          emit(this, "deactivate", this[selectedTab]);
          this[selectedTab] = null;
        }
        this.ignore(chromeWindow);
      }
    });


    // Currently gecko does not dispatches "TabSelect" events when different
    // window gets activated. To work around this limitation we emulate "select"
    // event for this case.
    windowObserver.on("activate", chromeWindow => {
      if (isBrowser(chromeWindow)) {
        emit(this, "select", getActiveTab(chromeWindow));
      }
    });

    // We should synchronize state, since probably we already have at least one
    // window open.
    for (let chromeWindow of browserWindowIterator()) {
      this.observe(chromeWindow);
    }

    when(_ => {
      // Don't dispatch a deactivate event during unload.
      this[selectedTab] = null;
    });
  },
  /**
   * Events that are supported and emitted by the module.
   */
  supportedEventsTypes: Object.keys(EVENTS),
  /**
   * Function handles all the supported events on all the windows that are
   * observed. Method is used to proxy events to the listeners registered on
   * this event emitter.
   * @param {Event} event
   *    Keyboard event being emitted.
   */
  handleEvent: function handleEvent(event) {
    emit(this, EVENTS[event.type], event.target, event);
  }
});

exports.observer = new Observer();