browser/components/customizableui/src/CustomizableWidgets.jsm

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     6 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
     8 this.EXPORTED_SYMBOLS = ["CustomizableWidgets"];
    10 Cu.import("resource:///modules/CustomizableUI.jsm");
    11 Cu.import("resource://gre/modules/Services.jsm");
    12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    13 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
    14   "resource://gre/modules/PlacesUtils.jsm");
    15 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
    16   "resource:///modules/PlacesUIUtils.jsm");
    17 XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
    18   "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
    19 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
    20   "resource://gre/modules/ShortcutUtils.jsm");
    21 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
    22   "resource://gre/modules/CharsetMenu.jsm");
    23 XPCOMUtils.defineLazyServiceGetter(this, "CharsetManager",
    24                                    "@mozilla.org/charset-converter-manager;1",
    25                                    "nsICharsetConverterManager");
    27 XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
    28   const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
    29   return Services.strings.createBundle(kCharsetBundle);
    30 });
    31 XPCOMUtils.defineLazyGetter(this, "BrandBundle", function() {
    32   const kBrandBundle = "chrome://branding/locale/brand.properties";
    33   return Services.strings.createBundle(kBrandBundle);
    34 });
    36 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    37 const kPrefCustomizationDebug = "browser.uiCustomization.debug";
    38 const kWidePanelItemClass = "panel-wide-item";
    40 let gModuleName = "[CustomizableWidgets]";
    41 #include logging.js
    43 function setAttributes(aNode, aAttrs) {
    44   let doc = aNode.ownerDocument;
    45   for (let [name, value] of Iterator(aAttrs)) {
    46     if (!value) {
    47       if (aNode.hasAttribute(name))
    48         aNode.removeAttribute(name);
    49     } else {
    50       if (name == "shortcutId") {
    51         continue;
    52       }
    53       if (name == "label" || name == "tooltiptext") {
    54         let stringId = (typeof value == "string") ? value : name;
    55         let additionalArgs = [];
    56         if (aAttrs.shortcutId) {
    57           let shortcut = doc.getElementById(aAttrs.shortcutId);
    58           if (doc) {
    59             additionalArgs.push(ShortcutUtils.prettifyShortcut(shortcut));
    60           }
    61         }
    62         value = CustomizableUI.getLocalizedProperty({id: aAttrs.id}, stringId, additionalArgs);
    63       }
    64       aNode.setAttribute(name, value);
    65     }
    66   }
    67 }
    69 function updateCombinedWidgetStyle(aNode, aArea, aModifyCloseMenu) {
    70   let inPanel = (aArea == CustomizableUI.AREA_PANEL);
    71   let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1 toolbarbutton-combined";
    72   let attrs = {class: cls};
    73   if (aModifyCloseMenu) {
    74     attrs.closemenu = inPanel ? "none" : null;
    75   }
    76   attrs["cui-areatype"] = aArea ? CustomizableUI.getAreaType(aArea) : null;
    77   for (let i = 0, l = aNode.childNodes.length; i < l; ++i) {
    78     if (aNode.childNodes[i].localName == "separator")
    79       continue;
    80     setAttributes(aNode.childNodes[i], attrs);
    81   }
    82 }
    84 function addShortcut(aNode, aDocument, aItem) {
    85   let shortcutId = aNode.getAttribute("key");
    86   if (!shortcutId) {
    87     return;
    88   }
    89   let shortcut = aDocument.getElementById(shortcutId);
    90   if (!shortcut) {
    91     return;
    92   }
    93   aItem.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(shortcut));
    94 }
    96 function fillSubviewFromMenuItems(aMenuItems, aSubview) {
    97   let attrs = ["oncommand", "onclick", "label", "key", "disabled",
    98                "command", "observes", "hidden", "class", "origin",
    99                "image", "checked"];
   101   let doc = aSubview.ownerDocument;
   102   let fragment = doc.createDocumentFragment();
   103   for (let menuChild of aMenuItems) {
   104     if (menuChild.hidden)
   105       continue;
   107     let subviewItem;
   108     if (menuChild.localName == "menuseparator") {
   109       // Don't insert duplicate or leading separators. This can happen if there are
   110       // menus (which we don't copy) above the separator.
   111       if (!fragment.lastChild || fragment.lastChild.localName == "menuseparator") {
   112         continue;
   113       }
   114       subviewItem = doc.createElementNS(kNSXUL, "menuseparator");
   115     } else if (menuChild.localName == "menuitem") {
   116       subviewItem = doc.createElementNS(kNSXUL, "toolbarbutton");
   117       addShortcut(menuChild, doc, subviewItem);
   119       let item = menuChild;
   120       if (!item.hasAttribute("onclick")) {
   121         subviewItem.addEventListener("click", event => {
   122           let newEvent = new doc.defaultView.MouseEvent(event.type, event);
   123           item.dispatchEvent(newEvent);
   124         });
   125       }
   127       if (!item.hasAttribute("oncommand")) {
   128         subviewItem.addEventListener("command", event => {
   129           let newEvent = doc.createEvent("XULCommandEvent");
   130           newEvent.initCommandEvent(
   131             event.type, event.bubbles, event.cancelable, event.view,
   132             event.detail, event.ctrlKey, event.altKey, event.shiftKey,
   133             event.metaKey, event.sourceEvent);
   134           item.dispatchEvent(newEvent);
   135         });
   136       }
   137     } else {
   138       continue;
   139     }
   140     for (let attr of attrs) {
   141       let attrVal = menuChild.getAttribute(attr);
   142       if (attrVal)
   143         subviewItem.setAttribute(attr, attrVal);
   144     }
   145     // We do this after so the .subviewbutton class doesn't get overriden.
   146     if (menuChild.localName == "menuitem") {
   147       subviewItem.classList.add("subviewbutton");
   148     }
   149     fragment.appendChild(subviewItem);
   150   }
   151   aSubview.appendChild(fragment);
   152 }
   154 function clearSubview(aSubview) {
   155   let parent = aSubview.parentNode;
   156   // We'll take the container out of the document before cleaning it out
   157   // to avoid reflowing each time we remove something.
   158   parent.removeChild(aSubview);
   160   while (aSubview.firstChild) {
   161     aSubview.firstChild.remove();
   162   }
   164   parent.appendChild(aSubview);
   165 }
   167 const CustomizableWidgets = [{
   168     id: "history-panelmenu",
   169     type: "view",
   170     viewId: "PanelUI-history",
   171     shortcutId: "key_gotoHistory",
   172     tooltiptext: "history-panelmenu.tooltiptext2",
   173     defaultArea: CustomizableUI.AREA_PANEL,
   174     onViewShowing: function(aEvent) {
   175       // Populate our list of history
   176       const kMaxResults = 15;
   177       let doc = aEvent.detail.ownerDocument;
   179       let options = PlacesUtils.history.getNewQueryOptions();
   180       options.excludeQueries = true;
   181       options.includeHidden = false;
   182       options.resultType = options.RESULTS_AS_URI;
   183       options.queryType = options.QUERY_TYPE_HISTORY;
   184       options.sortingMode = options.SORT_BY_DATE_DESCENDING;
   185       options.maxResults = kMaxResults;
   186       let query = PlacesUtils.history.getNewQuery();
   188       let items = doc.getElementById("PanelUI-historyItems");
   189       // Clear previous history items.
   190       while (items.firstChild) {
   191         items.removeChild(items.firstChild);
   192       }
   194       PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
   195                          .asyncExecuteLegacyQueries([query], 1, options, {
   196         handleResult: function (aResultSet) {
   197           let onHistoryVisit = function (aUri, aEvent, aItem) {
   198             doc.defaultView.openUILink(aUri, aEvent);
   199             CustomizableUI.hidePanelForNode(aItem);
   200           };
   201           let fragment = doc.createDocumentFragment();
   202           for (let row, i = 0; (row = aResultSet.getNextRow()); i++) {
   203             try {
   204               let uri = row.getResultByIndex(1);
   205               let title = row.getResultByIndex(2);
   206               let icon = row.getResultByIndex(6);
   208               let item = doc.createElementNS(kNSXUL, "toolbarbutton");
   209               item.setAttribute("label", title || uri);
   210               item.setAttribute("targetURI", uri);
   211               item.setAttribute("class", "subviewbutton");
   212               item.addEventListener("click", function (aEvent) {
   213                 onHistoryVisit(uri, aEvent, item);
   214               });
   215               if (icon)
   216                 item.setAttribute("image", "moz-anno:favicon:" + icon);
   217               fragment.appendChild(item);
   218             } catch (e) {
   219               ERROR("Error while showing history subview: " + e);
   220             }
   221           }
   222           items.appendChild(fragment);
   223         },
   224         handleError: function (aError) {
   225           LOG("History view tried to show but had an error: " + aError);
   226         },
   227         handleCompletion: function (aReason) {
   228           LOG("History view is being shown!");
   229         },
   230       });
   232       let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
   233       while (recentlyClosedTabs.firstChild) {
   234         recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild);
   235       }
   237       let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
   238       while (recentlyClosedWindows.firstChild) {
   239         recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild);
   240       }
   242 #ifdef MOZ_SERVICES_SYNC
   243       let tabsFromOtherComputers = doc.getElementById("sync-tabs-menuitem2");
   244       if (PlacesUIUtils.shouldShowTabsFromOtherComputersMenuitem()) {
   245         tabsFromOtherComputers.removeAttribute("hidden");
   246       } else {
   247         tabsFromOtherComputers.setAttribute("hidden", true);
   248       }
   250       if (PlacesUIUtils.shouldEnableTabsFromOtherComputersMenuitem()) {
   251         tabsFromOtherComputers.removeAttribute("disabled");
   252       } else {
   253         tabsFromOtherComputers.setAttribute("disabled", true);
   254       }
   255 #endif
   257       let utils = RecentlyClosedTabsAndWindowsMenuUtils;
   258       let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true,
   259                                                "menuRestoreAllTabsSubview.label");
   260       let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
   261       let elementCount = tabsFragment.childElementCount;
   262       separator.hidden = !elementCount;
   263       while (--elementCount >= 0) {
   264         tabsFragment.children[elementCount].classList.add("subviewbutton");
   265       }
   266       recentlyClosedTabs.appendChild(tabsFragment);
   268       let windowsFragment = utils.getWindowsFragment(doc.defaultView, "toolbarbutton", true,
   269                                                      "menuRestoreAllWindowsSubview.label");
   270       separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
   271       elementCount = windowsFragment.childElementCount;
   272       separator.hidden = !elementCount;
   273       while (--elementCount >= 0) {
   274         windowsFragment.children[elementCount].classList.add("subviewbutton");
   275       }
   276       recentlyClosedWindows.appendChild(windowsFragment);
   277     },
   278     onCreated: function(aNode) {
   279       // Middle clicking recently closed items won't close the panel - cope:
   280       let onRecentlyClosedClick = function(aEvent) {
   281         if (aEvent.button == 1) {
   282           CustomizableUI.hidePanelForNode(this);
   283         }
   284       };
   285       let doc = aNode.ownerDocument;
   286       let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
   287       let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
   288       recentlyClosedTabs.addEventListener("click", onRecentlyClosedClick);
   289       recentlyClosedWindows.addEventListener("click", onRecentlyClosedClick);
   290     },
   291     onViewHiding: function(aEvent) {
   292       LOG("History view is being hidden!");
   293     }
   294   }, {
   295     id: "privatebrowsing-button",
   296     shortcutId: "key_privatebrowsing",
   297     defaultArea: CustomizableUI.AREA_PANEL,
   298     onCommand: function(e) {
   299       if (e.target && e.target.ownerDocument && e.target.ownerDocument.defaultView) {
   300         let win = e.target.ownerDocument.defaultView;
   301         if (typeof win.OpenBrowserWindow == "function") {
   302           win.OpenBrowserWindow({private: true});
   303         }
   304       }
   305     }
   306   }, {
   307     id: "save-page-button",
   308     shortcutId: "key_savePage",
   309     tooltiptext: "save-page-button.tooltiptext3",
   310     defaultArea: CustomizableUI.AREA_PANEL,
   311     onCommand: function(aEvent) {
   312       let win = aEvent.target &&
   313                 aEvent.target.ownerDocument &&
   314                 aEvent.target.ownerDocument.defaultView;
   315       if (win && typeof win.saveDocument == "function") {
   316         win.saveDocument(win.content.document);
   317       }
   318     }
   319   }, {
   320     id: "find-button",
   321     shortcutId: "key_find",
   322     tooltiptext: "find-button.tooltiptext3",
   323     defaultArea: CustomizableUI.AREA_PANEL,
   324     onCommand: function(aEvent) {
   325       let win = aEvent.target &&
   326                 aEvent.target.ownerDocument &&
   327                 aEvent.target.ownerDocument.defaultView;
   328       if (win && win.gFindBar) {
   329         win.gFindBar.onFindCommand();
   330       }
   331     }
   332   }, {
   333     id: "open-file-button",
   334     shortcutId: "openFileKb",
   335     tooltiptext: "open-file-button.tooltiptext3",
   336     defaultArea: CustomizableUI.AREA_PANEL,
   337     onCommand: function(aEvent) {
   338       let win = aEvent.target
   339                 && aEvent.target.ownerDocument
   340                 && aEvent.target.ownerDocument.defaultView;
   341       if (win && typeof win.BrowserOpenFileWindow == "function") {
   342         win.BrowserOpenFileWindow();
   343       }
   344     }
   345   }, {
   346     id: "developer-button",
   347     type: "view",
   348     viewId: "PanelUI-developer",
   349     shortcutId: "key_devToolboxMenuItem",
   350     tooltiptext: "developer-button.tooltiptext2",
   351     defaultArea: CustomizableUI.AREA_PANEL,
   352     onViewShowing: function(aEvent) {
   353       // Populate the subview with whatever menuitems are in the developer
   354       // menu. We skip menu elements, because the menu panel has no way
   355       // of dealing with those right now.
   356       let doc = aEvent.target.ownerDocument;
   357       let win = doc.defaultView;
   359       let menu = doc.getElementById("menuWebDeveloperPopup");
   361       let itemsToDisplay = [...menu.children];
   362       // Hardcode the addition of the "work offline" menuitem at the bottom:
   363       itemsToDisplay.push({localName: "menuseparator", getAttribute: () => {}});
   364       itemsToDisplay.push(doc.getElementById("goOfflineMenuitem"));
   365       fillSubviewFromMenuItems(itemsToDisplay, doc.getElementById("PanelUI-developerItems"));
   367     },
   368     onViewHiding: function(aEvent) {
   369       let doc = aEvent.target.ownerDocument;
   370       clearSubview(doc.getElementById("PanelUI-developerItems"));
   371     }
   372   }, {
   373     id: "sidebar-button",
   374     type: "view",
   375     viewId: "PanelUI-sidebar",
   376     tooltiptext: "sidebar-button.tooltiptext2",
   377     onViewShowing: function(aEvent) {
   378       // Largely duplicated from the developer-button above with a couple minor
   379       // alterations.
   380       // Populate the subview with whatever menuitems are in the
   381       // sidebar menu. We skip menu elements, because the menu panel has no way
   382       // of dealing with those right now.
   383       let doc = aEvent.target.ownerDocument;
   384       let win = doc.defaultView;
   385       let menu = doc.getElementById("viewSidebarMenu");
   387       // First clear any existing menuitems then populate. Social sidebar
   388       // options may not have been added yet, so we do that here. Add it to the
   389       // standard menu first, then copy all sidebar options to the panel.
   390       win.SocialSidebar.clearProviderMenus();
   391       let providerMenuSeps = menu.getElementsByClassName("social-provider-menu");
   392       if (providerMenuSeps.length > 0)
   393         win.SocialSidebar.populateProviderMenu(providerMenuSeps[0]);
   395       fillSubviewFromMenuItems([...menu.children], doc.getElementById("PanelUI-sidebarItems"));
   396     },
   397     onViewHiding: function(aEvent) {
   398       let doc = aEvent.target.ownerDocument;
   399       clearSubview(doc.getElementById("PanelUI-sidebarItems"));
   400     }
   401   }, {
   402     id: "add-ons-button",
   403     shortcutId: "key_openAddons",
   404     tooltiptext: "add-ons-button.tooltiptext3",
   405     defaultArea: CustomizableUI.AREA_PANEL,
   406     onCommand: function(aEvent) {
   407       let win = aEvent.target &&
   408                 aEvent.target.ownerDocument &&
   409                 aEvent.target.ownerDocument.defaultView;
   410       if (win && typeof win.BrowserOpenAddonsMgr == "function") {
   411         win.BrowserOpenAddonsMgr();
   412       }
   413     }
   414   }, {
   415     id: "preferences-button",
   416     defaultArea: CustomizableUI.AREA_PANEL,
   417 #ifdef XP_WIN
   418     label: "preferences-button.labelWin",
   419     tooltiptext: "preferences-button.tooltipWin2",
   420 #else
   421 #ifdef XP_MACOSX
   422     tooltiptext: "preferences-button.tooltiptext.withshortcut",
   423     shortcutId: "key_preferencesCmdMac",
   424 #else
   425     tooltiptext: "preferences-button.tooltiptext2",
   426 #endif
   427 #endif
   428     onCommand: function(aEvent) {
   429       let win = aEvent.target &&
   430                 aEvent.target.ownerDocument &&
   431                 aEvent.target.ownerDocument.defaultView;
   432       if (win && typeof win.openPreferences == "function") {
   433         win.openPreferences();
   434       }
   435     }
   436   }, {
   437     id: "zoom-controls",
   438     type: "custom",
   439     tooltiptext: "zoom-controls.tooltiptext2",
   440     defaultArea: CustomizableUI.AREA_PANEL,
   441     onBuild: function(aDocument) {
   442       const kPanelId = "PanelUI-popup";
   443       let areaType = CustomizableUI.getAreaType(this.currentArea);
   444       let inPanel = areaType == CustomizableUI.TYPE_MENU_PANEL;
   445       let inToolbar = areaType == CustomizableUI.TYPE_TOOLBAR;
   447       let buttons = [{
   448         id: "zoom-out-button",
   449         command: "cmd_fullZoomReduce",
   450         label: true,
   451         tooltiptext: "tooltiptext2",
   452         shortcutId: "key_fullZoomReduce",
   453       }, {
   454         id: "zoom-reset-button",
   455         command: "cmd_fullZoomReset",
   456         tooltiptext: "tooltiptext2",
   457         shortcutId: "key_fullZoomReset",
   458       }, {
   459         id: "zoom-in-button",
   460         command: "cmd_fullZoomEnlarge",
   461         label: true,
   462         tooltiptext: "tooltiptext2",
   463         shortcutId: "key_fullZoomEnlarge",
   464       }];
   466       let node = aDocument.createElementNS(kNSXUL, "toolbaritem");
   467       node.setAttribute("id", "zoom-controls");
   468       node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
   469       node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
   470       // Set this as an attribute in addition to the property to make sure we can style correctly.
   471       node.setAttribute("removable", "true");
   472       node.classList.add("chromeclass-toolbar-additional");
   473       node.classList.add("toolbaritem-combined-buttons");
   474       node.classList.add(kWidePanelItemClass);
   476       buttons.forEach(function(aButton, aIndex) {
   477         if (aIndex != 0)
   478           node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
   479         let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
   480         setAttributes(btnNode, aButton);
   481         node.appendChild(btnNode);
   482       });
   484       // The middle node is the 'Reset Zoom' button.
   485       let zoomResetButton = node.childNodes[2];
   486       let window = aDocument.defaultView;
   487       function updateZoomResetButton() {
   488         let updateDisplay = true;
   489         // Label should always show 100% in customize mode, so don't update:
   490         if (aDocument.documentElement.hasAttribute("customizing")) {
   491           updateDisplay = false;
   492         }
   493         //XXXgijs in some tests we get called very early, and there's no docShell on the
   494         // tabbrowser. This breaks the zoom toolkit code (see bug 897410). Don't let that happen:
   495         let zoomFactor = 100;
   496         try {
   497           zoomFactor = Math.round(window.ZoomManager.zoom * 100);
   498         } catch (e) {}
   499         zoomResetButton.setAttribute("label", CustomizableUI.getLocalizedProperty(
   500           buttons[1], "label", [updateDisplay ? zoomFactor : 100]
   501         ));
   502       };
   504       // Register ourselves with the service so we know when the zoom prefs change.
   505       Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomChange", false);
   506       Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomReset", false);
   507       Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:location-change", false);
   509       if (inPanel) {
   510         let panel = aDocument.getElementById(kPanelId);
   511         panel.addEventListener("popupshowing", updateZoomResetButton);
   512       } else {
   513         if (inToolbar) {
   514           let container = window.gBrowser.tabContainer;
   515           container.addEventListener("TabSelect", updateZoomResetButton);
   516         }
   517         updateZoomResetButton();
   518       }
   519       updateCombinedWidgetStyle(node, this.currentArea, true);
   521       let listener = {
   522         onWidgetAdded: function(aWidgetId, aArea, aPosition) {
   523           if (aWidgetId != this.id)
   524             return;
   526           updateCombinedWidgetStyle(node, aArea, true);
   527           updateZoomResetButton();
   529           let areaType = CustomizableUI.getAreaType(aArea);
   530           if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
   531             let panel = aDocument.getElementById(kPanelId);
   532             panel.addEventListener("popupshowing", updateZoomResetButton);
   533           } else if (areaType == CustomizableUI.TYPE_TOOLBAR) {
   534             let container = window.gBrowser.tabContainer;
   535             container.addEventListener("TabSelect", updateZoomResetButton);
   536           }
   537         }.bind(this),
   539         onWidgetRemoved: function(aWidgetId, aPrevArea) {
   540           if (aWidgetId != this.id)
   541             return;
   543           let areaType = CustomizableUI.getAreaType(aPrevArea);
   544           if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
   545             let panel = aDocument.getElementById(kPanelId);
   546             panel.removeEventListener("popupshowing", updateZoomResetButton);
   547           } else if (areaType == CustomizableUI.TYPE_TOOLBAR) {
   548             let container = window.gBrowser.tabContainer;
   549             container.removeEventListener("TabSelect", updateZoomResetButton);
   550           }
   552           // When a widget is demoted to the palette ('removed'), it's visual
   553           // style should change.
   554           updateCombinedWidgetStyle(node, null, true);
   555           updateZoomResetButton();
   556         }.bind(this),
   558         onWidgetReset: function(aWidgetNode) {
   559           if (aWidgetNode != node)
   560             return;
   561           updateCombinedWidgetStyle(node, this.currentArea, true);
   562           updateZoomResetButton();
   563         }.bind(this),
   565         onWidgetMoved: function(aWidgetId, aArea) {
   566           if (aWidgetId != this.id)
   567             return;
   568           updateCombinedWidgetStyle(node, aArea, true);
   569           updateZoomResetButton();
   570         }.bind(this),
   572         onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
   573           if (aWidgetId != this.id || aDoc != aDocument)
   574             return;
   576           CustomizableUI.removeListener(listener);
   577           Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomChange");
   578           Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomReset");
   579           Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:location-change");
   580           let panel = aDoc.getElementById(kPanelId);
   581           panel.removeEventListener("popupshowing", updateZoomResetButton);
   582           let container = aDoc.defaultView.gBrowser.tabContainer;
   583           container.removeEventListener("TabSelect", updateZoomResetButton);
   584         }.bind(this),
   586         onCustomizeStart: function(aWindow) {
   587           if (aWindow.document == aDocument) {
   588             updateZoomResetButton();
   589           }
   590         },
   592         onCustomizeEnd: function(aWindow) {
   593           if (aWindow.document == aDocument) {
   594             updateZoomResetButton();
   595           }
   596         },
   598         onWidgetDrag: function(aWidgetId, aArea) {
   599           if (aWidgetId != this.id)
   600             return;
   601           aArea = aArea || this.currentArea;
   602           updateCombinedWidgetStyle(node, aArea, true);
   603         }.bind(this)
   604       };
   605       CustomizableUI.addListener(listener);
   607       return node;
   608     }
   609   }, {
   610     id: "edit-controls",
   611     type: "custom",
   612     tooltiptext: "edit-controls.tooltiptext2",
   613     defaultArea: CustomizableUI.AREA_PANEL,
   614     onBuild: function(aDocument) {
   615       let buttons = [{
   616         id: "cut-button",
   617         command: "cmd_cut",
   618         label: true,
   619         tooltiptext: "tooltiptext2",
   620         shortcutId: "key_cut",
   621       }, {
   622         id: "copy-button",
   623         command: "cmd_copy",
   624         label: true,
   625         tooltiptext: "tooltiptext2",
   626         shortcutId: "key_copy",
   627       }, {
   628         id: "paste-button",
   629         command: "cmd_paste",
   630         label: true,
   631         tooltiptext: "tooltiptext2",
   632         shortcutId: "key_paste",
   633       }];
   635       let node = aDocument.createElementNS(kNSXUL, "toolbaritem");
   636       node.setAttribute("id", "edit-controls");
   637       node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
   638       node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
   639       // Set this as an attribute in addition to the property to make sure we can style correctly.
   640       node.setAttribute("removable", "true");
   641       node.classList.add("chromeclass-toolbar-additional");
   642       node.classList.add("toolbaritem-combined-buttons");
   643       node.classList.add(kWidePanelItemClass);
   645       buttons.forEach(function(aButton, aIndex) {
   646         if (aIndex != 0)
   647           node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
   648         let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
   649         setAttributes(btnNode, aButton);
   650         node.appendChild(btnNode);
   651       });
   653       updateCombinedWidgetStyle(node, this.currentArea);
   655       let listener = {
   656         onWidgetAdded: function(aWidgetId, aArea, aPosition) {
   657           if (aWidgetId != this.id)
   658             return;
   659           updateCombinedWidgetStyle(node, aArea);
   660         }.bind(this),
   662         onWidgetRemoved: function(aWidgetId, aPrevArea) {
   663           if (aWidgetId != this.id)
   664             return;
   665           // When a widget is demoted to the palette ('removed'), it's visual
   666           // style should change.
   667           updateCombinedWidgetStyle(node);
   668         }.bind(this),
   670         onWidgetReset: function(aWidgetNode) {
   671           if (aWidgetNode != node)
   672             return;
   673           updateCombinedWidgetStyle(node, this.currentArea);
   674         }.bind(this),
   676         onWidgetMoved: function(aWidgetId, aArea) {
   677           if (aWidgetId != this.id)
   678             return;
   679           updateCombinedWidgetStyle(node, aArea);
   680         }.bind(this),
   682         onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
   683           if (aWidgetId != this.id || aDoc != aDocument)
   684             return;
   685           CustomizableUI.removeListener(listener);
   686         }.bind(this),
   688         onWidgetDrag: function(aWidgetId, aArea) {
   689           if (aWidgetId != this.id)
   690             return;
   691           aArea = aArea || this.currentArea;
   692           updateCombinedWidgetStyle(node, aArea);
   693         }.bind(this)
   694       };
   695       CustomizableUI.addListener(listener);
   697       return node;
   698     }
   699   },
   700   {
   701     id: "feed-button",
   702     type: "view",
   703     viewId: "PanelUI-feeds",
   704     tooltiptext: "feed-button.tooltiptext2",
   705     defaultArea: CustomizableUI.AREA_PANEL,
   706     onClick: function(aEvent) {
   707       let win = aEvent.target.ownerDocument.defaultView;
   708       let feeds = win.gBrowser.selectedBrowser.feeds;
   710       // Here, we only care about the case where we have exactly 1 feed and the
   711       // user clicked...
   712       let isClick = (aEvent.button == 0 || aEvent.button == 1);
   713       if (feeds && feeds.length == 1 && isClick) {
   714         aEvent.preventDefault();
   715         aEvent.stopPropagation();
   716         win.FeedHandler.subscribeToFeed(feeds[0].href, aEvent);
   717         CustomizableUI.hidePanelForNode(aEvent.target);
   718       }
   719     },
   720     onViewShowing: function(aEvent) {
   721       let doc = aEvent.detail.ownerDocument;
   722       let container = doc.getElementById("PanelUI-feeds");
   723       let gotView = doc.defaultView.FeedHandler.buildFeedList(container, true);
   725       // For no feeds or only a single one, don't show the panel.
   726       if (!gotView) {
   727         aEvent.preventDefault();
   728         aEvent.stopPropagation();
   729         return;
   730       }
   731     },
   732     onCreated: function(node) {
   733       let win = node.ownerDocument.defaultView;
   734       let selectedBrowser = win.gBrowser.selectedBrowser;
   735       let feeds = selectedBrowser && selectedBrowser.feeds;
   736       if (!feeds || !feeds.length) {
   737         node.setAttribute("disabled", "true");
   738       }
   739     }
   740   }, {
   741     id: "characterencoding-button",
   742     type: "view",
   743     viewId: "PanelUI-characterEncodingView",
   744     tooltiptext: "characterencoding-button.tooltiptext2",
   745     defaultArea: CustomizableUI.AREA_PANEL,
   746     maybeDisableMenu: function(aDocument) {
   747       let window = aDocument.defaultView;
   748       return !(window.gBrowser &&
   749                window.gBrowser.docShell &&
   750                window.gBrowser.docShell.mayEnableCharacterEncodingMenu);
   751     },
   752     populateList: function(aDocument, aContainerId, aSection) {
   753       let containerElem = aDocument.getElementById(aContainerId);
   755       containerElem.addEventListener("command", this.onCommand, false);
   757       let list = this.charsetInfo[aSection];
   759       for (let item of list) {
   760         let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
   761         elem.setAttribute("label", item.label);
   762         elem.setAttribute("type", "checkbox");
   763         elem.section = aSection;
   764         elem.value = item.value;
   765         elem.setAttribute("class", "subviewbutton");
   766         containerElem.appendChild(elem);
   767       }
   768     },
   769     updateCurrentCharset: function(aDocument) {
   770       let content = aDocument.defaultView.content;
   771       let currentCharset = content && content.document && content.document.characterSet;
   772       currentCharset = CharsetMenu.foldCharset(currentCharset);
   774       let pinnedContainer = aDocument.getElementById("PanelUI-characterEncodingView-pinned");
   775       let charsetContainer = aDocument.getElementById("PanelUI-characterEncodingView-charsets");
   776       let elements = [...(pinnedContainer.childNodes), ...(charsetContainer.childNodes)];
   778       this._updateElements(elements, currentCharset);
   779     },
   780     updateCurrentDetector: function(aDocument) {
   781       let detectorContainer = aDocument.getElementById("PanelUI-characterEncodingView-autodetect");
   782       let currentDetector;
   783       try {
   784         currentDetector = Services.prefs.getComplexValue(
   785           "intl.charset.detector", Ci.nsIPrefLocalizedString).data;
   786       } catch (e) {}
   788       this._updateElements(detectorContainer.childNodes, currentDetector);
   789     },
   790     _updateElements: function(aElements, aCurrentItem) {
   791       if (!aElements.length) {
   792         return;
   793       }
   794       let disabled = this.maybeDisableMenu(aElements[0].ownerDocument);
   795       for (let elem of aElements) {
   796         if (disabled) {
   797           elem.setAttribute("disabled", "true");
   798         } else {
   799           elem.removeAttribute("disabled");
   800         }
   801         if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
   802           elem.setAttribute("checked", "true");
   803         } else {
   804           elem.removeAttribute("checked");
   805         }
   806       }
   807     },
   808     onViewShowing: function(aEvent) {
   809       let document = aEvent.target.ownerDocument;
   811       let autoDetectLabelId = "PanelUI-characterEncodingView-autodetect-label";
   812       let autoDetectLabel = document.getElementById(autoDetectLabelId);
   813       if (!autoDetectLabel.hasAttribute("value")) {
   814         let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
   815         autoDetectLabel.setAttribute("value", label);
   816         this.populateList(document,
   817                           "PanelUI-characterEncodingView-pinned",
   818                           "pinnedCharsets");
   819         this.populateList(document,
   820                           "PanelUI-characterEncodingView-charsets",
   821                           "otherCharsets");
   822         this.populateList(document,
   823                           "PanelUI-characterEncodingView-autodetect",
   824                           "detectors");
   825       }
   826       this.updateCurrentDetector(document);
   827       this.updateCurrentCharset(document);
   828     },
   829     onCommand: function(aEvent) {
   830       let node = aEvent.target;
   831       if (!node.hasAttribute || !node.section) {
   832         return;
   833       }
   835       let window = node.ownerDocument.defaultView;
   836       let section = node.section;
   837       let value = node.value;
   839       // The behavior as implemented here is directly based off of the
   840       // `MultiplexHandler()` method in browser.js.
   841       if (section != "detectors") {
   842         window.BrowserSetForcedCharacterSet(value);
   843       } else {
   844         // Set the detector pref.
   845         try {
   846           let str = Cc["@mozilla.org/supports-string;1"]
   847                       .createInstance(Ci.nsISupportsString);
   848           str.data = value;
   849           Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
   850         } catch (e) {
   851           Cu.reportError("Failed to set the intl.charset.detector preference.");
   852         }
   853         // Prepare a browser page reload with a changed charset.
   854         window.BrowserCharsetReload();
   855       }
   856     },
   857     onCreated: function(aNode) {
   858       const kPanelId = "PanelUI-popup";
   859       let document = aNode.ownerDocument;
   861       let updateButton = () => {
   862         if (this.maybeDisableMenu(document))
   863           aNode.setAttribute("disabled", "true");
   864         else
   865           aNode.removeAttribute("disabled");
   866       };
   868       if (this.currentArea == CustomizableUI.AREA_PANEL) {
   869         let panel = document.getElementById(kPanelId);
   870         panel.addEventListener("popupshowing", updateButton);
   871       }
   873       let listener = {
   874         onWidgetAdded: (aWidgetId, aArea) => {
   875           if (aWidgetId != this.id)
   876             return;
   877           if (aArea == CustomizableUI.AREA_PANEL) {
   878             let panel = document.getElementById(kPanelId);
   879             panel.addEventListener("popupshowing", updateButton);
   880           }
   881         },
   882         onWidgetRemoved: (aWidgetId, aPrevArea) => {
   883           if (aWidgetId != this.id)
   884             return;
   885           aNode.removeAttribute("disabled");
   886           if (aPrevArea == CustomizableUI.AREA_PANEL) {
   887             let panel = document.getElementById(kPanelId);
   888             panel.removeEventListener("popupshowing", updateButton);
   889           }
   890         },
   891         onWidgetInstanceRemoved: (aWidgetId, aDoc) => {
   892           if (aWidgetId != this.id || aDoc != document)
   893             return;
   895           CustomizableUI.removeListener(listener);
   896           let panel = aDoc.getElementById(kPanelId);
   897           panel.removeEventListener("popupshowing", updateButton);
   898         }
   899       };
   900       CustomizableUI.addListener(listener);
   901       if (!this.charsetInfo) {
   902         this.charsetInfo = CharsetMenu.getData();
   903       }
   904     }
   905   }, {
   906     id: "email-link-button",
   907     tooltiptext: "email-link-button.tooltiptext3",
   908     onCommand: function(aEvent) {
   909       let win = aEvent.view;
   910       win.MailIntegration.sendLinkForWindow(win.content);
   911     }
   912   }];
   914 #ifdef XP_WIN
   915 #ifdef MOZ_METRO
   916 if (Services.metro && Services.metro.supported) {
   917   let widgetArgs = {tooltiptext: "switch-to-metro-button2.tooltiptext"};
   918   let brandShortName = BrandBundle.GetStringFromName("brandShortName");
   919   let metroTooltip = CustomizableUI.getLocalizedProperty(widgetArgs, "tooltiptext",
   920                                                          [brandShortName]);
   921   CustomizableWidgets.push({
   922     id: "switch-to-metro-button",
   923     label: "switch-to-metro-button2.label",
   924     tooltiptext: metroTooltip,
   925     defaultArea: CustomizableUI.AREA_PANEL,
   926     showInPrivateBrowsing: false, /* See bug 928068 */
   927     onCommand: function(aEvent) {
   928       let win = aEvent.view;
   929       if (win && typeof win.SwitchToMetro == "function") {
   930         win.SwitchToMetro();
   931       }
   932     }
   933   });
   934 }
   935 #endif
   936 #endif
   938 #ifdef NIGHTLY_BUILD
   939 /**
   940  * The e10s button's purpose is to lower the barrier of entry
   941  * for our Nightly testers to use e10s windows. We'll be removing it
   942  * once remote tabs are enabled. This button should never ever make it
   943  * to production. If it does, that'd be bad, and we should all feel bad.
   944  */
   945 if (Services.prefs.getBoolPref("browser.tabs.remote")) {
   946   let getCommandFunction = function(aOpenRemote) {
   947     return function(aEvent) {
   948       let win = aEvent.view;
   949       if (win && typeof win.OpenBrowserWindow == "function") {
   950         win.OpenBrowserWindow({remote: aOpenRemote});
   951       }
   952     };
   953   }
   955   let openRemote = !Services.prefs.getBoolPref("browser.tabs.remote.autostart");
   956   // Like the XUL menuitem counterparts, we hard-code these strings in because
   957   // this button should never roll into production.
   958   let buttonLabel = openRemote ? "New e10s Window"
   959                                : "New Non-e10s Window";
   961   CustomizableWidgets.push({
   962     id: "e10s-button",
   963     label: buttonLabel,
   964     tooltiptext: buttonLabel,
   965     defaultArea: CustomizableUI.AREA_PANEL,
   966     onCommand: getCommandFunction(openRemote),
   967   });
   968 }
   969 #endif

mercurial