+ /**
+ * Remove an event listener.
+ *
+ * @param {Element} target The element on which to listen.
+ * @param {string} event The event to listen for.
+ * @param {function} callback The function to call when the event is received.
+ * @param {boolean} capture When true, listen during the capture
+ * phase, otherwise during the bubbling phase.
+ */
+ unlisten: function (target, event, callback, capture) {
+ let doc = target.ownerDocument || target.document || target;
+ let listeners = this.getData(doc, "listeners");
+ if (event === true)
+ target = null;
+
+ this.setData(doc, "listeners", listeners.filter(function (args) {
+ if (target == null || args[0].get() == target && args[1] == event && args[2].wrapped == callback && args[3] == capture) {
+ args[0].get().removeEventListener.apply(args[0].get(), args.slice(1));
+ return false;
+ }
+ return !args[0].get();
+ }));
+ },
+
+ cleanup: function cleanup(reason) {
+ for (let doc in util.iterDocuments()) {
+ for (let elem in values(this.getData(doc, "overlayElements")))
+ if (elem.parentNode)
+ elem.parentNode.removeChild(elem);
+
+ for (let [elem, ns, name, orig, value] in values(this.getData(doc, "overlayAttributes")))
+ if (getAttr(elem, ns, name) === value)
+ setAttr(elem, ns, name, orig);
+
+ for (let callback in values(this.getData(doc, "cleanup")))
+ util.trapErrors(callback, doc, reason);
+
+ this.unlisten(doc, true);
+
+ delete doc[this.id];
+ delete doc.defaultView[this.id];
+ }
+ },
+
+ observers: {
+ "toplevel-window-ready": function (window, data) {
+ let listener = util.wrapCallback(function listener(event) {
+ if (event.originalTarget === window.document) {
+ window.removeEventListener("DOMContentLoaded", listener.wrapper, true);
+ window.removeEventListener("load", listener.wrapper, true);
+ overlay._loadOverlays(window);
+ }
+ });
+
+ window.addEventListener("DOMContentLoaded", listener, true);
+ window.addEventListener("load", listener, true);
+ },
+ "chrome-document-global-created": function (window, uri) { this.observe(window, "toplevel-window-ready", null); },
+ "content-document-global-created": function (window, uri) { this.observe(window, "toplevel-window-ready", null); },
+ "xul-window-visible": function () {
+ if (this.onWindowVisible)
+ this.onWindowVisible.forEach(f => { f.call(this); });
+ this.onWindowVisible = null;
+ }
+ },
+
+ getData: function getData(obj, key, constructor) {
+
+ if (!this.weakMap.has(obj))
+ try {
+ this.weakMap.set(obj, {});
+ }
+ catch (e if e instanceof TypeError) {
+ // util.dump("Bad WeakMap key: " + obj + " " + Components.stack.caller);
+ let { id } = this;