Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 <?xml version="1.0"?>
3 # -*- Mode: HTML -*-
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 <!DOCTYPE bindings [
9 <!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
10 %notificationDTD;
11 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
12 %browserDTD;
13 ]>
15 <bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl"
16 xmlns:html="http://www.w3.org/1999/xhtml"
17 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
18 xmlns:xbl="http://www.mozilla.org/xbl">
20 <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
22 <content sizetopopup="pref">
23 <xul:hbox anonid="textbox-container"
24 class="autocomplete-textbox-container urlbar-textbox-container"
25 flex="1" xbl:inherits="focused">
26 <children includes="image|deck|stack|box">
27 <xul:image class="autocomplete-icon" allowevents="true"/>
28 </children>
29 <xul:hbox anonid="textbox-input-box"
30 class="textbox-input-box urlbar-input-box"
31 flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
32 <children/>
33 <html:input anonid="input"
34 class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
35 allowevents="true"
36 xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
37 </xul:hbox>
38 <children includes="hbox"/>
39 </xul:hbox>
40 <xul:dropmarker anonid="historydropmarker"
41 class="autocomplete-history-dropmarker urlbar-history-dropmarker"
42 allowevents="true"
43 xbl:inherits="open,enablehistory,parentfocused=focused"/>
44 <xul:popupset anonid="popupset"
45 class="autocomplete-result-popupset"/>
46 <children includes="toolbarbutton"/>
47 </content>
49 <implementation implements="nsIObserver, nsIDOMEventListener">
50 <constructor><![CDATA[
51 this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
52 .getService(Components.interfaces.nsIPrefService)
53 .getBranch("browser.urlbar.");
55 this._prefs.addObserver("", this, false);
56 this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
57 this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
58 this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
59 this.timeout = this._prefs.getIntPref("delay");
60 this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
61 this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
62 this._ignoreNextSelect = false;
64 this.inputField.controllers.insertControllerAt(0, this._copyCutController);
65 this.inputField.addEventListener("mousedown", this, false);
66 this.inputField.addEventListener("mousemove", this, false);
67 this.inputField.addEventListener("mouseout", this, false);
68 this.inputField.addEventListener("overflow", this, false);
69 this.inputField.addEventListener("underflow", this, false);
71 try {
72 if (this._prefs.getBoolPref("unifiedcomplete")) {
73 this.setAttribute("autocompletesearch", "unifiedcomplete");
74 }
75 } catch (ex) {}
77 const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
78 var textBox = document.getAnonymousElementByAttribute(this,
79 "anonid", "textbox-input-box");
80 var cxmenu = document.getAnonymousElementByAttribute(textBox,
81 "anonid", "input-box-contextmenu");
82 var pasteAndGo;
83 cxmenu.addEventListener("popupshowing", function() {
84 if (!pasteAndGo)
85 return;
86 var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
87 var enabled = controller.isCommandEnabled("cmd_paste");
88 if (enabled)
89 pasteAndGo.removeAttribute("disabled");
90 else
91 pasteAndGo.setAttribute("disabled", "true");
92 }, false);
94 var insertLocation = cxmenu.firstChild;
95 while (insertLocation.nextSibling &&
96 insertLocation.getAttribute("cmd") != "cmd_paste")
97 insertLocation = insertLocation.nextSibling;
98 if (insertLocation) {
99 pasteAndGo = document.createElement("menuitem");
100 let label = Services.strings.createBundle("chrome://browser/locale/browser.properties").
101 GetStringFromName("pasteAndGo.label");
102 pasteAndGo.setAttribute("label", label);
103 pasteAndGo.setAttribute("anonid", "paste-and-go");
104 pasteAndGo.setAttribute("oncommand",
105 "gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
106 cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
107 }
108 ]]></constructor>
110 <destructor><![CDATA[
111 this._prefs.removeObserver("", this);
112 this._prefs = null;
113 this.inputField.controllers.removeController(this._copyCutController);
114 this.inputField.removeEventListener("mousedown", this, false);
115 this.inputField.removeEventListener("mousemove", this, false);
116 this.inputField.removeEventListener("mouseout", this, false);
117 this.inputField.removeEventListener("overflow", this, false);
118 this.inputField.removeEventListener("underflow", this, false);
119 ]]></destructor>
121 <field name="_value"></field>
123 <!--
124 onBeforeValueGet is called by the base-binding's .value getter.
125 It can return an object with a "value" property, to override the
126 return value of the getter.
127 -->
128 <method name="onBeforeValueGet">
129 <body><![CDATA[
130 if (this.hasAttribute("actiontype"))
131 return {value: this._value};
132 return null;
133 ]]></body>
134 </method>
136 <!--
137 onBeforeValueSet is called by the base-binding's .value setter.
138 It should return the value that the setter should use.
139 -->
140 <method name="onBeforeValueSet">
141 <parameter name="aValue"/>
142 <body><![CDATA[
143 this._value = aValue;
144 var returnValue = aValue;
145 var action = this._parseActionUrl(aValue);
146 // Don't put back the action if we are invoked while override actions
147 // is active.
148 if (action && this._numNoActionsKeys <= 0) {
149 returnValue = action.param;
150 this.setAttribute("actiontype", action.type);
151 } else {
152 this.removeAttribute("actiontype");
153 }
154 return returnValue;
155 ]]></body>
156 </method>
158 <field name="_mayTrimURLs">true</field>
159 <method name="trimValue">
160 <parameter name="aURL"/>
161 <body><![CDATA[
162 // This method must not modify the given URL such that calling
163 // nsIURIFixup::createFixupURI with the result will produce a different URI.
164 return this._mayTrimURLs ? trimURL(aURL) : aURL;
165 ]]></body>
166 </method>
168 <field name="_formattingEnabled">true</field>
169 <method name="formatValue">
170 <body><![CDATA[
171 if (!this._formattingEnabled || this.focused)
172 return;
174 let controller = this.editor.selectionController;
175 let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
176 selection.removeAllRanges();
178 let textNode = this.editor.rootElement.firstChild;
179 let value = textNode.textContent;
181 let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
182 if (protocol &&
183 ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
184 return;
185 let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
186 if (!matchedURL)
187 return;
189 let [, preDomain, domain] = matchedURL;
190 let baseDomain = domain;
191 let subDomain = "";
192 // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
193 if (domain[0] != "[") {
194 try {
195 baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
196 if (!domain.endsWith(baseDomain)) {
197 // getBaseDomainFromHost converts its resultant to ACE.
198 let IDNService = Cc["@mozilla.org/network/idn-service;1"]
199 .getService(Ci.nsIIDNService);
200 baseDomain = IDNService.convertACEtoUTF8(baseDomain);
201 }
202 } catch (e) {}
203 }
204 if (baseDomain != domain) {
205 subDomain = domain.slice(0, -baseDomain.length);
206 }
208 let rangeLength = preDomain.length + subDomain.length;
209 if (rangeLength) {
210 let range = document.createRange();
211 range.setStart(textNode, 0);
212 range.setEnd(textNode, rangeLength);
213 selection.addRange(range);
214 }
216 let startRest = preDomain.length + domain.length;
217 if (startRest < value.length) {
218 let range = document.createRange();
219 range.setStart(textNode, startRest);
220 range.setEnd(textNode, value.length);
221 selection.addRange(range);
222 }
223 ]]></body>
224 </method>
226 <method name="_clearFormatting">
227 <body><![CDATA[
228 if (!this._formattingEnabled)
229 return;
231 let controller = this.editor.selectionController;
232 let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
233 selection.removeAllRanges();
234 ]]></body>
235 </method>
237 <method name="handleRevert">
238 <body><![CDATA[
239 var isScrolling = this.popupOpen;
241 gBrowser.userTypedValue = null;
243 // don't revert to last valid url unless page is NOT loading
244 // and user is NOT key-scrolling through autocomplete list
245 if (!XULBrowserWindow.isBusy && !isScrolling) {
246 URLBarSetURI();
248 // If the value isn't empty and the urlbar has focus, select the value.
249 if (this.value && this.hasAttribute("focused"))
250 this.select();
251 }
253 // tell widget to revert to last typed text only if the user
254 // was scrolling when they hit escape
255 return !isScrolling;
256 ]]></body>
257 </method>
259 <method name="handleCommand">
260 <parameter name="aTriggeringEvent"/>
261 <body><![CDATA[
262 if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
263 return; // Do nothing for right clicks
265 var url = this.value;
266 var mayInheritPrincipal = false;
267 var postData = null;
269 var action = this._parseActionUrl(url);
270 let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
272 let matchLastLocationChange = true;
273 if (action) {
274 url = action.param;
275 if (this.hasAttribute("actiontype")) {
276 if (action.type == "switchtab") {
277 this.handleRevert();
278 let prevTab = gBrowser.selectedTab;
279 if (switchToTabHavingURI(url) &&
280 isTabEmpty(prevTab))
281 gBrowser.removeTab(prevTab);
282 }
283 return;
284 }
285 continueOperation.call(this);
286 }
287 else {
288 this._canonizeURL(aTriggeringEvent, response => {
289 [url, postData, mayInheritPrincipal] = response;
290 if (url) {
291 matchLastLocationChange = (lastLocationChange ==
292 gBrowser.selectedBrowser.lastLocationChange);
293 continueOperation.call(this);
294 }
295 });
296 }
298 function continueOperation()
299 {
300 this.value = url;
301 gBrowser.userTypedValue = url;
302 try {
303 addToUrlbarHistory(url);
304 } catch (ex) {
305 // Things may go wrong when adding url to session history,
306 // but don't let that interfere with the loading of the url.
307 Cu.reportError(ex);
308 }
310 function loadCurrent() {
311 let webnav = Ci.nsIWebNavigation;
312 let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
313 webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
314 // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
315 // inheriting the currently loaded document's principal, unless this
316 // URL is marked as safe to inherit (e.g. came from a bookmark
317 // keyword).
318 if (!mayInheritPrincipal)
319 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
320 gBrowser.loadURIWithFlags(url, flags, null, null, postData);
321 }
323 // Focus the content area before triggering loads, since if the load
324 // occurs in a new tab, we want focus to be restored to the content
325 // area when the current tab is re-selected.
326 gBrowser.selectedBrowser.focus();
328 let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
329 let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
331 if (altEnter) {
332 // XXX This was added a long time ago, and I'm not sure why it is
333 // necessary. Alt+Enter's default action might cause a system beep,
334 // or something like that?
335 aTriggeringEvent.preventDefault();
336 aTriggeringEvent.stopPropagation();
337 }
339 // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
340 altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
342 if (isMouseEvent || altEnter) {
343 // Use the standard UI link behaviors for clicks or Alt+Enter
344 let where = "tab";
345 if (isMouseEvent)
346 where = whereToOpenLink(aTriggeringEvent, false, false);
348 if (where == "current") {
349 if (matchLastLocationChange) {
350 loadCurrent();
351 }
352 } else {
353 this.handleRevert();
354 let params = { allowThirdPartyFixup: true,
355 postData: postData,
356 initiatingDoc: document };
357 openUILinkIn(url, where, params);
358 }
359 } else {
360 if (matchLastLocationChange) {
361 loadCurrent();
362 }
363 }
364 }
365 ]]></body>
366 </method>
368 <method name="_canonizeURL">
369 <parameter name="aTriggeringEvent"/>
370 <parameter name="aCallback"/>
371 <body><![CDATA[
372 var url = this.value;
373 if (!url) {
374 aCallback(["", null, false]);
375 return;
376 }
378 // Only add the suffix when the URL bar value isn't already "URL-like",
379 // and only if we get a keyboard event, to match user expectations.
380 if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
381 (aTriggeringEvent instanceof KeyEvent)) {
382 #ifdef XP_MACOSX
383 let accel = aTriggeringEvent.metaKey;
384 #else
385 let accel = aTriggeringEvent.ctrlKey;
386 #endif
387 let shift = aTriggeringEvent.shiftKey;
389 let suffix = "";
391 switch (true) {
392 case (accel && shift):
393 suffix = ".org/";
394 break;
395 case (shift):
396 suffix = ".net/";
397 break;
398 case (accel):
399 try {
400 suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
401 if (suffix.charAt(suffix.length - 1) != "/")
402 suffix += "/";
403 } catch(e) {
404 suffix = ".com/";
405 }
406 break;
407 }
409 if (suffix) {
410 // trim leading/trailing spaces (bug 233205)
411 url = url.trim();
413 // Tack www. and suffix on. If user has appended directories, insert
414 // suffix before them (bug 279035). Be careful not to get two slashes.
416 let firstSlash = url.indexOf("/");
418 if (firstSlash >= 0) {
419 url = url.substring(0, firstSlash) + suffix +
420 url.substring(firstSlash + 1);
421 } else {
422 url = url + suffix;
423 }
425 url = "http://www." + url;
426 }
427 }
429 getShortcutOrURIAndPostData(url, data => {
430 aCallback([data.url, data.postData, data.mayInheritPrincipal]);
431 });
432 ]]></body>
433 </method>
435 <field name="_contentIsCropped">false</field>
437 <method name="_initURLTooltip">
438 <body><![CDATA[
439 if (this.focused || !this._contentIsCropped)
440 return;
441 this.inputField.setAttribute("tooltiptext", this.value);
442 ]]></body>
443 </method>
445 <method name="_hideURLTooltip">
446 <body><![CDATA[
447 this.inputField.removeAttribute("tooltiptext");
448 ]]></body>
449 </method>
451 <method name="onDragOver">
452 <parameter name="aEvent"/>
453 <body>
454 var types = aEvent.dataTransfer.types;
455 if (types.contains("application/x-moz-file") ||
456 types.contains("text/x-moz-url") ||
457 types.contains("text/uri-list") ||
458 types.contains("text/unicode"))
459 aEvent.preventDefault();
460 </body>
461 </method>
463 <method name="onDrop">
464 <parameter name="aEvent"/>
465 <body><![CDATA[
466 let url = browserDragAndDrop.drop(aEvent, { })
468 // The URL bar automatically handles inputs with newline characters,
469 // so we can get away with treating text/x-moz-url flavours as text/plain.
470 if (url) {
471 aEvent.preventDefault();
472 this.value = url;
473 SetPageProxyState("invalid");
474 this.focus();
475 try {
476 urlSecurityCheck(url,
477 gBrowser.contentPrincipal,
478 Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
479 } catch (ex) {
480 return;
481 }
482 this.handleCommand();
483 }
484 ]]></body>
485 </method>
487 <method name="_getSelectedValueForClipboard">
488 <body><![CDATA[
489 // Grab the actual input field's value, not our value, which could include moz-action:
490 var inputVal = this.inputField.value;
491 var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
493 // If the selection doesn't start at the beginning or doesn't span the full domain or
494 // the URL bar is modified, nothing else to do here.
495 if (this.selectionStart > 0 || this.valueIsTyped)
496 return selectedVal;
497 // The selection doesn't span the full domain if it doesn't contain a slash and is
498 // followed by some character other than a slash.
499 if (!selectedVal.contains("/")) {
500 let remainder = inputVal.replace(selectedVal, "");
501 if (remainder != "" && remainder[0] != "/")
502 return selectedVal;
503 }
505 let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
507 let uri;
508 try {
509 uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
510 } catch (e) {}
511 if (!uri)
512 return selectedVal;
514 // Only copy exposable URIs
515 try {
516 uri = uriFixup.createExposableURI(uri);
517 } catch (ex) {}
519 // If the entire URL is selected, just use the actual loaded URI.
520 if (inputVal == selectedVal) {
521 // ... but only if isn't a javascript: or data: URI, since those
522 // are hard to read when encoded
523 if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
524 // Parentheses are known to confuse third-party applications (bug 458565).
525 selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
526 }
528 return selectedVal;
529 }
531 // Just the beginning of the URL is selected, check for a trimmed
532 // value
533 let spec = uri.spec;
534 let trimmedSpec = this.trimValue(spec);
535 if (spec != trimmedSpec) {
536 // Prepend the portion that trimValue removed from the beginning.
537 // This assumes trimValue will only truncate the URL at
538 // the beginning or end (or both).
539 let trimmedSegments = spec.split(trimmedSpec);
540 selectedVal = trimmedSegments[0] + selectedVal;
541 }
543 return selectedVal;
544 ]]></body>
545 </method>
547 <field name="_copyCutController"><![CDATA[
548 ({
549 urlbar: this,
550 doCommand: function(aCommand) {
551 var urlbar = this.urlbar;
552 var val = urlbar._getSelectedValueForClipboard();
553 if (!val)
554 return;
556 if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
557 let start = urlbar.selectionStart;
558 let end = urlbar.selectionEnd;
559 urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
560 urlbar.inputField.value.substring(end);
561 urlbar.selectionStart = urlbar.selectionEnd = start;
562 urlbar.removeAttribute("actiontype");
563 SetPageProxyState("invalid");
564 }
566 Cc["@mozilla.org/widget/clipboardhelper;1"]
567 .getService(Ci.nsIClipboardHelper)
568 .copyString(val, document);
569 },
570 supportsCommand: function(aCommand) {
571 switch (aCommand) {
572 case "cmd_copy":
573 case "cmd_cut":
574 return true;
575 }
576 return false;
577 },
578 isCommandEnabled: function(aCommand) {
579 return this.supportsCommand(aCommand) &&
580 (aCommand != "cmd_cut" || !this.urlbar.readOnly) &&
581 this.urlbar.selectionStart < this.urlbar.selectionEnd;
582 },
583 onEvent: function(aEventName) {}
584 })
585 ]]></field>
587 <method name="observe">
588 <parameter name="aSubject"/>
589 <parameter name="aTopic"/>
590 <parameter name="aData"/>
591 <body><![CDATA[
592 if (aTopic == "nsPref:changed") {
593 switch (aData) {
594 case "clickSelectsAll":
595 case "doubleClickSelectsAll":
596 this[aData] = this._prefs.getBoolPref(aData);
597 break;
598 case "autoFill":
599 this.completeDefaultIndex = this._prefs.getBoolPref(aData);
600 break;
601 case "delay":
602 this.timeout = this._prefs.getIntPref(aData);
603 break;
604 case "formatting.enabled":
605 this._formattingEnabled = this._prefs.getBoolPref(aData);
606 break;
607 case "trimURLs":
608 this._mayTrimURLs = this._prefs.getBoolPref(aData);
609 break;
610 case "unifiedcomplete":
611 let useUnifiedComplete = false;
612 try {
613 useUnifiedComplete = this._prefs.getBoolPref(aData);
614 } catch (ex) {}
615 this.setAttribute("autocompletesearch",
616 useUnifiedComplete ? "unifiedcomplete"
617 : "urlinline history");
618 }
619 }
620 ]]></body>
621 </method>
623 <method name="handleEvent">
624 <parameter name="aEvent"/>
625 <body><![CDATA[
626 switch (aEvent.type) {
627 case "mousedown":
628 if (this.doubleClickSelectsAll &&
629 aEvent.button == 0 && aEvent.detail == 2) {
630 this.editor.selectAll();
631 aEvent.preventDefault();
632 }
633 break;
634 case "mousemove":
635 this._initURLTooltip();
636 break;
637 case "mouseout":
638 this._hideURLTooltip();
639 break;
640 case "overflow":
641 this._contentIsCropped = true;
642 break;
643 case "underflow":
644 this._contentIsCropped = false;
645 this._hideURLTooltip();
646 break;
647 }
648 ]]></body>
649 </method>
651 <property name="textValue"
652 onget="return this.value;">
653 <setter>
654 <![CDATA[
655 try {
656 val = losslessDecodeURI(makeURI(val));
657 } catch (ex) { }
659 // Trim popup selected values, but never trim results coming from
660 // autofill.
661 if (this.popup.selectedIndex == -1)
662 this._disableTrim = true;
663 this.value = val;
664 this._disableTrim = false;
666 // Completing a result should simulate the user typing the result, so
667 // fire an input event.
668 let evt = document.createEvent("UIEvents");
669 evt.initUIEvent("input", true, false, window, 0);
670 this.mIgnoreInput = true;
671 this.dispatchEvent(evt);
672 this.mIgnoreInput = false;
674 return this.value;
675 ]]>
676 </setter>
677 </property>
679 <method name="_parseActionUrl">
680 <parameter name="aUrl"/>
681 <body><![CDATA[
682 if (!aUrl.startsWith("moz-action:"))
683 return null;
685 // url is in the format moz-action:ACTION,PARAM
686 let [, action, param] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
687 return {type: action, param: param};
688 ]]></body>
689 </method>
691 <field name="_numNoActionsKeys"><![CDATA[
692 0
693 ]]></field>
695 <method name="_clearNoActions">
696 <parameter name="aURL"/>
697 <body><![CDATA[
698 this._numNoActionsKeys = 0;
699 this.popup.removeAttribute("noactions");
700 let action = this._parseActionUrl(this._value);
701 if (action)
702 this.setAttribute("actiontype", action.type);
703 ]]></body>
704 </method>
706 <method name="selectTextRange">
707 <parameter name="aStartIndex"/>
708 <parameter name="aEndIndex"/>
709 <body><![CDATA[
710 this._ignoreNextSelect = true;
711 this.inputField.setSelectionRange(aStartIndex, aEndIndex);
712 ]]></body>
713 </method>
714 </implementation>
716 <handlers>
717 <handler event="keydown"><![CDATA[
718 if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
719 event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
720 this.popup.selectedIndex >= 0) {
721 this._numNoActionsKeys++;
722 this.popup.setAttribute("noactions", "true");
723 this.removeAttribute("actiontype");
724 }
725 ]]></handler>
727 <handler event="keyup"><![CDATA[
728 if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
729 event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
730 this._numNoActionsKeys > 0) {
731 this._numNoActionsKeys--;
732 if (this._numNoActionsKeys == 0)
733 this._clearNoActions();
734 }
735 ]]></handler>
737 <handler event="blur"><![CDATA[
738 this._clearNoActions();
739 this.formatValue();
740 ]]></handler>
742 <handler event="dragstart" phase="capturing"><![CDATA[
743 // Drag only if the gesture starts from the input field.
744 if (event.originalTarget != this.inputField)
745 return;
747 // Drag only if the entire value is selected and it's a valid URI.
748 var isFullSelection = this.selectionStart == 0 &&
749 this.selectionEnd == this.textLength;
750 if (!isFullSelection ||
751 this.getAttribute("pageproxystate") != "valid")
752 return;
754 var urlString = content.location.href;
755 var title = content.document.title || urlString;
756 var htmlString = "<a href=\"" + urlString + "\">" + urlString + "</a>";
758 var dt = event.dataTransfer;
759 dt.setData("text/x-moz-url", urlString + "\n" + title);
760 dt.setData("text/unicode", urlString);
761 dt.setData("text/html", htmlString);
763 dt.effectAllowed = "copyLink";
764 event.stopPropagation();
765 ]]></handler>
767 <handler event="focus" phase="capturing"><![CDATA[
768 this._hideURLTooltip();
769 this._clearFormatting();
770 ]]></handler>
772 <handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
773 <handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
774 <handler event="select"><![CDATA[
775 if (this._ignoreNextSelect) {
776 // If this select event is coming from autocomplete's selectTextRange,
777 // then we don't need to adjust what's on the selection keyboard here,
778 // but make sure to reset the flag since this should be a one-time
779 // suppression.
780 this._ignoreNextSelect = false;
781 return;
782 }
784 if (!Cc["@mozilla.org/widget/clipboard;1"]
785 .getService(Ci.nsIClipboard)
786 .supportsSelectionClipboard())
787 return;
789 var val = this._getSelectedValueForClipboard();
790 if (!val)
791 return;
793 Cc["@mozilla.org/widget/clipboardhelper;1"]
794 .getService(Ci.nsIClipboardHelper)
795 .copyStringToClipboard(val, Ci.nsIClipboard.kSelectionClipboard, document);
796 ]]></handler>
797 </handlers>
799 </binding>
801 <!-- Note: this binding is applied to the autocomplete popup used in the Search bar and in web page content -->
802 <binding id="browser-autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup">
803 <implementation>
804 <method name="openAutocompletePopup">
805 <parameter name="aInput"/>
806 <parameter name="aElement"/>
807 <body>
808 <![CDATA[
809 // initially the panel is hidden
810 // to avoid impacting startup / new window performance
811 aInput.popup.hidden = false;
813 // this method is defined on the base binding
814 this._openAutocompletePopup(aInput, aElement);
815 ]]></body>
816 </method>
818 <method name="onPopupClick">
819 <parameter name="aEvent"/>
820 <body><![CDATA[
821 // Ignore all right-clicks
822 if (aEvent.button == 2)
823 return;
825 var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
827 // Check for unmodified left-click, and use default behavior
828 if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
829 !aEvent.altKey && !aEvent.metaKey) {
830 controller.handleEnter(true);
831 return;
832 }
834 // Check for middle-click or modified clicks on the search bar
835 var searchBar = BrowserSearch.searchBar;
836 if (searchBar && searchBar.textbox == this.mInput) {
837 // Handle search bar popup clicks
838 var search = controller.getValueAt(this.selectedIndex);
840 // close the autocomplete popup and revert the entered search term
841 this.closePopup();
842 controller.handleEscape();
844 // Fill in the search bar's value
845 searchBar.value = search;
847 // open the search results according to the clicking subtlety
848 var where = whereToOpenLink(aEvent, false, true);
849 searchBar.doSearch(search, where);
850 }
851 ]]></body>
852 </method>
853 </implementation>
854 </binding>
856 <binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
857 <implementation>
858 <field name="_maxResults">0</field>
860 <field name="_bundle" readonly="true">
861 Cc["@mozilla.org/intl/stringbundle;1"].
862 getService(Ci.nsIStringBundleService).
863 createBundle("chrome://browser/locale/places/places.properties");
864 </field>
866 <property name="maxResults" readonly="true">
867 <getter>
868 <![CDATA[
869 if (!this._maxResults) {
870 var prefService =
871 Components.classes["@mozilla.org/preferences-service;1"]
872 .getService(Components.interfaces.nsIPrefBranch);
873 this._maxResults = prefService.getIntPref("browser.urlbar.maxRichResults");
874 }
875 return this._maxResults;
876 ]]>
877 </getter>
878 </property>
880 <method name="openAutocompletePopup">
881 <parameter name="aInput"/>
882 <parameter name="aElement"/>
883 <body>
884 <![CDATA[
885 // initially the panel is hidden
886 // to avoid impacting startup / new window performance
887 aInput.popup.hidden = false;
889 // this method is defined on the base binding
890 this._openAutocompletePopup(aInput, aElement);
891 ]]></body>
892 </method>
894 <method name="onPopupClick">
895 <parameter name="aEvent"/>
896 <body>
897 <![CDATA[
898 // Ignore right-clicks
899 if (aEvent.button == 2)
900 return;
902 var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
904 // Check for unmodified left-click, and use default behavior
905 if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
906 !aEvent.altKey && !aEvent.metaKey) {
907 controller.handleEnter(true);
908 return;
909 }
911 // Check for middle-click or modified clicks on the URL bar
912 if (gURLBar && this.mInput == gURLBar) {
913 var url = controller.getValueAt(this.selectedIndex);
915 // close the autocomplete popup and revert the entered address
916 this.closePopup();
917 controller.handleEscape();
919 // Check if this is meant to be an action
920 let action = this.mInput._parseActionUrl(url);
921 if (action) {
922 if (action.type == "switchtab")
923 url = action.param;
924 else
925 return;
926 }
928 // respect the usual clicking subtleties
929 openUILink(url, aEvent);
930 }
931 ]]>
932 </body>
933 </method>
935 <method name="createResultLabel">
936 <parameter name="aTitle"/>
937 <parameter name="aUrl"/>
938 <parameter name="aType"/>
939 <body>
940 <![CDATA[
941 var label = aTitle + " " + aUrl;
942 // convert aType (ex: "ac-result-type-<aType>") to text to be spoke aloud
943 // by screen readers. convert "tag" and "bookmark" to the localized versions,
944 // but don't do anything for "favicon" (the default)
945 if (aType != "favicon") {
946 label += " " + this._bundle.GetStringFromName(aType + "ResultLabel");
947 }
948 return label;
949 ]]>
950 </body>
951 </method>
953 </implementation>
954 </binding>
956 <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
957 <content align="start">
958 <xul:image class="popup-notification-icon"
959 xbl:inherits="popupid,src=icon"/>
960 <xul:vbox flex="1">
961 <xul:description class="popup-notification-description addon-progress-description"
962 xbl:inherits="xbl:text=label"/>
963 <xul:spacer flex="1"/>
964 <xul:hbox align="center">
965 <xul:progressmeter anonid="progressmeter" flex="1" mode="undetermined" class="popup-progress-meter"/>
966 <xul:button anonid="cancel" class="popup-progress-cancel" oncommand="document.getBindingParent(this).cancel()"/>
967 </xul:hbox>
968 <xul:label anonid="progresstext" class="popup-progress-label"/>
969 <xul:hbox class="popup-notification-button-container"
970 pack="end" align="center">
971 <xul:button anonid="button"
972 class="popup-notification-menubutton"
973 type="menu-button"
974 xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
975 <xul:menupopup anonid="menupopup"
976 xbl:inherits="oncommand=menucommand">
977 <children/>
978 <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
979 label="&closeNotificationItem.label;"
980 xbl:inherits="oncommand=closeitemcommand"/>
981 </xul:menupopup>
982 </xul:button>
983 </xul:hbox>
984 </xul:vbox>
985 <xul:vbox pack="start">
986 <xul:toolbarbutton anonid="closebutton"
987 class="messageCloseButton close-icon popup-notification-closebutton tabbable"
988 xbl:inherits="oncommand=closebuttoncommand"
989 tooltiptext="&closeNotification.tooltip;"/>
990 </xul:vbox>
991 </content>
992 <implementation>
993 <constructor><![CDATA[
994 this.cancelbtn.setAttribute("tooltiptext", gNavigatorBundle.getString("addonDownloadCancelTooltip"));
996 this.notification.options.installs.forEach(function(aInstall) {
997 aInstall.addListener(this);
998 }, this);
1000 // Calling updateProgress can sometimes cause this notification to be
1001 // removed in the middle of refreshing the notification panel which
1002 // makes the panel get refreshed again. Just initialise to the
1003 // undetermined state and then schedule a proper check at the next
1004 // opportunity
1005 this.setProgress(0, -1);
1006 this._updateProgressTimeout = setTimeout(this.updateProgress.bind(this), 0);
1007 ]]></constructor>
1009 <destructor><![CDATA[
1010 this.destroy();
1011 ]]></destructor>
1013 <field name="progressmeter" readonly="true">
1014 document.getAnonymousElementByAttribute(this, "anonid", "progressmeter");
1015 </field>
1016 <field name="progresstext" readonly="true">
1017 document.getAnonymousElementByAttribute(this, "anonid", "progresstext");
1018 </field>
1019 <field name="cancelbtn" readonly="true">
1020 document.getAnonymousElementByAttribute(this, "anonid", "cancel");
1021 </field>
1022 <field name="DownloadUtils" readonly="true">
1023 let utils = {};
1024 Components.utils.import("resource://gre/modules/DownloadUtils.jsm", utils);
1025 utils.DownloadUtils;
1026 </field>
1028 <method name="destroy">
1029 <body><![CDATA[
1030 this.notification.options.installs.forEach(function(aInstall) {
1031 aInstall.removeListener(this);
1032 }, this);
1033 clearTimeout(this._updateProgressTimeout);
1034 ]]></body>
1035 </method>
1037 <method name="setProgress">
1038 <parameter name="aProgress"/>
1039 <parameter name="aMaxProgress"/>
1040 <body><![CDATA[
1041 if (aMaxProgress == -1) {
1042 this.progressmeter.mode = "undetermined";
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>