summaryrefslogtreecommitdiffstats
path: root/dom/html/htmlMenuBuilder.js
blob: 863fc4d74b138d7a20a7b27199b327d55d39d71a (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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/* 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/. */

// This component is used to build the menus for the HTML contextmenu attribute.

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;

// A global value that is used to identify each menu item. It is
// incremented with each one that is found.
var gGeneratedId = 1;

function HTMLMenuBuilder() {
  this.currentNode = null;
  this.root = null;
  this.items = {};
  this.nestedStack = [];
};

// Building is done in two steps:
// The first generates a hierarchical JS object that contains the menu structure.
// This object is returned by toJSONString.
//
// The second step can take this structure and generate a XUL menu hierarchy or
// other UI from this object. The default UI is done in PageMenu.jsm.
//
// When a multi-process browser is used, the first step is performed by the child
// process and the second step is performed by the parent process.

HTMLMenuBuilder.prototype =
{
  classID:        Components.ID("{51c65f5d-0de5-4edc-9058-60e50cef77f8}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMenuBuilder]),

  currentNode: null,
  root: null,
  items: {},
  nestedStack: [],

  toJSONString: function() {
    return JSON.stringify(this.root);
  },

  openContainer: function(aLabel) {
    if (!this.currentNode) {
      this.root = {
        type: "menu",
        children: []
      };
      this.currentNode = this.root;
    }
    else {
      let parent = this.currentNode;
      this.currentNode = {
        type: "menu",
        label: aLabel,
        children: []
      };
      parent.children.push(this.currentNode);
      this.nestedStack.push(parent);
    }
  },

  addItemFor: function(aElement, aCanLoadIcon) {
    if (!("children" in this.currentNode)) {
      return;
    }

    let item = {
      type: "menuitem",
      label: aElement.label
    };

    let elementType = aElement.type;
    if (elementType == "checkbox" || elementType == "radio") {
      item.checkbox = true;

      if (aElement.checked) {
        item.checked = true;
      }
    }

    let icon = aElement.icon;
    if (icon.length > 0 && aCanLoadIcon) {
      item.icon = icon;
    }

    if (aElement.disabled) {
      item.disabled = true;
    }

    item.id = gGeneratedId++;
    this.currentNode.children.push(item);

    this.items[item.id] = aElement;
  },

  addSeparator: function() {
    if (!("children" in this.currentNode)) {
      return;
    }

    this.currentNode.children.push({ type: "separator"});
  },

  undoAddSeparator: function() {
    if (!("children" in this.currentNode)) {
      return;
    }

    let children = this.currentNode.children;
    if (children.length && children[children.length - 1].type == "separator") {
      children.pop();
    }
  },

  closeContainer: function() {
    this.currentNode = this.nestedStack.length ? this.nestedStack.pop() : this.root;
  },

  click: function(id) {
    let item = this.items[id];
    if (item) {
      item.click();
    }
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HTMLMenuBuilder]);