Revision control

Line Code
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
/* 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/. */

/* globals MozQueryInterface */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{

ChromeUtils.import("resource://gre/modules/Services.jsm");

const gXULDOMParser = new DOMParser();
gXULDOMParser.forceEnableXULXBL();

class MozXULElement extends XULElement {
  /**
   * Allows eager deterministic construction of XUL elements with XBL attached, by
   * parsing an element tree and returning a DOM fragment to be inserted in the
   * document before any of the inner elements is referenced by JavaScript.
   *
   * This process is required instead of calling the createElement method directly
   * because bindings get attached when:
   *
   * 1) the node gets a layout frame constructed, or
   * 2) the node gets its JavaScript reflector created, if it's in the document,
   *
   * whichever happens first. The createElement method would return a JavaScript
   * reflector, but the element wouldn't be in the document, so the node wouldn't
   * get XBL attached. After that point, even if the node is inserted into a
   * document, it won't get XBL attached until either the frame is constructed or
   * the reflector is garbage collected and the element is touched again.
   *
   * @param str
   *        String with the XML representation of XUL elements.
   * @param preamble
   *        String to be inserted above any markup. This can be used
   *        to insert XML entity text, for instance.
   *
   * @return DocumentFragment containing the corresponding element tree, including
   *         element nodes but excluding any text node.
   */
  static parseXULToFragment(str, preamble = "") {
    let doc = gXULDOMParser.parseFromString(`
      ${preamble}
      <box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
        ${str}
      </box>
    `, "application/xml");
    // The XUL/XBL parser is set to ignore all-whitespace nodes, whereas (X)HTML
    // does not do this. Most XUL code assumes that the whitespace has been
    // stripped out, so we simply remove all text nodes after using the parser.
    let nodeIterator = doc.createNodeIterator(doc, NodeFilter.SHOW_TEXT);
    let currentNode = nodeIterator.nextNode();
    while (currentNode) {
      currentNode.remove();
      currentNode = nodeIterator.nextNode();
    }
    // We use a range here so that we don't access the inner DOM elements from
    // JavaScript before they are imported and inserted into a document.
    let range = doc.createRange();
    range.selectNodeContents(doc.querySelector("box"));
    return range.extractContents();
  }

  /**
   * Indicate that a class defining an element implements one or more
   * XPCOM interfaces. The custom element getCustomInterface is added
   * as well as an implementation of QueryInterface.
   *
   * The supplied class should implement the properties and methods of
   * all of the interfaces that are specified.
   *
   * @param cls
   *        The class that implements the interface.
   * @param names
   *        Array of interface names
   */
  static implementCustomInterface(cls, ifaces) {
    cls.prototype.QueryInterface = ChromeUtils.generateQI(ifaces);
    cls.prototype.getCustomInterfaceCallback = function getCustomInterfaceCallback(iface) {
      if (ifaces.includes(Ci[Components.interfacesByID[iface.number]])) {
        return getInterfaceProxy(this);
      }
      return null;
    };
  }
}

/**
 * Given an object, add a proxy that reflects interface implementations
 * onto the object itself.
 */
function getInterfaceProxy(obj) {
  if (!obj._customInterfaceProxy) {
    obj._customInterfaceProxy = new Proxy(obj, {
      get(target, prop, receiver) {
        let propOrMethod = target[prop];
        if (typeof propOrMethod == "function") {
          if (propOrMethod instanceof MozQueryInterface) {
            return Reflect.get(target, prop, receiver);
          }
          return function(...args) {
            return propOrMethod.apply(target, args);
          };
        }
        return propOrMethod;
      }
    });
  }

  return obj._customInterfaceProxy;
}

// Attach the base class to the window so other scripts can use it:
window.MozXULElement = MozXULElement;

for (let script of [
  "chrome://global/content/elements/stringbundle.js",
  "chrome://global/content/elements/general.js",
  "chrome://global/content/elements/textbox.js",
]) {
  Services.scriptloader.loadSubScript(script, window);
}

customElements.setElementCreationCallback("printpreview-toolbar", type => {
  Services.scriptloader.loadSubScript(
    "chrome://global/content/printPreviewToolbar.js", window);
});

customElements.setElementCreationCallback("editor", type => {
  Services.scriptloader.loadSubScript(
    "chrome://global/content/elements/editor.js", window);
});

}