зеркало из https://github.com/mozilla/gecko-dev.git
418 строки
14 KiB
JavaScript
418 строки
14 KiB
JavaScript
# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
const gXPInstallObserver = {
|
|
_findChildShell: function (aDocShell, aSoughtShell)
|
|
{
|
|
if (aDocShell == aSoughtShell)
|
|
return aDocShell;
|
|
|
|
var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeNode);
|
|
for (var i = 0; i < node.childCount; ++i) {
|
|
var docShell = node.getChildAt(i);
|
|
docShell = this._findChildShell(docShell, aSoughtShell);
|
|
if (docShell == aSoughtShell)
|
|
return docShell;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
_getBrowser: function (aDocShell)
|
|
{
|
|
for (let browser of gBrowser.browsers) {
|
|
if (this._findChildShell(browser.docShell, aDocShell))
|
|
return browser;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
observe: function (aSubject, aTopic, aData)
|
|
{
|
|
var brandBundle = document.getElementById("bundle_brand");
|
|
var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
|
|
var win = installInfo.originatingWindow;
|
|
var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIWebNavigation)
|
|
.QueryInterface(Components.interfaces.nsIDocShell);
|
|
var browser = this._getBrowser(shell);
|
|
if (!browser)
|
|
return;
|
|
const anchorID = "addons-notification-icon";
|
|
var messageString, action;
|
|
var brandShortName = brandBundle.getString("brandShortName");
|
|
|
|
var notificationID = aTopic;
|
|
// Make notifications persist a minimum of 30 seconds
|
|
var options = {
|
|
timeout: Date.now() + 30000
|
|
};
|
|
|
|
switch (aTopic) {
|
|
case "addon-install-disabled":
|
|
notificationID = "xpinstall-disabled"
|
|
|
|
if (gPrefService.prefIsLocked("xpinstall.enabled")) {
|
|
messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
|
|
buttons = [];
|
|
}
|
|
else {
|
|
messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
|
|
|
|
action = {
|
|
label: gNavigatorBundle.getString("xpinstallDisabledButton"),
|
|
accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
|
|
callback: function editPrefs() {
|
|
gPrefService.setBoolPref("xpinstall.enabled", true);
|
|
}
|
|
};
|
|
}
|
|
|
|
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
|
action, null, options);
|
|
break;
|
|
case "addon-install-blocked":
|
|
messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
|
|
[brandShortName, installInfo.originatingURI.host]);
|
|
|
|
let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
|
|
action = {
|
|
label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
|
|
accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
|
|
callback: function() {
|
|
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
|
|
installInfo.install();
|
|
}
|
|
};
|
|
|
|
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
|
|
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
|
action, null, options);
|
|
break;
|
|
case "addon-install-started":
|
|
var needsDownload = function needsDownload(aInstall) {
|
|
return aInstall.state != AddonManager.STATE_DOWNLOADED;
|
|
}
|
|
// If all installs have already been downloaded then there is no need to
|
|
// show the download progress
|
|
if (!installInfo.installs.some(needsDownload))
|
|
return;
|
|
notificationID = "addon-progress";
|
|
messageString = gNavigatorBundle.getString("addonDownloading");
|
|
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
|
options.installs = installInfo.installs;
|
|
options.contentWindow = browser.contentWindow;
|
|
options.sourceURI = browser.currentURI;
|
|
options.eventCallback = function(aEvent) {
|
|
if (aEvent != "removed")
|
|
return;
|
|
options.contentWindow = null;
|
|
options.sourceURI = null;
|
|
};
|
|
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
|
null, null, options);
|
|
break;
|
|
case "addon-install-failed":
|
|
// TODO This isn't terribly ideal for the multiple failure case
|
|
for (let install of installInfo.installs) {
|
|
let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
|
|
installInfo.originatingURI.host;
|
|
if (!host)
|
|
host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
|
|
install.sourceURI.host;
|
|
|
|
let error = (host || install.error == 0) ? "addonError" : "addonLocalError";
|
|
if (install.error != 0)
|
|
error += install.error;
|
|
else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
|
|
error += "Blocklisted";
|
|
else
|
|
error += "Incompatible";
|
|
|
|
messageString = gNavigatorBundle.getString(error);
|
|
messageString = messageString.replace("#1", install.name);
|
|
if (host)
|
|
messageString = messageString.replace("#2", host);
|
|
messageString = messageString.replace("#3", brandShortName);
|
|
messageString = messageString.replace("#4", Services.appinfo.version);
|
|
|
|
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
|
action, null, options);
|
|
}
|
|
break;
|
|
case "addon-install-complete":
|
|
var needsRestart = installInfo.installs.some(function(i) {
|
|
return i.addon.pendingOperations != AddonManager.PENDING_NONE;
|
|
});
|
|
|
|
if (needsRestart) {
|
|
messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
|
|
action = {
|
|
label: gNavigatorBundle.getString("addonInstallRestartButton"),
|
|
accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
|
|
callback: function() {
|
|
Application.restart();
|
|
}
|
|
};
|
|
}
|
|
else {
|
|
messageString = gNavigatorBundle.getString("addonsInstalled");
|
|
action = null;
|
|
}
|
|
|
|
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
|
messageString = messageString.replace("#1", installInfo.installs[0].name);
|
|
messageString = messageString.replace("#2", installInfo.installs.length);
|
|
messageString = messageString.replace("#3", brandShortName);
|
|
|
|
// Remove notificaion on dismissal, since it's possible to cancel the
|
|
// install through the addons manager UI, making the "restart" prompt
|
|
// irrelevant.
|
|
options.removeOnDismissal = true;
|
|
|
|
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
|
action, null, options);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* When addons are installed/uninstalled, check and see if the number of items
|
|
* on the add-on bar changed:
|
|
* - If an add-on was installed, incrementing the count, show the bar.
|
|
* - If an add-on was uninstalled, and no more items are left, hide the bar.
|
|
*/
|
|
let AddonsMgrListener = {
|
|
get addonBar() document.getElementById("addon-bar"),
|
|
get statusBar() document.getElementById("status-bar"),
|
|
getAddonBarItemCount: function() {
|
|
// Take into account the contents of the status bar shim for the count.
|
|
var itemCount = this.statusBar.childNodes.length;
|
|
|
|
var defaultOrNoninteractive = this.addonBar.getAttribute("defaultset")
|
|
.split(",")
|
|
.concat(["separator", "spacer", "spring"]);
|
|
for (let item of this.addonBar.currentSet.split(",")) {
|
|
if (defaultOrNoninteractive.indexOf(item) == -1)
|
|
itemCount++;
|
|
}
|
|
|
|
return itemCount;
|
|
},
|
|
onInstalling: function(aAddon) {
|
|
this.lastAddonBarCount = this.getAddonBarItemCount();
|
|
},
|
|
onInstalled: function(aAddon) {
|
|
if (this.getAddonBarItemCount() > this.lastAddonBarCount)
|
|
setToolbarVisibility(this.addonBar, true);
|
|
},
|
|
onUninstalling: function(aAddon) {
|
|
this.lastAddonBarCount = this.getAddonBarItemCount();
|
|
},
|
|
onUninstalled: function(aAddon) {
|
|
if (this.getAddonBarItemCount() == 0)
|
|
setToolbarVisibility(this.addonBar, false);
|
|
},
|
|
onEnabling: function(aAddon) this.onInstalling(),
|
|
onEnabled: function(aAddon) this.onInstalled(),
|
|
onDisabling: function(aAddon) this.onUninstalling(),
|
|
onDisabled: function(aAddon) this.onUninstalled(),
|
|
};
|
|
|
|
|
|
var LightWeightThemeWebInstaller = {
|
|
handleEvent: function (event) {
|
|
switch (event.type) {
|
|
case "InstallBrowserTheme":
|
|
case "PreviewBrowserTheme":
|
|
case "ResetBrowserThemePreview":
|
|
// ignore requests from background tabs
|
|
if (event.target.ownerDocument.defaultView.top != content)
|
|
return;
|
|
}
|
|
switch (event.type) {
|
|
case "InstallBrowserTheme":
|
|
this._installRequest(event);
|
|
break;
|
|
case "PreviewBrowserTheme":
|
|
this._preview(event);
|
|
break;
|
|
case "ResetBrowserThemePreview":
|
|
this._resetPreview(event);
|
|
break;
|
|
case "pagehide":
|
|
case "TabSelect":
|
|
this._resetPreview();
|
|
break;
|
|
}
|
|
},
|
|
|
|
get _manager () {
|
|
var temp = {};
|
|
Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
|
|
delete this._manager;
|
|
return this._manager = temp.LightweightThemeManager;
|
|
},
|
|
|
|
_installRequest: function (event) {
|
|
var node = event.target;
|
|
var data = this._getThemeFromNode(node);
|
|
if (!data)
|
|
return;
|
|
|
|
if (this._isAllowed(node)) {
|
|
this._install(data);
|
|
return;
|
|
}
|
|
|
|
var allowButtonText =
|
|
gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
|
|
var allowButtonAccesskey =
|
|
gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
|
|
var message =
|
|
gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
|
|
[node.ownerDocument.location.host]);
|
|
var buttons = [{
|
|
label: allowButtonText,
|
|
accessKey: allowButtonAccesskey,
|
|
callback: function () {
|
|
LightWeightThemeWebInstaller._install(data);
|
|
}
|
|
}];
|
|
|
|
this._removePreviousNotifications();
|
|
|
|
var notificationBox = gBrowser.getNotificationBox();
|
|
var notificationBar =
|
|
notificationBox.appendNotification(message, "lwtheme-install-request", "",
|
|
notificationBox.PRIORITY_INFO_MEDIUM,
|
|
buttons);
|
|
notificationBar.persistence = 1;
|
|
},
|
|
|
|
_install: function (newLWTheme) {
|
|
var previousLWTheme = this._manager.currentTheme;
|
|
|
|
var listener = {
|
|
onEnabling: function(aAddon, aRequiresRestart) {
|
|
if (!aRequiresRestart)
|
|
return;
|
|
|
|
let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
|
|
[aAddon.name], 1);
|
|
|
|
let action = {
|
|
label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
|
|
accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
|
|
callback: function () {
|
|
Application.restart();
|
|
}
|
|
};
|
|
|
|
let options = {
|
|
timeout: Date.now() + 30000
|
|
};
|
|
|
|
PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
|
|
messageString, "addons-notification-icon",
|
|
action, null, options);
|
|
},
|
|
|
|
onEnabled: function(aAddon) {
|
|
LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
|
|
}
|
|
};
|
|
|
|
AddonManager.addAddonListener(listener);
|
|
this._manager.currentTheme = newLWTheme;
|
|
AddonManager.removeAddonListener(listener);
|
|
},
|
|
|
|
_postInstallNotification: function (newTheme, previousTheme) {
|
|
function text(id) {
|
|
return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
|
|
}
|
|
|
|
var buttons = [{
|
|
label: text("undoButton"),
|
|
accessKey: text("undoButton.accesskey"),
|
|
callback: function () {
|
|
LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
|
|
LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
|
|
}
|
|
}, {
|
|
label: text("manageButton"),
|
|
accessKey: text("manageButton.accesskey"),
|
|
callback: function () {
|
|
BrowserOpenAddonsMgr("addons://list/theme");
|
|
}
|
|
}];
|
|
|
|
this._removePreviousNotifications();
|
|
|
|
var notificationBox = gBrowser.getNotificationBox();
|
|
var notificationBar =
|
|
notificationBox.appendNotification(text("message"),
|
|
"lwtheme-install-notification", "",
|
|
notificationBox.PRIORITY_INFO_MEDIUM,
|
|
buttons);
|
|
notificationBar.persistence = 1;
|
|
notificationBar.timeout = Date.now() + 20000; // 20 seconds
|
|
},
|
|
|
|
_removePreviousNotifications: function () {
|
|
var box = gBrowser.getNotificationBox();
|
|
|
|
["lwtheme-install-request",
|
|
"lwtheme-install-notification"].forEach(function (value) {
|
|
var notification = box.getNotificationWithValue(value);
|
|
if (notification)
|
|
box.removeNotification(notification);
|
|
});
|
|
},
|
|
|
|
_previewWindow: null,
|
|
_preview: function (event) {
|
|
if (!this._isAllowed(event.target))
|
|
return;
|
|
|
|
var data = this._getThemeFromNode(event.target);
|
|
if (!data)
|
|
return;
|
|
|
|
this._resetPreview();
|
|
|
|
this._previewWindow = event.target.ownerDocument.defaultView;
|
|
this._previewWindow.addEventListener("pagehide", this, true);
|
|
gBrowser.tabContainer.addEventListener("TabSelect", this, false);
|
|
|
|
this._manager.previewTheme(data);
|
|
},
|
|
|
|
_resetPreview: function (event) {
|
|
if (!this._previewWindow ||
|
|
event && !this._isAllowed(event.target))
|
|
return;
|
|
|
|
this._previewWindow.removeEventListener("pagehide", this, true);
|
|
this._previewWindow = null;
|
|
gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
|
|
|
|
this._manager.resetPreview();
|
|
},
|
|
|
|
_isAllowed: function (node) {
|
|
var pm = Services.perms;
|
|
|
|
var uri = node.ownerDocument.documentURIObject;
|
|
return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
|
|
},
|
|
|
|
_getThemeFromNode: function (node) {
|
|
return this._manager.parseTheme(node.getAttribute("data-browsertheme"),
|
|
node.baseURI);
|
|
}
|
|
}
|