import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,389 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="buttonBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="button-base" extends="chrome://global/content/bindings/general.xml#basetext" role="xul:button">
<implementation implements="nsIDOMXULButtonElement">
<property name="type"
onget="return this.getAttribute('type');"
onset="this.setAttribute('type', val); return val;"/>
<property name="dlgType"
onget="return this.getAttribute('dlgtype');"
onset="this.setAttribute('dlgtype', val); return val;"/>
<property name="group"
onget="return this.getAttribute('group');"
onset="this.setAttribute('group', val); return val;"/>
<property name="open" onget="return this.hasAttribute('open');">
<setter><![CDATA[
if (this.boxObject instanceof MenuBoxObject) {
this.boxObject.openMenu(val);
} else if (val) {
// Fall back to just setting the attribute
this.setAttribute('open', 'true');
} else {
this.removeAttribute('open');
}
return val;
]]></setter>
</property>
<property name="checked" onget="return this.hasAttribute('checked');">
<setter><![CDATA[
if (this.type == "checkbox") {
this.checkState = val ? 1 : 0;
} else if (this.type == "radio" && val) {
var sibs = this.parentNode.getElementsByAttribute("group", this.group);
for (var i = 0; i < sibs.length; ++i)
sibs[i].removeAttribute("checked");
}
if (val)
this.setAttribute("checked", "true");
else
this.removeAttribute("checked");
return val;
]]></setter>
</property>
<property name="checkState">
<getter><![CDATA[
var state = this.getAttribute("checkState");
if (state == "")
return this.checked ? 1 : 0;
if (state == "0")
return 0;
if (state == "2")
return 2;
return 1;
]]></getter>
<setter><![CDATA[
this.setAttribute("checkState", val);
return val;
]]></setter>
</property>
<property name="autoCheck"
onget="return this.getAttribute('autoCheck') == 'true';"
onset="this.setAttribute('autoCheck', val); return val;"/>
<method name ="filterButtons">
<parameter name="node"/>
<body>
<![CDATA[
// if the node isn't visible, don't descend into it.
var cs = node.ownerDocument.defaultView.getComputedStyle(node, null);
if (cs.visibility != "visible" || cs.display == "none") {
return NodeFilter.FILTER_REJECT;
}
// but it may be a popup element, in which case we look at "state"...
if (cs.display == "-moz-popup" && node.state != "open") {
return NodeFilter.FILTER_REJECT;
}
// OK - the node seems visible, so it is a candidate.
if (node.localName == "button" && node.accessKey && !node.disabled)
return NodeFilter.FILTER_ACCEPT;
return NodeFilter.FILTER_SKIP;
]]>
</body>
</method>
<method name="fireAccessKeyButton">
<parameter name="aSubtree"/>
<parameter name="aAccessKeyLower"/>
<body>
<![CDATA[
var iterator = aSubtree.ownerDocument.createTreeWalker(aSubtree,
NodeFilter.SHOW_ELEMENT,
this.filterButtons);
while (iterator.nextNode()) {
var test = iterator.currentNode;
if (test.accessKey.toLowerCase() == aAccessKeyLower &&
!test.disabled && !test.collapsed && !test.hidden) {
test.focus();
test.click();
return true;
}
}
return false;
]]>
</body>
</method>
<method name="_handleClick">
<body>
<![CDATA[
if (!this.disabled &&
(this.autoCheck || !this.hasAttribute("autoCheck"))) {
if (this.type == "checkbox") {
this.checked = !this.checked;
} else if (this.type == "radio") {
this.checked = true;
}
}
]]>
</body>
</method>
</implementation>
<handlers>
<!-- While it would seem we could do this by handling oncommand, we can't
because any external oncommand handlers might get called before ours,
and then they would see the incorrect value of checked. Additionally
a command attribute would redirect the command events anyway.-->
<handler event="click" button="0" action="this._handleClick();"/>
<handler event="keypress" key=" ">
<![CDATA[
this._handleClick();
// Prevent page from scrolling on the space key.
event.preventDefault();
]]>
</handler>
<handler event="keypress">
<![CDATA[
if (this.boxObject instanceof MenuBoxObject) {
if (this.open)
return;
} else {
if (event.keyCode == KeyEvent.DOM_VK_UP ||
(event.keyCode == KeyEvent.DOM_VK_LEFT &&
document.defaultView.getComputedStyle(this.parentNode, "")
.direction == "ltr") ||
(event.keyCode == KeyEvent.DOM_VK_RIGHT &&
document.defaultView.getComputedStyle(this.parentNode, "")
.direction == "rtl")) {
event.preventDefault();
window.document.commandDispatcher.rewindFocus();
return;
}
if (event.keyCode == KeyEvent.DOM_VK_DOWN ||
(event.keyCode == KeyEvent.DOM_VK_RIGHT &&
document.defaultView.getComputedStyle(this.parentNode, "")
.direction == "ltr") ||
(event.keyCode == KeyEvent.DOM_VK_LEFT &&
document.defaultView.getComputedStyle(this.parentNode, "")
.direction == "rtl")) {
event.preventDefault();
window.document.commandDispatcher.advanceFocus();
return;
}
}
if (event.keyCode || event.charCode <= 32 || event.altKey ||
event.ctrlKey || event.metaKey)
return; // No printable char pressed, not a potential accesskey
// Possible accesskey pressed
var charPressedLower = String.fromCharCode(event.charCode).toLowerCase();
// If the accesskey of the current button is pressed, just activate it
if (this.accessKey.toLowerCase() == charPressedLower) {
this.click();
return;
}
// Search for accesskey in the list of buttons for this doc and each subdoc
// Get the buttons for the main document and all sub-frames
for (var frameCount = -1; frameCount < window.top.frames.length; frameCount++) {
var doc = (frameCount == -1)? window.top.document:
window.top.frames[frameCount].document
if (this.fireAccessKeyButton(doc.documentElement, charPressedLower))
return;
}
// Test anonymous buttons
var dlg = window.top.document;
var buttonBox = dlg.getAnonymousElementByAttribute(dlg.documentElement,
"anonid", "buttons");
if (buttonBox)
this.fireAccessKeyButton(buttonBox, charPressedLower);
]]>
</handler>
</handlers>
</binding>
<binding id="button" display="xul:button"
extends="chrome://global/content/bindings/button.xml#button-base">
<resources>
<stylesheet src="chrome://global/skin/button.css"/>
</resources>
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
align="center" pack="center" flex="1" anonid="button-box">
<children>
<xul:image class="button-icon" xbl:inherits="src=image"/>
<xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
</children>
</xul:hbox>
</content>
</binding>
<binding id="menu" display="xul:menu"
extends="chrome://global/content/bindings/button.xml#button">
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
align="center" pack="center" flex="1">
<children>
<xul:hbox class="box-inherit" xbl:inherits="align,dir,pack,orient"
align="center" pack="center" flex="1">
<xul:image class="button-icon" xbl:inherits="src=image"/>
<xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
</xul:hbox>
<xul:dropmarker class="button-menu-dropmarker" xbl:inherits="open,disabled,label"/>
</children>
</xul:hbox>
</content>
<handlers>
<handler event="keypress" keycode="VK_RETURN" action="this.open = true;"/>
<handler event="keypress" key=" ">
<![CDATA[
this.open = true;
// Prevent page from scrolling on the space key.
event.preventDefault();
]]>
</handler>
</handlers>
</binding>
<binding id="menu-button-base"
extends="chrome://global/content/bindings/button.xml#button-base">
<implementation implements="nsIDOMEventListener">
<constructor>
this.init();
</constructor>
<method name="init">
<body>
<![CDATA[
var btn = document.getAnonymousElementByAttribute(this, "anonid", "button");
if (!btn)
throw "XBL binding for <button type=\"menu-button\"/> binding must contain an element with anonid=\"button\"";
var menubuttonParent = this;
btn.addEventListener("mouseover", function() {
if (!this.disabled)
menubuttonParent.buttonover = true;
}, true);
btn.addEventListener("mouseout", function() {
menubuttonParent.buttonover = false;
}, true);
btn.addEventListener("mousedown", function() {
if (!this.disabled) {
menubuttonParent.buttondown = true;
document.addEventListener("mouseup", menubuttonParent, true);
}
}, true);
]]>
</body>
</method>
<property name="buttonover" onget="return this.getAttribute('buttonover');">
<setter>
<![CDATA[
var v = val || val == "true";
if (!v && this.buttondown) {
this.buttondown = false;
this._pendingActive = true;
}
else if (this._pendingActive) {
this.buttondown = true;
this._pendingActive = false;
}
if (v)
this.setAttribute("buttonover", "true");
else
this.removeAttribute("buttonover");
return val;
]]>
</setter>
</property>
<property name="buttondown" onget="return this.getAttribute('buttondown') == 'true';">
<setter>
<![CDATA[
if (val || val == "true")
this.setAttribute("buttondown", "true");
else
this.removeAttribute("buttondown");
return val;
]]>
</setter>
</property>
<field name="_pendingActive">false</field>
<method name="handleEvent">
<parameter name="aEvent"/>
<body>
<![CDATA[
this._pendingActive = false;
this.buttondown = false;
document.removeEventListener("mouseup", this, true);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="keypress" keycode="VK_RETURN">
if (event.originalTarget == this)
this.open = true;
</handler>
<handler event="keypress" key=" ">
if (event.originalTarget == this) {
this.open = true;
// Prevent page from scrolling on the space key.
event.preventDefault();
}
</handler>
</handlers>
</binding>
<binding id="menu-button" display="xul:menu"
extends="chrome://global/content/bindings/button.xml#menu-button-base">
<resources>
<stylesheet src="chrome://global/skin/button.css"/>
</resources>
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:button class="box-inherit button-menubutton-button"
anonid="button" flex="1" allowevents="true"
xbl:inherits="disabled,crop,image,label,accesskey,command,
buttonover,buttondown,align,dir,pack,orient">
<children/>
</xul:button>
<xul:dropmarker class="button-menubutton-dropmarker" xbl:inherits="open,disabled,label"/>
</content>
</binding>
<binding id="button-image" display="xul:button"
extends="chrome://global/content/bindings/button.xml#button">
<content>
<xul:image class="button-image-icon" xbl:inherits="src=image"/>
</content>
</binding>
<binding id="button-repeat" display="xul:autorepeatbutton"
extends="chrome://global/content/bindings/button.xml#button"/>
</bindings>

View file

@ -0,0 +1,84 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="checkboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="checkbox" extends="chrome://global/content/bindings/checkbox.xml#checkbox-baseline">
<resources>
<stylesheet src="chrome://global/skin/checkbox.css"/>
</resources>
</binding>
<binding id="checkbox-baseline" role="xul:checkbox"
extends="chrome://global/content/bindings/general.xml#basetext">
<content>
<xul:image class="checkbox-check" xbl:inherits="checked,disabled"/>
<xul:hbox class="checkbox-label-box" flex="1">
<xul:image class="checkbox-icon" xbl:inherits="src"/>
<xul:label class="checkbox-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
</xul:hbox>
</content>
<implementation implements="nsIDOMXULCheckboxElement">
<method name="setChecked">
<parameter name="aValue"/>
<body>
<![CDATA[
var change = (aValue != (this.getAttribute('checked') == 'true'));
if (aValue)
this.setAttribute('checked', 'true');
else
this.removeAttribute('checked');
if (change) {
var event = document.createEvent('Events');
event.initEvent('CheckboxStateChange', true, true);
this.dispatchEvent(event);
}
return aValue;
]]>
</body>
</method>
<!-- public implementation -->
<property name="checked" onset="return this.setChecked(val);"
onget="return this.getAttribute('checked') == 'true';"/>
</implementation>
<handlers>
<!-- While it would seem we could do this by handling oncommand, we need can't
because any external oncommand handlers might get called before ours, and
then they would see the incorrect value of checked. -->
<handler event="click" button="0" action="if (!this.disabled) this.checked = !this.checked;"/>
<handler event="keypress" key=" ">
<![CDATA[
this.checked = !this.checked;
// Prevent page from scrolling on the space key.
event.preventDefault();
]]>
</handler>
</handlers>
</binding>
<binding id="checkbox-with-spacing"
extends="chrome://global/content/bindings/checkbox.xml#checkbox">
<content>
<xul:hbox class="checkbox-spacer-box">
<xul:image class="checkbox-check" xbl:inherits="checked,disabled"/>
</xul:hbox>
<xul:hbox class="checkbox-label-center-box" flex="1">
<xul:hbox class="checkbox-label-box" flex="1">
<xul:image class="checkbox-icon" xbl:inherits="src"/>
<xul:label class="checkbox-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
</xul:hbox>
</xul:hbox>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,565 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="colorpickerBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="colorpicker" extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/colorpicker.css"/>
</resources>
<content>
<xul:vbox flex="1">
<xul:hbox>
<xul:image class="colorpickertile cp-light" color="#FFFFFF"/>
<xul:image class="colorpickertile cp-light" color="#FFCCCC"/>
<xul:image class="colorpickertile cp-light" color="#FFCC99"/>
<xul:image class="colorpickertile cp-light" color="#FFFF99"/>
<xul:image class="colorpickertile cp-light" color="#FFFFCC"/>
<xul:image class="colorpickertile cp-light" color="#99FF99"/>
<xul:image class="colorpickertile cp-light" color="#99FFFF"/>
<xul:image class="colorpickertile cp-light" color="#CCFFFF"/>
<xul:image class="colorpickertile cp-light" color="#CCCCFF"/>
<xul:image class="colorpickertile cp-light" color="#FFCCFF"/>
</xul:hbox>
<xul:hbox>
<xul:image class="colorpickertile" color="#CCCCCC"/>
<xul:image class="colorpickertile" color="#FF6666"/>
<xul:image class="colorpickertile" color="#FF9966"/>
<xul:image class="colorpickertile cp-light" color="#FFFF66"/>
<xul:image class="colorpickertile cp-light" color="#FFFF33"/>
<xul:image class="colorpickertile cp-light" color="#66FF99"/>
<xul:image class="colorpickertile cp-light" color="#33FFFF"/>
<xul:image class="colorpickertile cp-light" color="#66FFFF"/>
<xul:image class="colorpickertile" color="#9999FF"/>
<xul:image class="colorpickertile" color="#FF99FF"/>
</xul:hbox>
<xul:hbox>
<xul:image class="colorpickertile" color="#C0C0C0"/>
<xul:image class="colorpickertile" color="#FF0000"/>
<xul:image class="colorpickertile" color="#FF9900"/>
<xul:image class="colorpickertile" color="#FFCC66"/>
<xul:image class="colorpickertile cp-light" color="#FFFF00"/>
<xul:image class="colorpickertile cp-light" color="#33FF33"/>
<xul:image class="colorpickertile" color="#66CCCC"/>
<xul:image class="colorpickertile" color="#33CCFF"/>
<xul:image class="colorpickertile" color="#6666CC"/>
<xul:image class="colorpickertile" color="#CC66CC"/>
</xul:hbox>
<xul:hbox>
<xul:image class="colorpickertile" color="#999999"/>
<xul:image class="colorpickertile" color="#CC0000"/>
<xul:image class="colorpickertile" color="#FF6600"/>
<xul:image class="colorpickertile" color="#FFCC33"/>
<xul:image class="colorpickertile" color="#FFCC00"/>
<xul:image class="colorpickertile" color="#33CC00"/>
<xul:image class="colorpickertile" color="#00CCCC"/>
<xul:image class="colorpickertile" color="#3366FF"/>
<xul:image class="colorpickertile" color="#6633FF"/>
<xul:image class="colorpickertile" color="#CC33CC"/>
</xul:hbox>
<xul:hbox>
<xul:image class="colorpickertile" color="#666666"/>
<xul:image class="colorpickertile" color="#990000"/>
<xul:image class="colorpickertile" color="#CC6600"/>
<xul:image class="colorpickertile" color="#CC9933"/>
<xul:image class="colorpickertile" color="#999900"/>
<xul:image class="colorpickertile" color="#009900"/>
<xul:image class="colorpickertile" color="#339999"/>
<xul:image class="colorpickertile" color="#3333FF"/>
<xul:image class="colorpickertile" color="#6600CC"/>
<xul:image class="colorpickertile" color="#993399"/>
</xul:hbox>
<xul:hbox>
<xul:image class="colorpickertile" color="#333333"/>
<xul:image class="colorpickertile" color="#660000"/>
<xul:image class="colorpickertile" color="#993300"/>
<xul:image class="colorpickertile" color="#996633"/>
<xul:image class="colorpickertile" color="#666600"/>
<xul:image class="colorpickertile" color="#006600"/>
<xul:image class="colorpickertile" color="#336666"/>
<xul:image class="colorpickertile" color="#000099"/>
<xul:image class="colorpickertile" color="#333399"/>
<xul:image class="colorpickertile" color="#663366"/>
</xul:hbox>
<xul:hbox>
<xul:image class="colorpickertile" color="#000000"/>
<xul:image class="colorpickertile" color="#330000"/>
<xul:image class="colorpickertile" color="#663300"/>
<xul:image class="colorpickertile" color="#663333"/>
<xul:image class="colorpickertile" color="#333300"/>
<xul:image class="colorpickertile" color="#003300"/>
<xul:image class="colorpickertile" color="#003333"/>
<xul:image class="colorpickertile" color="#000066"/>
<xul:image class="colorpickertile" color="#330099"/>
<xul:image class="colorpickertile" color="#330033"/>
</xul:hbox>
</xul:vbox>
<!-- Something to take tab focus
<button style="border : 0px; width: 0px; height: 0px;"/>
-->
</content>
<implementation implements="nsIDOMEventListener">
<property name="color">
<getter><![CDATA[
return this.mSelectedCell ? this.mSelectedCell.getAttribute("color") : null;
]]></getter>
<setter><![CDATA[
if (!val)
return val;
var uppercaseVal = val.toUpperCase();
// Translate standard HTML color strings:
if (uppercaseVal[0] != "#") {
switch (uppercaseVal) {
case "GREEN":
uppercaseVal = "#008000";
break;
case "LIME":
uppercaseVal = "#00FF00";
break;
case "OLIVE":
uppercaseVal = "#808000";
break;
case "TEAL":
uppercaseVal = "#008080";
break;
case "YELLOW":
uppercaseVal = "#FFFF00";
break;
case "RED":
uppercaseVal = "#FF0000";
break;
case "MAROON":
uppercaseVal = "#800000";
break;
case "PURPLE":
uppercaseVal = "#800080";
break;
case "FUCHSIA":
uppercaseVal = "#FF00FF";
break;
case "NAVY":
uppercaseVal = "#000080";
break;
case "BLUE":
uppercaseVal = "#0000FF";
break;
case "AQUA":
uppercaseVal = "#00FFFF";
break;
case "WHITE":
uppercaseVal = "#FFFFFF";
break;
case "SILVER":
uppercaseVal = "#C0C0C0";
break;
case "GRAY":
uppercaseVal = "#808080";
break;
default: // BLACK
uppercaseVal = "#000000";
break;
}
}
var cells = this.mBox.getElementsByAttribute("color", uppercaseVal);
if (cells.item(0)) {
this.selectCell(cells[0]);
this.hoverCell(this.mSelectedCell);
}
return val;
]]></setter>
</property>
<method name="initColor">
<parameter name="aColor"/>
<body><![CDATA[
// Use this to initialize color without
// triggering the "onselect" handler,
// which closes window when it's a popup
this.mDoOnSelect = false;
this.color = aColor;
this.mDoOnSelect = true;
]]></body>
</method>
<method name="initialize">
<body><![CDATA[
this.mSelectedCell = null;
this.mHoverCell = null;
this.mBox = document.getAnonymousNodes(this)[0];
this.mIsPopup = false;
this.mDoOnSelect = true;
let imageEls = this.mBox.querySelectorAll("image");
// We set the background of the picker tiles here using images in
// order for the color to show up even when author colors are
// disabled or the user is using high contrast mode.
for (let el of imageEls) {
let dataURI = "data:image/svg+xml,<svg style='background-color: " +
encodeURIComponent(el.getAttribute("color")) +
"' xmlns='http://www.w3.org/2000/svg' />";
el.setAttribute("src", dataURI);
}
this.hoverCell(this.mBox.childNodes[0].childNodes[0]);
// used to capture keydown at the document level
this.mPickerKeyDown = function(aEvent)
{
document._focusedPicker.pickerKeyDown(aEvent);
}
]]></body>
</method>
<method name="_fireEvent">
<parameter name="aTarget"/>
<parameter name="aEventName"/>
<body>
<![CDATA[
try {
var event = document.createEvent("Events");
event.initEvent(aEventName, true, true);
var cancel = !aTarget.dispatchEvent(event);
if (aTarget.hasAttribute("on" + aEventName)) {
var fn = new Function ("event", aTarget.getAttribute("on" + aEventName));
var rv = fn.call(aTarget, event);
if (rv == false)
cancel = true;
}
return !cancel;
}
catch (e) {
Components.utils.reportError(e);
}
return false;
]]>
</body>
</method>
<method name="resetHover">
<body><![CDATA[
if (this.mHoverCell)
this.mHoverCell.removeAttribute("hover");
]]></body>
</method>
<method name="getColIndex">
<parameter name="aCell"/>
<body><![CDATA[
var cell = aCell;
var idx;
for (idx = -1; cell; idx++)
cell = cell.previousSibling;
return idx;
]]></body>
</method>
<method name="isColorCell">
<parameter name="aCell"/>
<body><![CDATA[
return aCell && aCell.hasAttribute("color");
]]></body>
</method>
<method name="hoverLeft">
<body><![CDATA[
var cell = this.mHoverCell.previousSibling;
this.hoverCell(cell);
]]></body>
</method>
<method name="hoverRight">
<body><![CDATA[
var cell = this.mHoverCell.nextSibling;
this.hoverCell(cell);
]]></body>
</method>
<method name="hoverUp">
<body><![CDATA[
var row = this.mHoverCell.parentNode.previousSibling;
if (row) {
var colIdx = this.getColIndex(this.mHoverCell);
var cell = row.childNodes[colIdx];
this.hoverCell(cell);
}
]]></body>
</method>
<method name="hoverDown">
<body><![CDATA[
var row = this.mHoverCell.parentNode.nextSibling;
if (row) {
var colIdx = this.getColIndex(this.mHoverCell);
var cell = row.childNodes[colIdx];
this.hoverCell(cell);
}
]]></body>
</method>
<method name="hoverTo">
<parameter name="aRow"/>
<parameter name="aCol"/>
<body><![CDATA[
var row = this.mBox.childNodes[aRow];
if (!row) return;
var cell = row.childNodes[aCol];
if (!cell) return;
this.hoverCell(cell);
]]></body>
</method>
<method name="hoverCell">
<parameter name="aCell"/>
<body><![CDATA[
if (this.isColorCell(aCell)) {
this.resetHover();
aCell.setAttribute("hover", "true");
this.mHoverCell = aCell;
var event = document.createEvent('Events');
event.initEvent('DOMMenuItemActive', true, true);
aCell.dispatchEvent(event);
}
]]></body>
</method>
<method name="selectHoverCell">
<body><![CDATA[
this.selectCell(this.mHoverCell);
]]></body>
</method>
<method name="selectCell">
<parameter name="aCell"/>
<body><![CDATA[
if (this.isColorCell(aCell)) {
if (this.mSelectedCell)
this.mSelectedCell.removeAttribute("selected");
this.mSelectedCell = aCell;
aCell.setAttribute("selected", "true");
if (this.mDoOnSelect)
this._fireEvent(this, "select");
}
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.keyCode) {
case 37: // left
this.hoverLeft();
break;
case 38: // up
this.hoverUp();
break;
case 39: // right
this.hoverRight();
break;
case 40: // down
this.hoverDown();
break;
case 13: // enter
case 32: // space
this.selectHoverCell();
break;
}
]]></body>
</method>
<constructor><![CDATA[
this.initialize();
]]></constructor>
</implementation>
<handlers>
<handler event="mouseover"><![CDATA[
this.hoverCell(event.originalTarget);
]]></handler>
<handler event="click"><![CDATA[
if (event.originalTarget.hasAttribute("color")) {
this.selectCell(event.originalTarget);
this.hoverCell(this.mSelectedCell);
}
]]></handler>
<handler event="focus" phase="capturing">
<![CDATA[
if (!mIsPopup && this.getAttribute('focused') != 'true') {
this.setAttribute('focused', 'true');
document.addEventListener("keydown", this, true);
if (this.mSelectedCell)
this.hoverCell(this.mSelectedCell);
}
]]>
</handler>
<handler event="blur" phase="capturing">
<![CDATA[
if (!mIsPopup && this.getAttribute('focused') == 'true') {
document.removeEventListener("keydown", this, true);
this.removeAttribute('focused');
this.resetHover();
}
]]>
</handler>
</handlers>
</binding>
<binding id="colorpicker-button" display="xul:menu" role="xul:colorpicker"
extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/colorpicker.css"/>
</resources>
<content>
<xul:image class="colorpicker-button-colorbox" anonid="colorbox" flex="1" xbl:inherits="disabled"/>
<xul:panel class="colorpicker-button-menupopup"
anonid="colorpopup" noautofocus="true" level="top"
onmousedown="event.stopPropagation()"
onpopupshowing="this._colorPicker.onPopupShowing()"
onpopuphiding="this._colorPicker.onPopupHiding()"
onselect="this._colorPicker.pickerChange()">
<xul:colorpicker xbl:inherits="palettename,disabled" allowevents="true" anonid="colorpicker"/>
</xul:panel>
</content>
<implementation>
<property name="open"
onget="return this.getAttribute('open') == 'true'"
onset="this.showPopup();"/>
<property name="color">
<getter><![CDATA[
return this.getAttribute("color");
]]></getter>
<setter><![CDATA[
this.mColorBox.setAttribute("src",
"data:image/svg+xml,<svg style='background-color: " +
encodeURIComponent(val) +
"' xmlns='http://www.w3.org/2000/svg' />");
this.setAttribute("color", val);
return val;
]]></setter>
</property>
<method name="initialize">
<body><![CDATA[
this.mColorBox = document.getAnonymousElementByAttribute(this, "anonid", "colorbox");
this.mColorBox.setAttribute("src",
"data:image/svg+xml,<svg style='background-color: " +
encodeURIComponent(this.color) +
"' xmlns='http://www.w3.org/2000/svg' />");
var popup = document.getAnonymousElementByAttribute(this, "anonid", "colorpopup")
popup._colorPicker = this;
this.mPicker = document.getAnonymousElementByAttribute(this, "anonid", "colorpicker")
]]></body>
</method>
<method name="_fireEvent">
<parameter name="aTarget"/>
<parameter name="aEventName"/>
<body>
<![CDATA[
try {
var event = document.createEvent("Events");
event.initEvent(aEventName, true, true);
var cancel = !aTarget.dispatchEvent(event);
if (aTarget.hasAttribute("on" + aEventName)) {
var fn = new Function ("event", aTarget.getAttribute("on" + aEventName));
var rv = fn.call(aTarget, event);
if (rv == false)
cancel = true;
}
return !cancel;
}
catch (e) {
dump(e);
}
return false;
]]>
</body>
</method>
<method name="showPopup">
<body><![CDATA[
this.mPicker.parentNode.openPopup(this, "after_start", 0, 0, false, false);
]]></body>
</method>
<method name="hidePopup">
<body><![CDATA[
this.mPicker.parentNode.hidePopup();
]]></body>
</method>
<method name="onPopupShowing">
<body><![CDATA[
if ("resetHover" in this.mPicker)
this.mPicker.resetHover();
document.addEventListener("keydown", this.mPicker, true);
this.mPicker.mIsPopup = true;
// Initialize to current button's color
this.mPicker.initColor(this.color);
]]></body>
</method>
<method name="onPopupHiding">
<body><![CDATA[
// Removes the key listener
document.removeEventListener("keydown", this.mPicker, true);
this.mPicker.mIsPopup = false;
]]></body>
</method>
<method name="pickerChange">
<body><![CDATA[
this.color = this.mPicker.color;
setTimeout(function(aPopup) { aPopup.hidePopup() }, 1, this.mPicker.parentNode);
this._fireEvent(this, "change");
]]></body>
</method>
<constructor><![CDATA[
this.initialize();
]]></constructor>
</implementation>
<handlers>
<handler event="keydown"><![CDATA[
// open popup if key is space/up/left/right/down and popup is closed
if ( (event.keyCode == 32 || (event.keyCode > 36 && event.keyCode < 41)) && !this.open)
this.showPopup();
else if ( (event.keyCode == 27) && this.open)
this.hidePopup();
]]></handler>
</handlers>
</binding>
<binding id="colorpickertile" role="xul:colorpickertile">
</binding>
</bindings>

View file

@ -0,0 +1,45 @@
/* 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/. */
@namespace url("http://www.w3.org/1999/xhtml");
@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
.datetime-input-box-wrapper {
-moz-appearance: none;
display: inline-flex;
cursor: default;
background-color: inherit;
color: inherit;
}
.datetime-input {
-moz-appearance: none;
text-align: center;
padding: 0;
border: 0;
margin: 0;
ime-mode: disabled;
}
.datetime-separator {
margin: 0 !important;
}
.datetime-input[readonly],
.datetime-input[disabled] {
color: GrayText;
-moz-user-select: none;
}
.datetime-reset-button {
background-image: url(chrome://global/skin/icons/input-clear.svg);
background-color: transparent;
background-repeat: no-repeat;
background-size: 12px, 12px;
border: none;
height: 12px;
width: 12px;
align-self: center;
justify-content: flex-end;
}

View file

@ -0,0 +1,807 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="datetimeboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="time-input"
extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
<resources>
<stylesheet src="chrome://global/content/textbox.css"/>
<stylesheet src="chrome://global/skin/textbox.css"/>
<stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
</resources>
<implementation>
<constructor>
<![CDATA[
// TODO: Bug 1301312 - localization for input type=time input.
this.mHour12 = true;
this.mAMIndicator = "AM";
this.mPMIndicator = "PM";
this.mPlaceHolder = "--";
this.mSeparatorText = ":";
this.mMillisecSeparatorText = ".";
this.mMaxLength = 2;
this.mMillisecMaxLength = 3;
this.mDefaultStep = 60 * 1000; // in milliseconds
this.mMinHourInHour12 = 1;
this.mMaxHourInHour12 = 12;
this.mMinMinute = 0;
this.mMaxMinute = 59;
this.mMinSecond = 0;
this.mMaxSecond = 59;
this.mMinMillisecond = 0;
this.mMaxMillisecond = 999;
this.mHourPageUpDownInterval = 3;
this.mMinSecPageUpDownInterval = 10;
this.mHourField =
document.getAnonymousElementByAttribute(this, "anonid", "input-one");
this.mHourField.setAttribute("typeBuffer", "");
this.mMinuteField =
document.getAnonymousElementByAttribute(this, "anonid", "input-two");
this.mMinuteField.setAttribute("typeBuffer", "");
this.mDayPeriodField =
document.getAnonymousElementByAttribute(this, "anonid", "input-three");
this.mDayPeriodField.classList.remove("numeric");
this.mHourField.placeholder = this.mPlaceHolder;
this.mMinuteField.placeholder = this.mPlaceHolder;
this.mDayPeriodField.placeholder = this.mPlaceHolder;
this.mHourField.setAttribute("min", this.mMinHourInHour12);
this.mHourField.setAttribute("max", this.mMaxHourInHour12);
this.mMinuteField.setAttribute("min", this.mMinMinute);
this.mMinuteField.setAttribute("max", this.mMaxMinute);
this.mMinuteSeparator =
document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
this.mMinuteSeparator.textContent = this.mSeparatorText;
this.mSpaceSeparator =
document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
// space between time and am/pm field
this.mSpaceSeparator.textContent = " ";
this.mSecondSeparator = null;
this.mSecondField = null;
this.mMillisecSeparator = null;
this.mMillisecField = null;
if (this.mInputElement.value) {
this.setFieldsFromInputValue();
}
]]>
</constructor>
<method name="insertSeparator">
<parameter name="aSeparatorText"/>
<body>
<![CDATA[
let container = this.mHourField.parentNode;
const HTML_NS = "http://www.w3.org/1999/xhtml";
let separator = document.createElementNS(HTML_NS, "span");
separator.textContent = aSeparatorText;
separator.setAttribute("class", "datetime-separator");
container.insertBefore(separator, this.mSpaceSeparator);
return separator;
]]>
</body>
</method>
<method name="insertAdditionalField">
<parameter name="aPlaceHolder"/>
<parameter name="aMin"/>
<parameter name="aMax"/>
<parameter name="aSize"/>
<parameter name="aMaxLength"/>
<body>
<![CDATA[
let container = this.mHourField.parentNode;
const HTML_NS = "http://www.w3.org/1999/xhtml";
let field = document.createElementNS(HTML_NS, "input");
field.classList.add("textbox-input", "datetime-input", "numeric");
field.setAttribute("size", aSize);
field.setAttribute("maxlength", aMaxLength);
field.setAttribute("min", aMin);
field.setAttribute("max", aMax);
field.setAttribute("typeBuffer", "");
field.disabled = this.mInputElement.disabled;
field.readOnly = this.mInputElement.readOnly;
field.tabIndex = this.mInputElement.tabIndex;
field.placeholder = aPlaceHolder;
container.insertBefore(field, this.mSpaceSeparator);
return field;
]]>
</body>
</method>
<method name="setFieldsFromInputValue">
<body>
<![CDATA[
let value = this.mInputElement.value;
if (!value) {
this.clearInputFields(true);
return;
}
this.log("setFieldsFromInputValue: " + value);
let [hour, minute, second] = value.split(':');
this.setFieldValue(this.mHourField, hour);
this.setFieldValue(this.mMinuteField, minute);
if (this.mHour12) {
this.mDayPeriodField.value = (hour >= this.mMaxHourInHour12) ?
this.mPMIndicator : this.mAMIndicator;
}
if (!this.isEmpty(second)) {
let index = second.indexOf(".");
let millisecond;
if (index != -1) {
millisecond = second.substring(index + 1);
second = second.substring(0, index);
}
if (!this.mSecondField) {
this.mSecondSeparator = this.insertSeparator(this.mSeparatorText);
this.mSecondField = this.insertAdditionalField(this.mPlaceHolder,
this.mMinSecond, this.mMaxSecond, this.mMaxLength,
this.mMaxLength);
}
this.setFieldValue(this.mSecondField, second);
if (!this.isEmpty(millisecond)) {
if (!this.mMillisecField) {
this.mMillisecSeparator = this.insertSeparator(
this.mMillisecSeparatorText);
this.mMillisecField = this.insertAdditionalField(
this.mPlaceHolder, this.mMinMillisecond, this.mMaxMillisecond,
this.mMillisecMaxLength, this.mMillisecMaxLength);
}
this.setFieldValue(this.mMillisecField, millisecond);
} else if (this.mMillisecField) {
this.mMillisecField.remove();
this.mMillisecField = null;
this.mMillisecSeparator.remove();
this.mMillisecSeparator = null;
}
} else {
if (this.mSecondField) {
this.mSecondField.remove();
this.mSecondField = null;
this.mSecondSeparator.remove();
this.mSecondSeparator = null;
}
if (this.mMillisecField) {
this.mMillisecField.remove();
this.mMillisecField = null;
this.mMillisecSeparator.remove();
this.mMillisecSeparator = null;
}
}
this.notifyPicker();
]]>
</body>
</method>
<method name="setInputValueFromFields">
<body>
<![CDATA[
if (this.isEmpty(this.mHourField.value) ||
this.isEmpty(this.mMinuteField.value) ||
(this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) ||
(this.mSecondField && this.isEmpty(this.mSecondField.value)) ||
(this.mMillisecField && this.isEmpty(this.mMillisecField.value))) {
// We still need to notify picker in case any of the field has
// changed. If we can set input element value, then notifyPicker
// will be called in setFieldsFromInputValue().
this.notifyPicker();
return;
}
let hour = Number(this.mHourField.value);
if (this.mHour12) {
let dayPeriod = this.mDayPeriodField.value;
if (dayPeriod == this.mPMIndicator &&
hour < this.mMaxHourInHour12) {
hour += this.mMaxHourInHour12;
} else if (dayPeriod == this.mAMIndicator &&
hour == this.mMaxHourInHour12) {
hour = 0;
}
}
hour = (hour < 10) ? ("0" + hour) : hour;
let time = hour + ":" + this.mMinuteField.value;
if (this.mSecondField) {
time += ":" + this.mSecondField.value;
}
if (this.mMillisecField) {
time += "." + this.mMillisecField.value;
}
this.log("setInputValueFromFields: " + time);
this.mInputElement.setUserInput(time);
]]>
</body>
</method>
<method name="setFieldsFromPicker">
<parameter name="aValue"/>
<body>
<![CDATA[
let hour = aValue.hour;
let minute = aValue.minute;
this.log("setFieldsFromPicker: " + hour + ":" + minute);
if (!this.isEmpty(hour)) {
this.setFieldValue(this.mHourField, hour);
if (this.mHour12) {
this.mDayPeriodField.value =
(hour >= this.mMaxHourInHour12) ? this.mPMIndicator
: this.mAMIndicator;
}
}
if (!this.isEmpty(minute)) {
this.setFieldValue(this.mMinuteField, minute);
}
]]>
</body>
</method>
<method name="clearInputFields">
<parameter name="aFromInputElement"/>
<body>
<![CDATA[
this.log("clearInputFields");
if (this.isDisabled() || this.isReadonly()) {
return;
}
if (this.mHourField && !this.mHourField.disabled &&
!this.mHourField.readOnly) {
this.mHourField.value = "";
}
if (this.mMinuteField && !this.mMinuteField.disabled &&
!this.mMinuteField.readOnly) {
this.mMinuteField.value = "";
}
if (this.mSecondField && !this.mSecondField.disabled &&
!this.mSecondField.readOnly) {
this.mSecondField.value = "";
}
if (this.mMillisecField && !this.mMillisecField.disabled &&
!this.mMillisecField.readOnly) {
this.mMillisecField.value = "";
}
if (this.mDayPeriodField && !this.mDayPeriodField.disabled &&
!this.mDayPeriodField.readOnly) {
this.mDayPeriodField.value = "";
}
if (!aFromInputElement) {
this.mInputElement.setUserInput("");
}
]]>
</body>
</method>
<method name="incrementFieldValue">
<parameter name="aTargetField"/>
<parameter name="aTimes"/>
<body>
<![CDATA[
let value;
// Use current time if field is empty.
if (this.isEmpty(aTargetField.value)) {
let now = new Date();
if (aTargetField == this.mHourField) {
value = now.getHours() % this.mMaxHourInHour12 ||
this.mMaxHourInHour12;
} else if (aTargetField == this.mMinuteField) {
value = now.getMinutes();
} else if (aTargetField == this.mSecondField) {
value = now.getSeconds();
} else if (aTargetField == this.mMillisecField) {
value = now.getMilliseconds();
} else {
this.log("Field not supported in incrementFieldValue.");
return;
}
} else {
value = Number(aTargetField.value);
}
let min = aTargetField.getAttribute("min");
let max = aTargetField.getAttribute("max");
value += aTimes;
if (value > max) {
value -= (max - min + 1);
} else if (value < min) {
value += (max - min + 1);
}
this.setFieldValue(aTargetField, value);
aTargetField.select();
]]>
</body>
</method>
<method name="handleKeyboardNav">
<parameter name="aEvent"/>
<body>
<![CDATA[
if (this.isDisabled() || this.isReadonly()) {
return;
}
let targetField = aEvent.originalTarget;
let key = aEvent.key;
if (this.mDayPeriodField &&
targetField == this.mDayPeriodField) {
// Home/End key does nothing on AM/PM field.
if (key == "Home" || key == "End") {
return;
}
this.mDayPeriodField.value =
this.mDayPeriodField.value == this.mAMIndicator ?
this.mPMIndicator : this.mAMIndicator;
this.mDayPeriodField.select();
this.setInputValueFromFields();
return;
}
switch (key) {
case "ArrowUp":
this.incrementFieldValue(targetField, 1);
break;
case "ArrowDown":
this.incrementFieldValue(targetField, -1);
break;
case "PageUp":
this.incrementFieldValue(targetField,
targetField == this.mHourField ? this.mHourPageUpDownInterval
: this.mMinSecPageUpDownInterval);
break;
case "PageDown":
this.incrementFieldValue(targetField,
targetField == this.mHourField ? (0 - this.mHourPageUpDownInterval)
: (0 - this.mMinSecPageUpDownInterval));
break;
case "Home":
let min = targetField.getAttribute("min");
this.setFieldValue(targetField, min);
targetField.select();
break;
case "End":
let max = targetField.getAttribute("max");
this.setFieldValue(targetField, max);
targetField.select();
break;
}
this.setInputValueFromFields();
]]>
</body>
</method>
<method name="handleKeypress">
<parameter name="aEvent"/>
<body>
<![CDATA[
if (this.isDisabled() || this.isReadonly()) {
return;
}
let targetField = aEvent.originalTarget;
let key = aEvent.key;
if (this.mDayPeriodField &&
targetField == this.mDayPeriodField) {
if (key == "a" || key == "A") {
this.mDayPeriodField.value = this.mAMIndicator;
this.mDayPeriodField.select();
} else if (key == "p" || key == "P") {
this.mDayPeriodField.value = this.mPMIndicator;
this.mDayPeriodField.select();
}
return;
}
if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
let buffer = targetField.getAttribute("typeBuffer") || "";
buffer = buffer.concat(key);
this.setFieldValue(targetField, buffer);
targetField.select();
let n = Number(buffer);
let max = targetField.getAttribute("max");
if (buffer.length >= targetField.maxLength || n * 10 > max) {
buffer = "";
this.advanceToNextField();
}
targetField.setAttribute("typeBuffer", buffer);
}
]]>
</body>
</method>
<method name="setFieldValue">
<parameter name="aField"/>
<parameter name="aValue"/>
<body>
<![CDATA[
let value = Number(aValue);
if (isNaN(value)) {
this.log("NaN on setFieldValue!");
return;
}
if (aField.maxLength == this.mMaxLength) { // For hour, minute and second
if (aField == this.mHourField && this.mHour12) {
value = (value > this.mMaxHourInHour12) ?
value - this.mMaxHourInHour12 : value;
if (aValue == "00") {
value = this.mMaxHourInHour12;
}
}
// prepend zero
if (value < 10) {
value = "0" + value;
}
} else if (aField.maxLength == this.mMillisecMaxLength) {
// prepend zeroes
if (value < 10) {
value = "00" + value;
} else if (value < 100) {
value = "0" + value;
}
}
aField.value = value;
]]>
</body>
</method>
<method name="isValueAvailable">
<body>
<![CDATA[
// Picker only cares about hour:minute.
return !this.isEmpty(this.mHourField.value) ||
!this.isEmpty(this.mMinuteField.value);
]]>
</body>
</method>
<method name="getCurrentValue">
<body>
<![CDATA[
let hour;
if (!this.isEmpty(this.mHourField.value)) {
hour = Number(this.mHourField.value);
if (this.mHour12) {
let dayPeriod = this.mDayPeriodField.value;
if (dayPeriod == this.mPMIndicator &&
hour < this.mMaxHourInHour12) {
hour += this.mMaxHourInHour12;
} else if (dayPeriod == this.mAMIndicator &&
hour == this.mMaxHourInHour12) {
hour = 0;
}
}
}
let minute;
if (!this.isEmpty(this.mMinuteField.value)) {
minute = Number(this.mMinuteField.value);
}
// Picker only needs hour/minute.
let time = { hour, minute };
this.log("getCurrentValue: " + JSON.stringify(time));
return time;
]]>
</body>
</method>
</implementation>
</binding>
<binding id="datetime-input-base">
<resources>
<stylesheet src="chrome://global/content/textbox.css"/>
<stylesheet src="chrome://global/skin/textbox.css"/>
<stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
</resources>
<content>
<html:div class="datetime-input-box-wrapper"
xbl:inherits="context,disabled,readonly">
<html:span>
<html:input anonid="input-one"
class="textbox-input datetime-input numeric"
size="2" maxlength="2"
xbl:inherits="disabled,readonly,tabindex"/>
<html:span anonid="sep-first" class="datetime-separator"></html:span>
<html:input anonid="input-two"
class="textbox-input datetime-input numeric"
size="2" maxlength="2"
xbl:inherits="disabled,readonly,tabindex"/>
<html:span anonid="sep-second" class="datetime-separator"></html:span>
<html:input anonid="input-three"
class="textbox-input datetime-input numeric"
size="2" maxlength="2"
xbl:inherits="disabled,readonly,tabindex"/>
</html:span>
<html:button class="datetime-reset-button" anoid="reset-button"
tabindex="-1" xbl:inherits="disabled"
onclick="document.getBindingParent(this).clearInputFields(false);"/>
</html:div>
</content>
<implementation implements="nsIDateTimeInputArea">
<constructor>
<![CDATA[
this.DEBUG = false;
this.mInputElement = this.parentNode;
this.mMin = this.mInputElement.min;
this.mMax = this.mInputElement.max;
this.mStep = this.mInputElement.step;
this.mIsPickerOpen = false;
]]>
</constructor>
<method name="log">
<parameter name="aMsg"/>
<body>
<![CDATA[
if (this.DEBUG) {
dump("[DateTimeBox] " + aMsg + "\n");
}
]]>
</body>
</method>
<method name="focusInnerTextBox">
<body>
<![CDATA[
this.log("focusInnerTextBox");
document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus();
]]>
</body>
</method>
<method name="blurInnerTextBox">
<body>
<![CDATA[
this.log("blurInnerTextBox");
if (this.mLastFocusedField) {
this.mLastFocusedField.blur();
}
]]>
</body>
</method>
<method name="notifyInputElementValueChanged">
<body>
<![CDATA[
this.log("inputElementValueChanged");
this.setFieldsFromInputValue();
]]>
</body>
</method>
<method name="setValueFromPicker">
<parameter name="aValue"/>
<body>
<![CDATA[
this.setFieldsFromPicker(aValue);
]]>
</body>
</method>
<method name="advanceToNextField">
<parameter name="aReverse"/>
<body>
<![CDATA[
this.log("advanceToNextField");
let focusedInput = this.mLastFocusedField;
let next = aReverse ? focusedInput.previousElementSibling
: focusedInput.nextElementSibling;
if (!next && !aReverse) {
this.setInputValueFromFields();
return;
}
while (next) {
if (next.type == "text" && !next.disabled) {
next.focus();
break;
}
next = aReverse ? next.previousElementSibling
: next.nextElementSibling;
}
]]>
</body>
</method>
<method name="setPickerState">
<parameter name="aIsOpen"/>
<body>
<![CDATA[
this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
this.mIsPickerOpen = aIsOpen;
]]>
</body>
</method>
<method name="isEmpty">
<parameter name="aValue"/>
<body>
return (aValue == undefined || 0 === aValue.length);
</body>
</method>
<method name="clearInputFields">
<body>
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
</body>
</method>
<method name="setFieldsFromInputValue">
<body>
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
</body>
</method>
<method name="setInputValueFromFields">
<body>
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
</body>
</method>
<method name="setFieldsFromPicker">
<body>
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
</body>
</method>
<method name="handleKeypress">
<body>
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
</body>
</method>
<method name="handleKeyboardNav">
<body>
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
</body>
</method>
<method name="notifyPicker">
<body>
<![CDATA[
if (this.mIsPickerOpen && this.isValueAvailable()) {
this.mInputElement.updateDateTimePicker(this.getCurrentValue());
}
]]>
</body>
</method>
<method name="isDisabled">
<body>
<![CDATA[
return this.hasAttribute("disabled");
]]>
</body>
</method>
<method name="isReadonly">
<body>
<![CDATA[
return this.hasAttribute("readonly");
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="focus">
<![CDATA[
this.log("focus on: " + event.originalTarget);
let target = event.originalTarget;
if (target.type == "text") {
this.mLastFocusedField = target;
target.select();
}
]]>
</handler>
<handler event="blur">
<![CDATA[
this.setInputValueFromFields();
]]>
</handler>
<handler event="click">
<![CDATA[
// XXX: .originalTarget is not expected.
// When clicking on one of the inner text boxes, the .originalTarget is
// a HTMLDivElement and when clicking on the reset button, it's a
// HTMLButtonElement but it's not equal to our reset-button.
this.log("click on: " + event.originalTarget);
if (event.defaultPrevented || this.isDisabled() || this.isReadonly()) {
return;
}
if (!(event.originalTarget instanceof HTMLButtonElement)) {
this.mInputElement.openDateTimePicker(this.getCurrentValue());
}
]]>
</handler>
<handler event="keypress" phase="capturing">
<![CDATA[
let key = event.key;
this.log("keypress: " + key);
if (key == "Backspace" || key == "Tab") {
return;
}
if (key == "Enter" || key == " ") {
// Close picker on Enter and Space.
this.mInputElement.closeDateTimePicker();
}
if (key == "ArrowUp" || key == "ArrowDown" ||
key == "PageUp" || key == "PageDown" ||
key == "Home" || key == "End") {
this.handleKeyboardNav(event);
} else if (key == "ArrowRight" || key == "ArrowLeft") {
this.advanceToNextField((key == "ArrowRight" ? false : true));
} else {
this.handleKeypress(event);
}
event.preventDefault();
]]>
</handler>
</handlers>
</binding>
</bindings>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,181 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="dateTimePopupBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="datetime-popup"
extends="chrome://global/content/bindings/popup.xml#arrowpanel">
<implementation>
<field name="dateTimePopupFrame">
this.querySelector("#dateTimePopupFrame");
</field>
<field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
<field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
<method name="loadPicker">
<parameter name="type"/>
<parameter name="detail"/>
<body><![CDATA[
this.hidden = false;
this.type = type;
this.pickerState = {};
// TODO: Resize picker according to content zoom level
this.style.fontSize = "10px";
switch (type) {
case "time": {
this.detail = detail;
this.dateTimePopupFrame.addEventListener("load", this, true);
this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/timepicker.xhtml");
this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
break;
}
}
]]></body>
</method>
<method name="closePicker">
<body><![CDATA[
this.hidden = true;
this.setInputBoxValue(true);
this.pickerState = {};
this.type = undefined;
this.dateTimePopupFrame.removeEventListener("load", this, true);
this.dateTimePopupFrame.contentDocument.removeEventListener("TimePickerPopupChanged", this, false);
this.dateTimePopupFrame.setAttribute("src", "");
]]></body>
</method>
<method name="setPopupValue">
<parameter name="data"/>
<body><![CDATA[
switch (this.type) {
case "time": {
this.postMessageToPicker({
name: "TimePickerSetValue",
detail: data.value
});
break;
}
}
]]></body>
</method>
<method name="initPicker">
<parameter name="detail"/>
<body><![CDATA[
switch (this.type) {
case "time": {
const { hour, minute } = detail.value;
const format = detail.format || "12";
const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");
this.postMessageToPicker({
name: "TimePickerInit",
detail: {
hour,
minute,
format,
locale,
min: detail.min,
max: detail.max,
step: detail.step,
}
});
break;
}
}
]]></body>
</method>
<method name="setInputBoxValue">
<parameter name="passAllValues"/>
<body><![CDATA[
/**
* @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
*/
switch (this.type) {
case "time": {
const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } = this.pickerState;
const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
if (passAllValues && isAnyValueSet) {
this.sendPickerValueChanged({ hour, minute });
} else {
this.sendPickerValueChanged({
hour: isHourSet || isDayPeriodSet ? hour : undefined,
minute: isMinuteSet ? minute : undefined
});
}
break;
}
}
]]></body>
</method>
<method name="sendPickerValueChanged">
<parameter name="value"/>
<body><![CDATA[
switch (this.type) {
case "time": {
this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
detail: {
hour: value.hour,
minute: value.minute
}
}));
break;
}
}
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.type) {
case "load": {
this.initPicker(this.detail);
this.dateTimePopupFrame.contentWindow.addEventListener("message", this, false);
break;
}
case "message": {
this.handleMessage(aEvent);
break;
}
}
]]></body>
</method>
<method name="handleMessage">
<parameter name="aEvent"/>
<body><![CDATA[
if (!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
return;
}
switch (aEvent.data.name) {
case "TimePickerPopupChanged": {
this.pickerState = aEvent.data.detail;
this.setInputBoxValue();
break;
}
}
]]></body>
</method>
<method name="postMessageToPicker">
<parameter name="data"/>
<body><![CDATA[
if (this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="popuphiding">
<![CDATA[
this.closePicker();
]]>
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,448 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="dialogBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="dialog" extends="chrome://global/content/bindings/general.xml#root-element">
<resources>
<stylesheet src="chrome://global/skin/dialog.css"/>
</resources>
<content>
<xul:vbox class="box-inherit dialog-content-box" flex="1">
<children/>
</xul:vbox>
<xul:hbox class="dialog-button-box" anonid="buttons"
xbl:inherits="pack=buttonpack,align=buttonalign,dir=buttondir,orient=buttonorient"
#ifdef XP_UNIX
>
<xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
<xul:button dlgtype="help" class="dialog-button" hidden="true"/>
<xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
<xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
<xul:spacer anonid="spacer" flex="1"/>
<xul:button dlgtype="cancel" class="dialog-button"/>
<xul:button dlgtype="accept" class="dialog-button" xbl:inherits="disabled=buttondisabledaccept"/>
#else
pack="end">
<xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
<xul:spacer anonid="spacer" flex="1" hidden="true"/>
<xul:button dlgtype="accept" class="dialog-button" xbl:inherits="disabled=buttondisabledaccept"/>
<xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
<xul:button dlgtype="cancel" class="dialog-button"/>
<xul:button dlgtype="help" class="dialog-button" hidden="true"/>
<xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
#endif
</xul:hbox>
</content>
<implementation>
<field name="_mStrBundle">null</field>
<field name="_closeHandler">(function(event) {
if (!document.documentElement.cancelDialog())
event.preventDefault();
})</field>
<property name="buttons"
onget="return this.getAttribute('buttons');"
onset="this._configureButtons(val); return val;"/>
<property name="defaultButton">
<getter>
<![CDATA[
if (this.hasAttribute("defaultButton"))
return this.getAttribute("defaultButton");
return "accept"; // default to the accept button
]]>
</getter>
<setter>
<![CDATA[
this._setDefaultButton(val);
return val;
]]>
</setter>
</property>
<method name="acceptDialog">
<body>
<![CDATA[
return this._doButtonCommand("accept");
]]>
</body>
</method>
<method name="cancelDialog">
<body>
<![CDATA[
return this._doButtonCommand("cancel");
]]>
</body>
</method>
<method name="getButton">
<parameter name="aDlgType"/>
<body>
<![CDATA[
return this._buttons[aDlgType];
]]>
</body>
</method>
<method name="moveToAlertPosition">
<body>
<![CDATA[
// hack. we need this so the window has something like its final size
if (window.outerWidth == 1) {
dump("Trying to position a sizeless window; caller should have called sizeToContent() or sizeTo(). See bug 75649.\n");
sizeToContent();
}
if (opener) {
var xOffset = (opener.outerWidth - window.outerWidth) / 2;
var yOffset = opener.outerHeight / 5;
var newX = opener.screenX + xOffset;
var newY = opener.screenY + yOffset;
} else {
newX = (screen.availWidth - window.outerWidth) / 2;
newY = (screen.availHeight - window.outerHeight) / 2;
}
// ensure the window is fully onscreen (if smaller than the screen)
if (newX < screen.availLeft)
newX = screen.availLeft + 20;
if ((newX + window.outerWidth) > (screen.availLeft + screen.availWidth))
newX = (screen.availLeft + screen.availWidth) - window.outerWidth - 20;
if (newY < screen.availTop)
newY = screen.availTop + 20;
if ((newY + window.outerHeight) > (screen.availTop + screen.availHeight))
newY = (screen.availTop + screen.availHeight) - window.outerHeight - 60;
window.moveTo( newX, newY );
]]>
</body>
</method>
<method name="centerWindowOnScreen">
<body>
<![CDATA[
var xOffset = screen.availWidth/2 - window.outerWidth/2;
var yOffset = screen.availHeight/2 - window.outerHeight/2;
xOffset = xOffset > 0 ? xOffset : 0;
yOffset = yOffset > 0 ? yOffset : 0;
window.moveTo(xOffset, yOffset);
]]>
</body>
</method>
<constructor>
<![CDATA[
this._configureButtons(this.buttons);
// listen for when window is closed via native close buttons
window.addEventListener("close", this._closeHandler, false);
// for things that we need to initialize after onload fires
window.addEventListener("load", this.postLoadInit, false);
window.moveToAlertPosition = this.moveToAlertPosition;
window.centerWindowOnScreen = this.centerWindowOnScreen;
]]>
</constructor>
<method name="postLoadInit">
<parameter name="aEvent"/>
<body>
<![CDATA[
function focusInit() {
const dialog = document.documentElement;
const defaultButton = dialog.getButton(dialog.defaultButton);
// give focus to the first focusable element in the dialog
if (!document.commandDispatcher.focusedElement) {
document.commandDispatcher.advanceFocusIntoSubtree(dialog);
var focusedElt = document.commandDispatcher.focusedElement;
if (focusedElt) {
var initialFocusedElt = focusedElt;
while (focusedElt.localName == "tab" ||
focusedElt.getAttribute("noinitialfocus") == "true") {
document.commandDispatcher.advanceFocusIntoSubtree(focusedElt);
focusedElt = document.commandDispatcher.focusedElement;
if (focusedElt == initialFocusedElt)
break;
}
if (initialFocusedElt.localName == "tab") {
if (focusedElt.hasAttribute("dlgtype")) {
// We don't want to focus on anonymous OK, Cancel, etc. buttons,
// so return focus to the tab itself
initialFocusedElt.focus();
}
}
else if (!/Mac/.test(navigator.platform) &&
focusedElt.hasAttribute("dlgtype") && focusedElt != defaultButton) {
defaultButton.focus();
}
}
}
try {
if (defaultButton)
window.notifyDefaultButtonLoaded(defaultButton);
} catch (e) { }
}
// Give focus after onload completes, see bug 103197.
setTimeout(focusInit, 0);
]]>
</body>
</method>
<property name="mStrBundle">
<getter>
<![CDATA[
if (!this._mStrBundle) {
// need to create string bundle manually instead of using <xul:stringbundle/>
// see bug 63370 for details
this._mStrBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService)
.createBundle("chrome://global/locale/dialog.properties");
}
return this._mStrBundle;
]]></getter>
</property>
<method name="_configureButtons">
<parameter name="aButtons"/>
<body>
<![CDATA[
// by default, get all the anonymous button elements
var buttons = {};
this._buttons = buttons;
buttons.accept = document.getAnonymousElementByAttribute(this, "dlgtype", "accept");
buttons.cancel = document.getAnonymousElementByAttribute(this, "dlgtype", "cancel");
buttons.extra1 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra1");
buttons.extra2 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra2");
buttons.help = document.getAnonymousElementByAttribute(this, "dlgtype", "help");
buttons.disclosure = document.getAnonymousElementByAttribute(this, "dlgtype", "disclosure");
// look for any overriding explicit button elements
var exBtns = this.getElementsByAttribute("dlgtype", "*");
var dlgtype;
var i;
for (i = 0; i < exBtns.length; ++i) {
dlgtype = exBtns[i].getAttribute("dlgtype");
buttons[dlgtype].hidden = true; // hide the anonymous button
buttons[dlgtype] = exBtns[i];
}
// add the label and oncommand handler to each button
for (dlgtype in buttons) {
var button = buttons[dlgtype];
button.addEventListener("command", this._handleButtonCommand, true);
// don't override custom labels with pre-defined labels on explicit buttons
if (!button.hasAttribute("label")) {
// dialog attributes override the default labels in dialog.properties
if (this.hasAttribute("buttonlabel"+dlgtype)) {
button.setAttribute("label", this.getAttribute("buttonlabel"+dlgtype));
if (this.hasAttribute("buttonaccesskey"+dlgtype))
button.setAttribute("accesskey", this.getAttribute("buttonaccesskey"+dlgtype));
} else if (dlgtype != "extra1" && dlgtype != "extra2") {
button.setAttribute("label", this.mStrBundle.GetStringFromName("button-"+dlgtype));
var accessKey = this.mStrBundle.GetStringFromName("accesskey-"+dlgtype);
if (accessKey)
button.setAttribute("accesskey", accessKey);
}
}
// allow specifying alternate icons in the dialog header
if (!button.hasAttribute("icon")) {
// if there's an icon specified, use that
if (this.hasAttribute("buttonicon"+dlgtype))
button.setAttribute("icon", this.getAttribute("buttonicon"+dlgtype));
// otherwise set defaults
else
switch (dlgtype) {
case "accept":
button.setAttribute("icon", "accept");
break;
case "cancel":
button.setAttribute("icon", "cancel");
break;
case "disclosure":
button.setAttribute("icon", "properties");
break;
case "help":
button.setAttribute("icon", "help");
break;
default:
break;
}
}
}
// ensure that hitting enter triggers the default button command
this.defaultButton = this.defaultButton;
// if there is a special button configuration, use it
if (aButtons) {
// expect a comma delimited list of dlgtype values
var list = aButtons.split(",");
// mark shown dlgtypes as true
var shown = { accept: false, cancel: false, help: false,
disclosure: false, extra1: false, extra2: false };
for (i = 0; i < list.length; ++i)
shown[list[i].replace(/ /g, "")] = true;
// hide/show the buttons we want
for (dlgtype in buttons)
buttons[dlgtype].hidden = !shown[dlgtype];
// show the spacer on Windows only when the extra2 button is present
if (/Win/.test(navigator.platform)) {
var spacer = document.getAnonymousElementByAttribute(this, "anonid", "spacer");
spacer.removeAttribute("hidden");
spacer.setAttribute("flex", shown["extra2"]?"1":"0");
}
}
]]>
</body>
</method>
<method name="_setDefaultButton">
<parameter name="aNewDefault"/>
<body>
<![CDATA[
// remove the default attribute from the previous default button, if any
var oldDefaultButton = this.getButton(this.defaultButton);
if (oldDefaultButton)
oldDefaultButton.removeAttribute("default");
var newDefaultButton = this.getButton(aNewDefault);
if (newDefaultButton) {
this.setAttribute("defaultButton", aNewDefault);
newDefaultButton.setAttribute("default", "true");
}
else {
this.setAttribute("defaultButton", "none");
if (aNewDefault != "none")
dump("invalid new default button: " + aNewDefault + ", assuming: none\n");
}
]]>
</body>
</method>
<method name="_handleButtonCommand">
<parameter name="aEvent"/>
<body>
<![CDATA[
return document.documentElement._doButtonCommand(
aEvent.target.getAttribute("dlgtype"));
]]>
</body>
</method>
<method name="_doButtonCommand">
<parameter name="aDlgType"/>
<body>
<![CDATA[
var button = this.getButton(aDlgType);
if (!button.disabled) {
var noCancel = this._fireButtonEvent(aDlgType);
if (noCancel) {
if (aDlgType == "accept" || aDlgType == "cancel") {
var closingEvent = new CustomEvent("dialogclosing", {
bubbles: true,
detail: { button: aDlgType },
});
this.dispatchEvent(closingEvent);
window.close();
}
}
return noCancel;
}
return true;
]]>
</body>
</method>
<method name="_fireButtonEvent">
<parameter name="aDlgType"/>
<body>
<![CDATA[
var event = document.createEvent("Events");
event.initEvent("dialog"+aDlgType, true, true);
// handle dom event handlers
var noCancel = this.dispatchEvent(event);
// handle any xml attribute event handlers
var handler = this.getAttribute("ondialog"+aDlgType);
if (handler != "") {
var fn = new Function("event", handler);
var returned = fn(event);
if (returned == false)
noCancel = false;
}
return noCancel;
]]>
</body>
</method>
<method name="_hitEnter">
<parameter name="evt"/>
<body>
<![CDATA[
if (evt.defaultPrevented)
return;
var btn = this.getButton(this.defaultButton);
if (btn)
this._doButtonCommand(this.defaultButton);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="keypress" keycode="VK_RETURN"
group="system" action="this._hitEnter(event);"/>
<handler event="keypress" keycode="VK_ESCAPE" group="system">
if (!event.defaultPrevented)
this.cancelDialog();
</handler>
#ifdef XP_MACOSX
<handler event="keypress" key="." modifiers="meta" phase="capturing" action="this.cancelDialog();"/>
#else
<handler event="focus" phase="capturing">
var btn = this.getButton(this.defaultButton);
if (btn)
btn.setAttribute("default", event.originalTarget == btn || !(event.originalTarget instanceof Components.interfaces.nsIDOMXULButtonElement));
</handler>
#endif
</handlers>
</binding>
<binding id="dialogheader">
<resources>
<stylesheet src="chrome://global/skin/dialog.css"/>
</resources>
<content>
<xul:label class="dialogheader-title" xbl:inherits="value=title,crop" crop="right" flex="1"/>
<xul:label class="dialogheader-description" xbl:inherits="value=description"/>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,195 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="editorBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="editor" role="outerdoc">
<implementation type="application/javascript">
<constructor>
<![CDATA[
// Make window editable immediately only
// if the "editortype" attribute is supplied
// This allows using same contentWindow for different editortypes,
// where the type is determined during the apps's window.onload handler.
if (this.editortype)
this.makeEditable(this.editortype, true);
]]>
</constructor>
<destructor/>
<field name="_editorContentListener">
<![CDATA[
({
QueryInterface: function(iid)
{
if (iid.equals(Components.interfaces.nsIURIContentListener) ||
iid.equals(Components.interfaces.nsISupportsWeakReference) ||
iid.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
onStartURIOpen: function(uri)
{
return false;
},
doContent: function(contentType, isContentPreferred, request, contentHandler)
{
return false;
},
isPreferred: function(contentType, desiredContentType)
{
return false;
},
canHandleContent: function(contentType, isContentPreferred, desiredContentType)
{
return false;
},
loadCookie: null,
parentContentListener: null
})
]]>
</field>
<method name="makeEditable">
<parameter name="editortype"/>
<parameter name="waitForUrlLoad"/>
<body>
<![CDATA[
this.editingSession.makeWindowEditable(this.contentWindow, editortype, waitForUrlLoad, true, false);
this.setAttribute("editortype", editortype);
this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIURIContentListener)
.parentContentListener = this._editorContentListener;
]]>
</body>
</method>
<method name="getEditor">
<parameter name="containingWindow"/>
<body>
<![CDATA[
return this.editingSession.getEditorForWindow(containingWindow);
]]>
</body>
</method>
<method name="getHTMLEditor">
<parameter name="containingWindow"/>
<body>
<![CDATA[
var editor = this.editingSession.getEditorForWindow(containingWindow);
return editor.QueryInterface(Components.interfaces.nsIHTMLEditor);
]]>
</body>
</method>
<field name="_finder">null</field>
<property name="finder" readonly="true">
<getter><![CDATA[
if (!this._finder) {
if (!this.docShell)
return null;
let Finder = Components.utils.import("resource://gre/modules/Finder.jsm", {}).Finder;
this._finder = new Finder(this.docShell);
}
return this._finder;
]]></getter>
</property>
<field name="_fastFind">null</field>
<property name="fastFind"
readonly="true">
<getter>
<![CDATA[
if (!this._fastFind) {
if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
return null;
if (!this.docShell)
return null;
this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
.createInstance(Components.interfaces.nsITypeAheadFind);
this._fastFind.init(this.docShell);
}
return this._fastFind;
]]>
</getter>
</property>
<field name="_lastSearchString">null</field>
<property name="editortype"
onget="return this.getAttribute('editortype');"
onset="this.setAttribute('editortype', val); return val;"/>
<property name="webNavigation"
onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
readonly="true"/>
<property name="contentDocument" readonly="true"
onget="return this.webNavigation.document;"/>
<property name="docShell" readonly="true">
<getter><![CDATA[
let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
return frameLoader ? frameLoader.docShell : null;
]]></getter>
</property>
<property name="currentURI"
readonly="true"
onget="return this.webNavigation.currentURI;"/>
<property name="contentWindow"
readonly="true"
onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
<property name="contentWindowAsCPOW"
readonly="true"
onget="return this.contentWindow;"/>
<property name="webBrowserFind"
readonly="true"
onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);"/>
<property name="markupDocumentViewer"
readonly="true"
onget="return this.docShell.contentViewer;"/>
<property name="editingSession"
readonly="true"
onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIEditingSession);"/>
<property name="commandManager"
readonly="true"
onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsICommandManager);"/>
<property name="fullZoom"
onget="return this.markupDocumentViewer.fullZoom;"
onset="this.markupDocumentViewer.fullZoom = val;"/>
<property name="textZoom"
onget="return this.markupDocumentViewer.textZoom;"
onset="this.markupDocumentViewer.textZoom = val;"/>
<property name="isSyntheticDocument"
onget="return this.contentDocument.isSyntheticDocument;"
readonly="true"/>
<property name="messageManager"
readonly="true">
<getter>
<![CDATA[
var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
if (!owner.frameLoader) {
return null;
}
return owner.frameLoader.messageManager;
]]>
</getter>
</property>
<property name="outerWindowID" readonly="true">
<getter><![CDATA[
return this.contentWindow
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.outerWindowID;
]]></getter>
</property>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,86 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="expanderBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="expander" display="xul:vbox">
<resources>
<stylesheet src="chrome://global/skin/expander.css"/>
</resources>
<content>
<xul:hbox align="center">
<xul:button type="disclosure" class="expanderButton" anonid="disclosure" xbl:inherits="disabled" mousethrough="always"/>
<xul:label class="header expanderButton" anonid="label" xbl:inherits="value=label,disabled" mousethrough="always" flex="1"/>
<xul:button anonid="clear-button" xbl:inherits="label=clearlabel,disabled=cleardisabled,hidden=clearhidden" mousethrough="always" icon="clear"/>
</xul:hbox>
<xul:vbox flex="1" anonid="settings" class="settingsContainer" collapsed="true" xbl:inherits="align">
<children/>
</xul:vbox>
</content>
<implementation>
<constructor><![CDATA[
var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
var open = this.getAttribute("open") == "true";
settings.collapsed = !open;
expander.open = open;
]]></constructor>
<property name="open">
<setter>
<![CDATA[
var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
settings.collapsed = !val;
expander.open = val;
if (val)
this.setAttribute("open", "true");
else
this.setAttribute("open", "false");
return val;
]]>
</setter>
<getter>
return this.getAttribute("open");
</getter>
</property>
<method name="onCommand">
<parameter name="aEvent"/>
<body><![CDATA[
var element = aEvent.originalTarget;
var button = element.getAttribute("anonid");
switch (button) {
case "disclosure":
case "label":
if (this.open == "true")
this.open = false;
else
this.open = true;
break;
case "clear-button":
var event = document.createEvent("Events");
event.initEvent("clear", true, true);
this.dispatchEvent(event);
break;
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="command"><![CDATA[
this.onCommand(event);
]]></handler>
<handler event="click"><![CDATA[
if (event.originalTarget.localName == "label")
this.onCommand(event);
]]></handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,96 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="filefieldBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="filefield" extends="chrome://global/content/bindings/general.xml#basetext">
<resources>
<stylesheet src="chrome://global/skin/filefield.css"/>
</resources>
<content>
<xul:stringbundle anonid="bundle" src="chrome://global/locale/filefield.properties"/>
<xul:hbox class="fileFieldContentBox" align="center" flex="1" xbl:inherits="disabled">
<xul:image class="fileFieldIcon" xbl:inherits="src=image,disabled"/>
<xul:textbox class="fileFieldLabel" xbl:inherits="value=label,disabled,accesskey,tabindex,aria-labelledby" flex="1" readonly="true"/>
</xul:hbox>
</content>
<implementation implements="nsIDOMXULLabeledControlElement">
<property name="label" onget="return this.getAttribute('label');">
<setter>
this.setAttribute('label', val);
var elt = document.getAnonymousElementByAttribute(this, "class", "fileFieldLabel");
return (elt.value = val);
</setter>
</property>
<field name="_file">null</field>
<property name="file" onget="return this._file">
<setter>
<![CDATA[
this._file = val;
if (val) {
this.image = this._getIconURLForFile(val);
this.label = this._getDisplayNameForFile(val);
}
else {
this.removeAttribute("image");
var bundle = document.getAnonymousElementByAttribute(this, "anonid", "bundle");
this.label = bundle.getString("downloadHelperNoneSelected");
}
return val;
]]>
</setter>
</property>
<method name="_getDisplayNameForFile">
<parameter name="aFile"/>
<body>
<![CDATA[
if (/Win/.test(navigator.platform)) {
var lfw = aFile.QueryInterface(Components.interfaces.nsILocalFileWin);
try {
return lfw.getVersionInfoField("FileDescription");
}
catch (e) {
// fall through to the filename
}
} else if (/Mac/.test(navigator.platform)) {
var lfm = aFile.QueryInterface(Components.interfaces.nsILocalFileMac);
try {
return lfm.bundleDisplayName;
}
catch (e) {
// fall through to the file name
}
}
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var url = ios.newFileURI(aFile).QueryInterface(Components.interfaces.nsIURL);
return url.fileName;
]]>
</body>
</method>
<method name="_getIconURLForFile">
<parameter name="aFile"/>
<body>
<![CDATA[
if (!aFile)
return "";
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var fph = ios.getProtocolHandler("file")
.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
var urlspec = fph.getURLSpecFromFile(aFile);
return "moz-icon://" + urlspec + "?size=16";
]]>
</body>
</method>
</implementation>
</binding>
</bindings>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,231 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="generalBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="basecontrol">
<implementation implements="nsIDOMXULControlElement">
<!-- public implementation -->
<property name="disabled" onset="if (val) this.setAttribute('disabled', 'true');
else this.removeAttribute('disabled');
return val;"
onget="return this.getAttribute('disabled') == 'true';"/>
<property name="tabIndex" onget="return parseInt(this.getAttribute('tabindex')) || 0"
onset="if (val) this.setAttribute('tabindex', val);
else this.removeAttribute('tabindex'); return val;"/>
</implementation>
</binding>
<binding id="basetext" extends="chrome://global/content/bindings/general.xml#basecontrol">
<implementation implements="nsIDOMXULLabeledControlElement">
<!-- public implementation -->
<property name="label" onset="this.setAttribute('label',val); return val;"
onget="return this.getAttribute('label');"/>
<property name="crop" onset="this.setAttribute('crop',val); return val;"
onget="return this.getAttribute('crop');"/>
<property name="image" onset="this.setAttribute('image',val); return val;"
onget="return this.getAttribute('image');"/>
<property name="command" onset="this.setAttribute('command',val); return val;"
onget="return this.getAttribute('command');"/>
<property name="accessKey">
<getter>
<![CDATA[
return this.labelElement ? this.labelElement.accessKey : this.getAttribute('accesskey');
]]>
</getter>
<setter>
<![CDATA[
// Always store on the control
this.setAttribute('accesskey', val);
// If there is a label, change the accesskey on the labelElement
// if it's also set there
if (this.labelElement) {
this.labelElement.accessKey = val;
}
return val;
]]>
</setter>
</property>
<field name="labelElement"/>
</implementation>
</binding>
<binding id="control-item" extends="chrome://global/content/bindings/general.xml#basetext">
<implementation>
<property name="value" onset="this.setAttribute('value', val); return val;"
onget="return this.getAttribute('value');"/>
</implementation>
</binding>
<binding id="root-element">
<implementation>
<field name="_lightweightTheme">null</field>
<constructor><![CDATA[
if (this.hasAttribute("lightweightthemes")) {
let temp = {};
Components.utils.import("resource://gre/modules/LightweightThemeConsumer.jsm", temp);
this._lightweightTheme = new temp.LightweightThemeConsumer(this.ownerDocument);
}
]]></constructor>
<destructor><![CDATA[
if (this._lightweightTheme) {
this._lightweightTheme.destroy();
this._lightweightTheme = null;
}
]]></destructor>
</implementation>
</binding>
<binding id="iframe" role="outerdoc">
<implementation>
<property name="docShell" readonly="true">
<getter><![CDATA[
let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
return frameLoader ? frameLoader.docShell : null;
]]></getter>
</property>
<property name="contentWindow"
readonly="true"
onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
<property name="webNavigation"
onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
readonly="true"/>
<property name="contentDocument" readonly="true"
onget="return this.webNavigation.document;"/>
</implementation>
</binding>
<binding id="statusbarpanel" display="xul:button">
<content>
<children>
<xul:label class="statusbarpanel-text" xbl:inherits="value=label,crop" crop="right" flex="1"/>
</children>
</content>
<implementation>
<property name="label"
onget="return this.getAttribute('label');"
onset="this.setAttribute('label',val); return val;"/>
<property name="image"
onget="return this.getAttribute('image');"
onset="this.setAttribute('image',val); return val;"/>
<property name="src"
onget="return this.getAttribute('src');"
onset="this.setAttribute('src',val); return val;"/>
</implementation>
</binding>
<binding id="statusbarpanel-menu-iconic" display="xul:menu"
extends="chrome://global/content/bindings/general.xml#statusbarpanel">
<content>
<xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
<children/>
</content>
</binding>
<binding id="statusbar" role="xul:statusbar">
<content>
<children/>
<xul:statusbarpanel class="statusbar-resizerpanel">
<xul:resizer dir="bottomend"/>
</xul:statusbarpanel>
</content>
</binding>
<binding id="statusbarpanel-iconic" display="xul:button" role="xul:button"
extends="chrome://global/content/bindings/general.xml#statusbarpanel">
<content>
<xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
</content>
</binding>
<binding id="statusbarpanel-iconic-text" display="xul:button" role="xul:button"
extends="chrome://global/content/bindings/general.xml#statusbarpanel">
<content>
<xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
<xul:label class="statusbarpanel-text" xbl:inherits="value=label,crop"/>
</content>
</binding>
<binding id="image" role="xul:image">
<implementation implements="nsIDOMXULImageElement">
<property name="src"
onget="return this.getAttribute('src');"
onset="this.setAttribute('src',val); return val;"/>
</implementation>
</binding>
<binding id="deck">
<implementation>
<property name="selectedIndex"
onget="return this.getAttribute('selectedIndex') || '0'">
<setter>
<![CDATA[
if (this.selectedIndex == val)
return val;
this.setAttribute("selectedIndex", val);
var event = document.createEvent('Events');
event.initEvent('select', true, true);
this.dispatchEvent(event);
return val;
]]>
</setter>
</property>
<property name="selectedPanel">
<getter>
<![CDATA[
return this.childNodes[this.selectedIndex];
]]>
</getter>
<setter>
<![CDATA[
var selectedIndex = -1;
for (var panel = val; panel != null; panel = panel.previousSibling)
++selectedIndex;
this.selectedIndex = selectedIndex;
return val;
]]>
</setter>
</property>
</implementation>
</binding>
<binding id="dropmarker" extends="xul:button" role="xul:dropmarker">
<resources>
<stylesheet src="chrome://global/skin/dropmarker.css"/>
</resources>
<content>
<xul:image class="dropmarker-icon"/>
</content>
</binding>
<binding id="windowdragbox">
<implementation>
<field name="_dragBindingAlive">true</field>
<constructor>
if (!this._draggableStarted) {
this._draggableStarted = true;
try {
let tmp = {};
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
let draghandle = new tmp.WindowDraggingElement(this);
draghandle.mouseDownCheck = function () {
return this._dragBindingAlive;
};
} catch (e) {}
}
</constructor>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,44 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="groupboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="groupbox-base">
<resources>
<stylesheet src="chrome://global/skin/groupbox.css"/>
</resources>
</binding>
<binding id="groupbox" role="xul:groupbox"
extends="chrome://global/content/bindings/groupbox.xml#groupbox-base">
<content>
<xul:hbox class="groupbox-title" align="center" pack="start">
<children includes="caption"/>
</xul:hbox>
<xul:box flex="1" class="groupbox-body" xbl:inherits="orient,align,pack">
<children/>
</xul:box>
</content>
</binding>
<binding id="caption" extends="chrome://global/content/bindings/general.xml#basetext">
<resources>
<stylesheet src="chrome://global/skin/groupbox.css"/>
</resources>
<content>
<children>
<xul:image class="caption-icon" xbl:inherits="src=image"/>
<xul:label class="caption-text" flex="1"
xbl:inherits="default,value=label,crop,accesskey"/>
</children>
</content>
</binding>
</bindings>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,286 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="menuitemBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="menuitem-base" role="xul:menuitem"
extends="chrome://global/content/bindings/general.xml#control-item">
<resources>
<stylesheet src="chrome://global/skin/menu.css"/>
</resources>
<implementation implements="nsIDOMXULSelectControlItemElement, nsIDOMXULContainerItemElement">
<!-- nsIDOMXULSelectControlItemElement -->
<property name="selected" readonly="true"
onget="return this.getAttribute('selected') == 'true';"/>
<property name="control" readonly="true">
<getter>
<![CDATA[
var parent = this.parentNode;
if (parent &&
parent.parentNode instanceof Components.interfaces.nsIDOMXULSelectControlElement)
return parent.parentNode;
return null;
]]>
</getter>
</property>
<!-- nsIDOMXULContainerItemElement -->
<property name="parentContainer" readonly="true">
<getter>
for (var parent = this.parentNode; parent; parent = parent.parentNode) {
if (parent instanceof Components.interfaces.nsIDOMXULContainerElement)
return parent;
}
return null;
</getter>
</property>
</implementation>
</binding>
<binding id="menu-base"
extends="chrome://global/content/bindings/menu.xml#menuitem-base">
<implementation implements="nsIDOMXULContainerElement">
<property name="open" onget="return this.hasAttribute('open');">
<setter><![CDATA[
this.boxObject.openMenu(val);
return val;
]]></setter>
</property>
<property name="openedWithKey" readonly="true">
<getter><![CDATA[
return this.boxObject.openedWithKey;
]]></getter>
</property>
<!-- nsIDOMXULContainerElement interface -->
<method name="appendItem">
<parameter name="aLabel"/>
<parameter name="aValue"/>
<body>
return this.insertItemAt(-1, aLabel, aValue);
</body>
</method>
<method name="insertItemAt">
<parameter name="aIndex"/>
<parameter name="aLabel"/>
<parameter name="aValue"/>
<body>
const XUL_NS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var menupopup = this.menupopup;
if (!menupopup) {
menupopup = this.ownerDocument.createElementNS(XUL_NS, "menupopup");
this.appendChild(menupopup);
}
var menuitem = this.ownerDocument.createElementNS(XUL_NS, "menuitem");
menuitem.setAttribute("label", aLabel);
menuitem.setAttribute("value", aValue);
var before = this.getItemAtIndex(aIndex);
if (before)
return menupopup.insertBefore(menuitem, before);
return menupopup.appendChild(menuitem);
</body>
</method>
<method name="removeItemAt">
<parameter name="aIndex"/>
<body>
<![CDATA[
var menupopup = this.menupopup;
if (menupopup) {
var item = this.getItemAtIndex(aIndex);
if (item)
return menupopup.removeChild(item);
}
return null;
]]>
</body>
</method>
<property name="itemCount" readonly="true">
<getter>
var menupopup = this.menupopup;
return menupopup ? menupopup.childNodes.length : 0;
</getter>
</property>
<method name="getIndexOfItem">
<parameter name="aItem"/>
<body>
<![CDATA[
var menupopup = this.menupopup;
if (menupopup) {
var items = menupopup.childNodes;
var length = items.length;
for (var index = 0; index < length; ++index) {
if (items[index] == aItem)
return index;
}
}
return -1;
]]>
</body>
</method>
<method name="getItemAtIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
var menupopup = this.menupopup;
if (!menupopup || aIndex < 0 || aIndex >= menupopup.childNodes.length)
return null;
return menupopup.childNodes[aIndex];
]]>
</body>
</method>
<property name="menupopup" readonly="true">
<getter>
<![CDATA[
const XUL_NS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
for (var child = this.firstChild; child; child = child.nextSibling) {
if (child.namespaceURI == XUL_NS && child.localName == "menupopup")
return child;
}
return null;
]]>
</getter>
</property>
</implementation>
</binding>
<binding id="menu"
extends="chrome://global/content/bindings/menu.xml#menu-base">
<content>
<xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
<xul:hbox class="menu-accel-container" anonid="accel">
<xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
</xul:hbox>
<xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
<xul:image/>
</xul:hbox>
<children includes="menupopup"/>
</content>
</binding>
<binding id="menuitem" extends="chrome://global/content/bindings/menu.xml#menuitem-base">
<content>
<xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
<xul:hbox class="menu-accel-container" anonid="accel">
<xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
</xul:hbox>
</content>
</binding>
<binding id="menucaption" extends="chrome://global/content/bindings/menu.xml#menu-base">
<content>
<xul:label class="menu-text" xbl:inherits="value=label,crop" crop="right"/>
</content>
</binding>
<binding id="menu-menubar"
extends="chrome://global/content/bindings/menu.xml#menu-base">
<content>
<xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
<children includes="menupopup"/>
</content>
</binding>
<binding id="menu-menubar-iconic"
extends="chrome://global/content/bindings/menu.xml#menu-base">
<content>
<xul:image class="menubar-left" xbl:inherits="src=image"/>
<xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
<children includes="menupopup"/>
</content>
</binding>
<binding id="menuitem-iconic" extends="chrome://global/content/bindings/menu.xml#menuitem">
<content>
<xul:hbox class="menu-iconic-left" align="center" pack="center"
xbl:inherits="selected,_moz-menuactive,disabled,checked">
<xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
</xul:hbox>
<xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
<children/>
<xul:hbox class="menu-accel-container" anonid="accel">
<xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
</xul:hbox>
</content>
</binding>
<binding id="menuitem-iconic-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
<content>
<xul:hbox class="menu-iconic-left" align="center" pack="center"
xbl:inherits="selected,disabled,checked">
<xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
</xul:hbox>
<xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
</content>
</binding>
<binding id="menucaption-inmenulist" extends="chrome://global/content/bindings/menu.xml#menucaption">
<content>
<xul:hbox class="menu-iconic-left" align="center" pack="center"
xbl:inherits="selected,disabled,checked">
<xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
</xul:hbox>
<xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,crop" crop="right"/>
</content>
</binding>
<binding id="menuitem-iconic-desc-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
<content>
<xul:hbox class="menu-iconic-left" align="center" pack="center"
xbl:inherits="selected,disabled,checked">
<xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
</xul:hbox>
<xul:label class="menu-iconic-text" xbl:inherits="value=label,accesskey,crop" crop="right" flex="1"/>
<xul:label class="menu-iconic-text menu-description" xbl:inherits="value=description" crop="right" flex="10000"/>
</content>
</binding>
<binding id="menu-iconic"
extends="chrome://global/content/bindings/menu.xml#menu-base">
<content>
<xul:hbox class="menu-iconic-left" align="center" pack="center">
<xul:image class="menu-iconic-icon" xbl:inherits="src=image"/>
</xul:hbox>
<xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
<xul:hbox class="menu-accel-container" anonid="accel">
<xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
</xul:hbox>
<xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
<xul:image/>
</xul:hbox>
<children includes="menupopup|template"/>
</content>
</binding>
<binding id="menubutton-item" extends="chrome://global/content/bindings/menu.xml#menuitem-base">
<content>
<xul:label class="menubutton-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
<children includes="menupopup"/>
</content>
</binding>
<binding id="menuseparator" role="xul:menuseparator"
extends="chrome://global/content/bindings/menu.xml#menuitem-base">
</binding>
</bindings>

View file

@ -0,0 +1,606 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="menulistBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="menulist-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/content/menulist.css"/>
<stylesheet src="chrome://global/skin/menulist.css"/>
</resources>
</binding>
<binding id="menulist" display="xul:menu" role="xul:menulist"
extends="chrome://global/content/bindings/menulist.xml#menulist-base">
<content sizetopopup="pref">
<xul:hbox class="menulist-label-box" flex="1">
<xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
<xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
</xul:hbox>
<xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
<children includes="menupopup"/>
</content>
<handlers>
<handler event="command" phase="capturing"
action="if (event.target.parentNode.parentNode == this) this.selectedItem = event.target;"/>
<handler event="popupshowing">
<![CDATA[
if (event.target.parentNode == this) {
this.menuBoxObject.activeChild = null;
if (this.selectedItem)
// Not ready for auto-setting the active child in hierarchies yet.
// For now, only do this when the outermost menupopup opens.
this.menuBoxObject.activeChild = this.mSelectedInternal;
}
]]>
</handler>
<handler event="keypress" modifiers="shift any" group="system">
<![CDATA[
if (!event.defaultPrevented &&
(event.keyCode == KeyEvent.DOM_VK_UP ||
event.keyCode == KeyEvent.DOM_VK_DOWN ||
event.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN ||
event.keyCode == KeyEvent.DOM_VK_HOME ||
event.keyCode == KeyEvent.DOM_VK_END ||
event.keyCode == KeyEvent.DOM_VK_BACK_SPACE ||
event.charCode > 0)) {
// Moving relative to an item: start from the currently selected item
this.menuBoxObject.activeChild = this.mSelectedInternal;
if (this.menuBoxObject.handleKeyPress(event)) {
this.menuBoxObject.activeChild.doCommand();
event.preventDefault();
}
}
]]>
</handler>
</handlers>
<implementation implements="nsIDOMXULMenuListElement">
<constructor>
this.mInputField = null;
this.mSelectedInternal = null;
this.mAttributeObserver = null;
this.menuBoxObject = this.boxObject;
this.setInitialSelection();
</constructor>
<method name="setInitialSelection">
<body>
<![CDATA[
var popup = this.menupopup;
if (popup) {
var arr = popup.getElementsByAttribute('selected', 'true');
var editable = this.editable;
var value = this.value;
if (!arr.item(0) && value)
arr = popup.getElementsByAttribute(editable ? 'label' : 'value', value);
if (arr.item(0))
this.selectedItem = arr[0];
else if (!editable)
this.selectedIndex = 0;
}
]]>
</body>
</method>
<property name="value" onget="return this.getAttribute('value');">
<setter>
<![CDATA[
// if the new value is null, we still need to remove the old value
if (val == null)
return this.selectedItem = val;
var arr = null;
var popup = this.menupopup;
if (popup)
arr = popup.getElementsByAttribute('value', val);
if (arr && arr.item(0))
this.selectedItem = arr[0];
else {
this.selectedItem = null;
this.setAttribute('value', val);
}
return val;
]]>
</setter>
</property>
<property name="inputField" readonly="true" onget="return null;"/>
<property name="crop" onset="this.setAttribute('crop',val); return val;"
onget="return this.getAttribute('crop');"/>
<property name="image" onset="this.setAttribute('image',val); return val;"
onget="return this.getAttribute('image');"/>
<property name="label" readonly="true" onget="return this.getAttribute('label');"/>
<property name="description" onset="this.setAttribute('description',val); return val;"
onget="return this.getAttribute('description');"/>
<property name="editable" onset="this.setAttribute('editable',val); return val;"
onget="return this.getAttribute('editable') == 'true';"/>
<property name="open" onset="this.menuBoxObject.openMenu(val);
return val;"
onget="return this.hasAttribute('open');"/>
<property name="itemCount" readonly="true"
onget="return this.menupopup ? this.menupopup.childNodes.length : 0"/>
<property name="menupopup" readonly="true">
<getter>
<![CDATA[
var popup = this.firstChild;
while (popup && popup.localName != "menupopup")
popup = popup.nextSibling;
return popup;
]]>
</getter>
</property>
<method name="contains">
<parameter name="item"/>
<body>
<![CDATA[
if (!item)
return false;
var parent = item.parentNode;
return (parent && parent.parentNode == this);
]]>
</body>
</method>
<property name="selectedIndex">
<getter>
<![CDATA[
// Quick and dirty. We won't deal with hierarchical menulists yet.
if (!this.selectedItem ||
!this.mSelectedInternal.parentNode ||
this.mSelectedInternal.parentNode.parentNode != this)
return -1;
var children = this.mSelectedInternal.parentNode.childNodes;
var i = children.length;
while (i--)
if (children[i] == this.mSelectedInternal)
break;
return i;
]]>
</getter>
<setter>
<![CDATA[
var popup = this.menupopup;
if (popup && 0 <= val) {
if (val < popup.childNodes.length)
this.selectedItem = popup.childNodes[val];
}
else
this.selectedItem = null;
return val;
]]>
</setter>
</property>
<property name="selectedItem">
<getter>
<![CDATA[
return this.mSelectedInternal;
]]>
</getter>
<setter>
<![CDATA[
var oldval = this.mSelectedInternal;
if (oldval == val)
return val;
if (val && !this.contains(val))
return val;
if (oldval) {
oldval.removeAttribute('selected');
this.mAttributeObserver.disconnect();
}
this.mSelectedInternal = val;
let attributeFilter = ["value", "label", "image", "description"];
if (val) {
val.setAttribute('selected', 'true');
for (let attr of attributeFilter) {
if (val.hasAttribute(attr)) {
this.setAttribute(attr, val.getAttribute(attr));
}
else {
this.removeAttribute(attr);
}
}
this.mAttributeObserver = new MutationObserver(this.handleMutation.bind(this));
this.mAttributeObserver.observe(val, { attributeFilter });
}
else {
for (let attr of attributeFilter) {
this.removeAttribute(attr);
}
}
var event = document.createEvent("Events");
event.initEvent("select", true, true);
this.dispatchEvent(event);
event = document.createEvent("Events");
event.initEvent("ValueChange", true, true);
this.dispatchEvent(event);
return val;
]]>
</setter>
</property>
<method name="handleMutation">
<parameter name="aRecords"/>
<body>
<![CDATA[
for (let record of aRecords) {
let t = record.target;
if (t == this.mSelectedInternal) {
let attrName = record.attributeName;
switch (attrName) {
case "value":
case "label":
case "image":
case "description":
if (t.hasAttribute(attrName)) {
this.setAttribute(attrName, t.getAttribute(attrName));
}
else {
this.removeAttribute(attrName);
}
}
}
}
]]>
</body>
</method>
<method name="getIndexOfItem">
<parameter name="item"/>
<body>
<![CDATA[
var popup = this.menupopup;
if (popup) {
var children = popup.childNodes;
var i = children.length;
while (i--)
if (children[i] == item)
return i;
}
return -1;
]]>
</body>
</method>
<method name="getItemAtIndex">
<parameter name="index"/>
<body>
<![CDATA[
var popup = this.menupopup;
if (popup) {
var children = popup.childNodes;
if (index >= 0 && index < children.length)
return children[index];
}
return null;
]]>
</body>
</method>
<method name="appendItem">
<parameter name="label"/>
<parameter name="value"/>
<parameter name="description"/>
<body>
<![CDATA[
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var popup = this.menupopup ||
this.appendChild(document.createElementNS(XULNS, "menupopup"));
var item = document.createElementNS(XULNS, "menuitem");
item.setAttribute("label", label);
item.setAttribute("value", value);
if (description)
item.setAttribute("description", description);
popup.appendChild(item);
return item;
]]>
</body>
</method>
<method name="insertItemAt">
<parameter name="index"/>
<parameter name="label"/>
<parameter name="value"/>
<parameter name="description"/>
<body>
<![CDATA[
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var popup = this.menupopup ||
this.appendChild(document.createElementNS(XULNS, "menupopup"));
var item = document.createElementNS(XULNS, "menuitem");
item.setAttribute("label", label);
item.setAttribute("value", value);
if (description)
item.setAttribute("description", description);
if (index >= 0 && index < popup.childNodes.length)
popup.insertBefore(item, popup.childNodes[index]);
else
popup.appendChild(item);
return item;
]]>
</body>
</method>
<method name="removeItemAt">
<parameter name="index"/>
<body>
<![CDATA[
var popup = this.menupopup;
if (popup && 0 <= index && index < popup.childNodes.length) {
var remove = popup.childNodes[index];
popup.removeChild(remove);
return remove;
}
return null;
]]>
</body>
</method>
<method name="removeAllItems">
<body>
<![CDATA[
this.selectedItem = null;
var popup = this.menupopup;
if (popup)
this.removeChild(popup);
]]>
</body>
</method>
<destructor>
<![CDATA[
if (this.mAttributeObserver) {
this.mAttributeObserver.disconnect();
}
]]>
</destructor>
</implementation>
</binding>
<binding id="menulist-editable" extends="chrome://global/content/bindings/menulist.xml#menulist">
<content sizetopopup="pref">
<xul:hbox class="menulist-editable-box textbox-input-box" xbl:inherits="context,disabled,readonly,focused" flex="1">
<html:input class="menulist-editable-input" anonid="input" allowevents="true"
xbl:inherits="value=label,value,disabled,tabindex,readonly,placeholder"/>
</xul:hbox>
<xul:dropmarker class="menulist-dropmarker" type="menu"
xbl:inherits="open,disabled,parentfocused=focused"/>
<children includes="menupopup"/>
</content>
<implementation>
<method name="_selectInputFieldValueInList">
<body>
<![CDATA[
if (this.hasAttribute("disableautoselect"))
return;
// Find and select the menuitem that matches inputField's "value"
var arr = null;
var popup = this.menupopup;
if (popup)
arr = popup.getElementsByAttribute('label', this.inputField.value);
this.setSelectionInternal(arr ? arr.item(0) : null);
]]>
</body>
</method>
<method name="setSelectionInternal">
<parameter name="val"/>
<body>
<![CDATA[
// This is called internally to set selected item
// without triggering infinite loop
// when using selectedItem's setter
if (this.mSelectedInternal == val)
return val;
if (this.mSelectedInternal)
this.mSelectedInternal.removeAttribute('selected');
this.mSelectedInternal = val;
if (val)
val.setAttribute('selected', 'true');
// Do NOT change the "value", which is owned by inputField
return val;
]]>
</body>
</method>
<property name="inputField" readonly="true">
<getter><![CDATA[
if (!this.mInputField)
this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
return this.mInputField;
]]></getter>
</property>
<property name="label" onset="this.inputField.value = val; return val;"
onget="return this.inputField.value;"/>
<property name="value" onget="return this.inputField.value;">
<setter>
<![CDATA[
// Override menulist's value setter to refer to the inputField's value
// (Allows using "menulist.value" instead of "menulist.inputField.value")
this.inputField.value = val;
this.setAttribute('value', val);
this.setAttribute('label', val);
this._selectInputFieldValueInList();
return val;
]]>
</setter>
</property>
<property name="selectedItem">
<getter>
<![CDATA[
// Make sure internally-selected item
// is in sync with inputField.value
this._selectInputFieldValueInList();
return this.mSelectedInternal;
]]>
</getter>
<setter>
<![CDATA[
var oldval = this.mSelectedInternal;
if (oldval == val)
return val;
if (val && !this.contains(val))
return val;
// This doesn't touch inputField.value or "value" and "label" attributes
this.setSelectionInternal(val);
if (val) {
// Editable menulist uses "label" as its "value"
var label = val.getAttribute('label');
this.inputField.value = label;
this.setAttribute('value', label);
this.setAttribute('label', label);
}
else {
this.inputField.value = "";
this.removeAttribute('value');
this.removeAttribute('label');
}
var event = document.createEvent("Events");
event.initEvent("select", true, true);
this.dispatchEvent(event);
event = document.createEvent("Events");
event.initEvent("ValueChange", true, true);
this.dispatchEvent(event);
return val;
]]>
</setter>
</property>
<property name="disableautoselect"
onset="if (val) this.setAttribute('disableautoselect','true');
else this.removeAttribute('disableautoselect'); return val;"
onget="return this.hasAttribute('disableautoselect');"/>
<property name="editor" readonly="true">
<getter><![CDATA[
const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
return this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
]]></getter>
</property>
<property name="readOnly" onset="this.inputField.readOnly = val;
if (val) this.setAttribute('readonly', 'true');
else this.removeAttribute('readonly'); return val;"
onget="return this.inputField.readOnly;"/>
<method name="select">
<body>
this.inputField.select();
</body>
</method>
</implementation>
<handlers>
<handler event="focus" phase="capturing">
<![CDATA[
this.setAttribute('focused', 'true');
]]>
</handler>
<handler event="blur" phase="capturing">
<![CDATA[
this.removeAttribute('focused');
]]>
</handler>
<handler event="popupshowing">
<![CDATA[
// editable menulists elements aren't in the focus order,
// so when the popup opens we need to force the focus to the inputField
if (event.target.parentNode == this) {
if (document.commandDispatcher.focusedElement != this.inputField)
this.inputField.focus();
this.menuBoxObject.activeChild = null;
if (this.selectedItem)
// Not ready for auto-setting the active child in hierarchies yet.
// For now, only do this when the outermost menupopup opens.
this.menuBoxObject.activeChild = this.mSelectedInternal;
}
]]>
</handler>
<handler event="keypress">
<![CDATA[
// open popup if key is up arrow, down arrow, or F4
if (!event.ctrlKey && !event.shiftKey) {
if (event.keyCode == KeyEvent.DOM_VK_UP ||
event.keyCode == KeyEvent.DOM_VK_DOWN ||
(event.keyCode == KeyEvent.DOM_VK_F4 && !event.altKey)) {
event.preventDefault();
this.open = true;
}
}
]]>
</handler>
</handlers>
</binding>
<binding id="menulist-description" display="xul:menu"
extends="chrome://global/content/bindings/menulist.xml#menulist">
<content sizetopopup="pref">
<xul:hbox class="menulist-label-box" flex="1">
<xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
<xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
<xul:label class="menulist-label menulist-description" xbl:inherits="value=description" crop="right" flex="10000"/>
</xul:hbox>
<xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
<children includes="menupopup"/>
</content>
</binding>
<binding id="menulist-popuponly" display="xul:menu"
extends="chrome://global/content/bindings/menulist.xml#menulist">
<content sizetopopup="pref">
<children includes="menupopup"/>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,551 @@
<?xml version="1.0"?>
<!-- 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/. -->
<!DOCTYPE bindings [
<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
%notificationDTD;
]>
<bindings id="notificationBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="notificationbox">
<content>
<xul:stack xbl:inherits="hidden=notificationshidden"
class="notificationbox-stack">
<xul:spacer/>
<children includes="notification"/>
</xul:stack>
<children/>
</content>
<implementation>
<field name="PRIORITY_INFO_LOW" readonly="true">1</field>
<field name="PRIORITY_INFO_MEDIUM" readonly="true">2</field>
<field name="PRIORITY_INFO_HIGH" readonly="true">3</field>
<field name="PRIORITY_WARNING_LOW" readonly="true">4</field>
<field name="PRIORITY_WARNING_MEDIUM" readonly="true">5</field>
<field name="PRIORITY_WARNING_HIGH" readonly="true">6</field>
<field name="PRIORITY_CRITICAL_LOW" readonly="true">7</field>
<field name="PRIORITY_CRITICAL_MEDIUM" readonly="true">8</field>
<field name="PRIORITY_CRITICAL_HIGH" readonly="true">9</field>
<field name="PRIORITY_CRITICAL_BLOCK" readonly="true">10</field>
<field name="currentNotification">null</field>
<field name="_closedNotification">null</field>
<field name="_blockingCanvas">null</field>
<field name="_animating">false</field>
<property name="notificationsHidden"
onget="return this.getAttribute('notificationshidden') == 'true';">
<setter>
if (val)
this.setAttribute('notificationshidden', true);
else this.removeAttribute('notificationshidden');
return val;
</setter>
</property>
<property name="allNotifications" readonly="true">
<getter>
<![CDATA[
var closedNotification = this._closedNotification;
var notifications = this.getElementsByTagName('notification');
return Array.filter(notifications, n => n != closedNotification);
]]>
</getter>
</property>
<method name="getNotificationWithValue">
<parameter name="aValue"/>
<body>
<![CDATA[
var notifications = this.allNotifications;
for (var n = notifications.length - 1; n >= 0; n--) {
if (aValue == notifications[n].getAttribute("value"))
return notifications[n];
}
return null;
]]>
</body>
</method>
<method name="appendNotification">
<parameter name="aLabel"/>
<parameter name="aValue"/>
<parameter name="aImage"/>
<parameter name="aPriority"/>
<parameter name="aButtons"/>
<parameter name="aEventCallback"/>
<body>
<![CDATA[
if (aPriority < this.PRIORITY_INFO_LOW ||
aPriority > this.PRIORITY_CRITICAL_BLOCK)
throw "Invalid notification priority " + aPriority;
// check for where the notification should be inserted according to
// priority. If two are equal, the existing one appears on top.
var notifications = this.allNotifications;
var insertPos = null;
for (var n = notifications.length - 1; n >= 0; n--) {
if (notifications[n].priority < aPriority)
break;
insertPos = notifications[n];
}
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var newitem = document.createElementNS(XULNS, "notification");
// Can't use instanceof in case this was created from a different document:
let labelIsDocFragment = aLabel && typeof aLabel == "object" && aLabel.nodeType &&
aLabel.nodeType == aLabel.DOCUMENT_FRAGMENT_NODE;
if (!labelIsDocFragment)
newitem.setAttribute("label", aLabel);
newitem.setAttribute("value", aValue);
if (aImage)
newitem.setAttribute("image", aImage);
newitem.eventCallback = aEventCallback;
if (aButtons) {
// The notification-button-default class is added to the button
// with isDefault set to true. If there is no such button, it is
// added to the first button (unless that button has isDefault
// set to false). There cannot be multiple default buttons.
var defaultElem;
for (var b = 0; b < aButtons.length; b++) {
var button = aButtons[b];
var buttonElem = document.createElementNS(XULNS, "button");
buttonElem.setAttribute("label", button.label);
if (typeof button.accessKey == "string")
buttonElem.setAttribute("accesskey", button.accessKey);
if (typeof button.type == "string") {
buttonElem.setAttribute("type", button.type);
if ((button.type == "menu-button" || button.type == "menu") &&
"popup" in button) {
buttonElem.appendChild(button.popup);
delete button.popup;
}
if (typeof button.anchor == "string")
buttonElem.setAttribute("anchor", button.anchor);
}
buttonElem.classList.add("notification-button");
if (button.isDefault ||
b == 0 && !("isDefault" in button))
defaultElem = buttonElem;
newitem.appendChild(buttonElem);
buttonElem.buttonInfo = button;
}
if (defaultElem)
defaultElem.classList.add("notification-button-default");
}
newitem.setAttribute("priority", aPriority);
if (aPriority >= this.PRIORITY_CRITICAL_LOW)
newitem.setAttribute("type", "critical");
else if (aPriority <= this.PRIORITY_INFO_HIGH)
newitem.setAttribute("type", "info");
else
newitem.setAttribute("type", "warning");
if (!insertPos) {
newitem.style.position = "fixed";
newitem.style.top = "100%";
newitem.style.marginTop = "-15px";
newitem.style.opacity = "0";
}
this.insertBefore(newitem, insertPos);
// Can only insert the document fragment after the item has been created because
// otherwise the XBL structure isn't there yet:
if (labelIsDocFragment) {
document.getAnonymousElementByAttribute(newitem, "anonid", "messageText")
.appendChild(aLabel);
}
if (!insertPos)
this._showNotification(newitem, true);
// Fire event for accessibility APIs
var event = document.createEvent("Events");
event.initEvent("AlertActive", true, true);
newitem.dispatchEvent(event);
return newitem;
]]>
</body>
</method>
<method name="removeNotification">
<parameter name="aItem"/>
<parameter name="aSkipAnimation"/>
<body>
<![CDATA[
if (aItem == this.currentNotification)
this.removeCurrentNotification(aSkipAnimation);
else if (aItem != this._closedNotification)
this._removeNotificationElement(aItem);
return aItem;
]]>
</body>
</method>
<method name="_removeNotificationElement">
<parameter name="aChild"/>
<body>
<![CDATA[
if (aChild.eventCallback)
aChild.eventCallback("removed");
this.removeChild(aChild);
// make sure focus doesn't get lost (workaround for bug 570835)
let fm = Components.classes["@mozilla.org/focus-manager;1"]
.getService(Components.interfaces.nsIFocusManager);
if (!fm.getFocusedElementForWindow(window, false, {}))
fm.moveFocus(window, this, fm.MOVEFOCUS_FORWARD, 0);
]]>
</body>
</method>
<method name="removeCurrentNotification">
<parameter name="aSkipAnimation"/>
<body>
<![CDATA[
this._showNotification(this.currentNotification, false, aSkipAnimation);
]]>
</body>
</method>
<method name="removeAllNotifications">
<parameter name="aImmediate"/>
<body>
<![CDATA[
var notifications = this.allNotifications;
for (var n = notifications.length - 1; n >= 0; n--) {
if (aImmediate)
this._removeNotificationElement(notifications[n]);
else
this.removeNotification(notifications[n]);
}
this.currentNotification = null;
// Must clear up any currently animating notification
if (aImmediate)
this._finishAnimation();
]]>
</body>
</method>
<method name="removeTransientNotifications">
<body>
<![CDATA[
var notifications = this.allNotifications;
for (var n = notifications.length - 1; n >= 0; n--) {
var notification = notifications[n];
if (notification.persistence)
notification.persistence--;
else if (Date.now() > notification.timeout)
this.removeNotification(notification);
}
]]>
</body>
</method>
<method name="_showNotification">
<parameter name="aNotification"/>
<parameter name="aSlideIn"/>
<parameter name="aSkipAnimation"/>
<body>
<![CDATA[
this._finishAnimation();
var height = aNotification.boxObject.height;
var skipAnimation = aSkipAnimation || (height == 0);
if (aSlideIn) {
this.currentNotification = aNotification;
aNotification.style.removeProperty("position");
aNotification.style.removeProperty("top");
aNotification.style.removeProperty("margin-top");
aNotification.style.removeProperty("opacity");
if (skipAnimation) {
this._setBlockingState(this.currentNotification);
return;
}
}
else {
this._closedNotification = aNotification;
var notifications = this.allNotifications;
var idx = notifications.length - 1;
this.currentNotification = (idx >= 0) ? notifications[idx] : null;
if (skipAnimation) {
this._removeNotificationElement(this._closedNotification);
this._closedNotification = null;
this._setBlockingState(this.currentNotification);
return;
}
aNotification.style.marginTop = -height + "px";
aNotification.style.opacity = 0;
}
this._animating = true;
]]>
</body>
</method>
<method name="_finishAnimation">
<body><![CDATA[
if (this._animating) {
this._animating = false;
if (this._closedNotification) {
this._removeNotificationElement(this._closedNotification);
this._closedNotification = null;
}
this._setBlockingState(this.currentNotification);
}
]]></body>
</method>
<method name="_setBlockingState">
<parameter name="aNotification"/>
<body>
<![CDATA[
var isblock = aNotification &&
aNotification.priority == this.PRIORITY_CRITICAL_BLOCK;
var canvas = this._blockingCanvas;
if (isblock) {
if (!canvas)
canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let content = this.firstChild;
if (!content ||
content.namespaceURI != XULNS ||
content.localName != "browser")
return;
var width = content.boxObject.width;
var height = content.boxObject.height;
content.collapsed = true;
canvas.setAttribute("width", width);
canvas.setAttribute("height", height);
canvas.setAttribute("flex", "1");
this.appendChild(canvas);
this._blockingCanvas = canvas;
var bgcolor = "white";
try {
var prefService = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
bgcolor = prefService.getCharPref("browser.display.background_color");
var win = content.contentWindow;
var context = canvas.getContext("2d");
context.globalAlpha = 0.5;
context.drawWindow(win, win.scrollX, win.scrollY,
width, height, bgcolor);
}
catch (ex) { }
}
else if (canvas) {
canvas.parentNode.removeChild(canvas);
this._blockingCanvas = null;
let content = this.firstChild;
if (content)
content.collapsed = false;
}
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="transitionend"><![CDATA[
if (event.target.localName == "notification" &&
event.propertyName == "margin-top")
this._finishAnimation();
]]></handler>
</handlers>
</binding>
<binding id="notification" role="xul:alert">
<content>
<xul:hbox class="notification-inner" flex="1" xbl:inherits="type">
<xul:hbox anonid="details" align="center" flex="1"
oncommand="this.parentNode.parentNode._doButtonCommand(event);">
<xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
<xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
<xul:spacer flex="1"/>
<children/>
</xul:hbox>
<xul:toolbarbutton ondblclick="event.stopPropagation();"
class="messageCloseButton close-icon tabbable"
xbl:inherits="hidden=hideclose"
tooltiptext="&closeNotification.tooltip;"
oncommand="document.getBindingParent(this).dismiss();"/>
</xul:hbox>
</content>
<resources>
<stylesheet src="chrome://global/skin/notification.css"/>
</resources>
<implementation>
<property name="label" onset="this.setAttribute('label', val); return val;"
onget="return this.getAttribute('label');"/>
<property name="value" onset="this.setAttribute('value', val); return val;"
onget="return this.getAttribute('value');"/>
<property name="image" onset="this.setAttribute('image', val); return val;"
onget="return this.getAttribute('image');"/>
<property name="type" onset="this.setAttribute('type', val); return val;"
onget="return this.getAttribute('type');"/>
<property name="priority" onget="return parseInt(this.getAttribute('priority')) || 0;"
onset="this.setAttribute('priority', val); return val;"/>
<property name="persistence" onget="return parseInt(this.getAttribute('persistence')) || 0;"
onset="this.setAttribute('persistence', val); return val;"/>
<field name="timeout">0</field>
<property name="control" readonly="true">
<getter>
<![CDATA[
var parent = this.parentNode;
while (parent) {
if (parent.localName == "notificationbox")
return parent;
parent = parent.parentNode;
}
return null;
]]>
</getter>
</property>
<!-- This method should only be called when the user has
manually closed the notification. If you want to
programmatically close the notification, you should
call close() instead. -->
<method name="dismiss">
<body>
<![CDATA[
if (this.eventCallback) {
this.eventCallback("dismissed");
}
this.close();
]]>
</body>
</method>
<method name="close">
<body>
<![CDATA[
var control = this.control;
if (control)
control.removeNotification(this);
else
this.hidden = true;
]]>
</body>
</method>
<method name="_doButtonCommand">
<parameter name="aEvent"/>
<body>
<![CDATA[
if (!("buttonInfo" in aEvent.target))
return;
var button = aEvent.target.buttonInfo;
if (button.popup) {
document.getElementById(button.popup).
openPopup(aEvent.originalTarget, "after_start", 0, 0, false, false, aEvent);
aEvent.stopPropagation();
}
else {
var callback = button.callback;
if (callback) {
var result = callback(this, button, aEvent.target);
if (!result)
this.close();
aEvent.stopPropagation();
}
}
]]>
</body>
</method>
</implementation>
</binding>
<binding id="popup-notification">
<content>
<xul:vbox>
<xul:image class="popup-notification-icon"
xbl:inherits="popupid,src=icon,class=iconclass"/>
</xul:vbox>
<xul:vbox class="popup-notification-body" xbl:inherits="popupid">
<xul:hbox align="start">
<xul:vbox flex="1">
<xul:label class="popup-notification-origin header"
xbl:inherits="value=origin,tooltiptext=origin"
crop="center"/>
<xul:description class="popup-notification-description"
xbl:inherits="xbl:text=label,popupid"/>
</xul:vbox>
<xul:toolbarbutton anonid="closebutton"
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
xbl:inherits="oncommand=closebuttoncommand"
tooltiptext="&closeNotification.tooltip;"/>
</xul:hbox>
<children includes="popupnotificationcontent"/>
<xul:label class="text-link popup-notification-learnmore-link"
xbl:inherits="onclick=learnmoreclick,href=learnmoreurl">&learnMore;</xul:label>
<xul:checkbox anonid="checkbox"
xbl:inherits="hidden=checkboxhidden,checked=checkboxchecked,label=checkboxlabel,oncommand=checkboxcommand" />
<xul:description class="popup-notification-warning" xbl:inherits="hidden=warninghidden,xbl:text=warninglabel"/>
<xul:spacer flex="1"/>
<xul:hbox class="popup-notification-button-container"
pack="end" align="center">
<children includes="button"/>
<xul:button anonid="button"
class="popup-notification-menubutton"
type="menu-button"
xbl:inherits="oncommand=buttoncommand,onpopupshown=buttonpopupshown,label=buttonlabel,accesskey=buttonaccesskey,disabled=mainactiondisabled">
<xul:menupopup anonid="menupopup"
xbl:inherits="oncommand=menucommand">
<children/>
<xul:menuitem class="menuitem-iconic popup-notification-closeitem"
label="&closeNotificationItem.label;"
xbl:inherits="oncommand=closeitemcommand,hidden=hidenotnow"/>
</xul:menupopup>
</xul:button>
</xul:hbox>
</xul:vbox>
</content>
<resources>
<stylesheet src="chrome://global/skin/notification.css"/>
</resources>
<implementation>
<field name="checkbox" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "checkbox");
</field>
<field name="closebutton" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "closebutton");
</field>
<field name="button" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "button");
</field>
<field name="menupopup" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "menupopup");
</field>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,304 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="numberboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="numberbox"
extends="chrome://global/content/bindings/textbox.xml#textbox">
<resources>
<stylesheet src="chrome://global/skin/numberbox.css"/>
</resources>
<content>
<xul:hbox class="textbox-input-box numberbox-input-box" flex="1" xbl:inherits="context,disabled,focused">
<html:input class="numberbox-input textbox-input" anonid="input"
xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
</xul:hbox>
<xul:spinbuttons anonid="buttons" xbl:inherits="disabled,hidden=hidespinbuttons"/>
</content>
<implementation>
<field name="_valueEntered">false</field>
<field name="_spinButtons">null</field>
<field name="_value">0</field>
<field name="decimalSymbol">"."</field>
<property name="spinButtons" readonly="true">
<getter>
<![CDATA[
if (!this._spinButtons)
this._spinButtons = document.getAnonymousElementByAttribute(this, "anonid", "buttons");
return this._spinButtons;
]]>
</getter>
</property>
<property name="value" onget="return '' + this.valueNumber"
onset="return this.valueNumber = val;"/>
<property name="valueNumber">
<getter>
if (this._valueEntered) {
var newval = this.inputField.value;
newval = newval.replace(this.decimalSymbol, ".");
this._validateValue(newval, false);
}
return this._value;
</getter>
<setter>
this._validateValue(val, false);
return val;
</setter>
</property>
<property name="wrapAround">
<getter>
<![CDATA[
return (this.getAttribute('wraparound') == 'true')
]]>
</getter>
<setter>
<![CDATA[
if (val)
this.setAttribute('wraparound', 'true');
else
this.removeAttribute('wraparound');
this._enableDisableButtons();
return val;
]]>
</setter>
</property>
<property name="min">
<getter>
var min = this.getAttribute("min");
return min ? Number(min) : 0;
</getter>
<setter>
<![CDATA[
if (typeof val == "number") {
this.setAttribute("min", val);
if (this.valueNumber < val)
this._validateValue(val, false);
}
return val;
]]>
</setter>
</property>
<property name="max">
<getter>
var max = this.getAttribute("max");
return max ? Number(max) : Infinity;
</getter>
<setter>
<![CDATA[
if (typeof val != "number")
return val;
var min = this.min;
if (val < min)
val = min;
this.setAttribute("max", val);
if (this.valueNumber > val)
this._validateValue(val, false);
return val;
]]>
</setter>
</property>
<property name="decimalPlaces">
<getter>
var places = this.getAttribute("decimalplaces");
return places ? Number(places) : 0;
</getter>
<setter>
if (typeof val == "number") {
this.setAttribute("decimalplaces", val);
this._validateValue(this.valueNumber, false);
}
return val;
</setter>
</property>
<property name="increment">
<getter>
var increment = this.getAttribute("increment");
return increment ? Number(increment) : 1;
</getter>
<setter>
<![CDATA[
if (typeof val == "number")
this.setAttribute("increment", val);
return val;
]]>
</setter>
</property>
<method name="decrease">
<body>
return this._validateValue(this.valueNumber - this.increment, true);
</body>
</method>
<method name="increase">
<body>
return this._validateValue(this.valueNumber + this.increment, true);
</body>
</method>
<method name="_modifyUp">
<body>
<![CDATA[
if (this.disabled || this.readOnly)
return;
var oldval = this.valueNumber;
var newval = this.increase();
this.inputField.select();
if (oldval != newval)
this._fireChange();
]]>
</body>
</method>
<method name="_modifyDown">
<body>
<![CDATA[
if (this.disabled || this.readOnly)
return;
var oldval = this.valueNumber;
var newval = this.decrease();
this.inputField.select();
if (oldval != newval)
this._fireChange();
]]>
</body>
</method>
<method name="_enableDisableButtons">
<body>
<![CDATA[
var buttons = this.spinButtons;
if (this.wrapAround) {
buttons.decreaseDisabled = buttons.increaseDisabled = false;
}
else if (this.disabled || this.readOnly) {
buttons.decreaseDisabled = buttons.increaseDisabled = true;
}
else {
buttons.decreaseDisabled = (this.valueNumber <= this.min);
buttons.increaseDisabled = (this.valueNumber >= this.max);
}
]]>
</body>
</method>
<method name="_validateValue">
<parameter name="aValue"/>
<parameter name="aIsIncDec"/>
<body>
<![CDATA[
aValue = Number(aValue) || 0;
var min = this.min;
var max = this.max;
var wrapAround = this.wrapAround &&
min != -Infinity && max != Infinity;
if (aValue < min)
aValue = (aIsIncDec && wrapAround ? max : min);
else if (aValue > max)
aValue = (aIsIncDec && wrapAround ? min : max);
var places = this.decimalPlaces;
aValue = (places == Infinity) ? "" + aValue : aValue.toFixed(places);
this._valueEntered = false;
this._value = Number(aValue);
this.inputField.value = aValue.replace(/\./, this.decimalSymbol);
if (!wrapAround)
this._enableDisableButtons();
return aValue;
]]>
</body>
</method>
<method name="_fireChange">
<body>
var evt = document.createEvent("Events");
evt.initEvent("change", true, true);
this.dispatchEvent(evt);
</body>
</method>
<constructor><![CDATA[
if (this.max < this.min)
this.max = this.min;
var dsymbol = (Number(5.4)).toLocaleString().match(/\D/);
if (dsymbol != null)
this.decimalSymbol = dsymbol[0];
var value = this.inputField.value || 0;
this._validateValue(value, false);
]]></constructor>
</implementation>
<handlers>
<handler event="input" phase="capturing">
this._valueEntered = true;
</handler>
<handler event="keypress">
<![CDATA[
if (!event.ctrlKey && !event.metaKey && !event.altKey && event.charCode) {
if (event.charCode == this.decimalSymbol.charCodeAt(0) &&
this.decimalPlaces &&
String(this.inputField.value).indexOf(this.decimalSymbol) == -1)
return;
if (event.charCode == 45 && this.min < 0)
return;
if (event.charCode < 48 || event.charCode > 57)
event.preventDefault();
}
]]>
</handler>
<handler event="keypress" keycode="VK_UP">
this._modifyUp();
</handler>
<handler event="keypress" keycode="VK_DOWN">
this._modifyDown();
</handler>
<handler event="up" preventdefault="true">
this._modifyUp();
</handler>
<handler event="down" preventdefault="true">
this._modifyDown();
</handler>
<handler event="change">
if (event.originalTarget == this.inputField) {
var newval = this.inputField.value;
newval = newval.replace(this.decimalSymbol, ".");
this._validateValue(newval, false);
}
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,43 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="optionsDialogBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="optionsDialog"
extends="chrome://global/content/bindings/dialog.xml#dialog">
<content>
#ifndef XP_MACOSX
<xul:hbox flex="1">
<xul:categoryBox anonid="prefsCategories">
<children/>
</xul:categoryBox>
<xul:vbox flex="1">
<xul:dialogheader id="panelHeader"/>
<xul:iframe anonid="panelFrame" name="panelFrame" style="width: 0px;" flex="1"/>
</xul:vbox>
</xul:hbox>
#else
<xul:vbox flex="1">
<xul:categoryBox anonid="prefsCategories">
<children/>
</xul:categoryBox>
<xul:vbox flex="1">
<xul:iframe anonid="panelFrame" name="panelFrame" style="width: 0px;" flex="1"/>
</xul:vbox>
</xul:vbox>
#endif
</content>
<implementation>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,650 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="popupBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="popup-base">
<resources>
<stylesheet src="chrome://global/skin/popup.css"/>
</resources>
<implementation implements="nsIDOMXULPopupElement">
<property name="label" onget="return this.getAttribute('label');"
onset="this.setAttribute('label', val); return val;"/>
<property name="position" onget="return this.getAttribute('position');"
onset="this.setAttribute('position', val); return val;"/>
<property name="popupBoxObject">
<getter>
return this.boxObject;
</getter>
</property>
<property name="state" readonly="true"
onget="return this.popupBoxObject.popupState"/>
<property name="triggerNode" readonly="true"
onget="return this.popupBoxObject.triggerNode"/>
<property name="anchorNode" readonly="true"
onget="return this.popupBoxObject.anchorNode"/>
<method name="openPopup">
<parameter name="aAnchorElement"/>
<parameter name="aPosition"/>
<parameter name="aX"/>
<parameter name="aY"/>
<parameter name="aIsContextMenu"/>
<parameter name="aAttributesOverride"/>
<parameter name="aTriggerEvent"/>
<body>
<![CDATA[
try {
var popupBox = this.popupBoxObject;
if (popupBox)
popupBox.openPopup(aAnchorElement, aPosition, aX, aY,
aIsContextMenu, aAttributesOverride, aTriggerEvent);
} catch (e) {}
]]>
</body>
</method>
<method name="openPopupAtScreen">
<parameter name="aX"/>
<parameter name="aY"/>
<parameter name="aIsContextMenu"/>
<parameter name="aTriggerEvent"/>
<body>
<![CDATA[
try {
var popupBox = this.popupBoxObject;
if (popupBox)
popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent);
} catch (e) {}
]]>
</body>
</method>
<method name="openPopupAtScreenRect">
<parameter name="aPosition"/>
<parameter name="aX"/>
<parameter name="aY"/>
<parameter name="aWidth"/>
<parameter name="aHeight"/>
<parameter name="aIsContextMenu"/>
<parameter name="aAttributesOverride"/>
<parameter name="aTriggerEvent"/>
<body>
<![CDATA[
try {
var popupBox = this.popupBoxObject;
if (popupBox)
popupBox.openPopupAtScreenRect(aPosition, aX, aY, aWidth, aHeight,
aIsContextMenu, aAttributesOverride, aTriggerEvent);
} catch (e) {}
]]>
</body>
</method>
<method name="showPopup">
<parameter name="element"/>
<parameter name="xpos"/>
<parameter name="ypos"/>
<parameter name="popuptype"/>
<parameter name="anchoralignment"/>
<parameter name="popupalignment"/>
<body>
<![CDATA[
var popupBox = null;
var menuBox = null;
try {
popupBox = this.popupBoxObject;
} catch (e) {}
try {
menuBox = this.parentNode.boxObject;
} catch (e) {}
if (menuBox instanceof MenuBoxObject)
menuBox.openMenu(true);
else if (popupBox)
popupBox.showPopup(element, this, xpos, ypos, popuptype, anchoralignment, popupalignment);
]]>
</body>
</method>
<method name="hidePopup">
<parameter name="cancel"/>
<body>
<![CDATA[
var popupBox = null;
var menuBox = null;
try {
popupBox = this.popupBoxObject;
} catch (e) {}
try {
menuBox = this.parentNode.boxObject;
} catch (e) {}
if (menuBox instanceof MenuBoxObject)
menuBox.openMenu(false);
else if (popupBox instanceof PopupBoxObject)
popupBox.hidePopup(cancel);
]]>
</body>
</method>
<property name="autoPosition">
<getter>
<![CDATA[
return this.popupBoxObject.autoPosition;
]]>
</getter>
<setter>
<![CDATA[
return this.popupBoxObject.autoPosition = val;
]]>
</setter>
</property>
<property name="alignmentPosition" readonly="true">
<getter>
<![CDATA[
return this.popupBoxObject.alignmentPosition;
]]>
</getter>
</property>
<property name="alignmentOffset" readonly="true">
<getter>
<![CDATA[
return this.popupBoxObject.alignmentOffset;
]]>
</getter>
</property>
<method name="enableKeyboardNavigator">
<parameter name="aEnableKeyboardNavigator"/>
<body>
<![CDATA[
this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator);
]]>
</body>
</method>
<method name="enableRollup">
<parameter name="aEnableRollup"/>
<body>
<![CDATA[
this.popupBoxObject.enableRollup(aEnableRollup);
]]>
</body>
</method>
<method name="sizeTo">
<parameter name="aWidth"/>
<parameter name="aHeight"/>
<body>
<![CDATA[
this.popupBoxObject.sizeTo(aWidth, aHeight);
]]>
</body>
</method>
<method name="moveTo">
<parameter name="aLeft"/>
<parameter name="aTop"/>
<body>
<![CDATA[
this.popupBoxObject.moveTo(aLeft, aTop);
]]>
</body>
</method>
<method name="moveToAnchor">
<parameter name="aAnchorElement"/>
<parameter name="aPosition"/>
<parameter name="aX"/>
<parameter name="aY"/>
<parameter name="aAttributesOverride"/>
<body>
<![CDATA[
this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
]]>
</body>
</method>
<method name="getOuterScreenRect">
<body>
<![CDATA[
return this.popupBoxObject.getOuterScreenRect();
]]>
</body>
</method>
<method name="setConstraintRect">
<parameter name="aRect"/>
<body>
<![CDATA[
this.popupBoxObject.setConstraintRect(aRect);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="popup" role="xul:menupopup"
extends="chrome://global/content/bindings/popup.xml#popup-base">
<content>
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
smoothscroll="false">
<children/>
</xul:arrowscrollbox>
</content>
<handlers>
<handler event="popupshowing" phase="target">
<![CDATA[
var array = [];
var width = 0;
for (var menuitem = this.firstChild; menuitem; menuitem = menuitem.nextSibling) {
if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel");
if (accel && accel.boxObject) {
array.push(accel);
if (accel.boxObject.width > width)
width = accel.boxObject.width;
}
}
}
for (var i = 0; i < array.length; i++)
array[i].width = width;
]]>
</handler>
</handlers>
</binding>
<binding id="panel" role="xul:panel"
extends="chrome://global/content/bindings/popup.xml#popup-base">
<implementation implements="nsIDOMXULPopupElement">
<field name="_prevFocus">0</field>
<field name="_dragBindingAlive">true</field>
<constructor>
<![CDATA[
if (this.getAttribute("backdrag") == "true" && !this._draggableStarted) {
this._draggableStarted = true;
try {
let tmp = {};
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
let draghandle = new tmp.WindowDraggingElement(this);
draghandle.mouseDownCheck = function () {
return this._dragBindingAlive;
}
} catch (e) {}
}
]]>
</constructor>
</implementation>
<handlers>
<handler event="popupshowing"><![CDATA[
// Capture the previous focus before has a chance to get set inside the panel
try {
this._prevFocus = Components.utils
.getWeakReference(document.commandDispatcher.focusedElement);
if (this._prevFocus.get())
return;
} catch (ex) { }
this._prevFocus = Components.utils.getWeakReference(document.activeElement);
]]></handler>
<handler event="popupshown"><![CDATA[
// Fire event for accessibility APIs
var alertEvent = document.createEvent("Events");
alertEvent.initEvent("AlertActive", true, true);
this.dispatchEvent(alertEvent);
]]></handler>
<handler event="popuphiding"><![CDATA[
try {
this._currentFocus = document.commandDispatcher.focusedElement;
} catch (e) {
this._currentFocus = document.activeElement;
}
]]></handler>
<handler event="popuphidden"><![CDATA[
function doFocus() {
// Focus was set on an element inside this panel,
// so we need to move it back to where it was previously
try {
let fm = Components.classes["@mozilla.org/focus-manager;1"]
.getService(Components.interfaces.nsIFocusManager);
fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
} catch (e) {
prevFocus.focus();
}
}
var currentFocus = this._currentFocus;
var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
this._currentFocus = null;
this._prevFocus = null;
// Avoid changing focus if focus changed while we hide the popup
// (This can happen e.g. if the popup is hiding as a result of a
// click/keypress that focused something)
let nowFocus;
try {
nowFocus = document.commandDispatcher.focusedElement;
} catch (e) {
nowFocus = document.activeElement;
}
if (nowFocus && nowFocus != currentFocus)
return;
if (prevFocus && this.getAttribute("norestorefocus") != "true") {
// Try to restore focus
try {
if (document.commandDispatcher.focusedWindow != window)
return; // Focus has already been set to a window outside of this panel
} catch (ex) {}
if (!currentFocus) {
doFocus();
return;
}
while (currentFocus) {
if (currentFocus == this) {
doFocus();
return;
}
currentFocus = currentFocus.parentNode;
}
}
]]></handler>
</handlers>
</binding>
<binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
<content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
<xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
xbl:inherits="side,panelopen">
<xul:box anonid="arrowbox" class="panel-arrowbox">
<xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
</xul:box>
<xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
<children/>
</xul:box>
</xul:vbox>
</content>
<implementation>
<field name="_fadeTimer">null</field>
<method name="sizeTo">
<parameter name="aWidth"/>
<parameter name="aHeight"/>
<body>
<![CDATA[
this.popupBoxObject.sizeTo(aWidth, aHeight);
if (this.state == "open") {
this.adjustArrowPosition();
}
]]>
</body>
</method>
<method name="moveToAnchor">
<parameter name="aAnchorElement"/>
<parameter name="aPosition"/>
<parameter name="aX"/>
<parameter name="aY"/>
<parameter name="aAttributesOverride"/>
<body>
<![CDATA[
this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
]]>
</body>
</method>
<method name="adjustArrowPosition">
<body>
<![CDATA[
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
var anchor = this.anchorNode;
if (!anchor) {
return;
}
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
var position = this.alignmentPosition;
var offset = this.alignmentOffset;
this.setAttribute("arrowposition", position);
if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
container.orient = "horizontal";
arrowbox.orient = "vertical";
if (position.indexOf("_after") > 0) {
arrowbox.pack = "end";
} else {
arrowbox.pack = "start";
}
arrowbox.style.transform = "translate(0, " + -offset + "px)";
// The assigned side stays the same regardless of direction.
var isRTL = (window.getComputedStyle(this).direction == "rtl");
if (position.indexOf("start_") == 0) {
container.dir = "reverse";
this.setAttribute("side", isRTL ? "left" : "right");
}
else {
container.dir = "";
this.setAttribute("side", isRTL ? "right" : "left");
}
}
else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
container.orient = "";
arrowbox.orient = "";
if (position.indexOf("_end") > 0) {
arrowbox.pack = "end";
} else {
arrowbox.pack = "start";
}
arrowbox.style.transform = "translate(" + -offset + "px, 0)";
if (position.indexOf("before_") == 0) {
container.dir = "reverse";
this.setAttribute("side", "bottom");
}
else {
container.dir = "";
this.setAttribute("side", "top");
}
}
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="popupshowing" phase="target">
<![CDATA[
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
arrow.hidden = this.anchorNode == null;
document.getAnonymousElementByAttribute(this, "anonid", "arrowbox")
.style.removeProperty("transform");
this.adjustArrowPosition();
if (this.getAttribute("animate") != "false") {
this.setAttribute("animate", "open");
}
// set fading
var fade = this.getAttribute("fade");
var fadeDelay = 0;
if (fade == "fast") {
fadeDelay = 1;
}
else if (fade == "slow") {
fadeDelay = 4000;
}
else {
return;
}
this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
]]>
</handler>
<handler event="popuphiding" phase="target">
let animate = (this.getAttribute("animate") != "false");
if (this._fadeTimer) {
clearTimeout(this._fadeTimer);
if (animate) {
this.setAttribute("animate", "fade");
}
}
else if (animate) {
this.setAttribute("animate", "cancel");
}
</handler>
<handler event="popupshown" phase="target">
this.setAttribute("panelopen", "true");
</handler>
<handler event="popuphidden" phase="target">
this.removeAttribute("panelopen");
if (this.getAttribute("animate") != "false") {
this.removeAttribute("animate");
}
</handler>
<handler event="popuppositioned" phase="target">
this.adjustArrowPosition();
</handler>
</handlers>
</binding>
<binding id="tooltip" role="xul:tooltip"
extends="chrome://global/content/bindings/popup.xml#popup-base">
<content>
<children>
<xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
</children>
</content>
<implementation>
<field name="_mouseOutCount">0</field>
<field name="_isMouseOver">false</field>
<property name="label"
onget="return this.getAttribute('label');"
onset="this.setAttribute('label', val); return val;"/>
<property name="page" onset="if (val) this.setAttribute('page', 'true');
else this.removeAttribute('page');
return val;"
onget="return this.getAttribute('page') == 'true';"/>
<property name="textProvider"
readonly="true">
<getter>
<![CDATA[
if (!this._textProvider) {
this._textProvider = Components.classes["@mozilla.org/embedcomp/default-tooltiptextprovider;1"]
.getService(Components.interfaces.nsITooltipTextProvider);
}
return this._textProvider;
]]>
</getter>
</property>
<!-- Given the supplied element within a page, set the tooltip's text to the text
for that element. Returns true if text was assigned, and false if the no text
is set, which normally would be used to cancel tooltip display.
-->
<method name="fillInPageTooltip">
<parameter name="tipElement"/>
<body>
<![CDATA[
let tttp = this.textProvider;
let textObj = {}, dirObj = {};
let shouldChangeText = tttp.getNodeText(tipElement, textObj, dirObj);
if (shouldChangeText) {
this.style.direction = dirObj.value;
this.label = textObj.value;
}
return shouldChangeText;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="mouseover"><![CDATA[
var rel = event.relatedTarget;
if (!rel)
return;
// find out if the node we entered from is one of our anonymous children
while (rel) {
if (rel == this)
break;
rel = rel.parentNode;
}
// if the exited node is not a descendant of ours, we are entering for the first time
if (rel != this)
this._isMouseOver = true;
]]></handler>
<handler event="mouseout"><![CDATA[
var rel = event.relatedTarget;
// relatedTarget is null when the titletip is first shown: a mouseout event fires
// because the mouse is exiting the main window and entering the titletip "window".
// relatedTarget is also null when the mouse exits the main window completely,
// so count how many times relatedTarget was null after titletip is first shown
// and hide popup the 2nd time
if (!rel) {
++this._mouseOutCount;
if (this._mouseOutCount > 1)
this.hidePopup();
return;
}
// find out if the node we are entering is one of our anonymous children
while (rel) {
if (rel == this)
break;
rel = rel.parentNode;
}
// if the entered node is not a descendant of ours, hide the tooltip
if (rel != this && this._isMouseOver) {
this.hidePopup();
}
]]></handler>
<handler event="popupshowing"><![CDATA[
if (this.page && !this.fillInPageTooltip(this.triggerNode)) {
event.preventDefault();
}
]]></handler>
<handler event="popuphiding"><![CDATA[
this._isMouseOver = false;
this._mouseOutCount = 0;
]]></handler>
</handlers>
</binding>
<binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
<content>
<xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
<children/>
</xul:hbox>
</content>
</binding>
</bindings>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,116 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="progressmeterBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="progressmeter" role="xul:progressmeter">
<resources>
<stylesheet src="chrome://global/skin/progressmeter.css"/>
</resources>
<content>
<xul:spacer class="progress-bar" xbl:inherits="mode"/>
<xul:spacer class="progress-remainder" xbl:inherits="mode"/>
</content>
<implementation>
<property name="mode" onset="if (this.mode != val) this.setAttribute('mode', val); return val;"
onget="return this.getAttribute('mode');"/>
<property name="value" onget="return this.getAttribute('value') || '0';">
<setter><![CDATA[
var p = Math.round(val);
var max = Math.round(this.max);
if (p < 0)
p = 0;
else if (p > max)
p = max;
var c = this.value;
if (p != c) {
var delta = p - c;
if (delta < 0)
delta = -delta;
if (delta > 3 || p == 0 || p == max) {
this.setAttribute("value", p);
// Fire DOM event so that accessible value change events occur
var event = document.createEvent('Events');
event.initEvent('ValueChange', true, true);
this.dispatchEvent(event);
}
}
return val;
]]></setter>
</property>
<property name="max"
onget="return this.getAttribute('max') || '100';"
onset="this.setAttribute('max', isNaN(val) ? 100 : Math.max(val, 1));
this.value = this.value;
return val;" />
</implementation>
</binding>
<binding id="progressmeter-undetermined"
extends="chrome://global/content/bindings/progressmeter.xml#progressmeter">
<content>
<xul:stack class="progress-remainder" flex="1" anonid="stack" style="overflow: -moz-hidden-unscrollable;">
<xul:spacer class="progress-bar" anonid="spacer" top="0" style="margin-right: -1000px;"/>
</xul:stack>
</content>
<implementation>
<field name="_alive">true</field>
<method name="_init">
<body><![CDATA[
var stack =
document.getAnonymousElementByAttribute(this, "anonid", "stack");
var spacer =
document.getAnonymousElementByAttribute(this, "anonid", "spacer");
var isLTR =
document.defaultView.getComputedStyle(this, null).direction == "ltr";
var startTime = performance.now();
var self = this;
function nextStep(t) {
try {
var width = stack.boxObject.width;
if (!width) {
// Maybe we've been removed from the document.
if (self._alive)
requestAnimationFrame(nextStep);
return;
}
var elapsedTime = t - startTime;
// Width of chunk is 1/5 (determined by the ratio 2000:400) of the
// total width of the progress bar. The left edge of the chunk
// starts at -1 and moves all the way to 4. It covers the distance
// in 2 seconds.
var position = isLTR ? ((elapsedTime % 2000) / 400) - 1 :
((elapsedTime % 2000) / -400) + 4;
width = width >> 2;
spacer.height = stack.boxObject.height;
spacer.width = width;
spacer.left = width * position;
requestAnimationFrame(nextStep);
} catch (e) {
}
}
requestAnimationFrame(nextStep);
]]></body>
</method>
<constructor>this._init();</constructor>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,526 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="radioBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="radiogroup" role="xul:radiogroup"
extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/radio.css"/>
</resources>
<implementation implements="nsIDOMXULSelectControlElement">
<constructor>
<![CDATA[
if (this.getAttribute("disabled") == "true")
this.disabled = true;
var children = this._getRadioChildren();
var length = children.length;
for (var i = 0; i < length; i++) {
if (children[i].getAttribute("selected") == "true") {
this.selectedIndex = i;
return;
}
}
var value = this.value;
if (value)
this.value = value;
else
this.selectedIndex = 0;
]]>
</constructor>
<property name="value" onget="return this.getAttribute('value');">
<setter>
<![CDATA[
this.setAttribute("value", val);
var children = this._getRadioChildren();
for (var i = 0; i < children.length; i++) {
if (String(children[i].value) == String(val)) {
this.selectedItem = children[i];
break;
}
}
return val;
]]>
</setter>
</property>
<property name="disabled">
<getter>
<![CDATA[
if (this.getAttribute('disabled') == 'true')
return true;
var children = this._getRadioChildren();
for (var i = 0; i < children.length; ++i) {
if (!children[i].hidden && !children[i].collapsed && !children[i].disabled)
return false;
}
return true;
]]>
</getter>
<setter>
<![CDATA[
if (val)
this.setAttribute('disabled', 'true');
else
this.removeAttribute('disabled');
var children = this._getRadioChildren();
for (var i = 0; i < children.length; ++i) {
children[i].disabled = val;
}
return val;
]]>
</setter>
</property>
<property name="itemCount" readonly="true"
onget="return this._getRadioChildren().length"/>
<property name="selectedIndex">
<getter>
<![CDATA[
var children = this._getRadioChildren();
for (var i = 0; i < children.length; ++i) {
if (children[i].selected)
return i;
}
return -1;
]]>
</getter>
<setter>
<![CDATA[
this.selectedItem = this._getRadioChildren()[val];
return val;
]]>
</setter>
</property>
<property name="selectedItem">
<getter>
<![CDATA[
var children = this._getRadioChildren();
for (var i = 0; i < children.length; ++i) {
if (children[i].selected)
return children[i];
}
return null;
]]>
</getter>
<setter>
<![CDATA[
var focused = this.getAttribute("focused") == "true";
var alreadySelected = false;
if (val) {
alreadySelected = val.getAttribute("selected") == "true";
val.setAttribute("focused", focused);
val.setAttribute("selected", "true");
this.setAttribute("value", val.value);
}
else {
this.removeAttribute("value");
}
// uncheck all other group nodes
var children = this._getRadioChildren();
var previousItem = null;
for (var i = 0; i < children.length; ++i) {
if (children[i] != val) {
if (children[i].getAttribute("selected") == "true")
previousItem = children[i];
children[i].removeAttribute("selected");
children[i].removeAttribute("focused");
}
}
var event = document.createEvent("Events");
event.initEvent("select", false, true);
this.dispatchEvent(event);
if (!alreadySelected && focused) {
// Only report if actual change
var myEvent;
if (val) {
myEvent = document.createEvent("Events");
myEvent.initEvent("RadioStateChange", true, true);
val.dispatchEvent(myEvent);
}
if (previousItem) {
myEvent = document.createEvent("Events");
myEvent.initEvent("RadioStateChange", true, true);
previousItem.dispatchEvent(myEvent);
}
}
return val;
]]>
</setter>
</property>
<property name="focusedItem">
<getter>
<![CDATA[
var children = this._getRadioChildren();
for (var i = 0; i < children.length; ++i) {
if (children[i].getAttribute("focused") == "true")
return children[i];
}
return null;
]]>
</getter>
<setter>
<![CDATA[
if (val) val.setAttribute("focused", "true");
// unfocus all other group nodes
var children = this._getRadioChildren();
for (var i = 0; i < children.length; ++i) {
if (children[i] != val)
children[i].removeAttribute("focused");
}
return val;
]]>
</setter>
</property>
<method name="checkAdjacentElement">
<parameter name="aNextFlag"/>
<body>
<![CDATA[
var currentElement = this.focusedItem || this.selectedItem;
var i;
var children = this._getRadioChildren();
for (i = 0; i < children.length; ++i ) {
if (children[i] == currentElement)
break;
}
var index = i;
if (aNextFlag) {
do {
if (++i == children.length)
i = 0;
if (i == index)
break;
}
while (children[i].hidden || children[i].collapsed || children[i].disabled);
// XXX check for display/visibility props too
this.selectedItem = children[i];
children[i].doCommand();
}
else {
do {
if (i == 0)
i = children.length;
if (--i == index)
break;
}
while (children[i].hidden || children[i].collapsed || children[i].disabled);
// XXX check for display/visibility props too
this.selectedItem = children[i];
children[i].doCommand();
}
]]>
</body>
</method>
<field name="_radioChildren">null</field>
<method name="_getRadioChildren">
<body>
<![CDATA[
if (this._radioChildren)
return this._radioChildren;
var radioChildren = [];
var doc = this.ownerDocument;
if (this.hasChildNodes()) {
// Don't store the collected child nodes immediately,
// collecting the child nodes could trigger constructors
// which would blow away our list.
const nsIDOMNodeFilter = Components.interfaces.nsIDOMNodeFilter;
var iterator = doc.createTreeWalker(this,
nsIDOMNodeFilter.SHOW_ELEMENT,
this._filterRadioGroup);
while (iterator.nextNode())
radioChildren.push(iterator.currentNode);
return this._radioChildren = radioChildren;
}
// We don't have child nodes.
const XUL_NS = "http://www.mozilla.org/keymaster/"
+ "gatekeeper/there.is.only.xul";
var elems = doc.getElementsByAttribute("group", this.id);
for (var i = 0; i < elems.length; i++) {
if ((elems[i].namespaceURI == XUL_NS) &&
(elems[i].localName == "radio")) {
radioChildren.push(elems[i]);
}
}
return this._radioChildren = radioChildren;
]]>
</body>
</method>
<method name="_filterRadioGroup">
<parameter name="node"/>
<body>
<![CDATA[
switch (node.localName) {
case "radio": return NodeFilter.FILTER_ACCEPT;
case "template":
case "radiogroup": return NodeFilter.FILTER_REJECT;
default: return NodeFilter.FILTER_SKIP;
}
]]>
</body>
</method>
<method name="getIndexOfItem">
<parameter name="item"/>
<body>
return this._getRadioChildren().indexOf(item);
</body>
</method>
<method name="getItemAtIndex">
<parameter name="index"/>
<body>
<![CDATA[
var children = this._getRadioChildren();
return (index >= 0 && index < children.length) ? children[index] : null;
]]>
</body>
</method>
<method name="appendItem">
<parameter name="label"/>
<parameter name="value"/>
<body>
<![CDATA[
var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var radio = document.createElementNS(XULNS, "radio");
radio.setAttribute("label", label);
radio.setAttribute("value", value);
this.appendChild(radio);
this._radioChildren = null;
return radio;
]]>
</body>
</method>
<method name="insertItemAt">
<parameter name="index"/>
<parameter name="label"/>
<parameter name="value"/>
<body>
<![CDATA[
var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var radio = document.createElementNS(XULNS, "radio");
radio.setAttribute("label", label);
radio.setAttribute("value", value);
var before = this.getItemAtIndex(index);
if (before)
before.parentNode.insertBefore(radio, before);
else
this.appendChild(radio);
this._radioChildren = null;
return radio;
]]>
</body>
</method>
<method name="removeItemAt">
<parameter name="index"/>
<body>
<![CDATA[
var remove = this.getItemAtIndex(index);
if (remove) {
remove.parentNode.removeChild(remove);
this._radioChildren = null;
}
return remove;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="mousedown">
if (this.disabled)
event.preventDefault();
</handler>
<!-- keyboard navigation -->
<!-- Here's how keyboard navigation works in radio groups on Windows:
The group takes 'focus'
The user is then free to navigate around inside the group
using the arrow keys. Accessing previous or following radio buttons
is done solely through the arrow keys and not the tab button. Tab
takes you to the next widget in the tab order -->
<handler event="keypress" key=" " phase="target">
this.selectedItem = this.focusedItem;
this.selectedItem.doCommand();
// Prevent page from scrolling on the space key.
event.preventDefault();
</handler>
<handler event="keypress" keycode="VK_UP" phase="target">
this.checkAdjacentElement(false);
event.stopPropagation();
event.preventDefault();
</handler>
<handler event="keypress" keycode="VK_LEFT" phase="target">
// left arrow goes back when we are ltr, forward when we are rtl
this.checkAdjacentElement(document.defaultView.getComputedStyle(
this, "").direction == "rtl");
event.stopPropagation();
event.preventDefault();
</handler>
<handler event="keypress" keycode="VK_DOWN" phase="target">
this.checkAdjacentElement(true);
event.stopPropagation();
event.preventDefault();
</handler>
<handler event="keypress" keycode="VK_RIGHT" phase="target">
// right arrow goes forward when we are ltr, back when we are rtl
this.checkAdjacentElement(document.defaultView.getComputedStyle(
this, "").direction == "ltr");
event.stopPropagation();
event.preventDefault();
</handler>
<!-- set a focused attribute on the selected item when the group
receives focus so that we can style it as if it were focused even though
it is not (Windows platform behaviour is for the group to receive focus,
not the item -->
<handler event="focus" phase="target">
<![CDATA[
this.setAttribute("focused", "true");
if (this.focusedItem)
return;
var val = this.selectedItem;
if (!val || val.disabled || val.hidden || val.collapsed) {
var children = this._getRadioChildren();
for (var i = 0; i < children.length; ++i) {
if (!children[i].hidden && !children[i].collapsed && !children[i].disabled) {
val = children[i];
break;
}
}
}
this.focusedItem = val;
]]>
</handler>
<handler event="blur" phase="target">
this.removeAttribute("focused");
this.focusedItem = null;
</handler>
</handlers>
</binding>
<binding id="radio" role="xul:radiobutton"
extends="chrome://global/content/bindings/general.xml#control-item">
<resources>
<stylesheet src="chrome://global/skin/radio.css"/>
</resources>
<content>
<xul:image class="radio-check" xbl:inherits="disabled,selected"/>
<xul:hbox class="radio-label-box" align="center" flex="1">
<xul:image class="radio-icon" xbl:inherits="src"/>
<xul:label class="radio-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
</xul:hbox>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<constructor>
<![CDATA[
// Just clear out the parent's cached list of radio children
var control = this.control;
if (control)
control._radioChildren = null;
]]>
</constructor>
<destructor>
<![CDATA[
if (!this.control)
return;
var radioList = this.control._radioChildren;
if (!radioList)
return;
for (var i = 0; i < radioList.length; ++i) {
if (radioList[i] == this) {
radioList.splice(i, 1);
return;
}
}
]]>
</destructor>
<property name="selected" readonly="true">
<getter>
<![CDATA[
return this.hasAttribute('selected');
]]>
</getter>
</property>
<property name="radioGroup" readonly="true" onget="return this.control"/>
<property name="control" readonly="true">
<getter>
<![CDATA[
const XUL_NS = "http://www.mozilla.org/keymaster/"
+ "gatekeeper/there.is.only.xul";
var parent = this.parentNode;
while (parent) {
if ((parent.namespaceURI == XUL_NS) &&
(parent.localName == "radiogroup")) {
return parent;
}
parent = parent.parentNode;
}
var group = this.getAttribute("group");
if (!group) {
return null;
}
parent = this.ownerDocument.getElementById(group);
if (!parent ||
(parent.namespaceURI != XUL_NS) ||
(parent.localName != "radiogroup")) {
parent = null;
}
return parent;
]]>
</getter>
</property>
</implementation>
<handlers>
<handler event="click" button="0">
<![CDATA[
if (!this.disabled)
this.control.selectedItem = this;
]]>
</handler>
<handler event="mousedown" button="0">
<![CDATA[
if (!this.disabled)
this.control.focusedItem = this;
]]>
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,591 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="firefoxBrowserBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="remote-browser" extends="chrome://global/content/bindings/browser.xml#browser">
<implementation type="application/javascript"
implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIRemoteBrowser">
<field name="_securityUI">null</field>
<property name="securityUI"
readonly="true">
<getter><![CDATA[
if (!this._securityUI) {
// Don't attempt to create the remote web progress if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
let RemoteSecurityUI = Components.utils.import(jsm, {}).RemoteSecurityUI;
this._securityUI = new RemoteSecurityUI();
}
// We want to double-wrap the JS implemented interface, so that QI and instanceof works.
var ptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
.createInstance(Components.interfaces.nsISupportsInterfacePointer);
ptr.data = this._securityUI;
return ptr.data.QueryInterface(Components.interfaces.nsISecureBrowserUI);
]]></getter>
</property>
<!-- increases or decreases the browser's network priority -->
<method name="adjustPriority">
<parameter name="adjustment"/>
<body><![CDATA[
this.messageManager.sendAsyncMessage("NetworkPrioritizer:AdjustPriority",
{adjustment});
]]></body>
</method>
<!-- sets the browser's network priority to a discrete value -->
<method name="setPriority">
<parameter name="priority"/>
<body><![CDATA[
this.messageManager.sendAsyncMessage("NetworkPrioritizer:SetPriority",
{priority});
]]></body>
</method>
<field name="_controller">null</field>
<field name="_selectParentHelper">null</field>
<field name="_remoteWebNavigation">null</field>
<property name="webNavigation"
onget="return this._remoteWebNavigation;"
readonly="true"/>
<field name="_remoteWebProgress">null</field>
<property name="webProgress" readonly="true">
<getter>
<![CDATA[
if (!this._remoteWebProgress) {
// Don't attempt to create the remote web progress if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
let { RemoteWebProgressManager } = Components.utils.import(jsm, {});
this._remoteWebProgressManager = new RemoteWebProgressManager(this);
this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
}
return this._remoteWebProgress;
]]>
</getter>
</property>
<field name="_remoteFinder">null</field>
<property name="finder" readonly="true">
<getter><![CDATA[
if (!this._remoteFinder) {
// Don't attempt to create the remote finder if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteFinder.jsm";
let { RemoteFinder } = Components.utils.import(jsm, {});
this._remoteFinder = new RemoteFinder(this);
}
return this._remoteFinder;
]]></getter>
</property>
<field name="_documentURI">null</field>
<field name="_documentContentType">null</field>
<!--
Used by session restore to ensure that currentURI is set so
that switch-to-tab works before the tab is fully
restored. This function also invokes onLocationChanged
listeners in tabbrowser.xml.
-->
<method name="_setCurrentURI">
<parameter name="aURI"/>
<body><![CDATA[
this._remoteWebProgressManager.setCurrentURI(aURI);
]]></body>
</method>
<property name="documentURI"
onget="return this._documentURI;"
readonly="true"/>
<property name="documentContentType"
onget="return this._documentContentType;"
readonly="true"/>
<field name="_contentTitle">""</field>
<property name="contentTitle"
onget="return this._contentTitle"
readonly="true"/>
<field name="_characterSet">""</field>
<property name="characterSet"
onget="return this._characterSet">
<setter><![CDATA[
this.messageManager.sendAsyncMessage("UpdateCharacterSet", {value: val});
this._characterSet = val;
]]></setter>
</property>
<field name="_mayEnableCharacterEncodingMenu">null</field>
<property name="mayEnableCharacterEncodingMenu"
onget="return this._mayEnableCharacterEncodingMenu;"
readonly="true"/>
<field name="_contentWindow">null</field>
<property name="contentWindow"
onget="return null"
readonly="true"/>
<property name="contentWindowAsCPOW"
onget="return this._contentWindow"
readonly="true"/>
<property name="contentDocument"
onget="return null"
readonly="true"/>
<field name="_contentPrincipal">null</field>
<property name="contentPrincipal"
onget="return this._contentPrincipal"
readonly="true"/>
<property name="contentDocumentAsCPOW"
onget="return this.contentWindowAsCPOW ? this.contentWindowAsCPOW.document : null"
readonly="true"/>
<field name="_imageDocument">null</field>
<property name="imageDocument"
onget="return this._imageDocument"
readonly="true"/>
<field name="_fullZoom">1</field>
<property name="fullZoom">
<getter><![CDATA[
return this._fullZoom;
]]></getter>
<setter><![CDATA[
let changed = val.toFixed(2) != this._fullZoom.toFixed(2);
this._fullZoom = val;
this.messageManager.sendAsyncMessage("FullZoom", {value: val});
if (changed) {
let event = new Event("FullZoomChange", {bubbles: true});
this.dispatchEvent(event);
}
]]></setter>
</property>
<field name="_textZoom">1</field>
<property name="textZoom">
<getter><![CDATA[
return this._textZoom;
]]></getter>
<setter><![CDATA[
let changed = val.toFixed(2) != this._textZoom.toFixed(2);
this._textZoom = val;
this.messageManager.sendAsyncMessage("TextZoom", {value: val});
if (changed) {
let event = new Event("TextZoomChange", {bubbles: true});
this.dispatchEvent(event);
}
]]></setter>
</property>
<field name="_isSyntheticDocument">false</field>
<property name="isSyntheticDocument">
<getter><![CDATA[
return this._isSyntheticDocument;
]]></getter>
</property>
<property name="hasContentOpener">
<getter><![CDATA[
let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
return frameLoader.tabParent.hasContentOpener;
]]></getter>
</property>
<field name="_outerWindowID">null</field>
<property name="outerWindowID"
onget="return this._outerWindowID"
readonly="true"/>
<field name="_innerWindowID">null</field>
<property name="innerWindowID">
<getter><![CDATA[
return this._innerWindowID;
]]></getter>
</property>
<property name="docShellIsActive">
<getter>
<![CDATA[
let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
return frameLoader.tabParent.docShellIsActive;
]]>
</getter>
<setter>
<![CDATA[
let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
frameLoader.tabParent.docShellIsActive = val;
return val;
]]>
</setter>
</property>
<method name="preserveLayers">
<parameter name="preserve"/>
<body><![CDATA[
let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
if (frameLoader.tabParent) {
frameLoader.tabParent.preserveLayers(preserve);
}
]]></body>
</method>
<field name="_manifestURI"/>
<property name="manifestURI"
onget="return this._manifestURI"
readonly="true"/>
<field name="mDestroyed">false</field>
<field name="_permitUnloadId">0</field>
<method name="getInPermitUnload">
<parameter name="aCallback"/>
<body>
<![CDATA[
let id = this._permitUnloadId++;
let mm = this.messageManager;
mm.sendAsyncMessage("InPermitUnload", {id});
mm.addMessageListener("InPermitUnload", function listener(msg) {
if (msg.data.id != id) {
return;
}
aCallback(msg.data.inPermitUnload);
});
]]>
</body>
</method>
<method name="permitUnload">
<body>
<![CDATA[
const kTimeout = 5000;
let finished = false;
let responded = false;
let permitUnload;
let id = this._permitUnloadId++;
let mm = this.messageManager;
let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
let msgListener = msg => {
if (msg.data.id != id) {
return;
}
if (msg.data.kind == "start") {
responded = true;
return;
}
done(msg.data.permitUnload);
};
let observer = subject => {
if (subject == mm) {
done(true);
}
};
function done(result) {
finished = true;
permitUnload = result;
mm.removeMessageListener("PermitUnload", msgListener);
Services.obs.removeObserver(observer, "message-manager-close");
}
mm.sendAsyncMessage("PermitUnload", {id});
mm.addMessageListener("PermitUnload", msgListener);
Services.obs.addObserver(observer, "message-manager-close", false);
let timedOut = false;
function timeout() {
if (!responded) {
timedOut = true;
}
// Dispatch something to ensure that the main thread wakes up.
Services.tm.mainThread.dispatch(function() {}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
}
let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
while (!finished && !timedOut) {
Services.tm.currentThread.processNextEvent(true);
}
return {permitUnload, timedOut};
]]>
</body>
</method>
<constructor>
<![CDATA[
/*
* Don't try to send messages from this function. The message manager for
* the <browser> element may not be initialized yet.
*/
this._remoteWebNavigation = Components.classes["@mozilla.org/remote-web-navigation;1"]
.createInstance(Components.interfaces.nsIWebNavigation);
this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
this._remoteWebNavigationImpl.swapBrowser(this);
// Initialize contentPrincipal to the about:blank principal for this loadcontext
let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
let aboutBlank = Services.io.newURI("about:blank", null, null);
let ssm = Services.scriptSecurityManager;
this._contentPrincipal = ssm.getLoadContextCodebasePrincipal(aboutBlank, this.loadContext);
this.messageManager.addMessageListener("Browser:Init", this);
this.messageManager.addMessageListener("DOMTitleChanged", this);
this.messageManager.addMessageListener("ImageDocumentLoaded", this);
this.messageManager.addMessageListener("FullZoomChange", this);
this.messageManager.addMessageListener("TextZoomChange", this);
this.messageManager.addMessageListener("ZoomChangeUsingMouseWheel", this);
this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
this.messageManager.addMessageListener("MozApplicationManifest", this);
this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
if (this.hasAttribute("selectmenulist")) {
this.messageManager.addMessageListener("Forms:ShowDropDown", this);
this.messageManager.addMessageListener("Forms:HideDropDown", this);
this.messageManager.loadFrameScript("chrome://global/content/select-child.js", true);
}
if (!this.hasAttribute("disablehistory")) {
Services.obs.addObserver(this, "browser:purge-session-history", true);
}
let jsm = "resource://gre/modules/RemoteController.jsm";
let RemoteController = Components.utils.import(jsm, {}).RemoteController;
this._controller = new RemoteController(this);
this.controllers.appendController(this._controller);
]]>
</constructor>
<destructor>
<![CDATA[
this.destroy();
]]>
</destructor>
<!-- This is necessary because the destructor doesn't always get called when
we are removed from a tabbrowser. This will be explicitly called by tabbrowser.
Note: This overrides the destroy() method from browser.xml. -->
<method name="destroy">
<body><![CDATA[
// Make sure that any open select is closed.
if (this._selectParentHelper) {
let menulist = document.getElementById(this.getAttribute("selectmenulist"));
this._selectParentHelper.hide(menulist, this);
}
if (this.mDestroyed)
return;
this.mDestroyed = true;
try {
this.controllers.removeController(this._controller);
} catch (ex) {
// This can fail when this browser element is not attached to a
// BrowserDOMWindow.
}
if (!this.hasAttribute("disablehistory")) {
let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
try {
Services.obs.removeObserver(this, "browser:purge-session-history");
} catch (ex) {
// It's not clear why this sometimes throws an exception.
}
}
]]></body>
</method>
<method name="receiveMessage">
<parameter name="aMessage"/>
<body><![CDATA[
let data = aMessage.data;
switch (aMessage.name) {
case "Browser:Init":
this._outerWindowID = data.outerWindowID;
break;
case "DOMTitleChanged":
this._contentTitle = data.title;
break;
case "ImageDocumentLoaded":
this._imageDocument = {
width: data.width,
height: data.height
};
break;
case "Forms:ShowDropDown": {
if (!this._selectParentHelper) {
this._selectParentHelper =
Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
}
let menulist = document.getElementById(this.getAttribute("selectmenulist"));
menulist.menupopup.style.direction = data.direction;
let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
this.isSyntheticDocument ? this._fullZoom : this._textZoom;
this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, zoom);
this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
break;
}
case "FullZoomChange": {
this._fullZoom = data.value;
let event = document.createEvent("Events");
event.initEvent("FullZoomChange", true, false);
this.dispatchEvent(event);
break;
}
case "TextZoomChange": {
this._textZoom = data.value;
let event = document.createEvent("Events");
event.initEvent("TextZoomChange", true, false);
this.dispatchEvent(event);
break;
}
case "ZoomChangeUsingMouseWheel": {
let event = document.createEvent("Events");
event.initEvent("ZoomChangeUsingMouseWheel", true, false);
this.dispatchEvent(event);
break;
}
case "Forms:HideDropDown": {
if (this._selectParentHelper) {
let menulist = document.getElementById(this.getAttribute("selectmenulist"));
this._selectParentHelper.hide(menulist, this);
}
break;
}
case "DOMFullscreen:RequestExit": {
let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
windowUtils.exitFullscreen();
break;
}
case "DOMFullscreen:RequestRollback": {
let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
windowUtils.remoteFrameFullscreenReverted();
break;
}
case "MozApplicationManifest":
this._manifestURI = aMessage.data.manifest;
break;
default:
// Delegate to browser.xml.
return this._receiveMessage(aMessage);
}
return undefined;
]]></body>
</method>
<method name="enableDisableCommands">
<parameter name="aAction"/>
<parameter name="aEnabledLength"/>
<parameter name="aEnabledCommands"/>
<parameter name="aDisabledLength"/>
<parameter name="aDisabledCommands"/>
<body>
if (this._controller) {
this._controller.enableDisableCommands(aAction,
aEnabledLength, aEnabledCommands,
aDisabledLength, aDisabledCommands);
}
</body>
</method>
<method name="purgeSessionHistory">
<body>
<![CDATA[
try {
this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
} catch (ex) {
// This can throw if the browser has started to go away.
if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
throw ex;
}
}
this._remoteWebNavigationImpl.canGoBack = false;
this._remoteWebNavigationImpl.canGoForward = false;
]]>
</body>
</method>
<method name="createAboutBlankContentViewer">
<parameter name="aPrincipal"/>
<body>
<![CDATA[
this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="dragstart">
<![CDATA[
// If we're a remote browser dealing with a dragstart, stop it
// from propagating up, since our content process should be dealing
// with the mouse movement.
event.stopPropagation();
]]>
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,39 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="resizerBindings"
xmlns="http://www.mozilla.org/xbl">
<binding id="resizer">
<resources>
<stylesheet src="chrome://global/skin/resizer.css"/>
</resources>
<implementation>
<constructor>
<![CDATA[
// don't do this for viewport resizers; causes a crash related to
// bugs 563665 and 581536 otherwise
if (this.parentNode == this.ownerDocument.documentElement)
return;
// if the direction is rtl, set the rtl attribute so that the
// stylesheet can use this to make the cursor appear properly
var cs = window.getComputedStyle(this, "");
if (cs.writingMode === undefined || cs.writingMode == "horizontal-tb") {
if (cs.direction == "rtl") {
this.setAttribute("rtl", "true");
}
} else if (cs.writingMode.endsWith("-rl")) {
// writing-modes 'vertical-rl' and 'sideways-rl' want rtl resizers,
// as they will appear at the bottom left of the element
this.setAttribute("rtl", "true");
}
]]>
</constructor>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,589 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="richlistboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="richlistbox"
extends="chrome://global/content/bindings/listbox.xml#listbox-base">
<resources>
<stylesheet src="chrome://global/skin/richlistbox.css"/>
</resources>
<content>
<children includes="listheader"/>
<xul:scrollbox allowevents="true" orient="vertical" anonid="main-box"
flex="1" style="overflow: auto;" xbl:inherits="dir,pack">
<children/>
</xul:scrollbox>
</content>
<implementation>
<field name="_scrollbox">
document.getAnonymousElementByAttribute(this, "anonid", "main-box");
</field>
<field name="scrollBoxObject">
this._scrollbox.boxObject;
</field>
<constructor>
<![CDATA[
// add a template build listener
if (this.builder)
this.builder.addListener(this._builderListener);
else
this._refreshSelection();
]]>
</constructor>
<destructor>
<![CDATA[
// remove the template build listener
if (this.builder)
this.builder.removeListener(this._builderListener);
]]>
</destructor>
<!-- Overriding baselistbox -->
<method name="_fireOnSelect">
<body>
<![CDATA[
// make sure not to modify last-selected when suppressing select events
// (otherwise we'll lose the selection when a template gets rebuilt)
if (this._suppressOnSelect || this.suppressOnSelect)
return;
// remember the current item and all selected items with IDs
var state = this.currentItem ? this.currentItem.id : "";
if (this.selType == "multiple" && this.selectedCount) {
let getId = function getId(aItem) { return aItem.id; }
state += " " + [... this.selectedItems].filter(getId).map(getId).join(" ");
}
if (state)
this.setAttribute("last-selected", state);
else
this.removeAttribute("last-selected");
// preserve the index just in case no IDs are available
if (this.currentIndex > -1)
this._currentIndex = this.currentIndex + 1;
var event = document.createEvent("Events");
event.initEvent("select", true, true);
this.dispatchEvent(event);
// always call this (allows a commandupdater without controller)
document.commandDispatcher.updateCommands("richlistbox-select");
]]>
</body>
</method>
<!-- We override base-listbox here because those methods don't take dir
into account on listbox (which doesn't support dir yet) -->
<method name="getNextItem">
<parameter name="aStartItem"/>
<parameter name="aDelta"/>
<body>
<![CDATA[
var prop = this.dir == "reverse" && this._mayReverse ?
"previousSibling" :
"nextSibling";
while (aStartItem) {
aStartItem = aStartItem[prop];
if (aStartItem && aStartItem instanceof
Components.interfaces.nsIDOMXULSelectControlItemElement &&
(!this._userSelecting || this._canUserSelect(aStartItem))) {
--aDelta;
if (aDelta == 0)
return aStartItem;
}
}
return null;
]]></body>
</method>
<method name="getPreviousItem">
<parameter name="aStartItem"/>
<parameter name="aDelta"/>
<body>
<![CDATA[
var prop = this.dir == "reverse" && this._mayReverse ?
"nextSibling" :
"previousSibling";
while (aStartItem) {
aStartItem = aStartItem[prop];
if (aStartItem && aStartItem instanceof
Components.interfaces.nsIDOMXULSelectControlItemElement &&
(!this._userSelecting || this._canUserSelect(aStartItem))) {
--aDelta;
if (aDelta == 0)
return aStartItem;
}
}
return null;
]]>
</body>
</method>
<method name="appendItem">
<parameter name="aLabel"/>
<parameter name="aValue"/>
<body>
return this.insertItemAt(-1, aLabel, aValue);
</body>
</method>
<method name="insertItemAt">
<parameter name="aIndex"/>
<parameter name="aLabel"/>
<parameter name="aValue"/>
<body>
const XULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var item =
this.ownerDocument.createElementNS(XULNS, "richlistitem");
item.setAttribute("value", aValue);
var label = this.ownerDocument.createElementNS(XULNS, "label");
label.setAttribute("value", aLabel);
label.setAttribute("flex", "1");
label.setAttribute("crop", "end");
item.appendChild(label);
var before = this.getItemAtIndex(aIndex);
if (!before)
this.appendChild(item);
else
this.insertBefore(item, before);
return item;
</body>
</method>
<property name="itemCount" readonly="true"
onget="return this.children.length"/>
<method name="getIndexOfItem">
<parameter name="aItem"/>
<body>
<![CDATA[
// don't search the children, if we're looking for none of them
if (aItem == null)
return -1;
return this.children.indexOf(aItem);
]]>
</body>
</method>
<method name="getItemAtIndex">
<parameter name="aIndex"/>
<body>
return this.children[aIndex] || null;
</body>
</method>
<method name="ensureIndexIsVisible">
<parameter name="aIndex"/>
<body>
<![CDATA[
// work around missing implementation in scrollBoxObject
return this.ensureElementIsVisible(this.getItemAtIndex(aIndex));
]]>
</body>
</method>
<method name="ensureElementIsVisible">
<parameter name="aElement"/>
<body>
<![CDATA[
if (!aElement)
return;
var targetRect = aElement.getBoundingClientRect();
var scrollRect = this._scrollbox.getBoundingClientRect();
var offset = targetRect.top - scrollRect.top;
if (offset >= 0) {
// scrollRect.bottom wouldn't take a horizontal scroll bar into account
let scrollRectBottom = scrollRect.top + this._scrollbox.clientHeight;
offset = targetRect.bottom - scrollRectBottom;
if (offset <= 0)
return;
}
this._scrollbox.scrollTop += offset;
]]>
</body>
</method>
<method name="scrollToIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
var item = this.getItemAtIndex(aIndex);
if (item)
this.scrollBoxObject.scrollToElement(item);
]]>
</body>
</method>
<method name="getNumberOfVisibleRows">
<!-- returns the number of currently visible rows -->
<!-- don't rely on this function, if the items' height can vary! -->
<body>
<![CDATA[
var children = this.children;
for (var top = 0; top < children.length && !this._isItemVisible(children[top]); top++);
for (var ix = top; ix < children.length && this._isItemVisible(children[ix]); ix++);
return ix - top;
]]>
</body>
</method>
<method name="getIndexOfFirstVisibleRow">
<body>
<![CDATA[
var children = this.children;
for (var ix = 0; ix < children.length; ix++)
if (this._isItemVisible(children[ix]))
return ix;
return -1;
]]>
</body>
</method>
<method name="getRowCount">
<body>
<![CDATA[
return this.children.length;
]]>
</body>
</method>
<method name="scrollOnePage">
<parameter name="aDirection"/> <!-- Must be -1 or 1 -->
<body>
<![CDATA[
var children = this.children;
if (children.length == 0)
return 0;
// If nothing is selected, we just select the first element
// at the extreme we're moving away from
if (!this.currentItem)
return aDirection == -1 ? children.length : 0;
// If the current item is visible, scroll by one page so that
// the new current item is at approximately the same position as
// the existing current item.
if (this._isItemVisible(this.currentItem))
this.scrollBoxObject.scrollBy(0, this.scrollBoxObject.height * aDirection);
// Figure out, how many items fully fit into the view port
// (including the currently selected one), and determine
// the index of the first one lying (partially) outside
var height = this.scrollBoxObject.height;
var startBorder = this.currentItem.boxObject.y;
if (aDirection == -1)
startBorder += this.currentItem.boxObject.height;
var index = this.currentIndex;
for (var ix = index; 0 <= ix && ix < children.length; ix += aDirection) {
var boxObject = children[ix].boxObject;
if (boxObject.height == 0)
continue; // hidden children have a y of 0
var endBorder = boxObject.y + (aDirection == -1 ? boxObject.height : 0);
if ((endBorder - startBorder) * aDirection > height)
break; // we've reached the desired distance
index = ix;
}
return index != this.currentIndex ? index - this.currentIndex : aDirection;
]]>
</body>
</method>
<!-- richlistbox specific -->
<property name="children" readonly="true">
<getter>
<![CDATA[
var childNodes = [];
var isReverse = this.dir == "reverse" && this._mayReverse;
var child = isReverse ? this.lastChild : this.firstChild;
var prop = isReverse ? "previousSibling" : "nextSibling";
while (child) {
if (child instanceof Components.interfaces.nsIDOMXULSelectControlItemElement)
childNodes.push(child);
child = child[prop];
}
return childNodes;
]]>
</getter>
</property>
<field name="_builderListener" readonly="true">
<![CDATA[
({
mOuter: this,
item: null,
willRebuild: function(builder) { },
didRebuild: function(builder) {
this.mOuter._refreshSelection();
}
});
]]>
</field>
<method name="_refreshSelection">
<body>
<![CDATA[
// when this method is called, we know that either the currentItem
// and selectedItems we have are null (ctor) or a reference to an
// element no longer in the DOM (template).
// first look for the last-selected attribute
var state = this.getAttribute("last-selected");
if (state) {
var ids = state.split(" ");
var suppressSelect = this._suppressOnSelect;
this._suppressOnSelect = true;
this.clearSelection();
for (let i = 1; i < ids.length; i++) {
var selectedItem = document.getElementById(ids[i]);
if (selectedItem)
this.addItemToSelection(selectedItem);
}
var currentItem = document.getElementById(ids[0]);
if (!currentItem && this._currentIndex)
currentItem = this.getItemAtIndex(Math.min(
this._currentIndex - 1, this.getRowCount()));
if (currentItem) {
this.currentItem = currentItem;
if (this.selType != "multiple" && this.selectedCount == 0)
this.selectedItem = currentItem;
if (this.scrollBoxObject.height) {
this.ensureElementIsVisible(currentItem);
}
else {
// XXX hack around a bug in ensureElementIsVisible as it will
// scroll beyond the last element, bug 493645.
var previousElement = this.dir == "reverse" ? currentItem.nextSibling :
currentItem.previousSibling;
this.ensureElementIsVisible(previousElement);
}
}
this._suppressOnSelect = suppressSelect;
// XXX actually it's just a refresh, but at least
// the Extensions manager expects this:
this._fireOnSelect();
return;
}
// try to restore the selected items according to their IDs
// (applies after a template rebuild, if last-selected was not set)
if (this.selectedItems) {
let itemIds = [];
for (let i = this.selectedCount - 1; i >= 0; i--) {
let selectedItem = this.selectedItems[i];
itemIds.push(selectedItem.id);
this.selectedItems.remove(selectedItem);
}
for (let i = 0; i < itemIds.length; i++) {
let selectedItem = document.getElementById(itemIds[i]);
if (selectedItem) {
this.selectedItems.append(selectedItem);
}
}
}
if (this.currentItem && this.currentItem.id)
this.currentItem = document.getElementById(this.currentItem.id);
else
this.currentItem = null;
// if we have no previously current item or if the above check fails to
// find the previous nodes (which causes it to clear selection)
if (!this.currentItem && this.selectedCount == 0) {
this.currentIndex = this._currentIndex ? this._currentIndex - 1 : 0;
// cf. listbox constructor:
// select items according to their attributes
var children = this.children;
for (let i = 0; i < children.length; ++i) {
if (children[i].getAttribute("selected") == "true")
this.selectedItems.append(children[i]);
}
}
if (this.selType != "multiple" && this.selectedCount == 0)
this.selectedItem = this.currentItem;
]]>
</body>
</method>
<method name="_isItemVisible">
<parameter name="aItem"/>
<body>
<![CDATA[
if (!aItem)
return false;
var y = this.scrollBoxObject.positionY + this.scrollBoxObject.y;
// Partially visible items are also considered visible
return (aItem.boxObject.y + aItem.boxObject.height > y) &&
(aItem.boxObject.y < y + this.scrollBoxObject.height);
]]>
</body>
</method>
<field name="_currentIndex">null</field>
<!-- For backwards-compatibility and for convenience.
Use getIndexOfItem instead. -->
<method name="getIndexOf">
<parameter name="aElement"/>
<body>
<![CDATA[
return this.getIndexOfItem(aElement);
]]>
</body>
</method>
<!-- For backwards-compatibility and for convenience.
Use ensureElementIsVisible instead -->
<method name="ensureSelectedElementIsVisible">
<body>
<![CDATA[
return this.ensureElementIsVisible(this.selectedItem);
]]>
</body>
</method>
<!-- For backwards-compatibility and for convenience.
Use moveByOffset instead. -->
<method name="goUp">
<body>
<![CDATA[
var index = this.currentIndex;
this.moveByOffset(-1, true, false);
return index != this.currentIndex;
]]>
</body>
</method>
<method name="goDown">
<body>
<![CDATA[
var index = this.currentIndex;
this.moveByOffset(1, true, false);
return index != this.currentIndex;
]]>
</body>
</method>
<!-- deprecated (is implied by currentItem and selectItem) -->
<method name="fireActiveItemEvent"><body/></method>
</implementation>
<handlers>
<handler event="click">
<![CDATA[
// clicking into nothing should unselect
if (event.originalTarget == this._scrollbox) {
this.clearSelection();
this.currentItem = null;
}
]]>
</handler>
<handler event="MozSwipeGesture">
<![CDATA[
// Only handle swipe gestures up and down
switch (event.direction) {
case event.DIRECTION_DOWN:
this._scrollbox.scrollTop = this._scrollbox.scrollHeight;
break;
case event.DIRECTION_UP:
this._scrollbox.scrollTop = 0;
break;
}
]]>
</handler>
</handlers>
</binding>
<binding id="richlistitem"
extends="chrome://global/content/bindings/listbox.xml#listitem">
<content>
<children/>
</content>
<resources>
<stylesheet src="chrome://global/skin/richlistbox.css"/>
</resources>
<implementation>
<destructor>
<![CDATA[
var control = this.control;
if (!control)
return;
// When we are destructed and we are current or selected, unselect ourselves
// so that richlistbox's selection doesn't point to something not in the DOM.
// We don't want to reset last-selected, so we set _suppressOnSelect.
if (this.selected) {
var suppressSelect = control._suppressOnSelect;
control._suppressOnSelect = true;
control.removeItemFromSelection(this);
control._suppressOnSelect = suppressSelect;
}
if (this.current)
control.currentItem = null;
]]>
</destructor>
<property name="label" readonly="true">
<!-- Setter purposely not implemented; the getter returns a
concatentation of label text to expose via accessibility APIs -->
<getter>
<![CDATA[
const XULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
return Array.map(this.getElementsByTagNameNS(XULNS, "label"),
label => label.value)
.join(" ");
]]>
</getter>
</property>
<property name="searchLabel">
<getter>
<![CDATA[
return this.hasAttribute("searchlabel") ?
this.getAttribute("searchlabel") : this.label;
]]>
</getter>
<setter>
<![CDATA[
if (val !== null)
this.setAttribute("searchlabel", val);
else
// fall back to the label property (default value)
this.removeAttribute("searchlabel");
return val;
]]>
</setter>
</property>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,232 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="scaleBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="scalethumb" extends="xul:button" role="xul:thumb">
<resources>
<stylesheet src="chrome://global/skin/scale.css"/>
</resources>
</binding>
<binding id="scaleslider" display="xul:slider"
extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/scale.css"/>
</resources>
</binding>
<binding id="scale" role="xul:scale"
extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/scale.css"/>
</resources>
<content align="center" pack="center">
<xul:slider anonid="slider" class="scale-slider" snap="true" flex="1"
xbl:inherits="disabled,orient,dir,curpos=value,minpos=min,maxpos=max,increment,pageincrement,movetoclick">
<xul:thumb class="scale-thumb" xbl:inherits="disabled,orient"/>
</xul:slider>
</content>
<implementation implements="nsISliderListener">
<property name="value" onget="return this._getIntegerAttribute('curpos', 0);"
onset="return this._setIntegerAttribute('curpos', val);"/>
<property name="min" onget="return this._getIntegerAttribute('minpos', 0);"
onset="return this._setIntegerAttribute('minpos', val);"/>
<property name="max" onget="return this._getIntegerAttribute('maxpos', 100);"
onset="return this._setIntegerAttribute('maxpos', val);"/>
<property name="increment" onget="return this._getIntegerAttribute('increment', 1);"
onset="return this._setIntegerAttribute('increment', val);"/>
<property name="pageIncrement" onget="return this._getIntegerAttribute('pageincrement', 10);"
onset="return this._setIntegerAttribute('pageincrement', val);"/>
<property name="_slider" readonly="true">
<getter>
if (!this._sliderElement)
this._sliderElement = document.getAnonymousElementByAttribute(this, "anonid", "slider");
return this._sliderElement;
</getter>
</property>
<constructor>
<![CDATA[
this._userChanged = false;
var value = parseInt(this.getAttribute("value"), 10);
if (!isNaN(value))
this.value = value;
else if (this.min > 0)
this.value = this.min;
else if (this.max < 0)
this.value = this.max;
]]>
</constructor>
<method name="_getIntegerAttribute">
<parameter name="aAttr"/>
<parameter name="aDefaultValue"/>
<body>
var value = this._slider.getAttribute(aAttr);
var intvalue = parseInt(value, 10);
if (!isNaN(intvalue))
return intvalue;
return aDefaultValue;
</body>
</method>
<method name="_setIntegerAttribute">
<parameter name="aAttr"/>
<parameter name="aValue"/>
<body>
<![CDATA[
var intvalue = parseInt(aValue, 10);
if (!isNaN(intvalue)) {
if (aAttr == "curpos") {
if (intvalue < this.min)
intvalue = this.min;
else if (intvalue > this.max)
intvalue = this.max;
}
this._slider.setAttribute(aAttr, intvalue);
}
return aValue;
]]>
</body>
</method>
<method name="decrease">
<body>
<![CDATA[
var newpos = this.value - this.increment;
var startpos = this.min;
this.value = (newpos > startpos) ? newpos : startpos;
]]>
</body>
</method>
<method name="increase">
<body>
<![CDATA[
var newpos = this.value + this.increment;
var endpos = this.max;
this.value = (newpos < endpos) ? newpos : endpos;
]]>
</body>
</method>
<method name="decreasePage">
<body>
<![CDATA[
var newpos = this.value - this.pageIncrement;
var startpos = this.min;
this.value = (newpos > startpos) ? newpos : startpos;
]]>
</body>
</method>
<method name="increasePage">
<body>
<![CDATA[
var newpos = this.value + this.pageIncrement;
var endpos = this.max;
this.value = (newpos < endpos) ? newpos : endpos;
]]>
</body>
</method>
<method name="valueChanged">
<parameter name="which"/>
<parameter name="newValue"/>
<parameter name="userChanged"/>
<body>
<![CDATA[
switch (which) {
case "curpos":
this.setAttribute("value", newValue);
// in the future, only fire the change event when userChanged
// or _userChanged is true
var changeEvent = document.createEvent("Events");
changeEvent.initEvent("change", true, true);
this.dispatchEvent(changeEvent);
break;
case "minpos":
this.setAttribute("min", newValue);
break;
case "maxpos":
this.setAttribute("max", newValue);
break;
}
]]>
</body>
</method>
<method name="dragStateChanged">
<parameter name="isDragging"/>
<body/>
</method>
</implementation>
<handlers>
<handler event="keypress" keycode="VK_LEFT" preventdefault="true">
<![CDATA[
this._userChanged = true;
(this.orient != "vertical" && this.dir == "reverse") ? this.increase() : this.decrease();
this._userChanged = false;
]]>
</handler>
<handler event="keypress" keycode="VK_RIGHT" preventdefault="true">
<![CDATA[
this._userChanged = true;
(this.orient != "vertical" && this.dir == "reverse") ? this.decrease() : this.increase();
this._userChanged = false;
]]>
</handler>
<handler event="keypress" keycode="VK_UP" preventdefault="true">
<![CDATA[
this._userChanged = true;
(this.orient == "vertical" && this.dir != "reverse") ? this.decrease() : this.increase();
this._userChanged = false;
]]>
</handler>
<handler event="keypress" keycode="VK_DOWN" preventdefault="true">
<![CDATA[
this._userChanged = true;
(this.orient == "vertical" && this.dir != "reverse") ? this.increase() : this.decrease();
this._userChanged = false;
]]>
</handler>
<handler event="keypress" keycode="VK_PAGE_UP" preventdefault="true">
<![CDATA[
this._userChanged = true;
(this.orient == "vertical" && this.dir != "reverse") ? this.decreasePage() : this.increasePage();
this._userChanged = false;
]]>
</handler>
<handler event="keypress" keycode="VK_PAGE_DOWN" preventdefault="true">
<![CDATA[
this._userChanged = true;
(this.orient == "vertical" && this.dir != "reverse") ? this.increasePage() : this.decreasePage();
this._userChanged = false;
]]>
</handler>
<handler event="keypress" keycode="VK_HOME" preventdefault="true">
this._userChanged = true;
this.value = (this.dir == "reverse") ? this.max : this.min;
this._userChanged = false;
</handler>
<handler event="keypress" keycode="VK_END" preventdefault="true">
this._userChanged = true;
this.value = (this.dir == "reverse") ? this.min : this.max;
this._userChanged = false;
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="scrollbarBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="thumb" extends="xul:button" />
<binding id="scrollbar-base" bindToUntrustedContent="true">
<handlers>
<handler event="contextmenu" preventdefault="true" action="event.stopPropagation();"/>
<handler event="click" preventdefault="true" action="event.stopPropagation();"/>
<handler event="dblclick" action="event.stopPropagation();"/>
<handler event="command" action="event.stopPropagation();"/>
</handlers>
</binding>
<binding id="scrollbar" bindToUntrustedContent="true" extends="chrome://global/content/bindings/scrollbar.xml#scrollbar-base">
<content clickthrough="always">
<xul:scrollbarbutton sbattr="scrollbar-up-top" type="decrement" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
<xul:scrollbarbutton sbattr="scrollbar-down-top" type="increment" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
<xul:slider flex="1" xbl:inherits="disabled,curpos,maxpos,pageincrement,increment,orient,sborient=orient">
<xul:thumb sbattr="scrollbar-thumb" xbl:inherits="orient,sborient=orient,collapsed=disabled"
align="center" pack="center"/>
</xul:slider>
<xul:scrollbarbutton sbattr="scrollbar-up-bottom" type="decrement" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
<xul:scrollbarbutton sbattr="scrollbar-down-bottom" type="increment" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,908 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="arrowscrollboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="scrollbox-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/scrollbox.css"/>
</resources>
</binding>
<binding id="scrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
<content>
<xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1">
<children/>
</xul:box>
</content>
</binding>
<binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
<content>
<xul:autorepeatbutton class="autorepeatbutton-up"
anonid="scrollbutton-up"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
oncommand="_autorepeatbuttonScroll(event);"/>
<xul:spacer class="arrowscrollbox-overflow-start-indicator"
xbl:inherits="collapsed=scrolledtostart"/>
<xul:scrollbox class="arrowscrollbox-scrollbox"
anonid="scrollbox"
flex="1"
xbl:inherits="orient,align,pack,dir">
<children/>
</xul:scrollbox>
<xul:spacer class="arrowscrollbox-overflow-end-indicator"
xbl:inherits="collapsed=scrolledtoend"/>
<xul:autorepeatbutton class="autorepeatbutton-down"
anonid="scrollbutton-down"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
oncommand="_autorepeatbuttonScroll(event);"/>
</content>
<implementation>
<constructor><![CDATA[
this.setAttribute("notoverflowing", "true");
this._updateScrollButtonsDisabledState();
]]></constructor>
<destructor><![CDATA[
this._stopSmoothScroll();
]]></destructor>
<field name="_scrollbox">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
</field>
<field name="_scrollButtonUp">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up");
</field>
<field name="_scrollButtonDown">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down");
</field>
<field name="__prefBranch">null</field>
<property name="_prefBranch" readonly="true">
<getter><![CDATA[
if (this.__prefBranch === null) {
this.__prefBranch = Components.classes['@mozilla.org/preferences-service;1']
.getService(Components.interfaces.nsIPrefBranch);
}
return this.__prefBranch;
]]></getter>
</property>
<field name="_scrollIncrement">null</field>
<property name="scrollIncrement" readonly="true">
<getter><![CDATA[
if (this._scrollIncrement === null) {
try {
this._scrollIncrement = this._prefBranch
.getIntPref("toolkit.scrollbox.scrollIncrement");
}
catch (ex) {
this._scrollIncrement = 20;
}
}
return this._scrollIncrement;
]]></getter>
</property>
<field name="_smoothScroll">null</field>
<property name="smoothScroll">
<getter><![CDATA[
if (this._smoothScroll === null) {
if (this.hasAttribute("smoothscroll")) {
this._smoothScroll = (this.getAttribute("smoothscroll") == "true");
} else {
try {
this._smoothScroll = this._prefBranch
.getBoolPref("toolkit.scrollbox.smoothScroll");
}
catch (ex) {
this._smoothScroll = true;
}
}
}
return this._smoothScroll;
]]></getter>
<setter><![CDATA[
this._smoothScroll = val;
return val;
]]></setter>
</property>
<field name="_scrollBoxObject">null</field>
<property name="scrollBoxObject" readonly="true">
<getter><![CDATA[
if (!this._scrollBoxObject) {
this._scrollBoxObject = this._scrollbox.boxObject;
}
return this._scrollBoxObject;
]]></getter>
</property>
<property name="scrollClientRect" readonly="true">
<getter><![CDATA[
return this._scrollbox.getBoundingClientRect();
]]></getter>
</property>
<property name="scrollClientSize" readonly="true">
<getter><![CDATA[
return this.orient == "vertical" ?
this._scrollbox.clientHeight :
this._scrollbox.clientWidth;
]]></getter>
</property>
<property name="scrollSize" readonly="true">
<getter><![CDATA[
return this.orient == "vertical" ?
this._scrollbox.scrollHeight :
this._scrollbox.scrollWidth;
]]></getter>
</property>
<property name="scrollPaddingRect" readonly="true">
<getter><![CDATA[
// This assumes that this._scrollbox doesn't have any border.
var outerRect = this.scrollClientRect;
var innerRect = {};
innerRect.left = outerRect.left - this._scrollbox.scrollLeft;
innerRect.top = outerRect.top - this._scrollbox.scrollTop;
innerRect.right = innerRect.left + this._scrollbox.scrollWidth;
innerRect.bottom = innerRect.top + this._scrollbox.scrollHeight;
return innerRect;
]]></getter>
</property>
<property name="scrollboxPaddingStart" readonly="true">
<getter><![CDATA[
var ltr = (window.getComputedStyle(this, null).direction == "ltr");
var paddingStartName = ltr ? "padding-left" : "padding-right";
var scrollboxStyle = window.getComputedStyle(this._scrollbox, null);
return parseFloat(scrollboxStyle.getPropertyValue(paddingStartName));
]]></getter>
</property>
<property name="scrollPosition">
<getter><![CDATA[
return this.orient == "vertical" ?
this._scrollbox.scrollTop :
this._scrollbox.scrollLeft;
]]></getter>
<setter><![CDATA[
if (this.orient == "vertical")
this._scrollbox.scrollTop = val;
else
this._scrollbox.scrollLeft = val;
return val;
]]></setter>
</property>
<property name="_startEndProps" readonly="true">
<getter><![CDATA[
return this.orient == "vertical" ?
["top", "bottom"] : ["left", "right"];
]]></getter>
</property>
<field name="_isRTLScrollbox"><![CDATA[
this.orient != "vertical" &&
document.defaultView.getComputedStyle(this._scrollbox, "").direction == "rtl";
]]></field>
<field name="_scrollTarget">null</field>
<method name="_canScrollToElement">
<parameter name="element"/>
<body><![CDATA[
return window.getComputedStyle(element).display != "none";
]]></body>
</method>
<method name="ensureElementIsVisible">
<parameter name="element"/>
<parameter name="aSmoothScroll"/>
<body><![CDATA[
if (!this._canScrollToElement(element))
return;
var vertical = this.orient == "vertical";
var rect = this.scrollClientRect;
var containerStart = vertical ? rect.top : rect.left;
var containerEnd = vertical ? rect.bottom : rect.right;
rect = element.getBoundingClientRect();
var elementStart = vertical ? rect.top : rect.left;
var elementEnd = vertical ? rect.bottom : rect.right;
var scrollPaddingRect = this.scrollPaddingRect;
let style = window.getComputedStyle(this._scrollbox, null);
var scrollContentRect = {
left: scrollPaddingRect.left + parseFloat(style.paddingLeft),
top: scrollPaddingRect.top + parseFloat(style.paddingTop),
right: scrollPaddingRect.right - parseFloat(style.paddingRight),
bottom: scrollPaddingRect.bottom - parseFloat(style.paddingBottom)
};
// Provide an entry point for derived bindings to adjust these values.
if (this._adjustElementStartAndEnd) {
[elementStart, elementEnd] =
this._adjustElementStartAndEnd(element, elementStart, elementEnd);
}
if (elementStart <= (vertical ? scrollContentRect.top : scrollContentRect.left)) {
elementStart = vertical ? scrollPaddingRect.top : scrollPaddingRect.left;
}
if (elementEnd >= (vertical ? scrollContentRect.bottom : scrollContentRect.right)) {
elementEnd = vertical ? scrollPaddingRect.bottom : scrollPaddingRect.right;
}
var amountToScroll;
if (elementStart < containerStart) {
amountToScroll = elementStart - containerStart;
} else if (containerEnd < elementEnd) {
amountToScroll = elementEnd - containerEnd;
} else if (this._isScrolling) {
// decelerate if a currently-visible element is selected during the scroll
const STOP_DISTANCE = 15;
if (this._isScrolling == -1 && elementStart - STOP_DISTANCE < containerStart)
amountToScroll = elementStart - containerStart;
else if (this._isScrolling == 1 && containerEnd - STOP_DISTANCE < elementEnd)
amountToScroll = elementEnd - containerEnd;
else
amountToScroll = this._isScrolling * STOP_DISTANCE;
} else {
return;
}
this._stopSmoothScroll();
if (aSmoothScroll != false && this.smoothScroll) {
this._smoothScrollByPixels(amountToScroll, element);
} else {
this.scrollByPixels(amountToScroll);
}
]]></body>
</method>
<method name="_smoothScrollByPixels">
<parameter name="amountToScroll"/>
<parameter name="element"/><!-- optional -->
<body><![CDATA[
this._stopSmoothScroll();
if (amountToScroll == 0)
return;
this._scrollTarget = element;
// Positive amountToScroll makes us scroll right (elements fly left), negative scrolls left.
this._isScrolling = amountToScroll < 0 ? -1 : 1;
this._scrollAnim.start(amountToScroll);
]]></body>
</method>
<field name="_scrollAnim"><![CDATA[({
scrollbox: this,
requestHandle: 0, /* 0 indicates there is no pending request */
start: function scrollAnim_start(distance) {
this.distance = distance;
this.startPos = this.scrollbox.scrollPosition;
this.duration = Math.min(1000, Math.round(50 * Math.sqrt(Math.abs(distance))));
this.startTime = window.performance.now();
if (!this.requestHandle)
this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
},
stop: function scrollAnim_stop() {
window.cancelAnimationFrame(this.requestHandle);
this.requestHandle = 0;
},
sample: function scrollAnim_handleEvent(timeStamp) {
const timePassed = timeStamp - this.startTime;
const pos = timePassed >= this.duration ? 1 :
1 - Math.pow(1 - timePassed / this.duration, 4);
this.scrollbox.scrollPosition = this.startPos + (this.distance * pos);
if (pos == 1)
this.scrollbox._stopSmoothScroll();
else
this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
}
})]]></field>
<method name="scrollByIndex">
<parameter name="index"/>
<parameter name="aSmoothScroll"/>
<body><![CDATA[
if (index == 0)
return;
// Each scrollByIndex call is expected to scroll the given number of
// items. If a previous call is still in progress because of smooth
// scrolling, we need to complete it before starting a new one.
if (this._scrollTarget) {
let elements = this._getScrollableElements();
if (this._scrollTarget != elements[0] &&
this._scrollTarget != elements[elements.length - 1])
this.ensureElementIsVisible(this._scrollTarget, false);
}
var rect = this.scrollClientRect;
var [start, end] = this._startEndProps;
var x = index > 0 ? rect[end] + 1 : rect[start] - 1;
var nextElement = this._elementFromPoint(x, index);
if (!nextElement)
return;
var targetElement;
if (this._isRTLScrollbox)
index *= -1;
while (index < 0 && nextElement) {
if (this._canScrollToElement(nextElement))
targetElement = nextElement;
nextElement = nextElement.previousSibling;
index++;
}
while (index > 0 && nextElement) {
if (this._canScrollToElement(nextElement))
targetElement = nextElement;
nextElement = nextElement.nextSibling;
index--;
}
if (!targetElement)
return;
this.ensureElementIsVisible(targetElement, aSmoothScroll);
]]></body>
</method>
<method name="scrollByPage">
<parameter name="pageDelta"/>
<parameter name="aSmoothScroll"/>
<body><![CDATA[
if (pageDelta == 0)
return;
// If a previous call is still in progress because of smooth
// scrolling, we need to complete it before starting a new one.
if (this._scrollTarget) {
let elements = this._getScrollableElements();
if (this._scrollTarget != elements[0] &&
this._scrollTarget != elements[elements.length - 1])
this.ensureElementIsVisible(this._scrollTarget, false);
}
var [start, end] = this._startEndProps;
var rect = this.scrollClientRect;
var containerEdge = pageDelta > 0 ? rect[end] + 1 : rect[start] - 1;
var pixelDelta = pageDelta * (rect[end] - rect[start]);
var destinationPosition = containerEdge + pixelDelta;
var nextElement = this._elementFromPoint(containerEdge, pageDelta);
if (!nextElement)
return;
// We need to iterate over our elements in the direction of pageDelta.
// pageDelta is the physical direction, so in a horizontal scroll box,
// positive values scroll to the right no matter if the scrollbox is
// LTR or RTL. But RTL changes how we need to advance the iteration
// (whether to get the next or the previous sibling of the current
// element).
var logicalAdvanceDir = pageDelta * (this._isRTLScrollbox ? -1 : 1);
var advance = logicalAdvanceDir > 0 ? (e => e.nextSibling) : (e => e.previousSibling);
var extendsPastTarget = (pageDelta > 0)
? (e => e.getBoundingClientRect()[end] > destinationPosition)
: (e => e.getBoundingClientRect()[start] < destinationPosition);
// We want to scroll to the last element we encounter before we find
// an element which extends past destinationPosition.
var targetElement;
do {
if (this._canScrollToElement(nextElement))
targetElement = nextElement;
nextElement = advance(nextElement);
} while (nextElement && !extendsPastTarget(nextElement));
if (!targetElement)
return;
this.ensureElementIsVisible(targetElement, aSmoothScroll);
]]></body>
</method>
<method name="_getScrollableElements">
<body><![CDATA[
var nodes = this.childNodes;
if (nodes.length == 1 &&
nodes[0].localName == "children" &&
nodes[0].namespaceURI == "http://www.mozilla.org/xbl") {
nodes = document.getBindingParent(this).childNodes;
}
return Array.filter(nodes, this._canScrollToElement, this);
]]></body>
</method>
<method name="_elementFromPoint">
<parameter name="aX"/>
<parameter name="aPhysicalScrollDir"/>
<body><![CDATA[
var elements = this._getScrollableElements();
if (!elements.length)
return null;
if (this._isRTLScrollbox)
elements.reverse();
var [start, end] = this._startEndProps;
var low = 0;
var high = elements.length - 1;
if (aX < elements[low].getBoundingClientRect()[start] ||
aX > elements[high].getBoundingClientRect()[end])
return null;
var mid, rect;
while (low <= high) {
mid = Math.floor((low + high) / 2);
rect = elements[mid].getBoundingClientRect();
if (rect[start] > aX)
high = mid - 1;
else if (rect[end] < aX)
low = mid + 1;
else
return elements[mid];
}
// There's no element at the requested coordinate, but the algorithm
// from above yields an element next to it, in a random direction.
// The desired scrolling direction leads to the correct element.
if (!aPhysicalScrollDir)
return null;
if (aPhysicalScrollDir < 0 && rect[start] > aX)
mid = Math.max(mid - 1, 0);
else if (aPhysicalScrollDir > 0 && rect[end] < aX)
mid = Math.min(mid + 1, elements.length - 1);
return elements[mid];
]]></body>
</method>
<method name="_autorepeatbuttonScroll">
<parameter name="event"/>
<body><![CDATA[
var dir = event.originalTarget == this._scrollButtonUp ? -1 : 1;
if (this._isRTLScrollbox)
dir *= -1;
this.scrollByPixels(this.scrollIncrement * dir);
event.stopPropagation();
]]></body>
</method>
<method name="scrollByPixels">
<parameter name="px"/>
<body><![CDATA[
this.scrollPosition += px;
]]></body>
</method>
<!-- 0: idle
1: scrolling right
-1: scrolling left -->
<field name="_isScrolling">0</field>
<field name="_prevMouseScrolls">[null, null]</field>
<field name="_touchStart">-1</field>
<method name="_stopSmoothScroll">
<body><![CDATA[
if (this._isScrolling) {
this._scrollAnim.stop();
this._isScrolling = 0;
this._scrollTarget = null;
}
]]></body>
</method>
<method name="_updateScrollButtonsDisabledState">
<body><![CDATA[
var scrolledToStart = false;
var scrolledToEnd = false;
if (this.hasAttribute("notoverflowing")) {
scrolledToStart = true;
scrolledToEnd = true;
}
else if (this.scrollPosition == 0) {
// In the RTL case, this means the _last_ element in the
// scrollbox is visible
if (this._isRTLScrollbox)
scrolledToEnd = true;
else
scrolledToStart = true;
}
else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) {
// In the RTL case, this means the _first_ element in the
// scrollbox is visible
if (this._isRTLScrollbox)
scrolledToStart = true;
else
scrolledToEnd = true;
}
if (scrolledToEnd)
this.setAttribute("scrolledtoend", "true");
else
this.removeAttribute("scrolledtoend");
if (scrolledToStart)
this.setAttribute("scrolledtostart", "true");
else
this.removeAttribute("scrolledtostart");
]]></body>
</method>
</implementation>
<handlers>
<handler event="wheel"><![CDATA[
if (this.orient == "vertical") {
if (event.deltaMode == event.DOM_DELTA_PIXEL)
this.scrollByPixels(event.deltaY);
else if (event.deltaMode == event.DOM_DELTA_PAGE)
this.scrollByPage(event.deltaY);
else
this.scrollByIndex(event.deltaY);
}
// We allow vertical scrolling to scroll a horizontal scrollbox
// because many users have a vertical scroll wheel but no
// horizontal support.
// Because of this, we need to avoid scrolling chaos on trackpads
// and mouse wheels that support simultaneous scrolling in both axes.
// We do this by scrolling only when the last two scroll events were
// on the same axis as the current scroll event.
// For diagonal scroll events we only respect the dominant axis.
else {
let isVertical = Math.abs(event.deltaY) > Math.abs(event.deltaX);
let delta = isVertical ? event.deltaY : event.deltaX;
let scrollByDelta = isVertical && this._isRTLScrollbox ? -delta : delta;
if (this._prevMouseScrolls.every(prev => prev == isVertical)) {
if (event.deltaMode == event.DOM_DELTA_PIXEL)
this.scrollByPixels(scrollByDelta);
else if (event.deltaMode == event.DOM_DELTA_PAGE)
this.scrollByPage(scrollByDelta);
else
this.scrollByIndex(scrollByDelta);
}
if (this._prevMouseScrolls.length > 1)
this._prevMouseScrolls.shift();
this._prevMouseScrolls.push(isVertical);
}
event.stopPropagation();
event.preventDefault();
]]></handler>
<handler event="touchstart"><![CDATA[
if (event.touches.length > 1) {
// Multiple touch points detected, abort. In particular this aborts
// the panning gesture when the user puts a second finger down after
// already panning with one finger. Aborting at this point prevents
// the pan gesture from being resumed until all fingers are lifted
// (as opposed to when the user is back down to one finger).
this._touchStart = -1;
} else {
this._touchStart = (this.orient == "vertical"
? event.touches[0].screenY
: event.touches[0].screenX);
}
]]></handler>
<handler event="touchmove"><![CDATA[
if (event.touches.length == 1 &&
this._touchStart >= 0) {
var touchPoint = (this.orient == "vertical"
? event.touches[0].screenY
: event.touches[0].screenX);
var delta = this._touchStart - touchPoint;
if (Math.abs(delta) > 0) {
this.scrollByPixels(delta);
this._touchStart = touchPoint;
}
event.preventDefault();
}
]]></handler>
<handler event="touchend"><![CDATA[
this._touchStart = -1;
]]></handler>
<handler event="underflow" phase="capturing"><![CDATA[
// filter underflow events which were dispatched on nested scrollboxes
if (event.target != this)
return;
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.orient == "vertical") {
if (event.detail == 1)
return;
}
else if (event.detail == 0) {
// horizontal scrollbox
return;
}
this.setAttribute("notoverflowing", "true");
try {
// See bug 341047 and comments in overflow handler as to why
// try..catch is needed here
this._updateScrollButtonsDisabledState();
let childNodes = this._getScrollableElements();
if (childNodes && childNodes.length)
this.ensureElementIsVisible(childNodes[0], false);
}
catch (e) {
this.removeAttribute("notoverflowing");
}
]]></handler>
<handler event="overflow" phase="capturing"><![CDATA[
// filter underflow events which were dispatched on nested scrollboxes
if (event.target != this)
return;
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.orient == "vertical") {
if (event.detail == 1)
return;
}
else if (event.detail == 0) {
// horizontal scrollbox
return;
}
this.removeAttribute("notoverflowing");
try {
// See bug 341047, the overflow event is dispatched when the
// scrollbox already is mostly destroyed. This causes some code in
// _updateScrollButtonsDisabledState() to throw an error. It also
// means that the notoverflowing attribute was removed erroneously,
// as the whole overflow event should not be happening in that case.
this._updateScrollButtonsDisabledState();
}
catch (e) {
this.setAttribute("notoverflowing", "true");
}
]]></handler>
<handler event="scroll" action="this._updateScrollButtonsDisabledState()"/>
</handlers>
</binding>
<binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
<content repeat="hover">
<xul:image class="autorepeatbutton-icon"/>
</content>
</binding>
<binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox">
<content>
<xul:toolbarbutton class="scrollbutton-up"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
anonid="scrollbutton-up"
onclick="_distanceScroll(event);"
onmousedown="if (event.button == 0) _startScroll(-1);"
onmouseup="if (event.button == 0) _stopScroll();"
onmouseover="_continueScroll(-1);"
onmouseout="_pauseScroll();"/>
<xul:spacer class="arrowscrollbox-overflow-start-indicator"
xbl:inherits="collapsed=scrolledtostart"/>
<xul:scrollbox class="arrowscrollbox-scrollbox"
anonid="scrollbox"
flex="1"
xbl:inherits="orient,align,pack,dir">
<children/>
</xul:scrollbox>
<xul:spacer class="arrowscrollbox-overflow-end-indicator"
xbl:inherits="collapsed=scrolledtoend"/>
<xul:toolbarbutton class="scrollbutton-down"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
anonid="scrollbutton-down"
onclick="_distanceScroll(event);"
onmousedown="if (event.button == 0) _startScroll(1);"
onmouseup="if (event.button == 0) _stopScroll();"
onmouseover="_continueScroll(1);"
onmouseout="_pauseScroll();"/>
</content>
<implementation implements="nsITimerCallback, nsIDOMEventListener">
<constructor><![CDATA[
try {
this._scrollDelay = this._prefBranch
.getIntPref("toolkit.scrollbox.clickToScroll.scrollDelay");
}
catch (ex) {
}
]]></constructor>
<destructor><![CDATA[
// Release timer to avoid reference cycles.
if (this._scrollTimer) {
this._scrollTimer.cancel();
this._scrollTimer = null;
}
]]></destructor>
<field name="_scrollIndex">0</field>
<field name="_scrollDelay">150</field>
<method name="notify">
<parameter name="aTimer"/>
<body>
<![CDATA[
if (!document)
aTimer.cancel();
this.scrollByIndex(this._scrollIndex);
]]>
</body>
</method>
<field name="_arrowScrollAnim"><![CDATA[({
scrollbox: this,
requestHandle: 0, /* 0 indicates there is no pending request */
start: function arrowSmoothScroll_start() {
this.lastFrameTime = window.performance.now();
if (!this.requestHandle)
this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
},
stop: function arrowSmoothScroll_stop() {
window.cancelAnimationFrame(this.requestHandle);
this.requestHandle = 0;
},
sample: function arrowSmoothScroll_handleEvent(timeStamp) {
const scrollIndex = this.scrollbox._scrollIndex;
const timePassed = timeStamp - this.lastFrameTime;
this.lastFrameTime = timeStamp;
const scrollDelta = 0.5 * timePassed * scrollIndex;
this.scrollbox.scrollPosition += scrollDelta;
this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
}
})]]></field>
<method name="_startScroll">
<parameter name="index"/>
<body><![CDATA[
if (this._isRTLScrollbox)
index *= -1;
this._scrollIndex = index;
this._mousedown = true;
if (this.smoothScroll) {
this._arrowScrollAnim.start();
return;
}
if (!this._scrollTimer)
this._scrollTimer =
Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
else
this._scrollTimer.cancel();
this._scrollTimer.initWithCallback(this, this._scrollDelay,
this._scrollTimer.TYPE_REPEATING_SLACK);
this.notify(this._scrollTimer);
]]>
</body>
</method>
<method name="_stopScroll">
<body><![CDATA[
if (this._scrollTimer)
this._scrollTimer.cancel();
this._mousedown = false;
if (!this._scrollIndex || !this.smoothScroll)
return;
this.scrollByIndex(this._scrollIndex);
this._scrollIndex = 0;
this._arrowScrollAnim.stop();
]]></body>
</method>
<method name="_pauseScroll">
<body><![CDATA[
if (this._mousedown) {
this._stopScroll();
this._mousedown = true;
document.addEventListener("mouseup", this, false);
document.addEventListener("blur", this, true);
}
]]></body>
</method>
<method name="_continueScroll">
<parameter name="index"/>
<body><![CDATA[
if (this._mousedown)
this._startScroll(index);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.type == "mouseup" ||
aEvent.type == "blur" && aEvent.target == document) {
this._mousedown = false;
document.removeEventListener("mouseup", this, false);
document.removeEventListener("blur", this, true);
}
]]></body>
</method>
<method name="_distanceScroll">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.detail < 2 || aEvent.detail > 3)
return;
var scrollBack = (aEvent.originalTarget == this._scrollButtonUp);
var scrollLeftOrUp = this._isRTLScrollbox ? !scrollBack : scrollBack;
var targetElement;
if (aEvent.detail == 2) {
// scroll by the size of the scrollbox
let [start, end] = this._startEndProps;
let x;
if (scrollLeftOrUp)
x = this.scrollClientRect[start] - this.scrollClientSize;
else
x = this.scrollClientRect[end] + this.scrollClientSize;
targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);
// the next partly-hidden element will become fully visible,
// so don't scroll too far
if (targetElement)
targetElement = scrollBack ?
targetElement.nextSibling :
targetElement.previousSibling;
}
if (!targetElement) {
// scroll to the first resp. last element
let elements = this._getScrollableElements();
targetElement = scrollBack ?
elements[0] :
elements[elements.length - 1];
}
this.ensureElementIsVisible(targetElement);
]]></body>
</method>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<bindings id="spinbuttonsBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="spinbuttons"
extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/spinbuttons.css"/>
</resources>
<content>
<xul:vbox class="spinbuttons-box" flex="1">
<xul:button anonid="increaseButton" type="repeat" flex="1"
class="spinbuttons-button spinbuttons-up"
xbl:inherits="disabled,disabled=increasedisabled"/>
<xul:button anonid="decreaseButton" type="repeat" flex="1"
class="spinbuttons-button spinbuttons-down"
xbl:inherits="disabled,disabled=decreasedisabled"/>
</xul:vbox>
</content>
<implementation>
<property name="_increaseButton" readonly="true">
<getter>
return document.getAnonymousElementByAttribute(this, "anonid", "increaseButton");
</getter>
</property>
<property name="_decreaseButton" readonly="true">
<getter>
return document.getAnonymousElementByAttribute(this, "anonid", "decreaseButton");
</getter>
</property>
<property name="increaseDisabled"
onget="return this._increaseButton.getAttribute('disabled') == 'true';"
onset="if (val) this._increaseButton.setAttribute('disabled', 'true');
else this._increaseButton.removeAttribute('disabled'); return val;"/>
<property name="decreaseDisabled"
onget="return this._decreaseButton.getAttribute('disabled') == 'true';"
onset="if (val) this._decreaseButton.setAttribute('disabled', 'true');
else this._decreaseButton.removeAttribute('disabled'); return val;"/>
</implementation>
<handlers>
<handler event="mousedown">
<![CDATA[
// on the Mac, the native theme draws the spinbutton as a single widget
// so a state attribute is set based on where the mouse button was pressed
if (event.originalTarget == this._increaseButton)
this.setAttribute("state", "up");
else if (event.originalTarget == this._decreaseButton)
this.setAttribute("state", "down");
]]>
</handler>
<handler event="mouseup">
this.removeAttribute("state");
</handler>
<handler event="mouseout">
this.removeAttribute("state");
</handler>
<handler event="command">
<![CDATA[
var eventname;
if (event.originalTarget == this._increaseButton)
eventname = "up";
else if (event.originalTarget == this._decreaseButton)
eventname = "down";
var evt = document.createEvent("Events");
evt.initEvent(eventname, true, true);
var cancel = this.dispatchEvent(evt);
if (this.hasAttribute("on" + eventname)) {
var fn = new Function("event", this.getAttribute("on" + eventname));
if (fn.call(this, event) == false)
cancel = true;
}
return !cancel;
]]>
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,514 @@
/* 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/. */
"use strict";
/*
* The spinner is responsible for displaying the items, and does
* not care what the values represent. The setValue function is called
* when it detects a change in value triggered by scroll event.
* Supports scrolling, clicking on up or down, clicking on item, and
* dragging.
*/
function Spinner(props, context) {
this.context = context;
this._init(props);
}
{
const debug = 0 ? console.log.bind(console, "[spinner]") : function() {};
const ITEM_HEIGHT = 2.5,
VIEWPORT_SIZE = 7,
VIEWPORT_COUNT = 5,
SCROLL_TIMEOUT = 100;
Spinner.prototype = {
/**
* Initializes a spinner. Set the default states and properties, cache
* element references, create the HTML markup, and add event listeners.
*
* @param {Object} props [Properties passed in from parent]
* {
* {Function} setValue: Takes a value and set the state to
* the parent component.
* {Function} getDisplayString: Takes a value, and output it
* as localized strings.
* {Number} viewportSize [optional]: Number of items in a
* viewport.
* {Boolean} hideButtons [optional]: Hide up & down buttons
* {Number} rootFontSize [optional]: Used to support zoom in/out
* }
*/
_init(props) {
const { setValue, getDisplayString, hideButtons, rootFontSize = 10 } = props;
const spinnerTemplate = document.getElementById("spinner-template");
const spinnerElement = document.importNode(spinnerTemplate.content, true);
// Make sure viewportSize is an odd number because we want to have the selected
// item in the center. If it's an even number, use the default size instead.
const viewportSize = props.viewportSize % 2 ? props.viewportSize : VIEWPORT_SIZE;
this.state = {
items: [],
isScrolling: false
};
this.props = {
setValue, getDisplayString, viewportSize, rootFontSize,
// We can assume that the viewportSize is an odd number. Calculate how many
// items we need to insert on top of the spinner so that the selected is at
// the center. Ex: if viewportSize is 5, we need 2 items on top.
viewportTopOffset: (viewportSize - 1) / 2
};
this.elements = {
container: spinnerElement.querySelector(".spinner-container"),
spinner: spinnerElement.querySelector(".spinner"),
up: spinnerElement.querySelector(".up"),
down: spinnerElement.querySelector(".down"),
itemsViewElements: []
};
this.elements.spinner.style.height = (ITEM_HEIGHT * viewportSize) + "rem";
if (hideButtons) {
this.elements.container.classList.add("hide-buttons");
}
this.context.appendChild(spinnerElement);
this._attachEventListeners();
},
/**
* Only the parent component calls setState on the spinner.
* It checks if the items have changed and updates the spinner.
* If only the value has changed, smooth scrolls to the new value.
*
* @param {Object} newState [The new spinner state]
* {
* {Number/String} value: The centered value
* {Array} items: The list of items for display
* {Boolean} isInfiniteScroll: Whether or not the spinner should
* have infinite scroll capability
* {Boolean} isValueSet: true if user has selected a value
* }
*/
setState(newState) {
const { spinner } = this.elements;
const { value, items } = this.state;
const { value: newValue, items: newItems, isValueSet, isInvalid } = newState;
if (this._isArrayDiff(newItems, items)) {
this.state = Object.assign(this.state, newState);
this._updateItems();
this._scrollTo(newValue, true);
} else if (newValue != value) {
this.state = Object.assign(this.state, newState);
this._smoothScrollTo(newValue);
}
if (isValueSet) {
if (isInvalid) {
this._removeSelection();
} else {
this._updateSelection();
}
}
},
/**
* Whenever scroll event is detected:
* - Update the index state
* - If a smooth scroll has reached its destination, set [isScrolling] state
* to false
* - If the value has changed, update the [value] state and call [setValue]
* - If infinite scrolling is on, reset the scrolling position if necessary
*/
_onScroll() {
const { items, itemsView, isInfiniteScroll } = this.state;
const { viewportSize, viewportTopOffset } = this.props;
const { spinner, itemsViewElements } = this.elements;
this.state.index = this._getIndexByOffset(spinner.scrollTop);
const value = itemsView[this.state.index + viewportTopOffset].value;
// Check if smooth scrolling has reached its destination.
// This prevents input box jump when input box changes values.
if (this.state.value == value && this.state.isScrolling) {
this.state.isScrolling = false;
}
// Call setValue if value has changed, and is not smooth scrolling
if (this.state.value != value && !this.state.isScrolling) {
this.state.value = value;
this.props.setValue(value);
}
// Do infinite scroll when items length is bigger or equal to viewport
// and isInfiniteScroll is not false.
if (items.length >= viewportSize && isInfiniteScroll) {
// If the scroll position is near the top or bottom, jump back to the middle
// so user can keep scrolling up or down.
if (this.state.index < viewportSize ||
this.state.index > itemsView.length - viewportSize) {
this._scrollTo(this.state.value, true);
}
}
// Use a timer to detect if a scroll event has not fired within some time
// (defined in SCROLL_TIMEOUT). This is required because we need to hide
// highlight and hover state when user is scrolling.
clearTimeout(this.state.scrollTimer);
this.elements.spinner.classList.add("scrolling");
this.state.scrollTimer = setTimeout(() => {
this.elements.spinner.classList.remove("scrolling");
this.elements.spinner.dispatchEvent(new CustomEvent("ScrollStop"));
}, SCROLL_TIMEOUT);
},
/**
* Updates the spinner items to the current states.
*/
_updateItems() {
const { viewportSize, viewportTopOffset } = this.props;
const { items, isInfiniteScroll } = this.state;
// Prepends null elements so the selected value is centered in spinner
let itemsView = new Array(viewportTopOffset).fill({}).concat(items);
if (items.length >= viewportSize && isInfiniteScroll) {
// To achieve infinite scroll, we move the scroll position back to the
// center when it is near the top or bottom. The scroll momentum could
// be lost in the process, so to minimize that, we need at least 2 sets
// of items to act as buffer: one for the top and one for the bottom.
// But if the number of items is small ( < viewportSize * viewport count)
// we should add more sets.
let count = Math.ceil(viewportSize * VIEWPORT_COUNT / items.length) * 2;
for (let i = 0; i < count; i += 1) {
itemsView.push(...items);
}
}
// Reuse existing DOM nodes when possible. Create or remove
// nodes based on how big itemsView is.
this._prepareNodes(itemsView.length, this.elements.spinner);
// Once DOM nodes are ready, set display strings using textContent
this._setDisplayStringAndClass(itemsView, this.elements.itemsViewElements);
this.state.itemsView = itemsView;
},
/**
* Make sure the number or child elements is the same as length
* and keep the elements' references for updating textContent
*
* @param {Number} length [The number of child elements]
* @param {DOMElement} parent [The parent element reference]
*/
_prepareNodes(length, parent) {
const diff = length - parent.childElementCount;
if (!diff) {
return;
}
if (diff > 0) {
// Add more elements if length is greater than current
let frag = document.createDocumentFragment();
// Remove margin bottom on the last element before appending
if (parent.lastChild) {
parent.lastChild.style.marginBottom = "";
}
for (let i = 0; i < diff; i++) {
let el = document.createElement("div");
frag.appendChild(el);
this.elements.itemsViewElements.push(el);
}
parent.appendChild(frag);
} else if (diff < 0) {
// Remove elements if length is less than current
for (let i = 0; i < Math.abs(diff); i++) {
parent.removeChild(parent.lastChild);
}
this.elements.itemsViewElements.splice(diff);
}
parent.lastChild.style.marginBottom =
(ITEM_HEIGHT * this.props.viewportTopOffset) + "rem";
},
/**
* Set the display string and class name to the elements.
*
* @param {Array<Object>} items
* [{
* {Number/String} value: The value in its original form
* {Boolean} enabled: Whether or not the item is enabled
* }]
* @param {Array<DOMElement>} elements
*/
_setDisplayStringAndClass(items, elements) {
const { getDisplayString } = this.props;
items.forEach((item, index) => {
elements[index].textContent =
item.value != undefined ? getDisplayString(item.value) : "";
elements[index].className = item.enabled ? "" : "disabled";
});
},
/**
* Attach event listeners to the spinner and buttons.
*/
_attachEventListeners() {
const { spinner } = this.elements;
spinner.addEventListener("scroll", this, { passive: true });
document.addEventListener("mouseup", this, { passive: true });
document.addEventListener("mousedown", this);
},
/**
* Handle events
* @param {DOMEvent} event
*/
handleEvent(event) {
const { mouseState = {}, index, itemsView } = this.state;
const { viewportTopOffset, setValue } = this.props;
const { spinner, up, down } = this.elements;
switch (event.type) {
case "scroll": {
this._onScroll();
break;
}
case "mousedown": {
// Use preventDefault to keep focus on input boxes
event.preventDefault();
event.target.setCapture();
this.state.mouseState = {
down: true,
layerX: event.layerX,
layerY: event.layerY
};
if (event.target == up) {
// An "active" class is needed to simulate :active pseudo-class
// because element is not focused.
event.target.classList.add("active");
this._smoothScrollToIndex(index + 1);
}
if (event.target == down) {
event.target.classList.add("active");
this._smoothScrollToIndex(index - 1);
}
if (event.target.parentNode == spinner) {
// Listen to dragging events
spinner.addEventListener("mousemove", this, { passive: true });
spinner.addEventListener("mouseleave", this, { passive: true });
}
break;
}
case "mouseup": {
this.state.mouseState.down = false;
if (event.target == up || event.target == down) {
event.target.classList.remove("active");
}
if (event.target.parentNode == spinner) {
// Check if user clicks or drags, scroll to the item if clicked,
// otherwise get the current index and smooth scroll there.
if (event.layerX == mouseState.layerX && event.layerY == mouseState.layerY) {
const newIndex = this._getIndexByOffset(event.target.offsetTop) - viewportTopOffset;
if (index == newIndex) {
// Set value manually if the clicked element is already centered.
// This happens when the picker first opens, and user pick the
// default value.
setValue(itemsView[index + viewportTopOffset].value);
} else {
this._smoothScrollToIndex(newIndex);
}
} else {
this._smoothScrollToIndex(this._getIndexByOffset(spinner.scrollTop));
}
// Stop listening to dragging
spinner.removeEventListener("mousemove", this, { passive: true });
spinner.removeEventListener("mouseleave", this, { passive: true });
}
break;
}
case "mouseleave": {
if (event.target == spinner) {
// Stop listening to drag event if mouse is out of the spinner
this._smoothScrollToIndex(this._getIndexByOffset(spinner.scrollTop));
spinner.removeEventListener("mousemove", this, { passive: true });
spinner.removeEventListener("mouseleave", this, { passive: true });
}
break;
}
case "mousemove": {
// Change spinner position on drag
spinner.scrollTop -= event.movementY;
break;
}
}
},
/**
* Find the index by offset
* @param {Number} offset: Offset value in pixel.
* @return {Number} Index number
*/
_getIndexByOffset(offset) {
return Math.round(offset / (ITEM_HEIGHT * this.props.rootFontSize));
},
/**
* Find the index of a value that is the closest to the current position.
* If centering is true, find the index closest to the center.
*
* @param {Number/String} value: The value to find
* @param {Boolean} centering: Whether or not to find the value closest to center
* @return {Number} index of the value, returns -1 if value is not found
*/
_getScrollIndex(value, centering) {
const { itemsView } = this.state;
const { viewportTopOffset } = this.props;
// If index doesn't exist, or centering is true, start from the middle point
let currentIndex = centering || (this.state.index == undefined) ?
Math.round((itemsView.length - viewportTopOffset) / 2) :
this.state.index;
let closestIndex = itemsView.length;
let indexes = [];
let diff = closestIndex;
let isValueFound = false;
// Find indexes of items match the value
itemsView.forEach((item, index) => {
if (item.value == value) {
indexes.push(index);
}
});
// Find the index closest to currentIndex
indexes.forEach(index => {
let d = Math.abs(index - currentIndex);
if (d < diff) {
diff = d;
closestIndex = index;
isValueFound = true;
}
});
return isValueFound ? (closestIndex - viewportTopOffset) : -1;
},
/**
* Scroll to a value.
*
* @param {Number/String} value: Value to scroll to
* @param {Boolean} centering: Whether or not to scroll to center location
*/
_scrollTo(value, centering) {
const index = this._getScrollIndex(value, centering);
// Do nothing if the value is not found
if (index > -1) {
this.state.index = index;
this.elements.spinner.scrollTop = this.state.index * ITEM_HEIGHT * this.props.rootFontSize;
}
},
/**
* Smooth scroll to a value.
*
* @param {Number/String} value: Value to scroll to
*/
_smoothScrollTo(value) {
const index = this._getScrollIndex(value);
// Do nothing if the value is not found
if (index > -1) {
this.state.index = index;
this._smoothScrollToIndex(this.state.index);
}
},
/**
* Smooth scroll to a value based on the index
*
* @param {Number} index: Index number
*/
_smoothScrollToIndex(index) {
const element = this.elements.spinner.children[index];
if (element) {
// Set the isScrolling flag before smooth scrolling begins
// and remove it when it has reached the destination.
// This prevents input box jump when input box changes values
this.state.isScrolling = true;
element.scrollIntoView({
behavior: "smooth", block: "start"
});
}
},
/**
* Update the selection state.
*/
_updateSelection() {
const { itemsViewElements, selected } = this.elements;
const { itemsView, index } = this.state;
const { viewportTopOffset } = this.props;
const currentItemIndex = index + viewportTopOffset;
if (selected && selected != itemsViewElements[currentItemIndex]) {
this._removeSelection();
}
this.elements.selected = itemsViewElements[currentItemIndex];
if (itemsView[currentItemIndex] && itemsView[currentItemIndex].enabled) {
this.elements.selected.classList.add("selection");
}
},
/**
* Remove selection if selected exists and different from current
*/
_removeSelection() {
const { selected } = this.elements;
if (selected) {
selected.classList.remove("selection");
}
},
/**
* Compares arrays of objects. It assumes the structure is an array of
* objects, and objects in a and b have the same number of properties.
*
* @param {Array<Object>} a
* @param {Array<Object>} b
* @return {Boolean} Returns true if a and b are different
*/
_isArrayDiff(a, b) {
// Check reference first, exit early if reference is the same.
if (a == b) {
return false;
}
if (a.length != b.length) {
return true;
}
for (let i = 0; i < a.length; i++) {
for (let prop in a[i]) {
if (a[i][prop] != b[i][prop]) {
return true;
}
}
}
return false;
}
};
}

View file

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="splitterBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="splitter" extends="xul:splitter">
<resources>
<stylesheet src="chrome://global/skin/splitter.css"/>
</resources>
</binding>
<binding id="grippy" extends="xul:button">
<resources>
<stylesheet src="chrome://global/skin/splitter.css"/>
</resources>
<handlers>
<handler event="command">
<![CDATA[
var splitter = this.parentNode;
if (splitter) {
var state = splitter.getAttribute("state");
if (state == "collapsed")
splitter.setAttribute("state", "open");
else
splitter.setAttribute("state", "collapsed");
}
]]>
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,96 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="stringBundleBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="stringbundleset" extends="xul:box"/>
<binding id="stringbundle" extends="xul:spacer">
<implementation name="XStringBundle">
<method name="getString">
<parameter name="aStringKey"/>
<body>
<![CDATA[
try {
return this.stringBundle.GetStringFromName(aStringKey);
}
catch (e) {
dump("*** Failed to get string " + aStringKey + " in bundle: " + this.src + "\n");
throw e;
}
]]>
</body>
</method>
<method name="getFormattedString">
<parameter name="aStringKey"/>
<parameter name="aStringsArray"/>
<body>
<![CDATA[
try {
return this.stringBundle.formatStringFromName(aStringKey, aStringsArray, aStringsArray.length);
}
catch (e) {
dump("*** Failed to format string " + aStringKey + " in bundle: " + this.src + "\n");
throw e;
}
]]>
</body>
</method>
<property name="stringBundle" readonly="true">
<getter>
<![CDATA[
if (!this._bundle) {
try {
this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService)
.createBundle(this.src);
}
catch (e) {
dump("Failed to get stringbundle:\n");
dump(e + "\n");
}
}
return this._bundle;
]]>
</getter>
</property>
<property name="src">
<getter>
<![CDATA[
return this.getAttribute("src");
]]>
</getter>
<setter>
<![CDATA[
this._bundle = null;
this.setAttribute("src", val);
return val;
]]>
</setter>
</property>
<property name="strings">
<getter>
<![CDATA[
// Note: this is a sucky method name! Should be:
// readonly attribute nsISimpleEnumerator strings;
return this.stringBundle.getSimpleEnumeration();
]]>
</getter>
</property>
<field name="_bundle">null</field>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,892 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="tabBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="tab-base">
<resources>
<stylesheet src="chrome://global/skin/tabbox.css"/>
</resources>
</binding>
<binding id="tabbox"
extends="chrome://global/content/bindings/tabbox.xml#tab-base">
<implementation implements="nsIDOMEventListener">
<property name="handleCtrlTab">
<setter>
<![CDATA[
this.setAttribute("handleCtrlTab", val);
return val;
]]>
</setter>
<getter>
<![CDATA[
return (this.getAttribute("handleCtrlTab") != "false");
]]>
</getter>
</property>
<property name="handleCtrlPageUpDown">
<setter>
<![CDATA[
this.setAttribute("handleCtrlPageUpDown", val);
return val;
]]>
</setter>
<getter>
<![CDATA[
return (this.getAttribute("handleCtrlPageUpDown") != "false");
]]>
</getter>
</property>
<field name="_handleMetaAltArrows" readonly="true">
/Mac/.test(navigator.platform)
</field>
<!-- _tabs and _tabpanels are deprecated, they exist only for
backwards compatibility. -->
<property name="_tabs" readonly="true" onget="return this.tabs;"/>
<property name="_tabpanels" readonly="true" onget="return this.tabpanels;"/>
<property name="tabs" readonly="true">
<getter>
<![CDATA[
return this.getElementsByTagNameNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tabs").item(0);
]]>
</getter>
</property>
<property name="tabpanels" readonly="true">
<getter>
<![CDATA[
return this.getElementsByTagNameNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tabpanels").item(0);
]]>
</getter>
</property>
<property name="selectedIndex">
<getter>
<![CDATA[
var tabs = this.tabs;
return tabs ? tabs.selectedIndex : -1;
]]>
</getter>
<setter>
<![CDATA[
var tabs = this.tabs;
if (tabs)
tabs.selectedIndex = val;
this.setAttribute("selectedIndex", val);
return val;
]]>
</setter>
</property>
<property name="selectedTab">
<getter>
<![CDATA[
var tabs = this.tabs;
return tabs && tabs.selectedItem;
]]>
</getter>
<setter>
<![CDATA[
if (val) {
var tabs = this.tabs;
if (tabs)
tabs.selectedItem = val;
}
return val;
]]>
</setter>
</property>
<property name="selectedPanel">
<getter>
<![CDATA[
var tabpanels = this.tabpanels;
return tabpanels && tabpanels.selectedPanel;
]]>
</getter>
<setter>
<![CDATA[
if (val) {
var tabpanels = this.tabpanels;
if (tabpanels)
tabpanels.selectedPanel = val;
}
return val;
]]>
</setter>
</property>
<method name="handleEvent">
<parameter name="event"/>
<body>
<![CDATA[
if (!event.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
// Don't check if the event was already consumed because tab
// navigation should always work for better user experience.
switch (event.keyCode) {
case event.DOM_VK_TAB:
if (event.ctrlKey && !event.altKey && !event.metaKey)
if (this.tabs && this.handleCtrlTab) {
this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
event.preventDefault();
}
break;
case event.DOM_VK_PAGE_UP:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
if (this.tabs && this.handleCtrlPageUpDown) {
this.tabs.advanceSelectedTab(-1, true);
event.preventDefault();
}
break;
case event.DOM_VK_PAGE_DOWN:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
if (this.tabs && this.handleCtrlPageUpDown) {
this.tabs.advanceSelectedTab(1, true);
event.preventDefault();
}
break;
case event.DOM_VK_LEFT:
if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
if (this.tabs && this._handleMetaAltArrows) {
var offset = window.getComputedStyle(this, "")
.direction == "ltr" ? -1 : 1;
this.tabs.advanceSelectedTab(offset, true);
event.preventDefault();
}
break;
case event.DOM_VK_RIGHT:
if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
if (this.tabs && this._handleMetaAltArrows) {
offset = window.getComputedStyle(this, "")
.direction == "ltr" ? 1 : -1;
this.tabs.advanceSelectedTab(offset, true);
event.preventDefault();
}
break;
}
]]>
</body>
</method>
<field name="_eventNode">this</field>
<property name="eventNode" onget="return this._eventNode;">
<setter>
<![CDATA[
if (val != this._eventNode) {
const nsIEventListenerService =
Components.interfaces.nsIEventListenerService;
let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
.getService(nsIEventListenerService);
els.addSystemEventListener(val, "keydown", this, false);
els.removeSystemEventListener(this._eventNode, "keydown", this, false);
this._eventNode = val;
}
return val;
]]>
</setter>
</property>
<constructor>
switch (this.getAttribute("eventnode")) {
case "parent": this._eventNode = this.parentNode; break;
case "window": this._eventNode = window; break;
case "document": this._eventNode = document; break;
}
const nsIEventListenerService =
Components.interfaces.nsIEventListenerService;
let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
.getService(nsIEventListenerService);
els.addSystemEventListener(this._eventNode, "keydown", this, false);
</constructor>
<destructor>
const nsIEventListenerService =
Components.interfaces.nsIEventListenerService;
let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
.getService(nsIEventListenerService);
els.removeSystemEventListener(this._eventNode, "keydown", this, false);
</destructor>
</implementation>
</binding>
<binding id="tabs" role="xul:tabs"
extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/tabbox.css"/>
</resources>
<content>
<xul:spacer class="tabs-left"/>
<children/>
<xul:spacer class="tabs-right" flex="1"/>
</content>
<implementation implements="nsIDOMXULSelectControlElement, nsIDOMXULRelatedElement">
<constructor>
<![CDATA[
// first and last tabs need to be able to have unique styles
// and also need to select first tab on startup.
if (this.firstChild)
this.firstChild.setAttribute("first-tab", "true");
if (this.lastChild)
this.lastChild.setAttribute("last-tab", "true");
if (!this.hasAttribute("orient"))
this.setAttribute("orient", "horizontal");
if (this.tabbox && this.tabbox.hasAttribute("selectedIndex")) {
let selectedIndex = parseInt(this.tabbox.getAttribute("selectedIndex"));
this.selectedIndex = selectedIndex > 0 ? selectedIndex : 0;
return;
}
var children = this.childNodes;
var length = children.length;
for (var i = 0; i < length; i++) {
if (children[i].getAttribute("selected") == "true") {
this.selectedIndex = i;
return;
}
}
var value = this.value;
if (value)
this.value = value;
else
this.selectedIndex = 0;
]]>
</constructor>
<!-- nsIDOMXULRelatedElement -->
<method name="getRelatedElement">
<parameter name="aTabElm"/>
<body>
<![CDATA[
if (!aTabElm)
return null;
let tabboxElm = this.tabbox;
if (!tabboxElm)
return null;
let tabpanelsElm = tabboxElm.tabpanels;
if (!tabpanelsElm)
return null;
// Get linked tab panel by 'linkedpanel' attribute on the given tab
// element.
let linkedPanelElm = null;
let linkedPanelId = aTabElm.linkedPanel;
if (linkedPanelId) {
let ownerDoc = this.ownerDocument;
// XXX bug 565858: if XUL tab element is anonymous element then
// suppose linked tab panel is hosted within the same XBL binding
// and search it by ID attribute inside an anonymous content of
// the binding. This is not robust assumption since tab elements may
// live outside a tabbox element so that for example tab elements
// can be explicit content but tab panels can be anonymous.
let bindingParent = ownerDoc.getBindingParent(aTabElm);
if (bindingParent)
return ownerDoc.getAnonymousElementByAttribute(bindingParent,
"id",
linkedPanelId);
return ownerDoc.getElementById(linkedPanelId);
}
// otherwise linked tabpanel element has the same index as the given
// tab element.
let tabElmIdx = this.getIndexOfItem(aTabElm);
return tabpanelsElm.childNodes[tabElmIdx];
]]>
</body>
</method>
<!-- nsIDOMXULSelectControlElement -->
<property name="itemCount" readonly="true"
onget="return this.childNodes.length"/>
<property name="value" onget="return this.getAttribute('value');">
<setter>
<![CDATA[
this.setAttribute("value", val);
var children = this.childNodes;
for (var c = children.length - 1; c >= 0; c--) {
if (children[c].value == val) {
this.selectedIndex = c;
break;
}
}
return val;
]]>
</setter>
</property>
<field name="_tabbox">null</field>
<property name="tabbox" readonly="true">
<getter><![CDATA[
// Memoize the result in a field rather than replacing this property,
// so that it can be reset along with the binding.
if (this._tabbox) {
return this._tabbox;
}
let parent = this.parentNode;
while (parent) {
if (parent.localName == "tabbox") {
break;
}
parent = parent.parentNode;
}
return this._tabbox = parent;
]]></getter>
</property>
<!-- _tabbox is deprecated, it exists only for backwards compatibility. -->
<field name="_tabbox" readonly="true"><![CDATA[
this.tabbox;
]]></field>
<property name="selectedIndex">
<getter>
<![CDATA[
const tabs = this.childNodes;
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].selected)
return i;
}
return -1;
]]>
</getter>
<setter>
<![CDATA[
var tab = this.getItemAtIndex(val);
if (tab) {
var alreadySelected = tab.selected;
Array.forEach(this.childNodes, function (aTab) {
if (aTab.selected && aTab != tab)
aTab._selected = false;
});
tab._selected = true;
this.setAttribute("value", tab.value);
let linkedPanel = this.getRelatedElement(tab);
if (linkedPanel) {
this.tabbox.setAttribute("selectedIndex", val);
// This will cause an onselect event to fire for the tabpanel
// element.
this.tabbox.tabpanels.selectedPanel = linkedPanel;
}
if (!alreadySelected) {
// Fire an onselect event for the tabs element.
var event = document.createEvent('Events');
event.initEvent('select', true, true);
this.dispatchEvent(event);
}
}
return val;
]]>
</setter>
</property>
<property name="selectedItem">
<getter>
<![CDATA[
const tabs = this.childNodes;
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].selected)
return tabs[i];
}
return null;
]]>
</getter>
<setter>
<![CDATA[
if (val && !val.selected)
// The selectedIndex setter ignores invalid values
// such as -1 if |val| isn't one of our child nodes.
this.selectedIndex = this.getIndexOfItem(val);
return val;
]]>
</setter>
</property>
<method name="getIndexOfItem">
<parameter name="item"/>
<body>
<![CDATA[
return Array.indexOf(this.childNodes, item);
]]>
</body>
</method>
<method name="getItemAtIndex">
<parameter name="index"/>
<body>
<![CDATA[
return this.childNodes.item(index);
]]>
</body>
</method>
<method name="_selectNewTab">
<parameter name="aNewTab"/>
<parameter name="aFallbackDir"/>
<parameter name="aWrap"/>
<body>
<![CDATA[
var requestedTab = aNewTab;
while (aNewTab.hidden || aNewTab.disabled || !this._canAdvanceToTab(aNewTab)) {
aNewTab = aFallbackDir == -1 ? aNewTab.previousSibling : aNewTab.nextSibling;
if (!aNewTab && aWrap)
aNewTab = aFallbackDir == -1 ? this.childNodes[this.childNodes.length - 1] :
this.childNodes[0];
if (!aNewTab || aNewTab == requestedTab)
return;
}
var isTabFocused = false;
try {
isTabFocused =
(document.commandDispatcher.focusedElement == this.selectedItem);
} catch (e) {}
this.selectedItem = aNewTab;
if (isTabFocused) {
aNewTab.focus();
}
else if (this.getAttribute("setfocus") != "false") {
let selectedPanel = this.tabbox.selectedPanel;
document.commandDispatcher.advanceFocusIntoSubtree(selectedPanel);
// Make sure that the focus doesn't move outside the tabbox
if (this.tabbox) {
try {
let el = document.commandDispatcher.focusedElement;
while (el && el != this.tabbox.tabpanels) {
if (el == this.tabbox || el == selectedPanel)
return;
el = el.parentNode;
}
aNewTab.focus();
} catch (e) {
}
}
}
]]>
</body>
</method>
<method name="_canAdvanceToTab">
<parameter name="aTab"/>
<body>
<![CDATA[
return true;
]]>
</body>
</method>
<method name="advanceSelectedTab">
<parameter name="aDir"/>
<parameter name="aWrap"/>
<body>
<![CDATA[
var startTab = this.selectedItem;
var next = startTab[aDir == -1 ? "previousSibling" : "nextSibling"];
if (!next && aWrap) {
next = aDir == -1 ? this.childNodes[this.childNodes.length - 1] :
this.childNodes[0];
}
if (next && next != startTab) {
this._selectNewTab(next, aDir, aWrap);
}
]]>
</body>
</method>
<method name="appendItem">
<parameter name="label"/>
<parameter name="value"/>
<body>
<![CDATA[
var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var tab = document.createElementNS(XULNS, "tab");
tab.setAttribute("label", label);
tab.setAttribute("value", value);
this.appendChild(tab);
return tab;
]]>
</body>
</method>
<method name="insertItemAt">
<parameter name="index"/>
<parameter name="label"/>
<parameter name="value"/>
<body>
<![CDATA[
var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var tab = document.createElementNS(XULNS, "tab");
tab.setAttribute("label", label);
tab.setAttribute("value", value);
var before = this.getItemAtIndex(index);
if (before)
this.insertBefore(tab, before);
else
this.appendChild(tab);
return tab;
]]>
</body>
</method>
<method name="removeItemAt">
<parameter name="index"/>
<body>
<![CDATA[
var remove = this.getItemAtIndex(index);
if (remove)
this.removeChild(remove);
return remove;
]]>
</body>
</method>
</implementation>
#ifdef MOZ_WIDGET_GTK
<handlers>
<handler event="DOMMouseScroll">
<![CDATA[
if (event.detail > 0)
this.advanceSelectedTab(1, false);
else
this.advanceSelectedTab(-1, false);
event.stopPropagation();
]]>
</handler>
</handlers>
#endif
</binding>
<binding id="tabpanels" role="xul:tabpanels"
extends="chrome://global/content/bindings/tabbox.xml#tab-base">
<implementation implements="nsIDOMXULRelatedElement">
<!-- nsIDOMXULRelatedElement -->
<method name="getRelatedElement">
<parameter name="aTabPanelElm"/>
<body>
<![CDATA[
if (!aTabPanelElm)
return null;
let tabboxElm = this.tabbox;
if (!tabboxElm)
return null;
let tabsElm = tabboxElm.tabs;
if (!tabsElm)
return null;
// Return tab element having 'linkedpanel' attribute equal to the id
// of the tab panel or the same index as the tab panel element.
let tabpanelIdx = Array.indexOf(this.childNodes, aTabPanelElm);
if (tabpanelIdx == -1)
return null;
let tabElms = tabsElm.childNodes;
let tabElmFromIndex = tabElms[tabpanelIdx];
let tabpanelId = aTabPanelElm.id;
if (tabpanelId) {
for (let idx = 0; idx < tabElms.length; idx++) {
var tabElm = tabElms[idx];
if (tabElm.linkedPanel == tabpanelId)
return tabElm;
}
}
return tabElmFromIndex;
]]>
</body>
</method>
<!-- public -->
<field name="_tabbox">null</field>
<property name="tabbox" readonly="true">
<getter><![CDATA[
// Memoize the result in a field rather than replacing this property,
// so that it can be reset along with the binding.
if (this._tabbox) {
return this._tabbox;
}
let parent = this.parentNode;
while (parent) {
if (parent.localName == "tabbox") {
break;
}
parent = parent.parentNode;
}
return this._tabbox = parent;
]]></getter>
</property>
<field name="_selectedPanel">this.childNodes.item(this.selectedIndex)</field>
<property name="selectedIndex">
<getter>
<![CDATA[
var indexStr = this.getAttribute("selectedIndex");
return indexStr ? parseInt(indexStr) : -1;
]]>
</getter>
<setter>
<![CDATA[
if (val < 0 || val >= this.childNodes.length)
return val;
var panel = this._selectedPanel;
this._selectedPanel = this.childNodes[val];
this.setAttribute("selectedIndex", val);
if (this._selectedPanel != panel) {
var event = document.createEvent("Events");
event.initEvent("select", true, true);
this.dispatchEvent(event);
}
return val;
]]>
</setter>
</property>
<property name="selectedPanel">
<getter>
<![CDATA[
return this._selectedPanel;
]]>
</getter>
<setter>
<![CDATA[
var selectedIndex = -1;
for (var panel = val; panel != null; panel = panel.previousSibling)
++selectedIndex;
this.selectedIndex = selectedIndex;
return val;
]]>
</setter>
</property>
</implementation>
</binding>
<binding id="tab" display="xul:button" role="xul:tab"
extends="chrome://global/content/bindings/general.xml#control-item">
<resources>
<stylesheet src="chrome://global/skin/tabbox.css"/>
</resources>
<content>
<xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1">
<xul:image class="tab-icon"
xbl:inherits="validate,src=image"
role="presentation"/>
<xul:label class="tab-text"
xbl:inherits="value=label,accesskey,crop,disabled"
flex="1"
role="presentation"/>
</xul:hbox>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<property name="control" readonly="true">
<getter>
<![CDATA[
var parent = this.parentNode;
if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
return parent;
return null;
]]>
</getter>
</property>
<property name="selected" readonly="true"
onget="return this.getAttribute('selected') == 'true';"/>
<property name="_selected">
<setter><![CDATA[
if (val) {
this.setAttribute("selected", "true");
this.setAttribute("visuallyselected", "true");
} else {
this.removeAttribute("selected");
this.removeAttribute("visuallyselected");
}
this._setPositionAttributes(val);
return val;
]]></setter>
</property>
<method name="_setPositionAttributes">
<parameter name="aSelected"/>
<body><![CDATA[
if (this.previousSibling && this.previousSibling.localName == "tab") {
if (aSelected)
this.previousSibling.setAttribute("beforeselected", "true");
else
this.previousSibling.removeAttribute("beforeselected");
this.removeAttribute("first-tab");
} else {
this.setAttribute("first-tab", "true");
}
if (this.nextSibling && this.nextSibling.localName == "tab") {
if (aSelected)
this.nextSibling.setAttribute("afterselected", "true");
else
this.nextSibling.removeAttribute("afterselected");
this.removeAttribute("last-tab");
} else {
this.setAttribute("last-tab", "true");
}
]]></body>
</method>
<property name="linkedPanel" onget="return this.getAttribute('linkedpanel')"
onset="this.setAttribute('linkedpanel', val); return val;"/>
<field name="arrowKeysShouldWrap" readonly="true">
/Mac/.test(navigator.platform)
</field>
<property name="TelemetryStopwatch" readonly="true">
<getter><![CDATA[
let module = {};
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", module);
Object.defineProperty(this, "TelemetryStopwatch", {
configurable: true,
enumerable: true,
writable: true,
value: module.TelemetryStopwatch
});
return module.TelemetryStopwatch;
]]></getter>
</property>
</implementation>
<handlers>
<handler event="mousedown" button="0">
<![CDATA[
if (this.disabled)
return;
if (this != this.parentNode.selectedItem) { // Not selected yet
let stopwatchid = this.parentNode.getAttribute("stopwatchid");
if (stopwatchid) {
this.TelemetryStopwatch.start(stopwatchid);
}
// Call this before setting the 'ignorefocus' attribute because this
// will pass on focus if the formerly selected tab was focused as well.
this.parentNode._selectNewTab(this);
var isTabFocused = false;
try {
isTabFocused = (document.commandDispatcher.focusedElement == this);
} catch (e) {}
// Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
// focus the tab; we only want tabs to be focusable by the mouse if
// they are already focused. After a short timeout we'll reset
// '-moz-user-focus' so that tabs can be focused by keyboard again.
if (!isTabFocused) {
this.setAttribute("ignorefocus", "true");
setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
}
if (stopwatchid) {
this.TelemetryStopwatch.finish(stopwatchid);
}
}
// Otherwise this tab is already selected and we will fall
// through to mousedown behavior which sets focus on the current tab,
// Only a click on an already selected tab should focus the tab itself.
]]>
</handler>
<handler event="keydown" keycode="VK_LEFT" group="system" preventdefault="true">
<![CDATA[
var direction = window.getComputedStyle(this.parentNode, null).direction;
this.parentNode.advanceSelectedTab(direction == 'ltr' ? -1 : 1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_RIGHT" group="system" preventdefault="true">
<![CDATA[
var direction = window.getComputedStyle(this.parentNode, null).direction;
this.parentNode.advanceSelectedTab(direction == 'ltr' ? 1 : -1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_UP" group="system" preventdefault="true">
<![CDATA[
this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_DOWN" group="system" preventdefault="true">
<![CDATA[
this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_HOME" group="system" preventdefault="true">
<![CDATA[
this.parentNode._selectNewTab(this.parentNode.childNodes[0]);
]]>
</handler>
<handler event="keydown" keycode="VK_END" group="system" preventdefault="true">
<![CDATA[
var tabs = this.parentNode.childNodes;
this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
]]>
</handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,386 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="textBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<!-- bound to <description>s -->
<binding id="text-base" role="xul:text">
<implementation implements="nsIDOMXULDescriptionElement">
<property name="disabled" onset="if (val) this.setAttribute('disabled', 'true');
else this.removeAttribute('disabled');
return val;"
onget="return this.getAttribute('disabled') == 'true';"/>
<property name="value" onget="return this.getAttribute('value');"
onset="this.setAttribute('value', val); return val;"/>
<property name="crop" onget="return this.getAttribute('crop');"
onset="this.setAttribute('crop', val); return val;"/>
</implementation>
</binding>
<binding id="text-label" extends="chrome://global/content/bindings/text.xml#text-base">
<implementation implements="nsIDOMXULLabelElement">
<property name="accessKey">
<getter>
<![CDATA[
var accessKey = this.getAttribute('accesskey');
return accessKey ? accessKey[0] : null;
]]>
</getter>
<setter>
<![CDATA[
this.setAttribute('accesskey', val);
return val;
]]>
</setter>
</property>
<property name="control" onget="return getAttribute('control');">
<setter>
<![CDATA[
// After this gets set, the label will use the binding #label-control
this.setAttribute('control', val);
return val;
]]>
</setter>
</property>
</implementation>
</binding>
<binding id="label-control" extends="chrome://global/content/bindings/text.xml#text-label">
<content>
<children/><html:span anonid="accessKeyParens"></html:span>
</content>
<implementation implements="nsIDOMXULLabelElement">
<constructor>
<![CDATA[
this.formatAccessKey(true);
]]>
</constructor>
<method name="formatAccessKey">
<parameter name="firstTime"/>
<body>
<![CDATA[
var control = this.labeledControlElement;
if (!control) {
var bindingParent = document.getBindingParent(this);
if (bindingParent instanceof Components.interfaces.nsIDOMXULLabeledControlElement) {
control = bindingParent; // For controls that make the <label> an anon child
}
}
if (control) {
control.labelElement = this;
}
var accessKey = this.accessKey;
// No need to remove existing formatting the first time.
if (firstTime && !accessKey)
return;
if (this.mInsertSeparator === undefined) {
try {
var prefs = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);
const nsIPrefLocalizedString =
Components.interfaces.nsIPrefLocalizedString;
const prefNameInsertSeparator =
"intl.menuitems.insertseparatorbeforeaccesskeys";
const prefNameAlwaysAppendAccessKey =
"intl.menuitems.alwaysappendaccesskeys";
var val = prefs.getComplexValue(prefNameInsertSeparator,
nsIPrefLocalizedString).data;
this.mInsertSeparator = (val == "true");
val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
nsIPrefLocalizedString).data;
this.mAlwaysAppendAccessKey = (val == "true");
}
catch (e) {
this.mInsertSeparator = true;
}
}
if (!this.mUnderlineAccesskey)
return;
var afterLabel = document.getAnonymousElementByAttribute(this, "anonid", "accessKeyParens");
afterLabel.textContent = "";
var oldAccessKey = this.getElementsByAttribute('class', 'accesskey').item(0);
if (oldAccessKey) { // Clear old accesskey
this.mergeElement(oldAccessKey);
}
var oldHiddenSpan =
this.getElementsByAttribute('class', 'hiddenColon').item(0);
if (oldHiddenSpan) {
this.mergeElement(oldHiddenSpan);
}
var labelText = this.textContent;
if (!accessKey || !labelText || !control) {
return;
}
var accessKeyIndex = -1;
if (!this.mAlwaysAppendAccessKey) {
accessKeyIndex = labelText.indexOf(accessKey);
if (accessKeyIndex < 0) { // Try again in upper case
accessKeyIndex =
labelText.toUpperCase().indexOf(accessKey.toUpperCase());
}
}
const HTML_NS = "http://www.w3.org/1999/xhtml";
var span = document.createElementNS(HTML_NS, "span");
span.className = "accesskey";
// Note that if you change the following code, see the comment of
// nsTextBoxFrame::UpdateAccessTitle.
// If accesskey is not in string, append in parentheses
if (accessKeyIndex < 0) {
// If end is colon, we should insert before colon.
// i.e., "label:" -> "label(X):"
var colonHidden = false;
if (/:$/.test(labelText)) {
labelText = labelText.slice(0, -1);
var hiddenSpan = document.createElementNS(HTML_NS, "span");
hiddenSpan.className = "hiddenColon";
hiddenSpan.style.display = "none";
// Hide the last colon by using span element.
// I.e., label<span style="display:none;">:</span>
this.wrapChar(hiddenSpan, labelText.length);
colonHidden = true;
}
// If end is space(U+20),
// we should not add space before parentheses.
var endIsSpace = false;
if (/ $/.test(labelText)) {
endIsSpace = true;
}
if (this.mInsertSeparator && !endIsSpace)
afterLabel.textContent = " (";
else
afterLabel.textContent = "(";
span.textContent = accessKey.toUpperCase();
afterLabel.appendChild(span);
if (!colonHidden)
afterLabel.appendChild(document.createTextNode(")"));
else
afterLabel.appendChild(document.createTextNode("):"));
return;
}
this.wrapChar(span, accessKeyIndex);
]]>
</body>
</method>
<method name="wrapChar">
<parameter name="element"/>
<parameter name="index"/>
<body>
<![CDATA[
var treeWalker = document.createTreeWalker(this,
NodeFilter.SHOW_TEXT,
null);
var node = treeWalker.nextNode();
while (index >= node.length) {
index -= node.length;
node = treeWalker.nextNode();
}
if (index) {
node = node.splitText(index);
}
node.parentNode.insertBefore(element, node);
if (node.length > 1) {
node.splitText(1);
}
element.appendChild(node);
]]>
</body>
</method>
<method name="mergeElement">
<parameter name="element"/>
<body>
<![CDATA[
if (element.previousSibling instanceof Text) {
element.previousSibling.appendData(element.textContent)
}
else {
element.parentNode.insertBefore(element.firstChild, element);
}
element.parentNode.removeChild(element);
]]>
</body>
</method>
<field name="mUnderlineAccesskey">
!/Mac/.test(navigator.platform)
</field>
<field name="mInsertSeparator"/>
<field name="mAlwaysAppendAccessKey">false</field>
<property name="accessKey">
<getter>
<![CDATA[
var accessKey = null;
var labeledEl = this.labeledControlElement;
if (labeledEl) {
accessKey = labeledEl.getAttribute('accesskey');
}
if (!accessKey) {
accessKey = this.getAttribute('accesskey');
}
return accessKey ? accessKey[0] : null;
]]>
</getter>
<setter>
<![CDATA[
// If this label already has an accesskey attribute store it here as well
if (this.hasAttribute('accesskey')) {
this.setAttribute('accesskey', val);
}
var control = this.labeledControlElement;
if (control) {
control.setAttribute('accesskey', val);
}
this.formatAccessKey(false);
return val;
]]>
</setter>
</property>
<property name="labeledControlElement" readonly="true"
onget="var control = this.control; return control ? document.getElementById(control) : null;" />
<property name="control" onget="return this.getAttribute('control');">
<setter>
<![CDATA[
var control = this.labeledControlElement;
if (control) {
control.labelElement = null; // No longer pointed to be this label
}
this.setAttribute('control', val);
this.formatAccessKey(false);
return val;
]]>
</setter>
</property>
</implementation>
<handlers>
<handler event="click" action="if (this.disabled) return;
var controlElement = this.labeledControlElement;
if(controlElement)
controlElement.focus();
"/>
</handlers>
</binding>
<binding id="text-link" extends="chrome://global/content/bindings/text.xml#text-label" role="xul:link">
<implementation>
<property name="href" onget="return this.getAttribute('href');"
onset="this.setAttribute('href', val); return val;" />
<method name="open">
<parameter name="aEvent"/>
<body>
<![CDATA[
var href = this.href;
if (!href || this.disabled || aEvent.defaultPrevented)
return;
var uri = null;
try {
const nsISSM = Components.interfaces.nsIScriptSecurityManager;
const secMan =
Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getService(nsISSM);
const ioService =
Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
uri = ioService.newURI(href, null, null);
let principal;
if (this.getAttribute("useoriginprincipal") == "true") {
principal = this.nodePrincipal;
} else {
principal = secMan.createNullPrincipal({});
}
try {
secMan.checkLoadURIWithPrincipal(principal, uri,
nsISSM.DISALLOW_INHERIT_PRINCIPAL);
}
catch (ex) {
var msg = "Error: Cannot open a " + uri.scheme + ": link using \
the text-link binding.";
Components.utils.reportError(msg);
return;
}
const cID = "@mozilla.org/uriloader/external-protocol-service;1";
const nsIEPS = Components.interfaces.nsIExternalProtocolService;
var protocolSvc = Components.classes[cID].getService(nsIEPS);
// if the scheme is not an exposed protocol, then opening this link
// should be deferred to the system's external protocol handler
if (!protocolSvc.isExposedProtocol(uri.scheme)) {
protocolSvc.loadUrl(uri);
aEvent.preventDefault()
return;
}
}
catch (ex) {
Components.utils.reportError(ex);
}
aEvent.preventDefault();
href = uri ? uri.spec : href;
// Try handing off the link to the host application, e.g. for
// opening it in a tabbed browser.
var linkHandled = Components.classes["@mozilla.org/supports-PRBool;1"]
.createInstance(Components.interfaces.nsISupportsPRBool);
linkHandled.data = false;
let {shiftKey, ctrlKey, metaKey, altKey, button} = aEvent;
let data = {shiftKey, ctrlKey, metaKey, altKey, button, href};
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.notifyObservers(linkHandled, "handle-xul-text-link", JSON.stringify(data));
if (linkHandled.data)
return;
// otherwise, fall back to opening the anchor directly
var win = window;
if (window instanceof Components.interfaces.nsIDOMChromeWindow) {
while (win.opener && !win.opener.closed)
win = win.opener;
}
win.open(href);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="click" phase="capturing" button="0" action="this.open(event)"/>
<handler event="click" phase="capturing" button="1" action="this.open(event)"/>
<handler event="keypress" preventdefault="true" keycode="VK_RETURN" action="this.click()" />
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,646 @@
<?xml version="1.0"?>
<!-- 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/. -->
<!DOCTYPE bindings [
<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
%textcontextDTD;
]>
<bindings id="textboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="textbox" extends="xul:box" role="xul:textbox">
<resources>
<stylesheet src="chrome://global/content/textbox.css"/>
<stylesheet src="chrome://global/skin/textbox.css"/>
</resources>
<content>
<children/>
<xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck">
<html:input class="textbox-input" anonid="input"
xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,noinitialfocus,mozactionhint,spellcheck"/>
</xul:hbox>
</content>
<implementation implements="nsIDOMXULTextBoxElement, nsIDOMXULLabeledControlElement">
<!-- nsIDOMXULLabeledControlElement -->
<field name="crop">""</field>
<field name="image">""</field>
<field name="command">""</field>
<field name="accessKey">""</field>
<field name="mInputField">null</field>
<field name="mIgnoreClick">false</field>
<field name="mIgnoreFocus">false</field>
<field name="mEditor">null</field>
<property name="inputField" readonly="true">
<getter><![CDATA[
if (!this.mInputField)
this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
return this.mInputField;
]]></getter>
</property>
<property name="value" onset="this.inputField.value = val; return val;"
onget="return this.inputField.value;"/>
<property name="defaultValue" onset="this.inputField.defaultValue = val; return val;"
onget="return this.inputField.defaultValue;"/>
<property name="label" onset="this.setAttribute('label', val); return val;"
onget="return this.getAttribute('label') ||
(this.labelElement ? this.labelElement.value :
this.placeholder);"/>
<property name="placeholder" onset="this.inputField.placeholder = val; return val;"
onget="return this.inputField.placeholder;"/>
<property name="emptyText" onset="this.placeholder = val; return val;"
onget="return this.placeholder;"/>
<property name="type" onset="if (val) this.setAttribute('type', val);
else this.removeAttribute('type'); return val;"
onget="return this.getAttribute('type');"/>
<property name="maxLength" onset="this.inputField.maxLength = val; return val;"
onget="return this.inputField.maxLength;"/>
<property name="disabled" onset="this.inputField.disabled = val;
if (val) this.setAttribute('disabled', 'true');
else this.removeAttribute('disabled'); return val;"
onget="return this.inputField.disabled;"/>
<property name="tabIndex" onget="return parseInt(this.getAttribute('tabindex'));"
onset="this.inputField.tabIndex = val;
if (val) this.setAttribute('tabindex', val);
else this.removeAttribute('tabindex'); return val;"/>
<property name="size" onset="this.inputField.size = val; return val;"
onget="return this.inputField.size;"/>
<property name="readOnly" onset="this.inputField.readOnly = val;
if (val) this.setAttribute('readonly', 'true');
else this.removeAttribute('readonly'); return val;"
onget="return this.inputField.readOnly;"/>
<property name="clickSelectsAll"
onget="return this.getAttribute('clickSelectsAll') == 'true';"
onset="if (val) this.setAttribute('clickSelectsAll', 'true');
else this.removeAttribute('clickSelectsAll'); return val;" />
<property name="editor" readonly="true">
<getter><![CDATA[
if (!this.mEditor) {
const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
this.mEditor = this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
}
return this.mEditor;
]]></getter>
</property>
<method name="reset">
<body><![CDATA[
this.value = this.defaultValue;
try {
this.editor.transactionManager.clear();
return true;
}
catch (e) {}
return false;
]]></body>
</method>
<method name="select">
<body>
this.inputField.select();
</body>
</method>
<property name="controllers" readonly="true" onget="return this.inputField.controllers"/>
<property name="textLength" readonly="true"
onget="return this.inputField.textLength;"/>
<property name="selectionStart" onset="this.inputField.selectionStart = val; return val;"
onget="return this.inputField.selectionStart;"/>
<property name="selectionEnd" onset="this.inputField.selectionEnd = val; return val;"
onget="return this.inputField.selectionEnd;"/>
<method name="setSelectionRange">
<parameter name="aSelectionStart"/>
<parameter name="aSelectionEnd"/>
<body>
this.inputField.setSelectionRange( aSelectionStart, aSelectionEnd );
</body>
</method>
<method name="_setNewlineHandling">
<body><![CDATA[
var str = this.getAttribute("newlines");
if (str && this.editor) {
const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
for (var x in nsIPlaintextEditor) {
if (/^eNewlines/.test(x)) {
if (str == RegExp.rightContext.toLowerCase()) {
this.editor.QueryInterface(nsIPlaintextEditor)
.newlineHandling = nsIPlaintextEditor[x];
break;
}
}
}
}
]]></body>
</method>
<method name="_maybeSelectAll">
<body><![CDATA[
if (!this.mIgnoreClick && this.clickSelectsAll &&
document.activeElement == this.inputField &&
this.inputField.selectionStart == this.inputField.selectionEnd)
this.editor.selectAll();
]]></body>
</method>
<constructor><![CDATA[
var str = this.boxObject.getProperty("value");
if (str) {
this.inputField.value = str;
this.boxObject.removeProperty("value");
}
this._setNewlineHandling();
if (this.hasAttribute("emptytext"))
this.placeholder = this.getAttribute("emptytext");
]]></constructor>
<destructor>
<![CDATA[
var field = this.inputField;
if (field && field.value)
this.boxObject.setProperty('value', field.value);
this.mInputField = null;
]]>
</destructor>
</implementation>
<handlers>
<handler event="focus" phase="capturing">
<![CDATA[
if (this.hasAttribute("focused"))
return;
switch (event.originalTarget) {
case this:
// Forward focus to actual HTML input
this.inputField.focus();
break;
case this.inputField:
if (this.mIgnoreFocus) {
this.mIgnoreFocus = false;
} else if (this.clickSelectsAll) {
try {
const nsIEditorIMESupport =
Components.interfaces.nsIEditorIMESupport;
let imeEditor = this.editor.QueryInterface(nsIEditorIMESupport);
if (!imeEditor || !imeEditor.composing)
this.editor.selectAll();
} catch (e) {}
}
break;
default:
// Allow other children (e.g. URL bar buttons) to get focus
return;
}
this.setAttribute("focused", "true");
]]>
</handler>
<handler event="blur" phase="capturing">
<![CDATA[
this.removeAttribute("focused");
// don't trigger clickSelectsAll when switching application windows
if (window == window.top &&
window.constructor == ChromeWindow &&
document.activeElement == this.inputField)
this.mIgnoreFocus = true;
]]>
</handler>
<handler event="mousedown">
<![CDATA[
this.mIgnoreClick = this.hasAttribute("focused");
if (!this.mIgnoreClick) {
this.mIgnoreFocus = true;
this.inputField.setSelectionRange(0, 0);
if (event.originalTarget == this ||
event.originalTarget == this.inputField.parentNode)
this.inputField.focus();
}
]]>
</handler>
<handler event="click" action="this._maybeSelectAll();"/>
#ifndef XP_WIN
<handler event="contextmenu">
// Only care about context clicks on the textbox itself.
if (event.target != this)
return;
if (!event.button) // context menu opened via keyboard shortcut
return;
this._maybeSelectAll();
// see bug 576135 comment 4
let box = this.inputField.parentNode;
let menu = document.getAnonymousElementByAttribute(box, "anonid", "input-box-contextmenu");
box._doPopupItemEnabling(menu);
</handler>
#endif
</handlers>
</binding>
<binding id="timed-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
<implementation>
<constructor><![CDATA[
try {
var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Components.interfaces.nsIScriptError);
scriptError.init("Timed textboxes are deprecated. Consider using type=\"search\" instead.",
this.ownerDocument.location.href, null, null,
null, scriptError.warningFlag, "XUL Widgets");
consoleService.logMessage(scriptError);
} catch (e) {}
]]></constructor>
<field name="_timer">null</field>
<property name="timeout"
onset="this.setAttribute('timeout', val); return val;"
onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
<property name="value"
onget="return this.inputField.value;">
<setter><![CDATA[
this.inputField.value = val;
if (this._timer)
clearTimeout(this._timer);
return val;
]]></setter>
</property>
<method name="_fireCommand">
<parameter name="me"/>
<body>
<![CDATA[
me._timer = null;
me.doCommand();
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="input">
<![CDATA[
if (this._timer)
clearTimeout(this._timer);
this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
]]>
</handler>
<handler event="keypress" keycode="VK_RETURN">
<![CDATA[
if (this._timer)
clearTimeout(this._timer);
this._fireCommand(this);
event.preventDefault();
]]>
</handler>
</handlers>
</binding>
<binding id="search-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
<content>
<children/>
<xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck" align="center">
<html:input class="textbox-input" anonid="input" mozactionhint="search"
xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,spellcheck"/>
<xul:deck class="textbox-search-icons" anonid="search-icons">
<xul:image class="textbox-search-icon" anonid="searchbutton-icon"
xbl:inherits="src=image,label=searchbuttonlabel,searchbutton,disabled"/>
<xul:image class="textbox-search-clear"
onclick="document.getBindingParent(this)._clearSearch();"
label="&searchTextBox.clear.label;"
xbl:inherits="disabled"/>
</xul:deck>
</xul:hbox>
</content>
<implementation>
<field name="_timer">null</field>
<field name="_searchIcons">
document.getAnonymousElementByAttribute(this, "anonid", "search-icons");
</field>
<field name="_searchButtonIcon">
document.getAnonymousElementByAttribute(this, "anonid", "searchbutton-icon");
</field>
<property name="timeout"
onset="this.setAttribute('timeout', val); return val;"
onget="return parseInt(this.getAttribute('timeout')) || 500;"/>
<property name="searchButton"
onget="return this.getAttribute('searchbutton') == 'true';">
<setter><![CDATA[
if (val) {
this.setAttribute("searchbutton", "true");
this.removeAttribute("aria-autocomplete");
// Hack for the button to get the right accessible:
this._searchButtonIcon.setAttribute("onclick", "true");
} else {
this.removeAttribute("searchbutton");
this._searchButtonIcon.removeAttribute("onclick");
this.setAttribute("aria-autocomplete", "list");
}
return val;
]]></setter>
</property>
<property name="value"
onget="return this.inputField.value;">
<setter><![CDATA[
this.inputField.value = val;
if (val)
this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
else
this._searchIcons.selectedIndex = 0;
if (this._timer)
clearTimeout(this._timer);
return val;
]]></setter>
</property>
<constructor><![CDATA[
// Ensure the button state is up to date:
this.searchButton = this.searchButton;
this._searchButtonIcon.addEventListener("click", (e) => this._iconClick(e), false);
]]></constructor>
<method name="_fireCommand">
<parameter name="me"/>
<body><![CDATA[
if (me._timer)
clearTimeout(me._timer);
me._timer = null;
me.doCommand();
]]></body>
</method>
<method name="_iconClick">
<body><![CDATA[
if (this.searchButton)
this._enterSearch();
else
this.focus();
]]></body>
</method>
<method name="_enterSearch">
<body><![CDATA[
if (this.disabled)
return;
if (this.searchButton && this.value && !this.readOnly)
this._searchIcons.selectedIndex = 1;
this._fireCommand(this);
]]></body>
</method>
<method name="_clearSearch">
<body><![CDATA[
if (!this.disabled && !this.readOnly && this.value) {
this.value = "";
this._fireCommand(this);
this._searchIcons.selectedIndex = 0;
return true;
}
return false;
]]></body>
</method>
</implementation>
<handlers>
<handler event="input">
<![CDATA[
if (this.searchButton) {
this._searchIcons.selectedIndex = 0;
return;
}
if (this._timer)
clearTimeout(this._timer);
this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
this._searchIcons.selectedIndex = this.value ? 1 : 0;
]]>
</handler>
<handler event="keypress" keycode="VK_ESCAPE">
<![CDATA[
if (this._clearSearch()) {
event.preventDefault();
event.stopPropagation();
}
]]>
</handler>
<handler event="keypress" keycode="VK_RETURN">
<![CDATA[
this._enterSearch();
event.preventDefault();
event.stopPropagation();
]]>
</handler>
</handlers>
</binding>
<binding id="textarea" extends="chrome://global/content/bindings/textbox.xml#textbox">
<content>
<xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck">
<html:textarea class="textbox-textarea" anonid="input"
xbl:inherits="xbl:text=value,disabled,tabindex,rows,cols,readonly,wrap,placeholder,mozactionhint,spellcheck"><children/></html:textarea>
</xul:hbox>
</content>
</binding>
<binding id="input-box">
<content context="_child">
<children/>
<xul:menupopup anonid="input-box-contextmenu"
class="textbox-contextmenu"
onpopupshowing="var input =
this.parentNode.getElementsByAttribute('anonid', 'input')[0];
if (document.commandDispatcher.focusedElement != input)
input.focus();
this.parentNode._doPopupItemEnabling(this);"
oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if(cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
<xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
<xul:menuseparator/>
<xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
<xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
<xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
<xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
<xul:menuseparator/>
<xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
</xul:menupopup>
</content>
<implementation>
<method name="_doPopupItemEnabling">
<parameter name="popupNode"/>
<body>
<![CDATA[
var children = popupNode.childNodes;
for (var i = 0; i < children.length; i++) {
var command = children[i].getAttribute("cmd");
if (command) {
var controller = document.commandDispatcher.getControllerForCommand(command);
var enabled = controller.isCommandEnabled(command);
if (enabled)
children[i].removeAttribute("disabled");
else
children[i].setAttribute("disabled", "true");
}
}
]]>
</body>
</method>
<method name="_setMenuItemVisibility">
<parameter name="anonid"/>
<parameter name="visible"/>
<body><![CDATA[
document.getAnonymousElementByAttribute(this, "anonid", anonid).
hidden = ! visible;
]]></body>
</method>
<method name="doCommand">
<parameter name="command"/>
<body>
<![CDATA[
var controller = document.commandDispatcher.getControllerForCommand(command);
controller.doCommand(command);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="input-box-spell" extends="chrome://global/content/bindings/textbox.xml#input-box">
<content context="_child">
<children/>
<xul:menupopup anonid="input-box-contextmenu"
class="textbox-contextmenu"
onpopupshowing="var input =
this.parentNode.getElementsByAttribute('anonid', 'input')[0];
if (document.commandDispatcher.focusedElement != input)
input.focus();
this.parentNode._doPopupItemEnablingSpell(this);"
onpopuphiding="this.parentNode._doPopupItemDisabling(this);"
oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if(cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
<xul:menuitem label="&spellNoSuggestions.label;" anonid="spell-no-suggestions" disabled="true"/>
<xul:menuitem label="&spellAddToDictionary.label;" accesskey="&spellAddToDictionary.accesskey;" anonid="spell-add-to-dictionary"
oncommand="this.parentNode.parentNode.spellCheckerUI.addToDictionary();"/>
<xul:menuitem label="&spellUndoAddToDictionary.label;" accesskey="&spellUndoAddToDictionary.accesskey;" anonid="spell-undo-add-to-dictionary"
oncommand="this.parentNode.parentNode.spellCheckerUI.undoAddToDictionary();"/>
<xul:menuseparator anonid="spell-suggestions-separator"/>
<xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
<xul:menuseparator/>
<xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
<xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
<xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
<xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
<xul:menuseparator/>
<xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
<xul:menuseparator anonid="spell-check-separator"/>
<xul:menuitem label="&spellCheckToggle.label;" type="checkbox" accesskey="&spellCheckToggle.accesskey;" anonid="spell-check-enabled"
oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled();"/>
<xul:menu label="&spellDictionaries.label;" accesskey="&spellDictionaries.accesskey;" anonid="spell-dictionaries">
<xul:menupopup anonid="spell-dictionaries-menu"
onpopupshowing="event.stopPropagation();"
onpopuphiding="event.stopPropagation();"/>
</xul:menu>
</xul:menupopup>
</content>
<implementation>
<field name="_spellCheckInitialized">false</field>
<field name="_enabledCheckbox">
document.getAnonymousElementByAttribute(this, "anonid", "spell-check-enabled");
</field>
<field name="_suggestionsSeparator">
document.getAnonymousElementByAttribute(this, "anonid", "spell-no-suggestions");
</field>
<field name="_dictionariesMenu">
document.getAnonymousElementByAttribute(this, "anonid", "spell-dictionaries-menu");
</field>
<property name="spellCheckerUI" readonly="true">
<getter><![CDATA[
if (!this._spellCheckInitialized) {
this._spellCheckInitialized = true;
const CI = Components.interfaces;
if (!(document instanceof CI.nsIDOMXULDocument))
return null;
var textbox = document.getBindingParent(this);
if (!textbox || !(textbox instanceof CI.nsIDOMXULTextBoxElement))
return null;
try {
Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm", this);
this.InlineSpellCheckerUI = new this.InlineSpellChecker(textbox.editor);
} catch (ex) { }
}
return this.InlineSpellCheckerUI;
]]></getter>
</property>
<method name="_doPopupItemEnablingSpell">
<parameter name="popupNode"/>
<body>
<![CDATA[
var spellui = this.spellCheckerUI;
if (!spellui || !spellui.canSpellCheck) {
this._setMenuItemVisibility("spell-no-suggestions", false);
this._setMenuItemVisibility("spell-check-enabled", false);
this._setMenuItemVisibility("spell-check-separator", false);
this._setMenuItemVisibility("spell-add-to-dictionary", false);
this._setMenuItemVisibility("spell-undo-add-to-dictionary", false);
this._setMenuItemVisibility("spell-suggestions-separator", false);
this._setMenuItemVisibility("spell-dictionaries", false);
return;
}
spellui.initFromEvent(document.popupRangeParent,
document.popupRangeOffset);
var enabled = spellui.enabled;
var showUndo = spellui.canSpellCheck && spellui.canUndo();
this._enabledCheckbox.setAttribute("checked", enabled);
var overMisspelling = spellui.overMisspelling;
this._setMenuItemVisibility("spell-add-to-dictionary", overMisspelling);
this._setMenuItemVisibility("spell-undo-add-to-dictionary", showUndo);
this._setMenuItemVisibility("spell-suggestions-separator", overMisspelling || showUndo);
// suggestion list
var numsug = spellui.addSuggestionsToMenu(popupNode, this._suggestionsSeparator, 5);
this._setMenuItemVisibility("spell-no-suggestions", overMisspelling && numsug == 0);
// dictionary list
var numdicts = spellui.addDictionaryListToMenu(this._dictionariesMenu, null);
this._setMenuItemVisibility("spell-dictionaries", enabled && numdicts > 1);
this._doPopupItemEnabling(popupNode);
]]>
</body>
</method>
<method name="_doPopupItemDisabling">
<body><![CDATA[
if (this.spellCheckerUI) {
this.spellCheckerUI.clearSuggestionsFromMenu();
this.spellCheckerUI.clearDictionaryListFromMenu();
}
]]></body>
</method>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,418 @@
/* 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/. */
"use strict";
/**
* TimeKeeper keeps track of the time states. Given min, max, step, and
* format (12/24hr), TimeKeeper will determine the ranges of possible
* selections, and whether or not the current time state is out of range
* or off step.
*
* @param {Object} props
* {
* {Date} min
* {Date} max
* {Number} stepInMs
* {String} format: Either "12" or "24"
* }
*/
function TimeKeeper(props) {
this.props = props;
this.state = { time: new Date(0), ranges: {} };
}
{
const debug = 0 ? console.log.bind(console, '[timekeeper]') : function() {};
const DAY_PERIOD_IN_HOURS = 12,
SECOND_IN_MS = 1000,
MINUTE_IN_MS = 60000,
HOUR_IN_MS = 3600000,
DAY_PERIOD_IN_MS = 43200000,
DAY_IN_MS = 86400000,
TIME_FORMAT_24 = "24";
TimeKeeper.prototype = {
/**
* Getters for different time units.
* @return {Number}
*/
get hour() {
return this.state.time.getUTCHours();
},
get minute() {
return this.state.time.getUTCMinutes();
},
get second() {
return this.state.time.getUTCSeconds();
},
get millisecond() {
return this.state.time.getUTCMilliseconds();
},
get dayPeriod() {
// 0 stands for AM and 12 for PM
return this.state.time.getUTCHours() < DAY_PERIOD_IN_HOURS ? 0 : DAY_PERIOD_IN_HOURS;
},
/**
* Get the ranges of different time units.
* @return {Object}
* {
* {Array<Number>} dayPeriod
* {Array<Number>} hours
* {Array<Number>} minutes
* {Array<Number>} seconds
* {Array<Number>} milliseconds
* }
*/
get ranges() {
return this.state.ranges;
},
/**
* Set new time, check if the current state is valid, and set ranges.
*
* @param {Object} timeState: The new time
* {
* {Number} hour [optional]
* {Number} minute [optional]
* {Number} second [optional]
* {Number} millisecond [optional]
* }
*/
setState(timeState) {
const { min, max } = this.props;
const { hour, minute, second, millisecond } = timeState;
if (hour != undefined) {
this.state.time.setUTCHours(hour);
}
if (minute != undefined) {
this.state.time.setUTCMinutes(minute);
}
if (second != undefined) {
this.state.time.setUTCSeconds(second);
}
if (millisecond != undefined) {
this.state.time.setUTCMilliseconds(millisecond);
}
this.state.isOffStep = this._isOffStep(this.state.time);
this.state.isOutOfRange = (this.state.time < min || this.state.time > max);
this.state.isInvalid = this.state.isOutOfRange || this.state.isOffStep;
this._setRanges(this.dayPeriod, this.hour, this.minute, this.second);
},
/**
* Set day-period (AM/PM)
* @param {Number} dayPeriod: 0 as AM, 12 as PM
*/
setDayPeriod(dayPeriod) {
if (dayPeriod == this.dayPeriod) {
return;
}
if (dayPeriod == 0) {
this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
} else {
this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS });
}
},
/**
* Set hour in 24hr format (0 ~ 23)
* @param {Number} hour
*/
setHour(hour) {
this.setState({ hour });
},
/**
* Set minute (0 ~ 59)
* @param {Number} minute
*/
setMinute(minute) {
this.setState({ minute });
},
/**
* Set second (0 ~ 59)
* @param {Number} second
*/
setSecond(second) {
this.setState({ second });
},
/**
* Set millisecond (0 ~ 999)
* @param {Number} millisecond
*/
setMillisecond(millisecond) {
this.setState({ millisecond });
},
/**
* Calculate the range of possible choices for each time unit.
* Reuse the old result if the input has not changed.
*
* @param {Number} dayPeriod
* @param {Number} hour
* @param {Number} minute
* @param {Number} second
*/
_setRanges(dayPeriod, hour, minute, second) {
this.state.ranges.dayPeriod =
this.state.ranges.dayPeriod || this._getDayPeriodRange();
if (this.state.dayPeriod != dayPeriod) {
this.state.ranges.hours = this._getHoursRange(dayPeriod);
}
if (this.state.hour != hour) {
this.state.ranges.minutes = this._getMinutesRange(hour);
}
if (this.state.hour != hour || this.state.minute != minute) {
this.state.ranges.seconds = this._getSecondsRange(hour, minute);
}
if (this.state.hour != hour || this.state.minute != minute || this.state.second != second) {
this.state.ranges.milliseconds = this._getMillisecondsRange(hour, minute, second);
}
// Save the time states for comparison.
this.state.dayPeriod = dayPeriod;
this.state.hour = hour;
this.state.minute = minute;
this.state.second = second;
},
/**
* Get the AM/PM range. Return an empty array if in 24hr mode.
*
* @return {Array<Number>}
*/
_getDayPeriodRange() {
if (this.props.format == TIME_FORMAT_24) {
return [];
}
const start = 0;
const end = DAY_IN_MS - 1;
const minStep = DAY_PERIOD_IN_MS;
const formatter = (time) =>
new Date(time).getUTCHours() < DAY_PERIOD_IN_HOURS ? 0 : DAY_PERIOD_IN_HOURS;
return this._getSteps(start, end, minStep, formatter);
},
/**
* Get the hours range.
*
* @param {Number} dayPeriod
* @return {Array<Number>}
*/
_getHoursRange(dayPeriod) {
const { format } = this.props;
const start = format == "24" ? 0 : dayPeriod * HOUR_IN_MS;
const end = format == "24" ? DAY_IN_MS - 1 : start + DAY_PERIOD_IN_MS - 1;
const minStep = HOUR_IN_MS;
const formatter = (time) => new Date(time).getUTCHours();
return this._getSteps(start, end, minStep, formatter);
},
/**
* Get the minutes range
*
* @param {Number} hour
* @return {Array<Number>}
*/
_getMinutesRange(hour) {
const start = hour * HOUR_IN_MS;
const end = start + HOUR_IN_MS - 1;
const minStep = MINUTE_IN_MS;
const formatter = (time) => new Date(time).getUTCMinutes();
return this._getSteps(start, end, minStep, formatter);
},
/**
* Get the seconds range
*
* @param {Number} hour
* @param {Number} minute
* @return {Array<Number>}
*/
_getSecondsRange(hour, minute) {
const start = hour * HOUR_IN_MS + minute * MINUTE_IN_MS;
const end = start + MINUTE_IN_MS - 1;
const minStep = SECOND_IN_MS;
const formatter = (time) => new Date(time).getUTCSeconds();
return this._getSteps(start, end, minStep, formatter);
},
/**
* Get the milliseconds range
* @param {Number} hour
* @param {Number} minute
* @param {Number} second
* @return {Array<Number>}
*/
_getMillisecondsRange(hour, minute, second) {
const start = hour * HOUR_IN_MS + minute * MINUTE_IN_MS + second * SECOND_IN_MS;
const end = start + SECOND_IN_MS - 1;
const minStep = 1;
const formatter = (time) => new Date(time).getUTCMilliseconds();
return this._getSteps(start, end, minStep, formatter);
},
/**
* Calculate the range of possible steps.
*
* @param {Number} startValue: Start time in ms
* @param {Number} endValue: End time in ms
* @param {Number} minStep: Smallest step in ms for the time unit
* @param {Function} formatter: Outputs time in a particular format
* @return {Array<Object>}
* {
* {Number} value
* {Boolean} enabled
* }
*/
_getSteps(startValue, endValue, minStep, formatter) {
const { min, max, stepInMs } = this.props;
// The timeStep should be big enough so that there won't be
// duplications. Ex: minimum step for minute should be 60000ms,
// if smaller than that, next step might return the same minute.
const timeStep = Math.max(minStep, stepInMs);
// Make sure the starting point and end point is not off step
let time = min.valueOf() + Math.ceil((startValue - min.valueOf()) / timeStep) * timeStep;
let maxValue = min.valueOf() + Math.floor((max.valueOf() - min.valueOf()) / stepInMs) * stepInMs;
let steps = [];
// Increment by timeStep until reaching the end of the range.
while (time <= endValue) {
steps.push({
value: formatter(time),
// Check if the value is within the min and max. If it's out of range,
// also check for the case when minStep is too large, and has stepped out
// of range when it should be enabled.
enabled: (time >= min.valueOf() && time <= max.valueOf()) ||
(time > maxValue && startValue <= maxValue &&
endValue >= maxValue && formatter(time) == formatter(maxValue))
});
time += timeStep;
}
return steps;
},
/**
* A generic function for stepping up or down from a value of a range.
* It stops at the upper and lower limits.
*
* @param {Number} current: The current value
* @param {Number} offset: The offset relative to current value
* @param {Array<Object>} range: List of possible steps
* @return {Number} The new value
*/
_step(current, offset, range) {
const index = range.findIndex(step => step.value == current);
const newIndex = offset > 0 ?
Math.min(index + offset, range.length - 1) :
Math.max(index + offset, 0);
return range[newIndex].value;
},
/**
* Step up or down AM/PM
*
* @param {Number} offset
*/
stepDayPeriodBy(offset) {
const current = this.dayPeriod;
const dayPeriod = this._step(current, offset, this.state.ranges.dayPeriod);
if (current != dayPeriod) {
this.hour < DAY_PERIOD_IN_HOURS ?
this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS }) :
this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
}
},
/**
* Step up or down hours
*
* @param {Number} offset
*/
stepHourBy(offset) {
const current = this.hour;
const hour = this._step(current, offset, this.state.ranges.hours);
if (current != hour) {
this.setState({ hour });
}
},
/**
* Step up or down minutes
*
* @param {Number} offset
*/
stepMinuteBy(offset) {
const current = this.minute;
const minute = this._step(current, offset, this.state.ranges.minutes);
if (current != minute) {
this.setState({ minute });
}
},
/**
* Step up or down seconds
*
* @param {Number} offset
*/
stepSecondBy(offset) {
const current = this.second;
const second = this._step(current, offset, this.state.ranges.seconds);
if (current != second) {
this.setState({ second });
}
},
/**
* Step up or down milliseconds
*
* @param {Number} offset
*/
stepMillisecondBy(offset) {
const current = this.milliseconds;
const millisecond = this._step(current, offset, this.state.ranges.millisecond);
if (current != millisecond) {
this.setState({ millisecond });
}
},
/**
* Checks if the time state is off step.
*
* @param {Date} time
* @return {Boolean}
*/
_isOffStep(time) {
const { min, stepInMs } = this.props;
return (time.valueOf() - min.valueOf()) % stepInMs != 0;
}
};
}

View file

@ -0,0 +1,277 @@
/* 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/. */
"use strict";
function TimePicker(context) {
this.context = context;
this._attachEventListeners();
}
{
const debug = 0 ? console.log.bind(console, "[timepicker]") : function() {};
const DAY_PERIOD_IN_HOURS = 12,
SECOND_IN_MS = 1000,
MINUTE_IN_MS = 60000,
DAY_IN_MS = 86400000;
TimePicker.prototype = {
/**
* Initializes the time picker. Set the default states and properties.
* @param {Object} props
* {
* {Number} hour [optional]: Hour in 24 hours format (0~23), default is current hour
* {Number} minute [optional]: Minute (0~59), default is current minute
* {String} min [optional]: Minimum time, in 24 hours format. ex: "05:45"
* {String} max [optional]: Maximum time, in 24 hours format. ex: "23:00"
* {Number} step [optional]: Step size in minutes. Default is 60.
* {String} format [optional]: "12" for 12 hours, "24" for 24 hours format
* {String} locale [optional]: User preferred locale
* }
*/
init(props) {
this.props = props || {};
this._setDefaultState();
this._createComponents();
this._setComponentStates();
},
/*
* Set initial time states. If there's no hour & minute, it will
* use the current time. The Time module keeps track of the time states,
* and calculates the valid options given the time, min, max, step,
* and format (12 or 24).
*/
_setDefaultState() {
const { hour, minute, min, max, step, format } = this.props;
const now = new Date();
let timerHour = hour == undefined ? now.getHours() : hour;
let timerMinute = minute == undefined ? now.getMinutes() : minute;
// The spec defines 1 step == 1 second, need to convert to ms for timekeeper
let timeKeeper = new TimeKeeper({
min: this._parseTimeString(min) || new Date(0),
max: this._parseTimeString(max) || new Date(DAY_IN_MS - 1),
stepInMs: step ? step * SECOND_IN_MS : MINUTE_IN_MS,
format: format || "12"
});
timeKeeper.setState({ hour: timerHour, minute: timerMinute });
this.state = { timeKeeper };
},
/**
* Convert a time string from DOM attribute to a date object.
*
* @param {String} timeString: (ex. "10:30", "23:55", "12:34:56.789")
* @return {Date/Boolean} Date object or false if date is invalid.
*/
_parseTimeString(timeString) {
let time = new Date("1970-01-01T" + timeString + "Z");
return time.toString() == "Invalid Date" ? false : time;
},
/**
* Initalize the spinner components.
*/
_createComponents() {
const { locale, step, format } = this.props;
const { timeKeeper } = this.state;
const wrapSetValueFn = (setTimeFunction) => {
return (value) => {
setTimeFunction(value);
this._setComponentStates();
this._dispatchState();
};
};
const numberFormat = new Intl.NumberFormat(locale).format;
this.components = {
hour: new Spinner({
setValue: wrapSetValueFn(value => {
timeKeeper.setHour(value);
this.state.isHourSet = true;
}),
getDisplayString: hour => {
if (format == "24") {
return numberFormat(hour);
}
// Hour 0 in 12 hour format is displayed as 12.
const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
return hourIn12 == 0 ? numberFormat(12)
: numberFormat(hourIn12);
}
}, this.context),
minute: new Spinner({
setValue: wrapSetValueFn(value => {
timeKeeper.setMinute(value);
this.state.isMinuteSet = true;
}),
getDisplayString: minute => numberFormat(minute)
}, this.context)
};
this._insertLayoutElement({
tag: "div",
textContent: ":",
className: "colon",
insertBefore: this.components.minute.elements.container
});
// The AM/PM spinner is only available in 12hr mode
// TODO: Replace AM & PM string with localized string
if (format == "12") {
this.components.dayPeriod = new Spinner({
setValue: wrapSetValueFn(value => {
timeKeeper.setDayPeriod(value);
this.state.isDayPeriodSet = true;
}),
getDisplayString: dayPeriod => dayPeriod == 0 ? "AM" : "PM",
hideButtons: true
}, this.context);
this._insertLayoutElement({
tag: "div",
className: "spacer",
insertBefore: this.components.dayPeriod.elements.container
});
}
},
/**
* Insert element for layout purposes.
*
* @param {Object}
* {
* {String} tag: The tag to create
* {DOMElement} insertBefore: The DOM node to insert before
* {String} className [optional]: Class name
* {String} textContent [optional]: Text content
* }
*/
_insertLayoutElement({ tag, insertBefore, className, textContent }) {
let el = document.createElement(tag);
el.textContent = textContent;
el.className = className;
this.context.insertBefore(el, insertBefore);
},
/**
* Set component states.
*/
_setComponentStates() {
const { timeKeeper, isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
const isInvalid = timeKeeper.state.isInvalid;
// Value is set to min if it's first opened and time state is invalid
const setToMinValue = !isHourSet && !isMinuteSet && !isDayPeriodSet && isInvalid;
this.components.hour.setState({
value: setToMinValue ? timeKeeper.ranges.hours[0].value : timeKeeper.hour,
items: timeKeeper.ranges.hours,
isInfiniteScroll: true,
isValueSet: isHourSet,
isInvalid
});
this.components.minute.setState({
value: setToMinValue ? timeKeeper.ranges.minutes[0].value : timeKeeper.minute,
items: timeKeeper.ranges.minutes,
isInfiniteScroll: true,
isValueSet: isMinuteSet,
isInvalid
});
// The AM/PM spinner is only available in 12hr mode
if (this.props.format == "12") {
this.components.dayPeriod.setState({
value: setToMinValue ? timeKeeper.ranges.dayPeriod[0].value : timeKeeper.dayPeriod,
items: timeKeeper.ranges.dayPeriod,
isInfiniteScroll: false,
isValueSet: isDayPeriodSet,
isInvalid
});
}
},
/**
* Dispatch CustomEvent to pass the state of picker to the panel.
*/
_dispatchState() {
const { hour, minute } = this.state.timeKeeper;
const { isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
// The panel is listening to window for postMessage event, so we
// do postMessage to itself to send data to input boxes.
window.postMessage({
name: "TimePickerPopupChanged",
detail: {
hour,
minute,
isHourSet,
isMinuteSet,
isDayPeriodSet
}
}, "*");
},
_attachEventListeners() {
window.addEventListener("message", this);
},
/**
* Handle events.
*
* @param {Event} event
*/
handleEvent(event) {
switch (event.type) {
case "message": {
this.handleMessage(event);
break;
}
}
},
/**
* Handle postMessage events.
*
* @param {Event} event
*/
handleMessage(event) {
switch (event.data.name) {
case "TimePickerSetValue": {
this.set(event.data.detail);
break;
}
case "TimePickerInit": {
this.init(event.data.detail);
break;
}
}
},
/**
* Set the time state and update the components with the new state.
*
* @param {Object} timeState
* {
* {Number} hour [optional]
* {Number} minute [optional]
* {Number} second [optional]
* {Number} millisecond [optional]
* }
*/
set(timeState) {
if (timeState.hour != undefined) {
this.state.isHourSet = true;
}
if (timeState.minute != undefined) {
this.state.isMinuteSet = true;
}
this.state.timeKeeper.setState(timeState);
this._setComponentStates();
}
};
}

View file

@ -0,0 +1,590 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="toolbarBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="toolbar-base">
<resources>
<stylesheet src="chrome://global/skin/toolbar.css"/>
</resources>
</binding>
<binding id="toolbox" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
<implementation>
<field name="palette">
null
</field>
<field name="toolbarset">
null
</field>
<field name="customToolbarCount">
0
</field>
<field name="externalToolbars">
[]
</field>
<!-- Set by customizeToolbar.js -->
<property name="customizing">
<getter><![CDATA[
return this.getAttribute("customizing") == "true";
]]></getter>
<setter><![CDATA[
if (val)
this.setAttribute("customizing", "true");
else
this.removeAttribute("customizing");
return val;
]]></setter>
</property>
<constructor>
<![CDATA[
// Look to see if there is a toolbarset.
this.toolbarset = this.firstChild;
while (this.toolbarset && this.toolbarset.localName != "toolbarset")
this.toolbarset = toolbarset.nextSibling;
if (this.toolbarset) {
// Create each toolbar described by the toolbarset.
var index = 0;
while (toolbarset.hasAttribute("toolbar"+(++index))) {
var toolbarInfo = toolbarset.getAttribute("toolbar"+index);
var infoSplit = toolbarInfo.split(":");
this.appendCustomToolbar(infoSplit[0], infoSplit[1]);
}
}
]]>
</constructor>
<method name="appendCustomToolbar">
<parameter name="aName"/>
<parameter name="aCurrentSet"/>
<body>
<![CDATA[
if (!this.toolbarset)
return null;
var toolbar = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"toolbar");
toolbar.id = "__customToolbar_" + aName.replace(" ", "_");
toolbar.setAttribute("customizable", "true");
toolbar.setAttribute("customindex", ++this.customToolbarCount);
toolbar.setAttribute("toolbarname", aName);
toolbar.setAttribute("currentset", aCurrentSet);
toolbar.setAttribute("mode", this.getAttribute("mode"));
toolbar.setAttribute("iconsize", this.getAttribute("iconsize"));
toolbar.setAttribute("context", this.toolbarset.getAttribute("context"));
toolbar.setAttribute("class", "chromeclass-toolbar");
this.insertBefore(toolbar, this.toolbarset);
return toolbar;
]]>
</body>
</method>
</implementation>
</binding>
<binding id="toolbar" role="xul:toolbar"
extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
<implementation>
<property name="toolbarName"
onget="return this.getAttribute('toolbarname');"
onset="this.setAttribute('toolbarname', val); return val;"/>
<field name="_toolbox">null</field>
<property name="toolbox" readonly="true">
<getter><![CDATA[
if (this._toolbox)
return this._toolbox;
let toolboxId = this.getAttribute("toolboxid");
if (toolboxId) {
let toolbox = document.getElementById(toolboxId);
if (!toolbox) {
let tbName = this.toolbarName;
if (tbName)
tbName = " (" + tbName + ")";
else
tbName = "";
throw new Error(`toolbar ID ${this.id}${tbName}: toolboxid attribute '${toolboxId}' points to a toolbox that doesn't exist`);
}
if (toolbox.externalToolbars.indexOf(this) == -1)
toolbox.externalToolbars.push(this);
return this._toolbox = toolbox;
}
return this._toolbox = (this.parentNode &&
this.parentNode.localName == "toolbox") ?
this.parentNode : null;
]]></getter>
</property>
<constructor>
<![CDATA[
if (document.readyState == "complete") {
this._init();
} else {
// Need to wait until XUL overlays are loaded. See bug 554279.
let self = this;
document.addEventListener("readystatechange", function (event) {
if (document.readyState != "complete")
return;
document.removeEventListener("readystatechange", arguments.callee, false);
self._init();
}, false);
}
]]>
</constructor>
<method name="_init">
<body>
<![CDATA[
// Searching for the toolbox palette in the toolbar binding because
// toolbars are constructed first.
var toolbox = this.toolbox;
if (!toolbox)
return;
if (!toolbox.palette) {
// Look to see if there is a toolbarpalette.
var node = toolbox.firstChild;
while (node) {
if (node.localName == "toolbarpalette")
break;
node = node.nextSibling;
}
if (!node)
return;
// Hold on to the palette but remove it from the document.
toolbox.palette = node;
toolbox.removeChild(node);
}
// Build up our contents from the palette.
var currentSet = this.getAttribute("currentset");
if (!currentSet)
currentSet = this.getAttribute("defaultset");
if (currentSet)
this.currentSet = currentSet;
]]>
</body>
</method>
<method name="_idFromNode">
<parameter name="aNode"/>
<body>
<![CDATA[
if (aNode.getAttribute("skipintoolbarset") == "true")
return "";
switch (aNode.localName) {
case "toolbarseparator":
return "separator";
case "toolbarspring":
return "spring";
case "toolbarspacer":
return "spacer";
default:
return aNode.id;
}
]]>
</body>
</method>
<property name="currentSet">
<getter>
<![CDATA[
var node = this.firstChild;
var currentSet = [];
while (node) {
var id = this._idFromNode(node);
if (id) {
currentSet.push(id);
}
node = node.nextSibling;
}
return currentSet.join(",") || "__empty";
]]>
</getter>
<setter>
<![CDATA[
if (val == this.currentSet)
return val;
var ids = (val == "__empty") ? [] : val.split(",");
var nodeidx = 0;
var paletteItems = { }, added = { };
var palette = this.toolbox ? this.toolbox.palette : null;
// build a cache of items in the toolbarpalette
var paletteChildren = palette ? palette.childNodes : [];
for (let c = 0; c < paletteChildren.length; c++) {
let curNode = paletteChildren[c];
paletteItems[curNode.id] = curNode;
}
var children = this.childNodes;
// iterate over the ids to use on the toolbar
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
// iterate over the existing nodes on the toolbar. nodeidx is the
// spot where we want to insert items.
let found = false;
for (let c = nodeidx; c < children.length; c++) {
let curNode = children[c];
if (this._idFromNode(curNode) == id) {
// the node already exists. If c equals nodeidx, we haven't
// iterated yet, so the item is already in the right position.
// Otherwise, insert it here.
if (c != nodeidx) {
this.insertBefore(curNode, children[nodeidx]);
}
added[curNode.id] = true;
nodeidx++;
found = true;
break;
}
}
if (found) {
// move on to the next id
continue;
}
// the node isn't already on the toolbar, so add a new one.
var nodeToAdd = paletteItems[id] || this._getToolbarItem(id);
if (nodeToAdd && !(nodeToAdd.id in added)) {
added[nodeToAdd.id] = true;
this.insertBefore(nodeToAdd, children[nodeidx] || null);
nodeToAdd.setAttribute("removable", "true");
nodeidx++;
}
}
// remove any leftover removable nodes
for (let i = children.length - 1; i >= nodeidx; i--) {
let curNode = children[i];
let curNodeId = this._idFromNode(curNode);
// skip over fixed items
if (curNodeId && curNode.getAttribute("removable") == "true") {
if (palette)
palette.appendChild(curNode);
else
this.removeChild(curNode);
}
}
return val;
]]>
</setter>
</property>
<field name="_newElementCount">0</field>
<method name="_getToolbarItem">
<parameter name="aId"/>
<body>
<![CDATA[
const XUL_NS = "http://www.mozilla.org/keymaster/" +
"gatekeeper/there.is.only.xul";
var newItem = null;
switch (aId) {
// Handle special cases
case "separator":
case "spring":
case "spacer":
newItem = document.createElementNS(XUL_NS, "toolbar" + aId);
// Due to timers resolution Date.now() can be the same for
// elements created in small timeframes. So ids are
// differentiated through a unique count suffix.
newItem.id = aId + Date.now() + (++this._newElementCount);
if (aId == "spring")
newItem.flex = 1;
break;
default:
var toolbox = this.toolbox;
if (!toolbox)
break;
// look for an item with the same id, as the item may be
// in a different toolbar.
var item = document.getElementById(aId);
if (item && item.parentNode &&
item.parentNode.localName == "toolbar" &&
item.parentNode.toolbox == toolbox) {
newItem = item;
break;
}
if (toolbox.palette) {
// Attempt to locate an item with a matching ID within
// the palette.
let paletteItem = this.toolbox.palette.firstChild;
while (paletteItem) {
if (paletteItem.id == aId) {
newItem = paletteItem;
break;
}
paletteItem = paletteItem.nextSibling;
}
}
break;
}
return newItem;
]]>
</body>
</method>
<method name="insertItem">
<parameter name="aId"/>
<parameter name="aBeforeElt"/>
<parameter name="aWrapper"/>
<body>
<![CDATA[
var newItem = this._getToolbarItem(aId);
if (!newItem)
return null;
var insertItem = newItem;
// make sure added items are removable
newItem.setAttribute("removable", "true");
// Wrap the item in another node if so inclined.
if (aWrapper) {
aWrapper.appendChild(newItem);
insertItem = aWrapper;
}
// Insert the palette item into the toolbar.
if (aBeforeElt)
this.insertBefore(insertItem, aBeforeElt);
else
this.appendChild(insertItem);
return newItem;
]]>
</body>
</method>
<method name="hasCustomInteractiveItems">
<parameter name="aCurrentSet"/>
<body><![CDATA[
if (aCurrentSet == "__empty")
return false;
var defaultOrNoninteractive = (this.getAttribute("defaultset") || "")
.split(",")
.concat(["separator", "spacer", "spring"]);
return aCurrentSet.split(",").some(function (item) {
return defaultOrNoninteractive.indexOf(item) == -1;
});
]]></body>
</method>
</implementation>
</binding>
<binding id="toolbar-menubar-autohide"
extends="chrome://global/content/bindings/toolbar.xml#toolbar">
<implementation>
<constructor>
this._setInactive();
</constructor>
<destructor>
this._setActive();
</destructor>
<field name="_inactiveTimeout">null</field>
<field name="_contextMenuListener"><![CDATA[({
toolbar: this,
contextMenu: null,
get active () {
return !!this.contextMenu;
},
init: function (event) {
var node = event.target;
while (node != this.toolbar) {
if (node.localName == "menupopup")
return;
node = node.parentNode;
}
var contextMenuId = this.toolbar.getAttribute("context");
if (!contextMenuId)
return;
this.contextMenu = document.getElementById(contextMenuId);
if (!this.contextMenu)
return;
this.contextMenu.addEventListener("popupshown", this, false);
this.contextMenu.addEventListener("popuphiding", this, false);
this.toolbar.addEventListener("mousemove", this, false);
},
handleEvent: function (event) {
switch (event.type) {
case "popupshown":
this.toolbar.removeEventListener("mousemove", this, false);
break;
case "popuphiding":
case "mousemove":
this.toolbar._setInactiveAsync();
this.toolbar.removeEventListener("mousemove", this, false);
this.contextMenu.removeEventListener("popuphiding", this, false);
this.contextMenu.removeEventListener("popupshown", this, false);
this.contextMenu = null;
break;
}
}
})]]></field>
<method name="_setInactive">
<body><![CDATA[
this.setAttribute("inactive", "true");
]]></body>
</method>
<method name="_setInactiveAsync">
<body><![CDATA[
this._inactiveTimeout = setTimeout(function (self) {
if (self.getAttribute("autohide") == "true") {
self._inactiveTimeout = null;
self._setInactive();
}
}, 0, this);
]]></body>
</method>
<method name="_setActive">
<body><![CDATA[
if (this._inactiveTimeout) {
clearTimeout(this._inactiveTimeout);
this._inactiveTimeout = null;
}
this.removeAttribute("inactive");
]]></body>
</method>
</implementation>
<handlers>
<handler event="DOMMenuBarActive" action="this._setActive();"/>
<handler event="popupshowing" action="this._setActive();"/>
<handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
<handler event="DOMMenuBarInactive"><![CDATA[
if (!this._contextMenuListener.active)
this._setInactiveAsync();
]]></handler>
</handlers>
</binding>
<binding id="toolbar-drag"
extends="chrome://global/content/bindings/toolbar.xml#toolbar">
<implementation>
<field name="_dragBindingAlive">true</field>
<constructor><![CDATA[
if (!this._draggableStarted) {
this._draggableStarted = true;
try {
let tmp = {};
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
let draggableThis = new tmp.WindowDraggingElement(this);
draggableThis.mouseDownCheck = function(e) {
// Don't move while customizing.
return this._dragBindingAlive &&
this.getAttribute("customizing") != "true";
};
} catch (e) {}
}
]]></constructor>
</implementation>
</binding>
<binding id="menubar" role="xul:menubar"
extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:menubar">
<implementation>
<field name="_active">false</field>
<field name="_statusbar">null</field>
<field name="_originalStatusText">null</field>
<property name="statusbar" onget="return this.getAttribute('statusbar');"
onset="this.setAttribute('statusbar', val); return val;"/>
<method name="_updateStatusText">
<parameter name="itemText"/>
<body>
<![CDATA[
if (!this._active)
return;
var newText = itemText ? itemText : this._originalStatusText;
if (newText != this._statusbar.label)
this._statusbar.label = newText;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="DOMMenuBarActive">
<![CDATA[
if (!this.statusbar) return;
this._statusbar = document.getElementById(this.statusbar);
if (!this._statusbar)
return;
this._active = true;
this._originalStatusText = this._statusbar.label;
]]>
</handler>
<handler event="DOMMenuBarInactive">
<![CDATA[
if (!this._active)
return;
this._active = false;
this._statusbar.label = this._originalStatusText;
]]>
</handler>
<handler event="DOMMenuItemActive">this._updateStatusText(event.target.statusText);</handler>
<handler event="DOMMenuItemInactive">this._updateStatusText("");</handler>
</handlers>
</binding>
<binding id="toolbardecoration" role="xul:toolbarseparator" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
</binding>
<binding id="toolbarpaletteitem" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:button">
<content>
<xul:hbox class="toolbarpaletteitem-box" flex="1" xbl:inherits="type,place">
<children/>
</xul:hbox>
</content>
</binding>
<binding id="toolbarpaletteitem-palette" extends="chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem">
<content>
<xul:hbox class="toolbarpaletteitem-box" xbl:inherits="type,place">
<children/>
</xul:hbox>
<xul:label xbl:inherits="value=title"/>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,115 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="toolbarbuttonBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="toolbarbutton" display="xul:button" role="xul:toolbarbutton"
extends="chrome://global/content/bindings/button.xml#button-base">
<resources>
<stylesheet src="chrome://global/skin/toolbarbutton.css"/>
</resources>
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,wrap"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey,wrap"/>
</content>
</binding>
<binding id="menu" display="xul:menu"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type,consumeanchor"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey,wrap"/>
<xul:dropmarker anonid="dropmarker" type="menu"
class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
</content>
</binding>
<binding id="menu-vertical" display="xul:menu"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:hbox flex="1" align="center">
<xul:vbox flex="1" align="center">
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey,wrap"/>
</xul:vbox>
<xul:dropmarker anonid="dropmarker" type="menu"
class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
</xul:hbox>
</content>
</binding>
<binding id="menu-button" display="xul:menu"
extends="chrome://global/content/bindings/button.xml#menu-button-base">
<resources>
<stylesheet src="chrome://global/skin/toolbarbutton.css"/>
</resources>
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:toolbarbutton class="box-inherit toolbarbutton-menubutton-button"
anonid="button" flex="1" allowevents="true"
xbl:inherits="disabled,crop,image,label,accesskey,command,wrap,badge,
align,dir,pack,orient,tooltiptext=buttontooltiptext"/>
<xul:dropmarker type="menu-button" class="toolbarbutton-menubutton-dropmarker"
anonid="dropmarker" xbl:inherits="align,dir,pack,orient,disabled,label,open,consumeanchor"/>
</content>
</binding>
<binding id="toolbarbutton-image"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
<xul:image class="toolbarbutton-icon" xbl:inherits="src=image"/>
</content>
</binding>
<binding id="toolbarbutton-badged"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:stack class="toolbarbutton-badge-stack">
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
<xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
</xul:stack>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,wrap"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey,wrap"/>
</content>
</binding>
<binding id="toolbarbutton-badged-menu" display="xul:menu"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:stack class="toolbarbutton-badge-stack">
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
<xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
</xul:stack>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey,wrap"/>
<xul:dropmarker anonid="dropmarker" type="menu"
class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
</content>
</binding>
</bindings>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,128 @@
/* 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/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
.scrubber,
.volumeControl {
-moz-binding: url("chrome://global/content/bindings/videocontrols.xml#suppressChangeEvent");
}
.scrubber .scale-thumb {
-moz-binding: url("chrome://global/content/bindings/videocontrols.xml#timeThumb");
}
.playButton,
.muteButton,
.scrubber .scale-slider,
.volumeControl .scale-slider {
-moz-user-focus: none;
}
.controlBar[fullscreen-unavailable] > .fullscreenButton {
display: none;
}
.mediaControlsFrame {
direction: ltr;
/* Prevent unwanted style inheritance. See bug 554717. */
text-align: left;
list-style-image: none !important;
font: normal normal normal 100%/normal sans-serif !important;
text-decoration: none !important;
}
.controlsSpacer[hideCursor] {
cursor: none;
}
.controlsOverlay[scaled] {
-moz-box-align: center;
}
/* CSS Transitions
*
* These are overriden by the default theme; the rules here just
* provide a fallback to drive the required transitionend event
* (in case a 3rd party theme does not provide transitions).
*/
.controlBar:not([immediate]) {
transition-property: opacity;
transition-duration: 1ms;
}
.controlBar[fadeout] {
opacity: 0;
}
.volumeStack:not([immediate]) {
transition-property: opacity, margin-top;
transition-duration: 1ms, 1ms;
}
.volumeStack[fadeout] {
opacity: 0;
margin-top: 0;
}
.statusOverlay:not([immediate]) {
transition-property: opacity;
transition-duration: 1ms;
transition-delay: 750ms;
}
.statusOverlay[fadeout] {
opacity: 0;
}
/* Statistics formatting */
html|td.statLabel {
font-weight: bold;
max-width: 20%;
white-space: nowrap;
}
html|td.statValue {
max-width: 30%;
}
html|td.filename {
max-width: 80%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
html|span.statActivity > html|span {
display: none;
}
html|span.statActivity[activity="paused"] > html|span.statActivityPaused,
html|span.statActivity[activity="playing"] > html|span.statActivityPlaying,
html|span.statActivity[activity="ended"] > html|span.statActivityEnded,
html|span.statActivity[seeking] > html|span.statActivitySeeking {
display: inline;
}
.controlBar[size="hidden"],
.controlBar[size="small"] .durationBox,
.controlBar[size="small"] .durationLabel,
.controlBar[size="small"] .positionLabel,
.controlBar[size="small"] .volumeStack {
visibility: collapse;
}
.controlBar[size="small"] .scrubberStack,
.controlBar[size="small"] .backgroundBar,
.controlBar[size="small"] .bufferBar,
.controlBar[size="small"] .progressBar,
.controlBar[size="small"] .scrubber {
visibility: hidden;
}
/* Error description formatting */
.errorLabel {
display: none;
}
[error="errorAborted"] > [anonid="errorAborted"],
[error="errorNetwork"] > [anonid="errorNetwork"],
[error="errorDecode"] > [anonid="errorDecode"],
[error="errorSrcNotSupported"] > [anonid="errorSrcNotSupported"],
[error="errorNoSource"] > [anonid="errorNoSource"],
[error="errorGeneric"] > [anonid="errorGeneric"] {
display: inline;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,607 @@
<?xml version="1.0"?>
<!-- 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/. -->
<!DOCTYPE bindings [
<!ENTITY % wizardDTD SYSTEM "chrome://global/locale/wizard.dtd">
%wizardDTD;
]>
<bindings id="wizardBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="wizard-base">
<resources>
<stylesheet src="chrome://global/skin/wizard.css"/>
</resources>
</binding>
<binding id="wizard" extends="chrome://global/content/bindings/general.xml#root-element">
<resources>
<stylesheet src="chrome://global/skin/wizard.css"/>
</resources>
<content>
<xul:hbox class="wizard-header" anonid="Header"/>
<xul:deck class="wizard-page-box" flex="1" anonid="Deck">
<children includes="wizardpage"/>
</xul:deck>
<children/>
<xul:hbox class="wizard-buttons" anonid="Buttons" xbl:inherits="pagestep,firstpage,lastpage"/>
</content>
<implementation>
<property name="title" onget="return document.title;"
onset="return document.title = val;"/>
<property name="canAdvance" onget="return this._canAdvance;"
onset="this._nextButton.disabled = !val; return this._canAdvance = val;"/>
<property name="canRewind" onget="return this._canRewind;"
onset="this._backButton.disabled = !val; return this._canRewind = val;"/>
<property name="pageStep" readonly="true" onget="return this._pageStack.length"/>
<field name="pageCount">0</field>
<field name="_accessMethod">null</field>
<field name="_pageStack">null</field>
<field name="_currentPage">null</field>
<property name="wizardPages">
<getter>
<![CDATA[
var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
return this.getElementsByTagNameNS(xulns, "wizardpage");
]]>
</getter>
</property>
<property name="currentPage" onget="return this._currentPage">
<setter>
<![CDATA[
if (!val)
return val;
this._currentPage = val;
// Setting this attribute allows wizard's clients to dynamically
// change the styles of each page based on purpose of the page.
this.setAttribute("currentpageid", val.pageid);
if (this.onFirstPage) {
this.canRewind = false;
this.setAttribute("firstpage", "true");
if (/Linux/.test(navigator.platform)) {
this._backButton.setAttribute('hidden', 'true');
}
} else {
this.canRewind = true;
this.setAttribute("firstpage", "false");
if (/Linux/.test(navigator.platform)) {
this._backButton.setAttribute('hidden', 'false');
}
}
if (this.onLastPage) {
this.canAdvance = true;
this.setAttribute("lastpage", "true");
} else {
this.setAttribute("lastpage", "false");
}
this._deck.setAttribute("selectedIndex", val.pageIndex);
this._advanceFocusToPage(val);
this._adjustWizardHeader();
this._wizardButtons.onPageChange();
this._fireEvent(val, "pageshow");
return val;
]]>
</setter>
</property>
<property name="pageIndex"
onget="return this._currentPage ? this._currentPage.pageIndex : -1;">
<setter>
<![CDATA[
if (val < 0 || val >= this.pageCount)
return val;
var page = this.wizardPages[val];
this._pageStack[this._pageStack.length-1] = page;
this.currentPage = page;
return val;
]]>
</setter>
</property>
<property name="onFirstPage" readonly="true"
onget="return this._pageStack.length == 1;"/>
<property name="onLastPage" readonly="true">
<getter><![CDATA[
var cp = this.currentPage;
return cp && ((this._accessMethod == "sequential" && cp.pageIndex == this.pageCount-1) ||
(this._accessMethod == "random" && cp.next == ""));
]]></getter>
</property>
<method name="getButton">
<parameter name="aDlgType"/>
<body>
<![CDATA[
var btns = this.getElementsByAttribute("dlgtype", aDlgType);
return btns.item(0) ? btns[0] : document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aDlgType);
]]>
</body>
</method>
<field name="_canAdvance"/>
<field name="_canRewind"/>
<field name="_wizardHeader"/>
<field name="_wizardButtons"/>
<field name="_deck"/>
<field name="_backButton"/>
<field name="_nextButton"/>
<field name="_cancelButton"/>
<!-- functions to be added as oncommand listeners to the wizard buttons -->
<field name="_backFunc">(function() { document.documentElement.rewind(); })</field>
<field name="_nextFunc">(function() { document.documentElement.advance(); })</field>
<field name="_finishFunc">(function() { document.documentElement.advance(); })</field>
<field name="_cancelFunc">(function() { document.documentElement.cancel(); })</field>
<field name="_extra1Func">(function() { document.documentElement.extra1(); })</field>
<field name="_extra2Func">(function() { document.documentElement.extra2(); })</field>
<field name="_closeHandler">(function(event) {
if (document.documentElement.cancel())
event.preventDefault();
})</field>
<constructor><![CDATA[
this._canAdvance = true;
this._canRewind = false;
this._hasLoaded = false;
this._pageStack = [];
try {
// need to create string bundle manually instead of using <xul:stringbundle/>
// see bug 63370 for details
this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService)
.createBundle("chrome://global/locale/wizard.properties");
} catch (e) {
// This fails in remote XUL, which has to provide titles for all pages
// see bug 142502
}
// get anonymous content references
this._wizardHeader = document.getAnonymousElementByAttribute(this, "anonid", "Header");
this._wizardButtons = document.getAnonymousElementByAttribute(this, "anonid", "Buttons");
this._deck = document.getAnonymousElementByAttribute(this, "anonid", "Deck");
this._initWizardButton("back");
this._initWizardButton("next");
this._initWizardButton("finish");
this._initWizardButton("cancel");
this._initWizardButton("extra1");
this._initWizardButton("extra2");
this._initPages();
window.addEventListener("close", this._closeHandler, false);
// start off on the first page
this.pageCount = this.wizardPages.length;
this.advance();
// give focus to the first focusable element in the dialog
window.addEventListener("load", this._setInitialFocus, false);
]]></constructor>
<method name="getPageById">
<parameter name="aPageId"/>
<body><![CDATA[
var els = this.getElementsByAttribute("pageid", aPageId);
return els.item(0);
]]></body>
</method>
<method name="extra1">
<body><![CDATA[
if (this.currentPage)
this._fireEvent(this.currentPage, "extra1");
]]></body>
</method>
<method name="extra2">
<body><![CDATA[
if (this.currentPage)
this._fireEvent(this.currentPage, "extra2");
]]></body>
</method>
<method name="rewind">
<body><![CDATA[
if (!this.canRewind)
return;
if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
return;
if (this.currentPage && !this._fireEvent(this.currentPage, "pagerewound"))
return;
if (!this._fireEvent(this, "wizardback"))
return;
this._pageStack.pop();
this.currentPage = this._pageStack[this._pageStack.length-1];
this.setAttribute("pagestep", this._pageStack.length);
]]></body>
</method>
<method name="advance">
<parameter name="aPageId"/>
<body><![CDATA[
if (!this.canAdvance)
return;
if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
return;
if (this.currentPage && !this._fireEvent(this.currentPage, "pageadvanced"))
return;
if (this.onLastPage && !aPageId) {
if (this._fireEvent(this, "wizardfinish"))
window.setTimeout(function() {window.close();}, 1);
} else {
if (!this._fireEvent(this, "wizardnext"))
return;
var page;
if (aPageId)
page = this.getPageById(aPageId);
else {
if (this.currentPage) {
if (this._accessMethod == "random")
page = this.getPageById(this.currentPage.next);
else
page = this.wizardPages[this.currentPage.pageIndex+1];
} else
page = this.wizardPages[0];
}
if (page) {
this._pageStack.push(page);
this.setAttribute("pagestep", this._pageStack.length);
this.currentPage = page;
}
}
]]></body>
</method>
<method name="goTo">
<parameter name="aPageId"/>
<body><![CDATA[
var page = this.getPageById(aPageId);
if (page) {
this._pageStack[this._pageStack.length-1] = page;
this.currentPage = page;
}
]]></body>
</method>
<method name="cancel">
<body><![CDATA[
if (!this._fireEvent(this, "wizardcancel"))
return true;
window.close();
window.setTimeout(function() {window.close();}, 1);
return false;
]]></body>
</method>
<method name="_setInitialFocus">
<parameter name="aEvent"/>
<body>
<![CDATA[
document.documentElement._hasLoaded = true;
var focusInit =
function() {
// give focus to the first focusable element in the dialog
if (!document.commandDispatcher.focusedElement)
document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);
try {
var button =
document.documentElement._wizardButtons.defaultButton;
if (button)
window.notifyDefaultButtonLoaded(button);
} catch (e) { }
};
// Give focus after onload completes, see bug 103197.
setTimeout(focusInit, 0);
]]>
</body>
</method>
<method name="_advanceFocusToPage">
<parameter name="aPage"/>
<body>
<![CDATA[
if (!this._hasLoaded)
return;
document.commandDispatcher.advanceFocusIntoSubtree(aPage);
// if advanceFocusIntoSubtree tries to focus one of our
// dialog buttons, then remove it and put it on the root
var focused = document.commandDispatcher.focusedElement;
if (focused && focused.hasAttribute("dlgtype"))
this.focus();
]]>
</body>
</method>
<method name="_initPages">
<body><![CDATA[
var meth = "sequential";
var pages = this.wizardPages;
for (var i = 0; i < pages.length; ++i) {
var page = pages[i];
page.pageIndex = i;
if (page.next != "")
meth = "random";
}
this._accessMethod = meth;
]]></body>
</method>
<method name="_initWizardButton">
<parameter name="aName"/>
<body><![CDATA[
var btn = document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aName);
if (btn) {
btn.addEventListener("command", this["_"+aName+"Func"], false);
this["_"+aName+"Button"] = btn;
}
return btn;
]]></body>
</method>
<method name="_adjustWizardHeader">
<body><![CDATA[
var label = this.currentPage.getAttribute("label");
if (!label && this.onFirstPage && this._bundle) {
if (/Mac/.test(navigator.platform)) {
label = this._bundle.GetStringFromName("default-first-title-mac");
} else {
label = this._bundle.formatStringFromName("default-first-title", [this.title], 1);
}
} else if (!label && this.onLastPage && this._bundle) {
if (/Mac/.test(navigator.platform)) {
label = this._bundle.GetStringFromName("default-last-title-mac");
} else {
label = this._bundle.formatStringFromName("default-last-title", [this.title], 1);
}
}
this._wizardHeader.setAttribute("label", label);
this._wizardHeader.setAttribute("description", this.currentPage.getAttribute("description"));
]]></body>
</method>
<method name="_hitEnter">
<parameter name="evt"/>
<body>
<![CDATA[
if (!evt.defaultPrevented)
this.advance();
]]>
</body>
</method>
<method name="_fireEvent">
<parameter name="aTarget"/>
<parameter name="aType"/>
<body>
<![CDATA[
var event = document.createEvent("Events");
event.initEvent(aType, true, true);
// handle dom event handlers
var noCancel = aTarget.dispatchEvent(event);
// handle any xml attribute event handlers
var handler = aTarget.getAttribute("on"+aType);
if (handler != "") {
var fn = new Function("event", handler);
var returned = fn.apply(aTarget, [event]);
if (returned == false)
noCancel = false;
}
return noCancel;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="keypress" keycode="VK_RETURN"
group="system" action="this._hitEnter(event)"/>
<handler event="keypress" keycode="VK_ESCAPE" group="system">
if (!event.defaultPrevented)
this.cancel();
</handler>
</handlers>
</binding>
<binding id="wizardpage" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
<implementation>
<field name="pageIndex">-1</field>
<property name="pageid" onget="return this.getAttribute('pageid');"
onset="this.setAttribute('pageid', val);"/>
<property name="next" onget="return this.getAttribute('next');"
onset="this.setAttribute('next', val);
this.parentNode._accessMethod = 'random';
return val;"/>
</implementation>
</binding>
#ifdef XP_MACOSX
<binding id="wizard-header" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
<content>
<xul:stack class="wizard-header-stack" flex="1">
<xul:vbox class="wizard-header-box-1">
<xul:vbox class="wizard-header-box-text">
<xul:label class="wizard-header-label" xbl:inherits="xbl:text=label"/>
</xul:vbox>
</xul:vbox>
<xul:hbox class="wizard-header-box-icon">
<xul:spacer flex="1"/>
<xul:image class="wizard-header-icon" xbl:inherits="src=iconsrc"/>
</xul:hbox>
</xul:stack>
</content>
</binding>
<binding id="wizard-buttons" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
<content>
<xul:vbox flex="1">
<xul:hbox class="wizard-buttons-btm">
<xul:button class="wizard-button" dlgtype="extra1" hidden="true"/>
<xul:button class="wizard-button" dlgtype="extra2" hidden="true"/>
<xul:button label="&button-cancel-mac.label;" class="wizard-button" dlgtype="cancel"/>
<xul:spacer flex="1"/>
<xul:button label="&button-back-mac.label;" accesskey="&button-back-mac.accesskey;"
class="wizard-button wizard-nav-button" dlgtype="back"/>
<xul:button label="&button-next-mac.label;" accesskey="&button-next-mac.accesskey;"
class="wizard-button wizard-nav-button" dlgtype="next"
default="true" xbl:inherits="hidden=lastpage" />
<xul:button label="&button-finish-mac.label;" class="wizard-button"
dlgtype="finish" default="true" xbl:inherits="hidden=hidefinishbutton" />
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<method name="onPageChange">
<body><![CDATA[
this.setAttribute("hidefinishbutton", !(this.getAttribute("lastpage") == "true"));
]]></body>
</method>
</implementation>
</binding>
#else
<binding id="wizard-header" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
<content>
<xul:hbox class="wizard-header-box-1" flex="1">
<xul:vbox class="wizard-header-box-text" flex="1">
<xul:label class="wizard-header-label" xbl:inherits="xbl:text=label"/>
<xul:label class="wizard-header-description" xbl:inherits="xbl:text=description"/>
</xul:vbox>
<xul:image class="wizard-header-icon" xbl:inherits="src=iconsrc"/>
</xul:hbox>
</content>
</binding>
<binding id="wizard-buttons" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
<content>
<xul:vbox class="wizard-buttons-box-1" flex="1">
<xul:separator class="wizard-buttons-separator groove"/>
<xul:hbox class="wizard-buttons-box-2">
<xul:button class="wizard-button" dlgtype="extra1" hidden="true"/>
<xul:button class="wizard-button" dlgtype="extra2" hidden="true"/>
<xul:spacer flex="1" anonid="spacer"/>
#ifdef XP_UNIX
<xul:button label="&button-cancel-unix.label;" class="wizard-button"
dlgtype="cancel" icon="cancel"/>
<xul:spacer style="width: 24px"/>
<xul:button label="&button-back-unix.label;" accesskey="&button-back-unix.accesskey;"
class="wizard-button" dlgtype="back" icon="go-back"/>
<xul:deck class="wizard-next-deck" anonid="WizardButtonDeck">
<xul:hbox>
<xul:button label="&button-finish-unix.label;" class="wizard-button"
dlgtype="finish" default="true" flex="1"/>
</xul:hbox>
<xul:hbox>
<xul:button label="&button-next-unix.label;" accesskey="&button-next-unix.accesskey;"
class="wizard-button" dlgtype="next" icon="go-forward"
default="true" flex="1"/>
</xul:hbox>
</xul:deck>
#else
<xul:button label="&button-back-win.label;" accesskey="&button-back-win.accesskey;"
class="wizard-button" dlgtype="back" icon="go-back"/>
<xul:deck class="wizard-next-deck" anonid="WizardButtonDeck">
<xul:hbox>
<xul:button label="&button-finish-win.label;" class="wizard-button"
dlgtype="finish" default="true" flex="1"/>
</xul:hbox>
<xul:hbox>
<xul:button label="&button-next-win.label;" accesskey="&button-next-win.accesskey;"
class="wizard-button" dlgtype="next" icon="go-forward"
default="true" flex="1"/>
</xul:hbox>
</xul:deck>
<xul:button label="&button-cancel-win.label;" class="wizard-button"
dlgtype="cancel" icon="cancel"/>
#endif
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<field name="_wizardButtonDeck" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "WizardButtonDeck");
</field>
<method name="onPageChange">
<body><![CDATA[
if (this.getAttribute("lastpage") == "true") {
this._wizardButtonDeck.setAttribute("selectedIndex", 0);
} else {
this._wizardButtonDeck.setAttribute("selectedIndex", 1);
}
]]></body>
</method>
<property name="defaultButton" readonly="true">
<getter><![CDATA[
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var buttons = this._wizardButtonDeck.selectedPanel
.getElementsByTagNameNS(kXULNS, "button");
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].getAttribute("default") == "true" &&
!buttons[i].hidden && !buttons[i].disabled)
return buttons[i];
}
return null;
]]></getter>
</property>
</implementation>
</binding>
#endif
</bindings>