Source code

Revision control

Other Tools

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
7
var EXPORTED_SYMBOLS = ["CustomizableWidgets"];
8
9
const { CustomizableUI } = ChromeUtils.import(
11
);
12
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13
const { XPCOMUtils } = ChromeUtils.import(
15
);
16
const { AppConstants } = ChromeUtils.import(
18
);
19
20
XPCOMUtils.defineLazyModuleGetters(this, {
22
RecentlyClosedTabsAndWindowsMenuUtils:
29
});
30
31
XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
32
const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
33
return Services.strings.createBundle(kCharsetBundle);
34
});
35
36
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
37
38
XPCOMUtils.defineLazyGetter(this, "log", () => {
39
let scope = {};
40
ChromeUtils.import("resource://gre/modules/Console.jsm", scope);
41
let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
42
let consoleOptions = {
43
maxLogLevel: debug ? "all" : "log",
44
prefix: "CustomizableWidgets",
45
};
46
return new scope.ConsoleAPI(consoleOptions);
47
});
48
49
function setAttributes(aNode, aAttrs) {
50
let doc = aNode.ownerDocument;
51
for (let [name, value] of Object.entries(aAttrs)) {
52
if (!value) {
53
if (aNode.hasAttribute(name)) {
54
aNode.removeAttribute(name);
55
}
56
} else {
57
if (name == "shortcutId") {
58
continue;
59
}
60
if (name == "label" || name == "tooltiptext") {
61
let stringId = typeof value == "string" ? value : name;
62
let additionalArgs = [];
63
if (aAttrs.shortcutId) {
64
let shortcut = doc.getElementById(aAttrs.shortcutId);
65
if (shortcut) {
66
additionalArgs.push(ShortcutUtils.prettifyShortcut(shortcut));
67
}
68
}
69
value = CustomizableUI.getLocalizedProperty(
70
{ id: aAttrs.id },
71
stringId,
72
additionalArgs
73
);
74
}
75
aNode.setAttribute(name, value);
76
}
77
}
78
}
79
80
const CustomizableWidgets = [
81
{
82
id: "history-panelmenu",
83
type: "view",
84
viewId: "PanelUI-history",
85
shortcutId: "key_gotoHistory",
86
tooltiptext: "history-panelmenu.tooltiptext2",
87
recentlyClosedTabsPanel: "appMenu-library-recentlyClosedTabs",
88
recentlyClosedWindowsPanel: "appMenu-library-recentlyClosedWindows",
89
handleEvent(event) {
90
switch (event.type) {
91
case "PanelMultiViewHidden":
92
this.onPanelMultiViewHidden(event);
93
break;
94
case "ViewShowing":
95
this.onSubViewShowing(event);
96
break;
97
default:
98
throw new Error(`Unsupported event for '${this.id}'`);
99
}
100
},
101
onViewShowing(event) {
102
if (this._panelMenuView) {
103
return;
104
}
105
106
let panelview = event.target;
107
let document = panelview.ownerDocument;
108
let window = document.defaultView;
109
110
// We restrict the amount of results to 42. Not 50, but 42. Why? Because 42.
111
let query =
112
"place:queryType=" +
113
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY +
114
"&sort=" +
115
Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING +
116
"&maxResults=42&excludeQueries=1";
117
118
this._panelMenuView = new window.PlacesPanelview(
119
document.getElementById("appMenu_historyMenu"),
120
panelview,
121
query
122
);
123
// When either of these sub-subviews show, populate them with recently closed
124
// objects data.
125
document
126
.getElementById(this.recentlyClosedTabsPanel)
127
.addEventListener("ViewShowing", this);
128
document
129
.getElementById(this.recentlyClosedWindowsPanel)
130
.addEventListener("ViewShowing", this);
131
// When the popup is hidden (thus the panelmultiview node as well), make
132
// sure to stop listening to PlacesDatabase updates.
133
panelview.panelMultiView.addEventListener("PanelMultiViewHidden", this);
134
},
135
onViewHiding(event) {
136
log.debug("History view is being hidden!");
137
},
138
onPanelMultiViewHidden(event) {
139
let panelMultiView = event.target;
140
let document = panelMultiView.ownerDocument;
141
if (this._panelMenuView) {
142
this._panelMenuView.uninit();
143
delete this._panelMenuView;
144
document
145
.getElementById(this.recentlyClosedTabsPanel)
146
.removeEventListener("ViewShowing", this);
147
document
148
.getElementById(this.recentlyClosedWindowsPanel)
149
.removeEventListener("ViewShowing", this);
150
}
151
panelMultiView.removeEventListener("PanelMultiViewHidden", this);
152
},
153
onSubViewShowing(event) {
154
let panelview = event.target;
155
let document = event.target.ownerDocument;
156
let window = document.defaultView;
157
let viewType =
158
panelview.id == this.recentlyClosedTabsPanel ? "Tabs" : "Windows";
159
160
this._panelMenuView.clearAllContents(panelview);
161
162
let utils = RecentlyClosedTabsAndWindowsMenuUtils;
163
let method = `get${viewType}Fragment`;
164
let fragment = utils[method](window, "toolbarbutton", true);
165
let elementCount = fragment.childElementCount;
166
this._panelMenuView._setEmptyPopupStatus(panelview, !elementCount);
167
if (!elementCount) {
168
return;
169
}
170
171
let body = document.createXULElement("vbox");
172
body.className = "panel-subview-body";
173
body.appendChild(fragment);
174
let footer;
175
while (--elementCount >= 0) {
176
let element = body.children[elementCount];
177
CustomizableUI.addShortcut(element);
178
element.classList.add("subviewbutton");
179
if (element.classList.contains("restoreallitem")) {
180
footer = element;
181
element.classList.add("panel-subview-footer");
182
} else {
183
element.classList.add("subviewbutton-iconic", "bookmark-item");
184
}
185
}
186
panelview.appendChild(body);
187
panelview.appendChild(footer);
188
},
189
},
190
{
191
id: "save-page-button",
192
shortcutId: "key_savePage",
193
tooltiptext: "save-page-button.tooltiptext3",
194
onCommand(aEvent) {
195
let win = aEvent.target.ownerGlobal;
196
win.saveBrowser(win.gBrowser.selectedBrowser);
197
},
198
},
199
{
200
id: "find-button",
201
shortcutId: "key_find",
202
tooltiptext: "find-button.tooltiptext3",
203
onCommand(aEvent) {
204
let win = aEvent.target.ownerGlobal;
205
if (win.gLazyFindCommand) {
206
win.gLazyFindCommand("onFindCommand");
207
}
208
},
209
},
210
{
211
id: "open-file-button",
212
shortcutId: "openFileKb",
213
tooltiptext: "open-file-button.tooltiptext3",
214
onCommand(aEvent) {
215
let win = aEvent.target.ownerGlobal;
216
win.BrowserOpenFileWindow();
217
},
218
},
219
{
220
id: "sidebar-button",
221
tooltiptext: "sidebar-button.tooltiptext2",
222
onCommand(aEvent) {
223
let win = aEvent.target.ownerGlobal;
224
win.SidebarUI.toggle();
225
},
226
onCreated(aNode) {
227
// Add an observer so the button is checked while the sidebar is open
228
let doc = aNode.ownerDocument;
229
let obChecked = doc.createXULElement("observes");
230
obChecked.setAttribute("element", "sidebar-box");
231
obChecked.setAttribute("attribute", "checked");
232
let obPosition = doc.createXULElement("observes");
233
obPosition.setAttribute("element", "sidebar-box");
234
obPosition.setAttribute("attribute", "positionend");
235
236
aNode.appendChild(obChecked);
237
aNode.appendChild(obPosition);
238
},
239
},
240
{
241
id: "add-ons-button",
242
shortcutId: "key_openAddons",
243
tooltiptext: "add-ons-button.tooltiptext3",
244
onCommand(aEvent) {
245
let win = aEvent.target.ownerGlobal;
246
win.BrowserOpenAddonsMgr();
247
},
248
},
249
{
250
id: "zoom-controls",
251
type: "custom",
252
tooltiptext: "zoom-controls.tooltiptext2",
253
onBuild(aDocument) {
254
let buttons = [
255
{
256
id: "zoom-out-button",
257
command: "cmd_fullZoomReduce",
258
label: true,
259
closemenu: "none",
260
tooltiptext: "tooltiptext2",
261
shortcutId: "key_fullZoomReduce",
262
class: "toolbarbutton-1 toolbarbutton-combined",
263
},
264
{
265
id: "zoom-reset-button",
266
command: "cmd_fullZoomReset",
267
closemenu: "none",
268
tooltiptext: "tooltiptext2",
269
shortcutId: "key_fullZoomReset",
270
class: "toolbarbutton-1 toolbarbutton-combined",
271
},
272
{
273
id: "zoom-in-button",
274
command: "cmd_fullZoomEnlarge",
275
closemenu: "none",
276
label: true,
277
tooltiptext: "tooltiptext2",
278
shortcutId: "key_fullZoomEnlarge",
279
class: "toolbarbutton-1 toolbarbutton-combined",
280
},
281
];
282
283
let node = aDocument.createXULElement("toolbaritem");
284
node.setAttribute("id", "zoom-controls");
285
node.setAttribute(
286
"label",
287
CustomizableUI.getLocalizedProperty(this, "label")
288
);
289
node.setAttribute(
290
"title",
291
CustomizableUI.getLocalizedProperty(this, "tooltiptext")
292
);
293
// Set this as an attribute in addition to the property to make sure we can style correctly.
294
node.setAttribute("removable", "true");
295
node.classList.add("chromeclass-toolbar-additional");
296
node.classList.add("toolbaritem-combined-buttons");
297
298
buttons.forEach(function(aButton, aIndex) {
299
if (aIndex != 0) {
300
node.appendChild(aDocument.createXULElement("separator"));
301
}
302
let btnNode = aDocument.createXULElement("toolbarbutton");
303
setAttributes(btnNode, aButton);
304
node.appendChild(btnNode);
305
});
306
return node;
307
},
308
},
309
{
310
id: "edit-controls",
311
type: "custom",
312
tooltiptext: "edit-controls.tooltiptext2",
313
onBuild(aDocument) {
314
let buttons = [
315
{
316
id: "cut-button",
317
command: "cmd_cut",
318
label: true,
319
tooltiptext: "tooltiptext2",
320
shortcutId: "key_cut",
321
class: "toolbarbutton-1 toolbarbutton-combined",
322
},
323
{
324
id: "copy-button",
325
command: "cmd_copy",
326
label: true,
327
tooltiptext: "tooltiptext2",
328
shortcutId: "key_copy",
329
class: "toolbarbutton-1 toolbarbutton-combined",
330
},
331
{
332
id: "paste-button",
333
command: "cmd_paste",
334
label: true,
335
tooltiptext: "tooltiptext2",
336
shortcutId: "key_paste",
337
class: "toolbarbutton-1 toolbarbutton-combined",
338
},
339
];
340
341
let node = aDocument.createXULElement("toolbaritem");
342
node.setAttribute("id", "edit-controls");
343
node.setAttribute(
344
"label",
345
CustomizableUI.getLocalizedProperty(this, "label")
346
);
347
node.setAttribute(
348
"title",
349
CustomizableUI.getLocalizedProperty(this, "tooltiptext")
350
);
351
// Set this as an attribute in addition to the property to make sure we can style correctly.
352
node.setAttribute("removable", "true");
353
node.classList.add("chromeclass-toolbar-additional");
354
node.classList.add("toolbaritem-combined-buttons");
355
356
buttons.forEach(function(aButton, aIndex) {
357
if (aIndex != 0) {
358
node.appendChild(aDocument.createXULElement("separator"));
359
}
360
let btnNode = aDocument.createXULElement("toolbarbutton");
361
setAttributes(btnNode, aButton);
362
node.appendChild(btnNode);
363
});
364
365
let listener = {
366
onWidgetInstanceRemoved: (aWidgetId, aDoc) => {
367
if (aWidgetId != this.id || aDoc != aDocument) {
368
return;
369
}
370
CustomizableUI.removeListener(listener);
371
},
372
onWidgetOverflow(aWidgetNode) {
373
if (aWidgetNode == node) {
374
node.ownerGlobal.updateEditUIVisibility();
375
}
376
},
377
onWidgetUnderflow(aWidgetNode) {
378
if (aWidgetNode == node) {
379
node.ownerGlobal.updateEditUIVisibility();
380
}
381
},
382
};
383
CustomizableUI.addListener(listener);
384
385
return node;
386
},
387
},
388
{
389
id: "characterencoding-button",
390
label: "characterencoding-button2.label",
391
type: "view",
392
viewId: "PanelUI-characterEncodingView",
393
tooltiptext: "characterencoding-button2.tooltiptext",
394
maybeDisableMenu(aDocument) {
395
let window = aDocument.defaultView;
396
return !(
397
window.gBrowser &&
398
window.gBrowser.selectedBrowser.mayEnableCharacterEncodingMenu
399
);
400
},
401
populateList(aDocument, aContainerId, aSection) {
402
let containerElem = aDocument.getElementById(aContainerId);
403
404
containerElem.addEventListener("command", this.onCommand);
405
406
let list = this.charsetInfo[aSection];
407
408
for (let item of list) {
409
let elem = aDocument.createXULElement("toolbarbutton");
410
elem.setAttribute("label", item.label);
411
elem.setAttribute("type", "checkbox");
412
elem.section = aSection;
413
elem.value = item.value;
414
elem.setAttribute("class", "subviewbutton");
415
containerElem.appendChild(elem);
416
}
417
},
418
updateCurrentCharset(aDocument) {
419
let currentCharset =
420
aDocument.defaultView.gBrowser.selectedBrowser.characterSet;
421
let {
422
charsetAutodetected,
423
} = aDocument.defaultView.gBrowser.selectedBrowser;
424
currentCharset = CharsetMenu.foldCharset(
425
currentCharset,
426
charsetAutodetected
427
);
428
429
let pinnedContainer = aDocument.getElementById(
430
"PanelUI-characterEncodingView-pinned"
431
);
432
let charsetContainer = aDocument.getElementById(
433
"PanelUI-characterEncodingView-charsets"
434
);
435
let elements = [
436
...pinnedContainer.children,
437
...charsetContainer.children,
438
];
439
440
this._updateElements(elements, currentCharset);
441
},
442
updateCurrentDetector(aDocument) {
443
let detectorContainer = aDocument.getElementById(
444
"PanelUI-characterEncodingView-autodetect"
445
);
446
let currentDetector;
447
try {
448
currentDetector = Services.prefs.getComplexValue(
449
"intl.charset.detector",
450
Ci.nsIPrefLocalizedString
451
).data;
452
} catch (e) {}
453
454
this._updateElements(detectorContainer.children, currentDetector);
455
},
456
_updateElements(aElements, aCurrentItem) {
457
if (!aElements.length) {
458
return;
459
}
460
let disabled = this.maybeDisableMenu(aElements[0].ownerDocument);
461
for (let elem of aElements) {
462
if (disabled) {
463
elem.setAttribute("disabled", "true");
464
} else {
465
elem.removeAttribute("disabled");
466
}
467
if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
468
elem.setAttribute("checked", "true");
469
} else {
470
elem.removeAttribute("checked");
471
}
472
}
473
},
474
onViewShowing(aEvent) {
475
if (!this._inited) {
476
this.onInit();
477
}
478
let document = aEvent.target.ownerDocument;
479
480
let autoDetectLabelId = "PanelUI-characterEncodingView-autodetect-label";
481
let autoDetectLabel = document.getElementById(autoDetectLabelId);
482
if (!autoDetectLabel.hasAttribute("value")) {
483
let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
484
autoDetectLabel.setAttribute("value", label);
485
this.populateList(
486
document,
487
"PanelUI-characterEncodingView-pinned",
488
"pinnedCharsets"
489
);
490
this.populateList(
491
document,
492
"PanelUI-characterEncodingView-charsets",
493
"otherCharsets"
494
);
495
this.populateList(
496
document,
497
"PanelUI-characterEncodingView-autodetect",
498
"detectors"
499
);
500
}
501
this.updateCurrentDetector(document);
502
this.updateCurrentCharset(document);
503
},
504
onCommand(aEvent) {
505
let node = aEvent.target;
506
if (!node.hasAttribute || !node.section) {
507
return;
508
}
509
510
let window = node.ownerGlobal;
511
let section = node.section;
512
let value = node.value;
513
514
// The behavior as implemented here is directly based off of the
515
// `MultiplexHandler()` method in browser.js.
516
if (section != "detectors") {
517
window.BrowserSetForcedCharacterSet(value);
518
} else {
519
// Set the detector pref.
520
try {
521
Services.prefs.setStringPref("intl.charset.detector", value);
522
} catch (e) {
523
Cu.reportError("Failed to set the intl.charset.detector preference.");
524
}
525
// Prepare a browser page reload with a changed charset.
526
window.BrowserCharsetReload();
527
}
528
},
529
onCreated(aNode) {
530
let document = aNode.ownerDocument;
531
532
let updateButton = () => {
533
if (this.maybeDisableMenu(document)) {
534
aNode.setAttribute("disabled", "true");
535
} else {
536
aNode.removeAttribute("disabled");
537
}
538
};
539
540
let getPanel = () => {
541
let { PanelUI } = document.ownerGlobal;
542
return PanelUI.overflowPanel;
543
};
544
545
if (
546
CustomizableUI.getAreaType(this.currentArea) ==
547
CustomizableUI.TYPE_MENU_PANEL
548
) {
549
getPanel().addEventListener("popupshowing", updateButton);
550
}
551
552
let listener = {
553
onWidgetAdded: (aWidgetId, aArea) => {
554
if (aWidgetId != this.id) {
555
return;
556
}
557
if (
558
CustomizableUI.getAreaType(aArea) == CustomizableUI.TYPE_MENU_PANEL
559
) {
560
getPanel().addEventListener("popupshowing", updateButton);
561
}
562
},
563
onWidgetRemoved: (aWidgetId, aPrevArea) => {
564
if (aWidgetId != this.id) {
565
return;
566
}
567
aNode.removeAttribute("disabled");
568
if (
569
CustomizableUI.getAreaType(aPrevArea) ==
570
CustomizableUI.TYPE_MENU_PANEL
571
) {
572
getPanel().removeEventListener("popupshowing", updateButton);
573
}
574
},
575
onWidgetInstanceRemoved: (aWidgetId, aDoc) => {
576
if (aWidgetId != this.id || aDoc != document) {
577
return;
578
}
579
580
CustomizableUI.removeListener(listener);
581
getPanel().removeEventListener("popupshowing", updateButton);
582
},
583
};
584
CustomizableUI.addListener(listener);
585
this.onInit();
586
},
587
onInit() {
588
this._inited = true;
589
if (!this.charsetInfo) {
590
this.charsetInfo = CharsetMenu.getData();
591
}
592
},
593
},
594
{
595
id: "email-link-button",
596
tooltiptext: "email-link-button.tooltiptext3",
597
onCommand(aEvent) {
598
let win = aEvent.view;
599
win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser);
600
},
601
},
602
];
603
604
if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
605
CustomizableWidgets.push({
606
id: "sync-button",
607
label: "remotetabs-panelmenu.label",
608
tooltiptext: "remotetabs-panelmenu.tooltiptext2",
609
type: "view",
610
viewId: "PanelUI-remotetabs",
611
deckIndices: {
612
DECKINDEX_TABS: 0,
613
DECKINDEX_TABSDISABLED: 1,
614
DECKINDEX_FETCHING: 2,
615
DECKINDEX_NOCLIENTS: 3,
616
},
617
TABS_PER_PAGE: 25,
618
NEXT_PAGE_MIN_TABS: 5, // Minimum number of tabs displayed when we click "Show All"
619
onViewShowing(aEvent) {
620
let doc = aEvent.target.ownerDocument;
621
this._tabsList = doc.getElementById("PanelUI-remotetabs-tabslist");
622
Services.obs.addObserver(this, SyncedTabs.TOPIC_TABS_CHANGED);
623
624
let deck = doc.getElementById("PanelUI-remotetabs-deck");
625
if (SyncedTabs.isConfiguredToSyncTabs) {
626
if (SyncedTabs.hasSyncedThisSession) {
627
deck.selectedIndex = this.deckIndices.DECKINDEX_TABS;
628
} else {
629
// Sync hasn't synced tabs yet, so show the "fetching" panel.
630
deck.selectedIndex = this.deckIndices.DECKINDEX_FETCHING;
631
}
632
// force a background sync.
633
SyncedTabs.syncTabs().catch(ex => {
634
Cu.reportError(ex);
635
});
636
// show the current list - it will be updated by our observer.
637
this._showTabs();
638
} else {
639
// not configured to sync tabs, so no point updating the list.
640
deck.selectedIndex = this.deckIndices.DECKINDEX_TABSDISABLED;
641
}
642
},
643
onViewHiding() {
644
Services.obs.removeObserver(this, SyncedTabs.TOPIC_TABS_CHANGED);
645
this._tabsList = null;
646
},
647
_tabsList: null,
648
observe(subject, topic, data) {
649
switch (topic) {
650
case SyncedTabs.TOPIC_TABS_CHANGED:
651
this._showTabs();
652
break;
653
default:
654
break;
655
}
656
},
657
658
_showTabsPromise: Promise.resolve(),
659
// Update the tab list after any existing in-flight updates are complete.
660
_showTabs(paginationInfo) {
661
this._showTabsPromise = this._showTabsPromise.then(
662
() => {
663
return this.__showTabs(paginationInfo);
664
},
665
e => {
666
Cu.reportError(e);
667
}
668
);
669
},
670
// Return a new promise to update the tab list.
671
__showTabs(paginationInfo) {
672
if (!this._tabsList) {
673
// Closed between the previous `this._showTabsPromise`
674
// resolving and now.
675
return undefined;
676
}
677
let doc = this._tabsList.ownerDocument;
678
let deck = doc.getElementById("PanelUI-remotetabs-deck");
679
return SyncedTabs.getTabClients()
680
.then(clients => {
681
// The view may have been hidden while the promise was resolving.
682
if (!this._tabsList) {
683
return;
684
}
685
if (clients.length === 0 && !SyncedTabs.hasSyncedThisSession) {
686
// the "fetching tabs" deck is being shown - let's leave it there.
687
// When that first sync completes we'll be notified and update.
688
return;
689
}
690
691
if (clients.length === 0) {
692
deck.selectedIndex = this.deckIndices.DECKINDEX_NOCLIENTS;
693
return;
694
}
695
696
deck.selectedIndex = this.deckIndices.DECKINDEX_TABS;
697
this._clearTabList();
698
SyncedTabs.sortTabClientsByLastUsed(clients);
699
let fragment = doc.createDocumentFragment();
700
701
for (let client of clients) {
702
// add a menu separator for all clients other than the first.
703
if (fragment.lastElementChild) {
704
let separator = doc.createXULElement("menuseparator");
705
fragment.appendChild(separator);
706
}
707
if (paginationInfo && paginationInfo.clientId == client.id) {
708
this._appendClient(client, fragment, paginationInfo.maxTabs);
709
} else {
710
this._appendClient(client, fragment);
711
}
712
}
713
this._tabsList.appendChild(fragment);
714
PanelView.forNode(
715
this._tabsList.closest("panelview")
716
).descriptionHeightWorkaround();
717
})
718
.catch(err => {
719
Cu.reportError(err);
720
})
721
.then(() => {
722
// an observer for tests.
723
Services.obs.notifyObservers(
724
null,
725
"synced-tabs-menu:test:tabs-updated"
726
);
727
});
728
},
729
_clearTabList() {
730
let list = this._tabsList;
731
while (list.lastChild) {
732
list.lastChild.remove();
733
}
734
},
735
_showNoClientMessage() {
736
this._appendMessageLabel("notabslabel");
737
},
738
_appendMessageLabel(messageAttr, appendTo = null) {
739
if (!appendTo) {
740
appendTo = this._tabsList;
741
}
742
let message = this._tabsList.getAttribute(messageAttr);
743
let doc = this._tabsList.ownerDocument;
744
let messageLabel = doc.createXULElement("label");
745
messageLabel.textContent = message;
746
appendTo.appendChild(messageLabel);
747
return messageLabel;
748
},
749
_appendClient(client, attachFragment, maxTabs = this.TABS_PER_PAGE) {
750
let doc = attachFragment.ownerDocument;
751
// Create the element for the remote client.
752
let clientItem = doc.createXULElement("label");
753
clientItem.setAttribute("itemtype", "client");
754
let window = doc.defaultView;
755
clientItem.setAttribute(
756
"tooltiptext",
757
window.gSync.formatLastSyncDate(new Date(client.lastModified))
758
);
759
clientItem.textContent = client.name;
760
761
attachFragment.appendChild(clientItem);
762
763
if (!client.tabs.length) {
764
let label = this._appendMessageLabel(
765
"notabsforclientlabel",
766
attachFragment
767
);
768
label.setAttribute("class", "PanelUI-remotetabs-notabsforclient-label");
769
} else {
770
// If this page will display all tabs, show no additional buttons.
771
// If the next page will display all the remaining tabs, show a "Show All" button
772
// Otherwise, show a "Shore More" button
773
let hasNextPage = client.tabs.length > maxTabs;
774
let nextPageIsLastPage =
775
hasNextPage && maxTabs + this.TABS_PER_PAGE >= client.tabs.length;
776
if (nextPageIsLastPage) {
777
// When the user clicks "Show All", try to have at least NEXT_PAGE_MIN_TABS more tabs
778
// to display in order to avoid user frustration
779
maxTabs = Math.min(
780
client.tabs.length - this.NEXT_PAGE_MIN_TABS,
781
maxTabs
782
);
783
}
784
if (hasNextPage) {
785
client.tabs = client.tabs.slice(0, maxTabs);
786
}
787
for (let tab of client.tabs) {
788
let tabEnt = this._createTabElement(doc, tab);
789
attachFragment.appendChild(tabEnt);
790
}
791
if (hasNextPage) {
792
let showAllEnt = this._createShowMoreElement(
793
doc,
794
client.id,
795
nextPageIsLastPage ? Infinity : maxTabs + this.TABS_PER_PAGE
796
);
797
attachFragment.appendChild(showAllEnt);
798
}
799
}
800
},
801
_createTabElement(doc, tabInfo) {
802
let item = doc.createXULElement("toolbarbutton");
803
let tooltipText =
804
(tabInfo.title ? tabInfo.title + "\n" : "") + tabInfo.url;
805
item.setAttribute("itemtype", "tab");
806
item.setAttribute("class", "subviewbutton");
807
item.setAttribute("targetURI", tabInfo.url);
808
item.setAttribute(
809
"label",
810
tabInfo.title != "" ? tabInfo.title : tabInfo.url
811
);
812
item.setAttribute("image", tabInfo.icon);
813
item.setAttribute("tooltiptext", tooltipText);
814
// We need to use "click" instead of "command" here so openUILink
815
// respects different buttons (eg, to open in a new tab).
816
item.addEventListener("click", e => {
817
doc.defaultView.openUILink(tabInfo.url, e, {
818
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
819
{}
820
),
821
});
822
if (doc.defaultView.whereToOpenLink(e) != "current") {
823
e.preventDefault();
824
e.stopPropagation();
825
} else {
826
CustomizableUI.hidePanelForNode(item);
827
}
828
});
829
return item;
830
},
831
_createShowMoreElement(doc, clientId, showCount) {
832
let labelAttr, tooltipAttr;
833
if (showCount === Infinity) {
834
labelAttr = "showAllLabel";
835
tooltipAttr = "showAllTooltipText";
836
} else {
837
labelAttr = "showMoreLabel";
838
tooltipAttr = "showMoreTooltipText";
839
}
840
let showAllItem = doc.createXULElement("toolbarbutton");
841
showAllItem.setAttribute("itemtype", "showmorebutton");
842
showAllItem.setAttribute("class", "subviewbutton");
843
let label = this._tabsList.getAttribute(labelAttr);
844
showAllItem.setAttribute("label", label);
845
let tooltipText = this._tabsList.getAttribute(tooltipAttr);
846
showAllItem.setAttribute("tooltiptext", tooltipText);
847
showAllItem.addEventListener("click", e => {
848
e.preventDefault();
849
e.stopPropagation();
850
this._showTabs({ clientId, maxTabs: showCount });
851
});
852
return showAllItem;
853
},
854
});
855
}
856
857
let preferencesButton = {
858
id: "preferences-button",
859
onCommand(aEvent) {
860
let win = aEvent.target.ownerGlobal;
861
win.openPreferences(undefined);
862
},
863
};
864
if (AppConstants.platform == "win") {
865
preferencesButton.label = "preferences-button.labelWin";
866
preferencesButton.tooltiptext = "preferences-button.tooltipWin2";
867
} else if (AppConstants.platform == "macosx") {
868
preferencesButton.tooltiptext = "preferences-button.tooltiptext.withshortcut";
869
preferencesButton.shortcutId = "key_preferencesCmdMac";
870
} else {
871
preferencesButton.tooltiptext = "preferences-button.tooltiptext2";
872
}
873
CustomizableWidgets.push(preferencesButton);
874
875
if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
876
CustomizableWidgets.push({
877
id: "panic-button",
878
type: "view",
879
viewId: "PanelUI-panicView",
880
881
forgetButtonCalled(aEvent) {
882
let doc = aEvent.target.ownerDocument;
883
let group = doc.getElementById("PanelUI-panic-timeSpan");
884
let itemsToClear = [
885
"cookies",
886
"history",
887
"openWindows",
888
"formdata",
889
"sessions",
890
"cache",
891
"downloads",
892
"offlineApps",
893
];
894
let newWindowPrivateState = PrivateBrowsingUtils.isWindowPrivate(
895
doc.defaultView
896
)
897
? "private"
898
: "non-private";
899
let promise = Sanitizer.sanitize(itemsToClear, {
900
ignoreTimespan: false,
901
range: Sanitizer.getClearRange(+group.value),
902
privateStateForNewWindow: newWindowPrivateState,
903
});
904
promise.then(function() {
905
let otherWindow = Services.wm.getMostRecentWindow("navigator:browser");
906
if (otherWindow.closed) {
907
Cu.reportError("Got a closed window!");
908
}
909
if (otherWindow.PanicButtonNotifier) {
910
otherWindow.PanicButtonNotifier.notify();
911
} else {
912
otherWindow.PanicButtonNotifierShouldNotify = true;
913
}
914
});
915
},
916
handleEvent(aEvent) {
917
switch (aEvent.type) {
918
case "command":
919
this.forgetButtonCalled(aEvent);
920
break;
921
}
922
},
923
onViewShowing(aEvent) {
924
let win = aEvent.target.ownerGlobal;
925
let doc = win.document;
926
let eventBlocker = null;
927
if (!doc.querySelector("#PanelUI-panic-timeframe")) {
928
win.MozXULElement.insertFTLIfNeeded("browser/panicButton.ftl");
929
let frag = win.MozXULElement.parseXULToFragment(`
930
<vbox class="panel-subview-body">
931
<hbox id="PanelUI-panic-timeframe">
932
<image id="PanelUI-panic-timeframe-icon" alt=""/>
933
<vbox flex="1">
934
<description data-l10n-id="panic-main-timeframe-desc" id="PanelUI-panic-mainDesc"></description>
935
<radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
936
<radio id="PanelUI-panic-5min" data-l10n-id="panic-button-5min" selected="true"
937
value="5" class="subviewradio"/>
938
<radio id="PanelUI-panic-2hr" data-l10n-id="panic-button-2hr"
939
value="2" class="subviewradio"/>
940
<radio id="PanelUI-panic-day" data-l10n-id="panic-button-day"
941
value="6" class="subviewradio"/>
942
</radiogroup>
943
</vbox>
944
</hbox>
945
<vbox id="PanelUI-panic-explanations">
946
<label id="PanelUI-panic-actionlist-main-label" data-l10n-id="panic-button-action-desc"></label>
947
948
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-tabs-and-windows"></label>
949
<label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-cookies"></label>
950
<label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-delete-history"></label>
951
<label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist" data-l10n-id="panic-button-open-new-window"></label>
952
953
<label id="PanelUI-panic-warning" data-l10n-id="panic-button-undo-warning"></label>
954
</vbox>
955
<button id="PanelUI-panic-view-button"
956
data-l10n-id="panic-button-forget-button"/>
957
</vbox>
958
`);
959
960
aEvent.target.appendChild(frag);
961
eventBlocker = doc.l10n.translateElements([aEvent.target]);
962
}
963
964
let forgetButton = aEvent.target.querySelector(
965
"#PanelUI-panic-view-button"
966
);
967
let group = doc.getElementById("PanelUI-panic-timeSpan");
968
group.selectedItem = doc.getElementById("PanelUI-panic-5min");
969
forgetButton.addEventListener("command", this);
970
971
if (eventBlocker) {
972
aEvent.detail.addBlocker(eventBlocker);
973
}
974
},
975
onViewHiding(aEvent) {
976
let forgetButton = aEvent.target.querySelector(
977
"#PanelUI-panic-view-button"
978
);
979
forgetButton.removeEventListener("command", this);
980
},
981
});
982
}
983
984
if (PrivateBrowsingUtils.enabled) {
985
CustomizableWidgets.push({
986
id: "privatebrowsing-button",
987
shortcutId: "key_privatebrowsing",
988
onCommand(e) {
989
let win = e.target.ownerGlobal;
990
win.OpenBrowserWindow({ private: true });
991
},
992
});
993
}