mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-17 12:47:30 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
2515
toolkit/content/widgets/autocomplete.xml
Normal file
2515
toolkit/content/widgets/autocomplete.xml
Normal file
File diff suppressed because it is too large
Load diff
1571
toolkit/content/widgets/browser.xml
Normal file
1571
toolkit/content/widgets/browser.xml
Normal file
File diff suppressed because it is too large
Load diff
389
toolkit/content/widgets/button.xml
Normal file
389
toolkit/content/widgets/button.xml
Normal 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>
|
||||
84
toolkit/content/widgets/checkbox.xml
Normal file
84
toolkit/content/widgets/checkbox.xml
Normal 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>
|
||||
565
toolkit/content/widgets/colorpicker.xml
Normal file
565
toolkit/content/widgets/colorpicker.xml
Normal 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>
|
||||
|
||||
45
toolkit/content/widgets/datetimebox.css
Normal file
45
toolkit/content/widgets/datetimebox.css
Normal 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;
|
||||
}
|
||||
807
toolkit/content/widgets/datetimebox.xml
Normal file
807
toolkit/content/widgets/datetimebox.xml
Normal 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>
|
||||
1301
toolkit/content/widgets/datetimepicker.xml
Normal file
1301
toolkit/content/widgets/datetimepicker.xml
Normal file
File diff suppressed because it is too large
Load diff
181
toolkit/content/widgets/datetimepopup.xml
Normal file
181
toolkit/content/widgets/datetimepopup.xml
Normal 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>
|
||||
448
toolkit/content/widgets/dialog.xml
Normal file
448
toolkit/content/widgets/dialog.xml
Normal 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>
|
||||
195
toolkit/content/widgets/editor.xml
Normal file
195
toolkit/content/widgets/editor.xml
Normal 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>
|
||||
86
toolkit/content/widgets/expander.xml
Normal file
86
toolkit/content/widgets/expander.xml
Normal 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>
|
||||
|
||||
|
||||
96
toolkit/content/widgets/filefield.xml
Normal file
96
toolkit/content/widgets/filefield.xml
Normal 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>
|
||||
1397
toolkit/content/widgets/findbar.xml
Normal file
1397
toolkit/content/widgets/findbar.xml
Normal file
File diff suppressed because it is too large
Load diff
231
toolkit/content/widgets/general.xml
Normal file
231
toolkit/content/widgets/general.xml
Normal 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>
|
||||
44
toolkit/content/widgets/groupbox.xml
Normal file
44
toolkit/content/widgets/groupbox.xml
Normal 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>
|
||||
1144
toolkit/content/widgets/listbox.xml
Normal file
1144
toolkit/content/widgets/listbox.xml
Normal file
File diff suppressed because it is too large
Load diff
286
toolkit/content/widgets/menu.xml
Normal file
286
toolkit/content/widgets/menu.xml
Normal 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>
|
||||
606
toolkit/content/widgets/menulist.xml
Normal file
606
toolkit/content/widgets/menulist.xml
Normal 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>
|
||||
551
toolkit/content/widgets/notification.xml
Normal file
551
toolkit/content/widgets/notification.xml
Normal 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>
|
||||
304
toolkit/content/widgets/numberbox.xml
Normal file
304
toolkit/content/widgets/numberbox.xml
Normal 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>
|
||||
43
toolkit/content/widgets/optionsDialog.xml
Normal file
43
toolkit/content/widgets/optionsDialog.xml
Normal 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>
|
||||
650
toolkit/content/widgets/popup.xml
Normal file
650
toolkit/content/widgets/popup.xml
Normal 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>
|
||||
1411
toolkit/content/widgets/preferences.xml
Normal file
1411
toolkit/content/widgets/preferences.xml
Normal file
File diff suppressed because it is too large
Load diff
116
toolkit/content/widgets/progressmeter.xml
Normal file
116
toolkit/content/widgets/progressmeter.xml
Normal 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>
|
||||
526
toolkit/content/widgets/radio.xml
Normal file
526
toolkit/content/widgets/radio.xml
Normal 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>
|
||||
591
toolkit/content/widgets/remote-browser.xml
Normal file
591
toolkit/content/widgets/remote-browser.xml
Normal 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>
|
||||
39
toolkit/content/widgets/resizer.xml
Normal file
39
toolkit/content/widgets/resizer.xml
Normal 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>
|
||||
589
toolkit/content/widgets/richlistbox.xml
Normal file
589
toolkit/content/widgets/richlistbox.xml
Normal 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>
|
||||
232
toolkit/content/widgets/scale.xml
Normal file
232
toolkit/content/widgets/scale.xml
Normal 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>
|
||||
35
toolkit/content/widgets/scrollbar.xml
Normal file
35
toolkit/content/widgets/scrollbar.xml
Normal 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>
|
||||
908
toolkit/content/widgets/scrollbox.xml
Normal file
908
toolkit/content/widgets/scrollbox.xml
Normal 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>
|
||||
96
toolkit/content/widgets/spinbuttons.xml
Normal file
96
toolkit/content/widgets/spinbuttons.xml
Normal 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>
|
||||
514
toolkit/content/widgets/spinner.js
Normal file
514
toolkit/content/widgets/spinner.js
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
37
toolkit/content/widgets/splitter.xml
Normal file
37
toolkit/content/widgets/splitter.xml
Normal 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>
|
||||
96
toolkit/content/widgets/stringbundle.xml
Normal file
96
toolkit/content/widgets/stringbundle.xml
Normal 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>
|
||||
892
toolkit/content/widgets/tabbox.xml
Normal file
892
toolkit/content/widgets/tabbox.xml
Normal 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>
|
||||
|
||||
386
toolkit/content/widgets/text.xml
Normal file
386
toolkit/content/widgets/text.xml
Normal 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>
|
||||
646
toolkit/content/widgets/textbox.xml
Normal file
646
toolkit/content/widgets/textbox.xml
Normal 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="©Cmd.label;" accesskey="©Cmd.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="©Cmd.label;" accesskey="©Cmd.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>
|
||||
418
toolkit/content/widgets/timekeeper.js
Normal file
418
toolkit/content/widgets/timekeeper.js
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
277
toolkit/content/widgets/timepicker.js
Normal file
277
toolkit/content/widgets/timepicker.js
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
590
toolkit/content/widgets/toolbar.xml
Normal file
590
toolkit/content/widgets/toolbar.xml
Normal 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>
|
||||
|
||||
115
toolkit/content/widgets/toolbarbutton.xml
Normal file
115
toolkit/content/widgets/toolbarbutton.xml
Normal 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>
|
||||
1561
toolkit/content/widgets/tree.xml
Normal file
1561
toolkit/content/widgets/tree.xml
Normal file
File diff suppressed because it is too large
Load diff
128
toolkit/content/widgets/videocontrols.css
Normal file
128
toolkit/content/widgets/videocontrols.css
Normal 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;
|
||||
}
|
||||
2027
toolkit/content/widgets/videocontrols.xml
Normal file
2027
toolkit/content/widgets/videocontrols.xml
Normal file
File diff suppressed because it is too large
Load diff
607
toolkit/content/widgets/wizard.xml
Normal file
607
toolkit/content/widgets/wizard.xml
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue