diff options
Diffstat (limited to 'application')
64 files changed, 1397 insertions, 735 deletions
diff --git a/application/palemoon/app/profile/palemoon.js b/application/palemoon/app/profile/palemoon.js index ee4a95d381..20919eca4e 100644 --- a/application/palemoon/app/profile/palemoon.js +++ b/application/palemoon/app/profile/palemoon.js @@ -756,6 +756,7 @@ pref("goanna.handlerService.allowRegisterFromDifferentHost", false); pref("browser.geolocation.warning.infoURL", "http://www.palemoon.org/info-url/geolocation.shtml"); pref("browser.mixedcontent.warning.infoURL", "http://www.palemoon.org/info-url/mixedcontent.shtml"); +pref("browser.push.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/push/"); pref("browser.EULA.version", 3); pref("browser.rights.version", 3); diff --git a/application/palemoon/base/content/browser-fullScreen.js b/application/palemoon/base/content/browser-fullScreen.js index 400340e77b..73b10ae850 100644 --- a/application/palemoon/base/content/browser-fullScreen.js +++ b/application/palemoon/base/content/browser-fullScreen.js @@ -354,11 +354,10 @@ var FullScreen = { "fullscreen", Services.perms.ALLOW_ACTION, Services.perms.EXPIRE_SESSION); - let host = uri.host; var onFullscreenchange = function onFullscreenchange(event) { if (event.target == document && document.mozFullScreenElement == null) { // The chrome document has left fullscreen. Remove the temporary permission grant. - Services.perms.remove(host, "fullscreen"); + Services.perms.remove(uri, "fullscreen"); document.removeEventListener("mozfullscreenchange", onFullscreenchange); } } diff --git a/application/palemoon/base/content/browser-plugins.js b/application/palemoon/base/content/browser-plugins.js index 769ac6d8af..8382682038 100644 --- a/application/palemoon/base/content/browser-plugins.js +++ b/application/palemoon/base/content/browser-plugins.js @@ -470,28 +470,12 @@ var gPluginHandler = { } }, - // Match the behaviour of nsPermissionManager - _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) { - if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) { - return "(null)"; - } - - try { - if (principal.URI.host) - return principal.URI.host; - } catch (e) {} - - return principal.origin; - }, - _makeCenterActions: function PH_makeCenterActions(notification) { let contentWindow = notification.browser.contentWindow; let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); let principal = contentWindow.document.nodePrincipal; - // This matches the behavior of nsPermssionManager, used for display purposes only - let principalHost = this._getHostFromPrincipal(principal); let centerActions = []; let pluginsFound = new Set(); @@ -517,11 +501,11 @@ var gPluginHandler = { let permissionObj = Services.perms. getPermissionObject(principal, pluginInfo.permissionString, false); if (permissionObj) { - pluginInfo.pluginPermissionHost = permissionObj.host; + pluginInfo.pluginPermissionPrePath = permissionObj.principal.originNoSuffix; pluginInfo.pluginPermissionType = permissionObj.expireType; } else { - pluginInfo.pluginPermissionHost = principalHost; + pluginInfo.pluginPermissionPrePath = principal.originNoSuffix; pluginInfo.pluginPermissionType = undefined; } diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js index c9f0aa4aa5..6dbd9677e9 100644 --- a/application/palemoon/base/content/browser.js +++ b/application/palemoon/base/content/browser.js @@ -10,6 +10,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/RecentWindow.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", "resource:///modules/CharsetMenu.jsm"); @@ -83,14 +86,8 @@ this.__defineSetter__("AddonManager", function (val) { return this.AddonManager = val; }); -this.__defineGetter__("PluralForm", function() { - Cu.import("resource://gre/modules/PluralForm.jsm"); - return this.PluralForm; -}); -this.__defineSetter__("PluralForm", function (val) { - delete this.PluralForm; - return this.PluralForm = val; -}); +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", "resource:///modules/AboutHomeUtils.jsm"); @@ -1901,88 +1898,126 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup) { } catch (e) {} } -function getShortcutOrURI(aURL, aPostDataRef, aMayInheritPrincipal) { - // Initialize outparam to false - if (aMayInheritPrincipal) - aMayInheritPrincipal.value = false; +/** + * Given a urlbar value, discerns between URIs, keywords and aliases. + * + * @param url + * The urlbar value. + * @param callback (optional, deprecated) + * The callback function invoked when done. This parameter is + * deprecated, please use the Promise that is returned. + * + * @return Promise<{ postData, url, mayInheritPrincipal }> + */ +function getShortcutOrURIAndPostData(url, callback = null) { + if (callback) { + Deprecated.warning("Please use the Promise returned by " + + "getShortcutOrURIAndPostData() instead of passing a " + + "callback", + "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294"); + } - var shortcutURL = null; - var keyword = aURL; - var param = ""; + return Task.spawn(function* () { + let mayInheritPrincipal = false; + let postData = null; + let shortcutURL = null; + let keyword = url; + let param = ""; - var offset = aURL.indexOf(" "); - if (offset > 0) { - keyword = aURL.substr(0, offset); - param = aURL.substr(offset + 1); - } + let offset = url.indexOf(" "); + if (offset > 0) { + keyword = url.substr(0, offset); + param = url.substr(offset + 1); + } - if (!aPostDataRef) - aPostDataRef = {}; + let engine = Services.search.getEngineByAlias(keyword); + if (engine) { + let submission = engine.getSubmission(param, null, "keyword"); + postData = submission.postData; + return { postData: submission.postData, url: submission.uri.spec, + mayInheritPrincipal }; + } - var engine = Services.search.getEngineByAlias(keyword); - if (engine) { - var submission = engine.getSubmission(param); - aPostDataRef.value = submission.postData; - return submission.uri.spec; - } + let entry = yield PlacesUtils.keywords.fetch(keyword); + if (entry) { + shortcutURL = entry.url.href; + postData = entry.postData; + } - [shortcutURL, aPostDataRef.value] = - PlacesUtils.getURLAndPostDataForKeyword(keyword); + if (!shortcutURL) { + return { postData, url, mayInheritPrincipal }; + } - if (!shortcutURL) - return aURL; + let escapedPostData = ""; + if (postData) + escapedPostData = unescape(postData); - var postData = ""; - if (aPostDataRef.value) - postData = unescape(aPostDataRef.value); + if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { + let charset = ""; + const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; + let matches = shortcutURL.match(re); - if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) { - var charset = ""; - const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; - var matches = shortcutURL.match(re); - if (matches) - [, shortcutURL, charset] = matches; - else { - // Try to get the saved character-set. - try { - // makeURI throws if URI is invalid. - // Will return an empty string if character-set is not found. - charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL)); - } catch (e) {} - } + if (matches) { + [, shortcutURL, charset] = matches; + } else { + let uri; + try { + // makeURI() throws if URI is invalid. + uri = makeURI(shortcutURL); + } catch (ex) {} + + if (uri) { + // Try to get the saved character-set. + // Will return an empty string if character-set is not found. + charset = yield PlacesUtils.getCharsetForURI(uri); + } + } - // encodeURIComponent produces UTF-8, and cannot be used for other charsets. - // escape() works in those cases, but it doesn't uri-encode +, @, and /. - // Therefore we need to manually replace these ASCII characters by their - // encodeURIComponent result, to match the behavior of nsEscape() with - // url_XPAlphas - var encodedParam = ""; - if (charset && charset != "UTF-8") - encodedParam = escape(convertFromUnicode(charset, param)). - replace(/[+@\/]+/g, encodeURIComponent); - else // Default charset is UTF-8 - encodedParam = encodeURIComponent(param); + // encodeURIComponent produces UTF-8, and cannot be used for other charsets. + // escape() works in those cases, but it doesn't uri-encode +, @, and /. + // Therefore we need to manually replace these ASCII characters by their + // encodeURIComponent result, to match the behavior of nsEscape() with + // url_XPAlphas + let encodedParam = ""; + if (charset && charset != "UTF-8") + encodedParam = escape(convertFromUnicode(charset, param)). + replace(/[+@\/]+/g, encodeURIComponent); + else // Default charset is UTF-8 + encodedParam = encodeURIComponent(param); - shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); + shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); - if (/%s/i.test(postData)) // POST keyword - aPostDataRef.value = getPostDataStream(postData, param, encodedParam, - "application/x-www-form-urlencoded"); - } - else if (param) { - // This keyword doesn't take a parameter, but one was provided. Just return - // the original URL. - aPostDataRef.value = null; + if (/%s/i.test(escapedPostData)) // POST keyword + postData = getPostDataStream(escapedPostData, param, encodedParam, + "application/x-www-form-urlencoded"); - return aURL; - } + // This URL came from a bookmark, so it's safe to let it inherit the current + // document's principal. + mayInheritPrincipal = true; + + return { postData, url: shortcutURL, mayInheritPrincipal }; + } + + if (param) { + // This keyword doesn't take a parameter, but one was provided. Just return + // the original URL. + postData = null; + + return { postData, url, mayInheritPrincipal }; + } + + // This URL came from a bookmark, so it's safe to let it inherit the current + // document's principal. + mayInheritPrincipal = true; - // This URL came from a bookmark, so it's safe to let it inherit the current - // document's principal. - if (aMayInheritPrincipal) - aMayInheritPrincipal.value = true; + return { postData, url: shortcutURL, mayInheritPrincipal }; + }).then(data => { + if (callback) { + callback(data); + } - return shortcutURL; + return data; + }); } function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) { @@ -2641,8 +2676,8 @@ var browserDragAndDrop = { } }, - drop: function (aEvent, aName, aDisallowInherit) { - return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit); + dropLinks: function (aEvent, aDisallowInherit) { + return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit); } }; @@ -2650,8 +2685,10 @@ var homeButtonObserver = { onDrop: function (aEvent) { // disallow setting home pages that inherit the principal - let url = browserDragAndDrop.drop(aEvent, {}, true); - setTimeout(openHomeDialog, 0, url); + let links = browserDragAndDrop.dropLinks(aEvent, true); + if (links.length) { + setTimeout(openHomeDialog, 0, links.map(link => link.url).join("|")); + } }, onDragOver: function (aEvent) @@ -2667,18 +2704,24 @@ var homeButtonObserver = { function openHomeDialog(aURL) { var promptTitle = gNavigatorBundle.getString("droponhometitle"); - var promptMsg = gNavigatorBundle.getString("droponhomemsg"); + var promptMsg; + if (aURL.includes("|")) { + promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple"); + } else { + promptMsg = gNavigatorBundle.getString("droponhomemsg"); + } + var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg, Services.prompt.STD_YES_NO_BUTTONS, null, null, null, null, {value:0}); if (pressedVal == 0) { try { - var str = Components.classes["@mozilla.org/supports-string;1"] - .createInstance(Components.interfaces.nsISupportsString); - str.data = aURL; + var homepageStr = Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString); + homepageStr.data = aURL; gPrefService.setComplexValue("browser.startup.homepage", - Components.interfaces.nsISupportsString, str); + Components.interfaces.nsISupportsString, homepageStr); } catch (ex) { dump("Failed to set the home page.\n"+ex+"\n"); } @@ -2726,13 +2769,16 @@ var newTabButtonObserver = { onDrop: function (aEvent) { - let url = browserDragAndDrop.drop(aEvent, { }); - var postData = {}; - url = getShortcutOrURI(url, postData); - if (url) { - // allow third-party services to fixup this URL - openNewTabWith(url, null, postData.value, aEvent, true); - } + let links = browserDragAndDrop.dropLinks(aEvent); + Task.spawn(function*() { + for (let link of links) { + let data = yield getShortcutOrURIAndPostData(link.url); + if (data.url) { + // allow third-party services to fixup this URL + openNewTabWith(data.url, null, data.postData, aEvent, true); + } + } + }); } } @@ -2746,13 +2792,16 @@ var newWindowButtonObserver = { }, onDrop: function (aEvent) { - let url = browserDragAndDrop.drop(aEvent, { }); - var postData = {}; - url = getShortcutOrURI(url, postData); - if (url) { - // allow third-party services to fixup this URL - openNewWindowWith(url, null, postData.value, true); - } + let links = browserDragAndDrop.dropLinks(aEvent); + Task.spawn(function*() { + for (let link of links) { + let data = yield getShortcutOrURIAndPostData(link.url); + if (data.url) { + // allow third-party services to fixup this URL + openNewWindowWith(data.url, null, data.postData, true); + } + } + }); } } @@ -5040,36 +5089,81 @@ function middleMousePaste(event) { // bar's behavior (stripsurroundingwhitespace) clipboard = clipboard.replace(/\s*\n\s*/g, ""); - let mayInheritPrincipal = { value: false }; - let url = getShortcutOrURI(clipboard, mayInheritPrincipal); - try { - makeURI(url); - } catch (ex) { - // Not a valid URI. - return; + // if it's not the current tab, we don't need to do anything because the + // browser doesn't exist. + let where = whereToOpenLink(event, true, false); + let lastLocationChange; + if (where == "current") { + lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; } - try { - addToUrlbarHistory(url); - } catch (ex) { - // Things may go wrong when adding url to session history, - // but don't let that interfere with the loading of the url. - Cu.reportError(ex); - } + getShortcutOrURIAndPostData(clipboard).then(data => { + try { + makeURI(data.url); + } catch (ex) { + // Not a valid URI. + return; + } - openUILink(url, event, - { ignoreButton: true, - disallowInheritPrincipal: !mayInheritPrincipal.value }); + try { + addToUrlbarHistory(data.url); + } catch (ex) { + // Things may go wrong when adding url to session history, + // but don't let that interfere with the loading of the url. + Cu.reportError(ex); + } + + if (where != "current" || + lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { + openUILink(data.url, event, + { ignoreButton: true, + disallowInheritPrincipal: !data.mayInheritPrincipal, + initiatingDoc: event ? event.target.ownerDocument : null }); + } + }); event.stopPropagation(); } -function handleDroppedLink(event, url, name) +// handleDroppedLink has the following 2 overloads: +// handleDroppedLink(event, url, name) +// handleDroppedLink(event, links) +function handleDroppedLink(event, urlOrLinks, name) { - let postData = { }; - let uri = getShortcutOrURI(url, postData); - if (uri) - loadURI(uri, null, postData.value, false); + let links; + if (Array.isArray(urlOrLinks)) { + links = urlOrLinks; + } else { + links = [{ url: urlOrLinks, name, type: "" }]; + } + + let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; + + let userContextId = gBrowser.selectedBrowser + .getAttribute("usercontextid") || 0; + + let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); + if (event.shiftKey) + inBackground = !inBackground; + + Task.spawn(function*() { + let urls = []; + let postDatas = []; + for (let link of links) { + let data = yield getShortcutOrURIAndPostData(link.url); + urls.push(data.url); + postDatas.push(data.postData); + } + if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { + gBrowser.loadTabs(urls, { + inBackground, + replace: true, + allowThirdPartyFixup: false, + postDatas, + userContextId, + }); + } + }); // Keep the event from being handled by the dragDrop listeners // built-in to goanna if they happen to be above us. @@ -5184,7 +5278,6 @@ function charsetLoadListener() { } } - var gPageStyleMenu = { _getAllStyleSheets: function (frameset) { diff --git a/application/palemoon/base/content/browser.xul b/application/palemoon/base/content/browser.xul index 11c4f16f98..c2553f2956 100644 --- a/application/palemoon/base/content/browser.xul +++ b/application/palemoon/base/content/browser.xul @@ -13,7 +13,7 @@ <?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?> #ifdef MOZ_DEVTOOLS -<?xml-stylesheet href="chrome://global/skin/devtools/common.css" type="text/css"?> +<?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?> #endif <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> diff --git a/application/palemoon/base/content/newtab/grid.js b/application/palemoon/base/content/newtab/grid.js index 46e0b804bf..a614d03968 100644 --- a/application/palemoon/base/content/newtab/grid.js +++ b/application/palemoon/base/content/newtab/grid.js @@ -59,8 +59,13 @@ var gGrid = { * Refreshes the grid and re-creates all sites. */ refresh: function Grid_refresh() { + let cells = this.cells; + if (!cells) { + return; + } + // Remove all sites. - this.cells.forEach(function (cell) { + cells.forEach(function (cell) { let node = cell.node; let child = node.firstElementChild; diff --git a/application/palemoon/base/content/nsContextMenu.js b/application/palemoon/base/content/nsContextMenu.js index 3d5d40e4c6..830c209986 100644 --- a/application/palemoon/base/content/nsContextMenu.js +++ b/application/palemoon/base/content/nsContextMenu.js @@ -909,7 +909,8 @@ nsContextMenu.prototype = { Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); let doc = this.target.ownerDocument; openUILink(viewURL, e, { disallowInheritPrincipal: true, - referrerURI: doc.documentURIObject }); + referrerURI: doc.documentURIObject, + forceAllowDataURI: true }); } }, diff --git a/application/palemoon/base/content/openLocation.js b/application/palemoon/base/content/openLocation.js index 316dfac701..1a10334c79 100644 --- a/application/palemoon/base/content/openLocation.js +++ b/application/palemoon/base/content/openLocation.js @@ -61,45 +61,52 @@ function doEnabling() function open() { - var url; - var postData = {}; - var mayInheritPrincipal = {value: false}; - if (browser) - url = browser.getShortcutOrURI(dialog.input.value, postData, mayInheritPrincipal); - else - url = dialog.input.value; + getShortcutOrURIAndPostData(dialog.input.value).then(data => { + let url; + let postData = null; + let mayInheritPrincipal = false; + + if (browser) { + url = data.url; + postData = data.postData; + mayInheritPrincipal = data.mayInheritPrincipal; + } else { + url = dialog.input.value; + } - try { - // Whichever target we use for the load, we allow third-party services to - // fixup the URI - switch (dialog.openWhereList.value) { - case "0": - var webNav = Components.interfaces.nsIWebNavigation; - var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | - webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; - if (!mayInheritPrincipal.value) - flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; - browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData.value); - break; - case "1": - window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no", - url, postData.value, null, null, true); - break; - case "3": - browser.delayedOpenTab(url, null, null, postData.value, true); - break; + try { + // Whichever target we use for the load, we allow third-party services to + // fixup the URI + switch (dialog.openWhereList.value) { + case "0": + var webNav = Components.interfaces.nsIWebNavigation; + var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | + webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; + if (!mayInheritPrincipal) + flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData); + break; + case "1": + window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no", + url, postData, null, null, true); + break; + case "3": + browser.delayedOpenTab(url, null, null, postData, true); + break; + } + } + catch(exception) { } - } - catch(exception) { - } - if (pref) { - gOpenLocationLastURL.value = dialog.input.value; - pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value); - } + if (pref) { + gOpenLocationLastURL.value = dialog.input.value; + pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value); + } + + // Delay closing slightly to avoid timing bug on Linux. + window.close(); + }); - // Delay closing slightly to avoid timing bug on Linux. - window.close(); return false; } diff --git a/application/palemoon/base/content/pageinfo/pageInfo.js b/application/palemoon/base/content/pageinfo/pageInfo.js index 83f0ddb913..6b02bc370a 100644 --- a/application/palemoon/base/content/pageinfo/pageInfo.js +++ b/application/palemoon/base/content/pageinfo/pageInfo.js @@ -1112,8 +1112,9 @@ var imagePermissionObserver = { var row = getSelectedRow(imageTree); var item = gImageView.data[row][COL_IMAGE_NODE]; var url = gImageView.data[row][COL_IMAGE_ADDRESS]; - if (makeURI(url).host == permission.host) + if (permission.matchesURI(makeURI(url), true)) { makeBlockImage(url); + } } } } diff --git a/application/palemoon/base/content/pageinfo/permissions.js b/application/palemoon/base/content/pageinfo/permissions.js index 2fa0cc3038..68261ce6e5 100644 --- a/application/palemoon/base/content/pageinfo/permissions.js +++ b/application/palemoon/base/content/pageinfo/permissions.js @@ -98,7 +98,7 @@ var permissionObserver = { if (aTopic == "perm-changed") { var permission = aSubject.QueryInterface( Components.interfaces.nsIPermission); - if (permission.host == gPermURI.host) { + if (permission.matchesURI(gPermURI, true)) { if (permission.type in gPermObj) initRow(permission.type); else if (permission.type.startsWith("plugin")) @@ -119,7 +119,7 @@ function onLoadPermission(principal) gPermURI = uri; gPermPrincipal = principal; var hostText = document.getElementById("hostText"); - hostText.value = gPermURI.host; + hostText.value = gPermURI.prePath; for (var i in gPermObj) initRow(i); diff --git a/application/palemoon/base/content/sanitize.js b/application/palemoon/base/content/sanitize.js index fccec6c989..f2eb24a556 100644 --- a/application/palemoon/base/content/sanitize.js +++ b/application/palemoon/base/content/sanitize.js @@ -148,7 +148,8 @@ Sanitizer.prototype = { if (cookie.creationTime > this.range[0]) // This cookie was created after our cutoff, clear it - cookieMgr.remove(cookie.host, cookie.name, cookie.path, false); + cookieMgr.remove(cookie.host, cookie.name, cookie.path, + false, cookie.originAttributes); } } else { @@ -213,10 +214,16 @@ Sanitizer.prototype = { history: { clear: function () { - if (this.range) - PlacesUtils.history.removeVisitsByTimeframe(this.range[0], this.range[1]); - else - PlacesUtils.history.removeAllPages(); + if (this.range) { + PlacesUtils.history.removeVisitsByFilter({ + beginDate: new Date(this.range[0] / 1000), + endDate: new Date(this.range[1] / 1000) + }).catch(Components.utils.reportError);; + } else { + // Remove everything. + PlacesUtils.history.clear() + .catch(Components.utils.reportError); + } try { var os = Components.classes["@mozilla.org/observer-service;1"] diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml index a7cc6deeaf..c06b49af01 100644 --- a/application/palemoon/base/content/tabbrowser.xml +++ b/application/palemoon/base/content/tabbrowser.xml @@ -570,6 +570,12 @@ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; const nsIChannel = Components.interfaces.nsIChannel; + let location, originalLocation; + try { + aRequest.QueryInterface(nsIChannel) + location = aRequest.URI; + originalLocation = aRequest.originalURI; + } catch (ex) {} if (aStateFlags & nsIWebProgressListener.STATE_START) { this.mRequestCount++; @@ -588,16 +594,8 @@ if (aStateFlags & nsIWebProgressListener.STATE_START && aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { - // It's okay to clear what the user typed when we start - // loading a document. If the user types, this counter gets - // set to zero, if the document load ends without an - // onLocationChange, this counter gets decremented - // (so we keep it while switching tabs after failed loads) - // We need to add 2 because loadURIWithFlags may have - // cancelled a pending load which would have cleared - // its anchor scroll detection temporary increment. if (aWebProgress.isTopLevel) - this.mBrowser.userTypedClear += 2; + this.mBrowser.urlbarChangeTracker.startedLoad(); if (this._shouldShowProgress(aRequest)) { if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { @@ -625,8 +623,8 @@ this.mTab.removeAttribute("progress"); if (aWebProgress.isTopLevel) { - if (!Components.isSuccessCode(aStatus) && - !isTabEmpty(this.mTab)) { + let isSuccessful = Components.isSuccessCode(aStatus); + if (!isSuccessful && !isTabEmpty(this.mTab)) { // Restore the current document's location in case the // request was stopped (possibly from a content script) // before the location changed. @@ -635,14 +633,8 @@ if (this.mTab.selected && gURLBar) URLBarSetURI(); - } else { - // The document is done loading, we no longer want the - // value cleared. - - if (this.mBrowser.userTypedClear > 1) - this.mBrowser.userTypedClear -= 2; - else if (this.mBrowser.userTypedClear > 0) - this.mBrowser.userTypedClear--; + } else if (isSuccessful) { + this.mBrowser.urlbarChangeTracker.finishedLoad(); } if (!this.mBrowser.mIconURL) @@ -652,8 +644,6 @@ if (this.mBlank) this.mBlank = false; - var location = aRequest.QueryInterface(nsIChannel).URI; - // For keyword URIs clear the user typed value since they will be changed into real URIs if (location.scheme == "keyword") this.mBrowser.userTypedValue = null; @@ -696,13 +686,12 @@ let topLevel = aWebProgress.isTopLevel; if (topLevel) { - // If userTypedClear > 0, the document loaded correctly and we should be - // clearing the user typed value. We also need to clear the typed value + // We need to clear the typed value // if the document failed to load, to make sure the urlbar reflects the // failed URI (particularly for SSL errors). However, don't clear the value // if the error page's URI is about:blank, because that causes complete // loss of urlbar contents for invalid URI errors (see bug 867957). - if (this.mBrowser.userTypedClear > 0 || + if (this.mBrowser.didStartLoadSinceLastUserTyping() || ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && aLocation.spec != "about:blank")) this.mBrowser.userTypedValue = null; @@ -738,8 +727,10 @@ aFlags]); } - if (topLevel) + if (topLevel) { this.mBrowser.lastURI = aLocation; + this.mBrowser.lastLocationChange = Date.now(); + } }, onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { @@ -1312,6 +1303,24 @@ <parameter name="aLoadInBackground"/> <parameter name="aReplace"/> <body><![CDATA[ + let aAllowThirdPartyFixup; + let aTargetTab; + let aNewIndex = -1; + let aPostDatas = []; + let aUserContextId; + if (arguments.length == 2 && + typeof arguments[1] == "object") { + let params = arguments[1]; + aLoadInBackground = params.inBackground; + aReplace = params.replace; + aAllowThirdPartyFixup = params.allowThirdPartyFixup; + aTargetTab = params.targetTab; + aNewIndex = typeof params.newIndex === "number" ? + params.newIndex : aNewIndex; + aPostDatas = params.postDatas || aPostDatas; + aUserContextId = params.userContextId; + } + if (!aURIs.length) return; @@ -1329,22 +1338,53 @@ var multiple = aURIs.length > 1; var owner = multiple || aLoadInBackground ? null : this.selectedTab; var firstTabAdded = null; + var targetTabIndex = -1; if (aReplace) { + let browser; + if (aTargetTab) { + browser = this.getBrowserForTab(aTargetTab); + targetTabIndex = aTargetTab._tPos; + } else { + browser = this.mCurrentBrowser; + targetTabIndex = this.tabContainer.selectedIndex; + } + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + if (aAllowThirdPartyFixup) { + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | + Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; + } try { - this.loadURI(aURIs[0], null, null); + browser.loadURIWithFlags(aURIs[0], { + flags, postData: aPostDatas[0] + }); } catch (e) { // Ignore failure in case a URI is wrong, so we can continue // opening the next ones. } + } else { + firstTabAdded = this.addTab(aURIs[0], { + ownerTab: owner, + skipAnimation: multiple, + allowThirdPartyFixup: aAllowThirdPartyFixup, + postData: aPostDatas[0], + userContextId: aUserContextId + }); + if (aNewIndex !== -1) { + this.moveTabTo(firstTabAdded, aNewIndex); + targetTabIndex = firstTabAdded._tPos; + } } - else - firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple}); - var tabNum = this.tabContainer.selectedIndex; + let tabNum = targetTabIndex; for (let i = 1; i < aURIs.length; ++i) { - let tab = this.addTab(aURIs[i], {skipAnimation: true}); - if (aReplace) + let tab = this.addTab(aURIs[i], { + skipAnimation: true, + allowThirdPartyFixup: aAllowThirdPartyFixup, + postData: aPostDatas[i], + userContextId: aUserContextId + }); + if (targetTabIndex !== -1) this.moveTabTo(tab, ++tabNum); } @@ -2976,10 +3016,6 @@ ]]></body> </method> - <property name="userTypedClear" - onget="return this.mCurrentBrowser.userTypedClear;" - onset="return this.mCurrentBrowser.userTypedClear = val;"/> - <property name="userTypedValue" onget="return this.mCurrentBrowser.userTypedValue;" onset="return this.mCurrentBrowser.userTypedValue = val;"/> @@ -4456,40 +4492,35 @@ this.tabbrowser.updateCurrentBrowser(true); } else { // Pass true to disallow dropping javascript: or data: urls - let url; + let links; try { - url = browserDragAndDrop.drop(event, { }, true); + links = browserDragAndDrop.dropLinks(event, true); } catch (ex) {} // // valid urls don't contain spaces ' '; if we have a space it isn't a valid url. // if (!url || url.includes(" ")) //PMed - if (!url) //FF + if (!links || links.length === 0) //FF return; - let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); + let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); if (event.shiftKey) - bgLoad = !bgLoad; + inBackground = !inBackground; - let tab = this._getDragTargetTab(event); - if (!tab || dropEffect == "copy") { - // We're adding a new tab. - let newIndex = this._getDropIndex(event); - let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true}); - this.tabbrowser.moveTabTo(newTab, newIndex); - } else { - // Load in an existing tab. - try { - let webNav = Ci.nsIWebNavigation; - let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | - webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; - this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, flags); - if (!bgLoad) - this.selectedItem = tab; - } catch(ex) { - // Just ignore invalid urls - } - } + let targetTab = this._getDragTargetTab(event); + let userContextId = this.selectedItem + .getAttribute("usercontextid") || 0; + let replace = !(!targetTab || dropEffect == "copy"); + let newIndex = this._getDropIndex(event); + let urls = links.map(link => link.url); + this.tabbrowser.loadTabs(urls, { + inBackground, + replace, + allowThirdPartyFixup: true, + targetTab, + newIndex, + userContextId, + }); } if (draggedTab) { diff --git a/application/palemoon/base/content/urlbarBindings.xml b/application/palemoon/base/content/urlbarBindings.xml index bf59ea1642..985769ec2d 100644 --- a/application/palemoon/base/content/urlbarBindings.xml +++ b/application/palemoon/base/content/urlbarBindings.xml @@ -263,6 +263,9 @@ var postData = null; var action = this._parseActionUrl(url); + let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; + + let matchLastLocationChange = true; if (action) { url = action.param; if (this.hasAttribute("actiontype")) { @@ -275,82 +278,94 @@ } return; } + continueOperation.call(this); } else { - [url, postData, mayInheritPrincipal] = this._canonizeURL(aTriggeringEvent); - if (!url) - return; - } - - this.value = url; - gBrowser.userTypedValue = url; - try { - addToUrlbarHistory(url); - } catch (ex) { - // Things may go wrong when adding url to session history, - // but don't let that interfere with the loading of the url. - Cu.reportError(ex); + this._canonizeURL(aTriggeringEvent, response => { + [url, postData, mayInheritPrincipal] = response; + if (url) { + matchLastLocationChange = (lastLocationChange == + gBrowser.selectedBrowser.lastLocationChange); + continueOperation.call(this); + } + }); } - function loadCurrent() { - let webnav = Ci.nsIWebNavigation; - let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | - webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; - // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from - // inheriting the currently loaded document's principal, unless this - // URL is marked as safe to inherit (e.g. came from a bookmark - // keyword). - if (!mayInheritPrincipal) - flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; - gBrowser.loadURIWithFlags(url, flags, null, null, postData); - } + function continueOperation() + { + this.value = url; + gBrowser.userTypedValue = url; + try { + addToUrlbarHistory(url); + } catch (ex) { + // Things may go wrong when adding url to session history, + // but don't let that interfere with the loading of the url. + Cu.reportError(ex); + } - // Focus the content area before triggering loads, since if the load - // occurs in a new tab, we want focus to be restored to the content - // area when the current tab is re-selected. - gBrowser.selectedBrowser.focus(); + function loadCurrent() { + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from + // inheriting the currently loaded document's principal, unless this + // URL is marked as safe to inherit (e.g. came from a bookmark + // keyword). + if (!mayInheritPrincipal) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + // If the value wasn't typed, we know that we decoded the value as + // UTF-8 (see losslessDecodeURI) + if (!this.valueIsTyped) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8; + gBrowser.loadURIWithFlags(url, flags, null, null, postData); + } - let isMouseEvent = aTriggeringEvent instanceof MouseEvent; - let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey; + // Focus the content area before triggering loads, since if the load + // occurs in a new tab, we want focus to be restored to the content + // area when the current tab is re-selected. + gBrowser.selectedBrowser.focus(); - if (altEnter) { - // XXX This was added a long time ago, and I'm not sure why it is - // necessary. Alt+Enter's default action might cause a system beep, - // or something like that? - aTriggeringEvent.preventDefault(); - aTriggeringEvent.stopPropagation(); - } + let isMouseEvent = aTriggeringEvent instanceof MouseEvent; - // If the current tab is empty, ignore Alt+Enter (just reuse this tab) - altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab); + // If the current tab is empty, ignore Alt+Enter (just reuse this tab) + let altEnter = !isMouseEvent && aTriggeringEvent && + aTriggeringEvent.altKey && !isTabEmpty(gBrowser.selectedTab); - if (isMouseEvent || altEnter) { - // Use the standard UI link behaviors for clicks or Alt+Enter - let where = "tab"; - if (isMouseEvent) - where = whereToOpenLink(aTriggeringEvent, false, false); + if (isMouseEvent || altEnter) { + // Use the standard UI link behaviors for clicks or Alt+Enter + let where = "tab"; + if (isMouseEvent) + where = whereToOpenLink(aTriggeringEvent, false, false); - if (where == "current") { - loadCurrent(); + if (where == "current") { + if (matchLastLocationChange) { + loadCurrent(); + } + } else { + this.handleRevert(); + let params = { allowThirdPartyFixup: true, + postData: postData, + initiatingDoc: document }; + if (!this.valueIsTyped) + params.isUTF8 = true; + openUILinkIn(url, where, params); + } } else { - this.handleRevert(); - let params = { allowThirdPartyFixup: true, - postData: postData, - initiatingDoc: document }; - openUILinkIn(url, where, params); + if (matchLastLocationChange) { + loadCurrent(); + } } - } else { - loadCurrent(); } ]]></body> </method> <method name="_canonizeURL"> <parameter name="aTriggeringEvent"/> + <parameter name="aCallback"/> <body><![CDATA[ var url = this.value; - if (!url) - return ["", null, false]; + if (!url) { + aCallback(["", null, false]); + return; + } // Only add the suffix when the URL bar value isn't already "URL-like", // and only if we get a keyboard event, to match user expectations. @@ -403,11 +418,9 @@ } } - var postData = {}; - var mayInheritPrincipal = { value: false }; - url = getShortcutOrURI(url, postData, mayInheritPrincipal); - - return [url, postData.value, mayInheritPrincipal.value]; + getShortcutOrURIAndPostData(url).then(data => { + aCallback([data.url, data.postData, data.mayInheritPrincipal]); + }); ]]></body> </method> @@ -442,11 +455,12 @@ <method name="onDrop"> <parameter name="aEvent"/> <body><![CDATA[ - let url = browserDragAndDrop.drop(aEvent, { }) + let links = browserDragAndDrop.dropLinks(aEvent); // The URL bar automatically handles inputs with newline characters, // so we can get away with treating text/x-moz-url flavours as text/plain. - if (url) { + if (links.length > 0 && links[0].url) { + let url = links[0].url; aEvent.preventDefault(); this.value = url; SetPageProxyState("invalid"); @@ -1356,8 +1370,8 @@ return; } - let host = gPluginHandler._getHostFromPrincipal(this.notification.browser.contentWindow.document.nodePrincipal); - this._setupDescription("pluginActivateMultiple.message", null, host); + let prePath = this.notification.browser.contentWindow.document.nodePrincipal.URI.prePath; + this._setupDescription("pluginActivateMultiple.message", null, prePath); var showBox = document.getAnonymousElementByAttribute(this, "anonid", "plugin-notification-showbox"); @@ -1396,7 +1410,7 @@ <method name="_setupSingleState"> <body><![CDATA[ var action = this.notification.options.centerActions[0]; - var host = action.pluginPermissionHost; + var prePath = action.pluginPermissionPrePath; let label, linkLabel, linkUrl, button1, button2; @@ -1491,7 +1505,7 @@ Cu.reportError(Error("Unexpected blocklist state")); } } - this._setupDescription(label, action.pluginName, host); + this._setupDescription(label, action.pluginName, prePath); this._setupLink(linkLabel, action.detailsLink); this._primaryButton.label = gNavigatorBundle.getString(button1.label); @@ -1512,7 +1526,7 @@ <method name="_setupDescription"> <parameter name="baseString" /> <parameter name="pluginName" /> <!-- null for the multiple-plugin case --> - <parameter name="host" /> + <parameter name="prePath" /> <body><![CDATA[ var bsn = this._brandShortName; var span = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description"); @@ -1520,17 +1534,17 @@ span.removeChild(span.lastChild); } - var args = ["__host__", this._brandShortName]; + var args = ["__prepath__", this._brandShortName]; if (pluginName) { args.unshift(pluginName); } var bases = gNavigatorBundle.getFormattedString(baseString, args). - split("__host__", 2); + split("__prepath__", 2); span.appendChild(document.createTextNode(bases[0])); - var hostSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "em"); - hostSpan.appendChild(document.createTextNode(host)); - span.appendChild(hostSpan); + var prePathSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "em"); + prePathSpan.appendChild(document.createTextNode(prePath)); + span.appendChild(prePathSpan); span.appendChild(document.createTextNode(bases[1] + " ")); ]]></body> </method> diff --git a/application/palemoon/base/content/utilityOverlay.js b/application/palemoon/base/content/utilityOverlay.js index b1e78d6a95..86cc5cea5a 100644 --- a/application/palemoon/base/content/utilityOverlay.js +++ b/application/palemoon/base/content/utilityOverlay.js @@ -205,6 +205,7 @@ function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI openLinkIn(url, where, params); } +/* eslint-disable complexity */ function openLinkIn(url, where, params) { if (!where || !url) return; @@ -215,6 +216,7 @@ function openLinkIn(url, where, params) { var aCharset = params.charset; var aReferrerURI = params.referrerURI; var aRelatedToCurrent = params.relatedToCurrent; + var aForceAllowDataURI = params.forceAllowDataURI; var aInBackground = params.inBackground; var aDisallowInheritPrincipal = params.disallowInheritPrincipal; var aInitiatingDoc = params.initiatingDoc; @@ -315,6 +317,9 @@ function openLinkIn(url, where, params) { } if (aDisallowInheritPrincipal) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + if (aForceAllowDataURI) { + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI; + } w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData); break; case "tabshifted": diff --git a/application/palemoon/components/about/AboutRedirector.cpp b/application/palemoon/components/about/AboutRedirector.cpp index d52b873b97..27f6540b23 100644 --- a/application/palemoon/components/about/AboutRedirector.cpp +++ b/application/palemoon/components/about/AboutRedirector.cpp @@ -137,8 +137,13 @@ AboutRedirector::NewChannel(nsIURI* aURI, for (int i = 0; i < kRedirTotal; i++) { if (!strcmp(path.get(), kRedirMap[i].id)) { nsCOMPtr<nsIChannel> tempChannel; - rv = ioService->NewChannel(nsDependentCString(kRedirMap[i].url), - nullptr, nullptr, getter_AddRefs(tempChannel)); + nsCOMPtr<nsIURI> tempURI; + rv = NS_NewURI(getter_AddRefs(tempURI), + nsDependentCString(kRedirMap[i].url)); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewChannelInternal(getter_AddRefs(tempChannel), + tempURI, + aLoadInfo); NS_ENSURE_SUCCESS(rv, rv); tempChannel->SetOriginalURI(aURI); diff --git a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js index 46e8670687..054f0405f4 100644 --- a/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js +++ b/application/palemoon/components/downloads/content/allDownloadsViewOverlay.js @@ -41,22 +41,6 @@ const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = const NOT_AVAILABLE = Number.MAX_VALUE; /** - * Download a URL. - * - * @param aURL - * the url to download (nsIURI object) - * @param [optional] aFileName - * the destination file name - */ -function DownloadURL(aURL, aFileName) { - // For private browsing, try to get document out of the most recent browser - // window, or provide our own if there's no browser window. - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - saveURL(aURL, aFileName, null, true, true, undefined, initiatingDoc); -} - -/** * A download element shell is responsible for handling the commands and the * displayed data for a single download view element. The download element * could represent either a past download (for which we get data from places) or @@ -654,7 +638,10 @@ DownloadElementShell.prototype = { // In future we may try to download into the same original target uri, when // we have it. Though that requires verifying the path is still valid and // may surprise the user if he wants to be requested every time. - DownloadURL(this.downloadURI, this.getDownloadMetaData().fileName); + let browserWin = RecentWindow.getMostRecentBrowserWindow(); + let initiatingDoc = browserWin ? browserWin.document : document; + DownloadURL(this.downloadURI, this.getDownloadMetaData().fileName, + initiatingDoc); }, /* nsIController */ @@ -1407,16 +1394,11 @@ DownloadsPlacesView.prototype = { _copySelectedDownloadsToClipboard: function DPV__copySelectedDownloadsToClipboard() { - let selectedElements = this._richlistbox.selectedItems; - // Tycho: let urls = [e._shell.downloadURI for each (e in selectedElements)]; - let urls = []; - - for each (e in selectedElements) { - urls.push(e._shell.downloadURI); - } + let urls = [for (element of this._richlistbox.selectedItems) + element._shell.downloadURI]; Cc["@mozilla.org/widget/clipboardhelper;1"]. - getService(Ci.nsIClipboardHelper).copyString(urls.join("\n"), document); + getService(Ci.nsIClipboardHelper).copyString(urls.join("\n")); }, _getURLFromClipboardData: function DPV__getURLFromClipboardData() { @@ -1450,10 +1432,16 @@ DownloadsPlacesView.prototype = { _downloadURLFromClipboard: function DPV__downloadURLFromClipboard() { let [url, name] = this._getURLFromClipboardData(); - DownloadURL(url, name); + let browserWin = RecentWindow.getMostRecentBrowserWindow(); + let initiatingDoc = browserWin ? browserWin.document : document; + DownloadURL(url, name, initiatingDoc); }, doCommand: function DPV_doCommand(aCommand) { + // Commands may be invoked with keyboard shortcuts even if disabled. + if (!this.isCommandEnabled(aCommand)) { + return; + } switch (aCommand) { case "cmd_copy": this._copySelectedDownloadsToClipboard(); @@ -1504,6 +1492,11 @@ DownloadsPlacesView.prototype = { else contextMenu.removeAttribute("state"); + if (state == nsIDM.DOWNLOAD_DOWNLOADING) { + // The resumable property of a download may change at any time, so + // ensure we update the related command now. + goUpdateCommand("downloadsCmd_pauseResume"); + } return true; }, @@ -1594,10 +1587,16 @@ DownloadsPlacesView.prototype = { if (dt.mozGetDataAt("application/x-moz-file", 0)) return; - let name = { }; - let url = Services.droppedLinkHandler.dropLink(aEvent, name); - if (url) - DownloadURL(url, name.value); + let links = Services.droppedLinkHandler.dropLinks(aEvent); + if (!links.length) + return; + let browserWin = RecentWindow.getMostRecentBrowserWindow(); + let initiatingDoc = browserWin ? browserWin.document : document; + for (let link of links) { + if (link.url.startsWith("about:")) + continue; + DownloadURL(link.url, link.name, initiatingDoc); + } } }; diff --git a/application/palemoon/components/downloads/content/downloads.js b/application/palemoon/components/downloads/content/downloads.js index 0412344bc8..833d7d72ff 100644 --- a/application/palemoon/components/downloads/content/downloads.js +++ b/application/palemoon/components/downloads/content/downloads.js @@ -507,8 +507,7 @@ const DownloadsPanel = { let uri = NetUtil.newURI(url); DownloadsCommon.log("Pasted URL seems valid. Starting download."); - saveURL(uri.spec, name || uri.spec, null, true, true, - undefined, document); + DownloadURL(uri.spec, name, document); } catch (ex) {} }, diff --git a/application/palemoon/components/downloads/content/indicator.js b/application/palemoon/components/downloads/content/indicator.js index e6a5bd0124..1a2175a92a 100644 --- a/application/palemoon/components/downloads/content/indicator.js +++ b/application/palemoon/components/downloads/content/indicator.js @@ -548,15 +548,18 @@ const DownloadsIndicatorView = { if (dt.mozGetDataAt("application/x-moz-file", 0)) return; - let name = {}; - let url = browserDragAndDrop.drop(aEvent, name); - if (url) { - if (url.startsWith("about:")) { - return; - } - - let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; - saveURL(url, name.value, null, true, true, null, sourceDoc); + let links = browserDragAndDrop.dropLinks(aEvent); + if (!links.length) + return; + let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; + let handled = false; + for (let link of links) { + if (link.url.startsWith("about:")) + continue; + saveURL(link.url, link.name, null, true, true, null, sourceDoc); + handled = true; + } + if (handled) { aEvent.preventDefault(); } }, diff --git a/application/palemoon/components/nsBrowserGlue.js b/application/palemoon/components/nsBrowserGlue.js index 225cddd529..6563df4e62 100644 --- a/application/palemoon/components/nsBrowserGlue.js +++ b/application/palemoon/components/nsBrowserGlue.js @@ -39,6 +39,9 @@ Cu.import("resource://gre/modules/Services.jsm"); ["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"], ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource)); +XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", + "@mozilla.org/alerts-service;1", "nsIAlertsService"); + const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; const PREF_PLUGINS_UPDATEURL = "plugins.update.url"; @@ -825,16 +828,6 @@ BrowserGlue.prototype = { if (actions.indexOf("showAlert") == -1) return; - let notifier; - try { - notifier = Cc["@mozilla.org/alerts-service;1"]. - getService(Ci.nsIAlertsService); - } - catch (e) { - // nsIAlertsService is not available for this platform - return; - } - let title = getNotifyString({propName: "alertTitle", stringName: "puAlertTitle", stringParams: [appName]}); @@ -856,10 +849,11 @@ BrowserGlue.prototype = { try { // This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot // be displayed per the idl. - notifier.showAlertNotification(null, title, text, - true, url, clickCallback); + AlertsService.showAlertNotification(null, title, text, + true, url, clickCallback); } catch (e) { + Cu.reportError(e); } }, @@ -1191,7 +1185,7 @@ BrowserGlue.prototype = { }, _migrateUI: function BG__migrateUI() { - const UI_VERSION = 15; + const UI_VERSION = 17; const BROWSER_DOCURL = "chrome://browser/content/browser.xul#"; let currentUIVersion = 0; try { @@ -1386,6 +1380,10 @@ BrowserGlue.prototype = { } } + if (currentUIVersion < 17) { + this._notifyNotificationsUpgrade(); + } + if (this._dirty) this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); @@ -1396,6 +1394,52 @@ BrowserGlue.prototype = { Services.prefs.setIntPref("browser.migration.version", UI_VERSION); }, + _hasExistingNotificationPermission: function BG__hasExistingNotificationPermission() { + let enumerator = Services.perms.enumerator; + while (enumerator.hasMoreElements()) { + let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission); + if (permission.type == "desktop-notification") { + return true; + } + } + return false; + }, + + _notifyNotificationsUpgrade: function BG__notifyNotificationsUpgrade() { + if (!this._hasExistingNotificationPermission()) { + return; + } + function clickCallback(subject, topic, data) { + if (topic != "alertclickcallback") + return; + let win = RecentWindow.getMostRecentBrowserWindow(); + win.openUILinkIn(data, "tab"); + } + // Show the application icon for XUL notifications. We assume system-level + // notifications will include their own icon. + let imageURL = this._hasSystemAlertsService() ? "" : + "chrome://branding/content/about-logo.png"; + let title = gBrowserBundle.GetStringFromName("webNotifications.upgradeTitle"); + let text = gBrowserBundle.GetStringFromName("webNotifications.upgradeBody"); + let url = Services.urlFormatter.formatURLPref("browser.push.warning.infoURL"); + + try { + AlertsService.showAlertNotification(imageURL, title, text, + true, url, clickCallback); + } + catch (e) { + Cu.reportError(e); + } + }, + + _hasSystemAlertsService: function() { + try { + return !!Cc["@mozilla.org/system-alerts-service;1"].getService( + Ci.nsIAlertsService); + } catch (e) {} + return false; + }, + _getPersist: function BG__getPersist(aSource, aProperty) { var target = this._dataSource.GetTarget(aSource, aProperty, true); if (target instanceof Ci.nsIRDFLiteral) @@ -1645,6 +1689,16 @@ ContentPermissionPrompt.prototype = { return chromeWin; }, + _getBrowserForRequest: function (aRequest) { + let requestingWindow = aRequest.window.top; + // find the requesting browser or iframe + let browser = requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler; + return browser; + }, + /** * Show a permission prompt. * @@ -1809,30 +1863,49 @@ ContentPermissionPrompt.prototype = { var message = browserBundle.formatStringFromName("webNotifications.showFromSite", [requestingURI.host], 1); - var actions = [ - { - stringId: "webNotifications.showForSession", - action: Ci.nsIPermissionManager.ALLOW_ACTION, - expireType: Ci.nsIPermissionManager.EXPIRE_SESSION, - callback: function() {}, - }, - { - stringId: "webNotifications.alwaysShow", - action: Ci.nsIPermissionManager.ALLOW_ACTION, - expireType: null, - callback: function() {}, - }, - { - stringId: "webNotifications.neverShow", - action: Ci.nsIPermissionManager.DENY_ACTION, - expireType: null, - callback: function() {}, - }, - ]; + var actions; + + var browser = this._getBrowserForRequest(aRequest); + // Only show "allow for session" in PB mode, we don't + // support "allow for session" in non-PB mode. + if (PrivateBrowsingUtils.isBrowserPrivate(browser)) { + actions = [ + { + stringId: "webNotifications.showForSession", + action: Ci.nsIPermissionManager.ALLOW_ACTION, + expireType: Ci.nsIPermissionManager.EXPIRE_SESSION, + callback: function() {}, + }, + ]; + } else { + actions = [ + { + stringId: "webNotifications.showForSession", + action: Ci.nsIPermissionManager.ALLOW_ACTION, + expireType: Ci.nsIPermissionManager.EXPIRE_SESSION, + callback: function() {}, + }, + { + stringId: "webNotifications.alwaysShow", + action: Ci.nsIPermissionManager.ALLOW_ACTION, + expireType: null, + callback: function() {}, + }, + { + stringId: "webNotifications.neverShow", + action: Ci.nsIPermissionManager.DENY_ACTION, + expireType: null, + callback: function() {}, + }, + ]; + } + var options = { + learnMoreURL: Services.urlFormatter.formatURLPref("browser.push.warning.infoURL"), + }; this._showPrompt(aRequest, message, "desktop-notification", actions, "web-notifications", - "web-notifications-notification-icon", null); + "web-notifications-notification-icon", options); }, _promptPointerLock: function CPP_promtPointerLock(aRequest, autoAllow) { @@ -1875,7 +1948,6 @@ ContentPermissionPrompt.prototype = { }, prompt: function CPP_prompt(request) { - // Only allow exactly one permission rquest here. let types = request.types.QueryInterface(Ci.nsIArray); if (types.length != 1) { @@ -1921,15 +1993,15 @@ ContentPermissionPrompt.prototype = { // Show the prompt. switch (perm.type) { - case "geolocation": - this._promptGeo(request); - break; - case "desktop-notification": - this._promptWebNotifications(request); - break; - case "pointerLock": - this._promptPointerLock(request, autoAllow); - break; + case "geolocation": + this._promptGeo(request); + break; + case "desktop-notification": + this._promptWebNotifications(request); + break; + case "pointerLock": + this._promptPointerLock(request, autoAllow); + break; } }, diff --git a/application/palemoon/components/preferences/aboutPermissions.js b/application/palemoon/components/preferences/aboutPermissions.js index 31b48f88e2..531bb061fa 100644 --- a/application/palemoon/components/preferences/aboutPermissions.js +++ b/application/palemoon/components/preferences/aboutPermissions.js @@ -2,17 +2,25 @@ * 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"; + var Ci = Components.interfaces; var Cc = Components.classes; var Cu = Components.utils; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/PluralForm.jsm"); Cu.import("resource://gre/modules/DownloadUtils.jsm"); Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/ForgetAboutSite.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + +var gSecMan = Cc["@mozilla.org/scriptsecuritymanager;1"]. + getService(Ci.nsIScriptSecurityManager); + var gFaviconService = Cc["@mozilla.org/browser/favicon-service;1"]. getService(Ci.nsIFaviconService); @@ -22,7 +30,7 @@ var gPlacesDatabase = Cc["@mozilla.org/browser/nav-history-service;1"]. clone(true); var gSitesStmt = gPlacesDatabase.createAsyncStatement( - "SELECT get_unreversed_host(rev_host) AS host " + + "SELECT url " + "FROM moz_places " + "WHERE rev_host > '.' " + "AND visit_count > 0 " + @@ -54,14 +62,11 @@ const MASTER_PASSWORD_MESSAGE = "User canceled master password entry"; const TEST_EXACT_PERM_TYPES = ["desktop-notification", "geo", "pointerLock"]; /** - * Site object represents a single site, uniquely identified by a host. + * Site object represents a single site, uniquely identified by a principal. */ -function Site(host) { - this.host = host; +function Site(principal) { + this.principal = principal; this.listitem = null; - - this.httpURI = NetUtil.newURI("http://" + this.host); - this.httpsURI = NetUtil.newURI("https://" + this.host); } Site.prototype = { @@ -83,16 +88,10 @@ Site.prototype = { } } - // Try to find favicon for both URIs, but always prefer the https favicon. - gFaviconService.getFaviconURLForPage(this.httpsURI, function(aURI) { + // Get the favicon for the origin + gFaviconService.getFaviconURLForPage(this.principal.URI, function (aURI) { if (aURI) { invokeCallback(aURI); - } else { - gFaviconService.getFaviconURLForPage(this.httpURI, function(aURI) { - if (aURI) { - invokeCallback(aURI); - } - }); } }.bind(this)); }, @@ -104,7 +103,9 @@ Site.prototype = { * A function that takes the visit count (a number) as a parameter. */ getVisitCount: function Site_getVisitCount(aCallback) { - let rev_host = this.host.split("").reverse().join("") + "."; + // XXX This won't be a very reliable system, as it will count both http: and https: visits + // Unfortunately, I don't think that there is a much better way to do it right now. + let rev_host = this.principal.URI.host.split("").reverse().join("") + "."; gVisitStmt.params.rev_host = rev_host; gVisitStmt.executeAsync({ handleResult: function(aResults) { @@ -147,9 +148,9 @@ Site.prototype = { let permissionValue; if (TEST_EXACT_PERM_TYPES.indexOf(aType) == -1) { - permissionValue = Services.perms.testPermission(this.httpURI, aType); + permissionValue = Services.perms.testPermissionFromPrincipal(this.principal, aType); } else { - permissionValue = Services.perms.testExactPermission(this.httpURI, aType); + permissionValue = Services.perms.testExactPermissionFromPrincipal(this.principal, aType); } aResultObj.value = permissionValue; @@ -187,9 +188,7 @@ Site.prototype = { } } - // Using httpURI is kind of bogus, but the permission manager stores - // the permission for the host, so the right thing happens in the end. - Services.perms.add(this.httpURI, aType, aPerm); + Services.perms.addFromPrincipal(this.principal, aType, aPerm); }, /** @@ -200,7 +199,7 @@ Site.prototype = { * e.g. "cookie", "geo", "indexedDB", "popup", "image" */ clearPermission: function Site_clearPermission(aType) { - Services.perms.remove(this.host, aType); + Services.perms.removeFromPrincipal(this.principal, aType); }, /** @@ -210,11 +209,9 @@ Site.prototype = { */ get logins() { try { - let httpLogins = Services.logins.findLogins( - {}, this.httpURI.prePath, "", ""); - let httpsLogins = Services.logins.findLogins( - {}, this.httpsURI.prePath, "", ""); - return httpLogins.concat(httpsLogins); + let logins = Services.logins.findLogins({}, + this.principal.originNoSuffix, "", ""); + return logins; } catch (e) { if (!e.message.includes(MASTER_PASSWORD_MESSAGE)) { Cu.reportError("AboutPermissions: " + e); @@ -227,8 +224,7 @@ Site.prototype = { // Only say that login saving is blocked if it is blocked for both // http and https. try { - return Services.logins.getLoginSavingEnabled(this.httpURI.prePath) && - Services.logins.getLoginSavingEnabled(this.httpsURI.prePath); + return Services.logins.getLoginSavingEnabled(this.principal.originNoSuffix); } catch (e) { if (!e.message.includes(MASTER_PASSWORD_MESSAGE)) { Cu.reportError("AboutPermissions: " + e); @@ -239,8 +235,7 @@ Site.prototype = { set loginSavingEnabled(isEnabled) { try { - Services.logins.setLoginSavingEnabled(this.httpURI.prePath, isEnabled); - Services.logins.setLoginSavingEnabled(this.httpsURI.prePath, isEnabled); + Services.logins.setLoginSavingEnabled(this.principal.originNoSuffix, isEnabled); } catch (e) { if (!e.message.includes(MASTER_PASSWORD_MESSAGE)) { Cu.reportError("AboutPermissions: " + e); @@ -279,7 +274,11 @@ Site.prototype = { * Removes all data from the browser corresponding to the site. */ forgetSite: function Site_forgetSite() { - ForgetAboutSite.removeDataFromDomain(this.host) + // XXX This removes data for an entire domain, rather than just + // an origin. This may produce confusing results, as data will + // be cleared for the http:// as well as the https:// domain + // if you try to forget the https:// site. + ForgetAboutSite.removeDataFromDomain(this.principal.URI.host) .catch(Cu.reportError); } } @@ -461,10 +460,18 @@ var AboutPermissions = { LIST_BUILD_DELAY: 100, // delay between intervals /** - * Stores a mapping of host strings to Site objects. + * Stores a mapping of origin strings to Site objects. */ _sites: {}, + /** + * Using a getter for sitesFilter to avoid races with tests. + */ + get sitesFilter () { + delete this.sitesFilter; + return this.sitesFilter = document.getElementById("sites-filter"); + }, + sitesList: null, _selectedSite: null, @@ -721,9 +728,9 @@ var AboutPermissions = { break; } let permission = aSubject.QueryInterface(Ci.nsIPermission); - // We can't compare selectedSite.host and permission.host here because - // we need to handle the case where a parent domain was changed in - // a way that affects the subdomain. + // We can't compare selectedSite.principal and permission.principal here + // because we need to handle the case where a parent domain was changed + // in a way that affects the subdomain. if (this._supportedPermissions.indexOf(permission.type) != -1) { this.updatePermission(permission.type); } @@ -798,8 +805,11 @@ var AboutPermissions = { AboutPermissions.startSitesListBatch(); let row; while (row = aResults.getNextRow()) { - let host = row.getResultByName("host"); - AboutPermissions.addHost(host); + let spec = row.getResultByName("url"); + let uri = NetUtil.newURI(spec); + let principal = gSecMan.getNoAppCodebasePrincipal(uri); + + AboutPermissions.addPrincipal(principal); } AboutPermissions.endSitesListBatch(); }, @@ -853,7 +863,8 @@ var AboutPermissions = { // i.e.: "chrome://weave" (Sync) if (!aLogin.hostname.startsWith(schemeChrome + ":")) { let uri = NetUtil.newURI(aLogin.hostname); - this.addHost(uri.host); + let principal = gSecMan.getNoAppCodebasePrincipal(uri); + this.addPrincipal(principal); } } catch (e) { Cu.reportError("AboutPermissions: " + e); @@ -869,7 +880,8 @@ var AboutPermissions = { // i.e.: "chrome://weave" (Sync) if (!aHostname.startsWith(schemeChrome + ":")) { let uri = NetUtil.newURI(aHostname); - this.addHost(uri.host); + let principal = gSecMan.getNoAppCodebasePrincipal(uri); + this.addPrincipal(principal); } } catch (e) { Cu.reportError("AboutPermissions: " + e); @@ -887,7 +899,7 @@ var AboutPermissions = { let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission); // Only include sites with exceptions set for supported permission types. if (this._supportedPermissions.indexOf(permission.type) != -1) { - this.addHost(permission.host); + this.addPrincipal(permission.principal); } itemCnt++; } @@ -898,15 +910,15 @@ var AboutPermissions = { /** * Creates a new Site and adds it to _sites if it's not already there. * - * @param aHost - * A host string. + * @param aPrincipal + * A principal. */ - addHost: function(aHost) { - if (aHost in this._sites) { + addPrincipal: function(aPrincipal) { + if (aPrincipal.origin in this._sites) { return; } - let site = new Site(aHost); - this._sites[aHost] = site; + let site = new Site(aPrincipal); + this._sites[aPrincipal.origin] = site; this.addToSitesList(site); }, @@ -919,7 +931,7 @@ var AboutPermissions = { addToSitesList: function(aSite) { let item = document.createElement("richlistitem"); item.setAttribute("class", "site"); - item.setAttribute("value", aSite.host); + item.setAttribute("value", aSite.principal.origin); aSite.getFavicon(function(aURL) { item.setAttribute("favicon", aURL); @@ -927,9 +939,8 @@ var AboutPermissions = { aSite.listitem = item; // Make sure to only display relevant items when list is filtered. - let filterValue = - document.getElementById("sites-filter").value.toLowerCase(); - item.collapsed = aSite.host.toLowerCase().indexOf(filterValue) == -1; + let filterValue = this.sitesFilter.value.toLowerCase(); + item.collapsed = aSite.principal.origin.toLowerCase().indexOf(filterValue) == -1; (this._listFragment || this.sitesList).appendChild(item); }, @@ -951,8 +962,7 @@ var AboutPermissions = { */ filterSitesList: function() { let siteItems = this.sitesList.children; - let filterValue = - document.getElementById("sites-filter").value.toLowerCase(); + let filterValue = this.sitesFilter.value.toLowerCase(); if (filterValue == "") { for (let i = 0, iLen = siteItems.length; i < iLen; i++) { @@ -983,9 +993,9 @@ var AboutPermissions = { * The host string corresponding to the site to delete. */ deleteFromSitesList: function(aHost) { - for (let host in this._sites) { - let site = this._sites[host]; - if (site.host.hasRootDomain(aHost)) { + for (let origin in this._sites) { + let site = this._sites[origin]; + if (site.principal.URI.host.hasRootDomain(aHost)) { if (site == this._selectedSite) { // Replace site-specific interface with "All Sites" interface. this.sitesList.selectedItem = @@ -993,7 +1003,7 @@ var AboutPermissions = { } this.sitesList.removeChild(site.listitem); - delete this._sites[site.host]; + delete this._sites[site.principal.origin]; } } }, @@ -1009,9 +1019,9 @@ var AboutPermissions = { return; } - let host = event.target.value; - let site = this._selectedSite = this._sites[host]; - document.getElementById("site-label").value = host; + let origin = event.target.value; + let site = this._selectedSite = this._sites[origin]; + document.getElementById("site-label").value = origin; document.getElementById("header-deck").selectedPanel = document.getElementById("site-header"); @@ -1245,19 +1255,19 @@ var AboutPermissions = { * Opens password manager dialog. */ managePasswords: function() { - let selectedHost = ""; + let selectedOrigin = ""; if (this._selectedSite) { - selectedHost = this._selectedSite.host; + selectedOrigin = this._selectedSite.principal.URI.prePath; } let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager"); if (win) { - win.setFilter(selectedHost); + win.setFilter(selectedOrigin); win.focus(); } else { window.openDialog("chrome://passwordmgr/content/passwordManager.xul", "Toolkit:PasswordManager", "", - {filterString : selectedHost}); + {filterString : selectedOrigin}); } }, @@ -1313,10 +1323,12 @@ var AboutPermissions = { * Opens cookie manager dialog. */ manageCookies: function() { + // Cookies are stored by-host, and thus we filter the cookie window + // using only the host of the selected principal's origin let selectedHost = ""; let selectedDomain = ""; if (this._selectedSite) { - selectedHost = this._selectedSite.host; + selectedHost = this._selectedSite.principal.URI.host; selectedDomain = this.domainFromHost(selectedHost); } @@ -1328,6 +1340,13 @@ var AboutPermissions = { window.openDialog("chrome://browser/content/preferences/cookies.xul", "Browser:Cookies", "", {filterString : selectedDomain}); } + }, + + /** + * Focusses the filter box. + */ + focusFilterBox: function() { + this.sitesFilter.focus(); } } diff --git a/application/palemoon/components/preferences/aboutPermissions.xul b/application/palemoon/components/preferences/aboutPermissions.xul index bd5a205c7a..c099161f25 100644 --- a/application/palemoon/components/preferences/aboutPermissions.xul +++ b/application/palemoon/components/preferences/aboutPermissions.xul @@ -25,6 +25,10 @@ <script type="application/javascript" src="chrome://browser/content/preferences/aboutPermissions.js"/> + <keyset> + <key key="&focusSearch.key;" modifiers="accel" oncommand="AboutPermissions.focusFilterBox();"/> + </keyset> + <hbox flex="1" id="permissions-header"> <label id="permissions-pagetitle">&permissionsManager.title;</label> </hbox> @@ -390,7 +394,6 @@ </hbox> </vbox> </hbox> - </vbox> </hbox> diff --git a/application/palemoon/components/preferences/advanced.js b/application/palemoon/components/preferences/advanced.js index 429a0c419e..0803496fee 100644 --- a/application/palemoon/components/preferences/advanced.js +++ b/application/palemoon/components/preferences/advanced.js @@ -8,6 +8,7 @@ Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); Components.utils.import("resource://gre/modules/ctypes.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/LoadContextInfo.jsm"); +Components.utils.import("resource://gre/modules/BrowserUtils.jsm"); var gAdvancedPane = { _inited: false, @@ -377,7 +378,7 @@ var gAdvancedPane = { }, // XXX: duplicated in browser.js - _getOfflineAppUsage: function (host, groups) + _getOfflineAppUsage: function (perm, groups) { var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"]. getService(Components.interfaces.nsIApplicationCacheService); @@ -390,7 +391,7 @@ var gAdvancedPane = { var usage = 0; for (var i = 0; i < groups.length; i++) { var uri = ios.newURI(groups[i], null, null); - if (uri.asciiHost == host) { + if (perm.matchesURI(uri, true)) { var cache = cacheService.getActiveCache(groups[i]); usage += cache.usage; } @@ -427,9 +428,9 @@ var gAdvancedPane = { var row = document.createElement("listitem"); row.id = ""; row.className = "offlineapp"; - row.setAttribute("host", perm.host); + row.setAttribute("origin", perm.principal.origin); var converted = DownloadUtils. - convertByteUnits(this._getOfflineAppUsage(perm.host, groups)); + convertByteUnits(this._getOfflineAppUsage(perm, groups)); row.setAttribute("usage", bundle.getFormattedString("offlineAppUsage", converted)); @@ -453,7 +454,8 @@ var gAdvancedPane = { { var list = document.getElementById("offlineAppsList"); var item = list.selectedItem; - var host = item.getAttribute("host"); + var origin = item.getAttribute("origin"); + var principal = BrowserUtils.principalFromOrigin(origin); var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService); @@ -462,35 +464,34 @@ var gAdvancedPane = { var bundle = document.getElementById("bundlePreferences"); var title = bundle.getString("offlineAppRemoveTitle"); - var prompt = bundle.getFormattedString("offlineAppRemovePrompt", [host]); + var prompt = bundle.getFormattedString("offlineAppRemovePrompt", [principal.URI.prePath]); var confirm = bundle.getString("offlineAppRemoveConfirm"); var result = prompts.confirmEx(window, title, prompt, flags, confirm, null, null, null, {}); if (result != 0) return; - // clear offline cache entries - var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"]. - getService(Components.interfaces.nsIApplicationCacheService); - var ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - var groups = cacheService.getGroups(); - for (var i = 0; i < groups.length; i++) { - var uri = ios.newURI(groups[i], null, null); - if (uri.asciiHost == host) { + // get the permission + var pm = Components.classes["@mozilla.org/permissionmanager;1"] + .getService(Components.interfaces.nsIPermissionManager); + var perm = pm.getPermissionObject(principal, "offline-app"); + if (perm) { + // clear offline cache entries + try { + var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"]. + getService(Components.interfaces.nsIApplicationCacheService); + var groups = cacheService.getGroups(); + for (var i = 0; i < groups.length; i++) { + var uri = Services.io.newURI(groups[i], null, null); + if (perm.matchesURI(uri, true)) { var cache = cacheService.getActiveCache(groups[i]); cache.discard(); + } } - } - - // remove the permission - var pm = Components.classes["@mozilla.org/permissionmanager;1"] - .getService(Components.interfaces.nsIPermissionManager); - pm.remove(host, "offline-app", - Components.interfaces.nsIPermissionManager.ALLOW_ACTION); - pm.remove(host, "offline-app", - Components.interfaces.nsIOfflineCacheUpdateService.ALLOW_NO_WARN); + } catch (e) {} + pm.removePermission(perm); + } list.removeChild(item); gAdvancedPane.offlineAppSelected(); this.updateActualAppCacheSize(); diff --git a/application/palemoon/components/preferences/cookies.js b/application/palemoon/components/preferences/cookies.js index c0455d679f..4ef30d48e0 100644 --- a/application/palemoon/components/preferences/cookies.js +++ b/application/palemoon/components/preferences/cookies.js @@ -5,6 +5,8 @@ const nsICookie = Components.interfaces.nsICookie; +Components.utils.import("resource://gre/modules/PluralForm.jsm"); + var gCookiesWindow = { _cm : Components.classes["@mozilla.org/cookiemanager;1"] .getService(Components.interfaces.nsICookieManager), @@ -24,6 +26,11 @@ var gCookiesWindow = { this._bundle = document.getElementById("bundlePreferences"); this._tree = document.getElementById("cookiesList"); + let removeAllCookies = document.getElementById("removeAllCookies"); + removeAllCookies.setAttribute("accesskey", this._bundle.getString("removeAllCookies.accesskey")); + let removeSelectedCookies = document.getElementById("removeSelectedCookies"); + removeSelectedCookies.setAttribute("accesskey", this._bundle.getString("removeSelectedCookies.accesskey")); + this._populateList(true); document.getElementById("filter").focus(); @@ -63,7 +70,9 @@ var gCookiesWindow = { _cookieEquals: function (aCookieA, aCookieB, aStrippedHost) { return aCookieA.rawHost == aStrippedHost && aCookieA.name == aCookieB.name && - aCookieA.path == aCookieB.path; + aCookieA.path == aCookieB.path && + ChromeUtils.isOriginAttributesEqual(aCookieA.originAttributes, + aCookieB.originAttributes); }, observe: function (aCookie, aTopic, aData) { @@ -268,15 +277,19 @@ var gCookiesWindow = { var item = this._getItemAtIndex(aIndex); if (!item) return; this._invalidateCache(aIndex - 1); - if (item.container) + if (item.container) { gCookiesWindow._hosts[item.rawHost] = null; - else { + } else { var parent = this._getItemAtIndex(item.parentIndex); for (var i = 0; i < parent.cookies.length; ++i) { var cookie = parent.cookies[i]; if (item.rawHost == cookie.rawHost && - item.name == cookie.name && item.path == cookie.path) + item.name == cookie.name && + item.path == cookie.path && + ChromeUtils.isOriginAttributesEqual(item.originAttributes, + cookie.originAttributes)) { parent.cookies.splice(i, removeCount); + } } } }, @@ -451,16 +464,17 @@ var gCookiesWindow = { _makeCookieObject: function (aStrippedHost, aCookie) { var host = aCookie.host; var formattedHost = host.charAt(0) == "." ? host.substring(1, host.length) : host; - var c = { name : aCookie.name, - value : aCookie.value, - isDomain : aCookie.isDomain, - host : aCookie.host, - rawHost : aStrippedHost, - path : aCookie.path, - isSecure : aCookie.isSecure, - expires : aCookie.expires, - level : 1, - container : false }; + var c = { name : aCookie.name, + value : aCookie.value, + isDomain : aCookie.isDomain, + host : aCookie.host, + rawHost : aStrippedHost, + path : aCookie.path, + isSecure : aCookie.isSecure, + expires : aCookie.expires, + level : 1, + container : false, + originAttributes: aCookie.originAttributes }; return c; }, @@ -551,12 +565,12 @@ var gCookiesWindow = { if (item && seln.count == 1 && item.container && item.open) selectedCookieCount += 2; - var removeCookie = document.getElementById("removeCookie"); - var removeCookies = document.getElementById("removeCookies"); - removeCookie.parentNode.selectedPanel = - selectedCookieCount == 1 ? removeCookie : removeCookies; + let buttonLabel = this._bundle.getString("removeSelectedCookies.label"); + let removeSelectedCookies = document.getElementById("removeSelectedCookies"); + removeSelectedCookies.label = PluralForm.get(selectedCookieCount, buttonLabel) + .replace("#1", selectedCookieCount); - removeCookie.disabled = removeCookies.disabled = !(seln.count > 0); + removeSelectedCookies.disabled = !(seln.count > 0); }, performDeletion: function gCookiesWindow_performDeletion(deleteItems) { @@ -567,7 +581,8 @@ var gCookiesWindow = { blockFutureCookies = psvc.getBoolPref("network.cookie.blockFutureCookies"); for (var i = 0; i < deleteItems.length; ++i) { var item = deleteItems[i]; - this._cm.remove(item.host, item.name, item.path, blockFutureCookies); + this._cm.remove(item.host, item.name, item.path, + blockFutureCookies, item.originAttributes); } }, @@ -717,8 +732,13 @@ var gCookiesWindow = { }, onCookieKeyPress: function (aEvent) { - if (aEvent.keyCode == 46) + if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE +#ifdef XP_MACOSX + || aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE +#endif + ) { this.deleteCookie(); + } }, _lastSortProperty : "", @@ -860,7 +880,17 @@ var gCookiesWindow = { }, _updateRemoveAllButton: function gCookiesWindow__updateRemoveAllButton() { - document.getElementById("removeAllCookies").disabled = this._view._rowCount == 0; + let removeAllCookies = document.getElementById("removeAllCookies"); + removeAllCookies.disabled = this._view._rowCount == 0; + + let labelStringID = "removeAllCookies.label"; + let accessKeyStringID = "removeAllCookies.accesskey"; + if (this._view._filtered) { + labelStringID = "removeAllShownCookies.label"; + accessKeyStringID = "removeAllShownCookies.accesskey"; + } + removeAllCookies.setAttribute("label", this._bundle.getString(labelStringID)); + removeAllCookies.setAttribute("accesskey", this._bundle.getString(accessKeyStringID)); }, filter: function () { diff --git a/application/palemoon/components/preferences/cookies.xul b/application/palemoon/components/preferences/cookies.xul index 8ff0d90ec3..60725e9d84 100644 --- a/application/palemoon/components/preferences/cookies.xul +++ b/application/palemoon/components/preferences/cookies.xul @@ -91,16 +91,9 @@ </vbox> <hbox align="end"> <hbox class="actionButtons" flex="1"> - <deck oncommand="gCookiesWindow.deleteCookie();"> - <button id="removeCookie" disabled="true" icon="remove" - label="&button.removecookie.label;" - accesskey="&button.removecookie.accesskey;"/> - <button id="removeCookies" disabled="true" icon="remove" - label="&button.removecookies.label;" - accesskey="&button.removecookie.accesskey;"/> - </deck> + <button id="removeSelectedCookies" disabled="true" icon="clear" + oncommand="gCookiesWindow.deleteCookie();"/> <button id="removeAllCookies" disabled="true" icon="clear" - label="&button.removeallcookies.label;" accesskey="&button.removeallcookies.accesskey;" oncommand="gCookiesWindow.deleteAllCookies();"/> <spacer flex="1"/> #ifndef XP_MACOSX diff --git a/application/palemoon/components/preferences/handlers.xml b/application/palemoon/components/preferences/handlers.xml index d607928039..5fb915ceef 100644 --- a/application/palemoon/components/preferences/handlers.xml +++ b/application/palemoon/components/preferences/handlers.xml @@ -72,7 +72,7 @@ extends="chrome://global/content/bindings/listbox.xml#listitem"> <content> <children> - <xul:listcell xbl:inherits="label=host"/> + <xul:listcell xbl:inherits="label=origin"/> <xul:listcell xbl:inherits="label=usage"/> </children> </content> diff --git a/application/palemoon/components/preferences/jar.mn b/application/palemoon/components/preferences/jar.mn index a277843051..798a2dae4a 100644 --- a/application/palemoon/components/preferences/jar.mn +++ b/application/palemoon/components/preferences/jar.mn @@ -15,7 +15,7 @@ browser.jar: * content/browser/preferences/applicationManager.js * content/browser/preferences/colors.xul * content/browser/preferences/cookies.xul - content/browser/preferences/cookies.js +* content/browser/preferences/cookies.js content/browser/preferences/content.xul content/browser/preferences/content.js * content/browser/preferences/connection.xul @@ -28,8 +28,8 @@ browser.jar: content/browser/preferences/languages.js * content/browser/preferences/main.xul content/browser/preferences/main.js -* content/browser/preferences/permissions.xul - content/browser/preferences/permissions.js + content/browser/preferences/permissions.xul +* content/browser/preferences/permissions.js * content/browser/preferences/preferences.xul content/browser/preferences/privacy.xul content/browser/preferences/privacy.js diff --git a/application/palemoon/components/preferences/permissions.js b/application/palemoon/components/preferences/permissions.js index 785e26d5e0..4b1bf41b2e 100644 --- a/application/palemoon/components/preferences/permissions.js +++ b/application/palemoon/components/preferences/permissions.js @@ -3,38 +3,40 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +Components.utils.import("resource://gre/modules/Services.jsm"); + const nsIPermissionManager = Components.interfaces.nsIPermissionManager; const nsICookiePermission = Components.interfaces.nsICookiePermission; const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions"; -function Permission(host, rawHost, type, capability, perm) +function Permission(principal, type, capability) { - this.host = host; - this.rawHost = rawHost; + this.principal = principal; + this.origin = principal.origin; this.type = type; this.capability = capability; - this.perm = perm; } var gPermissionManager = { - _type : "", - _permissions : [], - _pm : Components.classes["@mozilla.org/permissionmanager;1"] - .getService(Components.interfaces.nsIPermissionManager), - _bundle : null, - _tree : null, - + _type : "", + _permissions : [], + _permissionsToAdd : new Map(), + _permissionsToDelete : new Map(), + _bundle : null, + _tree : null, + _observerRemoved : false, + _view: { _rowCount: 0, - get rowCount() - { - return this._rowCount; + get rowCount() + { + return this._rowCount; }, getCellText: function (aRow, aColumn) { if (aColumn.id == "siteCol") - return gPermissionManager._permissions[aRow].rawHost; + return gPermissionManager._permissions[aRow].origin; else if (aColumn.id == "statusCol") return gPermissionManager._permissions[aRow].capability; return ""; @@ -57,7 +59,7 @@ var gPermissionManager = { return ""; } }, - + _getCapabilityString: function (aCapability) { var stringKey = null; @@ -77,44 +79,66 @@ var gPermissionManager = { } return this._bundle.getString(stringKey); }, - + addPermission: function (aCapability) { var textbox = document.getElementById("url"); - var host = textbox.value.replace(/^\s*([-\w]*:\/+)?/, ""); // trim any leading space and scheme + var input_url = textbox.value.replace(/^\s*/, ""); // trim any leading space + let principal; try { - var ioService = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); - var uri = ioService.newURI("http://"+host, null, null); - host = uri.host; + // The origin accessor on the principal object will throw if the + // principal doesn't have a canonical origin representation. This will + // help catch cases where the URI parser parsed something like + // `localhost:8080` as having the scheme `localhost`, rather than being + // an invalid URI. A canonical origin representation is required by the + // permission manager for storage, so this won't prevent any valid + // permissions from being entered by the user. + let uri; + try { + uri = Services.io.newURI(input_url, null, null); + principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri); + // If we have ended up with an unknown scheme, the following will throw. + principal.origin; + } catch(ex) { + uri = Services.io.newURI("http://" + input_url, null, null); + principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri); + // If we have ended up with an unknown scheme, the following will throw. + principal.origin; + } } catch(ex) { - var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] - .getService(Components.interfaces.nsIPromptService); var message = this._bundle.getString("invalidURI"); var title = this._bundle.getString("invalidURITitle"); - promptService.alert(window, title, message); + Services.prompt.alert(window, title, message); return; } var capabilityString = this._getCapabilityString(aCapability); // check whether the permission already exists, if not, add it - var exists = false; + let permissionExists = false; + let capabilityExists = false; for (var i = 0; i < this._permissions.length; ++i) { - if (this._permissions[i].rawHost == host) { - // Avoid calling the permission manager if the capability settings are - // the same. Otherwise allow the call to the permissions manager to - // update the listbox for us. - exists = this._permissions[i].perm == aCapability; + if (this._permissions[i].principal.equals(principal)) { + permissionExists = true; + capabilityExists = this._permissions[i].capability == capabilityString; + if (!capabilityExists) { + this._permissions[i].capability = capabilityString; + } break; } } - if (!exists) { - host = (host.charAt(0) == ".") ? host.substring(1,host.length) : host; - var uri = ioService.newURI("http://" + host, null, null); - this._pm.add(uri, this._type, aCapability); + + let permissionParams = {principal: principal, type: this._type, capability: aCapability}; + if (!permissionExists) { + this._permissionsToAdd.set(principal.origin, permissionParams); + this._addPermission(permissionParams); + } + else if (!capabilityExists) { + this._permissionsToAdd.set(principal.origin, permissionParams); + this._handleCapabilityChange(); } + textbox.value = ""; textbox.focus(); @@ -124,14 +148,58 @@ var gPermissionManager = { // enable "remove all" button as needed document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0; }, - + + _removePermission: function(aPermission) + { + this._removePermissionFromList(aPermission.principal); + + // If this permission was added during this session, let's remove + // it from the pending adds list to prevent calls to the + // permission manager. + let isNewPermission = this._permissionsToAdd.delete(aPermission.principal.origin); + + if (!isNewPermission) { + this._permissionsToDelete.set(aPermission.principal.origin, aPermission); + } + + }, + + _handleCapabilityChange: function () + { + // Re-do the sort, if the status changed from Block to Allow + // or vice versa, since if we're sorted on status, we may no + // longer be in order. + if (this._lastPermissionSortColumn == "statusCol") { + this._resortPermissions(); + } + this._tree.treeBoxObject.invalidate(); + }, + + _addPermission: function(aPermission) + { + this._addPermissionToList(aPermission); + ++this._view._rowCount; + this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1); + // Re-do the sort, since we inserted this new item at the end. + this._resortPermissions(); + }, + + _resortPermissions: function() + { + gTreeUtils.sort(this._tree, this._view, this._permissions, + this._lastPermissionSortColumn, + this._permissionsComparator, + this._lastPermissionSortColumn, + !this._lastPermissionSortAscending); // keep sort direction + }, + onHostInput: function (aSiteField) { document.getElementById("btnSession").disabled = !aSiteField.value; document.getElementById("btnBlock").disabled = !aSiteField.value; document.getElementById("btnAllow").disabled = !aSiteField.value; }, - + onWindowKeyPress: function (aEvent) { if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) @@ -143,14 +211,14 @@ var gPermissionManager = { if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) document.getElementById("btnAllow").click(); }, - + onLoad: function () { this._bundle = document.getElementById("bundlePreferences"); var params = window.arguments[0]; this.init(params); }, - + init: function (aParams) { if (this._type) { @@ -160,14 +228,14 @@ var gPermissionManager = { this._type = aParams.permissionType; this._manageCapability = aParams.manageCapability; - + var permissionsText = document.getElementById("permissionsText"); while (permissionsText.hasChildNodes()) permissionsText.removeChild(permissionsText.firstChild); permissionsText.appendChild(document.createTextNode(aParams.introText)); document.title = aParams.windowTitle; - + document.getElementById("btnBlock").hidden = !aParams.blockVisible; document.getElementById("btnSession").hidden = !aParams.sessionVisible; document.getElementById("btnAllow").hidden = !aParams.allowVisible; @@ -183,64 +251,64 @@ var gPermissionManager = { var urlLabel = document.getElementById("urlLabel"); urlLabel.hidden = !urlFieldVisible; - var os = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - os.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type); - os.addObserver(this, "perm-changed", false); + let treecols = document.getElementsByTagName("treecols")[0]; + treecols.addEventListener("click", event => { + if (event.target.nodeName != "treecol" || event.button != 0) { + return; + } + + let sortField = event.target.getAttribute("data-field-name"); + if (!sortField) { + return; + } + + gPermissionManager.onPermissionSort(sortField); + }); + + Services.obs.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type); + Services.obs.addObserver(this, "perm-changed", false); this._loadPermissions(); - + urlField.focus(); }, - + uninit: function () { - var os = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - os.removeObserver(this, "perm-changed"); + if (!this._observerRemoved) { + Services.obs.removeObserver(this, "perm-changed"); + + this._observerRemoved = true; + } }, - + observe: function (aSubject, aTopic, aData) { if (aTopic == "perm-changed") { var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission); + + // Ignore unrelated permission types. + if (permission.type != this._type) + return; + if (aData == "added") { - this._addPermissionToList(permission); - ++this._view._rowCount; - this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1); - // Re-do the sort, since we inserted this new item at the end. - gTreeUtils.sort(this._tree, this._view, this._permissions, - this._lastPermissionSortColumn, - this._permissionsComparator, - this._lastPermissionSortColumn, - !this._lastPermissionSortAscending); // keep sort direction + this._addPermission(permission); } else if (aData == "changed") { for (var i = 0; i < this._permissions.length; ++i) { - if (this._permissions[i].host == permission.host) { + if (permission.matches(this._permissions[i].principal, true)) { this._permissions[i].capability = this._getCapabilityString(permission.capability); break; } } - // Re-do the sort, if the status changed from Block to Allow - // or vice versa, since if we're sorted on status, we may no - // longer be in order. - if (this._lastPermissionSortColumn == "statusCol") { - gTreeUtils.sort(this._tree, this._view, this._permissions, - this._lastPermissionSortColumn, - this._permissionsComparator, - this._lastPermissionSortColumn, - !this._lastPermissionSortAscending); // keep sort direction - } - this._tree.treeBoxObject.invalidate(); + this._handleCapabilityChange(); + } + else if (aData == "deleted") { + this._removePermissionFromList(permission.principal); } - // No UI other than this window causes this method to be sent a "deleted" - // notification, so we don't need to implement it since Delete is handled - // directly by the Permission Removal handlers. If that ever changes, those - // implementations will have to move into here. } }, - + onPermissionSelected: function () { var hasSelection = this._tree.view.selection.count > 0; @@ -257,8 +325,8 @@ var gPermissionManager = { gTreeUtils.deleteSelectedItems(this._tree, this._view, this._permissions, removedPermissions); for (var i = 0; i < removedPermissions.length; ++i) { var p = removedPermissions[i]; - this._pm.remove(p.host, p.type); - } + this._removePermission(p); + } document.getElementById("removePermission").disabled = !this._permissions.length; document.getElementById("removeAllPermissions").disabled = !this._permissions.length; }, @@ -271,18 +339,23 @@ var gPermissionManager = { gTreeUtils.deleteAll(this._tree, this._view, this._permissions, removedPermissions); for (var i = 0; i < removedPermissions.length; ++i) { var p = removedPermissions[i]; - this._pm.remove(p.host, p.type); - } + this._removePermission(p); + } document.getElementById("removePermission").disabled = true; document.getElementById("removeAllPermissions").disabled = true; }, - + onPermissionKeyPress: function (aEvent) { - if (aEvent.keyCode == 46) + if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE +#ifdef XP_MACOSX + || aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE +#endif + ) { this.onPermissionDeleted(); + } }, - + _lastPermissionSortColumn: "", _lastPermissionSortAscending: false, _permissionsComparator : function (a, b) @@ -293,16 +366,34 @@ var gPermissionManager = { onPermissionSort: function (aColumn) { - this._lastPermissionSortAscending = gTreeUtils.sort(this._tree, - this._view, + this._lastPermissionSortAscending = gTreeUtils.sort(this._tree, + this._view, this._permissions, aColumn, this._permissionsComparator, - this._lastPermissionSortColumn, + this._lastPermissionSortColumn, this._lastPermissionSortAscending); this._lastPermissionSortColumn = aColumn; }, - + + onApplyChanges: function() + { + // Stop observing permission changes since we are about + // to write out the pending adds/deletes and don't need + // to update the UI + this.uninit(); + + for (let permissionParams of this._permissionsToAdd.values()) { + Services.perms.addFromPrincipal(permissionParams.principal, permissionParams.type, permissionParams.capability); + } + + for (let p of this._permissionsToDelete.values()) { + Services.perms.removeFromPrincipal(p.principal, p.type); + } + + window.close(); + }, + _loadPermissions: function () { this._tree = document.getElementById("permissionsTree"); @@ -310,48 +401,59 @@ var gPermissionManager = { // load permissions into a table var count = 0; - var enumerator = this._pm.enumerator; + var enumerator = Services.perms.enumerator; while (enumerator.hasMoreElements()) { var nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission); this._addPermissionToList(nextPermission); } - + this._view._rowCount = this._permissions.length; // sort and display the table this._tree.view = this._view; - this.onPermissionSort("rawHost"); + this.onPermissionSort("origin"); // disable "remove all" button if there are none document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0; }, - + _addPermissionToList: function (aPermission) { if (aPermission.type == this._type && (!this._manageCapability || (aPermission.capability == this._manageCapability))) { - var host = aPermission.host; + var principal = aPermission.principal; var capabilityString = this._getCapabilityString(aPermission.capability); - var p = new Permission(host, - (host.charAt(0) == ".") ? host.substring(1,host.length) : host, + var p = new Permission(principal, aPermission.type, - capabilityString, - aPermission.capability); + capabilityString); this._permissions.push(p); - } + } }, - - setHost: function (aHost) + + _removePermissionFromList: function (aPrincipal) + { + for (let i = 0; i < this._permissions.length; ++i) { + if (this._permissions[i].principal.equals(aPrincipal)) { + this._permissions.splice(i, 1); + this._view._rowCount--; + this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1); + this._tree.treeBoxObject.invalidate(); + break; + } + } + }, + + setOrigin: function (aOrigin) { - document.getElementById("url").value = aHost; + document.getElementById("url").value = aOrigin; } }; -function setHost(aHost) +function setOrigin(aOrigin) { - gPermissionManager.setHost(aHost); + gPermissionManager.setOrigin(aOrigin); } function initWithParams(aParams) diff --git a/application/palemoon/components/preferences/permissions.xul b/application/palemoon/components/preferences/permissions.xul index fd550e8f7d..33806cc277 100644 --- a/application/palemoon/components/preferences/permissions.xul +++ b/application/palemoon/components/preferences/permissions.xul @@ -1,12 +1,12 @@ <?xml version="1.0"?> -# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. +<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- --> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> -<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> <!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/permissions.dtd" > @@ -35,7 +35,7 @@ <separator class="thin"/> <label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/> <hbox align="start"> - <textbox id="url" flex="1" + <textbox id="url" flex="1" oninput="gPermissionManager.onHostInput(event.target);" onkeypress="gPermissionManager.onHostKeyPress(event);"/> </hbox> @@ -54,30 +54,32 @@ onselect="gPermissionManager.onPermissionSelected();"> <treecols> <treecol id="siteCol" label="&treehead.sitename.label;" flex="3" - onclick="gPermissionManager.onPermissionSort('rawHost');" persist="width"/> + data-field-name="origin" persist="width"/> <splitter class="tree-splitter"/> <treecol id="statusCol" label="&treehead.status.label;" flex="1" - onclick="gPermissionManager.onPermissionSort('capability');" persist="width"/> + data-field-name="capability" persist="width"/> </treecols> <treechildren/> </tree> </vbox> - <hbox align="end"> - <hbox class="actionButtons" flex="1"> + <vbox> + <hbox class="actionButtons" align="left" flex="1"> <button id="removePermission" disabled="true" accesskey="&removepermission.accesskey;" icon="remove" label="&removepermission.label;" oncommand="gPermissionManager.onPermissionDeleted();"/> <button id="removeAllPermissions" icon="clear" label="&removeallpermissions.label;" - accesskey="&removeallpermissions.accesskey;" + accesskey="&removeallpermissions.accesskey;" oncommand="gPermissionManager.onAllPermissionsDeleted();"/> - <spacer flex="1"/> -#ifndef XP_MACOSX + </hbox> + <spacer flex="1"/> + <hbox class="actionButtons" align="right" flex="1"> <button oncommand="close();" icon="close" - label="&button.close.label;" accesskey="&button.close.accesskey;"/> -#endif + label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" /> + <button id="btnApplyChanges" oncommand="gPermissionManager.onApplyChanges();" icon="save" + label="&button.ok.label;" accesskey="&button.ok.accesskey;"/> </hbox> <resizer type="window" dir="bottomend"/> - </hbox> + </vbox> </window> diff --git a/application/palemoon/components/preferences/security.js b/application/palemoon/components/preferences/security.js index 56664bf666..9d5f302a21 100644 --- a/application/palemoon/components/preferences/security.js +++ b/application/palemoon/components/preferences/security.js @@ -131,9 +131,21 @@ var gSecurityPane = { */ showPasswordExceptions: function () { + let bundlePrefs = document.getElementById("bundlePreferences"); + let params = { + blockVisible: true, + sessionVisible: false, + allowVisible: false, + hideStatusColumn: true, + prefilledHost: "", + permissionType: "login-saving", + windowTitle: bundlePrefs.getString("savedLoginsExceptions_title"), + introText: bundlePrefs.getString("savedLoginsExceptions_desc") + }; + document.documentElement.openWindow("Toolkit:PasswordManagerExceptions", - "chrome://passwordmgr/content/passwordManagerExceptions.xul", - "", null); + "chrome://browser/content/preferences/permissions.xul", + null, params); }, /** diff --git a/application/palemoon/components/sessionstore/SessionStore.jsm b/application/palemoon/components/sessionstore/SessionStore.jsm index f7c495be8b..b8d126e217 100644 --- a/application/palemoon/components/sessionstore/SessionStore.jsm +++ b/application/palemoon/components/sessionstore/SessionStore.jsm @@ -1951,7 +1951,13 @@ var SessionStoreInternal = { // userTypedValue. if (browser.userTypedValue) { tabData.userTypedValue = browser.userTypedValue; - tabData.userTypedClear = browser.userTypedClear; + // We always used to keep track of the loading state as an integer, where + // '0' indicated the user had typed since the last load (or no load was + // ongoing), and any positive value indicated we had started a load since + // the last time the user typed in the URL bar. Mimic this to keep the + // session store representation in sync, even though we now represent this + // more explicitly: + tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping() ? 1 : 0; } else { delete tabData.userTypedValue; delete tabData.userTypedClear; diff --git a/application/palemoon/locales/en-US/chrome/browser/browser.properties b/application/palemoon/locales/en-US/chrome/browser/browser.properties index bf363d1035..7f1c88a885 100644 --- a/application/palemoon/locales/en-US/chrome/browser/browser.properties +++ b/application/palemoon/locales/en-US/chrome/browser/browser.properties @@ -7,6 +7,7 @@ openFile=Open File droponhometitle=Set Home Page droponhomemsg=Do you want this document to be your new home page? +droponhomemsgMultiple=Do you want these documents to be your new home pages? # context menu strings @@ -304,6 +305,10 @@ webNotifications.alwaysShow.accesskey=A webNotifications.neverShow=Always Block Notifications webNotifications.neverShow.accesskey=N webNotifications.showFromSite=Would you like to show notifications from %S? +# LOCALIZATION NOTE (webNotifications.upgradeTitle): When using native notifications on OS X, the title may be truncated around 32 characters. +webNotifications.upgradeTitle=Upgraded notifications +# LOCALIZATION NOTE (webNotifications.upgradeBody): When using native notifications on OS X, the body may be truncated around 100 characters in some views. +webNotifications.upgradeBody=You can now receive notifications from sites that are not currently loaded. Click to learn more. # Pointer lock UI diff --git a/application/palemoon/locales/en-US/chrome/browser/preferences/aboutPermissions.dtd b/application/palemoon/locales/en-US/chrome/browser/preferences/aboutPermissions.dtd index ff1da3dd0d..9e36520c8f 100644 --- a/application/palemoon/locales/en-US/chrome/browser/preferences/aboutPermissions.dtd +++ b/application/palemoon/locales/en-US/chrome/browser/preferences/aboutPermissions.dtd @@ -54,3 +54,5 @@ <!ENTITY fullscreen.label "Fullscreen"> <!ENTITY pointerLock.label "Hide the Mouse Pointer"> + +<!ENTITY focusSearch.key "f"> diff --git a/application/palemoon/locales/en-US/chrome/browser/preferences/cookies.dtd b/application/palemoon/locales/en-US/chrome/browser/preferences/cookies.dtd index 06f57c435b..c83331328e 100644 --- a/application/palemoon/locales/en-US/chrome/browser/preferences/cookies.dtd +++ b/application/palemoon/locales/en-US/chrome/browser/preferences/cookies.dtd @@ -7,11 +7,6 @@ <!ENTITY cookiesonsystem.label "The following cookies are stored on your computer:"> <!ENTITY cookiename.label "Cookie Name"> <!ENTITY cookiedomain.label "Site"> -<!ENTITY button.removecookies.label "Remove Cookies"> -<!ENTITY button.removecookie.label "Remove Cookie"> -<!ENTITY button.removecookie.accesskey "R"> -<!ENTITY button.removeallcookies.label "Remove All Cookies"> -<!ENTITY button.removeallcookies.accesskey "A"> <!ENTITY props.name.label "Name:"> <!ENTITY props.value.label "Content:"> diff --git a/application/palemoon/locales/en-US/chrome/browser/preferences/permissions.dtd b/application/palemoon/locales/en-US/chrome/browser/preferences/permissions.dtd index f6a9c466c9..e61228b76d 100644 --- a/application/palemoon/locales/en-US/chrome/browser/preferences/permissions.dtd +++ b/application/palemoon/locales/en-US/chrome/browser/preferences/permissions.dtd @@ -3,7 +3,7 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <!ENTITY window.title "Exceptions"> -<!ENTITY window.width "36em"> +<!ENTITY window.width "45em"> <!ENTITY treehead.sitename.label "Site"> <!ENTITY treehead.status.label "Status"> @@ -21,6 +21,8 @@ <!ENTITY allow.accesskey "A"> <!ENTITY windowClose.key "w"> -<!ENTITY button.close.label "Close"> -<!ENTITY button.close.accesskey "C"> +<!ENTITY button.cancel.label "Cancel"> +<!ENTITY button.cancel.accesskey "C"> +<!ENTITY button.ok.label "Save Changes"> +<!ENTITY button.ok.accesskey "S"> diff --git a/application/palemoon/locales/en-US/chrome/browser/preferences/preferences.properties b/application/palemoon/locales/en-US/chrome/browser/preferences/preferences.properties index 826f1463dc..3eebbcbecc 100644 --- a/application/palemoon/locales/en-US/chrome/browser/preferences/preferences.properties +++ b/application/palemoon/locales/en-US/chrome/browser/preferences/preferences.properties @@ -15,7 +15,7 @@ labelDefaultFont=Default (%S) #### Permissions Manager -cookiepermissionstext=You can specify which websites are always or never allowed to use cookies. Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow. +cookiepermissionstext=You can specify which websites are always or never allowed to use cookies. Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow. cookiepermissionstitle=Exceptions - Cookies addonspermissionstext=You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow. addons_permissions_title=Allowed Sites - Add-ons Installation @@ -23,6 +23,8 @@ popuppermissionstext=You can specify which websites are allowed to open pop-up w popuppermissionstitle=Allowed Sites - Pop-ups invalidURI=Please enter a valid hostname invalidURITitle=Invalid Hostname Entered +savedLoginsExceptions_title=Exceptions - Saved Logins +savedLoginsExceptions_desc=Logins for the following sites will not be saved: #### Master Password @@ -93,9 +95,30 @@ noCookieSelected=<no cookie selected> cookiesAll=The following cookies are stored on your computer: cookiesFiltered=The following cookies match your search: +# LOCALIZATION NOTE (removeAllCookies, removeAllShownCookies): +# removeAllCookies and removeAllShownCookies are both used on the same one button, +# never displayed together and can share the same accesskey. +# When only partial cookies are shown as a result of keyword search, +# removeAllShownCookies is displayed as button label. +# removeAllCookies is displayed when no keyword search and all cookies are shown. +removeAllCookies.label=Remove All +removeAllCookies.accesskey=A +removeAllShownCookies.label=Remove All Shown +removeAllShownCookies.accesskey=A + +# LOCALIZATION NOTE (removeSelectedCookies): +# Semicolon-separated list of plural forms. See: +# http://developer.mozilla.org/en/docs/Localization_and_Plurals +# If you need to display the number of selected elements in your language, +# you can use #1 in your localization as a placeholder for the number. +# For example this is the English string with numbers: +# removeSelectedCookied=Remove #1 Selected;Remove #1 Selected +removeSelectedCookies.label=Remove Selected;Remove Selected +removeSelectedCookies.accesskey=R + #### Offline apps offlineAppRemoveTitle=Remove offline website data -offlineAppRemovePrompt=After removing this data, %S will not be available offline. Are you sure you want to remove this offline website? +offlineAppRemovePrompt=After removing this data, %S will not be available offline. Are you sure you want to remove this offline website? offlineAppRemoveConfirm=Remove offline data # LOCALIZATION NOTE: The next string is for the disk usage of the diff --git a/application/palemoon/modules/PopupNotifications.jsm b/application/palemoon/modules/PopupNotifications.jsm index 15c8915eda..0cb9702301 100644 --- a/application/palemoon/modules/PopupNotifications.jsm +++ b/application/palemoon/modules/PopupNotifications.jsm @@ -12,6 +12,7 @@ const NOTIFICATION_EVENT_DISMISSED = "dismissed"; const NOTIFICATION_EVENT_REMOVED = "removed"; const NOTIFICATION_EVENT_SHOWING = "showing"; const NOTIFICATION_EVENT_SHOWN = "shown"; +const NOTIFICATION_EVENT_SWAPPING = "swapping"; const ICON_SELECTOR = ".notification-anchor-icon"; const ICON_ATTRIBUTE_SHOWING = "showing"; @@ -237,9 +238,23 @@ PopupNotifications.prototype = { * tabs) * "removed": notification has been removed (due to * location change or user action) + * "showing": notification is about to be shown + * (this can be fired multiple times as + * notifications are dismissed and re-shown) * "shown": notification has been shown (this can be fired * multiple times as notifications are dismissed * and re-shown) + * "swapping": the docshell of the browser that created + * the notification is about to be swapped to + * another browser. A second parameter contains + * the browser that is receiving the docshell, + * so that the event callback can transfer stuff + * specific to this notification. + * If the callback returns true, the notification + * will be moved to the new browser. + * If the callback isn't implemented, returns false, + * or doesn't return any value, the notification + * will be removed. * neverShow: Indicate that no popup should be shown for this * notification. Useful for just showing the anchor icon. * removeOnDismissal: @@ -829,13 +844,60 @@ PopupNotifications.prototype = { this._update(notifications, anchor); }, - _fireCallback: function PopupNotifications_fireCallback(n, event) { + _swapBrowserNotifications: function PopupNotifications_swapBrowserNoficications(ourBrowser, otherBrowser) { + // When swaping browser docshells (e.g. dragging tab to new window) we need + // to update our notification map. + + let ourNotifications = this._getNotificationsForBrowser(ourBrowser); + let other = otherBrowser.ownerDocument.defaultView.PopupNotifications; + if (!other) { + if (ourNotifications.length > 0) + Cu.reportError("unable to swap notifications: otherBrowser doesn't support notifications"); + return; + } + let otherNotifications = other._getNotificationsForBrowser(otherBrowser); + if (ourNotifications.length < 1 && otherNotifications.length < 1) { + // No notification to swap. + return; + } + + otherNotifications = otherNotifications.filter(n => { + if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, ourBrowser)) { + n.browser = ourBrowser; + n.owner = this; + return true; + } + other._fireCallback(n, NOTIFICATION_EVENT_REMOVED); + return false; + }); + + ourNotifications = ourNotifications.filter(n => { + if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, otherBrowser)) { + n.browser = otherBrowser; + n.owner = other; + return true; + } + this._fireCallback(n, NOTIFICATION_EVENT_REMOVED); + return false; + }); + + this._setNotificationsForBrowser(otherBrowser, ourNotifications); + other._setNotificationsForBrowser(ourBrowser, otherNotifications); + + if (otherNotifications.length > 0) + this._update(otherNotifications, otherNotifications[0].anchorElement); + if (ourNotifications.length > 0) + other._update(ourNotifications, ourNotifications[0].anchorElement); + }, + + _fireCallback: function PopupNotifications_fireCallback(n, event, ...args) { try { if (n.options.eventCallback) - n.options.eventCallback.call(n, event); + return n.options.eventCallback.call(n, event, ...args); } catch (error) { Cu.reportError(error); } + return undefined; }, _onPopupHidden: function PopupNotifications_onPopupHidden(event) { diff --git a/application/palemoon/modules/webrtcUI.jsm b/application/palemoon/modules/webrtcUI.jsm index c957bfd9ab..819ca181f1 100644 --- a/application/palemoon/modules/webrtcUI.jsm +++ b/application/palemoon/modules/webrtcUI.jsm @@ -11,9 +11,11 @@ const Cc = Components.classes; const Ci = Components.interfaces; Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/PluralForm.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService", "@mozilla.org/mediaManagerService;1", "nsIMediaManagerService"); @@ -128,62 +130,14 @@ function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices) let message = stringBundle.getFormattedString("getUserMedia.share" + requestType + ".message", [ host ]); - function listDevices(menupopup, devices) { - while (menupopup.lastChild) - menupopup.removeChild(menupopup.lastChild); - - let deviceIndex = 0; - for (let device of devices) { - addDeviceToList(menupopup, device.name, deviceIndex); - deviceIndex++; - } - } - - function addDeviceToList(menupopup, deviceName, deviceIndex) { - let menuitem = chromeDoc.createElement("menuitem"); - menuitem.setAttribute("value", deviceIndex); - menuitem.setAttribute("label", deviceName); - menuitem.setAttribute("tooltiptext", deviceName); - menupopup.appendChild(menuitem); - } - - chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length; - chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length; - - let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup"); - let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup"); - listDevices(camMenupopup, videoDevices); - listDevices(micMenupopup, audioDevices); - if (requestType == "CameraAndMicrophone") { - addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1"); - addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1"); - } - let mainAction = { label: PluralForm.get(requestType == "CameraAndMicrophone" ? 2 : 1, stringBundle.getString("getUserMedia.shareSelectedDevices.label")), accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"), - callback: function () { - let allowedDevices = Cc["@mozilla.org/supports-array;1"] - .createInstance(Ci.nsISupportsArray); - if (videoDevices.length) { - let videoDeviceIndex = chromeDoc.getElementById("webRTC-selectCamera-menulist").value; - if (videoDeviceIndex != "-1") - allowedDevices.AppendElement(videoDevices[videoDeviceIndex]); - } - if (audioDevices.length) { - let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value; - if (audioDeviceIndex != "-1") - allowedDevices.AppendElement(audioDevices[audioDeviceIndex]); - } - - if (allowedDevices.Count() == 0) { - denyRequest(aCallID); - return; - } - - Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID); - } + // The real callback will be set during the "showing" event. The + // empty function here is so that PopupNotifications.show doesn't + // reject the action. + callback: function() {} }; let secondaryActions = [{ @@ -194,7 +148,72 @@ function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices) } }]; - let options = null; + let options = { + eventCallback: function(aTopic, aNewBrowser) { + if (aTopic == "swapping") + return true; + + if (aTopic != "showing") + return false; + + let chromeDoc = this.browser.ownerDocument; + + function listDevices(menupopup, devices) { + while (menupopup.lastChild) + menupopup.removeChild(menupopup.lastChild); + + let deviceIndex = 0; + for (let device of devices) { + addDeviceToList(menupopup, device.name, deviceIndex); + deviceIndex++; + } + } + + function addDeviceToList(menupopup, deviceName, deviceIndex) { + let menuitem = chromeDoc.createElement("menuitem"); + menuitem.setAttribute("value", deviceIndex); + menuitem.setAttribute("label", deviceName); + menuitem.setAttribute("tooltiptext", deviceName); + menupopup.appendChild(menuitem); + } + + chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length; + chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length; + + let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup"); + let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup"); + listDevices(camMenupopup, videoDevices); + listDevices(micMenupopup, audioDevices); + if (requestType == "CameraAndMicrophone") { + let stringBundle = chromeDoc.defaultView.gNavigatorBundle; + addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1"); + addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1"); + } + + this.mainAction.callback = function() { + let allowedDevices = Cc["@mozilla.org/supports-array;1"] + .createInstance(Ci.nsISupportsArray); + if (videoDevices.length) { + let videoDeviceIndex = chromeDoc.getElementById("webRTC-selectCamera-menulist").value; + if (videoDeviceIndex != "-1") + allowedDevices.AppendElement(videoDevices[videoDeviceIndex]); + } + if (audioDevices.length) { + let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value; + if (audioDeviceIndex != "-1") + allowedDevices.AppendElement(audioDevices[audioDeviceIndex]); + } + + if (allowedDevices.Count() == 0) { + denyRequest(aCallID); + return; + } + + Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID); + }; + return true; + } + }; chromeWin.PopupNotifications.show(browser, "webRTC-shareDevices", message, "webRTC-shareDevices-notification-icon", mainAction, @@ -254,7 +273,8 @@ function showBrowserSpecificIndicator(aBrowser) { }]; let options = { hideNotNow: true, - dismissed: true + dismissed: true, + eventCallback: function(aTopic) aTopic == "swapping" }; chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message, "webRTC-sharingDevices-notification-icon", mainAction, diff --git a/application/palemoon/themes/linux/Push-16.png b/application/palemoon/themes/linux/Push-16.png Binary files differnew file mode 100644 index 0000000000..082b177811 --- /dev/null +++ b/application/palemoon/themes/linux/Push-16.png diff --git a/application/palemoon/themes/linux/Push-64.png b/application/palemoon/themes/linux/Push-64.png Binary files differnew file mode 100644 index 0000000000..6e09ab9c32 --- /dev/null +++ b/application/palemoon/themes/linux/Push-64.png diff --git a/application/palemoon/themes/linux/browser.css b/application/palemoon/themes/linux/browser.css index 4f4964db8d..131a63a901 100644 --- a/application/palemoon/themes/linux/browser.css +++ b/application/palemoon/themes/linux/browser.css @@ -1169,6 +1169,10 @@ toolbar[iconsize="small"] #webrtc-status-button { list-style-image: url(chrome://browser/skin/Geolocation-64.png); } +.popup-notification-icon[popupid="push"] { + list-style-image: url(chrome://browser/skin/Push-64.png); +} + .popup-notification-icon[popupid="xpinstall-disabled"], .popup-notification-icon[popupid="addon-progress"], .popup-notification-icon[popupid="addon-install-cancelled"], @@ -1288,6 +1292,10 @@ toolbar[iconsize="small"] #webrtc-status-button { list-style-image: url(chrome://browser/skin/Geolocation-16.png); } +#push-notification-icon { + list-style-image: url(chrome://browser/skin/Push-16.png); +} + #addons-notification-icon { list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png); } @@ -1374,7 +1382,18 @@ toolbar[iconsize="small"] #webrtc-status-button { .web-notifications-notification-icon, #web-notifications-notification-icon { - list-style-image: url(chrome://browser/skin/notification-16.png); + list-style-image: url(chrome://browser/skin/web-notifications-tray.svg); + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.web-notifications-notification-icon:hover, +#web-notifications-notification-icon:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.web-notifications-notification-icon:hover:active, +#web-notifications-notification-icon:hover:active { + -moz-image-region: rect(0, 48px, 16px, 32px); } #pointerLock-notification-icon { diff --git a/application/palemoon/themes/linux/jar.mn b/application/palemoon/themes/linux/jar.mn index 7e67d01293..3c2ac406ee 100644 --- a/application/palemoon/themes/linux/jar.mn +++ b/application/palemoon/themes/linux/jar.mn @@ -36,8 +36,6 @@ browser.jar: skin/classic/browser/mixed-content-blocked-64.png skin/classic/browser/monitor.png skin/classic/browser/monitor_16-10.png - skin/classic/browser/notification-16.png - skin/classic/browser/notification-64.png * skin/classic/browser/pageInfo.css skin/classic/browser/pageInfo.png skin/classic/browser/page-livemarks.png @@ -54,6 +52,8 @@ browser.jar: skin/classic/browser/Toolbar.png skin/classic/browser/Toolbar-small.png skin/classic/browser/urlbar-arrow.png + skin/classic/browser/web-notifications-icon.svg + skin/classic/browser/web-notifications-tray.svg #ifdef MOZ_WEBRTC skin/classic/browser/webRTC-shareDevice-16.png skin/classic/browser/webRTC-shareDevice-64.png diff --git a/application/palemoon/themes/linux/notification-16.png b/application/palemoon/themes/linux/notification-16.png Binary files differdeleted file mode 100644 index 6b2df73413..0000000000 --- a/application/palemoon/themes/linux/notification-16.png +++ /dev/null diff --git a/application/palemoon/themes/linux/notification-64.png b/application/palemoon/themes/linux/notification-64.png Binary files differdeleted file mode 100644 index a01d0ab776..0000000000 --- a/application/palemoon/themes/linux/notification-64.png +++ /dev/null diff --git a/application/palemoon/themes/linux/preferences/aboutPermissions.css b/application/palemoon/themes/linux/preferences/aboutPermissions.css index 4065688310..224c880184 100644 --- a/application/palemoon/themes/linux/preferences/aboutPermissions.css +++ b/application/palemoon/themes/linux/preferences/aboutPermissions.css @@ -90,7 +90,7 @@ list-style-image: url(chrome://global/skin/icons/question-64.png); } .pref-icon[type="desktop-notification"] { - list-style-image: url(chrome://browser/skin/notification-64.png); + list-style-image: url(chrome://browser/skin/web-notifications-icon.svg); } .pref-icon[type="install"] { list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png); diff --git a/application/palemoon/themes/linux/web-notifications-icon.svg b/application/palemoon/themes/linux/web-notifications-icon.svg new file mode 100644 index 0000000000..f7186c7275 --- /dev/null +++ b/application/palemoon/themes/linux/web-notifications-icon.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="64" height="64" viewBox="0 0 64 64"> + <defs> + <style> + .icon { + fill: #a6a6a6; + fill-rule: evenodd; + } + </style> + </defs> + <path d="M57,48 L46,48 L46,60.016 L32.482,48 L7,48 C5.343,48 4,46.657 4,45 L4,11.031 C4,9.374 5.343,8.031 7,8.031 L57,8.031 C58.657,8.031 60,9.374 60,11.031 L60,45 C60,46.657 58.657,48 57,48 ZM36,16.031 C36,14.927 35.105,14.031 34,14.031 L30,14.031 C28.895,14.031 28,14.927 28,16.031 L28,30.031 C28,31.136 28.895,32.031 30,32.031 L34,32.031 C35.105,32.031 36,31.136 36,30.031 L36,16.031 ZM36,37.5 C36,36.672 35.328,36 34.5,36 L29.5,36 C28.672,36 28,36.672 28,37.5 L28,40.5 C28,41.328 28.672,42 29.5,42 L34.5,42 C35.328,42 36,41.328 36,40.5 L36,37.5 Z" class="icon"/> +</svg> diff --git a/application/palemoon/themes/linux/web-notifications-tray.svg b/application/palemoon/themes/linux/web-notifications-tray.svg new file mode 100644 index 0000000000..314026a106 --- /dev/null +++ b/application/palemoon/themes/linux/web-notifications-tray.svg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 96 32"> + <defs> + <style> + .style-icon-notification { + fill: #666666; + } + .style-icon-notification.hover { + fill: #808080; + } + .style-icon-notification.active { + fill: #4d4d4d; + } + </style> + <path id="shape-notifcations-push" d="M27,23.969 L24,23.969 L24,29.977 L17.241,23.969 L5,23.969 C3.343,23.969 2,22.626 2,20.969 L2,6.969 C2,5.312 3.343,3.969 5,3.969 L27,3.969 C28.657,3.969 30,5.312 30,6.969 L30,20.969 C30,22.626 28.657,23.969 27,23.969 ZM18,8.969 C18,7.864 17.105,6.969 16,6.969 C14.895,6.969 14,7.864 14,8.969 L14,13.969 C14,15.073 14.895,15.969 16,15.969 C17.105,15.969 18,15.073 18,13.969 L18,8.969 ZM16.5,17.969 L15.5,17.969 C14.672,17.969 14,18.640 14,19.469 C14,20.297 14.672,20.969 15.5,20.969 L16.5,20.969 C17.328,20.969 18,20.297 18,19.469 C18,18.640 17.328,17.969 16.5,17.969 Z"/> + </defs> + <use xlink:href="#shape-notifcations-push" class="style-icon-notification"/> + <use xlink:href="#shape-notifcations-push" transform="translate(32)" class="style-icon-notification hover"/> + <use xlink:href="#shape-notifcations-push" transform="translate(64)" class="style-icon-notification active"/> +</svg> diff --git a/application/palemoon/themes/osx/Push-16.png b/application/palemoon/themes/osx/Push-16.png Binary files differnew file mode 100644 index 0000000000..54ef8f8eae --- /dev/null +++ b/application/palemoon/themes/osx/Push-16.png diff --git a/application/palemoon/themes/osx/Push-64.png b/application/palemoon/themes/osx/Push-64.png Binary files differnew file mode 100644 index 0000000000..099b9c76f3 --- /dev/null +++ b/application/palemoon/themes/osx/Push-64.png diff --git a/application/palemoon/themes/osx/browser.css b/application/palemoon/themes/osx/browser.css index 8d709d8e1d..58443fa768 100644 --- a/application/palemoon/themes/osx/browser.css +++ b/application/palemoon/themes/osx/browser.css @@ -1975,6 +1975,10 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { list-style-image: url(chrome://browser/skin/Geolocation-16.png); } +#push-notification-icon { + list-style-image: url(chrome://browser/skin/Push-16.png); +} + #addons-notification-icon { list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png); } @@ -2061,7 +2065,18 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { .web-notifications-notification-icon, #web-notifications-notification-icon { - list-style-image: url(chrome://browser/skin/notification-16.png); + list-style-image: url(chrome://browser/skin/web-notifications-tray.svg); + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.web-notifications-notification-icon:hover, +#web-notifications-notification-icon:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.web-notifications-notification-icon:hover:active, +#web-notifications-notification-icon:hover:active { + -moz-image-region: rect(0, 48px, 16px, 32px); } #pointerLock-notification-icon { diff --git a/application/palemoon/themes/osx/jar.mn b/application/palemoon/themes/osx/jar.mn index 186cd8a750..a085c5f818 100644 --- a/application/palemoon/themes/osx/jar.mn +++ b/application/palemoon/themes/osx/jar.mn @@ -41,8 +41,6 @@ browser.jar: skin/classic/browser/mixed-content-blocked-64.png skin/classic/browser/monitor.png skin/classic/browser/monitor_16-10.png - skin/classic/browser/notification-16.png - skin/classic/browser/notification-64.png skin/classic/browser/pageInfo.css skin/classic/browser/pageInfo.png skin/classic/browser/page-livemarks.png @@ -70,6 +68,8 @@ browser.jar: skin/classic/browser/urlbar-history-dropmarker.png skin/classic/browser/webapps-16.png skin/classic/browser/webapps-64.png + skin/classic/browser/web-notifications-icon.svg + skin/classic/browser/web-notifications-tray.svg skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png) skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png) skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png) diff --git a/application/palemoon/themes/osx/notification-16.png b/application/palemoon/themes/osx/notification-16.png Binary files differdeleted file mode 100644 index 6b2df73413..0000000000 --- a/application/palemoon/themes/osx/notification-16.png +++ /dev/null diff --git a/application/palemoon/themes/osx/notification-64.png b/application/palemoon/themes/osx/notification-64.png Binary files differdeleted file mode 100644 index a01d0ab776..0000000000 --- a/application/palemoon/themes/osx/notification-64.png +++ /dev/null diff --git a/application/palemoon/themes/osx/preferences/aboutPermissions.css b/application/palemoon/themes/osx/preferences/aboutPermissions.css index cfb941bbb7..a97e71e041 100644 --- a/application/palemoon/themes/osx/preferences/aboutPermissions.css +++ b/application/palemoon/themes/osx/preferences/aboutPermissions.css @@ -93,7 +93,7 @@ list-style-image: url(chrome://global/skin/icons/question-64.png); } .pref-icon[type="desktop-notification"] { - list-style-image: url(chrome://browser/skin/notification-64.png); + list-style-image: url(chrome://browser/skin/web-notifications-icon.svg); } .pref-icon[type="install"] { list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png); diff --git a/application/palemoon/themes/osx/web-notifications-icon.svg b/application/palemoon/themes/osx/web-notifications-icon.svg new file mode 100644 index 0000000000..f7186c7275 --- /dev/null +++ b/application/palemoon/themes/osx/web-notifications-icon.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="64" height="64" viewBox="0 0 64 64"> + <defs> + <style> + .icon { + fill: #a6a6a6; + fill-rule: evenodd; + } + </style> + </defs> + <path d="M57,48 L46,48 L46,60.016 L32.482,48 L7,48 C5.343,48 4,46.657 4,45 L4,11.031 C4,9.374 5.343,8.031 7,8.031 L57,8.031 C58.657,8.031 60,9.374 60,11.031 L60,45 C60,46.657 58.657,48 57,48 ZM36,16.031 C36,14.927 35.105,14.031 34,14.031 L30,14.031 C28.895,14.031 28,14.927 28,16.031 L28,30.031 C28,31.136 28.895,32.031 30,32.031 L34,32.031 C35.105,32.031 36,31.136 36,30.031 L36,16.031 ZM36,37.5 C36,36.672 35.328,36 34.5,36 L29.5,36 C28.672,36 28,36.672 28,37.5 L28,40.5 C28,41.328 28.672,42 29.5,42 L34.5,42 C35.328,42 36,41.328 36,40.5 L36,37.5 Z" class="icon"/> +</svg> diff --git a/application/palemoon/themes/osx/web-notifications-tray.svg b/application/palemoon/themes/osx/web-notifications-tray.svg new file mode 100644 index 0000000000..314026a106 --- /dev/null +++ b/application/palemoon/themes/osx/web-notifications-tray.svg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 96 32"> + <defs> + <style> + .style-icon-notification { + fill: #666666; + } + .style-icon-notification.hover { + fill: #808080; + } + .style-icon-notification.active { + fill: #4d4d4d; + } + </style> + <path id="shape-notifcations-push" d="M27,23.969 L24,23.969 L24,29.977 L17.241,23.969 L5,23.969 C3.343,23.969 2,22.626 2,20.969 L2,6.969 C2,5.312 3.343,3.969 5,3.969 L27,3.969 C28.657,3.969 30,5.312 30,6.969 L30,20.969 C30,22.626 28.657,23.969 27,23.969 ZM18,8.969 C18,7.864 17.105,6.969 16,6.969 C14.895,6.969 14,7.864 14,8.969 L14,13.969 C14,15.073 14.895,15.969 16,15.969 C17.105,15.969 18,15.073 18,13.969 L18,8.969 ZM16.5,17.969 L15.5,17.969 C14.672,17.969 14,18.640 14,19.469 C14,20.297 14.672,20.969 15.5,20.969 L16.5,20.969 C17.328,20.969 18,20.297 18,19.469 C18,18.640 17.328,17.969 16.5,17.969 Z"/> + </defs> + <use xlink:href="#shape-notifcations-push" class="style-icon-notification"/> + <use xlink:href="#shape-notifcations-push" transform="translate(32)" class="style-icon-notification hover"/> + <use xlink:href="#shape-notifcations-push" transform="translate(64)" class="style-icon-notification active"/> +</svg> diff --git a/application/palemoon/themes/windows/Push-16.png b/application/palemoon/themes/windows/Push-16.png Binary files differnew file mode 100644 index 0000000000..d710e7336d --- /dev/null +++ b/application/palemoon/themes/windows/Push-16.png diff --git a/application/palemoon/themes/windows/Push-64.png b/application/palemoon/themes/windows/Push-64.png Binary files differnew file mode 100644 index 0000000000..27fecb8588 --- /dev/null +++ b/application/palemoon/themes/windows/Push-64.png diff --git a/application/palemoon/themes/windows/browser.css b/application/palemoon/themes/windows/browser.css index 1c51accae4..3d519ba856 100644 --- a/application/palemoon/themes/windows/browser.css +++ b/application/palemoon/themes/windows/browser.css @@ -2548,7 +2548,18 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { .web-notifications-notification-icon, #web-notifications-notification-icon { - list-style-image: url(chrome://browser/skin/notification-16.png); + list-style-image: url(chrome://browser/skin/web-notifications-tray.svg); + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.web-notifications-notification-icon:hover, +#web-notifications-notification-icon:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.web-notifications-notification-icon:hover:active, +#web-notifications-notification-icon:hover:active { + -moz-image-region: rect(0, 48px, 16px, 32px); } #pointerLock-notification-icon { diff --git a/application/palemoon/themes/windows/jar.mn b/application/palemoon/themes/windows/jar.mn index a66714b133..8c0d9a5ccb 100644 --- a/application/palemoon/themes/windows/jar.mn +++ b/application/palemoon/themes/windows/jar.mn @@ -42,8 +42,6 @@ browser.jar: skin/classic/browser/mixed-content-blocked-64.png skin/classic/browser/monitor.png skin/classic/browser/monitor_16-10.png - skin/classic/browser/notification-16.png - skin/classic/browser/notification-64.png skin/classic/browser/pageInfo.css skin/classic/browser/pageInfo.png skin/classic/browser/page-livemarks.png (feeds/feedIcon16.png) @@ -72,6 +70,8 @@ browser.jar: skin/classic/browser/urlbar-history-dropmarker.png skin/classic/browser/webapps-16.png skin/classic/browser/webapps-64.png + skin/classic/browser/web-notifications-icon.svg + skin/classic/browser/web-notifications-tray.svg skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png) skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png) skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png) diff --git a/application/palemoon/themes/windows/notification-16.png b/application/palemoon/themes/windows/notification-16.png Binary files differdeleted file mode 100644 index 6b2df73413..0000000000 --- a/application/palemoon/themes/windows/notification-16.png +++ /dev/null diff --git a/application/palemoon/themes/windows/notification-64.png b/application/palemoon/themes/windows/notification-64.png Binary files differdeleted file mode 100644 index a01d0ab776..0000000000 --- a/application/palemoon/themes/windows/notification-64.png +++ /dev/null diff --git a/application/palemoon/themes/windows/preferences/aboutPermissions.css b/application/palemoon/themes/windows/preferences/aboutPermissions.css index d9db6ccbf4..73b8d6e14c 100644 --- a/application/palemoon/themes/windows/preferences/aboutPermissions.css +++ b/application/palemoon/themes/windows/preferences/aboutPermissions.css @@ -93,7 +93,7 @@ list-style-image: url(chrome://global/skin/icons/question-48.png); } .pref-icon[type="desktop-notification"] { - list-style-image: url(chrome://browser/skin/notification-64.png); + list-style-image: url(chrome://browser/skin/web-notifications-icon.svg); } .pref-icon[type="install"] { list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-48.png); diff --git a/application/palemoon/themes/windows/web-notifications-icon.svg b/application/palemoon/themes/windows/web-notifications-icon.svg new file mode 100644 index 0000000000..f7186c7275 --- /dev/null +++ b/application/palemoon/themes/windows/web-notifications-icon.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="64" height="64" viewBox="0 0 64 64"> + <defs> + <style> + .icon { + fill: #a6a6a6; + fill-rule: evenodd; + } + </style> + </defs> + <path d="M57,48 L46,48 L46,60.016 L32.482,48 L7,48 C5.343,48 4,46.657 4,45 L4,11.031 C4,9.374 5.343,8.031 7,8.031 L57,8.031 C58.657,8.031 60,9.374 60,11.031 L60,45 C60,46.657 58.657,48 57,48 ZM36,16.031 C36,14.927 35.105,14.031 34,14.031 L30,14.031 C28.895,14.031 28,14.927 28,16.031 L28,30.031 C28,31.136 28.895,32.031 30,32.031 L34,32.031 C35.105,32.031 36,31.136 36,30.031 L36,16.031 ZM36,37.5 C36,36.672 35.328,36 34.5,36 L29.5,36 C28.672,36 28,36.672 28,37.5 L28,40.5 C28,41.328 28.672,42 29.5,42 L34.5,42 C35.328,42 36,41.328 36,40.5 L36,37.5 Z" class="icon"/> +</svg> diff --git a/application/palemoon/themes/windows/web-notifications-tray.svg b/application/palemoon/themes/windows/web-notifications-tray.svg new file mode 100644 index 0000000000..314026a106 --- /dev/null +++ b/application/palemoon/themes/windows/web-notifications-tray.svg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 96 32"> + <defs> + <style> + .style-icon-notification { + fill: #666666; + } + .style-icon-notification.hover { + fill: #808080; + } + .style-icon-notification.active { + fill: #4d4d4d; + } + </style> + <path id="shape-notifcations-push" d="M27,23.969 L24,23.969 L24,29.977 L17.241,23.969 L5,23.969 C3.343,23.969 2,22.626 2,20.969 L2,6.969 C2,5.312 3.343,3.969 5,3.969 L27,3.969 C28.657,3.969 30,5.312 30,6.969 L30,20.969 C30,22.626 28.657,23.969 27,23.969 ZM18,8.969 C18,7.864 17.105,6.969 16,6.969 C14.895,6.969 14,7.864 14,8.969 L14,13.969 C14,15.073 14.895,15.969 16,15.969 C17.105,15.969 18,15.073 18,13.969 L18,8.969 ZM16.5,17.969 L15.5,17.969 C14.672,17.969 14,18.640 14,19.469 C14,20.297 14.672,20.969 15.5,20.969 L16.5,20.969 C17.328,20.969 18,20.297 18,19.469 C18,18.640 17.328,17.969 16.5,17.969 Z"/> + </defs> + <use xlink:href="#shape-notifcations-push" class="style-icon-notification"/> + <use xlink:href="#shape-notifcations-push" transform="translate(32)" class="style-icon-notification hover"/> + <use xlink:href="#shape-notifcations-push" transform="translate(64)" class="style-icon-notification active"/> +</svg> |