diff options
Diffstat (limited to 'comm/suite/components/bindings')
-rw-r--r-- | comm/suite/components/bindings/datetimepicker.xml | 1316 | ||||
-rw-r--r-- | comm/suite/components/bindings/findbar.xml | 162 | ||||
-rw-r--r-- | comm/suite/components/bindings/general.xml | 37 | ||||
-rw-r--r-- | comm/suite/components/bindings/generalBindings.xml | 31 | ||||
-rw-r--r-- | comm/suite/components/bindings/jar.mn | 20 | ||||
-rw-r--r-- | comm/suite/components/bindings/moz.build | 7 | ||||
-rw-r--r-- | comm/suite/components/bindings/notification.xml | 2423 | ||||
-rw-r--r-- | comm/suite/components/bindings/numberbox.xml | 217 | ||||
-rw-r--r-- | comm/suite/components/bindings/preferences.xml | 817 | ||||
-rw-r--r-- | comm/suite/components/bindings/prefwindow.xml | 548 | ||||
-rw-r--r-- | comm/suite/components/bindings/spinbuttons.xml | 92 | ||||
-rw-r--r-- | comm/suite/components/bindings/textbox.xml | 251 | ||||
-rw-r--r-- | comm/suite/components/bindings/toolbar-xpfe.xml | 333 | ||||
-rw-r--r-- | comm/suite/components/bindings/toolbar.xml | 579 |
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> |