<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE bindings [
<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
%notificationDTD;
]>

<bindings id="notificationBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="notificationbox">
    <content>
      <xul:stack xbl:inherits="hidden=notificationshidden"
                 class="notificationbox-stack">
        <xul:spacer/>
        <children includes="notification"/>
      </xul:stack>
      <children/>
    </content>

    <implementation>
      <field name="PRIORITY_INFO_LOW" readonly="true">1</field>
      <field name="PRIORITY_INFO_MEDIUM" readonly="true">2</field>
      <field name="PRIORITY_INFO_HIGH" readonly="true">3</field>
      <field name="PRIORITY_WARNING_LOW" readonly="true">4</field>
      <field name="PRIORITY_WARNING_MEDIUM" readonly="true">5</field>
      <field name="PRIORITY_WARNING_HIGH" readonly="true">6</field>
      <field name="PRIORITY_CRITICAL_LOW" readonly="true">7</field>
      <field name="PRIORITY_CRITICAL_MEDIUM" readonly="true">8</field>
      <field name="PRIORITY_CRITICAL_HIGH" readonly="true">9</field>
      <field name="PRIORITY_CRITICAL_BLOCK" readonly="true">10</field>

      <field name="currentNotification">null</field>

      <field name="_closedNotification">null</field>
      <field name="_blockingCanvas">null</field>
      <field name="_animating">false</field>

      <property name="notificationsHidden"
                onget="return this.getAttribute('notificationshidden') == 'true';">
        <setter>
          if (val)
            this.setAttribute('notificationshidden', true);
          else this.removeAttribute('notificationshidden');
          return val;
        </setter>
      </property>

      <property name="allNotifications" readonly="true">
        <getter>
        <![CDATA[
          var closedNotification = this._closedNotification;
          var notifications = this.getElementsByTagName('notification');
          return Array.filter(notifications, n => n != closedNotification);
        ]]>
        </getter>
      </property>

      <method name="getNotificationWithValue">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var notifications = this.allNotifications;
            for (var n = notifications.length - 1; n >= 0; n--) {
              if (aValue == notifications[n].getAttribute("value"))
                return notifications[n];
            }
            return null;
          ]]>
        </body>
      </method>

      <method name="appendNotification">
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <parameter name="aImage"/>
        <parameter name="aPriority"/>
        <parameter name="aButtons"/>
        <parameter name="aEventCallback"/>
        <body>
          <![CDATA[
            if (aPriority < this.PRIORITY_INFO_LOW ||
                aPriority > this.PRIORITY_CRITICAL_BLOCK)
              throw "Invalid notification priority " + aPriority;

            // check for where the notification should be inserted according to
            // priority. If two are equal, the existing one appears on top.
            var notifications = this.allNotifications;
            var insertPos = null;
            for (var n = notifications.length - 1; n >= 0; n--) {
              if (notifications[n].priority < aPriority)
                break;
              insertPos = notifications[n];
            }

            const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            var newitem = document.createElementNS(XULNS, "notification");
            // Can't use instanceof in case this was created from a different document:
            let labelIsDocFragment = aLabel && typeof aLabel == "object" && aLabel.nodeType &&
                                     aLabel.nodeType == aLabel.DOCUMENT_FRAGMENT_NODE;
            if (!labelIsDocFragment)
              newitem.setAttribute("label", aLabel);
            newitem.setAttribute("value", aValue);
            if (aImage)
              newitem.setAttribute("image", aImage);
            newitem.eventCallback = aEventCallback;

            if (aButtons) {
              // The notification-button-default class is added to the button
              // with isDefault set to true. If there is no such button, it is
              // added to the first button (unless that button has isDefault
              // set to false). There cannot be multiple default buttons.
              var defaultElem;

              for (var b = 0; b < aButtons.length; b++) {
                var button = aButtons[b];
                var buttonElem = document.createElementNS(XULNS, "button");
                buttonElem.setAttribute("label", button.label);
                if (typeof button.accessKey == "string")
                  buttonElem.setAttribute("accesskey", button.accessKey);
                if (typeof button.type == "string") {
                  buttonElem.setAttribute("type", button.type);
                  if ((button.type == "menu-button" || button.type == "menu") &&
                      "popup" in button) {
                    buttonElem.appendChild(button.popup);
                    delete button.popup;
                  }
                  if (typeof button.anchor == "string")
                    buttonElem.setAttribute("anchor", button.anchor);
                }
                buttonElem.classList.add("notification-button");

                if (button.isDefault ||
                    b == 0 && !("isDefault" in button))
                  defaultElem = buttonElem;

                newitem.appendChild(buttonElem);
                buttonElem.buttonInfo = button;
              }

              if (defaultElem)
                defaultElem.classList.add("notification-button-default");
            }

            newitem.setAttribute("priority", aPriority);
            if (aPriority >= this.PRIORITY_CRITICAL_LOW)
              newitem.setAttribute("type", "critical");
            else if (aPriority <= this.PRIORITY_INFO_HIGH)
              newitem.setAttribute("type", "info");
            else
              newitem.setAttribute("type", "warning");

            if (!insertPos) {
              newitem.style.position = "fixed";
              newitem.style.top = "100%";
              newitem.style.marginTop = "-15px";
              newitem.style.opacity = "0";
            }
            this.insertBefore(newitem, insertPos);
            // Can only insert the document fragment after the item has been created because
            // otherwise the XBL structure isn't there yet:
            if (labelIsDocFragment) {
              document.getAnonymousElementByAttribute(newitem, "anonid", "messageText")
                .appendChild(aLabel);
            }

            if (!insertPos)
              this._showNotification(newitem, true);

            // Fire event for accessibility APIs
            var event = document.createEvent("Events");
            event.initEvent("AlertActive", true, true);
            newitem.dispatchEvent(event);

            return newitem;
          ]]>
        </body>
      </method>

      <method name="removeNotification">
        <parameter name="aItem"/>
        <parameter name="aSkipAnimation"/>
        <body>
          <![CDATA[
            if (aItem == this.currentNotification)
              this.removeCurrentNotification(aSkipAnimation);
            else if (aItem != this._closedNotification)
              this._removeNotificationElement(aItem);
            return aItem;
          ]]>
        </body>
      </method>

      <method name="_removeNotificationElement">
        <parameter name="aChild"/>
        <body>
          <![CDATA[
            if (aChild.eventCallback)
              aChild.eventCallback("removed");
            this.removeChild(aChild);

            // make sure focus doesn't get lost (workaround for bug 570835)
            let fm = Components.classes["@mozilla.org/focus-manager;1"]
                               .getService(Components.interfaces.nsIFocusManager);
            if (!fm.getFocusedElementForWindow(window, false, {}))
              fm.moveFocus(window, this, fm.MOVEFOCUS_FORWARD, 0);
          ]]>
        </body>
      </method>

      <method name="removeCurrentNotification">
        <parameter name="aSkipAnimation"/>
        <body>
          <![CDATA[
            this._showNotification(this.currentNotification, false, aSkipAnimation);
          ]]>
        </body>
      </method>

      <method name="removeAllNotifications">
        <parameter name="aImmediate"/>
        <body>
          <![CDATA[
            var notifications = this.allNotifications;
            for (var n = notifications.length - 1; n >= 0; n--) {
              if (aImmediate)
                this._removeNotificationElement(notifications[n]);
              else
                this.removeNotification(notifications[n]);
            }
            this.currentNotification = null;

            // Must clear up any currently animating notification
            if (aImmediate)
              this._finishAnimation();
          ]]>
        </body>
      </method>

      <method name="removeTransientNotifications">
        <body>
          <![CDATA[
            var notifications = this.allNotifications;
            for (var n = notifications.length - 1; n >= 0; n--) {
              var notification = notifications[n];
              if (notification.persistence)
                notification.persistence--;
              else if (Date.now() > notification.timeout)
                this.removeNotification(notification);
            }
          ]]>
        </body>
      </method>

      <method name="_showNotification">
        <parameter name="aNotification"/>
        <parameter name="aSlideIn"/>
        <parameter name="aSkipAnimation"/>
        <body>
          <![CDATA[
            this._finishAnimation();

            var height = aNotification.boxObject.height;
            var skipAnimation = aSkipAnimation || (height == 0);

            if (aSlideIn) {
              this.currentNotification = aNotification;
              aNotification.style.removeProperty("position");
              aNotification.style.removeProperty("top");
              aNotification.style.removeProperty("margin-top");
              aNotification.style.removeProperty("opacity");

              if (skipAnimation) {
                this._setBlockingState(this.currentNotification);
                return;
              }
            }
            else {
              this._closedNotification = aNotification;
              var notifications = this.allNotifications;
              var idx = notifications.length - 1;
              this.currentNotification = (idx >= 0) ? notifications[idx] : null;

              if (skipAnimation) {
                this._removeNotificationElement(this._closedNotification);
                this._closedNotification = null;
                this._setBlockingState(this.currentNotification);
                return;
              }

              aNotification.style.marginTop = -height + "px";
              aNotification.style.opacity = 0;
            }

            this._animating = true;
          ]]>
        </body>
      </method>

      <method name="_finishAnimation">
        <body><![CDATA[
          if (this._animating) {
            this._animating = false;
            if (this._closedNotification) {
              this._removeNotificationElement(this._closedNotification);
              this._closedNotification = null;
            }
            this._setBlockingState(this.currentNotification);
          }
        ]]></body>
      </method>

      <method name="_setBlockingState">
        <parameter name="aNotification"/>
        <body>
          <![CDATA[
            var isblock = aNotification &&
                          aNotification.priority == this.PRIORITY_CRITICAL_BLOCK;
            var canvas = this._blockingCanvas;
            if (isblock) {
              if (!canvas)
                canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
              const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
              let content = this.firstChild;
              if (!content ||
                   content.namespaceURI != XULNS ||
                   content.localName != "browser")
                return;

              var width = content.boxObject.width;
              var height = content.boxObject.height;
              content.collapsed = true;

              canvas.setAttribute("width", width);
              canvas.setAttribute("height", height);
              canvas.setAttribute("flex", "1");

              this.appendChild(canvas);
              this._blockingCanvas = canvas;

              var bgcolor = "white";
              try {
                var prefService = Components.classes["@mozilla.org/preferences-service;1"].
                                    getService(Components.interfaces.nsIPrefBranch);
                bgcolor = prefService.getCharPref("browser.display.background_color");

                var win = content.contentWindow;
                var context = canvas.getContext("2d");
                context.globalAlpha = 0.5;
                context.drawWindow(win, win.scrollX, win.scrollY,
                                   width, height, bgcolor);
              }
              catch (ex) { }
            }
            else if (canvas) {
              canvas.parentNode.removeChild(canvas);
              this._blockingCanvas = null;
              let content = this.firstChild;
              if (content)
                content.collapsed = false;
            }
          ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="transitionend"><![CDATA[
        if (event.target.localName == "notification" &&
            event.propertyName == "margin-top")
          this._finishAnimation();
      ]]></handler>
    </handlers>

  </binding>

  <binding id="notification" role="xul:alert">
    <content>
      <xul:hbox class="notification-inner" flex="1" xbl:inherits="type">
        <xul:hbox anonid="details" align="center" flex="1"
                  oncommand="this.parentNode.parentNode._doButtonCommand(event);">
          <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
          <xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
          <xul:spacer flex="1"/>
          <children/>
        </xul:hbox>
        <xul:toolbarbutton ondblclick="event.stopPropagation();"
                           class="messageCloseButton close-icon tabbable"
                           xbl:inherits="hidden=hideclose"
                           tooltiptext="&closeNotification.tooltip;"
                           oncommand="document.getBindingParent(this).dismiss();"/>
      </xul:hbox>
    </content>
    <resources>
      <stylesheet src="chrome://global/skin/notification.css"/>
    </resources>
    <implementation>
      <property name="label" onset="this.setAttribute('label', val); return val;"
                             onget="return this.getAttribute('label');"/>
      <property name="value" onset="this.setAttribute('value', val); return val;"
                             onget="return this.getAttribute('value');"/>
      <property name="image" onset="this.setAttribute('image', val); return val;"
                             onget="return this.getAttribute('image');"/>
      <property name="type" onset="this.setAttribute('type', val); return val;"
                            onget="return this.getAttribute('type');"/>
      <property name="priority" onget="return parseInt(this.getAttribute('priority')) || 0;"
                                onset="this.setAttribute('priority', val); return val;"/>
      <property name="persistence" onget="return parseInt(this.getAttribute('persistence')) || 0;"
                                   onset="this.setAttribute('persistence', val); return val;"/>
      <field name="timeout">0</field>

      <property name="control" readonly="true">
        <getter>
          <![CDATA[
            var parent = this.parentNode;
            while (parent) {
              if (parent.localName == "notificationbox")
                return parent;
              parent = parent.parentNode;
            }
            return null;
          ]]>
        </getter>
      </property>

      <!-- This method should only be called when the user has
           manually closed the notification. If you want to
           programmatically close the notification, you should
           call close() instead. -->
      <method name="dismiss">
        <body>
          <![CDATA[
            if (this.eventCallback) {
              this.eventCallback("dismissed");
            }
            this.close();
          ]]>
        </body>
      </method>

      <method name="close">
        <body>
          <![CDATA[
            var control = this.control;
            if (control)
              control.removeNotification(this);
            else
              this.hidden = true;
          ]]>
        </body>
      </method>

      <method name="_doButtonCommand">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            if (!("buttonInfo" in aEvent.target))
              return;

            var button = aEvent.target.buttonInfo;
            if (button.popup) {
              document.getElementById(button.popup).
                openPopup(aEvent.originalTarget, "after_start", 0, 0, false, false, aEvent);
              aEvent.stopPropagation();
            }
            else {
              var callback = button.callback;
              if (callback) {
                var result = callback(this, button, aEvent.target);
                if (!result)
                  this.close();
                aEvent.stopPropagation();
              }
            }
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="popup-notification">
    <content>
      <xul:vbox>
        <xul:image class="popup-notification-icon"
                   xbl:inherits="popupid,src=icon,class=iconclass"/>
      </xul:vbox>
      <xul:vbox class="popup-notification-body" xbl:inherits="popupid">
        <xul:hbox align="start">
          <xul:vbox flex="1">
            <xul:label class="popup-notification-origin header"
                       xbl:inherits="value=origin,tooltiptext=origin"
                       crop="center"/>
            <xul:description class="popup-notification-description"
                             xbl:inherits="xbl:text=label,popupid"/>
          </xul:vbox>
          <xul:toolbarbutton anonid="closebutton"
                             class="messageCloseButton close-icon popup-notification-closebutton tabbable"
                             xbl:inherits="oncommand=closebuttoncommand"
                             tooltiptext="&closeNotification.tooltip;"/>
        </xul:hbox>
        <children includes="popupnotificationcontent"/>
        <xul:label class="text-link popup-notification-learnmore-link"
               xbl:inherits="onclick=learnmoreclick,href=learnmoreurl">&learnMore;</xul:label>
        <xul:checkbox anonid="checkbox"
                      xbl:inherits="hidden=checkboxhidden,checked=checkboxchecked,label=checkboxlabel,oncommand=checkboxcommand" />
        <xul:description class="popup-notification-warning" xbl:inherits="hidden=warninghidden,xbl:text=warninglabel"/>
        <xul:spacer flex="1"/>
        <xul:hbox class="popup-notification-button-container"
                  pack="end" align="center">
          <children includes="button"/>
          <xul:button anonid="button"
                      class="popup-notification-menubutton"
                      type="menu-button"
                      xbl:inherits="oncommand=buttoncommand,onpopupshown=buttonpopupshown,label=buttonlabel,accesskey=buttonaccesskey,disabled=mainactiondisabled">
            <xul:menupopup anonid="menupopup"
                           xbl:inherits="oncommand=menucommand">
              <children/>
              <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
                            label="&closeNotificationItem.label;"
                            xbl:inherits="oncommand=closeitemcommand,hidden=hidenotnow"/>
            </xul:menupopup>
          </xul:button>
        </xul:hbox>
      </xul:vbox>
    </content>
    <resources>
      <stylesheet src="chrome://global/skin/notification.css"/>
    </resources>
    <implementation>
      <field name="checkbox" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "checkbox");
      </field>
      <field name="closebutton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "closebutton");
      </field>
      <field name="button" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "button");
      </field>
      <field name="menupopup" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "menupopup");
      </field>
    </implementation>
  </binding>
</bindings>
