diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 9ebb317ac6..c079f974cd 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -34,6 +34,9 @@ pref("browser.tabs.remote.autostart.1", false); // Bug 945235: Prevent all bars to be considered visible: pref("toolkit.defaultChromeFeatures", "chrome,dialog=no,close,resizable,scrollbars,extrachrome"); +// Disable focus rings +pref("browser.display.focus_ring_width", 0); + // Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density. pref("browser.viewport.scaleRatio", -1); diff --git a/browser/base/content/aboutTabCrashed.xhtml b/browser/base/content/aboutTabCrashed.xhtml new file mode 100644 index 0000000000..f593b6063b --- /dev/null +++ b/browser/base/content/aboutTabCrashed.xhtml @@ -0,0 +1,56 @@ + + + + + + %htmlDTD; + + %globalDTD; + + %browserDTD; + +]> + + + + + + + +
+

&tabCrashed.header;

+

&tabCrashed.message;

+
+ +
+ +
+ + + + diff --git a/browser/base/content/abouthome/aboutHome.js b/browser/base/content/abouthome/aboutHome.js index 9e826b69e9..5f8309c0d9 100644 --- a/browser/base/content/abouthome/aboutHome.js +++ b/browser/base/content/abouthome/aboutHome.js @@ -237,7 +237,7 @@ const SEARCH_ENGINES = { let gInitialized = false; let gObserver = new MutationObserver(function (mutations) { for (let mutation of mutations) { - if (mutation.attributeName == "searchEngineURL") { + if (mutation.attributeName == "searchEngineName") { setupSearchEngine(); if (!gInitialized) { gInitialized = true; @@ -253,6 +253,10 @@ window.addEventListener("pageshow", function () { window.gObserver.observe(document.documentElement, { attributes: true }); fitToWidth(); window.addEventListener("resize", fitToWidth); + + // Ask chrome to update snippets. + var event = new CustomEvent("AboutHomeLoad", {bubbles:true}); + document.dispatchEvent(event); }); window.addEventListener("pagehide", function() { @@ -263,52 +267,17 @@ window.addEventListener("pagehide", function() { function onSearchSubmit(aEvent) { let searchTerms = document.getElementById("searchText").value; - let searchURL = document.documentElement.getAttribute("searchEngineURL"); + let engineName = document.documentElement.getAttribute("searchEngineName"); - if (searchURL && searchTerms.length > 0) { - // Send an event that a search was performed. This was originally - // added so Firefox Health Report could record that a search from - // about:home had occurred. - let engineName = document.documentElement.getAttribute("searchEngineName"); - let event = new CustomEvent("AboutHomeSearchEvent", {detail: engineName}); + if (engineName && searchTerms.length > 0) { + // Send an event that will perform a search and Firefox Health Report will + // record that a search from about:home has occurred. + let eventData = JSON.stringify({ + engineName: engineName, + searchTerms: searchTerms + }); + let event = new CustomEvent("AboutHomeSearchEvent", {detail: eventData}); document.dispatchEvent(event); - - const SEARCH_TOKEN = "_searchTerms_"; - let searchPostData = document.documentElement.getAttribute("searchEnginePostData"); - if (searchPostData) { - // Check if a post form already exists. If so, remove it. - const POST_FORM_NAME = "searchFormPost"; - let form = document.forms[POST_FORM_NAME]; - if (form) { - form.parentNode.removeChild(form); - } - - // Create a new post form. - form = document.body.appendChild(document.createElement("form")); - form.setAttribute("name", POST_FORM_NAME); - // Set the URL to submit the form to. - form.setAttribute("action", searchURL.replace(SEARCH_TOKEN, searchTerms)); - form.setAttribute("method", "post"); - - // Create new elements for search param. - searchPostData = searchPostData.split("&"); - for (let postVar of searchPostData) { - let [name, value] = postVar.split("="); - if (value == SEARCH_TOKEN) { - value = searchTerms; - } - let input = document.createElement("input"); - input.setAttribute("type", "hidden"); - input.setAttribute("name", name); - input.setAttribute("value", value); - form.appendChild(input); - } - // Submit the form. - form.submit(); - } else { - searchURL = searchURL.replace(SEARCH_TOKEN, encodeURIComponent(searchTerms)); - window.location.href = searchURL; - } } aEvent.preventDefault(); diff --git a/browser/base/content/browser-fullZoom.js b/browser/base/content/browser-fullZoom.js index 0837bf7c22..2b1cd22331 100644 --- a/browser/base/content/browser-fullZoom.js +++ b/browser/base/content/browser-fullZoom.js @@ -49,6 +49,10 @@ var FullZoom = { // Initialization & Destruction init: function FullZoom_init() { + // Bug 691614 - zooming support for electrolysis + if (gMultiProcessBrowser) + return; + // Listen for scrollwheel events so we can save scrollwheel-based changes. window.addEventListener("DOMMouseScroll", this, false); @@ -79,6 +83,10 @@ var FullZoom = { }, destroy: function FullZoom_destroy() { + // Bug 691614 - zooming support for electrolysis + if (gMultiProcessBrowser) + return; + gPrefService.removeObserver("browser.zoom.", this); this._cps2.removeObserverForName(this.name, this); window.removeEventListener("DOMMouseScroll", this, false); @@ -233,6 +241,10 @@ var FullZoom = { * (optional) browser object displaying the document */ onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) { + // Bug 691614 - zooming support for electrolysis + if (gMultiProcessBrowser) + return; + let browser = aBrowser || gBrowser.selectedBrowser; // If we haven't been initialized yet but receive an onLocationChange // notification then let's store and replay it upon initialization. diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 73dd295466..335adcad53 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -88,9 +88,6 @@ this.__defineSetter__("PluralForm", function (val) { return this.PluralForm = val; }); -XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", - "resource:///modules/AboutHomeUtils.jsm"); - #ifdef MOZ_SERVICES_SYNC XPCOMUtils.defineLazyModuleGetter(this, "Weave", "resource://services-sync/main.js"); @@ -710,9 +707,12 @@ var gBrowserInit = { var mustLoadSidebar = false; - Cc["@mozilla.org/eventlistenerservice;1"] - .getService(Ci.nsIEventListenerService) - .addSystemEventListener(gBrowser, "click", contentAreaClick, true); + if (!gMultiProcessBrowser) { + // There is a Content:Click message manually sent from content. + Cc["@mozilla.org/eventlistenerservice;1"] + .getService(Ci.nsIEventListenerService) + .addSystemEventListener(gBrowser, "click", contentAreaClick, true); + } gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false); @@ -2322,51 +2322,6 @@ function PageProxyClickHandler(aEvent) middleMousePaste(aEvent); } -/** - * Handle load of some pages (about:*) so that we can make modifications - * to the DOM for unprivileged pages. - */ -function BrowserOnAboutPageLoad(doc) { - if (doc.documentURI.toLowerCase() == "about:home") { - let ss = Components.classes["@mozilla.org/browser/sessionstore;1"]. - getService(Components.interfaces.nsISessionStore); - if (ss.canRestoreLastSession && - !PrivateBrowsingUtils.isWindowPrivate(window)) - doc.getElementById("launcher").setAttribute("session", "true"); - - // Inject search engine and snippets URL. - let docElt = doc.documentElement; - // set the following attributes BEFORE searchEngineURL, which triggers to - // show the snippets when it's set. - docElt.setAttribute("snippetsURL", AboutHomeUtils.snippetsURL); - if (AboutHomeUtils.showKnowYourRights) { - docElt.setAttribute("showKnowYourRights", "true"); - // Set pref to indicate we've shown the notification. - let currentVersion = Services.prefs.getIntPref("browser.rights.version"); - Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true); - } - docElt.setAttribute("snippetsVersion", AboutHomeUtils.snippetsVersion); - - function updateSearchEngine() { - let engine = AboutHomeUtils.defaultSearchEngine; - docElt.setAttribute("searchEngineName", engine.name); - docElt.setAttribute("searchEnginePostData", engine.postDataString || ""); - docElt.setAttribute("searchEngineURL", engine.searchURL); - } - updateSearchEngine(); - - // Listen for the event that's triggered when the user changes search engine. - // At this point we simply reload about:home to reflect the change. - Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false); - - // Remove the observer when the page is reloaded or closed. - doc.defaultView.addEventListener("pagehide", function removeObserver() { - doc.defaultView.removeEventListener("pagehide", removeObserver); - Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified"); - }, false); - } -} - /** * Handle command events bubbling up from error page content */ @@ -2388,8 +2343,8 @@ let BrowserOnClick = { else if (ownerDoc.documentURI.startsWith("about:neterror")) { this.onAboutNetError(originalTarget, ownerDoc); } - else if (ownerDoc.documentURI.toLowerCase() == "about:home") { - this.onAboutHome(originalTarget, ownerDoc); + else if (ownerDoc.documentURI.startsWith("about:tabcrashed")) { + this.onAboutTabCrashed(aEvent, ownerDoc); } }, @@ -2434,51 +2389,28 @@ let BrowserOnClick = { } }, + /** + * The about:tabcrashed can't do window.reload() because that + * would reload the page but not use a remote browser. + */ + onAboutTabCrashed: function(aEvent, aOwnerDoc) { + let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); + if (!isTopFrame) { + return; + } + + let button = aEvent.originalTarget; + if (button.id == "tryAgain") { + openUILinkIn(button.getAttribute("url"), "current"); + } + }, + onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) { let elmId = aTargetElm.getAttribute("id"); if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI)) return; Services.io.offline = false; }, - - onAboutHome: function BrowserOnClick_onAboutHome(aTargetElm, aOwnerDoc) { - let elmId = aTargetElm.getAttribute("id"); - - switch (elmId) { - case "restorePreviousSession": - let ss = Cc["@mozilla.org/browser/sessionstore;1"]. - getService(Ci.nsISessionStore); - if (ss.canRestoreLastSession) { - ss.restoreLastSession(); - } - aOwnerDoc.getElementById("launcher").removeAttribute("session"); - break; - - case "downloads": - BrowserDownloadsUI(); - break; - - case "bookmarks": - PlacesCommandHook.showPlacesOrganizer("AllBookmarks"); - break; - - case "history": - PlacesCommandHook.showPlacesOrganizer("History"); - break; - - case "addons": - BrowserOpenAddonsMgr(); - break; - - case "sync": - openPreferences("paneSync"); - break; - - case "settings": - openPreferences(); - break; - } - }, }; /** @@ -2532,6 +2464,16 @@ function getWebNavigation() } function BrowserReloadWithFlags(reloadFlags) { + let url = gBrowser.currentURI.spec; + if (gBrowser._updateBrowserRemoteness(gBrowser.selectedBrowser, + gBrowser._shouldBrowserBeRemote(url))) { + // If the remoteness has changed, the new browser doesn't have any + // information of what was loaded before, so we need to load the previous + // URL again. + gBrowser.loadURIWithFlags(url, reloadFlags); + return; + } + /* First, we'll try to use the session history object to reload so * that framesets are handled properly. If we're in a special * window (such as view-source) that has no session history, fall @@ -3545,10 +3487,6 @@ var XULBrowserWindow = { init: function () { this.throbberElement = document.getElementById("navigator-throbber"); - // Bug 666809 - SecurityUI support for e10s - if (gMultiProcessBrowser) - return; - // Initialize the security button's state and tooltip text. Remember to reset // _hostChanged, otherwise onSecurityChange will short circuit. var securityUI = gBrowser.securityUI; @@ -3967,26 +3905,11 @@ var XULBrowserWindow = { gURLBar.removeAttribute("level"); } - if (gMultiProcessBrowser) - return; - - // Don't pass in the actual location object, since it can cause us to - // hold on to the window object too long. Just pass in the fields we - // care about. (bug 424829) - var location = gBrowser.contentWindow.location; - var locationObj = {}; + let uri = gBrowser.currentURI; try { - // about:blank can be used by webpages so pretend it is http - locationObj.protocol = location == "about:blank" ? "http:" : location.protocol; - locationObj.host = location.host; - locationObj.hostname = location.hostname; - locationObj.port = location.port; - } catch (ex) { - // Can sometimes throw if the URL being visited has no host/hostname, - // e.g. about:blank. The _state for these pages means we won't need these - // properties anyways, though. - } - gIdentityHandler.checkIdentity(this._state, locationObj); + uri = Services.uriFixup.createExposableURI(uri); + } catch (e) {} + gIdentityHandler.checkIdentity(this._state, uri); }, // simulate all change notifications after switching tabs @@ -4175,6 +4098,7 @@ var TabsProgressListener = { Components.isSuccessCode(aStatus) && doc.documentURI.startsWith("about:") && !doc.documentURI.toLowerCase().startsWith("about:blank") && + !doc.documentURI.toLowerCase().startsWith("about:home") && !doc.documentElement.hasAttribute("hasBrowserHandlers")) { // STATE_STOP may be received twice for documents, thus store an // attribute to ensure handling it just once. @@ -4188,9 +4112,6 @@ var TabsProgressListener = { if (event.target.documentElement) event.target.documentElement.removeAttribute("hasBrowserHandlers"); }, true); - - // We also want to make changes to page UI for unprivileged about pages. - BrowserOnAboutPageLoad(doc); } }, @@ -5075,9 +4996,26 @@ function handleLinkClick(event, href, linkNode) { return true; } + var referrerURI = doc.documentURIObject; + // if the mixedContentChannel is present and the referring URI passes + // a same origin check with the target URI, we can preserve the users + // decision of disabling MCB on a page for it's child tabs. + var persistAllowMixedContentInChildTab = false; + + if (where == "tab" && gBrowser.docShell.mixedContentChannel) { + const sm = Services.scriptSecurityManager; + try { + var targetURI = makeURI(href); + sm.checkSameOriginURI(referrerURI, targetURI, false); + persistAllowMixedContentInChildTab = true; + } + catch (e) { } + } + urlSecurityCheck(href, doc.nodePrincipal); - openLinkIn(href, where, { referrerURI: doc.documentURIObject, - charset: doc.characterSet }); + openLinkIn(href, where, { referrerURI: referrerURI, + charset: doc.characterSet, + allowMixedContent: persistAllowMixedContentInChildTab }); event.preventDefault(); return true; } @@ -6220,7 +6158,7 @@ var gIdentityHandler = { // Cache the most recent SSLStatus and Location seen in checkIdentity _lastStatus : null, - _lastLocation : null, + _lastUri : null, _mode : "unknownIdentity", // smart getters @@ -6358,19 +6296,29 @@ var gIdentityHandler = { * be called by onSecurityChange * * @param PRUint32 state - * @param JS Object location that mirrors an nsLocation (i.e. has .host and - * .hostname and .port) + * @param nsIURI uri The address for which the UI should be updated. */ - checkIdentity : function(state, location) { + checkIdentity : function(state, uri) { var currentStatus = gBrowser.securityUI .QueryInterface(Components.interfaces.nsISSLStatusProvider) .SSLStatus; this._lastStatus = currentStatus; - this._lastLocation = location; + this._lastUri = uri; let nsIWebProgressListener = Ci.nsIWebProgressListener; - if (location.protocol == "chrome:" || location.protocol == "about:") { + + // For some URIs like data: we can't get a host and so can't do + // anything useful here. Chrome URIs however get special treatment. + let unknown = false; + try { + uri.host; + } catch (e) { unknown = true; } + + if ((uri.scheme == "chrome" || uri.scheme == "about") && + uri.spec !== "about:blank") { this.setMode(this.IDENTITY_MODE_CHROMEUI); + } else if (unknown) { + this.setMode(this.IDENTITY_MODE_UNKNOWN); } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) { this.setMode(this.IDENTITY_MODE_IDENTIFIED); } else if (state & nsIWebProgressListener.STATE_IS_SECURE) { @@ -6433,12 +6381,12 @@ var gIdentityHandler = { getEffectiveHost : function() { try { let baseDomain = - Services.eTLD.getBaseDomainFromHost(this._lastLocation.hostname); + Services.eTLD.getBaseDomainFromHost(this._lastUri.host); return this._IDNService.convertToDisplayIDN(baseDomain, {}); } catch (e) { - // If something goes wrong (e.g. hostname is an IP address) just fail back + // If something goes wrong (e.g. host is an IP address) just fail back // to the full domain. - return this._lastLocation.hostname; + return this._lastUri.host; } }, @@ -6507,19 +6455,17 @@ var gIdentityHandler = { tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", [iData.caOrg]); - // Check whether this site is a security exception. XPConnect does the right - // thing here in terms of converting _lastLocation.port from string to int, but - // the overrideService doesn't like undefined ports, so make sure we have - // something in the default case (bug 432241). - // .hostname can return an empty string in some exceptional cases - - // hasMatchingOverride does not handle that, so avoid calling it. - // Updating the tooltip value in those cases isn't critical. - // FIXME: Fixing bug 646690 would probably makes this check unnecessary - if (this._lastLocation.hostname && - this._overrideService.hasMatchingOverride(this._lastLocation.hostname, - (this._lastLocation.port || 443), - iData.cert, {}, {})) + // This can't throw, because URI's with a host that throw don't end up in this case. + let host = this._lastUri.host; + let port = 443; + try { + if (this._lastUri.port > 0) + port = this._lastUri.port; + } catch (e) {} + + if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {})) tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you"); + break; } case this.IDENTITY_MODE_IDENTIFIED: { // If it's identified, then we can populate the dialog with credentials diff --git a/browser/base/content/content.js b/browser/base/content/content.js index 3587bbeef0..f048b51da8 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -15,6 +15,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils", "resource://gre/modules/InsecurePasswordUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver", "resource:///modules/FormSubmitObserver.jsm"); @@ -57,6 +59,223 @@ addEventListener("blur", function(event) { LoginManagerContent.onUsernameInput(event); }); +let AboutHomeListener = { + init: function(chromeGlobal) { + let self = this; + chromeGlobal.addEventListener('AboutHomeLoad', function(e) { self.onPageLoad(); }, false, true); + }, + + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "AboutHomeLoad": + this.onPageLoad(); + break; + } + }, + + receiveMessage: function(aMessage) { + switch (aMessage.name) { + case "AboutHome:Update": + this.onUpdate(aMessage.data); + break; + } + }, + + onUpdate: function(aData) { + let doc = content.document; + if (doc.documentURI.toLowerCase() != "about:home") + return; + + if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isWindowPrivate(content)) + doc.getElementById("launcher").setAttribute("session", "true"); + + // Inject search engine and snippets URL. + let docElt = doc.documentElement; + // set the following attributes BEFORE searchEngineName, which triggers to + // show the snippets when it's set. + docElt.setAttribute("snippetsURL", aData.snippetsURL); + if (aData.showKnowYourRights) + docElt.setAttribute("showKnowYourRights", "true"); + docElt.setAttribute("snippetsVersion", aData.snippetsVersion); + docElt.setAttribute("searchEngineName", aData.defaultEngineName); + }, + + onPageLoad: function() { + let doc = content.document; + if (doc.documentURI.toLowerCase() != "about:home" || + doc.documentElement.hasAttribute("hasBrowserHandlers")) { + return; + } + + doc.documentElement.setAttribute("hasBrowserHandlers", "true"); + let updateListener = this; + addMessageListener("AboutHome:Update", updateListener); + addEventListener("click", this.onClick, true); + addEventListener("pagehide", function onPageHide(event) { + if (event.target.defaultView.frameElement) + return; + removeMessageListener("AboutHome:Update", updateListener); + removeEventListener("click", this.onClick, true); + removeEventListener("pagehide", onPageHide, true); + if (event.target.documentElement) + event.target.documentElement.removeAttribute("hasBrowserHandlers"); + }, true); + + // XXX bug 738646 - when Marketplace is launched, remove this statement and + // the hidden attribute set on the apps button in aboutHome.xhtml + if (Services.prefs.getPrefType("browser.aboutHome.apps") == Services.prefs.PREF_BOOL && + Services.prefs.getBoolPref("browser.aboutHome.apps")) + doc.getElementById("apps").removeAttribute("hidden"); + + sendAsyncMessage("AboutHome:RequestUpdate"); + + doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) { + sendAsyncMessage("AboutHome:Search", { searchData: e.detail }); + }, true, true); + }, + + onClick: function(aEvent) { + if (!aEvent.isTrusted || // Don't trust synthetic events + aEvent.button == 2 || aEvent.target.localName != "button") { + return; + } + + let originalTarget = aEvent.originalTarget; + let ownerDoc = originalTarget.ownerDocument; + let elmId = originalTarget.getAttribute("id"); + + switch (elmId) { + case "restorePreviousSession": + sendAsyncMessage("AboutHome:RestorePreviousSession"); + ownerDoc.getElementById("launcher").removeAttribute("session"); + break; + + case "downloads": + sendAsyncMessage("AboutHome:Downloads"); + break; + + case "bookmarks": + sendAsyncMessage("AboutHome:Bookmarks"); + break; + + case "history": + sendAsyncMessage("AboutHome:History"); + break; + + case "apps": + sendAsyncMessage("AboutHome:Apps"); + break; + + case "addons": + sendAsyncMessage("AboutHome:Addons"); + break; + + case "sync": + sendAsyncMessage("AboutHome:Sync"); + break; + + case "settings": + sendAsyncMessage("AboutHome:Settings"); + break; + } + }, +}; +AboutHomeListener.init(this); + +var global = this; + +let ClickEventHandler = { + init: function init() { + Cc["@mozilla.org/eventlistenerservice;1"] + .getService(Ci.nsIEventListenerService) + .addSystemEventListener(global, "click", this, true); + }, + + handleEvent: function(event) { + // Bug 903016: Most of this code is an unfortunate duplication from + // contentAreaClick in browser.js. + if (!event.isTrusted || event.defaultPrevented || event.button == 2) + return; + + let [href, node] = this._hrefAndLinkNodeForClickEvent(event); + + let json = { button: event.button, shiftKey: event.shiftKey, + ctrlKey: event.ctrlKey, metaKey: event.metaKey, + altKey: event.altKey, href: null, title: null, + bookmark: false }; + + if (href) { + json.href = href; + if (node) { + json.title = node.getAttribute("title"); + + if (event.button == 0 && !event.ctrlKey && !event.shiftKey && + !event.altKey && !event.metaKey) { + json.bookmark = node.getAttribute("rel") == "sidebar"; + if (json.bookmark) + event.preventDefault(); // Need to prevent the pageload. + } + } + + sendAsyncMessage("Content:Click", json); + return; + } + + // This might be middle mouse navigation. + if (event.button == 1) + sendAsyncMessage("Content:Click", json); + }, + + /** + * Extracts linkNode and href for the current click target. + * + * @param event + * The click event. + * @return [href, linkNode]. + * + * @note linkNode will be null if the click wasn't on an anchor + * element (or XLink). + */ + _hrefAndLinkNodeForClickEvent: function(event) { + function isHTMLLink(aNode) { + // Be consistent with what nsContextMenu.js does. + return ((aNode instanceof content.HTMLAnchorElement && aNode.href) || + (aNode instanceof content.HTMLAreaElement && aNode.href) || + aNode instanceof content.HTMLLinkElement); + } + + function makeURLAbsolute(aBase, aUrl) { + // Note: makeURI() will throw if aUri is not a valid URI + return makeURI(aUrl, null, makeURI(aBase)).spec; + } + + let node = event.target; + while (node && !isHTMLLink(node)) { + node = node.parentNode; + } + + if (node) + return [node.href, node]; + + // If there is no linkNode, try simple XLink. + let href, baseURI; + node = event.target; + while (node && !href) { + if (node.nodeType == content.Node.ELEMENT_NODE) { + href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); + if (href) + baseURI = node.baseURI; + } + node = node.parentNode; + } + + // In case of XLink, we don't return the node we got href from since + // callers expect -like elements. + return [href ? makeURLAbsolute(baseURI, href) : null, null]; + } +}; +ClickEventHandler.init(); + // Lazily load the finder code addMessageListener("Finder:Initialize", function () { let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {}); diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 8e6bc96c23..818b30523f 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -762,9 +762,27 @@ nsContextMenu.prototype = { openLinkInTab: function() { var doc = this.target.ownerDocument; urlSecurityCheck(this.linkURL, doc.nodePrincipal); + var referrerURI = doc.documentURIObject; + + // if the mixedContentChannel is present and the referring URI passes + // a same origin check with the target URI, we can preserve the users + // decision of disabling MCB on a page for it's child tabs. + var persistAllowMixedContentInChildTab = false; + + if (this.browser.docShell && this.browser.docShell.mixedContentChannel) { + const sm = Services.scriptSecurityManager; + try { + var targetURI = this.linkURI; + sm.checkSameOriginURI(referrerURI, targetURI, false); + persistAllowMixedContentInChildTab = true; + } + catch (e) { } + } + openLinkIn(this.linkURL, "tab", { charset: doc.characterSet, - referrerURI: doc.documentURIObject }); + referrerURI: referrerURI, + allowMixedContent: persistAllowMixedContentInChildTab }); }, // open URL in current tab diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index 51f7063f3f..e698e7fe1d 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -9,6 +9,11 @@ %tabBrowserDTD; ]> +# MAKE_E10S_WORK surrounds code needed to have the front-end try to be smart +# about using non-remote browsers for loading certain URIs when remote tabs +# (browser.tabs.remote) are enabled. +#define MAKE_E10S_WORK 1 + +#ifdef MAKE_E10S_WORK + + + + + + + + + + + + + + + +#endif + @@ -1351,6 +1427,7 @@ var aFromExternal; var aRelatedToCurrent; var aSkipAnimation; + var aAllowMixedContent; if (arguments.length == 2 && typeof arguments[1] == "object" && !(arguments[1] instanceof Ci.nsIURI)) { @@ -1363,6 +1440,7 @@ aFromExternal = params.fromExternal; aRelatedToCurrent = params.relatedToCurrent; aSkipAnimation = params.skipAnimation; + aAllowMixedContent = params.allowMixedContent; } // if we're adding tabs, we're past interrupt mode, ditch the owner @@ -1382,6 +1460,13 @@ t.setAttribute("validate", "never"); //PMed t.setAttribute("onerror", "this.removeAttribute('image');"); t.className = "tabbrowser-tab"; +#ifdef MAKE_E10S_WORK + let remote = this._shouldBrowserBeRemote(aURI); +#else + let remote = gMultiProcessBrowser; +#endif + if (remote) + t.setAttribute("remote", "true"); this.tabContainer._unlockTabSizing(); @@ -1416,10 +1501,8 @@ b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu")); b.setAttribute("tooltip", this.getAttribute("contenttooltip")); - if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL && - Services.prefs.getBoolPref("browser.tabs.remote")) { + if (remote) b.setAttribute("remote", "true"); - } if (window.gShowPageResizers && document.getElementById("addon-bar").collapsed && window.windowState == window.STATE_NORMAL) { @@ -1496,7 +1579,8 @@ // Do nothing if we're a private window. let docShellsSwapped = false; if (aURI == BROWSER_NEW_TAB_URL && - !PrivateBrowsingUtils.isWindowPrivate(window)) { + !PrivateBrowsingUtils.isWindowPrivate(window) && + !gMultiProcessBrowser) { docShellsSwapped = gBrowserNewTabPreloader.newTab(t); } @@ -1522,6 +1606,8 @@ } if (aFromExternal) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL; + if (aAllowMixedContent) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT; try { b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData); } catch (ex) { @@ -2619,7 +2705,17 @@ @@ -2633,7 +2729,17 @@ @@ -2979,6 +3085,7 @@ browserStack.removeChild(this.mCurrentBrowser); this.mCurrentBrowser.setAttribute("remote", true); browserStack.appendChild(this.mCurrentBrowser); + this.tabContainer.firstChild.setAttribute("remote", "true"); } this.mCurrentTab = this.tabContainer.firstChild; @@ -3182,6 +3289,22 @@ tab.setAttribute("titlechanged", "true"); ]]> + + + diff --git a/browser/base/content/test/general/browser_aboutHome.js b/browser/base/content/test/general/browser_aboutHome.js new file mode 100644 index 0000000000..430b137b8c --- /dev/null +++ b/browser/base/content/test/general/browser_aboutHome.js @@ -0,0 +1,526 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/commonjs/sdk/core/promise.js"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", + "resource:///modules/AboutHome.jsm"); + +let gRightsVersion = Services.prefs.getIntPref("browser.rights.version"); + +registerCleanupFunction(function() { + // Ensure we don't pollute prefs for next tests. + Services.prefs.clearUserPref("network.cookies.cookieBehavior"); + Services.prefs.clearUserPref("network.cookie.lifetimePolicy"); + Services.prefs.clearUserPref("browser.rights.override"); + Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown"); +}); + +let gTests = [ + +{ + desc: "Check that clearing cookies does not clear storage", + setup: function () + { + Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService) + .notifyObservers(null, "cookie-changed", "cleared"); + }, + run: function (aSnippetsMap) + { + isnot(aSnippetsMap.get("snippets-last-update"), null, + "snippets-last-update should have a value"); + } +}, + +{ + desc: "Check default snippets are shown", + setup: function () { }, + run: function () + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element") + is(snippetsElt.getElementsByTagName("span").length, 1, + "A default snippet is present."); + } +}, + +{ + desc: "Check default snippets are shown if snippets are invalid xml", + setup: function (aSnippetsMap) + { + // This must be some incorrect xhtml code. + aSnippetsMap.set("snippets", "

"); + }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.getElementsByTagName("span").length, 1, + "A default snippet is present."); + + aSnippetsMap.delete("snippets"); + } +}, + +{ + desc: "Check that search engine logo has alt text", + setup: function () { }, + run: function () + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + + let searchEngineLogoElt = doc.getElementById("searchEngineLogo"); + ok(searchEngineLogoElt, "Found search engine logo"); + + let altText = searchEngineLogoElt.alt; + ok(typeof altText == "string" && altText.length > 0, + "Search engine logo's alt text is a nonempty string"); + + isnot(altText, "undefined", + "Search engine logo's alt text shouldn't be the string 'undefined'"); + } +}, + +{ + desc: "Check that performing a search fires a search event and records to " + + "Firefox Health Report.", + setup: function () { }, + run: function () { + try { + let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); + cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider"); + } catch (ex) { + // Health Report disabled, or no SearchesProvider. + return Promise.resolve(); + } + + let numSearchesBefore = 0; + let deferred = Promise.defer(); + let doc = gBrowser.contentDocument; + let engineName = doc.documentElement.getAttribute("searchEngineName"); + + doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) { + let data = JSON.parse(e.detail); + is(data.engineName, engineName, "Detail is search engine name"); + + // We use executeSoon() to ensure that this code runs after the + // count has been updated in browser.js, since it uses the same + // event. + executeSoon(function () { + getNumberOfSearches(engineName).then(num => { + is(num, numSearchesBefore + 1, "One more search recorded."); + deferred.resolve(); + }); + }); + }, true, true); + + // Get the current number of recorded searches. + getNumberOfSearches(engineName).then(num => { + numSearchesBefore = num; + + info("Perform a search."); + doc.getElementById("searchText").value = "a search"; + doc.getElementById("searchSubmit").click(); + gBrowser.stop(); + }); + + return deferred.promise; + } +}, + +{ + desc: "Check snippets map is cleared if cached version is old", + setup: function (aSnippetsMap) + { + aSnippetsMap.set("snippets", "test"); + aSnippetsMap.set("snippets-cached-version", 0); + }, + run: function (aSnippetsMap) + { + ok(!aSnippetsMap.has("snippets"), "snippets have been properly cleared"); + ok(!aSnippetsMap.has("snippets-cached-version"), + "cached-version has been properly cleared"); + } +}, + +{ + desc: "Check cached snippets are shown if cached version is current", + setup: function (aSnippetsMap) + { + aSnippetsMap.set("snippets", "test"); + }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.innerHTML, "test", "Cached snippet is present."); + + is(aSnippetsMap.get("snippets"), "test", "snippets still cached"); + is(aSnippetsMap.get("snippets-cached-version"), + AboutHomeUtils.snippetsVersion, + "cached-version is correct"); + ok(aSnippetsMap.has("snippets-last-update"), "last-update still exists"); + } +}, + +{ + desc: "Check if the 'Know Your Rights default snippet is shown when 'browser.rights.override' pref is set", + beforeRun: function () + { + Services.prefs.setBoolPref("browser.rights.override", false); + }, + setup: function () { }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let showRights = AboutHomeUtils.showKnowYourRights; + + ok(showRights, "AboutHomeUtils.showKnowYourRights should be TRUE"); + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.getElementsByTagName("a")[0].href, "about:rights", "Snippet link is present."); + + Services.prefs.clearUserPref("browser.rights.override"); + } +}, + +{ + desc: "Check if the 'Know Your Rights default snippet is NOT shown when 'browser.rights.override' pref is NOT set", + beforeRun: function () + { + Services.prefs.setBoolPref("browser.rights.override", true); + }, + setup: function () { }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let rightsData = AboutHomeUtils.knowYourRightsData; + + ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE"); + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", "Snippet link should not point to about:rights."); + + Services.prefs.clearUserPref("browser.rights.override"); + } +}, + +{ + desc: "Check that the search UI/ action is updated when the search engine is changed", + setup: function() {}, + run: function() + { + let currEngine = Services.search.currentEngine; + let unusedEngines = [].concat(Services.search.getVisibleEngines()).filter(x => x != currEngine); + let searchbar = document.getElementById("searchbar"); + + function checkSearchUI(engine) { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let searchText = doc.getElementById("searchText"); + let logoElt = doc.getElementById("searchEngineLogo"); + let engineName = doc.documentElement.getAttribute("searchEngineName"); + + is(engineName, engine.name, "Engine name should've been updated"); + + if (!logoElt.parentNode.hidden) { + is(logoElt.alt, engineName, "Alt text of logo image should match search engine name") + } else { + is(searchText.placeholder, engineName, "Placeholder text should match search engine name"); + } + } + // Do a sanity check that all attributes are correctly set to begin with + checkSearchUI(currEngine); + + let deferred = Promise.defer(); + promiseBrowserAttributes(gBrowser.selectedTab).then(function() { + // Test if the update propagated + checkSearchUI(unusedEngines[0]); + searchbar.currentEngine = currEngine; + deferred.resolve(); + }); + + // The following cleanup function will set currentEngine back to the previous + // engine if we fail to do so above. + registerCleanupFunction(function() { + searchbar.currentEngine = currEngine; + }); + // Set the current search engine to an unused one + searchbar.currentEngine = unusedEngines[0]; + searchbar.select(); + return deferred.promise; + } +}, + +{ + desc: "Check POST search engine support", + setup: function() {}, + run: function() + { + let deferred = Promise.defer(); + let currEngine = Services.search.defaultEngine; + let searchObserver = function search_observer(aSubject, aTopic, aData) { + let engine = aSubject.QueryInterface(Ci.nsISearchEngine); + info("Observer: " + aData + " for " + engine.name); + + if (aData != "engine-added") + return; + + if (engine.name != "POST Search") + return; + + // Ready to execute the tests! + let needle = "Search for something awesome."; + let document = gBrowser.selectedTab.linkedBrowser.contentDocument; + let searchText = document.getElementById("searchText"); + + // We're about to change the search engine. Once the change has + // propagated to the about:home content, we want to perform a search. + let mutationObserver = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + if (mutation.attributeName == "searchEngineName") { + searchText.value = needle; + searchText.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); + } + } + }); + mutationObserver.observe(document.documentElement, { attributes: true }); + + // Change the search engine, triggering the observer above. + Services.search.defaultEngine = engine; + + registerCleanupFunction(function() { + mutationObserver.disconnect(); + Services.search.removeEngine(engine); + Services.search.defaultEngine = currEngine; + }); + + + // When the search results load, check them for correctness. + waitForLoad(function() { + let loadedText = gBrowser.contentDocument.body.textContent; + ok(loadedText, "search page loaded"); + is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")), + "Search text should arrive correctly"); + deferred.resolve(); + }); + }; + Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false); + registerCleanupFunction(function () { + Services.obs.removeObserver(searchObserver, "browser-search-engine-modified"); + }); + Services.search.addEngine("http://test:80/browser/browser/base/content/test/POSTSearchEngine.xml", + Ci.nsISearchEngine.DATA_XML, null, false); + return deferred.promise; + } +} + +]; + +function test() +{ + waitForExplicitFinish(); + requestLongerTimeout(2); + ignoreAllUncaughtExceptions(); + + Task.spawn(function () { + for (let test of gTests) { + info(test.desc); + + if (test.beforeRun) + yield test.beforeRun(); + + let tab = yield promiseNewTabLoadEvent("about:home", "DOMContentLoaded"); + + // Must wait for both the snippets map and the browser attributes, since + // can't guess the order they will happen. + // So, start listening now, but verify the promise is fulfilled only + // after the snippets map setup. + let promise = promiseBrowserAttributes(tab); + // Prepare the snippets map with default values, then run the test setup. + let snippetsMap = yield promiseSetupSnippetsMap(tab, test.setup); + // Ensure browser has set attributes already, or wait for them. + yield promise; + info("Running test"); + yield test.run(snippetsMap); + info("Cleanup"); + gBrowser.removeCurrentTab(); + } + }).then(finish, ex => { + ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} + +/** + * Creates a new tab and waits for a load event. + * + * @param aUrl + * The url to load in a new tab. + * @param aEvent + * The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. Gets the new tab. + */ +function promiseNewTabLoadEvent(aUrl, aEventType="load") +{ + let deferred = Promise.defer(); + let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl); + info("Wait tab event: " + aEventType); + tab.linkedBrowser.addEventListener(aEventType, function load(event) { + if (event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank") { + info("skipping spurious load event"); + return; + } + tab.linkedBrowser.removeEventListener(aEventType, load, true); + info("Tab event received: " + aEventType); + deferred.resolve(tab); + }, true); + return deferred.promise; +} + +/** + * Cleans up snippets and ensures that by default we don't try to check for + * remote snippets since that may cause network bustage or slowness. + * + * @param aTab + * The tab containing about:home. + * @param aSetupFn + * The setup function to be run. + * @return {Promise} resolved when the snippets are ready. Gets the snippets map. + */ +function promiseSetupSnippetsMap(aTab, aSetupFn) +{ + let deferred = Promise.defer(); + let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject; + info("Waiting for snippets map"); + cw.ensureSnippetsMapThen(function (aSnippetsMap) { + info("Got snippets map: " + + "{ last-update: " + aSnippetsMap.get("snippets-last-update") + + ", cached-version: " + aSnippetsMap.get("snippets-cached-version") + + " }"); + // Don't try to update. + aSnippetsMap.set("snippets-last-update", Date.now()); + aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion); + // Clear snippets. + aSnippetsMap.delete("snippets"); + aSetupFn(aSnippetsMap); + // Must be sure to continue after the page snippets map setup. + executeSoon(function() deferred.resolve(aSnippetsMap)); + }); + return deferred.promise; +} + +/** + * Waits for the attributes being set by browser.js and overwrites snippetsURL + * to ensure we won't try to hit the network and we can force xhr to throw. + * + * @param aTab + * The tab containing about:home. + * @return {Promise} resolved when the attributes are ready. + */ +function promiseBrowserAttributes(aTab) +{ + let deferred = Promise.defer(); + + let docElt = aTab.linkedBrowser.contentDocument.documentElement; + //docElt.setAttribute("snippetsURL", "nonexistent://test"); + let observer = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + info("Got attribute mutation: " + mutation.attributeName + + " from " + mutation.oldValue); + if (mutation.attributeName == "snippetsURL" && + docElt.getAttribute("snippetsURL") != "nonexistent://test") { + docElt.setAttribute("snippetsURL", "nonexistent://test"); + } + + // Now we just have to wait for the last attribute. + if (mutation.attributeName == "searchEngineName") { + info("Remove attributes observer"); + observer.disconnect(); + // Must be sure to continue after the page mutation observer. + executeSoon(function() deferred.resolve()); + break; + } + } + }); + info("Add attributes observer"); + observer.observe(docElt, { attributes: true }); + + return deferred.promise; +} + +/** + * Retrieves the number of about:home searches recorded for the current day. + * + * @param aEngineName + * name of the setup search engine. + * + * @return {Promise} Returns a promise resolving to the number of searches. + */ +function getNumberOfSearches(aEngineName) { + let reporter = Components.classes["@mozilla.org/datareporting/service;1"] + .getService() + .wrappedJSObject + .healthReporter; + ok(reporter, "Health Reporter instance available."); + + return reporter.onInit().then(function onInit() { + let provider = reporter.getProvider("org.mozilla.searches"); + ok(provider, "Searches provider is available."); + + let m = provider.getMeasurement("counts", 2); + return m.getValues().then(data => { + let now = new Date(); + let yday = new Date(now); + yday.setDate(yday.getDate() - 1); + + // Add the number of searches recorded yesterday to the number of searches + // recorded today. This makes the test not fail intermittently when it is + // run at midnight and we accidentally compare the number of searches from + // different days. Tests are always run with an empty profile so there + // are no searches from yesterday, normally. Should the test happen to run + // past midnight we make sure to count them in as well. + return getNumberOfSearchesByDate(aEngineName, data, now) + + getNumberOfSearchesByDate(aEngineName, data, yday); + }); + }); +} + +function getNumberOfSearchesByDate(aEngineName, aData, aDate) { + if (aData.days.hasDay(aDate)) { + let id = Services.search.getEngineByName(aEngineName).identifier; + + let day = aData.days.getDay(aDate); + let field = id + ".abouthome"; + + if (day.has(field)) { + return day.get(field) || 0; + } + } + + return 0; // No records found. +} + +function waitForLoad(cb) { + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function listener() { + if (browser.currentURI.spec == "about:blank") + return; + info("Page loaded: " + browser.currentURI.spec); + browser.removeEventListener("load", listener, true); + + cb(); + }, true); +} diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index b1e78d6a95..79b020af89 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -215,6 +215,7 @@ function openLinkIn(url, where, params) { var aCharset = params.charset; var aReferrerURI = params.referrerURI; var aRelatedToCurrent = params.relatedToCurrent; + var aAllowMixedContent = params.allowMixedContent; var aInBackground = params.inBackground; var aDisallowInheritPrincipal = params.disallowInheritPrincipal; var aInitiatingDoc = params.initiatingDoc; @@ -328,7 +329,8 @@ function openLinkIn(url, where, params) { postData: aPostData, inBackground: loadInBackground, allowThirdPartyFixup: aAllowThirdPartyFixup, - relatedToCurrent: aRelatedToCurrent}); + relatedToCurrent: aRelatedToCurrent, + allowMixedContent: aAllowMixedContent}); break; } diff --git a/browser/base/jar.mn b/browser/base/jar.mn index 9031f3beb4..b9fc3b4063 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -47,6 +47,7 @@ browser.jar: content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png) content/browser/autorecovery.js (content/autorecovery.js) content/browser/autorecovery.xul (content/autorecovery.xul) + content/browser/aboutTabCrashed.xhtml (content/aboutTabCrashed.xhtml) * content/browser/browser.css (content/browser.css) content/browser/browser-menudragging.xul (content/browser-menudragging.xul) content/browser/browser-menudragging.js (content/browser-menudragging.js) diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index d927b79363..8c2928c58e 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -37,6 +37,10 @@ static RedirEntry kRedirMap[] = { nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT }, + { "tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT }, { "feeds", "chrome://browser/content/feeds/subscribe.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | diff --git a/browser/components/build/nsModule.cpp b/browser/components/build/nsModule.cpp index d5b79b455d..971baafac8 100644 --- a/browser/components/build/nsModule.cpp +++ b/browser/components/build/nsModule.cpp @@ -87,6 +87,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = { { NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "tabcrashed", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index b337640505..982e3a3506 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -13,9 +13,15 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AboutHome", + "resource:///modules/AboutHome.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ContentClick", + "resource:///modules/ContentClick.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); @@ -501,6 +507,7 @@ BrowserGlue.prototype = { NewTabUtils.init(); BrowserNewTabPreloader.init(); webrtcUI.init(); + AboutHome.init(); FormValidationHandler.init(); LoginManagerParent.init(); @@ -510,6 +517,9 @@ BrowserGlue.prototype = { Services.prefs.setBoolPref('media.mediasource.webm.enabled', false); } + if (Services.prefs.getBoolPref("browser.tabs.remote")) + ContentClick.init(); + Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); AddonWatcher.init(this._notifySlowAddon); diff --git a/browser/components/preferences/aboutPermissions.js b/browser/components/preferences/aboutPermissions.js index 1da646c396..0f4d03b7a4 100644 --- a/browser/components/preferences/aboutPermissions.js +++ b/browser/components/preferences/aboutPermissions.js @@ -534,6 +534,7 @@ let AboutPermissions = { Services.prefs.addObserver("dom.webnotifications.enabled", this, false); Services.prefs.addObserver("xpinstall.whitelist.required", this, false); Services.prefs.addObserver("geo.enabled", this, false); + Services.prefs.addObserver("dom.push.enabled", this, false); Services.prefs.addObserver("dom.indexedDB.enabled", this, false); Services.prefs.addObserver("plugins.click_to_play", this, false); Services.prefs.addObserver("full-screen-api.enabled", this, false); @@ -687,6 +688,7 @@ let AboutPermissions = { Services.prefs.removeObserver("dom.webnotifications.enabled", this, false); Services.prefs.removeObserver("xpinstall.whitelist.required", this, false); Services.prefs.removeObserver("geo.enabled", this, false); + Services.prefs.removeObserver("dom.push.enabled", this, false); Services.prefs.removeObserver("dom.indexedDB.enabled", this, false); Services.prefs.removeObserver("plugins.click_to_play", this, false); Services.prefs.removeObserver("full-screen-api.enabled", this, false); diff --git a/browser/components/preferences/tests/browser_permissions.js b/browser/components/preferences/tests/browser_permissions.js new file mode 100644 index 0000000000..2a896b3e0d --- /dev/null +++ b/browser/components/preferences/tests/browser_permissions.js @@ -0,0 +1,331 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +const ABOUT_PERMISSIONS_SPEC = "about:permissions"; + +const TEST_URI_1 = NetUtil.newURI("http://mozilla.com/"); +const TEST_URI_2 = NetUtil.newURI("http://mozilla.org/"); + +const TEST_PRINCIPAL_1 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(TEST_URI_1); +const TEST_PRINCIPAL_2 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(TEST_URI_2); + +// values from DefaultPermissions object +const PERM_UNKNOWN = 0; +const PERM_ALLOW = 1; +const PERM_DENY = 2; +// cookie specific permissions +const PERM_FIRST_PARTY_ONLY = 9; + +// used to set permissions on test sites +const TEST_PERMS = { + "password": PERM_ALLOW, + "cookie": PERM_ALLOW, + "geo": PERM_UNKNOWN, + "push": PERM_DENY, + "indexedDB": PERM_UNKNOWN, + "popup": PERM_DENY, + "fullscreen" : PERM_UNKNOWN, + "camera": PERM_UNKNOWN, + "microphone": PERM_UNKNOWN +}; + +const NO_GLOBAL_ALLOW = [ + "geo", + "indexedDB", + "fullscreen" +]; + +// number of managed permissions in the interface +const TEST_PERMS_COUNT = 9; + +function test() { + waitForExplicitFinish(); + registerCleanupFunction(cleanUp); + + // add test history visit + PlacesTestUtils.addVisits(TEST_URI_1).then(() => { + // set permissions ourselves to avoid problems with different defaults + // from test harness configuration + for (let type in TEST_PERMS) { + if (type == "password") { + Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true); + } else { + // set permissions on a site without history visits to test enumerateServices + Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, type, TEST_PERMS[type]); + } + } + + // open about:permissions + gBrowser.selectedTab = gBrowser.addTab("about:permissions"); + }); + + function observer() { + Services.obs.removeObserver(observer, "browser-permissions-initialized"); + runNextTest(); + } + Services.obs.addObserver(observer, "browser-permissions-initialized", false); +} + +function cleanUp() { + for (let type in TEST_PERMS) { + if (type != "password") { + Services.perms.removeFromPrincipal(TEST_PRINCIPAL_1, type); + Services.perms.removeFromPrincipal(TEST_PRINCIPAL_2, type); + } + } + + gBrowser.removeTab(gBrowser.selectedTab); +} + +function runNextTest() { + if (gTestIndex == tests.length) { + PlacesTestUtils.clearHistory().then(finish); + return; + } + + let nextTest = tests[gTestIndex++]; + info("[" + nextTest.name + "] running test"); + nextTest(); +} + +var gSitesList; +var gHeaderDeck; +var gSiteLabel; + +var gTestIndex = 0; +var tests = [ + function test_page_load() { + is(gBrowser.currentURI.spec, ABOUT_PERMISSIONS_SPEC, "about:permissions loaded"); + + gSitesList = gBrowser.contentDocument.getElementById("sites-list"); + ok(gSitesList, "got sites list"); + + gHeaderDeck = gBrowser.contentDocument.getElementById("header-deck"); + ok(gHeaderDeck, "got header deck"); + + gSiteLabel = gBrowser.contentDocument.getElementById("site-label"); + ok(gSiteLabel, "got site label"); + + runNextTest(); + }, + + function test_sites_list() { + is(gSitesList.firstChild.id, "all-sites-item", + "all sites is the first item in the sites list"); + + ok(getSiteItem(TEST_URI_1.host), "site item from places db exists"); + ok(getSiteItem(TEST_URI_2.host), "site item from enumerating services exists"); + + runNextTest(); + }, + + function test_filter_sites_list() { + // set filter to test host + let sitesFilter = gBrowser.contentDocument.getElementById("sites-filter"); + sitesFilter.value = TEST_URI_1.host; + sitesFilter.doCommand(); + + // make sure correct sites are collapsed/showing + let testSite1 = getSiteItem(TEST_URI_1.host); + ok(!testSite1.collapsed, "test site 1 is not collapsed"); + let testSite2 = getSiteItem(TEST_URI_2.host); + ok(testSite2.collapsed, "test site 2 is collapsed"); + + // clear filter + sitesFilter.value = ""; + sitesFilter.doCommand(); + + runNextTest(); + }, + + function test_all_sites() { + // "All Sites" item should be selected when the page is first loaded + is(gSitesList.selectedItem, gBrowser.contentDocument.getElementById("all-sites-item"), + "all sites item is selected"); + + let defaultsHeader = gBrowser.contentDocument.getElementById("defaults-header"); + is(defaultsHeader, gHeaderDeck.selectedPanel, + "correct header shown for all sites"); + + ok(gBrowser.contentDocument.getElementById("passwords-count").hidden, + "passwords count is hidden"); + ok(gBrowser.contentDocument.getElementById("cookies-count").hidden, + "cookies count is hidden"); + + // Test to make sure "Allow" items hidden for certain permission types + NO_GLOBAL_ALLOW.forEach(function(aType) { + let menuitem = gBrowser.contentDocument.getElementById(aType + "-" + PERM_ALLOW); + ok(menuitem.hidden, aType + " allow menuitem hidden for all sites"); + }); + + runNextTest(); + }, + + function test_all_sites_permission() { + // apply the old default of allowing all cookies + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + + // there should be no user-set pref for cookie behavior + is(Services.prefs.getIntPref("network.cookie.cookieBehavior"), PERM_UNKNOWN, + "network.cookie.cookieBehavior is expected default"); + + // the default behavior is to allow cookies + let cookieMenulist = getPermissionMenulist("cookie"); + is(cookieMenulist.value, PERM_ALLOW, + "menulist correctly shows that cookies are allowed"); + + // set the pref to block cookies + Services.prefs.setIntPref("network.cookie.cookieBehavior", PERM_DENY); + // check to make sure this change is reflected in the UI + is(cookieMenulist.value, PERM_DENY, "menulist correctly shows that cookies are blocked"); + + // clear the pref + Services.prefs.clearUserPref("network.cookie.cookieBehavior"); + + runNextTest(); + }, + + function test_manage_all_passwords() { + // make sure "Manage All Passwords..." button opens the correct dialog + addWindowListener("chrome://passwordmgr/content/passwordManager.xul", runNextTest); + gBrowser.contentDocument.getElementById("passwords-manage-all-button").doCommand(); + + }, + + function test_manage_all_cookies() { + // make sure "Manage All Cookies..." button opens the correct dialog + addWindowListener("chrome://browser/content/preferences/cookies.xul", runNextTest); + gBrowser.contentDocument.getElementById("cookies-manage-all-button").doCommand(); + }, + + function test_select_site() { + // select the site that has the permissions we set at the beginning of the test + let testSiteItem = getSiteItem(TEST_URI_2.host); + gSitesList.selectedItem = testSiteItem; + + let siteHeader = gBrowser.contentDocument.getElementById("site-header"); + is(siteHeader, gHeaderDeck.selectedPanel, + "correct header shown for a specific site"); + is(gSiteLabel.value, TEST_URI_2.host, "header updated for selected site"); + + ok(!gBrowser.contentDocument.getElementById("passwords-count").hidden, + "passwords count is not hidden"); + ok(!gBrowser.contentDocument.getElementById("cookies-count").hidden, + "cookies count is not hidden"); + + // Test to make sure "Allow" items are *not* hidden for certain permission types + NO_GLOBAL_ALLOW.forEach(function(aType) { + let menuitem = gBrowser.contentDocument.getElementById(aType + "-" + PERM_ALLOW); + ok(!menuitem.hidden, aType + " allow menuitem not hidden for single site"); + }); + + runNextTest(); + }, + + function test_permissions() { + let menulists = gBrowser.contentDocument.getElementsByClassName("pref-menulist"); + is(menulists.length, TEST_PERMS_COUNT, "got expected number of managed permissions"); + + for (let i = 0; i < menulists.length; i++) { + let permissionMenulist = menulists.item(i); + let permissionType = permissionMenulist.getAttribute("type"); + + // permissions should reflect what we set at the beginning of the test + is(permissionMenulist.value, TEST_PERMS[permissionType], + "got expected value for " + permissionType + " permission"); + } + + runNextTest(); + }, + + function test_permission_change() { + let geoMenulist = getPermissionMenulist("geo"); + is(geoMenulist.value, PERM_UNKNOWN, "menulist correctly shows that geolocation permission is unspecified"); + + // change a permission programatically + Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, "geo", PERM_DENY); + // check to make sure this change is reflected in the UI + is(geoMenulist.value, PERM_DENY, "menulist shows that geolocation is blocked"); + + // change a permisssion in the UI + let geoAllowItem = gBrowser.contentDocument.getElementById("geo-" + PERM_ALLOW); + geoMenulist.selectedItem = geoAllowItem; + geoMenulist.doCommand(); + // check to make sure this change is reflected in the permission manager + is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, "geo"), PERM_ALLOW, + "permission manager shows that geolocation is allowed"); + + + // change a site-specific cookie permission, just for fun + let cookieMenuList = getPermissionMenulist("cookie"); + let cookieItem = gBrowser.contentDocument.getElementById("cookie-" + PERM_FIRST_PARTY_ONLY); + cookieMenuList.selectedItem = cookieItem; + cookieMenuList.doCommand(); + is(cookieMenuList.value, PERM_FIRST_PARTY_ONLY, "menulist correctly shows that " + + "first party only cookies are allowed"); + is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, "cookie"), + PERM_FIRST_PARTY_ONLY, "permission manager shows that first party cookies " + + "are allowed"); + + runNextTest(); + }, + + function test_forget_site() { + // click "Forget About This Site" button + gBrowser.contentDocument.getElementById("forget-site-button").doCommand(); + PlacesTestUtils.clearHistory().then(() => { + is(gSiteLabel.value, "", "site label cleared"); + + let allSitesItem = gBrowser.contentDocument.getElementById("all-sites-item"); + is(gSitesList.selectedItem, allSitesItem, + "all sites item selected after forgetting selected site"); + + // check to make sure site is gone from sites list + let testSiteItem = getSiteItem(TEST_URI_2.host); + ok(!testSiteItem, "site removed from sites list"); + + // check to make sure we forgot all permissions corresponding to site + for (let type in TEST_PERMS) { + if (type == "password") { + ok(Services.logins.getLoginSavingEnabled(TEST_URI_2.prePath), + "password saving should be enabled by default"); + } else { + is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, type), PERM_UNKNOWN, + type + " permission should not be set for test site 2"); + } + } + + runNextTest(); + }); + } +]; + +function getPermissionMenulist(aType) { + return gBrowser.contentDocument.getElementById(aType + "-menulist"); +} + +function getSiteItem(aHost) { + return gBrowser.contentDocument. + querySelector(".site[value='" + aHost + "']"); +} + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow: function(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + domwindow.close(); + aCallback(); + }, domwindow); + }, + onCloseWindow: function(aXULWindow) { }, + onWindowTitleChange: function(aXULWindow, aNewTitle) { } + }); +} diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index 0225f422bd..d9b08d35c4 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -656,3 +656,7 @@ just addresses the organization to follow, e.g. "This site is run by " --> + + + + diff --git a/browser/modules/AboutHome.jsm b/browser/modules/AboutHome.jsm new file mode 100644 index 0000000000..35684dedf9 --- /dev/null +++ b/browser/modules/AboutHome.jsm @@ -0,0 +1,210 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let Cc = Components.classes; +let Ci = Components.interfaces; +let Cu = Components.utils; + +this.EXPORTED_SYMBOLS = [ "AboutHomeUtils", "AboutHome" ]; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); + +// Url to fetch snippets, in the urlFormatter service format. +const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl"; + +// Should be bumped up if the snippets content format changes. +const STARTPAGE_VERSION = 4; + +this.AboutHomeUtils = { + get snippetsVersion() STARTPAGE_VERSION, + + /* + * showKnowYourRights - Determines if the user should be shown the + * about:rights notification. The notification should *not* be shown if + * we've already shown the current version, or if the override pref says to + * never show it. The notification *should* be shown if it's never been seen + * before, if a newer version is available, or if the override pref says to + * always show it. + */ + get showKnowYourRights() { + // Look for an unconditional override pref. If set, do what it says. + // (true --> never show, false --> always show) + try { + return !Services.prefs.getBoolPref("browser.rights.override"); + } catch (e) { } + // Ditto, for the legacy EULA pref. + try { + return !Services.prefs.getBoolPref("browser.EULA.override"); + } catch (e) { } + +#ifndef MOZILLA_OFFICIAL + // Non-official builds shouldn't show the notification. + return false; +#endif + + // Look to see if the user has seen the current version or not. + var currentVersion = Services.prefs.getIntPref("browser.rights.version"); + try { + return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown"); + } catch (e) { } + + // Legacy: If the user accepted a EULA, we won't annoy them with the + // equivalent about:rights page until the version changes. + try { + return !Services.prefs.getBoolPref("browser.EULA." + currentVersion + ".accepted"); + } catch (e) { } + + // We haven't shown the notification before, so do so now. + return true; + } +}; + +/** + * Returns the URL to fetch snippets from, in the urlFormatter service format. + */ +XPCOMUtils.defineLazyGetter(AboutHomeUtils, "snippetsURL", function() { + let updateURL = Services.prefs + .getCharPref(SNIPPETS_URL_PREF) + .replace("%STARTPAGE_VERSION%", STARTPAGE_VERSION); + return Services.urlFormatter.formatURL(updateURL); +}); + +/** + * This code provides services to the about:home page. Whenever + * about:home needs to do something chrome-privileged, it sends a + * message that's handled here. + */ +let AboutHome = { + MESSAGES: [ + "AboutHome:RestorePreviousSession", + "AboutHome:Downloads", + "AboutHome:Bookmarks", + "AboutHome:History", + "AboutHome:Apps", + "AboutHome:Addons", + "AboutHome:Sync", + "AboutHome:Settings", + "AboutHome:RequestUpdate", + "AboutHome:Search", + ], + + init: function() { + let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); + + for (let msg of this.MESSAGES) { + mm.addMessageListener(msg, this); + } + + Services.obs.addObserver(this, "browser-search-engine-modified", false); + }, + + observe: function(aEngine, aTopic, aVerb) { + switch (aTopic) { + case "browser-search-engine-modified": + this.sendAboutHomeData(null); + break; + } + }, + + receiveMessage: function(aMessage) { + let window = aMessage.target.ownerDocument.defaultView; + + switch (aMessage.name) { + case "AboutHome:RestorePreviousSession": + let ss = Cc["@mozilla.org/browser/sessionstore;1"]. + getService(Ci.nsISessionStore); + if (ss.canRestoreLastSession) { + ss.restoreLastSession(); + } + break; + + case "AboutHome:Downloads": + window.BrowserDownloadsUI(); + break; + + case "AboutHome:Bookmarks": + window.PlacesCommandHook.showPlacesOrganizer("AllBookmarks"); + break; + + case "AboutHome:History": + window.PlacesCommandHook.showPlacesOrganizer("History"); + break; + + case "AboutHome:Apps": + window.openUILinkIn("https://marketplace.mozilla.org/", "tab"); + break; + + case "AboutHome:Addons": + window.BrowserOpenAddonsMgr(); + break; + + case "AboutHome:Sync": + window.openPreferences("paneSync"); + break; + + case "AboutHome:Settings": + window.openPreferences(); + break; + + case "AboutHome:RequestUpdate": + this.sendAboutHomeData(aMessage.target); + break; + + case "AboutHome:Search": + let data; + try { + data = JSON.parse(aMessage.data.searchData); + } catch(ex) { + Cu.reportError(ex); + break; + } +#ifdef MOZ_SERVICES_HEALTHREPORT + window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome"); +#endif + // Trigger a search through nsISearchEngine.getSubmission() + let submission = Services.search.currentEngine.getSubmission(data.searchTerms); + window.loadURI(submission.uri.spec, null, submission.postData); + break; + } + }, + + // Send all the chrome-privileged data needed by about:home. This + // gets re-sent when the search engine changes. + sendAboutHomeData: function(target) { + let wrapper = {}; + Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm", + wrapper); + let ss = wrapper.SessionStore; + ss.promiseInitialized.then(function() { + let data = { + showRestoreLastSession: ss.canRestoreLastSession, + snippetsURL: AboutHomeUtils.snippetsURL, + showKnowYourRights: AboutHomeUtils.showKnowYourRights, + snippetsVersion: AboutHomeUtils.snippetsVersion, + defaultEngineName: Services.search.defaultEngine.name + }; + + if (AboutHomeUtils.showKnowYourRights) { + // Set pref to indicate we've shown the notification. + let currentVersion = Services.prefs.getIntPref("browser.rights.version"); + Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true); + } + + if (target) { + target.messageManager.sendAsyncMessage("AboutHome:Update", data); + } else { + let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); + mm.broadcastAsyncMessage("AboutHome:Update", data); + } + }).then(null, function onError(x) { + Cu.reportError("Error in AboutHome.sendAboutHomeData " + x); + }); + }, +}; diff --git a/browser/modules/ContentClick.jsm b/browser/modules/ContentClick.jsm new file mode 100644 index 0000000000..d09affd8f5 --- /dev/null +++ b/browser/modules/ContentClick.jsm @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let Cc = Components.classes; +let Ci = Components.interfaces; +let Cu = Components.utils; + +this.EXPORTED_SYMBOLS = [ "ContentClick" ]; + +Cu.import("resource:///modules/PlacesUIUtils.jsm"); +Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +let ContentClick = { + init: function() { + let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); + mm.addMessageListener("Content:Click", this); + }, + + receiveMessage: function (message) { + switch (message.name) { + case "Content:Click": + this.contentAreaClick(message.json, message.target) + break; + } + }, + + contentAreaClick: function (json, browser) { + // This is heavily based on contentAreaClick from browser.js (Bug 903016) + // The json is set up in a way to look like an Event. + let window = browser.ownerDocument.defaultView; + + if (!json.href) { + // Might be middle mouse navigation. + if (Services.prefs.getBoolPref("middlemouse.contentLoadURL") && + !Services.prefs.getBoolPref("general.autoScroll")) { + window.middleMousePaste(json); + } + return; + } + + if (json.bookmark) { + // This is the Opera convention for a special link that, when clicked, + // allows to add a sidebar panel. The link's title attribute contains + // the title that should be used for the sidebar panel. + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: Services.io.newURI(json.href, null, null) + , title: json.title + , loadBookmarkInSidebar: true + , hiddenRows: [ "description" + , "location" + , "keyword" ] + }, window); + return; + } + + // Note: We don't need the sidebar code here. + + // This part is based on handleLinkClick. + var where = window.whereToOpenLink(json); + if (where == "current") + return false; + + // Todo(903022): code for where == save + + window.openLinkIn(json.href, where, { referrerURI: browser.documentURI, + charset: browser.characterSet }); + + // Mark the page as a user followed link. This is done so that history can + // distinguish automatic embed visits from user activated ones. For example + // pages loaded in frames are embed visits and lost with the session, while + // visits across frames should be preserved. + try { + if (!PrivateBrowsingUtils.isWindowPrivate(window)) + PlacesUIUtils.markPageAsFollowedLink(href); + } catch (ex) { /* Skip invalid URIs. */ } + } +}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build index b3459bcd5a..9e108ed7b9 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [ 'promise.js' ] EXTRA_JS_MODULES += [ 'BrowserNewTabPreloader.jsm', 'CharsetMenu.jsm', + 'ContentClick.jsm', 'FormSubmitObserver.jsm', 'FormValidationHandler.jsm', 'NetworkPrioritizer.jsm', @@ -31,9 +32,9 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': ] EXTRA_PP_JS_MODULES += [ - 'AboutHomeUtils.jsm', + 'AboutHome.jsm', 'RecentWindow.jsm', ] if CONFIG['MOZILLA_OFFICIAL']: - DEFINES['MOZILLA_OFFICIAL'] = 1 \ No newline at end of file + DEFINES['MOZILLA_OFFICIAL'] = 1 diff --git a/browser/themes/linux/aboutTabCrashed.css b/browser/themes/linux/aboutTabCrashed.css new file mode 100644 index 0000000000..d1a9ad53c9 --- /dev/null +++ b/browser/themes/linux/aboutTabCrashed.css @@ -0,0 +1,98 @@ +body { + background-color: rgb(241, 244, 248); + margin-top: 2em; + font: message-box; + font-size: 100%; +} + +p { + font-size: .8em; +} + +#error-box { + background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px; + -moz-padding-start: 30px; +} + +#error-box:-moz-locale-dir(rtl) { + background-position: right 4px; +} + +#main-error-msg { + color: #4b4b4b; + font-weight: bold; +} + + +#button-box { + text-align: center; + width: 75%; + margin: 0 auto; +} + +@media all and (min-width: 300px) { + #error-box { + max-width: 50%; + margin: 0 auto; + background-image: url('chrome://global/skin/icons/information-32.png'); + min-height: 36px; + -moz-padding-start: 38px; + } + + button { + width: auto !important; + min-width: 150px; + } +} + +@media all and (min-width: 780px) { + #error-box { + max-width: 30%; + } +} + +button { + font: message-box; + font-size: 0.6875em; + -moz-appearance: none; + -moz-user-select: none; + width: 100%; + margin: 2px 0; + padding: 2px 6px; + line-height: 1.2; + background-color: hsla(210,30%,95%,.1); + background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)); + background-clip: padding-box; + border: 1px solid hsla(210,15%,25%,.4); + border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4); + border-radius: 3px; + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1); + + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; + +} + +button:hover { + background-color: hsla(210,30%,95%,.8); + border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55); + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1), + 0 0 3px hsla(210,15%,25%,.1); + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} + +button:hover:active { + background-color: hsla(210,15%,25%,.2); + box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset, + 0 0 2px hsla(210,15%,25%,.4) inset; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css index d3d8f0d627..b0f3b7fea2 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -1478,6 +1478,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action- color: -moz-dialogtext; } +.tabbrowser-tab[remote] { + text-decoration: underline; +} + #main-window[tabsontop=false]:not([disablechrome]) .tabbrowser-tab[selected=true]:not(:-moz-lwtheme) { background-image: linear-gradient(to top, rgba(0,0,0,.3) 1px, transparent 1px), linear-gradient(@selectedTabHighlight@, @toolbarHighlight@ 32%), diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index fa0a5c7e2a..e97c1f866b 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -16,6 +16,7 @@ browser.jar: #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif + skin/classic/browser/aboutTabCrashed.css skin/classic/browser/actionicon-tab.png * skin/classic/browser/browser.css skin/classic/browser/click-to-play-warning-stripes.png diff --git a/browser/themes/osx/aboutTabCrashed.css b/browser/themes/osx/aboutTabCrashed.css new file mode 100644 index 0000000000..d1a9ad53c9 --- /dev/null +++ b/browser/themes/osx/aboutTabCrashed.css @@ -0,0 +1,98 @@ +body { + background-color: rgb(241, 244, 248); + margin-top: 2em; + font: message-box; + font-size: 100%; +} + +p { + font-size: .8em; +} + +#error-box { + background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px; + -moz-padding-start: 30px; +} + +#error-box:-moz-locale-dir(rtl) { + background-position: right 4px; +} + +#main-error-msg { + color: #4b4b4b; + font-weight: bold; +} + + +#button-box { + text-align: center; + width: 75%; + margin: 0 auto; +} + +@media all and (min-width: 300px) { + #error-box { + max-width: 50%; + margin: 0 auto; + background-image: url('chrome://global/skin/icons/information-32.png'); + min-height: 36px; + -moz-padding-start: 38px; + } + + button { + width: auto !important; + min-width: 150px; + } +} + +@media all and (min-width: 780px) { + #error-box { + max-width: 30%; + } +} + +button { + font: message-box; + font-size: 0.6875em; + -moz-appearance: none; + -moz-user-select: none; + width: 100%; + margin: 2px 0; + padding: 2px 6px; + line-height: 1.2; + background-color: hsla(210,30%,95%,.1); + background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)); + background-clip: padding-box; + border: 1px solid hsla(210,15%,25%,.4); + border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4); + border-radius: 3px; + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1); + + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; + +} + +button:hover { + background-color: hsla(210,30%,95%,.8); + border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55); + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1), + 0 0 3px hsla(210,15%,25%,.1); + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} + +button:hover:active { + background-color: hsla(210,15%,25%,.2); + box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset, + 0 0 2px hsla(210,15%,25%,.4) inset; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index e8a5f603b3..bad93672b1 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -1458,6 +1458,10 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url- min-width: 102px; } +.tabbrowser-tab[remote] { + text-decoration: underline; +} + /* When the tabs are on top and the window is maximized or in full- screen mode, unhide the transparent top border of the tabs so we have a 1px gap between the tabs and the top edge of the screen */ diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 84c720d05d..b8e866db4e 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -15,6 +15,7 @@ browser.jar: #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif + skin/classic/browser/aboutTabCrashed.css skin/classic/browser/actionicon-tab.png skin/classic/browser/appmenu-icons.png skin/classic/browser/appmenu-dropmarker.png diff --git a/browser/themes/windows/aboutTabCrashed.css b/browser/themes/windows/aboutTabCrashed.css new file mode 100644 index 0000000000..d1a9ad53c9 --- /dev/null +++ b/browser/themes/windows/aboutTabCrashed.css @@ -0,0 +1,98 @@ +body { + background-color: rgb(241, 244, 248); + margin-top: 2em; + font: message-box; + font-size: 100%; +} + +p { + font-size: .8em; +} + +#error-box { + background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px; + -moz-padding-start: 30px; +} + +#error-box:-moz-locale-dir(rtl) { + background-position: right 4px; +} + +#main-error-msg { + color: #4b4b4b; + font-weight: bold; +} + + +#button-box { + text-align: center; + width: 75%; + margin: 0 auto; +} + +@media all and (min-width: 300px) { + #error-box { + max-width: 50%; + margin: 0 auto; + background-image: url('chrome://global/skin/icons/information-32.png'); + min-height: 36px; + -moz-padding-start: 38px; + } + + button { + width: auto !important; + min-width: 150px; + } +} + +@media all and (min-width: 780px) { + #error-box { + max-width: 30%; + } +} + +button { + font: message-box; + font-size: 0.6875em; + -moz-appearance: none; + -moz-user-select: none; + width: 100%; + margin: 2px 0; + padding: 2px 6px; + line-height: 1.2; + background-color: hsla(210,30%,95%,.1); + background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)); + background-clip: padding-box; + border: 1px solid hsla(210,15%,25%,.4); + border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4); + border-radius: 3px; + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1); + + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; + +} + +button:hover { + background-color: hsla(210,30%,95%,.8); + border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55); + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1), + 0 0 3px hsla(210,15%,25%,.1); + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} + +button:hover:active { + background-color: hsla(210,15%,25%,.2); + box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset, + 0 0 2px hsla(210,15%,25%,.4) inset; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index 0be8d309a0..a0bdd91636 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -1929,6 +1929,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action- } } +.tabbrowser-tab[remote] { + text-decoration: underline; +} + .tabbrowser-tab:hover, .tabs-newtab-button:hover { background-image: @toolbarShadowOnTab@, var(--tab-background-hover), diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 605c6ff3c9..e1097fe1d3 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -15,6 +15,8 @@ browser.jar: #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif + skin/classic/browser/aboutTabCrashed.css + skin/classic/browser/aboutTabCrashed.css skin/classic/browser/actionicon-tab.png skin/classic/browser/appmenu-icons.png skin/classic/browser/appmenu-dropmarker.png diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 8d51f3289c..28205affaa 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -906,6 +906,18 @@ DOMInterfaces = { 'headerFile': 'HTMLPropertiesCollection.h', }, +'PushEvent': { + 'headerFile': 'ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::PushEvent', + 'workers': True +}, + +'PushMessageData': { + 'headerFile': 'ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::PushMessageData', + 'workers': True +}, + 'Range': { 'nativeType': 'nsRange', 'binaryNames': { diff --git a/dom/bindings/moz.build b/dom/bindings/moz.build index 25f2ffb835..6342644aaf 100644 --- a/dom/bindings/moz.build +++ b/dom/bindings/moz.build @@ -102,6 +102,10 @@ if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']: # This is needed for Window.webidl DEFINES['HAVE_SIDEBAR'] = True + +if CONFIG['MOZ_SIMPLEPUSH']: + DEFINES['MOZ_SIMPLEPUSH'] = True + PYTHON_UNIT_TESTS += [ 'mozwebidlcodegen/test/test_mozwebidlcodegen.py', ] diff --git a/dom/cache/test/mochitest/mochitest.ini b/dom/cache/test/mochitest/mochitest.ini index 9c76005179..60df62647a 100644 --- a/dom/cache/test/mochitest/mochitest.ini +++ b/dom/cache/test/mochitest/mochitest.ini @@ -18,6 +18,7 @@ support-files = test_cache_keys.js test_cache_put.js test_cache_requestCache.js + test_cache_delete.js [test_cache.html] [test_cache_add.html] @@ -29,3 +30,4 @@ support-files = [test_cache_keys.html] [test_cache_put.html] [test_cache_requestCache.html] +[test_cache_delete.html] diff --git a/dom/cache/test/mochitest/test_cache_delete.html b/dom/cache/test/mochitest/test_cache_delete.html new file mode 100644 index 0000000000..a9bcf4328f --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_delete.html @@ -0,0 +1,20 @@ + + + + + Validate the Cache.delete() method + + + + + + + + + diff --git a/dom/cache/test/mochitest/test_cache_delete.js b/dom/cache/test/mochitest/test_cache_delete.js new file mode 100644 index 0000000000..aa7bc94eb8 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_delete.js @@ -0,0 +1,111 @@ +var name = "delete" + context; +var c; + +function setupTest(reqs) { + return new Promise(function(resolve, reject) { + var cache; + caches.open(name).then(function(c) { + cache = c; + return c.addAll(reqs); + }).then(function() { + resolve(cache); + }).catch(function(err) { + reject(err); + }); + }); +} + +function testBasics() { + var tests = [ + "//mochi.test:8888/?foo" + context, + "//mochi.test:8888/?bar" + context, + ]; + var cache; + return setupTest(tests) + .then(function(c) { + cache = c; + return cache.delete("//mochi.test:8888/?baz"); + }).then(function(deleted) { + ok(!deleted, "Deleting a non-existing entry should fail"); + return cache.keys(); + }).then(function(keys) { + is(keys.length, 2, "No entries from the cache should be deleted"); + return cache.delete(tests[0]); + }).then(function(deleted) { + ok(deleted, "Deleting an existing entry should succeed"); + return cache.keys(); + }).then(function(keys) { + is(keys.length, 1, "Only one entry should exist now"); + ok(keys[0].url.indexOf(tests[1]) >= 0, "The correct entry must be deleted"); + }); +} + +function testFragment() { + var tests = [ + "//mochi.test:8888/?foo" + context, + "//mochi.test:8888/?bar" + context, + "//mochi.test:8888/?baz" + context + "#fragment", + ]; + var cache; + return setupTest(tests) + .then(function(c) { + cache = c; + return cache.delete(tests[0] + "#fragment"); + }).then(function(deleted) { + ok(deleted, "Deleting an existing entry should succeed"); + return cache.keys(); + }).then(function(keys) { + is(keys.length, 2, "Only one entry should exist now"); + ok(keys[0].url.indexOf(tests[1]) >= 0, "The correct entry must be deleted"); + ok(keys[1].url.indexOf(tests[2].replace("#fragment", "")) >= 0, "The correct entry must be deleted"); + // Now, delete a request that was added with a fragment + return cache.delete("//mochi.test:8888/?baz" + context); + }).then(function(deleted) { + ok(deleted, "Deleting an existing entry should succeed"); + return cache.keys(); + }).then(function(keys) { + is(keys.length, 1, "Only one entry should exist now"); + ok(keys[0].url.indexOf(tests[1]) >= 0, "3The correct entry must be deleted"); + }); +} + +function testInterleaved() { + var tests = [ + "//mochi.test:8888/?foo" + context, + "//mochi.test:8888/?bar" + context, + ]; + var newURL = "//mochi.test:8888/?baz" + context; + var cache; + return setupTest(tests) + .then(function(c) { + cache = c; + // Simultaneously add and delete a request + return Promise.all([ + cache.delete(newURL), + cache.add(newURL), + ]); + }).then(function(result) { + ok(!result[1], "deletion should fail"); + return cache.keys(); + }).then(function(keys) { + is(keys.length, 3, "Tree entries should still exist"); + ok(keys[0].url.indexOf(tests[0]) >= 0, "The correct entry must be deleted"); + ok(keys[1].url.indexOf(tests[1]) >= 0, "The correct entry must be deleted"); + ok(keys[2].url.indexOf(newURL) >= 0, "The new entry should be correctly inserted"); + }); +} + +// Make sure to clean up after each test step. +function step(testPromise) { + return testPromise.then(function() { + caches.delete(name); + }); +} + +step(testBasics()).then(function() { + return step(testFragment()); +}).then(function() { + return step(testInterleaved()); +}).then(function() { + testDone(); +}); diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl index 98fdc4f70a..69f5a8e35e 100644 --- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -33,7 +33,7 @@ interface nsIServiceWorkerInfo : nsISupports readonly attribute DOMString waitingCacheName; }; -[scriptable, builtinclass, uuid(861b55e9-d6ac-47cf-a528-8590e9b44de6)] +[scriptable, builtinclass, uuid(3cd3acce-8c80-4fcc-9265-067ebe8cab92)] interface nsIServiceWorkerManager : nsISupports { /** @@ -121,6 +121,9 @@ interface nsIServiceWorkerManager : nsISupports // This is meant to be used only by about:serviceworkers. It returns an array // of nsIServiceWorkerInfo. nsIArray getAllRegistrations(); + + void sendPushEvent(in ACString scope, in DOMString data); + void sendPushSubscriptionChangedEvent(in ACString scope); }; %{ C++ diff --git a/dom/ipc/jar.mn b/dom/ipc/jar.mn index 07297f4f69..edd8f1a93b 100644 --- a/dom/ipc/jar.mn +++ b/dom/ipc/jar.mn @@ -8,5 +8,6 @@ toolkit.jar: content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js) content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js) * content/global/BrowserElementPanning.js (../browser-element/BrowserElementPanning.js) + content/global/PushServiceChildPreload.js (../push/PushServiceChildPreload.js) content/global/preload.js (preload.js) content/global/post-fork-preload.js (post-fork-preload.js) diff --git a/dom/locales/en-US/chrome/appstrings.properties b/dom/locales/en-US/chrome/appstrings.properties index f8d53129bd..58195d40ae 100644 --- a/dom/locales/en-US/chrome/appstrings.properties +++ b/dom/locales/en-US/chrome/appstrings.properties @@ -33,4 +33,6 @@ phishingBlocked=The website at %S has been reported as a web forgery designed to cspBlocked=This page has a content security policy that prevents it from being loaded in this way. corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected. remoteXUL=This page uses an unsupported technology that is no longer available by default. +#LOCALIZATION NOTE (tabcrashed): The following string is shown in the tab title if a page with a blank title has crashed. Current UX says that the tab title should remain blank +tabcrashed= sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol. diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index 9038174142..6da58b2da0 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -609,22 +609,18 @@ MediaStreamGraphImpl::UpdateStreamOrder() // If this is a AudioNodeStream, force a AudioCallbackDriver. if (stream->AsAudioNodeStream()) { audioTrackPresent = true; - } - for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO); - !tracks.IsEnded(); tracks.Next()) { - audioTrackPresent = true; + } else { + for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO); + !tracks.IsEnded(); tracks.Next()) { + audioTrackPresent = true; + } } } if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) { - bool started; - { - MonitorAutoLock mon(mMonitor); - started = CurrentDriver()->AsAudioCallbackDriver()->IsStarted(); - } - if (started) { - MonitorAutoLock mon(mMonitor); + MonitorAutoLock mon(mMonitor); + if (CurrentDriver()->AsAudioCallbackDriver()->IsStarted()) { if (mLifecycleState == LIFECYCLE_RUNNING) { SystemClockDriver* driver = new SystemClockDriver(this); CurrentDriver()->SwitchAtNextIteration(driver); @@ -644,6 +640,12 @@ MediaStreamGraphImpl::UpdateStreamOrder() } #endif + if (!mStreamOrderDirty) { + return; + } + + mStreamOrderDirty = false; + // The algorithm for finding cycles is based on Tim Leslie's iterative // implementation [1][2] of Pearce's variant [3] of Tarjan's strongly // connected components (SCC) algorithm. There are variations (a) to @@ -1374,9 +1376,7 @@ MediaStreamGraphImpl::UpdateGraph(GraphTime aEndBlockingDecision) } mFrontMessageQueue.Clear(); - if (mStreamOrderDirty) { - UpdateStreamOrder(); - } + UpdateStreamOrder(); bool ensureNextIteration = false; diff --git a/dom/push/Push.js b/dom/push/Push.js index 81a253a251..b5c0a18f14 100644 --- a/dom/push/Push.js +++ b/dom/push/Push.js @@ -4,8 +4,8 @@ "use strict"; -// Don't modify this, instead set services.push.debug. -let gDebuggingEnabled = false; +// Don't modify this, instead set dom.push.debug. +let gDebuggingEnabled = true; function debug(s) { if (gDebuggingEnabled) @@ -21,6 +21,94 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); Cu.import("resource://gre/modules/AppsUtils.jsm"); +const PUSH_SUBSCRIPTION_CID = Components.ID("{CA86B665-BEDA-4212-8D0F-5C9F65270B58}"); + +function PushSubscription(pushEndpoint, scope, pageURL) { + debug("PushSubscription Constructor"); + this._pushEndpoint = pushEndpoint; + this._scope = scope; + this._pageURL = pageURL; +} + +PushSubscription.prototype = { + __proto__: DOMRequestIpcHelper.prototype, + + contractID: "@mozilla.org/push/PushSubscription;1", + + classID : PUSH_SUBSCRIPTION_CID, + + QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer, + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), + + init: function(aWindow) { + debug("PushSubscription init()"); + + this.initDOMRequestHelper(aWindow, [ + "PushService:Unregister:OK", + "PushService:Unregister:KO", + ]); + + this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] + .getService(Ci.nsISyncMessageSender); + }, + + __init: function(endpoint, scope, pageURL) { + this._pushEndpoint = endpoint; + this._scope = scope; + this._pageURL = pageURL; + }, + + get endpoint() { + return this._pushEndpoint; + }, + + get subscriptionId() { + // TODO bug 1149271. Not sure what this is about. + return "The twins of Mammon quarrelled."; + }, + + unsubscribe: function() { + debug("unsubscribe! ") + + let promiseInit = function(resolve, reject) { + let resolverId = this.getPromiseResolverId({resolve: resolve, + reject: reject }); + + this._cpmm.sendAsyncMessage("Push:Unregister", { + pageURL: this._pageURL, + scope: this._scope, + pushEndpoint: this._pushEndpoint, + requestID: resolverId + }); + }.bind(this); + + return this.createPromise(promiseInit); + }, + + receiveMessage: function(aMessage) { + debug("push subscription receiveMessage(): " + JSON.stringify(aMessage)) + + let json = aMessage.data; + let resolver = this.takePromiseResolver(json.requestID); + if (resolver == null) { + return; + } + + switch (aMessage.name) { + case "PushService:Unregister:OK": + resolver.resolve(false); + break; + case "PushService:Unregister:KO": + resolver.reject(true); + break; + default: + debug("NOT IMPLEMENTED! receiveMessage for " + aMessage.name); + } + }, + +}; + const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}"); /** @@ -47,101 +135,186 @@ Push.prototype = { // Set debug first so that all debugging actually works. // NOTE: We don't add an observer here like in PushService. Flipping the // pref will require a reload of the app/page, which seems acceptable. - gDebuggingEnabled = Services.prefs.getBoolPref("services.push.debug"); + gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug"); debug("init()"); - let principal = aWindow.document.nodePrincipal; - let appsService = Cc["@mozilla.org/AppsService;1"] - .getService(Ci.nsIAppsService); - - this._manifestURL = appsService.getManifestURLByLocalId(principal.appId); - this._pageURL = principal.URI; + this._pageURL = aWindow.document.nodePrincipal.URI; + this._window = aWindow; this.initDOMRequestHelper(aWindow, [ "PushService:Register:OK", "PushService:Register:KO", - "PushService:Unregister:OK", - "PushService:Unregister:KO", - "PushService:Registrations:OK", - "PushService:Registrations:KO" + "PushService:Registration:OK", + "PushService:Registration:KO" ]); this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] .getService(Ci.nsISyncMessageSender); }, + setScope: function(scope){ + debug('setScope ' + scope); + this._scope = scope; + }, + + askPermission: function (aAllowCallback, aCancelCallback) { + debug("askPermission"); + + let principal = this._window.document.nodePrincipal; + let type = "push"; + let permValue = + Services.perms.testExactPermissionFromPrincipal(principal, type); + + debug("Existing permission " + permValue); + + if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) { + aAllowCallback(); + return; + } + + if (permValue == Ci.nsIPermissionManager.DENY_ACTION) { + aCancelCallback(); + return; + } + + // Create an array with a single nsIContentPermissionType element. + type = { + type: "push", + access: null, + options: [], + QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType]) + }; + let typeArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + typeArray.appendElement(type, false); + + // create a nsIContentPermissionRequest + let request = { + types: typeArray, + principal: principal, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]), + allow: function() { + aAllowCallback(); + }, + cancel: function() { + aCancelCallback(); + }, + window: this._window + }; + + debug("asking the window utils about permission...") + // Using askPermission from nsIDOMWindowUtils that takes care of the + // remoting if needed. + let windowUtils = this._window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + windowUtils.askPermission(request); + }, + + + receiveMessage: function(aMessage) { - debug("receiveMessage()"); - let request = this.getRequest(aMessage.data.requestID); + debug("push receiveMessage(): " + JSON.stringify(aMessage)) + let json = aMessage.data; - if (!request) { - debug("No request " + json.requestID); + let resolver = this.takePromiseResolver(json.requestID); + + if (!resolver) { return; } switch (aMessage.name) { case "PushService:Register:OK": - Services.DOMRequest.fireSuccess(request, json.pushEndpoint); + { + let subscription = new this._window.PushSubscription(json.pushEndpoint, + this._scope, + this._pageURL.spec); + resolver.resolve(subscription); break; + } case "PushService:Register:KO": - Services.DOMRequest.fireError(request, json.error); + resolver.reject(null); break; - case "PushService:Unregister:OK": - Services.DOMRequest.fireSuccess(request, json.pushEndpoint); + case "PushService:Registration:OK": + { + let subscription = null; + try { + subscription = new this._window.PushSubscription(json.registration.pushEndpoint, + this._scope, this._pageURL.spec); + } catch(error) { + } + resolver.resolve(subscription); break; - case "PushService:Unregister:KO": - Services.DOMRequest.fireError(request, json.error); - break; - case "PushService:Registrations:OK": - Services.DOMRequest.fireSuccess(request, json.registrations); - break; - case "PushService:Registrations:KO": - Services.DOMRequest.fireError(request, json.error); + } + case "PushService:Registration:KO": + resolver.reject(null); break; default: debug("NOT IMPLEMENTED! receiveMessage for " + aMessage.name); } }, - register: function() { - debug("register()"); - let req = this.createRequest(); - if (!Services.prefs.getBoolPref("services.push.connection.enabled")) { - // If push socket is disabled by the user, immediately error rather than - // timing out. - Services.DOMRequest.fireErrorAsync(req, "NetworkError"); - return req; - } + subscribe: function() { + debug("subscribe()"); + let p = this.createPromise(function(resolve, reject) { + let resolverId = this.getPromiseResolverId({ resolve: resolve, reject: reject }); - this._cpmm.sendAsyncMessage("Push:Register", { - pageURL: this._pageURL.spec, - manifestURL: this._manifestURL, - requestID: this.getRequestId(req) - }); - return req; + this.askPermission( + function() { + this._cpmm.sendAsyncMessage("Push:Register", { + pageURL: this._pageURL.spec, + scope: this._scope, + requestID: resolverId + }); + }.bind(this), + + function() { + reject("denied"); + } + ); + }.bind(this)); + return p; }, - unregister: function(aPushEndpoint) { - debug("unregister(" + aPushEndpoint + ")"); - let req = this.createRequest(); - this._cpmm.sendAsyncMessage("Push:Unregister", { - pageURL: this._pageURL.spec, - manifestURL: this._manifestURL, - requestID: this.getRequestId(req), - pushEndpoint: aPushEndpoint - }); - return req; + getSubscription: function() { + debug("getSubscription()" + this._scope); + + let p = this.createPromise(function(resolve, reject) { + + let resolverId = this.getPromiseResolverId({ resolve: resolve, reject: reject }); + + this.askPermission( + function() { + this._cpmm.sendAsyncMessage("Push:Registration", { + pageURL: this._pageURL.spec, + scope: this._scope, + requestID: resolverId + }); + }.bind(this), + + function() { + reject("denied"); + } + ); + }.bind(this)); + return p; }, - registrations: function() { - debug("registrations()"); - let req = this.createRequest(); - this._cpmm.sendAsyncMessage("Push:Registrations", { - manifestURL: this._manifestURL, - requestID: this.getRequestId(req) - }); - return req; - } + hasPermission: function() { + debug("getSubscription()" + this._scope); + + let p = this.createPromise(function(resolve, reject) { + let permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); + let permission = permissionManager.testExactPermission(this._pageURL, "push"); + + let pushPermissionStatus = "default"; + if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) { + pushPermissionStatus = "granted"; + } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) { + pushPermissionStatus = "denied"; + } + resolve(pushPermissionStatus); + }.bind(this)); + return p; + }, } -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]); +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push, PushSubscription]); diff --git a/dom/push/Push.manifest b/dom/push/Push.manifest index aa859f8fa1..1527fbba23 100644 --- a/dom/push/Push.manifest +++ b/dom/push/Push.manifest @@ -2,6 +2,9 @@ component {cde1d019-fad8-4044-b141-65fb4fb7a245} Push.js contract @mozilla.org/push/PushManager;1 {cde1d019-fad8-4044-b141-65fb4fb7a245} +component {CA86B665-BEDA-4212-8D0F-5C9F65270B58} Push.js +contract @mozilla.org/push/PushSubscription;1 {CA86B665-BEDA-4212-8D0F-5C9F65270B58} + # Component to initialize PushService on startup. component {4b8caa3b-3c58-4f3c-a7f5-7bd9cb24c11d} PushServiceLauncher.js contract @mozilla.org/push/ServiceLauncher;1 {4b8caa3b-3c58-4f3c-a7f5-7bd9cb24c11d} diff --git a/dom/push/PushService.jsm b/dom/push/PushService.jsm index cd3f467dde..d88246504e 100644 --- a/dom/push/PushService.jsm +++ b/dom/push/PushService.jsm @@ -5,8 +5,8 @@ "use strict"; -// Don't modify this, instead set services.push.debug. -let gDebuggingEnabled = false; +// Don't modify this, instead set dom.push.debug. +let gDebuggingEnabled = true; function debug(s) { if (gDebuggingEnabled) @@ -41,7 +41,7 @@ var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadM this.EXPORTED_SYMBOLS = ["PushService"]; -const prefs = new Preferences("services.push."); +const prefs = new Preferences("dom.push."); // Set debug first so that all debugging actually works. gDebuggingEnabled = prefs.get("debug"); @@ -54,7 +54,7 @@ const kUDP_WAKEUP_WS_STATUS_CODE = 4774; // WebSocket Close status code sent // wake client up using UDP. const kCHILD_PROCESS_MESSAGES = ["Push:Register", "Push:Unregister", - "Push:Registrations"]; + "Push:Registration"]; const kWS_MAX_WENTDOWN = 2; @@ -79,10 +79,9 @@ this.PushDB.prototype = { // index to fetch records based on endpoints. used by unregister objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true }); - // index to fetch records per manifest, so we can identify endpoints - // associated with an app. Since an app can have multiple endpoints - // uniqueness cannot be enforced - objectStore.createIndex("manifestURL", "manifestURL", { unique: false }); + // index to fetch records per scope, so we can identify endpoints + // associated with an app. + objectStore.createIndex("scope", "scope", { unique: true }); }, /* @@ -94,7 +93,7 @@ this.PushDB.prototype = { * Callback function to invoke when there was an error. */ put: function(aChannelRecord, aSuccessCb, aErrorCb) { - debug("put()"); + debug("put()" + JSON.stringify(aChannelRecord)); this.newTxn( "readwrite", @@ -173,30 +172,20 @@ this.PushDB.prototype = { ); }, - getAllByManifestURL: function(aManifestURL, aSuccessCb, aErrorCb) { - debug("getAllByManifestURL()"); - if (!aManifestURL) { - if (typeof aErrorCb == "function") { - aErrorCb("PushDB.getAllByManifestURL: Got undefined aManifestURL"); - } - return; - } - let self = this; + getByScope: function(aScope, aSuccessCb, aErrorCb) { + debug("getByScope() " + aScope); + this.newTxn( "readonly", kPUSHDB_STORE_NAME, function txnCb(aTxn, aStore) { - let index = aStore.index("manifestURL"); - let range = IDBKeyRange.only(aManifestURL); - aTxn.result = []; - index.openCursor(range).onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - debug(cursor.value.manifestURL + " " + cursor.value.channelID); - aTxn.result.push(cursor.value); - cursor.continue(); - } + aTxn.result = undefined; + + let index = aStore.index("scope"); + index.get(aScope).onsuccess = function setTxnResult(aEvent) { + aTxn.result = aEvent.target.result; + debug("Fetch successful " + aEvent.target.result); } }, aSuccessCb, @@ -325,17 +314,17 @@ this.PushService = { } break; case "nsPref:changed": - if (aData == "services.push.serverURL") { - debug("services.push.serverURL changed! websocket. new value " + + if (aData == "dom.push.serverURL") { + debug("dom.push.serverURL changed! websocket. new value " + prefs.get("serverURL")); this._shutdownWS(); - } else if (aData == "services.push.connection.enabled") { + } else if (aData == "dom.push.connection.enabled") { if (prefs.get("connection.enabled")) { this._startListeningIfChannelsPresent(); } else { this._shutdownWS(); } - } else if (aData == "services.push.debug") { + } else if (aData == "dom.push.debug") { gDebuggingEnabled = prefs.get("debug"); } break; @@ -382,35 +371,30 @@ this.PushService = { return; } - // Only remove push registrations for apps. - if (data.browserOnly) { - return; - } - + // TODO 1149274. We should support site permissions as well as a way to go from manifest + // url to 'all scopes registered for push in this app' let appsService = Cc["@mozilla.org/AppsService;1"] .getService(Ci.nsIAppsService); - let manifestURL = appsService.getManifestURLByLocalId(data.appId); - if (!manifestURL) { - debug("webapps-clear-data: No manifest URL found for " + data.appId); + let scope = appsService.getScopeByLocalId(data.appId); + if (!scope) { + debug("webapps-clear-data: No scope found for " + data.appId); return; } - this._db.getAllByManifestURL(manifestURL, function(records) { - debug("Got " + records.length); - for (let i = 0; i < records.length; i++) { - this._db.delete(records[i].channelID, null, function() { - debug("webapps-clear-data: " + manifestURL + - " Could not delete entry " + records[i].channelID); - }); + this._db.getByScope(scope, function(record) { + this._db.delete(records.channelID, null, function() { + debug("webapps-clear-data: " + scope + + " Could not delete entry " + records.channelID); + // courtesy, but don't establish a connection // just for it if (this._ws) { debug("Had a connection, so telling the server"); - this._send("unregister", {channelID: records[i].channelID}); + this._send("unregister", {channelID: records.channelID}); } - } - }.bind(this), function() { - debug("webapps-clear-data: Error in getAllByManifestURL(" + manifestURL + ")"); + }.bind(this), function() { + debug("webapps-clear-data: Error in getByScope(" + scope + ")"); + }); }); break; @@ -465,7 +449,7 @@ this.PushService = { * 1) the gap between the maximum working ping and the first ping that * gives an error (timeout) OR * 2) we have reached the pref of the maximum value we allow for a ping - * (services.push.adaptive.upperLimit) + * (dom.push.adaptive.upperLimit) */ _recalculatePing: true, @@ -512,6 +496,11 @@ this.PushService = { if (!prefs.get("enabled")) return null; + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"] + .getService(Ci.nsIFrameScriptLoader); + + globalMM.loadFrameScript("chrome://global/content/PushServiceChildPreload.js", true); + this._db = new PushDB(); let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"] @@ -672,7 +661,7 @@ this.PushService = { * This algorithm tries to search the best value between a disconnection and a * valid ping, to ensure better battery life and network resources usage. * - * The value is saved in services.push.pingInterval + * The value is saved in dom.push.pingInterval * @param wsWentDown [Boolean] if the WebSocket was closed or it is still alive * */ @@ -842,7 +831,7 @@ this.PushService = { let serverURL = prefs.get("serverURL"); if (!serverURL) { - debug("No services.push.serverURL found!"); + debug("No dom.push.serverURL found!"); return; } @@ -850,7 +839,7 @@ this.PushService = { try { uri = Services.io.newURI(serverURL, null, null); } catch(e) { - debug("Error creating valid URI from services.push.serverURL (" + + debug("Error creating valid URI from dom.push.serverURL (" + serverURL + ")"); return; } @@ -1090,7 +1079,7 @@ this.PushService = { debug("got new UAID: all re-register"); this._notifyAllAppsRegister() - .then(this._dropRegistrations.bind(this)) + .then(this._dropRegistration.bind(this)) .then(finishHandshake.bind(this)); return; @@ -1279,35 +1268,32 @@ this.PushService = { }, // Fires a push-register system message to all applications that have - // registrations. + // registration. _notifyAllAppsRegister: function() { debug("notifyAllAppsRegister()"); let deferred = Promise.defer(); - // records are objects describing the registrations as stored in IndexedDB. + // records are objects describing the registration as stored in IndexedDB. function wakeupRegisteredApps(records) { // Pages to be notified. - // wakeupTable[manifestURL] -> [ pageURL ] + // wakeupTable[scope] -> [ pageURL ] let wakeupTable = {}; for (let i = 0; i < records.length; i++) { let record = records[i]; - if (!(record.manifestURL in wakeupTable)) - wakeupTable[record.manifestURL] = []; + if (!(record.scope in wakeupTable)) + wakeupTable[record.scope] = []; - wakeupTable[record.manifestURL].push(record.pageURL); + wakeupTable[record.scope].push(record.pageURL); } - let messenger = Cc["@mozilla.org/system-message-internal;1"] - .getService(Ci.nsISystemMessagesInternal); + // TODO -- test needed. E10s support needed. - for (let manifestURL in wakeupTable) { - wakeupTable[manifestURL].forEach(function(pageURL) { - messenger.sendMessage('push-register', {}, - Services.io.newURI(pageURL, null, null), - Services.io.newURI(manifestURL, null, null)); + let globalMM = Cc['@mozilla.org/globalmessagemanager;1'].getService(Ci.nsIMessageListenerManager); + for (let scope in wakeupTable) { + wakeupTable[scope].forEach(function(pageURL) { + globalMM.broadcastAsyncMessage('pushsubscriptionchanged', aPushRecord.scope); }); } - deferred.resolve(); } @@ -1317,22 +1303,36 @@ this.PushService = { }, _notifyApp: function(aPushRecord) { - if (!aPushRecord || !aPushRecord.pageURL || !aPushRecord.manifestURL) { - debug("notifyApp() something is undefined. Dropping notification"); + if (!aPushRecord || !aPushRecord.pageURL || !aPushRecord.scope) { + debug("notifyApp() something is undefined. Dropping notification: " + + JSON.stringify(aPushRecord) ); return; } debug("notifyApp() " + aPushRecord.pageURL + - " " + aPushRecord.manifestURL); + " " + aPushRecord.scope); let pageURI = Services.io.newURI(aPushRecord.pageURL, null, null); - let manifestURI = Services.io.newURI(aPushRecord.manifestURL, null, null); + let scopeURI = Services.io.newURI(aPushRecord.scope, null, null); let message = { pushEndpoint: aPushRecord.pushEndpoint, version: aPushRecord.version }; - let messenger = Cc["@mozilla.org/system-message-internal;1"] - .getService(Ci.nsISystemMessagesInternal); - messenger.sendMessage('push', message, pageURI, manifestURI); + + // If permission has been revoked, trash the message. + if(Services.perms.testExactPermission(scopeURI, "push") != Ci.nsIPermissionManager.ALLOW_ACTION) { + debug("Does not have permission for push.") + return; + } + + // TODO data. + let data = { + payload: "Short as life is, we make it still shorter by the careless waste of time.", + scope: aPushRecord.scope + }; + + let globalMM = Cc['@mozilla.org/globalmessagemanager;1'] + .getService(Ci.nsIMessageListenerManager); + globalMM.broadcastAsyncMessage('push', data); }, _updatePushRecord: function(aPushRecord) { @@ -1342,7 +1342,7 @@ this.PushService = { return deferred.promise; }, - _dropRegistrations: function() { + _dropRegistration: function() { let deferred = Promise.defer(); this._db.drop(deferred.resolve, deferred.reject); return deferred.promise; @@ -1365,9 +1365,8 @@ this.PushService = { * Called on message from the child process. aPageRecord is an object sent by * navigator.push, identifying the sending page and other fields. */ - register: function(aPageRecord, aMessageManager) { - debug("register()"); + _registerWithServer: function(aPageRecord, aMessageManager) { let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"] .getService(Ci.nsIUUIDGenerator); // generateUUID() gives a UUID surrounded by {...}, slice them off. @@ -1384,7 +1383,26 @@ this.PushService = { }, function(message) { aMessageManager.sendAsyncMessage("PushService:Register:KO", message); - }); + } + ); + }, + + register: function(aPageRecord, aMessageManager) { + debug("register(): " + JSON.stringify(aPageRecord)); + + this._db.getByScope(aPageRecord.scope, + function(aPageRecord, aMessageManager, pushRecord) { + if (pushRecord == null) { + this._registerWithServer(aPageRecord, aMessageManager); + } + else { + this._onRegistrationSuccess(aPageRecord, aMessageManager, pushRecord); + } + }.bind(this, aPageRecord, aMessageManager), + function () { + debug("getByScope failed"); + } + ); }, /** @@ -1421,10 +1439,12 @@ this.PushService = { channelID: data.channelID, pushEndpoint: data.pushEndpoint, pageURL: aPageRecord.pageURL, - manifestURL: aPageRecord.manifestURL, + scope: aPageRecord.scope, version: null }; + debug("scope in _onRegisterSuccess: " + aPageRecord.scope) + this._updatePushRecord(record) .then( function() { @@ -1436,7 +1456,7 @@ this.PushService = { this._send("unregister", {channelID: record.channelID}); message["error"] = error; deferred.reject(message); - } + }.bind(this) ); return deferred.promise; @@ -1479,7 +1499,7 @@ this.PushService = { * data is cheap, reliable notification is not. */ unregister: function(aPageRecord, aMessageManager) { - debug("unregister()"); + debug("unregister() " + JSON.stringify(aPageRecord)); let fail = function(error) { debug("unregister() fail() error " + error); @@ -1495,7 +1515,7 @@ this.PushService = { } // Non-owner tried to unregister, say success, but don't do anything. - if (record.manifestURL !== aPageRecord.manifestURL) { + if (record.scope !== aPageRecord.scope) { aMessageManager.sendAsyncMessage("PushService:Unregister:OK", { requestID: aPageRecord.requestID, pushEndpoint: aPageRecord.pushEndpoint @@ -1518,38 +1538,35 @@ this.PushService = { /** * Called on message from the child process */ - registrations: function(aPageRecord, aMessageManager) { - debug("registrations()"); - - if (aPageRecord.manifestURL) { - this._db.getAllByManifestURL(aPageRecord.manifestURL, - this._onRegistrationsSuccess.bind(this, aPageRecord, aMessageManager), - this._onRegistrationsError.bind(this, aPageRecord, aMessageManager)); - } - else { - this._onRegistrationsError(aPageRecord, aMessageManager); - } + registration: function(aPageRecord, aMessageManager) { + debug("registration()"); + this._db.getByScope(aPageRecord.scope, + this._onRegistrationSuccess.bind(this, aPageRecord, aMessageManager), + this._onRegistrationError.bind(this, aPageRecord, aMessageManager)); }, - _onRegistrationsSuccess: function(aPageRecord, - aMessageManager, - pushRecords) { - let registrations = []; - pushRecords.forEach(function(pushRecord) { - registrations.push({ - __exposedProps__: { pushEndpoint: 'r', version: 'r' }, - pushEndpoint: pushRecord.pushEndpoint, - version: pushRecord.version - }); - }); - aMessageManager.sendAsyncMessage("PushService:Registrations:OK", { + _onRegistrationSuccess: function(aPageRecord, + aMessageManager, + pushRecord) { + + + let registration = null; + + if (pushRecord) { + registration = { + pushEndpoint: pushRecord.pushEndpoint, + version: pushRecord.version + }; + } + + aMessageManager.sendAsyncMessage("PushService:Registration:OK", { requestID: aPageRecord.requestID, - registrations: registrations + registration: registration }); }, - _onRegistrationsError: function(aPageRecord, aMessageManager) { - aMessageManager.sendAsyncMessage("PushService:Registrations:KO", { + _onRegistrationError: function(aPageRecord, aMessageManager) { + aMessageManager.sendAsyncMessage("PushService:Registration:KO", { requestID: aPageRecord.requestID, error: "Database error" }); @@ -1733,7 +1750,7 @@ this.PushService = { this._udpServer = Cc["@mozilla.org/network/udp-socket;1"] .createInstance(Ci.nsIUDPSocket); - this._udpServer.init(-1, false, Services.scriptSecurityManager.getSystemPrincipal()); + this._udpServer.init(-1, false); this._udpServer.asyncListen(this); debug("listenForUDPWakeup listening on " + this._udpServer.port); diff --git a/dom/push/PushServiceChildPreload.js b/dom/push/PushServiceChildPreload.js new file mode 100644 index 0000000000..2123b5667a --- /dev/null +++ b/dom/push/PushServiceChildPreload.js @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +XPCOMUtils.defineLazyServiceGetter(this, + "swm", + "@mozilla.org/serviceworkers/manager;1", + "nsIServiceWorkerManager"); + +addMessageListener("push", function (aMessage) { + swm.sendPushEvent(aMessage.data.scope, aMessage.data.payload); +}); + +addMessageListener("pushsubscriptionchanged", function (aMessage) { + swm.sendPushSubscriptionChangedEvent(aMessage.data); +}); \ No newline at end of file diff --git a/dom/push/PushServiceLauncher.js b/dom/push/PushServiceLauncher.js index 4a0606e538..e9ec4015c1 100644 --- a/dom/push/PushServiceLauncher.js +++ b/dom/push/PushServiceLauncher.js @@ -30,11 +30,18 @@ PushServiceLauncher.prototype = { break; case "final-ui-startup": Services.obs.removeObserver(this, "final-ui-startup"); - if (!Services.prefs.getBoolPref("services.push.enabled")) { + if (!Services.prefs.getBoolPref("dom.push.enabled")) { return; } - Cu.import("resource://gre/modules/PushService.jsm"); - PushService.init(); + + let isParent = Cc["@mozilla.org/xre/runtime;1"] + .getService(Ci.nsIXULRuntime) + .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + + if (isParent) { + Cu.import("resource://gre/modules/PushService.jsm"); + PushService.init(); + } break; } } diff --git a/dom/push/moz.build b/dom/push/moz.build index 0d6805f3a9..96b273fa81 100644 --- a/dom/push/moz.build +++ b/dom/push/moz.build @@ -12,3 +12,7 @@ EXTRA_COMPONENTS += [ EXTRA_JS_MODULES += [ 'PushService.jsm', ] + +MOCHITEST_MANIFESTS += [ + 'test/mochitest.ini', +] diff --git a/dom/push/test/frame.html b/dom/push/test/frame.html new file mode 100644 index 0000000000..4e6d8388c3 --- /dev/null +++ b/dom/push/test/frame.html @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/dom/push/test/mochitest.ini b/dom/push/test/mochitest.ini new file mode 100644 index 0000000000..bf9b811897 --- /dev/null +++ b/dom/push/test/mochitest.ini @@ -0,0 +1,15 @@ +[DEFAULT] +subsuite = push +support-files = + worker.js + push-server.sjs + frame.html + +[test_has_permissions.html] +skip-if = os == "android" || toolkit == "gonk" +[test_permissions.html] +skip-if = os == "android" || toolkit == "gonk" +[test_register.html] +skip-if = os == "android" || toolkit == "gonk" +[test_multiple_register.html] +skip-if = os == "android" || toolkit == "gonk" \ No newline at end of file diff --git a/dom/push/test/push-server.sjs b/dom/push/test/push-server.sjs new file mode 100644 index 0000000000..2e1ad2ff76 --- /dev/null +++ b/dom/push/test/push-server.sjs @@ -0,0 +1,26 @@ +function debug(str) { +// dump("@@@ push-server " + str + "\n"); +} + +function handleRequest(request, response) +{ + debug("handling request!"); + + const Cc = Components.classes; + const Ci = Components.interfaces; + + let params = request.getHeader("X-Push-Server"); + debug("params = " + params); + + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); + xhr.open("PUT", params); + xhr.send(); + xhr.onload = function(e) { + debug("xhr : " + this.status); + } + xhr.onerror = function(e) { + debug("xhr error: " + e); + } + + response.setStatusLine(request.httpVersion, "200", "OK"); +} diff --git a/dom/push/test/test_has_permissions.html b/dom/push/test/test_has_permissions.html new file mode 100644 index 0000000000..d591fac218 --- /dev/null +++ b/dom/push/test/test_has_permissions.html @@ -0,0 +1,76 @@ + + + + + Test for Bug 1038811 + + + + +
Mozilla Bug 1038811 +

+ +
+
+ + + + diff --git a/dom/push/test/test_multiple_register.html b/dom/push/test/test_multiple_register.html new file mode 100644 index 0000000000..4f4ea654b6 --- /dev/null +++ b/dom/push/test/test_multiple_register.html @@ -0,0 +1,128 @@ + + + + + Test for Bug 1038811 + + + + +Mozilla Bug 1038811 +

+ +
+
+ + + + diff --git a/dom/push/test/test_permissions.html b/dom/push/test/test_permissions.html new file mode 100644 index 0000000000..1b2364de5f --- /dev/null +++ b/dom/push/test/test_permissions.html @@ -0,0 +1,91 @@ + + + + + Test for Bug 1038811 + + + + +Mozilla Bug 1038811 +

+ +
+
+ + + + diff --git a/dom/push/test/test_register.html b/dom/push/test/test_register.html new file mode 100644 index 0000000000..4505a0a852 --- /dev/null +++ b/dom/push/test/test_register.html @@ -0,0 +1,123 @@ + + + + + Test for Bug 1038811 + + + + +Mozilla Bug 1038811 +

+ +
+
+ + + + diff --git a/dom/push/test/worker.js b/dom/push/test/worker.js new file mode 100644 index 0000000000..eb583460f0 --- /dev/null +++ b/dom/push/test/worker.js @@ -0,0 +1,16 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +addEventListener("push", function(event) { + + self.clients.matchAll().then(function(result) { + if (event instanceof PushEvent && + event.data instanceof PushMessageData && + event.data.text().length > 0) { + + result[0].postMessage({type: "finished", okay: "yes"}); + return; + } + result[0].postMessage({type: "finished", okay: "no"}); + }); +}); diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 6de57e596c..44cc672a03 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -864,6 +864,8 @@ var interfaceNamesInGlobalScope = "Promise", // IMPORTANT: Do not change this list without review from a DOM peer! "PropertyNodeList", +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "PushManager", pref: "dom.push.enabled"}, // IMPORTANT: Do not change this list without review from a DOM peer! "RadioNodeList", // IMPORTANT: Do not change this list without review from a DOM peer! @@ -900,6 +902,8 @@ var interfaceNamesInGlobalScope = {name: "ServiceWorker", pref: "dom.serviceWorkers.enabled"}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "ServiceWorkerContainer", pref: "dom.serviceWorkers.enabled"}, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "ServiceWorkerRegistration", pref: "dom.serviceWorkers.enabled"}, // IMPORTANT: Do not change this list without review from a DOM peer! "SettingsLock", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/PushEvent.webidl b/dom/webidl/PushEvent.webidl new file mode 100644 index 0000000000..806b786a59 --- /dev/null +++ b/dom/webidl/PushEvent.webidl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/push-api/ + */ + +[Constructor(DOMString type, optional PushEventInit eventInitDict), Exposed=ServiceWorker] +interface PushEvent : ExtendableEvent { + readonly attribute PushMessageData data; +}; + +typedef USVString PushMessageDataInit; + +dictionary PushEventInit : ExtendableEventInit { + PushMessageDataInit data; +}; \ No newline at end of file diff --git a/dom/webidl/PushManager.webidl b/dom/webidl/PushManager.webidl index 1e3a4de601..b320b8b2af 100644 --- a/dom/webidl/PushManager.webidl +++ b/dom/webidl/PushManager.webidl @@ -2,15 +2,27 @@ /* 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/. +* +* The origin of this IDL file is +* https://w3c.github.io/push-api/ */ -[NoInterfaceObject, - NavigatorProperty="push", - JSImplementation="@mozilla.org/push/PushManager;1", - CheckPermissions="push", - Pref="services.push.enabled"] +[JSImplementation="@mozilla.org/push/PushManager;1", + Pref="dom.push.enabled"] interface PushManager { - DOMRequest register(); - DOMRequest unregister(DOMString pushEndpoint); - DOMRequest registrations(); + Promise subscribe(); + Promise getSubscription(); + Promise hasPermission(); + + // We need a setter in the bindings so that the C++ can use it, + // but we don't want it exposed to client JS. WebPushMethodHider + // always returns false. + [Func="ServiceWorkerRegistration::WebPushMethodHider"] void setScope(DOMString scope); +}; + +enum PushPermissionStatus +{ + "granted", + "denied", + "default" }; diff --git a/dom/webidl/PushMessageData.webidl b/dom/webidl/PushMessageData.webidl new file mode 100644 index 0000000000..16bd0ca87f --- /dev/null +++ b/dom/webidl/PushMessageData.webidl @@ -0,0 +1,17 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/push-api/ + */ + +[Exposed=ServiceWorker] +interface PushMessageData +{ + ArrayBuffer arrayBuffer(); + Blob blob(); + object json(); + USVString text(); +}; \ No newline at end of file diff --git a/dom/webidl/PushSubscription.webidl b/dom/webidl/PushSubscription.webidl new file mode 100644 index 0000000000..236a8fb5ad --- /dev/null +++ b/dom/webidl/PushSubscription.webidl @@ -0,0 +1,17 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. +* +* The origin of this IDL file is +* https://w3c.github.io/push-api/ +*/ + +[JSImplementation="@mozilla.org/push/PushSubscription;1", + Constructor(DOMString pushEndpoint, DOMString scope, DOMString pageURL), ChromeOnly] +interface PushSubscription +{ + readonly attribute USVString endpoint; + readonly attribute DOMString subscriptionId; + Promise unsubscribe(); +}; \ No newline at end of file diff --git a/dom/webidl/ServiceWorkerContainer.webidl b/dom/webidl/ServiceWorkerContainer.webidl index 271c76298a..ee684bf8d0 100644 --- a/dom/webidl/ServiceWorkerContainer.webidl +++ b/dom/webidl/ServiceWorkerContainer.webidl @@ -27,7 +27,7 @@ interface ServiceWorkerContainer : EventTarget { Promise getRegistration(optional USVString documentURL = ""); [Throws] - Promise> getRegistrations(); + Promise> getRegistrations(); attribute EventHandler oncontrollerchange; attribute EventHandler onreloadpage; diff --git a/dom/webidl/ServiceWorkerRegistration.webidl b/dom/webidl/ServiceWorkerRegistration.webidl index a9b18481e2..5f04198558 100644 --- a/dom/webidl/ServiceWorkerRegistration.webidl +++ b/dom/webidl/ServiceWorkerRegistration.webidl @@ -23,3 +23,10 @@ interface ServiceWorkerRegistration : EventTarget { // event attribute EventHandler onupdatefound; }; + +partial interface ServiceWorkerRegistration { +#ifndef MOZ_SIMPLEPUSH + [Throws, Pref="dom.push.enabled"] + readonly attribute PushManager pushManager; +#endif +}; diff --git a/dom/webidl/SimplePushManager.webidl b/dom/webidl/SimplePushManager.webidl index ab99ca36d4..b1f97ad957 100644 --- a/dom/webidl/SimplePushManager.webidl +++ b/dom/webidl/SimplePushManager.webidl @@ -8,7 +8,7 @@ JSImplementation="@mozilla.org/push/PushManager;1", CheckPermissions="push", Pref="services.push.enabled"] -interface PushManager { +interface SimplePushManager { DOMRequest register(); DOMRequest unregister(DOMString pushEndpoint); DOMRequest registrations(); diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 3548f33ff0..19d3c70569 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -11,6 +11,7 @@ GENERATED_WEBIDL_FILES = [ PREPROCESSED_WEBIDL_FILES = [ 'HTMLMediaElement.webidl', 'Navigator.webidl', + 'ServiceWorkerRegistration.webidl', 'Window.webidl', ] @@ -364,7 +365,6 @@ WEBIDL_FILES = [ 'ServiceWorker.webidl', 'ServiceWorkerContainer.webidl', 'ServiceWorkerGlobalScope.webidl', - 'ServiceWorkerRegistration.webidl', 'SettingChangeNotification.webidl', 'SettingsManager.webidl', 'ShadowRoot.webidl', @@ -658,9 +658,16 @@ if CONFIG['MOZ_B2G_RIL']: ] if CONFIG['MOZ_SIMPLEPUSH']: - WEBIDL_FILES += SimplePushManager.webidl + WEBIDL_FILES += [ + 'SimplePushManager.webidl' + ] else: WEBIDL_FILES += [ + 'PushEvent.webidl', + 'PushManager.webidl', + 'PushManager.webidl', + 'PushMessageData.webidl', + 'PushSubscription.webidl', ] if CONFIG['MOZ_NFC']: diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index dbe2308dc9..8503bd818f 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -405,4 +405,59 @@ NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent) NS_IMPL_CYCLE_COLLECTION_INHERITED(InstallEvent, ExtendableEvent, mActiveWorker) +#ifndef MOZ_SIMPLEPUSH + +PushMessageData::PushMessageData(const nsAString& aData) + : mData(aData) +{ +} + +PushMessageData::~PushMessageData() +{ +} + +NS_IMPL_ISUPPORTS0(PushMessageData); + + +void +PushMessageData::Json(JSContext* cx, JS::MutableHandle aRetval) +{ + //todo bug 1149195. Don't be lazy. + NS_ABORT(); +} + +void +PushMessageData::Text(nsAString& aData) +{ + aData = mData; +} + +void +PushMessageData::ArrayBuffer(JSContext* cx, JS::MutableHandle aRetval) +{ + //todo bug 1149195. Don't be lazy. + NS_ABORT(); +} + +mozilla::dom::File* +PushMessageData::Blob() +{ + //todo bug 1149195. Don't be lazy. + NS_ABORT(); + return nullptr; +} + +PushEvent::PushEvent(EventTarget* aOwner) + : ExtendableEvent(aOwner) +{ +} + +NS_INTERFACE_MAP_BEGIN(PushEvent) +NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent) + +NS_IMPL_ADDREF_INHERITED(PushEvent, ExtendableEvent) +NS_IMPL_RELEASE_INHERITED(PushEvent, ExtendableEvent) + +#endif /* ! MOZ_SIMPLEPUSH */ + END_WORKERS_NAMESPACE diff --git a/dom/workers/ServiceWorkerEvents.h b/dom/workers/ServiceWorkerEvents.h index 1005bf21dc..77a8d38760 100644 --- a/dom/workers/ServiceWorkerEvents.h +++ b/dom/workers/ServiceWorkerEvents.h @@ -12,6 +12,12 @@ #include "mozilla/dom/InstallEventBinding.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/Response.h" + +#ifndef MOZ_SIMPLEPUSH +#include "mozilla/dom/PushEventBinding.h" +#include "mozilla/dom/PushMessageDataBinding.h" +#endif + #include "nsProxyRelease.h" class nsIInterceptedChannel; @@ -218,5 +224,91 @@ public: } }; +#ifndef MOZ_SIMPLEPUSH + +class PushMessageData final : public nsISupports, + public nsWrapperCache +{ + nsString mData; + +public: + NS_DECL_ISUPPORTS + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override + { + return mozilla::dom::PushMessageDataBinding_workers::Wrap(aCx, this, aGivenProto); + } + + nsISupports* GetParentObject() const { + return nullptr; + } + + void Json(JSContext* cx, JS::MutableHandle aRetval); + void Text(nsAString& aData); + void ArrayBuffer(JSContext* cx, JS::MutableHandle aRetval); + mozilla::dom::File* Blob(); + + explicit PushMessageData(const nsAString& aData); +private: + ~PushMessageData(); + +}; + +class PushEvent final : public ExtendableEvent +{ + nsString mData; + nsMainThreadPtrHandle mServiceWorker; + +protected: + explicit PushEvent(mozilla::dom::EventTarget* aOwner); + ~PushEvent() {} + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_TO_EVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle aGivenProto) override + { + return mozilla::dom::PushEventBinding_workers::Wrap(aCx, this, aGivenProto); + } + + static already_AddRefed + Constructor(mozilla::dom::EventTarget* aOwner, + const nsAString& aType, + const PushEventInit& aOptions) + { + nsRefPtr e = new PushEvent(aOwner); + bool trusted = e->Init(aOwner); + e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable); + e->SetTrusted(trusted); + if(aOptions.mData.WasPassed()){ + e->mData = aOptions.mData.Value(); + } + return e.forget(); + } + + static already_AddRefed + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const PushEventInit& aOptions, + ErrorResult& aRv) + { + nsCOMPtr owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, aType, aOptions); + } + + void PostInit(nsMainThreadPtrHandle& aServiceWorker) + { + mServiceWorker = aServiceWorker; + } + + already_AddRefed Data() + { + nsRefPtr data = new PushMessageData(mData); + return data.forget(); + } +}; +#endif /* ! MOZ_SIMPLEPUSH */ + END_WORKERS_NAMESPACE #endif /* mozilla_dom_workers_serviceworkerevents_h__ */ diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 30dd16c30c..da4eaf716f 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -1467,6 +1467,139 @@ public: } }; +#ifndef MOZ_SIMPLEPUSH + +class SendPushEventRunnable final : public WorkerRunnable +{ + nsString mData; + nsMainThreadPtrHandle mServiceWorker; + +public: + SendPushEventRunnable( + WorkerPrivate* aWorkerPrivate, + const nsAString& aData, + nsMainThreadPtrHandle& aServiceWorker) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) + , mData(aData) + , mServiceWorker(aServiceWorker) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper()); + + PushEventInit pei; + pei.mData.Construct(mData); + pei.mBubbles = false; + pei.mCancelable = true; + + ErrorResult result; + nsRefPtr event = + PushEvent::Constructor(globalObj, NS_LITERAL_STRING("push"), pei, result); + if (NS_WARN_IF(result.Failed())) { + return false; + } + + event->SetTrusted(true); + event->PostInit(mServiceWorker); + + nsRefPtr target = do_QueryObject(aWorkerPrivate->GlobalScope()); + target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); + return true; + } +}; + +class SendPushSubscriptionChangeEventRunnable final : public WorkerRunnable +{ + nsMainThreadPtrHandle mServiceWorker; + +public: + SendPushSubscriptionChangeEventRunnable( + WorkerPrivate* aWorkerPrivate, + nsMainThreadPtrHandle& aServiceWorker) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) + , mServiceWorker(aServiceWorker) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + + nsRefPtr target = do_QueryObject(aWorkerPrivate->GlobalScope()); + + nsContentUtils::DispatchTrustedEvent(nullptr, target, + NS_LITERAL_STRING("pushsubscriptionchange"), + true, true); + return true; + } +}; + +#endif /* ! MOZ_SIMPLEPUSH */ + +NS_IMETHODIMP +ServiceWorkerManager::SendPushEvent(const nsACString& aScope, const nsAString& aData) +{ +#ifdef MOZ_SIMPLEPUSH + return NS_ERROR_NOT_AVAILABLE; +#else + nsRefPtr serviceWorker = CreateServiceWorkerForScope(aScope); + if (!serviceWorker) { + return NS_ERROR_FAILURE; + } + + nsMainThreadPtrHandle serviceWorkerHandle( + new nsMainThreadPtrHolder(serviceWorker)); + + nsRefPtr r = + new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), aData, serviceWorkerHandle); + + AutoJSAPI jsapi; + jsapi.Init(); + if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +#endif +} + +NS_IMETHODIMP +ServiceWorkerManager::SendPushSubscriptionChangedEvent(const nsACString& aScope) +{ +#ifdef MOZ_SIMPLEPUSH + return NS_ERROR_NOT_AVAILABLE; +#else + nsRefPtr serviceWorker = CreateServiceWorkerForScope(aScope); + if (!serviceWorker) { + return NS_ERROR_FAILURE; + } + nsMainThreadPtrHandle serviceWorkerHandle( + new nsMainThreadPtrHolder(serviceWorker)); + + nsRefPtr r = + new SendPushSubscriptionChangeEventRunnable(serviceWorker->GetWorkerPrivate(), serviceWorkerHandle); + + AutoJSAPI jsapi; + jsapi.Init(); + if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +#endif +} + NS_IMETHODIMP ServiceWorkerManager::GetReadyPromise(nsIDOMWindow* aWindow, nsISupports** aPromise) @@ -1568,6 +1701,37 @@ ServiceWorkerManager::CheckReadyPromise(nsPIDOMWindow* aWindow, return false; } +already_AddRefed +ServiceWorkerManager::CreateServiceWorkerForScope(const nsACString& aScope) +{ + AssertIsOnMainThread(); + + nsCOMPtr scopeURI; + nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr); + if (NS_FAILED(rv)) { + return nullptr; + } + nsRefPtr registration = GetServiceWorkerRegistrationInfo(scopeURI); + if (!registration) { + return nullptr; + } + + if (!registration->mActiveWorker) { + return nullptr; + } + + nsRefPtr sw; + rv = CreateServiceWorker(registration->mPrincipal, + registration->mActiveWorker, + getter_AddRefs(sw)); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return sw.forget(); +} + class ServiceWorkerUnregisterJob final : public ServiceWorkerJob { nsRefPtr mRegistration; @@ -2320,9 +2484,9 @@ private: nsRefPtr internalHeaders = new InternalHeaders(HeadersGuardEnum::Request); MOZ_ASSERT(mHeaderNames.Length() == mHeaderValues.Length()); for (uint32_t i = 0; i < mHeaderNames.Length(); i++) { - ErrorResult rv; - internalHeaders->Set(mHeaderNames[i], mHeaderValues[i], rv); - if (NS_WARN_IF(rv.Failed())) { + ErrorResult result; + internalHeaders->Set(mHeaderNames[i], mHeaderValues[i], result); + if (NS_WARN_IF(result.Failed())) { return false; } } @@ -2336,9 +2500,9 @@ private: reqInit.mMode.Construct(mRequestMode); reqInit.mCredentials.Construct(mRequestCredentials); - ErrorResult rv; - nsRefPtr request = Request::Constructor(globalObj, requestInfo, reqInit, rv); - if (NS_WARN_IF(rv.Failed())) { + ErrorResult result; + nsRefPtr request = Request::Constructor(globalObj, requestInfo, reqInit, result); + if (NS_WARN_IF(result.Failed())) { return false; } @@ -2351,8 +2515,8 @@ private: init.mCancelable = true; init.mIsReload.Construct(mIsReload); nsRefPtr event = - FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, rv); - if (NS_WARN_IF(rv.Failed())) { + FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result); + if (NS_WARN_IF(result.Failed())) { return false; } diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h index 6088134830..fe9627e53f 100644 --- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -427,6 +427,9 @@ private: WhichServiceWorker aWhichWorker, nsISupports** aServiceWorker); + already_AddRefed + CreateServiceWorkerForScope(const nsACString& aScope); + void InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration, WhichServiceWorker aWhichOnes); diff --git a/dom/workers/ServiceWorkerRegistration.cpp b/dom/workers/ServiceWorkerRegistration.cpp index e5a67ee43e..dd8656f54e 100644 --- a/dom/workers/ServiceWorkerRegistration.cpp +++ b/dom/workers/ServiceWorkerRegistration.cpp @@ -19,6 +19,10 @@ #include "nsISupportsPrimitives.h" #include "nsPIDOMWindow.h" +#ifndef MOZ_SIMPLEPUSH +#include "mozilla/dom/PushManagerBinding.h" +#endif + using namespace mozilla::dom::workers; namespace mozilla { @@ -30,12 +34,24 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper) +#ifdef MOZ_SIMPLEPUSH + NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper, mInstallingWorker, mWaitingWorker, mActiveWorker) +#else + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistration, + DOMEventTargetHelper, + mInstallingWorker, + mWaitingWorker, + mActiveWorker, + mPushManager) +#endif + ServiceWorkerRegistration::ServiceWorkerRegistration(nsPIDOMWindow* aWindow, const nsAString& aScope) : DOMEventTargetHelper(aWindow) @@ -291,5 +307,55 @@ ServiceWorkerRegistration::StopListeningForEvents() } } +already_AddRefed +ServiceWorkerRegistration::GetPushManager(ErrorResult& aRv) +{ + AssertIsOnMainThread(); + +#ifdef MOZ_SIMPLEPUSH + return nullptr; +#else + + if (!mPushManager) { + nsCOMPtr globalObject = do_QueryInterface(GetOwner()); + + if (!globalObject) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(globalObject))) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + JSContext* cx = jsapi.cx(); + + JS::RootedObject globalJs(cx, globalObject->GetGlobalJSObject()); + GlobalObject global(cx, globalJs); + + // TODO: bug 1148117. This will fail when swr is exposed on workers + JS::Rooted jsImplObj(cx); + nsCOMPtr unused = ConstructJSImplementation(cx, "@mozilla.org/push/PushManager;1", + global, &jsImplObj, aRv); + if (aRv.Failed()) { + return nullptr; + } + mPushManager = new PushManager(jsImplObj, globalObject); + + mPushManager->SetScope(mScope, aRv); + if (aRv.Failed()) { + mPushManager = nullptr; + return nullptr; + } + } + + nsRefPtr ret = mPushManager; + return ret.forget(); + + #endif /* ! MOZ_SIMPLEPUSH */ +} + } // dom namespace } // mozilla namespace diff --git a/dom/workers/ServiceWorkerRegistration.h b/dom/workers/ServiceWorkerRegistration.h index 694687f74e..f3222300fa 100644 --- a/dom/workers/ServiceWorkerRegistration.h +++ b/dom/workers/ServiceWorkerRegistration.h @@ -17,6 +17,7 @@ namespace mozilla { namespace dom { class Promise; +class PushManager; namespace workers { class ServiceWorker; @@ -62,6 +63,16 @@ public: // DOMEventTargethelper virtual void DisconnectFromOwner() override; + already_AddRefed + GetPushManager(ErrorResult& aRv); + + // Something that we can feed into the Func webidl property to ensure that + // SetScope is never exposed to the user. + static bool + WebPushMethodHider(JSContext* unusedContext, JSObject* unusedObject) { + return false; + } + private: ~ServiceWorkerRegistration(); @@ -82,6 +93,10 @@ private: nsRefPtr mWaitingWorker; nsRefPtr mActiveWorker; +#ifndef MOZ_SIMPLEPUSH + nsRefPtr mPushManager; +#endif + const nsString mScope; bool mListeningForEvents; }; @@ -89,4 +104,4 @@ private: } // namespace dom } // namespace mozilla -#endif /* mozilla_dom_ServiceWorkerRegistration_h */ +#endif /* mozilla_dom_ServiceWorkerRegistration_h */ \ No newline at end of file diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js index c9c675934d..848a3b77d5 100644 --- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js +++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js @@ -44,6 +44,8 @@ var ecmaGlobals = "Number", "Object", "Proxy", + "PushEvent", + "PushMessageData", "RangeError", "ReferenceError", "RegExp", diff --git a/image/encoders/png/nsPNGEncoder.cpp b/image/encoders/png/nsPNGEncoder.cpp index 37f5366432..d9d34d5cc4 100644 --- a/image/encoders/png/nsPNGEncoder.cpp +++ b/image/encoders/png/nsPNGEncoder.cpp @@ -660,14 +660,17 @@ nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, uint8_t* pixelOut = &aDest[x * pixelStride]; uint8_t alpha = (pixelIn & 0xff000000) >> 24; - if (alpha == 0) { - pixelOut[0] = pixelOut[1] = pixelOut[2] = pixelOut[3] = 0; + pixelOut[pixelStride - 1] = alpha; // overwritten below if pixelStride == 3 + if (alpha == 255) { + pixelOut[0] = (pixelIn & 0xff0000) >> 16; + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; + pixelOut[2] = (pixelIn & 0x0000ff) ; + } else if (alpha == 0) { + pixelOut[0] = pixelOut[1] = pixelOut[2] = 0; } else { pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; - pixelOut[2] = (((pixelIn & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; - if (aUseTransparency) - pixelOut[3] = alpha; + pixelOut[2] = (((pixelIn & 0x0000ff) ) * 255 + alpha / 2) / alpha; } } } diff --git a/image/test/mochitest/test_ImageContentLoaded.html b/image/test/mochitest/test_ImageContentLoaded.html index 38d3238a1d..7e483a792a 100644 --- a/image/test/mochitest/test_ImageContentLoaded.html +++ b/image/test/mochitest/test_ImageContentLoaded.html @@ -26,3 +26,31 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=691610 + + + + +Test for Bug 691610 + + + + + + + diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 2006e4d25a..cb79acb239 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -710,18 +710,22 @@ gc::MarkGCThingUnbarriered(JSTracer* trc, void** thingp, const char* name) /*** ID Marking ***/ static inline void -MarkIdInternal(JSTracer* trc, jsid* id) +MarkIdInternal(JSTracer *trc, jsid *id) { if (JSID_IS_STRING(*id)) { - JSString* str = JSID_TO_STRING(*id); - trc->setTracingLocation((void*)id); + JSString *str = JSID_TO_STRING(*id); + JSString *prior = str; + trc->setTracingLocation((void *)id); MarkInternal(trc, &str); - *id = NON_INTEGER_ATOM_TO_JSID(reinterpret_cast(str)); + if (str != prior) + *id = NON_INTEGER_ATOM_TO_JSID(reinterpret_cast(str)); } else if (JSID_IS_SYMBOL(*id)) { - JS::Symbol* sym = JSID_TO_SYMBOL(*id); - trc->setTracingLocation((void*)id); + JS::Symbol *sym = JSID_TO_SYMBOL(*id); + JS::Symbol *prior = sym; + trc->setTracingLocation((void *)id); MarkInternal(trc, &sym); - *id = SYMBOL_TO_JSID(sym); + if (sym != prior) + *id = SYMBOL_TO_JSID(sym); } else { /* Unset realLocation manually if we do not call MarkInternal. */ trc->unsetTracingLocation(); @@ -772,20 +776,28 @@ gc::MarkIdRootRange(JSTracer* trc, size_t len, jsid* vec, const char* name) /*** Value Marking ***/ static inline void -MarkValueInternal(JSTracer* trc, Value* v) +MarkValueInternal(JSTracer *trc, Value *v) { if (v->isMarkable()) { MOZ_ASSERT(v->toGCThing()); - void* thing = v->toGCThing(); - trc->setTracingLocation((void*)v); - MarkKind(trc, &thing, v->gcKind()); + void *thing = v->toGCThing(); + trc->setTracingLocation((void *)v); if (v->isString()) { - v->setString((JSString*)thing); + JSString *str = static_cast(thing); + MarkInternal(trc, &str); + if (str != thing) + v->setString(str); } else if (v->isObject()) { - v->setObjectOrNull((JSObject*)thing); + JSObject *obj = static_cast(thing); + MarkInternal(trc, &obj); + if (obj != thing) + v->setObjectOrNull(obj); } else { MOZ_ASSERT(v->isSymbol()); - v->setSymbol((JS::Symbol*)thing); + JS::Symbol *sym = static_cast(thing); + MarkInternal(trc, &sym); + if (sym != thing) + v->setSymbol(sym); } } else { /* Unset realLocation manually if we do not call MarkInternal. */ diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index f89d94b3a0..737589213b 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1356,8 +1356,8 @@ skip-if(B2G&&browserIsRemote) == 486848-1.xul 486848-1-ref.xul # bug 974780 == 490177-1.svg 490177-1-ref.svg == 490182-1a.html 490182-1-ref.html == 490182-1b.html 490182-1-ref.html -== 491180-1.html 491180-1-ref.html -== 491180-2.html 491180-2-ref.html +pref(browser.display.focus_ring_width,1) == 491180-1.html 491180-1-ref.html +pref(browser.display.focus_ring_width,1) == 491180-2.html 491180-2-ref.html skip-if(B2G&&browserIsRemote) == 491323-1.xul 491323-1-ref.xul # bug 974780 skip-if(B2G&&browserIsRemote) == 492239-1.xul 492239-1-ref.xul # bug 974780 == 492661-1.html 492661-1-ref.html diff --git a/layout/reftests/forms/button/reftest.list b/layout/reftests/forms/button/reftest.list index 99fe73a49f..b6daa6646d 100644 --- a/layout/reftests/forms/button/reftest.list +++ b/layout/reftests/forms/button/reftest.list @@ -7,9 +7,9 @@ # on B2G, despite their "-moz-appearance: none; background: gray", so they # don't quite match the reference case's normal
. That's why they're fuzzy. fuzzy-if(B2G||Android,125,20) == percent-height-child-1.html percent-height-child-1-ref.html -fuzzy-if(B2G||Android,125,80) == percent-height-child-2.html percent-height-child-2-ref.html +pref(browser.display.focus_ring_width,1) fuzzy-if(B2G||Android,125,80) == percent-height-child-2.html percent-height-child-2-ref.html fuzzy-if(B2G||Android,125,20) == percent-width-child-1.html percent-width-child-1-ref.html -fuzzy-if(B2G||Android,125,80) == percent-width-child-2.html percent-width-child-2-ref.html +pref(browser.display.focus_ring_width,1) fuzzy-if(B2G||Android,125,80) == percent-width-child-2.html percent-width-child-2-ref.html == vertical-centering.html vertical-centering-ref.html diff --git a/netwerk/cache2/CacheFileContextEvictor.cpp b/netwerk/cache2/CacheFileContextEvictor.cpp index b1bb6ebf34..c4980f07c1 100644 --- a/netwerk/cache2/CacheFileContextEvictor.cpp +++ b/netwerk/cache2/CacheFileContextEvictor.cpp @@ -551,7 +551,7 @@ CacheFileContextEvictor::EvictEntries() mEntries[0]->mIterator.get(), mEntries[0]->mInfo.get())); nsRefPtr handle; - CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false, + CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, getter_AddRefs(handle)); if (handle) { // We doom any active handle in CacheFileIOManager::EvictByContext(), so diff --git a/netwerk/cache2/CacheFileIOManager.cpp b/netwerk/cache2/CacheFileIOManager.cpp index a62437e65f..e4676da4c0 100644 --- a/netwerk/cache2/CacheFileIOManager.cpp +++ b/netwerk/cache2/CacheFileIOManager.cpp @@ -309,7 +309,6 @@ CacheFileHandles::~CacheFileHandles() nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash, - bool aReturnDoomed, CacheFileHandle **_retval) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); @@ -343,16 +342,12 @@ CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash, if (handle->IsDoomed()) { LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " "found doomed handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); - - // If the consumer doesn't want doomed handles, exit with NOT_AVAIL. - if (!aReturnDoomed) { - return NS_ERROR_NOT_AVAILABLE; - } - } else { - LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " - "found handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); + return NS_ERROR_NOT_AVAILABLE; } + LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " + "found handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); + handle.forget(_retval); return NS_OK; } @@ -809,7 +804,7 @@ public: if (!mIOMan) { rv = NS_ERROR_NOT_INITIALIZED; } else { - rv = mIOMan->DoomFileByKeyInternal(&mHash, false); + rv = mIOMan->DoomFileByKeyInternal(&mHash); mIOMan = nullptr; } @@ -1571,7 +1566,7 @@ CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash, NS_ENSURE_SUCCESS(rv, rv); nsRefPtr handle; - mHandles.GetHandle(aHash, false, getter_AddRefs(handle)); + mHandles.GetHandle(aHash, getter_AddRefs(handle)); if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) { if (handle) { @@ -2070,11 +2065,10 @@ CacheFileIOManager::DoomFileByKey(const nsACString &aKey, } nsresult -CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash, - bool aFailIfAlreadyDoomed) +CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash) { - LOG(("CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x," - " failIfAlreadyDoomed=%d]", LOGSHA1(aHash), aFailIfAlreadyDoomed)); + LOG(("CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]" + , LOGSHA1(aHash))); MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); @@ -2090,15 +2084,11 @@ CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash, // Find active handle nsRefPtr handle; - mHandles.GetHandle(aHash, true, getter_AddRefs(handle)); + mHandles.GetHandle(aHash, getter_AddRefs(handle)); if (handle) { handle->Log(); - if (handle->IsDoomed()) { - return aFailIfAlreadyDoomed ? NS_ERROR_NOT_AVAILABLE : NS_OK; - } - return DoomFileInternal(handle); } @@ -2240,7 +2230,7 @@ CacheFileIOManager::GetEntryInfo(const SHA1Sum::Hash *aHash, nsAutoCString uriSpec; nsRefPtr handle; - ioMan->mHandles.GetHandle(aHash, false, getter_AddRefs(handle)); + ioMan->mHandles.GetHandle(aHash, getter_AddRefs(handle)); if (handle) { nsRefPtr info = CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec); @@ -2611,7 +2601,7 @@ CacheFileIOManager::OverLimitEvictionInternal() rv = CacheIndex::GetEntryForEviction(false, &hash, &cnt); NS_ENSURE_SUCCESS(rv, rv); - rv = DoomFileByKeyInternal(&hash, true); + rv = DoomFileByKeyInternal(&hash); if (NS_SUCCEEDED(rv)) { consecutiveFailures = 0; } else if (rv == NS_ERROR_NOT_AVAILABLE) { @@ -2619,16 +2609,6 @@ CacheFileIOManager::OverLimitEvictionInternal() "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv)); // TODO index is outdated, start update -#ifdef DEBUG - // Dooming should never fail due to already doomed handle, but bug 1028415 - // shows that this unexpected state can happen. Assert in debug build so - // we can find the cause if we ever find a way to reproduce it with NSPR - // logging enabled. - nsRefPtr handle; - mHandles.GetHandle(&hash, true, getter_AddRefs(handle)); - MOZ_ASSERT(!handle || !handle->IsDoomed()); -#endif - // Make sure index won't return the same entry again CacheIndex::RemoveEntry(&hash); consecutiveFailures = 0; @@ -3616,7 +3596,7 @@ CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate) rv = CacheIndex::GetEntryForEviction(true, &hash, &cnt); if (NS_SUCCEEDED(rv)) { - rv = DoomFileByKeyInternal(&hash, true); + rv = DoomFileByKeyInternal(&hash); } if (NS_SUCCEEDED(rv)) { rv = aHandle->mFile->OpenNSPRFileDesc( diff --git a/netwerk/cache2/CacheFileIOManager.h b/netwerk/cache2/CacheFileIOManager.h index d44048db22..1355dc631c 100644 --- a/netwerk/cache2/CacheFileIOManager.h +++ b/netwerk/cache2/CacheFileIOManager.h @@ -90,7 +90,7 @@ public: CacheFileHandles(); ~CacheFileHandles(); - nsresult GetHandle(const SHA1Sum::Hash *aHash, bool aReturnDoomed, CacheFileHandle **_retval); + nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval); nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval); void RemoveHandle(CacheFileHandle *aHandlle); void GetAllHandles(nsTArray > *_retval); @@ -330,8 +330,7 @@ private: nsresult WriteInternal(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf, int32_t aCount, bool aValidate); nsresult DoomFileInternal(CacheFileHandle *aHandle); - nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash, - bool aFailIfAlreadyDoomed); + nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash); nsresult ReleaseNSPRHandleInternal(CacheFileHandle *aHandle); nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle, int64_t aTruncatePos, int64_t aEOFPos); diff --git a/netwerk/cache2/CacheIndex.cpp b/netwerk/cache2/CacheIndex.cpp index 4b851ab249..a1dbf9b50e 100644 --- a/netwerk/cache2/CacheIndex.cpp +++ b/netwerk/cache2/CacheIndex.cpp @@ -1277,7 +1277,7 @@ bool CacheIndex::IsForcedValidEntry(const SHA1Sum::Hash *aHash) nsRefPtr handle; CacheFileIOManager::gInstance->mHandles.GetHandle( - aHash, false, getter_AddRefs(handle)); + aHash, getter_AddRefs(handle)); if (!handle) return false; @@ -2752,7 +2752,7 @@ CacheIndex::BuildIndex() #ifdef DEBUG nsRefPtr handle; - CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false, + CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, getter_AddRefs(handle)); #endif @@ -2965,7 +2965,7 @@ CacheIndex::UpdateIndex() #ifdef DEBUG nsRefPtr handle; - CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false, + CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, getter_AddRefs(handle)); #endif diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 5325552395..53b2761fb2 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -4272,12 +4272,14 @@ nsHttpChannel::InstallCacheListener(int64_t offset) do_CreateInstance(kStreamListenerTeeCID, &rv); if (NS_FAILED(rv)) return rv; - nsCOMPtr serv = - do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr cacheIOTarget; - serv->GetIoTarget(getter_AddRefs(cacheIOTarget)); + if (!CacheObserver::UseNewCache()) { + nsCOMPtr serv = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + serv->GetIoTarget(getter_AddRefs(cacheIOTarget)); + } if (!cacheIOTarget) { LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x " diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index d5ffd745f6..714f54ad02 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -2761,7 +2761,7 @@ nsHttpConnectionMgr::ActivateTimeoutTick() { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() " - "this=%p mTimeoutTick=%p\n")); + "this=%p mTimeoutTick=%p\n", this, mTimeoutTick.get())); // The timer tick should be enabled if it is not already pending. // Upon running the tick will rearm itself if there are active diff --git a/python/lldbutils/lldbutils/gfx.py b/python/lldbutils/lldbutils/gfx.py index 8211a62ada..1ad9a37a7f 100644 --- a/python/lldbutils/lldbutils/gfx.py +++ b/python/lldbutils/lldbutils/gfx.py @@ -110,14 +110,13 @@ def rect_is_empty(valobj): def summarize_region(valobj, internal_dict): # This function makes use of the synthetic children generated for ns(Int)Regions. bounds = valobj.GetChildMemberWithName("bounds") - num_rects = valobj.GetChildMemberWithName("numRects").GetValueAsUnsigned(0) - if num_rects == 0: - return "empty" bounds_summary = summarize_rect(bounds, internal_dict) - if num_rects == 1: + num_rects = valobj.GetChildMemberWithName("numRects").GetValueAsUnsigned(0) + if num_rects <= 1: if rect_is_empty(bounds): return "empty" - return "one rect: " + bounds_summary + if num_rects == 1: + return "one rect: " + bounds_summary return str(num_rects) + " rects, bounds: " + bounds_summary def init(debugger): diff --git a/python/mozbuild/mozpack/path.py b/python/mozbuild/mozpack/path.py index 20e9053e82..1b71ace78e 100644 --- a/python/mozbuild/mozpack/path.py +++ b/python/mozbuild/mozpack/path.py @@ -99,12 +99,12 @@ def match(path, pattern): ''' if not pattern: return True - if not pattern in re_cache: - pattern = re.escape(pattern) - pattern = re.sub(r'(^|\\\/)\\\*\\\*\\\/', r'\1(?:.+/)?', pattern) - pattern = re.sub(r'(^|\\\/)\\\*\\\*$', r'(?:\1.+)?', pattern) - pattern = pattern.replace(r'\*', '[^/]*') + '(?:/.*)?$' - re_cache[pattern] = re.compile(pattern) + if pattern not in re_cache: + p = re.escape(pattern) + p = re.sub(r'(^|\\\/)\\\*\\\*\\\/', r'\1(?:.+/)?', p) + p = re.sub(r'(^|\\\/)\\\*\\\*$', r'(?:\1.+)?', p) + p = p.replace(r'\*', '[^/]*') + '(?:/.*)?$' + re_cache[pattern] = re.compile(p) return re_cache[pattern].match(path) is not None diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py index f778a4d169..ebae8570db 100644 --- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -2078,12 +2078,17 @@ class Mochitest(MochitestUtilsMixin): options, debuggerInfo is not None) + # If there are any Mulet-specific tests doing remote network access, # we will not be aware since we are explicitely allowing this, as for # B2G - if mozinfo.info.get( - 'buildapp') == 'mulet' and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: - del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] + # + # In addition, the push subsuite directly accesses the production + # push service. + if 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: + if mozinfo.info.get('buildapp') == 'mulet' or options.subsuite == 'push': + del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] + os.environ["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "0" if self.browserEnv is None: return 1 diff --git a/toolkit/modules/BrowserUtils.jsm b/toolkit/modules/BrowserUtils.jsm index 2154295d06..be4ac724e8 100644 --- a/toolkit/modules/BrowserUtils.jsm +++ b/toolkit/modules/BrowserUtils.jsm @@ -217,8 +217,11 @@ this.BrowserUtils = { * @return a boolean indicating if linkNode has a rel="noreferrer" attribute. */ linkHasNoReferrer: function (linkNode) { + // A null linkNode typically means that we're checking a link that wasn't + // provided via an link, like a text-selected URL. Don't leak + // referrer information in this case. if (!linkNode) - return false; + return true; let rel = linkNode.getAttribute("rel"); if (!rel) diff --git a/toolkit/modules/RemoteSecurityUI.jsm b/toolkit/modules/RemoteSecurityUI.jsm index a59176042e..e860679b97 100644 --- a/toolkit/modules/RemoteSecurityUI.jsm +++ b/toolkit/modules/RemoteSecurityUI.jsm @@ -1,3 +1,51 @@ +// -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +this.EXPORTED_SYMBOLS = ["RemoteSecurityUI"]; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +function RemoteSecurityUI() +{ + this._state = 0; + this._SSLStatus = null; +} + +RemoteSecurityUI.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISSLStatusProvider, Ci.nsISecureBrowserUI]), + + // nsISecureBrowserUI + get state() { return this._state; }, + get tooltipText() { return ""; }, + + // nsISSLStatusProvider + get SSLStatus() { return this._SSLStatus; }, + + _update: function (state, status) { + let deserialized = null; + if (status) { + let helper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Components.interfaces.nsISerializationHelper); + + deserialized = helper.deserializeObject(status) + deserialized.QueryInterface(Ci.nsISSLStatus); + } + + // We must check the Extended Validation (EV) state here, on the chrome + // process, because NSS is needed for that determination. + if (deserialized && deserialized.isExtendedValidation) + state |= Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL; + + this._state = state; + this._SSLStatus = deserialized; + } +}; // -*- indent-tabs-mode: nil; js-indent-level: 2 -*- // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/widget/windows/WinMouseScrollHandler.h b/widget/windows/WinMouseScrollHandler.h index 7689c915a9..c4725bb58e 100644 --- a/widget/windows/WinMouseScrollHandler.h +++ b/widget/windows/WinMouseScrollHandler.h @@ -289,8 +289,8 @@ private: bool IsPageScroll(bool aForVertical) const { MOZ_ASSERT(mInitialized, "SystemSettings must be initialized"); - return aForVertical ? (mScrollLines == WHEEL_PAGESCROLL) : - (mScrollChars == WHEEL_PAGESCROLL); + return aForVertical ? (uint32_t(mScrollLines) == WHEEL_PAGESCROLL) : + (uint32_t(mScrollChars) == WHEEL_PAGESCROLL); } private: