|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
|
7 |
|
8 this.EXPORTED_SYMBOLS = ["CustomizableWidgets"]; |
|
9 |
|
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"); |
|
26 |
|
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 }); |
|
35 |
|
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"; |
|
39 |
|
40 let gModuleName = "[CustomizableWidgets]"; |
|
41 #include logging.js |
|
42 |
|
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 } |
|
68 |
|
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 } |
|
83 |
|
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 } |
|
95 |
|
96 function fillSubviewFromMenuItems(aMenuItems, aSubview) { |
|
97 let attrs = ["oncommand", "onclick", "label", "key", "disabled", |
|
98 "command", "observes", "hidden", "class", "origin", |
|
99 "image", "checked"]; |
|
100 |
|
101 let doc = aSubview.ownerDocument; |
|
102 let fragment = doc.createDocumentFragment(); |
|
103 for (let menuChild of aMenuItems) { |
|
104 if (menuChild.hidden) |
|
105 continue; |
|
106 |
|
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); |
|
118 |
|
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 } |
|
126 |
|
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 } |
|
153 |
|
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); |
|
159 |
|
160 while (aSubview.firstChild) { |
|
161 aSubview.firstChild.remove(); |
|
162 } |
|
163 |
|
164 parent.appendChild(aSubview); |
|
165 } |
|
166 |
|
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; |
|
178 |
|
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(); |
|
187 |
|
188 let items = doc.getElementById("PanelUI-historyItems"); |
|
189 // Clear previous history items. |
|
190 while (items.firstChild) { |
|
191 items.removeChild(items.firstChild); |
|
192 } |
|
193 |
|
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); |
|
207 |
|
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 }); |
|
231 |
|
232 let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs"); |
|
233 while (recentlyClosedTabs.firstChild) { |
|
234 recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild); |
|
235 } |
|
236 |
|
237 let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows"); |
|
238 while (recentlyClosedWindows.firstChild) { |
|
239 recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild); |
|
240 } |
|
241 |
|
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 } |
|
249 |
|
250 if (PlacesUIUtils.shouldEnableTabsFromOtherComputersMenuitem()) { |
|
251 tabsFromOtherComputers.removeAttribute("disabled"); |
|
252 } else { |
|
253 tabsFromOtherComputers.setAttribute("disabled", true); |
|
254 } |
|
255 #endif |
|
256 |
|
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); |
|
267 |
|
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; |
|
358 |
|
359 let menu = doc.getElementById("menuWebDeveloperPopup"); |
|
360 |
|
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")); |
|
366 |
|
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"); |
|
386 |
|
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]); |
|
394 |
|
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; |
|
446 |
|
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 }]; |
|
465 |
|
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); |
|
475 |
|
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 }); |
|
483 |
|
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 }; |
|
503 |
|
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); |
|
508 |
|
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); |
|
520 |
|
521 let listener = { |
|
522 onWidgetAdded: function(aWidgetId, aArea, aPosition) { |
|
523 if (aWidgetId != this.id) |
|
524 return; |
|
525 |
|
526 updateCombinedWidgetStyle(node, aArea, true); |
|
527 updateZoomResetButton(); |
|
528 |
|
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), |
|
538 |
|
539 onWidgetRemoved: function(aWidgetId, aPrevArea) { |
|
540 if (aWidgetId != this.id) |
|
541 return; |
|
542 |
|
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 } |
|
551 |
|
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), |
|
557 |
|
558 onWidgetReset: function(aWidgetNode) { |
|
559 if (aWidgetNode != node) |
|
560 return; |
|
561 updateCombinedWidgetStyle(node, this.currentArea, true); |
|
562 updateZoomResetButton(); |
|
563 }.bind(this), |
|
564 |
|
565 onWidgetMoved: function(aWidgetId, aArea) { |
|
566 if (aWidgetId != this.id) |
|
567 return; |
|
568 updateCombinedWidgetStyle(node, aArea, true); |
|
569 updateZoomResetButton(); |
|
570 }.bind(this), |
|
571 |
|
572 onWidgetInstanceRemoved: function(aWidgetId, aDoc) { |
|
573 if (aWidgetId != this.id || aDoc != aDocument) |
|
574 return; |
|
575 |
|
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), |
|
585 |
|
586 onCustomizeStart: function(aWindow) { |
|
587 if (aWindow.document == aDocument) { |
|
588 updateZoomResetButton(); |
|
589 } |
|
590 }, |
|
591 |
|
592 onCustomizeEnd: function(aWindow) { |
|
593 if (aWindow.document == aDocument) { |
|
594 updateZoomResetButton(); |
|
595 } |
|
596 }, |
|
597 |
|
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); |
|
606 |
|
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 }]; |
|
634 |
|
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); |
|
644 |
|
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 }); |
|
652 |
|
653 updateCombinedWidgetStyle(node, this.currentArea); |
|
654 |
|
655 let listener = { |
|
656 onWidgetAdded: function(aWidgetId, aArea, aPosition) { |
|
657 if (aWidgetId != this.id) |
|
658 return; |
|
659 updateCombinedWidgetStyle(node, aArea); |
|
660 }.bind(this), |
|
661 |
|
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), |
|
669 |
|
670 onWidgetReset: function(aWidgetNode) { |
|
671 if (aWidgetNode != node) |
|
672 return; |
|
673 updateCombinedWidgetStyle(node, this.currentArea); |
|
674 }.bind(this), |
|
675 |
|
676 onWidgetMoved: function(aWidgetId, aArea) { |
|
677 if (aWidgetId != this.id) |
|
678 return; |
|
679 updateCombinedWidgetStyle(node, aArea); |
|
680 }.bind(this), |
|
681 |
|
682 onWidgetInstanceRemoved: function(aWidgetId, aDoc) { |
|
683 if (aWidgetId != this.id || aDoc != aDocument) |
|
684 return; |
|
685 CustomizableUI.removeListener(listener); |
|
686 }.bind(this), |
|
687 |
|
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); |
|
696 |
|
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; |
|
709 |
|
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); |
|
724 |
|
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); |
|
754 |
|
755 containerElem.addEventListener("command", this.onCommand, false); |
|
756 |
|
757 let list = this.charsetInfo[aSection]; |
|
758 |
|
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); |
|
773 |
|
774 let pinnedContainer = aDocument.getElementById("PanelUI-characterEncodingView-pinned"); |
|
775 let charsetContainer = aDocument.getElementById("PanelUI-characterEncodingView-charsets"); |
|
776 let elements = [...(pinnedContainer.childNodes), ...(charsetContainer.childNodes)]; |
|
777 |
|
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) {} |
|
787 |
|
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; |
|
810 |
|
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 } |
|
834 |
|
835 let window = node.ownerDocument.defaultView; |
|
836 let section = node.section; |
|
837 let value = node.value; |
|
838 |
|
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; |
|
860 |
|
861 let updateButton = () => { |
|
862 if (this.maybeDisableMenu(document)) |
|
863 aNode.setAttribute("disabled", "true"); |
|
864 else |
|
865 aNode.removeAttribute("disabled"); |
|
866 }; |
|
867 |
|
868 if (this.currentArea == CustomizableUI.AREA_PANEL) { |
|
869 let panel = document.getElementById(kPanelId); |
|
870 panel.addEventListener("popupshowing", updateButton); |
|
871 } |
|
872 |
|
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; |
|
894 |
|
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 }]; |
|
913 |
|
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 |
|
937 |
|
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 } |
|
954 |
|
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"; |
|
960 |
|
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 |