Wed, 31 Dec 2014 06:55:46 +0100
Added tag TORBROWSER_REPLICA for changeset 6474c204b198
1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 "use strict";
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
11 const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
13 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
14 Cu.import("resource://gre/modules/devtools/event-emitter.js");
16 this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"];
18 /**
19 * A breadcrumb-like list of items.
20 *
21 * Note: this widget should be used in tandem with the WidgetMethods in
22 * ViewHelpers.jsm.
23 *
24 * @param nsIDOMNode aNode
25 * The element associated with the widget.
26 */
27 this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) {
28 this.document = aNode.ownerDocument;
29 this.window = this.document.defaultView;
30 this._parent = aNode;
32 // Create an internal arrowscrollbox container.
33 this._list = this.document.createElement("arrowscrollbox");
34 this._list.className = "breadcrumbs-widget-container";
35 this._list.setAttribute("flex", "1");
36 this._list.setAttribute("orient", "horizontal");
37 this._list.setAttribute("clicktoscroll", "true")
38 this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
39 this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
40 this._parent.appendChild(this._list);
42 // By default, hide the arrows. We let the arrowscrollbox show them
43 // in case of overflow.
44 this._list._scrollButtonUp.collapsed = true;
45 this._list._scrollButtonDown.collapsed = true;
46 this._list.addEventListener("underflow", this._onUnderflow.bind(this), false);
47 this._list.addEventListener("overflow", this._onOverflow.bind(this), false);
50 // These separators are used for CSS purposes only, and are positioned
51 // off screen, but displayed with -moz-element.
52 this._separators = this.document.createElement("box");
53 this._separators.className = "breadcrumb-separator-container";
54 this._separators.innerHTML =
55 "<box id='breadcrumb-separator-before'></box>" +
56 "<box id='breadcrumb-separator-after'></box>" +
57 "<box id='breadcrumb-separator-normal'></box>";
58 this._parent.appendChild(this._separators);
60 // This widget emits events that can be handled in a MenuContainer.
61 EventEmitter.decorate(this);
63 // Delegate some of the associated node's methods to satisfy the interface
64 // required by MenuContainer instances.
65 ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
66 ViewHelpers.delegateWidgetEventMethods(this, aNode);
67 };
69 BreadcrumbsWidget.prototype = {
70 /**
71 * Inserts an item in this container at the specified index.
72 *
73 * @param number aIndex
74 * The position in the container intended for this item.
75 * @param nsIDOMNode aContents
76 * The node displayed in the container.
77 * @return nsIDOMNode
78 * The element associated with the displayed item.
79 */
80 insertItemAt: function(aIndex, aContents) {
81 let list = this._list;
82 let breadcrumb = new Breadcrumb(this, aContents);
83 return list.insertBefore(breadcrumb._target, list.childNodes[aIndex]);
84 },
86 /**
87 * Returns the child node in this container situated at the specified index.
88 *
89 * @param number aIndex
90 * The position in the container intended for this item.
91 * @return nsIDOMNode
92 * The element associated with the displayed item.
93 */
94 getItemAtIndex: function(aIndex) {
95 return this._list.childNodes[aIndex];
96 },
98 /**
99 * Removes the specified child node from this container.
100 *
101 * @param nsIDOMNode aChild
102 * The element associated with the displayed item.
103 */
104 removeChild: function(aChild) {
105 this._list.removeChild(aChild);
107 if (this._selectedItem == aChild) {
108 this._selectedItem = null;
109 }
110 },
112 /**
113 * Removes all of the child nodes from this container.
114 */
115 removeAllItems: function() {
116 let list = this._list;
118 while (list.hasChildNodes()) {
119 list.firstChild.remove();
120 }
122 this._selectedItem = null;
123 },
125 /**
126 * Gets the currently selected child node in this container.
127 * @return nsIDOMNode
128 */
129 get selectedItem() {
130 return this._selectedItem;
131 },
133 /**
134 * Sets the currently selected child node in this container.
135 * @param nsIDOMNode aChild
136 */
137 set selectedItem(aChild) {
138 let childNodes = this._list.childNodes;
140 if (!aChild) {
141 this._selectedItem = null;
142 }
143 for (let node of childNodes) {
144 if (node == aChild) {
145 node.setAttribute("checked", "");
146 this._selectedItem = node;
147 } else {
148 node.removeAttribute("checked");
149 }
150 }
151 },
153 /**
154 * Returns the value of the named attribute on this container.
155 *
156 * @param string aName
157 * The name of the attribute.
158 * @return string
159 * The current attribute value.
160 */
161 getAttribute: function(aName) {
162 if (aName == "scrollPosition") return this._list.scrollPosition;
163 if (aName == "scrollWidth") return this._list.scrollWidth;
164 return this._parent.getAttribute(aName);
165 },
167 /**
168 * Ensures the specified element is visible.
169 *
170 * @param nsIDOMNode aElement
171 * The element to make visible.
172 */
173 ensureElementIsVisible: function(aElement) {
174 if (!aElement) {
175 return;
176 }
178 // Repeated calls to ensureElementIsVisible would interfere with each other
179 // and may sometimes result in incorrect scroll positions.
180 setNamedTimeout("breadcrumb-select", ENSURE_SELECTION_VISIBLE_DELAY, () => {
181 if (this._list.ensureElementIsVisible) {
182 this._list.ensureElementIsVisible(aElement);
183 }
184 });
185 },
187 /**
188 * The underflow and overflow listener for the arrowscrollbox container.
189 */
190 _onUnderflow: function({ target }) {
191 if (target != this._list) {
192 return;
193 }
194 target._scrollButtonUp.collapsed = true;
195 target._scrollButtonDown.collapsed = true;
196 target.removeAttribute("overflows");
197 },
199 /**
200 * The underflow and overflow listener for the arrowscrollbox container.
201 */
202 _onOverflow: function({ target }) {
203 if (target != this._list) {
204 return;
205 }
206 target._scrollButtonUp.collapsed = false;
207 target._scrollButtonDown.collapsed = false;
208 target.setAttribute("overflows", "");
209 },
211 window: null,
212 document: null,
213 _parent: null,
214 _list: null,
215 _selectedItem: null
216 };
218 /**
219 * A Breadcrumb constructor for the BreadcrumbsWidget.
220 *
221 * @param BreadcrumbsWidget aWidget
222 * The widget to contain this breadcrumb.
223 * @param nsIDOMNode aContents
224 * The node displayed in the container.
225 */
226 function Breadcrumb(aWidget, aContents) {
227 this.document = aWidget.document;
228 this.window = aWidget.window;
229 this.ownerView = aWidget;
231 this._target = this.document.createElement("hbox");
232 this._target.className = "breadcrumbs-widget-item";
233 this._target.setAttribute("align", "center");
234 this.contents = aContents;
235 }
237 Breadcrumb.prototype = {
238 /**
239 * Sets the contents displayed in this item's view.
240 *
241 * @param string | nsIDOMNode aContents
242 * The string or node displayed in the container.
243 */
244 set contents(aContents) {
245 // If there are already some contents displayed, replace them.
246 if (this._target.hasChildNodes()) {
247 this._target.replaceChild(aContents, this._target.firstChild);
248 return;
249 }
250 // These are the first contents ever displayed.
251 this._target.appendChild(aContents);
252 },
254 window: null,
255 document: null,
256 ownerView: null,
257 _target: null
258 };