author | Wes Kocher <wkocher@mozilla.com> |
Tue, 28 Mar 2017 13:30:56 -0700 | |
changeset 350146 | 272ce6c2572164f5f6a9fba2a980ba9ccf50770c |
parent 350083 | e23cf1b38ad4b55416318d205864195d3666b4f3 (current diff) |
parent 350145 | 47dfce832d75edb30fa3dac79a62828dba22e70b (diff) |
child 350147 | 9ef667136bbe97e7e8e8606df7103f333b006ac2 |
child 350264 | 07bba24c4f671b46b351f6ae0c1bdc60fd419c8f |
push id | 31568 |
push user | kwierso@gmail.com |
push date | Tue, 28 Mar 2017 20:31:07 +0000 |
treeherder | mozilla-central@272ce6c25721 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 55.0a1 |
first release with | nightly linux32
272ce6c25721
/
55.0a1
/
20170329100319
/
files
nightly linux64
272ce6c25721
/
55.0a1
/
20170329100319
/
files
nightly mac
272ce6c25721
/
55.0a1
/
20170329030240
/
files
nightly win32
272ce6c25721
/
55.0a1
/
20170329030240
/
files
nightly win64
272ce6c25721
/
55.0a1
/
20170329030240
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
55.0a1
/
20170329100319
/
pushlog to previous
nightly linux64
55.0a1
/
20170329100319
/
pushlog to previous
nightly mac
55.0a1
/
20170329030240
/
pushlog to previous
nightly win32
55.0a1
/
20170329030240
/
pushlog to previous
nightly win64
55.0a1
/
20170329030240
/
pushlog to previous
|
layout/svg/SVGContextPaint.h | file | annotate | diff | comparison | revisions | |
layout/xul/nsImageBoxFrame.cpp | file | annotate | diff | comparison | revisions | |
toolkit/themes/osx/global/scale.css | file | annotate | diff | comparison | revisions | |
toolkit/themes/osx/global/scale/scale-tray-horiz.gif | file | annotate | diff | comparison | revisions | |
toolkit/themes/osx/global/scale/scale-tray-vert.gif | file | annotate | diff | comparison | revisions | |
toolkit/themes/windows/global/scale.css | file | annotate | diff | comparison | revisions | |
toolkit/themes/windows/global/scale/scale-tray-horiz.gif | file | annotate | diff | comparison | revisions | |
toolkit/themes/windows/global/scale/scale-tray-vert.gif | file | annotate | diff | comparison | revisions | |
widget/windows/nsWindow.cpp | file | annotate | diff | comparison | revisions |
--- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -204,16 +204,17 @@ skip-if = true # Needs to be rewritten a [browser_687710_2.js] [browser_694378.js] [browser_701377.js] [browser_705597.js] [browser_707862.js] [browser_739531.js] [browser_739805.js] [browser_819510_perwindowpb.js] +skip-if = (os == 'win' && bits == 64) # Bug 1284312 # Disabled for frequent intermittent failures [browser_464620_a.js] skip-if = true [browser_464620_b.js] skip-if = true # Disabled on OS X:
--- a/browser/modules/SitePermissions.jsm +++ b/browser/modules/SitePermissions.jsm @@ -572,19 +572,26 @@ var gPermissionObject = { } }, "desktop-notification": { exactHostMatch: true, labelID: "desktop-notification2", }, - "camera": {}, - "microphone": {}, + "camera": { + exactHostMatch: true, + }, + + "microphone": { + exactHostMatch: true, + }, + "screen": { + exactHostMatch: true, states: [ SitePermissions.UNKNOWN, SitePermissions.BLOCK ], }, "popup": { getDefault() { return Services.prefs.getBoolPref("dom.disable_open_during_load") ? SitePermissions.BLOCK : SitePermissions.ALLOW; }
--- a/browser/modules/test/unit/test_SitePermissions.js +++ b/browser/modules/test/unit/test_SitePermissions.js @@ -62,8 +62,40 @@ add_task(function* testGetAvailableState [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ]); Assert.deepEqual(SitePermissions.getAvailableStates("popup"), [ SitePermissions.ALLOW, SitePermissions.BLOCK ]); }); + +add_task(function* testExactHostMatch() { + let uri = Services.io.newURI("https://example.com"); + let subUri = Services.io.newURI("https://test1.example.com"); + + let exactHostMatched = ["desktop-notification", "camera", "microphone", "screen", "geo"]; + let nonExactHostMatched = ["image", "cookie", "popup", "install", "indexedDB"]; + + let permissions = SitePermissions.listPermissions(); + for (let permission of permissions) { + SitePermissions.set(uri, permission, SitePermissions.ALLOW); + + if (exactHostMatched.includes(permission)) { + // Check that the sub-origin does not inherit the permission from its parent. + Assert.equal(SitePermissions.get(subUri, permission).state, SitePermissions.UNKNOWN); + } else if (nonExactHostMatched.includes(permission)) { + // Check that the sub-origin does inherit the permission from its parent. + Assert.equal(SitePermissions.get(subUri, permission).state, SitePermissions.ALLOW); + } else { + Assert.ok(false, `Found an unknown permission ${permission} in exact host match test.` + + "Please add new permissions from SitePermissions.jsm to this test."); + } + + // Check that the permission can be made specific to the sub-origin. + SitePermissions.set(subUri, permission, SitePermissions.BLOCK); + Assert.equal(SitePermissions.get(subUri, permission).state, SitePermissions.BLOCK); + Assert.equal(SitePermissions.get(uri, permission).state, SitePermissions.ALLOW); + + SitePermissions.remove(subUri, permission); + SitePermissions.remove(uri, permission); + } +});
--- a/devtools/client/.eslintrc.js +++ b/devtools/client/.eslintrc.js @@ -1,9 +1,12 @@ "use strict"; module.exports = { + "globals": { + "define": true, + }, "rules": { // See bug 1288147, the devtools front-end wants to be able to run in // content privileged windows, where ownerGlobal doesn't exist. "mozilla/use-ownerGlobal": "off", } };
--- a/devtools/client/inspector/boxmodel/components/BoxModelMain.js +++ b/devtools/client/inspector/boxmodel/components/BoxModelMain.js @@ -75,31 +75,51 @@ module.exports = createClass({ }, getMarginValue(property, direction) { let { layout } = this.props.boxModel; let autoMargins = layout.autoMargins || {}; let value = "-"; if (direction in autoMargins) { - value = "auto"; + value = autoMargins[direction]; } else if (layout[property]) { - value = parseFloat(layout[property]); + let parsedValue = parseFloat(layout[property]); + + if (Number.isNaN(parsedValue)) { + // Not a number. We use the raw string. + // Useful for pseudo-elements with auto margins since they + // don't appear in autoMargins. + value = layout[property]; + } else { + value = parsedValue; + } } return value; }, getPositionValue(property) { let { layout } = this.props.boxModel; + let value = "-"; - if (layout.position === "static") { - return "-"; + if (!layout[property]) { + return value; } - return layout[property] ? parseFloat(layout[property]) : "-"; + + let parsedValue = parseFloat(layout[property]); + + if (Number.isNaN(parsedValue)) { + // Not a number. We use the raw string. + value = layout[property]; + } else { + value = parsedValue; + } + + return value; }, /** * While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92, * translate nodeFront to a grip-like object that can be used with an ElementNode rep. * * @params {NodeFront} nodeFront * The NodeFront for which we want to create a grip-like object.
--- a/devtools/client/inspector/boxmodel/test/browser.ini +++ b/devtools/client/inspector/boxmodel/test/browser.ini @@ -18,17 +18,19 @@ support-files = # Disabled for too many intermittent failures (bug 1009322) [browser_boxmodel_editablemodel_bluronclick.js] [browser_boxmodel_editablemodel_border.js] [browser_boxmodel_editablemodel_stylerules.js] [browser_boxmodel_guides.js] [browser_boxmodel_navigation.js] skip-if = true # Bug 1336198 [browser_boxmodel_offsetparent.js] +[browser_boxmodel_positions.js] [browser_boxmodel_properties.js] +[browser_boxmodel_pseudo-element.js] [browser_boxmodel_rotate-labels-on-sides.js] [browser_boxmodel_sync.js] [browser_boxmodel_tooltips.js] skip-if = true # Bug 1336198 [browser_boxmodel_update-after-navigation.js] [browser_boxmodel_update-after-reload.js] # [browser_boxmodel_update-in-iframes.js] # Bug 1020038 boxmodel-view updates for iframe elements changes
--- a/devtools/client/inspector/boxmodel/test/browser_boxmodel.js +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel.js @@ -17,16 +17,24 @@ var res1 = [ selector: ".boxmodel-size > .boxmodel-width", value: "100" }, { selector: ".boxmodel-size > .boxmodel-height", value: "100.117" }, { + selector: ".boxmodel-position.boxmodel-top > span", + value: 42 + }, + { + selector: ".boxmodel-position.boxmodel-left > span", + value: 42 + }, + { selector: ".boxmodel-margin.boxmodel-top > span", value: 30 }, { selector: ".boxmodel-margin.boxmodel-left > span", value: "auto" }, { @@ -80,16 +88,24 @@ var res2 = [ selector: ".boxmodel-size > .boxmodel-width", value: "100" }, { selector: ".boxmodel-size > .boxmodel-height", value: "150" }, { + selector: ".boxmodel-position.boxmodel-top > span", + value: 50 + }, + { + selector: ".boxmodel-position.boxmodel-left > span", + value: 42 + }, + { selector: ".boxmodel-margin.boxmodel-top > span", value: 30 }, { selector: ".boxmodel-margin.boxmodel-left > span", value: "auto" }, { @@ -160,17 +176,17 @@ function* testInitialValues(inspector, v } function* testChangingValues(inspector, view, testActor) { info("Test that changing the document updates the box model"); let viewdoc = view.document; let onUpdated = waitForUpdate(inspector); yield testActor.setAttribute("div", "style", - "height:150px;padding-right:50px;"); + "height:150px;padding-right:50px;top:50px"); yield onUpdated; for (let i = 0; i < res2.length; i++) { let elt = viewdoc.querySelector(res2[i].selector); is(elt.textContent, res2[i].value, res2[i].selector + " has the right value after style update."); } }
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_positions.js @@ -0,0 +1,65 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model displays the right values for positions. + +const TEST_URI = ` + <style type='text/css'> + div { + position: absolute; + left: 0; + margin: 0; + padding: 0; + display: none; + height: 100px; + width: 100px; + border: 10px solid black; + } + </style> + <div>Test Node</div> +`; + +// Expected values: +const res1 = [ + { + selector: ".boxmodel-position.boxmodel-top > span", + value: "auto" + }, + { + selector: ".boxmodel-position.boxmodel-right > span", + value: "auto" + }, + { + selector: ".boxmodel-position.boxmodel-bottom > span", + value: "auto" + }, + { + selector: ".boxmodel-position.boxmodel-left > span", + value: 0 + }, +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openBoxModelView(); + let node = yield getNodeFront("div", inspector); + let children = yield inspector.markup.walker.children(node); + let beforeElement = children.nodes[0]; + + yield selectNode(beforeElement, inspector); + yield testPositionValues(inspector, view); +}); + +function* testPositionValues(inspector, view) { + info("Test that the position values of the box model are correct"); + let viewdoc = view.document; + + for (let i = 0; i < res1.length; i++) { + let elt = viewdoc.querySelector(res1[i].selector); + is(elt.textContent, res1[i].value, + res1[i].selector + " has the right value."); + } +}
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js @@ -0,0 +1,120 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model displays the right values for a pseduo-element. + +const TEST_URI = ` + <style type='text/css'> + div { + box-sizing: border-box; + display: block; + float: left; + line-height: 20px; + position: relative; + z-index: 2; + height: 100px; + width: 100px; + border: 10px solid black; + padding: 20px; + margin: 30px auto; + } + + div::before { + content: 'before'; + display: block; + width: 32px; + height: 32px; + margin: 0 auto 6px; + } + </style> + <div>Test Node</div> +`; + +// Expected values: +const res1 = [ + { + selector: ".boxmodel-element-size", + value: "32" + "\u00D7" + "32" + }, + { + selector: ".boxmodel-size > .boxmodel-width", + value: "32" + }, + { + selector: ".boxmodel-size > .boxmodel-height", + value: "32" + }, + { + selector: ".boxmodel-margin.boxmodel-top > span", + value: 0 + }, + { + selector: ".boxmodel-margin.boxmodel-left > span", + value: "auto" + }, + { + selector: ".boxmodel-margin.boxmodel-bottom > span", + value: 6 + }, + { + selector: ".boxmodel-margin.boxmodel-right > span", + value: "auto" + }, + { + selector: ".boxmodel-padding.boxmodel-top > span", + value: 0 + }, + { + selector: ".boxmodel-padding.boxmodel-left > span", + value: 0 + }, + { + selector: ".boxmodel-padding.boxmodel-bottom > span", + value: 0 + }, + { + selector: ".boxmodel-padding.boxmodel-right > span", + value: 0 + }, + { + selector: ".boxmodel-border.boxmodel-top > span", + value: 0 + }, + { + selector: ".boxmodel-border.boxmodel-left > span", + value: 0 + }, + { + selector: ".boxmodel-border.boxmodel-bottom > span", + value: 0 + }, + { + selector: ".boxmodel-border.boxmodel-right > span", + value: 0 + }, +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openBoxModelView(); + let node = yield getNodeFront("div", inspector); + let children = yield inspector.markup.walker.children(node); + let beforeElement = children.nodes[0]; + + yield selectNode(beforeElement, inspector); + yield testInitialValues(inspector, view); +}); + +function* testInitialValues(inspector, view) { + info("Test that the initial values of the box model are correct"); + let viewdoc = view.document; + + for (let i = 0; i < res1.length; i++) { + let elt = viewdoc.querySelector(res1[i].selector); + is(elt.textContent, res1[i].value, + res1[i].selector + " has the right value."); + } +}
--- a/devtools/client/inspector/grids/components/GridOutline.js +++ b/devtools/client/inspector/grids/components/GridOutline.js @@ -10,48 +10,58 @@ const { throttle } = require("devtools/c const Types = require("../types"); // The delay prior to executing the grid cell highlighting. const GRID_CELL_MOUSEOVER_TIMEOUT = 150; // Move SVG grid to the right 100 units, so that it is not flushed against the edge of // layout border -const TRANSLATE_X = -100; +const TRANSLATE_X = 0; const TRANSLATE_Y = 0; -const VIEWPORT_HEIGHT = 100; -const VIEWPORT_WIDTH = 450; +const GRID_CELL_SCALE_FACTOR = 50; + +const VIEWPORT_MIN_HEIGHT = 100; +const VIEWPORT_MAX_HEIGHT = 150; module.exports = createClass({ displayName: "GridOutline", propTypes: { grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, onShowGridAreaHighlight: PropTypes.func.isRequired, onShowGridCellHighlight: PropTypes.func.isRequired, }, mixins: [ addons.PureRenderMixin ], getInitialState() { return { selectedGrids: [], + height: 0, + width: 0, }; }, componentWillMount() { // Throttle the grid highlighting of grid cells. It makes the UX smoother by not // lagging the grid cell highlighting if a lot of grid cells are mouseover in a // quick succession. this.highlightCell = throttle(this.highlightCell, GRID_CELL_MOUSEOVER_TIMEOUT); }, componentWillReceiveProps({ grids }) { + if (this.state.selectedGrids.length < 2) { + this.setState({ + height: 0, + width: 0, + }); + } this.setState({ selectedGrids: grids.filter(grid => grid.highlighted), }); }, /** * Returns the grid area name if the given grid cell is part of a grid area, otherwise * null. @@ -72,16 +82,33 @@ module.exports = createClass({ if (!gridArea) { return null; } return gridArea.name; }, + /** + * Returns the height of the grid outline ranging between a minimum and maximum height. + * + * @return {Number} The height of the grid outline. + */ + getHeight() { + const { height } = this.state; + + if (height >= VIEWPORT_MAX_HEIGHT) { + return VIEWPORT_MAX_HEIGHT; + } else if (height <= VIEWPORT_MIN_HEIGHT) { + return VIEWPORT_MIN_HEIGHT; + } + + return height; + }, + highlightCell({ target }) { const { grids, onShowGridAreaHighlight, onShowGridCellHighlight, } = this.props; const name = target.getAttribute("data-grid-area-name"); const id = target.getAttribute("data-grid-id"); @@ -92,61 +119,82 @@ module.exports = createClass({ target.setAttribute("fill", color); if (name) { onShowGridAreaHighlight(grids[id].nodeFront, name, color); } if (fragmentIndex && rowNumber && columnNumber) { - onShowGridCellHighlight(grids[id].nodeFront, fragmentIndex, + onShowGridCellHighlight(grids[id].nodeFront, color, fragmentIndex, rowNumber, columnNumber); } }, /** - * Renders the grid outline for the given grid container object. - * - * @param {Object} grid - * A single grid container in the document. - */ + * Renders the grid outline for the given grid container object. + * + * @param {Object} grid + * A single grid container in the document. + */ renderGrid(grid) { const { id, color, gridFragments } = grid; // TODO: We are drawing the first fragment since only one is currently being stored. // In the future we will need to iterate over all fragments of a grid. let gridFragmentIndex = 0; const { rows, cols, areas } = gridFragments[gridFragmentIndex]; const numberOfColumns = cols.lines.length - 1; const numberOfRows = rows.lines.length - 1; const rectangles = []; - - // Draw a rectangle that acts as a border for the grid outline. - const border = this.renderGridOutlineBorder(numberOfRows, numberOfColumns, color); - rectangles.push(border); - let x = 1; let y = 1; - const width = 10; - const height = 10; + let width = 0; + let height = 0; + // The grid outline border height/width is the total height/width of grid cells drawn. + let totalHeight = 0; + let totalWidth = 0; - // Draw the cells within the grid outline border. + // Draw the cells contained within the grid outline border. for (let rowNumber = 1; rowNumber <= numberOfRows; rowNumber++) { + height = GRID_CELL_SCALE_FACTOR * (rows.tracks[rowNumber - 1].breadth / 100); for (let columnNumber = 1; columnNumber <= numberOfColumns; columnNumber++) { + width = GRID_CELL_SCALE_FACTOR * (cols.tracks[columnNumber - 1].breadth / 100); + const gridAreaName = this.getGridAreaName(columnNumber, rowNumber, areas); - const gridCell = this.renderGridCell(id, gridFragmentIndex, x, y, rowNumber, - columnNumber, color, gridAreaName); + const gridCell = this.renderGridCell(id, gridFragmentIndex, x, y, + rowNumber, columnNumber, color, gridAreaName, width, height); rectangles.push(gridCell); x += width; } x = 1; y += height; + totalHeight += height; } + // Find the total width of the grid container so we can draw the border for it + for (let columnNumber = 0; columnNumber < numberOfColumns; columnNumber++) { + totalWidth += GRID_CELL_SCALE_FACTOR * (cols.tracks[columnNumber].breadth / 100); + } + + // 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. + if (totalHeight > this.state.height || totalWidth > this.state.width) { + this.setState({ + height: totalHeight + 20, + width: totalWidth, + }); + } + + // Draw a rectangle that acts as the grid outline border. + const border = this.renderGridOutlineBorder(totalWidth, totalHeight, color); + rectangles.unshift(border); + return rectangles; }, /** * Renders the grid cell of a grid fragment. * * @param {Number} id * The grid id stored on the grid fragment @@ -157,31 +205,35 @@ module.exports = createClass({ * @param {Number} y * The y-position of the grid cell. * @param {Number} rowNumber * The row number of the grid cell. * @param {Number} columnNumber * The column number of the grid cell. * @param {String|null} gridAreaName * The grid area name or null if the grid cell is not part of a grid area. + * @param {Number} width + * The width of grid cell. + * @param {Number} height + * The height of the grid cell. */ renderGridCell(id, gridFragmentIndex, x, y, rowNumber, columnNumber, color, - gridAreaName) { + gridAreaName, width, height) { return dom.rect( { className: "grid-outline-cell", "data-grid-area-name": gridAreaName, "data-grid-fragment-index": gridFragmentIndex, "data-grid-id": id, "data-grid-row": rowNumber, "data-grid-column": columnNumber, x, y, - width: 10, - height: 10, + width, + height, fill: "none", stroke: color, onMouseOver: this.onMouseOverCell, onMouseOut: this.onMouseLeaveCell, } ); }, @@ -189,24 +241,24 @@ module.exports = createClass({ return dom.g( { className: "grid-cell-group", }, grids.map(grid => this.renderGrid(grid)) ); }, - renderGridOutlineBorder(numberOfRows, numberOfColumns, color) { + renderGridOutlineBorder(borderWidth, borderHeight, color) { return dom.rect( { className: "grid-outline-border", x: 1, y: 1, - width: numberOfColumns * 10, - height: numberOfRows * 10, + width: borderWidth, + height: borderHeight, stroke: color, } ); }, onMouseLeaveCell({ target }) { const { grids, @@ -214,32 +266,34 @@ module.exports = createClass({ onShowGridCellHighlight, } = this.props; const id = target.getAttribute("data-grid-id"); const color = target.getAttribute("stroke"); target.setAttribute("fill", "none"); onShowGridAreaHighlight(grids[id].nodeFront, null, color); - onShowGridCellHighlight(grids[id].nodeFront); + onShowGridCellHighlight(grids[id].nodeFront, color); }, onMouseOverCell(event) { event.persist(); this.highlightCell(event); }, render() { - return this.state.selectedGrids.length ? + const { selectedGrids, height, width } = this.state; + + return selectedGrids.length ? dom.svg( { className: "grid-outline", width: "100%", - height: 100, - viewBox: `${TRANSLATE_X} ${TRANSLATE_Y} ${VIEWPORT_WIDTH} ${VIEWPORT_HEIGHT}`, + height: this.getHeight(), + viewBox: `${TRANSLATE_X} ${TRANSLATE_Y} ${width} ${height}`, }, - this.renderGridOutline(this.state.selectedGrids) + this.renderGridOutline(selectedGrids) ) : null; }, });
--- a/devtools/client/inspector/grids/grid-inspector.js +++ b/devtools/client/inspector/grids/grid-inspector.js @@ -340,29 +340,34 @@ GridInspector.prototype = { }, /** * Highlights the grid cell in the CSS Grid Highlighter for the given grid. * * @param {NodeFront} node * The NodeFront of the grid container element for which the grid * highlighter is highlighted for. + * @param {String} color + * The color of the grid cell for which the grid highlighter + * is highlighted for. * @param {Number|null} gridFragmentIndex * The index of the grid fragment for which the grid highlighter * is highlighted for. * @param {Number|null} rowNumber * The row number of the grid cell for which the grid highlighter * is highlighted for. * @param {Number|null} columnNumber * The column number of the grid cell for which the grid highlighter * is highlighted for. */ - onShowGridCellHighlight(node, gridFragmentIndex, rowNumber, columnNumber) { + onShowGridCellHighlight(node, color, gridFragmentIndex, rowNumber, columnNumber) { let { highlighterSettings } = this.store.getState(); + highlighterSettings.showGridCell = { gridFragmentIndex, rowNumber, columnNumber }; + highlighterSettings.color = color; this.highlighters.showGridHighlighter(node, highlighterSettings); }, /** * Handler for the inspector sidebar select event. Starts listening for * "grid-layout-changed" if the layout panel is visible. Otherwise, stop * listening for grid layout changes. Finally, refresh the layout view if
--- a/devtools/client/jsonview/main.js +++ b/devtools/client/jsonview/main.js @@ -46,17 +46,15 @@ var JsonView = { // Message handlers for events from child processes /** * Save JSON to a file needs to be implemented here * in the parent process. */ onSave: function (message) { JsonViewUtils.getTargetFile().then(file => { - if (file) { - JsonViewUtils.saveToFile(file, message.data); - } - }); + JsonViewUtils.saveToFile(file, message.data); + }, () => {}); } }; // Exports from this module module.exports.JsonView = JsonView;
--- a/devtools/client/jsonview/utils.js +++ b/devtools/client/jsonview/utils.js @@ -14,35 +14,46 @@ const OPEN_FLAGS = { RDONLY: parseInt("0x01", 16), WRONLY: parseInt("0x02", 16), CREATE_FILE: parseInt("0x08", 16), APPEND: parseInt("0x10", 16), TRUNCATE: parseInt("0x20", 16), EXCL: parseInt("0x80", 16) }; +let filePickerShown = false; + /** * Open File Save As dialog and let the user to pick proper file location. */ exports.getTargetFile = function () { - return new Promise(resolve => { + return new Promise((resolve, reject) => { + if (filePickerShown) { + reject(null); + return; + } + + filePickerShown = true; + let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); let win = getMostRecentBrowserWindow(); fp.init(win, null, Ci.nsIFilePicker.modeSave); fp.appendFilter("JSON Files", "*.json; *.jsonp;"); fp.appendFilters(Ci.nsIFilePicker.filterText); fp.appendFilters(Ci.nsIFilePicker.filterAll); fp.filterIndex = 0; fp.open(rv => { + filePickerShown = false; + if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) { resolve(fp.file); } else { - resolve(null); + reject(null); } }); }); }; /** * Save JSON to a file */
--- a/devtools/client/shared/components/tree/label-cell.js +++ b/devtools/client/shared/components/tree/label-cell.js @@ -18,20 +18,22 @@ define(function (require, exports, modul * Render the default cell used for toggle buttons */ let LabelCell = React.createClass({ displayName: "LabelCell", // See the TreeView component for details related // to the 'member' object. propTypes: { + id: PropTypes.string.isRequired, member: PropTypes.object.isRequired }, render: function () { + let id = this.props.id; let member = this.props.member; let level = member.level || 0; // Compute indentation dynamically. The deeper the item is // inside the hierarchy, the bigger is the left padding. let rowStyle = { "paddingInlineStart": (level * 16) + "px", }; @@ -45,20 +47,25 @@ define(function (require, exports, modul if (member.open) { iconClassList.push("open"); } return ( td({ className: "treeLabelCell", key: "default", - style: rowStyle}, - span({ className: iconClassList.join(" ") }), + style: rowStyle, + role: "presentation"}, + span({ + className: iconClassList.join(" "), + role: "presentation" + }), span({ className: "treeLabel " + member.type + "Label", + "aria-labelledby": id, "data-level": level }, member.name) ) ); } }); // Exports from this module
--- a/devtools/client/shared/components/tree/tree-cell.js +++ b/devtools/client/shared/components/tree/tree-cell.js @@ -100,28 +100,33 @@ define(function (require, exports, modul let cellElement; if (enableInput && this.state.inputEnabled && type !== "object") { classNames.push("inputEnabled"); cellElement = input({ autoFocus: true, onBlur: this.updateInputEnabled, readOnly: true, value, + "aria-labelledby": id }); } else { cellElement = span({ onClick: (type !== "object") ? this.updateInputEnabled : null, + "aria-labelledby": id }, renderValue(props) ); } // Render me! return ( - td({ className: classNames.join(" ") }, + td({ + className: classNames.join(" "), + role: "presentation" + }, cellElement ) ); } }); // Default value rendering. let defaultRenderValue = props => {
--- a/devtools/client/shared/components/tree/tree-header.js +++ b/devtools/client/shared/components/tree/tree-header.js @@ -74,27 +74,32 @@ define(function (require, exports, modul classNames = this.getHeaderClass(col.id); classNames.push("treeHeaderCell"); } cells.push( td({ className: classNames.join(" "), style: cellStyle, + role: "presentation", + id: col.id, key: col.id}, visible ? div({ className: "treeHeaderCellBox"}, col.title ) : null, ) ); }); return ( - thead({}, tr({ className: visible ? "treeHeaderRow" : "" }, - cells - )) + thead({ + role: "presentation" + }, tr({ + className: visible ? "treeHeaderRow" : "", + role: "presentation" + }, cells)) ); } }); // Exports from this module module.exports = TreeHeader; });
--- a/devtools/client/shared/components/tree/tree-row.js +++ b/devtools/client/shared/components/tree/tree-row.js @@ -10,16 +10,19 @@ define(function (require, exports, modul // ReactJS const React = require("devtools/client/shared/vendor/react"); const ReactDOM = require("devtools/client/shared/vendor/react-dom"); // Tree const TreeCell = React.createFactory(require("./tree-cell")); const LabelCell = React.createFactory(require("./label-cell")); + // Scroll + const { scrollIntoViewIfNeeded } = require("devtools/client/shared/scroll"); + // Shortcuts const { tr } = React.DOM; const PropTypes = React.PropTypes; /** * This template represents a node in TreeView component. It's rendered * using <tr> element (the entire tree is one big <table>). */ @@ -35,23 +38,27 @@ define(function (require, exports, modul type: PropTypes.string.isRequired, rowClass: PropTypes.string.isRequired, level: PropTypes.number.isRequired, hasChildren: PropTypes.bool, value: PropTypes.any, open: PropTypes.bool.isRequired, path: PropTypes.string.isRequired, hidden: PropTypes.bool, + selected: PropTypes.bool, }), decorator: PropTypes.object, renderCell: PropTypes.object, renderLabelCell: PropTypes.object, columns: PropTypes.array.isRequired, + id: PropTypes.string.isRequired, provider: PropTypes.object.isRequired, - onClick: PropTypes.func.isRequired + onClick: PropTypes.func.isRequired, + onMouseOver: PropTypes.func, + onMouseOut: PropTypes.func }, componentWillReceiveProps(nextProps) { // I don't like accessing the underlying DOM elements directly, // but this optimization makes the filtering so damn fast! // The row doesn't have to be re-rendered, all we really need // to do is toggling a class name. // The important part is that DOM elements don't need to be @@ -62,26 +69,37 @@ define(function (require, exports, modul } }, /** * Optimize row rendering. If props are the same do not render. * This makes the rendering a lot faster! */ shouldComponentUpdate: function (nextProps) { - let props = ["name", "open", "value", "loading"]; + let props = ["name", "open", "value", "loading", "selected", "hasChildren"]; for (let p in props) { if (nextProps.member[props[p]] != this.props.member[props[p]]) { return true; } } return false; }, + componentDidUpdate: function () { + if (this.props.member.selected) { + let row = ReactDOM.findDOMNode(this); + // Because this is called asynchronously, context window might be + // already gone. + if (row.ownerDocument.defaultView) { + scrollIntoViewIfNeeded(row); + } + } + }, + getRowClass: function (object) { let decorator = this.props.decorator; if (!decorator || !decorator.getRowClass) { return []; } // Decorator can return a simple string or array of strings. let classNames = decorator.getRowClass(object); @@ -94,82 +112,95 @@ define(function (require, exports, modul } return classNames; }, render: function () { let member = this.props.member; let decorator = this.props.decorator; + let props = { + id: this.props.id, + role: "treeitem", + "aria-level": member.level, + "aria-selected": !!member.selected, + onClick: this.props.onClick, + onMouseOver: this.props.onMouseOver, + onMouseOut: this.props.onMouseOut + }; // Compute class name list for the <tr> element. let classNames = this.getRowClass(member.object) || []; classNames.push("treeRow"); classNames.push(member.type + "Row"); if (member.hasChildren) { classNames.push("hasChildren"); + props["aria-expanded"] = false; } if (member.open) { classNames.push("opened"); + props["aria-expanded"] = true; } if (member.loading) { classNames.push("loading"); } + if (member.selected) { + classNames.push("selected"); + } + if (member.hidden) { classNames.push("hidden"); } + props.className = classNames.join(" "); + // The label column (with toggle buttons) is usually // the first one, but there might be cases (like in // the Memory panel) where the toggling is done // in the last column. let cells = []; // Get components for rendering cells. let renderCell = this.props.renderCell || RenderCell; let renderLabelCell = this.props.renderLabelCell || RenderLabelCell; if (decorator && decorator.renderLabelCell) { renderLabelCell = decorator.renderLabelCell(member.object) || renderLabelCell; } // Render a cell for every column. this.props.columns.forEach(col => { - let props = Object.assign({}, this.props, { + let cellProps = Object.assign({}, this.props, { key: col.id, id: col.id, value: this.props.provider.getValue(member.object, col.id) }); if (decorator && decorator.renderCell) { renderCell = decorator.renderCell(member.object, col.id); } let render = (col.id == "default") ? renderLabelCell : renderCell; // Some cells don't have to be rendered. This happens when some // other cells span more columns. Note that the label cells contains // toggle buttons and should be usually there unless we are rendering // a simple non-expandable table. if (render) { - cells.push(render(props)); + cells.push(render(cellProps)); } }); // Render tree row return ( - tr({ - className: classNames.join(" "), - onClick: this.props.onClick}, - cells - ) + tr(props, cells) ); } }); // Helpers let RenderCell = props => { return TreeCell(props);
--- a/devtools/client/shared/components/tree/tree-view.css +++ b/devtools/client/shared/components/tree/tree-view.css @@ -67,16 +67,25 @@ } .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover { cursor: pointer; color: var(--tree-link-color); text-decoration: underline; } +.treeTable .treeRow.selected { + background-color: var(--theme-selection-background); +} + +.treeTable .treeRow.selected:not(:hover) *, +.treeTable .treeRow.selected:not(:hover) .treeLabelCell::after { + color: var(--theme-selection-color); +} + /* Filtering */ .treeTable .treeRow.hidden { display: none; } .treeTable .treeValueCellDivider { display: flex; flex-wrap: wrap;
--- a/devtools/client/shared/components/tree/tree-view.js +++ b/devtools/client/shared/components/tree/tree-view.js @@ -57,16 +57,17 @@ define(function (require, exports, modul displayName: "TreeView", // The only required property (not set by default) is the input data // object that is used to puputate the tree. propTypes: { // The input data object. object: PropTypes.any, className: PropTypes.string, + label: PropTypes.string, // Data provider (see also the interface above) provider: PropTypes.shape({ getChildren: PropTypes.func, hasChildren: PropTypes.func, getLabel: PropTypes.func, getValue: PropTypes.func, getKey: PropTypes.func, getType: PropTypes.func, @@ -116,27 +117,38 @@ define(function (require, exports, modul expandableStrings: true, columns: [] }; }, getInitialState: function () { return { expandedNodes: this.props.expandedNodes, - columns: ensureDefaultColumn(this.props.columns) + columns: ensureDefaultColumn(this.props.columns), + selected: null }; }, componentWillReceiveProps: function (nextProps) { let { expandedNodes } = nextProps; this.setState(Object.assign({}, this.state, { expandedNodes, })); }, + componentDidUpdate: function () { + let selected = this.getSelectedRow(this.rows); + if (!selected && this.rows.length > 0) { + // TODO: Do better than just selecting the first row again. We want to + // select (in order) previous, next or parent in case when selected + // row is removed. + this.selectRow(this.rows[0].props.member.path); + } + }, + // Node expand/collapse toggle: function (nodePath) { let nodes = this.state.expandedNodes; if (this.isExpanded(nodePath)) { nodes.delete(nodePath); } else { nodes.add(nodePath); @@ -149,19 +161,82 @@ define(function (require, exports, modul }, isExpanded: function (nodePath) { return this.state.expandedNodes.has(nodePath); }, // Event Handlers + onKeyDown: function (event) { + if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes( + event.key)) { + event.preventDefault(); + } + }, + + onKeyUp: function (event) { + let row = this.getSelectedRow(this.rows); + if (!row) { + return; + } + + let index = this.rows.indexOf(row); + switch (event.key) { + case "ArrowRight": + let { hasChildren, open } = row.props.member; + if (hasChildren && !open) { + this.toggle(this.state.selected); + } + break; + case "ArrowLeft": + if (row && row.props.member.open) { + this.toggle(this.state.selected); + } + break; + case "ArrowDown": + let nextRow = this.rows[index + 1]; + if (nextRow) { + this.selectRow(nextRow.props.member.path); + } + break; + case "ArrowUp": + let previousRow = this.rows[index - 1]; + if (previousRow) { + this.selectRow(previousRow.props.member.path); + } + break; + default: + return; + } + + event.preventDefault(); + }, + onClickRow: function (nodePath, event) { event.stopPropagation(); this.toggle(nodePath); + this.selectRow(nodePath); + }, + + getSelectedRow: function (rows) { + if (!this.state.selected || rows.length === 0) { + return null; + } + return rows.find(row => this.isSelected(row.props.member.path)); + }, + + selectRow: function (nodePath) { + this.setState(Object.assign({}, this.state, { + selected: nodePath + })); + }, + + isSelected: function (nodePath) { + return nodePath === this.state.selected; }, // Filtering & Sorting /** * Filter out nodes that don't correspond to the current filter. * @return {Boolean} true if the node should be visible otherwise false. */ @@ -232,17 +307,19 @@ define(function (require, exports, modul hasChildren: hasChildren, // Value associated with this node (as provided by the data provider) value: value, // True if the node is expanded. open: this.isExpanded(nodePath), // Node path path: nodePath, // True if the node is hidden (used for filtering) - hidden: !this.onFilter(child) + hidden: !this.onFilter(child), + // True if the node is selected with keyboard + selected: this.isSelected(nodePath) }; }); }, /** * Render tree rows/nodes. */ renderRows: function (parent, level = 0, path = "") { @@ -263,16 +340,18 @@ define(function (require, exports, modul if (decorator && decorator.renderRow) { renderRow = decorator.renderRow(member.object) || renderRow; } let props = Object.assign({}, this.props, { key: member.path, member: member, columns: this.state.columns, + id: member.path, + ref: row => row && this.rows.push(row), onClick: this.onClickRow.bind(this, member.path) }); // Render single row. rows.push(renderRow(props)); // If a child node is expanded render its rows too. if (member.hasChildren && member.open) { @@ -293,16 +372,17 @@ define(function (require, exports, modul }); return rows; }, render: function () { let root = this.props.object; let classNames = ["treeTable"]; + this.rows = []; // Use custom class name from props. let className = this.props.className; if (className) { classNames.push(...className.split(" ")); } // Alright, let's render all tree rows. The tree is one big <table>. @@ -317,22 +397,28 @@ define(function (require, exports, modul let props = Object.assign({}, this.props, { columns: this.state.columns }); return ( DOM.table({ className: classNames.join(" "), + role: "tree", + tabIndex: 0, + onKeyDown: this.onKeyDown, + onKeyUp: this.onKeyUp, + "aria-label": this.props.label || "", + "aria-activedescendant": this.state.selected, cellPadding: 0, cellSpacing: 0}, TreeHeader(props), - DOM.tbody({}, - rows - ) + DOM.tbody({ + role: "presentation" + }, rows) ) ); } }); // Helpers /**
--- a/devtools/client/shared/scroll.js +++ b/devtools/client/shared/scroll.js @@ -1,52 +1,56 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -/** - * Scroll the document so that the element "elem" appears in the viewport. - * - * @param {DOMNode} elem - * The element that needs to appear in the viewport. - * @param {Boolean} centered - * true if you want it centered, false if you want it to appear on the - * top of the viewport. It is true by default, and that is usually what - * you want. - */ -function scrollIntoViewIfNeeded(elem, centered = true) { - let win = elem.ownerDocument.defaultView; - let clientRect = elem.getBoundingClientRect(); +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + /** + * Scroll the document so that the element "elem" appears in the viewport. + * + * @param {DOMNode} elem + * The element that needs to appear in the viewport. + * @param {Boolean} centered + * true if you want it centered, false if you want it to appear on the + * top of the viewport. It is true by default, and that is usually what + * you want. + */ + function scrollIntoViewIfNeeded(elem, centered = true) { + let win = elem.ownerDocument.defaultView; + let clientRect = elem.getBoundingClientRect(); - // The following are always from the {top, bottom} - // of the viewport, to the {top, …} of the box. - // Think of them as geometrical vectors, it helps. - // The origin is at the top left. + // The following are always from the {top, bottom} + // of the viewport, to the {top, …} of the box. + // Think of them as geometrical vectors, it helps. + // The origin is at the top left. - let topToBottom = clientRect.bottom; - let bottomToTop = clientRect.top - win.innerHeight; - // We allow one translation on the y axis. - let yAllowed = true; + let topToBottom = clientRect.bottom; + let bottomToTop = clientRect.top - win.innerHeight; + // We allow one translation on the y axis. + let yAllowed = true; - // Whatever `centered` is, the behavior is the same if the box is - // (even partially) visible. - if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) { - win.scrollBy(0, topToBottom - elem.offsetHeight); - yAllowed = false; - } else if ((bottomToTop < 0 || !centered) && - bottomToTop >= -elem.offsetHeight) { - win.scrollBy(0, bottomToTop + elem.offsetHeight); - yAllowed = false; - } + // Whatever `centered` is, the behavior is the same if the box is + // (even partially) visible. + if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) { + win.scrollBy(0, topToBottom - elem.offsetHeight); + yAllowed = false; + } else if ((bottomToTop < 0 || !centered) && + bottomToTop >= -elem.offsetHeight) { + win.scrollBy(0, bottomToTop + elem.offsetHeight); + yAllowed = false; + } - // If we want it centered, and the box is completely hidden, - // then we center it explicitly. - if (centered) { - if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) { - win.scroll(win.scrollX, - win.scrollY + clientRect.top - - (win.innerHeight - elem.offsetHeight) / 2); + // If we want it centered, and the box is completely hidden, + // then we center it explicitly. + if (centered) { + if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) { + win.scroll(win.scrollX, + win.scrollY + clientRect.top + - (win.innerHeight - elem.offsetHeight) / 2); + } } } -} -exports.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded; + // Exports from this module + module.exports.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded; +});
--- a/devtools/client/themes/layout.css +++ b/devtools/client/themes/layout.css @@ -58,23 +58,25 @@ * Grid Outline */ #grid-outline { margin: 5px auto; } .grid-outline-border { + fill: none; stroke-width: 0.75; - fill: none; + vector-effect: non-scaling-stroke; } .grid-outline-cell { pointer-events: all; stroke-dasharray: 0.5, 2; + vector-effect: non-scaling-stroke; } .grid-outline-cell:hover { opacity: 0.45; } /** * Container when no grids are present
--- a/dom/file/MutableBlobStorage.cpp +++ b/dom/file/MutableBlobStorage.cpp @@ -3,16 +3,17 @@ /* 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 "MutableBlobStorage.h" #include "MemoryBlobImpl.h" #include "mozilla/CheckedInt.h" #include "mozilla/Preferences.h" +#include "mozilla/TaskQueue.h" #include "File.h" #include "nsAnonymousTemporaryFile.h" #include "nsNetCID.h" #include "nsProxyRelease.h" #include "WorkerPrivate.h" #define BLOB_MEMORY_TEMPORARY_FILE 1048576 @@ -357,17 +358,22 @@ MutableBlobStorage::MutableBlobStorage(M MOZ_ASSERT(NS_IsMainThread()); } MutableBlobStorage::~MutableBlobStorage() { free(mData); if (mFD) { - DispatchToIOThread(new CloseFileRunnable(mFD)); + RefPtr<Runnable> runnable = new CloseFileRunnable(mFD); + DispatchToIOThread(runnable.forget()); + } + + if (mTaskQueue) { + mTaskQueue->BeginShutdown(); } } uint64_t MutableBlobStorage::GetBlobWhenReady(nsISupports* aParent, const nsACString& aContentType, MutableBlobStorageCallback* aCallback) { @@ -378,32 +384,28 @@ MutableBlobStorage::GetBlobWhenReady(nsI MOZ_ASSERT(mStorageState != eClosed); StorageState previousState = mStorageState; mStorageState = eClosed; if (previousState == eInTemporaryFile) { MOZ_ASSERT(mFD); if (NS_FAILED(mErrorResult)) { - NS_DispatchToMainThread( - new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult)); + RefPtr<Runnable> runnable = + new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult); + NS_DispatchToMainThread(runnable.forget()); return 0; } // We want to wait until all the WriteRunnable are completed. The way we do // this is to go to the I/O thread and then we come back: the runnables are // executed in order and this LastRunnable will be... the last one. - nsresult rv = DispatchToIOThread(new LastRunnable(this, aParent, - aContentType, aCallback)); - if (NS_WARN_IF(NS_FAILED(rv))) { - NS_DispatchToMainThread( - new BlobCreationDoneRunnable(this, aCallback, nullptr, rv)); - return 0; - } - + RefPtr<Runnable> runnable = + new LastRunnable(this, aParent, aContentType, aCallback); + DispatchToIOThread(runnable.forget()); return mDataLen; } RefPtr<BlobImpl> blobImpl; if (mData) { blobImpl = new MemoryBlobImpl(mData, mDataLen, NS_ConvertUTF8toUTF16(aContentType)); @@ -453,20 +455,17 @@ MutableBlobStorage::Append(const void* a MOZ_ASSERT(mFD); RefPtr<WriteRunnable> runnable = WriteRunnable::CopyBuffer(this, mFD, aData, aLength); if (NS_WARN_IF(!runnable)) { return NS_ERROR_OUT_OF_MEMORY; } - nsresult rv = DispatchToIOThread(runnable); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } + DispatchToIOThread(runnable.forget()); mDataLen += aLength; return NS_OK; } // By default, we store in memory. uint64_t offset = mDataLen; @@ -527,20 +526,18 @@ MutableBlobStorage::ShouldBeTemporarySto return bufferSize.value() >= Preferences::GetUint("dom.blob.memoryToTemporaryFile", BLOB_MEMORY_TEMPORARY_FILE); } nsresult MutableBlobStorage::MaybeCreateTemporaryFile() { if (XRE_IsParentProcess()) { - nsresult rv = DispatchToIOThread(new CreateTemporaryFileRunnable(this)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } + RefPtr<Runnable> runnable = new CreateTemporaryFileRunnable(this); + DispatchToIOThread(runnable.forget()); } else { RefPtr<MutableBlobStorage> self(this); ContentChild::GetSingleton()-> AsyncOpenAnonymousTemporaryFile([self](PRFileDesc* prfile) { if (prfile) { // The ownership of the prfile is moved to the FileCreatedRunnable. NS_DispatchToMainThread(new FileCreatedRunnable(self, prfile)); } @@ -554,34 +551,31 @@ MutableBlobStorage::MaybeCreateTemporary void MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile || mStorageState == eClosed); if (mStorageState == eClosed) { - DispatchToIOThread(new CloseFileRunnable(aFD)); + RefPtr<Runnable> runnable = new CloseFileRunnable(aFD); + DispatchToIOThread(runnable.forget()); return; } mStorageState = eInTemporaryFile; mFD = aFD; RefPtr<WriteRunnable> runnable = WriteRunnable::AdoptBuffer(this, mFD, mData, mDataLen); MOZ_ASSERT(runnable); mData = nullptr; - nsresult rv = DispatchToIOThread(runnable); - if (NS_WARN_IF(NS_FAILED(rv))) { - mErrorResult = rv; - return; - } + DispatchToIOThread(runnable.forget()); } void MutableBlobStorage::CreateBlobAndRespond(already_AddRefed<nsISupports> aParent, const nsACString& aContentType, already_AddRefed<MutableBlobStorageCallback> aCallback) { MOZ_ASSERT(NS_IsMainThread()); @@ -602,25 +596,25 @@ MutableBlobStorage::CreateBlobAndRespond void MutableBlobStorage::ErrorPropagated(nsresult aRv) { MOZ_ASSERT(NS_IsMainThread()); mErrorResult = aRv; } -/* static */ nsresult -MutableBlobStorage::DispatchToIOThread(Runnable* aRunnable) +void +MutableBlobStorage::DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable) { - nsCOMPtr<nsIEventTarget> target - = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); - MOZ_ASSERT(target); + if (!mTaskQueue) { + nsCOMPtr<nsIEventTarget> target + = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); - nsresult rv = target->Dispatch(aRunnable, NS_DISPATCH_NORMAL); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; + mTaskQueue = new TaskQueue(target.forget()); } - return NS_OK; + nsCOMPtr<nsIRunnable> runnable(aRunnable); + mTaskQueue->Dispatch(runnable.forget()); } } // dom namespace } // mozilla namespace
--- a/dom/file/MutableBlobStorage.h +++ b/dom/file/MutableBlobStorage.h @@ -6,16 +6,19 @@ #ifndef mozilla_dom_MutableBlobStorage_h #define mozilla_dom_MutableBlobStorage_h #include "mozilla/RefPtr.h" #include "prio.h" namespace mozilla { + +class TaskQueue; + namespace dom { class Blob; class BlobImpl; class MutableBlobStorage; class MutableBlobStorageCallback { @@ -62,17 +65,17 @@ private: ~MutableBlobStorage(); bool ExpandBufferSize(uint64_t aSize); bool ShouldBeTemporaryStorage(uint64_t aSize) const; nsresult MaybeCreateTemporaryFile(); - static nsresult DispatchToIOThread(Runnable* aRunnable); + void DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable); // All these variables are touched on the main thread only. void* mData; uint64_t mDataLen; uint64_t mDataBufferLen; enum StorageState { @@ -83,14 +86,16 @@ private: eClosed }; StorageState mStorageState; PRFileDesc* mFD; nsresult mErrorResult; + + RefPtr<TaskQueue> mTaskQueue; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_MutableBlobStorage_h
--- a/dom/media/webaudio/AudioContext.cpp +++ b/dom/media/webaudio/AudioContext.cpp @@ -135,16 +135,17 @@ AudioContext::AudioContext(nsPIDOMWindow , mSampleRate(GetSampleRateForAudioContext(aIsOffline, aSampleRate)) , mAudioContextState(AudioContextState::Suspended) , mNumberOfChannels(aNumberOfChannels) , mIsOffline(aIsOffline) , mIsStarted(!aIsOffline) , mIsShutDown(false) , mCloseCalled(false) , mSuspendCalled(false) + , mIsDisconnecting(false) { bool mute = aWindow->AddAudioContext(this); // Note: AudioDestinationNode needs an AudioContext that must already be // bound to the window. mDestination = new AudioDestinationNode(this, aIsOffline, aChannel, aNumberOfChannels, aLength, aSampleRate); @@ -255,17 +256,19 @@ AudioContext::Constructor(const GlobalOb RegisterWeakMemoryReporter(object); return object.forget(); } bool AudioContext::CheckClosed(ErrorResult& aRv) { - if (mAudioContextState == AudioContextState::Closed) { + if (mAudioContextState == AudioContextState::Closed || + mIsShutDown || + mIsDisconnecting) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return true; } return false; } already_AddRefed<AudioBufferSourceNode> AudioContext::CreateBufferSource(ErrorResult& aRv) @@ -635,32 +638,42 @@ AudioContext::DestinationStream() const double AudioContext::CurrentTime() const { MediaStream* stream = Destination()->Stream(); return stream->StreamTimeToSeconds(stream->GetCurrentTime()); } +void AudioContext::DisconnectFromOwner() +{ + mIsDisconnecting = true; + Shutdown(); + DOMEventTargetHelper::DisconnectFromOwner(); +} + void AudioContext::Shutdown() { mIsShutDown = true; - if (!mIsOffline) { - ErrorResult dummy; - RefPtr<Promise> ignored = Close(dummy); - } + // We don't want to touch promises if the global is going away soon. + if (!mIsDisconnecting) { + if (!mIsOffline) { + IgnoredErrorResult dummy; + RefPtr<Promise> ignored = Close(dummy); + } - for (auto p : mPromiseGripArray) { - p->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + for (auto p : mPromiseGripArray) { + p->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + mPromiseGripArray.Clear(); } - mPromiseGripArray.Clear(); - // Release references to active nodes. // Active AudioNodes don't unregister in destructors, at which point the // Node is already unregistered. mActiveNodes.Clear(); // For offline contexts, we can destroy the MediaStreamGraph at this point. if (mIsOffline && mDestination) { mDestination->OfflineShutdown();
--- a/dom/media/webaudio/AudioContext.h +++ b/dom/media/webaudio/AudioContext.h @@ -137,16 +137,18 @@ public: DOMEventTargetHelper) MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); } + virtual void DisconnectFromOwner() override; + void Shutdown(); // idempotent JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; using DOMEventTargetHelper::DispatchTrustedEvent; // Constructor for regular AudioContext static already_AddRefed<AudioContext> @@ -367,16 +369,17 @@ private: uint32_t mNumberOfChannels; bool mIsOffline; bool mIsStarted; bool mIsShutDown; // Close has been called, reject suspend and resume call. bool mCloseCalled; // Suspend has been called with no following resume. bool mSuspendCalled; + bool mIsDisconnecting; }; static const dom::AudioContext::AudioContextId NO_AUDIO_CONTEXT = 0; } // namespace dom } // namespace mozilla #endif
--- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -169,16 +169,17 @@ tags=capturestream [test_mediaStreamAudioSourceNodeResampling.html] tags=capturestream [test_mixingRules.html] skip-if = toolkit == 'android' # bug 1091965 [test_mozaudiochannel.html] # Android: bug 1061675; OSX 10.6: bug 1097721 skip-if = (toolkit == 'android') || (os == 'mac' && os_version == '10.6') [test_nodeToParamConnection.html] +[test_nodeCreationDocumentGone.html] [test_OfflineAudioContext.html] [test_offlineDestinationChannelCountLess.html] [test_offlineDestinationChannelCountMore.html] [test_oscillatorNode.html] [test_oscillatorNode2.html] [test_oscillatorNodeNegativeFrequency.html] [test_oscillatorNodePassThrough.html] [test_oscillatorNodeStart.html]
new file mode 100644 --- /dev/null +++ b/dom/media/webaudio/test/test_nodeCreationDocumentGone.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can create an AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({ + set: [ + [ "dom.disable_open_during_load", false ] + ] +}).then(function() { + var sub = encodeURI("data:text/html,<!DOCTYPE html>\n"+ + "<html><script>"+ + "var context = new AudioContext();"+ + "setTimeout(function(){window.close();},1000);\x3C/script></html>"); + window.onload = function(){ + var a = window.open(sub); + a.onbeforeunload = function(){ + setTimeout(function(){ + try { + a.context.createScriptProcessor(512, 1, 1); + } catch(e) { + ok (true,"got exception"); + } + setTimeout(function() { + ok (true,"no crash"); + SimpleTest.finish(); + }, 0); + }, 0); + }; + }; +}); + + +</script> +</pre> +</body> +</html>
--- a/dom/workers/PServiceWorkerManager.ipdl +++ b/dom/workers/PServiceWorkerManager.ipdl @@ -1,41 +1,46 @@ /* 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 protocol PBackground; +include protocol PServiceWorkerUpdater; include PBackgroundSharedTypes; include ServiceWorkerRegistrarTypes; using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; namespace mozilla { namespace dom { protocol PServiceWorkerManager { manager PBackground; + manages PServiceWorkerUpdater; parent: async Register(ServiceWorkerRegistrationData data); async Unregister(PrincipalInfo principalInfo, nsString scope); async PropagateSoftUpdate(OriginAttributes originAttributes, nsString scope); async PropagateUnregister(PrincipalInfo principalInfo, nsString scope); async PropagateRemove(nsCString host); async PropagateRemoveAll(); async Shutdown(); + async PServiceWorkerUpdater(OriginAttributes originAttributes, + nsCString scope); + child: async NotifyRegister(ServiceWorkerRegistrationData data); async NotifySoftUpdate(OriginAttributes originAttributes, nsString scope); async NotifyUnregister(PrincipalInfo principalInfo, nsString scope); async NotifyRemove(nsCString host); async NotifyRemoveAll(); async __delete__();
new file mode 100644 --- /dev/null +++ b/dom/workers/PServiceWorkerUpdater.ipdl @@ -0,0 +1,25 @@ +/* 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 protocol PServiceWorkerManager; + +namespace mozilla { +namespace dom { + +protocol PServiceWorkerUpdater +{ + manager PServiceWorkerManager; + +parent: + // This __delete__ is safe because it's called when Proceed() is received and + // no other IPC messages are received nor sent. + async __delete__(); + +child: + async Proceed(bool allowed); +}; + +} // namespace dom +} // namespace mozilla +
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -72,16 +72,17 @@ #include "ServiceWorkerPrivate.h" #include "ServiceWorkerRegisterJob.h" #include "ServiceWorkerRegistrar.h" #include "ServiceWorkerRegistration.h" #include "ServiceWorkerScriptCache.h" #include "ServiceWorkerEvents.h" #include "ServiceWorkerUnregisterJob.h" #include "ServiceWorkerUpdateJob.h" +#include "ServiceWorkerUpdaterChild.h" #include "SharedWorker.h" #include "WorkerInlines.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" #include "WorkerScope.h" #ifdef PostMessage #undef PostMessage @@ -503,16 +504,248 @@ public: return NS_OK; } private: ~PropagateRemoveAllRunnable() {} }; +class PromiseResolverCallback final : public ServiceWorkerUpdateFinishCallback +{ +public: + PromiseResolverCallback(ServiceWorkerUpdateFinishCallback* aCallback, + GenericPromise::Private* aPromise) + : mCallback(aCallback) + , mPromise(aPromise) + { + MOZ_DIAGNOSTIC_ASSERT(mPromise); + } + + void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override + { + MOZ_DIAGNOSTIC_ASSERT(mPromise); + + if (mCallback) { + mCallback->UpdateSucceeded(aInfo); + } + + MaybeResolve(); + } + + void UpdateFailed(ErrorResult& aStatus) override + { + MOZ_DIAGNOSTIC_ASSERT(mPromise); + + if (mCallback) { + mCallback->UpdateFailed(aStatus); + } + + MaybeResolve(); + } + +private: + ~PromiseResolverCallback() + { + MaybeResolve(); + } + + void + MaybeResolve() + { + if (mPromise) { + mPromise->Resolve(true, __func__); + mPromise = nullptr; + } + } + + RefPtr<ServiceWorkerUpdateFinishCallback> mCallback; + RefPtr<GenericPromise::Private> mPromise; +}; + +// This runnable is used for 2 different tasks: +// - to postpone the SoftUpdate() until the IPC SWM actor is created +// (aInternalMethod == false) +// - to call the 'real' SoftUpdate when the ServiceWorkerUpdaterChild is +// notified by the parent (aInternalMethod == true) +class SoftUpdateRunnable final : public CancelableRunnable +{ +public: + SoftUpdateRunnable(const OriginAttributes& aOriginAttributes, + const nsACString& aScope, bool aInternalMethod, + GenericPromise::Private* aPromise) + : mAttrs(aOriginAttributes) + , mScope(aScope) + , mInternalMethod(aInternalMethod) + , mPromise(aPromise) + {} + + NS_IMETHOD Run() override + { + AssertIsOnMainThread(); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + return NS_ERROR_FAILURE; + } + + if (mInternalMethod) { + RefPtr<PromiseResolverCallback> callback = + new PromiseResolverCallback(nullptr, mPromise); + mPromise = nullptr; + + swm->SoftUpdateInternal(mAttrs, mScope, callback); + } else { + swm->SoftUpdate(mAttrs, mScope); + } + + return NS_OK; + } + + nsresult + Cancel() override + { + mPromise = nullptr; + return NS_OK; + } + +private: + ~SoftUpdateRunnable() + { + if (mPromise) { + mPromise->Resolve(true, __func__); + } + } + + const OriginAttributes mAttrs; + const nsCString mScope; + bool mInternalMethod; + + RefPtr<GenericPromise::Private> mPromise; +}; + +// This runnable is used for 3 different tasks: +// - to postpone the Update() until the IPC SWM actor is created +// (aType == ePostpone) +// - to call the 'real' Update when the ServiceWorkerUpdaterChild is +// notified by the parent (aType == eSuccess) +// - an error must be propagated (aType == eFailure) +class UpdateRunnable final : public CancelableRunnable +{ +public: + enum Type { + ePostpone, + eSuccess, + eFailure, + }; + + UpdateRunnable(nsIPrincipal* aPrincipal, + const nsACString& aScope, + ServiceWorkerUpdateFinishCallback* aCallback, + Type aType, + GenericPromise::Private* aPromise) + : mPrincipal(aPrincipal) + , mScope(aScope) + , mCallback(aCallback) + , mType(aType) + , mPromise(aPromise) + {} + + NS_IMETHOD Run() override + { + AssertIsOnMainThread(); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + return NS_ERROR_FAILURE; + } + + if (mType == ePostpone) { + swm->Update(mPrincipal, mScope, mCallback); + return NS_OK; + } + + MOZ_ASSERT(mPromise); + + RefPtr<PromiseResolverCallback> callback = + new PromiseResolverCallback(mCallback, mPromise); + mPromise = nullptr; + + if (mType == eSuccess) { + swm->UpdateInternal(mPrincipal, mScope, callback); + return NS_OK; + } + + ErrorResult error(NS_ERROR_DOM_ABORT_ERR); + callback->UpdateFailed(error); + return NS_OK; + } + + nsresult + Cancel() override + { + mPromise = nullptr; + return NS_OK; + } + +private: + ~UpdateRunnable() + { + if (mPromise) { + mPromise->Resolve(true, __func__); + } + } + + nsCOMPtr<nsIPrincipal> mPrincipal; + const nsCString mScope; + RefPtr<ServiceWorkerUpdateFinishCallback> mCallback; + Type mType; + + RefPtr<GenericPromise::Private> mPromise; +}; + +class ResolvePromiseRunnable final : public CancelableRunnable +{ +public: + explicit ResolvePromiseRunnable(GenericPromise::Private* aPromise) + : mPromise(aPromise) + {} + + NS_IMETHOD + Run() override + { + MaybeResolve(); + return NS_OK; + } + + nsresult + Cancel() override + { + mPromise = nullptr; + return NS_OK; + } + +private: + ~ResolvePromiseRunnable() + { + MaybeResolve(); + } + + void + MaybeResolve() + { + if (mPromise) { + mPromise->Resolve(true, __func__); + mPromise = nullptr; + } + } + + RefPtr<GenericPromise::Private> mPromise; +}; + } // namespace // This function implements parts of the step 3 of the following algorithm: // https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-secure static bool IsFromAuthenticatedOrigin(nsIDocument* aDoc) { MOZ_ASSERT(aDoc); @@ -2721,16 +2954,90 @@ ServiceWorkerManager::SoftUpdate(const O const nsACString& aScope) { AssertIsOnMainThread(); if (mShuttingDown) { return; } + if (!mActor) { + RefPtr<Runnable> runnable = + new SoftUpdateRunnable(aOriginAttributes, aScope, false, nullptr); + AppendPendingOperation(runnable); + return; + } + + RefPtr<GenericPromise::Private> promise = + new GenericPromise::Private(__func__); + + RefPtr<CancelableRunnable> successRunnable = + new SoftUpdateRunnable(aOriginAttributes, aScope, true, promise); + + RefPtr<CancelableRunnable> failureRunnable = + new ResolvePromiseRunnable(promise); + + ServiceWorkerUpdaterChild* actor = + new ServiceWorkerUpdaterChild(promise, successRunnable, failureRunnable); + + mActor->SendPServiceWorkerUpdaterConstructor(actor, aOriginAttributes, + nsCString(aScope)); +} + +namespace { + +class UpdateJobCallback final : public ServiceWorkerJob::Callback +{ + RefPtr<ServiceWorkerUpdateFinishCallback> mCallback; + + ~UpdateJobCallback() = default; + +public: + explicit UpdateJobCallback(ServiceWorkerUpdateFinishCallback* aCallback) + : mCallback(aCallback) + { + AssertIsOnMainThread(); + MOZ_ASSERT(mCallback); + } + + void + JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aJob); + + if (aStatus.Failed()) { + mCallback->UpdateFailed(aStatus); + return; + } + + MOZ_DIAGNOSTIC_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Update); + RefPtr<ServiceWorkerUpdateJob> updateJob = + static_cast<ServiceWorkerUpdateJob*>(aJob); + RefPtr<ServiceWorkerRegistrationInfo> reg = updateJob->GetRegistration(); + mCallback->UpdateSucceeded(reg); + } + + NS_INLINE_DECL_REFCOUNTING(UpdateJobCallback) +}; + +} // anonymous namespace + +void +ServiceWorkerManager::SoftUpdateInternal(const OriginAttributes& aOriginAttributes, + const nsACString& aScope, + ServiceWorkerUpdateFinishCallback* aCallback) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aCallback); + + if (mShuttingDown) { + return; + } + nsCOMPtr<nsIURI> scopeURI; nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateCodebasePrincipal(scopeURI, aOriginAttributes); @@ -2774,64 +3081,62 @@ ServiceWorkerManager::SoftUpdate(const O // See: https://github.com/slightlyoff/ServiceWorker/issues/759 RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope); RefPtr<ServiceWorkerUpdateJob> job = new ServiceWorkerUpdateJob(principal, registration->mScope, newest->ScriptSpec(), nullptr, registration->GetLoadFlags()); + + RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback); + job->AppendResultCallback(cb); + queue->ScheduleJob(job); } -namespace { - -class UpdateJobCallback final : public ServiceWorkerJob::Callback -{ - RefPtr<ServiceWorkerUpdateFinishCallback> mCallback; - - ~UpdateJobCallback() - { - } - -public: - explicit UpdateJobCallback(ServiceWorkerUpdateFinishCallback* aCallback) - : mCallback(aCallback) - { - AssertIsOnMainThread(); - MOZ_ASSERT(mCallback); - } - - void - JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) - { - AssertIsOnMainThread(); - MOZ_ASSERT(aJob); - - if (aStatus.Failed()) { - mCallback->UpdateFailed(aStatus); - return; - } - - MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Update); - RefPtr<ServiceWorkerUpdateJob> updateJob = - static_cast<ServiceWorkerUpdateJob*>(aJob); - RefPtr<ServiceWorkerRegistrationInfo> reg = updateJob->GetRegistration(); - mCallback->UpdateSucceeded(reg); - } - - NS_INLINE_DECL_REFCOUNTING(UpdateJobCallback) -}; -} // anonymous namespace - void ServiceWorkerManager::Update(nsIPrincipal* aPrincipal, const nsACString& aScope, ServiceWorkerUpdateFinishCallback* aCallback) { + AssertIsOnMainThread(); + + if (!mActor) { + RefPtr<Runnable> runnable = + new UpdateRunnable(aPrincipal, aScope, aCallback, + UpdateRunnable::ePostpone, nullptr); + AppendPendingOperation(runnable); + return; + } + + RefPtr<GenericPromise::Private> promise = + new GenericPromise::Private(__func__); + + RefPtr<CancelableRunnable> successRunnable = + new UpdateRunnable(aPrincipal, aScope, aCallback, + UpdateRunnable::eSuccess, promise); + + RefPtr<CancelableRunnable> failureRunnable = + new UpdateRunnable(aPrincipal, aScope, aCallback, + UpdateRunnable::eFailure, promise); + + ServiceWorkerUpdaterChild* actor = + new ServiceWorkerUpdaterChild(promise, successRunnable, failureRunnable); + + mActor->SendPServiceWorkerUpdaterConstructor(actor, + aPrincipal->OriginAttributesRef(), + nsCString(aScope)); +} + +void +ServiceWorkerManager::UpdateInternal(nsIPrincipal* aPrincipal, + const nsACString& aScope, + ServiceWorkerUpdateFinishCallback* aCallback) +{ MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aCallback); nsAutoCString scopeKey; nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey); if (NS_WARN_IF(NS_FAILED(rv))) { return; }
--- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -160,20 +160,31 @@ public: ErrorResult& aRv); void Update(nsIPrincipal* aPrincipal, const nsACString& aScope, ServiceWorkerUpdateFinishCallback* aCallback); void + UpdateInternal(nsIPrincipal* aPrincipal, + const nsACString& aScope, + ServiceWorkerUpdateFinishCallback* aCallback); + + void SoftUpdate(const OriginAttributes& aOriginAttributes, const nsACString& aScope); void + SoftUpdateInternal(const OriginAttributes& aOriginAttributes, + const nsACString& aScope, + ServiceWorkerUpdateFinishCallback* aCallback); + + + void PropagateSoftUpdate(const OriginAttributes& aOriginAttributes, const nsAString& aScope); void PropagateRemove(const nsACString& aHost); void Remove(const nsACString& aHost);
--- a/dom/workers/ServiceWorkerManagerChild.cpp +++ b/dom/workers/ServiceWorkerManagerChild.cpp @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ServiceWorkerManagerChild.h" #include "ServiceWorkerManager.h" +#include "ServiceWorkerUpdaterChild.h" #include "mozilla/Unused.h" namespace mozilla { using namespace ipc; namespace dom { namespace workers { @@ -97,11 +98,25 @@ ServiceWorkerManagerChild::RecvNotifyRem RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); if (swm) { swm->RemoveAll(); } return IPC_OK(); } +PServiceWorkerUpdaterChild* +ServiceWorkerManagerChild::AllocPServiceWorkerUpdaterChild(const OriginAttributes& aOriginAttributes, + const nsCString& aScope) +{ + MOZ_CRASH("Do no use ServiceWorkerUpdaterChild IPC CTOR."); +} + +bool +ServiceWorkerManagerChild::DeallocPServiceWorkerUpdaterChild(PServiceWorkerUpdaterChild* aActor) +{ + delete aActor; + return true; +} + } // namespace workers } // namespace dom } // namespace mozilla
--- a/dom/workers/ServiceWorkerManagerChild.h +++ b/dom/workers/ServiceWorkerManagerChild.h @@ -41,16 +41,23 @@ public: virtual mozilla::ipc::IPCResult RecvNotifyUnregister(const PrincipalInfo& aPrincipalInfo, const nsString& aScope) override; virtual mozilla::ipc::IPCResult RecvNotifyRemove(const nsCString& aHost) override; virtual mozilla::ipc::IPCResult RecvNotifyRemoveAll() override; + virtual PServiceWorkerUpdaterChild* + AllocPServiceWorkerUpdaterChild(const OriginAttributes& originAttributes, + const nsCString& scope) override; + + virtual bool + DeallocPServiceWorkerUpdaterChild(PServiceWorkerUpdaterChild* aActor) override; + private: ServiceWorkerManagerChild() : mShuttingDown(false) {} ~ServiceWorkerManagerChild() {} bool mShuttingDown;
--- a/dom/workers/ServiceWorkerManagerParent.cpp +++ b/dom/workers/ServiceWorkerManagerParent.cpp @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ServiceWorkerManagerParent.h" #include "ServiceWorkerManagerService.h" +#include "ServiceWorkerUpdaterParent.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/Unused.h" #include "nsThreadUtils.h" namespace mozilla { @@ -305,16 +306,48 @@ ServiceWorkerManagerParent::RecvShutdown mService->UnregisterActor(this); mService = nullptr; Unused << Send__delete__(this); return IPC_OK(); } +PServiceWorkerUpdaterParent* +ServiceWorkerManagerParent::AllocPServiceWorkerUpdaterParent(const OriginAttributes& aOriginAttributes, + const nsCString& aScope) +{ + AssertIsOnBackgroundThread(); + return new ServiceWorkerUpdaterParent(); +} + +mozilla::ipc::IPCResult +ServiceWorkerManagerParent::RecvPServiceWorkerUpdaterConstructor(PServiceWorkerUpdaterParent* aActor, + const OriginAttributes& aOriginAttributes, + const nsCString& aScope) +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(!mService)) { + return IPC_FAIL_NO_REASON(this); + } + + mService->ProcessUpdaterActor(static_cast<ServiceWorkerUpdaterParent*>(aActor), + aOriginAttributes, aScope, mID); + return IPC_OK(); +} + +bool +ServiceWorkerManagerParent::DeallocPServiceWorkerUpdaterParent(PServiceWorkerUpdaterParent* aActor) +{ + AssertIsOnBackgroundThread(); + delete aActor; + return true; +} + void ServiceWorkerManagerParent::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnBackgroundThread(); if (mService) { // This object is about to be released and with it, also mService will be // released too.
--- a/dom/workers/ServiceWorkerManagerParent.h +++ b/dom/workers/ServiceWorkerManagerParent.h @@ -51,16 +51,28 @@ private: const nsString& aScope) override; virtual mozilla::ipc::IPCResult RecvPropagateRemove(const nsCString& aHost) override; virtual mozilla::ipc::IPCResult RecvPropagateRemoveAll() override; virtual mozilla::ipc::IPCResult RecvShutdown() override; + virtual PServiceWorkerUpdaterParent* + AllocPServiceWorkerUpdaterParent(const OriginAttributes& aOriginAttributes, + const nsCString& aScope) override; + + virtual mozilla::ipc::IPCResult + RecvPServiceWorkerUpdaterConstructor(PServiceWorkerUpdaterParent* aActor, + const OriginAttributes& aOriginAttributes, + const nsCString& aScope) override; + + virtual bool + DeallocPServiceWorkerUpdaterParent(PServiceWorkerUpdaterParent* aActor) override; + virtual void ActorDestroy(ActorDestroyReason aWhy) override; RefPtr<ServiceWorkerManagerService> mService; // We use this ID in the Service in order to avoid the sending of messages to // ourself. uint64_t mID; };
--- a/dom/workers/ServiceWorkerManagerService.cpp +++ b/dom/workers/ServiceWorkerManagerService.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 "ServiceWorkerManagerService.h" #include "ServiceWorkerManagerParent.h" #include "ServiceWorkerRegistrar.h" +#include "ServiceWorkerUpdaterParent.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/Unused.h" #include "nsAutoPtr.h" namespace mozilla { using namespace ipc; @@ -227,11 +228,56 @@ ServiceWorkerManagerService::PropagateRe } } #ifdef DEBUG MOZ_ASSERT(parentFound); #endif } +void +ServiceWorkerManagerService::ProcessUpdaterActor(ServiceWorkerUpdaterParent* aActor, + const OriginAttributes& aOriginAttributes, + const nsACString& aScope, + uint64_t aParentId) +{ + AssertIsOnBackgroundThread(); + + nsAutoCString suffix; + aOriginAttributes.CreateSuffix(suffix); + + nsCString scope(aScope); + scope.Append(suffix); + + for (uint32_t i = 0; i < mPendingUpdaterActors.Length(); ++i) { + // We already have an actor doing this update on another process. + if (mPendingUpdaterActors[i].mScope.Equals(scope) && + mPendingUpdaterActors[i].mParentId != aParentId) { + Unused << aActor->SendProceed(false); + return; + } + } + + if (aActor->Proceed(this)) { + PendingUpdaterActor* pua = mPendingUpdaterActors.AppendElement(); + pua->mActor = aActor; + pua->mScope = scope; + pua->mParentId = aParentId; + } +} + +void +ServiceWorkerManagerService::UpdaterActorDestroyed(ServiceWorkerUpdaterParent* aActor) +{ + for (uint32_t i = 0; i < mPendingUpdaterActors.Length(); ++i) { + // We already have an actor doing the update for this scope. + if (mPendingUpdaterActors[i].mActor == aActor) { + mPendingUpdaterActors.RemoveElementAt(i); + return; + } + } + + MOZ_CRASH("The actor should be found"); +} + } // namespace workers } // namespace dom } // namespace mozilla
--- a/dom/workers/ServiceWorkerManagerService.h +++ b/dom/workers/ServiceWorkerManagerService.h @@ -21,16 +21,17 @@ class PrincipalInfo; namespace dom { class ServiceWorkerRegistrationData; namespace workers { class ServiceWorkerManagerParent; +class ServiceWorkerUpdaterParent; class ServiceWorkerManagerService final { public: NS_INLINE_DECL_REFCOUNTING(ServiceWorkerManagerService) static already_AddRefed<ServiceWorkerManagerService> Get(); static already_AddRefed<ServiceWorkerManagerService> GetOrCreate(); @@ -48,20 +49,36 @@ public: void PropagateUnregister(uint64_t aParentID, const mozilla::ipc::PrincipalInfo& aPrincipalInfo, const nsAString& aScope); void PropagateRemove(uint64_t aParentID, const nsACString& aHost); void PropagateRemoveAll(uint64_t aParentID); + void ProcessUpdaterActor(ServiceWorkerUpdaterParent* aActor, + const OriginAttributes& aOriginAttributes, + const nsACString& aScope, + uint64_t aParentID); + + void UpdaterActorDestroyed(ServiceWorkerUpdaterParent* aActor); + private: ServiceWorkerManagerService(); ~ServiceWorkerManagerService(); nsTHashtable<nsPtrHashKey<ServiceWorkerManagerParent>> mAgents; + + struct PendingUpdaterActor + { + nsCString mScope; + ServiceWorkerUpdaterParent* mActor; + uint64_t mParentId; + }; + + nsTArray<PendingUpdaterActor> mPendingUpdaterActors; }; } // namespace workers } // namespace dom } // namespace mozilla #endif // mozilla_dom_ServiceWorkerManagerService_h
new file mode 100644 --- /dev/null +++ b/dom/workers/ServiceWorkerUpdaterChild.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ServiceWorkerUpdaterChild.h" + +namespace mozilla { +namespace dom { +namespace workers { + +ServiceWorkerUpdaterChild::ServiceWorkerUpdaterChild(GenericPromise* aPromise, + CancelableRunnable* aSuccessRunnable, + CancelableRunnable* aFailureRunnable) + : mSuccessRunnable(aSuccessRunnable) + , mFailureRunnable(aFailureRunnable) +{ + MOZ_ASSERT(aPromise); + MOZ_ASSERT(aSuccessRunnable); + MOZ_ASSERT(aFailureRunnable); + + aPromise->Then(AbstractThread::GetCurrent(), __func__, + [this]() { + mPromiseHolder.Complete(); + Unused << Send__delete__(this); + }).Track(mPromiseHolder); +} + +mozilla::ipc::IPCResult +ServiceWorkerUpdaterChild::RecvProceed(const bool& aAllowed) +{ + // If we have a callback, it will resolve the promise. + + if (aAllowed) { + mSuccessRunnable->Run(); + mFailureRunnable->Cancel(); + } else { + mFailureRunnable->Run(); + mSuccessRunnable->Cancel(); + } + + mSuccessRunnable = nullptr; + mFailureRunnable = nullptr; + + return IPC_OK(); +} + +void +ServiceWorkerUpdaterChild::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mSuccessRunnable) { + mSuccessRunnable->Cancel(); + } + + if (mFailureRunnable) { + mFailureRunnable->Cancel(); + } + + mPromiseHolder.DisconnectIfExists(); +} + +} // namespace workers +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/workers/ServiceWorkerUpdaterChild.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ServiceWorkerUpdaterChild_h +#define mozilla_dom_ServiceWorkerUpdaterChild_h + +#include "mozilla/dom/PServiceWorkerUpdaterChild.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/MozPromise.h" + +namespace mozilla { +namespace dom { +namespace workers { + +class ServiceWorkerUpdaterChild final : public PServiceWorkerUpdaterChild +{ +public: + ServiceWorkerUpdaterChild(GenericPromise* aPromise, + CancelableRunnable* aSuccessRunnable, + CancelableRunnable* aFailureRunnable); + + mozilla::ipc::IPCResult + RecvProceed(const bool& aAllowed) override; + +private: + void + ActorDestroy(ActorDestroyReason aWhy) override; + + MozPromiseRequestHolder<GenericPromise> mPromiseHolder; + + RefPtr<CancelableRunnable> mSuccessRunnable; + RefPtr<CancelableRunnable> mFailureRunnable; +}; + +} // namespace workers +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ServiceWorkerUpdaterChild_h
new file mode 100644 --- /dev/null +++ b/dom/workers/ServiceWorkerUpdaterParent.cpp @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ServiceWorkerUpdaterParent.h" +#include "ServiceWorkerManagerService.h" + +namespace mozilla { +namespace dom { +namespace workers { + +bool +ServiceWorkerUpdaterParent::Proceed(ServiceWorkerManagerService* aService) +{ + if (!SendProceed(true)) { + return false; + } + + mService = aService; + return true; +} + +void +ServiceWorkerUpdaterParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mService) { + mService->UpdaterActorDestroyed(this); + } +} + +} // namespace workers +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/workers/ServiceWorkerUpdaterParent.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ServiceWorkerUpdaterParent_h +#define mozilla_dom_ServiceWorkerUpdaterParent_h + +#include "mozilla/dom/PServiceWorkerUpdaterParent.h" +#include "mozilla/BasePrincipal.h" + +namespace mozilla { +namespace dom { +namespace workers { + +class ServiceWorkerManagerService; + +class ServiceWorkerUpdaterParent final : public PServiceWorkerUpdaterParent +{ +public: + void + ActorDestroy(ActorDestroyReason aWhy) override; + + bool + Proceed(ServiceWorkerManagerService* aService); + +private: + RefPtr<ServiceWorkerManagerService> mService; +}; + +} // namespace workers +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ServiceWorkerUpdaterParent_h
--- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -71,30 +71,33 @@ UNIFIED_SOURCES += [ 'ServiceWorkerPrivate.cpp', 'ServiceWorkerRegisterJob.cpp', 'ServiceWorkerRegistrar.cpp', 'ServiceWorkerRegistration.cpp', 'ServiceWorkerRegistrationInfo.cpp', 'ServiceWorkerScriptCache.cpp', 'ServiceWorkerUnregisterJob.cpp', 'ServiceWorkerUpdateJob.cpp', + 'ServiceWorkerUpdaterChild.cpp', + 'ServiceWorkerUpdaterParent.cpp', 'ServiceWorkerWindowClient.cpp', 'SharedWorker.cpp', 'WorkerDebuggerManager.cpp', 'WorkerHolder.cpp', 'WorkerLocation.cpp', 'WorkerNavigator.cpp', 'WorkerPrivate.cpp', 'WorkerRunnable.cpp', 'WorkerScope.cpp', 'WorkerThread.cpp', ] IPDL_SOURCES += [ 'PServiceWorkerManager.ipdl', + 'PServiceWorkerUpdater.ipdl', 'ServiceWorkerRegistrarTypes.ipdlh', ] LOCAL_INCLUDES += [ '../base', '../system', '/dom/base', '/dom/bindings',
--- a/dom/workers/test/serviceworkers/browser.ini +++ b/dom/workers/test/serviceworkers/browser.ini @@ -1,10 +1,14 @@ [DEFAULT] support-files = browser_base_force_refresh.html browser_cached_force_refresh.html download/window.html download/worker.js + file_multie10s_update.html force_refresh_browser_worker.js + server_multie10s_update.sjs [browser_force_refresh.js] [browser_download.js] +[browser_multie10s_update.js] +run-if=e10s
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/browser_multie10s_update.js @@ -0,0 +1,80 @@ +"use strict"; + +const { classes: Cc, interfaces: Ci, results: Cr } = Components; + +// Testing if 2 child processes are correctly managed when they both try to do +// an SW update. + +const BASE_URI = "http://mochi.test:8888/browser/dom/workers/test/serviceworkers/"; + +add_task(function* test_update() { + info("Setting the prefs to having multi-e10s enabled"); + yield SpecialPowers.pushPrefEnv({"set": [ + ["dom.ipc.processCount", 4], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ]}); + + let url = BASE_URI + "file_multie10s_update.html"; + + info("Creating the first tab..."); + let tab1 = gBrowser.addTab(url); + let browser1 = gBrowser.getBrowserForTab(tab1); + yield BrowserTestUtils.browserLoaded(browser1); + + info("Creating the second tab..."); + let tab2 = gBrowser.addTab(url); + let browser2 = gBrowser.getBrowserForTab(tab2); + yield BrowserTestUtils.browserLoaded(browser2); + + let sw = BASE_URI + "server_multie10s_update.sjs"; + + info("Let's start the test..."); + let status = yield ContentTask.spawn(browser1, sw, function(url) { + // Registration of the SW + return content.navigator.serviceWorker.register(url) + + // Activation + .then(function(r) { + return new content.window.Promise(resolve => { + let worker = r.installing; + worker.addEventListener('statechange', () => { + if (worker.state === 'installed') { + resolve(true); + } + }); + }); + }) + + // Waiting for the result. + .then(() => { + return new content.window.Promise(resolve => { + let results = []; + let bc = new content.window.BroadcastChannel('result'); + bc.onmessage = function(e) { + results.push(e.data); + if (results.length != 2) { + return; + } + + resolve(results[0] + results[1]); + } + + // Let's inform the tabs. + bc = new content.window.BroadcastChannel('start'); + bc.postMessage('go'); + }); + }); + }); + + if (status == 0) { + ok(false, "both succeeded. This is wrong."); + } else if (status == 1) { + ok(true, "one succeded, one failed. This is good."); + } else { + ok(false, "both failed. This is definitely wrong."); + } + + yield BrowserTestUtils.removeTab(tab1); + yield BrowserTestUtils.removeTab(tab2); +});
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/file_multie10s_update.html @@ -0,0 +1,34 @@ +<html> +<body> +<script> + +var bc = new BroadcastChannel('start'); +bc.onmessage = function(e) { + // This message is not for us. + if (e.data != 'go') { + return; + } + + // It can happen that we don't have the registrations yet. Let's try with a + // timeout. + function proceed() { + return navigator.serviceWorker.getRegistrations().then(regs => { + if (regs.length == 0) { + setTimeout(proceed, 200); + return; + } + + bc = new BroadcastChannel('result'); + regs[0].update().then(() => { + bc.postMessage(0); + }, () => { + bc.postMessage(1); + }); + }); + } + + proceed(); +} +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/server_multie10s_update.sjs @@ -0,0 +1,12 @@ +function handleRequest(request, response) +{ + response.processAsync(); + response.setHeader("Content-Type", "application/javascript", false); + + timer = Components.classes["@mozilla.org/timer;1"]. + createInstance(Components.interfaces.nsITimer); + timer.init(function() { + response.write("42"); + response.finish(); + }, 3000, Components.interfaces.nsITimer.TYPE_ONE_SHOT); +}
--- a/editor/libeditor/HTMLEditRules.cpp +++ b/editor/libeditor/HTMLEditRules.cpp @@ -1519,20 +1519,21 @@ HTMLEditRules::WillInsertBreak(Selection *aCancel = true; return NS_OK; } // Identify the block nsCOMPtr<Element> blockParent = htmlEditor->GetBlock(node); NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); - // If the active editing host is an inline element, or if the active editing - // host is the block parent itself, just append a br. + // When there is an active editing host (the <body> if it's in designMode) + // and a block which becomes the parent of line breaker is in it, do the + // standard thing. nsCOMPtr<Element> host = htmlEditor->GetActiveEditingHost(); - if (!EditorUtils::IsDescendantOf(blockParent, host)) { + if (host && !EditorUtils::IsDescendantOf(blockParent, host)) { nsresult rv = StandardBreakImpl(node, offset, aSelection); NS_ENSURE_SUCCESS(rv, rv); *aHandled = true; return NS_OK; } // If block is empty, populate with br. (For example, imagine a div that // contains the word "text". The user selects "text" and types return. @@ -6486,20 +6487,24 @@ HTMLEditRules::SplitParagraph(nsIDOMNode address_of(selNode), aOffset); // XXX When it fails, why do we need to return selection node? (Why can the // caller trust the result even when it returns error?) *aSelNode = GetAsDOMNode(selNode); NS_ENSURE_SUCCESS(rv, rv); // split the paragraph NS_ENSURE_STATE(mHTMLEditor); NS_ENSURE_STATE(selNode->IsContent()); - mHTMLEditor->SplitNodeDeep(*para, *selNode->AsContent(), *aOffset, - HTMLEditor::EmptyContainers::yes, - getter_AddRefs(leftPara), - getter_AddRefs(rightPara)); + int32_t offset = + mHTMLEditor->SplitNodeDeep(*para, *selNode->AsContent(), *aOffset, + HTMLEditor::EmptyContainers::yes, + getter_AddRefs(leftPara), + getter_AddRefs(rightPara)); + if (NS_WARN_IF(offset == -1)) { + return NS_ERROR_FAILURE; + } // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p) NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->IsVisBreak(aBRNode)) { NS_ENSURE_STATE(mHTMLEditor); rv = mHTMLEditor->DeleteNode(aBRNode); NS_ENSURE_SUCCESS(rv, rv); }
new file mode 100644 --- /dev/null +++ b/editor/libeditor/crashtests/1350772.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<script> +addEventListener("DOMContentLoaded", () => { + try { + document.designMode = 'on'; + document.removeChild(document.documentElement); + document.appendChild(document.createElement("p")); + document.execCommand("insertParagraph", false, null); + } catch(e) { + } +}); +</script> +</head> +</html>
--- a/editor/libeditor/crashtests/crashtests.list +++ b/editor/libeditor/crashtests/crashtests.list @@ -68,8 +68,9 @@ load 1158452.html load 1158651.html load 1244894.xhtml load 1264921.html load 1272490.html load 1317704.html load 1317718.html load 1324505.html load 1348851.html +load 1350772.html
--- a/gfx/config/gfxVars.h +++ b/gfx/config/gfxVars.h @@ -20,16 +20,17 @@ namespace gfx { class gfxVarReceiver; // Generator for graphics vars. #define GFX_VARS_LIST(_) \ /* C++ Name, Data Type, Default Value */ \ _(BrowserTabsRemoteAutostart, bool, false) \ _(ContentBackend, BackendType, BackendType::NONE) \ + _(SoftwareBackend, BackendType, BackendType::NONE) \ _(TileSize, IntSize, IntSize(-1, -1)) \ _(UseXRender, bool, false) \ _(OffscreenFormat, gfxImageFormat, mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32) \ _(RequiresAcceleratedGLContextForCompositorOGL, bool, false) \ _(CanUseHardwareVideoDecoding, bool, false) \ _(PDMWMFDisableD3D11Dlls, nsCString, nsCString()) \ _(PDMWMFDisableD3D9Dlls, nsCString, nsCString()) \ _(DXInterop2Blocked, bool, false) \
--- a/gfx/layers/client/ClientPaintedLayer.cpp +++ b/gfx/layers/client/ClientPaintedLayer.cpp @@ -52,16 +52,17 @@ ClientPaintedLayer::PaintThebes(nsTArray } #endif PaintState state = mContentClient->BeginPaintBuffer(this, flags); mValidRegion.Sub(mValidRegion, state.mRegionToInvalidate); if (!state.mRegionToDraw.IsEmpty() && !ClientManager()->GetPaintedLayerCallback()) { ClientManager()->SetTransactionIncomplete(); + mContentClient->EndPaint(nullptr); return; } // The area that became invalid and is visible needs to be repainted // (this could be the whole visible area if our buffer switched // from RGB to RGBA, because we might need to repaint with // subpixel AA) state.mRegionToInvalidate.And(state.mRegionToInvalidate,
--- a/gfx/layers/client/ContentClient.cpp +++ b/gfx/layers/client/ContentClient.cpp @@ -250,17 +250,17 @@ ContentClientRemoteBuffer::EndPaint(nsTA for (unsigned i = 0; i< mOldTextures.Length(); ++i) { if (mOldTextures[i]->IsLocked()) { mOldTextures[i]->Unlock(); } } mOldTextures.Clear(); if (mTextureClient && mTextureClient->IsLocked()) { - if (aReadbackUpdates->Length() > 0) { + if (aReadbackUpdates && aReadbackUpdates->Length() > 0) { RefPtr<TextureReadbackSink> readbackSink = new RemoteBufferReadbackProcessor(aReadbackUpdates, mBufferRect, mBufferRotation); mTextureClient->SetReadbackSink(readbackSink); } mTextureClient->Unlock(); mTextureClient->SyncWithObject(mForwarder->GetSyncObject()); }
--- a/gfx/layers/opengl/TextureHostOGL.cpp +++ b/gfx/layers/opengl/TextureHostOGL.cpp @@ -199,21 +199,20 @@ TextureImageTextureSourceOGL::EnsureBuff } mTexImage->Resize(aSize); } void TextureImageTextureSourceOGL::SetTextureSourceProvider(TextureSourceProvider* aProvider) { GLContext* newGL = aProvider ? aProvider->GetGLContext() : nullptr; - if (!mGL) { - mGL = newGL; - } else if (mGL != newGL) { + if (!newGL || mGL != newGL) { DeallocateDeviceData(); } + mGL = newGL; } gfx::IntSize TextureImageTextureSourceOGL::GetSize() const { if (mTexImage) { if (mIterating) { return mTexImage->GetTileRect().Size();
--- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -1689,16 +1689,17 @@ gfxPlatform::InitBackendPrefs(uint32_t a } uint32_t swBackendBits = BackendTypeBit(BackendType::SKIA) | BackendTypeBit(BackendType::CAIRO); mSoftwareBackend = GetContentBackendPref(swBackendBits); if (XRE_IsParentProcess()) { gfxVars::SetContentBackend(mContentBackend); + gfxVars::SetSoftwareBackend(mSoftwareBackend); } } /* static */ BackendType gfxPlatform::GetCanvasBackendPref(uint32_t aBackendBitmask) { return GetBackendPref("gfx.canvas.azure.backends", aBackendBitmask); }
--- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -2114,17 +2114,20 @@ MessageChannel::ShouldContinueFromTimeou MonitorAutoUnlock unlock(*mMonitor); cont = mListener->ShouldContinueFromReplyTimeout(); mListener->ArtificialSleep(); } static enum { UNKNOWN, NOT_DEBUGGING, DEBUGGING } sDebuggingChildren = UNKNOWN; if (sDebuggingChildren == UNKNOWN) { - sDebuggingChildren = getenv("MOZ_DEBUG_CHILD_PROCESS") ? DEBUGGING : NOT_DEBUGGING; + sDebuggingChildren = getenv("MOZ_DEBUG_CHILD_PROCESS") || + getenv("MOZ_DEBUG_CHILD_PAUSE") + ? DEBUGGING + : NOT_DEBUGGING; } if (sDebuggingChildren == DEBUGGING) { return true; } return cont; }
--- a/js/ipc/WrapperOwner.cpp +++ b/js/ipc/WrapperOwner.cpp @@ -124,17 +124,18 @@ class CPOWProxyHandler : public BaseProx virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const override; virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const override; virtual bool getBuiltinClass(JSContext* cx, HandleObject obj, js::ESClass* cls) const override; virtual bool isArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<RegExpShared*> shared) const override; virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override; virtual void objectMoved(JSObject* proxy, const JSObject* old) const override; virtual bool isCallable(JSObject* obj) const override; virtual bool isConstructor(JSObject* obj) const override; virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override; virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject protop) const override; @@ -849,23 +850,24 @@ WrapperOwner::getPrototypeIfOrdinary(JSC return false; objp.set(fromObjectOrNullVariant(cx, val)); return true; } bool -CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<RegExpShared*> shared) const { - FORWARD(regexp_toShared, (cx, proxy, g)); + FORWARD(regexp_toShared, (cx, proxy, shared)); } bool -WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) +WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy, MutableHandle<RegExpShared*> shared) { ObjectId objId = idOf(proxy); ReturnStatus status; nsString source; unsigned flags = 0; if (!SendRegExpToShared(objId, &status, &source, &flags)) return ipcfail(cx); @@ -875,17 +877,17 @@ WrapperOwner::regexp_toShared(JSContext* if (!ok(cx, status)) return false; RootedObject regexp(cx); regexp = JS_NewUCRegExpObject(cx, source.get(), source.Length(), flags); if (!regexp) return false; - return js::RegExpToSharedNonInline(cx, regexp, g); + return js::RegExpToSharedNonInline(cx, regexp, shared); } void CPOWProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const { AuxCPOWData* aux = AuxCPOWDataOf(proxy); OwnerOf(proxy)->drop(proxy);
--- a/js/ipc/WrapperOwner.h +++ b/js/ipc/WrapperOwner.h @@ -55,17 +55,18 @@ class WrapperOwner : public virtual Java bool hasInstance(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleValue v, bool* bp); bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy, js::ESClass* cls); bool isArray(JSContext* cx, JS::HandleObject proxy, JS::IsArrayAnswer* answer); const char* className(JSContext* cx, JS::HandleObject proxy); bool getPrototype(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleObject protop); bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy, bool* isOrdinary, JS::MutableHandleObject protop); - bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, js::RegExpGuard* g); + bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, + js::MutableHandle<js::RegExpShared*> shared); nsresult instanceOf(JSObject* obj, const nsID* id, bool* bp); bool toString(JSContext* cx, JS::HandleObject callee, JS::CallArgs& args); bool DOMQI(JSContext* cx, JS::HandleObject callee, JS::CallArgs& args); /* * Check that |obj| is a DOM wrapper whose prototype chain contains
--- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -573,40 +573,42 @@ struct UnusedGCThingSizes macro(Other, GCHeapUnused, script) \ macro(Other, GCHeapUnused, lazyScript) \ macro(Other, GCHeapUnused, shape) \ macro(Other, GCHeapUnused, baseShape) \ macro(Other, GCHeapUnused, objectGroup) \ macro(Other, GCHeapUnused, string) \ macro(Other, GCHeapUnused, symbol) \ macro(Other, GCHeapUnused, jitcode) \ - macro(Other, GCHeapUnused, scope) + macro(Other, GCHeapUnused, scope) \ + macro(Other, GCHeapUnused, regExpShared) UnusedGCThingSizes() : FOR_EACH_SIZE(ZERO_SIZE) dummy() {} UnusedGCThingSizes(UnusedGCThingSizes&& other) : FOR_EACH_SIZE(COPY_OTHER_SIZE) dummy() {} void addToKind(JS::TraceKind kind, intptr_t n) { switch (kind) { - case JS::TraceKind::Object: object += n; break; - case JS::TraceKind::String: string += n; break; - case JS::TraceKind::Symbol: symbol += n; break; - case JS::TraceKind::Script: script += n; break; - case JS::TraceKind::Shape: shape += n; break; - case JS::TraceKind::BaseShape: baseShape += n; break; - case JS::TraceKind::JitCode: jitcode += n; break; - case JS::TraceKind::LazyScript: lazyScript += n; break; - case JS::TraceKind::ObjectGroup: objectGroup += n; break; - case JS::TraceKind::Scope: scope += n; break; + case JS::TraceKind::Object: object += n; break; + case JS::TraceKind::String: string += n; break; + case JS::TraceKind::Symbol: symbol += n; break; + case JS::TraceKind::Script: script += n; break; + case JS::TraceKind::Shape: shape += n; break; + case JS::TraceKind::BaseShape: baseShape += n; break; + case JS::TraceKind::JitCode: jitcode += n; break; + case JS::TraceKind::LazyScript: lazyScript += n; break; + case JS::TraceKind::ObjectGroup: objectGroup += n; break; + case JS::TraceKind::Scope: scope += n; break; + case JS::TraceKind::RegExpShared: regExpShared += n; break; default: MOZ_CRASH("Bad trace kind for UnusedGCThingSizes"); } } void addSizes(const UnusedGCThingSizes& other) { FOR_EACH_SIZE(ADD_OTHER_SIZE) } @@ -638,16 +640,18 @@ struct ZoneStats macro(Other, GCHeapAdmin, gcHeapArenaAdmin) \ macro(Other, GCHeapUsed, lazyScriptsGCHeap) \ macro(Other, MallocHeap, lazyScriptsMallocHeap) \ macro(Other, GCHeapUsed, jitCodesGCHeap) \ macro(Other, GCHeapUsed, objectGroupsGCHeap) \ macro(Other, MallocHeap, objectGroupsMallocHeap) \ macro(Other, GCHeapUsed, scopesGCHeap) \ macro(Other, MallocHeap, scopesMallocHeap) \ + macro(Other, GCHeapUsed, regExpSharedsGCHeap) \ + macro(Other, MallocHeap, regExpSharedsMallocHeap) \ macro(Other, MallocHeap, typePool) \ macro(Other, MallocHeap, baselineStubsOptimized) \ macro(Other, MallocHeap, uniqueIdMap) \ macro(Other, MallocHeap, shapeTables) ZoneStats() : FOR_EACH_SIZE(ZERO_SIZE) unusedGCThings(),
--- a/js/public/Proxy.h +++ b/js/public/Proxy.h @@ -27,17 +27,18 @@ using JS::MutableHandle; using JS::MutableHandleObject; using JS::MutableHandleValue; using JS::NativeImpl; using JS::ObjectOpResult; using JS::PrivateValue; using JS::PropertyDescriptor; using JS::Value; -class RegExpGuard; +class RegExpShared; + class JS_FRIEND_API(Wrapper); /* * A proxy is a JSObject with highly customizable behavior. ES6 specifies a * single kind of proxy, but the customization mechanisms we use to implement * ES6 Proxy objects are also useful wherever an object with weird behavior is * wanted. Proxies are used to implement: * @@ -325,17 +326,18 @@ class JS_FRIEND_API(BaseProxyHandler) virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args) const; virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const; virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const; virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const; virtual const char* className(JSContext* cx, HandleObject proxy) const; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<js::RegExpShared*> shared) const; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const; virtual void trace(JSTracer* trc, JSObject* proxy) const; virtual void finalize(JSFreeOp* fop, JSObject* proxy) const; virtual void objectMoved(JSObject* proxy, const JSObject* old) const; // Allow proxies, wrappers in particular, to specify callability at runtime. // Note: These do not take const JSObject*, but they do in spirit. // We are not prepared to do this, as there's little const correctness
--- a/js/public/TraceKind.h +++ b/js/public/TraceKind.h @@ -11,16 +11,17 @@ #include "js/TypeDecls.h" // Forward declarations of all the types a TraceKind can denote. namespace js { class BaseShape; class LazyScript; class ObjectGroup; +class RegExpShared; class Shape; class Scope; namespace jit { class JitCode; } // namespace jit } // namespace js namespace JS { @@ -54,23 +55,25 @@ enum class TraceKind // The kind associated with a nullptr. Null = 0x06, // The following kinds do not have an exposed C++ idiom. BaseShape = 0x0F, JitCode = 0x1F, LazyScript = 0x2F, - Scope = 0x3F + Scope = 0x3F, + RegExpShared = 0x4F }; const static uintptr_t OutOfLineTraceKindMask = 0x07; static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set"); static_assert(uintptr_t(JS::TraceKind::JitCode) & OutOfLineTraceKindMask, "mask bits are set"); static_assert(uintptr_t(JS::TraceKind::LazyScript) & OutOfLineTraceKindMask, "mask bits are set"); static_assert(uintptr_t(JS::TraceKind::Scope) & OutOfLineTraceKindMask, "mask bits are set"); +static_assert(uintptr_t(JS::TraceKind::RegExpShared) & OutOfLineTraceKindMask, "mask bits are set"); // When this header is imported inside SpiderMonkey, the class definitions are // available and we can query those definitions to find the correct kind // directly from the class hierarchy. template <typename T> struct MapTypeToTraceKind { static const JS::TraceKind kind = T::TraceKind; }; @@ -83,17 +86,18 @@ struct MapTypeToTraceKind { D(JitCode, js::jit::JitCode, true) \ D(LazyScript, js::LazyScript, true) \ D(Scope, js::Scope, true) \ D(Object, JSObject, true) \ D(ObjectGroup, js::ObjectGroup, true) \ D(Script, JSScript, true) \ D(Shape, js::Shape, true) \ D(String, JSString, false) \ - D(Symbol, JS::Symbol, false) + D(Symbol, JS::Symbol, false) \ + D(RegExpShared, js::RegExpShared, true) // Map from all public types to their trace kind. #define JS_EXPAND_DEF(name, type, _) \ template <> struct MapTypeToTraceKind<type> { \ static const JS::TraceKind kind = JS::TraceKind::name; \ }; JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); #undef JS_EXPAND_DEF
--- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -159,16 +159,19 @@ class JS_PUBLIC_API(CallbackTracer) : pu onChild(JS::GCCellPtr(*codep, JS::TraceKind::JitCode)); } virtual void onLazyScriptEdge(js::LazyScript** lazyp) { onChild(JS::GCCellPtr(*lazyp, JS::TraceKind::LazyScript)); } virtual void onScopeEdge(js::Scope** scopep) { onChild(JS::GCCellPtr(*scopep, JS::TraceKind::Scope)); } + virtual void onRegExpSharedEdge(js::RegExpShared** sharedp) { + onChild(JS::GCCellPtr(*sharedp, JS::TraceKind::RegExpShared)); + } // Override this method to receive notification when a node in the GC // heap graph is visited. virtual void onChild(const JS::GCCellPtr& thing) = 0; // Access to the tracing context: // When tracing with a JS::CallbackTracer, we invoke the callback with the // edge location and the type of target. This is useful for operating on @@ -229,16 +232,17 @@ class JS_PUBLIC_API(CallbackTracer) : pu void dispatchToOnEdge(JS::Symbol** symp) { onSymbolEdge(symp); } void dispatchToOnEdge(JSScript** scriptp) { onScriptEdge(scriptp); } void dispatchToOnEdge(js::Shape** shapep) { onShapeEdge(shapep); } void dispatchToOnEdge(js::ObjectGroup** groupp) { onObjectGroupEdge(groupp); } void dispatchToOnEdge(js::BaseShape** basep) { onBaseShapeEdge(basep); } void dispatchToOnEdge(js::jit::JitCode** codep) { onJitCodeEdge(codep); } void dispatchToOnEdge(js::LazyScript** lazyp) { onLazyScriptEdge(lazyp); } void dispatchToOnEdge(js::Scope** scopep) { onScopeEdge(scopep); } + void dispatchToOnEdge(js::RegExpShared** sharedp) { onRegExpSharedEdge(sharedp); } protected: void setTraceWeakEdges(bool value) { traceWeakEdges_ = value; } private: friend class AutoTracingName;
--- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -116,46 +116,47 @@ CreateRegExpSearchResult(JSContext* cx, return position | (lastIndex << 15); } /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 * steps 3, 9-14, except 12.a.i, 12.c.i.1. */ static RegExpRunStatus -ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, RegExpShared& re, HandleLinearString input, - size_t searchIndex, MatchPairs* matches, size_t* endIndex) +ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, MutableHandleRegExpShared re, + HandleLinearString input, size_t searchIndex, MatchPairs* matches, + size_t* endIndex) { - RegExpRunStatus status = re.execute(cx, input, searchIndex, matches, endIndex); + RegExpRunStatus status = RegExpShared::execute(cx, re, input, searchIndex, matches, endIndex); /* Out of spec: Update RegExpStatics. */ if (status == RegExpRunStatus_Success && res) { if (matches) { if (!res->updateFromMatchPairs(cx, input, *matches)) return RegExpRunStatus_Error; } else { - res->updateLazily(cx, input, &re, searchIndex); + res->updateLazily(cx, input, re, searchIndex); } } return status; } /* Legacy ExecuteRegExp behavior is baked into the JSAPI. */ bool js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, Handle<RegExpObject*> reobj, HandleLinearString input, size_t* lastIndex, bool test, MutableHandleValue rval) { - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpObject::getShared(cx, reobj, &shared)) return false; ScopedMatchPairs matches(&cx->tempLifoAlloc()); - RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *shared, input, *lastIndex, + RegExpRunStatus status = ExecuteRegExpImpl(cx, res, &shared, input, *lastIndex, &matches, nullptr); if (status == RegExpRunStatus_Error) return false; if (status == RegExpRunStatus_Success_NotFound) { /* ExecuteRegExp() previously returned an array or null. */ rval.setNull(); return true; @@ -227,17 +228,17 @@ RegExpInitializeIgnoringLastIndex(JSCont /* Step 5. */ if (!ParseRegExpFlags(cx, flagStr, &flags)) return false; } if (sharedUse == UseRegExpShared) { /* Steps 7-8. */ - RegExpGuard re(cx); + RootedRegExpShared re(cx); if (!cx->compartment()->regExps.get(cx, pattern, flags, &re)) return false; /* Steps 9-12. */ obj->initIgnoringLastIndex(pattern, flags); obj->setShared(*re); } else { @@ -333,17 +334,17 @@ regexp_compile_impl(JSContext* cx, const // don't assume |patternObj.is<RegExpObject>()|. For the same reason, // don't reuse the RegExpShared below. RootedObject patternObj(cx, &patternValue.toObject()); RootedAtom sourceAtom(cx); RegExpFlag flags; { // Step 3b. - RegExpGuard g(cx); + RootedRegExpShared g(cx); if (!RegExpToShared(cx, patternObj, &g)) return false; sourceAtom = g->getSource(); flags = g->getFlags(); } // Step 5, minus lastIndex zeroing. @@ -428,17 +429,17 @@ js::regexp_construct(JSContext* cx, unsi // don't assume |patternObj.is<RegExpObject>()|. For the same reason, // don't reuse the RegExpShared below. RootedObject patternObj(cx, &patternValue.toObject()); RootedAtom sourceAtom(cx); RegExpFlag flags; { // Step 4.a. - RegExpGuard g(cx); + RootedRegExpShared g(cx); if (!RegExpToShared(cx, patternObj, &g)) return false; sourceAtom = g->getSource(); // Step 4.b. // Get original flags in all cases, to compare with passed flags. flags = g->getFlags(); } @@ -555,17 +556,17 @@ js::regexp_clone(JSContext* cx, unsigned { CallArgs args = CallArgsFromVp(argc, vp); RootedObject from(cx, &args[0].toObject()); RootedAtom sourceAtom(cx); RegExpFlag flags; { - RegExpGuard g(cx); + RootedRegExpShared g(cx); if (!RegExpToShared(cx, from, &g)) return false; sourceAtom = g->getSource(); flags = g->getFlags(); } Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx)); if (!regexp) @@ -912,17 +913,17 @@ ExecuteRegExp(JSContext* cx, HandleObjec * WARNING: Despite the presence of spec step comment numbers, this * algorithm isn't consistent with any ES6 version, draft or * otherwise. YOU HAVE BEEN WARNED. */ /* Steps 1-2 performed by the caller. */ Rooted<RegExpObject*> reobj(cx, ®exp->as<RegExpObject>()); - RegExpGuard re(cx); + RootedRegExpShared re(cx); if (!RegExpObject::getShared(cx, reobj, &re)) return RegExpRunStatus_Error; RegExpStatics* res; if (staticsUpdate == UpdateRegExpStatics) { res = GlobalObject::getRegExpStatics(cx, cx->global()); if (!res) return RegExpRunStatus_Error; @@ -963,17 +964,17 @@ ExecuteRegExp(JSContext* cx, HandleObjec * However, the spec will change to match our implementation's * behavior. See https://github.com/tc39/ecma262/issues/128. */ if (IsTrailSurrogateWithLeadSurrogate(cx, input, lastIndex)) lastIndex--; } /* Steps 3, 11-14, except 12.a.i, 12.c.i.1. */ - RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *re, input, lastIndex, matches, endIndex); + RegExpRunStatus status = ExecuteRegExpImpl(cx, res, &re, input, lastIndex, matches, endIndex); if (status == RegExpRunStatus_Error) return RegExpRunStatus_Error; /* Steps 12.a.i, 12.c.i.i, 15 are done by Self-hosted function. */ return status; }
--- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -246,22 +246,16 @@ IsMarkedBlack(JSObject* obj); #endif MOZ_ALWAYS_INLINE void CheckEdgeIsNotBlackToGray(JSObject* src, const Value& dst) { MOZ_ASSERT_IF(IsMarkedBlack(src), JS::ValueIsNotGray(dst)); } -MOZ_ALWAYS_INLINE void -CheckEdgeIsNotBlackToGray(JSObject* src, gc::Cell* dst) -{ - MOZ_ASSERT_IF(IsMarkedBlack(src), JS::CellIsNotGray(dst)); -} - template <typename T> struct InternalBarrierMethods {}; template <typename T> struct InternalBarrierMethods<T*> { static bool isMarkable(T* v) { return v != nullptr; }
--- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -115,23 +115,28 @@ struct MovingTracer : JS::CallbackTracer void onObjectEdge(JSObject** objp) override; void onShapeEdge(Shape** shapep) override; void onStringEdge(JSString** stringp) override; void onScriptEdge(JSScript** scriptp) override; void onLazyScriptEdge(LazyScript** lazyp) override; void onBaseShapeEdge(BaseShape** basep) override; void onScopeEdge(Scope** basep) override; + void onRegExpSharedEdge(RegExpShared** sharedp) override; void onChild(const JS::GCCellPtr& thing) override { MOZ_ASSERT(!RelocationOverlay::isCellForwarded(thing.asCell())); } #ifdef DEBUG TracerKind getTracerKind() const override { return TracerKind::Moving; } #endif + + private: + template <typename T> + void updateEdge(T** thingp); }; // Structure for counting how many times objects in a particular group have // been tenured during a minor collection. struct TenureCount { ObjectGroup* group; int count;
--- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -764,16 +764,18 @@ class GCRuntime bool isIncrementalGCAllowed() const { return incrementalAllowed; } void disallowIncrementalGC() { incrementalAllowed = false; } bool isIncrementalGCEnabled() const { return mode == JSGC_MODE_INCREMENTAL && incrementalAllowed; } bool isIncrementalGCInProgress() const { return state() != State::NotActive; } bool isCompactingGCEnabled() const; + bool isShrinkingGC() const { return invocationKind == GC_SHRINK; } + void setGrayRootsTracer(JSTraceDataOp traceOp, void* data); MOZ_MUST_USE bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data); void removeBlackRootsTracer(JSTraceDataOp traceOp, void* data); bool triggerGCForTooMuchMalloc() { return triggerGC(JS::gcreason::TOO_MUCH_MALLOC); } int32_t getMallocBytes() const { return mallocCounter.bytes(); } size_t maxMallocBytesAllocated() const { return mallocCounter.maxBytes(); } bool isTooMuchMalloc() const { return mallocCounter.isTooMuchMalloc(); }
--- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -104,55 +104,57 @@ enum class AllocKind { FAT_INLINE_STRING, STRING, EXTERNAL_STRING, FAT_INLINE_ATOM, ATOM, SYMBOL, JITCODE, SCOPE, + REGEXP_SHARED, LIMIT, LAST = LIMIT - 1 }; // Macro to enumerate the different allocation kinds supplying information about // the trace kind, C++ type and allocation size. #define FOR_EACH_OBJECT_ALLOCKIND(D) \ - /* AllocKind TraceKind TypeName SizedType */ \ - D(FUNCTION, Object, JSObject, JSFunction) \ - D(FUNCTION_EXTENDED, Object, JSObject, FunctionExtended) \ - D(OBJECT0, Object, JSObject, JSObject_Slots0) \ - D(OBJECT0_BACKGROUND, Object, JSObject, JSObject_Slots0) \ - D(OBJECT2, Object, JSObject, JSObject_Slots2) \ - D(OBJECT2_BACKGROUND, Object, JSObject, JSObject_Slots2) \ - D(OBJECT4, Object, JSObject, JSObject_Slots4) \ - D(OBJECT4_BACKGROUND, Object, JSObject, JSObject_Slots4) \ - D(OBJECT8, Object, JSObject, JSObject_Slots8) \ - D(OBJECT8_BACKGROUND, Object, JSObject, JSObject_Slots8) \ - D(OBJECT12, Object, JSObject, JSObject_Slots12) \ - D(OBJECT12_BACKGROUND, Object, JSObject, JSObject_Slots12) \ - D(OBJECT16, Object, JSObject, JSObject_Slots16) \ - D(OBJECT16_BACKGROUND, Object, JSObject, JSObject_Slots16) + /* AllocKind TraceKind TypeName SizedType */ \ + D(FUNCTION, Object, JSObject, JSFunction) \ + D(FUNCTION_EXTENDED, Object, JSObject, FunctionExtended) \ + D(OBJECT0, Object, JSObject, JSObject_Slots0) \ + D(OBJECT0_BACKGROUND, Object, JSObject, JSObject_Slots0) \ + D(OBJECT2, Object, JSObject, JSObject_Slots2) \ + D(OBJECT2_BACKGROUND, Object, JSObject, JSObject_Slots2) \ + D(OBJECT4, Object, JSObject, JSObject_Slots4) \ + D(OBJECT4_BACKGROUND, Object, JSObject, JSObject_Slots4) \ + D(OBJECT8, Object, JSObject, JSObject_Slots8) \ + D(OBJECT8_BACKGROUND, Object, JSObject, JSObject_Slots8) \ + D(OBJECT12, Object, JSObject, JSObject_Slots12) \ + D(OBJECT12_BACKGROUND, Object, JSObject, JSObject_Slots12) \ + D(OBJECT16, Object, JSObject, JSObject_Slots16) \ + D(OBJECT16_BACKGROUND, Object, JSObject, JSObject_Slots16) #define FOR_EACH_NONOBJECT_ALLOCKIND(D) \ - /* AllocKind TraceKind TypeName SizedType */ \ - D(SCRIPT, Script, JSScript, JSScript) \ - D(LAZY_SCRIPT, LazyScript, js::LazyScript, js::LazyScript) \ - D(SHAPE, Shape, js::Shape, js::Shape) \ - D(ACCESSOR_SHAPE, Shape, js::AccessorShape, js::AccessorShape) \ - D(BASE_SHAPE, BaseShape, js::BaseShape, js::BaseShape) \ - D(OBJECT_GROUP, ObjectGroup, js::ObjectGroup, js::ObjectGroup) \ - D(FAT_INLINE_STRING, String, JSFatInlineString, JSFatInlineString) \ - D(STRING, String, JSString, JSString) \ - D(EXTERNAL_STRING, String, JSExternalString, JSExternalString) \ - D(FAT_INLINE_ATOM, String, js::FatInlineAtom, js::FatInlineAtom) \ - D(ATOM, String, js::NormalAtom, js::NormalAtom) \ - D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ - D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) \ - D(SCOPE, Scope, js::Scope, js::Scope) + /* AllocKind TraceKind TypeName SizedType */ \ + D(SCRIPT, Script, JSScript, JSScript) \ + D(LAZY_SCRIPT, LazyScript, js::LazyScript, js::LazyScript) \ + D(SHAPE, Shape, js::Shape, js::Shape) \ + D(ACCESSOR_SHAPE, Shape, js::AccessorShape, js::AccessorShape) \ + D(BASE_SHAPE, BaseShape, js::BaseShape, js::BaseShape) \ + D(OBJECT_GROUP, ObjectGroup, js::ObjectGroup, js::ObjectGroup) \ + D(FAT_INLINE_STRING, String, JSFatInlineString, JSFatInlineString) \ + D(STRING, String, JSString, JSString) \ + D(EXTERNAL_STRING, String, JSExternalString, JSExternalString) \ + D(FAT_INLINE_ATOM, String, js::FatInlineAtom, js::FatInlineAtom) \ + D(ATOM, String, js::NormalAtom, js::NormalAtom) \ + D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ + D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) \ + D(SCOPE, Scope, js::Scope, js::Scope) \ + D(REGEXP_SHARED, RegExpShared, js::RegExpShared, js::RegExpShared) #define FOR_EACH_ALLOCKIND(D) \ FOR_EACH_OBJECT_ALLOCKIND(D) \ FOR_EACH_NONOBJECT_ALLOCKIND(D) static_assert(int(AllocKind::FIRST) == 0, "Various places depend on AllocKind starting at 0, " "please audit them carefully!"); static_assert(int(AllocKind::OBJECT_FIRST) == 0, "Various places depend on AllocKind::OBJECT_FIRST "
--- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -438,16 +438,24 @@ template <typename T> void js::TraceNullableEdge(JSTracer* trc, WriteBarrieredBase<T>* thingp, const char* name) { if (InternalBarrierMethods<T>::isMarkable(thingp->get())) DispatchToTracer(trc, ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name); } template <typename T> +void +js::TraceNullableEdge(JSTracer* trc, ReadBarriered<T>* thingp, const char* name) +{ + if (InternalBarrierMethods<T>::isMarkable(thingp->unbarrieredGet())) + DispatchToTracer(trc, ConvertToBase(thingp->unsafeGet()), name); +} + +template <typename T> JS_PUBLIC_API(void) JS::TraceEdge(JSTracer* trc, JS::Heap<T>* thingp, const char* name) { MOZ_ASSERT(thingp); if (InternalBarrierMethods<T>::isMarkable(*thingp->unsafeGet())) DispatchToTracer(trc, ConvertToBase(thingp->unsafeGet()), name); } @@ -554,16 +562,17 @@ js::TraceRootRange(JSTracer* trc, size_t } } // Instantiate a copy of the Tracing templates for each derived type. #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(type) \ template void js::TraceEdge<type>(JSTracer*, WriteBarrieredBase<type>*, const char*); \ template void js::TraceEdge<type>(JSTracer*, ReadBarriered<type>*, const char*); \ template void js::TraceNullableEdge<type>(JSTracer*, WriteBarrieredBase<type>*, const char*); \ + template void js::TraceNullableEdge<type>(JSTracer*, ReadBarriered<type>*, const char*); \ template void js::TraceManuallyBarrieredEdge<type>(JSTracer*, type*, const char*); \ template void js::TraceWeakEdge<type>(JSTracer*, WeakRef<type>*, const char*); \ template void js::TraceRoot<type>(JSTracer*, type*, const char*); \ template void js::TraceRoot<type>(JSTracer*, ReadBarriered<type>*, const char*); \ template void js::TraceNullableRoot<type>(JSTracer*, type*, const char*); \ template void js::TraceNullableRoot<type>(JSTracer*, ReadBarriered<type>*, const char*); \ template void js::TraceRange<type>(JSTracer*, size_t, WriteBarrieredBase<type>*, const char*); \ template void js::TraceRootRange<type>(JSTracer*, size_t, type*, const char*); @@ -870,16 +879,17 @@ js::GCMarker::markAndTraceChildren(T* th if (ThingIsPermanentAtomOrWellKnownSymbol(thing)) return; if (mark(thing)) thing->traceChildren(this); } namespace js { template <> void GCMarker::traverse(BaseShape* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(JS::Symbol* thing) { markAndTraceChildren(thing); } +template <> void GCMarker::traverse(RegExpShared* thing) { markAndTraceChildren(thing); } } // namespace js // Strings, LazyScripts, Shapes, and Scopes are extremely common, but have // simple patterns of recursion. We traverse trees of these edges immediately, // with aggressive, manual inlining, implemented by eagerlyTraceChildren. template <typename T> void js::GCMarker::markAndScan(T* thing) @@ -2570,44 +2580,33 @@ GCMarker::sizeOfExcludingThis(mozilla::M { size_t size = stack.sizeOfExcludingThis(mallocSizeOf); for (ZonesIter zone(runtime(), WithAtoms); !zone.done(); zone.next()) size += zone->gcGrayRoots().sizeOfExcludingThis(mallocSizeOf); return size; } #ifdef DEBUG - -static bool -IsCrossCompartmentEdge(JSObject* source, const Cell* target) -{ - if (!source->is<ProxyObject>()) - return false; - - const Value& priv = source->as<ProxyObject>().private_(); - return priv.isGCThing() && priv.toGCThing() == target; -} - Zone* GCMarker::stackContainsCrossZonePointerTo(const Cell* target) const { MOZ_ASSERT(!JS::CurrentThreadIsHeapCollecting()); Zone* targetZone = target->asTenured().zone(); for (MarkStackIter iter(stack); !iter.done(); iter.next()) { if (iter.peekTag() != MarkStack::ObjectTag) continue; auto source = iter.peekPtr().as<JSObject>(); Zone* sourceZone = source->zone(); if (sourceZone == targetZone) continue; - if (IsCrossCompartmentEdge(source, target) || + if ((source->is<ProxyObject>() && source->as<ProxyObject>().target() == target) || Debugger::isDebuggerCrossCompartmentEdge(source, target)) { return sourceZone; } } return nullptr; }
--- a/js/src/gc/Policy.h +++ b/js/src/gc/Policy.h @@ -81,16 +81,17 @@ class JitCode; D(js::ModuleEnvironmentObject*) \ D(js::ModuleNamespaceObject*) \ D(js::ModuleObject*) \ D(js::NativeObject*) \ D(js::ObjectGroup*) \ D(js::PlainObject*) \ D(js::PropertyName*) \ D(js::RegExpObject*) \ + D(js::RegExpShared*) \ D(js::SavedFrame*) \ D(js::Scope*) \ D(js::ScriptSourceObject*) \ D(js::Shape*) \ D(js::SharedArrayBufferObject*) \ D(js::StructTypeDescr*) \ D(js::UnownedBaseShape*) \ D(js::WasmFunctionScope*) \
--- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -179,16 +179,17 @@ static const PhaseInfo phases[] = { { PHASE_SWEEP_MISC, "Sweep Miscellaneous", PHASE_SWEEP_COMPARTMENTS, 29 }, { PHASE_SWEEP_TYPES, "Sweep type information", PHASE_SWEEP_COMPARTMENTS, 30 }, { PHASE_SWEEP_TYPES_BEGIN, "Sweep type tables and compilations", PHASE_SWEEP_TYPES, 31 }, { PHASE_SWEEP_TYPES_END, "Free type arena", PHASE_SWEEP_TYPES, 32 }, { PHASE_SWEEP_OBJECT, "Sweep Object", PHASE_SWEEP, 33 }, { PHASE_SWEEP_STRING, "Sweep String", PHASE_SWEEP, 34 }, { PHASE_SWEEP_SCRIPT, "Sweep Script", PHASE_SWEEP, 35 }, { PHASE_SWEEP_SCOPE, "Sweep Scope", PHASE_SWEEP, 59 }, + { PHASE_SWEEP_REGEXP_SHARED, "Sweep RegExpShared", PHASE_SWEEP, 61 }, { PHASE_SWEEP_SHAPE, "Sweep Shape", PHASE_SWEEP, 36 }, { PHASE_SWEEP_JITCODE, "Sweep JIT code", PHASE_SWEEP, 37 }, { PHASE_FINALIZE_END, "Finalize End Callback", PHASE_SWEEP, 38 }, { PHASE_DESTROY, "Deallocate", PHASE_SWEEP, 39 }, { PHASE_COMPACT, "Compact", PHASE_NO_PARENT, 40 }, { PHASE_COMPACT_MOVE, "Compact Move", PHASE_COMPACT, 41 }, { PHASE_COMPACT_UPDATE, "Compact Update", PHASE_COMPACT, 42 }, /* PHASE_MARK_ROOTS */ @@ -206,19 +207,19 @@ static const PhaseInfo phases[] = { { PHASE_BUFFER_GRAY_ROOTS, "Buffer Gray Roots", PHASE_MARK_ROOTS, 49 }, { PHASE_MARK_CCWS, "Mark Cross Compartment Wrappers", PHASE_MARK_ROOTS, 50 }, { PHASE_MARK_STACK, "Mark C and JS stacks", PHASE_MARK_ROOTS, 51 }, { PHASE_MARK_RUNTIME_DATA, "Mark Runtime-wide Data", PHASE_MARK_ROOTS, 52 }, { PHASE_MARK_EMBEDDING, "Mark Embedding", PHASE_MARK_ROOTS, 53 }, { PHASE_MARK_COMPARTMENTS, "Mark Compartments", PHASE_MARK_ROOTS, 54 }, { PHASE_PURGE_SHAPE_TABLES, "Purge ShapeTables", PHASE_NO_PARENT, 60 }, - { PHASE_LIMIT, nullptr, PHASE_NO_PARENT, 60 } + { PHASE_LIMIT, nullptr, PHASE_NO_PARENT, 61 } - // Current number of telemetryBuckets is 60. If you insert new phases + // Current number of telemetryBuckets is 61. If you insert new phases // somewhere, start at that number and count up. Do not change any existing // numbers. }; static mozilla::EnumeratedArray<Phase, PHASE_LIMIT, ExtraPhaseInfo> phaseExtra; // Mapping from all nodes with a multi-parented child to a Vector of all // multi-parented children and their descendants. (Single-parented children will
--- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -64,16 +64,17 @@ enum Phase : uint8_t { PHASE_SWEEP_MISC, PHASE_SWEEP_TYPES, PHASE_SWEEP_TYPES_BEGIN, PHASE_SWEEP_TYPES_END, PHASE_SWEEP_OBJECT, PHASE_SWEEP_STRING, PHASE_SWEEP_SCRIPT, PHASE_SWEEP_SCOPE, + PHASE_SWEEP_REGEXP_SHARED, PHASE_SWEEP_SHAPE, PHASE_SWEEP_JITCODE, PHASE_FINALIZE_END, PHASE_DESTROY, PHASE_COMPACT, PHASE_COMPACT_MOVE, PHASE_COMPACT_UPDATE, PHASE_COMPACT_UPDATE_CELLS,
--- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -60,16 +60,20 @@ template <typename T> void TraceEdge(JSTracer* trc, ReadBarriered<T>* thingp, const char* name); // Trace through an edge in the live object graph on behalf of tracing. template <typename T> void TraceNullableEdge(JSTracer* trc, WriteBarrieredBase<T>* thingp, const char* name); +template <typename T> +void +TraceNullableEdge(JSTracer* trc, ReadBarriered<T>* thingp, const char* name); + // Trace through a "root" edge. These edges are the initial edges in the object // graph traversal. Root edges are asserted to only be traversed in the initial // phase of a GC. template <typename T> void TraceRoot(JSTracer* trc, T* thingp, const char* name); template <typename T>
--- a/js/src/irregexp/RegExpEngine.cpp +++ b/js/src/irregexp/RegExpEngine.cpp @@ -1709,17 +1709,17 @@ IsNativeRegExpEnabled(JSContext* cx) #ifdef JS_CODEGEN_NONE return false; #else return cx->options().nativeRegExp(); #endif } RegExpCode -irregexp::CompilePattern(JSContext* cx, RegExpShared* shared, RegExpCompileData* data, +irregexp::CompilePattern(JSContext* cx, HandleRegExpShared shared, RegExpCompileData* data, HandleLinearString sample, bool is_global, bool ignore_case, bool is_ascii, bool match_only, bool force_bytecode, bool sticky, bool unicode) { if ((data->capture_count + 1) * 2 - 1 > RegExpMacroAssembler::kMaxRegister) { JS_ReportErrorASCII(cx, "regexp too big"); return RegExpCode(); }
--- a/js/src/irregexp/RegExpEngine.h +++ b/js/src/irregexp/RegExpEngine.h @@ -81,17 +81,17 @@ struct RegExpCode } void destroy() { js_free(byteCode); } }; RegExpCode -CompilePattern(JSContext* cx, RegExpShared* shared, RegExpCompileData* data, +CompilePattern(JSContext* cx, HandleRegExpShared shared, RegExpCompileData* data, HandleLinearString sample, bool is_global, bool ignore_case, bool is_ascii, bool match_only, bool force_bytecode, bool sticky, bool unicode); // Note: this may return RegExpRunStatus_Error if an interrupt was requested // while the code was executing. template <typename CharT> RegExpRunStatus
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/wasm-11.js @@ -0,0 +1,34 @@ +// Test single-stepping where the TLS register can be evicted by a non-trivial +// function body. + +if (!wasmIsSupported()) + quit(); + +var g = newGlobal(); +g.parent = this; +g.eval(` + var dbg = new Debugger(parent); +`); + +var i = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(` + (module + (func (export "f2") + i64.const 0 + i64.const 0 + i32.const 0 + select + drop + ) + ) +`))); + +g.eval(` + var calledOnStep = 0; + dbg.onEnterFrame = frame => { + if (frame.type === "wasmcall") + frame.onStep = () => { calledOnStep++ } + }; +`); + +i.exports.f2(); +assertEq(g.calledOnStep >= 5, true);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1342261.js @@ -0,0 +1,7 @@ +// With --baseline-eager, create a cross-compartment wrapper IC referring to a +// target in an otherwise-doomed compartment. + +fullcompartmentchecks(true); +newGlobal({ + sameZoneAs: [] +}).z;
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/latin1/rope-stringchar.js @@ -0,0 +1,16 @@ +function f(a, b) { + assertEq(isLatin1(a), true); + assertEq(isLatin1(b), false); + var c = a + b; + assertEq(isLatin1(c), false); + assertEq(c[4], 'b'); + assertEq(c.charCodeAt(4), 98); +} + +function test() { + for (var i = 0; i < 15; i++) { + f("aaaab\x00\x00\x00\x00\x00\x00", "\u{2603}"); + } +} + +test();
--- a/js/src/jit/BaselineCacheIRCompiler.cpp +++ b/js/src/jit/BaselineCacheIRCompiler.cpp @@ -261,17 +261,17 @@ BaselineCacheIRCompiler::emitGuardProto( masm.branchPtr(Assembler::NotEqual, addr, scratch, failure->label()); return true; } bool BaselineCacheIRCompiler::emitGuardCompartment() { Register obj = allocator.useRegister(masm, reader.objOperandId()); - reader.stubOffset(); // Read global. + reader.stubOffset(); // Read global wrapper. AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; Address addr(stubAddress(reader.stubOffset())); masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
--- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -842,22 +842,16 @@ DoGetElemFallback(JSContext* cx, Baselin if (!stub->addMonitorStubForValue(cx, &info, res)) { return false; } if (attached) return true; - // GetElem operations on non-native objects cannot be cached by either - // Baseline or Ion. Indicate this in the cache so that Ion does not - // generate a cache for this op. - if (lhs.isObject() && !lhs.toObject().isNative()) - stub->noteNonNativeAccess(); - // GetElem operations which could access negative indexes generally can't // be optimized without the potential for bailouts, as we can't statically // determine that an object has no properties on such indexes. if (rhs.isNumber() && rhs.toNumber() < 0) stub->noteNegativeIndex(); if (!attached && !isTemporarilyUnoptimizable) stub->noteUnoptimizableAccess();
--- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -366,28 +366,20 @@ class ICToNumber_Fallback : public ICFal class ICGetElem_Fallback : public ICMonitoredFallbackStub { friend class ICStubSpace; explicit ICGetElem_Fallback(JitCode* stubCode) : ICMonitoredFallbackStub(ICStub::GetElem_Fallback, stubCode) { } - static const uint16_t EXTRA_NON_NATIVE = 0x1; - static const uint16_t EXTRA_NEGATIVE_INDEX = 0x2; - static const uint16_t EXTRA_UNOPTIMIZABLE_ACCESS = 0x4; + static const uint16_t EXTRA_NEGATIVE_INDEX = 0x1; + static const uint16_t EXTRA_UNOPTIMIZABLE_ACCESS = 0x2; public: - void noteNonNativeAccess() { - extra_ |= EXTRA_NON_NATIVE; - } - bool hasNonNativeAccess() const { - return extra_ & EXTRA_NON_NATIVE; - } - void noteNegativeIndex() { extra_ |= EXTRA_NEGATIVE_INDEX; } bool hasNegativeIndex() const { return extra_ & EXTRA_NEGATIVE_INDEX; } void noteUnoptimizableAccess() { extra_ |= EXTRA_UNOPTIMIZABLE_ACCESS;
--- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -473,30 +473,16 @@ BaselineInspector::expectedBinaryArithSp if (TryToSpecializeBinaryArithOp(stubs, 2, &result)) return result; } return MIRType::None; } bool -BaselineInspector::hasSeenNonNativeGetElement(jsbytecode* pc) -{ - if (!hasBaselineScript()) - return false; - - const ICEntry& entry = icEntryFromPC(pc); - ICStub* stub = entry.fallbackStub(); - - if (stub->isGetElem_Fallback()) - return stub->toGetElem_Fallback()->hasNonNativeAccess(); - return false; -} - -bool BaselineInspector::hasSeenNegativeIndexGetElement(jsbytecode* pc) { if (!hasBaselineScript()) return false; const ICEntry& entry = icEntryFromPC(pc); ICStub* stub = entry.fallbackStub();
--- a/js/src/jit/BaselineInspector.h +++ b/js/src/jit/BaselineInspector.h @@ -100,17 +100,16 @@ class BaselineInspector return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback); } MIRType expectedResultType(jsbytecode* pc); MCompare::CompareType expectedCompareType(jsbytecode* pc); MIRType expectedBinaryArithSpecialization(jsbytecode* pc); MIRType expectedPropertyAccessInputType(jsbytecode* pc); - bool hasSeenNonNativeGetElement(jsbytecode* pc); bool hasSeenNegativeIndexGetElement(jsbytecode* pc); bool hasSeenAccessedGetter(jsbytecode* pc); bool hasSeenDoubleResult(jsbytecode* pc); bool hasSeenNonStringIterMore(jsbytecode* pc); MOZ_MUST_USE bool isOptimizableCallStringSplit(jsbytecode* pc, JSString** strOut, JSString** sepOut, JSObject** objOut); JSObject* getTemplateObject(jsbytecode* pc);
--- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -682,16 +682,20 @@ GetPropIRGenerator::tryAttachCrossCompar RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj)); MOZ_ASSERT(unwrapped == UnwrapOneChecked(obj)); // If we allowed different zones we would have to wrap strings. if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) return false; + RootedObject wrappedGlobal(cx_, &obj->global()); + if (!cx_->compartment()->wrap(cx_, &wrappedGlobal)) + return false; + AutoCompartment ac(cx_, unwrapped); // The first CCW for iframes is almost always wrapping another WindowProxy // so we optimize for that case as well. bool isWindowProxy = IsWindowProxy(unwrapped); if (isWindowProxy) { MOZ_ASSERT(ToWindowIfWindowProxy(unwrapped) == unwrapped->compartment()->maybeGlobal()); unwrapped = cx_->global(); @@ -720,17 +724,17 @@ GetPropIRGenerator::tryAttachCrossCompar maybeEmitIdGuard(id); writer.guardIsProxy(objId); writer.guardIsCrossCompartmentWrapper(objId); // Load the object wrapped by the CCW ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId); // If the compartment of the wrapped object is different we should fail. - writer.guardCompartment(wrapperTargetId, unwrapped->compartment()); + writer.guardCompartment(wrapperTargetId, wrappedGlobal, unwrapped->compartment()); ObjOperandId unwrappedId = wrapperTargetId; if (isWindowProxy) { // For the WindowProxy case also unwrap the inner window. // We avoid loadObject, because storing cross compartment objects in // stubs / JIT code is tricky. writer.guardClass(wrapperTargetId, GuardClassKind::WindowProxy); unwrappedId = writer.loadWrapperTarget(wrapperTargetId); @@ -1280,20 +1284,36 @@ GetPropIRGenerator::tryAttachStringLengt bool GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, ValOperandId indexId) { MOZ_ASSERT(idVal_.isInt32()); if (!val_.isString()) return false; + int32_t index = idVal_.toInt32(); + if (index < 0) + return false; + JSString* str = val_.toString(); - int32_t index = idVal_.toInt32(); - if (size_t(index) >= str->length() || - !str->isLinear() || + if (size_t(index) >= str->length()) + return false; + + // This follows JSString::getChar, otherwise we fail to attach getChar in a lot of cases. + if (str->isRope()) { + JSRope* rope = &str->asRope(); + + // Make sure the left side contains the index. + if (size_t(index) >= rope->leftChild()->length()) + return false; + + str = rope->leftChild(); + } + + if (!str->isLinear() || str->asLinear().latin1OrTwoByteChar(index) >= StaticStrings::UNIT_STATIC_LIMIT) { return false; } StringOperandId strId = writer.guardIsString(valId); Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId); writer.loadStringCharResult(strId, int32IndexId);
--- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -514,23 +514,22 @@ class MOZ_RAII CacheIRWriter : public JS void guardSpecificSymbol(SymbolOperandId sym, JS::Symbol* expected) { writeOpWithOperandId(CacheOp::GuardSpecificSymbol, sym); addStubField(uintptr_t(expected), StubField::Type::Symbol); } void guardMagicValue(ValOperandId val, JSWhyMagic magic) { writeOpWithOperandId(CacheOp::GuardMagicValue, val); buffer_.writeByte(uint32_t(magic)); } - void guardCompartment(ObjOperandId obj, JSCompartment* compartment) { + void guardCompartment(ObjOperandId obj, JSObject* global, JSCompartment* compartment) { writeOpWithOperandId(CacheOp::GuardCompartment, obj); // Add a reference to the compartment's global to keep it alive. - addStubField(uintptr_t(compartment->maybeGlobal()), StubField::Type::JSObject); + addStubField(uintptr_t(global), StubField::Type::JSObject); // Use RawWord, because compartments never move and it can't be GCed. addStubField(uintptr_t(compartment), StubField::Type::RawWord); - } void guardNoDetachedTypedObjects() { writeOp(CacheOp::GuardNoDetachedTypedObjects); } void guardFrameHasNoArgumentsObject() { writeOp(CacheOp::GuardFrameHasNoArgumentsObject); }
--- a/js/src/jit/CacheIRCompiler.cpp +++ b/js/src/jit/CacheIRCompiler.cpp @@ -1756,22 +1756,20 @@ CacheIRCompiler::emitLoadStringCharResul Register index = allocator.useRegister(masm, reader.int32OperandId()); AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output); AutoScratchRegister scratch2(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; - masm.branchIfRope(str, failure->label()); - // Bounds check, load string char. masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()), index, failure->label()); - masm.loadStringChar(str, index, scratch1); + masm.loadStringChar(str, index, scratch1, failure->label()); // Load StaticString for this char. masm.branch32(Assembler::AboveOrEqual, scratch1, Imm32(StaticStrings::UNIT_STATIC_LIMIT), failure->label()); masm.movePtr(ImmPtr(&cx_->staticStrings().unitStaticTable), scratch2); masm.loadPtr(BaseIndex(scratch2, scratch1, ScalePointer), scratch2); EmitStoreResult(masm, scratch2, JSVAL_TYPE_STRING, output);
--- a/js/src/jit/CacheIRSpewer.cpp +++ b/js/src/jit/CacheIRSpewer.cpp @@ -16,18 +16,21 @@ #define getpid _getpid #else #include <unistd.h> #endif #include <stdarg.h> #include "jsfun.h" +#include "jsobj.h" #include "jsscript.h" + #include "jscompartmentinlines.h" +#include "jsobjinlines.h" using namespace js; using namespace js::jit; CacheIRSpewer cacheIRspewer; CacheIRSpewer& jit::GetCacheIRSpewerSingleton() @@ -144,16 +147,19 @@ CacheIRSpewer::valueProperty(LockGuard<M j.doubleProperty("value", v.toDouble()); } else if (v.isString() || v.isSymbol()) { JSString* str = v.isString() ? v.toString() : v.toSymbol()->description(); if (str && str->isLinear()) { j.beginStringProperty("value"); QuoteString(output, &str->asLinear()); j.endStringProperty(); } + } else if (v.isObject()) { + j.stringProperty("value", "%p (shape: %p)", &v.toObject(), + v.toObject().maybeShape()); } j.endObject(); } void CacheIRSpewer::attached(LockGuard<Mutex>&, const char* name) {
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -7957,20 +7957,17 @@ static const VMFunction CharCodeAtInfo = void CodeGenerator::visitCharCodeAt(LCharCodeAt* lir) { Register str = ToRegister(lir->str()); Register index = ToRegister(lir->index()); Register output = ToRegister(lir->output()); OutOfLineCode* ool = oolCallVM(CharCodeAtInfo, lir, ArgList(str, index), StoreRegisterTo(output)); - - masm.branchIfRope(str, ool->entry()); - masm.loadStringChar(str, index, output); - + masm.loadStringChar(str, index, output, ool->entry()); masm.bind(ool->rejoin()); } typedef JSFlatString* (*StringFromCharCodeFn)(JSContext*, int32_t); static const VMFunction StringFromCharCodeInfo = FunctionInfo<StringFromCharCodeFn>(jit::StringFromCharCode, "StringFromCharCode"); void
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -7591,38 +7591,17 @@ IonBuilder::jsop_getelem() trackOptimizationAttempt(TrackedStrategy::GetElem_String); MOZ_TRY(getElemTryString(&emitted, obj, index)); if (emitted) return Ok(); } trackOptimizationAttempt(TrackedStrategy::GetElem_InlineCache); - MOZ_TRY(getElemTryCache(&emitted, obj, index)); - if (emitted) - return Ok(); - - // Emit call. - MInstruction* ins = MCallGetElement::New(alloc(), obj, index); - - current->add(ins); - current->push(ins); - - MOZ_TRY(resumeAfter(ins)); - - if (*pc == JSOP_CALLELEM && IsNullOrUndefined(obj->type())) { - // Due to inlining, it's possible the observed TypeSet is non-empty, - // even though we know |obj| is null/undefined and the MCallGetElement - // will throw. Don't push a TypeBarrier in this case, to avoid - // inlining the following (unreachable) JSOP_CALL. - return Ok(); - } - - TemporaryTypeSet* types = bytecodeTypes(pc); - return pushTypeBarrier(ins, types, BarrierKind::TypeSet); + return getElemAddCache(obj, index); } AbortReasonOr<Ok> IonBuilder::getElemTryTypedObject(bool* emitted, MDefinition* obj, MDefinition* index) { MOZ_ASSERT(*emitted == false); // The next several failures are all due to types not predicting that we @@ -8261,59 +8240,38 @@ IonBuilder::getElemTryArgumentsInlined(b return Ok(); } // inlined not constant not supported, yet. return abort(AbortReason::Disable, "NYI inlined not constant get argument element"); } AbortReasonOr<Ok> -IonBuilder::getElemTryCache(bool* emitted, MDefinition* obj, MDefinition* index) -{ - MOZ_ASSERT(*emitted == false); - - // Make sure we have at least an object. - if (!obj->mightBeType(MIRType::Object)) { - trackOptimizationOutcome(TrackedOutcome::NotObject); - return Ok(); - } - - // Don't cache for strings. - if (obj->mightBeType(MIRType::String)) { - trackOptimizationOutcome(TrackedOutcome::GetElemStringNotCached); - return Ok(); - } - - // Index should be integer, string, or symbol - if (!index->mightBeType(MIRType::Int32) && - !index->mightBeType(MIRType::String) && - !index->mightBeType(MIRType::Symbol)) - { - trackOptimizationOutcome(TrackedOutcome::IndexType); - return Ok(); - } - - // Turn off cacheing if the element is int32 and we've seen non-native objects as the target - // of this getelem. - bool nonNativeGetElement = inspector->hasSeenNonNativeGetElement(pc); - if (index->mightBeType(MIRType::Int32) && nonNativeGetElement) { - trackOptimizationOutcome(TrackedOutcome::NonNativeReceiver); - return Ok(); - } - - // Emit GetElementCache. +IonBuilder::getElemAddCache(MDefinition* obj, MDefinition* index) +{ + // Emit GetPropertyCache. TemporaryTypeSet* types = bytecodeTypes(pc); - BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, - nullptr, types); - - // Always add a barrier if the index might be a string or symbol, so that - // the cache can attach stubs for particular properties. - if (index->mightBeType(MIRType::String) || index->mightBeType(MIRType::Symbol)) + + BarrierKind barrier; + if (obj->type() == MIRType::Object) { + // Always add a barrier if the index is not an int32 value, so we can + // attach stubs for particular properties. + if (index->type() == MIRType::Int32) { + barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, + nullptr, types); + } else { + barrier = BarrierKind::TypeSet; + } + } else { + // PropertyReadNeedsTypeBarrier only accounts for object types, so for + // now always insert a barrier if the input is not known to be an + // object. barrier = BarrierKind::TypeSet; + } // Ensure we insert a type barrier for reads from typed objects, as type // information does not account for the initial undefined/null types. if (barrier != BarrierKind::TypeSet && !types->unknown()) { MOZ_ASSERT(obj->resultTypeSet()); switch (obj->resultTypeSet()->forAllClasses(constraints(), IsTypedObjectClass)) { case TemporaryTypeSet::ForAllResult::ALL_FALSE: case TemporaryTypeSet::ForAllResult::EMPTY: @@ -8339,17 +8297,16 @@ IonBuilder::getElemTryCache(bool* emitte if (knownType != MIRType::Value && knownType != MIRType::Double) ins->setResultType(knownType); } MOZ_TRY(pushTypeBarrier(ins, types, barrier)); trackOptimizationSuccess(); - *emitted = true; return Ok(); } TemporaryTypeSet* IonBuilder::computeHeapType(const TemporaryTypeSet* objTypes, const jsid id) { if (objTypes->unknownObject() || objTypes->getObjectCount() == 0) return nullptr; @@ -9039,34 +8996,24 @@ IonBuilder::initOrSetElemTryCache(bool* { MOZ_ASSERT(*emitted == false); if (!object->mightBeType(MIRType::Object)) { trackOptimizationOutcome(TrackedOutcome::NotObject); return Ok(); } - if (!index->mightBeType(MIRType::Int32) && - !index->mightBeType(MIRType::String) && - !index->mightBeType(MIRType::Symbol)) - { - trackOptimizationOutcome(TrackedOutcome::IndexType); - return Ok(); - } - if (value->type() == MIRType::MagicHole) { trackOptimizationOutcome(TrackedOutcome::InitHole); return Ok(); } bool barrier = true; - bool indexIsInt32 = index->type() == MIRType::Int32; - - if (indexIsInt32 && + if (index->type() == MIRType::Int32 && !PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &object, nullptr, &value, /* canModify = */ true)) { barrier = false; } // We can avoid worrying about holes in the IC if we know a priori we are safe // from them. If TI can guard that there are no indexed properties on the prototype
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -424,17 +424,17 @@ class IonBuilder AbortReasonOr<Ok> getElemTryGetProp(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr<Ok> getElemTryTypedStatic(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr<Ok> getElemTryTypedArray(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr<Ok> getElemTryTypedObject(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr<Ok> getElemTryString(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr<Ok> getElemTryArguments(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr<Ok> getElemTryArgumentsInlined(bool* emitted, MDefinition* obj, MDefinition* index); - AbortReasonOr<Ok> getElemTryCache(bool* emitted, MDefinition* obj, MDefinition* index); + AbortReasonOr<Ok> getElemAddCache(MDefinition* obj, MDefinition* index); AbortReasonOr<Ok> getElemTryScalarElemOfTypedObject(bool* emitted, MDefinition* obj, MDefinition* index, TypedObjectPrediction objTypeReprs, TypedObjectPrediction elemTypeReprs, uint32_t elemSize); AbortReasonOr<Ok> getElemTryReferenceElemOfTypedObject(bool* emitted, MDefinition* obj,
--- a/js/src/jit/IonCacheIRCompiler.cpp +++ b/js/src/jit/IonCacheIRCompiler.cpp @@ -538,17 +538,17 @@ IonCacheIRCompiler::emitGuardProto() masm.branchPtr(Assembler::NotEqual, scratch, ImmGCPtr(proto), failure->label()); return true; } bool IonCacheIRCompiler::emitGuardCompartment() { Register obj = allocator.useRegister(masm, reader.objOperandId()); - objectStubField(reader.stubOffset()); // Read global. + objectStubField(reader.stubOffset()); // Read global wrapper. JSCompartment* compartment = compartmentStubField(reader.stubOffset()); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false;
--- a/js/src/jit/MacroAssembler-inl.h +++ b/js/src/jit/MacroAssembler-inl.h @@ -404,16 +404,24 @@ MacroAssembler::branchIfRopeOrExternal(R static_assert(JSString::ROPE_FLAGS == 0, "Rope type flags must be 0"); branchTest32(Assembler::Zero, temp, temp, label); branch32(Assembler::Equal, temp, Imm32(JSString::EXTERNAL_FLAGS), label); } void +MacroAssembler::branchIfNotRope(Register str, Label* label) +{ + Address flags(str, JSString::offsetOfFlags()); + static_assert(JSString::ROPE_FLAGS == 0, "Rope type flags must be 0"); + branchTest32(Assembler::NonZero, flags, Imm32(JSString::TYPE_FLAGS_MASK), label); +} + +void MacroAssembler::branchLatin1String(Register string, Label* label) { branchTest32(Assembler::NonZero, Address(string, JSString::offsetOfFlags()), Imm32(JSString::LATIN1_CHARS_BIT), label); } void MacroAssembler::branchTwoByteString(Register string, Label* label)
--- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -1378,29 +1378,50 @@ MacroAssembler::loadStringChars(Register bind(&isInline); computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest); bind(&done); } void -MacroAssembler::loadStringChar(Register str, Register index, Register output) +MacroAssembler::loadStringChar(Register str, Register index, Register output, Label* fail) { MOZ_ASSERT(str != output); MOZ_ASSERT(index != output); - loadStringChars(str, output); + movePtr(str, output); + + // This follows JSString::getChar. + Label notRope; + branchIfNotRope(str, ¬Rope); + + // Load leftChild. + loadPtr(Address(str, JSRope::offsetOfLeft()), output); + + // Check if the index is contained in the leftChild. + // Todo: Handle index in the rightChild. + branch32(Assembler::BelowOrEqual, Address(output, JSString::offsetOfLength()), index, fail); + + // If the left side is another rope, give up. + branchIfRope(output, fail); + + bind(¬Rope); Label isLatin1, done; - branchLatin1String(str, &isLatin1); + // We have to check the left/right side for ropes, + // because a TwoByte rope might have a Latin1 child. + branchLatin1String(output, &isLatin1); + + loadStringChars(output, output); load16ZeroExtend(BaseIndex(output, index, TimesTwo), output); jump(&done); bind(&isLatin1); + loadStringChars(output, output); load8ZeroExtend(BaseIndex(output, index, TimesOne), output); bind(&done); } void MacroAssembler::loadJSContext(Register dest) {
--- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1112,16 +1112,18 @@ class MacroAssembler : public MacroAssem inline void branchIfFalseBool(Register reg, L label); // Branches to |label| if |reg| is true. |reg| should be a C++ bool. inline void branchIfTrueBool(Register reg, Label* label); inline void branchIfRope(Register str, Label* label); inline void branchIfRopeOrExternal(Register str, Register temp, Label* label); + inline void branchIfNotRope(Register str, Label* label); + inline void branchLatin1String(Register string, Label* label); inline void branchTwoByteString(Register string, Label* label); inline void branchIfFunctionHasNoScript(Register fun, Label* label); inline void branchIfInterpreted(Register fun, Label* label); inline void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind, Register fun, Register scratch, Label* label); @@ -1510,17 +1512,17 @@ class MacroAssembler : public MacroAssem loadPtr(Address(dest, ObjectGroup::offsetOfProto()), dest); } void loadStringLength(Register str, Register dest) { load32(Address(str, JSString::offsetOfLength()), dest); } void loadStringChars(Register str, Register dest); - void loadStringChar(Register str, Register index, Register output); + void loadStringChar(Register str, Register index, Register output, Label* fail); void loadJSContext(Register dest); void loadJitActivation(Register dest) { loadJSContext(dest); loadPtr(Address(dest, offsetof(JSContext, activation_)), dest); } void loadWasmActivationFromTls(Register dest);
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6093,32 +6093,32 @@ JS_ObjectIsRegExp(JSContext* cx, HandleO } JS_PUBLIC_API(unsigned) JS_GetRegExpFlags(JSContext* cx, HandleObject obj) { AssertHeapIsIdle(); CHECK_REQUEST(cx); - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpToShared(cx, obj, &shared)) return false; - return shared.re()->getFlags(); + return shared->getFlags(); } JS_PUBLIC_API(JSString*) JS_GetRegExpSource(JSContext* cx, HandleObject obj) { AssertHeapIsIdle(); CHECK_REQUEST(cx); - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpToShared(cx, obj, &shared)) return nullptr; - return shared.re()->getSource(); + return shared->getSource(); } /************************************************************************/ JS_PUBLIC_API(bool) JS_SetDefaultLocale(JSContext* cx, const char* locale) { AssertHeapIsIdle();
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -62,17 +62,17 @@ JSCompartment::JSCompartment(Zone* zone, firedOnNewGlobalObject(false), #endif global_(nullptr), enterCompartmentDepth(0), performanceMonitoring(runtime_), data(nullptr), allocationMetadataBuilder(nullptr), lastAnimationTime(0), - regExps(runtime_), + regExps(zone), globalWriteBarriered(0), detachedTypedObjects(0), objectMetadataState(ImmediateMetadata()), selfHostingScriptSource(nullptr), objectMetadataTable(nullptr), innerViews(zone, InnerViewTable()), lazyArrayBuffers(nullptr), wasm(zone), @@ -223,16 +223,23 @@ JSCompartment::ensureJitCompartmentExist jitCompartment_ = nullptr; return false; } return true; } #ifdef JSGC_HASH_TABLE_CHECKS + +void +js::DtoaCache::checkCacheAfterMovingGC() +{ + MOZ_ASSERT(!s || !IsForwarded(s)); +} + namespace { struct CheckGCThingAfterMovingGCFunctor { template <class T> void operator()(T* t) { CheckGCThingAfterMovingGC(*t); } }; } // namespace (anonymous) void JSCompartment::checkWrapperMapAfterMovingGC() @@ -245,17 +252,18 @@ JSCompartment::checkWrapperMapAfterMovin for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { e.front().mutableKey().applyToWrapped(CheckGCThingAfterMovingGCFunctor()); e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor()); WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key()); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); } } -#endif + +#endif // JSGC_HASH_TABLE_CHECKS bool JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, const js::Value& wrapper) { MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString()); MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject()); @@ -413,18 +421,16 @@ JSCompartment::getNonWrapperObjectForCur MOZ_ASSERT(!IsWindow(obj)); return true; } bool JSCompartment::getOrCreateWrapper(JSContext* cx, HandleObject existing, MutableHandleObject obj) { - MOZ_ASSERT(JS::ObjectIsNotGray(obj)); - // If we already have a wrapper for this value, use it. RootedValue key(cx, ObjectValue(*obj)); if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) { obj.set(&p->value().get().toObject()); MOZ_ASSERT(obj->is<CrossCompartmentWrapperObject>()); return true; }
--- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -61,17 +61,17 @@ class DtoaCache { void cache(int base, double d, JSFlatString* s) { this->base = base; this->d = d; this->s = s; } #ifdef JSGC_HASH_TABLE_CHECKS - void checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); } + void checkCacheAfterMovingGC(); #endif }; // Cache to speed up the group/shape lookup in ProxyObject::create. A proxy's // group/shape is only determined by the Class + proto, so a small cache for // this is very effective in practice. class NewProxyCache {
--- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1158,19 +1158,19 @@ CastToJSFreeOp(FreeOp* fop) extern JS_FRIEND_API(JSFlatString*) GetErrorTypeName(JSContext* cx, int16_t exnType); #ifdef JS_DEBUG extern JS_FRIEND_API(unsigned) GetEnterCompartmentDepth(JSContext* cx); #endif -class RegExpGuard; extern JS_FRIEND_API(bool) -RegExpToSharedNonInline(JSContext* cx, JS::HandleObject regexp, RegExpGuard* shared); +RegExpToSharedNonInline(JSContext* cx, JS::HandleObject regexp, + JS::MutableHandle<RegExpShared*> shared); /* Implemented in CrossCompartmentWrapper.cpp. */ typedef enum NukeReferencesToWindow { NukeWindowReferences, DontNukeWindowReferences } NukeReferencesToWindow; typedef enum NukeReferencesFromTarget {
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -362,17 +362,22 @@ static const FinalizePhase BackgroundFin AllocKind::OBJECT4_BACKGROUND, AllocKind::OBJECT8_BACKGROUND, AllocKind::OBJECT12_BACKGROUND, AllocKind::OBJECT16_BACKGROUND } }, { gcstats::PHASE_SWEEP_SCOPE, { - AllocKind::SCOPE + AllocKind::SCOPE, + } + }, + { + gcstats::PHASE_SWEEP_REGEXP_SHARED, { + AllocKind::REGEXP_SHARED, } }, { gcstats::PHASE_SWEEP_STRING, { AllocKind::FAT_INLINE_STRING, AllocKind::STRING, AllocKind::FAT_INLINE_ATOM, AllocKind::ATOM, @@ -1717,25 +1722,26 @@ static const AllocKind AllocKindsToReloc AllocKind::OBJECT8, AllocKind::OBJECT8_BACKGROUND, AllocKind::OBJECT12, AllocKind::OBJECT12_BACKGROUND, AllocKind::OBJECT16, AllocKind::OBJECT16_BACKGROUND, AllocKind::SCRIPT, AllocKind::LAZY_SCRIPT, - AllocKind::SCOPE, AllocKind::SHAPE, AllocKind::ACCESSOR_SHAPE, AllocKind::BASE_SHAPE, AllocKind::FAT_INLINE_STRING, AllocKind::STRING, AllocKind::EXTERNAL_STRING, AllocKind::FAT_INLINE_ATOM, - AllocKind::ATOM + AllocKind::ATOM, + AllocKind::SCOPE, + AllocKind::REGEXP_SHARED }; Arena* ArenaList::removeRemainingArenas(Arena** arenap) { // This is only ever called to remove arenas that are after the cursor, so // we don't need to update it. #ifdef DEBUG @@ -2049,71 +2055,33 @@ GCRuntime::relocateArenas(Zone* zone, JS freeCells += arena->countFreeCells(); MOZ_ASSERT(freeCells < Arena::thingsPerArena(i)); } #endif return true; } -void -MovingTracer::onObjectEdge(JSObject** objp) -{ - JSObject* obj = *objp; - if (obj->runtimeFromAnyThread() == runtime() && IsForwarded(obj)) - *objp = Forwarded(obj); -} - -void -MovingTracer::onShapeEdge(Shape** shapep) -{ - Shape* shape = *shapep; - if (shape->runtimeFromAnyThread() == runtime() && IsForwarded(shape)) - *shapep = Forwarded(shape); -} - -void -MovingTracer::onStringEdge(JSString** stringp) -{ - JSString* string = *stringp; - if (string->runtimeFromAnyThread() == runtime() && IsForwarded(string)) - *stringp = Forwarded(string); -} - -void -MovingTracer::onScriptEdge(JSScript** scriptp) -{ - JSScript* script = *scriptp; - if (script->runtimeFromAnyThread() == runtime() && IsForwarded(script)) - *scriptp = Forwarded(script); -} - -void -MovingTracer::onLazyScriptEdge(LazyScript** lazyp) -{ - LazyScript* lazy = *lazyp; - if (lazy->runtimeFromAnyThread() == runtime() && IsForwarded(lazy)) - *lazyp = Forwarded(lazy); -} - -void -MovingTracer::onBaseShapeEdge(BaseShape** basep) -{ - BaseShape* base = *basep; - if (base->runtimeFromAnyThread() == runtime() && IsForwarded(base)) - *basep = Forwarded(base); -} - -void -MovingTracer::onScopeEdge(Scope** scopep) -{ - Scope* scope = *scopep; - if (scope->runtimeFromAnyThread() == runtime() && IsForwarded(scope)) - *scopep = Forwarded(scope); -} +template <typename T> +inline void +MovingTracer::updateEdge(T** thingp) +{ + auto thing = *thingp; + if (thing->runtimeFromAnyThread() == runtime() && IsForwarded(thing)) + *thingp = Forwarded(thing); +} + +void MovingTracer::onObjectEdge(JSObject** objp) { updateEdge(objp); } +void MovingTracer::onShapeEdge(Shape** shapep) { updateEdge(shapep); } +void MovingTracer::onStringEdge(JSString** stringp) { updateEdge(stringp); } +void MovingTracer::onScriptEdge(JSScript** scriptp) { updateEdge(scriptp); } +void MovingTracer::onLazyScriptEdge(LazyScript** lazyp) { updateEdge(lazyp); } +void MovingTracer::onBaseShapeEdge(BaseShape** basep) { updateEdge(basep); } +void MovingTracer::onScopeEdge(Scope** scopep) { updateEdge(scopep); } +void MovingTracer::onRegExpSharedEdge(RegExpShared** sharedp) { updateEdge(sharedp); } void Zone::prepareForCompacting() { FreeOp* fop = runtimeFromActiveCooperatingThread()->defaultFreeOp(); discardJitCode(fop); }
--- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -116,16 +116,17 @@ IsNurseryAllocable(AllocKind kind) false, /* AllocKind::FAT_INLINE_STRING */ false, /* AllocKind::STRING */ false, /* AllocKind::EXTERNAL_STRING */ false, /* AllocKind::FAT_INLINE_ATOM */ false, /* AllocKind::ATOM */ false, /* AllocKind::SYMBOL */ false, /* AllocKind::JITCODE */ false, /* AllocKind::SCOPE */ + false, /* AllocKind::REGEXP_SHARED */ }; JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT)); return map[size_t(kind)]; } static inline bool IsBackgroundFinalized(AllocKind kind) { @@ -154,16 +155,17 @@ IsBackgroundFinalized(AllocKind kind) true, /* AllocKind::FAT_INLINE_STRING */ true, /* AllocKind::STRING */ false, /* AllocKind::EXTERNAL_STRING */ true, /* AllocKind::FAT_INLINE_ATOM */ true, /* AllocKind::ATOM */ true, /* AllocKind::SYMBOL */ false, /* AllocKind::JITCODE */ true, /* AllocKind::SCOPE */ + true, /* AllocKind::REGEXP_SHARED */ }; JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT)); return map[size_t(kind)]; } static inline bool CanBeFinalizedInBackground(AllocKind kind, const Class* clasp) { @@ -1131,119 +1133,39 @@ class RelocationOverlay // pointer to old location. // // MaybeForwarded - used before dereferencing a pointer that may refer to a // moved GC thing without updating it. For JSObjects this will // also update the object's shape pointer if it has been moved // to allow slots to be accessed. template <typename T> -struct MightBeForwarded -{ - static_assert(mozilla::IsBaseOf<Cell, T>::value, - "T must derive from Cell"); - static_assert(!mozilla::IsSame<Cell, T>::value && !mozilla::IsSame<TenuredCell, T>::value, - "T must not be Cell or TenuredCell"); - - static const bool value = mozilla::IsBaseOf<JSObject, T>::value || - mozilla::IsBaseOf<Shape, T>::value || - mozilla::IsBaseOf<BaseShape, T>::value || - mozilla::IsBaseOf<JSString, T>::value || - mozilla::IsBaseOf<JSScript, T>::value || - mozilla::IsBaseOf<js::LazyScript, T>::value || - mozilla::IsBaseOf<js::Scope, T>::value; -}; +inline bool IsForwarded(T* t); +inline bool IsForwarded(const JS::Value& value); template <typename T> -inline bool -IsForwarded(T* t) -{ - RelocationOverlay* overlay = RelocationOverlay::fromCell(t); - if (!MightBeForwarded<T>::value) { - MOZ_ASSERT(!overlay->isForwarded()); - return false; - } +inline T* Forwarded(T* t); - return overlay->isForwarded(); -} - -struct IsForwardedFunctor : public BoolDefaultAdaptor<Value, false> { - template <typename T> bool operator()(T* t) { return IsForwarded(t); } -}; - -inline bool -IsForwarded(const JS::Value& value) -{ - return DispatchTyped(IsForwardedFunctor(), value); -} +inline Value Forwarded(const JS::Value& value); template <typename T> -inline T* -Forwarded(T* t) -{ - RelocationOverlay* overlay = RelocationOverlay::fromCell(t); - MOZ_ASSERT(overlay->isForwarded()); - return reinterpret_cast<T*>(overlay->forwardingAddress()); -} - -struct ForwardedFunctor : public IdentityDefaultAdaptor<Value> { - template <typename T> inline Value operator()(T* t) { - return js::gc::RewrapTaggedPointer<Value, T>::wrap(Forwarded(t)); - } -}; - -inline Value -Forwarded(const JS::Value& value) -{ - return DispatchTyped(ForwardedFunctor(), value); -} - -template <typename T> -inline T -MaybeForwarded(T t) -{ - if (IsForwarded(t)) - t = Forwarded(t); - MakeAccessibleAfterMovingGC(t); - return t; -} +inline T MaybeForwarded(T t); #ifdef JSGC_HASH_TABLE_CHECKS template <typename T> -inline bool -IsGCThingValidAfterMovingGC(T* t) -{ - return !IsInsideNursery(t) && !RelocationOverlay::isCellForwarded(t); -} - -template <typename T> -inline void -CheckGCThingAfterMovingGC(T* t) -{ - if (t) - MOZ_RELEASE_ASSERT(IsGCThingValidAfterMovingGC(t)); -} +inline bool IsGCThingValidAfterMovingGC(T* t); template <typename T> -inline void -CheckGCThingAfterMovingGC(const ReadBarriered<T*>& t) -{ - CheckGCThingAfterMovingGC(t.unbarrieredGet()); -} +inline void CheckGCThingAfterMovingGC(T* t); -struct CheckValueAfterMovingGCFunctor : public VoidDefaultAdaptor<Value> { - template <typename T> void operator()(T* t) { CheckGCThingAfterMovingGC(t); } -}; +template <typename T> +inline void CheckGCThingAfterMovingGC(const ReadBarriered<T*>& t); -inline void -CheckValueAfterMovingGC(const JS::Value& value) -{ - DispatchTyped(CheckValueAfterMovingGCFunctor(), value); -} +inline void CheckValueAfterMovingGC(const JS::Value& value); #endif // JSGC_HASH_TABLE_CHECKS #define JS_FOR_EACH_ZEAL_MODE(D) \ D(Poke, 1) \ D(Alloc, 2) \ D(FrameGC, 3) \ D(VerifierPre, 4) \
--- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -483,12 +483,120 @@ RelocationOverlay::forwardTo(Cell* cell) static_assert(offsetof(RelocationOverlay, magic_) == offsetof(JSObject, group_) && offsetof(RelocationOverlay, magic_) == offsetof(js::Shape, base_) && offsetof(RelocationOverlay, magic_) == offsetof(JSString, d.u1.flags), "RelocationOverlay::magic_ is in the wrong location"); magic_ = Relocated; newLocation_ = cell; } +template <typename T> +struct MightBeForwarded +{ + static_assert(mozilla::IsBaseOf<Cell, T>::value, + "T must derive from Cell"); + static_assert(!mozilla::IsSame<Cell, T>::value && !mozilla::IsSame<TenuredCell, T>::value, + "T must not be Cell or TenuredCell"); + + static const bool value = mozilla::IsBaseOf<JSObject, T>::value || + mozilla::IsBaseOf<Shape, T>::value || + mozilla::IsBaseOf<BaseShape, T>::value || + mozilla::IsBaseOf<JSString, T>::value || + mozilla::IsBaseOf<JSScript, T>::value || + mozilla::IsBaseOf<js::LazyScript, T>::value || + mozilla::IsBaseOf<js::Scope, T>::value || + mozilla::IsBaseOf<js::RegExpShared, T>::value; +}; + +template <typename T> +inline bool +IsForwarded(T* t) +{ + RelocationOverlay* overlay = RelocationOverlay::fromCell(t); + if (!MightBeForwarded<T>::value) { + MOZ_ASSERT(!overlay->isForwarded()); + return false; + } + + return overlay->isForwarded(); +} + +struct IsForwardedFunctor : public BoolDefaultAdaptor<Value, false> { + template <typename T> bool operator()(T* t) { return IsForwarded(t); } +}; + +inline bool +IsForwarded(const JS::Value& value) +{ + return DispatchTyped(IsForwardedFunctor(), value); +} + +template <typename T> +inline T* +Forwarded(T* t) +{ + RelocationOverlay* overlay = RelocationOverlay::fromCell(t); + MOZ_ASSERT(overlay->isForwarded()); + return reinterpret_cast<T*>(overlay->forwardingAddress()); +} + +struct ForwardedFunctor : public IdentityDefaultAdaptor<Value> { + template <typename T> inline Value operator()(T* t) { + return js::gc::RewrapTaggedPointer<Value, T>::wrap(Forwarded(t)); + } +}; + +inline Value +Forwarded(const JS::Value& value) +{ + return DispatchTyped(ForwardedFunctor(), value); +} + +template <typename T> +inline T +MaybeForwarded(T t) +{ + if (IsForwarded(t)) + t = Forwarded(t); + MakeAccessibleAfterMovingGC(t); + return t; +} + +#ifdef JSGC_HASH_TABLE_CHECKS + +template <typename T> +inline bool +IsGCThingValidAfterMovingGC(T* t) +{ + return !IsInsideNursery(t) && !RelocationOverlay::isCellForwarded(t); +} + +template <typename T> +inline void +CheckGCThingAfterMovingGC(T* t) +{ + if (t) + MOZ_RELEASE_ASSERT(IsGCThingValidAfterMovingGC(t)); +} + +template <typename T> +inline void +CheckGCThingAfterMovingGC(const ReadBarriered<T*>& t) +{ + CheckGCThingAfterMovingGC(t.unbarrieredGet()); +} + +struct CheckValueAfterMovingGCFunctor : public VoidDefaultAdaptor<Value> { + template <typename T> void operator()(T* t) { CheckGCThingAfterMovingGC(t); } +}; + +inline void +CheckValueAfterMovingGC(const JS::Value& value) +{ + DispatchTyped(CheckValueAfterMovingGCFunctor(), value); +} + +#endif // JSGC_HASH_TABLE_CHECKS + } /* namespace gc */ } /* namespace js */ #endif /* jsgcinlines_h */
--- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -113,17 +113,17 @@ class JS_FRIEND_API(Wrapper) : public Ba bool* bp) const override; virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override; virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const override; + MutableHandle<RegExpShared*> shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; virtual bool isCallable(JSObject* obj) const override; virtual bool isConstructor(JSObject* obj) const override; virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override; public: using BaseProxyHandler::Action; @@ -136,17 +136,16 @@ class JS_FRIEND_API(Wrapper) : public Ba static JSObject* New(JSContext* cx, JSObject* obj, const Wrapper* handler, const WrapperOptions& options = WrapperOptions()); static JSObject* Renew(JSObject* existing, JSObject* obj, const Wrapper* handler); static const Wrapper* wrapperHandler(JSObject* wrapper); static JSObject* wrappedObject(JSObject* wrapper); - static JSObject* wrappedObjectMaybeGray(JSObject* wrapper); unsigned flags() const { return mFlags; } static const char family; static const Wrapper singleton; static const Wrapper singletonWithPrototype; @@ -208,17 +207,18 @@ class JS_FRIEND_API(CrossCompartmentWrap AutoIdVector& props) const override; virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args) const override; virtual bool hasInstance(JSContext* cx, HandleObject wrapper, MutableHandleValue v, bool* bp) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject wrapper, unsigned indent) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<RegExpShared*> shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; // Allocate CrossCompartmentWrappers in the nursery. virtual bool canNurseryAllocate() const override { return true; } static const CrossCompartmentWrapper singleton; static const CrossCompartmentWrapper singletonWithPrototype; }; @@ -306,17 +306,18 @@ class JS_FRIEND_API(SecurityWrapper) : p virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) const override; virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const override; virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args) const override; virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper, ESClass* cls) const override; virtual bool isArray(JSContext* cx, HandleObject wrapper, JS::IsArrayAnswer* answer) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<RegExpShared*> shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; // Allow isCallable and isConstructor. They used to be class-level, and so could not be guarded // against. virtual bool watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable) const override; virtual bool unwatch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id) const override;
--- a/js/src/proxy/BaseProxyHandler.cpp +++ b/js/src/proxy/BaseProxyHandler.cpp @@ -322,17 +322,17 @@ BaseProxyHandler::fun_toString(JSContext return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}"); RootedValue v(cx, ObjectValue(*proxy)); ReportIsNotFunction(cx, v); return nullptr; } bool BaseProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const + MutableHandleRegExpShared shared) const { MOZ_CRASH("This should have been a wrapped regexp"); } bool BaseProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const { vp.setUndefined();
--- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -453,29 +453,30 @@ CrossCompartmentWrapper::fun_toString(JS return nullptr; } if (!cx->compartment()->wrap(cx, &str)) return nullptr; return str; } bool -CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, RegExpGuard* g) const +CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, + MutableHandleRegExpShared shared) const { - RegExpGuard wrapperGuard(cx); + RootedRegExpShared re(cx); { AutoCompartment call(cx, wrappedObject(wrapper)); - if (!Wrapper::regexp_toShared(cx, wrapper, &wrapperGuard)) + if (!Wrapper::regexp_toShared(cx, wrapper, &re)) return false; } // Get an equivalent RegExpShared associated with the current compartment. - RegExpShared* re = wrapperGuard.re(); - cx->markAtom(re->getSource()); - return cx->compartment()->regExps.get(cx, re->getSource(), re->getFlags(), g); + RootedAtom source(cx, re->getSource()); + cx->markAtom(source); + return cx->compartment()->regExps.get(cx, source, re->getFlags(), shared); } bool CrossCompartmentWrapper::boxedValue_unbox(JSContext* cx, HandleObject wrapper, MutableHandleValue vp) const { PIERCE(cx, wrapper, NOTHING, Wrapper::boxedValue_unbox(cx, wrapper, vp), @@ -640,18 +641,16 @@ js::RemapWrapper(JSContext* cx, JSObject // Remap all cross-compartment wrappers pointing to |oldTarget| to point to // |newTarget|. All wrappers are recomputed. JS_FRIEND_API(bool) js::RemapAllWrappersForObject(JSContext* cx, JSObject* oldTargetArg, JSObject* newTargetArg) { MOZ_ASSERT(!IsInsideNursery(oldTargetArg)); MOZ_ASSERT(!IsInsideNursery(newTargetArg)); - MOZ_ASSERT(JS::ObjectIsNotGray(oldTargetArg)); - MOZ_ASSERT(JS::ObjectIsNotGray(newTargetArg)); RootedValue origv(cx, ObjectValue(*oldTargetArg)); RootedObject newTarget(cx, newTargetArg); AutoWrapperVector toTransplant(cx); if (!toTransplant.reserve(cx->runtime()->numCompartments)) return false; @@ -696,15 +695,14 @@ js::RecomputeWrappers(JSContext* cx, con if (!toRecompute.append(WrapperValue(e))) return false; } } // Recompute all the wrappers in the list. for (const WrapperValue& v : toRecompute) { JSObject* wrapper = &v.toObject(); - JSObject* wrapped = Wrapper::wrappedObjectMaybeGray(wrapper); - JS::ExposeObjectToActiveJS(wrapped); + JSObject* wrapped = Wrapper::wrappedObject(wrapper); RemapWrapper(cx, wrapper, wrapped); } return true; }
--- a/js/src/proxy/DeadObjectProxy.cpp +++ b/js/src/proxy/DeadObjectProxy.cpp @@ -137,17 +137,18 @@ DeadObjectProxy::className(JSContext* cx JSString* DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const { ReportDead(cx); return nullptr; } bool -DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<RegExpShared*> shared) const { ReportDead(cx); return false; } const char DeadObjectProxy::family = 0; const DeadObjectProxy DeadObjectProxy::singleton;
--- a/js/src/proxy/DeadObjectProxy.h +++ b/js/src/proxy/DeadObjectProxy.h @@ -44,17 +44,18 @@ class DeadObjectProxy : public BaseProxy virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args) const override; virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const override; virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override; virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<RegExpShared*> shared) const override; static const char family; static const DeadObjectProxy singleton; }; bool IsDeadProxyObject(JSObject* obj);
--- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -554,21 +554,21 @@ Proxy::fun_toString(JSContext* cx, Handl BaseProxyHandler::GET, /* mayThrow = */ false); // Do the safe thing if the policy rejects. if (!policy.allowed()) return handler->BaseProxyHandler::fun_toString(cx, proxy, indent); return handler->fun_toString(cx, proxy, indent); } bool -Proxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) +Proxy::regexp_toShared(JSContext* cx, HandleObject proxy, MutableHandleRegExpShared shared) { if (!CheckRecursionLimit(cx)) return false; - return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy, g); + return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy, shared); } bool Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) { if (!CheckRecursionLimit(cx)) return false; return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp);
--- a/js/src/proxy/Proxy.h +++ b/js/src/proxy/Proxy.h @@ -8,18 +8,16 @@ #define proxy_Proxy_h #include "NamespaceImports.h" #include "js/Class.h" namespace js { -class RegExpGuard; - /* * Dispatch point for handlers that executes the appropriate C++ or scripted traps. * * Important: All proxy methods need either (a) an AutoEnterPolicy in their * Proxy::foo entry point below or (b) an override in SecurityWrapper. See bug * 945826 comment 0. */ class Proxy @@ -57,17 +55,18 @@ class Proxy AutoIdVector& props); static bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args); static bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp); static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls); static bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer); static const char* className(JSContext* cx, HandleObject proxy); static JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent); - static bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g); + static bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle<RegExpShared*> shared); static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp); static bool watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable); static bool unwatch(JSContext* cx, HandleObject proxy, HandleId id); static bool getElements(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, ElementAdder* adder);
--- a/js/src/proxy/ScriptedProxyHandler.cpp +++ b/js/src/proxy/ScriptedProxyHandler.cpp @@ -1263,17 +1263,18 @@ JSString* ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, js_Function_str, js_toString_str, "object"); return nullptr; } bool -ScriptedProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +ScriptedProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandleRegExpShared shared) const { MOZ_CRASH("Should not end up in ScriptedProxyHandler::regexp_toShared"); return false; } bool ScriptedProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const
--- a/js/src/proxy/ScriptedProxyHandler.h +++ b/js/src/proxy/ScriptedProxyHandler.h @@ -65,17 +65,17 @@ class ScriptedProxyHandler : public Base bool* bp) const override; virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override; virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const override; + MutableHandle<RegExpShared*> shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; virtual bool isCallable(JSObject* obj) const override; virtual bool isConstructor(JSObject* obj) const override; virtual bool isScripted() const override { return true; }
--- a/js/src/proxy/SecurityWrapper.cpp +++ b/js/src/proxy/SecurityWrapper.cpp @@ -83,19 +83,20 @@ SecurityWrapper<Base>::isArray(JSContext { // This should ReportAccessDenied(cx), but bug 849730 disagrees. :-( *answer = JS::IsArrayAnswer::NotArray; return true; } template <class Base> bool -SecurityWrapper<Base>::regexp_toShared(JSContext* cx, HandleObject obj, RegExpGuard* g) const +SecurityWrapper<Base>::regexp_toShared(JSContext* cx, HandleObject obj, + MutableHandle<RegExpShared*> shared) const { - return Base::regexp_toShared(cx, obj, g); + return Base::regexp_toShared(cx, obj, shared); } template <class Base> bool SecurityWrapper<Base>::boxedValue_unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp) const { vp.setUndefined(); return true;
--- a/js/src/proxy/Wrapper.cpp +++ b/js/src/proxy/Wrapper.cpp @@ -263,20 +263,20 @@ JSString* Wrapper::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const { assertEnteredPolicy(cx, proxy, JSID_VOID, GET); RootedObject target(cx, proxy->as<ProxyObject>().target()); return fun_toStringHelper(cx, target, indent); } bool -Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy, MutableHandleRegExpShared shared) const { RootedObject target(cx, proxy->as<ProxyObject>().target()); - return RegExpToShared(cx, target, g); + return RegExpToShared(cx, target, shared); } bool Wrapper::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const { RootedObject target(cx, proxy->as<ProxyObject>().target()); return Unbox(cx, target, vp); } @@ -323,40 +323,35 @@ Wrapper::wrapperHandler(JSObject* wrappe { MOZ_ASSERT(wrapper->is<WrapperObject>()); return static_cast<const Wrapper*>(wrapper->as<ProxyObject>().handler()); } JSObject* Wrapper::wrappedObject(JSObject* wrapper) { - JSObject* target = wrappedObjectMaybeGray(wrapper); - MOZ_ASSERT(JS::ObjectIsNotGray(target)); + MOZ_ASSERT(wrapper->is<WrapperObject>()); + JSObject* target = wrapper->as<ProxyObject>().target(); + if (target) + JS::ExposeObjectToActiveJS(target); return target; } -JSObject* -Wrapper::wrappedObjectMaybeGray(JSObject* wrapper) -{ - MOZ_ASSERT(wrapper->is<WrapperObject>()); - return wrapper->as<ProxyObject>().target(); -} - JS_FRIEND_API(JSObject*) js::UncheckedUnwrap(JSObject* wrapped, bool stopAtWindowProxy, unsigned* flagsp) { unsigned flags = 0; while (true) { if (!wrapped->is<WrapperObject>() || MOZ_UNLIKELY(stopAtWindowProxy && IsWindowProxy(wrapped))) { break; } flags |= Wrapper::wrapperHandler(wrapped)->flags(); - wrapped = wrapped->as<ProxyObject>().target(); + wrapped = wrapped->as<ProxyObject>().private_().toObjectOrNull(); // This can be called from Wrapper::weakmapKeyDelegate() on a wrapper // whose referent has been moved while it is still unmarked. if (wrapped) wrapped = MaybeForwarded(wrapped); } if (flagsp) *flagsp = flags;
--- a/js/src/vm/GeckoProfiler.cpp +++ b/js/src/vm/GeckoProfiler.cpp @@ -14,16 +14,18 @@ #include "jit/BaselineFrame.h" #include "jit/BaselineJIT.h" #include "jit/JitcodeMap.h" #include "jit/JitFrameIterator.h" #include "jit/JitFrames.h" #include "vm/StringBuffer.h" +#include "jsgcinlines.h" + using namespace js; using mozilla::DebugOnly; GeckoProfiler::GeckoProfiler(JSRuntime* rt) : rt(rt), strings(mutexid::GeckoProfilerStrings), stack_(nullptr),
--- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -593,16 +593,23 @@ StatsCellCallback(JSRuntime* rt, void* d case JS::TraceKind::Scope: { Scope* scope = static_cast<Scope*>(thing); zStats->scopesGCHeap += thingSize; zStats->scopesMallocHeap += scope->sizeOfExcludingThis(rtStats->mallocSizeOf_); break; } + case JS::TraceKind::RegExpShared: { + auto regexp = static_cast<RegExpShared*>(thing); + zStats->regExpSharedsGCHeap += thingSize; + zStats->regExpSharedsMallocHeap += regexp->sizeOfExcludingThis(rtStats->mallocSizeOf_); + break; + } + default: MOZ_CRASH("invalid traceKind in StatsCellCallback"); } // Yes, this is a subtraction: see StatsArenaCallback() for details. zStats->unusedGCThings.addToKind(traceKind, -thingSize); }
--- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -1222,17 +1222,16 @@ class NativeObject : public ShapedObject } void setPrivate(void* data) { void** pprivate = &privateRef(numFixedSlots()); privateWriteBarrierPre(pprivate); *pprivate = data; } void setPrivateGCThing(gc::Cell* cell) { - CheckEdgeIsNotBlackToGray(this, cell); void** pprivate = &privateRef(numFixedSlots()); privateWriteBarrierPre(pprivate); *pprivate = reinterpret_cast<void*>(cell); privateWriteBarrierPost(pprivate); } void setPrivateUnbarriered(void* data) { void** pprivate = &privateRef(numFixedSlots());
--- a/js/src/vm/Printer.cpp +++ b/js/src/vm/Printer.cpp @@ -436,17 +436,17 @@ Fprinter::finish() fclose(file_); file_ = nullptr; } bool Fprinter::put(const char* s, size_t len) { MOZ_ASSERT(file_); - int i = fwrite(s, len, 1, file_); + int i = fwrite(s, /*size=*/ 1, /*nitems=*/ len, file_); if (size_t(i) != len) { reportOutOfMemory(); return false; } return true; } LSprinter::LSprinter(LifoAlloc* lifoAlloc)
--- a/js/src/vm/ProxyObject.cpp +++ b/js/src/vm/ProxyObject.cpp @@ -96,25 +96,23 @@ ProxyObject::objectMovedDuringMinorGC(Te else dst->zone()->group()->nursery().removeMallocedBuffer(psrc.data.values); return sizeof(detail::ProxyValueArray); } void ProxyObject::setCrossCompartmentPrivate(const Value& priv) { - CheckEdgeIsNotBlackToGray(this, priv); *slotOfPrivate() = priv; } void ProxyObject::setSameCompartmentPrivate(const Value& priv) { MOZ_ASSERT(IsObjectValueInCompartment(priv, compartment())); - CheckEdgeIsNotBlackToGray(this, priv); *slotOfPrivate() = priv; } void ProxyObject::nuke() { // Clear the target reference. setSameCompartmentPrivate(NullValue());
--- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.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 "vm/RegExpObject.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" +#include "jshashutil.h" #include "jsstr.h" #ifdef DEBUG #include "jsutil.h" #endif #include "builtin/RegExp.h" #include "frontend/TokenStream.h" #ifdef DEBUG @@ -114,39 +115,26 @@ VectorMatchPairs::allocOrExpandArray(siz pairs_ = &vec_[0]; pairCount_ = pairCount; return true; } /* RegExpObject */ -static inline void -RegExpSharedReadBarrier(JSContext* cx, RegExpShared* shared) +/* static */ bool +RegExpObject::getShared(JSContext* cx, Handle<RegExpObject*> regexp, + MutableHandleRegExpShared shared) { - Zone* zone = cx->zone(); - if (zone->needsIncrementalBarrier()) - shared->trace(zone->barrierTracer()); - if (shared->isMarkedGray()) - shared->unmarkGray(); -} - -/* static */ bool -RegExpObject::getShared(JSContext* cx, Handle<RegExpObject*> regexp, RegExpGuard* g) -{ - if (RegExpShared* shared = regexp->maybeShared()) { - // Fetching a RegExpShared from an object requires a read - // barrier, as the shared pointer might be weak. - RegExpSharedReadBarrier(cx, shared); - - g->init(*shared); + if (regexp->hasShared()) { + shared.set(regexp->sharedRef()); return true; } - return createShared(cx, regexp, g); + return createShared(cx, regexp, shared); } /* static */ bool RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlag* mask) { if (native == regexp_global) { *mask = GlobalFlag; return true; @@ -169,36 +157,42 @@ RegExpObject::isOriginalFlagGetter(JSNat } return false; } /* static */ void RegExpObject::trace(JSTracer* trc, JSObject* obj) { - RegExpShared* shared = obj->as<RegExpObject>().maybeShared(); - if (!shared) - return; + obj->as<RegExpObject>().trace(trc); +} - // When tracing through the object normally, we have the option of - // unlinking the object from its RegExpShared so that the RegExpShared may - // be collected. To detect this we need to test all the following - // conditions, since: +static inline bool +IsMarkingTrace(JSTracer* trc) +{ + // Determine whether tracing is happening during normal marking. We need to + // test all the following conditions, since: + // // 1. During TraceRuntime, CurrentThreadIsHeapBusy() is true, but the // tracer might not be a marking tracer. // 2. When a write barrier executes, IsMarkingTracer is true, but // CurrentThreadIsHeapBusy() will be false. - if (JS::CurrentThreadIsHeapCollecting() && - trc->isMarkingTracer() && - !obj->asTenured().zone()->isPreservingCode()) - { - obj->as<RegExpObject>().NativeObject::setPrivate(nullptr); - } else { - shared->trace(trc); - } + + return JS::CurrentThreadIsHeapCollecting() && trc->isMarkingTracer(); +} + +void +RegExpObject::trace(JSTracer* trc) +{ + // When marking the object normally we have the option of unlinking the + // object from its RegExpShared so that the RegExpShared may be collected. + if (IsMarkingTrace(trc) && !zone()->isPreservingCode()) + sharedRef() = nullptr; + + TraceNullableEdge(trc, &sharedRef(), "RegExpObject shared"); } static JSObject* CreateRegExpPrototype(JSContext* cx, JSProtoKey key) { return GlobalObject::createBlankPrototype(cx, cx->global(), &RegExpObject::protoClass_); } @@ -275,23 +269,25 @@ RegExpObject::create(JSContext* cx, Hand return nullptr; regexp->initAndZeroLastIndex(source, flags, cx); return regexp; } /* static */ bool -RegExpObject::createShared(JSContext* cx, Handle<RegExpObject*> regexp, RegExpGuard* g) +RegExpObject::createShared(JSContext* cx, Handle<RegExpObject*> regexp, + MutableHandleRegExpShared shared) { - MOZ_ASSERT(!regexp->maybeShared()); - if (!cx->compartment()->regExps.get(cx, regexp->getSource(), regexp->getFlags(), g)) + MOZ_ASSERT(!regexp->hasShared()); + RootedAtom source(cx, regexp->getSource()); + if (!cx->compartment()->regExps.get(cx, source, regexp->getFlags(), shared)) return false; - regexp->setShared(**g); + regexp->setShared(*shared); return true; } Shape* RegExpObject::assignInitialShape(JSContext* cx, Handle<RegExpObject*> self) { MOZ_ASSERT(self->empty()); @@ -503,24 +499,25 @@ RegExpObject::toString(JSContext* cx) co return nullptr; if (sticky() && !sb.append('y')) return nullptr; return sb.finishString(); } #ifdef DEBUG -bool -RegExpShared::dumpBytecode(JSContext* cx, bool match_only, HandleLinearString input) +/* static */ bool +RegExpShared::dumpBytecode(JSContext* cx, MutableHandleRegExpShared re, bool match_only, + HandleLinearString input) { CompilationMode mode = match_only ? MatchOnly : Normal; - if (!compileIfNecessary(cx, input, mode, ForceByteCode)) + if (!RegExpShared::compileIfNecessary(cx, re, input, mode, ForceByteCode)) return false; - const uint8_t* byteCode = compilation(mode, input->hasLatin1Chars()).byteCode; + const uint8_t* byteCode = re->compilation(mode, input->hasLatin1Chars()).byteCode; const uint8_t* pc = byteCode; auto Load32Aligned = [](const uint8_t* pc) -> int32_t { MOZ_ASSERT((reinterpret_cast<uintptr_t>(pc) & 3) == 0); return *reinterpret_cast<const int32_t*>(pc); }; auto Load16Aligned = [](const uint8_t* pc) -> int32_t { @@ -889,21 +886,21 @@ RegExpShared::dumpBytecode(JSContext* cx return true; } /* static */ bool RegExpObject::dumpBytecode(JSContext* cx, Handle<RegExpObject*> regexp, bool match_only, HandleLinearString input) { - RegExpGuard g(cx); - if (!getShared(cx, regexp, &g)) + RootedRegExpShared shared(cx); + if (!getShared(cx, regexp, &shared)) return false; - return g.re()->dumpBytecode(cx, match_only, input); + return RegExpShared::dumpBytecode(cx, &shared, match_only, input); } #endif template <typename CharT> static MOZ_ALWAYS_INLINE bool IsRegExpMetaChar(CharT ch) { switch (ch) { @@ -942,190 +939,181 @@ js::StringHasRegExpMetaChars(JSLinearStr return HasRegExpMetaChars(str->latin1Chars(nogc), str->length()); return HasRegExpMetaChars(str->twoByteChars(nogc), str->length()); } /* RegExpShared */ RegExpShared::RegExpShared(JSAtom* source, RegExpFlag flags) - : source(source), flags(flags), parenCount(0), canStringMatch(false), marked_(false) + : source(source), flags(flags), canStringMatch(false), parenCount(0) {} -RegExpShared::~RegExpShared() +void +RegExpShared::traceChildren(JSTracer* trc) { - for (size_t i = 0; i < tables.length(); i++) - js_delete(tables[i]); -} - -void -RegExpShared::trace(JSTracer* trc) -{ - if (trc->isMarkingTracer()) - marked_ = true; + // Discard code to avoid holding onto ExecutablePools. + if (IsMarkingTrace(trc) && trc->runtime()->gc.isShrinkingGC()) + discardJitCode(); TraceNullableEdge(trc, &source, "RegExpShared source"); for (auto& comp : compilationArray) TraceNullableEdge(trc, &comp.jitCode, "RegExpShared code"); } -bool -RegExpShared::isMarkedGray() const +void +RegExpShared::discardJitCode() { - if (source && source->isMarked(gc::GRAY)) - return true; - for (const auto& comp : compilationArray) { - if (comp.jitCode && comp.jitCode->isMarked(gc::GRAY)) - return true; - } - return false; + for (auto& comp : compilationArray) + comp.jitCode = nullptr; } void -RegExpShared::unmarkGray() +RegExpShared::finalize(FreeOp* fop) { - if (source) - JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr(source)); - for (const auto& comp : compilationArray) { - if (comp.jitCode) - JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr(comp.jitCode.get())); - } + for (auto& comp : compilationArray) + js_free(comp.byteCode); + for (size_t i = 0; i < tables.length(); i++) + js_free(tables[i]); + tables.~JitCodeTables(); } -bool -RegExpShared::compile(JSContext* cx, HandleLinearString input, +/* static */ bool +RegExpShared::compile(JSContext* cx, MutableHandleRegExpShared re, HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force) { TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx); AutoTraceLog logCompile(logger, TraceLogger_IrregexpCompile); - RootedAtom pattern(cx, source); - return compile(cx, pattern, input, mode, force); + RootedAtom pattern(cx, re->source); + return compile(cx, re, pattern, input, mode, force); } -bool -RegExpShared::compile(JSContext* cx, HandleAtom pattern, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force) +/* static */ bool +RegExpShared::compile(JSContext* cx, MutableHandleRegExpShared re, HandleAtom pattern, + HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force) { - if (!ignoreCase() && !StringHasRegExpMetaChars(pattern)) - canStringMatch = true; + if (!re->ignoreCase() && !StringHasRegExpMetaChars(pattern)) + re->canStringMatch = true; CompileOptions options(cx); TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr); LifoAllocScope scope(&cx->tempLifoAlloc()); /* Parse the pattern. */ irregexp::RegExpCompileData data; if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), pattern, - multiline(), mode == MatchOnly, unicode(), ignoreCase(), - global(), sticky(), &data)) + re->multiline(), mode == MatchOnly, re->unicode(), + re->ignoreCase(), re->global(), re->sticky(), &data)) { return false; } - this->parenCount = data.capture_count; + re->parenCount = data.capture_count; - irregexp::RegExpCode code = irregexp::CompilePattern(cx, this, &data, input, + irregexp::RegExpCode code = irregexp::CompilePattern(cx, re, &data, input, false /* global() */, - ignoreCase(), + re->ignoreCase(), input->hasLatin1Chars(), mode == MatchOnly, force == ForceByteCode, - sticky(), unicode()); + re->sticky(), + re->unicode()); if (code.empty()) return false; MOZ_ASSERT(!code.jitCode || !code.byteCode); MOZ_ASSERT_IF(force == ForceByteCode, code.byteCode); - RegExpCompilation& compilation = this->compilation(mode, input->hasLatin1Chars()); + RegExpCompilation& compilation = re->compilation(mode, input->hasLatin1Chars()); if (code.jitCode) compilation.jitCode = code.jitCode; else if (code.byteCode) compilation.byteCode = code.byteCode; return true; } -bool -RegExpShared::compileIfNecessary(JSContext* cx, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force) +/* static */ bool +RegExpShared::compileIfNecessary(JSContext* cx, MutableHandleRegExpShared re, + HandleLinearString input, CompilationMode mode, + ForceByteCodeEnum force) { - if (isCompiled(mode, input->hasLatin1Chars(), force)) + if (re->isCompiled(mode, input->hasLatin1Chars(), force)) return true; - return compile(cx, input, mode, force); + return compile(cx, re, input, mode, force); } -RegExpRunStatus -RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, - MatchPairs* matches, size_t* endIndex) +/* static */ RegExpRunStatus +RegExpShared::execute(JSContext* cx, MutableHandleRegExpShared re, HandleLinearString input, + size_t start, MatchPairs* matches, size_t* endIndex) { MOZ_ASSERT_IF(matches, !endIndex); MOZ_ASSERT_IF(!matches, endIndex); TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx); CompilationMode mode = matches ? Normal : MatchOnly; /* Compile the code at point-of-use. */ - if (!compileIfNecessary(cx, input, mode, DontForceByteCode)) + if (!compileIfNecessary(cx, re, input, mode, DontForceByteCode)) return RegExpRunStatus_Error; /* * Ensure sufficient memory for output vector. * No need to initialize it. The RegExp engine fills them in on a match. */ - if (matches && !matches->allocOrExpandArray(pairCount())) { + if (matches && !matches->allocOrExpandArray(re->pairCount())) { ReportOutOfMemory(cx); return RegExpRunStatus_Error; } size_t length = input->length(); // Reset the Irregexp backtrack stack if it grows during execution. irregexp::RegExpStackScope stackScope(cx); - if (canStringMatch) { - MOZ_ASSERT(pairCount() == 1); - size_t sourceLength = source->length(); - if (sticky()) { + if (re->canStringMatch) { + MOZ_ASSERT(re->pairCount() == 1); + size_t sourceLength = re->source->length(); + if (re->sticky()) { // First part checks size_t overflow. if (sourceLength + start < sourceLength || sourceLength + start > length) return RegExpRunStatus_Success_NotFound; - if (!HasSubstringAt(input, source, start)) + if (!HasSubstringAt(input, re->source, start)) return RegExpRunStatus_Success_NotFound; if (matches) { (*matches)[0].start = start; (*matches)[0].limit = start + sourceLength; matches->checkAgainst(length); } else if (endIndex) { *endIndex = start + sourceLength; } return RegExpRunStatus_Success; } - int res = StringFindPattern(input, source, start); + int res = StringFindPattern(input, re->source, start); if (res == -1) return RegExpRunStatus_Success_NotFound; if (matches) { (*matches)[0].start = res; (*matches)[0].limit = res + sourceLength; matches->checkAgainst(length); } else if (endIndex) { *endIndex = res + sourceLength; } return RegExpRunStatus_Success; } do { - jit::JitCode* code = compilation(mode, input->hasLatin1Chars()).jitCode; + jit::JitCode* code = re->compilation(mode, input->hasLatin1Chars()).jitCode; if (!code) break; RegExpRunStatus result; { AutoTraceLog logJIT(logger, TraceLogger_IrregexpExecute); AutoCheckCannotGC nogc; if (input->hasLatin1Chars()) { @@ -1154,20 +1142,20 @@ RegExpShared::execute(JSContext* cx, Han MOZ_ASSERT(result == RegExpRunStatus_Success); if (matches) matches->checkAgainst(length); return RegExpRunStatus_Success; } while (false); // Compile bytecode for the RegExp if necessary. - if (!compileIfNecessary(cx, input, mode, ForceByteCode)) + if (!compileIfNecessary(cx, re, input, mode, ForceByteCode)) return RegExpRunStatus_Error; - uint8_t* byteCode = compilation(mode, input->hasLatin1Chars()).byteCode; + uint8_t* byteCode = re->compilation(mode, input->hasLatin1Chars()).byteCode; AutoTraceLog logInterpreter(logger, TraceLogger_IrregexpExecute); AutoStableStringChars inputChars(cx); if (!inputChars.init(cx, input)) return RegExpRunStatus_Error; RegExpRunStatus result; if (inputChars.isLatin1()) { @@ -1179,62 +1167,55 @@ RegExpShared::execute(JSContext* cx, Han } if (result == RegExpRunStatus_Success && matches) matches->checkAgainst(length); return result; } size_t -RegExpShared::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) +RegExpShared::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { - size_t n = mallocSizeOf(this); + size_t n = 0; for (size_t i = 0; i < ArrayLength(compilationArray); i++) { const RegExpCompilation& compilation = compilationArray[i]; if (compilation.byteCode) n += mallocSizeOf(compilation.byteCode); } n += tables.sizeOfExcludingThis(mallocSizeOf); for (size_t i = 0; i < tables.length(); i++) n += mallocSizeOf(tables[i]); return n; } /* RegExpCompartment */ -RegExpCompartment::RegExpCompartment(JSRuntime* rt) - : set_(rt), +RegExpCompartment::RegExpCompartment(Zone* zone) + : set_(zone, Set(zone->runtimeFromActiveCooperatingThread())), matchResultTemplateObject_(nullptr), optimizableRegExpPrototypeShape_(nullptr), optimizableRegExpInstanceShape_(nullptr) {} RegExpCompartment::~RegExpCompartment() { - // Because of stray mark bits being set (see RegExpCompartment::sweep) - // there might still be RegExpShared instances which haven't been deleted. - if (set_.initialized()) { - for (Set::Enum e(set_); !e.empty(); e.popFront()) { - RegExpShared* shared = e.front(); - js_delete(shared); - } - } + MOZ_ASSERT_IF(set_.initialized(), set_.empty()); } ArrayObject* RegExpCompartment::createMatchResultTemplateObject(JSContext* cx) { MOZ_ASSERT(!matchResultTemplateObject_); /* Create template array object */ RootedArrayObject templateObject(cx, NewDenseUnallocatedArray(cx, RegExpObject::MaxPairCount, - nullptr, TenuredObject)); + nullptr, TenuredObject)); if (!templateObject) return matchResultTemplateObject_; // = nullptr // Create a new group for the template. Rooted<TaggedProto> proto(cx, templateObject->taggedProto()); ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, templateObject->getClass(), proto); if (!group) return matchResultTemplateObject_; // = nullptr @@ -1280,69 +1261,19 @@ RegExpCompartment::init(JSContext* cx) if (cx) ReportOutOfMemory(cx); return false; } return true; } -bool -RegExpShared::needsSweep(JSRuntime* rt) -{ - // Sometimes RegExpShared instances are marked without the compartment - // being subsequently cleared. This can happen if a GC is restarted while - // in progress (i.e. performing a full GC in the middle of an incremental - // GC) or if a RegExpShared referenced via the stack is traced but is not - // in a zone being collected. - // - // Because of this we only treat the marked_ bit as a hint, and destroy the - // RegExpShared if it was accidentally marked earlier but wasn't marked by - // the current trace. - bool keep = marked() && IsMarked(rt, &source); - for (size_t i = 0; i < ArrayLength(compilationArray); i++) { - RegExpShared::RegExpCompilation& compilation = compilationArray[i]; - if (compilation.jitCode && gc::IsAboutToBeFinalized(&compilation.jitCode)) - keep = false; - } - - MOZ_ASSERT(JS::CurrentThreadIsHeapMajorCollecting()); - if (keep || rt->gc.isHeapCompacting()) { - clearMarked(); - return false; - } - - return true; -} - -void -RegExpShared::discardJitCode() -{ - for (size_t i = 0; i < ArrayLength(compilationArray); i++) - compilationArray[i].jitCode = nullptr; -} - void RegExpCompartment::sweep(JSRuntime* rt) { - if (!set_.initialized()) - return; - - for (Set::Enum e(set_); !e.empty(); e.popFront()) { - RegExpShared* shared = e.front(); - if (shared->needsSweep(rt)) { - js_delete(shared); - e.removeFront(); - } else { - // Discard code to avoid holding onto ExecutablePools. - if (rt->gc.isHeapCompacting()) - shared->discardJitCode(); - } - } - if (matchResultTemplateObject_ && IsAboutToBeFinalized(&matchResultTemplateObject_)) { matchResultTemplateObject_.set(nullptr); } if (optimizableRegExpPrototypeShape_ && IsAboutToBeFinalized(&optimizableRegExpPrototypeShape_)) @@ -1353,65 +1284,55 @@ RegExpCompartment::sweep(JSRuntime* rt) if (optimizableRegExpInstanceShape_ && IsAboutToBeFinalized(&optimizableRegExpInstanceShape_)) { optimizableRegExpInstanceShape_.set(nullptr); } } bool -RegExpCompartment::get(JSContext* cx, JSAtom* source, RegExpFlag flags, RegExpGuard* g) +RegExpCompartment::get(JSContext* cx, HandleAtom source, RegExpFlag flags, + MutableHandleRegExpShared result) { - Key key(source, flags); - Set::AddPtr p = set_.lookupForAdd(key); + DependentAddPtr<Set> p(cx, set_.get(), Key(source, flags)); if (p) { - // Trigger a read barrier on existing RegExpShared instances fetched - // from the table (which only holds weak references). - RegExpSharedReadBarrier(cx, *p); - - g->init(**p); + result.set(*p); return true; } - ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags)); + auto shared = Allocate<RegExpShared>(cx); if (!shared) return false; - if (!set_.add(p, shared)) { + new (shared) RegExpShared(source, flags); + + if (!p.add(cx, set_.get(), Key(source, flags), shared)) { ReportOutOfMemory(cx); return false; } - // Trace RegExpShared instances created during an incremental GC. - RegExpSharedReadBarrier(cx, shared); - - g->init(*shared.forget()); + result.set(shared); return true; } bool -RegExpCompartment::get(JSContext* cx, HandleAtom atom, JSString* opt, RegExpGuard* g) +RegExpCompartment::get(JSContext* cx, HandleAtom atom, JSString* opt, + MutableHandleRegExpShared shared) { RegExpFlag flags = RegExpFlag(0); if (opt && !ParseRegExpFlags(cx, opt, &flags)) return false; - return get(cx, atom, flags, g); + return get(cx, atom, flags, shared); } size_t RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { - size_t n = 0; - n += set_.sizeOfExcludingThis(mallocSizeOf); - for (Set::Enum e(set_); !e.empty(); e.popFront()) { - RegExpShared* shared = e.front(); - n += shared->sizeOfIncludingThis(mallocSizeOf); - } - return n; + return set_.sizeOfExcludingThis(mallocSizeOf); } /* Functions */ JSObject* js::CloneRegExpObject(JSContext* cx, JSObject* obj_) { Rooted<RegExpObject*> regex(cx, &obj_->as<RegExpObject>()); @@ -1424,22 +1345,22 @@ js::CloneRegExpObject(JSContext* cx, JSO return nullptr; clone->initPrivate(nullptr); if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, clone)) return nullptr; Rooted<JSAtom*> source(cx, regex->getSource()); - RegExpGuard g(cx); - if (!RegExpObject::getShared(cx, regex, &g)) + RootedRegExpShared shared(cx); + if (!RegExpObject::getShared(cx, regex, &shared)) return nullptr; - clone->initAndZeroLastIndex(source, g->getFlags(), cx); - clone->setShared(*g.re()); + clone->initAndZeroLastIndex(source, shared->getFlags(), cx); + clone->setShared(*shared); return clone; } static bool HandleRegExpFlag(RegExpFlag flag, RegExpFlag* flags) { if (*flags & flag) @@ -1559,12 +1480,19 @@ js::CloneScriptRegExpObject(JSContext* c RootedAtom source(cx, reobj.getSource()); cx->markAtom(source); return RegExpObject::create(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc()); } JS_FRIEND_API(bool) -js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj, js::RegExpGuard* g) +js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj, MutableHandleRegExpShared shared) { - return RegExpToShared(cx, obj, g); + return RegExpToShared(cx, obj, shared); } + +JS::ubi::Node::Size +JS::ubi::Concrete<RegExpShared>::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + return js::gc::Arena::thingSize(gc::AllocKind::REGEXP_SHARED) + + get().sizeOfExcludingThis(mallocSizeOf); +}
--- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -27,31 +27,33 @@ * RegExpObject - The JS-visible object whose .[[Class]] equals "RegExp" * * RegExpShared - The compiled representation of the regexp. * * RegExpCompartment - Owns all RegExpShared instances in a compartment. * * To save memory, a RegExpShared is not created for a RegExpObject until it is * needed for execution. When a RegExpShared needs to be created, it is looked - * up in a per-compartment table to allow reuse between objects. Lastly, on - * GC, every RegExpShared (that is not active on the callstack) is discarded. - * Because of the last point, any code using a RegExpShared (viz., by executing - * a regexp) must indicate the RegExpShared is active via RegExpGuard. + * up in a per-compartment table to allow reuse between objects. Lastly, on GC, + * every RegExpShared that is not in active use is discarded. */ namespace js { struct MatchPair; class MatchPairs; class RegExpShared; class RegExpStatics; +using RootedRegExpShared = JS::Rooted<RegExpShared*>; +using HandleRegExpShared = JS::Handle<RegExpShared*>; +using MutableHandleRegExpShared = JS::MutableHandle<RegExpShared*>; + namespace frontend { class TokenStream; } -enum RegExpFlag +enum RegExpFlag : uint8_t { IgnoreCaseFlag = 0x01, GlobalFlag = 0x02, MultilineFlag = 0x04, StickyFlag = 0x08, UnicodeFlag = 0x10, NoFlags = 0x00, @@ -87,17 +89,17 @@ CloneRegExpObject(JSContext* cx, JSObjec * * During a GC, RegExpShared instances are marked and swept like GC things. * Usually, RegExpObjects clear their pointers to their RegExpShareds rather * than explicitly tracing them, so that the RegExpShared and any jitcode can * be reclaimed quicker. However, the RegExpShareds are traced through by * objects when we are preserving jitcode in their zone, to avoid the same * recompilation inefficiencies as normal Ion and baseline compilation. */ -class RegExpShared +class RegExpShared : public gc::TenuredCell { public: enum CompilationMode { Normal, MatchOnly }; enum ForceByteCodeEnum { @@ -108,73 +110,75 @@ class RegExpShared private: friend class RegExpCompartment; friend class RegExpStatics; typedef frontend::TokenStream TokenStream; struct RegExpCompilation { - HeapPtr<jit::JitCode*> jitCode; + ReadBarriered<jit::JitCode*> jitCode; uint8_t* byteCode; RegExpCompilation() : byteCode(nullptr) {} - ~RegExpCompilation() { js_free(byteCode); } bool compiled(ForceByteCodeEnum force = DontForceByteCode) const { return byteCode || (force == DontForceByteCode && jitCode); } }; /* Source to the RegExp, for lazy compilation. */ - HeapPtr<JSAtom*> source; + HeapPtr<JSAtom*> source; RegExpFlag flags; + bool canStringMatch; size_t parenCount; - bool canStringMatch; - bool marked_; RegExpCompilation compilationArray[4]; static int CompilationIndex(CompilationMode mode, bool latin1) { switch (mode) { case Normal: return latin1 ? 0 : 1; case MatchOnly: return latin1 ? 2 : 3; } MOZ_CRASH(); } // Tables referenced by JIT code. - Vector<uint8_t*, 0, SystemAllocPolicy> tables; + using JitCodeTables = Vector<uint8_t*, 0, SystemAllocPolicy>; + JitCodeTables tables; /* Internal functions. */ - bool compile(JSContext* cx, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force); - bool compile(JSContext* cx, HandleAtom pattern, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force); + RegExpShared(JSAtom* source, RegExpFlag flags); - bool compileIfNecessary(JSContext* cx, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force); + static bool compile(JSContext* cx, MutableHandleRegExpShared res, HandleLinearString input, + CompilationMode mode, ForceByteCodeEnum force); + static bool compile(JSContext* cx, MutableHandleRegExpShared res, HandleAtom pattern, + HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force); + + static bool compileIfNecessary(JSContext* cx, MutableHandleRegExpShared res, + HandleLinearString input, CompilationMode mode, + ForceByteCodeEnum force); const RegExpCompilation& compilation(CompilationMode mode, bool latin1) const { return compilationArray[CompilationIndex(mode, latin1)]; } RegExpCompilation& compilation(CompilationMode mode, bool latin1) { return compilationArray[CompilationIndex(mode, latin1)]; } public: - RegExpShared(JSAtom* source, RegExpFlag flags); - ~RegExpShared(); + ~RegExpShared() = delete; // Execute this RegExp on input starting from searchIndex, filling in // matches if specified and otherwise only determining if there is a match. - RegExpRunStatus execute(JSContext* cx, HandleLinearString input, size_t searchIndex, - MatchPairs* matches, size_t* endIndex); + static RegExpRunStatus execute(JSContext* cx, MutableHandleRegExpShared res, + HandleLinearString input, size_t searchIndex, + MatchPairs* matches, size_t* endIndex); // Register a table with this RegExpShared, and take ownership. bool addTable(uint8_t* table) { return tables.append(table); } /* Accessors */ @@ -198,25 +202,19 @@ class RegExpShared ForceByteCodeEnum force = DontForceByteCode) const { return compilation(mode, latin1).compiled(force); } bool isCompiled() const { return isCompiled(Normal, true) || isCompiled(Normal, false) || isCompiled(MatchOnly, true) || isCompiled(MatchOnly, false); } - void trace(JSTracer* trc); - bool needsSweep(JSRuntime* rt); + void traceChildren(JSTracer* trc); void discardJitCode(); - - bool marked() const { return marked_; } - void clearMarked() { marked_ = false; } - - bool isMarkedGray() const; - void unmarkGray(); + void finalize(FreeOp* fop); static size_t offsetOfSource() { return offsetof(RegExpShared, source); } static size_t offsetOfFlags() { return offsetof(RegExpShared, flags); } @@ -231,99 +229,54 @@ class RegExpShared + offsetof(RegExpCompilation, jitCode); } static size_t offsetOfTwoByteJitCode(CompilationMode mode) { return offsetof(RegExpShared, compilationArray) + (CompilationIndex(mode, false) * sizeof(RegExpCompilation)) + offsetof(RegExpCompilation, jitCode); } - size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); #ifdef DEBUG - bool dumpBytecode(JSContext* cx, bool match_only, HandleLinearString input); + static bool dumpBytecode(JSContext* cx, MutableHandleRegExpShared res, bool match_only, + HandleLinearString input); #endif }; -/* - * Extend the lifetime of a given RegExpShared to at least the lifetime of - * the guard object. See Regular Expression comment at the top. - */ -class RegExpGuard : public JS::CustomAutoRooter -{ - RegExpShared* re_; - - RegExpGuard(const RegExpGuard&) = delete; - void operator=(const RegExpGuard&) = delete; - - public: - explicit RegExpGuard(JSContext* cx) - : CustomAutoRooter(cx), re_(nullptr) - {} - - RegExpGuard(JSContext* cx, RegExpShared& re) - : CustomAutoRooter(cx), re_(nullptr) - { - init(re); - } - - ~RegExpGuard() { - release(); - } - - public: - void init(RegExpShared& re) { - MOZ_ASSERT(!initialized()); - re_ = &re; - } - - void release() { - re_ = nullptr; - } - - virtual void trace(JSTracer* trc) { - if (re_) - re_->trace(trc); - } - - bool initialized() const { return !!re_; } - RegExpShared* re() const { MOZ_ASSERT(initialized()); return re_; } - RegExpShared* operator->() { return re(); } - RegExpShared& operator*() { return *re(); } -}; - class RegExpCompartment { struct Key { JSAtom* atom; uint16_t flag; Key() {} Key(JSAtom* atom, RegExpFlag flag) : atom(atom), flag(flag) { } - MOZ_IMPLICIT Key(RegExpShared* shared) - : atom(shared->getSource()), flag(shared->getFlags()) + MOZ_IMPLICIT Key(const ReadBarriered<RegExpShared*>& shared) + : atom(shared.unbarrieredGet()->getSource()), + flag(shared.unbarrieredGet()->getFlags()) { } typedef Key Lookup; static HashNumber hash(const Lookup& l) { return DefaultHasher<JSAtom*>::hash(l.atom) ^ (l.flag << 1); } static bool match(Key l, Key r) { return l.atom == r.atom && l.flag == r.flag; } }; /* * The set of all RegExpShareds in the compartment. On every GC, every * RegExpShared that was not marked is deleted and removed from the set. */ - typedef HashSet<RegExpShared*, Key, RuntimeAllocPolicy> Set; - Set set_; + using Set = GCHashSet<ReadBarriered<RegExpShared*>, Key, RuntimeAllocPolicy>; + JS::WeakCache<Set> set_; /* * This is the template object where the result of re.exec() is based on, * if there is a result. This is used in CreateRegExpMatchResult to set * the input/index properties faster. */ ReadBarriered<ArrayObject*> matchResultTemplateObject_; @@ -346,28 +299,29 @@ class RegExpCompartment * * lastProperty is lastIndex * * prototype is RegExp.prototype */ ReadBarriered<Shape*> optimizableRegExpInstanceShape_; ArrayObject* createMatchResultTemplateObject(JSContext* cx); public: - explicit RegExpCompartment(JSRuntime* rt); + explicit RegExpCompartment(Zone* zone); ~RegExpCompartment(); bool init(JSContext* cx); void sweep(JSRuntime* rt); bool empty() { return set_.empty(); } - bool get(JSContext* cx, JSAtom* source, RegExpFlag flags, RegExpGuard* g); + bool get(JSContext* cx, HandleAtom source, RegExpFlag flags, MutableHandleRegExpShared shared); /* Like 'get', but compile 'maybeOpt' (if non-null). */ - bool get(JSContext* cx, HandleAtom source, JSString* maybeOpt, RegExpGuard* g); + bool get(JSContext* cx, HandleAtom source, JSString* maybeOpt, + MutableHandleRegExpShared shared); /* Get or create template object used to base the result of .exec() on. */ ArrayObject* getOrCreateMatchResultTemplateObject(JSContext* cx) { if (matchResultTemplateObject_) return matchResultTemplateObject_; return createMatchResultTemplateObject(cx); } @@ -479,24 +433,29 @@ class RegExpObject : public NativeObject bool global() const { return getFlags() & GlobalFlag; } bool multiline() const { return getFlags() & MultilineFlag; } bool sticky() const { return getFlags() & StickyFlag; } bool unicode() const { return getFlags() & UnicodeFlag; } static bool isOriginalFlagGetter(JSNative native, RegExpFlag* mask); static MOZ_MUST_USE bool getShared(JSContext* cx, Handle<RegExpObject*> regexp, - RegExpGuard* g); + MutableHandleRegExpShared shared); + + bool hasShared() { + return !!sharedRef(); + } void setShared(RegExpShared& shared) { - MOZ_ASSERT(!maybeShared()); - NativeObject::setPrivate(&shared); + MOZ_ASSERT(!hasShared()); + sharedRef() = &shared; } static void trace(JSTracer* trc, JSObject* obj); + void trace(JSTracer* trc); void initIgnoringLastIndex(HandleAtom source, RegExpFlag flags); // NOTE: This method is *only* safe to call on RegExps that haven't been // exposed to script, because it requires that the "lastIndex" // property be writable. void initAndZeroLastIndex(HandleAtom source, RegExpFlag flags, JSContext* cx); @@ -506,19 +465,21 @@ class RegExpObject : public NativeObject #endif private: /* * Precondition: the syntax for |source| has already been validated. * Side effect: sets the private field. */ static MOZ_MUST_USE bool createShared(JSContext* cx, Handle<RegExpObject*> regexp, - RegExpGuard* g); - RegExpShared* maybeShared() const { - return static_cast<RegExpShared*>(NativeObject::getPrivate(PRIVATE_SLOT)); + MutableHandleRegExpShared shared); + + ReadBarriered<RegExpShared*>& sharedRef() { + auto& ref = NativeObject::privateRef(PRIVATE_SLOT); + return reinterpret_cast<ReadBarriered<RegExpShared*>&>(ref); } /* Call setShared in preference to setPrivate. */ void setPrivate(void* priv) = delete; }; /* * Parse regexp flags. Report an error and return false if an invalid @@ -526,22 +487,22 @@ class RegExpObject : public NativeObject * * N.B. flagStr must be rooted. */ bool ParseRegExpFlags(JSContext* cx, JSString* flagStr, RegExpFlag* flagsOut); /* Assuming GetBuiltinClass(obj) is ESClass::RegExp, return a RegExpShared for obj. */ inline bool -RegExpToShared(JSContext* cx, HandleObject obj, RegExpGuard* g) +RegExpToShared(JSContext* cx, HandleObject obj, MutableHandleRegExpShared shared) { if (obj->is<RegExpObject>()) - return RegExpObject::getShared(cx, obj.as<RegExpObject>(), g); + return RegExpObject::getShared(cx, obj.as<RegExpObject>(), shared); - return Proxy::regexp_toShared(cx, obj, g); + return Proxy::regexp_toShared(cx, obj, shared); } template<XDRMode mode> bool XDRScriptRegExpObject(XDRState<mode>* xdr, MutableHandle<RegExpObject*> objp); extern JSObject* CloneScriptRegExpObject(JSContext* cx, RegExpObject& re); @@ -554,9 +515,34 @@ template <typename CharT> extern bool HasRegExpMetaChars(const CharT* chars, size_t length); extern bool StringHasRegExpMetaChars(JSLinearString* str); } /* namespace js */ +namespace JS { +namespace ubi { + +template <> +class Concrete<js::RegExpShared> : TracerConcrete<js::RegExpShared> +{ + protected: + explicit Concrete(js::RegExpShared* ptr) : TracerConcrete<js::RegExpShared>(ptr) { } + + public: + static void construct(void* storage, js::RegExpShared* ptr) { + new (storage) Concrete(ptr); + } + + CoarseType coarseType() const final { return CoarseType::Other; } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +} // namespace ubi +} // namespace JS + #endif /* vm_RegExpObject_h */
--- a/js/src/vm/RegExpStatics.cpp +++ b/js/src/vm/RegExpStatics.cpp @@ -76,28 +76,30 @@ RegExpStatics::executeLazy(JSContext* cx if (!pendingLazyEvaluation) return true; MOZ_ASSERT(lazySource); MOZ_ASSERT(matchesInput); MOZ_ASSERT(lazyIndex != size_t(-1)); /* Retrieve or create the RegExpShared in this compartment. */ - RegExpGuard g(cx); - if (!cx->compartment()->regExps.get(cx, lazySource, lazyFlags, &g)) + RootedRegExpShared shared(cx); + RootedAtom source(cx, lazySource); + if (!cx->compartment()->regExps.get(cx, source, lazyFlags, &shared)) return false; /* * It is not necessary to call aboutToWrite(): evaluation of * implicit copies is safe. */ /* Execute the full regular expression. */ RootedLinearString input(cx, matchesInput); - RegExpRunStatus status = g->execute(cx, input, lazyIndex, &this->matches, nullptr); + RegExpRunStatus status = RegExpShared::execute(cx, &shared, input, lazyIndex, &this->matches, + nullptr); if (status == RegExpRunStatus_Error) return false; /* * RegExpStatics are only updated on successful (matching) execution. * Re-running the same expression must therefore produce a matching result. */ MOZ_ASSERT(status == RegExpRunStatus_Success);
--- a/js/src/vm/Shape-inl.h +++ b/js/src/vm/Shape-inl.h @@ -14,16 +14,17 @@ #include "jsobj.h" #include "gc/Allocator.h" #include "vm/Interpreter.h" #include "vm/TypedArrayObject.h" #include "jsatominlines.h" #include "jscntxtinlines.h" +#include "jsgcinlines.h" namespace js { inline AutoKeepShapeTables::AutoKeepShapeTables(JSContext* cx) : cx_(cx), prev_(cx->zone()->keepShapeTables()) {
--- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -1408,17 +1408,17 @@ JSStructuredCloneWriter::startWrite(Hand if (backref) return true; ESClass cls; if (!GetBuiltinClass(context(), obj, &cls)) return false; if (cls == ESClass::RegExp) { - RegExpGuard re(context()); + RootedRegExpShared re(context()); if (!RegExpToShared(context(), obj, &re)) return false; return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) && writeString(SCTAG_STRING, re->getSource()); } else if (cls == ESClass::Date) { RootedValue unboxed(context()); if (!Unbox(context(), obj, &unboxed)) return false;
--- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -307,16 +307,17 @@ TracerConcrete<Referent>::zone() const return get().zoneFromAnyThread(); } template JS::Zone* TracerConcrete<JSScript>::zone() const; template JS::Zone* TracerConcrete<js::LazyScript>::zone() const; template JS::Zone* TracerConcrete<js::Shape>::zone() const; template JS::Zone* TracerConcrete<js::BaseShape>::zone() const; template JS::Zone* TracerConcrete<js::ObjectGroup>::zone() const; +template JS::Zone* TracerConcrete<js::RegExpShared>::zone() const; template JS::Zone* TracerConcrete<js::Scope>::zone() const; template JS::Zone* TracerConcrete<JS::Symbol>::zone() const; template JS::Zone* TracerConcrete<JSString>::zone() const; template<typename Referent> UniquePtr<EdgeRange> TracerConcrete<Referent>::edges(JSContext* cx, bool wantNames) const { UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(js_new<SimpleEdgeRange>()); @@ -329,16 +330,17 @@ TracerConcrete<Referent>::edges(JSContex return UniquePtr<EdgeRange>(range.release()); } template UniquePtr<EdgeRange> TracerConcrete<JSScript>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<js::LazyScript>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<js::Shape>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<js::BaseShape>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<js::ObjectGroup>::edges(JSContext* cx, bool wantNames) const; +template UniquePtr<EdgeRange> TracerConcrete<js::RegExpShared>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<js::Scope>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges(JSContext* cx, bool wantNames) const; template<typename Referent> JSCompartment* TracerConcreteWithCompartment<Referent>::compartment() const { @@ -393,16 +395,17 @@ Concrete<JSObject>::jsObjectConstructorN const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol"; const char16_t Concrete<JSScript>::concreteTypeName[] = u"JSScript"; const char16_t Concrete<js::LazyScript>::concreteTypeName[] = u"js::LazyScript"; const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] = u"js::jit::JitCode"; const char16_t Concrete<js::Shape>::concreteTypeName[] = u"js::Shape"; const char16_t Concrete<js::BaseShape>::concreteTypeName[] = u"js::BaseShape"; const char16_t Concrete<js::ObjectGroup>::concreteTypeName[] = u"js::ObjectGroup"; const char16_t Concrete<js::Scope>::concreteTypeName[] = u"js::Scope"; +const char16_t Concrete<js::RegExpShared>::concreteTypeName[] = u"js::RegExpShared"; namespace JS { namespace ubi { RootList::RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */) : noGC(noGC), cx(cx), edges(),
--- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -2190,16 +2190,19 @@ class BaseCompiler return iter_.controlOutermost(); } //////////////////////////////////////////////////////////// // // Labels void insertBreakablePoint(CallSiteDesc::Kind kind) { + // The debug trap exit requires WasmTlsReg be loaded. However, since we + // are emitting millions of these breakable points inline, we push this + // loading of TLS into the FarJumpIsland created by patchCallSites. masm.nopPatchableToCall(CallSiteDesc(iter_.errorOffset(), kind)); } ////////////////////////////////////////////////////////////////////// // // Function prologue and epilogue. void beginFunction() {
--- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -387,18 +387,21 @@ ModuleGenerator::patchCallSites() } case CallSiteDesc::Breakpoint: case CallSiteDesc::EnterFrame: case CallSiteDesc::LeaveFrame: { Uint32Vector& jumps = metadata_->debugTrapFarJumpOffsets; if (jumps.empty() || uint32_t(abs(int32_t(jumps.back()) - int32_t(callerOffset))) >= JumpRange()) { + // See BaseCompiler::insertBreakablePoint for why we must + // reload the TLS register on this path. Offsets offsets; offsets.begin = masm_.currentOffset(); + masm_.loadPtr(Address(FramePointer, offsetof(Frame, tls)), WasmTlsReg); uint32_t jumpOffset = masm_.farJumpWithPatch().offset(); offsets.end = masm_.currentOffset(); if (masm_.oom()) return false; if (!metadata_->codeRanges.emplaceBack(CodeRange::FarJumpIsland, offsets)) return false; if (!debugTrapFarJumps_.emplaceBack(jumpOffset))
--- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -1939,16 +1939,24 @@ ReportZoneStats(const JS::ZoneStats& zSt ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/gc-heap"), zStats.scopesGCHeap, "Scope information for scripts."); ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/malloc-heap"), zStats.scopesMallocHeap, "Arrays of binding names and other binding-related data."); + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("regexp-shareds/gc-heap"), + zStats.regExpSharedsGCHeap, + "Shared compiled regexp data."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("regexp-shareds/malloc-heap"), + zStats.regExpSharedsMallocHeap, + "Shared compiled regexp data."); + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-pool"), zStats.typePool, "Type sets and related data."); ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("baseline/optimized-stubs"), zStats.baselineStubsOptimized, "The Baseline JIT's optimized IC stubs (excluding code)."); @@ -2973,16 +2981,20 @@ JSReporter::CollectReports(WindowPaths* REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/lazy-scripts"), KIND_OTHER, rtStats.zTotals.unusedGCThings.lazyScript, "Unused lazy script cells within non-empty arenas."); REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/jitcode"), KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode, "Unused jitcode cells within non-empty arenas."); + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/regexp-shareds"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.regExpShared, + "Unused regexpshared cells within non-empty arenas."); + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/chunk-admin"), KIND_OTHER, rtStats.gcHeapChunkAdmin, "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/arena-admin"), KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin, "The same as 'js-main-runtime/zones/gc-heap-arena-admin'."); @@ -3024,16 +3036,20 @@ JSReporter::CollectReports(WindowPaths* MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/lazy-scripts"), KIND_OTHER, rtStats.zTotals.lazyScriptsGCHeap, "Used lazy script cells."); MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/jitcode"), KIND_OTHER, rtStats.zTotals.jitCodesGCHeap, "Used jitcode cells."); + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/regexp-shareds"), + KIND_OTHER, rtStats.zTotals.regExpSharedsGCHeap, + "Used regexpshared cells."); + MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings); // Report xpconnect. REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/runtime"), KIND_HEAP, xpcJSRuntimeSize, "The XPConnect runtime.");
--- a/layout/base/nsGenConList.cpp +++ b/layout/base/nsGenConList.cpp @@ -14,16 +14,17 @@ void nsGenConList::Clear() { // Delete entire list. mNodes.Clear(); while (nsGenConNode* node = mList.popFirst()) { delete node; } mSize = 0; + mLastInserted = nullptr; } bool nsGenConList::DestroyNodesFor(nsIFrame* aFrame) { // This algorithm relies on the invariant that nodes of a frame are // put contiguously in the linked list. This is guaranteed because // each frame is mapped to only one (nsIContent, pseudoType) pair, @@ -36,16 +37,19 @@ nsGenConList::DestroyNodesFor(nsIFrame* MOZ_ASSERT(node->mPseudoFrame == aFrame); while (node && node->mPseudoFrame == aFrame) { nsGenConNode* nextNode = Next(node); Destroy(node); node = nextNode; } + // Modification of the list invalidates the cached pointer. + mLastInserted = nullptr; + return true; } /** * Compute the type of the pseudo and the content for the pseudo that * we'll use for comparison purposes. * @param aContent the content to use is stored here; it's the element * that generated the ::before or ::after content, or (if not for generated @@ -103,16 +107,21 @@ nsGenConList::NodeAfter(const nsGenConNo } void nsGenConList::Insert(nsGenConNode* aNode) { // Check for append. if (mList.isEmpty() || NodeAfter(aNode, mList.getLast())) { mList.insertBack(aNode); + } else if (mLastInserted && mLastInserted != mList.getLast() && + NodeAfter(aNode, mLastInserted) && + NodeAfter(Next(mLastInserted), aNode)) { + // Fast path for inserting many consecutive nodes in one place + mLastInserted->setNext(aNode); } else { // Binary search. // the range of indices at which |aNode| could end up. // (We already know it can't be at index mSize.) uint32_t first = 0, last = mSize - 1; // A cursor to avoid walking more than the length of the list. @@ -137,16 +146,18 @@ nsGenConList::Insert(nsGenConNode* aNode } else { last = test; } } curNode->setPrevious(aNode); } ++mSize; + mLastInserted = aNode; + // Set the mapping only if it is the first node of the frame. // The DEBUG blocks below are for ensuring the invariant required by // nsGenConList::DestroyNodesFor. See comment there. if (IsFirst(aNode) || Prev(aNode)->mPseudoFrame != aNode->mPseudoFrame) { #ifdef DEBUG if (nsGenConNode* oldFrameFirstNode = mNodes.Get(aNode->mPseudoFrame)) { MOZ_ASSERT(Next(aNode) == oldFrameFirstNode,
--- a/layout/base/nsGenConList.h +++ b/layout/base/nsGenConList.h @@ -81,17 +81,17 @@ protected: }; class nsGenConList { protected: mozilla::LinkedList<nsGenConNode> mList; uint32_t mSize; public: - nsGenConList() : mSize(0) {} + nsGenConList() : mSize(0), mLastInserted(nullptr) {} ~nsGenConList() { Clear(); } void Clear(); static nsGenConNode* Next(nsGenConNode* aNode) { MOZ_ASSERT(aNode, "aNode cannot be nullptr!"); return aNode->getNext(); } static nsGenConNode* Prev(nsGenConNode* aNode) { MOZ_ASSERT(aNode, "aNode cannot be nullptr!"); @@ -122,11 +122,15 @@ private: { MOZ_ASSERT(aNode, "aNode cannot be nullptr!"); delete aNode; mSize--; } // Map from frame to the first nsGenConNode of it in the list. nsDataHashtable<nsPtrHashKey<nsIFrame>, nsGenConNode*> mNodes; + + // A weak pointer to the node most recently inserted, used to avoid repeated + // list traversals in Insert(). + nsGenConNode* mLastInserted; }; #endif /* nsGenConList_h___ */
--- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -1693,21 +1693,24 @@ nsImageFrame::PaintImage(nsRenderingCont StylePosition(), &anchorPoint); uint32_t flags = aFlags; if (mForceSyncDecoding) { flags |= imgIContainer::FLAG_SYNC_DECODE; } + Maybe<SVGImageContext> svgContext; + SVGImageContext::MaybeInitAndStoreContextPaint(svgContext, this, aImage); + DrawResult result = nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(), PresContext(), aImage, nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect, - /* no SVGImageContext */ Nothing(), flags, &anchorPoint); + svgContext, flags, &anchorPoint); nsImageMap* map = GetImageMap(); if (map) { gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(dest.TopLeft(), PresContext()->AppUnitsPerDevPixel()); AutoRestoreTransform autoRestoreTransform(drawTarget); drawTarget->SetTransform(
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/black100x100-ref.html @@ -0,0 +1,5 @@ +<html> +<body> + <div style="width: 100px; height: 100px; background: black"></div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/blue100x100-ref.html @@ -0,0 +1,5 @@ +<html> +<body> + <div style="width: 100px; height: 100px; background: blue"></div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-01.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Basic context-fill test</title> + <style> + +img { + width: 100px; + height: 100px; + fill: lime; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='context-fill blue'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-02.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <title>Basic context-fill test (without a fallback color)</title> + <style> + +img { + width: 100px; + height: 100px; + fill: lime; + border: 1px solid black; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='context-fill'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-03.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-fill where fill is semi-transparent</title> + <style> + +img { + width: 100px; + height: 100px; + fill: rgb(0% 100% 0% / 50%); +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='context-fill red'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-04.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-fill with fill-opacity</title> + <style> + +img { + width: 100px; + height: 100px; + fill: lime; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='context-fill red' fill-opacity='0.5'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-05.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-fill with different fill values (test image caching correctness)</title> + <style> + +img { + width: 100px; + height: 100px; +} + +#img1 { + fill: blue; +} + +#img2 { + fill: yellow; +} + + </style> + </head> + <body> + <img id="img1" src="context-fill-05.svg"> + <img id="img2" src="context-fill-05.svg"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-05.svg @@ -0,0 +1,3 @@ +<svg xmlns='http://www.w3.org/2000/svg'> + <rect width='100%' height='100%' fill='context-fill red'/> +</svg>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-06.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-fill works as a stroke value</title> + <style> + +img { + width: 100px; + height: 100px; + fill: lime; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-fill red'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-07-ref.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <style> + +div { + width: 100px; + height: 100px; + background-color: black; + display: inline-block; +} + + </style> + </head> + <body> + <div></div> + <div></div> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-07.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-fill when no context fill is provided</title> + <style> + +img { + width: 100px; + height: 100px; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='context-fill'/></svg>"> + <!-- The initial value for 'fill' is black, so we expect that to be the + context-fill that the following rect uses (the fallback color should + not be used). --> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='context-fill red'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-fill-or-stroke-05-ref.html @@ -0,0 +1,17 @@ +<html> +<head> + <style> + +div { + width: 100px; + height: 100px; + display: inline-block; +} + + </style> +</head> +<body> + <div style="background: blue"></div> + <div style="background: yellow"></div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-01.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Basic context-stroke test</title> + <style> + +img { + width: 100px; + height: 100px; + stroke: lime; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-stroke blue'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-02.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <title>Basic context-stroke test (without a fallback color)</title> + <style> + +img { + width: 100px; + height: 100px; + stroke: lime; + border: 1px solid black; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-stroke'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-03.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-stroke where stroke is semi-transparent</title> + <style> + +img { + width: 100px; + height: 100px; + stroke: rgb(0% 100% 0% / 50%); +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-stroke red'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-04.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-stroke with stroke-opacity</title> + <style> + +img { + width: 100px; + height: 100px; + stroke: lime; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-stroke red' stroke-opacity='0.5'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-05.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-stroke with different stroke values (test image caching correctness)</title> + <style> + +img { + width: 100px; + height: 100px; +} + +#img1 { + stroke: blue; +} + +#img2 { + stroke: yellow; +} + + </style> + </head> + <body> + <img id="img1" src="context-stroke-05.svg"> + <img id="img2" src="context-stroke-05.svg"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-05.svg @@ -0,0 +1,3 @@ +<svg xmlns='http://www.w3.org/2000/svg'> + <line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-stroke red'/> +</svg>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-06.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-stroke works as a fill value</title> + <style> + +img { + width: 100px; + height: 100px; + stroke: lime; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='context-stroke red'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-07-ref.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <style> + +div { + width: 100px; + height: 100px; + display: inline-block; + border: 1px solid black; +} + + </style> + </head> + <body> + <div></div> + <div style="background-color: lime;"></div> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/context-stroke-07.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test context-stroke when no context stroke is provided</title> + <style> + +img { + width: 100px; + height: 100px; + border: 1px solid black; +} + + </style> + </head> + <body> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-stroke'/></svg>"> + <!-- The following image currently results in the 'lime' fallback color + being used, but it probably shouldn't. See bug 1351243. --> + <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><line x1='0' y1='50%' x2='100%' y2='50%' stroke-width='120%' stroke='context-stroke lime'/></svg>"> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/lime100x100-50pct-ref.html @@ -0,0 +1,5 @@ +<html> +<body> + <div style="width: 100px; height: 100px; background: rgb(0% 100% 0% / 50%)"></div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/lime100x100-w-border-ref.html @@ -0,0 +1,5 @@ +<html> +<body> + <div style="width: 100px; height: 100px; background: lime; border: 1px solid black"></div> +</body> +</html>
--- a/layout/reftests/svg/as-image/reftest.list +++ b/layout/reftests/svg/as-image/reftest.list @@ -49,16 +49,38 @@ fuzzy-if(winWidget&&!d2d,1,10000) fuzzy- fuzzy(1,2) fuzzy-if(azureSkia,1,40000) == canvas-drawImage-alpha-2.html canvas-drawImage-alpha-2-ref.html == canvas-drawImage-slice-1a.html lime100x100-ref.html == canvas-drawImage-slice-1b.html lime100x100-ref.html == canvas-drawImage-origin-clean-1.html lime100x100-ref.html == canvas-drawImage-transform-restored.html canvas-drawImage-transform-restored-ref.html +# Context paint tests (this feature is currently not part of any spec.) +== context-fill-01.html blue100x100-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-fill-01.html lime100x100-ref.html +== context-fill-02.html transparent100x100-w-border-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-fill-02.html lime100x100-w-border-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-fill-03.html lime100x100-50pct-ref.html +# fuzz because on win8 the r & b components are off by one +fuzzy-if(winWidget,1,10000) test-pref(svg.context-properties.content.enabled,true) == context-fill-04.html lime100x100-50pct-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-fill-05.html context-fill-or-stroke-05-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-fill-06.html lime100x100-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-fill-07.html context-fill-07-ref.html +== context-stroke-01.html blue100x100-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-stroke-01.html lime100x100-ref.html +== context-stroke-02.html transparent100x100-w-border-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-stroke-02.html lime100x100-w-border-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-stroke-03.html lime100x100-50pct-ref.html +# fuzz because on win8 the r & b components are off by one +fuzzy-if(winWidget,1,10000) test-pref(svg.context-properties.content.enabled,true) == context-stroke-04.html lime100x100-50pct-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-stroke-05.html context-fill-or-stroke-05-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-stroke-06.html lime100x100-ref.html +test-pref(svg.context-properties.content.enabled,true) == context-stroke-07.html context-stroke-07-ref.html + # Simple <img> tests == img-simple-1.html lime100x100-ref.html == img-simple-2.html lime100x100-ref.html fuzzy-if(skiaContent,255,350) == img-simple-3.html img-simple-3-ref.html == img-simple-4.html lime100x100-ref.html fuzzy-if(skiaContent,255,90) == img-simple-5.html img-simple-5-ref.html == img-simple-6.html lime100x100-ref.html fuzzy-if(skiaContent,255,27) == img-simple-7.html img-simple-7-ref.html
new file mode 100644 --- /dev/null +++ b/layout/reftests/svg/as-image/transparent100x100-w-border-ref.html @@ -0,0 +1,5 @@ +<html> +<body> + <div style="width: 100px; height: 100px; border: 1px solid black"></div> +</body> +</html>
--- a/layout/style/nsLayoutStylesheetCache.cpp +++ b/layout/style/nsLayoutStylesheetCache.cpp @@ -142,16 +142,21 @@ StyleSheet* nsLayoutStylesheetCache::MinimalXULSheet() { return mMinimalXULSheet; } StyleSheet* nsLayoutStylesheetCache::XULSheet() { + if (!mXULSheet) { + LoadSheetURL("chrome://global/content/xul.css", + &mXULSheet, eAgentSheetFeatures, eCrash); + } + return mXULSheet; } StyleSheet* nsLayoutStylesheetCache::QuirkSheet() { return mQuirkSheet; } @@ -333,18 +338,20 @@ nsLayoutStylesheetCache::nsLayoutStylesh LoadSheetURL("resource://gre-resources/html.css", &mHTMLSheet, eAgentSheetFeatures, eCrash); LoadSheetURL("chrome://global/content/minimal-xul.css", &mMinimalXULSheet, eAgentSheetFeatures, eCrash); LoadSheetURL("resource://gre-resources/quirk.css", &mQuirkSheet, eAgentSheetFeatures, eCrash); LoadSheetURL("resource://gre/res/svg.css", &mSVGSheet, eAgentSheetFeatures, eCrash); - LoadSheetURL("chrome://global/content/xul.css", - &mXULSheet, eAgentSheetFeatures, eCrash); + if (XRE_IsParentProcess()) { + // We know we need xul.css for the UI, so load that now too: + XULSheet(); + } if (gUserContentSheetURL) { MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes."); LoadSheet(gUserContentSheetURL, &mUserContentSheet, eUserSheetFeatures, eLogToConsole); gUserContentSheetURL = nullptr; } // The remaining sheets are created on-demand do to their use being rarer
--- a/layout/svg/SVGContextPaint.h +++ b/layout/svg/SVGContextPaint.h @@ -87,25 +87,25 @@ public: static SVGContextPaint* GetContextPaint(nsIContent* aContent); // XXX This gets the geometry params from the gfxContext. We should get that // information from the actual paint context! void InitStrokeGeometry(gfxContext *aContext, float devUnitsPerSVGUnit); - FallibleTArray<gfxFloat>& GetStrokeDashArray() { + const FallibleTArray<gfxFloat>& GetStrokeDashArray() const { return mDashes; } - gfxFloat GetStrokeDashOffset() { + gfxFloat GetStrokeDashOffset() const { return mDashOffset; } - gfxFloat GetStrokeWidth() { + gfxFloat GetStrokeWidth() const { return mStrokeWidth; } virtual uint32_t Hash() const { MOZ_ASSERT_UNREACHABLE("Only VectorImage needs to hash, and that should " "only be operating on our SVGEmbeddingContextPaint " "subclass"); return 0; @@ -240,16 +240,25 @@ public: class SVGEmbeddingContextPaint : public SVGContextPaint { typedef gfx::Color Color; typedef mozilla::image::DrawResult DrawResult; public: SVGEmbeddingContextPaint() {} + bool operator==(const SVGEmbeddingContextPaint& aOther) const { + MOZ_ASSERT(GetStrokeWidth() == aOther.GetStrokeWidth() && + GetStrokeDashOffset() == aOther.GetStrokeDashOffset() && + GetStrokeDashArray() == aOther.GetStrokeDashArray(), + "We don't currently include these in the context information " + "from an embedding element"); + return mFill == aOther.mFill && mStroke == aOther.mStroke; + } + void SetFill(nscolor aFill) { mFill.emplace(gfx::ToDeviceColor(aFill)); } void SetStroke(nscolor aStroke) { mStroke.emplace(gfx::ToDeviceColor(aStroke)); } /**
--- a/layout/svg/SVGImageContext.cpp +++ b/layout/svg/SVGImageContext.cpp @@ -10,32 +10,41 @@ // Keep others in (case-insensitive) order: #include "gfxUtils.h" #include "mozilla/Preferences.h" #include "nsIFrame.h" #include "nsPresContext.h" namespace mozilla { -bool -SVGImageContext::MaybeStoreContextPaint(nsIFrame* aFromFrame) +/* static */ void +SVGImageContext::MaybeInitAndStoreContextPaint(Maybe<SVGImageContext>& aContext, + nsIFrame* aFromFrame, + imgIContainer* aImgContainer) { static bool sEnabledForContent = false; static bool sEnabledForContentCached = false; + MOZ_ASSERT(!aContext, "The emplace() call below with overwrite this object"); + if (!sEnabledForContentCached) { Preferences::AddBoolVarCache(&sEnabledForContent, "svg.context-properties.content.enabled", false); sEnabledForContentCached = true; } if (!sEnabledForContent && !aFromFrame->PresContext()->IsChrome()) { // Context paint is pref'ed off for content and this is a content doc. - return false; + return; + } + + if (aImgContainer->GetType() != imgIContainer::TYPE_VECTOR) { + // Avoid this overhead for raster images. + return; } // XXX return early if the 'context-properties' property is not set. bool haveContextPaint = false; RefPtr<SVGEmbeddingContextPaint> contextPaint = new SVGEmbeddingContextPaint(); @@ -48,15 +57,14 @@ SVGImageContext::MaybeStoreContextPaint( contextPaint->SetFill(style->mFill.GetColor()); } if (style->mStroke.Type() == eStyleSVGPaintType_Color) { haveContextPaint = true; contextPaint->SetStroke(style->mStroke.GetColor()); } if (haveContextPaint) { - mContextPaint = contextPaint.forget(); + aContext.emplace(); + aContext->mContextPaint = contextPaint.forget(); } - - return mContextPaint != nullptr; } } // namespace mozilla
--- a/layout/svg/SVGImageContext.h +++ b/layout/svg/SVGImageContext.h @@ -52,17 +52,19 @@ public: gfxFloat aOpacity = 1.0, bool aIsPaintingSVGImageElement = false) : mViewportSize(aViewportSize) , mPreserveAspectRatio(aPreserveAspectRatio) , mGlobalOpacity(aOpacity) , mIsPaintingSVGImageElement(aIsPaintingSVGImageElement) { } - bool MaybeStoreContextPaint(nsIFrame* aFromFrame); + static void MaybeInitAndStoreContextPaint(Maybe<SVGImageContext>& aContext, + nsIFrame* aFromFrame, + imgIContainer* aImgContainer); const Maybe<CSSIntSize>& GetViewportSize() const { return mViewportSize; } void SetViewportSize(const Maybe<CSSIntSize>& aSize) { mViewportSize = aSize; } @@ -74,26 +76,34 @@ public: void SetPreserveAspectRatio(const Maybe<SVGPreserveAspectRatio>& aPAR) { mPreserveAspectRatio = aPAR; } gfxFloat GetGlobalOpacity() const { return mGlobalOpacity; } - const SVGContextPaint* GetContextPaint() const { + const SVGEmbeddingContextPaint* GetContextPaint() const { return mContextPaint.get(); } bool IsPaintingForSVGImageElement() const { return mIsPaintingSVGImageElement; } bool operator==(const SVGImageContext& aOther) const { - return mViewportSize == aOther.mViewportSize && + bool contextPaintIsEqual = + // neither have context paint, or they have the same object: + (mContextPaint == aOther.mContextPaint) || + // or both have context paint that are different but equivalent objects: + (mContextPaint && aOther.mContextPaint && + *mContextPaint == *aOther.mContextPaint); + + return contextPaintIsEqual && + mViewportSize == aOther.mViewportSize && mPreserveAspectRatio == aOther.mPreserveAspectRatio && mGlobalOpacity == aOther.mGlobalOpacity && mIsPaintingSVGImageElement == aOther.mIsPaintingSVGImageElement; } bool operator!=(const SVGImageContext& aOther) const { return !(*this == aOther); } @@ -114,17 +124,17 @@ private: static uint32_t HashSize(const CSSIntSize& aSize) { return HashGeneric(aSize.width, aSize.height); } static uint32_t HashPAR(const SVGPreserveAspectRatio& aPAR) { return aPAR.Hash(); } // NOTE: When adding new member-vars, remember to update Hash() & operator==. - RefPtr<SVGContextPaint> mContextPaint; + RefPtr<SVGEmbeddingContextPaint> mContextPaint; Maybe<CSSIntSize> mViewportSize; Maybe<SVGPreserveAspectRatio> mPreserveAspectRatio; gfxFloat mGlobalOpacity; bool mIsPaintingSVGImageElement; }; } // namespace mozilla
--- a/layout/xul/nsImageBoxFrame.cpp +++ b/layout/xul/nsImageBoxFrame.cpp @@ -405,21 +405,17 @@ nsImageBoxFrame::PaintImage(nsRenderingC dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect, intrinsicSize, intrinsicRatio, StylePosition(), anchorPoint.ptr()); } Maybe<SVGImageContext> svgContext; - if (imgCon->GetType() == imgIContainer::TYPE_VECTOR) { - // We avoid this overhead for raster images. - svgContext.emplace(); - svgContext->MaybeStoreContextPaint(this); - } + SVGImageContext::MaybeInitAndStoreContextPaint(svgContext, this, imgCon); return nsLayoutUtils::DrawSingleImage( *aRenderingContext.ThebesContext(), PresContext(), imgCon, nsLayoutUtils::GetSamplingFilterForFrame(this), dest, dirty, svgContext, aFlags, anchorPoint.ptrOr(nullptr),
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -193,17 +193,17 @@ WebrtcVideoConduit::WebrtcVideoConduit(R , mStartBitrate(0) , mPrefMaxBitrate(0) , mNegotiatedMaxBitrate(0) , mMinBitrateEstimate(0) , mCodecMode(webrtc::kRealtimeVideo) , mCall(aCall) // refcounted store of the call object , mSendStreamConfig(this) // 'this' is stored but not dereferenced in the constructor. , mRecvStreamConfig(this) // 'this' is stored but not dereferenced in the constructor. - , mRecvSSRCSet(false) + , mRecvSSRC(0) , mRecvSSRCSetInProgress(false) , mSendCodecPlugin(nullptr) , mRecvCodecPlugin(nullptr) , mVideoStatsTimer(do_CreateInstance(NS_TIMER_CONTRACTID)) { mRecvStreamConfig.renderer = this; // Video Stats Callback @@ -694,17 +694,16 @@ bool WebrtcVideoConduit::SetRemoteSSRC(unsigned int ssrc) { mRecvStreamConfig.rtp.remote_ssrc = ssrc; unsigned int current_ssrc; if (!GetRemoteSSRC(¤t_ssrc)) { return false; } - mRecvSSRCSet = true; if (current_ssrc == ssrc) { return true; } bool wasReceiving = mEngineReceiving; if (StopReceiving() != kMediaConduitNoError) { return false; @@ -1172,30 +1171,34 @@ WebrtcVideoConduit::ConfigureRecvMediaCo mRecvStreamConfig.rtp.keyframe_method = kf_request_method; if (use_fec) { mRecvStreamConfig.rtp.fec.ulpfec_payload_type = ulpfec_payload_type; mRecvStreamConfig.rtp.fec.red_payload_type = red_payload_type; mRecvStreamConfig.rtp.fec.red_rtx_payload_type = -1; } - if (!mRecvSSRCSet) { + // XXX ugh! same SSRC==0 problem that webrtc.org has + if (mRecvSSRC == 0) { // Handle un-signalled SSRCs by creating a random one and then when it actually gets set, // we'll destroy and recreate. Simpler than trying to unwind all the logic that assumes // the receive stream is created and started when we ConfigureRecvMediaCodecs() unsigned int ssrc; do { SECStatus rv = PK11_GenerateRandom(reinterpret_cast<unsigned char*>(&ssrc), sizeof(ssrc)); if (rv != SECSuccess) { return kMediaConduitUnknownError; } } while (ssrc == 0); // webrtc.org code has fits if you select an SSRC of 0 mRecvStreamConfig.rtp.remote_ssrc = ssrc; } + // Either set via SetRemoteSSRC, or temp one we created. + mRecvSSRC = mRecvStreamConfig.rtp.remote_ssrc; + // 0 isn't allowed. Would be best to ask for a random SSRC from the // RTP code. Would need to call rtp_sender.cc -- GenerateNewSSRC(), // which isn't exposed. It's called on collision, or when we decide to // send. it should be called on receiver creation. Here, we're // generating the SSRC value - but this causes ssrc_forced in set in // rtp_sender, which locks us into the SSRC - even a collision won't // change it!!! MOZ_ASSERT(!mSendStreamConfig.rtp.ssrcs.empty()); @@ -1404,33 +1407,35 @@ WebrtcVideoConduit::SelectBitrates( } else { // At low framerates, don't reduce bandwidth as much - cut slope to 1/2. // Mostly this would be ultra-low-light situations/mobile or screensharing. out_min = out_min * ((10 - (framerate / 2)) / 30); out_start = out_start * ((10 - (framerate / 2)) / 30); out_max = std::max(static_cast<int>(out_max * ((10 - (framerate / 2)) / 30)), cap); } + // Note: mNegotiatedMaxBitrate is the max transport bitrate - it applies to + // a single codec encoding, but should also apply to the sum of all + // simulcast layers in this encoding! So sum(layers.maxBitrate) <= + // mNegotiatedMaxBitrate + // Note that out_max already has had mPrefMaxBitrate applied to it + out_max = MinIgnoreZero((int)mNegotiatedMaxBitrate, out_max); + out_min = std::min(out_min, out_max); + out_start = std::min(out_start, out_max); + if (mMinBitrate && mMinBitrate > out_min) { out_min = mMinBitrate; } // If we try to set a minimum bitrate that is too low, ViE will reject it. out_min = std::max(kViEMinCodecBitrate_bps, out_min); if (mStartBitrate && mStartBitrate > out_start) { out_start = mStartBitrate; } out_start = std::max(out_start, out_min); - // Note: mNegotiatedMaxBitrate is the max transport bitrate - it applies to - // a single codec encoding, but should also apply to the sum of all - // simulcast layers in this encoding! So sum(layers.maxBitrate) <= - // mNegotiatedMaxBitrate - // Note that out_max already has had mPrefMaxBitrate applied to it - out_max = MinIgnoreZero((int)mNegotiatedMaxBitrate, out_max); - MOZ_ASSERT(mPrefMaxBitrate == 0 || out_max <= mPrefMaxBitrate); } template <class t> static void ConstrainPreservingAspectRatioExact(uint32_t max_fs, t* width, t* height) { // We could try to pick a better starting divisor, but it won't make any real @@ -1798,20 +1803,25 @@ WebrtcVideoConduit::DeliverPacket(const return kMediaConduitNoError; } MediaConduitErrorCode WebrtcVideoConduit::ReceivedRTPPacket(const void* data, int len, uint32_t ssrc) { bool queue = mRecvSSRCSetInProgress; - if (!mRecvSSRCSet && !mRecvSSRCSetInProgress) { + if (mRecvSSRC != ssrc && !queue) { + // we "switch" here immediately, but buffer until the queue is released + mRecvSSRC = ssrc; mRecvSSRCSetInProgress = true; queue = true; - // Handle the ssrc-not-signaled case; lock onto first ssrc + // any queued packets are from a previous switch that hasn't completed + // yet; drop them and only process the latest SSRC + mQueuedPackets.Clear(); + // Handle the unknown ssrc (and ssrc-not-signaled case). // We can't just do this here; it has to happen on MainThread :-( // We also don't want to drop the packet, nor stall this thread, so we hold // the packet (and any following) for inserting once the SSRC is set. // Ensure lamba captures refs RefPtr<WebrtcVideoConduit> self = this; nsCOMPtr<nsIThread> thread; if (NS_WARN_IF(NS_FAILED(NS_GetCurrentThread(getter_AddRefs(thread))))) { @@ -1821,29 +1831,33 @@ WebrtcVideoConduit::ReceivedRTPPacket(co // Normally this is done in CreateOrUpdateMediaPipeline() for // initial creation and renegotiation, but here we're rebuilding the // Receive channel at a lower level. This is needed whenever we're // creating a GMPVideoCodec (in particular, H264) so it can communicate // errors to the PC. WebrtcGmpPCHandleSetter setter(self->mPCHandle); self->SetRemoteSSRC(ssrc); // this will likely re-create the VideoReceiveStream // We want to unblock the queued packets on the original thread - thread->Dispatch(media::NewRunnableFrom([self]() mutable { - self->mRecvSSRCSetInProgress = false; - // SSRC is set; insert queued packets - for (auto& packet : self->mQueuedPackets) { - CSFLogDebug(logTag, "%s: seq# %u, Len %d ", __FUNCTION__, - (uint16_t)ntohs(((uint16_t*) packet->mData)[1]), packet->mLen); + thread->Dispatch(media::NewRunnableFrom([self, ssrc]() mutable { + if (ssrc == self->mRecvSSRC) { + // SSRC is set; insert queued packets + for (auto& packet : self->mQueuedPackets) { + CSFLogDebug(logTag, "%s: seq# %u, Len %d ", __FUNCTION__, + (uint16_t)ntohs(((uint16_t*) packet->mData)[1]), packet->mLen); - if (self->DeliverPacket(packet->mData, packet->mLen) != kMediaConduitNoError) { - CSFLogError(logTag, "%s RTP Processing Failed", __FUNCTION__); - // Keep delivering and then clear the queue + if (self->DeliverPacket(packet->mData, packet->mLen) != kMediaConduitNoError) { + CSFLogError(logTag, "%s RTP Processing Failed", __FUNCTION__); + // Keep delivering and then clear the queue + } } + self->mQueuedPackets.Clear(); + // we don't leave inprogress until there are no changes in-flight + self->mRecvSSRCSetInProgress = false; } - self->mQueuedPackets.Clear(); + // else this is an intermediate switch; another is in-flight return NS_OK; }), NS_DISPATCH_NORMAL); return NS_OK; })); // we'll return after queuing } if (queue) {
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h @@ -498,20 +498,20 @@ private: // WEBRTC.ORG Call API RefPtr<WebRtcCallWrapper> mCall; webrtc::VideoSendStream::Config mSendStreamConfig; VideoEncoderConfigBuilder mEncoderConfig; webrtc::VideoCodecH264 mEncoderSpecificH264; webrtc::VideoReceiveStream::Config mRecvStreamConfig; - // We can't create mRecvStream without knowing the remote SSRC - // Atomic since we key off this on packet insertion, which happens - // on a different thread. - Atomic<bool> mRecvSSRCSet; + + // accessed on creation, and when receiving packets + uint32_t mRecvSSRC; // this can change during a stream! + // The runnable to set the SSRC is in-flight; queue packets until it's done. bool mRecvSSRCSetInProgress; struct QueuedPacket { int mLen; uint8_t mData[1]; }; nsTArray<UniquePtr<QueuedPacket>> mQueuedPackets;
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2971,16 +2971,24 @@ pref("svg.paint-order.enabled", true); pref("svg.marker-improvements.enabled", true); // Is support for the new getBBox method from SVG 2 enabled? // See https://svgwg.org/svg2-draft/single-page.html#types-SVGBoundingBoxOptions pref("svg.new-getBBox.enabled", false); pref("svg.transform-box.enabled", true); +# This pref controls whether the 'context-fill' and 'context-stroke' keywords +# can be used in SVG-as-an-image in the content processes to use the fill/ +# stroke specified on the element that embeds the image. (These keywords are +# always enabled in the chrome process, regardless of this pref.) +# Also, these keywords are currently not part of any spec, which is partly why +# we disable them for web content. +pref("svg.context-properties.content.enabled", false); + // Default font types and sizes by locale pref("font.default.ar", "sans-serif"); pref("font.minimum-size.ar", 0); pref("font.size.variable.ar", 16); pref("font.size.fixed.ar", 13); pref("font.default.el", "serif"); pref("font.minimum-size.el", 0);
--- a/moz.configure +++ b/moz.configure @@ -300,16 +300,17 @@ set_config('MOZ_SYSTEM_HUNSPELL', depend @depends(target) @imports('os') def makensis_progs(target): if target.kernel != 'WINNT': return candidates = [ + 'makensis-3.01.exe', 'makensis-3.0b3.exe', 'makensis-3.0b1.exe', 'makensis.exe', 'makensis', ] # Look for nsis installed by msys environment. But only the 32-bit version. # We use an absolute path and insert as the first entry so it is preferred
--- a/mozglue/build/WindowsDllBlocklist.cpp +++ b/mozglue/build/WindowsDllBlocklist.cpp @@ -220,16 +220,20 @@ static const DllBlockInfo sWindowsDllBlo { "vorbis.acm", MAKE_VERSION(0, 0, 3, 6) }, // AhnLab Internet Security, bug 1311969 { "nzbrcom.dll", ALL_VERSIONS }, // K7TotalSecurity, bug 1339083. { "k7pswsen.dll", MAKE_VERSION(15, 2, 2, 95) }, + // smci*.dll - goobzo crashware (bug 1339908) + { "smci32.dll", ALL_VERSIONS }, + { "smci64.dll", ALL_VERSIONS }, + { nullptr, 0 } }; #ifndef STATUS_DLL_NOT_FOUND #define STATUS_DLL_NOT_FOUND ((DWORD)0xC0000135L) #endif // define this for very verbose dll load debug spew
--- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -2994,25 +2994,19 @@ Http2Session::WriteSegments(nsAHttpSegme nsresult Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) { MOZ_ASSERT(mAttemptingEarlyData); LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this, aRestart, aAlpnChanged)); for (size_t i = 0; i < m0RTTStreams.Length(); ++i) { - // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for - // both arguments because as long as the alpn token stayed the same, we can - // just reuse what we have in our buffer to send instead of having to have - // the transaction rewind and read it all over again. We only need to rewind - // the transaction if we're switching to a new protocol, because our buffer - // won't get used in that case. Http2Stream *stream = mStreamIDHash.Get(m0RTTStreams[i]); if (stream) { - stream->Finish0RTT(aAlpnChanged, aAlpnChanged); + stream->Finish0RTT(aRestart, aAlpnChanged); } } if (aRestart) { // 0RTT failed if (aAlpnChanged) { // This is a slightly more involved case - we need to get all our streams/ // transactions back in the queue so they can restart as http/1
--- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -1508,14 +1508,30 @@ Http2Stream::Do0RTT() return mAttempting0RTT; } nsresult Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged) { MOZ_ASSERT(mTransaction); mAttempting0RTT = false; - return mTransaction->Finish0RTT(aRestart, aAlpnChanged); + // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for + // both arguments because as long as the alpn token stayed the same, we can + // just reuse what we have in our buffer to send instead of having to have + // the transaction rewind and read it all over again. We only need to rewind + // the transaction if we're switching to a new protocol, because our buffer + // won't get used in that case. + // .. + // however, we send in the aRestart value to indicate that early data failed + // for devtools purposes + nsresult rv = mTransaction->Finish0RTT(aAlpnChanged, aAlpnChanged); + if (aRestart) { + nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); + if (trans) { + trans->Refused0RTT(); + } + } + return rv; } } // namespace net } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpAtomList.h +++ b/netwerk/protocol/http/nsHttpAtomList.h @@ -87,10 +87,11 @@ HTTP_ATOM(Upgrade, "Up HTTP_ATOM(User_Agent, "User-Agent") HTTP_ATOM(Vary, "Vary") HTTP_ATOM(Version, "Version") HTTP_ATOM(WWW_Authenticate, "WWW-Authenticate") HTTP_ATOM(Warning, "Warning") HTTP_ATOM(X_Content_Type_Options, "X-Content-Type-Options") HTTP_ATOM(X_Firefox_Spdy, "X-Firefox-Spdy") HTTP_ATOM(X_Firefox_Spdy_Proxy, "X-Firefox-Spdy-Proxy") +HTTP_ATOM(X_Firefox_Early_Data, "X-Firefox-Early-Data") // methods are case sensitive and do not use atom table
--- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -136,16 +136,17 @@ nsHttpTransaction::nsHttpTransaction() , mForTakeResponseHead(nullptr) , mResponseHeadTaken(false) , mTopLevelOuterContentWindowId(0) , mSubmittedRatePacing(false) , mPassedRatePacing(false) , mSynchronousRatePaceRequest(false) , mClassOfService(0) , m0RTTInProgress(false) + , mEarlyDataDisposition(EARLY_NONE) , mTransportStatus(NS_OK) { LOG(("Creating nsHttpTransaction @%p\n", this)); #ifdef MOZ_VALGRIND memset(&mSelfAddr, 0, sizeof(NetAddr)); memset(&mPeerAddr, 0, sizeof(NetAddr)); #endif @@ -730,16 +731,21 @@ nsHttpTransaction::ReadSegments(nsAHttpS mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); } mDeferredSendProgress = false; mReader = reader; nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead); mReader = nullptr; + if (m0RTTInProgress && (mEarlyDataDisposition == EARLY_NONE) && + NS_SUCCEEDED(rv) && (*countRead > 0)) { + mEarlyDataDisposition = EARLY_SENT; + } + if (mDeferredSendProgress && mConnection && mConnection->Transport()) { // to avoid using mRequestStream concurrently, OnTransportStatus() // did not report upload status off the ReadSegments() stack from nsSocketTransport // do it now. OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0); } mDeferredSendProgress = false; @@ -1455,16 +1461,22 @@ nsHttpTransaction::ParseHead(char *buf, nsresult nsHttpTransaction::HandleContentStart() { LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this)); MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); if (mResponseHead) { + if (mEarlyDataDisposition == EARLY_ACCEPTED) { + Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data, NS_LITERAL_CSTRING("accepted")); + } else if (mEarlyDataDisposition == EARLY_SENT) { + Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data, NS_LITERAL_CSTRING("sent")); + } // no header on NONE case + if (LOG3_ENABLED()) { LOG3(("http response [\n")); nsAutoCString headers; mResponseHead->Flatten(headers, false); headers.AppendLiteral(" OriginalHeaders"); headers.AppendLiteral("\r\n"); mResponseHead->FlattenNetworkOriginalHeaders(headers); LogHeaders(headers.get()); @@ -2165,16 +2177,21 @@ nsHttpTransaction::Do0RTT() } nsresult nsHttpTransaction::Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) { LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n", this, aRestart, aAlpnChanged)); MOZ_ASSERT(m0RTTInProgress); m0RTTInProgress = false; + if (!aRestart && (mEarlyDataDisposition == EARLY_SENT)) { + // note that if this is invoked by a 3 param version of finish0rtt this + // disposition might be reverted + mEarlyDataDisposition = EARLY_ACCEPTED; + } if (aRestart) { // Reset request headers to be sent again. nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream); if (seekable) { seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); } else { return NS_ERROR_FAILURE; @@ -2182,10 +2199,19 @@ nsHttpTransaction::Finish0RTT(bool aRest } else if (!mConnected) { // this is code that was skipped in ::ReadSegments while in 0RTT mConnected = true; mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); } return NS_OK; } +void +nsHttpTransaction::Refused0RTT() +{ + LOG(("nsHttpTransaction::Refused0RTT %p\n", this)); + if (mEarlyDataDisposition == EARLY_ACCEPTED) { + mEarlyDataDisposition = EARLY_SENT; // undo accepted state + } +} + } // namespace net } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -166,16 +166,20 @@ public: mozilla::TimeStamp GetResponseStart(); mozilla::TimeStamp GetResponseEnd(); int64_t GetTransferSize() { return mTransferSize; } MOZ_MUST_USE bool Do0RTT() override; MOZ_MUST_USE nsresult Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) override; + // After Finish0RTT early data may have failed but the caller did not request + // restart - this indicates that state for dev tools + void Refused0RTT(); + uint64_t TopLevelOuterContentWindowId() override { return mTopLevelOuterContentWindowId; } private: friend class DeleteHttpTransaction; virtual ~nsHttpTransaction(); @@ -397,16 +401,22 @@ private: public: void GetNetworkAddresses(NetAddr &self, NetAddr &peer); private: NetAddr mSelfAddr; NetAddr mPeerAddr; bool m0RTTInProgress; + enum + { + EARLY_NONE, + EARLY_SENT, + EARLY_ACCEPTED + } mEarlyDataDisposition; nsresult mTransportStatus; }; } // namespace net } // namespace mozilla #endif // nsHttpTransaction_h__
--- a/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc @@ -11,32 +11,37 @@ #include "sandbox/win/src/policy_params.h" #include "sandbox/win/src/policy_target.h" #include "sandbox/win/src/sandbox_factory.h" #include "sandbox/win/src/sandbox_nt_util.h" #include "sandbox/win/src/sharedmem_ipc_client.h" #include "sandbox/win/src/target_services.h" #include "mozilla/sandboxing/sandboxLogging.h" +// This status occurs when trying to access a network share on the machine from +// which it is shared. +#define STATUS_NETWORK_OPEN_RESTRICTION ((NTSTATUS)0xC0000201L) + namespace sandbox { NTSTATUS WINAPI TargetNtCreateFile(NtCreateFileFunction orig_CreateFile, PHANDLE file, ACCESS_MASK desired_access, POBJECT_ATTRIBUTES object_attributes, PIO_STATUS_BLOCK io_status, PLARGE_INTEGER allocation_size, ULONG file_attributes, ULONG sharing, ULONG disposition, ULONG options, PVOID ea_buffer, ULONG ea_length) { // Check if the process can open it first. NTSTATUS status = orig_CreateFile(file, desired_access, object_attributes, io_status, allocation_size, file_attributes, sharing, disposition, options, ea_buffer, ea_length); - if (STATUS_ACCESS_DENIED != status) + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) return status; mozilla::sandboxing::LogBlocked("NtCreateFile", object_attributes->ObjectName->Buffer, object_attributes->ObjectName->Length); // We don't trust that the IPC can work this early. if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) @@ -106,17 +111,18 @@ NTSTATUS WINAPI TargetNtCreateFile(NtCre NTSTATUS WINAPI TargetNtOpenFile(NtOpenFileFunction orig_OpenFile, PHANDLE file, ACCESS_MASK desired_access, POBJECT_ATTRIBUTES object_attributes, PIO_STATUS_BLOCK io_status, ULONG sharing, ULONG options) { // Check if the process can open it first. NTSTATUS status = orig_OpenFile(file, desired_access, object_attributes, io_status, sharing, options); - if (STATUS_ACCESS_DENIED != status) + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) return status; mozilla::sandboxing::LogBlocked("NtOpenFile", object_attributes->ObjectName->Buffer, object_attributes->ObjectName->Length); // We don't trust that the IPC can work this early. if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) @@ -182,17 +188,18 @@ NTSTATUS WINAPI TargetNtOpenFile(NtOpenF } NTSTATUS WINAPI TargetNtQueryAttributesFile( NtQueryAttributesFileFunction orig_QueryAttributes, POBJECT_ATTRIBUTES object_attributes, PFILE_BASIC_INFORMATION file_attributes) { // Check if the process can query it first. NTSTATUS status = orig_QueryAttributes(object_attributes, file_attributes); - if (STATUS_ACCESS_DENIED != status) + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) return status; mozilla::sandboxing::LogBlocked("NtQueryAttributesFile", object_attributes->ObjectName->Buffer, object_attributes->ObjectName->Length); // We don't trust that the IPC can work this early. if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) @@ -244,17 +251,18 @@ NTSTATUS WINAPI TargetNtQueryAttributesF NTSTATUS WINAPI TargetNtQueryFullAttributesFile( NtQueryFullAttributesFileFunction orig_QueryFullAttributes, POBJECT_ATTRIBUTES object_attributes, PFILE_NETWORK_OPEN_INFORMATION file_attributes) { // Check if the process can query it first. NTSTATUS status = orig_QueryFullAttributes(object_attributes, file_attributes); - if (STATUS_ACCESS_DENIED != status) + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) return status; mozilla::sandboxing::LogBlocked("NtQueryFullAttributesFile", object_attributes->ObjectName->Buffer, object_attributes->ObjectName->Length); // We don't trust that the IPC can work this early. if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled())
--- a/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc @@ -77,17 +77,21 @@ namespace sandbox { bool FileSystemPolicy::GenerateRules(const wchar_t* name, TargetPolicy::Semantics semantics, LowLevelPolicy* policy) { base::string16 mod_name(name); if (mod_name.empty()) { return false; } - if (!PreProcessName(&mod_name)) { + // Don't pre-process the path name and check for reparse points if it is the + // special case of allowing read access to all paths. + if (!(semantics == TargetPolicy::FILES_ALLOW_READONLY + && mod_name.compare(L"*") == 0) + && !PreProcessName(&mod_name)) { // The path to be added might contain a reparse point. NOTREACHED(); return false; } // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the // infrastructure to normalize names. In any case we need to escape the // question marks.
--- a/security/sandbox/modifications-to-chromium-to-reapply-after-upstream-merge.txt +++ b/security/sandbox/modifications-to-chromium-to-reapply-after-upstream-merge.txt @@ -1,8 +1,9 @@ Please add a link to the bugzilla bug and patch name that should be re-applied. Also, please update any existing links to their actual mozilla-central changeset. https://hg.mozilla.org/mozilla-central/rev/a05726163a79 https://hg.mozilla.org/mozilla-central/rev/e834e810a3fa https://hg.mozilla.org/mozilla-central/rev/c70d06fa5302 https://hg.mozilla.org/mozilla-central/rev/d24db55deb85 -https://bugzilla.mozilla.org/show_bug.cgi?id=1321724 bug1321724.patch +https://hg.mozilla.org/mozilla-central/rev/0e6bf137521e +https://bugzilla.mozilla.org/show_bug.cgi?id=1344453 bug1344453part1.patch
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp @@ -214,16 +214,27 @@ SandboxBroker::SetSecurityLevelForConten mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER; result = mPolicy->SetDelayedProcessMitigations(mitigations); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Invalid flags for SetDelayedProcessMitigations."); + // We still have edge cases where the child at low integrity can't read some + // files, so add a rule to allow read access to everything when required. + if (aSandboxLevel == 1 || + aPrivs == base::ChildPrivileges::PRIVILEGES_FILEREAD) { + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_READONLY, + L"*"); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + } + // Add the policy for the client side of a pipe. It is just a file // in the \pipe\ namespace. We restrict it to pipes that start with // "chrome." so the sandboxed process cannot connect to system services. result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, sandbox::TargetPolicy::FILES_ALLOW_ANY, L"\\??\\pipe\\chrome.*"); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "With these static arguments AddRule should never fail, what happened?");
--- a/storage/mozStorageAsyncStatementExecution.cpp +++ b/storage/mozStorageAsyncStatementExecution.cpp @@ -128,36 +128,35 @@ private: class CompletionNotifier : public Runnable { public: /** * This takes ownership of the callback and the StatementData. They are * released on the thread this is dispatched to (which should always be the * calling thread). */ - CompletionNotifier(mozIStorageStatementCallback *aCallback, + CompletionNotifier(already_AddRefed<mozIStorageStatementCallback> aCallback, ExecutionState aReason) : Runnable("storage::CompletionNotifier") , mCallback(aCallback) , mReason(aReason) { } NS_IMETHOD Run() override { if (mCallback) { (void)mCallback->HandleCompletion(mReason); - NS_RELEASE(mCallback); } return NS_OK; } private: - mozIStorageStatementCallback *mCallback; + RefPtr<mozIStorageStatementCallback> mCallback; ExecutionState mReason; }; } // namespace //////////////////////////////////////////////////////////////////////////////// //// AsyncExecuteStatements @@ -206,25 +205,25 @@ AsyncExecuteStatements::AsyncExecuteStat , mCallback(aCallback) , mCallingThread(::do_GetCurrentThread()) , mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS)) , mIntervalStart(TimeStamp::Now()) , mState(PENDING) , mCancelRequested(false) , mMutex(aConnection->sharedAsyncExecutionMutex) , mDBMutex(aConnection->sharedDBMutex) - , mRequestStartDate(TimeStamp::Now()) +, mRequestStartDate(TimeStamp::Now()) { (void)mStatements.SwapElements(aStatements); NS_ASSERTION(mStatements.Length(), "We weren't given any statements!"); - NS_IF_ADDREF(mCallback); } AsyncExecuteStatements::~AsyncExecuteStatements() { + MOZ_ASSERT(!mCallback, "Never called the Completion callback!"); MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point"); } bool AsyncExecuteStatements::shouldNotify() { #ifdef DEBUG mMutex.AssertNotCurrentThreadOwns(); @@ -463,22 +462,21 @@ AsyncExecuteStatements::notifyComplete() NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback"); } mHasTransaction = false; } // Always generate a completion notification; it is what guarantees that our // destruction does not happen here on the async thread. RefPtr<CompletionNotifier> completionEvent = - new CompletionNotifier(mCallback, mState); - + new CompletionNotifier(mCallback.forget(), mState); // We no longer own mCallback (the CompletionNotifier takes ownership). - mCallback = nullptr; - - (void)mCallingThread->Dispatch(completionEvent, NS_DISPATCH_NORMAL); + // Make sure we don't delete the event on this thread, since it has + // other-thread-only members. + (void)mCallingThread->Dispatch(completionEvent.forget(), NS_DISPATCH_NORMAL); return NS_OK; } nsresult AsyncExecuteStatements::notifyError(int32_t aErrorCode, const char *aMessage) { @@ -502,32 +500,38 @@ AsyncExecuteStatements::notifyError(mozI if (!mCallback) return NS_OK; RefPtr<ErrorNotifier> notifier = new ErrorNotifier(mCallback, aError, this); NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY); - return mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL); + // Make sure we don't delete the event on this thread, since it has + // other-thread-only members. + return mCallingThread->Dispatch(notifier.forget(), NS_DISPATCH_NORMAL); } nsresult AsyncExecuteStatements::notifyResults() { mMutex.AssertNotCurrentThreadOwns(); NS_ASSERTION(mCallback, "notifyResults called without a callback!"); RefPtr<CallbackResultNotifier> notifier = new CallbackResultNotifier(mCallback, mResultSet, this); NS_ENSURE_TRUE(notifier, NS_ERROR_OUT_OF_MEMORY); - nsresult rv = mCallingThread->Dispatch(notifier, NS_DISPATCH_NORMAL); - if (NS_SUCCEEDED(rv)) + // Make sure we don't delete the event on this thread, since it has + // other-thread-only members. + nsresult rv = mCallingThread->Dispatch(notifier.forget(), NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) { + // it may be freed here or on the CallingThread mResultSet = nullptr; // we no longer own it on success + } return rv; } NS_IMPL_ISUPPORTS( AsyncExecuteStatements, nsIRunnable, mozIStoragePendingStatement )
--- a/storage/mozStorageAsyncStatementExecution.h +++ b/storage/mozStorageAsyncStatementExecution.h @@ -181,17 +181,20 @@ private: * @return true if an explicit transaction is needed, false otherwise. */ bool statementsNeedTransaction(); StatementDataArray mStatements; RefPtr<Connection> mConnection; sqlite3 *mNativeConnection; bool mHasTransaction; - mozIStorageStatementCallback *mCallback; + // Note, this may not be a threadsafe object - never addref/release off + // the calling thread. We take a reference when this is created, and + // release it in the CompletionNotifier::Run() call back to this thread. + RefPtr<mozIStorageStatementCallback> mCallback; nsCOMPtr<nsIThread> mCallingThread; RefPtr<ResultSet> mResultSet; /** * The maximum amount of time we want to wait between results. Defined by * MAX_MILLISECONDS_BETWEEN_RESULTS and set at construction. */ const TimeDuration mMaxWait;
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py @@ -234,16 +234,17 @@ class TestExecuteContent(MarionetteTestC self.assertTrue(send("return typeof Components.utils == 'undefined'")) self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'")) def test_no_callback(self): self.assertTrue(self.marionette.execute_script( "return typeof arguments[0] == 'undefined'")) + @skip_if_mobile("Intermittent on Android - bug 1334035") def test_window_set_timeout_is_not_cancelled(self): def content_timeout_triggered(mn): return mn.execute_script("return window.n", sandbox=None) > 0 # subsequent call to execute_script after this # should not cancel the setTimeout event self.marionette.navigate(inline(""" <script>
--- a/testing/mozharness/configs/releases/updates_firefox_release.py +++ b/testing/mozharness/configs/releases/updates_firefox_release.py @@ -34,14 +34,16 @@ config = { }, "release": { "version_regex": r"^\d+\.\d+(\.\d+)?$", "requires_mirrors": True, "patcher_config": "mozRelease-branch-patcher2.cfg", "update_verify_channel": "release-localtest", "mar_channel_ids": [], "channel_names": ["release", "release-localtest", "release-cdntest"], - "rules_to_update": ["firefox-release-cdntest", "firefox-release-localtest"], - "publish_rules": ["firefox-release", "firefox-release-nowebsense-bypass"], + "rules_to_update": ["firefox-release-cdntest", "firefox-release-localtest", "firefox-release-localtest-nowebsense-bypass", + "firefox-release-localtest-knownwebsense-bypass", "firefox-release-cdntest-nowebsense-bypass", + "firefox-release-cdntest-knownwebsense-bypass"], + "publish_rules": ["firefox-release", "firefox-release-nowebsense-bypass", "firefox-release-knownwebsense-bypass"], }, }, "balrog_use_dummy_suffix": False, }
--- a/toolkit/components/aboutmemory/content/aboutMemory.js +++ b/toolkit/components/aboutmemory/content/aboutMemory.js @@ -571,29 +571,18 @@ function updateAboutMemoryFromJSONObject "missing 'hasMozMallocUsableSize' property"); assertInput(aObj.reports && aObj.reports instanceof Array, "missing or non-array 'reports' property"); let processMemoryReportsFromFile = function(aHandleReport, aDisplayReports) { for (let i = 0; i < aObj.reports.length; i++) { let r = aObj.reports[i]; - - // A hack: for a brief time (late in the FF26 and early in the FF27 - // cycle) we were dumping memory report files that contained reports - // whose path began with "redundant/". Such reports were ignored by - // about:memory. These reports are no longer produced, but some older - // builds are still floating around and producing files that contain - // them, so we need to still handle them (i.e. ignore them). This hack - // can be removed once FF26 and associated products (e.g. B2G 1.2) are - // no longer in common use. - if (!r.path.startsWith("redundant/")) { - aHandleReport(r.process, r.path, r.kind, r.units, r.amount, - r.description, r._presence); - } + aHandleReport(r.process, r.path, r.kind, r.units, r.amount, + r.description, r._presence); } aDisplayReports(); } appendAboutMemoryMain(processMemoryReportsFromFile, aObj.hasMozMallocUsableSize); } catch (ex) { handleException(ex); }
--- a/toolkit/components/aboutmemory/tests/memory-reports-good.json +++ b/toolkit/components/aboutmemory/tests/memory-reports-good.json @@ -9,18 +9,16 @@ {"process": "Main Process (pid NNN)", "path": "size/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, {"process": "Main Process (pid NNN)", "path": "rss/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, {"process": "Main Process (pid NNN)", "path": "pss/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, {"process": "Main Process (pid NNN)", "path": "swap/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, {"process": "Main Process (pid NNN)", "path": "compartments/system/a", "kind": 1, "units": 0, "amount": 1024, "description": ""}, {"process": "Main Process (pid NNN)", "path": "ghost-windows/a", "kind": 1, "units": 0, "amount": 1024, "description": ""}, - {"process": "Main Process (pid NNN)", "path": "redundant/should-be-ignored", "kind": 1, "units": 0, "amount": 1024, "description": ""}, - {"process": "Heap-unclassified process", "path": "heap-allocated", "kind": 2, "units": 0, "amount": 262144000, "description": "Heap allocated."}, {"process": "Heap-unclassified process", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 52428800, "description": "A b."}, {"process": "Heap-unclassified process", "path": "explicit/heap-unclassified", "kind": 1, "units": 0, "amount": 209715200, "description": "Heap unclassified"}, {"process": "Explicit-only process", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 100000, "description": "A b."}, {"process": "Other-only process", "path": "a/b", "kind": 1, "units": 0, "amount": 100000, "description": "A b."}, {"process": "Other-only process", "path": "a/c", "kind": 1, "units": 0, "amount": 100000, "description": "A c."},
--- a/toolkit/themes/osx/global/jar.mn +++ b/toolkit/themes/osx/global/jar.mn @@ -29,17 +29,16 @@ toolkit.jar: skin/classic/global/numberbox.css skin/classic/global/popup.css skin/classic/global/preferences.css skin/classic/global/progressmeter.css skin/classic/global/radio.css skin/classic/global/resizer.css skin/classic/global/richlistbox.css skin/classic/global/scrollbars.css (nativescrollbars.css) - skin/classic/global/scale.css skin/classic/global/scrollbox.css skin/classic/global/spinbuttons.css skin/classic/global/splitter.css skin/classic/global/tabprompts.css skin/classic/global/tabbox.css skin/classic/global/textbox.css skin/classic/global/datetimepicker.css skin/classic/global/toolbar.css @@ -117,18 +116,16 @@ toolkit.jar: skin/classic/global/icons/question-large.png (icons/question-large.png) skin/classic/global/icons/sslWarning.png (icons/sslWarning.png) skin/classic/global/notification/close.png (notification/close.png) skin/classic/global/notification/error-icon.png (notification/error-icon.png) skin/classic/global/notification/info-icon.png (notification/info-icon.png) skin/classic/global/notification/warning-icon.png (notification/warning-icon.png) * skin/classic/global/in-content/common.css (in-content/common.css) * skin/classic/global/in-content/info-pages.css (in-content/info-pages.css) - skin/classic/global/scale/scale-tray-horiz.gif (scale/scale-tray-horiz.gif) - skin/classic/global/scale/scale-tray-vert.gif (scale/scale-tray-vert.gif) skin/classic/global/splitter/dimple.png (splitter/dimple.png) skin/classic/global/splitter/grip-bottom.gif (splitter/grip-bottom.gif) skin/classic/global/splitter/grip-top.gif (splitter/grip-top.gif) skin/classic/global/splitter/grip-left.gif (splitter/grip-left.gif) skin/classic/global/splitter/grip-right.gif (splitter/grip-right.gif) skin/classic/global/toolbar/spring.png (toolbar/spring.png) skin/classic/global/toolbar/toolbar-separator.png (toolbar/toolbar-separator.png) skin/classic/global/tree/arrow-disclosure.svg (tree/arrow-disclosure.svg)
deleted file mode 100644 index b87fe68c15a880deadf73304263a50657d7e6c7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 97687b2e229de10d081cb99cadc6fb92fd7af5ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@<O00001
--- a/toolkit/themes/shared/jar.inc.mn +++ b/toolkit/themes/shared/jar.inc.mn @@ -19,16 +19,18 @@ toolkit.jar: skin/classic/global/aboutReaderContent.css (../../shared/aboutReaderContent.css) * skin/classic/global/aboutReaderControls.css (../../shared/aboutReaderControls.css) skin/classic/global/aboutSupport.css (../../shared/aboutSupport.css) skin/classic/global/appPicker.css (../../shared/appPicker.css) skin/classic/global/config.css (../../shared/config.css) skin/classic/global/datetimeinputpickers.css (../../shared/datetimeinputpickers.css) skin/classic/global/datetimepopup.css (../../shared/datetimepopup.css) skin/classic/global/filters.svg (../../shared/filters.svg) + skin/classic/global/passwordmgr.css (../../shared/passwordmgr.css) + skin/classic/global/scale.css (../../shared/scale.css) skin/classic/global/icons/calendar-arrows.svg (../../shared/icons/calendar-arrows.svg) skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg) skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg) skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg) skin/classic/global/icons/loading.png (../../shared/icons/loading.png) skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png) skin/classic/global/icons/spinner-arrows.svg (../../shared/icons/spinner-arrows.svg) skin/classic/global/icons/menubutton-dropmarker.svg (../../shared/icons/menubutton-dropmarker.svg) @@ -49,17 +51,16 @@ toolkit.jar: skin/classic/global/menu/shared-menu-check-active.svg (../../shared/menu-check-active.svg) skin/classic/global/menu/shared-menu-check-black.svg (../../shared/menu-check-black.svg) skin/classic/global/menu/shared-menu-check-hover.svg (../../shared/menu-check-hover.svg) skin/classic/global/in-content/check.svg (../../shared/in-content/check.svg) skin/classic/global/in-content/check-partial.svg (../../shared/in-content/check-partial.svg) skin/classic/global/in-content/dropdown.svg (../../shared/in-content/dropdown.svg) skin/classic/global/in-content/help-glyph.svg (../../shared/in-content/help-glyph.svg) skin/classic/global/in-content/radio.svg (../../shared/in-content/radio.svg) - skin/classic/global/passwordmgr.css (../../shared/passwordmgr.css) skin/classic/global/reader/RM-Close-24x24.svg (../../shared/reader/RM-Close-24x24.svg) skin/classic/global/reader/RM-Minus-24x24.svg (../../shared/reader/RM-Minus-24x24.svg) skin/classic/global/reader/RM-Plus-24x24.svg (../../shared/reader/RM-Plus-24x24.svg) skin/classic/global/reader/RM-Type-Controls-24x24.svg (../../shared/reader/RM-Type-Controls-24x24.svg) skin/classic/global/reader/RM-Type-Controls-Arrow.svg (../../shared/reader/RM-Type-Controls-Arrow.svg) skin/classic/global/reader/RM-Content-Width-Minus-42x16.svg (../../shared/reader/RM-Content-Width-Minus-42x16.svg) skin/classic/global/reader/RM-Content-Width-Plus-44x16.svg (../../shared/reader/RM-Content-Width-Plus-44x16.svg) skin/classic/global/reader/RM-Line-Height-Minus-38x14.svg (../../shared/reader/RM-Line-Height-Minus-38x14.svg)
--- a/toolkit/themes/shared/non-mac.jar.inc.mn +++ b/toolkit/themes/shared/non-mac.jar.inc.mn @@ -16,17 +16,16 @@ skin/classic/global/dialog.css (../../windows/global/dialog.css) skin/classic/global/expander.css (../../windows/global/expander.css) skin/classic/global/filefield.css (../../windows/global/filefield.css) skin/classic/global/globalBindings.xml (../../windows/global/globalBindings.xml) skin/classic/global/linkTree.css (../../windows/global/linkTree.css) skin/classic/global/progressmeter.css (../../windows/global/progressmeter.css) skin/classic/global/resizer.css (../../windows/global/resizer.css) skin/classic/global/richlistbox.css (../../windows/global/richlistbox.css) - skin/classic/global/scale.css (../../windows/global/scale.css) #ifndef MOZ_THEME_FASTSTRIPE skin/classic/global/scrollbars.css (../../windows/global/xulscrollbars.css) #endif skin/classic/global/spinbuttons.css (../../windows/global/spinbuttons.css) skin/classic/global/tabprompts.css (../../windows/global/tabprompts.css) skin/classic/global/wizard.css (../../windows/global/wizard.css) skin/classic/global/arrow/arrow-dn.gif (../../windows/global/arrow/arrow-dn.gif) @@ -80,18 +79,16 @@ skin/classic/global/icons/tabprompts-bgtexture.png (../../windows/global/icons/tabprompts-bgtexture.png) skin/classic/global/icons/Warning.png (../../windows/global/icons/Warning.png) skin/classic/global/icons/warning-large.png (../../windows/global/icons/warning-large.png) skin/classic/global/icons/warning-16.png (../../windows/global/icons/warning-16.png) skin/classic/global/icons/warning-64.png (../../windows/global/icons/warning-64.png) skin/classic/global/icons/windowControls.png (../../windows/global/icons/windowControls.png) skin/classic/global/radio/radio-check.gif (../../windows/global/radio/radio-check.gif) skin/classic/global/radio/radio-check-dis.gif (../../windows/global/radio/radio-check-dis.gif) - skin/classic/global/scale/scale-tray-horiz.gif (../../windows/global/scale/scale-tray-horiz.gif) - skin/classic/global/scale/scale-tray-vert.gif (../../windows/global/scale/scale-tray-vert.gif) skin/classic/global/scrollbar/slider.gif (../../windows/global/scrollbar/slider.gif) skin/classic/global/splitter/grip-bottom.gif (../../windows/global/splitter/grip-bottom.gif) skin/classic/global/splitter/grip-top.gif (../../windows/global/splitter/grip-top.gif) skin/classic/global/splitter/grip-left.gif (../../windows/global/splitter/grip-left.gif) skin/classic/global/splitter/grip-right.gif (../../windows/global/splitter/grip-right.gif) skin/classic/global/toolbar/chevron.gif (../../windows/global/toolbar/chevron.gif) skin/classic/global/toolbar/chevron-inverted.png (../../windows/global/toolbar/chevron-inverted.png) skin/classic/global/tree/columnpicker.gif (../../windows/global/tree/columnpicker.gif)
rename from toolkit/themes/osx/global/scale.css rename to toolkit/themes/shared/scale.css --- a/toolkit/themes/osx/global/scale.css +++ b/toolkit/themes/shared/scale.css @@ -1,46 +1,31 @@ /* 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/. */ @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); .scale-slider { -moz-appearance: scale-horizontal; - background: url("chrome://global/skin/scale/scale-tray-horiz.gif") 0% 50% repeat-x; margin: 2px 4px; width: 100px; } -.scale-slider[orient="vertical"] -{ +.scale-slider[orient="vertical"] { -moz-appearance: scale-vertical; - background: url("chrome://global/skin/scale/scale-tray-vert.gif") 50% 0% repeat-y; margin: 4px 2px; width: auto; height: 100px; } .scale-thumb { -moz-appearance: scalethumb-horizontal; - border: 2px solid; - -moz-border-top-colors: ThreeDLightShadow ThreeDHighlight; - -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow; - -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow; - -moz-border-left-colors: ThreeDLightShadow ThreeDHighlight; - background-color: -moz-Dialog; min-width: 30px; min-height: 15px; } .scale-thumb[orient="vertical"] { -moz-appearance: scalethumb-vertical; min-width: 15px; min-height: 30px; } -.scale-thumb[disabled="true"] { - -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow !important; - -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow !important; - -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow !important; - -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow !important; -}
deleted file mode 100644 --- a/toolkit/themes/windows/global/scale.css +++ /dev/null @@ -1,50 +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/. */ - -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); - -/* ::::: scale ::::: */ - -.scale-slider { - -moz-appearance: scale-horizontal; - background: url("chrome://global/skin/scale/scale-tray-horiz.gif") 0% 50% repeat-x; - margin: 2px 4px; - width: 100px; -} - -.scale-slider[orient="vertical"] -{ - -moz-appearance: scale-vertical; - background: url("chrome://global/skin/scale/scale-tray-vert.gif") 50% 0% repeat-y; - margin: 4px 2px; - width: auto; - height: 100px; -} - -/* ::::: scale thumb ::::: */ - -.scale-thumb { - -moz-appearance: scalethumb-horizontal; - border: 2px solid; - -moz-border-top-colors: ThreeDLightShadow ThreeDHighlight; - -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow; - -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow; - -moz-border-left-colors: ThreeDLightShadow ThreeDHighlight; - background-color: -moz-Dialog; - min-width: 30px; - min-height: 15px; -} - -.scale-thumb[orient="vertical"] { - -moz-appearance: scalethumb-vertical; - min-width: 15px; - min-height: 30px; -} - -.scale-thumb[disabled="true"] { - -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow !important; - -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow !important; - -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow !important; - -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow !important; -}
deleted file mode 100644 index b87fe68c15a880deadf73304263a50657d7e6c7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 97687b2e229de10d081cb99cadc6fb92fd7af5ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@<O00001
--- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -333,16 +333,40 @@ AddContentSandboxLevelAnnotation() levelString.AppendInt(level); CrashReporter::AnnotateCrashReport( NS_LITERAL_CSTRING("ContentSandboxLevel"), levelString); } } #endif /* MOZ_CONTENT_SANDBOX && !MOZ_WIDGET_GONK */ #endif /* MOZ_CRASHREPORTER */ +namespace { + +int GetDebugChildPauseTime() { + auto pauseStr = PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE"); + if (pauseStr && *pauseStr) { + int pause = atoi(pauseStr); + if (pause != 1) { // must be !=1 since =1 enables the default pause time +#if defined(OS_WIN) + pause *= 1000; // convert to ms +#endif + return pause; + } + } +#ifdef OS_POSIX + return 30; // seconds +#elif defined(OS_WIN) + return 10000; // milliseconds +#else + return 0; +#endif +} + +} // namespace + nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], const XREChildData* aChildData) { NS_ENSURE_ARG_MIN(aArgc, 2); NS_ENSURE_ARG_POINTER(aArgv); NS_ENSURE_ARG_POINTER(aArgv[0]); @@ -538,27 +562,27 @@ XRE_InitChildProcess(int aArgc, PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { #if defined(XP_LINUX) && defined(DEBUG) if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) != 0) { printf_stderr("Could not allow ptrace from any process.\n"); } #endif printf_stderr("\n\nCHILDCHILDCHILDCHILD\n debug me @ %d\n\n", base::GetCurrentProcId()); - sleep(30); + sleep(GetDebugChildPauseTime()); } #elif defined(OS_WIN) if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS")) { NS_DebugBreak(NS_DEBUG_BREAK, "Invoking NS_DebugBreak() to debug child process", nullptr, __FILE__, __LINE__); } else if (PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { printf_stderr("\n\nCHILDCHILDCHILDCHILD\n debug me @ %d\n\n", base::GetCurrentProcId()); - ::Sleep(10000); + ::Sleep(GetDebugChildPauseTime()); } #endif // child processes launched by GeckoChildProcessHost get this magic // argument appended to their command lines const char* const parentPIDString = aArgv[aArgc-1]; MOZ_ASSERT(parentPIDString, "NULL parent PID"); --aArgc;
--- a/widget/windows/WinMessages.h +++ b/widget/windows/WinMessages.h @@ -10,16 +10,18 @@ * MOZ_WM_* messages ****************************************************************************/ // A magic APP message that can be sent to quit, sort of like a // QUERYENDSESSION/ENDSESSION, but without the query. #define MOZ_WM_APP_QUIT (WM_APP+0x0300) // Used as a "tracer" event to probe event loop latency. #define MOZ_WM_TRACE (WM_APP+0x0301) +// accessibility priming +#define MOZ_WM_STARTA11Y (WM_APP+0x0302) // Our internal message for WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL and // WM_HSCROLL #define MOZ_WM_MOUSEVWHEEL (WM_APP+0x0310) #define MOZ_WM_MOUSEHWHEEL (WM_APP+0x0311) #define MOZ_WM_VSCROLL (WM_APP+0x0312) #define MOZ_WM_HSCROLL (WM_APP+0x0313) #define MOZ_WM_MOUSEWHEEL_FIRST MOZ_WM_MOUSEVWHEEL #define MOZ_WM_MOUSEWHEEL_LAST MOZ_WM_HSCROLL
--- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -932,16 +932,24 @@ nsWindow::Create(nsIWidget* aParent, } // Query for command button metric data for rendering the titlebar. We // only do this once on the first window that has an actual titlebar if (ShouldCacheTitleBarInfo(mWindowType, mBorderStyle)) { nsUXThemeData::UpdateTitlebarInfo(mWnd); } + static bool a11yPrimed = false; + if (!a11yPrimed && + mWindowType == eWindowType_toplevel) { + a11yPrimed = true; + if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) { + ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0); + } + } return NS_OK; } // Close this nsWindow void nsWindow::Destroy() { // WM_DESTROY has already fired, avoid calling it twice if (mOnDestroyCalled) @@ -5057,16 +5065,21 @@ nsWindow::ProcessMessage(UINT msg, WPARA bool abortQuit; cancelQuit->GetData(&abortQuit); sCanQuit = abortQuit ? TRI_FALSE : TRI_TRUE; } *aRetValue = sCanQuit ? TRUE : FALSE; result = true; break; + case MOZ_WM_STARTA11Y: + (void*)GetAccessible(); + result = true; + break; + case WM_ENDSESSION: case MOZ_WM_APP_QUIT: if (msg == MOZ_WM_APP_QUIT || (wParam == TRUE && sCanQuit == TRI_TRUE)) { // Let's fake a shutdown sequence without actually closing windows etc. // to avoid Windows killing us in the middle. A proper shutdown would // require having a chance to pump some messages. Unfortunately // Windows won't let us do that. Bug 212316.
--- a/xpcom/base/CycleCollectedJSContext.h +++ b/xpcom/base/CycleCollectedJSContext.h @@ -478,17 +478,20 @@ private: EnvironmentPreparer mEnvironmentPreparer; }; void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); // Returns true if the JS::TraceKind is one the cycle collector cares about. inline bool AddToCCKind(JS::TraceKind aKind) { - return aKind == JS::TraceKind::Object || aKind == JS::TraceKind::Script || aKind == JS::TraceKind::Scope; + return aKind == JS::TraceKind::Object || + aKind == JS::TraceKind::Script || + aKind == JS::TraceKind::Scope || + aKind == JS::TraceKind::RegExpShared; } bool GetBuildId(JS::BuildIdCharVector* aBuildID); } // namespace mozilla #endif // mozilla_CycleCollectedJSContext_h__