Bug 1031527 - Remove dup fd from ParamTraits<MagicGrallocBufferHandle>::Read(). r...
[gecko.git] / browser / base / content / urlbarBindings.xml
blob65e39668bed501003a9521e521c982047a6c1a7b
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;
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";
1043           }
1044           else {
1045             this.progressmeter.mode = "determined";
1046             this.progressmeter.value = (aProgress * 100) / aMaxProgress;
1047           }
1049           let now = Date.now();
1051           if (!this.notification.lastUpdate) {
1052             this.notification.lastUpdate = now;
1053             this.notification.lastProgress = aProgress;
1054             return;
1055           }
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();
1090             }
1091             catch (e) {
1092               // Cancel will throw if the download has already failed
1093             }
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                 });
1114               }
1115             }
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);
1142           }
1143           else {
1144             this.setProgress(progress, maxProgress);
1145           }
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;
1255           }
1256           if (this.identity.selected) {
1257             // If the user already chose an identity then update the UI to reflect that
1258             this.onIdentitySelected();
1259           }
1260           // Update the view for the step
1261           this.step = this.step;
1262         }
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);
1339           }
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;
1370           }
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;
1385             }
1386           }
1387           if (!foundLastUsed) {
1388             this.identityList.selectedIndex = -1;
1389           }
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;
1405               }
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;
1418           }
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";
1464           }
1465           else {
1466             curState = "allowalways";
1467           }
1468         }
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;
1488         }
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;
1509           }
1510         }
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;
1517           }
1518           if (linkHandler) {
1519             link.addEventListener("click", linkHandler, false);
1520           }
1521         }
1522         else {
1523           link.hidden = true;
1524         }
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);
1611         }
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);
1620         }
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);
1633             }
1634         }
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;
1645           }
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;
1666               }
1667               child.hidden = this.notification.options.primaryPlugin !=
1668                              child.action.permissionString;
1669             }
1670             showBox.hidden = false;
1671           }
1672           else {
1673             for (let child of this.childNodes) {
1674               if (child.tagName != "row") {
1675                 continue;
1676               }
1677               child.hidden = false;
1678             }
1679             showBox.hidden = true;
1680           }
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"));
1725             }
1726           }
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;
1739           }
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;
1747           }
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"));
1780             }
1781           }
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");
1794           }
1795           else if (button2.default) {
1796             this._secondaryButton.setAttribute("default", "true");
1797           }
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);
1809           }
1811           var args = ["__host__", this._brandShortName];
1812           if (pluginName) {
1813             args.unshift(pluginName);
1814           }
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;
1833           }
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;
1886             }
1887             gPluginHandler._updatePluginPermission(this.notification,
1888               item.action, item.value);
1889           }
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();
1906           }
1907           else if (this._secondaryButton.getAttribute("default") == "true") {
1908             this._secondaryButton.click();
1909           }
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;
1964           }
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;
1983           }
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);
2003           }
2004         }
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;
2018         }
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;
2027         }
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;
2086         }
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];
2112           }
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";
2138           }
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";
2154           }
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;
2183           }
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;
2196             }
2197             else {
2198               this._viewsLeft = viewsLeft - 1;
2199             }
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);
2220           }
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>