summaryrefslogtreecommitdiffstats
path: root/comm/suite/components/bindings
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/components/bindings')
-rw-r--r--comm/suite/components/bindings/datetimepicker.xml1316
-rw-r--r--comm/suite/components/bindings/findbar.xml162
-rw-r--r--comm/suite/components/bindings/general.xml37
-rw-r--r--comm/suite/components/bindings/generalBindings.xml31
-rw-r--r--comm/suite/components/bindings/jar.mn20
-rw-r--r--comm/suite/components/bindings/moz.build7
-rw-r--r--comm/suite/components/bindings/notification.xml2423
-rw-r--r--comm/suite/components/bindings/numberbox.xml217
-rw-r--r--comm/suite/components/bindings/preferences.xml817
-rw-r--r--comm/suite/components/bindings/prefwindow.xml548
-rw-r--r--comm/suite/components/bindings/spinbuttons.xml92
-rw-r--r--comm/suite/components/bindings/textbox.xml251
-rw-r--r--comm/suite/components/bindings/toolbar-xpfe.xml333
-rw-r--r--comm/suite/components/bindings/toolbar.xml579
14 files changed, 6833 insertions, 0 deletions
diff --git a/comm/suite/components/bindings/datetimepicker.xml b/comm/suite/components/bindings/datetimepicker.xml
new file mode 100644
index 0000000000..7475b6c04e
--- /dev/null
+++ b/comm/suite/components/bindings/datetimepicker.xml
@@ -0,0 +1,1316 @@
+<?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 % datetimepickerDTD SYSTEM
+ "chrome://communicator/locale/datetimepicker.dtd">
+ %datetimepickerDTD;
+]>
+
+<bindings id="timepickerBindings"
+ 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="datetimepicker-base"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+
+ <content align="center">
+ <xul:hbox class="datetimepicker-input-box" align="center"
+ xbl:inherits="context,disabled,readonly">
+ <xul:moz-input-box class="textbox-input-box datetimepicker-input-subbox"
+ align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-one"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:moz-input-box>
+ <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
+ <xul:moz-input-box class="textbox-input-box datetimepicker-input-subbox"
+ align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-two"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:moz-input-box>
+ <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
+ <xul:moz-input-box class="textbox-input-box datetimepicker-input-subbox"
+ align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-three"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:moz-input-box>
+ <xul:moz-input-box class="textbox-input-box datetimepicker-input-subbox"
+ align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:moz-input-box>
+ </xul:hbox>
+ <xul:spinbuttons anonid="buttons" xbl:inherits="disabled"
+ onup="this.parentNode._increaseOrDecrease(1);"
+ ondown="this.parentNode._increaseOrDecrease(-1);"/>
+ </content>
+
+ <implementation>
+ <field name="_dateValue">null</field>
+ <field name="_fieldOne">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+ </field>
+ <field name="_fieldTwo">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+ </field>
+ <field name="_fieldThree">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+ </field>
+ <field name="_fieldAMPM">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-ampm");
+ </field>
+ <field name="_separatorFirst">
+ document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+ </field>
+ <field name="_separatorSecond">
+ document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+ </field>
+ <field name="_lastFocusedField">null</field>
+ <field name="_hasEntry">true</field>
+ <field name="_valueEntered">false</field>
+ <field name="attachedControl">null</field>
+
+ <property name="_currentField" readonly="true">
+ <getter>
+ var focusedInput = document.activeElement;
+ if (focusedInput == this._fieldOne ||
+ focusedInput == this._fieldTwo ||
+ focusedInput == this._fieldThree ||
+ focusedInput == this._fieldAMPM)
+ return focusedInput;
+ return this._lastFocusedField || this._fieldOne;
+ </getter>
+ </property>
+
+ <property name="dateValue" onget="return new Date(this._dateValue);">
+ <setter>
+ <![CDATA[
+ if (!(val instanceof Date))
+ throw "Invalid Date";
+
+ this._setValueNoSync(val);
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="readOnly" onset="if (val) this.setAttribute('readonly', 'true');
+ else this.removeAttribute('readonly'); return val;"
+ onget="return this.getAttribute('readonly') == 'true';"/>
+
+ <method name="_fireEvent">
+ <parameter name="aEventName"/>
+ <parameter name="aTarget"/>
+ <body>
+ var event = document.createEvent("Events");
+ event.initEvent(aEventName, true, true);
+ return !aTarget.dispatchEvent(event);
+ </body>
+ </method>
+
+ <method name="_setValueOnChange">
+ <parameter name="aField"/>
+ <body>
+ <![CDATA[
+ if (!this._hasEntry)
+ return;
+
+ if (aField == this._fieldOne ||
+ aField == this._fieldTwo ||
+ aField == this._fieldThree) {
+ var value = Number(aField.value);
+ if (isNaN(value))
+ value = 0;
+
+ value = this._constrainValue(aField, value, true);
+ this._setFieldValue(aField, value);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_init">
+ <body/>
+ </method>
+
+ <constructor>
+ this._init();
+
+ var cval = this.getAttribute("value");
+ if (cval) {
+ try {
+ this.value = cval;
+ return;
+ } catch (ex) { }
+ }
+ this.dateValue = new Date();
+ </constructor>
+
+ <destructor>
+ if (this.attachedControl) {
+ this.attachedControl.attachedControl = null;
+ this.attachedControl = null;
+ }
+ </destructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="focus" phase="capturing">
+ <![CDATA[
+ var target = event.originalTarget;
+ if (target == this._fieldOne ||
+ target == this._fieldTwo ||
+ target == this._fieldThree ||
+ target == this._fieldAMPM)
+ this._lastFocusedField = target;
+ ]]>
+ </handler>
+
+ <handler event="keypress">
+ <![CDATA[
+ if (this._hasEntry && event.charCode &&
+ this._currentField != this._fieldAMPM &&
+ !(event.altKey || event.ctrlKey || event.metaKey) &&
+ (event.charCode < 48 || event.charCode > 57))
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="keypress" keycode="VK_UP">
+ if (this._hasEntry)
+ this._increaseOrDecrease(1);
+ </handler>
+ <handler event="keypress" keycode="VK_DOWN">
+ if (this._hasEntry)
+ this._increaseOrDecrease(-1);
+ </handler>
+
+ <handler event="input">
+ this._valueEntered = true;
+ </handler>
+
+ <handler event="change">
+ this._setValueOnChange(event.originalTarget);
+ </handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="timepicker"
+#ifdef MOZ_SUITE
+ extends="chrome://communicator/content/bindings/datetimepicker.xml#datetimepicker-base">
+#else
+ extends="chrome://messenger/content/datetimepicker.xml#datetimepicker-base">
+#endif
+ <implementation>
+ <field name="is24HourClock">false</field>
+ <field name="hourLeadingZero">false</field>
+ <field name="minuteLeadingZero">true</field>
+ <field name="secondLeadingZero">true</field>
+ <field name="amIndicator">"AM"</field>
+ <field name="pmIndicator">"PM"</field>
+
+ <field name="hourField">null</field>
+ <field name="minuteField">null</field>
+ <field name="secondField">null</field>
+
+ <property name="value">
+ <getter>
+ <![CDATA[
+ var minute = this._dateValue.getMinutes();
+ if (minute < 10)
+ minute = "0" + minute;
+
+ var second = this._dateValue.getSeconds();
+ if (second < 10)
+ second = "0" + second;
+ return this._dateValue.getHours() + ":" + minute + ":" + second;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var items = val.match(/^([0-9]{1,2})\:([0-9]{1,2})\:?([0-9]{1,2})?$/);
+ if (!items)
+ throw "Invalid Time";
+
+ var dt = this.dateValue;
+ dt.setHours(items[1]);
+ dt.setMinutes(items[2]);
+ dt.setSeconds(items[3] ? items[3] : 0);
+ this.dateValue = dt;
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="hour" onget="return this._dateValue.getHours();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 23)
+ throw "Invalid Hour";
+ this._setFieldValue(this.hourField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="minute" onget="return this._dateValue.getMinutes();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 59)
+ throw "Invalid Minute";
+ this._setFieldValue(this.minuteField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="second" onget="return this._dateValue.getSeconds();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 59)
+ throw "Invalid Second";
+ this._setFieldValue(this.secondField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="isPM">
+ <getter>
+ <![CDATA[
+ return (this.hour >= 12);
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val) {
+ if (this.hour < 12)
+ this.hour += 12;
+ } else if (this.hour >= 12) {
+ this.hour -= 12;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="hideSeconds">
+ <getter>
+ return (this.getAttribute("hideseconds") == "true");
+ </getter>
+ <setter>
+ if (val)
+ this.setAttribute("hideseconds", "true");
+ else
+ this.removeAttribute("hideseconds");
+ if (this.secondField)
+ this.secondField.parentNode.collapsed = val;
+ this._separatorSecond.collapsed = val;
+ return val;
+ </setter>
+ </property>
+ <property name="increment">
+ <getter>
+ <![CDATA[
+ var increment = this.getAttribute("increment");
+ increment = Number(increment);
+ if (isNaN(increment) || increment <= 0 || increment >= 60)
+ return 1;
+ return increment;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (typeof val == "number")
+ this.setAttribute("increment", val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="_setValueNoSync">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var dt = new Date(aValue);
+ if (!isNaN(dt)) {
+ this._dateValue = dt;
+ this.setAttribute("value", this.value);
+ this._updateUI(this.hourField, this.hour);
+ this._updateUI(this.minuteField, this.minute);
+ this._updateUI(this.secondField, this.second);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecrease">
+ <parameter name="aDir"/>
+ <body>
+ <![CDATA[
+ if (this.disabled || this.readOnly)
+ return;
+
+ var field = this._currentField;
+ if (this._valueEntered)
+ this._setValueOnChange(field);
+
+ if (field == this._fieldAMPM) {
+ this.isPM = !this.isPM;
+ this._fireEvent("change", this);
+ } else {
+ var oldval;
+ var change = aDir;
+ if (field == this.hourField) {
+ oldval = this.hour;
+ } else if (field == this.minuteField) {
+ oldval = this.minute;
+ change *= this.increment;
+ } else if (field == this.secondField) {
+ oldval = this.second;
+ }
+
+ var newval = this._constrainValue(field, oldval + change, false);
+
+ if (field == this.hourField)
+ this.hour = newval;
+ else if (field == this.minuteField)
+ this.minute = newval;
+ else if (field == this.secondField)
+ this.second = newval;
+
+ if (oldval != newval)
+ this._fireEvent("change", this);
+ }
+ field.select();
+ ]]>
+ </body>
+ </method>
+ <method name="_setFieldValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (aField == this.hourField)
+ this._dateValue.setHours(aValue);
+ else if (aField == this.minuteField)
+ this._dateValue.setMinutes(aValue);
+ else if (aField == this.secondField)
+ this._dateValue.setSeconds(aValue);
+
+ this.setAttribute("value", this.value);
+ this._updateUI(aField, aValue);
+
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(this._dateValue);
+ ]]>
+ </body>
+ </method>
+ <method name="_updateUI">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ this._valueEntered = false;
+
+ var prependZero = false;
+ if (aField == this.hourField) {
+ prependZero = this.hourLeadingZero;
+ if (!this.is24HourClock) {
+ if (aValue > 12)
+ aValue -= 12;
+ else if (aValue == 0)
+ aValue = 12;
+ this._fieldAMPM.value = this.isPM ? this.pmIndicator :
+ this.amIndicator;
+ }
+ } else if (aField == this.minuteField) {
+ prependZero = this.minuteLeadingZero;
+ } else if (aField == this.secondField) {
+ prependZero = this.secondLeadingZero;
+ }
+
+ if (prependZero && aValue < 10)
+ aField.value = "0" + aValue;
+ else
+ aField.value = aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_constrainValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <parameter name="aNoWrap"/>
+ <body>
+ <![CDATA[
+ // aNoWrap is true when the user entered a value, so just
+ // constrain within limits. If false, the value is being
+ // incremented or decremented, so wrap around values
+ var max = 60;
+ if (aField == this.hourField) {
+ max = 24;
+ // User input in the hour field should be adjusted as
+ // needed for 12-hour vs. 24-hour time.
+ if (aNoWrap && !this.is24HourClock) {
+ if (aValue && aValue < 12 && this.isPM)
+ aValue += 12;
+ else if (aValue == 12 && !this.isPM)
+ aValue = 0;
+ }
+ }
+ if (aValue < 0)
+ return aNoWrap ? 0 : max + aValue;
+ if (aValue >= max)
+ return aNoWrap ? max - 1 : aValue - max;
+ return aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_init">
+ <body>
+ <![CDATA[
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ this.hourField = this._fieldOne;
+ this.minuteField = this._fieldTwo;
+ this.secondField = this._fieldThree;
+
+ var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;
+
+ // XXX TODO: The following hack should be fixed once Intl.Locale arrives in bug 1433303.
+ var locale = Services.locale.regionalPrefsLocales[0];
+ if (locale.includes("-u-"))
+ locale += "-ca-gregory-nu-latn";
+ else
+ locale += "-u-ca-gregory-nu-latn";
+ var dtf = new Services.intl.DateTimeFormat(locale, { timeStyle: "long" });
+
+ var pmTime = dtf.format(new Date(2000, 0, 1, 16, 7, 9));
+ var numberFields = pmTime.match(numberOrder);
+ if (numberFields) {
+ this._separatorFirst.value = numberFields[3];
+ this._separatorSecond.value = numberFields[5];
+ if (Number(numberFields[2]) > 12)
+ this.is24HourClock = true;
+ else
+ this.pmIndicator = numberFields[1] || numberFields[7];
+ }
+
+ var amTime = dtf.format(new Date(2000, 0, 1, 1, 7, 9));
+ numberFields = amTime.match(numberOrder);
+ if (numberFields) {
+ this.hourLeadingZero = (numberFields[2].length > 1);
+ this.minuteLeadingZero = (numberFields[4].length > 1);
+ this.secondLeadingZero = (numberFields[6].length > 1);
+
+ if (!this.is24HourClock) {
+ this.amIndicator = numberFields[1] || numberFields[7];
+ if (numberFields[1]) {
+ var mfield = this._fieldAMPM.parentNode;
+ var mcontainer = mfield.parentNode;
+ mcontainer.insertBefore(mfield, mcontainer.firstChild);
+ }
+ var size = (numberFields[1] || numberFields[7]).length;
+ if (this.pmIndicator.length > size)
+ size = this.pmIndicator.length;
+ this._fieldAMPM.size = size;
+ this._fieldAMPM.maxLength = size;
+ } else {
+ this._fieldAMPM.parentNode.collapsed = true;
+ }
+ }
+
+ this.hideSeconds = this.hideSeconds;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress">
+ <![CDATA[
+ // just allow any printable character to switch the AM/PM state
+ if (event.charCode && !this.disabled && !this.readOnly &&
+ this._currentField == this._fieldAMPM) {
+ this.isPM = !this.isPM;
+ this._fieldAMPM.select();
+ this._fireEvent("change", this);
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="datepicker"
+ extends="chrome://communicator/content/bindings/datetimepicker.xml#datetimepicker-base">
+ <implementation>
+ <field name="yearLeadingZero">false</field>
+ <field name="monthLeadingZero">true</field>
+ <field name="dateLeadingZero">true</field>
+
+ <field name="yearField"/>
+ <field name="monthField"/>
+ <field name="dateField"/>
+
+ <property name="value">
+ <getter>
+ <![CDATA[
+ var month = this._dateValue.getMonth();
+ month = (month < 9) ? month = "0" + ++month : month + 1;
+
+ var date = this._dateValue.getDate();
+ if (date < 10)
+ date = "0" + date;
+ return this._dateValue.getFullYear() + "-" + month + "-" + date;
+ ]]>
+
+ </getter>
+ <setter>
+ <![CDATA[
+ var results = val.match(/^([0-9]{1,4})\-([0-9]{1,2})\-([0-9]{1,2})$/);
+ if (!results)
+ throw "Invalid Date";
+
+ this.dateValue = new Date(results[1] + "/" + results[2] + "/" + results[3]);
+ this.setAttribute("value", this.value);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="year" onget="return this._dateValue.getFullYear();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 1 || valnum > 9999)
+ throw "Invalid Year";
+ this._setFieldValue(this.yearField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="month" onget="return this._dateValue.getMonth();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 11)
+ throw "Invalid Month";
+ this._setFieldValue(this.monthField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="date" onget="return this._dateValue.getDate();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 1 || valnum > 31)
+ throw "Invalid Date";
+ this._setFieldValue(this.dateField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="open" onget="return false;" onset="return val;"/>
+
+ <property name="displayedMonth" onget="return this.month;"
+ onset="this.month = val; return val;"/>
+ <property name="displayedYear" onget="return this.year;"
+ onset="this.year = val; return val;"/>
+
+ <method name="_setValueNoSync">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var dt = new Date(aValue);
+ if (!isNaN(dt)) {
+ this._dateValue = dt;
+ this.setAttribute("value", this.value);
+ this._updateUI(this.yearField, this.year);
+ this._updateUI(this.monthField, this.month);
+ this._updateUI(this.dateField, this.date);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecrease">
+ <parameter name="aDir"/>
+ <body>
+ <![CDATA[
+ if (this.disabled || this.readOnly)
+ return;
+
+ var field = this._currentField;
+ if (this._valueEntered)
+ this._setValueOnChange(field);
+
+ var oldval;
+ if (field == this.yearField)
+ oldval = this.year;
+ else if (field == this.monthField)
+ oldval = this.month;
+ else if (field == this.dateField)
+ oldval = this.date;
+
+ var newval = this._constrainValue(field, oldval + aDir, false);
+
+ if (field == this.yearField)
+ this.year = newval;
+ else if (field == this.monthField)
+ this.month = newval;
+ else if (field == this.dateField)
+ this.date = newval;
+
+ if (oldval != newval)
+ this._fireEvent("change", this);
+ field.select();
+ ]]>
+ </body>
+ </method>
+ <method name="_setFieldValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (aField == this.yearField) {
+ let oldDate = this.date;
+ this._dateValue.setFullYear(aValue);
+ if (oldDate != this.date) {
+ this._dateValue.setDate(0);
+ this._updateUI(this.dateField, this.date);
+ }
+ } else if (aField == this.monthField) {
+ let oldDate = this.date;
+ this._dateValue.setMonth(aValue);
+ if (oldDate != this.date) {
+ this._dateValue.setDate(0);
+ this._updateUI(this.dateField, this.date);
+ }
+ } else if (aField == this.dateField) {
+ this._dateValue.setDate(aValue);
+ }
+
+ this.setAttribute("value", this.value);
+ this._updateUI(aField, aValue);
+
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(this._dateValue);
+ ]]>
+ </body>
+ </method>
+ <method name="_updateUI">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ this._valueEntered = false;
+
+ var prependZero = false;
+ if (aField == this.yearField) {
+ if (this.yearLeadingZero) {
+ aField.value = ("000" + aValue).slice(-4);
+ return;
+ }
+ } else if (aField == this.monthField) {
+ aValue++;
+ prependZero = this.monthLeadingZero;
+ } else if (aField == this.dateField) {
+ prependZero = this.dateLeadingZero;
+ }
+ if (prependZero && aValue < 10)
+ aField.value = "0" + aValue;
+ else
+ aField.value = aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_constrainValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <parameter name="aNoWrap"/>
+ <body>
+ <![CDATA[
+ // the month will be 1 to 12 if entered by the user, so subtract 1
+ if (aNoWrap && aField == this.monthField)
+ aValue--;
+
+ if (aField == this.dateField) {
+ if (aValue < 1)
+ return new Date(this.year, this.month + 1, 0).getDate();
+
+ var currentMonth = this.month;
+ var dt = new Date(this.year, currentMonth, aValue);
+ return (dt.getMonth() != currentMonth ? 1 : aValue);
+ }
+ var min = (aField == this.monthField) ? 0 : 1;
+ var max = (aField == this.monthField) ? 11 : 9999;
+ if (aValue < min)
+ return aNoWrap ? min : max;
+ if (aValue > max)
+ return aNoWrap ? max : min;
+ return aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_init">
+ <body>
+ <![CDATA[
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ // We'll default to YYYY/MM/DD to start.
+ var yfield = "input-one";
+ var mfield = "input-two";
+ var dfield = "input-three";
+ var twoDigitYear = false;
+ this.yearLeadingZero = true;
+ this.monthLeadingZero = true;
+ this.dateLeadingZero = true;
+
+ var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;
+
+ // XXX TODO: The following hack should be fixed once Intl.Locale arrives in bug 1433303.
+ var locale = Services.locale.regionalPrefsLocales[0];
+ if (locale.includes("-u-"))
+ locale += "-ca-gregory-nu-latn";
+ else
+ locale += "-u-ca-gregory-nu-latn";
+ var dtf = new Services.intl.DateTimeFormat(locale, { dateStyle: "short" });
+
+ var dt = dtf.format(new Date(2002, 9, 4));
+ var numberFields = dt.match(numberOrder);
+ if (numberFields) {
+ this._separatorFirst.value = numberFields[3];
+ this._separatorSecond.value = numberFields[5];
+
+ var yi = 2, mi = 4, di = 6;
+
+ function fieldForNumber(i) {
+ if (i == 2)
+ return "input-one";
+ if (i == 4)
+ return "input-two";
+ return "input-three";
+ }
+
+ for (var i = 1; i < numberFields.length; i++) {
+ switch (Number(numberFields[i])) {
+ case 2:
+ twoDigitYear = true; // fall through
+ case 2002:
+ yi = i;
+ yfield = fieldForNumber(i);
+ break;
+ case 9:
+ case 10:
+ mi = i;
+ mfield = fieldForNumber(i);
+ break;
+ case 4:
+ di = i;
+ dfield = fieldForNumber(i);
+ break;
+ }
+ }
+
+ this.yearLeadingZero = (numberFields[yi].length > 1);
+ this.monthLeadingZero = (numberFields[mi].length > 1);
+ this.dateLeadingZero = (numberFields[di].length > 1);
+ }
+
+ this.yearField = document.getAnonymousElementByAttribute(this, "anonid", yfield);
+ if (!twoDigitYear)
+ this.yearField.parentNode.classList.add("datetimepicker-input-subbox", "datetimepicker-year");
+ this.monthField = document.getAnonymousElementByAttribute(this, "anonid", mfield);
+ this.dateField = document.getAnonymousElementByAttribute(this, "anonid", dfield);
+
+ this._fieldAMPM.parentNode.collapsed = true;
+ this.yearField.size = twoDigitYear ? 2 : 4;
+ this.yearField.maxLength = twoDigitYear ? 2 : 4;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ </binding>
+
+ <binding id="datepicker-grid"
+ extends="chrome://communicator/content/bindings/datetimepicker.xml#datepicker">
+ <content>
+ <vbox class="datepicker-mainbox"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox class="datepicker-monthbox" align="center">
+ <button class="datepicker-previous datepicker-button" type="repeat"
+ xbl:inherits="disabled"
+ oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(-1);"/>
+ <spacer flex="1"/>
+ <deck anonid="monthlabeldeck">
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ </deck>
+ <label anonid="yearlabel" class="datepicker-gridlabel"/>
+ <spacer flex="1"/>
+ <button class="datepicker-next datepicker-button" type="repeat"
+ xbl:inherits="disabled"
+ oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(1);"/>
+ </hbox>
+ <grid class="datepicker-grid" role="grid">
+ <columns>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ </columns>
+ <rows anonid="datebox">
+ <row anonid="dayofweekbox">
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </content>
+
+ <implementation>
+ <field name="_hasEntry">false</field>
+ <field name="_weekStart">&firstdayofweek.default;</field>
+ <field name="_displayedDate">null</field>
+ <field name="_todayItem">null</field>
+
+ <field name="yearField">
+ document.getAnonymousElementByAttribute(this, "anonid", "yearlabel");
+ </field>
+ <field name="monthField">
+ document.getAnonymousElementByAttribute(this, "anonid", "monthlabeldeck");
+ </field>
+ <field name="dateField">
+ document.getAnonymousElementByAttribute(this, "anonid", "datebox");
+ </field>
+
+ <field name="_selectedItem">null</field>
+
+ <property name="selectedItem" onget="return this._selectedItem">
+ <setter>
+ <![CDATA[
+ if (!val.value)
+ return val;
+ if (val.parentNode.parentNode != this.dateField)
+ return val;
+
+ if (this._selectedItem)
+ this._selectedItem.removeAttribute("selected");
+ this._selectedItem = val;
+ val.setAttribute("selected", "true");
+ this._displayedDate.setDate(val.value);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="displayedMonth">
+ <getter>
+ return this._displayedDate.getMonth();
+ </getter>
+ <setter>
+ this._updateUI(this.monthField, val, true);
+ return val;
+ </setter>
+ </property>
+ <property name="displayedYear">
+ <getter>
+ return this._displayedDate.getFullYear();
+ </getter>
+ <setter>
+ this._updateUI(this.yearField, val, true);
+ return val;
+ </setter>
+ </property>
+
+ <method name="_init">
+ <body>
+ <![CDATA[
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ // XXX TODO: The following hack should be fixed once Intl.Locale arrives in bug 1433303.
+ var locale = Services.locale.regionalPrefsLocales[0];
+ if (locale.includes("-u-"))
+ locale += "-ca-gregory";
+ else
+ locale += "-u-ca-gregory";
+ var dtfMonth = new Services.intl.DateTimeFormat(locale, {month: "long", timeZone: "UTC"});
+ var dtfWeekday = new Services.intl.DateTimeFormat(locale, {weekday: "narrow"});
+
+ var monthLabel = this.monthField.firstChild;
+ var tempDate = new Date(Date.UTC(2005, 0, 1));
+ for (var month = 0; month < 12; month++) {
+ tempDate.setUTCMonth(month);
+ monthLabel.setAttribute("value", dtfMonth.format(tempDate));
+ monthLabel = monthLabel.nextSibling;
+ }
+
+ var fdow = Number(this.getAttribute("firstdayofweek"));
+ if (!isNaN(fdow) && fdow >= 0 && fdow <= 6)
+ this._weekStart = fdow;
+
+ var weekbox = document.getAnonymousElementByAttribute(this, "anonid", "dayofweekbox").childNodes;
+ var date = new Date();
+ date.setDate(date.getDate() - (date.getDay() - this._weekStart));
+ for (var i = 0; i < weekbox.length; i++) {
+ weekbox[i].value = dtfWeekday.format(date);
+ date.setDate(date.getDate() + 1);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_setValueNoSync">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var dt = new Date(aValue);
+ if (!isNaN(dt)) {
+ this._dateValue = dt;
+ this.setAttribute("value", this.value);
+ this._updateUI();
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_updateUI">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <parameter name="aCheckMonth"/>
+ <body>
+ <![CDATA[
+ var date;
+ var currentMonth;
+ if (aCheckMonth) {
+ if (!this._displayedDate)
+ this._displayedDate = this.dateValue;
+
+ var expectedMonth = aValue;
+ if (aField == this.monthField) {
+ this._displayedDate.setMonth(aValue);
+ } else {
+ expectedMonth = this._displayedDate.getMonth();
+ this._displayedDate.setFullYear(aValue);
+ }
+
+ if (expectedMonth != -1 && expectedMonth != 12 &&
+ expectedMonth != this._displayedDate.getMonth()) {
+ // If the month isn't what was expected, then the month overflowed.
+ // Setting the date to 0 will go back to the last day of the right month.
+ this._displayedDate.setDate(0);
+ }
+
+ date = new Date(this._displayedDate);
+ currentMonth = this._displayedDate.getMonth();
+ } else {
+ var samemonth = (this._displayedDate &&
+ this._displayedDate.getMonth() == this.month &&
+ this._displayedDate.getFullYear() == this.year);
+ if (samemonth) {
+ var items = this.dateField.getElementsByAttribute("value", this.date);
+ if (items.length)
+ this.selectedItem = items[0];
+ return;
+ }
+
+ date = this.dateValue;
+ this._displayedDate = new Date(date);
+ currentMonth = this.month;
+ }
+
+ if (this._todayItem) {
+ this._todayItem.removeAttribute("today");
+ this._todayItem = null;
+ }
+
+ if (this._selectedItem) {
+ this._selectedItem.removeAttribute("selected");
+ this._selectedItem = null;
+ }
+
+ // Update the month and year title
+ this.monthField.selectedIndex = currentMonth;
+ this.yearField.setAttribute("value", date.getFullYear());
+
+ date.setDate(1);
+ var firstWeekday = (7 + date.getDay() - this._weekStart) % 7;
+ date.setDate(date.getDate() - firstWeekday);
+
+ var today = new Date();
+ var datebox = this.dateField;
+ for (var k = 1; k < datebox.childNodes.length; k++) {
+ var row = datebox.childNodes[k];
+ for (var i = 0; i < 7; i++) {
+ var item = row.childNodes[i];
+
+ if (currentMonth == date.getMonth()) {
+ item.value = date.getDate();
+
+ // highlight today
+ if (this._isSameDay(today, date)) {
+ this._todayItem = item;
+ item.setAttribute("today", "true");
+ }
+
+ // highlight the selected date
+ if (this._isSameDay(this._dateValue, date)) {
+ this._selectedItem = item;
+ item.setAttribute("selected", "true");
+ }
+ } else {
+ item.value = "";
+ }
+
+ date.setDate(date.getDate() + 1);
+ }
+ }
+
+ this._fireEvent("monthchange", this);
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecreaseDateFromEvent">
+ <parameter name="aEvent"/>
+ <parameter name="aDiff"/>
+ <body>
+ <![CDATA[
+ if (aEvent.originalTarget == this && !this.disabled && !this.readOnly) {
+ var newdate = this.dateValue;
+ newdate.setDate(newdate.getDate() + aDiff);
+ this.dateValue = newdate;
+ this._fireEvent("change", this);
+ }
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecreaseMonth">
+ <parameter name="aDir"/>
+ <body>
+ <![CDATA[
+ if (!this.disabled) {
+ var month = this._displayedDate ? this._displayedDate.getMonth() :
+ this.month;
+ this._updateUI(this.monthField, month + aDir, true);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_isSameDay">
+ <parameter name="aDate1"/>
+ <parameter name="aDate2"/>
+ <body>
+ <![CDATA[
+ return (aDate1 && aDate2 &&
+ aDate1.getDate() == aDate2.getDate() &&
+ aDate1.getMonth() == aDate2.getMonth() &&
+ aDate1.getFullYear() == aDate2.getFullYear());
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="click">
+ <![CDATA[
+ if (event.button != 0 || this.disabled || this.readOnly)
+ return;
+
+ var target = event.originalTarget;
+ if (target.classList.contains("datepicker-gridlabel") &&
+ target != this.selectedItem) {
+ this.selectedItem = target;
+ this._dateValue = new Date(this._displayedDate);
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(this._dateValue);
+ this._fireEvent("change", this);
+
+ if (this.attachedControl && "open" in this.attachedControl)
+ this.attachedControl.open = false; // close the popup
+ }
+ ]]>
+ </handler>
+ <handler event="MozMousePixelScroll" preventdefault="true"/>
+ <handler event="DOMMouseScroll" preventdefault="true">
+ <![CDATA[
+ this._increaseOrDecreaseMonth(event.detail < 0 ? -1 : 1);
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_LEFT"
+ action="this._increaseOrDecreaseDateFromEvent(event, -1);"/>
+ <handler event="keypress" keycode="VK_RIGHT"
+ action="this._increaseOrDecreaseDateFromEvent(event, 1);"/>
+ <handler event="keypress" keycode="VK_UP"
+ action="this._increaseOrDecreaseDateFromEvent(event, -7);"/>
+ <handler event="keypress" keycode="VK_DOWN"
+ action="this._increaseOrDecreaseDateFromEvent(event, 7);"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" preventdefault="true"
+ action="this._increaseOrDecreaseMonth(-1);"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" preventdefault="true"
+ action="this._increaseOrDecreaseMonth(1);"/>
+ </handlers>
+ </binding>
+
+ <binding id="datepicker-popup" display="xul:menu"
+ extends="chrome://communicator/content/bindings/datetimepicker.xml#datepicker">
+ <content align="center">
+ <xul:moz-input-box class="textbox-input-box datetimepicker-input-box"
+ align="center"
+ allowevents="true"
+ xbl:inherits="context,disabled,readonly">
+ <xul:hbox class="datetimepicker-input-subbox" align="baseline">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-one"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
+ <xul:hbox class="datetimepicker-input-subbox" align="baseline">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-two"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
+ <xul:hbox class="datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-three"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:hbox class="datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ </xul:moz-input-box>
+ <xul:spinbuttons anonid="buttons" xbl:inherits="disabled" allowevents="true"
+ onup="this.parentNode._increaseOrDecrease(1);"
+ ondown="this.parentNode._increaseOrDecrease(-1);"/>
+ <xul:dropmarker class="datepicker-dropmarker" xbl:inherits="disabled"/>
+ <xul:panel onpopupshown="this.firstChild.focus();" level="top">
+ <xul:datepicker anonid="grid" type="grid" class="datepicker-popupgrid"
+ xbl:inherits="disabled,readonly,firstdayofweek"/>
+ </xul:panel>
+ </content>
+ <implementation>
+ <constructor>
+ var grid = document.getAnonymousElementByAttribute(this, "anonid", "grid");
+ this.attachedControl = grid;
+ grid.attachedControl = this;
+ grid._setValueNoSync(this._dateValue);
+ </constructor>
+ <property name="open" onget="return this.hasAttribute('open');">
+ <setter>
+ <![CDATA[
+ if (this.hasMenu())
+ this.openMenu(val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="displayedMonth">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth;
+ </getter>
+ <setter>
+ document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth = val;
+ return val;
+ </setter>
+ </property>
+ <property name="displayedYear">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear;
+ </getter>
+ <setter>
+ document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear = val;
+ return val;
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/comm/suite/components/bindings/findbar.xml b/comm/suite/components/bindings/findbar.xml
new file mode 100644
index 0000000000..dde9c5ebd9
--- /dev/null
+++ b/comm/suite/components/bindings/findbar.xml
@@ -0,0 +1,162 @@
+<?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/. -->
+
+<!--
+ SeaMonkey Flexible Findbar
+
+ The binding implemented here mostly works like its toolkit ancestor,
+ except that it will not appear during a manually triggered type ahead find
+ if accessibility.typeaheadfind.usefindbar is false, and the automatic
+ typeahead find is controlled by the accessibility.typeaheadfind.autostart
+ preference instead of the accessibility.typeaheadfind preference.
+
+ This allows the in status bar type ahead find to be used in place of the
+ findbar implementation and allows the in status bar type ahead find
+ to only need to cache the accessibility.typeaheadfind preference branch.
+-->
+
+<!DOCTYPE bindings>
+
+<bindings id="findbarBindings"
+ xmlns="http://www.mozilla.org/xbl">
+
+ <binding id="findbar"
+ extends="chrome://global/content/bindings/findbar.xml#findbar">
+ <implementation>
+ <constructor><![CDATA[
+ var prefsvc =
+ Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+
+ prefsvc.removeObserver("accessibility.typeaheadfind",
+ this._observer);
+ prefsvc.addObserver("accessibility.typeaheadfind.autostart",
+ this._suiteObserver);
+ prefsvc.addObserver("accessibility.typeaheadfind.usefindbar",
+ this._suiteObserver);
+
+ this._findAsYouType =
+ prefsvc.getBoolPref("accessibility.typeaheadfind.autostart");
+ this._useFindbar =
+ prefsvc.getBoolPref("accessibility.typeaheadfind.usefindbar");
+ ]]></constructor>
+
+ <field name="_suiteObserver"><![CDATA[({
+ _self: this,
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIObserver) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(aSubject, aTopic, aPrefName) {
+ if (aTopic != "nsPref:changed")
+ return;
+
+ var prefsvc =
+ aSubject.QueryInterface(Ci.nsIPrefBranch);
+
+ switch (aPrefName) {
+ case "accessibility.typeaheadfind.autostart":
+ this._self._findAsYouType = prefsvc.getBoolPref(aPrefName);
+ this._self._updateBrowserWithState();
+ break;
+ case "accessibility.typeaheadfind.usefindbar":
+ this._self._useFindbar = prefsvc.getBoolPref(aPrefName);
+ break;
+ }
+ }
+ })]]></field>
+
+ <!-- This is necessary because the destructor isn't called when
+ we are removed from a document that is not destroyed. This
+ needs to be explicitly called in this case -->
+ <method name="destroy">
+ <body><![CDATA[
+ if (this._destroyed)
+ return;
+ this._destroyed = true;
+
+ this.browser = null;
+
+ var prefsvc =
+ Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
+ this._observer);
+ prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
+ this._observer);
+ prefsvc.removeObserver("findbar.entireword", this._observer);
+ prefsvc.removeObserver("findbar.highlightAll", this._observer);
+ prefsvc.removeObserver("findbar.modalHighlight", this._observer);
+
+ prefsvc.removeObserver("accessibility.typeaheadfind.usefindbar",
+ this._suiteObserver);
+ prefsvc.removeObserver("accessibility.typeaheadfind.autostart",
+ this._suiteObserver);
+
+ // Clear all timers that might still be running.
+ this._cancelTimers();
+ ]]></body>
+ </method>
+
+ <method name="_updateBrowserWithState">
+ <body><![CDATA[
+ window.messageManager.broadcastAsyncMessage("Findbar:UpdateState", {
+ findMode: this._findMode,
+ findAsYouType: this._findAsYouType,
+ });
+ ]]></body>
+ </method>
+
+ <method name="receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ switch (aMessage.name) {
+ case "Findbar:Mouseup":
+ if (!this.hidden && this._findMode != this.FIND_NORMAL)
+ this.close();
+ break;
+
+ case "Findbar:Keypress":
+ if (this._useFindbar)
+ return this._onBrowserKeypress(aMessage.data.fakeEvent,
+ aMessage.data.shouldFastFind);
+ break;
+ }
+ return undefined;
+ ]]></body>
+ </method>
+
+ <method name="startFastFind">
+ <parameter name="aMode"/>
+ <body><![CDATA[
+ if (this._findMode == aMode && this._quickFindTimeout) {
+ this._findField.select();
+ this._findField.focus();
+ return;
+ }
+
+ // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
+ // it doesn't get confused by a lingering value
+ this._findField.value = "";
+
+ if (this._quickFindTimeout)
+ clearTimeout(this._quickFindTimeout);
+ this.open(aMode);
+ this._setFindCloseTimeout();
+ this._findField.select();
+ this._findField.focus();
+
+ this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/comm/suite/components/bindings/general.xml b/comm/suite/components/bindings/general.xml
new file mode 100644
index 0000000000..9df42b3548
--- /dev/null
+++ b/comm/suite/components/bindings/general.xml
@@ -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="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="statusbarpanel-iconic" display="xul:button" role="xul:button"
+ extends="chrome://communicator/content/bindings/generalBindings.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://communicator/content/bindings/generalBindings.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="statusbarpanel-backgroundbox" display="xul:button"
+ extends="chrome://communicator/content/bindings/general.xml#statusbarpanel-iconic-text">
+ <content>
+ <xul:hbox class="statusbarpanel-contentbox" xbl:inherits="dir">
+ <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
+ <xul:label class="statusbarpanel-text" xbl:inherits="value=label,crop"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/comm/suite/components/bindings/generalBindings.xml b/comm/suite/components/bindings/generalBindings.xml
new file mode 100644
index 0000000000..385e1166a2
--- /dev/null
+++ b/comm/suite/components/bindings/generalBindings.xml
@@ -0,0 +1,31 @@
+<?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: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="menu-vertical"
+ 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>
+
+</bindings>
diff --git a/comm/suite/components/bindings/jar.mn b/comm/suite/components/bindings/jar.mn
new file mode 100644
index 0000000000..6e231a38da
--- /dev/null
+++ b/comm/suite/components/bindings/jar.mn
@@ -0,0 +1,20 @@
+# 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/.
+
+comm.jar:
+ content/communicator/customizeToolbar.css (../customizeToolbar.css)
+ content/communicator/customizeToolbar.js (../customizeToolbar.js)
+* content/communicator/customizeToolbar.xhtml (../customizeToolbar.xhtml)
+ content/communicator/bindings/datetimepicker.xml (datetimepicker.xml)
+ content/communicator/bindings/findbar.xml (findbar.xml)
+ content/communicator/bindings/general.xml (general.xml)
+ content/communicator/bindings/generalBindings.xml (generalBindings.xml)
+ content/communicator/bindings/notification.xml (notification.xml)
+ content/communicator/bindings/numberbox.xml (numberbox.xml)
+ content/communicator/bindings/preferences.xml (preferences.xml)
+ content/communicator/bindings/spinbuttons.xml (spinbuttons.xml)
+* content/communicator/bindings/textbox.xml (textbox.xml)
+ content/communicator/bindings/toolbar.xml (toolbar.xml)
+ content/communicator/bindings/toolbar-xpfe.xml (toolbar-xpfe.xml)
+* content/communicator/bindings/prefwindow.xml (prefwindow.xml)
diff --git a/comm/suite/components/bindings/moz.build b/comm/suite/components/bindings/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/comm/suite/components/bindings/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/suite/components/bindings/notification.xml b/comm/suite/components/bindings/notification.xml
new file mode 100644
index 0000000000..8965df67cb
--- /dev/null
+++ b/comm/suite/components/bindings/notification.xml
@@ -0,0 +1,2423 @@
+<?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 % commNotificationDTD SYSTEM "chrome://communicator/locale/notification.dtd">
+ %commNotificationDTD;
+<!ENTITY % globalNotificationDTD SYSTEM "chrome://global/locale/notification.dtd">
+ %globalNotificationDTD;
+]>
+
+<bindings id="browserNotificationBindings"
+ 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="browser-notificationbox"
+ extends="chrome://global/content/bindings/notification.xml#notificationbox">
+ <implementation implements="nsIObserver, nsIFormSubmitObserver, nsIWebProgressListener, nsIWebProgressListener2, nsIDOMEventListener">
+ <field name="_stringBundle" readonly="true">
+ <![CDATA[
+ Services.strings.createBundle("chrome://communicator/locale/notification.properties");
+ ]]>
+ </field>
+
+ <field name="_brandStringBundle" readonly="true">
+ <![CDATA[
+ Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ ]]>
+ </field>
+
+ <field name="_placesBundle" readonly="true">
+ <![CDATA[
+ Services.strings.createBundle("chrome://communicator/locale/places/places.properties");
+ ]]>
+ </field>
+
+ <field name="wrappedJSObject">this</field>
+
+ <field name="_activeBrowser">null</field>
+
+ <property name="activeBrowser" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._activeBrowser) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var browsers = this.getElementsByTagNameNS(XUL_NS, "browser");
+ for (var i = 0; this._activeBrowser = browsers.item(i); i++)
+ if (!this._activeBrowser.hidden)
+ break;
+ }
+ return this._activeBrowser;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_cwu">null</field>
+
+ <property name="contentWindowUtils" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._cwu)
+ this._cwu = this.activeBrowser.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ return this._cwu;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="usePrivateBrowsing" readonly="true">
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .usePrivateBrowsing
+ </field>
+
+ <method name="onDocumentChange">
+ <body>
+ <![CDATA[
+ this.crashNotified = false;
+ if (this.popupCount) {
+ this.popupCount = 0;
+ this.notifyPopupCountChanged();
+ }
+ this.removeTransientNotifications();
+ ]]>
+ </body>
+ </method>
+
+ <method name="addProgressListener">
+ <body>
+ <![CDATA[
+ if (this.activeBrowser && !this._addedProgressListener) {
+ this.activeBrowser.webProgress
+ .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_SECURITY |
+ Ci.nsIWebProgress.NOTIFY_LOCATION |
+ Ci.nsIWebProgress.NOTIFY_REFRESH);
+ this._addedProgressListener = true;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="lastMessage">"EnterInsecureMessage"</field>
+ <field name="lastState">0</field>
+
+ <method name="onSecurityChange">
+ <parameter name="aWebProgress"/>
+ <parameter name="aRequest"/>
+ <parameter name="aState"/>
+ <body>
+ <![CDATA[
+ if (aState < 0)
+ aState = this.lastState;
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ var pref = "security.warn_leaving_secure";
+ var message = "EnterInsecureMessage";
+ var priority = this.PRIORITY_WARNING_LOW;
+ var pane = "ssl_pane";
+ var buttons = [];
+ if (aState & nsIWebProgressListener.STATE_IS_SECURE) {
+ pref = "security.warn_entering_secure";
+ message = "EnterSecureMessage";
+ priority = this.PRIORITY_INFO_LOW;
+ } else if (aState & nsIWebProgressListener.STATE_IS_BROKEN) {
+ pref = "security.warn_viewing_mixed";
+ message = "MixedContentMessage";
+ priority = this.PRIORITY_CRITICAL_LOW;
+ }
+
+ if (aState & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT &&
+ Services.prefs.getBoolPref("security.warn_mixed_active_content")) {
+ pref = "security.warn_mixed_active_content";
+ message = "MixedActiveContentMessage";
+ priority = this.PRIORITY_CRITICAL_LOW;
+ } else if (aState & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT &&
+ Services.prefs.getBoolPref("security.warn_mixed_active_content")) {
+ pref = "security.warn_mixed_active_content";
+ message = "BlockedActiveContentMessage";
+ priority = this.PRIORITY_INFO_LOW;
+ this.lastState = aState & ~nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+ const nsIWebNavigation = Ci.nsIWebNavigation;
+ buttons = [{
+ label: this._stringBundle.GetStringFromName("SecurityKeepBlocking.label"),
+ accessKey: this._stringBundle.GetStringFromName("SecurityKeepBlocking.accesskey"),
+ callback: this.onSecurityChange.bind(this, null, null, -1)
+ }, {
+ label: this._stringBundle.GetStringFromName("SecurityUnblock.label"),
+ accessKey: this._stringBundle.GetStringFromName("SecurityUnblock.accesskey"),
+ callback: this.reloadPage.bind(this,
+ nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT |
+ nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE)
+ }];
+ } else if (aState & nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT &&
+ Services.prefs.getBoolPref("privacy.warn_tracking_content")) {
+ pref = "privacy.warn_tracking_content";
+ message = "TrackingContentMessage";
+ priority = this.PRIORITY_WARNING_LOW;
+ pane = "security_pane";
+ } else if (aState & nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT &&
+ Services.prefs.getBoolPref("privacy.warn_tracking_content")) {
+ pref = "privacy.warn_tracking_content";
+ message = "BlockedTrackingContentMessage";
+ priority = this.PRIORITY_INFO_LOW;
+ pane = "security_pane";
+ if (!this.usePrivateBrowsing) {
+ this.lastState = aState & ~nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
+ buttons = [{
+ label: this._stringBundle.GetStringFromName("SecurityKeepBlocking.label"),
+ accessKey: this._stringBundle.GetStringFromName("SecurityKeepBlocking.accesskey"),
+ callback: this.onSecurityChange.bind(this, null, null, -1)
+ }, {
+ label: this._stringBundle.GetStringFromName("SecurityUnblock.label"),
+ accessKey: this._stringBundle.GetStringFromName("SecurityUnblock.accesskey"),
+ callback: () => {
+ Services.perms.add(this.activeBrowser.currentURI,
+ "trackingprotection",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ this.reloadPage();
+ }
+ }];
+ }
+ } else if (aState & nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT &&
+ Services.prefs.getBoolPref("security.warn_mixed_display_content")) {
+ pref = "security.warn_mixed_display_content";
+ message = "MixedDisplayContentMessage";
+ priority = this.PRIORITY_WARNING_LOW;
+ } else if (aState & nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT &&
+ Services.prefs.getBoolPref("security.warn_mixed_display_content")) {
+ pref = "security.warn_mixed_display_content";
+ message = "BlockedDisplayContentMessage";
+ priority = this.PRIORITY_INFO_LOW;
+ }
+
+ if (this.lastMessage == message)
+ return false;
+
+ var box = this.getNotificationWithValue(this.lastMessage);
+ if (box)
+ box.close();
+
+ this.lastMessage = message;
+
+ if (!Services.prefs.getBoolPref(pref))
+ return true;
+
+ if ("goPreferences" in window) {
+ buttons.push({
+ label: this._stringBundle.GetStringFromName("SecurityPreferences.label"),
+ accessKey: this._stringBundle.GetStringFromName("SecurityPreferences.accesskey"),
+ callback: function() {
+ goPreferences(pane);
+ return true;
+ }
+ });
+ }
+ var text = this._stringBundle.GetStringFromName(message);
+ box = this.appendNotification(text, message, null, priority, buttons);
+ box.persistence = 1;
+ box.timeout = Date.now() + 20000; // 20 seconds
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="onLocationChange">
+ <parameter name="aWebProgress" />
+ <parameter name="aRequest" />
+ <parameter name="aLocation" />
+ <parameter name="aFlags" />
+ <body>
+ <![CDATA[
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ if (aWebProgress.DOMWindow == this.activeBrowser.contentWindow &&
+ !(aFlags & nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT))
+ this.onDocumentChange();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onRefreshAttempted">
+ <parameter name="aWebProgress" />
+ <parameter name="aURI" />
+ <parameter name="aDelay" />
+ <parameter name="aSameURI" />
+ <body>
+ <![CDATA[
+ if (Services.prefs.getBoolPref("accessibility.blockautorefresh")) {
+ let brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ let refreshButtonText =
+ this._stringBundle.GetStringFromName("refreshBlocked.goButton");
+ let refreshButtonAccesskey =
+ this._stringBundle.GetStringFromName("refreshBlocked.goButton.accesskey");
+ let message =
+ this._stringBundle.formatStringFromName(aSameURI ? "refreshBlocked.refreshLabel"
+ : "refreshBlocked.redirectLabel",
+ [brandShortName], 1);
+ let notification = this.getNotificationWithValue("refresh-blocked");
+ if (notification) {
+ notification.label = message;
+ } else {
+ let buttons = [{
+ label: refreshButtonText,
+ accessKey: refreshButtonAccesskey,
+ callback: function (aNotification, aButton) {
+ var refreshURI = aNotification.webProgress
+ .QueryInterface(Ci.nsIRefreshURI);
+ refreshURI.forceRefreshURI(aNotification.uri, null,
+ aNotification.delay, true);
+ }
+ }];
+ notification =
+ this.appendNotification(message, "refresh-blocked", null,
+ this.PRIORITY_INFO_MEDIUM, buttons);
+ }
+ // In the case of a meta refresh, the location has already
+ // changed. But in the case of an HTTP header refresh, the
+ // location changes synchronously after this call returns.
+ // Set the persistence to 1 temporarily to stop this from
+ // immediately clobbering the location bar (bug 516441),
+ // but set the persistence back to 0 as soon as possible.
+ setTimeout(function() { notification.persistence = 0; }, 0);
+ notification.persistence = 1;
+ notification.webProgress = aWebProgress;
+ notification.uri = aURI;
+ notification.delay = aDelay;
+ return false;
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="notify">
+ <parameter name="aFormElement"/>
+ <parameter name="aWindow"/>
+ <parameter name="aActionURI"/>
+ <parameter name="aCancel"/>
+ <body>
+ <![CDATA[
+ aCancel.value = false;
+ if (!aFormElement || !aWindow || !aActionURI)
+ return;
+
+ var browser = this.activeBrowser;
+
+ // inactive sidebar panel:
+ if (!browser || !browser.docShell || !browser.docShell.securityUI)
+ return;
+
+ // not our window:
+ if (aWindow.top != browser.contentWindow)
+ return;
+
+ // pref disabled:
+ if (!Services.prefs.getBoolPref("security.warn_submit_insecure"))
+ return;
+
+ // HTTPS uninteresting:
+ if (aActionURI.schemeIs("https"))
+ return;
+
+ // javascript doesn't hit the network:
+ if (aActionURI.schemeIs("javascript"))
+ return;
+
+ // PSM handles HTTPS source:
+ var uri;
+ try {
+ uri = aFormElement.nodePrincipal.URI;
+ } catch (e) {}
+ if (!uri)
+ uri = aFormElement.ownerDocument.documentURIObject;
+ if (uri.schemeIs("https"))
+ return;
+
+ var warn = { value: true };
+ var prompt = Services.prompt;
+ aCancel.value = prompt.confirmEx(
+ aWindow,
+ this._stringBundle.GetStringFromName("SecurityTitle"),
+ this._stringBundle.GetStringFromName("PostToInsecureFromInsecureMessage"),
+ prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0 +
+ prompt.BUTTON_TITLE_CANCEL * prompt.BUTTON_POS_1,
+ this._stringBundle.GetStringFromName("PostToInsecureContinue"),
+ null, null,
+ this._stringBundle.GetStringFromName("PostToInsecureFromInsecureShowAgain"),
+ warn) != 0;
+ if (!aCancel.value)
+ Services.prefs.setBoolPref("security.warn_submit_insecure", warn.value);
+ ]]>
+ </body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aSubject" />
+ <parameter name="aTopic" />
+ <parameter name="aData" />
+ <body>
+ <![CDATA[
+ var browser = this.activeBrowser;
+
+ // inactive sidebar panel:
+ if (!browser || !browser.docShell)
+ return;
+
+ switch (aTopic) {
+ case "indexedDB-permissions-prompt":
+ var requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
+ var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+ if (contentWindow.top == browser.contentWindow)
+ this.promptIndexedDB(requestor, contentWindow, "permissions");
+ break;
+
+ case "indexedDB-quota-prompt":
+ var requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
+ var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+ if (contentWindow.top == browser.contentWindow)
+ this.promptIndexedDB(requestor, contentWindow, "quota", aData);
+ break;
+
+ case "indexedDB-quota-cancel":
+ var requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
+ var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+ if (contentWindow.top == browser.contentWindow)
+ this.cancelIndexedDB(requestor, contentWindow, "quota", aData);
+ break;
+
+ case "addon-install-blocked":
+ var installInfo = aSubject.wrappedJSObject;
+ if (installInfo.browser == browser)
+ this.addonInstallBlocked(installInfo);
+ break;
+
+ case "addon-install-complete":
+ var installInfo = aSubject.wrappedJSObject;
+ if (installInfo.browser == browser)
+ this.addonInstallComplete(installInfo);
+ break;
+
+ case "addon-install-disabled":
+ var installInfo = aSubject.wrappedJSObject;
+ if (installInfo.browser == browser)
+ this.addonInstallDisabled(installInfo);
+ break;
+
+ case "addon-install-failed":
+ var installInfo = aSubject.wrappedJSObject;
+ if (installInfo.browser == browser)
+ this.addonInstallFailed(installInfo);
+ break;
+
+ case "addon-install-started":
+ var installInfo = aSubject.wrappedJSObject;
+ if (installInfo.browser == browser)
+ this.addonInstallStarted(installInfo);
+ break;
+
+ case "offline-cache-update-completed":
+ var doc = browser.contentDocument;
+ if (!doc.documentElement)
+ break;
+
+ var manifest = doc.documentElement.getAttribute("manifest");
+ if (!manifest)
+ break;
+
+ try {
+ var manifestURI = Services.io.newURI(manifest,
+ doc.characterSet,
+ doc.documentURIObject);
+ var cacheUpdate =
+ aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
+ if (manifestURI.equals(cacheUpdate.manifestURI))
+ this.checkUsage(manifestURI);
+ } catch (e) {
+ alert(e);
+ }
+ break;
+
+ case "nsPref:changed":
+ if (aData == "privacy.popups.showBrowserMessage") {
+ if (Services.prefs.getBoolPref(aData))
+ return;
+
+ var popupNotification = this.getNotificationWithValue("popup-blocked");
+ if (popupNotification)
+ this.removeNotification(popupNotification);
+ }
+
+ if (aData == "dom.disable_open_during_load") {
+ // remove notifications when popup blocking has been turned off
+ if (Services.prefs.getBoolPref(aData) || !this.popupCount)
+ return;
+
+ var popupNotification = this.getNotificationWithValue("popup-blocked");
+ if (popupNotification)
+ this.removeNotification(popupNotification);
+ this.popupCount = 0;
+ this.notifyPopupCountChanged();
+ }
+ break;
+
+ case "perm-changed":
+ // If all permissions are cleared aSubject is null.
+ if (!aSubject)
+ return;
+
+ var permission = aSubject.QueryInterface(Ci.nsIPermission);
+ if (permission.type != "popup" || aData != "added" || !this.popupCount)
+ return;
+
+ if (permission.matchesURI(this.activeBrowser.currentURI, false)) {
+ var popupNotification = this.getNotificationWithValue("popup-blocked");
+ if (popupNotification)
+ this.removeNotification(popupNotification);
+ this.popupCount = 0;
+ this.notifyPopupCountChanged();
+ }
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="CrashSubmit">null</field>
+
+ <field name="crashNotified">false</field>
+
+ <method name="openURLPref">
+ <parameter name="aPref"/>
+ <body>
+ <![CDATA[
+ var url = Services.urlFormatter.formatURLPref(aPref);
+ var nsIBrowserDOMWindow = Ci.nsIBrowserDOMWindow;
+
+ var browserWin;
+ var whereToOpen = Services.prefs.getIntPref("browser.link.open_external");
+
+ if (whereToOpen != nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
+ browserWin = Services.wm.getMostRecentWindow("navigator:browser");
+ }
+
+ if (!browserWin) {
+ var browserURL = "chrome://navigator/content/navigator.xul";
+ try {
+ browserURL = Services.prefs.getCharPref("browser.chromeURL");
+ } catch (ex) {}
+
+ window.openDialog(browserURL, "_blank", "chrome,all,dialog=no", url);
+ } else {
+ if (whereToOpen == nsIBrowserDOMWindow.OPEN_CURRENTWINDOW)
+ browserWin.loadURI(url);
+ else {
+ // new tab
+ var browser = browserWin.getBrowser();
+ var newTab = browser.addTab(url);
+ browser.selectedTab = newTab;
+ }
+ browserWin.content.focus();
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="makeNicePluginName">
+ <parameter name="aName"/>
+ <body>
+ <![CDATA[
+ // Clean up the plugin name by stripping off any trailing version
+ // numbers or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
+ // Do this by first stripping the numbers, etc. off the end, and
+ // then removing "Plugin" (and then trimming to get rid of any
+ // whitespace). Otherwise, something like "Java(TM) Plug-in
+ // 1.7.0_07" gets mangled.
+ var newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
+ return newName;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getPluginUI">
+ <parameter name="aPlugin"/>
+ <parameter name="aAnonId"/>
+ <body>
+ <![CDATA[
+ return aPlugin.ownerDocument.getAnonymousElementByAttribute(aPlugin, "anonid", aAnonId);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addLinkClickCallback">
+ <parameter name="linkNode"/>
+ <parameter name="callback"/>
+ <body>
+ <![CDATA[
+ // XXX just doing (callback)(arg) was giving a same-origin error. bug?
+
+ // We use event bubbling for the event listeners.
+ let callbackArgs = Array.from(arguments).slice(2);
+ linkNode.addEventListener("click",
+ function(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+ if (aEvent.button != 0)
+ return;
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ aEvent ];
+ callback.apply(this, callbackArgs);
+ }.bind(this));
+
+ linkNode.addEventListener("keydown",
+ function(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+ if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
+ return;
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ aEvent ];
+ callback.apply(this, callbackArgs);
+ }.bind(this));
+ ]]>
+ </body>
+ </method>
+
+ <!-- Callback for user clicking "submit a report" link -->
+ <method name="submitReport">
+ <parameter name="pluginDumpID"/>
+ <parameter name="plugin"/>
+ <body>
+ <![CDATA[
+ var keyVals = {};
+ if (plugin) {
+ let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
+ if (userComment)
+ keyVals.PluginUserComment = userComment;
+ if (this.getPluginUI(plugin, "submitURLOptIn").checked)
+ keyVals.PluginContentURL = plugin.ownerDocument.URL;
+ }
+ this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals,
+ recordSubmission: true });
+ ]]>
+ </body>
+ </method>
+
+ <!-- Callback for user clicking a "reload page" link -->
+ <method name="reloadPage">
+ <parameter name="flags"/>
+ <body>
+ <![CDATA[
+ this.activeBrowser.reloadWithFlags(flags);
+ ]]>
+ </body>
+ </method>
+
+ <!-- Callback for user clicking the help icon -->
+ <method name="openHelpPage">
+ <body>
+ <![CDATA[
+ //XXX Ratty need in app help here.
+ openHelp("plugin-crashed", "chrome://communicator/locale/help/suitehelp.rdf");
+ ]]>
+ </body>
+ </method>
+
+ <method name="showPluginCrashedNotification">
+ <parameter name="pluginDumpID"/>
+ <parameter name="messageString"/>
+ <body>
+ <![CDATA[
+ // If there's already an existing notification bar, don't do anything.
+ var notification = this.getNotificationWithValue("plugin-crashed");
+ if (notification)
+ return;
+
+ // Configure the notification bar
+ var priority = this.PRIORITY_WARNING_MEDIUM;
+ var reloadLabel = this._stringBundle.GetStringFromName("crashedpluginsMessage.reloadButton.label");
+ var reloadKey = this._stringBundle.GetStringFromName("crashedpluginsMessage.reloadButton.accesskey");
+ var submitLabel = this._stringBundle.GetStringFromName("crashedpluginsMessage.submitButton.label");
+ var submitKey = this._stringBundle.GetStringFromName("crashedpluginsMessage.submitButton.accesskey");
+
+ var buttons = [{
+ label: reloadLabel,
+ accessKey: reloadKey,
+ popup: null,
+ callback: this.reloadPage.bind(this)
+ }];
+
+ if (this.CrashSubmit && pluginDumpID) {
+ let submitButton = {
+ label: submitLabel,
+ accessKey: submitKey,
+ popup: null,
+ callback: this.submitReport.bind(this, pluginDumpID)
+ };
+ buttons.push(submitButton);
+ }
+
+ var notification = this.appendNotification(messageString, "plugin-crashed",
+ null, priority, buttons);
+
+ // Add the "learn more" link.
+ var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var link = notification.ownerDocument.createElementNS(XULNS, "label");
+ link.className = "text-link";
+ link.setAttribute("value", this._stringBundle.GetStringFromName("crashedpluginsMessage.learnMore"));
+ this.addLinkClickCallback(link, this.openHelpPage);
+ var description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
+ description.appendChild(link);
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (!aEvent.isTrusted)
+ return;
+ ]]>
+ </body>
+ </method>
+
+ <method name="playSoundForBlockedPopup">
+ <body>
+ <![CDATA[
+ const kCustomSound = 1;
+ var playSound = Services.prefs.getBoolPref("privacy.popups.sound_enabled");
+
+ if (playSound) {
+ var sound = Cc["@mozilla.org/sound;1"]
+ .createInstance(Ci.nsISound);
+
+ var soundType = Services.prefs.getIntPref("privacy.popups.sound_type");
+ if (soundType == kCustomSound) {
+ var soundUrlSpec = Services.prefs.getCharPref("privacy.popups.sound_url");
+ var fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"]
+ .getService(Ci.nsIFileProtocolHandler);
+ var file = fileHandler.getFileFromURLSpec(soundUrlSpec);
+ if (file.exists()) {
+ var soundUrl = fileHandler.newFileURI(file);
+ sound.play(soundUrl);
+ return;
+ }
+ }
+
+ // Either a custom sound is selected which does not exist
+ // or the system beep was selected, so make the system beep.
+ sound.beep();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="popupCount">0</field>
+
+ <method name="notifyPopupCountChanged">
+ <body>
+ <![CDATA[
+ this.dispatchEvent(new Event("PopupCountChanged",
+ { bubbles: true, cancelable: true }));
+ ]]>
+ </body>
+ </method>
+
+ <method name="allowPopupsForSite">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ Services.perms.add(this.activeBrowser.currentURI, "popup",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ this.removeCurrentNotification();
+ ]]>
+ </body>
+ </method>
+
+ <method name="offlineAppRequested">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var documentURI = aDocument.documentURIObject;
+ var pm = Services.perms;
+ if (pm.testExactPermission(documentURI, "offline-app") !=
+ Ci.nsIPermissionManager.UNKNOWN_ACTION)
+ return;
+
+ var host = documentURI.asciiHost;
+ var notificationName = "offline-app-requested-" + host;
+ var notification = this.getNotificationWithValue(notificationName);
+ if (notification)
+ notification.documents.push(aDocument);
+ else {
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("offlineApps.always"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.always.accesskey"),
+ callback: function() {
+ pm.add(documentURI, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+ var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
+ .getService(Ci.nsIOfflineCacheUpdateService);
+ notification.documents.forEach(function(aDocument) {
+ if (!aDocument.documentElement)
+ return;
+ var manifest = aDocument.documentElement.getAttribute("manifest");
+ if (!manifest)
+ return;
+
+ try {
+ var manifestURI =
+ Services.io.newURI(manifest,
+ aDocument.characterSet,
+ aDocument.documentURIObject);
+ updateService.scheduleUpdate(manifestURI,
+ aDocument.documentURIObject,
+ window);
+ } catch (e) {
+ }
+ });
+ }
+ }, {
+ label: this._stringBundle.GetStringFromName("offlineApps.never"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.never.accesskey"),
+ callback: function() {
+ pm.add(documentURI, "offline-app", Ci.nsIPermissionManager.DENY_ACTION);
+ }
+ }, {
+ label: this._stringBundle.GetStringFromName("offlineApps.later"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.later.accesskey"),
+ callback: function() { /* no-op */ }
+ }];
+
+ var messageString = this._stringBundle.formatStringFromName(this.usePrivateBrowsing ?
+ "offlineApps.private" : "offlineApps.permissions", [host], 1);
+ var priority = this.PRIORITY_INFO_LOW;
+ notification = this.appendNotification(messageString, notificationName,
+ null, priority,
+ this.usePrivateBrowsing ?
+ null : buttons);
+ notification.documents = [aDocument];
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="checkUsage">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ if (Services.perms.testExactPermission(aURI, "offline-app") ==
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN)
+ return;
+
+ var host = aURI.asciiHost;
+ var usage = 0;
+
+ var cacheService = Cc["@mozilla.org/network/application-cache-service;1"]
+ .getService(Ci.nsIApplicationCacheService);
+ cacheService.getGroups().forEach(function(aGroup) {
+ var uri = Services.io.newURI(aGroup);
+ if (uri.asciiHost == host)
+ usage += cacheService.getActiveCache(aGroup).usage;
+ });
+ var warnQuota = Services.prefs.getIntPref("offline-apps.quota.warn");
+ if (usage < warnQuota * 1024)
+ return;
+
+ var message = this._stringBundle.formatStringFromName("offlineApps.quota", [host, warnQuota / 1024], 2);
+ var priority = this.PRIORITY_WARNING_MEDIUM;
+ this.appendNotification(message, "offline-app-usage", null,
+ priority, null);
+ Services.perms.add(aURI, "offline-app",
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+ ]]>
+ </body>
+ </method>
+
+ <method name="showRightsNotification">
+ <body>
+ <![CDATA[
+ var rightsBundle = Services.strings.createBundle("chrome://branding/locale/aboutRights.properties");
+ var buttonLabel = rightsBundle.GetStringFromName("buttonLabel");
+ var buttonAccessKey = rightsBundle.GetStringFromName("buttonAccessKey");
+ var productName = this._brandStringBundle.GetStringFromName("brandFullName");
+ var notifyRightsText = rightsBundle.formatStringFromName("notifyRightsText2", [productName], 1);
+
+ var buttons = [{
+ label: buttonLabel,
+ accessKey: buttonAccessKey,
+ popup: null,
+ callback: function (aNotificationBox, aButton) {
+ var browser = document.getBindingParent(aNotificationBox);
+ browser.addTab("about:rights", { focusNewTab: true });
+ }
+ }];
+ var box = this.appendNotification(notifyRightsText, "about-rights",
+ null, this.PRIORITY_INFO_LOW,
+ buttons);
+ box.persistence = 3; // arbitrary number, just so bar sticks around for a bit
+ ]]>
+ </body>
+ </method>
+
+ <method name="showPlacesLockedWarning">
+ <body>
+ <![CDATA[
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ var message = this._placesBundle.formatStringFromName("lockPrompt.text", [brandShortName], 1);
+ var buttons = [{
+ label: this._placesBundle.GetStringFromName("lockPromptInfoButton.label"),
+ accessKey: this._placesBundle.GetStringFromName("lockPromptInfoButton.accesskey"),
+ popup: null,
+ callback: function() {
+ openHelp("places-locked", "chrome://communicator/locale/help/suitehelp.rdf");
+ }
+ }];
+ var box = this.appendNotification(message, "places-locked", null,
+ this.PRIORITY_CRITICAL_MEDIUM,
+ buttons);
+ box.persistence = -1; // until user closes it
+ ]]>
+ </body>
+ </method>
+
+ <method name="showUpdateWarning">
+ <body>
+ <![CDATA[
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ var message = this._stringBundle.formatStringFromName("updatePrompt.text", [brandShortName], 1);
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("updatePromptCheckButton.label"),
+ accessKey: this._stringBundle.GetStringFromName("updatePromptCheckButton.accesskey"),
+ popup: null,
+ callback: function() {
+ Cc["@mozilla.org/updates/update-prompt;1"]
+ .createInstance(Ci.nsIUpdatePrompt)
+ .checkForUpdates();
+ }
+ }];
+ var box = this.appendNotification(message, "update-warning", null,
+ this.PRIORITY_CRITICAL_MEDIUM,
+ buttons);
+ box.persistence = -1; // until user closes it
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeNotifications">
+ <parameter name="aNotifications"/>
+ <body>
+ <![CDATA[
+ aNotifications.forEach(function(value) {
+ var box = this.getNotificationWithValue(value);
+ if (box)
+ this.removeNotification(box);
+ }, this);
+ ]]>
+ </body>
+ </method>
+
+ <method name="lwthemeInstallRequest">
+ <parameter name="aHost"/>
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ var message = this._stringBundle.formatStringFromName("lwthemeInstallRequest.message", [aHost], 1);
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton.accesskey"),
+ popup: null,
+ callback: aCallback
+ }];
+ var box = this.appendNotification(message,
+ "lwtheme-install-request", null,
+ this.PRIORITY_INFO_MEDIUM,
+ buttons);
+ box.persistence = 1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="lwthemeInstallNotification">
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ var message = this._stringBundle.GetStringFromName("lwthemeInstallNotification.message");
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton.accesskey"),
+ callback: aCallback
+ }, {
+ label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton.accesskey"),
+ callback: function() {
+ window.toEM("addons://list/theme");
+ }
+ }];
+ var box = this.appendNotification(message,
+ "lwtheme-install-notification",
+ null, this.PRIORITY_INFO_MEDIUM,
+ buttons);
+ box.persistence = 1;
+ box.timeout = Date.now() + 20000; // 20 seconds
+ ]]>
+ </body>
+ </method>
+
+ <method name="lwthemeNeedsRestart">
+ <parameter name="aNewThemeName"/>
+ <body>
+ <![CDATA[
+ var message = this._stringBundle.formatStringFromName("lwthemeNeedsRestart.message", [aNewThemeName], 1);
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton.accesskey"),
+ popup: null,
+ callback: function() {
+ BrowserUtils.restartApplication();
+ }
+ }];
+ var box = this.appendNotification(message,
+ "lwtheme-install-notification",
+ null,this.PRIORITY_INFO_MEDIUM,
+ buttons);
+ box.persistence = 1;
+ box.timeout = Date.now() + 20000; // 20 seconds
+ ]]>
+ </body>
+ </method>
+
+ <method name="promptIndexedDB">
+ <parameter name="aRequestor"/>
+ <parameter name="aWindow"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ var host = aWindow.document.documentURIObject.asciiHost;
+ var property = this.usePrivateBrowsing ? "offlineApps.private" :
+ "offlineApps." + aTopic;
+ var message = this._stringBundle.formatStringFromName(property,
+ [host, aData], 2);
+ var observer = aRequestor.getInterface(Ci.nsIObserver);
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("offlineApps.always"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.always.accesskey"),
+ popup: null,
+ callback: function allowIndexedDB() {
+ clearTimeout(box.timeout);
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+ }, {
+ label: this._stringBundle.GetStringFromName("offlineApps.never"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.never.accesskey"),
+ popup: null,
+ callback: function denyIndexedDB() {
+ clearTimeout(box.timeout);
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.DENY_ACTION);
+ }
+ }, {
+ label: this._stringBundle.GetStringFromName("offlineApps.later"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.later.accesskey"),
+ popup: null,
+ callback: function laterIndexedDB() {
+ clearTimeout(box.timeout);
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ }
+ }];
+ var box = this.appendNotification(message,
+ "indexedDB-" + aTopic + "-prompt",
+ null, this.PRIORITY_INFO_LOW,
+ this.usePrivateBrowsing ?
+ null : buttons);
+ box.timeout = setTimeout(function() {
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ if (box.parentNode)
+ box.parentNode.removeNotification(box);
+ }, 300000); // 5 minutes
+ ]]>
+ </body>
+ </method>
+
+ <method name="cancelIndexedDB">
+ <parameter name="aRequestor"/>
+ <parameter name="aWindow"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ var popupNotification = this.getNotificationWithValue("indexedDB-" + aTopic + "-prompt");
+ if (popupNotification) {
+ clearTimeout(popupNotification.timeout);
+ this.removeNotification(popupNotification);
+ }
+
+ var observer = aRequestor.getInterface(Ci.nsIObserver);
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallBlocked">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var host;
+ try {
+ // this fails with nsSimpleURIs like data: URIs
+ host = installInfo.originatingURI.host;
+ } catch (ex) {
+ host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
+ }
+ var notificationName = "addon-install-blocked";
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ var messageString = this._stringBundle.formatStringFromName("xpinstallPromptWarning",
+ [brandShortName, host], 2);
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton"),
+ accessKey: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton.accesskey"),
+ popup: null,
+ callback: function allowInstall() {
+ installInfo.install();
+ return false;
+ }
+ }];
+
+ if (!this.getNotificationWithValue(notificationName)) {
+ var priority = this.PRIORITY_WARNING_MEDIUM;
+ this.appendNotification(messageString, notificationName,
+ null, priority, buttons);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallCancelled">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
+ var notificationName = "addon-install-cancelled";
+ var messageString = this._stringBundle.GetStringFromName("addonDownloadCancelled");
+ messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("addonDownloadRestartButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonDownloadRestartButton.accesskey"),
+ popup: null,
+ callback: function() {
+ var weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]
+ .getService(Ci.amIWebInstallListener);
+ if (weblistener.onWebInstallRequested(installInfo.browser, installInfo.originatingURI,
+ [aInstall], 1)) {
+ aInstall.install();
+ }
+ }
+ }];
+ var priority = this.PRIORITY_INFO_MEDIUM;
+ this.appendNotification(messageString, notificationName,
+ null, priority, buttons);
+
+ installInfo.installs.every(function(aInstall) {
+ aInstall.cancel();
+ });
+ return true; // the downloading notification closed automatically
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallComplete">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
+ ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
+
+ var notificationName = "addon-install-complete"
+ var addonNotification = this.getNotificationWithValue(notificationName);
+ if (addonNotification)
+ this.removeNotification(addonNotification);
+
+ var buttons = [];
+ var messageString;
+ if (installInfo.installs.some(install =>
+ install.addon.pendingOperations &
+ tmp.AddonManager.PENDING_INSTALL)) {
+ messageString = this._stringBundle.GetStringFromName("addonsInstalledNeedsRestart");
+ buttons.push({
+ label: this._stringBundle.GetStringFromName("addonInstallRestartButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonInstallRestartButton.accesskey"),
+ callback: function () {
+ BrowserUtils.restartApplication();
+ }
+ });
+ } else {
+ messageString = this._stringBundle.GetStringFromName("addonsInstalled");
+ }
+
+ if ("toEM" in window) {
+ buttons.push({
+ label: this._stringBundle.GetStringFromName("addonInstallManageButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonInstallManageButton.accesskey"),
+ callback: function() {
+ window.toEM("addons://list/extension");
+ }
+ });
+ }
+
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ messageString = tmp.PluralForm.get(installInfo.installs.length, messageString)
+ .replace("#1", installInfo.installs[0].name)
+ .replace("#2", installInfo.installs.length)
+ .replace("#3", brandShortName);
+ var priority = this.PRIORITY_WARNING_MEDIUM;
+ this.appendNotification(messageString, notificationName,
+ null, priority, buttons);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallDisabled">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var messageString;
+ var buttons;
+
+ var notificationName = "addon-install-disabled";
+ if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
+ messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessageLocked");
+ buttons = [];
+ } else {
+ messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessage");
+ buttons = [{
+ label: this._stringBundle.GetStringFromName("xpinstallDisabledButton"),
+ accessKey: this._stringBundle.GetStringFromName("xpinstallDisabledButton.accesskey"),
+ popup: null,
+ callback: function editPrefs() {
+ Services.prefs.setBoolPref("xpinstall.enabled", true);
+ return false;
+ }
+ }];
+ }
+
+ if (!this.getNotificationWithValue(notificationName)) {
+ var priority = this.PRIORITY_WARNING_MEDIUM;
+ this.appendNotification(messageString, notificationName,
+ null, priority, buttons);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallFailed">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var notificationName = "addon-install-failed";
+ if (!this.getNotificationWithValue(notificationName)) {
+ var host;
+ try {
+ // this fails with nsSimpleURIs like data: URIs
+ host = installInfo.originatingURI.host;
+ } catch (ex) {
+ host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
+ }
+
+ var error = "addonErrorIncompatible";
+ var name = installInfo.installs[0].name;
+ installInfo.installs.some(function(install) {
+ if (install.error) {
+ name = install.name;
+ error = "addonError" + install.error;
+ return true;
+ }
+ if (install.addon.blocklistState ==
+ Ci.nsIBlocklistService.STATE_BLOCKED) {
+ name = install.name;
+ error = "addonErrorBlocklisted";
+ }
+ return false;
+ });
+
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ var version = Services.appinfo.version;
+ var messageString = this._stringBundle.GetStringFromName(error)
+ .replace("#1", name)
+ .replace("#2", host)
+ .replace("#3", brandShortName)
+ .replace("#4", version);
+
+ var priority = this.PRIORITY_WARNING_MEDIUM;
+ this.appendNotification(messageString, notificationName,
+ null, priority, []);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallStarted">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
+ if (installInfo.installs.every(function(aInstall) {
+ return aInstall.state == tmp.AddonManager.STATE_DOWNLOADED;
+ }))
+ return;
+
+ ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
+ var notificationName = "addon-install-started";
+ var messageString = this._stringBundle.GetStringFromName("addonDownloading");
+ messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
+ var buttons = [{
+ label: this._stringBundle.GetStringFromName("addonDownloadCancelButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonDownloadCancelButton.accesskey"),
+ popup: null,
+ callback: this.addonInstallCancelled.bind(this, installInfo)
+ }];
+ var priority = this.PRIORITY_INFO_MEDIUM;
+ var box = this.appendNotification(messageString, notificationName,
+ null, priority, buttons);
+ box.installInfo = installInfo;
+ installInfo.installs.forEach(function(aInstall) {
+ aInstall.addListener(box);
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="ignoreSafeBrowsingWarning">
+ <parameter name="aReason"/>
+ <parameter name="aBlockedInfo"/>
+
+ <body>
+ <![CDATA[
+ var uri = this.activeBrowser.currentURI;
+ var flag = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER;
+ this.activeBrowser.loadURIWithFlags(uri.asciiSpec, flag,
+ null, null, null);
+
+ Services.perms.add(uri, "safe-browsing",
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+ var title, label, accessKey, reportName, buttons;
+
+ switch (aReason) {
+ case "phishing":
+ title = "safebrowsing.deceptiveSite";
+ label = "safebrowsing.notADeceptiveSiteButton.label";
+ accessKey = "safebrowsing.notADeceptiveSiteButton.accessKey";
+ reportName = "PhishMistake";
+ break;
+ case "malware":
+ title = "safebrowsing.reportedAttackSite";
+ label = "safebrowsing.notAnAttackButton.label";
+ accessKey = "safebrowsing.notAnAttackButton.accessKey";
+ reportName = "MalwareMistake";
+ break;
+ case "unwanted":
+ title = "safebrowsing.reportedUnwantedSite";
+ break;
+ // No notifications for unknown reasons.
+ default:
+ return;
+ }
+
+ title = this._stringBundle.GetStringFromName(title);
+
+ buttons = [{
+ label: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.label"),
+ accessKey: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.accessKey"),
+ callback: getMeOutOfHere
+ }]
+
+ if (reportName) {
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/SafeBrowsing.jsm", tmp);
+ var reportUrl = tmp.SafeBrowsing.getReportURL(reportName, aBlockedInfo);
+
+ // There's no button if we can not get report url, for example
+ // if the provider of blockedInfo is not Google.
+ if (reportUrl) {
+ buttons.push({
+ label: this._stringBundle.GetStringFromName(label),
+ accessKey: this._stringBundle.GetStringFromName(accessKey),
+ callback() { openUILinkIn(reportUrl, "tabfocused"); }
+ });
+ }
+ }
+
+ var type = "blocked-badware-page";
+ var notification = this.getNotificationWithValue(type);
+ if (notification)
+ this.removeNotification(notification);
+
+ var box = this.appendNotification(title, type, null,
+ this.PRIORITY_CRITICAL_HIGH,
+ buttons);
+
+ // Persist the notification until the user removes so it
+ // doesn't get removed on redirects.
+ box.persistence = -1;
+ ]]>
+ </body>
+ </method>
+ <constructor>
+ <![CDATA[
+ var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
+
+ Services.obs.addObserver(this, "indexedDB-permissions-prompt");
+ Services.obs.addObserver(this, "indexedDB-quota-prompt");
+ Services.obs.addObserver(this, "indexedDB-quota-cancel");
+ Services.obs.addObserver(this, "addon-install-blocked");
+ Services.obs.addObserver(this, "addon-install-complete");
+ Services.obs.addObserver(this, "addon-install-disabled");
+ Services.obs.addObserver(this, "addon-install-failed");
+ Services.obs.addObserver(this, "addon-install-started");
+ Services.obs.addObserver(this, "offline-cache-update-completed");
+ Services.obs.addObserver(this, "perm-changed");
+ Services.obs.addObserver(this, "formsubmit");
+
+ Services.prefs.addObserver("privacy.popups.showBrowserMessage", this);
+ Services.prefs.addObserver("dom.disable_open_during_load", this);
+
+ this.addProgressListener();
+
+ if (AppConstants.MOZ_CRASHREPORTER)
+ ChromeUtils.import("resource://gre/modules/CrashSubmit.jsm", this);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ this.destroy();
+ ]]>
+ </destructor>
+
+ <field name="mDestroyed">false</field>
+
+ <!-- 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 -->
+ <method name="destroy">
+ <body>
+ <![CDATA[
+ if (this.mDestroyed)
+ return;
+ this.mDestroyed = true;
+
+ if (this._addedProgressListener) {
+ this.activeBrowser.webProgress.removeProgressListener(this);
+ this._addedProgressListener = false;
+ }
+
+ this._activeBrowser = null;
+ try {
+ Services.obs.removeObserver(this, "indexedDB-permissions-prompt");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "indexedDB-quota-prompt");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "indexedDB-quota-cancel");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "addon-install-blocked");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "addon-install-complete");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "addon-install-disabled");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "addon-install-failed");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "addon-install-started");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "offline-cache-update-completed");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "perm-changed");
+ } catch (ex) {}
+ try {
+ Services.obs.removeObserver(this, "formsubmit");
+ } catch (ex) {}
+
+ try {
+ Services.prefs.removeObserver("privacy.popups.showBrowserMessage", this);
+ } catch (ex) {}
+ try {
+ Services.prefs.removeObserver("dom.disable_open_during_load", this);
+ } catch (ex) {}
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMContentLoaded" phase="capturing">
+ <![CDATA[
+ if (/^about:neterror\?e=netOffline/.test(event.target.documentURI))
+ event.target.addEventListener("click", function tryAgain(event) {
+ if (event.target.id == "errorTryAgain")
+ Services.io.offline = false;
+ }, true);
+ ]]>
+ </handler>
+
+ <handler event="DOMUpdatePageReport" phase="capturing">
+ <![CDATA[
+ var browser = this.activeBrowser;
+ if (!browser.blockedPopups || browser.blockedPopups.reported != false)
+ return;
+
+ // this.popupCount can be 0, while browser.blockedPopups has not been cleared.
+ if (!this.popupCount && browser.blockedPopups.length > 1) {
+ this.popupCount = browser.blockedPopups.length;
+ } else {
+ this.popupCount++;
+ }
+ this.playSoundForBlockedPopup();
+ this.notifyPopupCountChanged();
+
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
+ if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage"))
+ {
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ var message = this._stringBundle.GetStringFromName("popupWarning.message");
+ message = tmp.PluralForm.get(this.popupCount, message)
+ .replace("#1", brandShortName)
+ .replace("#2", this.popupCount);
+
+ var notification = this.getNotificationWithValue("popup-blocked");
+ if (notification) {
+ notification.label = message;
+ } else {
+ var popupButtonText = this._stringBundle.GetStringFromName("popupWarningButton");
+ var popupButtonAccesskey = this._stringBundle.GetStringFromName("popupWarningButton.accesskey");
+ var buttons = [{
+ label: popupButtonText,
+ accessKey: popupButtonAccesskey,
+ popup: "popupNotificationMenu",
+ callback: null
+ }];
+
+ const priority = this.PRIORITY_WARNING_MEDIUM;
+ this.appendNotification(message, "popup-blocked",
+ null, priority, buttons);
+ }
+ }
+ ]]>
+ </handler>
+
+ <handler event="PluginCrashed" phase="capturing">
+ <![CDATA[
+ // Ensure the plugin and event are of the right type.
+ var plugin = event.target;
+ var detail = event.detail;
+ if (!(detail instanceof Ci.nsIPropertyBag2))
+ return;
+
+ var submittedReport = detail.getPropertyAsBool("submittedCrashReport");
+ var doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt");
+ var submitReports = true; // XXX followup for .getPropertyAsBool("submitReports");
+ var pluginName = detail.getPropertyAsAString("pluginName");
+ var pluginFilename = detail.getPropertyAsAString("pluginFilename");
+ var pluginDumpID = detail.getPropertyAsAString("pluginDumpID");
+
+ // Remap the plugin name to a more user-presentable form.
+ pluginName = this.makeNicePluginName(pluginName);
+
+ // Force a style flush, so that we ensure our binding is attached.
+ plugin.clientTop;
+
+ // Configure the crashed-plugin placeholder.
+ var overlay = this.getPluginUI(plugin, "main");
+
+ var status;
+ var statusDiv = this.getPluginUI(plugin, "submitStatus");
+
+ if (this.CrashSubmit) {
+ // Determine which message to show regarding crash reports.
+ if (submittedReport) { // submitReports && !doPrompt, handled in observer
+ status = "submitted";
+ }
+ else if (!submitReports && !doPrompt) {
+ status = "noSubmit";
+ }
+ else { // doPrompt
+ status = "please";
+ this.getPluginUI(plugin, "submitButton").addEventListener("click",
+ function (event) {
+ if (event.button != 0 || !event.isTrusted)
+ return;
+ this.submitReport(pluginDumpID, plugin);
+ Services.prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", optInCB.checked);
+ }.bind(this));
+ let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
+ optInCB.checked = Services.prefs.getBoolPref("dom.ipc.plugins.reportCrashURL");
+ }
+
+ // If we're showing the link to manually trigger report submission, we'll
+ // want to be able to update all the instances of the UI for this crash to
+ // show an updated message when a report is submitted.
+ if (doPrompt) {
+ let observer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ observe: function(subject, topic, data) {
+ let propertyBag = subject;
+ if (!(propertyBag instanceof Ci.nsIPropertyBag2))
+ return;
+ // Ignore notifications for other crashes.
+ if (propertyBag.get("minidumpID") != pluginDumpID)
+ return;
+ statusDiv.setAttribute("status", data);
+ },
+
+ handleEvent : function(event) {
+ // Not expected to be called, just here for the closure.
+ }
+ }
+
+ // Use a weak reference, so we don't have to remove it...
+ Services.obs.addObserver(observer, "crash-report-status", true);
+ // ...alas, now we need something to hold a strong reference to prevent
+ // it from being GC. But I don't want to manually manage the reference's
+ // lifetime (which should be no greater than the page).
+ // Clever solution? Use a closure with an event listener on the statusDiv.
+ // When it goes away, so do the listener references and the closure.
+ statusDiv.addEventListener("mozCleverClosureHack", observer);
+ }
+ }
+
+ // If we don't have a minidumpID, we can't (or didn't) submit anything.
+ // This can happen if the plugin is killed from the task manager.
+ if (!pluginDumpID) {
+ status = "noReport";
+ }
+
+ statusDiv.setAttribute("status", status);
+
+ var helpIcon = this.getPluginUI(plugin, "helpIcon");
+ this.addLinkClickCallback(helpIcon, this.openHelpPage);
+
+ var messageString = this._stringBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1);
+ var crashText = this.getPluginUI(plugin, "crashedText");
+ crashText.textContent = messageString;
+
+ var link = this.getPluginUI(plugin, "reloadLink");
+ this.addLinkClickCallback(link, this.reloadPage);
+
+ overlay.classList.add("visible");
+ // If a previous plugin on the page was too small and resulted in
+ // adding a notification bar, then remove it because this plugin
+ // instance it big enough to serve as in-content notification.
+ var notification = this.getNotificationWithValue("plugin-crashed");
+ if (notification)
+ this.removeNotification(notification, true);
+ this.crashNotified = true;
+ ]]>
+ </handler>
+
+ <handler event="MozApplicationManifest" phase="capturing">
+ <![CDATA[
+ if (!Services.prefs.getBoolPref("browser.offline-apps.notify"))
+ return;
+
+ try {
+ if (Services.prefs.getBoolPref("offline-apps.allow_by_default"))
+ return;
+ } catch (e) {
+ }
+
+ this.offlineAppRequested(event.originalTarget);
+ ]]>
+ </handler>
+
+ <handler event="pageshow" phase="capturing">
+ <![CDATA[
+ // |event.persisted| is true when the page is loaded from the
+ // BF cache, so this code reshows the notification if necessary.
+ if (!event.persisted)
+ return;
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="popup-notification"
+ extends="chrome://communicator/content/bindings/notification.xml#browser-notificationbox">
+ <implementation>
+ <method name="onLocationChange">
+ <parameter name="aWebProgress" />
+ <parameter name="aRequest" />
+ <parameter name="aLocation" />
+ <parameter name="aFlags" />
+ <body>
+ <![CDATA[
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ if (aWebProgress.DOMWindow == this.activeBrowser.contentWindow &&
+ !(aFlags & nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+ this.onDocumentChange();
+ PopupNotifications.locationChange(this.activeBrowser);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeNotifications">
+ <parameter name="aNotifications"/>
+ <body>
+ <![CDATA[
+ aNotifications.forEach(function(value) {
+ var notification = PopupNotifications.getNotification(value);
+ if (notification)
+ PopupNotifications.remove(notification);
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="lwthemeInstallRequest">
+ <parameter name="aHost"/>
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ var message = this._stringBundle.formatStringFromName("lwthemeInstallRequest.message", [aHost], 1);
+ var action = {
+ label: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton.accesskey"),
+ callback: aCallback
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "lwtheme-install-request", message,
+ "addons-notification-icon", action);
+ ]]>
+ </body>
+ </method>
+
+ <method name="lwthemeInstallNotification">
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ var message = this._stringBundle.GetStringFromName("lwthemeInstallNotification.message");
+ var mainAction = {
+ label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton.accesskey"),
+ callback: aCallback
+ };
+ var secondaryActions = [{
+ label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton.accesskey"),
+ callback: function() {
+ window.toEM("addons://list/theme");
+ }
+ }];
+ var options = {
+ timeout: Date.now() + 20000 // 20 seconds
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "lwtheme-install-notification", message,
+ "addons-notification-icon", mainAction,
+ secondaryActions, options);
+ ]]>
+ </body>
+ </method>
+
+ <method name="lwthemeNeedsRestart">
+ <parameter name="aNewThemeName"/>
+ <body>
+ <![CDATA[
+ var message = this._stringBundle.formatStringFromName("lwthemeNeedsRestart.message", [aNewThemeName], 1);
+ var action = {
+ label: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton"),
+ accessKey: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton.accesskey"),
+ callback: function() {
+ BrowserUtils.restartApplication();
+ }
+ };
+ var options = {
+ timeout: Date.now() + 20000 // 20 seconds
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "lwtheme-install-notification", message,
+ "addons-notification-icon", action, null,
+ options);
+ ]]>
+ </body>
+ </method>
+
+ <method name="promptIndexedDB">
+ <parameter name="aRequestor"/>
+ <parameter name="aWindow"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ var host = aWindow.document.documentURIObject.asciiHost;
+ var property = this.usePrivateBrowsing ? "offlineApps.private" :
+ "offlineApps." + aTopic;
+ var message = this._stringBundle.formatStringFromName(property,
+ [host, aData], 2);
+ var observer = aRequestor.getInterface(Ci.nsIObserver);
+ var mainAction = {
+ label: this._stringBundle.GetStringFromName("offlineApps.always"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.always.accesskey"),
+ callback: function allowIndexedDB() {
+ clearTimeout(notification.timeout);
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+ };
+ var secondaryActions = [{
+ label: this._stringBundle.GetStringFromName("offlineApps.never"),
+ accessKey: this._stringBundle.GetStringFromName("offlineApps.never.accesskey"),
+ callback: function denyIndexedDB() {
+ clearTimeout(notification.timeout);
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.DENY_ACTION);
+ }
+ }];
+ function notificationTimedOut() {
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ notification.remove();
+ }
+ var options = {
+ eventCallback: function(state) {
+ if (notification) {
+ // Always clear the timeout up front. If the doorhanger was
+ // temporarily dismissed, we'll set a new 30 second timeout
+ // to automatically cancel the request. If the doorhanger
+ // gets redisplayed we don't want it to time out unless it
+ // gets dismissed again. And if the doorhanger gets removed
+ // then we aren't interested in it any more.
+ clearTimeout(timeout);
+ if (state == "dismissed")
+ timeout = setTimeout(notificationTimedOut, 30000);
+ }
+ }
+ };
+ var notification =
+ PopupNotifications.show(this.activeBrowser,
+ "indexedDB-" + aTopic + "-prompt",
+ message, "indexedDB-notification-icon",
+ this.usePrivateBrowsing ? null : mainAction,
+ secondaryActions, options);
+ var timeout = setTimeout(notificationTimedOut, 300000); // 5 minutes
+ ]]>
+ </body>
+ </method>
+
+ <method name="cancelIndexedDB">
+ <parameter name="aRequestor"/>
+ <parameter name="aWindow"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ var popupNotification = PopupNotifications.getNotification("indexedDB-" + aTopic + "-prompt", this.activeBrowser);
+ if (popupNotification)
+ popupNotification.remove(); // eventCallback clears the timeout
+
+ var observer = aRequestor.getInterface(Ci.nsIObserver);
+ observer.observe(null, "indexedDB-" + aTopic + "-response",
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallBlocked">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var host;
+ try {
+ // this fails with nsSimpleURIs like data: URIs
+ host = installInfo.originatingURI.host;
+ } catch (ex) {
+ host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
+ }
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ var messageString = this._stringBundle.formatStringFromName("xpinstallPromptWarning",
+ [brandShortName, host], 2);
+ var action = {
+ label: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton"),
+ accessKey: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton.accesskey"),
+ callback: function allowInstall() {
+ installInfo.install();
+ return false;
+ }
+ };
+
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ timeout: Date.now() + 30000
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "addon-install-blocked", messageString,
+ "addons-notification-icon", action,
+ null, options);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallCancelled">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
+ var messageString = this._stringBundle.GetStringFromName("addonDownloadCancelled");
+ messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
+ var action = {
+ label: this._stringBundle.GetStringFromName("addonDownloadRestartButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonDownloadRestartButton.accesskey"),
+ popup: null,
+ callback: function() {
+ var weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]
+ .getService(Ci.amIWebInstallListener);
+ if (weblistener.onWebInstallRequested(installInfo.browser, installInfo.originatingURI,
+ [aInstall], 1)) {
+ aInstall.install();
+ }
+ }
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "addon-install-cancelled", messageString,
+ "addons-notification-icon", action);
+
+ installInfo.installs.every(function(aInstall) {
+ aInstall.cancel();
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallComplete">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
+ ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
+
+ var messageString;
+ var mainAction = null;
+ var secondaryActions = null;
+
+ if ("toEM" in window) {
+ mainAction = {
+ label: this._stringBundle.GetStringFromName("addonInstallManageButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonInstallManageButton.accesskey"),
+ callback: function() {
+ window.toEM("addons://list/extension");
+ }
+ };
+ }
+
+ if (installInfo.installs.some(install =>
+ install.addon.pendingOperations &
+ tmp.AddonManager.PENDING_INSTALL)) {
+ messageString = this._stringBundle.GetStringFromName("addonsInstalledNeedsRestart");
+ if (mainAction)
+ secondaryActions = [mainAction];
+ mainAction = {
+ label: this._stringBundle.GetStringFromName("addonInstallRestartButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonInstallRestartButton.accesskey"),
+ callback: function () {
+ BrowserUtils.restartApplication();
+ }
+ };
+ } else {
+ messageString = this._stringBundle.GetStringFromName("addonsInstalled");
+ }
+
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ messageString = tmp.PluralForm.get(installInfo.installs.length, messageString)
+ .replace("#1", installInfo.installs[0].name)
+ .replace("#2", installInfo.installs.length)
+ .replace("#3", brandShortName);
+
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ timeout: Date.now() + 30000
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "addon-install-complete", messageString,
+ "addons-notification-icon", mainAction,
+ secondaryActions, options);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallDisabled">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var messageString;
+ var action = null;
+
+ if (Services.prefs.prefIsLocked("xpinstall.enabled"))
+ messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessageLocked");
+ else {
+ messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessage");
+ action = {
+ label: this._stringBundle.GetStringFromName("xpinstallDisabledButton"),
+ accessKey: this._stringBundle.GetStringFromName("xpinstallDisabledButton.accesskey"),
+ callback: function editPrefs() {
+ Services.prefs.setBoolPref("xpinstall.enabled", true);
+ }
+ };
+ }
+
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ timeout: Date.now() + 30000
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "addon-install-disabled", messageString,
+ "addons-notification-icon", action,
+ null, options);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallFailed">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var host;
+ try {
+ // this fails with nsSimpleURIs like data: URIs
+ host = installInfo.originatingURI.host;
+ } catch (ex) {
+ host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
+ }
+
+ var error = "addonErrorIncompatible";
+ var name = installInfo.installs[0].name;
+ installInfo.installs.some(function(install) {
+ if (install.error) {
+ name = install.name;
+ error = "addonError" + install.error;
+ return true;
+ }
+ if (install.addon.blocklistState ==
+ Ci.nsIBlocklistService.STATE_BLOCKED) {
+ name = install.name;
+ error = "addonErrorBlocklisted";
+ }
+ return false;
+ });
+
+ var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
+ var version = Services.appinfo.version;
+ var messageString = this._stringBundle.GetStringFromName(error)
+ .replace("#1", name)
+ .replace("#2", host)
+ .replace("#3", brandShortName)
+ .replace("#4", version);
+
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ timeout: Date.now() + 30000
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "addon-install-failed", messageString,
+ "addons-notification-icon", null,
+ null, options);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addonInstallStarted">
+ <parameter name="installInfo"/>
+ <body>
+ <![CDATA[
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
+ if (installInfo.installs.every(function(aInstall) {
+ return aInstall.state == tmp.AddonManager.STATE_DOWNLOADED;
+ }))
+ return;
+
+ ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
+ var messageString = this._stringBundle.GetStringFromName("addonDownloading");
+ messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
+ var action = {
+ label: this._stringBundle.GetStringFromName("addonDownloadCancelButton"),
+ accessKey: this._stringBundle.GetStringFromName("addonDownloadCancelButton.accesskey"),
+ callback: this.addonInstallCancelled.bind(this, installInfo)
+ };
+ var options = {
+ installInfo: installInfo
+ };
+ PopupNotifications.show(this.activeBrowser,
+ "addon-install-started", messageString,
+ "addons-notification-icon", action,
+ null, options);
+ ]]>
+ </body>
+ </method>
+ <constructor>
+ <![CDATA[
+ ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="addon-progress-notification"
+ extends="chrome://global/content/bindings/notification.xml#notification">
+ <content>
+ <xul:hbox class="notification-inner outset" 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" xbl:inherits="xbl:text=label"/>
+ <xul:progressmeter mode="undetermined" xbl:inherits="mode,value=progress"/>
+ <xul:label flex="1" xbl:inherits="value=status"/>
+ <children/>
+ </xul:hbox>
+ <xul:toolbarbutton ondblclick="event.stopPropagation();"
+ class="messageCloseButton tabbable"
+ xbl:inherits="hidden=hideclose"
+ tooltiptext="&closeNotification.tooltip;"
+ oncommand="document.getBindingParent(this).close();"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <destructor>
+ <![CDATA[
+ this.installInfo.installs.forEach(function(aInstall) {
+ aInstall.removeListener(this);
+ }, this);
+ ]]>
+ </destructor>
+
+ <method name="updateProgress">
+ <body>
+ <![CDATA[
+ var count = 0;
+ var progress = 0;
+ var max = 0;
+
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
+ this.installInfo.installs.forEach(function(aInstall) {
+ if (aInstall.maxProgress < 0)
+ max = -1;
+ else if (max >= 0)
+ max += aInstall.maxProgress;
+ progress += aInstall.progress;
+ if (aInstall.state < tmp.AddonManager.STATE_DOWNLOADED)
+ count++;
+ });
+
+ if (max < 0)
+ this.setAttribute("mode", "undetermined");
+ else {
+ this.setAttribute("mode", "determined");
+ this.setAttribute("progress", progress * 100 / max);
+ }
+
+ var now = Date.now();
+ if (!this.startTime) {
+ this.startTime = now;
+ this.lastUpdate = now - 750;
+ this.lastSeconds = null;
+ }
+
+ if (progress == max || now - this.lastUpdate >= 750) {
+ this.lastUpdate = now;
+ var elapsed = (now - this.startTime) / 1000;
+ var rate = elapsed && progress / elapsed;
+ ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm", tmp);
+ var status;
+ [status, this.lastSeconds] = tmp.DownloadUtils.getDownloadStatus(progress, max, rate, this.lastSeconds);
+ this.setAttribute("status", status);
+ }
+
+ if (!count)
+ this.close();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadProgress">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadCancelled">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadEnded">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="sidebar-notification"
+ extends="chrome://global/content/bindings/notification.xml#notification">
+ <content>
+ <xul:vbox class="notification-inner outset" flex="1" xbl:inherits="type">
+ <xul:hbox align="center">
+ <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
+ <xul:arrowscrollbox orient="horizontal" flex="1" pack="end"
+ oncommand="document.getBindingParent(this)._doButtonCommand(event);">
+ <children/>
+ </xul:arrowscrollbox>
+ <xul:toolbarbutton ondblclick="event.stopPropagation();"
+ class="messageCloseButton tabbable"
+ xbl:inherits="hidden=hideclose"
+ tooltiptext="&closeNotification.tooltip;"
+ oncommand="document.getBindingParent(this).close();"/>
+ </xul:hbox>
+ <xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
+ </xul:vbox>
+ </content>
+ </binding>
+
+ <binding id="sidebar-addon-progress-notification"
+ extends="chrome://communicator/content/bindings/notification.xml#addon-progress-notification">
+ <content>
+ <xul:vbox class="notification-inner outset" flex="1" xbl:inherits="type">
+ <xul:hbox align="center">
+ <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
+ <xul:arrowscrollbox orient="horizontal" flex="1" pack="end"
+ oncommand="document.getBindingParent(this)._doButtonCommand(event);">
+ <children/>
+ </xul:arrowscrollbox>
+ <xul:toolbarbutton ondblclick="event.stopPropagation();"
+ class="messageCloseButton tabbable"
+ xbl:inherits="hidden=hideclose"
+ tooltiptext="&closeNotification.tooltip;"
+ oncommand="document.getBindingParent(this).close();"/>
+ </xul:hbox>
+ <xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
+ <xul:progressmeter mode="undetermined" xbl:inherits="mode,value=progress"/>
+ <xul:label xbl:inherits="value=status"/>
+ </xul:vbox>
+ </content>
+ </binding>
+
+ <binding id="addon-progress-popup-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <content align="start">
+ <xul:image class="popup-notification-icon" xbl:inherits="popupid"/>
+ <xul:vbox flex="1">
+ <xul:description class="popup-notification-description addon-progress-description"
+ xbl:inherits="xbl:text=label"/>
+ <xul:hbox class="popup-notification-button-container" align="center">
+ <xul:progressmeter mode="undetermined"
+ xbl:inherits="mode,value=progress"/>
+ <xul:spacer flex="1"/>
+ <xul:button anonid="button"
+ class="popup-notification-menubutton"
+ type="menu-button"
+ xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
+ <xul:menupopup anonid="menupopup"
+ xbl:inherits="oncommand=menucommand">
+ <children/>
+ <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
+ label="&closeNotificationItem.label;"
+ xbl:inherits="oncommand=closeitemcommand"/>
+ </xul:menupopup>
+ </xul:button>
+ </xul:hbox>
+ <xul:label xbl:inherits="xbl:text=status"/>
+ </xul:vbox>
+ <xul:vbox pack="start">
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton close-icon popup-notification-closebutton tabbable"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:vbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.installInfo = this.notification.options.installInfo;
+ this.installInfo.installs.forEach(function(aInstall) {
+ aInstall.addListener(this);
+ }, this);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ this.installInfo.installs.forEach(function(aInstall) {
+ aInstall.removeListener(this);
+ }, this);
+ ]]>
+ </destructor>
+
+ <method name="updateProgress">
+ <body>
+ <![CDATA[
+ var count = 0;
+ var progress = 0;
+ var max = 0;
+
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
+ this.installInfo.installs.forEach(function(aInstall) {
+ if (aInstall.maxProgress < 0)
+ max = -1;
+ else if (max >= 0)
+ max += aInstall.maxProgress;
+ progress += aInstall.progress;
+ if (aInstall.state < tmp.AddonManager.STATE_DOWNLOADED)
+ count++;
+ });
+
+ if (max < 0)
+ this.setAttribute("mode", "undetermined");
+ else {
+ this.setAttribute("mode", "determined");
+ this.setAttribute("progress", progress * 100 / max);
+ }
+
+ var now = Date.now();
+ if (!this.startTime) {
+ this.startTime = now;
+ this.lastUpdate = now - 750;
+ this.lastSeconds = null;
+ }
+
+ if (progress == max || now - this.lastUpdate >= 750) {
+ this.lastUpdate = now;
+ var elapsed = (now - this.startTime) / 1000;
+ var rate = elapsed && progress / elapsed;
+ ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm", tmp);
+ var status;
+ [status, this.lastSeconds] = tmp.DownloadUtils.getDownloadStatus(progress, max, rate, this.lastSeconds);
+ this.setAttribute("status", status);
+ }
+
+ if (!count)
+ PopupNotifications.remove(this.notification);
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadProgress">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadCancelled">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDownloadEnded">
+ <body>
+ <![CDATA[
+ this.updateProgress();
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="center-item">
+ <content>
+ <xul:vbox flex="1" class="center-item-box"
+ xbl:inherits="warn,showseparator,padbottom">
+ <xul:hbox align="center">
+ <xul:image class="center-item-icon"
+ xbl:inherits="src=itemicon"/>
+ <xul:description class="center-item-label"
+ xbl:inherits="xbl:text=itemtext"/>
+ <xul:spacer flex="1"/>
+ <xul:button class="popup-notification-menubutton center-item-button"
+ oncommand="document.getBindingParent(this).runCallback();"
+ xbl:inherits="label=buttonlabel"/>
+ </xul:hbox>
+ <xul:hbox align="center" class="center-item-warning">
+ <xul:image class="center-item-warning-icon"/>
+ <xul:label class="center-item-warning-description" xbl:inherits="xbl:text=warningText"/>
+ <xul:label xbl:inherits="href=updateLink" value="&checkForUpdates;" class="text-link"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <field name="action"></field>
+ <method name="runCallback">
+ <body><![CDATA[
+ let action = this.action;
+ action.callback();
+ let cas = action.popupnotification.notification.options.centerActions;
+ cas.splice(cas.indexOf(action), 1);
+ PopupNotifications._dismiss();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/comm/suite/components/bindings/numberbox.xml b/comm/suite/components/bindings/numberbox.xml
new file mode 100644
index 0000000000..8f18bcbcdd
--- /dev/null
+++ b/comm/suite/components/bindings/numberbox.xml
@@ -0,0 +1,217 @@
+<?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://messenger/content/textbox.xml#textbox">
+
+ <content>
+ <xul:moz-input-box 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:moz-input-box>
+ <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>
+
+ <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;
+ this._validateValue(newval);
+ }
+ return this._value;
+ </getter>
+ <setter>
+ this._validateValue(val);
+ 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);
+ }
+ 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);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="_modifyUp">
+ <body>
+ <![CDATA[
+ if (this.disabled || this.readOnly)
+ return;
+ var oldval = this.valueNumber;
+ var newval = this._validateValue(this.valueNumber + 1);
+ 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._validateValue(this.valueNumber - 1);
+ this.inputField.select();
+ if (oldval != newval)
+ this._fireChange();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_enableDisableButtons">
+ <body>
+ <![CDATA[
+ var buttons = this.spinButtons;
+ 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"/>
+ <body>
+ <![CDATA[
+ aValue = Number(aValue) || 0;
+ aValue = Math.round(aValue);
+
+ var min = this.min;
+ var max = this.max;
+ if (aValue < min)
+ aValue = min;
+ else if (aValue > max)
+ aValue = max;
+
+ this._valueEntered = false;
+ this._value = Number(aValue);
+ this.inputField.value = aValue;
+
+ 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 value = this.inputField.value || 0;
+ this._validateValue(value);
+ ]]></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 == 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;
+ this._validateValue(newval);
+ }
+ </handler>
+ </handlers>
+
+ </binding>
+</bindings>
diff --git a/comm/suite/components/bindings/preferences.xml b/comm/suite/components/bindings/preferences.xml
new file mode 100644
index 0000000000..8c5872ee76
--- /dev/null
+++ b/comm/suite/components/bindings/preferences.xml
@@ -0,0 +1,817 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE bindings [
+ <!ENTITY % preferencesDTD SYSTEM "chrome://communicator/locale/pref/preferences.dtd">
+ %preferencesDTD;
+ <!ENTITY % globalKeysDTD SYSTEM "chrome://global/locale/globalKeys.dtd">
+ %globalKeysDTD;
+]>
+
+<bindings id="preferencesBindings"
+ 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">
+
+#
+# = Preferences Window Framework
+#
+# The syntax for use looks something like:
+#
+# <prefwindow>
+# <prefpane id="prefPaneA">
+# <preferences>
+# <preference id="preference1" name="app.preference1" type="bool" onchange="foo();"/>
+# <preference id="preference2" name="app.preference2" type="bool" useDefault="true"/>
+# </preferences>
+# <checkbox label="Preference" preference="preference1"/>
+# </prefpane>
+# </prefwindow>
+#
+
+ <binding id="preferences">
+ <implementation implements="nsIObserver">
+ <method name="_constructAfterChildren">
+ <body>
+ <![CDATA[
+ // This method will be called after the last of the child
+ // <preference> elements is constructed. Its purpose is to propagate
+ // the values to the associated form elements. Sometimes the code for
+ // some <preference> initializers depend on other <preference> elements
+ // being initialized so we wait and call updateElements on all of them
+ // once the last one has been constructed. See bugs 997570 and 992185.
+
+ var elements = this.getElementsByTagName("preference");
+ for (let element of elements) {
+ element.updateElements();
+ }
+
+ this._constructAfterChildrenCalled = true;
+ ]]>
+ </body>
+ </method>
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ for (var i = 0; i < this.childNodes.length; ++i) {
+ var preference = this.childNodes[i];
+ if (preference.name == aData) {
+ preference.value = preference.valueFromPreferences;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="fireChangedEvent">
+ <parameter name="aPreference"/>
+ <body>
+ <![CDATA[
+ // Value changed, synthesize an event
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent("change", true, true);
+ aPreference.dispatchEvent(event);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="service">
+ Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
+ </field>
+ <field name="rootBranch">
+ Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ </field>
+ <field name="defaultBranch">
+ this.service.getDefaultBranch("");
+ </field>
+ <field name="rootBranchInternal">
+ Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ </field>
+ <property name="type" readonly="true">
+ <getter>
+ <![CDATA[
+ return document.documentElement.type || "";
+ ]]>
+ </getter>
+ </property>
+ <property name="instantApply" readonly="true">
+ <getter>
+ <![CDATA[
+ var doc = document.documentElement;
+ return this.type == "child" ? doc.instantApply
+ : doc.instantApply || this.rootBranch.getBoolPref("browser.preferences.instantApply");
+ ]]>
+ </getter>
+ </property>
+
+ <!-- We want to call _constructAfterChildren after all child
+ <preference> elements have been constructed. To do this, we get
+ and store the node list of all child <preference> elements in the
+ constructor, and maintain a count which is incremented in the
+ constructor of <preference>. _constructAfterChildren is called
+ when the count matches the length of the list. -->
+ <field name="_constructedChildrenCount">0</field>
+ <field name="_preferenceChildren">null</field>
+ <!-- Some <preference> elements are added dynamically after
+ _constructAfterChildren has already been called - we want to
+ avoid looping over all of them again in this case so we remember
+ if we already called it. -->
+ <field name="_constructAfterChildrenCalled">false</field>
+ <constructor>
+ <![CDATA[
+ this._preferenceChildren = this.getElementsByTagName("preference");
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="preference">
+ <implementation>
+ <constructor>
+ <![CDATA[
+ // if the element has been inserted without the name attribute set,
+ // we have nothing to do here
+ if (!this.name)
+ return;
+
+ this.preferences.rootBranchInternal
+ .addObserver(this.name, this.preferences);
+ // In non-instant apply mode, we must try and use the last saved state
+ // from any previous opens of a child dialog instead of the value from
+ // preferences, to pick up any edits a user may have made.
+
+ var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ if (this.preferences.type == "child" &&
+ !this.instantApply && window.opener &&
+ secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
+ var pdoc = window.opener.document;
+
+ // Try to find a preference element for the same preference.
+ var preference = null;
+ var parentPreferences = pdoc.getElementsByTagName("preferences");
+ for (var k = 0; (k < parentPreferences.length && !preference); ++k) {
+ var parentPrefs = parentPreferences[k]
+ .getElementsByAttribute("name", this.name);
+ for (var l = 0; (l < parentPrefs.length && !preference); ++l) {
+ if (parentPrefs[l].localName == "preference")
+ preference = parentPrefs[l];
+ }
+ }
+
+ // Don't use the value setter here, we don't want updateElements to be prematurely fired.
+ this._value = preference ? preference.value : this.valueFromPreferences;
+ } else {
+ this._value = this.valueFromPreferences;
+ }
+ if (this.preferences._constructAfterChildrenCalled) {
+ // This <preference> was added after _constructAfterChildren() was already called.
+ // We can directly call updateElements().
+ this.updateElements();
+ } else {
+ this.preferences._constructedChildrenCount++;
+ if (this.preferences._constructedChildrenCount ==
+ this.preferences._preferenceChildren.length) {
+ // This is the last <preference>, time to updateElements() on all of them.
+ this.preferences._constructAfterChildren();
+ }
+ }
+
+ this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
+ ]]>
+ </constructor>
+ <destructor>
+ this.preferences.rootBranchInternal
+ .removeObserver(this.name, this.preferences);
+ </destructor>
+ <field name="_constructed">false</field>
+ <property name="instantApply">
+ <getter>
+ if (this.getAttribute("instantApply") == "false")
+ return false;
+ return this.getAttribute("instantApply") == "true" || this.preferences.instantApply;
+ </getter>
+ </property>
+
+ <property name="preferences" onget="return this.parentNode"/>
+ <property name="name" onget="return this.getAttribute('name');">
+ <setter>
+ if (val == this.name)
+ return val;
+
+ this.preferences.rootBranchInternal
+ .removeObserver(this.name, this.preferences);
+ this.setAttribute("name", val);
+ this.preferences.rootBranchInternal
+ .addObserver(val, this.preferences);
+
+ return val;
+ </setter>
+ </property>
+ <property name="type" onget="return this.getAttribute('type');"
+ onset="this.setAttribute('type', val); return val;"/>
+ <property name="inverted" onget="return this.getAttribute('inverted') == 'true';"
+ onset="this.setAttribute('inverted', val); return val;"/>
+ <property name="readonly" onget="return this.getAttribute('readonly') == 'true';"
+ onset="this.setAttribute('readonly', val); return val;"/>
+
+ <field name="_value">null</field>
+ <method name="_setValue">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (this.value !== aValue) {
+ this._value = aValue;
+ if (this.instantApply)
+ this.valueFromPreferences = aValue;
+ this.preferences.fireChangedEvent(this);
+ }
+ return aValue;
+ ]]>
+ </body>
+ </method>
+ <property name="value" onget="return this._value" onset="return this._setValue(val);"/>
+
+ <property name="locked">
+ <getter>
+ return this.preferences.rootBranch.prefIsLocked(this.name);
+ </getter>
+ </property>
+
+ <property name="disabled">
+ <getter>
+ return this.getAttribute("disabled") == "true";
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val)
+ this.setAttribute("disabled", "true");
+ else
+ this.removeAttribute("disabled");
+
+ if (!this.id)
+ return val;
+
+ var elements = document.getElementsByAttribute("preference", this.id);
+ for (var i = 0; i < elements.length; ++i) {
+ elements[i].disabled = val;
+
+ var labels = document.getElementsByAttribute("control", elements[i].id);
+ for (var j = 0; j < labels.length; ++j)
+ labels[j].disabled = val;
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="tabIndex">
+ <getter>
+ return parseInt(this.getAttribute("tabindex"));
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val)
+ this.setAttribute("tabindex", val);
+ else
+ this.removeAttribute("tabindex");
+
+ if (!this.id)
+ return val;
+
+ var elements = document.getElementsByAttribute("preference", this.id);
+ for (var i = 0; i < elements.length; ++i) {
+ elements[i].tabIndex = val;
+
+ var labels = document.getElementsByAttribute("control", elements[i].id);
+ for (var j = 0; j < labels.length; ++j)
+ labels[j].tabIndex = val;
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="hasUserValue">
+ <getter>
+ <![CDATA[
+ return this.preferences.rootBranch.prefHasUserValue(this.name) &&
+ this.value !== undefined;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="reset">
+ <body>
+ // defer reset until preference update
+ this.value = undefined;
+ </body>
+ </method>
+
+ <field name="_useDefault">false</field>
+ <property name="defaultValue">
+ <getter>
+ <![CDATA[
+ this._useDefault = true;
+ var val = this.valueFromPreferences;
+ this._useDefault = false;
+ return val;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="_branch">
+ <getter>
+ return this._useDefault ? this.preferences.defaultBranch : this.preferences.rootBranch;
+ </getter>
+ </property>
+
+ <field name="batching">false</field>
+
+ <method name="_reportUnknownType">
+ <body>
+ <![CDATA[
+ var consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+ var msg = "<preference> with id='" + this.id + "' and name='" +
+ this.name + "' has unknown type '" + this.type + "'.";
+ consoleService.logStringMessage(msg);
+ ]]>
+ </body>
+ </method>
+
+ <property name="valueFromPreferences">
+ <getter>
+ <![CDATA[
+ try {
+ // Force a resync of value with preferences.
+ switch (this.type) {
+ case "int":
+ return this._branch.getIntPref(this.name);
+ case "bool":
+ var val = this._branch.getBoolPref(this.name);
+ return this.inverted ? !val : val;
+ case "wstring":
+ return this._branch
+ .getComplexValue(this.name, Ci.nsIPrefLocalizedString)
+ .data;
+ case "string":
+ case "unichar":
+ return this._branch.getStringPref(this.name);
+ case "fontname":
+ var family = this._branch.getStringPref(this.name);
+ var fontEnumerator = Cc["@mozilla.org/gfx/fontenumerator;1"]
+ .createInstance(Ci.nsIFontEnumerator);
+ return fontEnumerator.getStandardFamilyName(family);
+ case "file":
+ var f = this._branch
+ .getComplexValue(this.name, Ci.nsIFile);
+ return f;
+ default:
+ this._reportUnknownType();
+ }
+ } catch (e) { }
+ return null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ // Exit early if nothing to do.
+ if (this.readonly || this.valueFromPreferences == val)
+ return val;
+
+ // The special value undefined means 'reset preference to default'.
+ if (val === undefined) {
+ this.preferences.rootBranch.clearUserPref(this.name);
+ return val;
+ }
+
+ // Force a resync of preferences with value.
+ switch (this.type) {
+ case "int":
+ this.preferences.rootBranch.setIntPref(this.name, val);
+ break;
+ case "bool":
+ this.preferences.rootBranch.setBoolPref(this.name, this.inverted ? !val : val);
+ break;
+ case "wstring":
+ var pls = Cc["@mozilla.org/pref-localizedstring;1"]
+ .createInstance(Ci.nsIPrefLocalizedString);
+ pls.data = val;
+ this.preferences.rootBranch
+ .setComplexValue(this.name, Ci.nsIPrefLocalizedString, pls);
+ break;
+ case "string":
+ case "unichar":
+ case "fontname":
+ this.preferences.rootBranch.setStringPref(this.name, val);
+ break;
+ case "file":
+ var lf;
+ if (typeof(val) == "string") {
+ lf = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsIFile);
+ lf.persistentDescriptor = val;
+ if (!lf.exists())
+ lf.initWithPath(val);
+ } else {
+ lf = val.QueryInterface(Ci.nsIFile);
+ }
+ this.preferences.rootBranch
+ .setComplexValue(this.name, Ci.nsIFile, lf);
+ break;
+ default:
+ this._reportUnknownType();
+ }
+ if (!this.batching)
+ this.preferences.service.savePrefFile(null);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="setElementValue">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ if (this.locked)
+ aElement.disabled = true;
+
+ if (!this.isElementEditable(aElement))
+ return;
+
+ var rv = undefined;
+ if (aElement.hasAttribute("onsyncfrompreference")) {
+ // Value changed, synthesize an event
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent("syncfrompreference", true, true);
+ var f = new Function("event",
+ aElement.getAttribute("onsyncfrompreference"));
+ rv = f.call(aElement, event);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ var val = rv;
+ if (val === undefined)
+ val = this.instantApply ? this.valueFromPreferences : this.value;
+ // if the preference is marked for reset, show default value in UI
+ if (val === undefined)
+ val = this.defaultValue;
+
+ /**
+ * Initialize a UI element property with a value. Handles the case
+ * where an element has not yet had a XBL binding attached for it and
+ * the property setter does not yet exist by setting the same attribute
+ * on the XUL element using DOM apis and assuming the element's
+ * constructor or property getters appropriately handle this state.
+ */
+ function setValue(element, attribute, value) {
+ if (attribute in element)
+ element[attribute] = value;
+ else
+ element.setAttribute(attribute, value);
+ }
+ if (aElement.localName == "checkbox") {
+ setValue(aElement, "checked", val);
+ } else if (aElement.localName == "colorpicker") {
+ setValue(aElement, "color", val);
+ } else if (aElement.localName == "textbox") {
+ // XXXmano Bug 303998: Avoid a caret placement issue if either the
+ // preference observer or its setter calls updateElements as a result
+ // of the input event handler.
+ if (aElement.value !== val)
+ setValue(aElement, "value", val);
+ } else {
+ setValue(aElement, "value", val);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getElementValue">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ if (aElement.hasAttribute("onsynctopreference")) {
+ // Value changed, synthesize an event
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent("synctopreference", true, true);
+ var f = new Function("event",
+ aElement.getAttribute("onsynctopreference"));
+ var rv = f.call(aElement, event);
+ if (rv !== undefined)
+ return rv;
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+
+ /**
+ * Read the value of an attribute from an element, assuming the
+ * attribute is a property on the element's node API. If the property
+ * is not present in the API, then assume its value is contained in
+ * an attribute, as is the case before a binding has been attached.
+ */
+ function getValue(element, attribute) {
+ if (attribute in element)
+ return element[attribute];
+ return element.getAttribute(attribute);
+ }
+ if (aElement.localName == "checkbox")
+ var value = getValue(aElement, "checked");
+ else if (aElement.localName == "colorpicker")
+ value = getValue(aElement, "color");
+ else
+ value = getValue(aElement, "value");
+
+ switch (this.type) {
+ case "int":
+ return parseInt(value, 10) || 0;
+ case "bool":
+ return typeof(value) == "boolean" ? value : value == "true";
+ }
+ return value;
+ ]]>
+ </body>
+ </method>
+
+ <method name="isElementEditable">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ switch (aElement.localName) {
+ case "checkbox":
+ case "colorpicker":
+ case "radiogroup":
+ case "textbox":
+ case "richlistitem":
+ case "richlistbox":
+ case "menulist":
+ return true;
+ }
+ return aElement.getAttribute("preference-editable") == "true";
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateElements">
+ <body>
+ <![CDATA[
+ if (!this.id)
+ return;
+
+ // This "change" event handler tracks changes made to preferences by
+ // sources other than the user in this window.
+ var elements = document.getElementsByAttribute("preference", this.id);
+ for (var i = 0; i < elements.length; ++i)
+ this.setElementValue(elements[i]);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="change">
+ this.updateElements();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="prefpane">
+ <implementation>
+ <method name="writePreferences">
+ <parameter name="aFlushToDisk"/>
+ <body>
+ <![CDATA[
+ // Write all values to preferences.
+ if (this._deferredValueUpdateElements.size) {
+ this._finalizeDeferredElements();
+ }
+
+ var preferences = this.preferences;
+ for (var i = 0; i < preferences.length; ++i) {
+ var preference = preferences[i];
+ preference.batching = true;
+ preference.valueFromPreferences = preference.value;
+ preference.batching = false;
+ }
+ if (aFlushToDisk) {
+ var psvc = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService);
+ psvc.savePrefFile(null);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="src"
+ onget="return this.getAttribute('src');"
+ onset="this.setAttribute('src', val); return val;"/>
+ <property name="selected"
+ onget="return this.getAttribute('selected') == 'true';"
+ onset="this.setAttribute('selected', val); return val;"/>
+ <property name="image"
+ onget="return this.getAttribute('image');"
+ onset="this.setAttribute('image', val); return val;"/>
+ <property name="label"
+ onget="return this.getAttribute('label');"
+ onset="this.setAttribute('label', val); return val;"/>
+
+ <property name="preferenceElements"
+ onget="return this.getElementsByAttribute('preference', '*');"/>
+ <property name="preferences"
+ onget="return this.getElementsByTagName('preference');"/>
+
+ <property name="helpTopic">
+ <getter>
+ <![CDATA[
+ // if there are tabs, and the selected tab provides a helpTopic, return that
+ var box = this.getElementsByTagName("tabbox");
+ if (box[0]) {
+ var tab = box[0].selectedTab;
+ if (tab && tab.hasAttribute("helpTopic"))
+ return tab.getAttribute("helpTopic");
+ }
+
+ // otherwise, return the helpTopic of the current panel
+ return this.getAttribute("helpTopic");
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_loaded">false</field>
+ <property name="loaded"
+ onget="return !this.src ? true : this._loaded;"
+ onset="this._loaded = val; return val;"/>
+
+ <method name="preferenceForElement">
+ <parameter name="aElement"/>
+ <body>
+ return document.getElementById(aElement.getAttribute("preference"));
+ </body>
+ </method>
+
+ <method name="getPreferenceElement">
+ <parameter name="aStartElement"/>
+ <body>
+ <![CDATA[
+ var temp = aStartElement;
+ while (temp && temp.nodeType == Node.ELEMENT_NODE &&
+ !temp.hasAttribute("preference"))
+ temp = temp.parentNode;
+ return temp && temp.nodeType == Node.ELEMENT_NODE ?
+ temp : aStartElement;
+ ]]>
+ </body>
+ </method>
+
+ <property name="DeferredTask" readonly="true">
+ <getter><![CDATA[
+ let module = {};
+ ChromeUtils.import("resource://gre/modules/DeferredTask.jsm", module);
+ Object.defineProperty(this, "DeferredTask", {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: module.DeferredTask,
+ });
+ return module.DeferredTask;
+ ]]></getter>
+ </property>
+ <method name="_deferredValueUpdate">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ delete aElement._deferredValueUpdateTask;
+ let preference = document.getElementById(aElement.getAttribute("preference"));
+ let prefVal = preference.getElementValue(aElement);
+ preference.value = prefVal;
+ this._deferredValueUpdateElements.delete(aElement);
+ ]]>
+ </body>
+ </method>
+ <field name="_deferredValueUpdateElements">
+ new Set();
+ </field>
+ <method name="_finalizeDeferredElements">
+ <body>
+ <![CDATA[
+ for (let el of this._deferredValueUpdateElements) {
+ if (el._deferredValueUpdateTask) {
+ el._deferredValueUpdateTask.finalize();
+ }
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="userChangedValue">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ let element = this.getPreferenceElement(aElement);
+ if (element.hasAttribute("preference")) {
+ if (element.getAttribute("delayprefsave") != "true") {
+ var preference = document.getElementById(element.getAttribute("preference"));
+ var prefVal = preference.getElementValue(element);
+ preference.value = prefVal;
+ } else {
+ if (!element._deferredValueUpdateTask) {
+ element._deferredValueUpdateTask = new this.DeferredTask(this._deferredValueUpdate.bind(this, element), 1000);
+ this._deferredValueUpdateElements.add(element);
+ } else {
+ // Each time the preference is changed, restart the delay.
+ element._deferredValueUpdateTask.disarm();
+ }
+ element._deferredValueUpdateTask.arm();
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="contentHeight">
+ <getter>
+ var targetHeight = parseInt(window.getComputedStyle(this).height);
+ targetHeight += parseInt(window.getComputedStyle(this).marginTop);
+ targetHeight += parseInt(window.getComputedStyle(this).marginBottom);
+ return targetHeight;
+ </getter>
+ </property>
+ </implementation>
+ <handlers>
+ <handler event="command">
+ // This "command" event handler tracks changes made to preferences by
+ // the user in this window.
+ if (event.sourceEvent)
+ event = event.sourceEvent;
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="select">
+ // This "select" event handler tracks changes made to colorpicker
+ // preferences by the user in this window.
+ if (event.target.localName == "colorpicker")
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="change">
+ // This "change" event handler tracks changes made to preferences by
+ // the user in this window.
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="input">
+ // This "input" event handler tracks changes made to preferences by
+ // the user in this window.
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="paneload">
+ <![CDATA[
+ // Initialize all values from preferences.
+ var elements = this.preferenceElements;
+ for (var i = 0; i < elements.length; ++i) {
+ try {
+ var preference = this.preferenceForElement(elements[i]);
+ preference.setElementValue(elements[i]);
+ } catch (e) {
+ dump("*** No preference found for " + elements[i].getAttribute("preference") + "\n");
+ }
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="panebutton" role="listitem"
+ extends="chrome://global/content/bindings/radio.xml#radio">
+ <content>
+ <xul:image class="paneButtonIcon" xbl:inherits="src"/>
+ <xul:label class="paneButtonLabel" xbl:inherits="value=label"/>
+ </content>
+ </binding>
+
+</bindings>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#
+# This is PrefWindow 6. The Code Could Well Be Ready, Are You?
+#
+# Historical References:
+# PrefWindow V (February 1, 2003)
+# PrefWindow IV (April 24, 2000)
+# PrefWindow III (January 6, 2000)
+# PrefWindow II (???)
+# PrefWindow I (June 4, 1999)
+#
diff --git a/comm/suite/components/bindings/prefwindow.xml b/comm/suite/components/bindings/prefwindow.xml
new file mode 100644
index 0000000000..64173b6c76
--- /dev/null
+++ b/comm/suite/components/bindings/prefwindow.xml
@@ -0,0 +1,548 @@
+<?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/. -->
+
+<!--
+ SeaMonkey Extended Preferences Window Framework
+
+ The binding implemented here mostly works like its toolkit ancestor, with
+ one important difference: the <prefwindow> recognizes the first <tree> in
+ its content and assumes that this is the navigation tree:
+
+ <prefwindow>
+ <tree>
+ ...
+ <treeitem id="prefTreeItemA" prefpane="prefPaneA">
+ ...
+ </tree>
+ <prefpane id="prefPaneB">...</prefpane>
+ <prefpane id="prefPaneA">
+ </prefwindow>
+
+ The <tree> structure defines the hierarchical layout of the preference
+ window's navigation tree. A <treeitem>'s "prefpane" attribute references
+ one of the <prefpane>s given on the <prefwindow>'s main level.
+ All <prefpane>s not referenced by a <treeitem> will be appended to the
+ navigation tree's top level. <treeitem>s can be nested as needed, but
+ <treeitem>s without a related <prefpane> will be hidden.
+
+ Furthermore, if the <prefwindow> has attribute "autopanes" set to "true",
+ non-existing <prefpane>s will be generated automatically from certain
+ attributes of the <treeitem>:
+ - "url" must contain the <prefpane>'s url
+ - "prefpane" should contain the <prefpane>'s desired id,
+ otherwise its url will be used as id
+ - "helpTopic" may contain an index into SeaMonkey's help
+
+ Unlike in XPFE, where preferences panels were loaded into a separate
+ iframe, <prefpane>s are an integral part of the <prefwindow> document,
+ by virtue of loadOverlay. Hence <script>s will be loaded into the
+ <prefwindow> scope and possibly clash. To avoid this, <prefpane>s should
+ specify a "script" attribute with a whitespace delimited list of scripts
+ to load into the <prefpane>'s context. The subscriptloader will take care
+ of any internal scoping, so no this.* fest is necessary inside the script.
+
+ <prefwindow> users who want to share the very same file between SeaMonkey
+ and other toolkit apps should hide the <tree> (set <tree>.hidden=true);
+ this binding will then unhide the <tree> if necessary, ie more than just
+ one <prefpane> exists.
+ Also, the <tree> will get the class "prefnavtree" added, so that it may be
+ prestyled by the SeaMonkey themes.
+ Setting <prefwindow xpfe="false"> will enforce the application of just the
+ basic toolkit <prefwindow> even in SeaMonkey. The same "xpfe" attribute
+ exists for <prefpane>, too.
+-->
+
+<!DOCTYPE bindings [
+ <!ENTITY % dtdPrefs SYSTEM "chrome://communicator/locale/pref/preferences.dtd"> %dtdPrefs;
+ <!ENTITY % dtdGlobalPrefs SYSTEM "chrome://global/locale/preferences.dtd"> %dtdGlobalPrefs;
+]>
+
+<bindings id="prefwindowBindings"
+ 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="prefwindow"
+ extends="chrome://communicator/content/bindings/preferences.xml#prefwindow">
+ <resources>
+ <stylesheet src="chrome://communicator/skin/preferences.css"/>
+ </resources>
+
+ <!-- The only difference between the following <content> and its toolkit
+ ancestor is the help button and the 'navTrees' <vbox> before the 'paneDeck'! -->
+ <content dlgbuttons="accept,cancel" persist="lastSelected screenX screenY"
+ closebuttonlabel="&preferencesCloseButton.label;"
+ closebuttonaccesskey="&preferencesCloseButton.accesskey;"
+ role="dialog">
+ <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
+ role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
+ <xul:hbox flex="1" class="paneDeckContainer">
+ <xul:vbox anonid="navTrees">
+ <children includes="tree"/>
+ </xul:vbox>
+ <xul:vbox flex="1">
+ <xul:dialogheader anonid="paneHeader" hidden="true"/>
+ <xul:deck anonid="paneDeck" flex="1">
+ <children includes="prefpane"/>
+ </xul:deck>
+ </xul:vbox>
+ </xul:hbox>
+ <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons" pack="end">
+#ifdef XP_UNIX
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
+ <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" icon="cancel"/>
+ <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
+#else
+ <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
+ <xul:spacer anonid="spacer" flex="1"/>
+ <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
+ <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+#endif
+ </xul:hbox>
+ <xul:hbox>
+ <children/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ // grab the first child tree and try to tie it to the prefpanes
+ var tree = this.getElementsByTagName('tree')[0];
+ this.initNavigationTree(tree);
+ // hide the toolkit pref strip if we have a tree
+ if (this._navigationTree)
+ this._selector.hidden = true;
+ ]]>
+ </constructor>
+
+ <field name="_navigationTree">null</field>
+
+ <!-- <prefwindow> users can call this method to exchange the <tree> -->
+ <method name="initNavigationTree">
+ <parameter name="aTreeElement"/>
+ <body>
+ <![CDATA[
+ this._navigationTree = null;
+ if (!aTreeElement)
+ return;
+
+ // don't grab trees in prefpanes etc.
+ if (aTreeElement.parentNode != this)
+ return;
+
+ // autogenerate <prefpane>s from <treecell>.url if requested
+ var autopanes = (this.getAttribute('autopanes') == 'true');
+ if (!autopanes)
+ {
+ // without autopanes, we can return early: don't bother
+ // with a navigation tree if we only have one prefpane
+ aTreeElement.hidden = (this.preferencePanes.length < 2);
+ if (aTreeElement.hidden)
+ return;
+ }
+
+ // ensure that we have a tree body
+ if (!aTreeElement.getElementsByTagName('treechildren').length)
+ aTreeElement.appendChild(document.createElement('treechildren'));
+
+ // ensure that we have a tree column
+ if (!aTreeElement.getElementsByTagName('treecol').length)
+ {
+ var navcols = document.createElement('treecols');
+ var navcol = document.createElement('treecol');
+ navcol.setAttribute('id', 'navtreecol');
+ navcol.setAttribute('primary', true);
+ navcol.setAttribute('flex', 1);
+ navcol.setAttribute('hideheader', true);
+ navcols.appendChild(navcol);
+ aTreeElement.appendChild(navcols);
+ aTreeElement.setAttribute('hidecolumnpicker', true);
+ }
+
+ // add the class "prefnavtree", so that themes can set defaults
+ aTreeElement.className += ' prefnavtree';
+
+ // Do some magic with the treeitem ingredient:
+ // - if it has a label attribute but no treerow child,
+ // generate a treerow with a treecell child with that label
+ // - if it has a prefpane attribute, tie it to that panel
+ // - if still no panel found and a url attribute is present,
+ // autogenerate the prefpane and connect to it
+ var treeitems = aTreeElement.getElementsByTagName('treeitem');
+ for (var i = 0; i < treeitems.length; ++i)
+ {
+ var node = treeitems[i];
+ var label = node.getAttribute('label');
+ if (label)
+ {
+ // autocreate the treecell?
+ var row = node.firstChild;
+ while (row && row.nodeName != 'treerow')
+ row = row.nextSibling;
+ if (!row)
+ {
+ var itemrow = document.createElement('treerow');
+ var itemcell = document.createElement('treecell');
+ itemcell.setAttribute('label', label);
+ itemrow.appendChild(itemcell);
+ node.appendChild(itemrow);
+ }
+ }
+ var paneID = node.getAttribute('prefpane');
+ var pane = paneID && document.getElementById(paneID);
+ if (!pane && autopanes)
+ {
+ // if we have a url, create a <prefpane> for it
+ var paneURL = node.getAttribute('url');
+ if (paneURL)
+ {
+ // reuse paneID if present, else use the url as id
+ pane = document.createElement('prefpane');
+ pane.setAttribute('id', paneID || paneURL);
+ pane.setAttribute('src', paneURL);
+ pane.setAttribute('label', label || paneID || paneURL);
+ var helpTopic = node.getAttribute('helpTopic');
+ if (helpTopic)
+ {
+ pane.setAttribute('helpURI', 'chrome://communicator/locale/help/suitehelp.rdf');
+ pane.setAttribute('helpTopic', helpTopic);
+ }
+ // add pane to prefwindow
+ this.appendChild(pane);
+ }
+ }
+ node.prefpane = pane;
+ if (pane)
+ pane.preftreeitem = node;
+ // hide unused treeitems
+ node.hidden = !pane;
+ }
+
+ // now that the number of <prefpane>s is known, try to return early:
+ // don't bother with a navigation tree if we only have one prefpane
+ aTreeElement.hidden = (this.preferencePanes.length < 2);
+ if (aTreeElement.hidden)
+ return;
+ this._navigationTree = aTreeElement;
+
+ // append any still unreferenced <prefpane>s to the tree's top level
+ for (var j = 0; j < this.preferencePanes.length; ++j)
+ {
+ // toolkit believes in fancy pane resizing - we don't
+ var lostpane = this.preferencePanes[j];
+ lostpane.setAttribute('flex', 1);
+
+ if (!("preftreeitem" in lostpane))
+ {
+ var treebody = this._navigationTree
+ .getElementsByTagName('treechildren')[0];
+ var treeitem = document.createElement('treeitem');
+ var treerow = document.createElement('treerow');
+ var treecell = document.createElement('treecell');
+ var label = lostpane.getAttribute('label');
+ if (!label)
+ label = lostpane.getAttribute('id');
+ treecell.setAttribute('label', label);
+ treerow.appendChild(treecell);
+ treeitem.appendChild(treerow);
+ treebody.appendChild(treeitem);
+ treeitem.prefpane = lostpane;
+ lostpane.preftreeitem = treeitem;
+ }
+ }
+
+ // Some parts of the toolkit base binding's initialization code (like
+ // panel select events) "fire" before we get here. Thus, we may need
+ // to sync the tree manually now (again), if we added any panels or
+ // if toolkit failed to select one.
+ // (This is a loose copy from the toolkit ctor.)
+ var lastPane = this.lastSelected &&
+ document.getElementById(this.lastSelected);
+ if (!lastPane)
+ this.lastSelected = "";
+ if ("arguments" in window && window.arguments[0])
+ {
+ var initialPane = document.getElementById(window.arguments[0]);
+ if (initialPane && initialPane.nodeName == "prefpane")
+ {
+ this.currentPane = initialPane;
+ this.lastSelected = initialPane.id;
+ }
+ }
+ else if (lastPane)
+ this.currentPane = lastPane;
+ try
+ {
+ this.showPane(this.currentPane); // may need to load it first
+ this.syncTreeWithPane(this.currentPane, true);
+ }
+ catch (e)
+ {
+ dump('***** broken prefpane: ' + this.currentPane.id + '\n' + e + '\n');
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- don't do any fancy animations -->
+ <property name="_shouldAnimate" onget="return false;"/>
+
+ <method name="setPaneTitle">
+ <parameter name="aPaneElement"/>
+ <body>
+#ifndef XP_MACOSX
+ <![CDATA[
+ // show pane title, if given
+ var paneHeader = document.getAnonymousElementByAttribute(this, 'anonid', 'paneHeader');
+ var paneHeaderLabel = '';
+ if (aPaneElement)
+ paneHeaderLabel = aPaneElement.getAttribute('label');
+ paneHeader.hidden = !paneHeaderLabel;
+ if (!paneHeader.hidden)
+ paneHeader.setAttribute('title', paneHeaderLabel);
+ ]]>
+#endif
+ </body>
+ </method>
+
+ <method name="syncPaneWithTree">
+ <parameter name="aTreeIndex"/>
+ <body>
+ <![CDATA[
+ var pane = null;
+ if ((this._navigationTree) && (aTreeIndex >= 0))
+ {
+ // load the prefpane associated with this treeitem
+ var treeitem = this._navigationTree.contentView
+ .getItemAtIndex(aTreeIndex);
+ if ('prefpane' in treeitem)
+ {
+ pane = treeitem.prefpane;
+ if (pane && (this.currentPane != pane))
+ {
+ try
+ {
+ this.showPane(pane); // may need to load it first
+ }
+ catch (e)
+ {
+ dump('***** broken prefpane: ' + pane.id + '\n' + e + '\n');
+ pane = null;
+ }
+ }
+ }
+ }
+ // don't show broken panels
+ this._paneDeck.hidden = (pane == null);
+ this.setPaneTitle(pane);
+ ]]>
+ </body>
+ </method>
+
+ <method name="syncTreeWithPane">
+ <parameter name="aPane"/>
+ <parameter name="aExpand"/>
+ <body>
+ <![CDATA[
+ if (this._navigationTree && aPane)
+ {
+ if ('preftreeitem' in aPane)
+ {
+ // make sure the treeitem is visible
+ var container = aPane.preftreeitem;
+ if (!aExpand)
+ container = container.parentNode.parentNode;
+ while (container != this._navigationTree)
+ {
+ container.setAttribute('open', true);
+ container = container.parentNode.parentNode;
+ }
+
+ // mark selected pane in navigation tree
+ var index = this._navigationTree.contentView
+ .getIndexOfItem(aPane.preftreeitem);
+ this._navigationTree.view.selection.select(index);
+ }
+ }
+ this.setPaneTitle(aPane);
+ if (this.getAttribute("overflow") != "auto")
+ {
+ if (this.scrollHeight > window.innerHeight)
+ window.innerHeight = this.scrollHeight;
+ if (this.scrollWidth > window.innerWidth)
+ window.innerWidth = this.scrollWidth;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- copied from contextHelp.js
+ Locate existing help window for this helpFileURI. -->
+ <method name="locateHelpWindow">
+ <parameter name="helpFileURI"/>
+ <body>
+ <![CDATA[
+ const iterator = Services.wm.getEnumerator("suite:help");
+ var topWindow = null;
+ var aWindow;
+
+ // Loop through help windows looking for one with selected helpFileURI
+ while (iterator.hasMoreElements())
+ {
+ aWindow = iterator.getNext();
+ if (aWindow.closed)
+ continue;
+ if (aWindow.getHelpFileURI() == helpFileURI)
+ topWindow = aWindow;
+ }
+ return topWindow;
+ ]]>
+ </body>
+ </method>
+
+ <!-- copied from contextHelp.js
+ Opens up the Help Viewer with the specified topic and helpFileURI. -->
+ <method name="openHelp">
+ <parameter name="topic"/>
+ <parameter name="helpFileURI"/>
+ <body>
+ <![CDATA[
+ // Empty help windows are not helpful...
+ if (!helpFileURI)
+ return;
+
+ // Try to find previously opened help.
+ var topWindow = this.locateHelpWindow(helpFileURI);
+ if (topWindow)
+ {
+ // Open topic in existing window.
+ topWindow.focus();
+ topWindow.displayTopic(topic);
+ }
+ else
+ {
+ // Open topic in new window.
+ const params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
+ .createInstance(Ci.nsIDialogParamBlock);
+ params.SetNumberStrings(2);
+ params.SetString(0, helpFileURI);
+ params.SetString(1, topic);
+ Services.ww.openWindow(null,
+ "chrome://help/content/help.xul",
+ "_blank",
+ "chrome,all,alwaysRaised,dialog=no",
+ params);
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="dialoghelp">
+ <![CDATA[
+ this.openHelp(this.currentPane.helpTopic, this.currentPane.getAttribute("helpURI"));
+ ]]>
+ </handler>
+ <handler event="select">
+ <![CDATA[
+ // navigation tree select or deck change?
+ var target = event.originalTarget;
+ if (target == this._navigationTree)
+ {
+ this.syncPaneWithTree(target.currentIndex);
+ }
+ else if (target == this._paneDeck)
+ {
+ // deck.selectedIndex is a string!
+ var pane = this.preferencePanes[Number(target.selectedIndex)];
+ this.syncTreeWithPane(pane, false);
+ }
+ ]]>
+ </handler>
+
+ <handler event="paneload">
+ <![CDATA[
+ // panes may load asynchronously,
+ // so we have to "late-sync" those to our navigation tree
+ this.syncTreeWithPane(event.originalTarget, false);
+ ]]>
+ </handler>
+
+ <handler event="keypress" key="&focusSearch.key;" modifiers="accel">
+ <![CDATA[
+ var searchBox = this.currentPane.getElementsByAttribute("type", "search")[0];
+ if (searchBox)
+ {
+ searchBox.focus();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="prefpane"
+ extends="chrome://communicator/content/bindings/preferences.xml#prefpane">
+ <resources>
+ <stylesheet src="chrome://communicator/skin/preferences.css"/>
+ </resources>
+
+ <handlers>
+ <handler event="paneload">
+ <![CDATA[
+ // Since all <prefpane>s now share the same global document, their
+ // <script>s might clash. Thus we expect the "script" attribute to
+ // contain a whitespace delimited list of script files to be loaded
+ // into the <prefpane>'s context.
+
+ // list of scripts to load
+ var scripts = this.getAttribute('script').match(/\S+/g);
+ if (!scripts)
+ return;
+ var count = scripts.length;
+ for (var i = 0; i < count; ++i)
+ {
+ var script = scripts[i];
+ if (script)
+ {
+ try
+ {
+ Services.scriptloader.loadSubScript(script, this);
+ }
+ catch (e)
+ {
+ let errorStr =
+ "prefpane.paneload: loadSubScript(" + script + ") failed:\n" +
+ (e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n"
+ : "") +
+ e + " - " + e.stack + "\n";
+ dump(errorStr);
+ Cu.reportError(errorStr);
+ }
+ }
+ }
+
+ // if we have a Startup method, call it
+ if ('Startup' in this)
+ this.Startup();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/comm/suite/components/bindings/spinbuttons.xml b/comm/suite/components/bindings/spinbuttons.xml
new file mode 100644
index 0000000000..90c249c9b2
--- /dev/null
+++ b/comm/suite/components/bindings/spinbuttons.xml
@@ -0,0 +1,92 @@
+<?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">
+
+ <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))
+ cancel = true;
+ }
+
+ return !cancel;
+ ]]>
+ </handler>
+
+ </handlers>
+ </binding>
+
+</bindings> \ No newline at end of file
diff --git a/comm/suite/components/bindings/textbox.xml b/comm/suite/components/bindings/textbox.xml
new file mode 100644
index 0000000000..74893c29a8
--- /dev/null
+++ b/comm/suite/components/bindings/textbox.xml
@@ -0,0 +1,251 @@
+<?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/. -->
+
+<!-- This files relies on these specific Chrome/XBL globals -->
+<!-- globals ChromeWindow -->
+
+<!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">
+
+ <content>
+ <children/>
+ <xul:moz-input-box 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:moz-input-box>
+ </content>
+
+ <implementation implements="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) {
+ this.mEditor = this.inputField.editor;
+ }
+ return this.mEditor;
+ ]]></getter>
+ </property>
+
+ <method name="reset">
+ <body><![CDATA[
+ this.value = this.defaultValue;
+ if (!this.editor) {
+ return false;
+ }
+ this.editor.clearUndoRedo();
+ return true;
+ ]]></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) {
+ for (let x in Ci.nsIEditor) {
+ if (/^eNewlines/.test(x)) {
+ if (str == RegExp.rightContext.toLowerCase()) {
+ this.editor.newlineHandling = Ci.nsIEditor[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 {
+ if (!this.editor || !this.editor.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.isChromeWindow &&
+ 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;
+ box._doPopupItemEnabling(box.menupopup);
+ </handler>
+#endif
+ </handlers>
+ </binding>
+</bindings>
diff --git a/comm/suite/components/bindings/toolbar-xpfe.xml b/comm/suite/components/bindings/toolbar-xpfe.xml
new file mode 100644
index 0000000000..d489a2eb73
--- /dev/null
+++ b/comm/suite/components/bindings/toolbar-xpfe.xml
@@ -0,0 +1,333 @@
+<?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">
+
+ <!-- With the move to the new toolkit, SeaMonkey needs to overwrite certain
+ bindings if it wants to keep its distinctive likeness. The bindings
+ found here reimplement the XPFE toolbar behaviour by providing toolbar
+ bindings in in the chrome://communicator/ domain that are based upon the
+ new toolkit's toolbar bindings in the chrome://global/ domain.
+ The now hidden new toolkit bindings are accessible via a set xpfe="false"
+ attribute, though.
+ -->
+
+ <binding id="grippytoolbox" extends="chrome://communicator/content/bindings/toolbar.xml#toolbox">
+ <content orient="vertical">
+ <xul:vbox flex="1" class="toolbar-internal-box">
+ <children/>
+ </xul:vbox>
+ <xul:hbox tbattr="collapsed-tray-holder" class="collapsed-tray-holder" moz-collapsed="true" xbl:inherits="collapsed=inFullscreen">
+ <xul:hbox tbattr="collapsed-tray" class="collapsed-tray"/>
+ <xul:spacer flex="1" class="collapsed-tray-spacer"/>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <field name="palette">
+ this.getElementsByTagName("toolbarpalette").item(0);
+ </field>
+
+ <constructor>
+ <![CDATA[
+ var set = this.toolbarset;
+ if (!set)
+ return;
+ var toolbars = this.getElementsByAttribute("customindex", "*");
+ for (let i = 0; i < toolbars.length; ++i) {
+ let bar = toolbars[i];
+ let name = bar.getAttribute("toolbarname").replace(" ", "_");
+ if (name) {
+ let attrs = ["mode", "iconsize", "labelalign", "hidden",
+ "collapsed", "moz-collapsed"];
+ for (let j = 0; j < attrs.length; j++) {
+ let attr = set.getAttribute(name + attrs[j]);
+ if (attr)
+ bar.setAttribute(attrs[j], attr);
+ }
+ bar.setAttribute("grippytooltiptext", name);
+ }
+ }
+ ]]>
+ </constructor>
+
+ <method name="collapseToolbar">
+ <parameter name="toolbar"/>
+ <body>
+ <![CDATA[
+ try {
+ this.createCollapsedGrippy(toolbar);
+ toolbar.setAttribute("collapsed", "true");
+ document.persist(toolbar.id, "collapsed");
+ toolbar.removeAttribute("moz-collapsed");
+ document.persist(toolbar.id, "moz-collapsed");
+ if (toolbar.hasAttribute("customindex"))
+ this.persistCustomCollapse(toolbar, "true");
+ }
+ catch(e) {
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="expandToolbar">
+ <parameter name="aGrippyID"/>
+ <body>
+ <![CDATA[
+ var idString = aGrippyID.substring("moz_tb_collapsed_".length, aGrippyID.length);
+ var toolbar = document.getElementById(idString);
+ toolbar.setAttribute("collapsed", "false");
+ var collapsedTray = document.getAnonymousElementByAttribute(this, "tbattr", "collapsed-tray");
+ var collapsedToolbar = document.getElementById("moz_tb_collapsed_" + toolbar.id);
+ collapsedToolbar.remove();
+ if (!collapsedTray.hasChildNodes())
+ document.getAnonymousElementByAttribute(this, "tbattr", "collapsed-tray-holder").setAttribute("moz-collapsed", "true");
+ document.persist(toolbar.id, "collapsed");
+ if (toolbar.hasAttribute("customindex"))
+ this.persistCustomCollapse(toolbar, "false");
+ ]]>
+ </body>
+ </method>
+
+ <method name="createCollapsedGrippy">
+ <parameter name="aToolbar"/>
+ <body>
+ <![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var existingGrippy = document.getAnonymousElementByAttribute(this, "id", "moz_tb_collapsed_" + aToolbar.id);
+ if (!existingGrippy) {
+ var grippy = document.getAnonymousElementByAttribute(aToolbar, "tbattr", "toolbar-grippy");
+ var boxObject = grippy.boxObject.QueryInterface(Ci.nsIBoxObject);
+ var collapsedGrippy = document.createElementNS(XUL_NS, "toolbargrippy");
+ if (collapsedGrippy) {
+ var width = boxObject.height > 20 ? boxObject.height : 23;
+ var height = boxObject.width > 10 ? boxObject.width : 12;
+ var styleString = "width: " + width + "px; height: " + height + "px;";
+ collapsedGrippy.setAttribute("style", styleString);
+ collapsedGrippy.setAttribute("tooltiptext", aToolbar.getAttribute("grippytooltiptext"));
+ collapsedGrippy.setAttribute("id", "moz_tb_collapsed_" + aToolbar.id);
+ collapsedGrippy.setAttribute("moz_grippy_collapsed", "true");
+ collapsedGrippy.setAttribute("tbgrippy-collapsed", "true");
+ var collapsedTrayHolder = document.getAnonymousElementByAttribute(this, "tbattr", "collapsed-tray-holder");
+ if (collapsedTrayHolder.getAttribute("moz-collapsed") == "true")
+ collapsedTrayHolder.removeAttribute("moz-collapsed");
+ document.getAnonymousElementByAttribute(this, "tbattr", "collapsed-tray").appendChild(collapsedGrippy);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="persistCustomCollapse">
+ <parameter name="toolbar"/>
+ <parameter name="collapsed"/>
+ <body>
+ <![CDATA[
+ var attr = toolbar.getAttribute("toolbarname").replace(" ", "_") + "collapsed";
+ this.toolbarset.setAttribute(attr, collapsed);
+ document.persist(this.toolbarset.id, attr);
+ var attr = toolbar.getAttribute("toolbarname").replace(" ", "_") + "moz-collapsed";
+ this.toolbarset.removeAttribute(attr);
+ document.persist(this.toolbarset.id, attr);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="grippytoolbar" extends="chrome://communicator/content/bindings/toolbar.xml#toolbar">
+ <content>
+ <xul:hbox flex="1" class="toolbar-box box-inherit">
+ <xul:toolbargrippy xbl:inherits="last-toolbar,hidden=grippyhidden,collapsed=inFullscreen"
+ tbattr="toolbar-grippy"
+ class="toolbar-grippy"/>
+ <xul:hbox flex="1" class="toolbar-holder box-inherit"
+ xbl:inherits="collapsed,last-toolbar,orient,align,pack">
+ <children/>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ if (Services.prefs.getBoolPref("browser.toolbars.grippyhidden")) {
+ this.setAttribute("grippyhidden", "true");
+ }
+
+ if (this.getAttribute("moz-collapsed") == "true" &&
+ this.parentNode.localName == "toolbox")
+ this.parentNode.collapseToolbar(this);
+ else if (this.getAttribute("collapsed") == "true" &&
+ this.parentNode.localName == "toolbox")
+ this.parentNode.createCollapsedGrippy(this);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="grippytoolbar-primary" extends="chrome://communicator/content/bindings/toolbar-xpfe.xml#grippytoolbar">
+ <implementation implements="nsIObserver">
+ <constructor>
+ <![CDATA[
+ this.prefs.addObserver(this.domain, this);
+ if (this.prefs.getIntPref(this.domain) != 2)
+ this.observe(this.prefs, "nsPref:changed", this.domain);
+ ]]>
+ </constructor>
+
+ <destructor>
+ this.prefs.removeObserver(this.domain, this);
+ </destructor>
+
+ <field name="domain" readonly="true">
+ "browser.chrome.toolbar_style"
+ </field>
+
+ <field name="prefs" readonly="true">
+ Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService).getBranch(null)
+ </field>
+
+ <method name="observe">
+ <parameter name="subject"/>
+ <parameter name="topic"/>
+ <parameter name="name"/>
+ <body>
+ <![CDATA[
+ if (topic == "nsPref:changed" && name == this.domain) {
+ const styles = ["icons", "text", "full"];
+ const style = styles[this.prefs.getIntPref(name)];
+ this.parentNode.setAttribute("mode", style); // toolbox
+ if (!this.hasAttribute("customizable") ||
+ !this.hasAttribute("ignoremodepref"))
+ this.setAttribute("mode", style);
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="grippytoolbar-drag"
+ extends="chrome://communicator/content/bindings/toolbar-xpfe.xml#grippytoolbar">
+ <implementation>
+ <field name="_dragBindingAlive">true</field>
+ <constructor>
+ <![CDATA[
+ if (!this._draggableStarted) {
+ this._draggableStarted = true;
+ try {
+ let tmp = {};
+ ChromeUtils.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="grippytoolbar-menubar"
+ extends="chrome://communicator/content/bindings/toolbar-xpfe.xml#grippytoolbar"
+ display="xul:menubar"/>
+
+ <binding id="grippymenubar" extends="chrome://communicator/content/bindings/toolbar.xml#menubar">
+ <content>
+ <xul:hbox flex="1" class="toolbar-box">
+ <xul:toolbargrippy xbl:inherits="last-toolbar,hidden=grippyhidden"
+ tbattr="toolbar-grippy" class="toolbar-grippy"/>
+ <xul:hbox flex="1" class="toolbar-holder" xbl:inherits="collapsed,last-toolbar">
+ <children/>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <constructor>
+ <![CDATA[
+ if (Services.prefs.getBoolPref("browser.toolbars.grippyhidden")) {
+ this.setAttribute("grippyhidden", "true");
+ }
+
+ if (this.getAttribute("moz-collapsed") == "true" &&
+ this.parentNode.localName == "toolbox")
+ this.parentNode.collapseToolbar(this);
+ else if (this.getAttribute("collapsed") == "true" &&
+ this.parentNode.localName == "toolbox")
+ this.parentNode.createCollapsedGrippy(this);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="toolbargrippy" display="xul:button"
+ extends="chrome://communicator/content/bindings/toolbar.xml#toolbar-base">
+ <content>
+ <xul:image class="toolbargrippy-arrow"/>
+ <xul:spacer class="toolbargrippy-texture" flex="1"/>
+ </content>
+
+ <implementation>
+ <property name="collapsed">
+ <getter>
+ return this.hasAttribute("moz_grippy_collapsed");
+ </getter>
+ <setter>
+ if (val)
+ this.setAttribute("moz_grippy_collapsed", "true");
+ else
+ this.removeAttribute("moz_grippy_collapsed");
+ return val;
+ </setter>
+ </property>
+
+ <method name="returnNode">
+ <parameter name="aNodeA"/>
+ <parameter name="aNodeB"/>
+ <body>
+ <![CDATA[
+ var node = this.parentNode;
+ while (node && node.localName != "window" &&
+ (node.localName != aNodeA && (node.localName != aNodeB))) {
+ node = node.parentNode;
+ }
+ return node;
+ ]]>
+ </body>
+ </method>
+
+ <method name="grippyTriggered">
+ <body>
+ <![CDATA[
+ var toolbox = this.returnNode("toolbox");
+ var toolbar = this.returnNode("toolbar", "menubar");
+ if (this.collapsed)
+ toolbox.expandToolbar(this.id);
+ else
+ toolbox.collapseToolbar(toolbar);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ this.grippyTriggered();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>
+
diff --git a/comm/suite/components/bindings/toolbar.xml b/comm/suite/components/bindings/toolbar.xml
new file mode 100644
index 0000000000..cf36b421ad
--- /dev/null
+++ b/comm/suite/components/bindings/toolbar.xml
@@ -0,0 +1,579 @@
+<?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="toolbox">
+ <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 = this.toolbarset.nextSibling;
+
+ if (this.toolbarset) {
+ // Create each toolbar described by the toolbarset.
+ var index = 0;
+ while (this.toolbarset.hasAttribute("toolbar" + (++index))) {
+ var toolbarInfo = this.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">
+ <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.includes(this))
+ 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 listener(event) {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", listener);
+ self._init();
+ });
+ }
+ ]]>
+ </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(item => !defaultOrNoninteractive.includes(item));
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="toolbar-menubar-autohide"
+ extends="chrome://communicator/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(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);
+ this.contextMenu.addEventListener("popuphiding", this);
+ this.toolbar.addEventListener("mousemove", this);
+ },
+ handleEvent(event) {
+ switch (event.type) {
+ case "popupshown":
+ this.toolbar.removeEventListener("mousemove", this);
+ break;
+ case "popuphiding":
+ case "mousemove":
+ this.toolbar._setInactiveAsync();
+ this.toolbar.removeEventListener("mousemove", this);
+ this.contextMenu.removeEventListener("popuphiding", this);
+ this.contextMenu.removeEventListener("popupshown", this);
+ 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="menubar" role="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="toolbarpaletteitem">
+ <content>
+ <xul:hbox class="toolbarpaletteitem-box" flex="1" xbl:inherits="type,place">
+ <children/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="toolbarpaletteitem-palette"
+ extends="chrome://communicator/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>
+
+ <binding id="toolbarpaletteitem-palette-wrapping-label"
+ extends="chrome://communicator/content/bindings/toolbar.xml#toolbarpaletteitem">
+ <content>
+ <xul:hbox class="toolbarpaletteitem-box" xbl:inherits="type,place">
+ <children/>
+ </xul:hbox>
+ <xul:label xbl:inherits="xbl:text=title"/>
+ </content>
+ </binding>
+
+ <binding id="menu-button"
+ extends="chrome://global/content/bindings/button.xml#button-base">
+
+ <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>
+
+</bindings>