1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5"use strict";
6
7// This JS module wraps the nsIXULStore XPCOM service with useful abstractions.
8// In particular, it wraps the enumerators returned by getIDsEnumerator()
9// and getAttributeEnumerator() in JS objects that implement the iterable
10// protocol.  It also implements the persist() method.  JS consumers should use
11// this module rather than accessing nsIXULStore directly.
12
13const EXPORTED_SYMBOLS = ["XULStore", "getXULStore"];
14
15// Services.xulStore loads this module and returns its `XULStore` symbol
16// when this implementation of XULStore is enabled, so using it here
17// would loop infinitely.  But the mozilla/use-services rule is a good
18// requiremnt for every other consumer of XULStore.
19// eslint-disable-next-line mozilla/use-services
20const xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
21
22// Enables logging.
23const debugMode = false;
24
25// Internal function for logging debug messages to the Error Console window
26function log(message) {
27  if (!debugMode) {
28    return;
29  }
30  console.log("XULStore: " + message);
31}
32
33const XULStore = {
34  setValue: xulStore.setValue,
35  hasValue: xulStore.hasValue,
36  getValue: xulStore.getValue,
37  removeValue: xulStore.removeValue,
38  removeDocument: xulStore.removeDocument,
39
40  /**
41   * Sets a value for a specified node's attribute, except in
42   * the case below:
43   * If the value is empty and if calling `hasValue` with the node's
44   * document and ID and `attr` would return true, then the
45   * value instead gets removed from the store (see Bug 1476680).
46   *
47   * @param node - DOM node
48   * @param attr - attribute to store
49   */
50  persist(node, attr) {
51    if (!node.id) {
52      throw new Error("Node without ID passed into persist()");
53    }
54
55    const uri = node.ownerDocument.documentURI;
56    const value = node.getAttribute(attr);
57
58    if (node.localName == "window") {
59      log("Persisting attributes to windows is handled by AppWindow.");
60      return;
61    }
62
63    // See Bug 1476680 - we could drop the `hasValue` check so that
64    // any time there's an empty attribute it gets removed from the
65    // store. Since this is copying behavior from document.persist,
66    // callers would need to be updated with that change.
67    if (!value && xulStore.hasValue(uri, node.id, attr)) {
68      xulStore.removeValue(uri, node.id, attr);
69    } else {
70      xulStore.setValue(uri, node.id, attr, value);
71    }
72  },
73
74  getIDsEnumerator(docURI) {
75    return new XULStoreEnumerator(xulStore.getIDsEnumerator(docURI));
76  },
77
78  getAttributeEnumerator(docURI, id) {
79    return new XULStoreEnumerator(xulStore.getAttributeEnumerator(docURI, id));
80  },
81};
82
83class XULStoreEnumerator {
84  constructor(enumerator) {
85    this.enumerator = enumerator;
86  }
87
88  hasMore() {
89    return this.enumerator.hasMore();
90  }
91
92  getNext() {
93    return this.enumerator.getNext();
94  }
95
96  *[Symbol.iterator]() {
97    while (this.enumerator.hasMore()) {
98      yield this.enumerator.getNext();
99    }
100  }
101}
102
103// Only here for the sake of component registration, which requires a
104// callable function.
105function getXULStore() {
106  return XULStore;
107}
108