browser/components/customizableui/content/toolbar.xml

branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
equal deleted inserted replaced
-1:000000000000 0:8525693c8023
1 <?xml version="1.0"?>
2 <!-- This Source Code Form is subject to the terms of the Mozilla Public
3 - License, v. 2.0. If a copy of the MPL was not distributed with this
4 - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
5
6 <bindings id="browserToolbarBindings"
7 xmlns="http://www.mozilla.org/xbl"
8 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
9 xmlns:xbl="http://www.mozilla.org/xbl">
10
11 <binding id="toolbar" role="xul:toolbar">
12 <resources>
13 <stylesheet src="chrome://global/skin/toolbar.css"/>
14 </resources>
15 <implementation>
16 <field name="overflowedDuringConstruction">null</field>
17
18 <constructor><![CDATA[
19 let scope = {};
20 Cu.import("resource:///modules/CustomizableUI.jsm", scope);
21 // Add an early overflow event listener that will mark if the
22 // toolbar overflowed during construction.
23 if (scope.CustomizableUI.isAreaOverflowable(this.id)) {
24 this.addEventListener("overflow", this);
25 this.addEventListener("underflow", this);
26 }
27
28 if (document.readyState == "complete") {
29 this._init();
30 } else {
31 // Need to wait until XUL overlays are loaded. See bug 554279.
32 let self = this;
33 document.addEventListener("readystatechange", function onReadyStateChange() {
34 if (document.readyState != "complete")
35 return;
36 document.removeEventListener("readystatechange", onReadyStateChange, false);
37 self._init();
38 }, false);
39 }
40 ]]></constructor>
41
42 <method name="_init">
43 <body><![CDATA[
44 let scope = {};
45 Cu.import("resource:///modules/CustomizableUI.jsm", scope);
46 let CustomizableUI = scope.CustomizableUI;
47
48 // Bug 989289: Forcibly set the now unsupported "mode" and "iconsize"
49 // attributes, just in case they accidentally get restored from
50 // persistence from a user that's been upgrading and downgrading.
51 if (CustomizableUI.isBuiltinToolbar(this.id)) {
52 const kAttributes = new Map([["mode", "icons"], ["iconsize", "small"]]);
53 for (let [attribute, value] of kAttributes) {
54 if (this.getAttribute(attribute) != value) {
55 this.setAttribute(attribute, value);
56 document.persist(this.id, attribute);
57 }
58 if (this.toolbox) {
59 if (this.toolbox.getAttribute(attribute) != value) {
60 this.toolbox.setAttribute(attribute, value);
61 document.persist(this.toolbox.id, attribute);
62 }
63 }
64 }
65 }
66
67 // Searching for the toolbox palette in the toolbar binding because
68 // toolbars are constructed first.
69 let toolbox = this.toolbox;
70 if (toolbox && !toolbox.palette) {
71 for (let node of toolbox.children) {
72 if (node.localName == "toolbarpalette") {
73 // Hold on to the palette but remove it from the document.
74 toolbox.palette = node;
75 toolbox.removeChild(node);
76 break;
77 }
78 }
79 }
80
81 // pass the current set of children for comparison with placements:
82 let children = [node.id for (node of this.childNodes)
83 if (node.getAttribute("skipintoolbarset") != "true" && node.id)];
84 CustomizableUI.registerToolbarNode(this, children);
85 ]]></body>
86 </method>
87
88 <method name="handleEvent">
89 <parameter name="aEvent"/>
90 <body><![CDATA[
91 if (aEvent.type == "overflow" && aEvent.detail > 0) {
92 if (this.overflowable && this.overflowable.initialized) {
93 this.overflowable.onOverflow(aEvent);
94 } else {
95 this.overflowedDuringConstruction = aEvent;
96 }
97 } else if (aEvent.type == "underflow" && aEvent.detail > 0) {
98 this.overflowedDuringConstruction = null;
99 }
100 ]]></body>
101 </method>
102
103 <method name="insertItem">
104 <parameter name="aId"/>
105 <parameter name="aBeforeElt"/>
106 <parameter name="aWrapper"/>
107 <body><![CDATA[
108 if (aWrapper) {
109 Cu.reportError("Can't insert " + aId + ": using insertItem " +
110 "no longer supports wrapper elements.");
111 return null;
112 }
113
114 // Hack, the customizable UI code makes this be the last position
115 let pos = null;
116 if (aBeforeElt) {
117 let beforeInfo = CustomizableUI.getPlacementOfWidget(aBeforeElt.id);
118 if (beforeInfo.area != this.id) {
119 Cu.reportError("Can't insert " + aId + " before " +
120 aBeforeElt.id + " which isn't in this area (" +
121 this.id + ").");
122 return null;
123 }
124 pos = beforeInfo.position;
125 }
126
127 CustomizableUI.addWidgetToArea(aId, this.id, pos);
128 return this.ownerDocument.getElementById(aId);
129 ]]></body>
130 </method>
131
132 <property name="toolbarName"
133 onget="return this.getAttribute('toolbarname');"
134 onset="this.setAttribute('toolbarname', val); return val;"/>
135
136 <property name="customizationTarget" readonly="true">
137 <getter><![CDATA[
138 if (this._customizationTarget)
139 return this._customizationTarget;
140
141 let id = this.getAttribute("customizationtarget");
142 if (id)
143 this._customizationTarget = document.getElementById(id);
144
145 if (this._customizationTarget)
146 this._customizationTarget.insertItem = this.insertItem.bind(this);
147 else
148 this._customizationTarget = this;
149
150 return this._customizationTarget;
151 ]]></getter>
152 </property>
153
154 <property name="toolbox" readonly="true">
155 <getter><![CDATA[
156 if (this._toolbox)
157 return this._toolbox;
158
159 let toolboxId = this.getAttribute("toolboxid");
160 if (toolboxId) {
161 let toolbox = document.getElementById(toolboxId);
162 if (toolbox) {
163 if (toolbox.externalToolbars.indexOf(this) == -1)
164 toolbox.externalToolbars.push(this);
165
166 this._toolbox = toolbox;
167 }
168 }
169
170 if (!this._toolbox && this.parentNode &&
171 this.parentNode.localName == "toolbox") {
172 this._toolbox = this.parentNode;
173 }
174
175 return this._toolbox;
176 ]]></getter>
177 </property>
178
179 <property name="currentSet">
180 <getter><![CDATA[
181 let currentWidgets = new Set();
182 for (let node of this.customizationTarget.children) {
183 let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
184 if (realNode.getAttribute("skipintoolbarset") != "true") {
185 currentWidgets.add(realNode.id);
186 }
187 }
188 if (this.getAttribute("overflowing") == "true") {
189 let overflowTarget = this.getAttribute("overflowtarget");
190 let overflowList = this.ownerDocument.getElementById(overflowTarget);
191 for (let node of overflowList.children) {
192 let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
193 if (realNode.getAttribute("skipintoolbarset") != "true") {
194 currentWidgets.add(realNode.id);
195 }
196 }
197 }
198 let orderedPlacements = CustomizableUI.getWidgetIdsInArea(this.id);
199 return orderedPlacements.filter((x) => currentWidgets.has(x)).join(',');
200 ]]></getter>
201 <setter><![CDATA[
202 // Get list of new and old ids:
203 let newVal = (val || '').split(',').filter(x => x);
204 let oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
205
206 // Get a list of items only in the new list
207 let newIds = [id for (id of newVal) if (oldIds.indexOf(id) == -1)];
208 CustomizableUI.beginBatchUpdate();
209 try {
210 for (let newId of newIds) {
211 oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
212 let nextId = newId;
213 let pos;
214 do {
215 // Get the next item
216 nextId = newVal[newVal.indexOf(nextId) + 1];
217 // Figure out where it is in the old list
218 pos = oldIds.indexOf(nextId);
219 // If it's not in the old list, repeat:
220 } while (pos == -1 && nextId);
221 if (pos == -1) {
222 pos = null; // We didn't find anything, insert at the end
223 }
224 CustomizableUI.addWidgetToArea(newId, this.id, pos);
225 }
226
227 let currentIds = this.currentSet.split(',');
228 let removedIds = [id for (id of currentIds) if (newIds.indexOf(id) == -1 && newVal.indexOf(id) == -1)];
229 for (let removedId of removedIds) {
230 CustomizableUI.removeWidgetFromArea(removedId);
231 }
232 } finally {
233 CustomizableUI.endBatchUpdate();
234 }
235 ]]></setter>
236 </property>
237
238
239 </implementation>
240 </binding>
241
242 <binding id="toolbar-menubar-stub">
243 <implementation>
244 <property name="toolbox" readonly="true">
245 <getter><![CDATA[
246 if (this._toolbox)
247 return this._toolbox;
248
249 if (this.parentNode && this.parentNode.localName == "toolbox") {
250 this._toolbox = this.parentNode;
251 }
252
253 return this._toolbox;
254 ]]></getter>
255 </property>
256 <property name="currentSet" readonly="true">
257 <getter><![CDATA[
258 return this.getAttribute("defaultset");
259 ]]></getter>
260 </property>
261 <method name="insertItem">
262 <body><![CDATA[
263 return null;
264 ]]></body>
265 </method>
266 </implementation>
267 </binding>
268
269 <!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
270 verbatim copies of their toolkit counterparts - they just inherit from
271 the customizableui's toolbar binding instead of toolkit's. We're currently
272 OK with the maintainance burden of having two copies of a binding, since
273 the long term goal is to move the customization framework into toolkit. -->
274
275 <binding id="toolbar-menubar-autohide"
276 extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
277 <implementation>
278 <constructor>
279 this._setInactive();
280 </constructor>
281 <destructor>
282 this._setActive();
283 </destructor>
284
285 <field name="_inactiveTimeout">null</field>
286
287 <field name="_contextMenuListener"><![CDATA[({
288 toolbar: this,
289 contextMenu: null,
290
291 get active () !!this.contextMenu,
292
293 init: function (event) {
294 let node = event.target;
295 while (node != this.toolbar) {
296 if (node.localName == "menupopup")
297 return;
298 node = node.parentNode;
299 }
300
301 let contextMenuId = this.toolbar.getAttribute("context");
302 if (!contextMenuId)
303 return;
304
305 this.contextMenu = document.getElementById(contextMenuId);
306 if (!this.contextMenu)
307 return;
308
309 this.contextMenu.addEventListener("popupshown", this, false);
310 this.contextMenu.addEventListener("popuphiding", this, false);
311 this.toolbar.addEventListener("mousemove", this, false);
312 },
313 handleEvent: function (event) {
314 switch (event.type) {
315 case "popupshown":
316 this.toolbar.removeEventListener("mousemove", this, false);
317 break;
318 case "popuphiding":
319 case "mousemove":
320 this.toolbar._setInactiveAsync();
321 this.toolbar.removeEventListener("mousemove", this, false);
322 this.contextMenu.removeEventListener("popuphiding", this, false);
323 this.contextMenu.removeEventListener("popupshown", this, false);
324 this.contextMenu = null;
325 break;
326 }
327 }
328 })]]></field>
329
330 <method name="_setInactive">
331 <body><![CDATA[
332 this.setAttribute("inactive", "true");
333 ]]></body>
334 </method>
335
336 <method name="_setInactiveAsync">
337 <body><![CDATA[
338 this._inactiveTimeout = setTimeout(function (self) {
339 if (self.getAttribute("autohide") == "true") {
340 self._inactiveTimeout = null;
341 self._setInactive();
342 }
343 }, 0, this);
344 ]]></body>
345 </method>
346
347 <method name="_setActive">
348 <body><![CDATA[
349 if (this._inactiveTimeout) {
350 clearTimeout(this._inactiveTimeout);
351 this._inactiveTimeout = null;
352 }
353 this.removeAttribute("inactive");
354 ]]></body>
355 </method>
356 </implementation>
357
358 <handlers>
359 <handler event="DOMMenuBarActive" action="this._setActive();"/>
360 <handler event="popupshowing" action="this._setActive();"/>
361 <handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
362 <handler event="DOMMenuBarInactive"><![CDATA[
363 if (!this._contextMenuListener.active)
364 this._setInactiveAsync();
365 ]]></handler>
366 </handlers>
367 </binding>
368
369 <binding id="toolbar-drag"
370 extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
371 <implementation>
372 <field name="_dragBindingAlive">true</field>
373 <constructor><![CDATA[
374 if (!this._draggableStarted) {
375 this._draggableStarted = true;
376 try {
377 let tmp = {};
378 Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
379 let draggableThis = new tmp.WindowDraggingElement(this);
380 draggableThis.mouseDownCheck = function(e) {
381 return this._dragBindingAlive;
382 };
383 } catch (e) {}
384 }
385 ]]></constructor>
386 </implementation>
387 </binding>
388
389
390 <!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
391 and immediately direct such content elsewhere. -->
392 <binding id="addonbar-delegating">
393 <implementation>
394 <constructor><![CDATA[
395 // Reading these immediately so nobody messes with them anymore:
396 this._delegatingToolbar = this.getAttribute("toolbar-delegate");
397 this._wasCollapsed = this.getAttribute("collapsed") == "true";
398 // Leaving those in here to unbreak some code:
399 if (document.readyState == "complete") {
400 this._init();
401 } else {
402 // Need to wait until XUL overlays are loaded. See bug 554279.
403 let self = this;
404 document.addEventListener("readystatechange", function onReadyStateChange() {
405 if (document.readyState != "complete")
406 return;
407 document.removeEventListener("readystatechange", onReadyStateChange, false);
408 self._init();
409 }, false);
410 }
411 ]]></constructor>
412
413 <method name="_init">
414 <body><![CDATA[
415 // Searching for the toolbox palette in the toolbar binding because
416 // toolbars are constructed first.
417 let toolbox = this.toolbox;
418 if (toolbox && !toolbox.palette) {
419 for (let node of toolbox.children) {
420 if (node.localName == "toolbarpalette") {
421 // Hold on to the palette but remove it from the document.
422 toolbox.palette = node;
423 toolbox.removeChild(node);
424 }
425 }
426 }
427
428 // pass the current set of children for comparison with placements:
429 let children = [];
430 for (let node of this.childNodes) {
431 if (node.getAttribute("skipintoolbarset") != "true" && node.id) {
432 // Force everything to be removable so that buildArea can chuck stuff
433 // out if the user has customized things / we've been here before:
434 if (!this._whiteListed.has(node.id)) {
435 node.setAttribute("removable", "true");
436 }
437 children.push(node);
438 }
439 }
440 CustomizableUI.registerToolbarNode(this, children);
441 let existingMigratedItems = (this.getAttribute("migratedset") || "").split(',');
442 for (let migratedItem of existingMigratedItems.filter((x) => !!x)) {
443 this._currentSetMigrated.add(migratedItem);
444 }
445 this.evictNodes();
446 // We can't easily use |this| or strong bindings for the observer fn here
447 // because that creates leaky circular references when the node goes away,
448 // and XBL destructors are unreliable.
449 let mutationObserver = new MutationObserver(function(mutations) {
450 if (!mutations.length) {
451 return;
452 }
453 let toolbar = mutations[0].target;
454 // Can't use our own attribute because we might not have one if we're set to
455 // collapsed
456 let areCustomizing = toolbar.ownerDocument.documentElement.getAttribute("customizing");
457 if (!toolbar._isModifying && !areCustomizing) {
458 toolbar.evictNodes();
459 }
460 });
461 mutationObserver.observe(this, {childList: true});
462 ]]></body>
463 </method>
464 <method name="evictNodes">
465 <body><![CDATA[
466 this._isModifying = true;
467 let i = this.childNodes.length;
468 while (i--) {
469 let node = this.childNodes[i];
470 if (this.childNodes[i].id) {
471 this.evictNode(this.childNodes[i]);
472 } else {
473 node.remove();
474 }
475 }
476 this._isModifying = false;
477 this._updateMigratedSet();
478 ]]></body>
479 </method>
480 <method name="evictNode">
481 <parameter name="aNode"/>
482 <body>
483 <![CDATA[
484 if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
485 return;
486 }
487 const kItemMaxWidth = 100;
488 let oldParent = aNode.parentNode;
489 aNode.setAttribute("removable", "true");
490 this._currentSetMigrated.add(aNode.id);
491
492 let movedOut = false;
493 if (!this._wasCollapsed) {
494 try {
495 let nodeWidth = aNode.getBoundingClientRect().width;
496 if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
497 throw new Error(aNode.id + " is too big (" + nodeWidth +
498 "px wide), moving to the palette");
499 }
500 CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
501 movedOut = true;
502 } catch (ex) {
503 // This will throw if the node is too big, or can't be moved there for
504 // some reason. Report this:
505 Cu.reportError(ex);
506 }
507 }
508
509 /* We won't have moved the widget if either the add-on bar was collapsed,
510 * or if it was too wide to be inserted into the navbar. */
511 if (!movedOut) {
512 try {
513 CustomizableUI.removeWidgetFromArea(aNode.id);
514 } catch (ex) {
515 Cu.reportError(ex);
516 aNode.remove();
517 }
518 }
519
520 // Surprise: addWidgetToArea(palette) will get you nothing if the palette
521 // is not constructed yet. Fix:
522 if (aNode.parentNode == oldParent) {
523 let palette = this.toolbox.palette;
524 if (palette && oldParent != palette) {
525 palette.appendChild(aNode);
526 }
527 }
528 ]]></body>
529 </method>
530 <method name="insertItem">
531 <parameter name="aId"/>
532 <parameter name="aBeforeElt"/>
533 <parameter name="aWrapper"/>
534 <body><![CDATA[
535 if (aWrapper) {
536 Cu.reportError("Can't insert " + aId + ": using insertItem " +
537 "no longer supports wrapper elements.");
538 return null;
539 }
540
541 let widget = CustomizableUI.getWidget(aId);
542 widget = widget && widget.forWindow(window);
543 let node = widget && widget.node;
544 if (!node) {
545 return null;
546 }
547
548 this._isModifying = true;
549 // Temporarily add it here so it can have a width, then ditch it:
550 this.appendChild(node);
551 this.evictNode(node);
552 this._isModifying = false;
553 this._updateMigratedSet();
554 // We will now have moved stuff around; kick off some events
555 // so add-ons know we've just moved their stuff:
556 // XXXgijs: only in this window. It's hard to know for sure what's the right
557 // thing to do here - typically insertItem is used on each window, so
558 // this seems to make the most sense, even if some of the effects of
559 // evictNode might affect multiple windows.
560 CustomizableUI.dispatchToolboxEvent("customizationchange", {}, window);
561 CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
562 return node;
563 ]]></body>
564 </method>
565 <method name="getMigratedItems">
566 <body><![CDATA[
567 return [... this._currentSetMigrated];
568 ]]></body>
569 </method>
570 <method name="_updateMigratedSet">
571 <body><![CDATA[
572 let newMigratedItems = this.getMigratedItems().join(',');
573 if (this.getAttribute("migratedset") != newMigratedItems) {
574 this.setAttribute("migratedset", newMigratedItems);
575 this.ownerDocument.persist(this.id, "migratedset");
576 }
577 ]]></body>
578 </method>
579 <property name="customizationTarget" readonly="true">
580 <getter><![CDATA[
581 return this;
582 ]]></getter>
583 </property>
584 <property name="currentSet">
585 <getter><![CDATA[
586 return [node.id for (node of this.children)].join(',');
587 ]]></getter>
588 <setter><![CDATA[
589 let v = val.split(',');
590 let newButtons = v.filter(x => x && (!this._whiteListed.has(x) &&
591 !CustomizableUI.isSpecialWidget(x) &&
592 !this._currentSetMigrated.has(x)));
593 for (let newButton of newButtons) {
594 this._currentSetMigrated.add(newButton);
595 this.insertItem(newButton);
596 }
597 this._updateMigratedSet();
598 ]]></setter>
599 </property>
600 <property name="toolbox" readonly="true">
601 <getter><![CDATA[
602 if (!this._toolbox && this.parentNode &&
603 this.parentNode.localName == "toolbox") {
604 this._toolbox = this.parentNode;
605 }
606
607 return this._toolbox;
608 ]]></getter>
609 </property>
610 <field name="_whiteListed" readonly="true">new Set(["addonbar-closebutton", "status-bar"])</field>
611 <field name="_isModifying">false</field>
612 <field name="_currentSetMigrated">new Set()</field>
613 </implementation>
614 </binding>
615 </bindings>

mercurial