summaryrefslogtreecommitdiffstats
path: root/application/palemoon/base/content
diff options
context:
space:
mode:
Diffstat (limited to 'application/palemoon/base/content')
-rw-r--r--application/palemoon/base/content/aboutDialog.css70
-rw-r--r--application/palemoon/base/content/aboutDialog.js590
-rw-r--r--application/palemoon/base/content/aboutDialog.xul120
-rw-r--r--application/palemoon/base/content/aboutRobots-icon.pngbin0 -> 9817 bytes
-rw-r--r--application/palemoon/base/content/aboutRobots-widget-left.pngbin0 -> 2224 bytes
-rw-r--r--application/palemoon/base/content/aboutRobots.xhtml108
-rw-r--r--application/palemoon/base/content/abouthome/aboutHome.css339
-rw-r--r--application/palemoon/base/content/abouthome/aboutHome.js354
-rw-r--r--application/palemoon/base/content/abouthome/aboutHome.xhtml60
-rw-r--r--application/palemoon/base/content/abouthome/addons.pngbin0 -> 1444 bytes
-rw-r--r--application/palemoon/base/content/abouthome/addons@2x.pngbin0 -> 3783 bytes
-rw-r--r--application/palemoon/base/content/abouthome/bookmarks.pngbin0 -> 1276 bytes
-rw-r--r--application/palemoon/base/content/abouthome/bookmarks@2x.pngbin0 -> 2946 bytes
-rw-r--r--application/palemoon/base/content/abouthome/downloads.pngbin0 -> 898 bytes
-rw-r--r--application/palemoon/base/content/abouthome/downloads@2x.pngbin0 -> 2018 bytes
-rw-r--r--application/palemoon/base/content/abouthome/history.pngbin0 -> 1654 bytes
-rw-r--r--application/palemoon/base/content/abouthome/history@2x.pngbin0 -> 4629 bytes
-rw-r--r--application/palemoon/base/content/abouthome/noise.pngbin0 -> 4025 bytes
-rw-r--r--application/palemoon/base/content/abouthome/restore-large.pngbin0 -> 2841 bytes
-rw-r--r--application/palemoon/base/content/abouthome/restore-large@2x.pngbin0 -> 7267 bytes
-rw-r--r--application/palemoon/base/content/abouthome/restore.pngbin0 -> 1796 bytes
-rw-r--r--application/palemoon/base/content/abouthome/restore@2x.pngbin0 -> 4810 bytes
-rw-r--r--application/palemoon/base/content/abouthome/settings.pngbin0 -> 1557 bytes
-rw-r--r--application/palemoon/base/content/abouthome/settings@2x.pngbin0 -> 3836 bytes
-rw-r--r--application/palemoon/base/content/abouthome/snippet1.pngbin0 -> 1470 bytes
-rw-r--r--application/palemoon/base/content/abouthome/snippet1@2x.pngbin0 -> 3243 bytes
-rw-r--r--application/palemoon/base/content/abouthome/snippet2.pngbin0 -> 3287 bytes
-rw-r--r--application/palemoon/base/content/abouthome/snippet2@2x.pngbin0 -> 11027 bytes
-rw-r--r--application/palemoon/base/content/abouthome/sync.pngbin0 -> 1879 bytes
-rw-r--r--application/palemoon/base/content/abouthome/sync@2x.pngbin0 -> 4615 bytes
-rw-r--r--application/palemoon/base/content/autorecovery.js60
-rw-r--r--application/palemoon/base/content/autorecovery.xul12
-rw-r--r--application/palemoon/base/content/baseMenuOverlay.xul100
-rw-r--r--application/palemoon/base/content/blockedSite.xhtml193
-rw-r--r--application/palemoon/base/content/browser-addons.js536
-rw-r--r--application/palemoon/base/content/browser-appmenu.inc394
-rw-r--r--application/palemoon/base/content/browser-charsetmenu.inc62
-rw-r--r--application/palemoon/base/content/browser-context.inc379
-rw-r--r--application/palemoon/base/content/browser-doctype.inc19
-rw-r--r--application/palemoon/base/content/browser-feeds.js224
-rw-r--r--application/palemoon/base/content/browser-fullScreen.js607
-rw-r--r--application/palemoon/base/content/browser-fullZoom.js550
-rw-r--r--application/palemoon/base/content/browser-gestureSupport.js1059
-rw-r--r--application/palemoon/base/content/browser-menubar.inc595
-rw-r--r--application/palemoon/base/content/browser-menudragging.js362
-rw-r--r--application/palemoon/base/content/browser-menudragging.xul13
-rw-r--r--application/palemoon/base/content/browser-places.js1303
-rw-r--r--application/palemoon/base/content/browser-plugins.js797
-rw-r--r--application/palemoon/base/content/browser-sets.inc436
-rw-r--r--application/palemoon/base/content/browser-syncui.js470
-rw-r--r--application/palemoon/base/content/browser-tabPreviews.js1051
-rw-r--r--application/palemoon/base/content/browser-tabPreviews.xml75
-rw-r--r--application/palemoon/base/content/browser-thumbnails.js198
-rw-r--r--application/palemoon/base/content/browser-title.css204
-rw-r--r--application/palemoon/base/content/browser-webrtcUI.js55
-rw-r--r--application/palemoon/base/content/browser.css693
-rw-r--r--application/palemoon/base/content/browser.js7163
-rw-r--r--application/palemoon/base/content/browser.xul1058
-rw-r--r--application/palemoon/base/content/browserMountPoints.inc12
-rw-r--r--application/palemoon/base/content/content.js64
-rw-r--r--application/palemoon/base/content/downloadManagerOverlay.xul32
-rw-r--r--application/palemoon/base/content/global-scripts.inc13
-rw-r--r--application/palemoon/base/content/hiddenWindow.xul19
-rw-r--r--application/palemoon/base/content/highlighter.css105
-rw-r--r--application/palemoon/base/content/jsConsoleOverlay.xul18
-rw-r--r--application/palemoon/base/content/macBrowserOverlay.xul64
-rw-r--r--application/palemoon/base/content/newtab/cells.js126
-rw-r--r--application/palemoon/base/content/newtab/drag.js151
-rw-r--r--application/palemoon/base/content/newtab/dragDataHelper.js22
-rw-r--r--application/palemoon/base/content/newtab/drop.js150
-rw-r--r--application/palemoon/base/content/newtab/dropPreview.js222
-rw-r--r--application/palemoon/base/content/newtab/dropTargetShim.js188
-rw-r--r--application/palemoon/base/content/newtab/grid.js171
-rw-r--r--application/palemoon/base/content/newtab/newTab.css209
-rw-r--r--application/palemoon/base/content/newtab/newTab.js57
-rw-r--r--application/palemoon/base/content/newtab/newTab.xul55
-rw-r--r--application/palemoon/base/content/newtab/page.js135
-rw-r--r--application/palemoon/base/content/newtab/sites.js192
-rw-r--r--application/palemoon/base/content/newtab/transformations.js265
-rw-r--r--application/palemoon/base/content/newtab/undo.js116
-rw-r--r--application/palemoon/base/content/newtab/updater.js186
-rw-r--r--application/palemoon/base/content/nsContextMenu.js1588
-rw-r--r--application/palemoon/base/content/openLocation.js134
-rw-r--r--application/palemoon/base/content/openLocation.xul57
-rw-r--r--application/palemoon/base/content/overrides/app-license.html6
-rw-r--r--application/palemoon/base/content/padlock.css203
-rw-r--r--application/palemoon/base/content/padlock.js234
-rw-r--r--application/palemoon/base/content/padlock.xul63
-rw-r--r--application/palemoon/base/content/padlock_classic_broken.pngbin0 -> 726 bytes
-rw-r--r--application/palemoon/base/content/padlock_classic_ev.pngbin0 -> 566 bytes
-rw-r--r--application/palemoon/base/content/padlock_classic_https.pngbin0 -> 589 bytes
-rw-r--r--application/palemoon/base/content/padlock_classic_low.pngbin0 -> 682 bytes
-rw-r--r--application/palemoon/base/content/padlock_mod_broken.pngbin0 -> 728 bytes
-rw-r--r--application/palemoon/base/content/padlock_mod_ev.pngbin0 -> 290 bytes
-rw-r--r--application/palemoon/base/content/padlock_mod_https.pngbin0 -> 338 bytes
-rw-r--r--application/palemoon/base/content/padlock_mod_low.pngbin0 -> 386 bytes
-rw-r--r--application/palemoon/base/content/pageinfo/feeds.js59
-rw-r--r--application/palemoon/base/content/pageinfo/feeds.xml40
-rw-r--r--application/palemoon/base/content/pageinfo/pageInfo.css26
-rw-r--r--application/palemoon/base/content/pageinfo/pageInfo.js1285
-rw-r--r--application/palemoon/base/content/pageinfo/pageInfo.xml29
-rw-r--r--application/palemoon/base/content/pageinfo/pageInfo.xul559
-rw-r--r--application/palemoon/base/content/pageinfo/permissions.js398
-rw-r--r--application/palemoon/base/content/pageinfo/security.js378
-rw-r--r--application/palemoon/base/content/popup-notifications.inc97
-rw-r--r--application/palemoon/base/content/report-phishing-overlay.xul35
-rw-r--r--application/palemoon/base/content/safeMode.css8
-rw-r--r--application/palemoon/base/content/safeMode.js128
-rw-r--r--application/palemoon/base/content/safeMode.xul55
-rw-r--r--application/palemoon/base/content/sanitize.js527
-rw-r--r--application/palemoon/base/content/sanitize.xul190
-rw-r--r--application/palemoon/base/content/sanitizeDialog.css23
-rw-r--r--application/palemoon/base/content/sanitizeDialog.js910
-rw-r--r--application/palemoon/base/content/softwareUpdateOverlay.xul18
-rw-r--r--application/palemoon/base/content/sync/aboutSyncTabs-bindings.xml46
-rw-r--r--application/palemoon/base/content/sync/aboutSyncTabs.css11
-rw-r--r--application/palemoon/base/content/sync/aboutSyncTabs.js313
-rw-r--r--application/palemoon/base/content/sync/aboutSyncTabs.xul68
-rw-r--r--application/palemoon/base/content/sync/addDevice.js157
-rw-r--r--application/palemoon/base/content/sync/addDevice.xul129
-rw-r--r--application/palemoon/base/content/sync/genericChange.js234
-rw-r--r--application/palemoon/base/content/sync/genericChange.xul123
-rw-r--r--application/palemoon/base/content/sync/key.xhtml54
-rw-r--r--application/palemoon/base/content/sync/notification.xml129
-rw-r--r--application/palemoon/base/content/sync/progress.js71
-rw-r--r--application/palemoon/base/content/sync/progress.xhtml55
-rw-r--r--application/palemoon/base/content/sync/quota.js267
-rw-r--r--application/palemoon/base/content/sync/quota.xul65
-rw-r--r--application/palemoon/base/content/sync/setup.js1071
-rw-r--r--application/palemoon/base/content/sync/setup.xul491
-rw-r--r--application/palemoon/base/content/sync/utils.js218
-rw-r--r--application/palemoon/base/content/tabbrowser.css68
-rw-r--r--application/palemoon/base/content/tabbrowser.xml4960
-rw-r--r--application/palemoon/base/content/test/general/audio.oggbin0 -> 47411 bytes
-rw-r--r--application/palemoon/base/content/urlbarBindings.xml1969
-rw-r--r--application/palemoon/base/content/utilityOverlay.js677
-rw-r--r--application/palemoon/base/content/viewSourceOverlay.xul26
-rw-r--r--application/palemoon/base/content/web-panels.js102
-rw-r--r--application/palemoon/base/content/web-panels.xul84
-rw-r--r--application/palemoon/base/content/win6BrowserOverlay.xul12
140 files changed, 41331 insertions, 0 deletions
diff --git a/application/palemoon/base/content/aboutDialog.css b/application/palemoon/base/content/aboutDialog.css
new file mode 100644
index 000000000..aa79b0795
--- /dev/null
+++ b/application/palemoon/base/content/aboutDialog.css
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#PMaboutDialog {
+ width: 620px;
+}
+
+#PMrightBox {
+ background-image: url("chrome://branding/content/about-wordmark.png");
+ background-repeat: no-repeat;
+ /* padding-top creates room for the wordmark */
+ padding-top: 38px;
+ margin-top:20px;
+}
+
+#PMrightBox:-moz-locale-dir(rtl) {
+ background-position: 100% 0;
+}
+
+#PMbottomBox {
+ padding: 15px 10px 0;
+}
+
+#PMversion {
+ margin-top: 10px;
+ -moz-margin-start: 0;
+ -moz-user-select: text;
+ -moz-user-focus: normal;
+ cursor: text;
+}
+
+#distribution,
+#distributionId {
+ font-weight: bold;
+ display: none;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.text-blurb {
+ margin-bottom: 10px;
+ -moz-margin-start: 0;
+ -moz-padding-start: 0;
+}
+
+#updateButton,
+#updateDeck > hbox > label {
+ -moz-margin-start: 0;
+ -moz-padding-start: 0;
+}
+
+.update-throbber {
+ width: 16px;
+ min-height: 16px;
+ -moz-margin-end: 3px;
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.text-link,
+.text-link:focus {
+ margin: 0px;
+ padding: 0px;
+}
+
+.bottom-link,
+.bottom-link:focus {
+ text-align: center;
+ margin: 0 40px;
+}
diff --git a/application/palemoon/base/content/aboutDialog.js b/application/palemoon/base/content/aboutDialog.js
new file mode 100644
index 000000000..f4c2a990c
--- /dev/null
+++ b/application/palemoon/base/content/aboutDialog.js
@@ -0,0 +1,590 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function init(aEvent)
+{
+ if (aEvent.target != document)
+ return;
+
+ try {
+ var distroId = Services.prefs.getCharPref("distribution.id");
+ if (distroId) {
+ var distroVersion = Services.prefs.getCharPref("distribution.version");
+
+ var distroIdField = document.getElementById("distributionId");
+ distroIdField.value = distroId + " - " + distroVersion;
+ distroIdField.style.display = "block";
+
+ try {
+ // This is in its own try catch due to bug 895473 and bug 900925.
+ var distroAbout = Services.prefs.getComplexValue("distribution.about",
+ Components.interfaces.nsISupportsString);
+ var distroField = document.getElementById("distribution");
+ distroField.value = distroAbout;
+ distroField.style.display = "block";
+ }
+ catch (ex) {
+ // Pref is unset
+ Components.utils.reportError(ex);
+ }
+ }
+ }
+ catch (e) {
+ // Pref is unset
+ }
+
+ // Include the build ID if this is an "a#" or "b#" build
+ let version = Services.appinfo.version;
+ if (/[ab]\d+$/.test(version)) {
+ let buildID = Services.appinfo.appBuildID;
+ let buildDate = buildID.slice(0,4) + "-" + buildID.slice(4,6) + "-" + buildID.slice(6,8);
+ document.getElementById("PMversion").textContent += " (" + buildDate + ")";
+ }
+
+#ifdef MOZ_UPDATER
+ gAppUpdater = new appUpdater();
+#endif
+
+#ifdef XP_MACOSX
+ // it may not be sized at this point, and we need its width to calculate its position
+ window.sizeToContent();
+ window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
+#endif
+
+// get release notes URL from prefs
+ var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter);
+ var releaseNotesURL = formatter.formatURLPref("app.releaseNotesURL");
+ if (releaseNotesURL != "about:blank") {
+ var relnotes = document.getElementById("releaseNotesURL");
+ relnotes.setAttribute("href", releaseNotesURL);
+ }
+}
+
+#ifdef MOZ_UPDATER
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
+var gAppUpdater;
+
+function onUnload(aEvent) {
+ if (gAppUpdater.isChecking)
+ gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK);
+ // Safe to call even when there isn't a download in progress.
+ gAppUpdater.removeDownloadListener();
+ gAppUpdater = null;
+}
+
+
+function appUpdater()
+{
+ this.updateDeck = document.getElementById("updateDeck");
+
+ // Hide the update deck when there is already an update window open to avoid
+ // syncing issues between them.
+ if (Services.wm.getMostRecentWindow("Update:Wizard")) {
+ this.updateDeck.hidden = true;
+ return;
+ }
+
+ XPCOMUtils.defineLazyServiceGetter(this, "aus",
+ "@mozilla.org/updates/update-service;1",
+ "nsIApplicationUpdateService");
+ XPCOMUtils.defineLazyServiceGetter(this, "checker",
+ "@mozilla.org/updates/update-checker;1",
+ "nsIUpdateChecker");
+ XPCOMUtils.defineLazyServiceGetter(this, "um",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager");
+
+ this.bundle = Services.strings.
+ createBundle("chrome://browser/locale/browser.properties");
+
+ this.updateBtn = document.getElementById("updateButton");
+
+ // The button label value must be set so its height is correct.
+ this.setupUpdateButton("update.checkInsideButton");
+
+ let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual");
+ let manualLink = document.getElementById("manualLink");
+ manualLink.value = manualURL;
+ manualLink.href = manualURL;
+ document.getElementById("failedLink").href = manualURL;
+
+ if (this.updateDisabledAndLocked) {
+ this.selectPanel("adminDisabled");
+ return;
+ }
+
+ if (this.isPending || this.isApplied) {
+ this.setupUpdateButton("update.restart." +
+ (this.isMajor ? "upgradeButton" : "updateButton"));
+ return;
+ }
+
+ if (this.aus.isOtherInstanceHandlingUpdates) {
+ this.selectPanel("otherInstanceHandlingUpdates");
+ return;
+ }
+
+ if (this.isDownloading) {
+ this.startDownload();
+ return;
+ }
+
+ if (this.updateEnabled && this.updateAuto) {
+ this.selectPanel("checkingForUpdates");
+ this.isChecking = true;
+ this.checker.checkForUpdates(this.updateCheckListener, true);
+ return;
+ }
+}
+
+appUpdater.prototype =
+{
+ // true when there is an update check in progress.
+ isChecking: false,
+
+ // true when there is an update already staged / ready to be applied.
+ get isPending() {
+ if (this.update) {
+ return this.update.state == "pending" ||
+ this.update.state == "pending-service";
+ }
+ return this.um.activeUpdate &&
+ (this.um.activeUpdate.state == "pending" ||
+ this.um.activeUpdate.state == "pending-service");
+ },
+
+ // true when there is an update already installed in the background.
+ get isApplied() {
+ if (this.update)
+ return this.update.state == "applied" ||
+ this.update.state == "applied-service";
+ return this.um.activeUpdate &&
+ (this.um.activeUpdate.state == "applied" ||
+ this.um.activeUpdate.state == "applied-service");
+ },
+
+ // true when there is an update download in progress.
+ get isDownloading() {
+ if (this.update)
+ return this.update.state == "downloading";
+ return this.um.activeUpdate &&
+ this.um.activeUpdate.state == "downloading";
+ },
+
+ // true when the update type is major.
+ get isMajor() {
+ if (this.update)
+ return this.update.type == "major";
+ return this.um.activeUpdate.type == "major";
+ },
+
+ // true when updating is disabled by an administrator.
+ get updateDisabledAndLocked() {
+ return !this.updateEnabled &&
+ Services.prefs.prefIsLocked("app.update.enabled");
+ },
+
+ // true when updating is enabled.
+ get updateEnabled() {
+ try {
+ return Services.prefs.getBoolPref("app.update.enabled");
+ }
+ catch (e) { }
+ return true; // Firefox default is true
+ },
+
+ // true when updating in background is enabled.
+ get backgroundUpdateEnabled() {
+ return this.updateEnabled &&
+ gAppUpdater.aus.canStageUpdates;
+ },
+
+ // true when updating is automatic.
+ get updateAuto() {
+ try {
+ return Services.prefs.getBoolPref("app.update.auto");
+ }
+ catch (e) { }
+ return true; // Firefox default is true
+ },
+
+ /**
+ * Sets the deck's selected panel.
+ *
+ * @param aChildID
+ * The id of the deck's child to select.
+ */
+ selectPanel: function(aChildID) {
+ this.updateDeck.selectedPanel = document.getElementById(aChildID);
+ this.updateBtn.disabled = (aChildID != "updateButtonBox");
+ },
+
+ /**
+ * Sets the update button's label and accesskey.
+ *
+ * @param aKeyPrefix
+ * The prefix for the properties file entry to use for setting the
+ * label and accesskey.
+ */
+ setupUpdateButton: function(aKeyPrefix) {
+ this.updateBtn.label = this.bundle.GetStringFromName(aKeyPrefix + ".label");
+ this.updateBtn.accessKey = this.bundle.GetStringFromName(aKeyPrefix + ".accesskey");
+ if (!document.commandDispatcher.focusedElement ||
+ document.commandDispatcher.focusedElement == this.updateBtn)
+ this.updateBtn.focus();
+ },
+
+ /**
+ * Handles oncommand for the update button.
+ */
+ buttonOnCommand: function() {
+ if (this.isPending || this.isApplied) {
+ // Notify all windows that an application quit has been requested.
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
+ createInstance(Components.interfaces.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data)
+ return;
+
+ let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
+ getService(Components.interfaces.nsIAppStartup);
+
+ // If already in safe mode restart in safe mode (bug 327119)
+ if (Services.appinfo.inSafeMode) {
+ appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
+ return;
+ }
+
+ appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
+ Components.interfaces.nsIAppStartup.eRestart);
+ return;
+ }
+
+ const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
+ // Firefox no longer displays a license for updates and the licenseURL check
+ // is just in case a distibution does.
+ if (this.update) {
+ var ary = null;
+ ary = Components.classes["@mozilla.org/supports-array;1"].
+ createInstance(Components.interfaces.nsISupportsArray);
+ ary.AppendElement(this.update);
+ var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
+ Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, "", openFeatures, ary);
+ window.close();
+ return;
+ }
+
+ this.selectPanel("checkingForUpdates");
+ this.isChecking = true;
+ this.checker.checkForUpdates(this.updateCheckListener, true);
+ },
+
+ /**
+ * Implements nsIUpdateCheckListener. The methods implemented by
+ * nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
+ * to make it clear which are used by each interface.
+ */
+ updateCheckListener: {
+ /**
+ * See nsIUpdateService.idl
+ */
+ onCheckComplete: function(aRequest, aUpdates, aUpdateCount) {
+ gAppUpdater.isChecking = false;
+ gAppUpdater.update = gAppUpdater.aus.
+ selectUpdate(aUpdates, aUpdates.length);
+ if (!gAppUpdater.update) {
+ gAppUpdater.selectPanel("noUpdatesFound");
+ return;
+ }
+
+ if (gAppUpdater.update.unsupported) {
+ if (gAppUpdater.update.detailsURL) {
+ let unsupportedLink = document.getElementById("unsupportedLink");
+ unsupportedLink.href = gAppUpdater.update.detailsURL;
+ }
+ gAppUpdater.selectPanel("unsupportedSystem");
+ return;
+ }
+
+ if (!gAppUpdater.aus.canApplyUpdates) {
+ gAppUpdater.selectPanel("manualUpdate");
+ return;
+ }
+
+ gAppUpdater.selectPanel("updateButtonBox");
+ gAppUpdater.setupUpdateButton("update.openUpdateUI." +
+ (this.isMajor ? "upgradeButton"
+ : "applyButton"));
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ onError: function(aRequest, aUpdate) {
+ // Errors in the update check are treated as no updates found. If the
+ // update check fails repeatedly without a success the user will be
+ // notified with the normal app update user interface so this is safe.
+ gAppUpdater.isChecking = false;
+ gAppUpdater.selectPanel("noUpdatesFound");
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
+ !aIID.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+ },
+
+ /**
+ * Checks the compatibility of add-ons for the application update.
+ */
+ checkAddonCompatibility: function() {
+ var self = this;
+ AddonManager.getAllAddons(function(aAddons) {
+ self.addons = [];
+ self.addonsCheckedCount = 0;
+ aAddons.forEach(function(aAddon) {
+ // Protect against code that overrides the add-ons manager and doesn't
+ // implement the isCompatibleWith or the findUpdates method.
+ if (!("isCompatibleWith" in aAddon) || !("findUpdates" in aAddon)) {
+ let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
+ "or the findUpdates method!";
+ if (aAddon.id)
+ errMsg += " Add-on ID: " + aAddon.id;
+ Components.utils.reportError(errMsg);
+ return;
+ }
+
+ // If an add-on isn't appDisabled and isn't userDisabled then it is
+ // either active now or the user expects it to be active after the
+ // restart. If that is the case and the add-on is not installed by the
+ // application and is not compatible with the new application version
+ // then the user should be warned that the add-on will become
+ // incompatible. If an addon's type equals plugin it is skipped since
+ // checking plugins compatibility information isn't supported and
+ // getting the scope property of a plugin breaks in some environments
+ // (see bug 566787).
+ try {
+ if (aAddon.type != "plugin" && aAddon.isCompatible &&
+ !aAddon.appDisabled && !aAddon.userDisabled &&
+ aAddon.scope != AddonManager.SCOPE_APPLICATION &&
+ !aAddon.isCompatibleWith(self.update.appVersion,
+ self.update.platformVersion))
+ self.addons.push(aAddon);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ });
+ self.addonsTotalCount = self.addons.length;
+ if (self.addonsTotalCount == 0) {
+ self.startDownload();
+ return;
+ }
+
+ self.checkAddonsForUpdates();
+ });
+ },
+
+ /**
+ * Checks if there are updates for add-ons that are incompatible with the
+ * application update.
+ */
+ checkAddonsForUpdates: function() {
+ this.addons.forEach(function(aAddon) {
+ aAddon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
+ this.update.appVersion,
+ this.update.platformVersion);
+ }, this);
+ },
+
+ /**
+ * See XPIProvider.jsm
+ */
+ onCompatibilityUpdateAvailable: function(aAddon) {
+ for (var i = 0; i < this.addons.length; ++i) {
+ if (this.addons[i].id == aAddon.id) {
+ this.addons.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ /**
+ * See XPIProvider.jsm
+ */
+ onUpdateAvailable: function(aAddon, aInstall) {
+ if (!Services.blocklist.isAddonBlocklisted(aAddon.id, aInstall.version,
+ this.update.appVersion,
+ this.update.platformVersion)) {
+ // Compatibility or new version updates mean the same thing here.
+ this.onCompatibilityUpdateAvailable(aAddon);
+ }
+ },
+
+ /**
+ * See XPIProvider.jsm
+ */
+ onUpdateFinished: function(aAddon) {
+ ++this.addonsCheckedCount;
+
+ if (this.addonsCheckedCount < this.addonsTotalCount)
+ return;
+
+ if (this.addons.length == 0) {
+ // Compatibility updates or new version updates were found for all add-ons
+ this.startDownload();
+ return;
+ }
+
+ this.selectPanel("updateButtonBox");
+ this.setupUpdateButton("update.openUpdateUI." +
+ (this.isMajor ? "upgradeButton" : "applyButton"));
+ },
+
+ /**
+ * Starts the download of an update mar.
+ */
+ startDownload: function() {
+ if (!this.update)
+ this.update = this.um.activeUpdate;
+ this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
+ this.update.setProperty("foregroundDownload", "true");
+
+ this.aus.pauseDownload();
+ let state = this.aus.downloadUpdate(this.update, false);
+ if (state == "failed") {
+ this.selectPanel("downloadFailed");
+ return;
+ }
+
+ this.setupDownloadingUI();
+ },
+
+ /**
+ * Switches to the UI responsible for tracking the download.
+ */
+ setupDownloadingUI: function() {
+ this.downloadStatus = document.getElementById("downloadStatus");
+ this.downloadStatus.value =
+ DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size);
+ this.selectPanel("downloading");
+ this.aus.addDownloadListener(this);
+ },
+
+ removeDownloadListener: function() {
+ if (this.aus) {
+ this.aus.removeDownloadListener(this);
+ }
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStartRequest: function(aRequest, aContext) {
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ switch (aStatusCode) {
+ case Components.results.NS_ERROR_UNEXPECTED:
+ if (this.update.selectedPatch.state == "download-failed" &&
+ (this.update.isCompleteUpdate || this.update.patchCount != 2)) {
+ // Verification error of complete patch, informational text is held in
+ // the update object.
+ this.removeDownloadListener();
+ this.selectPanel("downloadFailed");
+ break;
+ }
+ // Verification failed for a partial patch, complete patch is now
+ // downloading so return early and do NOT remove the download listener!
+ break;
+ case Components.results.NS_BINDING_ABORTED:
+ // Do not remove UI listener since the user may resume downloading again.
+ break;
+ case Components.results.NS_OK:
+ this.removeDownloadListener();
+ if (this.backgroundUpdateEnabled) {
+ this.selectPanel("applying");
+ let update = this.um.activeUpdate;
+ let self = this;
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // Update the UI when the background updater is finished
+ let status = aData;
+ if (status == "applied" || status == "applied-service" ||
+ status == "pending" || status == "pending-service") {
+ // If the update is successfully applied, or if the updater has
+ // fallen back to non-staged updates, show the Restart to Update
+ // button.
+ self.selectPanel("updateButtonBox");
+ self.setupUpdateButton("update.restart." +
+ (self.isMajor ? "upgradeButton" : "updateButton"));
+ } else if (status == "failed") {
+ // Background update has failed, let's show the UI responsible for
+ // prompting the user to update manually.
+ self.selectPanel("downloadFailed");
+ } else if (status == "downloading") {
+ // We've fallen back to downloading the full update because the
+ // partial update failed to get staged in the background.
+ // Therefore we need to keep our observer.
+ self.setupDownloadingUI();
+ return;
+ }
+ Services.obs.removeObserver(arguments.callee, "update-staged");
+ }, "update-staged", false);
+ } else {
+ this.selectPanel("updateButtonBox");
+ this.setupUpdateButton("update.restart." +
+ (this.isMajor ? "upgradeButton" : "updateButton"));
+ }
+ break;
+ default:
+ this.removeDownloadListener();
+ this.selectPanel("downloadFailed");
+ break;
+ }
+
+ },
+
+ /**
+ * See nsIProgressEventSink.idl
+ */
+ onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
+ },
+
+ /**
+ * See nsIProgressEventSink.idl
+ */
+ onProgress: function(aRequest, aContext, aProgress, aProgressMax) {
+ this.downloadStatus.value =
+ DownloadUtils.getTransferTotal(aProgress, aProgressMax);
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIProgressEventSink) &&
+ !aIID.equals(Components.interfaces.nsIRequestObserver) &&
+ !aIID.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+#endif
diff --git a/application/palemoon/base/content/aboutDialog.xul b/application/palemoon/base/content/aboutDialog.xul
new file mode 100644
index 000000000..1ba8f06a1
--- /dev/null
+++ b/application/palemoon/base/content/aboutDialog.xul
@@ -0,0 +1,120 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/aboutDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://branding/content/aboutDialog.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
+%aboutDialogDTD;
+]>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="PMaboutDialog"
+ windowtype="Browser:About"
+ onload="init(event);"
+#ifdef MOZ_UPDATER
+ onunload="onUnload(event);"
+#endif
+#ifdef XP_MACOSX
+ inwindowmenu="false"
+#else
+ title="&aboutDialog.title;"
+#endif
+ role="dialog"
+ aria-describedby="version distribution distributionId communityDesc contributeDesc trademark"
+ >
+
+ <script type="application/javascript" src="chrome://browser/content/aboutDialog.js"/>
+
+ <vbox id="aboutPMDialogContainer">
+ <hbox id="PMclientBox">
+ <vbox id="PMleftBox" flex="1"/>
+ <vbox id="PMrightBox" flex="1">
+#ifdef HAVE_64BIT_BUILD
+#expand <label id="PMversion">Version: __MOZ_APP_VERSION__ (64-bit)</label>
+#else
+#expand <label id="PMversion">Version: __MOZ_APP_VERSION__ (32-bit)</label>
+#endif
+ <label id="distribution" class="text-blurb"/>
+ <label id="distributionId" class="text-blurb"/>
+
+ <vbox id="detailsBox">
+ <vbox id="updateBox">
+#ifdef MOZ_UPDATER
+ <deck id="updateDeck" orient="vertical">
+ <hbox id="updateButtonBox" align="center">
+ <button id="updateButton" align="start"
+ oncommand="gAppUpdater.buttonOnCommand();"/>
+ <spacer flex="1"/>
+ </hbox>
+ <hbox id="checkingForUpdates" align="center">
+ <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
+ </hbox>
+ <hbox id="checkingAddonCompat" align="center">
+ <image class="update-throbber"/><label>&update.checkingAddonCompat;</label>
+ </hbox>
+ <hbox id="downloading" align="center">
+ <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
+ </hbox>
+ <hbox id="applying" align="center">
+ <image class="update-throbber"/><label>&update.applying;</label>
+ </hbox>
+ <hbox id="downloadFailed" align="center">
+ <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
+ </hbox>
+ <hbox id="adminDisabled" align="center">
+ <label>&update.adminDisabled;</label>
+ </hbox>
+ <hbox id="noUpdatesFound" align="center">
+ <label>&update.noUpdatesFound;</label>
+ </hbox>
+ <hbox id="manualUpdate" align="center">
+ <label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
+ </hbox>
+ </deck>
+#endif
+ </vbox>
+
+ <description class="text-pmcreds">
+ Pale Moon is released by <label class="text-link" href="http://www.moonchildproductions.info">Moonchild Productions</label>.
+ </description>
+ <description class="text-pmcreds">
+ Special thanks to all our supporters and donors for making this browser possible!
+ </description>
+ <description class="text-blurb">
+ If you wish to contribute, please consider helping out by providing support to other users on the <label class="text-link" href="https://forum.palemoon.org/">Pale Moon forum</label>
+ or getting involved in our development by tackling some of the issues found in our GitHub issue tracker.
+ </description>
+ </vbox>
+ </vbox>
+ </hbox>
+ <vbox id="PMbottomBox">
+ <hbox pack="center">
+ <label class="text-link bottom-link" href="about:license">Licensing information</label>
+ <label class="text-link bottom-link" href="about:rights">End-user rights</label>
+ <label class="text-link bottom-link" id="releaseNotesURL">Release notes</label>
+ </hbox>
+ <description id="PMtrademark">&trademarkInfo.part1;</description>
+ </vbox>
+ </vbox>
+
+ <keyset>
+ <key keycode="VK_ESCAPE" oncommand="window.close();"/>
+ </keyset>
+
+#ifdef XP_MACOSX
+#include browserMountPoints.inc
+#endif
+</window>
diff --git a/application/palemoon/base/content/aboutRobots-icon.png b/application/palemoon/base/content/aboutRobots-icon.png
new file mode 100644
index 000000000..1c4899aaf
--- /dev/null
+++ b/application/palemoon/base/content/aboutRobots-icon.png
Binary files differ
diff --git a/application/palemoon/base/content/aboutRobots-widget-left.png b/application/palemoon/base/content/aboutRobots-widget-left.png
new file mode 100644
index 000000000..3a1e48d5f
--- /dev/null
+++ b/application/palemoon/base/content/aboutRobots-widget-left.png
Binary files differ
diff --git a/application/palemoon/base/content/aboutRobots.xhtml b/application/palemoon/base/content/aboutRobots.xhtml
new file mode 100644
index 000000000..23fe3ba17
--- /dev/null
+++ b/application/palemoon/base/content/aboutRobots.xhtml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorDTD
+ SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutrobotsDTD
+ SYSTEM "chrome://browser/locale/aboutRobots.dtd">
+ %aboutrobotsDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&robots.pagetitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+ <link rel="icon" type="image/png" id="favicon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACGFjVEwAAAASAAAAAJNtBPIAAAAaZmNUTAAAAAAAAAAQAAAAEAAAAAAAAAAALuAD6AABhIDeugAAALhJREFUOI2Nk8sNxCAMRDlGohauXFOMpfTiAlxICqAELltHLqlgctg1InzMRhpFAc%2BLGWTnmoeZYamt78zXdZmaQtQMADlnU0OIAlbmJUBEcO4bRKQY2rUXIPmAGnDuG%2FBx3%2FfvOPVaDUg%2BoAPUf1PArIMCSD5glMEsUGaG%2BkyAFWIBaCsKuA%2BHGCNijLgP133XgOEtaPFMy2vUolEGJoCIzBmoRUR9%2B7rxj16DZaW%2FmgtmxnJ8V3oAnApQwNS5zpcAAAAaZmNUTAAAAAEAAAAQAAAAEAAAAAAAAAAAAB4D6AIB52fclgAAACpmZEFUAAAAAjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9WF%2Bu8QAAABpmY1RMAAAAAwAAABAAAAAQAAAAAAAAAAAAHgPoAgEK8Q9%2FAAAAFmZkQVQAAAAEOI1jYBgFo2AUjAIIAAAEEAAB0xIn4wAAABpmY1RMAAAABQAAABAAAAAQAAAAAAAAAAAAHgPoAgHnO30FAAAAQGZkQVQAAAAGOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVfozYcAAAABpmY1RMAAAABwAAABAAAAAQAAAAAAAAAAAAHgPoAgEKra7sAAAAFmZkQVQAAAAIOI1jYBgFo2AUjAIIAAAEEAABM9s3hAAAABpmY1RMAAAACQAAABAAAAAQAAAAAAAAAAAAHgPoAgHn3p%2BwAAAAKmZkQVQAAAAKOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F1BhPl6AAAAGmZjVEwAAAALAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQpITFkAAAAWZmRBVAAAAAw4jWNgGAWjYBSMAggAAAQQAAHaszpmAAAAGmZjVEwAAAANAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeeCPiMAAABAZmRBVAAAAA44jWNgGJ5gpxrDf2LEcIL%2FpzAVYxPDavP%2FUwz%2FpW79%2F%2F%2F%2FFMP%2FnWoQjC5GOxcgu4QYsVEwCmAAAOE0KxUmBL0KAAAAGmZjVEwAAAAPAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQoU7coAAAAWZmRBVAAAABA4jWNgGAWjYBSMAggAAAQQAAEpOBELAAAAGmZjVEwAAAARAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeYVWtoAAAAqZmRBVAAAABI4jWNgGAVYQXNz839ixHBq3qnG8B9ZAzYx2rlgFIwCcgAA8psX%2FWvpAecAAAAaZmNUTAAAABMAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC4OJMwAAABZmZEFUAAAAFDiNY2AYBaNgFIwCCAAABBAAAcBQHOkAAAAaZmNUTAAAABUAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5kn7SQAAAEBmZEFUAAAAFjiNY2AYnmCnGsN%2FYsRwgv%2BnMBVjE8Nq8%2F9TDP%2Blbv3%2F%2F%2F8Uw%2F%2BdahCMLkY7FyC7hBixUTAKYAAA4TQrFc%2BcEoQAAAAaZmNUTAAAABcAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC98ooAAAABZmZEFUAAAAGDiNY2AYBaNgFIwCCAAABBAAASCZDI4AAAAaZmNUTAAAABkAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5qwZ%2FAAAACpmZEFUAAAAGjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9cjJWbAAAABpmY1RMAAAAGwAAABAAAAAQAAAAAAAAAAAAHgPoAgELOsoVAAAAFmZkQVQAAAAcOI1jYBgFo2AUjAIIAAAEEAAByfEBbAAAABpmY1RMAAAAHQAAABAAAAAQAAAAAAAAAAAAHgPoAgHm8LhvAAAAQGZkQVQAAAAeOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVlxR3%2FgAAABpmY1RMAAAAHwAAABAAAAAQAAAAAAAAAAAAHgPoAgELZmuGAAAAFmZkQVQAAAAgOI1jYBgFo2AUjAIIAAAEEAABHP5cFQAAABpmY1RMAAAAIQAAABAAAAAQAAAAAAAAAAAAHgPoAgHlgtAOAAAAKmZkQVQAAAAiOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F0%2FMvDdAAAAAElFTkSuQmCC"/>
+
+ <script type="application/javascript"><![CDATA[
+ var buttonClicked = false;
+ function robotButton()
+ {
+ var button = document.getElementById('errorTryAgain');
+ if (buttonClicked) {
+ button.style.visibility = "hidden";
+ } else {
+ var newLabel = button.getAttribute("label2");
+ button.textContent = newLabel;
+ buttonClicked = true;
+ }
+ }
+ ]]></script>
+
+ <style type="text/css"><![CDATA[
+ #errorPageContainer {
+ background-image: none;
+ }
+
+ #errorPageContainer:before {
+ content: url('chrome://browser/content/aboutRobots-icon.png');
+ position: absolute;
+ }
+
+ body[dir=rtl] #icon,
+ body[dir=rtl] #errorPageContainer:before {
+ transform: scaleX(-1);
+ }
+ ]]></style>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText">&robots.errorTitleText;</h1>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText">&robots.errorShortDescText;</p>
+ </div>
+
+ <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
+ <div id="errorLongDesc">
+ <ul>
+ <li>&robots.errorLongDesc1;</li>
+ <li>&robots.errorLongDesc2;</li>
+ <li>&robots.errorLongDesc3;</li>
+ <li>&robots.errorLongDesc4;</li>
+ </ul>
+ </div>
+
+ <!-- Short Description -->
+ <div id="errorTrailerDesc">
+ <p id="errorTrailerDescText">&robots.errorTrailerDescText;</p>
+ </div>
+
+ </div>
+
+ <!-- Button -->
+ <button id="errorTryAgain"
+ label2="&robots.dontpress;"
+ onclick="robotButton();">&retry.label;</button>
+
+ <img src="chrome://browser/content/aboutRobots-widget-left.png"
+ style="position: absolute; bottom: -12px; left: -10px;"/>
+ <img src="chrome://browser/content/aboutRobots-widget-left.png"
+ style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/>
+ </div>
+
+ </body>
+</html>
diff --git a/application/palemoon/base/content/abouthome/aboutHome.css b/application/palemoon/base/content/abouthome/aboutHome.css
new file mode 100644
index 000000000..73c686202
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/aboutHome.css
@@ -0,0 +1,339 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+html {
+ font: message-box;
+ font-size: 100%;
+ background-color: hsl(0,0%,90%);
+ color: #000;
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ width: 100%;
+ height: 100%;
+ background-image: url(chrome://browser/content/abouthome/noise.png),
+ linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.4));
+}
+
+input,
+button {
+ font-size: inherit;
+ font-family: inherit;
+}
+
+a {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.spacer {
+ -moz-box-flex: 1;
+}
+
+#topSection {
+ text-align: center;
+}
+
+#brandLogo {
+ height: 192px;
+ width: 192px;
+ margin: 22px auto 31px;
+ background-image: url("chrome://branding/content/about-logo.png");
+ background-size: 192px auto;
+ background-position: center center;
+ background-repeat: no-repeat;
+}
+
+#searchForm {
+ width: 470px;
+}
+
+#searchForm {
+ display: -moz-box;
+}
+
+#searchLogoContainer {
+ display: -moz-box;
+ -moz-box-align: center;
+ padding-top: 2px;
+ -moz-padding-end: 8px;
+}
+
+#searchLogoContainer[hidden] {
+ display: none;
+}
+
+#searchEngineLogo {
+ display: inline-block;
+ height: 28px;
+ width: 70px;
+ min-width: 70px;
+}
+
+#searchText {
+ -moz-box-flex: 1;
+ padding: 6px 8px;
+ background: hsla(0,0%,100%,.9) padding-box;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+ 0 0 2px hsla(210,65%,9%,.1) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ border-radius: 2.5px 0 0 2.5px;
+}
+
+#searchText:-moz-dir(rtl) {
+ border-radius: 0 2.5px 2.5px 0;
+}
+
+#searchText:focus,
+#searchText[autofocus] {
+ border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
+}
+
+#searchSubmit {
+ -moz-margin-start: -1px;
+ background: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+ padding: 0 9px;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ -moz-border-start: 1px solid transparent;
+ border-radius: 0 2.5px 2.5px 0;
+ box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+#searchSubmit:-moz-dir(rtl) {
+ border-radius: 2.5px 0 0 2.5px;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText + #searchSubmit:hover,
+#searchText[autofocus] + #searchSubmit {
+ border-color: #59b5fc #45a3e7 #3294d5;
+ color: white;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[autofocus] + #searchSubmit {
+ background-image: linear-gradient(#4cb1ff, #1793e5);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03);
+}
+
+#searchText + #searchSubmit:hover {
+ background-image: linear-gradient(#66bdff, #0d9eff);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03),
+ 0 0 4px hsla(206,100%,20%,.2);
+}
+
+#searchText + #searchSubmit:hover:active {
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
+ 0 0 1px hsla(211,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+#launcher {
+ display: -moz-box;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ width: 100%;
+ background-color: hsla(0,0%,0%,.03);
+ border-top: 1px solid hsla(0,0%,0%,.03);
+ box-shadow: 0 1px 2px hsla(0,0%,0%,.02) inset,
+ 0 -1px 0 hsla(0,0%,100%,.25);
+}
+
+#launcher:not([session]),
+body[narrow] #launcher[session] {
+ display: block; /* display separator and restore button on separate lines */
+ text-align: center;
+ white-space: nowrap; /* prevent navigational buttons from wrapping */
+}
+
+.launchButton {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ margin: 16px 1px;
+ padding: 14px 6px;
+ min-width: 88px;
+ max-width: 176px;
+ max-height: 85px;
+ vertical-align: top;
+ white-space: normal;
+ background: transparent padding-box;
+ border: 1px solid transparent;
+ border-radius: 2.5px;
+ color: #525c66;
+ font-size: 75%;
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+body[narrow] #launcher[session] > .launchButton {
+ margin: 4px 1px;
+}
+
+.launchButton:hover {
+ background-color: hsla(211,79%,6%,.03);
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+}
+
+.launchButton:hover:active {
+ background-image: linear-gradient(hsla(211,79%,6%,.02), hsla(211,79%,6%,.05));
+ border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.23) hsla(210,54%,20%,.25);
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.05) inset,
+ 0 0 1px hsla(211,79%,6%,.1) inset;
+ transition-duration: 0ms;
+}
+
+.launchButton[hidden],
+#launcher:not([session]) > #restorePreviousSessionSeparator,
+#launcher:not([session]) > #restorePreviousSession {
+ display: none;
+}
+
+#restorePreviousSessionSeparator {
+ width: 3px;
+ height: 116px;
+ margin: 0 10px;
+ background-image: linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-position: left top, center, right bottom;
+ background-size: 1px auto;
+ background-repeat: no-repeat;
+}
+
+body[narrow] #restorePreviousSessionSeparator {
+ margin: 0 auto;
+ width: 512px;
+ height: 3px;
+ background-image: linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(to right, hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-size: auto 1px;
+}
+
+#restorePreviousSession {
+ max-width: none;
+ font-size: 90%;
+}
+
+body[narrow] #restorePreviousSession {
+ font-size: 80%;
+}
+
+.launchButton::before {
+ display: block;
+ width: 32px;
+ height: 32px;
+ margin: 0 auto 6px;
+ line-height: 0; /* remove extra vertical space due to non-zero font-size */
+}
+
+#downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads.png");
+}
+
+#bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks.png");
+}
+
+#history::before {
+ content: url("chrome://browser/content/abouthome/history.png");
+}
+
+#addons::before {
+ content: url("chrome://browser/content/abouthome/addons.png");
+}
+
+#sync::before {
+ content: url("chrome://browser/content/abouthome/sync.png");
+}
+
+#settings::before {
+ content: url("chrome://browser/content/abouthome/settings.png");
+}
+
+#restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large.png");
+ height: 48px;
+ width: 48px;
+ display: inline-block; /* display on same line as text label */
+ vertical-align: middle;
+ margin-bottom: 0;
+ -moz-margin-end: 8px;
+}
+
+#restorePreviousSession:-moz-dir(rtl)::before {
+ transform: scaleX(-1);
+}
+
+body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore.png");
+ height: 32px;
+ width: 32px;
+}
+
+/* [HiDPI]
+ * At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
+ * rather than upscaling the original-size ones (bug 818940).
+ */
+@media not all and (max-resolution: 1dppx) {
+ #brandLogo {
+ background-image: url("chrome://branding/content/about-logo@2x.png");
+ }
+
+ .launchButton::before {
+ transform: scale(.5);
+ transform-origin: 0 0;
+ }
+
+ #downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads@2x.png");
+ }
+
+ #bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks@2x.png");
+ }
+
+ #history::before {
+ content: url("chrome://browser/content/abouthome/history@2x.png");
+ }
+
+ #addons::before {
+ content: url("chrome://browser/content/abouthome/addons@2x.png");
+ }
+
+ #sync::before {
+ content: url("chrome://browser/content/abouthome/sync@2x.png");
+ }
+
+ #settings::before {
+ content: url("chrome://browser/content/abouthome/settings@2x.png");
+ }
+
+ #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large@2x.png");
+ }
+
+ body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore@2x.png");
+ }
+}
+
diff --git a/application/palemoon/base/content/abouthome/aboutHome.js b/application/palemoon/base/content/abouthome/aboutHome.js
new file mode 100644
index 000000000..9e826b69e
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/aboutHome.js
@@ -0,0 +1,354 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const SEARCH_ENGINES = {
+ "Google": {
+ // This is the "2x" image designed for OS X retina resolution, Windows at 192dpi, etc.;
+ // it will be scaled down as necessary on lower-dpi displays.
+ image: "data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ" +
+ "bWFnZVJlYWR5ccllPAAAGrFJREFUeNrtfHt4VdW172+utZOASLJ5+BaIFrUeXkFsa0Fl++gDnznV" +
+ "VlvFxt7aqvUUarXtse3Bau35ak/rZ9XT26NtfOvV6wFET+FYCQEKWqsQIT5RCAgSXnlnrzXneNw/" +
+ "1lphJSSQ8BB7bub3zW+LO3uN+fiNMcf4jTEX0N/6W3/rb/2tv30smtnXB3zmRi2FQakxQNKX3WkW" +
+ "9S/tgW3HLpmQM543A0BWVSHMYGIwOTDxzxrOf3/RQQfMZ2/SLAvKhTFVBGUqKFONH2QAzwOMF38a" +
+ "wHhYZAxWAqhe/iszp3+b970d/sInc57vz/J8L2eMB2MAEYkBQ6DQ3dRw4dq7AUjcP3rAfPZmLWXC" +
+ "LHKoIAcQAUxaB5EaEfc6AEBhjDEwmcx43/fO9HxT4vkReBIAAZgjgodW3NcPnn1sHgD/iHknn+0d" +
+ "6s8XEUhsXXac/34WAAGw8afuT8GZ3X055YeSJcIsG+pMZwFn0UihezRofPt3G54f/0E8cNMN+Myo" +
+ "8jVTCgYd823PLzrPeIBnABiUQ1F+UoWsVOYb33mkoKp/7/dKyT0AGc47X4s0sjBEoLxbBqAQAMfW" +
+ "Rfe38B4BM+VHUkYOs8mi1FrABbK4dcvK73zwp1M3xYPOxANKBqbpCdXNGb0UwPKRF74xpfDQ0t+K" +
+ "54+IvlKoahmAhaO/mv/ZmicG3tqPgT61ZM2dZMQJOYhIdByRM/F3dCCOox4Bc3oEliqyyNoQCPPu" +
+ "sXceKZqRsigu7pwaWBowiRb46+f9Q1V2wl1nDx09/R7jF30x9adNlN8yPx4DHwht+B/cBIBoRqeI" +
+ "E4hE/oshTcB0wNbT6/o/zrhFyohR5ZxmrVWE+fDxdx4puhGAH4OkPe5B6pykeJAc/7cDEMZ/095Y" +
+ "870P339m+BXs2v4kbCFsm9u2vnpJ3bzR7wAo2B/R2v+PjSnyXcRxtOLUSXFxwAFz5i2SZUIVO82S" +
+ "BWye/vLOIwNvjL8OYqCEfXCmJAZPHkC7sK1REbj2+lmbq86qTVmmfuuyN2cTiREWKCvACgml9kDL" +
+ "7HQksehsZmSdA6yVpsa6P38v3swg7m4vN1dGXrThKGP8yS5fP33j/LEvxKDbl2f2A0YFCtkZQDOa" +
+ "PjLAnP4jrmBGjh1AVhG2ttxfX33++vjY2eeNXf/siLUAzgEwMJZrY2vF/Vu/t4BRqCqgCmj07wMV" +
+ "HXUCzJQfUlZE72ICnANcqNj21h8eiK1AX46gXh29KT9H+rd9XxBjYGCgig7QHOgjPgMAKigXQZYp" +
+ "si4uCOc3v35zY2wF9ufGSgxA7fdd9g8ho9ol4P4ojiQWnSUMMANECrJNy1NWYH8eGfsEvJbLv1IK" +
+ "1XIAUwEtA0xplJMwjcaYlTDeShg8dOgjj6/cJxNYfWIWkHJoh5yyjkSZ8RbB89YBZq4/pXafGeuz" +
+ "b9WciXJxo2B2houqgAjABJCLOwFMqFv57+bBxMIAJm1det3avnl1OYCLAeSgWhofaY1QXQSRuYc+" +
+ "/OiD3QLmUzNdqTBKhRVMADsF5beuToXJB90KtFz+lVIVniXOVUAUqjpXVB4WwPjGTPB8/0zjeTnj" +
+ "ezl43szmKy6vNkDF4MeeXNc3oJyUhfAMkJsJkSxUVrLos6o6z/O8Ucb3phrPzyHKeVTwkpPXseg3" +
+ "Cqe+1SfG+swfaw6KGTAoJ5eyGF3IBeEIJB2AcXxb0FI/L45uFQBMGiu6Z3ai9eqrclBUClFWVatV" +
+ "5GERNT5wEVQnQLUcIuVNX75kFjn60rA5c1d0AoywlkcxfdwZ2LSgbOmBZAv70povu7RcyFUqcZYd" +
+ "Pbxix44fnLv8pbYUOWh+P3ZM9uJRo34xoLDgq8b3YTxvqhqsaPzyJTdmn36msjdyqPqkMhWqBFGZ" +
+ "MtV8uDX4zMjp2zemyEoPgGn4zyOvGzy48A54GcD3Sz1jFrqqE+4uOOvdmb0ASlYEs5mQE9afUdhy" +
+ "0yv3lHzwya/8ZcjgI0+5yssU3QKYkgQ4Ivp60LL1n8kBQfOWuvdnj6uLldgHQKoKxU7HV/eg2y1X" +
+ "XXmXEs1U0ZVb29o//4k5c5P5eQB+s+68aVeUFBTcCxUoS6kRWfjhueecc9SfX3ytA9QTr7eVACqY" +
+ "FDYEwnbB2qcHHg6gLY6ODhpomi77coUyVaojhKH9+ZHzF/wqXiztEg34APxNX/jCvQOLCi83fpy8" +
+ "UsCJXHLYnGdn785S0uKTyyBUBXJZcW5x4bSN56ciyLQcD4Bf/+ThVwwbUvRb+JkoswqAWX5b9Lm1" +
+ "M3uSM/UnUiaCKiZk2blvvnxX0ePxuBNAmpMur51wyLBPzjVeBBoVwIXBk6vuP+SG+LkcuwkWAA96" +
+ "/JjZKnKxkACkkFb5Nztz220xX9bJlWi+6opKFalQlpqlmzZNu6B6SaJ0knKJ/DW5qd8p8TO3x6AB" +
+ "qza1EE06cdmy9wDAY5LjmBTMkQnUnZ42H0ywNF52aU6FK4UY5NySI+cv+E3MCnMM5HyqtwFoO3rB" +
+ "gmuDMFjGjiCOIEQwzH9c+7lzju+JTaYlJ2ehUqXMWWFqeurFxqsAFMVf25Ss9kTOEZdvebClJbxT" +
+ "yUGZoEzwlL/b9tzRX+pOztSfSBZApSqyIrL45buKnkaUJEzLCN5+csxr+ab6fyILkI2OIZYBlx9/" +
+ "2bYvpLgw2+EqKLKdwoceVKJp+tfuEpYKZcaW1tZbLqheEsbj3GV+oxdV3x0GwQZrHUIiWKIST3Vm" +
+ "DG54zFrKrBBWiGgSyx9Uv6Xh0n/MKlGlOII4h80trQ+kuJt8HGklZHg6FZF/Y/uOb7O1YOvAzkGt" +
+ "Kxmoehe6SYNEpkErwZIFC4I2fuLKf2tLtDOPzumPhA6wAPJDLt1yuzjaAEcAMUCMApXfvPP7IcO6" +
+ "gkYFs4RRpgy49qanUsAPu/T8W48e/YwL6S/kYtBYwM8U/yu6KVlQUShr9CkKyK7b1vDVy0qVeaYy" +
+ "gaxbdeK85/8a/z7sYR3zgXM1gXUInEPoCEw8PR6z8YQxaidQPh6RrgrPEOZS4chKjFuydEEKFD1x" +
+ "QgrAnfO3V98Jw/B5dhFgmByU+MK/nnrq6K6gcQtPyqlIubJAibCxPv/fsVVNgCI9yGEAQdBq71NH" +
+ "UEdQIoBo5PBBeklazuQfSpYFM0UAFsDmd2yMf9+1XkUT3otc8AiRwpFChCBCI0detGbSLtYr5uw6" +
+ "tk26XctZwgxhRt65ZSmr1t389M1Jk85wzKcHRAiJkCfasDnI/0sMGN+jlLMrAigMhp0+f+TBBIw4" +
+ "milEYOcQBHZZAoZeEIgKgIIgeJbD2MqEFhxaDAFmdAWMisxQFigzlAUnX9e4rA9yeHuTna3koBQB" +
+ "RogxwOPvxNbQAAA7VHQEFKSQKEFIu4lA5d3HiiuFNB4XQZlhUHBK11QO0oRdD7ouROVCkeJZG7ak" +
+ "/KBOYHlz4sTy1WVlVY5oYego2+bs82+3tFw6YcVrp01dteqpxNfyhKQuGlxCMSsKBh570ABT/8XP" +
+ "5dhRVpyDWAd2Ns0O9yrhWdfcMpvCEByEoNCCwhBgvgBdM+PM5TH5FPW+1ZLo8de2viehe12dhVoH" +
+ "OAtDPO61O4o+kYCTnE5wVuGsxlzKHul7BUDKdomKgwpB2QHAyNiP2Dl+0Z2WRXZ9YP0F55WJczvX" +
+ "0jp09U3fLiurWD1+/NqQaHZIVNbu3O1vt7aM+fSqVRWXvPvu0pRldwAkQ5brjO+NMh0kgMIvGjYZ" +
+ "wIKETPxIrYt1U5M8iThKJil9yZGc++ab298dP36Jb8wZohqhQHRErKEeAA6fG5FT5yIlYYI6tzfO" +
+ "vtiQni3MYDw0ChqEgUMyejyAdwGwDeW4ZI9FAGQOmwzgv/cERmZbDXhnKBNUGMJkUhGVduSSJJ1P" +
+ "6rw8HIalJo7ilBkchgCgL48fVzLceDc4kZnWUdap1AQi10x+660n4jXyk1M7ZXEZgHhMUkMO4Njp" +
+ "hQGMf8h56Fx++ZE1a+1xZC2Szjs3sk9uUEhUbSMvP3LeyOGZ0tKJiearo1J1DHVRPYmS7JUcG2g1" +
+ "pxxUsooBnpmQWAOb10YbKGygcKFCZOC0XqxrRKokCBQG5euX77In2k1P+2hhWEZBAAoCuCCEcW7E" +
+ "2xMn/m6oYo0jyjnmuc3Off6UN96YMvmtt5LILSmQ61r3xAA0I+xqPBiIejAd1f7e2MPPfvm4LQs/" +
+ "89a+bP6nZuSzfsaU+T7g+UBixYQVRFGS01kFO22srRy0EgA4CEvFRHS3MANMY/fGbybmlQqAFSBV" +
+ "sCp8kWwCGA5dqefFShnnRV77ecHYU37iXuqLoB0tsuIo34v3NfJR1GlJsrnOuiXGy1y8k+rwxh57" +
+ "3srSD/6rbLdra7yMqgjUCGAULR8uWr0LJPYAGApCeCbKNygLPKIxJ65YOSU+YpLUUCYGiqBzQVy3" +
+ "Ft1zbevnJl60UARqACgcVDo9ZZr63Mqua68QxlpmrWJC1FmrmLSKCFVktcpZrbKhzg4D26E5Lgjg" +
+ "8vnoMwwh1hU/dvTRo/qcDyJqcESw5Dp6o3XNHVrqLDSubAdFjuXwwWZcX+Wc9APboKxQUoiLurXa" +
+ "IYfCpjlCDsoxZ6OCouLRt+xpbY3nA8aDMR6E2+9vffOWxl02cQ+Bbdjevt7l83D5ABRaKNHYO484" +
+ "YmgMkoJ4jElCOL8Lz9NN87YumrRDxc2DElQZKgIVhZcZcO1hZ74wtK/H0thvtuXGXdM2S0S/ziQ1" +
+ "FPJiG7pHwvbgDhtKnQ0VNhCEeUHQLmiuf2fymieGvJGY8DCfX+yCEC5xWIlwtO+P6+s4VESJGS4+" +
+ "liwxKjZ/2FGRZvPhYgktxEZdHWOAr2P34ihWIQWTgJ2CnWJbo9Ymz1g/5+h1QsF9wgKJ19Z4hV87" +
+ "4fKNE3cnx8v4V8H4UOjqhvce+zW6qdWVlOvSjQsDlw/WUT4A5QNQGIJDizMPHXR+CiRBb4GSzlYr" +
+ "26Z7vYKSC42nUOPBqA9VU1I0ZOJPEYWj1NvVW/3AoEUAFgO4IzZ1hYk2jf9WUw7IjCIXHUVhXrFp" +
+ "/sQtKZPIoXXr/PjoSkZeoHo6gP/bFyeciECqcHG3IrXp37a2SF3xQNPxRAXgq5nS1bHsDWCYALYA" +
+ "u+h0W/impI8Pad9ec/vAoWVTjV84Nsn5FAwcvmDMN5rOqf1jyatdHzjuGjvThloKYH3b5qVXt775" +
+ "44ZuN1QEKknF3a6ImfDee4tWjBrV6R5Qoeq1AP6Avaxx8gDolhdPXAh2qzQmZFQ4ZhALrj/mvLpT" +
+ "+qhxya0BP5VVZQBkA6jNR0AJ2xUUcjKGjsx4k3PVYUwaJU6rJ3reLiHlHppjBjF3fLYSzU/noEZ8" +
+ "3611VusoVJBVsFWAdezim/3jemSFe+SNIsvCpAhCXf7TBZI+PnTr4nO2t2xcME3ZroYKIouEEqDo" +
+ "xfHfav/GxOttFgBOucGWll0XVqrqXYDWNLz3aG7bsovWp4i2TvkhScLqNBezq/M/zxLBxV2Yx/75" +
+ "yCPP6usc04CJ+B3bcLMwQTiK+0UIwgz1ip8+4pyaYX0x0SnWMkjnYGygkm9nBO0MGzoI2TTDyQBw" +
+ "7ubNawPmeZYZNt5wZhrxX8OHX9yXSTJzGcVgIWasbs8/hc7XRzXM670cg0Vs5H+MHm6u74ucrb/K" +
+ "lAlFPoySoqFFn+rm+OCGV762df2cYWe4fP0M5qDWhoowRIm1/h+s1YZx3wrVOV1LDhXMaGzfXntF" +
+ "46vXtMQRS/clsqRRT9SNd0GMBo6edRStZbKeg4D//ciQIcP2CTDbqsdVKQePq1JMFkXxv4qO9AaM" +
+ "fPGoaeuG9kXp0LkU0wGgMFC1gYAdAeyg0m3IrE3W3mtTvodjRpHq9X3xL4h5Qsq63P/z9ra6LqSc" +
+ "vvmBPkwOTex2lnf4wNee/47fa99NGGVJ8Zl1qP3UPfwkdr15mDDV+Y3Pf+Kh9c9kz9pee89J7dve" +
+ "vaRt+7qLbVv47y5UUKggp3BB/okNz0/aHI8332OaIgELxWDpptQtt6X+Qcu03nVYGQYxjxzl+7/e" +
+ "GyvjdYrCtv31JiW7QTjy6qWj83jF4AeP/MLaodiHRtZBXAihEEIWkq4eSgGmvKGhqpX5d1YEVhiW" +
+ "BaI6Zf6QITN7s5ELhw4tZZavkwhIZMOC1rZfo5s64nPv4+1NzXot2/hYiqKckglH4/7eRojCOosp" +
+ "St6u2ijfS1Hv3I0SdVy5aam9ecumBeOqN8w7aRkxSlMVdRDmRHa4m5xWPKPEusUA6maIrcy/cCKw" +
+ "InASKaCoXrlo2LAH+xpMpAEjLauu2ObaNnxVmZqUHaI8SaR+KnIhTPHCo6ZtOn6vk4qUPNNGnV2P" +
+ "J0ptENweMq92zHBMcMwwIrfMLS6etKdJEnMlCYOZm9YE4dUPkWvsIUckJ/+SZwd5PCEOEBc5rh7j" +
+ "grqf+VfvSc7mO/xZSihVAra3YMY/PqqrUhZVe7C8yRHTBqAVQJuQN5idgJ2ASQAz4PJjptWevKc0" +
+ "RZQ0TQATRWDd/dmFDQ2VeaLH0z4dRVTK9EXZ7IqFJSXH7W6eLw0blntp2NAydGOSqPGVs/5mW9Zc" +
+ "JGKbRSxELIRDCFuIuAmiBa8eMW37rcdc1JDtM+3PYdSp43k9/ulPgmDrsnz+vFBktRWBZYEVKSlU" +
+ "feH5wYPP7u5Hfy4uzi4oLq50IjkSaXrf2vIfBPnV6PlKiwKg0XfyNe2BPkmJ8+oUGeh/bLjNu7En" +
+ "0Gy+w5sppLcyKRra9IZJ98hTvciop9MPSSFUwGTnEjHICsgpyKHYHzjquWMvrJ+wewUENPFjCIAx" +
+ "k3uStyIMbw5FVieWJvJpBE5kgqq+X1VcPGdRcfHMxSUluSUlJbmlUZ+1tKRkLRGVnrZ9Rw12rSLt" +
+ "sDpFg8vmfbpw0HH3wcuMMSaiao2XAbwMjPFhPL/ReN6DfsY8tHHekN0WXR929vqsCpWruFshPEqF" +
+ "o3IyADuWTxgea1rYTbRVeEMmc+SnCwp+OcB4l3kmLq0D4BnzkA/MMUBjvDMXC1DBqlkCFr9N9E//" +
+ "HIZpPyDsQVuTFwsMfP273k8GFeLbvo9izwe8DGA8VMPgIc/D2piALlPFDGWUMqNuazOun/RbeQU7" +
+ "L/zl0cfC+SPOXjG84NBRawCvJNoSE7PiBgr5Xx/MKf7jLnzIbUPKlHVF5C11KgJfD9+shY8Vxjd3" +
+ "0780rEvP8bFDDvnVQGO+lU5MeTDwzM5aTbOzNyrw/XNbWx9JFLknk+sjqjobUHJq9XS/cNj3jZcZ" +
+ "Ac9PwBIDyAeMD2O8RhhvpTFYqYpGqMQOM2UhlFOhsvjfgNJ6ofxyoZaXbHPt8mDNjDU9ACYBbyGA" +
+ "AT/KZEZ/MpO5qciYyRlgROeJGSh0nQCL21Ufmx4EL8dMpqScRt4DFVAAYMCtORx+0Rhz7aFF+GJB" +
+ "BmNM/JKklGo1KlBtHZ474U79P9hZOZcQYb0unD/mwu05qADCZwE4C8Y7I3kTk4kFx+mUuzfMKf5e" +
+ "+rn+rUMq4PR4hFII0gw0xpdvGAWGoDqHf9m8IuV8m2Qtf1pQMPok37+50JhpHlC8EzwRcAzwOqs+" +
+ "Vkv06I+da04nInd3RvuxgCIAhcUTF5zvFQ79oucP+Cy8zIjE6qQnt5Pviu5IqAogVKNCNSrBUte6" +
+ "blnrqi/Vo3O9rI3Pc7cbP6sgGQcAf7rvl3zK908uBKjAGK5jrrmNKKHj/RS3E6L3V2USLUzkZAB4" +
+ "i75pTivwwQMyoKYQ685+QOtScvzUHPbIlJ54ZVsuDPTrZDmnQqUQggo1qkoNRDyFeJ6XGQfjF0fW" +
+ "3O9YWxW6adNzw36Dzm/JKEJ0k7QgtfiSygd1vSrkdZ3jlb6fneT7Y+MN1xrmVX9gbkw9q1MdsemF" +
+ "U5wkpwqSRSw49gfZAcPPHOsVlIww/sBjjPEVnqfGZEQlWKVCjWK31TW/dv56pCruU126TGxPl+US" +
+ "IrAgNQ7TQ+pNukQqfalLNimApvMt6CZMTvsiu3VOJ17XnrNWZ9m85oK8Qmz4sFB+CeXrF29dfOqG" +
+ "1PwKs6fOKyvKjrnb8wrHGD8TWfCOEoX85zb96dgXY9leN2NM+y3SJZG4u7XsSldIykFPz09NHxbR" +
+ "T2U3M11AsKf8aRqtnBqQoG91oWkGOS0/XaQo2Pf3u5mUDK9LukD7Mv5Tv9teSQ4VzipsINUtW9Zc" +
+ "t/mFiRu7WbcOuQNP+MXQ4hGX3mEKBl1mjB9bbwAqSz6cf+TZ8Qaabta/u6hM92ItpZs5dvyor5R/" +
+ "dwvp9QAa6eFzfxRlpVMk2mXh93czeyPn1Bn5ShWtYAJsyEve+OPgC7Hzmgx3USDtejQedlbtDX7h" +
+ "0Ns6HChV5LcvP7rpb1+qx/690dHrtewL05c2c7ZLtrM91fOpDGjXyvT9+WYBPQAg3NPcey1n4vVt" +
+ "FUJSIfGNjJZNy2ekkqzpazIJOefSoTaA9q1VY+5Wbvs9NAoYVBkFh5Sesi9lJ/u6lt5+WETpoi2M" +
+ "PpZU/k9szmKGtVGRWBjQ6g3zP78pxfSGKb+tJ4LPAsi31S/+uXCUlVZmCIc+DlI15L4Cpr/1FA1d" +
+ "0VLqAilzgcCGChdQc5eoTXqpkNS66hv1YLsUElURiG1sOZj7lunf3v3fwlBKjRfX9EjEHKcscV98" +
+ "D40zRKIqgEpz4yvTVnfjU/VbmL/r4yhwTTbPCNsZNi8g50/OnvbCsXu5wQqVURCBuOb7seu98n7A" +
+ "/L23Tc8NX8mW6pL73UoOhYPH/GJv/I7Dzlqbg5pRUG1q++A//+Ng+4f9gDlATVzLHfErZiHioKrn" +
+ "H37uhgeG597sdYnIYeeszypQqQawre9dHNbd0Yj9/5KnfsB8DJpuXXj8Q+ryj3dUZglD1Uz3MsWv" +
+ "HX7uh1fv6QGHn7upAmrWQpEV2zSt+bVptamw+6C9VaP/hcoHrvkABgydUjPLywy6Oboh6HW6PgLj" +
+ "LYqStqYRQHKDMQflMhXOQrnata27tvGvufrEn8ZBfmdPP2AO7NpmAAw85B8qTyjKlt1svAHTjPGL" +
+ "k4w0jAcTAyllnBoh9Kxw/tEdS8cuT0WyH4vX1PYD5qMBzQDE2eFDxz09zsscWuwVHX6a8YwaFAiM" +
+ "NAkHr4vdUdf82rQN6JwnSl4N4vAxeKdxP2A+mjXuKTvcXcY9TdOnyxPk4zKZ/vbRAqe75C3QfZZY" +
+ "0P/y6/7299z+H4QrdGsoib8JAAAAAElFTkSuQmCC"
+ },
+ "DuckDuckGo": {
+ image: "data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAVhUlEQVR4Xu3dd5SU1d3A8e/vPs/0" +
+ "2crussBSdkHEAgoomEQSUTAW3hRbfMUeUwgSj9FoorGXqDGxBHvMazRGE0KsBQuiEVRUEEEM0pfO" +
+ "1tndmZ32PPf3knDCUZAlIYsxOfM553f2v91/vnufOzP33BFV5TOnQFQ1snFN/YCVb88Z6S2dd1B8" +
+ "3Qf7lTSv6R9PNle4uXQEVNRxvUy4qL29pPeGRNXA5d6g4fOLhoyYN2C/oe8Vl5QmAoFAnm72GQqm" +
+ "oKO9vXj5e/NHtr48/fjq92eOq2xYOsixvuMpKFuhfJywjQMYI5oKF7evrR09t/LE7z3Ze9TYZyPx" +
+ "+FpjjPdfEkxBY0ND9ftP//7EkpceOLNm/cJh+J6rylYWcIwSiCHhuEo4ggRdMCLq+UomK5pJq2Y7" +
+ "BD8HqoIAAmKhPdKjuX7EMc9WnfCde/YZOfot13Xz/6HBFKi1pdmlCya23Dz5PPeDN/eygCqAqIn3" +
+ "ULduiAb2Ha3BfUYgJeUgBhxHRAwgoupbfF/wPcXL461bRX7xm5Jb8q7Yhno0lzUYMIANx9Lh0y99" +
+ "svjEc292YkXzAfufE0yBse0tX+qY+uNrOp/+9SGo5yggTlADQw72I4efQGDf4Wg6RW7xO5Jf8g7+" +
+ "ulVi21rRXAr8HKpWRBzFCSGRIpyKSnX6701wv0PU7Vunms2RmfO0ZGc/Z/zWjSKiAqJOdV1LyUVT" +
+ "7wkdcuQvENP8mQ+mQGPZt2ZelLj2nCl+Q30ZAqijoVFH+rGTJiHROJnXniE75znxN64yms8AKghd" +
+ "062DEZVIqQbq9tHwYcdpcL+DNDvvFUlNv1dsywYHA0jAjx512lslF956vkSL5n5Wgymwfq+O/7vx" +
+ "jvZfX/0/+FkXC27N3n7xlOvVlFdp8pFfSnbuC0bTbYKqIOw+BcSoKeut0WNPtZEjjtPOx++X1FMP" +
+ "GPysAXD777epxy1PXuj2qXsEsJ+hYArUy9e2Xn7GtPTLj44AFVVHY1/7tld0+g8l+cht2vnE/Y7N" +
+ "p0S2htJ9FEDUlPWxxZOusE5VjSRunIK3YbkrAhIpzlRMfeGy4P6jbwH8z0AwBZrPDWqacvQzmfkv" +
+ "D0ZETbxCS3/wC9/t1ZeWq78t3oZlDqiwp6nRyJiveMXnXEL7fdeTef1JV9UKKlp118wrQgeNvX5X" +
+ "0Rj2uMJjqOmik/6UmbclFkSdylrb4/qHfU0naTzvK463fqkLKijo1oGt0/3ESudrT7jNPznTxL8x" +
+ "iehXvuUhroJKw6RxV+aWzJ8MyL9vhSmIJm778fT2h244CiPqVg+0Pa64TzPzZtv2X18XUD8jAIiB" +
+ "3nWEK6rBDaHZTmyiCb+lGe1MoGpB6FZOWR+/7KJbbXb+n0lOv8tV64mJlnX2mr74ZKei11PshMue" +
+ "UmA6X3nyqrbf/uxIAKe4l5ZdcqdNz5vNllhc9TKCAIAaQ6puNLEzzqN86EhQRTs78BvWkX3/bTpf" +
+ "mkZm3p/RbAoM3cJrWe+03PB9yn881drOlJd85gHXT7VGG77/1TvK7n1pRThe/MGnuMIU+M2bj91w" +
+ "wrBHbUdDnEDUVlx2n29TbbT8/AIXLy18hAQiFJ8wmdD44wnvPwoxZvs9ENlFb9D2qxvIzH0BxNId" +
+ "VMGtGuBXXPNrm7j7OskueNkBKDnjkudKp1x7ItD5KQRToNavaLzgGy91vjr9ABAtPuUCL/LFo2m8" +
+ "8ETHJlsMwsek9zqEztMvRbw8TjBMqLSU4spKiquqicVjiAgANtVBx8O3kbjvOtTPgPCvUwjufZBX" +
+ "ftEt2njBScZv2+gYN5KvfvCN84N7H3DHpxBMQerNmZc3nHvU5ajnBGqHedW3Psam848jv+I9F2FH" +
+ "4qA4gIJvkHgZgeGHEvzSUZSMP4FQccnHVpvk0w+Seu73ZN57Hc11guFfo6JFX/+uFzpgNE1XnOUi" +
+ "KpEDvriy4p4XxrrB0Jo9GExB0+bNtanvjX/VX7mor6jR6rtmeOk3ZpJ46CZXRKWrx4MTK6fkrB8S" +
+ "n3AqTnkVuAFEgO0qU1Xw8ngbVpO462o6ZjyCGMu/RB3tOfUZr+03t5B5+/kAIhq7/g8/rTrqhEv3" +
+ "YDAFCx+889qiWyZfahVihx2fL598haw7ebRRmzbshCgEBgyj+rY/Eui/F/8UVVp+eTmt918HRvlX" +
+ "hOqGexWX3q4bvn2kg582nZW1awc9vuhL4Whs1R4IpqC1ubnXhm8d/mp45cK9cEK29/0v+22P3Elq" +
+ "xsMBhJ3Ssj7U/OYVwv0GsTvU99h03nGkXnsKEXabqqNVV96b75z9vCRf+kPAEWi5+P4fjvzfs2/e" +
+ "Ay+rC96f9fzYPqsX11mF2EGH+yYal9TMJ4wCKJ9ILAQmXbXbsWSyeVLpPGUX3ULm3Tfxk43sNrG0" +
+ "/eE+Uz7pMk29/Li1Nmeyj917QsexJ9xbVFzcDmDoFgWe5wWysx7/mvq+o1Y0NuEUOp6bpjaXEgV2" +
+ "Nuke/Sg6+n8B8H3LklWNzJq7gtXrW7BW6UpzopN7fj+X+6bNZdqCNuKnnof6oOzmqEr2w/cc9fMa" +
+ "2OsAtQoVq947YPVfFu/XzStMQWtTU1WPJXNHWwWnR28bHjZKWu+9AUVFlE+mkDxoPEXxCNYq055f" +
+ "yKamJGNHD0REUFVA2JlgwOGbJxxMLBKkrSNDONWTjkfvxG/dwO6yXobO2TMl+sVjNPPBO+pmM+FV" +
+ "s18cP3T0597oxmAKNqxYtm9R07oaayG0/0HqNW4mt26Vg4LyycSD7N6jcIFM3iMWDTH5lKEEXId/" +
+ "RFEsxN+VFkfQWDXxcceReHQqGHaPqnS+NctUXnyzlUBIfS8jzvzXxnieF3ZdN+PSLQo6PlhwcMxa" +
+ "Y30IH/h5Mu+/o9bLsCu58l4AhIMuR4/ZG9cx/LNS6RwbGzuorSkjfuTxtP7hLsBntwjkNq0T9TxM" +
+ "RV/1Ni2jdPUH+3q5XNFfgzF0hwLHXfmXA3wFcRwN7zuC9HvviKqC0uXkjYsCIrItlpa2TmbM/pCV" +
+ "a5tR1a5DTWWZ+MNHuPTWGbwwZxnBQfvi9hwAym6PptvFb20kWDsQtRBNbO6ZSyX7dNcjqUA1HG9a" +
+ "308VJF6qblVvydUvQa2KCjtlFGwqScazRAMOAIn2NOdc9kfqN7Ry8jEHcvyRQ6mrKWdn1m5KsHJd" +
+ "C9Fw4G97oKMO+SrBQUPIbVgBwu5RJbP8Qwn03UvVn4FR39H21kFUVi0wdIeCYDjRWKkKpqiHqlr1" +
+ "WpsEdvGfDLgNa2nPeADbVpctEeD7lufnLGXpqka6MnhAJRMnDKdf7zLO/NpIxA0QqKlF7XZ/a+uA" +
+ "bB0UdGcrjKrkN9QT6N0fFVEVcFJt3bXCFKiq6zdtKlYFJxoDL49NZ1GlawLRVYtozfhUFwFA76pi" +
+ "vvyFvXnpjWVUlcU4aP8auuI6hovPOQxVRUQAMOE4WFC2MmEI9YaiUUJ0X0F9yKyGxIuW3AZA+DgF" +
+ "v61ZnPJKRQEFL9FS3k3BFAjq4uWCqkAoiFormvdF6ZoKRFcupjnt8XfhUIDLJx3BN48/mMqyGPFY" +
+ "iF1jWyyqis21E6iGyF5CdD8hMkQI9gYJCFgAiB6oaN7Q8LAFYQeay6iJRFQFVMHx8+HuC6ZAsCoA" +
+ "iICqKICyS6H1S9mcaEf7Fm1bIYJBl9qacrqm4DWguTWgafDbIL8O0u9R/qWn6HGEgxMTAFC2soAB" +
+ "P6G0zrS0PKEggPIxqqBWQURQUO3mE3cF4uG6nirYnAeOYzGOURB2wSTb8NavJrNPLyIBh11jayTN" +
+ "v0TbHgevETQHeKAWALcYQEDZSkBEyayDtlmWttlKvpGthE8WDInN5nRbLMZ43RdMgS/hWEqh3E+m" +
+ "RNygEgqqtrNrCsFlC2g79OBdB6OKpl5G10+C7CpAQYRtRPgYB/x2JTlfScxSUksUzW4XirIDtWDi" +
+ "ZeolWrEWACQUaeuuYApEck5JeTNKX789gRhHnJJS8pvXIkKX1ED0w3m0ZM+muoguaXYxWj8R/CYQ" +
+ "AQSskmsCJw5OVEDA71BSi5S217b+9FOg2/ekXUcc6NmX/MZ1YFUQcGJFm7ormAIh41b1Wm+VAzXZ" +
+ "gteR0GDNYNJL39cthF0IL1tIUzIPFXStcy74jSAGAFWl/lpLxzuKBMCJAgb8JKgHOHyMKv8QMUZD" +
+ "g4aQnPMiKoCIOqU9VnZbMAWSD9UN+QDlWJvJSeYv7xMeOpzEzD8h7Fpw43Kam5rw+xXjGGGnIsPB" +
+ "REHTgGDTkF6tqANY8JJsgwEUAJSPPL0EULoWjGmgujfp5R8KgImVtG0JZhWAoVsUlIz/2jtqRUGl" +
+ "8903NDb8EMSEUNjlmM40/pplpHIeXZHwUKTHZMAFwIkJ1acZghWAgNqPjAIGnDhE66DHl4Wacw0D" +
+ "LjGE+8FOP7VQcCur1cSKNbe+XhSIjfjCMhONd+cepiBYO/hdU1TW6idbyjvemWuqzv2JBqr62OzG" +
+ "FQ67oh7BD9+l/YjDKA4H2CkJID0vJ1OfQJvvI1QjlI8zFB0sZJYr2U3gd4I44JZAsEoI9gS3FCQo" +
+ "CEpmDXgZ2PnLftkS+xc0/eH7+Ml2wUB05Ji54jipbgymwEQi6yNDhi1Mvv3KYdk1SyW3ZqUWjz3G" +
+ "Njw81QgqdEFVCS9ZQFPGUlNC10yUxBt9aLjXEttHKB4txIcKsf3lb+GgoApYthLAQm6j0vqK0vSs" +
+ "Jd8CIjuPsnjcMdoy7TeiqBjj+LERh7wIaDcGUyCO27klkGc7tgSDlzctT/7eVpx8Ng2/uwfVHLsS" +
+ "Wv0+ifYUWhVBROiKWh8vBe3v6t/GhCHYE6IDhUidEKoGEwIvCZl6SP1F6Vyh+B2AbB1lRyiEB+zl" +
+ "B/v0p+PtOQaBQJ8BqyN77/c2QDcHU1AybsLTm35184Vec0NVYsbjUn3uj6Ro9OFe++szAghdcho3" +
+ "0LlpI7naHoRcoStueSXKNvgZSK+GzlWKiO74ASMg0vV7LwCqRstPPlsTzz2Gl2wTMVB82DHPumXl" +
+ "mwvXfewB6vvO6h+c/mDLE787Ra1or8mXeMWHHcmHJx3uiPiGLqgE2XTlg3z+xK9THg3SlbZZM1h+" +
+ "1gTApzsFq+u8QQ8+ydKTxomX2OSYaFHH4N++OD42YvTcPbDCFIjj+JWnn3tX2ysvTMgnmoo3P3CH" +
+ "6XHyWfT46kS/6YmHBFTYCdEcgSXvksh+lfIoXQrVDsKUVOIlNrGdrhaRrlmjvS66yjb+7n7JNW9y" +
+ "cUR7njFlRmz4qPl78H6YgtiBo96s/t4lz6iKesmEs/6Gy2yvC66QQGU/q12djbEQWrqI5lSOXa8E" +
+ "fQgP2ptP+n1N8SCpoPPPnbBT0dIj/icfrhssmx+611GBQGXftupvnX8bIvk9G0xhlfGqTv/2jZEB" +
+ "+zQAND89zU0teFv7Xn6TlUDUdtEMwbVLaG9N4FslmW+gKbOGjN+5wzFNE45QPGY8WFAAC4niEHdM" +
+ "GMjJU0bw4Ji+GPsP9qIQqq6zfS6+Rtb85HzRXMqAY/v+6PpH3PKKN9mOc+WVV9K9CiQQ3Bzdd1iw" +
+ "afrDX1LNO8m359LzrO+pW1yh7W+/blAr7AjJWzoOPZaaAX2Yu/lWHls1ldc2z2VjOklJsILiQBwR" +
+ "wVefXDRAy1N/gnyWv4yu4s4zhzCztox2DAIctaABlF1y4mW29md32y2bdJqfneYCUnzI4cv6XnrD" +
+ "d8SYxKd1e0OBaqz+yose23j/z8cBFA3/gjfw9l/Lxjt+rg2P/soFX9iBQ+OP7mTUWWeyoOkaXtv0" +
+ "KqtTsDxpSfoVfLn34YzoU8bsxnksb23EeWMxxwRyvDGigqVJWJ5U2vLQvznNA3cuIJLz6YqEiuyA" +
+ "a27x1fOov+J8x+bTxo2Xdw6btfDUYK8+j32aN1AViKT6/eS6ye1zXn45tWR+Tce7r7v1V/zQ73/N" +
+ "L0R9z2+Y9oCzQzTWx/1wEa1pH8SwlWDE0JBp5oHVv2eB+jQnhdaUoWNQnIE1LmQUUP4uHzDkHEOY" +
+ "nQSjYCJFtt9lN/kmFmflxZMdm0sbxbGDpj50+5ZYngT49IMpPJqW7TP9pVPf/fy+T3qJTcUtM59y" +
+ "FPEGXHuLOOUV3oZ7fuGieeEjgsvfo7WjE9cN8FECOI5gEEQEgJyFVF7ZnhXBIqiyA1UIlFb5tdff" +
+ "ZlFY+aMpjt/ebFSh/yU/nV467pgrAf/fdItmgVtS9uqwF98620TK0mCl5aUn3OWTT6dq4tky8Of3" +
+ "eSZSZlXZJrC+nmRTC0aibE/4OFVFAWv4GMcqxirbUysaG3yAN+S3T2i+sYHlF37H8doajSr0Ovv7" +
+ "s/qce+E5QPbffO1qQah33+kH/nnhaYHKfq2qKm3vvOYu/to43LIKhr0415aOOTpvNaBWwSSayNav" +
+ "QrR0hzhcP86g6H4MjNUyuuJArjrwO9w06hGOesWl3+oOgr5iBEpSecJZH2vZOiqKG7N9Jl3k7f2b" +
+ "P7Hp/+7RlZed7/rpdqM4ts+5lz5be+2txyHS/hm62Lkg39x05AenfOWejoVv9hdUkIBWTzzHqznv" +
+ "YumYN1fX//JnJvXBItNy7k8lftpgZm28iRVJZXM2yoiKcXx3yERqi3qxvaY/Pcqyb09kc0WQRf3i" +
+ "lKY8Rq5IYBF1wnFKDxtva6ZcaHONTdRffZF0Ll/iYsAEI/m6a29/qPq0b56/LZbPVjAFNpMeuvrK" +
+ "i2/f+ODdY9TmHXwI1dT6vSedpz3GHyvJhfN1VUMSjhljFrb/UuLBfeRzPY+hX7w/O2PzORYePYbk" +
+ "orcQFRXXJVBdo+Vjj7QVx5+MuAHZcPdt2vTsYw54gkKopq55yN2/vano4M/dBmQBPqvBFKiWtc56" +
+ "4YJlF3x3Unb96nIEUKOR2sG28usnafmErxOoHUwwGkLEiCDCNgg70paXnmPNjVdr0fCRWjJmLOEB" +
+ "daRXraDxj7+j9dUXjc2kBFTEuH7VSWfOrbvqpkvc0rI/Awrw2Q+mwPgdHaPX3X3rj9dNvfEom0kF" +
+ "VAEVdYvLtGjoAVo85ggtGf05CfcbqMGqKjGhMB9pRwEBUN/Ha23R9OrlZFatlMRrL2v73NclXb/C" +
+ "qJ8XMQCyJaZD1g687hdTi0aMvh+Rlv/AL9gq0Hw+3PbWnMPX3n7jlLY5s8baXDYEgIIiagIh3NIe" +
+ "Gqqq1EBVb9zyCtxoXDFGbT5n/PaE5ho2mtzmjeSbW/A720R9X8SwTbimf33Pb5zxUO9vTv5VoKKq" +
+ "/r/gK/wKbDYTTi1eNHTzH393SvPzT0/IrF5Zp2KNCFtpF8cqBba/ndVEYqmKCcfP6Xn8xEeLRx78" +
+ "rFtS2oCIAvx3BVMgms/H8q3N+zc9/cTYphlPf/6vIWU3ru+jnufySUTULSpujwzca9mWPcy8skMP" +
+ "e6Xkc4fODlb32iyOk6cb/T/N+faHj8AX2gAAAABJRU5ErkJggg=="
+ }
+};
+
+// This global tracks if the page has been set up before, to prevent double inits
+let gInitialized = false;
+let gObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "searchEngineURL") {
+ setupSearchEngine();
+ if (!gInitialized) {
+ gInitialized = true;
+ }
+ return;
+ }
+ }
+});
+
+window.addEventListener("pageshow", function () {
+ // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
+ // later and may use asynchronous getters.
+ window.gObserver.observe(document.documentElement, { attributes: true });
+ fitToWidth();
+ window.addEventListener("resize", fitToWidth);
+});
+
+window.addEventListener("pagehide", function() {
+ window.gObserver.disconnect();
+ window.removeEventListener("resize", fitToWidth);
+});
+
+function onSearchSubmit(aEvent)
+{
+ let searchTerms = document.getElementById("searchText").value;
+ let searchURL = document.documentElement.getAttribute("searchEngineURL");
+
+ if (searchURL && searchTerms.length > 0) {
+ // Send an event that a search was performed. This was originally
+ // added so Firefox Health Report could record that a search from
+ // about:home had occurred.
+ let engineName = document.documentElement.getAttribute("searchEngineName");
+ let event = new CustomEvent("AboutHomeSearchEvent", {detail: engineName});
+ document.dispatchEvent(event);
+
+ const SEARCH_TOKEN = "_searchTerms_";
+ let searchPostData = document.documentElement.getAttribute("searchEnginePostData");
+ if (searchPostData) {
+ // Check if a post form already exists. If so, remove it.
+ const POST_FORM_NAME = "searchFormPost";
+ let form = document.forms[POST_FORM_NAME];
+ if (form) {
+ form.parentNode.removeChild(form);
+ }
+
+ // Create a new post form.
+ form = document.body.appendChild(document.createElement("form"));
+ form.setAttribute("name", POST_FORM_NAME);
+ // Set the URL to submit the form to.
+ form.setAttribute("action", searchURL.replace(SEARCH_TOKEN, searchTerms));
+ form.setAttribute("method", "post");
+
+ // Create new <input type=hidden> elements for search param.
+ searchPostData = searchPostData.split("&");
+ for (let postVar of searchPostData) {
+ let [name, value] = postVar.split("=");
+ if (value == SEARCH_TOKEN) {
+ value = searchTerms;
+ }
+ let input = document.createElement("input");
+ input.setAttribute("type", "hidden");
+ input.setAttribute("name", name);
+ input.setAttribute("value", value);
+ form.appendChild(input);
+ }
+ // Submit the form.
+ form.submit();
+ } else {
+ searchURL = searchURL.replace(SEARCH_TOKEN, encodeURIComponent(searchTerms));
+ window.location.href = searchURL;
+ }
+ }
+
+ aEvent.preventDefault();
+}
+
+
+function setupSearchEngine()
+{
+ // The "autofocus" attribute doesn't focus the form element
+ // immediately when the element is first drawn, so the
+ // attribute is also used for styling when the page first loads.
+ let searchText = document.getElementById("searchText");
+ searchText.addEventListener("blur", function searchText_onBlur() {
+ searchText.removeEventListener("blur", searchText_onBlur);
+ searchText.removeAttribute("autofocus");
+ });
+
+ let searchEngineName = document.documentElement.getAttribute("searchEngineName");
+ let searchEngineInfo = SEARCH_ENGINES[searchEngineName];
+ let logoElt = document.getElementById("searchEngineLogo");
+
+ // Add search engine logo.
+ if (searchEngineInfo && searchEngineInfo.image) {
+ logoElt.parentNode.hidden = false;
+ logoElt.src = searchEngineInfo.image;
+ logoElt.alt = searchEngineName;
+ searchText.placeholder = "";
+ }
+ else {
+ logoElt.parentNode.hidden = true;
+ searchText.placeholder = searchEngineName;
+ }
+
+}
+
+function fitToWidth() {
+ if (window.scrollMaxX) {
+ document.body.setAttribute("narrow", "true");
+ } else if (document.body.hasAttribute("narrow")) {
+ document.body.removeAttribute("narrow");
+ fitToWidth();
+ }
+}
diff --git a/application/palemoon/base/content/abouthome/aboutHome.xhtml b/application/palemoon/base/content/abouthome/aboutHome.xhtml
new file mode 100644
index 000000000..cb3fa634a
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/aboutHome.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+ %aboutHomeDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+ %browserDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&abouthome.pageTitle;</title>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/content/abouthome/aboutHome.css"/>
+
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/abouthome/aboutHome.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div class="spacer"/>
+ <div id="topSection">
+ <div id="brandLogo"></div>
+
+ <div id="searchContainer">
+ <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)">
+ <div id="searchLogoContainer"><img id="searchEngineLogo"/></div>
+ <input type="text" name="q" value="" id="searchText" maxlength="256"
+ autofocus="autofocus"/>
+ <input id="searchSubmit" type="submit" value="&abouthome.searchEngineButton.label;"/>
+ </form>
+ </div>
+ </div>
+ <div class="spacer"/>
+
+ <div id="launcher">
+ <button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
+ <button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
+ <button class="launchButton" id="history">&abouthome.historyButton.label;</button>
+ <button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
+ <button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
+ <button class="launchButton" id="settings">&abouthome.settingsButton.label;</button>
+ <div id="restorePreviousSessionSeparator"/>
+ <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
+ </div>
+ </body>
+</html>
diff --git a/application/palemoon/base/content/abouthome/addons.png b/application/palemoon/base/content/abouthome/addons.png
new file mode 100644
index 000000000..41519ce49
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/addons.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/addons@2x.png b/application/palemoon/base/content/abouthome/addons@2x.png
new file mode 100644
index 000000000..d4d04ee8c
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/addons@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/bookmarks.png b/application/palemoon/base/content/abouthome/bookmarks.png
new file mode 100644
index 000000000..5c7e194a6
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/bookmarks.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/bookmarks@2x.png b/application/palemoon/base/content/abouthome/bookmarks@2x.png
new file mode 100644
index 000000000..7ede00744
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/bookmarks@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/downloads.png b/application/palemoon/base/content/abouthome/downloads.png
new file mode 100644
index 000000000..3d4d10e7a
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/downloads.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/downloads@2x.png b/application/palemoon/base/content/abouthome/downloads@2x.png
new file mode 100644
index 000000000..d384a22c6
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/downloads@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/history.png b/application/palemoon/base/content/abouthome/history.png
new file mode 100644
index 000000000..ae742b1aa
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/history.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/history@2x.png b/application/palemoon/base/content/abouthome/history@2x.png
new file mode 100644
index 000000000..696902e7c
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/history@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/noise.png b/application/palemoon/base/content/abouthome/noise.png
new file mode 100644
index 000000000..3467cf4d4
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/noise.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/restore-large.png b/application/palemoon/base/content/abouthome/restore-large.png
new file mode 100644
index 000000000..ef593e6e1
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/restore-large.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/restore-large@2x.png b/application/palemoon/base/content/abouthome/restore-large@2x.png
new file mode 100644
index 000000000..d5c71d0b0
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/restore-large@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/restore.png b/application/palemoon/base/content/abouthome/restore.png
new file mode 100644
index 000000000..5c3d6f437
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/restore.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/restore@2x.png b/application/palemoon/base/content/abouthome/restore@2x.png
new file mode 100644
index 000000000..5acb63052
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/restore@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/settings.png b/application/palemoon/base/content/abouthome/settings.png
new file mode 100644
index 000000000..4b0c30990
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/settings.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/settings@2x.png b/application/palemoon/base/content/abouthome/settings@2x.png
new file mode 100644
index 000000000..c77cb9a92
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/settings@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/snippet1.png b/application/palemoon/base/content/abouthome/snippet1.png
new file mode 100644
index 000000000..ce2ec55c2
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/snippet1.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/snippet1@2x.png b/application/palemoon/base/content/abouthome/snippet1@2x.png
new file mode 100644
index 000000000..f57cd0a82
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/snippet1@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/snippet2.png b/application/palemoon/base/content/abouthome/snippet2.png
new file mode 100644
index 000000000..e0724fb6d
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/snippet2.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/snippet2@2x.png b/application/palemoon/base/content/abouthome/snippet2@2x.png
new file mode 100644
index 000000000..40577f52f
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/snippet2@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/sync.png b/application/palemoon/base/content/abouthome/sync.png
new file mode 100644
index 000000000..11e40cc93
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/sync.png
Binary files differ
diff --git a/application/palemoon/base/content/abouthome/sync@2x.png b/application/palemoon/base/content/abouthome/sync@2x.png
new file mode 100644
index 000000000..6354f5bf9
--- /dev/null
+++ b/application/palemoon/base/content/abouthome/sync@2x.png
Binary files differ
diff --git a/application/palemoon/base/content/autorecovery.js b/application/palemoon/base/content/autorecovery.js
new file mode 100644
index 000000000..29ccaed3f
--- /dev/null
+++ b/application/palemoon/base/content/autorecovery.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Auto-recovery module.
+ * This module aims to catch fatal browser initialization errors and either
+ * automatically correct likely causes from them, or automatically restarting
+ * the browser in safe mode. This is hooked into the browser's "onload"
+ * event because it can be assumed that at that point, everything must
+ * have been properly initialized already.
+ */
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+// Services = object with smart getters for common XPCOM services
+Cu.import("resource://gre/modules/Services.jsm");
+
+var browser_autoRecovery =
+{
+ onLoad: function() {
+
+ var nsIAS = Ci.nsIAppStartup; // Application startup interface
+
+ if (typeof gBrowser === "undefined") {
+ // gBrowser should always be defined at this point, but if it is not, then most likely
+ // it is due to an incompatible or outdated language pack being installed and selected.
+ // In this case, we reset "general.useragent.locale" to try to recover browser startup.
+ if (Services.prefs.prefHasUserValue("general.useragent.locale")) {
+ // Restart automatically in en-US.
+ Services.prefs.clearUserPref("general.useragent.locale");
+ Cc["@mozilla.org/toolkit/app-startup;1"].getService(nsIAS).quit(nsIAS.eRestart | nsIAS.eAttemptQuit);
+ } else if (!Services.appinfo.inSafeMode) {
+ // gBrowser isn't defined, and we're not using a custom locale. Most likely
+ // a user-installed add-on causes issues here, so we restart in Safe Mode.
+ let RISM = Services.prompt.confirm(null, "Error",
+ "The Browser didn't start properly!\n"+
+ "This is usually caused by an add-on or misconfiguration.\n\n"+
+ "Restart in Safe Mode?");
+ if (RISM) {
+ Cc["@mozilla.org/toolkit/app-startup;1"].getService(nsIAS).restartInSafeMode(nsIAS.eRestart | nsIAS.eAttemptQuit);
+ } else {
+ // Force quit application
+ Cc["@mozilla.org/toolkit/app-startup;1"].getService(nsIAS).quit(nsIAS.eForceQuit);
+ }
+ }
+ // Something else caused this issue and we're already in Safe Mode, so we return
+ // without doing anything else, and let normal error handling take place.
+ return;
+ } // gBrowser undefined
+
+ // Other checks than gBrowser undefined can go here!
+
+ // Remove our listener, since we don't want this to fire on every load.
+ window.removeEventListener("load", browser_autoRecovery.onLoad, false);
+ }
+};
+
+window.addEventListener("load", browser_autoRecovery.onLoad, false);
diff --git a/application/palemoon/base/content/autorecovery.xul b/application/palemoon/base/content/autorecovery.xul
new file mode 100644
index 000000000..866bdf288
--- /dev/null
+++ b/application/palemoon/base/content/autorecovery.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+
+<overlay
+ id="autorecovery"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/x-javascript" src="chrome://browser/content/autorecovery.js"/>
+
+<!-- This is an empty overlay to allow separation of the script into its
+ own context (needed for locale issues preventing browser start) -->
+
+</overlay>
diff --git a/application/palemoon/base/content/baseMenuOverlay.xul b/application/palemoon/base/content/baseMenuOverlay.xul
new file mode 100644
index 000000000..c6c1b16dc
--- /dev/null
+++ b/application/palemoon/base/content/baseMenuOverlay.xul
@@ -0,0 +1,100 @@
+<?xml version="1.0"?>
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % baseMenuOverlayDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd">
+%baseMenuOverlayDTD;
+]>
+<overlay id="baseMenuOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+#ifdef XP_MACOSX
+<!-- nsMenuBarX hides these and uses them to build the Application menu.
+ When using Carbon widgets for Mac OS X widgets, some of these are not
+ used as they only apply to Cocoa widget builds. All version of Firefox
+ through Firefox 2 will use Carbon widgets. -->
+ <menupopup id="menu_ToolsPopup">
+ <menuitem id="menu_preferences" label="&preferencesCmdMac.label;" key="key_preferencesCmdMac" oncommand="openPreferences();"/>
+ <menuitem id="menu_mac_services" label="&servicesMenuMac.label;"/>
+ <menuitem id="menu_mac_hide_app" label="&hideThisAppCmdMac.label;" key="key_hideThisAppCmdMac"/>
+ <menuitem id="menu_mac_hide_others" label="&hideOtherAppsCmdMac.label;" key="key_hideOtherAppsCmdMac"/>
+ <menuitem id="menu_mac_show_all" label="&showAllAppsCmdMac.label;"/>
+ </menupopup>
+<!-- Mac window menu -->
+#include ../../../toolkit/content/macWindowMenu.inc
+#endif
+
+#ifdef XP_WIN
+ <menu id="helpMenu"
+ label="&helpMenuWin.label;"
+ accesskey="&helpMenuWin.accesskey;">
+#else
+ <menu id="helpMenu"
+ label="&helpMenu.label;"
+ accesskey="&helpMenu.accesskey;">
+#endif
+ <menupopup id="menu_HelpPopup" onpopupshowing="buildHelpMenu();">
+ <menuitem id="menu_openHelp"
+ oncommand="openHelpLink('firefox-help')"
+ onclick="checkForMiddleClick(this, event);"
+ label="&productHelp.label;"
+ accesskey="&productHelp.accesskey;"
+#ifdef XP_MACOSX
+ key="key_openHelpMac"/>
+#else
+ />
+#endif
+ <menuitem id="troubleShooting"
+ accesskey="&helpTroubleshootingInfo.accesskey;"
+ label="&helpTroubleshootingInfo.label;"
+ oncommand="openTroubleshootingPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="feedbackPage"
+ accesskey="&helpFeedbackPage.accesskey;"
+ label="&helpFeedbackPage.label;"
+ oncommand="openFeedbackPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="helpSafeMode"
+ accesskey="&helpSafeMode.accesskey;"
+ label="&helpSafeMode.label;"
+ oncommand="restart(true);"/>
+ <menuseparator id="aboutSeparator"/>
+ <menuitem id="aboutName"
+ accesskey="&aboutProduct.accesskey;"
+ label="&aboutProduct.label;"
+ oncommand="openAboutDialog();"/>
+ </menupopup>
+ </menu>
+
+ <keyset id="baseMenuKeyset">
+#ifdef XP_MACOSX
+ <key id="key_openHelpMac"
+ oncommand="openHelpLink('firefox-osxkey');"
+ key="&helpMac.commandkey;"
+ modifiers="accel"/>
+<!-- These are used to build the Application menu under Cocoa widgets -->
+ <key id="key_preferencesCmdMac"
+ key="&preferencesCmdMac.commandkey;"
+ modifiers="accel"/>
+ <key id="key_hideThisAppCmdMac"
+ key="&hideThisAppCmdMac.commandkey;"
+ modifiers="accel"/>
+ <key id="key_hideOtherAppsCmdMac"
+ key="&hideOtherAppsCmdMac.commandkey;"
+ modifiers="accel,alt"/>
+#endif
+ </keyset>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
+ <stringbundle id="bundle_browser_region" src="chrome://browser-region/locale/region.properties"/>
+ </stringbundleset>
+</overlay>
diff --git a/application/palemoon/base/content/blockedSite.xhtml b/application/palemoon/base/content/blockedSite.xhtml
new file mode 100644
index 000000000..b56875eb6
--- /dev/null
+++ b/application/palemoon/base/content/blockedSite.xhtml
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+ %brandDTD;
+ <!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+ %blockedSiteDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
+ <head>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+ <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
+
+ <script type="application/javascript"><![CDATA[
+ // Error url MUST be formatted like this:
+ // about:blocked?e=error_code&u=url
+
+ // Note that this file uses document.documentURI to get
+ // the URL (with the format from above). This is because
+ // document.location.href gets the current URI off the docshell,
+ // which is the URL displayed in the location bar, i.e.
+ // the URI that the user attempted to load.
+
+ function getErrorCode()
+ {
+ var url = document.documentURI;
+ var error = url.search(/e\=/);
+ var duffUrl = url.search(/\&u\=/);
+ return decodeURIComponent(url.slice(error + 2, duffUrl));
+ }
+
+ function getURL()
+ {
+ var url = document.documentURI;
+ var match = url.match(/&u=([^&]+)&/);
+
+ // match == null if not found; if so, return an empty string
+ // instead of what would turn out to be portions of the URI
+ if (!match)
+ return "";
+
+ url = decodeURIComponent(match[1]);
+
+ // If this is a view-source page, then get then real URI of the page
+ if (url.startsWith("view-source:"))
+ url = url.slice(12);
+ return url;
+ }
+
+ /**
+ * Attempt to get the hostname via document.location. Fail back
+ * to getURL so that we always return something meaningful.
+ */
+ function getHostString()
+ {
+ try {
+ return document.location.hostname;
+ } catch (e) {
+ return getURL();
+ }
+ }
+
+ function initPage()
+ {
+ // Handoff to the appropriate initializer, based on error code
+ switch (getErrorCode()) {
+ case "malwareBlocked" :
+ initPage_malware();
+ break;
+ case "phishingBlocked" :
+ initPage_phishing();
+ break;
+ }
+ }
+
+ /**
+ * Initialize custom strings and functionality for blocked malware case
+ */
+ function initPage_malware()
+ {
+ // Remove phishing strings
+ var el = document.getElementById("errorTitleText_phishing");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorShortDescText_phishing");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorLongDescText_phishing");
+ el.parentNode.removeChild(el);
+
+ // Set sitename
+ document.getElementById("malware_sitename").textContent = getHostString();
+ document.title = document.getElementById("errorTitleText_malware")
+ .innerHTML;
+ }
+
+ /**
+ * Initialize custom strings and functionality for blocked phishing case
+ */
+ function initPage_phishing()
+ {
+ // Remove malware strings
+ var el = document.getElementById("errorTitleText_malware");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorShortDescText_malware");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorLongDescText_malware");
+ el.parentNode.removeChild(el);
+
+ // Set sitename
+ document.getElementById("phishing_sitename").textContent = getHostString();
+ document.title = document.getElementById("errorTitleText_phishing")
+ .innerHTML;
+ }
+ ]]></script>
+ <style type="text/css">
+ /* Style warning button to look like a small text link in the
+ bottom right. This is preferable to just using a text link
+ since there is already a mechanism in browser.js for trapping
+ oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
+ #ignoreWarningButton {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ color: white; /* Hard coded because netError.css forces this page's background to dark red */
+ text-decoration: underline;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ top: 23px;
+ left: 20px;
+ font-size: smaller;
+ }
+
+ #ignoreWarning {
+ text-align: right;
+ }
+ </style>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title;</h1>
+ <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
+ </div>
+
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc;</p>
+ <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
+ </div>
+
+ <!-- Long Description -->
+ <div id="errorLongDesc">
+ <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc;</p>
+ <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
+ </div>
+
+ <!-- Action buttons -->
+ <div id="buttons">
+ <!-- Commands handled in browser.js -->
+ <button id="getMeOutButton">&safeb.palm.accept.label;</button>
+ <button id="reportButton">&safeb.palm.reportPage.label;</button>
+ </div>
+ </div>
+ <div id="ignoreWarning">
+ <button id="ignoreWarningButton">&safeb.palm.decline.label;</button>
+ </div>
+ </div>
+ <!--
+ - Note: It is important to run the script this way, instead of using
+ - an onload handler. This is because error pages are loaded as
+ - LOAD_BACKGROUND, which means that onload handlers will not be executed.
+ -->
+ <script type="application/javascript">initPage();</script>
+ </body>
+</html>
diff --git a/application/palemoon/base/content/browser-addons.js b/application/palemoon/base/content/browser-addons.js
new file mode 100644
index 000000000..7993a0c9c
--- /dev/null
+++ b/application/palemoon/base/content/browser-addons.js
@@ -0,0 +1,536 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Removes a doorhanger notification if all of the installs it was notifying
+// about have ended in some way.
+function removeNotificationOnEnd(notification, installs) {
+ let count = installs.length;
+
+ function maybeRemove(install) {
+ install.removeListener(this);
+
+ if (--count == 0) {
+ // Check that the notification is still showing
+ let current = PopupNotifications.getNotification(notification.id, notification.browser);
+ if (current === notification)
+ notification.remove();
+ }
+ }
+
+ for (let install of installs) {
+ install.addListener({
+ onDownloadCancelled: maybeRemove,
+ onDownloadFailed: maybeRemove,
+ onInstallFailed: maybeRemove,
+ onInstallEnded: maybeRemove
+ });
+ }
+}
+
+const gXPInstallObserver = {
+ _findChildShell: function (aDocShell, aSoughtShell)
+ {
+ if (aDocShell == aSoughtShell)
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
+ for (var i = 0; i < node.childCount; ++i) {
+ var docShell = node.getChildAt(i);
+ docShell = this._findChildShell(docShell, aSoughtShell);
+ if (docShell == aSoughtShell)
+ return docShell;
+ }
+ return null;
+ },
+
+ _getBrowser: function (aDocShell)
+ {
+ for (let browser of gBrowser.browsers) {
+ if (this._findChildShell(browser.docShell, aDocShell))
+ return browser;
+ }
+ return null;
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ var brandBundle = document.getElementById("bundle_brand");
+ var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+ var browser = installInfo.browser;
+
+ // Make sure the browser is still alive.
+ if (!browser || gBrowser.browsers.indexOf(browser) == -1)
+ return;
+
+ const anchorID = "addons-notification-icon";
+ var messageString, action;
+ var brandShortName = brandBundle.getString("brandShortName");
+
+ var notificationID = aTopic;
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ timeout: Date.now() + 30000
+ };
+
+ switch (aTopic) {
+ case "addon-install-disabled":
+ notificationID = "xpinstall-disabled"
+
+ if (gPrefService.prefIsLocked("xpinstall.enabled")) {
+ messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
+ buttons = [];
+ }
+ else {
+ messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
+
+ action = {
+ label: gNavigatorBundle.getString("xpinstallDisabledButton"),
+ accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
+ callback: function editPrefs() {
+ gPrefService.setBoolPref("xpinstall.enabled", true);
+ }
+ };
+ }
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ break;
+ case "addon-install-origin-blocked": {
+ messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarningOrigin",
+ [brandShortName]);
+
+ let popup = PopupNotifications.show(browser, notificationID,
+ messageString, anchorID,
+ null, null, options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ break; }
+ case "addon-install-blocked":
+ let originatingHost;
+ try {
+ originatingHost = installInfo.originatingURI.host;
+ } catch (ex) {
+ // Need to deal with missing originatingURI and with about:/data: URIs more gracefully,
+ // see bug 1063418 - but for now, bail:
+ return;
+ }
+ messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
+ [brandShortName, originatingHost]);
+
+ action = {
+ label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
+ accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
+ callback: function() {
+ installInfo.install();
+ }
+ };
+
+ let popup = PopupNotifications.show(browser, notificationID, messageString,
+ anchorID, action, null, options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ break;
+ case "addon-install-started":
+ var needsDownload = function needsDownload(aInstall) {
+ return aInstall.state != AddonManager.STATE_DOWNLOADED;
+ }
+ // If all installs have already been downloaded then there is no need to
+ // show the download progress
+ if (!installInfo.installs.some(needsDownload))
+ return;
+ notificationID = "addon-progress";
+ messageString = gNavigatorBundle.getString("addonDownloading");
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ options.installs = installInfo.installs;
+ options.contentWindow = browser.contentWindow;
+ options.sourceURI = browser.currentURI;
+ options.eventCallback = function(aEvent) {
+ if (aEvent != "removed")
+ return;
+ options.contentWindow = null;
+ options.sourceURI = null;
+ };
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ null, null, options);
+ break;
+ case "addon-install-failed":
+ // TODO This isn't terribly ideal for the multiple failure case
+ for (let install of installInfo.installs) {
+ let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
+ installInfo.originatingURI.host;
+ if (!host)
+ host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
+ install.sourceURI.host;
+
+ let error = (host || install.error == 0) ? "addonError" : "addonLocalError";
+ if (install.error != 0)
+ error += install.error;
+ else if (install.addon.jetsdk)
+ error += "JetSDK";
+ else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+ error += "Blocklisted";
+ else
+ error += "Incompatible";
+
+ messageString = gNavigatorBundle.getString(error);
+ messageString = messageString.replace("#1", install.name);
+ if (host)
+ messageString = messageString.replace("#2", host);
+ messageString = messageString.replace("#3", brandShortName);
+ messageString = messageString.replace("#4", Services.appinfo.version);
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ }
+ break;
+ case "addon-install-complete":
+ var needsRestart = installInfo.installs.some(function(i) {
+ return i.addon.pendingOperations != AddonManager.PENDING_NONE;
+ });
+
+ if (needsRestart) {
+ messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
+ action = {
+ label: gNavigatorBundle.getString("addonInstallRestartButton"),
+ accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
+ callback: function() {
+ Application.restart();
+ }
+ };
+ }
+ else {
+ messageString = gNavigatorBundle.getString("addonsInstalled");
+ action = null;
+ }
+
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ messageString = messageString.replace("#1", installInfo.installs[0].name);
+ messageString = messageString.replace("#2", installInfo.installs.length);
+ messageString = messageString.replace("#3", brandShortName);
+
+ // Remove notificaion on dismissal, since it's possible to cancel the
+ // install through the addons manager UI, making the "restart" prompt
+ // irrelevant.
+ options.removeOnDismissal = true;
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ break;
+ }
+ }
+};
+
+/*
+ * When addons are installed/uninstalled, check and see if the number of items
+ * on the add-on bar changed:
+ * - If an add-on was installed, incrementing the count, show the bar.
+ * - If an add-on was uninstalled, and no more items are left, hide the bar.
+ */
+let AddonsMgrListener = {
+ get addonBar() document.getElementById("addon-bar"),
+ get statusBar() document.getElementById("status-bar"),
+ getAddonBarItemCount: function() {
+ // Take into account the contents of the status bar shim for the count.
+ var itemCount = this.statusBar.childNodes.length;
+
+ var defaultOrNoninteractive = this.addonBar.getAttribute("defaultset")
+ .split(",")
+ .concat(["separator", "spacer", "spring"]);
+ for (let item of this.addonBar.currentSet.split(",")) {
+ if (defaultOrNoninteractive.indexOf(item) == -1)
+ itemCount++;
+ }
+
+ return itemCount;
+ },
+ onInstalling: function(aAddon) {
+ this.lastAddonBarCount = this.getAddonBarItemCount();
+ },
+ onInstalled: function(aAddon) {
+ if (this.getAddonBarItemCount() > this.lastAddonBarCount)
+ setToolbarVisibility(this.addonBar, true);
+ },
+ onUninstalling: function(aAddon) {
+ this.lastAddonBarCount = this.getAddonBarItemCount();
+ },
+ onUninstalled: function(aAddon) {
+ if (this.getAddonBarItemCount() == 0)
+ setToolbarVisibility(this.addonBar, false);
+ },
+ onEnabling: function(aAddon) this.onInstalling(),
+ onEnabled: function(aAddon) this.onInstalled(),
+ onDisabling: function(aAddon) this.onUninstalling(),
+ onDisabled: function(aAddon) this.onUninstalled(),
+};
+
+
+var LightWeightThemeWebInstaller = {
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "InstallBrowserTheme":
+ case "PreviewBrowserTheme":
+ case "ResetBrowserThemePreview":
+ // ignore requests from background tabs
+ if (event.target.ownerDocument.defaultView.top != content)
+ return;
+ }
+ switch (event.type) {
+ case "InstallBrowserTheme":
+ this._installRequest(event);
+ break;
+ case "PreviewBrowserTheme":
+ this._preview(event);
+ break;
+ case "ResetBrowserThemePreview":
+ this._resetPreview(event);
+ break;
+ case "pagehide":
+ case "TabSelect":
+ this._resetPreview();
+ break;
+ }
+ },
+
+ get _manager () {
+ var temp = {};
+ Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
+ delete this._manager;
+ return this._manager = temp.LightweightThemeManager;
+ },
+
+ _installRequest: function (event) {
+ var node = event.target;
+ var data = this._getThemeFromNode(node);
+ if (!data)
+ return;
+
+ if (this._isAllowed(node)) {
+ this._install(data);
+ return;
+ }
+
+ var allowButtonText =
+ gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
+ var allowButtonAccesskey =
+ gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
+ var message =
+ gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
+ [node.ownerDocument.location.host]);
+ var buttons = [{
+ label: allowButtonText,
+ accessKey: allowButtonAccesskey,
+ callback: function () {
+ LightWeightThemeWebInstaller._install(data);
+ }
+ }];
+
+ this._removePreviousNotifications();
+
+ var notificationBox = gBrowser.getNotificationBox();
+ var notificationBar =
+ notificationBox.appendNotification(message, "lwtheme-install-request", "",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notificationBar.persistence = 1;
+ },
+
+ _install: function (newLWTheme) {
+ var previousLWTheme = this._manager.currentTheme;
+
+ var listener = {
+ onEnabling: function(aAddon, aRequiresRestart) {
+ if (!aRequiresRestart)
+ return;
+
+ let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
+ [aAddon.name], 1);
+
+ let action = {
+ label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
+ accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
+ callback: function () {
+ Application.restart();
+ }
+ };
+
+ let options = {
+ timeout: Date.now() + 30000
+ };
+
+ PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
+ messageString, "addons-notification-icon",
+ action, null, options);
+ },
+
+ onEnabled: function(aAddon) {
+ LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
+ }
+ };
+
+ AddonManager.addAddonListener(listener);
+ this._manager.currentTheme = newLWTheme;
+ AddonManager.removeAddonListener(listener);
+ },
+
+ _postInstallNotification: function (newTheme, previousTheme) {
+ function text(id) {
+ return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
+ }
+
+ var buttons = [{
+ label: text("undoButton"),
+ accessKey: text("undoButton.accesskey"),
+ callback: function () {
+ LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
+ LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
+ }
+ }, {
+ label: text("manageButton"),
+ accessKey: text("manageButton.accesskey"),
+ callback: function () {
+ BrowserOpenAddonsMgr("addons://list/theme");
+ }
+ }];
+
+ this._removePreviousNotifications();
+
+ var notificationBox = gBrowser.getNotificationBox();
+ var notificationBar =
+ notificationBox.appendNotification(text("message"),
+ "lwtheme-install-notification", "",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notificationBar.persistence = 1;
+ notificationBar.timeout = Date.now() + 20000; // 20 seconds
+ },
+
+ _removePreviousNotifications: function () {
+ var box = gBrowser.getNotificationBox();
+
+ ["lwtheme-install-request",
+ "lwtheme-install-notification"].forEach(function (value) {
+ var notification = box.getNotificationWithValue(value);
+ if (notification)
+ box.removeNotification(notification);
+ });
+ },
+
+ _previewWindow: null,
+ _preview: function (event) {
+ if (!this._isAllowed(event.target))
+ return;
+
+ var data = this._getThemeFromNode(event.target);
+ if (!data)
+ return;
+
+ this._resetPreview();
+
+ this._previewWindow = event.target.ownerDocument.defaultView;
+ this._previewWindow.addEventListener("pagehide", this, true);
+ gBrowser.tabContainer.addEventListener("TabSelect", this, false);
+
+ this._manager.previewTheme(data);
+ },
+
+ _resetPreview: function (event) {
+ if (!this._previewWindow ||
+ event && !this._isAllowed(event.target))
+ return;
+
+ this._previewWindow.removeEventListener("pagehide", this, true);
+ this._previewWindow = null;
+ gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
+
+ this._manager.resetPreview();
+ },
+
+ _isAllowed: function (node) {
+ var pm = Services.perms;
+
+ var uri = node.ownerDocument.documentURIObject;
+ return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
+ },
+
+ _getThemeFromNode: function (node) {
+ return this._manager.parseTheme(node.getAttribute("data-browsertheme"),
+ node.baseURI);
+ }
+}
+
+/*
+ * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
+ */
+let LightweightThemeListener = {
+ _modifiedStyles: [],
+
+ init: function () {
+ XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
+ for (let i = document.styleSheets.length - 1; i >= 0; i--) {
+ let sheet = document.styleSheets[i];
+ if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
+ return sheet;
+ }
+ });
+
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ Services.obs.addObserver(this, "lightweight-theme-optimized", false);
+ if (document.documentElement.hasAttribute("lwtheme"))
+ this.updateStyleSheet(document.documentElement.style.backgroundImage);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ Services.obs.removeObserver(this, "lightweight-theme-optimized");
+ },
+
+ /**
+ * Append the headerImage to the background-image property of all rulesets in
+ * browser-lightweightTheme.css.
+ *
+ * @param headerImage - a string containing a CSS image for the lightweight theme header.
+ */
+ updateStyleSheet: function(headerImage) {
+ if (!this.styleSheet)
+ return;
+ this.substituteRules(this.styleSheet.cssRules, headerImage);
+ },
+
+ substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) {
+ let styleRulesModified = 0;
+ for (let i = 0; i < ruleList.length; i++) {
+ let rule = ruleList[i];
+ if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
+ // Add the number of modified sub-rules to the modified count
+ styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
+ } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
+ if (!rule.style.backgroundImage)
+ continue;
+ let modifiedIndex = existingStyleRulesModified + styleRulesModified;
+ if (!this._modifiedStyles[modifiedIndex])
+ this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
+
+ rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
+ styleRulesModified++;
+ } else {
+ Cu.reportError("Unsupported rule encountered");
+ }
+ }
+ return styleRulesModified;
+ },
+
+ // nsIObserver
+ observe: function (aSubject, aTopic, aData) {
+ if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
+ !this.styleSheet)
+ return;
+
+ if (aTopic == "lightweight-theme-optimized" && aSubject != window)
+ return;
+
+ let themeData = JSON.parse(aData);
+ if (!themeData)
+ return;
+ this.updateStyleSheet("url(" + themeData.headerURL + ")");
+ },
+};
diff --git a/application/palemoon/base/content/browser-appmenu.inc b/application/palemoon/base/content/browser-appmenu.inc
new file mode 100644
index 000000000..835bf22bc
--- /dev/null
+++ b/application/palemoon/base/content/browser-appmenu.inc
@@ -0,0 +1,394 @@
+# -*- Mode: HTML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<menupopup id="appmenu-popup"
+ onpopupshowing="if (event.target == this) {
+ updateEditUIVisibility();
+#ifdef MOZ_SERVICES_SYNC
+ gSyncUI.updateUI();
+#endif
+ return;
+ }
+ updateCharacterEncodingMenuState();
+ if (event.target.parentNode.parentNode.parentNode.parentNode == this)
+ this._currentPopup = event.target;">
+ <hbox>
+ <vbox id="appmenuPrimaryPane">
+ <menuitem id="appmenu_newTab_popup"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ key="key_newNavigatorTab"/>
+ <menuitem id="appmenu_newNavigator"
+ label="&newNavigatorCmd.label;"
+ command="cmd_newNavigator"
+ key="key_newNavigator"/>
+ <menuitem id="appmenu_newPrivateWindow"
+ class="menuitem-iconic menuitem-iconic-tooltip"
+ label="&newPrivateWindow.label;"
+ command="Tools:PrivateBrowsing"
+ key="key_privatebrowsing"/>
+ <menuitem label="&goOfflineCmd.label;"
+ id="appmenu_offlineModeRecovery"
+ type="checkbox"
+ observes="workOfflineMenuitemState"
+ oncommand="BrowserOffline.toggleOfflineStatus();"/>
+ <menuseparator class="appmenu-menuseparator"/>
+ <hbox>
+ <menuitem id="appmenu-edit-label"
+ label="&appMenuEdit.label;"
+ disabled="true"/>
+ <toolbarbutton id="appmenu-cut"
+ class="appmenu-edit-button"
+ command="cmd_cut"
+ onclick="if (!this.disabled) hidePopup();"
+ tooltiptext="&cutButton.tooltip;"/>
+ <toolbarbutton id="appmenu-copy"
+ class="appmenu-edit-button"
+ command="cmd_copy"
+ onclick="if (!this.disabled) hidePopup();"
+ tooltiptext="&copyButton.tooltip;"/>
+ <toolbarbutton id="appmenu-paste"
+ class="appmenu-edit-button"
+ command="cmd_paste"
+ onclick="if (!this.disabled) hidePopup();"
+ tooltiptext="&pasteButton.tooltip;"/>
+ <spacer flex="1"/>
+ <menu id="appmenu-editmenu">
+ <menupopup id="appmenu-editmenu-menupopup">
+ <menuitem id="appmenu-editmenu-cut"
+ class="menuitem-iconic"
+ label="&cutCmd.label;"
+ key="key_cut"
+ command="cmd_cut"/>
+ <menuitem id="appmenu-editmenu-copy"
+ class="menuitem-iconic"
+ label="&copyCmd.label;"
+ key="key_copy"
+ command="cmd_copy"/>
+ <menuitem id="appmenu-editmenu-paste"
+ class="menuitem-iconic"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ command="cmd_paste"/>
+ <menuseparator/>
+ <menuitem id="appmenu-editmenu-undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ command="cmd_undo"/>
+ <menuitem id="appmenu-editmenu-redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ command="cmd_redo"/>
+ <menuseparator/>
+ <menuitem id="appmenu-editmenu-selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem id="appmenu-editmenu-delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ command="cmd_delete"/>
+ </menupopup>
+ </menu>
+ </hbox>
+ <menuitem id="appmenu_find"
+ class="menuitem-tooltip"
+ label="&appMenuFind.label;"
+ command="cmd_find"
+ key="key_find"/>
+ <menuseparator class="appmenu-menuseparator"/>
+ <menuitem id="appmenu_openFile"
+ label="&openFileCmd.label;"
+ command="Browser:OpenFile"
+ key="openFileKb"/>
+ <menuitem id="appmenu_savePage"
+ class="menuitem-tooltip"
+ label="&savePageCmd.label;"
+ command="Browser:SavePage"
+ key="key_savePage"/>
+ <menuitem id="appmenu_sendLink"
+ label="&emailPageCmd.label;"
+ command="Browser:SendLink"/>
+ <splitmenu id="appmenu_print"
+ iconic="true"
+ label="&printCmd.label;"
+ command="cmd_print">
+ <menupopup>
+ <menuitem id="appmenu_print_popup"
+ class="menuitem-iconic"
+ label="&printCmd.label;"
+ command="cmd_print"
+ key="printKb"/>
+ <menuitem id="appmenu_printPreview"
+ label="&printPreviewCmd.label;"
+ command="cmd_printPreview"/>
+ <menuitem id="appmenu_printSetup"
+ label="&printSetupCmd.label;"
+ command="cmd_pageSetup"/>
+ </menupopup>
+ </splitmenu>
+ <menuseparator class="appmenu-menuseparator"/>
+ <splitmenu id="appmenu_webDeveloper"
+ command="Tools:DevToolbox"
+ label="&appMenuWebDeveloper.label;">
+ <menupopup id="appmenu_webDeveloper_popup">
+#ifdef MOZ_DEVTOOLS
+ <menuitem id="appmenu_devToolbox"
+ observes="devtoolsMenuBroadcaster_DevToolbox"/>
+ <menuseparator id="appmenu_devtools_separator"/>
+ <menuitem id="appmenu_devToolbar"
+ observes="devtoolsMenuBroadcaster_DevToolbar"/>
+ <menuitem id="appmenu_chromeDebugger"
+ observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
+ <menuitem id="appmenu_browserConsole"
+ observes="devtoolsMenuBroadcaster_BrowserConsole"/>
+ <menuitem id="appmenu_responsiveUI"
+ observes="devtoolsMenuBroadcaster_ResponsiveUI"/>
+ <menuitem id="appmenu_eyedropper"
+ observes="devtoolsMenuBroadcaster_Eyedropper"/>
+ <menuitem id="appmenu_scratchpad"
+ observes="devtoolsMenuBroadcaster_Scratchpad"/>
+#endif
+ <menuitem id="appmenu_pageSource"
+ observes="devtoolsMenuBroadcaster_PageSource"/>
+ <menuitem id="appmenu_errorConsole"
+ observes="devtoolsMenuBroadcaster_ErrorConsole"/>
+#ifdef MOZ_DEVTOOLS
+ <menuitem id="appmenu_devtools_connect"
+ observes="devtoolsMenuBroadcaster_connect"/>
+#endif
+ <menuseparator id="appmenu_devToolsEndSeparator"/>
+ <menuitem id="appmenu_getMoreDevtools"
+ observes="devtoolsMenuBroadcaster_GetMoreTools"/>
+ <menuseparator/>
+#define ID_PREFIX appmenu_developer_
+#define OMIT_ACCESSKEYS
+#include browser-charsetmenu.inc
+#undef ID_PREFIX
+#undef OMIT_ACCESSKEYS
+ <menuitem label="&goOfflineCmd.label;"
+ type="checkbox"
+ observes="workOfflineMenuitemState"
+ oncommand="BrowserOffline.toggleOfflineStatus();"/>
+ </menupopup>
+ </splitmenu>
+ <menuseparator class="appmenu-menuseparator"/>
+#define ID_PREFIX appmenu_
+#define OMIT_ACCESSKEYS
+#include browser-charsetmenu.inc
+#undef ID_PREFIX
+#undef OMIT_ACCESSKEYS
+ <menuitem id="appmenu_fullScreen"
+ class="menuitem-tooltip"
+ label="&fullScreenCmd.label;"
+ type="checkbox"
+ observes="View:FullScreen"
+ key="key_fullScreen"/>
+ <menuitem id="appmenu_restart"
+ class="menuitem-iconic"
+ label="&appMenuRestart.label;"
+ command="cmd_restartApplication"/>
+ <menuitem id="appmenu-quit"
+ class="menuitem-iconic"
+#ifdef XP_WIN
+ label="&quitApplicationCmdWin.label;"
+#else
+ label="&quitApplicationCmd.label;"
+#endif
+ command="cmd_quitApplication"/>
+ </vbox>
+ <vbox id="appmenuSecondaryPane">
+ <splitmenu id="appmenu_bookmarks"
+ iconic="true"
+ label="&bookmarksMenu.label;"
+ command="Browser:ShowAllBookmarks">
+ <menupopup id="appmenu_bookmarksPopup"
+ placespopup="true"
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarkingUI.onPopupShowing(event);
+ if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="appmenu_showAllBookmarks"
+ class="menuitem-iconic"
+ label="&organizeBookmarks.label;"
+ command="Browser:ShowAllBookmarks"
+ context=""
+ key="manBookmarkKb"/>
+ <menuseparator/>
+ <menuitem id="appmenu_bookmarkThisPage"
+ class="menuitem-iconic"
+ label="&bookmarkThisPageCmd.label;"
+ command="Browser:AddBookmarkAs"
+ key="addBookmarkAsKb"/>
+ <menuitem id="appmenu_subscribeToPage"
+ class="menuitem-iconic"
+ label="&subscribeToPageMenuitem.label;"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="singleFeedMenuitemState"/>
+ <menu id="appmenu_subscribeToPageMenu"
+ class="menu-iconic"
+ label="&subscribeToPageMenupopup.label;"
+ observes="multipleFeedsMenuState">
+ <menupopup id="appmenu_subscribeToPageMenupopup"
+ onpopupshowing="return FeedHandler.buildFeedList(event.target);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuseparator/>
+ <menu id="appmenu_bookmarksToolbar"
+ placesanonid="toolbar-autohide"
+ class="menu-iconic bookmark-item"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="appmenu_bookmarksToolbarPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menuseparator/>
+ <!-- Bookmarks menu items -->
+ <menuseparator builder="end"
+ class="hide-if-empty-places-result"/>
+ <menuitem id="appmenu_unsortedBookmarks"
+ class="menuitem-iconic"
+ label="&appMenuUnsorted.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
+ </menupopup>
+ </splitmenu>
+ <splitmenu id="appmenu_history"
+ iconic="true"
+ label="&historyMenu.label;"
+ command="Browser:ShowAllHistory">
+ <menupopup id="appmenu_historyMenupopup"
+ placespopup="true"
+ context="placesContext"
+ oncommand="this.parentNode._placesView._onCommand(event);"
+ onclick="checkForMiddleClick(this, event);"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new HistoryMenu(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="appmenu_showAllHistory"
+ class="menuitem-iconic"
+ label="&showAllHistoryCmd2.label;"
+ command="Browser:ShowAllHistory"
+ key="showAllHistoryKb"/>
+ <menuseparator/>
+ <menuitem id="appmenu_sanitizeHistory"
+ class="menuitem-iconic"
+ label="&clearRecentHistory.label;"
+ key="key_sanitize"
+ command="Tools:Sanitize"/>
+ <menuseparator class="hide-if-empty-places-result"/>
+#ifdef MOZ_SERVICES_SYNC
+ <menuitem id="appmenu_sync-tabs"
+ class="syncTabsMenuItem"
+ label="&syncTabsMenu2.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ disabled="true"/>
+#endif
+ <menuitem id="appmenu_restoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ command="Browser:RestoreLastSession"/>
+ <menu id="appmenu_recentlyClosedTabsMenu"
+ class="recentlyClosedTabsMenu"
+ label="&historyUndoMenu.label;"
+ disabled="true">
+ <menupopup id="appmenu_recentlyClosedTabsMenupopup"
+ onpopupshowing="document.getElementById('appmenu_history')._placesView.populateUndoSubmenu();"/>
+ </menu>
+ <menu id="appmenu_recentlyClosedWindowsMenu"
+ class="recentlyClosedWindowsMenu"
+ label="&historyUndoWindowMenu.label;"
+ disabled="true">
+ <menupopup id="appmenu_recentlyClosedWindowsMenupopup"
+ onpopupshowing="document.getElementById('appmenu_history')._placesView.populateUndoWindowSubmenu();"/>
+ </menu>
+ <menuseparator/>
+ </menupopup>
+ </splitmenu>
+ <menuitem id="appmenu_downloads"
+ class="menuitem-tooltip"
+ label="&downloads.label;"
+ command="Tools:Downloads"
+ key="key_openDownloads"/>
+ <spacer id="appmenuSecondaryPane-spacer"/>
+ <menuitem id="appmenu_addons"
+ class="menuitem-iconic menuitem-iconic-tooltip"
+ label="&addons.label;"
+ command="Tools:Addons"
+ key="key_openAddons"/>
+ <menuitem id="appmenu_permissions"
+ class="menuitem-iconic"
+ label="&permissions.label;"
+ command="Tools:Permissions"
+ key="key_openPermissions"/>
+#ifdef MOZ_SERVICES_SYNC
+ <!-- only one of sync-setup or sync-syncnow will be showing at once -->
+ <menuitem id="sync-setup-appmenu"
+ label="&syncSetup.label;"
+ observes="sync-setup-state"
+ oncommand="gSyncUI.openSetup()"/>
+ <menuitem id="sync-syncnowitem-appmenu"
+ label="&syncSyncNowItem.label;"
+ observes="sync-syncnow-state"
+ oncommand="gSyncUI.doSync(event);"/>
+#endif
+ <splitmenu id="appmenu_customize"
+ label="&preferencesCmd2.label;"
+ oncommand="openPreferences();">
+ <menupopup id="appmenu_customizeMenu"
+ onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('appmenu_toggleToolbarsSeparator'));">
+ <menuitem id="appmenu_preferences"
+ label="&preferencesCmd2.label;"
+ oncommand="openPreferences();"/>
+ <menuseparator/>
+ <menuseparator id="appmenu_toggleToolbarsSeparator"/>
+ <menuitem id="appmenu_toggleTabsOnTop"
+ label="&viewTabsOnTop.label;"
+ type="checkbox"
+ command="cmd_ToggleTabsOnTop"/>
+ <menuitem id="appmenu_toolbarLayout"
+ label="&appMenuToolbarLayout.label;"
+ command="cmd_CustomizeToolbars"/>
+ </menupopup>
+ </splitmenu>
+ <splitmenu id="appmenu_help"
+ label="&helpMenu.label;"
+ oncommand="openHelpLink('firefox-help')">
+ <menupopup id="appmenu_helpMenupopup">
+ <menuitem id="appmenu_openHelp"
+ label="&helpMenu.label;"
+ oncommand="openHelpLink('firefox-help')"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="appmenu_troubleshootingInfo"
+ label="&helpTroubleshootingInfo.label;"
+ oncommand="openTroubleshootingPage()"
+ onclick="checkForMiddleClick(this,event);"/>
+ <menuitem id="appmenu_feedbackPage"
+ label="&helpFeedbackPage.label;"
+ oncommand="openFeedbackPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuseparator/>
+ <menuitem id="appmenu_safeMode"
+ label="&appMenuSafeMode.label;"
+ oncommand="restart(true);"/>
+ <menuseparator/>
+ <menuitem id="appmenu_about"
+ label="&aboutProduct.label;"
+ oncommand="openAboutDialog();"/>
+ </menupopup>
+ </splitmenu>
+ </vbox>
+ </hbox>
+</menupopup>
diff --git a/application/palemoon/base/content/browser-charsetmenu.inc b/application/palemoon/base/content/browser-charsetmenu.inc
new file mode 100644
index 000000000..628de1341
--- /dev/null
+++ b/application/palemoon/base/content/browser-charsetmenu.inc
@@ -0,0 +1,62 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#filter substitution
+
+#expand <menu id="__ID_PREFIX__charsetMenu"
+ label="&charsetMenu.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenu.accesskey;"
+#endif
+ oncommand="MultiplexHandler(event)"
+#ifdef OMIT_ACCESSKEYS
+#expand onpopupshowing="CharsetMenu.build(event, '__ID_PREFIX__');"
+#else
+#expand onpopupshowing="CharsetMenu.build(event, '__ID_PREFIX__', true);"
+#endif
+ onpopupshown="UpdateMenus(event);">
+ <menupopup>
+ <menu label="&charsetMenuAutodet.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.accesskey;"
+#endif
+ >
+ <menupopup>
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.off"
+ label="&charsetMenuAutodet.off.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.off.accesskey;"
+#endif
+ />
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.ja_parallel_state_machine"
+ label="&charsetMenuAutodet.ja.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.ja.accesskey;"
+#endif
+ />
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.ruprob"
+ label="&charsetMenuAutodet.ru.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.ru.accesskey;"
+#endif
+ />
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.ukprob"
+ label="&charsetMenuAutodet.uk.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.uk.accesskey;"
+#endif
+ />
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ </menupopup>
+</menu>
diff --git a/application/palemoon/base/content/browser-context.inc b/application/palemoon/base/content/browser-context.inc
new file mode 100644
index 000000000..f672ede61
--- /dev/null
+++ b/application/palemoon/base/content/browser-context.inc
@@ -0,0 +1,379 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ <menuseparator id="page-menu-separator"/>
+ <menuitem id="spell-no-suggestions"
+ disabled="true"
+ label="&spellNoSuggestions.label;"/>
+ <menuitem id="spell-add-to-dictionary"
+ label="&spellAddToDictionary.label;"
+ accesskey="&spellAddToDictionary.accesskey;"
+ oncommand="InlineSpellCheckerUI.addToDictionary();"/>
+ <menuitem id="spell-undo-add-to-dictionary"
+ label="&spellUndoAddToDictionary.label;"
+ accesskey="&spellUndoAddToDictionary.accesskey;"
+ oncommand="InlineSpellCheckerUI.undoAddToDictionary();" />
+ <menuseparator id="spell-suggestions-separator"/>
+ <menuitem id="context-openlinkintab"
+ label="&openLinkCmdInTab.label;"
+ accesskey="&openLinkCmdInTab.accesskey;"
+ oncommand="gContextMenu.openLinkInTab();"/>
+ <menuitem id="context-openlink"
+ label="&openLinkCmd.label;"
+ accesskey="&openLinkCmd.accesskey;"
+ oncommand="gContextMenu.openLink();"/>
+ <menuitem id="context-openlinkprivate"
+ label="&openLinkInPrivateWindowCmd.label;"
+ accesskey="&openLinkInPrivateWindowCmd.accesskey;"
+ oncommand="gContextMenu.openLinkInPrivateWindow();"/>
+ <menuitem id="context-openlinkincurrent"
+ label="&openLinkCmdInCurrent.label;"
+ accesskey="&openLinkCmdInCurrent.accesskey;"
+ oncommand="gContextMenu.openLinkInCurrent();"/>
+ <menuseparator id="context-sep-open"/>
+ <menuitem id="context-bookmarklink"
+ label="&bookmarkThisLinkCmd.label;"
+ accesskey="&bookmarkThisLinkCmd.accesskey;"
+ oncommand="gContextMenu.bookmarkLink();"/>
+ <menuitem id="context-savelink"
+ label="&saveLinkCmd.label;"
+ accesskey="&saveLinkCmd.accesskey;"
+ oncommand="gContextMenu.saveLink();"/>
+ <menuitem id="context-sendlink"
+ label="&sendLinkCmd.label;"
+ accesskey="&sendLinkCmd.accesskey;"
+ oncommand="gContextMenu.sendLink();"/>
+ <menuitem id="context-copyemail"
+ label="&copyEmailCmd.label;"
+ accesskey="&copyEmailCmd.accesskey;"
+ oncommand="gContextMenu.copyEmail();"/>
+ <menuitem id="context-copylink"
+ label="&copyLinkCmd.label;"
+ accesskey="&copyLinkCmd.accesskey;"
+ oncommand="goDoCommand('cmd_copyLink');"/>
+ <menuseparator id="context-sep-copylink"/>
+ <menuitem id="context-media-play"
+ label="&mediaPlay.label;"
+ accesskey="&mediaPlay.accesskey;"
+ oncommand="gContextMenu.mediaCommand('play');"/>
+ <menuitem id="context-media-pause"
+ label="&mediaPause.label;"
+ accesskey="&mediaPause.accesskey;"
+ oncommand="gContextMenu.mediaCommand('pause');"/>
+ <menuitem id="context-media-mute"
+ label="&mediaMute.label;"
+ accesskey="&mediaMute.accesskey;"
+ oncommand="gContextMenu.mediaCommand('mute');"/>
+ <menuitem id="context-media-unmute"
+ label="&mediaUnmute.label;"
+ accesskey="&mediaUnmute.accesskey;"
+ oncommand="gContextMenu.mediaCommand('unmute');"/>
+ <menu id="context-media-playbackrate" label="&mediaPlaybackRate.label;" accesskey="&mediaPlaybackRate.accesskey;">
+ <menupopup>
+ <menuitem id="context-media-playbackrate-050x"
+ label="&mediaPlaybackRate050x.label;"
+ accesskey="&mediaPlaybackRate050x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 0.5);"/>
+ <menuitem id="context-media-playbackrate-100x"
+ label="&mediaPlaybackRate100x.label;"
+ accesskey="&mediaPlaybackRate100x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ checked="true"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 1.0);"/>
+ <menuitem id="context-media-playbackrate-150x"
+ label="&mediaPlaybackRate150x.label;"
+ accesskey="&mediaPlaybackRate150x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 1.5);"/>
+ <menuitem id="context-media-playbackrate-200x"
+ label="&mediaPlaybackRate200x.label;"
+ accesskey="&mediaPlaybackRate200x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 2.0);"/>
+ </menupopup>
+ </menu>
+ <menuitem id="context-media-showcontrols"
+ label="&mediaShowControls.label;"
+ accesskey="&mediaShowControls.accesskey;"
+ oncommand="gContextMenu.mediaCommand('showcontrols');"/>
+ <menuitem id="context-media-hidecontrols"
+ label="&mediaHideControls.label;"
+ accesskey="&mediaHideControls.accesskey;"
+ oncommand="gContextMenu.mediaCommand('hidecontrols');"/>
+ <menuitem id="context-video-showstats"
+ accesskey="&videoShowStats.accesskey;"
+ label="&videoShowStats.label;"
+ oncommand="gContextMenu.mediaCommand('showstats');"/>
+ <menuitem id="context-video-hidestats"
+ accesskey="&videoHideStats.accesskey;"
+ label="&videoHideStats.label;"
+ oncommand="gContextMenu.mediaCommand('hidestats');"/>
+ <menuitem id="context-video-fullscreen"
+ accesskey="&videoFullScreen.accesskey;"
+ label="&videoFullScreen.label;"
+ oncommand="gContextMenu.fullScreenVideo();"/>
+ <menuitem id="context-leave-dom-fullscreen"
+ accesskey="&leaveDOMFullScreen.accesskey;"
+ label="&leaveDOMFullScreen.label;"
+ oncommand="gContextMenu.leaveDOMFullScreen();"/>
+ <menuseparator id="context-media-sep-commands"/>
+ <menuitem id="context-reloadimage"
+ label="&reloadImageCmd.label;"
+ accesskey="&reloadImageCmd.accesskey;"
+ oncommand="gContextMenu.reloadImage();"/>
+ <menuitem id="context-viewimage"
+ label="&viewImageCmd.label;"
+ accesskey="&viewImageCmd.accesskey;"
+ oncommand="gContextMenu.viewMedia(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-viewvideo"
+ label="&viewVideoCmd.label;"
+ accesskey="&viewVideoCmd.accesskey;"
+ oncommand="gContextMenu.viewMedia(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+#ifdef CONTEXT_COPY_IMAGE_CONTENTS
+ <menuitem id="context-copyimage-contents"
+ label="&copyImageContentsCmd.label;"
+ accesskey="&copyImageContentsCmd.accesskey;"
+ oncommand="goDoCommand('cmd_copyImage');"/>
+#endif
+ <menuitem id="context-copyimage"
+ label="&copyImageCmd.label;"
+ accesskey="&copyImageCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuitem id="context-copyvideourl"
+ label="&copyVideoURLCmd.label;"
+ accesskey="&copyVideoURLCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuitem id="context-copyaudiourl"
+ label="&copyAudioURLCmd.label;"
+ accesskey="&copyAudioURLCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuseparator id="context-sep-copyimage"/>
+ <menuitem id="context-saveimage"
+ label="&saveImageCmd.label;"
+ accesskey="&saveImageCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-sendimage"
+ label="&emailImageCmd.label;"
+ accesskey="&emailImageCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-setDesktopBackground"
+ label="&setDesktopBackgroundCmd.label;"
+ accesskey="&setDesktopBackgroundCmd.accesskey;"
+ oncommand="gContextMenu.setDesktopBackground();"/>
+ <menuitem id="context-viewimageinfo"
+ label="&viewImageInfoCmd.label;"
+ accesskey="&viewImageInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewImageInfo();"/>
+ <menuitem id="context-savevideo"
+ label="&saveVideoCmd.label;"
+ accesskey="&saveVideoCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-saveaudio"
+ label="&saveAudioCmd.label;"
+ accesskey="&saveAudioCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-video-saveimage"
+ accesskey="&videoSaveImage.accesskey;"
+ label="&videoSaveImage.label;"
+ oncommand="gContextMenu.saveVideoFrameAsImage();"/>
+ <menuitem id="context-sendvideo"
+ label="&emailVideoCmd.label;"
+ accesskey="&emailVideoCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-sendaudio"
+ label="&emailAudioCmd.label;"
+ accesskey="&emailAudioCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-ctp-play"
+ label="&playPluginCmd.label;"
+ accesskey="&playPluginCmd.accesskey;"
+ oncommand="gContextMenu.playPlugin();"/>
+ <menuitem id="context-ctp-hide"
+ label="&hidePluginCmd.label;"
+ accesskey="&hidePluginCmd.accesskey;"
+ oncommand="gContextMenu.hidePlugin();"/>
+ <menuseparator id="context-sep-ctp"/>
+ <menuitem id="context-back"
+ label="&backCmd.label;"
+ accesskey="&backCmd.accesskey;"
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-forward"
+ label="&forwardCmd.label;"
+ accesskey="&forwardCmd.accesskey;"
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-reload"
+ label="&reloadCmd.label;"
+ accesskey="&reloadCmd.accesskey;"
+ oncommand="gContextMenu.reload(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-stop"
+ label="&stopCmd.label;"
+ accesskey="&stopCmd.accesskey;"
+ command="Browser:Stop"/>
+ <menuseparator id="context-sep-stop"/>
+ <menuitem id="context-bookmarkpage"
+ label="&bookmarkPageCmd2.label;"
+ accesskey="&bookmarkPageCmd2.accesskey;"
+ oncommand="gContextMenu.bookmarkThisPage();"/>
+ <menuitem id="context-savepage"
+ label="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey2;"
+ oncommand="gContextMenu.savePageAs();"/>
+ <menuitem id="context-sendpage"
+ label="&sendPageCmd.label;"
+ accesskey="&sendPageCmd.accesskey;"
+ oncommand="gContextMenu.sendPage();"/>
+ <menuseparator id="context-sep-viewbgimage"/>
+ <menuitem id="context-viewbgimage"
+ label="&viewBGImageCmd.label;"
+ accesskey="&viewBGImageCmd.accesskey;"
+ oncommand="gContextMenu.viewBGImage(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-undo"
+ label="&undoCmd.label;"
+ accesskey="&undoCmd.accesskey;"
+ command="cmd_undo"/>
+ <menuseparator id="context-sep-undo"/>
+ <menuitem id="context-cut"
+ label="&cutCmd.label;"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="context-copy"
+ label="&copyCmd.label;"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="context-paste"
+ label="&pasteCmd.label;"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuitem id="context-delete"
+ label="&deleteCmd.label;"
+ accesskey="&deleteCmd.accesskey;"
+ command="cmd_delete"/>
+ <menuseparator id="context-sep-paste"/>
+ <menuitem id="context-selectall"
+ label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator id="context-sep-selectall"/>
+ <menuitem id="context-keywordfield"
+ label="&keywordfield.label;"
+ accesskey="&keywordfield.accesskey;"
+ oncommand="AddKeywordForSearchField();"/>
+ <menuitem id="context-searchselect"
+ oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
+ <menuseparator id="frame-sep"/>
+ <menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
+ <menupopup>
+ <menuitem id="context-showonlythisframe"
+ label="&showOnlyThisFrameCmd.label;"
+ accesskey="&showOnlyThisFrameCmd.accesskey;"
+ oncommand="gContextMenu.showOnlyThisFrame();"/>
+ <menuitem id="context-openframeintab"
+ label="&openFrameCmdInTab.label;"
+ accesskey="&openFrameCmdInTab.accesskey;"
+ oncommand="gContextMenu.openFrameInTab();"/>
+ <menuitem id="context-openframe"
+ label="&openFrameCmd.label;"
+ accesskey="&openFrameCmd.accesskey;"
+ oncommand="gContextMenu.openFrame();"/>
+ <menuseparator id="open-frame-sep"/>
+ <menuitem id="context-reloadframe"
+ label="&reloadFrameCmd.label;"
+ accesskey="&reloadFrameCmd.accesskey;"
+ oncommand="gContextMenu.reloadFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-bookmarkframe"
+ label="&bookmarkThisFrameCmd.label;"
+ accesskey="&bookmarkThisFrameCmd.accesskey;"
+ oncommand="gContextMenu.addBookmarkForFrame();"/>
+ <menuitem id="context-saveframe"
+ label="&saveFrameCmd.label;"
+ accesskey="&saveFrameCmd.accesskey;"
+ oncommand="gContextMenu.saveFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-printframe"
+ label="&printFrameCmd.label;"
+ accesskey="&printFrameCmd.accesskey;"
+ oncommand="gContextMenu.printFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-viewframesource"
+ label="&viewFrameSourceCmd.label;"
+ accesskey="&viewFrameSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewFrameSource();"
+ observes="isFrameImage"/>
+ <menuitem id="context-viewframeinfo"
+ label="&viewFrameInfoCmd.label;"
+ accesskey="&viewFrameInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewFrameInfo();"/>
+ </menupopup>
+ </menu>
+ <menuitem id="context-viewpartialsource-selection"
+ label="&viewPartialSourceForSelectionCmd.label;"
+ accesskey="&viewPartialSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewPartialSource('selection');"
+ observes="isImage"/>
+ <menuitem id="context-viewpartialsource-mathml"
+ label="&viewPartialSourceForMathMLCmd.label;"
+ accesskey="&viewPartialSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewPartialSource('mathml');"
+ observes="isImage"/>
+ <menuseparator id="context-sep-viewsource"/>
+ <menuitem id="context-viewsource"
+ label="&viewPageSourceCmd.label;"
+ accesskey="&viewPageSourceCmd.accesskey;"
+ oncommand="BrowserViewSourceOfDocument(gContextMenu.browser.contentDocument);"
+ observes="isImage"/>
+ <menuitem id="context-viewinfo"
+ label="&viewPageInfoCmd.label;"
+ accesskey="&viewPageInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewInfo();"/>
+ <menuseparator id="spell-separator"/>
+ <menuitem id="spell-check-enabled"
+ label="&spellCheckToggle.label;"
+ type="checkbox"
+ accesskey="&spellCheckToggle.accesskey;"
+ oncommand="InlineSpellCheckerUI.toggleEnabled();"/>
+ <menuitem id="spell-add-dictionaries-main"
+ label="&spellAddDictionaries.label;"
+ accesskey="&spellAddDictionaries.accesskey;"
+ oncommand="gContextMenu.addDictionaries();"/>
+ <menu id="spell-dictionaries"
+ label="&spellDictionaries.label;"
+ accesskey="&spellDictionaries.accesskey;">
+ <menupopup id="spell-dictionaries-menu">
+ <menuseparator id="spell-language-separator"/>
+ <menuitem id="spell-add-dictionaries"
+ label="&spellAddDictionaries.label;"
+ accesskey="&spellAddDictionaries.accesskey;"
+ oncommand="gContextMenu.addDictionaries();"/>
+ </menupopup>
+ </menu>
+ <menuseparator hidden="true" id="context-sep-bidi"/>
+ <menuitem hidden="true" id="context-bidi-text-direction-toggle"
+ label="&bidiSwitchTextDirectionItem.label;"
+ accesskey="&bidiSwitchTextDirectionItem.accesskey;"
+ command="cmd_switchTextDirection"/>
+ <menuitem hidden="true" id="context-bidi-page-direction-toggle"
+ label="&bidiSwitchPageDirectionItem.label;"
+ accesskey="&bidiSwitchPageDirectionItem.accesskey;"
+ oncommand="gContextMenu.switchPageDirection();"/>
+#ifdef MOZ_DEVTOOLS
+ <menuseparator id="inspect-separator" hidden="true"/>
+ <menuitem id="context-inspect"
+ hidden="true"
+ label="&inspectContextMenu.label;"
+ accesskey="&inspectContextMenu.accesskey;"
+ oncommand="gContextMenu.inspectNode();"/>
+#endif
diff --git a/application/palemoon/base/content/browser-doctype.inc b/application/palemoon/base/content/browser-doctype.inc
new file mode 100644
index 000000000..6ee6384b6
--- /dev/null
+++ b/application/palemoon/base/content/browser-doctype.inc
@@ -0,0 +1,19 @@
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+%browserDTD;
+<!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
+%baseMenuDTD;
+<!ENTITY % charsetDTD SYSTEM "chrome://browser/locale/charsetMenu.dtd" >
+%charsetDTD;
+<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
+%textcontextDTD;
+<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
+ %customizeToolbarDTD;
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+%aboutHomeDTD;
+]>
+
diff --git a/application/palemoon/base/content/browser-feeds.js b/application/palemoon/base/content/browser-feeds.js
new file mode 100644
index 000000000..c67877269
--- /dev/null
+++ b/application/palemoon/base/content/browser-feeds.js
@@ -0,0 +1,224 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
+ * and shows UI when they are discovered.
+ */
+var FeedHandler = {
+
+ /* Pale Moon: Address Bar: Feeds
+ * The click handler for the Feed icon in the location bar. Opens the
+ * subscription page if user is not given a choice of feeds.
+ * (Otherwise the list of available feeds will be presented to the
+ * user in a popup menu.)
+ */
+ onFeedButtonPMClick: function(event) {
+ event.stopPropagation();
+
+ if (event.target.hasAttribute("feed") &&
+ event.eventPhase == Event.AT_TARGET &&
+ (event.button == 0 || event.button == 1)) {
+ this.subscribeToFeed(null, event);
+ }
+ },
+
+ /**
+ * The click handler for the Feed icon in the toolbar. Opens the
+ * subscription page if user is not given a choice of feeds.
+ * (Otherwise the list of available feeds will be presented to the
+ * user in a popup menu.)
+ */
+ onFeedButtonClick: function(event) {
+ event.stopPropagation();
+
+ let feeds = gBrowser.selectedBrowser.feeds || [];
+ // If there are multiple feeds, the menu will open, so no need to do
+ // anything. If there are no feeds, nothing to do either.
+ if (feeds.length != 1)
+ return;
+
+ if (event.eventPhase == Event.AT_TARGET &&
+ (event.button == 0 || event.button == 1)) {
+ this.subscribeToFeed(feeds[0].href, event);
+ }
+ },
+
+ /** Called when the user clicks on the Subscribe to This Page... menu item.
+ * Builds a menu of unique feeds associated with the page, and if there
+ * is only one, shows the feed inline in the browser window.
+ * @param menuPopup
+ * The feed list menupopup to be populated.
+ * @returns true if the menu should be shown, false if there was only
+ * one feed and the feed should be shown inline in the browser
+ * window (do not show the menupopup).
+ */
+ buildFeedList: function(menuPopup) {
+ var feeds = gBrowser.selectedBrowser.feeds;
+ if (feeds == null) {
+ // XXX hack -- menu opening depends on setting of an "open"
+ // attribute, and the menu refuses to open if that attribute is
+ // set (because it thinks it's already open). onpopupshowing gets
+ // called after the attribute is unset, and it doesn't get unset
+ // if we return false. so we unset it here; otherwise, the menu
+ // refuses to work past this point.
+ menuPopup.parentNode.removeAttribute("open");
+ return false;
+ }
+
+ while (menuPopup.firstChild)
+ menuPopup.removeChild(menuPopup.firstChild);
+
+ if (feeds.length == 1) {
+ var feedButtonPM = document.getElementById("ub-feed-button");
+ if (feedButtonPM)
+ feedButtonPM.setAttribute("feed", feeds[0].href);
+ return false;
+ }
+
+ if (feeds.length <= 1)
+ return false;
+
+ // Build the menu showing the available feed choices for viewing.
+ for (let feedInfo of feeds) {
+ var menuItem = document.createElement("menuitem");
+ var baseTitle = feedInfo.title || feedInfo.href;
+ var labelStr = gNavigatorBundle.getFormattedString("feedShowFeedNew", [baseTitle]);
+ menuItem.setAttribute("class", "feed-menuitem");
+ menuItem.setAttribute("label", labelStr);
+ menuItem.setAttribute("feed", feedInfo.href);
+ menuItem.setAttribute("tooltiptext", feedInfo.href);
+ menuItem.setAttribute("crop", "center");
+ menuPopup.appendChild(menuItem);
+ }
+ return true;
+ },
+
+ /**
+ * Subscribe to a given feed. Called when
+ * 1. Page has a single feed and user clicks feed icon in location bar
+ * 2. Page has a single feed and user selects Subscribe menu item
+ * 3. Page has multiple feeds and user selects from feed icon popup
+ * 4. Page has multiple feeds and user selects from Subscribe submenu
+ * @param href
+ * The feed to subscribe to. May be null, in which case the
+ * event target's feed attribute is examined.
+ * @param event
+ * The event this method is handling. Used to decide where
+ * to open the preview UI. (Optional, unless href is null)
+ */
+ subscribeToFeed: function(href, event) {
+ // Just load the feed in the content area to either subscribe or show the
+ // preview UI
+ if (!href)
+ href = event.target.getAttribute("feed");
+ urlSecurityCheck(href, gBrowser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ var feedURI = makeURI(href, document.characterSet);
+ // Use the feed scheme so X-Moz-Is-Feed will be set
+ // The value doesn't matter
+ if (/^https?$/.test(feedURI.scheme))
+ href = "feed:" + href;
+ this.loadFeed(href, event);
+ },
+
+ loadFeed: function(href, event) {
+ var feeds = gBrowser.selectedBrowser.feeds;
+ try {
+ openUILink(href, event, { ignoreAlt: true });
+ }
+ finally {
+ // We might default to a livebookmarks modal dialog,
+ // so reset that if the user happens to click it again
+ gBrowser.selectedBrowser.feeds = feeds;
+ }
+ },
+
+ get _feedMenuitem() {
+ delete this._feedMenuitem;
+ return this._feedMenuitem = document.getElementById("singleFeedMenuitemState");
+ },
+
+ get _feedMenupopup() {
+ delete this._feedMenupopup;
+ return this._feedMenupopup = document.getElementById("multipleFeedsMenuState");
+ },
+
+ /**
+ * Update the browser UI to show whether or not feeds are available when
+ * a page is loaded or the user switches tabs to a page that has feeds.
+ */
+ updateFeeds: function() {
+ if (this._updateFeedTimeout)
+ clearTimeout(this._updateFeedTimeout);
+
+ var feeds = gBrowser.selectedBrowser.feeds;
+ var haveFeeds = feeds && feeds.length > 0;
+
+ var feedButtonPM = document.getElementById("ub-feed-button");
+
+ var feedButton = document.getElementById("feed-button");
+
+ if (feedButton)
+ feedButton.disabled = !haveFeeds;
+
+ if (feedButtonPM) {
+ if (!haveFeeds) {
+ feedButtonPM.collapsed = true;
+ feedButtonPM.removeAttribute("feed");
+ } else {
+ feedButtonPM.collapsed = !gPrefService.getBoolPref("browser.urlbar.rss");
+ }
+ }
+
+ if (!haveFeeds) {
+ this._feedMenuitem.setAttribute("disabled", "true");
+ this._feedMenuitem.removeAttribute("hidden");
+ this._feedMenupopup.setAttribute("hidden", "true");
+ return;
+ }
+
+ if (feeds.length > 1) {
+ if (feedButtonPM)
+ feedButtonPM.removeAttribute("feed");
+ this._feedMenuitem.setAttribute("hidden", "true");
+ this._feedMenupopup.removeAttribute("hidden");
+ } else {
+ if (feedButtonPM)
+ feedButtonPM.setAttribute("feed", feeds[0].href);
+ this._feedMenuitem.setAttribute("feed", feeds[0].href);
+ this._feedMenuitem.removeAttribute("disabled");
+ this._feedMenuitem.removeAttribute("hidden");
+ this._feedMenupopup.setAttribute("hidden", "true");
+ }
+ },
+
+ addFeed: function(link, targetDoc) {
+ // find which tab this is for, and set the attribute on the browser
+ var browserForLink = gBrowser.getBrowserForDocument(targetDoc);
+ if (!browserForLink) {
+ // ignore feeds loaded in subframes (see bug 305472)
+ return;
+ }
+
+ if (!browserForLink.feeds)
+ browserForLink.feeds = [];
+
+ browserForLink.feeds.push({ href: link.href, title: link.title });
+
+ // If this addition was for the current browser, update the UI. For
+ // background browsers, we'll update on tab switch.
+ if (browserForLink == gBrowser.selectedBrowser) {
+ var feedButtonPM = document.getElementById("ub-feed-button");
+ if (feedButtonPM)
+ feedButtonPM.collapsed = !gPrefService.getBoolPref("browser.urlbar.rss");
+ // Batch updates to avoid updating the UI for multiple onLinkAdded events
+ // fired within 100ms of each other.
+ if (this._updateFeedTimeout)
+ clearTimeout(this._updateFeedTimeout);
+ this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
+ }
+ }
+};
diff --git a/application/palemoon/base/content/browser-fullScreen.js b/application/palemoon/base/content/browser-fullScreen.js
new file mode 100644
index 000000000..b8a29199e
--- /dev/null
+++ b/application/palemoon/base/content/browser-fullScreen.js
@@ -0,0 +1,607 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+var FullScreen = {
+ _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ get _fullScrToggler() {
+ delete this._fullScrToggler;
+ return this._fullScrToggler = document.getElementById("fullscr-toggler");
+ },
+ toggle: function (event) {
+ var enterFS = window.fullScreen;
+
+ // We get the fullscreen event _before_ the window transitions into or out of FS mode.
+ if (event && event.type == "fullscreen")
+ enterFS = !enterFS;
+
+ // Toggle the View:FullScreen command, which controls elements like the
+ // fullscreen menuitem, menubars, and the appmenu.
+ let fullscreenCommand = document.getElementById("View:FullScreen");
+ if (enterFS) {
+ fullscreenCommand.setAttribute("checked", enterFS);
+ } else {
+ fullscreenCommand.removeAttribute("checked");
+ }
+
+#ifdef XP_MACOSX
+ // Make sure the menu items are adjusted.
+ document.getElementById("enterFullScreenItem").hidden = enterFS;
+ document.getElementById("exitFullScreenItem").hidden = !enterFS;
+#endif
+
+ // On OS X Lion we don't want to hide toolbars when entering fullscreen, unless
+ // we're entering DOM fullscreen, in which case we should hide the toolbars.
+ // If we're leaving fullscreen, then we'll go through the exit code below to
+ // make sure toolbars are made visible in the case of DOM fullscreen.
+ if (enterFS && this.useLionFullScreen) {
+ if (document.mozFullScreen) {
+ this.showXULChrome("toolbar", false);
+ }
+ else {
+ gNavToolbox.setAttribute("inFullscreen", true);
+ document.documentElement.setAttribute("inFullscreen", true);
+ }
+ return;
+ }
+
+ // show/hide menubars, toolbars (except the full screen toolbar)
+ this.showXULChrome("toolbar", !enterFS);
+
+ if (enterFS) {
+ // Add a tiny toolbar to receive mouseover and dragenter events, and provide affordance.
+ // This will help simulate the "collapse" metaphor while also requiring less code and
+ // events than raw listening of mouse coords. We don't add the toolbar in DOM full-screen
+ // mode, only browser full-screen mode.
+ if (!document.mozFullScreen) {
+ this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
+ }
+ if (gPrefService.getBoolPref("browser.fullscreen.autohide"))
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, false);
+
+ document.addEventListener("keypress", this._keyToggleCallback, false);
+ document.addEventListener("popupshown", this._setPopupOpen, false);
+ document.addEventListener("popuphidden", this._setPopupOpen, false);
+ // We don't animate the toolbar collapse if in DOM full-screen mode,
+ // as the size of the content area would still be changing after the
+ // mozfullscreenchange event fired, which could confuse content script.
+ this._shouldAnimate = !document.mozFullScreen;
+ this.mouseoverToggle(false);
+
+ // Autohide prefs
+ gPrefService.addObserver("browser.fullscreen", this, false);
+ }
+ else {
+ // The user may quit fullscreen during an animation
+ this._cancelAnimation();
+ gNavToolbox.style.marginTop = "";
+ if (this._isChromeCollapsed)
+ this.mouseoverToggle(true);
+ // This is needed if they use the context menu to quit fullscreen
+ this._isPopupOpen = false;
+
+ document.documentElement.removeAttribute("inDOMFullscreen");
+
+ this.cleanup();
+ }
+ },
+
+ exitDomFullScreen : function() {
+ document.mozCancelFullScreen();
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "activate":
+ if (document.mozFullScreen) {
+ this.showWarning(this.fullscreenDoc);
+ }
+ break;
+ case "transitionend":
+ if (event.propertyName == "opacity")
+ this.cancelWarning();
+ break;
+ }
+ },
+
+ enterDomFullscreen : function(event) {
+ if (!document.mozFullScreen)
+ return;
+
+ // However, if we receive a "MozEnteredDomFullScreen" event for a document
+ // which is not a subdocument of a currently active (ie. visible) browser
+ // or iframe, we know that we've switched to a different frame since the
+ // request to enter full-screen was made, so we should exit full-screen
+ // since the "full-screen document" isn't acutally visible.
+ if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell).isActive) {
+ document.mozCancelFullScreen();
+ return;
+ }
+
+ let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ if (focusManager.activeWindow != window) {
+ // The top-level window has lost focus since the request to enter
+ // full-screen was made. Cancel full-screen.
+ document.mozCancelFullScreen();
+ return;
+ }
+
+ document.documentElement.setAttribute("inDOMFullscreen", true);
+
+ if (gFindBarInitialized)
+ gFindBar.close();
+
+ this.showWarning(event.target);
+
+ // Exit DOM full-screen mode upon open, close, or change tab.
+ gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
+ gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
+ gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
+
+ // Add listener to detect when the fullscreen window is re-focused.
+ // If a fullscreen window loses focus, we show a warning when the
+ // fullscreen window is refocused.
+ if (!this.useLionFullScreen) {
+ window.addEventListener("activate", this);
+ }
+
+ // Cancel any "hide the toolbar" animation which is in progress, and make
+ // the toolbar hide immediately.
+ this._cancelAnimation();
+ this.mouseoverToggle(false);
+
+ // Remove listeners on the full-screen toggler, so that mouseover
+ // the top of the screen will not cause the toolbar to re-appear.
+ this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
+ },
+
+ cleanup: function () {
+ if (window.fullScreen) {
+ gBrowser.mPanelContainer.removeEventListener("mousemove",
+ this._collapseCallback, false);
+ document.removeEventListener("keypress", this._keyToggleCallback, false);
+ document.removeEventListener("popupshown", this._setPopupOpen, false);
+ document.removeEventListener("popuphidden", this._setPopupOpen, false);
+ gPrefService.removeObserver("browser.fullscreen", this);
+
+ this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
+ this.cancelWarning();
+ gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
+ gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
+ gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
+ if (!this.useLionFullScreen)
+ window.removeEventListener("activate", this);
+ this.fullscreenDoc = null;
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData)
+ {
+ if (aData == "browser.fullscreen.autohide") {
+ if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+ else {
+ gBrowser.mPanelContainer.removeEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+ }
+ },
+
+ // Event callbacks
+ _expandCallback: function()
+ {
+ FullScreen.mouseoverToggle(true);
+ },
+ _collapseCallback: function()
+ {
+ FullScreen.mouseoverToggle(false);
+ },
+ _keyToggleCallback: function(aEvent)
+ {
+ // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
+ // should provide a way to collapse them too.
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ FullScreen._shouldAnimate = false;
+ FullScreen.mouseoverToggle(false, true);
+ }
+ // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
+ else if (aEvent.keyCode == aEvent.DOM_VK_F6)
+ FullScreen.mouseoverToggle(true);
+ },
+
+ // Checks whether we are allowed to collapse the chrome
+ _isPopupOpen: false,
+ _isChromeCollapsed: false,
+ _safeToCollapse: function(forceHide)
+ {
+ if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
+ return false;
+
+ // a popup menu is open in chrome: don't collapse chrome
+ if (!forceHide && this._isPopupOpen)
+ return false;
+
+ // a textbox in chrome is focused (location bar anyone?): don't collapse chrome
+ if (document.commandDispatcher.focusedElement &&
+ document.commandDispatcher.focusedElement.ownerDocument == document &&
+ document.commandDispatcher.focusedElement.localName == "input") {
+ if (forceHide)
+ // hidden textboxes that still have focus are bad bad bad
+ document.commandDispatcher.focusedElement.blur();
+ else
+ return false;
+ }
+ return true;
+ },
+
+ _setPopupOpen: function(aEvent)
+ {
+ // Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
+ // Otherwise, they would not affect chrome and the user would expect the chrome to go away.
+ // e.g. we wouldn't want the autoscroll icon firing this event, so when the user
+ // toggles chrome when moving mouse to the top, it doesn't go away again.
+ if (aEvent.type == "popupshown" && !FullScreen._isChromeCollapsed &&
+ aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
+ FullScreen._isPopupOpen = true;
+ else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
+ aEvent.target.localName != "window")
+ FullScreen._isPopupOpen = false;
+ },
+
+ // Autohide helpers for the context menu item
+ getAutohide: function(aItem)
+ {
+ aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
+ },
+ setAutohide: function()
+ {
+ gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
+ },
+
+ // Animate the toolbars disappearing
+ _shouldAnimate: true,
+ _isAnimating: false,
+ _animationTimeout: 0,
+ _animationHandle: 0,
+ _animateUp: function() {
+ // check again, the user may have done something before the animation was due to start
+ if (!window.fullScreen || !this._safeToCollapse(false)) {
+ this._isAnimating = false;
+ this._shouldAnimate = true;
+ return;
+ }
+
+ this._animateStartTime = window.mozAnimationStartTime;
+ if (!this._animationHandle)
+ this._animationHandle = window.mozRequestAnimationFrame(this);
+ },
+
+ sample: function (timeStamp) {
+ const duration = 1500;
+ const timePassed = timeStamp - this._animateStartTime;
+ const pos = timePassed >= duration ? 1 :
+ 1 - Math.pow(1 - timePassed / duration, 4);
+
+ if (pos >= 1) {
+ // We've animated enough
+ this._cancelAnimation();
+ gNavToolbox.style.marginTop = "";
+ this.mouseoverToggle(false);
+ return;
+ }
+
+ gNavToolbox.style.marginTop = (gNavToolbox.boxObject.height * pos * -1) + "px";
+ this._animationHandle = window.mozRequestAnimationFrame(this);
+ },
+
+ _cancelAnimation: function() {
+ window.mozCancelAnimationFrame(this._animationHandle);
+ this._animationHandle = 0;
+ clearTimeout(this._animationTimeout);
+ this._isAnimating = false;
+ this._shouldAnimate = false;
+ },
+
+ cancelWarning: function(event) {
+ if (!this.warningBox)
+ return;
+ this.warningBox.removeEventListener("transitionend", this);
+ if (this.warningFadeOutTimeout) {
+ clearTimeout(this.warningFadeOutTimeout);
+ this.warningFadeOutTimeout = null;
+ }
+
+ // Ensure focus switches away from the (now hidden) warning box. If the user
+ // clicked buttons in the fullscreen key authorization UI, it would have been
+ // focused, and any key events would be directed at the (now hidden) chrome
+ // document instead of the target document.
+ gBrowser.selectedBrowser.focus();
+
+ this.warningBox.setAttribute("hidden", true);
+ this.warningBox.removeAttribute("fade-warning-out");
+ this.warningBox.removeAttribute("obscure-browser");
+ this.warningBox = null;
+ },
+
+ setFullscreenAllowed: function(isApproved) {
+ // The "remember decision" checkbox is hidden when showing for documents that
+ // the permission manager can't handle (documents with URIs without a host).
+ // We simply require those to be approved every time instead.
+ let rememberCheckbox = document.getElementById("full-screen-remember-decision");
+ let uri = this.fullscreenDoc.nodePrincipal.URI;
+ if (!rememberCheckbox.hidden) {
+ if (rememberCheckbox.checked)
+ Services.perms.add(uri,
+ "fullscreen",
+ isApproved ? Services.perms.ALLOW_ACTION : Services.perms.DENY_ACTION,
+ Services.perms.EXPIRE_NEVER);
+ else if (isApproved) {
+ // The user has only temporarily approved fullscren for this fullscreen
+ // session only. Add the permission (so Goanna knows to approve any further
+ // fullscreen requests for this host in this fullscreen session) but add
+ // a listener to revoke the permission when the chrome document exits
+ // fullscreen.
+ Services.perms.add(uri,
+ "fullscreen",
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_SESSION);
+ let host = uri.host;
+ var onFullscreenchange = function onFullscreenchange(event) {
+ if (event.target == document && document.mozFullScreenElement == null) {
+ // The chrome document has left fullscreen. Remove the temporary permission grant.
+ Services.perms.remove(host, "fullscreen");
+ document.removeEventListener("mozfullscreenchange", onFullscreenchange);
+ }
+ }
+ document.addEventListener("mozfullscreenchange", onFullscreenchange);
+ }
+ }
+ if (this.warningBox)
+ this.warningBox.setAttribute("fade-warning-out", "true");
+ // If the document has been granted fullscreen, notify Goanna so it can resume
+ // any pending pointer lock requests, otherwise exit fullscreen; the user denied
+ // the fullscreen request.
+ if (isApproved)
+ Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", "");
+ else
+ document.mozCancelFullScreen();
+ },
+
+ warningBox: null,
+ warningFadeOutTimeout: null,
+ fullscreenDoc: null,
+
+ // Shows the fullscreen approval UI, or if the domain has already been approved
+ // for fullscreen, shows a warning that the site has entered fullscreen for a short
+ // duration.
+ showWarning: function(targetDoc) {
+ if (!document.mozFullScreen ||
+ !gPrefService.getBoolPref("full-screen-api.approval-required"))
+ return;
+
+ // Set the strings on the fullscreen approval UI.
+ this.fullscreenDoc = targetDoc;
+ let uri = this.fullscreenDoc.nodePrincipal.URI;
+ let host = null;
+ try {
+ host = uri.host;
+ } catch (e) { }
+ let hostLabel = document.getElementById("full-screen-domain-text");
+ let rememberCheckbox = document.getElementById("full-screen-remember-decision");
+ let isApproved = false;
+ if (host) {
+ // Document's principal's URI has a host. Display a warning including the hostname and
+ // show UI to enable the user to permanently grant this host permission to enter fullscreen.
+ let utils = {};
+ Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
+ let displayHost = utils.DownloadUtils.getURIHost(uri.spec)[0];
+ let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+
+ hostLabel.textContent = bundle.formatStringFromName("fullscreen.entered", [displayHost], 1);
+ hostLabel.removeAttribute("hidden");
+
+ rememberCheckbox.label = bundle.formatStringFromName("fullscreen.rememberDecision", [displayHost], 1);
+ rememberCheckbox.checked = false;
+ rememberCheckbox.removeAttribute("hidden");
+
+ // Note we only allow documents whose principal's URI has a host to
+ // store permission grants.
+ isApproved = Services.perms.testPermission(uri, "fullscreen") == Services.perms.ALLOW_ACTION;
+ } else {
+ hostLabel.setAttribute("hidden", "true");
+ rememberCheckbox.setAttribute("hidden", "true");
+ }
+
+ // Note: the warning box can be non-null if the warning box from the previous request
+ // wasn't hidden before another request was made.
+ if (!this.warningBox) {
+ this.warningBox = document.getElementById("full-screen-warning-container");
+ // Add a listener to clean up state after the warning is hidden.
+ this.warningBox.addEventListener("transitionend", this);
+ this.warningBox.removeAttribute("hidden");
+ } else {
+ if (this.warningFadeOutTimeout) {
+ clearTimeout(this.warningFadeOutTimeout);
+ this.warningFadeOutTimeout = null;
+ }
+ this.warningBox.removeAttribute("fade-warning-out");
+ }
+
+ // If fullscreen mode has not yet been approved for the fullscreen
+ // document's domain, show the approval UI and don't auto fade out the
+ // fullscreen warning box. Otherwise, we're just notifying of entry into
+ // fullscreen mode. Note if the resource's host is null, we must be
+ // showing a local file or a local data URI, and we require explicit
+ // approval every time.
+ let authUI = document.getElementById("full-screen-approval-pane");
+ if (isApproved) {
+ authUI.setAttribute("hidden", "true");
+ this.warningBox.removeAttribute("obscure-browser");
+ } else {
+ // Partially obscure the <browser> element underneath the approval UI.
+ this.warningBox.setAttribute("obscure-browser", "true");
+ authUI.removeAttribute("hidden");
+ }
+
+ // If we're not showing the fullscreen approval UI, we're just notifying the user
+ // of the transition, so set a timeout to fade the warning out after a few moments.
+ if (isApproved)
+ this.warningFadeOutTimeout =
+ setTimeout(
+ function() {
+ if (this.warningBox)
+ this.warningBox.setAttribute("fade-warning-out", "true");
+ }.bind(this),
+ 3000);
+ },
+
+ mouseoverToggle: function(aShow, forceHide)
+ {
+ // Don't do anything if:
+ // a) we're already in the state we want,
+ // b) we're animating and will become collapsed soon, or
+ // c) we can't collapse because it would be undesirable right now
+ if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) ||
+ (!aShow && !this._safeToCollapse(forceHide)))
+ return;
+
+ // browser.fullscreen.animateUp
+ // 0 - never animate up
+ // 1 - animate only for first collapse after entering fullscreen (default for perf's sake)
+ // 2 - animate every time it collapses
+ if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 0)
+ this._shouldAnimate = false;
+
+ if (!aShow && this._shouldAnimate) {
+ this._isAnimating = true;
+ this._shouldAnimate = false;
+ this._animationTimeout = setTimeout(this._animateUp.bind(this), 800);
+ return;
+ }
+
+ // The chrome is collapsed so don't spam needless mousemove events
+ if (aShow) {
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+ else {
+ gBrowser.mPanelContainer.removeEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+
+ // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox,
+ // so we just move it off-screen instead. See bug 430687.
+ gNavToolbox.style.marginTop =
+ aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px";
+
+ this._fullScrToggler.collapsed = aShow;
+ this._isChromeCollapsed = !aShow;
+ if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2)
+ this._shouldAnimate = true;
+ },
+
+ showXULChrome: function(aTag, aShow)
+ {
+ var els = document.getElementsByTagNameNS(this._XULNS, aTag);
+
+ for (let el of els) {
+ // XXX don't interfere with previously collapsed toolbars
+ if (el.getAttribute("fullscreentoolbar") == "true") {
+ if (!aShow) {
+
+ var toolbarMode = el.getAttribute("mode");
+ if (toolbarMode != "text") {
+ el.setAttribute("saved-mode", toolbarMode);
+ el.setAttribute("saved-iconsize", el.getAttribute("iconsize"));
+ el.setAttribute("mode", "icons");
+ el.setAttribute("iconsize", "small");
+ }
+
+ // Give the main nav bar and the tab bar the fullscreen context menu,
+ // otherwise remove context menu to prevent breakage
+ el.setAttribute("saved-context", el.getAttribute("context"));
+ if (el.id == "nav-bar" || el.id == "TabsToolbar")
+ el.setAttribute("context", "autohide-context");
+ else
+ el.removeAttribute("context");
+
+ // Set the inFullscreen attribute to allow specific styling
+ // in fullscreen mode
+ el.setAttribute("inFullscreen", true);
+ }
+ else {
+ var restoreAttr = function restoreAttr(attrName) {
+ var savedAttr = "saved-" + attrName;
+ if (el.hasAttribute(savedAttr)) {
+ el.setAttribute(attrName, el.getAttribute(savedAttr));
+ el.removeAttribute(savedAttr);
+ }
+ }
+
+ restoreAttr("mode");
+ restoreAttr("iconsize");
+ restoreAttr("context");
+
+ el.removeAttribute("inFullscreen");
+ }
+ } else {
+ // use moz-collapsed so it doesn't persist hidden/collapsed,
+ // so that new windows don't have missing toolbars
+ if (aShow)
+ el.removeAttribute("moz-collapsed");
+ else
+ el.setAttribute("moz-collapsed", "true");
+ }
+ }
+
+ if (aShow) {
+ gNavToolbox.removeAttribute("inFullscreen");
+ document.documentElement.removeAttribute("inFullscreen");
+ } else {
+ gNavToolbox.setAttribute("inFullscreen", true);
+ document.documentElement.setAttribute("inFullscreen", true);
+ }
+
+ // In tabs-on-top mode, move window controls to the tab bar,
+ // and in tabs-on-bottom mode, move them back to the navigation toolbar.
+ // When there is a chance the tab bar may be collapsed, put window
+ // controls on nav bar.
+ var fullscreenctls = document.getElementById("window-controls");
+ var navbar = document.getElementById("nav-bar");
+ var ctlsOnTabbar = window.toolbar.visible &&
+ (navbar.collapsed || (TabsOnTop.enabled &&
+ !gPrefService.getBoolPref("browser.tabs.autoHide")));
+ if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) {
+ fullscreenctls.removeAttribute("flex");
+ document.getElementById("TabsToolbar").appendChild(fullscreenctls);
+ }
+ else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) {
+ fullscreenctls.setAttribute("flex", "1");
+ navbar.appendChild(fullscreenctls);
+ }
+ fullscreenctls.hidden = aShow;
+
+ ToolbarIconColor.inferFromText();
+ }
+};
+XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
+ // We'll only use OS X Lion full screen if we're
+ // * on OS X
+ // * on Lion or higher (Darwin 11+)
+ // * have fullscreenbutton="true"
+#ifdef XP_MACOSX
+ return parseFloat(Services.sysinfo.getProperty("version")) >= 11 &&
+ document.documentElement.getAttribute("fullscreenbutton") == "true";
+#else
+ return false;
+#endif
+});
diff --git a/application/palemoon/base/content/browser-fullZoom.js b/application/palemoon/base/content/browser-fullZoom.js
new file mode 100644
index 000000000..0837bf7c2
--- /dev/null
+++ b/application/palemoon/base/content/browser-fullZoom.js
@@ -0,0 +1,550 @@
+/*
+#ifdef 0
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#endif
+ */
+
+// One of the possible values for the mousewheel.* preferences.
+// From nsEventStateManager.cpp.
+const MOUSE_SCROLL_ZOOM = 3;
+
+/**
+ * Controls the "full zoom" setting and its site-specific preferences.
+ */
+var FullZoom = {
+ // Identifies the setting in the content prefs database.
+ name: "browser.content.full-zoom",
+
+ // browser.zoom.siteSpecific preference cache
+ _siteSpecificPref: undefined,
+
+ // browser.zoom.updateBackgroundTabs preference cache
+ updateBackgroundTabs: undefined,
+
+ // This maps the browser to monotonically increasing integer
+ // tokens. _browserTokenMap[browser] is increased each time the zoom is
+ // changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
+ _browserTokenMap: new WeakMap(),
+
+ // Stores initial locations if we receive onLocationChange
+ // events before we're initialized.
+ _initialLocations: new WeakMap(),
+
+ get siteSpecific() {
+ return this._siteSpecificPref;
+ },
+
+ //**************************************************************************//
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
+ Ci.nsIObserver,
+ Ci.nsIContentPrefObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ init: function FullZoom_init() {
+ // Listen for scrollwheel events so we can save scrollwheel-based changes.
+ window.addEventListener("DOMMouseScroll", this, false);
+
+ // Register ourselves with the service so we know when our pref changes.
+ this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ this._cps2.addObserverForName(this.name, this);
+
+ this._siteSpecificPref =
+ gPrefService.getBoolPref("browser.zoom.siteSpecific");
+ this.updateBackgroundTabs =
+ gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
+ // Listen for changes to the browser.zoom branch so we can enable/disable
+ // updating background tabs and per-site saving and restoring of zoom levels.
+ gPrefService.addObserver("browser.zoom.", this, true);
+
+ // If we received onLocationChange events for any of the current browsers
+ // before we were initialized we want to replay those upon initialization.
+ for (let browser of gBrowser.browsers) {
+ if (this._initialLocations.has(browser)) {
+ this.onLocationChange(...this._initialLocations.get(browser), browser);
+ }
+ }
+
+ // This should be nulled after initialization.
+ this._initialLocations.clear();
+ this._initialLocations = null;
+ },
+
+ destroy: function FullZoom_destroy() {
+ gPrefService.removeObserver("browser.zoom.", this);
+ this._cps2.removeObserverForName(this.name, this);
+ window.removeEventListener("DOMMouseScroll", this, false);
+ },
+
+
+ //**************************************************************************//
+ // Event Handlers
+
+ // nsIDOMEventListener
+
+ handleEvent: function FullZoom_handleEvent(event) {
+ switch (event.type) {
+ case "DOMMouseScroll":
+ this._handleMouseScrolled(event);
+ break;
+ }
+ },
+
+ _handleMouseScrolled: function FullZoom__handleMouseScrolled(event) {
+ // Construct the "mousewheel action" pref key corresponding to this event.
+ // Based on nsEventStateManager::WheelPrefs::GetBasePrefName().
+ var pref = "mousewheel.";
+
+ var pressedModifierCount = event.shiftKey + event.ctrlKey + event.altKey +
+ event.metaKey + event.getModifierState("OS");
+ if (pressedModifierCount != 1) {
+ pref += "default.";
+ } else if (event.shiftKey) {
+ pref += "with_shift.";
+ } else if (event.ctrlKey) {
+ pref += "with_control.";
+ } else if (event.altKey) {
+ pref += "with_alt.";
+ } else if (event.metaKey) {
+ pref += "with_meta.";
+ } else {
+ pref += "with_win.";
+ }
+
+ pref += "action";
+
+ // Don't do anything if this isn't a "zoom" scroll event.
+ var isZoomEvent = false;
+ try {
+ isZoomEvent = (gPrefService.getIntPref(pref) == MOUSE_SCROLL_ZOOM);
+ } catch (e) {}
+ if (!isZoomEvent)
+ return;
+
+ // XXX Lazily cache all the possible action prefs so we don't have to get
+ // them anew from the pref service for every scroll event? We'd have to
+ // make sure to observe them so we can update the cache when they change.
+
+ // We have to call _applyZoomToPref in a timeout because we handle the
+ // event before the event state manager has a chance to apply the zoom
+ // during nsEventStateManager::PostHandleEvent.
+ let browser = gBrowser.selectedBrowser;
+ let token = this._getBrowserToken(browser);
+ window.setTimeout(function () {
+ if (token.isCurrent)
+ this._applyZoomToPref(browser);
+ }.bind(this), 0);
+ },
+
+ // nsIObserver
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ switch (aData) {
+ case "browser.zoom.siteSpecific":
+ this._siteSpecificPref =
+ gPrefService.getBoolPref("browser.zoom.siteSpecific");
+ break;
+ case "browser.zoom.updateBackgroundTabs":
+ this.updateBackgroundTabs =
+ gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
+ break;
+ }
+ break;
+ }
+ },
+
+ // nsIContentPrefObserver
+
+ onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) {
+ this._onContentPrefChanged(aGroup, aValue);
+ },
+
+ onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) {
+ this._onContentPrefChanged(aGroup, undefined);
+ },
+
+ /**
+ * Appropriately updates the zoom level after a content preference has
+ * changed.
+ *
+ * @param aGroup The group of the changed preference.
+ * @param aValue The new value of the changed preference. Pass undefined to
+ * indicate the preference's removal.
+ */
+ _onContentPrefChanged: function FullZoom__onContentPrefChanged(aGroup, aValue) {
+ if (this._isNextContentPrefChangeInternal) {
+ // Ignore changes that FullZoom itself makes. This works because the
+ // content pref service calls callbacks before notifying observers, and it
+ // does both in the same turn of the event loop.
+ delete this._isNextContentPrefChangeInternal;
+ return;
+ }
+
+ let browser = gBrowser.selectedBrowser;
+ if (!browser.currentURI)
+ return;
+
+ let domain = this._cps2.extractDomain(browser.currentURI.spec);
+ if (aGroup) {
+ if (aGroup == domain)
+ this._applyPrefToZoom(aValue, browser);
+ return;
+ }
+
+ this._globalValue = aValue === undefined ? aValue :
+ this._ensureValid(aValue);
+
+ // If the current page doesn't have a site-specific preference, then its
+ // zoom should be set to the new global preference now that the global
+ // preference has changed.
+ let hasPref = false;
+ let ctxt = this._loadContextFromBrowser(browser);
+ let token = this._getBrowserToken(browser);
+ this._cps2.getByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
+ handleResult: function () hasPref = true,
+ handleCompletion: function () {
+ if (!hasPref && token.isCurrent)
+ this._applyPrefToZoom(undefined, browser);
+ }.bind(this)
+ });
+ },
+
+ // location change observer
+
+ /**
+ * Called when the location of a tab changes.
+ * When that happens, we need to update the current zoom level if appropriate.
+ *
+ * @param aURI
+ * A URI object representing the new location.
+ * @param aIsTabSwitch
+ * Whether this location change has happened because of a tab switch.
+ * @param aBrowser
+ * (optional) browser object displaying the document
+ */
+ onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) {
+ let browser = aBrowser || gBrowser.selectedBrowser;
+ // If we haven't been initialized yet but receive an onLocationChange
+ // notification then let's store and replay it upon initialization.
+ if (this._initialLocations) {
+ this._initialLocations.set(browser, [aURI, aIsTabSwitch]);
+ return;
+ }
+
+ // Ignore all pending async zoom accesses in the browser. Pending accesses
+ // that started before the location change will be prevented from applying
+ // to the new location.
+ this._ignorePendingZoomAccesses(browser);
+
+ if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
+ this._notifyOnLocationChange();
+ return;
+ }
+
+ // Avoid the cps roundtrip and apply the default/global pref.
+ if (aURI.spec == "about:blank") {
+ this._applyPrefToZoom(undefined, browser,
+ this._notifyOnLocationChange.bind(this));
+ return;
+ }
+
+ // Media documents should always start at 1, and are not affected by prefs.
+ if (!aIsTabSwitch && browser.isSyntheticDocument) {
+ ZoomManager.setZoomForBrowser(browser, 1);
+ // _ignorePendingZoomAccesses already called above, so no need here.
+ this._notifyOnLocationChange();
+ return;
+ }
+
+ // See if the zoom pref is cached.
+ let ctxt = this._loadContextFromBrowser(browser);
+ let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
+ if (pref) {
+ this._applyPrefToZoom(pref.value, browser,
+ this._notifyOnLocationChange.bind(this));
+ return;
+ }
+
+ // It's not cached, so we have to asynchronously fetch it.
+ let value = undefined;
+ let token = this._getBrowserToken(browser);
+ this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
+ handleResult: function (resultPref) value = resultPref.value,
+ handleCompletion: function () {
+ if (!token.isCurrent) {
+ this._notifyOnLocationChange();
+ return;
+ }
+ this._applyPrefToZoom(value, browser,
+ this._notifyOnLocationChange.bind(this));
+ }.bind(this)
+ });
+ },
+
+ // update state of zoom type menu item
+
+ updateMenu: function FullZoom_updateMenu() {
+ var menuItem = document.getElementById("toggle_zoom");
+
+ menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
+ },
+
+ //**************************************************************************//
+ // Setting & Pref Manipulation
+
+ /**
+ * Reduces the zoom level of the page in the current browser.
+ */
+ reduce: function FullZoom_reduce() {
+ ZoomManager.reduce();
+ let browser = gBrowser.selectedBrowser;
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Enlarges the zoom level of the page in the current browser.
+ */
+ enlarge: function FullZoom_enlarge() {
+ ZoomManager.enlarge();
+ let browser = gBrowser.selectedBrowser;
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Sets the zoom level of the page in the current browser to the global zoom
+ * level.
+ */
+ reset: function FullZoom_reset() {
+ let browser = gBrowser.selectedBrowser;
+ let token = this._getBrowserToken(browser);
+ this._getGlobalValue(browser, function (value) {
+ if (token.isCurrent) {
+ ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
+ this._ignorePendingZoomAccesses(browser);
+ }
+ });
+ this._removePref(browser);
+ },
+
+ /**
+ * Set the zoom level for a given browser.
+ *
+ * Per nsPresContext::setFullZoom, we can set the zoom to its current value
+ * without significant impact on performance, as the setting is only applied
+ * if it differs from the current setting. In fact getting the zoom and then
+ * checking ourselves if it differs costs more.
+ *
+ * And perhaps we should always set the zoom even if it was more expensive,
+ * since nsDocumentViewer::SetTextZoom claims that child documents can have
+ * a different text zoom (although it would be unusual), and it implies that
+ * those child text zooms should get updated when the parent zoom gets set,
+ * and perhaps the same is true for full zoom
+ * (although nsDocumentViewer::SetFullZoom doesn't mention it).
+ *
+ * So when we apply new zoom values to the browser, we simply set the zoom.
+ * We don't check first to see if the new value is the same as the current
+ * one.
+ *
+ * @param aValue The zoom level value.
+ * @param aBrowser The zoom is set in this browser. Required.
+ * @param aCallback If given, it's asynchronously called when complete.
+ */
+ _applyPrefToZoom: function FullZoom__applyPrefToZoom(aValue, aBrowser, aCallback) {
+ if (!this.siteSpecific || gInPrintPreviewMode) {
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ // The browser is sometimes half-destroyed because this method is called
+ // by content pref service callbacks, which themselves can be called at any
+ // time, even after browsers are closed.
+ if (!aBrowser.parentNode || aBrowser.isSyntheticDocument) {
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ if (aValue !== undefined) {
+ ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(aValue));
+ this._ignorePendingZoomAccesses(aBrowser);
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ let token = this._getBrowserToken(aBrowser);
+ this._getGlobalValue(aBrowser, function (value) {
+ if (token.isCurrent) {
+ ZoomManager.setZoomForBrowser(aBrowser, value === undefined ? 1 : value);
+ this._ignorePendingZoomAccesses(aBrowser);
+ }
+ this._executeSoon(aCallback);
+ });
+ },
+
+ /**
+ * Saves the zoom level of the page in the given browser to the content
+ * prefs store.
+ *
+ * @param browser The zoom of this browser will be saved. Required.
+ */
+ _applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
+ if (!this.siteSpecific ||
+ gInPrintPreviewMode ||
+ browser.isSyntheticDocument)
+ return;
+
+ this._cps2.set(browser.currentURI.spec, this.name,
+ ZoomManager.getZoomForBrowser(browser),
+ this._loadContextFromBrowser(browser), {
+ handleCompletion: function () {
+ this._isNextContentPrefChangeInternal = true;
+ }.bind(this),
+ });
+ },
+
+ /**
+ * Removes from the content prefs store the zoom level of the given browser.
+ *
+ * @param browser The zoom of this browser will be removed. Required.
+ */
+ _removePref: function FullZoom__removePref(browser) {
+ if (browser.isSyntheticDocument)
+ return;
+ let ctxt = this._loadContextFromBrowser(browser);
+ this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
+ handleCompletion: function () {
+ this._isNextContentPrefChangeInternal = true;
+ }.bind(this),
+ });
+ },
+
+ //**************************************************************************//
+ // Utilities
+
+ /**
+ * Returns the zoom change token of the given browser. Asynchronous
+ * operations that access the given browser's zoom should use this method to
+ * capture the token before starting and use token.isCurrent to determine if
+ * it's safe to access the zoom when done. If token.isCurrent is false, then
+ * after the async operation started, either the browser's zoom was changed or
+ * the browser was destroyed, and depending on what the operation is doing, it
+ * may no longer be safe to set and get its zoom.
+ *
+ * @param browser The token of this browser will be returned.
+ * @return An object with an "isCurrent" getter.
+ */
+ _getBrowserToken: function FullZoom__getBrowserToken(browser) {
+ let map = this._browserTokenMap;
+ if (!map.has(browser))
+ map.set(browser, 0);
+ return {
+ token: map.get(browser),
+ get isCurrent() {
+ // At this point, the browser may have been destructed and unbound but
+ // its outer ID not removed from the map because outer-window-destroyed
+ // hasn't been received yet. In that case, the browser is unusable, it
+ // has no properties, so return false. Check for this case by getting a
+ // property, say, docShell.
+ return map.get(browser) === this.token && browser.parentNode;
+ },
+ };
+ },
+
+ /**
+ * Increments the zoom change token for the given browser so that pending
+ * async operations know that it may be unsafe to access they zoom when they
+ * finish.
+ *
+ * @param browser Pending accesses in this browser will be ignored.
+ */
+ _ignorePendingZoomAccesses: function FullZoom__ignorePendingZoomAccesses(browser) {
+ let map = this._browserTokenMap;
+ map.set(browser, (map.get(browser) || 0) + 1);
+ },
+
+ _ensureValid: function FullZoom__ensureValid(aValue) {
+ // Note that undefined is a valid value for aValue that indicates a known-
+ // not-to-exist value.
+ if (isNaN(aValue))
+ return 1;
+
+ if (aValue < ZoomManager.MIN)
+ return ZoomManager.MIN;
+
+ if (aValue > ZoomManager.MAX)
+ return ZoomManager.MAX;
+
+ return aValue;
+ },
+
+ /**
+ * Gets the global browser.content.full-zoom content preference.
+ *
+ * WARNING: callback may be called synchronously or asynchronously. The
+ * reason is that it's usually desirable to avoid turns of the event loop
+ * where possible, since they can lead to visible, jarring jumps in zoom
+ * level. It's not always possible to avoid them, though. As a convenience,
+ * then, this method takes a callback and returns nothing.
+ *
+ * @param browser The content browser pertaining to the zoom.
+ * @param callback Synchronously or asynchronously called when done. It's
+ * bound to this object (FullZoom) and called as:
+ * callback(prefValue)
+ */
+ _getGlobalValue: function FullZoom__getGlobalValue(browser, callback) {
+ // * !("_globalValue" in this) => global value not yet cached.
+ // * this._globalValue === undefined => global value known not to exist.
+ // * Otherwise, this._globalValue is a number, the global value.
+ if ("_globalValue" in this) {
+ callback.call(this, this._globalValue, true);
+ return;
+ }
+ let value = undefined;
+ this._cps2.getGlobal(this.name, this._loadContextFromBrowser(browser), {
+ handleResult: function (pref) value = pref.value,
+ handleCompletion: function (reason) {
+ this._globalValue = this._ensureValid(value);
+ callback.call(this, this._globalValue);
+ }.bind(this)
+ });
+ },
+
+ /**
+ * Gets the load context from the given content browser.
+ *
+ * @param Browser The Browser whose load context will be returned.
+ * @return The nsILoadContext of the given Browser.
+ */
+ _loadContextFromBrowser: function FullZoom__loadContextFromBrowser(browser) {
+ return browser.loadContext;
+ },
+
+ /**
+ * Asynchronously broadcasts a "browser-fullZoom:locationChange" notification
+ * so that tests can select tabs, load pages, etc. and be notified when the
+ * zoom levels on those pages change. The notification is always asynchronous
+ * so that observers are guaranteed a consistent behavior.
+ */
+ _notifyOnLocationChange: function FullZoom__notifyOnLocationChange() {
+ this._executeSoon(function () {
+ Services.obs.notifyObservers(null, "browser-fullZoom:locationChange", "");
+ });
+ },
+
+ _executeSoon: function FullZoom__executeSoon(callback) {
+ if (!callback)
+ return;
+ Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+};
diff --git a/application/palemoon/base/content/browser-gestureSupport.js b/application/palemoon/base/content/browser-gestureSupport.js
new file mode 100644
index 000000000..d88f47c79
--- /dev/null
+++ b/application/palemoon/base/content/browser-gestureSupport.js
@@ -0,0 +1,1059 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Simple gestures support
+//
+// As per bug #412486, web content must not be allowed to receive any
+// simple gesture events. Multi-touch gesture APIs are in their
+// infancy and we do NOT want to be forced into supporting an API that
+// will probably have to change in the future. (The current Mac OS X
+// API is undocumented and was reverse-engineered.) Until support is
+// implemented in the event dispatcher to keep these events as
+// chrome-only, we must listen for the simple gesture events during
+// the capturing phase and call stopPropagation on every event.
+
+let gGestureSupport = {
+ _currentRotation: 0,
+ _lastRotateDelta: 0,
+ _rotateMomentumThreshold: .75,
+
+ /**
+ * Add or remove mouse gesture event listeners
+ *
+ * @param aAddListener
+ * True to add/init listeners and false to remove/uninit
+ */
+ init: function GS_init(aAddListener) {
+ // Bug 863514 - Make gesture support work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ const gestureEvents = ["SwipeGestureStart",
+ "SwipeGestureUpdate", "SwipeGestureEnd", "SwipeGesture",
+ "MagnifyGestureStart", "MagnifyGestureUpdate", "MagnifyGesture",
+ "RotateGestureStart", "RotateGestureUpdate", "RotateGesture",
+ "TapGesture", "PressTapGesture"];
+
+ let addRemove = aAddListener ? window.addEventListener :
+ window.removeEventListener;
+
+ gestureEvents.forEach(function (event) addRemove("Moz" + event, this, true),
+ this);
+ },
+
+ /**
+ * Dispatch events based on the type of mouse gesture event. For now, make
+ * sure to stop propagation of every gesture event so that web content cannot
+ * receive gesture events.
+ *
+ * @param aEvent
+ * The gesture event to handle
+ */
+ handleEvent: function GS_handleEvent(aEvent) {
+ if (!Services.prefs.getBoolPref(
+ "dom.debug.propagate_gesture_events_through_content")) {
+ aEvent.stopPropagation();
+ }
+
+ // Create a preference object with some defaults
+ let def = function(aThreshold, aLatched)
+ ({ threshold: aThreshold, latched: !!aLatched });
+
+ switch (aEvent.type) {
+ case "MozSwipeGestureStart":
+ aEvent.preventDefault();
+ this._setupSwipeGesture(aEvent);
+ break;
+ case "MozSwipeGestureUpdate":
+ aEvent.preventDefault();
+ this._doUpdate(aEvent);
+ break;
+ case "MozSwipeGestureEnd":
+ aEvent.preventDefault();
+ this._doEnd(aEvent);
+ break;
+ case "MozSwipeGesture":
+ aEvent.preventDefault();
+ this.onSwipe(aEvent);
+ break;
+ case "MozMagnifyGestureStart":
+ aEvent.preventDefault();
+#ifdef XP_WIN
+ this._setupGesture(aEvent, "pinch", def(25, 0), "out", "in");
+#else
+ this._setupGesture(aEvent, "pinch", def(150, 1), "out", "in");
+#endif
+ break;
+ case "MozRotateGestureStart":
+ aEvent.preventDefault();
+ this._setupGesture(aEvent, "twist", def(25, 0), "right", "left");
+ break;
+ case "MozMagnifyGestureUpdate":
+ case "MozRotateGestureUpdate":
+ aEvent.preventDefault();
+ this._doUpdate(aEvent);
+ break;
+ case "MozTapGesture":
+ aEvent.preventDefault();
+ this._doAction(aEvent, ["tap"]);
+ break;
+ case "MozRotateGesture":
+ aEvent.preventDefault();
+ this._doAction(aEvent, ["twist", "end"]);
+ break;
+ /* case "MozPressTapGesture":
+ break; */
+ }
+ },
+
+ /**
+ * Called at the start of "pinch" and "twist" gestures to setup all of the
+ * information needed to process the gesture
+ *
+ * @param aEvent
+ * The continual motion start event to handle
+ * @param aGesture
+ * Name of the gesture to handle
+ * @param aPref
+ * Preference object with the names of preferences and defaults
+ * @param aInc
+ * Command to trigger for increasing motion (without gesture name)
+ * @param aDec
+ * Command to trigger for decreasing motion (without gesture name)
+ */
+ _setupGesture: function GS__setupGesture(aEvent, aGesture, aPref, aInc, aDec) {
+ // Try to load user-set values from preferences
+ for (let [pref, def] in Iterator(aPref))
+ aPref[pref] = this._getPref(aGesture + "." + pref, def);
+
+ // Keep track of the total deltas and latching behavior
+ let offset = 0;
+ let latchDir = aEvent.delta > 0 ? 1 : -1;
+ let isLatched = false;
+
+ // Create the update function here to capture closure state
+ this._doUpdate = function GS__doUpdate(aEvent) {
+ // Update the offset with new event data
+ offset += aEvent.delta;
+
+ // Check if the cumulative deltas exceed the threshold
+ if (Math.abs(offset) > aPref["threshold"]) {
+ // Trigger the action if we don't care about latching; otherwise, make
+ // sure either we're not latched and going the same direction of the
+ // initial motion; or we're latched and going the opposite way
+ let sameDir = (latchDir ^ offset) >= 0;
+ if (!aPref["latched"] || (isLatched ^ sameDir)) {
+ this._doAction(aEvent, [aGesture, offset > 0 ? aInc : aDec]);
+
+ // We must be getting latched or leaving it, so just toggle
+ isLatched = !isLatched;
+ }
+
+ // Reset motion counter to prepare for more of the same gesture
+ offset = 0;
+ }
+ };
+
+ // The start event also contains deltas, so handle an update right away
+ this._doUpdate(aEvent);
+ },
+
+ /**
+ * Checks whether a swipe gesture event can navigate the browser history or
+ * not.
+ *
+ * @param aEvent
+ * The swipe gesture event.
+ * @return true if the swipe event may navigate the history, false othwerwise.
+ */
+ _swipeNavigatesHistory: function GS__swipeNavigatesHistory(aEvent) {
+ return this._getCommand(aEvent, ["swipe", "left"])
+ == "Browser:BackOrBackDuplicate" &&
+ this._getCommand(aEvent, ["swipe", "right"])
+ == "Browser:ForwardOrForwardDuplicate";
+ },
+
+ /**
+ * Sets up the history swipe animations for a swipe gesture event, if enabled.
+ *
+ * @param aEvent
+ * The swipe gesture start event.
+ */
+ _setupSwipeGesture: function GS__setupSwipeGesture(aEvent) {
+ if (!this._swipeNavigatesHistory(aEvent))
+ return;
+
+ let canGoBack = gHistorySwipeAnimation.canGoBack();
+ let canGoForward = gHistorySwipeAnimation.canGoForward();
+ let isLTR = gHistorySwipeAnimation.isLTR;
+
+ if (canGoBack)
+ aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_LEFT :
+ aEvent.DIRECTION_RIGHT;
+ if (canGoForward)
+ aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_RIGHT :
+ aEvent.DIRECTION_LEFT;
+
+ gHistorySwipeAnimation.startAnimation();
+
+ this._doUpdate = function GS__doUpdate(aEvent) {
+ gHistorySwipeAnimation.updateAnimation(aEvent.delta);
+ };
+
+ this._doEnd = function GS__doEnd(aEvent) {
+ gHistorySwipeAnimation.swipeEndEventReceived();
+
+ this._doUpdate = function (aEvent) {};
+ this._doEnd = function (aEvent) {};
+ }
+ },
+
+ /**
+ * Generator producing the powerset of the input array where the first result
+ * is the complete set and the last result (before StopIteration) is empty.
+ *
+ * @param aArray
+ * Source array containing any number of elements
+ * @yield Array that is a subset of the input array from full set to empty
+ */
+ _power: function GS__power(aArray) {
+ // Create a bitmask based on the length of the array
+ let num = 1 << aArray.length;
+ while (--num >= 0) {
+ // Only select array elements where the current bit is set
+ yield aArray.reduce(function (aPrev, aCurr, aIndex) {
+ if (num & 1 << aIndex)
+ aPrev.push(aCurr);
+ return aPrev;
+ }, []);
+ }
+ },
+
+ /**
+ * Determine what action to do for the gesture based on which keys are
+ * pressed and which commands are set, and execute the command.
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aGesture
+ * Array of gesture name parts (to be joined by periods)
+ * @return Name of the executed command. Returns null if no command is
+ * found.
+ */
+ _doAction: function GS__doAction(aEvent, aGesture) {
+ let command = this._getCommand(aEvent, aGesture);
+ return command && this._doCommand(aEvent, command);
+ },
+
+ /**
+ * Determine what action to do for the gesture based on which keys are
+ * pressed and which commands are set
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aGesture
+ * Array of gesture name parts (to be joined by periods)
+ */
+ _getCommand: function GS__getCommand(aEvent, aGesture) {
+ // Create an array of pressed keys in a fixed order so that a command for
+ // "meta" is preferred over "ctrl" when both buttons are pressed (and a
+ // command for both don't exist)
+ let keyCombos = [];
+ ["shift", "alt", "ctrl", "meta"].forEach(function (key) {
+ if (aEvent[key + "Key"])
+ keyCombos.push(key);
+ });
+
+ // Try each combination of key presses in decreasing order for commands
+ for (let subCombo of this._power(keyCombos)) {
+ // Convert a gesture and pressed keys into the corresponding command
+ // action where the preference has the gesture before "shift" before
+ // "alt" before "ctrl" before "meta" all separated by periods
+ let command;
+ try {
+ command = this._getPref(aGesture.concat(subCombo).join("."));
+ } catch (e) {}
+
+ if (command)
+ return command;
+ }
+ return null;
+ },
+
+ /**
+ * Execute the specified command.
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aCommand
+ * Name of the command found for the event's keys and gesture.
+ */
+ _doCommand: function GS__doCommand(aEvent, aCommand) {
+ let node = document.getElementById(aCommand);
+ if (node) {
+ if (node.getAttribute("disabled") != "true") {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey,
+ aEvent.shiftKey, aEvent.metaKey, aEvent);
+ node.dispatchEvent(cmdEvent);
+ }
+
+ }
+ else {
+ goDoCommand(aCommand);
+ }
+ },
+
+ /**
+ * Handle continual motion events. This function will be set by
+ * _setupGesture or _setupSwipe.
+ *
+ * @param aEvent
+ * The continual motion update event to handle
+ */
+ _doUpdate: function(aEvent) {},
+
+ /**
+ * Handle gesture end events. This function will be set by _setupSwipe.
+ *
+ * @param aEvent
+ * The gesture end event to handle
+ */
+ _doEnd: function(aEvent) {},
+
+ /**
+ * Convert the swipe gesture into a browser action based on the direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ */
+ onSwipe: function GS_onSwipe(aEvent) {
+ // Figure out which one (and only one) direction was triggered
+ for (let dir of ["UP", "RIGHT", "DOWN", "LEFT"]) {
+ if (aEvent.direction == aEvent["DIRECTION_" + dir]) {
+ this._coordinateSwipeEventWithAnimation(aEvent, dir);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Process a swipe event based on the given direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ processSwipeEvent: function GS_processSwipeEvent(aEvent, aDir) {
+ this._doAction(aEvent, ["swipe", aDir.toLowerCase()]);
+ },
+
+ /**
+ * Coordinates the swipe event with the swipe animation, if any.
+ * If an animation is currently running, the swipe event will be
+ * processed once the animation stops. This will guarantee a fluid
+ * motion of the animation.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ _coordinateSwipeEventWithAnimation:
+ function GS__coordinateSwipeEventWithAnimation(aEvent, aDir) {
+ if ((gHistorySwipeAnimation.isAnimationRunning()) &&
+ (aDir == "RIGHT" || aDir == "LEFT")) {
+ gHistorySwipeAnimation.processSwipeEvent(aEvent, aDir);
+ }
+ else {
+ this.processSwipeEvent(aEvent, aDir);
+ }
+ },
+
+ /**
+ * Get a gesture preference or use a default if it doesn't exist
+ *
+ * @param aPref
+ * Name of the preference to load under the gesture branch
+ * @param aDef
+ * Default value if the preference doesn't exist
+ */
+ _getPref: function GS__getPref(aPref, aDef) {
+ // Preferences branch under which all gestures preferences are stored
+ const branch = "browser.gesture.";
+
+ try {
+ // Determine what type of data to load based on default value's type
+ let type = typeof aDef;
+ let getFunc = "get" + (type == "boolean" ? "Bool" :
+ type == "number" ? "Int" : "Char") + "Pref";
+ return gPrefService[getFunc](branch + aPref);
+ }
+ catch (e) {
+ return aDef;
+ }
+ },
+
+ /**
+ * Perform rotation for ImageDocuments
+ *
+ * @param aEvent
+ * The MozRotateGestureUpdate event triggering this call
+ */
+ rotate: function(aEvent) {
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+ // If we're currently snapping, cancel that snap
+ if (contentElement.classList.contains("completeRotation"))
+ this._clearCompleteRotation();
+
+ this.rotation = Math.round(this.rotation + aEvent.delta);
+ contentElement.style.transform = "rotate(" + this.rotation + "deg)";
+ this._lastRotateDelta = aEvent.delta;
+ },
+
+ /**
+ * Perform a rotation end for ImageDocuments
+ */
+ rotateEnd: function() {
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+
+ let transitionRotation = 0;
+
+ // The reason that 360 is allowed here is because when rotating between
+ // 315 and 360, setting rotate(0deg) will cause it to rotate the wrong
+ // direction around--spinning wildly.
+ if (this.rotation <= 45)
+ transitionRotation = 0;
+ else if (this.rotation > 45 && this.rotation <= 135)
+ transitionRotation = 90;
+ else if (this.rotation > 135 && this.rotation <= 225)
+ transitionRotation = 180;
+ else if (this.rotation > 225 && this.rotation <= 315)
+ transitionRotation = 270;
+ else
+ transitionRotation = 360;
+
+ // If we're going fast enough, and we didn't already snap ahead of rotation,
+ // then snap ahead of rotation to simulate momentum
+ if (this._lastRotateDelta > this._rotateMomentumThreshold &&
+ this.rotation > transitionRotation)
+ transitionRotation += 90;
+ else if (this._lastRotateDelta < -1 * this._rotateMomentumThreshold &&
+ this.rotation < transitionRotation)
+ transitionRotation -= 90;
+
+ // Only add the completeRotation class if it is is necessary
+ if (transitionRotation != this.rotation) {
+ contentElement.classList.add("completeRotation");
+ contentElement.addEventListener("transitionend", this._clearCompleteRotation);
+ }
+
+ contentElement.style.transform = "rotate(" + transitionRotation + "deg)";
+ this.rotation = transitionRotation;
+ },
+
+ /**
+ * Gets the current rotation for the ImageDocument
+ */
+ get rotation() {
+ return this._currentRotation;
+ },
+
+ /**
+ * Sets the current rotation for the ImageDocument
+ *
+ * @param aVal
+ * The new value to take. Can be any value, but it will be bounded to
+ * 0 inclusive to 360 exclusive.
+ */
+ set rotation(aVal) {
+ this._currentRotation = aVal % 360;
+ if (this._currentRotation < 0)
+ this._currentRotation += 360;
+ return this._currentRotation;
+ },
+
+ /**
+ * When the location/tab changes, need to reload the current rotation for the
+ * image
+ */
+ restoreRotationState: function() {
+ // Bug 863514 - Make gesture support work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ let transformValue = content.window.getComputedStyle(contentElement, null)
+ .transform;
+
+ if (transformValue == "none") {
+ this.rotation = 0;
+ return;
+ }
+
+ // transformValue is a rotation matrix--split it and do mathemagic to
+ // obtain the real rotation value
+ transformValue = transformValue.split("(")[1]
+ .split(")")[0]
+ .split(",");
+ this.rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
+ (180 / Math.PI));
+ },
+
+ /**
+ * Removes the transition rule by removing the completeRotation class
+ */
+ _clearCompleteRotation: function() {
+ let contentElement = content.document &&
+ content.document instanceof ImageDocument &&
+ content.document.body &&
+ content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+ contentElement.classList.remove("completeRotation");
+ contentElement.removeEventListener("transitionend", this._clearCompleteRotation);
+ },
+};
+
+// History Swipe Animation Support (bug 678392)
+let gHistorySwipeAnimation = {
+
+ active: false,
+ isLTR: false,
+
+ /**
+ * Initializes the support for history swipe animations, if it is supported
+ * by the platform/configuration.
+ */
+ init: function HSA_init() {
+ if (!this._isSupported())
+ return;
+
+ this.active = false;
+ this.isLTR = document.documentElement.mozMatchesSelector(
+ ":-moz-locale-dir(ltr)");
+ this._trackedSnapshots = [];
+ this._historyIndex = -1;
+ this._boxWidth = -1;
+ this._maxSnapshots = this._getMaxSnapshots();
+ this._lastSwipeDir = "";
+
+ // We only want to activate history swipe animations if we store snapshots.
+ // If we don't store any, we handle horizontal swipes without animations.
+ if (this._maxSnapshots > 0) {
+ this.active = true;
+ gBrowser.addEventListener("pagehide", this, false);
+ gBrowser.addEventListener("pageshow", this, false);
+ gBrowser.addEventListener("popstate", this, false);
+ gBrowser.tabContainer.addEventListener("TabClose", this, false);
+ }
+ },
+
+ /**
+ * Uninitializes the support for history swipe animations.
+ */
+ uninit: function HSA_uninit() {
+ gBrowser.removeEventListener("pagehide", this, false);
+ gBrowser.removeEventListener("pageshow", this, false);
+ gBrowser.removeEventListener("popstate", this, false);
+ gBrowser.tabContainer.removeEventListener("TabClose", this, false);
+
+ this.active = false;
+ this.isLTR = false;
+ },
+
+ /**
+ * Starts the swipe animation and handles fast swiping (i.e. a swipe animation
+ * is already in progress when a new one is initiated).
+ */
+ startAnimation: function HSA_startAnimation() {
+ if (this.isAnimationRunning()) {
+ gBrowser.stop();
+ this._lastSwipeDir = "RELOAD"; // just ensure that != ""
+ this._canGoBack = this.canGoBack();
+ this._canGoForward = this.canGoForward();
+ this._handleFastSwiping();
+ }
+ else {
+ this._historyIndex = gBrowser.webNavigation.sessionHistory.index;
+ this._canGoBack = this.canGoBack();
+ this._canGoForward = this.canGoForward();
+ if (this.active) {
+ this._takeSnapshot();
+ this._installPrevAndNextSnapshots();
+ this._addBoxes();
+ this._lastSwipeDir = "";
+ }
+ }
+ this.updateAnimation(0);
+ },
+
+ /**
+ * Stops the swipe animation.
+ */
+ stopAnimation: function HSA_stopAnimation() {
+ gHistorySwipeAnimation._removeBoxes();
+ },
+
+ /**
+ * Updates the animation between two pages in history.
+ *
+ * @param aVal
+ * A floating point value that represents the progress of the
+ * swipe gesture.
+ */
+ updateAnimation: function HSA_updateAnimation(aVal) {
+ if (!this.isAnimationRunning())
+ return;
+
+ if ((aVal >= 0 && this.isLTR) ||
+ (aVal <= 0 && !this.isLTR)) {
+ if (aVal > 1)
+ aVal = 1; // Cap value to avoid sliding the page further than allowed.
+
+ if (this._canGoBack)
+ this._prevBox.collapsed = false;
+ else
+ this._prevBox.collapsed = true;
+
+ // The current page is pushed to the right (LTR) or left (RTL),
+ // the intention is to go back.
+ // If there is a page to go back to, it should show in the background.
+ this._positionBox(this._curBox, aVal);
+
+ // The forward page should be pushed offscreen all the way to the right.
+ this._positionBox(this._nextBox, 1);
+ }
+ else {
+ if (aVal < -1)
+ aVal = -1; // Cap value to avoid sliding the page further than allowed.
+
+ // The intention is to go forward. If there is a page to go forward to,
+ // it should slide in from the right (LTR) or left (RTL).
+ // Otherwise, the current page should slide to the left (LTR) or
+ // right (RTL) and the backdrop should appear in the background.
+ // For the backdrop to be visible in that case, the previous page needs
+ // to be hidden (if it exists).
+ if (this._canGoForward) {
+ let offset = this.isLTR ? 1 : -1;
+ this._positionBox(this._curBox, 0);
+ this._positionBox(this._nextBox, offset + aVal); // aVal is negative
+ }
+ else {
+ this._prevBox.collapsed = true;
+ this._positionBox(this._curBox, aVal);
+ }
+ }
+ },
+
+ /**
+ * Event handler for events relevant to the history swipe animation.
+ *
+ * @param aEvent
+ * An event to process.
+ */
+ handleEvent: function HSA_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "TabClose":
+ let browser = gBrowser.getBrowserForTab(aEvent.target);
+ this._removeTrackedSnapshot(-1, browser);
+ break;
+ case "pageshow":
+ case "popstate":
+ if (this.isAnimationRunning()) {
+ if (aEvent.target != gBrowser.selectedBrowser.contentDocument)
+ break;
+ this.stopAnimation();
+ }
+ this._historyIndex = gBrowser.webNavigation.sessionHistory.index;
+ break;
+ case "pagehide":
+ if (aEvent.target == gBrowser.selectedBrowser.contentDocument) {
+ // Take a snapshot of a page whenever it's about to be navigated away
+ // from.
+ this._takeSnapshot();
+ }
+ break;
+ }
+ },
+
+ /**
+ * Checks whether the history swipe animation is currently running or not.
+ *
+ * @return true if the animation is currently running, false otherwise.
+ */
+ isAnimationRunning: function HSA_isAnimationRunning() {
+ return !!this._container;
+ },
+
+ /**
+ * Process a swipe event based on the given direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ processSwipeEvent: function HSA_processSwipeEvent(aEvent, aDir) {
+ if (aDir == "RIGHT")
+ this._historyIndex += this.isLTR ? 1 : -1;
+ else if (aDir == "LEFT")
+ this._historyIndex += this.isLTR ? -1 : 1;
+ else
+ return;
+ this._lastSwipeDir = aDir;
+ },
+
+ /**
+ * Checks if there is a page in the browser history to go back to.
+ *
+ * @return true if there is a previous page in history, false otherwise.
+ */
+ canGoBack: function HSA_canGoBack() {
+ if (this.isAnimationRunning())
+ return this._doesIndexExistInHistory(this._historyIndex - 1);
+ return gBrowser.webNavigation.canGoBack;
+ },
+
+ /**
+ * Checks if there is a page in the browser history to go forward to.
+ *
+ * @return true if there is a next page in history, false otherwise.
+ */
+ canGoForward: function HSA_canGoForward() {
+ if (this.isAnimationRunning())
+ return this._doesIndexExistInHistory(this._historyIndex + 1);
+ return gBrowser.webNavigation.canGoForward;
+ },
+
+ /**
+ * Used to notify the history swipe animation that the OS sent a swipe end
+ * event and that we should navigate to the page that the user swiped to, if
+ * any. This will also result in the animation overlay to be torn down.
+ */
+ swipeEndEventReceived: function HSA_swipeEndEventReceived() {
+ if (this._lastSwipeDir != "")
+ this._navigateToHistoryIndex();
+ else
+ this.stopAnimation();
+ },
+
+ /**
+ * Checks whether a particular index exists in the browser history or not.
+ *
+ * @param aIndex
+ * The index to check for availability for in the history.
+ * @return true if the index exists in the browser history, false otherwise.
+ */
+ _doesIndexExistInHistory: function HSA__doesIndexExistInHistory(aIndex) {
+ try {
+ gBrowser.webNavigation.sessionHistory.getEntryAtIndex(aIndex, false);
+ }
+ catch(ex) {
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Navigates to the index in history that is currently being tracked by
+ * |this|.
+ */
+ _navigateToHistoryIndex: function HSA__navigateToHistoryIndex() {
+ if (this._doesIndexExistInHistory(this._historyIndex)) {
+ gBrowser.webNavigation.gotoIndex(this._historyIndex);
+ }
+ },
+
+ /**
+ * Checks to see if history swipe animations are supported by this
+ * platform/configuration.
+ *
+ * return true if supported, false otherwise.
+ */
+ _isSupported: function HSA__isSupported() {
+ return window.matchMedia("(-moz-swipe-animation-enabled)").matches;
+ },
+
+ /**
+ * Handle fast swiping (i.e. a swipe animation is already in
+ * progress when a new one is initiated). This will swap out the snapshots
+ * used in the previous animation with the appropriate new ones.
+ */
+ _handleFastSwiping: function HSA__handleFastSwiping() {
+ this._installCurrentPageSnapshot(null);
+ this._installPrevAndNextSnapshots();
+ },
+
+ /**
+ * Adds the boxes that contain the snapshots used during the swipe animation.
+ */
+ _addBoxes: function HSA__addBoxes() {
+ let browserStack =
+ document.getAnonymousElementByAttribute(gBrowser.getNotificationBox(),
+ "class", "browserStack");
+ this._container = this._createElement("historySwipeAnimationContainer",
+ "stack");
+ browserStack.appendChild(this._container);
+
+ this._prevBox = this._createElement("historySwipeAnimationPreviousPage",
+ "box");
+ this._container.appendChild(this._prevBox);
+
+ this._curBox = this._createElement("historySwipeAnimationCurrentPage",
+ "box");
+ this._container.appendChild(this._curBox);
+
+ this._nextBox = this._createElement("historySwipeAnimationNextPage",
+ "box");
+ this._container.appendChild(this._nextBox);
+
+ this._boxWidth = this._curBox.getBoundingClientRect().width; // cache width
+ },
+
+ /**
+ * Removes the boxes.
+ */
+ _removeBoxes: function HSA__removeBoxes() {
+ this._curBox = null;
+ this._prevBox = null;
+ this._nextBox = null;
+ if (this._container)
+ this._container.parentNode.removeChild(this._container);
+ this._container = null;
+ this._boxWidth = -1;
+ },
+
+ /**
+ * Creates an element with a given identifier and tag name.
+ *
+ * @param aID
+ * An identifier to create the element with.
+ * @param aTagName
+ * The name of the tag to create the element for.
+ * @return the newly created element.
+ */
+ _createElement: function HSA__createElement(aID, aTagName) {
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let element = document.createElementNS(XULNS, aTagName);
+ element.id = aID;
+ return element;
+ },
+
+ /**
+ * Moves a given box to a given X coordinate position.
+ *
+ * @param aBox
+ * The box element to position.
+ * @param aPosition
+ * The position (in X coordinates) to move the box element to.
+ */
+ _positionBox: function HSA__positionBox(aBox, aPosition) {
+ aBox.style.transform = "translateX(" + this._boxWidth * aPosition + "px)";
+ },
+
+ /**
+ * Takes a snapshot of the page the browser is currently on.
+ */
+ _takeSnapshot: function HSA__takeSnapshot() {
+ if ((this._maxSnapshots < 1) ||
+ (gBrowser.webNavigation.sessionHistory.index < 0))
+ return;
+
+ let browser = gBrowser.selectedBrowser;
+ let r = browser.getBoundingClientRect();
+ let canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
+ "canvas");
+ canvas.mozOpaque = true;
+ canvas.width = r.width;
+ canvas.height = r.height;
+ let ctx = canvas.getContext("2d");
+ let zoom = browser.markupDocumentViewer.fullZoom;
+ ctx.scale(zoom, zoom);
+ ctx.drawWindow(browser.contentWindow, 0, 0, r.width, r.height, "white",
+ ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_VIEW |
+ ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
+ ctx.DRAWWINDOW_USE_WIDGET_LAYERS);
+
+ this._installCurrentPageSnapshot(canvas);
+ this._assignSnapshotToCurrentBrowser(canvas);
+ },
+
+ /**
+ * Retrieves the maximum number of snapshots that should be kept in memory.
+ * This limit is a global limit and is valid across all open tabs.
+ */
+ _getMaxSnapshots: function HSA__getMaxSnapshots() {
+ return gPrefService.getIntPref("browser.snapshots.limit");
+ },
+
+ /**
+ * Adds a snapshot to the list and initiates the compression of said snapshot.
+ * Once the compression is completed, it will replace the uncompressed
+ * snapshot in the list.
+ *
+ * @param aCanvas
+ * The snapshot to add to the list and compress.
+ */
+ _assignSnapshotToCurrentBrowser:
+ function HSA__assignSnapshotToCurrentBrowser(aCanvas) {
+ let browser = gBrowser.selectedBrowser;
+ let currIndex = browser.webNavigation.sessionHistory.index;
+
+ this._removeTrackedSnapshot(currIndex, browser);
+ this._addSnapshotRefToArray(currIndex, browser);
+
+ if (!("snapshots" in browser))
+ browser.snapshots = [];
+ let snapshots = browser.snapshots;
+ // Temporarily store the canvas as the compressed snapshot.
+ // This avoids a blank page if the user swipes quickly
+ // between pages before the compression could complete.
+ snapshots[currIndex] = aCanvas;
+
+ // Kick off snapshot compression.
+ aCanvas.toBlob(function(aBlob) {
+ snapshots[currIndex] = aBlob;
+ }, "image/png"
+ );
+ },
+
+ /**
+ * Removes a snapshot identified by the browser and index in the array of
+ * snapshots for that browser, if present. If no snapshot could be identified
+ * the method simply returns without taking any action. If aIndex is negative,
+ * all snapshots for a particular browser will be removed.
+ *
+ * @param aIndex
+ * The index in history of the new snapshot, or negative value if all
+ * snapshots for a browser should be removed.
+ * @param aBrowser
+ * The browser the new snapshot was taken in.
+ */
+ _removeTrackedSnapshot: function HSA__removeTrackedSnapshot(aIndex, aBrowser) {
+ let arr = this._trackedSnapshots;
+ let requiresExactIndexMatch = aIndex >= 0;
+ for (let i = 0; i < arr.length; i++) {
+ if ((arr[i].browser == aBrowser) &&
+ (aIndex < 0 || aIndex == arr[i].index)) {
+ delete aBrowser.snapshots[arr[i].index];
+ arr.splice(i, 1);
+ if (requiresExactIndexMatch)
+ return; // Found and removed the only element.
+ i--; // Make sure to revisit the index that we just removed an
+ // element at.
+ }
+ }
+ },
+
+ /**
+ * Adds a new snapshot reference for a given index and browser to the array
+ * of references to tracked snapshots.
+ *
+ * @param aIndex
+ * The index in history of the new snapshot.
+ * @param aBrowser
+ * The browser the new snapshot was taken in.
+ */
+ _addSnapshotRefToArray:
+ function HSA__addSnapshotRefToArray(aIndex, aBrowser) {
+ let id = { index: aIndex,
+ browser: aBrowser };
+ let arr = this._trackedSnapshots;
+ arr.unshift(id);
+
+ while (arr.length > this._maxSnapshots) {
+ let lastElem = arr[arr.length - 1];
+ delete lastElem.browser.snapshots[lastElem.index];
+ arr.splice(-1, 1);
+ }
+ },
+
+ /**
+ * Converts a compressed blob to an Image object. In some situations
+ * (especially during fast swiping) aBlob may still be a canvas, not a
+ * compressed blob. In this case, we simply return the canvas.
+ *
+ * @param aBlob
+ * The compressed blob to convert, or a canvas if a blob compression
+ * couldn't complete before this method was called.
+ * @return A new Image object representing the converted blob.
+ */
+ _convertToImg: function HSA__convertToImg(aBlob) {
+ if (!aBlob)
+ return null;
+
+ // Return aBlob if it's still a canvas and not a compressed blob yet.
+ if (aBlob instanceof HTMLCanvasElement)
+ return aBlob;
+
+ let img = new Image();
+ let url = URL.createObjectURL(aBlob);
+ img.onload = function() {
+ URL.revokeObjectURL(url);
+ };
+ img.src = url;
+ return img;
+ },
+
+ /**
+ * Sets the snapshot of the current page to the snapshot passed as parameter,
+ * or to the one previously stored for the current index in history if the
+ * parameter is null.
+ *
+ * @param aCanvas
+ * The snapshot to set the current page to. If this parameter is null,
+ * the previously stored snapshot for this index (if any) will be used.
+ */
+ _installCurrentPageSnapshot:
+ function HSA__installCurrentPageSnapshot(aCanvas) {
+ let currSnapshot = aCanvas;
+ if (!currSnapshot) {
+ let snapshots = gBrowser.selectedBrowser.snapshots || {};
+ let currIndex = this._historyIndex;
+ if (currIndex in snapshots)
+ currSnapshot = this._convertToImg(snapshots[currIndex]);
+ }
+ document.mozSetImageElement("historySwipeAnimationCurrentPageSnapshot",
+ currSnapshot);
+ },
+
+ /**
+ * Sets the snapshots of the previous and next pages to the snapshots
+ * previously stored for their respective indeces.
+ */
+ _installPrevAndNextSnapshots:
+ function HSA__installPrevAndNextSnapshots() {
+ let snapshots = gBrowser.selectedBrowser.snapshots || [];
+ let currIndex = this._historyIndex;
+ let prevIndex = currIndex - 1;
+ let prevSnapshot = null;
+ if (prevIndex in snapshots)
+ prevSnapshot = this._convertToImg(snapshots[prevIndex]);
+ document.mozSetImageElement("historySwipeAnimationPreviousPageSnapshot",
+ prevSnapshot);
+
+ let nextIndex = currIndex + 1;
+ let nextSnapshot = null;
+ if (nextIndex in snapshots)
+ nextSnapshot = this._convertToImg(snapshots[nextIndex]);
+ document.mozSetImageElement("historySwipeAnimationNextPageSnapshot",
+ nextSnapshot);
+ },
+};
diff --git a/application/palemoon/base/content/browser-menubar.inc b/application/palemoon/base/content/browser-menubar.inc
new file mode 100644
index 000000000..f818f5149
--- /dev/null
+++ b/application/palemoon/base/content/browser-menubar.inc
@@ -0,0 +1,595 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ <menubar id="main-menubar"
+ onpopupshowing="if (event.target.parentNode.parentNode == this &amp;&amp;
+ !('@mozilla.org/widget/nativemenuservice;1' in Cc))
+ this.setAttribute('openedwithkey',
+ event.target.parentNode.openedWithKey);"
+ style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
+ <menu id="file-menu" label="&fileMenu.label;"
+ accesskey="&fileMenu.accesskey;">
+ <menupopup id="menu_FilePopup">
+ <menuitem id="menu_newNavigatorTab"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ key="key_newNavigatorTab"
+ accesskey="&tabCmd.accesskey;"/>
+ <menuitem id="menu_newNavigator"
+ label="&newNavigatorCmd.label;"
+ accesskey="&newNavigatorCmd.accesskey;"
+ key="key_newNavigator"
+ command="cmd_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"
+ label="&newPrivateWindow.label;"
+ accesskey="&newPrivateWindow.accesskey;"
+ command="Tools:PrivateBrowsing"
+ key="key_privatebrowsing"/>
+ <menuitem id="menu_openLocation"
+ class="show-only-for-keyboard"
+ label="&openLocationCmd.label;"
+ command="Browser:OpenLocation"
+ key="focusURLBar"
+ accesskey="&openLocationCmd.accesskey;"/>
+ <menuitem id="menu_openFile"
+ label="&openFileCmd.label;"
+ command="Browser:OpenFile"
+ key="openFileKb"
+ accesskey="&openFileCmd.accesskey;"/>
+ <menuitem id="menu_close"
+ class="show-only-for-keyboard"
+ label="&closeCmd.label;"
+ key="key_close"
+ accesskey="&closeCmd.accesskey;"
+ command="cmd_close"/>
+ <menuitem id="menu_closeWindow"
+ class="show-only-for-keyboard"
+ hidden="true"
+ command="cmd_closeWindow"
+ key="key_closeWindow"
+ label="&closeWindow.label;"
+ accesskey="&closeWindow.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="menu_savePage"
+ label="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey;"
+ key="key_savePage"
+ command="Browser:SavePage"/>
+ <menuitem id="menu_sendLink"
+ label="&emailPageCmd.label;"
+ accesskey="&emailPageCmd.accesskey;"
+ command="Browser:SendLink"/>
+ <menuseparator/>
+ <menuitem id="menu_printSetup"
+ label="&printSetupCmd.label;"
+ accesskey="&printSetupCmd.accesskey;"
+ command="cmd_pageSetup"/>
+#ifndef XP_MACOSX
+ <menuitem id="menu_printPreview"
+ label="&printPreviewCmd.label;"
+ accesskey="&printPreviewCmd.accesskey;"
+ command="cmd_printPreview"/>
+#endif
+ <menuitem id="menu_print"
+ label="&printCmd.label;"
+ accesskey="&printCmd.accesskey;"
+ key="printKb"
+ command="cmd_print"/>
+ <menuseparator/>
+ <menuitem id="goOfflineMenuitem"
+ label="&goOfflineCmd.label;"
+ accesskey="&goOfflineCmd.accesskey;"
+ type="checkbox"
+ observes="workOfflineMenuitemState"
+ oncommand="BrowserOffline.toggleOfflineStatus();"/>
+ <menuitem id="menu_restart"
+ label="&appMenuRestart.label;"
+ accesskey="&appMenuRestart.accesskey;"
+ command="cmd_restartApplication"/>
+ <menuitem id="menu_FileQuitItem"
+#ifdef XP_WIN
+ label="&quitApplicationCmdWin.label;"
+ accesskey="&quitApplicationCmdWin.accesskey;"
+#else
+#ifdef XP_MACOSX
+ label="&quitApplicationCmdMac.label;"
+#else
+ label="&quitApplicationCmd.label;"
+ accesskey="&quitApplicationCmd.accesskey;"
+#endif
+#ifdef XP_UNIX
+ key="key_quitApplication"
+#endif
+#endif
+ command="cmd_quitApplication"/>
+ </menupopup>
+ </menu>
+
+ <menu id="edit-menu" label="&editMenu.label;"
+ accesskey="&editMenu.accesskey;">
+ <menupopup id="menu_EditPopup"
+ onpopupshowing="updateEditUIVisibility()"
+ onpopuphidden="updateEditUIVisibility()">
+ <menuitem id="menu_undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ accesskey="&undoCmd.accesskey;"
+ command="cmd_undo"/>
+ <menuitem id="menu_redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ accesskey="&redoCmd.accesskey;"
+ command="cmd_redo"/>
+ <menuseparator/>
+ <menuitem id="menu_cut"
+ label="&cutCmd.label;"
+ key="key_cut"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="menu_copy"
+ label="&copyCmd.label;"
+ key="key_copy"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="menu_paste"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuitem id="menu_delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"
+ command="cmd_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem id="menu_find"
+ label="&findOnCmd.label;"
+ accesskey="&findOnCmd.accesskey;"
+ key="key_find"
+ command="cmd_find"/>
+ <menuitem id="menu_findAgain"
+ class="show-only-for-keyboard"
+ label="&findAgainCmd.label;"
+ accesskey="&findAgainCmd.accesskey;"
+ key="key_findAgain"
+ command="cmd_findAgain"/>
+ <menuseparator hidden="true" id="textfieldDirection-separator"/>
+ <menuitem id="textfieldDirection-swap"
+ command="cmd_switchTextDirection"
+ key="key_switchTextDirection"
+ label="&bidiSwitchTextDirectionItem.label;"
+ accesskey="&bidiSwitchTextDirectionItem.accesskey;"
+ hidden="true"/>
+ </menupopup>
+ </menu>
+
+ <menu id="view-menu" label="&viewMenu.label;"
+ accesskey="&viewMenu.accesskey;">
+ <menupopup id="menu_viewPopup"
+ onpopupshowing="updateCharacterEncodingMenuState();">
+ <menu id="viewToolbarsMenu"
+ label="&viewToolbarsMenu.label;"
+ accesskey="&viewToolbarsMenu.accesskey;">
+ <menupopup onpopupshowing="onViewToolbarsPopupShowing(event);">
+ <menuseparator/>
+ <menuitem id="menu_tabsOnTop"
+ command="cmd_ToggleTabsOnTop"
+ type="checkbox"
+ label="&viewTabsOnTop.label;"
+ accesskey="&viewTabsOnTop.accesskey;"/>
+ <menuitem id="menu_customizeToolbars"
+ label="&viewCustomizeToolbar.label;"
+ accesskey="&viewCustomizeToolbar.accesskey;"
+ command="cmd_CustomizeToolbars"/>
+ </menupopup>
+ </menu>
+ <menu id="viewSidebarMenuMenu"
+ label="&viewSidebarMenu.label;"
+ accesskey="&viewSidebarMenu.accesskey;">
+ <menupopup id="viewSidebarMenu">
+ <menuitem id="menu_bookmarksSidebar"
+ key="viewBookmarksSidebarKb"
+ observes="viewBookmarksSidebar"
+ label="&bookmarksButton.label;"/>
+ <menuitem id="menu_historySidebar"
+ key="key_gotoHistory"
+ observes="viewHistorySidebar"
+ label="&historyButton.label;"/>
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ <menuitem id="menu_stop"
+ class="show-only-for-keyboard"
+ label="&stopCmd.label;"
+ accesskey="&stopCmd.accesskey;"
+ command="Browser:Stop"
+#ifdef XP_MACOSX
+ key="key_stop_mac"/>
+#else
+ key="key_stop"/>
+#endif
+ <menuitem id="menu_reload"
+ class="show-only-for-keyboard"
+ label="&reloadCmd.label;"
+ accesskey="&reloadCmd.accesskey;"
+ key="key_reload"
+ command="Browser:ReloadOrDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuseparator class="show-only-for-keyboard"/>
+ <menu id="viewFullZoomMenu" label="&fullZoom.label;"
+ accesskey="&fullZoom.accesskey;"
+ onpopupshowing="FullZoom.updateMenu();">
+ <menupopup>
+ <menuitem id="menu_zoomEnlarge"
+ key="key_fullZoomEnlarge"
+ label="&fullZoomEnlargeCmd.label;"
+ accesskey="&fullZoomEnlargeCmd.accesskey;"
+ command="cmd_fullZoomEnlarge"/>
+ <menuitem id="menu_zoomReduce"
+ key="key_fullZoomReduce"
+ label="&fullZoomReduceCmd.label;"
+ accesskey="&fullZoomReduceCmd.accesskey;"
+ command="cmd_fullZoomReduce"/>
+ <menuseparator/>
+ <menuitem id="menu_zoomReset"
+ key="key_fullZoomReset"
+ label="&fullZoomResetCmd.label;"
+ accesskey="&fullZoomResetCmd.accesskey;"
+ command="cmd_fullZoomReset"/>
+ <menuseparator/>
+ <menuitem id="toggle_zoom"
+ label="&fullZoomToggleCmd.label;"
+ accesskey="&fullZoomToggleCmd.accesskey;"
+ type="checkbox"
+ command="cmd_fullZoomToggle"
+ checked="false"/>
+ </menupopup>
+ </menu>
+ <menu id="pageStyleMenu" label="&pageStyleMenu.label;"
+ accesskey="&pageStyleMenu.accesskey;" observes="isImage">
+ <menupopup onpopupshowing="gPageStyleMenu.fillPopup(this);">
+ <menuitem id="menu_pageStyleNoStyle"
+ label="&pageStyleNoStyle.label;"
+ accesskey="&pageStyleNoStyle.accesskey;"
+ oncommand="gPageStyleMenu.disableStyle();"
+ type="radio"/>
+ <menuitem id="menu_pageStylePersistentOnly"
+ label="&pageStylePersistentOnly.label;"
+ accesskey="&pageStylePersistentOnly.accesskey;"
+ oncommand="gPageStyleMenu.switchStyleSheet('');"
+ type="radio"
+ checked="true"/>
+ <menuseparator/>
+ </menupopup>
+ </menu>
+#include browser-charsetmenu.inc
+ <menuseparator/>
+#ifdef XP_MACOSX
+ <menuitem id="enterFullScreenItem"
+ accesskey="&enterFullScreenCmd.accesskey;"
+ label="&enterFullScreenCmd.label;"
+ key="key_fullScreen">
+ <observes element="View:FullScreen" attribute="oncommand"/>
+ <observes element="View:FullScreen" attribute="disabled"/>
+ </menuitem>
+ <menuitem id="exitFullScreenItem"
+ accesskey="&exitFullScreenCmd.accesskey;"
+ label="&exitFullScreenCmd.label;"
+ key="key_fullScreen"
+ hidden="true">
+ <observes element="View:FullScreen" attribute="oncommand"/>
+ <observes element="View:FullScreen" attribute="disabled"/>
+ </menuitem>
+#else
+ <menuitem id="fullScreenItem"
+ accesskey="&fullScreenCmd.accesskey;"
+ label="&fullScreenCmd.label;"
+ key="key_fullScreen"
+ type="checkbox"
+ observes="View:FullScreen"/>
+#endif
+ <menuitem id="menu_showAllTabs"
+ hidden="true"
+ accesskey="&showAllTabsCmd.accesskey;"
+ label="&showAllTabsCmd.label;"
+ command="Browser:ShowAllTabs"
+ key="key_showAllTabs"/>
+ <menuseparator hidden="true" id="documentDirection-separator"/>
+ <menuitem id="documentDirection-swap"
+ hidden="true"
+ label="&bidiSwitchPageDirectionItem.label;"
+ accesskey="&bidiSwitchPageDirectionItem.accesskey;"
+ oncommand="SwitchDocumentDirection(window.content)"/>
+ </menupopup>
+ </menu>
+
+ <menu id="history-menu"
+ label="&historyMenu.label;"
+ accesskey="&historyMenu.accesskey;">
+ <menupopup id="goPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ oncommand="this.parentNode._placesView._onCommand(event);"
+ onclick="checkForMiddleClick(this, event);"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new HistoryMenu(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="historyMenuBack"
+ class="show-only-for-keyboard"
+ label="&backCmd.label;"
+#ifdef XP_MACOSX
+ key="goBackKb2"
+#else
+ key="goBackKb"
+#endif
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="historyMenuForward"
+ class="show-only-for-keyboard"
+ label="&forwardCmd.label;"
+#ifdef XP_MACOSX
+ key="goForwardKb2"
+#else
+ key="goForwardKb"
+#endif
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="historyMenuHome"
+ class="show-only-for-keyboard"
+ label="&historyHomeCmd.label;"
+ oncommand="BrowserGoHome(event);"
+ onclick="checkForMiddleClick(this, event);"
+ key="goHome"/>
+ <menuseparator id="historyMenuHomeSeparator"
+ class="show-only-for-keyboard"/>
+ <menuitem id="menu_showAllHistory"
+ label="&showAllHistoryCmd2.label;"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+ key="showAllHistoryKb"
+#endif
+ command="Browser:ShowAllHistory"/>
+ <menuitem id="sanitizeItem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&clearRecentHistory.label;"
+ key="key_sanitize"
+ command="Tools:Sanitize"/>
+ <menuseparator id="sanitizeSeparator"/>
+#ifdef MOZ_SERVICES_SYNC
+ <menuitem id="sync-tabs-menuitem"
+ class="syncTabsMenuItem"
+ label="&syncTabsMenu2.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ disabled="true"/>
+#endif
+ <menuitem id="historyRestoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ command="Browser:RestoreLastSession"/>
+ <menu id="historyUndoMenu"
+ class="recentlyClosedTabsMenu"
+ label="&historyUndoMenu.label;"
+ disabled="true">
+ <menupopup id="historyUndoPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoSubmenu();"/>
+ </menu>
+ <menu id="historyUndoWindowMenu"
+ class="recentlyClosedWindowsMenu"
+ label="&historyUndoWindowMenu.label;"
+ disabled="true">
+ <menupopup id="historyUndoWindowPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoWindowSubmenu();"/>
+ </menu>
+ <menuseparator id="startHistorySeparator"
+ class="hide-if-empty-places-result"/>
+ </menupopup>
+ </menu>
+
+ <menu id="bookmarksMenu"
+ label="&bookmarksMenu.label;"
+ accesskey="&bookmarksMenu.accesskey;"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);">
+ <menupopup id="bookmarksMenuPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="PlacesCommandHook.updateBookmarkAllTabsCommand();
+ if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem id="bookmarksShowAll"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&organizeBookmarks.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ <menuseparator id="organizeBookmarksSeparator"/>
+ <menuitem id="menu_bookmarkThisPage"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&bookmarkThisPageCmd.label;"
+ command="Browser:AddBookmarkAs"
+ key="addBookmarkAsKb"/>
+ <menuitem id="subscribeToPageMenuitem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&subscribeToPageMenuitem.label;"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="singleFeedMenuitemState"/>
+ <menu id="subscribeToPageMenupopup"
+#ifndef XP_MACOSX
+ class="menu-iconic"
+#endif
+ label="&subscribeToPageMenupopup.label;"
+ observes="multipleFeedsMenuState">
+ <menupopup id="subscribeToPageSubmenuMenupopup"
+ onpopupshowing="return FeedHandler.buildFeedList(event.target);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuitem id="menu_bookmarkAllTabs"
+ label="&addCurPagesCmd.label;"
+ class="show-only-for-keyboard"
+ command="Browser:BookmarkAllTabs"
+ key="bookmarkAllTabsKb"/>
+ <menuseparator id="bookmarksToolbarSeparator"/>
+ <menu id="bookmarksToolbarFolderMenu"
+ class="menu-iconic bookmark-item"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="bookmarksToolbarFolderPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menuseparator id="bookmarksMenuItemsSeparator"/>
+ <!-- Bookmarks menu items -->
+ <menuseparator builder="end"
+ class="hide-if-empty-places-result"/>
+ <menuitem id="menu_unsortedBookmarks"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&unsortedBookmarksCmd.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
+ </menupopup>
+ </menu>
+
+ <menu id="tools-menu"
+ label="&toolsMenu.label;"
+ accesskey="&toolsMenu.accesskey;">
+ <menupopup id="menu_ToolsPopup"
+#ifdef MOZ_SERVICES_SYNC
+ onpopupshowing="gSyncUI.updateUI();"
+#endif
+ >
+ <menuitem id="menu_search"
+ class="show-only-for-keyboard"
+ label="&search.label;"
+ accesskey="&search.accesskey;"
+ key="key_search"
+ command="Tools:Search"/>
+ <menuseparator id="browserToolsSeparator"
+ class="show-only-for-keyboard"/>
+ <menuitem id="menu_openDownloads"
+ label="&downloads.label;"
+ accesskey="&downloads.accesskey;"
+ key="key_openDownloads"
+ command="Tools:Downloads"/>
+ <menuitem id="menu_openAddons"
+ label="&addons.label;"
+ accesskey="&addons.accesskey;"
+ key="key_openAddons"
+ command="Tools:Addons"/>
+ <menuitem id="menu_openPermissions"
+ label="&permissions.label;"
+ command="Tools:Permissions"
+ accesskey="&permissions.accesskey;"/>
+#ifdef MOZ_SERVICES_SYNC
+ <!-- only one of sync-setup or sync-menu will be showing at once -->
+ <menuitem id="sync-setup"
+ label="&syncSetup.label;"
+ accesskey="&syncSetup.accesskey;"
+ observes="sync-setup-state"
+ oncommand="gSyncUI.openSetup()"/>
+ <menuitem id="sync-syncnowitem"
+ label="&syncSyncNowItem.label;"
+ accesskey="&syncSyncNowItem.accesskey;"
+ observes="sync-syncnow-state"
+ oncommand="gSyncUI.doSync(event);"/>
+#endif
+ <menuseparator id="devToolsSeparator"/>
+ <menu id="webDeveloperMenu"
+ label="&webDeveloperMenu.label;"
+ accesskey="&webDeveloperMenu.accesskey;">
+ <menupopup id="menuWebDeveloperPopup">
+#ifdef MOZ_DEVTOOLS
+ <menuitem id="menu_devToolbox"
+ observes="devtoolsMenuBroadcaster_DevToolbox"
+ accesskey="&devToolboxMenuItem.accesskey;"/>
+ <menuseparator id="menu_devtools_separator"/>
+ <menuitem id="menu_devToolbar"
+ observes="devtoolsMenuBroadcaster_DevToolbar"
+ accesskey="&devToolbarMenu.accesskey;"/>
+ <menuitem id="menu_chromeDebugger"
+ observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
+ <menuitem id="menu_browserConsole"
+ observes="devtoolsMenuBroadcaster_BrowserConsole"
+ accesskey="&browserConsoleCmd.accesskey;"/>
+ <menuitem id="menu_responsiveUI"
+ observes="devtoolsMenuBroadcaster_ResponsiveUI"
+ accesskey="&responsiveDesignTool.accesskey;"/>
+ <menuitem id="menu_eyedropper"
+ observes="devtoolsMenuBroadcaster_Eyedropper"
+ accesskey="&eyedropper.accesskey;"/>
+ <menuitem id="menu_scratchpad"
+ observes="devtoolsMenuBroadcaster_Scratchpad"
+ accesskey="&scratchpad.accesskey;"/>
+#endif
+ <menuitem id="menu_pageSource"
+ observes="devtoolsMenuBroadcaster_PageSource"
+ accesskey="&pageSourceCmd.accesskey;"/>
+ <menuitem id="javascriptConsole"
+ observes="devtoolsMenuBroadcaster_ErrorConsole"
+ accesskey="&errorConsoleCmd.accesskey;"/>
+#ifdef MOZ_DEVTOOLS
+ <menuitem id="menu_devtools_connect"
+ observes="devtoolsMenuBroadcaster_connect"/>
+#endif
+ <menuseparator id="devToolsEndSeparator"/>
+ <menuitem id="getMoreDevtools"
+ observes="devtoolsMenuBroadcaster_GetMoreTools"
+ accesskey="&getMoreDevtoolsCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_pageInfo"
+ accesskey="&pageInfoCmd.accesskey;"
+ label="&pageInfoCmd.label;"
+#ifndef XP_WIN
+ key="key_viewInfo"
+#endif
+ command="View:PageInfo"/>
+ <menuseparator id="prefSep"/>
+ <menuitem id="menu_preferences"
+ label="&preferencesCmd2.label;"
+ accesskey="&preferencesCmd2.accesskey;"
+ oncommand="openPreferences();"/>
+ </menupopup>
+ </menu>
+
+#ifdef XP_MACOSX
+ <menu id="windowMenu" />
+#endif
+ <menu id="helpMenu" />
+ </menubar>
diff --git a/application/palemoon/base/content/browser-menudragging.js b/application/palemoon/base/content/browser-menudragging.js
new file mode 100644
index 000000000..cf26b2ba4
--- /dev/null
+++ b/application/palemoon/base/content/browser-menudragging.js
@@ -0,0 +1,362 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+// Based on original code by alice0775 https://github.com/alice0775
+
+
+"use strict";
+var browserMenuDragging = {
+ //-- config --
+ STAY_OPEN_ONDRAGEXIT: false,
+ DEBUG: false,
+ //-- config --
+
+ menupopup: ['bookmarksMenuPopup',
+ 'PlacesToolbar',
+ 'BMB_bookmarksPopup',
+ 'appmenu_bookmarksPopup',
+ 'BookmarksMenuToolButtonPopup',
+ 'UnsortedBookmarksFolderToolButtonPopup',
+ 'bookmarksMenuPopup-context'],
+ timer:[],
+ count:[],
+
+
+ init: function(){
+ window.removeEventListener('load', this, false);
+ window.addEventListener('unload', this, false);
+ this.addPrefListener(this.PrefListener);
+
+ window.addEventListener('aftercustomization', this, false);
+
+ this.initPref();
+ this.delayedStartup();
+ },
+
+ uninit: function(){
+ window.removeEventListener('unload', this, false);
+ this.removePrefListener(this.PrefListener);
+
+ window.removeEventListener('aftercustomization', this, false);
+
+ for (var i = 0; i < this.menupopup.length; i++){
+ var menupopup = document.getElementById(this.menupopup[i]);
+ if (menupopup){
+ menupopup.removeEventListener('popupshowing', this, false);
+ menupopup.removeEventListener('popuphiding', this, false);
+ }
+ }
+
+ },
+
+ initPref: function(){
+ this.STAY_OPEN_ONDRAGEXIT =
+ this.getPref('browser.menu.dragging.stayOpen',
+ 'bool', false);
+ this.DEBUG =
+ this.getPref('browser.menu.dragging.debug',
+ 'bool', false);
+ },
+
+ //delayed startup
+ delayedStartup: function(){
+ //wait until construction of bookmarksBarContent is completed.
+ for (var i = 0; i < this.menupopup.length; i++){
+ this.count[i] = 0;
+ this.timer[i] = setInterval(function(self, i){
+ if(++self.count[i] > 50 || document.getElementById(self.menupopup[i])){
+ clearInterval(self.timer[i]);
+ var menupopup = document.getElementById(self.menupopup[i]);
+ if (menupopup) {
+ menupopup.addEventListener('popupshowing', self, false);
+ menupopup.addEventListener('popuphiding', self, false);
+ }
+ }
+ }, 250, this, i);
+ }
+ },
+
+ handleEvent: function(event){
+ switch (event.type) {
+ case 'popupshowing':
+ this.popupshowing(event);
+ break;
+ case 'popuphiding':
+ this.popuphiding(event);
+ break;
+ case 'aftercustomization':
+ setTimeout(function(self){self.delayedStartup(self);}, 0, this);
+ break;
+ case 'load':
+ this.init();
+ break;
+ case 'unload':
+ this.uninit();
+ break;
+ }
+ },
+
+ popuphiding: function(event) {
+ var menupopup = event.originalTarget;
+ menupopup.parentNode.parentNode.openNode = null;
+
+ if (menupopup.parentNode.localName == 'toolbarbutton') {
+ // Fix for Bug 225434 - dragging bookmark from personal toolbar and releasing
+ // (on same bookmark or elsewhere) or clicking on bookmark menu then cancelling
+ // leaves button depressed/sunken when hovered
+ menupopup.parentNode.parentNode._openedMenuButton = null;
+
+ if (!PlacesControllerDragHelper.getSession())
+ // Clear the dragover attribute if present, if we are dragging into a
+ // folder in the hierachy of current opened popup we don't clear
+ // this attribute on clearOverFolder. See Notify for closeTimer.
+ if (menupopup.parentNode.hasAttribute('dragover'))
+ menupopup.parentNode.removeAttribute('dragover');
+ }
+ },
+
+ popupshowing: function(event) {
+ var menupopup = event.originalTarget;
+ browserMenuDragging.debug("popupshowing ===============\n" + menupopup.parentNode.getAttribute('label'));
+
+ var parentPopup = menupopup.parentNode.parentNode;
+
+ if (!!parentPopup.openNode){
+ try {
+ parentPopup.openNode.hidePopup();
+ } catch(e){}
+ }
+ parentPopup.openNode = menupopup;
+
+ menupopup.onDragStart = function (event) {
+ // Bug 555474 - While bookmark is dragged, the tooltip should not appear
+ browserMenuDragging.hideTooltip();
+ }
+
+ menupopup.onDragOver = function (event) {
+ // Bug 555474 - While bookmark is dragged, the tooltip should not appear
+ browserMenuDragging.hideTooltip();
+
+ var target = event.originalTarget;
+ while (target) {
+ if (/menupopup/.test(target.localName))
+ break;
+ target = target.parentNode;
+ }
+ if (this != target)
+ return;
+ event.stopPropagation();
+ browserMenuDragging.debug("onDragOver " + "\n" + this.parentNode.getAttribute('label'));
+
+ PlacesControllerDragHelper.currentDropTarget = event.target;
+ let dt = event.dataTransfer;
+
+ let dropPoint = this._getDropPoint(event);
+
+ if (!dropPoint || !dropPoint.ip ||
+ !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
+ this._indicatorBar.hidden = true;
+ event.stopPropagation();
+ return;
+ }
+
+ // Mark this popup as being dragged over.
+ this.setAttribute('dragover', 'true');
+
+ if (dropPoint.folderElt) {
+ // We are dragging over a folder.
+ // _overFolder should take the care of opening it on a timer.
+ if (this._overFolder.elt &&
+ this._overFolder.elt != dropPoint.folderElt) {
+ }
+ if (!this._overFolder.elt) {
+ this._overFolder.elt = dropPoint.folderElt;
+ // Create the timer to open this folder.
+ this._overFolder.openTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+ }
+ else {
+ // We are not dragging over a folder.
+ }
+
+ // Autoscroll the popup strip if we drag over the scroll buttons.
+ let anonid = event.originalTarget.getAttribute('anonid');
+ let scrollDir = anonid == 'scrollbutton-up' ? -1 :
+ anonid == 'scrollbutton-down' ? 1 : 0;
+ if (scrollDir != 0) {
+ this._scrollBox.scrollByIndex(scrollDir, false);
+ }
+
+ // Check if we should hide the drop indicator for this target.
+ if (dropPoint.folderElt || this._hideDropIndicator(event)) {
+ this._indicatorBar.hidden = true;
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ // We should display the drop indicator relative to the arrowscrollbox.
+ let sbo = this._scrollBox.scrollBoxObject;
+ let newMarginTop = 0;
+ if (scrollDir == 0) {
+ let elt = this.firstChild;
+ while (elt && event.screenY > elt.boxObject.screenY +
+ elt.boxObject.height / 2)
+ elt = elt.nextSibling;
+ newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY :
+ sbo.height;
+ }
+ else if (scrollDir == 1)
+ newMarginTop = sbo.height;
+
+ // Set the new marginTop based on arrowscrollbox.
+ newMarginTop += sbo.y - this._scrollBox.boxObject.y;
+ this._indicatorBar.firstChild.style.marginTop = newMarginTop + 'px';
+ this._indicatorBar.hidden = false;
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ menupopup.onDragExit = function (event) {
+ var target = event.originalTarget;
+ while (target) {
+ if (/menupopup/.test(target.localName))
+ break;
+ target = target.parentNode;
+ }
+ if (this != target)
+ return;
+ event.stopPropagation();
+ browserMenuDragging.debug("onDragExit " + browserMenuDragging.STAY_OPEN_ONDRAGEXIT);
+
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this.removeAttribute('dragover');
+
+ // If we have not moved to a valid new target clear the drop indicator
+ // this happens when moving out of the popup.
+ target = event.relatedTarget;
+ if (!target)
+ this._indicatorBar.hidden = true;
+
+ // Close any folder being hovered over
+ if (this._overFolder.elt) {
+ this._overFolder.closeTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ // The auto-opened attribute is set when this folder was automatically
+ // opened after the user dragged over it. If this attribute is set,
+ // auto-close the folder on drag exit.
+ // We should also try to close this popup if the drag has started
+ // from here, the timer will check if we are dragging over a child.
+ if (this.hasAttribute('autoopened') ||
+ !browserMenuDragging.STAY_OPEN_ONDRAGEXIT &&
+ this.hasAttribute('dragstart')) {
+ this._overFolder.closeMenuTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ event.stopPropagation();
+ }
+
+ menupopup.addEventListener('dragstart', menupopup.onDragStart, true);
+ menupopup.addEventListener('dragover', menupopup.onDragOver, true);
+ menupopup.addEventListener('dragleave', menupopup.onDragExit, true);
+ },
+
+ hideTooltip: function() {
+ ['bhTooltip', 'btTooltip2'].forEach(function(id) {
+ var tooltip = document.getElementById(id);
+ if (tooltip)
+ tooltip.hidePopup();
+ });
+ },
+
+ get getVer(){
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ var info = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
+ var ver = parseInt(info.version.substr(0,3) * 10,10) / 10;
+ return ver;
+ },
+
+ debug: function(aMsg){
+ if (!browserMenuDragging.DEBUG)
+ return;
+ Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .logStringMessage(aMsg);
+ },
+
+ getPref: function(aPrefString, aPrefType, aDefault){
+ var xpPref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ try{
+ switch (aPrefType){
+ case 'complex':
+ return xpPref.getComplexValue(aPrefString, Components.interfaces.nsILocalFile); break;
+ case 'str':
+ return xpPref.getCharPref(aPrefString).toString(); break;
+ case 'int':
+ return xpPref.getIntPref(aPrefString); break;
+ case 'bool':
+ default:
+ return xpPref.getBoolPref(aPrefString); break;
+ }
+ }catch(e){
+ }
+ return aDefault;
+ },
+
+ setPref: function(aPrefString, aPrefType, aValue){
+ var xpPref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ try{
+ switch (aPrefType){
+ case 'complex':
+ return xpPref.setComplexValue(aPrefString, Components.interfaces.nsILocalFile, aValue); break;
+ case 'str':
+ return xpPref.setCharPref(aPrefString, aValue); break;
+ case 'int':
+ aValue = parseInt(aValue);
+ return xpPref.setIntPref(aPrefString, aValue); break;
+ case 'bool':
+ default:
+ return xpPref.setBoolPref(aPrefString, aValue); break;
+ }
+ }catch(e){
+ }
+ return null;
+ },
+
+ addPrefListener: function(aObserver) {
+ try {
+ var pbi = Components.classes["@mozilla.org/preferences;1"].
+ getService(Components.interfaces.nsIPrefBranch2);
+ pbi.addObserver(aObserver.domain, aObserver, false);
+ } catch(e) {}
+ },
+
+ removePrefListener: function(aObserver) {
+ try {
+ var pbi = Components.classes["@mozilla.org/preferences;1"].
+ getService(Components.interfaces.nsIPrefBranch2);
+ pbi.removeObserver(aObserver.domain, aObserver);
+ } catch(e) {}
+ },
+
+ PrefListener:{
+ domain : 'browser.menu.dragging.stayOpen',
+
+ observe : function(aSubject, aTopic, aPrefstring) {
+ if (aTopic == 'nsPref:changed') {
+ browserMenuDragging.initPref();
+ }
+ }
+ }
+}
+
+window.addEventListener('load', browserMenuDragging, false); \ No newline at end of file
diff --git a/application/palemoon/base/content/browser-menudragging.xul b/application/palemoon/base/content/browser-menudragging.xul
new file mode 100644
index 000000000..f5cabe5f6
--- /dev/null
+++ b/application/palemoon/base/content/browser-menudragging.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+
+
+<overlay id="menuDraggingOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="main-window">
+
+ <script type="application/x-javascript" src="chrome://browser/content/browser-menudragging.js" />
+
+</window>
+
+</overlay>
diff --git a/application/palemoon/base/content/browser-places.js b/application/palemoon/base/content/browser-places.js
new file mode 100644
index 000000000..cf9c28597
--- /dev/null
+++ b/application/palemoon/base/content/browser-places.js
@@ -0,0 +1,1303 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+////////////////////////////////////////////////////////////////////////////////
+//// StarUI
+
+var StarUI = {
+ _itemId: -1,
+ uri: null,
+ _batching: false,
+
+ _element: function(aID) {
+ return document.getElementById(aID);
+ },
+
+ // Edit-bookmark panel
+ get panel() {
+ delete this.panel;
+ var element = this._element("editBookmarkPanel");
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ element.hidden = false;
+ element.addEventListener("popuphidden", this, false);
+ element.addEventListener("keypress", this, false);
+ return this.panel = element;
+ },
+
+ // Array of command elements to disable when the panel is opened.
+ get _blockedCommands() {
+ delete this._blockedCommands;
+ return this._blockedCommands =
+ ["cmd_close", "cmd_closeWindow"].map(function (id) this._element(id), this);
+ },
+
+ _blockCommands: function SU__blockCommands() {
+ this._blockedCommands.forEach(function (elt) {
+ // make sure not to permanently disable this item (see bug 409155)
+ if (elt.hasAttribute("wasDisabled"))
+ return;
+ if (elt.getAttribute("disabled") == "true") {
+ elt.setAttribute("wasDisabled", "true");
+ } else {
+ elt.setAttribute("wasDisabled", "false");
+ elt.setAttribute("disabled", "true");
+ }
+ });
+ },
+
+ _restoreCommandsState: function SU__restoreCommandsState() {
+ this._blockedCommands.forEach(function (elt) {
+ if (elt.getAttribute("wasDisabled") != "true")
+ elt.removeAttribute("disabled");
+ elt.removeAttribute("wasDisabled");
+ });
+ },
+
+ // nsIDOMEventListener
+ handleEvent: function SU_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "popuphidden":
+ if (aEvent.originalTarget == this.panel) {
+ if (!this._element("editBookmarkPanelContent").hidden)
+ this.quitEditMode();
+
+ this._restoreCommandsState();
+ this._itemId = -1;
+ if (this._batching) {
+ PlacesUtils.transactionManager.endBatch(false);
+ this._batching = false;
+ }
+
+ switch (this._actionOnHide) {
+ case "cancel": {
+ PlacesUtils.transactionManager.undoTransaction();
+ break;
+ }
+ case "remove": {
+ // Remove all bookmarks for the bookmark's url, this also removes
+ // the tags for the url.
+ PlacesUtils.transactionManager.beginBatch(null);
+ let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
+ for (let i = 0; i < itemIds.length; i++) {
+ let txn = new PlacesRemoveItemTransaction(itemIds[i]);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ PlacesUtils.transactionManager.endBatch(false);
+ break;
+ }
+ }
+ this._actionOnHide = "";
+ }
+ break;
+ case "keypress":
+ if (aEvent.defaultPrevented) {
+ // The event has already been consumed inside of the panel.
+ break;
+ }
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_ESCAPE:
+ if (!this._element("editBookmarkPanelContent").hidden)
+ this.cancelButtonOnCommand();
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ if (aEvent.target.className == "expander-up" ||
+ aEvent.target.className == "expander-down" ||
+ aEvent.target.id == "editBMPanel_newFolderButton") {
+ //XXX Why is this necessary? The defaultPrevented check should
+ // be enough.
+ break;
+ }
+ this.panel.hidePopup();
+ break;
+ }
+ break;
+ }
+ },
+
+ _overlayLoaded: false,
+ _overlayLoading: false,
+ showEditBookmarkPopup:
+ function SU_showEditBookmarkPopup(aItemId, aAnchorElement, aPosition) {
+ // Performance: load the overlay the first time the panel is opened
+ // (see bug 392443).
+ if (this._overlayLoading)
+ return;
+
+ if (this._overlayLoaded) {
+ this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
+ return;
+ }
+
+ this._overlayLoading = true;
+ document.loadOverlay(
+ "chrome://browser/content/places/editBookmarkOverlay.xul",
+ (function (aSubject, aTopic, aData) {
+ //XXX We just caused localstore.rdf to be re-applied (bug 640158)
+ retrieveToolbarIconsizesFromTheme();
+
+ // Move the header (star, title, button) into the grid,
+ // so that it aligns nicely with the other items (bug 484022).
+ let header = this._element("editBookmarkPanelHeader");
+ let rows = this._element("editBookmarkPanelGrid").lastChild;
+ rows.insertBefore(header, rows.firstChild);
+ header.hidden = false;
+
+ this._overlayLoading = false;
+ this._overlayLoaded = true;
+ this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
+ }).bind(this)
+ );
+ },
+
+ _doShowEditBookmarkPanel:
+ function SU__doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition) {
+ if (this.panel.state != "closed")
+ return;
+
+ this._blockCommands(); // un-done in the popuphiding handler
+
+ // Set panel title:
+ // if we are batching, i.e. the bookmark has been added now,
+ // then show Page Bookmarked, else if the bookmark did already exist,
+ // we are about editing it, then use Edit This Bookmark.
+ this._element("editBookmarkPanelTitle").value =
+ this._batching ?
+ gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
+ gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
+
+ // No description; show the Done, Cancel;
+ this._element("editBookmarkPanelDescription").textContent = "";
+ this._element("editBookmarkPanelBottomButtons").hidden = false;
+ this._element("editBookmarkPanelContent").hidden = false;
+
+ // The remove button is shown only if we're not already batching, i.e.
+ // if the cancel button/ESC does not remove the bookmark.
+ this._element("editBookmarkPanelRemoveButton").hidden = this._batching;
+
+ // The label of the remove button differs if the URI is bookmarked
+ // multiple times.
+ var bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
+ var forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
+ var label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
+ this._element("editBookmarkPanelRemoveButton").label = label;
+
+ // unset the unstarred state, if set
+ this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
+
+ this._itemId = aItemId !== undefined ? aItemId : this._itemId;
+ this.beginBatch();
+
+ this.panel.openPopup(aAnchorElement, aPosition);
+
+ gEditItemOverlay.initPanel(this._itemId,
+ { hiddenRows: ["description", "location",
+ "loadInSidebar", "keyword"] });
+ },
+
+ panelShown:
+ function SU_panelShown(aEvent) {
+ if (aEvent.target == this.panel) {
+ if (!this._element("editBookmarkPanelContent").hidden) {
+ let fieldToFocus = "editBMPanel_" +
+ gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
+ var elt = this._element(fieldToFocus);
+ elt.focus();
+ elt.select();
+ }
+ else {
+ // Note this isn't actually used anymore, we should remove this
+ // once we decide not to bring back the page bookmarked notification
+ this.panel.focus();
+ }
+ }
+ },
+
+ quitEditMode: function SU_quitEditMode() {
+ this._element("editBookmarkPanelContent").hidden = true;
+ this._element("editBookmarkPanelBottomButtons").hidden = true;
+ gEditItemOverlay.uninitPanel(true);
+ },
+
+ cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
+ this._actionOnHide = "cancel";
+ this.panel.hidePopup();
+ },
+
+ removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
+ this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
+ this._actionOnHide = "remove";
+ this.panel.hidePopup();
+ },
+
+ beginBatch: function SU_beginBatch() {
+ if (!this._batching) {
+ PlacesUtils.transactionManager.beginBatch(null);
+ this._batching = true;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesCommandHook
+
+var PlacesCommandHook = {
+ /**
+ * Adds a bookmark to the page loaded in the given browser.
+ *
+ * @param aBrowser
+ * a <browser> element.
+ * @param [optional] aParent
+ * The folder in which to create a new bookmark if the page loaded in
+ * aBrowser isn't bookmarked yet, defaults to the unfiled root.
+ * @param [optional] aShowEditUI
+ * whether or not to show the edit-bookmark UI for the bookmark item
+ */
+ bookmarkPage: function PCH_bookmarkPage(aBrowser, aParent, aShowEditUI) {
+ var uri = aBrowser.currentURI;
+ var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
+ if (itemId == -1) {
+ // Copied over from addBookmarkForBrowser:
+ // Bug 52536: We obtain the URL and title from the nsIWebNavigation
+ // associated with a <browser/> rather than from a DOMWindow.
+ // This is because when a full page plugin is loaded, there is
+ // no DOMWindow (?) but information about the loaded document
+ // may still be obtained from the webNavigation.
+ var webNav = aBrowser.webNavigation;
+ var url = webNav.currentURI;
+ var title;
+ var description;
+ var charset;
+ try {
+ let isErrorPage = /^about:(neterror|certerror|blocked)/
+ .test(webNav.document.documentURI);
+ title = isErrorPage ? PlacesUtils.history.getPageTitle(url)
+ : webNav.document.title;
+ title = title || url.spec;
+ description = PlacesUIUtils.getDescriptionFromDocument(webNav.document);
+ charset = webNav.document.characterSet;
+ }
+ catch (e) { }
+
+ if (aShowEditUI) {
+ // If we bookmark the page here (i.e. page was not "starred" already)
+ // but open right into the "edit" state, start batching here, so
+ // "Cancel" in that state removes the bookmark.
+ StarUI.beginBatch();
+ }
+
+ var parent = aParent != undefined ?
+ aParent : PlacesUtils.unfiledBookmarksFolderId;
+ var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
+ var txn = new PlacesCreateBookmarkTransaction(uri, parent,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title, null, [descAnno]);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ itemId = txn.item.id;
+ // Set the character-set
+ if (charset && !PrivateBrowsingUtils.isWindowPrivate(aBrowser.contentWindow))
+ PlacesUtils.setCharsetForURI(uri, charset);
+ }
+
+ // Revert the contents of the location bar
+ if (gURLBar)
+ gURLBar.handleRevert();
+
+ // If it was not requested to open directly in "edit" mode, we are done.
+ if (!aShowEditUI)
+ return;
+
+ // Try to dock the panel to:
+ // 1. the bookmarks menu button
+ // 2. the page-proxy-favicon
+ // 3. the content area
+ if (BookmarkingUI.anchor) {
+ StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
+ "bottomcenter topright");
+ return;
+ }
+
+ let pageProxyFavicon = document.getElementById("page-proxy-favicon");
+ if (isElementVisible(pageProxyFavicon)) {
+ StarUI.showEditBookmarkPopup(itemId, pageProxyFavicon,
+ "bottomcenter topright");
+ } else {
+ StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
+ }
+ },
+
+ /**
+ * Adds a bookmark to the page loaded in the current tab.
+ */
+ bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
+ this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
+ },
+
+ /**
+ * Adds a bookmark to the page targeted by a link.
+ * @param aParent
+ * The folder in which to create a new bookmark if aURL isn't
+ * bookmarked.
+ * @param aURL (string)
+ * the address of the link target
+ * @param aTitle
+ * The link text
+ */
+ bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) {
+ var linkURI = makeURI(aURL);
+ var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
+ if (itemId == -1) {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: linkURI
+ , title: aTitle
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window);
+ }
+ else {
+ PlacesUIUtils.showBookmarkDialog({ action: "edit"
+ , type: "bookmark"
+ , itemId: itemId
+ }, window);
+ }
+ },
+
+ /**
+ * List of nsIURI objects characterizing the tabs currently open in the
+ * browser, modulo pinned tabs. The URIs will be in the order in which their
+ * corresponding tabs appeared and duplicates are discarded.
+ */
+ get uniqueCurrentPages() {
+ let uniquePages = {};
+ let URIs = [];
+ gBrowser.visibleTabs.forEach(function (tab) {
+ let spec = tab.linkedBrowser.currentURI.spec;
+ if (!tab.pinned && !(spec in uniquePages)) {
+ uniquePages[spec] = null;
+ URIs.push(tab.linkedBrowser.currentURI);
+ }
+ });
+ return URIs;
+ },
+
+ /**
+ * Adds a folder with bookmarks to all of the currently open tabs in this
+ * window.
+ */
+ bookmarkCurrentPages: function PCH_bookmarkCurrentPages() {
+ let pages = this.uniqueCurrentPages;
+ if (pages.length > 1) {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "folder"
+ , URIList: pages
+ , hiddenRows: [ "description" ]
+ }, window);
+ }
+ },
+
+ /**
+ * Updates disabled state for the "Bookmark All Tabs" command.
+ */
+ updateBookmarkAllTabsCommand:
+ function PCH_updateBookmarkAllTabsCommand() {
+ // There's nothing to do in non-browser windows.
+ if (window.location.href != getBrowserURL())
+ return;
+
+ // Disable "Bookmark All Tabs" if there are less than two
+ // "unique current pages".
+ goSetCommandEnabled("Browser:BookmarkAllTabs",
+ this.uniqueCurrentPages.length >= 2);
+ },
+
+ /**
+ * Adds a Live Bookmark to a feed associated with the current page.
+ * @param url
+ * The nsIURI of the page the feed was attached to
+ * @title title
+ * The title of the feed. Optional.
+ * @subtitle subtitle
+ * A short description of the feed. Optional.
+ */
+ addLiveBookmark: function PCH_addLiveBookmark(url, feedTitle, feedSubtitle) {
+ let toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId, -1);
+
+ let feedURI = makeURI(url);
+ let title = feedTitle || gBrowser.contentTitle;
+ let description = feedSubtitle;
+ if (!description) {
+ description = PlacesUIUtils.getDescriptionFromDocument(gBrowser.contentDocument);
+ }
+
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "livemark"
+ , feedURI: feedURI
+ , siteURI: gBrowser.currentURI
+ , title: title
+ , description: description
+ , defaultInsertionPoint: toolbarIP
+ , hiddenRows: [ "feedLocation"
+ , "siteLocation"
+ , "description" ]
+ }, window);
+ },
+
+ /**
+ * Opens the Places Organizer.
+ * @param aLeftPaneRoot
+ * The query to select in the organizer window - options
+ * are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
+ * UnfiledBookmarks, Tags and Downloads.
+ */
+ showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
+ var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
+ // Due to bug 528706, getMostRecentWindow can return closed windows.
+ if (!organizer || organizer.closed) {
+ // No currently open places window, so open one with the specified mode.
+ openDialog("chrome://browser/content/places/places.xul",
+ "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
+ }
+ else {
+ organizer.PlacesOrganizer.selectLeftPaneQuery(aLeftPaneRoot);
+ organizer.focus();
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// HistoryMenu
+
+// View for the history menu.
+function HistoryMenu(aPopupShowingEvent) {
+ // Workaround for Bug 610187. The sidebar does not include all the Places
+ // views definitions, and we don't need them there.
+ // Defining the prototype inheritance in the prototype itself would cause
+ // browser.js to halt on "PlacesMenu is not defined" error.
+ this.__proto__.__proto__ = PlacesMenu.prototype;
+ XPCOMUtils.defineLazyServiceGetter(this, "_ss",
+ "@mozilla.org/browser/sessionstore;1",
+ "nsISessionStore");
+ PlacesMenu.call(this, aPopupShowingEvent,
+ "place:sort=4&maxResults=15");
+}
+
+HistoryMenu.prototype = {
+ toggleRestoreLastSession: function HM_toggleRestoreLastSession() {
+ let restoreItem = this._rootElt.ownerDocument.getElementById("Browser:RestoreLastSession");
+
+ if (this._ss.canRestoreLastSession &&
+ !PrivateBrowsingUtils.isWindowPrivate(window))
+ restoreItem.removeAttribute("disabled");
+ else
+ restoreItem.setAttribute("disabled", true);
+ },
+
+ toggleRecentlyClosedTabs: function HM_toggleRecentlyClosedTabs() {
+ // enable/disable the Recently Closed Tabs sub menu
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
+
+ // no restorable tabs, so disable menu
+ if (this._ss.getClosedTabCount(window) == 0)
+ undoMenu.setAttribute("disabled", true);
+ else
+ undoMenu.removeAttribute("disabled");
+ },
+
+ /**
+ * Re-open a closed tab and put it to the end of the tab strip.
+ * Used for a middle click.
+ * @param aEvent
+ * The event when the user clicks the menu item
+ */
+ _undoCloseMiddleClick: function PHM__undoCloseMiddleClick(aEvent) {
+ if (aEvent.button != 1)
+ return;
+
+ undoCloseTab(aEvent.originalTarget.value);
+ gBrowser.moveTabToEnd();
+ },
+
+ /**
+ * Populate when the history menu is opened
+ */
+ populateUndoSubmenu: function PHM_populateUndoSubmenu() {
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
+ var undoPopup = undoMenu.firstChild;
+
+ // remove existing menu items
+ while (undoPopup.hasChildNodes())
+ undoPopup.removeChild(undoPopup.firstChild);
+
+ // no restorable tabs, so make sure menu is disabled, and return
+ if (this._ss.getClosedTabCount(window) == 0) {
+ undoMenu.setAttribute("disabled", true);
+ return;
+ }
+
+ // enable menu
+ undoMenu.removeAttribute("disabled");
+
+ // populate menu
+ var undoItems = JSON.parse(this._ss.getClosedTabData(window));
+ for (var i = 0; i < undoItems.length; i++) {
+ var m = document.createElement("menuitem");
+ m.setAttribute("label", undoItems[i].title);
+ if (undoItems[i].image) {
+ let iconURL = undoItems[i].image;
+ // don't initiate a connection just to fetch a favicon (see bug 467828)
+ if (/^https?:/.test(iconURL))
+ iconURL = "moz-anno:favicon:" + iconURL;
+ m.setAttribute("image", iconURL);
+ }
+ m.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+ m.setAttribute("value", i);
+ m.setAttribute("oncommand", "undoCloseTab(" + i + ");");
+
+ // Set the targetURI attribute so it will be shown in tooltip and trigger
+ // onLinkHovered. SessionStore uses one-based indexes, so we need to
+ // normalize them.
+ let tabData = undoItems[i].state;
+ let activeIndex = (tabData.index || tabData.entries.length) - 1;
+ if (activeIndex >= 0 && tabData.entries[activeIndex])
+ m.setAttribute("targetURI", tabData.entries[activeIndex].url);
+
+ m.addEventListener("click", this._undoCloseMiddleClick, false);
+ if (i == 0)
+ m.setAttribute("key", "key_undoCloseTab");
+ undoPopup.appendChild(m);
+ }
+
+ // "Restore All Tabs"
+ var strings = gNavigatorBundle;
+ undoPopup.appendChild(document.createElement("menuseparator"));
+ m = undoPopup.appendChild(document.createElement("menuitem"));
+ m.id = "menu_restoreAllTabs";
+ m.setAttribute("label", strings.getString("menuRestoreAllTabs.label"));
+ m.addEventListener("command", function() {
+ for (var i = 0; i < undoItems.length; i++)
+ undoCloseTab();
+ }, false);
+ },
+
+ toggleRecentlyClosedWindows: function PHM_toggleRecentlyClosedWindows() {
+ // enable/disable the Recently Closed Windows sub menu
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
+
+ // no restorable windows, so disable menu
+ if (this._ss.getClosedWindowCount() == 0)
+ undoMenu.setAttribute("disabled", true);
+ else
+ undoMenu.removeAttribute("disabled");
+ },
+
+ /**
+ * Populate when the history menu is opened
+ */
+ populateUndoWindowSubmenu: function PHM_populateUndoWindowSubmenu() {
+ let undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
+ let undoPopup = undoMenu.firstChild;
+ let menuLabelString = gNavigatorBundle.getString("menuUndoCloseWindowLabel");
+ let menuLabelStringSingleTab =
+ gNavigatorBundle.getString("menuUndoCloseWindowSingleTabLabel");
+
+ // remove existing menu items
+ while (undoPopup.hasChildNodes())
+ undoPopup.removeChild(undoPopup.firstChild);
+
+ // no restorable windows, so make sure menu is disabled, and return
+ if (this._ss.getClosedWindowCount() == 0) {
+ undoMenu.setAttribute("disabled", true);
+ return;
+ }
+
+ // enable menu
+ undoMenu.removeAttribute("disabled");
+
+ // populate menu
+ let undoItems = JSON.parse(this._ss.getClosedWindowData());
+ for (let i = 0; i < undoItems.length; i++) {
+ let undoItem = undoItems[i];
+ let otherTabsCount = undoItem.tabs.length - 1;
+ let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
+ : PluralForm.get(otherTabsCount, menuLabelString);
+ let menuLabel = label.replace("#1", undoItem.title)
+ .replace("#2", otherTabsCount);
+ let m = document.createElement("menuitem");
+ m.setAttribute("label", menuLabel);
+ let selectedTab = undoItem.tabs[undoItem.selected - 1];
+ if (selectedTab.image) {
+ let iconURL = selectedTab.image;
+ // don't initiate a connection just to fetch a favicon (see bug 467828)
+ if (/^https?:/.test(iconURL))
+ iconURL = "moz-anno:favicon:" + iconURL;
+ m.setAttribute("image", iconURL);
+ }
+ m.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+ m.setAttribute("oncommand", "undoCloseWindow(" + i + ");");
+
+ // Set the targetURI attribute so it will be shown in tooltip.
+ // SessionStore uses one-based indexes, so we need to normalize them.
+ let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
+ if (activeIndex >= 0 && selectedTab.entries[activeIndex])
+ m.setAttribute("targetURI", selectedTab.entries[activeIndex].url);
+
+ if (i == 0)
+ m.setAttribute("key", "key_undoCloseWindow");
+ undoPopup.appendChild(m);
+ }
+
+ // "Open All in Windows"
+ undoPopup.appendChild(document.createElement("menuseparator"));
+ let m = undoPopup.appendChild(document.createElement("menuitem"));
+ m.id = "menu_restoreAllWindows";
+ m.setAttribute("label", gNavigatorBundle.getString("menuRestoreAllWindows.label"));
+ m.setAttribute("oncommand",
+ "for (var i = 0; i < " + undoItems.length + "; i++) undoCloseWindow();");
+ },
+
+ toggleTabsFromOtherComputers: function PHM_toggleTabsFromOtherComputers() {
+ // This is a no-op if MOZ_SERVICES_SYNC isn't defined
+#ifdef MOZ_SERVICES_SYNC
+ // Enable/disable the Tabs From Other Computers menu. Some of the menus handled
+ // by HistoryMenu do not have this menuitem.
+ let menuitem = this._rootElt.getElementsByClassName("syncTabsMenuItem")[0];
+ if (!menuitem)
+ return;
+
+ // If Sync isn't configured yet, then don't show the menuitem.
+ if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
+ Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
+ menuitem.setAttribute("hidden", true);
+ return;
+ }
+
+ // The tabs engine might never be inited (if services.sync.registerEngines
+ // is modified), so make sure we avoid undefined errors.
+ let enabled = Weave.Service.isLoggedIn &&
+ Weave.Service.engineManager.get("tabs") &&
+ Weave.Service.engineManager.get("tabs").enabled;
+ menuitem.setAttribute("disabled", !enabled);
+ menuitem.setAttribute("hidden", false);
+#endif
+ },
+
+ _onPopupShowing: function HM__onPopupShowing(aEvent) {
+ PlacesMenu.prototype._onPopupShowing.apply(this, arguments);
+
+ // Don't handle events for submenus.
+ if (aEvent.target != aEvent.currentTarget)
+ return;
+
+ this.toggleRestoreLastSession();
+ this.toggleRecentlyClosedTabs();
+ this.toggleRecentlyClosedWindows();
+ this.toggleTabsFromOtherComputers();
+ },
+
+ _onCommand: function HM__onCommand(aEvent) {
+ let placesNode = aEvent.target._placesNode;
+ if (placesNode) {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsTyped(placesNode.uri);
+ openUILink(placesNode.uri, aEvent, { ignoreAlt: true });
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BookmarksEventHandler
+
+/**
+ * Functions for handling events in the Bookmarks Toolbar and menu.
+ */
+var BookmarksEventHandler = {
+ /**
+ * Handler for click event for an item in the bookmarks toolbar or menu.
+ * Menus and submenus from the folder buttons bubble up to this handler.
+ * Left-click is handled in the onCommand function.
+ * When items are middle-clicked (or clicked with modifier), open in tabs.
+ * If the click came through a menu, close the menu.
+ * @param aEvent
+ * DOMEvent for the click
+ * @param aView
+ * The places view which aEvent should be associated with.
+ */
+ onClick: function BEH_onClick(aEvent, aView) {
+ // Only handle middle-click or left-click with modifiers.
+#ifdef XP_MACOSX
+ var modifKey = aEvent.metaKey || aEvent.shiftKey;
+#else
+ var modifKey = aEvent.ctrlKey || aEvent.shiftKey;
+#endif
+ if (aEvent.button == 2 || (aEvent.button == 0 && !modifKey))
+ return;
+
+ var target = aEvent.originalTarget;
+ // If this event bubbled up from a menu or menuitem, close the menus.
+ // Do this before opening tabs, to avoid hiding the open tabs confirm-dialog.
+ if (target.localName == "menu" || target.localName == "menuitem") {
+ for (node = target.parentNode; node; node = node.parentNode) {
+ if (node.localName == "menupopup")
+ node.hidePopup();
+ else if (node.localName != "menu" &&
+ node.localName != "splitmenu" &&
+ node.localName != "hbox" &&
+ node.localName != "vbox" )
+ break;
+ }
+ }
+
+ if (target._placesNode && PlacesUtils.nodeIsContainer(target._placesNode)) {
+ // Don't open the root folder in tabs when the empty area on the toolbar
+ // is middle-clicked or when a non-bookmark item except for Open in Tabs)
+ // in a bookmarks menupopup is middle-clicked.
+ if (target.localName == "menu" || target.localName == "toolbarbutton")
+ PlacesUIUtils.openContainerNodeInTabs(target._placesNode, aEvent, aView);
+ }
+ else if (aEvent.button == 1) {
+ // left-clicks with modifier are already served by onCommand
+ this.onCommand(aEvent, aView);
+ }
+ },
+
+ /**
+ * Handler for command event for an item in the bookmarks toolbar.
+ * Menus and submenus from the folder buttons bubble up to this handler.
+ * Opens the item.
+ * @param aEvent
+ * DOMEvent for the command
+ * @param aView
+ * The places view which aEvent should be associated with.
+ */
+ onCommand: function BEH_onCommand(aEvent, aView) {
+ var target = aEvent.originalTarget;
+ if (target._placesNode)
+ PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
+ },
+
+ fillInBHTooltip: function BEH_fillInBHTooltip(aDocument, aEvent) {
+ var node;
+ var cropped = false;
+ var targetURI;
+
+ if (aDocument.tooltipNode.localName == "treechildren") {
+ var tree = aDocument.tooltipNode.parentNode;
+ var tbo = tree.treeBoxObject;
+ var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
+ if (cell.row == -1)
+ return false;
+ node = tree.view.nodeForTreeIndex(cell.row);
+ cropped = tbo.isCellCropped(cell.row, cell.col);
+ }
+ else {
+ // Check whether the tooltipNode is a Places node.
+ // In such a case use it, otherwise check for targetURI attribute.
+ var tooltipNode = aDocument.tooltipNode;
+ if (tooltipNode._placesNode)
+ node = tooltipNode._placesNode;
+ else {
+ // This is a static non-Places node.
+ targetURI = tooltipNode.getAttribute("targetURI");
+ }
+ }
+
+ if (!node && !targetURI)
+ return false;
+
+ // Show node.label as tooltip's title for non-Places nodes.
+ var title = node ? node.title : tooltipNode.label;
+
+ // Show URL only for Places URI-nodes or nodes with a targetURI attribute.
+ var url;
+ if (targetURI || PlacesUtils.nodeIsURI(node))
+ url = targetURI || node.uri;
+
+ // Show tooltip for containers only if their title is cropped.
+ if (!cropped && !url)
+ return false;
+
+ var tooltipTitle = aDocument.getElementById("bhtTitleText");
+ tooltipTitle.hidden = (!title || (title == url));
+ if (!tooltipTitle.hidden)
+ tooltipTitle.textContent = title;
+
+ var tooltipUrl = aDocument.getElementById("bhtUrlText");
+ tooltipUrl.hidden = !url;
+ if (!tooltipUrl.hidden)
+ tooltipUrl.value = url;
+
+ // Show tooltip.
+ return true;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesMenuDNDHandler
+
+// Handles special drag and drop functionality for Places menus that are not
+// part of a Places view (e.g. the bookmarks menu in the menubar).
+var PlacesMenuDNDHandler = {
+ _springLoadDelay: 350, // milliseconds
+ _loadTimer: null,
+ _closerTimer: null,
+
+ /**
+ * Called when the user enters the <menu> element during a drag.
+ * @param event
+ * The DragEnter event that spawned the opening.
+ */
+ onDragEnter: function PMDH_onDragEnter(event) {
+ // Opening menus in a Places popup is handled by the view itself.
+ if (!this._isStaticContainer(event.target))
+ return;
+
+ let popup = event.target.lastChild;
+ if (this._loadTimer || popup.state === "showing" || popup.state === "open")
+ return;
+
+ this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._loadTimer.initWithCallback(() => {
+ this._loadTimer = null;
+ popup.setAttribute("autoopened", "true");
+ popup.showPopup(popup);
+ }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ /**
+ * Handles dragleave on the <menu> element.
+ * @returns true if the element is a container element (menu or
+ * menu-toolbarbutton), false otherwise.
+ */
+ onDragLeave: function PMDH_onDragLeave(event) {
+ // Handle menu-button separate targets.
+ if (event.relatedTarget === event.currentTarget ||
+ event.relatedTarget.parentNode === event.currentTarget)
+ return;
+
+ // Closing menus in a Places popup is handled by the view itself.
+ if (!this._isStaticContainer(event.target))
+ return;
+
+ let popup = event.target.lastChild;
+
+ if (this._loadTimer) {
+ this._loadTimer.cancel();
+ this._loadTimer = null;
+ }
+ this._closeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._closeTimer.initWithCallback(function() {
+ this._closeTimer = null;
+ let node = PlacesControllerDragHelper.currentDropTarget;
+ let inHierarchy = false;
+ while (node && !inHierarchy) {
+ inHierarchy = node == event.target;
+ node = node.parentNode;
+ }
+ if (!inHierarchy && popup && popup.hasAttribute("autoopened")) {
+ popup.removeAttribute("autoopened");
+ popup.hidePopup();
+ }
+ }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ /**
+ * Determines if a XUL element represents a static container.
+ * @returns true if the element is a container element (menu or
+ *` menu-toolbarbutton), false otherwise.
+ */
+ _isStaticContainer: function PMDH__isContainer(node) {
+ let isMenu = node.localName == "menu" ||
+ (node.localName == "toolbarbutton" &&
+ (node.getAttribute("type") == "menu" ||
+ node.getAttribute("type") == "menu-button"));
+ let isStatic = !("_placesNode" in node) && node.lastChild &&
+ node.lastChild.hasAttribute("placespopup") &&
+ !node.parentNode.hasAttribute("placespopup");
+ return isMenu && isStatic;
+ },
+
+ /**
+ * Called when the user drags over the <menu> element.
+ * @param event
+ * The DragOver event.
+ */
+ onDragOver: function PMDH_onDragOver(event) {
+ let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+ if (ip && PlacesControllerDragHelper.canDrop(ip, event.dataTransfer))
+ event.preventDefault();
+
+ event.stopPropagation();
+ },
+
+ /**
+ * Called when the user drops on the <menu> element.
+ * @param event
+ * The Drop event.
+ */
+ onDrop: function PMDH_onDrop(event) {
+ // Put the item at the end of bookmark menu.
+ let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+ PlacesControllerDragHelper.onDrop(ip, event.dataTransfer);
+ event.stopPropagation();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesToolbarHelper
+
+/**
+ * This object handles the initialization and uninitialization of the bookmarks
+ * toolbar.
+ */
+let PlacesToolbarHelper = {
+ _place: "place:folder=TOOLBAR",
+
+ get _viewElt() {
+ return document.getElementById("PlacesToolbar");
+ },
+
+ init: function PTH_init() {
+ let viewElt = this._viewElt;
+ if (!viewElt || viewElt._placesView)
+ return;
+
+ // If the bookmarks toolbar item is hidden because the parent toolbar is
+ // collapsed or hidden (i.e. in a popup), spare the initialization. Also,
+ // there is no need to initialize the toolbar if customizing because
+ // init() will be called when the customization is done.
+ let toolbar = viewElt.parentNode.parentNode;
+ if (toolbar.collapsed ||
+ getComputedStyle(toolbar, "").display == "none" ||
+ this._isCustomizing)
+ return;
+
+ new PlacesToolbar(this._place);
+ },
+
+ customizeStart: function PTH_customizeStart() {
+ let viewElt = this._viewElt;
+ if (viewElt && viewElt._placesView)
+ viewElt._placesView.uninit();
+
+ this._isCustomizing = true;
+ },
+
+ customizeDone: function PTH_customizeDone() {
+ this._isCustomizing = false;
+ this.init();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BookmarkingUI
+
+/**
+ * Handles the bookmarks star button in the URL bar, as well as the bookmark
+ * menu button.
+ */
+
+let BookmarkingUI = {
+ get button() {
+ if (!this._button) {
+ this._button = document.getElementById("bookmarks-menu-button");
+ }
+ return this._button;
+ },
+
+ get star() {
+ if (!this._star) {
+ this._star = document.getElementById("star-button");
+ }
+ return this._star;
+ },
+
+ get anchor() {
+ if (this.star && isElementVisible(this.star)) {
+ // Anchor to the icon, so the panel looks more natural.
+ return this.star;
+ }
+ return null;
+ },
+
+ STATUS_UPDATING: -1,
+ STATUS_UNSTARRED: 0,
+ STATUS_STARRED: 1,
+ get status() {
+ if (this._pendingStmt)
+ return this.STATUS_UPDATING;
+ return this.star &&
+ this.star.hasAttribute("starred") ? this.STATUS_STARRED
+ : this.STATUS_UNSTARRED;
+ },
+
+ get _starredTooltip()
+ {
+ delete this._starredTooltip;
+ return this._starredTooltip =
+ gNavigatorBundle.getString("starButtonOn.tooltip");
+ },
+
+ get _unstarredTooltip()
+ {
+ delete this._unstarredTooltip;
+ return this._unstarredTooltip =
+ gNavigatorBundle.getString("starButtonOff.tooltip");
+ },
+
+ /**
+ * The popup contents must be updated when the user customizes the UI, or
+ * changes the personal toolbar collapsed status. In such a case, any needed
+ * change should be handled in the popupshowing helper, for performance
+ * reasons.
+ */
+ _popupNeedsUpdate: true,
+ onToolbarVisibilityChange: function BUI_onToolbarVisibilityChange() {
+ this._popupNeedsUpdate = true;
+ },
+
+ onPopupShowing: function BUI_onPopupShowing(event) {
+ // Don't handle events for submenus.
+ if (event.target != event.currentTarget)
+ return;
+
+ if (!this._popupNeedsUpdate)
+ return;
+ this._popupNeedsUpdate = false;
+
+ let popup = event.target;
+ let getPlacesAnonymousElement =
+ aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
+ "placesanonid",
+ aAnonId);
+
+ let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
+ if (viewToolbarMenuitem) {
+ // Update View bookmarks toolbar checkbox menuitem.
+ let personalToolbar = document.getElementById("PersonalToolbar");
+ viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
+ }
+
+ let toolbarMenuitem = getPlacesAnonymousElement("toolbar-autohide");
+ if (toolbarMenuitem) {
+ // If bookmarks items are visible, hide Bookmarks Toolbar menu and the
+ // separator after it.
+ toolbarMenuitem.collapsed = toolbarMenuitem.nextSibling.collapsed =
+ isElementVisible(document.getElementById("personal-bookmarks"));
+ }
+ },
+
+ /**
+ * Handles star styling based on page proxy state changes.
+ */
+ onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) {
+ if (!this.star) {
+ return;
+ }
+
+ if (aState == "invalid") {
+ this.star.setAttribute("disabled", "true");
+ this.star.removeAttribute("starred");
+ }
+ else {
+ this.star.removeAttribute("disabled");
+ }
+ },
+
+ _updateToolbarStyle: function BUI__updateToolbarStyle() {
+ if (!this.button) {
+ return;
+ }
+
+ let personalToolbar = document.getElementById("PersonalToolbar");
+ let onPersonalToolbar = this.button.parentNode == personalToolbar ||
+ this.button.parentNode.parentNode == personalToolbar;
+
+ if (onPersonalToolbar) {
+ this.button.classList.add("bookmark-item");
+ this.button.classList.remove("toolbarbutton-1");
+ }
+ else {
+ this.button.classList.remove("bookmark-item");
+ this.button.classList.add("toolbarbutton-1");
+ }
+ },
+
+ _uninitView: function BUI__uninitView() {
+ // When an element with a placesView attached is removed and re-inserted,
+ // XBL reapplies the binding causing any kind of issues and possible leaks,
+ // so kill current view and let popupshowing generate a new one.
+ if (this.button && this.button._placesView) {
+ this.button._placesView.uninit();
+ }
+ // Also uninit the main menubar placesView, since it would have the same
+ // issues.
+ let menubar = document.getElementById("bookmarksMenu");
+ if (menubar && menubar._placesView) {
+ menubar._placesView.uninit();
+ }
+ },
+
+ customizeStart: function BUI_customizeStart() {
+ this._uninitView();
+ },
+
+ customizeChange: function BUI_customizeChange() {
+ this._updateToolbarStyle();
+ },
+
+ customizeDone: function BUI_customizeDone() {
+ delete this._button;
+ this.onToolbarVisibilityChange();
+ this._updateToolbarStyle();
+ },
+
+ _hasBookmarksObserver: false,
+ uninit: function BUI_uninit() {
+ this._uninitView();
+
+ if (this._hasBookmarksObserver) {
+ PlacesUtils.removeLazyBookmarkObserver(this);
+ }
+
+ if (this._pendingStmt) {
+ this._pendingStmt.cancel();
+ delete this._pendingStmt;
+ }
+ },
+
+ onLocationChange: function BUI_onLocationChange() {
+ if (this._uri && gBrowser.currentURI.equals(this._uri)) {
+ return;
+ }
+ this.updateStarState();
+ },
+
+ updateStarState: function BUI_updateStarState() {
+ // Reset tracked values.
+ this._uri = gBrowser.currentURI;
+ this._itemIds = [];
+
+ if (this._pendingStmt) {
+ this._pendingStmt.cancel();
+ delete this._pendingStmt;
+ }
+
+ // We can load about:blank before the actual page, but there is no point in handling that page.
+ if (isBlankPageURL(this._uri.spec)) {
+ return;
+ }
+
+ this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, (aItemIds, aURI) => {
+ // Safety check that the bookmarked URI equals the tracked one.
+ if (!aURI.equals(this._uri)) {
+ Components.utils.reportError("BookmarkingUI did not receive current URI");
+ return;
+ }
+
+ // It's possible that onItemAdded gets called before the async statement
+ // calls back. For such an edge case, retain all unique entries from both
+ // arrays.
+ this._itemIds = this._itemIds.filter(
+ function (id) aItemIds.indexOf(id) == -1
+ ).concat(aItemIds);
+
+ this._updateStar();
+
+ // Start observing bookmarks if needed.
+ if (!this._hasBookmarksObserver) {
+ try {
+ PlacesUtils.addLazyBookmarkObserver(this);
+ this._hasBookmarksObserver = true;
+ } catch(ex) {
+ Components.utils.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
+ }
+ }
+
+ delete this._pendingStmt;
+ }, this);
+ },
+
+ _updateStar: function BUI__updateStar() {
+ if (!this.star) {
+ return;
+ }
+
+ if (this._itemIds.length > 0) {
+ this.star.setAttribute("starred", "true");
+ this.star.setAttribute("tooltiptext", this._starredTooltip);
+ }
+ else {
+ this.star.removeAttribute("starred");
+ this.star.setAttribute("tooltiptext", this._unstarredTooltip);
+ }
+ },
+
+ onCommand: function BUI_onCommand(aEvent) {
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+ // Ignore clicks on the star if we are updating its state.
+ if (!this._pendingStmt) {
+ PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
+ }
+ },
+
+ // nsINavBookmarkObserver
+ onItemAdded: function BUI_onItemAdded(aItemId, aParentId, aIndex, aItemType,
+ aURI) {
+ if (aURI && aURI.equals(this._uri)) {
+ // If a new bookmark has been added to the tracked uri, register it.
+ if (this._itemIds.indexOf(aItemId) == -1) {
+ this._itemIds.push(aItemId);
+ this._updateStar();
+ }
+ }
+ },
+
+ onItemRemoved: function BUI_onItemRemoved(aItemId) {
+ let index = this._itemIds.indexOf(aItemId);
+ // If one of the tracked bookmarks has been removed, unregister it.
+ if (index != -1) {
+ this._itemIds.splice(index, 1);
+ this._updateStar();
+ }
+ },
+
+ onItemChanged: function BUI_onItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty, aNewValue) {
+ if (aProperty == "uri") {
+ let index = this._itemIds.indexOf(aItemId);
+ // If the changed bookmark was tracked, check if it is now pointing to
+ // a different uri and unregister it.
+ if (index != -1 && aNewValue != this._uri.spec) {
+ this._itemIds.splice(index, 1);
+ this._updateStar();
+ }
+ // If another bookmark is now pointing to the tracked uri, register it.
+ else if (index == -1 && aNewValue == this._uri.spec) {
+ this._itemIds.push(aItemId);
+ this._updateStar();
+ }
+ }
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onBeforeItemRemoved: function () {},
+ onItemVisited: function () {},
+ onItemMoved: function () {},
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver
+ ])
+};
diff --git a/application/palemoon/base/content/browser-plugins.js b/application/palemoon/base/content/browser-plugins.js
new file mode 100644
index 000000000..769ac6d8a
--- /dev/null
+++ b/application/palemoon/base/content/browser-plugins.js
@@ -0,0 +1,797 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+const kPrefSessionPersistMinutes = "plugin.sessionPermissionNow.intervalInMinutes";
+const kPrefPersistentDays = "plugin.persistentPermissionAlways.intervalInDays";
+
+var gPluginHandler = {
+ PLUGIN_SCRIPTED_STATE_NONE: 0,
+ PLUGIN_SCRIPTED_STATE_FIRED: 1,
+ PLUGIN_SCRIPTED_STATE_DONE: 2,
+
+ getPluginUI: function (plugin, anonid) {
+ return plugin.ownerDocument.
+ getAnonymousElementByAttribute(plugin, "anonid", anonid);
+ },
+
+ _getPluginInfo: function (pluginElement) {
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ pluginElement.QueryInterface(Ci.nsIObjectLoadingContent);
+
+ let tagMimetype;
+ let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin");
+ let pluginTag = null;
+ let permissionString = null;
+ let fallbackType = null;
+ let blocklistState = null;
+
+ if (pluginElement instanceof HTMLAppletElement) {
+ tagMimetype = "application/x-java-vm";
+ } else {
+ tagMimetype = pluginElement.actualType;
+
+ if (tagMimetype == "") {
+ tagMimetype = pluginElement.type;
+ }
+ }
+
+ if (gPluginHandler.isKnownPlugin(pluginElement)) {
+ pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
+ pluginName = gPluginHandler.makeNicePluginName(pluginTag.name);
+
+ permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
+ fallbackType = pluginElement.defaultFallbackType;
+ blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
+ // Make state-softblocked == state-notblocked for our purposes,
+ // they have the same UI. STATE_OUTDATED should not exist for plugin
+ // items, but let's alias it anyway, just in case.
+ if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+ blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+ }
+ }
+
+ return { mimetype: tagMimetype,
+ pluginName: pluginName,
+ pluginTag: pluginTag,
+ permissionString: permissionString,
+ fallbackType: fallbackType,
+ blocklistState: blocklistState,
+ };
+ },
+
+ // Map the plugin's name to a filtered version more suitable for user UI.
+ makeNicePluginName : function (aName) {
+ if (aName == "Shockwave Flash")
+ return "Adobe Flash";
+
+ // Clean up the plugin name by stripping off any trailing version numbers
+ // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
+ // Do this by first stripping the numbers, etc. off the end, and then
+ // removing "Plugin" (and then trimming to get rid of any whitespace).
+ // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
+ let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
+ return newName;
+ },
+
+ isTooSmall : function (plugin, overlay) {
+ // Is the <object>'s size too small to hold what we want to show?
+ let pluginRect = plugin.getBoundingClientRect();
+ // XXX bug 446693. The text-shadow on the submitted-report text at
+ // the bottom causes scrollHeight to be larger than it should be.
+ // Clamp width/height to properly show CTP overlay on different
+ // zoom levels when embedded in iframes (rounding bug). (Bug 972237)
+ let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) ||
+ (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height));
+ return overflows;
+ },
+
+ addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
+ // XXX just doing (callback)(arg) was giving a same-origin error. bug?
+ let self = this;
+ let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
+ linkNode.addEventListener("click",
+ function(evt) {
+ if (!evt.isTrusted)
+ return;
+ evt.preventDefault();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ evt ];
+ (self[callbackName]).apply(self, callbackArgs);
+ },
+ true);
+
+ linkNode.addEventListener("keydown",
+ function(evt) {
+ if (!evt.isTrusted)
+ return;
+ if (evt.keyCode == evt.DOM_VK_RETURN) {
+ evt.preventDefault();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ evt ];
+ evt.preventDefault();
+ (self[callbackName]).apply(self, callbackArgs);
+ }
+ },
+ true);
+ },
+
+ // Helper to get the binding handler type from a plugin object
+ _getBindingType : function(plugin) {
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ return null;
+
+ switch (plugin.pluginFallbackType) {
+ case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
+ return "PluginNotFound";
+ case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
+ return "PluginDisabled";
+ case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
+ return "PluginBlocklisted";
+ case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
+ return "PluginOutdated";
+ case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
+ return "PluginClickToPlay";
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+ return "PluginVulnerableUpdatable";
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+ return "PluginVulnerableNoUpdate";
+ case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
+ return "PluginPlayPreview";
+ default:
+ // Not all states map to a handler
+ return null;
+ }
+ },
+
+ handleEvent : function(event) {
+ let plugin;
+ let doc;
+
+ let eventType = event.type;
+ if (eventType === "PluginRemoved") {
+ doc = event.target;
+ }
+ else {
+ plugin = event.target;
+ doc = plugin.ownerDocument;
+
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ return;
+ }
+
+ if (eventType == "PluginBindingAttached") {
+ // The plugin binding fires this event when it is created.
+ // As an untrusted event, ensure that this object actually has a binding
+ // and make sure we don't handle it twice
+ let overlay = this.getPluginUI(plugin, "main");
+ if (!overlay || overlay._bindingHandled) {
+ return;
+ }
+ overlay._bindingHandled = true;
+
+ // Lookup the handler for this binding
+ eventType = this._getBindingType(plugin);
+ if (!eventType) {
+ // Not all bindings have handlers
+ return;
+ }
+ }
+
+ let shouldShowNotification = false;
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+ if (!browser)
+ return;
+
+ switch (eventType) {
+ case "PluginCrashed":
+ this.pluginInstanceCrashed(plugin, event);
+ break;
+
+ case "PluginNotFound":
+ /* No action (plugin finder obsolete) */
+ break;
+
+ case "PluginBlocklisted":
+ case "PluginOutdated":
+ shouldShowNotification = true;
+ break;
+
+ case "PluginVulnerableUpdatable":
+ let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
+ this.addLinkClickCallback(updateLink, "openPluginUpdatePage");
+ /* FALLTHRU */
+
+ case "PluginVulnerableNoUpdate":
+ case "PluginClickToPlay":
+ this._handleClickToPlayEvent(plugin);
+ let overlay = this.getPluginUI(plugin, "main");
+ let pluginName = this._getPluginInfo(plugin).pluginName;
+ let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]);
+ let overlayText = this.getPluginUI(plugin, "clickToPlay");
+ overlayText.textContent = messageString;
+ if (eventType == "PluginVulnerableUpdatable" ||
+ eventType == "PluginVulnerableNoUpdate") {
+ let vulnerabilityString = gNavigatorBundle.getString(eventType);
+ let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
+ vulnerabilityText.textContent = vulnerabilityString;
+ }
+ shouldShowNotification = true;
+ break;
+
+ case "PluginPlayPreview":
+ this._handlePlayPreviewEvent(plugin);
+ break;
+
+ case "PluginDisabled":
+ let manageLink = this.getPluginUI(plugin, "managePluginsLink");
+ this.addLinkClickCallback(manageLink, "managePlugins");
+ shouldShowNotification = true;
+ break;
+
+ case "PluginInstantiated":
+ //Pale Moon: don't show the indicator when plugins are enabled/allowed
+ if (gPrefService.getBoolPref("plugins.always_show_indicator")) {
+ shouldShowNotification = true;
+ }
+ break;
+ case "PluginRemoved":
+ shouldShowNotification = true;
+ break;
+ }
+
+ // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
+ if (eventType != "PluginCrashed" && eventType != "PluginRemoved") {
+ let overlay = this.getPluginUI(plugin, "main");
+ if (overlay != null) {
+ if (!this.isTooSmall(plugin, overlay)) {
+ overlay.style.visibility = "visible";
+ }
+ plugin.addEventListener("overflow", function(event) {
+ overlay.style.visibility = "hidden";
+ });
+ plugin.addEventListener("underflow", function(event) {
+ // this is triggered if only one dimension underflows,
+ // the other dimension might still overflow
+ if (!gPluginHandler.isTooSmall(plugin, overlay)) {
+ overlay.style.visibility = "visible";
+ }
+ });
+ }
+ }
+
+ // Only show the notification after we've done the isTooSmall check, so
+ // that the notification can decide whether to show the "alert" icon
+ if (shouldShowNotification) {
+ this._showClickToPlayNotification(browser);
+ }
+ },
+
+ isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
+ return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
+ Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+ },
+
+ canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
+ // if this isn't a known plugin, we can't activate it
+ // (this also guards pluginHost.getPermissionStringForType against
+ // unexpected input)
+ if (!gPluginHandler.isKnownPlugin(objLoadingContent))
+ return false;
+
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+ let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal;
+ let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
+
+ let isFallbackTypeValid =
+ objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
+ objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
+
+ if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) {
+ // checking if play preview is subject to CTP rules
+ let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType);
+ isFallbackTypeValid = !playPreviewInfo.ignoreCTP;
+ }
+
+ return !objLoadingContent.activated &&
+ pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
+ isFallbackTypeValid;
+ },
+
+ hideClickToPlayOverlay: function(aPlugin) {
+ let overlay = this.getPluginUI(aPlugin, "main");
+ if (overlay)
+ overlay.style.visibility = "hidden";
+ },
+
+ stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) {
+ let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (objLoadingContent.activated)
+ return;
+
+ if (aPlayPlugin)
+ objLoadingContent.playPlugin();
+ else
+ objLoadingContent.cancelPlayPreview();
+ },
+
+ // Callback for user clicking on a disabled plugin
+ managePlugins: function (aEvent) {
+ BrowserOpenAddonsMgr("addons://list/plugin");
+ },
+
+ // Callback for user clicking on the link in a click-to-play plugin
+ // (where the plugin has an update)
+ openPluginUpdatePage: function (aEvent) {
+ openURL(Services.urlFormatter.formatURLPref("plugins.update.url"));
+ },
+
+ // Callback for user clicking a "reload page" link
+ reloadPage: function (browser) {
+ browser.reload();
+ },
+
+ // Callback for user clicking the help icon
+ openHelpPage: function () {
+ openHelpLink("plugin-crashed", false);
+ },
+
+ // Event listener for click-to-play plugins.
+ _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) {
+ let doc = aPlugin.ownerDocument;
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // guard against giving pluginHost.getPermissionStringForType a type
+ // not associated with any known plugin
+ if (!gPluginHandler.isKnownPlugin(objLoadingContent))
+ return;
+ let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+ let principal = doc.defaultView.top.document.nodePrincipal;
+ let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
+
+ let overlay = this.getPluginUI(aPlugin, "main");
+
+ if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
+ if (overlay)
+ overlay.style.visibility = "hidden";
+ return;
+ }
+
+ if (overlay) {
+ overlay.addEventListener("click", gPluginHandler._overlayClickListener, true);
+ let closeIcon = gPluginHandler.getPluginUI(aPlugin, "closeIcon");
+ closeIcon.addEventListener("click", function(aEvent) {
+ if (aEvent.button == 0 && aEvent.isTrusted)
+ gPluginHandler.hideClickToPlayOverlay(aPlugin);
+ }, true);
+ }
+ },
+
+ _overlayClickListener: {
+ handleEvent: function PH_handleOverlayClick(aEvent) {
+ let plugin = document.getBindingParent(aEvent.target);
+ let contentWindow = plugin.ownerDocument.defaultView.top;
+ // gBrowser.getBrowserForDocument does not exist in the case where we
+ // drag-and-dropped a tab from a window containing only that tab. In
+ // that case, the window gets destroyed.
+ let browser = gBrowser.getBrowserForDocument ?
+ gBrowser.getBrowserForDocument(contentWindow.document) :
+ null;
+ // If browser is null here, we've been drag-and-dropped from another
+ // window, and this is the wrong click handler.
+ if (!browser) {
+ aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+ return;
+ }
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // Have to check that the target is not the link to update the plugin
+ if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
+ (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') &&
+ aEvent.button == 0 && aEvent.isTrusted) {
+ gPluginHandler._showClickToPlayNotification(browser, plugin);
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+ }
+ },
+
+ _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
+ let doc = aPlugin.ownerDocument;
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let pluginInfo = this._getPluginInfo(aPlugin);
+ let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype);
+
+ let previewContent = this.getPluginUI(aPlugin, "previewPluginContent");
+ let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+ if (!iframe) {
+ // lazy initialization of the iframe
+ iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ iframe.className = "previewPluginContentFrame";
+ previewContent.appendChild(iframe);
+
+ // Force a style flush, so that we ensure our binding is attached.
+ aPlugin.clientTop;
+ }
+ iframe.src = playPreviewInfo.redirectURL;
+
+ // MozPlayPlugin event can be dispatched from the extension chrome
+ // code to replace the preview content with the native plugin
+ previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+
+ previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
+
+ let playPlugin = !aEvent.detail;
+ gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
+
+ // cleaning up: removes overlay iframe from the DOM
+ let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+ if (iframe)
+ previewContent.removeChild(iframe);
+ }, true);
+
+ if (!playPreviewInfo.ignoreCTP) {
+ gPluginHandler._showClickToPlayNotification(browser);
+ }
+ },
+
+ reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
+ let browser = gBrowser.selectedBrowser;
+ let contentWindow = browser.contentWindow;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let doc = contentWindow.document;
+ let plugins = cwu.plugins;
+ for (let plugin of plugins) {
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ if (overlay)
+ overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (gPluginHandler.canActivatePlugin(objLoadingContent))
+ gPluginHandler._handleClickToPlayEvent(plugin);
+ }
+ gPluginHandler._showClickToPlayNotification(browser);
+ },
+
+ _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
+ if (event == "showing") {
+ gPluginHandler._makeCenterActions(this);
+ }
+ else if (event == "dismissed") {
+ // Once the popup is dismissed, clicking the icon should show the full
+ // list again
+ this.options.primaryPlugin = null;
+ }
+ },
+
+ // Match the behaviour of nsPermissionManager
+ _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) {
+ if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) {
+ return "(null)";
+ }
+
+ try {
+ if (principal.URI.host)
+ return principal.URI.host;
+ } catch (e) {}
+
+ return principal.origin;
+ },
+
+ _makeCenterActions: function PH_makeCenterActions(notification) {
+ let contentWindow = notification.browser.contentWindow;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let principal = contentWindow.document.nodePrincipal;
+ // This matches the behavior of nsPermssionManager, used for display purposes only
+ let principalHost = this._getHostFromPrincipal(principal);
+
+ let centerActions = [];
+ let pluginsFound = new Set();
+ for (let plugin of cwu.plugins) {
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (plugin.getContentTypeForMIMEType(plugin.actualType) != Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
+ continue;
+ }
+
+ let pluginInfo = this._getPluginInfo(plugin);
+ if (pluginInfo.permissionString === null) {
+ Components.utils.reportError("No permission string for active plugin.");
+ continue;
+ }
+ if (pluginsFound.has(pluginInfo.permissionString)) {
+ continue;
+ }
+ pluginsFound.add(pluginInfo.permissionString);
+
+ // Add the per-site permissions and details URLs to pluginInfo here
+ // because they are more expensive to compute and so we avoid it in
+ // the tighter loop above.
+ let permissionObj = Services.perms.
+ getPermissionObject(principal, pluginInfo.permissionString, false);
+ if (permissionObj) {
+ pluginInfo.pluginPermissionHost = permissionObj.host;
+ pluginInfo.pluginPermissionType = permissionObj.expireType;
+ }
+ else {
+ pluginInfo.pluginPermissionHost = principalHost;
+ pluginInfo.pluginPermissionType = undefined;
+ }
+
+ let url;
+ // TODO: allow the blocklist to specify a better link, bug 873093
+ if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+ url = Services.urlFormatter.formatURLPref("plugins.update.url");
+ }
+ else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+ url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
+ }
+ pluginInfo.detailsLink = url;
+
+ centerActions.push(pluginInfo);
+ }
+ centerActions.sort(function(a, b) {
+ return a.pluginName.localeCompare(b.pluginName);
+ });
+
+ notification.options.centerActions = centerActions;
+ },
+
+ /**
+ * Called from the plugin doorhanger to set the new permissions for a plugin
+ * and activate plugins if necessary.
+ * aNewState should be either "allownow" "allowalways" or "block"
+ */
+ _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) {
+ let permission;
+ let expireType;
+ let expireTime;
+
+ switch (aNewState) {
+ case "allownow":
+ permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
+ expireTime = Date.now() + Services.prefs.getIntPref(kPrefSessionPersistMinutes) * 60 * 1000;
+ break;
+
+ case "allowalways":
+ permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
+ expireTime = Date.now() +
+ Services.prefs.getIntPref(kPrefPersistentDays) * 24 * 60 * 60 * 1000;
+ break;
+
+ case "block":
+ permission = Ci.nsIPermissionManager.PROMPT_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
+ expireTime = 0;
+ break;
+
+ // In case a plugin has already been allowed in another tab, the "continue allowing" button
+ // shouldn't change any permissions but should run the plugin-enablement code below.
+ case "continue":
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected plugin state: " + aNewState));
+ return;
+ }
+
+ let browser = aNotification.browser;
+ let contentWindow = browser.contentWindow;
+ if (aNewState != "continue") {
+ let principal = contentWindow.document.nodePrincipal;
+ Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
+ permission, expireType, expireTime);
+
+ if (aNewState == "block") {
+ return;
+ }
+ }
+
+ // Manually activate the plugins that would have been automatically
+ // activated.
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let plugins = cwu.plugins;
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+ for (let plugin of plugins) {
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // canActivatePlugin will return false if this isn't a known plugin type,
+ // so the pluginHost.getPermissionStringForType call is protected
+ if (gPluginHandler.canActivatePlugin(plugin) &&
+ aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
+ plugin.playPlugin();
+ }
+ }
+ },
+
+ _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPrimaryPlugin) {
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
+
+ let contentWindow = aBrowser.contentWindow;
+ let contentDoc = aBrowser.contentDocument;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ // Pale Moon: cwu.plugins may contain non-plugin <object>s, filter them out
+ let plugins = cwu.plugins.filter(function(plugin) {
+ return (plugin.getContentTypeForMIMEType(plugin.actualType) ==
+ Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+ });
+ if (plugins.length == 0) {
+ if (notification) {
+ PopupNotifications.remove(notification);
+ }
+ return;
+ }
+
+ let icon = 'plugins-notification-icon';
+ for (let plugin of plugins) {
+ let fallbackType = plugin.pluginFallbackType;
+ if (fallbackType == plugin.PLUGIN_VULNERABLE_UPDATABLE ||
+ fallbackType == plugin.PLUGIN_VULNERABLE_NO_UPDATE ||
+ fallbackType == plugin.PLUGIN_BLOCKLISTED) {
+ icon = 'blocked-plugins-notification-icon';
+ break;
+ }
+ if (fallbackType == plugin.PLUGIN_CLICK_TO_PLAY) {
+ let overlay = contentDoc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ if (!overlay || overlay.style.visibility == 'hidden') {
+ icon = 'alert-plugins-notification-icon';
+ }
+ }
+ }
+
+ let dismissed = notification ? notification.dismissed : true;
+ if (aPrimaryPlugin)
+ dismissed = false;
+
+ let primaryPluginPermission = null;
+ if (aPrimaryPlugin) {
+ primaryPluginPermission = this._getPluginInfo(aPrimaryPlugin).permissionString;
+ }
+
+ let options = {
+ dismissed: dismissed,
+ eventCallback: this._clickToPlayNotificationEventCallback,
+ primaryPlugin: primaryPluginPermission
+ };
+ PopupNotifications.show(aBrowser, "click-to-play-plugins",
+ "", icon,
+ null, null, options);
+ },
+
+ // Crashed-plugin observer. Notified once per plugin crash, before events
+ // are dispatched to individual plugin instances.
+ pluginCrashed : function(subject, topic, data) {
+ let propertyBag = subject;
+ if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
+ !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
+ return;
+ },
+
+ // Crashed-plugin event listener. Called for every instance of a
+ // plugin in content.
+ pluginInstanceCrashed: function (plugin, aEvent) {
+ // Ensure the plugin and event are of the right type.
+ if (!(aEvent instanceof Ci.nsIDOMDataContainerEvent))
+ return;
+
+ let submittedReport = aEvent.getData("submittedCrashReport");
+ let doPrompt = true; // XXX followup for .getData("doPrompt");
+ let submitReports = true; // XXX followup for .getData("submitReports");
+ let pluginName = aEvent.getData("pluginName");
+ let pluginDumpID = aEvent.getData("pluginDumpID");
+ let browserDumpID = aEvent.getData("browserDumpID");
+
+ // Remap the plugin name to a more user-presentable form.
+ pluginName = this.makeNicePluginName(pluginName);
+
+ let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]);
+
+ //
+ // Configure the crashed-plugin placeholder.
+ //
+
+ // Force a layout flush so the binding is attached.
+ plugin.clientTop;
+ let doc = plugin.ownerDocument;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ let statusDiv = doc.getAnonymousElementByAttribute(plugin, "class", "submitStatus");
+
+ let crashText = doc.getAnonymousElementByAttribute(plugin, "class", "msgCrashedText");
+ crashText.textContent = messageString;
+
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+
+ let link = doc.getAnonymousElementByAttribute(plugin, "class", "reloadLink");
+ this.addLinkClickCallback(link, "reloadPage", browser);
+
+ let notificationBox = gBrowser.getNotificationBox(browser);
+
+ let isShowing = true;
+
+ // Is the <object>'s size too small to hold what we want to show?
+ if (this.isTooSmall(plugin, overlay)) {
+ // First try hiding the crash report submission UI.
+ statusDiv.removeAttribute("status");
+
+ if (this.isTooSmall(plugin, overlay)) {
+ // Hide the overlay's contents. Use visibility style, so that it doesn't
+ // collapse down to 0x0.
+ overlay.style.visibility = "hidden";
+ isShowing = false;
+ }
+ }
+
+ if (isShowing) {
+ // If a previous plugin on the page was too small and resulted in adding a
+ // notification bar, then remove it because this plugin instance it big
+ // enough to serve as in-content notification.
+ hideNotificationBar();
+ doc.mozNoPluginCrashedNotification = true;
+ } else {
+ // If another plugin on the page was large enough to show our UI, we don't
+ // want to show a notification bar.
+ if (!doc.mozNoPluginCrashedNotification)
+ showNotificationBar(pluginDumpID, browserDumpID);
+ }
+
+ function hideNotificationBar() {
+ let notification = notificationBox.getNotificationWithValue("plugin-crashed");
+ if (notification)
+ notificationBox.removeNotification(notification, true);
+ }
+
+ function showNotificationBar(pluginDumpID, browserDumpID) {
+ // If there's already an existing notification bar, don't do anything.
+ let notification = notificationBox.getNotificationWithValue("plugin-crashed");
+ if (notification)
+ return;
+
+ // Configure the notification bar
+ let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
+ let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
+ let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
+ let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
+ let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
+
+ let buttons = [{
+ label: reloadLabel,
+ accessKey: reloadKey,
+ popup: null,
+ callback: function() { browser.reload(); },
+ }];
+
+ notification = notificationBox.appendNotification(messageString, "plugin-crashed",
+ iconURL, priority, buttons);
+
+ // Add the "learn more" link.
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let link = notification.ownerDocument.createElementNS(XULNS, "label");
+ link.className = "text-link";
+ link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
+ let crashurl = formatURL("app.support.baseURL", true);
+ crashurl += "plugin-crashed-notificationbar";
+ link.href = crashurl;
+
+ let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
+ description.appendChild(link);
+
+ // Remove the notfication when the page is reloaded.
+ doc.defaultView.top.addEventListener("unload", function() {
+ notificationBox.removeNotification(notification);
+ }, false);
+ }
+
+ }
+};
diff --git a/application/palemoon/base/content/browser-sets.inc b/application/palemoon/base/content/browser-sets.inc
new file mode 100644
index 000000000..78cfb7faa
--- /dev/null
+++ b/application/palemoon/base/content/browser-sets.inc
@@ -0,0 +1,436 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+#define XP_GNOME 1
+#endif
+#endif
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_shell" src="chrome://browser/locale/shellservice.properties"/>
+ <stringbundle id="bundle_preferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ </stringbundleset>
+
+ <commandset id="mainCommandSet">
+ <command id="cmd_newNavigator" oncommand="OpenBrowserWindow()"/>
+ <command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
+ <command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
+
+ <command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab();"/>
+ <command id="Browser:OpenFile" oncommand="BrowserOpenFileWindow();"/>
+ <command id="Browser:SavePage" oncommand="saveDocument(window.content.document);"/>
+
+ <command id="Browser:SendLink"
+ oncommand="MailIntegration.sendLinkForWindow(window.content);"/>
+
+ <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
+ <command id="cmd_print" oncommand="PrintUtils.print();"/>
+ <command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
+ <command id="cmd_close" oncommand="BrowserCloseTabOrWindow()"/>
+ <command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()"/>
+ <command id="cmd_ToggleTabsOnTop" oncommand="TabsOnTop.toggle()"/>
+ <command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/>
+ <command id="cmd_restartApplication" oncommand="restart(false);"/>
+ <command id="cmd_quitApplication" oncommand="goQuitApplication()"/>
+
+
+ <commandset id="editMenuCommands"/>
+
+ <command id="View:PageSource" oncommand="BrowserViewSourceOfDocument(content.document);" observes="isImage"/>
+ <command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
+ <command id="View:FullScreen" oncommand="BrowserFullScreen();"/>
+ <command id="cmd_find"
+ oncommand="gFindBar.onFindCommand();"
+ observes="isImage"/>
+ <command id="cmd_findAgain"
+ oncommand="gFindBar.onFindAgainCommand(false);"
+ observes="isImage"/>
+ <command id="cmd_findPrevious"
+ oncommand="gFindBar.onFindAgainCommand(true);"
+ observes="isImage"/>
+ <!-- work-around bug 392512 -->
+ <command id="Browser:AddBookmarkAs"
+ oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
+ <!-- The command disabled state must be manually updated through
+ PlacesCommandHook.updateBookmarkAllTabsCommand() -->
+ <command id="Browser:BookmarkAllTabs"
+ oncommand="PlacesCommandHook.bookmarkCurrentPages();"/>
+ <command id="Browser:Home" oncommand="BrowserHome();"/>
+ <command id="Browser:Back" oncommand="BrowserBack();" disabled="true"/>
+ <command id="Browser:BackOrBackDuplicate" oncommand="BrowserBack(event);" disabled="true">
+ <observes element="Browser:Back" attribute="disabled"/>
+ </command>
+ <command id="Browser:Forward" oncommand="BrowserForward();" disabled="true"/>
+ <command id="Browser:ForwardOrForwardDuplicate" oncommand="BrowserForward(event);" disabled="true">
+ <observes element="Browser:Forward" attribute="disabled"/>
+ </command>
+ <command id="Browser:Stop" oncommand="BrowserStop();" disabled="true"/>
+ <command id="Browser:Reload" oncommand="if (event.shiftKey) BrowserReloadSkipCache(); else BrowserReload()" disabled="true"/>
+ <command id="Browser:ReloadOrDuplicate" oncommand="BrowserReloadOrDuplicate(event)" disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ <command id="Browser:ReloadSkipCache" oncommand="BrowserReloadSkipCache()" disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ <command id="Browser:NextTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(1, true);"/>
+ <command id="Browser:PrevTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(-1, true);"/>
+ <command id="Browser:ShowAllTabs" oncommand="allTabs.open();"/>
+ <command id="Browser:FocusNextFrame" oncommand="focusNextFrame(event);"/>
+ <command id="cmd_fullZoomReduce" oncommand="FullZoom.reduce()"/>
+ <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
+ <command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/>
+ <command id="cmd_fullZoomToggle" oncommand="ZoomManager.toggleZoom();"/>
+ <command id="cmd_gestureRotateLeft" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
+ <command id="cmd_gestureRotateRight" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
+ <command id="cmd_gestureRotateEnd" oncommand="gGestureSupport.rotateEnd()"/>
+ <command id="Browser:OpenLocation" oncommand="openLocation();"/>
+ <command id="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
+
+ <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
+ <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
+#ifdef MOZ_DEVTOOLS
+ <command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
+ <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
+ <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
+ <command id="Tools:DevAppMgr" oncommand="gDevToolsBrowser.openAppManager(gBrowser);" disabled="true" hidden="true"/>
+ <command id="Tools:WebIDE" oncommand="gDevToolsBrowser.openWebIDE();" disabled="true" hidden="true"/>
+ <command id="Tools:ChromeDebugger" oncommand="BrowserToolboxProcess.init();" disabled="true" hidden="true"/>
+ <command id="Tools:BrowserConsole" oncommand="HUDService.openBrowserConsoleOrFocus();"/>
+ <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
+ <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
+ <command id="Tools:Eyedropper" oncommand="openEyedropper();"/>
+#endif
+ <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
+ <command id="Tools:Permissions" oncommand="BrowserOpenPermissionsMgr();"/>
+ <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
+#ifdef MOZ_DEVTOOLS
+ <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
+#endif
+ <command id="Tools:Sanitize"
+ oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
+ <command id="Tools:PrivateBrowsing"
+ oncommand="OpenBrowserWindow({private: true});"/>
+ <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
+ <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
+ <command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/>
+ </commandset>
+
+ <commandset id="placesCommands">
+ <command id="Browser:ShowAllBookmarks"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
+ <command id="Browser:ShowAllHistory"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
+ </commandset>
+
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="viewBookmarksSidebar" autoCheck="false" sidebartitle="&bookmarksButton.label;"
+ type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
+ oncommand="toggleSidebar('viewBookmarksSidebar');"/>
+
+ <!-- for both places and non-places, the sidebar lives at
+ chrome://browser/content/history/history-panel.xul so there are no
+ problems when switching between versions -->
+ <broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
+ type="checkbox" group="sidebar"
+ sidebarurl="chrome://browser/content/history/history-panel.xul"
+ oncommand="toggleSidebar('viewHistorySidebar');"/>
+
+ <broadcaster id="viewWebPanelsSidebar" autoCheck="false"
+ type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
+ oncommand="toggleSidebar('viewWebPanelsSidebar');"/>
+
+ <!-- popup blocking menu items -->
+ <broadcaster id="blockedPopupAllowSite"
+ accesskey="&allowPopups.accesskey;"
+ oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
+ <broadcaster id="blockedPopupEditSettings"
+#ifdef XP_WIN
+ label="&editPopupSettings.label;"
+#else
+ label="&editPopupSettingsUnix.label;"
+#endif
+ accesskey="&editPopupSettings.accesskey;"
+ oncommand="gPopupBlockerObserver.editPopupSettings();"/>
+ <broadcaster id="blockedPopupDontShowMessage"
+ accesskey="&dontShowMessage.accesskey;"
+ type="checkbox"
+ oncommand="gPopupBlockerObserver.dontShowMessage();"/>
+ <broadcaster id="blockedPopupsSeparator"/>
+ <broadcaster id="isImage"/>
+ <broadcaster id="isFrameImage"/>
+ <broadcaster id="singleFeedMenuitemState" disabled="true"/>
+ <broadcaster id="multipleFeedsMenuState" hidden="true"/>
+#ifdef MOZ_SERVICES_SYNC
+ <broadcaster id="sync-setup-state"/>
+ <broadcaster id="sync-syncnow-state"/>
+#endif
+ <broadcaster id="workOfflineMenuitemState"/>
+
+#ifdef MOZ_DEVTOOLS
+ <!-- DevTools broadcasters -->
+ <broadcaster id="devtoolsMenuBroadcaster_DevToolbox"
+ label="&devToolboxMenuItem.label;"
+ type="checkbox" autocheck="false"
+ command="Tools:DevToolbox"
+ key="key_devToolbox"/>
+ <broadcaster id="devtoolsMenuBroadcaster_DevToolbar"
+ label="&devToolbarMenu.label;"
+ type="checkbox" autocheck="false"
+ command="Tools:DevToolbar"
+ key="key_devToolbar"/>
+ <broadcaster id="devtoolsMenuBroadcaster_DevAppMgr"
+ label="&devAppMgrMenu.label;"
+ command="Tools:DevAppMgr"/>
+ <broadcaster id="devtoolsMenuBroadcaster_webide"
+ label="&webide.label;"
+ command="Tools:WebIDE"
+ key="key_webide"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ChromeDebugger"
+ label="&chromeDebuggerMenu.label;"
+ command="Tools:ChromeDebugger"/>
+ <broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
+ label="&browserConsoleCmd.label;"
+ key="key_browserConsole"
+ command="Tools:BrowserConsole"/>
+ <broadcaster id="devtoolsMenuBroadcaster_Scratchpad"
+ label="&scratchpad.label;"
+ command="Tools:Scratchpad"
+ key="key_scratchpad"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ResponsiveUI"
+ label="&responsiveDesignTool.label;"
+ type="checkbox" autocheck="false"
+ command="Tools:ResponsiveUI"
+ key="key_responsiveUI"/>
+ <broadcaster id="devtoolsMenuBroadcaster_Eyedropper"
+ label="&eyedropper.label;"
+ type="checkbox" autocheck="false"
+ command="Tools:Eyedropper"/>
+#endif
+ <broadcaster id="devtoolsMenuBroadcaster_PageSource"
+ label="&pageSourceCmd.label;"
+ key="key_viewSource"
+ command="View:PageSource"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
+ label="&errorConsoleCmd.label;"
+ command="Tools:ErrorConsole"/>
+ <broadcaster id="devtoolsMenuBroadcaster_GetMoreTools"
+ label="&getMoreDevtoolsCmd.label;"
+ oncommand="openUILinkIn(gPrefService.getCharPref('browser.getdevtools.url'), 'tab');"/>
+#ifdef MOZ_DEVTOOLS
+ <broadcaster id="devtoolsMenuBroadcaster_connect"
+ label="&devtoolsConnect.label;"
+ command="Tools:DevToolsConnect"/>
+#endif
+ </broadcasterset>
+
+ <keyset id="mainKeyset">
+ <key id="key_newNavigator"
+ key="&newNavigatorCmd.key;"
+ command="cmd_newNavigator"
+ modifiers="accel"/>
+ <key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTab"/>
+ <key id="focusURLBar" key="&openCmd.commandkey;" command="Browser:OpenLocation"
+ modifiers="accel"/>
+#ifndef XP_MACOSX
+ <key id="focusURLBar2" key="&urlbar.accesskey;" command="Browser:OpenLocation"
+ modifiers="alt"/>
+#endif
+
+#
+# Search Command Key Logic works like this:
+#
+# Unix: Ctrl+K (cross platform binding)
+# Ctrl+J (in case of emacs Ctrl-K conflict)
+# Mac: Cmd+K (cross platform binding)
+# Cmd+Opt+F (platform convention)
+# Win: Ctrl+K (cross platform binding)
+# Ctrl+E (IE compat)
+#
+# We support Ctrl+K on all platforms now and advertise it in the menu since it is
+# our standard - it is a "safe" choice since it is near no harmful keys like "W" as
+# "E" is. People mourning the loss of Ctrl+K for emacs compat can switch their GTK
+# system setting to use emacs emulation, and we should respect it. Focus-Search-Box
+# is a fundamental keybinding and we are maintaining a XP binding so that it is easy
+# for people to switch to Linux.
+#
+ <key id="key_search" key="&searchFocus.commandkey;" command="Tools:Search" modifiers="accel"/>
+#ifdef XP_MACOSX
+ <key id="key_search2" key="&findOnCmd.commandkey;" command="Tools:Search" modifiers="accel,alt"/>
+#endif
+#ifdef XP_WIN
+ <key id="key_search2" key="&searchFocus.commandkey2;" command="Tools:Search" modifiers="accel"/>
+#endif
+#ifdef XP_GNOME
+ <key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
+ <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
+#else
+ <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
+#endif
+ <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
+#ifdef MOZ_DEVTOOLS
+ <key id="key_browserConsole" key="&browserConsoleCmd.commandkey;" command="Tools:BrowserConsole" modifiers="accel,shift"/>
+ <key id="key_devToolbox" keycode="VK_F12" keytext="F12" command="Tools:DevToolbox"/>
+ <key id="key_devToolbar" keycode="&devToolbar.keycode;" modifiers="shift"
+ keytext="&devToolbar.keytext;" command="Tools:DevToolbarFocus"/>
+ <key id="key_responsiveUI" key="&responsiveDesignTool.commandkey;" command="Tools:ResponsiveUI"
+#ifdef XP_MACOSX
+ modifiers="accel,alt"
+#else
+ modifiers="accel,shift"
+#endif
+ />
+ <key id="key_scratchpad" keycode="&scratchpad.keycode;" modifiers="shift"
+ keytext="&scratchpad.keytext;" command="Tools:Scratchpad"/>
+#endif
+ <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile" modifiers="accel"/>
+ <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
+ <key id="printKb" key="&printCmd.commandkey;" command="cmd_print" modifiers="accel"/>
+ <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
+ <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
+ <key id="key_undo"
+ key="&undoCmd.key;"
+ modifiers="accel"/>
+#ifdef XP_UNIX
+ <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
+#else
+ <key id="key_redo" key="&redoCmd.key;" modifiers="accel"/>
+#endif
+ <key id="key_cut"
+ key="&cutCmd.key;"
+ modifiers="accel"/>
+ <key id="key_copy"
+ key="&copyCmd.key;"
+ modifiers="accel"/>
+ <key id="key_paste"
+ key="&pasteCmd.key;"
+ modifiers="accel"/>
+ <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
+ <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
+
+ <key keycode="VK_BACK" command="cmd_handleBackspace"/>
+ <key keycode="VK_BACK" command="cmd_handleShiftBackspace" modifiers="shift"/>
+#ifndef XP_MACOSX
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>
+#else
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="accel" />
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="accel" />
+#endif
+#ifdef XP_UNIX
+ <key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
+ <key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
+#endif
+ <key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
+ <key keycode="VK_F5" command="Browser:Reload"/>
+#ifndef XP_MACOSX
+ <key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
+ <key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
+ <key keycode="VK_F6" command="Browser:FocusNextFrame"/>
+ <key keycode="VK_F6" command="Browser:FocusNextFrame" modifiers="shift"/>
+ <key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
+#else
+ <key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
+ <key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
+ <key keycode="VK_F11" command="View:FullScreen"/>
+#endif
+ <key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
+ <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
+ <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
+#ifndef XP_WIN
+ <key id="key_viewInfo" key="&pageInfoCmd.commandkey;" command="View:PageInfo" modifiers="accel"/>
+#endif
+ <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
+ <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
+ <key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
+ <key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
+ <key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
+
+ <key id="addBookmarkAsKb" key="&bookmarkThisPageCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
+# Accel+Shift+A-F are reserved on GTK
+#ifndef MOZ_WIDGET_GTK
+ <key id="bookmarkAllTabsKb" key="&bookmarkThisPageCmd.commandkey;" oncommand="PlacesCommandHook.bookmarkCurrentPages();" modifiers="accel,shift"/>
+ <key id="manBookmarkKb" key="&bookmarksCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
+#else
+ <key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
+#endif
+ <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+#ifdef XP_WIN
+# Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
+# overridden for other purposes there.
+ <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+#endif
+
+# Navigation cancel keys: Esc performs a cancel on loading (i.e.: stop button equivalent)
+# Shift-Esc (and similar Shift-modified stop on Mac) performs a "superstop": this halts all
+# networking requests, XHR, animated gifs, etc.
+ <key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
+ <key id="key_stop_all" keycode="VK_ESCAPE" modifiers="shift" oncommand="BrowserStop();"/>
+#ifdef XP_MACOSX
+ <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
+ <key id="key_stop_all_mac" modifiers="accel,shift" key="&stopCmd.macCommandKey;" oncommand="BrowserStop();"/>
+#endif
+
+ <key id="key_gotoHistory"
+ key="&historySidebarCmd.commandKey;"
+#ifdef XP_MACOSX
+ modifiers="accel,shift"
+#else
+ modifiers="accel"
+#endif
+ command="viewHistorySidebar"/>
+
+ <key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
+ <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
+
+ <key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift"/>
+
+ <key id="key_switchTextDirection" key="&bidiSwitchTextDirectionItem.commandkey;" command="cmd_switchTextDirection" modifiers="accel,shift" />
+
+ <key id="key_privatebrowsing" command="Tools:PrivateBrowsing" key="&privateBrowsingCmd.commandkey;" modifiers="accel,shift"/>
+ <key id="key_sanitize" command="Tools:Sanitize" keycode="VK_DELETE" modifiers="accel,shift"/>
+#ifdef XP_MACOSX
+ <key id="key_sanitize_mac" command="Tools:Sanitize" keycode="VK_BACK" modifiers="accel,shift"/>
+#endif
+#ifdef XP_UNIX
+ <key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" command="cmd_quitApplication" modifiers="accel"/>
+#endif
+
+#ifdef FULL_BROWSER_WINDOW
+ <key id="key_undoCloseTab" command="History:UndoCloseTab" key="&tabCmd.commandkey;" modifiers="accel,shift"/>
+#endif
+ <key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
+
+#ifdef XP_GNOME
+#define NUM_SELECT_TAB_MODIFIER alt
+#else
+#define NUM_SELECT_TAB_MODIFIER accel
+#endif
+
+#expand <key id="key_selectTab1" oncommand="gBrowser.selectTabAtIndex(0, event);" key="1" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab2" oncommand="gBrowser.selectTabAtIndex(1, event);" key="2" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab3" oncommand="gBrowser.selectTabAtIndex(2, event);" key="3" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab4" oncommand="gBrowser.selectTabAtIndex(3, event);" key="4" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab5" oncommand="gBrowser.selectTabAtIndex(4, event);" key="5" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab6" oncommand="gBrowser.selectTabAtIndex(5, event);" key="6" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab7" oncommand="gBrowser.selectTabAtIndex(6, event);" key="7" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab8" oncommand="gBrowser.selectTabAtIndex(7, event);" key="8" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectLastTab" oncommand="gBrowser.selectTabAtIndex(-1, event);" key="9" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+
+ <key id="key_toggleAddonBar" command="Browser:ToggleAddonBar" key="&toggleAddonBarCmd.key;" modifiers="accel"/>
+
+ </keyset>
+
+# Used by baseMenuOverlay
+#ifdef XP_MACOSX
+ <commandset id="baseMenuCommandSet" />
+#endif
+ <keyset id="baseMenuKeyset" />
diff --git a/application/palemoon/base/content/browser-syncui.js b/application/palemoon/base/content/browser-syncui.js
new file mode 100644
index 000000000..fc8c7f016
--- /dev/null
+++ b/application/palemoon/base/content/browser-syncui.js
@@ -0,0 +1,470 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// gSyncUI handles updating the tools menu
+let gSyncUI = {
+ _obs: ["weave:service:sync:start",
+ "weave:service:sync:delayed",
+ "weave:service:quota:remaining",
+ "weave:service:setup-complete",
+ "weave:service:login:start",
+ "weave:service:login:finish",
+ "weave:service:logout:finish",
+ "weave:service:start-over",
+ "weave:ui:login:error",
+ "weave:ui:sync:error",
+ "weave:ui:sync:finish",
+ "weave:ui:clear-error",
+ ],
+
+ _unloaded: false,
+
+ init: function SUI_init() {
+ // Proceed to set up the UI if Sync has already started up.
+ // Otherwise we'll do it when Sync is firing up.
+ let xps = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ if (xps.ready) {
+ this.initUI();
+ return;
+ }
+
+ Services.obs.addObserver(this, "weave:service:ready", true);
+
+ // Remove the observer if the window is closed before the observer
+ // was triggered.
+ window.addEventListener("unload", function onUnload() {
+ gSyncUI._unloaded = true;
+ window.removeEventListener("unload", onUnload, false);
+ Services.obs.removeObserver(gSyncUI, "weave:service:ready");
+
+ if (Weave.Status.ready) {
+ gSyncUI._obs.forEach(function(topic) {
+ Services.obs.removeObserver(gSyncUI, topic);
+ });
+ }
+ }, false);
+ },
+
+ initUI: function SUI_initUI() {
+ // If this is a browser window?
+ if (gBrowser) {
+ this._obs.push("weave:notification:added");
+ }
+
+ this._obs.forEach(function(topic) {
+ Services.obs.addObserver(this, topic, true);
+ }, this);
+
+ if (gBrowser && Weave.Notifications.notifications.length) {
+ this.initNotifications();
+ }
+ this.updateUI();
+ },
+
+ initNotifications: function SUI_initNotifications() {
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let notificationbox = document.createElementNS(XULNS, "notificationbox");
+ notificationbox.id = "sync-notifications";
+ notificationbox.setAttribute("flex", "1");
+
+ let bottombox = document.getElementById("browser-bottombox");
+ bottombox.insertBefore(notificationbox, bottombox.firstChild);
+
+ // Force a style flush to ensure that our binding is attached.
+ notificationbox.clientTop;
+
+ // notificationbox will listen to observers from now on.
+ Services.obs.removeObserver(this, "weave:notification:added");
+ },
+
+ _wasDelayed: false,
+
+ _needsSetup: function SUI__needsSetup() {
+ let firstSync = "";
+ try {
+ firstSync = Services.prefs.getCharPref("services.sync.firstSync");
+ } catch (e) { }
+ return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
+ firstSync == "notReady";
+ },
+
+ updateUI: function SUI_updateUI() {
+ let needsSetup = this._needsSetup();
+ document.getElementById("sync-setup-state").hidden = !needsSetup;
+ document.getElementById("sync-syncnow-state").hidden = needsSetup;
+
+ if (!gBrowser)
+ return;
+
+ let button = document.getElementById("sync-button");
+ if (!button)
+ return;
+
+ button.removeAttribute("status");
+ this._updateLastSyncTime();
+ if (needsSetup)
+ button.removeAttribute("tooltiptext");
+ },
+
+
+ // Functions called by observers
+ onActivityStart: function SUI_onActivityStart() {
+ if (!gBrowser)
+ return;
+
+ let button = document.getElementById("sync-button");
+ if (!button)
+ return;
+
+ button.setAttribute("status", "active");
+ },
+
+ onSyncDelay: function SUI_onSyncDelay() {
+ // basically, we want to just inform users that stuff is going to take a while
+ let title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+ let description = this._stringBundle.GetStringFromName("error.sync.no_node_found");
+ let buttons = [new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
+ function() { gSyncUI.openServerStatus(); return true; }
+ )];
+ let notification = new Weave.Notification(
+ title, description, null, Weave.Notifications.PRIORITY_INFO, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ this._wasDelayed = true;
+ },
+
+ onLoginFinish: function SUI_onLoginFinish() {
+ // Clear out any login failure notifications
+ let title = this._stringBundle.GetStringFromName("error.login.title");
+ this.clearError(title);
+ },
+
+ onSetupComplete: function SUI_onSetupComplete() {
+ this.onLoginFinish();
+ },
+
+ onLoginError: function SUI_onLoginError() {
+ // if login fails, any other notifications are essentially moot
+ Weave.Notifications.removeAll();
+
+ // if we haven't set up the client, don't show errors
+ if (this._needsSetup()) {
+ this.updateUI();
+ return;
+ }
+
+ let title = this._stringBundle.GetStringFromName("error.login.title");
+
+ let description;
+ if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
+ // Convert to days
+ let lastSync =
+ Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
+ description =
+ this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
+ } else {
+ let reason = Weave.Utils.getErrorString(Weave.Status.login);
+ description =
+ this._stringBundle.formatStringFromName("error.sync.description", [reason], 1);
+ }
+
+ let buttons = [];
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.login.prefs.label"),
+ this._stringBundle.GetStringFromName("error.login.prefs.accesskey"),
+ function() { gSyncUI.openPrefs(); return true; }
+ ));
+
+ let notification = new Weave.Notification(title, description, null,
+ Weave.Notifications.PRIORITY_WARNING, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ this.updateUI();
+ },
+
+ onLogout: function SUI_onLogout() {
+ this.updateUI();
+ },
+
+ onStartOver: function SUI_onStartOver() {
+ this.clearError();
+ },
+
+ onQuotaNotice: function onQuotaNotice(subject, data) {
+ let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
+ let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
+ let buttons = [];
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"),
+ function() { gSyncUI.openQuotaDialog(); return true; }
+ ));
+
+ let notification = new Weave.Notification(
+ title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ },
+
+ openServerStatus: function () {
+ let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
+ window.openUILinkIn(statusURL, "tab");
+ },
+
+ // Commands
+ doSync: function SUI_doSync() {
+ setTimeout(function() Weave.Service.errorHandler.syncAndReportErrors(), 0);
+ },
+
+ handleToolbarButton: function SUI_handleStatusbarButton() {
+ if (this._needsSetup())
+ this.openSetup();
+ else
+ this.doSync();
+ },
+
+ //XXXzpao should be part of syncCommon.js - which we might want to make a module...
+ // To be fixed in a followup (bug 583366)
+
+ /**
+ * Invoke the Sync setup wizard.
+ *
+ * @param wizardType
+ * Indicates type of wizard to launch:
+ * null -- regular set up wizard
+ * "pair" -- pair a device first
+ * "reset" -- reset sync
+ */
+
+ openSetup: function SUI_openSetup(wizardType) {
+ let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+ if (win)
+ win.focus();
+ else {
+ window.openDialog("chrome://browser/content/sync/setup.xul",
+ "weaveSetup", "centerscreen,chrome,resizable=no",
+ wizardType);
+ }
+ },
+
+ openAddDevice: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return;
+
+ let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+ if (win)
+ win.focus();
+ else
+ window.openDialog("chrome://browser/content/sync/addDevice.xul",
+ "syncAddDevice", "centerscreen,chrome,resizable=no");
+ },
+
+ openQuotaDialog: function SUI_openQuotaDialog() {
+ let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
+ if (win)
+ win.focus();
+ else
+ Services.ww.activeWindow.openDialog(
+ "chrome://browser/content/sync/quota.xul", "",
+ "centerscreen,chrome,dialog,modal");
+ },
+
+ openPrefs: function SUI_openPrefs() {
+ openPreferences("paneSync");
+ },
+
+
+ // Helpers
+ _updateLastSyncTime: function SUI__updateLastSyncTime() {
+ if (!gBrowser)
+ return;
+
+ let syncButton = document.getElementById("sync-button");
+ if (!syncButton)
+ return;
+
+ let lastSync;
+ try {
+ lastSync = Services.prefs.getCharPref("services.sync.lastSync");
+ }
+ catch (e) { };
+ if (!lastSync || this._needsSetup()) {
+ syncButton.removeAttribute("tooltiptext");
+ return;
+ }
+
+ // Show the day-of-week and time (HH:MM) of last sync
+ let lastSyncDate = new Date(lastSync).toLocaleFormat("%a %H:%M");
+ let lastSyncLabel =
+ this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDate], 1);
+
+ syncButton.setAttribute("tooltiptext", lastSyncLabel);
+ },
+
+ clearError: function SUI_clearError(errorString) {
+ Weave.Notifications.removeAll(errorString);
+ this.updateUI();
+ },
+
+ onSyncFinish: function SUI_onSyncFinish() {
+ let title = this._stringBundle.GetStringFromName("error.sync.title");
+
+ // Clear out sync failures on a successful sync
+ this.clearError(title);
+
+ if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
+ title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+ this.clearError(title);
+ this._wasDelayed = false;
+ }
+ },
+
+ onSyncError: function SUI_onSyncError() {
+ let title = this._stringBundle.GetStringFromName("error.sync.title");
+
+ if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
+ this.onLoginError();
+ return;
+ }
+
+ let description;
+ if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
+ // Convert to days
+ let lastSync =
+ Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
+ description =
+ this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
+ } else {
+ let error = Weave.Utils.getErrorString(Weave.Status.sync);
+ description =
+ this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
+ }
+ let priority = Weave.Notifications.PRIORITY_WARNING;
+ let buttons = [];
+
+ // Check if the client is outdated in some way
+ let outdated = Weave.Status.sync == Weave.VERSION_OUT_OF_DATE;
+ for (let [engine, reason] in Iterator(Weave.Status.engines))
+ outdated = outdated || reason == Weave.VERSION_OUT_OF_DATE;
+
+ if (outdated) {
+ description = this._stringBundle.GetStringFromName(
+ "error.sync.needUpdate.description");
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.needUpdate.label"),
+ this._stringBundle.GetStringFromName("error.sync.needUpdate.accesskey"),
+ function() {
+ window.openUILinkIn(Services.prefs.getCharPref("services.sync.outdated.url"), "tab");
+ return true;
+ }
+ ));
+ }
+ else if (Weave.Status.sync == Weave.OVER_QUOTA) {
+ description = this._stringBundle.GetStringFromName(
+ "error.sync.quota.description");
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName(
+ "error.sync.viewQuotaButton.label"),
+ this._stringBundle.GetStringFromName(
+ "error.sync.viewQuotaButton.accesskey"),
+ function() { gSyncUI.openQuotaDialog(); return true; } )
+ );
+ }
+ else if (Weave.Status.enforceBackoff) {
+ priority = Weave.Notifications.PRIORITY_INFO;
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
+ function() { gSyncUI.openServerStatus(); return true; }
+ ));
+ }
+ else {
+ priority = Weave.Notifications.PRIORITY_INFO;
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"),
+ function() { gSyncUI.doSync(); return true; }
+ ));
+ }
+
+ let notification =
+ new Weave.Notification(title, description, null, priority, buttons);
+ Weave.Notifications.replaceTitle(notification);
+
+ if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
+ title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+ Weave.Notifications.removeAll(title);
+ this._wasDelayed = false;
+ }
+
+ this.updateUI();
+ },
+
+ observe: function SUI_observe(subject, topic, data) {
+ if (this._unloaded) {
+ Cu.reportError("SyncUI observer called after unload: " + topic);
+ return;
+ }
+
+ switch (topic) {
+ case "weave:service:sync:start":
+ this.onActivityStart();
+ break;
+ case "weave:ui:sync:finish":
+ this.onSyncFinish();
+ break;
+ case "weave:ui:sync:error":
+ this.onSyncError();
+ break;
+ case "weave:service:sync:delayed":
+ this.onSyncDelay();
+ break;
+ case "weave:service:quota:remaining":
+ this.onQuotaNotice();
+ break;
+ case "weave:service:setup-complete":
+ this.onSetupComplete();
+ break;
+ case "weave:service:login:start":
+ this.onActivityStart();
+ break;
+ case "weave:service:login:finish":
+ this.onLoginFinish();
+ break;
+ case "weave:ui:login:error":
+ this.onLoginError();
+ break;
+ case "weave:service:logout:finish":
+ this.onLogout();
+ break;
+ case "weave:service:start-over":
+ this.onStartOver();
+ break;
+ case "weave:service:ready":
+ this.initUI();
+ break;
+ case "weave:notification:added":
+ this.initNotifications();
+ break;
+ case "weave:ui:clear-error":
+ this.clearError();
+ break;
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ])
+};
+
+XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
+ //XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
+ // but for now just make it work
+ return Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle("chrome://weave/locale/services/sync.properties");
+});
+
diff --git a/application/palemoon/base/content/browser-tabPreviews.js b/application/palemoon/base/content/browser-tabPreviews.js
new file mode 100644
index 000000000..eaae78ba8
--- /dev/null
+++ b/application/palemoon/base/content/browser-tabPreviews.js
@@ -0,0 +1,1051 @@
+/*
+#ifdef 0
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#endif
+ */
+
+/**
+ * Tab previews utility, produces thumbnails
+ */
+var tabPreviews = {
+ aspectRatio: 0.5625, // 16:9
+
+ get width() {
+ delete this.width;
+ return this.width = Math.ceil(screen.availWidth / 5.75);
+ },
+
+ get height() {
+ delete this.height;
+ return this.height = Math.round(this.width * this.aspectRatio);
+ },
+
+ init: function tabPreviews_init() {
+ if (this._selectedTab)
+ return;
+ this._selectedTab = gBrowser.selectedTab;
+
+ gBrowser.tabContainer.addEventListener("TabSelect", this, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
+ },
+
+ get: function tabPreviews_get(aTab) {
+ let uri = aTab.linkedBrowser.currentURI.spec;
+
+ if (aTab.__thumbnail_lastURI &&
+ aTab.__thumbnail_lastURI != uri) {
+ aTab.__thumbnail = null;
+ aTab.__thumbnail_lastURI = null;
+ }
+
+ if (aTab.__thumbnail)
+ return aTab.__thumbnail;
+
+ if (aTab.getAttribute("pending") == "true") {
+ let img = new Image;
+ img.src = PageThumbs.getThumbnailURL(uri);
+ return img;
+ }
+
+ return this.capture(aTab, !aTab.hasAttribute("busy"));
+ },
+
+ capture: function tabPreviews_capture(aTab, aStore) {
+ var thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ thumbnail.mozOpaque = true;
+ thumbnail.height = this.height;
+ thumbnail.width = this.width;
+
+ var ctx = thumbnail.getContext("2d");
+ var win = aTab.linkedBrowser.contentWindow;
+ var snippetWidth = win.innerWidth * .6;
+ var scale = this.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(win, win.scrollX, win.scrollY,
+ snippetWidth, snippetWidth * this.aspectRatio, "rgb(255,255,255)");
+
+ if (aStore &&
+ aTab.linkedBrowser /* bug 795608: the tab may got removed while drawing the thumbnail */) {
+ aTab.__thumbnail = thumbnail;
+ aTab.__thumbnail_lastURI = aTab.linkedBrowser.currentURI.spec;
+ }
+
+ return thumbnail;
+ },
+
+ handleEvent: function tabPreviews_handleEvent(event) {
+ switch (event.type) {
+ case "TabSelect":
+ if (this._selectedTab &&
+ this._selectedTab.parentNode &&
+ !this._pendingUpdate) {
+ // Generate a thumbnail for the tab that was selected.
+ // The timeout keeps the UI snappy and prevents us from generating thumbnails
+ // for tabs that will be closed. During that timeout, don't generate other
+ // thumbnails in case multiple TabSelect events occur fast in succession.
+ this._pendingUpdate = true;
+ setTimeout(function (self, aTab) {
+ self._pendingUpdate = false;
+ if (aTab.parentNode &&
+ !aTab.hasAttribute("busy") &&
+ !aTab.hasAttribute("pending"))
+ self.capture(aTab, true);
+ }, 2000, this, this._selectedTab);
+ }
+ this._selectedTab = event.target;
+ break;
+ case "SSTabRestored":
+ this.capture(event.target, true);
+ break;
+ }
+ }
+};
+
+var tabPreviewPanelHelper = {
+ opening: function (host) {
+ host.panel.hidden = false;
+
+ var handler = this._generateHandler(host);
+ host.panel.addEventListener("popupshown", handler, false);
+ host.panel.addEventListener("popuphiding", handler, false);
+
+ host._prevFocus = document.commandDispatcher.focusedElement;
+ },
+ _generateHandler: function (host) {
+ var self = this;
+ return function (event) {
+ if (event.target == host.panel) {
+ host.panel.removeEventListener(event.type, arguments.callee, false);
+ self["_" + event.type](host);
+ }
+ };
+ },
+ _popupshown: function (host) {
+ if ("setupGUI" in host)
+ host.setupGUI();
+ },
+ _popuphiding: function (host) {
+ if ("suspendGUI" in host)
+ host.suspendGUI();
+
+ if (host._prevFocus) {
+ Cc["@mozilla.org/focus-manager;1"]
+ .getService(Ci.nsIFocusManager)
+ .setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
+ host._prevFocus = null;
+ } else
+ gBrowser.selectedBrowser.focus();
+
+ if (host.tabToSelect) {
+ gBrowser.selectedTab = host.tabToSelect;
+ host.tabToSelect = null;
+ }
+ }
+};
+
+/**
+ * Ctrl-Tab panel
+ */
+var ctrlTab = {
+ get panel () {
+ delete this.panel;
+ return this.panel = document.getElementById("ctrlTab-panel");
+ },
+ get showAllButton () {
+ delete this.showAllButton;
+ return this.showAllButton = document.getElementById("ctrlTab-showAll");
+ },
+ get previews () {
+ delete this.previews;
+ return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
+ },
+ get recentlyUsedLimit () {
+ delete this.recentlyUsedLimit;
+ return this.recentlyUsedLimit = gPrefService.getIntPref("browser.ctrlTab.recentlyUsedLimit");
+ },
+ get keys () {
+ var keys = {};
+ ["close", "find", "selectAll"].forEach(function (key) {
+ keys[key] = document.getElementById("key_" + key)
+ .getAttribute("key")
+ .toLocaleLowerCase().charCodeAt(0);
+ });
+ delete this.keys;
+ return this.keys = keys;
+ },
+ _selectedIndex: 0,
+ get selected () this._selectedIndex < 0 ?
+ document.activeElement :
+ this.previews.item(this._selectedIndex),
+ get isOpen () this.panel.state == "open" || this.panel.state == "showing" || this._timer,
+ get tabCount () this.tabList.length,
+ get tabPreviewCount () Math.min(this.previews.length - 1, this.tabCount),
+ get canvasWidth () Math.min(tabPreviews.width,
+ Math.ceil(screen.availWidth * .85 / this.tabPreviewCount)),
+ get canvasHeight () Math.round(this.canvasWidth * tabPreviews.aspectRatio),
+
+ get tabList () {
+ if (this._tabList)
+ return this._tabList;
+
+ // Using gBrowser.tabs instead of gBrowser.visibleTabs, as the latter
+ // exlcudes closing tabs, breaking the following loop in case the the
+ // selected tab is closing.
+ let list = Array.filter(gBrowser.tabs, function (tab) !tab.hidden);
+
+ // Rotate the list until the selected tab is first
+ while (!list[0].selected)
+ list.push(list.shift());
+
+ list = list.filter(function (tab) !tab.closing);
+
+ if (this.recentlyUsedLimit != 0) {
+ let recentlyUsedTabs = [];
+ for (let tab of this._recentlyUsedTabs) {
+ if (!tab.hidden && !tab.closing) {
+ recentlyUsedTabs.push(tab);
+ if (this.recentlyUsedLimit > 0 && recentlyUsedTabs.length >= this.recentlyUsedLimit)
+ break;
+ }
+ }
+ for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
+ list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
+ list.unshift(recentlyUsedTabs[i]);
+ }
+ }
+
+ return this._tabList = list;
+ },
+
+ init: function ctrlTab_init() {
+ if (!this._recentlyUsedTabs) {
+ tabPreviews.init();
+
+ this._recentlyUsedTabs = [gBrowser.selectedTab];
+ this._init(true);
+ }
+ },
+
+ uninit: function ctrlTab_uninit() {
+ this._recentlyUsedTabs = null;
+ this._init(false);
+ },
+
+ prefName: "browser.ctrlTab.previews",
+ readPref: function ctrlTab_readPref() {
+ var enable =
+ gPrefService.getBoolPref(this.prefName) &&
+ (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
+ !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
+
+ if (enable)
+ this.init();
+ else
+ this.uninit();
+ },
+ observe: function (aSubject, aTopic, aPrefName) {
+ this.readPref();
+ },
+
+ updatePreviews: function ctrlTab_updatePreviews() {
+ for (let i = 0; i < this.previews.length; i++)
+ this.updatePreview(this.previews[i], this.tabList[i]);
+
+ var showAllLabel = gNavigatorBundle.getString("ctrlTab.showAll.label");
+ this.showAllButton.label =
+ PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
+ },
+
+ updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
+ if (aPreview == this.showAllButton)
+ return;
+
+ aPreview._tab = aTab;
+
+ if (aPreview.firstChild)
+ aPreview.removeChild(aPreview.firstChild);
+ if (aTab) {
+ let canvasWidth = this.canvasWidth;
+ let canvasHeight = this.canvasHeight;
+ aPreview.appendChild(tabPreviews.get(aTab));
+ aPreview.setAttribute("label", aTab.label);
+ aPreview.setAttribute("tooltiptext", aTab.label);
+ aPreview.setAttribute("crop", aTab.crop);
+ aPreview.setAttribute("canvaswidth", canvasWidth);
+ aPreview.setAttribute("canvasstyle",
+ "max-width:" + canvasWidth + "px;" +
+ "min-width:" + canvasWidth + "px;" +
+ "max-height:" + canvasHeight + "px;" +
+ "min-height:" + canvasHeight + "px;");
+ if (aTab.image)
+ aPreview.setAttribute("image", aTab.image);
+ else
+ aPreview.removeAttribute("image");
+ aPreview.hidden = false;
+ } else {
+ aPreview.hidden = true;
+ aPreview.removeAttribute("label");
+ aPreview.removeAttribute("tooltiptext");
+ aPreview.removeAttribute("image");
+ }
+ },
+
+ advanceFocus: function ctrlTab_advanceFocus(aForward) {
+ let selectedIndex = Array.indexOf(this.previews, this.selected);
+ do {
+ selectedIndex += aForward ? 1 : -1;
+ if (selectedIndex < 0)
+ selectedIndex = this.previews.length - 1;
+ else if (selectedIndex >= this.previews.length)
+ selectedIndex = 0;
+ } while (this.previews[selectedIndex].hidden);
+
+ if (this._selectedIndex == -1) {
+ // Focus is already in the panel.
+ this.previews[selectedIndex].focus();
+ } else {
+ this._selectedIndex = selectedIndex;
+ }
+
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ this._openPanel();
+ }
+ },
+
+ _mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
+ if (this._trackMouseOver)
+ aPreview.focus();
+ },
+
+ pick: function ctrlTab_pick(aPreview) {
+ if (!this.tabCount)
+ return;
+
+ var select = (aPreview || this.selected);
+
+ if (select == this.showAllButton)
+ this.showAllTabs();
+ else
+ this.close(select._tab);
+ },
+
+ showAllTabs: function ctrlTab_showAllTabs(aPreview) {
+ this.close();
+ document.getElementById("Browser:ShowAllTabs").doCommand();
+ },
+
+ remove: function ctrlTab_remove(aPreview) {
+ if (aPreview._tab)
+ gBrowser.removeTab(aPreview._tab);
+ },
+
+ attachTab: function ctrlTab_attachTab(aTab, aPos) {
+ if (aPos == 0)
+ this._recentlyUsedTabs.unshift(aTab);
+ else if (aPos)
+ this._recentlyUsedTabs.splice(aPos, 0, aTab);
+ else
+ this._recentlyUsedTabs.push(aTab);
+ },
+ detachTab: function ctrlTab_detachTab(aTab) {
+ var i = this._recentlyUsedTabs.indexOf(aTab);
+ if (i >= 0)
+ this._recentlyUsedTabs.splice(i, 1);
+ },
+
+ open: function ctrlTab_open() {
+ if (this.isOpen)
+ return;
+
+ allTabs.close();
+
+ document.addEventListener("keyup", this, true);
+
+ this.updatePreviews();
+ this._selectedIndex = 1;
+
+ // Add a slight delay before showing the UI, so that a quick
+ // "ctrl-tab" keypress just flips back to the MRU tab.
+ this._timer = setTimeout(function (self) {
+ self._timer = null;
+ self._openPanel();
+ }, 200, this);
+ },
+
+ _openPanel: function ctrlTab_openPanel() {
+ tabPreviewPanelHelper.opening(this);
+
+ this.panel.width = Math.min(screen.availWidth * .99,
+ this.canvasWidth * 1.25 * this.tabPreviewCount);
+ var estimateHeight = this.canvasHeight * 1.25 + 75;
+ this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
+ screen.availTop + (screen.availHeight - estimateHeight) / 2,
+ false);
+ },
+
+ close: function ctrlTab_close(aTabToSelect) {
+ if (!this.isOpen)
+ return;
+
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ this.suspendGUI();
+ if (aTabToSelect)
+ gBrowser.selectedTab = aTabToSelect;
+ return;
+ }
+
+ this.tabToSelect = aTabToSelect;
+ this.panel.hidePopup();
+ },
+
+ setupGUI: function ctrlTab_setupGUI() {
+ this.selected.focus();
+ this._selectedIndex = -1;
+
+ // Track mouse movement after a brief delay so that the item that happens
+ // to be under the mouse pointer initially won't be selected unintentionally.
+ this._trackMouseOver = false;
+ setTimeout(function (self) {
+ if (self.isOpen)
+ self._trackMouseOver = true;
+ }, 0, this);
+ },
+
+ suspendGUI: function ctrlTab_suspendGUI() {
+ document.removeEventListener("keyup", this, true);
+
+ Array.forEach(this.previews, function (preview) {
+ this.updatePreview(preview, null);
+ }, this);
+
+ this._tabList = null;
+ },
+
+ onKeyPress: function ctrlTab_onKeyPress(event) {
+ var isOpen = this.isOpen;
+
+ if (isOpen) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ switch (event.keyCode) {
+ case event.DOM_VK_TAB:
+ if (event.ctrlKey && !event.altKey && !event.metaKey) {
+ if (isOpen) {
+ this.advanceFocus(!event.shiftKey);
+ } else if (!event.shiftKey) {
+ event.preventDefault();
+ event.stopPropagation();
+ let tabs = gBrowser.visibleTabs;
+ if (tabs.length > 2) {
+ this.open();
+ } else if (tabs.length == 2) {
+ let index = tabs[0].selected ? 1 : 0;
+ gBrowser.selectedTab = tabs[index];
+ }
+ }
+ }
+ break;
+ default:
+ if (isOpen && event.ctrlKey) {
+ if (event.keyCode == event.DOM_VK_DELETE) {
+ this.remove(this.selected);
+ break;
+ }
+ switch (event.charCode) {
+ case this.keys.close:
+ this.remove(this.selected);
+ break;
+ case this.keys.find:
+ case this.keys.selectAll:
+ this.showAllTabs();
+ break;
+ }
+ }
+ }
+ },
+
+ removeClosingTabFromUI: function ctrlTab_removeClosingTabFromUI(aTab) {
+ if (this.tabCount == 2) {
+ this.close();
+ return;
+ }
+
+ this._tabList = null;
+ this.updatePreviews();
+
+ if (this.selected.hidden)
+ this.advanceFocus(false);
+ if (this.selected == this.showAllButton)
+ this.advanceFocus(false);
+
+ // If the current tab is removed, another tab can steal our focus.
+ if (aTab.selected && this.panel.state == "open") {
+ setTimeout(function (selected) {
+ selected.focus();
+ }, 0, this.selected);
+ }
+ },
+
+ handleEvent: function ctrlTab_handleEvent(event) {
+ switch (event.type) {
+ case "TabAttrModified":
+ // tab attribute modified (e.g. label, crop, busy, image, selected)
+ for (let i = this.previews.length - 1; i >= 0; i--) {
+ if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
+ this.updatePreview(this.previews[i], event.target);
+ break;
+ }
+ }
+ break;
+ case "TabSelect":
+ this.detachTab(event.target);
+ this.attachTab(event.target, 0);
+ break;
+ case "TabOpen":
+ this.attachTab(event.target, 1);
+ break;
+ case "TabClose":
+ this.detachTab(event.target);
+ if (this.isOpen)
+ this.removeClosingTabFromUI(event.target);
+ break;
+ case "keypress":
+ this.onKeyPress(event);
+ break;
+ case "keyup":
+ if (event.keyCode == event.DOM_VK_CONTROL)
+ this.pick();
+ break;
+ }
+ },
+
+ _init: function ctrlTab__init(enable) {
+ var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
+
+ var tabContainer = gBrowser.tabContainer;
+ tabContainer[toggleEventListener]("TabOpen", this, false);
+ tabContainer[toggleEventListener]("TabAttrModified", this, false);
+ tabContainer[toggleEventListener]("TabSelect", this, false);
+ tabContainer[toggleEventListener]("TabClose", this, false);
+
+ document[toggleEventListener]("keypress", this, false);
+ gBrowser.mTabBox.handleCtrlTab = !enable;
+
+ // If we're not running, hide the "Show All Tabs" menu item,
+ // as Shift+Ctrl+Tab will be handled by the tab bar.
+ document.getElementById("menu_showAllTabs").hidden = !enable;
+
+ // Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
+ // Show All Tabs.
+ var key_showAllTabs = document.getElementById("key_showAllTabs");
+ if (enable)
+ key_showAllTabs.removeAttribute("disabled");
+ else
+ key_showAllTabs.setAttribute("disabled", "true");
+ }
+};
+
+
+/**
+ * All Tabs panel
+ */
+var allTabs = {
+ get panel () {
+ delete this.panel;
+ return this.panel = document.getElementById("allTabs-panel");
+ },
+ get filterField () {
+ delete this.filterField;
+ return this.filterField = document.getElementById("allTabs-filter");
+ },
+ get container () {
+ delete this.container;
+ return this.container = document.getElementById("allTabs-container");
+ },
+ get tabCloseButton () {
+ delete this.tabCloseButton;
+ return this.tabCloseButton = document.getElementById("allTabs-tab-close-button");
+ },
+ get toolbarButton() document.getElementById("alltabs-button"),
+ get previews () this.container.getElementsByClassName("allTabs-preview"),
+ get isOpen () this.panel.state == "open" || this.panel.state == "showing",
+
+ init: function allTabs_init() {
+ if (this._initiated)
+ return;
+ this._initiated = true;
+
+ tabPreviews.init();
+
+ Array.forEach(gBrowser.tabs, function (tab) {
+ this._addPreview(tab);
+ }, this);
+
+ gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+ gBrowser.tabContainer.addEventListener("TabAttrModified", this, false);
+ gBrowser.tabContainer.addEventListener("TabMove", this, false);
+ gBrowser.tabContainer.addEventListener("TabClose", this, false);
+ },
+
+ uninit: function allTabs_uninit() {
+ if (!this._initiated)
+ return;
+
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ gBrowser.tabContainer.removeEventListener("TabAttrModified", this, false);
+ gBrowser.tabContainer.removeEventListener("TabMove", this, false);
+ gBrowser.tabContainer.removeEventListener("TabClose", this, false);
+
+ while (this.container.hasChildNodes())
+ this.container.removeChild(this.container.firstChild);
+
+ this._initiated = false;
+ },
+
+ prefName: "browser.allTabs.previews",
+ readPref: function allTabs_readPref() {
+ var allTabsButton = this.toolbarButton;
+ if (!allTabsButton)
+ return;
+
+ if (gPrefService.getBoolPref(this.prefName)) {
+ allTabsButton.removeAttribute("type");
+ allTabsButton.setAttribute("command", "Browser:ShowAllTabs");
+ } else {
+ allTabsButton.setAttribute("type", "menu");
+ allTabsButton.removeAttribute("command");
+ allTabsButton.removeAttribute("oncommand");
+ }
+ },
+ observe: function (aSubject, aTopic, aPrefName) {
+ this.readPref();
+ },
+
+ pick: function allTabs_pick(aPreview) {
+ if (!aPreview)
+ aPreview = this._firstVisiblePreview;
+ if (aPreview)
+ this.tabToSelect = aPreview._tab;
+
+ this.close();
+ },
+
+ closeTab: function allTabs_closeTab(event) {
+ this.filterField.focus();
+ gBrowser.removeTab(event.currentTarget._targetPreview._tab);
+ },
+
+ filter: function allTabs_filter() {
+ if (this._currentFilter == this.filterField.value)
+ return;
+
+ this._currentFilter = this.filterField.value;
+
+ var filter = this._currentFilter.split(/\s+/g);
+ this._visible = 0;
+ Array.forEach(this.previews, function (preview) {
+ var tab = preview._tab;
+ var matches = 0;
+ if (filter.length && !tab.hidden) {
+ let tabstring = tab.linkedBrowser.currentURI.spec;
+ try {
+ tabstring = decodeURI(tabstring);
+ } catch (e) {}
+ tabstring = tab.label + " " + tab.label.toLocaleLowerCase() + " " + tabstring;
+ for (let i = 0; i < filter.length; i++)
+ matches += tabstring.includes(filter[i]);
+ }
+ if (matches < filter.length || tab.hidden) {
+ preview.hidden = true;
+ }
+ else {
+ this._visible++;
+ this._updatePreview(preview);
+ preview.hidden = false;
+ }
+ }, this);
+
+ this._reflow();
+ },
+
+ open: function allTabs_open() {
+ var allTabsButton = this.toolbarButton;
+ if (allTabsButton &&
+ allTabsButton.getAttribute("type") == "menu") {
+ // Without setTimeout, the menupopup won't stay open when invoking
+ // "View > Show All Tabs" and the menu bar auto-hides.
+ setTimeout(function () {
+ allTabsButton.open = true;
+ }, 0);
+ return;
+ }
+
+ this.init();
+
+ if (this.isOpen)
+ return;
+
+ this._maxPanelHeight = Math.max(gBrowser.clientHeight, screen.availHeight / 2);
+ this._maxPanelWidth = Math.max(gBrowser.clientWidth, screen.availWidth / 2);
+
+ this.filter();
+
+ tabPreviewPanelHelper.opening(this);
+
+ this.panel.popupBoxObject.setConsumeRollupEvent(PopupBoxObject.ROLLUP_NO_CONSUME);
+ this.panel.openPopup(gBrowser, "overlap", 0, 0, false, true);
+ },
+
+ close: function allTabs_close() {
+ this.panel.hidePopup();
+ },
+
+ setupGUI: function allTabs_setupGUI() {
+ this.filterField.focus();
+ this.filterField.placeholder = this.filterField.tooltipText;
+
+ this.panel.addEventListener("keypress", this, false);
+ this.panel.addEventListener("keypress", this, true);
+ this._browserCommandSet.addEventListener("command", this, false);
+
+ // When the panel is open, a second click on the all tabs button should
+ // close the panel but not re-open it.
+ document.getElementById("Browser:ShowAllTabs").setAttribute("disabled", "true");
+ },
+
+ suspendGUI: function allTabs_suspendGUI() {
+ this.filterField.placeholder = "";
+ this.filterField.value = "";
+ this._currentFilter = null;
+
+ this._updateTabCloseButton();
+
+ this.panel.removeEventListener("keypress", this, false);
+ this.panel.removeEventListener("keypress", this, true);
+ this._browserCommandSet.removeEventListener("command", this, false);
+
+ setTimeout(function () {
+ document.getElementById("Browser:ShowAllTabs").removeAttribute("disabled");
+ }, 300);
+ },
+
+ handleEvent: function allTabs_handleEvent(event) {
+ if (event.type.startsWith("Tab")) {
+ var tab = event.target;
+ if (event.type != "TabOpen")
+ var preview = this._getPreview(tab);
+ }
+ switch (event.type) {
+ case "TabAttrModified":
+ // tab attribute modified (e.g. label, crop, busy, image)
+ if (!preview.hidden)
+ this._updatePreview(preview);
+ break;
+ case "TabOpen":
+ if (this.isOpen)
+ this.close();
+ this._addPreview(tab);
+ break;
+ case "TabMove":
+ let siblingPreview = tab.nextSibling &&
+ this._getPreview(tab.nextSibling);
+ if (siblingPreview)
+ siblingPreview.parentNode.insertBefore(preview, siblingPreview);
+ else
+ this.container.lastChild.appendChild(preview);
+ if (this.isOpen && !preview.hidden) {
+ this._reflow();
+ preview.focus();
+ }
+ break;
+ case "TabClose":
+ this._removePreview(preview);
+ break;
+ case "keypress":
+ this._onKeyPress(event);
+ break;
+ case "command":
+ if (event.target.id != "Browser:ShowAllTabs") {
+ // Close the panel when there's a browser command executing in the background.
+ this.close();
+ }
+ break;
+ }
+ },
+
+ _visible: 0,
+ _currentFilter: null,
+ get _stack () {
+ delete this._stack;
+ return this._stack = document.getElementById("allTabs-stack");
+ },
+ get _browserCommandSet () {
+ delete this._browserCommandSet;
+ return this._browserCommandSet = document.getElementById("mainCommandSet");
+ },
+ get _previewLabelHeight () {
+ delete this._previewLabelHeight;
+ return this._previewLabelHeight = parseInt(getComputedStyle(this.previews[0], "").lineHeight);
+ },
+
+ get _visiblePreviews ()
+ Array.filter(this.previews, function (preview) !preview.hidden),
+
+ get _firstVisiblePreview () {
+ if (this._visible == 0)
+ return null;
+ var previews = this.previews;
+ for (let i = 0; i < previews.length; i++) {
+ if (!previews[i].hidden)
+ return previews[i];
+ }
+ return null;
+ },
+
+ _reflow: function allTabs_reflow() {
+ this._updateTabCloseButton();
+
+ const CONTAINER_MAX_WIDTH = this._maxPanelWidth * .95;
+ const CONTAINER_MAX_HEIGHT = this._maxPanelHeight - 35;
+ // the size of the whole preview relative to the thumbnail
+ const REL_PREVIEW_THUMBNAIL = 1.2;
+ const REL_PREVIEW_HEIGHT_WIDTH = tabPreviews.height / tabPreviews.width;
+ const PREVIEW_MAX_WIDTH = tabPreviews.width * REL_PREVIEW_THUMBNAIL;
+
+ var rows, previewHeight, previewWidth, outerHeight;
+ this._columns = Math.floor(CONTAINER_MAX_WIDTH / PREVIEW_MAX_WIDTH);
+ do {
+ rows = Math.ceil(this._visible / this._columns);
+ previewWidth = Math.min(PREVIEW_MAX_WIDTH,
+ Math.round(CONTAINER_MAX_WIDTH / this._columns));
+ previewHeight = Math.round(previewWidth * REL_PREVIEW_HEIGHT_WIDTH);
+ outerHeight = previewHeight + this._previewLabelHeight;
+ } while (rows * outerHeight > CONTAINER_MAX_HEIGHT && ++this._columns);
+
+ var outerWidth = previewWidth;
+ {
+ let innerWidth = Math.ceil(previewWidth / REL_PREVIEW_THUMBNAIL);
+ let innerHeight = Math.ceil(previewHeight / REL_PREVIEW_THUMBNAIL);
+ var canvasStyle = "max-width:" + innerWidth + "px;" +
+ "min-width:" + innerWidth + "px;" +
+ "max-height:" + innerHeight + "px;" +
+ "min-height:" + innerHeight + "px;";
+ }
+
+ var previews = Array.slice(this.previews);
+
+ while (this.container.hasChildNodes())
+ this.container.removeChild(this.container.firstChild);
+ for (let i = rows || 1; i > 0; i--)
+ this.container.appendChild(document.createElement("hbox"));
+
+ var row = this.container.firstChild;
+ var colCount = 0;
+ previews.forEach(function (preview) {
+ if (!preview.hidden &&
+ ++colCount > this._columns) {
+ row = row.nextSibling;
+ colCount = 1;
+ }
+ preview.setAttribute("minwidth", outerWidth);
+ preview.setAttribute("height", outerHeight);
+ preview.setAttribute("canvasstyle", canvasStyle);
+ preview.removeAttribute("closebuttonhover");
+ row.appendChild(preview);
+ }, this);
+
+ this._stack.width = this._maxPanelWidth;
+ this.container.width = Math.ceil(outerWidth * Math.min(this._columns, this._visible));
+ this.container.left = Math.round((this._maxPanelWidth - this.container.width) / 2);
+ this.container.maxWidth = this._maxPanelWidth - this.container.left;
+ this.container.maxHeight = rows * outerHeight;
+ },
+
+ _addPreview: function allTabs_addPreview(aTab) {
+ var preview = document.createElement("button");
+ preview.className = "allTabs-preview";
+ preview._tab = aTab;
+ this.container.lastChild.appendChild(preview);
+ },
+
+ _removePreview: function allTabs_removePreview(aPreview) {
+ var updateUI = (this.isOpen && !aPreview.hidden);
+ aPreview._tab = null;
+ aPreview.parentNode.removeChild(aPreview);
+ if (updateUI) {
+ this._visible--;
+ this._reflow();
+ this.filterField.focus();
+ }
+ },
+
+ _getPreview: function allTabs_getPreview(aTab) {
+ var previews = this.previews;
+ for (let i = 0; i < previews.length; i++)
+ if (previews[i]._tab == aTab)
+ return previews[i];
+ return null;
+ },
+
+ _updateTabCloseButton: function allTabs_updateTabCloseButton(event) {
+ if (event && event.target == this.tabCloseButton)
+ return;
+
+ if (this.tabCloseButton._targetPreview) {
+ if (event && event.target == this.tabCloseButton._targetPreview)
+ return;
+
+ this.tabCloseButton._targetPreview.removeAttribute("closebuttonhover");
+ }
+
+ if (event &&
+ event.target.parentNode.parentNode == this.container &&
+ (event.target._tab.previousSibling || event.target._tab.nextSibling)) {
+ let canvas = event.target.firstChild.getBoundingClientRect();
+ let container = this.container.getBoundingClientRect();
+ let tabCloseButton = this.tabCloseButton.getBoundingClientRect();
+ let alignLeft = getComputedStyle(this.panel, "").direction == "rtl";
+#ifdef XP_MACOSX
+ alignLeft = !alignLeft;
+#endif
+ this.tabCloseButton.left = canvas.left -
+ container.left +
+ parseInt(this.container.left) +
+ (alignLeft ? 0 :
+ canvas.width - tabCloseButton.width);
+ this.tabCloseButton.top = canvas.top - container.top;
+ this.tabCloseButton._targetPreview = event.target;
+ this.tabCloseButton.style.visibility = "visible";
+ event.target.setAttribute("closebuttonhover", "true");
+ } else {
+ this.tabCloseButton.style.visibility = "hidden";
+ this.tabCloseButton.left = this.tabCloseButton.top = 0;
+ this.tabCloseButton._targetPreview = null;
+ }
+ },
+
+ _updatePreview: function allTabs_updatePreview(aPreview) {
+ aPreview.setAttribute("label", aPreview._tab.label);
+ aPreview.setAttribute("tooltiptext", aPreview._tab.label);
+ aPreview.setAttribute("crop", aPreview._tab.crop);
+ if (aPreview._tab.image)
+ aPreview.setAttribute("image", aPreview._tab.image);
+ else
+ aPreview.removeAttribute("image");
+
+ var thumbnail = tabPreviews.get(aPreview._tab);
+ if (aPreview.firstChild) {
+ if (aPreview.firstChild == thumbnail)
+ return;
+ aPreview.removeChild(aPreview.firstChild);
+ }
+ aPreview.appendChild(thumbnail);
+ },
+
+ _onKeyPress: function allTabs_onKeyPress(event) {
+ if (event.eventPhase == event.CAPTURING_PHASE) {
+ this._onCapturingKeyPress(event);
+ return;
+ }
+
+ if (event.keyCode == event.DOM_VK_ESCAPE) {
+ this.close();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ if (event.target == this.filterField) {
+ switch (event.keyCode) {
+ case event.DOM_VK_UP:
+ if (this._visible) {
+ let previews = this._visiblePreviews;
+ let columns = Math.min(previews.length, this._columns);
+ previews[Math.floor(previews.length / columns) * columns - 1].focus();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ case event.DOM_VK_DOWN:
+ if (this._visible) {
+ this._firstVisiblePreview.focus();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ }
+ }
+ },
+
+ _onCapturingKeyPress: function allTabs_onCapturingKeyPress(event) {
+ switch (event.keyCode) {
+ case event.DOM_VK_UP:
+ case event.DOM_VK_DOWN:
+ if (event.target != this.filterField)
+ this._advanceFocusVertically(event);
+ break;
+ case event.DOM_VK_RETURN:
+ if (event.target == this.filterField) {
+ this.filter();
+ this.pick();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ }
+ },
+
+ _advanceFocusVertically: function allTabs_advanceFocusVertically(event) {
+ var preview = document.activeElement;
+ if (!preview || preview.parentNode.parentNode != this.container)
+ return;
+
+ event.stopPropagation();
+
+ var up = (event.keyCode == event.DOM_VK_UP);
+ var previews = this._visiblePreviews;
+
+ if (up && preview == previews[0]) {
+ this.filterField.focus();
+ return;
+ }
+
+ var i = previews.indexOf(preview);
+ var columns = Math.min(previews.length, this._columns);
+ var column = i % columns;
+ var row = Math.floor(i / columns);
+
+ function newIndex() row * columns + column;
+ function outOfBounds() newIndex() >= previews.length;
+
+ if (up) {
+ row--;
+ if (row < 0) {
+ let rows = Math.ceil(previews.length / columns);
+ row = rows - 1;
+ column--;
+ if (outOfBounds())
+ row--;
+ }
+ } else {
+ row++;
+ if (outOfBounds()) {
+ if (column == columns - 1) {
+ this.filterField.focus();
+ return;
+ }
+ row = 0;
+ column++;
+ }
+ }
+ previews[newIndex()].focus();
+ }
+};
diff --git a/application/palemoon/base/content/browser-tabPreviews.xml b/application/palemoon/base/content/browser-tabPreviews.xml
new file mode 100644
index 000000000..e957649e7
--- /dev/null
+++ b/application/palemoon/base/content/browser-tabPreviews.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+
+# -*- Mode: HTML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<bindings id="tabPreviews"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+ <binding id="ctrlTab-preview" extends="chrome://global/content/bindings/button.xml#button-base">
+ <content pack="center">
+ <xul:stack>
+ <xul:vbox class="ctrlTab-preview-inner" align="center" pack="center"
+ xbl:inherits="width=canvaswidth">
+ <xul:hbox class="tabPreview-canvas" xbl:inherits="style=canvasstyle">
+ <children/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=label,crop" class="plain"/>
+ </xul:vbox>
+ <xul:hbox class="ctrlTab-favicon-container" xbl:inherits="hidden=noicon">
+ <xul:image class="ctrlTab-favicon" xbl:inherits="src=image"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+ <handlers>
+ <handler event="mouseover" action="ctrlTab._mouseOverFocus(this);"/>
+ <handler event="command" action="ctrlTab.pick(this);"/>
+ <handler event="click" button="1" action="ctrlTab.remove(this);"/>
+#ifdef XP_MACOSX
+# Control+click is a right click on OS X
+ <handler event="click" button="2" action="ctrlTab.pick(this);"/>
+#endif
+ </handlers>
+ </binding>
+
+ <binding id="allTabs-preview" extends="chrome://global/content/bindings/button.xml#button-base">
+ <content pack="center" align="center">
+ <xul:stack>
+ <xul:vbox class="allTabs-preview-inner" align="center" pack="center">
+ <xul:hbox class="tabPreview-canvas" xbl:inherits="style=canvasstyle">
+ <children/>
+ </xul:hbox>
+ <xul:label flex="1" xbl:inherits="value=label,crop" class="allTabs-preview-label plain"/>
+ </xul:vbox>
+ <xul:hbox class="allTabs-favicon-container">
+ <xul:image class="allTabs-favicon" xbl:inherits="src=image"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+ <handlers>
+ <handler event="command" action="allTabs.pick(this);"/>
+ <handler event="click" button="1" action="gBrowser.removeTab(this._tab);"/>
+
+ <handler event="dragstart"><![CDATA[
+ event.dataTransfer.mozSetDataAt("application/x-moz-node", this._tab, 0);
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ let tab = event.dataTransfer.mozGetDataAt("application/x-moz-node", 0);
+ if (tab && tab.parentNode == gBrowser.tabContainer)
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ let tab = event.dataTransfer.mozGetDataAt("application/x-moz-node", 0);
+ if (tab && tab.parentNode == gBrowser.tabContainer) {
+ let newIndex = Array.indexOf(gBrowser.tabs, this._tab);
+ gBrowser.moveTabTo(tab, newIndex);
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/application/palemoon/base/content/browser-thumbnails.js b/application/palemoon/base/content/browser-thumbnails.js
new file mode 100644
index 000000000..dbe33e3ed
--- /dev/null
+++ b/application/palemoon/base/content/browser-thumbnails.js
@@ -0,0 +1,198 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * Keeps thumbnails of open web pages up-to-date.
+ */
+let gBrowserThumbnails = {
+ /**
+ * Pref that controls whether we can store SSL content on disk
+ */
+ PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
+
+ _captureDelayMS: 1000,
+
+ /**
+ * Used to keep track of disk_cache_ssl preference
+ */
+ _sslDiskCacheEnabled: null,
+
+ /**
+ * Map of capture() timeouts assigned to their browsers.
+ */
+ _timeouts: null,
+
+ /**
+ * List of tab events we want to listen for.
+ */
+ _tabEvents: ["TabClose", "TabSelect"],
+
+ init: function Thumbnails_init() {
+ // Bug 863512 - Make page thumbnails work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ try {
+ if (Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled"))
+ return;
+ } catch (e) {}
+
+ PageThumbs.addExpirationFilter(this);
+ gBrowser.addTabsProgressListener(this);
+ Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
+
+ this._sslDiskCacheEnabled =
+ Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+
+ this._tabEvents.forEach(function (aEvent) {
+ gBrowser.tabContainer.addEventListener(aEvent, this, false);
+ }, this);
+
+ this._timeouts = new WeakMap();
+ },
+
+ uninit: function Thumbnails_uninit() {
+ // Bug 863512 - Make page thumbnails work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ PageThumbs.removeExpirationFilter(this);
+ gBrowser.removeTabsProgressListener(this);
+ Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
+
+ this._tabEvents.forEach(function (aEvent) {
+ gBrowser.tabContainer.removeEventListener(aEvent, this, false);
+ }, this);
+ },
+
+ handleEvent: function Thumbnails_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "scroll":
+ let browser = aEvent.currentTarget;
+ if (this._timeouts.has(browser))
+ this._delayedCapture(browser);
+ break;
+ case "TabSelect":
+ this._delayedCapture(aEvent.target.linkedBrowser);
+ break;
+ case "TabClose": {
+ this._clearTimeout(aEvent.target.linkedBrowser);
+ break;
+ }
+ }
+ },
+
+ observe: function Thumbnails_observe() {
+ this._sslDiskCacheEnabled =
+ Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+ },
+
+ filterForThumbnailExpiration:
+ function Thumbnails_filterForThumbnailExpiration(aCallback) {
+ aCallback([browser.currentURI.spec for (browser of gBrowser.browsers)]);
+ },
+
+ /**
+ * State change progress listener for all tabs.
+ */
+ onStateChange: function Thumbnails_onStateChange(aBrowser, aWebProgress,
+ aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
+ this._delayedCapture(aBrowser);
+ },
+
+ _capture: function Thumbnails_capture(aBrowser) {
+ if (this._shouldCapture(aBrowser))
+ PageThumbs.captureAndStore(aBrowser);
+ },
+
+ _delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
+ if (this._timeouts.has(aBrowser))
+ clearTimeout(this._timeouts.get(aBrowser));
+ else
+ aBrowser.addEventListener("scroll", this, true);
+
+ let timeout = setTimeout(function () {
+ this._clearTimeout(aBrowser);
+ this._capture(aBrowser);
+ }.bind(this), this._captureDelayMS);
+
+ this._timeouts.set(aBrowser, timeout);
+ },
+
+ _shouldCapture: function Thumbnails_shouldCapture(aBrowser) {
+ // Capture only if it's the currently selected tab.
+ if (aBrowser != gBrowser.selectedBrowser)
+ return false;
+
+ // Don't capture in per-window private browsing mode.
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ return false;
+
+ let doc = aBrowser.contentDocument;
+
+ // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
+ // that currently regresses Talos SVG tests.
+ if (doc instanceof SVGDocument || doc instanceof XMLDocument)
+ return false;
+
+ // There's no point in taking screenshot of loading pages.
+ if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+ return false;
+
+ // Don't take screenshots of about: pages.
+ if (aBrowser.currentURI.schemeIs("about"))
+ return false;
+
+ let channel = aBrowser.docShell.currentDocumentChannel;
+
+ // No valid document channel. We shouldn't take a screenshot.
+ if (!channel)
+ return false;
+
+ // Don't take screenshots of internally redirecting about: pages.
+ // This includes error pages.
+ let uri = channel.originalURI;
+ if (uri.schemeIs("about"))
+ return false;
+
+ let httpChannel;
+ try {
+ httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (e) { /* Not an HTTP channel. */ }
+
+ if (httpChannel) {
+ // Continue only if we have a 2xx status code.
+ try {
+ if (Math.floor(httpChannel.responseStatus / 100) != 2)
+ return false;
+ } catch (e) {
+ // Can't get response information from the httpChannel
+ // because mResponseHead is not available.
+ return false;
+ }
+
+ // Cache-Control: no-store.
+ if (httpChannel.isNoStoreResponse())
+ return false;
+
+ // Don't capture HTTPS pages unless the user explicitly enabled it.
+ if (uri.schemeIs("https") && !this._sslDiskCacheEnabled)
+ return false;
+ }
+
+ return true;
+ },
+
+ _clearTimeout: function Thumbnails_clearTimeout(aBrowser) {
+ if (this._timeouts.has(aBrowser)) {
+ aBrowser.removeEventListener("scroll", this, false);
+ clearTimeout(this._timeouts.get(aBrowser));
+ this._timeouts.delete(aBrowser);
+ }
+ }
+};
diff --git a/application/palemoon/base/content/browser-title.css b/application/palemoon/base/content/browser-title.css
new file mode 100644
index 000000000..66b5e6731
--- /dev/null
+++ b/application/palemoon/base/content/browser-title.css
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#main-window::after {
+ content: attr(title);
+ line-height: 50px;
+ max-height: 50px;
+ overflow: -moz-hidden-unscrollable;
+ pointer-events: none;
+ position: fixed;
+ word-wrap: break-word;
+ -moz-hyphens: auto;
+ color: CaptionText;
+ font-weight: bold;
+ text-align: left;
+}
+
+#main-window:-moz-window-inactive::after {
+ color: InactiveCaptionText;
+}
+
+/* Win10 doesn't respond to inactive caption, so dim it instead */
+@media (-moz-os-version: windows-win10) {
+ #main-window:-moz-window-inactive::after {
+ opacity: 0.5;
+ }
+}
+
+/* Hide in fullscreen/TiT mode */
+#main-window[inFullscreen="true"]::after,
+#main-window[sizemode="maximized"][tabsintitlebar="true"]::after,
+#main-window:not([chromemargin])::after {
+ opacity: 0 !important;
+}
+
+
+#main-window::after {
+ padding: 0 132px; /* AppMenu button/wincontrols width offset */
+ left: 0;
+ right: 0;
+}
+
+#main-window[privatebrowsingmode=temporary]::after {
+ padding: 0px 132px 0px 152px; /* AppMenu button width offset for PB mode */
+}
+
+#main-window[sizemode="normal"]::after {
+ left: -12px;
+ right: -12px;
+}
+
+/* Lightweight Themes */
+
+#main-window:-moz-lwtheme::after {
+ color: inherit;
+ text-shadow: inherit;
+}
+
+/* Windows Classic theme */
+
+@media all and (-moz-windows-classic) {
+
+ #main-window::after {
+ top: -13px;
+ text-shadow: none !important;
+ background-position: 2px 18px;
+ }
+
+}
+
+
+/* Windows Aero (Vista, non-glass 7/8) */
+
+@media all and (-moz-windows-theme: aero) {
+
+ #main-window::after {
+ top: -11px;
+ font-weight: normal;
+ text-shadow: none;
+ background-position: 2px 17px;
+ }
+
+ #main-window[sizemode="maximized"]::after {
+ top: -7px;
+ }
+
+}
+
+
+/* Windows Aero Glass */
+
+@media (-moz-windows-glass) {
+
+ #main-window::after {
+ top: -13px;
+ color: black;
+ text-shadow: rgba(255,255,255,.6) 7px -1px 12px,
+ rgba(255,255,255,.6) 6px -1px 13px,
+ rgba(255,255,255,.9) 5px -1px 14px,
+ rgba(255,255,255,.6) -7px -1px 12px,
+ rgba(255,255,255,.6) -6px -1px 13px,
+ rgba(255,255,255,.9) -5px -1px 14px;
+ z-index: -99999;
+ background-position: 2px 18px;
+ font-weight: bold;
+ }
+
+ #main-window[sizemode="maximized"]::after {
+ top: -7px;
+ }
+
+ #main-window:-moz-window-inactive::after {
+ opacity: .9;
+ color: black;
+ text-shadow: rgba(255,255,255,.7) 7px -1px 12px,
+ rgba(255,255,255,.5) 6px -1px 13px,
+ rgba(255,255,255,.5) 5px -1px 14px,
+ rgba(255,255,255,.7) -7px -1px 12px,
+ rgba(255,255,255,.5) -6px -1px 13px,
+ rgba(255,255,255,.5) -5px -1px 14px;
+ }
+
+}
+
+
+/* Generic other themes */
+
+@media all and (-moz-windows-theme: generic) {
+
+ #main-window::after {
+ font-family: trebuchet MS;
+ font-size: 13px;
+ text-shadow: 1px 1px rgba(0, 0, 0, .2);
+ top: -9px;
+ background-position: 2px 16px;
+ }
+
+ #main-window:-moz-window-inactive::after {
+ text-shadow: none;
+ }
+
+}
+
+
+/* Compositor style for Win 8/10 */
+
+@media all and (-moz-windows-compositor) {
+ @media not all and (-moz-windows-glass) {
+
+ #main-window::after {
+ background-position: 4px 17px;
+ top: -13px;
+ }
+
+ @media (-moz-os-version: windows-win8) {
+ #main-window::after {
+ font-size: 15px;
+ text-align: center;
+ }
+
+ #main-window[darkwindowframe="true"]:not(:-moz-window-inactive):not(:-moz-lwtheme)::after {
+ /* Dark window frame/accent color on Win 8 */
+ color: white;
+ }
+ }
+
+ @media (-moz-os-version: windows-win10) {
+ #main-window::after {
+ text-align: left;
+ }
+
+ @media (-moz-windows-accent-color-applies: 0) {
+ #main-window:not(:-moz-window-inactive):not(:-moz-lwtheme)::after {
+ /* Default Windows 10 styling is white - apply black text styling */
+ color: black;
+ }
+ }
+
+ @media (-moz-windows-accent-color-applies) {
+ #main-window:not(:-moz-window-inactive):not(:-moz-lwtheme)::after {
+ /* Accent color is applied - use the associated text styling */
+ color: -moz-win-accentcolortext;
+ }
+ }
+ }
+
+ #main-window[sizemode="maximized"]::after {
+ top: -5px;
+ }
+ }
+
+}
+
+
+/* Hide for small windows */
+
+@media not all and (min-width: 320px) {
+
+ #main-window::after {
+ opacity: 0 !important;
+ }
+
+} \ No newline at end of file
diff --git a/application/palemoon/base/content/browser-webrtcUI.js b/application/palemoon/base/content/browser-webrtcUI.js
new file mode 100644
index 000000000..a6c9008ca
--- /dev/null
+++ b/application/palemoon/base/content/browser-webrtcUI.js
@@ -0,0 +1,55 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+let WebrtcIndicator = {
+ init: function () {
+ let temp = {};
+ Cu.import("resource:///modules/webrtcUI.jsm", temp);
+ this.UIModule = temp.webrtcUI;
+
+ this.updateButton();
+ },
+
+ get button() {
+ delete this.button;
+ return this.button = document.getElementById("webrtc-status-button");
+ },
+
+ updateButton: function () {
+ this.button.hidden = !this.UIModule.showGlobalIndicator;
+ },
+
+ fillPopup: function (aPopup) {
+ this._menuitemData = new WeakMap;
+ for (let streamData of this.UIModule.activeStreams) {
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", streamData.uri);
+ menuitem.setAttribute("tooltiptext", streamData.uri);
+
+ this._menuitemData.set(menuitem, streamData);
+
+ aPopup.appendChild(menuitem);
+ }
+ },
+
+ clearPopup: function (aPopup) {
+ while (aPopup.lastChild)
+ aPopup.removeChild(aPopup.lastChild);
+ },
+
+ menuCommand: function (aMenuitem) {
+ let streamData = this._menuitemData.get(aMenuitem);
+ if (!streamData)
+ return;
+
+ let browserWindow = streamData.browser.ownerDocument.defaultView;
+ if (streamData.tab) {
+ browserWindow.gBrowser.selectedTab = streamData.tab;
+ } else {
+ streamData.browser.focus();
+ }
+ browserWindow.focus();
+ }
+}
diff --git a/application/palemoon/base/content/browser.css b/application/palemoon/base/content/browser.css
new file mode 100644
index 000000000..2c8ba3e69
--- /dev/null
+++ b/application/palemoon/base/content/browser.css
@@ -0,0 +1,693 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+searchbar {
+ -moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
+}
+
+browser[remote="true"] {
+ -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
+}
+
+tabbrowser {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser");
+}
+
+.tabbrowser-tabs {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
+}
+
+#tabbrowser-tabs:not([overflow="true"]) ~ #alltabs-button,
+#tabbrowser-tabs:not([overflow="true"]) + #new-tab-button,
+#tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
+#TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
+#TabsToolbar[customizing="true"] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
+ visibility: collapse;
+}
+
+#alltabs-button { /* Pale Moon: Always show this button! (less jumpy UI) */
+ visibility: visible !important;
+}
+
+#tabbrowser-tabs:not([overflow="true"])[using-closing-tabs-spacer] ~ #alltabs-button {
+ visibility: hidden; /* temporary space to keep a tab's close button under the cursor */
+}
+
+.tabbrowser-tab {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
+}
+
+.tabbrowser-tab:not([pinned]) {
+ -moz-box-flex: 100;
+ max-width: 250px;
+ min-width: 100px;
+ width: 0;
+ transition: min-width 175ms ease-out,
+ max-width 200ms ease-out,
+ opacity 80ms ease-out 20ms /* hide the tab for the first 20ms of the max-width transition */;
+}
+
+.tabbrowser-tab:not([pinned]):not([fadein]) {
+ max-width: 0.1px;
+ min-width: 0.1px;
+ opacity: 0 !important;
+ transition: min-width 175ms ease-out,
+ max-width 200ms ease-out,
+ opacity 80ms ease-out 180ms /* hide the tab for the last 20ms of the max-width transition */;
+}
+
+.tab-throbber:not([fadein]):not([pinned]),
+.tab-label:not([fadein]):not([pinned]),
+.tab-icon-image:not([fadein]):not([pinned]),
+.tab-close-button:not([fadein]):not([pinned]) {
+ display: none;
+}
+
+.tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
+ position: fixed !important;
+ display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
+}
+
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
+ position: relative;
+ z-index: 2;
+ pointer-events: none; /* avoid blocking dragover events on scroll buttons */
+}
+
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
+ transition: transform 200ms ease-out;
+}
+
+#alltabs-popup {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
+}
+
+toolbar[printpreview="true"] {
+ -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
+}
+
+#toolbar-menubar {
+ -moz-box-ordinal-group: 5;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ -moz-box-ordinal-group: 50;
+}
+
+#TabsToolbar {
+ -moz-box-ordinal-group: 100;
+}
+
+#TabsToolbar[tabsontop="true"] {
+ -moz-box-ordinal-group: 10;
+}
+
+%ifdef CAN_DRAW_IN_TITLEBAR
+#main-window[inFullscreen] > #titlebar,
+#main-window[inFullscreen] .titlebar-placeholder,
+#main-window:not([tabsintitlebar]) .titlebar-placeholder {
+ display: none;
+}
+
+#titlebar {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
+}
+
+#titlebar-spacer {
+ pointer-events: none;
+}
+
+#main-window[tabsintitlebar] #appmenu-button-container,
+#main-window[tabsintitlebar] #titlebar-buttonbox {
+ position: relative;
+}
+%endif
+
+#main-window[inDOMFullscreen] #sidebar-box,
+#main-window[inDOMFullscreen] #sidebar-splitter {
+ visibility: collapse;
+}
+
+.bookmarks-toolbar-customize,
+#wrapper-personal-bookmarks > #personal-bookmarks > #PlacesToolbar > hbox > #PlacesToolbarItems {
+ display: none;
+}
+
+#wrapper-personal-bookmarks[place="toolbar"] > #personal-bookmarks > #PlacesToolbar > .bookmarks-toolbar-customize {
+ display: -moz-box;
+}
+
+#main-window[disablechrome] #navigator-toolbox[tabsontop="true"] > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ visibility: collapse;
+}
+
+#wrapper-urlbar-container #urlbar-container > #urlbar > toolbarbutton,
+#urlbar-container:not([combined]) > #urlbar > toolbarbutton,
+#urlbar-container[combined] + #reload-button + #stop-button,
+#urlbar-container[combined] + #reload-button,
+toolbar:not([mode="icons"]) > #urlbar-container > #urlbar > toolbarbutton,
+toolbar[mode="icons"] > #urlbar-container > #urlbar > #urlbar-reload-button:not([displaystop]) + #urlbar-stop-button,
+toolbar[mode="icons"] > #urlbar-container > #urlbar > #urlbar-reload-button[displaystop],
+toolbar[mode="icons"] > #reload-button:not([displaystop]) + #stop-button,
+toolbar[mode="icons"] > #reload-button[displaystop] {
+ visibility: collapse;
+}
+
+#feed-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+#feed-menu > .feed-menuitem:-moz-locale-dir(rtl) {
+ direction: rtl;
+}
+
+#main-window:-moz-lwtheme {
+ background-repeat: no-repeat;
+ background-position: top right;
+}
+
+%ifdef XP_MACOSX
+#main-window[inFullscreen="true"] {
+ padding-top: 0; /* override drawintitlebar="true" */
+}
+%endif
+
+#browser-bottombox[lwthemefooter="true"] {
+ background-repeat: no-repeat;
+ background-position: bottom left;
+}
+
+splitmenu {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#splitmenu");
+}
+
+.splitmenu-menuitem {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem");
+ list-style-image: inherit;
+ -moz-image-region: inherit;
+}
+
+.splitmenu-menuitem[iconic="true"] {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
+}
+
+.splitmenu-menu > .menu-text,
+:-moz-any(.splitmenu-menu, .splitmenu-menuitem) > .menu-accel-container,
+#appmenu-editmenu > .menu-text,
+#appmenu-editmenu > .menu-accel-container {
+ display: none;
+}
+
+.menuitem-tooltip {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-tooltip");
+}
+
+.menuitem-iconic-tooltip,
+.menuitem-tooltip[type="checkbox"],
+.menuitem-tooltip[type="radio"] {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip");
+}
+
+%ifdef MENUBAR_CAN_AUTOHIDE
+%ifndef CAN_DRAW_IN_TITLEBAR
+#appmenu-toolbar-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+%endif
+
+#appmenu_offlineModeRecovery:not([checked=true]) {
+ display: none;
+}
+%endif
+
+/* Hide menu elements intended for keyboard access support */
+#main-menubar[openedwithkey=false] .show-only-for-keyboard {
+ display: none;
+}
+
+/* ::::: location bar ::::: */
+#urlbar {
+ -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar);
+}
+
+.ac-url-text:-moz-locale-dir(rtl),
+.ac-title:-moz-locale-dir(rtl) > description {
+ direction: ltr !important;
+}
+
+/* For results that are actions, their description text is shown instead of
+ the URL - this needs to follow the locale's direction, unlike URLs. */
+panel:not([noactions]) > richlistbox > richlistitem[type~="action"]:-moz-locale-dir(rtl) > .ac-url-box {
+ direction: rtl;
+}
+
+panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-url > .ac-action-text,
+panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-action-icon {
+ visibility: collapse;
+}
+
+panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-url > .ac-url-text {
+ visibility: visible;
+}
+
+#urlbar:not([actiontype]) > #urlbar-display-box {
+ display: none;
+}
+
+#wrapper-urlbar-container > #urlbar-container > #urlbar {
+ -moz-user-input: disabled;
+ cursor: -moz-grab;
+}
+
+#PopupAutoComplete {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup");
+}
+
+#PopupAutoCompleteRichResult {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
+}
+
+/* Pale Moon: Address bar: Feeds */
+#ub-feed-button > .button-box > .box-inherit > .button-text,
+#ub-feed-button > .button-box > .button-menu-dropmarker {
+ display: none;
+}
+
+#ub-feed-menu > .feed-menuitem:-moz-locale-dir(rtl) {
+ direction: rtl;
+}
+
+
+#urlbar-container[combined] > #urlbar > #urlbar-icons > #go-button,
+#urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon:not(#go-button),
+#urlbar[pageproxystate="valid"] > #urlbar-icons > #go-button,
+#urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
+#urlbar[pageproxystate="valid"] > #urlbar-go-button,
+#urlbar:not([focused="true"]) > #urlbar-go-button {
+ visibility: collapse;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
+ visibility: collapse;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box {
+ pointer-events: none;
+}
+
+#identity-icon-labels {
+ max-width: 18em;
+}
+
+#identity-icon-country-label {
+ direction: ltr;
+}
+
+#identity-box.verifiedIdentity > #identity-icon-labels > #identity-icon-label {
+ -moz-margin-end: 0.25em !important;
+}
+
+#wrapper-search-container > #search-container > #searchbar > .searchbar-textbox > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input {
+ visibility: hidden;
+}
+
+/* ::::: Unified Back-/Forward Button ::::: */
+#back-button > .toolbarbutton-menu-dropmarker,
+#forward-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+.unified-nav-current {
+ font-weight: bold;
+}
+
+toolbarbutton.bookmark-item {
+ max-width: 13em;
+}
+
+%ifdef MENUBAR_CAN_AUTOHIDE
+#toolbar-menubar:not([autohide="true"]) ~ toolbar > #bookmarks-menu-button,
+#toolbar-menubar:not([autohide="true"]) > #bookmarks-menu-button,
+#toolbar-menubar:not([autohide="true"]) ~ toolbar > #history-menu-button,
+#toolbar-menubar:not([autohide="true"]) > #history-menu-button {
+ display: none;
+}
+%endif
+
+#editBMPanel_tagsSelector {
+ /* override default listbox width from xul.css */
+ width: auto;
+}
+
+menupopup[emptyplacesresult="true"] > .hide-if-empty-places-result {
+ display: none;
+}
+
+menuitem.spell-suggestion {
+ font-weight: bold;
+}
+
+#sidebar-header > .tabs-closebutton {
+ -moz-user-focus: normal;
+}
+
+/* apply Fitts' law to the notification bar's close button */
+window[sizemode="maximized"] #content .notification-inner {
+ border-right: 0px !important;
+}
+
+/* Hide extension toolbars that neglected to set the proper class */
+window[chromehidden~="location"][chromehidden~="toolbar"] toolbar:not(.chromeclass-menubar),
+window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(#nav-bar):not(#TabsToolbar):not(#print-preview-toolbar):not(.chromeclass-menubar) {
+ display: none;
+}
+
+#navigator-toolbox ,
+#status-bar ,
+#mainPopupSet {
+ min-width: 1px;
+}
+
+%ifdef MOZ_SERVICES_SYNC
+/* Sync notification UI */
+#sync-notifications {
+ -moz-binding: url("chrome://browser/content/sync/notification.xml#notificationbox");
+ overflow-y: visible !important;
+}
+
+#sync-notifications notification {
+ -moz-binding: url("chrome://browser/content/sync/notification.xml#notification");
+}
+%endif
+
+/* History Swipe Animation */
+
+#historySwipeAnimationContainer {
+ overflow: hidden;
+}
+
+#historySwipeAnimationPreviousPage,
+#historySwipeAnimationCurrentPage,
+#historySwipeAnimationNextPage {
+ background: none top left no-repeat white;
+}
+
+#historySwipeAnimationPreviousPage {
+ background-image: -moz-element(#historySwipeAnimationPreviousPageSnapshot);
+}
+
+#historySwipeAnimationCurrentPage {
+ background-image: -moz-element(#historySwipeAnimationCurrentPageSnapshot);
+}
+
+#historySwipeAnimationNextPage {
+ background-image: -moz-element(#historySwipeAnimationNextPageSnapshot);
+}
+
+/* Identity UI */
+#identity-popup-content-box.unknownIdentity > #identity-popup-connectedToLabel ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-runByLabel ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-content-host ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-content-owner ,
+#identity-popup-content-box.verifiedIdentity > #identity-popup-connectedToLabel2 ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-connectedToLabel2 {
+ display: none;
+}
+
+/* Full Screen UI */
+
+#fullscr-toggler {
+ height: 1px;
+ background: black;
+}
+
+#full-screen-warning-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 2147483647 !important;
+}
+
+#full-screen-warning-container[fade-warning-out] {
+ transition-property: opacity !important;
+ transition-duration: 500ms !important;
+ opacity: 0.0;
+}
+
+/* When the modal fullscreen approval UI is showing, don't allow interaction
+ with the page, but when we're just showing the warning upon entering
+ fullscreen on an already approved page, do allow interaction with the page.
+ */
+#full-screen-warning-container:not([obscure-browser]) {
+ pointer-events: none;
+}
+
+#full-screen-warning-message {
+ /* We must specify a max-width, otherwise word-wrap:break-word doesn't
+ work in descendant <description> and <label> elements. Bug 630864. */
+ max-width: 800px;
+}
+
+#full-screen-domain-text,
+#full-screen-remember-decision > .checkbox-label-box > .checkbox-label {
+ word-wrap: break-word;
+ /* We must specify a min-width, otherwise word-wrap:break-word doesn't work. Bug 630864. */
+ min-width: 1px;
+}
+
+#nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-icon {
+ display: -moz-box;
+}
+
+#nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-text {
+ display: none;
+}
+
+/* ::::: Keyboard UI Panel ::::: */
+.KUI-panel-closebutton {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image");
+}
+
+:-moz-any(.ctrlTab-preview, .allTabs-preview) > html|img,
+:-moz-any(.ctrlTab-preview, .allTabs-preview) > html|canvas {
+ min-width: inherit;
+ max-width: inherit;
+ min-height: inherit;
+ max-height: inherit;
+}
+
+.ctrlTab-favicon-container,
+.allTabs-favicon-container {
+ -moz-box-align: start;
+%ifdef XP_MACOSX
+ -moz-box-pack: end;
+%else
+ -moz-box-pack: start;
+%endif
+}
+
+.ctrlTab-favicon,
+.allTabs-favicon {
+ width: 16px;
+ height: 16px;
+}
+
+/* ::::: Ctrl-Tab Panel ::::: */
+.ctrlTab-preview {
+ -moz-binding: url("chrome://browser/content/browser-tabPreviews.xml#ctrlTab-preview");
+}
+
+/* ::::: All Tabs Panel ::::: */
+.allTabs-preview {
+ -moz-binding: url("chrome://browser/content/browser-tabPreviews.xml#allTabs-preview");
+}
+
+#allTabs-tab-close-button {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image");
+ margin: 0;
+}
+
+
+/* notification anchors should only be visible when their associated
+ notifications are */
+.notification-anchor-icon {
+ -moz-user-focus: normal;
+}
+
+.notification-anchor-icon:not([showing]) {
+ display: none;
+}
+
+/* This was added with the identity toolkit, does it have any other purpose? */
+#notification-popup .text-link.custom-link {
+ -moz-binding: url("chrome://global/content/bindings/text.xml#text-label");
+ text-decoration: none;
+}
+
+#invalid-form-popup > description {
+ max-width: 280px;
+}
+
+.form-validation-anchor {
+ /* should occupy space but not be visible */
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+}
+
+#addon-progress-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#addon-progress-notification");
+}
+
+#click-to-play-plugins-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
+}
+
+.plugin-popupnotification-centeritem {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item");
+}
+
+/* override hidden="true" for the status bar compatibility shim
+ in case it was persisted for the real status bar */
+#status-bar {
+ display: -moz-box;
+}
+
+/* Remove the resizer from the statusbar compatibility shim */
+#status-bar[hideresizer] > .statusbar-resizerpanel {
+ display: none;
+}
+
+browser[tabmodalPromptShowing] {
+ -moz-user-focus: none !important;
+}
+
+/* Status panel */
+
+statuspanel {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#statuspanel");
+ position: fixed;
+ margin-top: -3em;
+ left: 0;
+ max-width: calc(100% - 5px);
+ pointer-events: none;
+}
+
+statuspanel:-moz-locale-dir(ltr)[mirror],
+statuspanel:-moz-locale-dir(rtl):not([mirror]) {
+ left: auto;
+ right: 0;
+}
+
+statuspanel[sizelimit] {
+ max-width: 50%;
+}
+
+statuspanel[type=status] {
+ min-width: 23em;
+}
+
+@media all and (max-width: 800px) {
+ statuspanel[type=status] {
+ min-width: 33%;
+ }
+}
+
+statuspanel[type=overLink] {
+ transition: opacity 120ms ease-out;
+ direction: ltr;
+}
+
+statuspanel[inactive] {
+ transition: none;
+ opacity: 0;
+}
+
+statuspanel[inactive][previoustype=overLink] {
+ transition: opacity 200ms ease-out;
+}
+
+.statuspanel-inner {
+ height: 3em;
+ width: 100%;
+ -moz-box-align: end;
+}
+
+.panel-inner-arrowcontentfooter[footertype="promobox"] {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#promobox");
+}
+
+/* highlighter */
+%include highlighter.css
+
+/* gcli */
+
+html|*#gcli-tooltip-frame,
+html|*#gcli-output-frame,
+#gcli-output,
+#gcli-tooltip {
+ overflow-x: hidden;
+}
+
+.gclitoolbar-input-node,
+.gclitoolbar-complete-node {
+ direction: ltr;
+}
+
+#developer-toolbar-toolbox-button[error-count] > .toolbarbutton-icon {
+ display: none;
+}
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ content: attr(error-count);
+ display: -moz-box;
+ -moz-box-pack: center;
+}
+
+/* Responsive Mode */
+
+.browserContainer[responsivemode] {
+ overflow: auto;
+}
+
+.devtools-responsiveui-toolbar:-moz-locale-dir(rtl) {
+ -moz-box-pack: end;
+}
+
+.browserStack[responsivemode] {
+ transition-duration: 200ms;
+ transition-timing-function: linear;
+}
+
+.browserStack[responsivemode] {
+ transition-property: min-width, max-width, min-height, max-height;
+}
+
+.browserStack[responsivemode][notransition] {
+ transition: none;
+}
+
+.toolbarbutton-badge[badge]:not([badge=""])::after {
+ content: attr(badge);
+}
+
+toolbarbutton[type="badged"] {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#toolbarbutton-badged");
+}
+
+/* Strict icon size for PMkit 'ui/button' */
+toolbarbutton[pmkit-button="true"] > .toolbarbutton-badge-container > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Remove white bar at the bottom of the screen when watching HTML5 video in fullscreen */
+#main-window[inFullscreen] #global-notificationbox,
+#main-window[inFullscreen] #high-priority-global-notificationbox {
+ visibility: collapse;
+}
diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js
new file mode 100644
index 000000000..34b91b6cb
--- /dev/null
+++ b/application/palemoon/base/content/browser.js
@@ -0,0 +1,7163 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
+ "resource:///modules/CharsetMenu.jsm");
+
+const nsIWebNavigation = Ci.nsIWebNavigation;
+const gToolbarInfoSeparators = ["|", "-"];
+
+var gLastBrowserCharset = null;
+var gPrevCharset = null;
+var gProxyFavIcon = null;
+var gLastValidURLStr = "";
+var gInPrintPreviewMode = false;
+var gContextMenu = null; // nsContextMenu instance
+var gMultiProcessBrowser = false;
+
+#ifndef XP_MACOSX
+var gEditUIVisible = true;
+#endif
+
+[
+ ["gBrowser", "content"],
+ ["gNavToolbox", "navigator-toolbox"],
+ ["gURLBar", "urlbar"],
+ ["gNavigatorBundle", "bundle_browser"]
+].forEach(function (elementGlobal) {
+ var [name, id] = elementGlobal;
+ window.__defineGetter__(name, function () {
+ var element = document.getElementById(id);
+ if (!element)
+ return null;
+ delete window[name];
+ return window[name] = element;
+ });
+ window.__defineSetter__(name, function (val) {
+ delete window[name];
+ return window[name] = val;
+ });
+});
+
+// Smart getter for the findbar. If you don't wish to force the creation of
+// the findbar, check gFindBarInitialized first.
+var gFindBarInitialized = false;
+XPCOMUtils.defineLazyGetter(window, "gFindBar", function() {
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let findbar = document.createElementNS(XULNS, "findbar");
+ findbar.id = "FindToolbar";
+
+ let browserBottomBox = document.getElementById("browser-bottombox");
+ browserBottomBox.insertBefore(findbar, browserBottomBox.firstChild);
+
+ // Force a style flush to ensure that our binding is attached.
+ findbar.clientTop;
+ findbar.browser = gBrowser;
+ window.gFindBarInitialized = true;
+ return findbar;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
+ return Services.prefs;
+});
+
+this.__defineGetter__("AddonManager", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/AddonManager.jsm", tmp);
+ return this.AddonManager = tmp.AddonManager;
+});
+this.__defineSetter__("AddonManager", function (val) {
+ delete this.AddonManager;
+ return this.AddonManager = val;
+});
+
+this.__defineGetter__("PluralForm", function() {
+ Cu.import("resource://gre/modules/PluralForm.jsm");
+ return this.PluralForm;
+});
+this.__defineSetter__("PluralForm", function (val) {
+ delete this.PluralForm;
+ return this.PluralForm = val;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHomeUtils.jsm");
+
+#ifdef MOZ_SERVICES_SYNC
+XPCOMUtils.defineLazyModuleGetter(this, "Weave",
+ "resource://services-sync/main.js");
+#endif
+
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
+ let tmp = {};
+ Cu.import("resource:///modules/PopupNotifications.jsm", tmp);
+ try {
+ return new tmp.PopupNotifications(gBrowser,
+ document.getElementById("notification-popup"),
+ document.getElementById("notification-popup-box"));
+ } catch (ex) {
+ Cu.reportError(ex);
+ return null;
+ }
+});
+
+#ifdef MOZ_DEVTOOLS
+XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/devtools/DeveloperToolbar.jsm", tmp);
+ return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
+});
+
+XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/devtools/ToolboxProcess.jsm", tmp);
+ return tmp.BrowserToolboxProcess;
+});
+#endif
+
+XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
+ "resource://gre/modules/PageThumbs.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
+ "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
+ "resource:///modules/FormValidationHandler.jsm");
+
+let gInitialPages = [
+ "about:blank",
+ "about:newtab",
+ "about:home",
+ "about:privatebrowsing",
+ "about:sessionrestore",
+ "about:logopage"
+];
+
+#include browser-addons.js
+#include browser-feeds.js
+#include browser-fullScreen.js
+#include browser-fullZoom.js
+#include browser-places.js
+#include browser-plugins.js
+#include browser-tabPreviews.js
+#include browser-thumbnails.js
+#include browser-webrtcUI.js
+#include browser-gestureSupport.js
+
+#ifdef MOZ_SERVICES_SYNC
+#include browser-syncui.js
+#endif
+
+XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
+#ifdef XP_WIN
+ const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+ if (WINTASKBAR_CONTRACTID in Cc &&
+ Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
+ let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek;
+ return {
+ onOpenWindow: function () {
+ AeroPeek.onOpenWindow(window);
+ },
+ onCloseWindow: function () {
+ AeroPeek.onCloseWindow(window);
+ }
+ };
+ }
+#endif
+ return null;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PageMenu", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/PageMenu.jsm", tmp);
+ return new tmp.PageMenu();
+});
+
+/**
+* We can avoid adding multiple load event listeners and save some time by adding
+* one listener that calls all real handlers.
+*/
+function pageShowEventHandlers(persisted) {
+ charsetLoadListener();
+ XULBrowserWindow.asyncUpdateUI();
+
+ // The PluginClickToPlay events are not fired when navigating using the
+ // BF cache. |persisted| is true when the page is loaded from the
+ // BF cache, so this code reshows the notification if necessary.
+ if (persisted)
+ gPluginHandler.reshowClickToPlayNotification();
+}
+
+function UpdateBackForwardCommands(aWebNavigation) {
+ var backBroadcaster = document.getElementById("Browser:Back");
+ var forwardBroadcaster = document.getElementById("Browser:Forward");
+
+ // Avoid setting attributes on broadcasters if the value hasn't changed!
+ // Remember, guys, setting attributes on elements is expensive! They
+ // get inherited into anonymous content, broadcast to other widgets, etc.!
+ // Don't do it if the value hasn't changed! - dwh
+
+ var backDisabled = backBroadcaster.hasAttribute("disabled");
+ var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
+ if (backDisabled == aWebNavigation.canGoBack) {
+ if (backDisabled)
+ backBroadcaster.removeAttribute("disabled");
+ else
+ backBroadcaster.setAttribute("disabled", true);
+ }
+
+ if (forwardDisabled == aWebNavigation.canGoForward) {
+ if (forwardDisabled)
+ forwardBroadcaster.removeAttribute("disabled");
+ else
+ forwardBroadcaster.setAttribute("disabled", true);
+ }
+}
+
+/**
+ * Click-and-Hold implementation for the Back and Forward buttons
+ * XXXmano: should this live in toolbarbutton.xml?
+ */
+function SetClickAndHoldHandlers() {
+ var timer;
+
+ function openMenu(aButton) {
+ cancelHold(aButton);
+ aButton.firstChild.hidden = false;
+ aButton.open = true;
+ }
+
+ function mousedownHandler(aEvent) {
+ if (aEvent.button != 0 ||
+ aEvent.currentTarget.open ||
+ aEvent.currentTarget.disabled)
+ return;
+
+ // Prevent the menupopup from opening immediately
+ aEvent.currentTarget.firstChild.hidden = true;
+
+ aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false);
+ aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false);
+ timer = setTimeout(openMenu, 500, aEvent.currentTarget);
+ }
+
+ function mouseoutHandler(aEvent) {
+ let buttonRect = aEvent.currentTarget.getBoundingClientRect();
+ if (aEvent.clientX >= buttonRect.left &&
+ aEvent.clientX <= buttonRect.right &&
+ aEvent.clientY >= buttonRect.bottom)
+ openMenu(aEvent.currentTarget);
+ else
+ cancelHold(aEvent.currentTarget);
+ }
+
+ function mouseupHandler(aEvent) {
+ cancelHold(aEvent.currentTarget);
+ }
+
+ function cancelHold(aButton) {
+ clearTimeout(timer);
+ aButton.removeEventListener("mouseout", mouseoutHandler, false);
+ aButton.removeEventListener("mouseup", mouseupHandler, false);
+ }
+
+ function clickHandler(aEvent) {
+ if (aEvent.button == 0 &&
+ aEvent.target == aEvent.currentTarget &&
+ !aEvent.currentTarget.open &&
+ !aEvent.currentTarget.disabled) {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ }
+ }
+
+ function _addClickAndHoldListenersOnElement(aElm) {
+ aElm.addEventListener("mousedown", mousedownHandler, true);
+ aElm.addEventListener("click", clickHandler, true);
+ }
+
+ // Bug 414797: Clone unified-back-forward-button's context menu into both the
+ // back and the forward buttons.
+ var unifiedButton = document.getElementById("unified-back-forward-button");
+ if (unifiedButton && !unifiedButton._clickHandlersAttached) {
+ unifiedButton._clickHandlersAttached = true;
+
+ let popup = document.getElementById("backForwardMenu").cloneNode(true);
+ popup.removeAttribute("id");
+ // Prevent the context attribute on unified-back-forward-button from being
+ // inherited.
+ popup.setAttribute("context", "");
+
+ let backButton = document.getElementById("back-button");
+ backButton.setAttribute("type", "menu");
+ backButton.appendChild(popup);
+ _addClickAndHoldListenersOnElement(backButton);
+
+ let forwardButton = document.getElementById("forward-button");
+ popup = popup.cloneNode(true);
+ forwardButton.setAttribute("type", "menu");
+ forwardButton.appendChild(popup);
+ _addClickAndHoldListenersOnElement(forwardButton);
+ }
+}
+
+const gSessionHistoryObserver = {
+ observe: function(subject, topic, data)
+ {
+ if (topic != "browser:purge-session-history")
+ return;
+
+ var backCommand = document.getElementById("Browser:Back");
+ backCommand.setAttribute("disabled", "true");
+ var fwdCommand = document.getElementById("Browser:Forward");
+ fwdCommand.setAttribute("disabled", "true");
+
+ // Hide session restore button on about:home
+ window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton");
+
+ if (gURLBar) {
+ // Clear undo history of the URL bar
+ gURLBar.editor.transactionManager.clear()
+ }
+ }
+};
+
+/**
+ * Given a starting docshell and a URI to look up, find the docshell the URI
+ * is loaded in.
+ * @param aDocument
+ * A document to find instead of using just a URI - this is more specific.
+ * @param aDocShell
+ * The doc shell to start at
+ * @param aSoughtURI
+ * The URI that we're looking for
+ * @returns The doc shell that the sought URI is loaded in. Can be in
+ * subframes.
+ */
+function findChildShell(aDocument, aDocShell, aSoughtURI) {
+ aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument);
+ if ((aDocument && doc == aDocument) ||
+ (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec))
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
+ for (var i = 0; i < node.childCount; ++i) {
+ var docShell = node.getChildAt(i);
+ docShell = findChildShell(aDocument, docShell, aSoughtURI);
+ if (docShell)
+ return docShell;
+ }
+ return null;
+}
+
+var gPopupBlockerObserver = {
+ _reportButton: null,
+
+ onReportButtonClick: function (aEvent)
+ {
+ if (aEvent.button != 0 || aEvent.target != this._reportButton)
+ return;
+
+ document.getElementById("blockedPopupOptions")
+ .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent);
+ },
+
+ handleEvent: function (aEvent)
+ {
+ if (aEvent.originalTarget != gBrowser.selectedBrowser)
+ return;
+
+ if (!this._reportButton && gURLBar)
+ this._reportButton = document.getElementById("page-report-button");
+
+ if (!gBrowser.selectedBrowser.blockedPopups) {
+ // Hide the icon in the location bar (if the location bar exists)
+ if (gURLBar)
+ this._reportButton.hidden = true;
+ return;
+ }
+
+ if (gURLBar)
+ this._reportButton.hidden = false;
+
+ // Only show the notification again if we've not already shown it. Since
+ // notifications are per-browser, we don't need to worry about re-adding
+ // it.
+ if (!gBrowser.selectedBrowser.blockedPopups.reported) {
+ if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) {
+ var brandBundle = document.getElementById("bundle_brand");
+ var brandShortName = brandBundle.getString("brandShortName");
+ var popupCount = gBrowser.selectedBrowser.blockedPopups.length;
+ var popupButtonText = gNavigatorBundle.getString("popupWarningButton");
+ var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey");
+ var messageBase = gNavigatorBundle.getString("popupWarning.message");
+ var message = PluralForm.get(popupCount, messageBase)
+ .replace("#1", brandShortName)
+ .replace("#2", popupCount);
+
+ var notificationBox = gBrowser.getNotificationBox();
+ var notification = notificationBox.getNotificationWithValue("popup-blocked");
+ if (notification) {
+ notification.label = message;
+ }
+ else {
+ var buttons = [{
+ label: popupButtonText,
+ accessKey: popupButtonAccesskey,
+ popup: "blockedPopupOptions",
+ callback: null
+ }];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, "popup-blocked",
+ "chrome://browser/skin/Info.png",
+ priority, buttons);
+ }
+ }
+
+ // Record the fact that we've reported this blocked popup, so we don't
+ // show it again.
+ gBrowser.selectedBrowser.blockedPopups.reported = true;
+ }
+ },
+
+ toggleAllowPopupsForSite: function (aEvent)
+ {
+ var pm = Services.perms;
+ var shouldBlock = aEvent.target.getAttribute("block") == "true";
+ var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
+ pm.add(gBrowser.currentURI, "popup", perm);
+
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ },
+
+ fillPopupList: function (aEvent)
+ {
+ // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
+ // we should really walk the blockedPopups and create a list of "allow for <host>"
+ // menuitems for the common subset of hosts present in the report, this will
+ // make us frame-safe.
+ //
+ // XXXjst - Note that when this is fixed to work with multi-framed sites,
+ // also back out the fix for bug 343772 where
+ // nsGlobalWindow::CheckOpenAllow() was changed to also
+ // check if the top window's location is whitelisted.
+ let browser = gBrowser.selectedBrowser;
+ var uri = browser.currentURI;
+ var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite");
+ try {
+ blockedPopupAllowSite.removeAttribute("hidden");
+
+ var pm = Services.perms;
+ if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) {
+ // Offer an item to block popups for this site, if a whitelist entry exists
+ // already for it.
+ let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", blockString);
+ blockedPopupAllowSite.setAttribute("block", "true");
+ }
+ else {
+ // Offer an item to allow popups for this site
+ let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", allowString);
+ blockedPopupAllowSite.removeAttribute("block");
+ }
+ }
+ catch (e) {
+ blockedPopupAllowSite.setAttribute("hidden", "true");
+ }
+
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ blockedPopupAllowSite.setAttribute("disabled", "true");
+ else
+ blockedPopupAllowSite.removeAttribute("disabled");
+
+ var foundUsablePopupURI = false;
+ var blockedPopups = browser.blockedPopups;
+ if (blockedPopups) {
+ for (let i = 0; i < blockedPopups.length; i++) {
+ let blockedPopup = blockedPopups[i];
+
+ // popupWindowURI will be null if the file picker popup is blocked.
+ // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
+ if (!blockedPopup.popupWindowURI)
+ continue;
+ var popupURIspec = blockedPopup.popupWindowURI.spec;
+
+ // Sometimes the popup URI that we get back from the blockedPopup
+ // isn't useful (for instance, netscape.com's popup URI ends up
+ // being "http://www.netscape.com", which isn't really the URI of
+ // the popup they're trying to show). This isn't going to be
+ // useful to the user, so we won't create a menu item for it.
+ if (popupURIspec == "" || popupURIspec == "about:blank" ||
+ popupURIspec == uri.spec)
+ continue;
+
+ // Because of the short-circuit above, we may end up in a situation
+ // in which we don't have any usable popup addresses to show in
+ // the menu, and therefore we shouldn't show the separator. However,
+ // since we got past the short-circuit, we must've found at least
+ // one usable popup URI and thus we'll turn on the separator later.
+ foundUsablePopupURI = true;
+
+ var menuitem = document.createElement("menuitem");
+ var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix",
+ [popupURIspec]);
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("popupWindowURI", popupURIspec);
+ menuitem.setAttribute("popupWindowFeatures", blockedPopup.popupWindowFeatures);
+ menuitem.setAttribute("popupWindowName", blockedPopup.popupWindowName);
+ menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
+ menuitem.setAttribute("popupReportIndex", i);
+ menuitem.popupReportBrowser = browser;
+ aEvent.target.appendChild(menuitem);
+ }
+ }
+
+ // Show or hide the separator, depending on whether we added any
+ // showable popup addresses to the menu.
+ var blockedPopupsSeparator =
+ document.getElementById("blockedPopupsSeparator");
+ if (foundUsablePopupURI)
+ blockedPopupsSeparator.removeAttribute("hidden");
+ else
+ blockedPopupsSeparator.setAttribute("hidden", true);
+
+ var blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage");
+ var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
+ if (aEvent.target.anchorNode.id == "page-report-button") {
+ aEvent.target.anchorNode.setAttribute("open", "true");
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar"));
+ } else
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage"));
+ },
+
+ onPopupHiding: function (aEvent) {
+ if (aEvent.target.anchorNode.id == "page-report-button")
+ aEvent.target.anchorNode.removeAttribute("open");
+
+ let item = aEvent.target.lastChild;
+ while (item && item.getAttribute("observes") != "blockedPopupsSeparator") {
+ let next = item.previousSibling;
+ item.parentNode.removeChild(item);
+ item = next;
+ }
+ },
+
+ showBlockedPopup: function (aEvent)
+ {
+ var target = aEvent.target;
+ var popupReportIndex = target.getAttribute("popupReportIndex");
+ let browser = target.popupReportBrowser;
+ browser.unblockPopup(popupReportIndex);
+ },
+
+ editPopupSettings: function ()
+ {
+ var host = "";
+ try {
+ host = gBrowser.currentURI.host;
+ }
+ catch (e) { }
+
+ var bundlePreferences = document.getElementById("bundle_preferences");
+ var params = { blockVisible : false,
+ sessionVisible : false,
+ allowVisible : true,
+ prefilledHost : host,
+ permissionType : "popup",
+ windowTitle : bundlePreferences.getString("popuppermissionstitle"),
+ introText : bundlePreferences.getString("popuppermissionstext") };
+ var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
+ if (existingWindow) {
+ existingWindow.initWithParams(params);
+ existingWindow.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/permissions.xul",
+ "_blank", "resizable,dialog=no,centerscreen", params);
+ },
+
+ dontShowMessage: function ()
+ {
+ var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage);
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ }
+};
+
+const gXSSObserver = {
+
+ observe: function (aSubject, aTopic, aData)
+ {
+
+ // Don't do anything if the notification is disabled.
+ if (!gPrefService.getBoolPref("security.xssfilter.displayWarning"))
+ return;
+
+ // Parse incoming XSS array
+ aSubject.QueryInterface(Ci.nsIArray);
+ var policy = aSubject.queryElementAt(0, Ci.nsISupportsString).data;
+ var content = aSubject.queryElementAt(1, Ci.nsISupportsString).data;
+ var domain = aSubject.queryElementAt(2, Ci.nsISupportsString).data;
+ var url = aSubject.queryElementAt(3, Ci.nsISupportsCString).data;
+ var blockMode = aSubject.queryElementAt(4, Ci.nsISupportsPRBool).data;
+
+ // If it is a block mode event, do not display the infobar
+ if (blockMode)
+ return;
+
+ var nb = gBrowser.getNotificationBox();
+ const priority = nb.PRIORITY_WARNING_MEDIUM;
+
+ var buttons = [{
+ label: 'View Unsafe Content',
+ accessKey: 'V',
+ popup: null,
+ callback: function () {
+ alert(content);
+ }
+ }];
+
+ if (domain !== "")
+ buttons.push({
+ label: 'Add Domain Exception',
+ accessKey: 'A',
+ popup: null,
+ callback: function () {
+ let whitelist = gPrefService.getCharPref("security.xssfilter.whitelist");
+ if (whitelist != "") {
+ whitelist = whitelist + "," + domain;
+ } else {
+ whitelist = domain;
+ }
+ // Write the updated whitelist. Since this is observed by the XSS filter,
+ // it will automatically sync to the back-end and update immediately.
+ gPrefService.setCharPref("security.xssfilter.whitelist", whitelist);
+ // After setting this, we automatically reload the page.
+ BrowserReloadSkipCache();
+ }
+ });
+
+ nb.appendNotification("The XSS Filter has detected a potential XSS attack. Type: " +
+ policy, 'popup-blocked', 'chrome://browser/skin/Info.png',
+ priority, buttons);
+ }
+};
+
+var gBrowserInit = {
+ onLoad: function() {
+ gMultiProcessBrowser = gPrefService.getBoolPref("browser.tabs.remote");
+
+ var mustLoadSidebar = false;
+
+ Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
+
+ gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
+
+ // Note that the XBL binding is untrusted
+ gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true);
+ gBrowser.addEventListener("PluginCrashed", gPluginHandler, true);
+ gBrowser.addEventListener("PluginOutdated", gPluginHandler, true);
+ gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true);
+ gBrowser.addEventListener("PluginRemoved", gPluginHandler, true);
+
+ Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ messageManager.loadFrameScript("chrome://browser/content/content.js", true);
+ messageManager.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
+
+ // initialize observers and listeners
+ // and give C++ access to gBrowser
+ XULBrowserWindow.init();
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = window.XULBrowserWindow;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
+ new nsBrowserAccess();
+
+ // set default character set if provided
+ // window.arguments[1]: character set (string)
+ if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
+ if (window.arguments[1].startsWith("charset=")) {
+ var arrayArgComponents = window.arguments[1].split("=");
+ if (arrayArgComponents) {
+ //we should "inherit" the charset menu setting in a new window
+ //TFE FIXME: this is now a wrappednative and can't be set this way.
+ //getMarkupDocumentViewer().defaultCharacterSet = arrayArgComponents[1];
+ }
+ }
+ }
+
+ // Manually hook up session and global history for the first browser
+ // so that we don't have to load global history before bringing up a
+ // window.
+ // Wire up session and global history before any possible
+ // progress notifications for back/forward button updating
+ gBrowser.webNavigation.sessionHistory = Cc["@mozilla.org/browser/shistory;1"].
+ createInstance(Ci.nsISHistory);
+ Services.obs.addObserver(gBrowser.browsers[0], "browser:purge-session-history", false);
+
+ // remove the disablehistory attribute so the browser cleans up, as
+ // though it had done this work itself
+ gBrowser.browsers[0].removeAttribute("disablehistory");
+
+ // enable global history
+ try {
+ if (!gMultiProcessBrowser)
+ gBrowser.docShell.useGlobalHistory = true;
+ } catch(ex) {
+ Cu.reportError("Places database may be locked: " + ex);
+ }
+
+ // hook up UI through progress listener
+ gBrowser.addProgressListener(window.XULBrowserWindow);
+ gBrowser.addTabsProgressListener(window.TabsProgressListener);
+
+ // setup our common DOMLinkAdded listener
+ gBrowser.addEventListener("DOMLinkAdded", DOMLinkHandler, false);
+
+ // setup our MozApplicationManifest listener
+ gBrowser.addEventListener("MozApplicationManifest",
+ OfflineApps, false);
+
+ // setup simple gestures support
+ gGestureSupport.init(true);
+
+ // setup history swipe animation
+ gHistorySwipeAnimation.init();
+
+ if (window.opener && !window.opener.closed) {
+ let openerSidebarBox = window.opener.document.getElementById("sidebar-box");
+ // If the opener had a sidebar, open the same sidebar in our window.
+ // The opener can be the hidden window too, if we're coming from the state
+ // where no windows are open, and the hidden window has no sidebar box.
+ if (openerSidebarBox && !openerSidebarBox.hidden) {
+ let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand");
+ let sidebarCmdElem = document.getElementById(sidebarCmd);
+
+ // dynamically generated sidebars will fail this check.
+ if (sidebarCmdElem) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ let sidebarTitle = document.getElementById("sidebar-title");
+
+ sidebarTitle.setAttribute(
+ "value", window.opener.document.getElementById("sidebar-title").getAttribute("value"));
+ sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width);
+
+ sidebarBox.setAttribute("sidebarcommand", sidebarCmd);
+ // Note: we're setting 'src' on sidebarBox, which is a <vbox>, not on
+ // the <browser id="sidebar">. This lets us delay the actual load until
+ // delayedStartup().
+ sidebarBox.setAttribute(
+ "src", window.opener.document.getElementById("sidebar").getAttribute("src"));
+ mustLoadSidebar = true;
+
+ sidebarBox.hidden = false;
+ document.getElementById("sidebar-splitter").hidden = false;
+ sidebarCmdElem.setAttribute("checked", "true");
+ }
+ }
+ }
+ else {
+ let box = document.getElementById("sidebar-box");
+ if (box.hasAttribute("sidebarcommand")) {
+ let commandID = box.getAttribute("sidebarcommand");
+ if (commandID) {
+ let command = document.getElementById(commandID);
+ if (command) {
+ mustLoadSidebar = true;
+ box.hidden = false;
+ document.getElementById("sidebar-splitter").hidden = false;
+ command.setAttribute("checked", "true");
+ }
+ else {
+ // Remove the |sidebarcommand| attribute, because the element it
+ // refers to no longer exists, so we should assume this sidebar
+ // panel has been uninstalled. (249883)
+ box.removeAttribute("sidebarcommand");
+ }
+ }
+ }
+ }
+
+ // Certain kinds of automigration rely on this notification to complete their
+ // tasks BEFORE the browser window is shown.
+ Services.obs.notifyObservers(null, "browser-window-before-show", "");
+
+ // Set a sane starting width/height for all resolutions on new profiles.
+ if (!document.documentElement.hasAttribute("width")) {
+ let defaultWidth;
+ let defaultHeight;
+
+ // Very small: maximize the window
+ // Portrait : use about full width and 3/4 height, to view entire pages
+ // at once (without being obnoxiously tall)
+ // Widescreen: use about half width, to suggest side-by-side page view
+ // Otherwise : use 3/4 height and width
+ if (screen.availHeight <= 600) {
+ document.documentElement.setAttribute("sizemode", "maximized");
+ defaultWidth = 610;
+ defaultHeight = 450;
+ }
+ else {
+ if (screen.availWidth <= screen.availHeight) {
+ defaultWidth = screen.availWidth * .9;
+ defaultHeight = screen.availHeight * .75;
+ }
+ else if (screen.availWidth >= 2048) {
+ defaultWidth = (screen.availWidth / 2) - 20;
+ defaultHeight = screen.availHeight - 10;
+ }
+ else {
+ defaultWidth = screen.availWidth * .75;
+ defaultHeight = screen.availHeight * .75;
+ }
+
+#ifdef MOZ_WIDGET_GTK2
+ // On X, we're not currently able to account for the size of the window
+ // border. Use 28px as a guess (titlebar + bottom window border)
+ defaultHeight -= 28;
+#endif
+ }
+ document.documentElement.setAttribute("width", defaultWidth);
+ document.documentElement.setAttribute("height", defaultHeight);
+ }
+
+ if (!gShowPageResizers)
+ document.getElementById("status-bar").setAttribute("hideresizer", "true");
+
+ if (!window.toolbar.visible) {
+ // adjust browser UI for popups
+ if (gURLBar) {
+ gURLBar.setAttribute("readonly", "true");
+ gURLBar.setAttribute("enablehistory", "false");
+ }
+ goSetCommandEnabled("cmd_newNavigatorTab", false);
+ }
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+
+ // Misc. inits.
+ CombinedStopReload.init();
+ allTabs.readPref();
+ TabsOnTop.init();
+ gPrivateBrowsingUI.init();
+ TabsInTitlebar.init();
+ retrieveToolbarIconsizesFromTheme();
+ ToolbarIconColor.init();
+
+#ifdef XP_WIN
+ if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
+ window.matchMedia("(-moz-windows-default-theme)").matches) {
+ let windows8WindowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}).Windows8WindowFrameColor;
+
+ var windowFrameColor;
+ windowFrameColor = windows8WindowFrameColor.get_win8();
+
+ // Formula from Microsoft's UWP guideline.
+ let backgroundLuminance = (windowFrameColor[0] * 2 +
+ windowFrameColor[1] * 5 +
+ windowFrameColor[2]) / 8;
+ if (backgroundLuminance <= 128) {
+ document.documentElement.setAttribute("darkwindowframe", "true");
+ }
+ }
+#endif
+
+ // Wait until chrome is painted before executing code not critical to making the window visible
+ this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar);
+ window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
+
+ this._loadHandled = true;
+ },
+
+ _cancelDelayedStartup: function () {
+ window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
+ this._boundDelayedStartup = null;
+ },
+
+ _delayedStartup: function(mustLoadSidebar) {
+ let tmp = {};
+
+ this._cancelDelayedStartup();
+
+ let uriToLoad = this._getUriToLoad();
+ var isLoadingBlank = isBlankPageURL(uriToLoad);
+
+ // This pageshow listener needs to be registered before we may call
+ // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
+ gBrowser.addEventListener("pageshow", function(event) {
+ // Filter out events that are not about the document load we are interested in
+ if (content && event.target == content.document)
+ setTimeout(pageShowEventHandlers, 0, event.persisted);
+ }, true);
+
+ if (uriToLoad && uriToLoad != "about:blank") {
+ if (uriToLoad instanceof Ci.nsISupportsArray) {
+ let count = uriToLoad.Count();
+ let specs = [];
+ for (let i = 0; i < count; i++) {
+ let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString);
+ specs.push(urisstring.data);
+ }
+
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(specs, false, true);
+ } catch (e) {}
+ }
+ else if (uriToLoad instanceof XULElement) {
+ // swap the given tab with the default about:blank tab and then close
+ // the original tab in the other window.
+
+ // Stop the about:blank load
+ gBrowser.stop();
+ // make sure it has a docshell
+ gBrowser.docShell;
+
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);
+ }
+ // window.arguments[2]: referrer (nsIURI)
+ // [3]: postData (nsIInputStream)
+ // [4]: allowThirdPartyFixup (bool)
+ else if (window.arguments.length >= 3) {
+ loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null,
+ window.arguments[4] || false);
+ window.focus();
+ }
+ // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
+ // Such callers expect that window.arguments[0] is handled as a single URI.
+ else
+ loadOneOrMoreURIs(uriToLoad);
+ }
+
+ Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-origin-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+ Services.obs.addObserver(gXSSObserver, "xss-on-violate-policy", false);
+
+ BrowserOffline.init();
+ OfflineApps.init();
+ IndexedDBPromptHelper.init();
+ AddonManager.addAddonListener(AddonsMgrListener);
+ WebrtcIndicator.init();
+
+ // Ensure login manager is up and running.
+ Services.logins;
+
+ if (mustLoadSidebar) {
+ let sidebar = document.getElementById("sidebar");
+ let sidebarBox = document.getElementById("sidebar-box");
+ sidebar.setAttribute("src", sidebarBox.getAttribute("src"));
+ }
+
+ UpdateUrlbarSearchSplitterState();
+
+ if (!isLoadingBlank || !focusAndSelectUrlBar())
+ gBrowser.selectedBrowser.focus();
+
+ gNavToolbox.customizeDone = BrowserToolboxCustomizeDone;
+ gNavToolbox.customizeChange = BrowserToolboxCustomizeChange;
+
+ // Set up Sanitize Item
+ this._initializeSanitizer();
+
+ // Enable/Disable auto-hide tabbar
+ gBrowser.tabContainer.updateVisibility();
+
+ gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
+
+ var homeButton = document.getElementById("home-button");
+ gHomeButton.updateTooltip(homeButton);
+ gHomeButton.updatePersonalToolbarStyle(homeButton);
+
+ // BiDi UI
+ gBidiUI = isBidiEnabled();
+ if (gBidiUI) {
+ document.getElementById("documentDirection-separator").hidden = false;
+ document.getElementById("documentDirection-swap").hidden = false;
+ document.getElementById("textfieldDirection-separator").hidden = false;
+ document.getElementById("textfieldDirection-swap").hidden = false;
+ }
+
+ // Setup click-and-hold gestures access to the session history
+ // menus if global click-and-hold isn't turned on
+ if (!getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ // Initialize the full zoom setting.
+ // We do this before the session restore service gets initialized so we can
+ // apply full zoom settings to tabs restored by the session restore service.
+ FullZoom.init();
+
+ // Bug 666804 - NetworkPrioritizer support for e10s
+ if (!gMultiProcessBrowser) {
+ let NP = {};
+ Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
+ NP.trackBrowserWindow(window);
+ }
+
+ // initialize the session-restore service (in case it's not already running)
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ let ssPromise = ss.init(window);
+
+ PlacesToolbarHelper.init();
+
+ ctrlTab.readPref();
+ gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
+ gPrefService.addObserver(allTabs.prefName, allTabs, false);
+
+ // Initialize the download manager some time after the app starts so that
+ // auto-resume downloads begin (such as after crashing or quitting with
+ // active downloads) and speeds up the first-load of the download manager UI.
+ // If the user manually opens the download manager before the timeout, the
+ // downloads will start right away, and getting the service again won't hurt.
+ setTimeout(function() {
+ try {
+ Cu.import("resource:///modules/DownloadsCommon.jsm", {})
+ .DownloadsCommon.initializeAllDataLinks();
+ Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
+ .DownloadsTaskbar.registerIndicator(window);
+ } catch(ex) {
+ Cu.reportError(ex);
+ }
+ }, 10000);
+
+ // Load the Login Manager data from disk off the main thread, some time
+ // after startup. If the data is required before the timeout, for example
+ // because a restored page contains a password field, it will be loaded on
+ // the main thread, and this initialization request will be ignored.
+ setTimeout(function() {
+ try {
+ Services.logins;
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }, 3000);
+
+ // The object handling the downloads indicator is also initialized here in the
+ // delayed startup function, but the actual indicator element is not loaded
+ // unless there are downloads to be displayed.
+ DownloadsButton.initializeIndicator();
+
+#ifndef XP_MACOSX
+ updateEditUIVisibility();
+ let placesContext = document.getElementById("placesContext");
+ placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
+ placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
+#endif
+
+ gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
+
+ // Bug 666808 - AeroPeek support for e10s
+ if (!gMultiProcessBrowser) {
+ if (Win7Features)
+ Win7Features.onOpenWindow();
+ }
+
+ // called when we go into full screen, even if initiated by a web page script
+ window.addEventListener("fullscreen", onFullScreen, true);
+
+ // Called when we enter DOM full-screen mode. Note we can already be in browser
+ // full-screen mode when we enter DOM full-screen mode.
+ window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
+
+ if (window.fullScreen)
+ onFullScreen();
+ if (document.mozFullScreen)
+ onMozEnteredDomFullscreen();
+
+#ifdef MOZ_SERVICES_SYNC
+ // initialize the sync UI
+ gSyncUI.init();
+#endif
+
+ gBrowserThumbnails.init();
+
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ window.addEventListener("resize", function resizeHandler(event) {
+ if (event.target == window)
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ });
+
+#ifdef MOZ_DEVTOOLS
+ // Enable Chrome Debugger?
+ let chromeEnabled = gPrefService.getBoolPref("devtools.chrome.enabled");
+ let remoteEnabled = chromeEnabled &&
+ gPrefService.getBoolPref("devtools.debugger.chrome-enabled") &&
+ gPrefService.getBoolPref("devtools.debugger.remote-enabled");
+ if (remoteEnabled) {
+ let cmd = document.getElementById("Tools:ChromeDebugger");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+ // Enable Scratchpad in the UI, if the preference allows this.
+ let scratchpadEnabled = gPrefService.getBoolPref(Scratchpad.prefEnabledName);
+ if (scratchpadEnabled) {
+ let cmd = document.getElementById("Tools:Scratchpad");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+#endif
+
+ // Enable Error Console?
+ let consoleEnabled = gPrefService.getBoolPref("devtools.errorconsole.enabled");
+ if (consoleEnabled) {
+ let cmd = document.getElementById("Tools:ErrorConsole");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ // If the user (or the locale) hasn't enabled the top-level "Character
+ // Encoding" menu via the "browser.menu.showCharacterEncoding" preference,
+ // hide it.
+ if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding",
+ Ci.nsIPrefLocalizedString).data)
+ document.getElementById("appmenu_charsetMenu").hidden = true;
+#endif
+
+#ifdef MOZ_DEVTOOLS
+ // Enable Responsive UI?
+ let responsiveUIEnabled = gPrefService.getBoolPref("devtools.responsiveUI.enabled");
+ if (responsiveUIEnabled) {
+ let cmd = document.getElementById("Tools:ResponsiveUI");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+ // Add Devtools menuitems and listeners
+ gDevToolsBrowser.registerBrowserWindow(window);
+#endif
+
+ let appMenuButton = document.getElementById("appmenu-button");
+ let appMenuPopup = document.getElementById("appmenu-popup");
+ if (appMenuButton && appMenuPopup) {
+ let appMenuOpening = null;
+ appMenuButton.addEventListener("mousedown", function(event) {
+ if (event.button == 0)
+ appMenuOpening = new Date();
+ }, false);
+ appMenuPopup.addEventListener("popupshown", function(event) {
+ if (event.target != appMenuPopup || !appMenuOpening)
+ return;
+ let duration = new Date() - appMenuOpening;
+ appMenuOpening = null;
+ }, false);
+ }
+
+ window.addEventListener("mousemove", MousePosTracker, false);
+ window.addEventListener("dragover", MousePosTracker, false);
+
+ // End startup crash tracking after a delay to catch crashes while restoring
+ // tabs and to postpone saving the pref to disk.
+ try {
+ const startupCrashEndDelay = 30 * 1000;
+ setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay);
+ } catch (ex) {
+ Cu.reportError("Could not end startup crash tracking: " + ex);
+ }
+
+ ssPromise.then(() =>{
+ // Bail out if the window has been closed in the meantime.
+ if (window.closed) {
+ return;
+ }
+ if ("TabView" in window) {
+ TabView.init();
+ }
+ // XXX: do we still need this?...
+ setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
+ });
+
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
+ },
+
+ // Returns the URI(s) to load at startup.
+ _getUriToLoad: function () {
+ // window.arguments[0]: URI to load (string), or an nsISupportsArray of
+ // nsISupportsStrings to load, or a xul:tab of
+ // a tabbrowser, which will be replaced by this
+ // window (for this case, all other arguments are
+ // ignored).
+ if (!window.arguments || !window.arguments[0])
+ return null;
+
+ let uri = window.arguments[0];
+ let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
+ .getService(Ci.nsISessionStartup);
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"]
+ .getService(Ci.nsIBrowserHandler)
+ .defaultArgs;
+
+ // If the given URI matches defaultArgs (the default homepage) we want
+ // to block its load if we're going to restore a session anyway.
+ if (uri == defaultArgs && sessionStartup.willOverrideHomepage)
+ return null;
+
+ return uri;
+ },
+
+ onUnload: function() {
+ // In certain scenarios it's possible for unload to be fired before onload,
+ // (e.g. if the window is being closed after browser.js loads but before the
+ // load completes). In that case, there's nothing to do here.
+ if (!this._loadHandled)
+ return;
+
+#ifdef MOZ_DEVTOOLS
+ gDevToolsBrowser.forgetBrowserWindow(window);
+
+ let desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar");
+ if (desc && !desc.get) {
+ DeveloperToolbar.destroy();
+ }
+#endif
+
+ // First clean up services initialized in gBrowserInit.onLoad (or those whose
+ // uninit methods don't depend on the services having been initialized).
+
+ allTabs.uninit();
+
+ CombinedStopReload.uninit();
+
+ gGestureSupport.init(false);
+
+ gHistorySwipeAnimation.uninit();
+
+ FullScreen.cleanup();
+
+ Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
+
+ try {
+ gBrowser.removeProgressListener(window.XULBrowserWindow);
+ gBrowser.removeTabsProgressListener(window.TabsProgressListener);
+ } catch (ex) {
+ }
+
+ BookmarkingUI.uninit();
+
+ TabsOnTop.uninit();
+
+ TabsInTitlebar.uninit();
+
+ ToolbarIconColor.uninit();
+
+ var enumerator = Services.wm.getEnumerator(null);
+ enumerator.getNext();
+ if (!enumerator.hasMoreElements()) {
+ document.persist("sidebar-box", "sidebarcommand");
+ document.persist("sidebar-box", "width");
+ document.persist("sidebar-box", "src");
+ document.persist("sidebar-title", "value");
+ }
+
+ // Now either cancel delayedStartup, or clean up the services initialized from
+ // it.
+ if (this._boundDelayedStartup) {
+ this._cancelDelayedStartup();
+ } else {
+ if (Win7Features)
+ Win7Features.onCloseWindow();
+
+ gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
+ gPrefService.removeObserver(allTabs.prefName, allTabs);
+ ctrlTab.uninit();
+ if ("TabView" in window) {
+ TabView.uninit();
+ }
+ gBrowserThumbnails.uninit();
+ FullZoom.destroy();
+
+ Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-origin-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
+ Services.obs.removeObserver(gXSSObserver, "xss-on-violate-policy");
+
+ try {
+ gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ BrowserOffline.uninit();
+ OfflineApps.uninit();
+ IndexedDBPromptHelper.uninit();
+ AddonManager.removeAddonListener(AddonsMgrListener);
+ }
+
+ // Final window teardown, do this last.
+ window.XULBrowserWindow.destroy();
+ window.XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null;
+ },
+
+#ifdef XP_MACOSX
+ // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
+ // nonBrowserWindowShutdown() are used for non-browser windows in
+ // macBrowserOverlay
+ nonBrowserWindowStartup: function() {
+ // Disable inappropriate commands / submenus
+ var disabledItems = ['Browser:SavePage',
+ 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
+ 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload',
+ 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen',
+ 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs',
+ 'View:PageInfo', 'Browser:ToggleAddonBar'];
+ var element;
+
+ for (let disabledItem of disabledItems) {
+ element = document.getElementById(disabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // If no windows are active (i.e. we're the hidden window), disable the close, minimize
+ // and zoom menu commands as well
+ if (window.location.href == "chrome://browser/content/hiddenWindow.xul") {
+ var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow'];
+ for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) {
+ element = document.getElementById(hiddenWindowDisabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // also hide the window-list separator
+ element = document.getElementById("sep-window-list");
+ element.setAttribute("hidden", "true");
+
+ // Setup the dock menu.
+ let dockMenuElement = document.getElementById("menu_mac_dockmenu");
+ if (dockMenuElement != null) {
+ let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"]
+ .createInstance(Ci.nsIStandaloneNativeMenu);
+
+ try {
+ nativeMenu.init(dockMenuElement);
+
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.dockMenu = nativeMenu;
+ }
+ catch (e) {
+ }
+ }
+ }
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.getElementById("macDockMenuNewWindow").hidden = true;
+ }
+
+ this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0);
+ },
+
+ nonBrowserWindowDelayedStartup: function() {
+ this._delayedStartupTimeoutId = null;
+
+ // initialise the offline listener
+ BrowserOffline.init();
+
+ // Set up Sanitize Item
+ this._initializeSanitizer();
+
+ // initialize the private browsing UI
+ gPrivateBrowsingUI.init();
+
+#ifdef MOZ_SERVICES_SYNC
+ // initialize the sync UI
+ gSyncUI.init();
+#endif
+ },
+
+ nonBrowserWindowShutdown: function() {
+ // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do -
+ // just cancel the pending timeout and return;
+ if (this._delayedStartupTimeoutId) {
+ clearTimeout(this._delayedStartupTimeoutId);
+ return;
+ }
+
+ BrowserOffline.uninit();
+ },
+#endif
+
+ _initializeSanitizer: function() {
+ const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize";
+ if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) {
+ gPrefService.clearUserPref(kDidSanitizeDomain);
+ // We need to persist this preference change, since we want to
+ // check it at next app start even if the browser exits abruptly
+ gPrefService.savePrefFile(null);
+ }
+
+ /**
+ * Migrate Firefox 3.0 privacy.item prefs under one of these conditions:
+ *
+ * a) User has customized any privacy.item prefs
+ * b) privacy.sanitize.sanitizeOnShutdown is set
+ */
+ if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) {
+ let itemBranch = gPrefService.getBranch("privacy.item.");
+ let itemArray = itemBranch.getChildList("");
+
+ // See if any privacy.item prefs are set
+ let doMigrate = itemArray.some(function (name) itemBranch.prefHasUserValue(name));
+ // Or if sanitizeOnShutdown is set
+ if (!doMigrate)
+ doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown");
+
+ if (doMigrate) {
+ let cpdBranch = gPrefService.getBranch("privacy.cpd.");
+ let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown.");
+ for (let name of itemArray) {
+ try {
+ // don't migrate password or offlineApps clearing in the CRH dialog since
+ // there's no UI for those anymore. They default to false. bug 497656
+ if (name != "passwords" && name != "offlineApps")
+ cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+ clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+ }
+ catch(e) {
+ Cu.reportError("Exception thrown during privacy pref migration: " + e);
+ }
+ }
+ }
+
+ gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true);
+ }
+ },
+}
+
+
+/* Legacy global init functions */
+var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit);
+var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit);
+#ifdef XP_MACOSX
+var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit);
+var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit);
+var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit);
+#endif
+
+function HandleAppCommandEvent(evt) {
+ switch (evt.command) {
+ case "Back":
+ BrowserBack();
+ break;
+ case "Forward":
+ BrowserForward();
+ break;
+ case "Reload":
+ BrowserReloadSkipCache();
+ break;
+ case "Stop":
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ BrowserStop();
+ break;
+ case "Search":
+ BrowserSearch.webSearch();
+ break;
+ case "Bookmarks":
+ toggleSidebar('viewBookmarksSidebar');
+ break;
+ case "Home":
+ BrowserHome();
+ break;
+ case "New":
+ BrowserOpenTab();
+ break;
+ case "Close":
+ BrowserCloseTabOrWindow();
+ break;
+ case "Find":
+ gFindBar.onFindCommand();
+ break;
+ case "Help":
+ openHelpLink('firefox-help');
+ break;
+ case "Open":
+ BrowserOpenFileWindow();
+ break;
+ case "Print":
+ PrintUtils.print();
+ break;
+ case "Save":
+ saveDocument(window.content.document);
+ break;
+ case "SendMail":
+ MailIntegration.sendLinkForWindow(window.content);
+ break;
+ default:
+ return;
+ }
+ evt.stopPropagation();
+ evt.preventDefault();
+}
+
+function gotoHistoryIndex(aEvent) {
+ let index = aEvent.target.getAttribute("index");
+ if (!index)
+ return false;
+
+ let where = whereToOpenLink(aEvent);
+
+ if (where == "current") {
+ // Normal click. Go there in the current tab and update session history.
+
+ try {
+ gBrowser.gotoIndex(index);
+ }
+ catch(ex) {
+ return false;
+ }
+ return true;
+ }
+ // Modified click. Go there in a new tab/window.
+
+ duplicateTabIn(gBrowser.selectedTab, where, index - gBrowser.sessionHistory.index);
+ return true;
+}
+
+function BrowserForward(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goForward();
+ }
+ catch(ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, 1);
+ }
+}
+
+function BrowserBack(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goBack();
+ }
+ catch(ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, -1);
+ }
+}
+
+function BrowserHandleBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserBack();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageUp");
+ break;
+ }
+}
+
+function BrowserHandleShiftBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserForward();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageDown");
+ break;
+ }
+}
+
+function BrowserStop() {
+ const stopFlags = nsIWebNavigation.STOP_ALL;
+ gBrowser.webNavigation.stop(stopFlags);
+}
+
+function BrowserReloadOrDuplicate(aEvent) {
+ var backgroundTabModifier = aEvent.button == 1 ||
+#ifdef XP_MACOSX
+ aEvent.metaKey;
+#else
+ aEvent.ctrlKey;
+#endif
+ if (aEvent.shiftKey && !backgroundTabModifier) {
+ BrowserReloadSkipCache();
+ return;
+ }
+
+ let where = whereToOpenLink(aEvent, false, true);
+ if (where == "current")
+ BrowserReload();
+ else
+ duplicateTabIn(gBrowser.selectedTab, where);
+}
+
+function BrowserReload() {
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+function BrowserReloadSkipCache() {
+ // Bypass proxy and cache.
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+var BrowserHome = BrowserGoHome;
+function BrowserGoHome(aEvent) {
+ if (aEvent && "button" in aEvent &&
+ aEvent.button == 2) // right-click: do nothing
+ return;
+
+ var homePage = gHomeButton.getHomePage();
+ var where = whereToOpenLink(aEvent, false, true);
+ var urls;
+
+ // Home page should open in a new tab when current tab is an app tab
+ if (where == "current" &&
+ gBrowser &&
+ gBrowser.selectedTab.pinned)
+ where = "tab";
+
+ // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages
+ switch (where) {
+ case "current":
+ loadOneOrMoreURIs(homePage);
+ break;
+ case "tabshifted":
+ case "tab":
+ urls = homePage.split("|");
+ var loadInBackground = getBoolPref("browser.tabs.loadBookmarksInBackground", false);
+ gBrowser.loadTabs(urls, loadInBackground);
+ break;
+ case "window":
+ OpenBrowserWindow();
+ break;
+ }
+}
+
+function loadOneOrMoreURIs(aURIString)
+{
+#ifdef XP_MACOSX
+ // we're not a browser window, pass the URI string to a new browser window
+ if (window.location.href != getBrowserURL())
+ {
+ window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString);
+ return;
+ }
+#endif
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(aURIString.split("|"), false, true);
+ }
+ catch (e) {
+ }
+}
+
+function focusAndSelectUrlBar() {
+ if (gURLBar) {
+ if (window.fullScreen)
+ FullScreen.mouseoverToggle(true);
+
+ gURLBar.select();
+ if (document.activeElement == gURLBar.inputField)
+ return true;
+ }
+ return false;
+}
+
+function openLocation() {
+ if (focusAndSelectUrlBar())
+ return;
+
+#ifdef XP_MACOSX
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus()
+ win.openLocation();
+ }
+ else {
+ // If there are no open browser windows, open a new one
+ win = window.openDialog("chrome://browser/content/", "_blank",
+ "chrome,all,dialog=no", BROWSER_NEW_TAB_URL);
+ win.addEventListener("load", openLocationCallback, false);
+ }
+ return;
+ }
+#endif
+ openDialog("chrome://browser/content/openLocation.xul", "_blank",
+ "chrome,modal,titlebar", window);
+}
+
+function openLocationCallback()
+{
+ // make sure the DOM is ready
+ setTimeout(function() { this.openLocation(); }, 0);
+}
+
+function BrowserOpenTab()
+{
+ openUILinkIn(BROWSER_NEW_TAB_URL, "tab");
+}
+
+/* Called from the openLocation dialog. This allows that dialog to instruct
+ its opener to open a new window and then step completely out of the way.
+ Anything less byzantine is causing horrible crashes, rather believably,
+ though oddly only on Linux. */
+function delayedOpenWindow(chrome, flags, href, postData)
+{
+ // The other way to use setTimeout,
+ // setTimeout(openDialog, 10, chrome, "_blank", flags, url),
+ // doesn't work here. The extra "magic" extra argument setTimeout adds to
+ // the callback function would confuse gBrowserInit.onLoad() by making
+ // window.arguments[1] be an integer instead of null.
+ setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10);
+}
+
+/* Required because the tab needs time to set up its content viewers and get the load of
+ the URI kicked off before becoming the active content area. */
+function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup)
+{
+ gBrowser.loadOneTab(aUrl, {
+ referrerURI: aReferrer,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: false,
+ allowThirdPartyFixup: aAllowThirdPartyFixup});
+}
+
+var gLastOpenDirectory = {
+ _lastDir: null,
+ get path() {
+ if (!this._lastDir || !this._lastDir.exists()) {
+ try {
+ this._lastDir = gPrefService.getComplexValue("browser.open.lastDir",
+ Ci.nsILocalFile);
+ if (!this._lastDir.exists())
+ this._lastDir = null;
+ }
+ catch(e) {}
+ }
+ return this._lastDir;
+ },
+ set path(val) {
+ try {
+ if (!val || !val.isDirectory())
+ return;
+ } catch(e) {
+ return;
+ }
+ this._lastDir = val.clone();
+
+ // Don't save the last open directory pref inside the Private Browsing mode
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile,
+ this._lastDir);
+ },
+ reset: function() {
+ this._lastDir = null;
+ }
+};
+
+function BrowserOpenFileWindow()
+{
+ // Get filepicker component.
+ try {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ try {
+ if (fp.file) {
+ gLastOpenDirectory.path =
+ fp.file.parent.QueryInterface(Ci.nsILocalFile);
+ }
+ } catch (ex) {
+ }
+ openUILinkIn(fp.fileURL.spec, "current");
+ }
+ };
+
+ fp.init(window, gNavigatorBundle.getString("openFile"),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
+ nsIFilePicker.filterImages | nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML);
+ fp.displayDirectory = gLastOpenDirectory.path;
+ fp.open(fpCallback);
+ } catch (ex) {
+ }
+}
+
+function BrowserCloseTabOrWindow() {
+#ifdef XP_MACOSX
+ // If we're not a browser window, just close the window
+ if (window.location.href != getBrowserURL()) {
+ closeWindow(true);
+ return;
+ }
+#endif
+
+ // If the current tab is the last one, this will close the window.
+ gBrowser.removeCurrentTab({animate: true});
+}
+
+function BrowserTryToCloseWindow()
+{
+ if (WindowIsClosing())
+ window.close(); // WindowIsClosing does all the necessary checks
+}
+
+function loadURI(uri, referrer, postData, allowThirdPartyFixup) {
+ if (postData === undefined)
+ postData = null;
+
+ var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (allowThirdPartyFixup) {
+ flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+
+ try {
+ gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData);
+ } catch (e) {}
+}
+
+function getShortcutOrURI(aURL, aPostDataRef, aMayInheritPrincipal) {
+ // Initialize outparam to false
+ if (aMayInheritPrincipal)
+ aMayInheritPrincipal.value = false;
+
+ var shortcutURL = null;
+ var keyword = aURL;
+ var param = "";
+
+ var offset = aURL.indexOf(" ");
+ if (offset > 0) {
+ keyword = aURL.substr(0, offset);
+ param = aURL.substr(offset + 1);
+ }
+
+ if (!aPostDataRef)
+ aPostDataRef = {};
+
+ var engine = Services.search.getEngineByAlias(keyword);
+ if (engine) {
+ var submission = engine.getSubmission(param);
+ aPostDataRef.value = submission.postData;
+ return submission.uri.spec;
+ }
+
+ [shortcutURL, aPostDataRef.value] =
+ PlacesUtils.getURLAndPostDataForKeyword(keyword);
+
+ if (!shortcutURL)
+ return aURL;
+
+ var postData = "";
+ if (aPostDataRef.value)
+ postData = unescape(aPostDataRef.value);
+
+ if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) {
+ var charset = "";
+ const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
+ var matches = shortcutURL.match(re);
+ if (matches)
+ [, shortcutURL, charset] = matches;
+ else {
+ // Try to get the saved character-set.
+ try {
+ // makeURI throws if URI is invalid.
+ // Will return an empty string if character-set is not found.
+ charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL));
+ } catch (e) {}
+ }
+
+ // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
+ // escape() works in those cases, but it doesn't uri-encode +, @, and /.
+ // Therefore we need to manually replace these ASCII characters by their
+ // encodeURIComponent result, to match the behavior of nsEscape() with
+ // url_XPAlphas
+ var encodedParam = "";
+ if (charset && charset != "UTF-8")
+ encodedParam = escape(convertFromUnicode(charset, param)).
+ replace(/[+@\/]+/g, encodeURIComponent);
+ else // Default charset is UTF-8
+ encodedParam = encodeURIComponent(param);
+
+ shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
+
+ if (/%s/i.test(postData)) // POST keyword
+ aPostDataRef.value = getPostDataStream(postData, param, encodedParam,
+ "application/x-www-form-urlencoded");
+ }
+ else if (param) {
+ // This keyword doesn't take a parameter, but one was provided. Just return
+ // the original URL.
+ aPostDataRef.value = null;
+
+ return aURL;
+ }
+
+ // This URL came from a bookmark, so it's safe to let it inherit the current
+ // document's principal.
+ if (aMayInheritPrincipal)
+ aMayInheritPrincipal.value = true;
+
+ return shortcutURL;
+}
+
+function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
+ var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
+ dataStream.data = aStringData;
+
+ var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"].
+ createInstance(Ci.nsIMIMEInputStream);
+ mimeStream.addHeader("Content-Type", aType);
+ mimeStream.addContentLength = true;
+ mimeStream.setData(dataStream);
+ return mimeStream.QueryInterface(Ci.nsIInputStream);
+}
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function readFromClipboard()
+{
+ var url;
+
+ try {
+ // Create transferable that will transfer the text.
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+
+ trans.addDataFlavor("text/unicode");
+
+ // If available, use selection clipboard, otherwise global one
+ if (Services.clipboard.supportsSelectionClipboard())
+ Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard);
+ else
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ var data = {};
+ var dataLen = {};
+ trans.getTransferData("text/unicode", data, dataLen);
+
+ if (data) {
+ data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
+ url = data.data.substring(0, dataLen.value / 2);
+ }
+ } catch (ex) {
+ }
+
+ return url;
+}
+
+function BrowserViewSourceOfDocument(aDocument)
+{
+ var pageCookie;
+ var webNav;
+
+ // Get the document charset
+ var docCharset = "charset=" + aDocument.characterSet;
+
+ // Get the nsIWebNavigation associated with the document
+ try {
+ var win;
+ var ifRequestor;
+
+ // Get the DOMWindow for the requested document. If the DOMWindow
+ // cannot be found, then just use the content window...
+ //
+ // XXX: This is a bit of a hack...
+ win = aDocument.defaultView;
+ if (win == window) {
+ win = content;
+ }
+ ifRequestor = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+
+ webNav = ifRequestor.getInterface(nsIWebNavigation);
+ } catch(err) {
+ // If nsIWebNavigation cannot be found, just get the one for the whole
+ // window...
+ webNav = gBrowser.webNavigation;
+ }
+ //
+ // Get the 'PageDescriptor' for the current document. This allows the
+ // view-source to access the cached copy of the content rather than
+ // refetching it from the network...
+ //
+ try{
+ var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor);
+
+ pageCookie = PageLoader.currentDescriptor;
+ } catch(err) {
+ // If no page descriptor is available, just use the view-source URL...
+ }
+
+ top.gViewSourceUtils.viewSource(webNav.currentURI.spec, pageCookie, aDocument);
+}
+
+// doc - document to use for source, or null for this window's document
+// initialTab - name of the initial tab to display, or null for the first tab
+// imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
+function BrowserPageInfo(doc, initialTab, imageElement) {
+ var args = {doc: doc, initialTab: initialTab, imageElement: imageElement};
+ var windows = Services.wm.getEnumerator("Browser:page-info");
+
+ var documentURL = doc ? doc.location : window.content.document.location;
+
+ // Check for windows matching the url
+ while (windows.hasMoreElements()) {
+ var currentWindow = windows.getNext();
+ if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) {
+ currentWindow.focus();
+ currentWindow.resetPageInfo(args);
+ return currentWindow;
+ }
+ }
+
+ // We didn't find a matching window, so open a new one.
+ return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "",
+ "chrome,toolbar,dialog=no,resizable", args);
+}
+
+function URLBarSetURI(aURI) {
+ var value = gBrowser.userTypedValue;
+ var valid = false;
+
+ if (value == null) {
+ let uri = aURI || gBrowser.currentURI;
+ // Strip off "wyciwyg://" and passwords for the location bar
+ try {
+ uri = Services.uriFixup.createExposableURI(uri);
+ } catch (e) {}
+
+ // Replace initial page URIs with an empty string
+ // only if there's no opener (bug 370555).
+ // Bug 863515 - Make content.opener checks work in electrolysis.
+ if (gInitialPages.indexOf(uri.spec) != -1)
+ value = !gMultiProcessBrowser && content.opener ? uri.spec : "";
+ else
+ value = losslessDecodeURI(uri);
+
+ valid = !isBlankPageURL(uri.spec);
+ }
+
+ let isDifferentValidValue = valid && value != gURLBar.value;
+ gURLBar.value = value;
+ gURLBar.valueIsTyped = !valid;
+ if (isDifferentValidValue) {
+ gURLBar.selectionStart = gURLBar.selectionEnd = 0;
+ }
+
+ SetPageProxyState(valid ? "valid" : "invalid");
+}
+
+function losslessDecodeURI(aURI) {
+ let scheme = aURI.scheme;
+ let decodeASCIIOnly = !(/(https|http|file|ftp)/i.test(scheme));
+
+ var value = aURI.spec;
+
+ // Try to decode as UTF-8 if there's no encoding sequence that we would break.
+ if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) {
+ if (decodeASCIIOnly) {
+ // This only decodes ASCII characters (hex) 20-7e, except 25 (%).
+ // This avoids both cases stipulated below (%-related issues, and \r, \n
+ // and \t, which would be %0d, %0a and %09, respectively) as well as any
+ // non-US-ascii characters.
+ value = value.replace(/%(2[0-4]|2[6-9a-f]|[3-6][0-9a-f]|7[0-9a-e])/g, decodeURI);
+ } else {
+ try {
+ value = decodeURI(value)
+ // 1. decodeURI decodes %25 to %, which creates unintended
+ // encoding sequences. Re-encode it, unless it's part of
+ // a sequence that survived decodeURI, i.e. one for:
+ // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
+ // (RFC 3987 section 3.2)
+ // 2. Re-encode whitespace so that it doesn't get eaten away
+ // by the location bar (bug 410726).
+ .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig,
+ encodeURIComponent);
+ } catch (e) {}
+ }
+ }
+
+ // Encode invisible characters (C0/C1 control characters, U+007F [DEL],
+ // U+00A0 [no-break space], line and paragraph separator,
+ // object replacement character) (bug 452979, bug 909264)
+ value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\ufffc]/g,
+ encodeURIComponent);
+
+ // Encode default ignorable characters (bug 546013)
+ // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186).
+ // This includes all bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ value = value.replace(/[\u00ad\u034f\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g,
+ encodeURIComponent);
+ return value;
+}
+
+function UpdateUrlbarSearchSplitterState()
+{
+ var splitter = document.getElementById("urlbar-search-splitter");
+ var urlbar = document.getElementById("urlbar-container");
+ var searchbar = document.getElementById("search-container");
+ var stop = document.getElementById("stop-button");
+
+ var ibefore = null;
+ if (urlbar && searchbar) {
+ if (urlbar.nextSibling == searchbar ||
+ urlbar.getAttribute("combined") &&
+ stop && stop.nextSibling == searchbar)
+ ibefore = searchbar;
+ else if (searchbar.nextSibling == urlbar)
+ ibefore = urlbar;
+ }
+
+ if (ibefore) {
+ if (!splitter) {
+ splitter = document.createElement("splitter");
+ splitter.id = "urlbar-search-splitter";
+ splitter.setAttribute("resizebefore", "flex");
+ splitter.setAttribute("resizeafter", "flex");
+ splitter.setAttribute("skipintoolbarset", "true");
+ splitter.className = "chromeclass-toolbar-additional";
+ }
+ urlbar.parentNode.insertBefore(splitter, ibefore);
+ } else if (splitter)
+ splitter.parentNode.removeChild(splitter);
+}
+
+function setUrlAndSearchBarWidthForConditionalForwardButton() {
+ // Workaround for bug 694084: Showing/hiding the conditional forward button resizes
+ // the search bar when the url/search bar splitter hasn't been used.
+ var urlbarContainer = document.getElementById("urlbar-container");
+ var searchbarContainer = document.getElementById("search-container");
+ if (!urlbarContainer ||
+ !searchbarContainer ||
+ urlbarContainer.hasAttribute("width") ||
+ searchbarContainer.hasAttribute("width") ||
+ urlbarContainer.parentNode != searchbarContainer.parentNode)
+ return;
+ urlbarContainer.style.width = searchbarContainer.style.width = "";
+ var urlbarWidth = urlbarContainer.clientWidth;
+ var searchbarWidth = searchbarContainer.clientWidth;
+ urlbarContainer.style.width = urlbarWidth + "px";
+ searchbarContainer.style.width = searchbarWidth + "px";
+}
+
+function UpdatePageProxyState()
+{
+ if (gURLBar && gURLBar.value != gLastValidURLStr)
+ SetPageProxyState("invalid");
+}
+
+function SetPageProxyState(aState)
+{
+ BookmarkingUI.onPageProxyStateChanged(aState);
+
+ if (!gURLBar)
+ return;
+
+ if (!gProxyFavIcon)
+ gProxyFavIcon = document.getElementById("page-proxy-favicon");
+
+ gURLBar.setAttribute("pageproxystate", aState);
+ gProxyFavIcon.setAttribute("pageproxystate", aState);
+
+ // the page proxy state is set to valid via OnLocationChange, which
+ // gets called when we switch tabs.
+ if (aState == "valid") {
+ gLastValidURLStr = gURLBar.value;
+ gURLBar.addEventListener("input", UpdatePageProxyState, false);
+ PageProxySetIcon(gBrowser.getIcon());
+ } else if (aState == "invalid") {
+ gURLBar.removeEventListener("input", UpdatePageProxyState, false);
+ PageProxyClearIcon();
+ }
+}
+
+function PageProxySetIcon (aURL)
+{
+ if (!gProxyFavIcon)
+ return;
+
+ if (gBrowser.selectedBrowser.contentDocument instanceof ImageDocument) {
+ // PageProxyClearIcon();
+ gProxyFavIcon.setAttribute("src", "chrome://browser/skin/imagedocument.png");
+ return;
+ }
+
+ if (!aURL)
+ PageProxyClearIcon();
+ else if (gProxyFavIcon.getAttribute("src") != aURL)
+ gProxyFavIcon.setAttribute("src", aURL);
+}
+
+function PageProxyClearIcon ()
+{
+ gProxyFavIcon.removeAttribute("src");
+}
+
+
+function PageProxyClickHandler(aEvent)
+{
+ if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
+ middleMousePaste(aEvent);
+}
+
+/**
+ * Handle load of some pages (about:*) so that we can make modifications
+ * to the DOM for unprivileged pages.
+ */
+function BrowserOnAboutPageLoad(doc) {
+ if (doc.documentURI.toLowerCase() == "about:home") {
+ let ss = Components.classes["@mozilla.org/browser/sessionstore;1"].
+ getService(Components.interfaces.nsISessionStore);
+ if (ss.canRestoreLastSession &&
+ !PrivateBrowsingUtils.isWindowPrivate(window))
+ doc.getElementById("launcher").setAttribute("session", "true");
+
+ // Inject search engine and snippets URL.
+ let docElt = doc.documentElement;
+ // set the following attributes BEFORE searchEngineURL, which triggers to
+ // show the snippets when it's set.
+ docElt.setAttribute("snippetsURL", AboutHomeUtils.snippetsURL);
+ if (AboutHomeUtils.showKnowYourRights) {
+ docElt.setAttribute("showKnowYourRights", "true");
+ // Set pref to indicate we've shown the notification.
+ let currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
+ }
+ docElt.setAttribute("snippetsVersion", AboutHomeUtils.snippetsVersion);
+
+ function updateSearchEngine() {
+ let engine = AboutHomeUtils.defaultSearchEngine;
+ docElt.setAttribute("searchEngineName", engine.name);
+ docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+ docElt.setAttribute("searchEngineURL", engine.searchURL);
+ }
+ updateSearchEngine();
+
+ // Listen for the event that's triggered when the user changes search engine.
+ // At this point we simply reload about:home to reflect the change.
+ Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false);
+
+ // Remove the observer when the page is reloaded or closed.
+ doc.defaultView.addEventListener("pagehide", function removeObserver() {
+ doc.defaultView.removeEventListener("pagehide", removeObserver);
+ Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified");
+ }, false);
+ }
+}
+
+/**
+ * Handle command events bubbling up from error page content
+ */
+let BrowserOnClick = {
+ handleEvent: function BrowserOnClick_handleEvent(aEvent) {
+ if (!aEvent.isTrusted || // Don't trust synthetic events
+ aEvent.button == 2 || aEvent.target.localName != "button") {
+ return;
+ }
+
+ let originalTarget = aEvent.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+
+ // If the event came from an ssl error page, it is probably either the "Add
+ // Exception…" or "Get me out of here!" button
+ if (ownerDoc.documentURI.startsWith("about:certerror")) {
+ this.onAboutCertError(originalTarget, ownerDoc);
+ }
+ else if (ownerDoc.documentURI.startsWith("about:neterror")) {
+ this.onAboutNetError(originalTarget, ownerDoc);
+ }
+ else if (ownerDoc.documentURI.toLowerCase() == "about:home") {
+ this.onAboutHome(originalTarget, ownerDoc);
+ }
+ },
+
+ onAboutCertError: function BrowserOnClick_onAboutCertError(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+ let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
+
+ switch (elmId) {
+ case "exceptionDialogButton":
+ let params = { exceptionAdded : false };
+
+ try {
+ switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
+ case 2 : // Pre-fetch & pre-populate
+ params.prefetchCert = true;
+ case 1 : // Pre-populate
+ params.location = aOwnerDoc.location.href;
+ }
+ } catch (e) {
+ Components.utils.reportError("Couldn't get ssl_override pref: " + e);
+ }
+
+ window.openDialog('chrome://pippki/content/exceptionDialog.xul',
+ '','chrome,centerscreen,modal', params);
+
+ // If the user added the exception cert, attempt to reload the page
+ if (params.exceptionAdded) {
+ aOwnerDoc.location.reload();
+ }
+ break;
+
+ case "getMeOutOfHereButton":
+ getMeOutOfHere();
+ break;
+
+ case "technicalContent":
+ break;
+
+ case "expertContent":
+ break;
+
+ }
+ },
+
+ onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+ if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI))
+ return;
+ Services.io.offline = false;
+ },
+
+ onAboutHome: function BrowserOnClick_onAboutHome(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+
+ switch (elmId) {
+ case "restorePreviousSession":
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ if (ss.canRestoreLastSession) {
+ ss.restoreLastSession();
+ }
+ aOwnerDoc.getElementById("launcher").removeAttribute("session");
+ break;
+
+ case "downloads":
+ BrowserDownloadsUI();
+ break;
+
+ case "bookmarks":
+ PlacesCommandHook.showPlacesOrganizer("AllBookmarks");
+ break;
+
+ case "history":
+ PlacesCommandHook.showPlacesOrganizer("History");
+ break;
+
+ case "addons":
+ BrowserOpenAddonsMgr();
+ break;
+
+ case "sync":
+ openPreferences("paneSync");
+ break;
+
+ case "settings":
+ openPreferences();
+ break;
+ }
+ },
+};
+
+/**
+ * Re-direct the browser to a known-safe page. This function is
+ * used when, for example, the user browses to a known malware page
+ * and is presented with about:blocked. The "Get me out of here!"
+ * button should take the user to the default start page so that even
+ * when their own homepage is infected, we can get them somewhere safe.
+ */
+function getMeOutOfHere() {
+ try {
+ let toBlank = Services.prefs.getBoolPref("browser.escape_to_blank");
+ if (toBlank) {
+ content.location = "about:logopage";
+ return;
+ }
+ } catch(e) {
+ Components.utils.reportError("Couldn't get escape pref: " + e);
+ }
+ // Get the start page from the *default* pref branch, not the user's
+ var prefs = Services.prefs.getDefaultBranch(null);
+ var url = BROWSER_NEW_TAB_URL;
+ try {
+ url = prefs.getComplexValue("browser.startup.homepage",
+ Ci.nsIPrefLocalizedString).data;
+ // If url is a pipe-delimited set of pages, just take the first one.
+ if (url.includes("|"))
+ url = url.split("|")[0];
+ } catch(e) {
+ Components.utils.reportError("Couldn't get homepage pref: " + e);
+ }
+ content.location = url;
+}
+
+function BrowserFullScreen()
+{
+ window.fullScreen = !window.fullScreen;
+}
+
+function onFullScreen(event) {
+ FullScreen.toggle(event);
+}
+
+function onMozEnteredDomFullscreen(event) {
+ FullScreen.enterDomFullscreen(event);
+}
+
+function getWebNavigation()
+{
+ return gBrowser.webNavigation;
+}
+
+function BrowserReloadWithFlags(reloadFlags) {
+ /* First, we'll try to use the session history object to reload so
+ * that framesets are handled properly. If we're in a special
+ * window (such as view-source) that has no session history, fall
+ * back on using the web navigation's reload method.
+ */
+
+ var webNav = gBrowser.webNavigation;
+ try {
+ var sh = webNav.sessionHistory;
+ if (sh)
+ webNav = sh.QueryInterface(nsIWebNavigation);
+ } catch (e) {
+ }
+
+ try {
+ webNav.reload(reloadFlags);
+ } catch (e) {
+ }
+}
+
+var PrintPreviewListener = {
+ _printPreviewTab: null,
+ _tabBeforePrintPreview: null,
+
+ getPrintPreviewBrowser: function () {
+ if (!this._printPreviewTab) {
+ this._tabBeforePrintPreview = gBrowser.selectedTab;
+ this._printPreviewTab = gBrowser.loadOneTab("about:blank",
+ { inBackground: false });
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ return gBrowser.getBrowserForTab(this._printPreviewTab);
+ },
+ getSourceBrowser: function () {
+ return this._tabBeforePrintPreview ?
+ this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
+ },
+ getNavToolbox: function () {
+ return gNavToolbox;
+ },
+ onEnter: function () {
+ // We might have accidentally switched tabs since the user invoked print
+ // preview
+ if (gBrowser.selectedTab != this._printPreviewTab) {
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ gInPrintPreviewMode = true;
+ this._toggleAffectedChrome();
+ },
+ onExit: function () {
+ gBrowser.selectedTab = this._tabBeforePrintPreview;
+ this._tabBeforePrintPreview = null;
+ gInPrintPreviewMode = false;
+ this._toggleAffectedChrome();
+ gBrowser.removeTab(this._printPreviewTab);
+ this._printPreviewTab = null;
+ },
+ _toggleAffectedChrome: function () {
+ gNavToolbox.collapsed = gInPrintPreviewMode;
+
+ if (gInPrintPreviewMode)
+ this._hideChrome();
+ else
+ this._showChrome();
+
+ if (this._chromeState.sidebarOpen)
+ toggleSidebar(this._sidebarCommand);
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+ },
+ _hideChrome: function () {
+ this._chromeState = {};
+
+ var sidebar = document.getElementById("sidebar-box");
+ this._chromeState.sidebarOpen = !sidebar.hidden;
+ this._sidebarCommand = sidebar.getAttribute("sidebarcommand");
+
+ var notificationBox = gBrowser.getNotificationBox();
+ this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
+ notificationBox.notificationsHidden = true;
+
+ document.getElementById("sidebar").setAttribute("src", "about:blank");
+ var addonBar = document.getElementById("addon-bar");
+ this._chromeState.addonBarOpen = !addonBar.collapsed;
+ addonBar.collapsed = true;
+ gBrowser.updateWindowResizers();
+
+ this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
+ if (gFindBarInitialized)
+ gFindBar.close();
+
+ var globalNotificationBox = document.getElementById("global-notificationbox");
+ this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden;
+ globalNotificationBox.notificationsHidden = true;
+
+ this._chromeState.syncNotificationsOpen = false;
+ var syncNotifications = document.getElementById("sync-notifications");
+ if (syncNotifications) {
+ this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden;
+ syncNotifications.notificationsHidden = true;
+ }
+ },
+ _showChrome: function () {
+ if (this._chromeState.notificationsOpen)
+ gBrowser.getNotificationBox().notificationsHidden = false;
+
+ if (this._chromeState.addonBarOpen) {
+ document.getElementById("addon-bar").collapsed = false;
+ gBrowser.updateWindowResizers();
+ }
+
+ if (this._chromeState.findOpen)
+ gFindBar.open();
+
+ if (this._chromeState.globalNotificationsOpen)
+ document.getElementById("global-notificationbox").notificationsHidden = false;
+
+ if (this._chromeState.syncNotificationsOpen)
+ document.getElementById("sync-notifications").notificationsHidden = false;
+ }
+}
+
+function getMarkupDocumentViewer()
+{
+ return gBrowser.markupDocumentViewer;
+}
+
+// This function is obsolete. Newer code should use <tooltip page="true"/> instead.
+function FillInHTMLTooltip(tipElement)
+{
+ document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement);
+}
+
+var browserDragAndDrop = {
+ canDropLink: function (aEvent) Services.droppedLinkHandler.canDropLink(aEvent, true),
+
+ dragOver: function (aEvent)
+ {
+ if (this.canDropLink(aEvent)) {
+ aEvent.preventDefault();
+ }
+ },
+
+ drop: function (aEvent, aName, aDisallowInherit) {
+ return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit);
+ }
+};
+
+var homeButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ // disallow setting home pages that inherit the principal
+ let url = browserDragAndDrop.drop(aEvent, {}, true);
+ setTimeout(openHomeDialog, 0, url);
+ },
+
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+function openHomeDialog(aURL)
+{
+ var promptTitle = gNavigatorBundle.getString("droponhometitle");
+ var promptMsg = gNavigatorBundle.getString("droponhomemsg");
+ var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg,
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null, null, null, null, {value:0});
+
+ if (pressedVal == 0) {
+ try {
+ var str = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = aURL;
+ gPrefService.setComplexValue("browser.startup.homepage",
+ Components.interfaces.nsISupportsString, str);
+ } catch (ex) {
+ dump("Failed to set the home page.\n"+ex+"\n");
+ }
+ }
+}
+
+var bookmarksButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ let name = { };
+ let url = browserDragAndDrop.drop(aEvent, name);
+ try {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(url)
+ , title: name
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window);
+ } catch(ex) { }
+ },
+
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+var newTabButtonObserver = {
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+
+ onDragExit: function (aEvent)
+ {
+ },
+
+ onDrop: function (aEvent)
+ {
+ let url = browserDragAndDrop.drop(aEvent, { });
+ var postData = {};
+ url = getShortcutOrURI(url, postData);
+ if (url) {
+ // allow third-party services to fixup this URL
+ openNewTabWith(url, null, postData.value, aEvent, true);
+ }
+ }
+}
+
+var newWindowButtonObserver = {
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit: function (aEvent)
+ {
+ },
+ onDrop: function (aEvent)
+ {
+ let url = browserDragAndDrop.drop(aEvent, { });
+ var postData = {};
+ url = getShortcutOrURI(url, postData);
+ if (url) {
+ // allow third-party services to fixup this URL
+ openNewWindowWith(url, null, postData.value, true);
+ }
+ }
+}
+
+const DOMLinkHandler = {
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "DOMLinkAdded":
+ this.onLinkAdded(event);
+ break;
+ }
+ },
+ getLinkIconURI: function(aLink) {
+ let targetDoc = aLink.ownerDocument;
+ var uri = makeURI(aLink.href, targetDoc.characterSet);
+
+ // Verify that the load of this icon is legal.
+ // Some error or special pages can load their favicon.
+ // To be on the safe side, only allow chrome:// favicons.
+ var isAllowedPage = [
+ /^about:neterror\?/,
+ /^about:blocked\?/,
+ /^about:certerror\?/,
+ /^about:home$/,
+ ].some(function (re) re.test(targetDoc.documentURI));
+
+ if (!isAllowedPage || !uri.schemeIs("chrome")) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ } catch(e) {
+ return null;
+ }
+ }
+
+ try {
+ var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
+ getService(Ci.nsIContentPolicy);
+ } catch(e) {
+ return null; // Refuse to load if we can't do a security check.
+ }
+
+ // Security says okay, now ask content policy
+ if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+ uri, targetDoc.documentURIObject,
+ aLink, aLink.type, null)
+ != Ci.nsIContentPolicy.ACCEPT)
+ return null;
+
+ try {
+ uri.userPass = "";
+ } catch(e) {
+ // some URIs are immutable
+ }
+ return uri;
+ },
+ onLinkAdded: function (event) {
+ var link = event.originalTarget;
+ var rel = link.rel && link.rel.toLowerCase();
+ if (!link || !link.ownerDocument || !rel || !link.href)
+ return;
+
+ var feedAdded = false;
+ var iconAdded = false;
+ var searchAdded = false;
+ var rels = {};
+ for (let relString of rel.split(/\s+/))
+ rels[relString] = true;
+
+ for (let relVal in rels) {
+ switch (relVal) {
+ case "feed":
+ case "alternate":
+ if (!feedAdded) {
+ if (!rels.feed && rels.alternate && rels.stylesheet)
+ break;
+
+ if (isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) {
+ FeedHandler.addFeed(link, link.ownerDocument);
+ feedAdded = true;
+ }
+ }
+ break;
+ case "icon":
+ if (!iconAdded) {
+ if (!gPrefService.getBoolPref("browser.chrome.site_icons"))
+ break;
+
+ var uri = this.getLinkIconURI(link);
+ if (!uri)
+ break;
+
+ if (gBrowser.isFailedIcon(uri))
+ break;
+
+ var browserIndex = gBrowser.getBrowserIndexForDocument(link.ownerDocument);
+ // no browser? no favicon.
+ if (browserIndex == -1)
+ break;
+
+ let tab = gBrowser.tabs[browserIndex];
+ gBrowser.setIcon(tab, uri.spec);
+ iconAdded = true;
+ }
+ break;
+ case "search":
+ if (!searchAdded) {
+ var type = link.type && link.type.toLowerCase();
+ type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
+
+ if (type == "application/opensearchdescription+xml" && link.title &&
+ /^(?:https?|ftp):/i.test(link.href) &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ var engine = { title: link.title, href: link.href };
+ BrowserSearch.addEngine(engine, link.ownerDocument);
+ searchAdded = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+const BrowserSearch = {
+ addEngine: function(engine, targetDoc) {
+ if (!this.searchBar)
+ return;
+
+ var browser = gBrowser.getBrowserForDocument(targetDoc);
+ // ignore search engines from subframes (see bug 479408)
+ if (!browser)
+ return;
+
+ // Check to see whether we've already added an engine with this title
+ if (browser.engines) {
+ if (browser.engines.some(function (e) e.title == engine.title))
+ return;
+ }
+
+ // Append the URI and an appropriate title to the browser data.
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ var iconURL = null;
+ if (gBrowser.shouldLoadFavIcon(targetDoc.documentURIObject))
+ iconURL = targetDoc.documentURIObject.prePath + "/favicon.ico";
+
+ var hidden = false;
+ // If this engine (identified by title) is already in the list, add it
+ // to the list of hidden engines rather than to the main list.
+ // XXX This will need to be changed when engines are identified by URL;
+ // see bug 335102.
+ if (Services.search.getEngineByName(engine.title))
+ hidden = true;
+
+ var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
+
+ engines.push({ uri: engine.href,
+ title: engine.title,
+ icon: iconURL });
+
+ if (hidden)
+ browser.hiddenEngines = engines;
+ else
+ browser.engines = engines;
+ },
+
+ /**
+ * Gives focus to the search bar, if it is present on the toolbar, or loads
+ * the default engine's search form otherwise. For Mac, opens a new window
+ * or focuses an existing window, if necessary.
+ */
+ webSearch: function BrowserSearch_webSearch() {
+#ifdef XP_MACOSX
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus();
+ win.BrowserSearch.webSearch();
+ } else {
+ // If there are no open browser windows, open a new one
+ var observer = function observer(subject, topic, data) {
+ if (subject == win) {
+ BrowserSearch.webSearch();
+ Services.obs.removeObserver(observer, "browser-delayed-startup-finished");
+ }
+ }
+ win = window.openDialog(getBrowserURL(), "_blank",
+ "chrome,all,dialog=no", "about:blank");
+ Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
+ }
+ return;
+ }
+#endif
+ var searchBar = this.searchBar;
+ if (searchBar && window.fullScreen)
+ FullScreen.mouseoverToggle(true);
+ if (searchBar)
+ searchBar.select();
+ if (!searchBar || document.activeElement != searchBar.textbox.inputField)
+ openUILinkIn(Services.search.defaultEngine.searchForm, "current");
+ },
+
+ /**
+ * Loads a search results page, given a set of search terms. Uses the current
+ * engine if the search bar is visible, or the default engine otherwise.
+ *
+ * @param searchText
+ * The search terms to use for the search.
+ *
+ * @param useNewTab
+ * Boolean indicating whether or not the search should load in a new
+ * tab.
+ *
+ * @param purpose [optional]
+ * A string meant to indicate the context of the search request. This
+ * allows the search service to provide a different nsISearchSubmission
+ * depending on e.g. where the search is triggered in the UI.
+ *
+ * @return string Name of the search engine used to perform a search or null
+ * if a search was not performed.
+ */
+ loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
+ var engine;
+
+ // If the search bar is visible, use the current engine, otherwise, fall
+ // back to the default engine.
+ if (isElementVisible(this.searchBar))
+ engine = Services.search.currentEngine;
+ else
+ engine = Services.search.defaultEngine;
+
+ var submission = engine.getSubmission(searchText, null, purpose); // HTML response
+
+ // getSubmission can return null if the engine doesn't have a URL
+ // with a text/html response type. This is unlikely (since
+ // SearchService._addEngineToStore() should fail for such an engine),
+ // but let's be on the safe side.
+ if (!submission) {
+ return null;
+ }
+
+ let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
+ openLinkIn(submission.uri.spec,
+ useNewTab ? "tab" : "current",
+ { postData: submission.postData,
+ inBackground: inBackground,
+ relatedToCurrent: true });
+
+ return engine.name;
+ },
+
+ /**
+ * Perform a search initiated from the context menu.
+ *
+ * This should only be called from the context menu. See
+ * BrowserSearch.loadSearch for the preferred API.
+ */
+ loadSearchFromContext: function (terms) {
+ let engine = BrowserSearch.loadSearch(terms, true, "contextmenu");
+ },
+
+ /**
+ * Returns the search bar element if it is present in the toolbar, null otherwise.
+ */
+ get searchBar() {
+ return document.getElementById("searchbar");
+ },
+
+ loadAddEngines: function BrowserSearch_loadAddEngines() {
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+ var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true);
+ openUILinkIn(searchEnginesURL, where);
+ },
+};
+
+function FillHistoryMenu(aParent) {
+ // Lazily add the hover listeners on first showing and never remove them
+ if (!aParent.hasStatusListener) {
+ // Show history item's uri in the status bar when hovering, and clear on exit
+ aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
+ // Only the current page should have the checked attribute, so skip it
+ if (!aEvent.target.hasAttribute("checked"))
+ XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
+ }, false);
+ aParent.addEventListener("DOMMenuItemInactive", function() {
+ XULBrowserWindow.setOverLink("");
+ }, false);
+
+ aParent.hasStatusListener = true;
+ }
+
+ // Remove old entries if any
+ var children = aParent.childNodes;
+ for (var i = children.length - 1; i >= 0; --i) {
+ if (children[i].hasAttribute("index"))
+ aParent.removeChild(children[i]);
+ }
+
+ var webNav = gBrowser.webNavigation;
+ var sessionHistory = webNav.sessionHistory;
+
+ var count = sessionHistory.count;
+ if (count <= 1) // don't display the popup for a single item
+ return false;
+
+ const MAX_HISTORY_MENU_ITEMS = 15;
+ var index = sessionHistory.index;
+ var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
+ var start = Math.max(index - half_length, 0);
+ var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count);
+ if (end == count)
+ start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
+
+ var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
+ var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
+ var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
+
+ for (var j = end - 1; j >= start; j--) {
+ let item = document.createElement("menuitem");
+ let entry = sessionHistory.getEntryAtIndex(j, false);
+ let uri = entry.URI.spec;
+
+ item.setAttribute("uri", uri);
+ item.setAttribute("label", entry.title || uri);
+ item.setAttribute("index", j);
+
+ if (j != index) {
+ PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function (aURI) {
+ if (aURI) {
+ let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
+ item.style.listStyleImage = "url(" + iconURL + ")";
+ }
+ });
+ }
+
+ if (j < index) {
+ item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipBack);
+ } else if (j == index) {
+ item.setAttribute("type", "radio");
+ item.setAttribute("checked", "true");
+ item.className = "unified-nav-current";
+ item.setAttribute("tooltiptext", tooltipCurrent);
+ } else {
+ item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipForward);
+ }
+
+ aParent.appendChild(item);
+ }
+ return true;
+}
+
+function addToUrlbarHistory(aUrlToAdd) {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window) &&
+ aUrlToAdd &&
+ !aUrlToAdd.includes(" ") &&
+ !/[\x00-\x1F]/.test(aUrlToAdd))
+ PlacesUIUtils.markPageAsTyped(aUrlToAdd);
+}
+
+function toJavaScriptConsole()
+{
+ toOpenWindowByType("global:console", "chrome://global/content/console.xul");
+}
+
+function BrowserDownloadsUI()
+{
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ openUILinkIn("about:downloads", "tab");
+ } else {
+ PlacesCommandHook.showPlacesOrganizer("Downloads");
+ }
+}
+
+function toOpenWindowByType(inType, uri, features)
+{
+ var topWindow = Services.wm.getMostRecentWindow(inType);
+
+ if (topWindow)
+ topWindow.focus();
+ else if (features)
+ window.open(uri, "_blank", features);
+ else
+ window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
+}
+
+function OpenBrowserWindow(options)
+{
+ function newDocumentShown(doc, topic, data) {
+ if (topic == "document-shown" &&
+ doc != document &&
+ doc.defaultView == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ }
+ };
+ Services.obs.addObserver(newDocumentShown, "document-shown", false);
+
+ var charsetArg = new String();
+ var handler = Components.classes["@mozilla.org/browser/clh;1"]
+ .getService(Components.interfaces.nsIBrowserHandler);
+ var defaultArgs = handler.defaultArgs;
+ var wintype = document.documentElement.getAttribute('windowtype');
+
+ var extraFeatures = "";
+ if (options && options.private) {
+ extraFeatures = ",private";
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Force the new window to load about:privatebrowsing instead of the default home page
+ defaultArgs = "about:privatebrowsing";
+ }
+ } else {
+ extraFeatures = ",non-private";
+ }
+
+ // if and only if the current window is a browser window and it has a document with a character
+ // set, then extract the current charset menu setting from the current document and use it to
+ // initialize the new browser window...
+ var win;
+ if (window && (wintype == "navigator:browser") && window.content && window.content.document)
+ {
+ var DocCharset = window.content.document.characterSet;
+ charsetArg = "charset="+DocCharset;
+
+ //we should "inherit" the charset menu setting in a new window
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg);
+ }
+ else // forget about the charset information.
+ {
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs);
+ }
+
+ return win;
+}
+
+var gCustomizeSheet = false;
+function BrowserCustomizeToolbar() {
+ // Disable the toolbar context menu items
+ var menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", true);
+
+ var cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.setAttribute("disabled", "true");
+
+ var splitter = document.getElementById("urlbar-search-splitter");
+ if (splitter)
+ splitter.parentNode.removeChild(splitter);
+
+ CombinedStopReload.uninit();
+
+ PlacesToolbarHelper.customizeStart();
+ BookmarkingUI.customizeStart();
+ DownloadsButton.customizeStart();
+
+ TabsInTitlebar.allowedBy("customizing-toolbars", false);
+
+ var customizeURL = "chrome://global/content/customizeToolbar.xul";
+ gCustomizeSheet = getBoolPref("toolbar.customization.usesheet", false);
+
+ if (gCustomizeSheet) {
+ let sheetFrame = document.createElement("iframe");
+ let panel = document.getElementById("customizeToolbarSheetPopup");
+ sheetFrame.id = "customizeToolbarSheetIFrame";
+ sheetFrame.toolbox = gNavToolbox;
+ sheetFrame.panel = panel;
+ sheetFrame.setAttribute("style", panel.getAttribute("sheetstyle"));
+ panel.appendChild(sheetFrame);
+
+ // Open the panel, but make it invisible until the iframe has loaded so
+ // that the user doesn't see a white flash.
+ panel.style.visibility = "hidden";
+ gNavToolbox.addEventListener("beforecustomization", function onBeforeCustomization() {
+ gNavToolbox.removeEventListener("beforecustomization", onBeforeCustomization, false);
+ panel.style.removeProperty("visibility");
+ }, false);
+
+ sheetFrame.setAttribute("src", customizeURL);
+
+ panel.openPopup(gNavToolbox, "after_start", 0, 0);
+ } else {
+ window.openDialog(customizeURL,
+ "CustomizeToolbar",
+ "chrome,titlebar,toolbar,location,resizable,dependent",
+ gNavToolbox);
+ }
+}
+
+function BrowserToolboxCustomizeDone(aToolboxChanged) {
+ if (gCustomizeSheet) {
+ document.getElementById("customizeToolbarSheetPopup").hidePopup();
+ let iframe = document.getElementById("customizeToolbarSheetIFrame");
+ iframe.parentNode.removeChild(iframe);
+ }
+
+ // Update global UI elements that may have been added or removed
+ if (aToolboxChanged) {
+ gURLBar = document.getElementById("urlbar");
+
+ gProxyFavIcon = document.getElementById("page-proxy-favicon");
+ gHomeButton.updateTooltip();
+ gIdentityHandler._cacheElements();
+ window.XULBrowserWindow.init();
+
+#ifndef XP_MACOSX
+ updateEditUIVisibility();
+#endif
+
+ // Hacky: update the PopupNotifications' object's reference to the iconBox,
+ // if it already exists, since it may have changed if the URL bar was
+ // added/removed.
+ if (!window.__lookupGetter__("PopupNotifications"))
+ PopupNotifications.iconBox = document.getElementById("notification-popup-box");
+ }
+
+ PlacesToolbarHelper.customizeDone();
+ BookmarkingUI.customizeDone();
+ DownloadsButton.customizeDone();
+
+ // The url bar splitter state is dependent on whether stop/reload
+ // and the location bar are combined, so we need this ordering
+ CombinedStopReload.init();
+ UpdateUrlbarSearchSplitterState();
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+
+ // Update the urlbar
+ if (gURLBar) {
+ URLBarSetURI();
+ XULBrowserWindow.asyncUpdateUI();
+ BookmarkingUI.updateStarState();
+ }
+
+ TabsInTitlebar.allowedBy("customizing-toolbars", true);
+
+ // Re-enable parts of the UI we disabled during the dialog
+ var menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", false);
+ var cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.removeAttribute("disabled");
+
+ // make sure to re-enable click-and-hold
+ if (!getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ gBrowser.selectedBrowser.focus();
+}
+
+function BrowserToolboxCustomizeChange(aType) {
+ switch (aType) {
+ case "iconsize":
+ case "mode":
+ retrieveToolbarIconsizesFromTheme();
+ break;
+ default:
+ gHomeButton.updatePersonalToolbarStyle();
+ BookmarkingUI.customizeChange();
+ allTabs.readPref();
+ }
+}
+
+/**
+ * Allows themes to override the "iconsize" attribute on toolbars.
+ */
+function retrieveToolbarIconsizesFromTheme() {
+ function retrieveToolbarIconsize(aToolbar) {
+ if (aToolbar.localName != "toolbar")
+ return;
+
+ // The theme indicates that it wants to override the "iconsize" attribute
+ // by specifying a special value for the "counter-reset" property on the
+ // toolbar. A custom property cannot be used because getComputedStyle can
+ // only return the values of standard CSS properties.
+ let counterReset = getComputedStyle(aToolbar).counterReset;
+ if (counterReset == "smallicons 0")
+ aToolbar.setAttribute("iconsize", "small");
+ else if (counterReset == "largeicons 0")
+ aToolbar.setAttribute("iconsize", "large");
+ }
+
+ Array.forEach(gNavToolbox.childNodes, retrieveToolbarIconsize);
+ gNavToolbox.externalToolbars.forEach(retrieveToolbarIconsize);
+}
+
+/**
+ * Update the global flag that tracks whether or not any edit UI (the Edit menu,
+ * edit-related items in the context menu, and edit-related toolbar buttons
+ * is visible, then update the edit commands' enabled state accordingly. We use
+ * this flag to skip updating the edit commands on focus or selection changes
+ * when no UI is visible to improve performance (including pageload performance,
+ * since focus changes when you load a new page).
+ *
+ * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
+ * enabled state so the UI will reflect it appropriately.
+ *
+ * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
+ * still work and just lazily disable them as needed when the user presses a
+ * shortcut.
+ *
+ * This doesn't work on Mac, since Mac menus flash when users press their
+ * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
+ * and we need to always update the edit commands. Thus on Mac this function
+ * is a no op.
+ */
+function updateEditUIVisibility()
+{
+#ifndef XP_MACOSX
+ let editMenuPopupState = document.getElementById("menu_EditPopup").state;
+ let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state;
+ let placesContextMenuPopupState = document.getElementById("placesContext").state;
+#ifdef MENUBAR_CAN_AUTOHIDE
+ let appMenuPopupState = document.getElementById("appmenu-popup").state;
+#endif
+
+ // The UI is visible if the Edit menu is opening or open, if the context menu
+ // is open, or if the toolbar has been customized to include the Cut, Copy,
+ // or Paste toolbar buttons.
+ gEditUIVisible = editMenuPopupState == "showing" ||
+ editMenuPopupState == "open" ||
+ contextMenuPopupState == "showing" ||
+ contextMenuPopupState == "open" ||
+ placesContextMenuPopupState == "showing" ||
+ placesContextMenuPopupState == "open" ||
+#ifdef MENUBAR_CAN_AUTOHIDE
+ appMenuPopupState == "showing" ||
+ appMenuPopupState == "open" ||
+#endif
+ document.getElementById("cut-button") ||
+ document.getElementById("copy-button") ||
+ document.getElementById("paste-button") ? true : false;
+
+ // If UI is visible, update the edit commands' enabled state to reflect
+ // whether or not they are actually enabled for the current focus/selection.
+ if (gEditUIVisible)
+ goUpdateGlobalEditMenuItems();
+
+ // Otherwise, enable all commands, so that keyboard shortcuts still work,
+ // then lazily determine their actual enabled state when the user presses
+ // a keyboard shortcut.
+ else {
+ goSetCommandEnabled("cmd_undo", true);
+ goSetCommandEnabled("cmd_redo", true);
+ goSetCommandEnabled("cmd_cut", true);
+ goSetCommandEnabled("cmd_copy", true);
+ goSetCommandEnabled("cmd_paste", true);
+ goSetCommandEnabled("cmd_selectAll", true);
+ goSetCommandEnabled("cmd_delete", true);
+ goSetCommandEnabled("cmd_switchTextDirection", true);
+ }
+#endif
+}
+
+/**
+ * Makes the Character Encoding menu enabled or disabled as appropriate.
+ * To be called when the View menu or the app menu is opened.
+ */
+function updateCharacterEncodingMenuState()
+{
+ let charsetMenu = document.getElementById("charsetMenu");
+ let appCharsetMenu = document.getElementById("appmenu_charsetMenu");
+ let appDevCharsetMenu =
+ document.getElementById("appmenu_developer_charsetMenu");
+ // gBrowser is null on Mac when the menubar shows in the context of
+ // non-browser windows. The above elements may be null depending on
+ // what parts of the menubar are present. E.g. no app menu on Mac.
+ if (gBrowser &&
+ gBrowser.docShell &&
+ gBrowser.docShell.mayEnableCharacterEncodingMenu) {
+ if (charsetMenu) {
+ charsetMenu.removeAttribute("disabled");
+ }
+ if (appCharsetMenu) {
+ appCharsetMenu.removeAttribute("disabled");
+ }
+ if (appDevCharsetMenu) {
+ appDevCharsetMenu.removeAttribute("disabled");
+ }
+ } else {
+ if (charsetMenu) {
+ charsetMenu.setAttribute("disabled", "true");
+ }
+ if (appCharsetMenu) {
+ appCharsetMenu.setAttribute("disabled", "true");
+ }
+ if (appDevCharsetMenu) {
+ appDevCharsetMenu.setAttribute("disabled", "true");
+ }
+ }
+}
+
+/**
+ * Returns true if |aMimeType| is text-based, false otherwise.
+ *
+ * @param aMimeType
+ * The MIME type to check.
+ *
+ * If adding types to this function, please also check the similar
+ * function in findbar.xml
+ */
+function mimeTypeIsTextBased(aMimeType)
+{
+ return aMimeType.startsWith("text/") ||
+ aMimeType.endsWith("+xml") ||
+ aMimeType == "application/x-javascript" ||
+ aMimeType == "application/javascript" ||
+ aMimeType == "application/json" ||
+ aMimeType == "application/xml" ||
+ aMimeType == "mozilla.application/cached-xul";
+}
+
+var XULBrowserWindow = {
+ // Stored Status, Link and Loading values
+ status: "",
+ defaultStatus: "",
+ overLink: "",
+ startTime: 0,
+ statusText: "",
+ isBusy: false,
+/* Pale Moon: Don't hide navigation controls and toolbars for "special" pages. SBaD, M!
+ inContentWhitelist: ["about:addons", "about:downloads", "about:permissions",
+ "about:sync-progress"],*/
+ inContentWhitelist: [],
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsIWebProgressListener2) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsIXULBrowserWindow) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ get stopCommand () {
+ delete this.stopCommand;
+ return this.stopCommand = document.getElementById("Browser:Stop");
+ },
+ get reloadCommand () {
+ delete this.reloadCommand;
+ return this.reloadCommand = document.getElementById("Browser:Reload");
+ },
+ get statusTextField () {
+ delete this.statusTextField;
+ return this.statusTextField = document.getElementById("statusbar-display");
+ },
+ get isImage () {
+ delete this.isImage;
+ return this.isImage = document.getElementById("isImage");
+ },
+
+ init: function () {
+ this.throbberElement = document.getElementById("navigator-throbber");
+
+ // Bug 666809 - SecurityUI support for e10s
+ if (gMultiProcessBrowser)
+ return;
+
+ // Initialize the security button's state and tooltip text. Remember to reset
+ // _hostChanged, otherwise onSecurityChange will short circuit.
+ var securityUI = gBrowser.securityUI;
+ this._hostChanged = true;
+ this.onSecurityChange(null, null, securityUI.state);
+ },
+
+ destroy: function () {
+ // XXXjag to avoid leaks :-/, see bug 60729
+ delete this.throbberElement;
+ delete this.stopCommand;
+ delete this.reloadCommand;
+ delete this.statusTextField;
+ delete this.statusText;
+ },
+
+ setJSStatus: function () {
+ // unsupported
+ },
+
+ setDefaultStatus: function (status) {
+ this.defaultStatus = status;
+ this.updateStatusField();
+ },
+
+ setOverLink: function (url, anchorElt) {
+ // Encode bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
+ encodeURIComponent);
+
+ if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
+ url = trimURL(url);
+
+ this.overLink = url;
+ LinkTargetDisplay.update();
+ },
+
+ updateStatusField: function () {
+ var text, type, types = ["overLink"];
+ if (this._busyUI)
+ types.push("status");
+ types.push("defaultStatus");
+ for (type of types) {
+ text = this[type];
+ if (text)
+ break;
+ }
+
+ // check the current value so we don't trigger an attribute change
+ // and cause needless (slow!) UI updates
+ if (this.statusText != text) {
+ let field = this.statusTextField;
+ field.setAttribute("previoustype", field.getAttribute("type"));
+ field.setAttribute("type", type);
+ field.label = text;
+ field.setAttribute("crop", type == "overLink" ? "center" : "end");
+ this.statusText = text;
+ }
+ },
+
+ // Called before links are navigated to to allow us to retarget them if needed.
+ onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+ return target;
+ },
+
+ _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ // Don't modify non-default targets or targets that aren't in top-level app
+ // tab docshells (isAppTab will be false for app tab subframes).
+ if (originalTarget != "" || !isAppTab)
+ return originalTarget;
+
+ // External links from within app tabs should always open in new tabs
+ // instead of replacing the app tab's page (Bug 575561)
+ let linkHost;
+ let docHost;
+ try {
+ linkHost = linkURI.host;
+ docHost = linkNode.ownerDocument.documentURIObject.host;
+ } catch(e) {
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+ // If we fail to get either host, just return originalTarget.
+ return originalTarget;
+ }
+
+ if (docHost == linkHost)
+ return originalTarget;
+
+ // Special case: ignore "www" prefix if it is part of host string
+ let [longHost, shortHost] =
+ linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
+ if (longHost == "www." + shortHost)
+ return originalTarget;
+
+ return "_blank";
+ },
+
+ onLinkIconAvailable: function (aIconURL) {
+ if (gProxyFavIcon && gBrowser.userTypedValue === null) {
+ PageProxySetIcon(aIconURL); // update the favicon in the URL bar
+ }
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ // Do nothing.
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ // This function fires only for the currently selected tab.
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ const nsIChannel = Ci.nsIChannel;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (aRequest && aWebProgress.isTopLevel) {
+ // clear out feed data
+ gBrowser.selectedBrowser.feeds = null;
+
+ // clear out search-engine data
+ gBrowser.selectedBrowser.engines = null;
+ }
+
+ this.isBusy = true;
+
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this._busyUI = true;
+
+ // Turn the throbber on.
+ if (this.throbberElement)
+ this.throbberElement.setAttribute("busy", "true");
+
+ // XXX: This needs to be based on window activity...
+ this.stopCommand.removeAttribute("disabled");
+ CombinedStopReload.switchToStop();
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ // This (thanks to the filter) is a network stop or the last
+ // request stop outside of loading the document, stop throbbers
+ // and progress bars and such
+ if (aRequest) {
+ let msg = "";
+ let location;
+ // Get the URI either from a channel or a pseudo-object
+ if (aRequest instanceof nsIChannel || "URI" in aRequest) {
+ location = aRequest.URI;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword" && aWebProgress.isTopLevel)
+ gBrowser.userTypedValue = null;
+
+ if (location.spec != "about:blank") {
+ switch (aStatus) {
+ case Components.results.NS_ERROR_NET_TIMEOUT:
+ msg = gNavigatorBundle.getString("nv_timeout");
+ break;
+ }
+ }
+ }
+
+ this.status = "";
+ this.setDefaultStatus(msg);
+
+ // Disable menu entries for images, enable otherwise
+ if (!gMultiProcessBrowser && content.document && mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+ }
+
+ this.isBusy = false;
+
+ if (this._busyUI) {
+ this._busyUI = false;
+
+ // Turn the throbber off.
+ if (this.throbberElement)
+ this.throbberElement.removeAttribute("busy");
+
+ this.stopCommand.setAttribute("disabled", "true");
+ CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest);
+ }
+ }
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var location = aLocationURI ? aLocationURI.spec : "";
+ this._hostChanged = true;
+
+ // If displayed, hide the form validation popup.
+ FormValidationHandler.hidePopup();
+
+ let pageTooltip = document.getElementById("aHTMLTooltip");
+ let tooltipNode = pageTooltip.triggerNode;
+ if (tooltipNode) {
+ // Optimise for the common case
+ if (aWebProgress.isTopLevel) {
+ pageTooltip.hidePopup();
+ }
+ else {
+ for (let tooltipWindow = tooltipNode.ownerDocument.defaultView;
+ tooltipWindow != tooltipWindow.parent;
+ tooltipWindow = tooltipWindow.parent) {
+ if (tooltipWindow == aWebProgress.DOMWindow) {
+ pageTooltip.hidePopup();
+ break;
+ }
+ }
+ }
+ }
+
+ // Disable menu entries for images, enable otherwise
+ if (!gMultiProcessBrowser && content.document && mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+
+ this.hideOverLinkImmediately = true;
+ this.setOverLink("", null);
+ this.hideOverLinkImmediately = false;
+
+ // We should probably not do this if the value has changed since the user
+ // searched
+ // Update urlbar only if a new page was loaded on the primary content area
+ // Do not update urlbar if there was a subframe navigation
+
+ var browser = gBrowser.selectedBrowser;
+ if (aWebProgress.isTopLevel) {
+ if ((location == "about:blank" && (gMultiProcessBrowser || !content.opener)) ||
+ location == "") { // Second condition is for new tabs, otherwise
+ // reload function is enabled until tab is refreshed.
+ this.reloadCommand.setAttribute("disabled", "true");
+ } else {
+ this.reloadCommand.removeAttribute("disabled");
+ }
+
+ if (gURLBar) {
+ URLBarSetURI(aLocationURI);
+
+ // Update starring UI
+ BookmarkingUI.updateStarState();
+ }
+
+ // Show or hide browser chrome based on the whitelist
+ if (this.hideChromeForLocation(location)) {
+ document.documentElement.setAttribute("disablechrome", "true");
+ } else {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ if (ss.getTabValue(gBrowser.selectedTab, "appOrigin"))
+ document.documentElement.setAttribute("disablechrome", "true");
+ else
+ document.documentElement.removeAttribute("disablechrome");
+ }
+
+ // Utility functions for disabling find
+ var shouldDisableFind = function shouldDisableFind(aDocument) {
+ let docElt = aDocument.documentElement;
+ return docElt && docElt.getAttribute("disablefastfind") == "true";
+ }
+
+ var disableFindCommands = function disableFindCommands(aDisable) {
+ let findCommands = [document.getElementById("cmd_find"),
+ document.getElementById("cmd_findAgain"),
+ document.getElementById("cmd_findPrevious")];
+ for (let elt of findCommands) {
+ if (aDisable)
+ elt.setAttribute("disabled", "true");
+ else
+ elt.removeAttribute("disabled");
+ }
+ if (gFindBarInitialized) {
+ if (!gFindBar.hidden && aDisable) {
+ gFindBar.hidden = true;
+ this._findbarTemporarilyHidden = true;
+ } else if (this._findbarTemporarilyHidden && !aDisable) {
+ gFindBar.hidden = false;
+ this._findbarTemporarilyHidden = false;
+ }
+ }
+ }.bind(this);
+
+ var onContentRSChange = function onContentRSChange(e) {
+ if (e.target.readyState != "interactive" && e.target.readyState != "complete")
+ return;
+
+ e.target.removeEventListener("readystatechange", onContentRSChange);
+ disableFindCommands(shouldDisableFind(e.target));
+ }
+
+ // Disable find commands in documents that ask for them to be disabled.
+ if (!gMultiProcessBrowser && aLocationURI &&
+ (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) {
+ // Don't need to re-enable/disable find commands for same-document location changes
+ // (e.g. the replaceStates in about:addons)
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+ if (content.document.readyState == "interactive" || content.document.readyState == "complete")
+ disableFindCommands(shouldDisableFind(content.document));
+ else {
+ content.document.addEventListener("readystatechange", onContentRSChange);
+ }
+ }
+ } else
+ disableFindCommands(false);
+
+ if (gFindBarInitialized) {
+ if (gFindBar.findMode != gFindBar.FIND_NORMAL) {
+ // Close the Find toolbar if we're in old-style TAF mode
+ gFindBar.close();
+ }
+
+ if (!(gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallremember") ||
+ gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallbydefault"))) {
+ // fix bug 253793 - turn off highlight when page changes
+ gFindBar.getElement("highlight").checked = false;
+ }
+ }
+ }
+ UpdateBackForwardCommands(gBrowser.webNavigation);
+
+ gGestureSupport.restoreRotationState();
+
+ // See bug 358202, when tabs are switched during a drag operation,
+ // timers don't fire on windows (bug 203573)
+ if (aRequest)
+ setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0);
+ else
+ this.asyncUpdateUI();
+ },
+
+ asyncUpdateUI: function () {
+ FeedHandler.updateFeeds();
+ },
+
+ hideChromeForLocation: function(aLocation) {
+ aLocation = aLocation.toLowerCase();
+ return this.inContentWhitelist.some(function(aSpec) {
+ return aSpec == aLocation;
+ });
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ this.status = aMessage;
+ this.updateStatusField();
+ },
+
+ // Properties used to cache security state used to update the UI
+ _state: null,
+ _hostChanged: false, // onLocationChange will flip this bit
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ // Don't need to do anything if the data we use to update the UI hasn't
+ // changed
+ if (this._state == aState &&
+ !this._hostChanged) {
+#ifdef DEBUG
+ try {
+ var contentHost = gBrowser.contentWindow.location.host;
+ if (this._host !== undefined && this._host != contentHost) {
+ Components.utils.reportError(
+ "ASSERTION: browser.js host is inconsistent. Content window has " +
+ "<" + contentHost + "> but cached host is <" + this._host + ">.\n"
+ );
+ }
+ } catch (ex) {}
+#endif
+ return;
+ }
+ this._state = aState;
+
+#ifdef DEBUG
+ try {
+ this._host = gBrowser.contentWindow.location.host;
+ } catch(ex) {
+ this._host = null;
+ }
+#endif
+
+ this._hostChanged = false;
+
+ // aState is defined as a bitmask that may be extended in the future.
+ // We filter out any unknown bits before testing for known values.
+ const wpl = Components.interfaces.nsIWebProgressListener;
+ const wpl_security_bits = wpl.STATE_IS_SECURE |
+ wpl.STATE_IS_BROKEN |
+ wpl.STATE_IS_INSECURE;
+ var level;
+
+ switch (this._state & wpl_security_bits) {
+ case wpl.STATE_IS_SECURE:
+ level = "high";
+ break;
+ case wpl.STATE_IS_BROKEN:
+ level = "broken";
+ break;
+ }
+
+ if (level) {
+ // We don't style the Location Bar based on the the 'level' attribute
+ // anymore, but still set it for third-party themes.
+ if (gURLBar)
+ gURLBar.setAttribute("level", level);
+ } else {
+ if (gURLBar)
+ gURLBar.removeAttribute("level");
+ }
+
+ if (gMultiProcessBrowser)
+ return;
+
+ // Don't pass in the actual location object, since it can cause us to
+ // hold on to the window object too long. Just pass in the fields we
+ // care about. (bug 424829)
+ var location = gBrowser.contentWindow.location;
+ var locationObj = {};
+ try {
+ // about:blank can be used by webpages so pretend it is http
+ locationObj.protocol = location == "about:blank" ? "http:" : location.protocol;
+ locationObj.host = location.host;
+ locationObj.hostname = location.hostname;
+ locationObj.port = location.port;
+ } catch (ex) {
+ // Can sometimes throw if the URL being visited has no host/hostname,
+ // e.g. about:blank. The _state for these pages means we won't need these
+ // properties anyways, though.
+ }
+ gIdentityHandler.checkIdentity(this._state, locationObj);
+ },
+
+ // simulate all change notifications after switching tabs
+ onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
+ if (FullZoom.updateBackgroundTabs)
+ FullZoom.onLocationChange(gBrowser.currentURI, true);
+ var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
+ // use a pseudo-object instead of a (potentially nonexistent) channel for getting
+ // a correct error message - and make sure that the UI is always either in
+ // loading (STATE_START) or done (STATE_STOP) mode
+ this.onStateChange(
+ gBrowser.webProgress,
+ { URI: gBrowser.currentURI },
+ loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START,
+ aStatus
+ );
+ // status message and progress value are undefined if we're done with loading
+ if (loadingDone)
+ return;
+ this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
+ }
+};
+
+var LinkTargetDisplay = {
+ get DELAY_SHOW() {
+ delete this.DELAY_SHOW;
+ return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay");
+ },
+
+ DELAY_HIDE: 250,
+ _timer: 0,
+
+ get _isVisible () XULBrowserWindow.statusTextField.label != "",
+
+ update: function () {
+ clearTimeout(this._timer);
+ window.removeEventListener("mousemove", this, true);
+
+ if (!XULBrowserWindow.overLink) {
+ if (XULBrowserWindow.hideOverLinkImmediately)
+ this._hide();
+ else
+ this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
+ return;
+ }
+
+ if (this._isVisible) {
+ XULBrowserWindow.updateStatusField();
+ } else {
+ // Let the display appear when the mouse doesn't move within the delay
+ this._showDelayed();
+ window.addEventListener("mousemove", this, true);
+ }
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "mousemove":
+ // Restart the delay since the mouse was moved
+ clearTimeout(this._timer);
+ this._showDelayed();
+ break;
+ }
+ },
+
+ _showDelayed: function () {
+ this._timer = setTimeout(function (self) {
+ XULBrowserWindow.updateStatusField();
+ window.removeEventListener("mousemove", self, true);
+ }, this.DELAY_SHOW, this);
+ },
+
+ _hide: function () {
+ clearTimeout(this._timer);
+
+ XULBrowserWindow.updateStatusField();
+ }
+};
+
+var CombinedStopReload = {
+ init: function () {
+ if (this._initialized)
+ return;
+
+ var urlbar = document.getElementById("urlbar-container");
+ var reload = document.getElementById("reload-button");
+ var stop = document.getElementById("stop-button");
+
+ if (urlbar) {
+ if (urlbar.parentNode.getAttribute("mode") != "icons" ||
+ !reload || urlbar.nextSibling != reload ||
+ !stop || reload.nextSibling != stop)
+ urlbar.removeAttribute("combined");
+ else {
+ urlbar.setAttribute("combined", "true");
+ reload = document.getElementById("urlbar-reload-button");
+ stop = document.getElementById("urlbar-stop-button");
+ }
+ }
+ if (!stop || !reload || reload.nextSibling != stop)
+ return;
+
+ this._initialized = true;
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ reload.setAttribute("displaystop", "true");
+ stop.addEventListener("click", this, false);
+ this.reload = reload;
+ this.stop = stop;
+ },
+
+ uninit: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this._initialized = false;
+ this.stop.removeEventListener("click", this, false);
+ this.reload = null;
+ this.stop = null;
+ },
+
+ handleEvent: function (event) {
+ // the only event we listen to is "click" on the stop button
+ if (event.button == 0 &&
+ !this.stop.disabled)
+ this._stopClicked = true;
+ },
+
+ switchToStop: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this.reload.setAttribute("displaystop", "true");
+ },
+
+ switchToReload: function (aDelay) {
+ if (!this._initialized)
+ return;
+
+ this.reload.removeAttribute("displaystop");
+
+ if (!aDelay || this._stopClicked) {
+ this._stopClicked = false;
+ this._cancelTransition();
+ this.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ return;
+ }
+
+ if (this._timer)
+ return;
+
+ // Temporarily disable the reload button to prevent the user from
+ // accidentally reloading the page when intending to click the stop button
+ this.reload.disabled = true;
+ this._timer = setTimeout(function (self) {
+ self._timer = 0;
+ self.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ }, 650, this);
+ },
+
+ _cancelTransition: function () {
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = 0;
+ }
+ }
+};
+
+var TabsProgressListener = {
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+
+ // Attach a listener to watch for "click" events bubbling up from error
+ // pages and other similar page. This lets us fix bugs like 401575 which
+ // require error page UI to do privileged things, without letting error
+ // pages have any privilege themselves.
+ // We can't look for this during onLocationChange since at that point the
+ // document URI is not yet the about:-uri of the error page.
+
+ let doc = gMultiProcessBrowser ? null : aWebProgress.DOMWindow.document;
+ if (!gMultiProcessBrowser &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ Components.isSuccessCode(aStatus) &&
+ doc.documentURI.startsWith("about:") &&
+ !doc.documentURI.toLowerCase().startsWith("about:blank") &&
+ !doc.documentElement.hasAttribute("hasBrowserHandlers")) {
+ // STATE_STOP may be received twice for documents, thus store an
+ // attribute to ensure handling it just once.
+ doc.documentElement.setAttribute("hasBrowserHandlers", "true");
+ aBrowser.addEventListener("click", BrowserOnClick, true);
+ aBrowser.addEventListener("pagehide", function onPageHide(event) {
+ if (event.target.defaultView.frameElement)
+ return;
+ aBrowser.removeEventListener("click", BrowserOnClick, true);
+ aBrowser.removeEventListener("pagehide", onPageHide, true);
+ if (event.target.documentElement)
+ event.target.documentElement.removeAttribute("hasBrowserHandlers");
+ }, true);
+
+ // We also want to make changes to page UI for unprivileged about pages.
+ BrowserOnAboutPageLoad(doc);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ // Filter out location changes caused by anchor navigation
+ // or history.push/pop/replaceState.
+ if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
+ return;
+
+ // Only need to call locationChange if the PopupNotifications object
+ // for this window has already been initialized (i.e. its getter no
+ // longer exists)
+ if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get)
+ PopupNotifications.locationChange(aBrowser);
+
+ gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
+
+ // Filter out location changes in sub documents.
+ if (aWebProgress.isTopLevel) {
+ // Initialize the click-to-play state.
+ aBrowser._clickToPlayPluginsActivated = new Map();
+ aBrowser._clickToPlayAllPluginsActivated = false;
+ aBrowser._pluginScriptedState = gPluginHandler.PLUGIN_SCRIPTED_STATE_NONE;
+
+ FullZoom.onLocationChange(aLocationURI, false, aBrowser);
+ }
+ },
+
+ onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) {
+ if (gPrefService.getBoolPref("accessibility.blockautorefresh")) {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let refreshButtonText =
+ gNavigatorBundle.getString("refreshBlocked.goButton");
+ let refreshButtonAccesskey =
+ gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
+ let message =
+ gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel"
+ : "refreshBlocked.redirectLabel",
+ [brandShortName]);
+ let docShell = aWebProgress.DOMWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ let notificationBox = gBrowser.getNotificationBox(aBrowser);
+ let notification = notificationBox.getNotificationWithValue("refresh-blocked");
+ if (notification) {
+ notification.label = message;
+ notification.refreshURI = aURI;
+ notification.delay = aDelay;
+ notification.docShell = docShell;
+ } else {
+ let buttons = [{
+ label: refreshButtonText,
+ accessKey: refreshButtonAccesskey,
+ callback: function (aNotification, aButton) {
+ var refreshURI = aNotification.docShell
+ .QueryInterface(Ci.nsIRefreshURI);
+ refreshURI.forceRefreshURI(aNotification.refreshURI,
+ aNotification.delay, true);
+ }
+ }];
+ notification =
+ notificationBox.appendNotification(message, "refresh-blocked",
+ "chrome://browser/skin/Info.png",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notification.refreshURI = aURI;
+ notification.delay = aDelay;
+ notification.docShell = docShell;
+ }
+ return false;
+ }
+ return true;
+ }
+}
+
+function nsBrowserAccess() { }
+
+nsBrowserAccess.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
+
+ openURI: function (aURI, aOpener, aWhere, aContext) {
+ var newWindow = null;
+ var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ if (isExternal && aURI && aURI.schemeIs("chrome")) {
+ dump("use -chrome command-line option to load external chrome urls\n");
+ return null;
+ }
+
+ if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+ if (isExternal &&
+ gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
+ else
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
+ }
+ switch (aWhere) {
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
+ // FIXME: Bug 408379. So how come this doesn't send the
+ // referrer like the other loads do?
+ var url = aURI ? aURI.spec : "about:blank";
+ // Pass all params to openDialog to ensure that "url" isn't passed through
+ // loadOneOrMoreURIs, which splits based on "|"
+ newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
+ break;
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
+ let win, needToFocusWin;
+
+ // try the current window. if we're in a popup, fall back on the most recent browser window
+ if (window.toolbar.visible)
+ win = window;
+ else {
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
+ win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate});
+ needToFocusWin = true;
+ }
+
+ if (!win) {
+ // we couldn't find a suitable window, a new one needs to be opened.
+ return null;
+ }
+
+ if (isExternal && (!aURI || aURI.spec == "about:blank")) {
+ win.BrowserOpenTab(); // this also focuses the location bar
+ win.focus();
+ newWindow = win.content;
+ break;
+ }
+
+ let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
+ let referrer = aOpener ? makeURI(aOpener.location.href) : null;
+
+ let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
+ referrerURI: referrer,
+ fromExternal: isExternal,
+ inBackground: loadInBackground});
+ let browser = win.gBrowser.getBrowserForTab(tab);
+
+ if (gPrefService.getBoolPref("browser.tabs.noWindowActivationOnExternal")) {
+ isExternal = false; // this is a hack, but it works
+ }
+
+ newWindow = browser.contentWindow;
+ if (needToFocusWin || (!loadInBackground && isExternal))
+ newWindow.focus();
+ break;
+ default : // OPEN_CURRENTWINDOW or an illegal value
+ newWindow = content;
+ if (aURI) {
+ let referrer = aOpener ? makeURI(aOpener.location.href) : null;
+ let loadflags = isExternal ?
+ Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ gBrowser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
+ }
+ if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
+ window.focus();
+ }
+ return newWindow;
+ },
+
+ isTabContentWindow: function (aWindow) {
+ return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow);
+ }
+}
+
+function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
+ var popup = aEvent.target;
+ if (popup != aEvent.currentTarget)
+ return;
+
+ // Empty the menu
+ for (var i = popup.childNodes.length-1; i >= 0; --i) {
+ var deadItem = popup.childNodes[i];
+ if (deadItem.hasAttribute("toolbarId"))
+ popup.removeChild(deadItem);
+ }
+
+ var firstMenuItem = aInsertPoint || popup.firstChild;
+
+ let toolbarNodes = Array.slice(gNavToolbox.childNodes);
+ toolbarNodes.push(document.getElementById("addon-bar"));
+
+ for (let toolbar of toolbarNodes) {
+ let toolbarName = toolbar.getAttribute("toolbarname");
+ if (toolbarName) {
+ let menuItem = document.createElement("menuitem");
+ let hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+ menuItem.setAttribute("id", "toggle_" + toolbar.id);
+ menuItem.setAttribute("toolbarId", toolbar.id);
+ menuItem.setAttribute("type", "checkbox");
+ menuItem.setAttribute("label", toolbarName);
+ menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
+ if (popup.id != "appmenu_customizeMenu")
+ menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
+ if (popup.id != "toolbar-context-menu")
+ menuItem.setAttribute("key", toolbar.getAttribute("key"));
+
+ popup.insertBefore(menuItem, firstMenuItem);
+
+ menuItem.addEventListener("command", onViewToolbarCommand, false);
+ }
+ }
+}
+
+function onViewToolbarCommand(aEvent) {
+ var toolbarId = aEvent.originalTarget.getAttribute("toolbarId");
+ var toolbar = document.getElementById(toolbarId);
+ var isVisible = aEvent.originalTarget.getAttribute("checked") == "true";
+ setToolbarVisibility(toolbar, isVisible);
+}
+
+function setToolbarVisibility(toolbar, isVisible) {
+ var hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+
+ toolbar.setAttribute(hidingAttribute, !isVisible);
+ document.persist(toolbar.id, hidingAttribute);
+
+ // Customizable toolbars - persist the hiding attribute.
+ if (toolbar.hasAttribute("customindex")) {
+ var toolbox = toolbar.parentNode;
+ var name = toolbar.getAttribute("toolbarname");
+ if (toolbox.toolbarset) {
+ try {
+ // Checking all attributes starting with "toolbar".
+ Array.prototype.slice.call(toolbox.toolbarset.attributes, 0)
+ .find(x => {
+ if (x.name.startsWith("toolbar")) {
+ var toolbarInfo = x.value;
+ var infoSplit = toolbarInfo.split(gToolbarInfoSeparators[0]);
+ if (infoSplit[0] == name) {
+ infoSplit[1] = [
+ infoSplit[1].split(gToolbarInfoSeparators[1], 1), !isVisible
+ ].join(gToolbarInfoSeparators[1]);
+ toolbox.toolbarset.setAttribute(
+ x.name, infoSplit.join(gToolbarInfoSeparators[0]));
+ document.persist(toolbox.toolbarset.id, x.name);
+ }
+ }
+ });
+ } catch (e) {
+ Components.utils.reportError(
+ "Customizable toolbars - persist the hiding attribute: " + e);
+ }
+ }
+ }
+
+ PlacesToolbarHelper.init();
+ BookmarkingUI.onToolbarVisibilityChange();
+ gBrowser.updateWindowResizers();
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+
+ if (isVisible)
+ ToolbarIconColor.inferFromText();
+}
+
+var TabsOnTop = {
+ init: function TabsOnTop_init() {
+ Services.prefs.addObserver(this._prefName, this, false);
+// Pale Moon: Stop Being a Derp, Mozilla (#3)
+ // Only show the toggle UI if the user disabled tabs on top.
+// if (Services.prefs.getBoolPref(this._prefName)) {
+// for (let item of document.querySelectorAll("menuitem[command=cmd_ToggleTabsOnTop]"))
+// item.parentNode.removeChild(item);
+// }
+ },
+
+ uninit: function TabsOnTop_uninit() {
+ Services.prefs.removeObserver(this._prefName, this);
+ },
+
+ toggle: function () {
+ this.enabled = !Services.prefs.getBoolPref(this._prefName);
+ },
+
+ syncUI: function () {
+ let userEnabled = Services.prefs.getBoolPref(this._prefName);
+ let enabled = userEnabled && gBrowser.tabContainer.visible;
+
+ document.getElementById("cmd_ToggleTabsOnTop")
+ .setAttribute("checked", userEnabled);
+
+ document.documentElement.setAttribute("tabsontop", enabled);
+ document.getElementById("navigator-toolbox").setAttribute("tabsontop", enabled);
+ document.getElementById("TabsToolbar").setAttribute("tabsontop", enabled);
+ document.getElementById("nav-bar").setAttribute("tabsontop", enabled);
+ gBrowser.tabContainer.setAttribute("tabsontop", enabled);
+ TabsInTitlebar.allowedBy("tabs-on-top", enabled);
+ },
+
+ get enabled () {
+ return gNavToolbox.getAttribute("tabsontop") == "true";
+ },
+
+ set enabled (val) {
+ Services.prefs.setBoolPref(this._prefName, !!val);
+ return val;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this.syncUI();
+ },
+
+ _prefName: "browser.tabs.onTop"
+}
+
+var TabsInTitlebar = {
+ init: function () {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ this._readPref();
+ Services.prefs.addObserver(this._prefName, this, false);
+
+ // Don't trust the initial value of the sizemode attribute; wait for
+ // the resize event (handled in tabbrowser.xml).
+ this.allowedBy("sizemode", false);
+
+ this._initialized = true;
+#endif
+ },
+
+ allowedBy: function (condition, allow) {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ if (allow) {
+ if (condition in this._disallowed) {
+ delete this._disallowed[condition];
+ this._update();
+ }
+ } else {
+ if (!(condition in this._disallowed)) {
+ this._disallowed[condition] = null;
+ this._update();
+ }
+ }
+#endif
+ },
+
+ get enabled() {
+ return document.documentElement.getAttribute("tabsintitlebar") == "true";
+ },
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this._readPref();
+ },
+
+ _initialized: false,
+ _disallowed: {},
+ _prefName: "browser.tabs.drawInTitlebar",
+
+ _readPref: function () {
+ this.allowedBy("pref",
+ Services.prefs.getBoolPref(this._prefName));
+ },
+
+ _update: function () {
+ function $(id) document.getElementById(id);
+ function rect(ele) ele.getBoundingClientRect();
+
+ if (!this._initialized || window.fullScreen)
+ return;
+
+ let allowed = true;
+ for (let something in this._disallowed) {
+ allowed = false;
+ break;
+ }
+
+ if (allowed == this.enabled)
+ return;
+
+ let titlebar = $("titlebar");
+
+ if (allowed) {
+ let tabsToolbar = $("TabsToolbar");
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ let appmenuButtonBox = $("appmenu-button-container");
+ this._sizePlaceholder("appmenu-button", rect(appmenuButtonBox).width);
+#endif
+ let captionButtonsBox = $("titlebar-buttonbox");
+ this._sizePlaceholder("caption-buttons", rect(captionButtonsBox).width);
+
+ let tabsToolbarRect = rect(tabsToolbar);
+ let titlebarTop = rect($("titlebar-content")).top;
+ titlebar.style.marginBottom = - Math.min(tabsToolbarRect.top - titlebarTop,
+ tabsToolbarRect.height) + "px";
+
+ document.documentElement.setAttribute("tabsintitlebar", "true");
+
+ if (!this._draghandle) {
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
+ this._draghandle = new tmp.WindowDraggingElement(tabsToolbar);
+ this._draghandle.mouseDownCheck = function () {
+ return !this._dragBindingAlive && TabsInTitlebar.enabled;
+ };
+ }
+ } else {
+ document.documentElement.removeAttribute("tabsintitlebar");
+
+ titlebar.style.marginBottom = "";
+ }
+
+ ToolbarIconColor.inferFromText();
+ },
+
+ _sizePlaceholder: function (type, width) {
+ Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
+ function (node) { node.width = width; });
+ },
+#endif
+
+ uninit: function () {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ this._initialized = false;
+ Services.prefs.removeObserver(this._prefName, this);
+#endif
+ }
+};
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+function updateAppButtonDisplay() {
+ var displayAppButton =
+ !gInPrintPreviewMode &&
+ window.menubar.visible &&
+ document.getElementById("toolbar-menubar").getAttribute("autohide") == "true";
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ document.getElementById("titlebar").hidden = !displayAppButton;
+
+ if (displayAppButton)
+ document.documentElement.setAttribute("chromemargin", "0,2,2,2");
+ else
+ document.documentElement.removeAttribute("chromemargin");
+
+ TabsInTitlebar.allowedBy("drawing-in-titlebar", displayAppButton);
+#else
+ document.getElementById("appmenu-toolbar-button").hidden =
+ !displayAppButton;
+#endif
+}
+#endif
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+function onTitlebarMaxClick() {
+ if (window.windowState == window.STATE_MAXIMIZED)
+ window.restore();
+ else
+ window.maximize();
+}
+#endif
+
+function displaySecurityInfo()
+{
+ BrowserPageInfo(null, "securityTab");
+}
+
+/**
+ * Opens or closes the sidebar identified by commandID.
+ *
+ * @param commandID a string identifying the sidebar to toggle; see the
+ * note below. (Optional if a sidebar is already open.)
+ * @param forceOpen boolean indicating whether the sidebar should be
+ * opened regardless of its current state (optional).
+ * @note
+ * We expect to find a xul:broadcaster element with the specified ID.
+ * The following attributes on that element may be used and/or modified:
+ * - id (required) the string to match commandID. The convention
+ * is to use this naming scheme: 'view<sidebar-name>Sidebar'.
+ * - sidebarurl (required) specifies the URL to load in this sidebar.
+ * - sidebartitle or label (in that order) specify the title to
+ * display on the sidebar.
+ * - checked indicates whether the sidebar is currently displayed.
+ * Note that toggleSidebar updates this attribute when
+ * it changes the sidebar's visibility.
+ * - group this attribute must be set to "sidebar".
+ */
+function toggleSidebar(commandID, forceOpen) {
+
+ var sidebarBox = document.getElementById("sidebar-box");
+ if (!commandID)
+ commandID = sidebarBox.getAttribute("sidebarcommand");
+
+ var sidebarBroadcaster = document.getElementById(commandID);
+ var sidebar = document.getElementById("sidebar"); // xul:browser
+ var sidebarTitle = document.getElementById("sidebar-title");
+ var sidebarSplitter = document.getElementById("sidebar-splitter");
+
+ if (sidebarBroadcaster.getAttribute("checked") == "true") {
+ if (!forceOpen) {
+ // Replace the document currently displayed in the sidebar with about:blank
+ // so that we can free memory by unloading the page. We need to explicitly
+ // create a new content viewer because the old one doesn't get destroyed
+ // until about:blank has loaded (which does not happen as long as the
+ // element is hidden).
+ sidebar.setAttribute("src", "about:blank");
+ sidebar.docShell.createAboutBlankContentViewer(null);
+
+ sidebarBroadcaster.removeAttribute("checked");
+ sidebarBox.setAttribute("sidebarcommand", "");
+ sidebarTitle.value = "";
+ sidebarBox.hidden = true;
+ sidebarSplitter.hidden = true;
+ gBrowser.selectedBrowser.focus();
+ } else {
+ fireSidebarFocusedEvent();
+ }
+ return;
+ }
+
+ // now we need to show the specified sidebar
+
+ // ..but first update the 'checked' state of all sidebar broadcasters
+ var broadcasters = document.getElementsByAttribute("group", "sidebar");
+ for (let broadcaster of broadcasters) {
+ // skip elements that observe sidebar broadcasters and random
+ // other elements
+ if (broadcaster.localName != "broadcaster")
+ continue;
+
+ if (broadcaster != sidebarBroadcaster)
+ broadcaster.removeAttribute("checked");
+ else
+ sidebarBroadcaster.setAttribute("checked", "true");
+ }
+
+ sidebarBox.hidden = false;
+ sidebarSplitter.hidden = false;
+
+ var url = sidebarBroadcaster.getAttribute("sidebarurl");
+ var title = sidebarBroadcaster.getAttribute("sidebartitle");
+ if (!title)
+ title = sidebarBroadcaster.getAttribute("label");
+ sidebar.setAttribute("src", url); // kick off async load
+ sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id);
+ sidebarTitle.value = title;
+
+ // We set this attribute here in addition to setting it on the <browser>
+ // element itself, because the code in gBrowserInit.onUnload persists this
+ // attribute, not the "src" of the <browser id="sidebar">. The reason it
+ // does that is that we want to delay sidebar load a bit when a browser
+ // window opens. See delayedStartup().
+ sidebarBox.setAttribute("src", url);
+
+ if (sidebar.contentDocument.location.href != url)
+ sidebar.addEventListener("load", sidebarOnLoad, true);
+ else // older code handled this case, so we do it too
+ fireSidebarFocusedEvent();
+}
+
+function sidebarOnLoad(event) {
+ var sidebar = document.getElementById("sidebar");
+ sidebar.removeEventListener("load", sidebarOnLoad, true);
+ // We're handling the 'load' event before it bubbles up to the usual
+ // (non-capturing) event handlers. Let it bubble up before firing the
+ // SidebarFocused event.
+ setTimeout(fireSidebarFocusedEvent, 0);
+}
+
+/**
+ * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
+ * a chance to adjust focus as needed. An additional event is needed, because
+ * we don't want to focus the sidebar when it's opened on startup or in a new
+ * window, only when the user opens the sidebar.
+ */
+function fireSidebarFocusedEvent() {
+ var sidebar = document.getElementById("sidebar");
+ var event = document.createEvent("Events");
+ event.initEvent("SidebarFocused", true, false);
+ sidebar.contentWindow.dispatchEvent(event);
+}
+
+var gHomeButton = {
+ prefDomain: "browser.startup.homepage",
+ observe: function (aSubject, aTopic, aPrefName)
+ {
+ if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
+ return;
+
+ this.updateTooltip();
+ },
+
+ updateTooltip: function (homeButton)
+ {
+ if (!homeButton)
+ homeButton = document.getElementById("home-button");
+ if (homeButton) {
+ var homePage = this.getHomePage();
+ homePage = homePage.replace(/\|/g,', ');
+ if (homePage.toLowerCase() == "about:home")
+ homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip"));
+ else
+ homeButton.setAttribute("tooltiptext", homePage);
+ }
+ },
+
+ getHomePage: function ()
+ {
+ var url;
+ try {
+ url = gPrefService.getComplexValue(this.prefDomain,
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) {
+ }
+
+ // use this if we can't find the pref
+ if (!url) {
+ var configBundle = Services.strings
+ .createBundle("chrome://branding/locale/browserconfig.properties");
+ url = configBundle.GetStringFromName(this.prefDomain);
+ }
+
+ return url;
+ },
+
+ updatePersonalToolbarStyle: function (homeButton)
+ {
+ if (!homeButton)
+ homeButton = document.getElementById("home-button");
+ if (homeButton)
+ homeButton.className = homeButton.parentNode.id == "PersonalToolbar"
+ || homeButton.parentNode.parentNode.id == "PersonalToolbar" ?
+ homeButton.className.replace("toolbarbutton-1", "bookmark-item") :
+ homeButton.className.replace("bookmark-item", "toolbarbutton-1");
+ }
+};
+
+/**
+ * Gets the selected text in the active browser. Leading and trailing
+ * whitespace is removed, and consecutive whitespace is replaced by a single
+ * space. A maximum of 150 characters will be returned, regardless of the value
+ * of aCharLen.
+ *
+ * @param aCharLen
+ * The maximum number of characters to return.
+ */
+function getBrowserSelection(aCharLen) {
+ // selections of more than 150 characters aren't useful
+ const kMaxSelectionLen = 150;
+ const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
+ let commandDispatcher = document.commandDispatcher;
+
+ var focusedWindow = commandDispatcher.focusedWindow;
+ var selection = focusedWindow.getSelection().toString();
+ // try getting a selected text in text input.
+ if (!selection) {
+ let element = commandDispatcher.focusedElement;
+ var isOnTextInput = function isOnTextInput(elem) {
+ // we avoid to return a value if a selection is in password field.
+ // ref. bug 565717
+ return elem instanceof HTMLTextAreaElement ||
+ (elem instanceof HTMLInputElement && elem.mozIsTextField(true));
+ };
+
+ if (isOnTextInput(element)) {
+ selection = element.QueryInterface(Ci.nsIDOMNSEditableElement)
+ .editor.selection.toString();
+ }
+ }
+
+ if (selection) {
+ if (selection.length > charLen) {
+ // only use the first charLen important chars. see bug 221361
+ var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
+ pattern.test(selection);
+ selection = RegExp.lastMatch;
+ }
+
+ selection = selection.trim().replace(/\s+/g, " ");
+
+ if (selection.length > charLen)
+ selection = selection.substr(0, charLen);
+ }
+ return selection;
+}
+
+var gWebPanelURI;
+function openWebPanel(aTitle, aURI)
+{
+ // Ensure that the web panels sidebar is open.
+ toggleSidebar('viewWebPanelsSidebar', true);
+
+ // Set the title of the panel.
+ document.getElementById("sidebar-title").value = aTitle;
+
+ // Tell the Web Panels sidebar to load the bookmark.
+ var sidebar = document.getElementById("sidebar");
+ if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) {
+ sidebar.contentWindow.loadWebPanel(aURI);
+ if (gWebPanelURI) {
+ gWebPanelURI = "";
+ sidebar.removeEventListener("load", asyncOpenWebPanel, true);
+ }
+ }
+ else {
+ // The panel is still being constructed. Attach an onload handler.
+ if (!gWebPanelURI)
+ sidebar.addEventListener("load", asyncOpenWebPanel, true);
+ gWebPanelURI = aURI;
+ }
+}
+
+function asyncOpenWebPanel(event)
+{
+ var sidebar = document.getElementById("sidebar");
+ if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser'))
+ sidebar.contentWindow.loadWebPanel(gWebPanelURI);
+ gWebPanelURI = "";
+ sidebar.removeEventListener("load", asyncOpenWebPanel, true);
+}
+
+/*
+ * - [ Dependencies ] ---------------------------------------------------------
+ * utilityOverlay.js:
+ * - gatherTextUnder
+ */
+
+/**
+ * Extracts linkNode and href for the current click target.
+ *
+ * @param event
+ * The click event.
+ * @return [href, linkNode].
+ *
+ * @note linkNode will be null if the click wasn't on an anchor
+ * element (or XLink).
+ */
+function hrefAndLinkNodeForClickEvent(event)
+{
+ function isHTMLLink(aNode)
+ {
+ // Be consistent with what nsContextMenu.js does.
+ return ((aNode instanceof HTMLAnchorElement && aNode.href) ||
+ (aNode instanceof HTMLAreaElement && aNode.href) ||
+ aNode instanceof HTMLLinkElement);
+ }
+
+ let node = event.target;
+ while (node && !isHTMLLink(node)) {
+ node = node.parentNode;
+ }
+
+ if (node)
+ return [node.href, node];
+
+ // If there is no linkNode, try simple XLink.
+ let href, baseURI;
+ node = event.target;
+ while (node && !href) {
+ if (node.nodeType == Node.ELEMENT_NODE &&
+ (node.localName == "a" ||
+ node.namespaceURI == "http://www.w3.org/1998/Math/MathML")) {
+ href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+ if (href) {
+ baseURI = node.baseURI;
+ break;
+ }
+ }
+ node = node.parentNode;
+ }
+
+ // In case of XLink, we don't return the node we got href from since
+ // callers expect <a>-like elements.
+ return [href ? makeURLAbsolute(baseURI, href) : null, null];
+}
+
+/**
+ * Called whenever the user clicks in the content area.
+ *
+ * @param event
+ * The click event.
+ * @param isPanelClick
+ * Whether the event comes from a web panel.
+ * @note default event is prevented if the click is handled.
+ */
+function contentAreaClick(event, isPanelClick)
+{
+ if (!event.isTrusted || event.defaultPrevented || event.button == 2)
+ return;
+
+ let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
+ if (!href) {
+ // Not a link, handle middle mouse navigation.
+ if (event.button == 1 &&
+ gPrefService.getBoolPref("middlemouse.contentLoadURL") &&
+ !gPrefService.getBoolPref("general.autoScroll")) {
+ middleMousePaste(event);
+ event.preventDefault();
+ }
+ return;
+ }
+
+ // This code only applies if we have a linkNode (i.e. clicks on real anchor
+ // elements, as opposed to XLink).
+ if (linkNode && event.button == 0 &&
+ !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
+ // A Web panel's links should target the main content area. Do this
+ // if no modifier keys are down and if there's no target or the target
+ // equals _main (the IE convention) or _content (the Mozilla convention).
+ let target = linkNode.target;
+ let mainTarget = !target || target == "_content" || target == "_main";
+ if (isPanelClick && mainTarget) {
+ // javascript and data links should be executed in the current browser.
+ if (linkNode.getAttribute("onclick") ||
+ href.startsWith("javascript:") ||
+ href.startsWith("data:"))
+ return;
+
+ try {
+ urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
+ }
+ catch(ex) {
+ // Prevent loading unsecure destinations.
+ event.preventDefault();
+ return;
+ }
+
+ loadURI(href, null, null, false);
+ event.preventDefault();
+ return;
+ }
+
+ if (linkNode.getAttribute("rel") == "sidebar") {
+ // This is the Opera convention for a special link that, when clicked,
+ // allows to add a sidebar panel. The link's title attribute contains
+ // the title that should be used for the sidebar panel.
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(href)
+ , title: linkNode.getAttribute("title")
+ , loadBookmarkInSidebar: true
+ , hiddenRows: [ "description"
+ , "location"
+ , "keyword" ]
+ }, window);
+ event.preventDefault();
+ return;
+ }
+ }
+
+ handleLinkClick(event, href, linkNode);
+
+ // Mark the page as a user followed link. This is done so that history can
+ // distinguish automatic embed visits from user activated ones. For example
+ // pages loaded in frames are embed visits and lost with the session, while
+ // visits across frames should be preserved.
+ try {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsFollowedLink(href);
+ } catch (ex) { /* Skip invalid URIs. */ }
+}
+
+/**
+ * Handles clicks on links.
+ *
+ * @return true if the click event was handled, false otherwise.
+ */
+function handleLinkClick(event, href, linkNode) {
+ if (event.button == 2) // right click
+ return false;
+
+ var where = whereToOpenLink(event);
+ if (where == "current")
+ return false;
+
+ var doc = event.target.ownerDocument;
+
+ if (where == "save") {
+ saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true,
+ true, doc.documentURIObject, doc);
+ event.preventDefault();
+ return true;
+ }
+
+ urlSecurityCheck(href, doc.nodePrincipal);
+ openLinkIn(href, where, { referrerURI: doc.documentURIObject,
+ charset: doc.characterSet });
+ event.preventDefault();
+ return true;
+}
+
+function middleMousePaste(event) {
+ let clipboard = readFromClipboard();
+ if (!clipboard)
+ return;
+
+ // Strip embedded newlines and surrounding whitespace, to match the URL
+ // bar's behavior (stripsurroundingwhitespace)
+ clipboard = clipboard.replace(/\s*\n\s*/g, "");
+
+ let mayInheritPrincipal = { value: false };
+ let url = getShortcutOrURI(clipboard, mayInheritPrincipal);
+ try {
+ makeURI(url);
+ } catch (ex) {
+ // Not a valid URI.
+ return;
+ }
+
+ try {
+ addToUrlbarHistory(url);
+ } catch (ex) {
+ // Things may go wrong when adding url to session history,
+ // but don't let that interfere with the loading of the url.
+ Cu.reportError(ex);
+ }
+
+ openUILink(url, event,
+ { ignoreButton: true,
+ disallowInheritPrincipal: !mayInheritPrincipal.value });
+
+ event.stopPropagation();
+}
+
+function handleDroppedLink(event, url, name)
+{
+ let postData = { };
+ let uri = getShortcutOrURI(url, postData);
+ if (uri)
+ loadURI(uri, null, postData.value, false);
+
+ // Keep the event from being handled by the dragDrop listeners
+ // built-in to goanna if they happen to be above us.
+ event.preventDefault();
+};
+
+function MultiplexHandler(event)
+{ try {
+ var node = event.target;
+ var name = node.getAttribute('name');
+
+ if (name == 'detectorGroup') {
+ BrowserCharsetReload();
+ SelectDetector(event, false);
+ } else if (name == 'charsetGroup') {
+ var charset = node.getAttribute('id');
+ charset = charset.substring(charset.indexOf('charset.') + 'charset.'.length);
+ BrowserSetForcedCharacterSet(charset);
+ } else if (name == 'charsetCustomize') {
+ //do nothing - please remove this else statement, once the charset prefs moves to the pref window
+ } else {
+ BrowserSetForcedCharacterSet(node.getAttribute('id'));
+ }
+ } catch(ex) { alert(ex); }
+}
+
+function SelectDetector(event, doReload)
+{
+ var uri = event.target.getAttribute("id");
+ var prefvalue = uri.substring(uri.indexOf('chardet.') + 'chardet.'.length);
+ if ("off" == prefvalue) { // "off" is special value to turn off the detectors
+ prefvalue = "";
+ }
+
+ try {
+ var str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+
+ str.data = prefvalue;
+ gPrefService.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
+ if (doReload)
+ window.content.location.reload();
+ }
+ catch (ex) {
+ dump("Failed to set the intl.charset.detector preference.\n");
+ }
+}
+
+function BrowserSetForcedCharacterSet(aCharset)
+{
+ gBrowser.docShell.charset = aCharset;
+ // Save the forced character-set
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset);
+ BrowserCharsetReload();
+}
+
+function BrowserCharsetReload()
+{
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+}
+
+function charsetMenuGetElement(parent, id) {
+ return parent.getElementsByAttribute("id", id)[0];
+}
+
+function UpdateCurrentCharset(target) {
+ // extract the charset from DOM
+ var wnd = document.commandDispatcher.focusedWindow;
+ if ((window == wnd) || (wnd == null)) wnd = window.content;
+
+ // Uncheck previous item
+ if (gPrevCharset) {
+ var pref_item = charsetMenuGetElement(target, "charset." + gPrevCharset);
+ if (pref_item)
+ pref_item.setAttribute('checked', 'false');
+ }
+
+ var menuitem = charsetMenuGetElement(target, "charset." + wnd.document.characterSet);
+ if (menuitem) {
+ menuitem.setAttribute('checked', 'true');
+ }
+}
+
+function UpdateCharsetDetector(target) {
+ var prefvalue;
+
+ try {
+ prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data;
+ }
+ catch (ex) {}
+
+ if (!prefvalue)
+ prefvalue = "off";
+
+ var menuitem = charsetMenuGetElement(target, "chardet." + prefvalue);
+ if (menuitem)
+ menuitem.setAttribute("checked", "true");
+}
+
+function UpdateMenus(event) {
+ UpdateCurrentCharset(event.target);
+ UpdateCharsetDetector(event.target);
+}
+
+function charsetLoadListener() {
+ var charset = window.content.document.characterSet;
+
+ if (charset.length > 0 && (charset != gLastBrowserCharset)) {
+ gPrevCharset = gLastBrowserCharset;
+ gLastBrowserCharset = charset;
+ }
+}
+
+
+var gPageStyleMenu = {
+
+ _getAllStyleSheets: function (frameset) {
+ var styleSheetsArray = Array.slice(frameset.document.styleSheets);
+ for (let i = 0; i < frameset.frames.length; i++) {
+ let frameSheets = this._getAllStyleSheets(frameset.frames[i]);
+ styleSheetsArray = styleSheetsArray.concat(frameSheets);
+ }
+ return styleSheetsArray;
+ },
+
+ fillPopup: function (menuPopup) {
+ var noStyle = menuPopup.firstChild;
+ var persistentOnly = noStyle.nextSibling;
+ var sep = persistentOnly.nextSibling;
+ while (sep.nextSibling)
+ menuPopup.removeChild(sep.nextSibling);
+
+ var styleSheets = this._getAllStyleSheets(window.content);
+ var currentStyleSheets = {};
+ var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled;
+ var haveAltSheets = false;
+ var altStyleSelected = false;
+
+ for (let currentStyleSheet of styleSheets) {
+ if (!currentStyleSheet.title)
+ continue;
+
+ // Skip any stylesheets whose media attribute doesn't match.
+ if (currentStyleSheet.media.length > 0) {
+ let mediaQueryList = currentStyleSheet.media.mediaText;
+ if (!window.content.matchMedia(mediaQueryList).matches)
+ continue;
+ }
+
+ if (!currentStyleSheet.disabled)
+ altStyleSelected = true;
+
+ haveAltSheets = true;
+
+ let lastWithSameTitle = null;
+ if (currentStyleSheet.title in currentStyleSheets)
+ lastWithSameTitle = currentStyleSheets[currentStyleSheet.title];
+
+ if (!lastWithSameTitle) {
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("type", "radio");
+ menuItem.setAttribute("label", currentStyleSheet.title);
+ menuItem.setAttribute("data", currentStyleSheet.title);
+ menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled);
+ menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));");
+ menuPopup.appendChild(menuItem);
+ currentStyleSheets[currentStyleSheet.title] = menuItem;
+ } else if (currentStyleSheet.disabled) {
+ lastWithSameTitle.removeAttribute("checked");
+ }
+ }
+
+ noStyle.setAttribute("checked", styleDisabled);
+ persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled);
+ persistentOnly.hidden = (window.content.document.preferredStyleSheetSet) ? haveAltSheets : false;
+ sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
+ },
+
+ _stylesheetInFrame: function (frame, title) {
+ return Array.some(frame.document.styleSheets,
+ function (stylesheet) stylesheet.title == title);
+ },
+
+ _stylesheetSwitchFrame: function (frame, title) {
+ var docStyleSheets = frame.document.styleSheets;
+
+ for (let i = 0; i < docStyleSheets.length; ++i) {
+ let docStyleSheet = docStyleSheets[i];
+
+ if (docStyleSheet.title)
+ docStyleSheet.disabled = (docStyleSheet.title != title);
+ else if (docStyleSheet.disabled)
+ docStyleSheet.disabled = false;
+ }
+ },
+
+ _stylesheetSwitchAll: function (frameset, title) {
+ if (!title || this._stylesheetInFrame(frameset, title))
+ this._stylesheetSwitchFrame(frameset, title);
+
+ for (let i = 0; i < frameset.frames.length; i++)
+ this._stylesheetSwitchAll(frameset.frames[i], title);
+ },
+
+ switchStyleSheet: function (title, contentWindow) {
+ getMarkupDocumentViewer().authorStyleDisabled = false;
+ this._stylesheetSwitchAll(contentWindow || content, title);
+ },
+
+ disableStyle: function () {
+ getMarkupDocumentViewer().authorStyleDisabled = true;
+ },
+};
+
+/* Legacy global page-style functions */
+var getAllStyleSheets = gPageStyleMenu._getAllStyleSheets.bind(gPageStyleMenu);
+var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu);
+function stylesheetSwitchAll(contentWindow, title) {
+ gPageStyleMenu.switchStyleSheet(title, contentWindow);
+}
+function setStyleDisabled(disabled) {
+ if (disabled)
+ gPageStyleMenu.disableStyle();
+}
+
+
+var BrowserOffline = {
+ _inited: false,
+
+ /////////////////////////////////////////////////////////////////////////////
+ // BrowserOffline Public Methods
+ init: function ()
+ {
+ if (!this._uiElement)
+ this._uiElement = document.getElementById("workOfflineMenuitemState");
+
+ Services.obs.addObserver(this, "network:offline-status-changed", false);
+
+ this._updateOfflineUI(Services.io.offline);
+
+ this._inited = true;
+ },
+
+ uninit: function ()
+ {
+ if (this._inited) {
+ Services.obs.removeObserver(this, "network:offline-status-changed");
+ }
+ },
+
+ toggleOfflineStatus: function ()
+ {
+ var ioService = Services.io;
+
+ // Stop automatic management of the offline status
+ try {
+ ioService.manageOfflineStatus = false;
+ } catch (ex) {
+ }
+
+ if (!ioService.offline && !this._canGoOffline()) {
+ this._updateOfflineUI(false);
+ return;
+ }
+
+ ioService.offline = !ioService.offline;
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // nsIObserver
+ observe: function (aSubject, aTopic, aState)
+ {
+ if (aTopic != "network:offline-status-changed")
+ return;
+
+ this._updateOfflineUI(aState == "offline");
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // BrowserOffline Implementation Methods
+ _canGoOffline: function ()
+ {
+ try {
+ var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null);
+
+ // Something aborted the quit process.
+ if (cancelGoOffline.data)
+ return false;
+ }
+ catch (ex) {
+ }
+
+ return true;
+ },
+
+ _uiElement: null,
+ _updateOfflineUI: function (aOffline)
+ {
+ var offlineLocked = gPrefService.prefIsLocked("network.online");
+ if (offlineLocked)
+ this._uiElement.setAttribute("disabled", "true");
+
+ this._uiElement.setAttribute("checked", aOffline);
+ }
+};
+
+var OfflineApps = {
+ /////////////////////////////////////////////////////////////////////////////
+ // OfflineApps Public Methods
+ init: function ()
+ {
+ Services.obs.addObserver(this, "offline-cache-update-completed", false);
+ },
+
+ uninit: function ()
+ {
+ Services.obs.removeObserver(this, "offline-cache-update-completed");
+ },
+
+ handleEvent: function(event) {
+ if (event.type == "MozApplicationManifest") {
+ this.offlineAppRequested(event.originalTarget.defaultView);
+ }
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // OfflineApps Implementation Methods
+
+ // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow
+ // were taken from browser/components/feeds/src/WebContentConverter.
+ _getBrowserWindowForContentWindow: function(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .wrappedJSObject;
+ },
+
+ _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) {
+ // This depends on pseudo APIs of browser.js and tabbrowser.xml
+ aContentWindow = aContentWindow.top;
+ var browsers = aBrowserWindow.gBrowser.browsers;
+ for (let browser of browsers) {
+ if (browser.contentWindow == aContentWindow)
+ return browser;
+ }
+ // handle other browser/iframe elements that may need popupnotifications
+ let browser = aContentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ if (browser.getAttribute("popupnotificationanchor"))
+ return browser;
+ return null;
+ },
+
+ _getManifestURI: function(aWindow) {
+ if (!aWindow.document.documentElement)
+ return null;
+
+ var attr = aWindow.document.documentElement.getAttribute("manifest");
+ if (!attr)
+ return null;
+
+ try {
+ var contentURI = makeURI(aWindow.location.href, null, null);
+ return makeURI(attr, aWindow.document.characterSet, contentURI);
+ } catch (e) {
+ return null;
+ }
+ },
+
+ // A cache update isn't tied to a specific window. Try to find
+ // the best browser in which to warn the user about space usage
+ _getBrowserForCacheUpdate: function(aCacheUpdate) {
+ // Prefer the current browser
+ var uri = this._getManifestURI(content);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return gBrowser.selectedBrowser;
+ }
+
+ var browsers = gBrowser.browsers;
+ for (let browser of browsers) {
+ uri = this._getManifestURI(browser.contentWindow);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return browser;
+ }
+ }
+
+ // is this from a non-tab browser/iframe?
+ browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]");
+ for (let browser of browsers) {
+ uri = this._getManifestURI(browser.contentWindow);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return browser;
+ }
+ }
+
+ return null;
+ },
+
+ _warnUsage: function(aBrowser, aURI) {
+ if (!aBrowser)
+ return;
+
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.manageUsage"),
+ accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
+ callback: OfflineApps.manage
+ };
+
+ let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
+ let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
+ [ aURI.host,
+ warnQuota / 1024 ]);
+
+ let anchorID = "indexedDB-notification-icon";
+ PopupNotifications.show(aBrowser, "offline-app-usage", message,
+ anchorID, mainAction);
+
+ // Now that we've warned once, prevent the warning from showing up
+ // again.
+ Services.perms.add(aURI, "offline-app",
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+ },
+
+ // XXX: duplicated in preferences/advanced.js
+ _getOfflineAppUsage: function (host, groups)
+ {
+ var cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService);
+ if (!groups)
+ groups = cacheService.getGroups();
+
+ var usage = 0;
+ for (let group of groups) {
+ var uri = Services.io.newURI(group, null, null);
+ if (uri.asciiHost == host) {
+ var cache = cacheService.getActiveCache(group);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ _checkUsage: function(aURI) {
+ // if the user has already allowed excessive usage, don't bother checking
+ if (Services.perms.testExactPermission(aURI, "offline-app") !=
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
+ var usage = this._getOfflineAppUsage(aURI.asciiHost);
+ var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
+ if (usage >= warnQuota * 1024) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ offlineAppRequested: function(aContentWindow) {
+ if (!gPrefService.getBoolPref("browser.offline-apps.notify")) {
+ return;
+ }
+
+ let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
+ let browser = this._getBrowserForContentWindow(browserWindow,
+ aContentWindow);
+
+ let currentURI = aContentWindow.document.documentURIObject;
+
+ // don't bother showing UI if the user has already made a decision
+ if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
+ return;
+
+ try {
+ if (gPrefService.getBoolPref("offline-apps.allow_by_default")) {
+ // all pages can use offline capabilities, no need to ask the user
+ return;
+ }
+ } catch(e) {
+ // this pref isn't set by default, ignore failures
+ }
+
+ let host = currentURI.asciiHost;
+ let notificationID = "offline-app-requested-" + host;
+ let notification = PopupNotifications.getNotification(notificationID, browser);
+
+ if (notification) {
+ notification.options.documents.push(aContentWindow.document);
+ } else {
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ for (let document of notification.options.documents) {
+ OfflineApps.allowSite(document);
+ }
+ }
+ };
+ let secondaryActions = [{
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ for (let document of notification.options.documents) {
+ OfflineApps.disallowSite(document);
+ }
+ }
+ }];
+ let message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ let anchorID = "indexedDB-notification-icon";
+ let options= {
+ documents : [ aContentWindow.document ]
+ };
+ notification = PopupNotifications.show(browser, notificationID, message,
+ anchorID, mainAction,
+ secondaryActions, options);
+ }
+ },
+
+ allowSite: function(aDocument) {
+ Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
+
+ // When a site is enabled while loading, manifest resources will
+ // start fetching immediately. This one time we need to do it
+ // ourselves.
+ this._startFetching(aDocument);
+ },
+
+ disallowSite: function(aDocument) {
+ Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
+ },
+
+ manage: function() {
+ openAdvancedPreferences("networkTab");
+ },
+
+ _startFetching: function(aDocument) {
+ if (!aDocument.documentElement)
+ return;
+
+ var manifest = aDocument.documentElement.getAttribute("manifest");
+ if (!manifest)
+ return;
+
+ var manifestURI = makeURI(manifest, aDocument.characterSet,
+ aDocument.documentURIObject);
+
+ var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window);
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // nsIObserver
+ observe: function (aSubject, aTopic, aState)
+ {
+ if (aTopic == "offline-cache-update-completed") {
+ var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
+
+ var uri = cacheUpdate.manifestURI;
+ if (OfflineApps._checkUsage(uri)) {
+ var browser = this._getBrowserForCacheUpdate(cacheUpdate);
+ if (browser) {
+ OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
+ }
+ }
+ }
+ }
+};
+
+var IndexedDBPromptHelper = {
+ _permissionsPrompt: "indexedDB-permissions-prompt",
+ _permissionsResponse: "indexedDB-permissions-response",
+
+ _quotaPrompt: "indexedDB-quota-prompt",
+ _quotaResponse: "indexedDB-quota-response",
+ _quotaCancel: "indexedDB-quota-cancel",
+
+ _notificationIcon: "indexedDB-notification-icon",
+
+ init:
+ function IndexedDBPromptHelper_init() {
+ Services.obs.addObserver(this, this._permissionsPrompt, false);
+ Services.obs.addObserver(this, this._quotaPrompt, false);
+ Services.obs.addObserver(this, this._quotaCancel, false);
+ },
+
+ uninit:
+ function IndexedDBPromptHelper_uninit() {
+ Services.obs.removeObserver(this, this._permissionsPrompt);
+ Services.obs.removeObserver(this, this._quotaPrompt);
+ Services.obs.removeObserver(this, this._quotaCancel);
+ },
+
+ observe:
+ function IndexedDBPromptHelper_observe(subject, topic, data) {
+ if (topic != this._permissionsPrompt &&
+ topic != this._quotaPrompt &&
+ topic != this._quotaCancel) {
+ throw new Error("Unexpected topic!");
+ }
+
+ var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
+
+ var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+ var contentDocument = contentWindow.document;
+ var browserWindow =
+ OfflineApps._getBrowserWindowForContentWindow(contentWindow);
+
+ if (browserWindow != window) {
+ // Must belong to some other window.
+ return;
+ }
+
+ var browser =
+ OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow);
+
+ var host = contentDocument.documentURIObject.asciiHost;
+
+ var message;
+ var responseTopic;
+ if (topic == this._permissionsPrompt) {
+ message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ responseTopic = this._permissionsResponse;
+ }
+ else if (topic == this._quotaPrompt) {
+ message = gNavigatorBundle.getFormattedString("indexedDB.usage",
+ [ host, data ]);
+ responseTopic = this._quotaResponse;
+ }
+ else if (topic == this._quotaCancel) {
+ responseTopic = this._quotaResponse;
+ }
+
+ const hiddenTimeoutDuration = 30000; // 30 seconds
+ const firstTimeoutDuration = 300000; // 5 minutes
+
+ var timeoutId;
+
+ var observer = requestor.getInterface(Ci.nsIObserver);
+
+ var mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+ };
+
+ var secondaryActions = [
+ {
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.DENY_ACTION);
+ }
+ }
+ ];
+
+ // This will be set to the result of PopupNotifications.show() below, or to
+ // the result of PopupNotifications.getNotification() if this is a
+ // quotaCancel notification.
+ var notification;
+
+ function timeoutNotification() {
+ // Remove the notification.
+ if (notification) {
+ notification.remove();
+ }
+
+ // Clear all of our timeout stuff. We may be called directly, not just
+ // when the timeout actually elapses.
+ clearTimeout(timeoutId);
+
+ // And tell the page that the popup timed out.
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ }
+
+ var options = {
+ eventCallback: function(state) {
+ // Don't do anything if the timeout has not been set yet.
+ if (!timeoutId) {
+ return;
+ }
+
+ // If the popup is being dismissed start the short timeout.
+ if (state == "dismissed") {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration);
+ return;
+ }
+
+ // If the popup is being re-shown then clear the timeout allowing
+ // unlimited waiting.
+ if (state == "shown") {
+ clearTimeout(timeoutId);
+ }
+ }
+ };
+
+ if (topic == this._quotaCancel) {
+ notification = PopupNotifications.getNotification(this._quotaPrompt,
+ browser);
+ timeoutNotification();
+ return;
+ }
+
+ notification = PopupNotifications.show(browser, topic, message,
+ this._notificationIcon, mainAction,
+ secondaryActions, options);
+
+ // Set the timeoutId after the popup has been created, and use the long
+ // timeout value. If the user doesn't notice the popup after this amount of
+ // time then it is most likely not visible and we want to alert the page.
+ timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
+ }
+};
+
+function WindowIsClosing()
+{
+ let event = document.createEvent("Events");
+ event.initEvent("WindowIsClosing", true, true);
+ if (!window.dispatchEvent(event))
+ return false;
+
+ if (!closeWindow(false, warnAboutClosingWindow))
+ return false;
+
+ for (let browser of gBrowser.browsers) {
+ let ds = browser.docShell;
+ if (ds.contentViewer && !ds.contentViewer.permitUnload())
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks if this is the last full *browser* window around. If it is, this will
+ * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
+ * @returns true if closing can proceed, false if it got cancelled.
+ */
+function warnAboutClosingWindow() {
+ // Popups aren't considered full browser windows.
+ let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window);
+ if (!isPBWindow && !toolbar.visible)
+ return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+
+ // Figure out if there's at least one other browser window around.
+ let e = Services.wm.getEnumerator("navigator:browser");
+ let otherPBWindowExists = false;
+ let nonPopupPresent = false;
+ while (e.hasMoreElements()) {
+ let win = e.getNext();
+ if (win != window) {
+ if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win))
+ otherPBWindowExists = true;
+ if (win.toolbar.visible)
+ nonPopupPresent = true;
+ // If the current window is not in private browsing mode we don't need to
+ // look for other pb windows, we can leave the loop when finding the
+ // first non-popup window. If however the current window is in private
+ // browsing mode then we need at least one other pb and one non-popup
+ // window to break out early.
+ if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent)
+ break;
+ }
+ }
+
+ if (isPBWindow && !otherPBWindowExists) {
+ let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ exitingCanceled.data = false;
+ Services.obs.notifyObservers(exitingCanceled,
+ "last-pb-context-exiting",
+ null);
+ if (exitingCanceled.data)
+ return false;
+ }
+
+ if (nonPopupPresent) {
+ return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+ }
+
+ let os = Services.obs;
+
+ let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ os.notifyObservers(closingCanceled,
+ "browser-lastwindow-close-requested", null);
+ if (closingCanceled.data)
+ return false;
+
+ os.notifyObservers(null, "browser-lastwindow-close-granted", null);
+
+#ifdef XP_MACOSX
+ // OS X doesn't quit the application when the last window is closed, but keeps
+ // the session alive. Hence don't prompt users to save tabs, but warn about
+ // closing multiple tabs.
+ return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+#else
+ return true;
+#endif
+}
+
+var MailIntegration = {
+ sendLinkForWindow: function (aWindow) {
+ this.sendMessage(aWindow.location.href,
+ aWindow.document.title);
+ },
+
+ sendMessage: function (aBody, aSubject) {
+ // generate a mailto url based on the url and the url's title
+ var mailtoUrl = "mailto:";
+ if (aBody) {
+ mailtoUrl += "?body=" + encodeURIComponent(aBody);
+ mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
+ }
+
+ var uri = makeURI(mailtoUrl);
+
+ // now pass this uri to the operating system
+ this._launchExternalUrl(uri);
+ },
+
+ // a generic method which can be used to pass arbitrary urls to the operating
+ // system.
+ // aURL --> a nsIURI which represents the url to launch
+ _launchExternalUrl: function (aURL) {
+ var extProtocolSvc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ if (extProtocolSvc)
+ extProtocolSvc.loadUrl(aURL);
+ }
+};
+
+function BrowserOpenAddonsMgr(aView) {
+ if (aView) {
+ let emWindow;
+ let browserWindow;
+
+ var receivePong = function receivePong(aSubject, aTopic, aData) {
+ let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ if (!emWindow || browserWin == window /* favor the current window */) {
+ emWindow = aSubject;
+ browserWindow = browserWin;
+ }
+ }
+ Services.obs.addObserver(receivePong, "EM-pong", false);
+ Services.obs.notifyObservers(null, "EM-ping", "");
+ Services.obs.removeObserver(receivePong, "EM-pong");
+
+ if (emWindow) {
+ emWindow.loadView(aView);
+ browserWindow.gBrowser.selectedTab =
+ browserWindow.gBrowser._getTabForContentWindow(emWindow);
+ emWindow.focus();
+ return;
+ }
+ }
+
+ var newLoad = !switchToTabHavingURI("about:addons", true);
+
+ if (aView) {
+ // This must be a new load, else the ping/pong would have
+ // found the window above.
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observer, aTopic);
+ aSubject.loadView(aView);
+ }, "EM-loaded", false);
+ }
+}
+
+function BrowserOpenPermissionsMgr() {
+ switchToTabHavingURI("about:permissions", true);
+}
+
+function AddKeywordForSearchField() {
+ var node = document.popupNode;
+
+ var charset = node.ownerDocument.characterSet;
+
+ var docURI = makeURI(node.ownerDocument.URL,
+ charset);
+
+ var formURI = makeURI(node.form.getAttribute("action"),
+ charset,
+ docURI);
+
+ var spec = formURI.spec;
+
+ var isURLEncoded =
+ (node.form.method.toUpperCase() == "POST"
+ && (node.form.enctype == "application/x-www-form-urlencoded" ||
+ node.form.enctype == ""));
+
+ var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
+ [node.ownerDocument.title]);
+ var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
+
+ var formData = [];
+
+ function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
+ if (aIsFormUrlEncoded)
+ return escape(aName + "=" + aValue);
+ else
+ return escape(aName) + "=" + escape(aValue);
+ }
+
+ for (let el of node.form.elements) {
+ if (!el.type) // happens with fieldsets
+ continue;
+
+ if (el == node) {
+ formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
+ // Don't escape "%s", just append
+ escapeNameValuePair(el.name, "", false) + "%s");
+ continue;
+ }
+
+ let type = el.type.toLowerCase();
+
+ if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) ||
+ type == "hidden" || type == "textarea") ||
+ ((type == "checkbox" || type == "radio") && el.checked)) {
+ formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
+ } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) {
+ for (var j=0; j < el.options.length; j++) {
+ if (el.options[j].selected)
+ formData.push(escapeNameValuePair(el.name, el.options[j].value,
+ isURLEncoded));
+ }
+ }
+ }
+
+ var postData;
+
+ if (isURLEncoded)
+ postData = formData.join("&");
+ else
+ spec += "?" + formData.join("&");
+
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(spec)
+ , title: title
+ , description: description
+ , keyword: ""
+ , postData: postData
+ , charSet: charset
+ , hiddenRows: [ "location"
+ , "description"
+ , "tags"
+ , "loadInSidebar" ]
+ }, window);
+}
+
+function SwitchDocumentDirection(aWindow) {
+ // document.dir can also be "auto", in which case it won't change
+ if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") {
+ aWindow.document.dir = "rtl";
+ } else if (aWindow.document.dir == "rtl") {
+ aWindow.document.dir = "ltr";
+ }
+ for (var run = 0; run < aWindow.frames.length; run++)
+ SwitchDocumentDirection(aWindow.frames[run]);
+}
+
+function convertFromUnicode(charset, str)
+{
+ try {
+ var unicodeConverter = Components
+ .classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = charset;
+ str = unicodeConverter.ConvertFromUnicode(str);
+ return str + unicodeConverter.Finish();
+ } catch(ex) {
+ return null;
+ }
+}
+
+/**
+ * Re-open a closed tab.
+ * @param aIndex
+ * The index of the tab (via nsSessionStore.getClosedTabData)
+ * @returns a reference to the reopened tab.
+ */
+function undoCloseTab(aIndex) {
+ // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
+ var blankTabToRemove = null;
+ if (gBrowser.tabs.length == 1 &&
+ !gPrefService.getBoolPref("browser.tabs.autoHide") &&
+ isTabEmpty(gBrowser.selectedTab))
+ blankTabToRemove = gBrowser.selectedTab;
+
+ var tab = null;
+ var ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ if (ss.getClosedTabCount(window) > (aIndex || 0)) {
+ tab = ss.undoCloseTab(window, aIndex || 0);
+
+ if (blankTabToRemove)
+ gBrowser.removeTab(blankTabToRemove);
+ }
+
+ return tab;
+}
+
+/**
+ * Re-open a closed window.
+ * @param aIndex
+ * The index of the window (via nsSessionStore.getClosedWindowData)
+ * @returns a reference to the reopened window.
+ */
+function undoCloseWindow(aIndex) {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ let window = null;
+ if (ss.getClosedWindowCount() > (aIndex || 0))
+ window = ss.undoCloseWindow(aIndex || 0);
+
+ return window;
+}
+
+/*
+ * Determines if a tab is "empty", usually used in the context of determining
+ * if it's ok to close the tab.
+ */
+function isTabEmpty(aTab) {
+ if (aTab.hasAttribute("busy"))
+ return false;
+
+ let browser = aTab.linkedBrowser;
+ if (!isBlankPageURL(browser.currentURI.spec))
+ return false;
+
+ // Bug 863515 - Make content.opener checks work in electrolysis.
+ if (!gMultiProcessBrowser && browser.contentWindow.opener)
+ return false;
+
+ if (browser.sessionHistory && browser.sessionHistory.count >= 2)
+ return false;
+
+ return true;
+}
+
+#ifdef MOZ_SERVICES_SYNC
+function BrowserOpenSyncTabs() {
+ switchToTabHavingURI("about:sync-tabs", true);
+}
+#endif
+
+/**
+ * Format a URL
+ * eg:
+ * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
+ * > https://addons.mozilla.org/en-US/firefox/3.0a1/
+ *
+ * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
+ */
+function formatURL(aFormat, aIsPref) {
+ var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
+ return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat);
+}
+
+/**
+ * Utility object to handle manipulations of the identity indicators in the UI
+ */
+var gIdentityHandler = {
+ // Mode strings used to control CSS display
+ IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information
+ IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification
+ IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information
+ IDENTITY_MODE_MIXED_CONTENT : "unknownIdentity mixedContent", // SSL with unauthenticated content
+ IDENTITY_MODE_MIXED_ACTIVE_CONTENT : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated content
+ IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI
+
+ // Cache the most recent SSLStatus and Location seen in checkIdentity
+ _lastStatus : null,
+ _lastLocation : null,
+ _mode : "unknownIdentity",
+
+ // smart getters
+ get _encryptionLabel () {
+ delete this._encryptionLabel;
+ this._encryptionLabel = {};
+ this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] =
+ gNavigatorBundle.getString("identity.encrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] =
+ gNavigatorBundle.getString("identity.encrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] =
+ gNavigatorBundle.getString("identity.unencrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_MIXED_CONTENT] =
+ gNavigatorBundle.getString("identity.mixed_content");
+ this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT] =
+ gNavigatorBundle.getString("identity.mixed_content");
+ return this._encryptionLabel;
+ },
+ get _identityPopup () {
+ delete this._identityPopup;
+ return this._identityPopup = document.getElementById("identity-popup");
+ },
+ get _identityBox () {
+ delete this._identityBox;
+ return this._identityBox = document.getElementById("identity-box");
+ },
+ get _identityPopupContentBox () {
+ delete this._identityPopupContentBox;
+ return this._identityPopupContentBox =
+ document.getElementById("identity-popup-content-box");
+ },
+ get _identityPopupContentHost () {
+ delete this._identityPopupContentHost;
+ return this._identityPopupContentHost =
+ document.getElementById("identity-popup-content-host");
+ },
+ get _identityPopupContentOwner () {
+ delete this._identityPopupContentOwner;
+ return this._identityPopupContentOwner =
+ document.getElementById("identity-popup-content-owner");
+ },
+ get _identityPopupContentSupp () {
+ delete this._identityPopupContentSupp;
+ return this._identityPopupContentSupp =
+ document.getElementById("identity-popup-content-supplemental");
+ },
+ get _identityPopupContentVerif () {
+ delete this._identityPopupContentVerif;
+ return this._identityPopupContentVerif =
+ document.getElementById("identity-popup-content-verifier");
+ },
+ get _identityPopupEncLabel () {
+ delete this._identityPopupEncLabel;
+ return this._identityPopupEncLabel =
+ document.getElementById("identity-popup-encryption-label");
+ },
+ get _identityIconLabel () {
+ delete this._identityIconLabel;
+ return this._identityIconLabel = document.getElementById("identity-icon-label");
+ },
+ get _overrideService () {
+ delete this._overrideService;
+ return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ },
+ get _identityIconCountryLabel () {
+ delete this._identityIconCountryLabel;
+ return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
+ },
+ get _identityIcon () {
+ delete this._identityIcon;
+ return this._identityIcon = document.getElementById("page-proxy-favicon");
+ },
+
+ /**
+ * Rebuild cache of the elements that may or may not exist depending
+ * on whether there's a location bar.
+ */
+ _cacheElements : function() {
+ delete this._identityBox;
+ delete this._identityIconLabel;
+ delete this._identityIconCountryLabel;
+ delete this._identityIcon;
+ this._identityBox = document.getElementById("identity-box");
+ this._identityIconLabel = document.getElementById("identity-icon-label");
+ this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
+ this._identityIcon = document.getElementById("page-proxy-favicon");
+ },
+
+ /**
+ * Handler for mouseclicks on the "More Information" button in the
+ * "identity-popup" panel.
+ */
+ handleMoreInfoClick : function(event) {
+ displaySecurityInfo();
+ event.stopPropagation();
+ },
+
+ /**
+ * Helper to parse out the important parts of _lastStatus (of the SSL cert in
+ * particular) for use in constructing identity UI strings
+ */
+ getIdentityData : function() {
+ var result = {};
+ var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
+ var cert = status.serverCert;
+
+ // Human readable name of Subject
+ result.subjectOrg = cert.organization;
+
+ // SubjectName fields, broken up for individual access
+ if (cert.subjectName) {
+ result.subjectNameFields = {};
+ cert.subjectName.split(",").forEach(function(v) {
+ var field = v.split("=");
+ this[field[0]] = field[1];
+ }, result.subjectNameFields);
+
+ // Call out city, state, and country specifically
+ result.city = result.subjectNameFields.L;
+ result.state = result.subjectNameFields.ST;
+ result.country = result.subjectNameFields.C;
+ }
+
+ // Human readable name of Certificate Authority
+ result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
+ result.cert = cert;
+
+ return result;
+ },
+
+ /**
+ * Determine the identity of the page being displayed by examining its SSL cert
+ * (if available) and, if necessary, update the UI to reflect this. Intended to
+ * be called by onSecurityChange
+ *
+ * @param PRUint32 state
+ * @param JS Object location that mirrors an nsLocation (i.e. has .host and
+ * .hostname and .port)
+ */
+ checkIdentity : function(state, location) {
+ var currentStatus = gBrowser.securityUI
+ .QueryInterface(Components.interfaces.nsISSLStatusProvider)
+ .SSLStatus;
+ this._lastStatus = currentStatus;
+ this._lastLocation = location;
+
+ let nsIWebProgressListener = Ci.nsIWebProgressListener;
+ if (location.protocol == "chrome:" || location.protocol == "about:") {
+ this.setMode(this.IDENTITY_MODE_CHROMEUI);
+ } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
+ this.setMode(this.IDENTITY_MODE_IDENTIFIED);
+ } else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
+ this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
+ } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) {
+ if ((state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
+ gPrefService.getBoolPref("security.mixed_content.block_active_content")) {
+ this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT);
+ } else {
+ this.setMode(this.IDENTITY_MODE_MIXED_CONTENT);
+ }
+ } else {
+ this.setMode(this.IDENTITY_MODE_UNKNOWN);
+ }
+
+ // Ensure the doorhanger is shown when mixed active content is blocked.
+ if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT)
+ this.showMixedContentDoorhanger();
+ },
+
+ /**
+ * Display the Mixed Content Blocker doohanger, providing an option
+ * to the user to override mixed content blocking
+ */
+ showMixedContentDoorhanger : function() {
+ // If we've already got an active notification, bail out to avoid showing it repeatedly.
+ if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser))
+ return;
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]);
+ let action = {
+ label: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.label"),
+ accessKey: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.accesskey"),
+ callback: function() { /* NOP */ }
+ };
+ let secondaryActions = [
+ {
+ label: gNavigatorBundle.getString("mixedContentBlocked.unblock.label"),
+ accessKey: gNavigatorBundle.getString("mixedContentBlocked.unblock.accesskey"),
+ callback: function() {
+ // Reload the page with the content unblocked
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
+ }
+ }
+ ];
+ let options = {
+ dismissed: true,
+ learnMoreURL: Services.urlFormatter.formatURLPref("browser.mixedcontent.warning.infoURL"),
+ };
+ PopupNotifications.show(gBrowser.selectedBrowser, "mixed-content-blocked",
+ messageString, "mixed-content-blocked-notification-icon",
+ action, secondaryActions, options);
+ },
+
+ /**
+ * Return the eTLD+1 version of the current hostname
+ */
+ getEffectiveHost : function() {
+ try {
+ let baseDomain =
+ Services.eTLD.getBaseDomainFromHost(this._lastLocation.hostname);
+ return this._IDNService.convertToDisplayIDN(baseDomain, {});
+ } catch (e) {
+ // If something goes wrong (e.g. hostname is an IP address) just fail back
+ // to the full domain.
+ return this._lastLocation.hostname;
+ }
+ },
+
+ /**
+ * Update the UI to reflect the specified mode, which should be one of the
+ * IDENTITY_MODE_* constants.
+ */
+ setMode : function(newMode) {
+ if (!this._identityBox) {
+ // No identity box means the identity box is not visible, in which
+ // case there's nothing to do.
+ return;
+ }
+
+ this._identityBox.className = newMode;
+ this.setIdentityMessages(newMode);
+
+ // Update the popup too, if it's open
+ if (this._identityPopup.state == "open")
+ this.setPopupMessages(newMode);
+
+ this._mode = newMode;
+ },
+
+ /**
+ * Set up the messages for the primary identity UI based on the specified mode,
+ * and the details of the SSL cert, where applicable
+ *
+ * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
+ */
+ setIdentityMessages : function(newMode) {
+ let icon_label = "";
+ let tooltip = "";
+ let icon_country_label = "";
+ let icon_labels_dir = "ltr";
+
+ if (!this._IDNService)
+ this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ let punyID = gPrefService.getIntPref("browser.identity.display_punycode", 1);
+
+ switch (newMode) {
+ case this.IDENTITY_MODE_DOMAIN_VERIFIED: {
+ let iData = this.getIdentityData();
+
+ let label_display = "";
+
+ //Pale Moon: honor browser.identity.ssl_domain_display!
+ switch (gPrefService.getIntPref("browser.identity.ssl_domain_display")) {
+ case 2 : // Show full domain
+ label_display = this._lastLocation.hostname;
+ break;
+ case 1 : // Show eTLD.
+ label_display = this.getEffectiveHost();
+ }
+
+ if (punyID >= 1) {
+ // Display punycode version in identity panel
+ icon_label = this._IDNService.convertUTF8toACE(label_display);
+ } else {
+ icon_label = label_display;
+ }
+
+ // Verifier is either the CA Org, for a normal cert, or a special string
+ // for certs that are trusted because of a security exception.
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [iData.caOrg]);
+
+ // Check whether this site is a security exception. XPConnect does the right
+ // thing here in terms of converting _lastLocation.port from string to int, but
+ // the overrideService doesn't like undefined ports, so make sure we have
+ // something in the default case (bug 432241).
+ // .hostname can return an empty string in some exceptional cases -
+ // hasMatchingOverride does not handle that, so avoid calling it.
+ // Updating the tooltip value in those cases isn't critical.
+ // FIXME: Fixing bug 646690 would probably makes this check unnecessary
+ if (this._lastLocation.hostname &&
+ this._overrideService.hasMatchingOverride(this._lastLocation.hostname,
+ (this._lastLocation.port || 443),
+ iData.cert, {}, {}))
+ tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
+ break; }
+ case this.IDENTITY_MODE_IDENTIFIED: {
+ // If it's identified, then we can populate the dialog with credentials
+ let iData = this.getIdentityData();
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [iData.caOrg]);
+ icon_label = iData.subjectOrg;
+ if (iData.country)
+ icon_country_label = "(" + iData.country + ")";
+
+ // If the organization name starts with an RTL character, then
+ // swap the positions of the organization and country code labels.
+ // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
+ // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
+ // fixed, this test should be replaced by one adhering to the
+ // Unicode Bidirectional Algorithm proper (at the paragraph level).
+ icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
+ "rtl" : "ltr";
+ break; }
+ case this.IDENTITY_MODE_CHROMEUI:
+ break;
+ default:
+ tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
+ if (punyID == 2) {
+ // Check for IDN and display if so...
+ let rawHost = this._IDNService.convertUTF8toACE(this._lastLocation.hostname);
+ if (this._IDNService.isACE(rawHost)) {
+ icon_label = rawHost;
+ }
+ }
+ }
+
+ // Push the appropriate strings out to the UI
+ this._identityBox.tooltipText = tooltip;
+ this._identityIconLabel.value = icon_label;
+ this._identityIconCountryLabel.value = icon_country_label;
+ // Set cropping and direction
+ this._identityIconLabel.crop = icon_country_label ? "end" : "center";
+ this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
+ // Hide completely if the organization label is empty
+ this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
+ },
+
+ /**
+ * Set up the title and content messages for the identity message popup,
+ * based on the specified mode, and the details of the SSL cert, where
+ * applicable
+ *
+ * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
+ */
+ setPopupMessages : function(newMode) {
+
+ this._identityPopup.className = newMode;
+ this._identityPopupContentBox.className = newMode;
+
+ // Set the static strings up front
+ this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode];
+
+ // Initialize the optional strings to empty values
+ let supplemental = "";
+ let verifier = "";
+ let host = "";
+ let owner = "";
+
+ switch (newMode) {
+ case this.IDENTITY_MODE_DOMAIN_VERIFIED:
+ host = this.getEffectiveHost();
+ owner = gNavigatorBundle.getString("identity.ownerUnknown2");
+ verifier = this._identityBox.tooltipText;
+ break;
+ case this.IDENTITY_MODE_IDENTIFIED: {
+ // If it's identified, then we can populate the dialog with credentials
+ let iData = this.getIdentityData();
+ host = this.getEffectiveHost();
+ owner = iData.subjectOrg;
+ verifier = this._identityBox.tooltipText;
+
+ // Build an appropriate supplemental block out of whatever location data we have
+ if (iData.city)
+ supplemental += iData.city + "\n";
+ if (iData.state && iData.country)
+ supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
+ [iData.state, iData.country]);
+ else if (iData.state) // State only
+ supplemental += iData.state;
+ else if (iData.country) // Country only
+ supplemental += iData.country;
+ break; }
+ }
+
+ // Push the appropriate strings out to the UI
+ this._identityPopupContentHost.textContent = host;
+ this._identityPopupContentOwner.textContent = owner;
+ this._identityPopupContentSupp.textContent = supplemental;
+ this._identityPopupContentVerif.textContent = verifier;
+ },
+
+ hideIdentityPopup : function() {
+ this._identityPopup.hidePopup();
+ },
+
+ /**
+ * Click handler for the identity-box element in primary chrome.
+ */
+ handleIdentityButtonEvent : function(event) {
+ event.stopPropagation();
+
+ if ((event.type == "click" && event.button != 0) ||
+ (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
+ event.keyCode != KeyEvent.DOM_VK_RETURN)) {
+ return; // Left click, space or enter only
+ }
+
+ // Don't allow left click, space or enter if the location
+ // is chrome UI or the location has been modified.
+ if (this._mode == this.IDENTITY_MODE_CHROMEUI ||
+ gURLBar.getAttribute("pageproxystate") != "valid") {
+ return;
+ }
+
+ // Make sure that the display:none style we set in xul is removed now that
+ // the popup is actually needed
+ this._identityPopup.hidden = false;
+
+ // Update the popup strings
+ this.setPopupMessages(this._identityBox.className);
+
+ // Add the "open" attribute to the identity box for styling
+ this._identityBox.setAttribute("open", "true");
+ var self = this;
+ this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) {
+ e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false);
+ self._identityBox.removeAttribute("open");
+ }, false);
+
+ // Now open the popup, anchored off the primary chrome element
+ this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
+ },
+
+ onPopupShown : function(event) {
+ document.getElementById('identity-popup-more-info-button').focus();
+ },
+
+ onDragStart: function (event) {
+ if (gURLBar.getAttribute("pageproxystate") != "valid")
+ return;
+
+ var value = content.location.href;
+ var urlString = value + "\n" + content.document.title;
+ var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", urlString);
+ dt.setData("text/uri-list", value);
+ dt.setData("text/plain", value);
+ dt.setData("text/html", htmlString);
+ dt.setDragImage(gProxyFavIcon, 16, 16);
+ }
+};
+
+function getNotificationBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getNotificationBox(foundBrowser)
+ return null;
+};
+
+function getTabModalPromptBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getTabModalPromptBox(foundBrowser);
+ return null;
+};
+
+/* DEPRECATED */
+function getBrowser() gBrowser;
+function getNavToolbox() gNavToolbox;
+
+let gPrivateBrowsingUI = {
+ init: function PBUI_init() {
+ // Do nothing for normal windows
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+
+ // Disable the Clear Recent History... menu item when in PB mode
+ // temporary fix until bug 463607 is fixed
+ document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
+
+ if (window.location.href == getBrowserURL()) {
+#ifdef XP_MACOSX
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.documentElement.setAttribute("drawintitlebar", true);
+ }
+#endif
+
+ // Adjust the window's title
+ let docElement = document.documentElement;
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ docElement.setAttribute("title",
+ docElement.getAttribute("title_privatebrowsing"));
+ docElement.setAttribute("titlemodifier",
+ docElement.getAttribute("titlemodifier_privatebrowsing"));
+ }
+ docElement.setAttribute("privatebrowsingmode",
+ PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary");
+ gBrowser.updateTitlebar();
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Adjust the New Window menu entries
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow" },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow" },
+ ].forEach(function(menu) {
+ let newWindow = document.getElementById(menu.normal);
+ let newPrivateWindow = document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ newPrivateWindow.hidden = true;
+ newWindow.label = newPrivateWindow.label;
+ newWindow.accessKey = newPrivateWindow.accessKey;
+ newWindow.command = newPrivateWindow.command;
+ }
+ });
+ }
+ }
+
+ if (gURLBar &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Disable switch to tab autocompletion for private windows
+ // (not for "Always use private browsing" mode)
+ gURLBar.setAttribute("autocompletesearchparam", "");
+ }
+ }
+};
+
+
+/**
+ * Switch to a tab that has a given URI, and focusses its browser window.
+ * If a matching tab is in this window, it will be switched to. Otherwise, other
+ * windows will be searched.
+ *
+ * @param aURI
+ * URI to search for
+ * @param aOpenNew
+ * True to open a new tab and switch to it, if no existing tab is found.
+ * If no suitable window is found, a new one will be opened.
+ * @return True if an existing tab was found, false otherwise
+ */
+function switchToTabHavingURI(aURI, aOpenNew) {
+ // This will switch to the tab in aWindow having aURI, if present.
+ function switchIfURIInWindow(aWindow) {
+ // Only switch to the tab if neither the source and desination window are
+ // private and they are not in permanent private borwsing mode
+ if ((PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return false;
+ }
+
+ let browsers = aWindow.gBrowser.browsers;
+ for (let i = 0; i < browsers.length; i++) {
+ let browser = browsers[i];
+ if (browser.currentURI.equals(aURI)) {
+ // Focus the matching window & tab
+ aWindow.focus();
+ aWindow.gBrowser.tabContainer.selectedIndex = i;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // This can be passed either nsIURI or a string.
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = Services.io.newURI(aURI, null, null);
+
+ let isBrowserWindow = !!window.gBrowser;
+
+ // Prioritise this window.
+ if (isBrowserWindow && switchIfURIInWindow(window))
+ return true;
+
+ let winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let browserWin = winEnum.getNext();
+ // Skip closed (but not yet destroyed) windows,
+ // and the current window (which was checked earlier).
+ if (browserWin.closed || browserWin == window)
+ continue;
+ if (switchIfURIInWindow(browserWin))
+ return true;
+ }
+
+ // No opened tab has that url.
+ if (aOpenNew) {
+ if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab))
+ gBrowser.selectedBrowser.loadURI(aURI.spec);
+ else
+ openUILinkIn(aURI.spec, "tab");
+ }
+
+ return false;
+}
+
+function restoreLastSession() {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ ss.restoreLastSession();
+}
+
+var TabContextMenu = {
+ contextTab: null,
+ updateContextMenu: function updateContextMenu(aPopupMenu) {
+ this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
+ aPopupMenu.triggerNode : gBrowser.selectedTab;
+ let disabled = gBrowser.tabs.length == 1;
+
+ // Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
+ document.getElementById("context_closeTab").disabled =
+ disabled && gBrowser.tabContainer._closeWindowWithLastTab;
+
+ var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ disabled = gBrowser.visibleTabs.length == 1;
+ menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ // Session store
+ document.getElementById("context_undoCloseTab").disabled =
+ Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore).
+ getClosedTabCount(window) == 0;
+
+ // Only one of pin/unpin should be visible
+ document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
+ document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
+
+ // Disable "Close Tabs to the Right" if there are no tabs
+ // following it and hide it when the user rightclicked on a pinned
+ // tab.
+ document.getElementById("context_closeTabsToTheEnd").disabled =
+ gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
+ document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned;
+
+ // Disable "Close other Tabs" if there is only one unpinned tab and
+ // hide it when the user rightclicked on a pinned tab.
+ let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
+ document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1;
+ document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned;
+
+ // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible.
+ let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs");
+ bookmarkAllTabs.hidden = this.contextTab.pinned;
+ if (!bookmarkAllTabs.hidden)
+ PlacesCommandHook.updateBookmarkAllTabsCommand();
+ }
+};
+
+#ifdef MOZ_DEVTOOLS
+XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
+ "resource://gre/modules/devtools/gDevTools.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser",
+ "resource://gre/modules/devtools/gDevTools.jsm");
+
+Object.defineProperty(this, "HUDService", {
+ get: function HUDService_getter() {
+ let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+ return devtools.require("devtools/webconsole/hudservice").HUDService;
+ },
+ configurable: true,
+ enumerable: true
+});
+#endif
+
+// Prompt user to restart the browser in safe mode or normally
+function restart(safeMode)
+{
+ let promptTitleString = null;
+ let promptMessageString = null;
+ let restartTextString = null;
+ if (safeMode) {
+ promptTitleString = "safeModeRestartPromptTitle";
+ promptMessageString = "safeModeRestartPromptMessage";
+ restartTextString = "safeModeRestartButton";
+ } else {
+ promptTitleString = "restartPromptTitle";
+ promptMessageString = "restartPromptMessage";
+ restartTextString = "restartButton";
+ }
+
+ let flags = Ci.nsIAppStartup.eAttemptQuit;
+
+ // Prompt the user to confirm
+ let promptTitle = gNavigatorBundle.getString(promptTitleString);
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let promptMessage =
+ gNavigatorBundle.getFormattedString(promptMessageString, [brandShortName]);
+ let restartText = gNavigatorBundle.getString(restartTextString);
+ let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+ Services.prompt.BUTTON_TITLE_IS_STRING) +
+ (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_CANCEL) +
+ Services.prompt.BUTTON_POS_0_DEFAULT;
+
+ let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage,
+ buttonFlags, restartText, null, null,
+ null, {});
+
+ if (rv == 0) {
+ // Notify all windows that an application quit has been requested.
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data) {
+ return;
+ }
+
+ if (safeMode) {
+ Services.startup.restartInSafeMode(flags);
+ } else {
+ Services.startup.quit(flags | Ci.nsIAppStartup.eRestart);
+ }
+ }
+}
+
+/* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "tab" new tab
+ * "tabshifted" same as "tab" but in background if default is to select new
+ * tabs, and vice versa
+ * "window" new window
+ *
+ * delta is the offset to the history entry that you want to load.
+ */
+function duplicateTabIn(aTab, where, delta) {
+ let newTab = Cc['@mozilla.org/browser/sessionstore;1']
+ .getService(Ci.nsISessionStore)
+ .duplicateTab(window, aTab, delta);
+
+ switch (where) {
+ case "window":
+ gBrowser.hideTab(newTab);
+ gBrowser.replaceTabWithWindow(newTab);
+ break;
+ case "tabshifted":
+ // A background tab has been opened, nothing else to do here.
+ break;
+ case "tab":
+ gBrowser.selectedTab = newTab;
+ break;
+ }
+}
+
+function toggleAddonBar() {
+ let addonBar = document.getElementById("addon-bar");
+ setToolbarVisibility(addonBar, addonBar.collapsed);
+}
+
+#ifdef MOZ_DEVTOOLS
+var Scratchpad = {
+ prefEnabledName: "devtools.scratchpad.enabled",
+
+ openScratchpad: function SP_openScratchpad() {
+ return this.ScratchpadManager.openScratchpad();
+ }
+};
+
+XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/devtools/scratchpad-manager.jsm", tmp);
+ return tmp.ScratchpadManager;
+});
+
+var ResponsiveUI = {
+ toggle: function RUI_toggle() {
+ this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab);
+ }
+};
+
+XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/devtools/responsivedesign.jsm", tmp);
+ return tmp.ResponsiveUIManager;
+});
+
+function openEyedropper() {
+ var eyedropper = new this.Eyedropper(this, { context: "menu",
+ copyOnSelect: true });
+ eyedropper.open();
+}
+
+Object.defineProperty(this, "Eyedropper", {
+ get: function() {
+ let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+ return devtools.require("devtools/eyedropper/eyedropper").Eyedropper;
+ },
+ configurable: true,
+ enumerable: true
+});
+#endif
+
+XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
+#ifdef XP_WIN
+ // Only show resizers on Windows 2000 and XP
+ return parseFloat(Services.sysinfo.getProperty("version")) < 6;
+#else
+ return false;
+#endif
+});
+
+var MousePosTracker = {
+ _listeners: [],
+ _x: 0,
+ _y: 0,
+ get _windowUtils() {
+ delete this._windowUtils;
+ return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ addListener: function (listener) {
+ if (this._listeners.indexOf(listener) >= 0)
+ return;
+
+ listener._hover = false;
+ this._listeners.push(listener);
+
+ this._callListener(listener);
+ },
+
+ removeListener: function (listener) {
+ var index = this._listeners.indexOf(listener);
+ if (index < 0)
+ return;
+
+ this._listeners.splice(index, 1);
+ },
+
+ handleEvent: function (event) {
+ var fullZoom = this._windowUtils.fullZoom;
+ this._x = event.screenX / fullZoom - window.mozInnerScreenX;
+ this._y = event.screenY / fullZoom - window.mozInnerScreenY;
+
+ this._listeners.forEach(function (listener) {
+ try {
+ this._callListener(listener);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }, this);
+ },
+
+ _callListener: function (listener) {
+ let rect = listener.getMouseTargetRect();
+ let hover = this._x >= rect.left &&
+ this._x <= rect.right &&
+ this._y >= rect.top &&
+ this._y <= rect.bottom;
+
+ if (hover == listener._hover)
+ return;
+
+ listener._hover = hover;
+
+ if (hover) {
+ if (listener.onMouseEnter)
+ listener.onMouseEnter();
+ } else {
+ if (listener.onMouseLeave)
+ listener.onMouseLeave();
+ }
+ }
+};
+
+function focusNextFrame(event) {
+ let fm = Services.focus;
+ let dir = event.shiftKey ? fm.MOVEFOCUS_BACKWARDDOC : fm.MOVEFOCUS_FORWARDDOC;
+ let element = fm.moveFocus(window, null, dir, fm.FLAG_BYKEY);
+ if (element.ownerDocument == document)
+ focusAndSelectUrlBar();
+}
+let BrowserChromeTest = {
+ _cb: null,
+ _ready: false,
+ markAsReady: function () {
+ this._ready = true;
+ if (this._cb) {
+ this._cb();
+ this._cb = null;
+ }
+ },
+ runWhenReady: function (cb) {
+ if (this._ready)
+ cb();
+ else
+ this._cb = cb;
+ }
+};
+
+let ToolbarIconColor = {
+ init: function () {
+ this._initialized = true;
+
+ window.addEventListener("activate", this);
+ window.addEventListener("deactivate", this);
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ gPrefService.addObserver("ui.colorChanged", this, false);
+
+ // If the window isn't active now, we assume that it has never been active
+ // before and will soon become active such that inferFromText will be
+ // called from the initial activate event.
+ if (Services.focus.activeWindow == window)
+ this.inferFromText();
+ },
+
+ uninit: function () {
+ this._initialized = false;
+
+ window.removeEventListener("activate", this);
+ window.removeEventListener("deactivate", this);
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ gPrefService.removeObserver("ui.colorChanged", this);
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "activate":
+ case "deactivate":
+ this.inferFromText();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "lightweight-theme-styling-update":
+ // inferFromText needs to run after LightweightThemeConsumer.jsm's
+ // lightweight-theme-styling-update observer.
+ setTimeout(() => { this.inferFromText(); }, 0);
+ break;
+ case "nsPref:changed":
+ // system color change
+ var colorChangedPref = false;
+ try {
+ colorChangedPref = gPrefService.getBoolPref("ui.colorChanged");
+ } catch(e) { }
+ // if pref indicates change, call inferFromText() on a small delay
+ if (colorChangedPref == true)
+ setTimeout(() => { this.inferFromText(); }, 300);
+ break;
+ default:
+ console.error("ToolbarIconColor: Uncaught topic " + aTopic);
+ }
+ },
+
+ inferFromText: function () {
+ if (!this._initialized)
+ return;
+
+ function parseRGB(aColorString) {
+ let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
+ rgb.shift();
+ return rgb.map(x => parseInt(x));
+ }
+
+ let toolbarSelector = "toolbar:not([collapsed=true])";
+#ifdef XP_MACOSX
+ toolbarSelector += ":not([type=menubar])";
+#endif
+
+ // The getComputedStyle calls and setting the brighttext are separated in
+ // two loops to avoid flushing layout and making it dirty repeatedly.
+
+ let luminances = new Map;
+ for (let toolbar of document.querySelectorAll(toolbarSelector)) {
+ let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
+ let luminance = (2 * r + 5 * g + b) / 8;
+ luminances.set(toolbar, luminance);
+ }
+
+ for (let [toolbar, luminance] of luminances) {
+ if (luminance <= 128)
+ toolbar.removeAttribute("brighttext");
+ else
+ toolbar.setAttribute("brighttext", "true");
+ }
+
+ // Clear pref if set, since we're done applying the color changes.
+ gPrefService.clearUserPref("ui.colorChanged");
+ }
+}
diff --git a/application/palemoon/base/content/browser.xul b/application/palemoon/base/content/browser.xul
new file mode 100644
index 000000000..f83010023
--- /dev/null
+++ b/application/palemoon/base/content/browser.xul
@@ -0,0 +1,1058 @@
+#filter substitution
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
+
+# Restore title to AppMenu windowed use
+<?xml-stylesheet href="chrome://browser/content/browser-title.css" type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
+#ifdef MOZ_DEVTOOLS
+<?xml-stylesheet href="chrome://global/skin/devtools/common.css" type="text/css"?>
+#endif
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+# Padlock feature
+<?xul-overlay href="chrome://browser/content/padlock.xul"?>
+# Improve bookmark menu dragging
+<?xul-overlay href="chrome://browser/content/browser-menudragging.xul"?>
+# Automatic browser recovery
+<?xul-overlay href="chrome://browser/content/autorecovery.xul"?>
+
+
+# All DTD information is stored in a separate file so that it can be shared by
+# hiddenWindow.xul.
+#include browser-doctype.inc
+
+<window id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="gBrowserInit.onLoad()" onunload="gBrowserInit.onUnload()" onclose="return WindowIsClosing();"
+ title="&mainWindow.title;"
+ title_normal="&mainWindow.title;"
+#ifdef XP_MACOSX
+ title_privatebrowsing="&mainWindow.title;&mainWindow.titlemodifiermenuseparator;&mainWindow.titlePrivateBrowsingSuffix;"
+ titledefault="&mainWindow.title;"
+ titlemodifier=""
+ titlemodifier_normal=""
+ titlemodifier_privatebrowsing="&mainWindow.titlePrivateBrowsingSuffix;"
+#else
+ title_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
+ titlemodifier="&mainWindow.titlemodifier;"
+ titlemodifier_normal="&mainWindow.titlemodifier;"
+ titlemodifier_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
+#endif
+ titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
+ lightweightthemes="true"
+ lightweightthemesfooter="browser-bottombox"
+ windowtype="navigator:browser"
+ macanimationtype="document"
+ screenX="4" screenY="4"
+ fullscreenbutton="true"
+ persist="screenX screenY width height sizemode">
+
+# All JS files which are not content (only) dependent that browser.xul
+# wishes to include *must* go into the global-scripts.inc file
+# so that they can be shared by macBrowserOverlay.xul.
+#include global-scripts.inc
+<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
+
+<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+<script type="application/javascript" src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
+# browser-sets.inc file for sharing with hiddenWindow.xul.
+#define FULL_BROWSER_WINDOW
+#include browser-sets.inc
+#undef FULL_BROWSER_WINDOW
+
+ <popupset id="mainPopupSet">
+ <menupopup id="tabContextMenu"
+ onpopupshowing="if (event.target == this) TabContextMenu.updateContextMenu(this);"
+ onpopuphidden="if (event.target == this) TabContextMenu.contextTab = null;">
+ <menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
+ oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
+ <menuseparator/>
+ <menuitem id="context_pinTab" label="&pinTab.label;"
+ accesskey="&pinTab.accesskey;"
+ oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/>
+ <menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true"
+ accesskey="&unpinTab.accesskey;"
+ oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/>
+ <menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
+ accesskey="&moveToNewWindow.accesskey;"
+ tbattr="tabbrowser-multiple"
+ oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/>
+ <menuseparator/>
+ <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
+ tbattr="tabbrowser-multiple-visible"
+ oncommand="gBrowser.reloadAllTabs();"/>
+ <menuitem id="context_bookmarkAllTabs"
+ label="&bookmarkAllTabs.label;"
+ accesskey="&bookmarkAllTabs.accesskey;"
+ command="Browser:BookmarkAllTabs"/>
+ <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
+ oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab);"/>
+ <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
+ oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
+ <menuseparator/>
+ <menuitem id="context_undoCloseTab"
+ label="&undoCloseTab.label;"
+ accesskey="&undoCloseTab.accesskey;"
+ observes="History:UndoCloseTab"/>
+ <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
+ oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>
+ </menupopup>
+
+ <!-- bug 415444/582485: event.stopPropagation is here for the cloned version
+ of this menupopup -->
+ <menupopup id="backForwardMenu"
+ onpopupshowing="return FillHistoryMenu(event.target);"
+ oncommand="gotoHistoryIndex(event); event.stopPropagation();"
+ onclick="checkForMiddleClick(this, event);"/>
+ <tooltip id="aHTMLTooltip" page="true"/>
+
+ <!-- for search and content formfill/pw manager -->
+ <panel type="autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
+
+ <!-- for url bar autocomplete -->
+ <panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
+
+ <!-- for invalid form error message -->
+ <panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
+ <description/>
+ </panel>
+
+ <panel id="editBookmarkPanel"
+ type="arrow"
+ footertype="promobox"
+ orient="vertical"
+ ignorekeys="true"
+ consumeoutsideclicks="true"
+ hidden="true"
+ onpopupshown="StarUI.panelShown(event);"
+ aria-labelledby="editBookmarkPanelTitle">
+ <row id="editBookmarkPanelHeader" align="center" hidden="true">
+ <vbox align="center">
+ <image id="editBookmarkPanelStarIcon"/>
+ </vbox>
+ <vbox>
+ <label id="editBookmarkPanelTitle"/>
+ <description id="editBookmarkPanelDescription"/>
+ <hbox>
+ <button id="editBookmarkPanelRemoveButton"
+ class="editBookmarkPanelHeaderButton"
+ oncommand="StarUI.removeBookmarkButtonCommand();"
+ accesskey="&editBookmark.removeBookmark.accessKey;"/>
+ </hbox>
+ </vbox>
+ </row>
+ <vbox id="editBookmarkPanelContent" flex="1" hidden="true"/>
+ <hbox id="editBookmarkPanelBottomButtons" pack="end">
+#ifndef XP_UNIX
+ <button id="editBookmarkPanelDoneButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.done.label;"
+ default="true"
+ oncommand="StarUI.panel.hidePopup();"/>
+ <button id="editBookmarkPanelDeleteButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.cancel.label;"
+ oncommand="StarUI.cancelButtonOnCommand();"/>
+#else
+ <button id="editBookmarkPanelDeleteButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.cancel.label;"
+ oncommand="StarUI.cancelButtonOnCommand();"/>
+ <button id="editBookmarkPanelDoneButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.done.label;"
+ default="true"
+ oncommand="StarUI.panel.hidePopup();"/>
+#endif
+ </hbox>
+ </panel>
+
+ <menupopup id="toolbar-context-menu"
+ onpopupshowing="onViewToolbarsPopupShowing(event);">
+ <menuseparator/>
+ <menuitem command="cmd_ToggleTabsOnTop"
+ type="checkbox"
+ label="&viewTabsOnTop.label;"
+ accesskey="&viewTabsOnTop.accesskey;"/>
+ <menuitem command="cmd_CustomizeToolbars"
+ label="&viewCustomizeToolbar.label;"
+ accesskey="&viewCustomizeToolbar.accesskey;"/>
+ </menupopup>
+
+ <menupopup id="blockedPopupOptions"
+ onpopupshowing="gPopupBlockerObserver.fillPopupList(event);"
+ onpopuphiding="gPopupBlockerObserver.onPopupHiding(event);">
+ <menuitem observes="blockedPopupAllowSite"/>
+ <menuitem observes="blockedPopupEditSettings"/>
+ <menuitem observes="blockedPopupDontShowMessage"/>
+ <menuseparator observes="blockedPopupsSeparator"/>
+ </menupopup>
+
+ <menupopup id="autohide-context"
+ onpopupshowing="FullScreen.getAutohide(this.firstChild);">
+ <menuitem type="checkbox" label="&fullScreenAutohide.label;"
+ accesskey="&fullScreenAutohide.accesskey;"
+ oncommand="FullScreen.setAutohide();"/>
+ <menuseparator/>
+ <menuitem label="&fullScreenExit.label;"
+ accesskey="&fullScreenExit.accesskey;"
+ oncommand="BrowserFullScreen();"/>
+ </menupopup>
+
+ <menupopup id="contentAreaContextMenu" pagemenu="start"
+ onpopupshowing="if (event.target != this)
+ return true;
+ gContextMenu = new nsContextMenu(this, event.shiftKey);
+ if (gContextMenu.shouldDisplay)
+ updateEditUIVisibility();
+ return gContextMenu.shouldDisplay;"
+ onpopuphiding="if (event.target != this)
+ return;
+ gContextMenu.hiding();
+ gContextMenu = null;
+ updateEditUIVisibility();">
+#include browser-context.inc
+ </menupopup>
+
+ <menupopup id="placesContext"/>
+
+
+ <panel id="ctrlTab-panel" class="KUI-panel" hidden="true" norestorefocus="true" level="top">
+ <hbox>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ </hbox>
+ <hbox pack="center">
+ <button id="ctrlTab-showAll" class="ctrlTab-preview" noicon="true"/>
+ </hbox>
+ </panel>
+
+ <panel id="allTabs-panel" hidden="true" norestorefocus="true" ignorekeys="true"
+ onmouseover="allTabs._updateTabCloseButton(event);">
+ <hbox id="allTabs-meta" align="center">
+ <spacer flex="1"/>
+ <textbox id="allTabs-filter"
+ tooltiptext="&allTabs.filter.emptyText;"
+ type="search"
+ oncommand="allTabs.filter();"/>
+ <spacer flex="1"/>
+ <toolbarbutton class="KUI-panel-closebutton"
+ oncommand="allTabs.close()"
+ tooltiptext="&closeCmd.label;"/>
+ </hbox>
+ <stack id="allTabs-stack">
+ <vbox id="allTabs-container"><hbox/></vbox>
+ <toolbarbutton id="allTabs-tab-close-button"
+ class="tabs-closebutton close-icon"
+ oncommand="allTabs.closeTab(event);"
+ tooltiptext="&closeCmd.label;"
+ style="visibility:hidden"/>
+ </stack>
+ </panel>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <panel id="customizeToolbarSheetPopup"
+ noautohide="true"
+ sheetstyle="&dialog.dimensions;"/>
+
+ <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
+
+ <tooltip id="back-button-tooltip">
+ <label class="tooltip-label" value="&backButton.tooltip;"/>
+#ifdef XP_MACOSX
+ <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
+#else
+ <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
+#endif
+ </tooltip>
+
+ <tooltip id="forward-button-tooltip">
+ <label class="tooltip-label" value="&forwardButton.tooltip;"/>
+#ifdef XP_MACOSX
+ <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
+#else
+ <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
+#endif
+ </tooltip>
+
+#include popup-notifications.inc
+
+ </popupset>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+<vbox id="titlebar">
+ <hbox id="titlebar-content">
+#ifdef MENUBAR_CAN_AUTOHIDE
+ <hbox id="appmenu-button-container">
+ <button id="appmenu-button"
+ type="menu"
+ label="&brandShortName;"
+ tooltiptext="&appMenuButton.tooltip;"
+ style="-moz-user-focus: ignore;">
+#include browser-appmenu.inc
+ </button>
+ </hbox>
+#endif
+ <spacer id="titlebar-spacer" flex="1"/>
+ <hbox id="titlebar-buttonbox-container" align="start">
+ <hbox id="titlebar-buttonbox">
+ <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
+ <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
+ <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
+ </hbox>
+ </hbox>
+ </hbox>
+</vbox>
+#endif
+
+<deck flex="1" id="tab-view-deck">
+<vbox flex="1" id="browser-panel">
+
+ <toolbox id="navigator-toolbox"
+ defaultmode="icons" mode="icons"
+ iconsize="large">
+ <!-- Menu -->
+ <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
+ defaultset="menubar-items"
+ mode="icons" iconsize="small" defaulticonsize="small"
+ lockiconsize="true"
+#ifdef MENUBAR_CAN_AUTOHIDE
+ toolbarname="&menubarCmd.label;"
+ accesskey="&menubarCmd.accesskey;"
+#endif
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items" align="center">
+# The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
+# hiddenWindow.xul.
+#include browser-menubar.inc
+ </toolbaritem>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ <hbox class="titlebar-placeholder" type="appmenu-button" ordinal="0"/>
+ <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/>
+#endif
+ </toolbar>
+
+ <toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar"
+ toolbarname="&navbarCmd.label;" accesskey="&navbarCmd.accesskey;"
+ fullscreentoolbar="true" mode="icons" customizable="true"
+ iconsize="large"
+ defaultset="unified-back-forward-button,reload-button,stop-button,home-button,urlbar-container,search-container,bookmarks-menu-button,history-menu-button,downloads-button,window-controls"
+ context="toolbar-context-menu">
+
+ <toolbaritem id="unified-back-forward-button" class="chromeclass-toolbar-additional"
+ context="backForwardMenu" removable="true"
+ forwarddisabled="true"
+ title="&backForwardItem.title;">
+ <toolbarbutton id="back-button" class="toolbarbutton-1"
+ label="&backCmd.label;"
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltip="back-button-tooltip"/>
+ <toolbarbutton id="forward-button" class="toolbarbutton-1"
+ label="&forwardCmd.label;"
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltip="forward-button-tooltip"/>
+ <dummyobservertarget hidden="true"
+ onbroadcast="if (this.getAttribute('disabled') == 'true')
+ this.parentNode.setAttribute('forwarddisabled', 'true');
+ else
+ this.parentNode.removeAttribute('forwarddisabled');">
+ <observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/>
+ </dummyobservertarget>
+ </toolbaritem>
+
+ <toolbarbutton id="reload-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&reloadCmd.label;" removable="true"
+ command="Browser:ReloadOrDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&reloadButton.tooltip;"/>
+
+ <toolbarbutton id="stop-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&stopCmd.label;" removable="true"
+ command="Browser:Stop"
+ tooltiptext="&stopButton.tooltip;"/>
+
+ <toolbarbutton id="home-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ persist="class" removable="true"
+ label="&homeButton.label;"
+ ondragover="homeButtonObserver.onDragOver(event)"
+ ondragenter="homeButtonObserver.onDragOver(event)"
+ ondrop="homeButtonObserver.onDrop(event)"
+ ondragexit="homeButtonObserver.onDragExit(event)"
+ onclick="BrowserGoHome(event);"
+ aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
+
+ <toolbaritem id="urlbar-container" align="center" flex="400" persist="width" combined="true"
+ title="&locationItem.title;" class="chromeclass-location" removable="true">
+ <textbox id="urlbar" flex="1"
+ placeholder="&urlbar.placeholder2;"
+ type="autocomplete"
+ autocompletesearch="urlinline history"
+ autocompletesearchparam="enable-actions"
+ autocompletepopup="PopupAutoCompleteRichResult"
+ completeselectedindex="true"
+ tabscrolling="true"
+ showcommentcolumn="true"
+ showimagecolumn="true"
+ enablehistory="true"
+ maxrows="6"
+ newlines="stripsurroundingwhitespace"
+ oninput="gBrowser.userTypedValue = this.value;"
+ ontextentered="this.handleCommand(param);"
+ ontextreverted="return this.handleRevert();"
+ pageproxystate="invalid"
+ onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
+ onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);">
+ <box id="notification-popup-box" hidden="true" align="center">
+ <image id="default-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="alert-plugins-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="blocked-plugins-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/>
+ </box>
+ <!-- Use onclick instead of normal popup= syntax since the popup
+ code fires onmousedown, and hence eats our favicon drag events.
+ We only add the identity-box button to the tab order when the location bar
+ has focus, otherwise pressing F6 focuses it instead of the location bar -->
+ <box id="identity-box" role="button"
+ align="center"
+ onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
+ onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
+ ondragstart="gIdentityHandler.onDragStart(event);">
+ <image id="page-proxy-favicon"
+ onclick="PageProxyClickHandler(event);"
+ pageproxystate="invalid"/>
+ <hbox id="identity-icon-labels">
+ <label id="identity-icon-label" class="plain" flex="1"/>
+ <label id="identity-icon-country-label" class="plain"/>
+ </hbox>
+ </box>
+ <box id="urlbar-display-box" align="center">
+ <label id="urlbar-display" value="&urlbar.switchToTab.label;"/>
+ </box>
+ <hbox id="urlbar-icons">
+ <image id="page-report-button"
+ class="urlbar-icon"
+ hidden="true"
+ tooltiptext="&pageReportIcon.tooltip;"
+ onclick="gPopupBlockerObserver.onReportButtonClick(event);"/>
+ <button type="menu"
+ style="-moz-user-focus: none"
+ class="plain urlbar-icon"
+ id="ub-feed-button"
+ collapsed="true"
+ tooltiptext="&feedButton.tooltip;"
+ onclick="return FeedHandler.onFeedButtonPMClick(event);">
+ <menupopup position="after_end"
+ id="ub-feed-menu"
+ onpopupshowing="return FeedHandler.buildFeedList(this);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </button>
+ <image id="star-button"
+ class="urlbar-icon"
+ onclick="BookmarkingUI.onCommand(event);"/>
+ <image id="go-button"
+ class="urlbar-icon"
+ tooltiptext="&goEndCap.tooltip;"
+ onclick="gURLBar.handleCommand(event);"/>
+ </hbox>
+ <toolbarbutton id="urlbar-go-button"
+ class="chromeclass-toolbar-additional"
+ onclick="gURLBar.handleCommand(event);"
+ tooltiptext="&goEndCap.tooltip;"/>
+ <toolbarbutton id="urlbar-reload-button"
+ class="chromeclass-toolbar-additional"
+ command="Browser:ReloadOrDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&reloadButton.tooltip;"/>
+ <toolbarbutton id="urlbar-stop-button"
+ class="chromeclass-toolbar-additional"
+ command="Browser:Stop"
+ tooltiptext="&stopButton.tooltip;"/>
+ </textbox>
+ </toolbaritem>
+
+ <toolbaritem id="search-container" title="&searchItem.title;"
+ align="center" class="chromeclass-toolbar-additional"
+ flex="100" persist="width" removable="true">
+ <searchbar id="searchbar" flex="1"/>
+ </toolbaritem>
+
+ <toolbarbutton id="webrtc-status-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ type="menu"
+ hidden="true"
+ orient="horizontal"
+ label="&webrtcIndicatorButton.label;"
+ tooltiptext="&webrtcIndicatorButton.tooltip;">
+ <menupopup onpopupshowing="WebrtcIndicator.fillPopup(this);"
+ onpopuphiding="WebrtcIndicator.clearPopup(this);"
+ oncommand="WebrtcIndicator.menuCommand(event.target);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="bookmarks-menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ persist="class"
+ removable="true"
+ type="menu"
+ label="&bookmarksMenuButton.label;"
+ tooltiptext="&bookmarksMenuButton.tooltip;"
+ onclick="if (event.button == 1)
+ toggleSidebar('viewBookmarksSidebar');"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondragleave="PlacesMenuDNDHandler.onDragLeave(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);">
+ <menupopup id="BMB_bookmarksPopup"
+ placespopup="true"
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="event.stopPropagation();
+ BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarkingUI.onPopupShowing(event);
+ if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem id="BMB_viewBookmarksToolbar"
+ placesanonid="view-toolbar"
+ toolbarId="PersonalToolbar"
+ type="checkbox"
+ oncommand="onViewToolbarCommand(event)"
+ label="&viewBookmarksToolbar.label;"/>
+ <menuseparator/>
+ <menuitem id="BMB_bookmarksShowAll"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&organizeBookmarks.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ <menuseparator/>
+ <menuitem id="BMB_bookmarkThisPage"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&bookmarkThisPageCmd.label;"
+ command="Browser:AddBookmarkAs"
+ key="addBookmarkAsKb"/>
+ <menuitem id="BMB_subscribeToPageMenuitem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&subscribeToPageMenuitem.label;"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="singleFeedMenuitemState"/>
+ <menu id="BMB_subscribeToPageMenupopup"
+#ifndef XP_MACOSX
+ class="menu-iconic"
+#endif
+ label="&subscribeToPageMenupopup.label;"
+ observes="multipleFeedsMenuState">
+ <menupopup id="BMB_subscribeToPageSubmenuMenupopup"
+ onpopupshowing="return FeedHandler.buildFeedList(event.target);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuseparator/>
+ <menu id="BMB_bookmarksToolbar"
+ placesanonid="toolbar-autohide"
+ class="menu-iconic bookmark-item"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="BMB_bookmarksToolbarPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menuseparator/>
+ <!-- Bookmarks menu items -->
+ <menuseparator builder="end"
+ class="hide-if-empty-places-result"/>
+ <menuitem id="BMB_unsortedBookmarks"
+ class="menuitem-iconic"
+ label="&bookmarksMenuButton.unsorted.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="history-menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ persist="class"
+ removable="true"
+ type="menu"
+ label="&historyButton.label;"
+ tooltiptext="&historyButton.tooltip;"
+ onclick="if (event.button == 1)
+ toggleSidebar('viewHistorySidebar');">
+ <menupopup id="HMB_historyPopup"
+ placespopup="true"
+ context="placesContext"
+ oncommand="this.parentNode._placesView._onCommand(event);"
+ onclick="event.stopPropagation();
+ checkForMiddleClick(this, event);"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new HistoryMenu(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="HMB_showAllHistory"
+ label="&showAllHistoryCmd2.label;"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+ key="showAllHistoryKb"
+#endif
+ command="Browser:ShowAllHistory"/>
+ <menuitem id="HMB_sanitizeItem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&clearRecentHistory.label;"
+ key="key_sanitize"
+ command="Tools:Sanitize"/>
+ <menuseparator id="HMB_sanitizeSeparator"/>
+#ifdef MOZ_SERVICES_SYNC
+ <menuitem id="HMB_sync-tabs-menuitem"
+ class="syncTabsMenuItem"
+ label="&syncTabsMenu2.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ disabled="true"/>
+#endif
+ <menuitem id="HMB_historyRestoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ command="Browser:RestoreLastSession"/>
+ <menu id="HMB_historyUndoMenu"
+ class="recentlyClosedTabsMenu"
+ label="&historyUndoMenu.label;"
+ disabled="true">
+ <menupopup id="HMB_historyUndoPopup"
+ placespopup="true"
+ onpopupshowing="document.getElementById('history-menu-button')._placesView.populateUndoSubmenu();"/>
+ </menu>
+ <menu id="HMB_historyUndoWindowMenu"
+ class="recentlyClosedWindowsMenu"
+ label="&historyUndoWindowMenu.label;"
+ disabled="true">
+ <menupopup id="HMB_historyUndoWindowPopup"
+ placespopup="true"
+ onpopupshowing="document.getElementById('history-menu-button')._placesView.populateUndoWindowSubmenu();"/>
+ </menu>
+ <menuseparator id="HMB_startHistorySeparator"
+ class="hide-if-empty-places-result"/>
+ <!-- History menu items -->
+ </menupopup>
+ </toolbarbutton>
+
+ <hbox id="window-controls" hidden="true" pack="end">
+ <toolbarbutton id="minimize-button"
+ tooltiptext="&fullScreenMinimize.tooltip;"
+ oncommand="window.minimize();"/>
+
+ <toolbarbutton id="restore-button"
+ tooltiptext="&fullScreenRestore.tooltip;"
+ oncommand="BrowserFullScreen();"/>
+
+ <toolbarbutton id="close-button"
+ tooltiptext="&fullScreenClose.tooltip;"
+ oncommand="BrowserTryToCloseWindow();"/>
+ </hbox>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbar id="PersonalToolbar"
+ mode="icons" iconsize="small" defaulticonsize="small"
+ lockiconsize="true"
+ class="chromeclass-directories"
+ context="toolbar-context-menu"
+ defaultset="personal-bookmarks"
+ toolbarname="&personalbarCmd.label;" accesskey="&personalbarCmd.accesskey;"
+ collapsed="false"
+ customizable="true">
+ <toolbaritem flex="1" id="personal-bookmarks" title="&bookmarksItem.title;"
+ removable="true">
+ <hbox flex="1"
+ id="PlacesToolbar"
+ context="placesContext"
+ onclick="BookmarksEventHandler.onClick(event, this._placesView);"
+ oncommand="BookmarksEventHandler.onCommand(event, this._placesView);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <toolbarbutton class="bookmark-item bookmarks-toolbar-customize"
+ mousethrough="never"
+ label="&bookmarksToolbarItem.label;"/>
+ <hbox flex="1">
+ <hbox align="center">
+ <image id="PlacesToolbarDropIndicator"
+ mousethrough="always"
+ collapsed="true"/>
+ </hbox>
+ <scrollbox orient="horizontal"
+ id="PlacesToolbarItems"
+ flex="1"/>
+ <toolbarbutton type="menu"
+ id="PlacesChevron"
+ class="chevron"
+ mousethrough="never"
+ collapsed="true"
+ tooltiptext="&bookmarksToolbarChevron.tooltip;"
+ onpopupshowing="document.getElementById('PlacesToolbar')
+ ._placesView._onChevronPopupShowing(event);">
+ <menupopup id="PlacesChevronPopup"
+ placespopup="true"
+ tooltip="bhTooltip" popupsinherittooltip="true"
+ context="placesContext"/>
+ </toolbarbutton>
+ </hbox>
+ </hbox>
+ </toolbaritem>
+ </toolbar>
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+#ifndef CAN_DRAW_IN_TITLEBAR
+#define APPMENU_ON_TABBAR
+#endif
+#endif
+
+
+ <toolbar id="TabsToolbar"
+ class="toolbar-primary"
+ fullscreentoolbar="true"
+ customizable="true"
+ mode="icons" lockmode="true"
+ iconsize="small" defaulticonsize="small" lockiconsize="true"
+ aria-label="&tabsToolbar.label;"
+ context="toolbar-context-menu"
+#ifdef APPMENU_ON_TABBAR
+ defaultset="appmenu-toolbar-button,tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton"
+#else
+ defaultset="tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton"
+#endif
+ collapsed="true">
+
+#ifdef APPMENU_ON_TABBAR
+ <toolbarbutton id="appmenu-toolbar-button"
+ class="chromeclass-toolbar-additional"
+ type="menu"
+ label="&brandShortName;"
+ tooltiptext="&appMenuButton.tooltip;">
+#include browser-appmenu.inc
+ </toolbarbutton>
+#endif
+
+ <tabs id="tabbrowser-tabs"
+ class="tabbrowser-tabs"
+ tabbrowser="content"
+ flex="1"
+ setfocus="false"
+ tooltip="tabbrowser-tab-tooltip">
+ <tab class="tabbrowser-tab" selected="true" fadein="true"/>
+ </tabs>
+
+ <toolbarbutton id="new-tab-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&newTabButton.tooltip;"
+ ondrop="newTabButtonObserver.onDrop(event)"
+ ondragover="newTabButtonObserver.onDragOver(event)"
+ ondragenter="newTabButtonObserver.onDragOver(event)"
+ ondragexit="newTabButtonObserver.onDragExit(event)"
+ removable="true"/>
+
+ <toolbarbutton id="alltabs-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button"
+ type="menu"
+ label="&listAllTabs.label;"
+ tooltiptext="&listAllTabs.label;"
+ removable="true">
+ <menupopup id="alltabs-popup" position="after_end"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="tabs-closebutton"
+ class="close-button tabs-closebutton close-icon"
+ command="cmd_close"
+ label="&closeTab.label;"
+ tooltiptext="&closeTab.label;"/>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ <hbox class="titlebar-placeholder" type="appmenu-button" ordinal="0"/>
+ <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/>
+#endif
+ </toolbar>
+
+ <toolbarpalette id="BrowserToolbarPalette">
+
+# Update primaryToolbarButtons in browser/themes/shared/browser.inc when adding
+# or removing default items with the toolbarbutton-1 class.
+
+ <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&printButton.label;" command="cmd_print"
+ tooltiptext="&printButton.tooltip;"/>
+
+ <!-- This is a placeholder for the Downloads Indicator. It is visible
+ during the customization of the toolbar, in the palette, and before
+ the Downloads Indicator overlay is loaded. -->
+ <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ oncommand="DownloadsIndicatorView.onCommand(event);"
+ ondrop="DownloadsIndicatorView.onDrop(event);"
+ ondragover="DownloadsIndicatorView.onDragOver(event);"
+ ondragenter="DownloadsIndicatorView.onDragOver(event);"
+ label="&downloads.label;"
+ tooltiptext="&downloads.tooltip;"/>
+
+ <toolbarbutton id="history-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ observes="viewHistorySidebar" label="&historyButton.label;"
+ tooltiptext="&historyButton.tooltip;"/>
+
+ <toolbarbutton id="bookmarks-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ observes="viewBookmarksSidebar" label="&bookmarksButton.label;"
+ tooltiptext="&bookmarksButton.tooltip;"
+ ondrop="bookmarksButtonObserver.onDrop(event)"
+ ondragover="bookmarksButtonObserver.onDragOver(event)"
+ ondragenter="bookmarksButtonObserver.onDragOver(event)"
+ ondragexit="bookmarksButtonObserver.onDragExit(event)"/>
+
+ <toolbarbutton id="new-window-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&newNavigatorCmd.label;"
+ command="key_newNavigator"
+ tooltiptext="&newWindowButton.tooltip;"
+ ondrop="newWindowButtonObserver.onDrop(event)"
+ ondragover="newWindowButtonObserver.onDragOver(event)"
+ ondragenter="newWindowButtonObserver.onDragOver(event)"
+ ondragexit="newWindowButtonObserver.onDragExit(event)"/>
+
+ <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ observes="View:FullScreen"
+ type="checkbox"
+ label="&fullScreenCmd.label;"
+ tooltiptext="&fullScreenButton.tooltip;"/>
+
+ <toolbaritem id="zoom-controls" class="chromeclass-toolbar-additional"
+ title="&zoomControls.label;">
+ <toolbarbutton id="zoom-out-button" class="toolbarbutton-1"
+ label="&fullZoomReduceCmd.label;"
+ command="cmd_fullZoomReduce"
+ tooltiptext="&zoomOutButton.tooltip;"/>
+ <toolbarbutton id="zoom-in-button" class="toolbarbutton-1"
+ label="&fullZoomEnlargeCmd.label;"
+ command="cmd_fullZoomEnlarge"
+ tooltiptext="&zoomInButton.tooltip;"/>
+ </toolbaritem>
+
+ <toolbarbutton id="feed-button"
+ type="menu"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ disabled="true"
+ label="&feedButton.label;"
+ tooltiptext="&feedButton.tooltip;"
+ onclick="return FeedHandler.onFeedButtonClick(event);">
+ <menupopup position="after_end"
+ id="feed-menu"
+ onpopupshowing="return FeedHandler.buildFeedList(this);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="cut-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&cutCmd.label;"
+ command="cmd_cut"
+ tooltiptext="&cutButton.tooltip;"/>
+
+ <toolbarbutton id="copy-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&copyCmd.label;"
+ command="cmd_copy"
+ tooltiptext="&copyButton.tooltip;"/>
+
+ <toolbarbutton id="paste-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&pasteCmd.label;"
+ command="cmd_paste"
+ tooltiptext="&pasteButton.tooltip;"/>
+
+#ifdef MOZ_SERVICES_SYNC
+ <toolbarbutton id="sync-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&syncToolbarButton.label;"
+ oncommand="gSyncUI.handleToolbarButton()"/>
+#endif
+
+ <toolbaritem id="navigator-throbber" title="&throbberItem.title;" align="center" pack="center"
+ mousethrough="always">
+ <image/>
+ </toolbaritem>
+ </toolbarpalette>
+ </toolbox>
+
+ <hbox id="fullscr-toggler" collapsed="true"/>
+
+ <hbox flex="1" id="browser">
+ <vbox id="browser-border-start" hidden="true" layer="true"/>
+ <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
+ <sidebarheader id="sidebar-header" align="center">
+ <label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
+ <image id="sidebar-throbber"/>
+ <toolbarbutton class="tabs-closebutton close-icon" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="toggleSidebar();"/>
+ </sidebarheader>
+ <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true"
+ style="min-width: 14em; width: 18em; max-width: 36em;"/>
+ </vbox>
+
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
+ <vbox id="appcontent" flex="1">
+ <tabbrowser id="content" disablehistory="true"
+ flex="1" contenttooltip="aHTMLTooltip"
+ tabcontainer="tabbrowser-tabs"
+ contentcontextmenu="contentAreaContextMenu"
+ autocompletepopup="PopupAutoComplete"/>
+ <chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
+ <statuspanel id="statusbar-display" inactive="true"/>
+ </vbox>
+ <vbox id="browser-border-end" hidden="true" layer="true"/>
+ </hbox>
+
+ <hbox id="full-screen-warning-container" hidden="true" fadeout="true">
+ <hbox style="width: 100%;" pack="center"> <!-- Inner hbox needed due to bug 579776. -->
+ <vbox id="full-screen-warning-message" align="center">
+ <description id="full-screen-domain-text"/>
+ <description class="full-screen-description" value="&fullscreenExitHint.value;"/>
+ <vbox id="full-screen-approval-pane" align="center">
+ <description class="full-screen-description" value="&fullscreenApproval.value;"/>
+ <hbox>
+ <button label="&fullscreenAllowButton.label;"
+ oncommand="FullScreen.setFullscreenAllowed(true);"
+ class="full-screen-approval-button"/>
+ <button label="&fullscreenExitButton.label;"
+ oncommand="FullScreen.setFullscreenAllowed(false);"
+ class="full-screen-approval-button"/>
+ </hbox>
+ <checkbox id="full-screen-remember-decision"/>
+ </vbox>
+ </vbox>
+ </hbox>
+ </hbox>
+
+ <vbox id="browser-bottombox" layer="true">
+ <notificationbox id="global-notificationbox"/>
+#ifdef MOZ_DEVTOOLS
+ <toolbar id="developer-toolbar"
+ class="devtools-toolbar"
+ hidden="true">
+#ifdef XP_MACOSX
+ <toolbarbutton id="developer-toolbar-closebutton"
+ class="devtools-closebutton"
+ oncommand="DeveloperToolbar.hide();"
+ tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
+#endif
+ <stack class="gclitoolbar-stack-node" flex="1">
+ <textbox class="gclitoolbar-input-node" rows="1"/>
+ <hbox class="gclitoolbar-complete-node"/>
+ </stack>
+ <toolbarbutton id="developer-toolbar-toolbox-button"
+ class="developer-toolbar-button"
+ observes="devtoolsMenuBroadcaster_DevToolbox"
+ tooltiptext="&devToolbarToolsButton.tooltip;"/>
+#ifndef XP_MACOSX
+ <toolbarbutton id="developer-toolbar-closebutton"
+ class="devtools-closebutton"
+ oncommand="DeveloperToolbar.hide();"
+ tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
+#endif
+ </toolbar>
+#endif
+
+ <toolbar id="addon-bar"
+ toolbarname="&statusBar.label;" accesskey="&statusBar.accesskey;"
+ collapsed="true"
+ class="toolbar-primary chromeclass-toolbar"
+ context="toolbar-context-menu" toolboxid="navigator-toolbox"
+ mode="icons" iconsize="small" defaulticonsize="small"
+ lockiconsize="true"
+ defaultset="addonbar-closebutton,spring,status-bar"
+ customizable="true"
+ key="key_toggleAddonBar">
+ <toolbarbutton id="addonbar-closebutton"
+ class="close-icon"
+ tooltiptext="&addonBarCloseButton.tooltip;"
+ oncommand="setToolbarVisibility(this.parentNode, false);"/>
+ <statusbar id="status-bar" ordinal="1000"/>
+ </toolbar>
+ </vbox>
+
+#ifndef XP_UNIX
+ <svg:svg height="0">
+ <svg:clipPath id="windows-keyhole-forward-clip-path" clipPathUnits="objectBoundingBox">
+ <svg:path d="M 0,0 C 0.16,0.11 0.28,0.29 0.28,0.5 0.28,0.71 0.16,0.89 0,1 L 1,1 1,0 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="windows-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="M 0,0 0,7.8 C 2.5,11 4,14 4,18 4,22 2.5,25 0,28 l 0,22 10000,0 0,-50 L 0,0 z"/>
+ </svg:clipPath>
+ </svg:svg>
+#endif
+#ifdef XP_MACOSX
+ <svg:svg height="0">
+ <svg:clipPath id="osx-keyhole-forward-clip-path" clipPathUnits="objectBoundingBox">
+ <svg:path d="M 0,0 C 0.15,0.12 0.25,0.3 0.25,0.5 0.25,0.7 0.15,0.88 0,1 L 1,1 1,0 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,-5 0,4.03 C 3.6,1.8 6,6.1 6,11 6,16 3.6,20 0,23 l 0,27 10000,0 0,-55 L 0,-5 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-ontop-left-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="M 9,0 C 7.3,0 6,1.3 6,3 l 0,14 c 0,3 -2.2,5 -5,5 l -1,0 0,1 12,0 0,-1 0,-19 0,-3 -3,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-ontop-right-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,0 0,3 0,19 0,1 12,0 0,-1 -1,0 C 8.2,22 6,20 6,17 L 6,3 C 6,1.3 4.7,0 3,0 L 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-onbottom-left-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,0 0,1 1,0 c 2.8,0 5,2.2 5,5 l 0,14 c 0,2 1.3,3 3,3 l 3,0 0,-3 L 12,1 12,0 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-onbottom-right-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,0 0,1 0,19 0,3 3,0 c 1.7,0 3,-1 3,-3 L 6,6 C 6,3.2 8.2,1 11,1 L 12,1 12,0 0,0 z"/>
+ </svg:clipPath>
+ </svg:svg>
+#endif
+
+</vbox>
+# <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
+# Introducing the iframe dynamically, as needed, was found to be better than
+# starting with an empty iframe here in browser.xul from a Ts standpoint.
+</deck>
+
+</window>
diff --git a/application/palemoon/base/content/browserMountPoints.inc b/application/palemoon/base/content/browserMountPoints.inc
new file mode 100644
index 000000000..e4315b04a
--- /dev/null
+++ b/application/palemoon/base/content/browserMountPoints.inc
@@ -0,0 +1,12 @@
+<stringbundleset id="stringbundleset"/>
+
+<commandset id="mainCommandSet"/>
+<commandset id="baseMenuCommandSet"/>
+<commandset id="placesCommands"/>
+
+<broadcasterset id="mainBroadcasterSet"/>
+
+<keyset id="mainKeyset"/>
+<keyset id="baseMenuKeyset"/>
+
+<menubar id="main-menubar"/> \ No newline at end of file
diff --git a/application/palemoon/base/content/content.js b/application/palemoon/base/content/content.js
new file mode 100644
index 000000000..19032eb84
--- /dev/null
+++ b/application/palemoon/base/content/content.js
@@ -0,0 +1,64 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+ "resource://gre/modules/InsecurePasswordUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
+ "resource:///modules/FormSubmitObserver.jsm");
+
+// Bug 671101 - directly using webNavigation in this context
+// causes docshells to leak
+this.__defineGetter__("webNavigation", function () {
+ return docShell.QueryInterface(Ci.nsIWebNavigation);
+});
+
+addMessageListener("WebNavigation:LoadURI", function (message) {
+ let flags = message.json.flags || webNavigation.LOAD_FLAGS_NONE;
+
+ webNavigation.loadURI(message.json.uri, flags, null, null, null);
+});
+
+// TabChildGlobal
+var global = this;
+
+// Load the form validation popup handler
+var formSubmitObserver = new FormSubmitObserver(content, this);
+
+addMessageListener("Browser:HideSessionRestoreButton", function (message) {
+ // Hide session restore button on about:home
+ let doc = content.document;
+ let container;
+ if (doc.documentURI.toLowerCase() == "about:home" &&
+ (container = doc.getElementById("sessionRestoreContainer"))){
+ container.hidden = true;
+ }
+});
+
+addEventListener("DOMFormHasPassword", function(event) {
+ InsecurePasswordUtils.checkForInsecurePasswords(event.target);
+ LoginManagerContent.onFormPassword(event);
+});
+addEventListener("DOMAutoComplete", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
+addEventListener("blur", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
+
+// Lazily load the finder code
+addMessageListener("Finder:Initialize", function () {
+ let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
+ new RemoteFinderListener(global);
+}); \ No newline at end of file
diff --git a/application/palemoon/base/content/downloadManagerOverlay.xul b/application/palemoon/base/content/downloadManagerOverlay.xul
new file mode 100644
index 000000000..9987820cb
--- /dev/null
+++ b/application/palemoon/base/content/downloadManagerOverlay.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="downloadManagerOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="downloadManager">
+
+#include browserMountPoints.inc
+
+<script type="application/javascript"><![CDATA[
+ window.addEventListener("load", function(event) {
+ // Bug 405696: Map Edit -> Find command to the download manager's command
+ var findMenuItem = document.getElementById("menu_find");
+ findMenuItem.setAttribute("command", "cmd_findDownload");
+ findMenuItem.setAttribute("key", "key_findDownload");
+
+ // Bug 429614: Map Edit -> Select All command to download manager's command
+ let selectAllMenuItem = document.getElementById("menu_selectAll");
+ selectAllMenuItem.setAttribute("command", "cmd_selectAllDownloads");
+ selectAllMenuItem.setAttribute("key", "key_selectAllDownloads");
+ }, false);
+]]></script>
+
+</window>
+
+</overlay>
diff --git a/application/palemoon/base/content/global-scripts.inc b/application/palemoon/base/content/global-scripts.inc
new file mode 100644
index 000000000..b4de574ae
--- /dev/null
+++ b/application/palemoon/base/content/global-scripts.inc
@@ -0,0 +1,13 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<script type="application/javascript" src="chrome://global/content/printUtils.js"/>
+<script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
+<script type="application/javascript" src="chrome://browser/content/places/browserPlacesViews.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
+<script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
+<script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
diff --git a/application/palemoon/base/content/hiddenWindow.xul b/application/palemoon/base/content/hiddenWindow.xul
new file mode 100644
index 000000000..bf201fd60
--- /dev/null
+++ b/application/palemoon/base/content/hiddenWindow.xul
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<window id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+#include browserMountPoints.inc
+
+</window>
+
+#endif
diff --git a/application/palemoon/base/content/highlighter.css b/application/palemoon/base/content/highlighter.css
new file mode 100644
index 000000000..8fb9d8085
--- /dev/null
+++ b/application/palemoon/base/content/highlighter.css
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.highlighter-container {
+ pointer-events: none;
+}
+
+.highlighter-controls {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.highlighter-outline-container {
+ overflow: hidden;
+ position: relative;
+}
+
+.highlighter-outline {
+ position: absolute;
+}
+
+.highlighter-outline[hidden] {
+ opacity: 0;
+ pointer-events: none;
+ display: -moz-box;
+}
+
+.highlighter-outline:not([disable-transitions]) {
+ transition-property: opacity, top, left, width, height;
+ transition-duration: 0.1s;
+ transition-timing-function: linear;
+}
+
+/*
+ * Node Infobar
+ */
+
+.highlighter-nodeinfobar-container {
+ position: absolute;
+ max-width: 95%;
+}
+
+.highlighter-nodeinfobar-container[hidden] {
+ opacity: 0;
+ pointer-events: none;
+ display: -moz-box;
+}
+
+.highlighter-nodeinfobar-container:not([disable-transitions]),
+.highlighter-nodeinfobar-container[disable-transitions][force-transitions] {
+ transition-property: transform, opacity, top, left;
+ transition-duration: 0.1s;
+ transition-timing-function: linear;
+}
+
+.highlighter-nodeinfobar-text {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ direction: ltr;
+}
+
+.highlighter-nodeinfobar-button > .toolbarbutton-text {
+ display: none;
+}
+
+.highlighter-nodeinfobar-container:not([locked]):not(:hover) > .highlighter-nodeinfobar > .highlighter-nodeinfobar-button {
+ visibility: hidden;
+}
+
+.highlighter-nodeinfobar-container[locked] > .highlighter-nodeinfobar,
+.highlighter-nodeinfobar-container:not([locked]):hover > .highlighter-nodeinfobar {
+ pointer-events: auto;
+}
+
+html|*.highlighter-nodeinfobar-id,
+html|*.highlighter-nodeinfobar-classes,
+html|*.highlighter-nodeinfobar-pseudo-classes,
+html|*.highlighter-nodeinfobar-tagname {
+ -moz-user-select: text;
+ -moz-user-focus: normal;
+ cursor: text;
+}
+
+.highlighter-nodeinfobar-arrow {
+ display: none;
+}
+
+.highlighter-nodeinfobar-container[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
+ display: block;
+}
+
+.highlighter-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
+ display: block;
+}
+
+.highlighter-nodeinfobar-container[disabled] {
+ visibility: hidden;
+}
+
+html|*.highlighter-nodeinfobar-tagname {
+ text-transform: lowercase;
+}
diff --git a/application/palemoon/base/content/jsConsoleOverlay.xul b/application/palemoon/base/content/jsConsoleOverlay.xul
new file mode 100644
index 000000000..1bc518d4f
--- /dev/null
+++ b/application/palemoon/base/content/jsConsoleOverlay.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="jsConsoleOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="JSConsoleWindow">
+
+#include browserMountPoints.inc
+
+</window>
+
+</overlay>
diff --git a/application/palemoon/base/content/macBrowserOverlay.xul b/application/palemoon/base/content/macBrowserOverlay.xul
new file mode 100644
index 000000000..a4d583e16
--- /dev/null
+++ b/application/palemoon/base/content/macBrowserOverlay.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+# All DTD information is stored in a separate file so that it can be shared by
+# hiddenWindow.xul.
+#include browser-doctype.inc
+
+<overlay id="hidden-overlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+# All JS files which are not content (only) dependent that browser.xul
+# wishes to include *must* go into the global-scripts.inc file
+# so that they can be shared by this overlay.
+#include global-scripts.inc
+
+<script type="application/javascript">
+ function OpenBrowserWindowFromDockMenu(options) {
+ let win = OpenBrowserWindow(options);
+ win.addEventListener("load", function listener() {
+ win.removeEventListener("load", listener);
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.activateApplication(true);
+ });
+
+ return win;
+ }
+
+ addEventListener("load", function() { gBrowserInit.nonBrowserWindowStartup() }, false);
+ addEventListener("unload", function() { gBrowserInit.nonBrowserWindowShutdown() }, false);
+</script>
+
+# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
+# browser-sets.inc file for sharing with hiddenWindow.xul.
+#include browser-sets.inc
+
+# The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
+# hiddenWindow.xul.
+#include browser-menubar.inc
+
+<!-- Dock menu -->
+<popupset>
+ <menupopup id="menu_mac_dockmenu">
+ <!-- The command cannot be cmd_newNavigator because we need to activate
+ the application. -->
+ <menuitem label="&newNavigatorCmd.label;" oncommand="OpenBrowserWindowFromDockMenu();"
+ id="macDockMenuNewWindow" />
+ <menuitem label="&newPrivateWindow.label;" oncommand="OpenBrowserWindowFromDockMenu({private: true});" />
+ </menupopup>
+</popupset>
+
+</overlay>
diff --git a/application/palemoon/base/content/newtab/cells.js b/application/palemoon/base/content/newtab/cells.js
new file mode 100644
index 000000000..47d4ef52d
--- /dev/null
+++ b/application/palemoon/base/content/newtab/cells.js
@@ -0,0 +1,126 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This class manages a cell's DOM node (not the actually cell content, a site).
+ * It's mostly read-only, i.e. all manipulation of both position and content
+ * aren't handled here.
+ */
+function Cell(aGrid, aNode) {
+ this._grid = aGrid;
+ this._node = aNode;
+ this._node._newtabCell = this;
+
+ // Register drag-and-drop event handlers.
+ ["dragenter", "dragover", "dragexit", "drop"].forEach(function (aType) {
+ this._node.addEventListener(aType, this, false);
+ }, this);
+}
+
+Cell.prototype = {
+ /**
+ * The grid.
+ */
+ _grid: null,
+
+ /**
+ * The cell's DOM node.
+ */
+ get node() { return this._node; },
+
+ /**
+ * The cell's offset in the grid.
+ */
+ get index() {
+ let index = this._grid.cells.indexOf(this);
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "index", {value: index, enumerable: true});
+
+ return index;
+ },
+
+ /**
+ * The previous cell in the grid.
+ */
+ get previousSibling() {
+ let prev = this.node.previousElementSibling;
+ prev = prev && prev._newtabCell;
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "previousSibling", {value: prev, enumerable: true});
+
+ return prev;
+ },
+
+ /**
+ * The next cell in the grid.
+ */
+ get nextSibling() {
+ let next = this.node.nextElementSibling;
+ next = next && next._newtabCell;
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "nextSibling", {value: next, enumerable: true});
+
+ return next;
+ },
+
+ /**
+ * The site contained in the cell, if any.
+ */
+ get site() {
+ let firstChild = this.node.firstElementChild;
+ return firstChild && firstChild._newtabSite;
+ },
+
+ /**
+ * Checks whether the cell contains a pinned site.
+ * @return Whether the cell contains a pinned site.
+ */
+ containsPinnedSite: function Cell_containsPinnedSite() {
+ let site = this.site;
+ return site && site.isPinned();
+ },
+
+ /**
+ * Checks whether the cell contains a site (is empty).
+ * @return Whether the cell is empty.
+ */
+ isEmpty: function Cell_isEmpty() {
+ return !this.site;
+ },
+
+ /**
+ * Handles all cell events.
+ */
+ handleEvent: function Cell_handleEvent(aEvent) {
+ // We're not responding to external drag/drop events
+ // when our parent window is in private browsing mode.
+ if (inPrivateBrowsingMode() && !gDrag.draggedSite)
+ return;
+
+ if (aEvent.type != "dragexit" && !gDrag.isValid(aEvent))
+ return;
+
+ switch (aEvent.type) {
+ case "dragenter":
+ aEvent.preventDefault();
+ gDrop.enter(this, aEvent);
+ break;
+ case "dragover":
+ aEvent.preventDefault();
+ break;
+ case "dragexit":
+ gDrop.exit(this, aEvent);
+ break;
+ case "drop":
+ aEvent.preventDefault();
+ gDrop.drop(this, aEvent);
+ break;
+ }
+ }
+};
diff --git a/application/palemoon/base/content/newtab/drag.js b/application/palemoon/base/content/newtab/drag.js
new file mode 100644
index 000000000..8f0bf674e
--- /dev/null
+++ b/application/palemoon/base/content/newtab/drag.js
@@ -0,0 +1,151 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton implements site dragging functionality.
+ */
+let gDrag = {
+ /**
+ * The site offset to the drag start point.
+ */
+ _offsetX: null,
+ _offsetY: null,
+
+ /**
+ * The site that is dragged.
+ */
+ _draggedSite: null,
+ get draggedSite() { return this._draggedSite; },
+
+ /**
+ * The cell width/height at the point the drag started.
+ */
+ _cellWidth: null,
+ _cellHeight: null,
+ get cellWidth() { return this._cellWidth; },
+ get cellHeight() { return this._cellHeight; },
+
+ /**
+ * Start a new drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragstart' event.
+ */
+ start: function Drag_start(aSite, aEvent) {
+ this._draggedSite = aSite;
+
+ // Mark nodes as being dragged.
+ let selector = ".newtab-site, .newtab-control, .newtab-thumbnail";
+ let parentCell = aSite.node.parentNode;
+ let nodes = parentCell.querySelectorAll(selector);
+ for (let i = 0; i < nodes.length; i++)
+ nodes[i].setAttribute("dragged", "true");
+
+ parentCell.setAttribute("dragged", "true");
+
+ this._setDragData(aSite, aEvent);
+
+ // Store the cursor offset.
+ let node = aSite.node;
+ let rect = node.getBoundingClientRect();
+ this._offsetX = aEvent.clientX - rect.left;
+ this._offsetY = aEvent.clientY - rect.top;
+
+ // Store the cell dimensions.
+ let cellNode = aSite.cell.node;
+ this._cellWidth = cellNode.offsetWidth;
+ this._cellHeight = cellNode.offsetHeight;
+
+ gTransformation.freezeSitePosition(aSite);
+ },
+
+ /**
+ * Handles the 'drag' event.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'drag' event.
+ */
+ drag: function Drag_drag(aSite, aEvent) {
+ // Get the viewport size.
+ let {clientWidth, clientHeight} = document.documentElement;
+
+ // We'll want a padding of 5px.
+ let border = 5;
+
+ // Enforce minimum constraints to keep the drag image inside the window.
+ let left = Math.max(scrollX + aEvent.clientX - this._offsetX, border);
+ let top = Math.max(scrollY + aEvent.clientY - this._offsetY, border);
+
+ // Enforce maximum constraints to keep the drag image inside the window.
+ left = Math.min(left, scrollX + clientWidth - this.cellWidth - border);
+ top = Math.min(top, scrollY + clientHeight - this.cellHeight - border);
+
+ // Update the drag image's position.
+ gTransformation.setSitePosition(aSite, {left: left, top: top});
+ },
+
+ /**
+ * Ends the current drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragend' event.
+ */
+ end: function Drag_end(aSite, aEvent) {
+ let nodes = gGrid.node.querySelectorAll("[dragged]")
+ for (let i = 0; i < nodes.length; i++)
+ nodes[i].removeAttribute("dragged");
+
+ // Slide the dragged site back into its cell (may be the old or the new cell).
+ gTransformation.slideSiteTo(aSite, aSite.cell, {unfreeze: true});
+
+ this._draggedSite = null;
+ },
+
+ /**
+ * Checks whether we're responsible for a given drag event.
+ * @param aEvent The drag event to check.
+ * @return Whether we should handle this drag and drop operation.
+ */
+ isValid: function Drag_isValid(aEvent) {
+ let link = gDragDataHelper.getLinkFromDragEvent(aEvent);
+
+ // Check that the drag data is non-empty.
+ // Can happen when dragging places folders.
+ if (!link || !link.url) {
+ return false;
+ }
+
+ // Check that we're not accepting URLs which would inherit the caller's
+ // principal (such as javascript: or data:).
+ return gLinkChecker.checkLoadURI(link.url);
+ },
+
+ /**
+ * Initializes the drag data for the current drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragstart' event.
+ */
+ _setDragData: function Drag_setDragData(aSite, aEvent) {
+ let {url, title} = aSite;
+
+ let dt = aEvent.dataTransfer;
+ dt.mozCursor = "default";
+ dt.effectAllowed = "move";
+ dt.setData("text/plain", url);
+ dt.setData("text/uri-list", url);
+ dt.setData("text/x-moz-url", url + "\n" + title);
+ dt.setData("text/html", "<a href=\"" + url + "\">" + url + "</a>");
+
+ // Create and use an empty drag element. We don't want to use the default
+ // drag image with its default opacity.
+ let dragElement = document.createElementNS(HTML_NAMESPACE, "div");
+ dragElement.classList.add("newtab-drag");
+ let scrollbox = document.getElementById("newtab-scrollbox");
+ scrollbox.appendChild(dragElement);
+ dt.setDragImage(dragElement, 0, 0);
+
+ // After the 'dragstart' event has been processed we can remove the
+ // temporary drag element from the DOM.
+ setTimeout(() => scrollbox.removeChild(dragElement), 0);
+ }
+};
diff --git a/application/palemoon/base/content/newtab/dragDataHelper.js b/application/palemoon/base/content/newtab/dragDataHelper.js
new file mode 100644
index 000000000..a66e4e87e
--- /dev/null
+++ b/application/palemoon/base/content/newtab/dragDataHelper.js
@@ -0,0 +1,22 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+let gDragDataHelper = {
+ get mimeType() {
+ return "text/x-moz-url";
+ },
+
+ getLinkFromDragEvent: function DragDataHelper_getLinkFromDragEvent(aEvent) {
+ let dt = aEvent.dataTransfer;
+ if (!dt || !dt.types.contains(this.mimeType)) {
+ return null;
+ }
+
+ let data = dt.getData(this.mimeType) || "";
+ let [url, title] = data.split(/[\r\n]+/);
+ return {url: url, title: title};
+ }
+};
diff --git a/application/palemoon/base/content/newtab/drop.js b/application/palemoon/base/content/newtab/drop.js
new file mode 100644
index 000000000..d7bf30506
--- /dev/null
+++ b/application/palemoon/base/content/newtab/drop.js
@@ -0,0 +1,150 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+// A little delay that prevents the grid from being too sensitive when dragging
+// sites around.
+const DELAY_REARRANGE_MS = 100;
+
+/**
+ * This singleton implements site dropping functionality.
+ */
+let gDrop = {
+ /**
+ * The last drop target.
+ */
+ _lastDropTarget: null,
+
+ /**
+ * Handles the 'dragenter' event.
+ * @param aCell The drop target cell.
+ */
+ enter: function Drop_enter(aCell) {
+ this._delayedRearrange(aCell);
+ },
+
+ /**
+ * Handles the 'dragexit' event.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ exit: function Drop_exit(aCell, aEvent) {
+ if (aEvent.dataTransfer && !aEvent.dataTransfer.mozUserCancelled) {
+ this._delayedRearrange();
+ } else {
+ // The drag operation has been cancelled.
+ this._cancelDelayedArrange();
+ this._rearrange();
+ }
+ },
+
+ /**
+ * Handles the 'drop' event.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ drop: function Drop_drop(aCell, aEvent) {
+ // The cell that is the drop target could contain a pinned site. We need
+ // to find out where that site has gone and re-pin it there.
+ if (aCell.containsPinnedSite())
+ this._repinSitesAfterDrop(aCell);
+
+ // Pin the dragged or insert the new site.
+ this._pinDraggedSite(aCell, aEvent);
+
+ this._cancelDelayedArrange();
+
+ // Update the grid and move all sites to their new places.
+ gUpdater.updateGrid();
+ },
+
+ /**
+ * Re-pins all pinned sites in their (new) positions.
+ * @param aCell The drop target cell.
+ */
+ _repinSitesAfterDrop: function Drop_repinSitesAfterDrop(aCell) {
+ let sites = gDropPreview.rearrange(aCell);
+
+ // Filter out pinned sites.
+ let pinnedSites = sites.filter(function (aSite) {
+ return aSite && aSite.isPinned();
+ });
+
+ // Re-pin all shifted pinned cells.
+ pinnedSites.forEach(aSite => aSite.pin(sites.indexOf(aSite)));
+ },
+
+ /**
+ * Pins the dragged site in its new place.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ _pinDraggedSite: function Drop_pinDraggedSite(aCell, aEvent) {
+ let index = aCell.index;
+ let draggedSite = gDrag.draggedSite;
+
+ if (draggedSite) {
+ // Pin the dragged site at its new place.
+ if (aCell != draggedSite.cell)
+ draggedSite.pin(index);
+ } else {
+ let link = gDragDataHelper.getLinkFromDragEvent(aEvent);
+ if (link) {
+ // A new link was dragged onto the grid. Create it by pinning its URL.
+ gPinnedLinks.pin(link, index);
+
+ // Make sure the newly added link is not blocked.
+ gBlockedLinks.unblock(link);
+ }
+ }
+ },
+
+ /**
+ * Time a rearrange with a little delay.
+ * @param aCell The drop target cell.
+ */
+ _delayedRearrange: function Drop_delayedRearrange(aCell) {
+ // The last drop target didn't change so there's no need to re-arrange.
+ if (this._lastDropTarget == aCell)
+ return;
+
+ let self = this;
+
+ function callback() {
+ self._rearrangeTimeout = null;
+ self._rearrange(aCell);
+ }
+
+ this._cancelDelayedArrange();
+ this._rearrangeTimeout = setTimeout(callback, DELAY_REARRANGE_MS);
+
+ // Store the last drop target.
+ this._lastDropTarget = aCell;
+ },
+
+ /**
+ * Cancels a timed rearrange, if any.
+ */
+ _cancelDelayedArrange: function Drop_cancelDelayedArrange() {
+ if (this._rearrangeTimeout) {
+ clearTimeout(this._rearrangeTimeout);
+ this._rearrangeTimeout = null;
+ }
+ },
+
+ /**
+ * Rearrange all sites in the grid depending on the current drop target.
+ * @param aCell The drop target cell.
+ */
+ _rearrange: function Drop_rearrange(aCell) {
+ let sites = gGrid.sites;
+
+ // We need to rearrange the grid only if there's a current drop target.
+ if (aCell)
+ sites = gDropPreview.rearrange(aCell);
+
+ gTransformation.rearrangeSites(sites, {unfreeze: !aCell});
+ }
+};
diff --git a/application/palemoon/base/content/newtab/dropPreview.js b/application/palemoon/base/content/newtab/dropPreview.js
new file mode 100644
index 000000000..903762345
--- /dev/null
+++ b/application/palemoon/base/content/newtab/dropPreview.js
@@ -0,0 +1,222 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides the ability to re-arrange the current grid to
+ * indicate the transformation that results from dropping a cell at a certain
+ * position.
+ */
+let gDropPreview = {
+ /**
+ * Rearranges the sites currently contained in the grid when a site would be
+ * dropped onto the given cell.
+ * @param aCell The drop target cell.
+ * @return The re-arranged array of sites.
+ */
+ rearrange: function DropPreview_rearrange(aCell) {
+ let sites = gGrid.sites;
+
+ // Insert the dragged site into the current grid.
+ this._insertDraggedSite(sites, aCell);
+
+ // After the new site has been inserted we need to correct the positions
+ // of all pinned tabs that have been moved around.
+ this._repositionPinnedSites(sites, aCell);
+
+ return sites;
+ },
+
+ /**
+ * Inserts the currently dragged site into the given array of sites.
+ * @param aSites The array of sites to insert into.
+ * @param aCell The drop target cell.
+ */
+ _insertDraggedSite: function DropPreview_insertDraggedSite(aSites, aCell) {
+ let dropIndex = aCell.index;
+ let draggedSite = gDrag.draggedSite;
+
+ // We're currently dragging a site.
+ if (draggedSite) {
+ let dragCell = draggedSite.cell;
+ let dragIndex = dragCell.index;
+
+ // Move the dragged site into its new position.
+ if (dragIndex != dropIndex) {
+ aSites.splice(dragIndex, 1);
+ aSites.splice(dropIndex, 0, draggedSite);
+ }
+ // We're handling an external drag item.
+ } else {
+ aSites.splice(dropIndex, 0, null);
+ }
+ },
+
+ /**
+ * Correct the position of all pinned sites that might have been moved to
+ * different positions after the dragged site has been inserted.
+ * @param aSites The array of sites containing the dragged site.
+ * @param aCell The drop target cell.
+ */
+ _repositionPinnedSites:
+ function DropPreview_repositionPinnedSites(aSites, aCell) {
+
+ // Collect all pinned sites.
+ let pinnedSites = this._filterPinnedSites(aSites, aCell);
+
+ // Correct pinned site positions.
+ pinnedSites.forEach(function (aSite) {
+ aSites[aSites.indexOf(aSite)] = aSites[aSite.cell.index];
+ aSites[aSite.cell.index] = aSite;
+ }, this);
+
+ // There might be a pinned cell that got pushed out of the grid, try to
+ // sneak it in by removing a lower-priority cell.
+ if (this._hasOverflowedPinnedSite(aSites, aCell))
+ this._repositionOverflowedPinnedSite(aSites, aCell);
+ },
+
+ /**
+ * Filter pinned sites out of the grid that are still on their old positions
+ * and have not moved.
+ * @param aSites The array of sites to filter.
+ * @param aCell The drop target cell.
+ * @return The filtered array of sites.
+ */
+ _filterPinnedSites: function DropPreview_filterPinnedSites(aSites, aCell) {
+ let draggedSite = gDrag.draggedSite;
+
+ // When dropping on a cell that contains a pinned site make sure that all
+ // pinned cells surrounding the drop target are moved as well.
+ let range = this._getPinnedRange(aCell);
+
+ return aSites.filter(function (aSite, aIndex) {
+ // The site must be valid, pinned and not the dragged site.
+ if (!aSite || aSite == draggedSite || !aSite.isPinned())
+ return false;
+
+ let index = aSite.cell.index;
+
+ // If it's not in the 'pinned range' it's a valid pinned site.
+ return (index > range.end || index < range.start);
+ });
+ },
+
+ /**
+ * Determines the range of pinned sites surrounding the drop target cell.
+ * @param aCell The drop target cell.
+ * @return The range of pinned cells.
+ */
+ _getPinnedRange: function DropPreview_getPinnedRange(aCell) {
+ let dropIndex = aCell.index;
+ let range = {start: dropIndex, end: dropIndex};
+
+ // We need a pinned range only when dropping on a pinned site.
+ if (aCell.containsPinnedSite()) {
+ let links = gPinnedLinks.links;
+
+ // Find all previous siblings of the drop target that are pinned as well.
+ while (range.start && links[range.start - 1])
+ range.start--;
+
+ let maxEnd = links.length - 1;
+
+ // Find all next siblings of the drop target that are pinned as well.
+ while (range.end < maxEnd && links[range.end + 1])
+ range.end++;
+ }
+
+ return range;
+ },
+
+ /**
+ * Checks if the given array of sites contains a pinned site that has
+ * been pushed out of the grid.
+ * @param aSites The array of sites to check.
+ * @param aCell The drop target cell.
+ * @return Whether there is an overflowed pinned cell.
+ */
+ _hasOverflowedPinnedSite:
+ function DropPreview_hasOverflowedPinnedSite(aSites, aCell) {
+
+ // If the drop target isn't pinned there's no way a pinned site has been
+ // pushed out of the grid so we can just exit here.
+ if (!aCell.containsPinnedSite())
+ return false;
+
+ let cells = gGrid.cells;
+
+ // No cells have been pushed out of the grid, nothing to do here.
+ if (aSites.length <= cells.length)
+ return false;
+
+ let overflowedSite = aSites[cells.length];
+
+ // Nothing to do if the site that got pushed out of the grid is not pinned.
+ return (overflowedSite && overflowedSite.isPinned());
+ },
+
+ /**
+ * We have a overflowed pinned site that we need to re-position so that it's
+ * visible again. We try to find a lower-priority cell (empty or containing
+ * an unpinned site) that we can move it to.
+ * @param aSites The array of sites.
+ * @param aCell The drop target cell.
+ */
+ _repositionOverflowedPinnedSite:
+ function DropPreview_repositionOverflowedPinnedSite(aSites, aCell) {
+
+ // Try to find a lower-priority cell (empty or containing an unpinned site).
+ let index = this._indexOfLowerPrioritySite(aSites, aCell);
+
+ if (index > -1) {
+ let cells = gGrid.cells;
+ let dropIndex = aCell.index;
+
+ // Move all pinned cells to their new positions to let the overflowed
+ // site fit into the grid.
+ for (let i = index + 1, lastPosition = index; i < aSites.length; i++) {
+ if (i != dropIndex) {
+ aSites[lastPosition] = aSites[i];
+ lastPosition = i;
+ }
+ }
+
+ // Finally, remove the overflowed site from its previous position.
+ aSites.splice(cells.length, 1);
+ }
+ },
+
+ /**
+ * Finds the index of the last cell that is empty or contains an unpinned
+ * site. These are considered to be of a lower priority.
+ * @param aSites The array of sites.
+ * @param aCell The drop target cell.
+ * @return The cell's index.
+ */
+ _indexOfLowerPrioritySite:
+ function DropPreview_indexOfLowerPrioritySite(aSites, aCell) {
+
+ let cells = gGrid.cells;
+ let dropIndex = aCell.index;
+
+ // Search (beginning with the last site in the grid) for a site that is
+ // empty or unpinned (an thus lower-priority) and can be pushed out of the
+ // grid instead of the pinned site.
+ for (let i = cells.length - 1; i >= 0; i--) {
+ // The cell that is our drop target is not a good choice.
+ if (i == dropIndex)
+ continue;
+
+ let site = aSites[i];
+
+ // We can use the cell only if it's empty or the site is un-pinned.
+ if (!site || !site.isPinned())
+ return i;
+ }
+
+ return -1;
+ }
+};
diff --git a/application/palemoon/base/content/newtab/dropTargetShim.js b/application/palemoon/base/content/newtab/dropTargetShim.js
new file mode 100644
index 000000000..a85a6ccd6
--- /dev/null
+++ b/application/palemoon/base/content/newtab/dropTargetShim.js
@@ -0,0 +1,188 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides a custom drop target detection. We need this because
+ * the default DnD target detection relies on the cursor's position. We want
+ * to pick a drop target based on the dragged site's position.
+ */
+let gDropTargetShim = {
+ /**
+ * Cache for the position of all cells, cleaned after drag finished.
+ */
+ _cellPositions: null,
+
+ /**
+ * The last drop target that was hovered.
+ */
+ _lastDropTarget: null,
+
+ /**
+ * Initializes the drop target shim.
+ */
+ init: function DropTargetShim_init() {
+ let node = gGrid.node;
+
+ // Add drag event handlers.
+ node.addEventListener("dragstart", this, true);
+ node.addEventListener("dragend", this, true);
+ },
+
+ /**
+ * Handles all shim events.
+ */
+ handleEvent: function DropTargetShim_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "dragstart":
+ this._start(aEvent);
+ break;
+ case "dragover":
+ this._dragover(aEvent);
+ break;
+ case "dragend":
+ this._end(aEvent);
+ break;
+ }
+ },
+
+ /**
+ * Handles the 'dragstart' event.
+ * @param aEvent The 'dragstart' event.
+ */
+ _start: function DropTargetShim_start(aEvent) {
+ if (aEvent.target.classList.contains("newtab-link")) {
+ gGrid.lock();
+
+ // XXX bug 505521 - Listen for dragover on the document.
+ document.documentElement.addEventListener("dragover", this, false);
+ }
+ },
+
+ /**
+ * Handles the 'drag' event and determines the current drop target.
+ * @param aEvent The 'drag' event.
+ */
+ _drag: function DropTargetShim_drag(aEvent) {
+ // Let's see if we find a drop target.
+ let target = this._findDropTarget(aEvent);
+
+ if (target != this._lastDropTarget) {
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+
+ if (target)
+ // We're now hovering a (new) drop target.
+ this._dispatchEvent(aEvent, "dragenter", target);
+
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+
+ this._lastDropTarget = target;
+ }
+ },
+
+ /**
+ * Handles the 'dragover' event as long as bug 505521 isn't fixed to get
+ * current mouse cursor coordinates while dragging.
+ * @param aEvent The 'dragover' event.
+ */
+ _dragover: function DropTargetShim_dragover(aEvent) {
+ let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
+ gDrag.drag(sourceNode._newtabSite, aEvent);
+
+ this._drag(aEvent);
+ },
+
+ /**
+ * Handles the 'dragend' event.
+ * @param aEvent The 'dragend' event.
+ */
+ _end: function DropTargetShim_end(aEvent) {
+ // Make sure to determine the current drop target in case the dragenter
+ // event hasn't been fired.
+ this._drag(aEvent);
+
+ if (this._lastDropTarget) {
+ if (aEvent.dataTransfer.mozUserCancelled) {
+ // The drag operation was cancelled.
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+ } else {
+ // A site was successfully dropped.
+ this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
+ }
+
+ // Clean up.
+ this._lastDropTarget = null;
+ this._cellPositions = null;
+ }
+
+ gGrid.unlock();
+
+ // XXX bug 505521 - Remove the document's dragover listener.
+ document.documentElement.removeEventListener("dragover", this, false);
+ },
+
+ /**
+ * Determines the current drop target by matching the dragged site's position
+ * against all cells in the grid.
+ * @return The currently hovered drop target or null.
+ */
+ _findDropTarget: function DropTargetShim_findDropTarget() {
+ // These are the minimum intersection values - we want to use the cell if
+ // the site is >= 50% hovering its position.
+ let minWidth = gDrag.cellWidth / 2;
+ let minHeight = gDrag.cellHeight / 2;
+
+ let cellPositions = this._getCellPositions();
+ let rect = gTransformation.getNodePosition(gDrag.draggedSite.node);
+
+ // Compare each cell's position to the dragged site's position.
+ for (let i = 0; i < cellPositions.length; i++) {
+ let inter = rect.intersect(cellPositions[i].rect);
+
+ // If the intersection is big enough we found a drop target.
+ if (inter.width >= minWidth && inter.height >= minHeight)
+ return cellPositions[i].cell;
+ }
+
+ // No drop target found.
+ return null;
+ },
+
+ /**
+ * Gets the positions of all cell nodes.
+ * @return The (cached) cell positions.
+ */
+ _getCellPositions: function DropTargetShim_getCellPositions() {
+ if (this._cellPositions)
+ return this._cellPositions;
+
+ return this._cellPositions = gGrid.cells.map(function (cell) {
+ return {cell: cell, rect: gTransformation.getNodePosition(cell.node)};
+ });
+ },
+
+ /**
+ * Dispatches a custom DragEvent on the given target node.
+ * @param aEvent The source event.
+ * @param aType The event type.
+ * @param aTarget The target node that receives the event.
+ */
+ _dispatchEvent:
+ function DropTargetShim_dispatchEvent(aEvent, aType, aTarget) {
+
+ let node = aTarget.node;
+ let event = document.createEvent("DragEvents");
+
+ event.initDragEvent(aType, true, true, window, 0, 0, 0, 0, 0, false, false,
+ false, false, 0, node, aEvent.dataTransfer);
+
+ node.dispatchEvent(event);
+ }
+};
diff --git a/application/palemoon/base/content/newtab/grid.js b/application/palemoon/base/content/newtab/grid.js
new file mode 100644
index 000000000..37559a063
--- /dev/null
+++ b/application/palemoon/base/content/newtab/grid.js
@@ -0,0 +1,171 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton represents the grid that contains all sites.
+ */
+let gGrid = {
+ /**
+ * The DOM node of the grid.
+ */
+ _node: null,
+ get node() { return this._node; },
+
+ /**
+ * The cached DOM fragment for sites.
+ */
+ _siteFragment: null,
+
+ /**
+ * All cells contained in the grid.
+ */
+ _cells: null,
+ get cells() { return this._cells; },
+
+ /**
+ * All sites contained in the grid's cells. Sites may be empty.
+ */
+ get sites() { return [for (cell of this.cells) cell.site]; },
+
+ // Tells whether the grid has already been initialized.
+ get ready() { return !!this._node; },
+
+ /**
+ * Initializes the grid.
+ * @param aSelector The query selector of the grid.
+ */
+ init: function Grid_init() {
+ this._node = document.getElementById("newtab-grid");
+ this._createSiteFragment();
+ this._render();
+ },
+
+ /**
+ * Creates a new site in the grid.
+ * @param aLink The new site's link.
+ * @param aCell The cell that will contain the new site.
+ * @return The newly created site.
+ */
+ createSite: function Grid_createSite(aLink, aCell) {
+ let node = aCell.node;
+ node.appendChild(this._siteFragment.cloneNode(true));
+ return new Site(node.firstElementChild, aLink);
+ },
+
+ /**
+ * Refreshes the grid and re-creates all sites.
+ */
+ refresh: function Grid_refresh() {
+ // Remove all sites.
+ this.cells.forEach(function (cell) {
+ let node = cell.node;
+ let child = node.firstElementChild;
+
+ if (child)
+ node.removeChild(child);
+ }, this);
+
+ // Render the grid again.
+ this._render();
+ },
+
+ /**
+ * Locks the grid to block all pointer events.
+ */
+ lock: function Grid_lock() {
+ this.node.setAttribute("locked", "true");
+ },
+
+ /**
+ * Unlocks the grid to allow all pointer events.
+ */
+ unlock: function Grid_unlock() {
+ this.node.removeAttribute("locked");
+ },
+
+ /**
+ * Creates the newtab grid.
+ */
+ _renderGrid: function Grid_renderGrid() {
+ let row = document.createElementNS(HTML_NAMESPACE, "div");
+ let cell = document.createElementNS(HTML_NAMESPACE, "div");
+ row.classList.add("newtab-row");
+ cell.classList.add("newtab-cell");
+
+ // Clear the grid
+ this._node.innerHTML = "";
+
+ // Creates the structure of one row
+ for (let i = 0; i < gGridPrefs.gridColumns; i++) {
+ row.appendChild(cell.cloneNode(true));
+ }
+ // Creates the grid
+ for (let j = 0; j < gGridPrefs.gridRows; j++) {
+ this._node.appendChild(row.cloneNode(true));
+ }
+
+ // (Re-)initialize all cells.
+ let cellElements = this.node.querySelectorAll(".newtab-cell");
+ this._cells = [new Cell(this, cell) for (cell of cellElements)];
+ },
+
+ /**
+ * Creates the DOM fragment that is re-used when creating sites.
+ */
+ _createSiteFragment: function Grid_createSiteFragment() {
+ let site = document.createElementNS(HTML_NAMESPACE, "div");
+ site.classList.add("newtab-site");
+ site.setAttribute("draggable", "true");
+
+ // Create the site's inner HTML code.
+ site.innerHTML =
+ '<a class="newtab-link">' +
+ ' <span class="newtab-thumbnail"/>' +
+ ' <span class="newtab-title"/>' +
+ '</a>' +
+ '<input type="button" title="' + newTabString("pin") + '"' +
+ ' class="newtab-control newtab-control-pin"/>' +
+ '<input type="button" title="' + newTabString("block") + '"' +
+ ' class="newtab-control newtab-control-block"/>';
+
+ this._siteFragment = document.createDocumentFragment();
+ this._siteFragment.appendChild(site);
+ },
+
+ /**
+ * Renders the sites, creates all sites and puts them into their cells.
+ */
+ _renderSites: function Grid_renderSites() {
+ let cells = this.cells;
+ // Put sites into the cells.
+ let links = gLinks.getLinks();
+ let length = Math.min(links.length, cells.length);
+
+ for (let i = 0; i < length; i++) {
+ if (links[i])
+ this.createSite(links[i], cells[i]);
+ }
+ },
+
+ /**
+ * Renders the grid.
+ */
+ _render: function Grid_render() {
+ if (this._shouldRenderGrid()) {
+ this._renderGrid();
+ }
+
+ this._renderSites();
+ },
+
+ _shouldRenderGrid : function Grid_shouldRenderGrid() {
+ let rowsLength = this._node.querySelectorAll(".newtab-row").length;
+ let cellsLength = this._node.querySelectorAll(".newtab-cell").length;
+
+ return (rowsLength != gGridPrefs.gridRows ||
+ cellsLength != (gGridPrefs.gridRows * gGridPrefs.gridColumns));
+ }
+};
diff --git a/application/palemoon/base/content/newtab/newTab.css b/application/palemoon/base/content/newtab/newTab.css
new file mode 100644
index 000000000..830e4a8c1
--- /dev/null
+++ b/application/palemoon/base/content/newtab/newTab.css
@@ -0,0 +1,209 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+input[type=button] {
+ cursor: pointer;
+}
+
+/* SCROLLBOX */
+#newtab-scrollbox {
+ display: -moz-box;
+ position: relative;
+ -moz-box-flex: 1;
+ -moz-user-focus: normal;
+}
+
+#newtab-scrollbox:not([page-disabled]) {
+ overflow: auto;
+}
+
+/* UNDO */
+#newtab-undo-container {
+ transition: opacity 100ms ease-out;
+ display: -moz-box;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+#newtab-undo-container[undo-disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+/* TOGGLE */
+#newtab-toggle {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+}
+
+#newtab-toggle:-moz-locale-dir(rtl) {
+ left: 12px;
+ right: auto;
+}
+
+/* MARGINS */
+#newtab-vertical-margin {
+ display: -moz-box;
+ position: relative;
+ -moz-box-flex: 1;
+ -moz-box-orient: vertical;
+}
+
+#newtab-margin-top {
+ min-height: 50px;
+ max-height: 80px;
+ display: -moz-box;
+ -moz-box-flex: 1;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+#newtab-margin-bottom {
+ min-height: 40px;
+ max-height: 100px;
+ -moz-box-flex: 1;
+}
+
+#newtab-horizontal-margin {
+ display: -moz-box;
+ -moz-box-flex: 5;
+}
+
+.newtab-side-margin {
+ min-width: 40px;
+ max-width: 300px;
+ -moz-box-flex: 1;
+}
+
+/* GRID */
+#newtab-grid {
+ display: -moz-box;
+ -moz-box-flex: 5;
+ -moz-box-orient: vertical;
+ min-width: 600px;
+ min-height: 400px;
+ transition: 100ms ease-out;
+ transition-property: opacity;
+}
+
+#newtab-grid[page-disabled] {
+ opacity: 0;
+}
+
+#newtab-grid[locked],
+#newtab-grid[page-disabled] {
+ pointer-events: none;
+}
+
+/* ROWS */
+.newtab-row {
+ display: -moz-box;
+ -moz-box-orient: horizontal;
+ -moz-box-direction: normal;
+ -moz-box-flex: 1;
+}
+
+/* CELLS */
+.newtab-cell {
+ display: -moz-box;
+ -moz-box-flex: 1;
+}
+
+/* SITES */
+.newtab-site {
+ position: relative;
+ -moz-box-flex: 1;
+ transition: 100ms ease-out;
+ transition-property: top, left, opacity;
+}
+
+.newtab-site[frozen] {
+ position: absolute;
+ pointer-events: none;
+}
+
+.newtab-site[dragged] {
+ transition-property: none;
+ z-index: 10;
+}
+
+/* LINK + THUMBNAILS */
+.newtab-link,
+.newtab-thumbnail {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.newtab-thumbnail {
+ opacity: .8;
+ transition: opacity 100ms ease-out;
+}
+
+.newtab-thumbnail[dragged],
+.newtab-link:-moz-focusring > .newtab-thumbnail,
+.newtab-site:hover > .newtab-link > .newtab-thumbnail {
+ opacity: 1;
+}
+
+/* TITLES */
+.newtab-title {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* CONTROLS */
+.newtab-control {
+ position: absolute;
+ top: 4px;
+ opacity: 0;
+ transition: opacity 100ms ease-out;
+}
+
+.newtab-control:-moz-focusring,
+.newtab-site:hover > .newtab-control {
+ opacity: 1;
+}
+
+.newtab-control[dragged] {
+ opacity: 0 !important;
+}
+
+@media (-moz-touch-enabled) {
+ .newtab-control {
+ opacity: 1;
+ }
+}
+
+.newtab-control-pin:-moz-locale-dir(ltr),
+.newtab-control-block:-moz-locale-dir(rtl) {
+ left: 4px;
+}
+
+.newtab-control-block:-moz-locale-dir(ltr),
+.newtab-control-pin:-moz-locale-dir(rtl) {
+ right: 4px;
+}
+
+/* DRAG & DROP */
+
+/*
+ * This is just a temporary drag element used for dataTransfer.setDragImage()
+ * so that we can use custom drag images and elements. It needs an opacity of
+ * 0.01 so that the core code detects that it's in fact a visible element.
+ */
+.newtab-drag {
+ width: 1px;
+ height: 1px;
+ background-color: #fff;
+ opacity: 0.01;
+}
diff --git a/application/palemoon/base/content/newtab/newTab.js b/application/palemoon/base/content/newtab/newTab.js
new file mode 100644
index 000000000..77c929125
--- /dev/null
+++ b/application/palemoon/base/content/newtab/newTab.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let Cu = Components.utils;
+let Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/NewTabUtils.jsm");
+Cu.import("resource:///modules/promise.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Rect",
+ "resource://gre/modules/Geometry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+let {
+ links: gLinks,
+ allPages: gAllPages,
+ linkChecker: gLinkChecker,
+ pinnedLinks: gPinnedLinks,
+ blockedLinks: gBlockedLinks,
+ gridPrefs: gGridPrefs
+} = NewTabUtils;
+
+XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
+ return Services.strings.
+ createBundle("chrome://browser/locale/newTab.properties");
+});
+
+function newTabString(name) gStringBundle.GetStringFromName('newtab.' + name);
+
+function inPrivateBrowsingMode() {
+ return PrivateBrowsingUtils.isWindowPrivate(window);
+}
+
+const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
+
+#include transformations.js
+#include page.js
+#include grid.js
+#include cells.js
+#include sites.js
+#include drag.js
+#include dragDataHelper.js
+#include drop.js
+#include dropTargetShim.js
+#include dropPreview.js
+#include updater.js
+#include undo.js
+
+// Everything is loaded. Initialize the New Tab Page.
+gPage.init();
diff --git a/application/palemoon/base/content/newtab/newTab.xul b/application/palemoon/base/content/newtab/newTab.xul
new file mode 100644
index 000000000..6fc202f29
--- /dev/null
+++ b/application/palemoon/base/content/newtab/newTab.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
+ %newTabDTD;
+]>
+
+<xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&newtab.pageTitle;">
+
+ <div id="newtab-scrollbox">
+
+ <div id="newtab-vertical-margin">
+ <div id="newtab-margin-top">
+ <div id="newtab-undo-container" undo-disabled="true">
+ <xul:label id="newtab-undo-label"
+ value="&newtab.undo.removedLabel;" />
+ <xul:button id="newtab-undo-button" tabindex="-1"
+ label="&newtab.undo.undoButton;"
+ class="newtab-undo-button" />
+ <xul:button id="newtab-undo-restore-button" tabindex="-1"
+ label="&newtab.undo.restoreButton;"
+ class="newtab-undo-button" />
+ <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1"
+ class="close-icon"
+ tooltiptext="&newtab.undo.closeTooltip;" />
+ </div>
+ </div>
+
+ <div id="newtab-horizontal-margin">
+ <div class="newtab-side-margin"/>
+
+ <div id="newtab-grid">
+ </div>
+
+ <div class="newtab-side-margin"/>
+ </div>
+
+ <div id="newtab-margin-bottom"/>
+ </div>
+ <input id="newtab-toggle" type="button"/>
+ </div>
+
+ <xul:script type="text/javascript;version=1.8"
+ src="chrome://browser/content/newtab/newTab.js"/>
+</xul:window>
diff --git a/application/palemoon/base/content/newtab/page.js b/application/palemoon/base/content/newtab/page.js
new file mode 100644
index 000000000..afe5bfba8
--- /dev/null
+++ b/application/palemoon/base/content/newtab/page.js
@@ -0,0 +1,135 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton represents the whole 'New Tab Page' and takes care of
+ * initializing all its components.
+ */
+let gPage = {
+ /**
+ * Initializes the page.
+ */
+ init: function Page_init() {
+ // Add ourselves to the list of pages to receive notifications.
+ gAllPages.register(this);
+
+ // Listen for 'unload' to unregister this page.
+ addEventListener("unload", this, false);
+
+ // Listen for toggle button clicks.
+ let button = document.getElementById("newtab-toggle");
+ button.addEventListener("click", this, false);
+
+ // Check if the new tab feature is enabled.
+ let enabled = gAllPages.enabled;
+ if (enabled)
+ this._init();
+
+ this._updateAttributes(enabled);
+ },
+
+ /**
+ * Listens for notifications specific to this page.
+ */
+ observe: function Page_observe() {
+ let enabled = gAllPages.enabled;
+ this._updateAttributes(enabled);
+
+ // Initialize the whole page if we haven't done that, yet.
+ if (enabled) {
+ this._init();
+ } else {
+ gUndoDialog.hide();
+ }
+ },
+
+ /**
+ * Updates the whole page and the grid when the storage has changed.
+ */
+ update: function Page_update() {
+ // The grid might not be ready yet as we initialize it asynchronously.
+ if (gGrid.ready) {
+ gGrid.refresh();
+ }
+ },
+
+ /**
+ * Internally initializes the page. This runs only when/if the feature
+ * is/gets enabled.
+ */
+ _init: function Page_init() {
+ if (this._initialized)
+ return;
+
+ this._initialized = true;
+
+ gLinks.populateCache(function () {
+ // Initialize and render the grid.
+ gGrid.init();
+
+ // Initialize the drop target shim.
+ gDropTargetShim.init();
+
+#ifdef XP_MACOSX
+ // Workaround to prevent a delay on MacOSX due to a slow drop animation.
+ document.addEventListener("dragover", this, false);
+ document.addEventListener("drop", this, false);
+#endif
+ }.bind(this));
+ },
+
+ /**
+ * Updates the 'page-disabled' attributes of the respective DOM nodes.
+ * @param aValue Whether the New Tab Page is enabled or not.
+ */
+ _updateAttributes: function Page_updateAttributes(aValue) {
+ // Set the nodes' states.
+ let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid";
+ for (let node of document.querySelectorAll(nodeSelector)) {
+ if (aValue)
+ node.removeAttribute("page-disabled");
+ else
+ node.setAttribute("page-disabled", "true");
+ }
+
+ // Enables/disables the control and link elements.
+ let inputSelector = ".newtab-control, .newtab-link";
+ for (let input of document.querySelectorAll(inputSelector)) {
+ if (aValue)
+ input.removeAttribute("tabindex");
+ else
+ input.setAttribute("tabindex", "-1");
+ }
+
+ // Update the toggle button's title.
+ let toggle = document.getElementById("newtab-toggle");
+ toggle.setAttribute("title", newTabString(aValue ? "hide" : "show"));
+ },
+
+ /**
+ * Handles all page events.
+ */
+ handleEvent: function Page_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ gAllPages.unregister(this);
+ break;
+ case "click":
+ gAllPages.enabled = !gAllPages.enabled;
+ break;
+ case "dragover":
+ if (gDrag.isValid(aEvent) && gDrag.draggedSite)
+ aEvent.preventDefault();
+ break;
+ case "drop":
+ if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ break;
+ }
+ }
+};
diff --git a/application/palemoon/base/content/newtab/sites.js b/application/palemoon/base/content/newtab/sites.js
new file mode 100644
index 000000000..873ef201c
--- /dev/null
+++ b/application/palemoon/base/content/newtab/sites.js
@@ -0,0 +1,192 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This class represents a site that is contained in a cell and can be pinned,
+ * moved around or deleted.
+ */
+function Site(aNode, aLink) {
+ this._node = aNode;
+ this._node._newtabSite = this;
+
+ this._link = aLink;
+
+ this._render();
+ this._addEventHandlers();
+}
+
+Site.prototype = {
+ /**
+ * The site's DOM node.
+ */
+ get node() { return this._node; },
+
+ /**
+ * The site's link.
+ */
+ get link() { return this._link; },
+
+ /**
+ * The url of the site's link.
+ */
+ get url() { return this.link.url; },
+
+ /**
+ * The title of the site's link.
+ */
+ get title() { return this.link.title; },
+
+ /**
+ * The site's parent cell.
+ */
+ get cell() {
+ let parentNode = this.node.parentNode;
+ return parentNode && parentNode._newtabCell;
+ },
+
+ /**
+ * Pins the site on its current or a given index.
+ * @param aIndex The pinned index (optional).
+ */
+ pin: function Site_pin(aIndex) {
+ if (typeof aIndex == "undefined")
+ aIndex = this.cell.index;
+
+ this._updateAttributes(true);
+ gPinnedLinks.pin(this._link, aIndex);
+ },
+
+ /**
+ * Unpins the site and calls the given callback when done.
+ */
+ unpin: function Site_unpin() {
+ if (this.isPinned()) {
+ this._updateAttributes(false);
+ gPinnedLinks.unpin(this._link);
+ gUpdater.updateGrid();
+ }
+ },
+
+ /**
+ * Checks whether this site is pinned.
+ * @return Whether this site is pinned.
+ */
+ isPinned: function Site_isPinned() {
+ return gPinnedLinks.isPinned(this._link);
+ },
+
+ /**
+ * Blocks the site (removes it from the grid) and calls the given callback
+ * when done.
+ */
+ block: function Site_block() {
+ if (!gBlockedLinks.isBlocked(this._link)) {
+ gUndoDialog.show(this);
+ gBlockedLinks.block(this._link);
+ gUpdater.updateGrid();
+ }
+ },
+
+ /**
+ * Gets the DOM node specified by the given query selector.
+ * @param aSelector The query selector.
+ * @return The DOM node we found.
+ */
+ _querySelector: function Site_querySelector(aSelector) {
+ return this.node.querySelector(aSelector);
+ },
+
+ /**
+ * Updates attributes for all nodes which status depends on this site being
+ * pinned or unpinned.
+ * @param aPinned Whether this site is now pinned or unpinned.
+ */
+ _updateAttributes: function (aPinned) {
+ let control = this._querySelector(".newtab-control-pin");
+
+ if (aPinned) {
+ control.setAttribute("pinned", true);
+ control.setAttribute("title", newTabString("unpin"));
+ } else {
+ control.removeAttribute("pinned");
+ control.setAttribute("title", newTabString("pin"));
+ }
+ },
+
+ /**
+ * Renders the site's data (fills the HTML fragment).
+ */
+ _render: function Site_render() {
+ let url = this.url;
+ let title = this.title || url;
+ let tooltip = (title == url ? title : title + "\n" + url);
+
+ let link = this._querySelector(".newtab-link");
+ link.setAttribute("title", tooltip);
+ link.setAttribute("href", url);
+ this._querySelector(".newtab-title").textContent = title;
+
+ if (this.isPinned())
+ this._updateAttributes(true);
+
+ let thumbnailURL = PageThumbs.getThumbnailURL(this.url);
+ let thumbnail = this._querySelector(".newtab-thumbnail");
+ thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")";
+ },
+
+ /**
+ * Adds event handlers for the site and its buttons.
+ */
+ _addEventHandlers: function Site_addEventHandlers() {
+ // Register drag-and-drop event handlers.
+ this._node.addEventListener("dragstart", this, false);
+ this._node.addEventListener("dragend", this, false);
+ this._node.addEventListener("mouseover", this, false);
+
+ let controls = this.node.querySelectorAll(".newtab-control");
+ for (let i = 0; i < controls.length; i++)
+ controls[i].addEventListener("click", this, false);
+ },
+
+ /**
+ * Speculatively opens a connection to the current site.
+ */
+ _speculativeConnect: function Site_speculativeConnect() {
+ let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
+ let uri = Services.io.newURI(this.url, null, null);
+ sc.speculativeConnect(uri, null);
+ },
+
+ /**
+ * Handles all site events.
+ */
+ handleEvent: function Site_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "click":
+ aEvent.preventDefault();
+ if (aEvent.target.classList.contains("newtab-control-block"))
+ this.block();
+ else if (this.isPinned())
+ this.unpin();
+ else
+ this.pin();
+ break;
+ case "mouseover":
+ this._node.removeEventListener("mouseover", this, false);
+ this._speculativeConnect();
+ break;
+ case "dragstart":
+ gDrag.start(this, aEvent);
+ break;
+ case "drag":
+ gDrag.drag(this, aEvent);
+ break;
+ case "dragend":
+ gDrag.end(this, aEvent);
+ break;
+ }
+ }
+};
diff --git a/application/palemoon/base/content/newtab/transformations.js b/application/palemoon/base/content/newtab/transformations.js
new file mode 100644
index 000000000..6d1554f5f
--- /dev/null
+++ b/application/palemoon/base/content/newtab/transformations.js
@@ -0,0 +1,265 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton allows to transform the grid by repositioning a site's node
+ * in the DOM and by showing or hiding the node. It additionally provides
+ * convenience methods to work with a site's DOM node.
+ */
+let gTransformation = {
+ /**
+ * Returns the width of the left and top border of a cell. We need to take it
+ * into account when measuring and comparing site and cell positions.
+ */
+ get _cellBorderWidths() {
+ let cstyle = window.getComputedStyle(gGrid.cells[0].node, null);
+ let widths = {
+ left: parseInt(cstyle.getPropertyValue("border-left-width")),
+ top: parseInt(cstyle.getPropertyValue("border-top-width"))
+ };
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "_cellBorderWidths",
+ {value: widths, enumerable: true});
+
+ return widths;
+ },
+
+ /**
+ * Gets a DOM node's position.
+ * @param aNode The DOM node.
+ * @return A Rect instance with the position.
+ */
+ getNodePosition: function Transformation_getNodePosition(aNode) {
+ let {left, top, width, height} = aNode.getBoundingClientRect();
+ return new Rect(left + scrollX, top + scrollY, width, height);
+ },
+
+ /**
+ * Fades a given node from zero to full opacity.
+ * @param aNode The node to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ fadeNodeIn: function Transformation_fadeNodeIn(aNode, aCallback) {
+ this._setNodeOpacity(aNode, 1, function () {
+ // Clear the style property.
+ aNode.style.opacity = "";
+
+ if (aCallback)
+ aCallback();
+ });
+ },
+
+ /**
+ * Fades a given node from full to zero opacity.
+ * @param aNode The node to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ fadeNodeOut: function Transformation_fadeNodeOut(aNode, aCallback) {
+ this._setNodeOpacity(aNode, 0, aCallback);
+ },
+
+ /**
+ * Fades a given site from zero to full opacity.
+ * @param aSite The site to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ showSite: function Transformation_showSite(aSite, aCallback) {
+ this.fadeNodeIn(aSite.node, aCallback);
+ },
+
+ /**
+ * Fades a given site from full to zero opacity.
+ * @param aSite The site to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ hideSite: function Transformation_hideSite(aSite, aCallback) {
+ this.fadeNodeOut(aSite.node, aCallback);
+ },
+
+ /**
+ * Allows to set a site's position.
+ * @param aSite The site to re-position.
+ * @param aPosition The desired position for the given site.
+ */
+ setSitePosition: function Transformation_setSitePosition(aSite, aPosition) {
+ let style = aSite.node.style;
+ let {top, left} = aPosition;
+
+ style.top = top + "px";
+ style.left = left + "px";
+ },
+
+ /**
+ * Freezes a site in its current position by positioning it absolute.
+ * @param aSite The site to freeze.
+ */
+ freezeSitePosition: function Transformation_freezeSitePosition(aSite) {
+ if (this._isFrozen(aSite))
+ return;
+
+ let style = aSite.node.style;
+ let comp = getComputedStyle(aSite.node, null);
+ style.width = comp.getPropertyValue("width")
+ style.height = comp.getPropertyValue("height");
+
+ aSite.node.setAttribute("frozen", "true");
+ this.setSitePosition(aSite, this.getNodePosition(aSite.node));
+ },
+
+ /**
+ * Unfreezes a site by removing its absolute positioning.
+ * @param aSite The site to unfreeze.
+ */
+ unfreezeSitePosition: function Transformation_unfreezeSitePosition(aSite) {
+ if (!this._isFrozen(aSite))
+ return;
+
+ let style = aSite.node.style;
+ style.left = style.top = style.width = style.height = "";
+ aSite.node.removeAttribute("frozen");
+ },
+
+ /**
+ * Slides the given site to the target node's position.
+ * @param aSite The site to move.
+ * @param aTarget The slide target.
+ * @param aOptions Set of options (see below).
+ * unfreeze - unfreeze the site after sliding
+ * callback - the callback to call when finished
+ */
+ slideSiteTo: function Transformation_slideSiteTo(aSite, aTarget, aOptions) {
+ let currentPosition = this.getNodePosition(aSite.node);
+ let targetPosition = this.getNodePosition(aTarget.node)
+ let callback = aOptions && aOptions.callback;
+
+ let self = this;
+
+ function finish() {
+ if (aOptions && aOptions.unfreeze)
+ self.unfreezeSitePosition(aSite);
+
+ if (callback)
+ callback();
+ }
+
+ // We need to take the width of a cell's border into account.
+ targetPosition.left += this._cellBorderWidths.left;
+ targetPosition.top += this._cellBorderWidths.top;
+
+ // Nothing to do here if the positions already match.
+ if (currentPosition.left == targetPosition.left &&
+ currentPosition.top == targetPosition.top) {
+ finish();
+ } else {
+ this.setSitePosition(aSite, targetPosition);
+ this._whenTransitionEnded(aSite.node, finish);
+ }
+ },
+
+ /**
+ * Rearranges a given array of sites and moves them to their new positions or
+ * fades in/out new/removed sites.
+ * @param aSites An array of sites to rearrange.
+ * @param aOptions Set of options (see below).
+ * unfreeze - unfreeze the site after rearranging
+ * callback - the callback to call when finished
+ */
+ rearrangeSites: function Transformation_rearrangeSites(aSites, aOptions) {
+ let batch = [];
+ let cells = gGrid.cells;
+ let callback = aOptions && aOptions.callback;
+ let unfreeze = aOptions && aOptions.unfreeze;
+
+ aSites.forEach(function (aSite, aIndex) {
+ // Do not re-arrange empty cells or the dragged site.
+ if (!aSite || aSite == gDrag.draggedSite)
+ return;
+
+ let deferred = Promise.defer();
+ batch.push(deferred.promise);
+ let cb = function () deferred.resolve();
+
+ if (!cells[aIndex])
+ // The site disappeared from the grid, hide it.
+ this.hideSite(aSite, cb);
+ else if (this._getNodeOpacity(aSite.node) != 1)
+ // The site disappeared before but is now back, show it.
+ this.showSite(aSite, cb);
+ else
+ // The site's position has changed, move it around.
+ this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
+ }, this);
+
+ let wait = Promise.promised(function () callback && callback());
+ wait.apply(null, batch);
+ },
+
+ /**
+ * Listens for the 'transitionend' event on a given node and calls the given
+ * callback.
+ * @param aNode The node that is transitioned.
+ * @param aCallback The callback to call when finished.
+ */
+ _whenTransitionEnded:
+ function Transformation_whenTransitionEnded(aNode, aCallback) {
+
+ aNode.addEventListener("transitionend", function onEnd() {
+ aNode.removeEventListener("transitionend", onEnd, false);
+ aCallback();
+ }, false);
+ },
+
+ /**
+ * Gets a given node's opacity value.
+ * @param aNode The node to get the opacity value from.
+ * @return The node's opacity value.
+ */
+ _getNodeOpacity: function Transformation_getNodeOpacity(aNode) {
+ let cstyle = window.getComputedStyle(aNode, null);
+ return cstyle.getPropertyValue("opacity");
+ },
+
+ /**
+ * Sets a given node's opacity.
+ * @param aNode The node to set the opacity value for.
+ * @param aOpacity The opacity value to set.
+ * @param aCallback The callback to call when finished.
+ */
+ _setNodeOpacity:
+ function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) {
+
+ if (this._getNodeOpacity(aNode) == aOpacity) {
+ if (aCallback)
+ aCallback();
+ } else {
+ if (aCallback)
+ this._whenTransitionEnded(aNode, aCallback);
+
+ aNode.style.opacity = aOpacity;
+ }
+ },
+
+ /**
+ * Moves a site to the cell with the given index.
+ * @param aSite The site to move.
+ * @param aIndex The target cell's index.
+ * @param aOptions Options that are directly passed to slideSiteTo().
+ */
+ _moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) {
+ this.freezeSitePosition(aSite);
+ this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
+ },
+
+ /**
+ * Checks whether a site is currently frozen.
+ * @param aSite The site to check.
+ * @return Whether the given site is frozen.
+ */
+ _isFrozen: function Transformation_isFrozen(aSite) {
+ return aSite.node.hasAttribute("frozen");
+ }
+};
diff --git a/application/palemoon/base/content/newtab/undo.js b/application/palemoon/base/content/newtab/undo.js
new file mode 100644
index 000000000..5f619e980
--- /dev/null
+++ b/application/palemoon/base/content/newtab/undo.js
@@ -0,0 +1,116 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * Dialog allowing to undo the removal of single site or to completely restore
+ * the grid's original state.
+ */
+let gUndoDialog = {
+ /**
+ * The undo dialog's timeout in miliseconds.
+ */
+ HIDE_TIMEOUT_MS: 15000,
+
+ /**
+ * Contains undo information.
+ */
+ _undoData: null,
+
+ /**
+ * Initializes the undo dialog.
+ */
+ init: function UndoDialog_init() {
+ this._undoContainer = document.getElementById("newtab-undo-container");
+ this._undoContainer.addEventListener("click", this, false);
+ this._undoButton = document.getElementById("newtab-undo-button");
+ this._undoCloseButton = document.getElementById("newtab-undo-close-button");
+ this._undoRestoreButton = document.getElementById("newtab-undo-restore-button");
+ },
+
+ /**
+ * Shows the undo dialog.
+ * @param aSite The site that just got removed.
+ */
+ show: function UndoDialog_show(aSite) {
+ if (this._undoData)
+ clearTimeout(this._undoData.timeout);
+
+ this._undoData = {
+ index: aSite.cell.index,
+ wasPinned: aSite.isPinned(),
+ blockedLink: aSite.link,
+ timeout: setTimeout(this.hide.bind(this), this.HIDE_TIMEOUT_MS)
+ };
+
+ this._undoContainer.removeAttribute("undo-disabled");
+ this._undoButton.removeAttribute("tabindex");
+ this._undoCloseButton.removeAttribute("tabindex");
+ this._undoRestoreButton.removeAttribute("tabindex");
+ },
+
+ /**
+ * Hides the undo dialog.
+ */
+ hide: function UndoDialog_hide() {
+ if (!this._undoData)
+ return;
+
+ clearTimeout(this._undoData.timeout);
+ this._undoData = null;
+ this._undoContainer.setAttribute("undo-disabled", "true");
+ this._undoButton.setAttribute("tabindex", "-1");
+ this._undoCloseButton.setAttribute("tabindex", "-1");
+ this._undoRestoreButton.setAttribute("tabindex", "-1");
+ },
+
+ /**
+ * The undo dialog event handler.
+ * @param aEvent The event to handle.
+ */
+ handleEvent: function UndoDialog_handleEvent(aEvent) {
+ switch (aEvent.target.id) {
+ case "newtab-undo-button":
+ this._undo();
+ break;
+ case "newtab-undo-restore-button":
+ this._undoAll();
+ break;
+ case "newtab-undo-close-button":
+ this.hide();
+ break;
+ }
+ },
+
+ /**
+ * Undo the last blocked site.
+ */
+ _undo: function UndoDialog_undo() {
+ if (!this._undoData)
+ return;
+
+ let {index, wasPinned, blockedLink} = this._undoData;
+ gBlockedLinks.unblock(blockedLink);
+
+ if (wasPinned) {
+ gPinnedLinks.pin(blockedLink, index);
+ }
+
+ gUpdater.updateGrid();
+ this.hide();
+ },
+
+ /**
+ * Undo all blocked sites.
+ */
+ _undoAll: function UndoDialog_undoAll() {
+ NewTabUtils.undoAll(function() {
+ gUpdater.updateGrid();
+ this.hide();
+ }.bind(this));
+ }
+};
+
+gUndoDialog.init();
diff --git a/application/palemoon/base/content/newtab/updater.js b/application/palemoon/base/content/newtab/updater.js
new file mode 100644
index 000000000..7b483e037
--- /dev/null
+++ b/application/palemoon/base/content/newtab/updater.js
@@ -0,0 +1,186 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides functionality to update the current grid to a new
+ * set of pinned and blocked sites. It adds, moves and removes sites.
+ */
+let gUpdater = {
+ /**
+ * Updates the current grid according to its pinned and blocked sites.
+ * This removes old, moves existing and creates new sites to fill gaps.
+ * @param aCallback The callback to call when finished.
+ */
+ updateGrid: function Updater_updateGrid(aCallback) {
+ let links = gLinks.getLinks().slice(0, gGrid.cells.length);
+
+ // Find all sites that remain in the grid.
+ let sites = this._findRemainingSites(links);
+
+ let self = this;
+
+ // Remove sites that are no longer in the grid.
+ this._removeLegacySites(sites, function () {
+ // Freeze all site positions so that we can move their DOM nodes around
+ // without any visual impact.
+ self._freezeSitePositions(sites);
+
+ // Move the sites' DOM nodes to their new position in the DOM. This will
+ // have no visual effect as all the sites have been frozen and will
+ // remain in their current position.
+ self._moveSiteNodes(sites);
+
+ // Now it's time to animate the sites actually moving to their new
+ // positions.
+ self._rearrangeSites(sites, function () {
+ // Try to fill empty cells and finish.
+ self._fillEmptyCells(links, aCallback);
+
+ // Update other pages that might be open to keep them synced.
+ gAllPages.update(gPage);
+ });
+ });
+ },
+
+ /**
+ * Takes an array of links and tries to correlate them to sites contained in
+ * the current grid. If no corresponding site can be found (i.e. the link is
+ * new and a site will be created) then just set it to null.
+ * @param aLinks The array of links to find sites for.
+ * @return Array of sites mapped to the given links (can contain null values).
+ */
+ _findRemainingSites: function Updater_findRemainingSites(aLinks) {
+ let map = {};
+
+ // Create a map to easily retrieve the site for a given URL.
+ gGrid.sites.forEach(function (aSite) {
+ if (aSite)
+ map[aSite.url] = aSite;
+ });
+
+ // Map each link to its corresponding site, if any.
+ return aLinks.map(function (aLink) {
+ return aLink && (aLink.url in map) && map[aLink.url];
+ });
+ },
+
+ /**
+ * Freezes the given sites' positions.
+ * @param aSites The array of sites to freeze.
+ */
+ _freezeSitePositions: function Updater_freezeSitePositions(aSites) {
+ aSites.forEach(function (aSite) {
+ if (aSite)
+ gTransformation.freezeSitePosition(aSite);
+ });
+ },
+
+ /**
+ * Moves the given sites' DOM nodes to their new positions.
+ * @param aSites The array of sites to move.
+ */
+ _moveSiteNodes: function Updater_moveSiteNodes(aSites) {
+ let cells = gGrid.cells;
+
+ // Truncate the given array of sites to not have more sites than cells.
+ // This can happen when the user drags a bookmark (or any other new kind
+ // of link) onto the grid.
+ let sites = aSites.slice(0, cells.length);
+
+ sites.forEach(function (aSite, aIndex) {
+ let cell = cells[aIndex];
+ let cellSite = cell.site;
+
+ // The site's position didn't change.
+ if (!aSite || cellSite != aSite) {
+ let cellNode = cell.node;
+
+ // Empty the cell if necessary.
+ if (cellSite)
+ cellNode.removeChild(cellSite.node);
+
+ // Put the new site in place, if any.
+ if (aSite)
+ cellNode.appendChild(aSite.node);
+ }
+ }, this);
+ },
+
+ /**
+ * Rearranges the given sites and slides them to their new positions.
+ * @param aSites The array of sites to re-arrange.
+ * @param aCallback The callback to call when finished.
+ */
+ _rearrangeSites: function Updater_rearrangeSites(aSites, aCallback) {
+ let options = {callback: aCallback, unfreeze: true};
+ gTransformation.rearrangeSites(aSites, options);
+ },
+
+ /**
+ * Removes all sites from the grid that are not in the given links array or
+ * exceed the grid.
+ * @param aSites The array of sites remaining in the grid.
+ * @param aCallback The callback to call when finished.
+ */
+ _removeLegacySites: function Updater_removeLegacySites(aSites, aCallback) {
+ let batch = [];
+
+ // Delete sites that were removed from the grid.
+ gGrid.sites.forEach(function (aSite) {
+ // The site must be valid and not in the current grid.
+ if (!aSite || aSites.indexOf(aSite) != -1)
+ return;
+
+ let deferred = Promise.defer();
+ batch.push(deferred.promise);
+
+ // Fade out the to-be-removed site.
+ gTransformation.hideSite(aSite, function () {
+ let node = aSite.node;
+
+ // Remove the site from the DOM.
+ node.parentNode.removeChild(node);
+ deferred.resolve();
+ });
+ });
+
+ let wait = Promise.promised(aCallback);
+ wait.apply(null, batch);
+ },
+
+ /**
+ * Tries to fill empty cells with new links if available.
+ * @param aLinks The array of links.
+ * @param aCallback The callback to call when finished.
+ */
+ _fillEmptyCells: function Updater_fillEmptyCells(aLinks, aCallback) {
+ let {cells, sites} = gGrid;
+ let batch = [];
+
+ // Find empty cells and fill them.
+ sites.forEach(function (aSite, aIndex) {
+ if (aSite || !aLinks[aIndex])
+ return;
+
+ let deferred = Promise.defer();
+ batch.push(deferred.promise);
+
+ // Create the new site and fade it in.
+ let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
+
+ // Set the site's initial opacity to zero.
+ site.node.style.opacity = 0;
+
+ // Flush all style changes for the dynamically inserted site to make
+ // the fade-in transition work.
+ window.getComputedStyle(site.node).opacity;
+ gTransformation.showSite(site, function () deferred.resolve());
+ });
+
+ let wait = Promise.promised(aCallback);
+ wait.apply(null, batch);
+ }
+};
diff --git a/application/palemoon/base/content/nsContextMenu.js b/application/palemoon/base/content/nsContextMenu.js
new file mode 100644
index 000000000..8e6bc96c2
--- /dev/null
+++ b/application/palemoon/base/content/nsContextMenu.js
@@ -0,0 +1,1588 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+function nsContextMenu(aXulMenu, aIsShift) {
+ this.shouldDisplay = true;
+ this.initMenu(aXulMenu, aIsShift);
+}
+
+// Prototype for nsContextMenu "class."
+nsContextMenu.prototype = {
+ initMenu: function CM_initMenu(aXulMenu, aIsShift) {
+ // Get contextual info.
+ this.setTarget(document.popupNode, document.popupRangeParent,
+ document.popupRangeOffset);
+ if (!this.shouldDisplay)
+ return;
+
+ this.hasPageMenu = false;
+ if (!aIsShift) {
+ this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(this.target,
+ aXulMenu);
+ }
+
+ this.isFrameImage = document.getElementById("isFrameImage");
+ this.ellipsis = "\u2026";
+ try {
+ this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+ } catch (e) { }
+
+ this.isContentSelected = this.isContentSelection();
+ this.onPlainTextLink = false;
+
+ // Initialize (disable/remove) menu items.
+ this.initItems();
+ },
+
+ hiding: function CM_hiding() {
+ InlineSpellCheckerUI.clearSuggestionsFromMenu();
+ InlineSpellCheckerUI.clearDictionaryListFromMenu();
+ InlineSpellCheckerUI.uninit();
+ },
+
+ initItems: function CM_initItems() {
+ this.initPageMenuSeparator();
+ this.initOpenItems();
+ this.initNavigationItems();
+ this.initViewItems();
+ this.initMiscItems();
+ this.initSpellingItems();
+ this.initSaveItems();
+ this.initClipboardItems();
+ this.initMediaPlayerItems();
+ this.initLeaveDOMFullScreenItems();
+ this.initClickToPlayItems();
+ },
+
+ initPageMenuSeparator: function CM_initPageMenuSeparator() {
+ this.showItem("page-menu-separator", this.hasPageMenu);
+ },
+
+ initOpenItems: function CM_initOpenItems() {
+ var isMailtoInternal = false;
+ if (this.onMailtoLink) {
+ var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService).
+ getProtocolHandlerInfo("mailto");
+ isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
+ mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
+ (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
+ }
+
+ // Time to do some bad things and see if we've highlighted a URL that
+ // isn't actually linked.
+ if (this.isTextSelected && !this.onLink) {
+ // Ok, we have some text, let's figure out if it looks like a URL.
+ let selection = document.commandDispatcher.focusedWindow
+ .getSelection();
+ let linkText = selection.toString().trim();
+ let uri;
+ if (/^(?:https?|ftp):/i.test(linkText)) {
+ try {
+ uri = makeURI(linkText);
+ } catch (ex) {}
+ }
+ // Check if this could be a valid url, just missing the protocol.
+ else if (/^[-a-z\d\.]+\.[-a-z\d]{2,}[-_=~:#%&\?\w\/\.]*$/i.test(linkText)) {
+ let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
+ .getService(Ci.nsIURIFixup);
+ try {
+ uri = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE);
+ } catch (ex) {}
+ }
+
+ if (uri && uri.host) {
+ this.linkURI = uri;
+ this.linkURL = this.linkURI.spec;
+ this.onPlainTextLink = true;
+ }
+ }
+
+ var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
+ var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ this.showItem("context-openlink", shouldShow && !isWindowPrivate);
+ this.showItem("context-openlinkprivate", shouldShow);
+ this.showItem("context-openlinkintab", shouldShow);
+ this.showItem("context-openlinkincurrent", shouldShow);
+ this.showItem("context-sep-open", shouldShow);
+ },
+
+ initNavigationItems: function CM_initNavigationItems() {
+ var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
+ this.onCanvas || this.onVideo || this.onAudio ||
+ this.onTextInput);
+ this.showItem("context-back", shouldShow);
+ this.showItem("context-forward", shouldShow);
+
+ let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
+
+ let stopReloadItem = "";
+ if (shouldShow) {
+ stopReloadItem = stopped ? "reload" : "stop";
+ }
+
+ this.showItem("context-reload", stopReloadItem == "reload");
+ this.showItem("context-stop", stopReloadItem == "stop");
+ this.showItem("context-sep-stop", !!stopReloadItem);
+
+ // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
+ //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
+ },
+
+ initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
+ // only show the option if the user is in DOM fullscreen
+ var shouldShow = (this.target.ownerDocument.mozFullScreenElement != null);
+ this.showItem("context-leave-dom-fullscreen", shouldShow);
+
+ // Explicitly show if in DOM fullscreen, but do not hide it has already been shown
+ if (shouldShow)
+ this.showItem("context-media-sep-commands", true);
+ },
+
+ initSaveItems: function CM_initSaveItems() {
+ var shouldShow = !(this.onTextInput || this.onLink ||
+ this.isContentSelected || this.onImage ||
+ this.onCanvas || this.onVideo || this.onAudio);
+ this.showItem("context-savepage", shouldShow);
+ this.showItem("context-sendpage", shouldShow);
+
+ // Save+Send link depends on whether we're in a link, or selected text matches valid URL pattern.
+ this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink);
+ this.showItem("context-sendlink", this.onSaveableLink || this.onPlainTextLink);
+
+ // Save image depends on having loaded its content, video and audio don't.
+ this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
+ this.showItem("context-savevideo", this.onVideo);
+ this.showItem("context-saveaudio", this.onAudio);
+ this.showItem("context-video-saveimage", this.onVideo);
+ this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
+ this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
+ // Send media URL (but not for canvas, since it's a big data: URL)
+ this.showItem("context-sendimage", this.onImage);
+ this.showItem("context-sendvideo", this.onVideo);
+ this.showItem("context-sendaudio", this.onAudio);
+ this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
+ this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
+ },
+
+ initViewItems: function CM_initViewItems() {
+ // View source is always OK, unless in directory listing.
+ this.showItem("context-viewpartialsource-selection",
+ this.isContentSelected);
+ this.showItem("context-viewpartialsource-mathml",
+ this.onMathML && !this.isContentSelected);
+
+ var shouldShow = !(this.isContentSelected ||
+ this.onImage || this.onCanvas ||
+ this.onVideo || this.onAudio ||
+ this.onLink || this.onTextInput);
+ this.showItem("context-viewsource", shouldShow);
+ this.showItem("context-viewinfo", shouldShow);
+#ifdef MOZ_DEVTOOLS
+ var showInspect = gPrefService.getBoolPref("devtools.inspector.enabled");
+ this.showItem("inspect-separator", showInspect);
+ this.showItem("context-inspect", showInspect);
+#endif
+
+ this.showItem("context-sep-viewsource", shouldShow);
+
+ // Set as Desktop background depends on whether an image was clicked on,
+ // and only works if we have a shell service.
+ var haveSetDesktopBackground = false;
+#ifdef HAVE_SHELL_SERVICE
+ // Only enable Set as Desktop Background if we can get the shell service.
+ var shell = getShellService();
+ if (shell)
+ haveSetDesktopBackground = shell.canSetDesktopBackground;
+#endif
+ this.showItem("context-setDesktopBackground",
+ haveSetDesktopBackground && this.onLoadedImage);
+
+ if (haveSetDesktopBackground && this.onLoadedImage) {
+ document.getElementById("context-setDesktopBackground")
+ .disabled = this.disableSetDesktopBackground();
+ }
+
+ // Reload image depends on an image that's not fully loaded
+ this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
+
+ // View image depends on having an image that's not standalone
+ // (or is in a frame), or a canvas.
+ this.showItem("context-viewimage", (this.onImage &&
+ (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
+
+ // View video depends on not having a standalone video.
+ this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
+ this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL);
+
+ // View background image depends on whether there is one, but don't make
+ // background images of a stand-alone media document available.
+ this.showItem("context-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
+ this.showItem("context-sep-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
+ document.getElementById("context-viewbgimage")
+ .disabled = !this.hasBGImage;
+
+ this.showItem("context-viewimageinfo", this.onImage);
+ },
+
+ initMiscItems: function CM_initMiscItems() {
+ // Use "Bookmark This Link" if on a link.
+ this.showItem("context-bookmarkpage",
+ !(this.isContentSelected || this.onTextInput || this.onLink ||
+ this.onImage || this.onVideo || this.onAudio));
+ this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink) ||
+ this.onPlainTextLink);
+ this.showItem("context-keywordfield",
+ this.onTextInput && this.onKeywordField);
+ this.showItem("frame", this.inFrame);
+
+ let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
+ this.showItem("context-searchselect", showSearchSelect);
+ if (showSearchSelect) {
+ this.formatSearchContextItem();
+ }
+
+ // srcdoc cannot be opened separately due to concerns about web
+ // content with about:srcdoc in location bar masquerading as trusted
+ // chrome/addon content.
+ // No need to also test for this.inFrame as this is checked in the parent
+ // submenu.
+ this.showItem("context-showonlythisframe", !this.inSrcdocFrame);
+ this.showItem("context-openframeintab", !this.inSrcdocFrame);
+ this.showItem("context-openframe", !this.inSrcdocFrame);
+ this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
+ this.showItem("open-frame-sep", !this.inSrcdocFrame);
+
+ this.showItem("frame-sep", this.inFrame && this.isTextSelected);
+
+ // Hide menu entries for images, show otherwise
+ if (this.inFrame) {
+ if (mimeTypeIsTextBased(this.target.ownerDocument.contentType))
+ this.isFrameImage.removeAttribute('hidden');
+ else
+ this.isFrameImage.setAttribute('hidden', 'true');
+ }
+
+ // BiDi UI
+ this.showItem("context-sep-bidi", top.gBidiUI);
+ this.showItem("context-bidi-text-direction-toggle",
+ this.onTextInput && top.gBidiUI);
+ this.showItem("context-bidi-page-direction-toggle",
+ !this.onTextInput && top.gBidiUI);
+ },
+
+ initSpellingItems: function() {
+ var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
+ var onMisspelling = InlineSpellCheckerUI.overMisspelling;
+ var showUndo = canSpell && InlineSpellCheckerUI.canUndo();
+ this.showItem("spell-check-enabled", canSpell);
+ this.showItem("spell-separator", canSpell || this.onEditableArea);
+ document.getElementById("spell-check-enabled")
+ .setAttribute("checked", canSpell && InlineSpellCheckerUI.enabled);
+
+ this.showItem("spell-add-to-dictionary", onMisspelling);
+ this.showItem("spell-undo-add-to-dictionary", showUndo);
+
+ // suggestion list
+ this.showItem("spell-suggestions-separator", onMisspelling || showUndo);
+ if (onMisspelling) {
+ var suggestionsSeparator =
+ document.getElementById("spell-add-to-dictionary");
+ var numsug =
+ InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode,
+ suggestionsSeparator, 5);
+ this.showItem("spell-no-suggestions", numsug == 0);
+ }
+ else
+ this.showItem("spell-no-suggestions", false);
+
+ // dictionary list
+ this.showItem("spell-dictionaries", canSpell && InlineSpellCheckerUI.enabled);
+ if (canSpell) {
+ var dictMenu = document.getElementById("spell-dictionaries-menu");
+ var dictSep = document.getElementById("spell-language-separator");
+ InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
+ this.showItem("spell-add-dictionaries-main", false);
+ }
+ else if (this.onEditableArea) {
+ // when there is no spellchecker but we might be able to spellcheck
+ // add the add to dictionaries item. This will ensure that people
+ // with no dictionaries will be able to download them
+ this.showItem("spell-add-dictionaries-main", true);
+ }
+ else
+ this.showItem("spell-add-dictionaries-main", false);
+ },
+
+ initClipboardItems: function() {
+ // Copy depends on whether there is selected text.
+ // Enabling this context menu item is now done through the global
+ // command updating system
+ // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
+ goUpdateGlobalEditMenuItems();
+
+ this.showItem("context-undo", this.onTextInput);
+ this.showItem("context-sep-undo", this.onTextInput);
+ this.showItem("context-cut", this.onTextInput);
+ this.showItem("context-copy",
+ this.isContentSelected || this.onTextInput);
+ this.showItem("context-paste", this.onTextInput);
+ this.showItem("context-delete", this.onTextInput);
+ this.showItem("context-sep-paste", this.onTextInput);
+ this.showItem("context-selectall", !(this.onLink || this.onImage ||
+ this.onVideo || this.onAudio ||
+ this.inSyntheticDoc) ||
+ this.isDesignMode);
+ this.showItem("context-sep-selectall", this.isContentSelected );
+
+ // XXX dr
+ // ------
+ // nsDocumentViewer.cpp has code to determine whether we're
+ // on a link or an image. we really ought to be using that...
+
+ // Copy email link depends on whether we're on an email link.
+ this.showItem("context-copyemail", this.onMailtoLink);
+
+ // Copy link location depends on whether we're on a non-mailto link.
+ this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
+ this.showItem("context-sep-copylink", this.onLink &&
+ (this.onImage || this.onVideo || this.onAudio));
+
+#ifdef CONTEXT_COPY_IMAGE_CONTENTS
+ // Copy image contents depends on whether we're on an image.
+ this.showItem("context-copyimage-contents", this.onImage);
+#endif
+ // Copy image location depends on whether we're on an image.
+ this.showItem("context-copyimage", this.onImage);
+ this.showItem("context-copyvideourl", this.onVideo);
+ this.showItem("context-copyaudiourl", this.onAudio);
+ this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL);
+ this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL);
+ this.showItem("context-sep-copyimage", this.onImage ||
+ this.onVideo || this.onAudio);
+ },
+
+ initMediaPlayerItems: function() {
+ var onMedia = (this.onVideo || this.onAudio);
+ // Several mutually exclusive items... play/pause, mute/unmute, show/hide
+ this.showItem("context-media-play", onMedia && (this.target.paused || this.target.ended));
+ this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
+ this.showItem("context-media-mute", onMedia && !this.target.muted);
+ this.showItem("context-media-unmute", onMedia && this.target.muted);
+ this.showItem("context-media-playbackrate", onMedia);
+ this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
+ this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
+ this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
+ var statsShowing = this.onVideo && XPCNativeWrapper.unwrap(this.target).mozMediaStatisticsShowing;
+ this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
+ this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
+
+ // Disable them when there isn't a valid media source loaded.
+ if (onMedia) {
+ this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
+ this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
+ this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
+ this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
+ var hasError = this.target.error != null ||
+ this.target.networkState == this.target.NETWORK_NO_SOURCE;
+ this.setItemAttr("context-media-play", "disabled", hasError);
+ this.setItemAttr("context-media-pause", "disabled", hasError);
+ this.setItemAttr("context-media-mute", "disabled", hasError);
+ this.setItemAttr("context-media-unmute", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
+ this.setItemAttr("context-media-showcontrols", "disabled", hasError);
+ this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
+ if (this.onVideo) {
+ let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
+ this.setItemAttr("context-video-saveimage", "disabled", !canSaveSnapshot);
+ this.setItemAttr("context-video-fullscreen", "disabled", hasError);
+ this.setItemAttr("context-video-showstats", "disabled", hasError);
+ this.setItemAttr("context-video-hidestats", "disabled", hasError);
+ }
+ }
+ this.showItem("context-media-sep-commands", onMedia);
+ },
+
+ initClickToPlayItems: function() {
+ this.showItem("context-ctp-play", this.onCTPPlugin);
+ this.showItem("context-ctp-hide", this.onCTPPlugin);
+ this.showItem("context-sep-ctp", this.onCTPPlugin);
+ },
+
+ inspectNode: function CM_inspectNode() {
+ let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
+ let tt = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+ return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) {
+ let inspector = toolbox.getCurrentPanel();
+ inspector.selection.setNode(this.target, "browser-context-menu");
+ }.bind(this));
+ },
+
+ // Set various context menu attributes based on the state of the world.
+ setTarget: function (aNode, aRangeParent, aRangeOffset) {
+ const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ if (aNode.namespaceURI == xulNS ||
+ aNode.nodeType == Node.DOCUMENT_NODE ||
+ this.isDisabledForEvents(aNode)) {
+ this.shouldDisplay = false;
+ return;
+ }
+
+ // Initialize contextual info.
+ this.onImage = false;
+ this.onLoadedImage = false;
+ this.onCompletedImage = false;
+ this.onCanvas = false;
+ this.onVideo = false;
+ this.onAudio = false;
+ this.onTextInput = false;
+ this.onKeywordField = false;
+ this.mediaURL = "";
+ this.onLink = false;
+ this.onMailtoLink = false;
+ this.onSaveableLink = false;
+ this.link = null;
+ this.linkURL = "";
+ this.linkURI = null;
+ this.linkProtocol = "";
+ this.linkDownload = "";
+ this.onMathML = false;
+ this.inFrame = false;
+ this.inSrcdocFrame = false;
+ this.inSyntheticDoc = false;
+ this.hasBGImage = false;
+ this.bgImageURL = "";
+ this.onEditableArea = false;
+ this.isDesignMode = false;
+ this.onCTPPlugin = false;
+ this.canSpellCheck = false;
+ this.textSelected = getBrowserSelection();
+ this.isTextSelected = this.textSelected.length != 0;
+
+ // Remember the node that was clicked.
+ this.target = aNode;
+
+ this.browser = this.target.ownerDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+
+ // Check if we are in a synthetic document (stand alone image, video, etc.).
+ this.inSyntheticDoc = this.target.ownerDocument.mozSyntheticDocument;
+ // First, do checks for nodes that never have children.
+ if (this.target.nodeType == Node.ELEMENT_NODE) {
+ // See if the user clicked on an image.
+ if (this.target instanceof Ci.nsIImageLoadingContent &&
+ this.target.currentURI) {
+ this.onImage = true;
+
+ var request =
+ this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
+ this.onLoadedImage = true;
+ if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE))
+ this.onCompletedImage = true;
+
+ this.mediaURL = this.target.currentURI.spec;
+ }
+ else if (this.target instanceof HTMLCanvasElement) {
+ this.onCanvas = true;
+ }
+ else if (this.target instanceof HTMLVideoElement) {
+ this.mediaURL = this.target.currentSrc || this.target.src;
+ // Firefox always creates a HTMLVideoElement when loading an ogg file
+ // directly. If the media is actually audio, be smarter and provide a
+ // context menu with audio operations.
+ if (this.target.readyState >= this.target.HAVE_METADATA &&
+ (this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
+ this.onAudio = true;
+ } else {
+ this.onVideo = true;
+ }
+ }
+ else if (this.target instanceof HTMLAudioElement) {
+ this.onAudio = true;
+ this.mediaURL = this.target.currentSrc || this.target.src;
+ }
+ else if (this.target instanceof HTMLInputElement ) {
+ this.onTextInput = this.isTargetATextBox(this.target);
+ // Allow spellchecking UI on all text and search inputs.
+ if (this.onTextInput && ! this.target.readOnly &&
+ (this.target.type == "text" || this.target.type == "search")) {
+ this.onEditableArea = true;
+ InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ }
+ this.onKeywordField = this.isTargetAKeywordField(this.target);
+ }
+ else if (this.target instanceof HTMLTextAreaElement) {
+ this.onTextInput = true;
+ if (!this.target.readOnly) {
+ this.onEditableArea = true;
+ InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ }
+ }
+ else if (this.target instanceof HTMLHtmlElement) {
+ var bodyElt = this.target.ownerDocument.body;
+ if (bodyElt) {
+ let computedURL;
+ try {
+ computedURL = this.getComputedURL(bodyElt, "background-image");
+ this._hasMultipleBGImages = false;
+ } catch (e) {
+ this._hasMultipleBGImages = true;
+ }
+ if (computedURL) {
+ this.hasBGImage = true;
+ this.bgImageURL = makeURLAbsolute(bodyElt.baseURI,
+ computedURL);
+ }
+ }
+ }
+ else if ((this.target instanceof HTMLEmbedElement ||
+ this.target instanceof HTMLObjectElement ||
+ this.target instanceof HTMLAppletElement) &&
+ this.target.displayedType == HTMLObjectElement.TYPE_NULL &&
+ this.target.pluginFallbackType == HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
+ this.onCTPPlugin = true;
+ }
+
+ this.canSpellCheck = this._isSpellCheckEnabled(this.target);
+ }
+ else if (this.target.nodeType == Node.TEXT_NODE) {
+ // For text nodes, look at the parent node to determine the spellcheck attribute.
+ this.canSpellCheck = this.target.parentNode &&
+ this._isSpellCheckEnabled(this.target);
+ }
+
+ // Second, bubble out, looking for items of interest that can have childen.
+ // Always pick the innermost link, background image, etc.
+ const XMLNS = "http://www.w3.org/XML/1998/namespace";
+ var elem = this.target;
+ while (elem) {
+ if (elem.nodeType == Node.ELEMENT_NODE) {
+ // Link?
+ if (!this.onLink &&
+ // Be consistent with what hrefAndLinkNodeForClickEvent
+ // does in browser.js
+ ((elem instanceof HTMLAnchorElement && elem.href) ||
+ (elem instanceof HTMLAreaElement && elem.href) ||
+ elem instanceof HTMLLinkElement ||
+ elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
+
+ // Target is a link or a descendant of a link.
+ this.onLink = true;
+
+ // Remember corresponding element.
+ this.link = elem;
+ this.linkURL = this.getLinkURL();
+ this.linkURI = this.getLinkURI();
+ this.linkProtocol = this.getLinkProtocol();
+ this.onMailtoLink = (this.linkProtocol == "mailto");
+ this.onSaveableLink = this.isLinkSaveable( this.link );
+ try {
+ if (elem.download) {
+ // Ignore download attribute on cross-origin links?
+ // This shoudn't be an issue because the download link presents
+ // the originating URL domain and protocol to help user understand
+ // from where file is downloaded and make right decision.
+ // If we decide we want this restriction:
+ // this.principal.checkMayLoad(this.linkURI, false, true);
+ this.linkDownload = elem.download;
+ }
+ }
+ catch (ex) {}
+ }
+
+ // Background image? Don't bother if we've already found a
+ // background image further down the hierarchy. Otherwise,
+ // we look for the computed background-image style.
+ if (!this.hasBGImage &&
+ !this._hasMultipleBGImages) {
+ let bgImgUrl;
+ try {
+ bgImgUrl = this.getComputedURL(elem, "background-image");
+ this._hasMultipleBGImages = false;
+ } catch (e) {
+ this._hasMultipleBGImages = true;
+ }
+ if (bgImgUrl) {
+ this.hasBGImage = true;
+ this.bgImageURL = makeURLAbsolute(elem.baseURI,
+ bgImgUrl);
+ }
+ }
+ }
+
+ elem = elem.parentNode;
+ }
+
+ // See if the user clicked on MathML
+ const NS_MathML = "http://www.w3.org/1998/Math/MathML";
+ if ((this.target.nodeType == Node.TEXT_NODE &&
+ this.target.parentNode.namespaceURI == NS_MathML)
+ || (this.target.namespaceURI == NS_MathML))
+ this.onMathML = true;
+
+ // See if the user clicked in a frame.
+ var docDefaultView = this.target.ownerDocument.defaultView;
+ if (docDefaultView != docDefaultView.top) {
+ this.inFrame = true;
+
+ if (this.target.ownerDocument.isSrcdocDocument) {
+ this.inSrcdocFrame = true;
+ }
+ }
+
+ // if the document is editable, show context menu like in text inputs
+ if (!this.onEditableArea) {
+ var win = this.target.ownerDocument.defaultView;
+ if (win) {
+ var isEditable = false;
+ try {
+ var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ if (editingSession.windowIsEditable(win) &&
+ this.getComputedStyle(this.target, "-moz-user-modify") == "read-write") {
+ isEditable = true;
+ }
+ }
+ catch(ex) {
+ // If someone built with composer disabled, we can't get an editing session.
+ }
+
+ if (isEditable) {
+ this.onTextInput = true;
+ this.onKeywordField = false;
+ this.onImage = false;
+ this.onLoadedImage = false;
+ this.onCompletedImage = false;
+ this.onMathML = false;
+ this.inFrame = false;
+ this.inSrcdocFrame = false;
+ this.hasBGImage = false;
+ this.isDesignMode = true;
+ this.onEditableArea = true;
+ InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win));
+ var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ this.showItem("spell-check-enabled", canSpell);
+ this.showItem("spell-separator", canSpell);
+ }
+ }
+ }
+ },
+
+ // Returns the computed style attribute for the given element.
+ getComputedStyle: function(aElem, aProp) {
+ return aElem.ownerDocument
+ .defaultView
+ .getComputedStyle(aElem, "").getPropertyValue(aProp);
+ },
+
+ // Returns a "url"-type computed style attribute value, with the url() stripped.
+ getComputedURL: function(aElem, aProp) {
+ var url = aElem.ownerDocument
+ .defaultView.getComputedStyle(aElem, "")
+ .getPropertyCSSValue(aProp);
+ if (url instanceof CSSValueList) {
+ if (url.length != 1)
+ throw "found multiple URLs";
+ url = url[0];
+ }
+ return url.primitiveType == CSSPrimitiveValue.CSS_URI ?
+ url.getStringValue() : null;
+ },
+
+ // Returns true if clicked-on link targets a resource that can be saved.
+ isLinkSaveable: function(aLink) {
+ // We don't do the Right Thing for news/snews yet, so turn them off
+ // until we do.
+ return this.linkProtocol && !(
+ this.linkProtocol == "mailto" ||
+ this.linkProtocol == "javascript" ||
+ this.linkProtocol == "news" ||
+ this.linkProtocol == "snews" );
+ },
+
+ _isSpellCheckEnabled: function(aNode) {
+ // We can always force-enable spellchecking on textboxes
+ if (this.isTargetATextBox(aNode)) {
+ return true;
+ }
+ // We can never spell check something which is not content editable
+ var editable = aNode.isContentEditable;
+ if (!editable && aNode.ownerDocument) {
+ editable = aNode.ownerDocument.designMode == "on";
+ }
+ if (!editable) {
+ return false;
+ }
+ // Otherwise make sure that nothing in the parent chain disables spellchecking
+ return aNode.spellcheck;
+ },
+
+ // Open linked-to URL in a new window.
+ openLink : function () {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "window",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject });
+ },
+
+ // Open linked-to URL in a new private window.
+ openLinkInPrivateWindow : function () {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "window",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject,
+ private: true });
+ },
+
+ // Open linked-to URL in a new tab.
+ openLinkInTab: function() {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "tab",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject });
+ },
+
+ // open URL in current tab
+ openLinkInCurrent: function() {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "current",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject });
+ },
+
+ // Open frame in a new tab.
+ openFrameInTab: function() {
+ var doc = this.target.ownerDocument;
+ var frameURL = doc.location.href;
+ var referrer = doc.referrer;
+ openLinkIn(frameURL, "tab",
+ { charset: doc.characterSet,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ // Reload clicked-in frame.
+ reloadFrame: function() {
+ this.target.ownerDocument.location.reload();
+ },
+
+ // Open clicked-in frame in its own window.
+ openFrame: function() {
+ var doc = this.target.ownerDocument;
+ var frameURL = doc.location.href;
+ var referrer = doc.referrer;
+ openLinkIn(frameURL, "window",
+ { charset: doc.characterSet,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ // Open clicked-in frame in the same window.
+ showOnlyThisFrame: function() {
+ var doc = this.target.ownerDocument;
+ var frameURL = doc.location.href;
+
+ urlSecurityCheck(frameURL, this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ var referrer = doc.referrer;
+ openUILinkIn(frameURL, "current", { disallowInheritPrincipal: true,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ reload: function(event) {
+ BrowserReloadOrDuplicate(event);
+ },
+
+ // View Partial Source
+ viewPartialSource: function(aContext) {
+ var focusedWindow = document.commandDispatcher.focusedWindow;
+ if (focusedWindow == window)
+ focusedWindow = content;
+
+ var docCharset = null;
+ if (focusedWindow)
+ docCharset = "charset=" + focusedWindow.document.characterSet;
+
+ // "View Selection Source" and others such as "View MathML Source"
+ // are mutually exclusive, with the precedence given to the selection
+ // when there is one
+ var reference = null;
+ if (aContext == "selection")
+ reference = focusedWindow.getSelection();
+ else if (aContext == "mathml")
+ reference = this.target;
+ else
+ throw "not reached";
+
+ // unused (and play nice for fragments generated via XSLT too)
+ var docUrl = null;
+ window.openDialog("chrome://global/content/viewPartialSource.xul",
+ "_blank", "scrollbars,resizable,chrome,dialog=no",
+ docUrl, docCharset, reference, aContext);
+ },
+
+ // Open new "view source" window with the frame's URL.
+ viewFrameSource: function() {
+ BrowserViewSourceOfDocument(this.target.ownerDocument);
+ },
+
+ viewInfo: function() {
+ BrowserPageInfo(this.target.ownerDocument.defaultView.top.document);
+ },
+
+ viewImageInfo: function() {
+ BrowserPageInfo(this.target.ownerDocument.defaultView.top.document,
+ "mediaTab", this.target);
+ },
+
+ viewFrameInfo: function() {
+ BrowserPageInfo(this.target.ownerDocument);
+ },
+
+ reloadImage: function(e) {
+ urlSecurityCheck(this.mediaURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+
+ if (this.target instanceof Ci.nsIImageLoadingContent)
+ this.target.forceReload();
+ },
+
+ // Change current window to the URL of the image, video, or audio.
+ viewMedia: function(e) {
+ var viewURL;
+ var doc = this.target.ownerDocument;
+ if (this.onCanvas) {
+ var target = this.target;
+ var win = doc.defaultView;
+ if (!win) {
+ Components.utils.reportError(
+ "View Image (on the <canvas> element):\n" +
+ "This feature cannot be used, because it hasn't found " +
+ "an appropriate window.");
+ } else {
+ new Promise.resolve({then: function (resolve) {
+ target.toBlob((blob) => {
+ resolve(win.URL.createObjectURL(blob));
+ })
+ }}).then(function (blobURL) {
+ openUILink(blobURL, e, { disallowInheritPrincipal: true,
+ referrerURI: doc.documentURIObject });
+ }, Components.utils.reportError);
+ }
+ } else {
+ viewURL = this.mediaURL;
+ urlSecurityCheck(viewURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ let doc = this.target.ownerDocument;
+ openUILink(viewURL, e, { disallowInheritPrincipal: true,
+ referrerURI: doc.documentURIObject });
+ }
+ },
+
+ saveVideoFrameAsImage: function () {
+ urlSecurityCheck(this.mediaURL, this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ let name = "";
+ try {
+ let uri = makeURI(this.mediaURL);
+ let url = uri.QueryInterface(Ci.nsIURL);
+ if (url.fileBaseName)
+ name = decodeURI(url.fileBaseName) + ".jpg";
+ } catch (e) { }
+ if (!name)
+ name = "snapshot.jpg";
+ var video = this.target;
+ var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+ var ctxDraw = canvas.getContext("2d");
+ ctxDraw.drawImage(video, 0, 0);
+ saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject, this.target.ownerDocument);
+ },
+
+ fullScreenVideo: function () {
+ let video = this.target;
+ if (document.mozFullScreenEnabled)
+ video.mozRequestFullScreen();
+ },
+
+ leaveDOMFullScreen: function() {
+ document.mozCancelFullScreen();
+ },
+
+ // Change current window to the URL of the background image.
+ viewBGImage: function(e) {
+ urlSecurityCheck(this.bgImageURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ var doc = this.target.ownerDocument;
+ openUILink(this.bgImageURL, e, { disallowInheritPrincipal: true,
+ referrerURI: doc.documentURIObject });
+ },
+
+ disableSetDesktopBackground: function() {
+ // Disable the Set as Desktop Background menu item if we're still trying
+ // to load the image or the load failed.
+ if (!(this.target instanceof Ci.nsIImageLoadingContent))
+ return true;
+
+ if (("complete" in this.target) && !this.target.complete)
+ return true;
+
+ if (this.target.currentURI.schemeIs("javascript"))
+ return true;
+
+ var request = this.target
+ .QueryInterface(Ci.nsIImageLoadingContent)
+ .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (!request)
+ return true;
+
+ return false;
+ },
+
+ setDesktopBackground: function() {
+ // Paranoia: check disableSetDesktopBackground again, in case the
+ // image changed since the context menu was initiated.
+ if (this.disableSetDesktopBackground())
+ return;
+
+ urlSecurityCheck(this.target.currentURI.spec,
+ this.target.ownerDocument.nodePrincipal);
+
+ // Confirm since it's annoying if you hit this accidentally.
+ const kDesktopBackgroundURL =
+ "chrome://browser/content/setDesktopBackground.xul";
+#ifdef XP_MACOSX
+ // On Mac, the Set Desktop Background window is not modal.
+ // Don't open more than one Set Desktop Background window.
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
+ if (dbWin) {
+ dbWin.gSetBackground.init(this.target);
+ dbWin.focus();
+ }
+ else {
+ openDialog(kDesktopBackgroundURL, "",
+ "centerscreen,chrome,dialog=no,dependent,resizable=no",
+ this.target);
+ }
+#else
+ // On non-Mac platforms, the Set Wallpaper dialog is modal.
+ openDialog(kDesktopBackgroundURL, "",
+ "centerscreen,chrome,dialog,modal,dependent",
+ this.target);
+#endif
+ },
+
+ // Save URL of clicked-on frame.
+ saveFrame: function () {
+ saveDocument(this.target.ownerDocument);
+ },
+
+ // Helper function to wait for appropriate MIME-type headers and
+ // then prompt the user with a file picker
+ saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc,
+ linkDownload) {
+ // canonical def in nsURILoader.h
+ const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
+
+ // an object to proxy the data through to
+ // nsIExternalHelperAppService.doContent, which will wait for the
+ // appropriate MIME-type headers and then prompt the user with a
+ // file picker
+ function saveAsListener() {}
+ saveAsListener.prototype = {
+ extListener: null,
+
+ onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
+
+ // if the timer fired, the error status will have been caused by that,
+ // and we'll be restarting in onStopRequest, so no reason to notify
+ // the user
+ if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
+ return;
+
+ timer.cancel();
+
+ // some other error occured; notify the user...
+ if (!Components.isSuccessCode(aRequest.status)) {
+ try {
+ const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ const bundle = sbs.createBundle(
+ "chrome://mozapps/locale/downloads/downloads.properties");
+
+ const title = bundle.GetStringFromName("downloadErrorAlertTitle");
+ const msg = bundle.GetStringFromName("downloadErrorGeneric");
+
+ const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ promptSvc.alert(doc.defaultView, title, msg);
+ } catch (ex) {}
+ return;
+ }
+
+ var extHelperAppSvc =
+ Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+ getService(Ci.nsIExternalHelperAppService);
+ var channel = aRequest.QueryInterface(Ci.nsIChannel);
+ this.extListener =
+ extHelperAppSvc.doContent(channel.contentType, aRequest,
+ doc.defaultView, true);
+ this.extListener.onStartRequest(aRequest, aContext);
+ },
+
+ onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
+ aStatusCode) {
+ if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
+ // do it the old fashioned way, which will pick the best filename
+ // it can without waiting.
+ saveURL(linkURL, linkText, dialogTitle, bypassCache, false, doc.documentURIObject, doc);
+ }
+ if (this.extListener)
+ this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+ },
+
+ onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
+ aInputStream,
+ aOffset, aCount) {
+ this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ }
+ }
+
+ function callbacks() {}
+ callbacks.prototype = {
+ getInterface: function sLA_callbacks_getInterface(aIID) {
+ if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
+ // If the channel demands authentication prompt, we must cancel it
+ // because the save-as-timer would expire and cancel the channel
+ // before we get credentials from user. Both authentication dialog
+ // and save as dialog would appear on the screen as we fall back to
+ // the old fashioned way after the timeout.
+ timer.cancel();
+ channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ }
+
+ // if it we don't have the headers after a short time, the user
+ // won't have received any feedback from their click. that's bad. so
+ // we give up waiting for the filename.
+ function timerCallback() {}
+ timerCallback.prototype = {
+ notify: function sLA_timer_notify(aTimer) {
+ channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
+ return;
+ }
+ }
+
+ // set up a channel to do the saving
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var channel = ioService.newChannelFromURI(makeURI(linkURL));
+ if (linkDownload)
+ channel.contentDispositionFilename = linkDownload;
+ if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
+ let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
+ channel.setPrivate(docIsPrivate);
+ }
+ channel.notificationCallbacks = new callbacks();
+
+ let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ if (bypassCache)
+ flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+
+ if (channel instanceof Ci.nsICachingChannel)
+ flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
+
+ channel.loadFlags |= flags;
+
+ if (channel instanceof Ci.nsIHttpChannel) {
+ channel.referrer = doc.documentURIObject;
+ if (channel instanceof Ci.nsIHttpChannelInternal)
+ channel.forceAllowThirdPartyCookie = true;
+ }
+
+ // fallback to the old way if we don't see the headers quickly
+ var timeToWait =
+ gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(new timerCallback(), timeToWait,
+ timer.TYPE_ONE_SHOT);
+
+ // kick off the channel with our proxy object as the listener
+ channel.asyncOpen(new saveAsListener(), null);
+ },
+
+ // Save URL of clicked-on link.
+ saveLink: function() {
+ var doc = this.target.ownerDocument;
+ var linkText;
+ // If selected text is found to match valid URL pattern.
+ if (this.onPlainTextLink)
+ linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim();
+ else
+ linkText = this.linkText();
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+
+ this.saveHelper(this.linkURL, linkText, null, true, doc,
+ this.linkDownload);
+ },
+
+ sendLink: function() {
+ // we don't know the title of the link so pass in an empty string
+ MailIntegration.sendMessage( this.linkURL, "" );
+ },
+
+ // Backwards-compatibility wrapper
+ saveImage : function() {
+ if (this.onCanvas || this.onImage)
+ this.saveMedia();
+ },
+
+ // Save URL of the clicked upon image, video, or audio.
+ saveMedia: function() {
+ var doc = this.target.ownerDocument;
+ if (this.onCanvas) {
+ // Bypass cache, since it's a blob: URL.
+ var target = this.target;
+ var win = doc.defaultView;
+ if (!win) {
+ Components.utils.reportError(
+ "Save Image As (on the <canvas> element):\n" +
+ "This feature cannot be used, because it hasn't found " +
+ "an appropriate window.");
+ } else {
+ new Promise.resolve({then: function (resolve) {
+ target.toBlob((blob) => {
+ resolve(win.URL.createObjectURL(blob));
+ })
+ }}).then(function (blobURL) {
+ saveImageURL(blobURL, "canvas.png", "SaveImageTitle", true,
+ false, doc.documentURIObject, doc);
+ }, Components.utils.reportError);
+ }
+ } else if (this.onImage) {
+ urlSecurityCheck(this.mediaURL, doc.nodePrincipal);
+ saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
+ false, doc.documentURIObject, doc);
+ } else if (this.onVideo || this.onAudio) {
+ urlSecurityCheck(this.mediaURL, doc.nodePrincipal);
+ var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
+ this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, "");
+ }
+ },
+
+ // Backwards-compatibility wrapper
+ sendImage : function() {
+ if (this.onCanvas || this.onImage)
+ this.sendMedia();
+ },
+
+ sendMedia: function() {
+ MailIntegration.sendMessage(this.mediaURL, "");
+ },
+
+ playPlugin: function() {
+ gPluginHandler._showClickToPlayNotification(this.browser, this.target);
+ },
+
+ hidePlugin: function() {
+ gPluginHandler.hideClickToPlayOverlay(this.target);
+ },
+
+ // Generate email address and put it on clipboard.
+ copyEmail: function() {
+ // Copy the comma-separated list of email addresses only.
+ // There are other ways of embedding email addresses in a mailto:
+ // link, but such complex parsing is beyond us.
+ var url = this.linkURL;
+ var qmark = url.indexOf("?");
+ var addresses;
+
+ // 7 == length of "mailto:"
+ addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
+
+ // Let's try to unescape it using a character set
+ // in case the address is not ASCII.
+ try {
+ var characterSet = this.target.ownerDocument.characterSet;
+ const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
+ getService(Ci.nsITextToSubURI);
+ addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
+ }
+ catch(ex) {
+ // Do nothing.
+ }
+
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(addresses, document);
+ },
+
+ ///////////////
+ // Utilities //
+ ///////////////
+
+ // Show/hide one item (specified via name or the item element itself).
+ showItem: function(aItemOrId, aShow) {
+ var item = aItemOrId.constructor == String ?
+ document.getElementById(aItemOrId) : aItemOrId;
+ if (item)
+ item.hidden = !aShow;
+ },
+
+ // Set given attribute of specified context-menu item. If the
+ // value is null, then it removes the attribute (which works
+ // nicely for the disabled attribute).
+ setItemAttr: function(aID, aAttr, aVal ) {
+ var elem = document.getElementById(aID);
+ if (elem) {
+ if (aVal == null) {
+ // null indicates attr should be removed.
+ elem.removeAttribute(aAttr);
+ }
+ else {
+ // Set attr=val.
+ elem.setAttribute(aAttr, aVal);
+ }
+ }
+ },
+
+ // Set context menu attribute according to like attribute of another node
+ // (such as a broadcaster).
+ setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) {
+ var elem = document.getElementById(aOther_id);
+ if (elem && elem.getAttribute(aAttr) == "true")
+ this.setItemAttr(aItem_id, aAttr, "true");
+ else
+ this.setItemAttr(aItem_id, aAttr, null);
+ },
+
+ // Temporary workaround for DOM api not yet implemented by XUL nodes.
+ cloneNode: function(aItem) {
+ // Create another element like the one we're cloning.
+ var node = document.createElement(aItem.tagName);
+
+ // Copy attributes from argument item to the new one.
+ var attrs = aItem.attributes;
+ for (var i = 0; i < attrs.length; i++) {
+ var attr = attrs.item(i);
+ node.setAttribute(attr.nodeName, attr.nodeValue);
+ }
+
+ // Voila!
+ return node;
+ },
+
+ // Generate fully qualified URL for clicked-on link.
+ getLinkURL: function() {
+ var href = this.link.href;
+ if (href)
+ return href;
+
+ href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
+ "href");
+
+ if (!href || !href.match(/\S/)) {
+ // Without this we try to save as the current doc,
+ // for example, HTML case also throws if empty
+ throw "Empty href";
+ }
+
+ return makeURLAbsolute(this.link.baseURI, href);
+ },
+
+ getLinkURI: function() {
+ try {
+ return makeURI(this.linkURL);
+ }
+ catch (ex) {
+ // e.g. empty URL string
+ }
+
+ return null;
+ },
+
+ getLinkProtocol: function() {
+ if (this.linkURI)
+ return this.linkURI.scheme; // can be |undefined|
+
+ return null;
+ },
+
+ // Get text of link.
+ linkText: function() {
+ var text = gatherTextUnder(this.link);
+ if (!text || !text.match(/\S/)) {
+ text = this.link.getAttribute("title");
+ if (!text || !text.match(/\S/)) {
+ text = this.link.getAttribute("alt");
+ if (!text || !text.match(/\S/))
+ text = this.linkURL;
+ }
+ }
+
+ return text;
+ },
+
+ // Returns true if anything is selected.
+ isContentSelection: function() {
+ return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
+ },
+
+ toString: function () {
+ return "contextMenu.target = " + this.target + "\n" +
+ "contextMenu.onImage = " + this.onImage + "\n" +
+ "contextMenu.onLink = " + this.onLink + "\n" +
+ "contextMenu.link = " + this.link + "\n" +
+ "contextMenu.inFrame = " + this.inFrame + "\n" +
+ "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
+ },
+
+ isDisabledForEvents: function(aNode) {
+ let ownerDoc = aNode.ownerDocument;
+ return
+ ownerDoc.defaultView &&
+ ownerDoc.defaultView
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .isNodeDisabledForEvents(aNode);
+ },
+
+ isTargetATextBox: function(node) {
+ if (node instanceof HTMLInputElement)
+ return node.mozIsTextField(false);
+
+ return (node instanceof HTMLTextAreaElement);
+ },
+
+ isTargetAKeywordField: function(aNode) {
+ if (!(aNode instanceof HTMLInputElement))
+ return false;
+
+ var form = aNode.form;
+ if (!form || aNode.type == "password")
+ return false;
+
+ var method = form.method.toUpperCase();
+
+ // These are the following types of forms we can create keywords for:
+ //
+ // method encoding type can create keyword
+ // GET * YES
+ // * YES
+ // POST YES
+ // POST application/x-www-form-urlencoded YES
+ // POST text/plain NO (a little tricky to do)
+ // POST multipart/form-data NO
+ // POST everything else YES
+ return (method == "GET" || method == "") ||
+ (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
+ },
+
+ // Determines whether or not the separator with the specified ID should be
+ // shown or not by determining if there are any non-hidden items between it
+ // and the previous separator.
+ shouldShowSeparator: function (aSeparatorID) {
+ var separator = document.getElementById(aSeparatorID);
+ if (separator) {
+ var sibling = separator.previousSibling;
+ while (sibling && sibling.localName != "menuseparator") {
+ if (!sibling.hidden)
+ return true;
+ sibling = sibling.previousSibling;
+ }
+ }
+ return false;
+ },
+
+ addDictionaries: function() {
+ var uri = formatURL("browser.dictionaries.download.url", true);
+
+ var locale = "-";
+ try {
+ locale = gPrefService.getComplexValue("intl.accept_languages",
+ Ci.nsIPrefLocalizedString).data;
+ }
+ catch (e) { }
+
+ var version = "-";
+ try {
+ version = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo).version;
+ }
+ catch (e) { }
+
+ uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
+
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+
+ openUILinkIn(uri, where);
+ },
+
+ bookmarkThisPage: function CM_bookmarkThisPage() {
+ window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
+ },
+
+ bookmarkLink: function CM_bookmarkLink() {
+ var linkText;
+ // If selected text is found to match valid URL pattern.
+ if (this.onPlainTextLink)
+ linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim();
+ else
+ linkText = this.linkText();
+ window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL,
+ linkText);
+ },
+
+ addBookmarkForFrame: function CM_addBookmarkForFrame() {
+ var doc = this.target.ownerDocument;
+ var uri = doc.documentURIObject;
+
+ var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
+ if (itemId == -1) {
+ var title = doc.title;
+ var description = PlacesUIUtils.getDescriptionFromDocument(doc);
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: uri
+ , title: title
+ , description: description
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window.top);
+ }
+ else {
+ PlacesUIUtils.showBookmarkDialog({ action: "edit"
+ , type: "bookmark"
+ , itemId: itemId
+ }, window.top);
+ }
+ },
+
+ savePageAs: function CM_savePageAs() {
+ saveDocument(this.browser.contentDocument);
+ },
+
+ sendPage: function CM_sendPage() {
+ MailIntegration.sendLinkForWindow(this.browser.contentWindow);
+ },
+
+ printFrame: function CM_printFrame() {
+ PrintUtils.print(this.target.ownerDocument.defaultView);
+ },
+
+ switchPageDirection: function CM_switchPageDirection() {
+ SwitchDocumentDirection(this.browser.contentWindow);
+ },
+
+ mediaCommand : function CM_mediaCommand(command, data) {
+ var media = this.target;
+
+ switch (command) {
+ case "play":
+ media.play();
+ break;
+ case "pause":
+ media.pause();
+ break;
+ case "mute":
+ media.muted = true;
+ break;
+ case "unmute":
+ media.muted = false;
+ break;
+ case "playbackRate":
+ media.playbackRate = data;
+ break;
+ case "hidecontrols":
+ media.removeAttribute("controls");
+ break;
+ case "showcontrols":
+ media.setAttribute("controls", "true");
+ break;
+ case "hidestats":
+ case "showstats":
+ var event = media.ownerDocument.createEvent("CustomEvent");
+ event.initCustomEvent("media-showStatistics", false, true, command == "showstats");
+ media.dispatchEvent(event);
+ break;
+ }
+ },
+
+ copyMediaLocation : function () {
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.mediaURL, document);
+ },
+
+ get imageURL() {
+ if (this.onImage)
+ return this.mediaURL;
+ return "";
+ },
+
+ // Formats the 'Search <engine> for "<selection or link text>"' context menu.
+ formatSearchContextItem: function() {
+ var menuItem = document.getElementById("context-searchselect");
+ var selectedText = this.isTextSelected ? this.textSelected : this.linkText();
+
+ // Store searchTerms in context menu item so we know what to search onclick
+ menuItem.searchTerms = selectedText;
+
+ if (selectedText.length > 15)
+ selectedText = selectedText.substr(0,15) + this.ellipsis;
+
+ // Use the current engine if the search bar is visible, the default
+ // engine otherwise.
+ var engineName = "";
+ var ss = Cc["@mozilla.org/browser/search-service;1"].
+ getService(Ci.nsIBrowserSearchService);
+ if (isElementVisible(BrowserSearch.searchBar))
+ engineName = ss.currentEngine.name;
+ else
+ engineName = ss.defaultEngine.name;
+
+ // format "Search <engine> for <selection>" string to show in menu
+ var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
+ [engineName,
+ selectedText]);
+ menuItem.label = menuLabel;
+ menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
+ }
+};
diff --git a/application/palemoon/base/content/openLocation.js b/application/palemoon/base/content/openLocation.js
new file mode 100644
index 000000000..5b731c7e8
--- /dev/null
+++ b/application/palemoon/base/content/openLocation.js
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var browser;
+var dialog = {};
+var pref = null;
+let openLocationModule = {};
+try {
+ pref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+} catch (ex) {
+ // not critical, remain silent
+}
+
+Components.utils.import("resource:///modules/openLocationLastURL.jsm", openLocationModule);
+let gOpenLocationLastURL = new openLocationModule.OpenLocationLastURL(window.opener);
+
+function onLoad()
+{
+ dialog.input = document.getElementById("dialog.input");
+ dialog.open = document.documentElement.getButton("accept");
+ dialog.openWhereList = document.getElementById("openWhereList");
+ dialog.openTopWindow = document.getElementById("currentWindow");
+ dialog.bundle = document.getElementById("openLocationBundle");
+
+ if ("arguments" in window && window.arguments.length >= 1)
+ browser = window.arguments[0];
+
+ dialog.openWhereList.selectedItem = dialog.openTopWindow;
+
+ if (pref) {
+ try {
+ var useAutoFill = pref.getBoolPref("browser.urlbar.autoFill");
+ if (useAutoFill)
+ dialog.input.setAttribute("completedefaultindex", "true");
+ } catch (ex) {}
+
+ try {
+ var value = pref.getIntPref("general.open_location.last_window_choice");
+ var element = dialog.openWhereList.getElementsByAttribute("value", value)[0];
+ if (element)
+ dialog.openWhereList.selectedItem = element;
+ dialog.input.value = gOpenLocationLastURL.value;
+ }
+ catch(ex) {
+ }
+ if (dialog.input.value)
+ dialog.input.select(); // XXX should probably be done automatically
+ }
+
+ doEnabling();
+}
+
+function doEnabling()
+{
+ dialog.open.disabled = !dialog.input.value;
+}
+
+function open()
+{
+ var url;
+ var postData = {};
+ var mayInheritPrincipal = {value: false};
+ if (browser)
+ url = browser.getShortcutOrURI(dialog.input.value, postData, mayInheritPrincipal);
+ else
+ url = dialog.input.value;
+
+ try {
+ // Whichever target we use for the load, we allow third-party services to
+ // fixup the URI
+ switch (dialog.openWhereList.value) {
+ case "0":
+ var webNav = Components.interfaces.nsIWebNavigation;
+ var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ if (!mayInheritPrincipal.value)
+ flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+ browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData.value);
+ break;
+ case "1":
+ window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
+ url, postData.value, null, null, true);
+ break;
+ case "3":
+ browser.delayedOpenTab(url, null, null, postData.value, true);
+ break;
+ }
+ }
+ catch(exception) {
+ }
+
+ if (pref) {
+ gOpenLocationLastURL.value = dialog.input.value;
+ pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value);
+ }
+
+ // Delay closing slightly to avoid timing bug on Linux.
+ window.close();
+ return false;
+}
+
+function createInstance(contractid, iidName)
+{
+ var iid = Components.interfaces[iidName];
+ return Components.classes[contractid].createInstance(iid);
+}
+
+const nsIFilePicker = Components.interfaces.nsIFilePicker;
+function onChooseFile()
+{
+ try {
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK && fp.fileURL.spec &&
+ fp.fileURL.spec.length > 0) {
+ dialog.input.value = fp.fileURL.spec;
+ }
+ doEnabling();
+ };
+
+ fp.init(window, dialog.bundle.getString("chooseFileDialogTitle"),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
+ nsIFilePicker.filterImages | nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML);
+ fp.open(fpCallback);
+ } catch (ex) {
+ }
+}
diff --git a/application/palemoon/base/content/openLocation.xul b/application/palemoon/base/content/openLocation.xul
new file mode 100644
index 000000000..7bafed0fe
--- /dev/null
+++ b/application/palemoon/base/content/openLocation.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/openLocation.dtd">
+
+<dialog id="openLocation"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&caption.label;"
+ onload="onLoad()"
+ buttonlabelaccept="&openBtn.label;"
+ buttoniconaccept="open"
+ ondialogaccept="open()"
+ style="width: 40em;"
+ persist="screenX screenY"
+ screenX="24" screenY="24">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://browser/content/openLocation.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <stringbundle id="openLocationBundle" src="chrome://browser/locale/openLocation.properties"/>
+
+ <hbox>
+ <separator orient="vertical" class="thin"/>
+ <vbox flex="1">
+ <description>&enter.label;</description>
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <textbox id="dialog.input" flex="1" type="autocomplete"
+ completeselectedindex="true"
+ autocompletesearch="urlinline history"
+ enablehistory="true"
+ class="uri-element"
+ oninput="doEnabling();"/>
+ <button label="&chooseFile.label;" oncommand="onChooseFile();"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&openWhere.label;"/>
+ <menulist id="openWhereList">
+ <menupopup>
+ <menuitem value="0" id="currentWindow" label="&topTab.label;"/>
+ <menuitem value="3" label="&newTab.label;"/>
+ <menuitem value="1" label="&newWindow.label;"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+</dialog>
diff --git a/application/palemoon/base/content/overrides/app-license.html b/application/palemoon/base/content/overrides/app-license.html
new file mode 100644
index 000000000..2d2e3d55e
--- /dev/null
+++ b/application/palemoon/base/content/overrides/app-license.html
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+ <p><b>Binaries</b> of this product have been made available to you by the
+ <a href="http://www.palemoon.org/">Pale Moon Project</a> under the Pale Moon
+ Binary <a href="http://www.palemoon.org/redist.shtml">redistribution license</a>.</p> \ No newline at end of file
diff --git a/application/palemoon/base/content/padlock.css b/application/palemoon/base/content/padlock.css
new file mode 100644
index 000000000..649cb2777
--- /dev/null
+++ b/application/palemoon/base/content/padlock.css
@@ -0,0 +1,203 @@
+#padlock-ib {
+ -moz-appearance: none;
+ min-width: 0px;
+ margin-right: 1px !important;
+ background-repeat: no-repeat;
+ background-position: center;
+ z-index: 1000 !important;
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="low"],
+#padlock-ib[padshow="ib-trans-bg"][level="mixed"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+#padlock-ib-left {
+ -moz-appearance: none;
+ min-width: 0px;
+ margin-right: 1px !important;
+ background-repeat: no-repeat;
+ background-position: center;
+ z-index: 1000 !important;
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="low"],
+#padlock-ib-left[padshow="ib-left"][level="mixed"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ub-right {
+ -moz-appearance: none;
+ min-width: 0px;
+ margin-right: 1px !important;
+ background-repeat: no-repeat;
+ background-position: center;
+ z-index: 1000 !important;
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="low"],
+#padlock-ub-right[padshow="ub-right"][level="mixed"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+#padlock-sb {
+ -moz-appearance: none;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+#padlock-sb[padshow="statbar"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-sb[padshow="statbar"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-sb[padshow="statbar"][level="low"],
+#padlock-sb[padshow="statbar"][level="mixed"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-sb[padshow="statbar"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+#padlock-tab {
+ -moz-appearance: none;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="low"],
+#padlock-tab[padshow="tabs-bar"][level="mixed"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+/* Classic style */
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="ev"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="ev"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="ev"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="ev"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_ev.png");
+}
+
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="high"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="high"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="high"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="high"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_https.png");
+}
+
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="low"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="low"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="low"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="low"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="low"],
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="mixed"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="mixed"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="mixed"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="mixed"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="mixed"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_low.png");
+}
+
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="broken"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="broken"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="broken"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="broken"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_broken.png");
+}
+
+/* Remove a few px of dead space for disabled locations */
+#padlock-ib:not([padshow="ib-trans-bg"]),
+#padlock-ib-left:not([padshow="ib-left"]),
+#padlock-ub-right:not([padshow="ub-right"]),
+#padlock-sb:not([padshow="statbar"]),
+#padlock-tab:not([padshow="tabs-bar"]) {
+ visibility: collapse;
+} \ No newline at end of file
diff --git a/application/palemoon/base/content/padlock.js b/application/palemoon/base/content/padlock.js
new file mode 100644
index 000000000..53477fd17
--- /dev/null
+++ b/application/palemoon/base/content/padlock.js
@@ -0,0 +1,234 @@
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var padlock_PadLock =
+{
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+ onButtonClick: function(event) {
+ event.stopPropagation();
+ gIdentityHandler.handleMoreInfoClick(event);
+ },
+ onStateChange: function() {},
+ onProgressChange: function() {},
+ onLocationChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function(aCallerWebProgress, aRequestWithState, aState) {
+ // aState is defined as a bitmask that may be extended in the future.
+ // We filter out any unknown bits before testing for known values.
+ const wpl = Ci.nsIWebProgressListener;
+ const wpl_security_bits = wpl.STATE_IS_SECURE |
+ wpl.STATE_IS_BROKEN |
+ wpl.STATE_IS_INSECURE |
+ wpl.STATE_IDENTITY_EV_TOPLEVEL |
+ wpl.STATE_SECURE_HIGH |
+ wpl.STATE_SECURE_MED |
+ wpl.STATE_SECURE_LOW;
+ var level;
+ var is_insecure;
+ var highlight_urlbar = false;
+
+ switch (aState & wpl_security_bits) {
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_HIGH | wpl.STATE_IDENTITY_EV_TOPLEVEL:
+ level = "ev";
+ is_insecure = "";
+ highlight_urlbar = true;
+ break;
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_HIGH:
+ level = "high";
+ is_insecure = "";
+ highlight_urlbar = true;
+ break;
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_MED:
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_LOW:
+ level = "low";
+ is_insecure = "insecure";
+ break;
+ case wpl.STATE_IS_BROKEN | wpl.STATE_SECURE_LOW:
+ level = "mixed";
+ is_insecure = "insecure";
+ highlight_urlbar = true;
+ break;
+ case wpl.STATE_IS_BROKEN:
+ level = "broken";
+ is_insecure = "insecure";
+ highlight_urlbar = true;
+ break;
+ default: // should not be reached
+ level = null;
+ is_insecure = "insecure";
+ }
+
+ try {
+ var proto = gBrowser.contentWindow.location.protocol;
+ if (proto == "about:" || proto == "chrome:" || proto == "file:" ) {
+ // do not warn when using local protocols
+ is_insecure = false;
+ }
+ }
+ catch (ex) {}
+
+ let ub = document.getElementById("urlbar");
+ if (ub) { // Only call if URL bar is present.
+ if (highlight_urlbar) {
+ ub.setAttribute("security_level", level);
+ } else {
+ ub.removeAttribute("security_level");
+ }
+ }
+
+ try { // URL bar may be hidden
+ padlock_PadLock.setPadlockLevel("padlock-ib", level);
+ padlock_PadLock.setPadlockLevel("padlock-ib-left", level);
+ padlock_PadLock.setPadlockLevel("padlock-ub-right", level);
+ } catch(e) {}
+ padlock_PadLock.setPadlockLevel("padlock-sb", level);
+ padlock_PadLock.setPadlockLevel("padlock-tab", level);
+ },
+ setPadlockLevel: function(item, level) {
+ let secbut = document.getElementById(item);
+ var sectooltip = "";
+
+ if (level) {
+ secbut.setAttribute("level", level);
+ secbut.hidden = false;
+ } else {
+ secbut.hidden = true;
+ secbut.removeAttribute("level");
+ }
+
+ switch (level) {
+ case "ev":
+ sectooltip = "Extended Validated";
+ break;
+ case "high":
+ sectooltip = "Secure";
+ break;
+ case "low":
+ sectooltip = "Weak security";
+ break;
+ case "mixed":
+ sectooltip = "Mixed mode (partially encrypted)";
+ break;
+ case "broken":
+ sectooltip = "Not secure";
+ break;
+ default:
+ sectooltip = "";
+ }
+ secbut.setAttribute("tooltiptext", sectooltip);
+ },
+ prefbranch : null,
+ onLoad: function() {
+ gBrowser.addProgressListener(padlock_PadLock);
+
+ var prefService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
+ padlock_PadLock.prefbranch = prefService.getBranch("browser.padlock.");
+ padlock_PadLock.prefbranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
+ padlock_PadLock.usePrefs();
+ padlock_PadLock.prefbranch.addObserver("", padlock_PadLock, false);
+ },
+ onUnLoad: function() {
+ padlock_PadLock.prefbranch.removeObserver("", padlock_PadLock);
+ },
+ observe: function(subject, topic, data)
+ {
+ if (topic != "nsPref:changed")
+ return;
+ if (data != "style" && data != "urlbar_background" && data != "shown")
+ return;
+ padlock_PadLock.usePrefs();
+ },
+ usePrefs: function() {
+ var prefval = padlock_PadLock.prefbranch.getIntPref("style");
+ var position;
+ var padstyle;
+ if (prefval == 2) {
+ position = "ib-left";
+ padstyle = "modern";
+ }
+ else if (prefval == 3) {
+ position = "ub-right";
+ padstyle = "modern";
+ }
+ else if (prefval == 4) {
+ position = "statbar";
+ padstyle = "modern";
+ }
+ else if (prefval == 5) {
+ position = "tabs-bar";
+ padstyle = "modern";
+ }
+ else if (prefval == 6) {
+ position = "ib-trans-bg";
+ padstyle = "classic";
+ }
+ else if (prefval == 7) {
+ position = "ib-left";
+ padstyle = "classic";
+ }
+ else if (prefval == 8) {
+ position = "ub-right";
+ padstyle = "classic";
+ }
+ else if (prefval == 9) {
+ position = "statbar";
+ padstyle = "classic";
+ }
+ else if (prefval == 10) {
+ position = "tabs-bar";
+ padstyle = "classic";
+ }
+ else { // 1 or anything else_ default
+ position = "ib-trans-bg";
+ padstyle = "modern";
+ }
+
+ var colshow;
+ var colprefval = padlock_PadLock.prefbranch.getIntPref("urlbar_background");
+ switch (colprefval) {
+ case 3:
+ colshow = "all";
+ break;
+ case 2:
+ colshow = "secure-mixed";
+ break;
+ case 1:
+ colshow = "secure-only";
+ break;
+ default:
+ colshow = ""; // 0 or anything else: no shading
+ }
+ try { // URL bar may be hidden
+ document.getElementById("urlbar").setAttribute("https_color", colshow);
+ } catch(e) {}
+
+ var lockenabled = padlock_PadLock.prefbranch.getBoolPref("shown");
+ var padshow = "";
+ if (lockenabled) {
+ padshow = position;
+ }
+
+ try { // URL bar may be hidden
+ document.getElementById("padlock-ib").setAttribute("padshow", padshow);
+ document.getElementById("padlock-ib-left").setAttribute("padshow", padshow);
+ document.getElementById("padlock-ub-right").setAttribute("padshow", padshow);
+ } catch(e) {}
+ document.getElementById("padlock-sb").setAttribute("padshow", padshow);
+ document.getElementById("padlock-tab").setAttribute("padshow", padshow);
+
+ try { // URL bar may be hidden
+ document.getElementById("padlock-ib").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-ib-left").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-ub-right").setAttribute("padstyle", padstyle);
+ } catch(e) {}
+ document.getElementById("padlock-sb").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-tab").setAttribute("padstyle", padstyle);
+
+ }
+};
+
+window.addEventListener("load", padlock_PadLock.onLoad, false );
+window.addEventListener("unload", padlock_PadLock.onUnLoad, false );
diff --git a/application/palemoon/base/content/padlock.xul b/application/palemoon/base/content/padlock.xul
new file mode 100644
index 000000000..e820c19c7
--- /dev/null
+++ b/application/palemoon/base/content/padlock.xul
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://browser/content/padlock.css" type="text/css"?>
+
+<overlay
+ id="padlock"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/x-javascript" src="chrome://browser/content/padlock.js"/>
+
+ <hbox id="identity-box">
+ <image id="padlock-ib" insertafter="identity-icon-labels"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </hbox>
+
+ <hbox id="identity-box">
+ <image id="padlock-ib-left" insertbefore="identity-icon-labels"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </hbox>
+
+ <hbox id="urlbar-icons">
+ <image id="padlock-ub-right" insertbefore="star-button"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </hbox>
+
+ <statusbar id="status-bar">
+ <statusbarpanel insertafter="security-button"
+ id="padlock-sb-panel"
+ class="statusbar-iconic-text">
+ <image id="padlock-sb" insertbefore="star-button"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </statusbarpanel>
+ </statusbar>
+
+ <toolbar id="TabsToolbar">
+ <toolbaritem insertafter="tabs-closebutton" id="tabs-padlock-tbitem"
+ align="center" pack="center">
+ <image id="padlock-tab"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </toolbaritem>
+ </toolbar>
+
+
+</overlay>
diff --git a/application/palemoon/base/content/padlock_classic_broken.png b/application/palemoon/base/content/padlock_classic_broken.png
new file mode 100644
index 000000000..437036fe8
--- /dev/null
+++ b/application/palemoon/base/content/padlock_classic_broken.png
Binary files differ
diff --git a/application/palemoon/base/content/padlock_classic_ev.png b/application/palemoon/base/content/padlock_classic_ev.png
new file mode 100644
index 000000000..b3f80c0da
--- /dev/null
+++ b/application/palemoon/base/content/padlock_classic_ev.png
Binary files differ
diff --git a/application/palemoon/base/content/padlock_classic_https.png b/application/palemoon/base/content/padlock_classic_https.png
new file mode 100644
index 000000000..86026c04f
--- /dev/null
+++ b/application/palemoon/base/content/padlock_classic_https.png
Binary files differ
diff --git a/application/palemoon/base/content/padlock_classic_low.png b/application/palemoon/base/content/padlock_classic_low.png
new file mode 100644
index 000000000..652ad091f
--- /dev/null
+++ b/application/palemoon/base/content/padlock_classic_low.png
Binary files differ
diff --git a/application/palemoon/base/content/padlock_mod_broken.png b/application/palemoon/base/content/padlock_mod_broken.png
new file mode 100644
index 000000000..33a6c0645
--- /dev/null
+++ b/application/palemoon/base/content/padlock_mod_broken.png
Binary files differ
diff --git a/application/palemoon/base/content/padlock_mod_ev.png b/application/palemoon/base/content/padlock_mod_ev.png
new file mode 100644
index 000000000..3dfdcbde5
--- /dev/null
+++ b/application/palemoon/base/content/padlock_mod_ev.png
Binary files differ
diff --git a/application/palemoon/base/content/padlock_mod_https.png b/application/palemoon/base/content/padlock_mod_https.png
new file mode 100644
index 000000000..d494b42b0
--- /dev/null
+++ b/application/palemoon/base/content/padlock_mod_https.png
Binary files differ
diff --git a/application/palemoon/base/content/padlock_mod_low.png b/application/palemoon/base/content/padlock_mod_low.png
new file mode 100644
index 000000000..29179efaf
--- /dev/null
+++ b/application/palemoon/base/content/padlock_mod_low.png
Binary files differ
diff --git a/application/palemoon/base/content/pageinfo/feeds.js b/application/palemoon/base/content/pageinfo/feeds.js
new file mode 100644
index 000000000..468d8c19d
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/feeds.js
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function initFeedTab()
+{
+ const feedTypes = {
+ "application/rss+xml": gBundle.getString("feedRss"),
+ "application/atom+xml": gBundle.getString("feedAtom"),
+ "text/xml": gBundle.getString("feedXML"),
+ "application/xml": gBundle.getString("feedXML"),
+ "application/rdf+xml": gBundle.getString("feedXML")
+ };
+
+ // get the feeds
+ var linkNodes = gDocument.getElementsByTagName("link");
+ var length = linkNodes.length;
+ for (var i = 0; i < length; i++) {
+ var link = linkNodes[i];
+ if (!link.href)
+ continue;
+
+ var rel = link.rel && link.rel.toLowerCase();
+ var rels = {};
+ if (rel) {
+ for each (let relVal in rel.split(/\s+/))
+ rels[relVal] = true;
+ }
+
+ if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) {
+ var type = isValidFeed(link, gDocument.nodePrincipal, "feed" in rels);
+ if (type) {
+ type = feedTypes[type] || feedTypes["application/rss+xml"];
+ addRow(link.title, type, link.href);
+ }
+ }
+ }
+
+ var feedListbox = document.getElementById("feedListbox");
+ document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
+}
+
+function onSubscribeFeed()
+{
+ var listbox = document.getElementById("feedListbox");
+ openUILinkIn(listbox.selectedItem.getAttribute("feedURL"), "current",
+ { ignoreAlt: true });
+}
+
+function addRow(name, type, url)
+{
+ var item = document.createElement("richlistitem");
+ item.setAttribute("feed", "true");
+ item.setAttribute("name", name);
+ item.setAttribute("type", type);
+ item.setAttribute("feedURL", url);
+ document.getElementById("feedListbox").appendChild(item);
+}
diff --git a/application/palemoon/base/content/pageinfo/feeds.xml b/application/palemoon/base/content/pageinfo/feeds.xml
new file mode 100644
index 000000000..782c05a73
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/feeds.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+<bindings id="feedBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="feed" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:vbox flex="1">
+ <xul:hbox flex="1">
+ <xul:textbox flex="1" readonly="true" xbl:inherits="value=name"
+ class="feedTitle"/>
+ <xul:label xbl:inherits="value=type"/>
+ </xul:hbox>
+ <xul:vbox>
+ <xul:vbox align="start">
+ <xul:hbox>
+ <xul:label xbl:inherits="value=feedURL,tooltiptext=feedURL" class="text-link" flex="1"
+ onclick="openUILink(this.value, event);" crop="end"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:hbox flex="1" class="feed-subscribe">
+ <xul:spacer flex="1"/>
+ <xul:button label="&feedSubscribe;" accesskey="&feedSubscribe.accesskey;"
+ oncommand="onSubscribeFeed()"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/application/palemoon/base/content/pageinfo/pageInfo.css b/application/palemoon/base/content/pageinfo/pageInfo.css
new file mode 100644
index 000000000..622b56bb5
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/pageInfo.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#viewGroup > radio {
+ -moz-binding: url("chrome://browser/content/pageinfo/pageInfo.xml#viewbutton");
+}
+
+richlistitem[feed] {
+ -moz-binding: url("chrome://browser/content/pageinfo/feeds.xml#feed");
+}
+
+richlistitem[feed]:not([selected="true"]) .feed-subscribe {
+ display: none;
+}
+
+groupbox[closed="true"] > .groupbox-body {
+ visibility: collapse;
+}
+
+#thepreviewimage {
+ display: block;
+/* This following entry can be removed when Bug 522850 is fixed. */
+ min-width: 1px;
+}
diff --git a/application/palemoon/base/content/pageinfo/pageInfo.js b/application/palemoon/base/content/pageinfo/pageInfo.js
new file mode 100644
index 000000000..ba93ee817
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/pageInfo.js
@@ -0,0 +1,1285 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+//******** define a js object to implement nsITreeView
+function pageInfoTreeView(treeid, copycol)
+{
+ // copycol is the index number for the column that we want to add to
+ // the copy-n-paste buffer when the user hits accel-c
+ this.treeid = treeid;
+ this.copycol = copycol;
+ this.rows = 0;
+ this.tree = null;
+ this.data = [ ];
+ this.selection = null;
+ this.sortcol = -1;
+ this.sortdir = false;
+}
+
+pageInfoTreeView.prototype = {
+ set rowCount(c) { throw "rowCount is a readonly property"; },
+ get rowCount() { return this.rows; },
+
+ setTree: function(tree)
+ {
+ this.tree = tree;
+ },
+
+ getCellText: function(row, column)
+ {
+ // row can be null, but js arrays are 0-indexed.
+ // colidx cannot be null, but can be larger than the number
+ // of columns in the array. In this case it's the fault of
+ // whoever typoed while calling this function.
+ return this.data[row][column.index] || "";
+ },
+
+ setCellValue: function(row, column, value)
+ {
+ },
+
+ setCellText: function(row, column, value)
+ {
+ this.data[row][column.index] = value;
+ },
+
+ addRow: function(row)
+ {
+ this.rows = this.data.push(row);
+ this.rowCountChanged(this.rows - 1, 1);
+ if (this.selection.count == 0 && this.rowCount && !gImageElement)
+ this.selection.select(0);
+ },
+
+ rowCountChanged: function(index, count)
+ {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function()
+ {
+ this.tree.invalidate();
+ },
+
+ clear: function()
+ {
+ if (this.tree)
+ this.tree.rowCountChanged(0, -this.rows);
+ this.rows = 0;
+ this.data = [ ];
+ },
+
+ handleCopy: function(row)
+ {
+ return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
+ },
+
+ performActionOnRow: function(action, row)
+ {
+ if (action == "copy") {
+ var data = this.handleCopy(row)
+ this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
+ }
+ },
+
+ onPageMediaSort : function(columnname)
+ {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ this.sortdir =
+ gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ treecol.index,
+ function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); },
+ this.sortcol,
+ this.sortdir
+ );
+
+ this.sortcol = treecol.index;
+ },
+
+ getRowProperties: function(row) { return ""; },
+ getCellProperties: function(row, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function() { },
+ canDrop: function(index, orientation) { return false; },
+ drop: function(row, orientation) { return false; },
+ getParentIndex: function(index) { return 0; },
+ hasNextSibling: function(index, after) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, column) { },
+ getProgressMode: function(row, column) { },
+ getCellValue: function(row, column) { },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(col) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(row, column) { return false; },
+ isSelectable: function(row, column) { return false; },
+ performAction: function(action) { },
+ performActionOnCell: function(action, row, column) { }
+};
+
+// mmm, yummy. global variables.
+var gWindow = null;
+var gDocument = null;
+var gImageElement = null;
+
+// column number to help using the data array
+const COL_IMAGE_ADDRESS = 0;
+const COL_IMAGE_TYPE = 1;
+const COL_IMAGE_SIZE = 2;
+const COL_IMAGE_ALT = 3;
+const COL_IMAGE_COUNT = 4;
+const COL_IMAGE_NODE = 5;
+const COL_IMAGE_BG = 6;
+
+// column number to copy from, second argument to pageInfoTreeView's constructor
+const COPYCOL_NONE = -1;
+const COPYCOL_META_CONTENT = 1;
+const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
+
+// one nsITreeView for each tree in the window
+var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT);
+var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE);
+
+gImageView.getCellProperties = function(row, col) {
+ var data = gImageView.data[row];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var props = "";
+ if (!checkProtocol(data) ||
+ item instanceof HTMLEmbedElement ||
+ (item instanceof HTMLObjectElement && !item.type.startsWith("image/")))
+ props += "broken";
+
+ if (col.element.id == "image-address")
+ props += " ltr";
+
+ return props;
+};
+
+gImageView.getCellText = function(row, column) {
+ var value = this.data[row][column.index];
+ if (column.index == COL_IMAGE_SIZE) {
+ if (value == -1) {
+ return gStrings.unknown;
+ } else {
+ var kbSize = Number(Math.round(value / 1024 * 100) / 100);
+ return gBundle.getFormattedString("mediaFileSize", [kbSize]);
+ }
+ }
+ return value || "";
+};
+
+gImageView.onPageMediaSort = function(columnname) {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ var comparator;
+ if (treecol.index == COL_IMAGE_SIZE || treecol.index == COL_IMAGE_COUNT) {
+ comparator = function numComparator(a, b) { return a - b; };
+ } else {
+ comparator = function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); };
+ }
+
+ this.sortdir =
+ gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ treecol.index,
+ comparator,
+ this.sortcol,
+ this.sortdir
+ );
+
+ this.sortcol = treecol.index;
+};
+
+var gImageHash = { };
+
+// localized strings (will be filled in when the document is loaded)
+// this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
+var gStrings = { };
+var gBundle;
+
+const PERMISSION_CONTRACTID = "@mozilla.org/permissionmanager;1";
+const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1";
+const ATOM_CONTRACTID = "@mozilla.org/atom-service;1";
+
+// a number of services I'll need later
+// the cache services
+const nsICacheStorageService = Components.interfaces.nsICacheStorageService;
+const nsICacheStorage = Components.interfaces.nsICacheStorage;
+const cacheService = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"].getService(nsICacheStorageService);
+
+var loadContextInfo = LoadContextInfo.fromLoadContext(
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext), false);
+var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false);
+
+const nsICookiePermission = Components.interfaces.nsICookiePermission;
+const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+
+const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
+const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
+
+// clipboard helper
+function getClipboardHelper() {
+ try {
+ return Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
+ } catch(e) {
+ // do nothing, later code will handle the error
+ }
+}
+const gClipboardHelper = getClipboardHelper();
+
+// Interface for image loading content
+const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
+
+// namespaces, don't need all of these yet...
+const XLinkNS = "http://www.w3.org/1999/xlink";
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XMLNS = "http://www.w3.org/XML/1998/namespace";
+const XHTMLNS = "http://www.w3.org/1999/xhtml";
+const XHTML2NS = "http://www.w3.org/2002/06/xhtml2"
+
+const XHTMLNSre = "^http\:\/\/www\.w3\.org\/1999\/xhtml$";
+const XHTML2NSre = "^http\:\/\/www\.w3\.org\/2002\/06\/xhtml2$";
+const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, "");
+
+/* Overlays register functions here.
+ * These arrays are used to hold callbacks that Page Info will call at
+ * various stages. Use them by simply appending a function to them.
+ * For example, add a function to onLoadRegistry by invoking
+ * "onLoadRegistry.push(XXXLoadFunc);"
+ * The XXXLoadFunc should be unique to the overlay module, and will be
+ * invoked as "XXXLoadFunc();"
+ */
+
+// These functions are called to build the data displayed in the Page
+// Info window. The global variables gDocument and gWindow are set.
+var onLoadRegistry = [ ];
+
+// These functions are called to remove old data still displayed in
+// the window when the document whose information is displayed
+// changes. For example, at this time, the list of images of the Media
+// tab is cleared.
+var onResetRegistry = [ ];
+
+// These are called once for each subframe of the target document and
+// the target document itself. The frame is passed as an argument.
+var onProcessFrame = [ ];
+
+// These functions are called once for each element (in all subframes, if any)
+// in the target document. The element is passed as an argument.
+var onProcessElement = [ ];
+
+// These functions are called once when all the elements in all of the target
+// document (and all of its subframes, if any) have been processed
+var onFinished = [ ];
+
+// These functions are called once when the Page Info window is closed.
+var onUnloadRegistry = [ ];
+
+// These functions are called once when an image preview is shown.
+var onImagePreviewShown = [ ];
+
+/* Called when PageInfo window is loaded. Arguments are:
+ * window.arguments[0] - (optional) an object consisting of
+ * - doc: (optional) document to use for source. if not provided,
+ * the calling window's document will be used
+ * - initialTab: (optional) id of the inital tab to display
+ */
+function onLoadPageInfo()
+{
+ gBundle = document.getElementById("pageinfobundle");
+ gStrings.unknown = gBundle.getString("unknown");
+ gStrings.notSet = gBundle.getString("notset");
+ gStrings.mediaImg = gBundle.getString("mediaImg");
+ gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
+ gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
+ gStrings.mediaListImg = gBundle.getString("mediaListImg");
+ gStrings.mediaCursor = gBundle.getString("mediaCursor");
+ gStrings.mediaObject = gBundle.getString("mediaObject");
+ gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
+ gStrings.mediaLink = gBundle.getString("mediaLink");
+ gStrings.mediaInput = gBundle.getString("mediaInput");
+ gStrings.mediaVideo = gBundle.getString("mediaVideo");
+ gStrings.mediaAudio = gBundle.getString("mediaAudio");
+
+ var args = "arguments" in window &&
+ window.arguments.length >= 1 &&
+ window.arguments[0];
+
+ if (!args || !args.doc) {
+ gWindow = window.opener.content;
+ gDocument = gWindow.document;
+ }
+
+ // init media view
+ var imageTree = document.getElementById("imagetree");
+ imageTree.view = gImageView;
+
+ /* Select the requested tab, if the name is specified */
+ loadTab(args);
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(window, "page-info-dialog-loaded", null);
+
+ // Make sure the page info window gets focus even if a doorhanger might
+ // otherwise (async) steal it.
+ window.focus();
+}
+
+function loadPageInfo()
+{
+ var titleFormat = gWindow != gWindow.top ? "pageInfo.frame.title"
+ : "pageInfo.page.title";
+ document.title = gBundle.getFormattedString(titleFormat, [gDocument.location]);
+
+ document.getElementById("main-window").setAttribute("relatedUrl", gDocument.location);
+
+ // do the easy stuff first
+ makeGeneralTab();
+
+ // and then the hard stuff
+ makeTabs(gDocument, gWindow);
+
+ initFeedTab();
+ onLoadPermission();
+
+ /* Call registered overlay init functions */
+ onLoadRegistry.forEach(function(func) { func(); });
+}
+
+function resetPageInfo(args)
+{
+ /* Reset Meta tags part */
+ gMetaView.clear();
+
+ /* Reset Media tab */
+ var mediaTab = document.getElementById("mediaTab");
+ if (!mediaTab.hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ mediaTab.hidden = true;
+ }
+ gImageView.clear();
+ gImageHash = {};
+
+ /* Reset Feeds Tab */
+ var feedListbox = document.getElementById("feedListbox");
+ while (feedListbox.firstChild)
+ feedListbox.removeChild(feedListbox.firstChild);
+
+ /* Call registered overlay reset functions */
+ onResetRegistry.forEach(function(func) { func(); });
+
+ /* Rebuild the data */
+ loadTab(args);
+}
+
+function onUnloadPageInfo()
+{
+ // Remove the observer, only if there is at least 1 image.
+ if (!document.getElementById("mediaTab").hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ }
+
+ /* Call registered overlay unload functions */
+ onUnloadRegistry.forEach(function(func) { func(); });
+}
+
+function doHelpButton()
+{
+ const helpTopics = {
+ "generalPanel": "pageinfo_general",
+ "mediaPanel": "pageinfo_media",
+ "feedPanel": "pageinfo_feed",
+ "permPanel": "pageinfo_permissions",
+ "securityPanel": "pageinfo_security"
+ };
+
+ var deck = document.getElementById("mainDeck");
+ var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general";
+ openHelpLink(helpdoc);
+}
+
+function showTab(id)
+{
+ var deck = document.getElementById("mainDeck");
+ var pagel = document.getElementById(id + "Panel");
+ deck.selectedPanel = pagel;
+}
+
+function loadTab(args)
+{
+ if (args && args.doc) {
+ gDocument = args.doc;
+ gWindow = gDocument.defaultView;
+ }
+
+ gImageElement = args && args.imageElement;
+
+ /* Load the page info */
+ loadPageInfo();
+
+ var initialTab = (args && args.initialTab) || "generalTab";
+ var radioGroup = document.getElementById("viewGroup");
+ initialTab = document.getElementById(initialTab) || document.getElementById("generalTab");
+ radioGroup.selectedItem = initialTab;
+ radioGroup.selectedItem.doCommand();
+ radioGroup.focus();
+}
+
+function onClickMore()
+{
+ var radioGrp = document.getElementById("viewGroup");
+ var radioElt = document.getElementById("securityTab");
+ radioGrp.selectedItem = radioElt;
+ showTab('security');
+}
+
+function toggleGroupbox(id)
+{
+ var elt = document.getElementById(id);
+ if (elt.hasAttribute("closed")) {
+ elt.removeAttribute("closed");
+ if (elt.flexWhenOpened)
+ elt.flex = elt.flexWhenOpened;
+ }
+ else {
+ elt.setAttribute("closed", "true");
+ if (elt.flex) {
+ elt.flexWhenOpened = elt.flex;
+ elt.flex = 0;
+ }
+ }
+}
+
+function openCacheEntry(key, cb)
+{
+ var checkCacheListener = {
+ onCacheEntryCheck: function(entry, appCache) {
+ return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable: function(entry, isNew, appCache, status) {
+ cb(entry);
+ },
+ get mainThreadOnly() { return true; }
+ };
+ diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
+}
+
+function makeGeneralTab()
+{
+ var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle");
+ document.getElementById("titletext").value = title;
+
+ var url = gDocument.location.toString();
+ setItemValue("urltext", url);
+
+ var referrer = ("referrer" in gDocument && gDocument.referrer);
+ setItemValue("refertext", referrer);
+
+ var mode = ("compatMode" in gDocument && gDocument.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
+ document.getElementById("modetext").value = gBundle.getString(mode);
+
+ // find out the mime type
+ var mimeType = gDocument.contentType;
+ setItemValue("typetext", mimeType);
+
+ // get the document characterset
+ var encoding = gDocument.characterSet;
+ document.getElementById("encodingtext").value = encoding;
+
+ // get the meta tags
+ var metaNodes = gDocument.getElementsByTagName("meta");
+ var length = metaNodes.length;
+
+ var metaGroup = document.getElementById("metaTags");
+ if (!length)
+ metaGroup.collapsed = true;
+ else {
+ var metaTagsCaption = document.getElementById("metaTagsCaption");
+ if (length == 1)
+ metaTagsCaption.label = gBundle.getString("generalMetaTag");
+ else
+ metaTagsCaption.label = gBundle.getFormattedString("generalMetaTags", [length]);
+ var metaTree = document.getElementById("metatree");
+ metaTree.view = gMetaView;
+
+ for (var i = 0; i < length; i++)
+ gMetaView.addRow([metaNodes[i].name || metaNodes[i].httpEquiv, metaNodes[i].content]);
+
+ metaGroup.collapsed = false;
+ }
+
+ // get the date of last modification
+ var modifiedText = formatDate(gDocument.lastModified, gStrings.notSet);
+ document.getElementById("modifiedtext").value = modifiedText;
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ var sizeText;
+ if (cacheEntry) {
+ var pageSize = cacheEntry.dataSize;
+ var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
+ sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
+ }
+ setItemValue("sizetext", sizeText);
+ });
+
+ securityOnLoad();
+}
+
+//******** Generic Build-a-tab
+// Assumes the views are empty. Only called once to build the tabs, and
+// does so by farming the task off to another thread via setTimeout().
+// The actual work is done with a TreeWalker that calls doGrab() once for
+// each element node in the document.
+
+var gFrameList = [ ];
+
+function makeTabs(aDocument, aWindow)
+{
+ goThroughFrames(aDocument, aWindow);
+ processFrames();
+}
+
+function goThroughFrames(aDocument, aWindow)
+{
+ gFrameList.push(aDocument);
+ if (aWindow && aWindow.frames.length > 0) {
+ var num = aWindow.frames.length;
+ for (var i = 0; i < num; i++)
+ goThroughFrames(aWindow.frames[i].document, aWindow.frames[i]); // recurse through the frames
+ }
+}
+
+function processFrames()
+{
+ if (gFrameList.length) {
+ var doc = gFrameList[0];
+ onProcessFrame.forEach(function(func) { func(doc); });
+ var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll);
+ gFrameList.shift();
+ setTimeout(doGrab, 10, iterator);
+ onFinished.push(selectImage);
+ }
+ else
+ onFinished.forEach(function(func) { func(); });
+}
+
+function doGrab(iterator)
+{
+ for (var i = 0; i < 500; ++i)
+ if (!iterator.nextNode()) {
+ processFrames();
+ return;
+ }
+
+ setTimeout(doGrab, 10, iterator);
+}
+
+function addImage(url, type, alt, elem, isBg)
+{
+ if (!url)
+ return;
+
+ if (!gImageHash.hasOwnProperty(url))
+ gImageHash[url] = { };
+ if (!gImageHash[url].hasOwnProperty(type))
+ gImageHash[url][type] = { };
+ if (!gImageHash[url][type].hasOwnProperty(alt)) {
+ gImageHash[url][type][alt] = gImageView.data.length;
+ var row = [url, type, -1, alt, 1, elem, isBg];
+ gImageView.addRow(row);
+
+ // Fill in cache data asynchronously
+ openCacheEntry(url, function(cacheEntry) {
+ // The data at row[2] corresponds to the data size.
+ if (cacheEntry) {
+ row[2] = cacheEntry.dataSize;
+ // Invalidate the row to trigger a repaint.
+ gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+ }
+ });
+
+ // Add the observer, only once.
+ if (gImageView.data.length == 1) {
+ document.getElementById("mediaTab").hidden = false;
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .addObserver(imagePermissionObserver, "perm-changed", false);
+ }
+ }
+ else {
+ var i = gImageHash[url][type][alt];
+ gImageView.data[i][COL_IMAGE_COUNT]++;
+ if (elem == gImageElement)
+ gImageView.data[i][COL_IMAGE_NODE] = elem;
+ }
+}
+
+function grabAll(elem)
+{
+ // check for images defined in CSS (e.g. background, borders), any node may have multiple
+ var computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
+
+ if (computedStyle) {
+ var addImgFunc = function (label, val) {
+ if (val.primitiveType == CSSPrimitiveValue.CSS_URI) {
+ addImage(val.getStringValue(), label, gStrings.notSet, elem, true);
+ }
+ else if (val.primitiveType == CSSPrimitiveValue.CSS_STRING) {
+ // This is for -moz-image-rect.
+ // TODO: Reimplement once bug 714757 is fixed
+ var strVal = val.getStringValue();
+ if (strVal.search(/^.*url\(\"?/) > -1) {
+ url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,"");
+ addImage(url, label, gStrings.notSet, elem, true);
+ }
+ }
+ else if (val.cssValueType == CSSValue.CSS_VALUE_LIST) {
+ // recursively resolve multiple nested CSS value lists
+ for (var i = 0; i < val.length; i++)
+ addImgFunc(label, val.item(i));
+ }
+ };
+
+ addImgFunc(gStrings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
+ addImgFunc(gStrings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
+ addImgFunc(gStrings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
+ addImgFunc(gStrings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
+ }
+
+ // one swi^H^H^Hif-else to rule them all
+ if (elem instanceof HTMLImageElement)
+ addImage(elem.src, gStrings.mediaImg,
+ (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
+ else if (elem instanceof SVGImageElement) {
+ try {
+ // Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
+ // or the URI formed from the baseURI and the URL is not a valid URI
+ var href = makeURLAbsolute(elem.baseURI, elem.href.baseVal);
+ addImage(href, gStrings.mediaImg, "", elem, false);
+ } catch (e) { }
+ }
+ else if (elem instanceof HTMLVideoElement) {
+ addImage(elem.currentSrc, gStrings.mediaVideo, "", elem, false);
+ }
+ else if (elem instanceof HTMLAudioElement) {
+ addImage(elem.currentSrc, gStrings.mediaAudio, "", elem, false);
+ }
+ else if (elem instanceof HTMLLinkElement) {
+ if (elem.rel && /\bicon\b/i.test(elem.rel))
+ addImage(elem.href, gStrings.mediaLink, "", elem, false);
+ }
+ else if (elem instanceof HTMLInputElement || elem instanceof HTMLButtonElement) {
+ if (elem.type.toLowerCase() == "image")
+ addImage(elem.src, gStrings.mediaInput,
+ (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
+ }
+ else if (elem instanceof HTMLObjectElement)
+ addImage(elem.data, gStrings.mediaObject, getValueText(elem), elem, false);
+ else if (elem instanceof HTMLEmbedElement)
+ addImage(elem.src, gStrings.mediaEmbed, "", elem, false);
+
+ onProcessElement.forEach(function(func) { func(elem); });
+
+ return NodeFilter.FILTER_ACCEPT;
+}
+
+//******** Link Stuff
+function openURL(target)
+{
+ var url = target.parentNode.childNodes[2].value;
+ window.open(url, "_blank", "chrome");
+}
+
+function onBeginLinkDrag(event,urlField,descField)
+{
+ if (event.originalTarget.localName != "treechildren")
+ return;
+
+ var tree = event.target;
+ if (!("treeBoxObject" in tree))
+ tree = tree.parentNode;
+
+ var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (row == -1)
+ return;
+
+ // Adding URL flavor
+ var col = tree.columns[urlField];
+ var url = tree.view.getCellText(row, col);
+ col = tree.columns[descField];
+ var desc = tree.view.getCellText(row, col);
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", url + "\n" + desc);
+ dt.setData("text/url-list", url);
+ dt.setData("text/plain", url);
+}
+
+//******** Image Stuff
+function getSelectedRows(tree)
+{
+ var start = { };
+ var end = { };
+ var numRanges = tree.view.selection.getRangeCount();
+
+ var rowArray = [ ];
+ for (var t = 0; t < numRanges; t++) {
+ tree.view.selection.getRangeAt(t, start, end);
+ for (var v = start.value; v <= end.value; v++)
+ rowArray.push(v);
+ }
+
+ return rowArray;
+}
+
+function getSelectedRow(tree)
+{
+ var rows = getSelectedRows(tree);
+ return (rows.length == 1) ? rows[0] : -1;
+}
+
+function selectSaveFolder(aCallback)
+{
+ const nsILocalFile = Components.interfaces.nsILocalFile;
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ let titleText = gBundle.getString("mediaSelectFolder");
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ aCallback(fp.file.QueryInterface(nsILocalFile));
+ } else {
+ aCallback(null);
+ }
+ };
+
+ fp.init(window, titleText, nsIFilePicker.modeGetFolder);
+ fp.appendFilters(nsIFilePicker.filterAll);
+ try {
+ let prefs = Components.classes[PREFERENCES_CONTRACTID].
+ getService(Components.interfaces.nsIPrefBranch);
+ let initialDir = prefs.getComplexValue("browser.download.dir", nsILocalFile);
+ if (initialDir) {
+ fp.displayDirectory = initialDir;
+ }
+ } catch (ex) {
+ }
+ fp.open(fpCallback);
+}
+
+function saveMedia()
+{
+ var tree = document.getElementById("imagetree");
+ var rowArray = getSelectedRows(tree);
+ if (rowArray.length == 1) {
+ var row = rowArray[0];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+
+ if (url) {
+ var titleKey = "SaveImageTitle";
+
+ if (item instanceof HTMLVideoElement)
+ titleKey = "SaveVideoTitle";
+ else if (item instanceof HTMLAudioElement)
+ titleKey = "SaveAudioTitle";
+
+ saveURL(url, null, titleKey, false, false, makeURI(item.baseURI), gDocument);
+ }
+ } else {
+ selectSaveFolder(function(aDirectory) {
+ if (aDirectory) {
+ var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
+ internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
+ aChosenData, aBaseURI, gDocument);
+ };
+
+ for (var i = 0; i < rowArray.length; i++) {
+ var v = rowArray[i];
+ var dir = aDirectory.clone();
+ var item = gImageView.data[v][COL_IMAGE_NODE];
+ var uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
+ var uri = makeURI(uriString);
+
+ try {
+ uri.QueryInterface(Components.interfaces.nsIURL);
+ dir.append(decodeURIComponent(uri.fileName));
+ } catch(ex) {
+ /* data: uris */
+ }
+
+ if (i == 0) {
+ saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI));
+ } else {
+ // This delay is a hack which prevents the download manager
+ // from opening many times. See bug 377339.
+ setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri),
+ makeURI(item.baseURI));
+ }
+ }
+ }
+ });
+ }
+}
+
+function onBlockImage()
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var checkbox = document.getElementById("blockImage");
+ var uri = makeURI(document.getElementById("imageurltext").value);
+ if (checkbox.checked)
+ permissionManager.add(uri, "image", nsIPermissionManager.DENY_ACTION);
+ else
+ permissionManager.remove(uri.host, "image");
+}
+
+function onImageSelect()
+{
+ var previewBox = document.getElementById("mediaPreviewBox");
+ var mediaSaveBox = document.getElementById("mediaSaveBox");
+ var splitter = document.getElementById("mediaSplitter");
+ var tree = document.getElementById("imagetree");
+ var count = tree.view.selection.count;
+ if (count == 0) {
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = true;
+ tree.flex = 1;
+ }
+ else if (count > 1) {
+ splitter.collapsed = true;
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = false;
+ tree.flex = 1;
+ }
+ else {
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = false;
+ previewBox.collapsed = false;
+ tree.flex = 0;
+ makePreview(getSelectedRows(tree)[0]);
+ }
+}
+
+function makePreview(row)
+{
+ var imageTree = document.getElementById("imagetree");
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ var isBG = gImageView.data[row][COL_IMAGE_BG];
+ var isAudio = false;
+
+ setItemValue("imageurltext", url);
+
+ var imageText;
+ if (!isBG &&
+ !(item instanceof SVGImageElement) &&
+ !(gDocument instanceof ImageDocument)) {
+ imageText = item.title || item.alt;
+
+ if (!imageText && !(item instanceof HTMLImageElement))
+ imageText = getValueText(item);
+ }
+ setItemValue("imagetext", imageText);
+
+ setItemValue("imagelongdesctext", item.longDesc);
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ // find out the file size
+ var sizeText;
+ if (cacheEntry) {
+ var imageSize = cacheEntry.dataSize;
+ var kbSize = Math.round(imageSize / 1024 * 100) / 100;
+ sizeText = gBundle.getFormattedString("generalSize",
+ [formatNumber(kbSize), formatNumber(imageSize)]);
+ }
+ else
+ sizeText = gBundle.getString("mediaUnknownNotCached");
+ setItemValue("imagesizetext", sizeText);
+
+ var mimeType;
+ var numFrames = 1;
+ if (item instanceof HTMLObjectElement ||
+ item instanceof HTMLEmbedElement ||
+ item instanceof HTMLLinkElement)
+ mimeType = item.type;
+
+ if (!mimeType && !isBG && item instanceof nsIImageLoadingContent) {
+ var imageRequest = item.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
+ if (imageRequest) {
+ mimeType = imageRequest.mimeType;
+ var image = imageRequest.image;
+ if (image)
+ numFrames = image.numFrames;
+ }
+ }
+
+ if (!mimeType)
+ mimeType = getContentTypeFromHeaders(cacheEntry);
+
+ // if we have a data url, get the MIME type from the url
+ if (!mimeType && url.startsWith("data:")) {
+ let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
+ if (dataMimeType)
+ mimeType = dataMimeType[1].toLowerCase();
+ }
+
+ var imageType;
+ if (mimeType) {
+ // We found the type, try to display it nicely
+ let imageMimeType = /^image\/(.*)/i.exec(mimeType);
+ if (imageMimeType) {
+ imageType = imageMimeType[1].toUpperCase();
+ if (numFrames > 1)
+ imageType = gBundle.getFormattedString("mediaAnimatedImageType",
+ [imageType, numFrames]);
+ else
+ imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
+ }
+ else {
+ // the MIME type doesn't begin with image/, display the raw type
+ imageType = mimeType;
+ }
+ }
+ else {
+ // We couldn't find the type, fall back to the value in the treeview
+ imageType = gImageView.data[row][COL_IMAGE_TYPE];
+ }
+ setItemValue("imagetypetext", imageType);
+
+ var imageContainer = document.getElementById("theimagecontainer");
+ var oldImage = document.getElementById("thepreviewimage");
+
+ var isProtocolAllowed = checkProtocol(gImageView.data[row]);
+
+ var newImage = new Image;
+ newImage.id = "thepreviewimage";
+ var physWidth = 0, physHeight = 0;
+ var width = 0, height = 0;
+
+ if ((item instanceof HTMLLinkElement || item instanceof HTMLInputElement ||
+ item instanceof HTMLImageElement ||
+ item instanceof SVGImageElement ||
+ (item instanceof HTMLObjectElement && mimeType && mimeType.startsWith("image/")) || isBG) && isProtocolAllowed) {
+ newImage.setAttribute("src", url);
+ physWidth = newImage.width || 0;
+ physHeight = newImage.height || 0;
+
+ // "width" and "height" attributes must be set to newImage,
+ // even if there is no "width" or "height attribute in item;
+ // otherwise, the preview image cannot be displayed correctly.
+ if (!isBG) {
+ newImage.width = ("width" in item && item.width) || newImage.naturalWidth;
+ newImage.height = ("height" in item && item.height) || newImage.naturalHeight;
+ }
+ else {
+ // the Width and Height of an HTML tag should not be used for its background image
+ // (for example, "table" can have "width" or "height" attributes)
+ newImage.width = newImage.naturalWidth;
+ newImage.height = newImage.naturalHeight;
+ }
+
+ if (item instanceof SVGImageElement) {
+ newImage.width = item.width.baseVal.value;
+ newImage.height = item.height.baseVal.value;
+ }
+
+ width = newImage.width;
+ height = newImage.height;
+
+ document.getElementById("theimagecontainer").collapsed = false
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else if (item instanceof HTMLVideoElement && isProtocolAllowed) {
+ newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ width = physWidth = item.videoWidth;
+ height = physHeight = item.videoHeight;
+
+ document.getElementById("theimagecontainer").collapsed = false;
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else if (item instanceof HTMLAudioElement && isProtocolAllowed) {
+ newImage = new Audio;
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ isAudio = true;
+
+ document.getElementById("theimagecontainer").collapsed = false;
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else {
+ // fallback image for protocols not allowed (e.g., javascript:)
+ // or elements not [yet] handled (e.g., object, embed).
+ document.getElementById("brokenimagecontainer").collapsed = false;
+ document.getElementById("theimagecontainer").collapsed = true;
+ }
+
+ var imageSize = "";
+ if (url && !isAudio) {
+ if (width != physWidth || height != physHeight) {
+ imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
+ [formatNumber(physWidth),
+ formatNumber(physHeight),
+ formatNumber(width),
+ formatNumber(height)]);
+ }
+ else {
+ imageSize = gBundle.getFormattedString("mediaDimensions",
+ [formatNumber(width),
+ formatNumber(height)]);
+ }
+ }
+ setItemValue("imagedimensiontext", imageSize);
+
+ makeBlockImage(url);
+
+ imageContainer.removeChild(oldImage);
+ imageContainer.appendChild(newImage);
+
+ onImagePreviewShown.forEach(function(func) { func(); });
+ });
+}
+
+function makeBlockImage(url)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ var prefs = Components.classes[PREFERENCES_CONTRACTID]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ var checkbox = document.getElementById("blockImage");
+ var imagePref = prefs.getIntPref("permissions.default.image");
+ if (!(/^https?:/.test(url)) || imagePref == 2)
+ // We can't block the images from this host because either is is not
+ // for http(s) or we don't load images at all
+ checkbox.hidden = true;
+ else {
+ var uri = makeURI(url);
+ if (uri.host) {
+ checkbox.hidden = false;
+ checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
+ var perm = permissionManager.testPermission(uri, "image");
+ checkbox.checked = perm == nsIPermissionManager.DENY_ACTION;
+ }
+ else
+ checkbox.hidden = true;
+ }
+}
+
+var imagePermissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (document.getElementById("mediaPreviewBox").collapsed)
+ return;
+
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+ if (permission.type == "image") {
+ var imageTree = document.getElementById("imagetree");
+ var row = getSelectedRow(imageTree);
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ if (makeURI(url).host == permission.host)
+ makeBlockImage(url);
+ }
+ }
+ }
+}
+
+function getContentTypeFromHeaders(cacheEntryDescriptor)
+{
+ if (!cacheEntryDescriptor)
+ return null;
+
+ return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi
+ .exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1];
+}
+
+//******** Other Misc Stuff
+// Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
+// parse a node to extract the contents of the node
+function getValueText(node)
+{
+ var valueText = "";
+
+ // form input elements don't generally contain information that is useful to our callers, so return nothing
+ if (node instanceof HTMLInputElement ||
+ node instanceof HTMLSelectElement ||
+ node instanceof HTMLTextAreaElement)
+ return valueText;
+
+ // otherwise recurse for each child
+ var length = node.childNodes.length;
+ for (var i = 0; i < length; i++) {
+ var childNode = node.childNodes[i];
+ var nodeType = childNode.nodeType;
+
+ // text nodes are where the goods are
+ if (nodeType == Node.TEXT_NODE)
+ valueText += " " + childNode.nodeValue;
+ // and elements can have more text inside them
+ else if (nodeType == Node.ELEMENT_NODE) {
+ // images are special, we want to capture the alt text as if the image weren't there
+ if (childNode instanceof HTMLImageElement)
+ valueText += " " + getAltText(childNode);
+ else
+ valueText += " " + getValueText(childNode);
+ }
+ }
+
+ return stripWS(valueText);
+}
+
+// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
+// traverse the tree in search of an img or area element and grab its alt tag
+function getAltText(node)
+{
+ var altText = "";
+
+ if (node.alt)
+ return node.alt;
+ var length = node.childNodes.length;
+ for (var i = 0; i < length; i++)
+ if ((altText = getAltText(node.childNodes[i]) != undefined)) // stupid js warning...
+ return altText;
+ return "";
+}
+
+// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
+// strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space
+function stripWS(text)
+{
+ var middleRE = /\s+/g;
+ var endRE = /(^\s+)|(\s+$)/g;
+
+ text = text.replace(middleRE, " ");
+ return text.replace(endRE, "");
+}
+
+function setItemValue(id, value)
+{
+ var item = document.getElementById(id);
+ if (value) {
+ item.parentNode.collapsed = false;
+ item.value = value;
+ }
+ else
+ item.parentNode.collapsed = true;
+}
+
+function formatNumber(number)
+{
+ return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
+}
+
+function formatDate(datestr, unknown)
+{
+ // scriptable date formatter, for pretty printing dates
+ var dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Components.interfaces.nsIScriptableDateFormat);
+
+ var date = new Date(datestr);
+ if (!date.valueOf())
+ return unknown;
+
+ return dateService.FormatDateTime("", dateService.dateFormatLong,
+ dateService.timeFormatSeconds,
+ date.getFullYear(), date.getMonth()+1, date.getDate(),
+ date.getHours(), date.getMinutes(), date.getSeconds());
+}
+
+function doCopy()
+{
+ if (!gClipboardHelper)
+ return;
+
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem) {
+ var view = elem.view;
+ var selection = view.selection;
+ var text = [], tmp = '';
+ var min = {}, max = {};
+
+ var count = selection.getRangeCount();
+
+ for (var i = 0; i < count; i++) {
+ selection.getRangeAt(i, min, max);
+
+ for (var row = min.value; row <= max.value; row++) {
+ view.performActionOnRow("copy", row);
+
+ tmp = elem.getAttribute("copybuffer");
+ if (tmp)
+ text.push(tmp);
+ elem.removeAttribute("copybuffer");
+ }
+ }
+ gClipboardHelper.copyString(text.join("\n"), document);
+ }
+}
+
+function doSelectAll()
+{
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem)
+ elem.view.selection.selectAll();
+}
+
+function selectImage()
+{
+ if (!gImageElement)
+ return;
+
+ var tree = document.getElementById("imagetree");
+ for (var i = 0; i < tree.view.rowCount; i++) {
+ if (gImageElement == gImageView.data[i][COL_IMAGE_NODE] &&
+ !gImageView.data[i][COL_IMAGE_BG]) {
+ tree.view.selection.select(i);
+ tree.treeBoxObject.ensureRowIsVisible(i);
+ tree.focus();
+ return;
+ }
+ }
+}
+
+function checkProtocol(img)
+{
+ var url = img[COL_IMAGE_ADDRESS];
+ return /^data:image\//i.test(url) ||
+ /^(https?|ftp|file|about|chrome|resource):/.test(url);
+}
diff --git a/application/palemoon/base/content/pageinfo/pageInfo.xml b/application/palemoon/base/content/pageinfo/pageInfo.xml
new file mode 100644
index 000000000..20d330046
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/pageInfo.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="pageInfoBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <!-- based on preferences.xml paneButton -->
+ <binding id="viewbutton" extends="chrome://global/content/bindings/radio.xml#radio">
+ <content>
+ <xul:image class="viewButtonIcon" xbl:inherits="src"/>
+ <xul:label class="viewButtonLabel" xbl:inherits="value=label"/>
+ </content>
+ <implementation implements="nsIAccessibleProvider">
+ <property name="accessibleType" readonly="true">
+ <getter>
+ <![CDATA[
+ return Components.interfaces.nsIAccessibleProvider.XULListitem;
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/application/palemoon/base/content/pageinfo/pageInfo.xul b/application/palemoon/base/content/pageinfo/pageInfo.xul
new file mode 100644
index 000000000..5bca1b495
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/pageInfo.xul
@@ -0,0 +1,559 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/pageinfo/pageInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/pageInfo.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<window id="main-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="Browser:page-info"
+ onload="onLoadPageInfo()"
+ onunload="onUnloadPageInfo()"
+ align="stretch"
+ screenX="10" screenY="10"
+ width="&pageInfoWindow.width;" height="&pageInfoWindow.height;"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <stringbundleset id="pageinfobundleset">
+ <stringbundle id="pageinfobundle" src="chrome://browser/locale/pageInfo.properties"/>
+ <stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
+ <stringbundle id="browserBundle" src="chrome://browser/locale/browser.properties"/>
+ </stringbundleset>
+
+ <commandset id="pageInfoCommandSet">
+ <command id="cmd_close" oncommand="window.close();"/>
+ <command id="cmd_help" oncommand="doHelpButton();"/>
+ <command id="cmd_copy" oncommand="doCopy();"/>
+ <command id="cmd_selectall" oncommand="doSelectAll();"/>
+
+ <!-- permissions tab -->
+ <command id="cmd_imageDef" oncommand="onCheckboxClick('image');"/>
+ <command id="cmd_popupDef" oncommand="onCheckboxClick('popup');"/>
+ <command id="cmd_cookieDef" oncommand="onCheckboxClick('cookie');"/>
+ <command id="cmd_desktop-notificationDef" oncommand="onCheckboxClick('desktop-notification');"/>
+ <command id="cmd_installDef" oncommand="onCheckboxClick('install');"/>
+ <command id="cmd_fullscreenDef" oncommand="onCheckboxClick('fullscreen');"/>
+ <command id="cmd_geoDef" oncommand="onCheckboxClick('geo');"/>
+ <command id="cmd_indexedDBDef" oncommand="onCheckboxClick('indexedDB');"/>
+ <command id="cmd_pluginsDef" oncommand="onCheckboxClick('plugins');"/>
+ <command id="cmd_imageToggle" oncommand="onRadioClick('image');"/>
+ <command id="cmd_popupToggle" oncommand="onRadioClick('popup');"/>
+ <command id="cmd_cookieToggle" oncommand="onRadioClick('cookie');"/>
+ <command id="cmd_desktop-notificationToggle" oncommand="onRadioClick('desktop-notification');"/>
+ <command id="cmd_installToggle" oncommand="onRadioClick('install');"/>
+ <command id="cmd_fullscreenToggle" oncommand="onRadioClick('fullscreen');"/>
+ <command id="cmd_geoToggle" oncommand="onRadioClick('geo');"/>
+ <command id="cmd_indexedDBToggle" oncommand="onRadioClick('indexedDB');"/>
+ <command id="cmd_pluginsToggle" oncommand="onPluginRadioClick(event);"/>
+ <command id="cmd_pointerLockDef" oncommand="onCheckboxClick('pointerLock');"/>
+ <command id="cmd_pointerLockToggle" oncommand="onRadioClick('pointerLock');"/>
+ </commandset>
+
+ <keyset id="pageInfoKeySet">
+ <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
+ <key keycode="VK_ESCAPE" command="cmd_close"/>
+#ifdef XP_MACOSX
+ <key key="." modifiers="meta" command="cmd_close"/>
+#else
+ <key keycode="VK_F1" command="cmd_help"/>
+#endif
+ <key key="&copy.key;" modifiers="accel" command="cmd_copy"/>
+ <key key="&selectall.key;" modifiers="accel" command="cmd_selectall"/>
+ <key key="&selectall.key;" modifiers="alt" command="cmd_selectall"/>
+ </keyset>
+
+ <menupopup id="picontext">
+ <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
+ <menuitem id="menu_copy" label="&copy.label;" command="cmd_copy" accesskey="&copy.accesskey;"/>
+ </menupopup>
+
+ <windowdragbox id="topBar" class="viewGroupWrapper">
+ <radiogroup id="viewGroup" class="chromeclass-toolbar" orient="horizontal">
+ <radio id="generalTab" label="&generalTab;" accesskey="&generalTab.accesskey;"
+ oncommand="showTab('general');"/>
+ <radio id="mediaTab" label="&mediaTab;" accesskey="&mediaTab.accesskey;"
+ oncommand="showTab('media');" hidden="true"/>
+ <radio id="feedTab" label="&feedTab;" accesskey="&feedTab.accesskey;"
+ oncommand="showTab('feed');" hidden="true"/>
+ <radio id="permTab" label="&permTab;" accesskey="&permTab.accesskey;"
+ oncommand="showTab('perm');"/>
+ <radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
+ oncommand="showTab('security');"/>
+ <!-- Others added by overlay -->
+ </radiogroup>
+ </windowdragbox>
+
+ <deck id="mainDeck" flex="1">
+ <!-- General page information -->
+ <vbox id="generalPanel">
+ <textbox class="header" readonly="true" id="titletext"/>
+ <grid id="generalGrid">
+ <columns>
+ <column/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="generalRows">
+ <row id="generalURLRow">
+ <label control="urltext" value="&generalURL;"/>
+ <separator/>
+ <textbox readonly="true" id="urltext"/>
+ </row>
+ <row id="generalSeparatorRow1">
+ <separator class="thin"/>
+ </row>
+ <row id="generalTypeRow">
+ <label control="typetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="typetext"/>
+ </row>
+ <row id="generalModeRow">
+ <label control="modetext" value="&generalMode;"/>
+ <separator/>
+ <textbox readonly="true" crop="end" id="modetext"/>
+ </row>
+ <row id="generalEncodingRow">
+ <label control="encodingtext" value="&generalEncoding;"/>
+ <separator/>
+ <textbox readonly="true" id="encodingtext"/>
+ </row>
+ <row id="generalSizeRow">
+ <label control="sizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="sizetext"/>
+ </row>
+ <row id="generalReferrerRow">
+ <label control="refertext" value="&generalReferrer;"/>
+ <separator/>
+ <textbox readonly="true" id="refertext"/>
+ </row>
+ <row id="generalSeparatorRow2">
+ <separator class="thin"/>
+ </row>
+ <row id="generalModifiedRow">
+ <label control="modifiedtext" value="&generalModified;"/>
+ <separator/>
+ <textbox readonly="true" id="modifiedtext"/>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <groupbox id="metaTags" flex="1" class="collapsable treebox">
+ <caption id="metaTagsCaption" onclick="toggleGroupbox('metaTags');"/>
+ <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
+ <treecols>
+ <treecol id="meta-name" label="&generalMetaName;"
+ persist="width" flex="1"
+ onclick="gMetaView.onPageMediaSort('meta-name');"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="meta-content" label="&generalMetaContent;"
+ persist="width" flex="4"
+ onclick="gMetaView.onPageMediaSort('meta-content');"/>
+ </treecols>
+ <treechildren id="metatreechildren" flex="1"/>
+ </tree>
+ </groupbox>
+ <groupbox id="securityBox">
+ <caption id="securityBoxCaption" label="&securityHeader;"/>
+ <description id="general-security-identity" class="header"/>
+ <description id="general-security-privacy" class="header"/>
+ <hbox id="securityDetailsButtonBox" align="right">
+ <button id="security-view-details" label="&generalSecurityDetails;"
+ accesskey="&generalSecurityDetails.accesskey;"
+ oncommand="onClickMore();"/>
+ </hbox>
+ </groupbox>
+ </vbox>
+
+ <!-- Media information -->
+ <vbox id="mediaPanel">
+ <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
+ ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
+ <treecols>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="10"
+ width="10" id="image-address" label="&mediaAddress;"
+ onclick="gImageView.onPageMediaSort('image-address');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="image-type" label="&mediaType;"
+ onclick="gImageView.onPageMediaSort('image-type');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="2"
+ width="2" id="image-size" label="&mediaSize;" value="size"
+ onclick="gImageView.onPageMediaSort('image-size');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
+ width="4" id="image-alt" label="&mediaAltHeader;"
+ onclick="gImageView.onPageMediaSort('image-alt');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
+ width="1" id="image-count" label="&mediaCount;"
+ onclick="gImageView.onPageMediaSort('image-count');"/>
+ </treecols>
+ <treechildren id="imagetreechildren" flex="1"/>
+ </tree>
+ <splitter orient="vertical" id="mediaSplitter"/>
+ <vbox flex="1" id="mediaPreviewBox" collapsed="true">
+ <grid id="mediaGrid">
+ <columns>
+ <column id="mediaLabelColumn"/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="mediaRows">
+ <row id="mediaLocationRow">
+ <label control="imageurltext" value="&mediaLocation;"/>
+ <separator/>
+ <textbox readonly="true" id="imageurltext"/>
+ </row>
+ <row id="mediaTypeRow">
+ <label control="imagetypetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetypetext"/>
+ </row>
+ <row id="mediaSizeRow">
+ <label control="imagesizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="imagesizetext"/>
+ </row>
+ <row id="mediaDimensionRow">
+ <label control="imagedimensiontext" value="&mediaDimension;"/>
+ <separator/>
+ <textbox readonly="true" id="imagedimensiontext"/>
+ </row>
+ <row id="mediaTextRow">
+ <label control="imagetext" value="&mediaText;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetext"/>
+ </row>
+ <row id="mediaLongdescRow">
+ <label control="imagelongdesctext" value="&mediaLongdesc;"/>
+ <separator/>
+ <textbox readonly="true" id="imagelongdesctext"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox id="imageSaveBox" align="end">
+ <vbox id="blockImageBox">
+ <checkbox id="blockImage" hidden="true" oncommand="onBlockImage()"
+ accesskey="&mediaBlockImage.accesskey;"/>
+ <label control="thepreviewimage" value="&mediaPreview;" class="header"/>
+ </vbox>
+ <spacer id="imageSaveBoxSpacer" flex="1"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;"
+ icon="save" id="imagesaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <vbox id="imagecontainerbox" class="inset iframe" flex="1" pack="center">
+ <hbox id="theimagecontainer" pack="center">
+ <image id="thepreviewimage"/>
+ </hbox>
+ <hbox id="brokenimagecontainer" pack="center" collapsed="true">
+ <image id="brokenimage" src="resource://gre-resources/broken-image.png"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ <hbox id="mediaSaveBox" collapsed="true">
+ <spacer id="mediaSaveBoxSpacer" flex="1"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;"
+ icon="save" id="mediasaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ </vbox>
+
+ <!-- Feeds -->
+ <vbox id="feedPanel">
+ <richlistbox id="feedListbox" flex="1"/>
+ </vbox>
+
+ <!-- Permissions -->
+ <vbox id="permPanel">
+ <hbox id="permHostBox">
+ <label value="&permissionsFor;" control="hostText" />
+ <textbox id="hostText" class="header" readonly="true"
+ crop="end" flex="1"/>
+ </hbox>
+
+ <vbox id="permList" flex="1">
+ <vbox class="permission" id="permImageRow">
+ <label class="permissionLabel" id="permImageLabel"
+ value="&permImage;" control="imageRadioGroup"/>
+ <hbox id="permImageBox" role="group" aria-labelledby="permImageLabel">
+ <checkbox id="imageDef" command="cmd_imageDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="imageRadioGroup" orient="horizontal">
+ <radio id="image#1" command="cmd_imageToggle" label="&permAllow;"/>
+ <radio id="image#2" command="cmd_imageToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permPopupRow">
+ <label class="permissionLabel" id="permPopupLabel"
+ value="&permPopup;" control="popupRadioGroup"/>
+ <hbox id="permPopupBox" role="group" aria-labelledby="permPopupLabel">
+ <checkbox id="popupDef" command="cmd_popupDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="popupRadioGroup" orient="horizontal">
+ <radio id="popup#1" command="cmd_popupToggle" label="&permAllow;"/>
+ <radio id="popup#2" command="cmd_popupToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permCookieRow">
+ <label class="permissionLabel" id="permCookieLabel"
+ value="&permCookie;" control="cookieRadioGroup"/>
+ <hbox id="permCookieBox" role="group" aria-labelledby="permCookieLabel">
+ <checkbox id="cookieDef" command="cmd_cookieDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="cookieRadioGroup" orient="horizontal">
+ <radio id="cookie#1" command="cmd_cookieToggle" label="&permAllow;"/>
+ <radio id="cookie#8" command="cmd_cookieToggle" label="&permAllowSession;"/>
+ <radio id="cookie#9" command="cmd_cookieToggle" label="&permAllowFirstPartyOnly;"/>
+ <radio id="cookie#2" command="cmd_cookieToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permNotificationRow">
+ <label class="permissionLabel" id="permNotificationLabel"
+ value="&permNotifications;" control="desktop-notificationRadioGroup"/>
+ <hbox role="group" aria-labelledby="permNotificationLabel">
+ <checkbox id="desktop-notificationDef" command="cmd_desktop-notificationDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="desktop-notificationRadioGroup" orient="horizontal">
+ <radio id="desktop-notification#0" command="cmd_desktop-notificationToggle" label="&permAskAlways;"/>
+ <radio id="desktop-notification#1" command="cmd_desktop-notificationToggle" label="&permAllow;"/>
+ <radio id="desktop-notification#2" command="cmd_desktop-notificationToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permInstallRow">
+ <label class="permissionLabel" id="permInstallLabel"
+ value="&permInstall;" control="installRadioGroup"/>
+ <hbox id="permInstallBox" role="group" aria-labelledby="permInstallLabel">
+ <checkbox id="installDef" command="cmd_installDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="installRadioGroup" orient="horizontal">
+ <radio id="install#1" command="cmd_installToggle" label="&permAllow;"/>
+ <radio id="install#2" command="cmd_installToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permGeoRow" >
+ <label class="permissionLabel" id="permGeoLabel"
+ value="&permGeo;" control="geoRadioGroup"/>
+ <hbox id="permGeoBox" role="group" aria-labelledby="permGeoLabel">
+ <checkbox id="geoDef" command="cmd_geoDef" label="&permAskAlways;"/>
+ <spacer flex="1"/>
+ <radiogroup id="geoRadioGroup" orient="horizontal">
+ <radio id="geo#1" command="cmd_geoToggle" label="&permAllow;"/>
+ <radio id="geo#2" command="cmd_geoToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permIndexedDBRow">
+ <label class="permissionLabel" id="permIndexedDBLabel"
+ value="&permIndexedDB;" control="indexedDBRadioGroup"/>
+ <hbox id="permIndexedDBBox" role="group" aria-labelledby="permIndexedDBLabel">
+ <checkbox id="indexedDBDef" command="cmd_indexedDBDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="indexedDBRadioGroup" orient="horizontal">
+ <radio id="indexedDB#0" command="cmd_indexedDBToggle" label="&permAskAlways;"/>
+ <radio id="indexedDB#1" command="cmd_indexedDBToggle" label="&permAllow;"/>
+ <radio id="indexedDB#2" command="cmd_indexedDBToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ <hbox id="permIndexedDBExtras">
+ <spacer flex="1"/>
+ <vbox id="permIndexedDBStatusBox" pack="center">
+ <label id="indexedDBStatus" control="indexedDBClear" hidden="true"/>
+ </vbox>
+ <button id="indexedDBClear" label="&permClearStorage;" hidden="true"
+ accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permPluginsRow">
+ <label class="permissionLabel" id="permPluginsLabel"
+ value="&permPlugins;" control="pluginsRadioGroup"/>
+ <hbox id="permPluginTemplate" role="group" aria-labelledby="permPluginsLabel" align="baseline">
+ <label class="permPluginTemplateLabel"/>
+ <spacer flex="1"/>
+ <radiogroup class="permPluginTemplateRadioGroup" orient="horizontal" command="cmd_pluginsToggle">
+ <radio class="permPluginTemplateRadioDefault" label="&permUseDefault;"/>
+ <radio class="permPluginTemplateRadioAsk" label="&permAskAlways;"/>
+ <radio class="permPluginTemplateRadioAllow" label="&permAllow;"/>
+ <radio class="permPluginTemplateRadioBlock" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permFullscreenRow">
+ <label class="permissionLabel" id="permFullscreenLabel"
+ value="&permFullscreen;" control="fullscreenRadioGroup"/>
+ <hbox id="permFullscreenBox" role="group" aria-labelledby="permFullscreenLabel">
+ <checkbox id="fullscreenDef" command="cmd_fullscreenDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="fullscreenRadioGroup" orient="horizontal">
+ <radio id="fullscreen#0" command="cmd_fullscreenToggle" label="&permAskAlways;"/>
+ <radio id="fullscreen#1" command="cmd_fullscreenToggle" label="&permAllow;"/>
+ <radio id="fullscreen#2" command="cmd_fullscreenToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permPointerLockRow" >
+ <label class="permissionLabel" id="permPointerLockLabel"
+ value="&permPointerLock2;" control="pointerLockRadioGroup"/>
+ <hbox id="permPointerLockBox" role="group" aria-labelledby="permPointerLockLabel">
+ <checkbox id="pointerLockDef" command="cmd_pointerLockDef" label="&permAskAlways;"/>
+ <spacer flex="1"/>
+ <radiogroup id="pointerLockRadioGroup" orient="horizontal">
+ <radio id="pointerLock#1" command="cmd_pointerLockToggle" label="&permAllow;"/>
+ <radio id="pointerLock#2" command="cmd_pointerLockToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+
+ <!-- Security & Privacy -->
+ <vbox id="securityPanel">
+ <!-- Identity Section -->
+ <groupbox id="security-identity-groupbox" flex="1">
+ <caption id="security-identity" label="&securityView.identity.header;"/>
+ <grid id="security-identity-grid" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-identity-rows">
+ <!-- Domain -->
+ <row id="security-identity-domain-row">
+ <label id="security-identity-domain-label"
+ class="fieldLabel"
+ value="&securityView.identity.domain;"
+ control="security-identity-domain-value"/>
+ <textbox id="security-identity-domain-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Owner -->
+ <row id="security-identity-owner-row">
+ <label id="security-identity-owner-label"
+ class="fieldLabel"
+ value="&securityView.identity.owner;"
+ control="security-identity-owner-value"/>
+ <textbox id="security-identity-owner-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Verifier -->
+ <row id="security-identity-verifier-row">
+ <label id="security-identity-verifier-label"
+ class="fieldLabel"
+ value="&securityView.identity.verifier;"
+ control="security-identity-verifier-value"/>
+ <textbox id="security-identity-verifier-value"
+ class="fieldValue" readonly="true" />
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ <!-- Cert button -->
+ <hbox id="security-view-cert-box" pack="end">
+ <button id="security-view-cert" label="&securityView.certView;"
+ accesskey="&securityView.accesskey;"
+ oncommand="security.viewCert();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Privacy & History section -->
+ <groupbox id="security-privacy-groupbox" flex="1">
+ <caption id="security-privacy" label="&securityView.privacy.header;" />
+ <grid id="security-privacy-grid">
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-privacy-rows">
+ <!-- History -->
+ <row id="security-privacy-history-row">
+ <label id="security-privacy-history-label"
+ control="security-privacy-history-value"
+ class="fieldLabel">&securityView.privacy.history;</label>
+ <textbox id="security-privacy-history-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ readonly="true"/>
+ </row>
+ <!-- Cookies -->
+ <row id="security-privacy-cookies-row">
+ <label id="security-privacy-cookies-label"
+ control="security-privacy-cookies-value"
+ class="fieldLabel">&securityView.privacy.cookies;</label>
+ <hbox id="security-privacy-cookies-box" align="center">
+ <textbox id="security-privacy-cookies-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-cookies"
+ label="&securityView.privacy.viewCookies;"
+ accesskey="&securityView.privacy.viewCookies.accessKey;"
+ oncommand="security.viewCookies();"/>
+ </hbox>
+ </row>
+ <!-- Passwords -->
+ <row id="security-privacy-passwords-row">
+ <label id="security-privacy-passwords-label"
+ control="security-privacy-passwords-value"
+ class="fieldLabel">&securityView.privacy.passwords;</label>
+ <hbox id="security-privacy-passwords-box" align="center">
+ <textbox id="security-privacy-passwords-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-password"
+ label="&securityView.privacy.viewPasswords;"
+ accesskey="&securityView.privacy.viewPasswords.accessKey;"
+ oncommand="security.viewPasswords();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- Technical Details section -->
+ <groupbox id="security-technical-groupbox" flex="1">
+ <caption id="security-technical" label="&securityView.technical.header;" />
+ <vbox id="security-technical-box" flex="1">
+ <label id="security-technical-shortform" class="fieldValue"/>
+ <description id="security-technical-longform1" class="fieldLabel"/>
+ <description id="security-technical-longform2" class="fieldLabel"/>
+ </vbox>
+ </groupbox>
+ </vbox>
+ <!-- Others added by overlay -->
+ </deck>
+
+#ifdef XP_MACOSX
+#include ../browserMountPoints.inc
+#endif
+
+</window>
diff --git a/application/palemoon/base/content/pageinfo/permissions.js b/application/palemoon/base/content/pageinfo/permissions.js
new file mode 100644
index 000000000..7a0006b61
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/permissions.js
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const UNKNOWN = nsIPermissionManager.UNKNOWN_ACTION; // 0
+const ALLOW = nsIPermissionManager.ALLOW_ACTION; // 1
+const DENY = nsIPermissionManager.DENY_ACTION; // 2
+const SESSION = nsICookiePermission.ACCESS_SESSION; // 8
+
+const IMAGE_DENY = 2;
+
+const COOKIE_DENY = 2;
+const COOKIE_SESSION = 2;
+
+const nsIQuotaManager = Components.interfaces.nsIQuotaManager;
+
+var gPermURI;
+var gPrefs;
+var gUsageRequest;
+
+var gPermObj = {
+ image: function getImageDefaultPermission()
+ {
+ if (gPrefs.getIntPref("permissions.default.image") == IMAGE_DENY) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ popup: function getPopupDefaultPermission()
+ {
+ if (gPrefs.getBoolPref("dom.disable_open_during_load")) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ cookie: function getCookieDefaultPermission()
+ {
+ if (gPrefs.getIntPref("network.cookie.cookieBehavior") == COOKIE_DENY) {
+ return DENY;
+ }
+ if (gPrefs.getIntPref("network.cookie.lifetimePolicy") == COOKIE_SESSION) {
+ return SESSION;
+ }
+ return ALLOW;
+ },
+ "desktop-notification": function getNotificationDefaultPermission()
+ {
+ if (!gPrefs.getBoolPref("dom.webnotifications.enabled")) {
+ return DENY;
+ }
+ return UNKNOWN;
+ },
+ install: function getInstallDefaultPermission()
+ {
+ if (Services.prefs.getBoolPref("xpinstall.whitelist.required")) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ geo: function getGeoDefaultPermissions()
+ {
+ if (!gPrefs.getBoolPref("geo.enabled")) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ indexedDB: function getIndexedDBDefaultPermissions()
+ {
+ if (!gPrefs.getBoolPref("dom.indexedDB.enabled")) {
+ return DENY;
+ }
+ return UNKNOWN;
+ },
+ plugins: function getPluginsDefaultPermissions()
+ {
+ return UNKNOWN;
+ },
+ fullscreen: function getFullscreenDefaultPermissions()
+ {
+ if (!gPrefs.getBoolPref("full-screen-api.enabled")) {
+ return DENY;
+ }
+ return UNKNOWN;
+ },
+ pointerLock: function getPointerLockPermissions()
+ {
+ if (!gPrefs.getBoolPref("full-screen-api.pointer-lock.enabled")) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+};
+
+var permissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(
+ Components.interfaces.nsIPermission);
+ if (permission.host == gPermURI.host) {
+ if (permission.type in gPermObj)
+ initRow(permission.type);
+ else if (permission.type.startsWith("plugin"))
+ setPluginsRadioState();
+ }
+ }
+ }
+};
+
+function onLoadPermission()
+{
+ gPrefs = Components.classes[PREFERENCES_CONTRACTID]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ var uri = gDocument.documentURIObject;
+ var permTab = document.getElementById("permTab");
+ if (/^https?$/.test(uri.scheme)) {
+ gPermURI = uri;
+ var hostText = document.getElementById("hostText");
+ hostText.value = gPermURI.host;
+
+ for (var i in gPermObj)
+ initRow(i);
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(permissionObserver, "perm-changed", false);
+ onUnloadRegistry.push(onUnloadPermission);
+ permTab.hidden = false;
+ }
+ else
+ permTab.hidden = true;
+}
+
+function onUnloadPermission()
+{
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(permissionObserver, "perm-changed");
+
+ if (gUsageRequest) {
+ gUsageRequest.cancel();
+ gUsageRequest = null;
+ }
+}
+
+function initRow(aPartId)
+{
+ if (aPartId == "plugins") {
+ initPluginsRow();
+ return;
+ }
+
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var checkbox = document.getElementById(aPartId + "Def");
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ // Desktop Notification, Geolocation and PointerLock permission consumers
+ // use testExactPermission, not testPermission.
+ var perm;
+ if (aPartId == "desktop-notification" || aPartId == "geo" || aPartId == "pointerLock")
+ perm = permissionManager.testExactPermission(gPermURI, aPartId);
+ else
+ perm = permissionManager.testPermission(gPermURI, aPartId);
+
+ if (perm) {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ }
+ else {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ perm = gPermObj[aPartId]();
+ }
+ setRadioState(aPartId, perm);
+
+ if (aPartId == "indexedDB") {
+ initIndexedDBRow();
+ }
+}
+
+function onCheckboxClick(aPartId)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var checkbox = document.getElementById(aPartId + "Def");
+ if (checkbox.checked) {
+ permissionManager.remove(gPermURI.host, aPartId);
+ command.setAttribute("disabled", "true");
+ var perm = gPermObj[aPartId]();
+ setRadioState(aPartId, perm);
+ }
+ else {
+ onRadioClick(aPartId);
+ command.removeAttribute("disabled");
+ }
+}
+
+function onPluginRadioClick(aEvent) {
+ onRadioClick(aEvent.originalTarget.getAttribute("id").split('#')[0]);
+}
+
+function onRadioClick(aPartId)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var radioGroup = document.getElementById(aPartId + "RadioGroup");
+ var id = radioGroup.selectedItem.id;
+ var permission = id.split('#')[1];
+ if (permission == UNKNOWN) {
+ permissionManager.remove(gPermURI.host, aPartId);
+ } else {
+ permissionManager.add(gPermURI, aPartId, permission);
+ }
+}
+
+function setRadioState(aPartId, aValue)
+{
+ var radio = document.getElementById(aPartId + "#" + aValue);
+ radio.radioGroup.selectedItem = radio;
+}
+
+function initIndexedDBRow()
+{
+ let row = document.getElementById("permIndexedDBRow");
+ let extras = document.getElementById("permIndexedDBExtras");
+
+ row.appendChild(extras);
+
+ var quotaManager = Components.classes["@mozilla.org/dom/quota/manager;1"]
+ .getService(nsIQuotaManager);
+ gUsageRequest =
+ quotaManager.getUsageForURI(gPermURI, onIndexedDBUsageCallback);
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value = "";
+ status.setAttribute("hidden", "true");
+ button.setAttribute("hidden", "true");
+}
+
+function onIndexedDBClear()
+{
+ Components.classes["@mozilla.org/dom/quota/manager;1"]
+ .getService(nsIQuotaManager)
+ .clearStoragesForURI(gPermURI);
+
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ permissionManager.remove(gPermURI.host, "indexedDB");
+ initIndexedDBRow();
+}
+
+function onIndexedDBUsageCallback(uri, usage, fileUsage)
+{
+ if (!uri.equals(gPermURI)) {
+ throw new Error("Callback received for bad URI: " + uri);
+ }
+
+ if (usage) {
+ if (!("DownloadUtils" in window)) {
+ Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+ }
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value =
+ gBundle.getFormattedString("indexedDBUsage",
+ DownloadUtils.convertByteUnits(usage));
+ status.removeAttribute("hidden");
+ button.removeAttribute("hidden");
+ }
+}
+
+// XXX copied this from browser-plugins.js - is there a way to share?
+function makeNicePluginName(aName) {
+ if (aName == "Shockwave Flash")
+ return "Adobe Flash";
+
+ // Clean up the plugin name by stripping off any trailing version numbers
+ // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
+ // Do this by first stripping the numbers, etc. off the end, and then
+ // removing "Plugin" (and then trimming to get rid of any whitespace).
+ // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
+ let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
+ return newName;
+}
+
+function fillInPluginPermissionTemplate(aPermissionString, aPluginObject) {
+ let permPluginTemplate = document.getElementById("permPluginTemplate")
+ .cloneNode(true);
+ permPluginTemplate.setAttribute("permString", aPermissionString);
+ permPluginTemplate.setAttribute("tooltiptext", aPluginObject.description);
+ let attrs = [
+ [ ".permPluginTemplateLabel", "value", aPluginObject.name ],
+ [ ".permPluginTemplateRadioGroup", "id", aPermissionString + "RadioGroup" ],
+ [ ".permPluginTemplateRadioDefault", "id", aPermissionString + "#0" ],
+ [ ".permPluginTemplateRadioAsk", "id", aPermissionString + "#3" ],
+ [ ".permPluginTemplateRadioAllow", "id", aPermissionString + "#1" ],
+ [ ".permPluginTemplateRadioBlock", "id", aPermissionString + "#2" ]
+ ];
+
+ for (let attr of attrs) {
+ permPluginTemplate.querySelector(attr[0]).setAttribute(attr[1], attr[2]);
+ }
+
+ return permPluginTemplate;
+}
+
+function clearPluginPermissionTemplate() {
+ let permPluginTemplate = document.getElementById("permPluginTemplate");
+ permPluginTemplate.hidden = true;
+ permPluginTemplate.removeAttribute("permString");
+ permPluginTemplate.removeAttribute("tooltiptext");
+ document.querySelector(".permPluginTemplateLabel").removeAttribute("value");
+ document.querySelector(".permPluginTemplateRadioGroup").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAsk").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAllow").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioBlock").removeAttribute("id");
+}
+
+function initPluginsRow() {
+ let vulnerableLabel = document.getElementById("browserBundle")
+ .getString("pluginActivateVulnerable.label");
+ let pluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+ .getService(Components.interfaces.nsIPluginHost);
+ let tags = pluginHost.getPluginTags();
+
+ let permissionMap = new Map();
+
+ for (let plugin of tags) {
+ if (plugin.disabled) {
+ continue;
+ }
+ for (let mimeType of plugin.getMimeTypes()) {
+ if (mimeType == "application/x-shockwave-flash" && plugin.name != "Shockwave Flash") {
+ continue;
+ }
+ let permString = pluginHost.getPermissionStringForType(mimeType);
+ if (!permissionMap.has(permString)) {
+ var name = makeNicePluginName(plugin.name) + " " + plugin.version;
+ if (permString.startsWith("plugin-vulnerable:")) {
+ name += " \u2014 " + vulnerableLabel;
+ }
+ permissionMap.set(permString, {
+ "name": name,
+ "description": plugin.description,
+ });
+ }
+ }
+ }
+
+ let entries = [
+ {
+ "permission": item[0],
+ "obj": item[1],
+ }
+ for (item of permissionMap)
+ ];
+ entries.sort(function(a, b) {
+ return ((a.obj.name < b.obj.name) ? -1 : (a.obj.name == b.obj.name ? 0 : 1));
+ });
+
+ let permissionEntries = [
+ fillInPluginPermissionTemplate(p.permission, p.obj) for (p of entries)
+ ];
+
+ let permPluginsRow = document.getElementById("permPluginsRow");
+ clearPluginPermissionTemplate();
+ if (permissionEntries.length < 1) {
+ permPluginsRow.hidden = true;
+ return;
+ }
+
+ for (let permissionEntry of permissionEntries) {
+ permPluginsRow.appendChild(permissionEntry);
+ }
+
+ setPluginsRadioState();
+}
+
+function setPluginsRadioState() {
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ let box = document.getElementById("permPluginsRow");
+ for (let permissionEntry of box.childNodes) {
+ if (permissionEntry.hasAttribute("permString")) {
+ let permString = permissionEntry.getAttribute("permString");
+ let permission = permissionManager.testPermission(gPermURI, permString);
+ setRadioState(permString, permission);
+ }
+ }
+}
diff --git a/application/palemoon/base/content/pageinfo/security.js b/application/palemoon/base/content/pageinfo/security.js
new file mode 100644
index 000000000..e791ab92a
--- /dev/null
+++ b/application/palemoon/base/content/pageinfo/security.js
@@ -0,0 +1,378 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var security = {
+ // Display the server certificate (static)
+ viewCert : function () {
+ var cert = security._cert;
+ viewCertHelper(window, cert);
+ },
+
+ _getSecurityInfo : function() {
+ const nsIX509Cert = Components.interfaces.nsIX509Cert;
+ const nsIX509CertDB = Components.interfaces.nsIX509CertDB;
+ const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
+ const nsISSLStatusProvider = Components.interfaces.nsISSLStatusProvider;
+ const nsISSLStatus = Components.interfaces.nsISSLStatus;
+
+ // We don't have separate info for a frame, return null until further notice
+ // (see bug 138479)
+ if (gWindow != gWindow.top)
+ return null;
+
+ var hName = null;
+ try {
+ hName = gWindow.location.host;
+ }
+ catch (exception) { }
+
+ var ui = security._getSecurityUI();
+ if (!ui)
+ return null;
+
+ var isBroken =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_BROKEN);
+ var isMixed =
+ (ui.state & (Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
+ Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ var isInsecure =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_INSECURE);
+ var isEV =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
+ ui.QueryInterface(nsISSLStatusProvider);
+ var status = ui.SSLStatus;
+
+ if (!isInsecure && status) {
+ status.QueryInterface(nsISSLStatus);
+ var cert = status.serverCert;
+ var issuerName =
+ this.mapIssuerOrganization(cert.issuerOrganization) || cert.issuerName;
+
+ var retval = {
+ hostName : hName,
+ cAName : issuerName,
+ encryptionAlgorithm : undefined,
+ encryptionStrength : undefined,
+ encryptionSuite : undefined,
+ version: undefined,
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : cert,
+ fullLocation : gWindow.location
+ };
+
+ var version;
+ try {
+ retval.encryptionAlgorithm = status.cipherName;
+ retval.encryptionStrength = status.secretKeyLength;
+ retval.encryptionSuite = status.cipherSuite;
+ version = status.protocolVersion;
+ }
+ catch (e) {
+ }
+
+ switch (version) {
+ case nsISSLStatus.SSL_VERSION_3:
+ retval.version = "SSL 3";
+ break;
+ case nsISSLStatus.TLS_VERSION_1:
+ retval.version = "TLS 1.0";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_1:
+ retval.version = "TLS 1.1";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_2:
+ retval.version = "TLS 1.2"
+ break;
+ case nsISSLStatus.TLS_VERSION_1_3:
+ retval.version = "TLS 1.3"
+ break;
+ }
+
+ return retval;
+ } else {
+ return {
+ hostName : hName,
+ cAName : "",
+ encryptionAlgorithm : "",
+ encryptionStrength : 0,
+ encryptionSuite : "",
+ version: "",
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : null,
+ fullLocation : gWindow.location
+ };
+ }
+ },
+
+ // Find the secureBrowserUI object (if present)
+ _getSecurityUI : function() {
+ if (window.opener.gBrowser)
+ return window.opener.gBrowser.securityUI;
+ return null;
+ },
+
+ // Interface for mapping a certificate issuer organization to
+ // the value to be displayed.
+ // Bug 82017 - this implementation should be moved to pipnss C++ code
+ mapIssuerOrganization: function(name) {
+ if (!name) return null;
+
+ if (name == "RSA Data Security, Inc.") return "Verisign, Inc.";
+
+ // No mapping required
+ return name;
+ },
+
+ /**
+ * Open the cookie manager window
+ */
+ viewCookies : function()
+ {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("Browser:Cookies");
+ var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"].
+ getService(Components.interfaces.nsIEffectiveTLDService);
+
+ var eTLD;
+ var uri = gDocument.documentURIObject;
+ try {
+ eTLD = eTLDService.getBaseDomain(uri);
+ }
+ catch (e) {
+ // getBaseDomain will fail if the host is an IP address or is empty
+ eTLD = uri.asciiHost;
+ }
+
+ if (win) {
+ win.gCookiesWindow.setFilter(eTLD);
+ win.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/cookies.xul",
+ "Browser:Cookies", "", {filterString : eTLD});
+ },
+
+ /**
+ * Open the login manager window
+ */
+ viewPasswords : function()
+ {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("Toolkit:PasswordManager");
+ if (win) {
+ win.setFilter(this._getSecurityInfo().hostName);
+ win.focus();
+ }
+ else
+ window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
+ "Toolkit:PasswordManager", "",
+ {filterString : this._getSecurityInfo().hostName});
+ },
+
+ _cert : null
+};
+
+function securityOnLoad() {
+ var info = security._getSecurityInfo();
+ if (!info) {
+ document.getElementById("securityTab").hidden = true;
+ document.getElementById("securityBox").collapsed = true;
+ return;
+ }
+ else {
+ document.getElementById("securityTab").hidden = false;
+ document.getElementById("securityBox").collapsed = false;
+ }
+
+ const pageInfoBundle = document.getElementById("pageinfobundle");
+
+ /* Set Identity section text */
+ setText("security-identity-domain-value", info.hostName);
+
+ var owner, verifier, generalPageIdentityString;
+ if (info.cert && !info.isBroken) {
+ // Try to pull out meaningful values. Technically these fields are optional
+ // so we'll employ fallbacks where appropriate. The EV spec states that Org
+ // fields must be specified for subject and issuer so that case is simpler.
+ if (info.isEV) {
+ owner = info.cert.organization;
+ verifier = security.mapIssuerOrganization(info.cAName);
+ generalPageIdentityString = pageInfoBundle.getFormattedString("generalSiteIdentity",
+ [owner, verifier]);
+ }
+ else {
+ // Technically, a non-EV cert might specify an owner in the O field or not,
+ // depending on the CA's issuing policies. However we don't have any programmatic
+ // way to tell those apart, and no policy way to establish which organization
+ // vetting standards are good enough (that's what EV is for) so we default to
+ // treating these certs as domain-validated only.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = security.mapIssuerOrganization(info.cAName ||
+ info.cert.issuerCommonName ||
+ info.cert.issuerName);
+ generalPageIdentityString = owner;
+ }
+ }
+ else {
+ // We don't have valid identity credentials.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = pageInfoBundle.getString("notset");
+ generalPageIdentityString = owner;
+ }
+
+ setText("security-identity-owner-value", owner);
+ setText("security-identity-verifier-value", verifier);
+ setText("general-security-identity", generalPageIdentityString);
+
+ /* Manage the View Cert button*/
+ var viewCert = document.getElementById("security-view-cert");
+ if (info.cert) {
+ security._cert = info.cert;
+ viewCert.collapsed = false;
+ }
+ else
+ viewCert.collapsed = true;
+
+ /* Set Privacy & History section text */
+ var yesStr = pageInfoBundle.getString("yes");
+ var noStr = pageInfoBundle.getString("no");
+
+ var uri = gDocument.documentURIObject;
+ setText("security-privacy-cookies-value",
+ hostHasCookies(uri) ? yesStr : noStr);
+ setText("security-privacy-passwords-value",
+ realmHasPasswords(uri) ? yesStr : noStr);
+
+ var visitCount = previousVisitCount(info.hostName);
+ if(visitCount > 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getFormattedString("securityNVisits", [visitCount.toLocaleString()]));
+ }
+ else if (visitCount == 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getString("securityOneVisit"));
+ }
+ else {
+ setText("security-privacy-history-value", noStr);
+ }
+
+ /* Set the Technical Detail section messages */
+ const pkiBundle = document.getElementById("pkiBundle");
+ var hdr;
+ var msg1;
+ var msg2;
+
+ if (info.isBroken) {
+ if (info.isMixed) {
+ hdr = pkiBundle.getString("pageInfo_MixedContent");
+ } else {
+ hdr = pkiBundle.getFormattedString("pageInfo_BrokenEncryption",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ }
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Broken1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ else if (info.encryptionStrength > 0) {
+ hdr = pkiBundle.getFormattedString("pageInfo_EncryptionWithBitsAndProtocol",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
+ security._cert = info.cert;
+ }
+ else {
+ hdr = pkiBundle.getString("pageInfo_NoEncryption");
+ if (info.hostName != null)
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [info.hostName]);
+ else
+ msg1 = pkiBundle.getString("pageInfo_Privacy_None3");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ setText("security-technical-shortform", hdr);
+ setText("security-technical-longform1", msg1);
+ setText("security-technical-longform2", msg2);
+ setText("general-security-privacy", hdr);
+}
+
+function setText(id, value)
+{
+ var element = document.getElementById(id);
+ if (!element)
+ return;
+ if (element.localName == "textbox" || element.localName == "label")
+ element.value = value;
+ else {
+ if (element.hasChildNodes())
+ element.removeChild(element.firstChild);
+ var textNode = document.createTextNode(value);
+ element.appendChild(textNode);
+ }
+}
+
+function viewCertHelper(parent, cert)
+{
+ if (!cert)
+ return;
+
+ var cd = Components.classes[CERTIFICATEDIALOGS_CONTRACTID].getService(nsICertificateDialogs);
+ cd.viewCert(parent, cert);
+}
+
+/**
+ * Return true iff we have cookies for uri
+ */
+function hostHasCookies(uri) {
+ var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager2);
+
+ return cookieManager.countCookiesFromHost(uri.asciiHost) > 0;
+}
+
+/**
+ * Return true iff realm (proto://host:port) (extracted from uri) has
+ * saved passwords
+ */
+function realmHasPasswords(uri) {
+ var passwordManager = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ return passwordManager.countLogins(uri.prePath, "", "") > 0;
+}
+
+/**
+ * Return the number of previous visits recorded for host before today.
+ *
+ * @param host - the domain name to look for in history
+ */
+function previousVisitCount(host, endTimeReference) {
+ if (!host)
+ return false;
+
+ var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Components.interfaces.nsINavHistoryService);
+
+ var options = historyService.getNewQueryOptions();
+ options.resultType = options.RESULTS_AS_VISIT;
+
+ // Search for visits to this host before today
+ var query = historyService.getNewQuery();
+ query.endTimeReference = query.TIME_RELATIVE_TODAY;
+ query.endTime = 0;
+ query.domain = host;
+
+ var result = historyService.executeQuery(query, options);
+ result.root.containerOpen = true;
+ var cc = result.root.childCount;
+ result.root.containerOpen = false;
+ return cc;
+}
diff --git a/application/palemoon/base/content/popup-notifications.inc b/application/palemoon/base/content/popup-notifications.inc
new file mode 100644
index 000000000..04f4cb5b7
--- /dev/null
+++ b/application/palemoon/base/content/popup-notifications.inc
@@ -0,0 +1,97 @@
+# to be included inside a popupset element
+
+ <panel id="notification-popup"
+ type="arrow"
+ footertype="promobox"
+ position="after_start"
+ hidden="true"
+ orient="vertical"
+ role="alert"/>
+
+ <!-- Popup for site identity information -->
+ <panel id="identity-popup"
+ type="arrow"
+ hidden="true"
+ noautofocus="true"
+ consumeoutsideclicks="true"
+ onpopupshown="gIdentityHandler.onPopupShown(event);"
+ level="top">
+ <hbox id="identity-popup-container" align="top">
+ <image id="identity-popup-icon"/>
+ <vbox id="identity-popup-content-box">
+ <label id="identity-popup-connectedToLabel"
+ class="identity-popup-label"
+ value="&identity.connectedTo;"/>
+ <label id="identity-popup-connectedToLabel2"
+ class="identity-popup-label"
+ value="&identity.unverifiedsite2;"/>
+ <description id="identity-popup-content-host"
+ class="identity-popup-description"/>
+ <label id="identity-popup-runByLabel"
+ class="identity-popup-label"
+ value="&identity.runBy;"/>
+ <description id="identity-popup-content-owner"
+ class="identity-popup-description"/>
+ <description id="identity-popup-content-supplemental"
+ class="identity-popup-description"/>
+ <description id="identity-popup-content-verifier"
+ class="identity-popup-description"/>
+ <hbox id="identity-popup-encryption" flex="1">
+ <vbox>
+ <image id="identity-popup-encryption-icon"/>
+ </vbox>
+ <description id="identity-popup-encryption-label" flex="1"
+ class="identity-popup-description"/>
+ </hbox>
+ <!-- Footer button to open security page info -->
+ <hbox id="identity-popup-button-container" pack="end">
+ <button id="identity-popup-more-info-button"
+ label="&identity.moreInfoLinkText;"
+ onblur="gIdentityHandler.hideIdentityPopup();"
+ oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ </panel>
+
+
+ <popupnotification id="webRTC-shareDevices-notification" hidden="true">
+ <popupnotificationcontent id="webRTC-selectCamera" orient="vertical">
+ <separator class="thin"/>
+ <label value="&getUserMedia.selectCamera.label;"
+ accesskey="&getUserMedia.selectCamera.accesskey;"
+ control="webRTC-selectCamera-menulist"/>
+ <menulist id="webRTC-selectCamera-menulist">
+ <menupopup id="webRTC-selectCamera-menupopup"/>
+ </menulist>
+ </popupnotificationcontent>
+ <popupnotificationcontent id="webRTC-selectMicrophone" orient="vertical">
+ <separator class="thin"/>
+ <label value="&getUserMedia.selectMicrophone.label;"
+ accesskey="&getUserMedia.selectMicrophone.accesskey;"
+ control="webRTC-selectMicrophone-menulist"/>
+ <menulist id="webRTC-selectMicrophone-menulist">
+ <menupopup id="webRTC-selectMicrophone-menupopup"/>
+ </menulist>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="servicesInstall-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <!-- XXX bug 974146, tests are looking for this, can't remove yet. -->
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="pointerLock-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <separator class="thin"/>
+ <label id="pointerLock-cancel" value="&pointerLock.notification.message;"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="mixed-content-blocked-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <separator/>
+ <description id="mixed-content-blocked-moreinfo">&mixedContentBlocked.moreinfo;</description>
+ </popupnotificationcontent>
+ </popupnotification>
diff --git a/application/palemoon/base/content/report-phishing-overlay.xul b/application/palemoon/base/content/report-phishing-overlay.xul
new file mode 100644
index 000000000..76baf01da
--- /dev/null
+++ b/application/palemoon/base/content/report-phishing-overlay.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % reportphishDTD SYSTEM "chrome://browser/locale/safebrowsing/report-phishing.dtd">
+%reportphishDTD;
+<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+%safebrowsingDTD;
+]>
+
+<overlay id="reportPhishingMenuOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="reportPhishingBroadcaster" disabled="true"/>
+ <broadcaster id="reportPhishingErrorBroadcaster" disabled="true"/>
+ </broadcasterset>
+ <menupopup id="menu_HelpPopup">
+ <menuitem id="menu_HelpPopup_reportPhishingtoolmenu"
+ label="&reportPhishSiteMenu.title2;"
+ accesskey="&reportPhishSiteMenu.accesskey;"
+ insertbefore="aboutSeparator"
+ observes="reportPhishingBroadcaster"
+ oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu"
+ label="&safeb.palm.notforgery.label2;"
+ accesskey="&reportPhishSiteMenu.accesskey;"
+ insertbefore="aboutSeparator"
+ observes="reportPhishingErrorBroadcaster"
+ oncommand="openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menupopup>
+</overlay>
diff --git a/application/palemoon/base/content/safeMode.css b/application/palemoon/base/content/safeMode.css
new file mode 100644
index 000000000..4f093a452
--- /dev/null
+++ b/application/palemoon/base/content/safeMode.css
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#resetProfileFooter {
+ font-weight: bold;
+}
+
diff --git a/application/palemoon/base/content/safeMode.js b/application/palemoon/base/content/safeMode.js
new file mode 100644
index 000000000..e1e5c7285
--- /dev/null
+++ b/application/palemoon/base/content/safeMode.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cc = Components.classes,
+ Ci = Components.interfaces,
+ Cu = Components.utils;
+
+Cu.import("resource://gre/modules/AddonManager.jsm");
+
+function restartApp() {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart);
+}
+
+function clearAllPrefs() {
+ var prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService);
+ prefService.resetUserPrefs();
+
+ // Remove the pref-overrides dir, if it exists
+ try {
+ var fileLocator = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ const NS_APP_PREFS_OVERRIDE_DIR = "PrefDOverride";
+ var prefOverridesDir = fileLocator.get(NS_APP_PREFS_OVERRIDE_DIR,
+ Ci.nsIFile);
+ prefOverridesDir.remove(true);
+ } catch (ex) {
+ Components.utils.reportError(ex);
+ }
+}
+
+function restoreDefaultBookmarks() {
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefBranch.setBoolPref("browser.bookmarks.restore_default_bookmarks", true);
+}
+
+function deleteLocalstore() {
+ const nsIDirectoryServiceContractID = "@mozilla.org/file/directory_service;1";
+ const nsIProperties = Ci.nsIProperties;
+ var directoryService = Cc[nsIDirectoryServiceContractID]
+ .getService(nsIProperties);
+ // Local store file
+ var localstoreFile = directoryService.get("LStoreS", Components.interfaces.nsIFile);
+ // XUL store file
+ var xulstoreFile = directoryService.get("ProfD", Components.interfaces.nsIFile);
+ xulstoreFile.append("xulstore.json");
+ try {
+ xulstoreFile.remove(false);
+ if (localstoreFile.exists()) {
+ localstoreFile.remove(false);
+ }
+ } catch(e) {
+ Components.utils.reportError(e);
+ }
+}
+
+function disableAddons() {
+ AddonManager.getAllAddons(function(aAddons) {
+ aAddons.forEach(function(aAddon) {
+ if (aAddon.type == "theme") {
+ // Setting userDisabled to false on the default theme activates it,
+ // disables all other themes and deactivates the applied persona, if
+ // any.
+ const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
+ if (aAddon.id == DEFAULT_THEME_ID)
+ aAddon.userDisabled = false;
+ }
+ else {
+ aAddon.userDisabled = true;
+ }
+ });
+
+ restartApp();
+ });
+}
+
+function restoreDefaultSearchEngines() {
+ var searchService = Cc["@mozilla.org/browser/search-service;1"]
+ .getService(Ci.nsIBrowserSearchService);
+
+ searchService.restoreDefaultEngines();
+}
+
+function onOK() {
+ try {
+ if (document.getElementById("resetUserPrefs").checked)
+ clearAllPrefs();
+ if (document.getElementById("deleteBookmarks").checked)
+ restoreDefaultBookmarks();
+ if (document.getElementById("resetToolbars").checked)
+ deleteLocalstore();
+ if (document.getElementById("restoreSearch").checked)
+ restoreDefaultSearchEngines();
+ if (document.getElementById("disableAddons").checked) {
+ disableAddons();
+ // disableAddons will asynchronously restart the application
+ return false;
+ }
+ } catch(e) {
+ }
+
+ restartApp();
+ return false;
+}
+
+function onCancel() {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+}
+
+function onLoad() {
+ document.getElementById("tasks")
+ .addEventListener("CheckboxStateChange", UpdateOKButtonState, false);
+}
+
+function UpdateOKButtonState() {
+ document.documentElement.getButton("accept").disabled =
+ !document.getElementById("resetUserPrefs").checked &&
+ !document.getElementById("deleteBookmarks").checked &&
+ !document.getElementById("resetToolbars").checked &&
+ !document.getElementById("disableAddons").checked &&
+ !document.getElementById("restoreSearch").checked;
+}
diff --git a/application/palemoon/base/content/safeMode.xul b/application/palemoon/base/content/safeMode.xul
new file mode 100644
index 000000000..656df6eaf
--- /dev/null
+++ b/application/palemoon/base/content/safeMode.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+
+<!--
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE prefwindow [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % safeModeDTD SYSTEM "chrome://browser/locale/safeMode.dtd" >
+%safeModeDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+%browserDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<dialog id="safeModeDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&safeModeDialog.title;"
+ buttons="accept,cancel,extra1"
+ buttonlabelaccept="&changeAndRestartButton.label;"
+#ifdef XP_WIN
+ buttonlabelcancel="&quitApplicationCmdWin.label;"
+#else
+ buttonlabelcancel="&quitApplicationCmd.label;"
+#endif
+ buttonlabelextra1="&continueButton.label;"
+ width="&window.width;"
+ ondialogaccept="return onOK()"
+ ondialogcancel="onCancel()"
+ ondialogextra1="window.close()"
+ onload="onLoad();"
+ buttondisabledaccept="true">
+
+ <script type="application/javascript" src="chrome://browser/content/safeMode.js"/>
+
+ <stringbundle id="preferencesBundle" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <description>&safeModeDescription.label;</description>
+
+ <separator class="thin"/>
+
+ <label value="&safeModeDescription2.label;"/>
+ <vbox id="tasks">
+ <checkbox id="disableAddons" label="&disableAddons.label;" accesskey="&disableAddons.accesskey;"/>
+ <checkbox id="resetToolbars" label="&resetToolbars.label;" accesskey="&resetToolbars.accesskey;"/>
+ <checkbox id="deleteBookmarks" label="&deleteBookmarks.label;" accesskey="&deleteBookmarks.accesskey;"/>
+ <checkbox id="resetUserPrefs" label="&resetUserPrefs.label;" accesskey="&resetUserPrefs.accesskey;"/>
+ <checkbox id="restoreSearch" label="&restoreSearch.label;" accesskey="&restoreSearch.accesskey;"/>
+ </vbox>
+
+ <separator class="thin"/>
+</dialog>
diff --git a/application/palemoon/base/content/sanitize.js b/application/palemoon/base/content/sanitize.js
new file mode 100644
index 000000000..89843c86d
--- /dev/null
+++ b/application/palemoon/base/content/sanitize.js
@@ -0,0 +1,527 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource:///modules/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/devtools/Console.jsm");
+
+function Sanitizer() {}
+Sanitizer.prototype = {
+ // warning to the caller: this one may raise an exception (e.g. bug #265028)
+ clearItem: function (aItemName)
+ {
+ if (this.items[aItemName].canClear)
+ this.items[aItemName].clear();
+ },
+
+ canClearItem: function (aItemName, aCallback, aArg)
+ {
+ let canClear = this.items[aItemName].canClear;
+ if (typeof canClear == "function") {
+ canClear(aCallback, aArg);
+ return false;
+ }
+
+ aCallback(aItemName, canClear, aArg);
+ return canClear;
+ },
+
+ prefDomain: "",
+ isShutDown: false,
+
+ getNameFromPreference: function (aPreferenceName)
+ {
+ return aPreferenceName.substr(this.prefDomain.length);
+ },
+
+ /**
+ * Deletes privacy sensitive data in a batch, according to user preferences.
+ * Returns a promise which is resolved if no errors occurred. If an error
+ * occurs, a message is reported to the console and all other items are still
+ * cleared before the promise is finally rejected.
+ */
+ sanitize: function ()
+ {
+ var deferred = Promise.defer();
+ var psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ var branch = psvc.getBranch(this.prefDomain);
+ var seenError = false;
+
+ // Cache the range of times to clear
+ if (this.ignoreTimespan)
+ var range = null; // If we ignore timespan, clear everything
+ else
+ range = this.range || Sanitizer.getClearRange();
+
+ let itemCount = Object.keys(this.items).length;
+ let onItemComplete = function() {
+ if (!--itemCount) {
+ seenError ? deferred.reject() : deferred.resolve();
+ }
+ };
+ for (var itemName in this.items) {
+ let item = this.items[itemName];
+ item.range = range;
+ item.isShutDown = this.isShutDown;
+ if ("clear" in item && branch.getBoolPref(itemName)) {
+ let clearCallback = (itemName, aCanClear) => {
+ // Some of these clear() may raise exceptions (see bug #265028)
+ // to sanitize as much as possible, we catch and store them,
+ // rather than fail fast.
+ // Callers should check returned errors and give user feedback
+ // about items that could not be sanitized
+ let item = this.items[itemName];
+ try {
+ if (aCanClear)
+ item.clear();
+ } catch(er) {
+ seenError = true;
+ console.error("Error sanitizing " + itemName + ": " + er + "\n");
+ }
+ onItemComplete();
+ };
+ this.canClearItem(itemName, clearCallback);
+ } else {
+ onItemComplete();
+ }
+ }
+
+ return deferred.promise;
+ },
+
+ // Time span only makes sense in certain cases. Consumers who want
+ // to only clear some private data can opt in by setting this to false,
+ // and can optionally specify a specific range. If timespan is not ignored,
+ // and range is not set, sanitize() will use the value of the timespan
+ // pref to determine a range
+ ignoreTimespan : true,
+ range : null,
+
+ items: {
+ cache: {
+ clear: function ()
+ {
+ var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+ getService(Ci.nsICacheStorageService);
+ try {
+ // Cache doesn't consult timespan, nor does it have the
+ // facility for timespan-based eviction. Wipe it.
+ cache.clear();
+ } catch(er) {}
+
+ var imageCache = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools).getImgCacheForDocument(null);
+ try {
+ imageCache.clearCache(false); // true=chrome, false=content
+ } catch(er) {}
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ cookies: {
+ clear: function ()
+ {
+ var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ if (this.range) {
+ // Iterate through the cookies and delete any created after our cutoff.
+ var cookiesEnum = cookieMgr.enumerator;
+ while (cookiesEnum.hasMoreElements()) {
+ var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+
+ if (cookie.creationTime > this.range[0])
+ // This cookie was created after our cutoff, clear it
+ cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
+ }
+ }
+ else {
+ // Remove everything
+ cookieMgr.removeAll();
+ }
+
+ // Clear plugin data.
+ const phInterface = Ci.nsIPluginHost;
+ const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
+
+ // Determine age range in seconds. (-1 means clear all.) We don't know
+ // that this.range[1] is actually now, so we compute age range based
+ // on the lower bound. If this.range results in a negative age, do
+ // nothing.
+ let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000)
+ : -1;
+ if (!this.range || age >= 0) {
+ let tags = ph.getPluginTags();
+ for (let i = 0; i < tags.length; i++) {
+ try {
+ ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, age);
+ } catch (e) {
+ // If the plugin doesn't support clearing by age, clear everything.
+ if (e.result == Components.results.
+ NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
+ try {
+ ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, -1);
+ } catch (e) {
+ // Ignore errors from the plugin
+ }
+ }
+ }
+ }
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ offlineApps: {
+ clear: function ()
+ {
+ Components.utils.import("resource:///modules/offlineAppCache.jsm");
+ OfflineAppCacheHelper.clear();
+ if (!this.range || this.isShutDown) {
+ Components.utils.import("resource:///modules/QuotaManager.jsm");
+ QuotaManagerHelper.clear(this.isShutDown);
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ history: {
+ clear: function ()
+ {
+ if (this.range)
+ PlacesUtils.history.removeVisitsByTimeframe(this.range[0], this.range[1]);
+ else
+ PlacesUtils.history.removeAllPages();
+
+ try {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.notifyObservers(null, "browser:purge-session-history", "");
+ }
+ catch (e) { }
+
+ // Clear last URL of the Open Web Location dialog
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ try {
+ prefs.clearUserPref("general.open_location.last_url");
+ }
+ catch (e) { }
+ },
+
+ get canClear()
+ {
+ // bug 347231: Always allow clearing history due to dependencies on
+ // the browser:purge-session-history notification. (like error console)
+ return true;
+ }
+ },
+
+ formdata: {
+ clear: function ()
+ {
+ // Clear undo history of all searchBars
+ var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
+ .getService(Components.interfaces.nsIWindowMediator);
+ var windows = windowManager.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let currentDocument = windows.getNext().document;
+ let searchBar = currentDocument.getElementById("searchbar");
+ if (searchBar)
+ searchBar.textbox.reset();
+ let findBar = currentDocument.getElementById("FindToolbar");
+ if (findBar)
+ findBar.clear();
+ }
+
+ let change = { op: "remove" };
+ if (this.range) {
+ [ change.firstUsedStart, change.firstUsedEnd ] = this.range;
+ }
+ FormHistory.update(change);
+ },
+
+ canClear : function(aCallback, aArg)
+ {
+ var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
+ .getService(Components.interfaces.nsIWindowMediator);
+ var windows = windowManager.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let currentDocument = windows.getNext().document;
+ let searchBar = currentDocument.getElementById("searchbar");
+ if (searchBar) {
+ let transactionMgr = searchBar.textbox.editor.transactionManager;
+ if (searchBar.value ||
+ transactionMgr.numberOfUndoItems ||
+ transactionMgr.numberOfRedoItems) {
+ aCallback("formdata", true, aArg);
+ return false;
+ }
+ }
+ let findBar = currentDocument.getElementById("FindToolbar");
+ if (findBar && findBar.canClear) {
+ aCallback("formdata", true, aArg);
+ return false;
+ }
+ }
+
+ let count = 0;
+ let countDone = {
+ handleResult : function(aResult) count = aResult,
+ handleError : function(aError) Components.utils.reportError(aError),
+ handleCompletion :
+ function(aReason) { aCallback("formdata", aReason == 0 && count > 0, aArg); }
+ };
+ FormHistory.count({}, countDone);
+ return false;
+ }
+ },
+
+ downloads: {
+ clear: Task.async(function* (range) {
+ let refObj = {};
+ try {
+ let filterByTime = null;
+ if (range) {
+ // Convert microseconds back to milliseconds for date comparisons.
+ let rangeBeginMs = range[0] / 1000;
+ let rangeEndMs = range[1] / 1000;
+ filterByTime = download => download.startTime >= rangeBeginMs &&
+ download.startTime <= rangeEndMs;
+ }
+
+ // Clear all completed/cancelled downloads
+ let list = yield Downloads.getList(Downloads.ALL);
+ list.removeFinished(filterByTime);
+ } finally {}
+ }),
+
+ get canClear()
+ {
+ //Clearing is always possible with JSTransfers
+ return true;
+ }
+ },
+
+ passwords: {
+ clear: function ()
+ {
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ // Passwords are timeless, and don't respect the timeSpan setting
+ pwmgr.removeAllLogins();
+ },
+
+ get canClear()
+ {
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ var count = pwmgr.countLogins("", "", ""); // count all logins
+ return (count > 0);
+ }
+ },
+
+ sessions: {
+ clear: function ()
+ {
+ // clear all auth tokens
+ var sdr = Components.classes["@mozilla.org/security/sdr;1"]
+ .getService(Components.interfaces.nsISecretDecoderRing);
+ sdr.logoutAndTeardown();
+
+ // clear FTP and plain HTTP auth sessions
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.notifyObservers(null, "net:clear-active-logins", null);
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ siteSettings: {
+ clear: function ()
+ {
+ // Clear site-specific permissions like "Allow this site to open popups"
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+ pm.removeAll();
+
+ // Clear site-specific settings like page-zoom level
+ var cps = Components.classes["@mozilla.org/content-pref/service;1"]
+ .getService(Components.interfaces.nsIContentPrefService2);
+ cps.removeAllDomains(null);
+
+ // Clear "Never remember passwords for this site", which is not handled by
+ // the permission manager
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ var hosts = pwmgr.getAllDisabledHosts();
+ for each (var host in hosts) {
+ pwmgr.setLoginSavingEnabled(host, true);
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ connectivityData: {
+ clear: function ()
+ {
+ // Clear site security settings
+ var sss = Components.classes["@mozilla.org/ssservice;1"]
+ .getService(Components.interfaces.nsISiteSecurityService);
+ sss.clearAll();
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ }
+ }
+};
+
+
+
+// "Static" members
+Sanitizer.prefDomain = "privacy.sanitize.";
+Sanitizer.prefShutdown = "sanitizeOnShutdown";
+Sanitizer.prefDidShutdown = "didShutdownSanitize";
+
+// Time span constants corresponding to values of the privacy.sanitize.timeSpan
+// pref. Used to determine how much history to clear, for various items
+Sanitizer.TIMESPAN_EVERYTHING = 0;
+Sanitizer.TIMESPAN_HOUR = 1;
+Sanitizer.TIMESPAN_2HOURS = 2;
+Sanitizer.TIMESPAN_4HOURS = 3;
+Sanitizer.TIMESPAN_TODAY = 4;
+
+Sanitizer.IS_SHUTDOWN = true;
+
+// Return a 2 element array representing the start and end times,
+// in the uSec-since-epoch format that PRTime likes. If we should
+// clear everything, return null. Use ts if it is defined; otherwise
+// use the timeSpan pref.
+Sanitizer.getClearRange = function (ts) {
+ if (ts === undefined)
+ ts = Sanitizer.prefs.getIntPref("timeSpan");
+ if (ts === Sanitizer.TIMESPAN_EVERYTHING)
+ return null;
+
+ // PRTime is microseconds while JS time is milliseconds
+ var endDate = Date.now() * 1000;
+ switch (ts) {
+ case Sanitizer.TIMESPAN_HOUR :
+ var startDate = endDate - 3600000000; // 1*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_2HOURS :
+ startDate = endDate - 7200000000; // 2*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_4HOURS :
+ startDate = endDate - 14400000000; // 4*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_TODAY :
+ var d = new Date(); // Start with today
+ d.setHours(0); // zero us back to midnight...
+ d.setMinutes(0);
+ d.setSeconds(0);
+ startDate = d.valueOf() * 1000; // convert to epoch usec
+ break;
+ default:
+ throw "Invalid time span for clear private data: " + ts;
+ }
+ return [startDate, endDate];
+};
+
+Sanitizer._prefs = null;
+Sanitizer.__defineGetter__("prefs", function()
+{
+ return Sanitizer._prefs ? Sanitizer._prefs
+ : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch(Sanitizer.prefDomain);
+});
+
+// Shows sanitization UI
+Sanitizer.showUI = function(aParentWindow)
+{
+ var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIWindowWatcher);
+#ifdef XP_MACOSX
+ ww.openWindow(null, // make this an app-modal window on Mac
+#else
+ ww.openWindow(aParentWindow,
+#endif
+ "chrome://browser/content/sanitize.xul",
+ "Sanitize",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+};
+
+/**
+ * Deletes privacy sensitive data in a batch, optionally showing the
+ * sanitize UI, according to user preferences
+ */
+Sanitizer.sanitize = function(aParentWindow)
+{
+ Sanitizer.showUI(aParentWindow);
+};
+
+Sanitizer.onStartup = function()
+{
+ // we check for unclean exit with pending sanitization
+ Sanitizer._checkAndSanitize();
+};
+
+Sanitizer.onShutdown = function()
+{
+ // we check if sanitization is needed and perform it
+ Sanitizer._checkAndSanitize(Sanitizer.IS_SHUTDOWN);
+};
+
+// this is called on startup and shutdown, to perform pending sanitizations
+Sanitizer._checkAndSanitize = function(isShutDown)
+{
+ const prefs = Sanitizer.prefs;
+ if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
+ !prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
+ // this is a shutdown or a startup after an unclean exit
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.clearOnShutdown.";
+ s.isShutDown = isShutDown;
+ s.sanitize().then(function() {
+ prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
+ });
+ }
+};
diff --git a/application/palemoon/base/content/sanitize.xul b/application/palemoon/base/content/sanitize.xul
new file mode 100644
index 000000000..691be926e
--- /dev/null
+++ b/application/palemoon/base/content/sanitize.xul
@@ -0,0 +1,190 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/sanitizeDialog.css"?>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+#endif
+
+<?xml-stylesheet href="chrome://browser/content/sanitizeDialog.css"?>
+
+<!DOCTYPE prefwindow [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
+ %brandDTD;
+ %sanitizeDTD;
+]>
+
+<prefwindow id="SanitizeDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ dlgbuttons="accept,cancel"
+ title="&sanitizeDialog2.title;"
+ noneverythingtitle="&sanitizeDialog2.title;"
+ style="width: &dialog.width2;;"
+ ondialogaccept="return gSanitizePromptDialog.sanitize();">
+
+ <prefpane id="SanitizeDialogPane" onpaneload="gSanitizePromptDialog.init();">
+ <stringbundle id="bundleBrowser"
+ src="chrome://browser/locale/browser.properties"/>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sanitize.js"/>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ <script type="application/javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/treeView.js"/>
+ <script type="application/javascript"><![CDATA[
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
+ ]]></script>
+#endif
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sanitizeDialog.js"/>
+
+ <preferences id="sanitizePreferences">
+ <preference id="privacy.cpd.history" name="privacy.cpd.history" type="bool"/>
+ <preference id="privacy.cpd.formdata" name="privacy.cpd.formdata" type="bool"/>
+ <preference id="privacy.cpd.downloads" name="privacy.cpd.downloads" type="bool" disabled="true"/>
+ <preference id="privacy.cpd.cookies" name="privacy.cpd.cookies" type="bool"/>
+ <preference id="privacy.cpd.cache" name="privacy.cpd.cache" type="bool"/>
+ <preference id="privacy.cpd.sessions" name="privacy.cpd.sessions" type="bool"/>
+ <preference id="privacy.cpd.offlineApps" name="privacy.cpd.offlineApps" type="bool"/>
+ <preference id="privacy.cpd.siteSettings" name="privacy.cpd.siteSettings" type="bool"/>
+ <preference id="privacy.cpd.connectivityData" name="privacy.cpd.connectivityData" type="bool"/>
+ </preferences>
+
+ <preferences id="nonItemPreferences">
+ <preference id="privacy.sanitize.timeSpan"
+ name="privacy.sanitize.timeSpan"
+ type="int"/>
+ </preferences>
+
+ <hbox id="SanitizeDurationBox" align="center">
+ <label value="&clearTimeDuration.label;"
+ accesskey="&clearTimeDuration.accesskey;"
+ control="sanitizeDurationChoice"
+ id="sanitizeDurationLabel"/>
+ <menulist id="sanitizeDurationChoice"
+ preference="privacy.sanitize.timeSpan"
+ onselect="gSanitizePromptDialog.selectByTimespan();"
+ flex="1">
+ <menupopup id="sanitizeDurationPopup">
+#ifdef CRH_DIALOG_TREE_VIEW
+ <menuitem label="" value="-1" id="sanitizeDurationCustom"/>
+#endif
+ <menuitem label="&clearTimeDuration.lastHour;" value="1"/>
+ <menuitem label="&clearTimeDuration.last2Hours;" value="2"/>
+ <menuitem label="&clearTimeDuration.last4Hours;" value="3"/>
+ <menuitem label="&clearTimeDuration.today;" value="4"/>
+ <menuseparator/>
+ <menuitem label="&clearTimeDuration.everything;" value="0"/>
+ </menupopup>
+ </menulist>
+ <label id="sanitizeDurationSuffixLabel"
+ value="&clearTimeDuration.suffix;"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ <deck id="durationDeck">
+ <tree id="placesTree" flex="1" hidecolumnpicker="true" rows="10"
+ disabled="true" disableKeyNavigation="true">
+ <treecols>
+ <treecol id="date" label="&clearTimeDuration.dateColumn;" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="title" label="&clearTimeDuration.nameColumn;" flex="5"/>
+ </treecols>
+ <treechildren id="placesTreechildren"
+ ondragstart="gSanitizePromptDialog.grippyMoved('ondragstart', event);"
+ ondragover="gSanitizePromptDialog.grippyMoved('ondragover', event);"
+ onkeypress="gSanitizePromptDialog.grippyMoved('onkeypress', event);"
+ onmousedown="gSanitizePromptDialog.grippyMoved('onmousedown', event);"/>
+ </tree>
+#endif
+
+ <vbox id="sanitizeEverythingWarningBox">
+ <spacer flex="1"/>
+ <hbox align="center">
+ <image id="sanitizeEverythingWarningIcon"/>
+ <vbox id="sanitizeEverythingWarningDescBox" flex="1">
+ <description id="sanitizeEverythingWarning"/>
+ <description id="sanitizeEverythingUndoWarning">&sanitizeEverythingUndoWarning;</description>
+ </vbox>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ </deck>
+#endif
+
+ <separator class="thin"/>
+
+ <hbox id="detailsExpanderWrapper" align="center">
+ <button type="image"
+ id="detailsExpander"
+ class="expander-down"
+ persist="class"
+ oncommand="gSanitizePromptDialog.toggleItemList();"/>
+ <label id="detailsExpanderLabel"
+ value="&detailsProgressiveDisclosure.label;"
+ accesskey="&detailsProgressiveDisclosure.accesskey;"
+ control="detailsExpander"/>
+ </hbox>
+ <listbox id="itemList" rows="8" collapsed="true" persist="collapsed">
+ <listitem label="&itemHistoryAndDownloads.label;"
+ type="checkbox"
+ accesskey="&itemHistoryAndDownloads.accesskey;"
+ preference="privacy.cpd.history"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemFormSearchHistory.label;"
+ type="checkbox"
+ accesskey="&itemFormSearchHistory.accesskey;"
+ preference="privacy.cpd.formdata"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemCookies.label;"
+ type="checkbox"
+ accesskey="&itemCookies.accesskey;"
+ preference="privacy.cpd.cookies"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemCache.label;"
+ type="checkbox"
+ accesskey="&itemCache.accesskey;"
+ preference="privacy.cpd.cache"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemActiveLogins.label;"
+ type="checkbox"
+ accesskey="&itemActiveLogins.accesskey;"
+ preference="privacy.cpd.sessions"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemOfflineApps.label;"
+ type="checkbox"
+ accesskey="&itemOfflineApps.accesskey;"
+ preference="privacy.cpd.offlineApps"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemSitePreferences.label;"
+ type="checkbox"
+ accesskey="&itemSitePreferences.accesskey;"
+ preference="privacy.cpd.siteSettings"
+ noduration="true"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemConnectivityData.label;"
+ type="checkbox"
+ accesskey="&itemConnectivityData.accesskey;"
+ preference="privacy.cpd.connectivityData"
+ noduration="true"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ </listbox>
+
+ </prefpane>
+</prefwindow>
diff --git a/application/palemoon/base/content/sanitizeDialog.css b/application/palemoon/base/content/sanitizeDialog.css
new file mode 100644
index 000000000..27c3c0866
--- /dev/null
+++ b/application/palemoon/base/content/sanitizeDialog.css
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Places tree */
+
+#placesTreechildren {
+ -moz-user-focus: normal;
+}
+
+#placesTreechildren::-moz-tree-cell(grippyRow),
+#placesTreechildren::-moz-tree-cell-text(grippyRow),
+#placesTreechildren::-moz-tree-image(grippyRow) {
+ cursor: -moz-grab;
+}
+
+
+/* Sanitize everything warnings */
+
+#sanitizeEverythingWarning,
+#sanitizeEverythingUndoWarning {
+ white-space: pre-wrap;
+}
diff --git a/application/palemoon/base/content/sanitizeDialog.js b/application/palemoon/base/content/sanitizeDialog.js
new file mode 100644
index 000000000..18df5e4a4
--- /dev/null
+++ b/application/palemoon/base/content/sanitizeDialog.js
@@ -0,0 +1,910 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+var gSanitizePromptDialog = {
+
+ get bundleBrowser()
+ {
+ if (!this._bundleBrowser)
+ this._bundleBrowser = document.getElementById("bundleBrowser");
+ return this._bundleBrowser;
+ },
+
+ get selectedTimespan()
+ {
+ var durList = document.getElementById("sanitizeDurationChoice");
+ return parseInt(durList.value);
+ },
+
+ get sanitizePreferences()
+ {
+ if (!this._sanitizePreferences) {
+ this._sanitizePreferences =
+ document.getElementById("sanitizePreferences");
+ }
+ return this._sanitizePreferences;
+ },
+
+ get warningBox()
+ {
+ return document.getElementById("sanitizeEverythingWarningBox");
+ },
+
+ init: function ()
+ {
+ // This is used by selectByTimespan() to determine if the window has loaded.
+ this._inited = true;
+
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
+ for (let i = 0; i < sanitizeItemList.length; i++) {
+ let prefItem = sanitizeItemList[i];
+ let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
+ s.canClearItem(name, function canClearCallback(aItem, aCanClear, aPrefItem) {
+ if (!aCanClear) {
+ aPrefItem.preference = null;
+ aPrefItem.checked = false;
+ aPrefItem.disabled = true;
+ }
+ }, prefItem);
+ }
+
+ document.documentElement.getButton("accept").label =
+ this.bundleBrowser.getString("sanitizeButtonOK");
+
+ if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ this.warningBox.hidden = false;
+ document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ }
+ else
+ this.warningBox.hidden = true;
+ },
+
+ selectByTimespan: function ()
+ {
+ // This method is the onselect handler for the duration dropdown. As a
+ // result it's called a couple of times before onload calls init().
+ if (!this._inited)
+ return;
+
+ var warningBox = this.warningBox;
+
+ // If clearing everything
+ if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ if (warningBox.hidden) {
+ warningBox.hidden = false;
+ window.resizeBy(0, warningBox.boxObject.height);
+ }
+ window.document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ return;
+ }
+
+ // If clearing a specific time range
+ if (!warningBox.hidden) {
+ window.resizeBy(0, -warningBox.boxObject.height);
+ warningBox.hidden = true;
+ }
+ window.document.title =
+ window.document.documentElement.getAttribute("noneverythingtitle");
+ },
+
+ sanitize: function ()
+ {
+ // Update pref values before handing off to the sanitizer (bug 453440)
+ this.updatePrefs();
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ s.range = Sanitizer.getClearRange(this.selectedTimespan);
+ s.ignoreTimespan = !s.range;
+
+ // As the sanitize is async, we disable the buttons, update the label on
+ // the 'accept' button to indicate things are happening and return false -
+ // once the async operation completes (either with or without errors)
+ // we close the window.
+ let docElt = document.documentElement;
+ let acceptButton = docElt.getButton("accept");
+ acceptButton.disabled = true;
+ acceptButton.setAttribute("label",
+ this.bundleBrowser.getString("sanitizeButtonClearing"));
+ docElt.getButton("cancel").disabled = true;
+ try {
+ s.sanitize().then(window.close, window.close);
+ } catch (er) {
+ Components.utils.reportError("Exception during sanitize: " + er);
+ return true; // We *do* want to close immediately on error.
+ }
+ return false;
+ },
+
+ /**
+ * If the panel that displays a warning when the duration is "Everything" is
+ * not set up, sets it up. Otherwise does nothing.
+ *
+ * @param aDontShowItemList Whether only the warning message should be updated.
+ * True means the item list visibility status should not
+ * be changed.
+ */
+ prepareWarning: function (aDontShowItemList) {
+ // If the date and time-aware locale warning string is ever used again,
+ // initialize it here. Currently we use the no-visits warning string,
+ // which does not include date and time. See bug 480169 comment 48.
+
+ var warningStringID;
+ if (this.hasNonSelectedItems()) {
+ warningStringID = "sanitizeSelectedWarning";
+ if (!aDontShowItemList)
+ this.showItemList();
+ }
+ else {
+ warningStringID = "sanitizeEverythingWarning2";
+ }
+
+ var warningDesc = document.getElementById("sanitizeEverythingWarning");
+ warningDesc.textContent =
+ this.bundleBrowser.getString(warningStringID);
+ },
+
+ /**
+ * Called when the value of a preference element is synced from the actual
+ * pref. Enables or disables the OK button appropriately.
+ */
+ onReadGeneric: function ()
+ {
+ var found = false;
+
+ // Find any other pref that's checked and enabled.
+ var i = 0;
+ while (!found && i < this.sanitizePreferences.childNodes.length) {
+ var preference = this.sanitizePreferences.childNodes[i];
+
+ found = !!preference.value &&
+ !preference.disabled;
+ i++;
+ }
+
+ try {
+ document.documentElement.getButton("accept").disabled = !found;
+ }
+ catch (e) { }
+
+ // Update the warning prompt if needed
+ this.prepareWarning(true);
+
+ return undefined;
+ },
+
+ /**
+ * Sanitizer.prototype.sanitize() requires the prefs to be up-to-date.
+ * Because the type of this prefwindow is "child" -- and that's needed because
+ * without it the dialog has no OK and Cancel buttons -- the prefs are not
+ * updated on dialogaccept on platforms that don't support instant-apply
+ * (i.e., Windows). We must therefore manually set the prefs from their
+ * corresponding preference elements.
+ */
+ updatePrefs : function ()
+ {
+ var tsPref = document.getElementById("privacy.sanitize.timeSpan");
+ Sanitizer.prefs.setIntPref("timeSpan", this.selectedTimespan);
+
+ // Keep the pref for the download history in sync with the history pref.
+ document.getElementById("privacy.cpd.downloads").value =
+ document.getElementById("privacy.cpd.history").value;
+
+ // Now manually set the prefs from their corresponding preference
+ // elements.
+ var prefs = this.sanitizePreferences.rootBranch;
+ for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
+ var p = this.sanitizePreferences.childNodes[i];
+ prefs.setBoolPref(p.name, p.value);
+ }
+ },
+
+ /**
+ * Check if all of the history items have been selected like the default status.
+ */
+ hasNonSelectedItems: function () {
+ let checkboxes = document.querySelectorAll("#itemList > [preference]");
+ for (let i = 0; i < checkboxes.length; ++i) {
+ let pref = document.getElementById(checkboxes[i].getAttribute("preference"));
+ if (!pref.value)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Show the history items list.
+ */
+ showItemList: function () {
+ var itemList = document.getElementById("itemList");
+ var expanderButton = document.getElementById("detailsExpander");
+
+ if (itemList.collapsed) {
+ expanderButton.className = "expander-up";
+ itemList.setAttribute("collapsed", "false");
+ if (document.documentElement.boxObject.height)
+ window.resizeBy(0, itemList.boxObject.height);
+ }
+ },
+
+ /**
+ * Hide the history items list.
+ */
+ hideItemList: function () {
+ var itemList = document.getElementById("itemList");
+ var expanderButton = document.getElementById("detailsExpander");
+
+ if (!itemList.collapsed) {
+ expanderButton.className = "expander-down";
+ window.resizeBy(0, -itemList.boxObject.height);
+ itemList.setAttribute("collapsed", "true");
+ }
+ },
+
+ /**
+ * Called by the item list expander button to toggle the list's visibility.
+ */
+ toggleItemList: function ()
+ {
+ var itemList = document.getElementById("itemList");
+
+ if (itemList.collapsed)
+ this.showItemList();
+ else
+ this.hideItemList();
+ }
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ // A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
+ // Sanitizer.TIMESPAN_2HOURS, et al. This should match the value attribute
+ // of the sanitizeDurationCustom menuitem.
+ get TIMESPAN_CUSTOM()
+ {
+ return -1;
+ },
+
+ get placesTree()
+ {
+ if (!this._placesTree)
+ this._placesTree = document.getElementById("placesTree");
+ return this._placesTree;
+ },
+
+ init: function ()
+ {
+ // This is used by selectByTimespan() to determine if the window has loaded.
+ this._inited = true;
+
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
+ for (let i = 0; i < sanitizeItemList.length; i++) {
+ let prefItem = sanitizeItemList[i];
+ let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
+ s.canClearItem(name, function canClearCallback(aCanClear) {
+ if (!aCanClear) {
+ prefItem.preference = null;
+ prefItem.checked = false;
+ prefItem.disabled = true;
+ }
+ });
+ }
+
+ document.documentElement.getButton("accept").label =
+ this.bundleBrowser.getString("sanitizeButtonOK");
+
+ this.selectByTimespan();
+ },
+
+ /**
+ * Sets up the hashes this.durationValsToRows, which maps duration values
+ * to rows in the tree, this.durationRowsToVals, which maps rows in
+ * the tree to duration values, and this.durationStartTimes, which maps
+ * duration values to their corresponding start times.
+ */
+ initDurationDropdown: function ()
+ {
+ // First, calculate the start times for each duration.
+ this.durationStartTimes = {};
+ var durVals = [];
+ var durPopup = document.getElementById("sanitizeDurationPopup");
+ var durMenuitems = durPopup.childNodes;
+ for (let i = 0; i < durMenuitems.length; i++) {
+ let durMenuitem = durMenuitems[i];
+ let durVal = parseInt(durMenuitem.value);
+ if (durMenuitem.localName === "menuitem" &&
+ durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
+ durVal !== this.TIMESPAN_CUSTOM) {
+ durVals.push(durVal);
+ let durTimes = Sanitizer.getClearRange(durVal);
+ this.durationStartTimes[durVal] = durTimes[0];
+ }
+ }
+
+ // Sort the duration values ascending. Because one tree index can map to
+ // more than one duration, this ensures that this.durationRowsToVals maps
+ // a row index to the largest duration possible in the code below.
+ durVals.sort();
+
+ // Now calculate the rows in the tree of the durations' start times. For
+ // each duration, we are looking for the node in the tree whose time is the
+ // smallest time greater than or equal to the duration's start time.
+ this.durationRowsToVals = {};
+ this.durationValsToRows = {};
+ var view = this.placesTree.view;
+ // For all rows in the tree except the grippy row...
+ for (let i = 0; i < view.rowCount - 1; i++) {
+ let unfoundDurVals = [];
+ let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
+ nodeForTreeIndex(i).time;
+ // For all durations whose rows have not yet been found in the tree, see
+ // if index i is their index. An index may map to more than one duration,
+ // in which case the final duration (the largest) wins.
+ for (let j = 0; j < durVals.length; j++) {
+ let durVal = durVals[j];
+ let durStartTime = this.durationStartTimes[durVal];
+ if (nodeTime < durStartTime) {
+ this.durationValsToRows[durVal] = i - 1;
+ this.durationRowsToVals[i - 1] = durVal;
+ }
+ else
+ unfoundDurVals.push(durVal);
+ }
+ durVals = unfoundDurVals;
+ }
+
+ // If any durations were not found above, then every node in the tree has a
+ // time greater than or equal to the duration. In other words, those
+ // durations include the entire tree (except the grippy row).
+ for (let i = 0; i < durVals.length; i++) {
+ let durVal = durVals[i];
+ this.durationValsToRows[durVal] = view.rowCount - 2;
+ this.durationRowsToVals[view.rowCount - 2] = durVal;
+ }
+ },
+
+ /**
+ * If the Places tree is not set up, sets it up. Otherwise does nothing.
+ */
+ ensurePlacesTreeIsInited: function ()
+ {
+ if (this._placesTreeIsInited)
+ return;
+
+ this._placesTreeIsInited = true;
+
+ // Either "Last Four Hours" or "Today" will have the most history. If
+ // it's been more than 4 hours since today began, "Today" will. Otherwise
+ // "Last Four Hours" will.
+ var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
+
+ // If it's been less than 4 hours since today began, use the past 4 hours.
+ if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
+ times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
+ }
+
+ var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsINavHistoryService);
+ var query = histServ.getNewQuery();
+ query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
+ query.beginTime = times[0];
+ query.endTimeReference = query.TIME_RELATIVE_EPOCH;
+ query.endTime = times[1];
+ var opts = histServ.getNewQueryOptions();
+ opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
+ opts.queryType = opts.QUERY_TYPE_HISTORY;
+ var result = histServ.executeQuery(query, opts);
+
+ var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
+ new PlacesTreeView());
+ result.addObserver(view, false);
+ this.initDurationDropdown();
+ },
+
+ /**
+ * Called on select of the duration dropdown and when grippyMoved() sets a
+ * duration based on the location of the grippy row. Selects all the nodes in
+ * the tree that are contained in the selected duration. If clearing
+ * everything, the warning panel is shown instead.
+ */
+ selectByTimespan: function ()
+ {
+ // This method is the onselect handler for the duration dropdown. As a
+ // result it's called a couple of times before onload calls init().
+ if (!this._inited)
+ return;
+
+ var durDeck = document.getElementById("durationDeck");
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durVal = parseInt(durList.value);
+ var durCustom = document.getElementById("sanitizeDurationCustom");
+
+ // If grippy row is not at a duration boundary, show the custom menuitem;
+ // otherwise, hide it. Since the user cannot specify a custom duration by
+ // using the dropdown, this conditional is true only when this method is
+ // called onselect from grippyMoved(), so no selection need be made.
+ if (durVal === this.TIMESPAN_CUSTOM) {
+ durCustom.hidden = false;
+ return;
+ }
+ durCustom.hidden = true;
+
+ // If clearing everything, show the warning and change the dialog's title.
+ if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ durDeck.selectedIndex = 1;
+ window.document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ document.documentElement.getButton("accept").disabled = false;
+ return;
+ }
+
+ // Otherwise -- if clearing a specific time range -- select that time range
+ // in the tree.
+ this.ensurePlacesTreeIsInited();
+ durDeck.selectedIndex = 0;
+ window.document.title =
+ window.document.documentElement.getAttribute("noneverythingtitle");
+ var durRow = this.durationValsToRows[durVal];
+ gContiguousSelectionTreeHelper.rangedSelect(durRow);
+ gContiguousSelectionTreeHelper.scrollToGrippy();
+
+ // If duration is empty (there are no selected rows), disable the dialog's
+ // OK button.
+ document.documentElement.getButton("accept").disabled = durRow < 0;
+ },
+
+ sanitize: function ()
+ {
+ // Update pref values before handing off to the sanitizer (bug 453440)
+ this.updatePrefs();
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durValue = parseInt(durList.value);
+ s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
+
+ // Set the sanitizer's time range if we're not clearing everything.
+ if (!s.ignoreTimespan) {
+ // If user selected a custom timespan, use that.
+ if (durValue === this.TIMESPAN_CUSTOM) {
+ var view = this.placesTree.view;
+ var now = Date.now() * 1000;
+ // We disable the dialog's OK button if there's no selection, but we'll
+ // handle that case just in... case.
+ if (view.selection.getRangeCount() === 0)
+ s.range = [now, now];
+ else {
+ var startIndexRef = {};
+ // Tree sorted by visit date DEscending, so start time time comes last.
+ view.selection.getRangeAt(0, {}, startIndexRef);
+ view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
+ var startNode = view.nodeForTreeIndex(startIndexRef.value);
+ s.range = [startNode.time, now];
+ }
+ }
+ // Otherwise use the predetermined range.
+ else
+ s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
+ }
+
+ try {
+ s.sanitize();
+ } catch (er) {
+ Components.utils.reportError("Exception during sanitize: " + er);
+ }
+ return true;
+ },
+
+ /**
+ * In order to mark the custom Places tree view and its nsINavHistoryResult
+ * for garbage collection, we need to break the reference cycle between the
+ * two.
+ */
+ unload: function ()
+ {
+ let result = this.placesTree.getResult();
+ result.removeObserver(this.placesTree.view);
+ this.placesTree.view = null;
+ },
+
+ /**
+ * Called when the user moves the grippy by dragging it, clicking in the tree,
+ * or on keypress. Updates the duration dropdown so that it displays the
+ * appropriate specific or custom duration.
+ *
+ * @param aEventName
+ * The name of the event whose handler called this method, e.g.,
+ * "ondragstart", "onkeypress", etc.
+ * @param aEvent
+ * The event captured in the event handler.
+ */
+ grippyMoved: function (aEventName, aEvent)
+ {
+ gContiguousSelectionTreeHelper[aEventName](aEvent);
+ var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durValue = parseInt(durList.value);
+
+ // Multiple durations can map to the same row. Don't update the dropdown
+ // if the current duration is valid for lastSelRow.
+ if ((durValue !== this.TIMESPAN_CUSTOM ||
+ lastSelRow in this.durationRowsToVals) &&
+ (durValue === this.TIMESPAN_CUSTOM ||
+ this.durationValsToRows[durValue] !== lastSelRow)) {
+ // Setting durList.value causes its onselect handler to fire, which calls
+ // selectByTimespan().
+ if (lastSelRow in this.durationRowsToVals)
+ durList.value = this.durationRowsToVals[lastSelRow];
+ else
+ durList.value = this.TIMESPAN_CUSTOM;
+ }
+
+ // If there are no selected rows, disable the dialog's OK button.
+ document.documentElement.getButton("accept").disabled = lastSelRow < 0;
+ }
+#endif
+
+};
+
+
+#ifdef CRH_DIALOG_TREE_VIEW
+/**
+ * A helper for handling contiguous selection in the tree.
+ */
+var gContiguousSelectionTreeHelper = {
+
+ /**
+ * Gets the tree associated with this helper.
+ */
+ get tree()
+ {
+ return this._tree;
+ },
+
+ /**
+ * Sets the tree that this module handles. The tree is assigned a new view
+ * that is equipped to handle contiguous selection. You can pass in an
+ * object that will be used as the prototype of the new view. Otherwise
+ * the tree's current view is used as the prototype.
+ *
+ * @param aTreeElement
+ * The tree element
+ * @param aProtoTreeView
+ * If defined, this will be used as the prototype of the tree's new
+ * view
+ * @return The new view
+ */
+ setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
+ {
+ this._tree = aTreeElement;
+ var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
+ aTreeElement.view = newView;
+ return newView;
+ },
+
+ /**
+ * The index of the row that the grippy occupies. Note that the index of the
+ * last selected row is getGrippyRow() - 1. If getGrippyRow() is 0, then
+ * no selection exists.
+ *
+ * @return The row index of the grippy
+ */
+ getGrippyRow: function CSTH_getGrippyRow()
+ {
+ var sel = this.tree.view.selection;
+ var rangeCount = sel.getRangeCount();
+ if (rangeCount === 0)
+ return 0;
+ if (rangeCount !== 1) {
+ throw "contiguous selection tree helper: getGrippyRow called with " +
+ "multiple selection ranges";
+ }
+ var max = {};
+ sel.getRangeAt(0, {}, max);
+ return max.value + 1;
+ },
+
+ /**
+ * Helper function for the dragover event. Your dragover listener should
+ * call this. It updates the selection in the tree under the mouse.
+ *
+ * @param aEvent
+ * The observed dragover event
+ */
+ ondragover: function CSTH_ondragover(aEvent)
+ {
+ // Without this when dragging on Windows the mouse cursor is a "no" sign.
+ // This makes it a drop symbol.
+ var ds = Cc["@mozilla.org/widget/dragservice;1"].
+ getService(Ci.nsIDragService).
+ getCurrentSession();
+ ds.canDrop = true;
+ ds.dragAction = 0;
+
+ var tbo = this.tree.treeBoxObject;
+ aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
+ var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (hoverRow < 0)
+ return;
+
+ this.rangedSelect(hoverRow - 1);
+ },
+
+ /**
+ * Helper function for the dragstart event. Your dragstart listener should
+ * call this. It starts a drag session.
+ *
+ * @param aEvent
+ * The observed dragstart event
+ */
+ ondragstart: function CSTH_ondragstart(aEvent)
+ {
+ var tbo = this.tree.treeBoxObject;
+ var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (clickedRow !== this.getGrippyRow())
+ return;
+
+ // This part is a hack. What we really want is a grab and slide, not
+ // drag and drop. Start a move drag session with dummy data and a
+ // dummy region. Set the region's coordinates to (Infinity, Infinity)
+ // so it's drawn offscreen and its size to (1, 1).
+ var arr = Cc["@mozilla.org/supports-array;1"].
+ createInstance(Ci.nsISupportsArray);
+ var trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(null);
+ trans.setTransferData('dummy-flavor', null, 0);
+ arr.AppendElement(trans);
+ var reg = Cc["@mozilla.org/gfx/region;1"].
+ createInstance(Ci.nsIScriptableRegion);
+ reg.setToRect(Infinity, Infinity, 1, 1);
+ var ds = Cc["@mozilla.org/widget/dragservice;1"].
+ getService(Ci.nsIDragService);
+ ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
+ },
+
+ /**
+ * Helper function for the keypress event. Your keypress listener should
+ * call this. Users can use Up, Down, Page Up/Down, Home, and End to move
+ * the bottom of the selection window.
+ *
+ * @param aEvent
+ * The observed keypress event
+ */
+ onkeypress: function CSTH_onkeypress(aEvent)
+ {
+ var grippyRow = this.getGrippyRow();
+ var tbo = this.tree.treeBoxObject;
+ var rangeEnd;
+ switch (aEvent.keyCode) {
+ case aEvent.DOM_VK_HOME:
+ rangeEnd = 0;
+ break;
+ case aEvent.DOM_VK_PAGE_UP:
+ rangeEnd = grippyRow - tbo.getPageLength();
+ break;
+ case aEvent.DOM_VK_UP:
+ rangeEnd = grippyRow - 2;
+ break;
+ case aEvent.DOM_VK_DOWN:
+ rangeEnd = grippyRow;
+ break;
+ case aEvent.DOM_VK_PAGE_DOWN:
+ rangeEnd = grippyRow + tbo.getPageLength();
+ break;
+ case aEvent.DOM_VK_END:
+ rangeEnd = this.tree.view.rowCount - 2;
+ break;
+ default:
+ return;
+ break;
+ }
+
+ aEvent.stopPropagation();
+
+ // First, clip rangeEnd. this.rangedSelect() doesn't clip the range if we
+ // select past the ends of the tree.
+ if (rangeEnd < 0)
+ rangeEnd = -1;
+ else if (this.tree.view.rowCount - 2 < rangeEnd)
+ rangeEnd = this.tree.view.rowCount - 2;
+
+ // Next, (de)select.
+ this.rangedSelect(rangeEnd);
+
+ // Finally, scroll the tree. We always want one row above and below the
+ // grippy row to be visible if possible.
+ if (rangeEnd < grippyRow) // moved up
+ tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
+ else { // moved down
+ if (rangeEnd + 2 < this.tree.view.rowCount)
+ tbo.ensureRowIsVisible(rangeEnd + 2);
+ else if (rangeEnd + 1 < this.tree.view.rowCount)
+ tbo.ensureRowIsVisible(rangeEnd + 1);
+ }
+ },
+
+ /**
+ * Helper function for the mousedown event. Your mousedown listener should
+ * call this. Users can click on individual rows to make the selection
+ * jump to them immediately.
+ *
+ * @param aEvent
+ * The observed mousedown event
+ */
+ onmousedown: function CSTH_onmousedown(aEvent)
+ {
+ var tbo = this.tree.treeBoxObject;
+ var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
+ return;
+
+ if (clickedRow < this.getGrippyRow())
+ this.rangedSelect(clickedRow);
+ else if (clickedRow > this.getGrippyRow())
+ this.rangedSelect(clickedRow - 1);
+ },
+
+ /**
+ * Selects range [0, aEndRow] in the tree. The grippy row will then be at
+ * index aEndRow + 1. aEndRow may be -1, in which case the selection is
+ * cleared and the grippy row will be at index 0.
+ *
+ * @param aEndRow
+ * The range [0, aEndRow] will be selected.
+ */
+ rangedSelect: function CSTH_rangedSelect(aEndRow)
+ {
+ var tbo = this.tree.treeBoxObject;
+ if (aEndRow < 0)
+ this.tree.view.selection.clearSelection();
+ else
+ this.tree.view.selection.rangedSelect(0, aEndRow, false);
+ tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
+ },
+
+ /**
+ * Scrolls the tree so that the grippy row is in the center of the view.
+ */
+ scrollToGrippy: function CSTH_scrollToGrippy()
+ {
+ var rowCount = this.tree.view.rowCount;
+ var tbo = this.tree.treeBoxObject;
+ var pageLen = tbo.getPageLength() ||
+ parseInt(this.tree.getAttribute("rows")) ||
+ 10;
+
+ // All rows fit on a single page.
+ if (rowCount <= pageLen)
+ return;
+
+ var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
+
+ // Grippy row is in first half of first page.
+ if (scrollToRow < 0)
+ scrollToRow = 0;
+
+ // Grippy row is in last half of last page.
+ else if (rowCount < scrollToRow + pageLen)
+ scrollToRow = rowCount - pageLen;
+
+ tbo.scrollToRow(scrollToRow);
+ },
+
+ /**
+ * Creates a new tree view suitable for contiguous selection. If
+ * aProtoTreeView is specified, it's used as the new view's prototype.
+ * Otherwise the tree's current view is used as the prototype.
+ *
+ * @param aProtoTreeView
+ * Used as the new view's prototype if specified
+ */
+ _makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
+ {
+ var view = aProtoTreeView;
+ var that = this;
+
+ //XXXadw: When Alex gets the grippy icon done, this may or may not change,
+ // depending on how we style it.
+ view.isSeparator = function CSTH_View_isSeparator(aRow)
+ {
+ return aRow === that.getGrippyRow();
+ };
+
+ // rowCount includes the grippy row.
+ view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
+ view.__defineGetter__("rowCount",
+ function CSTH_View_rowCount()
+ {
+ return this._rowCount + 1;
+ });
+
+ // This has to do with visual feedback in the view itself, e.g., drawing
+ // a small line underneath the dropzone. Not what we want.
+ view.canDrop = function CSTH_View_canDrop() { return false; };
+
+ // No clicking headers to sort the tree or sort feedback on columns.
+ view.cycleHeader = function CSTH_View_cycleHeader() {};
+ view.sortingChanged = function CSTH_View_sortingChanged() {};
+
+ // Override a bunch of methods to account for the grippy row.
+
+ view._getCellProperties = view.getCellProperties;
+ view.getCellProperties =
+ function CSTH_View_getCellProperties(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "grippyRow";
+ if (aRow < grippyRow)
+ return this._getCellProperties(aRow, aCol);
+
+ return this._getCellProperties(aRow - 1, aCol);
+ };
+
+ view._getRowProperties = view.getRowProperties;
+ view.getRowProperties =
+ function CSTH_View_getRowProperties(aRow)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "grippyRow";
+
+ if (aRow < grippyRow)
+ return this._getRowProperties(aRow);
+
+ return this._getRowProperties(aRow - 1);
+ };
+
+ view._getCellText = view.getCellText;
+ view.getCellText =
+ function CSTH_View_getCellText(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "";
+ aRow = aRow < grippyRow ? aRow : aRow - 1;
+ return this._getCellText(aRow, aCol);
+ };
+
+ view._getImageSrc = view.getImageSrc;
+ view.getImageSrc =
+ function CSTH_View_getImageSrc(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "";
+ aRow = aRow < grippyRow ? aRow : aRow - 1;
+ return this._getImageSrc(aRow, aCol);
+ };
+
+ view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
+ view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
+ view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
+ view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
+ {
+ return aRow < this.rowCount - 1;
+ };
+
+ return view;
+ }
+};
+#endif
diff --git a/application/palemoon/base/content/softwareUpdateOverlay.xul b/application/palemoon/base/content/softwareUpdateOverlay.xul
new file mode 100644
index 000000000..01170e46c
--- /dev/null
+++ b/application/palemoon/base/content/softwareUpdateOverlay.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="softwareUpdateOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="updates">
+
+#include browserMountPoints.inc
+
+</window>
+
+</overlay>
diff --git a/application/palemoon/base/content/sync/aboutSyncTabs-bindings.xml b/application/palemoon/base/content/sync/aboutSyncTabs-bindings.xml
new file mode 100644
index 000000000..e6108209a
--- /dev/null
+++ b/application/palemoon/base/content/sync/aboutSyncTabs-bindings.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="tabBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tab-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="start">
+ <xul:image class="tabIcon"
+ xbl:inherits="src=icon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:label xbl:inherits="value=title,selected"
+ crop="end" flex="1" class="title"/>
+ <xul:label xbl:inherits="value=url,selected"
+ crop="end" flex="1" class="url"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ <handlers>
+ <handler event="dblclick" button="0">
+ <![CDATA[
+ RemoteTabViewer.openSelected();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="client-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox pack="start" align="center" onfocus="event.target.blur()" onselect="return false;">
+ <xul:image/>
+ <xul:label xbl:inherits="value=clientName"
+ class="clientName"
+ crop="center" flex="1"/>
+ </xul:hbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/application/palemoon/base/content/sync/aboutSyncTabs.css b/application/palemoon/base/content/sync/aboutSyncTabs.css
new file mode 100644
index 000000000..5a353175b
--- /dev/null
+++ b/application/palemoon/base/content/sync/aboutSyncTabs.css
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+richlistitem[type="tab"] {
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
+}
+
+richlistitem[type="client"] {
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
+}
diff --git a/application/palemoon/base/content/sync/aboutSyncTabs.js b/application/palemoon/base/content/sync/aboutSyncTabs.js
new file mode 100644
index 000000000..535c43e56
--- /dev/null
+++ b/application/palemoon/base/content/sync/aboutSyncTabs.js
@@ -0,0 +1,313 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cu = Components.utils;
+
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let RemoteTabViewer = {
+ _tabsList: null,
+
+ init: function () {
+ Services.obs.addObserver(this, "weave:service:login:finish", false);
+ Services.obs.addObserver(this, "weave:engine:sync:finish", false);
+
+ this._tabsList = document.getElementById("tabsList");
+
+ this.buildList(true);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "weave:service:login:finish");
+ Services.obs.removeObserver(this, "weave:engine:sync:finish");
+ },
+
+ createItem: function(attrs) {
+ let item = document.createElement("richlistitem");
+
+ // Copy the attributes from the argument into the item
+ for (let attr in attrs) {
+ item.setAttribute(attr, attrs[attr]);
+ }
+
+ if (attrs["type"] == "tab") {
+ item.label = attrs.title != "" ? attrs.title : attrs.url;
+ }
+
+ return item;
+ },
+
+ filterTabs: function(event) {
+ let val = event.target.value.toLowerCase();
+ let numTabs = this._tabsList.getRowCount();
+ let clientTabs = 0;
+ let currentClient = null;
+
+ for (let i = 0; i < numTabs; i++) {
+ let item = this._tabsList.getItemAtIndex(i);
+ let hide = false;
+ if (item.getAttribute("type") == "tab") {
+ if (!item.getAttribute("url").toLowerCase().includes(val) &&
+ !item.getAttribute("title").toLowerCase().includes(val)) {
+ hide = true;
+ } else {
+ clientTabs++;
+ }
+ }
+ else if (item.getAttribute("type") == "client") {
+ if (currentClient) {
+ if (clientTabs == 0) {
+ currentClient.hidden = true;
+ }
+ }
+ currentClient = item;
+ clientTabs = 0;
+ }
+ item.hidden = hide;
+ }
+ if (clientTabs == 0) {
+ currentClient.hidden = true;
+ }
+ },
+
+ openSelected: function() {
+ let items = this._tabsList.selectedItems;
+ let urls = [];
+ for (let i = 0;i < items.length;i++) {
+ if (items[i].getAttribute("type") == "tab") {
+ urls.push(items[i].getAttribute("url"));
+ let index = this._tabsList.getIndexOfItem(items[i]);
+ this._tabsList.removeItemAt(index);
+ }
+ }
+ if (urls.length) {
+ getTopWin().gBrowser.loadTabs(urls);
+ this._tabsList.clearSelection();
+ }
+ },
+
+ bookmarkSingleTab: function() {
+ let item = this._tabsList.selectedItems[0];
+ let uri = Weave.Utils.makeURI(item.getAttribute("url"));
+ let title = item.getAttribute("title");
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: uri
+ , title: title
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window.top);
+ },
+
+ bookmarkSelectedTabs: function() {
+ let items = this._tabsList.selectedItems;
+ let URIs = [];
+ for (let i = 0;i < items.length;i++) {
+ if (items[i].getAttribute("type") == "tab") {
+ let uri = Weave.Utils.makeURI(items[i].getAttribute("url"));
+ if (!uri) {
+ continue;
+ }
+
+ URIs.push(uri);
+ }
+ }
+ if (URIs.length) {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "folder"
+ , URIList: URIs
+ , hiddenRows: [ "description" ]
+ }, window.top);
+ }
+ },
+
+ getIcon: function (iconUri, defaultIcon) {
+ try {
+ let iconURI = Weave.Utils.makeURI(iconUri);
+ return PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec;
+ } catch (ex) {
+ // Do nothing.
+ }
+
+ // Just give the provided default icon or the system's default.
+ return defaultIcon || PlacesUtils.favicons.defaultFavicon.spec;
+ },
+
+ _waitingForBuildList: false,
+
+ _buildListRequested: false,
+
+ buildList: function (force) {
+ if (this._waitingForBuildList) {
+ this._buildListRequested = true;
+ return;
+ }
+
+ this._waitingForBuildList = true;
+ this._buildListRequested = false;
+
+ this._clearTabList();
+
+ if (Weave.Service.isLoggedIn && this._refetchTabs(force)) {
+ this._generateWeaveTabList();
+ } else {
+ //XXXzpao We should say something about not being logged in & not having data
+ // or tell the appropriate condition. (bug 583344)
+ }
+
+ function complete() {
+ this._waitingForBuildList = false;
+ if (this._buildListRequested) {
+ CommonUtils.nextTick(this.buildList, this);
+ }
+ }
+
+ complete();
+ },
+
+ _clearTabList: function () {
+ let list = this._tabsList;
+
+ // Clear out existing richlistitems
+ let count = list.getRowCount();
+ if (count > 0) {
+ for (let i = count - 1; i >= 0; i--) {
+ list.removeItemAt(i);
+ }
+ }
+ },
+
+ _generateWeaveTabList: function () {
+ let engine = Weave.Service.engineManager.get("tabs");
+ let list = this._tabsList;
+
+ let seenURLs = new Set();
+ let localURLs = engine.getOpenURLs();
+
+ for (let [guid, client] in Iterator(engine.getAllClients())) {
+ // Create the client node, but don't add it in-case we don't show any tabs
+ let appendClient = true;
+
+ client.tabs.forEach(function({title, urlHistory, icon}) {
+ let url = urlHistory[0];
+ if (!url || localURLs.has(url) || seenURLs.has(url)) {
+ return;
+ }
+ seenURLs.add(url);
+
+ if (appendClient) {
+ let attrs = {
+ type: "client",
+ clientName: client.clientName,
+ class: Weave.Service.clientsEngine.isMobile(client.id) ? "mobile" : "desktop"
+ };
+ let clientEnt = this.createItem(attrs);
+ list.appendChild(clientEnt);
+ appendClient = false;
+ clientEnt.disabled = true;
+ }
+ let attrs = {
+ type: "tab",
+ title: title || url,
+ url: url,
+ icon: this.getIcon(icon),
+ }
+ let tab = this.createItem(attrs);
+ list.appendChild(tab);
+ }, this);
+ }
+ },
+
+ adjustContextMenu: function(event) {
+ let mode = "all";
+ switch (this._tabsList.selectedItems.length) {
+ case 0:
+ break;
+ case 1:
+ mode = "single"
+ break;
+ default:
+ mode = "multiple";
+ break;
+ }
+
+ let menu = document.getElementById("tabListContext");
+ let el = menu.firstChild;
+ while (el) {
+ let showFor = el.getAttribute("showFor");
+ if (showFor) {
+ el.hidden = showFor != mode && showFor != "all";
+ }
+
+ el = el.nextSibling;
+ }
+ },
+
+ _refetchTabs: function(force) {
+ if (!force) {
+ // Don't bother refetching tabs if we already did so recently
+ let lastFetch = 0;
+ try {
+ lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch");
+ }
+ catch (e) {
+ /* Just use the default value of 0 */
+ }
+
+ let now = Math.floor(Date.now() / 1000);
+ if (now - lastFetch < 30) {
+ return false;
+ }
+ }
+
+ // if Clients hasn't synced yet this session, we need to sync it as well.
+ if (Weave.Service.clientsEngine.lastSync == 0) {
+ Weave.Service.clientsEngine.sync();
+ }
+
+ // Force a sync only for the tabs engine
+ let engine = Weave.Service.engineManager.get("tabs");
+ engine.lastModified = null;
+ engine.sync();
+ Services.prefs.setIntPref("services.sync.lastTabFetch",
+ Math.floor(Date.now() / 1000));
+
+ return true;
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "weave:service:login:finish":
+ this.buildList(true);
+ break;
+ case "weave:engine:sync:finish":
+ if (subject == "tabs") {
+ this.buildList(false);
+ }
+ break;
+ }
+ },
+
+ handleClick: function(event) {
+ if (event.target.getAttribute("type") != "tab") {
+ return;
+ }
+
+
+ if (event.button == 1) {
+ let url = event.target.getAttribute("url");
+ openUILink(url, event);
+ let index = this._tabsList.getIndexOfItem(event.target);
+ this._tabsList.removeItemAt(index);
+ }
+ }
+}
+
diff --git a/application/palemoon/base/content/sync/aboutSyncTabs.xul b/application/palemoon/base/content/sync/aboutSyncTabs.xul
new file mode 100644
index 000000000..a4aa0032f
--- /dev/null
+++ b/application/palemoon/base/content/sync/aboutSyncTabs.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
+ %aboutSyncTabsDTD;
+]>
+
+<window id="tabs-display"
+ onload="RemoteTabViewer.init()"
+ onunload="RemoteTabViewer.uninit()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&tabs.otherDevices.label;">
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <html:head>
+ <html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
+ </html:head>
+
+ <popupset id="contextmenus">
+ <menupopup id="tabListContext">
+ <menuitem label="&tabs.context.openTab.label;"
+ accesskey="&tabs.context.openTab.accesskey;"
+ oncommand="RemoteTabViewer.openSelected()"
+ showFor="single"/>
+ <menuitem label="&tabs.context.bookmarkSingleTab.label;"
+ accesskey="&tabs.context.bookmarkSingleTab.accesskey;"
+ oncommand="RemoteTabViewer.bookmarkSingleTab(event)"
+ showFor="single"/>
+ <menuitem label="&tabs.context.openMultipleTabs.label;"
+ accesskey="&tabs.context.openMultipleTabs.accesskey;"
+ oncommand="RemoteTabViewer.openSelected()"
+ showFor="multiple"/>
+ <menuitem label="&tabs.context.bookmarkMultipleTabs.label;"
+ accesskey="&tabs.context.bookmarkMultipleTabs.accesskey;"
+ oncommand="RemoteTabViewer.bookmarkSelectedTabs()"
+ showFor="multiple"/>
+ <menuseparator/>
+ <menuitem label="&tabs.context.refreshList.label;"
+ accesskey="&tabs.context.refreshList.accesskey;"
+ oncommand="RemoteTabViewer.buildList()"
+ showFor="all"/>
+ </menupopup>
+ </popupset>
+ <richlistbox context="tabListContext" id="tabsList" seltype="multiple"
+ align="center" flex="1"
+ onclick="RemoteTabViewer.handleClick(event)"
+ oncontextmenu="RemoteTabViewer.adjustContextMenu(event)">
+ <hbox id="headers" align="center">
+ <label id="tabsListHeading"
+ value="&tabs.otherDevices.label;"/>
+ <spacer flex="1"/>
+ <textbox type="search"
+ emptytext="&tabs.searchText.label;"
+ oncommand="RemoteTabViewer.filterTabs(event)"/>
+ </hbox>
+
+ </richlistbox>
+</window>
+
diff --git a/application/palemoon/base/content/sync/addDevice.js b/application/palemoon/base/content/sync/addDevice.js
new file mode 100644
index 000000000..556e75768
--- /dev/null
+++ b/application/palemoon/base/content/sync/addDevice.js
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PIN_PART_LENGTH = 4;
+
+const ADD_DEVICE_PAGE = 0;
+const SYNC_KEY_PAGE = 1;
+const DEVICE_CONNECTED_PAGE = 2;
+
+let gSyncAddDevice = {
+
+ init: function init() {
+ this.pin1.setAttribute("maxlength", PIN_PART_LENGTH);
+ this.pin2.setAttribute("maxlength", PIN_PART_LENGTH);
+ this.pin3.setAttribute("maxlength", PIN_PART_LENGTH);
+
+ this.nextFocusEl = {pin1: this.pin2,
+ pin2: this.pin3,
+ pin3: this.wizard.getButton("next")};
+
+ this.throbber = document.getElementById("pairDeviceThrobber");
+ this.errorRow = document.getElementById("errorRow");
+
+ // Kick off a sync. That way the server will have the most recent data from
+ // this computer and it will show up immediately on the new device.
+ Weave.Service.scheduler.scheduleNextSync(0);
+ },
+
+ onPageShow: function onPageShow() {
+ this.wizard.getButton("back").hidden = true;
+
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.onTextBoxInput();
+ this.wizard.canRewind = false;
+ this.wizard.getButton("next").hidden = false;
+ this.pin1.focus();
+ break;
+ case SYNC_KEY_PAGE:
+ this.wizard.canAdvance = false;
+ this.wizard.canRewind = true;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("next").hidden = true;
+ document.getElementById("weavePassphrase").value =
+ Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+ break;
+ case DEVICE_CONNECTED_PAGE:
+ this.wizard.canAdvance = true;
+ this.wizard.canRewind = false;
+ this.wizard.getButton("cancel").hidden = true;
+ break;
+ }
+ },
+
+ onWizardAdvance: function onWizardAdvance() {
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.startTransfer();
+ return false;
+ case DEVICE_CONNECTED_PAGE:
+ window.close();
+ return false;
+ }
+ return true;
+ },
+
+ startTransfer: function startTransfer() {
+ this.errorRow.hidden = true;
+ // When onAbort is called, Weave may already be gone.
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+ onPaired: function onPaired() {
+ let credentials = {account: Weave.Service.identity.account,
+ password: Weave.Service.identity.basicPassword,
+ synckey: Weave.Service.identity.syncKey,
+ serverURL: Weave.Service.serverURL};
+ jpakeclient.sendAndComplete(credentials);
+ },
+ onComplete: function onComplete() {
+ delete self._jpakeclient;
+ self.wizard.pageIndex = DEVICE_CONNECTED_PAGE;
+
+ // Schedule a Sync for soonish to fetch the data uploaded by the
+ // device with which we just paired.
+ Weave.Service.scheduler.scheduleNextSync(Weave.Service.scheduler.activeInterval);
+ },
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Aborted by user, ignore.
+ if (error == JPAKE_ERROR_USERABORT) {
+ return;
+ }
+
+ self.errorRow.hidden = false;
+ self.throbber.hidden = true;
+ self.pin1.value = self.pin2.value = self.pin3.value = "";
+ self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+ self.pin1.focus();
+ }
+ });
+ this.throbber.hidden = false;
+ this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+ this.wizard.canAdvance = false;
+
+ let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+ let expectDelay = false;
+ jpakeclient.pairWithPIN(pin, expectDelay);
+ },
+
+ onWizardBack: function onWizardBack() {
+ if (this.wizard.pageIndex != SYNC_KEY_PAGE)
+ return true;
+
+ this.wizard.pageIndex = ADD_DEVICE_PAGE;
+ return false;
+ },
+
+ onWizardCancel: function onWizardCancel() {
+ if (this._jpakeclient) {
+ this._jpakeclient.abort();
+ delete this._jpakeclient;
+ }
+ return true;
+ },
+
+ onTextBoxInput: function onTextBoxInput(textbox) {
+ if (textbox && textbox.value.length == PIN_PART_LENGTH)
+ this.nextFocusEl[textbox.id].focus();
+
+ this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH
+ && this.pin2.value.length == PIN_PART_LENGTH
+ && this.pin3.value.length == PIN_PART_LENGTH);
+ },
+
+ goToSyncKeyPage: function goToSyncKeyPage() {
+ this.wizard.pageIndex = SYNC_KEY_PAGE;
+ }
+
+};
+// onWizardAdvance() and onPageShow() are run before init() so we'll set
+// these up as lazy getters.
+["wizard", "pin1", "pin2", "pin3"].forEach(function (id) {
+ XPCOMUtils.defineLazyGetter(gSyncAddDevice, id, function() {
+ return document.getElementById(id);
+ });
+});
diff --git a/application/palemoon/base/content/sync/addDevice.xul b/application/palemoon/base/content/sync/addDevice.xul
new file mode 100644
index 000000000..f2371aad0
--- /dev/null
+++ b/application/palemoon/base/content/sync/addDevice.xul
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="wizard"
+ title="&pairDevice.title.label;"
+ windowtype="Sync:AddDevice"
+ persist="screenX screenY"
+ onwizardnext="return gSyncAddDevice.onWizardAdvance();"
+ onwizardback="return gSyncAddDevice.onWizardBack();"
+ onwizardcancel="gSyncAddDevice.onWizardCancel();"
+ onload="gSyncAddDevice.init();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/addDevice.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="addDevicePage"
+ label="&pairDevice.title.label;"
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <description>
+ &pairDevice.dialog.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+ </description>
+ <separator class="groove-thin"/>
+ <description>
+ &addDevice.dialog.enterCode.label;
+ </description>
+ <separator class="groove-thin"/>
+ <vbox align="center">
+ <textbox id="pin1"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin2"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin3"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ </vbox>
+ <separator class="groove-thin"/>
+ <vbox id="pairDeviceThrobber" align="center" hidden="true">
+ <image/>
+ </vbox>
+ <hbox id="errorRow" pack="center" hidden="true">
+ <image class="statusIcon" status="error"/>
+ <label class="status"
+ value="&addDevice.dialog.tryAgain.label;"/>
+ </hbox>
+ <spacer flex="3"/>
+ <label class="text-link"
+ value="&addDevice.dontHaveDevice.label;"
+ onclick="gSyncAddDevice.goToSyncKeyPage();"/>
+ </wizardpage>
+
+ <!-- Need a non-empty label here, otherwise we get a default label on Mac -->
+ <wizardpage id="syncKeyPage"
+ label=" "
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <description>
+ &addDevice.dialog.recoveryKey.label;
+ </description>
+ <spacer/>
+
+ <groupbox>
+ <label value="&recoveryKeyEntry.label;"
+ accesskey="&recoveryKeyEntry.accesskey;"
+ control="weavePassphrase"/>
+ <textbox id="weavePassphrase"
+ readonly="true"/>
+ </groupbox>
+
+ <groupbox align="center">
+ <description>&recoveryKeyBackup.description;</description>
+ <hbox>
+ <button id="printSyncKeyButton"
+ label="&button.syncKeyBackup.print.label;"
+ accesskey="&button.syncKeyBackup.print.accesskey;"
+ oncommand="gSyncUtils.passphrasePrint('weavePassphrase');"/>
+ <button id="saveSyncKeyButton"
+ label="&button.syncKeyBackup.save.label;"
+ accesskey="&button.syncKeyBackup.save.accesskey;"
+ oncommand="gSyncUtils.passphraseSave('weavePassphrase');"/>
+ </hbox>
+ </groupbox>
+ </wizardpage>
+
+ <wizardpage id="deviceConnectedPage"
+ label="&addDevice.dialog.connected.label;"
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <vbox align="center">
+ <image id="successPageIcon"/>
+ </vbox>
+ <separator/>
+ <description class="normal">
+ &addDevice.dialog.successful.label;
+ </description>
+ </wizardpage>
+
+</wizard>
diff --git a/application/palemoon/base/content/sync/genericChange.js b/application/palemoon/base/content/sync/genericChange.js
new file mode 100644
index 000000000..6d1ce9485
--- /dev/null
+++ b/application/palemoon/base/content/sync/genericChange.js
@@ -0,0 +1,234 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let Change = {
+ _dialog: null,
+ _dialogType: null,
+ _status: null,
+ _statusIcon: null,
+ _firstBox: null,
+ _secondBox: null,
+
+ get _passphraseBox() {
+ delete this._passphraseBox;
+ return this._passphraseBox = document.getElementById("passphraseBox");
+ },
+
+ get _currentPasswordInvalid() {
+ return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+ },
+
+ get _updatingPassphrase() {
+ return this._dialogType == "UpdatePassphrase";
+ },
+
+ onLoad: function Change_onLoad() {
+ /* Load labels */
+ let introText = document.getElementById("introText");
+ let introText2 = document.getElementById("introText2");
+ let warningText = document.getElementById("warningText");
+
+ // load some other elements & info from the window
+ this._dialog = document.getElementById("change-dialog");
+ this._dialogType = window.arguments[0];
+ this._duringSetup = window.arguments[1];
+ this._status = document.getElementById("status");
+ this._statusIcon = document.getElementById("statusIcon");
+ this._statusRow = document.getElementById("statusRow");
+ this._firstBox = document.getElementById("textBox1");
+ this._secondBox = document.getElementById("textBox2");
+
+ this._dialog.getButton("finish").disabled = true;
+ this._dialog.getButton("back").hidden = true;
+
+ this._stringBundle =
+ Services.strings.createBundle("chrome://browser/locale/syncGenericChange.properties");
+
+ switch (this._dialogType) {
+ case "UpdatePassphrase":
+ case "ResetPassphrase":
+ document.getElementById("textBox1Row").hidden = true;
+ document.getElementById("textBox2Row").hidden = true;
+ document.getElementById("passphraseLabel").value
+ = this._str("new.recoverykey.label");
+ document.getElementById("passphraseSpacer").hidden = false;
+
+ if (this._updatingPassphrase) {
+ document.getElementById("passphraseHelpBox").hidden = false;
+ document.title = this._str("new.recoverykey.title");
+ introText.textContent = this._str("new.recoverykey.introText");
+ this._dialog.getButton("finish").label
+ = this._str("new.recoverykey.acceptButton");
+ }
+ else {
+ document.getElementById("generatePassphraseButton").hidden = false;
+ document.getElementById("passphraseBackupButtons").hidden = false;
+ let pp = Weave.Service.identity.syncKey;
+ if (Weave.Utils.isPassphrase(pp))
+ pp = Weave.Utils.hyphenatePassphrase(pp);
+ this._passphraseBox.value = pp;
+ this._passphraseBox.focus();
+ document.title = this._str("change.recoverykey.title");
+ introText.textContent = this._str("change.synckey.introText2");
+ warningText.textContent = this._str("change.recoverykey.warningText");
+ this._dialog.getButton("finish").label
+ = this._str("change.recoverykey.acceptButton");
+ if (this._duringSetup) {
+ this._dialog.getButton("finish").disabled = false;
+ }
+ }
+ break;
+ case "ChangePassword":
+ document.getElementById("passphraseRow").hidden = true;
+ let box1label = document.getElementById("textBox1Label");
+ let box2label = document.getElementById("textBox2Label");
+ box1label.value = this._str("new.password.label");
+
+ if (this._currentPasswordInvalid) {
+ document.title = this._str("new.password.title");
+ introText.textContent = this._str("new.password.introText");
+ this._dialog.getButton("finish").label
+ = this._str("new.password.acceptButton");
+ document.getElementById("textBox2Row").hidden = true;
+ }
+ else {
+ document.title = this._str("change.password.title");
+ box2label.value = this._str("new.password.confirm");
+ introText.textContent = this._str("change.password3.introText");
+ warningText.textContent = this._str("change.password.warningText");
+ this._dialog.getButton("finish").label
+ = this._str("change.password.acceptButton");
+ }
+ break;
+ }
+ document.getElementById("change-page")
+ .setAttribute("label", document.title);
+ },
+
+ _clearStatus: function _clearStatus() {
+ this._status.value = "";
+ this._statusIcon.removeAttribute("status");
+ },
+
+ _updateStatus: function Change__updateStatus(str, state) {
+ this._updateStatusWithString(this._str(str), state);
+ },
+
+ _updateStatusWithString: function Change__updateStatusWithString(string, state) {
+ this._statusRow.hidden = false;
+ this._status.value = string;
+ this._statusIcon.setAttribute("status", state);
+
+ let error = state == "error";
+ this._dialog.getButton("cancel").disabled = !error;
+ this._dialog.getButton("finish").disabled = !error;
+ document.getElementById("printSyncKeyButton").disabled = !error;
+ document.getElementById("saveSyncKeyButton").disabled = !error;
+
+ if (state == "success")
+ window.setTimeout(window.close, 1500);
+ },
+
+ onDialogAccept: function() {
+ switch (this._dialogType) {
+ case "UpdatePassphrase":
+ case "ResetPassphrase":
+ return this.doChangePassphrase();
+ break;
+ case "ChangePassword":
+ return this.doChangePassword();
+ break;
+ }
+ },
+
+ doGeneratePassphrase: function () {
+ let passphrase = Weave.Utils.generatePassphrase();
+ this._passphraseBox.value = Weave.Utils.hyphenatePassphrase(passphrase);
+ this._dialog.getButton("finish").disabled = false;
+ },
+
+ doChangePassphrase: function Change_doChangePassphrase() {
+ let pp = Weave.Utils.normalizePassphrase(this._passphraseBox.value);
+ if (this._updatingPassphrase) {
+ Weave.Service.identity.syncKey = pp;
+ if (Weave.Service.login()) {
+ this._updateStatus("change.recoverykey.success", "success");
+ Weave.Service.persistLogin();
+ Weave.Service.scheduler.delayedAutoConnect(0);
+ }
+ else {
+ this._updateStatus("new.passphrase.status.incorrect", "error");
+ }
+ }
+ else {
+ this._updateStatus("change.recoverykey.label", "active");
+
+ if (Weave.Service.changePassphrase(pp))
+ this._updateStatus("change.recoverykey.success", "success");
+ else
+ this._updateStatus("change.recoverykey.error", "error");
+ }
+
+ return false;
+ },
+
+ doChangePassword: function Change_doChangePassword() {
+ if (this._currentPasswordInvalid) {
+ Weave.Service.identity.basicPassword = this._firstBox.value;
+ if (Weave.Service.login()) {
+ this._updateStatus("change.password.status.success", "success");
+ Weave.Service.persistLogin();
+ }
+ else {
+ this._updateStatus("new.password.status.incorrect", "error");
+ }
+ }
+ else {
+ this._updateStatus("change.password.status.active", "active");
+
+ if (Weave.Service.changePassword(this._firstBox.value))
+ this._updateStatus("change.password.status.success", "success");
+ else
+ this._updateStatus("change.password.status.error", "error");
+ }
+
+ return false;
+ },
+
+ validate: function (event) {
+ let valid = false;
+ let errorString = "";
+
+ if (this._dialogType == "ChangePassword") {
+ if (this._currentPasswordInvalid)
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox);
+ else
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox, this._secondBox);
+ }
+ else {
+ //Pale Moon: Enforce minimum length of 8 for allowed custom passphrase
+ //and don't restrict it to "out of sync" situations only. People who
+ //go to this page generally know what they are doing ;)
+ valid = this._passphraseBox.value.length >= 8;
+ }
+
+ if (errorString == "")
+ this._clearStatus();
+ else
+ this._updateStatusWithString(errorString, "error");
+
+ this._statusRow.hidden = valid;
+ this._dialog.getButton("finish").disabled = !valid;
+ },
+
+ _str: function Change__string(str) {
+ return this._stringBundle.GetStringFromName(str);
+ }
+};
diff --git a/application/palemoon/base/content/sync/genericChange.xul b/application/palemoon/base/content/sync/genericChange.xul
new file mode 100644
index 000000000..3c0b2cd6c
--- /dev/null
+++ b/application/palemoon/base/content/sync/genericChange.xul
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="change-dialog"
+ windowtype="Weave:ChangeSomething"
+ persist="screenX screenY"
+ onwizardnext="Change.onLoad()"
+ onwizardfinish="return Change.onDialogAccept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/genericChange.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="change-page"
+ label="">
+
+ <description id="introText">
+ </description>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <grid>
+ <columns>
+ <column align="right"/>
+ <column flex="3"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="textBox1Row" align="center">
+ <label id="textBox1Label" control="textBox1"/>
+ <textbox id="textBox1" type="password" oninput="Change.validate()"/>
+ <spacer/>
+ </row>
+ <row id="textBox2Row" align="center">
+ <label id="textBox2Label" control="textBox2"/>
+ <textbox id="textBox2" type="password" oninput="Change.validate()"/>
+ <spacer/>
+ </row>
+ </rows>
+ </grid>
+
+ <vbox id="passphraseRow">
+ <hbox flex="1">
+ <label id="passphraseLabel" control="passphraseBox"/>
+ <spacer flex="1"/>
+ <label id="generatePassphraseButton"
+ hidden="true"
+ value="&syncGenerateNewKey.label;"
+ class="text-link inline-link"
+ onclick="event.stopPropagation();
+ Change.doGeneratePassphrase();"/>
+ </hbox>
+ <textbox id="passphraseBox"
+ flex="1"
+ onfocus="this.select()"
+ oninput="Change.validate()"/>
+ </vbox>
+
+ <vbox id="feedback" pack="center">
+ <hbox id="statusRow" align="center">
+ <image id="statusIcon" class="statusIcon"/>
+ <label id="status" class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox id="passphraseBackupButtons"
+ hidden="true"
+ pack="center">
+ <button id="printSyncKeyButton"
+ label="&button.syncKeyBackup.print.label;"
+ accesskey="&button.syncKeyBackup.print.accesskey;"
+ oncommand="gSyncUtils.passphrasePrint('passphraseBox');"/>
+ <button id="saveSyncKeyButton"
+ label="&button.syncKeyBackup.save.label;"
+ accesskey="&button.syncKeyBackup.save.accesskey;"
+ oncommand="gSyncUtils.passphraseSave('passphraseBox');"/>
+ </hbox>
+
+ <vbox id="passphraseHelpBox"
+ hidden="true">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="http://www.palemoon.org/sync/help/recoverykey.shtml">
+ &addDevice.showMeHow.label;
+ </label>
+ </description>
+ </vbox>
+
+ <spacer id="passphraseSpacer"
+ flex="1"
+ hidden="true"/>
+
+ <description id="warningText" class="data">
+ </description>
+
+ <spacer flex="1"/>
+ </wizardpage>
+</wizard>
diff --git a/application/palemoon/base/content/sync/key.xhtml b/application/palemoon/base/content/sync/key.xhtml
new file mode 100644
index 000000000..92abf0ee6
--- /dev/null
+++ b/application/palemoon/base/content/sync/key.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+ %syncBrandDTD;
+ <!ENTITY % syncKeyDTD SYSTEM "chrome://browser/locale/syncKey.dtd">
+ %syncKeyDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
+ %globalDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&syncKey.page.title;</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="robots" content="noindex"/>
+ <style type="text/css">
+ #synckey { font-size: 150% }
+ footer { font-size: 70% }
+ /* Bug 575675: Need to have an a:visited rule in a chrome document. */
+ a:visited { color: purple; }
+ </style>
+</head>
+
+<body dir="&locale.dir;">
+<h1>&syncKey.page.title;</h1>
+
+<p id="synckey" dir="ltr">SYNCKEY</p>
+
+<p>&syncKey.page.description2;</p>
+
+<div id="column1">
+ <h2>&syncKey.keepItSecret.heading;</h2>
+ <p>&syncKey.keepItSecret.description;</p>
+</div>
+
+<div id="column2">
+ <h2>&syncKey.keepItSafe.heading;</h2>
+ <p><em>&syncKey.keepItSafe1.description;</em>&syncKey.keepItSafe2.description;<em>&syncKey.keepItSafe3.description;</em>&syncKey.keepItSafe4a.description;</p>
+</div>
+
+<p>&syncKey.findOutMore1.label;<a href="http://www.palemoon.org/sync/">http://www.palemoon.org/sync/</a>&syncKey.findOutMore2.label;</p>
+
+<footer>
+ &syncKey.footer1.label;<a id="tosLink" href="termsURL">termsURL</a>&syncKey.footer2.label;<a id="ppLink" href="privacyURL">privacyURL</a>&syncKey.footer3.label;
+</footer>
+
+</body>
+</html>
diff --git a/application/palemoon/base/content/sync/notification.xml b/application/palemoon/base/content/sync/notification.xml
new file mode 100644
index 000000000..7a2b77382
--- /dev/null
+++ b/application/palemoon/base/content/sync/notification.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
+%notificationDTD;
+]>
+
+<bindings id="notificationBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="notificationbox" extends="chrome://global/content/bindings/notification.xml#notificationbox">
+ <content>
+ <xul:vbox xbl:inherits="hidden=notificationshidden">
+ <xul:spacer/>
+ <children includes="notification"/>
+ </xul:vbox>
+ <children/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ let temp = {};
+ Cu.import("resource://services-common/observers.js", temp);
+ temp.Observers.add("weave:notification:added", this.onNotificationAdded, this);
+ temp.Observers.add("weave:notification:removed", this.onNotificationRemoved, this);
+
+ for each (var notification in Weave.Notifications.notifications)
+ this._appendNotification(notification);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ let temp = {};
+ Cu.import("resource://services-common/observers.js", temp);
+ temp.Observers.remove("weave:notification:added", this.onNotificationAdded, this);
+ temp.Observers.remove("weave:notification:removed", this.onNotificationRemoved, this);
+ ]]></destructor>
+
+ <method name="onNotificationAdded">
+ <parameter name="subject"/>
+ <parameter name="data"/>
+ <body><![CDATA[
+ this._appendNotification(subject);
+ ]]></body>
+ </method>
+
+ <method name="onNotificationRemoved">
+ <parameter name="subject"/>
+ <parameter name="data"/>
+ <body><![CDATA[
+ // If the view of the notification hasn't been removed yet, remove it.
+ var notifications = this.allNotifications;
+ for each (var notification in notifications) {
+ if (notification.notification == subject) {
+ notification.close();
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_appendNotification">
+ <parameter name="notification"/>
+ <body><![CDATA[
+ var node = this.appendNotification(notification.description,
+ notification.title,
+ notification.iconURL,
+ notification.priority,
+ notification.buttons);
+ node.notification = notification;
+ ]]></body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="notification" extends="chrome://global/content/bindings/notification.xml#notification">
+ <content>
+ <xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
+ <xul:toolbarbutton ondblclick="event.stopPropagation();"
+ class="messageCloseButton close-icon tabbable"
+ xbl:inherits="hidden=hideclose"
+ tooltiptext="&closeNotification.tooltip;"
+ oncommand="document.getBindingParent(this).close()"/>
+ <xul:hbox anonid="details" align="center" flex="1">
+ <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image"/>
+ <xul:description anonid="messageText" class="messageText" xbl:inherits="xbl:text=label"/>
+
+ <!-- The children are the buttons defined by the notification. -->
+ <xul:hbox oncommand="document.getBindingParent(this)._doButtonCommand(event);">
+ <children/>
+ </xul:hbox>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <!-- Note: this used to be a field, but for some reason it kept getting
+ - reset to its default value for TabNotification elements.
+ - As a property, that doesn't happen, even though the property stores
+ - its value in a JS property |_notification| that is not defined
+ - in XBL as a field or property. Maybe this is wrong, but it works.
+ -->
+ <property name="notification"
+ onget="return this._notification"
+ onset="this._notification = val; return val;"/>
+ <method name="close">
+ <body><![CDATA[
+ Weave.Notifications.remove(this.notification);
+
+ // We should be able to call the base class's close method here
+ // to remove the notification element from the notification box,
+ // but we can't because of bug 373652, so instead we copied its code
+ // and execute it below.
+ var control = this.control;
+ if (control)
+ control.removeNotification(this);
+ else
+ this.hidden = true;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/application/palemoon/base/content/sync/progress.js b/application/palemoon/base/content/sync/progress.js
new file mode 100644
index 000000000..2063f612a
--- /dev/null
+++ b/application/palemoon/base/content/sync/progress.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://services-sync/main.js");
+
+let gProgressBar;
+let gCounter = 0;
+
+function onLoad(event) {
+ Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
+ Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false);
+ Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false);
+ Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false);
+
+ gProgressBar = document.getElementById('uploadProgressBar');
+
+ if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
+ gProgressBar.hidden = false;
+ }
+ else {
+ gProgressBar.hidden = true;
+ }
+}
+
+function onUnload(event) {
+ cleanUpObservers();
+}
+
+function cleanUpObservers() {
+ try {
+ Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish");
+ Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error");
+ Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish");
+ Services.obs.removeObserver(onServiceSync, "weave:service:sync:error");
+ }
+ catch (e) {
+ // may be double called by unload & exit. Ignore.
+ }
+}
+
+function onEngineSync(subject, topic, data) {
+ // The Clients engine syncs first. At this point we don't necessarily know
+ // yet how many engines will be enabled, so we'll ignore the Clients engine
+ // and evaluate how many engines are enabled when the first "real" engine
+ // syncs.
+ if (data == "clients") {
+ return;
+ }
+
+ if (!gCounter &&
+ Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
+ gProgressBar.max = Weave.Service.engineManager.getEnabled().length;
+ }
+
+ gCounter += 1;
+ gProgressBar.setAttribute("value", gCounter);
+}
+
+function onServiceSync(subject, topic, data) {
+ // To address the case where 0 engines are synced, we will fill the
+ // progress bar so the user knows that the sync has finished.
+ gProgressBar.setAttribute("value", gProgressBar.max);
+ cleanUpObservers();
+}
+
+function closeTab() {
+ window.close();
+}
diff --git a/application/palemoon/base/content/sync/progress.xhtml b/application/palemoon/base/content/sync/progress.xhtml
new file mode 100644
index 000000000..d403cb20d
--- /dev/null
+++ b/application/palemoon/base/content/sync/progress.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % syncProgressDTD
+ SYSTEM "chrome://browser/locale/syncProgress.dtd">
+ %syncProgressDTD;
+ <!ENTITY % syncSetupDTD
+ SYSTEM "chrome://browser/locale/syncSetup.dtd">
+ %syncSetupDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&syncProgress.pageTitle;</title>
+
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/skin/syncProgress.css"/>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://browser/skin/sync-16.png"/>
+
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/sync/progress.js"/>
+ </head>
+ <body onload="onLoad(event)" onunload="onUnload(event)" dir="&locale.dir;">
+ <title>&setup.successPage.title;</title>
+ <div id="floatingBox" class="main-content">
+ <div id="title">
+ <h1>&setup.successPage.title;</h1>
+ </div>
+ <div id="successLogo">
+ <img id="brandSyncLogo" src="chrome://browser/skin/sync-128.png" alt="&syncProgress.logoAltText;" />
+ </div>
+ <div id="loadingText">
+ <p id="blurb">&syncProgress.textBlurb; </p>
+ </div>
+ <div id="progressBar">
+ <progress id="uploadProgressBar" value="0"/>
+ </div>
+ <div id="bottomRow">
+ <button id="closeButton" onclick="closeTab()">&syncProgress.closeButton; </button>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/application/palemoon/base/content/sync/quota.js b/application/palemoon/base/content/sync/quota.js
new file mode 100644
index 000000000..7117a2ddf
--- /dev/null
+++ b/application/palemoon/base/content/sync/quota.js
@@ -0,0 +1,267 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+
+let gSyncQuota = {
+
+ init: function init() {
+ this.bundle = document.getElementById("quotaStrings");
+ let caption = document.getElementById("treeCaption");
+ caption.firstChild.nodeValue = this.bundle.getString("quota.treeCaption.label");
+
+ gUsageTreeView.init();
+ this.tree = document.getElementById("usageTree");
+ this.tree.view = gUsageTreeView;
+
+ this.loadData();
+ },
+
+ loadData: function loadData() {
+ this._usage_req = Weave.Service.getStorageInfo(Weave.INFO_COLLECTION_USAGE,
+ function (error, usage) {
+ delete gSyncQuota._usage_req;
+ // displayUsageData handles null values, so no need to check 'error'.
+ gUsageTreeView.displayUsageData(usage);
+ });
+
+ let usageLabel = document.getElementById("usageLabel");
+ let bundle = this.bundle;
+
+ this._quota_req = Weave.Service.getStorageInfo(Weave.INFO_QUOTA,
+ function (error, quota) {
+ delete gSyncQuota._quota_req;
+
+ if (error) {
+ usageLabel.value = bundle.getString("quota.usageError.label");
+ return;
+ }
+ let used = gSyncQuota.convertKB(quota[0]);
+ if (!quota[1]) {
+ // No quota on the server.
+ usageLabel.value = bundle.getFormattedString(
+ "quota.usageNoQuota.label", used);
+ return;
+ }
+ let percent = Math.round(100 * quota[0] / quota[1]);
+ let total = gSyncQuota.convertKB(quota[1]);
+ usageLabel.value = bundle.getFormattedString(
+ "quota.usagePercentage.label", [percent].concat(used).concat(total));
+ });
+ },
+
+ onCancel: function onCancel() {
+ if (this._usage_req) {
+ this._usage_req.abort();
+ }
+ if (this._quota_req) {
+ this._quota_req.abort();
+ }
+ return true;
+ },
+
+ onAccept: function onAccept() {
+ let engines = gUsageTreeView.getEnginesToDisable();
+ for each (let engine in engines) {
+ Weave.Service.engineManager.get(engine).enabled = false;
+ }
+ if (engines.length) {
+ // The 'Weave' object will disappear once the window closes.
+ let Service = Weave.Service;
+ Weave.Utils.nextTick(function() { Service.sync(); });
+ }
+ return this.onCancel();
+ },
+
+ convertKB: function convertKB(value) {
+ return DownloadUtils.convertByteUnits(value * 1024);
+ }
+
+};
+
+let gUsageTreeView = {
+
+ _ignored: {keys: true,
+ meta: true,
+ clients: true},
+
+ /*
+ * Internal data structures underlaying the tree.
+ */
+ _collections: [],
+ _byname: {},
+
+ init: function init() {
+ let retrievingLabel = gSyncQuota.bundle.getString("quota.retrieving.label");
+ for each (let engine in Weave.Service.engineManager.getEnabled()) {
+ if (this._ignored[engine.name])
+ continue;
+
+ // Some engines use the same pref, which means they can only be turned on
+ // and off together. We need to combine them here as well.
+ let existing = this._byname[engine.prefName];
+ if (existing) {
+ existing.engines.push(engine.name);
+ continue;
+ }
+
+ let obj = {name: engine.prefName,
+ title: this._collectionTitle(engine),
+ engines: [engine.name],
+ enabled: true,
+ sizeLabel: retrievingLabel};
+ this._collections.push(obj);
+ this._byname[engine.prefName] = obj;
+ }
+ },
+
+ _collectionTitle: function _collectionTitle(engine) {
+ try {
+ return gSyncQuota.bundle.getString(
+ "collection." + engine.prefName + ".label");
+ } catch (ex) {
+ return engine.Name;
+ }
+ },
+
+ /*
+ * Process the quota information as returned by info/collection_usage.
+ */
+ displayUsageData: function displayUsageData(data) {
+ for each (let coll in this._collections) {
+ coll.size = 0;
+ // If we couldn't retrieve any data, just blank out the label.
+ if (!data) {
+ coll.sizeLabel = "";
+ continue;
+ }
+
+ for each (let engineName in coll.engines)
+ coll.size += data[engineName] || 0;
+ let sizeLabel = "";
+ sizeLabel = gSyncQuota.bundle.getFormattedString(
+ "quota.sizeValueUnit.label", gSyncQuota.convertKB(coll.size));
+ coll.sizeLabel = sizeLabel;
+ }
+ let sizeColumn = this.treeBox.columns.getNamedColumn("size");
+ this.treeBox.invalidateColumn(sizeColumn);
+ },
+
+ /*
+ * Handle click events on the tree.
+ */
+ onTreeClick: function onTreeClick(event) {
+ if (event.button == 2)
+ return;
+
+ let cell = this.treeBox.getCellAt(event.clientX, event.clientY);
+ if (cell.col && cell.col.id == "enabled")
+ this.toggle(cell.row);
+ },
+
+ /*
+ * Toggle enabled state of an engine.
+ */
+ toggle: function toggle(row) {
+ // Update the tree
+ let collection = this._collections[row];
+ collection.enabled = !collection.enabled;
+ this.treeBox.invalidateRow(row);
+
+ // Display which ones will be removed
+ let freeup = 0;
+ let toremove = [];
+ for each (collection in this._collections) {
+ if (collection.enabled)
+ continue;
+ toremove.push(collection.name);
+ freeup += collection.size;
+ }
+
+ let caption = document.getElementById("treeCaption");
+ if (!toremove.length) {
+ caption.className = "";
+ caption.firstChild.nodeValue = gSyncQuota.bundle.getString(
+ "quota.treeCaption.label");
+ return;
+ }
+
+ toremove = [this._byname[coll].title for each (coll in toremove)];
+ toremove = toremove.join(gSyncQuota.bundle.getString("quota.list.separator"));
+ caption.firstChild.nodeValue = gSyncQuota.bundle.getFormattedString(
+ "quota.removal.label", [toremove]);
+ if (freeup)
+ caption.firstChild.nodeValue += gSyncQuota.bundle.getFormattedString(
+ "quota.freeup.label", gSyncQuota.convertKB(freeup));
+ caption.className = "captionWarning";
+ },
+
+ /*
+ * Return a list of engines (or rather their pref names) that should be
+ * disabled.
+ */
+ getEnginesToDisable: function getEnginesToDisable() {
+ return [coll.name for each (coll in this._collections) if (!coll.enabled)];
+ },
+
+ // nsITreeView
+
+ get rowCount() {
+ return this._collections.length;
+ },
+
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(row, col) { return ""; },
+ getColumnProperties: function(col) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isContainerEmpty: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function() { return false; },
+ canDrop: function(index, orientation, dataTransfer) { return false; },
+ drop: function(row, orientation, dataTransfer) {},
+ getParentIndex: function(rowIndex) {},
+ hasNextSibling: function(rowIndex, afterIndex) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, col) {},
+
+ getCellValue: function(row, col) {
+ return this._collections[row].enabled;
+ },
+
+ getCellText: function getCellText(row, col) {
+ let collection = this._collections[row];
+ switch (col.id) {
+ case "collection":
+ return collection.title;
+ case "size":
+ return collection.sizeLabel;
+ default:
+ return "";
+ }
+ },
+
+ setTree: function setTree(tree) {
+ this.treeBox = tree;
+ },
+
+ toggleOpenState: function(index) {},
+ cycleHeader: function(col) {},
+ selectionChanged: function() {},
+ cycleCell: function(row, col) {},
+ isEditable: function(row, col) { return false; },
+ isSelectable: function (row, col) { return false; },
+ setCellValue: function(row, col, value) {},
+ setCellText: function(row, col, value) {},
+ performAction: function(action) {},
+ performActionOnRow: function(action, row) {},
+ performActionOnCell: function(action, row, col) {}
+
+};
diff --git a/application/palemoon/base/content/sync/quota.xul b/application/palemoon/base/content/sync/quota.xul
new file mode 100644
index 000000000..99e6ed78b
--- /dev/null
+++ b/application/palemoon/base/content/sync/quota.xul
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncQuota.css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncQuotaDTD SYSTEM "chrome://browser/locale/syncQuota.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncQuotaDTD;
+]>
+<dialog id="quotaDialog"
+ windowtype="Sync:ViewQuota"
+ persist="screenX screenY width height"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="gSyncQuota.init()"
+ buttons="accept,cancel"
+ title="&quota.dialogTitle.label;"
+ ondialogcancel="return gSyncQuota.onCancel();"
+ ondialogaccept="return gSyncQuota.onAccept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/quota.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="quotaStrings"
+ src="chrome://browser/locale/syncQuota.properties"/>
+ </stringbundleset>
+
+ <vbox flex="1">
+ <label id="usageLabel"
+ value="&quota.retrievingInfo.label;"/>
+ <separator/>
+ <tree id="usageTree"
+ seltype="single"
+ hidecolumnpicker="true"
+ onclick="gUsageTreeView.onTreeClick(event);"
+ flex="1">
+ <treecols>
+ <treecol id="enabled"
+ type="checkbox"
+ fixed="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="collection"
+ label="&quota.typeColumn.label;"
+ flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="size"
+ label="&quota.sizeColumn.label;"
+ flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ <separator/>
+ <description id="treeCaption"> </description>
+ </vbox>
+
+</dialog>
diff --git a/application/palemoon/base/content/sync/setup.js b/application/palemoon/base/content/sync/setup.js
new file mode 100644
index 000000000..99faa038e
--- /dev/null
+++ b/application/palemoon/base/content/sync/setup.js
@@ -0,0 +1,1071 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+// page consts
+
+const PAIR_PAGE = 0;
+const INTRO_PAGE = 1;
+const NEW_ACCOUNT_START_PAGE = 2;
+const EXISTING_ACCOUNT_CONNECT_PAGE = 3;
+const EXISTING_ACCOUNT_LOGIN_PAGE = 4;
+const OPTIONS_PAGE = 5;
+const OPTIONS_CONFIRM_PAGE = 6;
+
+// Broader than we'd like, but after this changed from api-secure.recaptcha.net
+// we had no choice. At least we only do this for the duration of setup.
+// See discussion in Bugs 508112 and 653307.
+const RECAPTCHA_DOMAIN = "https://www.google.com";
+
+const PIN_PART_LENGTH = 4;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
+
+
+function setVisibility(element, visible) {
+ element.style.visibility = visible ? "visible" : "hidden";
+}
+
+var gSyncSetup = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ captchaBrowser: null,
+ wizard: null,
+ _disabledSites: [],
+
+ status: {
+ password: false,
+ email: false,
+ server: false
+ },
+
+ get _remoteSites() [Weave.Service.serverURL, RECAPTCHA_DOMAIN],
+
+ get _usingMainServers() {
+ if (this._settingUpNew)
+ return document.getElementById("server").selectedIndex == 0;
+ return document.getElementById("existingServer").selectedIndex == 0;
+ },
+
+ init: function () {
+ let obs = [
+ ["weave:service:change-passphrase", "onResetPassphrase"],
+ ["weave:service:login:start", "onLoginStart"],
+ ["weave:service:login:error", "onLoginEnd"],
+ ["weave:service:login:finish", "onLoginEnd"]];
+
+ // Add the observers now and remove them on unload
+ let self = this;
+ let addRem = function(add) {
+ obs.forEach(function([topic, func]) {
+ //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
+ // of `this`. Fix in a followup. (bug 583347)
+ if (add)
+ Weave.Svc.Obs.add(topic, self[func], self);
+ else
+ Weave.Svc.Obs.remove(topic, self[func], self);
+ });
+ };
+ addRem(true);
+ window.addEventListener("unload", function() addRem(false), false);
+
+ window.setTimeout(function () {
+ // Force Service to be loaded so that engines are registered.
+ // See Bug 670082.
+ Weave.Service;
+ }, 0);
+
+ this.captchaBrowser = document.getElementById("captcha");
+
+ this.wizardType = null;
+ if (window.arguments && window.arguments[0]) {
+ this.wizardType = window.arguments[0];
+ }
+ switch (this.wizardType) {
+ case null:
+ this.wizard.pageIndex = INTRO_PAGE;
+ // Fall through!
+ case "pair":
+ this.captchaBrowser.addProgressListener(this);
+ Weave.Svc.Prefs.set("firstSync", "notReady");
+ break;
+ case "reset":
+ this._resettingSync = true;
+ this.wizard.pageIndex = OPTIONS_PAGE;
+ break;
+ }
+
+ this.wizard.getButton("extra1").label =
+ this._stringBundle.GetStringFromName("button.syncOptions.label");
+
+ // Remember these values because the options pages change them temporarily.
+ this._nextButtonLabel = this.wizard.getButton("next").label;
+ this._nextButtonAccesskey = this.wizard.getButton("next")
+ .getAttribute("accesskey");
+ this._backButtonLabel = this.wizard.getButton("back").label;
+ this._backButtonAccesskey = this.wizard.getButton("back")
+ .getAttribute("accesskey");
+ },
+
+ startNewAccountSetup: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return false;
+ this._settingUpNew = true;
+ this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE;
+ },
+
+ useExistingAccount: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return false;
+ this._settingUpNew = false;
+ if (this.wizardType == "pair") {
+ // We're already pairing, so there's no point in pairing again.
+ // Go straight to the manual login page.
+ this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ } else {
+ this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE;
+ }
+ },
+
+ resetPassphrase: function resetPassphrase() {
+ // Apply the existing form fields so that
+ // Weave.Service.changePassphrase() has the necessary credentials.
+ Weave.Service.identity.account = document.getElementById("existingAccountName").value;
+ Weave.Service.identity.basicPassword = document.getElementById("existingPassword").value;
+
+ // Generate a new passphrase so that Weave.Service.login() will
+ // actually do something.
+ let passphrase = Weave.Utils.generatePassphrase();
+ Weave.Service.identity.syncKey = passphrase;
+
+ // Only open the dialog if username + password are actually correct.
+ Weave.Service.login();
+ if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE,
+ Weave.LOGIN_FAILED_NO_PASSPHRASE,
+ Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) {
+ return;
+ }
+
+ // Hide any errors about the passphrase, we know it's not right.
+ let feedback = document.getElementById("existingPassphraseFeedbackRow");
+ feedback.hidden = true;
+ let el = document.getElementById("existingPassphrase");
+ el.value = Weave.Utils.hyphenatePassphrase(passphrase);
+
+ // changePassphrase() will sync, make sure we set the "firstSync" pref
+ // according to the user's pref.
+ Weave.Svc.Prefs.reset("firstSync");
+ this.setupInitialSync();
+ gSyncUtils.resetPassphrase(true);
+ },
+
+ onResetPassphrase: function () {
+ document.getElementById("existingPassphrase").value =
+ Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+ this.checkFields();
+ this.wizard.advance();
+ },
+
+ onLoginStart: function () {
+ this.toggleLoginFeedback(false);
+ },
+
+ onLoginEnd: function () {
+ this.toggleLoginFeedback(true);
+ },
+
+ sendCredentialsAfterSync: function () {
+ let send = function() {
+ Services.obs.removeObserver("weave:service:sync:finish", send);
+ Services.obs.removeObserver("weave:service:sync:error", send);
+ let credentials = {account: Weave.Service.identity.account,
+ password: Weave.Service.identity.basicPassword,
+ synckey: Weave.Service.identity.syncKey,
+ serverURL: Weave.Service.serverURL};
+ this._jpakeclient.sendAndComplete(credentials);
+ }.bind(this);
+ Services.obs.addObserver("weave:service:sync:finish", send, false);
+ Services.obs.addObserver("weave:service:sync:error", send, false);
+ },
+
+ toggleLoginFeedback: function (stop) {
+ document.getElementById("login-throbber").hidden = stop;
+ let password = document.getElementById("existingPasswordFeedbackRow");
+ let server = document.getElementById("existingServerFeedbackRow");
+ let passphrase = document.getElementById("existingPassphraseFeedbackRow");
+
+ if (!stop || (Weave.Status.login == Weave.LOGIN_SUCCEEDED)) {
+ password.hidden = server.hidden = passphrase.hidden = true;
+ return;
+ }
+
+ let feedback;
+ switch (Weave.Status.login) {
+ case Weave.LOGIN_FAILED_NETWORK_ERROR:
+ case Weave.LOGIN_FAILED_SERVER_ERROR:
+ feedback = server;
+ break;
+ case Weave.LOGIN_FAILED_LOGIN_REJECTED:
+ case Weave.LOGIN_FAILED_NO_USERNAME:
+ case Weave.LOGIN_FAILED_NO_PASSWORD:
+ feedback = password;
+ break;
+ case Weave.LOGIN_FAILED_INVALID_PASSPHRASE:
+ feedback = passphrase;
+ break;
+ }
+ this._setFeedbackMessage(feedback, false, Weave.Status.login);
+ },
+
+ setupInitialSync: function () {
+ let action = document.getElementById("mergeChoiceRadio").selectedItem.id;
+ switch (action) {
+ case "resetClient":
+ // if we're not resetting sync, we don't need to explicitly
+ // call resetClient
+ if (!this._resettingSync)
+ return;
+ // otherwise, fall through
+ case "wipeClient":
+ case "wipeRemote":
+ Weave.Svc.Prefs.set("firstSync", action);
+ break;
+ }
+ },
+
+ // fun with validation!
+ checkFields: function () {
+ this.wizard.canAdvance = this.readyToAdvance();
+ },
+
+ readyToAdvance: function () {
+ switch (this.wizard.pageIndex) {
+ case INTRO_PAGE:
+ return false;
+ case NEW_ACCOUNT_START_PAGE:
+ for (let i in this.status) {
+ if (!this.status[i])
+ return false;
+ }
+ if (this._usingMainServers)
+ return document.getElementById("tos").checked;
+
+ return true;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ let hasUser = document.getElementById("existingAccountName").value != "";
+ let hasPass = document.getElementById("existingPassword").value != "";
+ let hasKey = document.getElementById("existingPassphrase").value != "";
+
+ if (hasUser && hasPass && hasKey) {
+ if (this._usingMainServers)
+ return true;
+
+ if (this._validateServer(document.getElementById("existingServer"))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // Default, e.g. wizard's special page -1 etc.
+ return true;
+ },
+
+ onPINInput: function onPINInput(textbox) {
+ if (textbox && textbox.value.length == PIN_PART_LENGTH) {
+ this.nextFocusEl[textbox.id].focus();
+ }
+ this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH &&
+ this.pin2.value.length == PIN_PART_LENGTH &&
+ this.pin3.value.length == PIN_PART_LENGTH);
+ },
+
+ onEmailInput: function () {
+ // Check account validity when the user stops typing for 1 second.
+ if (this._checkAccountTimer)
+ window.clearTimeout(this._checkAccountTimer);
+ this._checkAccountTimer = window.setTimeout(function () {
+ gSyncSetup.checkAccount();
+ }, 1000);
+ },
+
+ checkAccount: function() {
+ delete this._checkAccountTimer;
+ let value = Weave.Utils.normalizeAccount(
+ document.getElementById("weaveEmail").value);
+ if (!value) {
+ this.status.email = false;
+ this.checkFields();
+ return;
+ }
+
+ let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ let feedback = document.getElementById("emailFeedbackRow");
+ let valid = re.test(value);
+
+ let str = "";
+ if (!valid) {
+ str = "invalidEmail.label";
+ } else {
+ let availCheck = Weave.Service.checkAccount(value);
+ valid = availCheck == "available";
+ if (!valid) {
+ if (availCheck == "notAvailable")
+ str = "usernameNotAvailable.label";
+ else
+ str = availCheck;
+ }
+ }
+
+ this._setFeedbackMessage(feedback, valid, str);
+ this.status.email = valid;
+ if (valid)
+ Weave.Service.identity.account = value;
+ this.checkFields();
+ },
+
+ onPasswordChange: function () {
+ let password = document.getElementById("weavePassword");
+ let pwconfirm = document.getElementById("weavePasswordConfirm");
+ let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm);
+
+ let feedback = document.getElementById("passwordFeedbackRow");
+ this._setFeedback(feedback, valid, errorString);
+
+ this.status.password = valid;
+ this.checkFields();
+ },
+
+ onPageShow: function() {
+ switch (this.wizard.pageIndex) {
+ case PAIR_PAGE:
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("extra1").hidden = true;
+ this.onPINInput();
+ this.pin1.focus();
+ break;
+ case INTRO_PAGE:
+ // We may not need the captcha in the Existing Account branch of the
+ // wizard. However, we want to preload it to avoid any flickering while
+ // the Create Account page is shown.
+ this.loadCaptcha();
+ this.wizard.getButton("next").hidden = true;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("extra1").hidden = true;
+ this.checkFields();
+ break;
+ case NEW_ACCOUNT_START_PAGE:
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.onServerCommand();
+ this.wizard.canRewind = true;
+ this.checkFields();
+ break;
+ case EXISTING_ACCOUNT_CONNECT_PAGE:
+ Weave.Svc.Prefs.set("firstSync", "existingAccount");
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.canAdvance = false;
+ this.wizard.canRewind = true;
+ this.startEasySetup();
+ break;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.canRewind = true;
+ this.checkFields();
+ break;
+ case OPTIONS_PAGE:
+ this.wizard.canRewind = false;
+ this.wizard.canAdvance = true;
+ if (!this._resettingSync) {
+ this.wizard.getButton("next").label =
+ this._stringBundle.GetStringFromName("button.syncOptionsDone.label");
+ this.wizard.getButton("next").removeAttribute("accesskey");
+ }
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("cancel").hidden = !this._resettingSync;
+ this.wizard.getButton("extra1").hidden = true;
+ document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
+ document.getElementById("syncOptions").collapsed = this._resettingSync;
+ document.getElementById("mergeOptions").collapsed = this._settingUpNew;
+ break;
+ case OPTIONS_CONFIRM_PAGE:
+ this.wizard.canRewind = true;
+ this.wizard.canAdvance = true;
+ this.wizard.getButton("back").label =
+ this._stringBundle.GetStringFromName("button.syncOptionsCancel.label");
+ this.wizard.getButton("back").removeAttribute("accesskey");
+ this.wizard.getButton("back").hidden = this._resettingSync;
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("finish").hidden = true;
+ break;
+ }
+ },
+
+ onWizardAdvance: function () {
+ // Check pageIndex so we don't prompt before the Sync setup wizard appears.
+ // This is a fallback in case the Master Password gets locked mid-wizard.
+ if ((this.wizard.pageIndex >= 0) &&
+ !Weave.Utils.ensureMPUnlocked()) {
+ return false;
+ }
+
+ switch (this.wizard.pageIndex) {
+ case PAIR_PAGE:
+ this.startPairing();
+ return false;
+ case NEW_ACCOUNT_START_PAGE:
+ // If the user selects Next (e.g. by hitting enter) when we haven't
+ // executed the delayed checks yet, execute them immediately.
+ if (this._checkAccountTimer) {
+ this.checkAccount();
+ }
+ if (this._checkServerTimer) {
+ this.checkServer();
+ }
+ if (!this.wizard.canAdvance) {
+ return false;
+ }
+
+ let doc = this.captchaBrowser.contentDocument;
+ let getField = function getField(field) {
+ let node = doc.getElementById("recaptcha_" + field + "_field");
+ return node && node.value;
+ };
+
+ // Display throbber
+ let feedback = document.getElementById("captchaFeedback");
+ let image = feedback.firstChild;
+ let label = image.nextSibling;
+ image.setAttribute("status", "active");
+ label.value = this._stringBundle.GetStringFromName("verifying.label");
+ setVisibility(feedback, true);
+
+ let password = document.getElementById("weavePassword").value;
+ let email = Weave.Utils.normalizeAccount(
+ document.getElementById("weaveEmail").value);
+ let challenge = getField("challenge");
+ let response = getField("response");
+
+ let error = Weave.Service.createAccount(email, password,
+ challenge, response);
+
+ if (error == null) {
+ Weave.Service.identity.account = email;
+ Weave.Service.identity.basicPassword = password;
+ Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase();
+ this._handleNoScript(false);
+ Weave.Svc.Prefs.set("firstSync", "newAccount");
+ this.wizardFinish();
+ return false;
+ }
+
+ image.setAttribute("status", "error");
+ label.value = Weave.Utils.getErrorString(error);
+ return false;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ Weave.Service.identity.account = Weave.Utils.normalizeAccount(
+ document.getElementById("existingAccountName").value);
+ Weave.Service.identity.basicPassword =
+ document.getElementById("existingPassword").value;
+ let pp = document.getElementById("existingPassphrase").value;
+ Weave.Service.identity.syncKey = Weave.Utils.normalizePassphrase(pp);
+ if (Weave.Service.login()) {
+ this.wizardFinish();
+ }
+ return false;
+ case OPTIONS_PAGE:
+ let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
+ // No confirmation needed on new account setup or merge option
+ // with existing account.
+ if (this._settingUpNew || (!this._resettingSync && desc == 0))
+ return this.returnFromOptions();
+ return this._handleChoice();
+ case OPTIONS_CONFIRM_PAGE:
+ if (this._resettingSync) {
+ this.wizardFinish();
+ return false;
+ }
+ return this.returnFromOptions();
+ }
+ return true;
+ },
+
+ onWizardBack: function () {
+ switch (this.wizard.pageIndex) {
+ case NEW_ACCOUNT_START_PAGE:
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ case EXISTING_ACCOUNT_CONNECT_PAGE:
+ this.abortEasySetup();
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ // If we were already pairing on entry, we went straight to the manual
+ // login page. If subsequently we go back, return to the page that lets
+ // us choose whether we already have an account.
+ if (this.wizardType == "pair") {
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ }
+ return true;
+ case OPTIONS_CONFIRM_PAGE:
+ // Backing up from the confirmation page = resetting first sync to merge.
+ document.getElementById("mergeChoiceRadio").selectedIndex = 0;
+ return this.returnFromOptions();
+ }
+ return true;
+ },
+
+ wizardFinish: function () {
+ this.setupInitialSync();
+
+ if (this.wizardType == "pair") {
+ this.completePairing();
+ }
+
+ if (!this._resettingSync) {
+ function isChecked(element) {
+ return document.getElementById(element).hasAttribute("checked");
+ }
+
+ let prefs = ["engine.bookmarks", "engine.passwords", "engine.history",
+ "engine.tabs", "engine.prefs", "engine.addons"];
+ for (let i = 0;i < prefs.length;i++) {
+ Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i]));
+ }
+
+ // XXX: Addons syncing is currently not operational;
+ // Make doubly-sure to always disable addons syncing pref
+ Weave.Svc.Prefs.set("engine.addons", false);
+
+ this._handleNoScript(false);
+ if (Weave.Svc.Prefs.get("firstSync", "") == "notReady")
+ Weave.Svc.Prefs.reset("firstSync");
+
+ Weave.Service.persistLogin();
+ Weave.Svc.Obs.notify("weave:service:setup-complete");
+
+ gSyncUtils.openFirstSyncProgressPage();
+ }
+ Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
+ window.close();
+ },
+
+ onWizardCancel: function () {
+ if (this._resettingSync)
+ return;
+
+ this.abortEasySetup();
+ this._handleNoScript(false);
+ Weave.Service.startOver();
+ },
+
+ onSyncOptions: function () {
+ this._beforeOptionsPage = this.wizard.pageIndex;
+ this.wizard.pageIndex = OPTIONS_PAGE;
+ },
+
+ returnFromOptions: function() {
+ this.wizard.getButton("next").label = this._nextButtonLabel;
+ this.wizard.getButton("next").setAttribute("accesskey",
+ this._nextButtonAccesskey);
+ this.wizard.getButton("back").label = this._backButtonLabel;
+ this.wizard.getButton("back").setAttribute("accesskey",
+ this._backButtonAccesskey);
+ this.wizard.getButton("cancel").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.pageIndex = this._beforeOptionsPage;
+ return false;
+ },
+
+ startPairing: function startPairing() {
+ this.pairDeviceErrorRow.hidden = true;
+ // When onAbort is called, Weave may already be gone.
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+ onPaired: function onPaired() {
+ self.wizard.pageIndex = INTRO_PAGE;
+ },
+ onComplete: function onComplete() {
+ // This method will never be called since SendCredentialsController
+ // will take over after the wizard completes.
+ },
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Aborted by user, ignore. The window is almost certainly going to close
+ // or is already closed.
+ if (error == JPAKE_ERROR_USERABORT) {
+ return;
+ }
+
+ self.pairDeviceErrorRow.hidden = false;
+ self.pairDeviceThrobber.hidden = true;
+ self.pin1.value = self.pin2.value = self.pin3.value = "";
+ self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+ if (self.wizard.pageIndex == PAIR_PAGE) {
+ self.pin1.focus();
+ }
+ }
+ });
+ this.pairDeviceThrobber.hidden = false;
+ this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+ this.wizard.canAdvance = false;
+
+ let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+ let expectDelay = true;
+ jpakeclient.pairWithPIN(pin, expectDelay);
+ },
+
+ completePairing: function completePairing() {
+ if (!this._jpakeclient) {
+ // The channel was aborted while we were setting up the account
+ // locally. XXX TODO should we do anything here, e.g. tell
+ // the user on the last wizard page that it's ok, they just
+ // have to pair again?
+ return;
+ }
+ let controller = new Weave.SendCredentialsController(this._jpakeclient,
+ Weave.Service);
+ this._jpakeclient.controller = controller;
+ },
+
+ startEasySetup: function () {
+ // Don't do anything if we have a client already (e.g. we went to
+ // Sync Options and just came back).
+ if (this._jpakeclient)
+ return;
+
+ // When onAbort is called, Weave may already be gone
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ this._jpakeclient = new Weave.JPAKEClient({
+ displayPIN: function displayPIN(pin) {
+ document.getElementById("easySetupPIN1").value = pin.slice(0, 4);
+ document.getElementById("easySetupPIN2").value = pin.slice(4, 8);
+ document.getElementById("easySetupPIN3").value = pin.slice(8);
+ },
+
+ onPairingStart: function onPairingStart() {},
+
+ onComplete: function onComplete(credentials) {
+ Weave.Service.identity.account = credentials.account;
+ Weave.Service.identity.basicPassword = credentials.password;
+ Weave.Service.identity.syncKey = credentials.synckey;
+ Weave.Service.serverURL = credentials.serverURL;
+ gSyncSetup.wizardFinish();
+ },
+
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Ignore if wizard is aborted.
+ if (error == JPAKE_ERROR_USERABORT)
+ return;
+
+ // Automatically go to manual setup if we couldn't acquire a channel.
+ if (error == Weave.JPAKE_ERROR_CHANNEL) {
+ self.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ return;
+ }
+
+ // Restart on all other errors.
+ self.startEasySetup();
+ }
+ });
+ this._jpakeclient.receiveNoPIN();
+ },
+
+ abortEasySetup: function () {
+ document.getElementById("easySetupPIN1").value = "";
+ document.getElementById("easySetupPIN2").value = "";
+ document.getElementById("easySetupPIN3").value = "";
+ if (!this._jpakeclient)
+ return;
+
+ this._jpakeclient.abort();
+ delete this._jpakeclient;
+ },
+
+ manualSetup: function () {
+ this.abortEasySetup();
+ this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ },
+
+ // _handleNoScript is needed because it blocks the captcha. So we temporarily
+ // allow the necessary sites so that we can verify the user is in fact a human.
+ // This was done with the help of Giorgio (NoScript author). See bug 508112.
+ _handleNoScript: function (addExceptions) {
+ // if NoScript isn't installed, or is disabled, bail out.
+ let ns = Cc["@maone.net/noscript-service;1"];
+ if (ns == null)
+ return;
+
+ ns = ns.getService().wrappedJSObject;
+ if (addExceptions) {
+ this._remoteSites.forEach(function(site) {
+ site = ns.getSite(site);
+ if (!ns.isJSEnabled(site)) {
+ this._disabledSites.push(site); // save status
+ ns.setJSEnabled(site, true); // allow site
+ }
+ }, this);
+ }
+ else {
+ this._disabledSites.forEach(function(site) {
+ ns.setJSEnabled(site, false);
+ });
+ this._disabledSites = [];
+ }
+ },
+
+ onExistingServerCommand: function () {
+ let control = document.getElementById("existingServer");
+ if (control.selectedIndex == 0) {
+ control.removeAttribute("editable");
+ Weave.Svc.Prefs.reset("serverURL");
+ } else {
+ control.setAttribute("editable", "true");
+ // Force a style flush to ensure that the binding is attached.
+ control.clientTop;
+ control.value = "";
+ control.inputField.focus();
+ }
+ document.getElementById("existingServerFeedbackRow").hidden = true;
+ this.checkFields();
+ },
+
+ onExistingServerInput: function () {
+ // Check custom server validity when the user stops typing for 1 second.
+ if (this._existingServerTimer)
+ window.clearTimeout(this._existingServerTimer);
+ this._existingServerTimer = window.setTimeout(function () {
+ gSyncSetup.checkFields();
+ }, 1000);
+ },
+
+ onServerCommand: function () {
+ setVisibility(document.getElementById("TOSRow"), this._usingMainServers);
+ let control = document.getElementById("server");
+ if (!this._usingMainServers) {
+ control.setAttribute("editable", "true");
+ // Force a style flush to ensure that the binding is attached.
+ control.clientTop;
+ control.value = "";
+ control.inputField.focus();
+ // checkServer() will call checkAccount() and checkFields().
+ this.checkServer();
+ return;
+ }
+ control.removeAttribute("editable");
+ Weave.Svc.Prefs.reset("serverURL");
+ if (this._settingUpNew) {
+ this.loadCaptcha();
+ }
+ this.checkAccount();
+ this.status.server = true;
+ document.getElementById("serverFeedbackRow").hidden = true;
+ this.checkFields();
+ },
+
+ onServerInput: function () {
+ // Check custom server validity when the user stops typing for 1 second.
+ if (this._checkServerTimer)
+ window.clearTimeout(this._checkServerTimer);
+ this._checkServerTimer = window.setTimeout(function () {
+ gSyncSetup.checkServer();
+ }, 1000);
+ },
+
+ checkServer: function () {
+ delete this._checkServerTimer;
+ let el = document.getElementById("server");
+ let valid = false;
+ let feedback = document.getElementById("serverFeedbackRow");
+ let str = "";
+ if (el.value) {
+ valid = this._validateServer(el);
+ let str = valid ? "" : "serverInvalid.label";
+ this._setFeedbackMessage(feedback, valid, str);
+ }
+ else
+ this._setFeedbackMessage(feedback, true);
+
+ // Recheck account against the new server.
+ if (valid)
+ this.checkAccount();
+
+ this.status.server = valid;
+ this.checkFields();
+ },
+
+ _validateServer: function (element) {
+ let valid = false;
+ let val = element.value;
+ if (!val)
+ return false;
+
+ let uri = Weave.Utils.makeURI(val);
+
+ if (!uri)
+ uri = Weave.Utils.makeURI("https://" + val);
+
+ if (uri && this._settingUpNew) {
+ function isValid(uri) {
+ Weave.Service.serverURL = uri.spec;
+ let check = Weave.Service.checkAccount("a");
+ return (check == "available" || check == "notAvailable");
+ }
+
+ if (uri.schemeIs("http")) {
+ uri.scheme = "https";
+ if (isValid(uri))
+ valid = true;
+ else
+ // setting the scheme back to http
+ uri.scheme = "http";
+ }
+ if (!valid)
+ valid = isValid(uri);
+
+ if (valid) {
+ this.loadCaptcha();
+ }
+ }
+ else if (uri) {
+ valid = true;
+ Weave.Service.serverURL = uri.spec;
+ }
+
+ if (valid)
+ element.value = Weave.Service.serverURL;
+ else
+ Weave.Svc.Prefs.reset("serverURL");
+
+ return valid;
+ },
+
+ _handleChoice: function () {
+ let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
+ document.getElementById("chosenActionDeck").selectedIndex = desc;
+ switch (desc) {
+ case 1:
+ if (this._case1Setup)
+ break;
+
+ let places_db = PlacesUtils.history
+ .QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ if (Weave.Service.engineManager.get("history").enabled) {
+ let daysOfHistory = 0;
+ let stm = places_db.createStatement(
+ "SELECT ROUND(( " +
+ "strftime('%s','now','localtime','utc') - " +
+ "( " +
+ "SELECT visit_date FROM moz_historyvisits " +
+ "ORDER BY visit_date ASC LIMIT 1 " +
+ ")/1000000 " +
+ ")/86400) AS daysOfHistory ");
+
+ if (stm.step())
+ daysOfHistory = stm.getInt32(0);
+ // Support %S for historical reasons (see bug 600141)
+ document.getElementById("historyCount").value =
+ PluralForm.get(daysOfHistory,
+ this._stringBundle.GetStringFromName("historyDaysCount.label"))
+ .replace("%S", daysOfHistory)
+ .replace("#1", daysOfHistory);
+ } else {
+ document.getElementById("historyCount").hidden = true;
+ }
+
+ if (Weave.Service.engineManager.get("bookmarks").enabled) {
+ let bookmarks = 0;
+ let stm = places_db.createStatement(
+ "SELECT count(*) AS bookmarks " +
+ "FROM moz_bookmarks b " +
+ "LEFT JOIN moz_bookmarks t ON " +
+ "b.parent = t.id WHERE b.type = 1 AND t.parent <> :tag");
+ stm.params.tag = PlacesUtils.tagsFolderId;
+ if (stm.executeStep())
+ bookmarks = stm.row.bookmarks;
+ // Support %S for historical reasons (see bug 600141)
+ document.getElementById("bookmarkCount").value =
+ PluralForm.get(bookmarks,
+ this._stringBundle.GetStringFromName("bookmarksCount.label"))
+ .replace("%S", bookmarks)
+ .replace("#1", bookmarks);
+ } else {
+ document.getElementById("bookmarkCount").hidden = true;
+ }
+
+ if (Weave.Service.engineManager.get("passwords").enabled) {
+ let logins = Services.logins.getAllLogins({});
+ // Support %S for historical reasons (see bug 600141)
+ document.getElementById("passwordCount").value =
+ PluralForm.get(logins.length,
+ this._stringBundle.GetStringFromName("passwordsCount.label"))
+ .replace("%S", logins.length)
+ .replace("#1", logins.length);
+ } else {
+ document.getElementById("passwordCount").hidden = true;
+ }
+
+ if (!Weave.Service.engineManager.get("prefs").enabled) {
+ document.getElementById("prefsWipe").hidden = true;
+ }
+
+ let addonsEngine = Weave.Service.engineManager.get("addons");
+ if (addonsEngine.enabled) {
+ let ids = addonsEngine._store.getAllIDs();
+ let blessedcount = 0;
+ for each (let i in ids) {
+ if (i) {
+ blessedcount++;
+ }
+ }
+ // bug 600141 does not apply, as this does not have to support existing strings
+ document.getElementById("addonCount").value =
+ PluralForm.get(blessedcount,
+ this._stringBundle.GetStringFromName("addonsCount.label"))
+ .replace("#1", blessedcount);
+ } else {
+ document.getElementById("addonCount").hidden = true;
+ }
+
+ this._case1Setup = true;
+ break;
+ case 2:
+ if (this._case2Setup)
+ break;
+ let count = 0;
+ function appendNode(label) {
+ let box = document.getElementById("clientList");
+ let node = document.createElement("label");
+ node.setAttribute("value", label);
+ node.setAttribute("class", "data indent");
+ box.appendChild(node);
+ }
+
+ for each (let name in Weave.Service.clientsEngine.stats.names) {
+ // Don't list the current client
+ if (name == Weave.Service.clientsEngine.localName)
+ continue;
+
+ // Only show the first several client names
+ if (++count <= 5)
+ appendNode(name);
+ }
+ if (count > 5) {
+ // Support %S for historical reasons (see bug 600141)
+ let label =
+ PluralForm.get(count - 5,
+ this._stringBundle.GetStringFromName("additionalClientCount.label"))
+ .replace("%S", count - 5)
+ .replace("#1", count - 5);
+ appendNode(label);
+ }
+ this._case2Setup = true;
+ break;
+ }
+
+ return true;
+ },
+
+ // sets class and string on a feedback element
+ // if no property string is passed in, we clear label/style
+ _setFeedback: function (element, success, string) {
+ element.hidden = success || !string;
+ let classname = success ? "success" : "error";
+ let image = element.getElementsByAttribute("class", "statusIcon")[0];
+ image.setAttribute("status", classname);
+ let label = element.getElementsByAttribute("class", "status")[0];
+ label.value = string;
+ },
+
+ // shim
+ _setFeedbackMessage: function (element, success, string) {
+ let str = "";
+ if (string) {
+ try {
+ str = this._stringBundle.GetStringFromName(string);
+ } catch(e) {}
+
+ if (!str)
+ str = Weave.Utils.getErrorString(string);
+ }
+ this._setFeedback(element, success, str);
+ },
+
+ loadCaptcha: function loadCaptcha() {
+ let captchaURI = Weave.Service.miscAPI + "captcha_html";
+ // First check for NoScript and whitelist the right sites.
+ this._handleNoScript(true);
+ if (this.captchaBrowser.currentURI.spec != captchaURI) {
+ this.captchaBrowser.loadURI(captchaURI);
+ }
+ },
+
+ onStateChange: function(webProgress, request, stateFlags, status) {
+ // We're only looking for the end of the frame load
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0)
+ return;
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) == 0)
+ return;
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) == 0)
+ return;
+
+ // If we didn't find a captcha, assume it's not needed and don't show it.
+ let responseStatus = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
+ setVisibility(this.captchaBrowser, responseStatus != 404);
+ //XXX TODO we should really log any responseStatus other than 200
+ },
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+ onLocationChange: function () {}
+};
+
+// Define lazy getters for various XUL elements.
+//
+// onWizardAdvance() and onPageShow() are run before init(), so we'll even
+// define things that will almost certainly be used (like 'wizard') as a lazy
+// getter here.
+["wizard",
+ "pin1",
+ "pin2",
+ "pin3",
+ "pairDeviceErrorRow",
+ "pairDeviceThrobber"].forEach(function (id) {
+ XPCOMUtils.defineLazyGetter(gSyncSetup, id, function() {
+ return document.getElementById(id);
+ });
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "nextFocusEl", function () {
+ return {pin1: this.pin2,
+ pin2: this.pin3,
+ pin3: this.wizard.getButton("next")};
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() {
+ return Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+});
diff --git a/application/palemoon/base/content/sync/setup.xul b/application/palemoon/base/content/sync/setup.xul
new file mode 100644
index 000000000..cf2cc77e4
--- /dev/null
+++ b/application/palemoon/base/content/sync/setup.xul
@@ -0,0 +1,491 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard id="wizard"
+ title="&accountSetupTitle.label;"
+ windowtype="Weave:AccountSetup"
+ persist="screenX screenY"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onwizardnext="return gSyncSetup.onWizardAdvance()"
+ onwizardback="return gSyncSetup.onWizardBack()"
+ onwizardcancel="gSyncSetup.onWizardCancel()"
+ onload="gSyncSetup.init()">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/setup.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="addDevicePage"
+ label="&pairDevice.title.label;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <description>
+ &pairDevice.dialog.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+ </description>
+ <separator class="groove-thin"/>
+ <description>
+ &addDevice.dialog.enterCode.label;
+ </description>
+ <separator class="groove-thin"/>
+ <vbox align="center">
+ <textbox id="pin1"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin2"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin3"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ </vbox>
+ <separator class="groove-thin"/>
+ <vbox id="pairDeviceThrobber" align="center" hidden="true">
+ <image/>
+ </vbox>
+ <hbox id="pairDeviceErrorRow" pack="center" hidden="true">
+ <image class="statusIcon" status="error"/>
+ <label class="status"
+ value="&addDevice.dialog.tryAgain.label;"/>
+ </hbox>
+ </wizardpage>
+
+ <wizardpage id="pickSetupType"
+ label="&syncBrand.fullName.label;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <vbox align="center" flex="1">
+ <description style="padding: 0 7em;">
+ &setup.pickSetupType.description2;
+ </description>
+ <spacer flex="3"/>
+ <button id="newAccount"
+ class="accountChoiceButton"
+ label="&button.createNewAccount.label;"
+ oncommand="gSyncSetup.startNewAccountSetup()"
+ align="center"/>
+ <spacer flex="1"/>
+ </vbox>
+ <separator class="groove"/>
+ <vbox align="center" flex="1">
+ <spacer flex="1"/>
+ <button id="existingAccount"
+ class="accountChoiceButton"
+ label="&button.haveAccount.label;"
+ oncommand="gSyncSetup.useExistingAccount()"/>
+ <spacer flex="3"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage label="&setup.newAccountDetailsPage.title.label;"
+ id="newAccountStart"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow();">
+ <grid>
+ <columns>
+ <column/>
+ <column class="inputColumn" flex="1"/>
+ </columns>
+ <rows>
+ <row id="emailRow" align="center">
+ <label value="&setup.emailAddress.label;"
+ accesskey="&setup.emailAddress.accesskey;"
+ control="weaveEmail"/>
+ <textbox id="weaveEmail"
+ oninput="gSyncSetup.onEmailInput()"/>
+ </row>
+ <row id="emailFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row id="passwordRow" align="center">
+ <label value="&setup.choosePassword.label;"
+ accesskey="&setup.choosePassword.accesskey;"
+ control="weavePassword"/>
+ <textbox id="weavePassword"
+ type="password"
+ onchange="gSyncSetup.onPasswordChange()"/>
+ </row>
+ <row id="confirmRow" align="center">
+ <label value="&setup.confirmPassword.label;"
+ accesskey="&setup.confirmPassword.accesskey;"
+ control="weavePasswordConfirm"/>
+ <textbox id="weavePasswordConfirm"
+ type="password"
+ onchange="gSyncSetup.onPasswordChange()"/>
+ </row>
+ <row id="passwordFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="server"
+ value="&server.label;"/>
+ <menulist id="server"
+ oncommand="gSyncSetup.onServerCommand()"
+ oninput="gSyncSetup.onServerInput()">
+ <menupopup>
+ <menuitem label="&serverType.default.label;"
+ value="main"/>
+ <menuitem label="&serverType.custom2.label;"
+ value="custom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="serverFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row id="TOSRow" align="center">
+ <spacer/>
+ <hbox align="center">
+ <checkbox id="tos"
+ accesskey="&setup.tosAgree1.accesskey;"
+ oncommand="this.focus(); gSyncSetup.checkFields();"/>
+ <description id="tosDesc"
+ flex="1"
+ onclick="document.getElementById('tos').focus();
+ document.getElementById('tos').click()">
+ &setup.tosAgree1.label;
+ <label class="text-link inline-link"
+ onclick="event.stopPropagation();gSyncUtils.openToS();">
+ &setup.tosLink.label;
+ </label>
+ &setup.tosAgree2.label;
+ <label class="text-link inline-link"
+ onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();">
+ &setup.ppLink.label;
+ </label>
+ &setup.tosAgree3.label;
+ </description>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ <vbox flex="1" align="center">
+ <browser height="150"
+ width="500"
+ id="captcha"
+ type="content"
+ disablehistory="true"/>
+ <spacer flex="1"/>
+ <hbox id="captchaFeedback">
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="addDevice"
+ label="&pairDevice.title.label;"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow()">
+ <description>
+ &pairDevice.setup.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+ </description>
+ <label value="&addDevice.setup.enterCode.label;"
+ control="easySetupPIN1"/>
+ <spacer flex="1"/>
+ <vbox align="center" flex="1">
+ <textbox id="easySetupPIN1"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ <textbox id="easySetupPIN2"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ <textbox id="easySetupPIN3"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ </vbox>
+ <spacer flex="3"/>
+ <label class="text-link"
+ value="&addDevice.dontHaveDevice.label;"
+ onclick="gSyncSetup.manualSetup();"/>
+ </wizardpage>
+
+ <wizardpage id="existingAccount"
+ label="&setup.signInPage.title.label;"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow()">
+ <grid>
+ <columns>
+ <column/>
+ <column class="inputColumn" flex="1"/>
+ </columns>
+ <rows>
+ <row id="existingAccountRow" align="center">
+ <label id="existingAccountLabel"
+ value="&signIn.account2.label;"
+ accesskey="&signIn.account2.accesskey;"
+ control="existingAccount"/>
+ <textbox id="existingAccountName"
+ oninput="gSyncSetup.checkFields(event)"
+ onchange="gSyncSetup.checkFields(event)"/>
+ </row>
+ <row id="existingPasswordRow" align="center">
+ <label id="existingPasswordLabel"
+ value="&signIn.password.label;"
+ accesskey="&signIn.password.accesskey;"
+ control="existingPassword"/>
+ <textbox id="existingPassword"
+ type="password"
+ onkeyup="gSyncSetup.checkFields(event)"
+ onchange="gSyncSetup.checkFields(event)"/>
+ </row>
+ <row id="existingPasswordFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row align="center">
+ <spacer/>
+ <label class="text-link"
+ value="&resetPassword.label;"
+ onclick="gSyncUtils.resetPassword(); return false;"/>
+ </row>
+ <row align="center">
+ <label control="existingServer"
+ value="&server.label;"/>
+ <menulist id="existingServer"
+ oncommand="gSyncSetup.onExistingServerCommand()"
+ oninput="gSyncSetup.onExistingServerInput()">
+ <menupopup>
+ <menuitem label="&serverType.default.label;"
+ value="main"/>
+ <menuitem label="&serverType.custom2.label;"
+ value="custom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="existingServerFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <vbox>
+ <label class="status" value=" "/>
+ </vbox>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <groupbox>
+ <label id="existingPassphraseLabel"
+ value="&signIn.recoveryKey.label;"
+ accesskey="&signIn.recoveryKey.accesskey;"
+ control="existingPassphrase"/>
+ <textbox id="existingPassphrase"
+ oninput="gSyncSetup.checkFields()"/>
+ <hbox id="login-throbber" hidden="true">
+ <image/>
+ <label value="&verifying.label;"/>
+ </hbox>
+ <vbox align="left" id="existingPassphraseFeedbackRow" hidden="true">
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <vbox id="passphraseHelpBox">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="http://www.palemoon.org/sync/help/recoverykey.shtml">
+ &addDevice.showMeHow.label;
+ </label>
+ <spacer id="passphraseHelpSpacer"/>
+ <label class="text-link"
+ onclick="gSyncSetup.resetPassphrase(); return false;">
+ &resetSyncKey.label;
+ </label>
+ </description>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="syncOptionsPage"
+ label="&setup.optionsPage.title;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <groupbox id="syncOptions">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1" style="-moz-margin-end: 2px"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&syncDeviceName.label;"
+ accesskey="&syncDeviceName.accesskey;"
+ control="syncComputerName"/>
+ <textbox id="syncComputerName" flex="1"
+ onchange="gSyncUtils.changeName(this)"/>
+ </row>
+ <row>
+ <label value="&syncMy.label;" />
+ <vbox>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ id="engine.addons"
+ checked="false"
+ hidden="true"/>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ id="engine.bookmarks"
+ checked="true"/>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ id="engine.passwords"
+ checked="true"/>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ id="engine.prefs"
+ checked="true"/>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ id="engine.history"
+ checked="true"/>
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ id="engine.tabs"
+ checked="true"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox id="mergeOptions">
+ <radiogroup id="mergeChoiceRadio" pack="start">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows flex="1">
+ <row align="center">
+ <radio id="resetClient"
+ class="mergeChoiceButton"
+ aria-labelledby="resetClientLabel"/>
+ <label id="resetClientLabel" control="resetClient">
+ <html:strong>&choice2.merge.recommended.label;</html:strong>
+ &choice2a.merge.main.label;
+ </label>
+ </row>
+ <row align="center">
+ <radio id="wipeClient"
+ class="mergeChoiceButton"
+ aria-labelledby="wipeClientLabel"/>
+ <label id="wipeClientLabel"
+ control="wipeClient">
+ &choice2a.client.main.label;
+ </label>
+ </row>
+ <row align="center">
+ <radio id="wipeRemote"
+ class="mergeChoiceButton"
+ aria-labelledby="wipeRemoteLabel"/>
+ <label id="wipeRemoteLabel"
+ control="wipeRemote">
+ &choice2a.server.main.label;
+ </label>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ </groupbox>
+ </wizardpage>
+
+ <wizardpage id="syncOptionsConfirm"
+ label="&setup.optionsConfirmPage.title;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <deck id="chosenActionDeck">
+ <vbox id="chosenActionMerge" class="confirm">
+ <description class="normal">
+ &confirm.merge2.label;
+ </description>
+ </vbox>
+ <vbox id="chosenActionWipeClient" class="confirm">
+ <description class="normal">
+ &confirm.client3.label;
+ </description>
+ <separator class="thin"/>
+ <vbox id="dataList">
+ <label class="data indent" id="bookmarkCount"/>
+ <label class="data indent" id="historyCount"/>
+ <label class="data indent" id="passwordCount"/>
+ <label class="data indent" id="addonCount"/>
+ <label class="data indent" id="prefsWipe"
+ value="&engine.prefs.label;"/>
+ </vbox>
+ <separator class="thin"/>
+ <description class="normal">
+ &confirm.client2.moreinfo.label;
+ </description>
+ </vbox>
+ <vbox id="chosenActionWipeServer" class="confirm">
+ <description class="normal">
+ &confirm.server2.label;
+ </description>
+ <separator class="thin"/>
+ <vbox id="clientList">
+ </vbox>
+ </vbox>
+ </deck>
+ </wizardpage>
+ <!-- In terms of the wizard flow shown to the user, the 'syncOptionsConfirm'
+ page above is not the last wizard page. To prevent the wizard binding from
+ assuming that it is, we're inserting this dummy page here. This also means
+ that the wizard needs to always be closed manually via wizardFinish(). -->
+ <wizardpage>
+ </wizardpage>
+</wizard>
+
diff --git a/application/palemoon/base/content/sync/utils.js b/application/palemoon/base/content/sync/utils.js
new file mode 100644
index 000000000..0c02b5bc0
--- /dev/null
+++ b/application/palemoon/base/content/sync/utils.js
@@ -0,0 +1,218 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Equivalent to 0o600 permissions; used for saved Sync Recovery Key.
+// This constant can be replaced when the equivalent values are available to
+// chrome JS; see Bug 433295 and Bug 757351.
+const PERMISSIONS_RWUSR = 0x180;
+
+// Weave should always exist before before this file gets included.
+let gSyncUtils = {
+ get bundle() {
+ delete this.bundle;
+ return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+ },
+
+ // opens in a new window if we're in a modal prefwindow world, in a new tab otherwise
+ _openLink: function (url) {
+ let thisDocEl = document.documentElement,
+ openerDocEl = window.opener && window.opener.document.documentElement;
+ if (thisDocEl.id == "accountSetup" && window.opener &&
+ openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
+ openUILinkIn(url, "window");
+ else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
+ openUILinkIn(url, "window");
+ else if (document.documentElement.id == "change-dialog")
+ Services.wm.getMostRecentWindow("navigator:browser")
+ .openUILinkIn(url, "tab");
+ else
+ openUILinkIn(url, "tab");
+ },
+
+ changeName: function changeName(input) {
+ // Make sure to update to a modified name, e.g., empty-string -> default
+ Weave.Service.clientsEngine.localName = input.value;
+ input.value = Weave.Service.clientsEngine.localName;
+ },
+
+ openChange: function openChange(type, duringSetup) {
+ // Just re-show the dialog if it's already open
+ let openedDialog = Services.wm.getMostRecentWindow("Sync:" + type);
+ if (openedDialog != null) {
+ openedDialog.focus();
+ return;
+ }
+
+ // Open up the change dialog
+ let changeXUL = "chrome://browser/content/sync/genericChange.xul";
+ let changeOpt = "centerscreen,chrome,resizable=no";
+ Services.ww.activeWindow.openDialog(changeXUL, "", changeOpt,
+ type, duringSetup);
+ },
+
+ changePassword: function () {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("ChangePassword");
+ },
+
+ resetPassphrase: function (duringSetup) {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("ResetPassphrase", duringSetup);
+ },
+
+ updatePassphrase: function () {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("UpdatePassphrase");
+ },
+
+ resetPassword: function () {
+ this._openLink(Weave.Service.pwResetURL);
+ },
+
+ openToS: function () {
+ this._openLink(Weave.Svc.Prefs.get("termsURL"));
+ },
+
+ openPrivacyPolicy: function () {
+ this._openLink(Weave.Svc.Prefs.get("privacyURL"));
+ },
+
+ openFirstSyncProgressPage: function () {
+ this._openLink("about:sync-progress");
+ },
+
+ /**
+ * Prepare an invisible iframe with the passphrase backup document.
+ * Used by both the print and saving methods.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ * @param callback : Function called once the iframe has loaded.
+ */
+ _preparePPiframe: function(elid, callback) {
+ let pp = document.getElementById(elid).value;
+
+ // Create an invisible iframe whose contents we can print.
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "chrome://browser/content/sync/key.xhtml");
+ iframe.collapsed = true;
+ document.documentElement.appendChild(iframe);
+ iframe.contentWindow.addEventListener("load", function() {
+ iframe.contentWindow.removeEventListener("load", arguments.callee, false);
+
+ // Insert the Sync Key into the page.
+ let el = iframe.contentDocument.getElementById("synckey");
+ el.firstChild.nodeValue = pp;
+
+ // Insert the TOS and Privacy Policy URLs into the page.
+ let termsURL = Weave.Svc.Prefs.get("termsURL");
+ el = iframe.contentDocument.getElementById("tosLink");
+ el.setAttribute("href", termsURL);
+ el.firstChild.nodeValue = termsURL;
+
+ let privacyURL = Weave.Svc.Prefs.get("privacyURL");
+ el = iframe.contentDocument.getElementById("ppLink");
+ el.setAttribute("href", privacyURL);
+ el.firstChild.nodeValue = privacyURL;
+
+ callback(iframe);
+ }, false);
+ },
+
+ /**
+ * Print passphrase backup document.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ */
+ passphrasePrint: function(elid) {
+ this._preparePPiframe(elid, function(iframe) {
+ let webBrowserPrint = iframe.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserPrint);
+ let printSettings = PrintUtils.getPrintSettings();
+
+ // Display no header/footer decoration except for the date.
+ printSettings.headerStrLeft
+ = printSettings.headerStrCenter
+ = printSettings.headerStrRight
+ = printSettings.footerStrLeft
+ = printSettings.footerStrCenter = "";
+ printSettings.footerStrRight = "&D";
+
+ try {
+ webBrowserPrint.print(printSettings, null);
+ } catch (ex) {
+ // print()'s return codes are expressed as exceptions. Ignore.
+ }
+ });
+ },
+
+ /**
+ * Save passphrase backup document to disk as HTML file.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ */
+ passphraseSave: function(elid) {
+ let dialogTitle = this.bundle.GetStringFromName("save.recoverykey.title");
+ let defaultSaveName = this.bundle.GetStringFromName("save.recoverykey.defaultfilename");
+ this._preparePPiframe(elid, function(iframe) {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK ||
+ aResult == Ci.nsIFilePicker.returnReplace) {
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(fp.file, -1, PERMISSIONS_RWUSR, 0);
+
+ let serializer = new XMLSerializer();
+ let output = serializer.serializeToString(iframe.contentDocument);
+ output = output.replace(/<!DOCTYPE (.|\n)*?]>/,
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' +
+ '"DTD/xhtml1-strict.dtd">');
+ output = Weave.Utils.encodeUTF8(output);
+ stream.write(output, output.length);
+ }
+ };
+
+ fp.init(window, dialogTitle, Ci.nsIFilePicker.modeSave);
+ fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+ fp.defaultString = defaultSaveName;
+ fp.open(fpCallback);
+ return false;
+ });
+ },
+
+ /**
+ * validatePassword
+ *
+ * @param el1 : the first textbox element in the form
+ * @param el2 : the second textbox element, if omitted it's an update form
+ *
+ * returns [valid, errorString]
+ */
+ validatePassword: function (el1, el2) {
+ let valid = false;
+ let val1 = el1.value;
+ let val2 = el2 ? el2.value : "";
+ let error = "";
+
+ if (!el2)
+ valid = val1.length >= Weave.MIN_PASS_LENGTH;
+ else if (val1 && val1 == Weave.Service.identity.username)
+ error = "change.password.pwSameAsUsername";
+ else if (val1 && val1 == Weave.Service.identity.account)
+ error = "change.password.pwSameAsEmail";
+ else if (val1 && val1 == Weave.Service.identity.basicPassword)
+ error = "change.password.pwSameAsPassword";
+ else if (val1 && val2) {
+ if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH)
+ valid = true;
+ else if (val1.length < Weave.MIN_PASS_LENGTH)
+ error = "change.password.tooShort";
+ else if (val1 != val2)
+ error = "change.password.mismatch";
+ }
+ let errorString = error ? Weave.Utils.getErrorString(error) : "";
+ return [valid, errorString];
+ }
+};
diff --git a/application/palemoon/base/content/tabbrowser.css b/application/palemoon/base/content/tabbrowser.css
new file mode 100644
index 000000000..94d6dbb2e
--- /dev/null
+++ b/application/palemoon/base/content/tabbrowser.css
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.tabbrowser-tabbox {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabbox");
+ /* Make the content area follow the system colors before load */
+ background: Menu;
+ color: MenuText;
+}
+
+.tabbrowser-arrowscrollbox {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-arrowscrollbox");
+}
+
+.tab-close-button {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-close-tab-button");
+ display: none;
+}
+
+.tabbrowser-tabs[closebuttons="activetab"] > * > * > * > .tab-close-button:not([pinned])[selected="true"],
+.tabbrowser-tabs[closebuttons="alltabs"] > * > * > * > .tab-close-button:not([pinned]) {
+ display: -moz-box;
+}
+
+.tab-label[pinned] {
+ width: 0;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.tab-stack {
+ vertical-align: top; /* for pinned tabs */
+}
+
+tabpanels {
+ background-color: transparent;
+}
+
+.tab-drop-indicator {
+ position: relative;
+ z-index: 2;
+}
+
+.tab-throbber:not([busy]),
+.tab-throbber[busy] + .tab-icon-image {
+ display: none;
+}
+
+.closing-tabs-spacer {
+ pointer-events: none;
+}
+
+.tabbrowser-tabs:not(:hover) > .tabbrowser-arrowscrollbox > .closing-tabs-spacer {
+ transition: width .15s ease-out;
+}
+
+/**
+ * Optimization for tabs that are restored lazily. We can save a good amount of
+ * memory that to-be-restored tabs would otherwise consume simply by setting
+ * their browsers to 'display: none' as that will prevent them from having to
+ * create a presentation and the like.
+ */
+browser[pending] {
+ display: none;
+}
diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml
new file mode 100644
index 000000000..b8d5f3e41
--- /dev/null
+++ b/application/palemoon/base/content/tabbrowser.xml
@@ -0,0 +1,4960 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+<!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
+%tabBrowserDTD;
+]>
+
+<bindings id="tabBrowserBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tabbrowser">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content>
+ <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
+ <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
+ flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
+ onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
+ <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
+ <xul:notificationbox flex="1">
+ <xul:hbox flex="1" class="browserSidebarContainer">
+ <xul:vbox flex="1" class="browserContainer">
+ <xul:stack flex="1" class="browserStack" anonid="browserStack">
+ <xul:browser anonid="initialBrowser" type="content-primary" message="true" disablehistory="true"
+ xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
+ </xul:stack>
+ </xul:vbox>
+ </xul:hbox>
+ </xul:notificationbox>
+ </xul:tabpanels>
+ </xul:tabbox>
+ <children/>
+ </content>
+ <implementation implements="nsIDOMEventListener, nsIMessageListener">
+
+ <property name="tabContextMenu" readonly="true"
+ onget="return this.tabContainer.contextMenu;"/>
+
+ <field name="tabContainer" readonly="true">
+ document.getElementById(this.getAttribute("tabcontainer"));
+ </field>
+ <field name="tabs" readonly="true">
+ this.tabContainer.childNodes;
+ </field>
+
+ <property name="visibleTabs" readonly="true">
+ <getter><![CDATA[
+ if (!this._visibleTabs)
+ this._visibleTabs = Array.filter(this.tabs,
+ function (tab) !tab.hidden && !tab.closing);
+ return this._visibleTabs;
+ ]]></getter>
+ </property>
+
+ <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>
+
+ <field name="_visibleTabs">null</field>
+
+ <field name="mURIFixup" readonly="true">
+ Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(Components.interfaces.nsIURIFixup);
+ </field>
+ <field name="mFaviconService" readonly="true">
+ Components.classes["@mozilla.org/browser/favicon-service;1"]
+ .getService(Components.interfaces.nsIFaviconService);
+ </field>
+ <field name="_placesAutocomplete" readonly="true">
+ Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+ </field>
+ <field name="mTabBox" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
+ </field>
+ <field name="mPanelContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
+ </field>
+ <field name="mStringBundle">
+ document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
+ </field>
+ <field name="mCurrentTab">
+ null
+ </field>
+ <field name="_lastRelatedTab">
+ null
+ </field>
+ <field name="mCurrentBrowser">
+ null
+ </field>
+ <field name="mProgressListeners">
+ []
+ </field>
+ <field name="mTabsProgressListeners">
+ []
+ </field>
+ <field name="mTabListeners">
+ []
+ </field>
+ <field name="mTabFilters">
+ []
+ </field>
+ <field name="mIsBusy">
+ false
+ </field>
+ <field name="_outerWindowIDBrowserMap">
+ new Map();
+ </field>
+ <field name="arrowKeysShouldWrap" readonly="true">
+#ifdef XP_MACOSX
+ true
+#else
+ false
+#endif
+ </field>
+
+ <field name="_autoScrollPopup">
+ null
+ </field>
+
+ <field name="_previewMode">
+ false
+ </field>
+
+ <property name="_numPinnedTabs" readonly="true">
+ <getter><![CDATA[
+ for (var i = 0; i < this.tabs.length; i++) {
+ if (!this.tabs[i].pinned)
+ break;
+ }
+ return i;
+ ]]></getter>
+ </property>
+
+ <property name="formValidationAnchor" readonly="true">
+ <getter><![CDATA[
+ if (this.mCurrentTab._formValidationAnchor) {
+ return this.mCurrentTab._formValidationAnchor;
+ }
+ let stack = this.mCurrentBrowser.parentNode;
+ // Create an anchor for the form validation popup
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let formValidationAnchor = document.createElementNS(NS_XUL, "hbox");
+ formValidationAnchor.className = "form-validation-anchor";
+ formValidationAnchor.hidden = true;
+ stack.appendChild(formValidationAnchor);
+ return this.mCurrentTab._formValidationAnchor = formValidationAnchor;
+ ]]></getter>
+ </property>
+
+ <method name="updateWindowResizers">
+ <body><![CDATA[
+ if (!window.gShowPageResizers)
+ return;
+
+ var show = document.getElementById("addon-bar").collapsed &&
+ window.windowState == window.STATE_NORMAL;
+ for (let i = 0; i < this.browsers.length; i++) {
+ this.browsers[i].showWindowResizer = show;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_setCloseKeyState">
+ <parameter name="aEnabled"/>
+ <body><![CDATA[
+ let keyClose = document.getElementById("key_close");
+ let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
+ if (closeKeyEnabled == aEnabled)
+ return;
+
+ if (aEnabled)
+ keyClose.removeAttribute("disabled");
+ else
+ keyClose.setAttribute("disabled", "true");
+
+ // We also want to remove the keyboard shortcut from the file menu
+ // when the shortcut is disabled, and bring it back when it's
+ // renabled.
+ //
+ // Fixing bug 630826 could make that happen automatically.
+ // Fixing bug 630830 could avoid the ugly hack below.
+
+ let closeMenuItem = document.getElementById("menu_close");
+ let parentPopup = closeMenuItem.parentNode;
+ let nextItem = closeMenuItem.nextSibling;
+ let clonedItem = closeMenuItem.cloneNode(true);
+
+ parentPopup.removeChild(closeMenuItem);
+
+ if (aEnabled)
+ clonedItem.setAttribute("key", "key_close");
+ else
+ clonedItem.removeAttribute("key");
+
+ parentPopup.insertBefore(clonedItem, nextItem);
+ ]]></body>
+ </method>
+
+ <method name="pinTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.pinned)
+ return;
+
+ if (aTab.hidden)
+ this.showTab(aTab);
+
+ this.moveTabTo(aTab, this._numPinnedTabs);
+ aTab.setAttribute("pinned", "true");
+ this.tabContainer._unlockTabSizing();
+ this.tabContainer._positionPinnedTabs();
+ this.tabContainer.adjustTabstrip();
+
+ this.getBrowserForTab(aTab).docShell.isAppTab = true;
+
+ if (aTab.selected)
+ this._setCloseKeyState(false);
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabPinned", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="unpinTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (!aTab.pinned)
+ return;
+
+ this.moveTabTo(aTab, this._numPinnedTabs - 1);
+ aTab.setAttribute("fadein", "true");
+ aTab.removeAttribute("pinned");
+ aTab.style.MozMarginStart = "";
+ this.tabContainer._unlockTabSizing();
+ this.tabContainer._positionPinnedTabs();
+ this.tabContainer.adjustTabstrip();
+
+ this.getBrowserForTab(aTab).docShell.isAppTab = false;
+
+ if (aTab.selected)
+ this._setCloseKeyState(true);
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabUnpinned", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="previewTab">
+ <parameter name="aTab"/>
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ let currentTab = this.selectedTab;
+ try {
+ // Suppress focus, ownership and selected tab changes
+ this._previewMode = true;
+ this.selectedTab = aTab;
+ aCallback();
+ } finally {
+ this.selectedTab = currentTab;
+ this._previewMode = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserAtIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.browsers[aIndex];
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserIndexForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aDocument.defaultView);
+ return tab ? tab._tPos : -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aDocument.defaultView);
+ return tab ? tab.linkedBrowser : null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForOuterWindowID">
+ <parameter name="aID"/>
+ <body>
+ <![CDATA[
+ return this._outerWindowIDBrowserMap.get(aID);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getTabForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ for (let i = 0; i < this.browsers.length; i++) {
+ if (this.browsers[i].contentWindow == aWindow)
+ return this.tabs[i];
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <!-- Binding from browser to tab -->
+ <field name="_tabForBrowser" readonly="true">
+ <![CDATA[
+ new WeakMap();
+ ]]>
+ </field>
+
+ <method name="_getTabForBrowser">
+ <parameter name="aBrowser" />
+ <body>
+ <![CDATA[
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ let text = "_getTabForBrowser` is now deprecated, please use `getTabForBrowser";
+ let url = "https://developer.mozilla.org/docs/Mozilla/Tech/XUL/Method/getTabForBrowser";
+ Deprecated.warning(text, url);
+ return this.getTabForBrowser(aBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this._tabForBrowser.get(aBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getNotificationBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this.getSidebarContainer(aBrowser).parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getSidebarContainer">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this.getBrowserContainer(aBrowser).parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserContainer">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabModalPromptBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let browser = (aBrowser || this.mCurrentBrowser);
+ let stack = browser.parentNode;
+ let self = this;
+
+ let promptBox = {
+ appendPrompt : function(args, onCloseCallback) {
+ let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
+ stack.appendChild(newPrompt);
+ browser.setAttribute("tabmodalPromptShowing", true);
+
+ newPrompt.clientTop; // style flush to assure binding is attached
+
+ let tab = self._getTabForContentWindow(browser.contentWindow);
+ newPrompt.init(args, tab, onCloseCallback);
+ return newPrompt;
+ },
+
+ removePrompt : function(aPrompt) {
+ stack.removeChild(aPrompt);
+
+ let prompts = this.listPrompts();
+ if (prompts.length) {
+ let prompt = prompts[prompts.length - 1];
+ prompt.Dialog.setDefaultFocus();
+ } else {
+ browser.removeAttribute("tabmodalPromptShowing");
+ browser.focus();
+ }
+ },
+
+ listPrompts : function(aPrompt) {
+ let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+ // NodeList --> real JS array
+ let prompts = Array.slice(els);
+ return prompts;
+ },
+ };
+
+ return promptBox;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_callProgressListeners">
+ <parameter name="aBrowser"/>
+ <parameter name="aMethod"/>
+ <parameter name="aArguments"/>
+ <parameter name="aCallGlobalListeners"/>
+ <parameter name="aCallTabsListeners"/>
+ <body><![CDATA[
+ var rv = true;
+
+ if (!aBrowser)
+ aBrowser = this.mCurrentBrowser;
+
+ if (aCallGlobalListeners != false &&
+ aBrowser == this.mCurrentBrowser) {
+ this.mProgressListeners.forEach(function (p) {
+ if (aMethod in p) {
+ try {
+ if (!p[aMethod].apply(p, aArguments))
+ rv = false;
+ } catch (e) {
+ // don't inhibit other listeners
+ Components.utils.reportError(e);
+ }
+ }
+ });
+ }
+
+ if (aCallTabsListeners != false) {
+ aArguments.unshift(aBrowser);
+
+ this.mTabsProgressListeners.forEach(function (p) {
+ if (aMethod in p) {
+ try {
+ if (!p[aMethod].apply(p, aArguments))
+ rv = false;
+ } catch (e) {
+ // don't inhibit other listeners
+ Components.utils.reportError(e);
+ }
+ }
+ });
+ }
+
+ return rv;
+ ]]></body>
+ </method>
+
+ <!-- A web progress listener object definition for a given tab. -->
+ <method name="mTabProgressListener">
+ <parameter name="aTab"/>
+ <parameter name="aBrowser"/>
+ <parameter name="aStartsBlank"/>
+ <body>
+ <![CDATA[
+ return ({
+ mTabBrowser: this,
+ mTab: aTab,
+ mBrowser: aBrowser,
+ mBlank: aStartsBlank,
+
+ // cache flags for correct status UI update after tab switching
+ mStateFlags: 0,
+ mStatus: 0,
+ mMessage: "",
+ mTotalProgress: 0,
+
+ // count of open requests (should always be 0 or 1)
+ mRequestCount: 0,
+
+ destroy: function () {
+ delete this.mTab;
+ delete this.mBrowser;
+ delete this.mTabBrowser;
+ },
+
+ _callProgressListeners: function () {
+ Array.unshift(arguments, this.mBrowser);
+ return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
+ },
+
+ _shouldShowProgress: function (aRequest) {
+ if (this.mBlank)
+ return false;
+
+ if (gMultiProcessBrowser)
+ return true;
+
+ // Don't show progress indicators in tabs for about: URIs
+ // pointing to local resources.
+ try {
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ if (channel.originalURI.schemeIs("about") &&
+ (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
+ return false;
+ } catch (e) {}
+
+ return true;
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
+
+ if (!this._shouldShowProgress(aRequest))
+ return;
+
+ if (this.mTotalProgress)
+ this.mTab.setAttribute("progress", "true");
+
+ this._callProgressListeners("onProgressChange",
+ [aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress]);
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (!aRequest)
+ return;
+
+ var oldBlank = this.mBlank;
+
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ const nsIChannel = Components.interfaces.nsIChannel;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START) {
+ this.mRequestCount++;
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ const NS_ERROR_UNKNOWN_HOST = 2152398878;
+ if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
+ // to prevent bug 235825: wait for the request handled
+ // by the automatic keyword resolver
+ return;
+ }
+ // since we (try to) only handle STATE_STOP of the last request,
+ // the count of open requests should now be 0
+ this.mRequestCount = 0;
+ }
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ // It's okay to clear what the user typed when we start
+ // loading a document. If the user types, this counter gets
+ // set to zero, if the document load ends without an
+ // onLocationChange, this counter gets decremented
+ // (so we keep it while switching tabs after failed loads)
+ // We need to add 2 because loadURIWithFlags may have
+ // cancelled a pending load which would have cleared
+ // its anchor scroll detection temporary increment.
+ if (aWebProgress.isTopLevel)
+ this.mBrowser.userTypedClear += 2;
+
+ if (this._shouldShowProgress(aRequest)) {
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this.mTab.setAttribute("busy", "true");
+ if (!gMultiProcessBrowser) {
+ if (aWebProgress.isTopLevel &&
+ !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
+ this.mTabBrowser.setTabTitleLoading(this.mTab);
+ }
+ }
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = true;
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (this.mTab.hasAttribute("busy")) {
+ this.mTab.removeAttribute("busy");
+ this.mTabBrowser._tabAttrModified(this.mTab);
+ if (!this.mTab.selected)
+ this.mTab.setAttribute("unread", "true");
+ }
+ this.mTab.removeAttribute("progress");
+
+ if (aWebProgress.isTopLevel) {
+ if (!Components.isSuccessCode(aStatus) &&
+ !isTabEmpty(this.mTab)) {
+ // Restore the current document's location in case the
+ // request was stopped (possibly from a content script)
+ // before the location changed.
+
+ this.mBrowser.userTypedValue = null;
+
+ if (this.mTab.selected && gURLBar)
+ URLBarSetURI();
+ } else {
+ // The document is done loading, we no longer want the
+ // value cleared.
+
+ if (this.mBrowser.userTypedClear > 1)
+ this.mBrowser.userTypedClear -= 2;
+ else if (this.mBrowser.userTypedClear > 0)
+ this.mBrowser.userTypedClear--;
+ }
+
+ if (!this.mBrowser.mIconURL)
+ this.mTabBrowser.useDefaultIcon(this.mTab);
+ }
+
+ if (this.mBlank)
+ this.mBlank = false;
+
+ var location = aRequest.QueryInterface(nsIChannel).URI;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword")
+ this.mBrowser.userTypedValue = null;
+
+ if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
+ this.mTabBrowser.setTabTitle(this.mTab);
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = false;
+ }
+
+ if (oldBlank) {
+ this._callProgressListeners("onUpdateCurrentBrowser",
+ [aStateFlags, aStatus, "", 0],
+ true, false);
+ } else {
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ true, false);
+ }
+
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ false);
+
+ if (aStateFlags & (nsIWebProgressListener.STATE_START |
+ nsIWebProgressListener.STATE_STOP)) {
+ // reset cached temporary values at beginning and end
+ this.mMessage = "";
+ this.mTotalProgress = 0;
+ }
+ this.mStateFlags = aStateFlags;
+ this.mStatus = aStatus;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocation,
+ aFlags) {
+ // OnLocationChange is called for both the top-level content
+ // and the subframes.
+ let topLevel = aWebProgress.isTopLevel;
+
+ if (topLevel) {
+ // If userTypedClear > 0, the document loaded correctly and we should be
+ // clearing the user typed value. We also need to clear the typed value
+ // if the document failed to load, to make sure the urlbar reflects the
+ // failed URI (particularly for SSL errors). However, don't clear the value
+ // if the error page's URI is about:blank, because that causes complete
+ // loss of urlbar contents for invalid URI errors (see bug 867957).
+ if (this.mBrowser.userTypedClear > 0 ||
+ ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
+ aLocation.spec != "about:blank"))
+ this.mBrowser.userTypedValue = null;
+
+ // Don't clear the favicon if this onLocationChange was
+ // triggered by a pushState or a replaceState. See bug 550565.
+ if (!gMultiProcessBrowser) {
+ if (aWebProgress.isLoadingDocument &&
+ !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
+ this.mBrowser.mIconURL = null;
+ }
+
+ let autocomplete = this.mTabBrowser._placesAutocomplete;
+ if (this.mBrowser.registeredOpenURI) {
+ autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
+ delete this.mBrowser.registeredOpenURI;
+ }
+ // Tabs in private windows aren't registered as "Open" so
+ // that they don't appear as switch-to-tab candidates.
+ if (!isBlankPageURL(aLocation.spec) &&
+ (!PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing)) {
+ autocomplete.registerOpenPage(aLocation);
+ this.mBrowser.registeredOpenURI = aLocation;
+ }
+ }
+
+ if (!this.mBlank) {
+ this._callProgressListeners("onLocationChange",
+ [aWebProgress, aRequest, aLocation,
+ aFlags]);
+ }
+
+ if (topLevel)
+ this.mBrowser.lastURI = aLocation;
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ if (this.mBlank)
+ return;
+
+ this._callProgressListeners("onStatusChange",
+ [aWebProgress, aRequest, aStatus, aMessage]);
+
+ this.mMessage = aMessage;
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ this._callProgressListeners("onSecurityChange",
+ [aWebProgress, aRequest, aState]);
+ },
+
+ onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
+ return this._callProgressListeners("onRefreshAttempted",
+ [aWebProgress, aURI, aDelay, aSameURI]);
+ },
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="setIcon">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+
+ if (aURI && this.mFaviconService) {
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = makeURI(aURI);
+ this.mFaviconService.setAndFetchFaviconForPage(browser.currentURI,
+ aURI, false,
+ PrivateBrowsingUtils.isWindowPrivate(window) ?
+ this.mFaviconService.FAVICON_LOAD_PRIVATE :
+ this.mFaviconService.FAVICON_LOAD_NON_PRIVATE);
+ }
+
+ let sizedIconUrl = browser.mIconURL || "";
+ if (sizedIconUrl) {
+ let size = Math.round(16 * window.devicePixelRatio);
+ sizedIconUrl += (sizedIconUrl.includes("#") ? "&" : "#") +
+ "-moz-resolution=" + size + "," + size;
+ }
+ if (sizedIconUrl != aTab.getAttribute("image")) {
+ if (browser.mIconURL) //PMed
+ aTab.setAttribute("image", sizedIconUrl);
+ else
+ aTab.removeAttribute("image");
+ this._tabAttrModified(aTab);
+ }
+
+ this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
+ return browser.mIconURL;
+ ]]>
+ </body>
+ </method>
+
+ <method name="shouldLoadFavIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ return (aURI &&
+ Services.prefs.getBoolPref("browser.chrome.site_icons") &&
+ Services.prefs.getBoolPref("browser.chrome.favicons") &&
+ ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
+ ]]>
+ </body>
+ </method>
+
+ <method name="useDefaultIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ // Bug 691610 - e10s support for useDefaultIcon
+ if (gMultiProcessBrowser)
+ return;
+
+ var browser = this.getBrowserForTab(aTab);
+ var docURIObject = browser.contentDocument.documentURIObject;
+ var icon = null;
+ <!-- Pale Moon: new image icon method, see bug #305986 -->
+ let req = browser.contentDocument.imageRequest;
+ let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
+ if (browser.contentDocument instanceof ImageDocument &&
+ req && req.image) {
+ if (Services.prefs.getBoolPref("browser.chrome.site_icons") && sz) {
+ try {
+ <!-- Main method: draw on a hidden canvas -->
+ var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ var tabImg = document.getAnonymousElementByAttribute(aTab, "anonid", "tab-icon");
+ var w = tabImg.boxObject.width;
+ var h = tabImg.boxObject.height;
+ canvas.width = w;
+ canvas.height = h;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(browser.contentDocument.body.firstChild, 0, 0, w, h);
+ icon = canvas.toDataURL();
+ }
+ catch (e) {
+ <!-- Fallback method in case canvas method fails, restricted by sz -->
+ try {
+
+ if (req &&
+ req.image &&
+ req.image.width <= sz &&
+ req.image.height <= sz)
+ icon = browser.currentURI;
+ }
+ catch (e) {
+ <!-- Both methods fail (very large or corrupt image): icon remains null -->
+ }
+ }
+ }
+ }
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ else if (this.shouldLoadFavIcon(docURIObject)) {
+ let url = docURIObject.prePath + "/favicon.ico";
+ if (!this.isFailedIcon(url))
+ icon = url;
+ }
+ this.setIcon(aTab, icon);
+ ]]>
+ </body>
+ </method>
+
+ <method name="isFailedIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ if (this.mFaviconService) {
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = makeURI(aURI);
+ return this.mFaviconService.isFailedFavicon(aURI);
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getWindowTitleForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ var newTitle = "";
+ var docElement = this.ownerDocument.documentElement;
+ var sep = docElement.getAttribute("titlemenuseparator");
+
+ // Strip out any null bytes in the content title, since the
+ // underlying widget implementations of nsWindow::SetTitle pass
+ // null-terminated strings to system APIs.
+ var docTitle = aBrowser.contentTitle.replace("\0", "", "g");
+
+ if (!docTitle)
+ docTitle = docElement.getAttribute("titledefault");
+
+ var modifier = docElement.getAttribute("titlemodifier");
+ if (docTitle) {
+ newTitle += docElement.getAttribute("titlepreface");
+ newTitle += docTitle;
+ if (modifier)
+ newTitle += sep;
+ }
+ newTitle += modifier;
+
+ // If location bar is hidden and the URL type supports a host,
+ // add the scheme and host to the title to prevent spoofing.
+ // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
+ try {
+ if (docElement.getAttribute("chromehidden").includes("location")) {
+ var uri = this.mURIFixup.createExposableURI(
+ aBrowser.currentURI);
+ if (uri.scheme == "about")
+ newTitle = uri.spec + sep + newTitle;
+ else
+ newTitle = uri.prePath + sep + newTitle;
+ }
+ } catch (e) {}
+
+ return newTitle;
+ ]]>
+ </body>
+ </method>
+
+ <method name="freezeTitlebar">
+ <parameter name="aTitle"/>
+ <body>
+ <![CDATA[
+ this._frozenTitle = aTitle || "";
+ this.updateTitlebar();
+ ]]>
+ </body>
+ </method>
+
+ <method name="unfreezeTitlebar">
+ <body>
+ <![CDATA[
+ this._frozenTitle = "";
+ this.updateTitlebar();
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateTitlebar">
+ <body>
+ <![CDATA[
+ this.ownerDocument.title = this._frozenTitle ||
+ this.getWindowTitleForBrowser(this.mCurrentBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateCurrentBrowser">
+ <parameter name="aForceUpdate"/>
+ <body>
+ <![CDATA[
+ var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
+ if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
+ return;
+
+ if (!aForceUpdate) {
+ window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
+ .beginTabSwitch();
+ }
+
+ var oldTab = this.mCurrentTab;
+
+ // Preview mode should not reset the owner
+ if (!this._previewMode && !oldTab.selected)
+ oldTab.owner = null;
+
+ if (this._lastRelatedTab) {
+ if (!this._lastRelatedTab.selected)
+ this._lastRelatedTab.owner = null;
+ this._lastRelatedTab = null;
+ }
+
+ var oldBrowser = this.mCurrentBrowser;
+ if (oldBrowser) {
+ oldBrowser.setAttribute("type", "content-targetable");
+ oldBrowser.docShellIsActive = false;
+ this.finder.mListeners.forEach(l => oldBrowser.finder.removeResultListener(l));
+ }
+
+ var updateBlockedPopups = false;
+ if (!oldBrowser ||
+ (oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
+ (!oldBrowser.blockedPopups && newBrowser.blockedPopups))
+ updateBlockedPopups = true;
+
+ newBrowser.setAttribute("type", "content-primary");
+ newBrowser.docShellIsActive =
+ (window.windowState != window.STATE_MINIMIZED);
+ this.mCurrentBrowser = newBrowser;
+ this.mCurrentTab = this.tabContainer.selectedItem;
+ this.finder.mListeners.forEach(l => this.mCurrentBrowser.finder.addResultListener(l));
+ this.showTab(this.mCurrentTab);
+
+ var backForwardContainer = document.getElementById("unified-back-forward-button");
+ if (backForwardContainer) {
+ backForwardContainer.setAttribute("switchingtabs", "true");
+ window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
+ window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
+ backForwardContainer.removeAttribute("switchingtabs");
+ });
+ }
+
+ if (updateBlockedPopups)
+ this.mCurrentBrowser.updateBlockedPopups();
+
+ // Update the URL bar.
+ var loc = this.mCurrentBrowser.currentURI;
+
+ // Bug 666809 - SecurityUI support for e10s
+ var webProgress = this.mCurrentBrowser.webProgress;
+ var securityUI = this.mCurrentBrowser.securityUI;
+
+ this._callProgressListeners(null, "onLocationChange",
+ [webProgress, null, loc, 0], true,
+ false);
+
+ if (securityUI) {
+ this._callProgressListeners(null, "onSecurityChange",
+ [webProgress, null, securityUI.state], true, false);
+ }
+
+ var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
+ if (listener && listener.mStateFlags) {
+ this._callProgressListeners(null, "onUpdateCurrentBrowser",
+ [listener.mStateFlags, listener.mStatus,
+ listener.mMessage, listener.mTotalProgress],
+ true, false);
+ }
+
+ if (!this._previewMode) {
+ this.mCurrentTab.removeAttribute("unread");
+ this.selectedTab.lastAccessed = Date.now();
+
+ // Bug 666816 - TypeAheadFind support for e10s
+ if (!gMultiProcessBrowser)
+ this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
+
+ this.updateTitlebar();
+
+ this.mCurrentTab.removeAttribute("titlechanged");
+ }
+
+ // If the new tab is busy, and our current state is not busy, then
+ // we need to fire a start to all progress listeners.
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
+ this.mIsBusy = true;
+ this._callProgressListeners(null, "onStateChange",
+ [webProgress, null,
+ nsIWebProgressListener.STATE_START |
+ nsIWebProgressListener.STATE_IS_NETWORK, 0],
+ true, false);
+ }
+
+ // If the new tab is not busy, and our current state is busy, then
+ // we need to fire a stop to all progress listeners.
+ if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
+ this.mIsBusy = false;
+ this._callProgressListeners(null, "onStateChange",
+ [webProgress, null,
+ nsIWebProgressListener.STATE_STOP |
+ nsIWebProgressListener.STATE_IS_NETWORK, 0],
+ true, false);
+ }
+
+ this._setCloseKeyState(!this.mCurrentTab.pinned);
+
+ // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
+ // that might rely upon the other changes suppressed.
+ // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
+ if (!this._previewMode) {
+ // We've selected the new tab, so go ahead and notify listeners.
+ let event = new CustomEvent("TabSelect", {
+ bubbles: true,
+ cancelable: false,
+ detail: {
+ previousTab: oldTab
+ }
+ });
+ this.mCurrentTab.dispatchEvent(event);
+
+ this._tabAttrModified(oldTab);
+ this._tabAttrModified(this.mCurrentTab);
+
+ // Adjust focus
+ oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
+ do {
+ // When focus is in the tab bar, retain it there.
+ if (document.activeElement == oldTab) {
+ // We need to explicitly focus the new tab, because
+ // tabbox.xml does this only in some cases.
+ this.mCurrentTab.focus();
+ break;
+ }
+
+ // If there's a tabmodal prompt showing, focus it.
+ if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
+ let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+ let prompt = prompts[prompts.length - 1];
+ prompt.Dialog.setDefaultFocus();
+ break;
+ }
+
+ // Focus the location bar if it was previously focused for that tab.
+ // In full screen mode, only bother making the location bar visible
+ // if the tab is a blank one.
+ if (newBrowser._urlbarFocused && gURLBar) {
+
+ // Explicitly close the popup if the URL bar retains focus
+ gURLBar.closePopup();
+
+ if (!window.fullScreen) {
+ gURLBar.focus();
+ break;
+ } else if (isTabEmpty(this.mCurrentTab)) {
+ focusAndSelectUrlBar();
+ break;
+ }
+ }
+
+ // If the find bar is focused, keep it focused.
+ if (gFindBarInitialized &&
+ !gFindBar.hidden &&
+ gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
+ break;
+
+ // Otherwise, focus the content area.
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ let focusFlags = fm.FLAG_NOSCROLL;
+
+ if (!gMultiProcessBrowser) {
+ let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
+
+ // for anchors, use FLAG_SHOWRING so that it is clear what link was
+ // last clicked when switching back to that tab
+ if (newFocusedElement &&
+ (newFocusedElement instanceof HTMLAnchorElement ||
+ newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
+ focusFlags |= fm.FLAG_SHOWRING;
+ }
+ fm.setFocus(newBrowser, focusFlags);
+ } while (false);
+ }
+
+ this.tabContainer._setPositionalAttributes();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_tabAttrModified">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.closing)
+ return;
+
+ // This event should be dispatched when any of these attributes change:
+ // label, crop, busy, image, selected
+ var event = document.createEvent("Events");
+ event.initEvent("TabAttrModified", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="setTabTitleLoading">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ aTab.label = this.mStringBundle.getString("tabs.connecting");
+ aTab.crop = "end";
+ this._tabAttrModified(aTab);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitle">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ var crop = "end";
+ var title = browser.contentTitle;
+
+ if (!title) {
+ if (browser.currentURI.spec) {
+ try {
+ title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
+ } catch(ex) {
+ title = browser.currentURI.spec;
+ }
+ }
+
+ if (title && !isBlankPageURL(title)) {
+ // At this point, we now have a URI.
+ // Let's try to unescape it using a character set
+ // in case the URI is not ASCII.
+ try {
+ var characterSet = browser.characterSet;
+ const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
+ .getService(Components.interfaces.nsITextToSubURI);
+ title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
+ } catch(ex) { /* Do nothing. */ }
+
+ crop = "center";
+
+ } else // Still no title? Fall back to our untitled string.
+ title = this.mStringBundle.getString("tabs.emptyTabTitle");
+ }
+
+ if (aTab.label == title &&
+ aTab.crop == crop)
+ return false;
+
+ aTab.label = title;
+ aTab.crop = crop;
+ this._tabAttrModified(aTab);
+
+ if (aTab.selected)
+ this.updateTitlebar();
+
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadOneTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ var aFromExternal;
+ var aRelatedToCurrent;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aReferrerURI = params.referrerURI;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aLoadInBackground = params.inBackground;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ }
+
+ var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
+ Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ var owner = bgLoad ? null : this.selectedTab;
+ var tab = this.addTab(aURI, {
+ referrerURI: aReferrerURI,
+ charset: aCharset,
+ postData: aPostData,
+ ownerTab: owner,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ fromExternal: aFromExternal,
+ relatedToCurrent: aRelatedToCurrent});
+ if (!bgLoad)
+ this.selectedTab = tab;
+
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadTabs">
+ <parameter name="aURIs"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aReplace"/>
+ <body><![CDATA[
+ if (!aURIs.length)
+ return;
+
+ // The tab selected after this new tab is closed (i.e. the new tab's
+ // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
+ // when several urls are opened here (i.e. closing the first should select
+ // the next of many URLs opened) or if the pref to have UI links opened in
+ // the background is set (i.e. the link is not being opened modally)
+ //
+ // i.e.
+ // Number of URLs Load UI Links in BG Focus Last Viewed?
+ // == 1 false YES
+ // == 1 true NO
+ // > 1 false/true NO
+ var multiple = aURIs.length > 1;
+ var owner = multiple || aLoadInBackground ? null : this.selectedTab;
+ var firstTabAdded = null;
+
+ if (aReplace) {
+ try {
+ this.loadURI(aURIs[0], null, null);
+ } catch (e) {
+ // Ignore failure in case a URI is wrong, so we can continue
+ // opening the next ones.
+ }
+ }
+ else
+ firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
+
+ var tabNum = this.tabContainer.selectedIndex;
+ for (let i = 1; i < aURIs.length; ++i) {
+ let tab = this.addTab(aURIs[i], {skipAnimation: true});
+ if (aReplace)
+ this.moveTabTo(tab, ++tabNum);
+ }
+
+ if (!aLoadInBackground) {
+ if (firstTabAdded) {
+ // .selectedTab setter focuses the content area
+ this.selectedTab = firstTabAdded;
+ }
+ else
+ this.selectedBrowser.focus();
+ }
+ ]]></body>
+ </method>
+
+ <method name="addTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aOwner"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var aFromExternal;
+ var aRelatedToCurrent;
+ var aSkipAnimation;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aReferrerURI = params.referrerURI;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aOwner = params.ownerTab;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aSkipAnimation = params.skipAnimation;
+ }
+
+ // if we're adding tabs, we're past interrupt mode, ditch the owner
+ if (this.mCurrentTab.owner)
+ this.mCurrentTab.owner = null;
+
+ var t = document.createElementNS(NS_XUL, "tab");
+
+ var uriIsAboutBlank = !aURI || aURI == "about:blank";
+
+ if (!aURI || isBlankPageURL(aURI))
+ t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
+ else
+ t.setAttribute("label", aURI);
+
+ t.setAttribute("crop", "end");
+ t.setAttribute("validate", "never"); //PMed
+ t.setAttribute("onerror", "this.removeAttribute('image');");
+ t.className = "tabbrowser-tab";
+
+ this.tabContainer._unlockTabSizing();
+
+ // When overflowing, new tabs are scrolled into view smoothly, which
+ // doesn't go well together with the width transition. So we skip the
+ // transition in that case.
+ let animate = !aSkipAnimation &&
+ this.tabContainer.getAttribute("overflow") != "true" &&
+ Services.prefs.getBoolPref("browser.tabs.animate");
+ if (!animate) {
+ t.setAttribute("fadein", "true");
+ setTimeout(function (tabContainer) {
+ tabContainer._handleNewTab(t);
+ }, 0, this.tabContainer);
+ }
+
+ // invalidate caches
+ this._browsers = null;
+ this._visibleTabs = null;
+
+ this.tabContainer.appendChild(t);
+
+ // If this new tab is owned by another, assert that relationship
+ if (aOwner)
+ t.owner = aOwner;
+
+ var b = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "browser");
+ b.setAttribute("type", "content-targetable");
+ b.setAttribute("message", "true");
+ b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
+ b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
+
+ if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL &&
+ Services.prefs.getBoolPref("browser.tabs.remote")) {
+ b.setAttribute("remote", "true");
+ }
+
+ if (window.gShowPageResizers && document.getElementById("addon-bar").collapsed &&
+ window.windowState == window.STATE_NORMAL) {
+ b.setAttribute("showresizer", "true");
+ }
+
+ if (this.hasAttribute("autocompletepopup"))
+ b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
+ b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+
+ // Create the browserStack container
+ var stack = document.createElementNS(NS_XUL, "stack");
+ stack.className = "browserStack";
+ stack.appendChild(b);
+ stack.setAttribute("flex", "1");
+
+ // Create the browserContainer
+ var browserContainer = document.createElementNS(NS_XUL, "vbox");
+ browserContainer.className = "browserContainer";
+ browserContainer.appendChild(stack);
+ browserContainer.setAttribute("flex", "1");
+
+ // Create the sidebar container
+ var browserSidebarContainer = document.createElementNS(NS_XUL,
+ "hbox");
+ browserSidebarContainer.className = "browserSidebarContainer";
+ browserSidebarContainer.appendChild(browserContainer);
+ browserSidebarContainer.setAttribute("flex", "1");
+
+ // Add the Message and the Browser to the box
+ var notificationbox = document.createElementNS(NS_XUL,
+ "notificationbox");
+ notificationbox.setAttribute("flex", "1");
+ notificationbox.appendChild(browserSidebarContainer);
+
+ var position = this.tabs.length - 1;
+ var uniqueId = "panel" + Date.now() + position;
+ notificationbox.id = uniqueId;
+ t.linkedPanel = uniqueId;
+ t.linkedBrowser = b;
+ this._tabForBrowser.set(b, t);
+ t._tPos = position;
+ this.tabContainer._setPositionalAttributes();
+
+ // Prevent the superfluous initial load of a blank document
+ // if we're going to load something other than about:blank.
+ if (!uriIsAboutBlank) {
+ b.setAttribute("nodefaultsrc", "true");
+ }
+
+ // NB: this appendChild call causes us to run constructors for the
+ // browser element, which fires off a bunch of notifications. Some
+ // of those notifications can cause code to run that inspects our
+ // state, so it is important that the tab element is fully
+ // initialized by this point.
+ this.mPanelContainer.appendChild(notificationbox);
+
+ this.tabContainer.updateVisibility();
+
+ // wire up a progress listener for the new browser object.
+ var tabListener = this.mTabProgressListener(t, b, uriIsAboutBlank);
+ const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Components.interfaces.nsIWebProgress);
+ filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[position] = tabListener;
+ this.mTabFilters[position] = filter;
+
+ b._fastFind = this.fastFind;
+ b.droppedLinkHandler = handleDroppedLink;
+
+ // If we just created a new tab that loads the default
+ // newtab url, swap in a preloaded page if possible.
+ // Do nothing if we're a private window.
+ let docShellsSwapped = false;
+ if (aURI == BROWSER_NEW_TAB_URL &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
+ }
+
+ // Dispatch a new tab notification. We do this once we're
+ // entirely done, so that things are in a consistent state
+ // even if the event listener opens or closes tabs.
+ var evt = document.createEvent("Events");
+ evt.initEvent("TabOpen", true, false);
+ t.dispatchEvent(evt);
+
+ // If we didn't swap docShells with a preloaded browser
+ // then let's just continue loading the page normally.
+ if (!docShellsSwapped && !uriIsAboutBlank) {
+ // pretend the user typed this so it'll be available till
+ // the document successfully loads
+ if (aURI && gInitialPages.indexOf(aURI) == -1)
+ b.userTypedValue = aURI;
+
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ if (aFromExternal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
+ try {
+ b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ // We start our browsers out as inactive, and then maintain
+ // activeness in the tab switcher.
+ b.docShellIsActive = false;
+
+ // When addTab() is called with an URL that is not "about:blank" we
+ // set the "nodefaultsrc" attribute that prevents a frameLoader
+ // from being created as soon as the linked <browser> is inserted
+ // into the DOM. We thus have to register the new outerWindowID
+ // for non-remote browsers after we have called browser.loadURI().
+ //
+ // Note: Only do this of we still have a docShell. The TabOpen
+ // event was dispatched above and a gBrowser.removeTab() call from
+ // one of its listeners could cause us to fail here.
+ if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL &&
+ !Services.prefs.getBoolPref("browser.tabs.remote")
+ && b.docShell) {
+ this._outerWindowIDBrowserMap.set(b.outerWindowID, b);
+ }
+
+ // Check if we're opening a tab related to the current tab and
+ // move it to after the current tab.
+ // aReferrerURI is null or undefined if the tab is opened from
+ // an external application or bookmark, i.e. somewhere other
+ // than the current tab.
+ if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
+ Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
+ let newTabPos = (this._lastRelatedTab ||
+ this.selectedTab)._tPos + 1;
+ if (this._lastRelatedTab)
+ this._lastRelatedTab.owner = null;
+ else
+ t.owner = this.selectedTab;
+ this.moveTabTo(t, newTabPos);
+ this._lastRelatedTab = t;
+ }
+
+ if (animate) {
+ mozRequestAnimationFrame(function () {
+ this.tabContainer._handleTabTelemetryStart(t, aURI);
+
+ // kick the animation off
+ t.setAttribute("fadein", "true");
+
+ // This call to adjustTabstrip is redundant but needed so that
+ // when opening a second tab, the first tab's close buttons
+ // appears immediately rather than when the transition ends.
+ if (this.tabs.length - this._removingTabs.length == 2)
+ this.tabContainer.adjustTabstrip();
+ }.bind(this));
+ }
+
+ return t;
+ ]]>
+ </body>
+ </method>
+
+ <method name="warnAboutClosingTabs">
+ <parameter name="aCloseTabs"/>
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var tabsToClose;
+ switch (aCloseTabs) {
+ case this.closingTabsEnum.ALL:
+ tabsToClose = this.tabs.length - this._removingTabs.length -
+ gBrowser._numPinnedTabs;
+ break;
+ case this.closingTabsEnum.OTHER:
+ tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
+ break;
+ case this.closingTabsEnum.TO_END:
+ if (!aTab)
+ throw new Error("Required argument missing: aTab");
+
+ tabsToClose = this.getTabsToTheEndFrom(aTab).length;
+ break;
+ default:
+ throw new Error("Invalid argument: " + aCloseTabs);
+ }
+
+ if (tabsToClose <= 1)
+ return true;
+
+ const pref = aCloseTabs == this.closingTabsEnum.ALL ?
+ "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
+ var shouldPrompt = Services.prefs.getBoolPref(pref);
+ if (!shouldPrompt)
+ return true;
+
+ var ps = Services.prompt;
+
+ // default to true: if it were false, we wouldn't get this far
+ var warnOnClose = { value: true };
+ var bundle = this.mStringBundle;
+
+ // focus the window before prompting.
+ // this will raise any minimized window, which will
+ // make it obvious which window the prompt is for and will
+ // solve the problem of windows "obscuring" the prompt.
+ // see bug #350299 for more details
+ window.focus();
+ var buttonPressed =
+ ps.confirmEx(window,
+ bundle.getString("tabs.closeWarningTitle"),
+ bundle.getFormattedString("tabs.closeWarningMultipleTabs",
+ [tabsToClose]),
+ (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
+ + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
+ bundle.getString("tabs.closeButtonMultiple"),
+ null, null,
+ aCloseTabs == this.closingTabsEnum.ALL ?
+ bundle.getString("tabs.closeWarningPromptMe") : null,
+ warnOnClose);
+ var reallyClose = (buttonPressed == 0);
+
+ // don't set the prefs unless they press OK and have unchecked the box
+ if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value) {
+ Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
+ Services.prefs.setBoolPref("browser.tabs.warnOnCloseOtherTabs", false);
+ }
+ return reallyClose;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var tabsToEnd = [];
+ let tabs = this.visibleTabs;
+ for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
+ tabsToEnd.push(tabs[i]);
+ }
+ return tabsToEnd.reverse();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
+ let tabs = this.getTabsToTheEndFrom(aTab);
+ for (let i = tabs.length - 1; i >= 0; --i) {
+ this.removeTab(tabs[i], {animate: true});
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeAllTabsBut">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.pinned)
+ return;
+
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
+ let tabs = this.visibleTabs;
+ this.selectedTab = aTab;
+
+ for (let i = tabs.length - 1; i >= 0; --i) {
+ if (tabs[i] != aTab && !tabs[i].pinned)
+ this.removeTab(tabs[i]);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentTab">
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ this.removeTab(this.mCurrentTab, aParams);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_removingTabs">
+ []
+ </field>
+
+ <method name="removeTab">
+ <parameter name="aTab"/>
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ if (aParams) {
+ var animate = aParams.animate;
+ var byMouse = aParams.byMouse;
+ }
+
+ // Handle requests for synchronously removing an already
+ // asynchronously closing tab.
+ if (!animate &&
+ aTab.closing) {
+ this._endRemoveTab(aTab);
+ return;
+ }
+
+ var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
+
+ if (!this._beginRemoveTab(aTab, false, null, true))
+ return;
+
+ if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
+ this.tabContainer._lockTabSizing(aTab);
+ else
+ this.tabContainer._unlockTabSizing();
+
+ if (!animate /* the caller didn't opt in */ ||
+ isLastTab ||
+ aTab.pinned ||
+ aTab.hidden ||
+ this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
+ aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
+ window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
+ !Services.prefs.getBoolPref("browser.tabs.animate")) {
+ this._endRemoveTab(aTab);
+ return;
+ }
+
+ this.tabContainer._handleTabTelemetryStart(aTab);
+
+ this._blurTab(aTab);
+ aTab.style.maxWidth = ""; // ensure that fade-out transition happens
+ aTab.removeAttribute("fadein");
+
+ if (this.tabs.length - this._removingTabs.length == 1) {
+ // The second tab just got closed and we will end up with a single
+ // one. Remove the first tab's close button immediately (if needed)
+ // rather than after the tabclose animation ends.
+ this.tabContainer.adjustTabstrip();
+ }
+
+ setTimeout(function (tab, tabbrowser) {
+ if (tab.parentNode &&
+ window.getComputedStyle(tab).maxWidth == "0.1px") {
+ NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
+ tabbrowser._endRemoveTab(tab);
+ }
+ }, 3000, aTab, this);
+ ]]>
+ </body>
+ </method>
+
+ <!-- Tab close requests are ignored if the window is closing anyway,
+ e.g. when holding Ctrl+W. -->
+ <field name="_windowIsClosing">
+ false
+ </field>
+
+ <method name="_beginRemoveTab">
+ <parameter name="aTab"/>
+ <parameter name="aTabWillBeMoved"/>
+ <parameter name="aCloseWindowWithLastTab"/>
+ <parameter name="aCloseWindowFastpath"/>
+ <body>
+ <![CDATA[
+ if (aTab.closing ||
+ aTab._pendingPermitUnload ||
+ this._windowIsClosing)
+ return false;
+
+ var browser = this.getBrowserForTab(aTab);
+
+ if (!aTabWillBeMoved) {
+ let ds = browser.docShell;
+ if (ds && ds.contentViewer) {
+ // We need to block while calling permitUnload() because it
+ // processes the event queue and may lead to another removeTab()
+ // call before permitUnload() even returned.
+ aTab._pendingPermitUnload = true;
+ let permitUnload = ds.contentViewer.permitUnload();
+ delete aTab._pendingPermitUnload;
+
+ if (!permitUnload)
+ return false;
+ }
+ }
+
+ var closeWindow = false;
+ var newTab = false;
+ if (this.tabs.length - this._removingTabs.length == 1) {
+ closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
+ !window.toolbar.visible ||
+ this.tabContainer._closeWindowWithLastTab;
+
+ // Closing the tab and replacing it with a blank one is notably slower
+ // than closing the window right away. If the caller opts in, take
+ // the fast path.
+ if (closeWindow &&
+ aCloseWindowFastpath &&
+ this._removingTabs.length == 0 &&
+ (this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow)))
+ return null;
+
+ newTab = true;
+ }
+
+ aTab.closing = true;
+ this._removingTabs.push(aTab);
+ this._visibleTabs = null; // invalidate cache
+ if (newTab)
+ this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
+ else
+ this.tabContainer.updateVisibility();
+
+ // We're committed to closing the tab now.
+ // Dispatch a notification.
+ // We dispatch it before any teardown so that event listeners can
+ // inspect the tab that's about to close.
+ var evt = document.createEvent("UIEvent");
+ evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
+ aTab.dispatchEvent(evt);
+
+ if (!gMultiProcessBrowser) {
+ // Prevent this tab from showing further dialogs, since we're closing it
+ var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+ windowUtils.disableDialogs();
+ }
+
+ // Remove the tab's filter and progress listener.
+ const filter = this.mTabFilters[aTab._tPos];
+
+ browser.webProgress.removeProgressListener(filter);
+
+ filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
+ this.mTabListeners[aTab._tPos].destroy();
+
+ if (browser.registeredOpenURI && !aTabWillBeMoved) {
+ this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
+ delete browser.registeredOpenURI;
+ }
+
+ // We are no longer the primary content area.
+ browser.setAttribute("type", "content-targetable");
+
+ // Remove this tab as the owner of any other tabs, since it's going away.
+ Array.forEach(this.tabs, function (tab) {
+ if ("owner" in tab && tab.owner == aTab)
+ // |tab| is a child of the tab we're removing, make it an orphan
+ tab.owner = null;
+ });
+
+ aTab._endRemoveArgs = [closeWindow, newTab];
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_endRemoveTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab || !aTab._endRemoveArgs)
+ return;
+
+ var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
+ aTab._endRemoveArgs = null;
+
+ if (this._windowIsClosing) {
+ aCloseWindow = false;
+ aNewTab = false;
+ }
+
+ this._lastRelatedTab = null;
+
+ // update the UI early for responsiveness
+ aTab.collapsed = true;
+ this.tabContainer._fillTrailingGap();
+ this._blurTab(aTab);
+
+ this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
+
+ if (aCloseWindow) {
+ this._windowIsClosing = true;
+ while (this._removingTabs.length)
+ this._endRemoveTab(this._removingTabs[0]);
+ } else if (!this._windowIsClosing) {
+ if (aNewTab)
+ focusAndSelectUrlBar();
+
+ // workaround for bug 345399
+ this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
+ }
+
+ // We're going to remove the tab and the browser now.
+ // Clean up mTabFilters and mTabListeners now rather than in
+ // _beginRemoveTab, so that their size is always in sync with the
+ // number of tabs and browsers (the xbl destructor depends on this).
+ this.mTabFilters.splice(aTab._tPos, 1);
+ this.mTabListeners.splice(aTab._tPos, 1);
+
+ var browser = this.getBrowserForTab(aTab);
+ this._outerWindowIDBrowserMap.delete(browser.outerWindowID);
+
+ // Because of the way XBL works (fields just set JS
+ // properties on the element) and the code we have in place
+ // to preserve the JS objects for any elements that have
+ // JS properties set on them, the browser element won't be
+ // destroyed until the document goes away. So we force a
+ // cleanup ourselves.
+ // This has to happen before we remove the child so that the
+ // XBL implementation of nsIObserver still works.
+ browser.destroy();
+
+ if (browser == this.mCurrentBrowser)
+ this.mCurrentBrowser = null;
+
+ var wasPinned = aTab.pinned;
+
+ // Invalidate browsers cache, as the tab is removed from the
+ // tab container.
+ this._browsers = null;
+
+ // Remove the tab ...
+ this.tabContainer.removeChild(aTab);
+
+ // ... and fix up the _tPos properties immediately.
+ for (let i = aTab._tPos; i < this.tabs.length; i++)
+ this.tabs[i]._tPos = i;
+
+ if (!this._windowIsClosing) {
+ if (wasPinned)
+ this.tabContainer._positionPinnedTabs();
+
+ // update tab close buttons state
+ this.tabContainer.adjustTabstrip();
+
+ setTimeout(function(tabs) {
+ tabs._lastTabClosedByMouse = false;
+ }, 0, this.tabContainer);
+ }
+
+ // Pale Moon: if resizing immediately, select the tab immediately to the left
+ // instead of the right (if not leftmost) to prevent focus swap and
+ // "selected tab not under cursor"
+ // FIXME: Tabs must be sliding in from the left for this, or it'd unfocus
+ // in the other direction! Disabled for now. Is there an easier way? :hover?
+ // Is this even needed when resizing immediately?...
+
+ //if (Services.prefs.getBoolPref("browser.tabs.resize_immediately")) {
+ // if (this.selectedTab._tPos > 1) {
+ // let newPos = this.selectedTab._tPos - 1;
+ // this.selectedTab = this.tabs[newPos];
+ // }
+ //}
+
+ // update tab positional properties and attributes
+ this.selectedTab._selected = true;
+ this.tabContainer._setPositionalAttributes();
+
+ // Removing the panel requires fixing up selectedPanel immediately
+ // (see below), which would be hindered by the potentially expensive
+ // browser removal. So we remove the browser and the panel in two
+ // steps.
+
+ var panel = this.getNotificationBox(browser);
+
+ // This will unload the document. An unload handler could remove
+ // dependant tabs, so it's important that the tabbrowser is now in
+ // a consistent state (tab removed, tab positions updated, etc.).
+ browser.parentNode.removeChild(browser);
+
+ // Release the browser in case something is erroneously holding a
+ // reference to the tab after its removal.
+ this._tabForBrowser.delete(aTab.linkedBrowser);
+ aTab.linkedBrowser = null;
+
+ // As the browser is removed, the removal of a dependent document can
+ // cause the whole window to close. So at this point, it's possible
+ // that the binding is destructed.
+ if (this.mTabBox) {
+ let selectedPanel = this.mTabBox.selectedPanel;
+
+ this.mPanelContainer.removeChild(panel);
+
+ // Under the hood, a selectedIndex attribute controls which panel
+ // is displayed. Removing a panel A which precedes the selected
+ // panel B makes selectedIndex point to the panel next to B. We
+ // need to explicitly preserve B as the selected panel.
+ this.mTabBox.selectedPanel = selectedPanel;
+ }
+
+ if (aCloseWindow)
+ this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_blurTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab.selected)
+ return;
+
+ if (aTab.owner &&
+ !aTab.owner.hidden &&
+ !aTab.owner.closing &&
+ Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
+ this.selectedTab = aTab.owner;
+ return;
+ }
+
+ // Switch to a visible tab unless there aren't any others remaining
+ let remainingTabs = this.visibleTabs;
+ let numTabs = remainingTabs.length;
+ if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
+ remainingTabs = Array.filter(this.tabs, function(tab) {
+ return !tab.closing;
+ }, this);
+ }
+
+ // Try to find a remaining tab that comes after the given tab
+ var tab = aTab;
+ do {
+ tab = tab.nextSibling;
+ } while (tab && remainingTabs.indexOf(tab) == -1);
+
+ if (!tab) {
+ tab = aTab;
+
+ do {
+ tab = tab.previousSibling;
+ } while (tab && remainingTabs.indexOf(tab) == -1);
+ }
+
+ this.selectedTab = tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="swapNewTabWithBrowser">
+ <parameter name="aNewTab"/>
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ // The browser must be standalone.
+ if (aBrowser.getTabBrowser())
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // The tab is definitely not loading.
+ aNewTab.removeAttribute("busy");
+ if (aNewTab.selected) {
+ this.mIsBusy = false;
+ }
+
+ this._swapBrowserDocShells(aNewTab, aBrowser);
+
+ // Update the new tab's title.
+ this.setTabTitle(aNewTab);
+
+ if (aNewTab.selected) {
+ this.updateCurrentBrowser(true);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="swapBrowsersAndCloseOther">
+ <parameter name="aOurTab"/>
+ <parameter name="aOtherTab"/>
+ <body>
+ <![CDATA[
+ // Do not allow transfering a private tab to a non-private window
+ // and vice versa.
+ if (PrivateBrowsingUtils.isWindowPrivate(window) !=
+ PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
+ return;
+
+ // That's gBrowser for the other window, not the tab's browser!
+ var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
+ var isPending = aOtherTab.hasAttribute("pending");
+
+ // First, start teardown of the other browser. Make sure to not
+ // fire the beforeunload event in the process. Close the other
+ // window if this was its last tab.
+ if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
+ return;
+
+ let ourBrowser = this.getBrowserForTab(aOurTab);
+ let otherBrowser = aOtherTab.linkedBrowser;
+
+ // If the other tab is pending (i.e. has not been restored, yet)
+ // then do not switch docShells but retrieve the other tab's state
+ // and apply it to our tab.
+ if (isPending) {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"]
+ .getService(Ci.nsISessionStore)
+ ss.setTabState(aOurTab, ss.getTabState(aOtherTab));
+
+ // Make sure to unregister any open URIs.
+ this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
+ } else {
+ // Workarounds for bug 458697
+ // Icon might have been set on DOMLinkAdded, don't override that.
+ if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
+ this.setIcon(aOurTab, otherBrowser.mIconURL);
+ var isBusy = aOtherTab.hasAttribute("busy");
+ if (isBusy) {
+ aOurTab.setAttribute("busy", "true");
+ this._tabAttrModified(aOurTab);
+ if (aOurTab.selected)
+ this.mIsBusy = true;
+ }
+
+ this._swapBrowserDocShells(aOurTab, otherBrowser);
+ }
+
+ // Finish tearing down the tab that's going away.
+ remoteBrowser._endRemoveTab(aOtherTab);
+
+ if (isBusy)
+ this.setTabTitleLoading(aOurTab);
+ else
+ this.setTabTitle(aOurTab);
+
+ // If the tab was already selected (this happpens in the scenario
+ // of replaceTabWithWindow), notify onLocationChange, etc.
+ if (aOurTab.selected)
+ this.updateCurrentBrowser(true);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_swapBrowserDocShells">
+ <parameter name="aOurTab"/>
+ <parameter name="aOtherBrowser"/>
+ <body>
+ <![CDATA[
+ // Unhook our progress listener
+ let index = aOurTab._tPos;
+ const filter = this.mTabFilters[index];
+ let tabListener = this.mTabListeners[index];
+ let ourBrowser = this.getBrowserForTab(aOurTab);
+ ourBrowser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(tabListener);
+
+ // Make sure to unregister any open URIs.
+ this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
+
+ // Unmap old outerWindowIDs.
+ this._outerWindowIDBrowserMap.delete(ourBrowser.outerWindowID);
+ let remoteBrowser = aOtherBrowser.ownerDocument.defaultView.gBrowser;
+ if (remoteBrowser) {
+ remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
+ }
+ // Swap the docshells
+ ourBrowser.swapDocShells(aOtherBrowser);
+
+ // Register new outerWindowIDs.
+ this._outerWindowIDBrowserMap.set(ourBrowser.outerWindowID, ourBrowser);
+ if (remoteBrowser) {
+ remoteBrowser._outerWindowIDBrowserMap.set(aOtherBrowser.outerWindowID, aOtherBrowser);
+ }
+ // Restore the progress listener
+ this.mTabListeners[index] = tabListener =
+ this.mTabProgressListener(aOurTab, ourBrowser, false);
+
+ const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
+ filter.addProgressListener(tabListener, notifyAll);
+ ourBrowser.webProgress.addProgressListener(filter, notifyAll);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_swapRegisteredOpenURIs">
+ <parameter name="aOurBrowser"/>
+ <parameter name="aOtherBrowser"/>
+ <body>
+ <![CDATA[
+ // If the current URI is registered as open remove it from the list.
+ if (aOurBrowser.registeredOpenURI) {
+ this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
+ delete aOurBrowser.registeredOpenURI;
+ }
+
+ // If the other/new URI is registered as open then copy it over.
+ if (aOtherBrowser.registeredOpenURI) {
+ aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
+ delete aOtherBrowser.registeredOpenURI;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadAllTabs">
+ <body>
+ <![CDATA[
+ let tabs = this.visibleTabs;
+ let l = tabs.length;
+ for (var i = 0; i < l; i++) {
+ try {
+ this.getBrowserForTab(tabs[i]).reload();
+ } catch (e) {
+ // ignore failure to reload so others will be reloaded
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ this.getBrowserForTab(aTab).reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="addProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ if (arguments.length != 1) {
+ Components.utils.reportError("gBrowser.addProgressListener was " +
+ "called with a second argument, " +
+ "which is not supported. See bug " +
+ "608628.");
+ }
+
+ this.mProgressListeners.push(aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ this.mProgressListeners =
+ this.mProgressListeners.filter(function (l) l != aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ this.mTabsProgressListeners.push(aListener);
+ </body>
+ </method>
+
+ <method name="removeTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ this.mTabsProgressListeners =
+ this.mTabsProgressListeners.filter(function (l) l != aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return aTab.linkedBrowser;
+ ]]>
+ </body>
+ </method>
+
+ <method name="showOnlyTheseTabs">
+ <parameter name="aTabs"/>
+ <body>
+ <![CDATA[
+ Array.forEach(this.tabs, function(tab) {
+ if (aTabs.indexOf(tab) == -1)
+ this.hideTab(tab);
+ else
+ this.showTab(tab);
+ }, this);
+
+ this.tabContainer._handleTabSelect(false);
+ ]]>
+ </body>
+ </method>
+
+ <method name="showTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.hidden) {
+ aTab.removeAttribute("hidden");
+ this._visibleTabs = null; // invalidate cache
+
+ this.tabContainer.adjustTabstrip();
+
+ this.tabContainer._setPositionalAttributes();
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabShow", true, false);
+ aTab.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="hideTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
+ !aTab.closing) {
+ aTab.setAttribute("hidden", "true");
+ this._visibleTabs = null; // invalidate cache
+
+ this.tabContainer.adjustTabstrip();
+
+ this.tabContainer._setPositionalAttributes();
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabHide", true, false);
+ aTab.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectTabAtIndex">
+ <parameter name="aIndex"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ let tabs = this.visibleTabs;
+
+ // count backwards for aIndex < 0
+ if (aIndex < 0)
+ aIndex += tabs.length;
+
+ if (aIndex >= 0 && aIndex < tabs.length)
+ this.selectedTab = tabs[aIndex];
+
+ if (aEvent) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="selectedTab">
+ <getter>
+ return this.mCurrentTab;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (gNavToolbox.collapsed) {
+ return this.mTabBox.selectedTab;
+ }
+ // Update the tab
+ this.mTabBox.selectedTab = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedBrowser"
+ onget="return this.mCurrentBrowser;"
+ readonly="true"/>
+
+ <property name="browsers" readonly="true">
+ <getter>
+ <![CDATA[
+ return this._browsers ||
+ (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
+ ]]>
+ </getter>
+ </property>
+ <field name="_browsers">null</field>
+
+ <!-- Moves a tab to a new browser window, unless it's already the only tab
+ in the current window, in which case this will do nothing. -->
+ <method name="replaceTabWithWindow">
+ <parameter name="aTab"/>
+ <parameter name="aOptions"/>
+ <body>
+ <![CDATA[
+ if (this.tabs.length == 1)
+ return null;
+
+ var options = "chrome,dialog=no,all";
+ for (var name in aOptions)
+ options += "," + name + "=" + aOptions[name];
+
+ // tell a new window to take the "dropped" tab
+ return window.openDialog(getBrowserURL(), "_blank", options, aTab);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabTo">
+ <parameter name="aTab"/>
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ var oldPosition = aTab._tPos;
+ if (oldPosition == aIndex)
+ return;
+
+ // Don't allow mixing pinned and unpinned tabs.
+ if (aTab.pinned)
+ aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
+ else
+ aIndex = Math.max(aIndex, this._numPinnedTabs);
+ if (oldPosition == aIndex)
+ return;
+
+ this._lastRelatedTab = null;
+
+ this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
+ this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
+
+ let wasFocused = (document.activeElement == this.mCurrentTab);
+
+ aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
+ this.mCurrentTab._selected = false;
+
+ // invalidate caches
+ this._browsers = null;
+ this._visibleTabs = null;
+
+ // use .item() instead of [] because dragging to the end of the strip goes out of
+ // bounds: .item() returns null (so it acts like appendChild), but [] throws
+ this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
+
+ for (let i = 0; i < this.tabs.length; i++) {
+ this.tabs[i]._tPos = i;
+ this.tabs[i]._selected = false;
+ }
+ this.mCurrentTab._selected = true;
+
+ if (wasFocused)
+ this.mCurrentTab.focus();
+
+ this.tabContainer._handleTabSelect(false);
+
+ if (aTab.pinned)
+ this.tabContainer._positionPinnedTabs();
+
+ this.tabContainer._setPositionalAttributes();
+
+ var evt = document.createEvent("UIEvents");
+ evt.initUIEvent("TabMove", true, false, window, oldPosition);
+ aTab.dispatchEvent(evt);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabForward">
+ <body>
+ <![CDATA[
+ let nextTab = this.mCurrentTab.nextSibling;
+ while (nextTab && nextTab.hidden)
+ nextTab = nextTab.nextSibling;
+
+ if (nextTab)
+ this.moveTabTo(this.mCurrentTab, nextTab._tPos);
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToStart();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabBackward">
+ <body>
+ <![CDATA[
+ let previousTab = this.mCurrentTab.previousSibling;
+ while (previousTab && previousTab.hidden)
+ previousTab = previousTab.previousSibling;
+
+ if (previousTab)
+ this.moveTabTo(this.mCurrentTab, previousTab._tPos);
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToEnd();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToStart">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos > 0)
+ this.moveTabTo(this.mCurrentTab, 0);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToEnd">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos < this.browsers.length - 1)
+ this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabOver">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var direction = window.getComputedStyle(this.parentNode, null).direction;
+ if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
+ (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
+ this.moveTabForward();
+ else
+ this.moveTabBackward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="duplicateTab">
+ <parameter name="aTab"/><!-- can be from a different window as well -->
+ <body>
+ <![CDATA[
+ return Cc["@mozilla.org/browser/sessionstore;1"]
+ .getService(Ci.nsISessionStore)
+ .duplicateTab(window, aTab);
+ ]]>
+ </body>
+ </method>
+
+ <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
+ MAKE SURE TO ADD IT HERE AS WELL. -->
+ <property name="canGoBack"
+ onget="return this.mCurrentBrowser.canGoBack;"
+ readonly="true"/>
+
+ <property name="canGoForward"
+ onget="return this.mCurrentBrowser.canGoForward;"
+ readonly="true"/>
+
+ <method name="goBack">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goBack();
+ ]]>
+ </body>
+ </method>
+
+ <method name="goForward">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goForward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reload">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadWithFlags">
+ <parameter name="aFlags"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reloadWithFlags(aFlags);
+ ]]>
+ </body>
+ </method>
+
+ <method name="stop">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.stop();
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURI">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURIWithFlags">
+ <parameter name="aURI"/>
+ <parameter name="aFlags"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
+ ]]>
+ </body>
+ </method>
+
+ <method name="goHome">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goHome();
+ ]]>
+ </body>
+ </method>
+
+ <property name="homePage">
+ <getter>
+ <![CDATA[
+ return this.mCurrentBrowser.homePage;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.mCurrentBrowser.homePage = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="gotoIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.gotoIndex(aIndex);
+ ]]>
+ </body>
+ </method>
+
+ <method name="attachFormFill">
+ <body><![CDATA[
+ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
+ var cb = this.getBrowserAtIndex(i);
+ cb.attachFormFill();
+ }
+ ]]></body>
+ </method>
+
+ <method name="detachFormFill">
+ <body><![CDATA[
+ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
+ var cb = this.getBrowserAtIndex(i);
+ cb.detachFormFill();
+ }
+ ]]></body>
+ </method>
+
+ <property name="currentURI"
+ onget="return this.mCurrentBrowser.currentURI;"
+ readonly="true"/>
+
+ <field name="_fastFind">null</field>
+ <property name="fastFind"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._fastFind) {
+ this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
+ .createInstance(Components.interfaces.nsITypeAheadFind);
+ this._fastFind.init(this.docShell);
+ }
+ return this._fastFind;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_lastSearchString">null</field>
+ <field name="_lastSearchHighlight">false</field>
+
+ <field name="finder"><![CDATA[
+ ({
+ mTabBrowser: this,
+ mListeners: new Set(),
+ get finder() {
+ return this.mTabBrowser.mCurrentBrowser.finder;
+ },
+ addResultListener: function(aListener) {
+ this.mListeners.add(aListener);
+ this.finder.addResultListener(aListener);
+ },
+ removeResultListener: function(aListener) {
+ this.mListeners.delete(aListener);
+ this.finder.removeResultListener(aListener);
+ },
+ get searchString() {
+ return this.finder.searchString;
+ },
+ get clipboardSearchString() {
+ return this.finder.clipboardSearchString;
+ },
+ set clipboardSearchString(val) {
+ return this.finder.clipboardSearchString = val;
+ },
+ set caseSensitive(val) {
+ return this.finder.caseSensitive = val;
+ },
+ fastFind: function(aSearchString, aLinksOnly, aDrawOutline) {
+ this.finder.fastFind(aSearchString, aLinksOnly, aDrawOutline);
+ },
+ findAgain: function(aFindBackwards, aLinksOnly, aDrawOutline) {
+ this.finder.findAgain(aFindBackwards, aLinksOnly, aDrawOutline);
+ },
+ setSearchStringToSelection: function() {
+ return this.finder.setSearchStringToSelection();
+ },
+ highlight: function(aHighlight, aWord) {
+ this.finder.highlight(aHighlight, aWord);
+ },
+ getInitialSelection: function() {
+ this.finder.getInitialSelection();
+ },
+ enableSelection: function() {
+ this.finder.enableSelection();
+ },
+ removeSelection: function() {
+ this.finder.removeSelection();
+ },
+ focusContent: function() {
+ this.finder.focusContent();
+ },
+ keyPress: function(aEvent) {
+ this.finder.keyPress(aEvent);
+ },
+ requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) {
+ this.finder.requestMatchesCount(aWord, aMatchLimit, aLinksOnly);
+ }
+ })
+ ]]></field>
+
+ <property name="docShell"
+ onget="return this.mCurrentBrowser.docShell"
+ readonly="true"/>
+
+ <property name="webNavigation"
+ onget="return this.mCurrentBrowser.webNavigation"
+ readonly="true"/>
+
+ <property name="webBrowserFind"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webBrowserFind"/>
+
+ <property name="webProgress"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webProgress"/>
+
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this.mCurrentBrowser.contentWindow"/>
+
+ <property name="sessionHistory"
+ onget="return this.mCurrentBrowser.sessionHistory;"
+ readonly="true"/>
+
+ <property name="markupDocumentViewer"
+ onget="return this.mCurrentBrowser.markupDocumentViewer;"
+ readonly="true"/>
+
+ <property name="contentViewerEdit"
+ onget="return this.mCurrentBrowser.contentViewerEdit;"
+ readonly="true"/>
+
+ <property name="contentViewerFile"
+ onget="return this.mCurrentBrowser.contentViewerFile;"
+ readonly="true"/>
+
+ <property name="contentDocument"
+ onget="return this.mCurrentBrowser.contentDocument;"
+ readonly="true"/>
+
+ <property name="contentTitle"
+ onget="return this.mCurrentBrowser.contentTitle;"
+ readonly="true"/>
+
+ <property name="contentPrincipal"
+ onget="return this.mCurrentBrowser.contentPrincipal;"
+ readonly="true"/>
+
+ <property name="securityUI"
+ onget="return this.mCurrentBrowser.securityUI;"
+ readonly="true"/>
+
+ <property name="fullZoom"
+ onget="return this.mCurrentBrowser.fullZoom;"
+ onset="this.mCurrentBrowser.fullZoom = val;"/>
+
+ <property name="textZoom"
+ onget="return this.mCurrentBrowser.textZoom;"
+ onset="this.mCurrentBrowser.textZoom = val;"/>
+
+ <property name="isSyntheticDocument"
+ onget="return this.mCurrentBrowser.isSyntheticDocument;"
+ readonly="true"/>
+
+ <method name="_handleKeyEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!aEvent.isTrusted) {
+ // Don't let untrusted events mess with tabs.
+ return;
+ }
+
+ if (aEvent.altKey)
+ return;
+
+ if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
+ switch (aEvent.keyCode) {
+ case aEvent.DOM_VK_PAGE_UP:
+ this.moveTabBackward();
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ return;
+ case aEvent.DOM_VK_PAGE_DOWN:
+ this.moveTabForward();
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ return;
+ }
+ }
+
+ // We need to take care of FAYT-watching as long as the findbar
+ // isn't initialized. The checks on aEvent are copied from
+ // _shouldFastFind (see findbar.xml).
+ if (!gFindBarInitialized &&
+ !(aEvent.ctrlKey || aEvent.metaKey) &&
+ !aEvent.defaultPrevented) {
+ let charCode = aEvent.charCode;
+ if (charCode) {
+ let char = String.fromCharCode(charCode);
+ if (char == "'" || char == "/" ||
+ Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
+ gFindBar._onBrowserKeypress(aEvent);
+ return;
+ }
+ }
+ }
+
+#ifdef XP_MACOSX
+ if (!aEvent.metaKey)
+ return;
+
+ var offset = 1;
+ switch (aEvent.charCode) {
+ case '}'.charCodeAt(0):
+ offset = -1;
+ case '{'.charCodeAt(0):
+ if (window.getComputedStyle(this, null).direction == "ltr")
+ offset *= -1;
+ this.tabContainer.advanceSelectedTab(offset, true);
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+#else
+ if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
+ aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
+ !this.mCurrentTab.pinned) {
+ this.removeCurrentTab({animate: true});
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+#endif
+ ]]></body>
+ </method>
+
+ <property name="userTypedClear"
+ onget="return this.mCurrentBrowser.userTypedClear;"
+ onset="return this.mCurrentBrowser.userTypedClear = val;"/>
+
+ <property name="userTypedValue"
+ onget="return this.mCurrentBrowser.userTypedValue;"
+ onset="return this.mCurrentBrowser.userTypedValue = val;"/>
+
+ <method name="createTooltip">
+ <parameter name="event"/>
+ <body><![CDATA[
+ event.stopPropagation();
+ var tab = document.tooltipNode;
+ if (tab.localName != "tab") {
+ event.preventDefault();
+ return;
+ }
+ event.target.setAttribute("label", tab.mOverCloseButton ?
+ tab.getAttribute("closetabtext") :
+ tab.getAttribute("label"));
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "keypress":
+ this._handleKeyEvent(aEvent);
+ break;
+ case "sizemodechange":
+ if (aEvent.target == window) {
+ this.mCurrentBrowser.docShellIsActive =
+ (window.windowState != window.STATE_MINIMIZED);
+ }
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ let json = aMessage.json;
+ let browser = aMessage.target;
+
+ switch (aMessage.name) {
+ case "DOMTitleChanged":
+ let tab = this.getTabForBrowser(browser);
+ if (!tab)
+ return;
+ let titleChanged = this.setTabTitle(tab);
+ if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
+ tab.setAttribute("titlechanged", "true");
+ }
+ ]]></body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
+ this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
+ if (Services.prefs.getBoolPref("browser.tabs.remote")) {
+ browserStack.removeChild(this.mCurrentBrowser);
+ this.mCurrentBrowser.setAttribute("remote", true);
+ browserStack.appendChild(this.mCurrentBrowser);
+ }
+
+ this.mCurrentTab = this.tabContainer.firstChild;
+ document.addEventListener("keypress", this, false);
+ window.addEventListener("sizemodechange", this, false);
+
+ var uniqueId = "panel" + Date.now();
+ this.mPanelContainer.childNodes[0].id = uniqueId;
+ this.mCurrentTab.linkedPanel = uniqueId;
+ this.mCurrentTab._tPos = 0;
+ this.mCurrentTab._fullyOpen = true;
+ this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
+ this._tabForBrowser.set(this.mCurrentBrowser, this.mCurrentTab);
+
+ // set up the shared autoscroll popup
+ this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
+ this._autoScrollPopup.id = "autoscroller";
+ this.appendChild(this._autoScrollPopup);
+ this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+ this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
+ this.updateWindowResizers();
+
+ // Hook up the event listeners to the first browser
+ var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
+ const nsIWebProgress = Components.interfaces.nsIWebProgress;
+ const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(nsIWebProgress);
+ filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[0] = tabListener;
+ this.mTabFilters[0] = filter;
+
+ try {
+ // We assume this can only fail because mCurrentBrowser's docShell
+ // hasn't been created, yet. This may be caused by code accessing
+ // gBrowser before the window has finished loading.
+ this._addProgressListenerForInitialTab();
+ } catch (e) {
+ // The binding was constructed too early, wait until the initial
+ // tab's document is ready, then add the progress listener.
+ this._waitForInitialContentDocument();
+ }
+
+ this.style.backgroundColor =
+ Services.prefs.getBoolPref("browser.display.use_system_colors") ?
+ "-moz-default-background-color" :
+ Services.prefs.getCharPref("browser.display.background_color");
+
+ if (Services.prefs.getBoolPref("browser.tabs.remote")) {
+ messageManager.addMessageListener("DOMTitleChanged", this);
+ } else {
+ this._outerWindowIDBrowserMap.set(this.mCurrentBrowser.outerWindowID,
+ this.mCurrentBrowser);
+ }
+ ]]>
+ </constructor>
+
+ <method name="_addProgressListenerForInitialTab">
+ <body><![CDATA[
+ this.webProgress.addProgressListener(this.mTabFilters[0], Ci.nsIWebProgress.NOTIFY_ALL);
+ ]]></body>
+ </method>
+
+ <method name="_waitForInitialContentDocument">
+ <body><![CDATA[
+ let obs = (subject, topic) => {
+ if (this.browsers[0].contentWindow == subject) {
+ Services.obs.removeObserver(obs, topic);
+ this._addProgressListenerForInitialTab();
+ }
+ };
+
+ // We use content-document-global-created as an approximation for
+ // "docShell is initialized". We can do this because in the
+ // mTabProgressListener we care most about the STATE_STOP notification
+ // that will reset mBlank. That means it's important to at least add
+ // the progress listener before the initial about:blank load stops
+ // if we can't do it before the load starts.
+ Services.obs.addObserver(obs, "content-document-global-created", false);
+ ]]></body>
+ </method>
+
+ <destructor>
+ <![CDATA[
+ for (var i = 0; i < this.mTabListeners.length; ++i) {
+ let browser = this.getBrowserAtIndex(i);
+ if (browser.registeredOpenURI) {
+ this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
+ delete browser.registeredOpenURI;
+ }
+ browser.webProgress.removeProgressListener(this.mTabFilters[i]);
+ this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
+ this.mTabFilters[i] = null;
+ this.mTabListeners[i].destroy();
+ this.mTabListeners[i] = null;
+ }
+ document.removeEventListener("keypress", this, false);
+ window.removeEventListener("sizemodechange", this, false);
+
+ if (Services.prefs.getBoolPref("browser.tabs.remote"))
+ messageManager.removeMessageListener("DOMTitleChanged", this);
+ ]]>
+ </destructor>
+
+ <!-- Deprecated stuff, implemented for backwards compatibility. -->
+ <method name="enterTabbedMode">
+ <body>
+ Application.console.log("enterTabbedMode is an obsolete method and " +
+ "will be removed in a future release.");
+ </body>
+ </method>
+ <field name="mTabbedMode" readonly="true">true</field>
+ <method name="setStripVisibilityTo">
+ <parameter name="aShow"/>
+ <body>
+ this.tabContainer.visible = aShow;
+ </body>
+ </method>
+ <method name="getStripVisibility">
+ <body>
+ return this.tabContainer.visible;
+ </body>
+ </method>
+ <property name="mContextTab" readonly="true"
+ onget="return TabContextMenu.contextTab;"/>
+ <property name="mPrefs" readonly="true"
+ onget="return Services.prefs;"/>
+ <property name="mTabContainer" readonly="true"
+ onget="return this.tabContainer;"/>
+ <property name="mTabs" readonly="true"
+ onget="return this.tabs;"/>
+ <!--
+ - Compatibility hack: several extensions depend on this property to
+ - access the tab context menu or tab container, so keep that working for
+ - now. Ideally we can remove this once extensions are using
+ - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
+ -->
+ <property name="mStrip" readonly="true">
+ <getter>
+ <![CDATA[
+ return ({
+ self: this,
+ childNodes: [null, this.tabContextMenu, this.tabContainer],
+ firstChild: { nextSibling: this.tabContextMenu },
+ getElementsByAttribute: function (attr, attrValue) {
+ if (attr == "anonid" && attrValue == "tabContextMenu")
+ return [this.self.tabContextMenu];
+ return [];
+ },
+ // Also support adding event listeners (forward to the tab container)
+ addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
+ removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
+ });
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMWindowClose" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ if (this.tabs.length == 1)
+ return;
+
+ var tab = this._getTabForContentWindow(event.target);
+ if (tab) {
+ this.removeTab(tab);
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ <handler event="DOMWillOpenModalDialog" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ // We're about to open a modal dialog, make sure the opening
+ // tab is brought to the front.
+ this.selectedTab = this._getTabForContentWindow(event.target.top);
+ ]]>
+ </handler>
+ <handler event="DOMTitleChanged">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ var contentWin = event.target.defaultView;
+ if (contentWin != contentWin.top)
+ return;
+
+ var tab = this._getTabForContentWindow(contentWin);
+ if (!tab) {
+ return;
+ }
+
+ var titleChanged = this.setTabTitle(tab);
+ if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
+ tab.setAttribute("titlechanged", "true");
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabbox"
+ extends="chrome://global/content/bindings/tabbox.xml#tabbox">
+ <implementation>
+ <property name="tabs" readonly="true"
+ onget="return document.getBindingParent(this).tabContainer;"/>
+ </implementation>
+ </binding>
+
+ <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+ <implementation>
+ <!-- Override scrollbox.xml method, since our scrollbox's children are
+ inherited from the binding parent -->
+ <method name="_getScrollableElements">
+ <body><![CDATA[
+ return Array.filter(document.getBindingParent(this).childNodes,
+ this._canScrollToElement, this);
+ ]]></body>
+ </method>
+ <method name="_canScrollToElement">
+ <parameter name="tab"/>
+ <body><![CDATA[
+ return !tab.pinned && !tab.hidden;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="underflow" phase="capturing"><![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.removeAttribute("overflow");
+
+ if (tabs._lastTabClosedByMouse)
+ tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
+
+ tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
+ tabs.tabbrowser);
+
+ tabs._positionPinnedTabs();
+ ]]></handler>
+ <handler event="overflow"><![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.setAttribute("overflow", "true");
+ tabs._positionPinnedTabs();
+ tabs._handleTabSelect(false);
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabs"
+ extends="chrome://global/content/bindings/tabbox.xml#tabs">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content>
+ <xul:hbox align="end">
+ <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
+ </xul:hbox>
+ <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
+ style="min-width: 1px;"
+#ifndef XP_MACOSX
+ clicktoscroll="true"
+#endif
+ class="tabbrowser-arrowscrollbox">
+# This is a hack to circumvent bug 472020, otherwise the tabs show up on the
+# right of the newtab button.
+ <children includes="tab"/>
+# This is to ensure anything extensions put here will go before the newtab
+# button, necessary due to the previous hack.
+ <children/>
+ <xul:toolbarbutton class="tabs-newtab-button"
+ command="cmd_newNavigatorTab"
+ onclick="checkForMiddleClick(this, event);"
+ onmouseover="document.getBindingParent(this)._enterNewTab();"
+ onmouseout="document.getBindingParent(this)._leaveNewTab();"
+ tooltiptext="&newTabButton.tooltip;"/>
+ <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
+ style="width: 0;"/>
+ </xul:arrowscrollbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor>
+ <![CDATA[
+ this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
+ this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
+ this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
+
+ var tab = this.firstChild;
+ tab.setAttribute("label",
+ this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
+ tab.setAttribute("crop", "end");
+ tab.setAttribute("onerror", "this.removeAttribute('image');");
+ this.adjustTabstrip();
+
+ Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
+ window.addEventListener("resize", this, false);
+ window.addEventListener("load", this, false);
+
+ try {
+ this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
+ } catch (ex) {
+ this._tabAnimationLoggingEnabled = false;
+ }
+ this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("browser.tabs.", this._prefObserver);
+ ]]>
+ </destructor>
+
+ <field name="tabbrowser" readonly="true">
+ document.getElementById(this.getAttribute("tabbrowser"));
+ </field>
+
+ <field name="tabbox" readonly="true">
+ this.tabbrowser.mTabBox;
+ </field>
+
+ <field name="contextMenu" readonly="true">
+ document.getElementById("tabContextMenu");
+ </field>
+
+ <field name="mTabstripWidth">0</field>
+
+ <field name="mTabstrip">
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
+ </field>
+
+ <field name="_firstTab">null</field>
+ <field name="_lastTab">null</field>
+ <field name="_afterSelectedTab">null</field>
+ <field name="_beforeHoveredTab">null</field>
+ <field name="_afterHoveredTab">null</field>
+
+ <method name="_setPositionalAttributes">
+ <body><![CDATA[
+ let visibleTabs = this.tabbrowser.visibleTabs;
+
+ if (!visibleTabs.length)
+ return;
+
+ let selectedIndex = visibleTabs.indexOf(this.selectedItem);
+
+ let lastVisible = visibleTabs.length - 1;
+
+ if (this._afterSelectedTab)
+ this._afterSelectedTab.removeAttribute("afterselected-visible");
+ if (this.selectedItem.closing || selectedIndex == lastVisible) {
+ this._afterSelectedTab = null;
+ } else {
+ this._afterSelectedTab = visibleTabs[selectedIndex + 1];
+ this._afterSelectedTab.setAttribute("afterselected-visible",
+ "true");
+ }
+
+ if (this._firstTab)
+ this._firstTab.removeAttribute("first-visible-tab");
+ this._firstTab = visibleTabs[0];
+ this._firstTab.setAttribute("first-visible-tab", "true");
+ if (this._lastTab)
+ this._lastTab.removeAttribute("last-visible-tab");
+ this._lastTab = visibleTabs[lastVisible];
+ this._lastTab.setAttribute("last-visible-tab", "true");
+ ]]></body>
+ </method>
+
+ <field name="_prefObserver"><![CDATA[({
+ tabContainer: this,
+
+ observe: function (subject, topic, data) {
+ switch (data) {
+ case "browser.tabs.closeButtons":
+ this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data);
+ this.tabContainer.adjustTabstrip();
+ break;
+ case "browser.tabs.autoHide":
+ this.tabContainer.updateVisibility();
+ break;
+ case "browser.tabs.closeWindowWithLastTab":
+ this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data);
+ this.tabContainer.adjustTabstrip();
+ break;
+ }
+ }
+ });]]></field>
+ <field name="_blockDblClick">false</field>
+
+ <field name="_tabDropIndicator">
+ document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
+ </field>
+
+ <field name="_dragOverDelay">350</field>
+ <field name="_dragTime">0</field>
+
+ <field name="_container" readonly="true"><![CDATA[
+ this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
+ ]]></field>
+
+ <field name="_propagatedVisibilityOnce">false</field>
+
+ <property name="visible"
+ onget="return !this._container.collapsed;">
+ <setter><![CDATA[
+ if (val == this.visible &&
+ this._propagatedVisibilityOnce)
+ return val;
+
+ this._container.collapsed = !val;
+
+ this._propagateVisibility();
+ this._propagatedVisibilityOnce = true;
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="_enterNewTab">
+ <body><![CDATA[
+ let visibleTabs = this.tabbrowser.visibleTabs;
+ let candidate = visibleTabs[visibleTabs.length - 1];
+ if (!candidate.selected) {
+ this._beforeHoveredTab = candidate;
+ candidate.setAttribute("beforehovered", "true");
+ }
+ ]]></body>
+ </method>
+
+ <method name="_leaveNewTab">
+ <body><![CDATA[
+ if (this._beforeHoveredTab) {
+ this._beforeHoveredTab.removeAttribute("beforehovered");
+ this._beforeHoveredTab = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_propagateVisibility">
+ <body><![CDATA[
+ let visible = this.visible;
+
+ document.getElementById("menu_closeWindow").hidden = !visible;
+ document.getElementById("menu_close").setAttribute("label",
+ this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
+
+ goSetCommandEnabled("cmd_ToggleTabsOnTop", visible);
+
+ TabsOnTop.syncUI();
+
+ TabsInTitlebar.allowedBy("tabs-visible", visible);
+ ]]></body>
+ </method>
+
+ <method name="updateVisibility">
+ <body><![CDATA[
+ if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
+ this.visible = window.toolbar.visible &&
+ !Services.prefs.getBoolPref("browser.tabs.autoHide");
+ else
+ this.visible = true;
+ ]]></body>
+ </method>
+
+ <method name="adjustTabstrip">
+ <body><![CDATA[
+ let numTabs = this.childNodes.length -
+ this.tabbrowser._removingTabs.length;
+ // modes for tabstrip
+ // 0 - button on active tab only
+ // 1 - close buttons on all tabs, if available space allows
+ // 2 - no close buttons at all
+ // 3 - close button at the end of the tabstrip
+ switch (this.mCloseButtons) {
+ case 0:
+ // If we decide we want to hide the close tab button on the last tab
+ // when closing the window with the last tab, then we should check
+ // if (numTabs == 1 && this._closeWindowWithLastTab) here and set
+ // this.setAttribute("closebuttons", "hidden") appropriately
+ this.setAttribute("closebuttons", "activetab");
+ break;
+ case 1:
+ if (numTabs == 1) {
+ // See remark about potentially hiding the close tab button, above.
+ this.setAttribute("closebuttons", "alltabs");
+ } else if (numTabs == 2) {
+ // This is an optimization to avoid layout flushes by calling
+ // getBoundingClientRect() when we just opened a second tab. In
+ // this case it's highly unlikely that the tab width is smaller
+ // than mTabClipWidth and the tab close button obscures too much
+ // of the tab's label. In the edge case of the window being too
+ // narrow (or if tabClipWidth has been set to a way higher value),
+ // we'll correct the 'closebuttons' attribute after the tabopen
+ // animation has finished.
+ this.setAttribute("closebuttons", "alltabs");
+ } else {
+ let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
+ if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
+ this.setAttribute("closebuttons", "alltabs");
+ else
+ this.setAttribute("closebuttons", "activetab");
+ }
+ break;
+ case 2:
+ case 3:
+ this.setAttribute("closebuttons", "never");
+ break;
+ }
+ var tabstripClosebutton = document.getElementById("tabs-closebutton");
+ if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container)
+ tabstripClosebutton.collapsed = this.mCloseButtons != 3;
+ ]]></body>
+ </method>
+
+ <method name="_handleTabSelect">
+ <parameter name="aSmoothScroll"/>
+ <body><![CDATA[
+ if (this.getAttribute("overflow") == "true")
+ this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
+ ]]></body>
+ </method>
+
+ <method name="_fillTrailingGap">
+ <body><![CDATA[
+ try {
+ // if we're at the right side (and not the logical end,
+ // which is why this works for both LTR and RTL)
+ // of the tabstrip, we need to ensure that we stay
+ // completely scrolled to the right side
+ var tabStrip = this.mTabstrip;
+ if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
+ tabStrip.scrollSize)
+ tabStrip.scrollByPixels(-1);
+ } catch (e) {}
+ ]]></body>
+ </method>
+
+ <field name="_closingTabsSpacer">
+ document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
+ </field>
+
+ <field name="_tabDefaultMaxWidth">NaN</field>
+ <field name="_lastTabClosedByMouse">false</field>
+ <field name="_hasTabTempMaxWidth">false</field>
+
+ <!-- Try to keep the active tab's close button under the mouse cursor -->
+ <method name="_lockTabSizing">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ var tabs = this.tabbrowser.visibleTabs;
+ if (!tabs.length)
+ return;
+
+ var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
+ var tabWidth = aTab.getBoundingClientRect().width;
+
+ if (!this._tabDefaultMaxWidth)
+ this._tabDefaultMaxWidth =
+ parseFloat(window.getComputedStyle(aTab).maxWidth);
+ this._lastTabClosedByMouse = true;
+
+ if (this.getAttribute("overflow") == "true") {
+#ifdef XP_WIN
+ // Don't need to do anything if we're in overflow mode and we're closing
+ // the last tab.
+ if (isEndTab)
+#else
+ // Don't need to do anything if we're in overflow mode and aren't scrolled
+ // all the way to the right, or if we're closing the last tab.
+ if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
+#endif
+ return;
+
+ // If the tab has an owner that will become the active tab, the owner will
+ // be to the left of it, so we actually want the left tab to slide over.
+ // This can't be done as easily in non-overflow mode, so we don't bother.
+ if (aTab.owner)
+ return;
+
+ // Resize immediately if preffed
+ // XXX: we may want to make this a three-state pref to disable this early
+ // exit if people prefer a mix of behavior (don't resize in overflow,
+ // but resize if not overflowing)
+ if (Services.prefs.getBoolPref("browser.tabs.resize_immediately"))
+ return;
+
+ this._expandSpacerBy(tabWidth);
+ } else { // non-overflow mode
+ // Locking is neither in effect nor needed, so let tabs expand normally.
+ if (isEndTab && !this._hasTabTempMaxWidth)
+ return;
+
+ // Resize immediately if preffed
+ // XXX: we may want to make this a three-state pref to disable this early
+ // exit if people prefer a mix of behavior (don't resize in overflow,
+ // but resize if not overflowing)
+ if (Services.prefs.getBoolPref("browser.tabs.resize_immediately"))
+ return;
+
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ // Force tabs to stay the same width, unless we're closing the last tab,
+ // which case we need to let them expand just enough so that the overall
+ // tabbar width is the same.
+ if (isEndTab) {
+ let numNormalTabs = tabs.length - numPinned;
+ tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
+ if (tabWidth > this._tabDefaultMaxWidth)
+ tabWidth = this._tabDefaultMaxWidth;
+ }
+ tabWidth += "px";
+ for (let i = numPinned; i < tabs.length; i++) {
+ let tab = tabs[i];
+ tab.style.setProperty("max-width", tabWidth, "important");
+ if (!isEndTab) { // keep tabs the same width
+ tab.style.transition = "none";
+ tab.clientTop; // flush styles to skip animation; see bug 649247
+ tab.style.transition = "";
+ }
+ }
+ this._hasTabTempMaxWidth = true;
+ this.tabbrowser.addEventListener("mousemove", this, false);
+ window.addEventListener("mouseout", this, false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_expandSpacerBy">
+ <parameter name="pixels"/>
+ <body><![CDATA[
+ let spacer = this._closingTabsSpacer;
+ spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
+ this.setAttribute("using-closing-tabs-spacer", "true");
+ this.tabbrowser.addEventListener("mousemove", this, false);
+ window.addEventListener("mouseout", this, false);
+ ]]></body>
+ </method>
+
+ <method name="_unlockTabSizing">
+ <body><![CDATA[
+ this.tabbrowser.removeEventListener("mousemove", this, false);
+ window.removeEventListener("mouseout", this, false);
+
+ if (this._hasTabTempMaxWidth) {
+ this._hasTabTempMaxWidth = false;
+ let tabs = this.tabbrowser.visibleTabs;
+ for (let i = 0; i < tabs.length; i++)
+ tabs[i].style.maxWidth = "";
+ }
+
+ if (this.hasAttribute("using-closing-tabs-spacer")) {
+ this.removeAttribute("using-closing-tabs-spacer");
+ this._closingTabsSpacer.style.width = 0;
+ }
+ ]]></body>
+ </method>
+
+ <field name="_lastNumPinned">0</field>
+ <method name="_positionPinnedTabs">
+ <body><![CDATA[
+ var numPinned = this.tabbrowser._numPinnedTabs;
+ var doPosition = this.getAttribute("overflow") == "true" &&
+ numPinned > 0;
+
+ if (doPosition) {
+ this.setAttribute("positionpinnedtabs", "true");
+
+ let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
+ let paddingStart = this.mTabstrip.scrollboxPaddingStart;
+ let width = 0;
+
+ for (let i = numPinned - 1; i >= 0; i--) {
+ let tab = this.childNodes[i];
+ width += tab.getBoundingClientRect().width;
+ tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
+ }
+
+ this.style.MozPaddingStart = width + paddingStart + "px";
+
+ } else {
+ this.removeAttribute("positionpinnedtabs");
+
+ for (let i = 0; i < numPinned; i++) {
+ let tab = this.childNodes[i];
+ tab.style.MozMarginStart = "";
+ }
+
+ this.style.MozPaddingStart = "";
+ }
+
+ if (this._lastNumPinned != numPinned) {
+ this._lastNumPinned = numPinned;
+ this._handleTabSelect(false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_animateTabMove">
+ <parameter name="event"/>
+ <body><![CDATA[
+ let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+
+ if (this.getAttribute("movingtab") != "true") {
+ this.setAttribute("movingtab", "true");
+ this.selectedItem = draggedTab;
+ }
+
+ if (!("animLastScreenX" in draggedTab._dragData))
+ draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
+
+ let screenX = event.screenX;
+ if (screenX == draggedTab._dragData.animLastScreenX)
+ return;
+
+ let draggingRight = screenX > draggedTab._dragData.animLastScreenX;
+ draggedTab._dragData.animLastScreenX = screenX;
+
+ let rtl = (window.getComputedStyle(this).direction == "rtl");
+ let pinned = draggedTab.pinned;
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ let tabs = this.tabbrowser.visibleTabs
+ .slice(pinned ? 0 : numPinned,
+ pinned ? numPinned : undefined);
+ if (rtl)
+ tabs.reverse();
+ let tabWidth = draggedTab.getBoundingClientRect().width;
+
+ // Move the dragged tab based on the mouse position.
+
+ let leftTab = tabs[0];
+ let rightTab = tabs[tabs.length - 1];
+ let tabScreenX = draggedTab.boxObject.screenX;
+ let translateX = screenX - draggedTab._dragData.screenX;
+ if (!pinned)
+ translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
+ let leftBound = leftTab.boxObject.screenX - tabScreenX;
+ let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
+ (tabScreenX + tabWidth);
+ translateX = Math.max(translateX, leftBound);
+ translateX = Math.min(translateX, rightBound);
+ draggedTab.style.transform = "translateX(" + translateX + "px)";
+
+ // Determine what tab we're dragging over.
+ // * Point of reference is the center of the dragged tab. If that
+ // point touches a background tab, the dragged tab would take that
+ // tab's position when dropped.
+ // * We're doing a binary search in order to reduce the amount of
+ // tabs we need to check.
+
+ let tabCenter = tabScreenX + translateX + tabWidth / 2;
+ let newIndex = -1;
+ let oldIndex = "animDropIndex" in draggedTab._dragData ?
+ draggedTab._dragData.animDropIndex : draggedTab._tPos;
+ let low = 0;
+ let high = tabs.length - 1;
+ while (low <= high) {
+ let mid = Math.floor((low + high) / 2);
+ if (tabs[mid] == draggedTab &&
+ ++mid > high)
+ break;
+ let boxObject = tabs[mid].boxObject;
+ let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
+ if (screenX > tabCenter) {
+ high = mid - 1;
+ } else if (screenX + boxObject.width < tabCenter) {
+ low = mid + 1;
+ } else {
+ newIndex = tabs[mid]._tPos;
+ break;
+ }
+ }
+ if (newIndex >= oldIndex)
+ newIndex++;
+ if (newIndex < 0 || newIndex == oldIndex)
+ return;
+ draggedTab._dragData.animDropIndex = newIndex;
+
+ // Shift background tabs to leave a gap where the dragged tab
+ // would currently be dropped.
+
+ for (let tab of tabs) {
+ if (tab != draggedTab) {
+ let shift = getTabShift(tab, newIndex);
+ tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
+ }
+ }
+
+ function getTabShift(tab, dropIndex) {
+ if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
+ return rtl ? -tabWidth : tabWidth;
+ if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
+ return rtl ? tabWidth : -tabWidth;
+ return 0;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_finishAnimateTabMove">
+ <body><![CDATA[
+ if (this.getAttribute("movingtab") != "true")
+ return;
+
+ for (let tab of this.tabbrowser.visibleTabs)
+ tab.style.transform = "";
+
+ this.removeAttribute("movingtab");
+
+ this._handleTabSelect();
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "load":
+ this.updateVisibility();
+ break;
+ case "resize":
+ if (aEvent.target != window)
+ break;
+
+ let sizemode = document.documentElement.getAttribute("sizemode");
+ TabsInTitlebar.allowedBy("sizemode",
+ sizemode == "maximized" || sizemode == "fullscreen");
+
+ var width = this.mTabstrip.boxObject.width;
+ if (width != this.mTabstripWidth) {
+ this.adjustTabstrip();
+ this._fillTrailingGap();
+ this._handleTabSelect();
+ this.mTabstripWidth = width;
+ }
+
+ this.tabbrowser.updateWindowResizers();
+ break;
+ case "mouseout":
+ // If the "related target" (the node to which the pointer went) is not
+ // a child of the current document, the mouse just left the window.
+ let relatedTarget = aEvent.relatedTarget;
+ if (relatedTarget && relatedTarget.ownerDocument == document)
+ break;
+ case "mousemove":
+ if (document.getElementById("tabContextMenu").state != "open")
+ this._unlockTabSizing();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <field name="_animateElement">
+ this.mTabstrip._scrollButtonDown;
+ </field>
+
+ <method name="_notifyBackgroundTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.pinned)
+ return;
+
+ var scrollRect = this.mTabstrip.scrollClientRect;
+ var tab = aTab.getBoundingClientRect();
+
+ // Is the new tab already completely visible?
+ if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
+ return;
+
+ if (this.mTabstrip.smoothScroll) {
+ let selected = !this.selectedItem.pinned &&
+ this.selectedItem.getBoundingClientRect();
+
+ // Can we make both the new tab and the selected tab completely visible?
+ if (!selected ||
+ Math.max(tab.right - selected.left, selected.right - tab.left) <=
+ scrollRect.width) {
+ this.mTabstrip.ensureElementIsVisible(aTab);
+ return;
+ }
+
+ this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
+ selected.right - scrollRect.right :
+ selected.left - scrollRect.left);
+ }
+
+ if (!this._animateElement.hasAttribute("notifybgtab")) {
+ this._animateElement.setAttribute("notifybgtab", "true");
+ setTimeout(function (ele) {
+ ele.removeAttribute("notifybgtab");
+ }, 150, this._animateElement);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getDragTargetTab">
+ <parameter name="event"/>
+ <body><![CDATA[
+ let tab = event.target.localName == "tab" ? event.target : null;
+ if (tab &&
+ (event.type == "drop" || event.type == "dragover") &&
+ event.dataTransfer.dropEffect == "link") {
+ let boxObject = tab.boxObject;
+ if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
+ event.screenX > boxObject.screenX + boxObject.width * .75)
+ return null;
+ }
+ return tab;
+ ]]></body>
+ </method>
+
+ <method name="_getDropIndex">
+ <parameter name="event"/>
+ <body><![CDATA[
+ var tabs = this.childNodes;
+ var tab = this._getDragTargetTab(event);
+ if (window.getComputedStyle(this, null).direction == "ltr") {
+ for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
+ if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
+ return i;
+ } else {
+ for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
+ if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
+ return i;
+ }
+ return tabs.length;
+ ]]></body>
+ </method>
+
+ <method name="_setEffectAllowedForDataTransfer">
+ <parameter name="event"/>
+ <body><![CDATA[
+ var dt = event.dataTransfer;
+ // Disallow dropping multiple items
+ if (dt.mozItemCount > 1)
+ return dt.effectAllowed = "none";
+
+ var types = dt.mozTypesAt(0);
+ var sourceNode = null;
+ // tabs are always added as the first type
+ if (types[0] == TAB_DROP_TYPE) {
+ var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (sourceNode instanceof XULElement &&
+ sourceNode.localName == "tab" &&
+ sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
+ sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
+ // Do not allow transfering a private tab to a non-private window
+ // and vice versa.
+ if (PrivateBrowsingUtils.isWindowPrivate(window) !=
+ PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
+ return dt.effectAllowed = "none";
+
+#ifdef XP_MACOSX
+ return dt.effectAllowed = event.altKey ? "copy" : "move";
+#else
+ return dt.effectAllowed = event.ctrlKey ? "copy" : "move";
+#endif
+ }
+ }
+
+ if (browserDragAndDrop.canDropLink(event)) {
+ // Here we need to do this manually
+ return dt.effectAllowed = dt.dropEffect = "link";
+ }
+ return dt.effectAllowed = "none";
+ ]]></body>
+ </method>
+
+ <method name="_handleNewTab">
+ <parameter name="tab"/>
+ <body><![CDATA[
+ if (tab.parentNode != this)
+ return;
+ tab._fullyOpen = true;
+
+ this.adjustTabstrip();
+
+ if (tab.getAttribute("selected") == "true") {
+ this._fillTrailingGap();
+ this._handleTabSelect();
+ } else {
+ this._notifyBackgroundTab(tab);
+ }
+
+ // XXXmano: this is a temporary workaround for bug 345399
+ // We need to manually update the scroll buttons disabled state
+ // if a tab was inserted to the overflow area or removed from it
+ // without any scrolling and when the tabbar has already
+ // overflowed.
+ this.mTabstrip._updateScrollButtonsDisabledState();
+ ]]></body>
+ </method>
+
+ <method name="_canAdvanceToTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return !aTab.closing;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabTelemetryStart">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ // Animation-smoothness telemetry/logging
+ if (this._tabAnimationLoggingEnabled) {
+ if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
+ // Indicate newtab page animation where other tabs are unaffected
+ // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
+ aTab._recordingTabOpenPlain = true;
+ }
+ aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .startFrameTimeRecording();
+ }
+
+ // Overall animation duration
+ aTab._animStartTime = Date.now();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabTelemetryEnd">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab._animStartTime) {
+ return;
+ }
+
+ aTab._animStartTime = 0;
+
+ // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
+ if (!("_recordingHandle" in aTab)) {
+ return;
+ }
+
+ let paints = {};
+ let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .stopFrameTimeRecording(aTab._recordingHandle, paints);
+ delete aTab._recordingHandle;
+ paints = paints.value; // The result array itself.
+ let frameCount = intervals.length;
+
+ if (this._tabAnimationLoggingEnabled) {
+ let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval / paint-processing):\n";
+ for (let i = 0; i < frameCount; i++) {
+ msg += Math.round(intervals[i]) + " / " + Math.round(paints[i]) + "\n";
+ }
+ Services.console.logStringMessage(msg);
+ }
+
+ // For telemetry, the first frame interval is not useful since it may represent an interval
+ // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
+ // But if we recorded only 1 frame (very rare), then the first paint duration is a good
+ // representative of the first frame interval for our cause (indicates very bad animation).
+ // First paint duration is always useful for us.
+ if (frameCount > 0) {
+ let averageInterval = 0;
+ let averagePaint = paints[0];
+ for (let i = 1; i < frameCount; i++) {
+ averageInterval += intervals[i];
+ averagePaint += paints[i];
+ };
+ averagePaint /= frameCount;
+ averageInterval = (frameCount == 1)
+ ? averagePaint
+ : averageInterval / (frameCount - 1);
+
+ if (aTab._recordingTabOpenPlain) {
+ delete aTab._recordingTabOpenPlain;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- Deprecated stuff, implemented for backwards compatibility. -->
+ <property name="mTabstripClosebutton" readonly="true"
+ onget="return document.getElementById('tabs-closebutton');"/>
+ <property name="mAllTabsPopup" readonly="true"
+ onget="return document.getElementById('alltabs-popup');"/>
+ </implementation>
+
+ <handlers>
+ <handler event="TabSelect" action="this._handleTabSelect();"/>
+
+ <handler event="transitionend"><![CDATA[
+ if (event.propertyName != "max-width")
+ return;
+
+ var tab = event.target;
+
+ this._handleTabTelemetryEnd(tab);
+
+ if (tab.getAttribute("fadein") == "true") {
+ if (tab._fullyOpen)
+ this.adjustTabstrip();
+ else
+ this._handleNewTab(tab);
+ } else if (tab.closing) {
+ this.tabbrowser._endRemoveTab(tab);
+ }
+ ]]></handler>
+
+ <handler event="dblclick"><![CDATA[
+#ifndef XP_MACOSX
+ // When the tabbar has an unified appearance with the titlebar
+ // and menubar, a double-click in it should have the same behavior
+ // as double-clicking the titlebar
+ if (TabsInTitlebar.enabled ||
+ (TabsOnTop.enabled && this.parentNode._dragBindingAlive))
+ return;
+#endif
+
+ if (event.button != 0 ||
+ event.originalTarget.localName != "box")
+ return;
+
+ // See hack note in the tabbrowser-close-tab-button binding
+ if (!this._blockDblClick)
+ BrowserOpenTab();
+
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="click"><![CDATA[
+ if (event.button != 1)
+ return;
+
+ if (event.target.localName == "tab") {
+ if (this.childNodes.length > 1 || !this._closeWindowWithLastTab)
+ this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true});
+ } else if (event.originalTarget.localName == "box") {
+ BrowserOpenTab();
+ } else {
+ return;
+ }
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="keypress"><![CDATA[
+ if (event.altKey || event.shiftKey ||
+#ifdef XP_MACOSX
+ !event.metaKey)
+#else
+ !event.ctrlKey || event.metaKey)
+#endif
+ return;
+
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_UP:
+ this.tabbrowser.moveTabBackward();
+ break;
+ case KeyEvent.DOM_VK_DOWN:
+ this.tabbrowser.moveTabForward();
+ break;
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_LEFT:
+ this.tabbrowser.moveTabOver(event);
+ break;
+ case KeyEvent.DOM_VK_HOME:
+ this.tabbrowser.moveTabToStart();
+ break;
+ case KeyEvent.DOM_VK_END:
+ this.tabbrowser.moveTabToEnd();
+ break;
+ default:
+ // Stop the keypress event for the above keyboard
+ // shortcuts only.
+ return;
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ var tab = this._getDragTargetTab(event);
+ if (!tab)
+ return;
+
+ let dt = event.dataTransfer;
+ dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
+ let browser = tab.linkedBrowser;
+
+ // We must not set text/x-moz-url or text/plain data here,
+ // otherwise trying to deatch the tab by dropping it on the desktop
+ // may result in an "internet shortcut"
+ dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
+
+ // Set the cursor to an arrow during tab drags.
+ dt.mozCursor = "default";
+
+ // Create a canvas to which we capture the current tab.
+ // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
+ // canvas size (in CSS pixels) to the window's backing resolution in order
+ // to get a full-resolution drag image for use on HiDPI displays.
+ let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
+ let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.mozOpaque = true;
+ canvas.width = 160 * scale;
+ canvas.height = 90 * scale;
+ PageThumbs.captureToCanvas(browser, canvas);
+ dt.setDragImage(canvas, -16 * scale, -16 * scale);
+
+ // _dragData.offsetX/Y give the coordinates that the mouse should be
+ // positioned relative to the corner of the new window created upon
+ // dragend such that the mouse appears to have the same position
+ // relative to the corner of the dragged tab.
+ function clientX(ele) ele.getBoundingClientRect().left;
+ let tabOffsetX = clientX(tab) - clientX(this);
+ tab._dragData = {
+ offsetX: event.screenX - window.screenX - tabOffsetX,
+ offsetY: event.screenY - window.screenY,
+ scrollX: this.mTabstrip.scrollPosition,
+ screenX: event.screenX
+ };
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ var effects = this._setEffectAllowedForDataTransfer(event);
+
+ var ind = this._tabDropIndicator;
+ if (effects == "" || effects == "none") {
+ ind.collapsed = true;
+ return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+
+ var tabStrip = this.mTabstrip;
+ var ltr = (window.getComputedStyle(this, null).direction == "ltr");
+
+ // autoscroll the tab strip if we drag over the scroll
+ // buttons, even if we aren't dragging a tab, but then
+ // return to avoid drawing the drop indicator
+ var pixelsToScroll = 0;
+ if (this.getAttribute("overflow") == "true") {
+ var targetAnonid = event.originalTarget.getAttribute("anonid");
+ switch (targetAnonid) {
+ case "scrollbutton-up":
+ pixelsToScroll = tabStrip.scrollIncrement * -1;
+ break;
+ case "scrollbutton-down":
+ pixelsToScroll = tabStrip.scrollIncrement;
+ break;
+ }
+ if (pixelsToScroll)
+ tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
+ }
+
+ if (effects == "move" &&
+ this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
+ ind.collapsed = true;
+ this._animateTabMove(event);
+ return;
+ }
+
+ this._finishAnimateTabMove();
+
+ if (effects == "link") {
+ let tab = this._getDragTargetTab(event);
+ if (tab) {
+ if (!this._dragTime)
+ this._dragTime = Date.now();
+ if (Date.now() >= this._dragTime + this._dragOverDelay)
+ this.selectedItem = tab;
+ ind.collapsed = true;
+ return;
+ }
+ }
+
+ var rect = tabStrip.getBoundingClientRect();
+ var newMargin;
+ if (pixelsToScroll) {
+ // if we are scrolling, put the drop indicator at the edge
+ // so that it doesn't jump while scrolling
+ let scrollRect = tabStrip.scrollClientRect;
+ let minMargin = scrollRect.left - rect.left;
+ let maxMargin = Math.min(minMargin + scrollRect.width,
+ scrollRect.right);
+ if (!ltr)
+ [minMargin, maxMargin] = [this.clientWidth - maxMargin,
+ this.clientWidth - minMargin];
+ newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
+ }
+ else {
+ let newIndex = this._getDropIndex(event);
+ if (newIndex == this.childNodes.length) {
+ let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
+ if (ltr)
+ newMargin = tabRect.right - rect.left;
+ else
+ newMargin = rect.right - tabRect.left;
+ }
+ else {
+ let tabRect = this.childNodes[newIndex].getBoundingClientRect();
+ if (ltr)
+ newMargin = tabRect.left - rect.left;
+ else
+ newMargin = rect.right - tabRect.right;
+ }
+ }
+
+ ind.collapsed = false;
+
+ newMargin += ind.clientWidth / 2;
+ if (!ltr)
+ newMargin *= -1;
+
+ ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
+ ind.style.MozMarginStart = (-ind.clientWidth) + "px";
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ var dt = event.dataTransfer;
+ var dropEffect = dt.dropEffect;
+ var draggedTab;
+ if (dropEffect != "link") { // copy or move
+ draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ // not our drop then
+ if (!draggedTab)
+ return;
+ }
+
+ this._tabDropIndicator.collapsed = true;
+ event.stopPropagation();
+ if (draggedTab && dropEffect == "copy") {
+ // copy the dropped tab (wherever it's from)
+ let newIndex = this._getDropIndex(event);
+ let newTab = this.tabbrowser.duplicateTab(draggedTab);
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+ if (draggedTab.parentNode != this || event.shiftKey)
+ this.selectedItem = newTab;
+ } else if (draggedTab && draggedTab.parentNode == this) {
+ this._finishAnimateTabMove();
+
+ // actually move the dragged tab
+ if ("animDropIndex" in draggedTab._dragData) {
+ let newIndex = draggedTab._dragData.animDropIndex;
+ if (newIndex > draggedTab._tPos)
+ newIndex--;
+ this.tabbrowser.moveTabTo(draggedTab, newIndex);
+ }
+ } else if (draggedTab) {
+ // swap the dropped tab with a new one we create and then close
+ // it in the other window (making it seem to have moved between
+ // windows)
+ let newIndex = this._getDropIndex(event);
+ let newTab = this.tabbrowser.addTab("about:blank");
+ let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
+ // Stop the about:blank load
+ newBrowser.stop();
+ // make sure it has a docshell
+ newBrowser.docShell;
+
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
+ this.tabbrowser.pinTab(newTab);
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+
+ // We need to select the tab before calling swapBrowsersAndCloseOther
+ // so that window.content in chrome windows points to the right tab
+ // when pagehide/show events are fired.
+ this.tabbrowser.selectedTab = newTab;
+
+ draggedTab.parentNode._finishAnimateTabMove();
+ this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
+
+ // Call updateCurrentBrowser to make sure the URL bar is up to date
+ // for our new tab after we've done swapBrowsersAndCloseOther.
+ this.tabbrowser.updateCurrentBrowser(true);
+ } else {
+ // Pass true to disallow dropping javascript: or data: urls
+ let url;
+ try {
+ url = browserDragAndDrop.drop(event, { }, true);
+ } catch (ex) {}
+
+// // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
+// if (!url || url.includes(" ")) //PMed
+ if (!url) //FF
+ return;
+
+ let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+
+ if (event.shiftKey)
+ bgLoad = !bgLoad;
+
+ let tab = this._getDragTargetTab(event);
+ if (!tab || dropEffect == "copy") {
+ // We're adding a new tab.
+ let newIndex = this._getDropIndex(event);
+ let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+ } else {
+ // Load in an existing tab.
+ try {
+ let webNav = Ci.nsIWebNavigation;
+ let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, flags);
+ if (!bgLoad)
+ this.selectedItem = tab;
+ } catch(ex) {
+ // Just ignore invalid urls
+ }
+ }
+ }
+
+ if (draggedTab) {
+ delete draggedTab._dragData;
+ }
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ // Note: while this case is correctly handled here, this event
+ // isn't dispatched when the tab is moved within the tabstrip,
+ // see bug 460801.
+
+ this._finishAnimateTabMove();
+
+ var dt = event.dataTransfer;
+ var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (dt.mozUserCancelled || dt.dropEffect != "none") {
+ delete draggedTab._dragData;
+ return;
+ }
+
+ // Disable detach within the browser toolbox
+ var eX = event.screenX;
+ var eY = event.screenY;
+ var wX = window.screenX;
+ // check if the drop point is horizontally within the window
+ if (eX > wX && eX < (wX + window.outerWidth)) {
+ let bo = this.mTabstrip.boxObject;
+ // also avoid detaching if the the tab was dropped too close to
+ // the tabbar (half a tab)
+ let endScreenY = bo.screenY + 1.5 * bo.height;
+ if (eY < endScreenY && eY > window.screenY)
+ return;
+ }
+
+ // screen.availLeft et. al. only check the screen that this window is on,
+ // but we want to look at the screen the tab is being dropped onto.
+ var sX = {}, sY = {}, sWidth = {}, sHeight = {};
+ Cc["@mozilla.org/gfx/screenmanager;1"]
+ .getService(Ci.nsIScreenManager)
+ .screenForRect(eX, eY, 1, 1)
+ .GetAvailRect(sX, sY, sWidth, sHeight);
+ // ensure new window entirely within screen
+ var winWidth = Math.min(window.outerWidth, sWidth.value);
+ var winHeight = Math.min(window.outerHeight, sHeight.value);
+ var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value),
+ sX.value + sWidth.value - winWidth);
+ var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value),
+ sY.value + sHeight.value - winHeight);
+
+ delete draggedTab._dragData;
+
+ if (this.tabbrowser.tabs.length == 1) {
+ // resize _before_ move to ensure the window fits the new screen. if
+ // the window is too large for its screen, the window manager may do
+ // automatic repositioning.
+ window.resizeTo(winWidth, winHeight);
+ window.moveTo(left, top);
+ window.focus();
+ } else {
+ this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
+ screenY: top,
+#ifndef XP_WIN
+ outerWidth: winWidth,
+ outerHeight: winHeight
+#endif
+ });
+ }
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragexit"><![CDATA[
+ this._dragTime = 0;
+
+ // This does not work at all (see bug 458613)
+ var target = event.relatedTarget;
+ while (target && target != this)
+ target = target.parentNode;
+ if (target)
+ return;
+
+ this._tabDropIndicator.collapsed = true;
+ event.stopPropagation();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <!-- close-tab-button binding
+ This binding relies on the structure of the tabbrowser binding.
+ Therefore it should only be used as a child of the tab or the tabs
+ element (in both cases, when they are anonymous nodes of <tabbrowser>).
+ -->
+ <binding id="tabbrowser-close-tab-button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
+ <handlers>
+ <handler event="click" button="0"><![CDATA[
+ var bindingParent = document.getBindingParent(this);
+ var tabContainer = bindingParent.parentNode;
+ /* The only sequence in which a second click event (i.e. dblclik)
+ * can be dispatched on an in-tab close button is when it is shown
+ * after the first click (i.e. the first click event was dispatched
+ * on the tab). This happens when we show the close button only on
+ * the active tab. (bug 352021)
+ * The only sequence in which a third click event can be dispatched
+ * on an in-tab close button is when the tab was opened with a
+ * double click on the tabbar. (bug 378344)
+ * In both cases, it is most likely that the close button area has
+ * been accidentally clicked, therefore we do not close the tab.
+ *
+ * We don't want to ignore processing of more than one click event,
+ * though, since the user might actually be repeatedly clicking to
+ * close many tabs at once.
+ */
+ if (event.detail > 1 && !this._ignoredClick) {
+ this._ignoredClick = true;
+ return;
+ }
+
+ // Reset the "ignored click" flag
+ this._ignoredClick = false;
+
+ tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
+ tabContainer._blockDblClick = true;
+
+ /* XXXmano hack (see bug 343628):
+ * Since we're removing the event target, if the user
+ * double-clicks this button, the dblclick event will be dispatched
+ * with the tabbar as its event target (and explicit/originalTarget),
+ * which treats that as a mouse gesture for opening a new tab.
+ * In this context, we're manually blocking the dblclick event
+ * (see dblclick handler).
+ */
+ var clickedOnce = false;
+ function enableDblClick(event) {
+ var target = event.originalTarget;
+ if (target.className == 'tab-close-button')
+ target._ignoredClick = true;
+ if (!clickedOnce) {
+ clickedOnce = true;
+ return;
+ }
+ tabContainer._blockDblClick = false;
+ tabContainer.removeEventListener("click", enableDblClick, true);
+ }
+ tabContainer.addEventListener("click", enableDblClick, true);
+ ]]></handler>
+
+ <handler event="dblclick" button="0" phase="capturing">
+ // for the one-close-button case
+ event.stopPropagation();
+ </handler>
+
+ <handler event="dragstart">
+ event.stopPropagation();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tab" display="xul:hbox"
+ extends="chrome://global/content/bindings/tabbox.xml#tab">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content context="tabContextMenu" closetabtext="&closeTab.label;">
+ <xul:stack class="tab-stack" flex="1">
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background">
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background-start"/>
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background-middle"/>
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background-end"/>
+ </xul:hbox>
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-content" align="center">
+ <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
+ class="tab-throbber"
+ role="presentation"
+ layer="true" />
+ <xul:image xbl:inherits="validate,src=image,fadein,pinned,selected"
+ class="tab-icon-image"
+ role="presentation"
+ anonid="tab-icon"/>
+ <xul:label flex="1"
+ xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
+ class="tab-text tab-label"
+ role="presentation"/>
+ <xul:toolbarbutton anonid="close-button"
+ xbl:inherits="fadein,pinned,selected"
+ class="tab-close-button close-icon"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+
+ <implementation>
+ <property name="pinned" readonly="true">
+ <getter>
+ return this.getAttribute("pinned") == "true";
+ </getter>
+ </property>
+ <property name="hidden" readonly="true">
+ <getter>
+ return this.getAttribute("hidden") == "true";
+ </getter>
+ </property>
+
+ <field name="mOverCloseButton">false</field>
+ <field name="mCorrespondingMenuitem">null</field>
+ <field name="closing">false</field>
+ <field name="lastAccessed">0</field>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ let anonid = event.originalTarget.getAttribute("anonid");
+ if (anonid == "close-button")
+ this.mOverCloseButton = true;
+
+ let tab = event.target;
+ if (tab.closing)
+ return;
+
+ let tabContainer = this.parentNode;
+ let visibleTabs = tabContainer.tabbrowser.visibleTabs;
+ let tabIndex = visibleTabs.indexOf(tab);
+ if (tabIndex == 0) {
+ tabContainer._beforeHoveredTab = null;
+ } else {
+ let candidate = visibleTabs[tabIndex - 1];
+ if (!candidate.selected) {
+ tabContainer._beforeHoveredTab = candidate;
+ candidate.setAttribute("beforehovered", "true");
+ }
+ }
+
+ if (tabIndex == visibleTabs.length - 1) {
+ tabContainer._afterHoveredTab = null;
+ } else {
+ let candidate = visibleTabs[tabIndex + 1];
+ if (!candidate.selected) {
+ tabContainer._afterHoveredTab = candidate;
+ candidate.setAttribute("afterhovered", "true");
+ }
+ }
+ ]]></handler>
+ <handler event="mouseout"><![CDATA[
+ let anonid = event.originalTarget.getAttribute("anonid");
+ if (anonid == "close-button")
+ this.mOverCloseButton = false;
+
+ let tabContainer = this.parentNode;
+ if (tabContainer._beforeHoveredTab) {
+ tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
+ tabContainer._beforeHoveredTab = null;
+ }
+ if (tabContainer._afterHoveredTab) {
+ tabContainer._afterHoveredTab.removeAttribute("afterhovered");
+ tabContainer._afterHoveredTab = null;
+ }
+ ]]></handler>
+ <handler event="dragstart" phase="capturing">
+ this.style.MozUserFocus = '';
+ </handler>
+ <handler event="mousedown" phase="capturing">
+ <![CDATA[
+ if (this.selected) {
+ this.style.MozUserFocus = 'ignore';
+ this.clientTop; // just using this to flush style updates
+ } else if (this.mOverCloseButton) {
+ // Prevent tabbox.xml from selecting the tab.
+ event.stopPropagation();
+ }
+ ]]>
+ </handler>
+ <handler event="mouseup">
+ this.style.MozUserFocus = '';
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-alltabs-popup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIDOMEventListener">
+ <method name="_tabOnAttrModified">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var tab = aEvent.target;
+ if (tab.mCorrespondingMenuitem)
+ this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
+ ]]></body>
+ </method>
+
+ <method name="_tabOnTabClose">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var tab = aEvent.target;
+ if (tab.mCorrespondingMenuitem)
+ this.removeChild(tab.mCorrespondingMenuitem);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "TabAttrModified":
+ this._tabOnAttrModified(aEvent);
+ break;
+ case "TabClose":
+ this._tabOnTabClose(aEvent);
+ break;
+ case "scroll":
+ this._updateTabsVisibilityStatus();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_updateTabsVisibilityStatus">
+ <body><![CDATA[
+ var tabContainer = gBrowser.tabContainer;
+ // We don't want menu item decoration unless there is overflow.
+ if (tabContainer.getAttribute("overflow") != "true")
+ return;
+
+ var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
+ for (var i = 0; i < this.childNodes.length; i++) {
+ let curTab = this.childNodes[i].tab;
+ let curTabBO = curTab.boxObject;
+ if (curTabBO.screenX >= tabstripBO.screenX &&
+ curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
+ this.childNodes[i].setAttribute("tabIsVisible", "true");
+ else
+ this.childNodes[i].removeAttribute("tabIsVisible");
+ }
+ ]]></body>
+ </method>
+
+ <method name="_createTabMenuItem">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ var menuItem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menuitem");
+
+ menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
+
+ this._setMenuitemAttributes(menuItem, aTab);
+
+ if (!aTab.mCorrespondingMenuitem) {
+ aTab.mCorrespondingMenuitem = menuItem;
+ menuItem.tab = aTab;
+
+ this.appendChild(menuItem);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_setMenuitemAttributes">
+ <parameter name="aMenuitem"/>
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ aMenuitem.setAttribute("label", aTab.label);
+ aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
+
+ if (aTab.hasAttribute("busy")) {
+ aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
+ aMenuitem.removeAttribute("image");
+ } else {
+ aMenuitem.setAttribute("image", aTab.getAttribute("image"));
+ aMenuitem.removeAttribute("busy");
+ }
+
+ if (aTab.hasAttribute("pending"))
+ aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
+ else
+ aMenuitem.removeAttribute("pending");
+
+ if (aTab.selected)
+ aMenuitem.setAttribute("selected", "true");
+ else
+ aMenuitem.removeAttribute("selected");
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing">
+ <![CDATA[
+ var tabcontainer = gBrowser.tabContainer;
+
+ // Listen for changes in the tab bar.
+ tabcontainer.addEventListener("TabAttrModified", this, false);
+ tabcontainer.addEventListener("TabClose", this, false);
+ tabcontainer.mTabstrip.addEventListener("scroll", this, false);
+
+ let tabs = gBrowser.visibleTabs;
+ for (var i = 0; i < tabs.length; i++) {
+ if (!tabs[i].pinned)
+ this._createTabMenuItem(tabs[i]);
+ }
+ this._updateTabsVisibilityStatus();
+ ]]></handler>
+
+ <handler event="popuphidden">
+ <![CDATA[
+ // clear out the menu popup and remove the listeners
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
+ let menuItem = this.childNodes[i];
+ if (menuItem.tab) {
+ menuItem.tab.mCorrespondingMenuitem = null;
+ this.removeChild(menuItem);
+ }
+ }
+ var tabcontainer = gBrowser.tabContainer;
+ tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
+ tabcontainer.removeEventListener("TabAttrModified", this, false);
+ tabcontainer.removeEventListener("TabClose", this, false);
+ ]]></handler>
+
+ <handler event="DOMMenuItemActive">
+ <![CDATA[
+ var tab = event.target.tab;
+ if (tab) {
+ let overLink = tab.linkedBrowser.currentURI.spec;
+ if (overLink == "about:blank")
+ overLink = "";
+ XULBrowserWindow.setOverLink(overLink, null);
+ }
+ ]]></handler>
+
+ <handler event="DOMMenuItemInactive">
+ <![CDATA[
+ XULBrowserWindow.setOverLink("", null);
+ ]]></handler>
+
+ <handler event="command"><![CDATA[
+ if (event.target.tab)
+ gBrowser.selectedTab = event.target.tab;
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="statuspanel" display="xul:hbox">
+ <content>
+ <xul:hbox class="statuspanel-inner">
+ <xul:label class="statuspanel-label"
+ role="status"
+ aria-live="off"
+ xbl:inherits="value=label,crop,mirror"
+ flex="1"
+ crop="end"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ window.addEventListener("resize", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ window.removeEventListener("resize", this, false);
+ MousePosTracker.removeListener(this);
+ ]]></destructor>
+
+ <property name="label">
+ <setter><![CDATA[
+ if (!this.label) {
+ this.removeAttribute("mirror");
+ this.removeAttribute("sizelimit");
+ }
+
+ this.style.minWidth = this.getAttribute("type") == "status" &&
+ this.getAttribute("previoustype") == "status"
+ ? getComputedStyle(this).width : "";
+
+ if (val) {
+ this.setAttribute("label", val);
+ this.removeAttribute("inactive");
+ this._calcMouseTargetRect();
+ MousePosTracker.addListener(this);
+ } else {
+ this.setAttribute("inactive", "true");
+ MousePosTracker.removeListener(this);
+ }
+
+ return val;
+ ]]></setter>
+ <getter>
+ return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
+ </getter>
+ </property>
+
+ <method name="getMouseTargetRect">
+ <body><![CDATA[
+ return this._mouseTargetRect;
+ ]]></body>
+ </method>
+
+ <method name="onMouseEnter">
+ <body>
+ this._mirror();
+ </body>
+ </method>
+
+ <method name="onMouseLeave">
+ <body>
+ this._mirror();
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if (!this.label)
+ return;
+
+ switch (event.type) {
+ case "resize":
+ this._calcMouseTargetRect();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_calcMouseTargetRect">
+ <body><![CDATA[
+ let alignRight = false;
+
+ if (getComputedStyle(document.documentElement).direction == "rtl")
+ alignRight = !alignRight;
+
+ let rect = this.getBoundingClientRect();
+ this._mouseTargetRect = {
+ top: rect.top,
+ bottom: rect.bottom,
+ left: alignRight ? window.innerWidth - rect.width : 0,
+ right: alignRight ? window.innerWidth : rect.width
+ };
+ ]]></body>
+ </method>
+
+ <method name="_mirror">
+ <body>
+ if (this.hasAttribute("mirror"))
+ this.removeAttribute("mirror");
+ else
+ this.setAttribute("mirror", "true");
+
+ if (!this.hasAttribute("sizelimit")) {
+ this.setAttribute("sizelimit", "true");
+ this._calcMouseTargetRect();
+ }
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/application/palemoon/base/content/test/general/audio.ogg b/application/palemoon/base/content/test/general/audio.ogg
new file mode 100644
index 000000000..7e6ef77ec
--- /dev/null
+++ b/application/palemoon/base/content/test/general/audio.ogg
Binary files differ
diff --git a/application/palemoon/base/content/urlbarBindings.xml b/application/palemoon/base/content/urlbarBindings.xml
new file mode 100644
index 000000000..c99819f0d
--- /dev/null
+++ b/application/palemoon/base/content/urlbarBindings.xml
@@ -0,0 +1,1969 @@
+<?xml version="1.0"?>
+
+# -*- Mode: HTML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!DOCTYPE bindings [
+<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
+%notificationDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+
+ <content sizetopopup="pref">
+ <xul:hbox anonid="textbox-container"
+ class="autocomplete-textbox-container urlbar-textbox-container"
+ flex="1" xbl:inherits="focused">
+ <children includes="image|deck|stack|box">
+ <xul:image class="autocomplete-icon" allowevents="true"/>
+ </children>
+ <xul:hbox anonid="textbox-input-box"
+ class="textbox-input-box urlbar-input-box"
+ flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
+ <children/>
+ <html:input anonid="input"
+ class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
+ allowevents="true"
+ xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
+ </xul:hbox>
+ <children includes="hbox"/>
+ </xul:hbox>
+ <xul:dropmarker anonid="historydropmarker"
+ class="autocomplete-history-dropmarker urlbar-history-dropmarker"
+ allowevents="true"
+ xbl:inherits="open,enablehistory,parentfocused=focused"/>
+ <xul:popupset anonid="popupset"
+ class="autocomplete-result-popupset"/>
+ <children includes="toolbarbutton"/>
+ </content>
+
+ <implementation implements="nsIObserver, nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch("browser.urlbar.");
+
+ this._prefs.addObserver("", this, false);
+ this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
+ this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
+ this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
+ this.timeout = this._prefs.getIntPref("delay");
+ this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
+ this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
+
+ this.inputField.controllers.insertControllerAt(0, this._copyCutController);
+ this.inputField.addEventListener("mousedown", this, false);
+ this.inputField.addEventListener("mousemove", this, false);
+ this.inputField.addEventListener("mouseout", this, false);
+ this.inputField.addEventListener("overflow", this, false);
+ this.inputField.addEventListener("underflow", this, false);
+
+ const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var textBox = document.getAnonymousElementByAttribute(this,
+ "anonid", "textbox-input-box");
+ var cxmenu = document.getAnonymousElementByAttribute(textBox,
+ "anonid", "input-box-contextmenu");
+ var pasteAndGo;
+ cxmenu.addEventListener("popupshowing", function() {
+ if (!pasteAndGo)
+ return;
+ var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+ var enabled = controller.isCommandEnabled("cmd_paste");
+ if (enabled)
+ pasteAndGo.removeAttribute("disabled");
+ else
+ pasteAndGo.setAttribute("disabled", "true");
+ }, false);
+
+ var insertLocation = cxmenu.firstChild;
+ while (insertLocation.nextSibling &&
+ insertLocation.getAttribute("cmd") != "cmd_paste")
+ insertLocation = insertLocation.nextSibling;
+ if (insertLocation) {
+ pasteAndGo = document.createElement("menuitem");
+ let label = Services.strings.createBundle("chrome://browser/locale/browser.properties").
+ GetStringFromName("pasteAndGo.label");
+ pasteAndGo.setAttribute("label", label);
+ pasteAndGo.setAttribute("anonid", "paste-and-go");
+ pasteAndGo.setAttribute("oncommand",
+ "gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
+ cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
+ }
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._prefs.removeObserver("", this);
+ this._prefs = null;
+ this.inputField.controllers.removeController(this._copyCutController);
+ this.inputField.removeEventListener("mousedown", this, false);
+ this.inputField.removeEventListener("mousemove", this, false);
+ this.inputField.removeEventListener("mouseout", this, false);
+ this.inputField.removeEventListener("overflow", this, false);
+ this.inputField.removeEventListener("underflow", this, false);
+ ]]></destructor>
+
+ <field name="_value">""</field>
+
+ <!--
+ onBeforeValueGet is called by the base-binding's .value getter.
+ It can return an object with a "value" property, to override the
+ return value of the getter.
+ -->
+ <method name="onBeforeValueGet">
+ <body><![CDATA[
+ if (this.hasAttribute("actiontype"))
+ return {value: this._value};
+ return null;
+ ]]></body>
+ </method>
+
+ <!--
+ onBeforeValueSet is called by the base-binding's .value setter.
+ It should return the value that the setter should use.
+ -->
+ <method name="onBeforeValueSet">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this._value = aValue;
+ var returnValue = aValue;
+ var action = this._parseActionUrl(aValue);
+
+ if (action) {
+ returnValue = action.param;
+ }
+
+ // Set the actiontype only if the user is not overriding actions.
+ if (action && this._noActionsKeys.size == 0) {
+ this.setAttribute("actiontype", action.type);
+ } else {
+ this.removeAttribute("actiontype");
+ }
+ return returnValue;
+ ]]></body>
+ </method>
+
+ <field name="_mayTrimURLs">true</field>
+ <method name="trimValue">
+ <parameter name="aURL"/>
+ <body><![CDATA[
+ // This method must not modify the given URL such that calling
+ // nsIURIFixup::createFixupURI with the result will produce a different URI.
+ return this._mayTrimURLs ? trimURL(aURL) : aURL;
+ ]]></body>
+ </method>
+
+ <field name="_formattingEnabled">true</field>
+ <method name="formatValue">
+ <body><![CDATA[
+ if (!this._formattingEnabled || this.focused)
+ return;
+
+ let controller = this.editor.selectionController;
+ let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+ selection.removeAllRanges();
+
+ let textNode = this.editor.rootElement.firstChild;
+ let value = textNode.textContent;
+
+ let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
+ if (protocol &&
+ ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
+ return;
+ let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
+ if (!matchedURL)
+ return;
+
+ let [, preDomain, domain] = matchedURL;
+ let baseDomain = domain;
+ let subDomain = "";
+ // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
+ if (domain[0] != "[") {
+ try {
+ baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
+ if (!domain.endsWith(baseDomain)) {
+ // getBaseDomainFromHost converts its resultant to ACE.
+ let IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ baseDomain = IDNService.convertACEtoUTF8(baseDomain);
+ }
+ } catch (e) {}
+ }
+ if (baseDomain != domain) {
+ subDomain = domain.slice(0, -baseDomain.length);
+ }
+
+ let rangeLength = preDomain.length + subDomain.length;
+ if (rangeLength) {
+ let range = document.createRange();
+ range.setStart(textNode, 0);
+ range.setEnd(textNode, rangeLength);
+ selection.addRange(range);
+ }
+
+ let startRest = preDomain.length + domain.length;
+ if (startRest < value.length) {
+ let range = document.createRange();
+ range.setStart(textNode, startRest);
+ range.setEnd(textNode, value.length);
+ selection.addRange(range);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_clearFormatting">
+ <body><![CDATA[
+ if (!this._formattingEnabled)
+ return;
+
+ let controller = this.editor.selectionController;
+ let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+ selection.removeAllRanges();
+ ]]></body>
+ </method>
+
+ <method name="handleRevert">
+ <body><![CDATA[
+ var isScrolling = this.popupOpen;
+
+ gBrowser.userTypedValue = null;
+
+ // don't revert to last valid url unless page is NOT loading
+ // and user is NOT key-scrolling through autocomplete list
+ if (!XULBrowserWindow.isBusy && !isScrolling) {
+ URLBarSetURI();
+
+ // If the value isn't empty and the urlbar has focus, select the value.
+ if (this.value && this.hasAttribute("focused"))
+ this.select();
+ }
+
+ // tell widget to revert to last typed text only if the user
+ // was scrolling when they hit escape
+ return !isScrolling;
+ ]]></body>
+ </method>
+
+ <method name="handleCommand">
+ <parameter name="aTriggeringEvent"/>
+ <body><![CDATA[
+ if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
+ return; // Do nothing for right clicks
+
+ var url = this.value;
+ var mayInheritPrincipal = false;
+ var postData = null;
+
+ var action = this._parseActionUrl(url);
+ if (action) {
+ url = action.param;
+ if (this.hasAttribute("actiontype")) {
+ if (action.type == "switchtab") {
+ this.handleRevert();
+ let prevTab = gBrowser.selectedTab;
+ if (switchToTabHavingURI(url) &&
+ isTabEmpty(prevTab))
+ gBrowser.removeTab(prevTab);
+ }
+ return;
+ }
+ }
+ else {
+ [url, postData, mayInheritPrincipal] = this._canonizeURL(aTriggeringEvent);
+ if (!url)
+ return;
+ }
+
+ this.value = url;
+ gBrowser.userTypedValue = url;
+ try {
+ addToUrlbarHistory(url);
+ } catch (ex) {
+ // Things may go wrong when adding url to session history,
+ // but don't let that interfere with the loading of the url.
+ Cu.reportError(ex);
+ }
+
+ function loadCurrent() {
+ let webnav = Ci.nsIWebNavigation;
+ let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
+ // inheriting the currently loaded document's principal, unless this
+ // URL is marked as safe to inherit (e.g. came from a bookmark
+ // keyword).
+ if (!mayInheritPrincipal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+ gBrowser.loadURIWithFlags(url, flags, null, null, postData);
+ }
+
+ // Focus the content area before triggering loads, since if the load
+ // occurs in a new tab, we want focus to be restored to the content
+ // area when the current tab is re-selected.
+ gBrowser.selectedBrowser.focus();
+
+ let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
+ let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
+
+ if (altEnter) {
+ // XXX This was added a long time ago, and I'm not sure why it is
+ // necessary. Alt+Enter's default action might cause a system beep,
+ // or something like that?
+ aTriggeringEvent.preventDefault();
+ aTriggeringEvent.stopPropagation();
+ }
+
+ // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
+ altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
+
+ if (isMouseEvent || altEnter) {
+ // Use the standard UI link behaviors for clicks or Alt+Enter
+ let where = "tab";
+ if (isMouseEvent)
+ where = whereToOpenLink(aTriggeringEvent, false, false);
+
+ if (where == "current") {
+ loadCurrent();
+ } else {
+ this.handleRevert();
+ let params = { allowThirdPartyFixup: true,
+ postData: postData,
+ initiatingDoc: document };
+ openUILinkIn(url, where, params);
+ }
+ } else {
+ loadCurrent();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_canonizeURL">
+ <parameter name="aTriggeringEvent"/>
+ <body><![CDATA[
+ var url = this.value;
+ if (!url)
+ return ["", null, false];
+
+ // Only add the suffix when the URL bar value isn't already "URL-like",
+ // and only if we get a keyboard event, to match user expectations.
+ if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
+ (aTriggeringEvent instanceof KeyEvent)) {
+#ifdef XP_MACOSX
+ let accel = aTriggeringEvent.metaKey;
+#else
+ let accel = aTriggeringEvent.ctrlKey;
+#endif
+ let shift = aTriggeringEvent.shiftKey;
+
+ let suffix = "";
+
+ switch (true) {
+ case (accel && shift):
+ suffix = ".org/";
+ break;
+ case (shift):
+ suffix = ".net/";
+ break;
+ case (accel):
+ try {
+ suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
+ if (suffix.charAt(suffix.length - 1) != "/")
+ suffix += "/";
+ } catch(e) {
+ suffix = ".com/";
+ }
+ break;
+ }
+
+ if (suffix) {
+ // trim leading/trailing spaces (bug 233205)
+ url = url.trim();
+
+ // Tack www. and suffix on. If user has appended directories, insert
+ // suffix before them (bug 279035). Be careful not to get two slashes.
+
+ let firstSlash = url.indexOf("/");
+
+ if (firstSlash >= 0) {
+ url = url.substring(0, firstSlash) + suffix +
+ url.substring(firstSlash + 1);
+ } else {
+ url = url + suffix;
+ }
+
+ url = "http://www." + url;
+ }
+ }
+
+ var postData = {};
+ var mayInheritPrincipal = { value: false };
+ url = getShortcutOrURI(url, postData, mayInheritPrincipal);
+
+ return [url, postData.value, mayInheritPrincipal.value];
+ ]]></body>
+ </method>
+
+ <field name="_contentIsCropped">false</field>
+
+ <method name="_initURLTooltip">
+ <body><![CDATA[
+ if (this.focused || !this._contentIsCropped)
+ return;
+ this.inputField.setAttribute("tooltiptext", this.value);
+ ]]></body>
+ </method>
+
+ <method name="_hideURLTooltip">
+ <body><![CDATA[
+ this.inputField.removeAttribute("tooltiptext");
+ ]]></body>
+ </method>
+
+ <method name="onDragOver">
+ <parameter name="aEvent"/>
+ <body>
+ var types = aEvent.dataTransfer.types;
+ if (types.contains("application/x-moz-file") ||
+ types.contains("text/x-moz-url") ||
+ types.contains("text/uri-list") ||
+ types.contains("text/unicode"))
+ aEvent.preventDefault();
+ </body>
+ </method>
+
+ <method name="onDrop">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let url = browserDragAndDrop.drop(aEvent, { })
+
+ // The URL bar automatically handles inputs with newline characters,
+ // so we can get away with treating text/x-moz-url flavours as text/plain.
+ if (url) {
+ aEvent.preventDefault();
+ this.value = url;
+ SetPageProxyState("invalid");
+ this.focus();
+ try {
+ urlSecurityCheck(url,
+ gBrowser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ } catch (ex) {
+ return;
+ }
+ this.handleCommand();
+ // Force not showing the dropped URI immediately.
+ gBrowser.userTypedValue = null;
+ URLBarSetURI();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getSelectedValueForClipboard">
+ <body><![CDATA[
+ // Grab the actual input field's value, not our value, which could include moz-action:
+ var inputVal = this.inputField.value;
+ var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
+
+ // If the selection doesn't start at the beginning or doesn't span the full domain or
+ // the URL bar is modified, nothing else to do here.
+ if (this.selectionStart > 0 || this.valueIsTyped)
+ return selectedVal;
+ // The selection doesn't span the full domain if it doesn't contain a slash and is
+ // followed by some character other than a slash.
+ if (!selectedVal.includes("/")) {
+ let remainder = inputVal.replace(selectedVal, "");
+ if (remainder != "" && remainder[0] != "/")
+ return selectedVal;
+ }
+
+ let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
+
+ let uri;
+ try {
+ uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
+ } catch (e) {}
+ if (!uri)
+ return selectedVal;
+
+ // Only copy exposable URIs
+ try {
+ uri = uriFixup.createExposableURI(uri);
+ } catch (ex) {}
+
+ // If the entire URL is selected, just use the actual loaded URI.
+ if (inputVal == selectedVal) {
+ // ... but only if isn't a javascript: or data: URI, since those
+ // are hard to read when encoded
+ if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
+ // Parentheses are known to confuse third-party applications (bug 458565).
+ selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
+ }
+
+ return selectedVal;
+ }
+
+ // Just the beginning of the URL is selected, check for a trimmed
+ // value
+ let spec = uri.spec;
+ let trimmedSpec = this.trimValue(spec);
+ if (spec != trimmedSpec) {
+ // Prepend the portion that trimValue removed from the beginning.
+ // This assumes trimValue will only truncate the URL at
+ // the beginning or end (or both).
+ let trimmedSegments = spec.split(trimmedSpec);
+ selectedVal = trimmedSegments[0] + selectedVal;
+ }
+
+ return selectedVal;
+ ]]></body>
+ </method>
+
+ <field name="_copyCutController"><![CDATA[
+ ({
+ urlbar: this,
+ doCommand: function(aCommand) {
+ var urlbar = this.urlbar;
+ var val = urlbar._getSelectedValueForClipboard();
+ if (!val)
+ return;
+
+ if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
+ let start = urlbar.selectionStart;
+ let end = urlbar.selectionEnd;
+ urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
+ urlbar.inputField.value.substring(end);
+ urlbar.selectionStart = urlbar.selectionEnd = start;
+ urlbar.removeAttribute("actiontype");
+ SetPageProxyState("invalid");
+ }
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(val, document);
+ },
+ supportsCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_copy":
+ case "cmd_cut":
+ return true;
+ }
+ return false;
+ },
+ isCommandEnabled: function(aCommand) {
+ return this.supportsCommand(aCommand) &&
+ (aCommand != "cmd_cut" || !this.urlbar.readOnly) &&
+ this.urlbar.selectionStart < this.urlbar.selectionEnd;
+ },
+ onEvent: function(aEventName) {}
+ })
+ ]]></field>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aTopic == "nsPref:changed") {
+ switch (aData) {
+ case "clickSelectsAll":
+ case "doubleClickSelectsAll":
+ this[aData] = this._prefs.getBoolPref(aData);
+ break;
+ case "autoFill":
+ this.completeDefaultIndex = this._prefs.getBoolPref(aData);
+ break;
+ case "delay":
+ this.timeout = this._prefs.getIntPref(aData);
+ break;
+ case "formatting.enabled":
+ this._formattingEnabled = this._prefs.getBoolPref(aData);
+ break;
+ case "trimURLs":
+ this._mayTrimURLs = this._prefs.getBoolPref(aData);
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "mousedown":
+ if (this.doubleClickSelectsAll &&
+ aEvent.button == 0 && aEvent.detail == 2) {
+ this.editor.selectAll();
+ aEvent.preventDefault();
+ }
+ break;
+ case "mousemove":
+ this._initURLTooltip();
+ break;
+ case "mouseout":
+ this._hideURLTooltip();
+ break;
+ case "overflow":
+ this._contentIsCropped = true;
+ break;
+ case "underflow":
+ this._contentIsCropped = false;
+ this._hideURLTooltip();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <property name="textValue"
+ onget="return this.value;">
+ <setter>
+ <![CDATA[
+ try {
+ val = losslessDecodeURI(makeURI(val));
+ } catch (ex) { }
+
+ // Trim popup selected values, but never trim results coming from
+ // autofill.
+ if (this.popup.selectedIndex == -1)
+ this._disableTrim = true;
+ this.value = val;
+ this._disableTrim = false;
+
+ // Completing a result should simulate the user typing the result, so
+ // fire an input event.
+ let evt = document.createEvent("UIEvents");
+ evt.initUIEvent("input", true, false, window, 0);
+ this.mIgnoreInput = true;
+ this.dispatchEvent(evt);
+ this.mIgnoreInput = false;
+
+ return this.value;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="_parseActionUrl">
+ <parameter name="aUrl"/>
+ <body><![CDATA[
+ if (!aUrl.startsWith("moz-action:"))
+ return null;
+
+ // url is in the format moz-action:ACTION,PARAM
+ let [, action, param] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
+ return {type: action, param: param};
+ ]]></body>
+ </method>
+
+ <field name="_noActionsKeys"><![CDATA[
+ new Set();
+ ]]></field>
+
+ <method name="_clearNoActions">
+ <parameter name="aURL"/>
+ <body><![CDATA[
+ this._noActionsKeys.clear();
+ this.popup.removeAttribute("noactions");
+ let action = this._parseActionUrl(this._value);
+ if (action)
+ this.setAttribute("actiontype", action.type);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keydown"><![CDATA[
+ if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
+ event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
+ this.popup.selectedIndex >= 0 &&
+ !this._noActionsKeys.has(event.keyCode)) {
+ if (this._noActionsKeys.size == 0) {
+ this.popup.setAttribute("noactions", "true");
+ this.removeAttribute("actiontype");
+ }
+ this._noActionsKeys.add(event.keyCode);
+ }
+ ]]></handler>
+
+ <handler event="keyup"><![CDATA[
+ if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
+ event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
+ this._noActionsKeys.has(event.keyCode)) {
+ this._noActionsKeys.delete(event.keyCode);
+ if (this._noActionsKeys.size == 0)
+ this._clearNoActions();
+ }
+ ]]></handler>
+
+ <handler event="blur"><![CDATA[
+ if (event.originalTarget != this.inputField)
+ return;
+ this._clearNoActions();
+ this.formatValue();
+ ]]></handler>
+
+ <handler event="dragstart" phase="capturing"><![CDATA[
+ // Drag only if the gesture starts from the input field.
+ if (event.originalTarget != this.inputField)
+ return;
+
+ // Drag only if the entire value is selected and it's a valid URI.
+ var isFullSelection = this.selectionStart == 0 &&
+ this.selectionEnd == this.textLength;
+ if (!isFullSelection ||
+ this.getAttribute("pageproxystate") != "valid")
+ return;
+
+ var urlString = content.location.href;
+ var title = content.document.title || urlString;
+ var htmlString = "<a href=\"" + urlString + "\">" + urlString + "</a>";
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", urlString + "\n" + title);
+ dt.setData("text/unicode", urlString);
+ dt.setData("text/html", htmlString);
+
+ dt.effectAllowed = "copyLink";
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="focus" phase="capturing"><![CDATA[
+ if (event.originalTarget != this.inputField)
+ return;
+ this._hideURLTooltip();
+ this._clearFormatting();
+ ]]></handler>
+
+ <handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
+ <handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
+ <handler event="select"><![CDATA[
+ if (!Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(Ci.nsIClipboard)
+ .supportsSelectionClipboard())
+ return;
+
+ // Check if this selection was actually user-generated, and exit if not
+ // to prevent copying the selection (e.g autofill) to clipboard/primary
+ if (!window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .isHandlingUserInput)
+ return;
+
+ var val = this._getSelectedValueForClipboard();
+ if (!val)
+ return;
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyStringToClipboard(val, Ci.nsIClipboard.kSelectionClipboard, document);
+ ]]></handler>
+ </handlers>
+
+ </binding>
+
+ <!-- Note: this binding is applied to the autocomplete popup used in the Search bar and in web page content -->
+ <binding id="browser-autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup">
+ <implementation>
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
+ // this method is defined on the base binding
+ this._openAutocompletePopup(aInput, aElement);
+ ]]></body>
+ </method>
+
+ <method name="onPopupClick">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ // Ignore all right-clicks
+ if (aEvent.button == 2)
+ return;
+
+ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+
+ // Check for unmodified left-click, and use default behavior
+ if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
+ !aEvent.altKey && !aEvent.metaKey) {
+ controller.handleEnter(true);
+ return;
+ }
+
+ // Check for middle-click or modified clicks on the search bar
+ var searchBar = BrowserSearch.searchBar;
+ if (searchBar && searchBar.textbox == this.mInput) {
+ // Handle search bar popup clicks
+ var search = controller.getValueAt(this.selectedIndex);
+
+ // close the autocomplete popup and revert the entered search term
+ this.closePopup();
+ controller.handleEscape();
+
+ // Fill in the search bar's value
+ searchBar.value = search;
+
+ // open the search results according to the clicking subtlety
+ var where = whereToOpenLink(aEvent, false, true);
+ searchBar.doSearch(search, where);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
+ <implementation>
+ <field name="_maxResults">0</field>
+
+ <field name="_bundle" readonly="true">
+ Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle("chrome://browser/locale/places/places.properties");
+ </field>
+
+ <property name="maxResults" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._maxResults) {
+ var prefService =
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ this._maxResults = prefService.getIntPref("browser.urlbar.maxRichResults");
+ }
+ return this._maxResults;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
+ // this method is defined on the base binding
+ this._openAutocompletePopup(aInput, aElement);
+ ]]></body>
+ </method>
+
+ <method name="onPopupClick">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // Ignore right-clicks
+ if (aEvent.button == 2)
+ return;
+
+ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+
+ // Check for unmodified left-click, and use default behavior
+ if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
+ !aEvent.altKey && !aEvent.metaKey) {
+ controller.handleEnter(true);
+ return;
+ }
+
+ // Check for middle-click or modified clicks on the URL bar
+ if (gURLBar && this.mInput == gURLBar) {
+ var url = controller.getValueAt(this.selectedIndex);
+
+ // close the autocomplete popup and revert the entered address
+ this.closePopup();
+ controller.handleEscape();
+
+ // Check if this is meant to be an action
+ let action = this.mInput._parseActionUrl(url);
+ if (action) {
+ if (action.type == "switchtab")
+ url = action.param;
+ else
+ return;
+ }
+
+ // respect the usual clicking subtleties
+ openUILink(url, aEvent);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="createResultLabel">
+ <parameter name="aTitle"/>
+ <parameter name="aUrl"/>
+ <parameter name="aType"/>
+ <body>
+ <![CDATA[
+ var label = aTitle + " " + aUrl;
+ // convert aType (ex: "ac-result-type-<aType>") to text to be spoke aloud
+ // by screen readers. convert "tag" and "bookmark" to the localized versions,
+ // but don't do anything for "favicon" (the default)
+ if (aType != "favicon") {
+ label += " " + this._bundle.GetStringFromName(aType + "ResultLabel");
+ }
+ return label;
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <content align="start">
+ <xul:image class="popup-notification-icon"
+ xbl:inherits="popupid,src=icon"/>
+ <xul:vbox flex="1">
+ <xul:description class="popup-notification-description addon-progress-description"
+ xbl:inherits="xbl:text=label"/>
+ <xul:spacer flex="1"/>
+ <xul:hbox align="center">
+ <xul:progressmeter anonid="progressmeter" flex="1" mode="undetermined" class="popup-progress-meter"/>
+ <xul:button anonid="cancel" class="popup-progress-cancel" oncommand="document.getBindingParent(this).cancel()"/>
+ </xul:hbox>
+ <xul:label anonid="progresstext" class="popup-progress-label"/>
+ <xul:hbox class="popup-notification-button-container"
+ pack="end" align="center">
+ <xul:button anonid="button"
+ class="popup-notification-menubutton"
+ type="menu-button"
+ xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
+ <xul:menupopup anonid="menupopup"
+ xbl:inherits="oncommand=menucommand">
+ <children/>
+ <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
+ label="&closeNotificationItem.label;"
+ xbl:inherits="oncommand=closeitemcommand"/>
+ </xul:menupopup>
+ </xul:button>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox pack="start">
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton close-icon popup-notification-closebutton tabbable"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <constructor><![CDATA[
+ this.cancelbtn.setAttribute("tooltiptext", gNavigatorBundle.getString("addonDownloadCancelTooltip"));
+
+ this.notification.options.installs.forEach(function(aInstall) {
+ aInstall.addListener(this);
+ }, this);
+
+ // Calling updateProgress can sometimes cause this notification to be
+ // removed in the middle of refreshing the notification panel which
+ // makes the panel get refreshed again. Just initialise to the
+ // undetermined state and then schedule a proper check at the next
+ // opportunity
+ this.setProgress(0, -1);
+ this._updateProgressTimeout = setTimeout(this.updateProgress.bind(this), 0);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this.destroy();
+ ]]></destructor>
+
+ <field name="progressmeter" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "progressmeter");
+ </field>
+ <field name="progresstext" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "progresstext");
+ </field>
+ <field name="cancelbtn" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "cancel");
+ </field>
+ <field name="DownloadUtils" readonly="true">
+ let utils = {};
+ Components.utils.import("resource://gre/modules/DownloadUtils.jsm", utils);
+ utils.DownloadUtils;
+ </field>
+
+ <method name="destroy">
+ <body><![CDATA[
+ this.notification.options.installs.forEach(function(aInstall) {
+ aInstall.removeListener(this);
+ }, this);
+ clearTimeout(this._updateProgressTimeout);
+ ]]></body>
+ </method>
+
+ <method name="setProgress">
+ <parameter name="aProgress"/>
+ <parameter name="aMaxProgress"/>
+ <body><![CDATA[
+ if (aMaxProgress == -1) {
+ this.progressmeter.mode = "undetermined";
+ }
+ else {
+ this.progressmeter.mode = "determined";
+ this.progressmeter.value = (aProgress * 100) / aMaxProgress;
+ }
+
+ let now = Date.now();
+
+ if (!this.notification.lastUpdate) {
+ this.notification.lastUpdate = now;
+ this.notification.lastProgress = aProgress;
+ return;
+ }
+
+ let delta = now - this.notification.lastUpdate;
+ if ((delta < 400) && (aProgress < aMaxProgress))
+ return;
+
+ delta /= 1000;
+
+ // This code is taken from nsDownloadManager.cpp
+ let speed = (aProgress - this.notification.lastProgress) / delta;
+ if (this.notification.speed)
+ speed = speed * 0.9 + this.notification.speed * 0.1;
+
+ this.notification.lastUpdate = now;
+ this.notification.lastProgress = aProgress;
+ this.notification.speed = speed;
+
+ let status = null;
+ [status, this.notification.last] = this.DownloadUtils.getDownloadStatus(aProgress, aMaxProgress, speed, this.notification.last);
+ this.progresstext.value = status;
+ ]]></body>
+ </method>
+
+ <method name="cancel">
+ <body><![CDATA[
+ // Cache these as cancelling the installs will remove this
+ // notification which will drop these references
+ let browser = this.notification.browser;
+ let contentWindow = this.notification.options.contentWindow;
+ let sourceURI = this.notification.options.sourceURI;
+
+ let installs = this.notification.options.installs;
+ installs.forEach(function(aInstall) {
+ try {
+ aInstall.cancel();
+ }
+ catch (e) {
+ // Cancel will throw if the download has already failed
+ }
+ }, this);
+
+ let anchorID = "addons-notification-icon";
+ let notificationID = "addon-install-cancelled";
+ let messageString = gNavigatorBundle.getString("addonDownloadCancelled");
+ messageString = PluralForm.get(installs.length, messageString);
+ let buttonText = gNavigatorBundle.getString("addonDownloadRestart");
+ buttonText = PluralForm.get(installs.length, buttonText);
+
+ let action = {
+ label: buttonText,
+ accessKey: gNavigatorBundle.getString("addonDownloadRestart.accessKey"),
+ callback: function() {
+ let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+ if (weblistener.onWebInstallRequested(contentWindow, sourceURI,
+ installs, installs.length)) {
+ installs.forEach(function(aInstall) {
+ aInstall.install();
+ });
+ }
+ }
+ };
+
+ PopupNotifications.show(browser, notificationID, messageString,
+ anchorID, action);
+ ]]></body>
+ </method>
+
+ <method name="updateProgress">
+ <body><![CDATA[
+ let downloadingCount = 0;
+ let progress = 0;
+ let maxProgress = 0;
+
+ this.notification.options.installs.forEach(function(aInstall) {
+ if (aInstall.maxProgress == -1)
+ maxProgress = -1;
+ progress += aInstall.progress;
+ if (maxProgress >= 0)
+ maxProgress += aInstall.maxProgress;
+ if (aInstall.state < AddonManager.STATE_DOWNLOADED)
+ downloadingCount++;
+ });
+
+ if (downloadingCount == 0) {
+ this.destroy();
+ PopupNotifications.remove(this.notification);
+ }
+ else {
+ this.setProgress(progress, maxProgress);
+ }
+ ]]></body>
+ </method>
+
+ <method name="onDownloadProgress">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadCancelled">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadEnded">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="plugin-popupnotification-center-item">
+ <content align="center">
+ <xul:vbox pack="center" anonid="itemBox" class="itemBox">
+ <xul:description anonid="center-item-label" class="center-item-label" />
+ <xul:hbox flex="1" pack="start" align="center" anonid="center-item-warning">
+ <xul:image anonid="center-item-warning-icon" class="center-item-warning-icon"/>
+ <xul:label anonid="center-item-warning-label"/>
+ <xul:label anonid="center-item-link" value="&checkForUpdates;" class="text-link"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox pack="center">
+ <xul:menulist class="center-item-menulist"
+ anonid="center-item-menulist">
+ <xul:menupopup>
+ <xul:menuitem anonid="allownow" value="allownow"
+ label="&pluginActivateNow.label;" />
+ <xul:menuitem anonid="allowalways" value="allowalways"
+ label="&pluginActivateAlways.label;" />
+ <xul:menuitem anonid="block" value="block"
+ label="&pluginBlockNow.label;" />
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <constructor><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-label").value = this.action.pluginName;
+
+ let curState = "block";
+ if (this.action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
+ if (this.action.pluginPermissionType == Ci.nsIPermissionManager.EXPIRE_SESSION) {
+ curState = "allownow";
+ }
+ else {
+ curState = "allowalways";
+ }
+ }
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").value = curState;
+
+ let warningString = "";
+ let linkString = "";
+
+ let link = document.getAnonymousElementByAttribute(this, "anonid", "center-item-link");
+
+ let url;
+ let linkHandler;
+
+ if (this.action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
+ warningString = gNavigatorBundle.getString("pluginActivateDisabled.label");
+ linkString = gNavigatorBundle.getString("pluginActivateDisabled.manage");
+ linkHandler = function(event) {
+ event.preventDefault();
+ gPluginHandler.managePlugins();
+ };
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-icon").hidden = true;
+ }
+ else {
+ url = this.action.detailsLink;
+
+ switch (this.action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning").hidden = true;
+ break;
+ case Ci.nsIBlocklistService.STATE_BLOCKED:
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
+ warningString = gNavigatorBundle.getString("pluginActivateBlocked.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.learnMore");
+ break;
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ warningString = gNavigatorBundle.getString("pluginActivateOutdated.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.updateLabel");
+ break;
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ warningString = gNavigatorBundle.getString("pluginActivateVulnerable.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.riskLabel");
+ break;
+ }
+ }
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-label").value = warningString;
+
+ if (url || linkHandler) {
+ link.value = linkString;
+ if (url) {
+ link.href = url;
+ }
+ if (linkHandler) {
+ link.addEventListener("click", linkHandler, false);
+ }
+ }
+ else {
+ link.hidden = true;
+ }
+ ]]></constructor>
+ <property name="value">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid",
+ "center-item-menulist").value;
+ </getter>
+ <setter><!-- This should be used only in automated tests -->
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "center-item-menulist").value = val;
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="click-to-play-plugins-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <content align="start" class="click-to-play-plugins-notification-content">
+ <xul:vbox flex="1" align="stretch" class="popup-notification-main-box"
+ xbl:inherits="popupid">
+ <xul:hbox class="click-to-play-plugins-notification-description-box" flex="1" align="start">
+ <xul:description class="click-to-play-plugins-outer-description" flex="1">
+ <html:span anonid="click-to-play-plugins-notification-description" />
+ <xul:label class="text-link click-to-play-plugins-notification-link" anonid="click-to-play-plugins-notification-link" />
+ </xul:description>
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton close-icon popup-notification-closebutton tabbable"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:hbox>
+ <xul:grid anonid="click-to-play-plugins-notification-center-box"
+ class="click-to-play-plugins-notification-center-box">
+ <xul:columns>
+ <xul:column flex="1"/>
+ <xul:column/>
+ </xul:columns>
+ <xul:rows>
+ <children includes="row"/>
+ <xul:hbox pack="start" anonid="plugin-notification-showbox">
+ <xul:button label="&pluginNotification.showAll.label;"
+ accesskey="&pluginNotification.showAll.accesskey;"
+ class="plugin-notification-showbutton"
+ oncommand="document.getBindingParent(this)._setState(2)"/>
+ </xul:hbox>
+ </xul:rows>
+ </xul:grid>
+ <xul:hbox anonid="button-container"
+ class="click-to-play-plugins-notification-button-container"
+ pack="center" align="center">
+ <xul:button anonid="primarybutton"
+ class="click-to-play-popup-button primary-button"
+ oncommand="document.getBindingParent(this)._onButton(this)"
+ flex="1"/>
+ <xul:button anonid="secondarybutton"
+ class="click-to-play-popup-button"
+ oncommand="document.getBindingParent(this)._onButton(this);"
+ flex="1"/>
+ </xul:hbox>
+ <xul:box hidden="true">
+ <children/>
+ </xul:box>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <field name="_states">
+ ({SINGLE: 0, MULTI_COLLAPSED: 1, MULTI_EXPANDED: 2})
+ </field>
+ <field name="_primaryButton">
+ document.getAnonymousElementByAttribute(this, "anonid", "primarybutton");
+ </field>
+ <field name="_secondaryButton">
+ document.getAnonymousElementByAttribute(this, "anonid", "secondarybutton")
+ </field>
+ <field name="_buttonContainer">
+ document.getAnonymousElementByAttribute(this, "anonid", "button-container")
+ </field>
+ <field name="_brandShortName">
+ document.getElementById("bundle_brand").getString("brandShortName")
+ </field>
+ <field name="_items">[]</field>
+ <constructor><![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ for (let action of this.notification.options.centerActions) {
+ let item = document.createElementNS(XUL_NS, "row");
+ item.setAttribute("class", "plugin-popupnotification-centeritem");
+ item.action = action;
+ this.appendChild(item);
+ this._items.push(item);
+ }
+ switch (this.notification.options.centerActions.length) {
+ case 0:
+ PopupNotifications._dismiss();
+ break;
+ case 1:
+ this._setState(this._states.SINGLE);
+ break;
+ default:
+ if (this.notification.options.primaryPlugin) {
+ this._setState(this._states.MULTI_COLLAPSED);
+ } else {
+ this._setState(this._states.MULTI_EXPANDED);
+ }
+ }
+ ]]></constructor>
+ <method name="_setState">
+ <parameter name="state" />
+ <body><![CDATA[
+ var grid = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-center-box");
+
+ if (this._states.SINGLE == state) {
+ grid.hidden = true;
+ this._setupSingleState();
+ return;
+ }
+
+ let host = gPluginHandler._getHostFromPrincipal(this.notification.browser.contentWindow.document.nodePrincipal);
+ this._setupDescription("pluginActivateMultiple.message", null, host);
+
+ var showBox = document.getAnonymousElementByAttribute(this, "anonid", "plugin-notification-showbox");
+
+ var dialogStrings = Services.strings.createBundle("chrome://global/locale/dialog.properties");
+ this._primaryButton.label = dialogStrings.GetStringFromName("button-accept");
+ this._primaryButton.setAttribute("default", "true");
+
+ this._secondaryButton.label = dialogStrings.GetStringFromName("button-cancel");
+ this._primaryButton.setAttribute("action", "_multiAccept");
+ this._secondaryButton.setAttribute("action", "_cancel");
+
+ grid.hidden = false;
+
+ if (this._states.MULTI_COLLAPSED == state) {
+ for (let child of this.childNodes) {
+ if (child.tagName != "row") {
+ continue;
+ }
+ child.hidden = this.notification.options.primaryPlugin !=
+ child.action.permissionString;
+ }
+ showBox.hidden = false;
+ }
+ else {
+ for (let child of this.childNodes) {
+ if (child.tagName != "row") {
+ continue;
+ }
+ child.hidden = false;
+ }
+ showBox.hidden = true;
+ }
+ this._setupLink(null);
+ ]]></body>
+ </method>
+ <method name="_setupSingleState">
+ <body><![CDATA[
+ var action = this.notification.options.centerActions[0];
+ var host = action.pluginPermissionHost;
+
+ let label, linkLabel, linkUrl, button1, button2;
+
+ if (action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
+ button1 = {
+ label: "pluginBlockNow.label",
+ accesskey: "pluginBlockNow.accesskey",
+ action: "_singleBlock"
+ };
+ button2 = {
+ label: "pluginContinue.label",
+ accesskey: "pluginContinue.accesskey",
+ action: "_singleContinue",
+ default: true
+ };
+ switch (action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ label = "pluginEnabled.message";
+ linkLabel = "pluginActivate.learnMore";
+ break;
+
+ case Ci.nsIBlocklistService.STATE_BLOCKED:
+ Cu.reportError(Error("Cannot happen!"));
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ label = "pluginEnabledOutdated.message";
+ linkLabel = "pluginActivate.updateLabel";
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ label = "pluginEnabledVulnerable.message";
+ linkLabel = "pluginActivate.riskLabel"
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected blocklist state"));
+ }
+ }
+ else if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
+ let linkElement =
+ document.getAnonymousElementByAttribute(
+ this, "anonid", "click-to-play-plugins-notification-link");
+ linkElement.textContent = gNavigatorBundle.getString("pluginActivateDisabled.manage");
+ linkElement.setAttribute("onclick", "gPluginHandler.managePlugins()");
+
+ let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ descElement.textContent = gNavigatorBundle.getFormattedString(
+ "pluginActivateDisabled.message", [action.pluginName, this._brandShortName]) + " ";
+ this._buttonContainer.hidden = true;
+ return;
+ }
+ else if (action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ descElement.textContent = gNavigatorBundle.getFormattedString(
+ "pluginActivateBlocked.message", [action.pluginName, this._brandShortName]) + " ";
+ this._setupLink("pluginActivate.learnMore", action.detailsLink);
+ this._buttonContainer.hidden = true;
+ return;
+ }
+ else {
+ button1 = {
+ label: "pluginActivateNow.label",
+ accesskey: "pluginActivateNow.accesskey",
+ action: "_singleActivateNow"
+ };
+ button2 = {
+ label: "pluginActivateAlways.label",
+ accesskey: "pluginActivateAlways.accesskey",
+ action: "_singleActivateAlways"
+ };
+ switch (action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ label = "pluginActivateNew.message";
+ linkLabel = "pluginActivate.learnMore";
+ button2.default = true;
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ label = "pluginActivateOutdated.message";
+ linkLabel = "pluginActivate.updateLabel";
+ button1.default = true;
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ label = "pluginActivateVulnerable.message";
+ linkLabel = "pluginActivate.riskLabel"
+ button1.default = true;
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected blocklist state"));
+ }
+ }
+ this._setupDescription(label, action.pluginName, host);
+ this._setupLink(linkLabel, action.detailsLink);
+
+ this._primaryButton.label = gNavigatorBundle.getString(button1.label);
+ this._primaryButton.accesskey = gNavigatorBundle.getString(button1.accesskey);
+ this._primaryButton.setAttribute("action", button1.action);
+
+ this._secondaryButton.label = gNavigatorBundle.getString(button2.label);
+ this._secondaryButton.accesskey = gNavigatorBundle.getString(button2.accesskey);
+ this._secondaryButton.setAttribute("action", button2.action);
+ if (button1.default) {
+ this._primaryButton.setAttribute("default", "true");
+ }
+ else if (button2.default) {
+ this._secondaryButton.setAttribute("default", "true");
+ }
+ ]]></body>
+ </method>
+ <method name="_setupDescription">
+ <parameter name="baseString" />
+ <parameter name="pluginName" /> <!-- null for the multiple-plugin case -->
+ <parameter name="host" />
+ <body><![CDATA[
+ var bsn = this._brandShortName;
+ var span = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ while (span.lastChild) {
+ span.removeChild(span.lastChild);
+ }
+
+ var args = ["__host__", this._brandShortName];
+ if (pluginName) {
+ args.unshift(pluginName);
+ }
+ var bases = gNavigatorBundle.getFormattedString(baseString, args).
+ split("__host__", 2);
+
+ span.appendChild(document.createTextNode(bases[0]));
+ var hostSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "em");
+ hostSpan.appendChild(document.createTextNode(host));
+ span.appendChild(hostSpan);
+ span.appendChild(document.createTextNode(bases[1] + " "));
+ ]]></body>
+ </method>
+ <method name="_setupLink">
+ <parameter name="linkString"/>
+ <parameter name="linkUrl" />
+ <body><![CDATA[
+ var link = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-link");
+ if (!linkString || !linkUrl) {
+ link.hidden = true;
+ return;
+ }
+
+ link.hidden = false;
+ link.textContent = gNavigatorBundle.getString(linkString);
+ link.href = linkUrl;
+ ]]></body>
+ </method>
+ <method name="_onButton">
+ <parameter name="aButton" />
+ <body><![CDATA[
+ let methodName = aButton.getAttribute("action");
+ this[methodName]();
+ ]]></body>
+ </method>
+ <method name="_singleActivateNow">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "allownow");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleBlock">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "block");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleActivateAlways">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "allowalways");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleContinue">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "continue");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_multiAccept">
+ <body><![CDATA[
+ for (let item of this._items) {
+ let action = item.action;
+ if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED ||
+ action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ continue;
+ }
+ gPluginHandler._updatePluginPermission(this.notification,
+ item.action, item.value);
+ }
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_cancel">
+ <body><![CDATA[
+ PopupNotifications._dismiss();
+ ]]></body>
+ </method>
+ <method name="_accept">
+ <parameter name="aEvent" />
+ <body><![CDATA[
+ if (aEvent.defaultPrevented)
+ return;
+ aEvent.preventDefault();
+ if (this._primaryButton.getAttribute("default") == "true") {
+ this._primaryButton.click();
+ }
+ else if (this._secondaryButton.getAttribute("default") == "true") {
+ this._secondaryButton.click();
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ <handlers>
+ <!-- The _accept method checks for .defaultPrevented so that if focus is in a button,
+ enter activates the button and not this default action -->
+ <handler event="keypress" keycode="VK_ENTER" group="system" action="this._accept(event);"/>
+ <handler event="keypress" keycode="VK_RETURN" group="system" action="this._accept(event);"/>
+ </handlers>
+ </binding>
+
+ <binding id="splitmenu">
+ <content>
+ <xul:hbox anonid="menuitem" flex="1"
+ class="splitmenu-menuitem"
+ xbl:inherits="iconic,label,disabled,onclick=oncommand,_moz-menuactive=active"/>
+ <xul:menu anonid="menu" class="splitmenu-menu"
+ xbl:inherits="disabled,_moz-menuactive=active"
+ oncommand="event.stopPropagation();">
+ <children includes="menupopup"/>
+ </xul:menu>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._parentMenupopup.addEventListener("DOMMenuItemActive", this, false);
+ this._parentMenupopup.addEventListener("popuphidden", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._parentMenupopup.removeEventListener("DOMMenuItemActive", this, false);
+ this._parentMenupopup.removeEventListener("popuphidden", this, false);
+ ]]></destructor>
+
+ <field name="menuitem" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "menuitem");
+ </field>
+ <field name="menu" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "menu");
+ </field>
+
+ <field name="_menuDelay">600</field>
+
+ <field name="_parentMenupopup"><![CDATA[
+ this._getParentMenupopup(this);
+ ]]></field>
+
+ <method name="_getParentMenupopup">
+ <parameter name="aNode"/>
+ <body><![CDATA[
+ let node = aNode.parentNode;
+ while (node) {
+ if (node.localName == "menupopup")
+ break;
+ node = node.parentNode;
+ }
+ return node;
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ switch (event.type) {
+ case "DOMMenuItemActive":
+ if (this.getAttribute("active") == "true" &&
+ event.target != this &&
+ this._getParentMenupopup(event.target) == this._parentMenupopup)
+ this.removeAttribute("active");
+ break;
+ case "popuphidden":
+ if (event.target == this._parentMenupopup)
+ this.removeAttribute("active");
+ break;
+ }
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ if (this.getAttribute("active") != "true") {
+ this.setAttribute("active", "true");
+
+ let event = document.createEvent("Events");
+ event.initEvent("DOMMenuItemActive", true, false);
+ this.dispatchEvent(event);
+
+ if (this.getAttribute("disabled") != "true") {
+ let self = this;
+ setTimeout(function () {
+ if (self.getAttribute("active") == "true")
+ self.menu.open = true;
+ }, this._menuDelay);
+ }
+ }
+ ]]></handler>
+
+ <handler event="popupshowing"><![CDATA[
+ if (event.target == this.firstChild &&
+ this._parentMenupopup._currentPopup)
+ this._parentMenupopup._currentPopup.hidePopup();
+ ]]></handler>
+
+ <handler event="click" phase="capturing"><![CDATA[
+ if (this.getAttribute("disabled") == "true") {
+ // Prevent the command from being carried out
+ event.stopPropagation();
+ return;
+ }
+
+ let node = event.originalTarget;
+ while (true) {
+ if (node == this.menuitem)
+ break;
+ if (node == this)
+ return;
+ node = node.parentNode;
+ }
+
+ this._parentMenupopup.hidePopup();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="menuitem-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem">
+ <implementation>
+ <constructor><![CDATA[
+ this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
+ // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
+ // 592424 is fixed
+ document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+ <binding id="menuitem-iconic-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem-iconic">
+ <implementation>
+ <constructor><![CDATA[
+ this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
+ // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
+ // 592424 is fixed
+ document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+ <binding id="promobox">
+ <content>
+ <xul:hbox class="panel-promo-box" align="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:image class="panel-promo-icon"/>
+ <xul:description anonid="promo-message" class="panel-promo-message" flex="1">
+ <xul:description anonid="promo-link"
+ class="plain text-link inline-link"
+ onclick="document.getBindingParent(this).onLinkClick();"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:toolbarbutton class="panel-promo-closebutton close-icon"
+ oncommand="document.getBindingParent(this).onCloseButtonCommand();"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._panel.addEventListener("popupshowing", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._panel.removeEventListener("popupshowing", this, false);
+ ]]></destructor>
+
+ <field name="_panel" readonly="true"><![CDATA[
+ let node = this.parentNode;
+ while(node && node.localName != "panel") {
+ node = node.parentNode;
+ }
+ node;
+ ]]></field>
+ <field name="_promomessage" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "promo-message");
+ </field>
+ <field name="_promolink" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "promo-link");
+ </field>
+ <field name="_brandBundle" readonly="true">
+ Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ </field>
+ <property name="_viewsLeftMap">
+ <getter><![CDATA[
+ let viewsLeftMap = {};
+ try {
+ viewsLeftMap = JSON.parse(Services.prefs.getCharPref("browser.syncPromoViewsLeftMap"));
+ } catch (ex) {
+ // If the old preference exists, migrate it to the new one.
+ try {
+ let oldPref = Services.prefs.getIntPref("browser.syncPromoViewsLeft");
+ Services.prefs.clearUserPref("browser.syncPromoViewsLeft");
+ viewsLeftMap.bookmarks = oldPref;
+ viewsLeftMap.passwords = oldPref;
+ Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
+ JSON.stringify(viewsLeftMap));
+ } catch (ex2) {}
+ }
+ return viewsLeftMap;
+ ]]></getter>
+ </property>
+ <property name="_viewsLeft">
+ <getter><![CDATA[
+ let views = 5;
+ let map = this._viewsLeftMap;
+ if (this._notificationType in map) {
+ views = map[this._notificationType];
+ }
+ return views;
+ ]]></getter>
+ <setter><![CDATA[
+ let map = this._viewsLeftMap;
+ map[this._notificationType] = val;
+ Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
+ JSON.stringify(map));
+ return val;
+ ]]></setter>
+ </property>
+ <property name="_notificationType">
+ <getter><![CDATA[
+ // Use the popupid attribute to identify the notification type,
+ // otherwise just rely on the panel id for common arrowpanels.
+ let type = this._panel.firstChild.getAttribute("popupid") ||
+ this._panel.id;
+ if (type.startsWith("password-"))
+ return "passwords";
+ if (type == "editBookmarkPanel")
+ return "bookmarks";
+ if (type == "addon-install-complete") {
+ if (!Services.prefs.prefHasUserValue("services.sync.username"))
+ return "addons";
+ if (!Services.prefs.getBoolPref("services.sync.engine.addons"))
+ return "addons-sync-disabled";
+ }
+ return null;
+ ]]></getter>
+ </property>
+ <property name="_notificationMessage">
+ <getter><![CDATA[
+ return gNavigatorBundle.getFormattedString(
+ "syncPromoNotification." + this._notificationType + ".description",
+ [this._brandBundle.GetStringFromName("syncBrandShortName")]
+ );
+ ]]></getter>
+ </property>
+ <property name="_notificationLink">
+ <getter><![CDATA[
+ if (this._notificationType == "addons-sync-disabled") {
+ return "https://forum.palemoon.org/viewforum.php?f=52";
+ }
+ return "http://www.palemoon.org/sync/";
+ ]]></getter>
+ </property>
+ <method name="onCloseButtonCommand">
+ <body><![CDATA[
+ this._viewsLeft = 0;
+ this.hidden = true;
+ ]]></body>
+ </method>
+ <method name="onLinkClick">
+ <body><![CDATA[
+ // Open a new selected tab and close the current panel.
+ gBrowser.loadOneTab(this._promolink.getAttribute("href"),
+ { inBackground: false });
+ this._panel.hidePopup();
+ ]]></body>
+ </method>
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if (event.type != "popupshowing" || event.target != this._panel)
+ return;
+
+ // A previous notification may have unhidden this.
+ this.hidden = true;
+
+ // Only handle supported notification panels.
+ if (!this._notificationType) {
+ return;
+ }
+
+ let viewsLeft = this._viewsLeft;
+ if (viewsLeft) {
+ // XXX: Short-circuit this for now.
+ // TO-DO: Clean up this code for sync promotion
+ this._viewsLeft = 0;
+ viewsLeft = 0;
+ return;
+ // XXX
+
+ if (Services.prefs.prefHasUserValue("services.sync.username") &&
+ this._notificationType != "addons-sync-disabled") {
+ // If the user has already setup Sync, don't show the notification.
+ this._viewsLeft = 0;
+ // Be sure to hide the panel, in case it was visible and the user
+ // decided to setup Sync after noticing it.
+ viewsLeft = 0;
+ // The panel is still hidden, just bail out.
+ return;
+ }
+ else {
+ this._viewsLeft = viewsLeft - 1;
+ }
+
+ this._promolink.setAttribute("href", this._notificationLink);
+ this._promolink.value = gNavigatorBundle.getString("syncPromoNotification.learnMoreLinkText");
+
+ this.hidden = false;
+
+ // HACK: The description element doesn't wrap correctly in panels,
+ // thus set a width on it, based on the available space, before
+ // setting its textContent. Then set its height as well, to
+ // fix wrong height calculation on Linux (bug 659578).
+ this._panel.addEventListener("popupshown", function panelShown() {
+ this._panel.removeEventListener("popupshown", panelShown, true);
+ // Previous popupShown events may close the panel or change
+ // its contents, so ensure this is still valid.
+ if (this._panel.state != "open" || !this._notificationType)
+ return;
+ this._promomessage.width = this._promomessage.getBoundingClientRect().width;
+ this._promomessage.firstChild.textContent = this._notificationMessage;
+ this._promomessage.height = this._promomessage.getBoundingClientRect().height;
+ }.bind(this), true);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="toolbarbutton-badged" display="xul:button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:hbox class="toolbarbutton-badge-container" align="start" pack="end" flex="1">
+ <xul:hbox class="toolbarbutton-badge" xbl:inherits="badge"/>
+ <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
+ </xul:hbox>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/application/palemoon/base/content/utilityOverlay.js b/application/palemoon/base/content/utilityOverlay.js
new file mode 100644
index 000000000..b1e78d6a9
--- /dev/null
+++ b/application/palemoon/base/content/utilityOverlay.js
@@ -0,0 +1,677 @@
+# -*- Mode: javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Components.utils.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "BROWSER_NEW_TAB_URL", function () {
+ const PREF = "browser.newtab.url";
+
+ function getNewTabPageURL() {
+ if (!Services.prefs.prefHasUserValue(PREF)) {
+ if (PrivateBrowsingUtils.isWindowPrivate(window) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing)
+ return "about:privatebrowsing";
+ }
+ return Services.prefs.getCharPref(PREF) || "about:blank";
+ }
+
+ function update() {
+ BROWSER_NEW_TAB_URL = getNewTabPageURL();
+ }
+
+ Services.prefs.addObserver(PREF, update, false);
+
+ addEventListener("unload", function onUnload() {
+ removeEventListener("unload", onUnload);
+ Services.prefs.removeObserver(PREF, update);
+ });
+
+ return getNewTabPageURL();
+});
+
+var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
+
+var gBidiUI = false;
+
+/**
+ * Determines whether the given url is considered a special URL for new tabs.
+ */
+function isBlankPageURL(aURL) {
+ // Pale Moon: Only make "about:blank", the logopage, or "about:newtab" be
+ // a "blank page" to fix focus issues.
+ // Original code: return aURL == "about:blank" || aURL == BROWSER_NEW_TAB_URL;
+ return aURL == "about:blank" || aURL == "about:newtab" || aURL == "about:logopage";
+}
+
+function getBrowserURL()
+{
+ return "chrome://browser/content/browser.xul";
+}
+
+function getTopWin(skipPopups) {
+ // If this is called in a browser window, use that window regardless of
+ // whether it's the frontmost window, since commands can be executed in
+ // background windows (bug 626148).
+ if (top.document.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ (!skipPopups || top.toolbar.visible))
+ return top;
+
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ return RecentWindow.getMostRecentBrowserWindow({private: isPrivate,
+ allowPopups: !skipPopups});
+}
+
+function openTopWin(url) {
+ /* deprecated */
+ openUILinkIn(url, "current");
+}
+
+function getBoolPref(prefname, def)
+{
+ try {
+ return Services.prefs.getBoolPref(prefname);
+ }
+ catch(er) {
+ return def;
+ }
+}
+
+/* openUILink handles clicks on UI elements that cause URLs to load.
+ *
+ * As the third argument, you may pass an object with the same properties as
+ * accepted by openUILinkIn, plus "ignoreButton" and "ignoreAlt".
+ */
+function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup,
+ aPostData, aReferrerURI) {
+ let params;
+
+ if (aIgnoreButton && typeof aIgnoreButton == "object") {
+ params = aIgnoreButton;
+
+ // don't forward "ignoreButton" and "ignoreAlt" to openUILinkIn
+ aIgnoreButton = params.ignoreButton;
+ aIgnoreAlt = params.ignoreAlt;
+ delete params.ignoreButton;
+ delete params.ignoreAlt;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI,
+ initiatingDoc: event ? event.target.ownerDocument : null
+ };
+ }
+
+ let where = whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
+ openUILinkIn(url, where, params);
+}
+
+
+/* whereToOpenLink() looks at an event to decide where to open a link.
+ *
+ * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter).
+ *
+ * On Windows, the modifiers are:
+ * Ctrl new tab, selected
+ * Shift new window
+ * Ctrl+Shift new tab, in background
+ * Alt save
+ *
+ * Middle-clicking is the same as Ctrl+clicking (it opens a new tab).
+ *
+ * Exceptions:
+ * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff.
+ * (Currently, the Alt isn't sent here at all for menu items, but that will change in bug 126189.)
+ * - Alt is hard to use in context menus, because pressing Alt closes the menu.
+ * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable".
+ * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click.
+ */
+function whereToOpenLink( e, ignoreButton, ignoreAlt )
+{
+ // This method must treat a null event like a left click without modifier keys (i.e.
+ // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 })
+ // for compatibility purposes.
+ if (!e)
+ return "current";
+
+ var shift = e.shiftKey;
+ var ctrl = e.ctrlKey;
+ var meta = e.metaKey;
+ var alt = e.altKey && !ignoreAlt;
+
+ // ignoreButton allows "middle-click paste" to use function without always opening in a new window.
+ var middle = !ignoreButton && e.button == 1;
+ var middleUsesTabs = getBoolPref("browser.tabs.opentabfor.middleclick", true);
+
+ // Don't do anything special with right-mouse clicks. They're probably clicks on context menu items.
+
+#ifdef XP_MACOSX
+ if (meta || (middle && middleUsesTabs))
+#else
+ if (ctrl || (middle && middleUsesTabs))
+#endif
+ return shift ? "tabshifted" : "tab";
+
+ if (alt && getBoolPref("browser.altClickSave", false))
+ return "save";
+
+ if (shift || (middle && !middleUsesTabs))
+ return "window";
+
+ return "current";
+}
+
+/* openUILinkIn opens a URL in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "current" current tab (if there aren't any browser windows, then in a new window instead)
+ * "tab" new tab (if there aren't any browser windows, then in a new window instead)
+ * "tabshifted" same as "tab" but in background if default is to select new tabs, and vice versa
+ * "window" new window
+ * "save" save to disk (with no filename hint!)
+ *
+ * aAllowThirdPartyFixup controls whether third party services such as Google's
+ * I Feel Lucky are allowed to interpret this URL. This parameter may be
+ * undefined, which is treated as false.
+ *
+ * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
+ * these properties:
+ * allowThirdPartyFixup (boolean)
+ * postData (nsIInputStream)
+ * referrerURI (nsIURI)
+ * relatedToCurrent (boolean)
+ */
+function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
+ var params;
+
+ if (arguments.length == 3 && typeof arguments[2] == "object") {
+ params = aAllowThirdPartyFixup;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI
+ };
+ }
+
+ params.fromChrome = true;
+
+ openLinkIn(url, where, params);
+}
+
+function openLinkIn(url, where, params) {
+ if (!where || !url)
+ return;
+
+ var aFromChrome = params.fromChrome;
+ var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ var aPostData = params.postData;
+ var aCharset = params.charset;
+ var aReferrerURI = params.referrerURI;
+ var aRelatedToCurrent = params.relatedToCurrent;
+ var aInBackground = params.inBackground;
+ var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
+ var aInitiatingDoc = params.initiatingDoc;
+ var aIsPrivate = params.private;
+ var sendReferrerURI = true;
+
+ if (where == "save") {
+ if (!aInitiatingDoc) {
+ Components.utils.reportError("openUILink/openLinkIn was called with " +
+ "where == 'save' but without initiatingDoc. See bug 814264.");
+ return;
+ }
+ saveURL(url, null, null, true, null, aReferrerURI, aInitiatingDoc);
+ return;
+ }
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var w = getTopWin();
+ if ((where == "tab" || where == "tabshifted") &&
+ w && !w.toolbar.visible) {
+ w = getTopWin(true);
+ aRelatedToCurrent = false;
+ }
+
+ if (!w || where == "window") {
+ // Strip referrer data when opening a new private window, to prevent
+ // regular browsing data from leaking into it.
+ if (aIsPrivate) {
+ sendReferrerURI = false;
+ }
+
+ var sa = Cc["@mozilla.org/supports-array;1"].
+ createInstance(Ci.nsISupportsArray);
+
+ var wuri = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ wuri.data = url;
+
+ let charset = null;
+ if (aCharset) {
+ charset = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ charset.data = "charset=" + aCharset;
+ }
+
+ var allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
+
+ sa.AppendElement(wuri);
+ sa.AppendElement(charset);
+ if (sendReferrerURI)
+ sa.AppendElement(aReferrerURI);
+ sa.AppendElement(aPostData);
+ sa.AppendElement(allowThirdPartyFixupSupports);
+
+ let features = "chrome,dialog=no,all";
+ if (aIsPrivate) {
+ features += ",private";
+ }
+
+ Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
+ return;
+ }
+
+ let loadInBackground = where == "current" ? false : aInBackground;
+ if (loadInBackground == null) {
+ loadInBackground = aFromChrome ?
+ false :
+ getBoolPref("browser.tabs.loadInBackground");
+ }
+
+ if (where == "current" && w.gBrowser.selectedTab.pinned) {
+ try {
+ let uriObj = Services.io.newURI(url, null, null);
+ if (!uriObj.schemeIs("javascript") &&
+ w.gBrowser.currentURI.host != uriObj.host) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ } catch (err) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ }
+
+ // Raise the target window before loading the URI, since loading it may
+ // result in a new frontmost window (e.g. "javascript:window.open('');").
+ w.focus();
+
+ switch (where) {
+ case "current":
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ if (aDisallowInheritPrincipal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+ w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData);
+ break;
+ case "tabshifted":
+ loadInBackground = !loadInBackground;
+ // fall through
+ case "tab":
+ let browser = w.gBrowser;
+ browser.loadOneTab(url, {
+ referrerURI: aReferrerURI,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: loadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ relatedToCurrent: aRelatedToCurrent});
+ break;
+ }
+
+ w.gBrowser.selectedBrowser.focus();
+
+ if (!loadInBackground && w.isBlankPageURL(url))
+ w.focusAndSelectUrlBar();
+}
+
+// Used as an onclick handler for UI elements with link-like behavior.
+// e.g. onclick="checkForMiddleClick(this, event);"
+function checkForMiddleClick(node, event) {
+ // We should be using the disabled property here instead of the attribute,
+ // but some elements that this function is used with don't support it (e.g.
+ // menuitem).
+ if (node.getAttribute("disabled") == "true")
+ return; // Do nothing
+
+ if (event.button == 1) {
+ /* Execute the node's oncommand or command.
+ *
+ * XXX: we should use node.oncommand(event) once bug 246720 is fixed.
+ */
+ var target = node.hasAttribute("oncommand") ? node :
+ node.ownerDocument.getElementById(node.getAttribute("command"));
+ var fn = new Function("event", target.getAttribute("oncommand"));
+ fn.call(target, event);
+
+ // If the middle-click was on part of a menu, close the menu.
+ // (Menus close automatically with left-click but not with middle-click.)
+ closeMenus(event.target);
+ }
+}
+
+// Closes all popups that are ancestors of the node.
+function closeMenus(node)
+{
+ if ("tagName" in node) {
+ if (node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ && (node.tagName == "menupopup" || node.tagName == "popup"))
+ node.hidePopup();
+
+ closeMenus(node.parentNode);
+ }
+}
+
+// Gather all descendent text under given document node.
+function gatherTextUnder ( root )
+{
+ var text = "";
+ var node = root.firstChild;
+ var depth = 1;
+ while ( node && depth > 0 ) {
+ // See if this node is text.
+ if ( node.nodeType == Node.TEXT_NODE ) {
+ // Add this text to our collection.
+ text += " " + node.data;
+ } else if ( node instanceof HTMLImageElement) {
+ // If it has an alt= attribute, use that.
+ var altText = node.getAttribute( "alt" );
+ if ( altText && altText != "" ) {
+ text = altText;
+ break;
+ }
+ }
+ // Find next node to test.
+ // First, see if this node has children.
+ if ( node.hasChildNodes() ) {
+ // Go to first child.
+ node = node.firstChild;
+ depth++;
+ } else {
+ // No children, try next sibling (or parent next sibling).
+ while ( depth > 0 && !node.nextSibling ) {
+ node = node.parentNode;
+ depth--;
+ }
+ if ( node.nextSibling ) {
+ node = node.nextSibling;
+ }
+ }
+ }
+ // Strip leading whitespace.
+ text = text.replace( /^\s+/, "" );
+ // Strip trailing whitespace.
+ text = text.replace( /\s+$/, "" );
+ // Compress remaining whitespace.
+ text = text.replace( /\s+/g, " " );
+ return text;
+}
+
+function getShellService()
+{
+ var shell = null;
+ try {
+ shell = Components.classes["@mozilla.org/browser/shell-service;1"]
+ .getService(Components.interfaces.nsIShellService);
+ } catch (e) {
+ }
+ return shell;
+}
+
+function isBidiEnabled() {
+ // first check the pref.
+ if (getBoolPref("bidi.browser.ui", false))
+ return true;
+
+ // if the pref isn't set, check for an RTL locale and force the pref to true
+ // if we find one.
+ var rv = false;
+
+ try {
+ var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
+ .getService(Components.interfaces.nsILocaleService);
+ var systemLocale = localeService.getSystemLocale().getCategory("NSILOCALE_CTYPE").substr(0,3);
+
+ switch (systemLocale) {
+ case "ar-":
+ case "he-":
+ case "fa-":
+ case "ur-":
+ case "syr":
+ rv = true;
+ Services.prefs.setBoolPref("bidi.browser.ui", true);
+ }
+ } catch (e) {}
+
+ return rv;
+}
+
+function openAboutDialog() {
+ var enumerator = Services.wm.getEnumerator("Browser:About");
+ while (enumerator.hasMoreElements()) {
+ // Only open one about window (Bug 599573)
+ let win = enumerator.getNext();
+ win.focus();
+ return;
+ }
+
+#ifdef XP_WIN
+ var features = "chrome,centerscreen,dependent";
+#elifdef XP_MACOSX
+ var features = "chrome,resizable=no,minimizable=no";
+#else
+ var features = "chrome,centerscreen,dependent,dialog=no";
+#endif
+ window.openDialog("chrome://browser/content/aboutDialog.xul", "", features);
+}
+
+function openPreferences(paneID, extraArgs)
+{
+ var instantApply = getBoolPref("browser.preferences.instantApply", false);
+ var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
+
+ var win = Services.wm.getMostRecentWindow("Browser:Preferences");
+ if (win) {
+ win.focus();
+ if (paneID) {
+ var pane = win.document.getElementById(paneID);
+ win.document.documentElement.showPane(pane);
+ }
+
+ if (extraArgs && extraArgs["advancedTab"]) {
+ var advancedPaneTabs = win.document.getElementById("advancedPrefs");
+ advancedPaneTabs.selectedTab = win.document.getElementById(extraArgs["advancedTab"]);
+ }
+
+ return win;
+ }
+
+ return openDialog("chrome://browser/content/preferences/preferences.xul",
+ "Preferences", features, paneID, extraArgs);
+}
+
+function openAdvancedPreferences(tabID)
+{
+ openPreferences("paneAdvanced", { "advancedTab" : tabID });
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openTroubleshootingPage()
+{
+ openUILinkIn("about:support", "tab");
+}
+
+/**
+ * Opens the feedback page for this version of the application.
+ */
+function openFeedbackPage()
+{
+ openUILinkIn(Services.prefs.getCharPref("browser.feedback.url"), "tab");
+}
+
+function buildHelpMenu()
+{
+ // Enable/disable the "Report Web Forgery" menu item.
+ if (typeof gSafeBrowsing != "undefined")
+ gSafeBrowsing.setReportPhishingMenu();
+}
+
+function isElementVisible(aElement)
+{
+ if (!aElement)
+ return false;
+
+ // If aElement or a direct or indirect parent is hidden or collapsed,
+ // height, width or both will be 0.
+ var bo = aElement.boxObject;
+ return (bo.height > 0 && bo.width > 0);
+}
+
+function makeURLAbsolute(aBase, aUrl)
+{
+ // Note: makeURI() will throw if aUri is not a valid URI
+ return makeURI(aUrl, null, makeURI(aBase)).spec;
+}
+
+
+/**
+ * openNewTabWith: opens a new tab with the given URL.
+ *
+ * @param aURL
+ * The URL to open (as a string).
+ * @param aDocument
+ * The document from which the URL came, or null. This is used to set the
+ * referrer header and to do a security check of whether the document is
+ * allowed to reference the URL. If null, there will be no referrer
+ * header and no security check.
+ * @param aPostData
+ * Form POST data, or null.
+ * @param aEvent
+ * The triggering event (for the purpose of determining whether to open
+ * in the background), or null.
+ * @param aAllowThirdPartyFixup
+ * If true, then we allow the URL text to be sent to third party services
+ * (e.g., Google's I Feel Lucky) for interpretation. This parameter may
+ * be undefined in which case it is treated as false.
+ * @param [optional] aReferrer
+ * If aDocument is null, then this will be used as the referrer.
+ * There will be no security check.
+ */
+function openNewTabWith(aURL, aDocument, aPostData, aEvent,
+ aAllowThirdPartyFixup, aReferrer) {
+ if (aDocument)
+ urlSecurityCheck(aURL, aDocument.nodePrincipal);
+
+ // As in openNewWindowWith(), we want to pass the charset of the
+ // current document over to a new tab.
+ var originCharset = aDocument && aDocument.characterSet;
+ if (!originCharset &&
+ document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = window.content.document.characterSet;
+
+ openLinkIn(aURL, aEvent && aEvent.shiftKey ? "tabshifted" : "tab",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aDocument ? aDocument.documentURIObject : aReferrer });
+}
+
+function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer) {
+ if (aDocument)
+ urlSecurityCheck(aURL, aDocument.nodePrincipal);
+
+ // if and only if the current window is a browser window and it has a
+ // document with a character set, then extract the current charset menu
+ // setting from the current document and use it to initialize the new browser
+ // window...
+ var originCharset = aDocument && aDocument.characterSet;
+ if (!originCharset &&
+ document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = window.content.document.characterSet;
+
+ openLinkIn(aURL, "window",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aDocument ? aDocument.documentURIObject : aReferrer });
+}
+
+/**
+ * isValidFeed: checks whether the given data represents a valid feed.
+ *
+ * @param aLink
+ * An object representing a feed with title, href and type.
+ * @param aPrincipal
+ * The principal of the document, used for security check.
+ * @param aIsFeed
+ * Whether this is already a known feed or not, if true only a security
+ * check will be performed.
+ */
+function isValidFeed(aLink, aPrincipal, aIsFeed)
+{
+ if (!aLink || !aPrincipal)
+ return false;
+
+ var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
+ if (!aIsFeed) {
+ aIsFeed = (type == "application/rss+xml" ||
+ type == "application/atom+xml");
+ }
+
+ if (aIsFeed) {
+ try {
+ urlSecurityCheck(aLink.href, aPrincipal,
+ Components.interfaces.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ return type || "application/rss+xml";
+ }
+ catch(ex) {
+ }
+ }
+
+ return null;
+}
+
+// aCalledFromModal is optional
+function openHelpLink(aHelpTopic, aCalledFromModal) {
+ var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter)
+ .formatURLPref("app.support.baseURL");
+ url += aHelpTopic;
+
+ var where = aCalledFromModal ? "window" : "tab";
+ openUILinkIn(url, where);
+}
+
+function openPrefsHelp() {
+ // non-instant apply prefwindows are usually modal, so we can't open in the topmost window,
+ // since its probably behind the window.
+ var instantApply = getBoolPref("browser.preferences.instantApply");
+
+ var helpTopic = document.getElementsByTagName("prefwindow")[0].currentPane.helpTopic;
+ openHelpLink(helpTopic, !instantApply);
+}
+
+function trimURL(aURL) {
+ // This function must not modify the given URL such that calling
+ // nsIURIFixup::createFixupURI with the result will produce a different URI.
+ return aURL /* remove single trailing slash for http/https/ftp URLs */
+ .replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
+ /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
+ .replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
+}
diff --git a/application/palemoon/base/content/viewSourceOverlay.xul b/application/palemoon/base/content/viewSourceOverlay.xul
new file mode 100644
index 000000000..8b40ddfd2
--- /dev/null
+++ b/application/palemoon/base/content/viewSourceOverlay.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+
+<overlay id="viewSourceOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="viewSource">
+ <commandset id="baseMenuCommandSet"/>
+ <keyset id="baseMenuKeyset"/>
+ <stringbundleset id="stringbundleset"/>
+</window>
+
+<menubar id="viewSource-main-menubar">
+#ifdef XP_MACOSX
+ <menu id="windowMenu"/>
+ <menupopup id="menu_ToolsPopup"/>
+#endif
+ <menu id="helpMenu"/>
+</menubar>
+
+</overlay>
diff --git a/application/palemoon/base/content/web-panels.js b/application/palemoon/base/content/web-panels.js
new file mode 100644
index 000000000..6e2bf5bc4
--- /dev/null
+++ b/application/palemoon/base/content/web-panels.js
@@ -0,0 +1,102 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const NS_ERROR_MODULE_NETWORK = 2152398848;
+const NS_NET_STATUS_READ_FROM = NS_ERROR_MODULE_NETWORK + 8;
+const NS_NET_STATUS_WROTE_TO = NS_ERROR_MODULE_NETWORK + 9;
+
+function getPanelBrowser()
+{
+ return document.getElementById("web-panels-browser");
+}
+
+var panelProgressListener = {
+ onProgressChange : function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (!aRequest)
+ return;
+
+ //ignore local/resource:/chrome: files
+ if (aStatus == NS_NET_STATUS_READ_FROM || aStatus == NS_NET_STATUS_WROTE_TO)
+ return;
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ window.parent.document.getElementById('sidebar-throbber').setAttribute("loading", "true");
+ }
+ else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ window.parent.document.getElementById('sidebar-throbber').removeAttribute("loading");
+ }
+ },
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags) {
+ UpdateBackForwardCommands(getPanelBrowser().webNavigation);
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) {
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState) {
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ }
+};
+
+var gLoadFired = false;
+function loadWebPanel(aURI) {
+ var panelBrowser = getPanelBrowser();
+ if (gLoadFired) {
+ panelBrowser.webNavigation
+ .loadURI(aURI, nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, null, null);
+ }
+ panelBrowser.setAttribute("cachedurl", aURI);
+}
+
+function load()
+{
+ var panelBrowser = getPanelBrowser();
+ panelBrowser.webProgress.addProgressListener(panelProgressListener,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ var cachedurl = panelBrowser.getAttribute("cachedurl")
+ if (cachedurl) {
+ panelBrowser.webNavigation
+ .loadURI(cachedurl, nsIWebNavigation.LOAD_FLAGS_NONE, null,
+ null, null);
+ }
+
+ gLoadFired = true;
+}
+
+function unload()
+{
+ getPanelBrowser().webProgress.removeProgressListener(panelProgressListener);
+}
+
+function PanelBrowserStop()
+{
+ getPanelBrowser().webNavigation.stop(nsIWebNavigation.STOP_ALL)
+}
+
+function PanelBrowserReload()
+{
+ getPanelBrowser().webNavigation
+ .sessionHistory
+ .QueryInterface(nsIWebNavigation)
+ .reload(nsIWebNavigation.LOAD_FLAGS_NONE);
+}
diff --git a/application/palemoon/base/content/web-panels.xul b/application/palemoon/base/content/web-panels.xul
new file mode 100644
index 000000000..ea1e2eba7
--- /dev/null
+++ b/application/palemoon/base/content/web-panels.xul
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE page [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd">
+%textcontextDTD;
+]>
+
+<page id="webpanels-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="load()" onunload="unload()">
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/browser.js"/>
+ <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
+ <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
+ <script type="application/javascript" src="chrome://browser/content/web-panels.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
+ </stringbundleset>
+
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="isFrameImage"/>
+ </broadcasterset>
+
+ <commandset id="mainCommandset">
+ <command id="Browser:Back"
+ oncommand="getPanelBrowser().webNavigation.goBack();"
+ disabled="true"/>
+ <command id="Browser:Forward"
+ oncommand="getPanelBrowser().webNavigation.goForward();"
+ disabled="true"/>
+ <command id="Browser:Stop" oncommand="PanelBrowserStop();"/>
+ <command id="Browser:Reload" oncommand="PanelBrowserReload();"/>
+ <command id="Browser:BackOrBackDuplicate"
+ oncommand="getPanelBrowser().webNavigation.goBack(event);"
+ disabled="true">
+ <observes element="Browser:Back" attribute="disabled"/>
+ </command>
+ <command id="Browser:ForwardOrForwardDuplicate"
+ oncommand="getPanelBrowser().webNavigation.goForward(event);"
+ disabled="true">
+ <observes element="Browser:Forward" attribute="disabled"/>
+ </command>
+ <command id="Browser:ReloadOrDuplicate"
+ oncommand="PanelBrowserReload(event)"
+ disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ </commandset>
+
+ <popupset id="mainPopupSet">
+ <tooltip id="aHTMLTooltip" page="true"/>
+ <menupopup id="contentAreaContextMenu" pagemenu="start"
+ onpopupshowing="if (event.target != this)
+ return true;
+ gContextMenu = new nsContextMenu(this, event.shiftKey);
+ if (gContextMenu.shouldDisplay)
+ document.popupNode = this.triggerNode;
+ return gContextMenu.shouldDisplay;"
+ onpopuphiding="if (event.target != this)
+ return;
+ gContextMenu.hiding();
+ gContextMenu = null;">
+#include browser-context.inc
+ </menupopup>
+ </popupset>
+
+ <commandset id="editMenuCommands"/>
+ <browser id="web-panels-browser" persist="cachedurl" type="content" flex="1"
+ context="contentAreaContextMenu" tooltip="aHTMLTooltip"
+ onclick="window.parent.contentAreaClick(event, true);"/>
+</page>
diff --git a/application/palemoon/base/content/win6BrowserOverlay.xul b/application/palemoon/base/content/win6BrowserOverlay.xul
new file mode 100644
index 000000000..a69e3f6bd
--- /dev/null
+++ b/application/palemoon/base/content/win6BrowserOverlay.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+
+<!-- -*- Mode: HTML -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="win6-browser-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <toolbar id="toolbar-menubar"
+ autohide="true"/>
+</overlay>