browser/base/content/urlbarBindings.xml

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 <?xml version="1.0"?>
     3 # -*- Mode: HTML -*-
     4 # This Source Code Form is subject to the terms of the Mozilla Public
     5 # License, v. 2.0. If a copy of the MPL was not distributed with this
     6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     8 <!DOCTYPE bindings [
     9 <!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
    10 %notificationDTD;
    11 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
    12 %browserDTD;
    13 ]>
    15 <bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl"
    16           xmlns:html="http://www.w3.org/1999/xhtml"
    17           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    18           xmlns:xbl="http://www.mozilla.org/xbl">
    20   <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
    22     <content sizetopopup="pref">
    23       <xul:hbox anonid="textbox-container"
    24                 class="autocomplete-textbox-container urlbar-textbox-container"
    25                 flex="1" xbl:inherits="focused">
    26         <children includes="image|deck|stack|box">
    27           <xul:image class="autocomplete-icon" allowevents="true"/>
    28         </children>
    29         <xul:hbox anonid="textbox-input-box"
    30                   class="textbox-input-box urlbar-input-box"
    31                   flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
    32           <children/>
    33           <html:input anonid="input"
    34                       class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
    35                       allowevents="true"
    36                       xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
    37         </xul:hbox>
    38         <children includes="hbox"/>
    39       </xul:hbox>
    40       <xul:dropmarker anonid="historydropmarker"
    41                       class="autocomplete-history-dropmarker urlbar-history-dropmarker"
    42                       allowevents="true"
    43                       xbl:inherits="open,enablehistory,parentfocused=focused"/>
    44       <xul:popupset anonid="popupset"
    45                     class="autocomplete-result-popupset"/>
    46       <children includes="toolbarbutton"/>
    47     </content>
    49     <implementation implements="nsIObserver, nsIDOMEventListener">
    50       <constructor><![CDATA[
    51         this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
    52                                 .getService(Components.interfaces.nsIPrefService)
    53                                 .getBranch("browser.urlbar.");
    55         this._prefs.addObserver("", this, false);
    56         this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
    57         this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
    58         this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
    59         this.timeout = this._prefs.getIntPref("delay");
    60         this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
    61         this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
    62         this._ignoreNextSelect = false;
    64         this.inputField.controllers.insertControllerAt(0, this._copyCutController);
    65         this.inputField.addEventListener("mousedown", this, false);
    66         this.inputField.addEventListener("mousemove", this, false);
    67         this.inputField.addEventListener("mouseout", this, false);
    68         this.inputField.addEventListener("overflow", this, false);
    69         this.inputField.addEventListener("underflow", this, false);
    71         try {
    72           if (this._prefs.getBoolPref("unifiedcomplete")) {
    73             this.setAttribute("autocompletesearch", "unifiedcomplete");
    74           }
    75         } catch (ex) {}
    77         const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    78         var textBox = document.getAnonymousElementByAttribute(this,
    79                                                 "anonid", "textbox-input-box");
    80         var cxmenu = document.getAnonymousElementByAttribute(textBox,
    81                                             "anonid", "input-box-contextmenu");
    82         var pasteAndGo;
    83         cxmenu.addEventListener("popupshowing", function() {
    84           if (!pasteAndGo)
    85             return;
    86           var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
    87           var enabled = controller.isCommandEnabled("cmd_paste");
    88           if (enabled)
    89             pasteAndGo.removeAttribute("disabled");
    90           else
    91             pasteAndGo.setAttribute("disabled", "true");
    92         }, false);
    94         var insertLocation = cxmenu.firstChild;
    95         while (insertLocation.nextSibling &&
    96                insertLocation.getAttribute("cmd") != "cmd_paste")
    97           insertLocation = insertLocation.nextSibling;
    98         if (insertLocation) {
    99           pasteAndGo = document.createElement("menuitem");
   100           let label = Services.strings.createBundle("chrome://browser/locale/browser.properties").
   101                                    GetStringFromName("pasteAndGo.label");
   102           pasteAndGo.setAttribute("label", label);
   103           pasteAndGo.setAttribute("anonid", "paste-and-go");
   104           pasteAndGo.setAttribute("oncommand",
   105               "gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
   106           cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
   107         }
   108       ]]></constructor>
   110       <destructor><![CDATA[
   111         this._prefs.removeObserver("", this);
   112         this._prefs = null;
   113         this.inputField.controllers.removeController(this._copyCutController);
   114         this.inputField.removeEventListener("mousedown", this, false);
   115         this.inputField.removeEventListener("mousemove", this, false);
   116         this.inputField.removeEventListener("mouseout", this, false);
   117         this.inputField.removeEventListener("overflow", this, false);
   118         this.inputField.removeEventListener("underflow", this, false);
   119       ]]></destructor>
   121       <field name="_value"></field>
   123       <!--
   124         onBeforeValueGet is called by the base-binding's .value getter.
   125         It can return an object with a "value" property, to override the
   126         return value of the getter.
   127       -->
   128       <method name="onBeforeValueGet">
   129         <body><![CDATA[
   130           if (this.hasAttribute("actiontype"))
   131             return {value: this._value};
   132           return null;
   133         ]]></body>
   134       </method>
   136       <!--
   137         onBeforeValueSet is called by the base-binding's .value setter.
   138         It should return the value that the setter should use.
   139       -->
   140       <method name="onBeforeValueSet">
   141         <parameter name="aValue"/>
   142         <body><![CDATA[
   143           this._value = aValue;
   144           var returnValue = aValue;
   145           var action = this._parseActionUrl(aValue);
   146           // Don't put back the action if we are invoked while override actions
   147           // is active.
   148           if (action && this._numNoActionsKeys <= 0) {
   149             returnValue = action.param;
   150             this.setAttribute("actiontype", action.type);
   151           } else {
   152             this.removeAttribute("actiontype");
   153           }
   154           return returnValue;
   155         ]]></body>
   156       </method>
   158       <field name="_mayTrimURLs">true</field>
   159       <method name="trimValue">
   160         <parameter name="aURL"/>
   161         <body><![CDATA[
   162           // This method must not modify the given URL such that calling
   163           // nsIURIFixup::createFixupURI with the result will produce a different URI.
   164           return this._mayTrimURLs ? trimURL(aURL) : aURL;
   165         ]]></body>
   166       </method>
   168       <field name="_formattingEnabled">true</field>
   169       <method name="formatValue">
   170         <body><![CDATA[
   171           if (!this._formattingEnabled || this.focused)
   172             return;
   174           let controller = this.editor.selectionController;
   175           let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
   176           selection.removeAllRanges();
   178           let textNode = this.editor.rootElement.firstChild;
   179           let value = textNode.textContent;
   181           let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
   182           if (protocol &&
   183               ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
   184             return;
   185           let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
   186           if (!matchedURL)
   187             return;
   189           let [, preDomain, domain] = matchedURL;
   190           let baseDomain = domain;
   191           let subDomain = "";
   192           // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
   193           if (domain[0] != "[") {
   194             try {
   195               baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
   196               if (!domain.endsWith(baseDomain)) {
   197                 // getBaseDomainFromHost converts its resultant to ACE.
   198                 let IDNService = Cc["@mozilla.org/network/idn-service;1"]
   199                                  .getService(Ci.nsIIDNService);
   200                 baseDomain = IDNService.convertACEtoUTF8(baseDomain);
   201               }
   202             } catch (e) {}
   203           }
   204           if (baseDomain != domain) {
   205             subDomain = domain.slice(0, -baseDomain.length);
   206           }
   208           let rangeLength = preDomain.length + subDomain.length;
   209           if (rangeLength) {
   210             let range = document.createRange();
   211             range.setStart(textNode, 0);
   212             range.setEnd(textNode, rangeLength);
   213             selection.addRange(range);
   214           }
   216           let startRest = preDomain.length + domain.length;
   217           if (startRest < value.length) {
   218             let range = document.createRange();
   219             range.setStart(textNode, startRest);
   220             range.setEnd(textNode, value.length);
   221             selection.addRange(range);
   222           }
   223         ]]></body>
   224       </method>
   226       <method name="_clearFormatting">
   227         <body><![CDATA[
   228           if (!this._formattingEnabled)
   229             return;
   231           let controller = this.editor.selectionController;
   232           let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
   233           selection.removeAllRanges();
   234         ]]></body>
   235       </method>
   237       <method name="handleRevert">
   238         <body><![CDATA[
   239           var isScrolling = this.popupOpen;
   241           gBrowser.userTypedValue = null;
   243           // don't revert to last valid url unless page is NOT loading
   244           // and user is NOT key-scrolling through autocomplete list
   245           if (!XULBrowserWindow.isBusy && !isScrolling) {
   246             URLBarSetURI();
   248             // If the value isn't empty and the urlbar has focus, select the value.
   249             if (this.value && this.hasAttribute("focused"))
   250               this.select();
   251           }
   253           // tell widget to revert to last typed text only if the user
   254           // was scrolling when they hit escape
   255           return !isScrolling;
   256         ]]></body>
   257       </method>
   259       <method name="handleCommand">
   260         <parameter name="aTriggeringEvent"/>
   261         <body><![CDATA[
   262           if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
   263             return; // Do nothing for right clicks
   265           var url = this.value;
   266           var mayInheritPrincipal = false;
   267           var postData = null;
   269           var action = this._parseActionUrl(url);
   270           let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
   272           let matchLastLocationChange = true;
   273           if (action) {
   274             url = action.param;
   275             if (this.hasAttribute("actiontype")) {
   276               if (action.type == "switchtab") {
   277                 this.handleRevert();
   278                 let prevTab = gBrowser.selectedTab;
   279                 if (switchToTabHavingURI(url) &&
   280                     isTabEmpty(prevTab))
   281                   gBrowser.removeTab(prevTab);
   282               }
   283               return;
   284             }
   285             continueOperation.call(this);
   286           }
   287           else {
   288             this._canonizeURL(aTriggeringEvent, response => {
   289               [url, postData, mayInheritPrincipal] = response;
   290               if (url) {
   291                 matchLastLocationChange = (lastLocationChange ==
   292                                            gBrowser.selectedBrowser.lastLocationChange);
   293                 continueOperation.call(this);
   294               }
   295             });
   296           }
   298           function continueOperation()
   299           {
   300             this.value = url;
   301             gBrowser.userTypedValue = url;
   302             try {
   303               addToUrlbarHistory(url);
   304             } catch (ex) {
   305               // Things may go wrong when adding url to session history,
   306               // but don't let that interfere with the loading of the url.
   307               Cu.reportError(ex);
   308             }
   310             function loadCurrent() {
   311               let webnav = Ci.nsIWebNavigation;
   312               let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
   313                           webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
   314               // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
   315               // inheriting the currently loaded document's principal, unless this
   316               // URL is marked as safe to inherit (e.g. came from a bookmark
   317               // keyword).
   318               if (!mayInheritPrincipal)
   319                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
   320               gBrowser.loadURIWithFlags(url, flags, null, null, postData);
   321             }
   323             // Focus the content area before triggering loads, since if the load
   324             // occurs in a new tab, we want focus to be restored to the content
   325             // area when the current tab is re-selected.
   326             gBrowser.selectedBrowser.focus();
   328             let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
   329             let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
   331             if (altEnter) {
   332               // XXX This was added a long time ago, and I'm not sure why it is
   333               // necessary. Alt+Enter's default action might cause a system beep,
   334               // or something like that?
   335               aTriggeringEvent.preventDefault();
   336               aTriggeringEvent.stopPropagation();
   337             }
   339             // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
   340             altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
   342             if (isMouseEvent || altEnter) {
   343               // Use the standard UI link behaviors for clicks or Alt+Enter
   344               let where = "tab";
   345               if (isMouseEvent)
   346                 where = whereToOpenLink(aTriggeringEvent, false, false);
   348               if (where == "current") {
   349                 if (matchLastLocationChange) {
   350                   loadCurrent();
   351                 }
   352               } else {
   353                 this.handleRevert();
   354                 let params = { allowThirdPartyFixup: true,
   355                                postData: postData,
   356                                initiatingDoc: document };
   357                 openUILinkIn(url, where, params);
   358               }
   359             } else {
   360               if (matchLastLocationChange) {
   361                 loadCurrent();
   362               }
   363             }
   364           }
   365         ]]></body>
   366       </method>
   368       <method name="_canonizeURL">
   369         <parameter name="aTriggeringEvent"/>
   370         <parameter name="aCallback"/>
   371         <body><![CDATA[
   372           var url = this.value;
   373           if (!url) {
   374             aCallback(["", null, false]);
   375             return;
   376           }
   378           // Only add the suffix when the URL bar value isn't already "URL-like",
   379           // and only if we get a keyboard event, to match user expectations.
   380           if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
   381               (aTriggeringEvent instanceof KeyEvent)) {
   382 #ifdef XP_MACOSX
   383             let accel = aTriggeringEvent.metaKey;
   384 #else
   385             let accel = aTriggeringEvent.ctrlKey;
   386 #endif
   387             let shift = aTriggeringEvent.shiftKey;
   389             let suffix = "";
   391             switch (true) {
   392               case (accel && shift):
   393                 suffix = ".org/";
   394                 break;
   395               case (shift):
   396                 suffix = ".net/";
   397                 break;
   398               case (accel):
   399                 try {
   400                   suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
   401                   if (suffix.charAt(suffix.length - 1) != "/")
   402                     suffix += "/";
   403                 } catch(e) {
   404                   suffix = ".com/";
   405                 }
   406                 break;
   407             }
   409             if (suffix) {
   410               // trim leading/trailing spaces (bug 233205)
   411               url = url.trim();
   413               // Tack www. and suffix on.  If user has appended directories, insert
   414               // suffix before them (bug 279035).  Be careful not to get two slashes.
   416               let firstSlash = url.indexOf("/");
   418               if (firstSlash >= 0) {
   419                 url = url.substring(0, firstSlash) + suffix +
   420                       url.substring(firstSlash + 1);
   421               } else {
   422                 url = url + suffix;
   423               }
   425               url = "http://www." + url;
   426             }
   427           }
   429           getShortcutOrURIAndPostData(url, data => {
   430             aCallback([data.url, data.postData, data.mayInheritPrincipal]);
   431           });
   432         ]]></body>
   433       </method>
   435       <field name="_contentIsCropped">false</field>
   437       <method name="_initURLTooltip">
   438         <body><![CDATA[
   439           if (this.focused || !this._contentIsCropped)
   440             return;
   441           this.inputField.setAttribute("tooltiptext", this.value);
   442         ]]></body>
   443       </method>
   445       <method name="_hideURLTooltip">
   446         <body><![CDATA[
   447           this.inputField.removeAttribute("tooltiptext");
   448         ]]></body>
   449       </method>
   451       <method name="onDragOver">
   452         <parameter name="aEvent"/>
   453         <body>
   454           var types = aEvent.dataTransfer.types;
   455           if (types.contains("application/x-moz-file") ||
   456               types.contains("text/x-moz-url") ||
   457               types.contains("text/uri-list") ||
   458               types.contains("text/unicode"))
   459             aEvent.preventDefault();
   460         </body>
   461       </method>
   463       <method name="onDrop">
   464         <parameter name="aEvent"/>
   465         <body><![CDATA[
   466           let url = browserDragAndDrop.drop(aEvent, { })
   468           // The URL bar automatically handles inputs with newline characters,
   469           // so we can get away with treating text/x-moz-url flavours as text/plain.
   470           if (url) {
   471             aEvent.preventDefault();
   472             this.value = url;
   473             SetPageProxyState("invalid");
   474             this.focus();
   475             try {
   476               urlSecurityCheck(url,
   477                                gBrowser.contentPrincipal,
   478                                Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
   479             } catch (ex) {
   480               return;
   481             }
   482             this.handleCommand();
   483           }
   484         ]]></body>
   485       </method>
   487       <method name="_getSelectedValueForClipboard">
   488         <body><![CDATA[
   489           // Grab the actual input field's value, not our value, which could include moz-action:
   490           var inputVal = this.inputField.value;
   491           var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
   493           // If the selection doesn't start at the beginning or doesn't span the full domain or
   494           // the URL bar is modified, nothing else to do here.
   495           if (this.selectionStart > 0 || this.valueIsTyped)
   496             return selectedVal;
   497           // The selection doesn't span the full domain if it doesn't contain a slash and is
   498           // followed by some character other than a slash.
   499           if (!selectedVal.contains("/")) {
   500             let remainder = inputVal.replace(selectedVal, "");
   501             if (remainder != "" && remainder[0] != "/")
   502               return selectedVal;
   503           }
   505           let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
   507           let uri;
   508           try {
   509             uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
   510           } catch (e) {}
   511           if (!uri)
   512             return selectedVal;
   514           // Only copy exposable URIs
   515           try {
   516             uri = uriFixup.createExposableURI(uri);
   517           } catch (ex) {}
   519           // If the entire URL is selected, just use the actual loaded URI.
   520           if (inputVal == selectedVal) {
   521             // ... but only if  isn't a javascript: or data: URI, since those
   522             // are hard to read when encoded
   523             if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
   524               // Parentheses are known to confuse third-party applications (bug 458565).
   525               selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
   526             }
   528             return selectedVal;
   529           }
   531           // Just the beginning of the URL is selected, check for a trimmed
   532           // value
   533           let spec = uri.spec;
   534           let trimmedSpec = this.trimValue(spec);
   535           if (spec != trimmedSpec) {
   536             // Prepend the portion that trimValue removed from the beginning.
   537             // This assumes trimValue will only truncate the URL at
   538             // the beginning or end (or both).
   539             let trimmedSegments = spec.split(trimmedSpec);
   540             selectedVal = trimmedSegments[0] + selectedVal;
   541           }
   543           return selectedVal;
   544         ]]></body>
   545       </method>
   547       <field name="_copyCutController"><![CDATA[
   548         ({
   549           urlbar: this,
   550           doCommand: function(aCommand) {
   551             var urlbar = this.urlbar;
   552             var val = urlbar._getSelectedValueForClipboard();
   553             if (!val)
   554               return;
   556             if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
   557               let start = urlbar.selectionStart;
   558               let end = urlbar.selectionEnd;
   559               urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
   560                                         urlbar.inputField.value.substring(end);
   561               urlbar.selectionStart = urlbar.selectionEnd = start;
   562               urlbar.removeAttribute("actiontype");
   563               SetPageProxyState("invalid");
   564             }
   566             Cc["@mozilla.org/widget/clipboardhelper;1"]
   567               .getService(Ci.nsIClipboardHelper)
   568               .copyString(val, document);
   569           },
   570           supportsCommand: function(aCommand) {
   571             switch (aCommand) {
   572               case "cmd_copy":
   573               case "cmd_cut":
   574                 return true;
   575             }
   576             return false;
   577           },
   578           isCommandEnabled: function(aCommand) {
   579             return this.supportsCommand(aCommand) &&
   580                    (aCommand != "cmd_cut" || !this.urlbar.readOnly) &&
   581                    this.urlbar.selectionStart < this.urlbar.selectionEnd;
   582           },
   583           onEvent: function(aEventName) {}
   584         })
   585       ]]></field>
   587       <method name="observe">
   588         <parameter name="aSubject"/>
   589         <parameter name="aTopic"/>
   590         <parameter name="aData"/>
   591         <body><![CDATA[
   592           if (aTopic == "nsPref:changed") {
   593             switch (aData) {
   594               case "clickSelectsAll":
   595               case "doubleClickSelectsAll":
   596                 this[aData] = this._prefs.getBoolPref(aData);
   597                 break;
   598               case "autoFill":
   599                 this.completeDefaultIndex = this._prefs.getBoolPref(aData);
   600                 break;
   601               case "delay":
   602                 this.timeout = this._prefs.getIntPref(aData);
   603                 break;
   604               case "formatting.enabled":
   605                 this._formattingEnabled = this._prefs.getBoolPref(aData);
   606                 break;
   607               case "trimURLs":
   608                 this._mayTrimURLs = this._prefs.getBoolPref(aData);
   609                 break;
   610               case "unifiedcomplete":
   611                 let useUnifiedComplete = false;
   612                 try {
   613                   useUnifiedComplete = this._prefs.getBoolPref(aData);
   614                 } catch (ex) {}
   615                 this.setAttribute("autocompletesearch",
   616                                   useUnifiedComplete ? "unifiedcomplete"
   617                                                      : "urlinline history");
   618             }
   619           }
   620         ]]></body>
   621       </method>
   623       <method name="handleEvent">
   624         <parameter name="aEvent"/>
   625         <body><![CDATA[
   626           switch (aEvent.type) {
   627             case "mousedown":
   628               if (this.doubleClickSelectsAll &&
   629                   aEvent.button == 0 && aEvent.detail == 2) {
   630                 this.editor.selectAll();
   631                 aEvent.preventDefault();
   632               }
   633               break;
   634             case "mousemove":
   635               this._initURLTooltip();
   636               break;
   637             case "mouseout":
   638               this._hideURLTooltip();
   639               break;
   640             case "overflow":
   641               this._contentIsCropped = true;
   642               break;
   643             case "underflow":
   644               this._contentIsCropped = false;
   645               this._hideURLTooltip();
   646               break;
   647           }
   648         ]]></body>
   649       </method>
   651       <property name="textValue"
   652                 onget="return this.value;">
   653         <setter>
   654           <![CDATA[
   655           try {
   656             val = losslessDecodeURI(makeURI(val));
   657           } catch (ex) { }
   659           // Trim popup selected values, but never trim results coming from
   660           // autofill.
   661           if (this.popup.selectedIndex == -1)
   662             this._disableTrim = true;
   663           this.value = val;
   664           this._disableTrim = false;
   666           // Completing a result should simulate the user typing the result, so
   667           // fire an input event.
   668           let evt = document.createEvent("UIEvents");
   669           evt.initUIEvent("input", true, false, window, 0);
   670           this.mIgnoreInput = true;
   671           this.dispatchEvent(evt);
   672           this.mIgnoreInput = false;
   674           return this.value;
   675           ]]>
   676         </setter>
   677       </property>
   679       <method name="_parseActionUrl">
   680         <parameter name="aUrl"/>
   681         <body><![CDATA[
   682           if (!aUrl.startsWith("moz-action:"))
   683             return null;
   685           // url is in the format moz-action:ACTION,PARAM
   686           let [, action, param] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
   687           return {type: action, param: param};
   688         ]]></body>
   689       </method>
   691       <field name="_numNoActionsKeys"><![CDATA[
   692         0
   693       ]]></field>
   695       <method name="_clearNoActions">
   696         <parameter name="aURL"/>
   697         <body><![CDATA[
   698           this._numNoActionsKeys = 0;
   699           this.popup.removeAttribute("noactions");
   700           let action = this._parseActionUrl(this._value);
   701           if (action)
   702             this.setAttribute("actiontype", action.type);
   703         ]]></body>
   704       </method>
   706       <method name="selectTextRange">
   707         <parameter name="aStartIndex"/>
   708         <parameter name="aEndIndex"/>
   709         <body><![CDATA[
   710           this._ignoreNextSelect = true;
   711           this.inputField.setSelectionRange(aStartIndex, aEndIndex);
   712         ]]></body>
   713       </method>
   714     </implementation>
   716     <handlers>
   717       <handler event="keydown"><![CDATA[
   718         if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
   719              event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
   720             this.popup.selectedIndex >= 0) {
   721           this._numNoActionsKeys++;
   722           this.popup.setAttribute("noactions", "true");
   723           this.removeAttribute("actiontype");
   724         }
   725       ]]></handler>
   727       <handler event="keyup"><![CDATA[
   728         if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
   729              event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
   730             this._numNoActionsKeys > 0) {
   731           this._numNoActionsKeys--;
   732           if (this._numNoActionsKeys == 0)
   733             this._clearNoActions();
   734         }
   735       ]]></handler>
   737       <handler event="blur"><![CDATA[
   738         this._clearNoActions();
   739         this.formatValue();
   740       ]]></handler>
   742       <handler event="dragstart" phase="capturing"><![CDATA[
   743         // Drag only if the gesture starts from the input field.
   744         if (event.originalTarget != this.inputField)
   745           return;
   747         // Drag only if the entire value is selected and it's a valid URI.
   748         var isFullSelection = this.selectionStart == 0 &&
   749                               this.selectionEnd == this.textLength;
   750         if (!isFullSelection ||
   751             this.getAttribute("pageproxystate") != "valid")
   752           return;
   754         var urlString = content.location.href;
   755         var title = content.document.title || urlString;
   756         var htmlString = "<a href=\"" + urlString + "\">" + urlString + "</a>";
   758         var dt = event.dataTransfer;
   759         dt.setData("text/x-moz-url", urlString + "\n" + title);
   760         dt.setData("text/unicode", urlString);
   761         dt.setData("text/html", htmlString);
   763         dt.effectAllowed = "copyLink";
   764         event.stopPropagation();
   765       ]]></handler>
   767       <handler event="focus" phase="capturing"><![CDATA[
   768         this._hideURLTooltip();
   769         this._clearFormatting();
   770       ]]></handler>
   772       <handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
   773       <handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
   774       <handler event="select"><![CDATA[
   775         if (this._ignoreNextSelect) {
   776           // If this select event is coming from autocomplete's selectTextRange,
   777           // then we don't need to adjust what's on the selection keyboard here,
   778           // but make sure to reset the flag since this should be a one-time
   779           // suppression.
   780           this._ignoreNextSelect = false;
   781           return;
   782         }
   784         if (!Cc["@mozilla.org/widget/clipboard;1"]
   785                .getService(Ci.nsIClipboard)
   786                .supportsSelectionClipboard())
   787           return;
   789         var val = this._getSelectedValueForClipboard();
   790         if (!val)
   791           return;
   793         Cc["@mozilla.org/widget/clipboardhelper;1"]
   794           .getService(Ci.nsIClipboardHelper)
   795           .copyStringToClipboard(val, Ci.nsIClipboard.kSelectionClipboard, document);
   796       ]]></handler>
   797     </handlers>
   799   </binding>
   801   <!-- Note: this binding is applied to the autocomplete popup used in the Search bar and in web page content -->
   802   <binding id="browser-autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup">
   803     <implementation>
   804       <method name="openAutocompletePopup">
   805         <parameter name="aInput"/>
   806         <parameter name="aElement"/>
   807         <body>
   808           <![CDATA[
   809           // initially the panel is hidden
   810           // to avoid impacting startup / new window performance
   811           aInput.popup.hidden = false;
   813           // this method is defined on the base binding
   814           this._openAutocompletePopup(aInput, aElement);
   815         ]]></body>
   816       </method>
   818       <method name="onPopupClick">
   819         <parameter name="aEvent"/>
   820         <body><![CDATA[
   821           // Ignore all right-clicks
   822           if (aEvent.button == 2)
   823             return;
   825           var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
   827           // Check for unmodified left-click, and use default behavior
   828           if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
   829               !aEvent.altKey && !aEvent.metaKey) {
   830             controller.handleEnter(true);
   831             return;
   832           }
   834           // Check for middle-click or modified clicks on the search bar
   835           var searchBar = BrowserSearch.searchBar;
   836           if (searchBar && searchBar.textbox == this.mInput) {
   837             // Handle search bar popup clicks
   838             var search = controller.getValueAt(this.selectedIndex);
   840             // close the autocomplete popup and revert the entered search term
   841             this.closePopup();
   842             controller.handleEscape();
   844             // Fill in the search bar's value
   845             searchBar.value = search;
   847             // open the search results according to the clicking subtlety
   848             var where = whereToOpenLink(aEvent, false, true);
   849             searchBar.doSearch(search, where);
   850           }
   851           ]]></body>
   852         </method>
   853       </implementation>
   854     </binding>
   856     <binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
   857       <implementation>
   858       <field name="_maxResults">0</field>
   860       <field name="_bundle" readonly="true">
   861         Cc["@mozilla.org/intl/stringbundle;1"].
   862           getService(Ci.nsIStringBundleService).
   863           createBundle("chrome://browser/locale/places/places.properties");
   864       </field>
   866       <property name="maxResults" readonly="true">
   867         <getter>
   868           <![CDATA[
   869             if (!this._maxResults) {
   870               var prefService =
   871                 Components.classes["@mozilla.org/preferences-service;1"]
   872                           .getService(Components.interfaces.nsIPrefBranch);
   873               this._maxResults = prefService.getIntPref("browser.urlbar.maxRichResults");
   874             }
   875             return this._maxResults;
   876           ]]>
   877         </getter>
   878       </property>
   880       <method name="openAutocompletePopup">
   881         <parameter name="aInput"/>
   882         <parameter name="aElement"/>
   883         <body>
   884           <![CDATA[
   885           // initially the panel is hidden
   886           // to avoid impacting startup / new window performance
   887           aInput.popup.hidden = false;
   889           // this method is defined on the base binding
   890           this._openAutocompletePopup(aInput, aElement);
   891         ]]></body>
   892       </method>
   894       <method name="onPopupClick">
   895         <parameter name="aEvent"/>
   896         <body>
   897           <![CDATA[
   898           // Ignore right-clicks
   899           if (aEvent.button == 2)
   900             return;
   902           var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
   904           // Check for unmodified left-click, and use default behavior
   905           if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
   906               !aEvent.altKey && !aEvent.metaKey) {
   907             controller.handleEnter(true);
   908             return;
   909           }
   911           // Check for middle-click or modified clicks on the URL bar
   912           if (gURLBar && this.mInput == gURLBar) {
   913             var url = controller.getValueAt(this.selectedIndex);
   915             // close the autocomplete popup and revert the entered address
   916             this.closePopup();
   917             controller.handleEscape();
   919             // Check if this is meant to be an action
   920             let action = this.mInput._parseActionUrl(url);
   921             if (action) {
   922               if (action.type == "switchtab")
   923                 url = action.param;
   924               else
   925                 return;
   926             }
   928             // respect the usual clicking subtleties
   929             openUILink(url, aEvent);
   930           }
   931         ]]>
   932         </body>
   933       </method>
   935       <method name="createResultLabel">
   936         <parameter name="aTitle"/>
   937         <parameter name="aUrl"/>
   938         <parameter name="aType"/>
   939         <body>
   940           <![CDATA[
   941             var label = aTitle + " " + aUrl;
   942             // convert aType (ex: "ac-result-type-<aType>") to text to be spoke aloud
   943             // by screen readers.  convert "tag" and "bookmark" to the localized versions,
   944             // but don't do anything for "favicon" (the default)
   945             if (aType != "favicon") {
   946               label += " " + this._bundle.GetStringFromName(aType + "ResultLabel");
   947             }
   948             return label;
   949           ]]>
   950         </body>
   951       </method>
   953     </implementation>
   954   </binding>
   956   <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
   957     <content align="start">
   958       <xul:image class="popup-notification-icon"
   959                  xbl:inherits="popupid,src=icon"/>
   960       <xul:vbox flex="1">
   961         <xul:description class="popup-notification-description addon-progress-description"
   962                          xbl:inherits="xbl:text=label"/>
   963         <xul:spacer flex="1"/>
   964         <xul:hbox align="center">
   965           <xul:progressmeter anonid="progressmeter" flex="1" mode="undetermined" class="popup-progress-meter"/>
   966           <xul:button anonid="cancel" class="popup-progress-cancel" oncommand="document.getBindingParent(this).cancel()"/>
   967         </xul:hbox>
   968         <xul:label anonid="progresstext" class="popup-progress-label"/>
   969         <xul:hbox class="popup-notification-button-container"
   970                   pack="end" align="center">
   971           <xul:button anonid="button"
   972                       class="popup-notification-menubutton"
   973                       type="menu-button"
   974                       xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
   975             <xul:menupopup anonid="menupopup"
   976                            xbl:inherits="oncommand=menucommand">
   977               <children/>
   978               <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
   979                             label="&closeNotificationItem.label;"
   980                             xbl:inherits="oncommand=closeitemcommand"/>
   981             </xul:menupopup>
   982           </xul:button>
   983         </xul:hbox>
   984       </xul:vbox>
   985       <xul:vbox pack="start">
   986         <xul:toolbarbutton anonid="closebutton"
   987                            class="messageCloseButton close-icon popup-notification-closebutton tabbable"
   988                            xbl:inherits="oncommand=closebuttoncommand"
   989                            tooltiptext="&closeNotification.tooltip;"/>
   990       </xul:vbox>
   991     </content>
   992     <implementation>
   993       <constructor><![CDATA[
   994         this.cancelbtn.setAttribute("tooltiptext", gNavigatorBundle.getString("addonDownloadCancelTooltip"));
   996         this.notification.options.installs.forEach(function(aInstall) {
   997           aInstall.addListener(this);
   998         }, this);
  1000         // Calling updateProgress can sometimes cause this notification to be
  1001         // removed in the middle of refreshing the notification panel which
  1002         // makes the panel get refreshed again. Just initialise to the
  1003         // undetermined state and then schedule a proper check at the next
  1004         // opportunity
  1005         this.setProgress(0, -1);
  1006         this._updateProgressTimeout = setTimeout(this.updateProgress.bind(this), 0);
  1007       ]]></constructor>
  1009       <destructor><![CDATA[
  1010         this.destroy();
  1011       ]]></destructor>
  1013       <field name="progressmeter" readonly="true">
  1014         document.getAnonymousElementByAttribute(this, "anonid", "progressmeter");
  1015       </field>
  1016       <field name="progresstext" readonly="true">
  1017         document.getAnonymousElementByAttribute(this, "anonid", "progresstext");
  1018       </field>
  1019       <field name="cancelbtn" readonly="true">
  1020         document.getAnonymousElementByAttribute(this, "anonid", "cancel");
  1021       </field>
  1022       <field name="DownloadUtils" readonly="true">
  1023         let utils = {};
  1024         Components.utils.import("resource://gre/modules/DownloadUtils.jsm", utils);
  1025         utils.DownloadUtils;
  1026       </field>
  1028       <method name="destroy">
  1029         <body><![CDATA[
  1030           this.notification.options.installs.forEach(function(aInstall) {
  1031             aInstall.removeListener(this);
  1032           }, this);
  1033           clearTimeout(this._updateProgressTimeout);
  1034         ]]></body>
  1035       </method>
  1037       <method name="setProgress">
  1038         <parameter name="aProgress"/>
  1039         <parameter name="aMaxProgress"/>
  1040         <body><![CDATA[
  1041           if (aMaxProgress == -1) {
  1042             this.progressmeter.mode = "undetermined";
  1044           else {
  1045             this.progressmeter.mode = "determined";
  1046             this.progressmeter.value = (aProgress * 100) / aMaxProgress;
  1049           let now = Date.now();
  1051           if (!this.notification.lastUpdate) {
  1052             this.notification.lastUpdate = now;
  1053             this.notification.lastProgress = aProgress;
  1054             return;
  1057           let delta = now - this.notification.lastUpdate;
  1058           if ((delta < 400) && (aProgress < aMaxProgress))
  1059             return;
  1061           delta /= 1000;
  1063           // This code is taken from nsDownloadManager.cpp
  1064           let speed = (aProgress - this.notification.lastProgress) / delta;
  1065           if (this.notification.speed)
  1066             speed = speed * 0.9 + this.notification.speed * 0.1;
  1068           this.notification.lastUpdate = now;
  1069           this.notification.lastProgress = aProgress;
  1070           this.notification.speed = speed;
  1072           let status = null;
  1073           [status, this.notification.last] = this.DownloadUtils.getDownloadStatus(aProgress, aMaxProgress, speed, this.notification.last);
  1074           this.progresstext.value = status;
  1075         ]]></body>
  1076       </method>
  1078       <method name="cancel">
  1079         <body><![CDATA[
  1080           // Cache these as cancelling the installs will remove this
  1081           // notification which will drop these references
  1082           let browser = this.notification.browser;
  1083           let contentWindow = this.notification.options.contentWindow;
  1084           let sourceURI = this.notification.options.sourceURI;
  1086           let installs = this.notification.options.installs;
  1087           installs.forEach(function(aInstall) {
  1088             try {
  1089               aInstall.cancel();
  1091             catch (e) {
  1092               // Cancel will throw if the download has already failed
  1094           }, this);
  1096           let anchorID = "addons-notification-icon";
  1097           let notificationID = "addon-install-cancelled";
  1098           let messageString = gNavigatorBundle.getString("addonDownloadCancelled");
  1099           messageString = PluralForm.get(installs.length, messageString);
  1100           let buttonText = gNavigatorBundle.getString("addonDownloadRestart");
  1101           buttonText = PluralForm.get(installs.length, buttonText);
  1103           let action = {
  1104             label: buttonText,
  1105             accessKey: gNavigatorBundle.getString("addonDownloadRestart.accessKey"),
  1106             callback: function() {
  1107               let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
  1108                                 getService(Ci.amIWebInstallListener);
  1109               if (weblistener.onWebInstallRequested(contentWindow, sourceURI,
  1110                                                     installs, installs.length)) {
  1111                 installs.forEach(function(aInstall) {
  1112                   aInstall.install();
  1113                 });
  1116           };
  1118           PopupNotifications.show(browser, notificationID, messageString,
  1119                                   anchorID, action);
  1120         ]]></body>
  1121       </method>
  1123       <method name="updateProgress">
  1124         <body><![CDATA[
  1125           let downloadingCount = 0;
  1126           let progress = 0;
  1127           let maxProgress = 0;
  1129           this.notification.options.installs.forEach(function(aInstall) {
  1130             if (aInstall.maxProgress == -1)
  1131               maxProgress = -1;
  1132             progress += aInstall.progress;
  1133             if (maxProgress >= 0)
  1134               maxProgress += aInstall.maxProgress;
  1135             if (aInstall.state < AddonManager.STATE_DOWNLOADED)
  1136               downloadingCount++;
  1137           });
  1139           if (downloadingCount == 0) {
  1140             this.destroy();
  1141             PopupNotifications.remove(this.notification);
  1143           else {
  1144             this.setProgress(progress, maxProgress);
  1146         ]]></body>
  1147       </method>
  1149       <method name="onDownloadProgress">
  1150         <body><![CDATA[
  1151           this.updateProgress();
  1152         ]]></body>
  1153       </method>
  1155       <method name="onDownloadFailed">
  1156         <body><![CDATA[
  1157           this.updateProgress();
  1158         ]]></body>
  1159       </method>
  1161       <method name="onDownloadCancelled">
  1162         <body><![CDATA[
  1163           this.updateProgress();
  1164         ]]></body>
  1165       </method>
  1167       <method name="onDownloadEnded">
  1168         <body><![CDATA[
  1169           this.updateProgress();
  1170         ]]></body>
  1171       </method>
  1172     </implementation>
  1173   </binding>
  1175   <binding id="identity-request-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
  1176     <content align="start">
  1178       <xul:image class="popup-notification-icon"
  1179                  xbl:inherits="popupid,src=icon"/>
  1181       <xul:vbox flex="1">
  1182         <xul:vbox anonid="identity-deck">
  1183           <xul:vbox flex="1" pack="center"> <!-- 1: add an email -->
  1184             <html:input type="email" anonid="email" required="required" size="30"/>
  1185             <xul:description anonid="newidentitydesc"/>
  1186             <xul:spacer flex="1"/>
  1187             <xul:label class="text-link custom-link small-margin" anonid="chooseemail" hidden="true"/>
  1188           </xul:vbox>
  1189           <xul:vbox flex="1" hidden="true"> <!-- 2: choose an email -->
  1190             <xul:description anonid="chooseidentitydesc"/>
  1191             <xul:radiogroup anonid="identities">
  1192             </xul:radiogroup>
  1193             <xul:label class="text-link custom-link" anonid="newemail"/>
  1194           </xul:vbox>
  1195         </xul:vbox>
  1196         <xul:hbox class="popup-notification-button-container"
  1197                   pack="end" align="center">
  1198           <xul:label anonid="tos" class="text-link" hidden="true"/>
  1199           <xul:label anonid="privacypolicy" class="text-link" hidden="true"/>
  1200           <xul:spacer flex="1"/>
  1201           <xul:image anonid="throbber" src="chrome://browser/skin/tabbrowser/loading.png"
  1202                      style="visibility:hidden" width="16" height="16"/>
  1203           <xul:button anonid="button"
  1204                       type="menu-button"
  1205                       class="popup-notification-menubutton"
  1206                       xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
  1207             <xul:menupopup anonid="menupopup"
  1208                            xbl:inherits="oncommand=menucommand">
  1209               <children/>
  1210               <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
  1211                             label="&closeNotificationItem.label;"
  1212                             xbl:inherits="oncommand=closeitemcommand"/>
  1213             </xul:menupopup>
  1214           </xul:button>
  1215         </xul:hbox>
  1216       </xul:vbox>
  1217       <xul:vbox pack="start">
  1218         <xul:toolbarbutton anonid="closebutton"
  1219                            class="messageCloseButton close-icon popup-notification-closebutton tabbable"
  1220                            xbl:inherits="oncommand=closebuttoncommand"
  1221                            tooltiptext="&closeNotification.tooltip;"/>
  1222       </xul:vbox>
  1223     </content>
  1224     <implementation>
  1225       <constructor><![CDATA[
  1226         // this.notification.options.identity is used to pass identity-specific info to the binding
  1227         let origin = this.identity.origin
  1229         // Populate text
  1230         this.emailField.placeholder = gNavigatorBundle.
  1231                                       getString("identity.newIdentity.email.placeholder");
  1232         this.newIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
  1233                                              "identity.newIdentity.description", [origin]);
  1234         this.chooseIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
  1235                                                 "identity.chooseIdentity.description", [origin]);
  1237         // Show optional terms of service and privacy policy links
  1238         this._populateLink(this.identity.termsOfService, "tos", "identity.termsOfService");
  1239         this._populateLink(this.identity.privacyPolicy, "privacypolicy", "identity.privacyPolicy");
  1241         // Populate the list of identities to choose from. The origin is used to provide
  1242         // better suggestions.
  1243         let identities = this.SignInToWebsiteUX.getIdentitiesForSite(origin);
  1245         this._populateIdentityList(identities);
  1247         if (typeof this.step == "undefined") {
  1248           // First opening of this notification
  1249           // Show the add email pane (0) if there are no existing identities otherwise show the list
  1250           this.step = "result" in identities && identities.result.length ? 1 : 0;
  1251         } else {
  1252           // Already opened so restore previous state
  1253           if (this.identity.typedEmail) {
  1254             this.emailField.value = this.identity.typedEmail;
  1256           if (this.identity.selected) {
  1257             // If the user already chose an identity then update the UI to reflect that
  1258             this.onIdentitySelected();
  1260           // Update the view for the step
  1261           this.step = this.step;
  1264         // Fire notification with the chosen identity when main button is clicked
  1265         this.button.addEventListener("command", this._onButtonCommand.bind(this), true);
  1267         // Do the same if enter is pressed in the email field
  1268         this.emailField.addEventListener("keypress", function emailFieldKeypress(aEvent) {
  1269           if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
  1270             return;
  1271           this._onButtonCommand(aEvent);
  1272         }.bind(this));
  1274         this.addEmailLink.value = gNavigatorBundle.getString("identity.newIdentity.label");
  1275         this.addEmailLink.accessKey = gNavigatorBundle.getString("identity.newIdentity.accessKey");
  1276         this.addEmailLink.addEventListener("click", function addEmailClick(evt) {
  1277           this.step = 0;
  1278         }.bind(this));
  1280         this.chooseEmailLink.value = gNavigatorBundle.getString("identity.chooseIdentity.label");
  1281         this.chooseEmailLink.hidden = !("result" in identities && identities.result.length);
  1282         this.chooseEmailLink.addEventListener("click", function chooseEmailClick(evt) {
  1283           this.step = 1;
  1284         }.bind(this));
  1286         this.emailField.addEventListener("blur", function onEmailBlur() {
  1287           this.identity.typedEmail = this.emailField.value;
  1288         }.bind(this));
  1289       ]]></constructor>
  1291       <field name="SignInToWebsiteUX" readonly="true">
  1292         let sitw = {};
  1293         Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
  1294         sitw.SignInToWebsiteUX;
  1295       </field>
  1297       <field name="newIdentityDesc" readonly="true">
  1298         document.getAnonymousElementByAttribute(this, "anonid", "newidentitydesc");
  1299       </field>
  1301       <field name="chooseIdentityDesc" readonly="true">
  1302         document.getAnonymousElementByAttribute(this, "anonid", "chooseidentitydesc");
  1303       </field>
  1305       <field name="identityList" readonly="true">
  1306         document.getAnonymousElementByAttribute(this, "anonid", "identities");
  1307       </field>
  1309       <field name="emailField" readonly="true">
  1310         document.getAnonymousElementByAttribute(this, "anonid", "email");
  1311       </field>
  1313       <field name="addEmailLink" readonly="true">
  1314         document.getAnonymousElementByAttribute(this, "anonid", "newemail");
  1315       </field>
  1317       <field name="chooseEmailLink" readonly="true">
  1318         document.getAnonymousElementByAttribute(this, "anonid", "chooseemail");
  1319       </field>
  1321       <field name="throbber" readonly="true">
  1322         document.getAnonymousElementByAttribute(this, "anonid", "throbber");
  1323       </field>
  1325       <field name="identity" readonly="true">
  1326         this.notification.options.identity;
  1327       </field>
  1329       <!-- persist the state on the identity object so we can re-create the
  1330            notification state upon re-opening -->
  1331       <property name="step">
  1332         <getter>
  1333           return this.identity.step;
  1334         </getter>
  1335         <setter><![CDATA[
  1336           let deck = document.getAnonymousElementByAttribute(this, "anonid", "identity-deck");
  1337           for (let i = 0; i < deck.children.length; i++) {
  1338             deck.children[i].hidden = (val != i);
  1340           this.identity.step = val;
  1341           switch (val) {
  1342             case 0:
  1343               this.emailField.focus();
  1344               break;
  1345           }]]>
  1346         </setter>
  1347       </property>
  1349       <method name="onIdentitySelected">
  1350         <body><![CDATA[
  1351           this.throbber.style.visibility = "visible";
  1352           this.button.disabled = true;
  1353           this.emailField.value = this.identity.selected
  1354           this.emailField.disabled = true;
  1355           this.identityList.disabled = true;
  1356         ]]></body>
  1357       </method>
  1359       <method name="_populateLink">
  1360         <parameter name="aURL"/>
  1361         <parameter name="aLinkId"/>
  1362         <parameter name="aStringId"/>
  1363         <body><![CDATA[
  1364           if (aURL) {
  1365             // Show optional link to aURL
  1366             let link = document.getAnonymousElementByAttribute(this, "anonid", aLinkId);
  1367             link.value = gNavigatorBundle.getString(aStringId);
  1368             link.href = aURL;
  1369             link.hidden = false;
  1371         ]]></body>
  1372       </method>
  1374       <method name="_populateIdentityList">
  1375         <parameter name="aIdentities"/>
  1376         <body><![CDATA[
  1377           let foundLastUsed = false;
  1378           let lastUsed = this.identity.selected || aIdentities.lastUsed;
  1379           for (let id in aIdentities.result) {
  1380             let label = aIdentities.result[id];
  1381             let opt = this.identityList.appendItem(label);
  1382             if (label == lastUsed) {
  1383               this.identityList.selectedItem = opt;
  1384               foundLastUsed = true;
  1387           if (!foundLastUsed) {
  1388             this.identityList.selectedIndex = -1;
  1390         ]]></body>
  1391       </method>
  1393       <method name="_onButtonCommand">
  1394         <parameter name="aEvent"/>
  1395         <body><![CDATA[
  1396           if (aEvent.target != aEvent.currentTarget)
  1397             return;
  1398           let chosenId;
  1399           switch (this.step) {
  1400             case 0:
  1401               aEvent.stopPropagation();
  1402               if (!this.emailField.validity.valid) {
  1403                 this.emailField.focus();
  1404                 return;
  1406               chosenId = this.emailField.value;
  1407               break;
  1408             case 1:
  1409               aEvent.stopPropagation();
  1410               let selectedItem = this.identityList.selectedItem
  1411               chosenId = selectedItem ? selectedItem.label : null;
  1412               if (!chosenId)
  1413                 return;
  1414               break;
  1415             default:
  1416               throw new Error("Unknown case");
  1417               return;
  1419           // Actually select the identity
  1420           this.SignInToWebsiteUX.selectIdentity(this.identity.rpId, chosenId);
  1421           this.identity.selected = chosenId;
  1422           this.onIdentitySelected();
  1423         ]]></body>
  1424       </method>
  1426     </implementation>
  1427   </binding>
  1429   <binding id="plugin-popupnotification-center-item">
  1430     <content align="center">
  1431       <xul:vbox pack="center" anonid="itemBox" class="itemBox">
  1432         <xul:description anonid="center-item-label" class="center-item-label" />
  1433         <xul:hbox flex="1" pack="start" align="center" anonid="center-item-warning">
  1434           <xul:image anonid="center-item-warning-icon" class="center-item-warning-icon"/>
  1435           <xul:label anonid="center-item-warning-label"/>
  1436           <xul:label anonid="center-item-link" value="&checkForUpdates;" class="text-link"/>
  1437         </xul:hbox>
  1438       </xul:vbox>
  1439       <xul:vbox pack="center">
  1440         <xul:menulist class="center-item-menulist"
  1441                       anonid="center-item-menulist">
  1442           <xul:menupopup>
  1443             <xul:menuitem anonid="allownow" value="allownow"
  1444                           label="&pluginActivateNow.label;" />
  1445             <xul:menuitem anonid="allowalways" value="allowalways"
  1446                           label="&pluginActivateAlways.label;" />
  1447             <xul:menuitem anonid="block" value="block"
  1448                           label="&pluginBlockNow.label;" />
  1449           </xul:menupopup>
  1450         </xul:menulist>
  1451       </xul:vbox>
  1452     </content>
  1453     <resources>
  1454       <stylesheet src="chrome://global/skin/notification.css"/>
  1455     </resources>
  1456     <implementation>
  1457       <constructor><![CDATA[
  1458         document.getAnonymousElementByAttribute(this, "anonid", "center-item-label").value = this.action.pluginName;
  1460         let curState = "block";
  1461         if (this.action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
  1462           if (this.action.pluginPermissionType == Ci.nsIPermissionManager.EXPIRE_SESSION) {
  1463             curState = "allownow";
  1465           else {
  1466             curState = "allowalways";
  1469         document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").value = curState;
  1471         let warningString = "";
  1472         let linkString = "";
  1474         let link = document.getAnonymousElementByAttribute(this, "anonid", "center-item-link");
  1476         let url;
  1477         let linkHandler;
  1479         if (this.action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
  1480           document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
  1481           warningString = gNavigatorBundle.getString("pluginActivateDisabled.label");
  1482           linkString = gNavigatorBundle.getString("pluginActivateDisabled.manage");
  1483           linkHandler = function(event) {
  1484             event.preventDefault();
  1485             gPluginHandler.managePlugins();
  1486           };
  1487           document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-icon").hidden = true;
  1489         else {
  1490           url = this.action.detailsLink;
  1492           switch (this.action.blocklistState) {
  1493           case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
  1494             document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning").hidden = true;
  1495             break;
  1496           case Ci.nsIBlocklistService.STATE_BLOCKED:
  1497             document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
  1498             warningString = gNavigatorBundle.getString("pluginActivateBlocked.label");
  1499             linkString = gNavigatorBundle.getString("pluginActivate.learnMore");
  1500             break;
  1501           case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
  1502             warningString = gNavigatorBundle.getString("pluginActivateOutdated.label");
  1503             linkString = gNavigatorBundle.getString("pluginActivate.updateLabel");
  1504             break;
  1505           case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
  1506             warningString = gNavigatorBundle.getString("pluginActivateVulnerable.label");
  1507             linkString = gNavigatorBundle.getString("pluginActivate.riskLabel");
  1508             break;
  1511         document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-label").value = warningString;
  1513         if (url || linkHandler) {
  1514           link.value = linkString;
  1515           if (url) {
  1516             link.href = url;
  1518           if (linkHandler) {
  1519             link.addEventListener("click", linkHandler, false);
  1522         else {
  1523           link.hidden = true;
  1525       ]]></constructor>
  1526       <property name="value">
  1527         <getter>
  1528           return document.getAnonymousElementByAttribute(this, "anonid",
  1529                    "center-item-menulist").value;
  1530         </getter>
  1531         <setter><!-- This should be used only in automated tests -->
  1532           document.getAnonymousElementByAttribute(this, "anonid",
  1533                     "center-item-menulist").value = val;
  1534         </setter>
  1535       </property>
  1536     </implementation>
  1537   </binding>
  1539   <binding id="click-to-play-plugins-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
  1540     <content align="start" style="width: &pluginNotification.width;;">
  1541       <xul:vbox flex="1" align="stretch" class="popup-notification-main-box"
  1542                 xbl:inherits="popupid">
  1543         <xul:hbox class="click-to-play-plugins-notification-description-box" flex="1" align="start">
  1544           <xul:description class="click-to-play-plugins-outer-description" flex="1">
  1545             <html:span anonid="click-to-play-plugins-notification-description" />
  1546             <xul:label class="text-link click-to-play-plugins-notification-link" anonid="click-to-play-plugins-notification-link" />
  1547           </xul:description>
  1548           <xul:toolbarbutton anonid="closebutton"
  1549                              class="messageCloseButton popup-notification-closebutton tabbable close-icon"
  1550                              xbl:inherits="oncommand=closebuttoncommand"
  1551                              tooltiptext="&closeNotification.tooltip;"/>
  1552         </xul:hbox>
  1553         <xul:grid anonid="click-to-play-plugins-notification-center-box"
  1554                   class="click-to-play-plugins-notification-center-box">
  1555           <xul:columns>
  1556             <xul:column flex="1"/>
  1557             <xul:column/>
  1558           </xul:columns>
  1559           <xul:rows>
  1560             <children includes="row"/>
  1561             <xul:hbox pack="start" anonid="plugin-notification-showbox">
  1562               <xul:button label="&pluginNotification.showAll.label;"
  1563                           accesskey="&pluginNotification.showAll.accesskey;"
  1564                           class="plugin-notification-showbutton"
  1565                           oncommand="document.getBindingParent(this)._setState(2)"/>
  1566             </xul:hbox>
  1567           </xul:rows>
  1568         </xul:grid>
  1569         <xul:hbox anonid="button-container"
  1570                   class="click-to-play-plugins-notification-button-container"
  1571                   pack="center" align="center">
  1572           <xul:button anonid="primarybutton"
  1573                       class="click-to-play-popup-button"
  1574                       oncommand="document.getBindingParent(this)._onButton(this)"
  1575                       flex="1"/>
  1576           <xul:button anonid="secondarybutton"
  1577                       class="click-to-play-popup-button"
  1578                       oncommand="document.getBindingParent(this)._onButton(this);"
  1579                       flex="1"/>
  1580         </xul:hbox>
  1581         <xul:box hidden="true">
  1582           <children/>
  1583         </xul:box>
  1584       </xul:vbox>
  1585     </content>
  1586     <resources>
  1587       <stylesheet src="chrome://global/skin/notification.css"/>
  1588     </resources>
  1589     <implementation>
  1590       <field name="_states">
  1591         ({SINGLE: 0, MULTI_COLLAPSED: 1, MULTI_EXPANDED: 2})
  1592       </field>
  1593       <field name="_primaryButton">
  1594         document.getAnonymousElementByAttribute(this, "anonid", "primarybutton");
  1595       </field>
  1596       <field name="_secondaryButton">
  1597         document.getAnonymousElementByAttribute(this, "anonid", "secondarybutton")
  1598       </field>
  1599       <field name="_buttonContainer">
  1600         document.getAnonymousElementByAttribute(this, "anonid", "button-container")
  1601       </field>
  1602       <field name="_brandShortName">
  1603         document.getElementById("bundle_brand").getString("brandShortName")
  1604       </field>
  1605       <field name="_items">[]</field>
  1606       <constructor><![CDATA[
  1607         const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  1608         let sortedActions = [];
  1609         for (let action of this.notification.options.pluginData.values()) {
  1610           sortedActions.push(action);
  1612         sortedActions.sort((a, b) => a.pluginName.localeCompare(b.pluginName));
  1614         for (let action of sortedActions) {
  1615           let item = document.createElementNS(XUL_NS, "row");
  1616           item.setAttribute("class", "plugin-popupnotification-centeritem");
  1617           item.action = action;
  1618           this.appendChild(item);
  1619           this._items.push(item);
  1621         switch (this._items.length) {
  1622           case 0:
  1623             PopupNotifications._dismiss();
  1624             break;
  1625           case 1:
  1626             this._setState(this._states.SINGLE);
  1627             break;
  1628           default:
  1629             if (this.notification.options.primaryPlugin) {
  1630               this._setState(this._states.MULTI_COLLAPSED);
  1631             } else {
  1632               this._setState(this._states.MULTI_EXPANDED);
  1635       ]]></constructor>
  1636       <method name="_setState">
  1637         <parameter name="state" />
  1638         <body><![CDATA[
  1639           var grid = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-center-box");
  1641           if (this._states.SINGLE == state) {
  1642             grid.hidden = true;
  1643             this._setupSingleState();
  1644             return;
  1647           let host = gPluginHandler._getHostFromPrincipal(this.notification.browser.contentWindow.document.nodePrincipal);
  1648           this._setupDescription("pluginActivateMultiple.message", null, host);
  1650           var showBox = document.getAnonymousElementByAttribute(this, "anonid", "plugin-notification-showbox");
  1652           var dialogStrings = Services.strings.createBundle("chrome://global/locale/dialog.properties");
  1653           this._primaryButton.label = dialogStrings.GetStringFromName("button-accept");
  1654           this._primaryButton.setAttribute("default", "true");
  1656           this._secondaryButton.label = dialogStrings.GetStringFromName("button-cancel");
  1657           this._primaryButton.setAttribute("action", "_multiAccept");
  1658           this._secondaryButton.setAttribute("action", "_cancel");
  1660           grid.hidden = false;
  1662           if (this._states.MULTI_COLLAPSED == state) {
  1663             for (let child of this.childNodes) {
  1664               if (child.tagName != "row") {
  1665                 continue;
  1667               child.hidden = this.notification.options.primaryPlugin !=
  1668                              child.action.permissionString;
  1670             showBox.hidden = false;
  1672           else {
  1673             for (let child of this.childNodes) {
  1674               if (child.tagName != "row") {
  1675                 continue;
  1677               child.hidden = false;
  1679             showBox.hidden = true;
  1681           this._setupLink(null);
  1682         ]]></body>
  1683       </method>
  1684       <method name="_setupSingleState">
  1685         <body><![CDATA[
  1686           var action = this._items[0].action;
  1687           var host = action.pluginPermissionHost;
  1689           let label, linkLabel, linkUrl, button1, button2;
  1691           if (action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
  1692             button1 = {
  1693               label: "pluginBlockNow.label",
  1694               accesskey: "pluginBlockNow.accesskey",
  1695               action: "_singleBlock"
  1696             };
  1697             button2 = {
  1698               label: "pluginContinue.label",
  1699               accesskey: "pluginContinue.accesskey",
  1700               action: "_singleContinue",
  1701               default: true
  1702             };
  1703             switch (action.blocklistState) {
  1704             case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
  1705               label = "pluginEnabled.message";
  1706               linkLabel = "pluginActivate.learnMore";
  1707               break;
  1709             case Ci.nsIBlocklistService.STATE_BLOCKED:
  1710               Cu.reportError(Error("Cannot happen!"));
  1711               break;
  1713             case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
  1714               label = "pluginEnabledOutdated.message";
  1715               linkLabel = "pluginActivate.updateLabel";
  1716               break;
  1718             case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
  1719               label = "pluginEnabledVulnerable.message";
  1720               linkLabel = "pluginActivate.riskLabel"
  1721               break;
  1723             default:
  1724               Cu.reportError(Error("Unexpected blocklist state"));
  1727           else if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
  1728             let linkElement =
  1729               document.getAnonymousElementByAttribute(
  1730                          this, "anonid", "click-to-play-plugins-notification-link");
  1731             linkElement.textContent = gNavigatorBundle.getString("pluginActivateDisabled.manage");
  1732             linkElement.setAttribute("onclick", "gPluginHandler.managePlugins()");
  1734             let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
  1735             descElement.textContent = gNavigatorBundle.getFormattedString(
  1736               "pluginActivateDisabled.message", [action.pluginName, this._brandShortName]) + " ";
  1737             this._buttonContainer.hidden = true;
  1738             return;
  1740           else if (action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
  1741             let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
  1742             descElement.textContent = gNavigatorBundle.getFormattedString(
  1743               "pluginActivateBlocked.message", [action.pluginName, this._brandShortName]) + " ";
  1744             this._setupLink("pluginActivate.learnMore", action.detailsLink);
  1745             this._buttonContainer.hidden = true;
  1746             return;
  1748           else {
  1749             button1 = {
  1750               label: "pluginActivateNow.label",
  1751               accesskey: "pluginActivateNow.accesskey",
  1752               action: "_singleActivateNow"
  1753             };
  1754             button2 = {
  1755               label: "pluginActivateAlways.label",
  1756               accesskey: "pluginActivateAlways.accesskey",
  1757               action: "_singleActivateAlways"
  1758             };
  1759             switch (action.blocklistState) {
  1760             case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
  1761               label = "pluginActivateNew.message";
  1762               linkLabel = "pluginActivate.learnMore";
  1763               button2.default = true;
  1764               break;
  1766             case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
  1767               label = "pluginActivateOutdated.message";
  1768               linkLabel = "pluginActivate.updateLabel";
  1769               button1.default = true;
  1770               break;
  1772             case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
  1773               label = "pluginActivateVulnerable.message";
  1774               linkLabel = "pluginActivate.riskLabel"
  1775               button1.default = true;
  1776               break;
  1778             default:
  1779               Cu.reportError(Error("Unexpected blocklist state"));
  1782           this._setupDescription(label, action.pluginName, host);
  1783           this._setupLink(linkLabel, action.detailsLink);
  1785           this._primaryButton.label = gNavigatorBundle.getString(button1.label);
  1786           this._primaryButton.accesskey = gNavigatorBundle.getString(button1.accesskey);
  1787           this._primaryButton.setAttribute("action", button1.action);
  1789           this._secondaryButton.label = gNavigatorBundle.getString(button2.label);
  1790           this._secondaryButton.accesskey = gNavigatorBundle.getString(button2.accesskey);
  1791           this._secondaryButton.setAttribute("action", button2.action);
  1792           if (button1.default) {
  1793             this._primaryButton.setAttribute("default", "true");
  1795           else if (button2.default) {
  1796             this._secondaryButton.setAttribute("default", "true");
  1798         ]]></body>
  1799       </method>
  1800       <method name="_setupDescription">
  1801         <parameter name="baseString" />
  1802         <parameter name="pluginName" /> <!-- null for the multiple-plugin case -->
  1803         <parameter name="host" />
  1804         <body><![CDATA[
  1805           var bsn = this._brandShortName;
  1806           var span = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
  1807           while (span.lastChild) {
  1808             span.removeChild(span.lastChild);
  1811           var args = ["__host__", this._brandShortName];
  1812           if (pluginName) {
  1813             args.unshift(pluginName);
  1815           var bases = gNavigatorBundle.getFormattedString(baseString, args).
  1816             split("__host__", 2);
  1818           span.appendChild(document.createTextNode(bases[0]));
  1819           var hostSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "em");
  1820           hostSpan.appendChild(document.createTextNode(host));
  1821           span.appendChild(hostSpan);
  1822           span.appendChild(document.createTextNode(bases[1] + " "));
  1823         ]]></body>
  1824       </method>
  1825       <method name="_setupLink">
  1826         <parameter name="linkString"/>
  1827         <parameter name="linkUrl" />
  1828         <body><![CDATA[
  1829           var link = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-link");
  1830           if (!linkString || !linkUrl) {
  1831             link.hidden = true;
  1832             return;
  1835           link.hidden = false;
  1836           link.textContent = gNavigatorBundle.getString(linkString);
  1837           link.href = linkUrl;
  1838         ]]></body>
  1839       </method>
  1840       <method name="_onButton">
  1841         <parameter name="aButton" />
  1842         <body><![CDATA[
  1843           let methodName = aButton.getAttribute("action");
  1844           this[methodName]();
  1845         ]]></body>
  1846       </method>
  1847       <method name="_singleActivateNow">
  1848         <body><![CDATA[
  1849           gPluginHandler._updatePluginPermission(this.notification,
  1850             this._items[0].action,
  1851             "allownow");
  1852           this._cancel();
  1853         ]]></body>
  1854       </method>
  1855       <method name="_singleBlock">
  1856         <body><![CDATA[
  1857           gPluginHandler._updatePluginPermission(this.notification,
  1858             this._items[0].action,
  1859             "block");
  1860             this._cancel();
  1861         ]]></body>
  1862       </method>
  1863       <method name="_singleActivateAlways">
  1864         <body><![CDATA[
  1865           gPluginHandler._updatePluginPermission(this.notification,
  1866             this._items[0].action,
  1867             "allowalways");
  1868           this._cancel();
  1869         ]]></body>
  1870       </method>
  1871       <method name="_singleContinue">
  1872         <body><![CDATA[
  1873           gPluginHandler._updatePluginPermission(this.notification,
  1874             this._items[0].action,
  1875             "continue");
  1876           this._cancel();
  1877         ]]></body>
  1878       </method>
  1879       <method name="_multiAccept">
  1880         <body><![CDATA[
  1881           for (let item of this._items) {
  1882             let action = item.action;
  1883             if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED ||
  1884                 action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
  1885               continue;
  1887             gPluginHandler._updatePluginPermission(this.notification,
  1888               item.action, item.value);
  1890           this._cancel();
  1891         ]]></body>
  1892       </method>
  1893       <method name="_cancel">
  1894         <body><![CDATA[
  1895           PopupNotifications._dismiss();
  1896         ]]></body>
  1897       </method>
  1898       <method name="_accept">
  1899         <parameter name="aEvent" />
  1900         <body><![CDATA[
  1901           if (aEvent.defaultPrevented)
  1902             return;
  1903           aEvent.preventDefault();
  1904           if (this._primaryButton.getAttribute("default") == "true") {
  1905             this._primaryButton.click();
  1907           else if (this._secondaryButton.getAttribute("default") == "true") {
  1908             this._secondaryButton.click();
  1910         ]]></body>
  1911       </method>
  1912     </implementation>
  1913     <handlers>
  1914       <!-- The _accept method checks for .defaultPrevented so that if focus is in a button,
  1915            enter activates the button and not this default action -->
  1916       <handler event="keypress" keycode="VK_RETURN" group="system" action="this._accept(event);"/>
  1917     </handlers>
  1918   </binding>
  1920   <binding id="splitmenu">
  1921     <content>
  1922       <xul:hbox anonid="menuitem" flex="1"
  1923                 class="splitmenu-menuitem"
  1924                 xbl:inherits="iconic,label,disabled,onclick=oncommand,_moz-menuactive=active"/>
  1925       <xul:menu anonid="menu" class="splitmenu-menu"
  1926                 xbl:inherits="disabled,_moz-menuactive=active"
  1927                 oncommand="event.stopPropagation();">
  1928         <children includes="menupopup"/>
  1929       </xul:menu>
  1930     </content>
  1932     <implementation implements="nsIDOMEventListener">
  1933       <constructor><![CDATA[
  1934         this._parentMenupopup.addEventListener("DOMMenuItemActive", this, false);
  1935         this._parentMenupopup.addEventListener("popuphidden", this, false);
  1936       ]]></constructor>
  1938       <destructor><![CDATA[
  1939         this._parentMenupopup.removeEventListener("DOMMenuItemActive", this, false);
  1940         this._parentMenupopup.removeEventListener("popuphidden", this, false);
  1941       ]]></destructor>
  1943       <field name="menuitem" readonly="true">
  1944         document.getAnonymousElementByAttribute(this, "anonid", "menuitem");
  1945       </field>
  1946       <field name="menu" readonly="true">
  1947         document.getAnonymousElementByAttribute(this, "anonid", "menu");
  1948       </field>
  1950       <field name="_menuDelay">600</field>
  1952       <field name="_parentMenupopup"><![CDATA[
  1953         this._getParentMenupopup(this);
  1954       ]]></field>
  1956       <method name="_getParentMenupopup">
  1957         <parameter name="aNode"/>
  1958         <body><![CDATA[
  1959           let node = aNode.parentNode;
  1960           while (node) {
  1961             if (node.localName == "menupopup")
  1962               break;
  1963             node = node.parentNode;
  1965           return node;
  1966         ]]></body>
  1967       </method>
  1969       <method name="handleEvent">
  1970         <parameter name="event"/>
  1971         <body><![CDATA[
  1972           switch (event.type) {
  1973             case "DOMMenuItemActive":
  1974               if (this.getAttribute("active") == "true" &&
  1975                   event.target != this &&
  1976                   this._getParentMenupopup(event.target) == this._parentMenupopup)
  1977                 this.removeAttribute("active");
  1978               break;
  1979             case "popuphidden":
  1980               if (event.target == this._parentMenupopup)
  1981                 this.removeAttribute("active");
  1982               break;
  1984         ]]></body>
  1985       </method>
  1986     </implementation>
  1988     <handlers>
  1989       <handler event="mouseover"><![CDATA[
  1990         if (this.getAttribute("active") != "true") {
  1991           this.setAttribute("active", "true");
  1993           let event = document.createEvent("Events");
  1994           event.initEvent("DOMMenuItemActive", true, false);
  1995           this.dispatchEvent(event);
  1997           if (this.getAttribute("disabled") != "true") {
  1998             let self = this;
  1999             setTimeout(function () {
  2000               if (self.getAttribute("active") == "true")
  2001                 self.menu.open = true;
  2002             }, this._menuDelay);
  2005       ]]></handler>
  2007       <handler event="popupshowing"><![CDATA[
  2008         if (event.target == this.firstChild &&
  2009             this._parentMenupopup._currentPopup)
  2010           this._parentMenupopup._currentPopup.hidePopup();
  2011       ]]></handler>
  2013       <handler event="click" phase="capturing"><![CDATA[
  2014         if (this.getAttribute("disabled") == "true") {
  2015           // Prevent the command from being carried out
  2016           event.stopPropagation();
  2017           return;
  2020         let node = event.originalTarget;
  2021         while (true) {
  2022           if (node == this.menuitem)
  2023             break;
  2024           if (node == this)
  2025             return;
  2026           node = node.parentNode;
  2029         this._parentMenupopup.hidePopup();
  2030       ]]></handler>
  2031     </handlers>
  2032   </binding>
  2034   <binding id="menuitem-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem">
  2035     <implementation>
  2036       <constructor><![CDATA[
  2037         this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
  2038         // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
  2039         // 592424 is fixed
  2040         document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
  2041       ]]></constructor>
  2042     </implementation>
  2043   </binding>
  2045   <binding id="menuitem-iconic-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem-iconic">
  2046     <implementation>
  2047       <constructor><![CDATA[
  2048         this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
  2049         // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
  2050         // 592424 is fixed
  2051         document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
  2052       ]]></constructor>
  2053     </implementation>
  2054   </binding>
  2056  <binding id="promobox">
  2057     <content>
  2058       <xul:hbox class="panel-promo-box" align="start" flex="1">
  2059         <xul:hbox align="center" flex="1">
  2060           <xul:image class="panel-promo-icon"/>
  2061           <xul:description anonid="promo-message" class="panel-promo-message" flex="1">
  2062             <xul:description anonid="promo-link"
  2063                              class="plain text-link inline-link"
  2064                              onclick="document.getBindingParent(this).onLinkClick();"/>
  2065           </xul:description>
  2066         </xul:hbox>
  2067         <xul:toolbarbutton class="panel-promo-closebutton close-icon"
  2068                            oncommand="document.getBindingParent(this).onCloseButtonCommand();"
  2069                            tooltiptext="&closeNotification.tooltip;"/>
  2070       </xul:hbox>
  2071     </content>
  2073     <implementation implements="nsIDOMEventListener">
  2074       <constructor><![CDATA[
  2075         this._panel.addEventListener("popupshowing", this, false);
  2076       ]]></constructor>
  2078       <destructor><![CDATA[
  2079         this._panel.removeEventListener("popupshowing", this, false);
  2080       ]]></destructor>
  2082       <field name="_panel" readonly="true"><![CDATA[
  2083         let node = this.parentNode;
  2084         while(node && node.localName != "panel") {
  2085           node = node.parentNode;
  2087         node;
  2088       ]]></field>
  2089       <field name="_promomessage" readonly="true">
  2090         document.getAnonymousElementByAttribute(this, "anonid", "promo-message");
  2091       </field>
  2092       <field name="_promolink" readonly="true">
  2093         document.getAnonymousElementByAttribute(this, "anonid", "promo-link");
  2094       </field>
  2095       <field name="_brandBundle" readonly="true">
  2096         Services.strings.createBundle("chrome://branding/locale/brand.properties");
  2097       </field>
  2098       <property name="_viewsLeftMap">
  2099         <getter><![CDATA[
  2100           try {
  2101             return JSON.parse(Services.prefs.getCharPref("browser.syncPromoViewsLeftMap"));
  2102           } catch (ex) {}
  2103           return {};
  2104         ]]></getter>
  2105       </property>
  2106       <property name="_viewsLeft">
  2107         <getter><![CDATA[
  2108           let views = 5;
  2109           let map = this._viewsLeftMap;
  2110           if (this._notificationType in map) {
  2111             views = map[this._notificationType];
  2113           return views;
  2114         ]]></getter>
  2115         <setter><![CDATA[
  2116           let map = this._viewsLeftMap;
  2117           map[this._notificationType] = val;
  2118           Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
  2119                                      JSON.stringify(map));
  2120           return val;
  2121         ]]></setter>
  2122       </property>
  2123       <property name="_notificationType">
  2124         <getter><![CDATA[
  2125           // Use the popupid attribute to identify the notification type,
  2126           // otherwise just rely on the panel id for common arrowpanels.
  2127           let type = this._panel.firstChild.getAttribute("popupid") ||
  2128                      this._panel.id;
  2129           if (type.startsWith("password-"))
  2130             return "passwords";
  2131           if (type == "editBookmarkPanel")
  2132             return "bookmarks";
  2133           if (type == "addon-install-complete") {
  2134             if (!Services.prefs.prefHasUserValue("services.sync.username"))
  2135               return "addons";
  2136             if (!Services.prefs.getBoolPref("services.sync.engine.addons"))
  2137               return "addons-sync-disabled";
  2139           return null;
  2140         ]]></getter>
  2141       </property>
  2142       <property name="_notificationMessage">
  2143         <getter><![CDATA[
  2144           return gNavigatorBundle.getFormattedString(
  2145             "syncPromoNotification." + this._notificationType + ".description",
  2146             [this._brandBundle.GetStringFromName("syncBrandShortName")]
  2147           );
  2148         ]]></getter>
  2149       </property>
  2150       <property name="_notificationLink">
  2151         <getter><![CDATA[
  2152           if (this._notificationType == "addons-sync-disabled") {
  2153             return "https://support.mozilla.org/kb/how-do-i-enable-add-sync";
  2155           return "https://services.mozilla.com/sync/";
  2156         ]]></getter>
  2157       </property>
  2158       <method name="onCloseButtonCommand">
  2159         <body><![CDATA[
  2160           this._viewsLeft = 0;
  2161           this.hidden = true;
  2162         ]]></body>
  2163       </method>
  2164       <method name="onLinkClick">
  2165         <body><![CDATA[
  2166           // Open a new selected tab and close the current panel.
  2167           openUILinkIn(this._promolink.getAttribute("href"), "tab");
  2168           this._panel.hidePopup();
  2169         ]]></body>
  2170       </method>
  2171       <method name="handleEvent">
  2172         <parameter name="event"/>
  2173         <body><![CDATA[
  2174           if (event.type != "popupshowing" || event.target != this._panel)
  2175             return;
  2177           // A previous notification may have unhidden this.
  2178           this.hidden = true;
  2180           // Only handle supported notification panels.
  2181           if (!this._notificationType) {
  2182             return;
  2185           let viewsLeft = this._viewsLeft;
  2186           if (viewsLeft) {
  2187             if (Services.prefs.prefHasUserValue("services.sync.username") &&
  2188                this._notificationType != "addons-sync-disabled") {
  2189               // If the user has already setup Sync, don't show the notification.
  2190               this._viewsLeft = 0;
  2191               // Be sure to hide the panel, in case it was visible and the user
  2192               // decided to setup Sync after noticing it.
  2193               viewsLeft = 0;
  2194               // The panel is still hidden, just bail out.
  2195               return;
  2197             else {
  2198               this._viewsLeft = viewsLeft - 1;
  2201             this._promolink.setAttribute("href", this._notificationLink);
  2202             this._promolink.value = gNavigatorBundle.getString("syncPromoNotification.learnMoreLinkText");
  2204             this.hidden = false;
  2206             // HACK: The description element doesn't wrap correctly in panels,
  2207             // thus set a width on it, based on the available space, before
  2208             // setting its textContent.  Then set its height as well, to
  2209             // fix wrong height calculation on Linux (bug 659578).
  2210             this._panel.addEventListener("popupshown", function panelShown() {
  2211               this._panel.removeEventListener("popupshown", panelShown, true);
  2212               // Previous popupShown events may close the panel or change
  2213               // its contents, so ensure this is still valid.
  2214               if (this._panel.state != "open" || !this._notificationType)
  2215                 return;
  2216               this._promomessage.width = this._promomessage.getBoundingClientRect().width;
  2217               this._promomessage.firstChild.textContent = this._notificationMessage;
  2218               this._promomessage.height = this._promomessage.getBoundingClientRect().height;
  2219             }.bind(this), true);
  2221         ]]></body>
  2222       </method>
  2223     </implementation>
  2224   </binding>
  2226   <binding id="toolbarbutton-badged" display="xul:button"
  2227            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
  2228     <content>
  2229       <children includes="observes|template|menupopup|panel|tooltip"/>
  2230       <xul:hbox class="toolbarbutton-badge-container" align="start" pack="end">
  2231         <xul:hbox class="toolbarbutton-badge" xbl:inherits="badge"/>
  2232         <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
  2233       </xul:hbox>
  2234       <xul:label class="toolbarbutton-text" crop="right" flex="1"
  2235                  xbl:inherits="value=label,accesskey,crop,wrap"/>
  2236       <xul:label class="toolbarbutton-multiline-text" flex="1"
  2237                  xbl:inherits="xbl:text=label,accesskey,wrap"/>
  2238     </content>
  2239   </binding>
  2241 </bindings>

mercurial