merge m-c to fx-team; a=merge
--HG-- extra : amend_source : d5fc0736172ed22bc792e03fe69d0f67531b6273
@ -526,6 +526,7 @@ pref("privacy.clearOnShutdown.cache", true);
|
||||
pref("privacy.clearOnShutdown.sessions", true);
|
||||
pref("privacy.clearOnShutdown.offlineApps", false);
|
||||
pref("privacy.clearOnShutdown.siteSettings", false);
|
||||
pref("privacy.clearOnShutdown.openWindows", false);
|
||||
|
||||
pref("privacy.cpd.history", true);
|
||||
pref("privacy.cpd.formdata", true);
|
||||
@ -536,6 +537,7 @@ pref("privacy.cpd.cache", true);
|
||||
pref("privacy.cpd.sessions", true);
|
||||
pref("privacy.cpd.offlineApps", false);
|
||||
pref("privacy.cpd.siteSettings", false);
|
||||
pref("privacy.cpd.openWindows", false);
|
||||
|
||||
// What default should we use for the time span in the sanitizer:
|
||||
// 0 - Clear everything
|
||||
@ -543,11 +545,15 @@ pref("privacy.cpd.siteSettings", false);
|
||||
// 2 - Last 2 Hours
|
||||
// 3 - Last 4 Hours
|
||||
// 4 - Today
|
||||
// 5 - Last 5 minutes
|
||||
// 6 - Last 24 hours
|
||||
pref("privacy.sanitize.timeSpan", 1);
|
||||
pref("privacy.sanitize.sanitizeOnShutdown", false);
|
||||
|
||||
pref("privacy.sanitize.migrateFx3Prefs", false);
|
||||
|
||||
pref("privacy.panicButton.enabled", true);
|
||||
|
||||
pref("network.proxy.share_proxy_settings", false); // use the same proxy settings for all protocols
|
||||
|
||||
// simple gestures support
|
||||
|
@ -1297,6 +1297,8 @@ var gBrowserInit = {
|
||||
.getBoolPref("privacy.trackingprotection.enabled");
|
||||
Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED")
|
||||
.add(tpEnabled);
|
||||
|
||||
PanicButtonNotifier.init();
|
||||
});
|
||||
this.delayedStartupFinished = true;
|
||||
|
||||
@ -7412,3 +7414,35 @@ let ToolbarIconColor = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let PanicButtonNotifier = {
|
||||
init: function() {
|
||||
this._initialized = true;
|
||||
if (window.PanicButtonNotifierShouldNotify) {
|
||||
delete window.PanicButtonNotifierShouldNotify;
|
||||
this.notify();
|
||||
}
|
||||
},
|
||||
notify: function() {
|
||||
if (!this._initialized) {
|
||||
window.PanicButtonNotifierShouldNotify = true;
|
||||
return;
|
||||
}
|
||||
// Display notification panel here...
|
||||
try {
|
||||
let popup = document.getElementById("panic-button-success-notification");
|
||||
popup.hidden = false;
|
||||
let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
|
||||
let anchor = widget.anchor;
|
||||
anchor = document.getAnonymousElementByAttribute(anchor, "class", "toolbarbutton-icon");
|
||||
popup.openPopup(anchor, popup.getAttribute("position"));
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
let popup = document.getElementById("panic-button-success-notification");
|
||||
popup.hidePopup();
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm");
|
||||
Cu.import("resource:///modules/DirectoryLinksProvider.jsm");
|
||||
Cu.import("resource://gre/modules/NewTabUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Rect",
|
||||
|
@ -4,6 +4,7 @@
|
||||
# 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");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
|
||||
@ -50,14 +51,34 @@ Sanitizer.prototype = {
|
||||
* Returns a promise which is resolved if no errors occurred. If an error
|
||||
* occurs, a message is reported to the console and all other items are still
|
||||
* cleared before the promise is finally rejected.
|
||||
*
|
||||
* If the consumer specifies the (optional) array parameter, only those
|
||||
* items get cleared (irrespective of the preference settings)
|
||||
*/
|
||||
sanitize: function ()
|
||||
sanitize: function (aItemsToClear)
|
||||
{
|
||||
var deferred = Promise.defer();
|
||||
var psvc = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefService);
|
||||
var branch = psvc.getBranch(this.prefDomain);
|
||||
var seenError = false;
|
||||
if (Array.isArray(aItemsToClear)) {
|
||||
var itemsToClear = [...aItemsToClear];
|
||||
} else {
|
||||
let branch = Services.prefs.getBranch(this.prefDomain);
|
||||
itemsToClear = Object.keys(this.items).filter(itemName => branch.getBoolPref(itemName));
|
||||
}
|
||||
|
||||
// Ensure open windows get cleared first, if they're in our list, so that they don't stick
|
||||
// around in the recently closed windows list, and so we can cancel the whole thing
|
||||
// if the user selects to keep a window open from a beforeunload prompt.
|
||||
let openWindowsIndex = itemsToClear.indexOf("openWindows");
|
||||
if (openWindowsIndex != -1) {
|
||||
itemsToClear.splice(openWindowsIndex, 1);
|
||||
let item = this.items.openWindows;
|
||||
if (!item.clear()) {
|
||||
// When cancelled, reject the deferred and return the promise:
|
||||
deferred.reject();
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the range of times to clear
|
||||
if (this.ignoreTimespan)
|
||||
@ -65,16 +86,16 @@ Sanitizer.prototype = {
|
||||
else
|
||||
range = this.range || Sanitizer.getClearRange();
|
||||
|
||||
let itemCount = Object.keys(this.items).length;
|
||||
let itemCount = Object.keys(itemsToClear).length;
|
||||
let onItemComplete = function() {
|
||||
if (!--itemCount) {
|
||||
seenError ? deferred.reject() : deferred.resolve();
|
||||
}
|
||||
};
|
||||
for (var itemName in this.items) {
|
||||
for (let itemName of itemsToClear) {
|
||||
let item = this.items[itemName];
|
||||
item.range = range;
|
||||
if ("clear" in item && branch.getBoolPref(itemName)) {
|
||||
if ("clear" in item) {
|
||||
let clearCallback = (itemName, aCanClear) => {
|
||||
// Some of these clear() may raise exceptions (see bug #265028)
|
||||
// to sanitize as much as possible, we catch and store them,
|
||||
@ -401,7 +422,89 @@ Sanitizer.prototype = {
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
openWindows: {
|
||||
privateStateForNewWindow: "non-private",
|
||||
_canCloseWindow: function(aWindow) {
|
||||
// Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process
|
||||
if (!aWindow.gMultiProcessBrowser) {
|
||||
// Cargo-culted out of browser.js' WindowIsClosing because we don't care
|
||||
// about TabView or the regular 'warn me before closing windows with N tabs'
|
||||
// stuff here, and more importantly, we want to set aCallerClosesWindow to true
|
||||
// when calling into permitUnload:
|
||||
for (let browser of aWindow.gBrowser.browsers) {
|
||||
let ds = browser.docShell;
|
||||
// 'true' here means we will be closing the window soon, so please don't dispatch
|
||||
// another onbeforeunload event when we do so. If unload is *not* permitted somewhere,
|
||||
// we will reset the flag that this triggers everywhere so that we don't interfere
|
||||
// with the browser after all:
|
||||
if (ds.contentViewer && !ds.contentViewer.permitUnload(true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
_resetAllWindowClosures: function(aWindowList) {
|
||||
for (let win of aWindowList) {
|
||||
win.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
|
||||
}
|
||||
},
|
||||
clear: function()
|
||||
{
|
||||
// NB: this closes all *browser* windows, not other windows like the library, about window,
|
||||
// browser console, etc.
|
||||
|
||||
// Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
|
||||
// dialogs
|
||||
let existingWindow = Services.appShell.hiddenDOMWindow;
|
||||
let startDate = existingWindow.performance.now();
|
||||
|
||||
// First check if all these windows are OK with being closed:
|
||||
let windowEnumerator = Services.wm.getEnumerator("navigator:browser");
|
||||
let windowList = [];
|
||||
while (windowEnumerator.hasMoreElements()) {
|
||||
let someWin = windowEnumerator.getNext();
|
||||
windowList.push(someWin);
|
||||
// If someone says "no" to a beforeunload prompt, we abort here:
|
||||
if (!this._canCloseWindow(someWin)) {
|
||||
this._resetAllWindowClosures(windowList);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...however, beforeunload prompts spin the event loop, and so the code here won't get
|
||||
// hit until the prompt has been dismissed. If more than 1 minute has elapsed since we
|
||||
// started prompting, stop, because the user might not even remember initiating the
|
||||
// 'forget', and the timespans will be all wrong by now anyway:
|
||||
if (existingWindow.performance.now() > (startDate + 60 * 1000)) {
|
||||
this._resetAllWindowClosures(windowList);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If/once we get here, we should actually be able to close all windows.
|
||||
|
||||
// First create a new window. We do this first so that on non-mac, we don't
|
||||
// accidentally close the app by closing all the windows.
|
||||
let handler = Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler);
|
||||
let defaultArgs = handler.defaultArgs;
|
||||
let features = "chrome,all,dialog=no," + this.privateStateForNewWindow;
|
||||
let newWindow = existingWindow.openDialog("chrome://browser/content/", "_blank",
|
||||
features, defaultArgs);
|
||||
|
||||
// Then close all those windows we checked:
|
||||
while (windowList.length) {
|
||||
windowList.pop().close();
|
||||
}
|
||||
newWindow.focus();
|
||||
return true;
|
||||
},
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@ -419,6 +522,8 @@ Sanitizer.TIMESPAN_HOUR = 1;
|
||||
Sanitizer.TIMESPAN_2HOURS = 2;
|
||||
Sanitizer.TIMESPAN_4HOURS = 3;
|
||||
Sanitizer.TIMESPAN_TODAY = 4;
|
||||
Sanitizer.TIMESPAN_5MIN = 5;
|
||||
Sanitizer.TIMESPAN_24HOURS = 6;
|
||||
|
||||
// Return a 2 element array representing the start and end times,
|
||||
// in the uSec-since-epoch format that PRTime likes. If we should
|
||||
@ -433,8 +538,11 @@ Sanitizer.getClearRange = function (ts) {
|
||||
// PRTime is microseconds while JS time is milliseconds
|
||||
var endDate = Date.now() * 1000;
|
||||
switch (ts) {
|
||||
case Sanitizer.TIMESPAN_5MIN :
|
||||
var startDate = endDate - 300000000; // 5*60*1000000
|
||||
break;
|
||||
case Sanitizer.TIMESPAN_HOUR :
|
||||
var startDate = endDate - 3600000000; // 1*60*60*1000000
|
||||
startDate = endDate - 3600000000; // 1*60*60*1000000
|
||||
break;
|
||||
case Sanitizer.TIMESPAN_2HOURS :
|
||||
startDate = endDate - 7200000000; // 2*60*60*1000000
|
||||
@ -449,6 +557,9 @@ Sanitizer.getClearRange = function (ts) {
|
||||
d.setSeconds(0);
|
||||
startDate = d.valueOf() * 1000; // convert to epoch usec
|
||||
break;
|
||||
case Sanitizer.TIMESPAN_24HOURS :
|
||||
startDate = endDate - 86400000000; // 24*60*60*1000000
|
||||
break;
|
||||
default:
|
||||
throw "Invalid time span for clear private data: " + ts;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
|
||||
*/
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
let DirectoryLinksProvider = Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
|
||||
let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
|
||||
let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils;
|
||||
let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
|
||||
|
@ -9,7 +9,7 @@ Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/Promise.jsm", tmp);
|
||||
Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
|
||||
Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm", tmp);
|
||||
Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://browser/content/sanitize.js", tmp);
|
||||
|
@ -20,6 +20,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
||||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
|
||||
"resource://gre/modules/CharsetMenu.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
|
||||
const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
|
||||
@ -947,6 +949,98 @@ if (Services.metro && Services.metro.supported) {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
|
||||
CustomizableWidgets.push({
|
||||
id: "panic-button",
|
||||
type: "view",
|
||||
viewId: "PanelUI-panicView",
|
||||
_sanitizer: null,
|
||||
_ensureSanitizer: function() {
|
||||
if (!this.sanitizer) {
|
||||
let scope = {};
|
||||
Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js",
|
||||
scope);
|
||||
this._Sanitizer = scope.Sanitizer;
|
||||
this._sanitizer = new scope.Sanitizer();
|
||||
this._sanitizer.ignoreTimespan = false;
|
||||
}
|
||||
},
|
||||
_getSanitizeRange: function(aDocument) {
|
||||
let group = aDocument.getElementById("PanelUI-panic-timeSpan");
|
||||
return this._Sanitizer.getClearRange(+group.value);
|
||||
},
|
||||
forgetButtonCalled: function(aEvent) {
|
||||
let doc = aEvent.target.ownerDocument;
|
||||
this._ensureSanitizer();
|
||||
this._sanitizer.range = this._getSanitizeRange(doc);
|
||||
let group = doc.getElementById("PanelUI-panic-timeSpan");
|
||||
group.selectedItem = doc.getElementById("PanelUI-panic-5min");
|
||||
let itemsToClear = [
|
||||
"cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"
|
||||
];
|
||||
let newWindowPrivateState = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView) ?
|
||||
"private" : "non-private";
|
||||
this._sanitizer.items.openWindows.privateStateForNewWindow = newWindowPrivateState;
|
||||
let promise = this._sanitizer.sanitize(itemsToClear);
|
||||
promise.then(function() {
|
||||
let otherWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
if (otherWindow.closed) {
|
||||
Cu.reportError("Got a closed window!");
|
||||
}
|
||||
if (otherWindow.PanicButtonNotifier) {
|
||||
otherWindow.PanicButtonNotifier.notify();
|
||||
} else {
|
||||
otherWindow.PanicButtonNotifierShouldNotify = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "command":
|
||||
this.forgetButtonCalled(aEvent);
|
||||
break;
|
||||
case "popupshowing":
|
||||
let popup = aEvent.target;
|
||||
if (popup.id == "customizationui-widget-panel" &&
|
||||
popup.querySelector("#PanelUI-panicView")) {
|
||||
popup.ownerDocument.removeEventListener("popupshowing", this);
|
||||
this._updateHeights(popup, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
// Workaround bug 451997 by hardcoding heights for (potentially) wrapped items:
|
||||
_updateHeights: function(aContainer, aSetHeights) {
|
||||
// Make sure we don't get stuck not finding anything because of the XBL binding between
|
||||
// the popup and the radio/label elements:
|
||||
let view = aContainer.ownerDocument.getElementById("PanelUI-panicView");
|
||||
let variableHeightItems = view.querySelectorAll("radio, label");
|
||||
let win = aContainer.ownerDocument.defaultView;
|
||||
for (let item of variableHeightItems) {
|
||||
if (aSetHeights) {
|
||||
item.style.height = win.getComputedStyle(item, null).getPropertyValue("height");
|
||||
} else {
|
||||
item.style.removeProperty("height");
|
||||
}
|
||||
}
|
||||
},
|
||||
onViewShowing: function(aEvent) {
|
||||
let view = aEvent.target;
|
||||
let forgetButton = view.querySelector("#PanelUI-panic-view-button");
|
||||
forgetButton.addEventListener("command", this);
|
||||
// When the popup starts showing, fix the label and radio heights
|
||||
// if we're in a standalone view (can't tell from here) - see updateHeights.
|
||||
view.ownerDocument.addEventListener("popupshowing", this);
|
||||
},
|
||||
onViewHiding: function(aEvent) {
|
||||
let view = aEvent.target;
|
||||
let forgetButton = view.querySelector("#PanelUI-panic-view-button");
|
||||
forgetButton.removeEventListener("command", this);
|
||||
this._updateHeights(view, false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
/**
|
||||
* The e10s button's purpose is to lower the barrier of entry
|
||||
|
@ -179,6 +179,40 @@
|
||||
</vbox>
|
||||
</panelview>
|
||||
|
||||
<panelview id="PanelUI-panicView" flex="1">
|
||||
<vbox class="panel-subview-body">
|
||||
<hbox id="PanelUI-panic-timeframe">
|
||||
<image id="PanelUI-panic-timeframe-icon" alt=""/>
|
||||
<vbox flex="1">
|
||||
<hbox id="PanelUI-panic-header">
|
||||
<image id="PanelUI-panic-timeframe-icon-small" alt=""/>
|
||||
<description value="&panicButton.view.mainTimeframeDesc;" id="PanelUI-panic-mainDesc"/>
|
||||
</hbox>
|
||||
<radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc">
|
||||
<radio id="PanelUI-panic-5min" label="&panicButton.view.5min;" selected="true"
|
||||
value="5" class="subviewradio"/>
|
||||
<radio id="PanelUI-panic-2hr" label="&panicButton.view.2hr;"
|
||||
value="2" class="subviewradio"/>
|
||||
<radio id="PanelUI-panic-day" label="&panicButton.view.day;"
|
||||
value="6" class="subviewradio"/>
|
||||
</radiogroup>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<vbox id="PanelUI-panic-explanations">
|
||||
<label id="PanelUI-panic-actionlist-main-label" value="&panicButton.view.mainActionDesc;"/>
|
||||
|
||||
<label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist">&panicButton.view.deleteCookies;</label>
|
||||
<label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist">&panicButton.view.deleteHistory;</label>
|
||||
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
|
||||
<label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist">&panicButton.view.openNewWindow;</label>
|
||||
|
||||
<label id="PanelUI-panic-warning" value="&panicButton.view.undoWarning;"/>
|
||||
</vbox>
|
||||
<button id="PanelUI-panic-view-button"
|
||||
label="&panicButton.view.forgetButton;"/>
|
||||
</vbox>
|
||||
</panelview>
|
||||
|
||||
</panelmultiview>
|
||||
<!-- These menupopups are located here to prevent flickering,
|
||||
see bug 492960 comment 20. -->
|
||||
@ -247,3 +281,21 @@
|
||||
</vbox>
|
||||
</hbox>
|
||||
</panel>
|
||||
|
||||
<panel id="panic-button-success-notification"
|
||||
type="arrow"
|
||||
position="bottomcenter topright"
|
||||
hidden="true"
|
||||
role="alert"
|
||||
orient="vertical">
|
||||
<hbox id="panic-button-success-header">
|
||||
<image id="panic-button-success-icon" alt=""/>
|
||||
<vbox>
|
||||
<description>&panicButton.thankyou.msg1;</description>
|
||||
<description>&panicButton.thankyou.msg2;</description>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<button label="&panicButton.thankyou.buttonlabel;"
|
||||
id="panic-button-success-closebutton"
|
||||
oncommand="PanicButtonNotifier.close()"/>
|
||||
</panel>
|
||||
|
@ -23,7 +23,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "ContentClick",
|
||||
"resource:///modules/ContentClick.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
|
||||
"resource://gre/modules/DirectoryLinksProvider.jsm");
|
||||
"resource:///modules/DirectoryLinksProvider.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
66
browser/components/sessionstore/RunState.jsm
Normal file
@ -0,0 +1,66 @@
|
||||
/* 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 = ["RunState"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
|
||||
const STATE_STOPPED = 0;
|
||||
const STATE_RUNNING = 1;
|
||||
const STATE_QUITTING = 2;
|
||||
|
||||
// We're initially stopped.
|
||||
let state = STATE_STOPPED;
|
||||
|
||||
function observer(subj, topic) {
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
state = STATE_QUITTING;
|
||||
}
|
||||
|
||||
// Listen for when the application is quitting.
|
||||
Services.obs.addObserver(observer, "quit-application-granted", false);
|
||||
|
||||
/**
|
||||
* This module keeps track of SessionStore's current run state. We will
|
||||
* always start out at STATE_STOPPED. After the sessionw as read from disk and
|
||||
* the initial browser window has loaded we switch to STATE_RUNNING. On the
|
||||
* first notice that a browser shutdown was granted we switch to STATE_QUITTING.
|
||||
*/
|
||||
this.RunState = Object.freeze({
|
||||
// If we're stopped then SessionStore hasn't been initialized yet. As soon
|
||||
// as the session is read from disk and the initial browser window has loaded
|
||||
// the run state will change to STATE_RUNNING.
|
||||
get isStopped() {
|
||||
return state == STATE_STOPPED;
|
||||
},
|
||||
|
||||
// STATE_RUNNING is our default mode of operation that we'll spend most of
|
||||
// the time in. After the session was read from disk and the first browser
|
||||
// window has loaded we remain running until the browser quits.
|
||||
get isRunning() {
|
||||
return state == STATE_RUNNING;
|
||||
},
|
||||
|
||||
// We will enter STATE_QUITTING as soon as we receive notice that a browser
|
||||
// shutdown was granted. SessionStore will use this information to prevent
|
||||
// us from collecting partial information while the browser is shutting down
|
||||
// as well as to allow a last single write to disk and block all writes after
|
||||
// that.
|
||||
get isQuitting() {
|
||||
return state == STATE_QUITTING;
|
||||
},
|
||||
|
||||
// Switch the run state to STATE_RUNNING. This must be called after the
|
||||
// session was read from, the initial browser window has loaded and we're
|
||||
// now ready to restore session data.
|
||||
setRunning() {
|
||||
if (this.isStopped) {
|
||||
state = STATE_RUNNING;
|
||||
}
|
||||
}
|
||||
});
|
@ -38,6 +38,8 @@ Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RunState",
|
||||
"resource:///modules/sessionstore/RunState.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
|
||||
"resource://gre/modules/TelemetryStopwatch.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
@ -267,7 +269,7 @@ let SessionFileInternal = {
|
||||
}
|
||||
|
||||
let isFinalWrite = false;
|
||||
if (Services.startup.shuttingDown) {
|
||||
if (RunState.isQuitting) {
|
||||
// If shutdown has started, we will want to stop receiving
|
||||
// write instructions.
|
||||
isFinalWrite = this._isClosed = true;
|
||||
|
@ -11,10 +11,6 @@ const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
const STATE_STOPPED = 0;
|
||||
const STATE_RUNNING = 1;
|
||||
const STATE_QUITTING = -1;
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
@ -31,8 +27,7 @@ const MAX_CONCURRENT_TAB_RESTORES = 3;
|
||||
// global notifications observed
|
||||
const OBSERVING = [
|
||||
"browser-window-before-show", "domwindowclosed",
|
||||
"quit-application-requested", "quit-application-granted",
|
||||
"browser-lastwindow-close-granted",
|
||||
"quit-application-requested", "browser-lastwindow-close-granted",
|
||||
"quit-application", "browser:purge-session-history",
|
||||
"browser:purge-domain-data",
|
||||
"gather-telemetry",
|
||||
@ -112,6 +107,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
|
||||
"resource:///modules/sessionstore/GlobalState.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
|
||||
"resource:///modules/sessionstore/PrivacyFilter.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RunState",
|
||||
"resource:///modules/sessionstore/RunState.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
|
||||
"resource:///modules/devtools/scratchpad-manager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
|
||||
@ -288,9 +285,6 @@ let SessionStoreInternal = {
|
||||
Ci.nsISupportsWeakReference
|
||||
]),
|
||||
|
||||
// set default load state
|
||||
_loadState: STATE_STOPPED,
|
||||
|
||||
_globalState: new GlobalState(),
|
||||
|
||||
// During the initial restore and setBrowserState calls tracks the number of
|
||||
@ -472,7 +466,7 @@ let SessionStoreInternal = {
|
||||
|
||||
// at this point, we've as good as resumed the session, so we can
|
||||
// clear the resume_session_once flag, if it's set
|
||||
if (this._loadState != STATE_QUITTING &&
|
||||
if (!RunState.isQuitting &&
|
||||
this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
|
||||
this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
|
||||
|
||||
@ -531,9 +525,6 @@ let SessionStoreInternal = {
|
||||
case "quit-application-requested":
|
||||
this.onQuitApplicationRequested();
|
||||
break;
|
||||
case "quit-application-granted":
|
||||
this.onQuitApplicationGranted();
|
||||
break;
|
||||
case "browser-lastwindow-close-granted":
|
||||
this.onLastWindowCloseGranted();
|
||||
break;
|
||||
@ -739,7 +730,7 @@ let SessionStoreInternal = {
|
||||
return;
|
||||
|
||||
// ignore windows opened while shutting down
|
||||
if (this._loadState == STATE_QUITTING)
|
||||
if (RunState.isQuitting)
|
||||
return;
|
||||
|
||||
// Assign the window a unique identifier we can use to reference
|
||||
@ -764,8 +755,8 @@ let SessionStoreInternal = {
|
||||
this._windows[aWindow.__SSi].isPopup = true;
|
||||
|
||||
// perform additional initialization when the first window is loading
|
||||
if (this._loadState == STATE_STOPPED) {
|
||||
this._loadState = STATE_RUNNING;
|
||||
if (RunState.isStopped) {
|
||||
RunState.setRunning();
|
||||
SessionSaver.updateLastSaveTime();
|
||||
|
||||
// restore a crashed session resp. resume the last session if requested
|
||||
@ -1008,7 +999,7 @@ let SessionStoreInternal = {
|
||||
let winData = this._windows[aWindow.__SSi];
|
||||
|
||||
// Collect window data only when *not* closed during shutdown.
|
||||
if (this._loadState == STATE_RUNNING) {
|
||||
if (RunState.isRunning) {
|
||||
// Flush all data queued in the content script before the window is gone.
|
||||
TabState.flushWindow(aWindow);
|
||||
|
||||
@ -1100,14 +1091,6 @@ let SessionStoreInternal = {
|
||||
DirtyWindows.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* On quit application granted
|
||||
*/
|
||||
onQuitApplicationGranted: function ssi_onQuitApplicationGranted() {
|
||||
// freeze the data at what we've got (ignoring closing windows)
|
||||
this._loadState = STATE_QUITTING;
|
||||
},
|
||||
|
||||
/**
|
||||
* On last browser window close
|
||||
*/
|
||||
@ -1141,7 +1124,6 @@ let SessionStoreInternal = {
|
||||
LastSession.clear();
|
||||
}
|
||||
|
||||
this._loadState = STATE_QUITTING; // just to be sure
|
||||
this._uninit();
|
||||
},
|
||||
|
||||
@ -1153,7 +1135,7 @@ let SessionStoreInternal = {
|
||||
// If the browser is shutting down, simply return after clearing the
|
||||
// session data on disk as this notification fires after the
|
||||
// quit-application notification so the browser is about to exit.
|
||||
if (this._loadState == STATE_QUITTING)
|
||||
if (RunState.isQuitting)
|
||||
return;
|
||||
LastSession.clear();
|
||||
let openWindows = {};
|
||||
@ -1179,7 +1161,7 @@ let SessionStoreInternal = {
|
||||
var win = this._getMostRecentBrowserWindow();
|
||||
if (win) {
|
||||
win.setTimeout(() => SessionSaver.run(), 0);
|
||||
} else if (this._loadState == STATE_RUNNING) {
|
||||
} else if (RunState.isRunning) {
|
||||
SessionSaver.run();
|
||||
}
|
||||
|
||||
@ -1237,7 +1219,7 @@ let SessionStoreInternal = {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._loadState == STATE_RUNNING) {
|
||||
if (RunState.isRunning) {
|
||||
SessionSaver.run();
|
||||
}
|
||||
|
||||
@ -1368,7 +1350,7 @@ let SessionStoreInternal = {
|
||||
* Window reference
|
||||
*/
|
||||
onTabSelect: function ssi_onTabSelect(aWindow) {
|
||||
if (this._loadState == STATE_RUNNING) {
|
||||
if (RunState.isRunning) {
|
||||
this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
|
||||
|
||||
let tab = aWindow.gBrowser.selectedTab;
|
||||
@ -2033,7 +2015,7 @@ let SessionStoreInternal = {
|
||||
var activeWindow = this._getMostRecentBrowserWindow();
|
||||
|
||||
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
|
||||
if (this._loadState == STATE_RUNNING) {
|
||||
if (RunState.isRunning) {
|
||||
// update the data for all windows with activities since the last save operation
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
|
||||
@ -2090,7 +2072,7 @@ let SessionStoreInternal = {
|
||||
//XXXzpao We should do this for _restoreLastWindow == true, but that has
|
||||
// its own check for popups. c.f. bug 597619
|
||||
if (nonPopupCount == 0 && lastClosedWindowsCopy.length > 0 &&
|
||||
this._loadState == STATE_QUITTING) {
|
||||
RunState.isQuitting) {
|
||||
// prepend the last non-popup browser window, so that if the user loads more tabs
|
||||
// at startup we don't accidentally add them to a popup window
|
||||
do {
|
||||
@ -2155,7 +2137,7 @@ let SessionStoreInternal = {
|
||||
if (!this._isWindowLoaded(aWindow))
|
||||
return this._statesToRestore[aWindow.__SS_restoreID];
|
||||
|
||||
if (this._loadState == STATE_RUNNING) {
|
||||
if (RunState.isRunning) {
|
||||
this._collectWindowData(aWindow);
|
||||
}
|
||||
|
||||
@ -2637,7 +2619,7 @@ let SessionStoreInternal = {
|
||||
*/
|
||||
restoreNextTab: function ssi_restoreNextTab() {
|
||||
// If we call in here while quitting, we don't actually want to do anything
|
||||
if (this._loadState == STATE_QUITTING)
|
||||
if (RunState.isQuitting)
|
||||
return;
|
||||
|
||||
// Don't exceed the maximum number of concurrent tab restores.
|
||||
|
@ -31,6 +31,7 @@ EXTRA_JS_MODULES.sessionstore = [
|
||||
'PrivacyFilter.jsm',
|
||||
'PrivacyLevel.jsm',
|
||||
'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
|
||||
'RunState.jsm',
|
||||
'SessionCookies.jsm',
|
||||
'SessionFile.jsm',
|
||||
'SessionHistory.jsm',
|
||||
|
@ -2260,6 +2260,8 @@ this.Experiments.PreviousExperimentProvider = function (experiments) {
|
||||
}
|
||||
|
||||
this.Experiments.PreviousExperimentProvider.prototype = Object.freeze({
|
||||
get name() "PreviousExperimentProvider",
|
||||
|
||||
startup: function () {
|
||||
this._log.trace("startup()");
|
||||
Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC, false);
|
||||
|
@ -773,3 +773,28 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
||||
|
||||
<!ENTITY switchToMetroCmd2.label "Relaunch in &brandShortName; for Windows 8 Touch">
|
||||
|
||||
<!-- LOCALIZATION NOTE: (panicButton.view.mainTimeframeDesc, panicButton.view.5min, panicButton.view.2hr, panicButton.view.day):
|
||||
The .mainTimeframeDesc string combined with any of the 3 others is meant to form a complete sentence, e.g. "Forget the last: Five minutes".
|
||||
Please ensure that this remains the case in the translation. -->
|
||||
<!ENTITY panicButton.view.mainTimeframeDesc "Forget the last:">
|
||||
<!ENTITY panicButton.view.5min "Five minutes">
|
||||
<!ENTITY panicButton.view.2hr "Two hours">
|
||||
<!ENTITY panicButton.view.day "24 hours">
|
||||
|
||||
<!-- LOCALIZATION NOTE: (panicButton.view.mainLabel, panicButton.view.deleteCookies, panicButton.view.deleteHistory, panicButton.view.deleteTabsAndWindows, panicButton.view.openNewWindow):
|
||||
The .mainActionDesc string combined with any of the 4 others is meant to form a complete sentence, e.g. "Proceeding will: Delete Recent Cookies".
|
||||
Note also that the deleteCookies, deleteHistory and deleteTabsAndWindows strings include <html:strong> tags for emphasis on the words "Cookies", "History", "Tabs" and "Windows".
|
||||
The translation should do the same. -->
|
||||
<!ENTITY panicButton.view.mainActionDesc "Proceeding will:">
|
||||
<!ENTITY panicButton.view.deleteCookies "Delete Recent <html:strong>Cookies</html:strong>">
|
||||
<!ENTITY panicButton.view.deleteHistory "Delete Recent <html:strong>History</html:strong>">
|
||||
<!ENTITY panicButton.view.deleteTabsAndWindows "Close all <html:strong>Tabs</html:strong> and <html:strong>Windows</html:strong>">
|
||||
<!ENTITY panicButton.view.openNewWindow "Open a new clean Window">
|
||||
|
||||
<!ENTITY panicButton.view.undoWarning "This action cannot be undone.">
|
||||
<!ENTITY panicButton.view.forgetButton "Forget!">
|
||||
|
||||
<!ENTITY panicButton.thankyou.msg1 "Your recent history is cleared.">
|
||||
<!ENTITY panicButton.thankyou.msg2 "Safe browsing!">
|
||||
<!ENTITY panicButton.thankyou.buttonlabel "Thanks!">
|
||||
|
||||
|
@ -99,3 +99,6 @@ quit-button.tooltiptext.mac = Quit %1$S (%2$S)
|
||||
|
||||
loop-call-button.label = Invite someone to talk
|
||||
loop-call-button.tooltiptext = Invite someone to talk
|
||||
|
||||
panic-button.label = Forget
|
||||
panic-button.tooltiptext = Forget about some browsing history
|
||||
|
@ -103,6 +103,10 @@ XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() {
|
||||
result.push("characterencoding-button");
|
||||
}
|
||||
|
||||
if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
|
||||
result.push("panic-button");
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,10 @@
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/social/xpcshell.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'test/unit/social/xpcshell.ini',
|
||||
'test/xpcshell/xpcshell.ini',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'BrowserNewTabPreloader.jsm',
|
||||
@ -17,6 +20,7 @@ EXTRA_JS_MODULES += [
|
||||
'ContentSearch.jsm',
|
||||
'ContentWebRTC.jsm',
|
||||
'CustomizationTabPreloader.jsm',
|
||||
'DirectoryLinksProvider.jsm',
|
||||
'E10SUtils.jsm',
|
||||
'Feeds.jsm',
|
||||
'FormSubmitObserver.jsm',
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu, Constructor: CC } = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm");
|
||||
Cu.import("resource:///modules/DirectoryLinksProvider.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Http.jsm");
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/osfile.jsm")
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
6
browser/modules/test/xpcshell/xpcshell.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
|
||||
[test_DirectoryLinksProvider.js]
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
@ -121,6 +121,9 @@ browser.jar:
|
||||
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
|
||||
* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
|
||||
skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
|
||||
skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)
|
||||
skin/classic/browser/panic-panel/header-small.png (../shared/panic-panel/header-small.png)
|
||||
skin/classic/browser/panic-panel/icons.png (../shared/panic-panel/icons.png)
|
||||
skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
|
||||
skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
|
||||
skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 69 KiB |
@ -1115,6 +1115,23 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
||||
-moz-image-region: rect(36px, 1368px, 72px, 1332px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 1404px, 36px, 1368px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="toolbar"]:hover:active:not([disabled="true"]) {
|
||||
-moz-image-region: rect(36px, 1404px, 72px, 1368px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="toolbar"][open] {
|
||||
-moz-image-region: rect(72px, 1404px, 108px, 1368px);
|
||||
}
|
||||
|
||||
#panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
|
||||
:-moz-any(@primaryToolbarButtons@) > .toolbarbutton-icon,
|
||||
:-moz-any(@primaryToolbarButtons@) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
|
||||
width: 18px;
|
||||
@ -1264,6 +1281,15 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
||||
-moz-image-region: rect(0px, 1728px, 64px, 1664px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #panic-button {
|
||||
-moz-image-region: rect(0, 1792px, 64px, 1728px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
|
||||
-moz-image-region: rect(64px, 1792px, 128px, 1728px);
|
||||
}
|
||||
|
||||
toolbaritem[sdkstylewidget="true"] > toolbarbutton {
|
||||
-moz-image-region: rect(0, 1664px, 64px, 1600px);
|
||||
}
|
||||
|
@ -70,6 +70,30 @@
|
||||
background-image: url("chrome://global/skin/menu/shared-menu-check@2x.png");
|
||||
}
|
||||
|
||||
#panic-button-success-icon,
|
||||
#PanelUI-panic-timeframe-icon {
|
||||
list-style-image: url(chrome://browser/skin/panic-panel/header@2x.png);
|
||||
}
|
||||
|
||||
#PanelUI-panic-timeframe-icon-small {
|
||||
list-style-image: url(chrome://browser/skin/panic-panel/header-small@2x.png);
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-cookies {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 32, 32, 0);
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-history {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 64, 32, 32);
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-windows {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 96, 32, 64);
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-newwindow {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 128, 32, 96);
|
||||
}
|
||||
}
|
||||
|
||||
.panelUI-grid .toolbarbutton-1 {
|
||||
|
@ -202,6 +202,12 @@ browser.jar:
|
||||
skin/classic/browser/setDesktopBackground.css
|
||||
skin/classic/browser/monitor.png
|
||||
skin/classic/browser/monitor_16-10.png
|
||||
skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)
|
||||
skin/classic/browser/panic-panel/header@2x.png (../shared/panic-panel/header@2x.png)
|
||||
skin/classic/browser/panic-panel/header-small.png (../shared/panic-panel/header-small.png)
|
||||
skin/classic/browser/panic-panel/header-small@2x.png (../shared/panic-panel/header-small@2x.png)
|
||||
skin/classic/browser/panic-panel/icons.png (../shared/panic-panel/icons.png)
|
||||
skin/classic/browser/panic-panel/icons@2x.png (../shared/panic-panel/icons@2x.png)
|
||||
skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)
|
||||
* skin/classic/browser/places/places.css (places/places.css)
|
||||
* skin/classic/browser/places/organizer.css (places/organizer.css)
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 62 KiB |
@ -2,7 +2,7 @@
|
||||
|
||||
% Note that zoom-reset-button is a bit different since it doesn't use an image and thus has the image with display: none.
|
||||
%define nestedButtons #zoom-out-button, #zoom-reset-button, #zoom-in-button, #cut-button, #copy-button, #paste-button
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #tabview-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #switch-to-metro-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #tabview-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #switch-to-metro-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button, #panic-button
|
||||
|
||||
%ifdef XP_MACOSX
|
||||
% Prior to 10.7 there wasn't a native fullscreen button so we use #restore-button to exit fullscreen
|
||||
|
@ -1107,3 +1107,187 @@ toolbaritem[overflowedItem=true],
|
||||
menuitem[checked="true"].subviewbutton > .menu-iconic-left {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#PanelUI-panicView.cui-widget-panelview {
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
#PanelUI-panic-timeframe {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
#panic-button-success-icon,
|
||||
#PanelUI-panic-timeframe-icon,
|
||||
#PanelUI-panic-timeframe-icon-small {
|
||||
background-color: transparent;
|
||||
-moz-margin-end: 10px;
|
||||
}
|
||||
|
||||
#panic-button-success-icon,
|
||||
#PanelUI-panic-timeframe-icon {
|
||||
list-style-image: url(chrome://browser/skin/panic-panel/header.png);
|
||||
max-height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
#PanelUI-panic-timeframe-icon-small {
|
||||
list-style-image: url(chrome://browser/skin/panic-panel/header-small.png);
|
||||
max-height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
/* current attribute is only set when in use as a subview instead of a main view */
|
||||
#PanelUI-panicView[current] #PanelUI-panic-timeframe-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#PanelUI-panicView.cui-widget-panelview #PanelUI-panic-timeframe-icon-small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#panic-button-success-header,
|
||||
#PanelUI-panic-header {
|
||||
-moz-box-align: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#PanelUI-panicView.cui-widget-panelview #PanelUI-panic-header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#PanelUI-panic-timeframe-icon-small:-moz-locale-dir(rtl),
|
||||
#PanelUI-panic-timeframe-icon:-moz-locale-dir(rtl) {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.subviewradio {
|
||||
-moz-binding: url(chrome://global/content/bindings/radio.xml#radio);
|
||||
-moz-appearance: none;
|
||||
padding: 1px;
|
||||
margin: 0 0 2px;
|
||||
background-color: hsla(210,4%,10%,0);
|
||||
border-radius: 2px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: hsla(210,4%,10%,0);
|
||||
}
|
||||
|
||||
.subviewradio@buttonStateHover@ {
|
||||
background-color: hsla(210,4%,10%,.08);
|
||||
border-color: hsla(210,4%,10%,.11);
|
||||
}
|
||||
|
||||
.subviewradio[selected],
|
||||
.subviewradio[selected]:hover,
|
||||
.subviewradio@buttonStateActive@ {
|
||||
background-color: hsla(210,4%,10%,.12);
|
||||
border-color: hsla(210,4%,10%,.14);
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
|
||||
}
|
||||
|
||||
.subviewradio > .radio-check {
|
||||
-moz-appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #e7e7e7;
|
||||
border-radius: 50%;
|
||||
margin: 1px 5px;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.subviewradio > .radio-check[selected] {
|
||||
background-color: #fff;
|
||||
border: 4px solid #177ee6;
|
||||
}
|
||||
|
||||
#PanelUI-panic-explanations {
|
||||
padding: 10px 10px 0;
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-main-label {
|
||||
color: GrayText;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.PanelUI-panic-actionlist {
|
||||
-moz-padding-start: 20px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
background-size: 16px 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: transparent;
|
||||
background-position: center left;
|
||||
}
|
||||
|
||||
.PanelUI-panic-actionlist:-moz-locale-dir(rtl) {
|
||||
background-position: center right;
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-cookies {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 16, 16, 0);
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-history {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 32, 16, 16);
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-windows {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 48, 16, 32);
|
||||
}
|
||||
|
||||
#PanelUI-panic-actionlist-newwindow {
|
||||
background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 64, 16, 48);
|
||||
}
|
||||
|
||||
#PanelUI-panic-warning {
|
||||
color: #C11F14;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#PanelUI-panic-view-button {
|
||||
-moz-appearance: none;
|
||||
background-color: #d92316;
|
||||
color: white;
|
||||
margin: 5px 15px 11px;
|
||||
border: 1px solid #c92014;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#PanelUI-panic-view-button:hover {
|
||||
background-color: #bf1f13;
|
||||
border-color: #b81d12;
|
||||
}
|
||||
|
||||
#PanelUI-panic-view-button:hover:active {
|
||||
background-color: #99180f;
|
||||
border-color: #91170f;
|
||||
}
|
||||
|
||||
#PanelUI-panic-view-button > .toolbarbutton-text {
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#panic-button-success-closebutton {
|
||||
background-color: #e5e5e5;
|
||||
color: black;
|
||||
margin: 5px 0 0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#panic-button-success-closebutton:hover {
|
||||
background-color: #dedede;
|
||||
border-color: #bbb;
|
||||
}
|
||||
|
||||
#panic-button-success-closebutton:hover:active {
|
||||
background-color: #d0d0d0;
|
||||
border-color: #aaa;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Menu panel and palette styles */
|
||||
/* Note that this file isn't used for HiDPI on OS X. */
|
||||
|
||||
toolbaritem[sdkstylewidget="true"] > toolbarbutton,
|
||||
:-moz-any(@primaryToolbarButtons@)[cui-areatype="menu-panel"],
|
||||
@ -146,6 +147,15 @@ toolbarpaletteitem[place="palette"] > #sidebar-button {
|
||||
-moz-image-region: rect(32px, 864px, 64px, 832px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #panic-button {
|
||||
-moz-image-region: rect(0, 896px, 32px, 864px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
|
||||
-moz-image-region: rect(32px, 896px, 64px, 864px);
|
||||
}
|
||||
|
||||
toolbaritem[sdkstylewidget="true"] > toolbarbutton {
|
||||
-moz-image-region: rect(0, 832px, 32px, 800px);
|
||||
}
|
||||
|
BIN
browser/themes/shared/panic-panel/header-small.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
browser/themes/shared/panic-panel/header-small@2x.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
browser/themes/shared/panic-panel/header.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
browser/themes/shared/panic-panel/header@2x.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
browser/themes/shared/panic-panel/icons.png
Normal file
After Width: | Height: | Size: 679 B |
BIN
browser/themes/shared/panic-panel/icons@2x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
@ -168,6 +168,29 @@ toolbar[brighttext] #sync-button[status="active"]:hover:active:not([disabled="tr
|
||||
-moz-image-region: rect(0, 684px, 18px, 666px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 702px, 18px, 684px);
|
||||
}
|
||||
|
||||
%ifdef XP_MACOSX
|
||||
#panic-button[cui-areatype="toolbar"]:hover:active:not([disabled="true"]) {
|
||||
-moz-image-region: rect(18px, 702px, 36px, 684px);
|
||||
}
|
||||
|
||||
#panic-button[cui-areatype="toolbar"][open] {
|
||||
-moz-image-region: rect(36px, 702px, 54px, 684px);
|
||||
}
|
||||
%else
|
||||
#panic-button[cui-areatype="toolbar"][open] {
|
||||
-moz-image-region: rect(18px, 702px, 36px, 684px);
|
||||
}
|
||||
%endif
|
||||
|
||||
#panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
|
||||
#loop-call-button > .toolbarbutton-badge-container {
|
||||
list-style-image: url(chrome://browser/skin/loop/toolbar.png);
|
||||
-moz-image-region: rect(0, 18px, 18px, 0);
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.9 KiB |
@ -145,6 +145,9 @@ browser.jar:
|
||||
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
|
||||
* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
|
||||
skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
|
||||
skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)
|
||||
skin/classic/browser/panic-panel/header-small.png (../shared/panic-panel/header-small.png)
|
||||
skin/classic/browser/panic-panel/icons.png (../shared/panic-panel/icons.png)
|
||||
skin/classic/browser/places/places.css (places/places.css)
|
||||
* skin/classic/browser/places/organizer.css (places/organizer.css)
|
||||
skin/classic/browser/places/bookmark.png (places/bookmark.png)
|
||||
@ -569,6 +572,9 @@ browser.jar:
|
||||
skin/classic/aero/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
|
||||
* skin/classic/aero/browser/newtab/newTab.css (newtab/newTab.css)
|
||||
skin/classic/aero/browser/newtab/controls.svg (../shared/newtab/controls.svg)
|
||||
skin/classic/aero/browser/panic-panel/header.png (../shared/panic-panel/header.png)
|
||||
skin/classic/aero/browser/panic-panel/header-small.png (../shared/panic-panel/header-small.png)
|
||||
skin/classic/aero/browser/panic-panel/icons.png (../shared/panic-panel/icons.png)
|
||||
* skin/classic/aero/browser/places/places.css (places/places-aero.css)
|
||||
* skin/classic/aero/browser/places/organizer.css (places/organizer-aero.css)
|
||||
skin/classic/aero/browser/places/bookmark.png (places/bookmark-aero.png)
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@ -195,6 +195,7 @@ public final class NotificationHelper implements GeckoEventListener {
|
||||
notificationIntent.setData(dataUri);
|
||||
notificationIntent.putExtra(HELPER_NOTIFICATION, true);
|
||||
notificationIntent.putExtra(COOKIE_ATTR, message.optString(COOKIE_ATTR));
|
||||
notificationIntent.setClass(mContext, GeckoAppShell.getGeckoInterface().getActivity().getClass());
|
||||
return notificationIntent;
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,18 @@ let WebappRT = {
|
||||
|
||||
// If the app is in debug mode, configure and enable the remote debugger.
|
||||
Messaging.sendRequestForResult({ type: "NativeApp:IsDebuggable" }).then((response) => {
|
||||
if (response.isDebuggable) {
|
||||
this._enableRemoteDebugger(aUrl);
|
||||
let that = this;
|
||||
let name = this._getAppName(aUrl);
|
||||
|
||||
if (response.isDebuggable) {
|
||||
Notifications.create({
|
||||
title: Strings.browser.formatStringFromName("remoteStartNotificationTitle", [name], 1),
|
||||
message: Strings.browser.GetStringFromName("remoteStartNotificationMessage"),
|
||||
icon: "drawable://warning_doorhanger",
|
||||
onClick: function(aId, aCookie) {
|
||||
that._enableRemoteDebugger(aUrl);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -139,6 +149,18 @@ let WebappRT = {
|
||||
}
|
||||
},
|
||||
|
||||
_getAppName: function(aUrl) {
|
||||
let name = Strings.browser.GetStringFromName("remoteNotificationGenericName");
|
||||
let app = DOMApplicationRegistry.getAppByManifestURL(aUrl);
|
||||
|
||||
if (app) {
|
||||
name = app.name;
|
||||
}
|
||||
|
||||
return name;
|
||||
},
|
||||
|
||||
|
||||
_enableRemoteDebugger: function(aUrl) {
|
||||
// Skip the connection prompt in favor of notifying the user below.
|
||||
Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false);
|
||||
@ -158,13 +180,7 @@ let WebappRT = {
|
||||
// Notify the user that we enabled the debugger and which port it's using
|
||||
// so they can use the DevTools Connect… dialog to connect the client to it.
|
||||
DOMApplicationRegistry.registryReady.then(() => {
|
||||
let name;
|
||||
let app = DOMApplicationRegistry.getAppByManifestURL(aUrl);
|
||||
if (app) {
|
||||
name = app.name;
|
||||
} else {
|
||||
name = Strings.browser.GetStringFromName("remoteNotificationGenericName");
|
||||
}
|
||||
let name = this._getAppName(aUrl);
|
||||
|
||||
Notifications.create({
|
||||
title: Strings.browser.formatStringFromName("remoteNotificationTitle", [name], 1),
|
||||
|
@ -322,6 +322,10 @@ remoteNotificationGenericName=App
|
||||
# LOCALIZATION NOTE (remoteNotificationMessage): %S is the port on which
|
||||
# the remote debugger server is listening.
|
||||
remoteNotificationMessage=Listening on port %S
|
||||
# LOCALIZATION NOTE (remoteStartNotificationTitle): %S is the name of the app.
|
||||
remoteStartNotificationTitle=Activate debugging for %S
|
||||
# LOCALIZATION NOTE (remoteStartNotificationMessage):
|
||||
remoteStartNotificationMessage=Touch to activate remote debugger
|
||||
|
||||
# Helper apps
|
||||
helperapps.open=Open
|
||||
|
@ -20,7 +20,6 @@ EXTRA_JS_MODULES += [
|
||||
'DeferredTask.jsm',
|
||||
'Deprecated.jsm',
|
||||
'Dict.jsm',
|
||||
'DirectoryLinksProvider.jsm',
|
||||
'FileUtils.jsm',
|
||||
'Finder.jsm',
|
||||
'Geometry.jsm',
|
||||
|
@ -10,7 +10,6 @@ support-files =
|
||||
[test_BinarySearch.js]
|
||||
[test_DeferredTask.js]
|
||||
[test_dict.js]
|
||||
[test_DirectoryLinksProvider.js]
|
||||
[test_FileUtils.js]
|
||||
[test_GMPInstallManager.js]
|
||||
# GMPInstallManager is not shipped on Android
|
||||
|
@ -122,6 +122,8 @@ let logger = Log.repository.getLogger(LOGGER_ID);
|
||||
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
|
||||
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
|
||||
|
||||
const UNNAMED_PROVIDER = "<unnamed-provider>";
|
||||
|
||||
/**
|
||||
* Preference listener which listens for a change in the
|
||||
* "extensions.logging.enabled" preference and changes the logging level of the
|
||||
@ -464,6 +466,8 @@ var gUpdateEnabled = true;
|
||||
var gAutoUpdateDefault = true;
|
||||
var gHotfixID = null;
|
||||
var gShutdownBarrier = null;
|
||||
var gRepoShutdownState = "";
|
||||
var gShutdownInProgress = false;
|
||||
|
||||
/**
|
||||
* This is the real manager, kept here rather than in AddonManager to keep its
|
||||
@ -475,6 +479,7 @@ var AddonManagerInternal = {
|
||||
addonListeners: [],
|
||||
typeListeners: [],
|
||||
providers: [],
|
||||
providerShutdowns: new Map(),
|
||||
types: {},
|
||||
startupChanges: {},
|
||||
// Store telemetry details per addon provider
|
||||
@ -613,6 +618,33 @@ var AddonManagerInternal = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Start up a provider, and register its shutdown hook if it has one
|
||||
*/
|
||||
_startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) {
|
||||
if (!gStarted)
|
||||
throw Components.Exception("AddonManager is not initialized",
|
||||
Cr.NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion);
|
||||
if ('shutdown' in aProvider) {
|
||||
let name = aProvider.name || "Provider";
|
||||
let AMProviderShutdown = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.debug("Calling shutdown blocker for " + name);
|
||||
resolve(aProvider.shutdown());
|
||||
})
|
||||
.catch(err => {
|
||||
logger.warn("Failure during shutdown of " + name, err);
|
||||
AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err);
|
||||
});
|
||||
};
|
||||
logger.debug("Registering shutdown blocker for " + name);
|
||||
this.providerShutdowns.set(aProvider, AMProviderShutdown);
|
||||
AddonManager.shutdown.addBlocker(name, AMProviderShutdown);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the AddonManager, loading any known providers and initializing
|
||||
* them.
|
||||
@ -742,15 +774,17 @@ var AddonManagerInternal = {
|
||||
}
|
||||
|
||||
// Register our shutdown handler with the AsyncShutdown manager
|
||||
gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for clients to shut down.");
|
||||
AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers",
|
||||
this.shutdownManager.bind(this));
|
||||
gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down.");
|
||||
AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.",
|
||||
this.shutdownManager.bind(this),
|
||||
{fetchState: this.shutdownState.bind(this)});
|
||||
|
||||
// Once we start calling providers we must allow all normal methods to work.
|
||||
gStarted = true;
|
||||
|
||||
this.callProviders("startup", appChanged, oldAppVersion,
|
||||
oldPlatformVersion);
|
||||
for (let provider of this.providers) {
|
||||
this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion);
|
||||
}
|
||||
|
||||
// If this is a new profile just pretend that there were no changes
|
||||
if (appChanged === undefined) {
|
||||
@ -813,8 +847,9 @@ var AddonManagerInternal = {
|
||||
}
|
||||
|
||||
// If we're registering after startup call this provider's startup.
|
||||
if (gStarted)
|
||||
callProvider(aProvider, "startup");
|
||||
if (gStarted) {
|
||||
this._startProvider(aProvider);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -822,6 +857,9 @@ var AddonManagerInternal = {
|
||||
*
|
||||
* @param aProvider
|
||||
* The provider to unregister
|
||||
* @return Whatever the provider's 'shutdown' method returns (if anything).
|
||||
* For providers that have async shutdown methods returning Promises,
|
||||
* the caller should wait for that Promise to resolve.
|
||||
*/
|
||||
unregisterProvider: function AMI_unregisterProvider(aProvider) {
|
||||
if (!aProvider || typeof aProvider != "object")
|
||||
@ -851,9 +889,19 @@ var AddonManagerInternal = {
|
||||
}
|
||||
}
|
||||
|
||||
// If we're unregistering after startup call this provider's shutdown.
|
||||
if (gStarted)
|
||||
callProvider(aProvider, "shutdown");
|
||||
// If we're unregistering after startup but before shutting down,
|
||||
// remove the blocker for this provider's shutdown and call it.
|
||||
// If we're already shutting down, just let gShutdownBarrier call it to avoid races.
|
||||
if (gStarted && !gShutdownInProgress) {
|
||||
logger.debug("Unregistering shutdown blocker for " + (aProvider.name || "Provider"));
|
||||
let shutter = this.providerShutdowns.get(aProvider);
|
||||
if (shutter) {
|
||||
this.providerShutdowns.delete(aProvider);
|
||||
gShutdownBarrier.client.removeBlocker(shutter);
|
||||
return shutter();
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -884,46 +932,21 @@ var AddonManagerInternal = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls a method on all registered providers, if the provider implements
|
||||
* the method. The called method is expected to return a promise, and
|
||||
* callProvidersAsync returns a promise that resolves when every provider
|
||||
* method has either resolved or rejected. Rejection reasons are logged
|
||||
* but otherwise ignored. Return values are ignored. Any parameters after the
|
||||
* method parameter are passed to the provider's method.
|
||||
*
|
||||
* @param aMethod
|
||||
* The method name to call
|
||||
* @see callProvider
|
||||
* Report the current state of asynchronous shutdown
|
||||
*/
|
||||
callProvidersAsync: function AMI_callProviders(aMethod, ...aArgs) {
|
||||
if (!aMethod || typeof aMethod != "string")
|
||||
throw Components.Exception("aMethod must be a non-empty string",
|
||||
Cr.NS_ERROR_INVALID_ARG);
|
||||
|
||||
let allProviders = [];
|
||||
|
||||
let providers = this.providers.slice(0);
|
||||
for (let provider of providers) {
|
||||
try {
|
||||
if (aMethod in provider) {
|
||||
// Resolve a new promise with the result of the method, to handle both
|
||||
// methods that return values (or nothing) and methods that return promises.
|
||||
let providerResult = provider[aMethod].apply(provider, aArgs);
|
||||
let nextPromise = Promise.resolve(providerResult);
|
||||
// Log and swallow the errors from methods that do return promises.
|
||||
nextPromise = nextPromise.then(
|
||||
null,
|
||||
e => logger.error("Exception calling provider " + aMethod, e));
|
||||
allProviders.push(nextPromise);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Exception calling provider " + aMethod, e);
|
||||
}
|
||||
shutdownState() {
|
||||
let state = [];
|
||||
if (gShutdownBarrier) {
|
||||
state.push({
|
||||
name: gShutdownBarrier.client.name,
|
||||
state: gShutdownBarrier.state
|
||||
});
|
||||
}
|
||||
// Because we use promise.then to catch and log all errors above, Promise.all()
|
||||
// will never exit early because of a rejection.
|
||||
return Promise.all(allProviders);
|
||||
state.push({
|
||||
name: "AddonRepository: async shutdown",
|
||||
state: gRepoShutdownState
|
||||
});
|
||||
return state;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -932,8 +955,10 @@ var AddonManagerInternal = {
|
||||
* @return Promise{null} that resolves when all providers and dependent modules
|
||||
* have finished shutting down
|
||||
*/
|
||||
shutdownManager: function() {
|
||||
shutdownManager: Task.async(function* () {
|
||||
logger.debug("shutdown");
|
||||
gRepoShutdownState = "pending";
|
||||
gShutdownInProgress = true;
|
||||
// Clean up listeners
|
||||
Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
|
||||
Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
|
||||
@ -942,37 +967,47 @@ var AddonManagerInternal = {
|
||||
Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
|
||||
Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this);
|
||||
|
||||
// Only shut down providers if they've been started. Shut down
|
||||
// AddonRepository after providers (if any).
|
||||
let shuttingDown = null;
|
||||
let savedError = null;
|
||||
// Only shut down providers if they've been started.
|
||||
if (gStarted) {
|
||||
shuttingDown = gShutdownBarrier.wait()
|
||||
.then(null, err => logger.error("Failure during wait for shutdown barrier", err))
|
||||
.then(() => this.callProvidersAsync("shutdown"))
|
||||
.then(null,
|
||||
err => logger.error("Failure during async provider shutdown", err))
|
||||
.then(() => AddonRepository.shutdown());
|
||||
}
|
||||
else {
|
||||
shuttingDown = AddonRepository.shutdown();
|
||||
try {
|
||||
yield gShutdownBarrier.wait();
|
||||
}
|
||||
catch(err) {
|
||||
savedError = err;
|
||||
logger.error("Failure during wait for shutdown barrier", err);
|
||||
AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
|
||||
}
|
||||
}
|
||||
|
||||
shuttingDown.then(val => logger.debug("Async provider shutdown done"),
|
||||
err => logger.error("Failure during AddonRepository shutdown", err))
|
||||
.then(() => {
|
||||
this.managerListeners.splice(0, this.managerListeners.length);
|
||||
this.installListeners.splice(0, this.installListeners.length);
|
||||
this.addonListeners.splice(0, this.addonListeners.length);
|
||||
this.typeListeners.splice(0, this.typeListeners.length);
|
||||
for (let type in this.startupChanges)
|
||||
delete this.startupChanges[type];
|
||||
gStarted = false;
|
||||
gStartupComplete = false;
|
||||
gShutdownBarrier = null;
|
||||
});
|
||||
// Shut down AddonRepository after providers (if any).
|
||||
try {
|
||||
gRepoShutdownState = "in progress";
|
||||
yield AddonRepository.shutdown();
|
||||
gRepoShutdownState = "done";
|
||||
}
|
||||
catch(err) {
|
||||
savedError = err;
|
||||
logger.error("Failure during AddonRepository shutdown", err);
|
||||
AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
|
||||
}
|
||||
|
||||
return shuttingDown;
|
||||
},
|
||||
logger.debug("Async provider shutdown done");
|
||||
this.managerListeners.splice(0, this.managerListeners.length);
|
||||
this.installListeners.splice(0, this.installListeners.length);
|
||||
this.addonListeners.splice(0, this.addonListeners.length);
|
||||
this.typeListeners.splice(0, this.typeListeners.length);
|
||||
this.providerShutdowns.clear();
|
||||
for (let type in this.startupChanges)
|
||||
delete this.startupChanges[type];
|
||||
gStarted = false;
|
||||
gStartupComplete = false;
|
||||
gShutdownBarrier = null;
|
||||
gShutdownInProgress = false;
|
||||
if (savedError) {
|
||||
throw savedError;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Notified when a preference we're interested in has changed.
|
||||
|
@ -70,6 +70,8 @@ var _themeIDBeingEnabled = null;
|
||||
var _themeIDBeingDisabled = null;
|
||||
|
||||
this.LightweightThemeManager = {
|
||||
get name() "LightweightThemeManager",
|
||||
|
||||
get usedThemes () {
|
||||
try {
|
||||
return JSON.parse(_prefs.getComplexValue("usedThemes",
|
||||
|
@ -245,6 +245,8 @@ let OpenH264Wrapper = {
|
||||
};
|
||||
|
||||
let OpenH264Provider = {
|
||||
get name() "OpenH264Provider",
|
||||
|
||||
startup: function() {
|
||||
configureLogging();
|
||||
this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.OpenH264Provider",
|
||||
|
@ -48,6 +48,8 @@ function getIDHashForString(aStr) {
|
||||
}
|
||||
|
||||
var PluginProvider = {
|
||||
get name() "PluginProvider",
|
||||
|
||||
// A dictionary mapping IDs to names and descriptions
|
||||
plugins: null,
|
||||
|
||||
|
@ -1787,6 +1787,8 @@ this.XPIStates = {
|
||||
};
|
||||
|
||||
this.XPIProvider = {
|
||||
get name() "XPIProvider",
|
||||
|
||||
// An array of known install locations
|
||||
installLocations: null,
|
||||
// A dictionary of known install locations by name
|
||||
|
@ -26,6 +26,7 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm");
|
||||
Components.utils.import("resource://gre/modules/Task.jsm");
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
Components.utils.import("resource://gre/modules/AsyncShutdown.jsm");
|
||||
|
||||
Services.prefs.setBoolPref("toolkit.osfile.log", true);
|
||||
|
||||
@ -37,31 +38,18 @@ let AddonManagerInternal = AMscope.AddonManagerInternal;
|
||||
// down AddonManager from the test
|
||||
let MockAsyncShutdown = {
|
||||
hook: null,
|
||||
status: null,
|
||||
profileBeforeChange: {
|
||||
addBlocker: function(aName, aBlocker) {
|
||||
addBlocker: function(aName, aBlocker, aOptions) {
|
||||
do_print("Mock profileBeforeChange blocker for '" + aName + "'");
|
||||
MockAsyncShutdown.hook = aBlocker;
|
||||
MockAsyncShutdown.status = aOptions.fetchState;
|
||||
}
|
||||
},
|
||||
Barrier: function (name) {
|
||||
this.name = name;
|
||||
this.client.addBlocker = (name, blocker) => {
|
||||
do_print("Mock Barrier blocker for '" + name + "' for barrier '" + this.name + "'");
|
||||
this.blockers.push({name: name, blocker: blocker});
|
||||
};
|
||||
},
|
||||
// We can use the real Barrier
|
||||
Barrier: AsyncShutdown.Barrier
|
||||
};
|
||||
|
||||
MockAsyncShutdown.Barrier.prototype = Object.freeze({
|
||||
blockers: [],
|
||||
client: {},
|
||||
wait: Task.async(function* () {
|
||||
for (let b of this.blockers) {
|
||||
yield b.blocker();
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
AMscope.AsyncShutdown = MockAsyncShutdown;
|
||||
|
||||
var gInternalManager = null;
|
||||
|
@ -0,0 +1,98 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Verify that we report shutdown status for Addon Manager providers
|
||||
// and AddonRepository correctly.
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||
|
||||
// Make a mock AddonRepository that just lets us hang shutdown.
|
||||
// Needs two promises - one to let us know that AM has called shutdown,
|
||||
// and one for us to let AM know that shutdown is done.
|
||||
function mockAddonProvider(aName) {
|
||||
let mockProvider = {
|
||||
donePromise: null,
|
||||
doneResolve: null,
|
||||
doneReject: null,
|
||||
shutdownPromise: null,
|
||||
shutdownResolve: null,
|
||||
|
||||
get name() aName,
|
||||
|
||||
shutdown() {
|
||||
this.shutdownResolve();
|
||||
return this.donePromise;
|
||||
},
|
||||
};
|
||||
mockProvider.donePromise = new Promise((resolve, reject) => {
|
||||
mockProvider.doneResolve = resolve;
|
||||
mockProvider.doneResject = reject;
|
||||
});
|
||||
mockProvider.shutdownPromise = new Promise((resolve, reject) => {
|
||||
mockProvider.shutdownResolve = resolve;
|
||||
});
|
||||
return mockProvider;
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Helper to find a particular shutdown blocker's status in the JSON blob
|
||||
function findInStatus(aStatus, aName) {
|
||||
for (let {name, state} of aStatus.state) {
|
||||
if (name == aName) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure we report correctly when an add-on provider or AddonRepository block shutdown
|
||||
*/
|
||||
add_task(function* blockRepoShutdown() {
|
||||
// Reach into the AddonManager scope and inject our mock AddonRepository
|
||||
let realAddonRepo = AMscope.AddonRepository;
|
||||
// the mock provider behaves enough like AddonRepository for the purpose of this test
|
||||
let mockRepo = mockAddonProvider("Mock repo");
|
||||
AMscope.AddonRepository = mockRepo;
|
||||
|
||||
let mockProvider = mockAddonProvider("Mock provider");
|
||||
|
||||
startupManager();
|
||||
AddonManagerPrivate.registerProvider(mockProvider);
|
||||
|
||||
// Start shutting the manager down
|
||||
let managerDown = promiseShutdownManager();
|
||||
|
||||
// Wait for manager to call provider shutdown.
|
||||
yield mockProvider.shutdownPromise;
|
||||
// check AsyncShutdown state
|
||||
let status = MockAsyncShutdown.status();
|
||||
equal(findInStatus(status[0], "Mock provider"), "(none)");
|
||||
equal(status[1].name, "AddonRepository: async shutdown");
|
||||
equal(status[1].state, "pending");
|
||||
// let the provider finish
|
||||
mockProvider.doneResolve();
|
||||
|
||||
// Wait for manager to call repo shutdown and start waiting for it
|
||||
yield mockRepo.shutdownPromise;
|
||||
// Check the shutdown state
|
||||
status = MockAsyncShutdown.status();
|
||||
do_print(JSON.stringify(status));
|
||||
equal(status[0].name, "AddonManager: Waiting for providers to shut down.");
|
||||
equal(status[0].state, "Complete");
|
||||
equal(status[1].name, "AddonRepository: async shutdown");
|
||||
equal(status[1].state, "in progress");
|
||||
|
||||
// Now finish our shutdown, and wait for the manager to wrap up
|
||||
mockRepo.doneResolve();
|
||||
yield managerDown;
|
||||
|
||||
// Check the shutdown state again
|
||||
status = MockAsyncShutdown.status();
|
||||
equal(status[0].name, "AddonRepository: async shutdown");
|
||||
equal(status[0].state, "done");
|
||||
});
|
@ -14,6 +14,7 @@ support-files =
|
||||
[test_metadata_update.js]
|
||||
[test_openh264.js]
|
||||
run-if = appname == "firefox"
|
||||
[test_provider_shutdown.js]
|
||||
[test_shutdown.js]
|
||||
[test_XPIcancel.js]
|
||||
[test_XPIStates.js]
|
||||
|