Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 const gXPInstallObserver = {
7 _findChildShell: function (aDocShell, aSoughtShell)
8 {
9 if (aDocShell == aSoughtShell)
10 return aDocShell;
12 var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
13 for (var i = 0; i < node.childCount; ++i) {
14 var docShell = node.getChildAt(i);
15 docShell = this._findChildShell(docShell, aSoughtShell);
16 if (docShell == aSoughtShell)
17 return docShell;
18 }
19 return null;
20 },
22 _getBrowser: function (aDocShell)
23 {
24 for (let browser of gBrowser.browsers) {
25 if (this._findChildShell(browser.docShell, aDocShell))
26 return browser;
27 }
28 return null;
29 },
31 observe: function (aSubject, aTopic, aData)
32 {
33 var brandBundle = document.getElementById("bundle_brand");
34 var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
35 var win = installInfo.originatingWindow;
36 var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
37 .getInterface(Components.interfaces.nsIWebNavigation)
38 .QueryInterface(Components.interfaces.nsIDocShell);
39 var browser = this._getBrowser(shell);
40 if (!browser)
41 return;
42 const anchorID = "addons-notification-icon";
43 var messageString, action;
44 var brandShortName = brandBundle.getString("brandShortName");
46 var notificationID = aTopic;
47 // Make notifications persist a minimum of 30 seconds
48 var options = {
49 timeout: Date.now() + 30000
50 };
52 switch (aTopic) {
53 case "addon-install-disabled":
54 notificationID = "xpinstall-disabled"
56 if (gPrefService.prefIsLocked("xpinstall.enabled")) {
57 messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
58 buttons = [];
59 }
60 else {
61 messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
63 action = {
64 label: gNavigatorBundle.getString("xpinstallDisabledButton"),
65 accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
66 callback: function editPrefs() {
67 gPrefService.setBoolPref("xpinstall.enabled", true);
68 }
69 };
70 }
72 PopupNotifications.show(browser, notificationID, messageString, anchorID,
73 action, null, options);
74 break;
75 case "addon-install-blocked":
76 messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
77 [brandShortName, installInfo.originatingURI.host]);
79 let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
80 action = {
81 label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
82 accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
83 callback: function() {
84 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
85 installInfo.install();
86 }
87 };
89 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
90 PopupNotifications.show(browser, notificationID, messageString, anchorID,
91 action, null, options);
92 break;
93 case "addon-install-started":
94 var needsDownload = function needsDownload(aInstall) {
95 return aInstall.state != AddonManager.STATE_DOWNLOADED;
96 }
97 // If all installs have already been downloaded then there is no need to
98 // show the download progress
99 if (!installInfo.installs.some(needsDownload))
100 return;
101 notificationID = "addon-progress";
102 messageString = gNavigatorBundle.getString("addonDownloading");
103 messageString = PluralForm.get(installInfo.installs.length, messageString);
104 options.installs = installInfo.installs;
105 options.contentWindow = browser.contentWindow;
106 options.sourceURI = browser.currentURI;
107 options.eventCallback = function(aEvent) {
108 if (aEvent != "removed")
109 return;
110 options.contentWindow = null;
111 options.sourceURI = null;
112 };
113 PopupNotifications.show(browser, notificationID, messageString, anchorID,
114 null, null, options);
115 break;
116 case "addon-install-failed":
117 // TODO This isn't terribly ideal for the multiple failure case
118 for (let install of installInfo.installs) {
119 let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
120 installInfo.originatingURI.host;
121 if (!host)
122 host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
123 install.sourceURI.host;
125 let error = (host || install.error == 0) ? "addonError" : "addonLocalError";
126 if (install.error != 0)
127 error += install.error;
128 else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
129 error += "Blocklisted";
130 else
131 error += "Incompatible";
133 messageString = gNavigatorBundle.getString(error);
134 messageString = messageString.replace("#1", install.name);
135 if (host)
136 messageString = messageString.replace("#2", host);
137 messageString = messageString.replace("#3", brandShortName);
138 messageString = messageString.replace("#4", Services.appinfo.version);
140 PopupNotifications.show(browser, notificationID, messageString, anchorID,
141 action, null, options);
142 }
143 break;
144 case "addon-install-complete":
145 var needsRestart = installInfo.installs.some(function(i) {
146 return i.addon.pendingOperations != AddonManager.PENDING_NONE;
147 });
149 if (needsRestart) {
150 messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
151 action = {
152 label: gNavigatorBundle.getString("addonInstallRestartButton"),
153 accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
154 callback: function() {
155 Application.restart();
156 }
157 };
158 }
159 else {
160 messageString = gNavigatorBundle.getString("addonsInstalled");
161 action = null;
162 }
164 messageString = PluralForm.get(installInfo.installs.length, messageString);
165 messageString = messageString.replace("#1", installInfo.installs[0].name);
166 messageString = messageString.replace("#2", installInfo.installs.length);
167 messageString = messageString.replace("#3", brandShortName);
169 // Remove notificaion on dismissal, since it's possible to cancel the
170 // install through the addons manager UI, making the "restart" prompt
171 // irrelevant.
172 options.removeOnDismissal = true;
174 PopupNotifications.show(browser, notificationID, messageString, anchorID,
175 action, null, options);
176 break;
177 }
178 }
179 };
181 var LightWeightThemeWebInstaller = {
182 handleEvent: function (event) {
183 switch (event.type) {
184 case "InstallBrowserTheme":
185 case "PreviewBrowserTheme":
186 case "ResetBrowserThemePreview":
187 // ignore requests from background tabs
188 if (event.target.ownerDocument.defaultView.top != content)
189 return;
190 }
191 switch (event.type) {
192 case "InstallBrowserTheme":
193 this._installRequest(event);
194 break;
195 case "PreviewBrowserTheme":
196 this._preview(event);
197 break;
198 case "ResetBrowserThemePreview":
199 this._resetPreview(event);
200 break;
201 case "pagehide":
202 case "TabSelect":
203 this._resetPreview();
204 break;
205 }
206 },
208 get _manager () {
209 var temp = {};
210 Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
211 delete this._manager;
212 return this._manager = temp.LightweightThemeManager;
213 },
215 _installRequest: function (event) {
216 var node = event.target;
217 var data = this._getThemeFromNode(node);
218 if (!data)
219 return;
221 if (this._isAllowed(node)) {
222 this._install(data);
223 return;
224 }
226 var allowButtonText =
227 gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
228 var allowButtonAccesskey =
229 gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
230 var message =
231 gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
232 [node.ownerDocument.location.host]);
233 var buttons = [{
234 label: allowButtonText,
235 accessKey: allowButtonAccesskey,
236 callback: function () {
237 LightWeightThemeWebInstaller._install(data);
238 }
239 }];
241 this._removePreviousNotifications();
243 var notificationBox = gBrowser.getNotificationBox();
244 var notificationBar =
245 notificationBox.appendNotification(message, "lwtheme-install-request", "",
246 notificationBox.PRIORITY_INFO_MEDIUM,
247 buttons);
248 notificationBar.persistence = 1;
249 },
251 _install: function (newLWTheme) {
252 var previousLWTheme = this._manager.currentTheme;
254 var listener = {
255 onEnabling: function(aAddon, aRequiresRestart) {
256 if (!aRequiresRestart)
257 return;
259 let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
260 [aAddon.name], 1);
262 let action = {
263 label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
264 accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
265 callback: function () {
266 Application.restart();
267 }
268 };
270 let options = {
271 timeout: Date.now() + 30000
272 };
274 PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
275 messageString, "addons-notification-icon",
276 action, null, options);
277 },
279 onEnabled: function(aAddon) {
280 LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
281 }
282 };
284 AddonManager.addAddonListener(listener);
285 this._manager.currentTheme = newLWTheme;
286 AddonManager.removeAddonListener(listener);
287 },
289 _postInstallNotification: function (newTheme, previousTheme) {
290 function text(id) {
291 return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
292 }
294 var buttons = [{
295 label: text("undoButton"),
296 accessKey: text("undoButton.accesskey"),
297 callback: function () {
298 LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
299 LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
300 }
301 }, {
302 label: text("manageButton"),
303 accessKey: text("manageButton.accesskey"),
304 callback: function () {
305 BrowserOpenAddonsMgr("addons://list/theme");
306 }
307 }];
309 this._removePreviousNotifications();
311 var notificationBox = gBrowser.getNotificationBox();
312 var notificationBar =
313 notificationBox.appendNotification(text("message"),
314 "lwtheme-install-notification", "",
315 notificationBox.PRIORITY_INFO_MEDIUM,
316 buttons);
317 notificationBar.persistence = 1;
318 notificationBar.timeout = Date.now() + 20000; // 20 seconds
319 },
321 _removePreviousNotifications: function () {
322 var box = gBrowser.getNotificationBox();
324 ["lwtheme-install-request",
325 "lwtheme-install-notification"].forEach(function (value) {
326 var notification = box.getNotificationWithValue(value);
327 if (notification)
328 box.removeNotification(notification);
329 });
330 },
332 _previewWindow: null,
333 _preview: function (event) {
334 if (!this._isAllowed(event.target))
335 return;
337 var data = this._getThemeFromNode(event.target);
338 if (!data)
339 return;
341 this._resetPreview();
343 this._previewWindow = event.target.ownerDocument.defaultView;
344 this._previewWindow.addEventListener("pagehide", this, true);
345 gBrowser.tabContainer.addEventListener("TabSelect", this, false);
347 this._manager.previewTheme(data);
348 },
350 _resetPreview: function (event) {
351 if (!this._previewWindow ||
352 event && !this._isAllowed(event.target))
353 return;
355 this._previewWindow.removeEventListener("pagehide", this, true);
356 this._previewWindow = null;
357 gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
359 this._manager.resetPreview();
360 },
362 _isAllowed: function (node) {
363 var pm = Services.perms;
365 var uri = node.ownerDocument.documentURIObject;
366 return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
367 },
369 _getThemeFromNode: function (node) {
370 return this._manager.parseTheme(node.getAttribute("data-browsertheme"),
371 node.baseURI);
372 }
373 }
375 /*
376 * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
377 */
378 let LightweightThemeListener = {
379 _modifiedStyles: [],
381 init: function () {
382 XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
383 for (let i = document.styleSheets.length - 1; i >= 0; i--) {
384 let sheet = document.styleSheets[i];
385 if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
386 return sheet;
387 }
388 });
390 Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
391 Services.obs.addObserver(this, "lightweight-theme-optimized", false);
392 if (document.documentElement.hasAttribute("lwtheme"))
393 this.updateStyleSheet(document.documentElement.style.backgroundImage);
394 },
396 uninit: function () {
397 Services.obs.removeObserver(this, "lightweight-theme-styling-update");
398 Services.obs.removeObserver(this, "lightweight-theme-optimized");
399 },
401 /**
402 * Append the headerImage to the background-image property of all rulesets in
403 * browser-lightweightTheme.css.
404 *
405 * @param headerImage - a string containing a CSS image for the lightweight theme header.
406 */
407 updateStyleSheet: function(headerImage) {
408 if (!this.styleSheet)
409 return;
410 this.substituteRules(this.styleSheet.cssRules, headerImage);
411 },
413 substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) {
414 let styleRulesModified = 0;
415 for (let i = 0; i < ruleList.length; i++) {
416 let rule = ruleList[i];
417 if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
418 // Add the number of modified sub-rules to the modified count
419 styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
420 } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
421 if (!rule.style.backgroundImage)
422 continue;
423 let modifiedIndex = existingStyleRulesModified + styleRulesModified;
424 if (!this._modifiedStyles[modifiedIndex])
425 this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
427 rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
428 styleRulesModified++;
429 } else {
430 Cu.reportError("Unsupported rule encountered");
431 }
432 }
433 return styleRulesModified;
434 },
436 // nsIObserver
437 observe: function (aSubject, aTopic, aData) {
438 if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
439 !this.styleSheet)
440 return;
442 if (aTopic == "lightweight-theme-optimized" && aSubject != window)
443 return;
445 let themeData = JSON.parse(aData);
446 if (!themeData)
447 return;
448 this.updateStyleSheet("url(" + themeData.headerURL + ")");
449 },
450 };