Wed, 31 Dec 2014 13:27:57 +0100
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