author | Brindusan Cristian <cbrindusan@mozilla.com> |
Thu, 23 Aug 2018 01:00:10 +0300 | |
changeset 488023 | f8d52bf9ffdedf8d9197690ee848c3d88f360b53 |
parent 487976 | 120c4145368ddf63e4837f136244e87f6b75c8a8 (current diff) |
parent 488022 | 2f5f240834b06ed1ca71635b10d5b2a60a422f8d (diff) |
child 488024 | a54366d12608cce2a936777520ee692396fe10e8 |
child 488037 | 867a77e99ab0d656c7e8f10f7d1d5950501e7535 |
child 488056 | bb407121dfdedbea6672e59419ad74d1b436ab71 |
push id | 9719 |
push user | ffxbld-merge |
push date | Fri, 24 Aug 2018 17:49:46 +0000 |
treeherder | mozilla-beta@719ec98fba77 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 63.0a1 |
first release with | nightly linux32
f8d52bf9ffde
/
63.0a1
/
20180822221004
/
files
nightly linux64
f8d52bf9ffde
/
63.0a1
/
20180822221004
/
files
nightly mac
f8d52bf9ffde
/
63.0a1
/
20180822221004
/
files
nightly win32
f8d52bf9ffde
/
63.0a1
/
20180822221004
/
files
nightly win64
f8d52bf9ffde
/
63.0a1
/
20180822221004
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
63.0a1
/
20180822221004
/
pushlog to previous
nightly linux64
63.0a1
/
20180822221004
/
pushlog to previous
nightly mac
63.0a1
/
20180822221004
/
pushlog to previous
nightly win32
63.0a1
/
20180822221004
/
pushlog to previous
nightly win64
63.0a1
/
20180822221004
/
pushlog to previous
|
js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js | file | annotate | diff | comparison | revisions | |
js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js | file | annotate | diff | comparison | revisions | |
toolkit/components/privatebrowsing/PrivateBrowsing.manifest | file | annotate | diff | comparison | revisions | |
toolkit/components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js | file | annotate | diff | comparison | revisions | |
toolkit/components/privatebrowsing/moz.build | file | annotate | diff | comparison | revisions | |
toolkit/components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl | file | annotate | diff | comparison | revisions |
--- a/browser/base/content/browser-contentblocking.js +++ b/browser/base/content/browser-contentblocking.js @@ -413,23 +413,21 @@ var ContentBlocking = { // This state will be overriden later if there's an exception set for this site. let active = this.enabled && detected; for (let blocker of this.blockers) { blocker.categoryItem.classList.toggle("blocked", this.enabled && blocker.enabled); } // Check whether the user has added an exception for this site. - let hasException = false; - if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) { - hasException = PrivateBrowsingUtils.existsInTrackingAllowlist(baseURI); - } else { - hasException = Services.perms.testExactPermission(baseURI, - "trackingprotection") == Services.perms.ALLOW_ACTION; - } + let type = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser) ? + "trackingprotection-pb" : + "trackingprotection"; + let hasException = Services.perms.testExactPermission(baseURI, type) == + Services.perms.ALLOW_ACTION; this.content.toggleAttribute("detected", detected); this.content.toggleAttribute("hasException", hasException); this.iconBox.toggleAttribute("active", active); this.iconBox.toggleAttribute("hasException", this.enabled && hasException); if (isSimulated) {
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -548,17 +548,18 @@ scrollButtonWidth: arrowScrollbox._scrollButtonDown.getBoundingClientRect().width }; } let width = 0; for (let i = numPinned - 1; i >= 0; i--) { let tab = this.children[i]; width += layoutData.pinnedTabWidth; - tab.style.marginInlineStart = -(width + layoutData.scrollButtonWidth) + "px"; + tab.style.setProperty("margin-inline-start", + -(width + layoutData.scrollButtonWidth) + "px", "important"); tab._pinnedUnscrollable = true; } this.style.paddingInlineStart = width + "px"; } else { this.removeAttribute("positionpinnedtabs"); for (let i = 0; i < numPinned; i++) { let tab = this.children[i];
--- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -281,8 +281,9 @@ skip-if = debug skip-if = !crashreporter || !e10s # Tabs can't crash without e10s [browser_cookies.js] [browser_cookies_legacy.js] [browser_cookies_privacy.js] [browser_speculative_connect.js] [browser_1446343-windowsize.js] [browser_restore_reversed_z_order.js] +skip-if = true #Bug 1455602
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -383,20 +383,16 @@ @RESPATH@/actors/* ; Safe Browsing @RESPATH@/components/nsURLClassifier.manifest @RESPATH@/components/nsUrlClassifierHashCompleter.js @RESPATH@/components/nsUrlClassifierListManager.js @RESPATH@/components/nsUrlClassifierLib.js -; Private Browsing -@RESPATH@/components/PrivateBrowsing.manifest -@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js - ; Security Reports @RESPATH@/components/SecurityReporter.manifest @RESPATH@/components/SecurityReporter.js ; ANGLE GLES-on-D3D rendering library #ifdef MOZ_ANGLE_RENDERER @BINPATH@/libEGL.dll @BINPATH@/libGLESv2.dll
--- a/browser/themes/shared/tabs.inc.css +++ b/browser/themes/shared/tabs.inc.css @@ -65,18 +65,18 @@ } } .tabbrowser-tab { -moz-appearance: none; background-color: transparent; border-radius: 0; border-width: 0; - margin: 0; - padding: 0; + margin: 0 !important /* override tabbox.css */; + padding: 0 !important /* override tabbox.css */; -moz-box-align: stretch; } /* The selected tab should appear above the border between the tabs toolbar and the navigation toolbar. */ .tabbrowser-tab[visuallyselected=true] { position: relative; z-index: 2;
--- a/devtools/client/inspector/boxmodel/actions/box-model.js +++ b/devtools/client/inspector/boxmodel/actions/box-model.js @@ -8,40 +8,40 @@ const { UPDATE_GEOMETRY_EDITOR_ENABLED, UPDATE_LAYOUT, UPDATE_OFFSET_PARENT, } = require("./index"); module.exports = { /** - * Update the geometry editor's enabled state. + * Updates the geometry editor's enabled state. * * @param {Boolean} enabled * Whether or not the geometry editor is enabled or not. */ updateGeometryEditorEnabled(enabled) { return { type: UPDATE_GEOMETRY_EDITOR_ENABLED, enabled, }; }, /** - * Update the layout state with the new layout properties. + * Updates the layout state with the new layout properties. */ updateLayout(layout) { return { type: UPDATE_LAYOUT, layout, }; }, /** - * Update the offset parent state with the new DOM node. + * Updates the offset parent state with the new DOM node. */ updateOffsetParent(offsetParent) { return { type: UPDATE_OFFSET_PARENT, offsetParent, }; }
--- a/devtools/client/inspector/boxmodel/actions/index.js +++ b/devtools/client/inspector/boxmodel/actions/index.js @@ -3,18 +3,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { createEnum } = require("devtools/client/shared/enum"); createEnum([ - // Update the geometry editor's enabled state. + // Updates the geometry editor's enabled state. "UPDATE_GEOMETRY_EDITOR_ENABLED", - // Update the layout state with the latest layout properties. + // Updates the layout state with the latest layout properties. "UPDATE_LAYOUT", - // Update the offset parent state with the new DOM node. + // Updates the offset parent state with the new DOM node. "UPDATE_OFFSET_PARENT", ], module.exports);
--- a/devtools/client/inspector/boxmodel/components/BoxModel.js +++ b/devtools/client/inspector/boxmodel/components/BoxModel.js @@ -13,23 +13,23 @@ const BoxModelMain = createFactory(requi const BoxModelProperties = createFactory(require("./BoxModelProperties")); const Types = require("../types"); class BoxModel extends PureComponent { static get propTypes() { return { boxModel: PropTypes.shape(Types.boxModel).isRequired, - setSelectedNode: PropTypes.func.isRequired, - showBoxModelProperties: PropTypes.bool.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onShowBoxModelEditor: PropTypes.func.isRequired, onShowBoxModelHighlighter: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onToggleGeometryEditor: PropTypes.func.isRequired, + showBoxModelProperties: PropTypes.bool.isRequired, + setSelectedNode: PropTypes.func.isRequired, }; } constructor(props) { super(props); this.onKeyDown = this.onKeyDown.bind(this); } @@ -39,54 +39,56 @@ class BoxModel extends PureComponent { if (target == this.boxModelContainer) { this.boxModelMain.onKeyDown(event); } } render() { const { boxModel, - setSelectedNode, - showBoxModelProperties, onHideBoxModelHighlighter, onShowBoxModelEditor, onShowBoxModelHighlighter, onShowBoxModelHighlighterForNode, onToggleGeometryEditor, + setSelectedNode, + showBoxModelProperties, } = this.props; - return dom.div( - { - className: "boxmodel-container", - tabIndex: 0, - ref: div => { - this.boxModelContainer = div; - }, - onKeyDown: this.onKeyDown, - }, - BoxModelMain({ - boxModel, - boxModelContainer: this.boxModelContainer, - ref: boxModelMain => { - this.boxModelMain = boxModelMain; + return ( + dom.div( + { + className: "boxmodel-container", + tabIndex: 0, + ref: div => { + this.boxModelContainer = div; + }, + onKeyDown: this.onKeyDown, }, - onHideBoxModelHighlighter, - onShowBoxModelEditor, - onShowBoxModelHighlighter, - }), - BoxModelInfo({ - boxModel, - onToggleGeometryEditor, - }), - showBoxModelProperties ? - BoxModelProperties({ + BoxModelMain({ + boxModel, + boxModelContainer: this.boxModelContainer, + ref: boxModelMain => { + this.boxModelMain = boxModelMain; + }, + onHideBoxModelHighlighter, + onShowBoxModelEditor, + onShowBoxModelHighlighter, + }), + BoxModelInfo({ boxModel, - setSelectedNode, - onHideBoxModelHighlighter, - onShowBoxModelHighlighterForNode, - }) - : - null + onToggleGeometryEditor, + }), + showBoxModelProperties ? + BoxModelProperties({ + boxModel, + setSelectedNode, + onHideBoxModelHighlighter, + onShowBoxModelHighlighterForNode, + }) + : + null + ) ); } } module.exports = BoxModel;
--- a/devtools/client/inspector/boxmodel/components/BoxModelEditable.js +++ b/devtools/client/inspector/boxmodel/components/BoxModelEditable.js @@ -13,19 +13,19 @@ const LONG_TEXT_ROTATE_LIMIT = 3; class BoxModelEditable extends PureComponent { static get propTypes() { return { box: PropTypes.string.isRequired, direction: PropTypes.string, focusable: PropTypes.bool.isRequired, level: PropTypes.string, + onShowBoxModelEditor: PropTypes.func.isRequired, property: PropTypes.string.isRequired, textContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - onShowBoxModelEditor: PropTypes.func.isRequired, }; } componentDidMount() { const { property, onShowBoxModelEditor } = this.props; editableItem({ element: this.boxModelEditable, @@ -44,31 +44,33 @@ class BoxModelEditable extends PureCompo textContent, } = this.props; const rotate = direction && (direction == "left" || direction == "right") && box !== "position" && textContent.toString().length > LONG_TEXT_ROTATE_LIMIT; - return dom.p( - { - className: `boxmodel-${box} - ${direction ? " boxmodel-" + direction : "boxmodel-" + property} - ${rotate ? " boxmodel-rotate" : ""}`, - }, - dom.span( + return ( + dom.p( { - className: "boxmodel-editable", - "data-box": box, - tabIndex: box === level && focusable ? 0 : -1, - title: property, - ref: span => { - this.boxModelEditable = span; + className: `boxmodel-${box} + ${direction ? " boxmodel-" + direction : "boxmodel-" + property} + ${rotate ? " boxmodel-rotate" : ""}`, + }, + dom.span( + { + className: "boxmodel-editable", + "data-box": box, + tabIndex: box === level && focusable ? 0 : -1, + title: property, + ref: span => { + this.boxModelEditable = span; + }, }, - }, - textContent + textContent + ) ) ); } } module.exports = BoxModelEditable;
--- a/devtools/client/inspector/boxmodel/components/BoxModelInfo.js +++ b/devtools/client/inspector/boxmodel/components/BoxModelInfo.js @@ -44,42 +44,30 @@ class BoxModelInfo extends PureComponent width = "-", } = layout; let buttonClass = "layout-geometry-editor devtools-button"; if (geometryEditorEnabled) { buttonClass += " checked"; } - return dom.div( - { - className: "boxmodel-info", - }, - dom.span( - { - className: "boxmodel-element-size", - }, - SHARED_L10N.getFormatStr("dimensions", width, height) - ), - dom.section( - { - className: "boxmodel-position-group", - }, - isPositionEditable ? - dom.button({ - className: buttonClass, - title: BOXMODEL_L10N.getStr("boxmodel.geometryButton.tooltip"), - onClick: this.onToggleGeometryEditor, - }) - : - null, - dom.span( - { - className: "boxmodel-element-position", - }, - position + return ( + dom.div({ className: "boxmodel-info" }, + dom.span({ className: "boxmodel-element-size" }, + SHARED_L10N.getFormatStr("dimensions", width, height) + ), + dom.section({ className: "boxmodel-position-group" }, + isPositionEditable ? + dom.button({ + className: buttonClass, + title: BOXMODEL_L10N.getStr("boxmodel.geometryButton.tooltip"), + onClick: this.onToggleGeometryEditor, + }) + : + null, + dom.span({ className: "boxmodel-element-position" }, position) ) ) ); } } module.exports = BoxModelInfo;
--- a/devtools/client/inspector/boxmodel/components/BoxModelMain.js +++ b/devtools/client/inspector/boxmodel/components/BoxModelMain.js @@ -41,20 +41,20 @@ class BoxModelMain extends PureComponent this.getBorderOrPaddingValue = this.getBorderOrPaddingValue.bind(this); this.getContextBox = this.getContextBox.bind(this); this.getDisplayPosition = this.getDisplayPosition.bind(this); this.getHeightValue = this.getHeightValue.bind(this); this.getMarginValue = this.getMarginValue.bind(this); this.getPositionValue = this.getPositionValue.bind(this); this.getWidthValue = this.getWidthValue.bind(this); this.moveFocus = this.moveFocus.bind(this); - this.setAriaActive = this.setAriaActive.bind(this); this.onHighlightMouseOver = this.onHighlightMouseOver.bind(this); this.onKeyDown = this.onKeyDown.bind(this); this.onLevelClick = this.onLevelClick.bind(this); + this.setAriaActive = this.setAriaActive.bind(this); } componentDidUpdate() { const displayPosition = this.getDisplayPosition(); const isContentBox = this.getContextBox(); this.layouts = { "position": new Map([ @@ -409,313 +409,300 @@ class BoxModelMain extends PureComponent const marginRight = this.getMarginValue("margin-right", "right"); const marginBottom = this.getMarginValue("margin-bottom", "bottom"); const marginLeft = this.getMarginValue("margin-left", "left"); height = this.getHeightValue(height); width = this.getWidthValue(width); const contentBox = layout["box-sizing"] == "content-box" ? - dom.div( - { - className: "boxmodel-size", - }, + dom.div({ className: "boxmodel-size" }, BoxModelEditable({ box: "content", focusable, level, property: "width", ref: editable => { this.contentEditable = editable; }, textContent: width, onShowBoxModelEditor }), - dom.span( - {}, - "\u00D7" - ), + dom.span({}, "\u00D7"), BoxModelEditable({ box: "content", focusable, level, property: "height", textContent: height, onShowBoxModelEditor }) ) : - dom.p( - { - className: "boxmodel-size", - }, - dom.span( - { - title: "content", - }, + dom.p({ className: "boxmodel-size" }, + dom.span({ title: "content" }, SHARED_L10N.getFormatStr("dimensions", width, height) ) ); - return dom.div( - { - className: "boxmodel-main devtools-monospace", - "data-box": "position", - ref: div => { - this.positionLayout = div; - }, - onClick: this.onLevelClick, - onKeyDown: this.onKeyDown, - onMouseOver: this.onHighlightMouseOver, - onMouseOut: this.props.onHideBoxModelHighlighter, - }, - displayPosition ? - dom.span( - { - className: "boxmodel-legend", - "data-box": "position", - title: "position", - }, - "position" - ) - : - null, + return ( dom.div( { - className: "boxmodel-box" - }, - dom.span( - { - className: "boxmodel-legend", - "data-box": "margin", - title: "margin", + className: "boxmodel-main devtools-monospace", + "data-box": "position", + ref: div => { + this.positionLayout = div; }, - "margin" - ), - dom.div( - { - className: "boxmodel-margins", - "data-box": "margin", - title: "margin", - ref: div => { - this.marginLayout = div; - }, - }, + onClick: this.onLevelClick, + onKeyDown: this.onKeyDown, + onMouseOver: this.onHighlightMouseOver, + onMouseOut: this.props.onHideBoxModelHighlighter, + }, + displayPosition ? dom.span( { className: "boxmodel-legend", - "data-box": "border", - title: "border", + "data-box": "position", + title: "position", }, - "border" + "position" + ) + : + null, + dom.div({ className: "boxmodel-box" }, + dom.span( + { + className: "boxmodel-legend", + "data-box": "margin", + title: "margin", + }, + "margin" ), dom.div( { - className: "boxmodel-borders", - "data-box": "border", - title: "border", + className: "boxmodel-margins", + "data-box": "margin", + title: "margin", ref: div => { - this.borderLayout = div; + this.marginLayout = div; }, }, dom.span( { className: "boxmodel-legend", - "data-box": "padding", - title: "padding", + "data-box": "border", + title: "border", }, - "padding" + "border" ), dom.div( { - className: "boxmodel-paddings", - "data-box": "padding", - title: "padding", + className: "boxmodel-borders", + "data-box": "border", + title: "border", ref: div => { - this.paddingLayout = div; + this.borderLayout = div; }, }, - dom.div({ - className: "boxmodel-contents", - "data-box": "content", - title: "content", - ref: div => { - this.contentLayout = div; + dom.span( + { + className: "boxmodel-legend", + "data-box": "padding", + title: "padding", }, - }) + "padding" + ), + dom.div( + { + className: "boxmodel-paddings", + "data-box": "padding", + title: "padding", + ref: div => { + this.paddingLayout = div; + }, + }, + dom.div({ + className: "boxmodel-contents", + "data-box": "content", + title: "content", + ref: div => { + this.contentLayout = div; + }, + }) + ) ) ) - ) - ), - displayPosition ? + ), + displayPosition ? + BoxModelEditable({ + box: "position", + direction: "top", + focusable, + level, + property: "position-top", + ref: editable => { + this.positionEditable = editable; + }, + textContent: positionTop, + onShowBoxModelEditor, + }) + : + null, + displayPosition ? + BoxModelEditable({ + box: "position", + direction: "right", + focusable, + level, + property: "position-right", + textContent: positionRight, + onShowBoxModelEditor, + }) + : + null, + displayPosition ? + BoxModelEditable({ + box: "position", + direction: "bottom", + focusable, + level, + property: "position-bottom", + textContent: positionBottom, + onShowBoxModelEditor, + }) + : + null, + displayPosition ? + BoxModelEditable({ + box: "position", + direction: "left", + focusable, + level, + property: "position-left", + textContent: positionLeft, + onShowBoxModelEditor, + }) + : + null, BoxModelEditable({ - box: "position", + box: "margin", direction: "top", focusable, level, - property: "position-top", + property: "margin-top", ref: editable => { - this.positionEditable = editable; + this.marginEditable = editable; }, - textContent: positionTop, + textContent: marginTop, onShowBoxModelEditor, - }) - : - null, - displayPosition ? + }), BoxModelEditable({ - box: "position", + box: "margin", direction: "right", focusable, level, - property: "position-right", - textContent: positionRight, + property: "margin-right", + textContent: marginRight, onShowBoxModelEditor, - }) - : - null, - displayPosition ? + }), BoxModelEditable({ - box: "position", + box: "margin", direction: "bottom", focusable, level, - property: "position-bottom", - textContent: positionBottom, + property: "margin-bottom", + textContent: marginBottom, onShowBoxModelEditor, - }) - : - null, - displayPosition ? + }), BoxModelEditable({ - box: "position", + box: "margin", direction: "left", focusable, level, - property: "position-left", - textContent: positionLeft, + property: "margin-left", + textContent: marginLeft, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "top", + focusable, + level, + property: "border-top-width", + ref: editable => { + this.borderEditable = editable; + }, + textContent: borderTop, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "right", + focusable, + level, + property: "border-right-width", + textContent: borderRight, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "bottom", + focusable, + level, + property: "border-bottom-width", + textContent: borderBottom, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "border", + direction: "left", + focusable, + level, + property: "border-left-width", + textContent: borderLeft, onShowBoxModelEditor, - }) - : - null, - BoxModelEditable({ - box: "margin", - direction: "top", - focusable, - level, - property: "margin-top", - ref: editable => { - this.marginEditable = editable; - }, - textContent: marginTop, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "margin", - direction: "right", - focusable, - level, - property: "margin-right", - textContent: marginRight, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "margin", - direction: "bottom", - focusable, - level, - property: "margin-bottom", - textContent: marginBottom, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "margin", - direction: "left", - focusable, - level, - property: "margin-left", - textContent: marginLeft, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "border", - direction: "top", - focusable, - level, - property: "border-top-width", - ref: editable => { - this.borderEditable = editable; - }, - textContent: borderTop, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "border", - direction: "right", - focusable, - level, - property: "border-right-width", - textContent: borderRight, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "border", - direction: "bottom", - focusable, - level, - property: "border-bottom-width", - textContent: borderBottom, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "border", - direction: "left", - focusable, - level, - property: "border-left-width", - textContent: borderLeft, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "padding", - direction: "top", - focusable, - level, - property: "padding-top", - ref: editable => { - this.paddingEditable = editable; - }, - textContent: paddingTop, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "padding", - direction: "right", - focusable, - level, - property: "padding-right", - textContent: paddingRight, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "padding", - direction: "bottom", - focusable, - level, - property: "padding-bottom", - textContent: paddingBottom, - onShowBoxModelEditor, - }), - BoxModelEditable({ - box: "padding", - direction: "left", - focusable, - level, - property: "padding-left", - textContent: paddingLeft, - onShowBoxModelEditor, - }), - contentBox + }), + BoxModelEditable({ + box: "padding", + direction: "top", + focusable, + level, + property: "padding-top", + ref: editable => { + this.paddingEditable = editable; + }, + textContent: paddingTop, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "padding", + direction: "right", + focusable, + level, + property: "padding-right", + textContent: paddingRight, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "padding", + direction: "bottom", + focusable, + level, + property: "padding-bottom", + textContent: paddingBottom, + onShowBoxModelEditor, + }), + BoxModelEditable({ + box: "padding", + direction: "left", + focusable, + level, + property: "padding-left", + textContent: paddingLeft, + onShowBoxModelEditor, + }), + contentBox + ) ); } } module.exports = BoxModelMain;
--- a/devtools/client/inspector/boxmodel/components/BoxModelProperties.js +++ b/devtools/client/inspector/boxmodel/components/BoxModelProperties.js @@ -15,19 +15,19 @@ const Types = require("../types"); const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties"; const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI); class BoxModelProperties extends PureComponent { static get propTypes() { return { boxModel: PropTypes.shape(Types.boxModel).isRequired, - setSelectedNode: PropTypes.func.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, }; } constructor(props) { super(props); this.state = { isOpen: true, @@ -68,44 +68,41 @@ class BoxModelProperties extends PureCom isOpen: !this.state.isOpen, }); event.stopPropagation(); } render() { const { boxModel, - setSelectedNode, onHideBoxModelHighlighter, onShowBoxModelHighlighterForNode, + setSelectedNode, } = this.props; const { layout } = boxModel; const layoutInfo = ["box-sizing", "display", "float", "line-height", "position", "z-index"]; const properties = layoutInfo.map(info => { const { referenceElement, referenceElementType } = this.getReferenceElement(info); return ComputedProperty({ name: info, key: info, - value: layout[info], + onHideBoxModelHighlighter, + onShowBoxModelHighlighterForNode, referenceElement, referenceElementType, setSelectedNode, - onHideBoxModelHighlighter, - onShowBoxModelHighlighterForNode, + value: layout[info], }); }); - return dom.div( - { - className: "boxmodel-properties", - }, + return dom.div({ className: "boxmodel-properties" }, dom.div( { className: "boxmodel-properties-header", onDoubleClick: this.onToggleExpander, }, dom.span( { className: "boxmodel-properties-expander theme-twisty",
--- a/devtools/client/inspector/grids/actions/grids.js +++ b/devtools/client/inspector/grids/actions/grids.js @@ -8,49 +8,49 @@ const { UPDATE_GRID_COLOR, UPDATE_GRID_HIGHLIGHTED, UPDATE_GRIDS, } = require("./index"); module.exports = { /** - * Update the color used for the grid's highlighter. + * Updates the color used for the grid's highlighter. * * @param {NodeFront} nodeFront * The NodeFront of the DOM node to toggle the grid highlighter. * @param {String} color * The color to use for this nodeFront's grid highlighter. */ updateGridColor(nodeFront, color) { return { type: UPDATE_GRID_COLOR, color, nodeFront, }; }, /** - * Update the grid highlighted state. + * Updates the grid highlighted state. * * @param {NodeFront} nodeFront * The NodeFront of the DOM node to toggle the grid highlighter. * @param {Boolean} highlighted * Whether or not the grid highlighter is highlighting the grid. */ updateGridHighlighted(nodeFront, highlighted) { return { type: UPDATE_GRID_HIGHLIGHTED, highlighted, nodeFront, }; }, /** - * Update the grid state with the new list of grids. + * Updates the grid state with the new list of grids. */ updateGrids(grids) { return { type: UPDATE_GRIDS, grids, }; },
--- a/devtools/client/inspector/grids/actions/highlighter-settings.js +++ b/devtools/client/inspector/grids/actions/highlighter-settings.js @@ -8,43 +8,43 @@ const { UPDATE_SHOW_GRID_AREAS, UPDATE_SHOW_GRID_LINE_NUMBERS, UPDATE_SHOW_INFINITE_LINES, } = require("./index"); module.exports = { /** - * Update the grid highlighter's show grid areas preference. + * Updates the grid highlighter's show grid areas preference. * * @param {Boolean} enabled * Whether or not the grid highlighter should show the grid areas. */ updateShowGridAreas(enabled) { return { type: UPDATE_SHOW_GRID_AREAS, enabled, }; }, /** - * Update the grid highlighter's show grid line numbers preference. + * Updates the grid highlighter's show grid line numbers preference. * * @param {Boolean} enabled * Whether or not the grid highlighter should show the grid line numbers. */ updateShowGridLineNumbers(enabled) { return { type: UPDATE_SHOW_GRID_LINE_NUMBERS, enabled, }; }, /** - * Update the grid highlighter's show infinite lines preference. + * Updates the grid highlighter's show infinite lines preference. * * @param {Boolean} enabled * Whether or not the grid highlighter should extend grid lines infinitely. */ updateShowInfiniteLines(enabled) { return { type: UPDATE_SHOW_INFINITE_LINES, enabled,
--- a/devtools/client/inspector/grids/actions/index.js +++ b/devtools/client/inspector/grids/actions/index.js @@ -3,27 +3,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { createEnum } = require("devtools/client/shared/enum"); createEnum([ - // Update the color used for the overlay of a grid. + // Updates the color used for the overlay of a grid. "UPDATE_GRID_COLOR", - // Update the grid highlighted state. + // Updates the grid highlighted state. "UPDATE_GRID_HIGHLIGHTED", - // Update the entire grids state with the new list of grids. + // Updates the entire grids state with the new list of grids. "UPDATE_GRIDS", - // Update the grid highlighter's show grid areas state. + // Updates the grid highlighter's show grid areas state. "UPDATE_SHOW_GRID_AREAS", - // Update the grid highlighter's show grid line numbers state. + // Updates the grid highlighter's show grid line numbers state. "UPDATE_SHOW_GRID_LINE_NUMBERS", - // Update the grid highlighter's show infinite lines state. + // Updates the grid highlighter's show infinite lines state. "UPDATE_SHOW_INFINITE_LINES", ], module.exports);
--- a/devtools/client/inspector/grids/components/Grid.js +++ b/devtools/client/inspector/grids/components/Grid.js @@ -16,77 +16,73 @@ const GridOutline = createFactory(requir const Types = require("../types"); class Grid extends PureComponent { static get propTypes() { return { getSwatchColorPickerTooltip: PropTypes.func.isRequired, grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired, - setSelectedNode: PropTypes.func.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onShowGridOutlineHighlight: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, onToggleShowGridAreas: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, onToggleShowInfiniteLines: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, }; } render() { const { getSwatchColorPickerTooltip, grids, highlighterSettings, - setSelectedNode, onHideBoxModelHighlighter, onSetGridOverlayColor, onShowBoxModelHighlighterForNode, onShowGridOutlineHighlight, onToggleShowGridAreas, onToggleGridHighlighter, onToggleShowGridLineNumbers, onToggleShowInfiniteLines, + setSelectedNode, } = this.props; - return grids.length ? - dom.div( - { - id: "layout-grid-container", - }, - dom.div( - { - className: "grid-content", - }, + if (!grids.length) { + return ( + dom.div({ className: "devtools-sidepanel-no-result" }, + getStr("layout.noGridsOnThisPage") + ) + ); + } + + return ( + dom.div({ id: "layout-grid-container" }, + dom.div({ className: "grid-content" }, GridList({ getSwatchColorPickerTooltip, grids, - setSelectedNode, onHideBoxModelHighlighter, onSetGridOverlayColor, onShowBoxModelHighlighterForNode, onToggleGridHighlighter, + setSelectedNode, }), GridDisplaySettings({ highlighterSettings, onToggleShowGridAreas, onToggleShowGridLineNumbers, onToggleShowInfiniteLines, }) ), GridOutline({ grids, onShowGridOutlineHighlight, }) ) - : - dom.div( - { - className: "devtools-sidepanel-no-result", - }, - getStr("layout.noGridsOnThisPage") - ); + ); } } module.exports = Grid;
--- a/devtools/client/inspector/grids/components/GridDisplaySettings.js +++ b/devtools/client/inspector/grids/components/GridDisplaySettings.js @@ -18,16 +18,17 @@ class GridDisplaySettings extends PureCo onToggleShowGridAreas: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, onToggleShowInfiniteLines: PropTypes.func.isRequired, }; } constructor(props) { super(props); + this.onShowGridAreasCheckboxClick = this.onShowGridAreasCheckboxClick.bind(this); this.onShowGridLineNumbersCheckboxClick = this.onShowGridLineNumbersCheckboxClick.bind(this); this.onShowInfiniteLinesCheckboxClick = this.onShowInfiniteLinesCheckboxClick.bind(this); } onShowGridAreasCheckboxClick() { @@ -57,75 +58,58 @@ class GridDisplaySettings extends PureCo onToggleShowInfiniteLines(!highlighterSettings.showInfiniteLines); } render() { const { highlighterSettings, } = this.props; - return dom.div( - { - className: "grid-container", - }, - dom.span( - {}, - getStr("layout.gridDisplaySettings") - ), - dom.ul( - {}, - dom.li( - { - className: "grid-settings-item", - }, - dom.label( - {}, - dom.input( - { - id: "grid-setting-show-grid-line-numbers", - type: "checkbox", - checked: highlighterSettings.showGridLineNumbers, - onChange: this.onShowGridLineNumbersCheckboxClick, - } - ), - getStr("layout.displayLineNumbers") - ) - ), - dom.li( - { - className: "grid-settings-item", - }, - dom.label( - {}, - dom.input( - { - id: "grid-setting-show-grid-areas", - type: "checkbox", - checked: highlighterSettings.showGridAreasOverlay, - onChange: this.onShowGridAreasCheckboxClick, - } - ), - getStr("layout.displayAreaNames") - ) - ), - dom.li( - { - className: "grid-settings-item", - }, - dom.label( - {}, - dom.input( - { - id: "grid-setting-extend-grid-lines", - type: "checkbox", - checked: highlighterSettings.showInfiniteLines, - onChange: this.onShowInfiniteLinesCheckboxClick, - } - ), - getStr("layout.extendLinesInfinitely") + return ( + dom.div({ className: "grid-container" }, + dom.span({}, getStr("layout.gridDisplaySettings")), + dom.ul({}, + dom.li({ className: "grid-settings-item" }, + dom.label({}, + dom.input( + { + id: "grid-setting-show-grid-line-numbers", + type: "checkbox", + checked: highlighterSettings.showGridLineNumbers, + onChange: this.onShowGridLineNumbersCheckboxClick, + } + ), + getStr("layout.displayLineNumbers") + ) + ), + dom.li({ className: "grid-settings-item" }, + dom.label({}, + dom.input( + { + id: "grid-setting-show-grid-areas", + type: "checkbox", + checked: highlighterSettings.showGridAreasOverlay, + onChange: this.onShowGridAreasCheckboxClick, + } + ), + getStr("layout.displayAreaNames") + ) + ), + dom.li({ className: "grid-settings-item" }, + dom.label({}, + dom.input( + { + id: "grid-setting-extend-grid-lines", + type: "checkbox", + checked: highlighterSettings.showInfiniteLines, + onChange: this.onShowInfiniteLinesCheckboxClick, + } + ), + getStr("layout.extendLinesInfinitely") + ) ) ) ) ); } } module.exports = GridDisplaySettings;
--- a/devtools/client/inspector/grids/components/GridItem.js +++ b/devtools/client/inspector/grids/components/GridItem.js @@ -17,29 +17,30 @@ const ElementNode = REPS.ElementNode; const Types = require("../types"); class GridItem extends PureComponent { static get propTypes() { return { getSwatchColorPickerTooltip: PropTypes.func.isRequired, grid: PropTypes.shape(Types.grid).isRequired, - setSelectedNode: PropTypes.func.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, }; } constructor(props) { super(props); - this.setGridColor = this.setGridColor.bind(this); + this.onGridCheckboxClick = this.onGridCheckboxClick.bind(this); this.onGridInspectIconClick = this.onGridInspectIconClick.bind(this); + this.setGridColor = this.setGridColor.bind(this); } componentDidMount() { const swatchEl = findDOMNode(this).querySelector(".grid-color-swatch"); const tooltip = this.props.getSwatchColorPickerTooltip(); let previousColor; tooltip.addSwatch(swatchEl, { @@ -92,55 +93,50 @@ class GridItem extends PureComponent { render() { const { grid, onHideBoxModelHighlighter, onShowBoxModelHighlighterForNode, } = this.props; const { nodeFront } = grid; - return dom.li( - {}, - dom.label( - {}, - dom.input( + return ( + dom.li({}, + dom.label({}, + dom.input( + { + checked: grid.highlighted, + type: "checkbox", + value: grid.id, + onChange: this.onGridCheckboxClick, + } + ), + Rep( + { + defaultRep: ElementNode, + mode: MODE.TINY, + object: translateNodeFrontToGrip(nodeFront), + onDOMNodeMouseOut: () => onHideBoxModelHighlighter(), + onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront), + onInspectIconClick: () => this.onGridInspectIconClick(nodeFront), + } + ) + ), + dom.div( { - checked: grid.highlighted, - type: "checkbox", - value: grid.id, - onChange: this.onGridCheckboxClick, + className: "grid-color-swatch", + style: { + backgroundColor: grid.color, + }, + title: grid.color, } ), - Rep( - { - defaultRep: ElementNode, - mode: MODE.TINY, - object: translateNodeFrontToGrip(nodeFront), - onDOMNodeMouseOut: () => onHideBoxModelHighlighter(), - onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront), - onInspectIconClick: () => this.onGridInspectIconClick(nodeFront), - } - ) - ), - dom.div( - { - className: "grid-color-swatch", - style: { - backgroundColor: grid.color, - }, - title: grid.color, - } - ), - // The SwatchColorPicker relies on the nextSibling of the swatch element to apply - // the selected color. This is why we use a span in display: none for now. - // Ideally we should modify the SwatchColorPickerTooltip to bypass this requirement. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=1341578 - dom.span( - { - className: "grid-color-value" - }, - grid.color + // The SwatchColorPicker relies on the nextSibling of the swatch element to apply + // the selected color. This is why we use a span in display: none for now. + // Ideally we should modify the SwatchColorPickerTooltip to bypass this + // requirement. See https://bugzilla.mozilla.org/show_bug.cgi?id=1341578 + dom.span({ className: "grid-color-value" }, grid.color) ) ); } } module.exports = GridItem;
--- a/devtools/client/inspector/grids/components/GridList.js +++ b/devtools/client/inspector/grids/components/GridList.js @@ -13,56 +13,52 @@ const GridItem = createFactory(require(" const Types = require("../types"); class GridList extends PureComponent { static get propTypes() { return { getSwatchColorPickerTooltip: PropTypes.func.isRequired, grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, - setSelectedNode: PropTypes.func.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, }; } render() { const { getSwatchColorPickerTooltip, grids, - setSelectedNode, onHideBoxModelHighlighter, onSetGridOverlayColor, onShowBoxModelHighlighterForNode, onToggleGridHighlighter, + setSelectedNode, } = this.props; - return dom.div( - { - className: "grid-container", - }, - dom.span( - {}, - getStr("layout.overlayGrid") - ), - dom.ul( - { - id: "grid-list", - className: "devtools-monospace", - }, - grids.map(grid => GridItem({ - key: grid.id, - getSwatchColorPickerTooltip, - grid, - setSelectedNode, - onHideBoxModelHighlighter, - onSetGridOverlayColor, - onShowBoxModelHighlighterForNode, - onToggleGridHighlighter, - })) + return ( + dom.div({ className: "grid-container" }, + dom.span({}, getStr("layout.overlayGrid")), + dom.ul( + { + id: "grid-list", + className: "devtools-monospace", + }, + grids.map(grid => GridItem({ + key: grid.id, + getSwatchColorPickerTooltip, + grid, + onHideBoxModelHighlighter, + onSetGridOverlayColor, + onShowBoxModelHighlighterForNode, + onToggleGridHighlighter, + setSelectedNode, + })) + ) ) ); } } module.exports = GridList;
--- a/devtools/client/inspector/grids/components/GridOutline.js +++ b/devtools/client/inspector/grids/components/GridOutline.js @@ -53,23 +53,23 @@ class GridOutline extends PureComponent showOutline: true, width: 0, }; this.doHighlightCell = this.doHighlightCell.bind(this); this.getGridAreaName = this.getGridAreaName.bind(this); this.getHeight = this.getHeight.bind(this); this.getTotalWidthAndHeight = this.getTotalWidthAndHeight.bind(this); + this.onHighlightCell = this.onHighlightCell.bind(this); this.renderCannotShowOutlineText = this.renderCannotShowOutlineText.bind(this); this.renderGrid = this.renderGrid.bind(this); this.renderGridCell = this.renderGridCell.bind(this); this.renderGridOutline = this.renderGridOutline.bind(this); this.renderGridOutlineBorder = this.renderGridOutlineBorder.bind(this); this.renderOutline = this.renderOutline.bind(this); - this.onHighlightCell = this.onHighlightCell.bind(this); } componentWillReceiveProps({ grids }) { const selectedGrid = grids.find(grid => grid.highlighted); // Store the height of the grid container in the component state to prevent overflow // issues. We want to store the width of the grid container as well so that the // viewbox is only the calculated width of the grid outline.
--- a/devtools/client/inspector/grids/grid-inspector.js +++ b/devtools/client/inspector/grids/grid-inspector.js @@ -51,29 +51,28 @@ const GRID_COLORS = [ class GridInspector { constructor(inspector, window) { this.document = window.document; this.inspector = inspector; this.store = inspector.store; this.telemetry = inspector.telemetry; this.walker = this.inspector.walker; - this.updateGridPanel = this.updateGridPanel.bind(this); - this.onHighlighterShown = this.onHighlighterShown.bind(this); this.onHighlighterHidden = this.onHighlighterHidden.bind(this); this.onNavigate = this.onNavigate.bind(this); this.onReflow = throttle(this.onReflow, 500, this); this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this); this.onShowGridOutlineHighlight = this.onShowGridOutlineHighlight.bind(this); this.onSidebarSelect = this.onSidebarSelect.bind(this); this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this); this.onToggleShowGridAreas = this.onToggleShowGridAreas.bind(this); this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this); this.onToggleShowInfiniteLines = this.onToggleShowInfiniteLines.bind(this); + this.updateGridPanel = this.updateGridPanel.bind(this); this.init(); } get highlighters() { if (!this._highlighters) { this._highlighters = this.inspector.highlighters; }
--- a/devtools/client/inspector/layout/components/LayoutApp.js +++ b/devtools/client/inspector/layout/components/LayoutApp.js @@ -22,39 +22,38 @@ const Accordion = createFactory(require( const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties"; const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI); const LAYOUT_STRINGS_URI = "devtools/client/locales/layout.properties"; const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI); const FLEXBOX_ENABLED_PREF = "devtools.flexboxinspector.enabled"; - const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened"; const FLEXBOX_OPENED_PREF = "devtools.layout.flexbox.opened"; const GRID_OPENED_PREF = "devtools.layout.grid.opened"; class LayoutApp extends PureComponent { static get propTypes() { return { boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired, getSwatchColorPickerTooltip: PropTypes.func.isRequired, grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired, highlighterSettings: PropTypes.shape(GridTypes.highlighterSettings).isRequired, - setSelectedNode: PropTypes.func.isRequired, - showBoxModelProperties: PropTypes.bool.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onSetFlexboxOverlayColor: PropTypes.func.isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onShowBoxModelEditor: PropTypes.func.isRequired, onShowBoxModelHighlighter: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, onToggleShowInfiniteLines: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + showBoxModelProperties: PropTypes.bool.isRequired, }; } render() { let items = [ { component: Grid, componentProps: this.props, @@ -88,16 +87,17 @@ class LayoutApp extends PureComponent { const opened = Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF); Services.prefs.setBoolPref(FLEXBOX_OPENED_PREF, !opened); } }, ...items ]; } - return dom.div( - { id: "layout-container" }, - Accordion({ items }) + return ( + dom.div({ id: "layout-container" }, + Accordion({ items }) + ) ); } } module.exports = connect(state => state)(LayoutApp);
--- a/devtools/client/inspector/layout/layout.js +++ b/devtools/client/inspector/layout/layout.js @@ -29,18 +29,18 @@ class LayoutView { } init() { if (!this.inspector) { return; } const { + onShowBoxModelHighlighterForNode, setSelectedNode, - onShowBoxModelHighlighterForNode, } = this.inspector.getCommonComponentProps(); const { onHideBoxModelHighlighter, onShowBoxModelEditor, onShowBoxModelHighlighter, onToggleGeometryEditor, } = this.inspector.getPanel("boxmodel").getComponentProps(); @@ -58,35 +58,35 @@ class LayoutView { onToggleGridHighlighter, onToggleShowGridAreas, onToggleShowGridLineNumbers, onToggleShowInfiniteLines, } = this.gridInspector.getComponentProps(); const layoutApp = LayoutApp({ getSwatchColorPickerTooltip: this.getSwatchColorPickerTooltip, - setSelectedNode, - /** - * Shows the box model properties under the box model if true, otherwise, hidden by - * default. - */ - showBoxModelProperties: true, onHideBoxModelHighlighter, onSetFlexboxOverlayColor, onSetGridOverlayColor, onShowBoxModelEditor, onShowBoxModelHighlighter, onShowBoxModelHighlighterForNode, onShowGridOutlineHighlight, onToggleFlexboxHighlighter, onToggleGeometryEditor, onToggleGridHighlighter, onToggleShowGridAreas, onToggleShowGridLineNumbers, onToggleShowInfiniteLines, + setSelectedNode, + /** + * Shows the box model properties under the box model if true, otherwise, hidden by + * default. + */ + showBoxModelProperties: true, }); const provider = createElement(Provider, { id: "layoutview", key: "layoutview", store: this.store, title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"), }, layoutApp);
--- a/devtools/client/responsive.html/components/App.js +++ b/devtools/client/responsive.html/components/App.js @@ -12,24 +12,24 @@ const PropTypes = require("devtools/clie const { connect } = require("devtools/client/shared/vendor/react-redux"); const Toolbar = createFactory(require("./Toolbar")); const Viewports = createFactory(require("./Viewports")); loader.lazyGetter(this, "DeviceModal", () => createFactory(require("./DeviceModal"))); +const { changeNetworkThrottling } = require("devtools/client/shared/components/throttling/actions"); const { addCustomDevice, removeCustomDevice, updateDeviceDisplayed, updateDeviceModal, updatePreferredDevices, } = require("../actions/devices"); -const { changeNetworkThrottling } = require("devtools/client/shared/components/throttling/actions"); const { changeReloadCondition } = require("../actions/reload-conditions"); const { takeScreenshot } = require("../actions/screenshot"); const { changeTouchSimulation } = require("../actions/touch-simulation"); const { toggleLeftAlignment } = require("../actions/ui"); const { changeDevice, changePixelRatio, removeDeviceAssociation, @@ -217,60 +217,59 @@ class App extends Component { const selectedDevice = viewports[0].device; const selectedPixelRatio = viewports[0].pixelRatio; let deviceAdderViewportTemplate = {}; if (devices.modalOpenedFromViewport !== null) { deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport]; } - return dom.div( - { - id: "app", - }, - Toolbar({ - devices, - displayPixelRatio, - networkThrottling, - reloadConditions, - screenshot, - selectedDevice, - selectedPixelRatio, - touchSimulation, - viewport: viewports[0], - onChangeDevice, - onChangeNetworkThrottling, - onChangePixelRatio, - onChangeReloadCondition, - onChangeTouchSimulation, - onExit, - onRemoveDeviceAssociation, - onResizeViewport, - onRotateViewport, - onScreenshot, - onToggleLeftAlignment, - onUpdateDeviceModal, - }), - Viewports({ - screenshot, - viewports, - onBrowserMounted, - onContentResize, - onRemoveDeviceAssociation, - onResizeViewport, - }), - devices.isModalOpen ? - DeviceModal({ - deviceAdderViewportTemplate, + return ( + dom.div({ id: "app" }, + Toolbar({ devices, - onAddCustomDevice, - onDeviceListUpdate, - onRemoveCustomDevice, - onUpdateDeviceDisplayed, + displayPixelRatio, + networkThrottling, + reloadConditions, + screenshot, + selectedDevice, + selectedPixelRatio, + touchSimulation, + viewport: viewports[0], + onChangeDevice, + onChangeNetworkThrottling, + onChangePixelRatio, + onChangeReloadCondition, + onChangeTouchSimulation, + onExit, + onRemoveDeviceAssociation, + onResizeViewport, + onRotateViewport, + onScreenshot, + onToggleLeftAlignment, onUpdateDeviceModal, - }) - : - null + }), + Viewports({ + screenshot, + viewports, + onBrowserMounted, + onContentResize, + onRemoveDeviceAssociation, + onResizeViewport, + }), + devices.isModalOpen ? + DeviceModal({ + deviceAdderViewportTemplate, + devices, + onAddCustomDevice, + onDeviceListUpdate, + onRemoveCustomDevice, + onUpdateDeviceDisplayed, + onUpdateDeviceModal, + }) + : + null + ) ); } } module.exports = connect(state => state)(App);
--- a/devtools/client/responsive.html/components/Browser.js +++ b/devtools/client/responsive.html/components/Browser.js @@ -21,20 +21,20 @@ const FRAME_SCRIPT = "resource://devtool class Browser extends PureComponent { /** * This component is not allowed to depend directly on frequently changing data (width, * height). Any changes in props would cause the <iframe> to be removed and added again, * throwing away the current state of the page. */ static get propTypes() { return { + onBrowserMounted: PropTypes.func.isRequired, + onContentResize: PropTypes.func.isRequired, swapAfterMount: PropTypes.bool.isRequired, userContextId: PropTypes.number.isRequired, - onBrowserMounted: PropTypes.func.isRequired, - onContentResize: PropTypes.func.isRequired, }; } constructor(props) { super(props); this.onContentResize = this.onContentResize.bind(this); } @@ -144,29 +144,31 @@ class Browser extends PureComponent { // In the case of @remote and @remoteType, the attribute must be set before the // element is added to the DOM to have any effect, which we are able to do with this // approach. // // @noisolation and @allowfullscreen are needed so that these frames have the same // access to browser features as regular browser tabs. The `swapFrameLoaders` platform // API we use compares such features before allowing the swap to proceed. - return dom.iframe( - { - allowFullScreen: "true", - className: "browser", - height: "100%", - mozbrowser: "true", - noisolation: "true", - remote: "true", - remotetype: "web", - src: "about:blank", - usercontextid: userContextId, - width: "100%", - ref: browser => { - this.browser = browser; - }, - } + return ( + dom.iframe( + { + allowFullScreen: "true", + className: "browser", + height: "100%", + mozbrowser: "true", + noisolation: "true", + remote: "true", + remotetype: "web", + src: "about:blank", + usercontextid: userContextId, + width: "100%", + ref: browser => { + this.browser = browser; + }, + } + ) ); } } module.exports = Browser;
--- a/devtools/client/responsive.html/components/DeviceAdder.js +++ b/devtools/client/responsive.html/components/DeviceAdder.js @@ -5,27 +5,27 @@ /* eslint-env browser */ "use strict"; const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const ViewportDimension = createFactory(require("./ViewportDimension.js")); +const ViewportDimension = createFactory(require("./ViewportDimension")); const { getFormatStr, getStr } = require("../utils/l10n"); const Types = require("../types"); class DeviceAdder extends PureComponent { static get propTypes() { return { devices: PropTypes.shape(Types.devices).isRequired, + onAddCustomDevice: PropTypes.func.isRequired, viewportTemplate: PropTypes.shape(Types.viewport).isRequired, - onAddCustomDevice: PropTypes.func.isRequired, }; } constructor(props) { super(props); const { height, @@ -131,129 +131,89 @@ class DeviceAdder extends PureComponent deviceName = getStr("responsive.customDeviceName"); Object.assign(normalizedViewport, { pixelRatio: window.devicePixelRatio, userAgent: navigator.userAgent, touch: false, }); } - return dom.div( - { - id: "device-adder" - }, - dom.div( - { - id: "device-adder-content", - }, - dom.div( - { - id: "device-adder-column-1", - }, - dom.label( - { - id: "device-adder-name", - }, - dom.span( - { - className: "device-adder-label", - }, - getStr("responsive.deviceAdderName") + return ( + dom.div({ id: "device-adder" }, + dom.div({ id: "device-adder-content" }, + dom.div({ id: "device-adder-column-1" }, + dom.label({ id: "device-adder-name" }, + dom.span({ className: "device-adder-label" }, + getStr("responsive.deviceAdderName") + ), + dom.input({ + defaultValue: deviceName, + ref: input => { + this.nameInput = input; + }, + }) ), - dom.input({ - defaultValue: deviceName, - ref: input => { - this.nameInput = input; - }, - }) - ), - dom.label( - { - id: "device-adder-size", - }, - dom.span( - { - className: "device-adder-label" - }, - getStr("responsive.deviceAdderSize") - ), - ViewportDimension({ - viewport: { - width, - height, - }, - onResizeViewport: this.onChangeSize, - onRemoveDeviceAssociation: () => {}, - }) - ), - dom.label( - { - id: "device-adder-pixel-ratio", - }, - dom.span( - { - className: "device-adder-label" - }, - getStr("responsive.deviceAdderPixelRatio") + dom.label({ id: "device-adder-size" }, + dom.span({ className: "device-adder-label" }, + getStr("responsive.deviceAdderSize") + ), + ViewportDimension({ + viewport: { + width, + height, + }, + onResizeViewport: this.onChangeSize, + onRemoveDeviceAssociation: () => {}, + }) ), - dom.input({ - type: "number", - step: "any", - defaultValue: normalizedViewport.pixelRatio, - ref: input => { - this.pixelRatioInput = input; - }, - }) - ) - ), - dom.div( - { - id: "device-adder-column-2", - }, - dom.label( - { - id: "device-adder-user-agent", - }, - dom.span( - { - className: "device-adder-label" - }, - getStr("responsive.deviceAdderUserAgent") + dom.label({ id: "device-adder-pixel-ratio" }, + dom.span({ className: "device-adder-label" }, + getStr("responsive.deviceAdderPixelRatio") + ), + dom.input({ + type: "number", + step: "any", + defaultValue: normalizedViewport.pixelRatio, + ref: input => { + this.pixelRatioInput = input; + }, + }) + ) + ), + dom.div({ id: "device-adder-column-2" }, + dom.label({ id: "device-adder-user-agent" }, + dom.span({ className: "device-adder-label" }, + getStr("responsive.deviceAdderUserAgent") + ), + dom.input({ + defaultValue: normalizedViewport.userAgent, + ref: input => { + this.userAgentInput = input; + }, + }) ), - dom.input({ - defaultValue: normalizedViewport.userAgent, - ref: input => { - this.userAgentInput = input; - }, - }) + dom.label({ id: "device-adder-touch" }, + dom.span({ className: "device-adder-label" }, + getStr("responsive.deviceAdderTouch") + ), + dom.input({ + defaultChecked: normalizedViewport.touch, + type: "checkbox", + ref: input => { + this.touchInput = input; + }, + }) + ) ), - dom.label( - { - id: "device-adder-touch", - }, - dom.span( - { - className: "device-adder-label" - }, - getStr("responsive.deviceAdderTouch") - ), - dom.input({ - defaultChecked: normalizedViewport.touch, - type: "checkbox", - ref: input => { - this.touchInput = input; - }, - }) - ) ), - ), - dom.button( - { - id: "device-adder-save", - onClick: this.onDeviceAdderSave, - }, - getStr("responsive.deviceAdderSave") + dom.button( + { + id: "device-adder-save", + onClick: this.onDeviceAdderSave, + }, + getStr("responsive.deviceAdderSave") + ) ) ); } } module.exports = DeviceAdder;
--- a/devtools/client/responsive.html/components/DeviceModal.js +++ b/devtools/client/responsive.html/components/DeviceModal.js @@ -128,101 +128,94 @@ class DeviceModal extends PureComponent } = this; const sortedDevices = {}; for (const type of devices.types) { sortedDevices[type] = Object.assign([], devices[type]) .sort((a, b) => a.name.localeCompare(b.name)); } - return dom.div( - { - id: "device-modal-wrapper", - className: this.props.devices.isModalOpen ? "opened" : "closed", - }, + return ( dom.div( { - className: "device-modal", + id: "device-modal-wrapper", + className: this.props.devices.isModalOpen ? "opened" : "closed", }, - dom.button({ - id: "device-close-button", - className: "devtools-button", - onClick: () => onUpdateDeviceModal(false), - }), + dom.div({ className: "device-modal" }, + dom.button({ + id: "device-close-button", + className: "devtools-button", + onClick: () => onUpdateDeviceModal(false), + }), + dom.div({ className: "device-modal-content" }, + devices.types.map(type => { + return dom.div( + { + className: `device-type device-type-${type}`, + key: type, + }, + dom.header({ className: "device-header" }, + type + ), + sortedDevices[type].map(device => { + const details = getFormatStr( + "responsive.deviceDetails", device.width, device.height, + device.pixelRatio, device.userAgent, device.touch + ); + + let removeDeviceButton; + if (type == "custom") { + removeDeviceButton = dom.button({ + className: "device-remove-button devtools-button", + onClick: () => onRemoveCustomDevice(device), + }); + } + + return dom.label( + { + className: "device-label", + key: device.name, + title: details, + }, + dom.input({ + className: "device-input-checkbox", + type: "checkbox", + value: device.name, + checked: this.state[device.name], + onChange: this.onDeviceCheckboxChange, + }), + dom.span( + { + className: "device-name", + }, + device.name + ), + removeDeviceButton + ); + }) + ); + }) + ), + DeviceAdder({ + devices, + viewportTemplate: deviceAdderViewportTemplate, + onAddCustomDevice, + }), + dom.button( + { + id: "device-submit-button", + onClick: this.onDeviceModalSubmit, + }, + getStr("responsive.done") + ) + ), dom.div( { - className: "device-modal-content", - }, - devices.types.map(type => { - return dom.div( - { - className: `device-type device-type-${type}`, - key: type, - }, - dom.header( - { - className: "device-header", - }, - type - ), - sortedDevices[type].map(device => { - const details = getFormatStr( - "responsive.deviceDetails", device.width, device.height, - device.pixelRatio, device.userAgent, device.touch - ); - - let removeDeviceButton; - if (type == "custom") { - removeDeviceButton = dom.button({ - className: "device-remove-button devtools-button", - onClick: () => onRemoveCustomDevice(device), - }); - } - - return dom.label( - { - className: "device-label", - key: device.name, - title: details, - }, - dom.input({ - className: "device-input-checkbox", - type: "checkbox", - value: device.name, - checked: this.state[device.name], - onChange: this.onDeviceCheckboxChange, - }), - dom.span( - { - className: "device-name", - }, - device.name - ), - removeDeviceButton - ); - }) - ); - }) - ), - DeviceAdder({ - devices, - viewportTemplate: deviceAdderViewportTemplate, - onAddCustomDevice, - }), - dom.button( - { - id: "device-submit-button", - onClick: this.onDeviceModalSubmit, - }, - getStr("responsive.done") + className: "modal-overlay", + onClick: () => onUpdateDeviceModal(false), + } ) - ), - dom.div( - { - className: "modal-overlay", - onClick: () => onUpdateDeviceModal(false), - } ) ); } } module.exports = DeviceModal;
--- a/devtools/client/responsive.html/components/DevicePixelRatioMenu.js +++ b/devtools/client/responsive.html/components/DevicePixelRatioMenu.js @@ -17,19 +17,19 @@ loader.lazyRequireGetter(this, "showMenu const PIXEL_RATIO_PRESET = [1, 2, 3]; class DevicePixelRatioMenu extends PureComponent { static get propTypes() { return { devices: PropTypes.shape(Types.devices).isRequired, displayPixelRatio: Types.pixelRatio.value.isRequired, + onChangePixelRatio: PropTypes.func.isRequired, selectedDevice: PropTypes.string.isRequired, selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired, - onChangePixelRatio: PropTypes.func.isRequired, }; } constructor(props) { super(props); this.onShowDevicePixelMenu = this.onShowDevicePixelMenu.bind(this); }
--- a/devtools/client/responsive.html/components/DeviceSelector.js +++ b/devtools/client/responsive.html/components/DeviceSelector.js @@ -12,37 +12,37 @@ const { getStr } = require("../utils/l10 const Types = require("../types"); loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true); class DeviceSelector extends PureComponent { static get propTypes() { return { devices: PropTypes.shape(Types.devices).isRequired, - selectedDevice: PropTypes.string.isRequired, - viewportId: PropTypes.number.isRequired, onChangeDevice: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, onUpdateDeviceModal: PropTypes.func.isRequired, + selectedDevice: PropTypes.string.isRequired, + viewportId: PropTypes.number.isRequired, }; } constructor(props) { super(props); this.onShowDeviceMenu = this.onShowDeviceMenu.bind(this); } onShowDeviceMenu(event) { const { devices, - selectedDevice, - viewportId, onChangeDevice, onResizeViewport, onUpdateDeviceModal, + selectedDevice, + viewportId, } = this.props; const menuItems = []; for (const type of devices.types) { for (const device of devices[type]) { if (device.displayed) { menuItems.push({
--- a/devtools/client/responsive.html/components/ResizableViewport.js +++ b/devtools/client/responsive.html/components/ResizableViewport.js @@ -17,23 +17,23 @@ const Types = require("../types"); const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION; const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION; class ResizableViewport extends Component { static get propTypes() { return { leftAlignmentEnabled: PropTypes.bool.isRequired, - screenshot: PropTypes.shape(Types.screenshot).isRequired, - swapAfterMount: PropTypes.bool.isRequired, - viewport: PropTypes.shape(Types.viewport).isRequired, onBrowserMounted: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired, onRemoveDeviceAssociation: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, + screenshot: PropTypes.shape(Types.screenshot).isRequired, + swapAfterMount: PropTypes.bool.isRequired, + viewport: PropTypes.shape(Types.viewport).isRequired, }; } constructor(props) { super(props); this.state = { isResizing: false,
--- a/devtools/client/responsive.html/components/SettingsMenu.js +++ b/devtools/client/responsive.html/components/SettingsMenu.js @@ -13,33 +13,33 @@ const { getStr } = require("../utils/l10 const Types = require("../types"); loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true); class SettingsMenu extends PureComponent { static get propTypes() { return { leftAlignmentEnabled: PropTypes.bool.isRequired, - reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired, onChangeReloadCondition: PropTypes.func.isRequired, onToggleLeftAlignment: PropTypes.func.isRequired, + reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired, }; } constructor(props) { super(props); this.onToggleSettingMenu = this.onToggleSettingMenu.bind(this); } onToggleSettingMenu(event) { const { leftAlignmentEnabled, - reloadConditions, onChangeReloadCondition, onToggleLeftAlignment, + reloadConditions, } = this.props; const menuItems = [ { id: "toggleLeftAlignment", checked: leftAlignmentEnabled, label: getStr("responsive.leftAlignViewport"), type: "checkbox",
--- a/devtools/client/responsive.html/components/ViewportDimension.js +++ b/devtools/client/responsive.html/components/ViewportDimension.js @@ -10,19 +10,19 @@ const PropTypes = require("devtools/clie const { isKeyIn } = require("../utils/key"); const { MIN_VIEWPORT_DIMENSION } = require("../constants"); const Types = require("../types"); class ViewportDimension extends Component { static get propTypes() { return { - viewport: PropTypes.shape(Types.viewport).isRequired, onResizeViewport: PropTypes.func.isRequired, onRemoveDeviceAssociation: PropTypes.func.isRequired, + viewport: PropTypes.shape(Types.viewport).isRequired, }; } constructor(props) { super(props); const { width, height } = props.viewport;
--- a/devtools/client/responsive.html/components/Viewports.js +++ b/devtools/client/responsive.html/components/Viewports.js @@ -12,34 +12,34 @@ const PropTypes = require("devtools/clie const ResizableViewport = createFactory(require("./ResizableViewport")); const Types = require("../types"); class Viewports extends Component { static get propTypes() { return { leftAlignmentEnabled: PropTypes.bool.isRequired, - screenshot: PropTypes.shape(Types.screenshot).isRequired, - viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired, onBrowserMounted: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired, onRemoveDeviceAssociation: PropTypes.func.isRequired, onResizeViewport: PropTypes.func.isRequired, + screenshot: PropTypes.shape(Types.screenshot).isRequired, + viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired, }; } render() { const { leftAlignmentEnabled, - screenshot, - viewports, onBrowserMounted, onContentResize, onRemoveDeviceAssociation, onResizeViewport, + screenshot, + viewports, } = this.props; const viewportSize = window.getViewportSize(); // The viewport may not have been created yet. Default to justify-content: center // for the container. let justifyContent = "center"; // If the RDM viewport is bigger than the window's inner width, set the container's @@ -63,23 +63,23 @@ class Viewports extends Component { { id: "viewports", className: leftAlignmentEnabled ? "left-aligned" : "", }, viewports.map((viewport, i) => { return ResizableViewport({ key: viewport.id, leftAlignmentEnabled, - screenshot, - swapAfterMount: i == 0, - viewport, onBrowserMounted, onContentResize, onRemoveDeviceAssociation, onResizeViewport, + screenshot, + swapAfterMount: i == 0, + viewport, }); }) ) ) ); } }
--- a/dom/base/test/test_bug574596.html +++ b/dom/base/test/test_bug574596.html @@ -37,16 +37,17 @@ var dragLinkText = [[ { type:"text/_moz_htmlcontext", data:"", eqTest:ignoreFunc }, { type:"text/_moz_htmlinfo", data:"", eqTest:ignoreFunc }, { type:"text/html", data:'<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>' }, { type:"text/plain", data:"http://www.mozilla.org/" } ]]; function dumpTransfer(dataTransfer,expect) { + dataTransfer = SpecialPowers.wrap(dataTransfer); dtData = dataTransfer.mozItemCount + "items:\n"; for (var i = 0; i < dataTransfer.mozItemCount; i++) { var dtTypes = dataTransfer.mozTypesAt(i); for (var j = 0; j < dtTypes.length; j++) { var actualData = dataTransfer.mozGetDataAt(dtTypes[j],i) if (expect && expect[i] && expect[i][j]) { if (expect[i][j].eqTest) dtData += expect[i][j].eqTest(actualData,expect[i][j].data) ? "ok" : "fail";
--- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -35,16 +35,18 @@ #include "mozilla/dom/Directory.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/FileList.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/OSFileSystem.h" #include "mozilla/dom/Promise.h" #include "nsNetUtil.h" +#define MOZ_CALLS_ENABLED_PREF "dom.datatransfer.mozAtAPIs" + namespace mozilla { namespace dom { NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems) @@ -1518,10 +1520,27 @@ DataTransfer::SetMode(DataTransfer::Mode { if (!PrefProtected() && aMode == Mode::Protected) { mMode = Mode::ReadOnly; } else { mMode = aMode; } } +/* static */ +bool +DataTransfer::MozAtAPIsEnabled(JSContext* aCx, JSObject* aObj /*unused*/) +{ + // Read the pref + static bool sPrefCached = false; + static bool sPrefCacheValue = false; + + if (!sPrefCached) { + sPrefCached = true; + Preferences::AddBoolVarCache(&sPrefCacheValue, MOZ_CALLS_ENABLED_PREF); + } + + // We can expose moz* APIs if we are chrome code or if pref is enabled + return nsContentUtils::IsSystemCaller(aCx) || sPrefCacheValue; +} + } // namespace dom } // namespace mozilla
--- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -423,16 +423,21 @@ public: // // If kFileMime is supported, then it will be placed either at // index 0 or at index 1 in aResult static void GetExternalClipboardFormats(const int32_t& aWhichClipboard, const bool& aPlainTextOnly, nsTArray<nsCString>* aResult); + // Returns true if moz* APIs should be exposed (true for chrome code or if + // dom.datatransfer.moz pref is enabled). + // The affected moz* APIs are mozItemCount, mozTypesAt, mozClearDataAt, mozSetDataAt, mozGetDataAt + static bool MozAtAPIsEnabled(JSContext* cx, JSObject* obj); + protected: // caches text and uri-list data formats that exist in the drag service or // clipboard for retrieval later. nsresult CacheExternalData(const char* aFormat, uint32_t aIndex, nsIPrincipal* aPrincipal, bool aHidden); // caches the formats that exist in the drag service that were added by an
--- a/dom/events/test/test_DataTransferItemList.html +++ b/dom/events/test/test_DataTransferItemList.html @@ -1,21 +1,21 @@ <html> <head> - <title>Tests for the DatTransferItemList object</title> + <title>Tests for the DataTransferItemList object</title> <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/AddTask.js"></script> <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script> <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> </head> <body style="height: 300px; overflow: auto;"> <p id="display"> </p> <img id="image" draggable="true" src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82"> -<div id="over" "style="width: 100px; height: 100px; border: 2px black dashed;"> +<div id="over" style="width: 100px; height: 100px; border: 2px black dashed;"> drag over here </div> <script> function spin() { // Defer to the event loop twice to wait for any events to be flushed out. return new Promise(function(a) { SimpleTest.executeSoon(function() {
--- a/dom/events/test/test_bug1264380.html +++ b/dom/events/test/test_bug1264380.html @@ -25,17 +25,17 @@ function runTests() target.href = "http://www.mozilla.org/"; shadow.appendChild(target); let dataTransfer; let trapDrag = function(event) { ok(true, "Got dragstart event"); dataTransfer = event.dataTransfer; ok(dataTransfer, "DataTransfer object is available."); - is(dataTransfer.mozItemCount, 1, "initial link item count"); + is(SpecialPowers.wrap(dataTransfer).mozItemCount, 1, "initial link item count"); is(dataTransfer.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list"); is(dataTransfer.getData("text/plain"), "http://www.mozilla.org/", "link text/plain"); } ok(!dragService.getCurrentSession(), "There shouldn't be a drag session!"); iframeWin.addEventListener("dragstart", trapDrag, true); synthesizeMouse(target, 2, 2, { type: "mousedown" }, iframeWin); synthesizeMouse(target, 11, 11, { type: "mousemove" }, iframeWin);
--- a/dom/events/test/test_dragstart.html +++ b/dom/events/test/test_dragstart.html @@ -36,20 +36,16 @@ function afterDragTests() // be read only. ok(gDataTransfer instanceof DataTransfer, "DataTransfer after dragstart event"); checkTypes(gDataTransfer, [], 0, "after dragstart event"); expectError(() => gDataTransfer.setData("text/plain", "Some Text"), "NoModificationAllowedError", "setData when read only"); expectError(() => gDataTransfer.clearData("text/plain"), "NoModificationAllowedError", "clearData when read only"); - expectError(() => gDataTransfer.mozSetDataAt("text/plain", "Some Text", 0), - "NoModificationAllowedError", "setDataAt when read only"); - expectError(() => gDataTransfer.mozClearDataAt("text/plain", 0), - "NoModificationAllowedError", "clearDataAt when read only"); expectError(() => gDataTransfer.addElement(draggable), "NoModificationAllowedError", "addElement when read only"); var evt = document.createEvent("dragevent"); ok(evt instanceof DragEvent, "synthetic dragevent class") ok(evt instanceof MouseEvent, "synthetic event inherits from MouseEvent") evt.initDragEvent("dragstart", true, true, window, 1, 40, 35, 20, 15, false, true, false, false, 0, null, null); @@ -125,33 +121,37 @@ function doDragStartSelection(event) // text/unicode and Text are available for compatibility. They retrieve the // text/plain data is(dt.getData("text/unicode"), "This is a draggable bit of text.", "initial selection text/unicode"); is(dt.getData("Text"), "This is a draggable bit of text.", "initial selection Text"); is(dt.getData("TEXT"), "This is a draggable bit of text.", "initial selection TEXT"); is(dt.getData("text/UNICODE"), "This is a draggable bit of text.", "initial selection text/UNICODE"); - is(dt.mozItemCount, 1, "initial selection item count"); + is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial selection item count"); dt.clearData("text/plain"); dt.clearData("text/html"); dt.clearData("text/_moz_htmlinfo"); dt.clearData("text/_moz_htmlcontext"); test_DataTransfer(dt); setTimeout(afterDragTests, 0); } function test_DataTransfer(dt) { - is(dt.mozItemCount, 0, "empty itemCount"); + is(SpecialPowers.wrap(dt).mozItemCount, 0, "empty itemCount"); var types = dt.types; ok(Array.isArray(types), "empty types is an Array"); + // The above test fails if we have SpecialPowers.wrap(dt).types instead of dt.types + // because chrome consumers get the 'ReturnValueNeedsContainsHack'. + // So wrap with special powers after the test + dt = SpecialPowers.wrap(dt); checkTypes(dt, [], 0, "empty"); is(dt.getData("text/plain"), "", "empty data is empty"); // calling setDataAt requires an index that is 0 <= index <= dt.itemCount expectError(() => dt.mozSetDataAt("text/plain", "Some Text", 1), "IndexSizeError", "setDataAt index too high"); is(dt.mozUserCancelled, false, "userCancelled"); @@ -311,30 +311,28 @@ function test_DataTransfer(dt) checkOneDataItem(dt, ["text/plain", "text/html"], ["Changed Second Item", "<em>Second Item</em>"], 1, "changed with setData item at index 1"); dt.mozSetDataAt("application/-moz-node", "draggable", 2); is(dt.mozItemCount, 3, "setDataAt node itemCount"); checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 2, "setDataAt node item at index 2"); // Try to add and then remove a non-string type to the DataTransfer and ensure - // that the type appears in DataTransfer.types. These calls need to be called - // with SpecialPowers.wrap(), as adding and removing non-string/file types to - // DataTransfer is chrome-only. + // that the type appears in DataTransfer.types. { - SpecialPowers.wrap(dt).mozSetDataAt("application/-x-body", document.body, 0); + dt.mozSetDataAt("application/-x-body", document.body, 0); let found = false; for (let i = 0; i < dt.types.length; ++i) { if (dt.types[i] == "application/-x-body") { found = true; break; } } ok(found, "Data should appear in datatransfer.types despite having a non-string type"); - SpecialPowers.wrap(dt).mozClearDataAt("application/-x-body", 0); + dt.mozClearDataAt("application/-x-body", 0); } dt.mozClearDataAt("text/html", 1); is(dt.mozItemCount, 3, "clearDataAt itemCount"); checkOneDataItem(dt, ["text/plain", "text/html"], ["First Item", "Changed with setData"], 0, "clearDataAt item at index 0"); checkOneDataItem(dt, ["text/plain"], ["Changed Second Item"], 1, "clearDataAt item at index 1"); @@ -414,48 +412,48 @@ function test_DataTransfer(dt) } function doDragStartLink(event) { var dt = event.dataTransfer; checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list", "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial link"); - is(dt.mozItemCount, 1, "initial link item count"); + is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial link item count"); is(dt.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list"); is(dt.getData("text/plain"), "http://www.mozilla.org/", "link text/plain"); event.preventDefault(); gExtraDragTests++; } function doDragStartImage(event) { var dataurl = $("image").src; var dt = event.dataTransfer; checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list", "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial image"); - is(dt.mozItemCount, 1, "initial image item count"); + is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial image item count"); is(dt.getData("text/uri-list"), dataurl, "image text/uri-list"); is(dt.getData("text/plain"), dataurl, "image text/plain"); event.preventDefault(); gExtraDragTests++; } function doDragStartInput(event) { var dt = event.dataTransfer; checkTypes(dt, ["text/plain"], 0, "initial input"); - is(dt.mozItemCount, 1, "initial input item count"); + is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial input item count"); // is(dt.getData("text/plain"), "Text", "input text/plain"); // event.preventDefault(); } function doDragStartSynthetic(event) { is(event.type, "dragstart", "synthetic dragstart event type"); @@ -506,21 +504,22 @@ function doDragOverSynthetic(event) // Uncomment next two lines once the todo instanceof above is fixed. // dt.setData("text/plain", "Text"); // is(dt.getData("text/plain"), "Text", "synthetic dragover data is set after adding"); } function onDragStartDraggable(event) { var dt = event.dataTransfer; - ok(dt.mozItemCount == 0 && dt.types.length == 0 && event.originalTarget == gDragInfo.target, gDragInfo.testid); + ok(SpecialPowers.wrap(dt).mozItemCount == 0 && dt.types.length == 0 && event.originalTarget == gDragInfo.target, gDragInfo.testid); gExtraDragTests++; } +// Expects dt wrapped in SpecialPowers function checkOneDataItem(dt, expectedtypes, expecteddata, index, testid) { checkTypes(dt, expectedtypes, index, testid); for (var f = 0; f < expectedtypes.length; f++) { if (index == 0) is(dt.getData(expectedtypes[f]), expecteddata[f], testid + " getData " + expectedtypes[f]); is(dt.mozGetDataAt(expectedtypes[f], index), expecteddata[f] ? expecteddata[f] : null, testid + " getDataAt " + expectedtypes[f]); @@ -532,23 +531,24 @@ function checkTypes(dt, expectedtypes, i if (index == 0) { var types = dt.types; is(types.length, expectedtypes.length, testid + " types length"); for (var f = 0; f < expectedtypes.length; f++) { is(types[f], expectedtypes[f], testid + " " + types[f] + " check"); } } - types = dt.mozTypesAt(index); + types = SpecialPowers.wrap(dt).mozTypesAt(index); is(types.length, expectedtypes.length, testid + " typesAt length"); for (var f = 0; f < expectedtypes.length; f++) { is(types[f], expectedtypes[f], testid + " " + types[f] + " at " + index + " check"); } } +// Expects dt wrapped in SpecialPowers function checkURL(dt, url, fullurllist, index, testid) { is(dt.getData("text/uri-list"), fullurllist, testid + " text/uri-list"); is(dt.getData("URL"), url, testid + " URL"); is(dt.mozGetDataAt("text/uri-list", 0), fullurllist, testid + " text/uri-list"); is(dt.mozGetDataAt("URL", 0), fullurllist, testid + " URL"); }
--- a/dom/media/mediasink/VideoSink.cpp +++ b/dom/media/mediasink/VideoSink.cpp @@ -42,16 +42,17 @@ VideoSink::VideoSink(AbstractThread* aTh FrameStatistics& aFrameStats, uint32_t aVQueueSentToCompositerSize) : mOwnerThread(aThread) , mAudioSink(aAudioSink) , mVideoQueue(aVideoQueue) , mContainer(aContainer) , mProducerID(ImageContainer::AllocateProducerID()) , mFrameStats(aFrameStats) + , mOldDroppedCount(0) , mHasVideo(false) , mUpdateScheduler(aThread) , mVideoQueueSendToCompositorSize(aVQueueSentToCompositerSize) , mMinVideoQueueSize(StaticPrefs::MediaRuinAvSyncEnabled() ? 1 : 0) #ifdef XP_WIN , mHiResTimersRequested(false) #endif @@ -459,16 +460,23 @@ VideoSink::RenderVideoFrames(int32_t aMa VSINK_LOG_V("playing video frame %" PRId64 " (id=%x) (vq-queued=%zu)", frame->mTime.ToMicroseconds(), frame->mFrameID, VideoQueue().GetSize()); } if (images.Length() > 0) { mContainer->SetCurrentFrames(frames[0]->mDisplay, images); + uint32_t droppedCount = mContainer->GetDroppedImageCount(); + uint32_t dropped = droppedCount - mOldDroppedCount; + if (dropped > 0) { + mFrameStats.NotifyDecodedFrames({0, 0, dropped}); + mOldDroppedCount = droppedCount; + VSINK_LOG_V("%u video frame discarded by compositor", dropped); + } } } void VideoSink::UpdateRenderedVideoFrames() { AssertOwnerThread(); MOZ_ASSERT(mAudioSink->IsPlaying(), "should be called while playing.");
--- a/dom/media/mediasink/VideoSink.h +++ b/dom/media/mediasink/VideoSink.h @@ -103,17 +103,18 @@ private: void MaybeResolveEndPromise(); void AssertOwnerThread() const { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); } - MediaQueue<VideoData>& VideoQueue() const { + MediaQueue<VideoData>& VideoQueue() const + { return mVideoQueue; } const RefPtr<AbstractThread> mOwnerThread; RefPtr<MediaSink> mAudioSink; MediaQueue<VideoData>& mVideoQueue; VideoFrameContainer* mContainer; @@ -126,16 +127,18 @@ private: RefPtr<GenericPromise> mEndPromise; MozPromiseHolder<GenericPromise> mEndPromiseHolder; MozPromiseRequestHolder<GenericPromise> mVideoSinkEndRequest; // The presentation end time of the last video frame which has been displayed. TimeUnit mVideoFrameEndTime; + uint32_t mOldDroppedCount; + // Event listeners for VideoQueue MediaEventListener mPushListener; MediaEventListener mFinishListener; // True if this sink is going to handle video track. bool mHasVideo; // Used to trigger another update of rendered frames in next round.
--- a/dom/media/tests/mochitest/test_peerConnection_stats.html +++ b/dom/media/tests/mochitest/test_peerConnection_stats.html @@ -8,30 +8,30 @@ <script type="application/javascript"> createHTML({ bug: "1337525", title: "webRtc Stats composition and sanity" }); var statsExpectedByType = { "inbound-rtp": { expected: ["id", "timestamp", "type", "ssrc", "isRemote", "mediaType", - "packetsReceived", "packetsLost", "bytesReceived", "jitter",], + "kind", "packetsReceived", "packetsLost", "bytesReceived", "jitter",], optional: ["roundTripTime", "remoteId", "nackCount",], localVideoOnly: ["discardedPackets", "framerateStdDev", "framerateMean", "bitrateMean", "bitrateStdDev", "firCount", "pliCount", "framesDecoded",], unimplemented: ["mediaTrackId", "transportId", "codecId", "packetsDiscarded", "associateStatsId", "sliCount", "qpSum", "packetsRepaired", "fractionLost", "burstPacketsLost", "burstLossCount", "burstDiscardCount", "gapDiscardRate", "gapLossRate",], deprecated: ["mozRtt"], }, "outbound-rtp": { expected: ["id", "timestamp", "type", "ssrc", "isRemote", "mediaType", - "packetsSent", "bytesSent", "remoteId",], + "kind", "packetsSent", "bytesSent", "remoteId",], optional: ["remoteId", "nackCount",], localVideoOnly: ["droppedFrames", "bitrateMean", "bitrateStdDev", "framerateMean", "framerateStdDev", "framesEncoded", "firCount", "pliCount",], unimplemented: ["mediaTrackId", "transportId", "codecId", "sliCount", "qpSum", "targetBitrate",], deprecated: [], }, @@ -143,20 +143,26 @@ var pedanticChecks = report => { // // SSRC ok(stat.ssrc, stat.type + ".ssrc has a value"); // isRemote ok(stat.isRemote !== undefined, stat.type + ".isRemote exists."); - // mediaType + // kind + ok(["audio", "video"].includes(stat.kind), + stat.type + ".kind is 'audio' or 'video'"); + + // mediaType, renamed to kind but remains for backward compability. ok(["audio", "video"].includes(stat.mediaType), stat.type + ".mediaType is 'audio' or 'video'"); + ok(stat.kind == stat.mediaType, "kind equals legacy mediaType"); + // remote id if (stat.remoteId) { ok(report.has(stat.remoteId), "remoteId exists in report."); is(report.get(stat.remoteId).ssrc, stat.ssrc, "remote ssrc and local ssrc match."); is(report.get(stat.remoteId).remoteId, stat.id, "remote object has local object as it's own remote object."); } @@ -164,17 +170,17 @@ var pedanticChecks = report => { // nackCount if (!stat.inner.isRemote) { ok(stat.nackCount >= 0, stat.type + ".nackCount is sane."); } else { is(stat.nackCount, undefined, stat.type + ".nackCount is only set when isRemote is false"); } - if (!stat.inner.isRemote && stat.inner.mediaType == "video") { + if (!stat.inner.isRemote && stat.inner.kind == "video") { // firCount ok(stat.firCount >= 0 && stat.firCount < 100, stat.type + ".firCount is a sane number for a short test. value=" + stat.firCount); // pliCount ok(stat.pliCount >= 0 && stat.pliCount < 100, stat.type + ".pliCount is a sane number for a short test. value=" @@ -200,17 +206,17 @@ var pedanticChecks = report => { + stat.bytesReceived); // packetsLost ok(stat.packetsLost < 100, stat.type + ".packetsLost is a sane number for a short test. value=" + stat.packetsLost); // This should be much lower for audio, TODO: Bug 1330575 - let expectedJitter = stat.mediaType == "video" ? 0.5 : 1; + let expectedJitter = stat.kind == "video" ? 0.5 : 1; // jitter ok(stat.jitter < expectedJitter, stat.type + ".jitter is sane number for a local only test. value=" + stat.jitter); // packetsDiscarded // special exception for, TODO: Bug 1335967 // if (!stat.inner.isRemote && stat.discardedPackets !== undefined) { @@ -235,30 +241,30 @@ var pedanticChecks = report => { } else { is(stat.roundTripTime, undefined, stat.type + ".roundTripTime is only set when isRemote is true"); } // // Local video only stats // - if (stat.inner.isRemote || stat.inner.mediaType != "video") { + if (stat.inner.isRemote || stat.inner.kind != "video") { expectations.localVideoOnly.forEach(field => { if (stat.inner.isRemote) { ok(stat[field] === undefined, stat.type + " does not have field " + field + " when isRemote is true"); - } else { // mediaType != video + } else { // kind != video ok(stat[field] === undefined, stat.type + " does not have field " - + field + " when mediaType is not 'video'"); + + field + " when kind is not 'video'"); } }); } else { expectations.localVideoOnly.forEach(field => { ok(stat.inner[field] !== undefined, stat.type + " has field " + field - + " when mediaType is video"); + + " when kind is video"); }); // discardedPackets ok(stat.discardedPackets < 100, stat.type + ".discardedPackets is a sane number for a short test. value=" + stat.discardedPackets); // framesDecoded ok(stat.framesDecoded > 0 && stat.framesDecoded < 1000000, stat.type + ".framesDecoded is a sane number for a short test. value=" @@ -316,30 +322,30 @@ var pedanticChecks = report => { // // Optional fields // // // Local video only stats // - if (stat.inner.isRemote || stat.inner.mediaType != "video") { + if (stat.inner.isRemote || stat.inner.kind != "video") { expectations.localVideoOnly.forEach(field => { if (stat.inner.isRemote) { ok(stat[field] === undefined, stat.type + " does not have field " + field + " when isRemote is true"); - } else { // mediaType != video + } else { // kind != video ok(stat[field] === undefined, stat.type + " does not have field " - + field + " when mediaType is not 'video'"); + + field + " when kind is not 'video'"); } }); } else { expectations.localVideoOnly.forEach(field => { ok(stat.inner[field] !== undefined, stat.type + " has field " + field - + " when mediaType is video and isRemote is false"); + + " when kind is video and isRemote is false"); }); // bitrateMean if (stat.bitrateMean !== undefined) { // TODO: uncomment when Bug 1341533 lands // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25, // stat.type + ".bitrateMean is sane. value=" // + stat.bitrateMean);
--- a/dom/media/webrtc/WebrtcGlobal.h +++ b/dom/media/webrtc/WebrtcGlobal.h @@ -287,16 +287,17 @@ static void WriteRTCRtpStreamStats( WriteParam(aMsg, aParam.mBitrateMean); WriteParam(aMsg, aParam.mBitrateStdDev); WriteParam(aMsg, aParam.mCodecId); WriteParam(aMsg, aParam.mFramerateMean); WriteParam(aMsg, aParam.mFramerateStdDev); WriteParam(aMsg, aParam.mIsRemote); WriteParam(aMsg, aParam.mMediaTrackId); WriteParam(aMsg, aParam.mMediaType); + WriteParam(aMsg, aParam.mKind); WriteParam(aMsg, aParam.mRemoteId); WriteParam(aMsg, aParam.mSsrc); WriteParam(aMsg, aParam.mTransportId); } static bool ReadRTCRtpStreamStats( const Message* aMsg, PickleIterator* aIter, mozilla::dom::RTCRtpStreamStats* aResult) @@ -304,16 +305,17 @@ static bool ReadRTCRtpStreamStats( if (!ReadParam(aMsg, aIter, &(aResult->mBitrateMean)) || !ReadParam(aMsg, aIter, &(aResult->mBitrateStdDev)) || !ReadParam(aMsg, aIter, &(aResult->mCodecId)) || !ReadParam(aMsg, aIter, &(aResult->mFramerateMean)) || !ReadParam(aMsg, aIter, &(aResult->mFramerateStdDev)) || !ReadParam(aMsg, aIter, &(aResult->mIsRemote)) || !ReadParam(aMsg, aIter, &(aResult->mMediaTrackId)) || !ReadParam(aMsg, aIter, &(aResult->mMediaType)) || + !ReadParam(aMsg, aIter, &(aResult->mKind)) || !ReadParam(aMsg, aIter, &(aResult->mRemoteId)) || !ReadParam(aMsg, aIter, &(aResult->mSsrc)) || !ReadParam(aMsg, aIter, &(aResult->mTransportId))) { return false; } return true; }
--- a/dom/media/webrtc/moz.build +++ b/dom/media/webrtc/moz.build @@ -83,11 +83,8 @@ FINAL_LIBRARY = 'xul' if CONFIG['CC_TYPE'] in ('msvc', 'clang-cl'): CXXFLAGS += [ '-wd4275', # non dll-interface class used as base for dll-interface class '-wd4312', # This is intended as a temporary hack to support building with VS2015 # 'reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size ] DEFINES['__PRETTY_FUNCTION__'] = '__FUNCSIG__' - -if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl': - AllowCompilerWarnings() # workaround for bug 1306642
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp +++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp @@ -1936,17 +1936,17 @@ ServiceWorkerPrivate::TerminateWorker() if (DOMPrefs::ServiceWorkersTestingEnabled()) { nsCOMPtr<nsIObserverService> os = services::GetObserverService(); if (os) { os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr); } } Unused << NS_WARN_IF(!mWorkerPrivate->Cancel()); - mWorkerPrivate = nullptr; + RefPtr<WorkerPrivate> workerPrivate(mWorkerPrivate.forget()); mSupportsArray.Clear(); // Any pending events are never going to fire on this worker. Cancel // them so that intercepted channels can be reset and other resources // cleaned up. nsTArray<RefPtr<WorkerRunnable>> pendingEvents; mPendingFunctionalEvents.SwapElements(pendingEvents); for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
--- a/dom/tests/mochitest/general/mochitest.ini +++ b/dom/tests/mochitest/general/mochitest.ini @@ -85,16 +85,17 @@ subsuite = clipboard [test_bug1434273.html] [test_clientRects.html] [test_clipboard_disallowed.html] [test_clipboard_events.html] subsuite = clipboard [test_consoleAPI.html] [test_contentViewer_overrideDPPX.html] [test_CCW_optimization.html] +[test_datatransfer_disallowed.html] [test_devicePixelRatio_with_zoom.html] [test_DOMMatrix.html] [test_domWindowUtils.html] [test_domWindowUtils_scrollbarSize.html] [test_domWindowUtils_scrollXY.html] [test_donottrack.html] [test_focus_scrollchildframe.html] [test_focus_legend_noparent.html]
--- a/dom/tests/mochitest/general/test_clipboard_disallowed.html +++ b/dom/tests/mochitest/general/test_clipboard_disallowed.html @@ -19,41 +19,33 @@ function doTest() } function checkAllowed(event) { let clipboardData = event.clipboardData; let exception; try { - clipboardData.mozSetDataAt("text/customdata", document.getElementById("input"), 0); - } catch(ex) { - exception = ex; - } - is(String(exception).indexOf("SecurityError"), 0, "Cannot set non-string"); - - exception = null; - try { - clipboardData.mozSetDataAt("application/x-moz-file", "Test", 0); + clipboardData.setData("application/x-moz-file", "Test"); } catch(ex) { exception = ex; } is(String(exception).indexOf("SecurityError"), 0, "Cannot set file"); exception = null; try { - clipboardData.mozSetDataAt("application/x-moz-file-promise", "Test", 0); + clipboardData.setData("application/x-moz-file-promise", "Test"); } catch(ex) { exception = ex; } is(String(exception).indexOf("SecurityError"), 0, "Cannot set file promise"); exception = null; try { - clipboardData.mozSetDataAt("application/something", "This is data", 0); + clipboardData.setData("application/something", "This is data"); } catch(ex) { exception = ex; } is(exception, null, "Can set custom data to a string"); SimpleTest.finish(); } SimpleTest.waitForExplicitFinish();
--- a/dom/tests/mochitest/general/test_clipboard_events.html +++ b/dom/tests/mochitest/general/test_clipboard_events.html @@ -297,17 +297,17 @@ add_task(async function test_input_cut_d // Cut using event.dataTransfer. The event is not cancelled so the default // cut should occur selectContentInput(); contentInput.oncut = function(event) { ok(event instanceof ClipboardEvent, "cut event is a ClipboardEvent"); ok(event.clipboardData instanceof DataTransfer, "cut event dataTransfer is a DataTransfer"); is(event.target, contentInput, "cut event target"); - is(event.clipboardData.mozItemCount, 0, "cut event mozItemCount"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "cut event mozItemCount"); is(event.clipboardData.getData("text/plain"), "", "cut event getData"); event.clipboardData.setData("text/plain", "This is some dataTransfer text"); cachedCutData = event.clipboardData; }; try { await putOnClipboard("INPUT TEXT", () => { synthesizeKey("x", {accelKey: 1}); }, "cut using dataTransfer on plaintext editor set clipboard correctly"); @@ -344,17 +344,17 @@ add_task(async function test_input_copy_ await reset(); // Copy using event.dataTransfer selectContentInput(); contentInput.oncopy = function(event) { ok(event instanceof ClipboardEvent, "copy event is a ClipboardEvent"); ok(event.clipboardData instanceof DataTransfer, "copy event dataTransfer is a DataTransfer"); is(event.target, contentInput, "copy event target"); - is(event.clipboardData.mozItemCount, 0, "copy event mozItemCount"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "copy event mozItemCount"); is(event.clipboardData.getData("text/plain"), "", "copy event getData"); event.clipboardData.setData("text/plain", "Copied dataTransfer text"); cachedCopyData = event.clipboardData; }; try { await putOnClipboard("INPUT TEXT", () => { synthesizeKey("c", {accelKey: 1}); }, "copy using dataTransfer on plaintext editor set clipboard correctly"); @@ -389,17 +389,17 @@ add_task(async function test_input_paste await reset(); // Paste using event.dataTransfer selectContentInput(); contentInput.onpaste = function(event) { ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent"); ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer"); is(event.target, contentInput, "paste event target"); - is(event.clipboardData.mozItemCount, 1, "paste event mozItemCount"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "paste event mozItemCount"); is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "paste event getData"); cachedPasteData = event.clipboardData; }; try { synthesizeKey("v", {accelKey: 1}); is(getClipboardText(), clipboardInitialValue, "paste using dataTransfer on plaintext editor did not modify clipboard contents"); is(contentInput.value, clipboardInitialValue, @@ -435,31 +435,31 @@ add_task(async function test_input_copyp // Cut several types of data and paste it again contentInput.value = "This is a line of text"; contentInput.oncopy = function(event) { var cd = event.clipboardData; cd.setData("text/plain", "would be a phrase"); var exh = false; - try { cd.mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; } + try { SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; } ok(exh, "exception occured mozSetDataAt 1"); exh = false; - try { cd.mozTypesAt(1); } catch (ex) { exh = true; } + try { SpecialPowers.wrap(cd).mozTypesAt(1); } catch (ex) { exh = true; } ok(exh, "exception occured mozTypesAt 1"); exh = false; - try { cd.mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; } + try { SpecialPowers.wrap(cd).mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; } ok(exh, "exception occured mozGetDataAt 1"); exh = false; try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; } ok(exh, "exception occured mozClearDataAt 1"); cd.setData("text/x-moz-url", "http://www.mozilla.org"); - cd.mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0); - is(cd.mozItemCount, 1, "mozItemCount after set multiple types"); + SpecialPowers.wrap(cd).mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0); + is(SpecialPowers.wrap(cd).mozItemCount, 1, "mozItemCount after set multiple types"); return false; }; try { selectContentInput(); await putOnClipboard("would be a phrase", () => { synthesizeKey("c", {accelKey: 1}); @@ -468,17 +468,17 @@ add_task(async function test_input_copyp finally { contentInput.oncopy = null; } contentInput.setSelectionRange(5, 14); contentInput.onpaste = function(event) { var cd = event.clipboardData; - is(cd.mozItemCount, 1, "paste after copy multiple types mozItemCount"); + is(SpecialPowers.wrap(cd).mozItemCount, 1, "paste after copy multiple types mozItemCount"); is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types"); // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore // disabling the following test. Enable this once bug #840101 is fixed. if (!navigator.appVersion.includes("Android")) { is(cd.getData("text/x-moz-url"), "http://www.mozilla.org", "paste text/x-moz-url multiple types"); is(cd.getData("text/x-custom"), "Custom Text with \u0000 null", "paste text/custom multiple types"); } else { @@ -635,20 +635,20 @@ function compareSynthetic(event, eventty if (step == "during") { is(eventtype, expectedData.type, "synthetic " + eventtype + " event fired"); } ok(event.clipboardData instanceof DataTransfer, "clipboardData is assigned"); is(event.type, expectedData.type, "synthetic " + eventtype + " event type"); if (expectedData.data === null) { - is(event.clipboardData.mozItemCount, 0, "synthetic " + eventtype + " empty data"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "synthetic " + eventtype + " empty data"); } else { - is(event.clipboardData.mozItemCount, 1, "synthetic " + eventtype + " item count"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "synthetic " + eventtype + " item count"); is(event.clipboardData.types.length, 1, "synthetic " + eventtype + " types length"); is(event.clipboardData.getData(expectedData.dataType), expectedData.data, "synthetic " + eventtype + " data"); } is(getClipboardText(), "empty", "event does not change the clipboard " + step + " dispatch"); if (step == "during") { @@ -661,17 +661,17 @@ async function checkCachedDataTransfer(c await putOnClipboard("Some Clipboard Text", () => { setClipboardText("Some Clipboard Text") }, "change clipboard outside of event"); var oldtext = cd.getData("text/plain"); ok(!oldtext, "clipboard get using " + testprefix); try { - cd.mozSetDataAt("text/plain", "Test Cache Data", 0); + SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Test Cache Data", 0); } catch (ex) {} ok(!cd.getData("text/plain"), "clipboard set using " + testprefix); is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix); try { cd.mozClearDataAt("text/plain", 0); } catch (ex) {}
new file mode 100644 --- /dev/null +++ b/dom/tests/mochitest/general/test_datatransfer_disallowed.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DataTransfer moz* APIs</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +function run_test() +{ + SpecialPowers.pushPrefEnv({"set": [ + ["dom.datatransfer.moz", false], + ]}, function() { + let hiddenMethods = ["mozTypesAt", "mozClearDataAt", "mozGetDataAt", "mozSetDataAt", "mozItemCount"]; + let exposedMethods = Object.getOwnPropertyNames(DataTransfer.prototype); + for (var idx in hiddenMethods) { + if (exposedMethods.includes(hiddenMethods[idx])) { + ok(false, hiddenMethods[idx] + " should not be exposed"); + } else { + ok(true, hiddenMethods[idx] + " was not exposed"); + } + } + SimpleTest.finish(); + }); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(run_test); +</script>
--- a/dom/webidl/DataTransfer.webidl +++ b/dom/webidl/DataTransfer.webidl @@ -49,17 +49,17 @@ partial interface DataTransfer { * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified */ [Throws, UseCounter] void addElement(Element element); /** * The number of items being dragged. */ - [UseCounter] + [Func="DataTransfer::MozAtAPIsEnabled"] readonly attribute unsigned long mozItemCount; /** * Sets the drag cursor state. Primarily used to control the cursor during * tab drags, but could be expanded to other uses. XXX Currently implemented * on Win32 only. * * Possible values: @@ -72,34 +72,34 @@ partial interface DataTransfer { [UseCounter] attribute DOMString mozCursor; /** * Holds a list of the format types of the data that is stored for an item * at the specified index. If the index is not in the range from 0 to * itemCount - 1, an empty string list is returned. */ - [Throws, NeedsCallerType, UseCounter] + [Throws, NeedsCallerType, Func="DataTransfer::MozAtAPIsEnabled"] DOMStringList mozTypesAt(unsigned long index); /** * Remove the data associated with the given format for an item at the * specified index. The index is in the range from zero to itemCount - 1. * * If the last format for the item is removed, the entire item is removed, * reducing the itemCount by one. * * If format is empty, then the data associated with all formats is removed. * If the format is not found, then this method has no effect. * * @param format the format to remove * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater or equal than itemCount * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified */ - [Throws, NeedsSubjectPrincipal, UseCounter] + [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozAtAPIsEnabled"] void mozClearDataAt(DOMString format, unsigned long index); /* * A data transfer may store multiple items, each at a given zero-based * index. setDataAt may only be called with an index argument less than * itemCount in which case an existing item is modified, or equal to * itemCount in which case a new item is added, and the itemCount is * incremented by one. @@ -113,29 +113,29 @@ partial interface DataTransfer { * (which will be converted into a string) or an nsISupports. * * @param format the format to add * @param data the data to add * @throws NS_ERROR_NULL_POINTER if the data is null * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater than itemCount * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified */ - [Throws, NeedsSubjectPrincipal, UseCounter] + [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozAtAPIsEnabled"] void mozSetDataAt(DOMString format, any data, unsigned long index); /** * Retrieve the data associated with the given format for an item at the * specified index, or null if it does not exist. The index should be in the * range from zero to itemCount - 1. * * @param format the format of the data to look up * @returns the data of the given format, or null if it doesn't exist. * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater or equal than itemCount */ - [Throws, NeedsSubjectPrincipal, UseCounter] + [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozAtAPIsEnabled"] any mozGetDataAt(DOMString format, unsigned long index); /** * Update the drag image. Arguments are the same as setDragImage. This is only * valid within the parent chrome process. */ [ChromeOnly] void updateDragImage(Element image, long x, long y);
--- a/dom/webidl/RTCStatsReport.webidl +++ b/dom/webidl/RTCStatsReport.webidl @@ -24,16 +24,17 @@ dictionary RTCStats { DOMHighResTimeStamp timestamp; RTCStatsType type; DOMString id; }; dictionary RTCRtpStreamStats : RTCStats { unsigned long ssrc; DOMString mediaType; + DOMString kind; DOMString remoteId; boolean isRemote = false; DOMString mediaTrackId; DOMString transportId; DOMString codecId; // Video encoder/decoder measurements, not present in RTCP case double bitrateMean;
--- a/gfx/layers/ImageContainer.cpp +++ b/gfx/layers/ImageContainer.cpp @@ -117,16 +117,25 @@ ImageContainerListener::NotifyComposite( { MutexAutoLock lock(mLock); if (mImageContainer) { mImageContainer->NotifyComposite(aNotification); } } void +ImageContainerListener::NotifyDropped(uint32_t aDropped) +{ + MutexAutoLock lock(mLock); + if (mImageContainer) { + mImageContainer->NotifyDropped(aDropped); + } +} + +void ImageContainerListener::ClearImageContainer() { MutexAutoLock lock(mLock); mImageContainer = nullptr; } void ImageContainerListener::DropImageClient() @@ -253,39 +262,17 @@ ImageContainer::SetCurrentImageInternal( mGenerationCounter = ++sGenerationCounter; if (!aImages.IsEmpty()) { NS_ASSERTION(mCurrentImages.IsEmpty() || mCurrentImages[0].mProducerID != aImages[0].mProducerID || mCurrentImages[0].mFrameID <= aImages[0].mFrameID, "frame IDs shouldn't go backwards"); if (aImages[0].mProducerID != mCurrentProducerID) { - mFrameIDsNotYetComposited.Clear(); mCurrentProducerID = aImages[0].mProducerID; - } else if (!aImages[0].mTimeStamp.IsNull()) { - // Check for expired frames - for (auto& img : mCurrentImages) { - if (img.mProducerID != aImages[0].mProducerID || - img.mTimeStamp.IsNull() || - img.mTimeStamp >= aImages[0].mTimeStamp) { - break; - } - if (!img.mComposited && !img.mTimeStamp.IsNull() && - img.mFrameID != aImages[0].mFrameID) { - mFrameIDsNotYetComposited.AppendElement(img.mFrameID); - } - } - - // Remove really old frames, assuming they'll never be composited. - const uint32_t maxFrames = 100; - if (mFrameIDsNotYetComposited.Length() > maxFrames) { - uint32_t dropFrames = mFrameIDsNotYetComposited.Length() - maxFrames; - mDroppedImageCount += dropFrames; - mFrameIDsNotYetComposited.RemoveElementsAt(0, dropFrames); - } } } nsTArray<OwningImage> newImages; for (uint32_t i = 0; i < aImages.Length(); ++i) { NS_ASSERTION(aImages[i].mImage, "image can't be null"); NS_ASSERTION(!aImages[i].mTimeStamp.IsNull() || aImages.Length() == 1, @@ -298,17 +285,17 @@ ImageContainer::SetCurrentImageInternal( NS_ASSERTION(aImages[i].mProducerID == aImages[i - 1].mProducerID, "ProducerIDs must be the same"); } OwningImage* img = newImages.AppendElement(); img->mImage = aImages[i].mImage; img->mTimeStamp = aImages[i].mTimeStamp; img->mFrameID = aImages[i].mFrameID; img->mProducerID = aImages[i].mProducerID; - for (auto& oldImg : mCurrentImages) { + for (const auto& oldImg : mCurrentImages) { if (oldImg.mFrameID == img->mFrameID && oldImg.mProducerID == img->mProducerID) { img->mComposited = oldImg.mComposited; break; } } } @@ -434,40 +421,35 @@ ImageContainer::NotifyComposite(const Im RecursiveMutexAutoLock lock(mRecursiveMutex); // An image composition notification is sent the first time a particular // image is composited by an ImageHost. Thus, every time we receive such // a notification, a new image has been painted. ++mPaintCount; if (aNotification.producerID() == mCurrentProducerID) { - uint32_t i; - for (i = 0; i < mFrameIDsNotYetComposited.Length(); ++i) { - if (mFrameIDsNotYetComposited[i] <= aNotification.frameID()) { - if (mFrameIDsNotYetComposited[i] < aNotification.frameID()) { - ++mDroppedImageCount; - } - } else { - break; - } - } - mFrameIDsNotYetComposited.RemoveElementsAt(0, i); for (auto& img : mCurrentImages) { if (img.mFrameID == aNotification.frameID()) { img.mComposited = true; } } } if (!aNotification.imageTimeStamp().IsNull()) { - mPaintDelay = aNotification.firstCompositeTimeStamp() - - aNotification.imageTimeStamp(); + mPaintDelay = + aNotification.firstCompositeTimeStamp() - aNotification.imageTimeStamp(); } } +void +ImageContainer::NotifyDropped(uint32_t aDropped) +{ + mDroppedImageCount += aDropped; +} + #ifdef XP_WIN D3D11YCbCrRecycleAllocator* ImageContainer::GetD3D11YCbCrRecycleAllocator(KnowsCompositor* aAllocator) { if (mD3D11YCbCrRecycleAllocator && aAllocator == mD3D11YCbCrRecycleAllocator->GetAllocator()) { return mD3D11YCbCrRecycleAllocator; }
--- a/gfx/layers/ImageContainer.h +++ b/gfx/layers/ImageContainer.h @@ -351,16 +351,17 @@ protected: class ImageContainerListener final { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageContainerListener) public: explicit ImageContainerListener(ImageContainer* aImageContainer); void NotifyComposite(const ImageCompositeNotification& aNotification); + void NotifyDropped(uint32_t aDropped); void ClearImageContainer(); void DropImageClient(); private: typedef mozilla::Mutex Mutex; ~ImageContainerListener(); Mutex mLock; @@ -608,21 +609,21 @@ public: * non-null timestamp, and in a SetCurrentImages call the new image list is * non-empty, the timestamp of the first new image is non-null and greater * than the timestamp associated with the image, and the first new image's * frameID is not the same as the entry's. * Every expired image that is never composited is counted as dropped. */ uint32_t GetDroppedImageCount() { - RecursiveMutexAutoLock lock(mRecursiveMutex); return mDroppedImageCount; } void NotifyComposite(const ImageCompositeNotification& aNotification); + void NotifyDropped(uint32_t aDropped); ImageContainerListener* GetImageContainerListener() { return mNotifyCompositeListener; } /** * Get the ImageClient associated with this container. Returns only after @@ -670,18 +671,18 @@ private: // Number of contained images that have been painted at least once. It's up // to the ImageContainer implementation to ensure accesses to this are // threadsafe. uint32_t mPaintCount; // See GetPaintDelay. Accessed only with mRecursiveMutex held. TimeDuration mPaintDelay; - // See GetDroppedImageCount. Accessed only with mRecursiveMutex held. - uint32_t mDroppedImageCount; + // See GetDroppedImageCount. + mozilla::Atomic<uint32_t> mDroppedImageCount; // This is the image factory used by this container, layer managers using // this container can set an alternative image factory that will be used to // create images for this container. RefPtr<ImageFactory> mImageFactory; gfx::IntSize mScaleHint; @@ -696,19 +697,17 @@ private: // In this case the ImageContainer is perfectly usable, but it will forward // frames to the compositor through transactions in the main thread rather than // asynchronusly using the ImageBridge IPDL protocol. RefPtr<ImageClient> mImageClient; bool mIsAsync; CompositableHandle mAsyncContainerHandle; - nsTArray<FrameID> mFrameIDsNotYetComposited; - // ProducerID for last current image(s), including the frames in - // mFrameIDsNotYetComposited + // ProducerID for last current image(s) ProducerID mCurrentProducerID; RefPtr<ImageContainerListener> mNotifyCompositeListener; static mozilla::Atomic<uint32_t> sGenerationCounter; }; class AutoLockImage
--- a/gfx/layers/composite/CompositableHost.h +++ b/gfx/layers/composite/CompositableHost.h @@ -122,21 +122,22 @@ public: return false; } /** * Returns the front buffer. * *aPictureRect (if non-null, and the returned TextureHost is non-null) * is set to the picture rect. */ - virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) { + virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) + { return nullptr; } - virtual gfx::IntSize GetImageSize() const + virtual gfx::IntSize GetImageSize() { MOZ_ASSERT(false, "Should have been overridden"); return gfx::IntSize(); } /** * Adds a mask effect using this texture as the mask, if possible. * @return true if the effect was added, false otherwise. @@ -239,16 +240,18 @@ public: /// Called when shutting down the layer tree. /// This is a good place to clear all potential gpu resources before the widget /// is is destroyed. virtual void CleanupResources() {} virtual void BindTextureSource() {} + virtual uint32_t GetDroppedFrames() { return 0; } + protected: HostLayerManager* GetLayerManager() const; protected: TextureInfo mTextureInfo; AsyncCompositableRef mAsyncRef; uint64_t mCompositorBridgeID; RefPtr<TextureSourceProvider> mTextureSourceProvider;
--- a/gfx/layers/composite/ImageComposite.cpp +++ b/gfx/layers/composite/ImageComposite.cpp @@ -13,79 +13,94 @@ using namespace gfx; namespace layers { /* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f; ImageComposite::ImageComposite() : mLastFrameID(-1) , mLastProducerID(-1) , mBias(BIAS_NONE) -{} + , mDroppedFrames(0) + , mLastChosenImageIndex(0) +{ +} ImageComposite::~ImageComposite() { } -/* static */ TimeStamp -ImageComposite::GetBiasedTime(const TimeStamp& aInput, ImageComposite::Bias aBias) +TimeStamp +ImageComposite::GetBiasedTime(const TimeStamp& aInput) const { - switch (aBias) { + switch (mBias) { case ImageComposite::BIAS_NEGATIVE: return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS); case ImageComposite::BIAS_POSITIVE: return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS); default: return aInput; } } -/* static */ ImageComposite::Bias -ImageComposite::UpdateBias(const TimeStamp& aCompositionTime, - const TimeStamp& aCompositedImageTime, - const TimeStamp& aNextImageTime, // may be null - ImageComposite::Bias aBias) +void +ImageComposite::UpdateBias(size_t aImageIndex) { - if (aCompositedImageTime.IsNull()) { - return ImageComposite::BIAS_NONE; + MOZ_ASSERT(aImageIndex < ImagesCount()); + + TimeStamp compositionTime = GetCompositionTime(); + TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp; + TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount() + ? mImages[aImageIndex + 1].mTimeStamp + : TimeStamp(); + + if (compositedImageTime.IsNull()) { + mBias = ImageComposite::BIAS_NONE; + return; } TimeDuration threshold = TimeDuration::FromMilliseconds(1.0); - if (aCompositionTime - aCompositedImageTime < threshold && - aCompositionTime - aCompositedImageTime > -threshold) { + if (compositionTime - compositedImageTime < threshold && + compositionTime - compositedImageTime > -threshold) { // The chosen frame's time is very close to the composition time (probably // just before the current composition time, but due to previously set // negative bias, it could be just after the current composition time too). // If the inter-frame time is almost exactly equal to (a multiple of) // the inter-composition time, then we're in a dangerous situation because // jitter might cause frames to fall one side or the other of the // composition times, causing many frames to be skipped or duplicated. // Try to prevent that by adding a negative bias to the frame times during // the next composite; that should ensure the next frame's time is treated // as falling just before a composite time. - return ImageComposite::BIAS_NEGATIVE; + mBias = ImageComposite::BIAS_NEGATIVE; + return; } - if (!aNextImageTime.IsNull() && - aNextImageTime - aCompositionTime < threshold && - aNextImageTime - aCompositionTime > -threshold) { + if (!nextImageTime.IsNull() && + nextImageTime - compositionTime < threshold && + nextImageTime - compositionTime > -threshold) { // The next frame's time is very close to our composition time (probably // just after the current composition time, but due to previously set // positive bias, it could be just before the current composition time too). // We're in a dangerous situation because jitter might cause frames to // fall one side or the other of the composition times, causing many frames // to be skipped or duplicated. // Try to prevent that by adding a negative bias to the frame times during // the next composite; that should ensure the next frame's time is treated // as falling just before a composite time. - return ImageComposite::BIAS_POSITIVE; + mBias = ImageComposite::BIAS_POSITIVE; + return; } - return ImageComposite::BIAS_NONE; + mBias = ImageComposite::BIAS_NONE; } int -ImageComposite::ChooseImageIndex() const +ImageComposite::ChooseImageIndex() { + // ChooseImageIndex is called for all images in the layer when it is visible. + // Change to this behaviour would break dropped frames counting calculation: + // We rely on this assumption to determine if during successive runs an + // image is returned that isn't the one following immediately the previous one if (mImages.IsEmpty()) { return -1; } TimeStamp now = GetCompositionTime(); if (now.IsNull()) { // Not in a composition, so just return the last image we composited // (if it's one of the current images). @@ -93,30 +108,121 @@ ImageComposite::ChooseImageIndex() const if (mImages[i].mFrameID == mLastFrameID && mImages[i].mProducerID == mLastProducerID) { return i; } } return -1; } - uint32_t result = 0; + uint32_t result = mLastChosenImageIndex; while (result + 1 < mImages.Length() && - GetBiasedTime(mImages[result + 1].mTimeStamp, mBias) <= now) { + GetBiasedTime(mImages[result + 1].mTimeStamp) <= now) { ++result; } + if (result - mLastChosenImageIndex > 1) { + // We're not returning the same image as the last call to ChooseImageIndex + // or the immediately next one. We can assume that the frames not returned + // have been dropped as they were too late to be displayed + mDroppedFrames += result - mLastChosenImageIndex - 1; + } + mLastChosenImageIndex = result; return result; } -const ImageComposite::TimedImage* ImageComposite::ChooseImage() const +const ImageComposite::TimedImage* ImageComposite::ChooseImage() { int index = ChooseImageIndex(); return index >= 0 ? &mImages[index] : nullptr; } -ImageComposite::TimedImage* ImageComposite::ChooseImage() +void +ImageComposite::RemoveImagesWithTextureHost(TextureHost* aTexture) +{ + for (int32_t i = mImages.Length() - 1; i >= 0; --i) { + if (mImages[i].mTextureHost == aTexture) { + aTexture->UnbindTextureSource(); + mImages.RemoveElementAt(i); + } + } +} + +void +ImageComposite::ClearImages() +{ + mImages.Clear(); + mLastChosenImageIndex = 0; +} + +uint32_t +ImageComposite::ScanForLastFrameIndex(const nsTArray<TimedImage>& aNewImages) { - int index = ChooseImageIndex(); - return index >= 0 ? &mImages[index] : nullptr; + if (mImages.IsEmpty()) { + return 0; + } + uint32_t i = mLastChosenImageIndex; + uint32_t newIndex = 0; + uint32_t dropped = 0; + // See if the new array of images have any images in common with the + // previous list that we haven't played yet. + uint32_t j = 0; + while (i < mImages.Length() && j < aNewImages.Length()) { + if (mImages[i].mProducerID != aNewImages[j].mProducerID) { + // This is new content, can stop. + newIndex = j; + break; + } + int32_t oldFrameID = mImages[i].mFrameID; + int32_t newFrameID = aNewImages[j].mFrameID; + if (oldFrameID > newFrameID) { + // This is an image we have already returned, we don't need to present + // it again and can start from this index next time. + newIndex = ++j; + continue; + } + if (oldFrameID < mLastFrameID) { + // we have already returned that frame previously, ignore. + i++; + continue; + } + if (oldFrameID < newFrameID) { + // This is a new image, all images prior the new one and not yet + // rendered can be considered as dropped. Those images have a FrameID + // inferior to the new image. + for (++i; i < mImages.Length() && mImages[i].mFrameID < newFrameID && + mImages[i].mProducerID == aNewImages[j].mProducerID; + i++) { + dropped++; + } + break; + } + i++; + j++; + } + if (dropped > 0) { + mDroppedFrames += dropped; + } + if (newIndex >= aNewImages.Length()) { + // Somehow none of those images should be rendered (can this happen?) + // We will always return the last one for now. + newIndex = aNewImages.Length() - 1; + } + return newIndex; +} + +void +ImageComposite::SetImages(nsTArray<TimedImage>&& aNewImages) +{ + mLastChosenImageIndex = ScanForLastFrameIndex(aNewImages); + mImages = std::move(aNewImages); +} + +const ImageComposite::TimedImage* +ImageComposite::GetImage(size_t aIndex) const +{ + if (aIndex >= mImages.Length()) { + return nullptr; + } + return &mImages[aIndex]; } } // namespace layers } // namespace mozilla
--- a/gfx/layers/composite/ImageComposite.h +++ b/gfx/layers/composite/ImageComposite.h @@ -35,33 +35,34 @@ public: int32_t GetProducerID() { const TimedImage* img = ChooseImage(); return img ? img->mProducerID : -1; } int32_t GetLastFrameID() const { return mLastFrameID; } int32_t GetLastProducerID() const { return mLastProducerID; } + uint32_t GetDroppedFramesAndReset() + { + uint32_t dropped = mDroppedFrames; + mDroppedFrames = 0; + return dropped; + } enum Bias { // Don't apply bias to frame times BIAS_NONE, // Apply a negative bias to frame times to keep them before the vsync time BIAS_NEGATIVE, // Apply a positive bias to frame times to keep them after the vsync time BIAS_POSITIVE, }; - static TimeStamp GetBiasedTime(const TimeStamp& aInput, ImageComposite::Bias aBias); - protected: - static Bias UpdateBias(const TimeStamp& aCompositionTime, - const TimeStamp& aCompositedImageTime, - const TimeStamp& aNextImageTime, // may be null - ImageComposite::Bias aBias); + void UpdateBias(size_t aImageIndex); virtual TimeStamp GetCompositionTime() const = 0; struct TimedImage { CompositableTextureHostRef mTextureHost; TimeStamp mTimeStamp; gfx::IntRect mPictureRect; int32_t mFrameID; @@ -69,25 +70,42 @@ protected: }; /** * ChooseImage is guaranteed to return the same TimedImage every time it's * called during the same composition, up to the end of Composite() --- * it depends only on mImages, mCompositor->GetCompositionTime(), and mBias. * mBias is updated at the end of Composite(). */ - const TimedImage* ChooseImage() const; - TimedImage* ChooseImage(); - int ChooseImageIndex() const; + const TimedImage* ChooseImage(); + int ChooseImageIndex(); + const TimedImage* GetImage(size_t aIndex) const; + size_t ImagesCount() const { return mImages.Length(); } + const nsTArray<TimedImage>& Images() const { return mImages; } - nsTArray<TimedImage> mImages; + void RemoveImagesWithTextureHost(TextureHost* aTexture); + void ClearImages(); + void SetImages(nsTArray<TimedImage>&& aNewImages); + int32_t mLastFrameID; int32_t mLastProducerID; + +private: + nsTArray<TimedImage> mImages; + TimeStamp GetBiasedTime(const TimeStamp& aInput) const; + // Scan new images and look for common ones in the existing mImages array. + // Will determine if an image has been dropped through gaps between images and + // adjust mDroppedFrames accordingly. + // Return the index of what the last returned image would have been. + uint32_t ScanForLastFrameIndex(const nsTArray<TimedImage>& aNewImages); + /** * Bias to apply to the next frame. */ Bias mBias; + uint32_t mDroppedFrames; + uint32_t mLastChosenImageIndex; }; } // namespace layers } // namespace mozilla #endif // MOZILLA_GFX_IMAGECOMPOSITE_H
--- a/gfx/layers/composite/ImageHost.cpp +++ b/gfx/layers/composite/ImageHost.cpp @@ -4,16 +4,17 @@ * 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 "ImageHost.h" #include "LayersLogging.h" // for AppendToString #include "composite/CompositableHost.h" // for CompositableHost, etc #include "ipc/IPCMessageUtils.h" // for null_t +#include "mozilla/Move.h" #include "mozilla/layers/Compositor.h" // for Compositor #include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc #include "mozilla/layers/LayerManagerComposite.h" // for TexturedEffect, Effect, etc #include "nsAString.h" #include "nsDebug.h" // for NS_WARNING, NS_ASSERTION #include "nsPrintfCString.h" // for nsPrintfCString #include "nsString.h" // for nsAutoCString @@ -61,37 +62,36 @@ ImageHost::UseTextureHost(const nsTArray img.mTimeStamp = t.mTimeStamp; img.mPictureRect = t.mPictureRect; img.mFrameID = t.mFrameID; img.mProducerID = t.mProducerID; img.mTextureHost->SetCropRect(img.mPictureRect); img.mTextureHost->Updated(); } - mImages.SwapElements(newImages); - newImages.Clear(); + SetImages(std::move(newImages)); // If we only have one image we can upload it right away, otherwise we'll upload // on-demand during composition after we have picked the proper timestamp. - if (mImages.Length() == 1) { - SetCurrentTextureHost(mImages[0].mTextureHost); + if (ImagesCount() == 1) { + SetCurrentTextureHost(GetImage(0)->mTextureHost); } HostLayerManager* lm = GetLayerManager(); // Video producers generally send replacement images with the same frameID but // slightly different timestamps in order to sync with the audio clock. This // means that any CompositeUntil() call we made in Composite() may no longer // guarantee that we'll composite until the next frame is ready. Fix that here. if (lm && mLastFrameID >= 0) { - for (size_t i = 0; i < mImages.Length(); ++i) { - bool frameComesAfter = mImages[i].mFrameID > mLastFrameID || - mImages[i].mProducerID != mLastProducerID; - if (frameComesAfter && !mImages[i].mTimeStamp.IsNull()) { - lm->CompositeUntil(mImages[i].mTimeStamp + + for (const auto& img : Images()) { + bool frameComesAfter = + img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID; + if (frameComesAfter && !img.mTimeStamp.IsNull()) { + lm->CompositeUntil(img.mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); break; } } } } void @@ -137,54 +137,49 @@ ImageHost::CleanupResources() } void ImageHost::RemoveTextureHost(TextureHost* aTexture) { MOZ_ASSERT(!mLocked); CompositableHost::RemoveTextureHost(aTexture); - - for (int32_t i = mImages.Length() - 1; i >= 0; --i) { - if (mImages[i].mTextureHost == aTexture) { - aTexture->UnbindTextureSource(); - mImages.RemoveElementAt(i); - } - } + RemoveImagesWithTextureHost(aTexture); } TimeStamp ImageHost::GetCompositionTime() const { TimeStamp time; if (HostLayerManager* lm = GetLayerManager()) { time = lm->GetCompositionTime(); } return time; } TextureHost* ImageHost::GetAsTextureHost(IntRect* aPictureRect) { - TimedImage* img = ChooseImage(); - if (img) { - SetCurrentTextureHost(img->mTextureHost); + const TimedImage* img = ChooseImage(); + if (!img) { + return nullptr; } - if (aPictureRect && img) { + SetCurrentTextureHost(img->mTextureHost); + if (aPictureRect) { *aPictureRect = img->mPictureRect; } - return img ? img->mTextureHost.get() : nullptr; + return img->mTextureHost; } void ImageHost::Attach(Layer* aLayer, TextureSourceProvider* aProvider, AttachFlags aFlags) { CompositableHost::Attach(aLayer, aProvider, aFlags); - for (auto& img : mImages) { + for (const auto& img : Images()) { img.mTextureHost->SetTextureSourceProvider(aProvider); img.mTextureHost->Updated(); } } void ImageHost::Composite(Compositor* aCompositor, LayerComposite* aLayer, @@ -196,17 +191,17 @@ ImageHost::Composite(Compositor* aCompos const nsIntRegion* aVisibleRegion, const Maybe<gfx::Polygon>& aGeometry) { RenderInfo info; if (!PrepareToRender(aCompositor, &info)) { return; } - TimedImage* img = info.img; + const TimedImage* img = info.img; { AutoLockCompositableHost autoLock(this); if (autoLock.Failed()) { NS_WARNING("failed to lock front buffer"); return; } @@ -314,21 +309,22 @@ ImageHost::PrepareToRender(TextureSource return false; } int imageIndex = ChooseImageIndex(); if (imageIndex < 0) { return false; } - if (uint32_t(imageIndex) + 1 < mImages.Length()) { - lm->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + if (uint32_t(imageIndex) + 1 < ImagesCount()) { + lm->CompositeUntil(GetImage(imageIndex + 1)->mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); } - TimedImage* img = &mImages[imageIndex]; + const TimedImage* img = GetImage(imageIndex); img->mTextureHost->SetTextureSourceProvider(aProvider); SetCurrentTextureHost(img->mTextureHost); aOutInfo->imageIndex = imageIndex; aOutInfo->img = img; aOutInfo->host = mCurrentTextureHost; return true; } @@ -342,17 +338,17 @@ ImageHost::AcquireTextureSource(const Re } return mCurrentTextureSource.get(); } void ImageHost::FinishRendering(const RenderInfo& aInfo) { HostLayerManager* lm = GetLayerManager(); - TimedImage* img = aInfo.img; + const TimedImage* img = aInfo.img; int imageIndex = aInfo.imageIndex; if (mLastFrameID != img->mFrameID || mLastProducerID != img->mProducerID) { if (mAsyncRef) { ImageCompositeNotificationInfo info; info.mImageBridgeProcessId = mAsyncRef.mProcessId; info.mNotification = ImageCompositeNotification( mAsyncRef.mHandle, @@ -364,78 +360,74 @@ ImageHost::FinishRendering(const RenderI mLastProducerID = img->mProducerID; } // Update mBias last. This can change which frame ChooseImage(Index) would // return, and we don't want to do that until we've finished compositing // since callers of ChooseImage(Index) assume the same image will be chosen // during a given composition. This must happen after autoLock's // destructor! - mBias = UpdateBias( - lm->GetCompositionTime(), mImages[imageIndex].mTimeStamp, - uint32_t(imageIndex + 1) < mImages.Length() ? - mImages[imageIndex + 1].mTimeStamp : TimeStamp(), - mBias); + UpdateBias(imageIndex); } void ImageHost::SetTextureSourceProvider(TextureSourceProvider* aProvider) { if (mTextureSourceProvider != aProvider) { - for (auto& img : mImages) { + for (const auto& img : Images()) { img.mTextureHost->SetTextureSourceProvider(aProvider); } } CompositableHost::SetTextureSourceProvider(aProvider); } void ImageHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) { aStream << aPrefix; aStream << nsPrintfCString("ImageHost (0x%p)", this).get(); nsAutoCString pfx(aPrefix); pfx += " "; - for (auto& img : mImages) { + for (const auto& img : Images()) { aStream << "\n"; img.mTextureHost->PrintInfo(aStream, pfx.get()); AppendToString(aStream, img.mPictureRect, " [picture-rect=", "]"); } } void ImageHost::Dump(std::stringstream& aStream, const char* aPrefix, bool aDumpHtml) { - for (auto& img : mImages) { + for (const auto& img : Images()) { aStream << aPrefix; aStream << (aDumpHtml ? "<ul><li>TextureHost: " : "TextureHost: "); DumpTextureHost(aStream, img.mTextureHost); aStream << (aDumpHtml ? " </li></ul> " : " "); } } already_AddRefed<gfx::DataSourceSurface> ImageHost::GetAsSurface() { - TimedImage* img = ChooseImage(); + const TimedImage* img = ChooseImage(); if (img) { return img->mTextureHost->GetAsSurface(); } return nullptr; } bool ImageHost::Lock() { MOZ_ASSERT(!mLocked); - TimedImage* img = ChooseImage(); + const TimedImage* img = ChooseImage(); if (!img) { return false; } SetCurrentTextureHost(img->mTextureHost); if (!mCurrentTextureHost->Lock()) { return false; @@ -451,17 +443,17 @@ ImageHost::Unlock() if (mCurrentTextureHost) { mCurrentTextureHost->Unlock(); } mLocked = false; } IntSize -ImageHost::GetImageSize() const +ImageHost::GetImageSize() { const TimedImage* img = ChooseImage(); if (img) { return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height()); } return IntSize(); } @@ -484,17 +476,17 @@ ImageHost::IsOpaque() return true; } return false; } already_AddRefed<TexturedEffect> ImageHost::GenEffect(const gfx::SamplingFilter aSamplingFilter) { - TimedImage* img = ChooseImage(); + const TimedImage* img = ChooseImage(); if (!img) { return nullptr; } SetCurrentTextureHost(img->mTextureHost); if (!mCurrentTextureHost->BindTextureSource(mCurrentTextureSource)) { return nullptr; } bool isAlphaPremultiplied = true;
--- a/gfx/layers/composite/ImageHost.h +++ b/gfx/layers/composite/ImageHost.h @@ -64,17 +64,17 @@ public: virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override; virtual void Attach(Layer* aLayer, TextureSourceProvider* aProvider, AttachFlags aFlags = NO_FLAGS) override; virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; - gfx::IntSize GetImageSize() const override; + gfx::IntSize GetImageSize() override; virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; virtual void Dump(std::stringstream& aStream, const char* aPrefix = "", bool aDumpHtml = false) override; virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; @@ -86,19 +86,24 @@ public: virtual already_AddRefed<TexturedEffect> GenEffect(const gfx::SamplingFilter aSamplingFilter) override; void SetCurrentTextureHost(TextureHost* aTexture); virtual void CleanupResources() override; bool IsOpaque(); + uint32_t GetDroppedFrames() override + { + return GetDroppedFramesAndReset(); + } + struct RenderInfo { int imageIndex; - TimedImage* img; + const TimedImage* img; RefPtr<TextureHost> host; RenderInfo() : imageIndex(-1), img(nullptr) {} }; // Acquire rendering information for the current frame. bool PrepareToRender(TextureSourceProvider* aProvider, RenderInfo* aOutInfo);
--- a/gfx/layers/ipc/CompositableTransactionParent.cpp +++ b/gfx/layers/ipc/CompositableTransactionParent.cpp @@ -63,51 +63,58 @@ bool CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation& aEdit) { // Ignore all operations on compositables created on stale compositors. We // return true because the child is unable to handle errors. RefPtr<CompositableHost> compositable = FindCompositable(aEdit.compositable()); if (!compositable) { return false; } - if (TextureSourceProvider* provider = compositable->GetTextureSourceProvider()) { + return ReceiveCompositableUpdate(aEdit.detail(), WrapNotNull(compositable)); +} + +bool +CompositableParentManager::ReceiveCompositableUpdate( + const CompositableOperationDetail& aDetail, + NotNull<CompositableHost*> aCompositable) +{ + if (TextureSourceProvider* provider = aCompositable->GetTextureSourceProvider()) { if (!provider->IsValid()) { return false; } } - switch (aEdit.detail().type()) { + switch (aDetail.type()) { case CompositableOperationDetail::TOpPaintTextureRegion: { MOZ_LAYERS_LOG(("[ParentSide] Paint PaintedLayer")); - const OpPaintTextureRegion& op = aEdit.detail().get_OpPaintTextureRegion(); - Layer* layer = compositable->GetLayer(); + const OpPaintTextureRegion& op = aDetail.get_OpPaintTextureRegion(); + Layer* layer = aCompositable->GetLayer(); if (!layer || layer->GetType() != Layer::TYPE_PAINTED) { return false; } PaintedLayerComposite* thebes = static_cast<PaintedLayerComposite*>(layer); const ThebesBufferData& bufferData = op.bufferData(); RenderTraceInvalidateStart(thebes, "FF00FF", op.updatedRegion().GetBounds()); - if (!compositable->UpdateThebes(bufferData, - op.updatedRegion(), - thebes->GetValidRegion())) - { + if (!aCompositable->UpdateThebes(bufferData, + op.updatedRegion(), + thebes->GetValidRegion())) { return false; } RenderTraceInvalidateEnd(thebes, "FF00FF"); break; } case CompositableOperationDetail::TOpUseTiledLayerBuffer: { MOZ_LAYERS_LOG(("[ParentSide] Paint TiledLayerBuffer")); - const OpUseTiledLayerBuffer& op = aEdit.detail().get_OpUseTiledLayerBuffer(); - TiledContentHost* tiledHost = compositable->AsTiledContentHost(); + const OpUseTiledLayerBuffer& op = aDetail.get_OpUseTiledLayerBuffer(); + TiledContentHost* tiledHost = aCompositable->AsTiledContentHost(); NS_ASSERTION(tiledHost, "The compositable is not tiled"); const SurfaceDescriptorTiles& tileDesc = op.tileLayerDescriptor(); bool success = tiledHost->UseTiledLayerBuffer(this, tileDesc); const InfallibleTArray<TileDescriptor>& tileDescriptors = tileDesc.tiles(); @@ -135,90 +142,90 @@ CompositableParentManager::ReceiveCompos } } if (!success) { return false; } break; } case CompositableOperationDetail::TOpRemoveTexture: { - const OpRemoveTexture& op = aEdit.detail().get_OpRemoveTexture(); + const OpRemoveTexture& op = aDetail.get_OpRemoveTexture(); RefPtr<TextureHost> tex = TextureHost::AsTextureHost(op.textureParent()); MOZ_ASSERT(tex.get()); - compositable->RemoveTextureHost(tex); + aCompositable->RemoveTextureHost(tex); break; } case CompositableOperationDetail::TOpUseTexture: { - const OpUseTexture& op = aEdit.detail().get_OpUseTexture(); + const OpUseTexture& op = aDetail.get_OpUseTexture(); AutoTArray<CompositableHost::TimedTexture,4> textures; for (auto& timedTexture : op.textures()) { CompositableHost::TimedTexture* t = textures.AppendElement(); t->mTexture = TextureHost::AsTextureHost(timedTexture.textureParent()); MOZ_ASSERT(t->mTexture); t->mTimeStamp = timedTexture.timeStamp(); t->mPictureRect = timedTexture.picture(); t->mFrameID = timedTexture.frameID(); t->mProducerID = timedTexture.producerID(); if (timedTexture.readLocked()) { t->mTexture->SetReadLocked(); } } if (textures.Length() > 0) { - compositable->UseTextureHost(textures); + aCompositable->UseTextureHost(textures); for (auto& timedTexture : op.textures()) { RefPtr<TextureHost> texture = TextureHost::AsTextureHost(timedTexture.textureParent()); if (texture) { texture->SetLastFwdTransactionId(mFwdTransactionId); // Make sure that each texture was handled by the compositable // because the recycling logic depends on it. MOZ_ASSERT(texture->NumCompositableRefs() > 0); } } } - if (UsesImageBridge() && compositable->GetLayer()) { - ScheduleComposition(compositable); + if (UsesImageBridge() && aCompositable->GetLayer()) { + ScheduleComposition(aCompositable); } break; } case CompositableOperationDetail::TOpUseComponentAlphaTextures: { - const OpUseComponentAlphaTextures& op = aEdit.detail().get_OpUseComponentAlphaTextures(); + const OpUseComponentAlphaTextures& op = aDetail.get_OpUseComponentAlphaTextures(); RefPtr<TextureHost> texOnBlack = TextureHost::AsTextureHost(op.textureOnBlackParent()); RefPtr<TextureHost> texOnWhite = TextureHost::AsTextureHost(op.textureOnWhiteParent()); if (op.readLockedBlack()) { texOnBlack->SetReadLocked(); } if (op.readLockedWhite()) { texOnWhite->SetReadLocked(); } MOZ_ASSERT(texOnBlack && texOnWhite); - compositable->UseComponentAlphaTextures(texOnBlack, texOnWhite); + aCompositable->UseComponentAlphaTextures(texOnBlack, texOnWhite); if (texOnBlack) { texOnBlack->SetLastFwdTransactionId(mFwdTransactionId); // Make sure that each texture was handled by the compositable // because the recycling logic depends on it. MOZ_ASSERT(texOnBlack->NumCompositableRefs() > 0); } if (texOnWhite) { texOnWhite->SetLastFwdTransactionId(mFwdTransactionId); // Make sure that each texture was handled by the compositable // because the recycling logic depends on it. MOZ_ASSERT(texOnWhite->NumCompositableRefs() > 0); } if (UsesImageBridge()) { - ScheduleComposition(compositable); + ScheduleComposition(aCompositable); } break; } default: { MOZ_ASSERT(false, "bad type"); } }
--- a/gfx/layers/ipc/CompositableTransactionParent.h +++ b/gfx/layers/ipc/CompositableTransactionParent.h @@ -4,16 +4,17 @@ * 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_LAYERS_COMPOSITABLETRANSACTIONPARENT_H #define MOZILLA_LAYERS_COMPOSITABLETRANSACTIONPARENT_H #include <vector> // for vector #include "mozilla/Attributes.h" // for override +#include "mozilla/NotNull.h" #include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator #include "mozilla/layers/LayersMessages.h" // for EditReply, etc #include "mozilla/layers/TextureClient.h" #include "CompositableHost.h" namespace mozilla { namespace layers { @@ -43,16 +44,18 @@ public: bool aUseWebRender); RefPtr<CompositableHost> FindCompositable(const CompositableHandle& aHandle); protected: /** * Handle the IPDL messages that affect PCompositable actors. */ bool ReceiveCompositableUpdate(const CompositableOperation& aEdit); + bool ReceiveCompositableUpdate(const CompositableOperationDetail& aDetail, + NotNull<CompositableHost*> aCompositable); void ReleaseCompositable(const CompositableHandle& aHandle); uint64_t mFwdTransactionId = 0; /** * Mapping form IDs to CompositableHosts. */
--- a/gfx/layers/ipc/ImageBridgeChild.cpp +++ b/gfx/layers/ipc/ImageBridgeChild.cpp @@ -992,35 +992,51 @@ ImageBridgeChild::RecvParentAsyncMessage default: NS_ERROR("unknown AsyncParentMessageData type"); return IPC_FAIL_NO_REASON(this); } } return IPC_OK(); } +RefPtr<ImageContainerListener> +ImageBridgeChild::FindListener(const CompositableHandle& aHandle) +{ + RefPtr<ImageContainerListener> listener; + MutexAutoLock lock(mContainerMapLock); + auto it = mImageContainerListeners.find(aHandle.Value()); + if (it != mImageContainerListeners.end()) { + listener = it->second; + } + return listener; +} + mozilla::ipc::IPCResult ImageBridgeChild::RecvDidComposite(InfallibleTArray<ImageCompositeNotification>&& aNotifications) { for (auto& n : aNotifications) { - RefPtr<ImageContainerListener> listener; - { - MutexAutoLock lock(mContainerMapLock); - auto it = mImageContainerListeners.find(n.compositable().Value()); - if (it != mImageContainerListeners.end()) { - listener = it->second; - } - } + RefPtr<ImageContainerListener> listener = FindListener(n.compositable()); if (listener) { listener->NotifyComposite(n); } } return IPC_OK(); } +mozilla::ipc::IPCResult +ImageBridgeChild::RecvReportFramesDropped(const CompositableHandle& aHandle, const uint32_t& aFrames) +{ + RefPtr<ImageContainerListener> listener = FindListener(aHandle); + if (listener) { + listener->NotifyDropped(aFrames); + } + + return IPC_OK(); +} + PTextureChild* ImageBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, wr::MaybeExternalImageId& aExternalImageId, nsIEventTarget* aTarget)
--- a/gfx/layers/ipc/ImageBridgeChild.h +++ b/gfx/layers/ipc/ImageBridgeChild.h @@ -90,17 +90,17 @@ bool InImageBridgeChildThread(); * - (A) The ImageContainer uses ImageBridge. The image is already available to the * compositor process because it has been sent with SetCurrentImage. Yet, the * CompositableHost on the compositor side will needs the ID referring to the * ImageContainer to access the Image. So during the Swap operation that happens * in the transaction, we swap the container ID rather than the image data. * - (B) Since the ImageContainer does not use ImageBridge, the image data is swaped. * * - During composition: - * - (A) The CompositableHost has an AsyncID, it looks up the ID in the + * - (A) The CompositableHost has an AsyncID, it looks up the ID in the * global table to see if there is an image. If there is no image, nothing is rendered. * - (B) The CompositableHost has image data rather than an ID (meaning it is not * using ImageBridge), then it just composites the image data normally. * * This means that there might be a possibility for the ImageBridge to send the first * frame before the first layer transaction that will pass the container ID to the * CompositableHost happens. In this (unlikely) case the layer is not composited * until the layer transaction happens. This means this scenario is not harmful. @@ -189,16 +189,19 @@ public: DeallocPMediaSystemResourceManagerChild(PMediaSystemResourceManagerChild* aActor) override; virtual mozilla::ipc::IPCResult RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages) override; virtual mozilla::ipc::IPCResult RecvDidComposite(InfallibleTArray<ImageCompositeNotification>&& aNotifications) override; + virtual mozilla::ipc::IPCResult + RecvReportFramesDropped(const CompositableHandle& aHandle, const uint32_t& aFrames) override; + // Create an ImageClient from any thread. RefPtr<ImageClient> CreateImageClient( CompositableType aType, ImageContainer* aImageContainer); // Create an ImageClient from the ImageBridge thread. RefPtr<ImageClient> CreateImageClientNow( CompositableType aType, @@ -398,16 +401,17 @@ private: */ std::unordered_map<uint64_t, RefPtr<TextureClient>> mTexturesWaitingRecycled; /** * Mapping from async compositable IDs to image containers. */ Mutex mContainerMapLock; std::unordered_map<uint64_t, RefPtr<ImageContainerListener>> mImageContainerListeners; + RefPtr<ImageContainerListener> FindListener(const CompositableHandle& aHandle); #if defined(XP_WIN) /** * Used for checking if D3D11Device is updated. */ RefPtr<ID3D11Device> mImageDevice; #endif };
--- a/gfx/layers/ipc/ImageBridgeParent.cpp +++ b/gfx/layers/ipc/ImageBridgeParent.cpp @@ -193,20 +193,27 @@ mozilla::ipc::IPCResult ImageBridgeParent::RecvUpdate(EditArray&& aEdits, OpDestroyArray&& aToDestroy, const uint64_t& aFwdTransactionId) { // This ensures that destroy operations are always processed. It is not safe // to early-return from RecvUpdate without doing so. AutoImageBridgeParentAsyncMessageSender autoAsyncMessageSender(this, &aToDestroy); UpdateFwdTransactionId(aFwdTransactionId); - for (EditArray::index_type i = 0; i < aEdits.Length(); ++i) { - if (!ReceiveCompositableUpdate(aEdits[i])) { + for (const auto& edit : aEdits) { + RefPtr<CompositableHost> compositable = + FindCompositable(edit.compositable()); + if (!compositable || + !ReceiveCompositableUpdate(edit.detail(), WrapNotNull(compositable))) { return IPC_FAIL_NO_REASON(this); } + uint32_t dropped = compositable->GetDroppedFrames(); + if (dropped) { + Unused << SendReportFramesDropped(edit.compositable(), dropped); + } } if (!IsSameProcess()) { // Ensure that any pending operations involving back and front // buffers have completed, so that neither process stomps on the // other's buffer contents. LayerManagerComposite::PlatformSyncBeforeReplyUpdate(); }
--- a/gfx/layers/ipc/PImageBridge.ipdl +++ b/gfx/layers/ipc/PImageBridge.ipdl @@ -31,16 +31,19 @@ sync protocol PImageBridge manages PTexture; manages PMediaSystemResourceManager; child: async ParentAsyncMessages(AsyncParentMessageData[] aMessages); async DidComposite(ImageCompositeNotification[] aNotifications); + // Report the number of frames dropped for the given CompositableHost. + async ReportFramesDropped(CompositableHandle aHandle, uint32_t aFrames); + parent: async Update(CompositableOperation[] ops, OpDestroy[] toDestroy, uint64_t fwdTransactionId); // First step of the destruction sequence. This puts ImageBridge // in a state in which it can't send asynchronous messages // so as to not race with the channel getting closed. // In the child side, the Closing the channel does not happen right after WillClose, // it is scheduled in the ImageBridgeChild's message queue in order to ensure
--- a/gfx/layers/wr/WebRenderImageHost.cpp +++ b/gfx/layers/wr/WebRenderImageHost.cpp @@ -2,16 +2,17 @@ /* 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 "WebRenderImageHost.h" #include "LayersLogging.h" +#include "mozilla/Move.h" #include "mozilla/layers/Compositor.h" // for Compositor #include "mozilla/layers/CompositorVsyncScheduler.h" // for CompositorVsyncScheduler #include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc #include "mozilla/layers/LayerManagerComposite.h" // for TexturedEffect, Effect, etc #include "mozilla/layers/WebRenderBridgeParent.h" #include "mozilla/layers/AsyncImagePipelineManager.h" #include "nsAString.h" #include "nsDebug.h" // for NS_WARNING, NS_ASSERTION @@ -63,86 +64,77 @@ WebRenderImageHost::UseTextureHost(const img.mTimeStamp = t.mTimeStamp; img.mPictureRect = t.mPictureRect; img.mFrameID = t.mFrameID; img.mProducerID = t.mProducerID; img.mTextureHost->SetCropRect(img.mPictureRect); img.mTextureHost->Updated(); } - mImages.SwapElements(newImages); - newImages.Clear(); + SetImages(std::move(newImages)); if (mWrBridge && mWrBridge->CompositorScheduler() && GetAsyncRef()) { // Will check if we will generate frame. mWrBridge->CompositorScheduler()->ScheduleComposition(); } // Video producers generally send replacement images with the same frameID but // slightly different timestamps in order to sync with the audio clock. This // means that any CompositeUntil() call we made in Composite() may no longer // guarantee that we'll composite until the next frame is ready. Fix that here. if (mWrBridge && mLastFrameID >= 0) { MOZ_ASSERT(mWrBridge->AsyncImageManager()); - for (size_t i = 0; i < mImages.Length(); ++i) { - bool frameComesAfter = mImages[i].mFrameID > mLastFrameID || - mImages[i].mProducerID != mLastProducerID; - if (frameComesAfter && !mImages[i].mTimeStamp.IsNull()) { - mWrBridge->AsyncImageManager()->CompositeUntil(mImages[i].mTimeStamp + - TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + for (const auto& img : Images()) { + bool frameComesAfter = + img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID; + if (frameComesAfter && !img.mTimeStamp.IsNull()) { + mWrBridge->AsyncImageManager()->CompositeUntil( + img.mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); break; } } } } void WebRenderImageHost::UseComponentAlphaTextures(TextureHost* aTextureOnBlack, TextureHost* aTextureOnWhite) { MOZ_ASSERT_UNREACHABLE("unexpected to be called"); } void WebRenderImageHost::CleanupResources() { - nsTArray<TimedImage> newImages; - mImages.SwapElements(newImages); - newImages.Clear(); + ClearImages(); SetCurrentTextureHost(nullptr); } void WebRenderImageHost::RemoveTextureHost(TextureHost* aTexture) { CompositableHost::RemoveTextureHost(aTexture); - - for (int32_t i = mImages.Length() - 1; i >= 0; --i) { - if (mImages[i].mTextureHost == aTexture) { - aTexture->UnbindTextureSource(); - mImages.RemoveElementAt(i); - } - } + RemoveImagesWithTextureHost(aTexture); } TimeStamp WebRenderImageHost::GetCompositionTime() const { TimeStamp time; if (mWrBridge) { MOZ_ASSERT(mWrBridge->AsyncImageManager()); time = mWrBridge->AsyncImageManager()->GetCompositionTime(); } return time; } TextureHost* WebRenderImageHost::GetAsTextureHost(IntRect* aPictureRect) { - TimedImage* img = ChooseImage(); + const TimedImage* img = ChooseImage(); if (img) { return img->mTextureHost; } return nullptr; } TextureHost* WebRenderImageHost::GetAsTextureHostForComposite() @@ -152,44 +144,41 @@ WebRenderImageHost::GetAsTextureHostForC } int imageIndex = ChooseImageIndex(); if (imageIndex < 0) { SetCurrentTextureHost(nullptr); return nullptr; } - if (uint32_t(imageIndex) + 1 < mImages.Length()) { + if (uint32_t(imageIndex) + 1 < ImagesCount()) { MOZ_ASSERT(mWrBridge->AsyncImageManager()); - mWrBridge->AsyncImageManager()->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + mWrBridge->AsyncImageManager()->CompositeUntil( + GetImage(imageIndex + 1)->mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); } - TimedImage* img = &mImages[imageIndex]; + const TimedImage* img = GetImage(imageIndex); if (mLastFrameID != img->mFrameID || mLastProducerID != img->mProducerID) { if (mAsyncRef) { ImageCompositeNotificationInfo info; info.mImageBridgeProcessId = mAsyncRef.mProcessId; info.mNotification = ImageCompositeNotification( mAsyncRef.mHandle, img->mTimeStamp, mWrBridge->AsyncImageManager()->GetCompositionTime(), img->mFrameID, img->mProducerID); mWrBridge->AsyncImageManager()->AppendImageCompositeNotification(info); } mLastFrameID = img->mFrameID; mLastProducerID = img->mProducerID; } SetCurrentTextureHost(img->mTextureHost); - mBias = UpdateBias( - mWrBridge->AsyncImageManager()->GetCompositionTime(), - mImages[imageIndex].mTimeStamp, - uint32_t(imageIndex + 1) < mImages.Length() ? - mImages[imageIndex + 1].mTimeStamp : TimeStamp(), - mBias); + UpdateBias(imageIndex); return mCurrentTextureHost; } void WebRenderImageHost::SetCurrentTextureHost(TextureHost* aTexture) { if (aTexture == mCurrentTextureHost.get()) { @@ -218,56 +207,56 @@ WebRenderImageHost::Composite(Compositor { MOZ_ASSERT_UNREACHABLE("unexpected to be called"); } void WebRenderImageHost::SetTextureSourceProvider(TextureSourceProvider* aProvider) { if (mTextureSourceProvider != aProvider) { - for (auto& img : mImages) { + for (const auto& img : Images()) { img.mTextureHost->SetTextureSourceProvider(aProvider); } } CompositableHost::SetTextureSourceProvider(aProvider); } void WebRenderImageHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) { aStream << aPrefix; aStream << nsPrintfCString("WebRenderImageHost (0x%p)", this).get(); nsAutoCString pfx(aPrefix); pfx += " "; - for (auto& img : mImages) { + for (const auto& img : Images()) { aStream << "\n"; img.mTextureHost->PrintInfo(aStream, pfx.get()); AppendToString(aStream, img.mPictureRect, " [picture-rect=", "]"); } } void WebRenderImageHost::Dump(std::stringstream& aStream, - const char* aPrefix, - bool aDumpHtml) + const char* aPrefix, + bool aDumpHtml) { - for (auto& img : mImages) { + for (const auto& img : Images()) { aStream << aPrefix; aStream << (aDumpHtml ? "<ul><li>TextureHost: " : "TextureHost: "); DumpTextureHost(aStream, img.mTextureHost); aStream << (aDumpHtml ? " </li></ul> " : " "); } } already_AddRefed<gfx::DataSourceSurface> WebRenderImageHost::GetAsSurface() { - TimedImage* img = ChooseImage(); + const TimedImage* img = ChooseImage(); if (img) { return img->mTextureHost->GetAsSurface(); } return nullptr; } bool WebRenderImageHost::Lock() @@ -278,17 +267,17 @@ WebRenderImageHost::Lock() void WebRenderImageHost::Unlock() { MOZ_ASSERT_UNREACHABLE("unexpected to be called"); } IntSize -WebRenderImageHost::GetImageSize() const +WebRenderImageHost::GetImageSize() { const TimedImage* img = ChooseImage(); if (img) { return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height()); } return IntSize(); }
--- a/gfx/layers/wr/WebRenderImageHost.h +++ b/gfx/layers/wr/WebRenderImageHost.h @@ -45,32 +45,37 @@ public: virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override; virtual void Attach(Layer* aLayer, TextureSourceProvider* aProvider, AttachFlags aFlags = NO_FLAGS) override; virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; - gfx::IntSize GetImageSize() const override; + gfx::IntSize GetImageSize() override; virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; virtual void Dump(std::stringstream& aStream, const char* aPrefix = "", bool aDumpHtml = false) override; virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; virtual bool Lock() override; virtual void Unlock() override; virtual void CleanupResources() override; + uint32_t GetDroppedFrames() override + { + return GetDroppedFramesAndReset(); + } + virtual WebRenderImageHost* AsWebRenderImageHost() override { return this; } TextureHost* GetAsTextureHostForComposite(); void SetWrBridge(WebRenderBridgeParent* aWrBridge); void ClearWrBridge();
--- a/gfx/skia/skia/src/utils/SkDashPath.cpp +++ b/gfx/skia/skia/src/utils/SkDashPath.cpp @@ -370,22 +370,23 @@ bool SkDashPath::InternalFilter(SkPath* const SkPath* srcPtr = &src; if (cull_path(src, *rec, cullRect, intervalLength, &cullPathStorage)) { // if rect is closed, starts in a dash, and ends in a dash, add the initial join // potentially a better fix is described here: bug.skia.org/7445 if (src.isRect(nullptr) && src.isLastContourClosed() && is_even(initialDashIndex)) { SkScalar pathLength = SkPathMeasure(src, false, rec->getResScale()).getLength(); SkScalar endPhase = SkScalarMod(pathLength + initialDashLength, intervalLength); int index = 0; - while (endPhase > intervals[index]) { - endPhase -= intervals[index++]; + SkScalar sum = 0; + while (endPhase > sum + intervals[index]) { + sum += intervals[index++]; SkASSERT(index <= count); } // if dash ends inside "on", or ends at beginning of "off" - if (is_even(index) == (endPhase > 0)) { + if (is_even(index) == (endPhase > sum)) { SkPoint midPoint = src.getPoint(0); // get vector at end of rect int last = src.countPoints() - 1; while (midPoint == src.getPoint(last)) { --last; SkASSERT(last >= 0); } // get vector at start of rect
--- a/gfx/tests/mochitest/mochitest.ini +++ b/gfx/tests/mochitest/mochitest.ini @@ -1,9 +1,9 @@ [DEFAULT] [test_acceleration.html] -skip-if = (os == 'win' && os_version == '10.0' && !debug) # Bug 1430530 +skip-if = (os == 'win') # Bug 1430530 subsuite = gpu [test_bug509244.html] [test_bug513439.html] [test_font_whitelist.html] skip-if = (os == "win" && asan) # Bug 1458364
--- a/gfx/thebes/gfxFontMissingGlyphs.cpp +++ b/gfx/thebes/gfxFontMissingGlyphs.cpp @@ -152,17 +152,16 @@ public: if (!aManager->HasUserData(&sWRUserDataKey)) { aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager)); } } void Remove() { - remove(); mManager->RemoveUserData(&sWRUserDataKey); } layers::WebRenderLayerManager* mManager; static UserDataKey sWRUserDataKey; }; @@ -227,18 +226,21 @@ PurgeWRGlyphAtlas() (uint32_t)(uintptr_t)gWRGlyphAtlas[i]->GetUserData( reinterpret_cast<UserDataKey*>(manager)); if (handle) { manager->AddImageKeyForDiscard( wr::ImageKey{manager->WrBridge()->GetNamespace(), handle}); } } } - // Remove the layer manager's destroy notification. - user->Remove(); + } + // Remove the layer managers' destroy notifications only after processing + // so as not to mess up gWRUsers iteration. + while (!gWRUsers.isEmpty()) { + gWRUsers.popFirst()->Remove(); } // Finally, clear out the atlases. for (size_t i = 0; i < 8; i++) { gWRGlyphAtlas[i] = nullptr; } } WRUserData::WRUserData(layers::WebRenderLayerManager* aManager)
--- a/ipc/glue/BackgroundChildImpl.cpp +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -415,16 +415,17 @@ BackgroundChildImpl::AllocPCamerasChild( bool BackgroundChildImpl::DeallocPCamerasChild(camera::PCamerasChild *aActor) { #ifdef MOZ_WEBRTC RefPtr<camera::CamerasChild> child = dont_AddRef(static_cast<camera::CamerasChild*>(aActor)); MOZ_ASSERT(aActor); + camera::Shutdown(); #endif return true; } // ----------------------------------------------------------------------------- // ServiceWorkerManager // -----------------------------------------------------------------------------
--- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -30,16 +30,17 @@ #include "vm/JSObject-inl.h" using namespace js; using mozilla::AssertedCast; using mozilla::IsFinite; using mozilla::IsNaN; using mozilla::IsNegative; +using mozilla::SpecificNaN; using js::intl::CallICU; using js::intl::DateTimeFormatOptions; using js::intl::GetAvailableLocales; using js::intl::IcuLocale; using JS::AutoStableStringChars; @@ -371,30 +372,36 @@ NewUNumberFormat(JSContext* cx, Handle<N } unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping); unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); return toClose.forget(); } static JSString* -PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double x, +PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double* x, UFieldPositionIterator* fpositer) { - return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) { - return unum_formatDoubleForFields(nf, x, chars, size, fpositer, status); + // ICU incorrectly formats NaN values with the sign bit set, as if they + // were negative. Replace all NaNs with a single pattern with sign bit + // unset ("positive", that is) until ICU is fixed. + if (MOZ_UNLIKELY(IsNaN(*x))) + *x = SpecificNaN<double>(0, 1); + + return CallICU(cx, [nf, d = *x, fpositer](UChar* chars, int32_t size, UErrorCode* status) { + return unum_formatDoubleForFields(nf, d, chars, size, fpositer, status); }); } static bool intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result) { // Passing null for |fpositer| will just not compute partition information, // letting us common up all ICU number-formatting code. - JSString* str = PartitionNumberPattern(cx, nf, x, nullptr); + JSString* str = PartitionNumberPattern(cx, nf, &x, nullptr); if (!str) return false; result.setString(str); return true; } using FieldType = ImmutablePropertyNamePtr JSAtomState::*; @@ -423,16 +430,21 @@ GetFieldTypeForNumberField(UNumberFormat case UNUM_FRACTION_FIELD: return &JSAtomState::fraction; case UNUM_SIGN_FIELD: { // Manual trawling through the ICU call graph appears to indicate that // the basic formatting we request will never include a positive sign. // But this analysis may be mistaken, so don't absolutely trust it. + MOZ_ASSERT(!IsNaN(d), + "ICU appearing not to produce positive-sign among fields, " + "plus our coercing all NaNs to one with sign bit unset " + "(i.e. \"positive\"), means we shouldn't reach here with a " + "NaN value"); return IsNegative(d) ? &JSAtomState::minusSign : &JSAtomState::plusSign; } case UNUM_PERCENT_FIELD: return &JSAtomState::percentSign; case UNUM_CURRENCY_FIELD: return &JSAtomState::currency; @@ -473,17 +485,17 @@ intl_FormatNumberToParts(JSContext* cx, if (U_FAILURE(status)) { intl::ReportInternalError(cx); return false; } MOZ_ASSERT(fpositer); ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer); - RootedString overallResult(cx, PartitionNumberPattern(cx, nf, x, fpositer)); + RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer)); if (!overallResult) return false; RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx)); if (!partsArray) return false; // First, vacuum up fields in the overall formatted string.
--- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -146,18 +146,19 @@ GCRuntime::tryNewNurseryString(JSContext Cell* cell = cx->nursery().allocateString(cx->zone(), thingSize, kind); if (cell) return static_cast<JSString*>(cell); if (allowGC && !cx->suppressGC) { cx->runtime()->gc.minorGC(JS::gcreason::OUT_OF_NURSERY); - // Exceeding gcMaxBytes while tenuring can disable the Nursery. - if (cx->nursery().isEnabled()) + // Exceeding gcMaxBytes while tenuring can disable the Nursery, and + // other heuristics can disable nursery strings for this zone. + if (cx->nursery().isEnabled() && cx->zone()->allocNurseryStrings) return static_cast<JSString*>(cx->nursery().allocateString(cx->zone(), thingSize, kind)); } return nullptr; } template <typename StringAllocT, AllowGC allowGC /* = CanGC */> StringAllocT* js::AllocateString(JSContext* cx, InitialHeap heap)
--- a/js/src/gc/WeakMap-inl.h +++ b/js/src/gc/WeakMap-inl.h @@ -20,34 +20,34 @@ static T extractUnbarriered(const WriteB return v.get(); } template <typename T> static T* extractUnbarriered(T* v) { return v; } -template <class K, class V, class HP> -WeakMap<K, V, HP>::WeakMap(JSContext* cx, JSObject* memOf) +template <class K, class V> +WeakMap<K, V>::WeakMap(JSContext* cx, JSObject* memOf) : Base(cx->zone()), WeakMapBase(memOf, cx->zone()) { zone()->gcWeakMapList().insertFront(this); marked = JS::IsIncrementalGCInProgress(TlsContext.get()); } // Trace a WeakMap entry based on 'markedCell' getting marked, where 'origKey' // is the key in the weakmap. These will probably be the same, but can be // different eg when markedCell is a delegate for origKey. // // This implementation does not use 'markedCell'; it looks up origKey and checks // the mark bits on everything it cares about, one of which will be // markedCell. But a subclass might use it to optimize the liveness check. -template <class K, class V, class HP> +template <class K, class V> void -WeakMap<K, V, HP>::markEntry(GCMarker* marker, gc::Cell* markedCell, JS::GCCellPtr origKey) +WeakMap<K, V>::markEntry(GCMarker* marker, gc::Cell* markedCell, JS::GCCellPtr origKey) { MOZ_ASSERT(marked); // If this cast fails, then you're instantiating the WeakMap with a // Lookup that can't be constructed from a Cell*. The WeakKeyTable // mechanism is indexed with a GCCellPtr, so that won't work. Ptr p = Base::lookup(static_cast<Lookup>(origKey.asCell())); MOZ_ASSERT(p.found()); @@ -59,19 +59,19 @@ WeakMap<K, V, HP>::markEntry(GCMarker* m } else if (keyNeedsMark(key)) { TraceEdge(marker, &p->value(), "WeakMap ephemeron value"); TraceEdge(marker, &key, "proxy-preserved WeakMap ephemeron key"); MOZ_ASSERT(key == p->key()); // No moving } key.unsafeSet(nullptr); // Prevent destructor from running barriers. } -template <class K, class V, class HP> +template <class K, class V> void -WeakMap<K, V, HP>::trace(JSTracer* trc) +WeakMap<K, V>::trace(JSTracer* trc) { MOZ_ASSERT_IF(JS::RuntimeHeapIsBusy(), isInList()); TraceNullableEdge(trc, &memberOf, "WeakMap owner"); if (trc->isMarkingTracer()) { MOZ_ASSERT(trc->weakMapAction() == ExpandWeakMaps); marked = true; @@ -89,19 +89,19 @@ WeakMap<K, V, HP>::trace(JSTracer* trc) } // Always trace all values (unless weakMapAction() is // DoNotTraceWeakMaps). for (Range r = Base::all(); !r.empty(); r.popFront()) TraceEdge(trc, &r.front().value(), "WeakMap entry value"); } -template <class K, class V, class HP> +template <class K, class V> /* static */ void -WeakMap<K, V, HP>::addWeakEntry(GCMarker* marker, JS::GCCellPtr key, +WeakMap<K, V>::addWeakEntry(GCMarker* marker, JS::GCCellPtr key, const gc::WeakMarkable& markable) { Zone* zone = key.asCell()->asTenured().zone(); auto p = zone->gcWeakKeys().get(key); if (p) { gc::WeakEntryVector& weakEntries = p->value; if (!weakEntries.append(markable)) @@ -109,19 +109,19 @@ WeakMap<K, V, HP>::addWeakEntry(GCMarker } else { gc::WeakEntryVector weakEntries; MOZ_ALWAYS_TRUE(weakEntries.append(markable)); if (!zone->gcWeakKeys().put(JS::GCCellPtr(key), std::move(weakEntries))) marker->abortLinearWeakMarking(); } } -template <class K, class V, class HP> +template <class K, class V> bool -WeakMap<K, V, HP>::markIteratively(GCMarker* marker) +WeakMap<K, V>::markIteratively(GCMarker* marker) { MOZ_ASSERT(marked); bool markedAny = false; for (Enum e(*this); !e.empty(); e.popFront()) { // If the entry is live, ensure its key and value are marked. bool keyIsMarked = gc::IsMarked(marker->runtime(), &e.front().mutableKey()); @@ -147,110 +147,112 @@ WeakMap<K, V, HP>::markIteratively(GCMar if (JSObject* delegate = getDelegate(e.front().key())) addWeakEntry(marker, JS::GCCellPtr(delegate), markable); } } return markedAny; } -template <class K, class V, class HP> +template <class K, class V> inline JSObject* -WeakMap<K, V, HP>::getDelegate(JSObject* key) const +WeakMap<K, V>::getDelegate(JSObject* key) const { + JS::AutoSuppressGCAnalysis nogc; + JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp(); if (!op) return nullptr; JSObject* obj = op(key); if (!obj) return nullptr; MOZ_ASSERT(obj->runtimeFromMainThread() == zone()->runtimeFromMainThread()); return obj; } -template <class K, class V, class HP> +template <class K, class V> inline JSObject* -WeakMap<K, V, HP>::getDelegate(JSScript* script) const +WeakMap<K, V>::getDelegate(JSScript* script) const { return nullptr; } -template <class K, class V, class HP> +template <class K, class V> inline JSObject* -WeakMap<K, V, HP>::getDelegate(LazyScript* script) const +WeakMap<K, V>::getDelegate(LazyScript* script) const { return nullptr; } -template <class K, class V, class HP> +template <class K, class V> inline bool -WeakMap<K, V, HP>::keyNeedsMark(JSObject* key) const +WeakMap<K, V>::keyNeedsMark(JSObject* key) const { JSObject* delegate = getDelegate(key); /* * Check if the delegate is marked with any color to properly handle * gray marking when the key's delegate is black and the map is gray. */ return delegate && gc::IsMarkedUnbarriered(zone()->runtimeFromMainThread(), &delegate); } -template <class K, class V, class HP> +template <class K, class V> inline bool -WeakMap<K, V, HP>::keyNeedsMark(JSScript* script) const +WeakMap<K, V>::keyNeedsMark(JSScript* script) const { return false; } -template <class K, class V, class HP> +template <class K, class V> inline bool -WeakMap<K, V, HP>::keyNeedsMark(LazyScript* script) const +WeakMap<K, V>::keyNeedsMark(LazyScript* script) const { return false; } -template <class K, class V, class HP> +template <class K, class V> void -WeakMap<K, V, HP>::sweep() +WeakMap<K, V>::sweep() { /* Remove all entries whose keys remain unmarked. */ for (Enum e(*this); !e.empty(); e.popFront()) { if (gc::IsAboutToBeFinalized(&e.front().mutableKey())) e.removeFront(); } #if DEBUG // Once we've swept, all remaining edges should stay within the known-live // part of the graph. assertEntriesNotAboutToBeFinalized(); #endif } /* memberOf can be nullptr, which means that the map is not part of a JSObject. */ -template <class K, class V, class HP> +template <class K, class V> void -WeakMap<K, V, HP>::traceMappings(WeakMapTracer* tracer) +WeakMap<K, V>::traceMappings(WeakMapTracer* tracer) { for (Range r = Base::all(); !r.empty(); r.popFront()) { gc::Cell* key = gc::ToMarkable(r.front().key()); gc::Cell* value = gc::ToMarkable(r.front().value()); if (key && value) { tracer->trace(memberOf, JS::GCCellPtr(r.front().key().get()), JS::GCCellPtr(r.front().value().get())); } } } #if DEBUG -template <class K, class V, class HP> +template <class K, class V> void -WeakMap<K, V, HP>::assertEntriesNotAboutToBeFinalized() +WeakMap<K, V>::assertEntriesNotAboutToBeFinalized() { for (Range r = Base::all(); !r.empty(); r.popFront()) { K k(r.front().key()); MOZ_ASSERT(!gc::IsAboutToBeFinalized(&k)); MOZ_ASSERT(!gc::IsAboutToBeFinalized(&r.front().value())); MOZ_ASSERT(k == r.front().key()); } }
--- a/js/src/gc/WeakMap.h +++ b/js/src/gc/WeakMap.h @@ -106,23 +106,22 @@ class WeakMapBase : public mozilla::Link // Zone containing this weak map. JS::Zone* zone_; // Whether this object has been traced during garbage collection. bool marked; }; -template <class Key, class Value, - class HashPolicy = DefaultHasher<Key> > -class WeakMap : public HashMap<Key, Value, HashPolicy, ZoneAllocPolicy>, +template <class Key, class Value> +class WeakMap : public HashMap<Key, Value, MovableCellHasher<Key>, ZoneAllocPolicy>, public WeakMapBase { public: - typedef HashMap<Key, Value, HashPolicy, ZoneAllocPolicy> Base; + typedef HashMap<Key, Value, MovableCellHasher<Key>, ZoneAllocPolicy> Base; typedef typename Base::Enum Enum; typedef typename Base::Lookup Lookup; typedef typename Base::Entry Entry; typedef typename Base::Range Range; typedef typename Base::Ptr Ptr; typedef typename Base::AddPtr AddPtr; explicit WeakMap(JSContext* cx, JSObject* memOf = nullptr); @@ -186,23 +185,21 @@ class WeakMap : public HashMap<Key, Valu protected: #if DEBUG void assertEntriesNotAboutToBeFinalized(); #endif }; -class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>, - MovableCellHasher<HeapPtr<JSObject*>>> +class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>> { public: ObjectValueMap(JSContext* cx, JSObject* obj) - : WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>, - MovableCellHasher<HeapPtr<JSObject*>>>(cx, obj) + : WeakMap(cx, obj) {} bool findZoneEdges() override; }; // Generic weak map for mapping objects to other objects. class ObjectWeakMap
--- a/js/src/gc/WeakMapPtr.cpp +++ b/js/src/gc/WeakMapPtr.cpp @@ -36,19 +36,18 @@ struct DataType<JS::Value> using BarrieredType = HeapPtr<Value>; static JS::Value NullValue() { return JS::UndefinedValue(); } }; template <typename K, typename V> struct Utils { typedef typename DataType<K>::BarrieredType KeyType; - typedef typename DataType<K>::HasherType HasherType; typedef typename DataType<V>::BarrieredType ValueType; - typedef WeakMap<KeyType, ValueType, HasherType> Type; + typedef WeakMap<KeyType, ValueType> Type; typedef Type* PtrType; static PtrType cast(void* ptr) { return static_cast<PtrType>(ptr); } }; } /* WeakMapDetails */ template <typename K, typename V> void
--- a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js +++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js @@ -1,24 +1,24 @@ -// If debugger.onEnterFrame returns {return:val}, the frame returns immediately. +// If debugger.onEnterFrame returns null, the debuggee is terminated immediately. load(libdir + "asserts.js"); var g = newGlobal(); g.set = false; var dbg = Debugger(g); var savedFrame; dbg.onDebuggerStatement = function (frame) { var innerSavedFrame; dbg.onEnterFrame = function (frame) { innerSavedFrame = frame; return null; }; - // Using frame.eval lets us catch termination. + // Using frame.eval lets us catch termination. assertEq(frame.eval("set = true;"), null); assertEq(innerSavedFrame.live, false); savedFrame = frame; return { return: "pass" }; }; savedFrame = undefined; assertEq(g.eval("debugger;"), "pass");
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-live-06.js @@ -0,0 +1,26 @@ +// frame.live is false for generator frames after they return. + +let g = newGlobal(); +g.eval("function* f() { debugger; }"); + +let dbg = Debugger(g); +let savedFrame; + +dbg.onDebuggerStatement = frame => { + savedFrame = frame; + assertEq(frame.callee.name, "f"); + assertEq(frame.live, true); + frame.onPop = function() { + assertEq(frame.live, true); + }; +}; +g.f().next(); + +assertEq(savedFrame.live, false); +try { + savedFrame.older; + throw new Error("expected exception, none thrown"); +} catch (exc) { + assertEq(exc.message, "Debugger.Frame is not live"); +} +
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-live-07.js @@ -0,0 +1,53 @@ +// frame.live is false for generator frames popped due to exception or termination. + +load(libdir + "/asserts.js"); + +function test(when, what) { + let g = newGlobal(); + g.eval("function* f(x) { yield x; }"); + + let dbg = new Debugger; + let gw = dbg.addDebuggee(g); + let fw = gw.getOwnPropertyDescriptor("f").value; + + let t = 0; + let poppedFrame; + + function tick(frame) { + if (frame.callee == fw) { + if (t == when) { + poppedFrame = frame; + dbg.onEnterFrame = undefined; + frame.onPop = undefined; + return what; + } + t++; + } + return undefined; + } + + dbg.onDebuggerStatement = frame => { + dbg.onEnterFrame = frame => { + frame.onPop = function() { + return tick(this); + }; + return tick(frame); + }; + let result = frame.eval("for (let _ of f(0)) {}"); + assertDeepEq(result, what); + }; + g.eval("debugger;"); + + assertEq(t, when); + assertEq(poppedFrame.live, false); + assertErrorMessage(() => poppedFrame.older, + Error, + "Debugger.Frame is not live"); +} + +for (let when = 0; when < 6; when++) { + for (let what of [null, {throw: "fit"}]) { + test(when, what); + } +} +
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-04.js @@ -0,0 +1,26 @@ +// Terminating a generator from the onPop callback for its initial yield +// leaves the Frame in a sane but inactive state. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval("function* f(x) { yield x; }"); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +let genFrame = null; +dbg.onDebuggerStatement = frame => { + dbg.onEnterFrame = frame => { + if (frame.callee == gw.getOwnPropertyDescriptor("f").value) { + genFrame = frame; + frame.onPop = completion => null; + } + }; + assertEq(frame.eval("f(0);"), null); +}; + +g.eval("debugger;"); + +assertEq(genFrame instanceof Debugger.Frame, true); +assertEq(genFrame.live, false); +assertThrowsInstanceOf(() => genFrame.callee, Error);
deleted file mode 100644 --- a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js +++ /dev/null @@ -1,20 +0,0 @@ -// Returning {throw:} from an onPop handler when yielding works and -// closes the generator-iterator. - -load(libdir + "iteration.js"); - -var g = newGlobal(); -var dbg = new Debugger; -var gw = dbg.addDebuggee(g); -dbg.onDebuggerStatement = function handleDebugger(frame) { - frame.onPop = function (c) { - return {throw: "fit"}; - }; -}; -g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); -g.eval("var it = g();"); -var rv = gw.executeInGlobal("it.next();"); -assertEq(rv.throw, "fit"); - -dbg.enabled = false; -assertIteratorDone(g.it);
deleted file mode 100644 --- a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js +++ /dev/null @@ -1,21 +0,0 @@ -// |jit-test| error: fit - -// Throwing an exception from an onPop handler when yielding terminates the debuggee -// but does not close the generator-iterator. - -load(libdir + 'iteration.js') - -var g = newGlobal(); -var dbg = new Debugger; -var gw = dbg.addDebuggee(g); -dbg.onDebuggerStatement = function handleDebugger(frame) { - frame.onPop = function (c) { - throw "fit"; - }; -}; -g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); -g.eval("var it = g();"); -assertEq(gw.executeInGlobal("it.next();"), null); - -dbg.enabled = false; -assertIteratorNext(g.it, 1);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-01.js @@ -0,0 +1,31 @@ +// Stepping into the `.next()` method of a generator works as expected. + +let g = newGlobal(); +g.eval(`\ +function* nums() { // line 1 + yield 1; // 2 + yield 2; // 3 +} // 4 +function f() { // 5 + let gen = nums(); // 6 + gen.next(); // 7 + gen.next(); // 8 + gen.next(); // 9 +} // 10 +`); + +let log = []; +let previousLine = -1; +let dbg = new Debugger(g); +dbg.onEnterFrame = frame => { + frame.onStep = () => { + let line = frame.script.getOffsetLocation(frame.offset).lineNumber; + if (previousLine != line) { // We stepped to a new line. + log.push(line); + previousLine = line; + } + }; +}; + +g.f(); +assertEq(log.join(" "), "5 6 1 6 7 1 2 7 8 2 3 8 9 3 9 10");
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-02.js @@ -0,0 +1,44 @@ +// Stepping into the `.throw()` method of a generator with no relevant catch block. +// +// The debugger fires onEnterFrame and then frame.onPop for the generator frame when +// `gen.throw()` is called. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval(`\ +function* z() { // line 1 + yield 1; // 2 + yield 2; // 3 +} // 4 +function f() { // 5 + let gen = z(); // 6 + gen.next(); // 7 + gen.throw("fit"); // 8 +} // 9 +`); + +let log = ""; +let previousLine = -1; +let dbg = new Debugger(g); +dbg.onEnterFrame = frame => { + log += frame.callee.name + "{"; + frame.onStep = () => { + let line = frame.script.getOffsetLocation(frame.offset).lineNumber; + if (previousLine != line) { // We stepped to a new line. + log += line; + previousLine = line; + } + }; + frame.onPop = completion => { + if ("throw" in completion) + log += "!"; + log += "}"; + } +}; + +assertThrowsValue(() => g.f(), "fit"); +// z{1} is the initial generator setup. +// z{12} is the first .next() call, running to `yield 1` on line 2 +// The final `z{!}` is for the .throw() call. +assertEq(log, "f{56z{1}67z{12}78z{!}!}");
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-03.js @@ -0,0 +1,45 @@ +// Stepping into the `.throw()` method of a generator with a relevant catch block. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval(`\ +function* z() { // line 1 + try { // 2 + yield 1; // 3 + } catch (exc) { // 4 + yield 2; // 5 + } // 6 +} // 7 +function f() { // 8 + let gen = z(); // 9 + gen.next(); // 10 + gen.throw("fit"); // 11 +} // 12 +`); + +let log = []; +let previousLine = -1; +let dbg = new Debugger(g); +dbg.onEnterFrame = frame => { + log.push(frame.callee.name + " in"); + frame.onStep = () => { + let line = frame.script.getOffsetLocation(frame.offset).lineNumber; + if (previousLine != line) { // We stepped to a new line. + log.push(line); + previousLine = line; + } + }; + frame.onPop = completion => { + log.push(frame.callee.name + " out"); + }; +}; + +g.f(); +assertEq( + log.join(", "), + "f in, 8, 9, z in, 1, z out, " + + "9, 10, z in, 1, 2, 3, z out, " + + "10, 11, z in, 2, 4, 5, z out, " + // not sure why we hit line 2 here, source notes bug maybe + "11, 12, f out" +);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-defaults.js @@ -0,0 +1,33 @@ +// onStep works during the evaluation of default parameter values in generators. +// +// (They're evaluated at a weird time in the generator life cycle, before the +// generator object is created.) + +let g = newGlobal(); +g.eval(`\ + function f1() {} // line 1 + function f2() {} // 2 + function f3() {} // 3 + // 4 + function* gen( // 5 + name, // 6 + schema = f1(), // 7 + timeToLive = f2(), // 8 + lucidity = f3() // 9 + ) { // 10 + } // 11 +`); + +let dbg = Debugger(g); +let log = []; +dbg.onEnterFrame = frame => { + frame.onStep = () => { + let line = frame.script.getOffsetLocation(frame.offset).lineNumber; + if (log.length == 0 || line != log[log.length - 1]) { + log.push(line); + } + }; +}; + +g.gen(0); +assertEq(log.toSource(), [5, 7, 1, 8, 2, 9, 3, 10].toSource());
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-oom-01.js @@ -0,0 +1,42 @@ +// Test for OOM hitting a breakpoint in a generator. +// +// (The purpose is to test OOM-handling in the code that creates the +// Debugger.Frame object and associates it with the generator object.) + +if (!('oomTest' in this)) + quit(); + +let g = newGlobal(); +g.eval(`\ + function* gen(x) { // line 1 + x++; // 2 + yield x; // 3 + } // 4 +`); + +let dbg = new Debugger; + +// On OOM in the debugger, propagate it to the debuggee. +dbg.uncaughtExceptionHook = exc => exc === "out of memory" ? {throw: exc} : null; + +let gw = dbg.addDebuggee(g); +let script = gw.makeDebuggeeValue(g.gen).script; +let hits = 0; +let handler = { + hit(frame) { + hits++; + print("x=", frame.environment.getVariable("x")); + } +}; +for (let offset of script.getLineOffsets(2)) + script.setBreakpoint(offset, handler); + +let result; +oomTest(() => { + hits = 0; + result = g.gen(1).next(); +}, false); +assertEq(hits, 1); +assertEq(result.done, false); +assertEq(result.value, 2); +
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1479429.js @@ -0,0 +1,15 @@ +// Bug 1479429 - Methods throw on out-of-range bytecode offsets. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function(frame) { + assertThrowsInstanceOf( + () => frame.script.getPredecessorOffsets(0x400000), + TypeError); + assertThrowsInstanceOf( + () => frame.script.getSuccessorOffsets(-1), + TypeError); +} +g.eval("debugger;");
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onDebuggerStatement-async-generator-resumption-01.js @@ -0,0 +1,60 @@ +// A Debugger can {return:} from onDebuggerStatement in an async generator. +// A resolved promise for a {value: _, done: true} object is returned. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval(` + async function* f(x) { + debugger; // when==0 to force return here + await x; + yield 1; + debugger; // when==1 to force return here + } +`); + +let exc = null; +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); +function test(when) { + let hits = 0; + let outcome = "FAIL"; + dbg.onDebuggerStatement = frame => { + if (hits++ == when) + return {return: "ponies"}; + }; + + let iter = g.f(0); + + // At the initial suspend. + assertEq(hits, 0); + iter.next().then(result => { + // At the yield point, unless we already force-returned from the first + // debugger statement. + assertEq(hits, 1); + if (when == 0) + return result; + assertEq(result.value, 1); + assertEq(result.done, false); + return iter.next(); + }).then(result => { + // After forced return. + assertEq(hits, when + 1); + assertEq(result.value, "ponies"); + assertEq(result.done, true); + outcome = "pass"; + }).catch(e => { + // An assertion failed. + exc = e; + }); + + assertEq(hits, 1); + drainJobQueue(); + if (exc !== null) + throw exc; + assertEq(outcome, "pass"); +} + +for (let i = 0; i < 2; i++) { + test(i); +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-01.js @@ -0,0 +1,34 @@ +// A Debugger can {return:} from onDebuggerStatement in an async function. +// The async function's promise is resolved with the returned value. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval(` + async function f(x) { + debugger; // when==0 to force return here + await x; + debugger; // when==1 to force return here + } +`); + +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); +function test(when, what, expected) { + let hits = 0; + let result = "FAIL"; + dbg.onDebuggerStatement = frame => { + if (hits++ == when) + return {return: gw.makeDebuggeeValue(what)}; + }; + g.f(0).then(x => { result = x; }); + assertEq(hits, 1); + drainJobQueue(); + assertEq(hits, when + 1); + assertEq(result, expected); +} + +for (let i = 0; i < 2; i++) { + test(i, "ok", "ok"); + test(i, g.Promise.resolve(37), 37); +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js @@ -0,0 +1,33 @@ +// A Debugger can {return:} from the first onEnterFrame for an async function. +// (The exact behavior is undocumented; we're testing that it doesn't crash.) + +let g = newGlobal(); +g.hit2 = false; +g.eval(`async function f(x) { await x; return "ponies"; }`); + +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); +let hits = 0; +let resumption = undefined; +dbg.onEnterFrame = frame => { + if (frame.type == "call" && frame.callee.name === "f") { + frame.onPop = completion => { + assertEq(completion.return, resumption.return); + hits++; + }; + + // Don't tell anyone, but if we force-return a generator object here, + // the robots accept it as one of their own and plug it right into the + // async function machinery. This may be handy against Skynet someday. + resumption = frame.eval(`(function* f2() { hit2 = true; throw "fit"; })()`); + assertEq(resumption.return.class, "Generator"); + return resumption; + } +}; + +let p = g.f(0); +assertEq(hits, 1); +let pw = gw.makeDebuggeeValue(p); +assertEq(pw.isPromise, true); +assertEq(pw.promiseState, "rejected"); +assertEq(pw.promiseReason, "fit");
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js @@ -0,0 +1,36 @@ +// A Debugger can {throw:} from onEnterFrame in an async function. +// The resulting promise (if any) is rejected with the thrown error value. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval(` + async function f() { await 1; } + var err = new TypeError("object too hairy"); +`); + +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); +let errw = gw.makeDebuggeeValue(g.err); + +// Repeat the test for each onEnterFrame event. +// It fires up to three times: +// - when the async function g.f is called; +// - when we enter it to run to `await 1`; +// - when we resume after the await to run to the end. +for (let when = 0; when < 3; when++) { + let hits = 0; + dbg.onEnterFrame = frame => { + return hits++ < when ? undefined : {throw: errw}; + }; + + let result = undefined; + g.f() + .then(value => { result = {returned: value}; }) + .catch(err => { result = {threw: err}; }); + + drainJobQueue(); + + assertEq(hits, when + 1); + assertDeepEq(result, {threw: g.err}); +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js @@ -0,0 +1,30 @@ +// A Debugger can {return:} from onEnterFrame at any resume point in an async function. +// The async function's promise is resolved with the returned value. + +let g = newGlobal(); +g.eval(`async function f(x) { await x; }`); + +let dbg = new Debugger(g); +function test(when) { + let hits = 0; + dbg.onEnterFrame = frame => { + if (frame.type == "call" && frame.callee.name === "f") { + if (hits++ == when) { + return {return: "exit"}; + } + } + }; + + let result = undefined; + let finished = false; + g.f("hello").then(value => { result = value; finished = true; }); + drainJobQueue(); + assertEq(finished, true); + assertEq(hits, when + 1); + assertEq(result, "exit"); +} + +// onEnterFrame with hits==0 is not a resume point; {return:} behaves differently there +// (see onEnterFrame-async-resumption-02.js). +test(1); // force return from first resume point, immediately after the initial suspend +test(2); // force return from second resume point, immediately after the await instruction
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-01.js @@ -0,0 +1,81 @@ +// Frame properties and methods work in generator-resuming onEnterFrame events. +// Also tests onPop events, for good measure. + +let g = newGlobal(); +g.eval(`\ + function* gen(lo, hi) { + var a = 1/2; + yield a; + yield a * a; + } +`); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +let hits = 0; +let savedScript = null; +let savedEnv = null; +let savedOffsets = new Set; + +function check(frame) { + assertEq(frame.type, "call"); + assertEq(frame.constructing, false); + assertEq(frame.callee, gw.makeDebuggeeValue(g.gen)); + + // `arguments` elements don't work in resumed generator frames, + // because generators don't keep the arguments around. + // The first onEnterFrame and onPop events can see them. + assertEq(frame.arguments.length, hits < 2 ? args.length : 0); + for (var i = 0; i < frame.arguments.length; i++) { + assertEq(frame.arguments.hasOwnProperty(i), true); + + if (hits < 2) + assertEq(frame.arguments[i], gw.makeDebuggeeValue(args[i]), `arguments[${i}]`); + else + assertEq(frame.arguments[i], undefined); + } + + if (savedEnv === null) { + savedEnv = frame.environment; + assertEq(savedScript, null); + savedScript = frame.script; + } else { + assertEq(frame.environment, savedEnv); + assertEq(frame.script, savedScript); + } + let a_expected = hits < 3 ? undefined : 1/2; + assertEq(savedEnv.getVariable("a"), a_expected); + + assertEq(frame.generator, true); + assertEq(frame.live, true); + + let pc = frame.offset; + assertEq(savedOffsets.has(pc), false); + savedOffsets.add(pc); + + assertEq(frame.older, null); + assertEq(frame.this, gw); + assertEq(typeof frame.implementation, "string"); + + // And the moment of truth: + assertEq(frame.eval("2 + 2").return, 4); + assertEq(frame.eval("a").return, a_expected); + assertEq(frame.eval("if (a !== undefined) { assertEq(a < (lo + hi) / 2, true); } 7;").return, 7); +} + +dbg.onEnterFrame = frame => { + if (frame.type === "eval") + return; + check(frame); + hits++; + frame.onPop = completion => { + check(frame); + hits++; + }; +}; + +// g.gen ignores the arguments passed to it, but we use them to test +// frame.arguments. +let args = [0, 10, g, dbg]; +for (let v of g.gen(...args)) {} +assertEq(hits, 8);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-02.js @@ -0,0 +1,27 @@ +// onEnterFrame fires after the [[GeneratorState]] is set to "executing". +// +// This test checks that Debugger doesn't accidentally make it possible to +// reenter a generator frame that's already on the stack. (Also tests a fun +// corner case in baseline debug-mode OSR.) + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval('function* f() { yield 1; yield 2; }'); +let dbg = Debugger(g); +let genObj = null; +let hits = 0; +dbg.onEnterFrame = frame => { + // The first time onEnterFrame fires, there is no generator object, so + // there's nothing to test. The generator object doesn't exist until + // JSOP_GENERATOR is reached, right before the initial yield. + if (genObj !== null) { + dbg.removeDebuggee(g); // avoid the DebuggeeWouldRun exception + assertThrowsInstanceOf(() => genObj.next(), g.TypeError); + dbg.addDebuggee(g); + hits++; + } +}; +genObj = g.f(); +for (let x of genObj) {} +assertEq(hits, 3);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-03.js @@ -0,0 +1,25 @@ +// If onEnterFrame terminates a generator, the Frame is left in a sane but inactive state. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval("function* f(x) { yield x; }"); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +let genFrame = null; +dbg.onDebuggerStatement = frame => { + dbg.onEnterFrame = frame => { + if (frame.callee == gw.getOwnPropertyDescriptor("f").value) { + genFrame = frame; + return null; + } + }; + assertEq(frame.eval("f(0);"), null); +}; + +g.eval("debugger;"); + +assertEq(genFrame instanceof Debugger.Frame, true); +assertEq(genFrame.live, false); +assertThrowsInstanceOf(() => genFrame.callee, Error);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-01.js @@ -0,0 +1,36 @@ +// A debugger can {throw:} from onEnterFrame at any resume point in a generator. +// It closes the generator. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval(` + function* f() { yield 1; } + var exn = new TypeError("object too hairy"); +`); + +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +// Repeat the test for each onEnterFrame event. +// It fires up to three times: +// - when the generator g.f is called; +// - when we enter it to run to `yield 1`; +// - when we resume after the yield to run to the end. +for (let i = 0; i < 3; i++) { + let hits = 0; + dbg.onEnterFrame = frame => { + return hits++ < i ? undefined : {throw: gw.makeDebuggeeValue(g.exn)}; + }; + let genObj; + assertThrowsValue( + () => { + genObj = g.f(); + for (let x of genObj) {} + }, + g.exn + ); + assertEq(hits, i + 1); + if (hits > 1) + assertEq(genObj.next().done, true); +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-02.js @@ -0,0 +1,39 @@ +// A Debugger can {return:} from onEnterFrame at any resume point in a generator. +// Force-returning closes the generator. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.values = [1, 2, 3]; +g.eval(`function* f() { yield* values; }`); + +let dbg = Debugger(g); + +// onEnterFrame will fire up to 5 times. +// - once for the initial call to g.f(); +// - four times at resume points: +// - initial resume at the top of the generator body +// - resume after yielding 1 +// - resume after yielding 2 +// - resume after yielding 3 (this resumption will run to the end). +// This test ignores the initial call and focuses on resume points. +for (let i = 1; i < 5; i++) { + let hits = 0; + dbg.onEnterFrame = frame => { + return hits++ < i ? undefined : {return: "we're done here"}; + }; + + let genObj = g.f(); + let actual = []; + while (true) { + let r = genObj.next(); + if (r.done) { + assertDeepEq(r, {value: "we're done here", done: true}); + break; + } + actual.push(r.value); + } + assertEq(hits, i + 1); + assertDeepEq(actual, g.values.slice(0, i - 1)); + assertDeepEq(genObj.next(), {value: undefined, done: true}); +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-03.js @@ -0,0 +1,35 @@ +// Returning {throw:} from onEnterFrame when resuming inside a try block in a +// generator causes control to jump to the catch block. + +let g = newGlobal(); +g.eval(` + function* gen() { + try { + yield 0; + return "fail"; + } catch (exc) { + assertEq(exc, "fit"); + return "ok"; + } + } +`) + +let dbg = new Debugger(g); +let hits = 0; +dbg.onEnterFrame = frame => { + assertEq(frame.callee.name, "gen"); + if (++hits == 3) { + // First hit is when calling gen(); + // second hit is resuming at the implicit initial yield; + // third hit is resuming inside the try block. + return {throw: "fit"}; + } +}; + +let it = g.gen(); +let result = it.next(); +assertEq(result.done, false); +assertEq(result.value, 0); +result = it.next(); +assertEq(result.done, true); +assertEq(result.value, "ok");
--- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -4642,31 +4642,44 @@ BaselineCompiler::emit_JSOP_YIELD() } bool BaselineCompiler::emit_JSOP_AWAIT() { return emit_JSOP_YIELD(); } -typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*); +typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*, jsbytecode*, bool*); static const VMFunction DebugAfterYieldInfo = FunctionInfo<DebugAfterYieldFn>(jit::DebugAfterYield, "DebugAfterYield"); bool BaselineCompiler::emit_JSOP_DEBUGAFTERYIELD() { if (!compileDebugInstrumentation_) return true; frame.assertSyncedStack(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); prepareVMCall(); + pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); - return callVM(DebugAfterYieldInfo); + if (!callVM(DebugAfterYieldInfo)) + return false; + + icEntries_.back().setFakeKind(ICEntry::Kind_DebugAfterYield); + + Label done; + masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done); + { + masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); + masm.jump(&return_); + } + masm.bind(&done); + return true; } typedef bool (*FinalSuspendFn)(JSContext*, HandleObject, jsbytecode*); static const VMFunction FinalSuspendInfo = FunctionInfo<FinalSuspendFn>(jit::FinalSuspend, "FinalSuspend"); bool BaselineCompiler::emit_JSOP_FINALYIELDRVAL() @@ -4688,17 +4701,17 @@ BaselineCompiler::emit_JSOP_FINALYIELDRV typedef bool (*InterpretResumeFn)(JSContext*, HandleObject, HandleValue, HandlePropertyName, MutableHandleValue); static const VMFunction InterpretResumeInfo = FunctionInfo<InterpretResumeFn>(jit::InterpretResume, "InterpretResume"); typedef bool (*GeneratorThrowFn)(JSContext*, BaselineFrame*, Handle<GeneratorObject*>, HandleValue, uint32_t); -static const VMFunction GeneratorThrowInfo = +static const VMFunction GeneratorThrowOrReturnInfo = FunctionInfo<GeneratorThrowFn>(jit::GeneratorThrowOrReturn, "GeneratorThrowOrReturn", TailCall); bool BaselineCompiler::emit_JSOP_RESUME() { GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(pc); frame.syncStack(0); @@ -4873,17 +4886,17 @@ BaselineCompiler::emit_JSOP_RESUME() masm.loadBaselineFramePtr(BaselineFrameReg, scratch2); prepareVMCall(); pushArg(Imm32(resumeKind)); pushArg(retVal); pushArg(genObj); pushArg(scratch2); - TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(GeneratorThrowInfo); + TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(GeneratorThrowOrReturnInfo); // Create the frame descriptor. masm.subStackPtrFrom(scratch1); masm.makeFrameDescriptor(scratch1, JitFrame_BaselineJS, ExitFrameLayout::Size()); // Push the frame descriptor and a dummy return address (it doesn't // matter what we push here, frame iterators will use the frame pc // set in jit::GeneratorThrowOrReturn).
--- a/js/src/jit/BaselineDebugModeOSR.cpp +++ b/js/src/jit/BaselineDebugModeOSR.cpp @@ -98,16 +98,17 @@ struct DebugModeOSREntry bool needsRecompileInfo() const { return frameKind == ICEntry::Kind_CallVM || frameKind == ICEntry::Kind_WarmupCounter || frameKind == ICEntry::Kind_StackCheck || frameKind == ICEntry::Kind_EarlyStackCheck || frameKind == ICEntry::Kind_DebugTrap || frameKind == ICEntry::Kind_DebugPrologue || + frameKind == ICEntry::Kind_DebugAfterYield || frameKind == ICEntry::Kind_DebugEpilogue; } bool recompiled() const { return oldBaselineScript != script->baselineScript(); } BaselineDebugModeOSRInfo* takeRecompInfo() { @@ -302,16 +303,18 @@ ICEntryKindToString(ICEntry::Kind kind) case ICEntry::Kind_StackCheck: return "stack check"; case ICEntry::Kind_EarlyStackCheck: return "early stack check"; case ICEntry::Kind_DebugTrap: return "debug trap"; case ICEntry::Kind_DebugPrologue: return "debug prologue"; + case ICEntry::Kind_DebugAfterYield: + return "debug after yield"; case ICEntry::Kind_DebugEpilogue: return "debug epilogue"; default: MOZ_CRASH("bad ICEntry kind"); } } #endif // JS_JITSPEW @@ -362,16 +365,17 @@ PatchBaselineFramesForDebugMode(JSContex // H. From inside HandleExceptionBaseline. // I. From inside the interrupt handler via the prologue stack check. // J. From the warmup counter in the prologue. // // On to Off: // - All the ways above. // C. From the debug trap handler. // D. From the debug prologue. + // K. From a JSOP_DEBUGAFTERYIELD instruction. // E. From the debug epilogue. // // Cycles (On to Off to On)+ or (Off to On to Off)+: // F. Undo cases B, C, D, E, I or J above on previously patched yet unpopped // frames. // // In general, we patch the return address from the VM call to return to a // "continuation fixer" to fix up machine state (registers and stack @@ -465,16 +469,17 @@ PatchBaselineFramesForDebugMode(JSContex MOZ_ASSERT(info->pc == pc); MOZ_ASSERT(info->frameKind == kind); MOZ_ASSERT(kind == ICEntry::Kind_CallVM || kind == ICEntry::Kind_WarmupCounter || kind == ICEntry::Kind_StackCheck || kind == ICEntry::Kind_EarlyStackCheck || kind == ICEntry::Kind_DebugTrap || kind == ICEntry::Kind_DebugPrologue || + kind == ICEntry::Kind_DebugAfterYield || kind == ICEntry::Kind_DebugEpilogue); // We will have allocated a new recompile info, so delete the // existing one. frame.baselineFrame()->deleteDebugModeOSRInfo(); } // The RecompileInfo must already be allocated so that this @@ -541,16 +546,27 @@ PatchBaselineFramesForDebugMode(JSContex // Case D above. // // We patch a jump directly to the right place in the prologue // after popping the frame reg and checking for forced return. recompInfo->resumeAddr = bl->postDebugPrologueAddr(); popFrameReg = true; break; + case ICEntry::Kind_DebugAfterYield: + // Case K above. + // + // Resume at the next instruction. + MOZ_ASSERT(*pc == JSOP_DEBUGAFTERYIELD); + recompInfo->resumeAddr = bl->nativeCodeForPC(script, + pc + JSOP_DEBUGAFTERYIELD_LENGTH, + &recompInfo->slotInfo); + popFrameReg = true; + break; + default: // Case E above. // // We patch a jump directly to the epilogue after popping the // frame reg and checking for forced return. MOZ_ASSERT(kind == ICEntry::Kind_DebugEpilogue); recompInfo->resumeAddr = bl->epilogueEntryAddr(); popFrameReg = true; @@ -940,19 +956,19 @@ HasForcedReturn(BaselineDebugModeOSRInfo { ICEntry::Kind kind = info->frameKind; // The debug epilogue always checks its resumption value, so we don't need // to check rv. if (kind == ICEntry::Kind_DebugEpilogue) return true; - // |rv| is the value in ReturnReg. If true, in the case of the prologue, - // it means a forced return. - if (kind == ICEntry::Kind_DebugPrologue) + // |rv| is the value in ReturnReg. If true, in the case of the prologue or + // after yield, it means a forced return. + if (kind == ICEntry::Kind_DebugPrologue || kind == ICEntry::Kind_DebugAfterYield) return rv; // N.B. The debug trap handler handles its own forced return, so no // need to deal with it here. return false; } static inline bool
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -8696,17 +8696,18 @@ JitRealm::generateStringConcatStub(JSCon } masm.bind(¬Inline); // Keep AND'ed flags in temp1. // Ensure result length <= JSString::MAX_LENGTH. masm.branch32(Assembler::Above, temp2, Imm32(JSString::MAX_LENGTH), &failure); - // Allocate a new rope, guaranteed to be in the nursery. + // Allocate a new rope, guaranteed to be in the nursery if + // stringsCanBeInNursery. (As a result, no post barriers are needed below.) masm.newGCString(output, temp3, &failure, stringsCanBeInNursery); // Store rope length and flags. temp1 still holds the result of AND'ing the // lhs and rhs flags, so we just have to clear the other flags and set // NON_ATOM_BIT to get our rope flags (Latin1 if both lhs and rhs are // Latin1). static_assert(JSString::INIT_ROPE_FLAGS == JSString::NON_ATOM_BIT, "Rope type flags must be NON_ATOM_BIT only");
--- a/js/src/jit/SharedIC.h +++ b/js/src/jit/SharedIC.h @@ -251,18 +251,19 @@ class ICEntry // As above, but for the early check. See emitStackCheck. Kind_EarlyStackCheck, // A fake IC entry for returning from DebugTrapHandler. Kind_DebugTrap, // A fake IC entry for returning from a callVM to - // Debug{Prologue,Epilogue}. + // Debug{Prologue,AfterYield,Epilogue}. Kind_DebugPrologue, + Kind_DebugAfterYield, Kind_DebugEpilogue, Kind_Invalid }; private: // What this IC is for. Kind kind_ : 4;
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -952,37 +952,54 @@ InterpretResume(JSContext* cx, HandleObj args[1].set(val); args[2].setString(kind); return CallSelfHostedFunction(cx, cx->names().InterpretGeneratorResume, UndefinedHandleValue, args, rval); } bool -DebugAfterYield(JSContext* cx, BaselineFrame* frame) +DebugAfterYield(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool* mustReturn) { + *mustReturn = false; + // The BaselineFrame has just been constructed by JSOP_RESUME in the // caller. We need to set its debuggee flag as necessary. - if (frame->script()->isDebuggee()) + // + // If a breakpoint is set on JSOP_DEBUGAFTERYIELD, or stepping is enabled, + // we may already have done this work. Don't fire onEnterFrame again. + if (frame->script()->isDebuggee() && !frame->isDebuggee()) { frame->setIsDebuggee(); + return DebugPrologue(cx, frame, pc, mustReturn); + } return true; } bool GeneratorThrowOrReturn(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObject*> genObj, HandleValue arg, uint32_t resumeKind) { // Set the frame's pc to the current resume pc, so that frame iterators // work. This function always returns false, so we're guaranteed to enter // the exception handler where we will clear the pc. JSScript* script = frame->script(); uint32_t offset = script->yieldAndAwaitOffsets()[genObj->yieldAndAwaitIndex()]; - frame->setOverridePc(script->offsetToPC(offset)); + jsbytecode* pc = script->offsetToPC(offset); + frame->setOverridePc(pc); + + // In the interpreter, GeneratorObject::resume marks the generator as running, + // so we do the same. + genObj->setRunning(); - MOZ_ALWAYS_TRUE(DebugAfterYield(cx, frame)); + bool mustReturn = false; + if (!DebugAfterYield(cx, frame, pc, &mustReturn)) + return false; + if (mustReturn) + resumeKind = GeneratorObject::RETURN; + MOZ_ALWAYS_FALSE(js::GeneratorThrowOrReturn(cx, frame, genObj, arg, resumeKind)); return false; } bool CheckGlobalOrEvalDeclarationConflicts(JSContext* cx, BaselineFrame* frame) { RootedScript script(cx, frame->script()); @@ -1082,21 +1099,25 @@ bool HandleDebugTrap(JSContext* cx, BaselineFrame* frame, uint8_t* retAddr, bool* mustReturn) { *mustReturn = false; RootedScript script(cx, frame->script()); jsbytecode* pc = script->baselineScript()->icEntryFromReturnAddress(retAddr).pc(script); if (*pc == JSOP_DEBUGAFTERYIELD) { - // JSOP_DEBUGAFTERYIELD will set the frame's debuggee flag, but if we - // set a breakpoint there we have to do it now. + // JSOP_DEBUGAFTERYIELD will set the frame's debuggee flag and call the + // onEnterFrame handler, but if we set a breakpoint there we have to do + // it now. MOZ_ASSERT(!frame->isDebuggee()); - if (!DebugAfterYield(cx, frame)) + + if (!DebugAfterYield(cx, frame, pc, mustReturn)) return false; + if (*mustReturn) + return true; } MOZ_ASSERT(frame->isDebuggee()); MOZ_ASSERT(script->stepModeEnabled() || script->hasBreakpointsAt(pc)); RootedValue rval(cx); ResumeMode resumeMode = ResumeMode::Continue;
--- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -780,17 +780,17 @@ MOZ_MUST_USE bool NormalSuspend(JSContext* cx, HandleObject obj, BaselineFrame* frame, jsbytecode* pc, uint32_t stackDepth); MOZ_MUST_USE bool FinalSuspend(JSContext* cx, HandleObject obj, jsbytecode* pc); MOZ_MUST_USE bool InterpretResume(JSContext* cx, HandleObject obj, HandleValue val, HandlePropertyName kind, MutableHandleValue rval); MOZ_MUST_USE bool -DebugAfterYield(JSContext* cx, BaselineFrame* frame); +DebugAfterYield(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool* mustReturn); MOZ_MUST_USE bool GeneratorThrowOrReturn(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObject*> genObj, HandleValue arg, uint32_t resumeKind); MOZ_MUST_USE bool GlobalNameConflictsCheckFromIon(JSContext* cx, HandleScript script); MOZ_MUST_USE bool CheckGlobalOrEvalDeclarationConflicts(JSContext* cx, BaselineFrame* frame);
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4472,17 +4472,20 @@ BinParse(JSContext* cx, unsigned argc, V JS::Rooted<JSLinearString*> linearFormat(cx); linearFormat = stringFormat->ensureLinear(cx); if (StringEqualsAscii(linearFormat, "multipart")) { useMultipart = true; } else if (StringEqualsAscii(linearFormat, "simple")) { useMultipart = false; } else { JSAutoByteString printable; - JS_ReportErrorASCII(cx, "Unknown value for option `format`, expected 'multipart' or 'simple', got %s", ValueToPrintableUTF8(cx, optionFormat, &printable)); + JS_ReportErrorUTF8(cx, + "Unknown value for option `format`, expected 'multipart' or " + "'simple', got %s", + ValueToPrintableUTF8(cx, optionFormat, &printable)); return false; } } else { const char* typeName = InformalValueTypeName(optionFormat); JS_ReportErrorASCII(cx, "option `format` should be a string, got %s", typeName); return false; } }
new file mode 100644 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js @@ -0,0 +1,35 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1484943; +var summary = "Don't crash doing format/formatToParts on -NaN"; + +print(BUGNUMBER + ": " + summary); + +//----------------------------------------------------------------------------- + +assertEq("formatToParts" in Intl.NumberFormat(), true); + +var nf = new Intl.NumberFormat("en-US"); +var parts; + +var values = [NaN, -NaN]; + +for (var v of values) +{ + assertEq(nf.format(v), "NaN"); + + parts = nf.formatToParts(v); + assertEq(parts.length, 1); + assertEq(parts[0].type, "nan"); + assertEq(parts[0].value, "NaN"); +} + +//----------------------------------------------------------------------------- + +if (typeof reportCompare === "function") + reportCompare(0, 0, 'ok'); + +print("Tests complete");
--- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1812,16 +1812,25 @@ Debugger::fireEnterFrame(JSContext* cx, MOZ_ASSERT(hook->isCallable()); Maybe<AutoRealm> ar; ar.emplace(cx, object); RootedValue scriptFrame(cx); FrameIter iter(cx); + +#if DEBUG + // Assert that the hook won't be able to re-enter the generator. + if (iter.hasScript() && *iter.pc() == JSOP_DEBUGAFTERYIELD) { + GeneratorObject* genObj = GetGeneratorObjectForFrame(cx, iter.abstractFramePtr()); + MOZ_ASSERT(genObj->isRunning() || genObj->isClosing()); + } +#endif + if (!getFrame(cx, iter, &scriptFrame)) return reportUncaughtException(ar); RootedValue fval(cx, ObjectValue(*hook)); RootedValue rv(cx); bool ok = js::Call(cx, fval, object, scriptFrame, &rv); return processHandlerResult(ar, ok, rv, iter.abstractFramePtr(), iter.pc(), vp); @@ -6090,18 +6099,23 @@ class DebuggerScriptGetSuccessorOrPredec bool successor_; MutableHandleObject result_; public: DebuggerScriptGetSuccessorOrPredecessorOffsetsMatcher(JSContext* cx, size_t offset, bool successor, MutableHandleObject result) : cx_(cx), offset_(offset), successor_(successor), result_(result) { } + using ReturnType = bool; + ReturnType match(HandleScript script) { + if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) + return false; + PcVector adjacent; if (successor_) { if (!GetSuccessorBytecodes(script->code() + offset_, adjacent)) { ReportOutOfMemory(cx_); return false; } } else { if (!GetPredecessorBytecodes(script, script->code() + offset_, adjacent)) { @@ -6115,22 +6129,24 @@ class DebuggerScriptGetSuccessorOrPredec return false; for (jsbytecode* pc : adjacent) { if (!NewbornArrayPush(cx_, result_, NumberValue(pc - script->code()))) return false; } return true; } + ReturnType match(Handle<LazyScript*> lazyScript) { RootedScript script(cx_, DelazifyScript(cx_, lazyScript)); if (!script) return false; return match(script); } + ReturnType match(Handle<WasmInstanceObject*> instance) { JS_ReportErrorASCII(cx_, "getSuccessorOrPredecessorOffsets NYI on wasm instances"); return false; } }; static bool DebuggerScript_getSuccessorOrPredecessorOffsets(JSContext* cx, unsigned argc, Value* vp,
--- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -120,33 +120,32 @@ CheckDebuggeeThing(JSObject* obj, bool i * created. * * Also note that keys in these weakmaps can be in any compartment, debuggee or * not, because they cannot be deleted when a compartment is no longer a * debuggee: the values need to maintain object identity across add/remove/add * transitions. */ template <class UnbarrieredKey, bool InvisibleKeysOk=false> -class DebuggerWeakMap : private WeakMap<HeapPtr<UnbarrieredKey>, HeapPtr<JSObject*>, - MovableCellHasher<HeapPtr<UnbarrieredKey>>> +class DebuggerWeakMap : private WeakMap<HeapPtr<UnbarrieredKey>, HeapPtr<JSObject*>> { private: typedef HeapPtr<UnbarrieredKey> Key; typedef HeapPtr<JSObject*> Value; typedef HashMap<JS::Zone*, uintptr_t, DefaultHasher<JS::Zone*>, ZoneAllocPolicy> CountMap; CountMap zoneCounts; JS::Compartment* compartment; public: - typedef WeakMap<Key, Value, MovableCellHasher<Key>> Base; + typedef WeakMap<Key, Value> Base; explicit DebuggerWeakMap(JSContext* cx) : Base(cx), zoneCounts(cx->zone()), compartment(cx->compartment()) { } public:
--- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -129,35 +129,33 @@ js::SetGeneratorClosed(JSContext* cx, Ab } bool js::GeneratorThrowOrReturn(JSContext* cx, AbstractFramePtr frame, Handle<GeneratorObject*> genObj, HandleValue arg, uint32_t resumeKind) { if (resumeKind == GeneratorObject::THROW) { cx->setPendingException(arg); - genObj->setRunning(); } else { MOZ_ASSERT(resumeKind == GeneratorObject::RETURN); MOZ_ASSERT(arg.isObject()); frame.setReturnValue(arg); RootedValue closing(cx, MagicValue(JS_GENERATOR_CLOSING)); cx->setPendingException(closing); genObj->setClosing(); } return false; } bool GeneratorObject::resume(JSContext* cx, InterpreterActivation& activation, - HandleObject obj, HandleValue arg, GeneratorObject::ResumeKind resumeKind) + Handle<GeneratorObject*> genObj, HandleValue arg) { - Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>()); MOZ_ASSERT(genObj->isSuspended()); RootedFunction callee(cx, &genObj->callee()); RootedObject envChain(cx, &genObj->environmentChain()); if (!activation.resumeGeneratorFrame(callee, envChain)) return false; activation.regs().fp()->setResumedGenerator(); @@ -179,28 +177,18 @@ GeneratorObject::resume(JSContext* cx, I // Always push on a value, even if we are raising an exception. In the // exception case, the stack needs to have something on it so that exception // handling doesn't skip the catch blocks. See TryNoteIter::settle. activation.regs().sp++; MOZ_ASSERT(activation.regs().spForStackDepth(activation.regs().stackDepth())); activation.regs().sp[-1] = arg; - switch (resumeKind) { - case NEXT: - genObj->setRunning(); - return true; - - case THROW: - case RETURN: - return GeneratorThrowOrReturn(cx, activation.regs().fp(), genObj, arg, resumeKind); - - default: - MOZ_CRASH("bad resumeKind"); - } + genObj->setRunning(); + return true; } const Class GeneratorObject::class_ = { "Generator", JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS) }; static const JSFunctionSpec generator_methods[] = {
--- a/js/src/vm/GeneratorObject.h +++ b/js/src/vm/GeneratorObject.h @@ -55,17 +55,17 @@ class GeneratorObject : public NativeObj return THROW; MOZ_ASSERT(atom == cx->names().return_); return RETURN; } static JSObject* create(JSContext* cx, AbstractFramePtr frame); static bool resume(JSContext* cx, InterpreterActivation& activation, - HandleObject obj, HandleValue arg, ResumeKind resumeKind); + Handle<GeneratorObject*> genObj, HandleValue arg); static bool initialSuspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, jsbytecode* pc) { return suspend(cx, obj, frame, pc, nullptr, 0); } static bool normalSuspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, jsbytecode* pc, Value* vp, unsigned nvalues) { return suspend(cx, obj, frame, pc, vp, nvalues); @@ -142,23 +142,27 @@ class GeneratorObject : public NativeObj "test below should return false for YIELD_AND_AWAIT_INDEX_RUNNING"); return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32() < YIELD_AND_AWAIT_INDEX_CLOSING; } void setRunning() { MOZ_ASSERT(isSuspended()); setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(YIELD_AND_AWAIT_INDEX_RUNNING)); } void setClosing() { - MOZ_ASSERT(isSuspended()); + MOZ_ASSERT(isRunning()); setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(YIELD_AND_AWAIT_INDEX_CLOSING)); } void setYieldAndAwaitIndex(uint32_t yieldAndAwaitIndex) { MOZ_ASSERT_IF(yieldAndAwaitIndex == 0, getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).isUndefined()); MOZ_ASSERT_IF(yieldAndAwaitIndex != 0, isRunning() || isClosing()); + setYieldAndAwaitIndexNoAssert(yieldAndAwaitIndex); + } + // Debugger has to flout the state machine rules a bit. + void setYieldAndAwaitIndexNoAssert(uint32_t yieldAndAwaitIndex) { MOZ_ASSERT(yieldAndAwaitIndex < uint32_t(YIELD_AND_AWAIT_INDEX_CLOSING)); setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(yieldAndAwaitIndex)); MOZ_ASSERT(isSuspended()); } uint32_t yieldAndAwaitIndex() const { MOZ_ASSERT(isSuspended()); return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32(); }
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4262,36 +4262,60 @@ CASE(JSOP_AWAIT) POP_RETURN_VALUE(); goto successful_return_continuation; } CASE(JSOP_RESUME) { { - ReservedRooted<JSObject*> gen(&rootObject0, ®S.sp[-2].toObject()); + Rooted<GeneratorObject*> gen(cx, ®S.sp[-2].toObject().as<GeneratorObject>()); ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]); // popInlineFrame expects there to be an additional value on the stack // to pop off, so leave "gen" on the stack. GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(REGS.pc); - bool ok = GeneratorObject::resume(cx, activation, gen, val, resumeKind); + if (!GeneratorObject::resume(cx, activation, gen, val)) + goto error; JSScript* generatorScript = REGS.fp()->script(); if (cx->realm() != generatorScript->realm()) cx->enterRealmOf(generatorScript); SET_SCRIPT(generatorScript); TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx); TraceLoggerEvent scriptEvent(TraceLogger_Scripts, script); TraceLogStartEvent(logger, scriptEvent); TraceLogStartEvent(logger, TraceLogger_Interpreter); - if (!ok) + switch (Debugger::onEnterFrame(cx, REGS.fp())) { + case ResumeMode::Continue: + break; + case ResumeMode::Throw: + case ResumeMode::Terminate: goto error; + case ResumeMode::Return: + MOZ_ASSERT_IF(REGS.fp()->callee().isGenerator(), // as opposed to an async function + gen->isClosed()); + if (!ForcedReturn(cx, REGS)) + goto error; + goto successful_return_continuation; + } + + switch (resumeKind) { + case GeneratorObject::NEXT: + break; + case GeneratorObject::THROW: + case GeneratorObject::RETURN: + MOZ_ALWAYS_FALSE(GeneratorThrowOrReturn(cx, activation.regs().fp(), gen, val, + resumeKind)); + goto error; + default: + MOZ_CRASH("bad resumeKind"); + } } ADVANCE_AND_DISPATCH(0); } CASE(JSOP_DEBUGAFTERYIELD) { // No-op in the interpreter, as GeneratorObject::resume takes care of // fixing up InterpreterFrames.
--- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -2154,17 +2154,17 @@ class FrameIter size_t numFrameSlots() const; Value frameSlotValue(size_t index) const; // Ensures that we have rematerialized the top frame and its associated // inline frames. Can only be called when isIon(). bool ensureHasRematerializedFrame(JSContext* cx); // True when isInterp() or isBaseline(). True when isIon() if it - // has a rematerialized frame. False otherwise false otherwise. + // has a rematerialized frame. False otherwise. bool hasUsableAbstractFramePtr() const; // ----------------------------------------------------------- // The following functions can only be called when isInterp(), // isBaseline(), isWasm() or isIon(). Further, abstractFramePtr() can // only be called when hasUsableAbstractFramePtr(). // -----------------------------------------------------------
--- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -322,17 +322,16 @@ struct SCOutput { JS::StructuredCloneScope scope() const { return buf.scope(); } MOZ_MUST_USE bool write(uint64_t u); MOZ_MUST_USE bool writePair(uint32_t tag, uint32_t data); MOZ_MUST_USE bool writeDouble(double d); MOZ_MUST_USE bool writeBytes(const void* p, size_t nbytes); MOZ_MUST_USE bool writeChars(const Latin1Char* p, size_t nchars); MOZ_MUST_USE bool writeChars(const char16_t* p, size_t nchars); - MOZ_MUST_USE bool writePtr(const void*); template <class T> MOZ_MUST_USE bool writeArray(const T* p, size_t nbytes); void setCallbacks(const JSStructuredCloneCallbacks* callbacks, void* closure, OwnTransferablePolicy policy) { @@ -834,20 +833,21 @@ SCInput::getPtr(uint64_t data, void** pt // No endianness conversion is used for pointers, since they are not sent // across address spaces anyway. *ptr = reinterpret_cast<void*>(data); } bool SCInput::readPtr(void** p) { + // See endianness comment in getPtr, above. uint64_t u; if (!readNativeEndian(&u)) return false; - *p = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(u)); + *p = reinterpret_cast<void*>(u); return true; } SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope) : cx(cx), buf(scope) { } @@ -948,22 +948,16 @@ SCOutput::writeChars(const char16_t* p, bool SCOutput::writeChars(const Latin1Char* p, size_t nchars) { static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte"); return writeBytes(p, nchars); } -bool -SCOutput::writePtr(const void* p) -{ - return write(reinterpret_cast<uint64_t>(p)); -} - void SCOutput::discardTransferables() { buf.discardTransferables(); } } // namespace js @@ -1788,17 +1782,17 @@ JSStructuredCloneWriter::writeTransferMa ReportOutOfMemory(context()); return false; } // Emit a placeholder pointer. We defer stealing the data until later // (and, if necessary, detaching this object if it's an ArrayBuffer). if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED)) return false; - if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents. + if (!out.write(0)) // Pointer to ArrayBuffer contents. return false; if (!out.write(0)) // extraData return false; } return true; } @@ -1913,17 +1907,17 @@ JSStructuredCloneWriter::transferOwnersh return reportDataCloneError(JS_SCERR_TRANSFERABLE); if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag, &ownership, &content, &extraData)) return false; MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY); } point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership))); point.next(); - point.write(NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content))); + point.write(reinterpret_cast<uint64_t>(content)); point.next(); point.write(NativeEndian::swapToLittleEndian(extraData)); point.next(); } #if DEBUG // Make sure there aren't any more transfer map entries after the expected // number we read out.
--- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -1082,35 +1082,40 @@ XPCJSContext::Initialize(XPCJSContext* a kStackQuotaMin) : kStackQuotaMin; # if defined(MOZ_ASAN) // See the standalone MOZ_ASAN branch below for the ASan case. const size_t kTrustedScriptBuffer = 450 * 1024; # else const size_t kTrustedScriptBuffer = 180 * 1024; # endif +#elif defined(XP_WIN) + // 1MB is the default stack size on Windows. We use the -STACK linker flag + // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack, + // so we determine the stack size at runtime. + const size_t kStackQuota = GetWindowsStackSize(); +# if defined(MOZ_ASAN) + // See the standalone MOZ_ASAN branch below for the ASan case. + const size_t kTrustedScriptBuffer = 450 * 1024; +# else + const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024 //win64 + : 120 * 1024; //win32 +# endif #elif defined(MOZ_ASAN) // ASan requires more stack space due to red-zones, so give it double the // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements // were not taken at the time of this writing, so we hazard a guess that // ASAN builds have roughly thrice the stack overhead as normal builds. // On normal builds, the largest stack frame size we might encounter is // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k. // // FIXME: Does this branch make sense for Windows and Android? // (See bug 1415195) const size_t kStackQuota = 2 * kDefaultStackQuota; const size_t kTrustedScriptBuffer = 450 * 1024; -#elif defined(XP_WIN) - // 1MB is the default stack size on Windows. We use the -STACK linker flag - // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack, - // so we determine the stack size at runtime. - const size_t kStackQuota = GetWindowsStackSize(); - const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024 //win64 - : 120 * 1024; //win32 #elif defined(ANDROID) // Android appears to have 1MB stacks. Allow the use of 3/4 of that size // (768KB on 32-bit), since otherwise we can crash with a stack overflow // when nearing the 1MB limit. const size_t kStackQuota = kDefaultStackQuota + kDefaultStackQuota / 2; const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; #else // Catch-all configuration for other environments.
--- a/js/xpconnect/src/moz.build +++ b/js/xpconnect/src/moz.build @@ -59,11 +59,8 @@ LOCAL_INCLUDES += [ '/layout/style', ] if CONFIG['CC_TYPE'] in ('clang', 'gcc'): CXXFLAGS += ['-Wno-shadow', '-Werror=format'] if CONFIG['NIGHTLY_BUILD']: DEFINES['ENABLE_WASM_GC'] = True - -if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl': - AllowCompilerWarnings() # workaround for bug 1090497
--- a/media/webrtc/signaling/gtest/moz.build +++ b/media/webrtc/signaling/gtest/moz.build @@ -43,11 +43,8 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'uiki if CONFIG['CC_TYPE'] in ('msvc', 'clang-cl'): # This is intended as a temporary workaround to enable warning free building # with VS2015. # reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size CXXFLAGS += ['-wd4312'] # Disable warning for decorated name length exceeded, name was truncated CXXFLAGS += ['-wd4503'] - -if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl': - AllowCompilerWarnings() # workaround for bug 1306642
--- a/media/webrtc/signaling/src/media-conduit/moz.build +++ b/media/webrtc/signaling/src/media-conduit/moz.build @@ -30,11 +30,8 @@ UNIFIED_SOURCES += [ if CONFIG['OS_TARGET'] == 'Android': UNIFIED_SOURCES += [ 'MediaCodecVideoCodec.cpp', 'WebrtcMediaCodecVP8VideoCodec.cpp', ] FINAL_LIBRARY = 'xul' - -if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl': - AllowCompilerWarnings() # workaround for bug 1306642
--- a/media/webrtc/signaling/src/mediapipeline/moz.build +++ b/media/webrtc/signaling/src/mediapipeline/moz.build @@ -24,11 +24,8 @@ UNIFIED_SOURCES += [ 'MediaPipelineFilter.cpp', 'RtpLogger.cpp', 'TransportLayerPacketDumper.cpp', ] DEFINES['TRACING'] = True FINAL_LIBRARY = 'xul' - -if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl': - AllowCompilerWarnings() # workaround for bug 1306642
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -3428,19 +3428,19 @@ PeerConnectionImpl::ExecuteStatsQuery_s( MOZ_ASSERT(query->pipelines[p]); MOZ_ASSERT(query->pipelines[p]->Conduit()); if (!query->pipelines[p] || !query->pipelines[p]->Conduit()) { // continue if we don't have a valid conduit continue; } const MediaPipeline& mp = *query->pipelines[p]; bool isAudio = (mp.Conduit()->type() == MediaSessionConduit::AUDIO); - nsString mediaType = isAudio ? + nsString kind = isAudio ? NS_LITERAL_STRING("audio") : NS_LITERAL_STRING("video"); - nsString idstr = mediaType; + nsString idstr = kind; idstr.AppendLiteral("_"); idstr.AppendInt((uint32_t)p); // TODO(@@NG):ssrcs handle Conduits having multiple stats at the same level // This is pending spec work // Gather pipeline stats. switch (mp.Direction()) { case MediaPipeline::DirectionType::TRANSMIT: { @@ -3467,17 +3467,18 @@ PeerConnectionImpl::ExecuteStatsQuery_s( &packetsLost, &rtt)) { remoteId = NS_LITERAL_STRING("outbound_rtcp_") + idstr; RTCInboundRTPStreamStats s; s.mTimestamp.Construct(timestamp); s.mId.Construct(remoteId); s.mType.Construct(RTCStatsType::Inbound_rtp); ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);}); - s.mMediaType.Construct(mediaType); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); s.mJitter.Construct(double(jitterMs)/1000); s.mRemoteId.Construct(localId); s.mIsRemote = true; s.mPacketsReceived.Construct(packetsReceived); s.mBytesReceived.Construct(bytesReceived); s.mPacketsLost.Construct(packetsLost); if (rtt > 0) { s.mRoundTripTime.Construct(rtt); @@ -3488,17 +3489,18 @@ PeerConnectionImpl::ExecuteStatsQuery_s( } // Then, fill in local side (with cross-link to remote only if present) { RTCOutboundRTPStreamStats s; s.mTimestamp.Construct(query->now); s.mId.Construct(localId); s.mType.Construct(RTCStatsType::Outbound_rtp); ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);}); - s.mMediaType.Construct(mediaType); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); s.mRemoteId.Construct(remoteId); s.mIsRemote = false; s.mPacketsSent.Construct(mp.RtpPacketsSent()); s.mBytesSent.Construct(mp.RtpBytesSent()); // Fill in packet type statistics webrtc::RtcpPacketTypeCounter counters; if (mp.Conduit()->GetSendPacketTypeStats(&counters)) { @@ -3553,32 +3555,34 @@ PeerConnectionImpl::ExecuteStatsQuery_s( if (mp.Conduit()->GetRTCPSenderReport(×tamp, &packetsSent, &bytesSent)) { remoteId = NS_LITERAL_STRING("inbound_rtcp_") + idstr; RTCOutboundRTPStreamStats s; s.mTimestamp.Construct(timestamp); s.mId.Construct(remoteId); s.mType.Construct(RTCStatsType::Outbound_rtp); ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);}); - s.mMediaType.Construct(mediaType); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); s.mRemoteId.Construct(localId); s.mIsRemote = true; s.mPacketsSent.Construct(packetsSent); s.mBytesSent.Construct(bytesSent); query->report->mOutboundRTPStreamStats.Value().AppendElement(s, fallible); } } // Then, fill in local side (with cross-link to remote only if present) RTCInboundRTPStreamStats s; s.mTimestamp.Construct(query->now); s.mId.Construct(localId); s.mType.Construct(RTCStatsType::Inbound_rtp); ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);}); - s.mMediaType.Construct(mediaType); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); unsigned int jitterMs, packetsLost; if (mp.Conduit()->GetRTPStats(&jitterMs, &packetsLost)) { s.mJitter.Construct(double(jitterMs)/1000); s.mPacketsLost.Construct(packetsLost); } if (remoteId.Length()) { s.mRemoteId.Construct(remoteId); }
--- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -248,20 +248,16 @@ @BINPATH@/actors/* ; Safe Browsing @BINPATH@/components/nsURLClassifier.manifest @BINPATH@/components/nsUrlClassifierHashCompleter.js @BINPATH@/components/nsUrlClassifierListManager.js @BINPATH@/components/nsUrlClassifierLib.js -; Private Browsing -@BINPATH@/components/PrivateBrowsing.manifest -@BINPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js - ; Security Reports @BINPATH@/components/SecurityReporter.manifest @BINPATH@/components/SecurityReporter.js ; [Browser Chrome Files] @BINPATH@/chrome/toolkit@JAREXT@ @BINPATH@/chrome/toolkit.manifest
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4889,17 +4889,17 @@ pref("layers.child-process-shutdown", tr pref("layers.max-active", -1); // Compositor target frame rate. NOTE: If vsync is enabled the compositor // frame rate will still be capped. // -1 -> default (match layout.frame_rate or 60 FPS) // 0 -> full-tilt mode: Recomposite even if not transaction occured. pref("layers.offmainthreadcomposition.frame-rate", -1); -#if defined(XP_MACOSX) +#if defined(XP_MACOSX) || defined (OS_OPENBSD) pref("layers.enable-tiles", true); #else pref("layers.enable-tiles", false); #endif #if defined(XP_WIN) pref("layers.enable-tiles-if-skia-pomtp", true); #else pref("layers.enable-tiles-if-skia-pomtp", false); @@ -5932,8 +5932,15 @@ pref("browser.fastblock.enabled", false) pref("browser.fastblock.timeout", 5000); // Enable clipboard readText() and writeText() by default pref("dom.events.asyncClipboard", true); // Disable clipboard read() and write() by default pref("dom.events.asyncClipboard.dataTransfer", false); // Should only be enabled in tests pref("dom.events.testing.asyncClipboard", false); + +#ifdef NIGHTLY_BUILD +// Disable moz* APIs in DataTransfer +pref("dom.datatransfer.mozAtAPIs", false); +#else +pref("dom.datatransfer.mozAtAPIs", true); +#endif
--- a/netwerk/base/nsChannelClassifier.cpp +++ b/netwerk/base/nsChannelClassifier.cpp @@ -17,32 +17,32 @@ #include "nsIClassOfService.h" #include "nsIDocShell.h" #include "nsIDocument.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIIOService.h" #include "nsIParentChannel.h" #include "nsIPermissionManager.h" -#include "nsIPrivateBrowsingTrackingProtectionWhitelist.h" #include "nsIProtocolHandler.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsISecureBrowserUI.h" #include "nsISecurityEventSink.h" #include "nsISupportsPriority.h" #include "nsIURL.h" #include "nsIWebProgressListener.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" #include "nsXULAppAPI.h" #include "nsQueryObject.h" #include "nsIUrlClassifierDBService.h" #include "nsIURLFormatter.h" +#include "mozilla/AntiTrackingCommon.h" #include "mozilla/ErrorNames.h" #include "mozilla/Logging.h" #include "mozilla/Preferences.h" #include "mozilla/net/HttpBaseChannel.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs.h" #include "mozilla/Unused.h" @@ -444,94 +444,49 @@ nsChannelClassifier::ShouldEnableTrackin return NS_OK; } } if (AddonMayLoad(aChannel, chanURI)) { return NS_OK; } - nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIIOService> ios = services::GetIOService(); + NS_ENSURE_TRUE(ios, NS_ERROR_FAILURE); nsCOMPtr<nsIURI> topWinURI; rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI)); if (NS_FAILED(rv)) { return rv; } if (!topWinURI && CachedPrefs::GetInstance()->IsAllowListExample()) { LOG(("nsChannelClassifier[%p]: Allowlisting test domain\n", this)); rv = ios->NewURI(NS_LITERAL_CSTRING("http://allowlisted.example.com"), nullptr, nullptr, getter_AddRefs(topWinURI)); NS_ENSURE_SUCCESS(rv, rv); } - // Take the host/port portion so we can allowlist by site. Also ignore the - // scheme, since users who put sites on the allowlist probably don't expect - // allowlisting to depend on scheme. - nsCOMPtr<nsIURL> url = do_QueryInterface(topWinURI, &rv); + rv = AntiTrackingCommon::IsOnContentBlockingAllowList(topWinURI, mIsAllowListed); if (NS_FAILED(rv)) { return rv; // normal for some loads, no need to print a warning } - nsCString escaped(NS_LITERAL_CSTRING("https://")); - nsAutoCString temp; - rv = url->GetHostPort(temp); - NS_ENSURE_SUCCESS(rv, rv); - escaped.Append(temp); - - // Stuff the whole thing back into a URI for the permission manager. - rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIPermissionManager> permMgr = - do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION; - rv = permMgr->TestPermission(topWinURI, "trackingprotection", &permissions); - NS_ENSURE_SUCCESS(rv, rv); - - if (permissions == nsIPermissionManager::ALLOW_ACTION) { + if (mIsAllowListed) { + *result = false; if (LOG_ENABLED()) { nsCString chanSpec = chanURI->GetSpecOrDefault(); chanSpec.Truncate(std::min(chanSpec.Length(), sMaxSpecLength)); - LOG(("nsChannelClassifier[%p]: User override on channel[%p] (%s) for %s", - this, aChannel, chanSpec.get(), escaped.get())); + LOG(("nsChannelClassifier[%p]: User override on channel[%p] (%s)", + this, aChannel, chanSpec.get())); } - mIsAllowListed = true; - *result = false; } else { *result = true; } - // In Private Browsing Mode we also check against an in-memory list. - if (NS_UsePrivateBrowsing(aChannel)) { - nsCOMPtr<nsIPrivateBrowsingTrackingProtectionWhitelist> pbmtpWhitelist = - do_GetService(NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - bool exists = false; - rv = pbmtpWhitelist->ExistsInAllowList(topWinURI, &exists); - NS_ENSURE_SUCCESS(rv, rv); - - if (exists) { - mIsAllowListed = true; - if (LOG_ENABLED()) { - nsCString chanSpec = chanURI->GetSpecOrDefault(); - chanSpec.Truncate(std::min(chanSpec.Length(), sMaxSpecLength)); - LOG(("nsChannelClassifier[%p]: User override (PBM) on channel[%p] (%s) for %s", - this, aChannel, chanSpec.get(), escaped.get())); - } - } - - *result = !exists; - } - // Tracking protection will be enabled so return without updating // the security state. If any channels are subsequently cancelled // (page elements blocked) the state will be then updated. if (*result) { if (LOG_ENABLED()) { nsCString chanSpec = chanURI->GetSpecOrDefault(); chanSpec.Truncate(std::min(chanSpec.Length(), sMaxSpecLength)); nsCString topWinSpec = topWinURI->GetSpecOrDefault(); @@ -1227,17 +1182,22 @@ TrackingURICallback::OnTrackerFound(nsre nsCOMPtr<nsIChannel> channel = mChannelClassifier->GetChannel(); MOZ_ASSERT(channel); if (aErrorCode == NS_ERROR_TRACKING_URI && mChannelClassifier->ShouldEnableTrackingProtection()) { mChannelClassifier->SetBlockedContent(channel, aErrorCode, mList, mProvider, mFullHash); LOG(("TrackingURICallback[%p]::OnTrackerFound, cancelling channel[%p]", mChannelClassifier.get(), channel.get())); - channel->Cancel(aErrorCode); + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(channel); + if (httpChannel) { + Unused << httpChannel->CancelForTrackingProtection(); + } else { + Unused << channel->Cancel(aErrorCode); + } } else { MOZ_ASSERT(aErrorCode == NS_ERROR_TRACKING_ANNOTATION_URI); MOZ_ASSERT(mChannelClassifier->ShouldEnableTrackingAnnotation()); bool isThirdPartyWithTopLevelWinURI = false; nsresult rv = IsThirdParty(channel, &isThirdPartyWithTopLevelWinURI); if (NS_WARN_IF(NS_FAILED(rv))) { LOG(("TrackingURICallback[%p]::OnTrackerFound IsThirdParty() failed",
--- a/netwerk/base/nsIChannelEventSink.idl +++ b/netwerk/base/nsIChannelEventSink.idl @@ -48,16 +48,23 @@ interface nsIChannelEventSink : nsISuppo /** * This is a special-cased redirect coming from hitting HSTS upgrade * redirect from http to https only. In some cases this type of redirect * may be considered as safe despite not being the-same-origin redirect. */ const unsigned long REDIRECT_STS_UPGRADE = 1 << 3; /** + * This redirect has already been presented to the nsILoadURIDelegate + * for possible handling; if this flag is set we may safely skip checking + * if the nsILoadURIDelegate will handle the redirect. + */ + const unsigned long REDIRECT_DELEGATES_CHECKED = 1 << 4; + + /** * Called when a redirect occurs. This may happen due to an HTTP 3xx status * code. The purpose of this method is to notify the sink that a redirect * is about to happen, but also to give the sink the right to veto the * redirect by throwing or passing a failure-code in the callback. * * Note that vetoing the redirect simply means that |newChannel| will not * be opened. It is important to understand that |oldChannel| will continue * loading as if it received a HTTP 200, which includes notifying observers
--- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -4719,10 +4719,16 @@ HttpBaseChannel::GetNativeServerTiming(n if (NS_SUCCEEDED(mURI->SchemeIs("https", &isHTTPS)) && isHTTPS) { ParseServerTimingHeader(mResponseHead, aServerTiming); ParseServerTimingHeader(mResponseTrailers, aServerTiming); } return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::CancelForTrackingProtection() +{ + return Cancel(NS_ERROR_TRACKING_URI); +} + } // namespace net } // namespace mozilla
--- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -266,16 +266,17 @@ public: virtual void SetAltDataForChild(bool aIsForChild) override; NS_IMETHOD GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) override; NS_IMETHOD GetIntegrityMetadata(nsAString& aIntegrityMetadata) override; NS_IMETHOD SetIntegrityMetadata(const nsAString& aIntegrityMetadata) override; NS_IMETHOD GetLastRedirectFlags(uint32_t *aValue) override; NS_IMETHOD SetLastRedirectFlags(uint32_t aValue) override; NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override; NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override; + NS_IMETHOD CancelForTrackingProtection() override; inline void CleanRedirectCacheChainIfNecessary() { mRedirectedCachekeys = nullptr; } NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName, nsIHttpUpgradeListener *aListener) override;
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -462,16 +462,31 @@ nsHttpChannel::PrepareToConnect() mCallOnResume = &nsHttpChannel::HandleOnBeforeConnect; return NS_OK; } return OnBeforeConnect(); } void +nsHttpChannel::HandleContinueCancelledByTrackingProtection() +{ + MOZ_ASSERT(!mCallOnResume, "How did that happen?"); + + if (mSuspendCount) { + LOG(("Waiting until resume HandleContinueCancelledByTrackingProtection [this=%p]\n", this)); + mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection; + return; + } + + LOG(("nsHttpChannel::HandleContinueCancelledByTrackingProtection [this=%p]\n", this)); + ContinueCancelledByTrackingProtection(); +} + +void nsHttpChannel::HandleOnBeforeConnect() { MOZ_ASSERT(!mCallOnResume, "How did that happen?"); nsresult rv; if (mSuspendCount) { LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this)); mCallOnResume = &nsHttpChannel::HandleOnBeforeConnect; @@ -5982,27 +5997,98 @@ NS_INTERFACE_MAP_END_INHERITING(HttpBase //----------------------------------------------------------------------------- NS_IMETHODIMP nsHttpChannel::Cancel(nsresult status) { MOZ_ASSERT(NS_IsMainThread()); // We should never have a pump open while a CORS preflight is in progress. MOZ_ASSERT_IF(mPreflightChannel, !mCachePump); + MOZ_ASSERT(status != NS_ERROR_TRACKING_URI, + "NS_ERROR_TRACKING_URI needs to be handled by CancelForTrackingProtection()"); LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n", this, static_cast<uint32_t>(status))); if (mCanceled) { LOG((" ignoring; already canceled\n")); return NS_OK; } if (mWaitingForRedirectCallback) { LOG(("channel canceled during wait for redirect callback")); } + + return CancelInternal(status); +} + +NS_IMETHODIMP +nsHttpChannel::CancelForTrackingProtection() +{ + MOZ_ASSERT(NS_IsMainThread()); + // We should never have a pump open while a CORS preflight is in progress. + MOZ_ASSERT_IF(mPreflightChannel, !mCachePump); + + LOG(("nsHttpChannel::CancelForTrackingProtection [this=%p]\n", this)); + + if (mCanceled) { + LOG((" ignoring; already canceled\n")); + return NS_OK; + } + + // We are being canceled by the channel classifier because of tracking + // protection, but we haven't yet had a chance to dispatch the + // "http-on-modify-request" notifications yet (this would normally be + // done in PrepareToConnect()). So do that now, before proceeding to + // cancel. + // + // Note that running these observers can itself result in the channel + // being canceled. In that case, we accept that cancelation code as + // the cause of the cancelation, as if the classification of the channel + // would have occurred past this point! + + // notify "http-on-modify-request" observers + CallOnModifyRequestObservers(); + + SetLoadGroupUserAgentOverride(); + + // Check if request was cancelled during on-modify-request or on-useragent. + if (mCanceled) { + return mStatus; + } + + if (mSuspendCount) { + LOG(("Waiting until resume in Cancel [this=%p]\n", this)); + MOZ_ASSERT(!mCallOnResume); + mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection; + return NS_OK; + } + + return CancelInternal(NS_ERROR_TRACKING_URI); +} + +void +nsHttpChannel::ContinueCancelledByTrackingProtection() +{ + MOZ_ASSERT(NS_IsMainThread()); + // We should never have a pump open while a CORS preflight is in progress. + MOZ_ASSERT_IF(mPreflightChannel, !mCachePump); + + LOG(("nsHttpChannel::ContinueCancelledByTrackingProtection [this=%p]\n", + this)); + if (mCanceled) { + LOG((" ignoring; already canceled\n")); + return; + } + + Unused << CancelInternal(NS_ERROR_TRACKING_URI); +} + +nsresult +nsHttpChannel::CancelInternal(nsresult status) +{ mCanceled = true; mStatus = status; if (mProxyRequest) mProxyRequest->Cancel(status); CancelNetworkRequest(status); mCacheInputStream.CloseAndRelease(); if (mCachePump) mCachePump->Cancel(status);
--- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -158,16 +158,17 @@ public: NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener) override; // nsIHttpChannel NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override; // nsIHttpChannelInternal NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override; NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override; NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override; NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override; + NS_IMETHOD CancelForTrackingProtection() override; // nsISupportsPriority NS_IMETHOD SetPriority(int32_t value) override; // nsIClassOfService NS_IMETHOD SetClassFlags(uint32_t inFlags) override; NS_IMETHOD AddClassFlags(uint32_t inFlags) override; NS_IMETHOD ClearClassFlags(uint32_t inFlags) override; // nsIResumableChannel @@ -279,16 +280,19 @@ public: protected: virtual ~nsHttpChannel(); private: typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result); bool RequestIsConditional(); + void HandleContinueCancelledByTrackingProtection(); + nsresult CancelInternal(nsresult status); + void ContinueCancelledByTrackingProtection(); // Connections will only be established in this function. // (including DNS prefetch and speculative connection.) nsresult BeginConnectActual(); // We might synchronously or asynchronously call BeginConnectActual, // which includes DNS prefetch and speculative connection, according to // whether an async tracker lookup is required. If the tracker lookup
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -328,9 +328,17 @@ interface nsIHttpChannelInternal : nsISu * SetupReplacementChannel() method. */ [noscript, infallible] attribute unsigned long lastRedirectFlags; // This is use to determine the duration since navigation started. [noscript] attribute TimeStamp navigationStartTimeStamp; + /** + * Cancel a channel because we have determined that it needs to be blocked + * for tracking protection. This is an internal API that is meant to be + * called by the channel classifier. Please DO NOT use this API if you don't + * know whether you should be using it. + */ + [noscript] void cancelForTrackingProtection(); + };
--- a/old-configure.in +++ b/old-configure.in @@ -2783,16 +2783,18 @@ if test -n "$MOZ_LIBJPEG_TURBO" -a -n "$ Darwin:arm*) ;; WINNT:x86) LIBJPEG_TURBO_ASFLAGS="-DPIC -DWIN32" ;; WINNT:x86_64) LIBJPEG_TURBO_ASFLAGS="-D__x86_64__ -DPIC -DWIN64 -DMSVC" ;; + WINNT:*) + ;; *:arm) LIBJPEG_TURBO_ASFLAGS="-march=armv7-a -mfpu=neon" ;; *:aarch64) LIBJPEG_TURBO_ASFLAGS="-march=armv8-a" ;; *:mips32) LIBJPEG_TURBO_ASFLAGS="-mdspr2"
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js +++ b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -2579,17 +2579,17 @@ function synthesizeDragOver(aSrcElement, // This method runs before other callbacks, and acts as a way to inject the // initial drag data into the DataTransfer. function fillDrag(event) { if (aDragData) { for (var i = 0; i < aDragData.length; i++) { var item = aDragData[i]; for (var j = 0; j < item.length; j++) { - event.dataTransfer.mozSetDataAt(item[j].type, item[j].data, i); + _EU_maybeWrap(event.dataTransfer).mozSetDataAt(item[j].type, item[j].data, i); } } } event.dataTransfer.dropEffect = aDropEffect || "move"; event.preventDefault(); } function trapDrag(subject, topic) {
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp +++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp @@ -11,36 +11,40 @@ #include "mozilla/AbstractThread.h" #include "mozilla/Logging.h" #include "mozilla/StaticPrefs.h" #include "mozIThirdPartyUtil.h" #include "nsContentUtils.h" #include "nsGlobalWindowInner.h" #include "nsICookiePermission.h" #include "nsICookieService.h" +#include "nsIIOService.h" #include "nsIPermissionManager.h" #include "nsIPrincipal.h" #include "nsIURI.h" +#include "nsIURL.h" #include "nsPIDOMWindow.h" #include "nsScriptSecurityManager.h" #include "prtime.h" #define ANTITRACKING_PERM_KEY "3rdPartyStorage" using namespace mozilla; using mozilla::dom::ContentChild; static LazyLogModule gAntiTrackingLog("AntiTracking"); +static const nsCString::size_type sMaxSpecLength = 128; #define LOG(format) MOZ_LOG(gAntiTrackingLog, mozilla::LogLevel::Debug, format) #define LOG_SPEC(format, uri) \ PR_BEGIN_MACRO \ if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) { \ nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)")); \ + _specStr.Truncate(std::min(_specStr.Length(), sMaxSpecLength)); \ if (uri) { \ _specStr = uri->GetSpecOrDefault(); \ } \ const char* _spec = _specStr.get(); \ LOG(format); \ } \ PR_END_MACRO @@ -637,8 +641,71 @@ AntiTrackingCommon::MaybeIsFirstPartySto Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI)); LOG_SPEC(("Testing permission type %s for %s resulted in %d (%s)", type.get(), _spec, int(result), result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"), parentPrincipalURI); return result == nsIPermissionManager::ALLOW_ACTION; } + +nsresult +AntiTrackingCommon::IsOnContentBlockingAllowList(nsIURI* aTopWinURI, + bool& aIsAllowListed) +{ + aIsAllowListed = false; + + LOG_SPEC(("Deciding whether the user has overridden content blocking for %s", + _spec), aTopWinURI); + + nsCOMPtr<nsIIOService> ios = services::GetIOService(); + NS_ENSURE_TRUE(ios, NS_ERROR_FAILURE); + + // Take the host/port portion so we can allowlist by site. Also ignore the + // scheme, since users who put sites on the allowlist probably don't expect + // allowlisting to depend on scheme. + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIURL> url = do_QueryInterface(aTopWinURI, &rv); + if (NS_FAILED(rv)) { + return rv; // normal for some loads, no need to print a warning + } + + nsCString escaped(NS_LITERAL_CSTRING("https://")); + nsAutoCString temp; + rv = url->GetHostPort(temp); + NS_ENSURE_SUCCESS(rv, rv); + escaped.Append(temp); + + // Stuff the whole thing back into a URI for the permission manager. + nsCOMPtr<nsIURI> topWinURI; + rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPermissionManager> permMgr = + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Check both the normal mode and private browsing mode user override permissions. + const char* types[] = { + "trackingprotection", + "trackingprotection-pb" + }; + + for (size_t i = 0; i < ArrayLength(types); ++i) { + uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION; + rv = permMgr->TestPermission(topWinURI, types[i], &permissions); + NS_ENSURE_SUCCESS(rv, rv); + + if (permissions == nsIPermissionManager::ALLOW_ACTION) { + aIsAllowListed = true; + LOG_SPEC(("Found user override type %s for %s", types[i], _spec), + topWinURI); + // Stop checking the next permisson type if we decided to override. + break; + } + } + + if (!aIsAllowListed) { + LOG(("No user override found")); + } + + return NS_OK; +}
--- a/toolkit/components/antitracking/AntiTrackingCommon.h +++ b/toolkit/components/antitracking/AntiTrackingCommon.h @@ -78,13 +78,18 @@ public: // For IPC only. static void SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal, const nsCString& aParentOrigin, const nsCString& aGrantedOrigin, FirstPartyStorageAccessGrantedForOriginResolver&& aResolver); + + // Check whether a top window URI is on the content blocking allow list. + static nsresult + IsOnContentBlockingAllowList(nsIURI* aTopWinURI, bool& aIsAllowListed); + }; } // namespace mozilla #endif // mozilla_antitrackingservice_h
--- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -44,17 +44,16 @@ DIRS += [ 'mozintl', 'mozprotocol', 'osfile', 'parentalcontrols', 'passwordmgr', 'perf', 'perfmonitoring', 'places', - 'privatebrowsing', 'processsingleton', 'promiseworker', 'prompts', 'protobuf', 'reader', 'remotebrowserutils', 'remotepagemanager', 'reflect',
deleted file mode 100644 --- a/toolkit/components/privatebrowsing/PrivateBrowsing.manifest +++ /dev/null @@ -1,2 +0,0 @@ -component {a319b616-c45d-4037-8d86-01c592b5a9af} PrivateBrowsingTrackingProtectionWhitelist.js -contract @mozilla.org/pbm-tp-whitelist;1 {a319b616-c45d-4037-8d86-01c592b5a9af}
deleted file mode 100644 --- a/toolkit/components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js +++ /dev/null @@ -1,62 +0,0 @@ -/* 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/. */ - -ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); -ChromeUtils.import("resource://gre/modules/Services.jsm"); - -function PrivateBrowsingTrackingProtectionWhitelist() { - // The list of URIs explicitly excluded from tracking protection. - this._allowlist = []; - - Services.obs.addObserver(this, "last-pb-context-exited", true); -} - -PrivateBrowsingTrackingProtectionWhitelist.prototype = { - classID: Components.ID("{a319b616-c45d-4037-8d86-01c592b5a9af}"), - QueryInterface: ChromeUtils.generateQI([Ci.nsIPrivateBrowsingTrackingProtectionWhitelist, Ci.nsIObserver, Ci.nsISupportsWeakReference]), - _xpcom_factory: XPCOMUtils.generateSingletonFactory(PrivateBrowsingTrackingProtectionWhitelist), - - /** - * Add the provided URI to the list of allowed tracking sites. - * - * @param uri nsIURI - * The URI to add to the list. - */ - addToAllowList(uri) { - if (!this._allowlist.includes(uri.spec)) { - this._allowlist.push(uri.spec); - } - }, - - /** - * Remove the provided URI from the list of allowed tracking sites. - * - * @param uri nsIURI - * The URI to add to the list. - */ - removeFromAllowList(uri) { - let index = this._allowlist.indexOf(uri.spec); - if (index !== -1) { - this._allowlist.splice(index, 1); - } - }, - - /** - * Check if the provided URI exists in the list of allowed tracking sites. - * - * @param uri nsIURI - * The URI to add to the list. - */ - existsInAllowList(uri) { - return this._allowlist.includes(uri.spec); - }, - - observe(subject, topic, data) { - if (topic == "last-pb-context-exited") { - this._allowlist = []; - } - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PrivateBrowsingTrackingProtectionWhitelist]);
deleted file mode 100644 --- a/toolkit/components/privatebrowsing/moz.build +++ /dev/null @@ -1,19 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -with Files('**'): - BUG_COMPONENT = ('Core', 'DOM: Security') - -XPIDL_SOURCES += [ - 'nsIPrivateBrowsingTrackingProtectionWhitelist.idl', -] - -XPIDL_MODULE = 'privatebrowsing' - -EXTRA_COMPONENTS += [ - 'PrivateBrowsing.manifest', - 'PrivateBrowsingTrackingProtectionWhitelist.js', -]
deleted file mode 100644 --- a/toolkit/components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl +++ /dev/null @@ -1,46 +0,0 @@ -/* 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 "nsISupports.idl" - -interface nsIURI; - -/** - * The Private Browsing Tracking Protection service checks a URI against an - * in-memory list of tracking sites. - */ -[scriptable, uuid(c77ddfac-6cd6-43a9-84e8-91682a1a7b18)] -interface nsIPrivateBrowsingTrackingProtectionWhitelist : nsISupports -{ - /** - * Add a URI to the list of allowed tracking sites in Private Browsing mode - * (essentially a tracking whitelist). This operation will cause the URI to - * be registered if it does not currently exist. If it already exists, then - * the operation is essentially a no-op. - * - * @param uri the uri to add to the list - */ - void addToAllowList(in nsIURI uri); - - /** - * Remove a URI from the list of allowed tracking sites in Private Browsing - * mode (the tracking whitelist). If the URI is not already in the list, - * then the operation is essentially a no-op. - * - * @param uri the uri to remove from the list - */ - void removeFromAllowList(in nsIURI uri); - - /** - * Check if a URI exists in the list of allowed tracking sites in Private - * Browsing mode (the tracking whitelist). - * - * @param uri the uri to look for in the list - */ - bool existsInAllowList(in nsIURI uri); -}; - -%{ C++ -#define NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID "@mozilla.org/pbm-tp-whitelist;1" -%}
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -464,17 +464,17 @@ "kind": "exponential", "high": 10000, "n_buckets": 50, "description": "Time spent on one asynchronous SnowWhite freeing (ms)" }, "CYCLE_COLLECTOR_SLICE_DURING_IDLE": { "record_in_processes": ["main", "content"], "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org"], - "expires_in_version": "62", + "expires_in_version": "never", "kind": "linear", "high": 100, "n_buckets": 50, "bug_numbers": [1372042], "description": "Percent of cycle collector slice done during idle time" }, "DEFERRED_FINALIZE_ASYNC": { "record_in_processes": ["main", "content"], @@ -516,17 +516,17 @@ "kind": "exponential", "high": 10000, "n_buckets": 50, "description": "Max time spent on one forget skippable (ms)" }, "FORGET_SKIPPABLE_DURING_IDLE": { "record_in_processes": ["main", "content"], "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org"], - "expires_in_version": "62", + "expires_in_version": "never", "kind": "linear", "high": 100, "n_buckets": 50, "bug_numbers": [1372042], "description": "Percent of the cycle collector's forget skippable done during idle time" }, "FULLSCREEN_TRANSITION_BLACK_MS": { "record_in_processes": ["main", "content"], @@ -782,17 +782,17 @@ "kind": "enumerated", "n_values": 32, "bug_numbers": [1293262], "description": "How many objects groups were selected for pretenuring by a minor GC" }, "GC_SLICE_DURING_IDLE": { "record_in_processes": ["main", "content"], "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org"], - "expires_in_version": "62", + "expires_in_version": "never", "kind": "linear", "high": 100, "n_buckets": 50, "bug_numbers": [1372042], "description": "Percent of GC slice done during idle time" }, "GC_BUDGET_OVERRUN": { "record_in_processes": ["main", "content"],
--- a/toolkit/content/widgets.css +++ b/toolkit/content/widgets.css @@ -13,10 +13,11 @@ @import url("chrome://global/skin/groupbox.css"); @import url("chrome://global/skin/menu.css"); @import url("chrome://global/skin/menulist.css"); @import url("chrome://global/skin/notification.css"); @import url("chrome://global/skin/popup.css"); @import url("chrome://global/skin/progressmeter.css"); @import url("chrome://global/skin/richlistbox.css"); @import url("chrome://global/skin/splitter.css"); +@import url("chrome://global/skin/tabbox.css"); @import url("chrome://global/skin/toolbar.css"); @import url("chrome://global/skin/wizard.css");
--- a/toolkit/content/widgets/tabbox.xml +++ b/toolkit/content/widgets/tabbox.xml @@ -3,22 +3,17 @@ - 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/. --> <bindings id="tabBindings" xmlns="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"> - <binding id="tabbox"> - <resources> - <stylesheet src="chrome://global/skin/tabbox.css"/> - </resources> - <implementation> <property name="handleCtrlTab"> <setter> <![CDATA[ this.setAttribute("handleCtrlTab", val); return val; ]]> </setter> @@ -212,20 +207,16 @@ .getService(Ci.nsIEventListenerService); els.removeSystemEventListener(this._eventNode, "keydown", this, false); </destructor> </implementation> </binding> <binding id="tabs" extends="chrome://global/content/bindings/general.xml#basecontrol"> - <resources> - <stylesheet src="chrome://global/skin/tabbox.css"/> - </resources> - <content> <xul:spacer class="tabs-left"/> <children/> <xul:spacer class="tabs-right" flex="1"/> </content> <implementation implements="nsIDOMXULSelectControlElement, nsIDOMXULRelatedElement"> <constructor> @@ -528,20 +519,16 @@ event.stopPropagation(); ]]> </handler> </handlers> #endif </binding> <binding id="tabpanels"> - <resources> - <stylesheet src="chrome://global/skin/tabbox.css"/> - </resources> - <implementation implements="nsIDOMXULRelatedElement"> <!-- nsIDOMXULRelatedElement --> <method name="getRelatedElement"> <parameter name="aTabPanelElm"/> <body> <![CDATA[ if (!aTabPanelElm) return null; @@ -644,20 +631,16 @@ ]]> </setter> </property> </implementation> </binding> <binding id="tab" display="xul:button" extends="chrome://global/content/bindings/general.xml#basetext"> - <resources> - <stylesheet src="chrome://global/skin/tabbox.css"/> - </resources> - <content> <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1"> <xul:image class="tab-icon" xbl:inherits="validate,src=image" role="presentation"/> <xul:label class="tab-text" xbl:inherits="value=label,accesskey,crop,disabled" flex="1"
--- a/toolkit/modules/PrivateBrowsingUtils.jsm +++ b/toolkit/modules/PrivateBrowsingUtils.jsm @@ -1,20 +1,50 @@ /* 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/. */ var EXPORTED_SYMBOLS = ["PrivateBrowsingUtils"]; ChromeUtils.import("resource://gre/modules/Services.jsm"); -ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function PrivateBrowsingContentBlockingAllowList() { + Services.obs.addObserver(this, "last-pb-context-exited", true); +} + +PrivateBrowsingContentBlockingAllowList.prototype = { + QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), -XPCOMUtils.defineLazyServiceGetter(this, "gPBMTPWhitelist", - "@mozilla.org/pbm-tp-whitelist;1", - "nsIPrivateBrowsingTrackingProtectionWhitelist"); + /** + * Add the provided URI to the list of allowed tracking sites. + * + * @param uri nsIURI + * The URI to add to the list. + */ + addToAllowList(uri) { + Services.perms.add(uri, "trackingprotection-pb", Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION); + }, + + /** + * Remove the provided URI from the list of allowed tracking sites. + * + * @param uri nsIURI + * The URI to remove from the list. + */ + removeFromAllowList(uri) { + Services.perms.remove(uri, "trackingprotection-pb"); + }, + + observe(subject, topic, data) { + if (topic == "last-pb-context-exited") { + Services.perms.removeByType("trackingprotection-pb"); + } + } +}; const kAutoStartPref = "browser.privatebrowsing.autostart"; // This will be set to true when the PB mode is autostarted from the command // line for the current session. var gTemporaryAutoStartMode = false; var PrivateBrowsingUtils = { @@ -51,26 +81,27 @@ var PrivateBrowsingUtils = { } return this.privacyContextFromWindow(aBrowser.contentWindow).usePrivateBrowsing; }, privacyContextFromWindow: function pbu_privacyContextFromWindow(aWindow) { return aWindow.docShell.QueryInterface(Ci.nsILoadContext); }, - addToTrackingAllowlist(aURI) { - gPBMTPWhitelist.addToAllowList(aURI); + get _pbCBAllowList() { + delete this._pbCBAllowList; + return this._pbCBAllowList = new PrivateBrowsingContentBlockingAllowList(); }, - existsInTrackingAllowlist(aURI) { - return gPBMTPWhitelist.existsInAllowList(aURI); + addToTrackingAllowlist(aURI) { + this._pbCBAllowList.addToAllowList(aURI); }, removeFromTrackingAllowlist(aURI) { - gPBMTPWhitelist.removeFromAllowList(aURI); + this._pbCBAllowList.removeFromAllowList(aURI); }, get permanentPrivateBrowsing() { try { return gTemporaryAutoStartMode || Services.prefs.getBoolPref(kAutoStartPref); } catch (e) { // The pref does not exist
--- a/uriloader/base/nsDocLoader.cpp +++ b/uriloader/base/nsDocLoader.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; 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/. */ #include "nspr.h" #include "mozilla/Logging.h" #include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" #include "nsDocLoader.h" #include "nsCURILoader.h" #include "nsNetUtil.h" #include "nsIHttpChannel.h" #include "nsIWebProgressListener2.h" #include "nsIServiceManager.h" @@ -29,16 +31,18 @@ #include "nsIScriptSecurityManager.h" #include "nsITransport.h" #include "nsISocketTransport.h" #include "nsIDocShell.h" #include "nsIDocument.h" #include "nsPresContext.h" #include "nsIAsyncVerifyRedirectCallback.h" +#include "nsILoadURIDelegate.h" +#include "nsIBrowserDOMWindow.h" using mozilla::DebugOnly; using mozilla::LogLevel; // // Log module for nsIDocumentLoader logging... // // To enable logging (see mozilla/Logging.h for full details): @@ -1416,21 +1420,116 @@ int64_t nsDocLoader::CalculateMaxProgres if (info->mMaxProgress < info->mCurrentProgress) { return int64_t(-1); } max += info->mMaxProgress; } return max; } +class LoadURIDelegateRedirectHandler final : public mozilla::dom::PromiseNativeHandler +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(LoadURIDelegateRedirectHandler) + + LoadURIDelegateRedirectHandler(nsDocLoader* aDocLoader, + nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aFlags, + nsIAsyncVerifyRedirectCallback* aCallback) + : mDocLoader(aDocLoader) + , mOldChannel(aOldChannel) + , mNewChannel(aNewChannel) + , mFlags(aFlags) + , mCallback(aCallback) + {} + + void + ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override + { + if (aValue.isBoolean() && aValue.toBoolean()) { + // The app handled the redirect, notify the callback + mCallback->OnRedirectVerifyCallback(NS_ERROR_ABORT); + } else { + UnhandledCallback(); + } + } + + void + RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override + { + UnhandledCallback(); + } + +private: + ~LoadURIDelegateRedirectHandler() + {} + + void UnhandledCallback() + { + // If the redirect wasn't handled by the nsILoadURIDelegate, let Gecko + // handle it. + mFlags |= nsIChannelEventSink::REDIRECT_DELEGATES_CHECKED; + mDocLoader->AsyncOnChannelRedirect(mOldChannel, mNewChannel, mFlags, + mCallback); + } + + RefPtr<nsDocLoader> mDocLoader; + nsCOMPtr<nsIChannel> mOldChannel; + nsCOMPtr<nsIChannel> mNewChannel; + uint32_t mFlags; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback; +}; + +NS_IMPL_CYCLE_COLLECTION(LoadURIDelegateRedirectHandler, mDocLoader, + mOldChannel, mNewChannel, mCallback) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadURIDelegateRedirectHandler) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadURIDelegateRedirectHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadURIDelegateRedirectHandler) + NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel, nsIChannel *aNewChannel, uint32_t aFlags, nsIAsyncVerifyRedirectCallback *cb) { + if ((aFlags & + (nsIChannelEventSink::REDIRECT_TEMPORARY | + nsIChannelEventSink::REDIRECT_PERMANENT)) && + !(aFlags & nsIChannelEventSink::REDIRECT_DELEGATES_CHECKED)) { + nsCOMPtr<nsIDocShell> docShell = + do_QueryInterface(static_cast<nsIRequestObserver*>(this)); + + nsCOMPtr<nsILoadURIDelegate> delegate; + docShell->GetLoadURIDelegate(getter_AddRefs(delegate)); + + nsCOMPtr<nsIURI> newURI; + aNewChannel->GetURI(getter_AddRefs(newURI)); + + if (newURI && delegate) { + RefPtr<mozilla::dom::Promise> promise; + const int where = nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; + nsresult rv = delegate->LoadURI(newURI, where, /* flags */ 0, + /* triggering principal */ nullptr, + getter_AddRefs(promise)); + if (NS_SUCCEEDED(rv) && promise) { + RefPtr<LoadURIDelegateRedirectHandler> handler = + new LoadURIDelegateRedirectHandler(this, aOldChannel, aNewChannel, + aFlags, cb); + + promise->AppendNativeHandler(handler); + return NS_OK; + } + } + } + if (aOldChannel) { nsLoadFlags loadFlags = 0; int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING | nsIWebProgressListener::STATE_IS_REQUEST; aOldChannel->GetLoadFlags(&loadFlags); // If the document channel is being redirected, then indicate that the
--- a/xpcom/components/nsComponentManager.cpp +++ b/xpcom/components/nsComponentManager.cpp @@ -475,23 +475,42 @@ AssertNotMallocAllocated(T* aPtr) MOZ_ASSERT(info.tag == TagUnknown); #endif } template<typename T> static void AssertNotStackAllocated(T* aPtr) { - // The main thread's stack should be allocated at the top of our address - // space. Anything stack allocated should be above us on the stack, and - // therefore above our first argument pointer. - // Only this is apparently not the case on Windows. -#if !(defined(XP_WIN) || defined(ANDROID)) - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(uintptr_t(aPtr) < uintptr_t(&aPtr)); + // On all of our supported platforms, the stack grows down. Any address + // located below the address of our argument is therefore guaranteed not to be + // stack-allocated by the caller. + // + // For addresses above our argument, things get trickier. The main thread + // stack is traditionally placed at the top of the program's address space, + // but that is becoming less reliable as more and more systems adopt address + // space layout randomization strategies, so we have to guess how much space + // above our argument pointer we need to care about. + // + // On most systems, we're guaranteed at least several KiB at the top of each + // stack for TLS. We'd probably be safe assuming at least 4KiB in the stack + // segment above our argument address, but safer is... well, safer. + // + // For threads with huge stacks, it's theoretically possible that we could + // wind up being passed a stack-allocated string from farther up the stack, + // but this is a best-effort thing, so we'll assume we only care about the + // immediate caller. For that case, max 2KiB per stack frame is probably a + // reasonable guess most of the time, and is less than the ~4KiB that we + // expect for TLS, so go with that to avoid the risk of bumping into heap + // data just above the stack. +#ifdef DEBUG + static constexpr size_t kFuzz = 2048; + + MOZ_ASSERT(uintptr_t(aPtr) < uintptr_t(&aPtr) || + uintptr_t(aPtr) > uintptr_t(&aPtr) + kFuzz); #endif } static inline nsCString AsLiteralCString(const char* aStr) { AssertNotMallocAllocated(aStr); AssertNotStackAllocated(aStr);