+ /**
+ * Adds an event listener for this session and removes it on
+ * dactyl shutdown.
+ *
+ * @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.
+ * @param {boolean} allowUntrusted When true, allow capturing of
+ * untrusted events.
+ */
+ listen: function (target, event, callback, capture, allowUntrusted) {
+ let doc = target.ownerDocument || target.document || target;
+ let listeners = this.getData(doc, "listeners");
+
+ if (!isObject(event))
+ var [self, events] = [null, array.toObject([[event, callback]])];
+ else
+ [self, events] = [event, event[callback || "events"]];
+
+ for (let [event, callback] in Iterator(events)) {
+ let args = [util.weakReference(target),
+ event,
+ util.wrapCallback(callback, self),
+ capture,
+ allowUntrusted];
+
+ target.addEventListener.apply(target, args.slice(1));
+ listeners.push(args);
+ }
+ },
+
+ /**
+ * 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(function (f) f.call(this), this);
+ this.onWindowVisible = null;
+ }
+ },
+
+ getData: function getData(obj, key, constructor) {
+ let { id } = this;
+
+ if (!(id in obj && obj[id]))
+ obj[id] = {};
+
+ if (arguments.length == 1)
+ return obj[id];
+
+ if (obj[id][key] === undefined)
+ if (constructor === undefined || callable(constructor))
+ obj[id][key] = (constructor || Array)();
+ else
+ obj[id][key] = constructor;
+
+ return obj[id][key];
+ },
+
+ setData: function setData(obj, key, val) {
+ let { id } = this;
+
+ if (!(id in obj))
+ obj[id] = {};
+
+ return obj[id][key] = val;
+ },
+
+ overlayWindow: function (url, fn) {
+ if (url instanceof Ci.nsIDOMWindow)
+ overlay._loadOverlay(url, fn);
+ else {
+ Array.concat(url).forEach(function (url) {
+ if (!this.overlays[url])
+ this.overlays[url] = [];
+ this.overlays[url].push(fn);
+ }, this);
+
+ for (let doc in util.iterDocuments())
+ if (~["interactive", "complete"].indexOf(doc.readyState)) {
+ this.observe(doc.defaultView, "xul-window-visible");
+ this._loadOverlays(doc.defaultView);
+ }
+ else {
+ if (!this.onWindowVisible)
+ this.onWindowVisible = [];
+ this.observe(doc.defaultView, "toplevel-window-ready");
+ }
+ }
+ },
+
+ _loadOverlays: function _loadOverlays(window) {
+ let overlays = this.getData(window, "overlays");
+
+ for each (let obj in overlay.overlays[window.document.documentURI] || []) {
+ if (~overlays.indexOf(obj))
+ continue;
+ overlays.push(obj);
+ this._loadOverlay(window, obj(window));
+ }
+ },
+
+ _loadOverlay: function _loadOverlay(window, obj) {
+ let doc = window.document;
+ let elems = this.getData(doc, "overlayElements");
+ let attrs = this.getData(doc, "overlayAttributes");
+
+ function insert(key, fn) {
+ if (obj[key]) {
+ let iterator = Iterator(obj[key]);
+ if (!isObject(obj[key]))
+ iterator = ([elem.@id, elem.elements(), elem.@*::*.(function::name() != "id")] for each (elem in obj[key]));
+
+ for (let [elem, xml, attr] in iterator) {
+ if (elem = doc.getElementById(elem)) {
+ let node = DOM.fromXML(xml, doc, obj.objects);
+ if (!(node instanceof Ci.nsIDOMDocumentFragment))
+ elems.push(node);
+ else
+ for (let n in array.iterValues(node.childNodes))
+ elems.push(n);
+
+ fn(elem, node);
+ for each (let attr in attr || []) {
+ let ns = attr.namespace(), name = attr.localName();
+ attrs.push([elem, ns, name, getAttr(elem, ns, name), String(attr)]);
+ if (attr.name() != "highlight")
+ elem.setAttributeNS(ns, name, String(attr));
+ else
+ highlight.highlightNode(elem, String(attr));