mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 08:18:41 +09:00
Add Pale Moon
This commit is contained in:
parent
dcd9973243
commit
fe8028fa2e
1173 changed files with 143053 additions and 934 deletions
67
application/palemoon/modules/AboutHomeUtils.jsm
Normal file
67
application/palemoon/modules/AboutHomeUtils.jsm
Normal file
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
436
application/palemoon/modules/BrowserNewTabPreloader.jsm
Normal file
436
application/palemoon/modules/BrowserNewTabPreloader.jsm
Normal file
|
|
@ -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,<window%20id='win'/>";
|
||||
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 <tabbrowser>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);
|
||||
}
|
||||
}
|
||||
};
|
||||
160
application/palemoon/modules/CharsetMenu.jsm
Normal file
160
application/palemoon/modules/CharsetMenu.jsm
Normal file
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
251
application/palemoon/modules/FormSubmitObserver.jsm
Normal file
251
application/palemoon/modules/FormSubmitObserver.jsm
Normal file
|
|
@ -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])
|
||||
};
|
||||
157
application/palemoon/modules/FormValidationHandler.jsm
Normal file
157
application/palemoon/modules/FormValidationHandler.jsm
Normal file
|
|
@ -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 <browser>, 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();
|
||||
}
|
||||
}
|
||||
};
|
||||
179
application/palemoon/modules/NetworkPrioritizer.jsm
Normal file
179
application/palemoon/modules/NetworkPrioritizer.jsm
Normal file
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
238
application/palemoon/modules/PageMenu.jsm
Normal file
238
application/palemoon/modules/PageMenu.jsm
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
843
application/palemoon/modules/PopupNotifications.jsm
Normal file
843
application/palemoon/modules/PopupNotifications.jsm
Normal file
|
|
@ -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 <xul:tabbrowser/>. Used to observe tab switching events and
|
||||
* for determining the active browser element.
|
||||
* @param panel
|
||||
* The <xul:panel/> element to use for notifications. The panel is
|
||||
* populated with <popupnotification> 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 <xul:browser> 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, "");
|
||||
},
|
||||
};
|
||||
45
application/palemoon/modules/QuotaManager.jsm
Normal file
45
application/palemoon/modules/QuotaManager.jsm
Normal file
|
|
@ -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) {}
|
||||
}
|
||||
};
|
||||
68
application/palemoon/modules/RecentWindow.jsm
Normal file
68
application/palemoon/modules/RecentWindow.jsm
Normal file
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
221
application/palemoon/modules/SharedFrame.jsm
Normal file
221
application/palemoon/modules/SharedFrame.jsm
Normal file
|
|
@ -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,<!-- Unloaded frame " + aStr + " -->";
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
53
application/palemoon/modules/Windows8WindowFrameColor.jsm
Normal file
53
application/palemoon/modules/Windows8WindowFrameColor.jsm
Normal file
|
|
@ -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];
|
||||
}
|
||||
};
|
||||
581
application/palemoon/modules/WindowsJumpLists.jsm
Normal file
581
application/palemoon/modules/WindowsJumpLists.jsm
Normal file
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
};
|
||||
708
application/palemoon/modules/WindowsPreviewPerTab.jsm
Normal file
708
application/palemoon/modules/WindowsPreviewPerTab.jsm
Normal file
|
|
@ -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 <canvas> element. If no
|
||||
* previews or thumbnails have been requested for some time, the controller will
|
||||
* discard its cached tab contents.
|
||||
*
|
||||
* Screen real estate is limited so when there are too many thumbnails to fit
|
||||
* on the screen, the taskbar stops displaying thumbnails and instead displays
|
||||
* just the title, icon and close button in a similar fashion to previous
|
||||
* versions of the taskbar. If there are still too many previews to fit on the
|
||||
* screen, the taskbar resorts to a scroll up and scroll down button pair to let
|
||||
* the user scroll through the list of tabs. Since this is undoubtedly
|
||||
* inconvenient for users with many tabs, the AeroPeek objects turns off all of
|
||||
* the tab previews. This tells the taskbar to revert to one preview per window.
|
||||
* If the number of tabs falls below this magic threshold, the preview-per-tab
|
||||
* behavior returns. There is no reliable way to determine when the scroll
|
||||
* buttons appear on the taskbar, so a magic pref-controlled number determines
|
||||
* when this threshold has been crossed.
|
||||
*/
|
||||
this.EXPORTED_SYMBOLS = ["AeroPeek"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
// Pref to enable/disable preview-per-tab
|
||||
const TOGGLE_PREF_NAME = "browser.taskbar.previews.enable";
|
||||
// Pref to determine the magic auto-disable threshold
|
||||
const DISABLE_THRESHOLD_PREF_NAME = "browser.taskbar.previews.max";
|
||||
// Pref to control the time in seconds that tab contents live in the cache
|
||||
const CACHE_EXPIRATION_TIME_PREF_NAME = "browser.taskbar.previews.cachetime";
|
||||
|
||||
const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Various utility properties
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ioSvc",
|
||||
"@mozilla.org/network/io-service;1",
|
||||
"nsIIOService");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
|
||||
"@mozilla.org/image/tools;1",
|
||||
"imgITools");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "faviconSvc",
|
||||
"@mozilla.org/browser/favicon-service;1",
|
||||
"nsIFaviconService");
|
||||
|
||||
// nsIURI -> imgIContainer
|
||||
function _imageFromURI(uri, privateMode, callback) {
|
||||
let channel = ioSvc.newChannelFromURI(uri);
|
||||
try {
|
||||
channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
|
||||
channel.setPrivate(privateMode);
|
||||
} catch (e) {
|
||||
// Ignore channels which do not support nsIPrivateBrowsingChannel
|
||||
}
|
||||
NetUtil.asyncFetch(channel, function(inputStream, resultCode) {
|
||||
if (!Components.isSuccessCode(resultCode))
|
||||
return;
|
||||
try {
|
||||
let out_img = { value: null };
|
||||
imgTools.decodeImageData(inputStream, channel.contentType, out_img);
|
||||
callback(out_img.value);
|
||||
} catch (e) {
|
||||
// We failed, so use the default favicon (only if this wasn't the default
|
||||
// favicon).
|
||||
let defaultURI = faviconSvc.defaultFavicon;
|
||||
if (!defaultURI.equals(uri))
|
||||
_imageFromURI(defaultURI, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// string? -> imgIContainer
|
||||
function getFaviconAsImage(iconurl, privateMode, callback) {
|
||||
if (iconurl)
|
||||
_imageFromURI(NetUtil.newURI(iconurl), privateMode, callback);
|
||||
else
|
||||
_imageFromURI(faviconSvc.defaultFavicon, privateMode, callback);
|
||||
}
|
||||
|
||||
// Snaps the given rectangle to be pixel-aligned at the given scale
|
||||
function snapRectAtScale(r, scale) {
|
||||
let x = Math.floor(r.x * scale);
|
||||
let y = Math.floor(r.y * scale);
|
||||
let width = Math.ceil((r.x + r.width) * scale) - x;
|
||||
let height = Math.ceil((r.y + r.height) * scale) - y;
|
||||
|
||||
r.x = x / scale;
|
||||
r.y = y / scale;
|
||||
r.width = width / scale;
|
||||
r.height = height / scale;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// PreviewController
|
||||
|
||||
/*
|
||||
* This class manages the behavior of the preview.
|
||||
*
|
||||
* To give greater performance when drawing, the dirty areas of the content
|
||||
* window are tracked and drawn on demand into a canvas of the same size.
|
||||
* This provides a great increase in responsiveness when drawing a preview
|
||||
* for unchanged (or even only slightly changed) tabs.
|
||||
*
|
||||
* @param win
|
||||
* The TabWindow (see below) that owns the preview that this controls
|
||||
* @param tab
|
||||
* The <tab> that this preview is associated with
|
||||
*/
|
||||
function PreviewController(win, tab) {
|
||||
this.win = win;
|
||||
this.tab = tab;
|
||||
this.linkedBrowser = tab.linkedBrowser;
|
||||
this.preview = this.win.createTabPreview(this);
|
||||
|
||||
this.linkedBrowser.addEventListener("MozAfterPaint", this, false);
|
||||
this.tab.addEventListener("TabAttrModified", this, false);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
|
||||
let canvas = this.win.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
canvas.mozOpaque = true;
|
||||
return canvas;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "dirtyRegion",
|
||||
function () {
|
||||
let dirtyRegion = Cc["@mozilla.org/gfx/region;1"]
|
||||
.createInstance(Ci.nsIScriptableRegion);
|
||||
dirtyRegion.init();
|
||||
return dirtyRegion;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "winutils",
|
||||
function () {
|
||||
let win = tab.linkedBrowser.contentWindow;
|
||||
return win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
});
|
||||
}
|
||||
|
||||
PreviewController.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
|
||||
Ci.nsIDOMEventListener]),
|
||||
destroy: function () {
|
||||
this.tab.removeEventListener("TabAttrModified", this, false);
|
||||
this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
|
||||
|
||||
// Break cycles, otherwise we end up leaking the window with everything
|
||||
// attached to it.
|
||||
delete this.win;
|
||||
delete this.preview;
|
||||
delete this.dirtyRegion;
|
||||
},
|
||||
get wrappedJSObject() {
|
||||
return this;
|
||||
},
|
||||
|
||||
get dirtyRects() {
|
||||
let rectstream = this.dirtyRegion.getRects();
|
||||
if (!rectstream)
|
||||
return [];
|
||||
let rects = [];
|
||||
for (let i = 0; i < rectstream.length; i+= 4) {
|
||||
let r = {x: rectstream[i],
|
||||
y: rectstream[i+1],
|
||||
width: rectstream[i+2],
|
||||
height: rectstream[i+3]};
|
||||
rects.push(r);
|
||||
}
|
||||
return rects;
|
||||
},
|
||||
|
||||
// Resizes the canvasPreview to 0x0, essentially freeing its memory.
|
||||
// updateCanvasPreview() will detect the size mismatch as a resize event
|
||||
// the next time it is called.
|
||||
resetCanvasPreview: function () {
|
||||
this.canvasPreview.width = 0;
|
||||
this.canvasPreview.height = 0;
|
||||
},
|
||||
|
||||
get zoom() {
|
||||
// Note that winutils.fullZoom accounts for "quantization" of the zoom factor
|
||||
// from nsIMarkupDocumentViewer due to conversion through appUnits.
|
||||
// We do -not- want screenPixelsPerCSSPixel here, because that would -also-
|
||||
// incorporate any scaling that is applied due to hi-dpi resolution options.
|
||||
return this.winutils.fullZoom;
|
||||
},
|
||||
|
||||
// Updates the controller's canvas with the parts of the <browser> that need
|
||||
// to be redrawn.
|
||||
updateCanvasPreview: function () {
|
||||
let win = this.linkedBrowser.contentWindow;
|
||||
let bx = this.linkedBrowser.boxObject;
|
||||
// Check for resize
|
||||
if (bx.width != this.canvasPreview.width ||
|
||||
bx.height != this.canvasPreview.height) {
|
||||
// Invalidate the entire area and repaint
|
||||
this.onTabPaint({left:0, top:0, right:win.innerWidth, bottom:win.innerHeight});
|
||||
this.canvasPreview.width = bx.width;
|
||||
this.canvasPreview.height = bx.height;
|
||||
}
|
||||
|
||||
// Draw dirty regions
|
||||
let ctx = this.canvasPreview.getContext("2d");
|
||||
let scale = this.zoom;
|
||||
|
||||
let flags = this.canvasPreviewFlags;
|
||||
// The dirty region may include parts that are offscreen so we clip to the
|
||||
// canvas area.
|
||||
this.dirtyRegion.intersectRect(0, 0, win.innerWidth, win.innerHeight);
|
||||
this.dirtyRects.forEach(function (r) {
|
||||
// We need to snap the rectangle to be pixel aligned in the destination
|
||||
// coordinate space. Otherwise natively themed widgets might not draw.
|
||||
snapRectAtScale(r, scale);
|
||||
let x = r.x;
|
||||
let y = r.y;
|
||||
let width = r.width;
|
||||
let height = r.height;
|
||||
|
||||
ctx.save();
|
||||
ctx.scale(scale, scale);
|
||||
ctx.translate(x, y);
|
||||
ctx.drawWindow(win, x, y, width, height, "white", flags);
|
||||
ctx.restore();
|
||||
});
|
||||
this.dirtyRegion.setToRect(0,0,0,0);
|
||||
|
||||
// If we're updating the canvas, then we're in the middle of a peek so
|
||||
// don't discard the cache of previews.
|
||||
AeroPeek.resetCacheTimer();
|
||||
},
|
||||
|
||||
onTabPaint: function (rect) {
|
||||
let x = Math.floor(rect.left),
|
||||
y = Math.floor(rect.top),
|
||||
width = Math.ceil(rect.right) - x,
|
||||
height = Math.ceil(rect.bottom) - y;
|
||||
this.dirtyRegion.unionRect(x, y, width, height);
|
||||
},
|
||||
|
||||
updateTitleAndTooltip: function () {
|
||||
let title = this.win.tabbrowser.getWindowTitleForBrowser(this.linkedBrowser);
|
||||
this.preview.title = title;
|
||||
this.preview.tooltip = title;
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// nsITaskbarPreviewController
|
||||
|
||||
get width() {
|
||||
return this.win.width;
|
||||
},
|
||||
|
||||
get height() {
|
||||
return this.win.height;
|
||||
},
|
||||
|
||||
get thumbnailAspectRatio() {
|
||||
let boxObject = this.tab.linkedBrowser.boxObject;
|
||||
// Avoid returning 0
|
||||
let tabWidth = boxObject.width || 1;
|
||||
// Avoid divide by 0
|
||||
let tabHeight = boxObject.height || 1;
|
||||
return tabWidth / tabHeight;
|
||||
},
|
||||
|
||||
drawPreview: function (ctx) {
|
||||
let self = this;
|
||||
this.win.tabbrowser.previewTab(this.tab, function () self.previewTabCallback(ctx));
|
||||
|
||||
// We must avoid having the frame drawn around the window. See bug 520807
|
||||
return false;
|
||||
},
|
||||
|
||||
previewTabCallback: function (ctx) {
|
||||
// This will extract the resolution-scale component of the scaling we need,
|
||||
// which should be applied to both chrome and content;
|
||||
// the page zoom component is applied (to content only) within updateCanvasPreview.
|
||||
let scale = this.winutils.screenPixelsPerCSSPixel / this.winutils.fullZoom;
|
||||
ctx.save();
|
||||
ctx.scale(scale, scale);
|
||||
let width = this.win.width;
|
||||
let height = this.win.height;
|
||||
// Draw our toplevel window
|
||||
ctx.drawWindow(this.win.win, 0, 0, width, height, "transparent");
|
||||
|
||||
// XXX (jfkthame): Pending tabs don't seem to draw with the proper scaling
|
||||
// unless we use this block of code; but doing this for "normal" (loaded) tabs
|
||||
// results in blurry rendering on hidpi systems, so we avoid it if possible.
|
||||
// I don't understand why pending and loaded tabs behave differently here...
|
||||
// (see bug 857061).
|
||||
if (this.tab.hasAttribute("pending")) {
|
||||
// Compositor, where art thou?
|
||||
// Draw the tab content on top of the toplevel window
|
||||
this.updateCanvasPreview();
|
||||
|
||||
let boxObject = this.linkedBrowser.boxObject;
|
||||
ctx.translate(boxObject.x, boxObject.y);
|
||||
ctx.drawImage(this.canvasPreview, 0, 0);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
drawThumbnail: function (ctx, width, height) {
|
||||
this.updateCanvasPreview();
|
||||
|
||||
let scale = width/this.linkedBrowser.boxObject.width;
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawImage(this.canvasPreview, 0, 0);
|
||||
|
||||
// Don't draw a frame around the thumbnail
|
||||
return false;
|
||||
},
|
||||
|
||||
onClose: function () {
|
||||
this.win.tabbrowser.removeTab(this.tab);
|
||||
},
|
||||
|
||||
onActivate: function () {
|
||||
this.win.tabbrowser.selectedTab = this.tab;
|
||||
|
||||
// Accept activation - this will restore the browser window
|
||||
// if it's minimized
|
||||
return true;
|
||||
},
|
||||
|
||||
//// nsIDOMEventListener
|
||||
handleEvent: function (evt) {
|
||||
switch (evt.type) {
|
||||
case "MozAfterPaint":
|
||||
if (evt.originalTarget === this.linkedBrowser.contentWindow) {
|
||||
let clientRects = evt.clientRects;
|
||||
let length = clientRects.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
let r = clientRects.item(i);
|
||||
this.onTabPaint(r);
|
||||
}
|
||||
}
|
||||
let preview = this.preview;
|
||||
if (preview.visible)
|
||||
preview.invalidate();
|
||||
break;
|
||||
case "TabAttrModified":
|
||||
this.updateTitleAndTooltip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
|
||||
function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D;
|
||||
return canvasInterface.DRAWWINDOW_DRAW_VIEW
|
||||
| canvasInterface.DRAWWINDOW_DRAW_CARET
|
||||
| canvasInterface.DRAWWINDOW_ASYNC_DECODE_IMAGES
|
||||
| canvasInterface.DRAWWINDOW_DO_NOT_FLUSH;
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// TabWindow
|
||||
|
||||
/*
|
||||
* This class monitors a browser window for changes to its tabs
|
||||
*
|
||||
* @param win
|
||||
* The nsIDOMWindow browser window
|
||||
*/
|
||||
function TabWindow(win) {
|
||||
this.win = win;
|
||||
this.tabbrowser = win.gBrowser;
|
||||
|
||||
this.previews = new Map();
|
||||
|
||||
for (let i = 0; i < this.tabEvents.length; i++)
|
||||
this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false);
|
||||
this.tabbrowser.addTabsProgressListener(this);
|
||||
|
||||
AeroPeek.windows.push(this);
|
||||
let tabs = this.tabbrowser.tabs;
|
||||
for (let i = 0; i < tabs.length; i++)
|
||||
this.newTab(tabs[i]);
|
||||
|
||||
this.updateTabOrdering();
|
||||
AeroPeek.checkPreviewCount();
|
||||
}
|
||||
|
||||
TabWindow.prototype = {
|
||||
_enabled: false,
|
||||
tabEvents: ["TabOpen", "TabClose", "TabSelect", "TabMove"],
|
||||
|
||||
destroy: function () {
|
||||
this._destroying = true;
|
||||
|
||||
let tabs = this.tabbrowser.tabs;
|
||||
|
||||
this.tabbrowser.removeTabsProgressListener(this);
|
||||
for (let i = 0; i < this.tabEvents.length; i++)
|
||||
this.tabbrowser.tabContainer.removeEventListener(this.tabEvents[i], this, false);
|
||||
|
||||
for (let i = 0; i < tabs.length; i++)
|
||||
this.removeTab(tabs[i]);
|
||||
|
||||
let idx = AeroPeek.windows.indexOf(this.win.gTaskbarTabGroup);
|
||||
AeroPeek.windows.splice(idx, 1);
|
||||
AeroPeek.checkPreviewCount();
|
||||
},
|
||||
|
||||
get width () {
|
||||
return this.win.innerWidth;
|
||||
},
|
||||
get height () {
|
||||
return this.win.innerHeight;
|
||||
},
|
||||
|
||||
// Invoked when the given tab is added to this window
|
||||
newTab: function (tab) {
|
||||
let controller = new PreviewController(this, tab);
|
||||
// It's OK to add the preview now while the favicon still loads.
|
||||
this.previews.set(tab, controller.preview);
|
||||
AeroPeek.addPreview(controller.preview);
|
||||
// updateTitleAndTooltip relies on having controller.preview which is lazily resolved.
|
||||
// Now that we've updated this.previews, it will resolve successfully.
|
||||
controller.updateTitleAndTooltip();
|
||||
},
|
||||
|
||||
createTabPreview: function (controller) {
|
||||
let docShell = this.win
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
let preview = AeroPeek.taskbar.createTaskbarTabPreview(docShell, controller);
|
||||
preview.visible = AeroPeek.enabled;
|
||||
preview.active = this.tabbrowser.selectedTab == controller.tab;
|
||||
// Grab the default favicon
|
||||
getFaviconAsImage(null, PrivateBrowsingUtils.isWindowPrivate(this.win), function (img) {
|
||||
// It is possible that we've already gotten the real favicon, so make sure
|
||||
// we have not set one before setting this default one.
|
||||
if (!preview.icon)
|
||||
preview.icon = img;
|
||||
});
|
||||
|
||||
return preview;
|
||||
},
|
||||
|
||||
// Invoked when the given tab is closed
|
||||
removeTab: function (tab) {
|
||||
let preview = this.previewFromTab(tab);
|
||||
preview.active = false;
|
||||
preview.visible = false;
|
||||
preview.move(null);
|
||||
preview.controller.wrappedJSObject.destroy();
|
||||
|
||||
// We don't want to splice from the array if the tabs aren't being removed
|
||||
// from the tab bar as well (as is the case when the window closes).
|
||||
this.previews.delete(tab);
|
||||
AeroPeek.removePreview(preview);
|
||||
},
|
||||
|
||||
get enabled () {
|
||||
return this._enabled;
|
||||
},
|
||||
|
||||
set enabled (enable) {
|
||||
this._enabled = enable;
|
||||
// Because making a tab visible requires that the tab it is next to be
|
||||
// visible, it is far simpler to unset the 'next' tab and recreate them all
|
||||
// at once.
|
||||
for (let [tab, preview] of this.previews) {
|
||||
preview.move(null);
|
||||
preview.visible = enable;
|
||||
}
|
||||
this.updateTabOrdering();
|
||||
},
|
||||
|
||||
previewFromTab: function (tab) {
|
||||
return this.previews.get(tab);
|
||||
},
|
||||
|
||||
updateTabOrdering: function () {
|
||||
let previews = this.previews;
|
||||
let tabs = this.tabbrowser.tabs;
|
||||
|
||||
// Previews are internally stored using a map, so we need to iterate over
|
||||
// the tabbrowser's array of tabs to retrieve previews in the same order.
|
||||
let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))];
|
||||
|
||||
// Since the internal taskbar array has not yet been updated, we must force
|
||||
// the sorting order of our local array on it. To do so, we must walk
|
||||
// the local array backwards, because otherwise we would send move requests
|
||||
// in the wrong order. See bug 522610 for details.
|
||||
for (let i = inorder.length - 1; i >= 0; i--) {
|
||||
inorder[i].move(inorder[i + 1] || null);
|
||||
}
|
||||
},
|
||||
|
||||
//// nsIDOMEventListener
|
||||
handleEvent: function (evt) {
|
||||
let tab = evt.originalTarget;
|
||||
switch (evt.type) {
|
||||
case "TabOpen":
|
||||
this.newTab(tab);
|
||||
this.updateTabOrdering();
|
||||
break;
|
||||
case "TabClose":
|
||||
this.removeTab(tab);
|
||||
this.updateTabOrdering();
|
||||
break;
|
||||
case "TabSelect":
|
||||
this.previewFromTab(tab).active = true;
|
||||
break;
|
||||
case "TabMove":
|
||||
this.updateTabOrdering();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
//// Browser progress listener
|
||||
onLinkIconAvailable: function (aBrowser, aIconURL) {
|
||||
let self = this;
|
||||
getFaviconAsImage(aIconURL, PrivateBrowsingUtils.isWindowPrivate(this.win), function (img) {
|
||||
let index = self.tabbrowser.browsers.indexOf(aBrowser);
|
||||
// Only add it if we've found the index. The tab could have closed!
|
||||
if (index != -1) {
|
||||
let tab = self.tabbrowser.tabs[index];
|
||||
self.previews.get(tab).icon = img;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// AeroPeek
|
||||
|
||||
/*
|
||||
* This object acts as global storage and external interface for this feature.
|
||||
* It maintains the values of the prefs.
|
||||
*/
|
||||
this.AeroPeek = {
|
||||
available: false,
|
||||
// Does the pref say we're enabled?
|
||||
_prefenabled: true,
|
||||
|
||||
_enabled: true,
|
||||
|
||||
// nsITaskbarTabPreview array
|
||||
previews: [],
|
||||
|
||||
// TabWindow array
|
||||
windows: [],
|
||||
|
||||
// nsIWinTaskbar service
|
||||
taskbar: null,
|
||||
|
||||
// Maximum number of previews
|
||||
maxpreviews: 20,
|
||||
|
||||
// Length of time in seconds that previews are cached
|
||||
cacheLifespan: 20,
|
||||
|
||||
initialize: function () {
|
||||
if (!(WINTASKBAR_CONTRACTID in Cc))
|
||||
return;
|
||||
this.taskbar = Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar);
|
||||
this.available = this.taskbar.available;
|
||||
if (!this.available)
|
||||
return;
|
||||
|
||||
this.prefs.addObserver(TOGGLE_PREF_NAME, this, false);
|
||||
this.prefs.addObserver(DISABLE_THRESHOLD_PREF_NAME, this, false);
|
||||
this.prefs.addObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this, false);
|
||||
|
||||
this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME);
|
||||
|
||||
this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
|
||||
|
||||
this.enabled = this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
|
||||
},
|
||||
|
||||
destroy: function destroy() {
|
||||
this._enabled = false;
|
||||
|
||||
this.prefs.removeObserver(TOGGLE_PREF_NAME, this);
|
||||
this.prefs.removeObserver(DISABLE_THRESHOLD_PREF_NAME, this);
|
||||
this.prefs.removeObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this);
|
||||
|
||||
if (this.cacheTimer)
|
||||
this.cacheTimer.cancel();
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return this._enabled;
|
||||
},
|
||||
|
||||
set enabled(enable) {
|
||||
if (this._enabled == enable)
|
||||
return;
|
||||
|
||||
this._enabled = enable;
|
||||
|
||||
this.windows.forEach(function (win) {
|
||||
win.enabled = enable;
|
||||
});
|
||||
},
|
||||
|
||||
addPreview: function (preview) {
|
||||
this.previews.push(preview);
|
||||
this.checkPreviewCount();
|
||||
},
|
||||
|
||||
removePreview: function (preview) {
|
||||
let idx = this.previews.indexOf(preview);
|
||||
this.previews.splice(idx, 1);
|
||||
this.checkPreviewCount();
|
||||
},
|
||||
|
||||
checkPreviewCount: function () {
|
||||
if (this.previews.length > this.maxpreviews)
|
||||
this.enabled = false;
|
||||
else
|
||||
this.enabled = this._prefenabled;
|
||||
},
|
||||
|
||||
onOpenWindow: function (win) {
|
||||
// This occurs when the taskbar service is not available (xp, vista)
|
||||
if (!this.available)
|
||||
return;
|
||||
|
||||
win.gTaskbarTabGroup = new TabWindow(win);
|
||||
},
|
||||
|
||||
onCloseWindow: function (win) {
|
||||
// This occurs when the taskbar service is not available (xp, vista)
|
||||
if (!this.available)
|
||||
return;
|
||||
|
||||
win.gTaskbarTabGroup.destroy();
|
||||
delete win.gTaskbarTabGroup;
|
||||
|
||||
if (this.windows.length == 0)
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
resetCacheTimer: function () {
|
||||
this.cacheTimer.cancel();
|
||||
this.cacheTimer.init(this, 1000*this.cacheLifespan, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
},
|
||||
|
||||
//// nsIObserver
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "nsPref:changed":
|
||||
if (aData == CACHE_EXPIRATION_TIME_PREF_NAME)
|
||||
break;
|
||||
|
||||
if (aData == TOGGLE_PREF_NAME)
|
||||
this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
|
||||
else if (aData == DISABLE_THRESHOLD_PREF_NAME)
|
||||
this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
|
||||
// Might need to enable/disable ourselves
|
||||
this.checkPreviewCount();
|
||||
break;
|
||||
case "timer-callback":
|
||||
this.previews.forEach(function (preview) {
|
||||
let controller = preview.controller.wrappedJSObject;
|
||||
controller.resetCanvasPreview();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(AeroPeek, "cacheTimer", function ()
|
||||
Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(AeroPeek, "prefs",
|
||||
"@mozilla.org/preferences-service;1",
|
||||
"nsIPrefBranch");
|
||||
|
||||
AeroPeek.initialize();
|
||||
39
application/palemoon/modules/moz.build
Normal file
39
application/palemoon/modules/moz.build
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# -*- 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/.
|
||||
|
||||
|
||||
# XXX: Include this until we convert browser/ to use toolkit promises directly
|
||||
EXTRA_JS_MODULES += [ 'promise.js' ]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'BrowserNewTabPreloader.jsm',
|
||||
'CharsetMenu.jsm',
|
||||
'FormSubmitObserver.jsm',
|
||||
'FormValidationHandler.jsm',
|
||||
'NetworkPrioritizer.jsm',
|
||||
'offlineAppCache.jsm',
|
||||
'openLocationLastURL.jsm',
|
||||
'PageMenu.jsm',
|
||||
'PopupNotifications.jsm',
|
||||
'QuotaManager.jsm',
|
||||
'SharedFrame.jsm',
|
||||
'webrtcUI.jsm'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
||||
EXTRA_JS_MODULES += [
|
||||
'Windows8WindowFrameColor.jsm',
|
||||
'WindowsJumpLists.jsm',
|
||||
'WindowsPreviewPerTab.jsm',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
'AboutHomeUtils.jsm',
|
||||
'RecentWindow.jsm',
|
||||
]
|
||||
|
||||
if CONFIG['MOZILLA_OFFICIAL']:
|
||||
DEFINES['MOZILLA_OFFICIAL'] = 1
|
||||
20
application/palemoon/modules/offlineAppCache.jsm
Normal file
20
application/palemoon/modules/offlineAppCache.jsm
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* 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 = ["OfflineAppCacheHelper"];
|
||||
|
||||
Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
this.OfflineAppCacheHelper = {
|
||||
clear: function() {
|
||||
var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
|
||||
var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
|
||||
try {
|
||||
appCacheStorage.asyncEvictStorage(null);
|
||||
} catch(er) {}
|
||||
}
|
||||
};
|
||||
85
application/palemoon/modules/openLocationLastURL.jsm
Normal file
85
application/palemoon/modules/openLocationLastURL.jsm
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* 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 LAST_URL_PREF = "general.open_location.last_url";
|
||||
const nsISupportsString = Components.interfaces.nsISupportsString;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "OpenLocationLastURL" ];
|
||||
|
||||
let prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let gOpenLocationLastURLData = "";
|
||||
|
||||
let observer = {
|
||||
QueryInterface: function (aIID) {
|
||||
if (aIID.equals(Components.interfaces.nsIObserver) ||
|
||||
aIID.equals(Components.interfaces.nsISupports) ||
|
||||
aIID.equals(Components.interfaces.nsISupportsWeakReference))
|
||||
return this;
|
||||
throw Components.results.NS_NOINTERFACE;
|
||||
},
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "last-pb-context-exited":
|
||||
gOpenLocationLastURLData = "";
|
||||
break;
|
||||
case "browser:purge-session-history":
|
||||
prefSvc.clearUserPref(LAST_URL_PREF);
|
||||
gOpenLocationLastURLData = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let os = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
os.addObserver(observer, "last-pb-context-exited", true);
|
||||
os.addObserver(observer, "browser:purge-session-history", true);
|
||||
|
||||
|
||||
this.OpenLocationLastURL = function OpenLocationLastURL(aWindow) {
|
||||
this.window = aWindow;
|
||||
}
|
||||
|
||||
OpenLocationLastURL.prototype = {
|
||||
isPrivate: function OpenLocationLastURL_isPrivate() {
|
||||
// Assume not in private browsing mode, unless the browser window is
|
||||
// in private mode.
|
||||
if (!this.window)
|
||||
return false;
|
||||
|
||||
return PrivateBrowsingUtils.isWindowPrivate(this.window);
|
||||
},
|
||||
get value() {
|
||||
if (this.isPrivate())
|
||||
return gOpenLocationLastURLData;
|
||||
else {
|
||||
try {
|
||||
return prefSvc.getComplexValue(LAST_URL_PREF, nsISupportsString).data;
|
||||
}
|
||||
catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
},
|
||||
set value(val) {
|
||||
if (typeof val != "string")
|
||||
val = "";
|
||||
if (this.isPrivate())
|
||||
gOpenLocationLastURLData = val;
|
||||
else {
|
||||
let str = Components.classes["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Components.interfaces.nsISupportsString);
|
||||
str.data = val;
|
||||
prefSvc.setComplexValue(LAST_URL_PREF, nsISupportsString, str);
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
prefSvc.clearUserPref(LAST_URL_PREF);
|
||||
gOpenLocationLastURLData = "";
|
||||
}
|
||||
};
|
||||
118
application/palemoon/modules/promise.js
Normal file
118
application/palemoon/modules/promise.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* 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';
|
||||
|
||||
/*
|
||||
* Uses `Promise.jsm` as a core implementation, with additional sugar
|
||||
* from previous implementation, with inspiration from `Q` and `when`
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm
|
||||
* https://github.com/cujojs/when
|
||||
* https://github.com/kriskowal/q
|
||||
*/
|
||||
const PROMISE_URI = 'resource://gre/modules/Promise.jsm';
|
||||
|
||||
getEnvironment.call(this, function ({ require, exports, module, Cu }) {
|
||||
|
||||
const Promise = Cu.import(PROMISE_URI, {}).Promise;
|
||||
const { Debugging, defer, resolve, all, reject, race } = Promise;
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'unstable'
|
||||
};
|
||||
|
||||
let promised = (function() {
|
||||
// Note: Define shortcuts and utility functions here in order to avoid
|
||||
// slower property accesses and unnecessary closure creations on each
|
||||
// call of this popular function.
|
||||
|
||||
var call = Function.call;
|
||||
var concat = Array.prototype.concat;
|
||||
|
||||
// Utility function that does following:
|
||||
// execute([ f, self, args...]) => f.apply(self, args)
|
||||
function execute (args) call.apply(call, args)
|
||||
|
||||
// Utility function that takes promise of `a` array and maybe promise `b`
|
||||
// as arguments and returns promise for `a.concat(b)`.
|
||||
function promisedConcat(promises, unknown) {
|
||||
return promises.then(function (values) {
|
||||
return resolve(unknown)
|
||||
.then(function (value) values.concat([value]));
|
||||
});
|
||||
}
|
||||
|
||||
return function promised(f, prototype) {
|
||||
/**
|
||||
Returns a wrapped `f`, which when called returns a promise that resolves to
|
||||
`f(...)` passing all the given arguments to it, which by the way may be
|
||||
promises. Optionally second `prototype` argument may be provided to be used
|
||||
a prototype for a returned promise.
|
||||
|
||||
## Example
|
||||
|
||||
var promise = promised(Array)(1, promise(2), promise(3))
|
||||
promise.then(console.log) // => [ 1, 2, 3 ]
|
||||
**/
|
||||
|
||||
return function promised(...args) {
|
||||
// create array of [ f, this, args... ]
|
||||
return [f, this, ...args].
|
||||
// reduce it via `promisedConcat` to get promised array of fulfillments
|
||||
reduce(promisedConcat, resolve([], prototype)).
|
||||
// finally map that to promise of `f.apply(this, args...)`
|
||||
then(execute);
|
||||
};
|
||||
};
|
||||
})();
|
||||
|
||||
exports.promised = promised;
|
||||
exports.all = all;
|
||||
exports.defer = defer;
|
||||
exports.resolve = resolve;
|
||||
exports.reject = reject;
|
||||
exports.race = race;
|
||||
exports.Promise = Promise;
|
||||
exports.Debugging = Debugging;
|
||||
});
|
||||
|
||||
function getEnvironment (callback) {
|
||||
let Cu, _exports, _module, _require;
|
||||
|
||||
// CommonJS / SDK
|
||||
if (typeof(require) === 'function') {
|
||||
Cu = require('chrome').Cu;
|
||||
_exports = exports;
|
||||
_module = module;
|
||||
_require = require;
|
||||
}
|
||||
// JSM
|
||||
else if (String(this).indexOf('BackstagePass') >= 0) {
|
||||
Cu = this['Components'].utils;
|
||||
_exports = this.Promise = {};
|
||||
_module = { uri: __URI__, id: 'promise/core' };
|
||||
_require = uri => {
|
||||
let imports = {};
|
||||
Cu.import(uri, imports);
|
||||
return imports;
|
||||
};
|
||||
this.EXPORTED_SYMBOLS = ['Promise'];
|
||||
// mozIJSSubScriptLoader.loadSubscript
|
||||
} else if (~String(this).indexOf('Sandbox')) {
|
||||
Cu = this['Components'].utils;
|
||||
_exports = this;
|
||||
_module = { id: 'promise/core' };
|
||||
_require = uri => {};
|
||||
}
|
||||
|
||||
callback({
|
||||
Cu: Cu,
|
||||
exports: _exports,
|
||||
module: _module,
|
||||
require: _require
|
||||
});
|
||||
}
|
||||
|
||||
272
application/palemoon/modules/webrtcUI.jsm
Normal file
272
application/palemoon/modules/webrtcUI.jsm
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/* 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 = ["webrtcUI"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
|
||||
"@mozilla.org/mediaManagerService;1",
|
||||
"nsIMediaManagerService");
|
||||
|
||||
this.webrtcUI = {
|
||||
init: function () {
|
||||
Services.obs.addObserver(handleRequest, "getUserMedia:request", false);
|
||||
Services.obs.addObserver(updateIndicators, "recording-device-events", false);
|
||||
Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
Services.obs.removeObserver(handleRequest, "getUserMedia:request");
|
||||
Services.obs.removeObserver(updateIndicators, "recording-device-events");
|
||||
Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
|
||||
},
|
||||
|
||||
showGlobalIndicator: false,
|
||||
|
||||
get activeStreams() {
|
||||
let contentWindowSupportsArray = MediaManagerService.activeMediaCaptureWindows;
|
||||
let count = contentWindowSupportsArray.Count();
|
||||
let activeStreams = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
let contentWindow = contentWindowSupportsArray.GetElementAt(i);
|
||||
let browser = getBrowserForWindow(contentWindow);
|
||||
let browserWindow = browser.ownerDocument.defaultView;
|
||||
let tab = browserWindow.gBrowser &&
|
||||
browserWindow.gBrowser._getTabForContentWindow(contentWindow.top);
|
||||
activeStreams.push({
|
||||
uri: contentWindow.location.href,
|
||||
tab: tab,
|
||||
browser: browser
|
||||
});
|
||||
}
|
||||
return activeStreams;
|
||||
}
|
||||
}
|
||||
|
||||
function getBrowserForWindowId(aWindowID) {
|
||||
return getBrowserForWindow(Services.wm.getOuterWindowWithId(aWindowID));
|
||||
}
|
||||
|
||||
function getBrowserForWindow(aContentWindow) {
|
||||
return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
}
|
||||
|
||||
function handleRequest(aSubject, aTopic, aData) {
|
||||
let {windowID: windowID, callID: callID} = JSON.parse(aData);
|
||||
|
||||
let params = aSubject.QueryInterface(Ci.nsIMediaStreamOptions);
|
||||
|
||||
Services.wm.getMostRecentWindow(null).navigator.mozGetUserMediaDevices(
|
||||
function (devices) {
|
||||
prompt(windowID, callID, params.audio, params.video || params.picture, devices);
|
||||
},
|
||||
function (error) {
|
||||
// bug 827146 -- In the future, the UI should catch NO_DEVICES_FOUND
|
||||
// and allow the user to plug in a device, instead of immediately failing.
|
||||
denyRequest(callID, error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function denyRequest(aCallID, aError) {
|
||||
let msg = null;
|
||||
if (aError) {
|
||||
msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
|
||||
msg.data = aError;
|
||||
}
|
||||
Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aCallID);
|
||||
}
|
||||
|
||||
function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices) {
|
||||
let audioDevices = [];
|
||||
let videoDevices = [];
|
||||
for (let device of aDevices) {
|
||||
device = device.QueryInterface(Ci.nsIMediaDevice);
|
||||
switch (device.type) {
|
||||
case "audio":
|
||||
if (aAudioRequested)
|
||||
audioDevices.push(device);
|
||||
break;
|
||||
case "video":
|
||||
if (aVideoRequested)
|
||||
videoDevices.push(device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let requestType;
|
||||
if (audioDevices.length && videoDevices.length)
|
||||
requestType = "CameraAndMicrophone";
|
||||
else if (audioDevices.length)
|
||||
requestType = "Microphone";
|
||||
else if (videoDevices.length)
|
||||
requestType = "Camera";
|
||||
else {
|
||||
denyRequest(aCallID, "NO_DEVICES_FOUND");
|
||||
return;
|
||||
}
|
||||
|
||||
let contentWindow = Services.wm.getOuterWindowWithId(aWindowID);
|
||||
let host = contentWindow.document.documentURIObject.host;
|
||||
let browser = getBrowserForWindow(contentWindow);
|
||||
let chromeDoc = browser.ownerDocument;
|
||||
let chromeWin = chromeDoc.defaultView;
|
||||
let stringBundle = chromeWin.gNavigatorBundle;
|
||||
let message = stringBundle.getFormattedString("getUserMedia.share" + requestType + ".message",
|
||||
[ host ]);
|
||||
|
||||
function listDevices(menupopup, devices) {
|
||||
while (menupopup.lastChild)
|
||||
menupopup.removeChild(menupopup.lastChild);
|
||||
|
||||
let deviceIndex = 0;
|
||||
for (let device of devices) {
|
||||
addDeviceToList(menupopup, device.name, deviceIndex);
|
||||
deviceIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
function addDeviceToList(menupopup, deviceName, deviceIndex) {
|
||||
let menuitem = chromeDoc.createElement("menuitem");
|
||||
menuitem.setAttribute("value", deviceIndex);
|
||||
menuitem.setAttribute("label", deviceName);
|
||||
menuitem.setAttribute("tooltiptext", deviceName);
|
||||
menupopup.appendChild(menuitem);
|
||||
}
|
||||
|
||||
chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length;
|
||||
chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length;
|
||||
|
||||
let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
|
||||
let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
|
||||
listDevices(camMenupopup, videoDevices);
|
||||
listDevices(micMenupopup, audioDevices);
|
||||
if (requestType == "CameraAndMicrophone") {
|
||||
addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1");
|
||||
addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
|
||||
}
|
||||
|
||||
let mainAction = {
|
||||
label: PluralForm.get(requestType == "CameraAndMicrophone" ? 2 : 1,
|
||||
stringBundle.getString("getUserMedia.shareSelectedDevices.label")),
|
||||
accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
|
||||
callback: function () {
|
||||
let allowedDevices = Cc["@mozilla.org/supports-array;1"]
|
||||
.createInstance(Ci.nsISupportsArray);
|
||||
if (videoDevices.length) {
|
||||
let videoDeviceIndex = chromeDoc.getElementById("webRTC-selectCamera-menulist").value;
|
||||
if (videoDeviceIndex != "-1")
|
||||
allowedDevices.AppendElement(videoDevices[videoDeviceIndex]);
|
||||
}
|
||||
if (audioDevices.length) {
|
||||
let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
|
||||
if (audioDeviceIndex != "-1")
|
||||
allowedDevices.AppendElement(audioDevices[audioDeviceIndex]);
|
||||
}
|
||||
|
||||
if (allowedDevices.Count() == 0) {
|
||||
denyRequest(aCallID);
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
|
||||
}
|
||||
};
|
||||
|
||||
let secondaryActions = [{
|
||||
label: stringBundle.getString("getUserMedia.denyRequest.label"),
|
||||
accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
|
||||
callback: function () {
|
||||
denyRequest(aCallID);
|
||||
}
|
||||
}];
|
||||
|
||||
let options = null;
|
||||
|
||||
chromeWin.PopupNotifications.show(browser, "webRTC-shareDevices", message,
|
||||
"webRTC-shareDevices-notification-icon", mainAction,
|
||||
secondaryActions, options);
|
||||
}
|
||||
|
||||
function updateIndicators() {
|
||||
webrtcUI.showGlobalIndicator =
|
||||
MediaManagerService.activeMediaCaptureWindows.Count() > 0;
|
||||
|
||||
let e = Services.wm.getEnumerator("navigator:browser");
|
||||
while (e.hasMoreElements())
|
||||
e.getNext().WebrtcIndicator.updateButton();
|
||||
|
||||
for (let {browser: browser} of webrtcUI.activeStreams)
|
||||
showBrowserSpecificIndicator(browser);
|
||||
}
|
||||
|
||||
function showBrowserSpecificIndicator(aBrowser) {
|
||||
let hasVideo = {};
|
||||
let hasAudio = {};
|
||||
MediaManagerService.mediaCaptureWindowState(aBrowser.contentWindow,
|
||||
hasVideo, hasAudio);
|
||||
let captureState;
|
||||
if (hasVideo.value && hasAudio.value) {
|
||||
captureState = "CameraAndMicrophone";
|
||||
} else if (hasVideo.value) {
|
||||
captureState = "Camera";
|
||||
} else if (hasAudio.value) {
|
||||
captureState = "Microphone";
|
||||
} else {
|
||||
Cu.reportError("showBrowserSpecificIndicator: got neither video nor audio access");
|
||||
return;
|
||||
}
|
||||
|
||||
let chromeWin = aBrowser.ownerDocument.defaultView;
|
||||
let stringBundle = chromeWin.gNavigatorBundle;
|
||||
|
||||
let message = stringBundle.getString("getUserMedia.sharing" + captureState + ".message2");
|
||||
|
||||
let windowId = aBrowser.contentWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.currentInnerWindowID;
|
||||
let mainAction = {
|
||||
label: "Continue Sharing", //stringBundle.getString("getUserMedia.continueSharing.label"),
|
||||
accessKey: "C", //stringBundle.getString("getUserMedia.continueSharing.accesskey"),
|
||||
callback: function () {},
|
||||
dismiss: true
|
||||
};
|
||||
let secondaryActions = [{
|
||||
label: "Stop Sharing", //stringBundle.getString("getUserMedia.stopSharing.label"),
|
||||
accessKey: "S", //stringBundle.getString("getUserMedia.stopSharing.accesskey"),
|
||||
callback: function () {
|
||||
Services.obs.notifyObservers(null, "getUserMedia:revoke", windowId);
|
||||
}
|
||||
}];
|
||||
let options = {
|
||||
hideNotNow: true,
|
||||
dismissed: true
|
||||
};
|
||||
chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
|
||||
"webRTC-sharingDevices-notification-icon", mainAction,
|
||||
secondaryActions, options);
|
||||
}
|
||||
|
||||
function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
|
||||
let browser = getBrowserForWindowId(aData);
|
||||
let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
|
||||
let notification = PopupNotifications &&
|
||||
PopupNotifications.getNotification("webRTC-sharingDevices",
|
||||
browser);
|
||||
if (notification)
|
||||
PopupNotifications.remove(notification);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue