diff options
277 files changed, 5243 insertions, 1665 deletions
diff --git a/.eslintrc.js b/.eslintrc.js index a7a886292..3d3ad2b80 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,6 +7,9 @@ module.exports = { ], "rules": { "mozilla/import-globals": "warn", + + // No (!foo in bar) or (!object instanceof Class) + "no-unsafe-negation": "error", }, "env": { "es6": true @@ -12,6 +12,12 @@ applications. This repository will contain at least one application to demonstrate and make use of the platform: The Basilisk web browser, a close twin to Mozilla's Firefox. +## Additional documentation + +Additional documentation relevant to this source code can be found in the `/docs` +directory. This will contain relevant documentation regarding contributing, +using and distributing this code and its binaries. + ### A note about trademarks and branding Although this repository is licensed under Mozilla Public License v2.0, the diff --git a/application/palemoon/app/Makefile.in b/application/palemoon/app/Makefile.in index 6f9bbfaf0..c0f01212c 100644 --- a/application/palemoon/app/Makefile.in +++ b/application/palemoon/app/Makefile.in @@ -76,7 +76,7 @@ AB := $(firstword $(subst -, ,$(AB_CD))) clean clobber repackage:: $(RM) -r $(dist_dest) -MAC_BUNDLE_VERSION = $(shell $(PYTHON) $(srcdir)/macversion.py --version=$(MOZ_APP_VERSION) --buildid=$(DEPTH)/config/buildid) +MAC_BUNDLE_VERSION = $(shell $(PYTHON) $(srcdir)/macversion.py --version=$(MOZ_APP_VERSION) --buildid=$(DEPTH)/buildid.h) .PHONY: repackage tools repackage:: $(PROGRAM) diff --git a/application/palemoon/app/macversion.py b/application/palemoon/app/macversion.py index 8c360368e..839aac1ff 100644 --- a/application/palemoon/app/macversion.py +++ b/application/palemoon/app/macversion.py @@ -28,7 +28,7 @@ if not options.version: # builds), but also so that newly-built older versions (e.g. beta build) aren't # considered "newer" than previously-built newer versions (e.g. a trunk nightly) -buildid = open(options.buildid, 'r').read() +define, MOZ_BUILDID, buildid = open(options.buildid, 'r').read().split() # extract only the major version (i.e. "14" from "14.0b1") majorVersion = re.match(r'^(\d+)[^\d].*', options.version).group(1) diff --git a/application/palemoon/app/profile/palemoon.js b/application/palemoon/app/profile/palemoon.js index 20919eca4..f0e860749 100644 --- a/application/palemoon/app/profile/palemoon.js +++ b/application/palemoon/app/profile/palemoon.js @@ -236,6 +236,15 @@ pref("general.useragent.complexOverride.moodle", false); // bug 797703 // At startup, check if we're the default browser and prompt user if not. pref("browser.shell.checkDefaultBrowser", true); pref("browser.shell.shortcutFavicons",true); +pref("browser.shell.mostRecentDateSetAsDefault", ""); +#ifdef RELEASE_OR_BETA +pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", false); +#else +pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true); +#endif +pref("browser.shell.skipDefaultBrowserCheck", true); +pref("browser.shell.defaultBrowserCheckCount", 0); +pref("browser.defaultbrowser.notificationbar", false); // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore diff --git a/application/palemoon/base/content/browser-fullScreen.js b/application/palemoon/base/content/browser-fullScreen.js index 73b10ae85..ffe1da450 100644 --- a/application/palemoon/base/content/browser-fullScreen.js +++ b/application/palemoon/base/content/browser-fullScreen.js @@ -5,16 +5,9 @@ var FullScreen = { _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", - get _fullScrToggler() { - delete this._fullScrToggler; - return this._fullScrToggler = document.getElementById("fullscr-toggler"); - }, - toggle: function (event) { - var enterFS = window.fullScreen; - // We get the fullscreen event _before_ the window transitions into or out of FS mode. - if (event && event.type == "fullscreen") - enterFS = !enterFS; + toggle: function () { + var enterFS = window.fullScreen; // Toggle the View:FullScreen command, which controls elements like the // fullscreen menuitem, menubars, and the appmenu. @@ -31,6 +24,12 @@ var FullScreen = { document.getElementById("exitFullScreenItem").hidden = !enterFS; #endif + if (!this._fullScrToggler) { + this._fullScrToggler = document.getElementById("fullscr-toggler"); + this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false); + this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false); + } + // On OS X Lion we don't want to hide toolbars when entering fullscreen, unless // we're entering DOM fullscreen, in which case we should hide the toolbars. // If we're leaving fullscreen, then we'll go through the exit code below to @@ -50,36 +49,17 @@ var FullScreen = { this.showXULChrome("toolbar", !enterFS); if (enterFS) { - // Add a tiny toolbar to receive mouseover and dragenter events, and provide affordance. - // This will help simulate the "collapse" metaphor while also requiring less code and - // events than raw listening of mouse coords. We don't add the toolbar in DOM full-screen - // mode, only browser full-screen mode. - if (!document.mozFullScreen) { - this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false); - this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false); - } - if (gPrefService.getBoolPref("browser.fullscreen.autohide")) - gBrowser.mPanelContainer.addEventListener("mousemove", - this._collapseCallback, false); - document.addEventListener("keypress", this._keyToggleCallback, false); document.addEventListener("popupshown", this._setPopupOpen, false); document.addEventListener("popuphidden", this._setPopupOpen, false); + this._shouldAnimate = true; // We don't animate the toolbar collapse if in DOM full-screen mode, // as the size of the content area would still be changing after the // mozfullscreenchange event fired, which could confuse content script. - this._shouldAnimate = !document.mozFullScreen; - this.mouseoverToggle(false); - - // Autohide prefs - gPrefService.addObserver("browser.fullscreen", this, false); + this.hideNavToolbox(document.mozFullScreen); } else { - // The user may quit fullscreen during an animation - this._cancelAnimation(); - gNavToolbox.style.marginTop = ""; - if (this._isChromeCollapsed) - this.mouseoverToggle(true); + this.showNavToolbox(false); // This is needed if they use the context menu to quit fullscreen this._isPopupOpen = false; @@ -152,26 +132,16 @@ var FullScreen = { // Cancel any "hide the toolbar" animation which is in progress, and make // the toolbar hide immediately. - this._cancelAnimation(); - this.mouseoverToggle(false); - - // Remove listeners on the full-screen toggler, so that mouseover - // the top of the screen will not cause the toolbar to re-appear. - this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false); - this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false); + this.hideNavToolbox(true); }, cleanup: function () { - if (window.fullScreen) { - gBrowser.mPanelContainer.removeEventListener("mousemove", - this._collapseCallback, false); + if (!window.fullScreen) { + MousePosTracker.removeListener(this); document.removeEventListener("keypress", this._keyToggleCallback, false); document.removeEventListener("popupshown", this._setPopupOpen, false); document.removeEventListener("popuphidden", this._setPopupOpen, false); - gPrefService.removeObserver("browser.fullscreen", this); - this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false); - this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false); this.cancelWarning(); gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen); gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen); @@ -182,40 +152,30 @@ var FullScreen = { } }, - observe: function(aSubject, aTopic, aData) + getMouseTargetRect: function() { - if (aData == "browser.fullscreen.autohide") { - if (gPrefService.getBoolPref("browser.fullscreen.autohide")) { - gBrowser.mPanelContainer.addEventListener("mousemove", - this._collapseCallback, false); - } - else { - gBrowser.mPanelContainer.removeEventListener("mousemove", - this._collapseCallback, false); - } - } + return this._mouseTargetRect; }, // Event callbacks _expandCallback: function() { - FullScreen.mouseoverToggle(true); + FullScreen.showNavToolbox(); }, - _collapseCallback: function() + onMouseEnter: function() { - FullScreen.mouseoverToggle(false); + FullScreen.hideNavToolbox(); }, _keyToggleCallback: function(aEvent) { // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we // should provide a way to collapse them too. if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) { - FullScreen._shouldAnimate = false; - FullScreen.mouseoverToggle(false, true); + FullScreen.hideNavToolbox(true); } // F6 is another shortcut to the address bar, but its not covered in OpenLocation() else if (aEvent.keyCode == aEvent.DOM_VK_F6) - FullScreen.mouseoverToggle(true); + FullScreen.showNavToolbox(); }, // Checks whether we are allowed to collapse the chrome @@ -269,47 +229,6 @@ var FullScreen = { // Animate the toolbars disappearing _shouldAnimate: true, - _isAnimating: false, - _animationTimeout: 0, - _animationHandle: 0, - _animateUp: function() { - // check again, the user may have done something before the animation was due to start - if (!window.fullScreen || !this._safeToCollapse(false)) { - this._isAnimating = false; - this._shouldAnimate = true; - return; - } - - this._animateStartTime = window.mozAnimationStartTime; - if (!this._animationHandle) - this._animationHandle = window.mozRequestAnimationFrame(this); - }, - - sample: function (timeStamp) { - const duration = 1500; - const timePassed = timeStamp - this._animateStartTime; - const pos = timePassed >= duration ? 1 : - 1 - Math.pow(1 - timePassed / duration, 4); - - if (pos >= 1) { - // We've animated enough - this._cancelAnimation(); - gNavToolbox.style.marginTop = ""; - this.mouseoverToggle(false); - return; - } - - gNavToolbox.style.marginTop = (gNavToolbox.boxObject.height * pos * -1) + "px"; - this._animationHandle = window.mozRequestAnimationFrame(this); - }, - - _cancelAnimation: function() { - window.cancelAnimationFrame(this._animationHandle); - this._animationHandle = 0; - clearTimeout(this._animationTimeout); - this._isAnimating = false; - this._shouldAnimate = false; - }, cancelWarning: function(event) { if (!this.warningBox) @@ -463,49 +382,69 @@ var FullScreen = { 3000); }, - mouseoverToggle: function(aShow, forceHide) - { - // Don't do anything if: - // a) we're already in the state we want, - // b) we're animating and will become collapsed soon, or - // c) we can't collapse because it would be undesirable right now - if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) || - (!aShow && !this._safeToCollapse(forceHide))) + showNavToolbox: function(trackMouse = true) { + this._fullScrToggler.hidden = true; + gNavToolbox.removeAttribute("fullscreenShouldAnimate"); + gNavToolbox.style.marginTop = ""; + + if (!this._isChromeCollapsed) { + return; + } + + // Track whether mouse is near the toolbox + this._isChromeCollapsed = false; + if (trackMouse) { + let rect = gBrowser.mPanelContainer.getBoundingClientRect(); + this._mouseTargetRect = { + top: rect.top + 50, + bottom: rect.bottom, + left: rect.left, + right: rect.right + }; + MousePosTracker.addListener(this); + } + }, + + hideNavToolbox: function(forceHide = false) { + this._fullScrToggler.hidden = document.mozFullScreen; + if (this._isChromeCollapsed) { + if (forceHide) { + gNavToolbox.removeAttribute("fullscreenShouldAnimate"); + } + return; + } + if (!this._safeToCollapse(forceHide)) { + this._fullScrToggler.hidden = true; return; + } // browser.fullscreen.animateUp // 0 - never animate up // 1 - animate only for first collapse after entering fullscreen (default for perf's sake) // 2 - animate every time it collapses - if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 0) - this._shouldAnimate = false; - - if (!aShow && this._shouldAnimate) { - this._isAnimating = true; + let animateUp = gPrefService.getIntPref("browser.fullscreen.animateUp"); + if (animateUp == 0) { this._shouldAnimate = false; - this._animationTimeout = setTimeout(this._animateUp.bind(this), 800); - return; - } - - // The chrome is collapsed so don't spam needless mousemove events - if (aShow) { - gBrowser.mPanelContainer.addEventListener("mousemove", - this._collapseCallback, false); + } else if (animateUp == 2) { + this._shouldAnimate = true; } - else { - gBrowser.mPanelContainer.removeEventListener("mousemove", - this._collapseCallback, false); + if (this._shouldAnimate && !forceHide) { + gNavToolbox.setAttribute("fullscreenShouldAnimate", true); + this._shouldAnimate = false; + // Hide the fullscreen toggler until the transition ends. + let listener = () => { + gNavToolbox.removeEventListener("transitionend", listener, true); + if (this._isChromeCollapsed) + this._fullScrToggler.hidden = false; + }; + gNavToolbox.addEventListener("transitionend", listener, true); + this._fullScrToggler.hidden = true; } - // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox, - // so we just move it off-screen instead. See bug 430687. gNavToolbox.style.marginTop = - aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px"; - - this._fullScrToggler.collapsed = aShow; - this._isChromeCollapsed = !aShow; - if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2) - this._shouldAnimate = true; + -gNavToolbox.getBoundingClientRect().height + "px"; + this._isChromeCollapsed = true; + MousePosTracker.removeListener(this); }, showXULChrome: function(aTag, aShow) diff --git a/application/palemoon/base/content/browser.css b/application/palemoon/base/content/browser.css index 658655970..05dd00997 100644 --- a/application/palemoon/base/content/browser.css +++ b/application/palemoon/base/content/browser.css @@ -760,3 +760,7 @@ toolbarbutton[pmkit-button="true"] > .toolbarbutton-badge-stack > .toolbarbutton #main-window[inFullscreen] #high-priority-global-notificationbox { visibility: collapse; } + +#navigator-toolbox[fullscreenShouldAnimate] { + transition: 1.5s margin-top ease-out; +} diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js index 6dbd9677e..72d51ce4a 100644 --- a/application/palemoon/base/content/browser.js +++ b/application/palemoon/base/content/browser.js @@ -122,7 +122,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"); -let gInitialPages = [ +var gInitialPages = [ "about:blank", "about:newtab", "about:home", @@ -990,12 +990,23 @@ var gBrowserInit = { gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad); } - // window.arguments[2]: referrer (nsIURI) + // window.arguments[2]: referrer (nsIURI | string) // [3]: postData (nsIInputStream) // [4]: allowThirdPartyFixup (bool) + // [5]: referrerPolicy (int) else if (window.arguments.length >= 3) { - loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null, - window.arguments[4] || false); + let referrerURI = window.arguments[2]; + if (typeof(referrerURI) == "string") { + try { + referrerURI = makeURI(referrerURI); + } catch (e) { + referrerURI = null; + } + } + let referrerPolicy = (window.arguments[5] != undefined ? + window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT); + loadURI(uriToLoad, referrerURI, window.arguments[3] || null, + window.arguments[4] || false, referrerPolicy); window.focus(); } // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3. @@ -1730,7 +1741,7 @@ function loadOneOrMoreURIs(aURIString) function focusAndSelectUrlBar() { if (gURLBar) { if (window.fullScreen) - FullScreen.mouseoverToggle(true); + FullScreen.showNavToolbox(); gURLBar.select(); if (document.activeElement == gURLBar.inputField) @@ -1883,7 +1894,7 @@ function BrowserTryToCloseWindow() window.close(); // WindowIsClosing does all the necessary checks } -function loadURI(uri, referrer, postData, allowThirdPartyFixup) { +function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy) { if (postData === undefined) postData = null; @@ -1894,7 +1905,12 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup) { } try { - gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData); + gBrowser.loadURIWithFlags(uri, { + flags: flags, + referrerURI: referrer, + referrerPolicy: referrerPolicy, + postData: postData, + }); } catch (e) {} } @@ -2517,8 +2533,8 @@ function BrowserFullScreen() window.fullScreen = !window.fullScreen; } -function onFullScreen(event) { - FullScreen.toggle(event); +function onFullScreen() { + FullScreen.toggle(); } function onMozEnteredDomFullscreen(event) { @@ -2999,7 +3015,7 @@ const BrowserSearch = { #endif var searchBar = this.searchBar; if (searchBar && window.fullScreen) - FullScreen.mouseoverToggle(true); + FullScreen.showNavToolbox(); if (searchBar) searchBar.select(); if (!searchBar || document.activeElement != searchBar.textbox.inputField) @@ -4288,6 +4304,13 @@ nsBrowserAccess.prototype = { else aWhere = gPrefService.getIntPref("browser.link.open_newwindow"); } + + let referrer = aOpener ? makeURI(aOpener.location.href) : null; + let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT; + if (aOpener && aOpener.document) { + referrerPolicy = aOpener.document.referrerPolicy; + } + switch (aWhere) { case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW : // FIXME: Bug 408379. So how come this doesn't send the @@ -4326,6 +4349,7 @@ nsBrowserAccess.prototype = { let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", { referrerURI: referrer, + referrerPolicy: referrerPolicy, fromExternal: isExternal, inBackground: loadInBackground}); let browser = win.gBrowser.getBrowserForTab(tab); @@ -4341,11 +4365,14 @@ nsBrowserAccess.prototype = { default : // OPEN_CURRENTWINDOW or an illegal value newWindow = content; if (aURI) { - let referrer = aOpener ? makeURI(aOpener.location.href) : null; let loadflags = isExternal ? Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : Ci.nsIWebNavigation.LOAD_FLAGS_NONE; - gBrowser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null); + gBrowser.loadURIWithFlags(aURI.spec, { + flags: loadflags, + referrerURI: referrer, + referrerPolicy: referrerPolicy, + }); } if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground")) window.focus(); @@ -5075,7 +5102,8 @@ function handleLinkClick(event, href, linkNode) { urlSecurityCheck(href, doc.nodePrincipal); openLinkIn(href, where, { referrerURI: doc.documentURIObject, - charset: doc.characterSet }); + charset: doc.characterSet, + referrerPolicy: doc.referrerPolicy }); event.preventDefault(); return true; } diff --git a/application/palemoon/base/content/browser.xul b/application/palemoon/base/content/browser.xul index c2553f295..9b37f7b6f 100644 --- a/application/palemoon/base/content/browser.xul +++ b/application/palemoon/base/content/browser.xul @@ -941,7 +941,7 @@ </toolbarpalette> </toolbox> - <hbox id="fullscr-toggler" collapsed="true"/> + <hbox id="fullscr-toggler" hidden="true"/> <hbox flex="1" id="browser"> <vbox id="browser-border-start" hidden="true" layer="true"/> diff --git a/application/palemoon/base/content/nsContextMenu.js b/application/palemoon/base/content/nsContextMenu.js index 830c20998..f389491d3 100644 --- a/application/palemoon/base/content/nsContextMenu.js +++ b/application/palemoon/base/content/nsContextMenu.js @@ -753,7 +753,8 @@ nsContextMenu.prototype = { urlSecurityCheck(this.linkURL, doc.nodePrincipal); openLinkIn(this.linkURL, "window", { charset: doc.characterSet, - referrerURI: doc.documentURIObject }); + referrerURI: doc.documentURIObject, + referrerPolicy: doc.referrerPolicy }); }, // Open linked-to URL in a new private window. @@ -763,6 +764,7 @@ nsContextMenu.prototype = { openLinkIn(this.linkURL, "window", { charset: doc.characterSet, referrerURI: doc.documentURIObject, + referrerPolicy: doc.referrerPolicy, private: true }); }, @@ -772,7 +774,8 @@ nsContextMenu.prototype = { urlSecurityCheck(this.linkURL, doc.nodePrincipal); openLinkIn(this.linkURL, "tab", { charset: doc.characterSet, - referrerURI: doc.documentURIObject }); + referrerURI: doc.documentURIObject, + referrerPolicy: doc.referrerPolicy }); }, // open URL in current tab diff --git a/application/palemoon/base/content/openLocation.js b/application/palemoon/base/content/openLocation.js index 1a10334c7..f39e34666 100644 --- a/application/palemoon/base/content/openLocation.js +++ b/application/palemoon/base/content/openLocation.js @@ -83,7 +83,7 @@ function open() var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; if (!mayInheritPrincipal) - flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL; browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData); break; case "1": diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml index c06b49af0..1b8099785 100644 --- a/application/palemoon/base/content/tabbrowser.xml +++ b/application/palemoon/base/content/tabbrowser.xml @@ -1264,6 +1264,7 @@ <parameter name="aAllowThirdPartyFixup"/> <body> <![CDATA[ + var aReferrerPolicy; var aFromExternal; var aRelatedToCurrent; if (arguments.length == 2 && @@ -1271,6 +1272,7 @@ !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; aReferrerURI = params.referrerURI; + aReferrerPolicy = params.referrerPolicy; aCharset = params.charset; aPostData = params.postData; aLoadInBackground = params.inBackground; @@ -1284,6 +1286,7 @@ var owner = bgLoad ? null : this.selectedTab; var tab = this.addTab(aURI, { referrerURI: aReferrerURI, + referrerPolicy: aReferrerPolicy, charset: aCharset, postData: aPostData, ownerTab: owner, @@ -1409,6 +1412,7 @@ <body> <![CDATA[ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var aReferrerPolicy; var aFromExternal; var aRelatedToCurrent; var aSkipAnimation; @@ -1417,6 +1421,7 @@ !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; aReferrerURI = params.referrerURI; + aReferrerPolicy = params.referrerPolicy; aCharset = params.charset; aPostData = params.postData; aOwner = params.ownerTab; @@ -1588,7 +1593,13 @@ if (aFromExternal) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL; try { - b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData); + b.loadURIWithFlags(aURI, { + flags: flags, + referrerURI: aReferrerURI, + referrerPolicy: aReferrerPolicy, + charset: aCharset, + postData: aPostData, + }); } catch (ex) { Cu.reportError(ex); } @@ -2700,6 +2711,11 @@ <parameter name="aPostData"/> <body> <![CDATA[ + // Note - the callee understands both: + // (a) loadURIWithFlags(aURI, aFlags, ...) + // (b) loadURIWithFlags(aURI, { flags: aFlags, ... }) + // Forwarding it as (a) here actually supports both (a) and (b), + // so you can call us either way too. return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData); ]]> </body> diff --git a/application/palemoon/base/content/urlbarBindings.xml b/application/palemoon/base/content/urlbarBindings.xml index 985769ec2..d188e6658 100644 --- a/application/palemoon/base/content/urlbarBindings.xml +++ b/application/palemoon/base/content/urlbarBindings.xml @@ -305,12 +305,12 @@ function loadCurrent() { let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; - // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from + // Pass LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL to prevent any loads from // inheriting the currently loaded document's principal, unless this // URL is marked as safe to inherit (e.g. came from a bookmark // keyword). if (!mayInheritPrincipal) - flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL; // If the value wasn't typed, we know that we decoded the value as // UTF-8 (see losslessDecodeURI) if (!this.valueIsTyped) diff --git a/application/palemoon/base/content/utilityOverlay.js b/application/palemoon/base/content/utilityOverlay.js index 86cc5cea5..633cb8853 100644 --- a/application/palemoon/base/content/utilityOverlay.js +++ b/application/palemoon/base/content/utilityOverlay.js @@ -104,7 +104,8 @@ function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup allowThirdPartyFixup: aAllowThirdPartyFixup, postData: aPostData, referrerURI: aReferrerURI, - initiatingDoc: event ? event.target.ownerDocument : null + referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT, + initiatingDoc: event ? event.target.ownerDocument : null, }; } @@ -196,7 +197,8 @@ function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI params = { allowThirdPartyFixup: aAllowThirdPartyFixup, postData: aPostData, - referrerURI: aReferrerURI + referrerURI: aReferrerURI, + referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT, }; } @@ -209,12 +211,16 @@ function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI function openLinkIn(url, where, params) { if (!where || !url) return; + const Cc = Components.classes; + const Ci = Components.interfaces; var aFromChrome = params.fromChrome; var aAllowThirdPartyFixup = params.allowThirdPartyFixup; var aPostData = params.postData; var aCharset = params.charset; var aReferrerURI = params.referrerURI; + var aReferrerPolicy = ('referrerPolicy' in params ? + params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT); var aRelatedToCurrent = params.relatedToCurrent; var aForceAllowDataURI = params.forceAllowDataURI; var aInBackground = params.inBackground; @@ -229,11 +235,10 @@ function openLinkIn(url, where, params) { "where == 'save' but without initiatingDoc. See bug 814264."); return; } + // TODO(1073187): propagate referrerPolicy. saveURL(url, null, null, true, null, aReferrerURI, aInitiatingDoc); return; } - const Cc = Components.classes; - const Ci = Components.interfaces; var w = getTopWin(); if ((where == "tab" || where == "tabshifted") && @@ -243,6 +248,7 @@ function openLinkIn(url, where, params) { } if (!w || where == "window") { + // This propagates to window.arguments. // Strip referrer data when opening a new private window, to prevent // regular browsing data from leaking into it. if (aIsPrivate) { @@ -267,12 +273,23 @@ function openLinkIn(url, where, params) { createInstance(Ci.nsISupportsPRBool); allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup; + var referrerURISupports = null; + if (aReferrerURI && sendReferrerURI) { + referrerURISupports = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + referrerURISupports.data = aReferrerURI.spec; + } + + var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"]. + createInstance(Ci.nsISupportsPRUint32); + referrerPolicySupports.data = aReferrerPolicy; + sa.AppendElement(wuri); sa.AppendElement(charset); - if (sendReferrerURI) - sa.AppendElement(aReferrerURI); + sa.AppendElement(referrerURISupports); sa.AppendElement(aPostData); sa.AppendElement(allowThirdPartyFixupSupports); + sa.AppendElement(referrerPolicySupports); let features = "chrome,dialog=no,all"; if (aIsPrivate) { @@ -316,11 +333,16 @@ function openLinkIn(url, where, params) { flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; } if (aDisallowInheritPrincipal) - flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL; if (aForceAllowDataURI) { flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI; } - w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData); + w.gBrowser.loadURIWithFlags(url, { + flags: flags, + referrerURI: aReferrerURI, + referrerPolicy: aReferrerPolicy, + postData: aPostData, + }); break; case "tabshifted": loadInBackground = !loadInBackground; @@ -329,6 +351,7 @@ function openLinkIn(url, where, params) { let browser = w.gBrowser; browser.loadOneTab(url, { referrerURI: aReferrerURI, + referrerPolicy: aReferrerPolicy, charset: aCharset, postData: aPostData, inBackground: loadInBackground, @@ -577,9 +600,11 @@ function makeURLAbsolute(aBase, aUrl) * @param [optional] aReferrer * If aDocument is null, then this will be used as the referrer. * There will be no security check. + * @param [optional] aReferrerPolicy + * Referrer policy - Ci.nsIHttpChannel.REFERRER_POLICY_*. */ function openNewTabWith(aURL, aDocument, aPostData, aEvent, - aAllowThirdPartyFixup, aReferrer) { + aAllowThirdPartyFixup, aReferrer, aReferrerPolicy) { if (aDocument) urlSecurityCheck(aURL, aDocument.nodePrincipal); @@ -594,10 +619,13 @@ function openNewTabWith(aURL, aDocument, aPostData, aEvent, { charset: originCharset, postData: aPostData, allowThirdPartyFixup: aAllowThirdPartyFixup, - referrerURI: aDocument ? aDocument.documentURIObject : aReferrer }); + referrerURI: aDocument ? aDocument.documentURIObject : aReferrer, + referrerPolicy: aReferrerPolicy, + }); } -function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer) { +function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, + aReferrer, aReferrerPolicy) { if (aDocument) urlSecurityCheck(aURL, aDocument.nodePrincipal); @@ -614,7 +642,9 @@ function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, aR { charset: originCharset, postData: aPostData, allowThirdPartyFixup: aAllowThirdPartyFixup, - referrerURI: aDocument ? aDocument.documentURIObject : aReferrer }); + referrerURI: aDocument ? aDocument.documentURIObject : aReferrer, + referrerPolicy: aReferrerPolicy, + }); } /** diff --git a/application/palemoon/base/jar.mn b/application/palemoon/base/jar.mn index fd2df79e1..622d8e0da 100644 --- a/application/palemoon/base/jar.mn +++ b/application/palemoon/base/jar.mn @@ -108,7 +108,7 @@ browser.jar: * content/browser/sanitize.xul (content/sanitize.xul) * content/browser/sanitizeDialog.js (content/sanitizeDialog.js) content/browser/sanitizeDialog.css (content/sanitizeDialog.css) -* content/browser/autocomplete.css (content/autocomplete.css) + content/browser/autocomplete.css (content/autocomplete.css) * content/browser/autocomplete.xml (content/autocomplete.xml) content/browser/tabbrowser.css (content/tabbrowser.css) * content/browser/tabbrowser.xml (content/tabbrowser.xml) diff --git a/application/palemoon/components/feeds/FeedWriter.js b/application/palemoon/components/feeds/FeedWriter.js index cbb146564..2ae31dffa 100644 --- a/application/palemoon/components/feeds/FeedWriter.js +++ b/application/palemoon/components/feeds/FeedWriter.js @@ -1173,7 +1173,7 @@ FeedWriter.prototype = { var secman = Cc["@mozilla.org/scriptsecuritymanager;1"]. getService(Ci.nsIScriptSecurityManager); - this._feedPrincipal = secman.getSimpleCodebasePrincipal(this._feedURI); + this._feedPrincipal = secman.createCodebasePrincipal(this._feedURI, {}); LOG("Subscribe Preview: feed uri = " + this._window.location.href); diff --git a/application/palemoon/components/nsBrowserGlue.js b/application/palemoon/components/nsBrowserGlue.js index 6563df4e6..aa24d88ef 100644 --- a/application/palemoon/components/nsBrowserGlue.js +++ b/application/palemoon/components/nsBrowserGlue.js @@ -13,7 +13,11 @@ 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"); -// Define Lazy Module Gitters +// Define Lazy Service Getters +XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", + "@mozilla.org/alerts-service;1", "nsIAlertsService"); + +// Define Lazy Module Getters [ ["AddonManager", "resource://gre/modules/AddonManager.jsm"], ["NetUtil", "resource://gre/modules/NetUtil.jsm"], @@ -37,10 +41,18 @@ Cu.import("resource://gre/modules/Services.jsm"); ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"], ["AutoCompletePopup", "resource:///modules/AutoCompletePopup.jsm"], ["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"], + ["ShellService", "resource:///modules/ShellService.jsm"], ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource)); -XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", - "@mozilla.org/alerts-service;1", "nsIAlertsService"); +// Define Lazy Getters + +XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() { + return Services.strings.createBundle('chrome://branding/locale/brand.properties'); +}); + +XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() { + return Services.strings.createBundle('chrome://browser/locale/browser.properties'); +}); const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; const PREF_PLUGINS_UPDATEURL = "plugins.update.url"; @@ -463,9 +475,7 @@ BrowserGlue.prototype = { if (!win) return; - let productName = Services.strings - .createBundle("chrome://branding/locale/brand.properties") - .GetStringFromName("brandFullName"); + let productName = gBrandBundle.GetStringFromName("brandFullName"); let message = win.gNavigatorBundle.getFormattedString("slowStartup.message", [productName]); let buttons = [ @@ -562,17 +572,17 @@ BrowserGlue.prototype = { } // Perform default browser checking. - var shell; - try { - shell = Components.classes["@mozilla.org/browser/shell-service;1"] - .getService(Components.interfaces.nsIShellService); - } catch (e) { } - if (shell) { -#ifdef DEBUG - let shouldCheck = false; -#else - let shouldCheck = shell.shouldCheckDefaultBrowser; -#endif + if (ShellService) { + let shouldCheck = ShellService.shouldCheckDefaultBrowser; + + const skipDefaultBrowserCheck = + Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheckOnFirstRun") && + Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheck"); + + const usePromptLimit = false; + let promptCount = + usePromptLimit ? Services.prefs.getIntPref("browser.shell.defaultBrowserCheckCount") : 0; + let willRecoverSession = false; try { let ss = Cc["@mozilla.org/browser/sessionstartup;1"]. @@ -582,9 +592,25 @@ BrowserGlue.prototype = { } catch (ex) { /* never mind; suppose SessionStore is broken */ } - let isDefault = shell.isDefaultBrowser(true, false); // startup check, check all assoc + // startup check, check all assoc + let isDefault = false; + let isDefaultError = false; + try { + isDefault = ShellService.isDefaultBrowser(true, false); + } catch (ex) { + isDefaultError = true; + } + + if (isDefault) { + let now = (Math.floor(Date.now() / 1000)).toString(); + Services.prefs.setCharPref("browser.shell.mostRecentDateSetAsDefault", now); + } + + let willPrompt = shouldCheck && !isDefault && !willRecoverSession; - if (shouldCheck && !isDefault && !willRecoverSession) { + // Skip the "Set Default Browser" check during first-run or after the + // browser has been run a few times. + if (willPrompt) { Services.tm.mainThread.dispatch(function() { var win = this.getMostRecentBrowserWindow(); var brandBundle = win.document.getElementById("bundle_brand"); @@ -613,9 +639,9 @@ BrowserGlue.prototype = { claimAllTypes = (parseFloat(version) < 6.2); } catch (ex) { } #endif - shell.setDefaultBrowser(claimAllTypes, false); + ShellService.setDefaultBrowser(claimAllTypes, false); } - shell.shouldCheckDefaultBrowser = checkEveryTime.value; + ShellService.shouldCheckDefaultBrowser = checkEveryTime.value; }.bind(this), Ci.nsIThread.DISPATCH_NORMAL); } } @@ -696,9 +722,8 @@ BrowserGlue.prototype = { let prompt = Services.prompt; let quitBundle = Services.strings.createBundle("chrome://browser/locale/quitDialog.properties"); - let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); - let appName = brandBundle.GetStringFromName("brandShortName"); + let appName = gBrandBundle.GetStringFromName("brandShortName"); let quitDialogTitle = quitBundle.formatStringFromName("quitDialogTitle", [appName], 1); let neverAskText = quitBundle.GetStringFromName("neverAsk2"); @@ -775,9 +800,7 @@ BrowserGlue.prototype = { var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. getService(Ci.nsIURLFormatter); - var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); - var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); - var appName = brandBundle.GetStringFromName("brandShortName"); + var appName = gBrandBundle.GetStringFromName("brandShortName"); function getNotifyString(aPropData) { var propValue = update.getProperty(aPropData.propName); @@ -785,11 +808,11 @@ BrowserGlue.prototype = { if (aPropData.prefName) propValue = formatter.formatURLPref(aPropData.prefName); else if (aPropData.stringParams) - propValue = browserBundle.formatStringFromName(aPropData.stringName, - aPropData.stringParams, - aPropData.stringParams.length); + propValue = gBrowserBundle.formatStringFromName(aPropData.stringName, + aPropData.stringParams, + aPropData.stringParams.length); else - propValue = browserBundle.GetStringFromName(aPropData.stringName); + propValue = gBrowserBundle.GetStringFromName(aPropData.stringName); } return propValue; } @@ -1150,8 +1173,7 @@ BrowserGlue.prototype = { * Show the notificationBox for a locked places database. */ _showPlacesLockedNotificationBox: function BG__showPlacesLockedNotificationBox() { - var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); - var applicationName = brandBundle.GetStringFromName("brandShortName"); + var applicationName = gBrandBundle.GetStringFromName("brandShortName"); var placesBundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties"); var title = placesBundle.GetStringFromName("lockPrompt.title"); var text = placesBundle.formatStringFromName("lockPrompt.text", [applicationName], 1); @@ -1719,8 +1741,6 @@ ContentPermissionPrompt.prototype = { popup.remove(); } - var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); - var requestingWindow = aRequest.window.top; var chromeWin = this._getChromeWindow(requestingWindow).wrappedJSObject; var browser = chromeWin.gBrowser.getBrowserForDocument(requestingWindow.document); @@ -1746,8 +1766,8 @@ ContentPermissionPrompt.prototype = { } var action = { - label: browserBundle.GetStringFromName(promptAction.stringId), - accessKey: browserBundle.GetStringFromName(promptAction.stringId + ".accesskey"), + label: gBrowserBundle.GetStringFromName(promptAction.stringId), + accessKey: gBrowserBundle.GetStringFromName(promptAction.stringId + ".accesskey"), callback: function() { if (promptAction.callback) { promptAction.callback(); @@ -1806,7 +1826,6 @@ ContentPermissionPrompt.prototype = { }, _promptGeo : function(aRequest) { - var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); var requestingURI = aRequest.principal.URI; var message; @@ -1822,11 +1841,11 @@ ContentPermissionPrompt.prototype = { }]; if (requestingURI.schemeIs("file")) { - message = browserBundle.formatStringFromName("geolocation.shareWithFile", - [requestingURI.path], 1); + message = gBrowserBundle.formatStringFromName("geolocation.shareWithFile", + [requestingURI.path], 1); } else { - message = browserBundle.formatStringFromName("geolocation.shareWithSite", - [requestingURI.host], 1); + message = gBrowserBundle.formatStringFromName("geolocation.shareWithSite", + [requestingURI.host], 1); // Always share location action. actions.push({ stringId: "geolocation.alwaysShareLocation", @@ -1857,11 +1876,10 @@ ContentPermissionPrompt.prototype = { }, _promptWebNotifications : function(aRequest) { - var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); var requestingURI = aRequest.principal.URI; - var message = browserBundle.formatStringFromName("webNotifications.showFromSite", - [requestingURI.host], 1); + var message = gBrowserBundle.formatStringFromName("webNotifications.showFromSite", + [requestingURI.host], 1); var actions; @@ -1909,14 +1927,12 @@ ContentPermissionPrompt.prototype = { }, _promptPointerLock: function CPP_promtPointerLock(aRequest, autoAllow) { - - let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); let requestingURI = aRequest.principal.URI; let originString = requestingURI.schemeIs("file") ? requestingURI.path : requestingURI.host; - let message = browserBundle.formatStringFromName(autoAllow ? - "pointerLock.autoLock.title2" : "pointerLock.title2", - [originString], 1); + let message = gBrowserBundle.formatStringFromName(autoAllow ? + "pointerLock.autoLock.title2" : "pointerLock.title2", + [originString], 1); // If this is an autoAllow info prompt, offer no actions. // _showPrompt() will allow the request when it's dismissed. let actions = []; diff --git a/application/palemoon/components/places/content/editBookmarkOverlay.js b/application/palemoon/components/places/content/editBookmarkOverlay.js index 43645cc73..69d7d32eb 100644 --- a/application/palemoon/components/places/content/editBookmarkOverlay.js +++ b/application/palemoon/components/places/content/editBookmarkOverlay.js @@ -12,6 +12,7 @@ var gEditItemOverlay = { _uris: [], _tags: [], _allTags: [], + _keyword: null, _multiEdit: false, _itemType: -1, _readOnly: false, @@ -139,9 +140,8 @@ var gEditItemOverlay = { this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId); if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) { this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId); - this._initTextField("keywordField", - PlacesUtils.bookmarks - .getKeywordForBookmark(this._itemId)); + this._keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId); + this._initTextField("keywordField", this._keyword); this._element("loadInSidebarCheckbox").checked = PlacesUtils.annotations.itemHasAnnotation(this._itemId, PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO); @@ -610,9 +610,13 @@ var gEditItemOverlay = { }, onKeywordFieldBlur: function EIO_onKeywordFieldBlur() { - var keyword = this._element("keywordField").value; - if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) { - var txn = new PlacesEditBookmarkKeywordTransaction(this._itemId, keyword); + let oldKeyword = this._keyword; + let keyword = this._keyword = this._element("keywordField").value; + if (keyword != oldKeyword) { + let txn = new PlacesEditBookmarkKeywordTransaction(this._itemId, + keyword, + null, + oldKeyword); PlacesUtils.transactionManager.doTransaction(txn); } }, @@ -1004,9 +1008,8 @@ var gEditItemOverlay = { } break; case "keyword": - this._initTextField("keywordField", - PlacesUtils.bookmarks - .getKeywordForBookmark(this._itemId)); + this._keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId); + this._initTextField("keywordField", this._keyword); break; case PlacesUIUtils.DESCRIPTION_ANNO: this._initTextField("descriptionField", diff --git a/application/palemoon/components/sessionstore/SessionStore.jsm b/application/palemoon/components/sessionstore/SessionStore.jsm index b8d126e21..4f95f10a7 100644 --- a/application/palemoon/components/sessionstore/SessionStore.jsm +++ b/application/palemoon/components/sessionstore/SessionStore.jsm @@ -2085,7 +2085,7 @@ var SessionStoreInternal = { } catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right - if (aEntry.owner) { + if (aEntry.triggeringPrincipal) { // Not catching anything specific here, just possible errors // from writeCompoundObject and the like. try { @@ -2094,19 +2094,19 @@ var SessionStoreInternal = { var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); pipe.init(false, false, 0, 0xffffffff, null); binaryStream.setOutputStream(pipe.outputStream); - binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true); + binaryStream.writeCompoundObject(aEntry.triggeringPrincipal, Ci.nsIPrincipal, true); binaryStream.close(); // Now we want to read the data from the pipe's input end and encode it. var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"]. createInstance(Ci.nsIBinaryInputStream); scriptableStream.setInputStream(pipe.inputStream); - var ownerBytes = + var triggeringPrincipalBytes = scriptableStream.readByteArray(scriptableStream.available()); // We can stop doing base64 encoding once our serialization into JSON // is guaranteed to handle all chars in strings, including embedded // nulls. - entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes)); + entry.triggeringPrincipal_b64 = btoa(String.fromCharCode.apply(null, triggeringPrincipalBytes)); } catch (ex) { debug(ex); } } @@ -3400,16 +3400,24 @@ var SessionStoreInternal = { } } - if (aEntry.owner_b64) { - var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(Ci.nsIStringInputStream); - var binaryData = atob(aEntry.owner_b64); - ownerInput.setData(binaryData, binaryData.length); + // The field aEntry.owner_b64 got renamed to aEntry.triggeringPricipal_b64 in + // Bug 1286472. To remain backward compatible we still have to support that + // field for a few cycles before we can remove it within Bug 1289785. + if (aEntry.owner_b64) { + aEntry.triggeringPrincipal_b64 = aEntry.owner_b64; + delete aEntry.owner_b64; + } + + if (aEntry.triggeringPrincipal_b64) { + var triggeringPrincipalInput = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsIStringInputStream); + var binaryData = atob(aEntry.triggeringPrincipal_b64); + triggeringPrincipalInput.setData(binaryData, binaryData.length); var binaryStream = Cc["@mozilla.org/binaryinputstream;1"]. createInstance(Ci.nsIObjectInputStream); - binaryStream.setInputStream(ownerInput); + binaryStream.setInputStream(triggeringPrincipalInput); try { // Catch possible deserialization exceptions - shEntry.owner = binaryStream.readObject(true); + shEntry.triggeringPrincipal = binaryStream.readObject(true); } catch (ex) { debug(ex); } } diff --git a/application/palemoon/confvars.sh b/application/palemoon/confvars.sh index 8a1d00c68..5109c0829 100644 --- a/application/palemoon/confvars.sh +++ b/application/palemoon/confvars.sh @@ -79,12 +79,6 @@ MOZ_JSDOWNLOADS=1 # conformant implementations. MOZ_WEBGL_CONFORMANT=1 -# Platform Feature: Windows Maintaince Service -# XXX: This is never used -if test "$OS_ARCH" = "WINNT"; then - MOZ_MAINTENANCE_SERVICE= -fi - # Set the chrome packing format # Possible values are omni, jar, and flat # Currently, only omni and flat are supported @@ -103,3 +97,11 @@ if test "$OS_ARCH" = "WINNT" -o \ "$OS_ARCH" = "Linux"; then MOZ_BUNDLED_FONTS=1 fi + +# Short-circuit a few services to be removed +MOZ_MAINTENANCE_SERVICE= +MOZ_SERVICES_HEALTHREPORT= +MOZ_ADDON_SIGNING=0 +MOZ_REQUIRE_SIGNING=0 +MOZ_PROFILE_MIGRATOR= + diff --git a/application/palemoon/locales/en-US/chrome/browser/browser.properties b/application/palemoon/locales/en-US/chrome/browser/browser.properties index 7f1c88a88..481a061d9 100644 --- a/application/palemoon/locales/en-US/chrome/browser/browser.properties +++ b/application/palemoon/locales/en-US/chrome/browser/browser.properties @@ -59,7 +59,8 @@ addonError-1=The add-on could not be downloaded because of a connection failure addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected. addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt. addonError-4=#1 could not be installed because #3 cannot modify the needed file. -addonError-5=The add-on downloaded from #2 could not be installed because #3 does not support WebExtensions. +addonError-8=The add-on downloaded from #2 could not be installed because #3 does not support Jetpack (SDK) extensions. +addonError-9=The add-on downloaded from #2 could not be installed because #3 does not support WebExtensions. # LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted): # #1 is the add-on name, #3 is the application name, #4 is the application version @@ -67,10 +68,11 @@ addonLocalError-1=This add-on could not be installed because of a filesystem err addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected. addonLocalError-3=This add-on could not be installed because it appears to be corrupt. addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file. -addonLocalError-5=This add-on could not be installed because #3 does not support WebExtensions. +addonLocalError-8=This add-on could not be installed because #3 does not support Jetpack (SDK) extensions. +addonLocalError-9=This add-on could not be installed because #3 does not support WebExtensions. addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4. addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems. -addonErrorJetSDK=#1 could not be installed because it is a Jetpack/SDK extension which are not supported in #3 #4. + # LOCALIZATION NOTE (lwthemeInstallRequest.message): %S will be replaced with # the host name of the site. diff --git a/application/palemoon/themes/linux/Push-16.png b/application/palemoon/themes/linux/Push-16.png Binary files differdeleted file mode 100644 index 082b17781..000000000 --- a/application/palemoon/themes/linux/Push-16.png +++ /dev/null diff --git a/application/palemoon/themes/linux/Push-64.png b/application/palemoon/themes/linux/Push-64.png Binary files differdeleted file mode 100644 index 6e09ab9c3..000000000 --- a/application/palemoon/themes/linux/Push-64.png +++ /dev/null diff --git a/application/palemoon/themes/linux/browser.css b/application/palemoon/themes/linux/browser.css index 131a63a90..43fd637fb 100644 --- a/application/palemoon/themes/linux/browser.css +++ b/application/palemoon/themes/linux/browser.css @@ -1292,10 +1292,6 @@ toolbar[iconsize="small"] #webrtc-status-button { list-style-image: url(chrome://browser/skin/Geolocation-16.png); } -#push-notification-icon { - list-style-image: url(chrome://browser/skin/Push-16.png); -} - #addons-notification-icon { list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png); } diff --git a/application/palemoon/themes/osx/Push-16.png b/application/palemoon/themes/osx/Push-16.png Binary files differdeleted file mode 100644 index 54ef8f8ea..000000000 --- a/application/palemoon/themes/osx/Push-16.png +++ /dev/null diff --git a/application/palemoon/themes/osx/Push-64.png b/application/palemoon/themes/osx/Push-64.png Binary files differdeleted file mode 100644 index 099b9c76f..000000000 --- a/application/palemoon/themes/osx/Push-64.png +++ /dev/null diff --git a/application/palemoon/themes/osx/browser.css b/application/palemoon/themes/osx/browser.css index 58443fa76..d5feadf29 100644 --- a/application/palemoon/themes/osx/browser.css +++ b/application/palemoon/themes/osx/browser.css @@ -1975,10 +1975,6 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { list-style-image: url(chrome://browser/skin/Geolocation-16.png); } -#push-notification-icon { - list-style-image: url(chrome://browser/skin/Push-16.png); -} - #addons-notification-icon { list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png); } diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 696a2871a..2380f5d21 100755 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -71,6 +71,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing", ["gDNSService", "@mozilla.org/network/dns-service;1", "nsIDNSService"], ].forEach(([name, cc, ci]) => XPCOMUtils.defineLazyServiceGetter(this, name, cc, ci)); +XPCOMUtils.defineLazyServiceGetter(this, "gSerializationHelper", + "@mozilla.org/network/serialization-helper;1", + "nsISerializationHelper"); + XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() { let tmp = {}; Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp); @@ -807,6 +811,7 @@ function _loadURIWithFlags(browser, uri, params) { if (!uri) { uri = "about:blank"; } + let triggeringPrincipal = params.triggeringPrincipal || null; let flags = params.flags || 0; let referrer = params.referrerURI; let referrerPolicy = ('referrerPolicy' in params ? params.referrerPolicy : @@ -831,7 +836,7 @@ function _loadURIWithFlags(browser, uri, params) { browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy, - postData, null, null); + postData, null, null, triggeringPrincipal); } else { // Check if the current browser is allowed to unload. let {permitUnload, timedOut} = browser.permitUnload(); @@ -845,6 +850,9 @@ function _loadURIWithFlags(browser, uri, params) { let loadParams = { uri: uri, + triggeringPrincipal: triggeringPrincipal + ? gSerializationHelper.serializeToString(triggeringPrincipal) + : null, flags: flags, referrer: referrer ? referrer.spec : null, referrerPolicy: referrerPolicy, @@ -872,7 +880,7 @@ function _loadURIWithFlags(browser, uri, params) { } browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy, - postData, null, null); + postData, null, null, triggeringPrincipal); } else { throw e; } @@ -1164,6 +1172,7 @@ var gBrowserInit = { // [5]: referrerPolicy (int) // [6]: userContextId (int) // [7]: originPrincipal (nsIPrincipal) + // [8]: triggeringPrincipal (nsIPrincipal) else if (window.arguments.length >= 3) { let referrerURI = window.arguments[2]; if (typeof(referrerURI) == "string") { @@ -1181,7 +1190,7 @@ var gBrowserInit = { window.arguments[4] || false, referrerPolicy, userContextId, // pass the origin principal (if any) and force its use to create // an initial about:blank viewer if present: - window.arguments[7], !!window.arguments[7]); + window.arguments[7], !!window.arguments[7], window.arguments[8]); window.focus(); } // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3. @@ -2067,7 +2076,8 @@ function BrowserTryToCloseWindow() } function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy, - userContextId, originPrincipal, forceAboutBlankViewerInCurrent) { + userContextId, originPrincipal, forceAboutBlankViewerInCurrent, + triggeringPrincipal) { try { openLinkIn(uri, "current", { referrerURI: referrer, @@ -2076,6 +2086,7 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy, allowThirdPartyFixup: allowThirdPartyFixup, userContextId: userContextId, originPrincipal, + triggeringPrincipal, forceAboutBlankViewerInCurrent, }); } catch (e) {} @@ -2779,24 +2790,6 @@ var BrowserOnClick = { } }, - handleEvent: function (event) { - if (!event.isTrusted || // Don't trust synthetic events - event.button == 2) { - return; - } - - let originalTarget = event.originalTarget; - let ownerDoc = originalTarget.ownerDocument; - if (!ownerDoc) { - return; - } - - if (gMultiProcessBrowser && - ownerDoc.documentURI.toLowerCase() == "about:newtab") { - this.onE10sAboutNewTab(event, ownerDoc); - } - }, - receiveMessage: function (msg) { switch (msg.name) { case "Browser:CertExceptionError": @@ -2990,28 +2983,6 @@ var BrowserOnClick = { } }, - /** - * This functions prevents navigation from happening directly through the <a> - * link in about:newtab (which is loaded in the parent and therefore would load - * the next page also in the parent) and instructs the browser to open the url - * in the current tab which will make it update the remoteness of the tab. - */ - onE10sAboutNewTab: function(event, ownerDoc) { - let isTopFrame = (ownerDoc.defaultView.parent === ownerDoc.defaultView); - if (!isTopFrame) { - return; - } - - let anchorTarget = event.originalTarget.parentNode; - - if (anchorTarget instanceof HTMLAnchorElement && - anchorTarget.classList.contains("newtab-link")) { - event.preventDefault(); - let where = whereToOpenLink(event, false, false); - openLinkIn(anchorTarget.href, where, { charset: ownerDoc.characterSet, referrerURI: ownerDoc.documentURIObject }); - } - }, - ignoreWarningButton: function (reason) { // Allow users to override and continue through to the site, // but add a notify bar as a reminder, so that they don't lose @@ -4809,13 +4780,9 @@ var TabsProgressListener = { } } - // Attach a listener to watch for "click" events bubbling up from error - // pages and other similar pages (like about:newtab). This lets us fix bugs - // like 401575 which require error page UI to do privileged things, without - // letting error pages have any privilege themselves. - // We can't look for this during onLocationChange since at that point the - // document URI is not yet the about:-uri of the error page. - + // We used to listen for clicks in the browser here, but when that + // became unnecessary, removing the code below caused focus issues. + // This code should be removed. Tracked in bug 1337794. let isRemoteBrowser = aBrowser.isRemoteBrowser; // We check isRemoteBrowser here to avoid requesting the doc CPOW let doc = isRemoteBrowser ? null : aWebProgress.DOMWindow.document; @@ -4830,11 +4797,9 @@ var TabsProgressListener = { // STATE_STOP may be received twice for documents, thus store an // attribute to ensure handling it just once. doc.documentElement.setAttribute("hasBrowserHandlers", "true"); - aBrowser.addEventListener("click", BrowserOnClick, true); aBrowser.addEventListener("pagehide", function onPageHide(event) { if (event.target.defaultView.frameElement) return; - aBrowser.removeEventListener("click", BrowserOnClick, true); aBrowser.removeEventListener("pagehide", onPageHide, true); if (event.target.documentElement) event.target.documentElement.removeAttribute("hasBrowserHandlers"); @@ -4883,7 +4848,7 @@ nsBrowserAccess.prototype = { _openURIInNewTab: function(aURI, aReferrer, aReferrerPolicy, aIsPrivate, aIsExternal, aForceNotRemote=false, aUserContextId=Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID, - aOpener=null) { + aOpener = null, aTriggeringPrincipal = null) { let win, needToFocusWin; // try the current window. if we're in a popup, fall back on the most recent browser window @@ -4908,6 +4873,7 @@ nsBrowserAccess.prototype = { let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"); let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", { + triggeringPrincipal: aTriggeringPrincipal, referrerURI: aReferrer, referrerPolicy: aReferrerPolicy, userContextId: aUserContextId, @@ -4956,9 +4922,11 @@ nsBrowserAccess.prototype = { } let referrer = aOpener ? makeURI(aOpener.location.href) : null; + let triggeringPrincipal = null; let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT; if (aOpener && aOpener.document) { referrerPolicy = aOpener.document.referrerPolicy; + triggeringPrincipal = aOpener.document.nodePrincipal; } let isPrivate = aOpener ? PrivateBrowsingUtils.isContentWindowPrivate(aOpener) @@ -4992,7 +4960,7 @@ nsBrowserAccess.prototype = { let browser = this._openURIInNewTab(aURI, referrer, referrerPolicy, isPrivate, isExternal, forceNotRemote, userContextId, - openerWindow); + openerWindow, triggeringPrincipal); if (browser) newWindow = browser.contentWindow; break; @@ -5003,6 +4971,7 @@ nsBrowserAccess.prototype = { Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : Ci.nsIWebNavigation.LOAD_FLAGS_NONE; gBrowser.loadURIWithFlags(aURI.spec, { + triggeringPrincipal, flags: loadflags, referrerURI: referrer, referrerPolicy: referrerPolicy, @@ -5031,7 +5000,8 @@ nsBrowserAccess.prototype = { aParams.referrerPolicy, aParams.isPrivate, isExternal, false, - userContextId); + userContextId, null, + aParams.triggeringPrincipal); if (browser) return browser.QueryInterface(Ci.nsIFrameLoaderOwner); @@ -5584,6 +5554,7 @@ function handleLinkClick(event, href, linkNode) { referrerPolicy: referrerPolicy, noReferrer: BrowserUtils.linkHasNoReferrer(linkNode), originPrincipal: doc.nodePrincipal, + triggeringPrincipal: doc.nodePrincipal, }; // The new tab/window must use the same userContextId diff --git a/browser/base/content/content.js b/browser/base/content/content.js index 8d6f0745e..46e9b45d6 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -482,6 +482,7 @@ var ClickEventHandler = { ctrlKey: event.ctrlKey, metaKey: event.metaKey, altKey: event.altKey, href: null, title: null, bookmark: false, referrerPolicy: referrerPolicy, + triggeringPrincipal: principal, originAttributes: principal ? principal.originAttributes : {}, isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)}; @@ -521,6 +522,7 @@ var ClickEventHandler = { } catch (e) {} } json.originPrincipal = ownerDoc.nodePrincipal; + json.triggeringPrincipal = ownerDoc.nodePrincipal; sendAsyncMessage("Content:Click", json); return; diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index ddf695202..955184f64 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -968,6 +968,7 @@ nsContextMenu.prototype = { _openLinkInParameters : function (extra) { let params = { charset: gContextMenuContentData.charSet, originPrincipal: this.principal, + triggeringPrincipal: this.principal, referrerURI: gContextMenuContentData.documentURIObject, referrerPolicy: gContextMenuContentData.referrerPolicy, noReferrer: this.linkHasNoReferrer }; @@ -1147,10 +1148,12 @@ nsContextMenu.prototype = { // Change current window to the URL of the image, video, or audio. viewMedia: function(e) { let referrerURI = gContextMenuContentData.documentURIObject; + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); if (this.onCanvas) { this._canvasToBlobURL(this.target).then(function(blobURL) { openUILink(blobURL, e, { disallowInheritPrincipal: true, - referrerURI: referrerURI }); + referrerURI: referrerURI, + triggeringPrincipal: systemPrincipal}); }, Cu.reportError); } else { diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index b27846835..463e74a52 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -1513,6 +1513,7 @@ <parameter name="aAllowThirdPartyFixup"/> <body> <![CDATA[ + var aTriggeringPrincipal; var aReferrerPolicy; var aFromExternal; var aRelatedToCurrent; @@ -1528,6 +1529,7 @@ typeof arguments[1] == "object" && !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; + aTriggeringPrincipal = params.triggeringPrincipal aReferrerURI = params.referrerURI; aReferrerPolicy = params.referrerPolicy; aCharset = params.charset; @@ -1550,6 +1552,7 @@ Services.prefs.getBoolPref("browser.tabs.loadInBackground"); var owner = bgLoad ? null : this.selectedTab; var tab = this.addTab(aURI, { + triggeringPrincipal: aTriggeringPrincipal, referrerURI: aReferrerURI, referrerPolicy: aReferrerPolicy, charset: aCharset, @@ -2120,6 +2123,7 @@ "use strict"; const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var aTriggeringPrincipal; var aReferrerPolicy; var aFromExternal; var aRelatedToCurrent; @@ -2136,6 +2140,7 @@ typeof arguments[1] == "object" && !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; + aTriggeringPrincipal = params.triggeringPrincipal; aReferrerURI = params.referrerURI; aReferrerPolicy = params.referrerPolicy; aCharset = params.charset; @@ -2267,6 +2272,7 @@ try { b.loadURIWithFlags(aURI, { flags, + triggeringPrincipal: aTriggeringPrincipal, referrerURI: aNoReferrer ? null: aReferrerURI, referrerPolicy: aReferrerPolicy, charset: aCharset, diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index 6ceaf773e..b041915a7 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -225,6 +225,7 @@ function openLinkIn(url, where, params) { var aUserContextId = params.userContextId; var aIndicateErrorPageLoad = params.indicateErrorPageLoad; var aPrincipal = params.originPrincipal; + var aTriggeringPrincipal = params.triggeringPrincipal; var aForceAboutBlankViewerInCurrent = params.forceAboutBlankViewerInCurrent; @@ -259,6 +260,24 @@ function openLinkIn(url, where, params) { return; } + // Teach the principal about the right OA to use, e.g. in case when + // opening a link in a new private window, or in a new container tab. + // Please note we do not have to do that for SystemPrincipals and we + // can not do it for NullPrincipals since NullPrincipals are only + // identical if they actually are the same object (See Bug: 1346759) + function useOAForPrincipal(principal) { + if (principal && principal.isCodebasePrincipal) { + let attrs = { + userContextId: aUserContextId, + privateBrowsingId: aIsPrivate || (w && PrivateBrowsingUtils.isWindowPrivate(w)), + }; + return Services.scriptSecurityManager.createCodebasePrincipal(principal.URI, attrs); + } + return principal; + } + aPrincipal = useOAForPrincipal(aPrincipal); + aTriggeringPrincipal = useOAForPrincipal(aTriggeringPrincipal); + if (!w || where == "window") { // Strip referrer data when opening a new private window, to prevent // regular browsing data from leaking into it. @@ -308,6 +327,7 @@ function openLinkIn(url, where, params) { sa.appendElement(referrerPolicySupports, /* weak =*/ false); sa.appendElement(userContextIdSupports, /* weak =*/ false); sa.appendElement(aPrincipal, /* weak =*/ false); + sa.appendElement(aTriggeringPrincipal, /* weak =*/ false); let features = "chrome,dialog=no,all"; if (aIsPrivate) { @@ -394,6 +414,7 @@ function openLinkIn(url, where, params) { } aCurrentBrowser.loadURIWithFlags(url, { + triggeringPrincipal: aTriggeringPrincipal, flags: flags, referrerURI: aNoReferrer ? null : aReferrerURI, referrerPolicy: aReferrerPolicy, @@ -419,6 +440,7 @@ function openLinkIn(url, where, params) { noReferrer: aNoReferrer, userContextId: aUserContextId, originPrincipal: aPrincipal, + triggeringPrincipal: aTriggeringPrincipal, }); browserUsedForLoad = tabUsedForLoad.linkedBrowser; break; diff --git a/browser/components/feeds/FeedConverter.js b/browser/components/feeds/FeedConverter.js index aa70620d4..c2c565608 100644 --- a/browser/components/feeds/FeedConverter.js +++ b/browser/components/feeds/FeedConverter.js @@ -264,7 +264,7 @@ FeedConverter.prototype = { } chromeChannel.loadGroup = this._request.loadGroup; - chromeChannel.asyncOpen(this._listener, null); + chromeChannel.asyncOpen2(this._listener); } finally { this._releaseHandles(); diff --git a/browser/components/sessionstore/ContentRestore.jsm b/browser/components/sessionstore/ContentRestore.jsm index 976016770..d4972bcaf 100644 --- a/browser/components/sessionstore/ContentRestore.jsm +++ b/browser/components/sessionstore/ContentRestore.jsm @@ -204,6 +204,9 @@ ContentRestoreInternal.prototype = { : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT); let postData = loadArguments.postData ? Utils.makeInputStream(loadArguments.postData) : null; + let triggeringPrincipal = loadArguments.triggeringPrincipal + ? Utils.deserializePrincipal(loadArguments.triggeringPrincipal) + : null; if (loadArguments.userContextId) { webNavigation.setOriginAttributesBeforeLoading({ userContextId: loadArguments.userContextId }); @@ -211,7 +214,7 @@ ContentRestoreInternal.prototype = { webNavigation.loadURIWithOptions(loadArguments.uri, loadArguments.flags, referrer, referrerPolicy, postData, - null, null); + null, null, triggeringPrincipal); } else if (tabData.userTypedValue && tabData.userTypedClear) { // If the user typed a URL into the URL bar and hit enter right before // we crashed, we want to start loading that page again. A non-zero diff --git a/browser/components/sessionstore/SessionHistory.jsm b/browser/components/sessionstore/SessionHistory.jsm index aa9c10379..3d28d87db 100644 --- a/browser/components/sessionstore/SessionHistory.jsm +++ b/browser/components/sessionstore/SessionHistory.jsm @@ -95,7 +95,10 @@ var SessionHistoryInternal = { // record it. For about:blank we explicitly want an empty array without // an 'index' property to denote that there are no history entries. if (uri != "about:blank" || (body && body.hasChildNodes())) { - data.entries.push({ url: uri }); + data.entries.push({ + url: uri, + triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL + }); data.index = 1; } } diff --git a/browser/components/sessionstore/SessionMigration.jsm b/browser/components/sessionstore/SessionMigration.jsm index ff339eba9..1aa22f1a9 100644 --- a/browser/components/sessionstore/SessionMigration.jsm +++ b/browser/components/sessionstore/SessionMigration.jsm @@ -11,6 +11,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/osfile.jsm", this); +XPCOMUtils.defineLazyModuleGetter(this, "Utils", + "resource://gre/modules/sessionstore/Utils.jsm"); + // An encoder to UTF-8. XPCOMUtils.defineLazyGetter(this, "gEncoder", function () { return new TextEncoder(); @@ -27,7 +30,7 @@ var SessionMigrationInternal = { * only contain: * - open windows * - with tabs - * - with history entries with only title, url + * - with history entries with only title, url, triggeringPrincipal * - with pinned state * - with tab group info (hidden + group id) * - with selected tab info @@ -45,9 +48,11 @@ var SessionMigrationInternal = { var win = {extData: {}}; win.tabs = oldWin.tabs.map(function(oldTab) { var tab = {}; - // Keep only titles and urls for history entries + // Keep only titles, urls and triggeringPrincipals for history entries tab.entries = oldTab.entries.map(function(entry) { - return {url: entry.url, title: entry.title}; + return { url: entry.url, + triggeringPrincipal_base64: entry.triggeringPrincipal_base64, + title: entry.title }; }); tab.index = oldTab.index; tab.hidden = oldTab.hidden; @@ -60,7 +65,8 @@ var SessionMigrationInternal = { }); let url = "about:welcomeback"; let formdata = {id: {sessionData: state}, url}; - return {windows: [{tabs: [{entries: [{url}], formdata}]}]}; + let entry = { url, triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL }; + return { windows: [{ tabs: [{ entries: [ entry ], formdata}]}]}; }, /** * Asynchronously read session restore state (JSON) from a path diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 93e21357f..6b30943f3 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -603,12 +603,14 @@ var SessionStoreInternal = { // replace the crashed session with a restore-page-only session let url = "about:sessionrestore"; let formdata = {id: {sessionData: state}, url}; - state = { windows: [{ tabs: [{ entries: [{url}], formdata }] }] }; + let entry = {url, triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL }; + state = { windows: [{ tabs: [{ entries: [entry], formdata }] }] }; } else if (this._hasSingleTabWithURL(state.windows, "about:welcomeback")) { // On a single about:welcomeback URL that crashed, replace about:welcomeback // with about:sessionrestore, to make clear to the user that we crashed. state.windows[0].tabs[0].entries[0].url = "about:sessionrestore"; + state.windows[0].tabs[0].entries[0].triggeringPrincipal_base64 = Utils.SERIALIZED_SYSTEMPRINCIPAL; } } diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index 31f61632b..f7f3e9339 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -85,12 +85,14 @@ addonInstallError-2=The add-on could not be installed because it does not match addonInstallError-3=The add-on downloaded from this site could not be installed because it appears to be corrupt. addonInstallError-4=%2$S could not be installed because %1$S cannot modify the needed file. addonInstallError-5=%1$S has prevented this site from installing an unverified add-on. +addonInstallError-8=%2$S could not be installed because %1$S does not support Jetpack (SDK) extensions. addonInstallError-9=%2$S could not be installed because %1$S does not support WebExtensions. addonLocalInstallError-1=This add-on could not be installed because of a filesystem error. addonLocalInstallError-2=This add-on could not be installed because it does not match the add-on %1$S expected. addonLocalInstallError-3=This add-on could not be installed because it appears to be corrupt. addonLocalInstallError-4=%2$S could not be installed because %1$S cannot modify the needed file. addonLocalInstallError-5=This add-on could not be installed because it has not been verified. +addonLocalInstallError-8=%2$S could not be installed because %1$S does not support Jetpack (SDK) extensions. addonLocalInstallError-9=%2$S could not be installed because %1$S does not support WebExtensions. diff --git a/browser/modules/ContentClick.jsm b/browser/modules/ContentClick.jsm index 8abc32525..40101d5d3 100644 --- a/browser/modules/ContentClick.jsm +++ b/browser/modules/ContentClick.jsm @@ -85,6 +85,7 @@ var ContentClick = { allowMixedContent: json.allowMixedContent, isContentWindowPrivate: json.isContentWindowPrivate, originPrincipal: json.originPrincipal, + triggeringPrincipal: json.triggeringPrincipal, }; // The new tab/window must use the same userContextId. diff --git a/browser/themes/osx/shared.inc b/browser/themes/osx/shared.inc index 3076450e2..b3ea4e199 100644 --- a/browser/themes/osx/shared.inc +++ b/browser/themes/osx/shared.inc @@ -1,4 +1,4 @@ -%include ../../../../toolkit/themes/osx/global/shared.inc
+%include ../../../toolkit/themes/osx/global/shared.inc
%include ../shared/browser.inc
%filter substitution
diff --git a/browser/themes/shared/tabs.inc.css b/browser/themes/shared/tabs.inc.css index 632a6e606..c505416e4 100644 --- a/browser/themes/shared/tabs.inc.css +++ b/browser/themes/shared/tabs.inc.css @@ -55,8 +55,9 @@ .tab-background-middle { -moz-box-flex: 1; background-clip: padding-box; - border-left: @tabCurveHalfWidth@ solid transparent; - border-right: @tabCurveHalfWidth@ solid transparent; + /* Deliberately create a 1px overlap left/right to cover rounding gaps */ + border-left: calc(@tabCurveHalfWidth@ - 1px) solid transparent; + border-right: calc(@tabCurveHalfWidth@ - 1px) solid transparent; margin: 0 -@tabCurveHalfWidth@; } diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure index ffdea81b0..9f29e68c9 100644 --- a/build/moz.configure/old.configure +++ b/build/moz.configure/old.configure @@ -235,6 +235,7 @@ def old_configure_options(*options): '--enable-system-pixman', '--enable-system-sqlite', '--enable-tasktracer', + '--enable-tests', '--enable-thread-sanitizer', '--enable-trace-logging', '--enable-ui-locale', diff --git a/devtools/client/responsive.html/browser/web-navigation.js b/devtools/client/responsive.html/browser/web-navigation.js index 4519df0bd..eee24993a 100644 --- a/devtools/client/responsive.html/browser/web-navigation.js +++ b/devtools/client/responsive.html/browser/web-navigation.js @@ -8,6 +8,7 @@ const { Ci, Cu, Cr } = require("chrome"); const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm"); const Services = require("Services"); const { NetUtil } = require("resource://gre/modules/NetUtil.jsm"); +const { Utils } = require("resource://gre/modules/sessionstore/Utils.jsm"); function readInputStreamToString(stream) { return NetUtil.readInputStreamToString(stream, stream.available()); @@ -61,11 +62,11 @@ BrowserElementWebNavigation.prototype = { // No equivalent in the current BrowserElement API this.loadURIWithOptions(uri, flags, referrer, Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT, - postData, headers, null); + postData, headers, null, null); }, loadURIWithOptions(uri, flags, referrer, referrerPolicy, postData, headers, - baseURI) { + baseURI, triggeringPrincipal) { // No equivalent in the current BrowserElement API this._sendMessage("WebNavigation:LoadURI", { uri, @@ -75,6 +76,9 @@ BrowserElementWebNavigation.prototype = { postData: postData ? readInputStreamToString(postData) : null, headers: headers ? readInputStreamToString(headers) : null, baseURI: baseURI ? baseURI.spec : null, + triggeringPrincipal: triggeringPrincipal + ? Utils.serializePrincipal(triggeringPrincipal) + : null, }); }, diff --git a/devtools/client/sourceeditor/tern/def.js b/devtools/client/sourceeditor/tern/def.js index 71f6e7991..f4a7ba9c8 100755 --- a/devtools/client/sourceeditor/tern/def.js +++ b/devtools/client/sourceeditor/tern/def.js @@ -77,7 +77,8 @@ } }, word: function(re) { - var word = "", ch, re = re || /[\w$]/; + var word = "", ch; + re = re || /[\w$]/; while ((ch = this.spec.charAt(this.pos)) && re.test(ch)) { word += ch; ++this.pos; } return word; }, @@ -187,7 +188,7 @@ if (top && this.forceNew) return new infer.Obj(base); return infer.getInstance(base); } else if (this.eat(":")) { - var name = this.word(/[\w$\.]/) + name = this.word(/[\w$\.]/) return infer.getSymbol(name) } else if (comp && this.eat("!")) { var arg = this.word(/\d/); diff --git a/devtools/moz.build b/devtools/moz.build index 8e368facb..dd9f90c5a 100644 --- a/devtools/moz.build +++ b/devtools/moz.build @@ -7,11 +7,13 @@ if CONFIG['MOZ_DEVTOOLS']: DIRS += ['client'] - -DIRS += [ - 'server', - 'shared', -] +if CONFIG['MOZ_DEVTOOLS_SERVER']: + DIRS += [ + 'server', + 'shared', + ] +else: + DIRS += ['shared/heapsnapshot/'] # /browser uses DIST_SUBDIR. We opt-in to this treatment when building # DevTools for the browser to keep the root omni.ja slim for use by external XUL diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index 9712ff32d..a1eba84ed 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -778,8 +778,8 @@ WebConsoleActor.prototype = } // See `window` definition. It isn't always a DOM Window. - let requestStartTime = this.window && this.window.performance ? - this.window.performance.timing.requestStart : 0; + let winStartTime = this.window && this.window.performance ? + this.window.performance.timing.navigationStart : 0; let cache = this.consoleAPIListener .getCachedMessages(!this.parentActor.isRootActor); @@ -787,7 +787,7 @@ WebConsoleActor.prototype = // Filter out messages that came from a ServiceWorker but happened // before the page was requested. if (aMessage.innerID === "ServiceWorker" && - requestStartTime > aMessage.timeStamp) { + winStartTime > aMessage.timeStamp) { return; } diff --git a/devtools/server/child.js b/devtools/server/child.js index e2838f08d..c69b0a3fb 100644 --- a/devtools/server/child.js +++ b/devtools/server/child.js @@ -61,7 +61,7 @@ try { try { m = require(module); - if (!setupChild in m) { + if (!(setupChild in m)) { dumpn(`ERROR: module '${module}' does not export '${setupChild}'`); return false; } diff --git a/devtools/server/main.js b/devtools/server/main.js index 475995493..ac76adb83 100644 --- a/devtools/server/main.js +++ b/devtools/server/main.js @@ -1040,7 +1040,7 @@ var DebuggerServer = { try { m = require(module); - if (!setupParent in m) { + if (!(setupParent in m)) { dumpn(`ERROR: module '${module}' does not export '${setupParent}'`); return false; } diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index 83b3efafc..070167496 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -3064,6 +3064,7 @@ exports.CSS_PROPERTIES = { "text-emphasis-style", "-webkit-text-fill-color", "text-indent", + "text-justify", "text-orientation", "text-overflow", "text-rendering", @@ -3240,6 +3241,7 @@ exports.CSS_PROPERTIES = { "dialog", "difference", "disabled", + "distribute", "dotted", "double", "drag", @@ -3299,6 +3301,8 @@ exports.CSS_PROPERTIES = { "inline-table", "inset", "inside", + "inter-character", + "inter-word", "intersect", "isolate", "italic", @@ -8865,6 +8869,23 @@ exports.CSS_PROPERTIES = { "unset" ] }, + "text-justify": { + "isInherited": true, + "subproperties": [ + "text-justify" + ], + "supports": [], + "values": [ + "auto", + "distribute", + "inherit", + "initial", + "inter-character", + "inter-word", + "none", + "unset" + ] + }, "text-orientation": { "isInherited": true, "subproperties": [ diff --git a/devtools/shared/heapsnapshot/moz.build b/devtools/shared/heapsnapshot/moz.build index d020da727..fa9ef3915 100644 --- a/devtools/shared/heapsnapshot/moz.build +++ b/devtools/shared/heapsnapshot/moz.build @@ -48,15 +48,16 @@ DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True FINAL_LIBRARY = 'xul' -DevToolsModules( - 'census-tree-node.js', - 'CensusUtils.js', - 'DominatorTreeNode.js', - 'HeapAnalysesClient.js', - 'HeapAnalysesWorker.js', - 'HeapSnapshotFileUtils.js', - 'shortest-paths.js', -) +if CONFIG['MOZ_DEVTOOLS_SERVER']: + DevToolsModules( + 'census-tree-node.js', + 'CensusUtils.js', + 'DominatorTreeNode.js', + 'HeapAnalysesClient.js', + 'HeapAnalysesWorker.js', + 'HeapSnapshotFileUtils.js', + 'shortest-paths.js', + ) if CONFIG['GNU_CXX']: CXXFLAGS += ['-Wno-error=shadow'] diff --git a/.github/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7acdedb4f..7acdedb4f 100644 --- a/.github/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index bd2a8a433..b3e26da33 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -1644,7 +1644,7 @@ nsDocShell::LoadStream(nsIInputStream* aStream, nsIURI* aURI, uri, aStream, triggeringPrincipal, - nsILoadInfo::SEC_NORMAL, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER, aContentType, aContentCharset); @@ -4732,7 +4732,7 @@ nsDocShell::LoadURI(const char16_t* aURI, { return LoadURIWithOptions(aURI, aLoadFlags, aReferringURI, mozilla::net::RP_Default, aPostStream, - aHeaderStream, nullptr); + aHeaderStream, nullptr, nullptr); } NS_IMETHODIMP @@ -4742,7 +4742,8 @@ nsDocShell::LoadURIWithOptions(const char16_t* aURI, uint32_t aReferrerPolicy, nsIInputStream* aPostStream, nsIInputStream* aHeaderStream, - nsIURI* aBaseURI) + nsIURI* aBaseURI, + nsIPrincipal* aTriggeringPrincipal) { NS_ASSERTION((aLoadFlags & 0xf) == 0, "Unexpected flags"); @@ -4861,6 +4862,7 @@ nsDocShell::LoadURIWithOptions(const char16_t* aURI, loadInfo->SetReferrerPolicy(aReferrerPolicy); loadInfo->SetHeadersStream(aHeaderStream); loadInfo->SetBaseURI(aBaseURI); + loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal); loadInfo->SetForceAllowDataURI(forceAllowDataURI); if (fixupInfo) { @@ -5687,6 +5689,11 @@ nsDocShell::LoadPage(nsISupports* aPageDescriptor, uint32_t aDisplayType) } shEntry->SetURI(newUri); shEntry->SetOriginalURI(nullptr); + // shEntry's current triggering principal is whoever loaded that page initially. + // But now we're doing another load of the page, via an API that is only exposed + // to system code. The triggering principal for this load should be the system + // principal. + shEntry->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); } rv = LoadHistoryEntry(shEntry, LOAD_HISTORY); @@ -9145,8 +9152,13 @@ nsDocShell::CreateContentViewer(const nsACString& aContentType, // Make sure we have a URI to set currentURI. nsCOMPtr<nsIURI> failedURI; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; if (failedChannel) { NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI)); + } else { + // if there is no failed channel we have to explicitly provide + // a triggeringPrincipal for the history entry. + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); } if (!failedURI) { @@ -9167,7 +9179,8 @@ nsDocShell::CreateContentViewer(const nsACString& aContentType, // Create an shistory entry for the old load. if (failedURI) { bool errorOnLocationChangeNeeded = OnNewURI( - failedURI, failedChannel, nullptr, nullptr, mLoadType, false, false, false); + failedURI, failedChannel, triggeringPrincipal, + nullptr, mLoadType, false, false, false); if (errorOnLocationChangeNeeded) { FireOnLocationChange(this, failedChannel, failedURI, @@ -10394,10 +10407,13 @@ nsDocShell::InternalLoad(nsIURI* aURI, * call OnNewURI() so that, this traversal will be * recorded in session and global history. */ - nsCOMPtr<nsIPrincipal> triggeringPrincipal, principalToInherit; + nsCOMPtr<nsIPrincipal> newURITriggeringPrincipal, newURIPrincipalToInherit; if (mOSHE) { - mOSHE->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); - mOSHE->GetPrincipalToInherit(getter_AddRefs(principalToInherit)); + mOSHE->GetTriggeringPrincipal(getter_AddRefs(newURITriggeringPrincipal)); + mOSHE->GetPrincipalToInherit(getter_AddRefs(newURIPrincipalToInherit)); + } else { + newURITriggeringPrincipal = aTriggeringPrincipal; + newURIPrincipalToInherit = doc->NodePrincipal(); } // Pass true for aCloneSHChildren, since we're not // changing documents here, so all of our subframes are @@ -10407,7 +10423,7 @@ nsDocShell::InternalLoad(nsIURI* aURI, // flag on firing onLocationChange(...). // Anyway, aCloneSHChildren param is simply reflecting // doShortCircuitedLoad in this scope. - OnNewURI(aURI, nullptr, triggeringPrincipal, principalToInherit, + OnNewURI(aURI, nullptr, newURITriggeringPrincipal, newURIPrincipalToInherit, mLoadType, true, true, true); nsCOMPtr<nsIInputStream> postData; @@ -10606,7 +10622,7 @@ nsDocShell::InternalLoad(nsIURI* aURI, } bool shouldLoad; rv = browserChrome3->ShouldLoadURI(this, uriForShouldLoadCheck, aReferrer, - &shouldLoad); + aTriggeringPrincipal, &shouldLoad); if (NS_SUCCEEDED(rv) && !shouldLoad) { return NS_OK; } @@ -10961,7 +10977,8 @@ nsDocShell::DoURILoad(nsIURI* aURI, } nsLoadFlags loadFlags = mDefaultLoadFlags; - nsSecurityFlags securityFlags = nsILoadInfo::SEC_NORMAL; + nsSecurityFlags securityFlags = + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL; if (aFirstParty) { // tag first party URL loads @@ -12123,7 +12140,9 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle, // Since we're not changing which page we have loaded, pass // true for aCloneChildren. - rv = AddToSessionHistory(newURI, nullptr, nullptr, nullptr, true, + rv = AddToSessionHistory(newURI, nullptr, + document->NodePrincipal(), // triggeringPrincipal + nullptr, true, getter_AddRefs(newSHEntry)); NS_ENSURE_SUCCESS(rv, rv); @@ -12399,11 +12418,6 @@ nsDocShell::AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel, discardLayoutState = ShouldDiscardLayoutState(httpChannel); } - // XXX Bug 1286838: Replace channel owner with loadInfo triggeringPrincipal - nsCOMPtr<nsISupports> owner; - aChannel->GetOwner(getter_AddRefs(owner)); - triggeringPrincipal = do_QueryInterface(owner); - nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); if (loadInfo) { if (!triggeringPrincipal) { @@ -12649,10 +12663,6 @@ nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType) srcdoc = NullString(); } - // If there is no triggeringPrincipal we can fall back to using the - // SystemPrincipal as the triggeringPrincipal for loading the history - // entry, since the history entry can only end up in history if security - // checks passed in the initial loading phase. if (!triggeringPrincipal) { triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); } @@ -13917,7 +13927,8 @@ public: const nsAString& aFileName, nsIInputStream* aPostDataStream, nsIInputStream* aHeadersDataStream, - bool aIsTrusted); + bool aIsTrusted, + nsIPrincipal* aTriggeringPrincipal); NS_IMETHOD Run() override { @@ -13933,7 +13944,7 @@ public: mHandler->OnLinkClickSync(mContent, mURI, mTargetSpec.get(), mFileName, mPostDataStream, mHeadersDataStream, - nullptr, nullptr); + nullptr, nullptr, mTriggeringPrincipal); } return NS_OK; } @@ -13948,6 +13959,7 @@ private: nsCOMPtr<nsIContent> mContent; PopupControlState mPopupState; bool mIsTrusted; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; }; OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, @@ -13957,7 +13969,8 @@ OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, const nsAString& aFileName, nsIInputStream* aPostDataStream, nsIInputStream* aHeadersDataStream, - bool aIsTrusted) + bool aIsTrusted, + nsIPrincipal* aTriggeringPrincipal) : mHandler(aHandler) , mURI(aURI) , mTargetSpec(aTargetSpec) @@ -13967,6 +13980,7 @@ OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, , mContent(aContent) , mPopupState(mHandler->mScriptGlobal->GetPopupControlState()) , mIsTrusted(aIsTrusted) + , mTriggeringPrincipal(aTriggeringPrincipal) { } @@ -13977,7 +13991,8 @@ nsDocShell::OnLinkClick(nsIContent* aContent, const nsAString& aFileName, nsIInputStream* aPostDataStream, nsIInputStream* aHeadersDataStream, - bool aIsTrusted) + bool aIsTrusted, + nsIPrincipal* aTriggeringPrincipal) { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); @@ -14016,7 +14031,8 @@ nsDocShell::OnLinkClick(nsIContent* aContent, nsCOMPtr<nsIRunnable> ev = new OnLinkClickEvent(this, aContent, aURI, target.get(), aFileName, - aPostDataStream, aHeadersDataStream, aIsTrusted); + aPostDataStream, aHeadersDataStream, + aIsTrusted, aTriggeringPrincipal); return NS_DispatchToCurrentThread(ev); } @@ -14028,7 +14044,8 @@ nsDocShell::OnLinkClickSync(nsIContent* aContent, nsIInputStream* aPostDataStream, nsIInputStream* aHeadersDataStream, nsIDocShell** aDocShell, - nsIRequest** aRequest) + nsIRequest** aRequest, + nsIPrincipal* aTriggeringPrincipal) { // Initialize the DocShell / Request if (aDocShell) { @@ -14151,13 +14168,18 @@ nsDocShell::OnLinkClickSync(nsIContent* aContent, return NS_ERROR_OUT_OF_MEMORY; } + // if the triggeringPrincipal is not passed explicitly, then we + // fall back to using doc->NodePrincipal() as the triggeringPrincipal. + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + aTriggeringPrincipal ? aTriggeringPrincipal + : aContent->NodePrincipal(); + nsresult rv = InternalLoad(clonedURI, // New URI nullptr, // Original URI false, // LoadReplace referer, // Referer URI refererPolicy, // Referer policy - aContent->NodePrincipal(), // Triggering is our node's - // principal + triggeringPrincipal, aContent->NodePrincipal(), flags, target, // Window target diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 63a4e3358..f510a15b0 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -201,7 +201,8 @@ public: const nsAString& aFileName, nsIInputStream* aPostDataStream, nsIInputStream* aHeadersDataStream, - bool aIsTrusted) override; + bool aIsTrusted, + nsIPrincipal* aTriggeringPrincipal) override; NS_IMETHOD OnLinkClickSync(nsIContent* aContent, nsIURI* aURI, const char16_t* aTargetSpec, @@ -209,7 +210,8 @@ public: nsIInputStream* aPostDataStream = 0, nsIInputStream* aHeadersDataStream = 0, nsIDocShell** aDocShell = 0, - nsIRequest** aRequest = 0) override; + nsIRequest** aRequest = 0, + nsIPrincipal* aTriggeringPrincipal = nullptr) override; NS_IMETHOD OnOverLink(nsIContent* aContent, nsIURI* aURI, const char16_t* aTargetSpec) override; diff --git a/docshell/base/nsILinkHandler.h b/docshell/base/nsILinkHandler.h index 7cdcd566d..7069f1f1d 100644 --- a/docshell/base/nsILinkHandler.h +++ b/docshell/base/nsILinkHandler.h @@ -37,6 +37,8 @@ public: * @param aFileName non-null when the link should be downloaded as the given file * @param aHeadersDataStream ??? * @param aIsTrusted false if the triggerer is an untrusted DOM event. + * @param aTriggeringPrincipal, if not passed explicitly we fall back to + * the document's principal. */ NS_IMETHOD OnLinkClick(nsIContent* aContent, nsIURI* aURI, @@ -44,7 +46,8 @@ public: const nsAString& aFileName, nsIInputStream* aPostDataStream, nsIInputStream* aHeadersDataStream, - bool aIsTrusted) = 0; + bool aIsTrusted, + nsIPrincipal* aTriggeringPrincipal) = 0; /** * Process a click on a link. @@ -61,6 +64,8 @@ public: * @param aHeadersDataStream ??? * @param aDocShell (out-param) the DocShell that the request was opened on * @param aRequest the request that was opened + * @param aTriggeringPrincipal, if not passed explicitly we fall back to + * the document's principal. */ NS_IMETHOD OnLinkClickSync(nsIContent* aContent, nsIURI* aURI, @@ -69,7 +74,8 @@ public: nsIInputStream* aPostDataStream = 0, nsIInputStream* aHeadersDataStream = 0, nsIDocShell** aDocShell = 0, - nsIRequest** aRequest = 0) = 0; + nsIRequest** aRequest = 0, + nsIPrincipal* aTriggeringPrincipal = nullptr) = 0; /** * Process a mouse-over a link. diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl index 241d0731c..c3e2fc550 100644 --- a/docshell/base/nsIWebNavigation.idl +++ b/docshell/base/nsIWebNavigation.idl @@ -9,6 +9,7 @@ interface nsIDOMDocument; interface nsIInputStream; interface nsISHistory; interface nsIURI; +interface nsIPrincipal; /** * The nsIWebNavigation interface defines an interface for navigating the web. @@ -288,14 +289,20 @@ interface nsIWebNavigation : nsISupports * that at present this argument is only used with view-source aURIs * and cannot be used to resolve aURI. * This parameter is optional and may be null. - */ - void loadURIWithOptions(in wstring aURI, - in unsigned long aLoadFlags, - in nsIURI aReferrer, - in unsigned long aReferrerPolicy, - in nsIInputStream aPostData, - in nsIInputStream aHeaders, - in nsIURI aBaseURI); + * @param aTriggeringPrincipal + * The principal that initiated the load of aURI. If omitted docShell + * tries to create a codeBasePrincipal from aReferrer if not null. If + * aReferrer is also null docShell peforms a load using the + * SystemPrincipal as the triggeringPrincipal. + */ + void loadURIWithOptions(in wstring aURI, + in unsigned long aLoadFlags, + in nsIURI aReferrer, + in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in nsIInputStream aHeaders, + in nsIURI aBaseURI, + [optional] in nsIPrincipal aTriggeringPrincipal); /** * Tells the Object to reload the current page. There may be cases where the diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp index 9d972136f..6b0b066d9 100644 --- a/docshell/shistory/nsSHEntry.cpp +++ b/docshell/shistory/nsSHEntry.cpp @@ -416,6 +416,9 @@ nsSHEntry::Create(nsIURI* aURI, const nsAString& aTitle, uint64_t aDocShellID, bool aDynamicCreation) { + MOZ_ASSERT(aTriggeringPrincipal, + "need a valid triggeringPrincipal to create a session history entry"); + mURI = aURI; mTitle = aTitle; mPostData = aInputStream; diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp index 7c148ffcc..9443b92bc 100644 --- a/docshell/shistory/nsSHistory.cpp +++ b/docshell/shistory/nsSHistory.cpp @@ -1582,7 +1582,8 @@ nsSHistory::LoadURIWithOptions(const char16_t* aURI, uint32_t aReferrerPolicy, nsIInputStream* aPostStream, nsIInputStream* aExtraHeaderStream, - nsIURI* aBaseURI) + nsIURI* aBaseURI, + nsIPrincipal* aTriggeringPrincipal) { return NS_OK; } diff --git a/docshell/test/browser/browser.ini b/docshell/test/browser/browser.ini index 9211092a4..300caff1a 100644 --- a/docshell/test/browser/browser.ini +++ b/docshell/test/browser/browser.ini @@ -1,5 +1,6 @@ [DEFAULT] support-files = + dummy_page.html favicon_bug655270.ico file_bug234628-1-child.html file_bug234628-1.html @@ -45,6 +46,7 @@ support-files = browser_timelineMarkers-frame-05.js head.js frame-head.js + file_click_link_within_view_source.html [browser_bug1206879.js] [browser_bug1309900_crossProcessHistoryNavigation.js] @@ -91,3 +93,4 @@ skip-if = true # Bug 1220415 [browser_timelineMarkers-04.js] [browser_timelineMarkers-05.js] [browser_ua_emulation.js] +[browser_click_link_within_view_source.js] diff --git a/docshell/test/browser/browser_click_link_within_view_source.js b/docshell/test/browser/browser_click_link_within_view_source.js new file mode 100644 index 000000000..84cfc1f0f --- /dev/null +++ b/docshell/test/browser/browser_click_link_within_view_source.js @@ -0,0 +1,60 @@ +"use strict"; + +/** + * Test for Bug 1359204 + * + * Loading a local file, then view-source on that file. Make sure that + * clicking a link within that view-source page is not blocked by security checks. + */ + +add_task(function* test_click_link_within_view_source() { + let TEST_FILE = "file_click_link_within_view_source.html"; + let TEST_FILE_URI = getChromeDir(getResolvedURI(gTestPath)); + TEST_FILE_URI.append(TEST_FILE); + TEST_FILE_URI = Services.io.newFileURI(TEST_FILE_URI).spec; + + let DUMMY_FILE = "dummy_page.html"; + let DUMMY_FILE_URI = getChromeDir(getResolvedURI(gTestPath)); + DUMMY_FILE_URI.append(DUMMY_FILE); + DUMMY_FILE_URI = Services.io.newFileURI(DUMMY_FILE_URI).spec; + + yield BrowserTestUtils.withNewTab(TEST_FILE_URI, function*(aBrowser) { + let tabSpec = gBrowser.selectedBrowser.currentURI.spec; + info("loading: " + tabSpec); + ok(tabSpec.startsWith("file://") && tabSpec.endsWith(TEST_FILE), + "sanity check to make sure html loaded"); + + info("click view-source of html"); + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + document.getElementById("View:PageSource").doCommand(); + + let tab = yield tabPromise; + tabSpec = gBrowser.selectedBrowser.currentURI.spec; + info("loading: " + tabSpec); + ok(tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(TEST_FILE), + "loading view-source of html succeeded"); + + info("click testlink within view-source page"); + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url => url.endsWith("dummy_page.html")); + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() { + if (content.document.readyState != "complete") { + yield ContentTaskUtils.waitForEvent(content.document, "readystatechange", false, () => + content.document.readyState == "complete"); + } + // document.getElementById() does not work on a view-source page, hence we use document.links + let linksOnPage = content.document.links; + is (linksOnPage.length, 1, "sanity check: make sure only one link is present on page"); + let myLink = content.document.links[0]; + myLink.click(); + }); + + yield loadPromise; + + tabSpec = gBrowser.selectedBrowser.currentURI.spec; + info("loading: " + tabSpec); + ok(tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(DUMMY_FILE), + "loading view-source of html succeeded"); + + yield BrowserTestUtils.removeTab(tab); + }); +}); diff --git a/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js b/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js new file mode 100644 index 000000000..96908bbc2 --- /dev/null +++ b/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js @@ -0,0 +1,50 @@ +"use strict"; + +const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com"); +const HTML_URI = TEST_PATH + "dummy_page.html"; +const VIEW_SRC_URI = "view-source:" + HTML_URI; + +add_task(function*() { + info("load baseline html in new tab"); + yield BrowserTestUtils.withNewTab(HTML_URI, function*(aBrowser) { + is(gBrowser.selectedBrowser.currentURI.spec, HTML_URI, + "sanity check to make sure html loaded"); + + info("right-click -> view-source of html"); + let vSrcCtxtMenu = document.getElementById("contentAreaContextMenu"); + let popupPromise = BrowserTestUtils.waitForEvent(vSrcCtxtMenu, "popupshown"); + BrowserTestUtils.synthesizeMouseAtCenter("body", { type: "contextmenu", button: 2 }, aBrowser); + yield popupPromise; + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, VIEW_SRC_URI); + let vSrcItem = vSrcCtxtMenu.getElementsByAttribute("id", "context-viewsource")[0]; + vSrcItem.click(); + vSrcCtxtMenu.hidePopup(); + let tab = yield tabPromise; + is(gBrowser.selectedBrowser.currentURI.spec, VIEW_SRC_URI, + "loading view-source of html succeeded"); + + info ("load html file again before going .back()"); + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, HTML_URI); + yield ContentTask.spawn(tab.linkedBrowser, HTML_URI, HTML_URI => { + content.document.location = HTML_URI; + }); + yield loadPromise; + is(gBrowser.selectedBrowser.currentURI.spec, HTML_URI, + "loading html another time succeeded"); + + info("click .back() to view-source of html again and make sure the history entry has a triggeringPrincipal"); + let backCtxtMenu = document.getElementById("contentAreaContextMenu"); + popupPromise = BrowserTestUtils.waitForEvent(backCtxtMenu, "popupshown"); + BrowserTestUtils.synthesizeMouseAtCenter("body", { type: "contextmenu", button: 2 }, aBrowser); + yield popupPromise; + loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, VIEW_SRC_URI); + let backItem = backCtxtMenu.getElementsByAttribute("id", "context-back")[0]; + backItem.click(); + backCtxtMenu.hidePopup(); + yield loadPromise; + is(gBrowser.selectedBrowser.currentURI.spec, VIEW_SRC_URI, + "clicking .back() to view-source of html succeeded"); + + yield BrowserTestUtils.removeTab(tab); + }); +}); diff --git a/docshell/test/browser/dummy_page.html b/docshell/test/browser/dummy_page.html new file mode 100644 index 000000000..59bf2a5f8 --- /dev/null +++ b/docshell/test/browser/dummy_page.html @@ -0,0 +1,6 @@ +<html> +<head> <meta charset="utf-8"> </head> + <body> + just a dummy html file + </body> +</html> diff --git a/docshell/test/browser/file_click_link_within_view_source.html b/docshell/test/browser/file_click_link_within_view_source.html new file mode 100644 index 000000000..d78e4ba0f --- /dev/null +++ b/docshell/test/browser/file_click_link_within_view_source.html @@ -0,0 +1,6 @@ +<html> +<head> <meta charset="utf-8"> </head> + <body> + <a id="testlink" href="dummy_page.html">clickme</a> + </body> +</html> diff --git a/docshell/test/dummy_page.html b/docshell/test/dummy_page.html new file mode 100644 index 000000000..59bf2a5f8 --- /dev/null +++ b/docshell/test/dummy_page.html @@ -0,0 +1,6 @@ +<html> +<head> <meta charset="utf-8"> </head> + <body> + just a dummy html file + </body> +</html> diff --git a/docshell/test/mochitest.ini b/docshell/test/mochitest.ini index 7b27908fb..2298bed74 100644 --- a/docshell/test/mochitest.ini +++ b/docshell/test/mochitest.ini @@ -11,6 +11,7 @@ support-files = bug668513_redirect.html bug668513_redirect.html^headers^ bug691547_frame.html + dummy_page.html file_anchor_scroll_after_document_open.html file_bug385434_1.html file_bug385434_2.html @@ -94,3 +95,4 @@ skip-if = toolkit == 'android' # bug 784321 support-files = file_framedhistoryframes.html [test_pushState_after_document_open.html] [test_windowedhistoryframes.html] +[test_triggeringprincipal_location_seturi.html] diff --git a/docshell/test/test_triggeringprincipal_location_seturi.html b/docshell/test/test_triggeringprincipal_location_seturi.html new file mode 100644 index 000000000..3b0c7bac5 --- /dev/null +++ b/docshell/test/test_triggeringprincipal_location_seturi.html @@ -0,0 +1,102 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const SAME_ORIGIN_URI = "http://mochi.test:8888/tests/docshell/test/dummy_page.html"; +const CROSS_ORIGIN_URI = "http://example.com/tests/docshell/test/dummy_page.html"; +const NUMBER_OF_TESTS = 3; +let testCounter = 0; + +function checkFinish() { + testCounter++; + if (testCounter < NUMBER_OF_TESTS) { + return; + } + SimpleTest.finish(); +} + +// ---- test 1 ---- + +let myFrame1 = document.createElement("iframe"); +myFrame1.src = SAME_ORIGIN_URI; +myFrame1.addEventListener("load", checkLoadFrame1); +document.documentElement.appendChild(myFrame1); + +function checkLoadFrame1() { + myFrame1.removeEventListener('load', checkLoadFrame1, false); + // window.location.href is no longer cross-origin accessible in gecko. + is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI, + "initial same origin dummy loaded into frame1"); + + SpecialPowers.wrap(myFrame1.contentWindow).location.hash = "#bar"; + is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar", + "initial same origin dummy#bar loaded into iframe1"); + + myFrame1.addEventListener("load", checkNavFrame1); + myFrame1.src = CROSS_ORIGIN_URI; +} + +function checkNavFrame1() { + myFrame1.removeEventListener('load', checkNavFrame1, false); + is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, CROSS_ORIGIN_URI, + "cross origin dummy loaded into frame1"); + + myFrame1.addEventListener("load", checkBackNavFrame1); + myFrame1.src = SAME_ORIGIN_URI + "#bar"; +} + +function checkBackNavFrame1() { + myFrame1.removeEventListener('load', checkBackNavFrame1, false); + is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar", + "navagiating back to same origin dummy for frame1"); + checkFinish(); +} + +// ---- test 2 ---- + +let myFrame2 = document.createElement("iframe"); +myFrame2.src = "about:blank"; +myFrame2.addEventListener("load", checkLoadFrame2); +document.documentElement.appendChild(myFrame2); + +function checkLoadFrame2() { + myFrame2.removeEventListener('load', checkLoadFrame2, false); + is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank", + "initial about:blank frame loaded"); + + myFrame2.contentWindow.location.hash = "#foo"; + is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank#foo", + "about:blank#foo frame loaded"); + + myFrame2.addEventListener('load', checkHistoryFrame2); + myFrame2.src = "about:blank"; +} + +function checkHistoryFrame2() { + myFrame2.removeEventListener('load', checkHistoryFrame2, false); + is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank", + "about:blank frame loaded again"); + checkFinish(); +} + +// ---- test 3 ---- + +let myFrame3 = document.createElement("frame"); +document.documentElement.appendChild(myFrame3); +myFrame3.contentWindow.location.hash = "#foo"; + +is(myFrame3.contentWindow.location.href, "about:blank#foo", + "created history entry with about:blank#foo"); +checkFinish(); + +</script> +</body> +</html> diff --git a/dom/animation/Animation.cpp b/dom/animation/Animation.cpp index 6dd583ed1..bd318f79e 100644 --- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -230,6 +230,10 @@ Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline) return; } + StickyTimeDuration activeTime = mEffect + ? mEffect->GetComputedTiming().mActiveTime + : StickyTimeDuration(); + RefPtr<AnimationTimeline> oldTimeline = mTimeline; if (oldTimeline) { oldTimeline->RemoveAnimation(this); @@ -240,6 +244,9 @@ Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline) mHoldTime.SetNull(); } + if (!aTimeline) { + MaybeQueueCancelEvent(activeTime); + } UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } @@ -722,8 +729,10 @@ TimeStamp Animation::ElapsedTimeToTimeStamp( const StickyTimeDuration& aElapsedTime) const { - return AnimationTimeToTimeStamp(aElapsedTime + - mEffect->SpecifiedTiming().mDelay); + TimeDuration delay = mEffect + ? mEffect->SpecifiedTiming().mDelay + : TimeDuration(); + return AnimationTimeToTimeStamp(aElapsedTime + delay); } @@ -771,14 +780,28 @@ Animation::CancelNoUpdate() DispatchPlaybackEvent(NS_LITERAL_STRING("cancel")); + StickyTimeDuration activeTime = mEffect + ? mEffect->GetComputedTiming().mActiveTime + : StickyTimeDuration(); + mHoldTime.SetNull(); mStartTime.SetNull(); - UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); - if (mTimeline) { mTimeline->RemoveAnimation(this); } + MaybeQueueCancelEvent(activeTime); + + // When an animation is cancelled it no longer needs further ticks from the + // timeline. However, if we queued a cancel event and this was the last + // animation attached to the timeline, the timeline will stop observing the + // refresh driver and there may be no subsequent refresh driver tick for + // dispatching the queued event. + // + // By calling UpdateTiming *after* removing ourselves from our timeline, we + // ensure the timeline will register with the refresh driver for at least one + // more tick. + UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); } void @@ -819,6 +842,17 @@ Animation::HasLowerCompositeOrderThan(const Animation& aOther) const return thisTransition->HasLowerCompositeOrderThan(*otherTransition); } if (thisTransition || otherTransition) { + // Cancelled transitions no longer have an owning element. To be strictly + // correct we should store a strong reference to the owning element + // so that if we arrive here while sorting cancel events, we can sort + // them in the correct order. + // + // However, given that cancel events are almost always queued + // synchronously in some deterministic manner, we can be fairly sure + // that cancel events will be dispatched in a deterministic order + // (which is our only hard requirement until specs say otherwise). + // Furthermore, we only reach here when we have events with equal + // timestamps so this is an edge case we can probably ignore for now. return thisTransition; } } diff --git a/dom/animation/Animation.h b/dom/animation/Animation.h index c59d7d6ce..3263b30c4 100644 --- a/dom/animation/Animation.h +++ b/dom/animation/Animation.h @@ -326,6 +326,16 @@ public: void NotifyEffectTimingUpdated(); + /** + * Used by subclasses to synchronously queue a cancel event in situations + * where the Animation may have been cancelled. + * + * We need to do this synchronously because after a CSS animation/transition + * is canceled, it will be released by its owning element and may not still + * exist when we would normally go to queue events on the next tick. + */ + virtual void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) {}; + protected: void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime); void SilentlySetPlaybackRate(double aPlaybackRate); diff --git a/dom/animation/AnimationEffectReadOnly.cpp b/dom/animation/AnimationEffectReadOnly.cpp index aff28a37b..bf2e2197d 100644 --- a/dom/animation/AnimationEffectReadOnly.cpp +++ b/dom/animation/AnimationEffectReadOnly.cpp @@ -127,10 +127,6 @@ AnimationEffectReadOnly::GetComputedTimingAt( } const TimeDuration& localTime = aLocalTime.Value(); - // Calculate the time within the active interval. - // https://w3c.github.io/web-animations/#active-time - StickyTimeDuration activeTime; - StickyTimeDuration beforeActiveBoundary = std::max(std::min(StickyTimeDuration(aTiming.mDelay), result.mEndTime), zeroDuration); @@ -148,7 +144,7 @@ AnimationEffectReadOnly::GetComputedTimingAt( // The animation isn't active or filling at this time. return result; } - activeTime = + result.mActiveTime = std::max(std::min(StickyTimeDuration(localTime - aTiming.mDelay), result.mActiveDuration), zeroDuration); @@ -159,13 +155,14 @@ AnimationEffectReadOnly::GetComputedTimingAt( // The animation isn't active or filling at this time. return result; } - activeTime = std::max(StickyTimeDuration(localTime - aTiming.mDelay), - zeroDuration); + result.mActiveTime + = std::max(StickyTimeDuration(localTime - aTiming.mDelay), + zeroDuration); } else { MOZ_ASSERT(result.mActiveDuration != zeroDuration, "How can we be in the middle of a zero-duration interval?"); result.mPhase = ComputedTiming::AnimationPhase::Active; - activeTime = localTime - aTiming.mDelay; + result.mActiveTime = localTime - aTiming.mDelay; } // Convert active time to a multiple of iterations. @@ -176,7 +173,7 @@ AnimationEffectReadOnly::GetComputedTimingAt( ? 0.0 : result.mIterations; } else { - overallProgress = activeTime / result.mDuration; + overallProgress = result.mActiveTime / result.mDuration; } // Factor in iteration start offset. @@ -208,7 +205,8 @@ AnimationEffectReadOnly::GetComputedTimingAt( if (result.mPhase == ComputedTiming::AnimationPhase::After && progress == 0.0 && result.mIterations != 0.0 && - (activeTime != zeroDuration || result.mDuration == zeroDuration)) { + (result.mActiveTime != zeroDuration || + result.mDuration == zeroDuration)) { // The only way we can be in the after phase with a progress of zero and // a current iteration of zero, is if we have a zero iteration count or // were clipped using a negative end delay--both of which we should have diff --git a/dom/animation/ComputedTiming.h b/dom/animation/ComputedTiming.h index 4a98e3933..b1c6674a5 100644 --- a/dom/animation/ComputedTiming.h +++ b/dom/animation/ComputedTiming.h @@ -29,6 +29,8 @@ struct ComputedTiming // Will equal StickyTimeDuration::Forever() if the animation repeats // indefinitely. StickyTimeDuration mActiveDuration; + // The time within the active interval. + StickyTimeDuration mActiveTime; // The effect end time in local time (i.e. an offset from the effect's // start time). Will equal StickyTimeDuration::Forever() if the animation // plays indefinitely. @@ -62,12 +64,12 @@ struct ComputedTiming } enum class AnimationPhase { - Null, // Not sampled (null sample time) + Idle, // Not sampled (null sample time) Before, // Sampled prior to the start of the active interval Active, // Sampled within the active interval After // Sampled after (or at) the end of the active interval }; - AnimationPhase mPhase = AnimationPhase::Null; + AnimationPhase mPhase = AnimationPhase::Idle; ComputedTimingFunction::BeforeFlag mBeforeFlag = ComputedTimingFunction::BeforeFlag::Unset; diff --git a/dom/animation/test/css-animations/file_event-dispatch.html b/dom/animation/test/css-animations/file_event-dispatch.html new file mode 100644 index 000000000..266205bc3 --- /dev/null +++ b/dom/animation/test/css-animations/file_event-dispatch.html @@ -0,0 +1,252 @@ +<!doctype html> +<meta charset=utf-8> +<title>Tests for CSS animation event dispatch</title> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/> +<script src="../testcommon.js"></script> +<style> + @keyframes anim { + from { margin-left: 0px; } + to { margin-left: 100px; } + } +</style> +<body> +<script> +'use strict'; + +/** + * Helper class to record the elapsedTime member of each event. + * The EventWatcher class in testharness.js allows us to wait on + * multiple events in a certain order but only records the event + * parameters of the most recent event. + */ +function AnimationEventHandler(target) { + this.target = target; + this.target.onanimationstart = function(evt) { + this.animationstart = evt.elapsedTime; + }.bind(this); + this.target.onanimationiteration = function(evt) { + this.animationiteration = evt.elapsedTime; + }.bind(this); + this.target.onanimationend = function(evt) { + this.animationend = evt.elapsedTime; + }.bind(this); +} +AnimationEventHandler.prototype.clear = function() { + this.animationstart = undefined; + this.animationiteration = undefined; + this.animationend = undefined; +} + +function setupAnimation(t, animationStyle) { + var div = addDiv(t, { style: "animation: " + animationStyle }); + var watcher = new EventWatcher(t, div, [ 'animationstart', + 'animationiteration', + 'animationend' ]); + var handler = new AnimationEventHandler(div); + var animation = div.getAnimations()[0]; + + return [animation, watcher, handler, div]; +} + +promise_test(function(t) { + // Add 1ms delay to ensure that the delay is not included in the elapsedTime. + const [animation, watcher] = setupAnimation(t, 'anim 100s 1ms'); + + return watcher.wait_for('animationstart').then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Idle -> Active'); + +promise_test(function(t) { + const [animation, watcher, handler] = setupAnimation(t, 'anim 100s'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + assert_equals(handler.animationstart, 0.0); + assert_equals(handler.animationend, 100); + }); +}, 'Idle -> After'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + return animation.ready.then(function() { + // Seek to Active phase. + animation.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('animationstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Before -> Active'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + return animation.ready.then(function() { + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', 'animationend' ]); + }).then(function(evt) { + assert_equals(handler.animationstart, 0.0); + assert_equals(handler.animationend, 100.0); + }); +}, 'Before -> After'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + // Seek to Active phase. + animation.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('animationstart').then(function() { + // Seek to Before phase. + animation.currentTime = 0; + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Before'); + +promise_test(function(t) { + const [animation, watcher, handler] = setupAnimation(t, 'anim 100s paused'); + + return watcher.wait_for('animationstart').then(function(evt) { + // Seek to After phase. + animation.finish(); + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100.0); + }); +}, 'Active -> After'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + // Seek to Before phase. + animation.currentTime = 0; + handler.clear(); + return watcher.wait_for([ 'animationstart', 'animationend' ]); + }).then(function() { + assert_equals(handler.animationstart, 100.0); + assert_equals(handler.animationend, 0.0); + }); +}, 'After -> Before'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + // Seek to Active phase. + animation.currentTime = 100 * MS_PER_SEC; + handler.clear(); + return watcher.wait_for('animationstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100.0); + }); +}, 'After -> Active'); + +promise_test(function(t) { + const [animation, watcher, handler] + = setupAnimation(t, 'anim 100s 100s 3 paused'); + + return animation.ready.then(function() { + // Seek to iteration 0 (no animationiteration event should be dispatched) + animation.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('animationstart'); + }).then(function(evt) { + // Seek to iteration 2 + animation.currentTime = 300 * MS_PER_SEC; + handler.clear(); + return watcher.wait_for('animationiteration'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 200); + // Seek to After phase (no animationiteration event should be dispatched) + animation.currentTime = 400 * MS_PER_SEC; + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 300); + }); +}, 'Active -> Active (forwards)'); + +promise_test(function(t) { + const [animation, watcher, handler] = setupAnimation(t, 'anim 100s 100s 3'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + // Seek to iteration 2 (no animationiteration event should be dispatched) + animation.pause(); + animation.currentTime = 300 * MS_PER_SEC; + return watcher.wait_for('animationstart'); + }).then(function() { + // Seek to mid of iteration 0 phase. + animation.currentTime = 200 * MS_PER_SEC; + return watcher.wait_for('animationiteration'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 200.0); + // Seek to before phase (no animationiteration event should be dispatched) + animation.currentTime = 0; + return watcher.wait_for('animationend'); + }); +}, 'Active -> Active (backwards)'); + +promise_test(function(t) { + const [animation, watcher, handler, div] = + setupAnimation(t, 'anim 100s paused'); + return watcher.wait_for('animationstart').then(function(evt) { + // Seek to Idle phase. + div.style.display = 'none'; + flushComputedStyle(div); + + // FIXME: bug 1302648: Add test for animationcancel event here. + + // Restart this animation. + div.style.display = ''; + return watcher.wait_for('animationstart'); + }); +}, 'Active -> Idle -> Active: animationstart is fired by restarting animation'); + +promise_test(function(t) { + const [animation, watcher, handler, div] = + setupAnimation(t, 'anim 100s 100s 2 paused'); + + // Make After. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function(evt) { + animation.playbackRate = -1; + return watcher.wait_for('animationstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 200); + // Seek to 1st iteration + animation.currentTime = 200 * MS_PER_SEC - 1; + return watcher.wait_for('animationiteration'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100); + // Seek to before + animation.currentTime = 100 * MS_PER_SEC - 1; + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0); + assert_equals(animation.playState, 'running'); // delay + }); +}, 'Negative playbackRate sanity test(Before -> Active -> Before)'); + +done(); +</script> +</body> +</html> diff --git a/dom/animation/test/css-animations/file_event-order.html b/dom/animation/test/css-animations/file_event-order.html new file mode 100644 index 000000000..da78b6541 --- /dev/null +++ b/dom/animation/test/css-animations/file_event-order.html @@ -0,0 +1,160 @@ +<!doctype html> +<meta charset=utf-8> +<title>Tests for CSS animation event order</title> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/> +<script src="../testcommon.js"></script> +<style> + @keyframes anim { + from { margin-left: 0px; } + to { margin-left: 100px; } + } +</style> +<body> +<script type='text/javascript'> +'use strict'; + +/** + * Asserts that the set of actual and received events match. + * @param actualEvents An array of the received AnimationEvent objects. + * @param expectedEvents A series of array objects representing the expected + * events, each having the form: + * [ event type, target element, elapsed time ] + */ +function checkEvents(actualEvents, ...expectedEvents) { + assert_equals(actualEvents.length, expectedEvents.length, + `Number of actual events (${actualEvents.length}: \ +${actualEvents.map(event => event.type).join(', ')}) should match expected \ +events (${expectedEvents.map(event => event.type).join(', ')})`); + + actualEvents.forEach((actualEvent, i) => { + assert_equals(expectedEvents[i][0], actualEvent.type, + 'Event type should match'); + assert_equals(expectedEvents[i][1], actualEvent.target, + 'Event target should match'); + assert_equals(expectedEvents[i][2], actualEvent.elapsedTime, + 'Event\'s elapsed time should match'); + }); +} + +function setupAnimation(t, animationStyle, receiveEvents) { + const div = addDiv(t, { style: "animation: " + animationStyle }); + const watcher = new EventWatcher(t, div, [ 'animationstart', + 'animationiteration', + 'animationend' ]); + + ['start', 'iteration', 'end'].forEach(name => { + div['onanimation' + name] = function(evt) { + receiveEvents.push({ type: evt.type, + target: evt.target, + elapsedTime: evt.elapsedTime }); + }.bind(this); + }); + + const animation = div.getAnimations()[0]; + + return [animation, watcher, div]; +} + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 100s 2 paused', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 100s 2 paused', events); + + return Promise.all([ watcher1.wait_for('animationstart'), + watcher2.wait_for('animationstart') ]).then(function() { + checkEvents(events, ['animationstart', div1, 0], + ['animationstart', div2, 0]); + + events.length = 0; // Clear received event array + + animation1.currentTime = 100 * MS_PER_SEC; + animation2.currentTime = 100 * MS_PER_SEC; + return Promise.all([ watcher1.wait_for('animationiteration'), + watcher2.wait_for('animationiteration') ]); + }).then(function() { + checkEvents(events, ['animationiteration', div1, 100], + ['animationiteration', div2, 100]); + + events.length = 0; // Clear received event array + + animation1.finish(); + animation2.finish(); + + return Promise.all([ watcher1.wait_for('animationend'), + watcher2.wait_for('animationend') ]); + }).then(function() { + checkEvents(events, ['animationend', div1, 200], + ['animationend', div2, 200]); + }); +}, 'Test same events are ordered by elements.'); + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 200s 400s', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 300s 2', events); + + return watcher2.wait_for('animationstart').then(function(evt) { + animation1.currentTime = 400 * MS_PER_SEC; + animation2.currentTime = 400 * MS_PER_SEC; + + events.length = 0; // Clear received event array + + return Promise.all([ watcher1.wait_for('animationstart'), + watcher2.wait_for('animationiteration') ]); + }).then(function() { + checkEvents(events, ['animationiteration', div2, 300], + ['animationstart', div1, 0]); + }); +}, 'Test start and iteration events are ordered by time.'); + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 150s', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 100s 2', events); + + return Promise.all([ watcher1.wait_for('animationstart'), + watcher2.wait_for('animationstart') ]).then(function() { + animation1.currentTime = 150 * MS_PER_SEC; + animation2.currentTime = 150 * MS_PER_SEC; + + events.length = 0; // Clear received event array + + return Promise.all([ watcher1.wait_for('animationend'), + watcher2.wait_for('animationiteration') ]); + }).then(function() { + checkEvents(events, ['animationiteration', div2, 100], + ['animationend', div1, 150]); + }); +}, 'Test iteration and end events are ordered by time.'); + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 100s 100s', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 100s 2', events); + + animation1.finish(); + animation2.finish(); + + return Promise.all([ watcher1.wait_for([ 'animationstart', + 'animationend' ]), + watcher2.wait_for([ 'animationstart', + 'animationend' ]) ]).then(function() { + checkEvents(events, ['animationstart', div2, 0], + ['animationstart', div1, 0], + ['animationend', div1, 100], + ['animationend', div2, 200]); + }); +}, 'Test start and end events are sorted correctly when fired simultaneously'); + +done(); +</script> +</body> +</html> diff --git a/dom/animation/test/css-animations/test_event-dispatch.html b/dom/animation/test/css-animations/test_event-dispatch.html new file mode 100644 index 000000000..de3be0301 --- /dev/null +++ b/dom/animation/test/css-animations/test_event-dispatch.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_event-dispatch.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-transitions/test_csstransition-events.html b/dom/animation/test/css-animations/test_event-order.html index 92559ad67..57f1f3876 100644 --- a/dom/animation/test/css-transitions/test_csstransition-events.html +++ b/dom/animation/test/css-animations/test_event-order.html @@ -9,6 +9,7 @@ setup({explicit_done: true}); SpecialPowers.pushPrefEnv( { "set": [["dom.animations-api.core.enabled", true]]}, function() { - window.open("file_csstransition-events.html"); + window.open("file_event-order.html"); }); </script> +</html> diff --git a/dom/animation/test/css-transitions/file_animation-cancel.html b/dom/animation/test/css-transitions/file_animation-cancel.html index 6094b383f..71f02fb11 100644 --- a/dom/animation/test/css-transitions/file_animation-cancel.html +++ b/dom/animation/test/css-transitions/file_animation-cancel.html @@ -11,13 +11,12 @@ promise_test(function(t) { div.style.transition = 'margin-left 100s'; div.style.marginLeft = '1000px'; - flushComputedStyle(div); - var animation = div.getAnimations()[0]; - return animation.ready.then(waitForFrame).then(function() { + var transition = div.getAnimations()[0]; + return transition.ready.then(waitForFrame).then(function() { assert_not_equals(getComputedStyle(div).marginLeft, '1000px', 'transform style is animated before cancelling'); - animation.cancel(); + transition.cancel(); assert_equals(getComputedStyle(div).marginLeft, div.style.marginLeft, 'transform style is no longer animated after cancelling'); }); @@ -29,45 +28,21 @@ promise_test(function(t) { div.style.transition = 'margin-left 100s'; div.style.marginLeft = '1000px'; - flushComputedStyle(div); - - div.addEventListener('transitionend', function() { - assert_unreached('Got unexpected end event on cancelled transition'); - }); - - var animation = div.getAnimations()[0]; - return animation.ready.then(function() { - // Seek to just before the end then cancel - animation.currentTime = 99.9 * 1000; - animation.cancel(); - // Then wait a couple of frames and check that no event was dispatched - return waitForAnimationFrames(2); - }); -}, 'Cancelled CSS transitions do not dispatch events'); - -promise_test(function(t) { - var div = addDiv(t, { style: 'margin-left: 0px' }); - flushComputedStyle(div); - - div.style.transition = 'margin-left 100s'; - div.style.marginLeft = '1000px'; - flushComputedStyle(div); - - var animation = div.getAnimations()[0]; - return animation.ready.then(function() { - animation.cancel(); + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + transition.cancel(); assert_equals(getComputedStyle(div).marginLeft, '1000px', 'margin-left style is not animated after cancelling'); - animation.play(); + transition.play(); assert_equals(getComputedStyle(div).marginLeft, '0px', 'margin-left style is animated after re-starting transition'); - return animation.ready; + return transition.ready; }).then(function() { - assert_equals(animation.playState, 'running', + assert_equals(transition.playState, 'running', 'Transition succeeds in running after being re-started'); }); -}, 'After cancelling a transition, it can still be re-used'); +}, 'After canceling a transition, it can still be re-used'); promise_test(function(t) { var div = addDiv(t, { style: 'margin-left: 0px' }); @@ -75,20 +50,19 @@ promise_test(function(t) { div.style.transition = 'margin-left 100s'; div.style.marginLeft = '1000px'; - flushComputedStyle(div); - var animation = div.getAnimations()[0]; - return animation.ready.then(function() { - animation.finish(); - animation.cancel(); + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + transition.finish(); + transition.cancel(); assert_equals(getComputedStyle(div).marginLeft, '1000px', 'margin-left style is not animated after cancelling'); - animation.play(); + transition.play(); assert_equals(getComputedStyle(div).marginLeft, '0px', 'margin-left style is animated after re-starting transition'); - return animation.ready; + return transition.ready; }).then(function() { - assert_equals(animation.playState, 'running', + assert_equals(transition.playState, 'running', 'Transition succeeds in running after being re-started'); }); }, 'After cancelling a finished transition, it can still be re-used'); @@ -99,10 +73,9 @@ test(function(t) { div.style.transition = 'margin-left 100s'; div.style.marginLeft = '1000px'; - flushComputedStyle(div); - var animation = div.getAnimations()[0]; - animation.cancel(); + var transition = div.getAnimations()[0]; + transition.cancel(); assert_equals(getComputedStyle(div).marginLeft, '1000px', 'margin-left style is not animated after cancelling'); @@ -113,7 +86,7 @@ test(function(t) { assert_equals(getComputedStyle(div).marginLeft, '1000px', 'margin-left style is still not animated after updating' + ' transition-duration'); - assert_equals(animation.playState, 'idle', + assert_equals(transition.playState, 'idle', 'Transition is still idle after updating transition-duration'); }, 'After cancelling a transition, updating transition properties doesn\'t make' + ' it live again'); @@ -124,15 +97,14 @@ promise_test(function(t) { div.style.transition = 'margin-left 100s'; div.style.marginLeft = '1000px'; - flushComputedStyle(div); - var animation = div.getAnimations()[0]; - return animation.ready.then(function() { - assert_equals(animation.playState, 'running'); + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + assert_equals(transition.playState, 'running'); div.style.display = 'none'; return waitForFrame(); }).then(function() { - assert_equals(animation.playState, 'idle'); + assert_equals(transition.playState, 'idle'); assert_equals(getComputedStyle(div).marginLeft, '1000px'); }); }, 'Setting display:none on an element cancels its transitions'); @@ -147,19 +119,115 @@ promise_test(function(t) { childDiv.style.transition = 'margin-left 100s'; childDiv.style.marginLeft = '1000px'; - flushComputedStyle(childDiv); - var animation = childDiv.getAnimations()[0]; - return animation.ready.then(function() { - assert_equals(animation.playState, 'running'); + var transition = childDiv.getAnimations()[0]; + return transition.ready.then(function() { + assert_equals(transition.playState, 'running'); parentDiv.style.display = 'none'; return waitForFrame(); }).then(function() { - assert_equals(animation.playState, 'idle'); + assert_equals(transition.playState, 'idle'); assert_equals(getComputedStyle(childDiv).marginLeft, '1000px'); }); }, 'Setting display:none cancels transitions on a child element'); +promise_test(function(t) { + var div = addDiv(t, { style: 'margin-left: 0px' }); + flushComputedStyle(div); + + div.style.transition = 'margin-left 100s'; + div.style.marginLeft = '1000px'; + + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + assert_equals(transition.playState, 'running'); + // Set an unrecognized property value + div.style.transitionProperty = 'none'; + flushComputedStyle(div); + return waitForFrame(); + }).then(function() { + assert_equals(transition.playState, 'idle'); + assert_equals(getComputedStyle(div).marginLeft, '1000px'); + }); +}, 'Removing a property from transition-property cancels transitions on that '+ + 'property'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'margin-left: 0px' }); + flushComputedStyle(div); + + div.style.transition = 'margin-left 100s'; + div.style.marginLeft = '1000px'; + + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + assert_equals(transition.playState, 'running'); + div.style.transition = 'margin-top 10s -10s'; // combined duration is zero + flushComputedStyle(div); + return waitForFrame(); + }).then(function() { + assert_equals(transition.playState, 'idle'); + assert_equals(getComputedStyle(div).marginLeft, '1000px'); + }); +}, 'Setting zero combined duration'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'margin-left: 0px' }); + flushComputedStyle(div); + + div.style.transition = 'margin-left 100s'; + div.style.marginLeft = '1000px'; + + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + assert_equals(transition.playState, 'running'); + div.style.marginLeft = '2000px'; + flushComputedStyle(div); + return waitForFrame(); + }).then(function() { + assert_equals(transition.playState, 'idle'); + }); +}, 'Changing style to another interpolable value cancels the original ' + + 'transition'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'margin-left: 0px' }); + flushComputedStyle(div); + + div.style.transition = 'margin-left 100s'; + div.style.marginLeft = '1000px'; + + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + assert_equals(transition.playState, 'running'); + div.style.marginLeft = 'auto'; + flushComputedStyle(div); + return waitForFrame(); + }).then(function() { + assert_equals(div.getAnimations().length, 0, + 'There should be no transitions'); + assert_equals(transition.playState, 'idle'); + }); +}, 'An after-change style value can\'t be interpolated'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'margin-left: 0px' }); + flushComputedStyle(div); + + div.style.transition = 'margin-left 100s'; + div.style.marginLeft = '1000px'; + + var transition = div.getAnimations()[0]; + return transition.ready.then(function() { + assert_equals(transition.playState, 'running'); + div.style.marginLeft = '0px'; + flushComputedStyle(div); + return waitForFrame(); + }).then(function() { + assert_equals(transition.playState, 'idle'); + }); +}, 'Reversing a running transition cancels the original transition'); + done(); </script> </body> diff --git a/dom/animation/test/css-transitions/file_csstransition-events.html b/dom/animation/test/css-transitions/file_csstransition-events.html deleted file mode 100644 index 5011bc130..000000000 --- a/dom/animation/test/css-transitions/file_csstransition-events.html +++ /dev/null @@ -1,223 +0,0 @@ -<!doctype html> -<meta charset=utf-8> -<title>Tests for CSS-Transition events</title> -<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#transition-events"> -<script src="../testcommon.js"></script> -<body> -<script> -'use strict'; - -/** - * Helper class to record the elapsedTime member of each event. - * The EventWatcher class in testharness.js allows us to wait on - * multiple events in a certain order but only records the event - * parameters of the most recent event. - */ -function TransitionEventHandler(target) { - this.target = target; - this.target.ontransitionrun = function(evt) { - this.transitionrun = evt.elapsedTime; - }.bind(this); - this.target.ontransitionstart = function(evt) { - this.transitionstart = evt.elapsedTime; - }.bind(this); - this.target.ontransitionend = function(evt) { - this.transitionend = evt.elapsedTime; - }.bind(this); -} - -TransitionEventHandler.prototype.clear = function() { - this.transitionrun = undefined; - this.transitionstart = undefined; - this.transitionend = undefined; -}; - -function setupTransition(t, transitionStyle) { - var div, watcher, handler, transition; - transitionStyle = transitionStyle || 'transition: margin-left 100s 100s'; - div = addDiv(t, { style: transitionStyle }); - watcher = new EventWatcher(t, div, [ 'transitionrun', - 'transitionstart', - 'transitionend' ]); - handler = new TransitionEventHandler(div); - flushComputedStyle(div); - - div.style.marginLeft = '100px'; - flushComputedStyle(div); - - transition = div.getAnimations()[0]; - - return [transition, watcher, handler]; -} - -// On the next frame (i.e. when events are queued), whether or not the -// transition is still pending depends on the implementation. -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - return watcher.wait_for('transitionrun').then(function(evt) { - assert_equals(evt.elapsedTime, 0.0); - }); -}, 'Idle -> Pending or Before'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - // Force the transition to leave the idle phase - transition.startTime = document.timeline.currentTime; - return watcher.wait_for('transitionrun').then(function(evt) { - assert_equals(evt.elapsedTime, 0.0); - }); -}, 'Idle -> Before'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - // Seek to Active phase. - transition.currentTime = 100 * MS_PER_SEC; - transition.pause(); - return watcher.wait_for([ 'transitionrun', - 'transitionstart' ]).then(function(evt) { - assert_equals(handler.transitionrun, 0.0); - assert_equals(handler.transitionstart, 0.0); - }); -}, 'Idle or Pending -> Active'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - // Seek to After phase. - transition.finish(); - return watcher.wait_for([ 'transitionrun', - 'transitionstart', - 'transitionend' ]).then(function(evt) { - assert_equals(handler.transitionrun, 0.0); - assert_equals(handler.transitionstart, 0.0); - assert_equals(handler.transitionend, 100.0); - }); -}, 'Idle or Pending -> After'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - - return Promise.all([ watcher.wait_for('transitionrun'), - transition.ready ]).then(function() { - transition.currentTime = 100 * MS_PER_SEC; - return watcher.wait_for('transitionstart'); - }).then(function() { - assert_equals(handler.transitionstart, 0.0); - }); -}, 'Before -> Active'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - return Promise.all([ watcher.wait_for('transitionrun'), - transition.ready ]).then(function() { - // Seek to After phase. - transition.currentTime = 200 * MS_PER_SEC; - return watcher.wait_for([ 'transitionstart', 'transitionend' ]); - }).then(function(evt) { - assert_equals(handler.transitionstart, 0.0); - assert_equals(handler.transitionend, 100.0); - }); -}, 'Before -> After'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - // Seek to Active phase. - transition.currentTime = 100 * MS_PER_SEC; - return watcher.wait_for([ 'transitionrun', - 'transitionstart' ]).then(function(evt) { - // Seek to Before phase. - transition.currentTime = 0; - return watcher.wait_for('transitionend'); - }).then(function(evt) { - assert_equals(evt.elapsedTime, 0.0); - }); -}, 'Active -> Before'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - // Seek to Active phase. - transition.currentTime = 100 * MS_PER_SEC; - return watcher.wait_for([ 'transitionrun', - 'transitionstart' ]).then(function(evt) { - // Seek to After phase. - transition.currentTime = 200 * MS_PER_SEC; - return watcher.wait_for('transitionend'); - }).then(function(evt) { - assert_equals(evt.elapsedTime, 100.0); - }); -}, 'Active -> After'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - // Seek to After phase. - transition.finish(); - return watcher.wait_for([ 'transitionrun', - 'transitionstart', - 'transitionend' ]).then(function(evt) { - // Seek to Before phase. - transition.currentTime = 0; - return watcher.wait_for([ 'transitionstart', 'transitionend' ]); - }).then(function(evt) { - assert_equals(handler.transitionstart, 100.0); - assert_equals(handler.transitionend, 0.0); - }); -}, 'After -> Before'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - // Seek to After phase. - transition.finish(); - return watcher.wait_for([ 'transitionrun', - 'transitionstart', - 'transitionend' ]).then(function(evt) { - // Seek to Active phase. - transition.currentTime = 100 * MS_PER_SEC; - return watcher.wait_for('transitionstart'); - }).then(function(evt) { - assert_equals(evt.elapsedTime, 100.0); - }); -}, 'After -> Active'); - -promise_test(function(t) { - var [transition, watcher, handler] = - setupTransition(t, 'transition: margin-left 100s -50s'); - - return watcher.wait_for([ 'transitionrun', - 'transitionstart' ]).then(function() { - assert_equals(handler.transitionrun, 50.0); - assert_equals(handler.transitionstart, 50.0); - transition.finish(); - return watcher.wait_for('transitionend'); - }).then(function(evt) { - assert_equals(evt.elapsedTime, 100.0); - }); -}, 'Calculating the interval start and end time with negative start delay.'); - -promise_test(function(t) { - var [transition, watcher, handler] = setupTransition(t); - - return watcher.wait_for('transitionrun').then(function(evt) { - // We can't set the end delay via generated effect timing. - // Because CSS-Transition use the AnimationEffectTimingReadOnly. - transition.effect = new KeyframeEffect(handler.target, - { marginleft: [ '0px', '100px' ]}, - { duration: 100 * MS_PER_SEC, - endDelay: -50 * MS_PER_SEC }); - // Seek to Before and play. - transition.cancel(); - transition.play(); - return watcher.wait_for('transitionstart'); - }).then(function() { - assert_equals(handler.transitionstart, 0.0); - - // Seek to After phase. - transition.finish(); - return watcher.wait_for('transitionend'); - }).then(function(evt) { - assert_equals(evt.elapsedTime, 50.0); - }); -}, 'Calculating the interval start and end time with negative end delay.'); - -done(); -</script> -</body> -</html> diff --git a/dom/animation/test/css-transitions/file_event-dispatch.html b/dom/animation/test/css-transitions/file_event-dispatch.html new file mode 100644 index 000000000..7140cda36 --- /dev/null +++ b/dom/animation/test/css-transitions/file_event-dispatch.html @@ -0,0 +1,474 @@ +<!doctype html> +<meta charset=utf-8> +<title>Tests for CSS-Transition events</title> +<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#transition-events"> +<script src="../testcommon.js"></script> +<body> +<script> +'use strict'; + +/** + * Helper class to record the elapsedTime member of each event. + * The EventWatcher class in testharness.js allows us to wait on + * multiple events in a certain order but only records the event + * parameters of the most recent event. + */ +function TransitionEventHandler(target) { + this.target = target; + this.target.ontransitionrun = function(evt) { + this.transitionrun = evt.elapsedTime; + }.bind(this); + this.target.ontransitionstart = function(evt) { + this.transitionstart = evt.elapsedTime; + }.bind(this); + this.target.ontransitionend = function(evt) { + this.transitionend = evt.elapsedTime; + }.bind(this); + this.target.ontransitioncancel = function(evt) { + this.transitioncancel = evt.elapsedTime; + }.bind(this); +} + +TransitionEventHandler.prototype.clear = function() { + this.transitionrun = undefined; + this.transitionstart = undefined; + this.transitionend = undefined; + this.transitioncancel = undefined; +}; + +function setupTransition(t, transitionStyle) { + var div = addDiv(t, { style: 'transition: ' + transitionStyle }); + var watcher = new EventWatcher(t, div, [ 'transitionrun', + 'transitionstart', + 'transitionend', + 'transitioncancel' ]); + flushComputedStyle(div); + + div.style.marginLeft = '100px'; + var transition = div.getAnimations()[0]; + + return [transition, watcher, div]; +} + +// On the next frame (i.e. when events are queued), whether or not the +// transition is still pending depends on the implementation. +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + return watcher.wait_for('transitionrun').then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Idle -> Pending or Before'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + // Force the transition to leave the idle phase + transition.startTime = document.timeline.currentTime; + return watcher.wait_for('transitionrun').then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Idle -> Before'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + var handler = new TransitionEventHandler(div); + + // Seek to Active phase. + transition.currentTime = 100 * MS_PER_SEC; + transition.pause(); + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + assert_equals(handler.transitionrun, 0.0); + assert_equals(handler.transitionstart, 0.0); + }); +}, 'Idle or Pending -> Active'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + var handler = new TransitionEventHandler(div); + + // Seek to After phase. + transition.finish(); + return watcher.wait_for([ 'transitionrun', + 'transitionstart', + 'transitionend' ]).then(function(evt) { + assert_equals(handler.transitionrun, 0.0); + assert_equals(handler.transitionstart, 0.0); + assert_equals(handler.transitionend, 100.0); + }); +}, 'Idle or Pending -> After'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + + return Promise.all([ watcher.wait_for('transitionrun'), + transition.ready ]).then(function() { + // Make idle + div.style.display = 'none'; + flushComputedStyle(div); + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Before -> Idle (display: none)'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + + return Promise.all([ watcher.wait_for('transitionrun'), + transition.ready ]).then(function() { + // Make idle + transition.timeline = null; + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Before -> Idle (Animation.timeline = null)'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + + return Promise.all([ watcher.wait_for('transitionrun'), + transition.ready ]).then(function() { + transition.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('transitionstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Before -> Active'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + var handler = new TransitionEventHandler(div); + + return Promise.all([ watcher.wait_for('transitionrun'), + transition.ready ]).then(function() { + // Seek to After phase. + transition.currentTime = 200 * MS_PER_SEC; + return watcher.wait_for([ 'transitionstart', 'transitionend' ]); + }).then(function(evt) { + assert_equals(handler.transitionstart, 0.0); + assert_equals(handler.transitionend, 100.0); + }); +}, 'Before -> After'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s'); + + // Seek to Active start position. + transition.pause(); + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Make idle + div.style.display = 'none'; + flushComputedStyle(div); + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Idle, no delay (display: none)'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s'); + + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Make idle + transition.currentTime = 0; + transition.timeline = null; + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Idle, no delay (Animation.timeline = null)'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + // Pause so the currentTime is fixed and we can accurately compare the event + // time in transition cancel events. + transition.pause(); + + // Seek to Active phase. + transition.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Make idle + div.style.display = 'none'; + flushComputedStyle(div); + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Idle, with positive delay (display: none)'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + + // Seek to Active phase. + transition.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Make idle + transition.currentTime = 100 * MS_PER_SEC; + transition.timeline = null; + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Idle, with positive delay (Animation.timeline = null)'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s -50s'); + + // Pause so the currentTime is fixed and we can accurately compare the event + // time in transition cancel events. + transition.pause(); + + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Make idle + div.style.display = 'none'; + flushComputedStyle(div); + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 50.0); + }); +}, 'Active -> Idle, with negative delay (display: none)'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s -50s'); + + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Make idle + transition.currentTime = 50 * MS_PER_SEC; + transition.timeline = null; + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Idle, with negative delay (Animation.timeline = null)'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + // Seek to Active phase. + transition.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Seek to Before phase. + transition.currentTime = 0; + return watcher.wait_for('transitionend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Before'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + // Seek to Active phase. + transition.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Seek to After phase. + transition.currentTime = 200 * MS_PER_SEC; + return watcher.wait_for('transitionend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100.0); + }); +}, 'Active -> After'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + var handler = new TransitionEventHandler(div); + + // Seek to After phase. + transition.finish(); + return watcher.wait_for([ 'transitionrun', + 'transitionstart', + 'transitionend' ]).then(function(evt) { + // Seek to Before phase. + transition.currentTime = 0; + return watcher.wait_for([ 'transitionstart', 'transitionend' ]); + }).then(function(evt) { + assert_equals(handler.transitionstart, 100.0); + assert_equals(handler.transitionend, 0.0); + }); +}, 'After -> Before'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s 100s'); + // Seek to After phase. + transition.finish(); + return watcher.wait_for([ 'transitionrun', + 'transitionstart', + 'transitionend' ]).then(function(evt) { + // Seek to Active phase. + transition.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('transitionstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100.0); + }); +}, 'After -> Active'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s -50s'); + var handler = new TransitionEventHandler(div); + + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function() { + assert_equals(handler.transitionrun, 50.0); + assert_equals(handler.transitionstart, 50.0); + transition.finish(); + return watcher.wait_for('transitionend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100.0); + }); +}, 'Calculating the interval start and end time with negative start delay.'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + var handler = new TransitionEventHandler(div); + + return watcher.wait_for('transitionrun').then(function(evt) { + // We can't set the end delay via generated effect timing. + // Because CSS-Transition use the AnimationEffectTimingReadOnly. + transition.effect = new KeyframeEffect(div, + { marginleft: [ '0px', '100px' ]}, + { duration: 100 * MS_PER_SEC, + endDelay: -50 * MS_PER_SEC }); + // Seek to Before and play. + transition.cancel(); + transition.play(); + return watcher.wait_for([ 'transitioncancel', + 'transitionrun', + 'transitionstart' ]); + }).then(function() { + assert_equals(handler.transitionstart, 0.0); + + // Seek to After phase. + transition.finish(); + return watcher.wait_for('transitionend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 50.0); + }); +}, 'Calculating the interval start and end time with negative end delay.'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + + return watcher.wait_for('transitionrun').then(function() { + // Make idle + div.style.display = 'none'; + flushComputedStyle(div); + return watcher.wait_for('transitioncancel'); + }).then(function() { + transition.cancel(); + // Then wait a couple of frames and check that no event was dispatched + return waitForAnimationFrames(2); + }); +}, 'Call Animation.cancel after cancelling transition.'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + + return watcher.wait_for('transitionrun').then(function(evt) { + // Make idle + div.style.display = 'none'; + flushComputedStyle(div); + transition.play(); + watcher.wait_for([ 'transitioncancel', + 'transitionrun', + 'transitionstart' ]); + }); +}, 'Restart transition after cancelling transition immediately'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s 100s'); + + return watcher.wait_for('transitionrun').then(function(evt) { + // Make idle + div.style.display = 'none'; + flushComputedStyle(div); + transition.play(); + transition.cancel(); + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + // Then wait a couple of frames and check that no event was dispatched + return waitForAnimationFrames(2); + }); +}, 'Call Animation.cancel after restarting transition immediately'); + +promise_test(function(t) { + var [transition, watcher] = + setupTransition(t, 'margin-left 100s'); + + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + // Make idle + transition.timeline = null; + return watcher.wait_for('transitioncancel'); + }).then(function(evt) { + transition.timeline = document.timeline; + transition.play(); + + return watcher.wait_for(['transitionrun', 'transitionstart']); + }); +}, 'Set timeline and play transition after clear the timeline'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s'); + + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function() { + transition.cancel(); + return watcher.wait_for('transitioncancel'); + }).then(function() { + // Make After phase + transition.effect = null; + + // Then wait a couple of frames and check that no event was dispatched + return waitForAnimationFrames(2); + }); +}, 'Set null target effect after cancel the transition'); + +promise_test(function(t) { + var [transition, watcher, div] = + setupTransition(t, 'margin-left 100s'); + + return watcher.wait_for([ 'transitionrun', + 'transitionstart' ]).then(function(evt) { + transition.effect = null; + return watcher.wait_for('transitionend'); + }).then(function(evt) { + transition.cancel(); + return watcher.wait_for('transitioncancel'); + }); +}, 'Cancel the transition after clearing the target effect'); + +done(); +</script> +</body> +</html> diff --git a/dom/animation/test/css-transitions/file_setting-effect.html b/dom/animation/test/css-transitions/file_setting-effect.html index c61877194..81279ea55 100644 --- a/dom/animation/test/css-transitions/file_setting-effect.html +++ b/dom/animation/test/css-transitions/file_setting-effect.html @@ -7,6 +7,8 @@ promise_test(function(t) { var div = addDiv(t); + var watcher = new EventWatcher(t, div, [ 'transitionend', + 'transitioncancel' ]); div.style.left = '0px'; div.style.transition = 'left 100s'; @@ -20,11 +22,14 @@ promise_test(function(t) { assert_equals(transition.transitionProperty, 'left'); assert_equals(transition.playState, 'finished'); assert_equals(window.getComputedStyle(div).left, '100px'); + return watcher.wait_for('transitionend'); }); }, 'Test for removing a transition effect'); promise_test(function(t) { var div = addDiv(t); + var watcher = new EventWatcher(t, div, [ 'transitionend', + 'transitioncancel' ]); div.style.left = '0px'; div.style.transition = 'left 100s'; @@ -46,6 +51,8 @@ promise_test(function(t) { promise_test(function(t) { var div = addDiv(t); + var watcher = new EventWatcher(t, div, [ 'transitionend', + 'transitioncancel' ]); div.style.left = '0px'; div.style.width = '0px'; @@ -65,6 +72,8 @@ promise_test(function(t) { promise_test(function(t) { var div = addDiv(t); + var watcher = new EventWatcher(t, div, [ 'transitionend', + 'transitioncancel' ]); div.style.left = '0px'; div.style.width = '0px'; diff --git a/dom/animation/test/css-transitions/test_event-dispatch.html b/dom/animation/test/css-transitions/test_event-dispatch.html new file mode 100644 index 000000000..c90431cd1 --- /dev/null +++ b/dom/animation/test/css-transitions/test_event-dispatch.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_event-dispatch.html"); + }); +</script> diff --git a/dom/animation/test/mochitest.ini b/dom/animation/test/mochitest.ini index feb424518..db6dffada 100644 --- a/dom/animation/test/mochitest.ini +++ b/dom/animation/test/mochitest.ini @@ -19,6 +19,8 @@ support-files = css-animations/file_document-get-animations.html css-animations/file_effect-target.html css-animations/file_element-get-animations.html + css-animations/file_event-dispatch.html + css-animations/file_event-order.html css-animations/file_keyframeeffect-getkeyframes.html css-animations/file_pseudoElement-get-animations.html css-transitions/file_animation-cancel.html @@ -32,6 +34,7 @@ support-files = css-transitions/file_document-get-animations.html css-transitions/file_effect-target.html css-transitions/file_element-get-animations.html + css-transitions/file_event-dispatch.html css-transitions/file_keyframeeffect-getkeyframes.html css-transitions/file_pseudoElement-get-animations.html css-transitions/file_setting-effect.html @@ -72,6 +75,8 @@ support-files = [css-animations/test_document-get-animations.html] [css-animations/test_effect-target.html] [css-animations/test_element-get-animations.html] +[css-animations/test_event-dispatch.html] +[css-animations/test_event-order.html] [css-animations/test_keyframeeffect-getkeyframes.html] [css-animations/test_pseudoElement-get-animations.html] [css-transitions/test_animation-cancel.html] @@ -85,6 +90,7 @@ support-files = [css-transitions/test_document-get-animations.html] [css-transitions/test_effect-target.html] [css-transitions/test_element-get-animations.html] +[css-transitions/test_event-dispatch.html] [css-transitions/test_keyframeeffect-getkeyframes.html] [css-transitions/test_pseudoElement-get-animations.html] [css-transitions/test_setting-effect.html] diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index ef87a250e..34c7d23b8 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -281,6 +281,7 @@ bool nsContentUtils::sIsCutCopyAllowed = true; bool nsContentUtils::sIsFrameTimingPrefEnabled = false; bool nsContentUtils::sIsPerformanceTimingEnabled = false; bool nsContentUtils::sIsResourceTimingEnabled = false; +bool nsContentUtils::sIsPerformanceNavigationTimingEnabled = false; bool nsContentUtils::sIsUserTimingLoggingEnabled = false; bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false; bool nsContentUtils::sEncodeDecodeURLHash = false; @@ -571,6 +572,9 @@ nsContentUtils::Init() Preferences::AddBoolVarCache(&sIsResourceTimingEnabled, "dom.enable_resource_timing", true); + Preferences::AddBoolVarCache(&sIsPerformanceNavigationTimingEnabled, + "dom.enable_performance_navigation_timing", true); + Preferences::AddBoolVarCache(&sIsUserTimingLoggingEnabled, "dom.performance.enable_user_timing_logging", false); @@ -5100,7 +5104,7 @@ nsContentUtils::TriggerLink(nsIContent *aContent, nsPresContext *aPresContext, handler->OnLinkClick(aContent, aLinkURI, fileName.IsVoid() ? aTargetSpec.get() : EmptyString().get(), - fileName, nullptr, nullptr, aIsTrusted); + fileName, nullptr, nullptr, aIsTrusted, aContent->NodePrincipal()); } } @@ -9772,9 +9776,13 @@ nsContentUtils::AttemptLargeAllocationLoad(nsIHttpChannel* aChannel) rv = aChannel->GetReferrer(getter_AddRefs(referrer)); NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo->TriggeringPrincipal(); + // Actually perform the cross process load bool reloadSucceeded = false; - rv = wbc3->ReloadInFreshProcess(docShell, uri, referrer, &reloadSucceeded); + rv = wbc3->ReloadInFreshProcess(docShell, uri, referrer, + triggeringPrincipal, &reloadSucceeded); NS_ENSURE_SUCCESS(rv, false); return reloadSucceeded; diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 0932f451e..9ae6d2155 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -2033,6 +2033,14 @@ public: } /* + * Returns true if the performance timing APIs are enabled. + */ + static bool IsPerformanceNavigationTimingEnabled() + { + return sIsPerformanceNavigationTimingEnabled; + } + + /* * Returns true if notification should be sent for peformance timing events. */ static bool SendPerformanceTimingNotifications() @@ -2825,6 +2833,7 @@ private: static uint32_t sHandlingInputTimeout; static bool sIsPerformanceTimingEnabled; static bool sIsResourceTimingEnabled; + static bool sIsPerformanceNavigationTimingEnabled; static bool sIsUserTimingLoggingEnabled; static bool sIsFrameTimingPrefEnabled; static bool sIsExperimentalAutocompleteEnabled; diff --git a/dom/base/nsDOMNavigationTiming.cpp b/dom/base/nsDOMNavigationTiming.cpp index 31b2932fb..32ce8a8cb 100644 --- a/dom/base/nsDOMNavigationTiming.cpp +++ b/dom/base/nsDOMNavigationTiming.cpp @@ -15,6 +15,9 @@ #include "nsPrintfCString.h" #include "mozilla/dom/PerformanceNavigation.h" #include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" + +using namespace mozilla; nsDOMNavigationTiming::nsDOMNavigationTiming() { @@ -30,47 +33,36 @@ nsDOMNavigationTiming::Clear() { mNavigationType = TYPE_RESERVED; mNavigationStartHighRes = 0; - mBeforeUnloadStart = 0; - mUnloadStart = 0; - mUnloadEnd = 0; - mLoadEventStart = 0; - mLoadEventEnd = 0; - mDOMLoading = 0; - mDOMInteractive = 0; - mDOMContentLoadedEventStart = 0; - mDOMContentLoadedEventEnd = 0; - mDOMComplete = 0; - - mLoadEventStartSet = false; - mLoadEventEndSet = false; - mDOMLoadingSet = false; - mDOMInteractiveSet = false; - mDOMContentLoadedEventStartSet = false; - mDOMContentLoadedEventEndSet = false; - mDOMCompleteSet = false; + mBeforeUnloadStart = TimeStamp(); + mUnloadStart = TimeStamp(); + mUnloadEnd = TimeStamp(); + mLoadEventStart = TimeStamp(); + mLoadEventEnd = TimeStamp(); + mDOMLoading = TimeStamp(); + mDOMInteractive = TimeStamp(); + mDOMContentLoadedEventStart = TimeStamp(); + mDOMContentLoadedEventEnd = TimeStamp(); + mDOMComplete = TimeStamp(); + mDocShellHasBeenActiveSinceNavigationStart = false; } DOMTimeMilliSec -nsDOMNavigationTiming::TimeStampToDOM(mozilla::TimeStamp aStamp) const +nsDOMNavigationTiming::TimeStampToDOM(TimeStamp aStamp) const { if (aStamp.IsNull()) { return 0; } - mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp; - return GetNavigationStart() + static_cast<int64_t>(duration.ToMilliseconds()); -} -DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart() -{ - return TimeStampToDOM(mozilla::TimeStamp::Now()); + TimeDuration duration = aStamp - mNavigationStart; + return GetNavigationStart() + static_cast<int64_t>(duration.ToMilliseconds()); } void nsDOMNavigationTiming::NotifyNavigationStart(DocShellState aDocShellState) { mNavigationStartHighRes = (double)PR_Now() / PR_USEC_PER_MSEC; - mNavigationStartTimeStamp = mozilla::TimeStamp::Now(); + mNavigationStart = TimeStamp::Now(); mDocShellHasBeenActiveSinceNavigationStart = (aDocShellState == DocShellState::eActive); } @@ -86,7 +78,7 @@ nsDOMNavigationTiming::NotifyFetchStart(nsIURI* aURI, Type aNavigationType) void nsDOMNavigationTiming::NotifyBeforeUnload() { - mBeforeUnloadStart = DurationFromStart(); + mBeforeUnloadStart = TimeStamp::Now(); } void @@ -99,105 +91,107 @@ nsDOMNavigationTiming::NotifyUnloadAccepted(nsIURI* aOldURI) void nsDOMNavigationTiming::NotifyUnloadEventStart() { - mUnloadStart = DurationFromStart(); + mUnloadStart = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyUnloadEventEnd() { - mUnloadEnd = DurationFromStart(); + mUnloadEnd = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyLoadEventStart() { - if (!mLoadEventStartSet) { - mLoadEventStart = DurationFromStart(); - mLoadEventStartSet = true; + if (!mLoadEventStart.IsNull()) { + return; } + mLoadEventStart = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyLoadEventEnd() { - if (!mLoadEventEndSet) { - mLoadEventEnd = DurationFromStart(); - mLoadEventEndSet = true; + if (!mLoadEventEnd.IsNull()) { + return; } + mLoadEventEnd = TimeStamp::Now(); } void -nsDOMNavigationTiming::SetDOMLoadingTimeStamp(nsIURI* aURI, mozilla::TimeStamp aValue) +nsDOMNavigationTiming::SetDOMLoadingTimeStamp(nsIURI* aURI, TimeStamp aValue) { - if (!mDOMLoadingSet) { - mLoadedURI = aURI; - mDOMLoading = TimeStampToDOM(aValue); - mDOMLoadingSet = true; + if (!mDOMLoading.IsNull()) { + return; } + mLoadedURI = aURI; + mDOMLoading = aValue; } void nsDOMNavigationTiming::NotifyDOMLoading(nsIURI* aURI) { - if (!mDOMLoadingSet) { - mLoadedURI = aURI; - mDOMLoading = DurationFromStart(); - mDOMLoadingSet = true; + if (!mDOMLoading.IsNull()) { + return; } + mLoadedURI = aURI; + mDOMLoading = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyDOMInteractive(nsIURI* aURI) { - if (!mDOMInteractiveSet) { - mLoadedURI = aURI; - mDOMInteractive = DurationFromStart(); - mDOMInteractiveSet = true; + if (!mDOMInteractive.IsNull()) { + return; } + mLoadedURI = aURI; + mDOMInteractive = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyDOMComplete(nsIURI* aURI) { - if (!mDOMCompleteSet) { - mLoadedURI = aURI; - mDOMComplete = DurationFromStart(); - mDOMCompleteSet = true; + if (!mDOMComplete.IsNull()) { + return; } + mLoadedURI = aURI; + mDOMComplete = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyDOMContentLoadedStart(nsIURI* aURI) { - if (!mDOMContentLoadedEventStartSet) { - mLoadedURI = aURI; - mDOMContentLoadedEventStart = DurationFromStart(); - mDOMContentLoadedEventStartSet = true; + if (!mDOMContentLoadedEventStart.IsNull()) { + return; } + + mLoadedURI = aURI; + mDOMContentLoadedEventStart = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyDOMContentLoadedEnd(nsIURI* aURI) { - if (!mDOMContentLoadedEventEndSet) { - mLoadedURI = aURI; - mDOMContentLoadedEventEnd = DurationFromStart(); - mDOMContentLoadedEventEndSet = true; + if (!mDOMContentLoadedEventEnd.IsNull()) { + return; } + + mLoadedURI = aURI; + mDOMContentLoadedEventEnd = TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyNonBlankPaintForRootContentDocument() { MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(!mNavigationStartTimeStamp.IsNull()); + MOZ_ASSERT(!mNavigationStart.IsNull()); - if (!mNonBlankPaintTimeStamp.IsNull()) { + if (!mNonBlankPaint.IsNull()) { return; } - mNonBlankPaintTimeStamp = TimeStamp::Now(); - TimeDuration elapsed = mNonBlankPaintTimeStamp - mNavigationStartTimeStamp; + mNonBlankPaint = TimeStamp::Now(); + TimeDuration elapsed = mNonBlankPaint - mNavigationStart; if (profiler_is_active()) { nsAutoCString spec; @@ -212,8 +206,8 @@ nsDOMNavigationTiming::NotifyNonBlankPaintForRootContentDocument() if (mDocShellHasBeenActiveSinceNavigationStart) { Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_MS, - mNavigationStartTimeStamp, - mNonBlankPaintTimeStamp); + mNavigationStart, + mNonBlankPaint); } } @@ -224,24 +218,24 @@ nsDOMNavigationTiming::NotifyDocShellStateChanged(DocShellState aDocShellState) (aDocShellState == DocShellState::eActive); } -DOMTimeMilliSec -nsDOMNavigationTiming::GetUnloadEventStart() +mozilla::TimeStamp +nsDOMNavigationTiming::GetUnloadEventStartTimeStamp() const { nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false); if (NS_SUCCEEDED(rv)) { return mUnloadStart; } - return 0; + return mozilla::TimeStamp(); } -DOMTimeMilliSec -nsDOMNavigationTiming::GetUnloadEventEnd() +mozilla::TimeStamp +nsDOMNavigationTiming::GetUnloadEventEndTimeStamp() const { nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false); if (NS_SUCCEEDED(rv)) { return mUnloadEnd; } - return 0; + return mozilla::TimeStamp(); } diff --git a/dom/base/nsDOMNavigationTiming.h b/dom/base/nsDOMNavigationTiming.h index 9babece96..3be2527ca 100644 --- a/dom/base/nsDOMNavigationTiming.h +++ b/dom/base/nsDOMNavigationTiming.h @@ -47,38 +47,91 @@ public: mozilla::TimeStamp GetNavigationStartTimeStamp() const { - return mNavigationStartTimeStamp; + return mNavigationStart; + } + + DOMTimeMilliSec GetUnloadEventStart() + { + return TimeStampToDOM(GetUnloadEventStartTimeStamp()); + } + + DOMTimeMilliSec GetUnloadEventEnd() + { + return TimeStampToDOM(GetUnloadEventEndTimeStamp()); } - DOMTimeMilliSec GetUnloadEventStart(); - DOMTimeMilliSec GetUnloadEventEnd(); DOMTimeMilliSec GetDomLoading() const { - return mDOMLoading; + return TimeStampToDOM(mDOMLoading); } DOMTimeMilliSec GetDomInteractive() const { - return mDOMInteractive; + return TimeStampToDOM(mDOMInteractive); } DOMTimeMilliSec GetDomContentLoadedEventStart() const { - return mDOMContentLoadedEventStart; + return TimeStampToDOM(mDOMContentLoadedEventStart); } DOMTimeMilliSec GetDomContentLoadedEventEnd() const { - return mDOMContentLoadedEventEnd; + return TimeStampToDOM(mDOMContentLoadedEventEnd); } DOMTimeMilliSec GetDomComplete() const { - return mDOMComplete; + return TimeStampToDOM(mDOMComplete); } DOMTimeMilliSec GetLoadEventStart() const { - return mLoadEventStart; + return TimeStampToDOM(mLoadEventStart); } DOMTimeMilliSec GetLoadEventEnd() const { - return mLoadEventEnd; + return TimeStampToDOM(mLoadEventEnd); + } + DOMTimeMilliSec GetTimeToNonBlankPaint() const + { + return TimeStampToDOM(mNonBlankPaint); + } + + DOMHighResTimeStamp GetUnloadEventStartHighRes() + { + mozilla::TimeStamp stamp = GetUnloadEventStartTimeStamp(); + if (stamp.IsNull()) { + return 0; + } + return TimeStampToDOMHighRes(stamp); + } + DOMHighResTimeStamp GetUnloadEventEndHighRes() + { + mozilla::TimeStamp stamp = GetUnloadEventEndTimeStamp(); + if (stamp.IsNull()) { + return 0; + } + return TimeStampToDOMHighRes(stamp); + } + DOMHighResTimeStamp GetDomInteractiveHighRes() const + { + return TimeStampToDOMHighRes(mDOMInteractive); + } + DOMHighResTimeStamp GetDomContentLoadedEventStartHighRes() const + { + return TimeStampToDOMHighRes(mDOMContentLoadedEventStart); + } + DOMHighResTimeStamp GetDomContentLoadedEventEndHighRes() const + { + return TimeStampToDOMHighRes(mDOMContentLoadedEventEnd); + } + DOMHighResTimeStamp GetDomCompleteHighRes() const + { + return TimeStampToDOMHighRes(mDOMComplete); + } + DOMHighResTimeStamp GetLoadEventStartHighRes() const + { + return TimeStampToDOMHighRes(mLoadEventStart); + } + DOMHighResTimeStamp GetLoadEventEndHighRes() const + { + return TimeStampToDOMHighRes(mLoadEventEnd); } enum class DocShellState : uint8_t { @@ -108,9 +161,13 @@ public: DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const; - inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) + inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) const { - mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp; + MOZ_ASSERT(!aStamp.IsNull(), "The timestamp should not be null"); + if (aStamp.IsNull()) { + return 0; + } + mozilla::TimeDuration duration = aStamp - mNavigationStart; return duration.ToMilliseconds(); } @@ -120,37 +177,29 @@ private: void Clear(); + mozilla::TimeStamp GetUnloadEventStartTimeStamp() const; + mozilla::TimeStamp GetUnloadEventEndTimeStamp() const; + nsCOMPtr<nsIURI> mUnloadedURI; nsCOMPtr<nsIURI> mLoadedURI; Type mNavigationType; DOMHighResTimeStamp mNavigationStartHighRes; - mozilla::TimeStamp mNavigationStartTimeStamp; - mozilla::TimeStamp mNonBlankPaintTimeStamp; - DOMTimeMilliSec DurationFromStart(); - - DOMTimeMilliSec mBeforeUnloadStart; - DOMTimeMilliSec mUnloadStart; - DOMTimeMilliSec mUnloadEnd; - DOMTimeMilliSec mLoadEventStart; - DOMTimeMilliSec mLoadEventEnd; - - DOMTimeMilliSec mDOMLoading; - DOMTimeMilliSec mDOMInteractive; - DOMTimeMilliSec mDOMContentLoadedEventStart; - DOMTimeMilliSec mDOMContentLoadedEventEnd; - DOMTimeMilliSec mDOMComplete; - - // Booleans to keep track of what things we've already been notified - // about. We don't update those once we've been notified about them - // once. - bool mLoadEventStartSet : 1; - bool mLoadEventEndSet : 1; - bool mDOMLoadingSet : 1; - bool mDOMInteractiveSet : 1; - bool mDOMContentLoadedEventStartSet : 1; - bool mDOMContentLoadedEventEndSet : 1; - bool mDOMCompleteSet : 1; + mozilla::TimeStamp mNavigationStart; + mozilla::TimeStamp mNonBlankPaint; + + mozilla::TimeStamp mBeforeUnloadStart; + mozilla::TimeStamp mUnloadStart; + mozilla::TimeStamp mUnloadEnd; + mozilla::TimeStamp mLoadEventStart; + mozilla::TimeStamp mLoadEventEnd; + + mozilla::TimeStamp mDOMLoading; + mozilla::TimeStamp mDOMInteractive; + mozilla::TimeStamp mDOMContentLoadedEventStart; + mozilla::TimeStamp mDOMContentLoadedEventEnd; + mozilla::TimeStamp mDOMComplete; + bool mDocShellHasBeenActiveSinceNavigationStart : 1; }; diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index e4ae7ede8..50b4449ec 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -694,6 +694,7 @@ GK_ATOM(onadapterremoved, "onadapterremoved") GK_ATOM(onafterprint, "onafterprint") GK_ATOM(onafterscriptexecute, "onafterscriptexecute") GK_ATOM(onalerting, "onalerting") +GK_ATOM(onanimationcancel, "onanimationcancel") GK_ATOM(onanimationend, "onanimationend") GK_ATOM(onanimationiteration, "onanimationiteration") GK_ATOM(onanimationstart, "onanimationstart") @@ -704,6 +705,7 @@ GK_ATOM(onattributechanged, "onattributechanged") GK_ATOM(onattributereadreq, "onattributereadreq") GK_ATOM(onattributewritereq, "onattributewritereq") GK_ATOM(onaudioprocess, "onaudioprocess") +GK_ATOM(onauxclick, "onauxclick") GK_ATOM(onbeforecopy, "onbeforecopy") GK_ATOM(onbeforecut, "onbeforecut") GK_ATOM(onbeforepaste, "onbeforepaste") @@ -941,6 +943,7 @@ GK_ATOM(ontouchstart, "ontouchstart") GK_ATOM(ontouchend, "ontouchend") GK_ATOM(ontouchmove, "ontouchmove") GK_ATOM(ontouchcancel, "ontouchcancel") +GK_ATOM(ontransitioncancel, "ontransitioncancel") GK_ATOM(ontransitionend, "ontransitionend") GK_ATOM(ontransitionrun, "ontransitionrun") GK_ATOM(ontransitionstart, "ontransitionstart") diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 3d5c44a78..4ffccde9d 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -2521,8 +2521,13 @@ nsGlobalWindow::WouldReuseInnerWindow(nsIDocument* aNewDocument) return false; } - NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()), - "How'd this happen?"); +#ifdef DEBUG +{ + nsCOMPtr<nsIURI> uri; + mDoc->GetDocumentURI()->CloneIgnoringRef(getter_AddRefs(uri)); + NS_ASSERTION(NS_IsAboutBlank(uri), "How'd this happen?"); +} +#endif // Great, we're the original document, check for one of the other // conditions. @@ -3124,8 +3129,7 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, newInnerWindow->mPerformance = Performance::CreateForMainThread(newInnerWindow->AsInner(), currentInner->mPerformance->GetDOMTiming(), - currentInner->mPerformance->GetChannel(), - currentInner->mPerformance->GetParentPerformance()); + currentInner->mPerformance->GetChannel()); } } @@ -4339,22 +4343,7 @@ nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded() timedChannel = nullptr; } if (timing) { - // If we are dealing with an iframe, we will need the parent's performance - // object (so we can add the iframe as a resource of that page). - Performance* parentPerformance = nullptr; - nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull(); - if (parentWindow) { - nsPIDOMWindowInner* parentInnerWindow = nullptr; - if (parentWindow) { - parentInnerWindow = parentWindow->GetCurrentInnerWindow(); - } - if (parentInnerWindow) { - parentPerformance = parentInnerWindow->GetPerformance(); - } - } - mPerformance = - Performance::CreateForMainThread(this, timing, timedChannel, - parentPerformance); + mPerformance = Performance::CreateForMainThread(this, timing, timedChannel); } } diff --git a/dom/base/nsISelectionPrivate.idl b/dom/base/nsISelectionPrivate.idl index 68412885e..049873b28 100644 --- a/dom/base/nsISelectionPrivate.idl +++ b/dom/base/nsISelectionPrivate.idl @@ -29,7 +29,7 @@ native nsDirection(nsDirection); native ScrollAxis(nsIPresShell::ScrollAxis); [scriptable, builtinclass, uuid(0c9f4f74-ee7e-4fe9-be6b-0ba856368178)] -interface nsISelectionPrivate : nsISelection +interface nsISelectionPrivate : nsISupports { const short ENDOFPRECEDINGLINE=0; const short STARTOFNEXTLINE=1; diff --git a/dom/base/test/file_simplecontentpolicy.js b/dom/base/test/file_simplecontentpolicy.js index 1f9606c49..2727b9530 100644 --- a/dom/base/test/file_simplecontentpolicy.js +++ b/dom/base/test/file_simplecontentpolicy.js @@ -39,7 +39,6 @@ var policy = { { // Remember last content type seen for the test url if (contentLocation.spec.endsWith(urlSuffix)) { - assert.ok(frame === browserElement, "correct <browser> element"); sendAsyncMessage("shouldLoad", {contentType: contentType, isTopLevel: isTopLevel}); return Ci.nsIContentPolicy.REJECT_REQUEST; } diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 33f5f7a44..7056658a7 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -259,8 +259,8 @@ TErrorResult<CleanupPolicy>::ThrowJSException(JSContext* cx, JS::Handle<JS::Valu // Make sure mJSException is initialized _before_ we try to root it. But // don't set it to exn yet, because we don't want to do that until after we // root. - mJSException.setUndefined(); - if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::mJSException")) { + mJSException.asValueRef().setUndefined(); + if (!js::AddRawValueRoot(cx, &mJSException.asValueRef(), "TErrorResult::mJSException")) { // Don't use NS_ERROR_DOM_JS_EXCEPTION, because that indicates we have // in fact rooted mJSException. mResult = NS_ERROR_OUT_OF_MEMORY; @@ -289,7 +289,7 @@ TErrorResult<CleanupPolicy>::SetPendingJSException(JSContext* cx) mJSException = exception; // If JS_WrapValue failed, not much we can do about it... No matter // what, go ahead and unroot mJSException. - js::RemoveRawValueRoot(cx, &mJSException); + js::RemoveRawValueRoot(cx, &mJSException.asValueRef()); mResult = NS_OK; #ifdef DEBUG @@ -395,8 +395,8 @@ TErrorResult<CleanupPolicy>::ClearUnionData() if (IsJSException()) { JSContext* cx = dom::danger::GetJSContext(); MOZ_ASSERT(cx); - mJSException.setUndefined(); - js::RemoveRawValueRoot(cx, &mJSException); + mJSException.asValueRef().setUndefined(); + js::RemoveRawValueRoot(cx, &mJSException.asValueRef()); #ifdef DEBUG mUnionState = HasNothing; #endif // DEBUG @@ -439,13 +439,13 @@ TErrorResult<CleanupPolicy>::operator=(TErrorResult<CleanupPolicy>&& aRHS) } else if (aRHS.IsJSException()) { JSContext* cx = dom::danger::GetJSContext(); MOZ_ASSERT(cx); - mJSException.setUndefined(); - if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::mJSException")) { + mJSException.asValueRef().setUndefined(); + if (!js::AddRawValueRoot(cx, &mJSException.asValueRef(), "TErrorResult::mJSException")) { MOZ_CRASH("Could not root mJSException, we're about to OOM"); } mJSException = aRHS.mJSException; - aRHS.mJSException.setUndefined(); - js::RemoveRawValueRoot(cx, &aRHS.mJSException); + aRHS.mJSException.asValueRef().setUndefined(); + js::RemoveRawValueRoot(cx, &aRHS.mJSException.asValueRef()); } else if (aRHS.IsDOMException()) { mDOMExceptionInfo = aRHS.mDOMExceptionInfo; aRHS.mDOMExceptionInfo = nullptr; @@ -497,7 +497,7 @@ TErrorResult<CleanupPolicy>::CloneTo(TErrorResult& aRv) const aRv.mUnionState = HasJSException; #endif JSContext* cx = dom::danger::GetJSContext(); - JS::Rooted<JS::Value> exception(cx, mJSException); + JS::Rooted<JS::Value> exception(cx, mJSException.asValueRef()); aRv.ThrowJSException(cx, exception); } } diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 3174c37dd..7a6668687 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -1069,6 +1069,20 @@ class CGHeaders(CGWrapper): if parent: ancestors.append(parent) interfaceDeps.extend(ancestors) + + # Include parent interface headers needed for jsonifier code. + jsonInterfaceParents = [] + for desc in descriptors: + if not desc.operations['Jsonifier']: + continue + parent = desc.interface.parent + while parent: + parentDesc = desc.getDescriptor(parent.identifier.name) + if parentDesc.operations['Jsonifier']: + jsonInterfaceParents.append(parentDesc.interface) + parent = parent.parent + interfaceDeps.extend(jsonInterfaceParents) + bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps) # Grab all the implementation declaration files we need. diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h index c45e7ea3b..7c3fc9e2f 100644 --- a/dom/bindings/ErrorResult.h +++ b/dom/bindings/ErrorResult.h @@ -461,7 +461,7 @@ private: // (and deallocated) by SetPendingDOMException. union { Message* mMessage; // valid when IsErrorWithMessage() - JS::Value mJSException; // valid when IsJSException() + JS::UninitializedValue mJSException; // valid when IsJSException() DOMExceptionInfo* mDOMExceptionInfo; // valid when IsDOMException() }; diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp index 79e3eadc5..ff5a92167 100755 --- a/dom/console/Console.cpp +++ b/dom/console/Console.cpp @@ -1336,10 +1336,7 @@ Console::MethodInternal(JSContext* aCx, MethodName aMethodName, WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); - TimeDuration duration = - mozilla::TimeStamp::Now() - workerPrivate->NowBaseTimeStamp(); - - monotonicTimer = TimerClamping::ReduceMsTimeValue(duration.ToMilliseconds()); + monotonicTimer = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now()); } } diff --git a/dom/console/Console.h b/dom/console/Console.h index b334d79f9..2f375c8eb 100644 --- a/dom/console/Console.h +++ b/dom/console/Console.h @@ -258,9 +258,8 @@ private: // the max number of timers is reached. // * aCx - the JSContext rooting aName. // * aName - this is (should be) the name of the timer as JS::Value. - // * aTimestamp - the monotonicTimer for this context (taken from - // window->performance.now() or from Now() - - // workerPrivate->NowBaseTimeStamp() in workers. + // * aTimestamp - the monotonicTimer for this context taken from + // performance.now(). // * aTimerLabel - This label will be populated with the aName converted to a // string. // * aTimerValue - the StartTimer value stored into (or taken from) @@ -290,9 +289,8 @@ private: // the aName timer doesn't exist in the mTimerRegistry. // * aCx - the JSContext rooting aName. // * aName - this is (should be) the name of the timer as JS::Value. - // * aTimestamp - the monotonicTimer for this context (taken from - // window->performance.now() or from Now() - - // workerPrivate->NowBaseTimeStamp() in workers. + // * aTimestamp - the monotonicTimer for this context taken from + // performance.now(). // * aTimerLabel - This label will be populated with the aName converted to a // string. // * aTimerDuration - the difference between aTimestamp and when the timer diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index 7e19cd74d..4b9776c0a 100755 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -1146,16 +1146,11 @@ Event::TimeStampImpl() const return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->mTimeStamp); } - // For dedicated workers, we should make times relative to the navigation - // start of the document that created the worker, which is the same as the - // timebase for performance.now(). workers::WorkerPrivate* workerPrivate = workers::GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); - TimeDuration duration = - mEvent->mTimeStamp - workerPrivate->NowBaseTimeStamp(); - return duration.ToMilliseconds(); + return workerPrivate->TimeStampToDOMHighRes(mEvent->mTimeStamp); } bool diff --git a/dom/events/EventNameList.h b/dom/events/EventNameList.h index ba2427623..509863e6c 100644 --- a/dom/events/EventNameList.h +++ b/dom/events/EventNameList.h @@ -164,6 +164,10 @@ EVENT(change, eFormChange, EventNameType_HTMLXUL, eBasicEventClass) +EVENT(auxclick, + eMouseAuxClick, + EventNameType_All, + eMouseEventClass) EVENT(click, eMouseClick, EventNameType_All, @@ -1003,6 +1007,10 @@ EVENT(transitionend, eTransitionEnd, EventNameType_All, eTransitionEventClass) +EVENT(transitioncancel, + eTransitionCancel, + EventNameType_All, + eTransitionEventClass) EVENT(animationstart, eAnimationStart, EventNameType_All, @@ -1015,6 +1023,10 @@ EVENT(animationiteration, eAnimationIteration, EventNameType_All, eAnimationEventClass) +EVENT(animationcancel, + eAnimationCancel, + EventNameType_All, + eAnimationEventClass) // Webkit-prefixed versions of Transition & Animation events, for web compat: EVENT(webkitAnimationEnd, diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index c6b304183..7bbfe21b7 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -492,6 +492,7 @@ IsMessageMouseUserActivity(EventMessage aMessage) return aMessage == eMouseMove || aMessage == eMouseUp || aMessage == eMouseDown || + aMessage == eMouseAuxClick || aMessage == eMouseDoubleClick || aMessage == eMouseClick || aMessage == eMouseActivate || @@ -4633,6 +4634,32 @@ EventStateManager::SetClickCount(WidgetMouseEvent* aEvent, } nsresult +EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aEvent, + nsEventStatus* aStatus, + EventMessage aMessage, + nsIPresShell* aPresShell, + nsIContent* aMouseTarget, + nsWeakFrame aCurrentTarget, + bool aNoContentDispatch) +{ + WidgetMouseEvent event(aEvent->IsTrusted(), aMessage, + aEvent->mWidget, WidgetMouseEvent::eReal); + + event.mRefPoint = aEvent->mRefPoint; + event.mClickCount = aEvent->mClickCount; + event.mModifiers = aEvent->mModifiers; + event.buttons = aEvent->buttons; + event.mTime = aEvent->mTime; + event.mTimeStamp = aEvent->mTimeStamp; + event.mFlags.mNoContentDispatch = aNoContentDispatch; + event.button = aEvent->button; + event.inputSource = aEvent->inputSource; + + return aPresShell->HandleEventWithTarget(&event, aCurrentTarget, + aMouseTarget, aStatus); +} + +nsresult EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent, nsEventStatus* aStatus) { @@ -4651,17 +4678,7 @@ EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent, (aEvent->button == WidgetMouseEvent::eMiddleButton || aEvent->button == WidgetMouseEvent::eRightButton); - WidgetMouseEvent event(aEvent->IsTrusted(), eMouseClick, - aEvent->mWidget, WidgetMouseEvent::eReal); - event.mRefPoint = aEvent->mRefPoint; - event.mClickCount = aEvent->mClickCount; - event.mModifiers = aEvent->mModifiers; - event.buttons = aEvent->buttons; - event.mTime = aEvent->mTime; - event.mTimeStamp = aEvent->mTimeStamp; - event.mFlags.mNoContentDispatch = notDispatchToContents; - event.button = aEvent->button; - event.inputSource = aEvent->inputSource; + bool fireAuxClick = notDispatchToContents; nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell(); if (presShell) { @@ -4680,23 +4697,22 @@ EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent, // HandleEvent clears out mCurrentTarget which we might need again nsWeakFrame currentTarget = mCurrentTarget; - ret = presShell->HandleEventWithTarget(&event, currentTarget, - mouseContent, aStatus); + ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseClick, + presShell, mouseContent, currentTarget, + notDispatchToContents); + if (NS_SUCCEEDED(ret) && aEvent->mClickCount == 2 && mouseContent && mouseContent->IsInComposedDoc()) { //fire double click - WidgetMouseEvent event2(aEvent->IsTrusted(), eMouseDoubleClick, - aEvent->mWidget, WidgetMouseEvent::eReal); - event2.mRefPoint = aEvent->mRefPoint; - event2.mClickCount = aEvent->mClickCount; - event2.mModifiers = aEvent->mModifiers; - event2.buttons = aEvent->buttons; - event2.mFlags.mNoContentDispatch = notDispatchToContents; - event2.button = aEvent->button; - event2.inputSource = aEvent->inputSource; - - ret = presShell->HandleEventWithTarget(&event2, currentTarget, - mouseContent, aStatus); + ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseDoubleClick, + presShell, mouseContent, currentTarget, + notDispatchToContents); + } + if (NS_SUCCEEDED(ret) && mouseContent && fireAuxClick && + mouseContent->IsInComposedDoc()) { + ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseAuxClick, + presShell, mouseContent, currentTarget, + false); } } } diff --git a/dom/events/EventStateManager.h b/dom/events/EventStateManager.h index 49ecf0586..d0461e7fa 100644 --- a/dom/events/EventStateManager.h +++ b/dom/events/EventStateManager.h @@ -415,6 +415,13 @@ protected: */ void UpdateDragDataTransfer(WidgetDragEvent* dragEvent); + static nsresult InitAndDispatchClickEvent(WidgetMouseEvent* aEvent, + nsEventStatus* aStatus, + EventMessage aMessage, + nsIPresShell* aPresShell, + nsIContent* aMouseTarget, + nsWeakFrame aCurrentTarget, + bool aNoContentDispatch); nsresult SetClickCount(WidgetMouseEvent* aEvent, nsEventStatus* aStatus); nsresult CheckForAndDispatchClick(WidgetMouseEvent* aEvent, nsEventStatus* aStatus); @@ -1046,6 +1053,7 @@ private: #define NS_EVENT_NEEDS_FRAME(event) \ (!(event)->HasPluginActivationEventMessage() && \ (event)->mMessage != eMouseClick && \ - (event)->mMessage != eMouseDoubleClick) + (event)->mMessage != eMouseDoubleClick && \ + (event)->mMessage != eMouseAuxClick) #endif // mozilla_EventStateManager_h_ diff --git a/dom/events/WheelHandlingHelper.cpp b/dom/events/WheelHandlingHelper.cpp index 7665ee922..81f2b6bfa 100644 --- a/dom/events/WheelHandlingHelper.cpp +++ b/dom/events/WheelHandlingHelper.cpp @@ -257,6 +257,7 @@ WheelTransaction::OnEvent(WidgetEvent* aEvent) case eMouseUp: case eMouseDown: case eMouseDoubleClick: + case eMouseAuxClick: case eMouseClick: case eContextMenu: case eDrop: diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini index e100e60a1..0397487bb 100644 --- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -184,3 +184,4 @@ skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM [test_wheel_default_action.html] [test_bug687787.html] [test_bug1298970.html] +[test_bug1304044.html] diff --git a/dom/events/test/test_bug1304044.html b/dom/events/test/test_bug1304044.html new file mode 100644 index 000000000..0911dcf73 --- /dev/null +++ b/dom/events/test/test_bug1304044.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1304044 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1304044</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + var eventsFired = []; + var target; + var eventsExpected; + + function GetNodeString(node) { + if (node == window) + return "window"; + if (node == document) + return "document"; + if (node.id) + return node.id; + if (node.nodeName) + return node.nodeName; + return node; + } + + function TargetAndListener(listener, target) { + this.listener = listener; + this.target = target; + } + + TargetAndListener.prototype.toString = function() { + var targetName = GetNodeString(this.target); + var listenerName = GetNodeString(this.listener); + return "(listener: " + listenerName + ", target: " + targetName + ")"; + } + + var tests = [ + TestAuxClickBubblesForEventListener, + TestAuxClickBubblesForOnAuxClick, + ]; + + function CompareEvents(evt, expected) { + return evt && expected && evt.listener == expected.listener && + evt.target == expected.target; + } + + function ResetEventsFired() { + eventsFired = []; + } + + function ClearEventListeners() { + for (i in arguments) { + arguments[i].removeEventListener("auxclick", log_event); + } + } + + function ClickTarget(tgt) { + synthesizeMouseAtCenter(tgt, {type : "mousedown", button: 2}, window); + synthesizeMouseAtCenter(tgt, {type : "mouseup", button: 2}, window); + } + + function log_event(e) { + eventsFired[eventsFired.length] = new TargetAndListener(this, e.target); + } + + function CompareEventsToExpected(expected, actual) { + for (var i = 0; i < expected.length || i < actual.length; i++) { + ok(CompareEvents(actual[i], expected[i]), + "Auxclick receiver's don't match: TargetAndListener " + + i + ": Expected: " + expected[i] + ", Actual: " + actual[i]); + } + } + + function TestAuxClickBubblesForEventListener() { + target.addEventListener("auxclick", log_event); + document.addEventListener("auxclick", log_event); + window.addEventListener("auxclick", log_event); + + ClickTarget(target) + CompareEventsToExpected(eventsExpected, eventsFired); + ResetEventsFired(); + ClearEventListeners(target, document, window); + } + + function TestAuxClickBubblesForOnAuxClick() { + target.onauxclick = log_event; + document.onauxclick = log_event; + window.onauxclick = log_event; + + ClickTarget(target); + CompareEventsToExpected(eventsExpected, eventsFired); + ResetEventsFired(); + } + + function RunTests(){ + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + } + + function Begin() { + target = document.getElementById("target"); + eventsExpected = [ + new TargetAndListener(target, target), + new TargetAndListener(document, target), + new TargetAndListener(window, target), + ]; + RunTests(); + target.remove(); + SimpleTest.finish(); + } + + window.onload = function() { + SimpleTest.waitForExplicitFinish(); + SimpleTest.executeSoon(Begin); + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1304044">Mozilla Bug 1304044</a> +<p id="display"> + <div id="target">Target</div> +</p> +<div id="content" style:"display:none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/events/test/test_eventTimeStamp.html b/dom/events/test/test_eventTimeStamp.html index 107a21f87..056203e92 100644 --- a/dom/events/test/test_eventTimeStamp.html +++ b/dom/events/test/test_eventTimeStamp.html @@ -17,7 +17,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=77992 <script type="text/js-worker" id="worker-src"> // Simply returns the event timestamp onmessage = function(evt) { - postMessage(evt.timeStamp); + postMessage(evt.timeStamp + performance.timeOrigin); } </script> <script type="text/js-worker" id="shared-worker-src"> @@ -25,7 +25,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=77992 onconnect = function(evt) { var port = evt.ports[0]; port.onmessage = function(messageEvt) { - port.postMessage(messageEvt.timeStamp); + port.postMessage(messageEvt.timeStamp + performance.timeOrigin); }; }; </script> @@ -57,9 +57,9 @@ function testRegularEvents() { finishTests(); return; } - var timeBeforeEvent = window.performance.now(); - window.addEventListener("load", function(evt) { - var timeAfterEvent = window.performance.now(); + var timeBeforeEvent = performance.now(); + addEventListener("load", function(evt) { + var timeAfterEvent = performance.now(); ok(evt.timeStamp >= timeBeforeEvent && evt.timeStamp <= timeAfterEvent, "Event timestamp (" + evt.timeStamp + ") is in expected range: (" + @@ -71,19 +71,18 @@ function testRegularEvents() { function testWorkerEvents() { var blob = new Blob([ document.getElementById("worker-src").textContent ], { type: "text/javascript" }); - var worker = new Worker(window.URL.createObjectURL(blob)); + var worker = new Worker(URL.createObjectURL(blob)); worker.onmessage = function(evt) { - var timeAfterEvent = window.performance.now(); - // Comparing times across timelines may break now - // ok(evt.data >= timeBeforeEvent && - // evt.data <= timeAfterEvent, + var timeAfterEvent = performance.now() + performance.timeOrigin; + ok(evt.data >= timeBeforeEvent && + evt.data <= timeAfterEvent, // "Event timestamp in dedicated worker (" + evt.data + // ") is in expected range: (" + // timeBeforeEvent + ", " + timeAfterEvent + ")"); worker.terminate(); testSharedWorkerEvents(); }; - var timeBeforeEvent = window.performance.now(); + var timeBeforeEvent = performance.now() + performance.timeOrigin; worker.postMessage(""); } @@ -93,17 +92,16 @@ function testSharedWorkerEvents() { { type: "text/javascript" }); // Delay creation of worker slightly so it is easier to distinguish // shared worker creation time from this document's navigation start - window.setTimeout(function() { - var timeBeforeWorkerCreation = window.performance.now(); - var worker = new SharedWorker(window.URL.createObjectURL(blob)); + setTimeout(function() { + var timeBeforeEvent = performance.now() + performance.timeOrigin; + var worker = new SharedWorker(URL.createObjectURL(blob)); worker.port.onmessage = function(evt) { - var timeAfterEvent = window.performance.now(); - // Comparing times across timelines may break now - // ok(evt.data >= 0 && - // evt.data <= timeAfterEvent - timeBeforeWorkerCreation, + var timeAfterEvent = performance.now() + performance.timeOrigin; + ok(evt.data >= timeBeforeEvent && + evt.data <= timeAfterEvent, // "Event timestamp in shared worker (" + evt.data + // ") is in expected range: (0, " + - // (timeAfterEvent - timeBeforeWorkerCreation) + ")"); + // timeBeforeEvent + ", " + timeAfterEvent + ")"); worker.port.close(); finishTests(); }; diff --git a/dom/events/test/test_legacy_event.html b/dom/events/test/test_legacy_event.html index d772be106..b2105a6df 100644 --- a/dom/events/test/test_legacy_event.html +++ b/dom/events/test/test_legacy_event.html @@ -73,22 +73,15 @@ function triggerShortAnimation(node) { node.style.animation = "anim1 1ms linear"; } -// This function triggers a long animation with two iterations, which is -// *nearly* at the end of its first iteration. It will hit the end of that -// iteration (firing an event) almost immediately, 1ms in the future. +// This function triggers a very short (10ms long) animation with many +// iterations, which will cause a start event followed by an iteration event +// on each subsequent tick, to fire. // -// NOTE: It's important that this animation have a *long* duration. If it were -// short (e.g. 1ms duration), then we might jump past all its iterations in -// a single refresh-driver tick. And if that were to happens, we'd *never* fire -// any animationiteration events -- the CSS Animations spec says this event -// must not be fired "...when an animationend event would fire at the same time" -// (which would be the case in this example with a 1ms duration). So, to make -// sure our event does fire, we use a long duration and a nearly-as-long -// negative delay. This ensures we hit the end of the first iteration right -// away, and that we don't risk hitting the end of the second iteration at the -// same time. +// NOTE: We need the many iterations since if an animation frame coincides +// with the animation starting or ending we dispatch only the start or end +// event and not the iteration event. function triggerAnimationIteration(node) { - node.style.animation = "anim1 300s -299.999s linear 2"; + node.style.animation = "anim1 10ms linear 20000"; } // GENERAL UTILITY FUNCTIONS diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp index cdb63f890..90171db10 100644 --- a/dom/jsurl/nsJSProtocolHandler.cpp +++ b/dom/jsurl/nsJSProtocolHandler.cpp @@ -36,7 +36,6 @@ #include "nsIContentViewer.h" #include "nsIXPConnect.h" #include "nsContentUtils.h" -#include "nsNullPrincipal.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" #include "nsIScriptChannel.h" @@ -336,7 +335,7 @@ public: NS_FORWARD_SAFE_NSIPROPERTYBAG(mPropertyBag) NS_FORWARD_SAFE_NSIPROPERTYBAG2(mPropertyBag) - nsresult Init(nsIURI *aURI); + nsresult Init(nsIURI *aURI, nsILoadInfo* aLoadInfo); // Actually evaluate the script. void EvaluateScript(); @@ -354,17 +353,16 @@ protected: nsCOMPtr<nsIChannel> mStreamChannel; nsCOMPtr<nsIPropertyBag2> mPropertyBag; nsCOMPtr<nsIStreamListener> mListener; // Our final listener - nsCOMPtr<nsISupports> mContext; // The context passed to AsyncOpen nsCOMPtr<nsPIDOMWindowInner> mOriginalInnerWindow; // The inner window our load // started against. - // If we blocked onload on a document in AsyncOpen, this is the document we + // If we blocked onload on a document in AsyncOpen2, this is the document we // did it on. nsCOMPtr<nsIDocument> mDocumentOnloadBlockedOn; nsresult mStatus; // Our status nsLoadFlags mLoadFlags; - nsLoadFlags mActualLoadFlags; // See AsyncOpen + nsLoadFlags mActualLoadFlags; // See AsyncOpen2 RefPtr<nsJSThunk> mIOThunk; PopupControlState mPopupState; @@ -404,7 +402,7 @@ nsresult nsJSChannel::StopAll() return rv; } -nsresult nsJSChannel::Init(nsIURI *aURI) +nsresult nsJSChannel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) { RefPtr<nsJSURI> jsURI; nsresult rv = aURI->QueryInterface(kJSURICID, @@ -418,21 +416,13 @@ nsresult nsJSChannel::Init(nsIURI *aURI) // Remember, until AsyncOpen is called, the script will not be evaluated // and the underlying Input Stream will not be created... nsCOMPtr<nsIChannel> channel; - - nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create(); - - // If the resultant script evaluation actually does return a value, we - // treat it as html. - // The following channel is never openend, so it does not matter what - // securityFlags we pass; let's follow the principle of least privilege. - rv = NS_NewInputStreamChannel(getter_AddRefs(channel), - aURI, - mIOThunk, - nullPrincipal, - nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, - nsIContentPolicy::TYPE_OTHER, - NS_LITERAL_CSTRING("text/html")); - if (NS_FAILED(rv)) return rv; + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), + aURI, + mIOThunk, + NS_LITERAL_CSTRING("text/html"), + EmptyCString(), + aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); rv = mIOThunk->Init(aURI); if (NS_SUCCEEDED(rv)) { @@ -563,6 +553,7 @@ nsJSChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) "security flags in loadInfo but asyncOpen2() not called"); } #endif + MOZ_RELEASE_ASSERT(!aContext, "please call AsyncOpen2()"); NS_ENSURE_ARG(aListener); @@ -584,7 +575,6 @@ nsJSChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) } mListener = aListener; - mContext = aContext; mIsActive = true; @@ -655,7 +645,7 @@ nsJSChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) return mStatus; } - // We're returning success from asyncOpen(), but we didn't open a + // We're returning success from asyncOpen2(), but we didn't open a // stream channel. We'll have to notify ourselves, but make sure to do // it asynchronously. method = &nsJSChannel::NotifyListener; @@ -772,7 +762,7 @@ nsJSChannel::EvaluateScript() return; } - mStatus = mStreamChannel->AsyncOpen(this, mContext); + mStatus = mStreamChannel->AsyncOpen2(this); if (NS_SUCCEEDED(mStatus)) { // mStreamChannel will call OnStartRequest and OnStopRequest on // us, so we'll be sure to call them on our listener. @@ -800,8 +790,8 @@ nsJSChannel::EvaluateScript() void nsJSChannel::NotifyListener() { - mListener->OnStartRequest(this, mContext); - mListener->OnStopRequest(this, mContext, mStatus); + mListener->OnStartRequest(this, nullptr); + mListener->OnStopRequest(this, nullptr, mStatus); CleanupStrongRefs(); } @@ -810,7 +800,6 @@ void nsJSChannel::CleanupStrongRefs() { mListener = nullptr; - mContext = nullptr; mOriginalInnerWindow = nullptr; if (mDocumentOnloadBlockedOn) { mDocumentOnloadBlockedOn->UnblockOnload(false); @@ -1240,11 +1229,7 @@ nsJSProtocolHandler::NewChannel2(nsIURI* uri, return NS_ERROR_OUT_OF_MEMORY; } - rv = channel->Init(uri); - NS_ENSURE_SUCCESS(rv, rv); - - // set the loadInfo on the new channel - rv = channel->SetLoadInfo(aLoadInfo); + rv = channel->Init(uri, aLoadInfo); NS_ENSURE_SUCCESS(rv, rv); if (NS_SUCCEEDED(rv)) { diff --git a/dom/media/MediaStreamTrack.cpp b/dom/media/MediaStreamTrack.cpp index 8ccdeb90c..75cdeb1d1 100644 --- a/dom/media/MediaStreamTrack.cpp +++ b/dom/media/MediaStreamTrack.cpp @@ -165,11 +165,15 @@ MediaStreamTrack::Destroy() mPrincipalHandleListener->Forget(); mPrincipalHandleListener = nullptr; } - for (auto l : mTrackListeners) { - RemoveListener(l); + // Remove all listeners -- avoid iterating over the list we're removing from + const nsTArray<RefPtr<MediaStreamTrackListener>> trackListeners(mTrackListeners); + for (auto listener : trackListeners) { + RemoveListener(listener); } - for (auto l : mDirectTrackListeners) { - RemoveDirectListener(l); + // Do the same as above for direct listeners + const nsTArray<RefPtr<DirectMediaStreamTrackListener>> directTrackListeners(mDirectTrackListeners); + for (auto listener : directTrackListeners) { + RemoveDirectListener(listener); } } diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp index 8dc239b05..93a6b7313 100755 --- a/dom/performance/Performance.cpp +++ b/dom/performance/Performance.cpp @@ -13,12 +13,14 @@ #include "PerformanceMeasure.h" #include "PerformanceObserver.h" #include "PerformanceResourceTiming.h" +#include "PerformanceService.h" #include "PerformanceWorker.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceEntryEvent.h" #include "mozilla/dom/PerformanceNavigationBinding.h" #include "mozilla/dom/PerformanceObserverBinding.h" +#include "mozilla/dom/PerformanceNavigationTiming.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Preferences.h" #include "mozilla/TimerClamping.h" @@ -38,27 +40,6 @@ using namespace workers; namespace { -// Helper classes -class MOZ_STACK_CLASS PerformanceEntryComparator final -{ -public: - bool Equals(const PerformanceEntry* aElem1, - const PerformanceEntry* aElem2) const - { - MOZ_ASSERT(aElem1 && aElem2, - "Trying to compare null performance entries"); - return aElem1->StartTime() == aElem2->StartTime(); - } - - bool LessThan(const PerformanceEntry* aElem1, - const PerformanceEntry* aElem2) const - { - MOZ_ASSERT(aElem1 && aElem2, - "Trying to compare null performance entries"); - return aElem1->StartTime() < aElem2->StartTime(); - } -}; - class PrefEnabledRunnable final : public WorkerCheckAPIExposureOnMainThreadRunnable { @@ -103,14 +84,12 @@ NS_IMPL_RELEASE_INHERITED(Performance, DOMEventTargetHelper) /* static */ already_AddRefed<Performance> Performance::CreateForMainThread(nsPIDOMWindowInner* aWindow, nsDOMNavigationTiming* aDOMTiming, - nsITimedChannel* aChannel, - Performance* aParentPerformance) + nsITimedChannel* aChannel) { MOZ_ASSERT(NS_IsMainThread()); RefPtr<Performance> performance = - new PerformanceMainThread(aWindow, aDOMTiming, aChannel, - aParentPerformance); + new PerformanceMainThread(aWindow, aDOMTiming, aChannel); return performance.forget(); } @@ -142,6 +121,24 @@ Performance::Performance(nsPIDOMWindowInner* aWindow) Performance::~Performance() {} +DOMHighResTimeStamp +Performance::Now() const +{ + TimeDuration duration = TimeStamp::Now() - CreationTimeStamp(); + return RoundTime(duration.ToMilliseconds()); +} + +DOMHighResTimeStamp +Performance::TimeOrigin() +{ + if (!mPerformanceService) { + mPerformanceService = PerformanceService::GetOrCreate(); + } + + MOZ_ASSERT(mPerformanceService); + return mPerformanceService->TimeOrigin(CreationTimeStamp()); +} + JSObject* Performance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { @@ -266,7 +263,7 @@ Performance::ClearMarks(const Optional<nsAString>& aName) DOMHighResTimeStamp Performance::ResolveTimestampFromName(const nsAString& aName, - ErrorResult& aRv) + ErrorResult& aRv) { AutoTArray<RefPtr<PerformanceEntry>, 1> arr; DOMHighResTimeStamp ts; diff --git a/dom/performance/Performance.h b/dom/performance/Performance.h index bc70589a5..4debecc90 100644 --- a/dom/performance/Performance.h +++ b/dom/performance/Performance.h @@ -24,6 +24,7 @@ namespace dom { class PerformanceEntry; class PerformanceNavigation; class PerformanceObserver; +class PerformanceService; class PerformanceTiming; namespace workers { @@ -45,8 +46,7 @@ public: static already_AddRefed<Performance> CreateForMainThread(nsPIDOMWindowInner* aWindow, nsDOMNavigationTiming* aDOMTiming, - nsITimedChannel* aChannel, - Performance* aParentPerformance); + nsITimedChannel* aChannel); static already_AddRefed<Performance> CreateForWorker(workers::WorkerPrivate* aWorkerPrivate); @@ -54,21 +54,23 @@ public: JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override; - void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval); + virtual void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval); - void GetEntriesByType(const nsAString& aEntryType, - nsTArray<RefPtr<PerformanceEntry>>& aRetval); + virtual void GetEntriesByType(const nsAString& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval); - void GetEntriesByName(const nsAString& aName, - const Optional<nsAString>& aEntryType, - nsTArray<RefPtr<PerformanceEntry>>& aRetval); + virtual void GetEntriesByName(const nsAString& aName, + const Optional<nsAString>& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval); virtual void AddEntry(nsIHttpChannel* channel, nsITimedChannel* timedChannel) = 0; void ClearResourceTimings(); - virtual DOMHighResTimeStamp Now() const = 0; + DOMHighResTimeStamp Now() const; + + DOMHighResTimeStamp TimeOrigin(); void Mark(const nsAString& aName, ErrorResult& aRv); @@ -101,8 +103,6 @@ public: virtual nsITimedChannel* GetChannel() const = 0; - virtual Performance* GetParentPerformance() const = 0; - protected: Performance(); explicit Performance(nsPIDOMWindowInner* aWindow); @@ -126,10 +126,16 @@ protected: virtual DOMHighResTimeStamp CreationTime() const = 0; - virtual bool IsPerformanceTimingAttribute(const nsAString& aName) = 0; + virtual bool IsPerformanceTimingAttribute(const nsAString& aName) + { + return false; + } virtual DOMHighResTimeStamp - GetPerformanceTimingFromString(const nsAString& aTimingName) = 0; + GetPerformanceTimingFromString(const nsAString& aTimingName) + { + return 0; + } bool IsResourceEntryLimitReached() const { @@ -147,13 +153,15 @@ protected: nsTObserverArray<PerformanceObserver*> mObservers; -private: +protected: nsTArray<RefPtr<PerformanceEntry>> mUserEntries; nsTArray<RefPtr<PerformanceEntry>> mResourceEntries; uint64_t mResourceTimingBufferSize; static const uint64_t kDefaultResourceTimingBufferSize = 150; bool mPendingNotificationObserversTask; + + RefPtr<PerformanceService> mPerformanceService; }; } // namespace dom diff --git a/dom/performance/PerformanceEntry.h b/dom/performance/PerformanceEntry.h index bc4f84f1c..0af9f669e 100644 --- a/dom/performance/PerformanceEntry.h +++ b/dom/performance/PerformanceEntry.h @@ -90,6 +90,27 @@ protected: nsString mEntryType; }; +// Helper classes +class MOZ_STACK_CLASS PerformanceEntryComparator final +{ +public: + bool Equals(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const + { + MOZ_ASSERT(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() == aElem2->StartTime(); + } + + bool LessThan(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const + { + MOZ_ASSERT(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() < aElem2->StartTime(); + } +}; + } // namespace dom } // namespace mozilla diff --git a/dom/performance/PerformanceMainThread.cpp b/dom/performance/PerformanceMainThread.cpp index 86d42c5f8..64c06d3ea 100644 --- a/dom/performance/PerformanceMainThread.cpp +++ b/dom/performance/PerformanceMainThread.cpp @@ -6,6 +6,7 @@ #include "PerformanceMainThread.h" #include "PerformanceNavigation.h" +#include "nsICacheInfoChannel.h" namespace mozilla { namespace dom { @@ -16,7 +17,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread, Performance) NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming, mNavigation, - mParentPerformance) + mDocEntry) tmp->mMozMemory = nullptr; mozilla::DropJSObjects(this); NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -25,7 +26,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread, Performance) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTiming, mNavigation, - mParentPerformance) + mDocEntry) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -45,12 +46,10 @@ NS_INTERFACE_MAP_END_INHERITING(Performance) PerformanceMainThread::PerformanceMainThread(nsPIDOMWindowInner* aWindow, nsDOMNavigationTiming* aDOMTiming, - nsITimedChannel* aChannel, - Performance* aParentPerformance) + nsITimedChannel* aChannel) : Performance(aWindow) , mDOMTiming(aDOMTiming) , mChannel(aChannel) - , mParentPerformance(aParentPerformance) { MOZ_ASSERT(aWindow, "Parent window object should be provided"); } @@ -78,7 +77,7 @@ PerformanceTiming* PerformanceMainThread::Timing() { if (!mTiming) { - // For navigation timing, the third argument (an nsIHtttpChannel) is null + // For navigation timing, the third argument (an nsIHttpChannel) is null // since the cross-domain redirect were already checked. The last argument // (zero time) for performance.timing is the navigation start value. mTiming = new PerformanceTiming(this, mChannel, nullptr, @@ -108,12 +107,6 @@ PerformanceMainThread::Navigation() return mNavigation; } -DOMHighResTimeStamp -PerformanceMainThread::Now() const -{ - return RoundTime(GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now())); -} - /** * An entry should be added only after the resource is loaded. * This method is not thread safe and can only be called on the main thread. @@ -161,27 +154,7 @@ PerformanceMainThread::AddEntry(nsIHttpChannel* channel, // The PerformanceResourceTiming object will use the PerformanceTiming // object to get all the required timings. RefPtr<PerformanceResourceTiming> performanceEntry = - new PerformanceResourceTiming(performanceTiming, this, entryName); - - nsAutoCString protocol; - channel->GetProtocolVersion(protocol); - performanceEntry->SetNextHopProtocol(NS_ConvertUTF8toUTF16(protocol)); - - uint64_t encodedBodySize = 0; - channel->GetEncodedBodySize(&encodedBodySize); - performanceEntry->SetEncodedBodySize(encodedBodySize); - - uint64_t transferSize = 0; - channel->GetTransferSize(&transferSize); - performanceEntry->SetTransferSize(transferSize); - - uint64_t decodedBodySize = 0; - channel->GetDecodedBodySize(&decodedBodySize); - if (decodedBodySize == 0) { - decodedBodySize = encodedBodySize; - } - performanceEntry->SetDecodedBodySize(decodedBodySize); - + new PerformanceResourceTiming(performanceTiming, this, entryName, channel); // If the initiator type had no valid value, then set it to the default // ("other") value. if (initiatorType.IsEmpty()) { @@ -335,5 +308,65 @@ PerformanceMainThread::CreationTime() const return GetDOMTiming()->GetNavigationStart(); } +void +PerformanceMainThread::EnsureDocEntry() +{ + if (!mDocEntry && nsContentUtils::IsPerformanceNavigationTimingEnabled()) { + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); + RefPtr<PerformanceTiming> timing = + new PerformanceTiming(this, mChannel, nullptr, 0); + mDocEntry = new PerformanceNavigationTiming(timing, this, + httpChannel); + } +} + + +void +PerformanceMainThread::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) +{ + aRetval = mResourceEntries; + aRetval.AppendElements(mUserEntries); + + EnsureDocEntry(); + if (mDocEntry) { + aRetval.AppendElement(mDocEntry); + } + + aRetval.Sort(PerformanceEntryComparator()); +} + +void +PerformanceMainThread::GetEntriesByType(const nsAString& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval) +{ + if (aEntryType.EqualsLiteral("navigation")) { + aRetval.Clear(); + EnsureDocEntry(); + if (mDocEntry) { + aRetval.AppendElement(mDocEntry); + } + return; + } + + Performance::GetEntriesByType(aEntryType, aRetval); +} + +void +PerformanceMainThread::GetEntriesByName(const nsAString& aName, + const Optional<nsAString>& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval) +{ + if (aName.EqualsLiteral("document")) { + aRetval.Clear(); + EnsureDocEntry(); + if (mDocEntry) { + aRetval.AppendElement(mDocEntry); + } + return; + } + + Performance::GetEntriesByName(aName, aEntryType, aRetval); +} + } // dom namespace } // mozilla namespace diff --git a/dom/performance/PerformanceMainThread.h b/dom/performance/PerformanceMainThread.h index 84773f29b..9f0e185fc 100644 --- a/dom/performance/PerformanceMainThread.h +++ b/dom/performance/PerformanceMainThread.h @@ -17,16 +17,12 @@ class PerformanceMainThread final : public Performance public: PerformanceMainThread(nsPIDOMWindowInner* aWindow, nsDOMNavigationTiming* aDOMTiming, - nsITimedChannel* aChannel, - Performance* aParentPerformance); + nsITimedChannel* aChannel); NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PerformanceMainThread, Performance) - // Performance WebIDL methods - DOMHighResTimeStamp Now() const override; - virtual PerformanceTiming* Timing() override; virtual PerformanceNavigation* Navigation() override; @@ -51,10 +47,14 @@ public: return mChannel; } - virtual Performance* GetParentPerformance() const override - { - return mParentPerformance; - } + // The GetEntries* methods need to be overriden in order to add the + // the document entry of type navigation. + virtual void GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval) override; + virtual void GetEntriesByType(const nsAString& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval) override; + virtual void GetEntriesByName(const nsAString& aName, + const Optional<nsAString>& aEntryType, + nsTArray<RefPtr<PerformanceEntry>>& aRetval) override; protected: ~PerformanceMainThread(); @@ -72,12 +72,13 @@ protected: GetPerformanceTimingFromString(const nsAString& aTimingName) override; void DispatchBufferFullEvent() override; + void EnsureDocEntry(); + RefPtr<PerformanceEntry> mDocEntry; RefPtr<nsDOMNavigationTiming> mDOMTiming; nsCOMPtr<nsITimedChannel> mChannel; RefPtr<PerformanceTiming> mTiming; RefPtr<PerformanceNavigation> mNavigation; - RefPtr<Performance> mParentPerformance; JS::Heap<JSObject*> mMozMemory; }; diff --git a/dom/performance/PerformanceNavigationTiming.cpp b/dom/performance/PerformanceNavigationTiming.cpp new file mode 100644 index 000000000..4e00b2bb2 --- /dev/null +++ b/dom/performance/PerformanceNavigationTiming.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/dom/PerformanceNavigationTiming.h" +#include "mozilla/dom/PerformanceNavigationTimingBinding.h" + +using namespace mozilla::dom; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceNavigationTiming) +NS_INTERFACE_MAP_END_INHERITING(PerformanceResourceTiming) + +NS_IMPL_ADDREF_INHERITED(PerformanceNavigationTiming, PerformanceResourceTiming) +NS_IMPL_RELEASE_INHERITED(PerformanceNavigationTiming, PerformanceResourceTiming) + +JSObject* +PerformanceNavigationTiming::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return PerformanceNavigationTimingBinding::Wrap(aCx, this, aGivenProto); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::UnloadEventStart() const +{ + return mTiming->GetDOMTiming()->GetUnloadEventStartHighRes(); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::UnloadEventEnd() const +{ + return mTiming->GetDOMTiming()->GetUnloadEventEndHighRes(); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::DomInteractive() const +{ + return mTiming->GetDOMTiming()->GetDomInteractiveHighRes(); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::DomContentLoadedEventStart() const +{ + return mTiming->GetDOMTiming()->GetDomContentLoadedEventStartHighRes(); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::DomContentLoadedEventEnd() const +{ + return mTiming->GetDOMTiming()->GetDomContentLoadedEventEndHighRes(); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::DomComplete() const +{ + return mTiming->GetDOMTiming()->GetDomCompleteHighRes(); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::LoadEventStart() const +{ + return mTiming->GetDOMTiming()->GetLoadEventStartHighRes(); +} + +DOMHighResTimeStamp +PerformanceNavigationTiming::LoadEventEnd() const +{ + return mTiming->GetDOMTiming()->GetLoadEventEndHighRes(); +} + +NavigationType +PerformanceNavigationTiming::Type() const +{ + switch(mTiming->GetDOMTiming()->GetType()) { + case nsDOMNavigationTiming::TYPE_NAVIGATE: + return NavigationType::Navigate; + break; + case nsDOMNavigationTiming::TYPE_RELOAD: + return NavigationType::Reload; + break; + case nsDOMNavigationTiming::TYPE_BACK_FORWARD: + return NavigationType::Back_forward; + break; + default: + // The type is TYPE_RESERVED or some other value that was later added. + // We fallback to the default of Navigate. + return NavigationType::Navigate; + } +} + +uint16_t +PerformanceNavigationTiming::RedirectCount() const +{ + return mTiming->GetRedirectCount(); +} diff --git a/dom/performance/PerformanceNavigationTiming.h b/dom/performance/PerformanceNavigationTiming.h new file mode 100644 index 000000000..8555f1987 --- /dev/null +++ b/dom/performance/PerformanceNavigationTiming.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_PerformanceNavigationTiming_h___ +#define mozilla_dom_PerformanceNavigationTiming_h___ + +#include "nsCOMPtr.h" +#include "nsIChannel.h" +#include "nsITimedChannel.h" +#include "mozilla/dom/PerformanceResourceTiming.h" +#include "mozilla/dom/PerformanceNavigationTimingBinding.h" +#include "nsIHttpChannel.h" + +namespace mozilla { +namespace dom { + +// https://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming +class PerformanceNavigationTiming final + : public PerformanceResourceTiming +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + // Note that aPerformanceTiming must be initalized with zeroTime = 0 + // so that timestamps are relative to startTime, as opposed to the + // performance.timing object for which timestamps are absolute and has a + // zeroTime initialized to navigationStart + explicit PerformanceNavigationTiming(PerformanceTiming* aPerformanceTiming, + Performance* aPerformance, + nsIHttpChannel* aChannel) + : PerformanceResourceTiming(aPerformanceTiming, aPerformance, + NS_LITERAL_STRING("document"), aChannel) { + SetEntryType(NS_LITERAL_STRING("navigation")); + SetInitiatorType(NS_LITERAL_STRING("navigation")); + } + + DOMHighResTimeStamp Duration() const override + { + return LoadEventEnd() - StartTime(); + } + + DOMHighResTimeStamp StartTime() const override + { + return 0; + } + + JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + DOMHighResTimeStamp UnloadEventStart() const; + DOMHighResTimeStamp UnloadEventEnd() const; + + DOMHighResTimeStamp DomInteractive() const; + DOMHighResTimeStamp DomContentLoadedEventStart() const; + DOMHighResTimeStamp DomContentLoadedEventEnd() const; + DOMHighResTimeStamp DomComplete() const; + DOMHighResTimeStamp LoadEventStart() const; + DOMHighResTimeStamp LoadEventEnd() const; + NavigationType Type() const; + uint16_t RedirectCount() const; + +private: + ~PerformanceNavigationTiming() {} +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PerformanceNavigationTiming_h___ diff --git a/dom/performance/PerformanceObserver.cpp b/dom/performance/PerformanceObserver.cpp index 11dd30ac2..d02acfb09 100644 --- a/dom/performance/PerformanceObserver.cpp +++ b/dom/performance/PerformanceObserver.cpp @@ -114,12 +114,13 @@ PerformanceObserver::Notify() RefPtr<PerformanceObserverEntryList> list = new PerformanceObserverEntryList(this, mQueuedEntries); + mQueuedEntries.Clear(); + ErrorResult rv; mCallback->Call(this, *list, *this, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); } - mQueuedEntries.Clear(); } void @@ -170,6 +171,17 @@ PerformanceObserver::Observe(const PerformanceObserverInit& aOptions, mEntryTypes.SwapElements(validEntryTypes); mPerformance->AddObserver(this); + + if (aOptions.mBuffered) { + for (auto entryType : mEntryTypes) { + nsTArray<RefPtr<PerformanceEntry>> existingEntries; + mPerformance->GetEntriesByType(entryType, existingEntries); + if (!existingEntries.IsEmpty()) { + mQueuedEntries.AppendElements(existingEntries); + } + } + } + mConnected = true; } diff --git a/dom/performance/PerformanceObserverEntryList.cpp b/dom/performance/PerformanceObserverEntryList.cpp index 349103f08..20e818f3d 100644 --- a/dom/performance/PerformanceObserverEntryList.cpp +++ b/dom/performance/PerformanceObserverEntryList.cpp @@ -66,6 +66,7 @@ PerformanceObserverEntryList::GetEntries( aRetval.AppendElement(entry); } + aRetval.Sort(PerformanceEntryComparator()); } void @@ -79,6 +80,7 @@ PerformanceObserverEntryList::GetEntriesByType( aRetval.AppendElement(entry); } } + aRetval.Sort(PerformanceEntryComparator()); } void @@ -88,9 +90,18 @@ PerformanceObserverEntryList::GetEntriesByName( nsTArray<RefPtr<PerformanceEntry>>& aRetval) { aRetval.Clear(); + const bool typePassed = aEntryType.WasPassed(); for (const RefPtr<PerformanceEntry>& entry : mEntries) { - if (entry->GetName().Equals(aName)) { - aRetval.AppendElement(entry); + if (!entry->GetName().Equals(aName)) { + continue; } + + if (typePassed && + !entry->GetEntryType().Equals(aEntryType.Value())) { + continue; + } + + aRetval.AppendElement(entry); } + aRetval.Sort(PerformanceEntryComparator()); } diff --git a/dom/performance/PerformanceResourceTiming.cpp b/dom/performance/PerformanceResourceTiming.cpp index 60a20ca28..2eaa4eb9a 100644 --- a/dom/performance/PerformanceResourceTiming.cpp +++ b/dom/performance/PerformanceResourceTiming.cpp @@ -25,7 +25,8 @@ NS_IMPL_RELEASE_INHERITED(PerformanceResourceTiming, PerformanceEntry) PerformanceResourceTiming::PerformanceResourceTiming(PerformanceTiming* aPerformanceTiming, Performance* aPerformance, - const nsAString& aName) + const nsAString& aName, + nsIHttpChannel* aChannel) : PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("resource")), mTiming(aPerformanceTiming), mEncodedBodySize(0), @@ -33,6 +34,34 @@ PerformanceResourceTiming::PerformanceResourceTiming(PerformanceTiming* aPerform mDecodedBodySize(0) { MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); + SetPropertiesFromChannel(aChannel); +} + +void +PerformanceResourceTiming::SetPropertiesFromChannel(nsIHttpChannel* aChannel) +{ + if (!aChannel) { + return; + } + + nsAutoCString protocol; + Unused << aChannel->GetProtocolVersion(protocol); + SetNextHopProtocol(NS_ConvertUTF8toUTF16(protocol)); + + uint64_t encodedBodySize = 0; + Unused << aChannel->GetEncodedBodySize(&encodedBodySize); + SetEncodedBodySize(encodedBodySize); + + uint64_t transferSize = 0; + Unused << aChannel->GetTransferSize(&transferSize); + SetTransferSize(transferSize); + + uint64_t decodedBodySize = 0; + Unused << aChannel->GetDecodedBodySize(&decodedBodySize); + if (decodedBodySize == 0) { + decodedBodySize = encodedBodySize; + } + SetDecodedBodySize(decodedBodySize); } PerformanceResourceTiming::~PerformanceResourceTiming() @@ -42,8 +71,22 @@ PerformanceResourceTiming::~PerformanceResourceTiming() DOMHighResTimeStamp PerformanceResourceTiming::StartTime() const { - DOMHighResTimeStamp startTime = mTiming->RedirectStartHighRes(); - return startTime ? startTime : mTiming->FetchStartHighRes(); + // Force the start time to be the earliest of: + // - RedirectStart + // - WorkerStart + // - AsyncOpen + // Ignore zero values. The RedirectStart and WorkerStart values + // can come from earlier redirected channels prior to the AsyncOpen + // time being recorded. + DOMHighResTimeStamp redirect = mTiming->RedirectStartHighRes(); + redirect = redirect ? redirect : DBL_MAX; + + DOMHighResTimeStamp worker = mTiming->WorkerStartHighRes(); + worker = worker ? worker : DBL_MAX; + + DOMHighResTimeStamp asyncOpen = mTiming->AsyncOpenHighRes(); + + return std::min(asyncOpen, std::min(redirect, worker)); } JSObject* diff --git a/dom/performance/PerformanceResourceTiming.h b/dom/performance/PerformanceResourceTiming.h index 2dd6b4a06..98a03327e 100644 --- a/dom/performance/PerformanceResourceTiming.h +++ b/dom/performance/PerformanceResourceTiming.h @@ -18,7 +18,7 @@ namespace mozilla { namespace dom { // http://www.w3.org/TR/resource-timing/#performanceresourcetiming -class PerformanceResourceTiming final : public PerformanceEntry +class PerformanceResourceTiming : public PerformanceEntry { public: typedef mozilla::TimeStamp TimeStamp; @@ -30,7 +30,8 @@ public: PerformanceResourceTiming(PerformanceTiming* aPerformanceTiming, Performance* aPerformance, - const nsAString& aName); + const nsAString& aName, + nsIHttpChannel* aChannel = nullptr); virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; @@ -62,6 +63,12 @@ public: mNextHopProtocol = aNextHopProtocol; } + DOMHighResTimeStamp WorkerStart() const { + return mTiming && mTiming->TimingAllowed() + ? mTiming->WorkerStartHighRes() + : 0; + } + DOMHighResTimeStamp FetchStart() const { return mTiming ? mTiming->FetchStartHighRes() @@ -170,6 +177,7 @@ public: protected: virtual ~PerformanceResourceTiming(); + void SetPropertiesFromChannel(nsIHttpChannel* aChannel); nsString mInitiatorType; nsString mNextHopProtocol; diff --git a/dom/performance/PerformanceService.cpp b/dom/performance/PerformanceService.cpp new file mode 100644 index 000000000..cf119af89 --- /dev/null +++ b/dom/performance/PerformanceService.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "PerformanceService.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace dom { + +static StaticRefPtr<PerformanceService> gPerformanceService; +static StaticMutex gPerformanceServiceMutex; + +/* static */ PerformanceService* +PerformanceService::GetOrCreate() +{ + StaticMutexAutoLock al(gPerformanceServiceMutex); + + if (!gPerformanceService) { + gPerformanceService = new PerformanceService(); + ClearOnShutdown(&gPerformanceService); + } + + return gPerformanceService; +} + +DOMHighResTimeStamp +PerformanceService::TimeOrigin(const TimeStamp& aCreationTimeStamp) const +{ + return (aCreationTimeStamp - mCreationTimeStamp).ToMilliseconds() + + (mCreationEpochTime / PR_USEC_PER_MSEC); +} + +PerformanceService::PerformanceService() +{ + mCreationTimeStamp = TimeStamp::Now(); + mCreationEpochTime = PR_Now(); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/performance/PerformanceService.h b/dom/performance/PerformanceService.h new file mode 100644 index 000000000..9abbd674d --- /dev/null +++ b/dom/performance/PerformanceService.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef dom_performance_PerformanceService_h +#define dom_performance_PerformanceService_h + +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" +#include "nsDOMNavigationTiming.h" + +namespace mozilla { +namespace dom { + +// This class is thread-safe. + +// We use this singleton for having the correct value of performance.timeOrigin. +// This value must be calculated on top of the pair: +// - mCreationTimeStamp (monotonic clock) +// - mCreationEpochTime (unix epoch time) +// These 2 values must be taken "at the same time" in order to be used +// correctly. + +class PerformanceService +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PerformanceService) + + static PerformanceService* + GetOrCreate(); + + DOMHighResTimeStamp + TimeOrigin(const TimeStamp& aCreationTimeStamp) const; + +private: + PerformanceService(); + ~PerformanceService() = default; + + TimeStamp mCreationTimeStamp; + PRTime mCreationEpochTime; +}; + +} // dom namespace +} // mozilla namespace + +#endif // dom_performance_PerformanceService_h diff --git a/dom/performance/PerformanceTiming.cpp b/dom/performance/PerformanceTiming.cpp index e2f76a21f..887a23938 100755 --- a/dom/performance/PerformanceTiming.cpp +++ b/dom/performance/PerformanceTiming.cpp @@ -73,6 +73,7 @@ PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel) { if (aChannel) { aChannel->GetAsyncOpen(&mAsyncOpen); + aChannel->GetDispatchFetchEventStart(&mWorkerStart); aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin); aChannel->GetRedirectCount(&mRedirectCount); aChannel->GetRedirectStart(&mRedirectStart); @@ -88,31 +89,39 @@ PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel) aChannel->GetResponseEnd(&mResponseEnd); aChannel->GetCacheReadEnd(&mCacheReadEnd); - // the performance timing api essentially requires that the event timestamps - // are >= asyncOpen().. but in truth the browser engages in a number of - // speculative activities that sometimes mean connections and lookups begin - // earlier. Workaround that here by just using asyncOpen as the minimum - // timestamp for dns and connection info. + // The performance timing api essentially requires that the event timestamps + // have a strict relation with each other. The truth, however, is the browser + // engages in a number of speculative activities that sometimes mean connections + // and lookups begin at different times. Workaround that here by clamping + // these values to what we expect FetchStart to be. This means the later of + // AsyncOpen or WorkerStart times. if (!mAsyncOpen.IsNull()) { - if (!mDomainLookupStart.IsNull() && mDomainLookupStart < mAsyncOpen) { - mDomainLookupStart = mAsyncOpen; + // We want to clamp to the expected FetchStart value. This is later of + // the AsyncOpen and WorkerStart values. + const TimeStamp* clampTime = &mAsyncOpen; + if (!mWorkerStart.IsNull() && mWorkerStart > mAsyncOpen) { + clampTime = &mWorkerStart; } - if (!mDomainLookupEnd.IsNull() && mDomainLookupEnd < mAsyncOpen) { - mDomainLookupEnd = mAsyncOpen; + if (!mDomainLookupStart.IsNull() && mDomainLookupStart < *clampTime) { + mDomainLookupStart = *clampTime; } - if (!mConnectStart.IsNull() && mConnectStart < mAsyncOpen) { - mConnectStart = mAsyncOpen; + if (!mDomainLookupEnd.IsNull() && mDomainLookupEnd < *clampTime) { + mDomainLookupEnd = *clampTime; + } + + if (!mConnectStart.IsNull() && mConnectStart < *clampTime) { + mConnectStart = *clampTime; } if (mSecureConnection && !mSecureConnectionStart.IsNull() && - mSecureConnectionStart < mAsyncOpen) { - mSecureConnectionStart = mAsyncOpen; + mSecureConnectionStart < *clampTime) { + mSecureConnectionStart = *clampTime; } - if (!mConnectEnd.IsNull() && mConnectEnd < mAsyncOpen) { - mConnectEnd = mAsyncOpen; + if (!mConnectEnd.IsNull() && mConnectEnd < *clampTime) { + mConnectEnd = *clampTime; } } } @@ -131,9 +140,13 @@ PerformanceTiming::FetchStartHighRes() } MOZ_ASSERT(!mAsyncOpen.IsNull(), "The fetch start time stamp should always be " "valid if the performance timing is enabled"); - mFetchStart = (!mAsyncOpen.IsNull()) - ? TimeStampToDOMHighRes(mAsyncOpen) - : 0.0; + if (!mAsyncOpen.IsNull()) { + if (!mWorkerStart.IsNull() && mWorkerStart > mAsyncOpen) { + mFetchStart = TimeStampToDOMHighRes(mWorkerStart); + } else { + mFetchStart = TimeStampToDOMHighRes(mAsyncOpen); + } + } } return TimerClamping::ReduceMsTimeValue(mFetchStart); } @@ -180,7 +193,7 @@ PerformanceTiming::TimingAllowed() const return mTimingAllowed; } -uint16_t +uint8_t PerformanceTiming::GetRedirectCount() const { if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { @@ -205,6 +218,26 @@ PerformanceTiming::ShouldReportCrossOriginRedirect() const return (mRedirectCount != 0) && mReportCrossOriginRedirect; } +DOMHighResTimeStamp +PerformanceTiming::AsyncOpenHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() || + mAsyncOpen.IsNull()) { + return mZeroTime; + } + return TimeStampToReducedDOMHighResOrFetchStart(mAsyncOpen); +} + +DOMHighResTimeStamp +PerformanceTiming::WorkerStartHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() || + mWorkerStart.IsNull()) { + return mZeroTime; + } + return TimeStampToReducedDOMHighResOrFetchStart(mWorkerStart); +} + /** * RedirectStartHighRes() is used by both the navigation timing and the * resource timing. Since, navigation timing and resource timing check and diff --git a/dom/performance/PerformanceTiming.h b/dom/performance/PerformanceTiming.h index fc7e7d5bd..435e1bca1 100755 --- a/dom/performance/PerformanceTiming.h +++ b/dom/performance/PerformanceTiming.h @@ -139,7 +139,7 @@ public: return TimerClamping::ReduceMsTimeValue(GetDOMTiming()->GetUnloadEventEnd()); } - uint16_t GetRedirectCount() const; + uint8_t GetRedirectCount() const; // Checks if the resource is either same origin as the page that started // the load, or if the response contains the Timing-Allow-Origin header @@ -155,7 +155,12 @@ public: // the timing-allow-origin check in HttpBaseChannel::TimingAllowCheck bool ShouldReportCrossOriginRedirect() const; + // The last channel's AsyncOpen time. This may occur before the FetchStart + // in some cases. + DOMHighResTimeStamp AsyncOpenHighRes(); + // High resolution (used by resource timing) + DOMHighResTimeStamp WorkerStartHighRes(); DOMHighResTimeStamp FetchStartHighRes(); DOMHighResTimeStamp RedirectStartHighRes(); DOMHighResTimeStamp RedirectEndHighRes(); @@ -237,6 +242,14 @@ public: return TimerClamping::ReduceMsTimeValue(GetDOMTiming()->GetLoadEventEnd()); } + DOMTimeMilliSec TimeToNonBlankPaint() const + { + if (!nsContentUtils::IsPerformanceTimingEnabled()) { + return 0; + } + return TimerClamping::ReduceMsTimeValue(GetDOMTiming()->GetTimeToNonBlankPaint()); + } + private: ~PerformanceTiming(); @@ -253,6 +266,7 @@ private: DOMHighResTimeStamp mZeroTime; TimeStamp mAsyncOpen; + TimeStamp mWorkerStart; TimeStamp mRedirectStart; TimeStamp mRedirectEnd; TimeStamp mDomainLookupStart; @@ -265,7 +279,7 @@ private: TimeStamp mCacheReadStart; TimeStamp mResponseEnd; TimeStamp mCacheReadEnd; - uint16_t mRedirectCount; + uint8_t mRedirectCount; bool mTimingAllowed; bool mAllRedirectsSameOrigin; bool mInitialized; diff --git a/dom/performance/PerformanceWorker.cpp b/dom/performance/PerformanceWorker.cpp index 85ca2ccd8..f10c58446 100644 --- a/dom/performance/PerformanceWorker.cpp +++ b/dom/performance/PerformanceWorker.cpp @@ -23,37 +23,6 @@ PerformanceWorker::~PerformanceWorker() mWorkerPrivate->AssertIsOnWorkerThread(); } -DOMHighResTimeStamp -PerformanceWorker::Now() const -{ - TimeDuration duration = - TimeStamp::Now() - mWorkerPrivate->NowBaseTimeStamp(); - return RoundTime(duration.ToMilliseconds()); -} - -// To be removed once bug 1124165 lands -bool -PerformanceWorker::IsPerformanceTimingAttribute(const nsAString& aName) -{ - // In workers we just support navigationStart. - return aName.EqualsASCII("navigationStart"); -} - -DOMHighResTimeStamp -PerformanceWorker::GetPerformanceTimingFromString(const nsAString& aProperty) -{ - if (!IsPerformanceTimingAttribute(aProperty)) { - return 0; - } - - if (aProperty.EqualsLiteral("navigationStart")) { - return mWorkerPrivate->NowBaseTime(); - } - - MOZ_CRASH("IsPerformanceTimingAttribute and GetPerformanceTimingFromString are out of sync"); - return 0; -} - void PerformanceWorker::InsertUserEntry(PerformanceEntry* aEntry) { @@ -72,13 +41,13 @@ PerformanceWorker::InsertUserEntry(PerformanceEntry* aEntry) TimeStamp PerformanceWorker::CreationTimeStamp() const { - return mWorkerPrivate->NowBaseTimeStamp(); + return mWorkerPrivate->CreationTimeStamp(); } DOMHighResTimeStamp PerformanceWorker::CreationTime() const { - return mWorkerPrivate->NowBaseTime(); + return mWorkerPrivate->CreationTime(); } } // dom namespace diff --git a/dom/performance/PerformanceWorker.h b/dom/performance/PerformanceWorker.h index 7eef0d974..346bdd026 100644 --- a/dom/performance/PerformanceWorker.h +++ b/dom/performance/PerformanceWorker.h @@ -21,9 +21,6 @@ class PerformanceWorker final : public Performance public: explicit PerformanceWorker(workers::WorkerPrivate* aWorkerPrivate); - // Performance WebIDL methods - DOMHighResTimeStamp Now() const override; - virtual PerformanceTiming* Timing() override { MOZ_CRASH("This should not be called on workers."); @@ -64,12 +61,6 @@ public: return nullptr; } - virtual Performance* GetParentPerformance() const override - { - MOZ_CRASH("This should not be called on workers."); - return nullptr; - } - protected: ~PerformanceWorker(); @@ -80,11 +71,6 @@ protected: void InsertUserEntry(PerformanceEntry* aEntry) override; - bool IsPerformanceTimingAttribute(const nsAString& aName) override; - - DOMHighResTimeStamp - GetPerformanceTimingFromString(const nsAString& aTimingName) override; - void DispatchBufferFullEvent() override { MOZ_CRASH("This should not be called on workers."); diff --git a/dom/performance/moz.build b/dom/performance/moz.build index 3286a0a4c..e1f96fec8 100644 --- a/dom/performance/moz.build +++ b/dom/performance/moz.build @@ -10,9 +10,11 @@ EXPORTS.mozilla.dom += [ 'PerformanceMark.h', 'PerformanceMeasure.h', 'PerformanceNavigation.h', + 'PerformanceNavigationTiming.h', 'PerformanceObserver.h', 'PerformanceObserverEntryList.h', 'PerformanceResourceTiming.h', + 'PerformanceService.h', 'PerformanceTiming.h', ] @@ -23,9 +25,11 @@ UNIFIED_SOURCES += [ 'PerformanceMark.cpp', 'PerformanceMeasure.cpp', 'PerformanceNavigation.cpp', + 'PerformanceNavigationTiming.cpp', 'PerformanceObserver.cpp', 'PerformanceObserverEntryList.cpp', 'PerformanceResourceTiming.cpp', + 'PerformanceService.cpp', 'PerformanceTiming.cpp', 'PerformanceWorker.cpp', ] diff --git a/dom/performance/tests/mochitest.ini b/dom/performance/tests/mochitest.ini index 18f7f0e45..bee0b2e70 100644 --- a/dom/performance/tests/mochitest.ini +++ b/dom/performance/tests/mochitest.ini @@ -1,13 +1,11 @@ [DEFAULT] support-files = - performance_observer.html test_performance_observer.js test_performance_user_timing.js test_worker_performance_now.js worker_performance_user_timing.js worker_performance_observer.js sharedworker_performance_user_timing.js - worker_performance_observer.html [test_performance_observer.html] [test_performance_user_timing.html] @@ -15,3 +13,4 @@ support-files = [test_worker_observer.html] [test_sharedWorker_performance_user_timing.html] [test_worker_performance_now.html] +[test_timeOrigin.html] diff --git a/dom/performance/tests/performance_observer.html b/dom/performance/tests/performance_observer.html deleted file mode 100644 index b8ced9296..000000000 --- a/dom/performance/tests/performance_observer.html +++ /dev/null @@ -1,74 +0,0 @@ -<!-- - Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ ---> -<!DOCTYPE html> -<meta charset=utf-8> -<html> -<head> -<title>Test for performance observer</title> -<script> -'use strict'; -[ - "promise_test", "test", "setup", - "assert_true", "assert_equals", "assert_array_equals", - "assert_throws", "assert_unreached" -].forEach(func => { - window[func] = opener[func].bind(opener); -}); -function done() { - opener.add_completion_callback(() => { - self.close(); - }); - opener.done(); -} - -</script> -<script src="test_performance_observer.js"></script> -</head> -<body> -<div id="log"></div> -<script> -function makeXHR(aUrl) { - var xmlhttp = new XMLHttpRequest(); - xmlhttp.open("get", aUrl, true); - xmlhttp.send(); -} - -promise_test(t => { - var promise = new Promise(resolve => { - performance.clearResourceTimings(); - - var observer = new PerformanceObserver(list => resolve(list)); - observer.observe({entryTypes: ['resource']}); - t.add_cleanup(() => observer.disconnect()); - }); - - makeXHR("test-data.json"); - - return promise.then(list => { - assert_equals(list.getEntries().length, 1); - assert_array_equals(list.getEntries(), - performance.getEntriesByType("resource"), - "Observed 'resource' entries should equal to entries obtained by getEntriesByType."); - - // getEntries filtering tests - assert_array_equals(list.getEntries({name: "http://mochi.test:8888/tests/dom/base/test/test-data.json"}), - performance.getEntriesByName("http://mochi.test:8888/tests/dom/base/test/test-data.json"), - "getEntries with name filter should return correct results."); - assert_array_equals(list.getEntries({entryType: "resource"}), - performance.getEntriesByType("resource"), - "getEntries with entryType filter should return correct results."); - assert_array_equals(list.getEntries({initiatorType: "xmlhttprequest"}), - performance.getEntriesByType("resource"), - "getEntries with initiatorType filter should return correct results."); - assert_array_equals(list.getEntries({initiatorType: "link"}), - [], - "getEntries with non-existent initiatorType filter should return an empty array."); - }); -}, "resource-timing test"); - -done(); - -</script> -</body> diff --git a/dom/performance/tests/test_performance_observer.html b/dom/performance/tests/test_performance_observer.html index d36878315..7df881bd4 100644 --- a/dom/performance/tests/test_performance_observer.html +++ b/dom/performance/tests/test_performance_observer.html @@ -3,15 +3,55 @@ http://creativecommons.org/publicdomain/zero/1.0/ --> <!DOCTYPE html> +<html> +<head> <meta charset=utf-8> <title>Test for performance observer</title> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> -<div id=log></div> +</head> +<body> +<div id="log"></div> +<script src="test_performance_observer.js"></script> <script> -'use strict'; -SpecialPowers.pushPrefEnv({"set": [["dom.enable_performance_observer", true]]}, - function() { - window.open("performance_observer.html"); - }); +function makeXHR(aUrl) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open("get", aUrl, true); + xmlhttp.send(); +} + +promise_test(t => { + var promise = new Promise(resolve => { + performance.clearResourceTimings(); + + var observer = new PerformanceObserver(list => resolve(list)); + observer.observe({entryTypes: ['resource']}); + t.add_cleanup(() => observer.disconnect()); + }); + + makeXHR("test-data.json"); + + return promise.then(list => { + assert_equals(list.getEntries().length, 1); + assert_array_equals(list.getEntries(), + performance.getEntriesByType("resource"), + "Observed 'resource' entries should equal to entries obtained by getEntriesByType."); + + // getEntries filtering tests + assert_array_equals(list.getEntries({name: "http://mochi.test:8888/tests/dom/base/test/test-data.json"}), + performance.getEntriesByName("http://mochi.test:8888/tests/dom/base/test/test-data.json"), + "getEntries with name filter should return correct results."); + assert_array_equals(list.getEntries({entryType: "resource"}), + performance.getEntriesByType("resource"), + "getEntries with entryType filter should return correct results."); + assert_array_equals(list.getEntries({initiatorType: "xmlhttprequest"}), + performance.getEntriesByType("resource"), + "getEntries with initiatorType filter should return correct results."); + assert_array_equals(list.getEntries({initiatorType: "link"}), + [], + "getEntries with non-existent initiatorType filter should return an empty array."); + }); +}, "resource-timing test"); + </script> +</body> diff --git a/dom/performance/tests/test_performance_user_timing.js b/dom/performance/tests/test_performance_user_timing.js index cd8261bbd..a15dbebb6 100644 --- a/dom/performance/tests/test_performance_user_timing.js +++ b/dom/performance/tests/test_performance_user_timing.js @@ -126,15 +126,18 @@ var steps = [ }, // Test measure function () { - ok(true, "Running measure addition with no start/end time test"); - performance.measure("test"); - var measures = performance.getEntriesByType("measure"); - is(measures.length, 1, "number of measures should be 1"); - var measure = measures[0]; - is(measure.name, "test", "measure name should be 'test'"); - is(measure.entryType, "measure", "measure type should be 'measure'"); - is(measure.startTime, 0, "measure start time should be zero"); - ok(measure.duration >= 0, "measure duration should not be negative"); + // We don't have navigationStart in workers. + if ("window" in self) { + ok(true, "Running measure addition with no start/end time test"); + performance.measure("test", "navigationStart"); + var measures = performance.getEntriesByType("measure"); + is(measures.length, 1, "number of measures should be 1"); + var measure = measures[0]; + is(measure.name, "test", "measure name should be 'test'"); + is(measure.entryType, "measure", "measure type should be 'measure'"); + is(measure.startTime, 0, "measure start time should be zero"); + ok(measure.duration >= 0, "measure duration should not be negative"); + } }, function () { ok(true, "Running measure addition with only start time test"); diff --git a/dom/performance/tests/test_timeOrigin.html b/dom/performance/tests/test_timeOrigin.html new file mode 100644 index 000000000..5a8a461f3 --- /dev/null +++ b/dom/performance/tests/test_timeOrigin.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test for performance.timeOrigin</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="test_performance_user_timing.js"></script> + </head> + <body> + <script type="text/js-worker" id="worker-src"> + postMessage({ now: performance.now(), timeOrigin: performance.timeOrigin }); + </script> + + <script type="text/js-worker" id="shared-worker-src"> + onconnect = function(evt) { + evt.ports[0].postMessage({ now: performance.now(), timeOrigin: performance.timeOrigin }); + }; + </script> + + <script class="testbody" type="text/javascript"> + +function testBasic() { + ok("timeOrigin" in performance, "Performance.timeOrigin exists."); + ok(performance.timeOrigin > 0, "TimeOrigin must be greater than 0."); + next(); +} + +function testWorker() { + var now = performance.now(); + + var blob = new Blob([ document.getElementById("worker-src").textContent ], + { type: "text/javascript" }); + var w = new Worker(URL.createObjectURL(blob)); + w.onmessage = function(e) { + ok (e.now + e.timeOrigin > now + performance.now, "Comparing worker.now and window.now"); + next(); + } +} + +function testSharedWorker() { + var now = performance.now(); + + var blob = new Blob([ document.getElementById("shared-worker-src").textContent ], + { type: "text/javascript" }); + var w = new SharedWorker(URL.createObjectURL(blob)); + w.port.onmessage = function(e) { + ok (e.now + e.timeOrigin > now + performance.now, "Comparing worker.now and window.now"); + next(); + } +} + +var tests = [ testBasic, testWorker, testSharedWorker ]; +function next() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(next); + </script> + </pre> + </body> +</html> diff --git a/dom/performance/tests/test_worker_observer.html b/dom/performance/tests/test_worker_observer.html index b9ed0c964..9a55ef1d5 100644 --- a/dom/performance/tests/test_worker_observer.html +++ b/dom/performance/tests/test_worker_observer.html @@ -3,15 +3,16 @@ http://creativecommons.org/publicdomain/zero/1.0/ --> <!DOCTYPE html> +<html> +<head> <meta charset=utf-8> <title>Test for performance observer in worker</title> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> -<div id=log></div> +</head> +<body> +<div id="log"></div> <script> -'use strict'; -SpecialPowers.pushPrefEnv({"set": [["dom.enable_performance_observer", true]]}, - function() { - window.open("worker_performance_observer.html"); - }); +fetch_tests_from_worker(new Worker("worker_performance_observer.js")); </script> +</body> diff --git a/dom/performance/tests/worker_performance_observer.html b/dom/performance/tests/worker_performance_observer.html deleted file mode 100644 index 613762f52..000000000 --- a/dom/performance/tests/worker_performance_observer.html +++ /dev/null @@ -1,32 +0,0 @@ -<!-- - Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ ---> -<!DOCTYPE html> -<meta charset=utf-8> -<html> -<head> -<title>Test for performance observer in worker</title> -</head> -<body> -<div id="log"></div> -<script> -[ - "async_test", "test", "setup", - "assert_true", "assert_equals", "assert_array_equals", - "assert_throws", "fetch_tests_from_worker" -].forEach(func => { - window[func] = opener[func].bind(opener); -}); - -function done() { - opener.add_completion_callback(() => { - self.close(); - }); - opener.done(); -} - -fetch_tests_from_worker(new Worker("worker_performance_observer.js")); -done(); -</script> -</body> diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index b7651be1a..d5b1eb9ea 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -535,16 +535,6 @@ NS_IMETHODIMP nsPluginInstanceOwner::GetURL(const char *aURL, nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, baseURI); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); - if (aDoCheckLoadURIChecks) { - nsCOMPtr<nsIScriptSecurityManager> secMan( - do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); - NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE); - - rv = secMan->CheckLoadURIWithPrincipal(content->NodePrincipal(), uri, - nsIScriptSecurityManager::STANDARD); - NS_ENSURE_SUCCESS(rv, rv); - } - nsCOMPtr<nsIInputStream> headersDataStream; if (aPostStream && aHeadersData) { if (!aHeadersDataLen) @@ -563,8 +553,21 @@ NS_IMETHODIMP nsPluginInstanceOwner::GetURL(const char *aURL, Preferences::GetInt("privacy.popups.disable_from_plugins"); nsAutoPopupStatePusher popupStatePusher((PopupControlState)blockPopups); + + // if security checks (in particular CheckLoadURIWithPrincipal) needs + // to be skipped we are creating a codebasePrincipal to make sure + // that security check succeeds. Please note that we do not want to + // fall back to using the systemPrincipal, because that would also + // bypass ContentPolicy checks which should still be enforced. + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + if (!aDoCheckLoadURIChecks) { + mozilla::PrincipalOriginAttributes attrs = + BasePrincipal::Cast(content->NodePrincipal())->OriginAttributesRef(); + triggeringPrincipal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); + } + rv = lh->OnLinkClick(content, uri, unitarget.get(), NullString(), - aPostStream, headersDataStream, true); + aPostStream, headersDataStream, true, triggeringPrincipal); return rv; } @@ -2532,6 +2535,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent) NS_ASSERTION(anEvent.mMessage == eMouseDown || anEvent.mMessage == eMouseUp || anEvent.mMessage == eMouseDoubleClick || + anEvent.mMessage == eMouseAuxClick || anEvent.mMessage == eMouseOver || anEvent.mMessage == eMouseOut || anEvent.mMessage == eMouseMove || @@ -2594,6 +2598,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent) switch (anEvent.mMessage) { case eMouseClick: case eMouseDoubleClick: + case eMouseAuxClick: // Button up/down events sent instead. return rv; default: @@ -2797,6 +2802,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent) switch (anEvent.mMessage) { case eMouseClick: case eMouseDoubleClick: + case eMouseAuxClick: // Button up/down events sent instead. return rv; default: diff --git a/dom/plugins/test/mochitest/test_bug813906.html b/dom/plugins/test/mochitest/test_bug813906.html index 04c34daaf..d18dbbff2 100644 --- a/dom/plugins/test/mochitest/test_bug813906.html +++ b/dom/plugins/test/mochitest/test_bug813906.html @@ -18,21 +18,35 @@ function f() { </script> <script type="application/javascript"> +SimpleTest.requestFlakyTimeout( + "Blocking an iframe does not cause the onerror event to be fired"); + SimpleTest.waitForExplicitFinish(); setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); var frameLoadCount = 0; + +function frameNavBlocked() { + isnot(SpecialPowers.wrap(window.frame1).location.href.indexOf('chrome://'), + 0, 'plugin shouldnt be able to cause navigation to chrome URLs'); + SimpleTest.finish(); +} + function frameLoaded() { frameLoadCount++; if (frameLoadCount == 1) { document.getElementsByTagName("object")[0].type = "application/x-test"; document.getElementsByTagName("use")[0].setAttributeNS("http://www.w3.org/1999/xlink", "href", location.href + "#a"); - } else if (frameLoadCount == 2) { - isnot(SpecialPowers.wrap(window.frame1).location.href.indexOf('chrome://'), - 0, 'plugin shouldnt be able to cause navigation to chrome URLs'); - SimpleTest.finish(); + + // wait two seconds and verify that frame navigation did not succeed + setTimeout(frameNavBlocked, 2000); + return; } + // we should never get here, but just in case, make sure the test fails in that case. + ok(false, "onload() event should not fire for blocked navigation"); + SimpleTest.finish(); } + </script> <!-- Note that <svg:use> ends up creating an anonymous subtree, which means that the plugin diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp index 0cc4933fe..c6558fc93 100644 --- a/dom/security/nsContentSecurityManager.cpp +++ b/dom/security/nsContentSecurityManager.cpp @@ -10,6 +10,8 @@ #include "nsIStreamListener.h" #include "nsIDocument.h" #include "nsMixedContentBlocker.h" +#include "nsCDefaultURIFixup.h" +#include "nsIURIFixup.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/TabChild.h" @@ -244,10 +246,6 @@ DoCORSChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo, static nsresult DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) { - nsCOMPtr<nsIURI> uri; - nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); - NS_ENSURE_SUCCESS(rv, rv); - nsContentPolicyType contentPolicyType = aLoadInfo->GetExternalContentPolicyType(); nsContentPolicyType internalContentPolicyType = @@ -255,12 +253,24 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) nsCString mimeTypeGuess; nsCOMPtr<nsISupports> requestingContext = nullptr; -#ifdef DEBUG - // Don't enforce TYPE_DOCUMENT assertions for loads - // initiated by javascript tests. - bool skipContentTypeCheck = false; - skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion"); -#endif + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) { + // TYPE_DOCUMENT and TYPE_SUBDOCUMENT loads might potentially + // be wyciwyg:// channels. Let's fix up the URI so we can + // perform proper security checks. + nsCOMPtr<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && urifixup) { + nsCOMPtr<nsIURI> fixedURI; + rv = urifixup->CreateExposableURI(uri, getter_AddRefs(fixedURI)); + if (NS_SUCCEEDED(rv)) { + uri = fixedURI; + } + } + } switch(contentPolicyType) { case nsIContentPolicy::TYPE_OTHER: { @@ -294,16 +304,14 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) } case nsIContentPolicy::TYPE_DOCUMENT: { - MOZ_ASSERT(skipContentTypeCheck || false, "contentPolicyType not supported yet"); + mimeTypeGuess = EmptyCString(); + requestingContext = aLoadInfo->LoadingNode(); break; } case nsIContentPolicy::TYPE_SUBDOCUMENT: { mimeTypeGuess = NS_LITERAL_CSTRING("text/html"); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_subdocument requires requestingContext of type Document"); break; } @@ -470,18 +478,32 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) MOZ_ASSERT(false, "can not perform security check without a valid contentType"); } + // For document loads we use the triggeringPrincipal as the originPrincipal. + // Note the the loadingPrincipal for loads of TYPE_DOCUMENT is a nullptr. + nsCOMPtr<nsIPrincipal> principal = + (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) + ? aLoadInfo->TriggeringPrincipal() + : aLoadInfo->LoadingPrincipal(); + int16_t shouldLoad = nsIContentPolicy::ACCEPT; rv = NS_CheckContentLoadPolicy(internalContentPolicyType, uri, - aLoadInfo->LoadingPrincipal(), + principal, requestingContext, mimeTypeGuess, nullptr, //extra, &shouldLoad, nsContentUtils::GetContentPolicy(), nsContentUtils::GetSecurityManager()); - NS_ENSURE_SUCCESS(rv, rv); - if (NS_CP_REJECTED(shouldLoad)) { + + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { + if ((NS_SUCCEEDED(rv) && shouldLoad == nsIContentPolicy::REJECT_TYPE) && + (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT)) { + // for docshell loads we might have to return SHOW_ALT. + return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; + } return NS_ERROR_CONTENT_BLOCKED; } @@ -629,6 +651,24 @@ nsContentSecurityManager::CheckChannel(nsIChannel* aChannel) nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); + nsContentPolicyType contentPolicyType = + loadInfo->GetExternalContentPolicyType(); + + if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) { + // TYPE_DOCUMENT and TYPE_SUBDOCUMENT loads might potentially + // be wyciwyg:// channels. Let's fix up the URI so we can + // perform proper security checks. + nsCOMPtr<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && urifixup) { + nsCOMPtr<nsIURI> fixedURI; + rv = urifixup->CreateExposableURI(uri, getter_AddRefs(fixedURI)); + if (NS_SUCCEEDED(rv)) { + uri = fixedURI; + } + } + } + // Handle cookie policies uint32_t cookiePolicy = loadInfo->GetCookiePolicy(); if (cookiePolicy == nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) { diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index acbc12e07..4b47d2b4f 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -751,9 +751,11 @@ var interfaceNamesInGlobalScope = // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceNavigation", // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "PerformanceObserver", nightly: true}, + "PerformanceNavigationTiming", // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "PerformanceObserverEntryList", nightly: true}, + "PerformanceObserver" +// IMPORTANT: Do not change this list without review from a DOM peer! + "PerformanceObserverEntryList" // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceResourceTiming", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/EventHandler.webidl b/dom/webidl/EventHandler.webidl index e65a75787..1edc45ac9 100644 --- a/dom/webidl/EventHandler.webidl +++ b/dom/webidl/EventHandler.webidl @@ -33,6 +33,7 @@ interface GlobalEventHandlers { // attribute OnErrorEventHandler onerror; attribute EventHandler onfocus; //(Not implemented)attribute EventHandler oncancel; + attribute EventHandler onauxclick; attribute EventHandler oncanplay; attribute EventHandler oncanplaythrough; attribute EventHandler onchange; @@ -128,14 +129,14 @@ interface GlobalEventHandlers { attribute EventHandler onmozpointerlockerror; // CSS-Animation and CSS-Transition handlers. + attribute EventHandler onanimationcancel; attribute EventHandler onanimationend; attribute EventHandler onanimationiteration; attribute EventHandler onanimationstart; + attribute EventHandler ontransitioncancel; attribute EventHandler ontransitionend; - // We will ship transitionrun and transitionstart events - // on Firefox 53. (For detail, see bug 1324985) -// attribute EventHandler ontransitionrun; -// attribute EventHandler ontransitionstart; + attribute EventHandler ontransitionrun; + attribute EventHandler ontransitionstart; // CSS-Animation and CSS-Transition legacy handlers. // This handler isn't standard. diff --git a/dom/webidl/Performance.webidl b/dom/webidl/Performance.webidl index eaede253c..0bd2677df 100644 --- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -17,6 +17,9 @@ typedef sequence <PerformanceEntry> PerformanceEntryList; interface Performance { [DependsOn=DeviceState, Affects=Nothing] DOMHighResTimeStamp now(); + + [Constant] + readonly attribute DOMHighResTimeStamp timeOrigin; }; [Exposed=Window] diff --git a/dom/webidl/PerformanceNavigationTiming.webidl b/dom/webidl/PerformanceNavigationTiming.webidl new file mode 100644 index 000000000..fa3ecaec4 --- /dev/null +++ b/dom/webidl/PerformanceNavigationTiming.webidl @@ -0,0 +1,33 @@ +/* -*- 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://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming + * + * Copyright © 2016 W3C® (MIT, ERCIM, Keio, Beihang). + * W3C liability, trademark and document use rules apply. + */ + +enum NavigationType { + "navigate", + "reload", + "back_forward", + "prerender" +}; + +interface PerformanceNavigationTiming : PerformanceResourceTiming { + readonly attribute DOMHighResTimeStamp unloadEventStart; + readonly attribute DOMHighResTimeStamp unloadEventEnd; + readonly attribute DOMHighResTimeStamp domInteractive; + readonly attribute DOMHighResTimeStamp domContentLoadedEventStart; + readonly attribute DOMHighResTimeStamp domContentLoadedEventEnd; + readonly attribute DOMHighResTimeStamp domComplete; + readonly attribute DOMHighResTimeStamp loadEventStart; + readonly attribute DOMHighResTimeStamp loadEventEnd; + readonly attribute NavigationType type; + readonly attribute unsigned short redirectCount; + + jsonifier; +}; diff --git a/dom/webidl/PerformanceObserver.webidl b/dom/webidl/PerformanceObserver.webidl index a3a14cb1e..4cebecbeb 100644 --- a/dom/webidl/PerformanceObserver.webidl +++ b/dom/webidl/PerformanceObserver.webidl @@ -9,6 +9,7 @@ dictionary PerformanceObserverInit { required sequence<DOMString> entryTypes; + boolean buffered = false; }; callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries, PerformanceObserver observer); diff --git a/dom/webidl/PerformanceResourceTiming.webidl b/dom/webidl/PerformanceResourceTiming.webidl index 021b84ae2..112228325 100644 --- a/dom/webidl/PerformanceResourceTiming.webidl +++ b/dom/webidl/PerformanceResourceTiming.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is - * http://w3c-test.org/webperf/specs/ResourceTiming/#performanceresourcetiming + * https://w3c.github.io/resource-timing/#performanceresourcetiming * * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C * liability, trademark and document use rules apply. @@ -12,14 +12,10 @@ interface PerformanceResourceTiming : PerformanceEntry { - // A string with the name of that element that initiated the load. - // If the initiator is a CSS resource, the initiatorType attribute must return - // the string "css". - // If the initiator is an XMLHttpRequest object, the initiatorType attribute - // must return the string "xmlhttprequest". readonly attribute DOMString initiatorType; readonly attribute DOMString nextHopProtocol; + readonly attribute DOMHighResTimeStamp workerStart; readonly attribute DOMHighResTimeStamp redirectStart; readonly attribute DOMHighResTimeStamp redirectEnd; readonly attribute DOMHighResTimeStamp fetchStart; diff --git a/dom/webidl/PerformanceTiming.webidl b/dom/webidl/PerformanceTiming.webidl index e14201440..4aa403a50 100644 --- a/dom/webidl/PerformanceTiming.webidl +++ b/dom/webidl/PerformanceTiming.webidl @@ -33,5 +33,11 @@ interface PerformanceTiming { readonly attribute unsigned long long loadEventStart; readonly attribute unsigned long long loadEventEnd; + // This is a Chrome proprietary extension and not part of the + // performance/navigation timing specification. + // Returns 0 if a non-blank paint has not happened. + [Pref="dom.performance.time_to_non_blank_paint.enabled"] + readonly attribute unsigned long long timeToNonBlankPaint; + jsonifier; }; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 8682aee97..8469c9001 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -347,6 +347,7 @@ WEBIDL_FILES = [ 'PerformanceMark.webidl', 'PerformanceMeasure.webidl', 'PerformanceNavigation.webidl', + 'PerformanceNavigationTiming.webidl', 'PerformanceObserver.webidl', 'PerformanceObserverEntryList.webidl', 'PerformanceResourceTiming.webidl', diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index 780b2f5f8..1f79e2c92 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -12,6 +12,7 @@ #include "nsINetworkInterceptController.h" #include "nsIOutputStream.h" #include "nsIScriptError.h" +#include "nsITimedChannel.h" #include "nsIUnicodeDecoder.h" #include "nsIUnicodeEncoder.h" #include "nsContentPolicyUtils.h" @@ -108,6 +109,12 @@ NS_IMETHODIMP CancelChannelRunnable::Run() { MOZ_ASSERT(NS_IsMainThread()); + + // TODO: When bug 1204254 is implemented, this time marker should be moved to + // the point where the body of the network request is complete. + mChannel->SetHandleFetchEventEnd(TimeStamp::Now()); + mChannel->SaveTimeStampsToUnderlyingChannel(); + mChannel->Cancel(mStatus); mRegistration->MaybeScheduleUpdate(); return NS_OK; @@ -230,6 +237,9 @@ public: return NS_OK; } + mChannel->SetHandleFetchEventEnd(TimeStamp::Now()); + mChannel->SaveTimeStampsToUnderlyingChannel(); + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); if (obsService) { obsService->NotifyObservers(underlyingChannel, "service-worker-synthesized-response", nullptr); diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp index eaa548f95..24b2e11e6 100644 --- a/dom/workers/ServiceWorkerPrivate.cpp +++ b/dom/workers/ServiceWorkerPrivate.cpp @@ -13,6 +13,7 @@ #include "nsINetworkInterceptController.h" #include "nsIPushErrorReporter.h" #include "nsISupportsImpl.h" +#include "nsITimedChannel.h" #include "nsIUploadChannel2.h" #include "nsNetUtil.h" #include "nsProxyRelease.h" @@ -1255,6 +1256,7 @@ class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable nsCString mMethod; nsString mClientId; bool mIsReload; + bool mMarkLaunchServiceWorkerEnd; RequestCache mCacheMode; RequestMode mRequestMode; RequestRedirect mRequestRedirect; @@ -1273,13 +1275,15 @@ public: const nsACString& aScriptSpec, nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration, const nsAString& aDocumentId, - bool aIsReload) + bool aIsReload, + bool aMarkLaunchServiceWorkerEnd) : ExtendableFunctionalEventWorkerRunnable( aWorkerPrivate, aKeepAliveToken, aRegistration) , mInterceptedChannel(aChannel) , mScriptSpec(aScriptSpec) , mClientId(aDocumentId) , mIsReload(aIsReload) + , mMarkLaunchServiceWorkerEnd(aMarkLaunchServiceWorkerEnd) , mCacheMode(RequestCache::Default) , mRequestMode(RequestMode::No_cors) , mRequestRedirect(RequestRedirect::Follow) @@ -1417,6 +1421,12 @@ public: WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); + + if (mMarkLaunchServiceWorkerEnd) { + mInterceptedChannel->SetLaunchServiceWorkerEnd(TimeStamp::Now()); + } + + mInterceptedChannel->SetDispatchFetchEventEnd(TimeStamp::Now()); return DispatchFetchEvent(aCx, aWorkerPrivate); } @@ -1445,6 +1455,10 @@ private: NS_IMETHOD Run() override { AssertIsOnMainThread(); + + mChannel->SetHandleFetchEventEnd(TimeStamp::Now()); + mChannel->SaveTimeStampsToUnderlyingChannel(); + nsresult rv = mChannel->ResetInterception(); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to resume intercepted network request"); @@ -1520,6 +1534,8 @@ private: event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec); event->SetTrusted(true); + mInterceptedChannel->SetHandleFetchEventStart(TimeStamp::Now()); + RefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope()); nsresult rv2 = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) { @@ -1614,9 +1630,21 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, nsCOMPtr<nsIRunnable> failRunnable = NewRunnableMethod(aChannel, &nsIInterceptedChannel::ResetInterception); - nsresult rv = SpawnWorkerIfNeeded(FetchEvent, failRunnable, aLoadGroup); + aChannel->SetLaunchServiceWorkerStart(TimeStamp::Now()); + aChannel->SetDispatchFetchEventStart(TimeStamp::Now()); + + bool newWorkerCreated = false; + nsresult rv = SpawnWorkerIfNeeded(FetchEvent, + failRunnable, + &newWorkerCreated, + aLoadGroup); + NS_ENSURE_SUCCESS(rv, rv); + if (!newWorkerCreated) { + aChannel->SetLaunchServiceWorkerEnd(TimeStamp::Now()); + } + nsMainThreadPtrHandle<nsIInterceptedChannel> handle( new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false)); @@ -1646,7 +1674,7 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, RefPtr<FetchEventRunnable> r = new FetchEventRunnable(mWorkerPrivate, token, handle, mInfo->ScriptSpec(), regInfo, - aDocumentId, aIsReload); + aDocumentId, aIsReload, newWorkerCreated); rv = r->Init(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; @@ -1669,6 +1697,7 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, nsresult ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, nsIRunnable* aLoadFailedRunnable, + bool* aNewWorkerCreated, nsILoadGroup* aLoadGroup) { AssertIsOnMainThread(); @@ -1679,6 +1708,12 @@ ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, // the overriden load group when intercepting a fetch. MOZ_ASSERT_IF(aWhy == FetchEvent, aLoadGroup); + // Defaults to no new worker created, but if there is one, we'll set the value + // to true at the end of this function. + if (aNewWorkerCreated) { + *aNewWorkerCreated = false; + } + if (mWorkerPrivate) { mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup); RenewKeepAliveToken(aWhy); @@ -1762,6 +1797,10 @@ ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, RenewKeepAliveToken(aWhy); + if (aNewWorkerCreated) { + *aNewWorkerCreated = true; + } + return NS_OK; } diff --git a/dom/workers/ServiceWorkerPrivate.h b/dom/workers/ServiceWorkerPrivate.h index 8d59ea1d0..911b07a11 100644 --- a/dom/workers/ServiceWorkerPrivate.h +++ b/dom/workers/ServiceWorkerPrivate.h @@ -189,6 +189,7 @@ private: nsresult SpawnWorkerIfNeeded(WakeUpReason aWhy, nsIRunnable* aLoadFailedRunnable, + bool* aNewWorkerCreated = nullptr, nsILoadGroup* aLoadGroup = nullptr); ~ServiceWorkerPrivate(); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index bd8a33032..8848e881a 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -2419,8 +2419,6 @@ WorkerPrivateParent<Derived>::WorkerPrivateParent( MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext); MOZ_ASSERT(IsDedicatedWorker()); - mNowBaseTimeStamp = aParent->NowBaseTimeStamp(); - mNowBaseTimeHighRes = aParent->NowBaseTime(); if (aParent->mParentFrozen) { Freeze(nullptr); @@ -2451,18 +2449,6 @@ WorkerPrivateParent<Derived>::WorkerPrivateParent( .creationOptions().setSecureContext(true); } - if (IsDedicatedWorker() && mLoadInfo.mWindow && - mLoadInfo.mWindow->GetPerformance()) { - mNowBaseTimeStamp = mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()-> - GetNavigationStartTimeStamp(); - mNowBaseTimeHighRes = - mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()-> - GetNavigationStartHighRes(); - } else { - mNowBaseTimeStamp = CreationTimeStamp(); - mNowBaseTimeHighRes = CreationTime(); - } - // Our parent can get suspended after it initiates the async creation // of a new worker thread. In this case suspend the new worker as well. if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsSuspended()) { diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 465c0f9a3..28283bed7 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -216,8 +216,6 @@ private: WorkerType mWorkerType; TimeStamp mCreationTimeStamp; DOMHighResTimeStamp mCreationTimeHighRes; - TimeStamp mNowBaseTimeStamp; - DOMHighResTimeStamp mNowBaseTimeHighRes; protected: // The worker is owned by its thread, which is represented here. This is set @@ -579,14 +577,11 @@ public: return mCreationTimeHighRes; } - TimeStamp NowBaseTimeStamp() const + DOMHighResTimeStamp TimeStampToDOMHighRes(const TimeStamp& aTimeStamp) const { - return mNowBaseTimeStamp; - } - - DOMHighResTimeStamp NowBaseTime() const - { - return mNowBaseTimeHighRes; + MOZ_ASSERT(!aTimeStamp.IsNull()); + TimeDuration duration = aTimeStamp - mCreationTimeStamp; + return duration.ToMilliseconds(); } nsIPrincipal* diff --git a/dom/workers/test/serviceworkers/chrome.ini b/dom/workers/test/serviceworkers/chrome.ini index e064e7fd0..6d7dbebd0 100644 --- a/dom/workers/test/serviceworkers/chrome.ini +++ b/dom/workers/test/serviceworkers/chrome.ini @@ -3,6 +3,8 @@ skip-if = os == 'android' support-files = chrome_helpers.js empty.js + fetch.js + hello.html serviceworker.html serviceworkerinfo_iframe.html serviceworkermanager_iframe.html @@ -10,6 +12,7 @@ support-files = worker.js worker2.js +[test_devtools_serviceworker_interception.html] [test_privateBrowsing.html] [test_serviceworkerinfo.xul] [test_serviceworkermanager.xul] diff --git a/dom/workers/test/serviceworkers/test_devtools_serviceworker_interception.html b/dom/workers/test/serviceworkers/test_devtools_serviceworker_interception.html new file mode 100644 index 000000000..d49ebb2c9 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_devtools_serviceworker_interception.html @@ -0,0 +1,168 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1168875 - test devtools serviceworker interception.</title> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" + type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + +// Constants +const Ci = Components.interfaces; +const workerScope = "http://mochi.test:8888/chrome/dom/workers/test/serviceworkers/"; +const workerURL = workerScope + "fetch.js"; +const contentPage = workerScope + "hello.html"; + +function createTestWindow(aURL) { + var mainwindow = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + var win = mainwindow.OpenBrowserWindow(contentPage); + + return new Promise(aResolve => { + win.addEventListener("DOMContentLoaded", function callback() { + if (win.content.location.href != aURL) { + win.gBrowser.loadURI(aURL); + return; + } + + win.removeEventListener("DOMContentLoaded", callback); + aResolve(win.content); + }); + }); +} + +function executeTest(aWindow) { + var registration; + + return Promise.resolve() + // Should not be intercepted. + .then(_ => fetchAndCheckTimedChannel(aWindow, false, true, "hello.html")) + + // Regist a service worker. + .then(_ => register(aWindow, workerURL, workerScope)) + .then(r => registration = r) + + // Should be intercpeted and synthesized. + .then(_ => fetchAndCheckTimedChannel(aWindow, true, false, "fake.html")) + + // Should be intercepted but still fetch from network. + .then(_ => fetchAndCheckTimedChannel(aWindow, true, true, + "hello.html?ForBypassingHttpCache")) + + // Tear down + .then(_ => registration.unregister()); +} + +function register(aWindow, aURL, aScope) { + return aWindow.navigator.serviceWorker.register(aURL, {scope: aScope}) + .then(r => { + var worker = r.installing; + return new Promise(function(aResolve) { + worker.onstatechange = function() { + if (worker.state == "activated") { + aResolve(r); + } + } + }); + }); +} + +function fetchAndCheckTimedChannel(aWindow, aIntercepted, aFetch, aURL) { + var resolveFunction; + var promise = new Promise(aResolve => resolveFunction = aResolve); + + var topic = aFetch ? "http-on-examine-response" + : "service-worker-synthesized-response"; + + function observer(aSubject) { + var channel = aSubject.QueryInterface(Ci.nsIChannel); + ok(channel.URI.spec.endsWith(aURL)); + + var tc = aSubject.QueryInterface(Ci.nsITimedChannel); + + // Check service worker related timings. + var serviceWorkerTimings = [{start: tc.launchServiceWorkerStartTime, + end: tc.launchServiceWorkerEndTime}, + {start: tc.dispatchFetchEventStartTime, + end: tc.dispatchFetchEventEndTime}, + {start: tc.handleFetchEventStartTime, + end: tc.handleFetchEventEndTime}]; + if (aIntercepted) { + serviceWorkerTimings.reduce((aPreviousTimings, aCurrentTimings) => { + ok(aPreviousTimings.start <= aCurrentTimings.start, + "Start time order check."); + ok(aPreviousTimings.end <= aCurrentTimings.end, + "End time order check."); + ok(aCurrentTimings.start <= aCurrentTimings.end, + "Start time should be smaller than end time."); + return aCurrentTimings; + }); + } else { + serviceWorkerTimings.forEach(aTimings => { + is(aTimings.start, 0); + is(aTimings.end, 0); + }); + } + + // Check network related timings. + var networkTimings = [tc.domainLookupStartTime, + tc.domainLookupEndTime, + tc.connectStartTime, + tc.connectEndTime, + tc.requestStartTime, + tc.responseStartTime, + tc.responseEndTime]; + if (aFetch) { + networkTimings.reduce((aPreviousTiming, aCurrentTiming) => { + ok(aPreviousTiming <= aCurrentTiming); + return aCurrentTiming; + }); + } else { + networkTimings.forEach(aTiming => is(aTiming, 0)); + } + + SpecialPowers.removeObserver(observer, topic); + resolveFunction(); + } + + SpecialPowers.addObserver(observer, topic, false); + + // return promise; + return Promise.all([aWindow.fetch(aURL), promise]); +} + +function runTest() { + return Promise.resolve() + .then(_ => createTestWindow(contentPage)) + .then(w => executeTest(w)) + .catch(e => ok(false, "Some test failed with error " + e)) + .then(_ => SimpleTest.finish()); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], +]}, runTest); + +</script> +</pre> +</body> +</html> + diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js index 9dbfcc099..a4d498fb8 100644 --- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js +++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js @@ -172,9 +172,9 @@ var interfaceNamesInGlobalScope = // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceMeasure", // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "PerformanceObserver", nightly: true }, + "PerformanceObserver", // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "PerformanceObserverEntryList", nightly: true }, + "PerformanceObserverEntryList", // IMPORTANT: Do not change this list without review from a DOM peer! "Request", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js index e0647682c..6fe5fcaff 100644 --- a/dom/workers/test/test_worker_interfaces.js +++ b/dom/workers/test/test_worker_interfaces.js @@ -163,9 +163,9 @@ var interfaceNamesInGlobalScope = // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceMeasure", // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "PerformanceObserver", nightly: true }, + "PerformanceObserver", // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "PerformanceObserverEntryList", nightly: true }, + "PerformanceObserverEntryList", // IMPORTANT: Do not change this list without review from a DOM peer! "Request", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/xslt/xpath/txXPCOMExtensionFunction.cpp b/dom/xslt/xpath/txXPCOMExtensionFunction.cpp index 4913702aa..032161722 100644 --- a/dom/xslt/xpath/txXPCOMExtensionFunction.cpp +++ b/dom/xslt/xpath/txXPCOMExtensionFunction.cpp @@ -322,7 +322,7 @@ public: void trace(JSTracer* trc) { for (uint8_t i = 0; i < mCount; ++i) { if (mArray[i].type == nsXPTType::T_JSVAL) { - JS::UnsafeTraceRoot(trc, &mArray[i].val.j, "txParam value"); + JS::UnsafeTraceRoot(trc, &mArray[i].val.j.asValueRef(), "txParam value"); } } } diff --git a/embedding/browser/nsIWebBrowserChrome3.idl b/embedding/browser/nsIWebBrowserChrome3.idl index a95cab911..d78a1d63b 100644 --- a/embedding/browser/nsIWebBrowserChrome3.idl +++ b/embedding/browser/nsIWebBrowserChrome3.idl @@ -8,6 +8,7 @@ interface nsIDocShell; interface nsIInputStream; +interface nsIPrincipal; /** * nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2. @@ -43,10 +44,13 @@ interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2 * The URI being loaded. * @param aReferrer * The referrer of the load. + * @param aTriggeringPrincipal + * The principal that initiated the load of aURI. */ bool shouldLoadURI(in nsIDocShell aDocShell, in nsIURI aURI, - in nsIURI aReferrer); + in nsIURI aReferrer, + in nsIPrincipal aTriggeringPrincipal); /** * Attempts to load the currently loaded page into a fresh process to increase @@ -57,5 +61,6 @@ interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2 */ bool reloadInFreshProcess(in nsIDocShell aDocShell, in nsIURI aURI, - in nsIURI aReferrer); + in nsIURI aReferrer, + in nsIPrincipal aTriggeringPrincipal); }; diff --git a/embedding/browser/nsWebBrowser.cpp b/embedding/browser/nsWebBrowser.cpp index 655aa1e43..c034fc03e 100644 --- a/embedding/browser/nsWebBrowser.cpp +++ b/embedding/browser/nsWebBrowser.cpp @@ -654,13 +654,14 @@ nsWebBrowser::LoadURIWithOptions(const char16_t* aURI, uint32_t aLoadFlags, uint32_t aReferrerPolicy, nsIInputStream* aPostDataStream, nsIInputStream* aExtraHeaderStream, - nsIURI* aBaseURI) + nsIURI* aBaseURI, + nsIPrincipal* aTriggeringPrincipal) { NS_ENSURE_STATE(mDocShell); return mDocShellAsNav->LoadURIWithOptions( aURI, aLoadFlags, aReferringURI, aReferrerPolicy, aPostDataStream, - aExtraHeaderStream, aBaseURI); + aExtraHeaderStream, aBaseURI, aTriggeringPrincipal); } NS_IMETHODIMP diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index 857ae5958..f54326360 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -1145,6 +1145,7 @@ APZCTreeManager::UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, case eMouseUp: case eMouseDown: case eMouseDoubleClick: + case eMouseAuxClick: case eMouseClick: case eContextMenu: case eDrop: diff --git a/js/public/Value.h b/js/public/Value.h index a40e65c83..01666ed4e 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -140,12 +140,16 @@ static_assert(sizeof(JSValueShiftedTag) == sizeof(uint64_t), #define JSVAL_TYPE_TO_TAG(type) ((JSValueTag)(JSVAL_TAG_CLEAR | (type))) +#define JSVAL_RAW64_UNDEFINED (uint64_t(JSVAL_TAG_UNDEFINED) << 32) + #define JSVAL_UPPER_EXCL_TAG_OF_PRIMITIVE_SET JSVAL_TAG_OBJECT #define JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET JSVAL_TAG_INT32 #define JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET JSVAL_TAG_STRING #elif defined(JS_PUNBOX64) +#define JSVAL_RAW64_UNDEFINED (uint64_t(JSVAL_TAG_UNDEFINED) << JSVAL_TAG_SHIFT) + #define JSVAL_PAYLOAD_MASK 0x00007FFFFFFFFFFFLL #define JSVAL_TAG_MASK 0xFFFF800000000000LL #define JSVAL_TYPE_TO_TAG(type) ((JSValueTag)(JSVAL_TAG_MAX_DOUBLE | (type))) @@ -817,7 +821,7 @@ class MOZ_NON_PARAM alignas(8) Value double asDouble; void* asPtr; - layout() = default; + layout() : asBits(JSVAL_RAW64_UNDEFINED) {} explicit constexpr layout(uint64_t bits) : asBits(bits) {} explicit constexpr layout(double d) : asDouble(d) {} } data; @@ -843,7 +847,7 @@ class MOZ_NON_PARAM alignas(8) Value size_t asWord; uintptr_t asUIntPtr; - layout() = default; + layout() : asBits(JSVAL_RAW64_UNDEFINED) {} explicit constexpr layout(uint64_t bits) : asBits(bits) {} explicit constexpr layout(double d) : asDouble(d) {} } data; @@ -871,7 +875,7 @@ class MOZ_NON_PARAM alignas(8) Value double asDouble; void* asPtr; - layout() = default; + layout() : asBits(JSVAL_RAW64_UNDEFINED) {} explicit constexpr layout(uint64_t bits) : asBits(bits) {} explicit constexpr layout(double d) : asDouble(d) {} } data; @@ -895,7 +899,7 @@ class MOZ_NON_PARAM alignas(8) Value size_t asWord; uintptr_t asUIntPtr; - layout() = default; + layout() : asBits(JSVAL_RAW64_UNDEFINED) {} explicit constexpr layout(uint64_t bits) : asBits(bits) {} explicit constexpr layout(double d) : asDouble(d) {} } data; @@ -948,8 +952,51 @@ class MOZ_NON_PARAM alignas(8) Value } } JS_HAZ_GC_POINTER; +/** + * This is a null-constructible structure that can convert to and from + * a Value, allowing UninitializedValue to be stored in unions. + */ +struct MOZ_NON_PARAM alignas(8) UninitializedValue +{ + private: + uint64_t bits; + + public: + UninitializedValue() = default; + UninitializedValue(const UninitializedValue&) = default; + MOZ_IMPLICIT UninitializedValue(const Value& val) : bits(val.asRawBits()) {} + + inline uint64_t asRawBits() const { + return bits; + } + + inline Value& asValueRef() { + return *reinterpret_cast<Value*>(this); + } + inline const Value& asValueRef() const { + return *reinterpret_cast<const Value*>(this); + } + + inline operator Value&() { + return asValueRef(); + } + inline operator Value const&() const { + return asValueRef(); + } + inline operator Value() const { + return asValueRef(); + } + + inline void operator=(Value const& other) { + asValueRef() = other; + } +}; + static_assert(sizeof(Value) == 8, "Value size must leave three tag bits, be a binary power, and is ubiquitously depended upon everywhere"); +static_assert(sizeof(UninitializedValue) == sizeof(Value), "Value and UninitializedValue must be the same size"); +static_assert(alignof(UninitializedValue) == alignof(Value), "Value and UninitializedValue must have same alignment"); + inline bool IsOptimizedPlaceholderMagicValue(const Value& v) { diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index 509168d7a..6391c3e70 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -9,7 +9,8 @@ JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false, JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false, JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false, - JSMSG_DATE_NOT_FINITE: false, + JSMSG_DATE_NOT_FINITE: false, JSMSG_INVALID_KEYS_TYPE: false, + JSMSG_INVALID_KEY: false, intl_Collator_availableLocales: false, intl_availableCollations: false, intl_CompareStrings: false, @@ -3024,3 +3025,126 @@ function Intl_getCalendarInfo(locales) { return result; } + +/** + * This function is a custom method designed after Intl API, but currently + * not part of the spec or spec proposal. + * We want to use it internally to retrieve translated values from CLDR in + * order to ensure they're aligned with what Intl API returns. + * + * This API may one day be a foundation for an ECMA402 API spec proposal. + * + * The function takes two arguments - locales which is a list of locale strings + * and options which is an object with two optional properties: + * + * keys: + * an Array of string values that are paths to individual terms + * + * style: + * a String with a value "long", "short" or "narrow" + * + * It returns an object with properties: + * + * locale: + * a negotiated locale string + * + * style: + * negotiated style + * + * values: + * A key-value pair list of requested keys and corresponding + * translated values + * + */ +function Intl_getDisplayNames(locales, options) { + // 1. Let requestLocales be ? CanonicalizeLocaleList(locales). + const requestedLocales = CanonicalizeLocaleList(locales); + + // 2. If options is undefined, then + if (options === undefined) + // a. Let options be ObjectCreate(%ObjectPrototype%). + options = {}; + // 3. Else, + else + // a. Let options be ? ToObject(options). + options = ToObject(options); + + const DateTimeFormat = dateTimeFormatInternalProperties; + + // 4. Let localeData be %DateTimeFormat%.[[localeData]]. + const localeData = DateTimeFormat.localeData; + + // 5. Let opt be a new Record. + const localeOpt = new Record(); + // 6. Set localeOpt.[[localeMatcher]] to "best fit". + localeOpt.localeMatcher = "best fit"; + + // 7. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], requestedLocales, localeOpt, + // %DateTimeFormat%.[[relevantExtensionKeys]], localeData). + const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), + requestedLocales, + localeOpt, + DateTimeFormat.relevantExtensionKeys, + localeData); + + // 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long"). + const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long"); + // 9. Let keys be ? Get(options, "keys"). + let keys = options.keys; + + // 10. If keys is undefined, + if (keys === undefined) { + // a. Let keys be ArrayCreate(0). + keys = []; + } else if (!IsObject(keys)) { + // 11. Else, + // a. If Type(keys) is not Object, throw a TypeError exception. + ThrowTypeError(JSMSG_INVALID_KEYS_TYPE); + } + + // 12. Let processedKeys be ArrayCreate(0). + // (This really should be a List, but we use an Array here in order that + // |intl_ComputeDisplayNames| may infallibly access the list's length via + // |ArrayObject::length|.) + let processedKeys = []; + // 13. Let len be ? ToLength(? Get(keys, "length")). + let len = ToLength(keys.length); + // 14. Let i be 0. + // 15. Repeat, while i < len + for (let i = 0; i < len; i++) { + // a. Let processedKey be ? ToString(? Get(keys, i)). + // b. Perform ? CreateDataPropertyOrThrow(processedKeys, i, processedKey). + callFunction(std_Array_push, processedKeys, ToString(keys[i])); + } + + // 16. Let names be ? ComputeDisplayNames(r.[[locale]], style, processedKeys). + const names = intl_ComputeDisplayNames(r.locale, style, processedKeys); + + // 17. Let values be ObjectCreate(%ObjectPrototype%). + const values = {}; + + // 18. Set i to 0. + // 19. Repeat, while i < len + for (let i = 0; i < len; i++) { + // a. Let key be ? Get(processedKeys, i). + const key = processedKeys[i]; + // b. Let name be ? Get(names, i). + const name = names[i]; + // c. Assert: Type(name) is string. + assert(typeof name === "string", "unexpected non-string value"); + // d. Assert: the length of name is greater than zero. + assert(name.length > 0, "empty string value"); + // e. Perform ? DefinePropertyOrThrow(values, key, name). + _DefineDataProperty(values, key, name); + } + + // 20. Let options be ObjectCreate(%ObjectPrototype%). + // 21. Perform ! DefinePropertyOrThrow(result, "locale", r.[[locale]]). + // 22. Perform ! DefinePropertyOrThrow(result, "style", style). + // 23. Perform ! DefinePropertyOrThrow(result, "values", values). + const result = { locale: r.locale, style, values }; + + // 24. Return result. + return result; + +} diff --git a/js/src/jit/BaselineFrameInfo.h b/js/src/jit/BaselineFrameInfo.h index 13bf0358d..1691270ac 100644 --- a/js/src/jit/BaselineFrameInfo.h +++ b/js/src/jit/BaselineFrameInfo.h @@ -67,7 +67,7 @@ class StackValue union { struct { - Value v; + JS::UninitializedValue v; } constant; struct { mozilla::AlignedStorage2<ValueOperand> reg; @@ -112,7 +112,7 @@ class StackValue } Value constant() const { MOZ_ASSERT(kind_ == Constant); - return data.constant.v; + return data.constant.v.asValueRef(); } ValueOperand reg() const { MOZ_ASSERT(kind_ == Register); diff --git a/js/src/jit/RegisterSets.h b/js/src/jit/RegisterSets.h index 0a4045dd7..08ae53f16 100644 --- a/js/src/jit/RegisterSets.h +++ b/js/src/jit/RegisterSets.h @@ -226,13 +226,13 @@ class ConstantOrRegister // Space to hold either a Value or a TypedOrValueRegister. union U { - Value constant; + JS::UninitializedValue constant; TypedOrValueRegister reg; } data; - const Value& dataValue() const { + Value dataValue() const { MOZ_ASSERT(constant()); - return data.constant; + return data.constant.asValueRef(); } void setDataValue(const Value& value) { MOZ_ASSERT(constant()); @@ -268,7 +268,7 @@ class ConstantOrRegister return constant_; } - const Value& value() const { + Value value() const { return dataValue(); } diff --git a/js/src/jit/RematerializedFrame.cpp b/js/src/jit/RematerializedFrame.cpp index cb324220c..32fad1267 100644 --- a/js/src/jit/RematerializedFrame.cpp +++ b/js/src/jit/RematerializedFrame.cpp @@ -61,9 +61,17 @@ RematerializedFrame::New(JSContext* cx, uint8_t* top, InlineFrameIterator& iter, { unsigned numFormals = iter.isFunctionFrame() ? iter.calleeTemplate()->nargs() : 0; unsigned argSlots = Max(numFormals, iter.numActualArgs()); - size_t numBytes = sizeof(RematerializedFrame) + - (argSlots + iter.script()->nfixed()) * sizeof(Value) - - sizeof(Value); // 1 Value included in sizeof(RematerializedFrame) + unsigned extraSlots = argSlots + iter.script()->nfixed(); + + // One Value slot is included in sizeof(RematerializedFrame), so we can + // reduce the extra slot count by one. However, if there are zero slot + // allocations total, then reducing the slots by one will lead to + // the memory allocation being smaller than sizeof(RematerializedFrame). + if (extraSlots > 0) + extraSlots -= 1; + + size_t numBytes = sizeof(RematerializedFrame) + (extraSlots * sizeof(Value)); + MOZ_ASSERT(numBytes >= sizeof(RematerializedFrame)); void* buf = cx->pod_calloc<uint8_t>(numBytes); if (!buf) diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 3b92aa21b..7e9621d4a 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -9,6 +9,7 @@ #include "mozilla/HashFunctions.h" #include "mozilla/PodOperations.h" +#include "mozilla/TextUtils.h" #include <stdio.h> @@ -95,7 +96,7 @@ struct JSSubString { #define JS7_UNOCT(c) (JS7_UNDEC(c)) #define JS7_ISHEX(c) ((c) < 128 && isxdigit(c)) #define JS7_UNHEX(c) (unsigned)(JS7_ISDEC(c) ? (c) - '0' : 10 + tolower(c) - 'a') -#define JS7_ISLET(c) ((c) < 128 && isalpha(c)) +#define JS7_ISLET(c) (mozilla::IsAsciiAlpha(c)) extern size_t js_strlen(const char16_t* s); diff --git a/js/src/old-configure.in b/js/src/old-configure.in index 7432ab9e2..162a071d7 100644 --- a/js/src/old-configure.in +++ b/js/src/old-configure.in @@ -1534,6 +1534,14 @@ MOZ_ARG_ENABLE_STRING(ui-locale, AC_SUBST(MOZ_UI_LOCALE) dnl ======================================================== +dnl Build the tests? +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(tests, +[ --enable-tests Build test libraries & programs], + ENABLE_TESTS=1, + ENABLE_TESTS= ) + +dnl ======================================================== dnl = dnl = Module specific options dnl = @@ -2091,6 +2099,8 @@ AC_SUBST(MOZ_DEBUG_LDFLAGS) AC_SUBST(WARNINGS_AS_ERRORS) AC_SUBST(LIBICONV) +AC_SUBST(ENABLE_TESTS) + AC_SUBST(ENABLE_STRIP) AC_SUBST(PKG_SKIP_STRIP) AC_SUBST(INCREMENTAL_LINKER) diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index b4f41c3d5..7fade24fb 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -857,7 +857,7 @@ class NumLit private: Which which_; union { - Value scalar_; + JS::UninitializedValue scalar_; SimdConstant simd_; } u; @@ -880,7 +880,7 @@ class NumLit int32_t toInt32() const { MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt || which_ == BigUnsigned); - return u.scalar_.toInt32(); + return u.scalar_.asValueRef().toInt32(); } uint32_t toUint32() const { @@ -889,17 +889,17 @@ class NumLit RawF64 toDouble() const { MOZ_ASSERT(which_ == Double); - return RawF64(u.scalar_.toDouble()); + return RawF64(u.scalar_.asValueRef().toDouble()); } RawF32 toFloat() const { MOZ_ASSERT(which_ == Float); - return RawF32(float(u.scalar_.toDouble())); + return RawF32(float(u.scalar_.asValueRef().toDouble())); } Value scalarValue() const { MOZ_ASSERT(which_ != OutOfRangeInt); - return u.scalar_; + return u.scalar_.asValueRef(); } bool isSimd() const diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index acf92f3c3..a12e36baa 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -1785,9 +1785,12 @@ CallMethodHelper::ConvertIndependentParam(uint8_t i) // indirectly, regardless of in/out-ness. if (type_tag == nsXPTType::T_JSVAL) { // Root the value. - dp->val.j.setUndefined(); - if (!js::AddRawValueRoot(mCallContext, &dp->val.j, "XPCWrappedNative::CallMethod param")) + dp->val.j.asValueRef().setUndefined(); + if (!js::AddRawValueRoot(mCallContext, &dp->val.j.asValueRef(), + "XPCWrappedNative::CallMethod param")) + { return false; + } } // Flag cleanup for anything that isn't self-contained. diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index c8c91b251..9d8dd81bf 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -6653,8 +6653,9 @@ nsLayoutUtils::DrawSingleImage(gfxContext& aContext, nscoord appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel(); CSSIntSize pixelImageSize(ComputeSizeForDrawingWithFallback(aImage, aDest.Size())); if (pixelImageSize.width < 1 || pixelImageSize.height < 1) { - NS_WARNING("Image width or height is non-positive"); - return DrawResult::TEMPORARY_ERROR; + NS_ASSERTION(pixelImageSize.width >= 0 && pixelImageSize.height >= 0, + "Image width or height is negative"); + return DrawResult::SUCCESS; // no point in drawing a zero size image } nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize)); @@ -7013,7 +7014,8 @@ nsLayoutUtils::GetTextRunFlagsForStyle(nsStyleContext* aStyleContext, nscoord aLetterSpacing) { uint32_t result = 0; - if (aLetterSpacing != 0) { + if (aLetterSpacing != 0 || + aStyleText->mTextJustify == StyleTextJustify::InterCharacter) { result |= gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES; } if (aStyleText->mControlCharacterVisibility == NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN) { diff --git a/layout/generic/Selection.h b/layout/generic/Selection.h index 3d5e334fc..5414d15c1 100644 --- a/layout/generic/Selection.h +++ b/layout/generic/Selection.h @@ -51,8 +51,9 @@ struct RangeData namespace mozilla { namespace dom { -class Selection final : public nsISelectionPrivate, +class Selection final : public nsISelection, public nsWrapperCache, + public nsISelectionPrivate, public nsSupportsWeakReference { protected: @@ -63,7 +64,7 @@ public: explicit Selection(nsFrameSelection *aList); NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Selection, nsISelectionPrivate) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Selection, nsISelection) NS_DECL_NSISELECTION NS_DECL_NSISELECTIONPRIVATE diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 00c0016fd..fa31443fd 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -2936,22 +2936,40 @@ nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag, return offsets; } -static bool IsJustifiableCharacter(const nsTextFragment* aFrag, int32_t aPos, +static bool IsJustifiableCharacter(const nsStyleText* aTextStyle, + const nsTextFragment* aFrag, int32_t aPos, bool aLangIsCJ) { NS_ASSERTION(aPos >= 0, "negative position?!"); + + StyleTextJustify justifyStyle = aTextStyle->mTextJustify; + if (justifyStyle == StyleTextJustify::None) { + return false; + } + char16_t ch = aFrag->CharAt(aPos); - if (ch == '\n' || ch == '\t' || ch == '\r') + if (ch == '\n' || ch == '\t' || ch == '\r') { return true; + } if (ch == ' ' || ch == CH_NBSP) { // Don't justify spaces that are combined with diacriticals - if (!aFrag->Is2b()) + if (!aFrag->Is2b()) { return true; + } return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( - aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); + aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); } - if (ch < 0x2150u) + + if (justifyStyle == StyleTextJustify::InterCharacter) { + return true; + } else if (justifyStyle == StyleTextJustify::InterWord) { + return false; + } + + // text-justify: auto + if (ch < 0x2150u) { return false; + } if (aLangIsCJ) { if ((0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics @@ -3279,7 +3297,7 @@ PropertyProvider::ComputeJustification( gfxSkipCharsIterator iter = run.GetPos(); for (uint32_t i = 0; i < length; ++i) { uint32_t offset = originalOffset + i; - if (!IsJustifiableCharacter(mFrag, offset, isCJ)) { + if (!IsJustifiableCharacter(mTextStyle, mFrag, offset, isCJ)) { continue; } diff --git a/layout/reftests/transform-3d/animate-backface-hidden.html b/layout/reftests/transform-3d/animate-backface-hidden.html index ce957bf73..27b587006 100644 --- a/layout/reftests/transform-3d/animate-backface-hidden.html +++ b/layout/reftests/transform-3d/animate-backface-hidden.html @@ -17,7 +17,7 @@ body { padding: 50px } height: 200px; width: 200px; backface-visibility: hidden; /* use a -99.9s delay to start at 99.9% and then move to 0% */ - animation: flip 100s -99.9s linear 2; + animation: flip 100s -99.9s linear 2 paused; } </style> @@ -27,7 +27,13 @@ body { padding: 50px } <script> -document.getElementById("test").addEventListener("animationiteration", IterationListener, false); +document.getElementById("test").addEventListener("animationstart", StartListener, false); + +function StartListener(event) { + var test = document.getElementById("test"); + test.style.animationPlayState = 'running'; + test.addEventListener("animationiteration", IterationListener, false); +} function IterationListener(event) { setTimeout(RemoveReftestWait, 0); diff --git a/layout/reftests/transform-3d/animate-preserve3d-parent.html b/layout/reftests/transform-3d/animate-preserve3d-parent.html index ae3fec196..d05beac6f 100644 --- a/layout/reftests/transform-3d/animate-preserve3d-parent.html +++ b/layout/reftests/transform-3d/animate-preserve3d-parent.html @@ -18,7 +18,7 @@ body { padding: 50px } border: 1px solid black; transform-style: preserve-3d; /* use a -99.9s delay to start at 99.9% and then move to 0% */ - animation: spin 100s -99.9s linear 2; + animation: spin 100s -99.9s linear 2 paused; } #child { @@ -39,7 +39,13 @@ body { padding: 50px } <script> -document.getElementById("parent").addEventListener("animationiteration", IterationListener, false); +document.getElementById("parent").addEventListener("animationstart", StartListener, false); + +function StartListener(event) { + var test = document.getElementById("parent"); + test.style.animationPlayState = 'running'; + test.addEventListener("animationiteration", IterationListener, false); +} function IterationListener(event) { setTimeout(RemoveReftestWait, 0); diff --git a/layout/reftests/w3c-css/submitted/check-for-references.sh b/layout/reftests/w3c-css/submitted/check-for-references.sh index 977cee3f4..c30e18515 100755 --- a/layout/reftests/w3c-css/submitted/check-for-references.sh +++ b/layout/reftests/w3c-css/submitted/check-for-references.sh @@ -15,7 +15,7 @@ do else echo "Unexpected type $TYPE for $DIRNAME/$TEST" fi - if grep "rel=\"$REFTYPE\"" "$DIRNAME/$TEST" | head -1 | grep -q "href=\"$REF\"" + if grep "rel=\(\"$REFTYPE\"\|'$REFTYPE'\)" "$DIRNAME/$TEST" | head -1 | grep -q "href=\(\"$REF\"\|'$REF'\)" then #echo "Good link for $DIRNAME/$TEST" echo -n diff --git a/layout/reftests/w3c-css/submitted/text3/reftest.list b/layout/reftests/w3c-css/submitted/text3/reftest.list index 2712e4363..a1e3d70d0 100644 --- a/layout/reftests/w3c-css/submitted/text3/reftest.list +++ b/layout/reftests/w3c-css/submitted/text3/reftest.list @@ -5,4 +5,9 @@ == text-align-match-parent-root-ltr.html text-align-match-parent-root-ltr-ref.html == text-align-match-parent-root-rtl.html text-align-match-parent-root-rtl-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-none-001.html text-justify-none-001-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-inter-word-001.html text-justify-inter-word-001-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-inter-character-001.html text-justify-inter-character-001-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-distribute-001.html text-justify-inter-character-001-ref.html + == text-word-spacing-001.html text-word-spacing-ref.html diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-distribute-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-distribute-001.html new file mode 100644 index 000000000..4d29f0fee --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-distribute-001.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: distribute</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-inter-character-001-ref.html'> +<meta name="assert" content="text-justify:distribute means justification adjusts spacing between each pair of adjacent typographic character units."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: distribute; +} +</style> +</head> +<body> +<p lang="en" class="test">XX</p> +<p lang="ja" class="test">文字</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001-ref.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001-ref.html new file mode 100644 index 000000000..0a42a5cf8 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001-ref.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-character</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.right { + float: right; +} +</style> +</head> +<body> +<p lang="en">X<span class="right">X</span></p> +<p lang="ja">文<span class="right">字</span></p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001.html new file mode 100644 index 000000000..639ab7ecb --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-character</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-inter-character-001-ref.html'> +<meta name="assert" content="text-justify:inter-character means justification adjusts spacing between each pair of adjacent typographic character units."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: inter-character; +} +</style> +</head> +<body> +<p lang="en" class="test">XX</p> +<p lang="ja" class="test">文字</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001-ref.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001-ref.html new file mode 100644 index 000000000..687e864e7 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001-ref.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-word</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.right { + float: right; +} +</style> +</head> +<body> +<p lang="en">Latin<span class="right">text</span></p> +<p lang="ja">日本<span class="right">文字</span></p> +<p lang="th">อักษรไทย<span class="right">อักษรไทย</span></p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001.html new file mode 100644 index 000000000..75aec2f5f --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-word</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-inter-word-001-ref.html'> +<meta name="assert" content="text-justify:inter-word means justification adjusts spacing at word separators only."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: inter-word; +} +</style> +</head> +<body> +<p lang="en" class="test">Latin text</p> +<p lang="ja" class="test">日本 文字</p> +<p lang="th" class="test">อักษรไทย อักษรไทย</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-none-001-ref.html b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001-ref.html new file mode 100644 index 000000000..c8500ac9f --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001-ref.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: none</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +</style> +</head> +<body> +<p lang="en">Latin text</p> +<p lang="ja">日本 文字</p> +<p lang="th">อักษรไทย อักษรไทย</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-none-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001.html new file mode 100644 index 000000000..2b5511195 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: none</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-none-001-ref.html'> +<meta name="assert" content="text-justify:none means justification is disabled: there are no justification opportunities within the text."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: none; +} +</style> +</head> +<body> +<p lang="en" class="test">Latin text</p> +<p lang="ja" class="test">日本 文字</p> +<p lang="th" class="test">อักษรไทย อักษรไทย</p> +</body> +</html> diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h index 37030411c..025c034a4 100644 --- a/layout/style/AnimationCommon.h +++ b/layout/style/AnimationCommon.h @@ -251,6 +251,26 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, aField.Traverse(&aCallback, aName); } +// Return the TransitionPhase or AnimationPhase to use when the animation +// doesn't have a target effect. +template <typename PhaseType> +PhaseType GetAnimationPhaseWithoutEffect(const dom::Animation& aAnimation) +{ + MOZ_ASSERT(!aAnimation.GetEffect(), + "Should only be called when we do not have an effect"); + + Nullable<TimeDuration> currentTime = aAnimation.GetCurrentTime(); + if (currentTime.IsNull()) { + return PhaseType::Idle; + } + + // If we don't have a target effect, the duration will be zero so the phase is + // 'before' if the current time is less than zero. + return currentTime.Value() < TimeDuration() + ? PhaseType::Before + : PhaseType::After; +}; + } // namespace mozilla #endif /* !defined(mozilla_css_AnimationCommon_h) */ diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp index ed2b5afc7..aa1b6fe78 100644 --- a/layout/style/nsAnimationManager.cpp +++ b/layout/style/nsAnimationManager.cpp @@ -33,11 +33,15 @@ using mozilla::dom::AnimationPlayState; using mozilla::dom::KeyframeEffectReadOnly; using mozilla::dom::CSSAnimation; +typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase; + namespace { -// Pair of an event message and elapsed time used when determining the set of -// events to queue. -typedef Pair<EventMessage, StickyTimeDuration> EventPair; +struct AnimationEventParams { + EventMessage mMessage; + StickyTimeDuration mElapsedTime; + TimeStamp mTimeStamp; +}; } // anonymous namespace @@ -154,12 +158,8 @@ CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const } void -CSSAnimation::QueueEvents() +CSSAnimation::QueueEvents(StickyTimeDuration aActiveTime) { - if (!mEffect) { - return; - } - // If the animation is pending, we ignore animation events until we finish // pending. if (mPendingState != PendingState::NotPending) { @@ -194,77 +194,116 @@ CSSAnimation::QueueEvents() } nsAnimationManager* manager = presContext->AnimationManager(); - ComputedTiming computedTiming = mEffect->GetComputedTiming(); - if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Null) { - return; // do nothing + const StickyTimeDuration zeroDuration; + uint64_t currentIteration = 0; + ComputedTiming::AnimationPhase currentPhase; + StickyTimeDuration intervalStartTime; + StickyTimeDuration intervalEndTime; + StickyTimeDuration iterationStartTime; + + if (!mEffect) { + currentPhase = GetAnimationPhaseWithoutEffect + <ComputedTiming::AnimationPhase>(*this); + } else { + ComputedTiming computedTiming = mEffect->GetComputedTiming(); + currentPhase = computedTiming.mPhase; + currentIteration = computedTiming.mCurrentIteration; + if (currentPhase == mPreviousPhase && + currentIteration == mPreviousIteration) { + return; + } + intervalStartTime = + std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay), + computedTiming.mActiveDuration), + zeroDuration); + intervalEndTime = + std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay), + computedTiming.mActiveDuration), + zeroDuration); + + uint64_t iterationBoundary = mPreviousIteration > currentIteration + ? currentIteration + 1 + : currentIteration; + iterationStartTime = + computedTiming.mDuration.MultDouble( + (iterationBoundary - computedTiming.mIterationStart)); } - // Note that script can change the start time, so we have to handle moving - // backwards through the animation as well as forwards. An 'animationstart' - // is dispatched if we enter the active phase (regardless if that is from - // before or after the animation's active phase). An 'animationend' is - // dispatched if we leave the active phase (regardless if that is to before - // or after the animation's active phase). - - bool wasActive = mPreviousPhaseOrIteration != PREVIOUS_PHASE_BEFORE && - mPreviousPhaseOrIteration != PREVIOUS_PHASE_AFTER; - bool isActive = - computedTiming.mPhase == ComputedTiming::AnimationPhase::Active; - bool isSameIteration = - computedTiming.mCurrentIteration == mPreviousPhaseOrIteration; - bool skippedActivePhase = - (mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE && - computedTiming.mPhase == ComputedTiming::AnimationPhase::After) || - (mPreviousPhaseOrIteration == PREVIOUS_PHASE_AFTER && - computedTiming.mPhase == ComputedTiming::AnimationPhase::Before); - bool skippedFirstIteration = - isActive && - mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE && - computedTiming.mCurrentIteration > 0; - - MOZ_ASSERT(!skippedActivePhase || (!isActive && !wasActive), - "skippedActivePhase only makes sense if we were & are inactive"); - - if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Before) { - mPreviousPhaseOrIteration = PREVIOUS_PHASE_BEFORE; - } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Active) { - mPreviousPhaseOrIteration = computedTiming.mCurrentIteration; - } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::After) { - mPreviousPhaseOrIteration = PREVIOUS_PHASE_AFTER; + TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime); + TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime); + TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime); + + AutoTArray<AnimationEventParams, 2> events; + + // Handle cancel event first + if ((mPreviousPhase != AnimationPhase::Idle && + mPreviousPhase != AnimationPhase::After) && + currentPhase == AnimationPhase::Idle) { + TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime); + events.AppendElement(AnimationEventParams{ eAnimationCancel, + aActiveTime, + activeTimeStamp }); } - AutoTArray<EventPair, 2> events; - StickyTimeDuration initialAdvance = StickyTimeDuration(InitialAdvance()); - StickyTimeDuration iterationStart = computedTiming.mDuration * - computedTiming.mCurrentIteration; - const StickyTimeDuration& activeDuration = computedTiming.mActiveDuration; - - if (skippedFirstIteration) { - // Notify animationstart and animationiteration in same tick. - events.AppendElement(EventPair(eAnimationStart, initialAdvance)); - events.AppendElement(EventPair(eAnimationIteration, - std::max(iterationStart, initialAdvance))); - } else if (!wasActive && isActive) { - events.AppendElement(EventPair(eAnimationStart, initialAdvance)); - } else if (wasActive && !isActive) { - events.AppendElement(EventPair(eAnimationEnd, activeDuration)); - } else if (wasActive && isActive && !isSameIteration) { - events.AppendElement(EventPair(eAnimationIteration, iterationStart)); - } else if (skippedActivePhase) { - events.AppendElement(EventPair(eAnimationStart, - std::min(initialAdvance, activeDuration))); - events.AppendElement(EventPair(eAnimationEnd, activeDuration)); - } else { - return; // No events need to be sent + switch (mPreviousPhase) { + case AnimationPhase::Idle: + case AnimationPhase::Before: + if (currentPhase == AnimationPhase::Active) { + events.AppendElement(AnimationEventParams{ eAnimationStart, + intervalStartTime, + startTimeStamp }); + } else if (currentPhase == AnimationPhase::After) { + events.AppendElement(AnimationEventParams{ eAnimationStart, + intervalStartTime, + startTimeStamp }); + events.AppendElement(AnimationEventParams{ eAnimationEnd, + intervalEndTime, + endTimeStamp }); + } + break; + case AnimationPhase::Active: + if (currentPhase == AnimationPhase::Before) { + events.AppendElement(AnimationEventParams{ eAnimationEnd, + intervalStartTime, + startTimeStamp }); + } else if (currentPhase == AnimationPhase::Active) { + // The currentIteration must have changed or element we would have + // returned early above. + MOZ_ASSERT(currentIteration != mPreviousIteration); + events.AppendElement(AnimationEventParams{ eAnimationIteration, + iterationStartTime, + iterationTimeStamp }); + } else if (currentPhase == AnimationPhase::After) { + events.AppendElement(AnimationEventParams{ eAnimationEnd, + intervalEndTime, + endTimeStamp }); + } + break; + case AnimationPhase::After: + if (currentPhase == AnimationPhase::Before) { + events.AppendElement(AnimationEventParams{ eAnimationStart, + intervalEndTime, + startTimeStamp}); + events.AppendElement(AnimationEventParams{ eAnimationEnd, + intervalStartTime, + endTimeStamp }); + } else if (currentPhase == AnimationPhase::Active) { + events.AppendElement(AnimationEventParams{ eAnimationStart, + intervalEndTime, + endTimeStamp }); + } + break; } - for (const EventPair& pair : events){ + mPreviousPhase = currentPhase; + mPreviousIteration = currentIteration; + + for (const AnimationEventParams& event : events){ manager->QueueEvent( AnimationEventInfo(owningElement, owningPseudoType, - pair.first(), mAnimationName, - pair.second(), - ElapsedTimeToTimeStamp(pair.second()), + event.mMessage, mAnimationName, + event.mElapsedTime, event.mTimeStamp, this)); } } diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h index abe3aeeb8..d838d090a 100644 --- a/layout/style/nsAnimationManager.h +++ b/layout/style/nsAnimationManager.h @@ -76,7 +76,8 @@ public: , mIsStylePaused(false) , mPauseShouldStick(false) , mNeedsNewAnimationIndexWhenRun(false) - , mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE) + , mPreviousPhase(ComputedTiming::AnimationPhase::Idle) + , mPreviousIteration(0) { // We might need to drop this assertion once we add a script-accessible // constructor but for animations generated from CSS markup the @@ -109,8 +110,6 @@ public: void PauseFromStyle(); void CancelFromStyle() override { - mOwningElement = OwningElementRef(); - // When an animation is disassociated with style it enters an odd state // where its composite order is undefined until it first transitions // out of the idle state. @@ -125,10 +124,15 @@ public: mNeedsNewAnimationIndexWhenRun = true; Animation::CancelFromStyle(); + + // We need to do this *after* calling CancelFromStyle() since + // CancelFromStyle might synchronously trigger a cancel event for which + // we need an owning element to target the event at. + mOwningElement = OwningElementRef(); } void Tick() override; - void QueueEvents(); + void QueueEvents(StickyTimeDuration aActiveTime = StickyTimeDuration()); bool IsStylePaused() const { return mIsStylePaused; } @@ -157,6 +161,10 @@ public: // reflect changes to that markup. bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } + void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override { + QueueEvents(aActiveTime); + } + protected: virtual ~CSSAnimation() { @@ -257,13 +265,10 @@ protected: // its animation index should be updated. bool mNeedsNewAnimationIndexWhenRun; - enum { - PREVIOUS_PHASE_BEFORE = uint64_t(-1), - PREVIOUS_PHASE_AFTER = uint64_t(-2) - }; - // One of the PREVIOUS_PHASE_* constants, or an integer for the iteration - // whose start we last notified on. - uint64_t mPreviousPhaseOrIteration; + // Phase and current iteration from the previous time we queued events. + // This is used to determine what new events to dispatch. + ComputedTiming::AnimationPhase mPreviousPhase; + uint64_t mPreviousIteration; }; } /* namespace dom */ diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 933ff6e7b..94968faca 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -238,6 +238,7 @@ CSS_KEY(disc, disc) CSS_KEY(disclosure-closed, disclosure_closed) CSS_KEY(disclosure-open, disclosure_open) CSS_KEY(discretionary-ligatures, discretionary_ligatures) +CSS_KEY(distribute, distribute) CSS_KEY(dot, dot) CSS_KEY(dotted, dotted) CSS_KEY(double, double) @@ -333,7 +334,8 @@ CSS_KEY(inline-start, inline_start) CSS_KEY(inline-table, inline_table) CSS_KEY(inset, inset) CSS_KEY(inside, inside) -// CSS_KEY(inter-character, inter_character) // TODO see bug 1055672 +CSS_KEY(inter-character, inter_character) +CSS_KEY(inter-word, inter_word) CSS_KEY(interpolatematrix, interpolatematrix) CSS_KEY(intersect, intersect) CSS_KEY(isolate, isolate) diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 6931d8c2b..b04921dcb 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -4027,6 +4027,17 @@ CSS_PROP_TEXT( nullptr, offsetof(nsStyleText, mTextIndent), eStyleAnimType_Coord) +CSS_PROP_TEXT( + text-justify, + text_justify, + TextJustify, + CSS_PROPERTY_PARSE_VALUE | + CSS_PROPERTY_APPLIES_TO_PLACEHOLDER, + "layout.css.text-justify.enabled", + VARIANT_HK, + kTextJustifyKTable, + CSS_PROP_NO_OFFSET, + eStyleAnimType_Discrete) CSS_PROP_VISIBILITY( text-orientation, text_orientation, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index f3a7f898d..9805eae14 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -2035,6 +2035,17 @@ KTableEntry nsCSSProps::kTextAlignLastKTable[] = { { eCSSKeyword_UNKNOWN, -1 } }; +const KTableEntry nsCSSProps::kTextJustifyKTable[] = { + { eCSSKeyword_none, StyleTextJustify::None }, + { eCSSKeyword_auto, StyleTextJustify::Auto }, + { eCSSKeyword_inter_word, StyleTextJustify::InterWord }, + { eCSSKeyword_inter_character, StyleTextJustify::InterCharacter }, + // For legacy reasons, UAs must also support the keyword "distribute" with + // the exact same meaning and behavior as "inter-character". + { eCSSKeyword_distribute, StyleTextJustify::InterCharacter }, + { eCSSKeyword_UNKNOWN, -1 } +}; + const KTableEntry nsCSSProps::kTextCombineUprightKTable[] = { { eCSSKeyword_none, NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE }, { eCSSKeyword_all, NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL }, diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index ab78e6174..dfe35afd8 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -869,6 +869,7 @@ public: static const KTableEntry kTextEmphasisPositionKTable[]; static const KTableEntry kTextEmphasisStyleFillKTable[]; static const KTableEntry kTextEmphasisStyleShapeKTable[]; + static const KTableEntry kTextJustifyKTable[]; static const KTableEntry kTextOrientationKTable[]; static const KTableEntry kTextOverflowKTable[]; static const KTableEntry kTextTransformKTable[]; diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 4eb24b76b..4f8d3edf6 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3875,6 +3875,16 @@ nsComputedDOMStyle::DoGetTextIndent() } already_AddRefed<CSSValue> +nsComputedDOMStyle::DoGetTextJustify() +{ + RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; + val->SetIdent( + nsCSSProps::ValueToKeywordEnum(StyleText()->mTextJustify, + nsCSSProps::kTextJustifyKTable)); + return val.forget(); +} + +already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetTextOrientation() { RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue; diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index 223b29a14..27e2086e9 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -421,6 +421,7 @@ private: already_AddRefed<CSSValue> DoGetTextEmphasisPosition(); already_AddRefed<CSSValue> DoGetTextEmphasisStyle(); already_AddRefed<CSSValue> DoGetTextIndent(); + already_AddRefed<CSSValue> DoGetTextJustify(); already_AddRefed<CSSValue> DoGetTextOrientation(); already_AddRefed<CSSValue> DoGetTextOverflow(); already_AddRefed<CSSValue> DoGetTextTransform(); diff --git a/layout/style/nsComputedDOMStylePropertyList.h b/layout/style/nsComputedDOMStylePropertyList.h index 7c0457e34..1983208ac 100644 --- a/layout/style/nsComputedDOMStylePropertyList.h +++ b/layout/style/nsComputedDOMStylePropertyList.h @@ -239,6 +239,7 @@ COMPUTED_STYLE_PROP(text_emphasis_color, TextEmphasisColor) COMPUTED_STYLE_PROP(text_emphasis_position, TextEmphasisPosition) COMPUTED_STYLE_PROP(text_emphasis_style, TextEmphasisStyle) COMPUTED_STYLE_PROP(text_indent, TextIndent) +COMPUTED_STYLE_PROP(text_justify, TextJustify) COMPUTED_STYLE_PROP(text_orientation, TextOrientation) COMPUTED_STYLE_PROP(text_overflow, TextOverflow) COMPUTED_STYLE_PROP(text_shadow, TextShadow) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index fa29fe0f1..9b9fc3948 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1414,6 +1414,7 @@ struct SetEnumValueHelper DEFINE_ENUM_CLASS_SETTER(StyleFillRule, Nonzero, Evenodd) DEFINE_ENUM_CLASS_SETTER(StyleFloat, None, InlineEnd) DEFINE_ENUM_CLASS_SETTER(StyleFloatEdge, ContentBox, MarginBox) + DEFINE_ENUM_CLASS_SETTER(StyleTextJustify, None, InterCharacter) DEFINE_ENUM_CLASS_SETTER(StyleUserFocus, None, SelectMenu) DEFINE_ENUM_CLASS_SETTER(StyleUserSelect, None, MozText) DEFINE_ENUM_CLASS_SETTER(StyleUserInput, None, Auto) @@ -4783,6 +4784,12 @@ nsRuleNode::ComputeTextData(void* aStartStruct, SETCOORD_UNSET_INHERIT, aContext, mPresContext, conditions); + // text-justify: enum, inherit, initial + SetValue(*aRuleData->ValueForTextJustify(), text->mTextJustify, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mTextJustify, + StyleTextJustify::Auto); + // text-transform: enum, inherit, initial SetValue(*aRuleData->ValueForTextTransform(), text->mTextTransform, conditions, SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index ee78dcb64..be588113e 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -185,6 +185,14 @@ enum class StyleShapeSourceType : uint8_t { Box, }; +// text-justify +enum class StyleTextJustify : uint8_t { + None, + Auto, + InterWord, + InterCharacter, +}; + // user-focus enum class StyleUserFocus : uint8_t { None, diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 553239e0e..52491a288 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -3788,6 +3788,7 @@ nsStyleText::nsStyleText(StyleStructContext aContext) , mTextAlignLast(NS_STYLE_TEXT_ALIGN_AUTO) , mTextAlignTrue(false) , mTextAlignLastTrue(false) + , mTextJustify(StyleTextJustify::Auto) , mTextTransform(NS_STYLE_TEXT_TRANSFORM_NONE) , mWhiteSpace(NS_STYLE_WHITESPACE_NORMAL) , mWordBreak(NS_STYLE_WORDBREAK_NORMAL) @@ -3824,6 +3825,7 @@ nsStyleText::nsStyleText(const nsStyleText& aSource) , mTextAlignLast(aSource.mTextAlignLast) , mTextAlignTrue(false) , mTextAlignLastTrue(false) + , mTextJustify(aSource.mTextJustify) , mTextTransform(aSource.mTextTransform) , mWhiteSpace(aSource.mWhiteSpace) , mWordBreak(aSource.mWordBreak) @@ -3885,6 +3887,7 @@ nsStyleText::CalcDifference(const nsStyleText& aNewData) const (mTextSizeAdjust != aNewData.mTextSizeAdjust) || (mLetterSpacing != aNewData.mLetterSpacing) || (mLineHeight != aNewData.mLineHeight) || + (mTextJustify != aNewData.mTextJustify) || (mTextIndent != aNewData.mTextIndent) || (mWordSpacing != aNewData.mWordSpacing) || (mTabSize != aNewData.mTabSize)) { diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 05a6be91e..1cadea840 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -2071,6 +2071,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText uint8_t mTextAlignLast; // [inherited] see nsStyleConsts.h bool mTextAlignTrue : 1; // [inherited] see nsStyleConsts.h bool mTextAlignLastTrue : 1; // [inherited] see nsStyleConsts.h + mozilla::StyleTextJustify mTextJustify; // [inherited] uint8_t mTextTransform; // [inherited] see nsStyleConsts.h uint8_t mWhiteSpace; // [inherited] see nsStyleConsts.h uint8_t mWordBreak; // [inherited] see nsStyleConsts.h diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp index 4a1a5b7ad..118702e8f 100644 --- a/layout/style/nsTransitionManager.cpp +++ b/layout/style/nsTransitionManager.cpp @@ -46,8 +46,6 @@ using mozilla::dom::KeyframeEffectReadOnly; using namespace mozilla; using namespace mozilla::css; -typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase; - namespace { struct TransitionEventParams { EventMessage mMessage; @@ -180,10 +178,9 @@ CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag) } void -CSSTransition::QueueEvents() +CSSTransition::QueueEvents(StickyTimeDuration aActiveTime) { - if (!mEffect || - !mOwningElement.IsSet()) { + if (!mOwningElement.IsSet()) { return; } @@ -197,69 +194,123 @@ CSSTransition::QueueEvents() return; } - ComputedTiming computedTiming = mEffect->GetComputedTiming(); - const StickyTimeDuration zeroDuration; - StickyTimeDuration intervalStartTime = - std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay), - computedTiming.mActiveDuration), zeroDuration); - StickyTimeDuration intervalEndTime = - std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay), - computedTiming.mActiveDuration), zeroDuration); + const StickyTimeDuration zeroDuration = StickyTimeDuration(); + + TransitionPhase currentPhase; + StickyTimeDuration intervalStartTime; + StickyTimeDuration intervalEndTime; + + if (!mEffect) { + currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this); + intervalStartTime = zeroDuration; + intervalEndTime = zeroDuration; + } else { + ComputedTiming computedTiming = mEffect->GetComputedTiming(); + + currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase); + intervalStartTime = + std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay), + computedTiming.mActiveDuration), zeroDuration); + intervalEndTime = + std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay), + computedTiming.mActiveDuration), zeroDuration); + } // TimeStamps to use for ordering the events when they are dispatched. We // use a TimeStamp so we can compare events produced by different elements, // perhaps even with different timelines. // The zero timestamp is for transitionrun events where we ignore the delay // for the purpose of ordering events. - TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime); - TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime); + TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration); + TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime); + TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime); - TransitionPhase currentPhase; if (mPendingState != PendingState::NotPending && (mPreviousTransitionPhase == TransitionPhase::Idle || mPreviousTransitionPhase == TransitionPhase::Pending)) { currentPhase = TransitionPhase::Pending; - } else { - currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase); } AutoTArray<TransitionEventParams, 3> events; + + // Handle cancel events firts + if (mPreviousTransitionPhase != TransitionPhase::Idle && + currentPhase == TransitionPhase::Idle) { + TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime); + events.AppendElement(TransitionEventParams{ eTransitionCancel, + aActiveTime, + activeTimeStamp }); + } + + // All other events switch (mPreviousTransitionPhase) { case TransitionPhase::Idle: - if (currentPhase == TransitionPhase::After) { + if (currentPhase == TransitionPhase::Pending || + currentPhase == TransitionPhase::Before) { + events.AppendElement(TransitionEventParams{ eTransitionRun, + intervalStartTime, + zeroTimeStamp }); + } else if (currentPhase == TransitionPhase::Active) { + events.AppendElement(TransitionEventParams{ eTransitionRun, + intervalStartTime, + zeroTimeStamp }); + events.AppendElement(TransitionEventParams{ eTransitionStart, + intervalStartTime, + startTimeStamp }); + } else if (currentPhase == TransitionPhase::After) { + events.AppendElement(TransitionEventParams{ eTransitionRun, + intervalStartTime, + zeroTimeStamp }); + events.AppendElement(TransitionEventParams{ eTransitionStart, + intervalStartTime, + startTimeStamp }); events.AppendElement(TransitionEventParams{ eTransitionEnd, - intervalEndTime, - endTimeStamp }); + intervalEndTime, + endTimeStamp }); } break; case TransitionPhase::Pending: case TransitionPhase::Before: - if (currentPhase == TransitionPhase::After) { + if (currentPhase == TransitionPhase::Active) { + events.AppendElement(TransitionEventParams{ eTransitionStart, + intervalStartTime, + startTimeStamp }); + } else if (currentPhase == TransitionPhase::After) { + events.AppendElement(TransitionEventParams{ eTransitionStart, + intervalStartTime, + startTimeStamp }); events.AppendElement(TransitionEventParams{ eTransitionEnd, - intervalEndTime, - endTimeStamp }); + intervalEndTime, + endTimeStamp }); } break; case TransitionPhase::Active: if (currentPhase == TransitionPhase::After) { events.AppendElement(TransitionEventParams{ eTransitionEnd, - intervalEndTime, - endTimeStamp }); + intervalEndTime, + endTimeStamp }); } else if (currentPhase == TransitionPhase::Before) { events.AppendElement(TransitionEventParams{ eTransitionEnd, - intervalStartTime, - startTimeStamp }); + intervalStartTime, + startTimeStamp }); } break; case TransitionPhase::After: - if (currentPhase == TransitionPhase::Before) { + if (currentPhase == TransitionPhase::Active) { + events.AppendElement(TransitionEventParams{ eTransitionStart, + intervalEndTime, + startTimeStamp }); + } else if (currentPhase == TransitionPhase::Before) { + events.AppendElement(TransitionEventParams{ eTransitionStart, + intervalEndTime, + startTimeStamp }); events.AppendElement(TransitionEventParams{ eTransitionEnd, - intervalStartTime, - endTimeStamp }); + intervalStartTime, + endTimeStamp }); } break; } diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h index 56ec61572..1c48cc8cd 100644 --- a/layout/style/nsTransitionManager.h +++ b/layout/style/nsTransitionManager.h @@ -214,6 +214,10 @@ public: const TimeDuration& aStartTime, double aPlaybackRate); + void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override { + QueueEvents(aActiveTime); + } + protected: virtual ~CSSTransition() { @@ -225,7 +229,10 @@ protected: void UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag) override; - void QueueEvents(); + void QueueEvents(StickyTimeDuration activeTime = StickyTimeDuration()); + + + enum class TransitionPhase; // The (pseudo-)element whose computed transition-property refers to this // transition (if any). @@ -250,7 +257,7 @@ protected: // to be queued on this tick. // See: https://drafts.csswg.org/css-transitions-2/#transition-phase enum class TransitionPhase { - Idle = static_cast<int>(ComputedTiming::AnimationPhase::Null), + Idle = static_cast<int>(ComputedTiming::AnimationPhase::Idle), Before = static_cast<int>(ComputedTiming::AnimationPhase::Before), Active = static_cast<int>(ComputedTiming::AnimationPhase::Active), After = static_cast<int>(ComputedTiming::AnimationPhase::After), diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 62d413d98..272931c15 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -5694,6 +5694,17 @@ if (IsCSSPropertyPrefEnabled("layout.css.text-combine-upright.enabled")) { } } +if (IsCSSPropertyPrefEnabled("layout.css.text-justify.enabled")) { + gCSSProperties["text-justify"] = { + domProp: "textJustify", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "none", "inter-word", "inter-character", "distribute" ], + invalid_values: [] + }; +} + if (IsCSSPropertyPrefEnabled("svg.paint-order.enabled")) { gCSSProperties["paint-order"] = { domProp: "paintOrder", diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html index eaccba122..4019af77f 100644 --- a/layout/style/test/test_animations.html +++ b/layout/style/test/test_animations.html @@ -1195,9 +1195,6 @@ is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01, "large negative delay test at 0ms"); check_events([{ type: 'animationstart', target: div, animationName: 'anim2', elapsedTime: 3.6, - pseudoElement: "" }, - { type: 'animationiteration', target: div, - animationName: 'anim2', elapsedTime: 3.6, pseudoElement: "" }], "right after start in large negative delay test"); advance_clock(380); diff --git a/layout/style/test/test_animations_event_handler_attribute.html b/layout/style/test/test_animations_event_handler_attribute.html index e5def2b34..036a77779 100644 --- a/layout/style/test/test_animations_event_handler_attribute.html +++ b/layout/style/test/test_animations_event_handler_attribute.html @@ -88,21 +88,55 @@ checkReceivedEvents("animationend", targets); targets.forEach(div => { div.remove(); }); -// 2. Test CSS Transition event handlers. +// 2a. Test CSS Transition event handlers (without transitioncancel) -var targets = createAndRegisterTargets([ 'ontransitionend' ]); +var targets = createAndRegisterTargets([ 'ontransitionrun', + 'ontransitionstart', + 'ontransitionend', + 'ontransitioncancel' ]); targets.forEach(div => { - div.style.transition = 'margin-left 100ms'; + div.style.transition = 'margin-left 100ms 200ms'; getComputedStyle(div).marginLeft; // flush div.style.marginLeft = "200px"; getComputedStyle(div).marginLeft; // flush }); +advance_clock(0); +checkReceivedEvents("transitionrun", targets); + +advance_clock(200); +checkReceivedEvents("transitionstart", targets); + advance_clock(100); checkReceivedEvents("transitionend", targets); targets.forEach(div => { div.remove(); }); +// 2b. Test CSS Transition cancel event handler. + +var targets = createAndRegisterTargets([ 'ontransitioncancel' ]); +targets.forEach(div => { + div.style.transition = 'margin-left 100ms 200ms'; + getComputedStyle(div).marginLeft; // flush + div.style.marginLeft = "200px"; + getComputedStyle(div).marginLeft; // flush +}); + +advance_clock(200); + +targets.forEach(div => { + div.style.display = "none" +}); +getComputedStyle(targets[0]).display; // flush + +advance_clock(0); +checkReceivedEvents("transitioncancel", targets); + +advance_clock(100); +targets.forEach( div => { is(div.receivedEventType, undefined); }); + +targets.forEach(div => { div.remove(); }); + // 3. Test prefixed CSS Animation event handlers. var targets = createAndRegisterTargets([ 'onwebkitanimationstart', diff --git a/layout/style/test/test_animations_event_order.html b/layout/style/test/test_animations_event_order.html index 5af7639cc..7204934d2 100644 --- a/layout/style/test/test_animations_event_order.html +++ b/layout/style/test/test_animations_event_order.html @@ -46,7 +46,10 @@ var gDisplay = document.getElementById('display'); [ 'animationstart', 'animationiteration', 'animationend', - 'transitionend' ] + 'transitionrun', + 'transitionstart', + 'transitionend', + 'transitioncancel' ] .forEach(event => gDisplay.addEventListener(event, event => gEventsReceived.push(event), @@ -322,9 +325,13 @@ getComputedStyle(divs[0]).marginLeft; advance_clock(0); advance_clock(10000); -checkEventOrder([ divs[0], 'transitionend' ], +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionend' ], [ divs[1], 'transitionend' ], - 'Simultaneous transitionend on siblings'); + 'Simultaneous transitionrun/start/end on siblings'); divs.forEach(div => div.remove()); divs = []; @@ -360,10 +367,16 @@ getComputedStyle(divs[0]).marginLeft; advance_clock(0); advance_clock(10000); -checkEventOrder([ divs[0], 'transitionend' ], +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[2], 'transitionrun' ], + [ divs[2], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionend' ], [ divs[2], 'transitionend' ], [ divs[1], 'transitionend' ], - 'Simultaneous transitionend on children'); + 'Simultaneous transitionrun/start/end on children'); divs.forEach(div => div.remove()); divs = []; @@ -408,11 +421,19 @@ getComputedStyle(divs[0]).marginLeft; advance_clock(0); advance_clock(10000); -checkEventOrder([ divs[0], 'transitionend' ], +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[0], '::before', 'transitionrun' ], + [ divs[0], '::before', 'transitionstart' ], + [ divs[0], '::after', 'transitionrun' ], + [ divs[0], '::after', 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionend' ], [ divs[0], '::before', 'transitionend' ], [ divs[0], '::after', 'transitionend' ], [ divs[1], 'transitionend' ], - 'Simultaneous transitionend on pseudo-elements'); + 'Simultaneous transitionrun/start/end on pseudo-elements'); divs.forEach(div => div.remove()); divs = []; @@ -441,9 +462,13 @@ getComputedStyle(divs[0]).marginLeft; advance_clock(0); advance_clock(10000); -checkEventOrder([ divs[1], 'transitionend' ], +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[1], 'transitionend' ], [ divs[0], 'transitionend' ], - 'Sorting of transitionend events by time'); + 'Sorting of transitionrun/start/end events by time'); divs.forEach(div => div.remove()); divs = []; @@ -468,9 +493,13 @@ getComputedStyle(divs[0]).marginLeft; advance_clock(0); advance_clock(10 * 1000); -checkEventOrder([ divs[1], 'transitionend' ], +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionend' ], [ divs[0], 'transitionend' ], - 'Sorting of transitionend events by time' + + 'Sorting of transitionrun/start/end events by time' + '(including delay)'); divs.forEach(div => div.remove()); @@ -492,9 +521,14 @@ getComputedStyle(div).marginLeft; advance_clock(0); advance_clock(10000); -checkEventOrder([ 'margin-left', 'transitionend' ], +checkEventOrder([ 'margin-left', 'transitionrun' ], + [ 'margin-left', 'transitionstart' ], + [ 'opacity', 'transitionrun' ], + [ 'opacity', 'transitionstart' ], + [ 'margin-left', 'transitionend' ], [ 'opacity', 'transitionend' ], - 'Sorting of transitionend events by transition-property') + 'Sorting of transitionrun/start/end events by ' + + 'transition-property') div.remove(); div = undefined; @@ -519,7 +553,11 @@ getComputedStyle(divs[0]).marginLeft; advance_clock(0); advance_clock(10000); -checkEventOrder([ divs[0], 'transitionend' ], +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionend' ], [ divs[1], 'transitionend' ], 'Transition events are sorted by document position first, ' + 'before transition-property'); @@ -543,7 +581,11 @@ getComputedStyle(div).marginLeft; advance_clock(0); advance_clock(10000); -checkEventOrder([ 'opacity', 'transitionend' ], +checkEventOrder([ 'margin-left', 'transitionrun' ], + [ 'margin-left', 'transitionstart' ], + [ 'opacity', 'transitionrun' ], + [ 'opacity', 'transitionstart' ], + [ 'opacity', 'transitionend' ], [ 'margin-left', 'transitionend' ], 'Transition events are sorted by time first, before ' + 'transition-property'); @@ -571,9 +613,50 @@ getComputedStyle(divs[0]).marginLeft; advance_clock(0); advance_clock(15 * 1000); -checkEventOrder([ divs[1], 'transitionend' ], +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionend' ], [ divs[0], 'transitionend' ], - 'Simultaneous transitionend on siblings'); + 'Simultaneous transitionrun/start/end on siblings'); + +divs.forEach(div => div.remove()); +divs = []; + +// 4j. Test sorting transitions with cancel +// The order of transitioncancel is based on StyleManager. +// So this test looks like wrong result at a glance. However +// the gecko will cancel div1's transition before div2 in this case. + +divs = [ document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.style.marginLeft = '0px'; + div.setAttribute('id', 'div' + i); +}); + +divs[0].style.transition = 'margin-left 10s 5s'; +divs[1].style.transition = 'margin-left 10s'; + +getComputedStyle(divs[0]).marginLeft; +divs.forEach(div => div.style.marginLeft = '100px'); +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(5 * 1000); +divs.forEach(div => div.style.display = 'none' ); +getComputedStyle(divs[0]).display; +advance_clock(10 * 1000); + +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitioncancel' ], + [ divs[0], 'transitioncancel' ], + 'Simultaneous transitionrun/start/cancel on siblings'); divs.forEach(div => div.remove()); divs = []; diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html index 4b276c896..0b2a61ecc 100644 --- a/layout/style/test/test_animations_omta.html +++ b/layout/style/test/test_animations_omta.html @@ -1408,9 +1408,6 @@ addAsyncAnimTest(function *() { "large negative delay test at 0ms"); check_events([{ type: 'animationstart', target: gDiv, animationName: 'anim2', elapsedTime: 3.6, - pseudoElement: "" }, - { type: 'animationiteration', target: gDiv, - animationName: 'anim2', elapsedTime: 3.6, pseudoElement: "" }], "right after start in large negative delay test"); advance_clock(380); diff --git a/mfbt/TextUtils.h b/mfbt/TextUtils.h new file mode 100644 index 000000000..9494296ce --- /dev/null +++ b/mfbt/TextUtils.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* Character/text operations. */ + +#ifndef mozilla_TextUtils_h +#define mozilla_TextUtils_h + +#include "mozilla/TypeTraits.h" + +namespace mozilla { + +namespace detail { + +template<typename Char> +class MakeUnsignedChar + : public MakeUnsigned<Char> +{}; + +template<> +class MakeUnsignedChar<char16_t> +{ +public: + using Type = char16_t; +}; + +template<> +class MakeUnsignedChar<char32_t> +{ +public: + using Type = char32_t; +}; + +} // namespace detail + +/** + * Returns true iff |aChar| matches [a-zA-Z]. + * + * This function is basically what you thought isalpha was, except its behavior + * doesn't depend on the user's current locale. + */ +template<typename Char> +constexpr bool +IsAsciiAlpha(Char aChar) +{ + using UnsignedChar = typename detail::MakeUnsignedChar<Char>::Type; + return ('a' <= static_cast<UnsignedChar>(aChar) && + static_cast<UnsignedChar>(aChar) <= 'z') || + ('A' <= static_cast<UnsignedChar>(aChar) && + static_cast<UnsignedChar>(aChar) <= 'Z'); +} + +} // namespace mozilla + +#endif /* mozilla_TextUtils_h */ diff --git a/mfbt/moz.build b/mfbt/moz.build index f23a3b6f5..897a686f4 100644 --- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -87,6 +87,7 @@ EXPORTS.mozilla = [ 'StaticAnalysisFunctions.h', 'TaggedAnonymousMemory.h', 'TemplateLib.h', + 'TextUtils.h', 'ThreadLocal.h', 'ToString.h', 'Tuple.h', diff --git a/mfbt/tests/TestTextUtils.cpp b/mfbt/tests/TestTextUtils.cpp new file mode 100644 index 000000000..db481c138 --- /dev/null +++ b/mfbt/tests/TestTextUtils.cpp @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/TextUtils.h" + +using mozilla::IsAsciiAlpha; + +// char + +static_assert(!IsAsciiAlpha('@'), "'@' isn't ASCII alpha"); +static_assert('@' == 0x40, "'@' has value 0x40"); + +static_assert('A' == 0x41, "'A' has value 0x41"); +static_assert(IsAsciiAlpha('A'), "'A' is ASCII alpha"); +static_assert(IsAsciiAlpha('B'), "'B' is ASCII alpha"); +static_assert(IsAsciiAlpha('M'), "'M' is ASCII alpha"); +static_assert(IsAsciiAlpha('Y'), "'Y' is ASCII alpha"); +static_assert(IsAsciiAlpha('Z'), "'Z' is ASCII alpha"); + +static_assert('Z' == 0x5A, "'Z' has value 0x5A"); +static_assert('[' == 0x5B, "'[' has value 0x5B"); +static_assert(!IsAsciiAlpha('['), "'[' isn't ASCII alpha"); + +static_assert(!IsAsciiAlpha('`'), "'`' isn't ASCII alpha"); +static_assert('`' == 0x60, "'`' has value 0x60"); + +static_assert('a' == 0x61, "'a' has value 0x61"); +static_assert(IsAsciiAlpha('a'), "'a' is ASCII alpha"); +static_assert(IsAsciiAlpha('b'), "'b' is ASCII alpha"); +static_assert(IsAsciiAlpha('m'), "'m' is ASCII alpha"); +static_assert(IsAsciiAlpha('y'), "'y' is ASCII alpha"); +static_assert(IsAsciiAlpha('z'), "'z' is ASCII alpha"); + +static_assert('z' == 0x7A, "'z' has value 0x7A"); +static_assert('{' == 0x7B, "'{' has value 0x7B"); +static_assert(!IsAsciiAlpha('{'), "'{' isn't ASCII alpha"); + +// char16_t + +static_assert(!IsAsciiAlpha(u'@'), "u'@' isn't ASCII alpha"); +static_assert(u'@' == 0x40, "u'@' has value 0x40"); + +static_assert(u'A' == 0x41, "u'A' has value 0x41"); +static_assert(IsAsciiAlpha(u'A'), "u'A' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'B'), "u'B' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'M'), "u'M' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'Y'), "u'Y' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'Z'), "u'Z' is ASCII alpha"); + +static_assert(u'Z' == 0x5A, "u'Z' has value 0x5A"); +static_assert(u'[' == 0x5B, "u'[' has value 0x5B"); +static_assert(!IsAsciiAlpha(u'['), "u'[' isn't ASCII alpha"); + +static_assert(!IsAsciiAlpha(u'`'), "u'`' isn't ASCII alpha"); +static_assert(u'`' == 0x60, "u'`' has value 0x60"); + +static_assert(u'a' == 0x61, "u'a' has value 0x61"); +static_assert(IsAsciiAlpha(u'a'), "u'a' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'b'), "u'b' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'm'), "u'm' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'y'), "u'y' is ASCII alpha"); +static_assert(IsAsciiAlpha(u'z'), "u'z' is ASCII alpha"); + +static_assert(u'z' == 0x7A, "u'z' has value 0x7A"); +static_assert(u'{' == 0x7B, "u'{' has value 0x7B"); +static_assert(!IsAsciiAlpha(u'{'), "u'{' isn't ASCII alpha"); + +// char32_t + +static_assert(!IsAsciiAlpha(U'@'), "U'@' isn't ASCII alpha"); +static_assert(U'@' == 0x40, "U'@' has value 0x40"); + +static_assert(U'A' == 0x41, "U'A' has value 0x41"); +static_assert(IsAsciiAlpha(U'A'), "U'A' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'B'), "U'B' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'M'), "U'M' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'Y'), "U'Y' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'Z'), "U'Z' is ASCII alpha"); + +static_assert(U'Z' == 0x5A, "U'Z' has value 0x5A"); +static_assert(U'[' == 0x5B, "U'[' has value 0x5B"); +static_assert(!IsAsciiAlpha(U'['), "U'[' isn't ASCII alpha"); + +static_assert(!IsAsciiAlpha(U'`'), "U'`' isn't ASCII alpha"); +static_assert(U'`' == 0x60, "U'`' has value 0x60"); + +static_assert(U'a' == 0x61, "U'a' has value 0x61"); +static_assert(IsAsciiAlpha(U'a'), "U'a' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'b'), "U'b' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'm'), "U'm' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'y'), "U'y' is ASCII alpha"); +static_assert(IsAsciiAlpha(U'z'), "U'z' is ASCII alpha"); + +static_assert(U'z' == 0x7A, "U'z' has value 0x7A"); +static_assert(U'{' == 0x7B, "U'{' has value 0x7B"); +static_assert(!IsAsciiAlpha(U'{'), "U'{' isn't ASCII alpha"); + +int +main() +{ + return 0; +} diff --git a/mfbt/tests/moz.build b/mfbt/tests/moz.build index f96117e03..bd25ab1d0 100644 --- a/mfbt/tests/moz.build +++ b/mfbt/tests/moz.build @@ -42,6 +42,7 @@ CppUnitTests([ 'TestSHA1', 'TestSplayTree', 'TestTemplateLib', + 'TestTextUtils', 'TestTuple', 'TestTypedEnum', 'TestTypeTraits', diff --git a/mobile/android/chrome/content/InputWidgetHelper.js b/mobile/android/chrome/content/InputWidgetHelper.js index 9c753bd7b..cf66a263e 100644 --- a/mobile/android/chrome/content/InputWidgetHelper.js +++ b/mobile/android/chrome/content/InputWidgetHelper.js @@ -63,7 +63,7 @@ var InputWidgetHelper = { }, hasInputWidget: function(aElement) { - if (!aElement instanceof HTMLInputElement) + if (!(aElement instanceof HTMLInputElement)) return false; let type = aElement.getAttribute('type'); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 903665ff8..bf7626391 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -183,6 +183,9 @@ pref("dom.enable_resource_timing", true); // Enable high-resolution timing markers for users pref("dom.enable_user_timing", true); +// Whether performance.GetEntries* will contain an entry for the active document +pref("dom.enable_performance_navigation_timing", true); + // Enable printing performance marks/measures to log pref("dom.performance.enable_user_timing_logging", false); @@ -192,6 +195,9 @@ pref("dom.performance.enable_notify_performance_timing", false); // Enable Permission API's .revoke() method pref("dom.permissions.revoke.enable", false); +// Enable exposing timeToNonBlankPaint +pref("dom.performance.time_to_non_blank_paint.enabled", false); + // Enable Performance Observer API #ifdef NIGHTLY_BUILD pref("dom.enable_performance_observer", true); @@ -2510,6 +2516,9 @@ pref("layout.css.convertFromNode.enabled", true); // Is support for CSS "text-align: unsafe X" enabled? pref("layout.css.text-align-unsafe-value.enabled", false); +// Is support for CSS text-justify property enabled? +pref("layout.css.text-justify.enabled", true); + // Is support for CSS "float: inline-{start,end}" and // "clear: inline-{start,end}" enabled? #if defined(MOZ_B2G) || !defined(RELEASE_OR_BETA) diff --git a/moz.configure b/moz.configure index cecc1335e..64cdc8ac6 100644 --- a/moz.configure +++ b/moz.configure @@ -52,38 +52,6 @@ def compile_environment(compile_env): set_config('COMPILE_ENVIRONMENT', compile_environment) add_old_configure_assignment('COMPILE_ENVIRONMENT', compile_environment) -js_option('--disable-tests', - help='Do not build test libraries & programs') - -@depends('--disable-tests') -def enable_tests(value): - if value: - return True - -set_config('ENABLE_TESTS', enable_tests) -set_define('ENABLE_TESTS', enable_tests) - -@depends(enable_tests) -def gtest_has_rtti(value): - if value: - return '0' - -set_define('GTEST_HAS_RTTI', gtest_has_rtti) - -@depends(target, enable_tests) -def linux_gtest_defines(target, enable_tests): - if enable_tests and target.os == 'Android': - return namespace(os_linux_android=True, - use_own_tr1_tuple=True, - has_clone='0') - -set_define('GTEST_OS_LINUX_ANDROID', - delayed_getattr(linux_gtest_defines, 'os_linux_android')) -set_define('GTEST_USE_OWN_TR1_TUPLE', - delayed_getattr(linux_gtest_defines, 'use_own_tr1_tuple')) -set_define('GTEST_HAS_CLONE', - delayed_getattr(linux_gtest_defines, 'has_clone')) - js_option('--enable-debug', nargs='?', help='Enable building with developer debug info ' diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl index 17d27de42..721b7a334 100644 --- a/netwerk/base/nsINetworkInterceptController.idl +++ b/netwerk/base/nsINetworkInterceptController.idl @@ -14,12 +14,16 @@ interface nsIURI; %{C++ #include "nsIConsoleReportCollector.h" namespace mozilla { +class TimeStamp; + namespace dom { class ChannelInfo; } } %} +native TimeStamp(mozilla::TimeStamp); + [ptr] native ChannelInfo(mozilla::dom::ChannelInfo); /** @@ -97,6 +101,30 @@ interface nsIInterceptedChannel : nsISupports [noscript] readonly attribute nsIConsoleReportCollector consoleReportCollector; + /** + * Save the timestamps of various service worker interception phases. + */ + [noscript] + void SetLaunchServiceWorkerStart(in TimeStamp aTimeStamp); + + [noscript] + void SetLaunchServiceWorkerEnd(in TimeStamp aTimeStamp); + + [noscript] + void SetDispatchFetchEventStart(in TimeStamp aTimeStamp); + + [noscript] + void SetDispatchFetchEventEnd(in TimeStamp aTimeStamp); + + [noscript] + void SetHandleFetchEventStart(in TimeStamp aTimeStamp); + + [noscript] + void SetHandleFetchEventEnd(in TimeStamp aTimeStamp); + + [noscript] + void SaveTimeStampsToUnderlyingChannel(); + %{C++ already_AddRefed<nsIConsoleReportCollector> GetConsoleReportCollector() diff --git a/netwerk/base/nsITimedChannel.idl b/netwerk/base/nsITimedChannel.idl index 13b65e7b8..83670a11e 100644 --- a/netwerk/base/nsITimedChannel.idl +++ b/netwerk/base/nsITimedChannel.idl @@ -21,7 +21,8 @@ interface nsITimedChannel : nsISupports { attribute boolean timingEnabled; // The number of redirects - attribute uint16_t redirectCount; + attribute uint8_t redirectCount; + attribute uint8_t internalRedirectCount; [noscript] readonly attribute TimeStamp channelCreation; [noscript] readonly attribute TimeStamp asyncOpen; @@ -37,6 +38,15 @@ interface nsITimedChannel : nsISupports { [noscript] readonly attribute TimeStamp responseStart; [noscript] readonly attribute TimeStamp responseEnd; + // The following are only set when the request is intercepted by a service + // worker no matter the response is synthesized. + [noscript] attribute TimeStamp launchServiceWorkerStart; + [noscript] attribute TimeStamp launchServiceWorkerEnd; + [noscript] attribute TimeStamp dispatchFetchEventStart; + [noscript] attribute TimeStamp dispatchFetchEventEnd; + [noscript] attribute TimeStamp handleFetchEventStart; + [noscript] attribute TimeStamp handleFetchEventEnd; + // The redirect attributes timings must be writeble, se we can transfer // the data from one channel to the redirected channel. [noscript] attribute TimeStamp redirectStart; @@ -67,6 +77,12 @@ interface nsITimedChannel : nsISupports { // All following are PRTime versions of the above. readonly attribute PRTime channelCreationTime; readonly attribute PRTime asyncOpenTime; + readonly attribute PRTime launchServiceWorkerStartTime; + readonly attribute PRTime launchServiceWorkerEndTime; + readonly attribute PRTime dispatchFetchEventStartTime; + readonly attribute PRTime dispatchFetchEventEndTime; + readonly attribute PRTime handleFetchEventStartTime; + readonly attribute PRTime handleFetchEventEndTime; readonly attribute PRTime domainLookupStartTime; readonly attribute PRTime domainLookupEndTime; readonly attribute PRTime connectStartTime; diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index 8ff3e788f..bc9bcf88a 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -1285,16 +1285,10 @@ NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport) nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); MOZ_RELEASE_ASSERT(loadInfo, "Origin tracking only works for channels created with a loadinfo"); -#ifdef DEBUG - // Don't enforce TYPE_DOCUMENT assertions for loads - // initiated by javascript tests. - bool skipContentTypeCheck = false; - skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion"); -#endif - - MOZ_ASSERT(skipContentTypeCheck || - loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT, - "calling NS_HasBeenCrossOrigin on a top level load"); + // TYPE_DOCUMENT loads have a null LoadingPrincipal and can not be cross origin. + if (!loadInfo->LoadingPrincipal()) { + return false; + } // Always treat tainted channels as cross-origin. if (loadInfo->GetTainting() != LoadTainting::Basic) { diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh index 4f4dcf6a9..bb7562c64 100644 --- a/netwerk/ipc/NeckoChannelParams.ipdlh +++ b/netwerk/ipc/NeckoChannelParams.ipdlh @@ -20,6 +20,7 @@ using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h"; using struct nsHttpAtom from "nsHttp.h"; using class nsHttpResponseHead from "nsHttpResponseHead.h"; +using class mozilla::TimeStamp from "mozilla/TimeStamp.h"; namespace mozilla { namespace net { @@ -134,6 +135,12 @@ struct HttpChannelOpenArgs nsCString channelId; uint64_t contentWindowId; nsCString preferredAlternativeType; + TimeStamp launchServiceWorkerStart; + TimeStamp launchServiceWorkerEnd; + TimeStamp dispatchFetchEventStart; + TimeStamp dispatchFetchEventEnd; + TimeStamp handleFetchEventStart; + TimeStamp handleFetchEventEnd; }; struct HttpChannelConnectArgs diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index 278c94db0..d161f9a43 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -105,6 +105,7 @@ HttpBaseChannel::HttpBaseChannel() , mHttpHandler(gHttpHandler) , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE) , mRedirectCount(0) + , mInternalRedirectCount(0) , mForcePending(false) , mCorsIncludeCredentials(false) , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS) @@ -3128,12 +3129,6 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, // convey the mAllowPipelining and mAllowSTS flags httpChannel->SetAllowPipelining(mAllowPipelining); httpChannel->SetAllowSTS(mAllowSTS); - // convey the new redirection limit - // make sure we don't underflow - uint32_t redirectionLimit = mRedirectionLimit - ? mRedirectionLimit - 1 - : 0; - httpChannel->SetRedirectionLimit(redirectionLimit); // convey the Accept header value { @@ -3215,23 +3210,40 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, do_QueryInterface(static_cast<nsIHttpChannel*>(this))); if (oldTimedChannel && newTimedChannel) { newTimedChannel->SetTimingEnabled(mTimingEnabled); - newTimedChannel->SetRedirectCount(mRedirectCount + 1); + + if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { + int8_t newCount = mInternalRedirectCount + 1; + newTimedChannel->SetInternalRedirectCount( + std::max(newCount, mInternalRedirectCount)); + } else { + int8_t newCount = mRedirectCount + 1; + newTimedChannel->SetRedirectCount( + std::max(newCount, mRedirectCount)); + } // If the RedirectStart is null, we will use the AsyncOpen value of the // previous channel (this is the first redirect in the redirects chain). if (mRedirectStartTimeStamp.IsNull()) { - TimeStamp asyncOpen; - oldTimedChannel->GetAsyncOpen(&asyncOpen); - newTimedChannel->SetRedirectStart(asyncOpen); - } - else { + // Only do this for real redirects. Internal redirects should be hidden. + if (!(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) { + TimeStamp asyncOpen; + oldTimedChannel->GetAsyncOpen(&asyncOpen); + newTimedChannel->SetRedirectStart(asyncOpen); + } + } else { newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp); } - // The RedirectEnd timestamp is equal to the previous channel response end. - TimeStamp prevResponseEnd; - oldTimedChannel->GetResponseEnd(&prevResponseEnd); - newTimedChannel->SetRedirectEnd(prevResponseEnd); + // For internal redirects just propagate the last redirect end time + // forward. Otherwise the new redirect end time is the last response + // end time. + TimeStamp newRedirectEnd; + if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { + oldTimedChannel->GetRedirectEnd(&newRedirectEnd); + } else { + oldTimedChannel->GetResponseEnd(&newRedirectEnd); + } + newTimedChannel->SetRedirectEnd(newRedirectEnd); nsAutoString initiatorType; oldTimedChannel->GetInitiatorType(initiatorType); @@ -3253,6 +3265,16 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, mAllRedirectsPassTimingAllowCheck && oldTimedChannel->TimingAllowCheck(principal)); } + + // Propagate service worker measurements across redirects. The + // PeformanceResourceTiming.workerStart API expects to see the + // worker start time after a redirect. + newTimedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart); + newTimedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd); + newTimedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart); + newTimedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd); + newTimedChannel->SetHandleFetchEventStart(mHandleFetchEventStart); + newTimedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd); } // Pass the preferred alt-data type on to the new channel. @@ -3318,20 +3340,34 @@ HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) { * redirects. This check must be done by the consumers. */ NS_IMETHODIMP -HttpBaseChannel::GetRedirectCount(uint16_t *aRedirectCount) +HttpBaseChannel::GetRedirectCount(uint8_t *aRedirectCount) { *aRedirectCount = mRedirectCount; return NS_OK; } NS_IMETHODIMP -HttpBaseChannel::SetRedirectCount(uint16_t aRedirectCount) +HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount) { mRedirectCount = aRedirectCount; return NS_OK; } NS_IMETHODIMP +HttpBaseChannel::GetInternalRedirectCount(uint8_t *aRedirectCount) +{ + *aRedirectCount = mInternalRedirectCount; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount) +{ + mInternalRedirectCount = aRedirectCount; + return NS_OK; +} + +NS_IMETHODIMP HttpBaseChannel::GetRedirectStart(TimeStamp* _retval) { *_retval = mRedirectStartTimeStamp; @@ -3431,6 +3467,84 @@ HttpBaseChannel::TimingAllowCheck(nsIPrincipal *aOrigin, bool *_retval) } NS_IMETHODIMP +HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) { + MOZ_ASSERT(_retval); + *_retval = mLaunchServiceWorkerStart; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) { + mLaunchServiceWorkerStart = aTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) { + MOZ_ASSERT(_retval); + *_retval = mLaunchServiceWorkerEnd; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) { + mLaunchServiceWorkerEnd = aTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) { + MOZ_ASSERT(_retval); + *_retval = mDispatchFetchEventStart; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) { + mDispatchFetchEventStart = aTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) { + MOZ_ASSERT(_retval); + *_retval = mDispatchFetchEventEnd; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) { + mDispatchFetchEventEnd = aTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) { + MOZ_ASSERT(_retval); + *_retval = mHandleFetchEventStart; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) { + mHandleFetchEventStart = aTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) { + MOZ_ASSERT(_retval); + *_retval = mHandleFetchEventEnd; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) { + mHandleFetchEventEnd = aTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) { *_retval = mTransactionTimings.domainLookupStart; return NS_OK; @@ -3520,6 +3634,12 @@ HttpBaseChannel::Get##name##Time(PRTime* _retval) { \ IMPL_TIMING_ATTR(ChannelCreation) IMPL_TIMING_ATTR(AsyncOpen) +IMPL_TIMING_ATTR(LaunchServiceWorkerStart) +IMPL_TIMING_ATTR(LaunchServiceWorkerEnd) +IMPL_TIMING_ATTR(DispatchFetchEventStart) +IMPL_TIMING_ATTR(DispatchFetchEventEnd) +IMPL_TIMING_ATTR(HandleFetchEventStart) +IMPL_TIMING_ATTR(HandleFetchEventEnd) IMPL_TIMING_ATTR(DomainLookupStart) IMPL_TIMING_ATTR(DomainLookupEnd) IMPL_TIMING_ATTR(ConnectStart) diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index c8184a601..9aa696a70 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -506,7 +506,9 @@ protected: // the HTML file. nsString mInitiatorType; // Number of redirects that has occurred. - int16_t mRedirectCount; + int8_t mRedirectCount; + // Number of internal redirects that has occurred. + int8_t mInternalRedirectCount; // A time value equal to the starting time of the fetch that initiates the // redirect. mozilla::TimeStamp mRedirectStartTimeStamp; @@ -519,6 +521,12 @@ protected: TimeStamp mAsyncOpenTime; TimeStamp mCacheReadStart; TimeStamp mCacheReadEnd; + TimeStamp mLaunchServiceWorkerStart; + TimeStamp mLaunchServiceWorkerEnd; + TimeStamp mDispatchFetchEventStart; + TimeStamp mDispatchFetchEventEnd; + TimeStamp mHandleFetchEventStart; + TimeStamp mHandleFetchEventEnd; // copied from the transaction before we null out mTransaction // so that the timing can still be queried from OnStopRequest TimingStruct mTransactionTimings; diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index f0b9e2136..6d09135c4 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -2132,6 +2132,13 @@ HttpChannelChild::ContinueAsyncOpen() return NS_ERROR_FAILURE; } + openArgs.launchServiceWorkerStart() = mLaunchServiceWorkerStart; + openArgs.launchServiceWorkerEnd() = mLaunchServiceWorkerEnd; + openArgs.dispatchFetchEventStart() = mDispatchFetchEventStart; + openArgs.dispatchFetchEventEnd() = mDispatchFetchEventEnd; + openArgs.handleFetchEventStart() = mHandleFetchEventStart; + openArgs.handleFetchEventEnd() = mHandleFetchEventEnd; + // The socket transport in the chrome process now holds a logical ref to us // until OnStopRequest, or we do a redirect, or we hit an IPDL error. AddIPDLReference(); diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index 5f0859f28..90ed597a6 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -130,7 +130,13 @@ HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) a.initialRwin(), a.blockAuthPrompt(), a.suspendAfterSynthesizeResponse(), a.allowStaleCacheContent(), a.contentTypeHint(), - a.channelId(), a.contentWindowId(), a.preferredAlternativeType()); + a.channelId(), a.contentWindowId(), a.preferredAlternativeType(), + a.launchServiceWorkerStart(), + a.launchServiceWorkerEnd(), + a.dispatchFetchEventStart(), + a.dispatchFetchEventEnd(), + a.handleFetchEventStart(), + a.handleFetchEventEnd()); } case HttpChannelCreationArgs::THttpChannelConnectArgs: { @@ -329,7 +335,13 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, const nsCString& aContentTypeHint, const nsCString& aChannelId, const uint64_t& aContentWindowId, - const nsCString& aPreferredAlternativeType) + const nsCString& aPreferredAlternativeType, + const TimeStamp& aLaunchServiceWorkerStart, + const TimeStamp& aLaunchServiceWorkerEnd, + const TimeStamp& aDispatchFetchEventStart, + const TimeStamp& aDispatchFetchEventEnd, + const TimeStamp& aHandleFetchEventStart, + const TimeStamp& aHandleFetchEventEnd) { nsCOMPtr<nsIURI> uri = DeserializeURI(aURI); if (!uri) { @@ -534,6 +546,13 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, mChannel->SetInitialRwin(aInitialRwin); mChannel->SetBlockAuthPrompt(aBlockAuthPrompt); + mChannel->SetLaunchServiceWorkerStart(aLaunchServiceWorkerStart); + mChannel->SetLaunchServiceWorkerEnd(aLaunchServiceWorkerEnd); + mChannel->SetDispatchFetchEventStart(aDispatchFetchEventStart); + mChannel->SetDispatchFetchEventEnd(aDispatchFetchEventEnd); + mChannel->SetHandleFetchEventStart(aHandleFetchEventStart); + mChannel->SetHandleFetchEventEnd(aHandleFetchEventEnd); + nsCOMPtr<nsIApplicationCacheChannel> appCacheChan = do_QueryObject(mChannel); nsCOMPtr<nsIApplicationCacheService> appCacheService = @@ -1159,7 +1178,7 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) nsCString secInfoSerialization; UpdateAndSerializeSecurityInfo(secInfoSerialization); - uint16_t redirectCount = 0; + uint8_t redirectCount = 0; chan->GetRedirectCount(&redirectCount); nsCOMPtr<nsISupports> cacheKey; diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h index a3b377d49..56854bb55 100644 --- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -143,7 +143,13 @@ protected: const nsCString& aContentTypeHint, const nsCString& aChannelId, const uint64_t& aContentWindowId, - const nsCString& aPreferredAlternativeType); + const nsCString& aPreferredAlternativeType, + const TimeStamp& aLaunchServiceWorkerStart, + const TimeStamp& aLaunchServiceWorkerEnd, + const TimeStamp& aDispatchFetchEventStart, + const TimeStamp& aDispatchFetchEventEnd, + const TimeStamp& aHandleFetchEventStart, + const TimeStamp& aHandleFetchEventEnd); virtual bool RecvSetPriority(const uint16_t& priority) override; virtual bool RecvSetClassOfService(const uint32_t& cos) override; diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp index 9e38e2734..2dadbe760 100644 --- a/netwerk/protocol/http/InterceptedChannel.cpp +++ b/netwerk/protocol/http/InterceptedChannel.cpp @@ -10,6 +10,7 @@ #include "nsInputStreamPump.h" #include "nsIPipe.h" #include "nsIStreamListener.h" +#include "nsITimedChannel.h" #include "nsHttpChannel.h" #include "HttpChannelChild.h" #include "nsHttpResponseHead.h" @@ -134,6 +135,40 @@ InterceptedChannelBase::SetReleaseHandle(nsISupports* aHandle) return NS_OK; } +NS_IMETHODIMP +InterceptedChannelBase::SaveTimeStampsToUnderlyingChannel() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIChannel> underlyingChannel; + nsresult rv = GetChannel(getter_AddRefs(underlyingChannel)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsITimedChannel> timedChannel = + do_QueryInterface(underlyingChannel); + MOZ_ASSERT(timedChannel); + + rv = timedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + rv = timedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + rv = timedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + rv = timedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + rv = timedChannel->SetHandleFetchEventStart(mHandleFetchEventStart); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + rv = timedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + return rv; +} + /* static */ already_AddRefed<nsIURI> InterceptedChannelBase::SecureUpgradeChannelURI(nsIChannel* aChannel) diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h index 688f211de..efab7abca 100644 --- a/netwerk/protocol/http/InterceptedChannel.h +++ b/netwerk/protocol/http/InterceptedChannel.h @@ -46,6 +46,13 @@ protected: nsresult DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason); nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue); + TimeStamp mLaunchServiceWorkerStart; + TimeStamp mLaunchServiceWorkerEnd; + TimeStamp mDispatchFetchEventStart; + TimeStamp mDispatchFetchEventEnd; + TimeStamp mHandleFetchEventStart; + TimeStamp mHandleFetchEventEnd; + virtual ~InterceptedChannelBase(); public: explicit InterceptedChannelBase(nsINetworkInterceptController* aController); @@ -60,6 +67,50 @@ public: NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override; NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override; + NS_IMETHODIMP + SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) override + { + mLaunchServiceWorkerStart = aTimeStamp; + return NS_OK; + } + + NS_IMETHODIMP + SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) override + { + mLaunchServiceWorkerEnd = aTimeStamp; + return NS_OK; + } + + NS_IMETHODIMP + SetDispatchFetchEventStart(TimeStamp aTimeStamp) override + { + mDispatchFetchEventStart = aTimeStamp; + return NS_OK; + } + + NS_IMETHODIMP + SetDispatchFetchEventEnd(TimeStamp aTimeStamp) override + { + mDispatchFetchEventEnd = aTimeStamp; + return NS_OK; + } + + NS_IMETHODIMP + SetHandleFetchEventStart(TimeStamp aTimeStamp) override + { + mHandleFetchEventStart = aTimeStamp; + return NS_OK; + } + + NS_IMETHODIMP + SetHandleFetchEventEnd(TimeStamp aTimeStamp) override + { + mHandleFetchEventEnd = aTimeStamp; + return NS_OK; + } + + NS_IMETHODIMP SaveTimeStampsToUnderlyingChannel() override; + static already_AddRefed<nsIURI> SecureUpgradeChannelURI(nsIChannel* aChannel); }; diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp index 61efe3956..2954006ad 100644 --- a/netwerk/protocol/http/NullHttpChannel.cpp +++ b/netwerk/protocol/http/NullHttpChannel.cpp @@ -539,13 +539,25 @@ NullHttpChannel::SetTimingEnabled(bool aTimingEnabled) } NS_IMETHODIMP -NullHttpChannel::GetRedirectCount(uint16_t *aRedirectCount) +NullHttpChannel::GetRedirectCount(uint8_t *aRedirectCount) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP -NullHttpChannel::SetRedirectCount(uint16_t aRedirectCount) +NullHttpChannel::SetRedirectCount(uint8_t aRedirectCount) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullHttpChannel::GetInternalRedirectCount(uint8_t *aRedirectCount) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NullHttpChannel::SetInternalRedirectCount(uint8_t aRedirectCount) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -565,6 +577,90 @@ NullHttpChannel::GetAsyncOpen(mozilla::TimeStamp *aAsyncOpen) } NS_IMETHODIMP +NullHttpChannel::GetLaunchServiceWorkerStart(mozilla::TimeStamp *_retval) +{ + MOZ_ASSERT(_retval); + *_retval = mAsyncOpenTime; + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::SetLaunchServiceWorkerStart(mozilla::TimeStamp aTimeStamp) +{ + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::GetLaunchServiceWorkerEnd(mozilla::TimeStamp *_retval) +{ + MOZ_ASSERT(_retval); + *_retval = mAsyncOpenTime; + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::SetLaunchServiceWorkerEnd(mozilla::TimeStamp aTimeStamp) +{ + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::GetDispatchFetchEventStart(mozilla::TimeStamp *_retval) +{ + MOZ_ASSERT(_retval); + *_retval = mAsyncOpenTime; + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::SetDispatchFetchEventStart(mozilla::TimeStamp aTimeStamp) +{ + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::GetDispatchFetchEventEnd(mozilla::TimeStamp *_retval) +{ + MOZ_ASSERT(_retval); + *_retval = mAsyncOpenTime; + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::SetDispatchFetchEventEnd(mozilla::TimeStamp aTimeStamp) +{ + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::GetHandleFetchEventStart(mozilla::TimeStamp *_retval) +{ + MOZ_ASSERT(_retval); + *_retval = mAsyncOpenTime; + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::SetHandleFetchEventStart(mozilla::TimeStamp aTimeStamp) +{ + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::GetHandleFetchEventEnd(mozilla::TimeStamp *_retval) +{ + MOZ_ASSERT(_retval); + *_retval = mAsyncOpenTime; + return NS_OK; +} + +NS_IMETHODIMP +NullHttpChannel::SetHandleFetchEventEnd(mozilla::TimeStamp aTimeStamp) +{ + return NS_OK; +} + +NS_IMETHODIMP NullHttpChannel::GetDomainLookupStart(mozilla::TimeStamp *aDomainLookupStart) { *aDomainLookupStart = mAsyncOpenTime; @@ -761,6 +857,12 @@ NullHttpChannel::Get##name##Time(PRTime* _retval) { \ IMPL_TIMING_ATTR(ChannelCreation) IMPL_TIMING_ATTR(AsyncOpen) +IMPL_TIMING_ATTR(LaunchServiceWorkerStart) +IMPL_TIMING_ATTR(LaunchServiceWorkerEnd) +IMPL_TIMING_ATTR(DispatchFetchEventStart) +IMPL_TIMING_ATTR(DispatchFetchEventEnd) +IMPL_TIMING_ATTR(HandleFetchEventStart) +IMPL_TIMING_ATTR(HandleFetchEventEnd) IMPL_TIMING_ATTR(DomainLookupStart) IMPL_TIMING_ATTR(DomainLookupEnd) IMPL_TIMING_ATTR(ConnectStart) diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 94b0d9bf9..05699df62 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -5402,7 +5402,7 @@ nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII, locationBuf)) location = locationBuf; - if (mRedirectionLimit == 0) { + if (mRedirectCount >= mRedirectionLimit || mInternalRedirectCount >= mRedirectionLimit) { LOG(("redirection limit reached!\n")); return NS_ERROR_REDIRECT_LOOP; } diff --git a/old-configure.in b/old-configure.in index d8a6d01f0..7d336d37f 100644 --- a/old-configure.in +++ b/old-configure.in @@ -2256,6 +2256,7 @@ dnl ======================================================== MOZ_ARG_HEADER(Application) +ENABLE_TESTS= ENABLE_SYSTEM_EXTENSION_DIRS=1 MOZ_BRANDING_DIRECTORY= MOZ_OFFICIAL_BRANDING= @@ -3794,6 +3795,32 @@ if test -n "$MOZ_UPDATER"; then fi dnl ======================================================== +dnl Build the tests? +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(tests, +[ --enable-tests Build test libraries & programs], + ENABLE_TESTS=1, + ENABLE_TESTS= ) + +if test -n "$ENABLE_TESTS"; then + GTEST_HAS_RTTI=0 + AC_DEFINE(ENABLE_TESTS) + AC_DEFINE_UNQUOTED(GTEST_HAS_RTTI, 0) + AC_SUBST(GTEST_HAS_RTTI) + if test -n "$_WIN32_MSVC"; then + AC_DEFINE_UNQUOTED(_VARIADIC_MAX, 10) + fi + if test "${OS_TARGET}" = "Android"; then + AC_DEFINE(GTEST_OS_LINUX_ANDROID) + AC_DEFINE(GTEST_USE_OWN_TR1_TUPLE) + AC_DEFINE_UNQUOTED(GTEST_HAS_CLONE, 0) + AC_SUBST(GTEST_OS_LINUX_ANDROID) + AC_SUBST(GTEST_USE_OWN_TR1_TUPLE) + AC_SUBST(GTEST_HAS_CLONE) + fi +fi + +dnl ======================================================== dnl parental controls (for Windows Vista) dnl ======================================================== MOZ_ARG_DISABLE_BOOL(parental-controls, @@ -5245,6 +5272,8 @@ AC_SUBST(LIBICONV) AC_SUBST(MOZ_TOOLKIT_SEARCH) AC_SUBST(MOZ_FEEDS) +AC_SUBST(ENABLE_TESTS) + AC_SUBST(MOZ_UNIVERSALCHARDET) AC_SUBST(ACCESSIBILITY) AC_SUBST(MOZ_SPELLCHECK) diff --git a/security/manager/.eslintrc.js b/security/manager/.eslintrc.js index dc9d688aa..6b292f366 100644 --- a/security/manager/.eslintrc.js +++ b/security/manager/.eslintrc.js @@ -131,9 +131,6 @@ module.exports = { // eslint-disable-line no-undef // No reassigning native JS objects "no-native-reassign": "error", - // No (!foo in bar) - "no-negated-in-lhs": "error", - // Nested ternary statements are confusing "no-nested-ternary": "error", diff --git a/services/fxaccounts/FxAccountsProfileClient.jsm b/services/fxaccounts/FxAccountsProfileClient.jsm index 37115a3fa..1e5edc634 100644 --- a/services/fxaccounts/FxAccountsProfileClient.jsm +++ b/services/fxaccounts/FxAccountsProfileClient.jsm @@ -89,7 +89,7 @@ this.FxAccountsProfileClient.prototype = { try { return (yield this._rawRequest(path, method, token)); } catch (ex) { - if (!ex instanceof FxAccountsProfileClientError || ex.code != 401) { + if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) { throw ex; } // If this object was instantiated with a token then we don't refresh it. @@ -105,7 +105,7 @@ this.FxAccountsProfileClient.prototype = { try { return (yield this._rawRequest(path, method, token)); } catch (ex) { - if (!ex instanceof FxAccountsProfileClientError || ex.code != 401) { + if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) { throw ex; } log.info("Retry fetching the profile still returned a 401 - revoking our token and failing"); diff --git a/services/fxaccounts/FxAccountsStorage.jsm b/services/fxaccounts/FxAccountsStorage.jsm index 021763b92..43e2d21a0 100644 --- a/services/fxaccounts/FxAccountsStorage.jsm +++ b/services/fxaccounts/FxAccountsStorage.jsm @@ -403,7 +403,7 @@ this.FxAccountsStorageManager.prototype = { try { yield this.secureStorage.set(this.cachedPlain.uid, toWriteSecure); } catch (ex) { - if (!ex instanceof this.secureStorage.STORAGE_LOCKED) { + if (!(ex instanceof this.secureStorage.STORAGE_LOCKED)) { throw ex; } // This shouldn't be possible as once it is unlocked it can't be diff --git a/services/sync/modules/record.js b/services/sync/modules/record.js index 02f7f281a..f7a69d9ef 100644 --- a/services/sync/modules/record.js +++ b/services/sync/modules/record.js @@ -43,7 +43,7 @@ WBORecord.prototype = { // Get thyself from your URI, then deserialize. // Set thine 'response' field. fetch: function fetch(resource) { - if (!resource instanceof Resource) { + if (!(resource instanceof Resource)) { throw new Error("First argument must be a Resource instance."); } @@ -56,7 +56,7 @@ WBORecord.prototype = { }, upload: function upload(resource) { - if (!resource instanceof Resource) { + if (!(resource instanceof Resource)) { throw new Error("First argument must be a Resource instance."); } diff --git a/services/sync/modules/service.js b/services/sync/modules/service.js index b0eb0f41d..5c91323b5 100644 --- a/services/sync/modules/service.js +++ b/services/sync/modules/service.js @@ -455,7 +455,7 @@ Sync11Service.prototype = { this.clientsEngine = new ClientEngine(this); for (let name of engines) { - if (!name in ENGINE_MODULES) { + if (!(name in ENGINE_MODULES)) { this._log.info("Do not know about engine: " + name); continue; } diff --git a/services/sync/tps/extensions/mozmill/resource/driver/controller.js b/services/sync/tps/extensions/mozmill/resource/driver/controller.js index a378ce51f..8d66a41ae 100644 --- a/services/sync/tps/extensions/mozmill/resource/driver/controller.js +++ b/services/sync/tps/extensions/mozmill/resource/driver/controller.js @@ -978,7 +978,7 @@ function browserAdditions (controller) { }, "Timeout", timeout, aInterval); } catch (ex) { - if (!ex instanceof errors.TimeoutError) { + if (!(ex instanceof errors.TimeoutError)) { throw ex; } timed_out = true; diff --git a/services/sync/tps/extensions/mozmill/resource/modules/assertions.js b/services/sync/tps/extensions/mozmill/resource/modules/assertions.js index c9991acf0..c76f95747 100644 --- a/services/sync/tps/extensions/mozmill/resource/modules/assertions.js +++ b/services/sync/tps/extensions/mozmill/resource/modules/assertions.js @@ -659,7 +659,7 @@ Expect.prototype.waitFor = function Expect_waitFor(aCallback, aMessage, aTimeout Assert.prototype.waitFor.apply(this, arguments); } catch (ex) { - if (!ex instanceof errors.AssertionError) { + if (!(ex instanceof errors.AssertionError)) { throw ex; } message = ex.message; diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index ac2c1e077..d30ae89f5 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -182,6 +182,9 @@ user_pref("layout.css.prefixes.device-pixel-ratio-webkit", true); // Enable CSS shape-outside for testing user_pref("layout.css.shape-outside.enabled", true); +// Enable CSS text-justify for testing +user_pref("layout.css.text-justify.enabled", true); + // Disable spammy layout warnings because they pollute test logs user_pref("layout.spammy_warnings.enabled", false); diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index 3c7df67fa..496f8e3cb 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -31100,6 +31100,10 @@ "url": "/user-timing/test_user_timing_mark.html" }, { + "path": "user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html", + "url": "/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html" + }, + { "path": "user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html", "url": "/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html" }, diff --git a/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/keep-scheme-redirect/no-opt-in-blocks.https.html.ini b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/keep-scheme-redirect/no-opt-in-blocks.https.html.ini new file mode 100644 index 000000000..c65b27a08 --- /dev/null +++ b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/keep-scheme-redirect/no-opt-in-blocks.https.html.ini @@ -0,0 +1,3 @@ +[no-opt-in-blocks.https.html] + type: testharness + prefs: [security.mixed_content.send_hsts_priming:false, security.mixed_content.use_hsts:false] diff --git a/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini new file mode 100644 index 000000000..c65b27a08 --- /dev/null +++ b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini @@ -0,0 +1,3 @@ +[no-opt-in-blocks.https.html] + type: testharness + prefs: [security.mixed_content.send_hsts_priming:false, security.mixed_content.use_hsts:false] diff --git a/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/swap-scheme-redirect/no-opt-in-blocks.https.html.ini b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/swap-scheme-redirect/no-opt-in-blocks.https.html.ini new file mode 100644 index 000000000..c65b27a08 --- /dev/null +++ b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/iframe-tag/top-level/swap-scheme-redirect/no-opt-in-blocks.https.html.ini @@ -0,0 +1,3 @@ +[no-opt-in-blocks.https.html] + type: testharness + prefs: [security.mixed_content.send_hsts_priming:false, security.mixed_content.use_hsts:false] diff --git a/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/keep-scheme-redirect/no-opt-in-blocks.https.html.ini b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/keep-scheme-redirect/no-opt-in-blocks.https.html.ini new file mode 100644 index 000000000..c65b27a08 --- /dev/null +++ b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/keep-scheme-redirect/no-opt-in-blocks.https.html.ini @@ -0,0 +1,3 @@ +[no-opt-in-blocks.https.html] + type: testharness + prefs: [security.mixed_content.send_hsts_priming:false, security.mixed_content.use_hsts:false] diff --git a/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini new file mode 100644 index 000000000..c65b27a08 --- /dev/null +++ b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini @@ -0,0 +1,3 @@ +[no-opt-in-blocks.https.html] + type: testharness + prefs: [security.mixed_content.send_hsts_priming:false, security.mixed_content.use_hsts:false] diff --git a/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/swap-scheme-redirect/no-opt-in-blocks.https.html.ini b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/swap-scheme-redirect/no-opt-in-blocks.https.html.ini new file mode 100644 index 000000000..c65b27a08 --- /dev/null +++ b/testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/swap-scheme-redirect/no-opt-in-blocks.https.html.ini @@ -0,0 +1,3 @@ +[no-opt-in-blocks.https.html] + type: testharness + prefs: [security.mixed_content.send_hsts_priming:false, security.mixed_content.use_hsts:false] diff --git a/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini b/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini index b399d5f38..b6f02262b 100644 --- a/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini +++ b/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini @@ -1,9 +1,2 @@ [resource-timing.https.html] type: testharness - disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1178713 - [Controlled resource loads] - expected: FAIL - - [Non-controlled resource loads] - expected: FAIL - diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html index cc1dac472..fd7419832 100644 --- a/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html @@ -110,7 +110,8 @@ async_test(function(t) { frame.src = scope + '?mode=cors&url=' + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path + - '?ACAOrigin=' + host_info['HTTPS_ORIGIN']); + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true'); document.body.appendChild(frame); return getLoadedFrameAsObject(frame); }) @@ -183,7 +184,8 @@ async_test(function(t) { var win = window.open( scope + '?mode=cors&url=' + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path + - '?ACAOrigin=' + host_info['HTTPS_ORIGIN'])); + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true')); return getLoadedWindowAsObject(win); }) .then(function(result) { diff --git a/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html index f33c41d71..3a1adfa48 100644 --- a/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html +++ b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html @@ -13,6 +13,10 @@ function verify(performance, resource, description) { assert_greater_than(entry.workerStart, 0, description); assert_greater_than_equal(entry.workerStart, entry.startTime, description); assert_less_than_equal(entry.workerStart, entry.fetchStart, description); + assert_greater_than_equal(entry.responseStart, entry.fetchStart, description); + assert_greater_than_equal(entry.responseEnd, entry.responseStart, description); + assert_greater_than(entry.responseEnd, entry.fetchStart, description); + assert_greater_than(entry.duration, 0, description); if (resource.indexOf('redirect.py') != -1) { assert_less_than_equal(entry.workerStart, entry.redirectStart, description); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js index 481a6536a..452876838 100644 --- a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js +++ b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js @@ -1,5 +1,9 @@ self.addEventListener('fetch', function(event) { if (event.request.url.indexOf('dummy.js') != -1) { - event.respondWith(new Response()); + event.respondWith(new Promise(resolve => { + // Slightly delay the response so we ensure we get a non-zero + // duration. + setTimeout(_ => resolve(new Response()), 100); + })); } }); diff --git a/testing/web-platform/tests/user-timing/resources/webperftestharness.js b/testing/web-platform/tests/user-timing/resources/webperftestharness.js index 750946dde..f1597bbe7 100644 --- a/testing/web-platform/tests/user-timing/resources/webperftestharness.js +++ b/testing/web-platform/tests/user-timing/resources/webperftestharness.js @@ -12,7 +12,7 @@ policies and contribution forms [3]. // Helper Functions for NavigationTiming W3C tests // -var performanceNamespace = window.performance; +var performanceNamespace = self.performance; var timingAttributes = [ 'connectEnd', 'connectStart', diff --git a/testing/web-platform/tests/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html b/testing/web-platform/tests/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html new file mode 100644 index 000000000..aea8cb6e9 --- /dev/null +++ b/testing/web-platform/tests/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>exception test of performance.mark and performance.measure</title> + <meta rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/webperftestharness.js"></script> + </head> + <body> + <script> + setup({explicit_done: true}); + test_namespace(); + + test(function() { + for (var i in timingAttributes) { + assert_throws("SyntaxError", function() { window.performance.mark(timingAttributes[i]); }); + assert_throws("SyntaxError", function() { window.performance.measure(timingAttributes[i]); }); + } + }, "performance.mark and performance.measure should throw if used with timing attribute values"); + + fetch_tests_from_worker(new Worker("test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js")); + + done(); + + </script> + <h1>Description</h1> + <p>This test validates exception scenarios of invoking mark() and measure() with timing attributes as value.</p> + <div id="log"></div> + </body> +</html> diff --git a/testing/web-platform/tests/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js b/testing/web-platform/tests/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js new file mode 100644 index 000000000..f015402f9 --- /dev/null +++ b/testing/web-platform/tests/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js @@ -0,0 +1,14 @@ +importScripts("/resources/testharness.js"); +importScripts("resources/webperftestharness.js"); + +test(function() { + for (var i in timingAttributes) { + performance.mark(timingAttributes[i]); + performance.clearMarks(timingAttributes[i]); + + performance.measure(timingAttributes[i]); + performance.clearMeasures(timingAttributes[i]); + } +}, "performance.mark and performance.measure should not throw if used with timing attribute values in workers"); + +done(); diff --git a/toolkit/.eslintrc.js b/toolkit/.eslintrc.js index 181f19f29..891a114b3 100644 --- a/toolkit/.eslintrc.js +++ b/toolkit/.eslintrc.js @@ -111,9 +111,6 @@ module.exports = { // No reassigning native JS objects "no-native-reassign": "error", - // No (!foo in bar) - "no-negated-in-lhs": "error", - // Nested ternary statements are confusing "no-nested-ternary": "error", diff --git a/toolkit/components/osfile/modules/osfile_unix_front.jsm b/toolkit/components/osfile/modules/osfile_unix_front.jsm index 19a27ae1a..bd60d4d84 100644 --- a/toolkit/components/osfile/modules/osfile_unix_front.jsm +++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm @@ -838,7 +838,7 @@ * implementation. */ File.DirectoryIterator.Entry.toMsg = function toMsg(value) { - if (!value instanceof File.DirectoryIterator.Entry) { + if (!(value instanceof File.DirectoryIterator.Entry)) { throw new TypeError("parameter of " + "File.DirectoryIterator.Entry.toMsg must be a " + "File.DirectoryIterator.Entry"); @@ -905,7 +905,7 @@ * is asymmetric and returns an object with a different implementation. */ File.Info.toMsg = function toMsg(stat) { - if (!stat instanceof File.Info) { + if (!(stat instanceof File.Info)) { throw new TypeError("parameter of File.Info.toMsg must be a File.Info"); } let serialized = {}; diff --git a/toolkit/components/osfile/modules/osfile_win_front.jsm b/toolkit/components/osfile/modules/osfile_win_front.jsm index 387dd08b5..1123b251c 100644 --- a/toolkit/components/osfile/modules/osfile_win_front.jsm +++ b/toolkit/components/osfile/modules/osfile_win_front.jsm @@ -909,7 +909,7 @@ * implementation. */ File.DirectoryIterator.Entry.toMsg = function toMsg(value) { - if (!value instanceof File.DirectoryIterator.Entry) { + if (!(value instanceof File.DirectoryIterator.Entry)) { throw new TypeError("parameter of " + "File.DirectoryIterator.Entry.toMsg must be a " + "File.DirectoryIterator.Entry"); @@ -958,7 +958,7 @@ * is asymmetric and returns an object with a different implementation. */ File.Info.toMsg = function toMsg(stat) { - if (!stat instanceof File.Info) { + if (!(stat instanceof File.Info)) { throw new TypeError("parameter of File.Info.toMsg must be a File.Info"); } let serialized = {}; diff --git a/toolkit/components/reader/.eslintrc.js b/toolkit/components/reader/.eslintrc.js index 1c09e0bf7..617c1f9cf 100644 --- a/toolkit/components/reader/.eslintrc.js +++ b/toolkit/components/reader/.eslintrc.js @@ -109,9 +109,6 @@ module.exports = { // No reassigning native JS objects "no-native-reassign": "error", - // No (!foo in bar) - "no-negated-in-lhs": "error", - // Nested ternary statements are confusing "no-nested-ternary": "error", diff --git a/toolkit/components/search/nsSearchService.js b/toolkit/components/search/nsSearchService.js index fb3f69f4c..9f9003516 100644 --- a/toolkit/components/search/nsSearchService.js +++ b/toolkit/components/search/nsSearchService.js @@ -23,8 +23,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", - "resource://gre/modules/Deprecated.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SearchStaticData", "resource://gre/modules/SearchStaticData.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "setTimeout", @@ -2695,13 +2693,6 @@ SearchService.prototype = { return; } - let warning = - "Search service falling back to synchronous initialization. " + - "This is generally the consequence of an add-on using a deprecated " + - "search service API."; - Deprecated.warning(warning, "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIBrowserSearchService#async_warning"); - LOG(warning); - this._syncInit(); if (!Components.isSuccessCode(this._initRV)) { throw this._initRV; diff --git a/toolkit/components/viewsource/content/viewSource-content.js b/toolkit/components/viewsource/content/viewSource-content.js index fa1dd19f1..70d23eaa4 100644 --- a/toolkit/components/viewsource/content/viewSource-content.js +++ b/toolkit/components/viewsource/content/viewSource-content.js @@ -330,6 +330,8 @@ var ViewSourceContent = { .createInstance(Ci.nsISHEntry); shEntry.setURI(BrowserUtils.makeURI(viewSrcURL, null, null)); shEntry.setTitle(viewSrcURL); + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + shEntry.triggeringPrincipal = systemPrincipal; shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory; shEntry.cacheKey = shEntrySource.cacheKey; docShell.QueryInterface(Ci.nsIWebNavigation) diff --git a/toolkit/components/webextensions/.eslintrc.js b/toolkit/components/webextensions/.eslintrc.js index 70196fc6a..70f91ab6d 100644 --- a/toolkit/components/webextensions/.eslintrc.js +++ b/toolkit/components/webextensions/.eslintrc.js @@ -173,9 +173,6 @@ module.exports = { // eslint-disable-line no-undef // No reassigning native JS objects "no-native-reassign": "error", - // No (!foo in bar) - "no-negated-in-lhs": "error", - // Nested ternary statements are confusing "no-nested-ternary": "error", diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index a5f37b62a..e595c847d 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -135,6 +135,7 @@ aURI = "about:blank"; var aReferrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT; + var aTriggeringPrincipal; // Check for loadURIWithFlags(uri, { ... }); var params = arguments[1]; @@ -144,6 +145,9 @@ if ('referrerPolicy' in params) { aReferrerPolicy = params.referrerPolicy; } + if ("triggeringPrincipal" in params) { + aTriggeringPrincipal = params.triggeringPrincipal; + } aCharset = params.charset; aPostData = params.postData; } @@ -151,7 +155,7 @@ this._wrapURIChangeCall(() => this.webNavigation.loadURIWithOptions( aURI, aFlags, aReferrerURI, aReferrerPolicy, - aPostData, null, null)); + aPostData, null, null, aTriggeringPrincipal)); ]]> </body> </method> diff --git a/toolkit/content/widgets/datetimepopup.xml b/toolkit/content/widgets/datetimepopup.xml index b4335e1ce..19e7b4f8a 100644 --- a/toolkit/content/widgets/datetimepopup.xml +++ b/toolkit/content/widgets/datetimepopup.xml @@ -26,8 +26,8 @@ this.l10n = {}; const mozIntl = Components.classes["@mozilla.org/mozintl;1"] .getService(Components.interfaces.mozIMozIntl); - mozIntl.addGetCalendarInfo(l10n); - mozIntl.addGetDisplayNames(l10n); + mozIntl.addGetCalendarInfo(this.l10n); + mozIntl.addGetDisplayNames(this.l10n); // Notify DateTimePickerHelper.jsm that binding is ready. this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady")); ]]></constructor> diff --git a/toolkit/jetpack/dev/volcan.js b/toolkit/jetpack/dev/volcan.js index 6a68ed12d..7ec208eec 100644 --- a/toolkit/jetpack/dev/volcan.js +++ b/toolkit/jetpack/dev/volcan.js @@ -139,7 +139,7 @@ var Client = Class({ .then(this.onReady.bind(this, this.root), this.onFail); } else { var actor = this.get(packet.from) || this.root; - var event = actor.events[packet.type]; + event = actor.events[packet.type]; if (event) { var message = new MessageEvent(packet.type, { data: event.read(packet) diff --git a/toolkit/jetpack/sdk/lang/weak-set.js b/toolkit/jetpack/sdk/lang/weak-set.js index 8972602a5..0b81a395b 100644 --- a/toolkit/jetpack/sdk/lang/weak-set.js +++ b/toolkit/jetpack/sdk/lang/weak-set.js @@ -2,12 +2,12 @@ * 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"; + module.metadata = { "stability": "experimental" }; -"use strict"; - const { Cu } = require("chrome"); function makeGetterFor(Type) { diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd index c74fdeb2f..febc18dfd 100644 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd +++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd @@ -1,6 +1,7 @@ <!-- 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/. --> + <!ENTITY addons.windowTitle "Add-ons Manager"> <!ENTITY search.placeholder "Search all add-ons"> @@ -210,7 +211,7 @@ <!ENTITY addon.loadingReleaseNotes.label "Loading…"> <!ENTITY addon.errorLoadingReleaseNotes.label "Sorry, but there was an error loading the release notes."> -<!ENTITY addon.nativeAddon "This add-on directly targets Pale Moon"> +<!ENTITY addon.nativeAddon "This add-on directly targets &brandFullName;"> <!ENTITY addon.compatAddon "This add-on targets Mozilla Firefox and runs in compatibility mode"> <!ENTITY addon.createdBy.label "By "> diff --git a/toolkit/modules/Sqlite.jsm b/toolkit/modules/Sqlite.jsm index e8d986c0e..6f7a7d94c 100644 --- a/toolkit/modules/Sqlite.jsm +++ b/toolkit/modules/Sqlite.jsm @@ -995,7 +995,7 @@ function cloneStorageConnection(options) { if (!source) { throw new TypeError("connection not specified in clone options."); } - if (!source instanceof Ci.mozIStorageAsyncConnection) { + if (!(source instanceof Ci.mozIStorageAsyncConnection)) { throw new TypeError("Connection must be a valid Storage connection."); } diff --git a/toolkit/modules/sessionstore/Utils.jsm b/toolkit/modules/sessionstore/Utils.jsm index 863bca6f5..25b75c71b 100644 --- a/toolkit/modules/sessionstore/Utils.jsm +++ b/toolkit/modules/sessionstore/Utils.jsm @@ -16,12 +16,17 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); XPCOMUtils.defineLazyServiceGetter(this, "serializationHelper", "@mozilla.org/network/serialization-helper;1", "nsISerializationHelper"); +XPCOMUtils.defineLazyGetter(this, "SERIALIZED_SYSTEMPRINCIPAL", function() { + return Utils.serializePrincipal(Services.scriptSecurityManager.getSystemPrincipal()); +}); function debug(msg) { Services.console.logStringMessage("Utils: " + msg); } this.Utils = Object.freeze({ + get SERIALIZED_SYSTEMPRINCIPAL() { return SERIALIZED_SYSTEMPRINCIPAL; }, + makeURI: function (url) { return Services.io.newURI(url, null, null); }, diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 3913c2088..d4c3a6967 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2671,6 +2671,8 @@ this.AddonManager = { ERROR_CORRUPT_FILE: -3, // An error occured trying to write to the filesystem. ERROR_FILE_ACCESS: -4, + // The downloaded file seems to be Jetpack. + ERROR_JETPACKSDK_FILE: -8, // The downloaded file seems to be WebExtension. ERROR_WEBEXT_FILE: -9, diff --git a/toolkit/mozapps/extensions/AddonPathService.cpp b/toolkit/mozapps/extensions/AddonPathService.cpp index 006149100..ddfdbe817 100644 --- a/toolkit/mozapps/extensions/AddonPathService.cpp +++ b/toolkit/mozapps/extensions/AddonPathService.cpp @@ -128,6 +128,16 @@ AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdStri return NS_OK; } +NS_IMETHODIMP +AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) +{ + if (JSAddonId* id = MapURIToAddonID(aURI)) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); + AssignJSFlatString(addonIdString, flat); + } + return NS_OK; +} + static nsresult ResolveURI(nsIURI* aURI, nsAString& out) { diff --git a/toolkit/mozapps/extensions/amIAddonPathService.idl b/toolkit/mozapps/extensions/amIAddonPathService.idl index 863689858..9c9197a61 100644 --- a/toolkit/mozapps/extensions/amIAddonPathService.idl +++ b/toolkit/mozapps/extensions/amIAddonPathService.idl @@ -5,6 +5,8 @@ #include "nsISupports.idl" +interface nsIURI; + /** * This service maps file system paths where add-ons reside to the ID * of the add-on. Paths are added by the add-on manager. They can @@ -26,4 +28,10 @@ interface amIAddonPathService : nsISupports * associated with the given add-on ID. */ void insertPath(in AString path, in AString addonId); + + /** + * Given a URI to a file, return the ID of the add-on that the file belongs + * to. Returns an empty string if there is no add-on there. + */ + AString mapURIToAddonId(in nsIURI aURI); }; diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index 8d9c132e6..fc4392231 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -18,10 +18,12 @@ Cu.import("resource://gre/modules/addons/AddonRepository.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); +#ifdef MOZ_DEVTOOLS XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () { return Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}). BrowserToolboxProcess; }); +#endif const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; @@ -1002,6 +1004,7 @@ var gViewController = { } }, +#ifdef MOZ_DEVTOOLS cmd_debugItem: { doCommand: function cmd_debugItem_doCommand(aAddon) { BrowserToolboxProcess.init({ addonID: aAddon.id }); @@ -1015,6 +1018,7 @@ var gViewController = { return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled; } }, +#endif cmd_showItemPreferences: { isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) { diff --git a/toolkit/mozapps/extensions/content/extensions.xml b/toolkit/mozapps/extensions/content/extensions.xml index fab340540..cbd05bfa9 100644 --- a/toolkit/mozapps/extensions/content/extensions.xml +++ b/toolkit/mozapps/extensions/content/extensions.xml @@ -9,6 +9,8 @@ %extensionsDTD; <!ENTITY % aboutDTD SYSTEM "chrome://mozapps/locale/extensions/about.dtd"> %aboutDTD; +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; ]> <bindings id="addonBindings" @@ -855,8 +857,10 @@ <xul:label anonid="name" class="name" crop="end" flex="1" xbl:inherits="value=name,tooltiptext=name"/> <xul:label anonid="version" class="version"/> +#ifdef MOZ_PHOENIX_EXTENSIONS <xul:label class="nativeIndicator nativeAddon" value="●" tooltiptext="&addon.nativeAddon;"/> <xul:label class="nativeIndicator compatAddon" value="●" tooltiptext="&addon.compatAddon;"/> +#endif <xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/> <xul:label class="update-postfix" value="&addon.update.postfix;"/> <xul:spacer flex="5000"/> <!-- Necessary to make the name crop --> @@ -1362,8 +1366,10 @@ [this.mAddon.name], 1); } else { this.removeAttribute("notification"); +#ifdef MOZ_PHOENIX_EXTENSIONS if (this.mAddon.type == "extension") this.setAttribute("native", this.mAddon.native); +#endif } } @@ -1622,11 +1628,13 @@ ]]></body> </method> +#ifdef MOZ_DEVTOOLS <method name="debug"> <body><![CDATA[ gViewController.doCommand("cmd_debugItem", this.mAddon); ]]></body> </method> +#endif <method name="showPreferences"> <body><![CDATA[ diff --git a/toolkit/mozapps/extensions/content/extensions.xul b/toolkit/mozapps/extensions/content/extensions.xul index c1a8edc86..c5eeb534f 100644 --- a/toolkit/mozapps/extensions/content/extensions.xul +++ b/toolkit/mozapps/extensions/content/extensions.xul @@ -53,8 +53,10 @@ <menuitem id="menuitem_uninstallItem" command="cmd_uninstallItem" label="&cmd.uninstallAddon.label;" accesskey="&cmd.uninstallAddon.accesskey;"/> +#ifdef MOZ_DEVTOOLS <menuitem id="menuitem_debugItem" command="cmd_debugItem" label="&cmd.debugAddon.label;"/> +#endif <menuseparator id="addonitem-menuseparator" /> <menuitem id="menuitem_preferences" command="cmd_showItemPreferences" #ifdef XP_WIN @@ -101,7 +103,9 @@ <command id="cmd_findItemUpdates"/> <command id="cmd_showItemPreferences"/> <command id="cmd_showItemAbout"/> +#ifdef MOZ_DEVTOOLS <command id="cmd_debugItem"/> +#endif <command id="cmd_enableItem"/> <command id="cmd_disableItem"/> <command id="cmd_installItem"/> @@ -631,9 +635,11 @@ #endif command="cmd_showItemPreferences"/> <spacer flex="1"/> +#ifdef MOZ_DEVTOOLS <button id="detail-debug-btn" class="addon-control debug" label="Debug" command="cmd_debugItem" /> +#endif <button id="detail-enable-btn" class="addon-control enable" label="&cmd.enableAddon.label;" accesskey="&cmd.enableAddon.accesskey;" diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm index 939e2e269..8d742ea42 100644 --- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm @@ -766,8 +766,17 @@ this.AddonUpdateChecker = { * down in-progress update requests */ checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl, aObserver) { - // Exclude default theme - if (aId != "{972ce4c6-7e08-4474-a285-3208198ce6fd}") + // Define an array of internally used IDs to NOT send to AUS such as the + // Default Theme. Please keep this list in sync with: + // toolkit/mozapps/webextensions/AddonUpdateChecker.jsm + let internalIDS = [ + '{972ce4c6-7e08-4474-a285-3208198ce6fd}', + 'modern@themes.mozilla.org' + ]; + + // If the ID is not in the array then go ahead and query AUS + if (internalIDS.indexOf(aId) == -1) { return new UpdateParser(aId, aUpdateKey, aUrl, aObserver); + } } }; diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index c43811ba8..5b3585cd8 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -36,8 +36,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); +#ifdef MOZ_DEVTOOLS XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm"); +#endif XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI", "resource://gre/modules/Console.jsm"); @@ -52,6 +54,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "ResProtocolHandler", "@mozilla.org/network/protocol;1?name=resource", "nsIResProtocolHandler"); +XPCOMUtils.defineLazyServiceGetter(this, + "AddonPathService", + "@mozilla.org/addon-path-service;1", + "amIAddonPathService"); const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); @@ -106,6 +112,10 @@ const DIR_TRASH = "trash"; const FILE_DATABASE = "extensions.json"; const FILE_OLD_CACHE = "extensions.cache"; const FILE_INSTALL_MANIFEST = "install.rdf"; +#ifndef MOZ_JETPACK +const FILE_JETPACK_MANIFEST_1 = "harness-options.json"; +const FILE_JETPACK_MANIFEST_2 = "package.json"; +#endif const FILE_WEBEXT_MANIFEST = "manifest.json"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; @@ -134,7 +144,9 @@ const FIREFOX_APPCOMPATVERSION = "56.9" // The value for this is in Makefile.in #expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__; +#ifdef MOZ_DEVTOOLS const NOTIFICATION_TOOLBOXPROCESS_LOADED = "ToolboxProcessLoaded"; +#endif // Properties that exist in the install manifest const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL", @@ -1059,37 +1071,36 @@ function loadManifestFromDir(aDir) { * @throws if the XPI file does not contain a valid install manifest. * Throws with |webext:true| if a WebExtension manifest was found * to distinguish between WebExtensions and corrupt files. + * Throws with |jetpacksdk:true| if a Jetpack files were found + * if Jetpack its self isn't built. */ function loadManifestFromZipReader(aZipReader) { - let zis; - try { - zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST); - } catch (e) { - // We're going to throw here, but depending on whether we have a - // WebExtension manifest in the XPI, we'll throw with the webext flag. - try { - let zws = aZipReader.getInputStream(FILE_WEBEXT_MANIFEST); - zws.close(); - } catch(e2) { - // We have neither an install manifest nor a WebExtension manifest; - // this means the extension file has a structural problem. - // Just pass the original error up the chain in that case. + // If WebExtension but not install.rdf throw an error + if (aZipReader.hasEntry(FILE_WEBEXT_MANIFEST)) { + if (!aZipReader.hasEntry(FILE_INSTALL_MANIFEST)) { throw { - name: e.name, - message: e.message + name: "UnsupportedExtension", + message: Services.appinfo.name + " does not support WebExtensions", + webext: true }; } - // If we get here, we have a WebExtension manifest but no install - // manifest. Pass the error up the chain with the webext flag. + } + +#ifndef MOZ_JETPACK + // If Jetpack is not built throw an error + if (aZipReader.hasEntry(FILE_JETPACK_MANIFEST_1) || + aZipReader.hasEntry(FILE_JETPACK_MANIFEST_2)) { throw { - name: e.name, - message: e.message, - webext: true + name: "UnsupportedExtension", + message: Services.appinfo.name + " does not support Jetpack Extensions", + jetpacksdk: true }; } - - // We found an install manifest, so it's either a regular or hybrid - // extension. Continue processing. +#endif + + // Attempt to open install.rdf else throw normally + let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST); + // Create a buffered input stream for install.rdf let bis = Cc["@mozilla.org/network/buffered-input-stream;1"]. createInstance(Ci.nsIBufferedInputStream); bis.init(zis, 4096); @@ -1118,7 +1129,9 @@ function loadManifestFromZipReader(aZipReader) { return addon; } finally { + // Close the buffered input stream bis.close(); + // Close the input stream to install.rdf zis.close(); } } @@ -1842,8 +1855,10 @@ this.XPIProvider = { _enabledExperiments: null, // A Map from an add-on install to its ID _addonFileMap: new Map(), +#ifdef MOZ_DEVTOOLS // Flag to know if ToolboxProcess.jsm has already been loaded by someone or not _toolboxProcessLoaded: false, +#endif // Have we started shutting down bootstrap add-ons? _closing: false, @@ -1887,8 +1902,7 @@ this.XPIProvider = { logger.info("Mapping " + aID + " to " + aFile.path); this._addonFileMap.set(aID, aFile.path); - let service = Cc["@mozilla.org/addon-path-service;1"].getService(Ci.amIAddonPathService); - service.insertPath(aFile.path, aID); + AddonPathService.insertPath(aFile.path, aID); }, /** @@ -2080,6 +2094,8 @@ this.XPIProvider = { Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false); Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false); Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false); + +#ifdef MOZ_DEVTOOLS if (Cu.isModuleLoaded("resource://devtools/client/framework/ToolboxProcess.jsm")) { // If BrowserToolboxProcess is already loaded, set the boolean to true // and do whatever is needed @@ -2091,6 +2107,7 @@ this.XPIProvider = { // Else, wait for it to load Services.obs.addObserver(this, NOTIFICATION_TOOLBOXPROCESS_LOADED, false); } +#endif let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion); @@ -3916,16 +3933,8 @@ this.XPIProvider = { * @see amIAddonManager.mapURIToAddonID */ mapURIToAddonID: function XPI_mapURIToAddonID(aURI) { - let resolved = this._resolveURIToFile(aURI); - if (!resolved || !(resolved instanceof Ci.nsIFileURL)) - return null; - - for (let [id, path] of this._addonFileMap) { - if (resolved.file.path.startsWith(path)) - return id; - } - - return null; + // Returns `null` instead of empty string if the URI can't be mapped. + return AddonPathService.mapURIToAddonId(aURI) || null; }, /** @@ -4093,12 +4102,14 @@ this.XPIProvider = { } return; } +#ifdef MOZ_DEVTOOLS else if (aTopic == NOTIFICATION_TOOLBOXPROCESS_LOADED) { Services.obs.removeObserver(this, NOTIFICATION_TOOLBOXPROCESS_LOADED, false); this._toolboxProcessLoaded = true; BrowserToolboxProcess.on("connectionchange", this.onDebugConnectionChange.bind(this)); } +#endif if (aTopic == "nsPref:changed") { switch (aData) { @@ -4363,12 +4374,14 @@ this.XPIProvider = { logger.warn("Error loading bootstrap.js for " + aId, e); } +#ifdef MOZ_DEVTOOLS // Only access BrowserToolboxProcess if ToolboxProcess.jsm has been // initialized as otherwise, when it will be initialized, all addons' // globals will be added anyways if (this._toolboxProcessLoaded) { BrowserToolboxProcess.setAddonOptions(aId, { global: this.bootstrapScopes[aId] }); } +#endif }, /** @@ -4388,11 +4401,13 @@ this.XPIProvider = { this.persistBootstrappedAddons(); this.addAddonsToCrashReporter(); +#ifdef MOZ_DEVTOOLS // Only access BrowserToolboxProcess if ToolboxProcess.jsm has been // initialized as otherwise, there won't be any addon globals added to it if (this._toolboxProcessLoaded) { BrowserToolboxProcess.setAddonOptions(aId, { global: null }); } +#endif }, /** @@ -5010,6 +5025,11 @@ AddonInstall.prototype = { if (e.webext) { logger.warn("WebExtension XPI", e); this.error = AddonManager.ERROR_WEBEXT_FILE; +#ifndef MOZ_JETPACK + } else if (e.jetpacksdk) { + logger.warn("Jetpack XPI", e); + this.error = AddonManager.ERROR_JETPACKSDK_FILE; +#endif } else { logger.warn("Invalid XPI", e); this.error = AddonManager.ERROR_CORRUPT_FILE; @@ -5651,6 +5671,10 @@ AddonInstall.prototype = { catch (e) { if (e.webext) { this.downloadFailed(AddonManager.ERROR_WEBEXT_FILE, e); +#ifndef MOZ_JETPACK + } else if (e.jetpacksdk) { + this.downloadFailed(AddonManager.ERROR_JETPACKSDK_FILE, e); +#endif } else { this.downloadFailed(AddonManager.ERROR_CORRUPT_FILE, e); } @@ -6447,21 +6471,23 @@ AddonInternal.prototype = { if (!aPlatformVersion) aPlatformVersion = Services.appinfo.platformVersion; +#ifdef MOZ_PHOENIX_EXTENSIONS this.native = false; - +#endif + let version; if (app.id == Services.appinfo.ID) { version = aAppVersion; +#ifdef MOZ_PHOENIX_EXTENSIONS this.native = true; } -#ifdef MOZ_PHOENIX_EXTENSIONS else if (app.id == FIREFOX_ID) { version = FIREFOX_APPCOMPATVERSION; if (this.type == "locale") //Never allow language packs in Firefox compatibility mode return false; - } #endif + } else if (app.id == TOOLKIT_ID) version = aPlatformVersion diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index d26029455..6b37ed640 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -70,7 +70,11 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "skinnable", "size", "sourceURI", "releaseNotesURI", "softDisabled", "foreignInstall", "hasBinaryComponents", "strictCompatibility", "locales", "targetApplications", - "targetPlatforms", "multiprocessCompatible", "native"]; + "targetPlatforms", "multiprocessCompatible", +#ifdef MOZ_PHOENIX_EXTENSIONS + "native" +#endif + ]; // Time to wait before async save of XPI JSON database, in milliseconds const ASYNC_SAVE_DELAY_MS = 20; diff --git a/toolkit/mozapps/extensions/nsBlocklistService.js b/toolkit/mozapps/extensions/nsBlocklistService.js index 936c9d1b5..487dae8e5 100644 --- a/toolkit/mozapps/extensions/nsBlocklistService.js +++ b/toolkit/mozapps/extensions/nsBlocklistService.js @@ -910,7 +910,7 @@ Blocklist.prototype = { let issuer = blocklistElement.getAttribute("issuerName"); for (let snElement of blocklistElement.children) { try { - gCertBlocklistService.addRevokedCert(issuer, snElement.textContent); + gCertBlocklistService.revokeCertByIssuerAndSerial(issuer, snElement.textContent); } catch (e) { // we want to keep trying other elements since missing all items // is worse than missing one diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js b/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js index a6f9c8052..a153256dc 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js @@ -95,8 +95,10 @@ function run_test_early() { "resource://gre/modules/addons/XPIProvider.jsm", {}); // Make the early API call. - do_check_null(s.XPIProvider.mapURIToAddonID(uri)); + // AddonManager still misses its provider and so doesn't work yet. do_check_null(AddonManager.mapURIToAddonID(uri)); + // But calling XPIProvider directly works immediately + do_check_eq(s.XPIProvider.mapURIToAddonID(uri), id); // Actually start up the manager. startupManager(false); diff --git a/toolkit/mozapps/webextensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/webextensions/internal/AddonUpdateChecker.jsm index 918ba5328..bdd3a81e7 100644 --- a/toolkit/mozapps/webextensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/webextensions/internal/AddonUpdateChecker.jsm @@ -929,8 +929,17 @@ this.AddonUpdateChecker = { * down in-progress update requests */ checkForUpdates: function(aId, aUpdateKey, aUrl, aObserver) { - // Exclude default theme - if (aId != "{972ce4c6-7e08-4474-a285-3208198ce6fd}") + // Define an array of internally used IDs to NOT send to AUS such as the + // Default Theme. Please keep this list in sync with: + // toolkit/mozapps/extensions/AddonUpdateChecker.jsm + let internalIDS = [ + '{972ce4c6-7e08-4474-a285-3208198ce6fd}', + 'modern@themes.mozilla.org' + ]; + + // If the ID is not in the array then go ahead and query AUS + if (internalIDS.indexOf(aId) == -1) { return new UpdateParser(aId, aUpdateKey, aUrl, aObserver); + } } }; diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild index 3b3bf80ae..b06c58162 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -132,8 +132,7 @@ DIRS += [ if CONFIG['MOZ_PREF_EXTENSIONS']: DIRS += ['/extensions/pref'] -if CONFIG['MOZ_DEVTOOLS_SERVER']: - DIRS += ['/devtools'] +DIRS += ['/devtools'] DIRS += [ '/services', diff --git a/uriloader/base/nsURILoader.cpp b/uriloader/base/nsURILoader.cpp index 69475d68f..ea370aa37 100644 --- a/uriloader/base/nsURILoader.cpp +++ b/uriloader/base/nsURILoader.cpp @@ -834,7 +834,7 @@ NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel, // the preferred protocol handler. // But for now, I'm going to let necko do the work for us.... - rv = channel->AsyncOpen(loader, nullptr); + rv = channel->AsyncOpen2(loader); // no content from this load - that's OK. if (rv == NS_ERROR_NO_CONTENT) { diff --git a/widget/BasicEvents.h b/widget/BasicEvents.h index da8d819ef..a6228f179 100644 --- a/widget/BasicEvents.h +++ b/widget/BasicEvents.h @@ -585,6 +585,7 @@ public: case eMouseEventClass: mFlags.mComposed = mMessage == eMouseClick || mMessage == eMouseDoubleClick || + mMessage == eMouseAuxClick || mMessage == eMouseDown || mMessage == eMouseUp || mMessage == eMouseEnter || mMessage == eMouseLeave || mMessage == eMouseOver || mMessage == eMouseOut || diff --git a/widget/EventMessageList.h b/widget/EventMessageList.h index 7fe642637..2eed7e618 100644 --- a/widget/EventMessageList.h +++ b/widget/EventMessageList.h @@ -84,6 +84,7 @@ NS_EVENT_MESSAGE(eMouseEnterIntoWidget) NS_EVENT_MESSAGE(eMouseExitFromWidget) NS_EVENT_MESSAGE(eMouseDoubleClick) NS_EVENT_MESSAGE(eMouseClick) +NS_EVENT_MESSAGE(eMouseAuxClick) // eMouseActivate is fired when the widget is activated by a click. NS_EVENT_MESSAGE(eMouseActivate) NS_EVENT_MESSAGE(eMouseOver) @@ -340,9 +341,11 @@ NS_EVENT_MESSAGE(eScrolledAreaChanged) NS_EVENT_MESSAGE(eTransitionStart) NS_EVENT_MESSAGE(eTransitionRun) NS_EVENT_MESSAGE(eTransitionEnd) +NS_EVENT_MESSAGE(eTransitionCancel) NS_EVENT_MESSAGE(eAnimationStart) NS_EVENT_MESSAGE(eAnimationEnd) NS_EVENT_MESSAGE(eAnimationIteration) +NS_EVENT_MESSAGE(eAnimationCancel) // Webkit-prefixed versions of Transition & Animation events, for web compat: NS_EVENT_MESSAGE(eWebkitTransitionEnd) diff --git a/widget/WidgetEventImpl.cpp b/widget/WidgetEventImpl.cpp index 52e2b9b40..7dd292cb0 100644 --- a/widget/WidgetEventImpl.cpp +++ b/widget/WidgetEventImpl.cpp @@ -236,6 +236,7 @@ WidgetEvent::HasMouseEventMessage() const case eMouseUp: case eMouseClick: case eMouseDoubleClick: + case eMouseAuxClick: case eMouseEnterIntoWidget: case eMouseExitFromWidget: case eMouseActivate: diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp index b820fed3c..909660f71 100644 --- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -3086,6 +3086,7 @@ case _value: eventName.AssignLiteral(_name) ; break _ASSIGN_eventName(eMouseDown,"eMouseDown"); _ASSIGN_eventName(eMouseUp,"eMouseUp"); _ASSIGN_eventName(eMouseClick,"eMouseClick"); + _ASSIGN_eventName(eMouseAuxClick,"eMouseAuxClick"); _ASSIGN_eventName(eMouseDoubleClick,"eMouseDoubleClick"); _ASSIGN_eventName(eMouseMove,"eMouseMove"); _ASSIGN_eventName(eLoad,"eLoad"); diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp index 0a57ad439..bd42e78f6 100644 --- a/widget/windows/WinUtils.cpp +++ b/widget/windows/WinUtils.cpp @@ -1138,7 +1138,8 @@ WinUtils::GetIsMouseFromTouch(EventMessage aEventMessage) const uint32_t MOZ_T_I_SIGNATURE = TABLET_INK_TOUCH | TABLET_INK_SIGNATURE; const uint32_t MOZ_T_I_CHECK_TCH = TABLET_INK_TOUCH | TABLET_INK_CHECK; return ((aEventMessage == eMouseMove || aEventMessage == eMouseDown || - aEventMessage == eMouseUp || aEventMessage == eMouseDoubleClick) && + aEventMessage == eMouseUp || aEventMessage == eMouseAuxClick || + aEventMessage == eMouseDoubleClick) && (GetMessageExtraInfo() & MOZ_T_I_SIGNATURE) == MOZ_T_I_CHECK_TCH); } diff --git a/xpcom/idl-parser/xpidl/xpidl.py b/xpcom/idl-parser/xpidl/xpidl.py index 8b2d8c884..6bb1ad5de 100755 --- a/xpcom/idl-parser/xpidl/xpidl.py +++ b/xpcom/idl-parser/xpidl/xpidl.py @@ -529,7 +529,7 @@ class Interface(object): raise IDLError("interface '%s' inherits from non-interface type '%s'" % (self.name, self.base), self.location) if self.attributes.scriptable and not realbase.attributes.scriptable: - print >>sys.stderr, IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base), self.location, warning=True) + raise IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base), self.location, warning=True) if self.attributes.scriptable and realbase.attributes.builtinclass and not self.attributes.builtinclass: raise IDLError("interface '%s' is not builtinclass but derives from builtinclass '%s'" % (self.name, self.base), self.location) diff --git a/xpcom/reflect/xptcall/xptcall.h b/xpcom/reflect/xptcall/xptcall.h index 1f2367b5d..304787635 100644 --- a/xpcom/reflect/xptcall/xptcall.h +++ b/xpcom/reflect/xptcall/xptcall.h @@ -39,7 +39,7 @@ struct nsXPTCMiniVariant // Types below here are unknown to the assembly implementations, and // therefore _must_ be passed with indirect semantics. We put them in // the union here for type safety, so that we can avoid void* tricks. - JS::Value j; + JS::UninitializedValue j; } val; }; diff --git a/xpfe/appshell/nsContentTreeOwner.cpp b/xpfe/appshell/nsContentTreeOwner.cpp index b39b7610f..f0fcdef9a 100644 --- a/xpfe/appshell/nsContentTreeOwner.cpp +++ b/xpfe/appshell/nsContentTreeOwner.cpp @@ -390,6 +390,7 @@ NS_IMETHODIMP nsContentTreeOwner::OnBeforeLinkTraversal(const nsAString &origina NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell, nsIURI *aURI, nsIURI *aReferrer, + nsIPrincipal* aTriggeringPrincipal, bool *_retval) { NS_ENSURE_STATE(mXULWindow); @@ -398,7 +399,8 @@ NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell, mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow)); if (xulBrowserWindow) - return xulBrowserWindow->ShouldLoadURI(aDocShell, aURI, aReferrer, _retval); + return xulBrowserWindow->ShouldLoadURI(aDocShell, aURI, aReferrer, + aTriggeringPrincipal, _retval); *_retval = true; return NS_OK; @@ -407,6 +409,7 @@ NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell, NS_IMETHODIMP nsContentTreeOwner::ReloadInFreshProcess(nsIDocShell* aDocShell, nsIURI* aURI, nsIURI* aReferrer, + nsIPrincipal* aTriggeringPrincipal, bool* aRetVal) { NS_WARNING("Cannot reload in fresh process from a nsContentTreeOwner!"); diff --git a/xpfe/appshell/nsIXULBrowserWindow.idl b/xpfe/appshell/nsIXULBrowserWindow.idl index 40f1898c8..5dbc2d409 100644 --- a/xpfe/appshell/nsIXULBrowserWindow.idl +++ b/xpfe/appshell/nsIXULBrowserWindow.idl @@ -13,6 +13,7 @@ interface nsIDOMElement; interface nsIInputStream; interface nsIDocShell; interface nsITabParent; +interface nsIPrincipal; interface mozIDOMWindowProxy; /** @@ -60,10 +61,13 @@ interface nsIXULBrowserWindow : nsISupports * The URI being loaded. * @param aReferrer * The referrer of the load. + * @param aTriggeringPrincipal + * The principal that initiated the load of aURI. */ bool shouldLoadURI(in nsIDocShell aDocShell, in nsIURI aURI, - in nsIURI aReferrer); + in nsIURI aReferrer, + in nsIPrincipal aTriggeringPrincipal); /** * Show/hide a tooltip (when the user mouses over a link, say). */ diff --git a/xpfe/appshell/nsWindowMediator.cpp b/xpfe/appshell/nsWindowMediator.cpp index 6d69bc764..35ae550ae 100644 --- a/xpfe/appshell/nsWindowMediator.cpp +++ b/xpfe/appshell/nsWindowMediator.cpp @@ -616,12 +616,10 @@ nsWindowMediator::GetZLevel(nsIXULWindow *aWindow, uint32_t *_retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = nsIXULWindow::normalZ; + // This can fail during window destruction. nsWindowInfo *info = GetInfoFor(aWindow); if (info) { *_retval = info->mZLevel; - } else { - NS_WARNING("getting z level of unregistered window"); - // this goes off during window destruction } return NS_OK; } |