+
+
diff --git a/application/palemoon/base/content/abouthome/addons.png b/application/palemoon/base/content/abouthome/addons.png
new file mode 100644
index 0000000000..41519ce498
Binary files /dev/null and b/application/palemoon/base/content/abouthome/addons.png 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 0000000000..d4d04ee8ca
Binary files /dev/null and b/application/palemoon/base/content/abouthome/addons@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/bookmarks.png b/application/palemoon/base/content/abouthome/bookmarks.png
new file mode 100644
index 0000000000..5c7e194a61
Binary files /dev/null and b/application/palemoon/base/content/abouthome/bookmarks.png 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 0000000000..7ede00744c
Binary files /dev/null and b/application/palemoon/base/content/abouthome/bookmarks@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/downloads.png b/application/palemoon/base/content/abouthome/downloads.png
new file mode 100644
index 0000000000..3d4d10e7ab
Binary files /dev/null and b/application/palemoon/base/content/abouthome/downloads.png 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 0000000000..d384a22c6d
Binary files /dev/null and b/application/palemoon/base/content/abouthome/downloads@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/history.png b/application/palemoon/base/content/abouthome/history.png
new file mode 100644
index 0000000000..ae742b1aa8
Binary files /dev/null and b/application/palemoon/base/content/abouthome/history.png 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 0000000000..696902e7ca
Binary files /dev/null and b/application/palemoon/base/content/abouthome/history@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/noise.png b/application/palemoon/base/content/abouthome/noise.png
new file mode 100644
index 0000000000..3467cf4d47
Binary files /dev/null and b/application/palemoon/base/content/abouthome/noise.png 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 0000000000..ef593e6e14
Binary files /dev/null and b/application/palemoon/base/content/abouthome/restore-large.png 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 0000000000..d5c71d0b04
Binary files /dev/null and b/application/palemoon/base/content/abouthome/restore-large@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/restore.png b/application/palemoon/base/content/abouthome/restore.png
new file mode 100644
index 0000000000..5c3d6f4376
Binary files /dev/null and b/application/palemoon/base/content/abouthome/restore.png 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 0000000000..5acb630522
Binary files /dev/null and b/application/palemoon/base/content/abouthome/restore@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/settings.png b/application/palemoon/base/content/abouthome/settings.png
new file mode 100644
index 0000000000..4b0c309909
Binary files /dev/null and b/application/palemoon/base/content/abouthome/settings.png 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 0000000000..c77cb9a92c
Binary files /dev/null and b/application/palemoon/base/content/abouthome/settings@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/snippet1.png b/application/palemoon/base/content/abouthome/snippet1.png
new file mode 100644
index 0000000000..ce2ec55c23
Binary files /dev/null and b/application/palemoon/base/content/abouthome/snippet1.png 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 0000000000..f57cd0a827
Binary files /dev/null and b/application/palemoon/base/content/abouthome/snippet1@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/snippet2.png b/application/palemoon/base/content/abouthome/snippet2.png
new file mode 100644
index 0000000000..e0724fb6df
Binary files /dev/null and b/application/palemoon/base/content/abouthome/snippet2.png 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 0000000000..40577f52f6
Binary files /dev/null and b/application/palemoon/base/content/abouthome/snippet2@2x.png differ
diff --git a/application/palemoon/base/content/abouthome/sync.png b/application/palemoon/base/content/abouthome/sync.png
new file mode 100644
index 0000000000..11e40cc937
Binary files /dev/null and b/application/palemoon/base/content/abouthome/sync.png 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 0000000000..6354f5bf90
Binary files /dev/null and b/application/palemoon/base/content/abouthome/sync@2x.png differ
diff --git a/application/palemoon/base/content/autorecovery.js b/application/palemoon/base/content/autorecovery.js
new file mode 100644
index 0000000000..29ccaed3fe
--- /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 0000000000..866bdf288b
--- /dev/null
+++ b/application/palemoon/base/content/autorecovery.xul
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/application/palemoon/base/content/baseMenuOverlay.xul b/application/palemoon/base/content/baseMenuOverlay.xul
new file mode 100644
index 0000000000..c6c1b16dc7
--- /dev/null
+++ b/application/palemoon/base/content/baseMenuOverlay.xul
@@ -0,0 +1,100 @@
+
+
+# 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/.
+
+
+%brandDTD;
+
+%baseMenuOverlayDTD;
+]>
+
+
+
+
+#ifdef XP_MACOSX
+
+
+
+
+
+
+
+
+
+#include ../../../toolkit/content/macWindowMenu.inc
+#endif
+
+#ifdef XP_WIN
+
diff --git a/application/palemoon/base/content/blockedSite.xhtml b/application/palemoon/base/content/blockedSite.xhtml
new file mode 100644
index 0000000000..b56875eb6c
--- /dev/null
+++ b/application/palemoon/base/content/blockedSite.xhtml
@@ -0,0 +1,193 @@
+
+
+
+ %htmlDTD;
+
+ %globalDTD;
+
+ %brandDTD;
+
+ %blockedSiteDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
&safeb.blocked.phishingPage.title;
+
&safeb.blocked.malwarePage.title;
+
+
+
+
+
+
+
&safeb.blocked.phishingPage.shortDesc;
+
&safeb.blocked.malwarePage.shortDesc;
+
+
+
+
+
&safeb.blocked.phishingPage.longDesc;
+
&safeb.blocked.malwarePage.longDesc;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/palemoon/base/content/browser-addons.js b/application/palemoon/base/content/browser-addons.js
new file mode 100644
index 0000000000..7993a0c9c9
--- /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 0000000000..835bf22bcf
--- /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/.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef MOZ_DEVTOOLS
+
+
+
+
+
+
+
+
+#endif
+
+
+#ifdef MOZ_DEVTOOLS
+
+#endif
+
+
+
+#define ID_PREFIX appmenu_developer_
+#define OMIT_ACCESSKEYS
+#include browser-charsetmenu.inc
+#undef ID_PREFIX
+#undef OMIT_ACCESSKEYS
+
+
+
+
+#define ID_PREFIX appmenu_
+#define OMIT_ACCESSKEYS
+#include browser-charsetmenu.inc
+#undef ID_PREFIX
+#undef OMIT_ACCESSKEYS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef MOZ_SERVICES_SYNC
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef MOZ_SERVICES_SYNC
+
+
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/palemoon/base/content/browser-charsetmenu.inc b/application/palemoon/base/content/browser-charsetmenu.inc
new file mode 100644
index 0000000000..628de1341f
--- /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
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/palemoon/base/content/browser-context.inc b/application/palemoon/base/content/browser-context.inc
new file mode 100644
index 0000000000..f672ede615
--- /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/.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef CONTEXT_COPY_IMAGE_CONTENTS
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef MOZ_DEVTOOLS
+
+
+#endif
diff --git a/application/palemoon/base/content/browser-doctype.inc b/application/palemoon/base/content/browser-doctype.inc
new file mode 100644
index 0000000000..6ee6384b64
--- /dev/null
+++ b/application/palemoon/base/content/browser-doctype.inc
@@ -0,0 +1,19 @@
+
+%brandDTD;
+
+%browserDTD;
+
+%baseMenuDTD;
+
+%charsetDTD;
+
+%textcontextDTD;
+
+ %customizeToolbarDTD;
+
+%placesDTD;
+
+%aboutHomeDTD;
+]>
+
diff --git a/application/palemoon/base/content/browser-feeds.js b/application/palemoon/base/content/browser-feeds.js
new file mode 100644
index 0000000000..c678772694
--- /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 0000000000..b8a29199ee
--- /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 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 0000000000..0837bf7c22
--- /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 0000000000..d88f47c793
--- /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 0000000000..f818f5149e
--- /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/.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifndef XP_MACOSX
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#else
+ key="key_stop"/>
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#include browser-charsetmenu.inc
+
+#ifdef XP_MACOSX
+
+
+#else
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef MOZ_SERVICES_SYNC
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef MOZ_SERVICES_SYNC
+
+
+
+#endif
+
+
+
+#ifdef MOZ_DEVTOOLS
+
+
+
+
+
+
+
+
+#endif
+
+
+#ifdef MOZ_DEVTOOLS
+
+#endif
+
+
+
+
+
+
+
+
+
+
+#ifdef XP_MACOSX
+
+#endif
+
+
diff --git a/application/palemoon/base/content/browser-menudragging.js b/application/palemoon/base/content/browser-menudragging.js
new file mode 100644
index 0000000000..cf26b2ba42
--- /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 0000000000..f5cabe5f62
--- /dev/null
+++ b/application/palemoon/base/content/browser-menudragging.xul
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/palemoon/base/content/browser-places.js b/application/palemoon/base/content/browser-places.js
new file mode 100644
index 0000000000..cf9c28597f
--- /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 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 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 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 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 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 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 0000000000..769ac6d8af
--- /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
">
+
+
+
+
Check the address for typing errors such as
+ ww.example.com instead of
+ www.example.com
+
If you are unable to load any pages, check your computer's network
+ connection.
+
If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.
+
+">
+
+
+
+
Check the file name for capitalization or other typing errors.
+
Check to see if the file was moved, renamed or deleted.
+
+">
+
+
+
+&brandShortName; can't load this page for some reason.
+">
+
+
+
+
Web addresses are usually written like
+ http://www.example.com/
+
Make sure that you're using forward slashes (i.e.
+ /).
+
+">
+
+
+
+
+
+The requested document is not available in &brandShortName;'s cache.
As a security precaution, &brandShortName; does not automatically re-request sensitive documents.
Click Try Again to re-request the document from the website.
">
+
+
+
+
Press "Try Again" to switch to online mode and reload the page.
+
+">
+
+
+
+
Please contact the website owners to inform them of this problem.
+
+">
+
+
+
+
Please contact the website owners to inform them of this problem.
+
+">
+
+
+
+
+
+
+
+
+
+
You might need to install other software to open this address.
+
+">
+
+
+
+
Check the proxy settings to make sure that they are correct.
+
Contact your network administrator to make sure the proxy server is
+ working.
+
+">
+
+
+
+
Check the proxy settings to make sure that they are correct.
+
Check to make sure your computer has a working network connection.
+
If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.
+
+">
+
+
+
+
This problem can sometimes be caused by disabling or refusing to accept
+ cookies.
+
+">
+
+
+
+
Check to make sure your system has the Personal Security Manager
+ installed.
+
This might be due to a non-standard configuration on the server.
+
+">
+
+
+
+
The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
+
Please contact the website owners to inform them of this problem.
+
+">
+
+
+
+
This could be a problem with the server's configuration, or it could be
+someone trying to impersonate the server.
+
If you have connected to this server successfully in the past, the error may
+be temporary, and you can try again later.
+
+">
+
+
+
The site could be temporarily unavailable or too busy. Try again in a few
+ moments.
+
If you are unable to load any pages, check your computer's network
+ connection.
+
If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.
+
+">
+
+
+Attack sites try to install programs that steal private information, use your computer to attack others, or damage your system.
+
Website owners who believe their site has been reported as an attack site in error may request a review.
+">
+
+
+Entering any personal information on this page may result in identity theft or other fraud.
+
These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.
+">
+
+
+&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.">
+
+
+
+
&brandShortName; blocked further actions on this page, because it contains
+ injected JavaScript code.
+
Loading of this page has been suspended because of either the explicit request
+ by the website to block this page in case of XSS attacks, or because &brandShortName;
+ has been configured to block pages in that situation.
+
+">
+
+
+The page you are trying to view cannot be shown because an error in the data transmission was detected.
Please contact the website owners to inform them of this problem.
">
+
+
+
+
+
+
+
+
+You should not add an exception if you are using an internet connection that you do not trust completely or if you are not used to seeing a warning for this server.
+
+
+
+">
+
+
+ will help us identify and block malicious sites. Thanks for helping create a safer web!">
+
+
+
+
+
+
+
+
+
Please contact the website owners to inform them of this problem.
">
+
+
+
+
+
diff --git a/application/palemoon/locales/en-US/chrome/overrides/settingsChange.dtd b/application/palemoon/locales/en-US/chrome/overrides/settingsChange.dtd
new file mode 100644
index 0000000000..efbdf4e46f
--- /dev/null
+++ b/application/palemoon/locales/en-US/chrome/overrides/settingsChange.dtd
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/application/palemoon/locales/en-US/crashreporter/crashreporter-override.ini b/application/palemoon/locales/en-US/crashreporter/crashreporter-override.ini
new file mode 100644
index 0000000000..3345d76810
--- /dev/null
+++ b/application/palemoon/locales/en-US/crashreporter/crashreporter-override.ini
@@ -0,0 +1,9 @@
+; 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/.
+
+# This file is in the UTF-8 encoding
+[Strings]
+# LOCALIZATION NOTE (CrashReporterProductErrorText2): The %s is replaced with a string containing detailed information.
+CrashReporterProductErrorText2=Firefox had a problem and crashed. We'll try to restore your tabs and windows when it restarts.\n\nUnfortunately the crash reporter is unable to submit a crash report.\n\nDetails: %s
+CrashReporterDescriptionText2=Firefox had a problem and crashed. We'll try to restore your tabs and windows when it restarts.\n\nTo help us diagnose and fix the problem, you can send us a crash report.
diff --git a/application/palemoon/locales/en-US/defines.inc b/application/palemoon/locales/en-US/defines.inc
new file mode 100644
index 0000000000..539b8096c4
--- /dev/null
+++ b/application/palemoon/locales/en-US/defines.inc
@@ -0,0 +1,12 @@
+# 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 emptyLines
+
+#define MOZ_LANGPACK_CREATOR palemoon.org
+
+# If non-English locales wish to credit multiple contributors, uncomment this
+# variable definition and use the format specified.
+# #define MOZ_LANGPACK_CONTRIBUTORS Joe SolonSuzy Solon
+
+#unfilter emptyLines
diff --git a/application/palemoon/locales/en-US/installer/custom.properties b/application/palemoon/locales/en-US/installer/custom.properties
new file mode 100644
index 0000000000..ef29b1e7e6
--- /dev/null
+++ b/application/palemoon/locales/en-US/installer/custom.properties
@@ -0,0 +1,83 @@
+# 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/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+REG_APP_DESC=$BrandShortName delivers safe, easy web browsing. A familiar user interface, enhanced security features including protection from online identity theft, and integrated search let you get the most out of the web.
+CONTEXT_OPTIONS=$BrandShortName &Options
+CONTEXT_SAFE_MODE=$BrandShortName &Safe Mode
+OPTIONS_PAGE_TITLE=Setup Type
+OPTIONS_PAGE_SUBTITLE=Choose setup options
+SHORTCUTS_PAGE_TITLE=Set Up Shortcuts
+SHORTCUTS_PAGE_SUBTITLE=Create Program Icons
+COMPONENTS_PAGE_TITLE=Set Up Optional Components
+COMPONENTS_PAGE_SUBTITLE=Optional Recommended Components
+SUMMARY_PAGE_TITLE=Summary
+SUMMARY_PAGE_SUBTITLE=Ready to start installing $BrandShortName
+SUMMARY_INSTALLED_TO=$BrandShortName will be installed to the following location:
+SUMMARY_REBOOT_REQUIRED_INSTALL=A restart of your computer may be required to complete the installation.
+SUMMARY_REBOOT_REQUIRED_UNINSTALL=A restart of your computer may be required to complete the uninstall.
+SUMMARY_TAKE_DEFAULTS=U&se $BrandShortName as my default web browser
+SUMMARY_INSTALL_CLICK=Click Install to continue.
+SUMMARY_UPGRADE_CLICK=Click Upgrade to continue.
+SURVEY_TEXT=&Tell us what you thought of $BrandShortName
+LAUNCH_TEXT=&Launch $BrandShortName now
+CREATE_ICONS_DESC=Create icons for $BrandShortName:
+ICONS_DESKTOP=On my &Desktop
+ICONS_STARTMENU=In my &Start Menu Programs folder
+ICONS_QUICKLAUNCH=In my &Quick Launch bar
+WARN_MANUALLY_CLOSE_APP_INSTALL=$BrandShortName must be closed to proceed with the installation.\n\nPlease close $BrandShortName to continue.
+WARN_MANUALLY_CLOSE_APP_UNINSTALL=$BrandShortName must be closed to proceed with the uninstall.\n\nPlease close $BrandShortName to continue.
+WARN_MANUALLY_CLOSE_APP_LAUNCH=$BrandShortName is already running.\n\nPlease close $BrandShortName prior to launching the version you have just installed.
+WARN_WRITE_ACCESS=You don't have access to write to the installation directory.\n\nClick OK to select a different directory.
+WARN_DISK_SPACE=You don't have sufficient disk space to install to this location.\n\nClick OK to select a different location.
+WARN_MIN_SUPPORTED_OS_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer.
+WARN_RESTART_REQUIRED_UNINSTALL=Your computer must be restarted to complete a previous uninstall of $BrandShortName. Do you want to reboot now?
+WARN_RESTART_REQUIRED_UPGRADE=Your computer must be restarted to complete a previous upgrade of $BrandShortName. Do you want to reboot now?
+ERROR_CREATE_DIRECTORY_PREFIX=Error creating directory:
+ERROR_CREATE_DIRECTORY_SUFFIX=Click Cancel to stop the installation or\nRetry to try again.
+
+UN_CONFIRM_PAGE_TITLE=Uninstall $BrandFullName
+UN_CONFIRM_PAGE_SUBTITLE=Remove $BrandFullName from your computer.
+UN_CONFIRM_UNINSTALLED_FROM=$BrandShortName will be uninstalled from the following location:
+UN_CONFIRM_CLICK=Click Uninstall to continue.
+UN_REMOVE_PROFILES=&Remove my $BrandShortName personal data and customizations
+UN_REMOVE_PROFILES_DESC=This will permanently remove your bookmarks, saved passwords, cookies and customizations. You may wish to keep this information if you plan on installing another version of $BrandShortName in the future.
+
+BANNER_CHECK_EXISTING=Checking existing installation…
+
+STATUS_INSTALL_APP=Installing $BrandShortName…
+STATUS_INSTALL_LANG=Installing Language Files (${AB_CD})…
+STATUS_UNINSTALL_MAIN=Uninstalling $BrandShortName…
+STATUS_CLEANUP=A Little Housekeeping…
+
+# _DESC strings support approximately 65 characters per line.
+# One line
+OPTIONS_SUMMARY=Choose the type of setup you prefer, then click Next.
+# One line
+OPTION_STANDARD_DESC=$BrandShortName will be installed with the most common options.
+OPTION_STANDARD_RADIO=&Standard
+# Two lines
+OPTION_CUSTOM_DESC=You may choose individual options to be installed. Recommended for experienced users.
+OPTION_CUSTOM_RADIO=&Custom
+
+# LOCALIZATION NOTE:
+# The following text replaces the Install button text on the summary page.
+# Verify that the access key for InstallBtn (in override.properties) and
+# UPGRADE_BUTTON is not already used by SUMMARY_TAKE_DEFAULTS.
+UPGRADE_BUTTON=&Upgrade
diff --git a/application/palemoon/locales/en-US/installer/mui.properties b/application/palemoon/locales/en-US/installer/mui.properties
new file mode 100644
index 0000000000..c786dbba25
--- /dev/null
+++ b/application/palemoon/locales/en-US/installer/mui.properties
@@ -0,0 +1,61 @@
+# 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/.
+
+# To make the l10n tinderboxen see changes to this file you can change a value
+# name by adding - to the end of the name followed by chars (e.g. Branding-2).
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+MUI_TEXT_WELCOME_INFO_TITLE=Welcome to the $BrandFullNameDA Setup Wizard
+MUI_TEXT_WELCOME_INFO_TEXT=This wizard will guide you through the installation of $BrandFullNameDA.\n\nIt is recommended that you close all other applications before starting Setup. This will make it possible to update relevant system files without having to reboot your computer.\n\n$_CLICK
+MUI_TEXT_COMPONENTS_TITLE=Choose Components
+MUI_TEXT_COMPONENTS_SUBTITLE=Choose which features of $BrandFullNameDA you want to install.
+MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE=Description
+MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO=Position your mouse over a component to see its description.
+MUI_TEXT_DIRECTORY_TITLE=Choose Install Location
+MUI_TEXT_DIRECTORY_SUBTITLE=Choose the folder in which to install $BrandFullNameDA.
+MUI_TEXT_INSTALLING_TITLE=Installing
+MUI_TEXT_INSTALLING_SUBTITLE=Please wait while $BrandFullNameDA is being installed.
+MUI_TEXT_FINISH_TITLE=Installation Complete
+MUI_TEXT_FINISH_SUBTITLE=Setup was completed successfully.
+MUI_TEXT_ABORT_TITLE=Installation Aborted
+MUI_TEXT_ABORT_SUBTITLE=Setup was not completed successfully.
+MUI_BUTTONTEXT_FINISH=&Finish
+MUI_TEXT_FINISH_INFO_TITLE=Completing the $BrandFullNameDA Setup Wizard
+MUI_TEXT_FINISH_INFO_TEXT=$BrandFullNameDA has been installed on your computer.\n\nClick Finish to close this wizard.
+MUI_TEXT_FINISH_INFO_REBOOT=Your computer must be restarted in order to complete the installation of $BrandFullNameDA. Do you want to reboot now?
+MUI_TEXT_FINISH_REBOOTNOW=Reboot now
+MUI_TEXT_FINISH_REBOOTLATER=I want to manually reboot later
+MUI_TEXT_STARTMENU_TITLE=Choose Start Menu Folder
+MUI_TEXT_STARTMENU_SUBTITLE=Choose a Start Menu folder for the $BrandFullNameDA shortcuts.
+MUI_INNERTEXT_STARTMENU_TOP=Select the Start Menu folder in which you would like to create the program's shortcuts. You can also enter a name to create a new folder.
+MUI_TEXT_ABORTWARNING=Are you sure you want to quit $BrandFullName Setup?
+MUI_UNTEXT_WELCOME_INFO_TITLE=Welcome to the $BrandFullNameDA Uninstall Wizard
+MUI_UNTEXT_WELCOME_INFO_TEXT=This wizard will guide you through the uninstallation of $BrandFullNameDA.\n\nBefore starting the uninstallation, make sure $BrandFullNameDA is not running.\n\n$_CLICK
+MUI_UNTEXT_CONFIRM_TITLE=Uninstall $BrandFullNameDA
+MUI_UNTEXT_CONFIRM_SUBTITLE=Remove $BrandFullNameDA from your computer.
+MUI_UNTEXT_UNINSTALLING_TITLE=Uninstalling
+MUI_UNTEXT_UNINSTALLING_SUBTITLE=Please wait while $BrandFullNameDA is being uninstalled.
+MUI_UNTEXT_FINISH_TITLE=Uninstallation Complete
+MUI_UNTEXT_FINISH_SUBTITLE=Uninstall was completed successfully.
+MUI_UNTEXT_ABORT_TITLE=Uninstallation Aborted
+MUI_UNTEXT_ABORT_SUBTITLE=Uninstall was not completed successfully.
+MUI_UNTEXT_FINISH_INFO_TITLE=Completing the $BrandFullNameDA Uninstall Wizard
+MUI_UNTEXT_FINISH_INFO_TEXT=$BrandFullNameDA has been uninstalled from your computer.\n\nClick Finish to close this wizard.
+MUI_UNTEXT_FINISH_INFO_REBOOT=Your computer must be restarted in order to complete the uninstallation of $BrandFullNameDA. Do you want to reboot now?
+MUI_UNTEXT_ABORTWARNING=Are you sure you want to quit $BrandFullName Uninstall?
diff --git a/application/palemoon/locales/en-US/installer/nsisstrings.properties b/application/palemoon/locales/en-US/installer/nsisstrings.properties
new file mode 100644
index 0000000000..0144c2a98f
--- /dev/null
+++ b/application/palemoon/locales/en-US/installer/nsisstrings.properties
@@ -0,0 +1,64 @@
+# 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/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+WIN_CAPTION=$BrandShortName Setup
+
+INTRO_BLURB1=Thanks for choosing $BrandFullName, the browser that chooses you above everything else.
+INSTALL_BLURB1=You're about to enjoy the very latest in speed, flexibility and security so you're always in control.
+INSTALL_BLURB2=That's because $BrandShortName is made by a non-profit to make browsing and the Web better for you.
+INSTALL_BLURB3=You're also joining a global community of users, contributors and developers working to make the best browser in the world.
+
+WARN_MIN_SUPPORTED_OS_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer.
+WARN_WRITE_ACCESS=You don't have access to write to the installation directory.\n\nClick OK to select a different directory.
+WARN_DISK_SPACE=You don't have sufficient disk space to install to this location.\n\nClick OK to select a different location.
+WARN_ROOT_INSTALL=Unable to install to the root of your disk.\n\nClick OK to select a different location.
+WARN_MANUALLY_CLOSE_APP_LAUNCH=$BrandShortName is already running.\n\nPlease close $BrandShortName prior to launching the version you have just installed.
+
+ERROR_DOWNLOAD=Your download was interrupted.\n\nPlease click the OK button to continue.
+
+INSTALL_BUTTON=&Install
+UPGRADE_BUTTON=&Upgrade
+CANCEL_BUTTON=Cancel
+OPTIONS_BUTTON=&Options
+
+MAKE_DEFAULT=&Make $BrandShortName my default browser
+CREATE_SHORTCUTS=Create Shortcuts for $BrandShortName:
+ADD_SC_TASKBAR=On my &Task bar
+ADD_SC_QUICKLAUNCHBAR=On my &Quick Launch bar
+ADD_CheckboxShortcutInStartMenu=In my &Start Menu Programs Folder
+ADD_CheckboxShortcutOnDesktop=On my &Desktop
+SPACE_REQUIRED=Space Required:
+SPACE_AVAILABLE=Space Available:
+ONE_MOMENT=One moment, $BrandShortName will launch as soon as the install is complete…
+SEND_PING=S&end information about this installation to Mozilla
+BROWSE_BUTTON=B&rowse…
+DEST_FOLDER=Destination Folder
+
+DOWNLOADING_IN_PROGRESS=Downloading…
+DOWNLOADING_DONE=Downloaded
+INSTALLING_TO_BE_DONE=Installing
+INSTALLING_IN_PROGRESS=Installing…
+
+SELECT_FOLDER_TEXT=Select the folder to install $BrandShortName in.
+
+BYTE=B
+KILO=K
+MEGA=M
+GIGA=G
diff --git a/application/palemoon/locales/en-US/installer/override.properties b/application/palemoon/locales/en-US/installer/override.properties
new file mode 100644
index 0000000000..288f674805
--- /dev/null
+++ b/application/palemoon/locales/en-US/installer/override.properties
@@ -0,0 +1,86 @@
+# 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/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+# Strings that require a space at the end should be enclosed with double
+# quotes and the double quotes will be removed. To add quotes to the beginning
+# and end of a strong enclose the add and additional double quote to the
+# beginning and end of the string (e.g. ""This will include quotes"").
+
+SetupCaption=$BrandFullName Setup
+UninstallCaption=$BrandFullName Uninstall
+BackBtn=< &Back
+NextBtn=&Next >
+AcceptBtn=I &accept the terms in the License Agreement
+DontAcceptBtn=I &do not accept the terms in the License Agreement
+InstallBtn=&Install
+UninstallBtn=&Uninstall
+CancelBtn=Cancel
+CloseBtn=&Close
+BrowseBtn=B&rowse…
+ShowDetailsBtn=Show &details
+ClickNext=Click Next to continue.
+ClickInstall=Click Install to start the installation.
+ClickUninstall=Click Uninstall to start the uninstallation.
+Completed=Completed
+LicenseTextRB=Please review the license agreement before installing $BrandFullNameDA. If you accept all terms of the agreement, select the first option below. $_CLICK
+ComponentsText=Check the components you want to install and uncheck the components you don't want to install. $_CLICK
+ComponentsSubText2_NoInstTypes=Select components to install:
+DirText=Setup will install $BrandFullNameDA in the following folder. To install in a different folder, click Browse and select another folder. $_CLICK
+DirSubText=Destination Folder
+DirBrowseText=Select the folder to install $BrandFullNameDA in:
+SpaceAvailable="Space available: "
+SpaceRequired="Space required: "
+UninstallingText=$BrandFullNameDA will be uninstalled from the following folder. $_CLICK
+UninstallingSubText=Uninstalling from:
+FileError=Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Abort to stop the installation,\r\nRetry to try again, or\r\nIgnore to skip this file.
+FileError_NoIgnore=Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Retry to try again, or\r\nCancel to stop the installation.
+CantWrite="Can't write: "
+CopyFailed=Copy failed
+CopyTo="Copy to "
+Registering="Registering: "
+Unregistering="Unregistering: "
+SymbolNotFound="Could not find symbol: "
+CouldNotLoad="Could not load: "
+CreateFolder="Create folder: "
+CreateShortcut="Create shortcut: "
+CreatedUninstaller="Created uninstaller: "
+Delete="Delete file: "
+DeleteOnReboot="Delete on reboot: "
+ErrorCreatingShortcut="Error creating shortcut: "
+ErrorCreating="Error creating: "
+ErrorDecompressing=Error decompressing data! Corrupted installer?
+ErrorRegistering=Error registering DLL
+ExecShell="ExecShell: "
+Exec="Execute: "
+Extract="Extract: "
+ErrorWriting="Extract: error writing to file "
+InvalidOpcode=Installer corrupted: invalid opcode
+NoOLE="No OLE for: "
+OutputFolder="Output folder: "
+RemoveFolder="Remove folder: "
+RenameOnReboot="Rename on reboot: "
+Rename="Rename: "
+Skipped="Skipped: "
+CopyDetails=Copy Details To Clipboard
+LogInstall=Log install process
+Byte=B
+Kilo=K
+Mega=M
+Giga=G
diff --git a/application/palemoon/locales/en-US/palemoon-l10n.js b/application/palemoon/locales/en-US/palemoon-l10n.js
new file mode 100644
index 0000000000..642ad6534a
--- /dev/null
+++ b/application/palemoon/locales/en-US/palemoon-l10n.js
@@ -0,0 +1,7 @@
+# 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
+
+pref("general.useragent.locale", "@AB_CD@");
diff --git a/application/palemoon/locales/en-US/pdfviewer/chrome.properties b/application/palemoon/locales/en-US/pdfviewer/chrome.properties
new file mode 100644
index 0000000000..0b469195c2
--- /dev/null
+++ b/application/palemoon/locales/en-US/pdfviewer/chrome.properties
@@ -0,0 +1,18 @@
+# Copyright 2012 Mozilla Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Chrome notification bar messages and buttons
+unsupported_feature=This PDF document might not be displayed correctly.
+open_with_different_viewer=Open With Different Viewer
+open_with_different_viewer.accessKey=o
diff --git a/application/palemoon/locales/en-US/pdfviewer/viewer.properties b/application/palemoon/locales/en-US/pdfviewer/viewer.properties
new file mode 100644
index 0000000000..ffc025362b
--- /dev/null
+++ b/application/palemoon/locales/en-US/pdfviewer/viewer.properties
@@ -0,0 +1,124 @@
+# Copyright 2012 Mozilla Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Main toolbar buttons (tooltips and alt text for images)
+previous.title=Previous Page
+previous_label=Previous
+next.title=Next Page
+next_label=Next
+
+# LOCALIZATION NOTE (page_label, page_of):
+# These strings are concatenated to form the "Page: X of Y" string.
+# Do not translate "{{pageCount}}", it will be substituted with a number
+# representing the total number of pages.
+page_label=Page:
+page_of=of {{pageCount}}
+
+zoom_out.title=Zoom Out
+zoom_out_label=Zoom Out
+zoom_in.title=Zoom In
+zoom_in_label=Zoom In
+zoom.title=Zoom
+print.title=Print
+print_label=Print
+presentation_mode.title=Switch to Presentation Mode
+presentation_mode_label=Presentation Mode
+open_file.title=Open File
+open_file_label=Open
+download.title=Download
+download_label=Download
+bookmark.title=Current view (copy or open in new window)
+bookmark_label=Current View
+
+# Tooltips and alt text for side panel toolbar buttons
+# (the _label strings are alt text for the buttons, the .title strings are
+# tooltips)
+toggle_sidebar.title=Toggle Sidebar
+toggle_sidebar_label=Toggle Sidebar
+outline.title=Show Document Outline
+outline_label=Document Outline
+thumbs.title=Show Thumbnails
+thumbs_label=Thumbnails
+findbar.title=Find in Document
+findbar_label=Find
+
+# Thumbnails panel item (tooltip and alt text for images)
+# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
+# number.
+thumb_page_title=Page {{page}}
+# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
+# number.
+thumb_page_canvas=Thumbnail of Page {{page}}
+
+# Context menu
+first_page.label=Go to First Page
+last_page.label=Go to Last Page
+page_rotate_cw.label=Rotate Clockwise
+page_rotate_ccw.label=Rotate Counterclockwise
+
+# Find panel button title and messages
+find_label=Find:
+find_previous.title=Find the previous occurrence of the phrase
+find_previous_label=Previous
+find_next.title=Find the next occurrence of the phrase
+find_next_label=Next
+find_highlight=Highlight all
+find_match_case_label=Match case
+find_reached_top=Reached top of document, continued from bottom
+find_reached_bottom=Reached end of document, continued from top
+find_not_found=Phrase not found
+
+# Error panel labels
+error_more_info=More Information
+error_less_info=Less Information
+error_close=Close
+# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
+# replaced by the PDF.JS version and build ID.
+error_version_info=PDF.js v{{version}} (build: {{build}})
+# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
+# english string describing the error.
+error_message=Message: {{message}}
+# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
+# trace.
+error_stack=Stack: {{stack}}
+# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
+error_file=File: {{file}}
+# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
+error_line=Line: {{line}}
+rendering_error=An error occurred while rendering the page.
+
+# Predefined zoom values
+page_scale_width=Page Width
+page_scale_fit=Page Fit
+page_scale_auto=Automatic Zoom
+page_scale_actual=Actual Size
+
+# Loading indicator messages
+loading_error_indicator=Error
+loading_error=An error occurred while loading the PDF.
+invalid_file_error=Invalid or corrupted PDF file.
+missing_file_error=Missing PDF file.
+
+# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
+# "{{type}}" will be replaced with an annotation type from a list defined in
+# the PDF spec (32000-1:2008 Table 169 – Annotation types).
+# Some common types are e.g.: "Check", "Text", "Comment", "Note"
+text_annotation_type.alt=[{{type}} Annotation]
+request_password=PDF is protected by a password:
+invalid_password=Invalid Password.
+
+printing_not_supported=Warning: Printing is not fully supported by this browser.
+printing_not_ready=Warning: The PDF is not fully loaded for printing.
+web_fonts_disabled=Web fonts are disabled: unable to use embedded PDF fonts.
+document_colors_disabled=PDF documents are not allowed to use their own colors: \'Allow pages to choose their own colors\' is deactivated in the browser.
diff --git a/application/palemoon/locales/en-US/profile/bookmarks.inc b/application/palemoon/locales/en-US/profile/bookmarks.inc
new file mode 100644
index 0000000000..d2a701e52b
--- /dev/null
+++ b/application/palemoon/locales/en-US/profile/bookmarks.inc
@@ -0,0 +1,40 @@
+# 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 emptyLines
+
+# LOCALIZATION NOTE: The 'en-US' strings in the URLs will be replaced with
+# your locale code, and link to your translated pages as soon as they're
+# live.
+
+#define bookmarks_title Bookmarks
+#define bookmarks_heading Bookmarks
+
+#define bookmarks_toolbarfolder Bookmarks Toolbar Folder
+#define bookmarks_toolbarfolder_description Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar
+
+# LOCALIZATION NOTE (getting_started):
+# link title for https://www.mozilla.org/en-US/firefox/central/
+#define getting_started Getting Started
+
+# LOCALIZATION NOTE (firefox_heading):
+# Firefox links folder name
+#define firefox_heading Mozilla Firefox
+
+# LOCALIZATION NOTE (firefox_help):
+# link title for https://www.mozilla.org/en-US/firefox/help/
+#define firefox_help Help and Tutorials
+
+# LOCALIZATION NOTE (firefox_customize):
+# link title for https://www.mozilla.org/en-US/firefox/customize/
+#define firefox_customize Customize Firefox
+
+# LOCALIZATION NOTE (firefox_community):
+# link title for https://www.mozilla.org/en-US/contribute/
+#define firefox_community Get Involved
+
+# LOCALIZATION NOTE (firefox_about):
+# link title for https://www.mozilla.org/en-US/about/
+#define firefox_about About Us
+
+#unfilter emptyLines
diff --git a/application/palemoon/locales/en-US/profile/chrome/userChrome-example.css b/application/palemoon/locales/en-US/profile/chrome/userChrome-example.css
new file mode 100644
index 0000000000..2495795b4a
--- /dev/null
+++ b/application/palemoon/locales/en-US/profile/chrome/userChrome-example.css
@@ -0,0 +1,50 @@
+/* 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/. */
+
+/*
+ * Edit this file and copy it as userChrome.css into your
+ * profile-directory/chrome/
+ */
+
+/*
+ * This file can be used to customize the look of Mozilla's user interface
+ * You should consider using !important on rules which you want to
+ * override default settings.
+ */
+
+/*
+ * Do not remove the @namespace line -- it's required for correct functioning
+ */
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
+
+
+/*
+ * Some possible accessibility enhancements:
+ */
+/*
+ * Make all the default font sizes 20 pt:
+ *
+ * * {
+ * font-size: 20pt !important
+ * }
+ */
+/*
+ * Make menu items in particular 15 pt instead of the default size:
+ *
+ * menupopup > * {
+ * font-size: 15pt !important
+ * }
+ */
+/*
+ * Give the Location (URL) Bar a fixed-width font
+ *
+ * #urlbar {
+ * font-family: monospace !important;
+ * }
+ */
+
+/*
+ * For more examples see http://www.mozilla.org/unix/customizing.html
+ */
+
diff --git a/application/palemoon/locales/en-US/profile/chrome/userContent-example.css b/application/palemoon/locales/en-US/profile/chrome/userContent-example.css
new file mode 100644
index 0000000000..a90694d6c9
--- /dev/null
+++ b/application/palemoon/locales/en-US/profile/chrome/userContent-example.css
@@ -0,0 +1,32 @@
+/* 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/. */
+
+/*
+ * Edit this file and copy it as userContent.css into your
+ * profile-directory/chrome/
+ */
+
+/*
+ * This file can be used to apply a style to all web pages you view
+ * Rules without !important are overruled by author rules if the
+ * author sets any. Rules with !important overrule author rules.
+ */
+
+/*
+ * example: give all tables a 2px border
+ *
+ * table { border: 2px solid; }
+ */
+
+/*
+ * example: turn off "marquee" element
+ *
+ * marquee { -moz-binding: none; }
+ *
+ */
+
+/*
+ * For more examples see http://www.mozilla.org/unix/customizing.html
+ */
+
diff --git a/application/palemoon/locales/en-US/searchplugins/amazondotcom.xml b/application/palemoon/locales/en-US/searchplugins/amazondotcom.xml
new file mode 100644
index 0000000000..56362e8ae2
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/amazondotcom.xml
@@ -0,0 +1,15 @@
+
+
+
+Amazon.com
+Amazon.com Search
+ISO-8859-1
+data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==
+
+
+
+
+http://www.amazon.com/
+
diff --git a/application/palemoon/locales/en-US/searchplugins/answers.xml b/application/palemoon/locales/en-US/searchplugins/answers.xml
new file mode 100644
index 0000000000..b219e61cb9
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/answers.xml
@@ -0,0 +1,16 @@
+
+
+
+Answers.com
+Dictionary Search on Answers.com
+UTF-8
+data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////K////4f////E////5f///+n////P////mv///0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8E////fv///+//////////////////////7NnP/+LFtv/////+////of///xYAAAAAAAAAAAAAAAD///8D////lf////////////j0//bi1v/OlXf/tGU9/6FCEv/OmH3////////////////D////FgAAAAAAAAAA////avPm4P/evaz/8NbI//7r3//23M3/xYRi/5kzAP/Df1z//u/l//749P/v4dn/+PPw/////6j///8B////GP///+W/f1//smM7//bczf/+69///uvf/9ytlP+ZMwD/5se3/+/f1//AgmP/nj0N/927qv/////+////QP///2z/////8NvQ/8WCYP/+69///uvf//7r3/+7ckz/pUkb/9m1ov+ePQ3/okUW/8+fh//38O3//////////5r///+t//////7y6v/Cflv/58Cr//DRwP/mwKv/okQU/8ODYv/cuqj/yZN4//Tq5f/+9e///vDn///////////Q////yf/////+7+b/05yA/65ZLv+9dVD/sF40/5kzAP/kvKb//vTu//Tr5v/7+Pb//vfz//707f//////////6f///8X//////vDm/+K4ov/KjGz//ure/8uNbf+jRBX/+OTX/+3b0v+jSBr/pk0h/717Wv/Wrpr//Pn4/////+b///+i//////7z7f/02Mj/wn5b//vl2P+uWS7/vXhU//v49//48u//1q6a/717Wv+oUSb/tWxH//jz8P/////K////V///////+/j//ure/8aFZP/fs5v/oEAQ/9q1o/+zaEL/1ayX//718P/+9/P/+PHu//jz8P//////////h////wr////O///////38v/YpYr/tGQ7/6ZLHf/06eX/s2dB/549Df/x49z//vDn//7x6f//////////8////yoAAAAA////R/////v/////7dXI/5kzAP+7cUv//vHp/+vYzv+bNwX/vHlY//38/P///////////////30AAAAAAAAAAAAAAAD///9n////+/z5+P++e1n/3LGc//7w5//++PT/0KKL/8OIa//9/Pv//////////5X///8GAAAAAAAAAAAAAAAAAAAAAP///0n////K///////////////////////////+/v7/////5v///2z///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Cv///1f///+g////xP///8n///+r////bP///xoAAAAAAAAAAAAAAAAAAAAA+B////AH///AA///wAH//4AB//+AAP//AAD//wAA//8AAP//AAD//4AA//+AAf//wAP//+AD///wD////D///w==
+
+
+
+
+http://www.answers.com/
+
diff --git a/application/palemoon/locales/en-US/searchplugins/bing.xml b/application/palemoon/locales/en-US/searchplugins/bing.xml
new file mode 100644
index 0000000000..22019ce40c
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/bing.xml
@@ -0,0 +1,18 @@
+
+
+
+ Bing
+ Bing. Search by Microsoft.
+ UTF-8
+ data:image/x-icon;base64,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
+
+
+
+
+
+
+
+ https://www.bing.com/search
+
diff --git a/application/palemoon/locales/en-US/searchplugins/creativecommons.xml b/application/palemoon/locales/en-US/searchplugins/creativecommons.xml
new file mode 100644
index 0000000000..d9ac67e34d
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/creativecommons.xml
@@ -0,0 +1,14 @@
+
+
+
+Creative Commons
+Find photos, movies, music, and text to rip, sample, mash, and share.
+utf-8
+data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAJUSURBVDiNjZO9S1thGMXPvTfJFbnkipNkLLS0ksFg0M0lf4CboNCEgIIg/RiKtEOn0qGWTtbVoBYcIji10I9J0ApWCjp0kRaXdhHjTW4+uGnur4NJ8GOwZ3nf4TnnfZ5z3scAdBGGYdyVdN+yrGHTNNOtVqsVhuG+pO+S3gE/LtV3BIxzPDJNc8FxHGN0dNRKpVIGoJ2dndr+/r5Vr9cl6bmkN0AoSQIEyHXdj5KYnZ3F932uolKpkM/nK5KQ9FmSCZwLOI7zQBLr6+vXiFdRLBaDtsiTTve3YrFYkM/nbyR3MDU1dSKpLumO+vr6Xruui+d5AFSrVVZWVtjY2KDRaABwdHTE4uIie3t7AJTLZaLRaFXSCyUSid1MJgOA53n09/eTTqdJJpPMzc2xurqKbduMj48Tj8fZ3d0FYHBw8FjSezmOU56fnwdgeXkZ27ap1WpUKhWazSZjY2Nks1kASqVSd4zp6eljSX/MtiHdRDpnEATyfb+bkiSVSqXu3TCM8xgHBga+dkY4OzvDdV2GhoZIJBLMzMxQKBSIRqNkMhlisRhbW1sAJJPJn5I+KB6Pv7poou/7rK2tsbm5SRAEXROXlpY4ODgAoFarYdu2J+llN8ZcLvffMeZyud+SGpLuCVBPT89jSRQKhRvJxWKxISmU9JTOT5Rk9Pb2fpHE5OQkJycn14inp6dMTEx4bdM/SbKAy8sk6WEkElmwLCuSSqUYGRmxgHB7e7t+eHgYazabgaRnkt7SeZnr63xbUtYwjGHTNNNhGP4F9iR9a6/zr4v1/wDE1D9XlC4rrAAAAABJRU5ErkJggg==
+
+
+
+http://search.creativecommons.org/
+
diff --git a/application/palemoon/locales/en-US/searchplugins/duckduckgo-palemoon.xml b/application/palemoon/locales/en-US/searchplugins/duckduckgo-palemoon.xml
new file mode 100644
index 0000000000..ae03986141
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/duckduckgo-palemoon.xml
@@ -0,0 +1,16 @@
+
+
+ DuckDuckGo
+ Search DuckDuckGo
+ UTF-8
+ Search Plugin for DuckDuckGo (HTTPS version)
+ data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA11RgALs6oACbQ9wAj0v8AI9L/ACfQ9wAu0agANdUYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzzN4CNdL/oK/z//////////////////////+jsPv/BDXX/wAz0t4AAAAAAAAAAAAAAAAAAAAAAAAAAAAyzvNSduD//////8jK/v+P+Lf/IbQL/17RPP+J3Y//wOKX//////9YeuX/ADLO8wAAAAAAAAAAAAAAAAAw091piOX/8/X9/1Fx5P9xhu//WOWZ/0W9Lv9Lwjn/J8BB/xyDAP9bdfL/9fP//2mI5v8AMNPdAAAAAAc610YRQ9f//////0Zr4P8AGdD/sb32////////////wrv//wAh1/8MPab/ACPc/05r4///////EkPX/wc610YANtWkrr/y/6S48P8AJ9L/AB3R/+/w/v///////////3+D7f8AQeL/AYTw/wFr5/8AMNb/p7Tv/6698v8AM9WkADLW//////8yXt//AC3V/wAw1/////////////z///8A0P7/AKb1/wWI7P8AuPf/AJ3w/zZW3P//////ADHV/wAx2P//////AzrZ/wAu1/84ZOL////////////e////AND//wC1+f8Atff/AZbv/wY62f8ELNf//////wAw1/8AMtn//////wAw2f8ALNn/kKrz////+//cwbH////////////R////Rcb8/wDO/f8A/P//AHzo//////8AMNj/ADXa//////8vXuL/ACna/4yq9///79T/jUkg/9i+r///////r2Q0/7Cozv8BKdr/AirY/zdZ4P//////ADTa/wI72tOuv/T/prr0/wAl2v+JqPb//7yW/+bUxv/9+/n////u//W+n/+Op/L/ADPd/wAv2v+ru/T/r7/0/wI72tMLQd1DEEjg//////9Cbef/ADng///////////////////////R3///AC3g/wAy3v9SeOn//////xFI4P8LQd1DAAAAAAM64PNmiuz/9/j//2mN7f/m7P3///////////9Cb+n/ACXd/wAt3v9rju3//////2iL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/P8J0vz/ELDz/xt85v8mSdj/JknY/yZJ2P8lStowJUjXYCZJ2P8nSdhwJErZryZK2f8oSNcgJUnajyZK2f8mStn/JkrZ/yZK2f8mStn/iJPA////////////////////////////0ff+/xjV/P8J0vz/Drn1/xiO6/8Yjuv/GI7r/xCw8/8Lyvr/CdL8/xmF6P8mStn/JkrZ/yVJ2o8oSNcgJkrZ/yRK2a8jStrfI0rZ3wAAAAAlSdq/Jkra/yZK2v8mStr/Jkra/yZK2v+xtsf///////////////////////////8o2Pz/CdL8/wvK+v8mStr/Jkra/yZK2v8mStr/Jkra/yZK2v8iW97/Jkra/yZK2v8mStr/JUnavwAAAAAjStnfI0ra3yZK2v8lSdq/AAAAACZH2O8mStr/Jkra/yZK2v8mStr/L1HY/9HR0f///////////////////////////yjY/P8J0vz/CdL8/xCw9P8QsPT/ELD0/xSf7/8ddeX/Jkra/yZK2v8mStr/Jkra/yZK2v8mR9jvAAAAACVJ2r8mStr/Jkvb/yVJ2r8AAAAAJkvb/yZL2/8mS9v/Jkvb/yZL2/9KZtL/4+Pj////////////////////////////4Pn//0fd/f8J0vz/CdL8/wnS/P8J0vz/CdL8/wnS/P8Lyvr/Fpfu/yJc3/8mS9v/Jkvb/yZL2/8AAAAAJUnavyZL2/8mS9z/JUncvwAAAAAmS9z/Jkvc/yZL3P8mS9z/Jkvc/26AyP/x8fH//////////////////////////////////////9H3/v/C9P7/o+7+/2fa+/8Oufb/CdL8/wnS/P8J0vz/CdL8/xiP7P8mS9z/Jkvc/wAAAAAlSdy/Jkvc/yZM3P8lTNy/AAAAACZJ2e8mTNz/Jkzc/yZM3P8mTNz/iJTB////////////qnth/5VaOf/x6eX///////////////////////Hp5f/x6eX/ydL2/yZM3P8kVN7/G37o/xKo8v8QsfT/HXbm/yZM3P8mSdnvAAAAACVM3L8mTNz/I0vc3yZJ2u8AAAAAJUzevyZM3f8mTN3/Jkzd/yZM3f+fqc3///////////+VWjn/v5yI/+re1///////////////////////jk8s/7iRe//J0vb/Jkzd/yZM3f8mTN3/Jkzd/yZM3f8mTN3/Jkzd/yVM3r8AAAAAI0vc3yNL3N8kTd2vJk3d/yhQ3yAlTd2PJk3d/yZN3f8mTd3/Jk3d/6St0v////////////Hp5f/q3tf///////////////////////////+xhm7/49PK/6Cx8P8mTd3/Jk3d/yZN3f8mTd3/Jk3d/yZN3f8mTd3/JU3djyhQ3yAmTd3/JE3drydN33AmTd7/J03fcCVK3zAmTd7/Jk3e/yZN3v8mTd7/pK7S///////Sp5r/////////////////////////////////////////////////T27k/yZN3v8mTd7/Jk3e/yZN3v8mTd7/Jk3e/yZN3v8lSt8wJ03fcCZN3v8nTd9wKFDfICZO3/8mTt3PAAAAACVN3r8mTt//Jk7f/yZO3/+EltX//////+fRyv/SqaD/59LO///////////////////////at63/vIBy/7Glxf8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8mTt//JU3evwAAAAAmTt3PJk7f/yhQ3yAAAAAAJE/dryZO3/8oUN9AKFDfQCZO3/8mTt//Jk7f/zhb2v/o6/T/////////////////////////////////////////////////XHrn/yZO3/8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8oUN9AKFDfQCZO3/8kT92vAAAAAAAAAAAoUN9AJk7g/yZO4M8AAAAAJk/hnyZO4P8mTuD/Jk7g/05v5v/k6fv//////////////////////////////////////3eR7P8mTuD/Jk7g/yZO4P8mTuD/Jk7g/yZO4P8mTuD/Jk/hnwAAAAAmTuDPJk7g/yhQ30AAAAAAAAAAAAAAAAAjT+GfJU/h/yVO4Y8gUN8QIk7gzyVP4f8lT+H/SWnW/0lp1v+bq+H/8fHx/////////////////6Cy8v9OcOb/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yJO4M8gUN8QJU7hjyVP4f8jT+GfAAAAAAAAAAAAAAAAAAAAACBQ3xAlTOHvJU/h/yVQ4mAgUN8QIk7hzyVP4f+ktOv///////////////////////H0/f9phur/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yVP4f8iTuHPIFDfECVQ4mAlT+H/JUzh7yBQ3xAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ3zAlUOLvJVDi/yVQ4mAgUN8QI1Din4mb2//J0/j/ydP4/6299P93ku3/M1vk/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/I1DinyBQ3xAlUOJgJVDi/yVQ4u8lUN8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAlUOLvJVDi/yVQ4o8AAAAAJFDjQCVQ4r8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDivyRQ40AAAAAAJVDijyVQ4v8lUOLvJVDkMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAjUeTfJVHj/yNR5N8kUONAAAAAACVQ5DAmUuOAJVHivyNR5N8lUeP/JVHj/yNR5N8lUeK/JlLjgCVQ5DAAAAAAJFDjQCNR5N8lUeP/I1Hk3yVQ5DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBQ3xAjUuSfJVHk/yVR5P8jUeTfJFLkcChQ5yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoUOcgJFLkcCNR5N8lUeT/JVHk/yNS5J8gUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkUONAI1LknyVS5P8lUuT/JVLk/yVS5O8lUeS/JVHkvyVR5L8lUeS/JVLk7yVS5P8lUuT/JVLk/yRS468kUONAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFDfECVS5GAjUuWfIlPlzyVS5f8lUuX/JVLl/yVS5f8iU+XPI1LlnyVS5GAgUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/ggBB/wgAEP4AAAB8AAAAPAAAADiAAAEYAAAAEQAAAIAAAAAAAAAAAgAAAEIAAABCAAAAQgAAAEIAAABCAAAAQAAAAAAAAAABAAAAiAAAABiAAAEcAAAAPAAAAD4AAAB/CAAQ/4IAQf/AfgP/8AAP//wAP/
+
+
+
+
+
+
+
+
+
diff --git a/application/palemoon/locales/en-US/searchplugins/eBay.xml b/application/palemoon/locales/en-US/searchplugins/eBay.xml
new file mode 100644
index 0000000000..74af34c98d
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/eBay.xml
@@ -0,0 +1,19 @@
+
+
+
+eBay
+eBay - Online auctions
+ISO-8859-1
+data:image/x-icon;base64,AAABAAIAEBAAAAAAAAB6AQAAJgAAACAgAAAAAAAAQgMAAKABAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAFBSURBVDjLtZPdK0MBGIf3J5Babhx3rinFBWuipaUskX9DYvkopqgV90q5UJpyp0OKrUWM2VrRsS9D0zZKHGaOnW1nj4vtypVtPPe/533r9746QAAOAJXfo5Yzgg44pHrcugon/6Sgo0b+XuAOZ2iZiVQmyPoDpIwmUkYTzqM7GsdDdC7F6Lbf8pzOkfWOouzqeZem2b+2AqAV8zjD8yVBqqcf2b7C66yNiMGMfixIQSvi8Mp0LEbR5ADq1QSKWM+Gx0RC9nOZ2GLzwlIWdPWiuNzk4w/EpThNkyEAXKEP2ud8KGId2sspilhPMrmNwzfCuqePr/xbSfC5I/I0MMSj2YJ3z49gDdO2cEOrLUowJpE9G0QRG1ClKbR0EIdvmOPYcnUtnN+vsnZiQC1k/qnGagQ1n3LNzySUJZVskitnmr8BlQG7T2hvgxsAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAMJSURBVFjD7ZddSFNhGMeHXXQTZFFCWfR1pRhUECQlBdWVToo+6KYu1KigtDASG5qUfZgFZvahEDosECPDktKZS1FL+1DRnEvdUptjug91X2dnZzv/3vO6OZbWnR4v9sADL+fs7P97/s/znu2VAJD4UkpSSdKG+QubTyPBr+sXz8XCR64fIAHihVTis0SsUAoAVhEBrBKIHCGAEMB/ARi3F5LkbpS2WMRzYEEBXC2tsD6T03R9agsCGLNyqPw6CXmrBT06JvhbPHZwmkdwtR0B138PPKOHgzXD5jLAy3tmibo4K9weZwDAazJj/FQKRnfugfHMeRiTz0K3Ixam1HQKcPC+Fisu9NK1P08Uj4DleHgMdXC+WQ7nu3UEOhFMfTQcVUvQ1H4IN2sj8H2k7K+2TqCc3GseyA8AmDOzMBq7D9bS8sAr6nEJdNt3UbHVF1XQGtmZew8bTPT6tWoD3KpsUvlR8NxUoEICMvl6KQo+xqCwcRs4T8Ax5c8bFExjbAgAjO7aS8VsLypgq3g5nWStjztAhWRVhqAqeB6IuKTClkw1eNYEbrCQQBwD8yGGOsAooogLYejQPKBi7UPF9DkH+ezd+o141ZkUPAOC+L9SAMivNc7q46YMNSLTe4n1kaQF4XD3ZIDTPgU3XEYciKcAHrsGJS1xKFBGgyVzouiT4VbdGhjt/cEA5isyKsaz7jl3we7bg7Rqf6j0LoSldON4wWcqJDgQNGTN++l13vELA+MK6kKd6iryFOvxtidt9i5gO7owdjKJQliflNAU1pas6xQgnAzg1ux+lJEdILixNr0Pq9JUUA8NwVG9DM73G0jlcnh+V4BpjIWzJmIGQIjnXw5TiDuKSEwxurm3ITc8DNO51BnrLbIcsrW0dNA6RxgUKU1UdGVqLy5X6qGzTLvlnewiBZyGs3Yz6X8UeaYI3olvZDhzwLumZ+eHvooCCC0Q5VUsb4unwycM4YIDqA01tPqmgbzQr2EIYPECiPm33LYoDiZSsY9moh9O/Znoa4d9HkXtPg2pX/cPKCoRQ+ocZa4AAAAASUVORK5CYII=
+
+
+
+
+
+
+
+http://search.ebay.com/
+
+
diff --git a/application/palemoon/locales/en-US/searchplugins/ecosia.xml b/application/palemoon/locales/en-US/searchplugins/ecosia.xml
new file mode 100644
index 0000000000..04c8d229b2
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/ecosia.xml
@@ -0,0 +1,12 @@
+
+ Ecosia
+ Search Ecosia
+ UTF-8
+ info@ecosia.org
+ Ecosia Search
+
+ data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAACMuAAAjLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8qzQBuaw3UrmsN6u5rDfruaw37bmsN+25rDfSuaw3fLmsNyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2rTokrLFGurqsNv+5rDf/uaw3/7msN/+5rDf/uaw3/7urNP/AqS7suqw2aAAAAAAAAAAAAAAAAAAAAAC/qjApkbpn4mvJlf/EqCr/uaw3/7msN/+5rDf/uaw3/7urNP+rsUj/ib5x/7qsNv+9qzKBAAAAAAAAAAC5rDcLwKkvzom9cf813Nb/lrlh/8KoLP+5rDf/uaw3/7msN//BqS3/eMSF/yXj6v+BwHv/lbli/7atO1IAAAAAuaw3bsCqL/+Rumb/K+Di/z3ZzP+dtln/vqox/7msN/+5rDf/waku/23Ikv8s4OH/ONvS/5m4Xv+7qzXZuaw3CbmsN9DBqS7/hL93/zDe3f8v393/RdbD/7OuPv+7qzX/uqw2/8WoKf99wn//Lt/e/y/e3f99wn//v6ow/7msN0+7qzT7s64+/0bWwf8y3tn/L97d/03TuP+usET/vKoz/7isOP+vr0P/XM6n/zDe3P813Nb/L97d/5O6Zf/EpymOu6s0/7OuPv8+2cv/J+Hn/1HStP+0rjz/vasy/76qMP9zxYr/NtzV/zTd1/823NX/NtzV/zLd2f9I1b//mbheqsGpLf+gtVX/bseR/3fEhv+wr0L/vaoy/7msN/+/qjD/Wc+q/yvg4/813Nb/Md7b/zfc1P833NT/Mt7a/zbc1aqHvnT6bMiT/522WP+wr0L/vqox/7msN/+5rDf/vaoy/6C1VP8/2cr/N9zT/2vJlf9hzKD/NtzU/zbc1f813NaONdzWz3HGjv9ky53/prNN/8SoKv+8qzT/uaw3/7msOP/EqCr/ecOE/0HYx/9V0K//N9vT/zXc1v823NX/NtzVTjXc120w3tz/Lt/e/0zUu/+Fv3X/rrBF/7msN/+7qzX/vaoy/6qxSf9G1sH/L9/d/zPd2P8x3tv/L9/e2C/f3Qk23NUKNtzVzDbc1v823NX/OdvQ/0nVvv+xr0H/ta07/7+qL/+7qzT/r69D/2LMoP823NX/VNGx/2TLnVEAAAAAAAAAADbc1Sc03dfgQNnJ/2bKm/862tD/pLRP/1vOqf9S0rP/ib1x/8CpL/+4rDj/qLJM/7qsNn4AAAAAAAAAAAAAAAAAAAAAM93YI0vUvLtux5H/VdGw/3DHj/9Zz6r/Xc2m/3rDgv+5rDf/u6s1672rM2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyaYjUburNaytsUbZuK056cGpLuS/qjDGuaw3gLmsNx4AAAAAAAAAAAAAAAAAAAAA+D8AAOAPAADAAwAAgAMAAIABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAACAAQAAgAMAAMAHAADgDwAA+B8AAA==
+
+
+
+
diff --git a/application/palemoon/locales/en-US/searchplugins/list.txt b/application/palemoon/locales/en-US/searchplugins/list.txt
new file mode 100644
index 0000000000..2f2ca3275f
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/list.txt
@@ -0,0 +1,6 @@
+duckduckgo-palemoon
+bing
+ecosia
+twitter
+wikipedia
+yahoo
diff --git a/application/palemoon/locales/en-US/searchplugins/twitter.xml b/application/palemoon/locales/en-US/searchplugins/twitter.xml
new file mode 100644
index 0000000000..e1909e77f0
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/twitter.xml
@@ -0,0 +1,15 @@
+
+
+
+Twitter
+Realtime Twitter Search
+UTF-8
+data:image/x-icon;base64,AAABAAIAEBAAAAAAAAALAgAAJgAAACAgAAAAAAAAQQQAADECAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAHSSURBVDjLfVO/axRREP6SS0ihlv4FQpoYWxHyHxgCsUxhmToBS4tF7NLZWaiIEHLv7d5dJNEQ0tgYEAmIjeAfYC57++7MGbzLjxu/Nxt27zZrBoadnZnvm3kz7wEqMpZqmdAfBBMIZBzGVGCkorbXq4kFp/yPtBgPZCJzZvYQuNZ+jKhVo75G2LyHVz9uoeFmc6bILaHaepv9v/w6iRcypXbonmNPBB9OBbv82tYfmPgn7NHTnKAaP8E+g8Ztot68k/nXDm8ooNEVhHEP1vXROBbUf/M/Wc0Jwl8zsEkHOxcMtE6Y+A42nodpLtLfR9QWRMmA9jm2eh78aXQY4eF9VvjCgKDWEewM2PIZQc4nD9Sf6hk+sohNqorzW0kN91BBdYKtO9dE/00JZITA50XxsxTHlWarqMYBh/O3UPGqrednx7ox3o+UhbIRT7O1BO9PfIsXI5V9J34WvrqN94buwFi+b3N0k0nrCjAFAp1+Nz1Czd3N2y8Tm6ywYk9JTHKqk/cD9cM17YW89WExbo6Ja1zhvgK3+4Itql+rt8PkO1f6oBysBN3brLrMVhs84wHJvpHoMwnf8CI9ygZd3nbJgymrMvxeSoLjmlCsIJf+a17lP6juZmUWkMzvAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAECElEQVRYw+1XS2gTURS9ia0/EMSCCC4siCIKrly4UVwIrgRR3KgbXQmCCiIiCha7EQRR6cIupHShSeZjf6lttdWFgqBRBPGDKIqF1mQ+1Wp/SZvnvXfmTWfMpJ3U0pUPHplJ3rv33HO/Afi/5nPViTjuKm8rYlFlAoSI8a500R1SGLYUZRH+Hvee6+qqygvxW1KJ1XLd+74aUuY+0OzjoHw/BElj4zQQHxv0HDBUvjR9WQlNYmnJhSjK9dxFUC0LOsYE3M8LSI8LUKw8AmoG5ecqPtP8vgZ04zKeO+jqjUtBDi2qWY+71xPaKKpnpF0uzUpCnxDQOixQ+BSo9iR/3vsh4CF+r5of8D3F75r9udQAKUwx++AJXkhZCe9QBkGEuUSC1swTjhK0VrUmEYzw7SKCyUPrLwEP8IxufQN9aCfKPwCJwdq/lCPluvUG2n4jhQW8bD6Hu+amABvSd/LOTbEENOMjdIyicLuA1om/ADhbMqIZn/DMOMq+7iqPBanUzWfQOUGUjUFnnj5H0WdnQOlfFoxssdix3tjGgnWbrCuGKvdA4LmuSQJ6s9SN0zFwg6lS7Qmms+WngK4pEvAVg+w83MltCAAhAHSGFMwEgGQRq2quwZe2cShJkaYvtey39hEHMfsQL7cjxd1F4dBnPWUKVeMwaLmTkRggGd1oiJI9GxLcLg2NA8s5DTXzCKePbjtBJAVoVoGjmFxEwsgiCi5tFuoDDFinHcb9xWg6CBcjpS9ReReCeF9GOL5TsCEYhRkSs26HmSLXh2R2f8DlJS7Q7BZOwyhWzeZ3P2hijtJRG9wSXuQkIj27m1OKKKPUiQIiCv1ptF4x33rUh/Yb+aVqN8BjIf07HwDy0EPFzU2/ck0rgCpln8MgtOcBgOOiFizRSWt7aQCWA0GHdesSBuOAm+fFOQIoOFXVeBRqaGnvlsGIed5LwWiO/LP11KASg7tmt97fnRoz1Xj5NXc4WQP0Sliw6d4EN6mUdStyew8cbB6uQWGdXL+pPHMrjQLCVe40tHdo1NJoQ47X5XBnfKUykduBQ0U9+jEbKR5IOTc0IwfJb+tnjvwoq+3DCmxEV1iwvzyHtlzbSTnVHMRKuTm6crK8AcelDrGc/a/0r4WUsRcF3ebWzC4YCqt8Rbdo5XmO4IZj9IGSW+PFUuSVyu7BeeABIrdZMNFIfiSrCQQFouwDNHxQLyBQdK6nSPOfgftUSXWtaOlD67BkXkCLXrFV1IYJBAUifcpnspQHFjyjmi+4y8nBs6KIL1cJuRZkt6K1xzCYrvFAqdtptDCNz3dxX8V91Gsufqvn8r/CByJemd/kvJiprui/RAQkMaZR/r1i4W6a0rP/N/gXi6OvmDvBLoiyBV1/AN29Cs9hVFoUAAAAAElFTkSuQmCC
+https://twitter.com/search/
+
+
+
+
+
diff --git a/application/palemoon/locales/en-US/searchplugins/wikipedia.xml b/application/palemoon/locales/en-US/searchplugins/wikipedia.xml
new file mode 100644
index 0000000000..6bfb0ccd81
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/wikipedia.xml
@@ -0,0 +1,18 @@
+
+
+
+Wikipedia (en)
+Wikipedia, the free encyclopedia
+UTF-8
+data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=
+
+
+
+
+
+
+
+http://en.wikipedia.org/wiki/Special:Search
+
diff --git a/application/palemoon/locales/en-US/searchplugins/yahoo.xml b/application/palemoon/locales/en-US/searchplugins/yahoo.xml
new file mode 100644
index 0000000000..244e85f4e2
--- /dev/null
+++ b/application/palemoon/locales/en-US/searchplugins/yahoo.xml
@@ -0,0 +1,17 @@
+
+
+
+Yahoo
+Yahoo Search
+UTF-8
+data:image/x-icon;base64,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
+
+
+
+
+
+https://search.yahoo.com/
+
diff --git a/application/palemoon/locales/en-US/updater/updater.ini b/application/palemoon/locales/en-US/updater/updater.ini
new file mode 100644
index 0000000000..6bc731f161
--- /dev/null
+++ b/application/palemoon/locales/en-US/updater/updater.ini
@@ -0,0 +1,10 @@
+; 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/.
+
+; This file is in the UTF-8 encoding
+; All strings must be less than 600 chars.
+[Strings]
+TitleText=%MOZ_APP_DISPLAYNAME% Update
+InfoText=%MOZ_APP_DISPLAYNAME% is installing your updates and will start in a few moments…
+MozillaMaintenanceDescription=The Mozilla Maintenance Service ensures that you have the latest and most secure version of Mozilla Firefox on your computer. Keeping Firefox up to date is very important for your online security, and Mozilla strongly recommends that you keep this service enabled.
diff --git a/application/palemoon/locales/filter.py b/application/palemoon/locales/filter.py
new file mode 100644
index 0000000000..8e097db059
--- /dev/null
+++ b/application/palemoon/locales/filter.py
@@ -0,0 +1,37 @@
+# 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/.
+
+def test(mod, path, entity = None):
+ import re
+ # ignore anything but Firefox
+ if mod not in ("netwerk", "dom", "toolkit", "security/manager",
+ "browser", "browser/metro", "webapprt",
+ "extensions/reporter", "extensions/spellcheck",
+ "other-licenses/branding/firefox",
+ "browser/branding/official",
+ "services/sync"):
+ return False
+ if mod != "browser" and mod != "extensions/spellcheck":
+ # we only have exceptions for browser and extensions/spellcheck
+ return True
+ if not entity:
+ if mod == "extensions/spellcheck":
+ return False
+ # browser
+ return not (re.match(r"searchplugins\/.+\.xml", path) or
+ re.match(r"chrome\/help\/images\/[A-Za-z-_]+\.png", path))
+ if mod == "extensions/spellcheck":
+ # l10n ships en-US dictionary or something, do compare
+ return True
+ if path == "defines.inc":
+ return entity != "MOZ_LANGPACK_CONTRIBUTORS"
+
+ if path != "chrome/browser-region/region.properties":
+ # only region.properties exceptions remain, compare all others
+ return True
+
+ return not (re.match(r"browser\.search\.order\.[1-9]", entity) or
+ re.match(r"browser\.contentHandlers\.types\.[0-5]", entity) or
+ re.match(r"goanna\.handlerService\.schemes\.", entity) or
+ re.match(r"goanna\.handlerService\.defaultHandlersVersion", entity))
diff --git a/application/palemoon/locales/generic/extract-bookmarks.py b/application/palemoon/locales/generic/extract-bookmarks.py
new file mode 100644
index 0000000000..7bf711fff1
--- /dev/null
+++ b/application/palemoon/locales/generic/extract-bookmarks.py
@@ -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/.
+
+import sys
+import re
+import codecs
+try:
+ from Mozilla.Parser import getParser
+except ImportError:
+ sys.exit('''extract-bookmarks needs compare-locales
+
+Find that on http://pypi.python.org/pypi/compare-locales.
+This script has been tested with version 0.6, and might work with future
+versions.''')
+
+ll=re.compile('\.(title|a|dd|h[0-9])$')
+
+p = getParser(sys.argv[1])
+p.readFile(sys.argv[1])
+
+template = '''#filter emptyLines
+
+# LOCALIZATION NOTE: The 'en-US' strings in the URLs will be replaced with
+# your locale code, and link to your translated pages as soon as they're
+# live.
+
+#define bookmarks_title %s
+#define bookmarks_heading %s
+
+#define bookmarks_toolbarfolder %s
+#define bookmarks_toolbarfolder_description %s
+
+# LOCALIZATION NOTE (getting_started):
+# link title for https://www.mozilla.org/en-US/firefox/central/
+#define getting_started %s
+
+# LOCALIZATION NOTE (firefox_heading):
+# Firefox links folder name
+#define firefox_heading %s
+
+# LOCALIZATION NOTE (firefox_help):
+# link title for https://www.mozilla.org/en-US/firefox/help/
+#define firefox_help %s
+
+# LOCALIZATION NOTE (firefox_customize):
+# link title for https://www.mozilla.org/en-US/firefox/customize/
+#define firefox_customize %s
+
+# LOCALIZATION NOTE (firefox_community):
+# link title for https://www.mozilla.org/en-US/contribute/
+#define firefox_community %s
+
+# LOCALIZATION NOTE (firefox_about):
+# link title for https://www.mozilla.org/en-US/about/
+#define firefox_about %s
+
+#unfilter emptyLines'''
+
+strings = tuple(e.val for e in p if ll.search(e.key))
+
+print codecs.utf_8_encode(template % strings)[0]
diff --git a/application/palemoon/locales/generic/install.rdf b/application/palemoon/locales/generic/install.rdf
new file mode 100644
index 0000000000..d04c67a61a
--- /dev/null
+++ b/application/palemoon/locales/generic/install.rdf
@@ -0,0 +1,30 @@
+
+
+
+
+
+#ifdef MOZ_LANGPACK_CONTRIBUTORS
+ @MOZ_LANGPACK_CONTRIBUTORS@
+#endif
+
+
+
+ {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+ @MOZ_APP_VERSION@
+ @MOZ_APP_MAXVERSION@
+
+
+
+
diff --git a/application/palemoon/locales/generic/profile/bookmarks.html.in b/application/palemoon/locales/generic/profile/bookmarks.html.in
new file mode 100644
index 0000000000..96270641ab
--- /dev/null
+++ b/application/palemoon/locales/generic/profile/bookmarks.html.in
@@ -0,0 +1,19 @@
+#filter substitution
+
+
+
+@bookmarks_title@
+
diff --git a/application/palemoon/locales/generic/profile/localstore.rdf b/application/palemoon/locales/generic/profile/localstore.rdf
new file mode 100644
index 0000000000..cc4b3b5105
--- /dev/null
+++ b/application/palemoon/locales/generic/profile/localstore.rdf
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/application/palemoon/locales/generic/profile/mimeTypes.rdf b/application/palemoon/locales/generic/profile/mimeTypes.rdf
new file mode 100644
index 0000000000..84079404cc
--- /dev/null
+++ b/application/palemoon/locales/generic/profile/mimeTypes.rdf
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/palemoon/locales/jar.mn b/application/palemoon/locales/jar.mn
new file mode 100644
index 0000000000..451a86ab7b
--- /dev/null
+++ b/application/palemoon/locales/jar.mn
@@ -0,0 +1,102 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale browser @AB_CD@ %locale/browser/
+ locale/browser/aboutCertError.dtd (%chrome/browser/aboutCertError.dtd)
+ locale/browser/aboutDialog.dtd (%chrome/browser/aboutDialog.dtd)
+ locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)
+ locale/browser/aboutRobots.dtd (%chrome/browser/aboutRobots.dtd)
+ locale/browser/aboutHome.dtd (%chrome/browser/aboutHome.dtd)
+ locale/browser/aboutSessionRestore.dtd (%chrome/browser/aboutSessionRestore.dtd)
+#ifdef MOZ_SERVICES_SYNC
+ locale/browser/syncProgress.dtd (%chrome/browser/syncProgress.dtd)
+ locale/browser/aboutSyncTabs.dtd (%chrome/browser/aboutSyncTabs.dtd)
+#endif
+ locale/browser/browser.dtd (%chrome/browser/browser.dtd)
+ locale/browser/baseMenuOverlay.dtd (%chrome/browser/baseMenuOverlay.dtd)
+ locale/browser/charsetOverlay.dtd (%chrome/browser/charsetOverlay.dtd)
+ locale/browser/browser.properties (%chrome/browser/browser.properties)
+ locale/browser/charsetMenu.properties (%chrome/browser/charsetMenu.properties)
+ locale/browser/charsetMenu.dtd (%chrome/browser/charsetMenu.dtd)
+ locale/browser/newTab.dtd (%chrome/browser/newTab.dtd)
+ locale/browser/newTab.properties (%chrome/browser/newTab.properties)
+ locale/browser/openLocation.dtd (%chrome/browser/openLocation.dtd)
+ locale/browser/openLocation.properties (%chrome/browser/openLocation.properties)
+ locale/browser/pageInfo.dtd (%chrome/browser/pageInfo.dtd)
+ locale/browser/pageInfo.properties (%chrome/browser/pageInfo.properties)
+ locale/browser/quitDialog.properties (%chrome/browser/quitDialog.properties)
+ locale/browser/safeMode.dtd (%chrome/browser/safeMode.dtd)
+ locale/browser/sanitize.dtd (%chrome/browser/sanitize.dtd)
+ locale/browser/search.properties (%chrome/browser/search.properties)
+ locale/browser/searchbar.dtd (%chrome/browser/searchbar.dtd)
+ locale/browser/engineManager.dtd (%chrome/browser/engineManager.dtd)
+ locale/browser/engineManager.properties (%chrome/browser/engineManager.properties)
+ locale/browser/setDesktopBackground.dtd (%chrome/browser/setDesktopBackground.dtd)
+ locale/browser/shellservice.properties (%chrome/browser/shellservice.properties)
+ locale/browser/statusbar/statusbar-overlay.dtd (%chrome/browser/statusbar/statusbar-overlay.dtd)
+ locale/browser/statusbar/statusbar-prefs.dtd (%chrome/browser/statusbar/statusbar-prefs.dtd)
+ locale/browser/statusbar/meta.properties (%chrome/browser/statusbar/meta.properties)
+ locale/browser/statusbar/overlay.properties (%chrome/browser/statusbar/overlay.properties)
+ locale/browser/statusbar/prefs.properties (%chrome/browser/statusbar/prefs.properties)
+ locale/browser/tabbrowser.dtd (%chrome/browser/tabbrowser.dtd)
+ locale/browser/tabbrowser.properties (%chrome/browser/tabbrowser.properties)
+ locale/browser/taskbar.properties (%chrome/browser/taskbar.properties)
+ locale/browser/downloads/downloads.dtd (%chrome/browser/downloads/downloads.dtd)
+ locale/browser/downloads/downloads.properties (%chrome/browser/downloads/downloads.properties)
+ locale/browser/places/places.dtd (%chrome/browser/places/places.dtd)
+ locale/browser/places/places.properties (%chrome/browser/places/places.properties)
+ locale/browser/places/editBookmarkOverlay.dtd (%chrome/browser/places/editBookmarkOverlay.dtd)
+ locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
+ locale/browser/preferences/selectBookmark.dtd (%chrome/browser/preferences/selectBookmark.dtd)
+ locale/browser/places/moveBookmarks.dtd (%chrome/browser/places/moveBookmarks.dtd)
+ locale/browser/feeds/subscribe.dtd (%chrome/browser/feeds/subscribe.dtd)
+ locale/browser/feeds/subscribe.properties (%chrome/browser/feeds/subscribe.properties)
+ locale/browser/migration/migration.dtd (%chrome/browser/migration/migration.dtd)
+ locale/browser/migration/migration.properties (%chrome/browser/migration/migration.properties)
+ locale/browser/preferences/aboutPermissions.dtd (%chrome/browser/preferences/aboutPermissions.dtd)
+ locale/browser/preferences/aboutPermissions.properties (%chrome/browser/preferences/aboutPermissions.properties)
+ locale/browser/preferences/advanced.dtd (%chrome/browser/preferences/advanced.dtd)
+ locale/browser/preferences/applicationManager.dtd (%chrome/browser/preferences/applicationManager.dtd)
+ locale/browser/preferences/applicationManager.properties (%chrome/browser/preferences/applicationManager.properties)
+ locale/browser/preferences/colors.dtd (%chrome/browser/preferences/colors.dtd)
+ locale/browser/preferences/cookies.dtd (%chrome/browser/preferences/cookies.dtd)
+ locale/browser/preferences/content.dtd (%chrome/browser/preferences/content.dtd)
+ locale/browser/preferences/connection.dtd (%chrome/browser/preferences/connection.dtd)
+ locale/browser/preferences/applications.dtd (%chrome/browser/preferences/applications.dtd)
+ locale/browser/preferences/fonts.dtd (%chrome/browser/preferences/fonts.dtd)
+ locale/browser/preferences/main.dtd (%chrome/browser/preferences/main.dtd)
+ locale/browser/preferences/languages.dtd (%chrome/browser/preferences/languages.dtd)
+ locale/browser/preferences/permissions.dtd (%chrome/browser/preferences/permissions.dtd)
+ locale/browser/preferences/preferences.dtd (%chrome/browser/preferences/preferences.dtd)
+ locale/browser/preferences/preferences.properties (%chrome/browser/preferences/preferences.properties)
+ locale/browser/preferences/privacy.dtd (%chrome/browser/preferences/privacy.dtd)
+ locale/browser/preferences/security.dtd (%chrome/browser/preferences/security.dtd)
+#ifdef MOZ_SERVICES_SYNC
+ locale/browser/preferences/sync.dtd (%chrome/browser/preferences/sync.dtd)
+#endif
+ locale/browser/preferences/tabs.dtd (%chrome/browser/preferences/tabs.dtd)
+#ifdef MOZ_SERVICES_SYNC
+ locale/browser/syncBrand.dtd (%chrome/browser/syncBrand.dtd)
+ locale/browser/syncSetup.dtd (%chrome/browser/syncSetup.dtd)
+ locale/browser/syncSetup.properties (%chrome/browser/syncSetup.properties)
+ locale/browser/syncGenericChange.properties (%chrome/browser/syncGenericChange.properties)
+ locale/browser/syncKey.dtd (%chrome/browser/syncKey.dtd)
+ locale/browser/syncQuota.dtd (%chrome/browser/syncQuota.dtd)
+ locale/browser/syncQuota.properties (%chrome/browser/syncQuota.properties)
+#endif
+% locale browser-region @AB_CD@ %locale/browser-region/
+ locale/browser-region/region.properties (%chrome/browser-region/region.properties)
+# the following files are browser-specific overrides
+ locale/browser/netError.dtd (%chrome/overrides/netError.dtd)
+ locale/browser/appstrings.properties (%chrome/overrides/appstrings.properties)
+ locale/browser/downloads/settingsChange.dtd (%chrome/overrides/settingsChange.dtd)
+% override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
+% override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
+% override chrome://mozapps/locale/downloads/settingsChange.dtd chrome://browser/locale/downloads/settingsChange.dtd
+% locale pdf.js @AB_CD@ %locale/pdfviewer/
+ locale/pdfviewer/viewer.properties (%pdfviewer/viewer.properties)
+ locale/pdfviewer/chrome.properties (%pdfviewer/chrome.properties)
diff --git a/application/palemoon/locales/l10n.ini b/application/palemoon/locales/l10n.ini
new file mode 100644
index 0000000000..df5eb7ac03
--- /dev/null
+++ b/application/palemoon/locales/l10n.ini
@@ -0,0 +1,22 @@
+# 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/.
+
+[general]
+depth = ../..
+all = browser/locales/all-locales
+
+[compare]
+dirs = browser
+ extensions/reporter
+ other-licenses/branding/firefox
+ browser/branding/official
+
+[includes]
+# non-central apps might want to use %(topsrcdir)s here, or other vars
+# RFE: that needs to be supported by compare-locales, too, though
+toolkit = toolkit/locales/l10n.ini
+services_sync = services/sync/locales/l10n.ini
+
+[extras]
+dirs = extensions/spellcheck
diff --git a/application/palemoon/locales/moz.build b/application/palemoon/locales/moz.build
new file mode 100644
index 0000000000..c97072bba2
--- /dev/null
+++ b/application/palemoon/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
diff --git a/application/palemoon/locales/shipped-locales b/application/palemoon/locales/shipped-locales
new file mode 100644
index 0000000000..96bd960a01
--- /dev/null
+++ b/application/palemoon/locales/shipped-locales
@@ -0,0 +1,90 @@
+ach
+af
+ak
+ar
+as
+ast
+be
+bg
+bn-BD
+bn-IN
+br
+bs
+ca
+cs
+csb
+cy
+da
+de
+el
+en-GB
+en-US
+en-ZA
+eo
+es-AR
+es-CL
+es-ES
+es-MX
+et
+eu
+fa
+ff
+fi
+fr
+fy-NL
+ga-IE
+gd
+gl
+gu-IN
+he
+hi-IN
+hr
+hu
+hy-AM
+id
+is
+it
+ja linux win32
+ja-JP-mac osx
+kk
+km
+kn
+ko
+ku
+lg
+lij
+lt
+lv
+mai
+mk
+ml
+mr
+nb-NO
+nl
+nn-NO
+nso
+or
+pa-IN
+pl
+pt-BR
+pt-PT
+rm
+ro
+ru
+si
+sk
+sl
+son
+sq
+sr
+sv-SE
+ta
+ta-LK
+te
+th
+tr
+uk
+vi
+zh-CN
+zh-TW
+zu
diff --git a/application/palemoon/modules/AboutHomeUtils.jsm b/application/palemoon/modules/AboutHomeUtils.jsm
new file mode 100644
index 0000000000..1d4070eafa
--- /dev/null
+++ b/application/palemoon/modules/AboutHomeUtils.jsm
@@ -0,0 +1,67 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [ "AboutHomeUtils" ];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+this.AboutHomeUtils = {
+ /**
+ * Returns an object containing the name and searchURL of the original default
+ * search engine.
+ */
+ get defaultSearchEngine() {
+ let defaultEngine = Services.search.defaultEngine;
+ let submission = defaultEngine.getSubmission("_searchTerms_", null, "homepage");
+
+ return Object.freeze({
+ name: defaultEngine.name,
+ searchURL: submission.uri.spec,
+ postDataString: submission.postDataString
+ });
+ },
+
+ /*
+ * showKnowYourRights - Determines if the user should be shown the
+ * about:rights notification. The notification should *not* be shown if
+ * we've already shown the current version, or if the override pref says to
+ * never show it. The notification *should* be shown if it's never been seen
+ * before, if a newer version is available, or if the override pref says to
+ * always show it.
+ */
+ get showKnowYourRights() {
+ // Look for an unconditional override pref. If set, do what it says.
+ // (true --> never show, false --> always show)
+ try {
+ return !Services.prefs.getBoolPref("browser.rights.override");
+ } catch (e) { }
+ // Ditto, for the legacy EULA pref.
+ try {
+ return !Services.prefs.getBoolPref("browser.EULA.override");
+ } catch (e) { }
+
+#ifndef MOZILLA_OFFICIAL
+ // Non-official builds shouldn't show the notification.
+ return false;
+#endif
+
+ // Look to see if the user has seen the current version or not.
+ var currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ try {
+ return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown");
+ } catch (e) { }
+
+ // Legacy: If the user accepted a EULA, we won't annoy them with the
+ // equivalent about:rights page until the version changes.
+ try {
+ return !Services.prefs.getBoolPref("browser.EULA." + currentVersion + ".accepted");
+ } catch (e) { }
+
+ // We haven't shown the notification before, so do so now.
+ return true;
+ }
+};
diff --git a/application/palemoon/modules/BrowserNewTabPreloader.jsm b/application/palemoon/modules/BrowserNewTabPreloader.jsm
new file mode 100644
index 0000000000..719a9e05e2
--- /dev/null
+++ b/application/palemoon/modules/BrowserNewTabPreloader.jsm
@@ -0,0 +1,436 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["BrowserNewTabPreloader"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,";
+const NEWTAB_URL = "about:newtab";
+const PREF_BRANCH = "browser.newtab.";
+
+// The interval between swapping in a preload docShell and kicking off the
+// next preload in the background.
+const PRELOADER_INTERVAL_MS = 600;
+// The initial delay before we start preloading our first new tab page. The
+// timer is started after the first 'browser-delayed-startup' has been sent.
+const PRELOADER_INIT_DELAY_MS = 5000;
+// The number of miliseconds we'll wait after we received a notification that
+// causes us to update our list of browsers and tabbrowser sizes. This acts as
+// kind of a damper when too many events are occuring in quick succession.
+const PRELOADER_UPDATE_DELAY_MS = 3000;
+
+const TOPIC_TIMER_CALLBACK = "timer-callback";
+const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
+const TOPIC_XUL_WINDOW_CLOSED = "xul-window-destroyed";
+
+function createTimer(obj, delay) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+}
+
+function clearTimer(timer) {
+ if (timer) {
+ timer.cancel();
+ }
+ return null;
+}
+
+this.BrowserNewTabPreloader = {
+ init: function Preloader_init() {
+ Initializer.start();
+ },
+
+ uninit: function Preloader_uninit() {
+ Initializer.stop();
+ HostFrame.destroy();
+ Preferences.uninit();
+ HiddenBrowsers.uninit();
+ },
+
+ newTab: function Preloader_newTab(aTab) {
+ let win = aTab.ownerDocument.defaultView;
+ if (win.gBrowser) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let {width, height} = utils.getBoundsWithoutFlushing(win.gBrowser);
+ let hiddenBrowser = HiddenBrowsers.get(width, height)
+ if (hiddenBrowser) {
+ return hiddenBrowser.swapWithNewTab(aTab);
+ }
+ }
+
+ return false;
+ }
+};
+
+Object.freeze(BrowserNewTabPreloader);
+
+let Initializer = {
+ _timer: null,
+ _observing: false,
+
+ start: function Initializer_start() {
+ Services.obs.addObserver(this, TOPIC_DELAYED_STARTUP, false);
+ this._observing = true;
+ },
+
+ stop: function Initializer_stop() {
+ this._timer = clearTimer(this._timer);
+
+ if (this._observing) {
+ Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
+ this._observing = false;
+ }
+ },
+
+ observe: function Initializer_observe(aSubject, aTopic, aData) {
+ if (aTopic == TOPIC_DELAYED_STARTUP) {
+ Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
+ this._observing = false;
+ this._startTimer();
+ } else if (aTopic == TOPIC_TIMER_CALLBACK) {
+ this._timer = null;
+ this._startPreloader();
+ }
+ },
+
+ _startTimer: function Initializer_startTimer() {
+ this._timer = createTimer(this, PRELOADER_INIT_DELAY_MS);
+ },
+
+ _startPreloader: function Initializer_startPreloader() {
+ Preferences.init();
+ if (Preferences.enabled) {
+ HiddenBrowsers.init();
+ }
+ }
+};
+
+let Preferences = {
+ _enabled: null,
+ _branch: null,
+
+ get enabled() {
+ if (this._enabled === null) {
+ this._enabled = this._branch.getBoolPref("preload") &&
+ !this._branch.prefHasUserValue("url");
+ }
+
+ return this._enabled;
+ },
+
+ init: function Preferences_init() {
+ this._branch = Services.prefs.getBranch(PREF_BRANCH);
+ this._branch.addObserver("", this, false);
+ },
+
+ uninit: function Preferences_uninit() {
+ if (this._branch) {
+ this._branch.removeObserver("", this);
+ this._branch = null;
+ }
+ },
+
+ observe: function Preferences_observe() {
+ let prevEnabled = this._enabled;
+ this._enabled = null;
+
+ if (prevEnabled && !this.enabled) {
+ HiddenBrowsers.uninit();
+ } else if (!prevEnabled && this.enabled) {
+ HiddenBrowsers.init();
+ }
+ },
+};
+
+let HiddenBrowsers = {
+ _browsers: null,
+ _updateTimer: null,
+
+ _topics: [
+ TOPIC_DELAYED_STARTUP,
+ TOPIC_XUL_WINDOW_CLOSED
+ ],
+
+ init: function () {
+ this._browsers = new Map();
+ this._updateBrowserSizes();
+ this._topics.forEach(t => Services.obs.addObserver(this, t, false));
+ },
+
+ uninit: function () {
+ if (this._browsers) {
+ this._topics.forEach(t => Services.obs.removeObserver(this, t, false));
+ this._updateTimer = clearTimer(this._updateTimer);
+
+ for (let [key, browser] of this._browsers) {
+ browser.destroy();
+ }
+ this._browsers = null;
+ }
+ },
+
+ get: function (width, height) {
+ // We haven't been initialized, yet.
+ if (!this._browsers) {
+ return null;
+ }
+
+ let key = width + "x" + height;
+ if (!this._browsers.has(key)) {
+ // Update all browsers' sizes if we can't find a matching one.
+ this._updateBrowserSizes();
+ }
+
+ // We should now have a matching browser.
+ if (this._browsers.has(key)) {
+ return this._browsers.get(key);
+ }
+
+ // We should never be here. Return the first browser we find.
+ Cu.reportError("NewTabPreloader: no matching browser found after updating");
+ for (let [size, browser] of this._browsers) {
+ return browser;
+ }
+
+ // We should really never be here.
+ Cu.reportError("NewTabPreloader: not even a single browser was found?");
+ return null;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic === TOPIC_TIMER_CALLBACK) {
+ this._updateTimer = null;
+ this._updateBrowserSizes();
+ } else {
+ this._updateTimer = clearTimer(this._updateTimer);
+ this._updateTimer = createTimer(this, PRELOADER_UPDATE_DELAY_MS);
+ }
+ },
+
+ _updateBrowserSizes: function () {
+ let sizes = this._collectTabBrowserSizes();
+ let toRemove = [];
+
+ // Iterate all browsers and check that they
+ // each can be assigned to one of the sizes.
+ for (let [key, browser] of this._browsers) {
+ if (sizes.has(key)) {
+ // We already have a browser for that size, great!
+ sizes.delete(key);
+ } else {
+ // This browser is superfluous or needs to be resized.
+ toRemove.push(browser);
+ this._browsers.delete(key);
+ }
+ }
+
+ // Iterate all sizes that we couldn't find a browser for.
+ for (let [key, {width, height}] of sizes) {
+ let browser;
+ if (toRemove.length) {
+ // Let's just resize one of the superfluous
+ // browsers and put it back into the map.
+ browser = toRemove.shift();
+ browser.resize(width, height);
+ } else {
+ // No more browsers to reuse, create a new one.
+ browser = new HiddenBrowser(width, height);
+ }
+
+ this._browsers.set(key, browser);
+ }
+
+ // Finally, remove all browsers we don't need anymore.
+ toRemove.forEach(b => b.destroy());
+ },
+
+ _collectTabBrowserSizes: function () {
+ let sizes = new Map();
+
+ function tabBrowserBounds() {
+ let wins = Services.ww.getWindowEnumerator("navigator:browser");
+ while (wins.hasMoreElements()) {
+ let win = wins.getNext();
+ if (win.gBrowser) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ yield utils.getBoundsWithoutFlushing(win.gBrowser);
+ }
+ }
+ }
+
+ // Collect the sizes of all s out there.
+ for (let {width, height} of tabBrowserBounds()) {
+ if (width > 0 && height > 0) {
+ let key = width + "x" + height;
+ if (!sizes.has(key)) {
+ sizes.set(key, {width: width, height: height});
+ }
+ }
+ }
+
+ return sizes;
+ }
+};
+
+function HiddenBrowser(width, height) {
+ this.resize(width, height);
+
+ HostFrame.get().then(aFrame => {
+ let doc = aFrame.document;
+ this._browser = doc.createElementNS(XUL_NS, "browser");
+ this._browser.setAttribute("type", "content");
+ this._browser.setAttribute("src", NEWTAB_URL);
+ this._applySize();
+ doc.getElementById("win").appendChild(this._browser);
+ });
+}
+
+HiddenBrowser.prototype = {
+ _width: null,
+ _height: null,
+ _timer: null,
+ _needsFrameScripts: true,
+
+ get isPreloaded() {
+ return this._browser &&
+ this._browser.contentDocument &&
+ this._browser.contentDocument.readyState === "complete" &&
+ this._browser.currentURI.spec === NEWTAB_URL;
+ },
+
+ swapWithNewTab: function (aTab) {
+ if (!this.isPreloaded || this._timer) {
+ return false;
+ }
+
+ let win = aTab.ownerDocument.defaultView;
+ let tabbrowser = win.gBrowser;
+
+ if (!tabbrowser) {
+ return false;
+ }
+
+ // Swap docShells.
+ tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
+
+ // Load all default frame scripts.
+ if (this._needsFrameScripts) {
+ this._needsFrameScripts = false;
+
+ let mm = aTab.linkedBrowser.messageManager;
+ mm.loadFrameScript("chrome://browser/content/content.js", true);
+ mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
+
+ if ("TabView" in win) {
+ mm.loadFrameScript("chrome://browser/content/tabview-content.js", true);
+ }
+ }
+
+ // Start a timer that will kick off preloading the next newtab page.
+ this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
+
+ // Signal that we swapped docShells.
+ return true;
+ },
+
+ observe: function () {
+ this._timer = null;
+
+ // Start pre-loading the new tab page.
+ this._browser.loadURI(NEWTAB_URL);
+ },
+
+ resize: function (width, height) {
+ this._width = width;
+ this._height = height;
+ this._applySize();
+ },
+
+ _applySize: function () {
+ if (this._browser) {
+ this._browser.style.width = this._width + "px";
+ this._browser.style.height = this._height + "px";
+ }
+ },
+
+ destroy: function () {
+ if (this._browser) {
+ this._browser.remove();
+ this._browser = null;
+ }
+
+ this._timer = clearTimer(this._timer);
+ }
+};
+
+let HostFrame = {
+ _frame: null,
+ _deferred: null,
+
+ get hiddenDOMDocument() {
+ return Services.appShell.hiddenDOMWindow.document;
+ },
+
+ get isReady() {
+ return this.hiddenDOMDocument.readyState === "complete";
+ },
+
+ get: function () {
+ if (!this._deferred) {
+ this._deferred = Promise.defer();
+ this._create();
+ }
+
+ return this._deferred.promise;
+ },
+
+ destroy: function () {
+ if (this._frame) {
+ if (!Cu.isDeadWrapper(this._frame)) {
+ this._frame.removeEventListener("load", this, true);
+ this._frame.remove();
+ }
+
+ this._frame = null;
+ this._deferred = null;
+ }
+ },
+
+ handleEvent: function () {
+ let contentWindow = this._frame.contentWindow;
+ if (contentWindow.location.href === XUL_PAGE) {
+ this._frame.removeEventListener("load", this, true);
+ this._deferred.resolve(contentWindow);
+ } else {
+ contentWindow.location = XUL_PAGE;
+ }
+ },
+
+ _create: function () {
+ if (this.isReady) {
+ let doc = this.hiddenDOMDocument;
+ this._frame = doc.createElementNS(HTML_NS, "iframe");
+ this._frame.addEventListener("load", this, true);
+ doc.documentElement.appendChild(this._frame);
+ } else {
+ let flags = Ci.nsIThread.DISPATCH_NORMAL;
+ Services.tm.currentThread.dispatch(() => this._create(), flags);
+ }
+ }
+};
diff --git a/application/palemoon/modules/CharsetMenu.jsm b/application/palemoon/modules/CharsetMenu.jsm
new file mode 100644
index 0000000000..f973088bc5
--- /dev/null
+++ b/application/palemoon/modules/CharsetMenu.jsm
@@ -0,0 +1,160 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = [ "CharsetMenu" ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "gBundle", function() {
+ const kUrl = "chrome://browser/locale/charsetMenu.properties";
+ return Services.strings.createBundle(kUrl);
+});
+/**
+ * This set contains encodings that are in the Encoding Standard, except:
+ * - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be
+ * too common not to be included).
+ * - x-user-defined, which practically never makes sense as an end-user-chosen
+ * override.
+ * - Encodings that IE11 doesn't have in its correspoding menu.
+ */
+const kEncodings = new Set([
+ // Globally relevant
+ "UTF-8",
+ "windows-1252",
+ // Arabic
+ "windows-1256",
+ "ISO-8859-6",
+ // Baltic
+ "windows-1257",
+ "ISO-8859-4",
+ // "ISO-8859-13", // Hidden since not in menu in IE11
+ // Central European
+ "windows-1250",
+ "ISO-8859-2",
+ // Chinese, Simplified
+ "gbk",
+ "gb18030",
+ // Chinese, Traditional
+ "Big5",
+ // Cyrillic
+ "windows-1251",
+ "ISO-8859-5",
+ "KOI8-R",
+ "KOI8-U",
+ "IBM866", // Not in menu in Chromium. Maybe drop this?
+ // "x-mac-cyrillic", // Not in menu in IE11 or Chromium.
+ // Greek
+ "windows-1253",
+ "ISO-8859-7",
+ // Hebrew
+ "windows-1255",
+ "ISO-8859-8-I",
+ "ISO-8859-8",
+ // Japanese
+ "Shift_JIS",
+ "EUC-JP",
+ "ISO-2022-JP",
+ // Korean
+ "EUC-KR",
+ // Thai
+ "windows-874",
+ // Turkish
+ "windows-1254",
+ // Vietnamese
+ "windows-1258",
+ // Hiding rare European encodings that aren't in the menu in IE11 and would
+ // make the menu messy by sorting all over the place
+ // "ISO-8859-3",
+ // "ISO-8859-10",
+ // "ISO-8859-14",
+ // "ISO-8859-15",
+ // "ISO-8859-16",
+ // "macintosh"
+]);
+
+// Always at the start of the menu, in this order, followed by a separator.
+const kPinned = [
+ "UTF-8",
+ "windows-1252"
+];
+
+this.CharsetMenu = Object.freeze({
+ build: function BuildCharsetMenu(event) {
+ let parent = event.target;
+ if (parent.lastChild.localName != "menuseparator") {
+ // Detector menu or charset menu already built
+ return;
+ }
+ let doc = parent.ownerDocument;
+
+ function createItem(encoding) {
+ let menuItem = doc.createElement("menuitem");
+ menuItem.setAttribute("type", "radio");
+ menuItem.setAttribute("name", "charsetGroup");
+ try {
+ menuItem.setAttribute("label", gBundle.GetStringFromName(encoding));
+ } catch (e) {
+ // Localization error but put *something* in the menu to recover.
+ menuItem.setAttribute("label", encoding);
+ }
+ try {
+ menuItem.setAttribute("accesskey",
+ gBundle.GetStringFromName(encoding + ".key"));
+ } catch (e) {
+ // Some items intentionally don't have an accesskey
+ }
+ menuItem.setAttribute("id", "charset." + encoding);
+ return menuItem;
+ }
+
+ // Clone the set in order to be able to remove the pinned encodings from
+ // the cloned set.
+ let encodings = new Set(kEncodings);
+ for (let encoding of kPinned) {
+ encodings.delete(encoding);
+ parent.appendChild(createItem(encoding));
+ }
+ parent.appendChild(doc.createElement("menuseparator"));
+ let list = [];
+ for (let encoding of encodings) {
+ list.push(createItem(encoding));
+ }
+
+ list.sort(function (a, b) {
+ let titleA = a.getAttribute("label");
+ let titleB = b.getAttribute("label");
+ // Normal sorting sorts the part in parenthesis in an order that
+ // happens to make the less frequently-used items first.
+ let index;
+ if ((index = titleA.indexOf("(")) > -1) {
+ titleA = titleA.substring(0, index);
+ }
+ if ((index = titleB.indexOf("(")) > -1) {
+ titleA = titleB.substring(0, index);
+ }
+ let comp = titleA.localeCompare(titleB);
+ if (comp) {
+ return comp;
+ }
+ // secondarily reverse sort by encoding name to sort "windows" or
+ // "shift_jis" first. This works regardless of localization, because
+ // the ids aren't localized.
+ let idA = a.getAttribute("id");
+ let idB = b.getAttribute("id");
+ if (idA < idB) {
+ return 1;
+ }
+ if (idB < idA) {
+ return -1;
+ }
+ return 0;
+ });
+
+ for (let item of list) {
+ parent.appendChild(item);
+ }
+ },
+});
\ No newline at end of file
diff --git a/application/palemoon/modules/FormSubmitObserver.jsm b/application/palemoon/modules/FormSubmitObserver.jsm
new file mode 100644
index 0000000000..c058f2acfd
--- /dev/null
+++ b/application/palemoon/modules/FormSubmitObserver.jsm
@@ -0,0 +1,251 @@
+/* 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/. */
+
+/*
+ * Handles the validation callback from nsIFormFillController and
+ * the display of the help panel on invalid elements.
+ */
+
+"use strict";
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+let HTMLInputElement = Ci.nsIDOMHTMLInputElement;
+let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
+let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
+let HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
+
+this.EXPORTED_SYMBOLS = [ "FormSubmitObserver" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/BrowserUtils.jsm");
+
+function FormSubmitObserver(aWindow, aTabChildGlobal) {
+ this.init(aWindow, aTabChildGlobal);
+}
+
+FormSubmitObserver.prototype =
+{
+ _validationMessage: "",
+ _content: null,
+ _element: null,
+
+ /*
+ * Public apis
+ */
+
+ init: function(aWindow, aTabChildGlobal)
+ {
+ this._content = aWindow;
+ this._tab = aTabChildGlobal;
+ this._mm =
+ this._content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ // nsIFormSubmitObserver callback about invalid forms. See HTMLFormElement
+ // for details.
+ Services.obs.addObserver(this, "invalidformsubmit", false);
+ this._tab.addEventListener("pageshow", this, false);
+ this._tab.addEventListener("unload", this, false);
+ },
+
+ uninit: function()
+ {
+ Services.obs.removeObserver(this, "invalidformsubmit");
+ this._content.removeEventListener("pageshow", this, false);
+ this._content.removeEventListener("unload", this, false);
+ this._mm = null;
+ this._element = null;
+ this._content = null;
+ this._tab = null;
+ },
+
+ /*
+ * Events
+ */
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "pageshow":
+ if (this._isRootDocumentEvent(aEvent)) {
+ this._hidePopup();
+ }
+ break;
+ case "unload":
+ this.uninit();
+ break;
+ case "input":
+ this._onInput(aEvent);
+ break;
+ case "blur":
+ this._onBlur(aEvent);
+ break;
+ }
+ },
+
+ /*
+ * nsIFormSubmitObserver
+ */
+
+ notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+ {
+ // We are going to handle invalid form submission attempt by focusing the
+ // first invalid element and show the corresponding validation message in a
+ // panel attached to the element.
+ if (!aInvalidElements.length) {
+ return;
+ }
+
+ // Insure that this is the FormSubmitObserver associated with the form
+ // element / window this notification is about.
+ if (this._content != aFormElement.ownerDocument.defaultView.top.document.defaultView) {
+ return;
+ }
+
+ let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
+ if (!(element instanceof HTMLInputElement ||
+ element instanceof HTMLTextAreaElement ||
+ element instanceof HTMLSelectElement ||
+ element instanceof HTMLButtonElement)) {
+ return;
+ }
+
+ // Don't connect up to the same element more than once.
+ if (this._element == element) {
+ this._showPopup(element);
+ return;
+ }
+ this._element = element;
+
+ element.focus();
+
+ this._validationMessage = element.validationMessage;
+
+ // Watch for input changes which may change the validation message.
+ element.addEventListener("input", this, false);
+
+ // Watch for focus changes so we can disconnect our listeners and
+ // hide the popup.
+ element.addEventListener("blur", this, false);
+
+ this._showPopup(element);
+ },
+
+ /*
+ * Internal
+ */
+
+ /*
+ * Handles input changes on the form element we've associated a popup
+ * with. Updates the validation message or closes the popup if form data
+ * becomes valid.
+ */
+ _onInput: function (aEvent) {
+ let element = aEvent.originalTarget;
+
+ // If the form input is now valid, hide the popup.
+ if (element.validity.valid) {
+ this._hidePopup();
+ return;
+ }
+
+ // If the element is still invalid for a new reason, we should update
+ // the popup error message.
+ if (this._validationMessage != element.validationMessage) {
+ this._validationMessage = element.validationMessage;
+ this._showPopup(element);
+ }
+ },
+
+ /*
+ * Blur event handler in which we disconnect from the form element and
+ * hide the popup.
+ */
+ _onBlur: function (aEvent) {
+ aEvent.originalTarget.removeEventListener("input", this, false);
+ aEvent.originalTarget.removeEventListener("blur", this, false);
+ this._element = null;
+ this._hidePopup();
+ },
+
+ /*
+ * Send the show popup message to chrome with appropriate position
+ * information. Can be called repetitively to update the currently
+ * displayed popup position and text.
+ */
+ _showPopup: function (aElement) {
+ // Collect positional information and show the popup
+ let panelData = {};
+
+ panelData.message = this._validationMessage;
+
+ // Note, this is relative to the browser and needs to be translated
+ // in chrome.
+ panelData.contentRect = this._msgRect(aElement);
+
+ // We want to show the popup at the middle of checkbox and radio buttons
+ // and where the content begin for the other elements.
+ let offset = 0;
+ let position = "";
+
+ if (aElement.tagName == 'INPUT' &&
+ (aElement.type == 'radio' || aElement.type == 'checkbox')) {
+ panelData.position = "bottomcenter topleft";
+ } else {
+ let win = aElement.ownerDocument.defaultView;
+ let style = win.getComputedStyle(aElement, null);
+ if (style.direction == 'rtl') {
+ offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
+ } else {
+ offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
+ }
+ let zoomFactor = this._getWindowUtils().fullZoom;
+ panelData.offset = Math.round(offset * zoomFactor);
+ panelData.position = "after_start";
+ }
+ this._mm.sendAsyncMessage("FormValidation:ShowPopup", panelData);
+ },
+
+ _hidePopup: function () {
+ this._mm.sendAsyncMessage("FormValidation:HidePopup", {});
+ },
+
+ _getWindowUtils: function () {
+ return this._content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ _isRootDocumentEvent: function (aEvent) {
+ if (this._content == null) {
+ return true;
+ }
+ let target = aEvent.originalTarget;
+ return (target == this._content.document ||
+ (target.ownerDocument && target.ownerDocument == this._content.document));
+ },
+
+ /*
+ * Return a message manager rect for the element's bounding client rect
+ * in top level browser coords.
+ */
+ _msgRect: function (aElement) {
+ let domRect = aElement.getBoundingClientRect();
+ let zoomFactor = this._getWindowUtils().fullZoom;
+ let { offsetX, offsetY } = BrowserUtils.offsetToTopLevelWindow(this._content, aElement);
+ return {
+ left: (domRect.left + offsetX) * zoomFactor,
+ top: (domRect.top + offsetY) * zoomFactor,
+ width: domRect.width * zoomFactor,
+ height: domRect.height * zoomFactor
+ };
+ },
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver])
+};
diff --git a/application/palemoon/modules/FormValidationHandler.jsm b/application/palemoon/modules/FormValidationHandler.jsm
new file mode 100644
index 0000000000..05be510e18
--- /dev/null
+++ b/application/palemoon/modules/FormValidationHandler.jsm
@@ -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/. */
+
+/*
+ * Chrome side handling of form validation popup.
+ */
+
+"use strict";
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "FormValidationHandler" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+let FormValidationHandler =
+{
+ _panel: null,
+ _anchor: null,
+
+ /*
+ * Public apis
+ */
+
+ init: function () {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("FormValidation:ShowPopup", this);
+ mm.addMessageListener("FormValidation:HidePopup", this);
+ },
+
+ uninit: function () {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.removeMessageListener("FormValidation:ShowPopup", this);
+ mm.removeMessageListener("FormValidation:HidePopup", this);
+ this._panel = null;
+ this._anchor = null;
+ },
+
+ hidePopup: function () {
+ this._hidePopup();
+ },
+
+ /*
+ * Events
+ */
+
+ receiveMessage: function (aMessage) {
+ let window = aMessage.target.ownerDocument.defaultView;
+ let json = aMessage.json;
+ let tabBrowser = window.gBrowser;
+ switch (aMessage.name) {
+ case "FormValidation:ShowPopup":
+ // target is the , make sure we're receiving a message
+ // from the foreground tab.
+ if (tabBrowser && aMessage.target != tabBrowser.selectedBrowser) {
+ return;
+ }
+ this._showPopup(window, json);
+ break;
+ case "FormValidation:HidePopup":
+ this._hidePopup();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ this._hidePopup();
+ },
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "FullZoomChange":
+ case "TextZoomChange":
+ case "ZoomChangeUsingMouseWheel":
+ case "scroll":
+ this._hidePopup();
+ break;
+ case "popuphiding":
+ this._onPopupHiding(aEvent);
+ break;
+ }
+ },
+
+ /*
+ * Internal
+ */
+
+ _onPopupHiding: function (aEvent) {
+ aEvent.originalTarget.removeEventListener("popuphiding", this, true);
+ let tabBrowser = aEvent.originalTarget.ownerDocument.getElementById("content");
+ tabBrowser.selectedBrowser.removeEventListener("scroll", this, true);
+ tabBrowser.selectedBrowser.removeEventListener("FullZoomChange", this, false);
+ tabBrowser.selectedBrowser.removeEventListener("TextZoomChange", this, false);
+ tabBrowser.selectedBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this, false);
+
+ this._panel.hidden = true;
+ this._panel = null;
+ this._anchor.hidden = true;
+ this._anchor = null;
+ },
+
+ /*
+ * Shows the form validation popup at a specified position or updates the
+ * messaging and position if the popup is already displayed.
+ *
+ * @aWindow - the chrome window
+ * @aPanelData - Object that contains popup information
+ * aPanelData stucture detail:
+ * contentRect - the bounding client rect of the target element. If
+ * content is remote, this is relative to the browser, otherwise its
+ * relative to the window.
+ * position - popup positional string constants.
+ * message - the form element validation message text.
+ */
+ _showPopup: function (aWindow, aPanelData) {
+ let previouslyShown = !!this._panel;
+ this._panel = aWindow.document.getElementById("invalid-form-popup");
+ this._panel.firstChild.textContent = aPanelData.message;
+ this._panel.hidden = false;
+
+ let tabBrowser = aWindow.gBrowser;
+ this._anchor = tabBrowser.formValidationAnchor;
+ this._anchor.left = aPanelData.contentRect.left;
+ this._anchor.top = aPanelData.contentRect.top;
+ this._anchor.width = aPanelData.contentRect.width;
+ this._anchor.height = aPanelData.contentRect.height;
+ this._anchor.hidden = false;
+
+ // Display the panel if it isn't already visible.
+ if (!previouslyShown) {
+ // Cleanup after the popup is hidden
+ this._panel.addEventListener("popuphiding", this, true);
+
+ // Hide if the user scrolls the page
+ tabBrowser.selectedBrowser.addEventListener("scroll", this, true);
+ tabBrowser.selectedBrowser.addEventListener("FullZoomChange", this, false);
+ tabBrowser.selectedBrowser.addEventListener("TextZoomChange", this, false);
+ tabBrowser.selectedBrowser.addEventListener("ZoomChangeUsingMouseWheel", this, false);
+
+ // Open the popup
+ this._panel.openPopup(this._anchor, aPanelData.position, 0, 0, false);
+ }
+ },
+
+ /*
+ * Hide the popup if currently displayed. Will fire an event to onPopupHiding
+ * above if visible.
+ */
+ _hidePopup: function () {
+ if (this._panel) {
+ this._panel.hidePopup();
+ }
+ }
+};
diff --git a/application/palemoon/modules/NetworkPrioritizer.jsm b/application/palemoon/modules/NetworkPrioritizer.jsm
new file mode 100644
index 0000000000..ea4a877904
--- /dev/null
+++ b/application/palemoon/modules/NetworkPrioritizer.jsm
@@ -0,0 +1,179 @@
+/* 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/. */
+
+/*
+ * This module adjusts network priority for tabs in a way that gives 'important'
+ * tabs a higher priority. There are 3 levels of priority. Each is listed below
+ * with the priority adjustment used.
+ *
+ * Highest (-10): Selected tab in the focused window.
+ * Medium (0): Background tabs in the focused window.
+ * Selected tab in background windows.
+ * Lowest (+10): Background tabs in background windows.
+ */
+
+this.EXPORTED_SYMBOLS = ["trackBrowserWindow"];
+
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+
+// Lazy getters
+XPCOMUtils.defineLazyServiceGetter(this, "_focusManager",
+ "@mozilla.org/focus-manager;1",
+ "nsIFocusManager");
+
+
+// Constants
+const TAB_EVENTS = ["TabOpen", "TabSelect"];
+const WINDOW_EVENTS = ["activate", "unload"];
+// PRIORITY DELTA is -10 because lower priority value is actually a higher priority
+const PRIORITY_DELTA = -10;
+
+
+// Variables
+let _lastFocusedWindow = null;
+let _windows = [];
+
+
+// Exported symbol
+this.trackBrowserWindow = function trackBrowserWindow(aWindow) {
+ WindowHelper.addWindow(aWindow);
+}
+
+
+// Global methods
+function _handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "TabOpen":
+ BrowserHelper.onOpen(aEvent.target.linkedBrowser);
+ break;
+ case "TabSelect":
+ BrowserHelper.onSelect(aEvent.target.linkedBrowser);
+ break;
+ case "activate":
+ WindowHelper.onActivate(aEvent.target);
+ break;
+ case "unload":
+ WindowHelper.removeWindow(aEvent.currentTarget);
+ break;
+ }
+}
+
+
+// Methods that impact a browser. Put into single object for organization.
+let BrowserHelper = {
+ onOpen: function NP_BH_onOpen(aBrowser) {
+ // If the tab is in the focused window, leave priority as it is
+ if (aBrowser.ownerDocument.defaultView != _lastFocusedWindow)
+ this.decreasePriority(aBrowser);
+ },
+
+ onSelect: function NP_BH_onSelect(aBrowser) {
+ let windowEntry = WindowHelper.getEntry(aBrowser.ownerDocument.defaultView);
+ if (windowEntry.lastSelectedBrowser)
+ this.decreasePriority(windowEntry.lastSelectedBrowser);
+ this.increasePriority(aBrowser);
+
+ windowEntry.lastSelectedBrowser = aBrowser;
+ },
+
+ increasePriority: function NP_BH_increasePriority(aBrowser) {
+ aBrowser.adjustPriority(PRIORITY_DELTA);
+ },
+
+ decreasePriority: function NP_BH_decreasePriority(aBrowser) {
+ aBrowser.adjustPriority(PRIORITY_DELTA * -1);
+ }
+};
+
+
+// Methods that impact a window. Put into single object for organization.
+let WindowHelper = {
+ addWindow: function NP_WH_addWindow(aWindow) {
+ // Build internal data object
+ _windows.push({ window: aWindow, lastSelectedBrowser: null });
+
+ // Add event listeners
+ TAB_EVENTS.forEach(function(event) {
+ aWindow.gBrowser.tabContainer.addEventListener(event, _handleEvent, false);
+ });
+ WINDOW_EVENTS.forEach(function(event) {
+ aWindow.addEventListener(event, _handleEvent, false);
+ });
+
+ // This gets called AFTER activate event, so if this is the focused window
+ // we want to activate it. Otherwise, deprioritize it.
+ if (aWindow == _focusManager.activeWindow)
+ this.handleFocusedWindow(aWindow);
+ else
+ this.decreasePriority(aWindow);
+
+ // Select the selected tab
+ BrowserHelper.onSelect(aWindow.gBrowser.selectedBrowser);
+ },
+
+ removeWindow: function NP_WH_removeWindow(aWindow) {
+ if (aWindow == _lastFocusedWindow)
+ _lastFocusedWindow = null;
+
+ // Delete this window from our tracking
+ _windows.splice(this.getEntryIndex(aWindow), 1);
+
+ // Remove the event listeners
+ TAB_EVENTS.forEach(function(event) {
+ aWindow.gBrowser.tabContainer.removeEventListener(event, _handleEvent, false);
+ });
+ WINDOW_EVENTS.forEach(function(event) {
+ aWindow.removeEventListener(event, _handleEvent, false);
+ });
+ },
+
+ onActivate: function NP_WH_onActivate(aWindow, aHasFocus) {
+ // If this window was the last focused window, we don't need to do anything
+ if (aWindow == _lastFocusedWindow)
+ return;
+
+ // handleFocusedWindow will deprioritize the current window
+ this.handleFocusedWindow(aWindow);
+
+ // Lastly we should increase priority for this window
+ this.increasePriority(aWindow);
+ },
+
+ handleFocusedWindow: function NP_WH_handleFocusedWindow(aWindow) {
+ // If we have a last focused window, we need to deprioritize it first
+ if (_lastFocusedWindow)
+ this.decreasePriority(_lastFocusedWindow);
+
+ // aWindow is now focused
+ _lastFocusedWindow = aWindow;
+ },
+
+ // Auxiliary methods
+ increasePriority: function NP_WH_increasePriority(aWindow) {
+ aWindow.gBrowser.browsers.forEach(function(aBrowser) {
+ BrowserHelper.increasePriority(aBrowser);
+ });
+ },
+
+ decreasePriority: function NP_WH_decreasePriority(aWindow) {
+ aWindow.gBrowser.browsers.forEach(function(aBrowser) {
+ BrowserHelper.decreasePriority(aBrowser);
+ });
+ },
+
+ getEntry: function NP_WH_getEntry(aWindow) {
+ return _windows[this.getEntryIndex(aWindow)];
+ },
+
+ getEntryIndex: function NP_WH_getEntryAtIndex(aWindow) {
+ // Assumes that every object has a unique window & it's in the array
+ for (let i = 0; i < _windows.length; i++)
+ if (_windows[i].window == aWindow)
+ return i;
+ }
+};
+
diff --git a/application/palemoon/modules/PageMenu.jsm b/application/palemoon/modules/PageMenu.jsm
new file mode 100644
index 0000000000..d01f62601a
--- /dev/null
+++ b/application/palemoon/modules/PageMenu.jsm
@@ -0,0 +1,238 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["PageMenu"];
+
+this.PageMenu = function PageMenu() {
+}
+
+PageMenu.prototype = {
+ PAGEMENU_ATTR: "pagemenu",
+ GENERATEDITEMID_ATTR: "generateditemid",
+
+ _popup: null,
+ _builder: null,
+
+ // Given a target node, get the context menu for it or its ancestor.
+ getContextMenu: function(aTarget) {
+ let target = aTarget;
+ while (target) {
+ let contextMenu = target.contextMenu;
+ if (contextMenu) {
+ return contextMenu;
+ }
+ target = target.parentNode;
+ }
+
+ return null;
+ },
+
+ // Given a target node, generate a JSON object for any context menu
+ // associated with it, or null if there is no context menu.
+ maybeBuild: function(aTarget) {
+ let pageMenu = this.getContextMenu(aTarget);
+ if (!pageMenu) {
+ return null;
+ }
+
+ pageMenu.QueryInterface(Components.interfaces.nsIHTMLMenu);
+ pageMenu.sendShowEvent();
+ // the show event is not cancelable, so no need to check a result here
+
+ this._builder = pageMenu.createBuilder();
+ if (!this._builder) {
+ return null;
+ }
+
+ pageMenu.build(this._builder);
+
+ // This serializes then parses again, however this could be avoided in
+ // the single-process case with further improvement.
+ let menuString = this._builder.toJSONString();
+ if (!menuString) {
+ return null;
+ }
+
+ return JSON.parse(menuString);
+ },
+
+ // Given a JSON menu object and popup, add the context menu to the popup.
+ buildAndAttachMenuWithObject: function(aMenu, aBrowser, aPopup) {
+ if (!aMenu) {
+ return false;
+ }
+
+ let insertionPoint = this.getInsertionPoint(aPopup);
+ if (!insertionPoint) {
+ return false;
+ }
+
+ let fragment = aPopup.ownerDocument.createDocumentFragment();
+ this.buildXULMenu(aMenu, fragment);
+
+ let pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
+ if (pos == "start") {
+ insertionPoint.insertBefore(fragment,
+ insertionPoint.firstChild);
+ } else if (pos.startsWith("#")) {
+ insertionPoint.insertBefore(fragment, insertionPoint.querySelector(pos));
+ } else {
+ insertionPoint.appendChild(fragment);
+ }
+
+ this._popup = aPopup;
+
+ this._popup.addEventListener("command", this);
+ this._popup.addEventListener("popuphidden", this);
+
+ return true;
+ },
+
+ // Construct the XUL menu structure for a given JSON object.
+ buildXULMenu: function(aNode, aElementForAppending) {
+ let document = aElementForAppending.ownerDocument;
+
+ let children = aNode.children;
+ for (let child of children) {
+ let menuitem;
+ switch (child.type) {
+ case "menuitem":
+ if (!child.id) {
+ continue; // Ignore children without ids
+ }
+
+ menuitem = document.createElement("menuitem");
+ if (child.checkbox) {
+ menuitem.setAttribute("type", "checkbox");
+ if (child.checked) {
+ menuitem.setAttribute("checked", "true");
+ }
+ }
+
+ if (child.label) {
+ menuitem.setAttribute("label", child.label);
+ }
+ if (child.icon) {
+ menuitem.setAttribute("image", child.icon);
+ menuitem.className = "menuitem-iconic";
+ }
+ if (child.disabled) {
+ menuitem.setAttribute("disabled", true);
+ }
+
+ break;
+
+ case "separator":
+ menuitem = document.createElement("menuseparator");
+ break;
+
+ case "menu":
+ menuitem = document.createElement("menu");
+ if (child.label) {
+ menuitem.setAttribute("label", child.label);
+ }
+
+ let menupopup = document.createElement("menupopup");
+ menuitem.appendChild(menupopup);
+
+ this.buildXULMenu(child, menupopup);
+ break;
+ }
+
+ menuitem.setAttribute(this.GENERATEDITEMID_ATTR, child.id ? child.id : 0);
+ aElementForAppending.appendChild(menuitem);
+ }
+ },
+
+ // Called when the generated menuitem is executed.
+ handleEvent: function(event) {
+ let type = event.type;
+ let target = event.target;
+ if (type == "command" && target.hasAttribute(this.GENERATEDITEMID_ATTR)) {
+ // If a builder is assigned, call click on it directly. Otherwise, this is
+ // likely a menu with data from another process, so send a message to the
+ // browser to execute the menuitem.
+ if (this._builder) {
+ this._builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
+ }
+ } else if (type == "popuphidden" && this._popup == target) {
+ this.removeGeneratedContent(this._popup);
+
+ this._popup.removeEventListener("popuphidden", this);
+ this._popup.removeEventListener("command", this);
+
+ this._popup = null;
+ this._builder = null;
+ }
+ },
+
+ // Get the first child of the given element with the given tag name.
+ getImmediateChild: function(element, tag) {
+ let child = element.firstChild;
+ while (child) {
+ if (child.localName == tag) {
+ return child;
+ }
+ child = child.nextSibling;
+ }
+ return null;
+ },
+
+ // Return the location where the generated items should be inserted into the
+ // given popup. They should be inserted as the next sibling of the returned
+ // element.
+ getInsertionPoint: function(aPopup) {
+ if (aPopup.hasAttribute(this.PAGEMENU_ATTR))
+ return aPopup;
+
+ let element = aPopup.firstChild;
+ while (element) {
+ if (element.localName == "menu") {
+ let popup = this.getImmediateChild(element, "menupopup");
+ if (popup) {
+ let result = this.getInsertionPoint(popup);
+ if (result) {
+ return result;
+ }
+ }
+ }
+ element = element.nextSibling;
+ }
+
+ return null;
+ },
+
+ // Returns true if custom menu items were present.
+ maybeBuildAndAttachMenu: function(aTarget, aPopup) {
+ let menuObject = this.maybeBuild(aTarget);
+ if (!menuObject) {
+ return false;
+ }
+
+ return this.buildAndAttachMenuWithObject(menuObject, null, aPopup);
+ },
+
+ // Remove the generated content from the given popup.
+ removeGeneratedContent: function(aPopup) {
+ let ungenerated = [];
+ ungenerated.push(aPopup);
+
+ let count;
+ while (0 != (count = ungenerated.length)) {
+ let last = count - 1;
+ let element = ungenerated[last];
+ ungenerated.splice(last, 1);
+
+ let i = element.childNodes.length;
+ while (i-- > 0) {
+ let child = element.childNodes[i];
+ if (!child.hasAttribute(this.GENERATEDITEMID_ATTR)) {
+ ungenerated.push(child);
+ continue;
+ }
+ element.removeChild(child);
+ }
+ }
+ }
+}
diff --git a/application/palemoon/modules/PopupNotifications.jsm b/application/palemoon/modules/PopupNotifications.jsm
new file mode 100644
index 0000000000..9b2e8e5d16
--- /dev/null
+++ b/application/palemoon/modules/PopupNotifications.jsm
@@ -0,0 +1,843 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["PopupNotifications"];
+
+var Cc = Components.classes, Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const NOTIFICATION_EVENT_DISMISSED = "dismissed";
+const NOTIFICATION_EVENT_REMOVED = "removed";
+const NOTIFICATION_EVENT_SHOWING = "showing";
+const NOTIFICATION_EVENT_SHOWN = "shown";
+
+const ICON_SELECTOR = ".notification-anchor-icon";
+const ICON_ATTRIBUTE_SHOWING = "showing";
+
+const PREF_SECURITY_DELAY = "security.notification_enable_delay";
+
+let popupNotificationsMap = new WeakMap();
+let gNotificationParents = new WeakMap;
+
+function getAnchorFromBrowser(aBrowser) {
+ let anchor = aBrowser.getAttribute("popupnotificationanchor") ||
+ aBrowser.popupnotificationanchor;
+ if (anchor) {
+ if (anchor instanceof Ci.nsIDOMXULElement) {
+ return anchor;
+ }
+ return aBrowser.ownerDocument.getElementById(anchor);
+ }
+ return null;
+}
+
+/**
+ * Notification object describes a single popup notification.
+ *
+ * @see PopupNotifications.show()
+ */
+function Notification(id, message, anchorID, mainAction, secondaryActions,
+ browser, owner, options) {
+ this.id = id;
+ this.message = message;
+ this.anchorID = anchorID;
+ this.mainAction = mainAction;
+ this.secondaryActions = secondaryActions || [];
+ this.browser = browser;
+ this.owner = owner;
+ this.options = options || {};
+}
+
+Notification.prototype = {
+
+ id: null,
+ message: null,
+ anchorID: null,
+ mainAction: null,
+ secondaryActions: null,
+ browser: null,
+ owner: null,
+ options: null,
+ timeShown: null,
+
+ /**
+ * Removes the notification and updates the popup accordingly if needed.
+ */
+ remove: function Notification_remove() {
+ this.owner.remove(this);
+ },
+
+ get anchorElement() {
+ let iconBox = this.owner.iconBox;
+
+ let anchorElement = getAnchorFromBrowser(this.browser);
+
+ if (!iconBox)
+ return anchorElement;
+
+ if (!anchorElement && this.anchorID)
+ anchorElement = iconBox.querySelector("#"+this.anchorID);
+
+ // Use a default anchor icon if it's available
+ if (!anchorElement)
+ anchorElement = iconBox.querySelector("#default-notification-icon") ||
+ iconBox;
+
+ return anchorElement;
+ },
+
+ reshow: function() {
+ this.owner._reshowNotifications(this.anchorElement, this.browser);
+ }
+};
+
+/**
+ * The PopupNotifications object manages popup notifications for a given browser
+ * window.
+ * @param tabbrowser
+ * window's . Used to observe tab switching events and
+ * for determining the active browser element.
+ * @param panel
+ * The element to use for notifications. The panel is
+ * populated with children and displayed it as
+ * needed.
+ * @param iconBox
+ * Reference to a container element that should be hidden or
+ * unhidden when notifications are hidden or shown. It should be the
+ * parent of anchor elements whose IDs are passed to show().
+ * It is used as a fallback popup anchor if notifications specify
+ * invalid or non-existent anchor IDs.
+ */
+this.PopupNotifications = function PopupNotifications(tabbrowser, panel, iconBox) {
+ if (!(tabbrowser instanceof Ci.nsIDOMXULElement))
+ throw "Invalid tabbrowser";
+ if (iconBox && !(iconBox instanceof Ci.nsIDOMXULElement))
+ throw "Invalid iconBox";
+ if (!(panel instanceof Ci.nsIDOMXULElement))
+ throw "Invalid panel";
+
+ this.window = tabbrowser.ownerDocument.defaultView;
+ this.panel = panel;
+ this.tabbrowser = tabbrowser;
+ this.iconBox = iconBox;
+ this.buttonDelay = Services.prefs.getIntPref(PREF_SECURITY_DELAY);
+
+ this.panel.addEventListener("popuphidden", this, true);
+
+ this.window.addEventListener("activate", this, true);
+ if (this.tabbrowser.tabContainer)
+ this.tabbrowser.tabContainer.addEventListener("TabSelect", this, true);
+}
+
+PopupNotifications.prototype = {
+
+ window: null,
+ panel: null,
+ tabbrowser: null,
+
+ _iconBox: null,
+ set iconBox(iconBox) {
+ // Remove the listeners on the old iconBox, if needed
+ if (this._iconBox) {
+ this._iconBox.removeEventListener("click", this, false);
+ this._iconBox.removeEventListener("keypress", this, false);
+ }
+ this._iconBox = iconBox;
+ if (iconBox) {
+ iconBox.addEventListener("click", this, false);
+ iconBox.addEventListener("keypress", this, false);
+ }
+ },
+ get iconBox() {
+ return this._iconBox;
+ },
+
+ /**
+ * Retrieve a Notification object associated with the browser/ID pair.
+ * @param id
+ * The Notification ID to search for.
+ * @param browser
+ * The browser whose notifications should be searched. If null, the
+ * currently selected browser's notifications will be searched.
+ *
+ * @returns the corresponding Notification object, or null if no such
+ * notification exists.
+ */
+ getNotification: function PopupNotifications_getNotification(id, browser) {
+ let n = null;
+ let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
+ notifications.some(function(x) x.id == id && (n = x));
+ return n;
+ },
+
+ /**
+ * Adds a new popup notification.
+ * @param browser
+ * The element associated with the notification. Must not
+ * be null.
+ * @param id
+ * A unique ID that identifies the type of notification (e.g.
+ * "geolocation"). Only one notification with a given ID can be visible
+ * at a time. If a notification already exists with the given ID, it
+ * will be replaced.
+ * @param message
+ * The text to be displayed in the notification.
+ * @param anchorID
+ * The ID of the element that should be used as this notification
+ * popup's anchor. May be null, in which case the notification will be
+ * anchored to the iconBox.
+ * @param mainAction
+ * A JavaScript object literal describing the notification button's
+ * action. If present, it must have the following properties:
+ * - label (string): the button's label.
+ * - accessKey (string): the button's accessKey.
+ * - callback (function): a callback to be invoked when the button is
+ * pressed.
+ * If null, the notification will not have a button, and
+ * secondaryActions will be ignored.
+ * @param secondaryActions
+ * An optional JavaScript array describing the notification's alternate
+ * actions. The array should contain objects with the same properties
+ * as mainAction. These are used to populate the notification button's
+ * dropdown menu.
+ * @param options
+ * An options JavaScript object holding additional properties for the
+ * notification. The following properties are currently supported:
+ * persistence: An integer. The notification will not automatically
+ * dismiss for this many page loads.
+ * timeout: A time in milliseconds. The notification will not
+ * automatically dismiss before this time.
+ * persistWhileVisible:
+ * A boolean. If true, a visible notification will always
+ * persist across location changes.
+ * dismissed: Whether the notification should be added as a dismissed
+ * notification. Dismissed notifications can be activated
+ * by clicking on their anchorElement.
+ * eventCallback:
+ * Callback to be invoked when the notification changes
+ * state. The callback's first argument is a string
+ * identifying the state change:
+ * "dismissed": notification has been dismissed by the
+ * user (e.g. by clicking away or switching
+ * tabs)
+ * "removed": notification has been removed (due to
+ * location change or user action)
+ * "shown": notification has been shown (this can be fired
+ * multiple times as notifications are dismissed
+ * and re-shown)
+ * neverShow: Indicate that no popup should be shown for this
+ * notification. Useful for just showing the anchor icon.
+ * removeOnDismissal:
+ * Notifications with this parameter set to true will be
+ * removed when they would have otherwise been dismissed
+ * (i.e. any time the popup is closed due to user
+ * interaction).
+ * popupIconURL:
+ * A string. URL of the image to be displayed in the popup.
+ * Normally specified in CSS using list-style-image and the
+ * .popup-notification-icon[popupid=...] selector.
+ * learnMoreURL:
+ * A string URL. Setting this property will make the
+ * prompt display a "Learn More" link that, when clicked,
+ * opens the URL in a new tab.
+ * @returns the Notification object corresponding to the added notification.
+ */
+ show: function PopupNotifications_show(browser, id, message, anchorID,
+ mainAction, secondaryActions, options) {
+ function isInvalidAction(a) {
+ return !a || !(typeof(a.callback) == "function") || !a.label || !a.accessKey;
+ }
+
+ if (!browser)
+ throw "PopupNotifications_show: invalid browser";
+ if (!id)
+ throw "PopupNotifications_show: invalid ID";
+ if (mainAction && isInvalidAction(mainAction))
+ throw "PopupNotifications_show: invalid mainAction";
+ if (secondaryActions && secondaryActions.some(isInvalidAction))
+ throw "PopupNotifications_show: invalid secondaryActions";
+
+ let notification = new Notification(id, message, anchorID, mainAction,
+ secondaryActions, browser, this, options);
+
+ if (options && options.dismissed)
+ notification.dismissed = true;
+
+ let existingNotification = this.getNotification(id, browser);
+ if (existingNotification)
+ this._remove(existingNotification);
+
+ let notifications = this._getNotificationsForBrowser(browser);
+ notifications.push(notification);
+
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ if (browser.docShell.isActive && fm.activeWindow == this.window) {
+ // show panel now
+ this._update(notifications, notification.anchorElement, true);
+ } else {
+ // Otherwise, update() will display the notification the next time the
+ // relevant tab/window is selected.
+
+ // If the tab is selected but the window is in the background, let the OS
+ // tell the user that there's a notification waiting in that window.
+ // At some point we might want to do something about background tabs here
+ // too. When the user switches to this window, we'll show the panel if
+ // this browser is a tab (thus showing the anchor icon). For
+ // non-tabbrowser browsers, we need to make the icon visible now or the
+ // user will not be able to open the panel.
+ if (!notification.dismissed && browser.docShell.isActive) {
+ this.window.getAttention();
+ if (notification.anchorElement.parentNode != this.iconBox) {
+ notification.anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
+ }
+ }
+
+ // Notify observers that we're not showing the popup (useful for testing)
+ this._notify("backgroundShow");
+ }
+
+ return notification;
+ },
+
+ /**
+ * Returns true if the notification popup is currently being displayed.
+ */
+ get isPanelOpen() {
+ let panelState = this.panel.state;
+
+ return panelState == "showing" || panelState == "open";
+ },
+
+ /**
+ * Called by the consumer to indicate that a browser's location has changed,
+ * so that we can update the active notifications accordingly.
+ */
+ locationChange: function PopupNotifications_locationChange(aBrowser) {
+ if (!aBrowser)
+ throw "PopupNotifications_locationChange: invalid browser";
+
+ let notifications = this._getNotificationsForBrowser(aBrowser);
+
+ notifications = notifications.filter(function (notification) {
+ // The persistWhileVisible option allows an open notification to persist
+ // across location changes
+ if (notification.options.persistWhileVisible &&
+ this.isPanelOpen) {
+ if ("persistence" in notification.options &&
+ notification.options.persistence)
+ notification.options.persistence--;
+ return true;
+ }
+
+ // The persistence option allows a notification to persist across multiple
+ // page loads
+ if ("persistence" in notification.options &&
+ notification.options.persistence) {
+ notification.options.persistence--;
+ return true;
+ }
+
+ // The timeout option allows a notification to persist until a certain time
+ if ("timeout" in notification.options &&
+ Date.now() <= notification.options.timeout) {
+ return true;
+ }
+
+ this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
+ return false;
+ }, this);
+
+ this._setNotificationsForBrowser(aBrowser, notifications);
+
+ if (aBrowser.docShell.isActive) {
+ // get the anchor element if the browser has defined one so it will
+ // _update will handle both the tabs iconBox and non-tab permission
+ // anchors.
+ let anchorElement = notifications.length > 0 ? notifications[0].anchorElement : null;
+ if (!anchorElement)
+ anchorElement = getAnchorFromBrowser(aBrowser);
+ this._update(notifications, anchorElement);
+ }
+ },
+
+ /**
+ * Removes a Notification.
+ * @param notification
+ * The Notification object to remove.
+ */
+ remove: function PopupNotifications_remove(notification) {
+ this._remove(notification);
+
+ if (notification.browser.docShell.isActive) {
+ let notifications = this._getNotificationsForBrowser(notification.browser);
+ this._update(notifications, notification.anchorElement);
+ }
+ },
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "popuphidden":
+ this._onPopupHidden(aEvent);
+ break;
+ case "activate":
+ case "TabSelect":
+ let self = this;
+ // setTimeout(..., 0) needed, otherwise openPopup from "activate" event
+ // handler results in the popup being hidden again for some reason...
+ this.window.setTimeout(function () {
+ self._update();
+ }, 0);
+ break;
+ case "click":
+ case "keypress":
+ this._onIconBoxCommand(aEvent);
+ break;
+ }
+ },
+
+////////////////////////////////////////////////////////////////////////////////
+// Utility methods
+////////////////////////////////////////////////////////////////////////////////
+
+ _ignoreDismissal: null,
+ _currentAnchorElement: null,
+
+ /**
+ * Gets notifications for the currently selected browser.
+ */
+ get _currentNotifications() {
+ return this.tabbrowser.selectedBrowser ? this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser) : [];
+ },
+
+ _remove: function PopupNotifications_removeHelper(notification) {
+ // This notification may already be removed, in which case let's just fail
+ // silently.
+ let notifications = this._getNotificationsForBrowser(notification.browser);
+ if (!notifications)
+ return;
+
+ var index = notifications.indexOf(notification);
+ if (index == -1)
+ return;
+
+ if (notification.browser.docShell.isActive)
+ notification.anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+
+ // remove the notification
+ notifications.splice(index, 1);
+ this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
+ },
+
+ /**
+ * Dismisses the notification without removing it.
+ */
+ _dismiss: function PopupNotifications_dismiss() {
+ let browser = this.panel.firstChild &&
+ this.panel.firstChild.notification.browser;
+ if (typeof this.panel.hidePopup === "function") {
+ this.panel.hidePopup();
+ }
+ if (browser)
+ browser.focus();
+ },
+
+ /**
+ * Hides the notification popup.
+ */
+ _hidePanel: function PopupNotifications_hide() {
+ this._ignoreDismissal = true;
+ if (typeof this.panel.hidePopup === "function") {
+ this.panel.hidePopup();
+ }
+ this._ignoreDismissal = false;
+ },
+
+ /**
+ * Removes all notifications from the notification popup.
+ */
+ _clearPanel: function () {
+ let popupnotification;
+ while ((popupnotification = this.panel.lastChild)) {
+ this.panel.removeChild(popupnotification);
+
+ // If this notification was provided by the chrome document rather than
+ // created ad hoc, move it back to where we got it from.
+ let originalParent = gNotificationParents.get(popupnotification);
+ if (originalParent) {
+ popupnotification.notification = null;
+
+ // Remove nodes dynamically added to the notification's menu button
+ // in _refreshPanel. Keep popupnotificationcontent nodes; they are
+ // provided by the chrome document.
+ let contentNode = popupnotification.lastChild;
+ while (contentNode) {
+ let previousSibling = contentNode.previousSibling;
+ if (contentNode.nodeName != "popupnotificationcontent")
+ popupnotification.removeChild(contentNode);
+ contentNode = previousSibling;
+ }
+
+ // Re-hide the notification such that it isn't rendered in the chrome
+ // document. _refreshPanel will unhide it again when needed.
+ popupnotification.hidden = true;
+
+ originalParent.appendChild(popupnotification);
+ }
+ }
+ },
+
+ _refreshPanel: function PopupNotifications_refreshPanel(notificationsToShow) {
+ this._clearPanel();
+
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ notificationsToShow.forEach(function (n) {
+ let doc = this.window.document;
+
+ // Append "-notification" to the ID to try to avoid ID conflicts with other stuff
+ // in the document.
+ let popupnotificationID = n.id + "-notification";
+
+ // If the chrome document provides a popupnotification with this id, use
+ // that. Otherwise create it ad-hoc.
+ let popupnotification = doc.getElementById(popupnotificationID);
+ if (popupnotification)
+ gNotificationParents.set(popupnotification, popupnotification.parentNode);
+ else
+ popupnotification = doc.createElementNS(XUL_NS, "popupnotification");
+
+ popupnotification.setAttribute("label", n.message);
+ popupnotification.setAttribute("id", popupnotificationID);
+ popupnotification.setAttribute("popupid", n.id);
+ popupnotification.setAttribute("closebuttoncommand", "PopupNotifications._dismiss();");
+ if (n.mainAction) {
+ popupnotification.setAttribute("buttonlabel", n.mainAction.label);
+ popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
+ popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonCommand(event);");
+ popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
+ popupnotification.setAttribute("closeitemcommand", "PopupNotifications._dismiss();event.stopPropagation();");
+ } else {
+ popupnotification.removeAttribute("buttonlabel");
+ popupnotification.removeAttribute("buttonaccesskey");
+ popupnotification.removeAttribute("buttoncommand");
+ popupnotification.removeAttribute("menucommand");
+ popupnotification.removeAttribute("closeitemcommand");
+ }
+
+ if (n.options.popupIconURL)
+ popupnotification.setAttribute("icon", n.options.popupIconURL);
+ if (n.options.learnMoreURL)
+ popupnotification.setAttribute("learnmoreurl", n.options.learnMoreURL);
+ else
+ popupnotification.removeAttribute("learnmoreurl");
+
+ popupnotification.notification = n;
+
+ if (n.secondaryActions) {
+ n.secondaryActions.forEach(function (a) {
+ let item = doc.createElementNS(XUL_NS, "menuitem");
+ item.setAttribute("label", a.label);
+ item.setAttribute("accesskey", a.accessKey);
+ item.notification = n;
+ item.action = a;
+
+ popupnotification.appendChild(item);
+ }, this);
+
+ if (n.secondaryActions.length) {
+ let closeItemSeparator = doc.createElementNS(XUL_NS, "menuseparator");
+ popupnotification.appendChild(closeItemSeparator);
+ }
+ }
+
+ this.panel.appendChild(popupnotification);
+
+ // The popupnotification may be hidden if we got it from the chrome
+ // document rather than creating it ad hoc.
+ popupnotification.hidden = false;
+ }, this);
+ },
+
+ _showPanel: function PopupNotifications_showPanel(notificationsToShow, anchorElement) {
+ this.panel.hidden = false;
+
+ notificationsToShow.forEach(function (n) {
+ this._fireCallback(n, NOTIFICATION_EVENT_SHOWING);
+ }, this);
+ this._refreshPanel(notificationsToShow);
+
+ if (this.isPanelOpen && this._currentAnchorElement == anchorElement)
+ return;
+
+ // If the panel is already open but we're changing anchors, we need to hide
+ // it first. Otherwise it can appear in the wrong spot. (_hidePanel is
+ // safe to call even if the panel is already hidden.)
+ this._hidePanel();
+
+ // If the anchor element is hidden or null, use the tab as the anchor. We
+ // only ever show notifications for the current browser, so we can just use
+ // the current tab.
+ let selectedTab = this.tabbrowser.selectedTab;
+ if (anchorElement) {
+ let bo = anchorElement.boxObject;
+ if (bo.height == 0 && bo.width == 0)
+ anchorElement = selectedTab; // hidden
+ } else {
+ anchorElement = selectedTab; // null
+ }
+
+ this._currentAnchorElement = anchorElement;
+
+ // On OS X and Linux we need a different panel arrow color for
+ // click-to-play plugins, so copy the popupid and use css.
+ this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
+ notificationsToShow.forEach(function (n) {
+ // Remember the time the notification was shown for the security delay.
+ n.timeShown = this.window.performance.now();
+ }, this);
+ this.panel.openPopup(anchorElement, "bottomcenter topleft");
+ notificationsToShow.forEach(function (n) {
+ this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
+ }, this);
+ },
+
+ /**
+ * Updates the notification state in response to window activation or tab
+ * selection changes.
+ *
+ * @param notifications an array of Notification instances. if null,
+ * notifications will be retrieved off the current
+ * browser tab
+ * @param anchor is a XUL element that the notifications panel will be
+ * anchored to
+ * @param dismissShowing if true, dismiss any currently visible notifications
+ * if there are no notifications to show. Otherwise,
+ * currently displayed notifications will be left alone.
+ */
+ _update: function PopupNotifications_update(notifications, anchor, dismissShowing = false) {
+ let useIconBox = this.iconBox && (!anchor || anchor.parentNode == this.iconBox);
+ if (useIconBox) {
+ // hide icons of the previous tab.
+ this._hideIcons();
+ }
+
+ let anchorElement = anchor, notificationsToShow = [];
+ if (!notifications)
+ notifications = this._currentNotifications;
+ let haveNotifications = notifications.length > 0;
+ if (haveNotifications) {
+ // Only show the notifications that have the passed-in anchor (or the
+ // first notification's anchor, if none was passed in). Other
+ // notifications will be shown once these are dismissed.
+ anchorElement = anchor || notifications[0].anchorElement;
+
+ if (useIconBox) {
+ this._showIcons(notifications);
+ this.iconBox.hidden = false;
+ } else if (anchorElement) {
+ anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
+ // use the anchorID as a class along with the default icon class as a
+ // fallback if anchorID is not defined in CSS. We always use the first
+ // notifications icon, so in the case of multiple notifications we'll
+ // only use the default icon
+ if (anchorElement.classList.contains("notification-anchor-icon")) {
+ // remove previous icon classes
+ let className = anchorElement.className.replace(/([-\w]+-notification-icon\s?)/g,"")
+ className = "default-notification-icon " + className;
+ if (notifications.length == 1) {
+ className = notifications[0].anchorID + " " + className;
+ }
+ anchorElement.className = className;
+ }
+ }
+
+ // Also filter out notifications that have been dismissed.
+ notificationsToShow = notifications.filter(function (n) {
+ return !n.dismissed && n.anchorElement == anchorElement &&
+ !n.options.neverShow;
+ });
+ }
+
+ if (notificationsToShow.length > 0) {
+ this._showPanel(notificationsToShow, anchorElement);
+ } else {
+ // Notify observers that we're not showing the popup (useful for testing)
+ this._notify("updateNotShowing");
+
+ // Close the panel if there are no notifications to show.
+ // When called from PopupNotifications.show() we should never close the
+ // panel, however. It may just be adding a dismissed notification, in
+ // which case we want to continue showing any existing notifications.
+ if (!dismissShowing)
+ this._dismiss();
+
+ // Only hide the iconBox if we actually have no notifications (as opposed
+ // to not having any showable notifications)
+ if (!haveNotifications) {
+ if (useIconBox)
+ this.iconBox.hidden = true;
+ else if (anchorElement)
+ anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+ }
+ }
+ },
+
+ _showIcons: function PopupNotifications_showIcons(aCurrentNotifications) {
+ for (let notification of aCurrentNotifications) {
+ let anchorElm = notification.anchorElement;
+ if (anchorElm) {
+ anchorElm.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
+ }
+ }
+ },
+
+ _hideIcons: function PopupNotifications_hideIcons() {
+ let icons = this.iconBox.querySelectorAll(ICON_SELECTOR);
+ for (let icon of icons) {
+ icon.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+ }
+ },
+
+ /**
+ * Gets and sets notifications for the browser.
+ */
+ _getNotificationsForBrowser: function PopupNotifications_getNotifications(browser) {
+ let notifications = popupNotificationsMap.get(browser);
+ if (!notifications) {
+ // Initialize the WeakMap for the browser so callers can reference/manipulate the array.
+ notifications = [];
+ popupNotificationsMap.set(browser, notifications);
+ }
+ return notifications;
+ },
+ _setNotificationsForBrowser: function PopupNotifications_setNotifications(browser, notifications) {
+ popupNotificationsMap.set(browser, notifications);
+ return notifications;
+ },
+
+ _onIconBoxCommand: function PopupNotifications_onIconBoxCommand(event) {
+ // Left click, space or enter only
+ let type = event.type;
+ if (type == "click" && event.button != 0)
+ return;
+
+ if (type == "keypress" &&
+ !(event.charCode == Ci.nsIDOMKeyEvent.DOM_VK_SPACE ||
+ event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN))
+ return;
+
+ if (this._currentNotifications.length == 0)
+ return;
+
+ // Get the anchor that is the immediate child of the icon box
+ let anchor = event.target;
+ while (anchor && anchor.parentNode != this.iconBox)
+ anchor = anchor.parentNode;
+
+ this._reshowNotifications(anchor);
+ },
+
+ _reshowNotifications: function PopupNotifications_reshowNotifications(anchor, browser) {
+ // Mark notifications anchored to this anchor as un-dismissed
+ let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
+ notifications.forEach(function (n) {
+ if (n.anchorElement == anchor)
+ n.dismissed = false;
+ });
+
+ // ...and then show them.
+ this._update(notifications, anchor);
+ },
+
+ _fireCallback: function PopupNotifications_fireCallback(n, event) {
+ if (n.options.eventCallback)
+ n.options.eventCallback.call(n, event);
+ },
+
+ _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
+ if (event.target != this.panel || this._ignoreDismissal)
+ return;
+
+ let browser = this.panel.firstChild &&
+ this.panel.firstChild.notification.browser;
+ if (!browser)
+ return;
+
+ let notifications = this._getNotificationsForBrowser(browser);
+ // Mark notifications as dismissed and call dismissal callbacks
+ Array.forEach(this.panel.childNodes, function (nEl) {
+ let notificationObj = nEl.notification;
+ // Never call a dismissal handler on a notification that's been removed.
+ if (notifications.indexOf(notificationObj) == -1)
+ return;
+
+ // Do not mark the notification as dismissed or fire NOTIFICATION_EVENT_DISMISSED
+ // if the notification is removed.
+ if (notificationObj.options.removeOnDismissal)
+ this._remove(notificationObj);
+ else {
+ notificationObj.dismissed = true;
+ this._fireCallback(notificationObj, NOTIFICATION_EVENT_DISMISSED);
+ }
+ }, this);
+
+ this._clearPanel();
+
+ this._update();
+ },
+
+ _onButtonCommand: function PopupNotifications_onButtonCommand(event) {
+ // Need to find the associated notification object, which is a bit tricky
+ // since it isn't associated with the button directly - this is kind of
+ // gross and very dependent on the structure of the popupnotification
+ // binding's content.
+ let target = event.originalTarget;
+ let notificationEl;
+ let parent = target;
+ while (parent && (parent = target.ownerDocument.getBindingParent(parent)))
+ notificationEl = parent;
+
+ if (!notificationEl)
+ throw "PopupNotifications_onButtonCommand: couldn't find notification element";
+
+ if (!notificationEl.notification)
+ throw "PopupNotifications_onButtonCommand: couldn't find notification";
+
+ let notification = notificationEl.notification;
+ let timeSinceShown = this.window.performance.now() - notification.timeShown;
+
+ // Only report the first time mainAction is triggered and remember that this occurred.
+ if (!notification.timeMainActionFirstTriggered) {
+ notification.timeMainActionFirstTriggered = timeSinceShown;
+ }
+
+ if (timeSinceShown < this.buttonDelay) {
+ Services.console.logStringMessage("PopupNotifications_onButtonCommand: " +
+ "Button click happened before the security delay: " +
+ timeSinceShown + "ms");
+ return;
+ }
+ notification.mainAction.callback.call();
+
+ this._remove(notification);
+ this._update();
+ },
+
+ _onMenuCommand: function PopupNotifications_onMenuCommand(event) {
+ let target = event.originalTarget;
+ if (!target.action || !target.notification)
+ throw "menucommand target has no associated action/notification";
+
+ event.stopPropagation();
+ target.action.callback.call();
+
+ this._remove(target.notification);
+ this._update();
+ },
+
+ _notify: function PopupNotifications_notify(topic) {
+ Services.obs.notifyObservers(null, "PopupNotifications-" + topic, "");
+ },
+};
diff --git a/application/palemoon/modules/QuotaManager.jsm b/application/palemoon/modules/QuotaManager.jsm
new file mode 100644
index 0000000000..e03161a693
--- /dev/null
+++ b/application/palemoon/modules/QuotaManager.jsm
@@ -0,0 +1,45 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["QuotaManagerHelper"];
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+this.QuotaManagerHelper = {
+ clear: function(isShutDown) {
+ try {
+ var stord = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ stord.append("storage");
+ if (stord.exists() && stord.isDirectory()) {
+ var doms = {};
+ for (var stor of ["default", "permanent", "temporary"]) {
+ var storsubd = stord.clone();
+ storsubd.append(stor);
+ if (storsubd.exists() && storsubd.isDirectory()) {
+ var entries = storsubd.directoryEntries;
+ while(entries.hasMoreElements()) {
+ var host, entry = entries.getNext();
+ entry.QueryInterface(Ci.nsIFile);
+ if ((host = /^(https?|file)\+\+\+(.+)$/.exec(entry.leafName)) !== null) {
+ if (isShutDown) {
+ entry.remove(true);
+ } else {
+ doms[host[1] + "://" + host[2]] = true;
+ }
+ }
+ }
+ }
+ }
+ var qm = Cc["@mozilla.org/dom/quota/manager;1"].getService(Ci.nsIQuotaManager);
+ for (var dom in doms) {
+ var uri = Services.io.newURI(dom, null, null);
+ qm.clearStoragesForURI(uri);
+ }
+ }
+ } catch(er) {}
+ }
+};
diff --git a/application/palemoon/modules/RecentWindow.jsm b/application/palemoon/modules/RecentWindow.jsm
new file mode 100644
index 0000000000..0018b502c7
--- /dev/null
+++ b/application/palemoon/modules/RecentWindow.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["RecentWindow"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+#ifndef XP_WIN
+#define BROKEN_WM_Z_ORDER
+#endif
+
+this.RecentWindow = {
+ /*
+ * Get the most recent browser window.
+ *
+ * @param aOptions an object accepting the arguments for the search.
+ * * private: true to restrict the search to private windows
+ * only, false to restrict the search to non-private only.
+ * Omit the property to search in both groups.
+ * * allowPopups: true if popup windows are permissable.
+ */
+ getMostRecentBrowserWindow: function RW_getMostRecentBrowserWindow(aOptions) {
+ let checkPrivacy = typeof aOptions == "object" &&
+ "private" in aOptions;
+
+ let allowPopups = typeof aOptions == "object" && !!aOptions.allowPopups;
+
+ function isSuitableBrowserWindow(win) {
+ return (!win.closed &&
+ (allowPopups || win.toolbar.visible) &&
+ (!checkPrivacy ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing ||
+ PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private));
+ }
+
+#ifdef BROKEN_WM_Z_ORDER
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ // if we're lucky, this isn't a popup, and we can just return this
+ if (win && !isSuitableBrowserWindow(win)) {
+ win = null;
+ let windowList = Services.wm.getEnumerator("navigator:browser");
+ // this is oldest to newest, so this gets a bit ugly
+ while (windowList.hasMoreElements()) {
+ let nextWin = windowList.getNext();
+ if (isSuitableBrowserWindow(nextWin))
+ win = nextWin;
+ }
+ }
+ return win;
+#else
+ let windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
+ while (windowList.hasMoreElements()) {
+ let win = windowList.getNext();
+ if (isSuitableBrowserWindow(win))
+ return win;
+ }
+ return null;
+#endif
+ }
+};
+
diff --git a/application/palemoon/modules/SharedFrame.jsm b/application/palemoon/modules/SharedFrame.jsm
new file mode 100644
index 0000000000..4d248ae5ba
--- /dev/null
+++ b/application/palemoon/modules/SharedFrame.jsm
@@ -0,0 +1,221 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [ "SharedFrame" ];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+/**
+ * The purpose of this module is to create and group various iframe
+ * elements that are meant to all display the same content and only
+ * one at a time. This makes it possible to have the content loaded
+ * only once, while the other iframes can be kept as placeholders to
+ * quickly move the content to them through the swapFrameLoaders function
+ * when another one of the placeholder is meant to be displayed.
+ * */
+
+let Frames = new Map();
+
+/**
+ * The Frames map is the main data structure that holds information
+ * about the groups being tracked. Each entry's key is the group name,
+ * and the object holds information about what is the URL being displayed
+ * on that group, and what is the active element on the group (the frame that
+ * holds the loaded content).
+ * The reference to the activeFrame is a weak reference, which allows the
+ * frame to go away at any time, and when that happens the module considers that
+ * there are no active elements in that group. The group can be reactivated
+ * by changing the URL, calling preload again or adding a new element.
+ *
+ *
+ * Frames = {
+ * "messages-panel": {
+ * url: string,
+ * activeFrame: weakref
+ * }
+ * }
+ *
+ * Each object on the map is called a _SharedFrameGroup, which is an internal
+ * class of this module which does not automatically keep track of its state. This
+ * object should not be used externally, and all control should be handled by the
+ * module's functions.
+ */
+
+function UNLOADED_URL(aStr) "data:text/html;charset=utf-8,";
+
+
+this.SharedFrame = {
+ /**
+ * Creates an iframe element and track it as part of the specified group
+ * The module must create the iframe itself because it needs to do some special
+ * handling for the element's src attribute.
+ *
+ * @param aGroupName the name of the group to which this frame belongs
+ * @param aParent the parent element to which the frame will be appended to
+ * @param aFrameAttributes an object with a list of attributes to set in the iframe
+ * before appending it to the DOM. The "src" attribute has
+ * special meaning here and if it's not blank it specifies
+ * the URL that will be initially assigned to this group
+ * @param aPreload optional, tells if the URL specified in the src attribute
+ * should be preloaded in the frame being created, in case
+ * it's not yet preloaded in any other frame of the group.
+ * This parameter has no meaning if src is blank.
+ */
+ createFrame: function (aGroupName, aParent, aFrameAttributes, aPreload = true) {
+ let frame = aParent.ownerDocument.createElement("iframe");
+
+ for (let [key, val] of Iterator(aFrameAttributes)) {
+ frame.setAttribute(key, val);
+ }
+
+ let src = aFrameAttributes.src;
+ if (!src) {
+ aPreload = false;
+ }
+
+ let group = Frames.get(aGroupName);
+
+ if (group) {
+ // If this group has already been created
+
+ if (aPreload && !group.isAlive) {
+ // If aPreload is set and the group is not already loaded, load it.
+ // This can happen if:
+ // - aPreload was not used while creating the previous frames of this group, or
+ // - the previously active frame went dead in the meantime
+ group.url = src;
+ this.preload(aGroupName, frame);
+ } else {
+ // If aPreload is not set, or the group is already loaded in a different frame,
+ // there's not much that we need to do here: just create this frame as an
+ // inactivate placeholder
+ frame.setAttribute("src", UNLOADED_URL(aGroupName));
+ }
+
+ } else {
+ // This is the first time we hear about this group, so let's start tracking it,
+ // and also preload it if the src attribute was set and aPreload = true
+ group = new _SharedFrameGroup(src);
+ Frames.set(aGroupName, group);
+
+ if (aPreload) {
+ this.preload(aGroupName, frame);
+ } else {
+ frame.setAttribute("src", UNLOADED_URL(aGroupName));
+ }
+ }
+
+ aParent.appendChild(frame);
+ return frame;
+
+ },
+
+ /**
+ * Function that moves the loaded content from one active frame to
+ * another one that is currently a placeholder. If there's no active
+ * frame in the group, the content is loaded/reloaded.
+ *
+ * @param aGroupName the name of the group
+ * @param aTargetFrame the frame element to which the content should
+ * be moved to.
+ */
+ setOwner: function (aGroupName, aTargetFrame) {
+ let group = Frames.get(aGroupName);
+ let frame = group.activeFrame;
+
+ if (frame == aTargetFrame) {
+ // nothing to do here
+ return;
+ }
+
+ if (group.isAlive) {
+ // Move document ownership to the desired frame, and make it the active one
+ frame.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(aTargetFrame);
+ group.activeFrame = aTargetFrame;
+ } else {
+ // Previous owner was dead, reload the document at the new owner and make it the active one
+ aTargetFrame.setAttribute("src", group.url);
+ group.activeFrame = aTargetFrame;
+ }
+ },
+
+ /**
+ * Updates the current URL in use by this group, and loads it into the active frame.
+ *
+ * @param aGroupName the name of the group
+ * @param aURL the new url
+ */
+ updateURL: function (aGroupName, aURL) {
+ let group = Frames.get(aGroupName);
+ group.url = aURL;
+
+ if (group.isAlive) {
+ group.activeFrame.setAttribute("src", aURL);
+ }
+ },
+
+ /**
+ * Loads the group's url into a target frame, if the group doesn't have a currently
+ * active frame.
+ *
+ * @param aGroupName the name of the group
+ * @param aTargetFrame the frame element which should be made active and
+ * have the group's content loaded to
+ */
+ preload: function (aGroupName, aTargetFrame) {
+ let group = Frames.get(aGroupName);
+ if (!group.isAlive) {
+ aTargetFrame.setAttribute("src", group.url);
+ group.activeFrame = aTargetFrame;
+ }
+ },
+
+ /**
+ * Tells if a group currently have an active element.
+ *
+ * @param aGroupName the name of the group
+ */
+ isGroupAlive: function (aGroupName) {
+ return Frames.get(aGroupName).isAlive;
+ },
+
+ /**
+ * Forgets about this group. This function doesn't need to be used
+ * unless the group's name needs to be reused.
+ *
+ * @param aGroupName the name of the group
+ */
+ forgetGroup: function (aGroupName) {
+ Frames.delete(aGroupName);
+ }
+}
+
+
+function _SharedFrameGroup(aURL) {
+ this.url = aURL;
+ this._activeFrame = null;
+}
+
+_SharedFrameGroup.prototype = {
+ get isAlive() {
+ let frame = this.activeFrame;
+ return !!(frame &&
+ frame.contentDocument &&
+ frame.contentDocument.location);
+ },
+
+ get activeFrame() {
+ return this._activeFrame &&
+ this._activeFrame.get();
+ },
+
+ set activeFrame(aActiveFrame) {
+ this._activeFrame = aActiveFrame
+ ? Cu.getWeakReference(aActiveFrame)
+ : null;
+ }
+}
diff --git a/application/palemoon/modules/Windows8WindowFrameColor.jsm b/application/palemoon/modules/Windows8WindowFrameColor.jsm
new file mode 100644
index 0000000000..d424da499a
--- /dev/null
+++ b/application/palemoon/modules/Windows8WindowFrameColor.jsm
@@ -0,0 +1,53 @@
+/* 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";
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["Windows8WindowFrameColor"];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/WindowsRegistry.jsm");
+
+const Windows8WindowFrameColor = {
+ _windowFrameColor: null,
+
+ get_win8: function() {
+ if (this._windowFrameColor)
+ return this._windowFrameColor;
+
+ let HKCU = Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER;
+ let dwmKey = "Software\\Microsoft\\Windows\\DWM";
+
+ // Window frame base color component values when Color Intensity is at 0.
+ let frameBaseColor = 217;
+
+ let windowFrameColor = WindowsRegistry.readRegKey(HKCU, dwmKey,
+ "ColorizationColor");
+ if (windowFrameColor == undefined) {
+ // Return the default color if unset or colorization not used
+ return this._windowFrameColor = [frameBaseColor, frameBaseColor, frameBaseColor];
+ }
+ // The color returned from the Registry is in decimal form.
+ let windowFrameColorHex = windowFrameColor.toString(16);
+ // Zero-pad the number just to make sure that it is 8 digits.
+ windowFrameColorHex = ("00000000" + windowFrameColorHex).substr(-8);
+ let windowFrameColorArray = windowFrameColorHex.match(/../g);
+ let [unused, fgR, fgG, fgB] = windowFrameColorArray.map(function(val) parseInt(val, 16));
+ let windowFrameColorBalance = WindowsRegistry.readRegKey(HKCU, dwmKey,
+ "ColorizationColorBalance");
+ // Default to balance=78 if reg key isn't defined
+ if (windowFrameColorBalance == undefined) {
+ windowFrameColorBalance = 78;
+ }
+ let alpha = windowFrameColorBalance / 100;
+
+ // Alpha-blend the foreground color with the frame base color.
+ let r = Math.round(fgR * alpha + frameBaseColor * (1 - alpha));
+ let g = Math.round(fgG * alpha + frameBaseColor * (1 - alpha));
+ let b = Math.round(fgB * alpha + frameBaseColor * (1 - alpha));
+ return this._windowFrameColor = [r, g, b];
+ }
+};
diff --git a/application/palemoon/modules/WindowsJumpLists.jsm b/application/palemoon/modules/WindowsJumpLists.jsm
new file mode 100644
index 0000000000..e7f7855198
--- /dev/null
+++ b/application/palemoon/modules/WindowsJumpLists.jsm
@@ -0,0 +1,581 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Constants
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// Stop updating jumplists after some idle time.
+const IDLE_TIMEOUT_SECONDS = 5 * 60;
+
+// Prefs
+const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
+const PREF_TASKBAR_ENABLED = "enabled";
+const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
+const PREF_TASKBAR_FREQUENT = "frequent.enabled";
+const PREF_TASKBAR_RECENT = "recent.enabled";
+const PREF_TASKBAR_TASKS = "tasks.enabled";
+const PREF_TASKBAR_REFRESH = "refreshInSeconds";
+
+// Hash keys for pendingStatements.
+const LIST_TYPE = {
+ FREQUENT: 0
+, RECENT: 1
+}
+
+/**
+ * Exports
+ */
+
+this.EXPORTED_SYMBOLS = [
+ "WinTaskbarJumpList",
+];
+
+/**
+ * Smart getters
+ */
+
+XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
+ return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
+});
+
+XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
+ return Services.strings
+ .createBundle("chrome://browser/locale/taskbar.properties");
+});
+
+XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ return PlacesUtils;
+});
+
+XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
+ Components.utils.import("resource://gre/modules/NetUtil.jsm");
+ return NetUtil;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "_idle",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
+ "@mozilla.org/windows-taskbar;1",
+ "nsIWinTaskbar");
+
+XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
+ "@mozilla.org/browser/shell-service;1",
+ "nsIWindowsShellService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+/**
+ * Global functions
+ */
+
+function _getString(name) {
+ return _stringBundle.GetStringFromName(name);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Task list configuration data object.
+
+var tasksCfg = [
+ /**
+ * Task configuration options: title, description, args, iconIndex, open, close.
+ *
+ * title - Task title displayed in the list. (strings in the table are temp fillers.)
+ * description - Tooltip description on the list item.
+ * args - Command line args to invoke the task.
+ * iconIndex - Optional win icon index into the main application for the
+ * list item.
+ * open - Boolean indicates if the command should be visible after the browser opens.
+ * close - Boolean indicates if the command should be visible after the browser closes.
+ */
+ // Open new tab
+ {
+ get title() _getString("taskbar.tasks.newTab.label"),
+ get description() _getString("taskbar.tasks.newTab.description"),
+ args: "-new-tab about:blank",
+ iconIndex: 3, // New window icon
+ open: true,
+ close: true, // The jump list already has an app launch icon, but
+ // we don't always update the list on shutdown.
+ // Thus true for consistency.
+ },
+
+ // Open new window
+ {
+ get title() _getString("taskbar.tasks.newWindow.label"),
+ get description() _getString("taskbar.tasks.newWindow.description"),
+ args: "-browser",
+ iconIndex: 2, // New tab icon
+ open: true,
+ close: true, // No point, but we don't always update the list on
+ // shutdown. Thus true for consistency.
+ },
+
+ // Open new private window
+ {
+ get title() _getString("taskbar.tasks.newPrivateWindow.label"),
+ get description() _getString("taskbar.tasks.newPrivateWindow.description"),
+ args: "-private-window",
+ iconIndex: 4, // Private browsing mode icon
+ open: true,
+ close: true, // No point, but we don't always update the list on
+ // shutdown. Thus true for consistency.
+ },
+];
+
+/////////////////////////////////////////////////////////////////////////////
+// Implementation
+
+this.WinTaskbarJumpList =
+{
+ _builder: null,
+ _tasks: null,
+ _shuttingDown: false,
+
+ /**
+ * Startup, shutdown, and update
+ */
+
+ startup: function WTBJL_startup() {
+ // exit if this isn't win7 or higher.
+ if (!this._initTaskbar())
+ return;
+
+ // Win shell shortcut maintenance. If we've gone through an update,
+ // this will update any pinned taskbar shortcuts. Not specific to
+ // jump lists, but this was a convienent place to call it.
+ try {
+ // dev builds may not have helper.exe, ignore failures.
+ this._shortcutMaintenance();
+ } catch (ex) {
+ }
+
+ // Store our task list config data
+ this._tasks = tasksCfg;
+
+ // retrieve taskbar related prefs.
+ this._refreshPrefs();
+
+ // observer for private browsing and our prefs branch
+ this._initObs();
+
+ // jump list refresh timer
+ this._updateTimer();
+ },
+
+ update: function WTBJL_update() {
+ // are we disabled via prefs? don't do anything!
+ if (!this._enabled)
+ return;
+
+ // do what we came here to do, update the taskbar jumplist
+ this._buildList();
+ },
+
+ _shutdown: function WTBJL__shutdown() {
+ this._shuttingDown = true;
+
+ // Correctly handle a clear history on shutdown. If there are no
+ // entries be sure to empty all history lists. Luckily Places caches
+ // this value, so it's a pretty fast call.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ this.update();
+ }
+
+ this._free();
+ },
+
+ _shortcutMaintenance: function WTBJL__maintenace() {
+ _winShellService.shortcutMaintenance();
+ },
+
+ /**
+ * List building
+ *
+ * @note Async builders must add their mozIStoragePendingStatement to
+ * _pendingStatements object, using a different LIST_TYPE entry for
+ * each statement. Once finished they must remove it and call
+ * commitBuild(). When there will be no more _pendingStatements,
+ * commitBuild() will commit for real.
+ */
+
+ _pendingStatements: {},
+ _hasPendingStatements: function WTBJL__hasPendingStatements() {
+ return Object.keys(this._pendingStatements).length > 0;
+ },
+
+ _buildList: function WTBJL__buildList() {
+ if (this._hasPendingStatements()) {
+ // We were requested to update the list while another update was in
+ // progress, this could happen at shutdown, idle or privatebrowsing.
+ // Abort the current list building.
+ for (let listType in this._pendingStatements) {
+ this._pendingStatements[listType].cancel();
+ delete this._pendingStatements[listType];
+ }
+ this._builder.abortListBuild();
+ }
+
+ // anything to build?
+ if (!this._showFrequent && !this._showRecent && !this._showTasks) {
+ // don't leave the last list hanging on the taskbar.
+ this._deleteActiveJumpList();
+ return;
+ }
+
+ if (!this._startBuild())
+ return;
+
+ if (this._showTasks)
+ this._buildTasks();
+
+ // Space for frequent items takes priority over recent.
+ if (this._showFrequent)
+ this._buildFrequent();
+
+ if (this._showRecent)
+ this._buildRecent();
+
+ this._commitBuild();
+ },
+
+ /**
+ * Taskbar api wrappers
+ */
+
+ _startBuild: function WTBJL__startBuild() {
+ var removedItems = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._builder.abortListBuild();
+ if (this._builder.initListBuild(removedItems)) {
+ // Prior to building, delete removed items from history.
+ this._clearHistory(removedItems);
+ return true;
+ }
+ return false;
+ },
+
+ _commitBuild: function WTBJL__commitBuild() {
+ if (!this._hasPendingStatements() && !this._builder.commitListBuild()) {
+ this._builder.abortListBuild();
+ }
+ },
+
+ _buildTasks: function WTBJL__buildTasks() {
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._tasks.forEach(function (task) {
+ if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
+ return;
+ var item = this._getHandlerAppItem(task.title, task.description,
+ task.args, task.iconIndex, null);
+ items.appendElement(item, false);
+ }, this);
+
+ if (items.length > 0)
+ this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
+ },
+
+ _buildCustom: function WTBJL__buildCustom(title, items) {
+ if (items.length > 0)
+ this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
+ },
+
+ _buildFrequent: function WTBJL__buildFrequent() {
+ // If history is empty, just bail out.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ return;
+ }
+
+ // Windows supports default frequent and recent lists,
+ // but those depend on internal windows visit tracking
+ // which we don't populate. So we build our own custom
+ // frequent and recent lists using our nav history data.
+
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ // track frequent items so that we don't add them to
+ // the recent list.
+ this._frequentHashList = [];
+
+ this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
+ Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
+ this._maxItemCount,
+ function (aResult) {
+ if (!aResult) {
+ delete this._pendingStatements[LIST_TYPE.FREQUENT];
+ // The are no more results, build the list.
+ this._buildCustom(_getString("taskbar.frequent.label"), items);
+ this._commitBuild();
+ return;
+ }
+
+ let title = aResult.title || aResult.uri;
+ let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
+ let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
+ faviconPageUri);
+ items.appendElement(shortcut, false);
+ this._frequentHashList.push(aResult.uri);
+ },
+ this
+ );
+ },
+
+ _buildRecent: function WTBJL__buildRecent() {
+ // If history is empty, just bail out.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ return;
+ }
+
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ // Frequent items will be skipped, so we select a double amount of
+ // entries and stop fetching results at _maxItemCount.
+ var count = 0;
+
+ this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
+ this._maxItemCount * 2,
+ function (aResult) {
+ if (!aResult) {
+ // The are no more results, build the list.
+ this._buildCustom(_getString("taskbar.recent.label"), items);
+ delete this._pendingStatements[LIST_TYPE.RECENT];
+ this._commitBuild();
+ return;
+ }
+
+ if (count >= this._maxItemCount) {
+ return;
+ }
+
+ // Do not add items to recent that have already been added to frequent.
+ if (this._frequentHashList &&
+ this._frequentHashList.indexOf(aResult.uri) != -1) {
+ return;
+ }
+
+ let title = aResult.title || aResult.uri;
+ let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
+ let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
+ faviconPageUri);
+ items.appendElement(shortcut, false);
+ count++;
+ },
+ this
+ );
+ },
+
+ _deleteActiveJumpList: function WTBJL__deleteAJL() {
+ this._builder.deleteActiveList();
+ },
+
+ /**
+ * Jump list item creation helpers
+ */
+
+ _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
+ args, iconIndex,
+ faviconPageUri) {
+ var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile);
+
+ var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.executable = file;
+ // handlers default to the leaf name if a name is not specified
+ if (name && name.length != 0)
+ handlerApp.name = name;
+ handlerApp.detailedDescription = description;
+ handlerApp.appendParameter(args);
+
+ var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].
+ createInstance(Ci.nsIJumpListShortcut);
+ item.app = handlerApp;
+ item.iconIndex = iconIndex;
+ item.faviconPageUri = faviconPageUri;
+ return item;
+ },
+
+ _getSeparatorItem: function WTBJL__getSeparatorItem() {
+ var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
+ createInstance(Ci.nsIJumpListSeparator);
+ return item;
+ },
+
+ /**
+ * Nav history helpers
+ */
+
+ _getHistoryResults:
+ function WTBLJL__getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.maxResults = aLimit;
+ options.sortingMode = aSortingMode;
+ var query = PlacesUtils.history.getNewQuery();
+
+ // Return the pending statement to the caller, to allow cancelation.
+ return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .asyncExecuteLegacyQueries([query], 1, options, {
+ handleResult: function (aResultSet) {
+ for (let row; (row = aResultSet.getNextRow());) {
+ try {
+ aCallback.call(aScope,
+ { uri: row.getResultByIndex(1)
+ , title: row.getResultByIndex(2)
+ });
+ } catch (e) {}
+ }
+ },
+ handleError: function (aError) {
+ Components.utils.reportError(
+ "Async execution error (" + aError.result + "): " + aError.message);
+ },
+ handleCompletion: function (aReason) {
+ aCallback.call(WinTaskbarJumpList, null);
+ },
+ });
+ },
+
+ _clearHistory: function WTBJL__clearHistory(items) {
+ if (!items)
+ return;
+ var URIsToRemove = [];
+ var e = items.enumerate();
+ while (e.hasMoreElements()) {
+ let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut);
+ if (oldItem) {
+ try { // in case we get a bad uri
+ let uriSpec = oldItem.app.getParameter(0);
+ URIsToRemove.push(NetUtil.newURI(uriSpec));
+ } catch (err) { }
+ }
+ }
+ if (URIsToRemove.length > 0) {
+ PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
+ }
+ },
+
+ /**
+ * Prefs utilities
+ */
+
+ _refreshPrefs: function WTBJL__refreshPrefs() {
+ this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
+ this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
+ this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
+ this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
+ this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
+ },
+
+ /**
+ * Init and shutdown utilities
+ */
+
+ _initTaskbar: function WTBJL__initTaskbar() {
+ this._builder = _taskbarService.createJumpListBuilder();
+ if (!this._builder || !this._builder.available)
+ return false;
+
+ return true;
+ },
+
+ _initObs: function WTBJL__initObs() {
+ // If the browser is closed while in private browsing mode, the "exit"
+ // notification is fired on quit-application-granted.
+ // History cleanup can happen at profile-change-teardown.
+ Services.obs.addObserver(this, "profile-before-change", false);
+ Services.obs.addObserver(this, "browser:purge-session-history", false);
+ _prefs.addObserver("", this, false);
+ },
+
+ _freeObs: function WTBJL__freeObs() {
+ Services.obs.removeObserver(this, "profile-before-change");
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ _prefs.removeObserver("", this);
+ },
+
+ _updateTimer: function WTBJL__updateTimer() {
+ if (this._enabled && !this._shuttingDown && !this._timer) {
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._timer.initWithCallback(this,
+ _prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
+ this._timer.TYPE_REPEATING_SLACK);
+ }
+ else if ((!this._enabled || this._shuttingDown) && this._timer) {
+ this._timer.cancel();
+ delete this._timer;
+ }
+ },
+
+ _hasIdleObserver: false,
+ _updateIdleObserver: function WTBJL__updateIdleObserver() {
+ if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
+ _idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._hasIdleObserver = true;
+ }
+ else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) {
+ _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._hasIdleObserver = false;
+ }
+ },
+
+ _free: function WTBJL__free() {
+ this._freeObs();
+ this._updateTimer();
+ this._updateIdleObserver();
+ delete this._builder;
+ },
+
+ /**
+ * Notification handlers
+ */
+
+ notify: function WTBJL_notify(aTimer) {
+ // Add idle observer on the first notification so it doesn't hit startup.
+ this._updateIdleObserver();
+ this.update();
+ },
+
+ observe: function WTBJL_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED))
+ this._deleteActiveJumpList();
+ this._refreshPrefs();
+ this._updateTimer();
+ this._updateIdleObserver();
+ this.update();
+ break;
+
+ case "profile-before-change":
+ this._shutdown();
+ break;
+
+ case "browser:purge-session-history":
+ this.update();
+ break;
+ case "idle":
+ if (this._timer) {
+ this._timer.cancel();
+ delete this._timer;
+ }
+ break;
+
+ case "back":
+ this._updateTimer();
+ break;
+ }
+ },
+};
diff --git a/application/palemoon/modules/WindowsPreviewPerTab.jsm b/application/palemoon/modules/WindowsPreviewPerTab.jsm
new file mode 100644
index 0000000000..41b38f0cf9
--- /dev/null
+++ b/application/palemoon/modules/WindowsPreviewPerTab.jsm
@@ -0,0 +1,708 @@
+/* vim: se cin sw=2 ts=2 et filetype=javascript :
+ * 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/. */
+/*
+ * This module implements the front end behavior for AeroPeek. Starting in
+ * Windows Vista, the taskbar began showing live thumbnail previews of windows
+ * when the user hovered over the window icon in the taskbar. Starting with
+ * Windows 7, the taskbar allows an application to expose its tabbed interface
+ * in the taskbar by showing thumbnail previews rather than the default window
+ * preview. Additionally, when a user hovers over a thumbnail (tab or window),
+ * they are shown a live preview of the window (or tab + its containing window).
+ *
+ * In Windows 7, a title, icon, close button and optional toolbar are shown for
+ * each preview. This feature does not make use of the toolbar. For window
+ * previews, the title is the window title and the icon the window icon. For
+ * tab previews, the title is the page title and the page's favicon. In both
+ * cases, the close button "does the right thing."
+ *
+ * The primary objects behind this feature are nsITaskbarTabPreview and
+ * nsITaskbarPreviewController. Each preview has a controller. The controller
+ * responds to the user's interactions on the taskbar and provides the required
+ * data to the preview for determining the size of the tab and thumbnail. The
+ * PreviewController class implements this interface. The preview will request
+ * the controller to provide a thumbnail or preview when the user interacts with
+ * the taskbar. To reduce the overhead of drawing the tab area, the controller
+ * implementation caches the tab's contents in a