author | Dorel Luca <dluca@mozilla.com> |
Thu, 15 Feb 2018 00:23:02 +0200 | |
changeset 456169 | 27fd083ed7ee5782e52a5eaf0286c5ffa8b35a9e |
parent 456118 | 9284d8cfa1e7618836fb630288d780eca3d07a9c (current diff) |
parent 456168 | 04b90b86ce92b4f2859e968e61fc7d1a3bcbd6ec (diff) |
child 456249 | 223a0551c4e0a4e1a4243c8d7b6de0a774aec829 |
child 456370 | 0cd7406c124f4f11eca9cc898bd42a3531456c0b |
push id | 8799 |
push user | mtabara@mozilla.com |
push date | Thu, 01 Mar 2018 16:46:23 +0000 |
treeherder | mozilla-beta@15334014dc67 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 60.0a1 |
first release with | nightly linux32
27fd083ed7ee
/
60.0a1
/
20180214224814
/
files
nightly linux64
27fd083ed7ee
/
60.0a1
/
20180214224814
/
files
nightly mac
27fd083ed7ee
/
60.0a1
/
20180214224814
/
files
nightly win32
27fd083ed7ee
/
60.0a1
/
20180214224814
/
files
nightly win64
27fd083ed7ee
/
60.0a1
/
20180214224814
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
60.0a1
/
20180214224814
/
pushlog to previous
nightly linux64
60.0a1
/
20180214224814
/
pushlog to previous
nightly mac
60.0a1
/
20180214224814
/
pushlog to previous
nightly win32
60.0a1
/
20180214224814
/
pushlog to previous
nightly win64
60.0a1
/
20180214224814
/
pushlog to previous
|
new file mode 100644 --- /dev/null +++ b/browser/base/content/aboutRobots.css @@ -0,0 +1,26 @@ +#errorPageContainer { + background-image: none; +} + +#errorPageContainer:before { + content: url('chrome://browser/content/aboutRobots-icon.png'); + position: absolute; +} + +body[dir=rtl] #icon, +body[dir=rtl] #errorPageContainer:before { + transform: scaleX(-1); +} + +#widget1 { + position: absolute; + bottom: -12px; + left: -10px; +} + +#widget2 { + position: absolute; + bottom: -12px; + right: -10px; + transform: scaleX(-1); +}
new file mode 100644 --- /dev/null +++ b/browser/base/content/aboutRobots.js @@ -0,0 +1,11 @@ +var buttonClicked = false; +var button = document.getElementById("errorTryAgain"); +button.onclick = function() { + if (buttonClicked) { + button.style.visibility = "hidden"; + } else { + var newLabel = button.getAttribute("label2"); + button.textContent = newLabel; + buttonClicked = true; + } +};
--- a/browser/base/content/aboutRobots.xhtml +++ b/browser/base/content/aboutRobots.xhtml @@ -17,49 +17,21 @@ %globalDTD; <!ENTITY % aboutrobotsDTD SYSTEM "chrome://browser/locale/aboutRobots.dtd"> %aboutrobotsDTD; ]> <html xmlns="http://www.w3.org/1999/xhtml"> <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:" /> <title>&robots.pagetitle;</title> <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" /> <link rel="icon" type="image/png" id="favicon" href="chrome://browser/content/robot.ico"/> - - <script type="application/javascript"><![CDATA[ - var buttonClicked = false; - function robotButton() { - var button = document.getElementById("errorTryAgain"); - if (buttonClicked) { - button.style.visibility = "hidden"; - } else { - var newLabel = button.getAttribute("label2"); - button.textContent = newLabel; - buttonClicked = true; - } - } - ]]></script> - - <style type="text/css"><![CDATA[ - #errorPageContainer { - background-image: none; - } - - #errorPageContainer:before { - content: url('chrome://browser/content/aboutRobots-icon.png'); - position: absolute; - } - - body[dir=rtl] #icon, - body[dir=rtl] #errorPageContainer:before { - transform: scaleX(-1); - } - ]]></style> + <link rel="stylesheet" href="chrome://browser/content/aboutRobots.css" type="text/css"/> </head> <body dir="&locale.dir;"> <!-- PAGE CONTAINER (for styling purposes only) --> <div id="errorPageContainer"> <!-- Error Title --> @@ -89,19 +61,16 @@ <div id="errorTrailerDesc"> <p id="errorTrailerDescText">&robots.errorTrailerDescText;</p> </div> </div> <!-- Button --> <button id="errorTryAgain" - label2="&robots.dontpress;" - onclick="robotButton();">&retry.label;</button> + label2="&robots.dontpress;">&retry.label;</button> - <img src="chrome://browser/content/aboutRobots-widget-left.png" - style="position: absolute; bottom: -12px; left: -10px;"/> - <img src="chrome://browser/content/aboutRobots-widget-left.png" - style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/> + <img id="widget1" src="chrome://browser/content/aboutRobots-widget-left.png"/> + <img id="widget2" src="chrome://browser/content/aboutRobots-widget-left.png"/> </div> - </body> + <script type="application/javascript" src="chrome://browser/content/aboutRobots.js"/> </html>
--- a/browser/base/content/aboutTabCrashed.xhtml +++ b/browser/base/content/aboutTabCrashed.xhtml @@ -16,16 +16,17 @@ %brandDTD; <!ENTITY % tabCrashedDTD SYSTEM "chrome://browser/locale/aboutTabCrashed.dtd"> %tabCrashedDTD; ]> <html xmlns="http://www.w3.org/1999/xhtml"> <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:" /> <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/in-content/info-pages.css"/> <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/aboutTabCrashed.css"/> <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/aboutTabCrashed.css"/> <title>&tabCrashed.title;</title>
--- a/browser/base/content/abouthome/aboutHome.xhtml +++ b/browser/base/content/abouthome/aboutHome.xhtml @@ -14,16 +14,17 @@ <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd"> %aboutHomeDTD; <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" > %browserDTD; ]> <html xmlns="http://www.w3.org/1999/xhtml"> <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:" /> <title>&abouthome.pageTitle;</title> <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/> <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/contentSearchUI.css"/> <link rel="stylesheet" type="text/css" media="all" defer="defer" href="chrome://browser/content/abouthome/aboutHome.css"/>
--- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -6,16 +6,18 @@ browser.jar: % overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul % overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul content/browser/aboutDialog-appUpdater.js (content/aboutDialog-appUpdater.js) * content/browser/aboutDialog.xul (content/aboutDialog.xul) content/browser/aboutDialog.js (content/aboutDialog.js) content/browser/aboutDialog.css (content/aboutDialog.css) content/browser/aboutRobots.xhtml (content/aboutRobots.xhtml) + content/browser/aboutRobots.js (content/aboutRobots.js) + content/browser/aboutRobots.css (content/aboutRobots.css) * content/browser/abouthome/aboutHome.xhtml (content/abouthome/aboutHome.xhtml) content/browser/abouthome/aboutHome.js (content/abouthome/aboutHome.js) * content/browser/abouthome/aboutHome.css (content/abouthome/aboutHome.css) content/browser/abouthome/snippet1.png (content/abouthome/snippet1.png) content/browser/abouthome/snippet2.png (content/abouthome/snippet2.png) content/browser/abouthome/downloads.png (content/abouthome/downloads.png) content/browser/abouthome/bookmarks.png (content/abouthome/bookmarks.png) content/browser/abouthome/history.png (content/abouthome/history.png)
--- a/browser/components/feeds/content/subscribe.js +++ b/browser/components/feeds/content/subscribe.js @@ -16,8 +16,18 @@ var SubscribeHandler = { writeContent: function SH_writeContent() { this._feedWriter.writeContent(); }, uninit: function SH_uninit() { this._feedWriter.close(); } }; + +SubscribeHandler.init(); + +window.onload = function() { + SubscribeHandler.writeContent(); +}; + +window.onunload = function() { + SubscribeHandler.uninit(); +};
--- a/browser/components/feeds/content/subscribe.xhtml +++ b/browser/components/feeds/content/subscribe.xhtml @@ -17,25 +17,24 @@ %feedDTD; ]> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <html id="feedHandler" xmlns="http://www.w3.org/1999/xhtml"> <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; img-src *; media-src *" /> <title>&feedPage.title;</title> <link rel="stylesheet" href="chrome://browser/skin/feeds/subscribe.css" type="text/css" media="all"/> - <script type="application/javascript" - src="chrome://browser/content/feeds/subscribe.js"/> </head> - <body onload="SubscribeHandler.writeContent();" onunload="SubscribeHandler.uninit();"> + <body> <div id="feedHeaderContainer"> <div id="feedHeader" dir="&locale.dir;"> <div id="feedIntroText"> <p id="feedSubscriptionInfo1" /> <p id="feedSubscriptionInfo2" /> </div> <div id="feedSubscribeLine"> <label id="subscribeUsingDescription"> @@ -47,28 +46,23 @@ <label id="checkboxText"> <input type="checkbox" id="alwaysUse" class="alwaysUse" checked="false"/> </label> <button id="subscribeButton">&feedSubscribeNow;</button> </div> </div> <div id="feedHeaderContainerSpacer"/> </div> - - <script type="application/javascript"> - /* import-globals-from subscribe.js */ - SubscribeHandler.init(); - </script> - <div id="feedBody"> <div id="feedTitle"> <a id="feedTitleLink"> <img id="feedTitleImage"/> </a> <div id="feedTitleContainer"> <h1 id="feedTitleText"/> <h2 id="feedSubtitleText"/> </div> </div> <div id="feedContent"/> </div> </body> + <script type="application/javascript" src="chrome://browser/content/feeds/subscribe.js"/> </html>
new file mode 100644 --- /dev/null +++ b/browser/components/places/tests/browser/bookmarklet_windowOpen_dummy.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>Bookmarklet windowOpen Dummy</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<p>Bookmarklet windowOpen Dummy</p> +</body> +</html>
--- a/browser/components/places/tests/browser/browser.ini +++ b/browser/components/places/tests/browser/browser.ini @@ -17,16 +17,18 @@ support-files = skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmark_change_location.js] skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmark_folder_moveability.js] skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmark_remove_tags.js] skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmarklet_windowOpen.js] +support-files = + bookmarklet_windowOpen_dummy.html [browser_bookmarks_change_title.js] skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmarks_sidebar_search.js] skip-if = (os == 'win' && ccov) # Bug 1423667 support-files = pageopeningwindow.html [browser_bookmarkProperties_addFolderDefaultButton.js] skip-if = (os == 'win' && ccov) # Bug 1423667
--- a/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js +++ b/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js @@ -1,11 +1,14 @@ "use strict"; -const TEST_URL = "http://example.com/browser/browser/components/places/tests/browser/pageopeningwindow.html"; +let BASE_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", + "http://example.com/"); +const TEST_URL = BASE_URL + "pageopeningwindow.html"; +const DUMMY_URL = BASE_URL + "bookmarklet_windowOpen_dummy.html"; function makeBookmarkFor(url, keyword) { return Promise.all([ PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, title: "bookmarklet", url }), PlacesUtils.keywords.insert({url, keyword}) @@ -16,17 +19,17 @@ function makeBookmarkFor(url, keyword) { add_task(async function openKeywordBookmarkWithWindowOpen() { // This is the current default, but let's not assume that... await SpecialPowers.pushPrefEnv({"set": [ [ "browser.link.open_newwindow", 3 ], [ "dom.disable_open_during_load", true ] ]}); let moztab; - let tabOpened = BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla") + let tabOpened = BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_URL) .then((tab) => { moztab = tab; }); let keywordForBM = "openmeatab"; let bookmarkInfo; let bookmarkCreated = makeBookmarkFor("javascript:void open('" + TEST_URL + "')", keywordForBM) .then((values) => { bookmarkInfo = values[0];
--- a/devtools/client/framework/components/toolbox-controller.js +++ b/devtools/client/framework/components/toolbox-controller.js @@ -18,17 +18,17 @@ class ToolboxController extends Componen super(props, context); // See the ToolboxToolbar propTypes for documentation on each of these items in // state, and for the definitions of the props that are expected to be passed in. this.state = { focusedButton: ELEMENT_PICKER_ID, currentToolId: null, canRender: false, - highlightedTool: "", + highlightedTools: new Set(), areDockButtonsEnabled: true, panelDefinitions: [], hostTypes: [], canCloseToolbox: true, toolboxButtons: [], buttonIds: [], checkedButtonsUpdated: () => { this.forceUpdate(); @@ -113,22 +113,26 @@ class ToolboxController extends Componen } setOptionsPanel(optionsPanel) { this.setState({ optionsPanel }); this.updateButtonIds(); } highlightTool(highlightedTool) { - this.setState({ highlightedTool }); + let { highlightedTools } = this.state; + highlightedTools.add(highlightedTool); + this.setState({ highlightedTools }); } unhighlightTool(id) { - if (this.state.highlightedTool === id) { - this.setState({ highlightedTool: "" }); + let { highlightedTools } = this.state; + if (highlightedTools.has(id)) { + highlightedTools.delete(id); + this.setState({ highlightedTools }); } } setDockButtonsEnabled(areDockButtonsEnabled) { this.setState({ areDockButtonsEnabled }); this.updateButtonIds(); }
--- a/devtools/client/framework/components/toolbox-tab.js +++ b/devtools/client/framework/components/toolbox-tab.js @@ -10,17 +10,17 @@ const {img, button, span} = dom; class ToolboxTab extends Component { // See toolbox-toolbar propTypes for details on the props used here. static get propTypes() { return { currentToolId: PropTypes.string, focusButton: PropTypes.func, focusedButton: PropTypes.string, - highlightedTool: PropTypes.string, + highlightedTools: PropTypes.object.isRequired, panelDefinition: PropTypes.object, selectTool: PropTypes.func, }; } constructor(props) { super(props); this.renderIcon = this.renderIcon.bind(this); @@ -34,25 +34,25 @@ class ToolboxTab extends Component { return [ img({ src: icon }), ]; } render() { - const {panelDefinition, currentToolId, highlightedTool, selectTool, + const {panelDefinition, currentToolId, highlightedTools, selectTool, focusedButton, focusButton} = this.props; const {id, tooltip, label, iconOnly} = panelDefinition; const isHighlighted = id === currentToolId; const className = [ "devtools-tab", currentToolId === id ? "selected" : "", - highlightedTool === id ? "highlighted" : "", + highlightedTools.has(id) ? "highlighted" : "", iconOnly ? "devtools-tab-icon-only" : "" ].join(" "); return button( { className, id: `toolbox-tab-${id}`, "data-id": id,
--- a/devtools/client/framework/components/toolbox-tabs.js +++ b/devtools/client/framework/components/toolbox-tabs.js @@ -15,17 +15,17 @@ const ToolboxTab = createFactory(require class ToolboxTabs extends Component { // See toolbox-toolbar propTypes for details on the props used here. static get propTypes() { return { currentToolId: PropTypes.string, focusButton: PropTypes.func, focusedButton: PropTypes.string, - highlightedTool: PropTypes.string, + highlightedTools: PropTypes.object, panelDefinitions: PropTypes.array, selectTool: PropTypes.func, toolbox: PropTypes.object, L10N: PropTypes.object, }; } constructor(props) { @@ -83,26 +83,26 @@ class ToolboxTabs extends Component { * a toolbox tab for each of them. Will render an all-tabs button if the * container has an overflow. */ render() { let { currentToolId, focusButton, focusedButton, - highlightedTool, + highlightedTools, panelDefinitions, selectTool, } = this.props; let tabs = panelDefinitions.map(panelDefinition => ToolboxTab({ currentToolId, focusButton, focusedButton, - highlightedTool, + highlightedTools, panelDefinition, selectTool, })); // A wrapper is needed to get flex sizing correct in XUL. return div( { className: "toolbox-tabs-wrapper"
--- a/devtools/client/framework/components/toolbox-toolbar.js +++ b/devtools/client/framework/components/toolbox-toolbar.js @@ -22,18 +22,19 @@ class ToolboxToolbar extends Component { return { // The currently focused item (for arrow keyboard navigation) // This ID determines the tabindex being 0 or -1. focusedButton: PropTypes.string, // List of command button definitions. toolboxButtons: PropTypes.array, // The id of the currently selected tool, e.g. "inspector" currentToolId: PropTypes.string, - // An optionally highlighted tool, e.g. "inspector" - highlightedTool: PropTypes.string, + // An optionally highlighted tools, e.g. "inspector". + // Note: highlightedTools must be an instance of Set. + highlightedTools: PropTypes.object, // List of tool panel definitions. panelDefinitions: PropTypes.array, // Function to select a tool based on its id. selectTool: PropTypes.func, // Keep a record of what button is focused. focusButton: PropTypes.func, // The options button definition. optionsPanel: PropTypes.object, @@ -144,23 +145,25 @@ function renderToolboxButtons({toolboxBu * The options button is a ToolboxTab just like in the ToolboxTabs component. However * it is separate from the normal tabs, so deal with it separately here. * * @param {Object} optionsPanel - A single panel definition for the options panel. * @param {String} currentToolId - The currently selected tool's id; e.g. "inspector". * @param {Function} selectTool - Function to select a tool in the toolbox. * @param {String} focusedButton - The id of the focused button. * @param {Function} focusButton - Keep a record of the currently focused button. + * @param {Object} highlightedTools - Optionally highlighted tools, e.g. "inspector". */ function renderOptions({optionsPanel, currentToolId, selectTool, focusedButton, - focusButton}) { + focusButton, highlightedTools}) { return div({id: "toolbox-option-container"}, ToolboxTab({ panelDefinition: optionsPanel, currentToolId, selectTool, + highlightedTools, focusedButton, focusButton, })); } /** * Render a separator. */
--- a/devtools/client/framework/test/browser_toolbox_highlight.js +++ b/devtools/client/framework/test/browser_toolbox_highlight.js @@ -1,43 +1,59 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + var {Toolbox} = require("devtools/client/framework/toolbox"); var toolbox = null; function test() { Task.spawn(function* () { const URL = "data:text/plain;charset=UTF-8,Nothing to see here, move along"; const TOOL_ID_1 = "jsdebugger"; const TOOL_ID_2 = "webconsole"; yield addTab(URL); const target = TargetFactory.forTab(gBrowser.selectedTab); - toolbox = yield gDevTools.showToolbox(target, TOOL_ID_1, Toolbox.HostType.BOTTOM) + toolbox = yield gDevTools.showToolbox(target, TOOL_ID_1, Toolbox.HostType.BOTTOM); // select tool 2 - yield toolbox.selectTool(TOOL_ID_2) + yield toolbox.selectTool(TOOL_ID_2); // and highlight the first one yield highlightTab(TOOL_ID_1); // to see if it has the proper class. yield checkHighlighted(TOOL_ID_1); // Now switch back to first tool yield toolbox.selectTool(TOOL_ID_1); // to check again. But there is no easy way to test if // it is showing orange or not. yield checkNoHighlightWhenSelected(TOOL_ID_1); // Switch to tool 2 again yield toolbox.selectTool(TOOL_ID_2); // and check again. yield checkHighlighted(TOOL_ID_1); + // Highlight another tool + yield highlightTab(TOOL_ID_2); + // Check that both tools are highlighted. + yield checkHighlighted(TOOL_ID_1); + // Check second tool being both highlighted and selected. + yield checkNoHighlightWhenSelected(TOOL_ID_2); + // Select tool 1 + yield toolbox.selectTool(TOOL_ID_1); + // Check second tool is still highlighted + yield checkHighlighted(TOOL_ID_2); + // Unhighlight the second tool + yield unhighlightTab(TOOL_ID_2); + // to see the classes gone. + yield checkNoHighlight(TOOL_ID_2); // Now unhighlight the tool yield unhighlightTab(TOOL_ID_1); // to see the classes gone. yield checkNoHighlight(TOOL_ID_1); // Now close the toolbox and exit. executeSoon(() => { toolbox.destroy().then(() => {
--- a/devtools/client/shared/components/VirtualizedTree.js +++ b/devtools/client/shared/components/VirtualizedTree.js @@ -190,16 +190,19 @@ class Tree extends Component { // Optional props // The currently focused item, if any such item exists. focused: PropTypes.any, // Handle when a new item is focused. onFocus: PropTypes.func, + // Handle when item is activated with a keyboard (using Space or Enter) + onActivate: PropTypes.func, + // The depth to which we should automatically expand new items. autoExpandDepth: PropTypes.number, // Note: the two properties below are mutually exclusive. Only one of the // label properties is necessary. // ID of an element whose textual content serves as an accessible label for // a tree. labelledby: PropTypes.string, @@ -238,16 +241,19 @@ class Tree extends Component { }; this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this); this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this); this._onScroll = oncePerAnimationFrame(this._onScroll).bind(this); this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this); this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this); this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this); + this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(this); + this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this); + this._activateNode = oncePerAnimationFrame(this._activateNode).bind(this); this._autoExpand = this._autoExpand.bind(this); this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this); this._updateHeight = this._updateHeight.bind(this); this._dfs = this._dfs.bind(this); this._dfsFromRoots = this._dfsFromRoots.bind(this); this._focus = this._focus.bind(this); this._onBlur = this._onBlur.bind(this); @@ -318,19 +324,22 @@ class Tree extends Component { } } } /** * Updates the state's height based on clientHeight. */ _updateHeight() { - this.setState({ - height: this.refs.tree.clientHeight - }); + if (this.refs.tree.clientHeight && + this.refs.tree.clientHeight !== this.state.height) { + this.setState({ + height: this.refs.tree.clientHeight + }); + } } /** * Perform a pre-order depth-first search from item. */ _dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) { traversal.push({ item, depth: _depth }); @@ -489,19 +498,49 @@ class Tree extends Component { case "ArrowRight": if (!this.props.isExpanded(this.props.focused)) { this._onExpand(this.props.focused); } else { this._focusNextNode(); } break; + + case "Home": + this._focusFirstNode(); + break; + + case "End": + this._focusLastNode(); + break; + + case "Enter": + case " ": + this._activateNode(); + break; } } + _activateNode() { + if (this.props.onActivate) { + this.props.onActivate(this.props.focused); + } + } + + _focusFirstNode() { + const traversal = this._dfsFromRoots(); + this._focus(0, traversal[0].item); + } + + _focusLastNode() { + const traversal = this._dfsFromRoots(); + const lastIndex = traversal.length - 1; + this._focus(lastIndex, traversal[lastIndex].item); + } + /** * Sets the previous node relative to the currently focused item, to focused. */ _focusPrevNode() { // Start a depth first search and keep going until we reach the currently // focused node. Focus the previous node in the DFS, if it exists. If it // doesn't exist, we're at the first node already.
--- a/devtools/client/shared/components/test/mochitest/chrome.ini +++ b/devtools/client/shared/components/test/mochitest/chrome.ini @@ -21,8 +21,9 @@ support-files = [test_tree_04.html] [test_tree_05.html] [test_tree_06.html] [test_tree_07.html] [test_tree_08.html] [test_tree_09.html] [test_tree_10.html] [test_tree_11.html] +[test_tree_12.html]
new file mode 100644 --- /dev/null +++ b/devtools/client/shared/components/test/mochitest/test_tree_12.html @@ -0,0 +1,258 @@ +<!-- 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/. --> +<!DOCTYPE HTML> +<html> +<!-- +Test keyboard navigation/activation with the VirtualizedTree component. +--> +<head> + <meta charset="utf-8"> + <title>Tree component test</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript"></script> +<script type="application/javascript"> + +"use strict"; + +window.onload = async function () { + try { + const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); + const { createFactory } = browserRequire("devtools/client/shared/vendor/react"); + const { Simulate } = + browserRequire("devtools/client/shared/vendor/react-dom-test-utils"); + const Tree = + createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree")); + + function renderTree(props) { + const treeProps = { + ...TEST_TREE_INTERFACE, + onFocus: x => renderTree({ focused: x }), + ...props + }; + + return ReactDOM.render(Tree(treeProps), window.document.body); + } + + const checker = Symbol(); + let isActivated; + const mockFn = activated => { + isActivated = activated; + }; + + const tree = renderTree(); + + TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split("")); + + // Test Home key ----------------------------------------------------------- + + info("Press Home to move to the first node."); + renderTree({ focused: "L" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "Home" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Home key, A should be focused."); + + info("Press Home again when already on first node."); + Simulate.keyDown(document.querySelector(".tree"), { key: "Home" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:true", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Home key again, A should still be focused."); + + // Test End key ------------------------------------------------------------ + + info("Press End to move to the last node."); + Simulate.keyDown(document.querySelector(".tree"), { key: "End" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:true", + ], "After the End key, O should be focused."); + + info("Press End again when already on last node."); + Simulate.keyDown(document.querySelector(".tree"), { key: "End" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:true", + ], "After the End key again, O should still be focused."); + + // Test Enter key ---------------------------------------------------------- + + info("Press Enter to activate node, when onActivate is not passed."); + isActivated = checker; + renderTree({ focused: "L" }); + Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:true", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Enter, L should be focused and the tree remained unchanged."); + ok(isActivated === checker, + "Since onActivate was not specified, 'isActivated' should not be set."); + + info("Press Enter to activate node, when onActivate is passed."); + isActivated = checker; + renderTree({ focused: "L", onActivate: mockFn }); + Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:false", + "---L:true", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Enter, L should be focused and the tree remained unchanged."); + is(isActivated, "L", "onActivate function was called with the right node."); + + // Test Space key ---------------------------------------------------------- + + info("Press Space to activate node, when onActivate is not passed."); + isActivated = checker; + renderTree({ focused: "K" }); + Simulate.keyDown(document.querySelector(".tree"), { key: " " }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:true", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Space, K should be focused and the tree remained unchanged."); + ok(isActivated === checker, + "Since onActivate was not specified, 'isActivated' should not be set."); + + info("Press Space to activate node, when onActivate is passed."); + isActivated = checker; + renderTree({ focused: "K", onActivate: mockFn }); + Simulate.keyDown(document.querySelector(".tree"), { key: " " }); + await forceRender(tree); + + isRenderedTree(document.body.textContent, [ + "A:false", + "-B:false", + "--E:false", + "---K:true", + "---L:false", + "--F:false", + "--G:false", + "-C:false", + "--H:false", + "--I:false", + "-D:false", + "--J:false", + "M:false", + "-N:false", + "--O:false", + ], "After the Space, K should be focused and the tree remained unchanged."); + is(isActivated, "K", "onActivate function was called with the right node."); + } catch (e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html>
--- a/devtools/client/shared/components/tree/TreeHeader.js +++ b/devtools/client/shared/components/tree/TreeHeader.js @@ -81,17 +81,20 @@ define(function (require, exports, modul cells.push( td({ className: classNames.join(" "), style: cellStyle, role: "presentation", id: col.id, key: col.id, }, - visible ? div({ className: "treeHeaderCellBox" }, col.title) : null + visible ? div({ + className: "treeHeaderCellBox", + role: "presentation" + }, col.title) : null ) ); }); return ( thead({ role: "presentation" }, tr({
--- a/devtools/client/shared/components/tree/TreeView.js +++ b/devtools/client/shared/components/tree/TreeView.js @@ -13,21 +13,31 @@ define(function (require, exports, modul const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); // Reps const { ObjectProvider } = require("./ObjectProvider"); const TreeRow = createFactory(require("./TreeRow")); const TreeHeader = createFactory(require("./TreeHeader")); + const SUPPORTED_KEYS = [ + "ArrowUp", + "ArrowDown", + "ArrowLeft", + "ArrowRight", + "End", + "Home" + ]; + const defaultProps = { object: null, renderRow: null, provider: ObjectProvider, expandedNodes: new Set(), + selected: null, expandableStrings: true, columns: [] }; /** * This component represents a tree view with expandable/collapsible nodes. * The tree is rendered using <table> element where every node is represented * by <tr> element. The tree is one big table where nodes (rows) are properly @@ -94,20 +104,24 @@ define(function (require, exports, modul // Custom cell renderer renderCell: PropTypes.func, // Custom value renderer renderValue: PropTypes.func, // Custom tree label (including a toggle button) renderer renderLabelCell: PropTypes.func, // Set of expanded nodes expandedNodes: PropTypes.object, + // Selected node + selected: PropTypes.string, // Custom filtering callback onFilter: PropTypes.func, // Custom sorting callback onSort: PropTypes.func, + // Custom row click callback + onClickRow: PropTypes.func, // A header is displayed if set to true header: PropTypes.bool, // Long string is expandable by a toggle button expandableStrings: PropTypes.bool, // Array of columns columns: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, title: PropTypes.string, @@ -121,46 +135,52 @@ define(function (require, exports, modul } constructor(props) { super(props); this.state = { expandedNodes: props.expandedNodes, columns: ensureDefaultColumn(props.columns), - selected: null + selected: props.selected, + lastSelectedIndex: 0 }; this.toggle = this.toggle.bind(this); this.isExpanded = this.isExpanded.bind(this); this.onKeyDown = this.onKeyDown.bind(this); this.onClickRow = this.onClickRow.bind(this); this.getSelectedRow = this.getSelectedRow.bind(this); this.selectRow = this.selectRow.bind(this); this.isSelected = this.isSelected.bind(this); this.onFilter = this.onFilter.bind(this); this.onSort = this.onSort.bind(this); this.getMembers = this.getMembers.bind(this); this.renderRows = this.renderRows.bind(this); } componentWillReceiveProps(nextProps) { - let { expandedNodes } = nextProps; - this.setState(Object.assign({}, this.state, { + let { expandedNodes, selected } = nextProps; + let state = { expandedNodes, - })); + lastSelectedIndex: this.getSelectedRowIndex() + }; + + if (selected) { + state.selected = selected; + } + + this.setState(Object.assign({}, this.state, state)); } componentDidUpdate() { - let selected = this.getSelectedRow(this.rows); + let selected = this.getSelectedRow(); 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]); + this.selectRow(this.rows[ + Math.min(this.state.lastSelectedIndex, this.rows.length - 1)]); } } static subPath(path, subKey) { return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&"); } /** @@ -224,21 +244,21 @@ define(function (require, exports, modul isExpanded(nodePath) { return this.state.expandedNodes.has(nodePath); } // Event Handlers onKeyDown(event) { - if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) { + if (!SUPPORTED_KEYS.includes(event.key)) { return; } - let row = this.getSelectedRow(this.rows); + let row = this.getSelectedRow(); if (!row) { return; } let index = this.rows.indexOf(row); switch (event.key) { case "ArrowRight": let { hasChildren, open } = row.props.member; @@ -258,39 +278,74 @@ define(function (require, exports, modul } break; case "ArrowUp": let previousRow = this.rows[index - 1]; if (previousRow) { this.selectRow(previousRow); } break; + case "Home": + let firstRow = this.rows[0]; + if (firstRow) { + this.selectRow(firstRow); + } + break; + case "End": + let lastRow = this.rows[this.rows.length - 1]; + if (lastRow) { + this.selectRow(lastRow); + } + break; } + // Focus should always remain on the tree container itself. + this.tree.focus(); event.preventDefault(); } onClickRow(nodePath, event) { + let onClickRow = this.props.onClickRow; + + if (onClickRow) { + onClickRow.call(this, nodePath, event); + return; + } + event.stopPropagation(); let cell = event.target.closest("td"); if (cell && cell.classList.contains("treeLabelCell")) { this.toggle(nodePath); } this.selectRow(event.currentTarget); } - getSelectedRow(rows) { - if (!this.state.selected || rows.length === 0) { + getSelectedRow() { + if (!this.state.selected || this.rows.length === 0) { return null; } - return rows.find(row => this.isSelected(row.props.member.path)); + return this.rows.find(row => this.isSelected(row.props.member.path)); + } + + getSelectedRowIndex() { + let row = this.getSelectedRow(); + if (!row) { + // If selected row is not found, return index of the first row. + return 0; + } + + return this.rows.indexOf(row); } selectRow(row) { row = findDOMNode(row); + if (this.state.selected === row.id) { + return; + } + this.setState(Object.assign({}, this.state, { selected: row.id })); row.scrollIntoView({block: "nearest"}); } isSelected(nodePath) { return nodePath === this.state.selected; @@ -460,25 +515,29 @@ define(function (require, exports, modul let props = Object.assign({}, this.props, { columns: this.state.columns }); return ( dom.table({ className: classNames.join(" "), role: "tree", + ref: tree => { + this.tree = tree; + }, tabIndex: 0, onKeyDown: this.onKeyDown, "aria-label": this.props.label || "", "aria-activedescendant": this.state.selected, cellPadding: 0, cellSpacing: 0}, TreeHeader(props), dom.tbody({ - role: "presentation" + role: "presentation", + tabIndex: -1 }, rows) ) ); } } // Helpers
--- a/devtools/server/actors/highlighters.css +++ b/devtools/server/actors/highlighters.css @@ -636,8 +636,15 @@ :-moz-native-anonymous .shapes-markers-outline { fill: var(--highlighter-guide-color); } :-moz-native-anonymous .shapes-marker-hover { fill: var(--highlighter-guide-color); } + +/* Accessible highlighter */ + +:-moz-native-anonymous .accessible-bounds { + opacity: 0.6; + fill: #6a5acd; +}
--- a/devtools/server/actors/highlighters.js +++ b/devtools/server/actors/highlighters.js @@ -433,52 +433,54 @@ exports.HighlighterActor = protocol.Acto * type name and allows to show/hide it. */ exports.CustomHighlighterActor = protocol.ActorClassWithSpec(customHighlighterSpec, { /** * Create a highlighter instance given its typename * The typename must be one of HIGHLIGHTER_CLASSES and the class must * implement constructor(tabActor), show(node), hide(), destroy() */ - initialize: function (inspector, typeName) { + initialize: function (parent, typeName) { protocol.Actor.prototype.initialize.call(this, null); - this._inspector = inspector; + this._parent = parent; let modulePath = highlighterTypes.get(typeName); if (!modulePath) { let list = [...highlighterTypes.keys()]; throw new Error(`${typeName} isn't a valid highlighter class (${list})`); } - // The assumption is that all custom highlighters need the canvasframe - // container to append their elements, so if this is a XUL window, bail out. - if (!isXUL(this._inspector.tabActor.window)) { + let constructor = require("./highlighters/" + modulePath)[typeName]; + // The assumption is that custom highlighters either need the canvasframe + // container to append their elements and thus a non-XUL window or they have + // to define a static XULSupported flag that indicates that the highlighter + // supports XUL windows. Otherwise, bail out. + if (!isXUL(this._parent.tabActor.window) || constructor.XULSupported) { this._highlighterEnv = new HighlighterEnvironment(); - this._highlighterEnv.initFromTabActor(inspector.tabActor); - let constructor = require("./highlighters/" + modulePath)[typeName]; + this._highlighterEnv.initFromTabActor(parent.tabActor); this._highlighter = new constructor(this._highlighterEnv); if (this._highlighter.on) { this._highlighter.on("highlighter-event", this._onHighlighterEvent.bind(this)); } } else { throw new Error("Custom " + typeName + "highlighter cannot be created in a XUL window"); } }, get conn() { - return this._inspector && this._inspector.conn; + return this._parent && this._parent.conn; }, destroy: function () { protocol.Actor.prototype.destroy.call(this); this.finalize(); - this._inspector = null; + this._parent = null; }, release: function () {}, /** * Show the highlighter. * This calls through to the highlighter instance's |show(node, options)| * method.
new file mode 100644 --- /dev/null +++ b/devtools/server/actors/highlighters/accessible.js @@ -0,0 +1,270 @@ +/* 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"; + +const { AutoRefreshHighlighter } = require("./auto-refresh"); +const { getBounds } = require("./utils/accessibility"); + +const { + CanvasFrameAnonymousContentHelper, + createNode, + createSVGNode +} = require("./utils/markup"); + +const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils"); + +/** + * The AccessibleHighlighter draws the bounds of an accessible object. + * + * Usage example: + * + * let h = new AccessibleHighlighter(env); + * h.show(node, { x, y, w, h, [duration] }); + * h.hide(); + * h.destroy(); + * + * Available options: + * - {Number} x + * x coordinate of the top left corner of the accessible object + * - {Number} y + * y coordinate of the top left corner of the accessible object + * - {Number} w + * width of the the accessible object + * - {Number} h + * height of the the accessible object + * - {Number} duration + * Duration of time that the highlighter should be shown. + * + * Structure: + * <div class="highlighter-container"> + * <div class="accessible-root"> + * <svg class="accessible-elements" hidden="true"> + * <path class="accessible-bounds" points="..." /> + * </svg> + * </div> + * </div> + */ +class AccessibleHighlighter extends AutoRefreshHighlighter { + constructor(highlighterEnv) { + super(highlighterEnv); + + this.ID_CLASS_PREFIX = "accessible-"; + + this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv, + this._buildMarkup.bind(this)); + + this.onPageHide = this.onPageHide.bind(this); + this.onWillNavigate = this.onWillNavigate.bind(this); + + this.highlighterEnv.on("will-navigate", this.onWillNavigate); + + this.pageListenerTarget = highlighterEnv.pageListenerTarget; + this.pageListenerTarget.addEventListener("pagehide", this.onPageHide); + } + + /** + * Build highlighter markup. + * + * @return {Object} Container element for the highlighter markup. + */ + _buildMarkup() { + let container = createNode(this.win, { + attributes: { + "class": "highlighter-container", + "role": "presentation" + } + }); + + let root = createNode(this.win, { + parent: container, + attributes: { + "id": "root", + "class": "root", + "role": "presentation" + }, + prefix: this.ID_CLASS_PREFIX + }); + + // Build the SVG element. + let svg = createSVGNode(this.win, { + nodeType: "svg", + parent: root, + attributes: { + "id": "elements", + "width": "100%", + "height": "100%", + "hidden": "true", + "role": "presentation" + }, + prefix: this.ID_CLASS_PREFIX + }); + + createSVGNode(this.win, { + nodeType: "path", + parent: svg, + attributes: { + "class": "bounds", + "id": "bounds", + "role": "presentation" + }, + prefix: this.ID_CLASS_PREFIX + }); + + return container; + } + + /** + * Destroy the nodes. Remove listeners. + */ + destroy() { + if (this._highlightTimer) { + clearTimeout(this._highlightTimer); + this._highlightTimer = null; + } + + this.highlighterEnv.off("will-navigate", this.onWillNavigate); + this.pageListenerTarget.removeEventListener("pagehide", this.onPageHide); + this.pageListenerTarget = null; + + this.markup.destroy(); + AutoRefreshHighlighter.prototype.destroy.call(this); + } + + /** + * Find an element in highlighter markup. + * + * @param {String} id + * Highlighter markup elemet id attribute. + * @return {DOMNode} Element in the highlighter markup. + */ + getElement(id) { + return this.markup.getElement(this.ID_CLASS_PREFIX + id); + } + + /** + * Show the highlighter on a given accessible. + * + * @return {Boolean} True if accessible is highlighted, false otherwise. + */ + _show() { + if (this._highlightTimer) { + clearTimeout(this._highlightTimer); + this._highlightTimer = null; + } + + let { duration } = this.options; + let shown = this._update(); + if (shown && duration) { + this._highlightTimer = setTimeout(() => { + this.hide(); + }, duration); + } + return shown; + } + + /** + * Update and show accessible bounds for a current accessible. + * + * @return {Boolean} True if accessible is highlighted, false otherwise. + */ + _update() { + let shown = false; + setIgnoreLayoutChanges(true); + + if (this._updateAccessibleBounds()) { + this._showAccessibleBounds(); + shown = true; + } else { + // Nothing to highlight (0px rectangle like a <script> tag for instance) + this.hide(); + } + + setIgnoreLayoutChanges(false, + this.highlighterEnv.window.document.documentElement); + + return shown; + } + + /** + * Hide the highlighter. + */ + _hide() { + setIgnoreLayoutChanges(true); + this._hideAccessibleBounds(); + setIgnoreLayoutChanges(false, + this.highlighterEnv.window.document.documentElement); + } + + /** + * Hide the accessible bounds container. + */ + _hideAccessibleBounds() { + this.getElement("elements").setAttribute("hidden", "true"); + } + + /** + * Showthe accessible bounds container. + */ + _showAccessibleBounds() { + this.getElement("elements").removeAttribute("hidden"); + } + + /** + * Get current accessible bounds. + * + * @return {Object|null} Returns, if available, positioning and bounds + * information for the accessible object. + */ + get _bounds() { + return getBounds(this.win, this.options); + } + + /** + * Update accessible bounds for a current accessible. Re-draw highlighter + * markup. + * + * @return {Boolean} True if accessible is highlighted, false otherwise. + */ + _updateAccessibleBounds() { + let bounds = this._bounds; + if (!bounds) { + this._hideAccessibleBounds(); + return false; + } + + let boundsEl = this.getElement("bounds"); + let { left, right, top, bottom } = bounds; + let path = `M${left},${top} L${right},${top} L${right},${bottom} L${left},${bottom}`; + boundsEl.setAttribute("d", path); + + // Un-zoom the root wrapper if the page was zoomed. + let rootId = this.ID_CLASS_PREFIX + "elements"; + this.markup.scaleRootElement(this.currentNode, rootId); + + return true; + } + + /** + * Hide highlighter on page hide. + */ + onPageHide({ target }) { + // If a pagehide event is triggered for current window's highlighter, hide + // the highlighter. + if (target.defaultView === this.win) { + this.hide(); + } + } + + /** + * Hide highlighter on navigation. + */ + onWillNavigate({ isTopLevel }) { + if (isTopLevel) { + this.hide(); + } + } +} + +exports.AccessibleHighlighter = AccessibleHighlighter;
--- a/devtools/server/actors/highlighters/moz.build +++ b/devtools/server/actors/highlighters/moz.build @@ -4,22 +4,24 @@ # 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/. DIRS += [ 'utils', ] DevToolsModules( + 'accessible.js', 'auto-refresh.js', 'box-model.js', 'css-grid.js', 'css-transform.js', 'eye-dropper.js', 'flexbox.js', 'geometry-editor.js', 'measuring-tool.js', 'paused-debugger.js', 'rulers.js', 'selector.js', 'shapes.js', - 'simple-outline.js' + 'simple-outline.js', + 'xul-accessible.js' )
new file mode 100644 --- /dev/null +++ b/devtools/server/actors/highlighters/utils/accessibility.js @@ -0,0 +1,49 @@ +/* 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"; + +const { getCurrentZoom } = require("devtools/shared/layout/utils"); + +/** + * A helper function that calculate accessible object bounds and positioning to + * be used for highlighting. + * + * @param {Object} win + * window that contains accessible object. + * @param {Object} options + * Object used for passing options: + * - {Number} x + * x coordinate of the top left corner of the accessible object + * - {Number} y + * y coordinate of the top left corner of the accessible object + * - {Number} w + * width of the the accessible object + * - {Number} h + * height of the the accessible object + * @return {Object|null} Returns, if available, positioning and bounds information for + * the accessible object. + */ +function getBounds(win, { x, y, w, h }) { + let { devicePixelRatio, mozInnerScreenX, mozInnerScreenY, scrollX, scrollY } = win; + let zoom = getCurrentZoom(win); + let left = x, right = x + w, top = y, bottom = y + h; + + left -= (mozInnerScreenX - scrollX) * devicePixelRatio; + right -= (mozInnerScreenX - scrollX) * devicePixelRatio; + top -= (mozInnerScreenY - scrollY) * devicePixelRatio; + bottom -= (mozInnerScreenY - scrollY) * devicePixelRatio; + + left *= zoom / devicePixelRatio; + right *= zoom / devicePixelRatio; + top *= zoom / devicePixelRatio; + bottom *= zoom / devicePixelRatio; + + let width = right - left; + let height = bottom - top; + + return { left, right, top, bottom, width, height }; +} + +exports.getBounds = getBounds;
--- a/devtools/server/actors/highlighters/utils/moz.build +++ b/devtools/server/actors/highlighters/utils/moz.build @@ -1,10 +1,11 @@ # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'accessibility.js', 'canvas.js', 'markup.js' )
new file mode 100644 --- /dev/null +++ b/devtools/server/actors/highlighters/xul-accessible.js @@ -0,0 +1,221 @@ +/* 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"; + +const { getBounds } = require("./utils/accessibility"); +const { createNode, isNodeValid } = require("./utils/markup"); +const { loadSheet } = require("devtools/shared/layout/utils"); + +/** + * Stylesheet used for highlighter styling of accessible objects in chrome. It + * is consistent with the styling of an in-content accessible highlighter. + */ +const ACCESSIBLE_BOUNDS_SHEET = "data:text/css;charset=utf-8," + encodeURIComponent(` + .accessible-bounds { + position: fixed; + pointer-events: none; + z-index: 10; + display: block; + background-color: #6a5acd!important; + opacity: 0.6; + }`); + +/** + * The XULWindowAccessibleHighlighter is a class that has the same API as the + * AccessibleHighlighter, and by extension other highlighters that implement + * auto-refresh highlighter, but instead of drawing in canvas frame anonymous + * content (that is not available for chrome accessible highlighting) it adds a + * transparrent inactionable element with the same position and bounds as the + * accessible object highlighted. Unlike SimpleOutlineHighlighter, we can't use + * element (that corresponds to accessible object) itself because the accessible + * position and bounds are calculated differently. + * + * It is used when canvasframe-based AccessibleHighlighter can't be used. This + * is the case for XUL windows. + */ +class XULWindowAccessibleHighlighter { + constructor(highlighterEnv) { + this.highlighterEnv = highlighterEnv; + this.win = highlighterEnv.window; + } + + /** + * Static getter that indicates that XULWindowAccessibleHighlighter supports + * highlighting in XUL windows. + */ + static get XULSupported() { + return true; + } + + /** + * Build highlighter markup. + */ + _buildMarkup() { + let doc = this.win.document; + loadSheet(doc.ownerGlobal, ACCESSIBLE_BOUNDS_SHEET); + + this.container = createNode(this.win, { + parent: doc.body || doc.documentElement, + attributes: { + "class": "highlighter-container", + "role": "presentation" + } + }); + + this.bounds = createNode(this.win, { + parent: this.container, + attributes: { + "class": "accessible-bounds", + "role": "presentation" + } + }); + } + + /** + * Get current accessible bounds. + * + * @return {Object|null} Returns, if available, positioning and bounds + * information for the accessible object. + */ + get _bounds() { + return getBounds(this.win, this.options); + } + + /** + * Show the highlighter on a given accessible. + * + * @param {DOMNode} node + * A dom node that corresponds to the accessible object. + * @param {Object} options + * Object used for passing options. Available options: + * - {Number} x + * x coordinate of the top left corner of the accessible object + * - {Number} y + * y coordinate of the top left corner of the accessible object + * - {Number} w + * width of the the accessible object + * - {Number} h + * height of the the accessible object + * - duration {Number} + * Duration of time that the highlighter should be shown. + * @return {Boolean} True if accessible is highlighted, false otherwise. + */ + show(node, options = {}) { + let isSameNode = node === this.currentNode; + let hasBounds = options && typeof options.x == "number" && + typeof options.y == "number" && + typeof options.w == "number" && + typeof options.h == "number"; + if (!hasBounds || !isNodeValid(node) || isSameNode) { + return false; + } + + this.options = options; + this.currentNode = node; + + return this._show(); + } + + /** + * Internal show method that updates bounds and tracks duration based + * highlighting. + * + * @return {Boolean} True if accessible is highlighted, false otherwise. + */ + _show() { + if (this._highlightTimer) { + clearTimeout(this._highlightTimer); + this._highlightTimer = null; + } + + let shown = this._update(); + let { duration } = this.options; + if (shown && duration) { + this._highlightTimer = setTimeout(() => { + this._hideAccessibleBounds(); + }, duration); + } + return shown; + } + + /** + * Update accessible bounds for a current accessible. Re-draw highlighter + * markup. + * + * @return {Boolean} True if accessible is highlighted, false otherwise. + */ + _update() { + this._hideAccessibleBounds(); + let bounds = this._bounds; + if (!bounds) { + return false; + } + + let boundsEl = this.bounds; + if (!boundsEl) { + this._buildMarkup(); + boundsEl = this.bounds; + } + + let { left, top, width, height } = bounds; + boundsEl.style.top = `${top}px`; + boundsEl.style.left = `${left}px`; + boundsEl.style.width = `${width}px`; + boundsEl.style.height = `${height}px`; + this._showAccessibleBounds(); + + return true; + } + + /** + * Hide the highlighter + */ + hide() { + if (!this.currentNode || !this.highlighterEnv.window) { + return; + } + + this._hideAccessibleBounds(); + this.currentNode = null; + this.options = null; + } + + /** + * Show accessible bounds highlighter. + */ + _showAccessibleBounds() { + if (this.container) { + this.container.removeAttribute("hidden"); + } + } + + /** + * Hide accessible bounds highlighter. + */ + _hideAccessibleBounds() { + if (this.container) { + this.container.setAttribute("hidden", "true"); + } + } + + /** + * Hide accessible highlighter, clean up and remove the markup. + */ + destroy() { + if (this._highlightTimer) { + clearTimeout(this._highlightTimer); + this._highlightTimer = null; + } + + this.hide(); + if (this.container) { + this.container.remove(); + } + + this.win = null; + } +} + +exports.XULWindowAccessibleHighlighter = XULWindowAccessibleHighlighter;
--- a/dom/base/DocumentOrShadowRoot.cpp +++ b/dom/base/DocumentOrShadowRoot.cpp @@ -141,10 +141,30 @@ DocumentOrShadowRoot::GetPointerLockElem } nsIContent* retargetedPointerLockedElement = Retarget(pointerLockedElement); return retargetedPointerLockedElement && retargetedPointerLockedElement->IsElement() ? retargetedPointerLockedElement->AsElement() : nullptr; } +Element* +DocumentOrShadowRoot::GetFullscreenElement() +{ + if (!AsNode().IsInComposedDoc()) { + return nullptr; + } + + Element* element = AsNode().OwnerDoc()->FullScreenStackTop(); + NS_ASSERTION(!element || + element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), + "Fullscreen element should have fullscreen styles applied"); + + nsIContent* retargeted = Retarget(element); + if (retargeted && retargeted->IsElement()) { + return retargeted->AsElement(); + } + + return nullptr; +} + } }
--- a/dom/base/DocumentOrShadowRoot.h +++ b/dom/base/DocumentOrShadowRoot.h @@ -109,16 +109,17 @@ public: mozilla::ErrorResult&); already_AddRefed<nsContentList> GetElementsByClassName(const nsAString& aClasses); ~DocumentOrShadowRoot() = default; Element* GetPointerLockElement(); + Element* GetFullscreenElement(); protected: nsIContent* Retarget(nsIContent* aContent) const; /** * If focused element's subtree root is this document or shadow root, return * focused element, otherwise, get the shadow host recursively until the * shadow host's subtree root is this document or shadow root. */
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -8812,17 +8812,17 @@ nsDocument::OnPageHide(bool aPersisted, mVisible = false; UpdateVisibilityState(); EnumerateExternalResources(NotifyPageHide, &aPersisted); EnumerateActivityObservers(NotifyActivityChanged, nullptr); ClearPendingFullscreenRequests(this); - if (GetFullscreenElement()) { + if (FullScreenStackTop()) { // If this document was fullscreen, we should exit fullscreen in this // doctree branch. This ensures that if the user navigates while in // fullscreen mode we don't leave its still visible ancestor documents // in fullscreen mode. So exit fullscreen in the document's fullscreen // root document, as this will exit fullscreen in all the root's // descendant documents. Note that documents are removed from the // doctree by the time OnPageHide() is called, so we must store a // reference to the root (in nsDocument::mFullscreenRoot) since we can't @@ -10614,17 +10614,17 @@ nsIDocument::AsyncExitFullscreen(nsIDocu } else { NS_DispatchToCurrentThread(exit.forget()); } } static bool CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData) { - if (aDoc->GetFullscreenElement()) { + if (aDoc->FullScreenStackTop()) { uint32_t* count = static_cast<uint32_t*>(aData); (*count)++; } return true; } static uint32_t CountFullscreenSubDocuments(nsIDocument* aDoc) @@ -10634,30 +10634,30 @@ CountFullscreenSubDocuments(nsIDocument* return count; } bool nsDocument::IsFullscreenLeaf() { // A fullscreen leaf document is fullscreen, and has no fullscreen // subdocuments. - if (!GetFullscreenElement()) { + if (!FullScreenStackTop()) { return false; } return CountFullscreenSubDocuments(this) == 0; } static bool ResetFullScreen(nsIDocument* aDocument, void* aData) { - if (aDocument->GetFullscreenElement()) { + if (aDocument->FullScreenStackTop()) { NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1, "Should have at most 1 fullscreen subdocument."); static_cast<nsDocument*>(aDocument)->CleanupFullscreenState(); - NS_ASSERTION(!aDocument->GetFullscreenElement(), + NS_ASSERTION(!aDocument->FullScreenStackTop(), "Should reset full-screen"); auto changed = reinterpret_cast<nsCOMArray<nsIDocument>*>(aData); changed->AppendElement(aDocument); aDocument->EnumerateSubDocuments(ResetFullScreen, aData); } return true; } @@ -10698,17 +10698,17 @@ private: nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) { MOZ_ASSERT(aMaybeNotARootDoc); // Unlock the pointer UnlockPointer(); nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot(); - if (!root || !root->GetFullscreenElement()) { + if (!root || !root->FullScreenStackTop()) { // If a document was detached before exiting from fullscreen, it is // possible that the root had left fullscreen state. In this case, // we would not get anything from the ResetFullScreen() call. Root's // not being a fullscreen doc also means the widget should have // exited fullscreen state. It means even if we do not return here, // we would actually do nothing below except crashing ourselves via // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent // document. @@ -10727,34 +10727,34 @@ nsIDocument::ExitFullscreenInDocTree(nsI // Dispatch "fullscreenchange" events. Note this loop is in reverse // order so that the events for the leaf document arrives before the root // document, as required by the spec. for (uint32_t i = 0; i < changed.Length(); ++i) { DispatchFullScreenChange(changed[changed.Length() - i - 1]); } - NS_ASSERTION(!root->GetFullscreenElement(), + NS_ASSERTION(!root->FullScreenStackTop(), "Fullscreen root should no longer be a fullscreen doc..."); // Move the top-level window out of fullscreen mode. FullscreenRoots::Remove(root); nsContentUtils::AddScriptRunner( new ExitFullscreenScriptRunnable(Move(changed))); } bool GetFullscreenLeaf(nsIDocument* aDoc, void* aData) { if (aDoc->IsFullscreenLeaf()) { nsIDocument** result = static_cast<nsIDocument**>(aData); *result = aDoc; return false; - } else if (aDoc->GetFullscreenElement()) { + } else if (aDoc->FullScreenStackTop()) { aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData); } return true; } static nsIDocument* GetFullscreenLeaf(nsIDocument* aDoc) { @@ -10763,30 +10763,30 @@ GetFullscreenLeaf(nsIDocument* aDoc) if (leaf) { return leaf; } // Otherwise we could be either in a non-fullscreen doc tree, or we're // below the fullscreen doc. Start the search from the root. nsIDocument* root = nsContentUtils::GetRootDocument(aDoc); // Check that the root is actually fullscreen so we don't waste time walking // around its descendants. - if (!root->GetFullscreenElement()) { + if (!root->FullScreenStackTop()) { return nullptr; } GetFullscreenLeaf(root, &leaf); return leaf; } void nsDocument::RestorePreviousFullScreenState() { - NS_ASSERTION(!GetFullscreenElement() || !FullscreenRoots::IsEmpty(), + NS_ASSERTION(!FullScreenStackTop() || !FullscreenRoots::IsEmpty(), "Should have at least 1 fullscreen root when fullscreen!"); - if (!GetFullscreenElement() || !GetWindow() || FullscreenRoots::IsEmpty()) { + if (!FullScreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) { return; } nsCOMPtr<nsIDocument> fullScreenDoc = GetFullscreenLeaf(this); AutoTArray<nsDocument*, 8> exitDocs; nsIDocument* doc = fullScreenDoc; // Collect all subdocuments. @@ -10955,17 +10955,17 @@ nsDocument::FullScreenStackPush(Element* { NS_ASSERTION(aElement, "Must pass non-null to FullScreenStackPush()"); Element* top = FullScreenStackTop(); if (top == aElement || !aElement) { return false; } EventStateManager::SetFullScreenState(aElement, true); mFullScreenStack.AppendElement(do_GetWeakReference(aElement)); - NS_ASSERTION(GetFullscreenElement() == aElement, "Should match"); + NS_ASSERTION(FullScreenStackTop() == aElement, "Should match"); UpdateViewportScrollbarOverrideForFullscreen(this); return true; } void nsDocument::FullScreenStackPop() { if (mFullScreenStack.IsEmpty()) { @@ -11003,17 +11003,17 @@ Element* nsDocument::FullScreenStackTop() { if (mFullScreenStack.IsEmpty()) { return nullptr; } uint32_t last = mFullScreenStack.Length() - 1; nsCOMPtr<Element> element(do_QueryReferent(mFullScreenStack[last])); NS_ASSERTION(element, "Should have full-screen element!"); - NS_ASSERTION(element->IsInUncomposedDoc(), "Full-screen element should be in doc"); + NS_ASSERTION(element->IsInComposedDoc(), "Full-screen element should be in doc"); NS_ASSERTION(element->OwnerDoc() == this, "Full-screen element should be in this doc"); return element; } /* virtual */ nsTArray<Element*> nsDocument::GetFullscreenStack() const { nsTArray<Element*> elements; @@ -11081,17 +11081,17 @@ nsresult nsDocument::RemoteFrameFullscre nsresult nsDocument::RemoteFrameFullscreenReverted() { RestorePreviousFullScreenState(); return NS_OK; } /* static */ bool -nsDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject) +nsIDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject) { MOZ_ASSERT(NS_IsMainThread()); return nsContentUtils::IsSystemCaller(aCx) || nsContentUtils::IsUnprefixedFullscreenApiEnabled(); } static bool HasFullScreenSubDocument(nsIDocument* aDoc) @@ -11128,20 +11128,20 @@ GetFullscreenError(nsIDocument* aDoc, bo } bool nsDocument::FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome) { NS_ASSERTION(aElement, "Must pass non-null element to nsDocument::RequestFullScreen"); - if (!aElement || aElement == GetFullscreenElement()) { + if (!aElement || aElement == FullScreenStackTop()) { return false; } - if (!aElement->IsInUncomposedDoc()) { + if (!aElement->IsInComposedDoc()) { DispatchFullscreenError("FullscreenDeniedNotInDocument"); return false; } if (aElement->OwnerDoc() != this) { DispatchFullscreenError("FullscreenDeniedMovedDocument"); return false; } if (!GetWindow()) { @@ -11155,18 +11155,21 @@ nsDocument::FullscreenElementReadyCheck( if (!IsVisible()) { DispatchFullscreenError("FullscreenDeniedHidden"); return false; } if (HasFullScreenSubDocument(this)) { DispatchFullscreenError("FullscreenDeniedSubDocFullScreen"); return false; } - if (GetFullscreenElement() && - !nsContentUtils::ContentIsDescendantOf(aElement, GetFullscreenElement())) { + //XXXsmaug Note, we don't follow the latest fullscreen spec here. + // This whole check could be probably removed. + if (FullScreenStackTop() && + !nsContentUtils::ContentIsHostIncludingDescendantOf(aElement, + FullScreenStackTop())) { // If this document is full-screen, only grant full-screen requests from // a descendant of the current full-screen element. DispatchFullscreenError("FullscreenDeniedNotDescendant"); return false; } if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) { DispatchFullscreenError("FullscreenDeniedNotFocusedTab"); return false; @@ -11507,26 +11510,16 @@ nsDocument::ApplyFullscreen(const Fullsc // order so that the events for the root document arrives before the leaf // document, as required by the spec. for (uint32_t i = 0; i < changed.Length(); ++i) { DispatchFullScreenChange(changed[changed.Length() - i - 1]); } return true; } -Element* -nsDocument::GetFullscreenElement() -{ - Element* element = FullScreenStackTop(); - NS_ASSERTION(!element || - element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), - "Fullscreen element should have fullscreen styles applied"); - return element; -} - bool nsDocument::FullscreenEnabled(CallerType aCallerType) { return !GetFullscreenError(this, aCallerType == CallerType::System); } uint16_t nsDocument::CurrentOrientationAngle() const
--- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -878,18 +878,16 @@ public: // Returns strong references to mBlockedTrackingNodes. (nsIDocument.h) // // This array contains nodes that have been blocked to prevent // user tracking. They most likely have had their nsIChannel // canceled by the URL classifier (Safebrowsing). // already_AddRefed<nsSimpleContentList> BlockedTrackingNodes() const; - static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject); - // Do the "fullscreen element ready check" from the fullscreen spec. // It returns true if the given element is allowed to go into fullscreen. bool FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome); // This is called asynchronously by nsIDocument::AsyncRequestFullScreen() // to move this document into full-screen mode if allowed. void RequestFullScreen(mozilla::UniquePtr<FullscreenRequest>&& aRequest); @@ -903,21 +901,20 @@ public: bool FullScreenStackPush(Element* aElement); // Remove the top element from the full-screen stack. Removes the full-screen // styles from the former top element, and applies them to the new top // element, if there is one. void FullScreenStackPop(); // Returns the top element from the full-screen stack. - Element* FullScreenStackTop(); + Element* FullScreenStackTop() override; // DOM-exposed fullscreen API bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) override; - Element* GetFullscreenElement() override; virtual bool AllowPaymentRequest() const override; virtual void SetAllowPaymentRequest(bool aIsAllowPaymentRequest) override; void RequestPointerLock(Element* aElement, mozilla::dom::CallerType aCallerType) override; bool SetPointerLock(Element* aElement, int aCursorStyle); static void UnlockPointer(nsIDocument* aDoc = nullptr);
--- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -2921,26 +2921,29 @@ public: } Element* GetCurrentScript(); void ReleaseCapture() const; virtual void MozSetImageElement(const nsAString& aImageElementId, Element* aElement) = 0; nsIURI* GetDocumentURIObject() const; // Not const because all the full-screen goop is not const virtual bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) = 0; - virtual Element* GetFullscreenElement() = 0; + virtual Element* FullScreenStackTop() = 0; bool Fullscreen() { return !!GetFullscreenElement(); } void ExitFullscreen(); void ExitPointerLock() { UnlockPointer(this); } + + static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject); + #ifdef MOZILLA_INTERNAL_API bool Hidden() const { return mVisibilityState != mozilla::dom::VisibilityState::Visible; } mozilla::dom::VisibilityState VisibilityState() const { return mVisibilityState;
--- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -6,17 +6,16 @@ #include "Fetch.h" #include "FetchConsumer.h" #include "FetchStream.h" #include "nsIDocument.h" #include "nsIGlobalObject.h" #include "nsIStreamLoader.h" -#include "nsIThreadRetargetableRequest.h" #include "nsCharSeparatedTokenizer.h" #include "nsDOMString.h" #include "nsJSUtils.h" #include "nsNetUtil.h" #include "nsReadableUtils.h" #include "nsStreamUtils.h" #include "nsStringStream.h"
--- a/dom/fetch/FetchConsumer.cpp +++ b/dom/fetch/FetchConsumer.cpp @@ -8,16 +8,17 @@ #include "FetchConsumer.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsIInputStreamPump.h" +#include "nsIThreadRetargetableRequest.h" #include "nsProxyRelease.h" namespace mozilla { namespace dom { namespace { template <class Derived> @@ -579,19 +580,17 @@ FetchBodyConsumer<Derived>::BeginConsume // stays alive for the lifetime of the FetchConsumer. mConsumeBodyPump = pump; // It is ok for retargeting to fail and reads to happen on the main thread. autoReject.DontFail(); // Try to retarget, otherwise fall back to main thread. nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump); - nsCOMPtr<nsIThreadRetargetableStreamListener> rl = - do_QueryInterface(listener); - if (rr && rl) { + if (rr) { nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); rv = rr->RetargetDeliveryTo(sts); if (NS_FAILED(rv)) { NS_WARNING("Retargeting failed"); } } }
--- a/dom/file/MutableBlobStorage.cpp +++ b/dom/file/MutableBlobStorage.cpp @@ -103,17 +103,16 @@ private: // the temporary file, if its File Descriptor has not been already closed. class WriteRunnable final : public Runnable { public: static WriteRunnable* CopyBuffer(MutableBlobStorage* aBlobStorage, const void* aData, uint32_t aLength) { - MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aBlobStorage); MOZ_ASSERT(aData); // We have to take a copy of this buffer. void* data = malloc(aLength); if (!data) { return nullptr; } @@ -157,17 +156,16 @@ public: private: WriteRunnable(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength) : Runnable("dom::WriteRunnable") , mBlobStorage(aBlobStorage) , mData(aData) , mLength(aLength) { - MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mBlobStorage); MOZ_ASSERT(aData); } ~WriteRunnable() { free(mData); } @@ -330,17 +328,18 @@ private: RefPtr<MutableBlobStorageCallback> mCallback; }; } // anonymous namespace MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType, nsIEventTarget* aEventTarget, uint32_t aMaxMemory) - : mData(nullptr) + : mMutex("MutableBlobStorage::mMutex") + , mData(nullptr) , mDataLen(0) , mDataBufferLen(0) , mStorageState(aType == eOnlyInMemory ? eKeepInMemory : eInMemory) , mFD(nullptr) , mErrorResult(NS_OK) , mEventTarget(aEventTarget) , mMaxMemory(aMaxMemory) { @@ -380,16 +379,18 @@ MutableBlobStorage::~MutableBlobStorage( void MutableBlobStorage::GetBlobWhenReady(nsISupports* aParent, const nsACString& aContentType, MutableBlobStorageCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aCallback); + MutexAutoLock lock(mMutex); + // GetBlob can be called just once. MOZ_ASSERT(mStorageState != eClosed); StorageState previousState = mStorageState; mStorageState = eClosed; if (previousState == eInTemporaryFile) { if (NS_FAILED(mErrorResult)) { MOZ_ASSERT(!mActor); @@ -444,28 +445,30 @@ MutableBlobStorage::GetBlobWhenReady(nsI if (NS_WARN_IF(NS_FAILED(error))) { return; } } nsresult MutableBlobStorage::Append(const void* aData, uint32_t aLength) { - MOZ_ASSERT(NS_IsMainThread()); + // This method can be called on any thread. + + MutexAutoLock lock(mMutex); MOZ_ASSERT(mStorageState != eClosed); NS_ENSURE_ARG_POINTER(aData); if (!aLength) { return NS_OK; } // If eInMemory is the current Storage state, we could maybe migrate to // a temporary file. - if (mStorageState == eInMemory && ShouldBeTemporaryStorage(aLength) && - !MaybeCreateTemporaryFile()) { + if (mStorageState == eInMemory && ShouldBeTemporaryStorage(lock, aLength) && + !MaybeCreateTemporaryFile(lock)) { return NS_ERROR_FAILURE; } // If we are already in the temporaryFile mode, we have to dispatch a // runnable. if (mStorageState == eInTemporaryFile) { // If a previous operation failed, let's return that error now. if (NS_FAILED(mErrorResult)) { @@ -486,28 +489,28 @@ MutableBlobStorage::Append(const void* a mDataLen += aLength; return NS_OK; } // By default, we store in memory. uint64_t offset = mDataLen; - if (!ExpandBufferSize(aLength)) { + if (!ExpandBufferSize(lock, aLength)) { return NS_ERROR_OUT_OF_MEMORY; } memcpy((char*)mData + offset, aData, aLength); return NS_OK; } bool -MutableBlobStorage::ExpandBufferSize(uint64_t aSize) +MutableBlobStorage::ExpandBufferSize(const MutexAutoLock& aProofOfLock, + uint64_t aSize) { - MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mStorageState < eInTemporaryFile); if (mDataBufferLen >= mDataLen + aSize) { mDataLen += aSize; return true; } // Start at 1 or we'll loop forever. @@ -528,60 +531,78 @@ MutableBlobStorage::ExpandBufferSize(uin mData = data; mDataBufferLen = bufferLen.value(); mDataLen += aSize; return true; } bool -MutableBlobStorage::ShouldBeTemporaryStorage(uint64_t aSize) const +MutableBlobStorage::ShouldBeTemporaryStorage(const MutexAutoLock& aProofOfLock, + uint64_t aSize) const { MOZ_ASSERT(mStorageState == eInMemory); CheckedUint32 bufferSize = mDataLen; bufferSize += aSize; if (!bufferSize.isValid()) { return false; } return bufferSize.value() >= mMaxMemory; } bool -MutableBlobStorage::MaybeCreateTemporaryFile() +MutableBlobStorage::MaybeCreateTemporaryFile(const MutexAutoLock& aProofOfLock) +{ + mStorageState = eWaitingForTemporaryFile; + + if (!NS_IsMainThread()) { + RefPtr<MutableBlobStorage> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "MutableBlobStorage::MaybeCreateTemporaryFile", + [self]() { self->MaybeCreateTemporaryFileOnMainThread(); }); + EventTarget()->Dispatch(r.forget(), NS_DISPATCH_SYNC); + return !!mActor; + } + + MaybeCreateTemporaryFileOnMainThread(); + return !!mActor; +} + +void +MutableBlobStorage::MaybeCreateTemporaryFileOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); - - mStorageState = eWaitingForTemporaryFile; + MOZ_ASSERT(!mActor); mozilla::ipc::PBackgroundChild* actorChild = mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); if (NS_WARN_IF(!actorChild)) { - return false; + return; } mActor = new TemporaryIPCBlobChild(this); actorChild->SendPTemporaryIPCBlobConstructor(mActor); // We need manually to increase the reference for this actor because the // IPC allocator method is not triggered. The Release() is called by IPDL // when the actor is deleted. mActor.get()->AddRef(); // The actor will call us when the FileDescriptor is received. - - return true; } void MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD) { MOZ_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mMutex); MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile || mStorageState == eClosed); MOZ_ASSERT_IF(mPendingCallback, mStorageState == eClosed); MOZ_ASSERT(mActor); MOZ_ASSERT(aFD); // If the object has been already closed and we don't need to execute a // callback, we need just to close the file descriptor in the correct thread. @@ -638,16 +659,18 @@ MutableBlobStorage::TemporaryFileCreated } } void MutableBlobStorage::AskForBlob(TemporaryIPCBlobChildCallback* aCallback, const nsACString& aContentType) { MOZ_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mMutex); MOZ_ASSERT(mStorageState == eClosed); MOZ_ASSERT(mFD); MOZ_ASSERT(mActor); MOZ_ASSERT(aCallback); // Let's pass the FileDescriptor to the parent actor in order to keep the file // locked on windows. mActor->AskForBlob(aCallback, aContentType, mFD); @@ -661,16 +684,18 @@ MutableBlobStorage::AskForBlob(Temporary mFD = nullptr; mActor = nullptr; } void MutableBlobStorage::ErrorPropagated(nsresult aRv) { MOZ_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mMutex); mErrorResult = aRv; if (mActor) { mActor->SendOperationFailed(); mActor = nullptr; } } @@ -690,33 +715,36 @@ MutableBlobStorage::DispatchToIOThread(a if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } size_t -MutableBlobStorage::SizeOfCurrentMemoryBuffer() const +MutableBlobStorage::SizeOfCurrentMemoryBuffer() { MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mMutex); return mStorageState < eInTemporaryFile ? mDataLen : 0; } PRFileDesc* -MutableBlobStorage::GetFD() const +MutableBlobStorage::GetFD() { MOZ_ASSERT(!NS_IsMainThread()); + MutexAutoLock lock(mMutex); return mFD; } void MutableBlobStorage::CloseFD() { MOZ_ASSERT(!NS_IsMainThread()); + MutexAutoLock lock(mMutex); MOZ_ASSERT(mFD); PR_Close(mFD); mFD = nullptr; } } // dom namespace } // mozilla namespace
--- a/dom/file/MutableBlobStorage.h +++ b/dom/file/MutableBlobStorage.h @@ -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/. */ #ifndef mozilla_dom_MutableBlobStorage_h #define mozilla_dom_MutableBlobStorage_h #include "mozilla/RefPtr.h" +#include "mozilla/Mutex.h" #include "prio.h" class nsIEventTarget; class nsIRunnable; namespace mozilla { class TaskQueue; @@ -30,17 +31,18 @@ class MutableBlobStorageCallback public: NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage, Blob* aBlob, nsresult aRv) = 0; }; -// This class is main-thread only. +// This class is must be created and used on main-thread, except for Append() +// that can be called on any thread. class MutableBlobStorage final { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MutableBlobStorage) enum MutableBlobStorageType { eOnlyInMemory, @@ -69,35 +71,41 @@ public: nsIEventTarget* EventTarget() { MOZ_ASSERT(mEventTarget); return mEventTarget; } // Returns the heap size in bytes of our internal buffers. // Note that this intentionally ignores the data in the temp file. - size_t SizeOfCurrentMemoryBuffer() const; + size_t SizeOfCurrentMemoryBuffer(); - PRFileDesc* GetFD() const; + PRFileDesc* GetFD(); void CloseFD(); private: ~MutableBlobStorage(); - bool ExpandBufferSize(uint64_t aSize); + bool ExpandBufferSize(const MutexAutoLock& aProofOfLock, + uint64_t aSize); - bool ShouldBeTemporaryStorage(uint64_t aSize) const; + bool ShouldBeTemporaryStorage(const MutexAutoLock& aProofOfLock, + uint64_t aSize) const; - bool MaybeCreateTemporaryFile(); + bool MaybeCreateTemporaryFile(const MutexAutoLock& aProofOfLock); + void MaybeCreateTemporaryFileOnMainThread(); MOZ_MUST_USE nsresult DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable); - // All these variables are touched on the main thread only. + Mutex mMutex; + + // All these variables are touched on the main thread only or in the + // retargeted thread when used by Append(). They are protected by mMutex. void* mData; uint64_t mDataLen; uint64_t mDataBufferLen; enum StorageState { eKeepInMemory, eInMemory,
--- a/dom/file/MutableBlobStreamListener.cpp +++ b/dom/file/MutableBlobStreamListener.cpp @@ -33,16 +33,17 @@ MutableBlobStreamListener::MutableBlobSt MutableBlobStreamListener::~MutableBlobStreamListener() { MOZ_ASSERT(NS_IsMainThread()); } NS_IMPL_ISUPPORTS(MutableBlobStreamListener, nsIStreamListener, + nsIThreadRetargetableStreamListener, nsIRequestObserver) NS_IMETHODIMP MutableBlobStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mStorage); MOZ_ASSERT(mEventTarget); @@ -74,39 +75,45 @@ MutableBlobStreamListener::OnStopRequest NS_IMETHODIMP MutableBlobStreamListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream, uint64_t aSourceOffset, uint32_t aCount) { - MOZ_ASSERT(NS_IsMainThread()); + // This method could be called on any thread. MOZ_ASSERT(mStorage); uint32_t countRead; return aStream->ReadSegments(WriteSegmentFun, this, aCount, &countRead); } nsresult MutableBlobStreamListener::WriteSegmentFun(nsIInputStream* aWriterStream, void* aClosure, const char* aFromSegment, uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) { - MOZ_ASSERT(NS_IsMainThread()); + // This method could be called on any thread. MutableBlobStreamListener* self = static_cast<MutableBlobStreamListener*>(aClosure); MOZ_ASSERT(self->mStorage); nsresult rv = self->mStorage->Append(aFromSegment, aCount); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aWriteCount = aCount; return NS_OK; } +NS_IMETHODIMP +MutableBlobStreamListener::CheckListenerChain() +{ + return NS_OK; +} + } // namespace net } // namespace mozilla
--- a/dom/file/MutableBlobStreamListener.h +++ b/dom/file/MutableBlobStreamListener.h @@ -3,29 +3,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/. */ #ifndef mozilla_dom_MutableBlobStreamListener_h #define mozilla_dom_MutableBlobStreamListener_h #include "nsIStreamListener.h" +#include "nsIThreadRetargetableStreamListener.h" #include "mozilla/dom/MutableBlobStorage.h" class nsIEventTarget; namespace mozilla { namespace dom { -// This class is main-thread only. class MutableBlobStreamListener final : public nsIStreamListener + , public nsIThreadRetargetableStreamListener { public: - NS_DECL_ISUPPORTS + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER NS_DECL_NSIREQUESTOBSERVER MutableBlobStreamListener(MutableBlobStorage::MutableBlobStorageType aType, nsISupports* aParent, const nsACString& aContentType, MutableBlobStorageCallback* aCallback, nsIEventTarget* aEventTarget = nullptr);
new file mode 100644 --- /dev/null +++ b/dom/html/test/file_fullscreen-shadowdom.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> + <!-- + https://bugzilla.mozilla.org/show_bug.cgi?id=1430305 + Bug 1430305 - Implement ShadowRoot.fullscreenElement + --> + <head> + <title>Bug 1430305</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"> + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430305"> + Mozilla Bug 1430305</a> + + <div id="host"></div> + + <pre id="test"> + <script type="application/javascript"> + + function begin() { + var host = document.getElementById("host"); + var shadowRoot = host.attachShadow({mode: "open"}); + shadowRoot.innerHTML = "<div>test</div>"; + var elem = shadowRoot.firstChild; + var gotFullscreenEvent = false; + + document.addEventListener("fullscreenchange", function (e) { + if (document.fullscreenElement === host) { + is(shadowRoot.fullscreenElement, elem, + "Expected element entered fullsceen"); + gotFullscreenEvent = true; + document.exitFullscreen(); + } else { + opener.ok(gotFullscreenEvent, "Entered fullscreen as expected"); + is(shadowRoot.fullscreenElement, null, + "Shouldn't have fullscreenElement anymore."); + is(document.fullscreenElement, null, + "Shouldn't have fullscreenElement anymore."); + opener.nextTest(); + } + }); + elem.requestFullscreen(); + } + </script> + </pre> + </body> +</html>
--- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -454,16 +454,17 @@ support-files = file_fullscreen-multiple.html file_fullscreen-navigation.html file_fullscreen-nested.html file_fullscreen-prefixed.html file_fullscreen-plugins.html file_fullscreen-rollback.html file_fullscreen-scrollbar.html file_fullscreen-selector.html + file_fullscreen-shadowdom.html file_fullscreen-svg-element.html file_fullscreen-table.html file_fullscreen-top-layer.html file_fullscreen-unprefix-disabled-inner.html file_fullscreen-unprefix-disabled.html file_fullscreen-utils.js [test_fullscreen-api-race.html] tags = fullscreen
--- a/dom/html/test/test_fullscreen-api.html +++ b/dom/html/test/test_fullscreen-api.html @@ -33,16 +33,17 @@ var gTestWindows = [ "file_fullscreen-denied.html", "file_fullscreen-api.html", "file_fullscreen-plugins.html", "file_fullscreen-hidden.html", "file_fullscreen-svg-element.html", "file_fullscreen-navigation.html", "file_fullscreen-scrollbar.html", "file_fullscreen-selector.html", + "file_fullscreen-shadowdom.html", "file_fullscreen-top-layer.html", "file_fullscreen-backdrop.html", "file_fullscreen-nested.html", "file_fullscreen-prefixed.html", "file_fullscreen-unprefix-disabled.html", "file_fullscreen-lenient-setters.html", "file_fullscreen-table.html", ]; @@ -141,16 +142,17 @@ is(window.fullScreen, false, "Shouldn't // to write addLoadEvent(function() { SpecialPowers.pushPrefEnv({ "set": [ ["full-screen-api.enabled", true], ["full-screen-api.unprefix.enabled", true], ["full-screen-api.allow-trusted-requests-only", false], ["full-screen-api.transition-duration.enter", "0 0"], - ["full-screen-api.transition-duration.leave", "0 0"] + ["full-screen-api.transition-duration.leave", "0 0"], + ["dom.webcomponents.shadowdom.enabled", true] ]}, nextTest); }); SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/dom/media/systemservices/CamerasChild.cpp +++ b/dom/media/systemservices/CamerasChild.cpp @@ -29,17 +29,19 @@ mozilla::LazyLogModule gCamerasChildLog( namespace mozilla { namespace camera { CamerasSingleton::CamerasSingleton() : mCamerasMutex("CamerasSingleton::mCamerasMutex"), mCameras(nullptr), mCamerasChildThread(nullptr), - mFakeDeviceChangeEventThread(nullptr) { + mFakeDeviceChangeEventThread(nullptr), + mInShutdown(false) +{ LOG(("CamerasSingleton: %p", this)); } CamerasSingleton::~CamerasSingleton() { LOG(("~CamerasSingleton: %p", this)); } class FakeOnDeviceChangeEventRunnable : public Runnable @@ -285,36 +287,38 @@ CamerasChild::DispatchToParent(nsIRunnab int CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine, const char* deviceUniqueIdUTF8) { LOG((__PRETTY_FUNCTION__)); LOG(("NumberOfCapabilities for %s", deviceUniqueIdUTF8)); nsCString unique_id(deviceUniqueIdUTF8); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString>( "camera::PCamerasChild::SendNumberOfCapabilities", - this, + self, &CamerasChild::SendNumberOfCapabilities, aCapEngine, unique_id); LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture capability count: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } int CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine) { LOG((__PRETTY_FUNCTION__)); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine>( "camera::PCamerasChild::SendNumberOfCaptureDevices", - this, + self, &CamerasChild::SendNumberOfCaptureDevices, aCapEngine); LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture Devices: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } mozilla::ipc::IPCResult @@ -328,39 +332,41 @@ CamerasChild::RecvReplyNumberOfCaptureDe monitor.Notify(); return IPC_OK(); } int CamerasChild::EnsureInitialized(CaptureEngine aCapEngine) { LOG((__PRETTY_FUNCTION__)); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine>( "camera::PCamerasChild::SendEnsureInitialized", - this, + self, &CamerasChild::SendEnsureInitialized, aCapEngine); LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture Devices: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } int CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int capability_number, webrtc::VideoCaptureCapability& capability) { LOG(("GetCaptureCapability: %s %d", unique_idUTF8, capability_number)); nsCString unique_id(unique_idUTF8); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString, unsigned int>( "camera::PCamerasChild::SendGetCaptureCapability", - this, + self, &CamerasChild::SendGetCaptureCapability, aCapEngine, unique_id, capability_number); LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); if (dispatcher.Success()) { capability = mReplyCapability; } @@ -389,20 +395,21 @@ int CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine, unsigned int list_number, char* device_nameUTF8, const unsigned int device_nameUTF8Length, char* unique_idUTF8, const unsigned int unique_idUTF8Length, bool* scary) { LOG((__PRETTY_FUNCTION__)); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine, unsigned int>( "camera::PCamerasChild::SendGetCaptureDevice", - this, + self, &CamerasChild::SendGetCaptureDevice, aCapEngine, list_number); LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); if (dispatcher.Success()) { base::strlcpy(device_nameUTF8, mReplyDeviceName.get(), device_nameUTF8Length); base::strlcpy(unique_idUTF8, mReplyDeviceID.get(), unique_idUTF8Length); if (scary) { @@ -433,22 +440,23 @@ int CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int unique_idUTF8Length, int& aStreamId, const mozilla::ipc::PrincipalInfo& aPrincipalInfo) { LOG((__PRETTY_FUNCTION__)); nsCString unique_id(unique_idUTF8); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString, const mozilla::ipc::PrincipalInfo&>( "camera::PCamerasChild::SendAllocateCaptureDevice", - this, + self, &CamerasChild::SendAllocateCaptureDevice, aCapEngine, unique_id, aPrincipalInfo); LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); if (dispatcher.Success()) { LOG(("Capture Device allocated: %d", mReplyInteger)); aStreamId = mReplyInteger; @@ -469,20 +477,21 @@ CamerasChild::RecvReplyAllocateCaptureDe return IPC_OK(); } int CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id) { LOG((__PRETTY_FUNCTION__)); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine, int>( "camera::PCamerasChild::SendReleaseCaptureDevice", - this, + self, &CamerasChild::SendReleaseCaptureDevice, aCapEngine, capture_id); LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); return dispatcher.ReturnValue(); } void @@ -520,50 +529,55 @@ CamerasChild::StartCapture(CaptureEngine AddCallback(aCapEngine, capture_id, cb); VideoCaptureCapability capCap(webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, webrtcCaps.expectedCaptureDelay, webrtcCaps.rawType, webrtcCaps.codecType, webrtcCaps.interlaced); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla:: NewNonOwningRunnableMethod<CaptureEngine, int, VideoCaptureCapability>( "camera::PCamerasChild::SendStartCapture", - this, + self, &CamerasChild::SendStartCapture, aCapEngine, capture_id, capCap); LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); return dispatcher.ReturnValue(); } int CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id) { LOG((__PRETTY_FUNCTION__)); + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> runnable = mozilla::NewNonOwningRunnableMethod<CaptureEngine, int>( "camera::PCamerasChild::SendStopCapture", - this, + self, &CamerasChild::SendStopCapture, aCapEngine, capture_id); LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); if (dispatcher.Success()) { RemoveCallback(aCapEngine, capture_id); } return dispatcher.ReturnValue(); } void Shutdown(void) { OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); + + CamerasSingleton::StartShutdown(); + CamerasChild* child = CamerasSingleton::Child(); if (!child) { // We don't want to cause everything to get fired up if we're // really already shut down. LOG(("Shutdown when already shut down")); return; } child->ShutdownAll(); @@ -605,18 +619,19 @@ CamerasChild::ShutdownParent() mIPCIsAlive = false; monitor.NotifyAll(); } if (CamerasSingleton::Thread()) { LOG(("Dispatching actor deletion")); // Delete the parent actor. // CamerasChild (this) will remain alive and is only deleted by the // IPC layer when SendAllDone returns. + RefPtr<CamerasChild> self(this); nsCOMPtr<nsIRunnable> deleteRunnable = mozilla::NewNonOwningRunnableMethod( - "camera::PCamerasChild::SendAllDone", this, &CamerasChild::SendAllDone); + "camera::PCamerasChild::SendAllDone", self, &CamerasChild::SendAllDone); CamerasSingleton::Thread()->Dispatch(deleteRunnable, NS_DISPATCH_NORMAL); } else { LOG(("ShutdownParent called without PBackground thread")); } } void CamerasChild::ShutdownChild() @@ -713,17 +728,17 @@ CamerasChild::CamerasChild() MOZ_COUNT_CTOR(CamerasChild); } CamerasChild::~CamerasChild() { LOG(("~CamerasChild: %p", this)); - { + if (!CamerasSingleton::InShutdown()) { OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); // In normal circumstances we've already shut down and the // following does nothing. But on fatal IPC errors we will // get destructed immediately, and should not try to reach // the parent. ShutdownChild(); }
--- a/dom/media/systemservices/CamerasChild.h +++ b/dom/media/systemservices/CamerasChild.h @@ -87,16 +87,24 @@ public: return gTheInstance.get()->mCamerasChildThread; } static nsCOMPtr<nsIThread>& FakeDeviceChangeEventThread() { Mutex().AssertCurrentThreadOwns(); return gTheInstance.get()->mFakeDeviceChangeEventThread; } + static bool InShutdown() { + return gTheInstance.get()->mInShutdown; + } + + static void StartShutdown() { + gTheInstance.get()->mInShutdown = true; + } + private: static Singleton<CamerasSingleton> gTheInstance; // Reinitializing CamerasChild will change the pointers below. // We don't want this to happen in the middle of preparing IPC. // We will be alive on destruction, so this needs to be off the books. mozilla::OffTheBooksMutex mCamerasMutex; @@ -104,16 +112,17 @@ private: // It will set and clear this pointer as appropriate in setup/teardown. // We'd normally make this a WeakPtr but unfortunately the IPC code already // uses the WeakPtr mixin in a protected base class of CamerasChild, and in // any case the object becomes unusable as soon as IPC is tearing down, which // will be before actual destruction. CamerasChild* mCameras; nsCOMPtr<nsIThread> mCamerasChildThread; nsCOMPtr<nsIThread> mFakeDeviceChangeEventThread; + Atomic<bool> mInShutdown; }; // Get a pointer to a CamerasChild object we can use to do IPC with. // This does everything needed to set up, including starting the IPC // channel with PBackground, blocking until thats done, and starting the // thread to do IPC on. This will fail if we're in shutdown. On success // it will set up the CamerasSingleton. CamerasChild* GetCamerasChild(); @@ -143,17 +152,17 @@ class CamerasChild final : public PCamer ,public DeviceChangeCallback { friend class mozilla::ipc::BackgroundChildImpl; template <class T> friend class mozilla::camera::LockAndDispatch; public: // We are owned by the PBackground thread only. CamerasSingleton // takes a non-owning reference. - NS_INLINE_DECL_REFCOUNTING(CamerasChild) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CamerasChild) // IPC messages recevied, received on the PBackground thread // these are the actual callbacks with data mozilla::ipc::IPCResult RecvDeliverFrame(const CaptureEngine&, const int&, mozilla::ipc::Shmem&&, const VideoFrameProperties & prop) override; mozilla::ipc::IPCResult RecvDeviceChange() override;
--- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -237,20 +237,16 @@ partial interface Document { [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"] readonly attribute boolean fullscreen; [BinaryName="fullscreen"] readonly attribute boolean mozFullScreen; [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled", NeedsCallerType] readonly attribute boolean fullscreenEnabled; [BinaryName="fullscreenEnabled", NeedsCallerType] readonly attribute boolean mozFullScreenEnabled; - [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"] - readonly attribute Element? fullscreenElement; - [BinaryName="fullscreenElement"] - readonly attribute Element? mozFullScreenElement; [Func="nsDocument::IsUnprefixedFullscreenEnabled"] void exitFullscreen(); [BinaryName="exitFullscreen"] void mozCancelFullScreen(); // Events handlers [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
--- a/dom/webidl/DocumentOrShadowRoot.webidl +++ b/dom/webidl/DocumentOrShadowRoot.webidl @@ -18,11 +18,13 @@ interface DocumentOrShadowRoot { // sequence<Element> elementsFromPoint (float x, float y); // Not implemented yet: bug 1430307. // CaretPosition? caretPositionFromPoint (float x, float y); readonly attribute Element? activeElement; readonly attribute StyleSheetList styleSheets; readonly attribute Element? pointerLockElement; - // Not implemented yet: bug 1430305. - // readonly attribute Element? fullscreenElement; + [LenientSetter, Func="nsIDocument::IsUnprefixedFullscreenEnabled"] + readonly attribute Element? fullscreenElement; + [BinaryName="fullscreenElement"] + readonly attribute Element? mozFullScreenElement; };
--- a/dom/workers/WorkerHolder.h +++ b/dom/workers/WorkerHolder.h @@ -28,21 +28,16 @@ class WorkerPrivate; * | Terminating | yes | yes | * +-------------+-------------+-----------------+ * | Canceling | yes | yes | * +-------------+-------------+-----------------+ * | Killing | yes | yes | * +-------------+-------------+-----------------+ */ -#ifdef Status -/* Xlib headers insist on this for some reason... Nuke it because - it'll override our member name */ -#undef Status -#endif enum WorkerStatus { // Not yet scheduled. Pending = 0, // This status means that the worker is active. Running,
--- a/gfx/2d/FilterNodeSoftware.cpp +++ b/gfx/2d/FilterNodeSoftware.cpp @@ -608,17 +608,88 @@ already_AddRefed<DataSourceSurface> FilterNodeSoftware::GetOutput(const IntRect &aRect) { MOZ_ASSERT(GetOutputRectInRect(aRect).Contains(aRect)); if (aRect.Overflows()) { return nullptr; } - return Render(aRect); + IntRect cachedRect; + IntRect requestedRect; + RefPtr<DataSourceSurface> cachedOutput; + + // Lock the cache and retrieve a cached surface if we have one and it can + // satisfy this request, or else request a rect we will compute and cache + { + MutexAutoLock lock(mCacheMutex); + + if (!mCachedRect.Contains(aRect)) { + RequestRect(aRect); + requestedRect = mRequestedRect; + } else { + MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?"); + cachedRect = mCachedRect; + cachedOutput = mCachedOutput; + } + } + + if (!cachedOutput) { + // Compute the output + cachedOutput = Render(requestedRect); + + // Update the cache for future requests + MutexAutoLock lock(mCacheMutex); + + mCachedOutput = cachedOutput; + if (!mCachedOutput) { + mCachedRect = IntRect(); + mRequestedRect = IntRect(); + return nullptr; + } + mCachedRect = requestedRect; + mRequestedRect = IntRect(); + + cachedRect = mCachedRect; + } + + return GetDataSurfaceInRect(cachedOutput, cachedRect, aRect, EDGE_MODE_NONE); +} + +void +FilterNodeSoftware::RequestRect(const IntRect &aRect) +{ + if (mRequestedRect.Contains(aRect)) { + // Bail out now. Otherwise pathological filters can spend time exponential + // in the number of primitives, e.g. if each primitive takes the + // previous primitive as its two inputs. + return; + } + mRequestedRect = mRequestedRect.Union(aRect); + RequestFromInputsForRect(aRect); +} + +void +FilterNodeSoftware::RequestInputRect(uint32_t aInputEnumIndex, const IntRect &aRect) +{ + if (aRect.Overflows()) { + return; + } + + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { + gfxDevCrash(LogReason::FilterInputError) << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs(); + return; + } + if (mInputSurfaces[inputIndex]) { + return; + } + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + MOZ_ASSERT(filter, "missing input"); + filter->RequestRect(filter->GetOutputRectInRect(aRect)); } SurfaceFormat FilterNodeSoftware::DesiredFormat(SurfaceFormat aCurrentFormat, FormatHint aFormatHint) { if (aCurrentFormat == SurfaceFormat::A8 && aFormatHint == CAN_HANDLE_A8) { return SurfaceFormat::A8; @@ -767,18 +838,66 @@ FilterNodeSoftware::GetInputRectInRect(u } size_t FilterNodeSoftware::NumberOfSetInputs() { return std::max(mInputSurfaces.size(), mInputFilters.size()); } +void +FilterNodeSoftware::AddInvalidationListener(FilterInvalidationListener* aListener) +{ + MOZ_ASSERT(aListener, "null listener"); + mInvalidationListeners.push_back(aListener); +} + +void +FilterNodeSoftware::RemoveInvalidationListener(FilterInvalidationListener* aListener) +{ + MOZ_ASSERT(aListener, "null listener"); + std::vector<FilterInvalidationListener*>::iterator it = + std::find(mInvalidationListeners.begin(), mInvalidationListeners.end(), aListener); + mInvalidationListeners.erase(it); +} + +void +FilterNodeSoftware::FilterInvalidated(FilterNodeSoftware* aFilter) +{ + Invalidate(); +} + +void +FilterNodeSoftware::Invalidate() +{ + MutexAutoLock lock(mCacheMutex); + mCachedOutput = nullptr; + mCachedRect = IntRect(); + for (std::vector<FilterInvalidationListener*>::iterator it = mInvalidationListeners.begin(); + it != mInvalidationListeners.end(); it++) { + (*it)->FilterInvalidated(this); + } +} + +FilterNodeSoftware::FilterNodeSoftware() + : mCacheMutex("FilterNodeSoftware::mCacheMutex") +{ +} + FilterNodeSoftware::~FilterNodeSoftware() { + MOZ_ASSERT(!mInvalidationListeners.size(), + "All invalidation listeners should have unsubscribed themselves by now!"); + + for (std::vector<RefPtr<FilterNodeSoftware> >::iterator it = mInputFilters.begin(); + it != mInputFilters.end(); it++) { + if (*it) { + (*it)->RemoveInvalidationListener(this); + } + } } void FilterNodeSoftware::SetInput(uint32_t aIndex, FilterNode *aFilter) { if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_SOFTWARE) { MOZ_ASSERT(false, "can only take software filters as inputs"); return; @@ -802,21 +921,28 @@ FilterNodeSoftware::SetInput(uint32_t aI gfxDevCrash(LogReason::FilterInputSet) << "Invalid set " << inputIndex; return; } if ((uint32_t)inputIndex >= NumberOfSetInputs()) { mInputSurfaces.resize(inputIndex + 1); mInputFilters.resize(inputIndex + 1); } mInputSurfaces[inputIndex] = aSurface; + if (mInputFilters[inputIndex]) { + mInputFilters[inputIndex]->RemoveInvalidationListener(this); + } + if (aFilter) { + aFilter->AddInvalidationListener(this); + } mInputFilters[inputIndex] = aFilter; if (!aSurface && !aFilter && (size_t)inputIndex == NumberOfSetInputs()) { mInputSurfaces.resize(inputIndex); mInputFilters.resize(inputIndex); } + Invalidate(); } FilterNodeBlendSoftware::FilterNodeBlendSoftware() : mBlendMode(BLEND_MODE_MULTIPLY) {} int32_t FilterNodeBlendSoftware::InputIndex(uint32_t aInputEnumIndex) @@ -828,16 +954,17 @@ FilterNodeBlendSoftware::InputIndex(uint } } void FilterNodeBlendSoftware::SetAttribute(uint32_t aIndex, uint32_t aBlendMode) { MOZ_ASSERT(aIndex == ATT_BLEND_BLENDMODE); mBlendMode = static_cast<BlendMode>(aBlendMode); + Invalidate(); } static CompositionOp ToBlendOp(BlendMode aOp) { switch (aOp) { case BLEND_MODE_MULTIPLY: return CompositionOp::OP_MULTIPLY; case BLEND_MODE_SCREEN: @@ -931,16 +1058,23 @@ FilterNodeBlendSoftware::Render(const In } Rect r(0, 0, size.width, size.height); dt->DrawSurface(input2, r, r, DrawSurfaceOptions(), DrawOptions(1.0f, ToBlendOp(mBlendMode))); dt->Flush(); return target.forget(); } +void +FilterNodeBlendSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_BLEND_IN, aRect); + RequestInputRect(IN_BLEND_IN2, aRect); +} + IntRect FilterNodeBlendSoftware::GetOutputRectInRect(const IntRect& aRect) { return GetInputRectInRect(IN_BLEND_IN, aRect).Union( GetInputRectInRect(IN_BLEND_IN2, aRect)).Intersect(aRect); } FilterNodeTransformSoftware::FilterNodeTransformSoftware() @@ -956,23 +1090,25 @@ FilterNodeTransformSoftware::InputIndex( } } void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, uint32_t aFilter) { MOZ_ASSERT(aIndex == ATT_TRANSFORM_FILTER); mSamplingFilter = static_cast<SamplingFilter>(aFilter); + Invalidate(); } void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, const Matrix &aMatrix) { MOZ_ASSERT(aIndex == ATT_TRANSFORM_MATRIX); mMatrix = aMatrix; + Invalidate(); } IntRect FilterNodeTransformSoftware::SourceRectForOutputRect(const IntRect &aRect) { if (aRect.IsEmpty()) { return IntRect(); } @@ -1037,16 +1173,22 @@ FilterNodeTransformSoftware::Render(cons dt->SetTransform(transform); dt->DrawSurface(input, r, r, DrawSurfaceOptions(mSamplingFilter)); dt->Flush(); surf->Unmap(); return surf.forget(); } +void +FilterNodeTransformSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_TRANSFORM_IN, SourceRectForOutputRect(aRect)); +} + IntRect FilterNodeTransformSoftware::GetOutputRectInRect(const IntRect& aRect) { IntRect srcRect = SourceRectForOutputRect(aRect); if (srcRect.IsEmpty()) { return IntRect(); } @@ -1074,24 +1216,26 @@ FilterNodeMorphologySoftware::InputIndex void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, const IntSize &aRadii) { MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_RADII); mRadii.width = std::min(std::max(aRadii.width, 0), 100000); mRadii.height = std::min(std::max(aRadii.height, 0), 100000); + Invalidate(); } void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, uint32_t aOperator) { MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_OPERATOR); mOperator = static_cast<MorphologyOperator>(aOperator); + Invalidate(); } static already_AddRefed<DataSourceSurface> ApplyMorphology(const IntRect& aSourceRect, DataSourceSurface* aInput, const IntRect& aDestRect, int32_t rx, int32_t ry, MorphologyOperator aOperator) { IntRect srcRect = aSourceRect - aDestRect.TopLeft(); @@ -1170,16 +1314,24 @@ FilterNodeMorphologySoftware::Render(con if (rx == 0 && ry == 0) { return input.forget(); } return ApplyMorphology(srcRect, input, aRect, rx, ry, mOperator); } +void +FilterNodeMorphologySoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + IntRect srcRect = aRect; + srcRect.Inflate(mRadii); + RequestInputRect(IN_MORPHOLOGY_IN, srcRect); +} + IntRect FilterNodeMorphologySoftware::GetOutputRectInRect(const IntRect& aRect) { IntRect inflatedSourceRect = aRect; inflatedSourceRect.Inflate(mRadii); IntRect inputRect = GetInputRectInRect(IN_MORPHOLOGY_IN, inflatedSourceRect); if (mOperator == MORPHOLOGY_OPERATOR_ERODE) { inputRect.Deflate(mRadii); @@ -1199,24 +1351,26 @@ FilterNodeColorMatrixSoftware::InputInde } void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex, const Matrix5x4 &aMatrix) { MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_MATRIX); mMatrix = aMatrix; + Invalidate(); } void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex, uint32_t aAlphaMode) { MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_ALPHA_MODE); mAlphaMode = (AlphaMode)aAlphaMode; + Invalidate(); } static already_AddRefed<DataSourceSurface> Premultiply(DataSourceSurface* aSurface) { if (aSurface->GetFormat() == SurfaceFormat::A8) { RefPtr<DataSourceSurface> surface(aSurface); return surface.forget(); @@ -1296,30 +1450,37 @@ FilterNodeColorMatrixSoftware::Render(co if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) { result = Premultiply(result); } return result.forget(); } +void +FilterNodeColorMatrixSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_COLOR_MATRIX_IN, aRect); +} + IntRect FilterNodeColorMatrixSoftware::GetOutputRectInRect(const IntRect& aRect) { if (mMatrix._54 > 0.0f) { return aRect; } return GetInputRectInRect(IN_COLOR_MATRIX_IN, aRect); } void FilterNodeFloodSoftware::SetAttribute(uint32_t aIndex, const Color &aColor) { MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR); mColor = aColor; + Invalidate(); } static uint32_t ColorToBGRA(const Color& aColor) { union { uint32_t color; uint8_t components[4]; @@ -1412,16 +1573,17 @@ FilterNodeTileSoftware::InputIndex(uint3 void FilterNodeTileSoftware::SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) { MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT); mSourceRect.SetRect(int32_t(aSourceRect.X()), int32_t(aSourceRect.Y()), int32_t(aSourceRect.Width()), int32_t(aSourceRect.Height())); + Invalidate(); } namespace { struct CompareIntRects { bool operator()(const IntRect& a, const IntRect& b) const { if (a.X() != b.X()) { @@ -1500,16 +1662,25 @@ FilterNodeTileSoftware::Render(const Int CopyRect(input, target, srcRect - srcRect.TopLeft(), destRect.TopLeft() - aRect.TopLeft()); } } return target.forget(); } +void +FilterNodeTileSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + // Do not request anything. + // Source rects for the tile filter can be discontinuous with large gaps + // between them. Requesting those from our input filter might cause it to + // render the whole bounding box of all of them, which would be wasteful. +} + IntRect FilterNodeTileSoftware::GetOutputRectInRect(const IntRect& aRect) { return aRect; } FilterNodeComponentTransferSoftware::FilterNodeComponentTransferSoftware() : mDisableR(true) @@ -1533,16 +1704,17 @@ FilterNodeComponentTransferSoftware::Set mDisableB = aDisable; break; case ATT_TRANSFER_DISABLE_A: mDisableA = aDisable; break; default: MOZ_CRASH("GFX: FilterNodeComponentTransferSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeComponentTransferSoftware::GenerateLookupTable(ptrdiff_t aComponent, uint8_t aTables[4][256], bool aDisabled) { if (aDisabled) { @@ -1659,16 +1831,22 @@ FilterNodeComponentTransferSoftware::Ren TransferComponents<1>(input, target, &lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_A]); } else { TransferComponents<4>(input, target, lookupTables); } return target.forget(); } +void +FilterNodeComponentTransferSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_TRANSFER_IN, aRect); +} + IntRect FilterNodeComponentTransferSoftware::GetOutputRectInRect(const IntRect& aRect) { if (mDisableA) { return GetInputRectInRect(IN_TRANSFER_IN, aRect); } return aRect; } @@ -1699,16 +1877,17 @@ FilterNodeTableTransferSoftware::SetAttr mTableB = table; break; case ATT_TABLE_TRANSFER_TABLE_A: mTableA = table; break; default: MOZ_CRASH("GFX: FilterNodeTableTransferSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeTableTransferSoftware::FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) { switch (aComponent) { case B8G8R8A8_COMPONENT_BYTEOFFSET_R: @@ -1767,16 +1946,17 @@ FilterNodeDiscreteTransferSoftware::SetA mTableB = discrete; break; case ATT_DISCRETE_TRANSFER_TABLE_A: mTableA = discrete; break; default: MOZ_CRASH("GFX: FilterNodeDiscreteTransferSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeDiscreteTransferSoftware::FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) { switch (aComponent) { case B8G8R8A8_COMPONENT_BYTEOFFSET_R: @@ -1855,16 +2035,17 @@ FilterNodeLinearTransferSoftware::SetAtt mSlopeA = aValue; break; case ATT_LINEAR_TRANSFER_INTERCEPT_A: mInterceptA = aValue; break; default: MOZ_CRASH("GFX: FilterNodeLinearTransferSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeLinearTransferSoftware::FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) { switch (aComponent) { case B8G8R8A8_COMPONENT_BYTEOFFSET_R: @@ -1948,16 +2129,17 @@ FilterNodeGammaTransferSoftware::SetAttr mExponentA = aValue; break; case ATT_GAMMA_TRANSFER_OFFSET_A: mOffsetA = aValue; break; default: MOZ_CRASH("GFX: FilterNodeGammaTransferSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) { switch (aComponent) { case B8G8R8A8_COMPONENT_BYTEOFFSET_R: @@ -2009,84 +2191,92 @@ FilterNodeConvolveMatrixSoftware::InputI } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, const IntSize &aKernelSize) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_SIZE); mKernelSize = aKernelSize; + Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, const Float *aMatrix, uint32_t aSize) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_MATRIX); mKernelMatrix = std::vector<Float>(aMatrix, aMatrix + aSize); + Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, Float aValue) { switch (aIndex) { case ATT_CONVOLVE_MATRIX_DIVISOR: mDivisor = aValue; break; case ATT_CONVOLVE_MATRIX_BIAS: mBias = aValue; break; default: MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, const Size &aKernelUnitLength) { switch (aIndex) { case ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH: mKernelUnitLength = aKernelUnitLength; break; default: MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, const IntPoint &aTarget) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_TARGET); mTarget = aTarget; + Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_SOURCE_RECT); mSourceRect = aSourceRect; + Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, uint32_t aEdgeMode) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_EDGE_MODE); mEdgeMode = static_cast<ConvolveMatrixEdgeMode>(aEdgeMode); + Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, bool aPreserveAlpha) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA); mPreserveAlpha = aPreserveAlpha; + Invalidate(); } #ifdef DEBUG static inline void DebugOnlyCheckColorSamplingAccess(const uint8_t* aSampleAddress, const uint8_t* aBoundsBegin, const uint8_t* aBoundsEnd) { MOZ_ASSERT(aSampleAddress >= aBoundsBegin, "accessing before start"); MOZ_ASSERT(aSampleAddress < aBoundsEnd, "accessing after end"); @@ -2323,16 +2513,22 @@ FilterNodeConvolveMatrixSoftware::DoRend aKernelUnitLengthX, aKernelUnitLengthY); } } delete[] intKernel; return target.forget(); } +void +FilterNodeConvolveMatrixSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect)); +} + IntRect FilterNodeConvolveMatrixSoftware::InflatedSourceRect(const IntRect &aDestRect) { if (aDestRect.IsEmpty()) { return IntRect(); } IntMargin margin; @@ -2389,31 +2585,33 @@ FilterNodeDisplacementMapSoftware::Input } void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, Float aScale) { MOZ_ASSERT(aIndex == ATT_DISPLACEMENT_MAP_SCALE); mScale = aScale; + Invalidate(); } void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, uint32_t aValue) { switch (aIndex) { case ATT_DISPLACEMENT_MAP_X_CHANNEL: mChannelX = static_cast<ColorChannel>(aValue); break; case ATT_DISPLACEMENT_MAP_Y_CHANNEL: mChannelY = static_cast<ColorChannel>(aValue); break; default: MOZ_CRASH("GFX: FilterNodeDisplacementMapSoftware::SetAttribute"); } + Invalidate(); } already_AddRefed<DataSourceSurface> FilterNodeDisplacementMapSoftware::Render(const IntRect& aRect) { IntRect srcRect = InflatedSourceOrDestRect(aRect); RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(IN_DISPLACEMENT_MAP_IN, srcRect, NEED_COLOR_CHANNELS); @@ -2468,16 +2666,23 @@ FilterNodeDisplacementMapSoftware::Rende // Keep valgrind happy. PodZero(&targetData[y * targetStride + 4 * aRect.Width()], targetStride - 4 * aRect.Width()); } return target.forget(); } +void +FilterNodeDisplacementMapSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_DISPLACEMENT_MAP_IN, InflatedSourceOrDestRect(aRect)); + RequestInputRect(IN_DISPLACEMENT_MAP_IN2, aRect); +} + IntRect FilterNodeDisplacementMapSoftware::InflatedSourceOrDestRect(const IntRect &aDestOrSourceRect) { IntRect sourceOrDestRect = aDestOrSourceRect; sourceOrDestRect.Inflate(ceil(fabs(mScale) / 2)); return sourceOrDestRect; } @@ -2508,36 +2713,39 @@ FilterNodeTurbulenceSoftware::SetAttribu switch (aIndex) { case ATT_TURBULENCE_BASE_FREQUENCY: mBaseFrequency = aBaseFrequency; break; default: MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); break; } + Invalidate(); } void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, const IntRect &aRect) { switch (aIndex) { case ATT_TURBULENCE_RECT: mRenderRect = aRect; break; default: MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); break; } + Invalidate(); } void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, bool aStitchable) { MOZ_ASSERT(aIndex == ATT_TURBULENCE_STITCHABLE); mStitchable = aStitchable; + Invalidate(); } void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, uint32_t aValue) { switch (aIndex) { case ATT_TURBULENCE_NUM_OCTAVES: mNumOctaves = aValue; @@ -2547,16 +2755,17 @@ FilterNodeTurbulenceSoftware::SetAttribu break; case ATT_TURBULENCE_TYPE: mType = static_cast<TurbulenceType>(aValue); break; default: MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); break; } + Invalidate(); } already_AddRefed<DataSourceSurface> FilterNodeTurbulenceSoftware::Render(const IntRect& aRect) { return FilterProcessing::RenderTurbulence( aRect.Size(), aRect.TopLeft(), mBaseFrequency, mSeed, mNumOctaves, mType, mStitchable, Rect(mRenderRect)); @@ -2590,16 +2799,18 @@ FilterNodeArithmeticCombineSoftware::Set { MOZ_ASSERT(aIndex == ATT_ARITHMETIC_COMBINE_COEFFICIENTS); MOZ_ASSERT(aSize == 4); mK1 = aFloat[0]; mK2 = aFloat[1]; mK3 = aFloat[2]; mK4 = aFloat[3]; + + Invalidate(); } already_AddRefed<DataSourceSurface> FilterNodeArithmeticCombineSoftware::Render(const IntRect& aRect) { RefPtr<DataSourceSurface> input1 = GetInputDataSourceSurface(IN_ARITHMETIC_COMBINE_IN, aRect, NEED_COLOR_CHANNELS); RefPtr<DataSourceSurface> input2 = @@ -2620,16 +2831,23 @@ FilterNodeArithmeticCombineSoftware::Ren k1 = 0.0f; k3 = 0.0f; input2 = input1; } return FilterProcessing::ApplyArithmeticCombine(input1, input2, k1, k2, k3, k4); } +void +FilterNodeArithmeticCombineSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_ARITHMETIC_COMBINE_IN, aRect); + RequestInputRect(IN_ARITHMETIC_COMBINE_IN2, aRect); +} + IntRect FilterNodeArithmeticCombineSoftware::GetOutputRectInRect(const IntRect& aRect) { if (mK4 > 0.0f) { return aRect; } IntRect rectFrom1 = GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN, aRect).Intersect(aRect); IntRect rectFrom2 = GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN2, aRect).Intersect(aRect); @@ -2656,16 +2874,17 @@ FilterNodeCompositeSoftware::InputIndex( return aInputEnumIndex - IN_COMPOSITE_IN_START; } void FilterNodeCompositeSoftware::SetAttribute(uint32_t aIndex, uint32_t aCompositeOperator) { MOZ_ASSERT(aIndex == ATT_COMPOSITE_OPERATOR); mOperator = static_cast<CompositeOperator>(aCompositeOperator); + Invalidate(); } already_AddRefed<DataSourceSurface> FilterNodeCompositeSoftware::Render(const IntRect& aRect) { RefPtr<DataSourceSurface> start = GetInputDataSourceSurface(IN_COMPOSITE_IN_START, aRect, NEED_COLOR_CHANNELS); RefPtr<DataSourceSurface> dest = @@ -2702,16 +2921,24 @@ FilterNodeCompositeSoftware::Render(cons // no additional input can get rid of that transparency. return nullptr; } } } return dest.forget(); } +void +FilterNodeCompositeSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { + RequestInputRect(IN_COMPOSITE_IN_START + inputIndex, aRect); + } +} + IntRect FilterNodeCompositeSoftware::GetOutputRectInRect(const IntRect& aRect) { IntRect rect; for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { IntRect inputRect = GetInputRectInRect(IN_COMPOSITE_IN_START + inputIndex, aRect); if (mOperator == COMPOSITE_OPERATOR_IN && inputIndex > 0) { rect = rect.Intersect(inputRect); @@ -2787,16 +3014,22 @@ FilterNodeBlurXYSoftware::Render(const I blur.Blur(channel3Map.GetData()); } target = FilterProcessing::CombineColorChannels(channel0, channel1, channel2, channel3); } return GetDataSurfaceInRect(target, srcRect, aRect, EDGE_MODE_NONE); } +void +FilterNodeBlurXYSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect)); +} + IntRect FilterNodeBlurXYSoftware::InflatedSourceOrDestRect(const IntRect &aDestRect) { Size sigmaXY = StdDeviationXY(); IntSize d = AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height)); IntRect srcRect = aDestRect; srcRect.Inflate(d); return srcRect; @@ -2827,16 +3060,17 @@ FilterNodeGaussianBlurSoftware::SetAttri { switch (aIndex) { case ATT_GAUSSIAN_BLUR_STD_DEVIATION: mStdDeviation = ClampStdDeviation(aStdDeviation); break; default: MOZ_CRASH("GFX: FilterNodeGaussianBlurSoftware::SetAttribute"); } + Invalidate(); } Size FilterNodeGaussianBlurSoftware::StdDeviationXY() { return Size(mStdDeviation, mStdDeviation); } @@ -2850,29 +3084,31 @@ FilterNodeDirectionalBlurSoftware::SetAt { switch (aIndex) { case ATT_DIRECTIONAL_BLUR_STD_DEVIATION: mStdDeviation = ClampStdDeviation(aStdDeviation); break; default: MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute"); } + Invalidate(); } void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex, uint32_t aBlurDirection) { switch (aIndex) { case ATT_DIRECTIONAL_BLUR_DIRECTION: mBlurDirection = (BlurDirection)aBlurDirection; break; default: MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute"); } + Invalidate(); } Size FilterNodeDirectionalBlurSoftware::StdDeviationXY() { float sigmaX = mBlurDirection == BLUR_DIRECTION_X ? mStdDeviation : 0; float sigmaY = mBlurDirection == BLUR_DIRECTION_Y ? mStdDeviation : 0; return Size(sigmaX, sigmaY); @@ -2892,24 +3128,31 @@ FilterNodeCropSoftware::SetAttribute(uin const Rect &aSourceRect) { MOZ_ASSERT(aIndex == ATT_CROP_RECT); Rect srcRect = aSourceRect; srcRect.Round(); if (!srcRect.ToIntRect(&mCropRect)) { mCropRect = IntRect(); } + Invalidate(); } already_AddRefed<DataSourceSurface> FilterNodeCropSoftware::Render(const IntRect& aRect) { return GetInputDataSourceSurface(IN_CROP_IN, aRect.Intersect(mCropRect)); } +void +FilterNodeCropSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_CROP_IN, aRect.Intersect(mCropRect)); +} + IntRect FilterNodeCropSoftware::GetOutputRectInRect(const IntRect& aRect) { return GetInputRectInRect(IN_CROP_IN, aRect).Intersect(mCropRect); } int32_t FilterNodePremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) @@ -2923,16 +3166,22 @@ FilterNodePremultiplySoftware::InputInde already_AddRefed<DataSourceSurface> FilterNodePremultiplySoftware::Render(const IntRect& aRect) { RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(IN_PREMULTIPLY_IN, aRect); return input ? Premultiply(input) : nullptr; } +void +FilterNodePremultiplySoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_PREMULTIPLY_IN, aRect); +} + IntRect FilterNodePremultiplySoftware::GetOutputRectInRect(const IntRect& aRect) { return GetInputRectInRect(IN_PREMULTIPLY_IN, aRect); } int32_t FilterNodeUnpremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) @@ -2946,16 +3195,22 @@ FilterNodeUnpremultiplySoftware::InputIn already_AddRefed<DataSourceSurface> FilterNodeUnpremultiplySoftware::Render(const IntRect& aRect) { RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(IN_UNPREMULTIPLY_IN, aRect); return input ? Unpremultiply(input) : nullptr; } +void +FilterNodeUnpremultiplySoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_UNPREMULTIPLY_IN, aRect); +} + IntRect FilterNodeUnpremultiplySoftware::GetOutputRectInRect(const IntRect& aRect) { return GetInputRectInRect(IN_UNPREMULTIPLY_IN, aRect); } bool PointLightSoftware::SetAttribute(uint32_t aIndex, const Point3D &aPoint) @@ -3056,57 +3311,62 @@ FilterNodeLightingSoftware<LightType, Li } } template<typename LightType, typename LightingType> void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, const Point3D &aPoint) { if (mLight.SetAttribute(aIndex, aPoint)) { + Invalidate(); return; } MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute point"); } template<typename LightType, typename LightingType> void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, Float aValue) { if (mLight.SetAttribute(aIndex, aValue) || mLighting.SetAttribute(aIndex, aValue)) { + Invalidate(); return; } switch (aIndex) { case ATT_LIGHTING_SURFACE_SCALE: mSurfaceScale = std::fpclassify(aValue) == FP_SUBNORMAL ? 0.0 : aValue; break; default: MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute float"); } + Invalidate(); } template<typename LightType, typename LightingType> void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, const Size &aKernelUnitLength) { switch (aIndex) { case ATT_LIGHTING_KERNEL_UNIT_LENGTH: mKernelUnitLength = aKernelUnitLength; break; default: MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute size"); } + Invalidate(); } template<typename LightType, typename LightingType> void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, const Color &aColor) { MOZ_ASSERT(aIndex == ATT_LIGHTING_COLOR); mColor = aColor; + Invalidate(); } template<typename LightType, typename LightingType> IntRect FilterNodeLightingSoftware<LightType, LightingType>::GetOutputRectInRect(const IntRect& aRect) { return aRect; } @@ -3227,16 +3487,26 @@ FilterNodeLightingSoftware<LightType, Li { if (mKernelUnitLength.width == floor(mKernelUnitLength.width) && mKernelUnitLength.height == floor(mKernelUnitLength.height)) { return DoRender(aRect, (int32_t)mKernelUnitLength.width, (int32_t)mKernelUnitLength.height); } return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height); } +template<typename LightType, typename LightingType> +void +FilterNodeLightingSoftware<LightType, LightingType>::RequestFromInputsForRect(const IntRect &aRect) +{ + IntRect srcRect = aRect; + srcRect.Inflate(ceil(mKernelUnitLength.width), + ceil(mKernelUnitLength.height)); + RequestInputRect(IN_LIGHTING_IN, srcRect); +} + template<typename LightType, typename LightingType> template<typename CoordType> already_AddRefed<DataSourceSurface> FilterNodeLightingSoftware<LightType, LightingType>::DoRender(const IntRect& aRect, CoordType aKernelUnitLengthX, CoordType aKernelUnitLengthY) { MOZ_ASSERT(aKernelUnitLengthX > 0, "aKernelUnitLengthX can be a negative or zero value"); MOZ_ASSERT(aKernelUnitLengthY > 0, "aKernelUnitLengthY can be a negative or zero value");
--- a/gfx/2d/FilterNodeSoftware.h +++ b/gfx/2d/FilterNodeSoftware.h @@ -15,39 +15,59 @@ namespace mozilla { namespace gfx { class DataSourceSurface; class DrawTarget; struct DrawOptions; class FilterNodeSoftware; /** + * Can be attached to FilterNodeSoftware instances using + * AddInvalidationListener. FilterInvalidated is called whenever the output of + * the observed filter may have changed; that is, whenever cached GetOutput() + * results (and results derived from them) need to discarded. + */ +class FilterInvalidationListener +{ +public: + virtual void FilterInvalidated(FilterNodeSoftware* aFilter) = 0; +}; + +/** * This is the base class for the software (i.e. pure CPU, non-accelerated) * FilterNode implementation. The software implementation is backend-agnostic, * so it can be used as a fallback for all DrawTarget implementations. */ -class FilterNodeSoftware : public FilterNode +class FilterNodeSoftware : public FilterNode, + public FilterInvalidationListener { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeSoftware, override) + FilterNodeSoftware(); virtual ~FilterNodeSoftware(); // Factory method, intended to be called from DrawTarget*::CreateFilter. static already_AddRefed<FilterNode> Create(FilterType aType); // Draw the filter, intended to be called by DrawTarget*::DrawFilter. void Draw(DrawTarget* aDrawTarget, const Rect &aSourceRect, const Point &aDestPoint, const DrawOptions &aOptions); virtual FilterBackend GetBackendType() override { return FILTER_BACKEND_SOFTWARE; } virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) override; virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override; virtual const char* GetName() { return "Unknown"; } + virtual void AddInvalidationListener(FilterInvalidationListener* aListener); + virtual void RemoveInvalidationListener(FilterInvalidationListener* aListener); + + // FilterInvalidationListener implementation + virtual void FilterInvalidated(FilterNodeSoftware* aFilter) override; + protected: // The following methods are intended to be overriden by subclasses. /** * Translates a *FilterInputs enum value into an index for the * mInputFilters / mInputSurfaces arrays. Returns -1 for invalid inputs. * If somebody calls SetInput(enumValue, input) with an enumValue for which @@ -71,16 +91,23 @@ protected: * May return nullptr in error conditions or for an empty aRect. * Implementations are not required to allocate a new surface and may even * pass through input surfaces unchanged. * Callers need to treat the returned surface as immutable. */ virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) = 0; /** + * Call RequestRect (see below) on any input filters with the desired input + * rect, so that the input filter knows what to cache the next time it + * renders. + */ + virtual void RequestFromInputsForRect(const IntRect &aRect) {} + + /** * This method provides a caching default implementation but can be overriden * by subclasses that don't want to cache their output. Those classes should * call Render(aRect) directly from here. */ virtual already_AddRefed<DataSourceSurface> GetOutput(const IntRect &aRect); // The following methods are non-virtual helper methods. @@ -123,36 +150,81 @@ protected: /** * Returns the intersection of the input filter's or surface's output rect * with aInRect. */ IntRect GetInputRectInRect(uint32_t aInputEnumIndex, const IntRect& aInRect); /** + * Calls RequestRect on the specified input, if it's a filter. + */ + void RequestInputRect(uint32_t aInputEnumIndex, const IntRect& aRect); + + /** * Returns the number of set input filters or surfaces. Needed for filters * which can have an arbitrary number of inputs. */ size_t NumberOfSetInputs(); /** + * Discard the cached surface that was stored in the GetOutput default + * implementation. Needs to be called whenever attributes or inputs are set + * that might change the result of a Render() call. + */ + void Invalidate(); + + /** + * Called in order to let this filter know what to cache during the next + * GetOutput call. Expected to call RequestRect on this filter's input + * filters. + */ + void RequestRect(const IntRect &aRect); + + /** * Set input filter and clear input surface for this input index, or set * input surface and clear input filter. One of aSurface and aFilter should * be null. */ void SetInput(uint32_t aIndex, SourceSurface *aSurface, FilterNodeSoftware *aFilter); protected: /** * mInputSurfaces / mInputFilters: For each input index, either a surface or * a filter is set, and the other is null. */ std::vector<RefPtr<SourceSurface> > mInputSurfaces; std::vector<RefPtr<FilterNodeSoftware> > mInputFilters; + + /** + * Weak pointers to our invalidation listeners, i.e. to those filters who + * have this filter as an input. Invalidation listeners are required to + * unsubscribe themselves from us when they let go of their reference to us. + * This ensures that the pointers in this array are never stale. + */ + std::vector<FilterInvalidationListener*> mInvalidationListeners; + + /** + * Lock guarding mRequestedRect, mCachedRect, and mCachedOutput. All uses + * of those members must aquire this lock. + */ + Mutex mCacheMutex; + + /** + * Stores the rect which we want to render and cache on the next call to + * GetOutput. + */ + IntRect mRequestedRect; + + /** + * Stores our cached output. + */ + IntRect mCachedRect; + RefPtr<DataSourceSurface> mCachedOutput; }; // Subclasses for specific filters. class FilterNodeTransformSoftware : public FilterNodeSoftware { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTransformSoftware, override) @@ -161,16 +233,17 @@ public: using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, uint32_t aGraphicsFilter) override; virtual void SetAttribute(uint32_t aIndex, const Matrix &aMatrix) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; IntRect SourceRectForOutputRect(const IntRect &aRect); private: Matrix mMatrix; SamplingFilter mSamplingFilter; }; class FilterNodeBlendSoftware : public FilterNodeSoftware @@ -181,16 +254,17 @@ public: virtual const char* GetName() override { return "Blend"; } using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, uint32_t aBlendMode) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: BlendMode mBlendMode; }; class FilterNodeMorphologySoftware : public FilterNodeSoftware { public: @@ -200,16 +274,17 @@ public: using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, const IntSize &aRadii) override; virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: IntSize mRadii; MorphologyOperator mOperator; }; class FilterNodeColorMatrixSoftware : public FilterNodeSoftware { @@ -219,16 +294,17 @@ public: using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &aMatrix) override; virtual void SetAttribute(uint32_t aIndex, uint32_t aAlphaMode) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: Matrix5x4 mMatrix; AlphaMode mAlphaMode; }; class FilterNodeFloodSoftware : public FilterNodeSoftware { @@ -254,16 +330,17 @@ public: virtual const char* GetName() override { return "Tile"; } using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: IntRect mSourceRect; }; /** * Baseclass for the four different component transfer filters. */ @@ -275,16 +352,17 @@ public: using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, bool aDisable) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; virtual void GenerateLookupTable(ptrdiff_t aComponent, uint8_t aTables[4][256], bool aDisabled); virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) = 0; bool mDisableR; bool mDisableG; bool mDisableB; bool mDisableA; @@ -399,16 +477,17 @@ public: virtual void SetAttribute(uint32_t aIndex, const IntPoint &aTarget) override; virtual void SetAttribute(uint32_t aIndex, uint32_t aEdgeMode) override; virtual void SetAttribute(uint32_t aIndex, bool aPreserveAlpha) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: template<typename CoordType> already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect, CoordType aKernelUnitLengthX, CoordType aKernelUnitLengthY); IntRect InflatedSourceRect(const IntRect &aDestRect); @@ -434,16 +513,17 @@ public: using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, Float aScale) override; virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: IntRect InflatedSourceOrDestRect(const IntRect &aDestOrSourceRect); Float mScale; ColorChannel mChannelX; ColorChannel mChannelY; }; @@ -482,16 +562,17 @@ public: virtual const char* GetName() override { return "ArithmeticCombine"; } using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: Float mK1; Float mK2; Float mK3; Float mK4; }; @@ -503,32 +584,34 @@ public: virtual const char* GetName() override { return "Composite"; } using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: CompositeOperator mOperator; }; // Base class for FilterNodeGaussianBlurSoftware and // FilterNodeDirectionalBlurSoftware. class FilterNodeBlurXYSoftware : public FilterNodeSoftware { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlurXYSoftware, override) protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; IntRect InflatedSourceOrDestRect(const IntRect &aDestRect); + virtual void RequestFromInputsForRect(const IntRect &aRect) override; // Implemented by subclasses. virtual Size StdDeviationXY() = 0; }; class FilterNodeGaussianBlurSoftware : public FilterNodeBlurXYSoftware { public: @@ -570,41 +653,44 @@ public: virtual const char* GetName() override { return "Crop"; } using FilterNodeSoftware::SetAttribute; virtual void SetAttribute(uint32_t aIndex, const Rect &aSourceRect) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: IntRect mCropRect; }; class FilterNodePremultiplySoftware : public FilterNodeSoftware { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplySoftware, override) virtual const char* GetName() override { return "Premultiply"; } protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; }; class FilterNodeUnpremultiplySoftware : public FilterNodeSoftware { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeUnpremultiplySoftware, override) virtual const char* GetName() override { return "Unpremultiply"; } protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; }; template<typename LightType, typename LightingType> class FilterNodeLightingSoftware : public FilterNodeSoftware { public: #if defined(MOZILLA_INTERNAL_API) && (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING)) // Helpers for refcounted @@ -618,16 +704,17 @@ public: virtual void SetAttribute(uint32_t aIndex, const Size &) override; virtual void SetAttribute(uint32_t aIndex, const Point3D &) override; virtual void SetAttribute(uint32_t aIndex, const Color &) override; protected: virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; private: template<typename CoordType> already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect, CoordType aKernelUnitLengthX, CoordType aKernelUnitLengthY); Mutex mLock;
--- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -600,22 +600,21 @@ WebRenderDebugPrefChangeCallback(const c if (Preferences::GetBool(WR_DEBUG_PREF suffix, false)) { \ flags |= (bit); \ } // TODO: It would be nice to get the bit patterns directly from the rust code. GFX_WEBRENDER_DEBUG(".profiler", 1 << 0) GFX_WEBRENDER_DEBUG(".render-targets", 1 << 1) GFX_WEBRENDER_DEBUG(".texture-cache", 1 << 2) - GFX_WEBRENDER_DEBUG(".alpha-primitives", 1 << 3) - GFX_WEBRENDER_DEBUG(".gpu-time-queries", 1 << 4) - GFX_WEBRENDER_DEBUG(".gpu-sample-queries", 1 << 5) - GFX_WEBRENDER_DEBUG(".disable-batching", 1 << 6) - GFX_WEBRENDER_DEBUG(".epochs", 1 << 7) - GFX_WEBRENDER_DEBUG(".compact-profiler", 1 << 8) + GFX_WEBRENDER_DEBUG(".gpu-time-queries", 1 << 3) + GFX_WEBRENDER_DEBUG(".gpu-sample-queries", 1 << 4) + GFX_WEBRENDER_DEBUG(".disable-batching", 1 << 5) + GFX_WEBRENDER_DEBUG(".epochs", 1 << 6) + GFX_WEBRENDER_DEBUG(".compact-profiler", 1 << 7) #undef GFX_WEBRENDER_DEBUG gfx::gfxVars::SetWebRenderDebugFlags(flags); } #if defined(USE_SKIA) static uint32_t GetSkiaGlyphCacheSize()
--- a/js/src/builtin/DataViewObject.cpp +++ b/js/src/builtin/DataViewObject.cpp @@ -38,20 +38,18 @@ using namespace js; using namespace js::gc; using mozilla::AssertedCast; using JS::CanonicalizeNaN; using JS::ToInt32; using JS::ToUint32; static NewObjectKind -DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto) +DataViewNewObjectKind(JSContext* cx) { - if (!proto && byteLength >= TypedArrayObject::SINGLETON_BYTE_LENGTH) - return SingletonObject; jsbytecode* pc; JSScript* script = cx->currentScript(&pc); if (script && ObjectGroup::useSingletonForAllocationSite(script, pc, &DataViewObject::class_)) return SingletonObject; return GenericObject; } DataViewObject* @@ -65,17 +63,17 @@ DataViewObject::create(JSContext* cx, ui MOZ_ASSERT(byteOffset <= INT32_MAX); MOZ_ASSERT(byteLength <= INT32_MAX); MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX); RootedObject proto(cx, protoArg); RootedObject obj(cx); - NewObjectKind newKind = DataViewNewObjectKind(cx, byteLength, proto); + NewObjectKind newKind = DataViewNewObjectKind(cx); obj = NewObjectWithClassProto(cx, &class_, proto, newKind); if (!obj) return nullptr; if (!proto) { if (byteLength >= TypedArrayObject::SINGLETON_BYTE_LENGTH) { MOZ_ASSERT(obj->isSingleton()); } else {
--- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -5,17 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef builtin_ModuleObject_h #define builtin_ModuleObject_h #include "mozilla/Maybe.h" #include "jsapi.h" -#include "jsatom.h" #include "builtin/SelfHostingDefines.h" #include "js/GCVector.h" #include "js/Id.h" #include "js/UniquePtr.h" #include "vm/NativeObject.h" #include "vm/ProxyObject.h"
--- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -8,17 +8,16 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/Move.h" #include <stdlib.h> #include "jsarray.h" -#include "jsatom.h" #include "jsobj.h" #include "jspubtd.h" #include "builtin/Reflect.h" #include "frontend/Parser.h" #include "frontend/TokenStream.h" #include "js/CharacterEncoding.h" #include "vm/RegExpObject.h"
--- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -16,17 +16,16 @@ #include "builtin/SIMD.h" #include "gc/Marking.h" #include "js/Vector.h" #include "vm/GlobalObject.h" #include "vm/String.h" #include "vm/StringBuffer.h" #include "vm/TypedArrayObject.h" -#include "jsatominlines.h" #include "jsobjinlines.h" #include "gc/Nursery-inl.h" #include "gc/StoreBuffer-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Shape-inl.h" using mozilla::AssertedCast;
--- a/js/src/builtin/intl/CommonFunctions.js +++ b/js/src/builtin/intl/CommonFunctions.js @@ -7,21 +7,16 @@ /** * Holder object for encapsulating regexp instances. * * Regular expression instances should be created after the initialization of * self-hosted global. */ var internalIntlRegExps = std_Object_create(null); internalIntlRegExps.unicodeLocaleExtensionSequenceRE = null; -internalIntlRegExps.languageTagRE = null; -internalIntlRegExps.duplicateVariantRE = null; -internalIntlRegExps.duplicateSingletonRE = null; -internalIntlRegExps.isWellFormedCurrencyCodeRE = null; -internalIntlRegExps.currencyDigitsRE = null; /** * Regular expression matching a "Unicode locale extension sequence", which the * specification defines as: "any substring of a language tag that starts with * a separator '-' and the singleton 'u' and includes the maximum sequence of * following non-singleton subtags and their preceding '-' separators." * * Alternatively, this may be defined as: the components of a language tag that @@ -69,244 +64,565 @@ function removeUnicodeExtensions(locale) return true; var xindex = callFunction(std_String_indexOf, combined, "-x-"); return xindex > 0 && xindex < uindex; }(), "recombination failed to remove all Unicode locale extension sequences"); return combined; } +/* eslint-disable complexity */ /** - * Regular expression defining BCP 47 language tags. + * Parser for BCP 47 language tags. + * + * Returns null if |locale| can't be parsed as a Language-Tag. If the input is + * an irregular grandfathered language tag, the object + * + * { + * locale: locale.toLowerCase(), + * grandfathered: true, + * } + * + * is returned. Otherwise the returned object has the following structure: + * + * { + * locale: locale.toLowerCase(), + * language: language subtag without extlang / undefined, + * extlang1: first extlang subtag / undefined, + * extlang2: second extlang subtag / undefined, + * extlang3: third extlang subtag / undefined, + * script: script subtag / undefined, + * region: region subtag / undefined, + * variants: array of variant subtags, + * extensions: array of extension subtags, + * privateuse: privateuse subtag / undefined, + * } + * + * All language tag subtags are returned in lower-case: + * + * var langtag = parseLanguageTag("en-Latn-US"); + * assertEq("en-latn-us", langtag.locale); + * assertEq("en", langtag.language); + * assertEq("latn", langtag.script); + * assertEq("us", langtag.region); * * Spec: RFC 5646 section 2.1. */ -function getLanguageTagRE() { - if (internalIntlRegExps.languageTagRE) - return internalIntlRegExps.languageTagRE; +function parseLanguageTag(locale) { + assert(typeof locale === "string", "locale is a string"); + + // Current parse index in |locale|. + var index = 0; + + // The three possible token type bits. Expressed as #defines to avoid + // extra named lookups in the interpreter/jits. + #define NONE 0b00 + #define ALPHA 0b01 + #define DIGIT 0b10 + + // The current token type, its start index, and its length. + var token = 0; + var tokenStart = 0; + var tokenLength = 0; + + // Constants for code units used below. + #define HYPHEN 0x2D + #define DIGIT_ZERO 0x30 + #define DIGIT_NINE 0x39 + #define UPPER_A 0x41 + #define UPPER_Z 0x5A + #define LOWER_A 0x61 + #define LOWER_X 0x78 + #define LOWER_Z 0x7A + assert(std_String_fromCharCode(HYPHEN) === "-" && + std_String_fromCharCode(DIGIT_ZERO) === "0" && + std_String_fromCharCode(DIGIT_NINE) === "9" && + std_String_fromCharCode(UPPER_A) === "A" && + std_String_fromCharCode(UPPER_Z) === "Z" && + std_String_fromCharCode(LOWER_A) === "a" && + std_String_fromCharCode(LOWER_X) === "x" && + std_String_fromCharCode(LOWER_Z) === "z", + "code unit constants should match the expected characters"); - // RFC 5234 section B.1 - // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - var ALPHA = "[a-zA-Z]"; - // DIGIT = %x30-39 - // ; 0-9 - var DIGIT = "[0-9]"; + // Reads the next token, returns |false| if an illegal character was + // found, otherwise returns |true|. + function nextToken() { + var type = NONE; + for (var i = index; i < locale.length; i++) { + // RFC 5234 section B.1 + // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + // DIGIT = %x30-39 ; 0-9 + var c = callFunction(std_String_charCodeAt, locale, i); + if ((UPPER_A <= c && c <= UPPER_Z) || (LOWER_A <= c && c <= LOWER_Z)) + type |= ALPHA; + else if (DIGIT_ZERO <= c && c <= DIGIT_NINE) + type |= DIGIT; + else if (c === HYPHEN && i > index && i + 1 < locale.length) + break; + else + return false; + } + + token = type; + tokenStart = index; + tokenLength = i - index; + index = i + 1; + return true; + } + + // Language tags are compared and processed case-insensitively, so + // technically it's not necessary to adjust case. But for easier processing, + // and because the canonical form for most subtags is lower case, we start + // with lower case for all. + // + // Note that the tokenizer function keeps using the original input string + // to properly detect non-ASCII characters. The lower-case string can't be + // used to detect those characters, because some non-ASCII characters + // lower-case map into ASCII characters, e.g. U+212A (KELVIN SIGN) lower- + // case maps to U+006B (LATIN SMALL LETTER K). + var localeLowercase = callFunction(std_String_toLowerCase, locale); + + // Returns the code unit of the first character at the current token + // position. Always returns the lower-case form of an alphabetical + // character. + function tokenStartCodeUnitLower() { + var c = callFunction(std_String_charCodeAt, localeLowercase, tokenStart); + assert((DIGIT_ZERO <= c && c <= DIGIT_NINE) || (LOWER_A <= c && c <= LOWER_Z), + "unexpected code unit"); + return c; + } + + // Returns the current token part transformed to lower-case. + function tokenStringLower() { + return Substring(localeLowercase, tokenStart, tokenLength); + } - // RFC 5646 section 2.1 - // alphanum = (ALPHA / DIGIT) ; letters and numbers - var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; - // regular = "art-lojban" ; these tags match the 'langtag' - // / "cel-gaulish" ; production, but their subtags - // / "no-bok" ; are not extended language - // / "no-nyn" ; or variant subtags: their meaning - // / "zh-guoyu" ; is defined by their registration - // / "zh-hakka" ; and all of these are deprecated - // / "zh-min" ; in favor of a more modern - // / "zh-min-nan" ; subtag or sequence of subtags - // / "zh-xiang" - var regular = "(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)"; - // irregular = "en-GB-oed" ; irregular tags do not match - // / "i-ami" ; the 'langtag' production and - // / "i-bnn" ; would not otherwise be - // / "i-default" ; considered 'well-formed' - // / "i-enochian" ; These tags are all valid, - // / "i-hak" ; but most are deprecated - // / "i-klingon" ; in favor of more modern - // / "i-lux" ; subtags or subtag - // / "i-mingo" ; combination - // / "i-navajo" - // / "i-pwn" - // / "i-tao" - // / "i-tay" - // / "i-tsu" - // / "sgn-BE-FR" - // / "sgn-BE-NL" - // / "sgn-CH-DE" - var irregular = "(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)"; - // grandfathered = irregular ; non-redundant tags registered - // / regular ; during the RFC 3066 era - var grandfathered = "(?:" + irregular + "|" + regular + ")"; - // privateuse = "x" 1*("-" (1*8alphanum)) - var privateuse = "(?:x(?:-[a-z0-9]{1,8})+)"; - // singleton = DIGIT ; 0 - 9 - // / %x41-57 ; A - W - // / %x59-5A ; Y - Z - // / %x61-77 ; a - w - // / %x79-7A ; y - z - var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; - // extension = singleton 1*("-" (2*8alphanum)) - var extension = "(?:" + singleton + "(?:-" + alphanum + "{2,8})+)"; - // variant = 5*8alphanum ; registered variants - // / (DIGIT 3alphanum) - var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; - // region = 2ALPHA ; ISO 3166-1 code - // / 3DIGIT ; UN M.49 code - var region = "(?:" + ALPHA + "{2}|" + DIGIT + "{3})"; - // script = 4ALPHA ; ISO 15924 code - var script = "(?:" + ALPHA + "{4})"; - // extlang = 3ALPHA ; selected ISO 639 codes - // *2("-" 3ALPHA) ; permanently reserved - var extlang = "(?:" + ALPHA + "{3}(?:-" + ALPHA + "{3}){0,2})"; - // language = 2*3ALPHA ; shortest ISO 639 code - // ["-" extlang] ; sometimes followed by - // ; extended language subtags - // / 4ALPHA ; or reserved for future use - // / 5*8ALPHA ; or registered language subtag - var language = "(?:" + ALPHA + "{2,3}(?:-" + extlang + ")?|" + ALPHA + "{4}|" + ALPHA + "{5,8})"; - // langtag = language - // ["-" script] - // ["-" region] - // *("-" variant) - // *("-" extension) - // ["-" privateuse] - var langtag = language + "(?:-" + script + ")?(?:-" + region + ")?(?:-" + - variant + ")*(?:-" + extension + ")*(?:-" + privateuse + ")?"; - // Language-Tag = langtag ; normal language tags - // / privateuse ; private use tag - // / grandfathered ; grandfathered tags - var languageTag = "^(?:" + langtag + "|" + privateuse + "|" + grandfathered + ")$"; + // Language-Tag = langtag ; normal language tags + // / privateuse ; private use tag + // / grandfathered ; grandfathered tags + if (!nextToken()) + return null; + + // All Language-Tag productions start with the ALPHA token and contain + // less-or-equal to eight characters. + if (token !== ALPHA || tokenLength > 8) + return null; + + assert(tokenLength > 0, "token length is not zero if type is ALPHA"); + + var language, extlang1, extlang2, extlang3, script, region, privateuse; + var variants = []; + var extensions = []; + + // langtag = language + // ["-" script] + // ["-" region] + // *("-" variant) + // *("-" extension) + // ["-" privateuse] + if (tokenLength > 1) { + // language = 2*3ALPHA ; shortest ISO 639 code + // ["-" extlang] ; sometimes followed by + // ; extended language subtags + // / 4ALPHA ; or reserved for future use + // / 5*8ALPHA ; or registered language subtag + if (tokenLength <= 3) { + language = tokenStringLower(); + if (!nextToken()) + return null; - // Language tags are case insensitive (RFC 5646 section 2.1.1). - return (internalIntlRegExps.languageTagRE = RegExpCreate(languageTag, "i")); -} + // extlang = 3ALPHA ; selected ISO 639 codes + // *2("-" 3ALPHA) ; permanently reserved + if (token === ALPHA && tokenLength === 3) { + extlang1 = tokenStringLower(); + if (!nextToken()) + return null; + if (token === ALPHA && tokenLength === 3) { + extlang2 = tokenStringLower(); + if (!nextToken()) + return null; + if (token === ALPHA && tokenLength === 3) { + extlang3 = tokenStringLower(); + if (!nextToken()) + return null; + } + } + } + } else { + assert(4 <= tokenLength && tokenLength <= 8, "reserved/registered language subtags"); + language = tokenStringLower(); + if (!nextToken()) + return null; + } + + // script = 4ALPHA ; ISO 15924 code + if (tokenLength === 4 && token === ALPHA) { + script = tokenStringLower(); + if (!nextToken()) + return null; + } + + // region = 2ALPHA ; ISO 3166-1 code + // / 3DIGIT ; UN M.49 code + if ((tokenLength === 2 && token === ALPHA) || (tokenLength === 3 && token === DIGIT)) { + region = tokenStringLower(); + if (!nextToken()) + return null; + } + + // variant = 5*8alphanum ; registered variants + // / (DIGIT 3alphanum) + // + // RFC 5646 section 2.1 + // alphanum = (ALPHA / DIGIT) ; letters and numbers + while ((5 <= tokenLength && tokenLength <= 8) || + (tokenLength === 4 && tokenStartCodeUnitLower() <= DIGIT_NINE)) + { + assert(!(tokenStartCodeUnitLower() <= DIGIT_NINE) || + tokenStartCodeUnitLower() >= DIGIT_ZERO, + "token-start-code-unit <= '9' implies token-start-code-unit is in '0'..'9'"); + + // Language tags are case insensitive (RFC 5646 section 2.1.1). + // All seen variants are compared ignoring case differences by + // using the lower-case form. This allows to properly detect and + // reject variant repetitions with differing case, e.g. + // "en-variant-Variant". + var variant = tokenStringLower(); -function getDuplicateVariantRE() { - if (internalIntlRegExps.duplicateVariantRE) - return internalIntlRegExps.duplicateVariantRE; + // Reject the language tag if a duplicate variant was found. + // + // This linear-time verification step means the whole variant + // subtag checking is potentially quadratic, but we're okay doing + // that because language tags are unlikely to be deliberately + // pathological. + if (callFunction(ArrayIndexOf, variants, variant) !== -1) + return null; + _DefineDataProperty(variants, variants.length, variant); + + if (!nextToken()) + return null; + } + + // extension = singleton 1*("-" (2*8alphanum)) + // singleton = DIGIT ; 0 - 9 + // / %x41-57 ; A - W + // / %x59-5A ; Y - Z + // / %x61-77 ; a - w + // / %x79-7A ; y - z + var seenSingletons = []; + while (tokenLength === 1) { + var extensionStart = tokenStart; + var singleton = tokenStartCodeUnitLower(); + if (singleton === LOWER_X) + break; + + // Language tags are case insensitive (RFC 5646 section 2.1.1). + // Ensure |tokenStartCodeUnitLower()| does not return the code + // unit of an upper-case character, so we can properly detect and + // reject language tags with different case, e.g. "en-u-foo-U-foo". + assert(!(UPPER_A <= singleton && singleton <= UPPER_Z), + "unexpected upper-case code unit"); - // RFC 5234 section B.1 - // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - var ALPHA = "[a-zA-Z]"; - // DIGIT = %x30-39 - // ; 0-9 - var DIGIT = "[0-9]"; + // Reject the input if a duplicate singleton was found. + // + // Similar to the variant validation step this check is O(n**2), + // but given that there are only 35 possible singletons the + // quadratic runtime is negligible. + if (callFunction(ArrayIndexOf, seenSingletons, singleton) !== -1) + return null; + _DefineDataProperty(seenSingletons, seenSingletons.length, singleton); + + if (!nextToken()) + return null; + + if (!(2 <= tokenLength && tokenLength <= 8)) + return null; + do { + if (!nextToken()) + return null; + } while (2 <= tokenLength && tokenLength <= 8); - // RFC 5646 section 2.1 - // alphanum = (ALPHA / DIGIT) ; letters and numbers - var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; - // variant = 5*8alphanum ; registered variants - // / (DIGIT 3alphanum) - var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; + var extension = Substring(localeLowercase, extensionStart, + (tokenStart - 1 - extensionStart)); + _DefineDataProperty(extensions, extensions.length, extension); + } + } + + // Either trailing privateuse component of the langtag production or + // standalone privateuse tag. + // + // privateuse = "x" 1*("-" (1*8alphanum)) + if (tokenLength === 1 && tokenStartCodeUnitLower() === LOWER_X) { + var privateuseStart = tokenStart; + if (!nextToken()) + return null; + + if (!(1 <= tokenLength && tokenLength <= 8)) + return null; + do { + if (!nextToken()) + return null; + } while (1 <= tokenLength && tokenLength <= 8); + + privateuse = Substring(localeLowercase, privateuseStart, + localeLowercase.length - privateuseStart); + } - // Match a langtag that contains a duplicate variant. - var duplicateVariant = - // Match everything in a langtag prior to any variants, and maybe some - // of the variants as well (which makes this pattern inefficient but - // not wrong, for our purposes); - "^(?:" + alphanum + "{2,8}-)+" + - // a variant, parenthesised so that we can refer back to it later; - "(" + variant + ")-" + - // zero or more subtags at least two characters long (thus stopping - // before extension and privateuse components); - "(?:" + alphanum + "{2,8}-)*" + - // and the same variant again - "\\1" + - // ...but not followed by any characters that would turn it into a - // different subtag. - "(?!" + alphanum + ")"; + // Return if the complete input was successfully parsed. That means it is + // either a langtag or privateuse-only language tag, or it is a regular + // grandfathered language tag. + if (token === NONE) { + return { + locale: localeLowercase, + language, + extlang1, + extlang2, + extlang3, + script, + region, + variants, + extensions, + privateuse, + }; + } + + // Before we can compare the lower-case form of locale to the list of + // grandfathered language tags, we need to ensure any remaining parts are + // alphanum-only ASCII characters. This step is necessary because locale + // could include other characters which lower-case map into ASCII + // characters. + // For example we need to reject "i-ha\u212A" (U+212A KELVIN SIGN) even + // though its lower-case form "i-hak" matches a grandfathered language + // tag. + do { + if (!nextToken()) + return null; + } while (token !== NONE); - // Language tags are case insensitive (RFC 5646 section 2.1.1). Using - // character classes covering both upper- and lower-case characters nearly - // addresses this -- but for the possibility of variant repetition with - // differing case, e.g. "en-variant-Variant". Use a case-insensitive - // regular expression to address this. (Note that there's no worry about - // case transformation accepting invalid characters here: users have - // already verified the string is alphanumeric Latin plus "-".) - return (internalIntlRegExps.duplicateVariantRE = RegExpCreate(duplicateVariant, "i")); + // grandfathered = irregular ; non-redundant tags registered + // / regular ; during the RFC 3066 era + switch (localeLowercase) { +#ifdef DEBUG + // regular = "art-lojban" ; these tags match the 'langtag' + // / "cel-gaulish" ; production, but their subtags + // / "no-bok" ; are not extended language + // / "no-nyn" ; or variant subtags: their meaning + // / "zh-guoyu" ; is defined by their registration + // / "zh-hakka" ; and all of these are deprecated + // / "zh-min" ; in favor of a more modern + // / "zh-min-nan" ; subtag or sequence of subtags + // / "zh-xiang" + case "art-lojban": + case "cel-gaulish": + case "no-bok": + case "no-nyn": + case "zh-guoyu": + case "zh-hakka": + case "zh-min": + case "zh-min-nan": + case "zh-xiang": + assert(false, "regular grandfathered tags should have been matched above"); +#endif /* DEBUG */ + + // irregular = "en-GB-oed" ; irregular tags do not match + // / "i-ami" ; the 'langtag' production and + // / "i-bnn" ; would not otherwise be + // / "i-default" ; considered 'well-formed' + // / "i-enochian" ; These tags are all valid, + // / "i-hak" ; but most are deprecated + // / "i-klingon" ; in favor of more modern + // / "i-lux" ; subtags or subtag + // / "i-mingo" ; combination + // / "i-navajo" + // / "i-pwn" + // / "i-tao" + // / "i-tay" + // / "i-tsu" + // / "sgn-BE-FR" + // / "sgn-BE-NL" + // / "sgn-CH-DE" + case "en-gb-oed": + case "i-ami": + case "i-bnn": + case "i-default": + case "i-enochian": + case "i-hak": + case "i-klingon": + case "i-lux": + case "i-mingo": + case "i-navajo": + case "i-pwn": + case "i-tao": + case "i-tay": + case "i-tsu": + case "sgn-be-fr": + case "sgn-be-nl": + case "sgn-ch-de": + return { locale: localeLowercase, grandfathered: true }; + + default: + return null; + } + + #undef NONE + #undef ALPHA + #undef DIGIT + #undef HYPHEN + #undef DIGIT_ZERO + #undef DIGIT_NINE + #undef UPPER_A + #undef UPPER_Z + #undef LOWER_A + #undef LOWER_X + #undef LOWER_Z } - -function getDuplicateSingletonRE() { - if (internalIntlRegExps.duplicateSingletonRE) - return internalIntlRegExps.duplicateSingletonRE; - - // RFC 5234 section B.1 - // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - var ALPHA = "[a-zA-Z]"; - // DIGIT = %x30-39 - // ; 0-9 - var DIGIT = "[0-9]"; - - // RFC 5646 section 2.1 - // alphanum = (ALPHA / DIGIT) ; letters and numbers - var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; - // singleton = DIGIT ; 0 - 9 - // / %x41-57 ; A - W - // / %x59-5A ; Y - Z - // / %x61-77 ; a - w - // / %x79-7A ; y - z - var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; - - // Match a langtag that contains a duplicate singleton. - var duplicateSingleton = - // Match a singleton subtag, parenthesised so that we can refer back to - // it later; - "-(" + singleton + ")-" + - // then zero or more subtags; - "(?:" + alphanum + "+-)*" + - // and the same singleton again - "\\1" + - // ...but not followed by any characters that would turn it into a - // different subtag. - "(?!" + alphanum + ")"; - - // Language tags are case insensitive (RFC 5646 section 2.1.1). Using - // character classes covering both upper- and lower-case characters nearly - // addresses this -- but for the possibility of singleton repetition with - // differing case, e.g. "en-u-foo-U-foo". Use a case-insensitive regular - // expression to address this. (Note that there's no worry about case - // transformation accepting invalid characters here: users have already - // verified the string is alphanumeric Latin plus "-".) - return (internalIntlRegExps.duplicateSingletonRE = RegExpCreate(duplicateSingleton, "i")); -} +/* eslint-enable complexity */ /** * Verifies that the given string is a well-formed BCP 47 language tag * with no duplicate variant or singleton subtags. * * Spec: ECMAScript Internationalization API Specification, 6.2.2. */ function IsStructurallyValidLanguageTag(locale) { - assert(typeof locale === "string", "IsStructurallyValidLanguageTag"); - var languageTagRE = getLanguageTagRE(); - if (!regexp_test_no_statics(languageTagRE, locale)) - return false; - - // Before checking for duplicate variant or singleton subtags with - // regular expressions, we have to get private use subtag sequences - // out of the picture. - if (callFunction(std_String_startsWith, locale, "x-")) - return true; - var pos = callFunction(std_String_indexOf, locale, "-x-"); - if (pos !== -1) - locale = callFunction(String_substring, locale, 0, pos); - - // Check for duplicate variant or singleton subtags. - var duplicateVariantRE = getDuplicateVariantRE(); - var duplicateSingletonRE = getDuplicateSingletonRE(); - return !regexp_test_no_statics(duplicateVariantRE, locale) && - !regexp_test_no_statics(duplicateSingletonRE, locale); + return parseLanguageTag(locale) !== null; } /** - * Joins the array elements in the given range with the supplied separator. + * Canonicalizes the given structurally valid BCP 47 language tag, including + * regularized case of subtags. For example, the language tag + * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where + * + * Zh ; 2*3ALPHA + * -NAN ; ["-" extlang] + * -haNS ; ["-" script] + * -bu ; ["-" region] + * -variant2 ; *("-" variant) + * -Variant1 + * -u-ca-chinese ; *("-" extension) + * -t-Zh-laTN + * -x-PRIVATE ; ["-" privateuse] + * + * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private + * + * Spec: ECMAScript Internationalization API Specification, 6.2.3. + * Spec: RFC 5646, section 4.5. */ -function ArrayJoinRange(array, separator, from, to = array.length) { - assert(typeof separator === "string", "|separator| is a string value"); - assert(typeof from === "number", "|from| is a number value"); - assert(typeof to === "number", "|to| is a number value"); - assert(0 <= from && from <= to && to <= array.length, "|from| and |to| form a valid range"); +function CanonicalizeLanguageTagFromObject(localeObj) { + assert(IsObject(localeObj), "CanonicalizeLanguageTagFromObject"); + + var {locale} = localeObj; + assert(locale === callFunction(std_String_toLowerCase, locale), + "expected lower-case form for locale string"); + + // Handle mappings for complete tags. + if (hasOwn(locale, langTagMappings)) + return langTagMappings[locale]; + + assert(!hasOwn("grandfathered", localeObj), + "grandfathered tags should be mapped completely"); + + var { + language, + extlang1, + extlang2, + extlang3, + script, + region, + variants, + extensions, + privateuse, + } = localeObj; + + // Be careful of a Language-Tag that is entirely privateuse. + if (!language) { + assert(typeof privateuse === "string", "language or privateuse subtag required"); + return privateuse; + } + + // Replace deprecated language tags with their preferred values. + // "in" -> "id" + if (hasOwn(language, languageMappings)) + language = languageMappings[language]; + + var canonical = language; - if (from === to) - return ""; + if (extlang1) { + // When an extlang subtag is encountered with its corresponding + // primary language tag prefix, replace the combination with the + // preferred value -- which MUST be the unadorned extlang subtag. + // For example, this entry + // + // Type: extlang + // Subtag: nan + // Description: Min Nan Chinese + // Added: 2009-07-29 + // Preferred-Value: nan + // Prefix: zh + // Macrolanguage: zh + // + // is interpreted to say that if a "nan" extlang appears after a "zh" + // primary language prefix, the extlang and its prefix must be + // replaced by its preferred value, so "zh-nan" must be replaced by + // the preferred value "nan". (RFC 5646 section 2.2.2) + if (hasOwn(extlang1, extlangMappings) && extlangMappings[extlang1] === language) + canonical = extlang1; + else + canonical += "-" + extlang1; + } + + // The second extlang subtag will always be left as is. + // (RFC 5646 section 2.2.2) + if (extlang2) + canonical += "-" + extlang2; + + // The third extlang subtag will always be left as is. + // (RFC 5646 section 2.2.2) + if (extlang3) + canonical += "-" + extlang3; - var result = array[from]; - for (var i = from + 1; i < to; i++) { - result += separator + array[i]; + if (script) { + // The first character of a script code needs to be capitalized. + // "hans" -> "Hans" + script = callFunction(std_String_toUpperCase, script[0]) + + Substring(script, 1, script.length - 1); + + // No script replacements are currently present, so append as is. + canonical += "-" + script; } - return result; + + if (region) { + // Region codes need to be in upper-case. "bu" -> "BU" + region = callFunction(std_String_toUpperCase, region); + + // Replace deprecated subtags with their preferred values. + // "BU" -> "MM" + if (hasOwn(region, regionMappings)) + region = regionMappings[region]; + + canonical += "-" + region; + } + + // No variant replacements are currently present, so append as is. + if (variants.length > 0) + canonical += "-" + callFunction(std_Array_join, variants, "-"); + + if (extensions.length > 0) { + // Extension sequences are sorted by their singleton characters. + // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese" + callFunction(ArraySort, extensions); + + canonical += "-" + callFunction(std_Array_join, extensions, "-"); + } + + // Private use sequences are left as is. "x-private" + if (privateuse) + canonical += "-" + privateuse; + + return canonical; } /** * Canonicalizes the given structurally valid BCP 47 language tag, including * regularized case of subtags. For example, the language tag * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where * * Zh ; 2*3ALPHA @@ -320,129 +636,20 @@ function ArrayJoinRange(array, separator * -x-PRIVATE ; ["-" privateuse] * * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private * * Spec: ECMAScript Internationalization API Specification, 6.2.3. * Spec: RFC 5646, section 4.5. */ function CanonicalizeLanguageTag(locale) { - assert(IsStructurallyValidLanguageTag(locale), "CanonicalizeLanguageTag"); - - // The input - // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" - // will be used throughout this method to illustrate how it works. - - // Language tags are compared and processed case-insensitively, so - // technically it's not necessary to adjust case. But for easier processing, - // and because the canonical form for most subtags is lower case, we start - // with lower case for all. - // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" -> - // "zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private" - locale = callFunction(std_String_toLowerCase, locale); - - // Handle mappings for complete tags. - if (hasOwn(locale, langTagMappings)) - return langTagMappings[locale]; - - var subtags = StringSplitString(locale, "-"); - var i = 0; - - // Handle the standard part: All subtags before the first singleton or "x". - // "zh-nan-hans-bu-variant2-variant1" - while (i < subtags.length) { - var subtag = subtags[i]; - - // If we reach the start of an extension sequence or private use part, - // we're done with this loop. We have to check for i > 0 because for - // irregular language tags, such as i-klingon, the single-character - // subtag "i" is not the start of an extension sequence. - // In the example, we break at "u". - if (subtag.length === 1 && (i > 0 || subtag === "x")) - break; + var localeObj = parseLanguageTag(locale); + assert(localeObj !== null, "CanonicalizeLanguageTag"); - if (i !== 0) { - if (subtag.length === 4) { - // 4-character subtags that are not in initial position are - // script codes; their first character needs to be capitalized. - // "hans" -> "Hans" - subtag = callFunction(std_String_toUpperCase, subtag[0]) + - callFunction(String_substring, subtag, 1); - } else if (subtag.length === 2) { - // 2-character subtags that are not in initial position are - // region codes; they need to be upper case. "bu" -> "BU" - subtag = callFunction(std_String_toUpperCase, subtag); - } - } - if (hasOwn(subtag, langSubtagMappings)) { - // Replace deprecated subtags with their preferred values. - // "BU" -> "MM" - // This has to come after we capitalize region codes because - // otherwise some language and region codes could be confused. - // For example, "in" is an obsolete language code for Indonesian, - // but "IN" is the country code for India. - // Note that the script generating langSubtagMappings makes sure - // that no regular subtag mapping will replace an extlang code. - subtag = langSubtagMappings[subtag]; - } else if (hasOwn(subtag, extlangMappings)) { - // Replace deprecated extlang subtags with their preferred values, - // and remove the preceding subtag if it's a redundant prefix. - // "zh-nan" -> "nan" - // Note that the script generating extlangMappings makes sure that - // no extlang mapping will replace a normal language code. - // The preferred value for all current deprecated extlang subtags - // is equal to the extlang subtag, so we only need remove the - // redundant prefix to get the preferred value. - if (i === 1 && extlangMappings[subtag] === subtags[0]) { - callFunction(std_Array_shift, subtags); - i--; - } - } - subtags[i] = subtag; - i++; - } - - // Directly return when the language tag doesn't contain any extension or - // private use sub-tags. - if (i === subtags.length) - return callFunction(std_Array_join, subtags, "-"); - - var normal = ArrayJoinRange(subtags, "-", 0, i); - - // Extension sequences are sorted by their singleton characters. - // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese" - var extensions = []; - while (i < subtags.length && subtags[i] !== "x") { - var extensionStart = i; - i++; - while (i < subtags.length && subtags[i].length > 1) - i++; - var extension = ArrayJoinRange(subtags, "-", extensionStart, i); - _DefineDataProperty(extensions, extensions.length, extension); - } - callFunction(ArraySort, extensions); - - // Private use sequences are left as is. "x-private" - var privateUse = ""; - if (i < subtags.length) - privateUse = ArrayJoinRange(subtags, "-", i); - - // Put everything back together. - var canonical = normal; - if (extensions.length > 0) - canonical += "-" + callFunction(std_Array_join, extensions, "-"); - if (privateUse.length > 0) { - // Be careful of a Language-Tag that is entirely privateuse. - if (canonical.length > 0) - canonical += "-" + privateUse; - else - canonical = privateUse; - } - - return canonical; + return CanonicalizeLanguageTagFromObject(localeObj); } /** * Returns true if the input contains only ASCII alphabetical characters. */ function IsASCIIAlphaString(s) { assert(typeof s === "string", "IsASCIIAlphaString"); @@ -475,28 +682,29 @@ function ValidateAndCanonicalizeLanguage // The language subtag is canonicalized to lower case. locale = callFunction(std_String_toLowerCase, locale); // langTagMappings doesn't contain any 2*3ALPHA keys, so we don't need // to check for possible replacements in this map. assert(!hasOwn(locale, langTagMappings), "langTagMappings contains no 2*3ALPHA mappings"); // Replace deprecated subtags with their preferred values. - locale = hasOwn(locale, langSubtagMappings) - ? langSubtagMappings[locale] + locale = hasOwn(locale, languageMappings) + ? languageMappings[locale] : locale; assert(locale === CanonicalizeLanguageTag(locale), "expected same canonicalization"); return locale; } - if (!IsStructurallyValidLanguageTag(locale)) + var localeObj = parseLanguageTag(locale); + if (localeObj === null) ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, locale); - return CanonicalizeLanguageTag(locale); + return CanonicalizeLanguageTagFromObject(localeObj); } function localeContainsNoUnicodeExtensions(locale) { // No "-u-", no possible Unicode extension. if (callFunction(std_String_indexOf, locale, "-u-") === -1) return true; // "-u-" within privateuse also isn't one. @@ -551,21 +759,21 @@ var localeCache = { */ function DefaultLocaleIgnoringAvailableLocales() { const runtimeDefaultLocale = RuntimeDefaultLocale(); if (runtimeDefaultLocale === localeCandidateCache.runtimeDefaultLocale) return localeCandidateCache.candidateDefaultLocale; // If we didn't get a cache hit, compute the candidate default locale and // cache it. Fall back on the last-ditch locale when necessary. - var candidate; - if (!IsStructurallyValidLanguageTag(runtimeDefaultLocale)) { + var candidate = parseLanguageTag(runtimeDefaultLocale); + if (candidate === null) { candidate = lastDitchLocale(); } else { - candidate = CanonicalizeLanguageTag(runtimeDefaultLocale); + candidate = CanonicalizeLanguageTagFromObject(candidate); // The default locale must be in [[availableLocales]], and that list // must not contain any locales with Unicode extension sequences, so // remove any present in the candidate. candidate = removeUnicodeExtensions(candidate); if (hasOwn(candidate, oldStyleLanguageTagMappings)) candidate = oldStyleLanguageTagMappings[candidate]; @@ -653,36 +861,31 @@ function addSpecialMissingLanguageTags(a /** * Canonicalizes a locale list. * * Spec: ECMAScript Internationalization API Specification, 9.2.1. */ function CanonicalizeLocaleList(locales) { if (locales === undefined) return []; - if (typeof locales === "string") { - if (!IsStructurallyValidLanguageTag(locales)) - ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, locales); - return [CanonicalizeLanguageTag(locales)]; - } + if (typeof locales === "string") + return [ValidateAndCanonicalizeLanguageTag(locales)]; var seen = []; var O = ToObject(locales); var len = ToLength(O.length); var k = 0; while (k < len) { // Don't call ToString(k) - SpiderMonkey is faster with integers. var kPresent = HasProperty(O, k); if (kPresent) { var kValue = O[k]; if (!(typeof kValue === "string" || IsObject(kValue))) ThrowTypeError(JSMSG_INVALID_LOCALES_ELEMENT); var tag = ToString(kValue); - if (!IsStructurallyValidLanguageTag(tag)) - ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, tag); - tag = CanonicalizeLanguageTag(tag); + tag = ValidateAndCanonicalizeLanguageTag(tag); if (callFunction(ArrayIndexOf, seen, tag) === -1) _DefineDataProperty(seen, seen.length, tag); } k++; } return seen; }
--- a/js/src/builtin/intl/LangTagMappingsGenerated.js +++ b/js/src/builtin/intl/LangTagMappingsGenerated.js @@ -53,26 +53,20 @@ var langTagMappings = { "zh-hakka": "hak", "zh-min": "zh-min", "zh-min-nan": "nan", "zh-wuu": "wuu", "zh-xiang": "hsn", "zh-yue": "yue", }; -// Mappings from non-extlang subtags to preferred values. +// Mappings from language subtags to preferred values. // Derived from IANA Language Subtag Registry, file date 2018-01-25. // https://www.iana.org/assignments/language-subtag-registry -var langSubtagMappings = { - "BU": "MM", - "DD": "DE", - "FX": "FR", - "TP": "TL", - "YD": "YE", - "ZR": "CD", +var languageMappings = { "aam": "aas", "adp": "dz", "aue": "ktz", "ayx": "nun", "bgm": "bcg", "bjd": "drl", "ccq": "rki", "cjr": "mom", @@ -142,16 +136,28 @@ var langSubtagMappings = { "xsj": "suj", "ybd": "rki", "yma": "lrr", "ymt": "mtm", "yos": "zom", "yuu": "yug", }; +// Mappings from region subtags to preferred values. +// Derived from IANA Language Subtag Registry, file date 2018-01-25. +// https://www.iana.org/assignments/language-subtag-registry +var regionMappings = { + "BU": "MM", + "DD": "DE", + "FX": "FR", + "TP": "TL", + "YD": "YE", + "ZR": "CD", +}; + // Mappings from extlang subtags to preferred values. // All current deprecated extlang subtags have the form `<prefix>-<extlang>` // and their preferred value is exactly equal to `<extlang>`. So each key in // extlangMappings acts both as the extlang subtag and its preferred value. // Derived from IANA Language Subtag Registry, file date 2018-01-25. // https://www.iana.org/assignments/language-subtag-registry var extlangMappings = { "aao": "ar",
--- a/js/src/builtin/intl/NumberFormat.js +++ b/js/src/builtin/intl/NumberFormat.js @@ -186,27 +186,20 @@ function toASCIIUpperCase(s) { return result; } /** * Verifies that the given string is a well-formed ISO 4217 currency code. * * Spec: ECMAScript Internationalization API Specification, 6.3.1. */ -function getIsWellFormedCurrencyCodeRE() { - return internalIntlRegExps.isWellFormedCurrencyCodeRE || - (internalIntlRegExps.isWellFormedCurrencyCodeRE = RegExpCreate("[^A-Z]")); -} +function IsWellFormedCurrencyCode(currency) { + assert(typeof currency === "string", "currency is a string value"); -function IsWellFormedCurrencyCode(currency) { - var c = ToString(currency); - var normalized = toASCIIUpperCase(c); - if (normalized.length !== 3) - return false; - return !regexp_test_no_statics(getIsWellFormedCurrencyCodeRE(), normalized); + return currency.length === 3 && IsASCIIAlphaString(currency); } /** * Initializes an object as a NumberFormat. * * This method is complicated a moderate bit by its implementing initialization * as a *lazy* concept. Everything that must happen now, does -- but we defer * all the work we can until the object is actually used as a NumberFormat. @@ -334,23 +327,20 @@ function InitializeNumberFormat(numberFo return numberFormat; } /** * Returns the number of decimal digits to be used for the given currency. * * Spec: ECMAScript Internationalization API Specification, 11.1.1. */ -function getCurrencyDigitsRE() { - return internalIntlRegExps.currencyDigitsRE || - (internalIntlRegExps.currencyDigitsRE = RegExpCreate("^[A-Z]{3}$")); -} function CurrencyDigits(currency) { - assert(typeof currency === "string", "CurrencyDigits"); - assert(regexp_test_no_statics(getCurrencyDigitsRE(), currency), "CurrencyDigits"); + assert(typeof currency === "string", "currency is a string value"); + assert(IsWellFormedCurrencyCode(currency), "currency is well-formed"); + assert(currency == toASCIIUpperCase(currency), "currency is all upper-case"); if (hasOwn(currency, currencyDigits)) return currencyDigits[currency]; return 2; } /** * Returns the subset of the given locale list for which this locale list has a
--- a/js/src/builtin/intl/make_intl_data.py +++ b/js/src/builtin/intl/make_intl_data.py @@ -71,29 +71,30 @@ def readRegistryRecord(registry): def readRegistry(registry): """ Reads IANA Language Subtag Registry and extracts information for Intl.js. Information extracted: - langTagMappings: mappings from complete language tags to preferred complete language tags - - langSubtagMappings: mappings from subtags to preferred subtags + - languageMappings: mappings from language subtags to preferred subtags + - regionMappings: mappings from region subtags to preferred subtags - extlangMappings: mappings from extlang subtags to preferred subtags, with prefix to be removed - Returns these three mappings as dictionaries, along with the registry's + Returns these four mappings as dictionaries, along with the registry's file date. - We also check that mappings for language subtags don't affect extlang - subtags and vice versa, so that CanonicalizeLanguageTag doesn't have - to separate them for processing. Region codes are separated by case, - and script codes by length, so they're unproblematic. + We also check that extlang mappings don't generate preferred values + which in turn are subject to language subtag mappings, so that + CanonicalizeLanguageTag can process subtags sequentially. """ langTagMappings = {} - langSubtagMappings = {} + languageMappings = {} + regionMappings = {} extlangMappings = {} languageSubtags = set() extlangSubtags = set() for record in readRegistryRecord(registry): if "File-Date" in record: fileDate = record["File-Date"] continue @@ -109,65 +110,84 @@ def readRegistry(registry): langTagMappings[tag.lower()] = record["Preferred-Value"] else: langTagMappings[tag.lower()] = tag elif record["Type"] == "redundant": # For langTagMappings, keys must be in lower case; values in # the case used in the registry. if "Preferred-Value" in record: langTagMappings[record["Tag"].lower()] = record["Preferred-Value"] - elif record["Type"] in ("language", "script", "region", "variant"): - # For langSubtagMappings, keys and values must be in the case used + elif record["Type"] == "language": + # For languageMappings, keys and values must be in the case used # in the registry. subtag = record["Subtag"] - if record["Type"] == "language": - languageSubtags.add(subtag) + languageSubtags.add(subtag) + if "Preferred-Value" in record: + # The 'Prefix' field is not allowed for language records. + # https://tools.ietf.org/html/rfc5646#section-3.1.2 + assert "Prefix" not in record, "language subtags can't have a prefix" + languageMappings[subtag] = record["Preferred-Value"] + elif record["Type"] == "region": + # For regionMappings, keys and values must be in the case used in + # the registry. + subtag = record["Subtag"] + if "Preferred-Value" in record: + # The 'Prefix' field is not allowed for region records. + # https://tools.ietf.org/html/rfc5646#section-3.1.2 + assert "Prefix" not in record, "region subtags can't have a prefix" + regionMappings[subtag] = record["Preferred-Value"] + elif record["Type"] == "script": + if "Preferred-Value" in record: + # The registry currently doesn't contain mappings for scripts. + raise Exception("Unexpected mapping for script subtags") + elif record["Type"] == "variant": + subtag = record["Subtag"] if "Preferred-Value" in record: if subtag == "heploc": # The entry for heploc is unique in its complexity; handle # it as special case below. continue - if "Prefix" in record: - # This might indicate another heploc-like complex case. - raise Exception("Please evaluate: subtag mapping with prefix value.") - langSubtagMappings[subtag] = record["Preferred-Value"] + # The registry currently doesn't contain mappings for variants, + # except for heploc which is already handled above. + raise Exception("Unexpected mapping for variant subtags") elif record["Type"] == "extlang": # For extlangMappings, keys must be in the case used in the # registry; values are records with the preferred value and the # prefix to be removed. subtag = record["Subtag"] extlangSubtags.add(subtag) if "Preferred-Value" in record: preferred = record["Preferred-Value"] + # The 'Preferred-Value' and 'Subtag' fields MUST be identical. + # https://tools.ietf.org/html/rfc5646#section-2.2.2 + assert preferred == subtag, "{0} = {1}".format(preferred, subtag) prefix = record["Prefix"] extlangMappings[subtag] = {"preferred": preferred, "prefix": prefix} else: # No other types are allowed by # https://tools.ietf.org/html/rfc5646#section-3.1.3 assert False, "Unrecognized Type: {0}".format(record["Type"]) # Check that mappings for language subtags and extlang subtags don't affect # each other. - for lang in languageSubtags: - if lang in extlangMappings and extlangMappings[lang]["preferred"] != lang: - raise Exception("Conflict: lang with extlang mapping: " + lang) for extlang in extlangSubtags: - if extlang in langSubtagMappings: + if extlang in languageMappings: raise Exception("Conflict: extlang with lang mapping: " + extlang) # Special case for heploc. langTagMappings["ja-latn-hepburn-heploc"] = "ja-Latn-alalc97" - # ValidateAndCanonicalizeLanguageTag in Intl.js expects langTagMappings - # contains no 2*3ALPHA. + # ValidateAndCanonicalizeLanguageTag in CommonFunctions.js expects + # langTagMappings contains no 2*3ALPHA. assert all(len(lang) > 3 for lang in langTagMappings.iterkeys()) return {"fileDate": fileDate, "langTagMappings": langTagMappings, - "langSubtagMappings": langSubtagMappings, + "languageMappings": languageMappings, + "regionMappings": regionMappings, "extlangMappings": extlangMappings} def writeMappingsVar(intlData, dict, name, description, fileDate, url): """ Writes a variable definition with a mapping table to file intlData. Writes the contents of dictionary dict to file intlData with the given variable name and a comment with description, fileDate, and URL. @@ -189,22 +209,25 @@ def writeMappingsVar(intlData, dict, nam prefix = dict[key]["prefix"] if key != preferred: raise Exception("Expected '{0}' matches preferred locale '{1}'".format(key, preferred)) value = '"{0}"'.format(prefix) intlData.write(' "{0}": {1},\n'.format(key, value)) intlData.write("};\n") -def writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings): +def writeLanguageTagData(intlData, fileDate, url, langTagMappings, languageMappings, + regionMappings, extlangMappings): """ Writes the language tag data to the Intl data file. """ writeMappingsVar(intlData, langTagMappings, "langTagMappings", "Mappings from complete tags to preferred values.", fileDate, url) - writeMappingsVar(intlData, langSubtagMappings, "langSubtagMappings", - "Mappings from non-extlang subtags to preferred values.", fileDate, url) + writeMappingsVar(intlData, languageMappings, "languageMappings", + "Mappings from language subtags to preferred values.", fileDate, url) + writeMappingsVar(intlData, regionMappings, "regionMappings", + "Mappings from region subtags to preferred values.", fileDate, url) writeMappingsVar(intlData, extlangMappings, "extlangMappings", ["Mappings from extlang subtags to preferred values.", "All current deprecated extlang subtags have the form `<prefix>-<extlang>`", "and their preferred value is exactly equal to `<extlang>`. So each key in", "extlangMappings acts both as the extlang subtag and its preferred value."], fileDate, url) def updateLangTags(args): @@ -230,23 +253,25 @@ def updateLangTags(args): registry.write(text) registry.seek(0) print("Processing IANA Language Subtag Registry...") with closing(registry) as reg: data = readRegistry(reg) fileDate = data["fileDate"] langTagMappings = data["langTagMappings"] - langSubtagMappings = data["langSubtagMappings"] + languageMappings = data["languageMappings"] + regionMappings = data["regionMappings"] extlangMappings = data["extlangMappings"] print("Writing Intl data...") with codecs.open(out, "w", encoding="utf-8") as intlData: intlData.write("// Generated by make_intl_data.py. DO NOT EDIT.\n") - writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings) + writeLanguageTagData(intlData, fileDate, url, langTagMappings, languageMappings, + regionMappings, extlangMappings) def flines(filepath, encoding="utf-8"): """ Open filepath and iterate over its content. """ with io.open(filepath, mode="r", encoding=encoding) as f: for line in f: yield line class Zone:
--- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -41,17 +41,16 @@ #include "builtin/TypedObject.h" #include "ctypes/Library.h" #include "gc/FreeOp.h" #include "gc/Policy.h" #include "gc/Zone.h" #include "jit/AtomicOperations.h" #include "js/Vector.h" -#include "jsatominlines.h" #include "jsobjinlines.h" using namespace std; using JS::AutoCheckCannotGC; namespace js { namespace ctypes {
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -14,34 +14,32 @@ #include "mozilla/DebugOnly.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include <string.h> #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsfun.h" #include "jsnum.h" #include "jsopcode.h" #include "jsscript.h" #include "jstypes.h" #include "jsutil.h" #include "ds/Nestable.h" #include "frontend/Parser.h" #include "frontend/TokenStream.h" #include "vm/Debugger.h" #include "vm/GeneratorObject.h" #include "vm/Stack.h" #include "wasm/AsmJS.h" -#include "jsatominlines.h" #include "jsscriptinlines.h" #include "frontend/ParseNode-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::gc;
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -19,33 +19,31 @@ #include "frontend/Parser.h" #include "mozilla/Range.h" #include "mozilla/Sprintf.h" #include "mozilla/TypeTraits.h" #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsfun.h" #include "jsopcode.h" #include "jsscript.h" #include "jstypes.h" #include "builtin/ModuleObject.h" #include "builtin/SelfHostingDefines.h" #include "frontend/BytecodeCompiler.h" #include "frontend/FoldConstants.h" #include "frontend/TokenStream.h" #include "irregexp/RegExpParser.h" #include "vm/RegExpObject.h" #include "wasm/AsmJS.h" -#include "jsatominlines.h" #include "jsscriptinlines.h" #include "frontend/ParseContext-inl.h" #include "frontend/ParseNode-inl.h" #include "vm/EnvironmentObject-inl.h" using namespace js; using namespace js::gc;
--- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -2,17 +2,16 @@ * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 frontend_SharedContext_h #define frontend_SharedContext_h -#include "jsatom.h" #include "jsopcode.h" #include "jspubtd.h" #include "jsscript.h" #include "jstypes.h" #include "builtin/ModuleObject.h" #include "ds/InlineTable.h" #include "frontend/ParseNode.h"
--- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -13,17 +13,16 @@ #include "mozilla/PodOperations.h" #include "mozilla/ScopeExit.h" #include <ctype.h> #include <stdarg.h> #include <stdio.h> #include <string.h> -#include "jsatom.h" #include "jscntxt.h" #include "jscompartment.h" #include "jsexn.h" #include "jsnum.h" #include "frontend/BytecodeCompiler.h" #include "frontend/Parser.h" #include "frontend/ReservedWords.h"
--- a/js/src/gc/AllocKind.h +++ b/js/src/gc/AllocKind.h @@ -13,16 +13,17 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/EnumeratedArray.h" #include "mozilla/EnumeratedRange.h" #include <stdint.h> #include "js/TraceKind.h" +#include "js/Utility.h" namespace js { namespace gc { // The GC allocation kinds. // // These are defined by macros which enumerate the different allocation kinds // and supply the following information:
--- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -30,21 +30,24 @@ void FinishGC(JSContext* cx); * heap in order to trace through it... */ class MOZ_RAII AutoTraceSession { public: explicit AutoTraceSession(JSRuntime* rt, JS::HeapState state = JS::HeapState::Tracing); ~AutoTraceSession(); - // Threads with an exclusive context can hit refillFreeList while holding - // the exclusive access lock. To avoid deadlocking when we try to acquire - // this lock during GC and the other thread is waiting, make sure we hold - // the exclusive access lock during GC sessions. - AutoLockForExclusiveAccess lock; + // Constructing an AutoTraceSession takes the exclusive access lock, but GC + // may release it during a trace session if we're not collecting the atoms + // zone. + mozilla::Maybe<AutoLockForExclusiveAccess> maybeLock; + + AutoLockForExclusiveAccess& lock() { + return maybeLock.ref(); + } protected: JSRuntime* runtime; private: AutoTraceSession(const AutoTraceSession&) = delete; void operator=(const AutoTraceSession&) = delete;
--- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -6,28 +6,27 @@ #ifndef gc_GCRuntime_h #define gc_GCRuntime_h #include "mozilla/Atomics.h" #include "mozilla/EnumSet.h" #include "mozilla/Maybe.h" -#include "jsatom.h" - #include "gc/ArenaList.h" #include "gc/AtomMarking.h" #include "gc/GCHelperState.h" #include "gc/GCMarker.h" #include "gc/GCParallelTask.h" #include "gc/Nursery.h" #include "gc/Statistics.h" #include "gc/StoreBuffer.h" #include "js/GCAnnotations.h" #include "js/UniquePtr.h" +#include "vm/AtomsTable.h" namespace js { class AutoLockGC; class AutoLockGCBgAlloc; class AutoLockHelperThreadState; class VerifyPreTracer; @@ -791,18 +790,18 @@ class GCRuntime void runDebugGC(); void notifyRootsRemoved(); enum TraceOrMarkRuntime { TraceRuntime, MarkRuntime }; - void traceRuntime(JSTracer* trc, AutoLockForExclusiveAccess& lock); - void traceRuntimeForMinorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock); + void traceRuntime(JSTracer* trc, AutoTraceSession& session); + void traceRuntimeForMinorGC(JSTracer* trc, AutoTraceSession& session); void shrinkBuffers(); void onOutOfMallocMemory(); void onOutOfMallocMemory(const AutoLockGC& lock); #ifdef JS_GC_ZEAL const void* addressOfZealModeBits() { return &zealModeBits; } void getZealBits(uint32_t* zealBits, uint32_t* frequency, uint32_t* nextScheduled); @@ -1053,113 +1052,113 @@ class GCRuntime friend class BackgroundAllocTask; bool wantBackgroundAllocation(const AutoLockGC& lock) const; void startBackgroundAllocTaskIfIdle(); void requestMajorGC(JS::gcreason::Reason reason); SliceBudget defaultBudget(JS::gcreason::Reason reason, int64_t millis); IncrementalResult budgetIncrementalGC(bool nonincrementalByAPI, JS::gcreason::Reason reason, - SliceBudget& budget, AutoLockForExclusiveAccess& lock); - IncrementalResult resetIncrementalGC(AbortReason reason, AutoLockForExclusiveAccess& lock); + SliceBudget& budget, AutoTraceSession& session); + IncrementalResult resetIncrementalGC(AbortReason reason, AutoTraceSession& session); // Assert if the system state is such that we should never // receive a request to do GC work. void checkCanCallAPI(); // Check if the system state is such that GC has been supressed // or otherwise delayed. MOZ_MUST_USE bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason); gcstats::ZoneGCStats scanZonesBeforeGC(); void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason) JS_HAZ_GC_CALL; MOZ_MUST_USE IncrementalResult gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason); bool shouldRepeatForDeadZone(JS::gcreason::Reason reason); void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason, - AutoLockForExclusiveAccess& lock); + AutoTraceSession& session); friend class AutoCallGCCallbacks; void maybeCallBeginCallback(); void maybeCallEndCallback(); void pushZealSelectedObjects(); - void purgeRuntime(AutoLockForExclusiveAccess& lock); - MOZ_MUST_USE bool beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock); + void purgeRuntime(); + MOZ_MUST_USE bool beginMarkPhase(JS::gcreason::Reason reason, AutoTraceSession& session); bool prepareZonesForCollection(JS::gcreason::Reason reason, bool* isFullOut, AutoLockForExclusiveAccess& lock); bool shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime, JS::gcreason::Reason reason, bool canAllocateMoreCode); - void traceRuntimeForMajorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock); + void traceRuntimeForMajorGC(JSTracer* trc, AutoTraceSession& session); void traceRuntimeAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock); void traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark, - AutoLockForExclusiveAccess& lock); + AutoTraceSession& session); void maybeDoCycleCollection(); void markCompartments(); IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::PhaseKind phase); template <class CompartmentIterT> void markWeakReferences(gcstats::PhaseKind phase); void markWeakReferencesInCurrentGroup(gcstats::PhaseKind phase); template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::PhaseKind phase); void markBufferedGrayRoots(JS::Zone* zone); void markGrayReferencesInCurrentGroup(gcstats::PhaseKind phase); void markAllWeakReferences(gcstats::PhaseKind phase); void markAllGrayReferences(gcstats::PhaseKind phase); - void beginSweepPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock); - void groupZonesForSweeping(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock); + void beginSweepPhase(JS::gcreason::Reason reason, AutoTraceSession& session); + void groupZonesForSweeping(JS::gcreason::Reason reason); MOZ_MUST_USE bool findInterZoneEdges(); void getNextSweepGroup(); IncrementalProgress endMarkingSweepGroup(FreeOp* fop, SliceBudget& budget); IncrementalProgress beginSweepingSweepGroup(FreeOp* fop, SliceBudget& budget); #ifdef JS_GC_ZEAL IncrementalProgress maybeYieldForSweepingZeal(FreeOp* fop, SliceBudget& budget); #endif bool shouldReleaseObservedTypes(); void sweepDebuggerOnMainThread(FreeOp* fop); void sweepJitDataOnMainThread(FreeOp* fop); IncrementalProgress endSweepingSweepGroup(FreeOp* fop, SliceBudget& budget); - IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock); + IncrementalProgress performSweepActions(SliceBudget& sliceBudget); IncrementalProgress sweepTypeInformation(FreeOp* fop, SliceBudget& budget, Zone* zone); IncrementalProgress releaseSweptEmptyArenas(FreeOp* fop, SliceBudget& budget, Zone* zone); void startSweepingAtomsTable(); IncrementalProgress sweepAtomsTable(FreeOp* fop, SliceBudget& budget); IncrementalProgress sweepWeakCaches(FreeOp* fop, SliceBudget& budget); IncrementalProgress finalizeAllocKind(FreeOp* fop, SliceBudget& budget, Zone* zone, AllocKind kind); IncrementalProgress sweepShapeTree(FreeOp* fop, SliceBudget& budget, Zone* zone); - void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock); + void endSweepPhase(bool lastGC); bool allCCVisibleZonesWereCollected() const; void sweepZones(FreeOp* fop, ZoneGroup* group, bool lastGC); void sweepZoneGroups(FreeOp* fop, bool destroyingRuntime); void decommitAllWithoutUnlocking(const AutoLockGC& lock); void startDecommit(); void queueZonesForBackgroundSweep(ZoneList& zones); void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks); void assertBackgroundSweepingFinished(); bool shouldCompact(); void beginCompactPhase(); IncrementalProgress compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget, - AutoLockForExclusiveAccess& lock); + AutoTraceSession& session); void endCompactPhase(JS::gcreason::Reason reason); void sweepTypesAfterCompacting(Zone* zone); void sweepZoneAfterCompacting(Zone* zone); MOZ_MUST_USE bool relocateArenas(Zone* zone, JS::gcreason::Reason reason, Arena*& relocatedListOut, SliceBudget& sliceBudget); void updateTypeDescrObjects(MovingTracer* trc, Zone* zone); void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount); void updateAllCellPointers(MovingTracer* trc, Zone* zone); - void updateZonePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock); - void updateRuntimePointersToRelocatedCells(AutoLockForExclusiveAccess& lock); + void updateZonePointersToRelocatedCells(Zone* zone); + void updateRuntimePointersToRelocatedCells(AutoTraceSession& session); void protectAndHoldArenas(Arena* arenaList); void unprotectHeldRelocatedArenas(); void releaseRelocatedArenas(Arena* arenaList); void releaseRelocatedArenasWithoutUnlocking(Arena* arenaList, const AutoLockGC& lock); void finishCollection(JS::gcreason::Reason reason); - void computeNonIncrementalMarkingForValidation(AutoLockForExclusiveAccess& lock); + void computeNonIncrementalMarkingForValidation(AutoTraceSession& session); void validateIncrementalMarking(); void finishMarkingValidation(); #ifdef DEBUG void checkForCompartmentMismatches(); #endif void callFinalizeCallbacks(FreeOp* fop, JSFinalizeStatus status) const;
--- a/js/src/gc/Iteration-inl.h +++ b/js/src/gc/Iteration-inl.h @@ -52,22 +52,24 @@ class GrayObjectIter : public ZoneCellIt JSObject* get() const { return ZoneCellIter<js::gc::TenuredCell>::get<JSObject>(); } operator JSObject*() const { return get(); } JSObject* operator ->() const { return get(); } }; class GCZonesIter { - private: ZonesIter zone; public: explicit GCZonesIter(JSRuntime* rt, ZoneSelector selector = WithAtoms) : zone(rt, selector) { MOZ_ASSERT(JS::CurrentThreadIsHeapBusy()); + MOZ_ASSERT_IF(rt->gc.atomsZone->isCollectingFromAnyThread(), + !rt->hasHelperThreadZones()); + if (!zone->isCollectingFromAnyThread()) next(); } bool done() const { return zone.done(); } void next() { MOZ_ASSERT(!done());
--- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -804,17 +804,17 @@ js::Nursery::doCollection(JS::gcreason:: sb.traceWholeCells(mover); endProfile(ProfileKey::TraceWholeCells); startProfile(ProfileKey::TraceGenericEntries); sb.traceGenericEntries(&mover); endProfile(ProfileKey::TraceGenericEntries); startProfile(ProfileKey::MarkRuntime); - rt->gc.traceRuntimeForMinorGC(&mover, session.lock); + rt->gc.traceRuntimeForMinorGC(&mover, session); endProfile(ProfileKey::MarkRuntime); startProfile(ProfileKey::MarkDebugger); { gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::MARK_ROOTS); Debugger::traceAllForMovingGC(&mover); } endProfile(ProfileKey::MarkDebugger);
--- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -252,82 +252,84 @@ PropertyDescriptor::trace(JSTracer* trc) if ((attrs & JSPROP_SETTER) && setter) { JSObject* tmp = JS_FUNC_TO_DATA_PTR(JSObject*, setter); TraceRoot(trc, &tmp, "Descriptor::set"); setter = JS_DATA_TO_FUNC_PTR(JSSetterOp, tmp); } } void -js::gc::GCRuntime::traceRuntimeForMajorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock) +js::gc::GCRuntime::traceRuntimeForMajorGC(JSTracer* trc, AutoTraceSession& session) { + MOZ_ASSERT_IF(atomsZone->isCollecting(), session.maybeLock.isSome()); + // FinishRoots will have asserted that every root that we do not expect // is gone, so we can simply skip traceRuntime here. if (rt->isBeingDestroyed()) return; gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS); - if (rt->atomsCompartment(lock)->zone()->isCollecting()) - traceRuntimeAtoms(trc, lock); + if (atomsZone->isCollecting()) + traceRuntimeAtoms(trc, session.lock()); JSCompartment::traceIncomingCrossCompartmentEdgesForZoneGC(trc); - traceRuntimeCommon(trc, MarkRuntime, lock); + traceRuntimeCommon(trc, MarkRuntime, session); } void -js::gc::GCRuntime::traceRuntimeForMinorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock) +js::gc::GCRuntime::traceRuntimeForMinorGC(JSTracer* trc, AutoTraceSession& session) { // Note that we *must* trace the runtime during the SHUTDOWN_GC's minor GC // despite having called FinishRoots already. This is because FinishRoots // does not clear the crossCompartmentWrapper map. It cannot do this // because Proxy's trace for CrossCompartmentWrappers asserts presence in // the map. And we can reach its trace function despite having finished the // roots via the edges stored by the pre-barrier verifier when we finish // the verifier for the last time. gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS); jit::JitRuntime::TraceJitcodeGlobalTableForMinorGC(trc); - traceRuntimeCommon(trc, TraceRuntime, lock); + traceRuntimeCommon(trc, TraceRuntime, session); } void js::TraceRuntime(JSTracer* trc) { MOZ_ASSERT(!trc->isMarkingTracer()); JSRuntime* rt = trc->runtime(); EvictAllNurseries(rt); AutoPrepareForTracing prep(TlsContext.get(), WithAtoms); gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP); - rt->gc.traceRuntime(trc, prep.session().lock); + rt->gc.traceRuntime(trc, prep.session()); } void -js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoLockForExclusiveAccess& lock) +js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoTraceSession& session) { MOZ_ASSERT(!rt->isBeingDestroyed()); gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS); - traceRuntimeAtoms(trc, lock); - traceRuntimeCommon(trc, TraceRuntime, lock); + traceRuntimeAtoms(trc, session.lock()); + traceRuntimeCommon(trc, TraceRuntime, session); } void js::gc::GCRuntime::traceRuntimeAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock) { gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_RUNTIME_DATA); TracePermanentAtoms(trc); TraceAtoms(trc, lock); TraceWellKnownSymbols(trc); jit::JitRuntime::Trace(trc, lock); } void js::gc::GCRuntime::traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark, - AutoLockForExclusiveAccess& lock) + AutoTraceSession& session) { MOZ_ASSERT(!TlsContext.get()->suppressGC); { gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_STACK); JSContext* cx = TlsContext.get(); for (const CooperatingContext& target : rt->cooperatingContexts()) { @@ -362,17 +364,17 @@ js::gc::GCRuntime::traceRuntimeCommon(JS target.context()->trace(trc); // Trace all compartment roots, but not the compartment itself; it is // traced via the parent pointer if traceRoots actually traces anything. for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) c->traceRoots(trc, traceOrMark); // Trace helper thread roots. - HelperThreadState().trace(trc); + HelperThreadState().trace(trc, session); // Trace the embedding's black and gray roots. if (!JS::CurrentThreadIsHeapMinorCollecting()) { gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_EMBEDDING); /* * The embedding can register additional roots here. * @@ -428,17 +430,17 @@ js::gc::GCRuntime::finishRoots() // The nsWrapperCache may not be empty before our shutdown GC, so we have // to skip that table when verifying that we are fully unrooted. auto prior = grayRootTracer; grayRootTracer = Callback<JSTraceDataOp>(nullptr, nullptr); AssertNoRootsTracer trc(rt, TraceWeakMapKeysValues); AutoPrepareForTracing prep(TlsContext.get(), WithAtoms); gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP); - traceRuntime(&trc, prep.session().lock); + traceRuntime(&trc, prep.session()); // Restore the wrapper tracing so that we leak instead of leaving dangling // pointers. grayRootTracer = prior; #endif // DEBUG } // Append traced things to a buffer on the zone for use later in the GC.
--- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -216,17 +216,17 @@ gc::GCRuntime::startVerifyPreBarriers() goto oom; /* Create the root node. */ trc->curnode = MakeNode(trc, nullptr, JS::TraceKind(0)); incrementalState = State::MarkRoots; /* Make all the roots be edges emanating from the root node. */ - traceRuntime(trc, prep.session().lock); + traceRuntime(trc, prep.session()); VerifyNode* node; node = trc->curnode; if (trc->edgeptr == trc->term) goto oom; /* For each edge, make a node for it if one doesn't already exist. */ while ((char*)node < trc->edgeptr) { @@ -453,17 +453,17 @@ js::gc::GCRuntime::finishVerifier() #if defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG) class HeapCheckTracerBase : public JS::CallbackTracer { public: explicit HeapCheckTracerBase(JSRuntime* rt, WeakMapTraceKind weakTraceKind); bool init(); - bool traceHeap(AutoLockForExclusiveAccess& lock); + bool traceHeap(AutoTraceSession& session); virtual void checkCell(Cell* cell) = 0; protected: void dumpCellInfo(Cell* cell); void dumpCellPath(); Cell* parentCell() { return parentIndex == -1 ? nullptr : stack[parentIndex].thing.asCell(); @@ -534,22 +534,22 @@ HeapCheckTracerBase::onChild(const JS::G return; WorkItem item(thing, contextName(), parentIndex); if (!stack.append(item)) oom = true; } bool -HeapCheckTracerBase::traceHeap(AutoLockForExclusiveAccess& lock) +HeapCheckTracerBase::traceHeap(AutoTraceSession& session) { // The analysis thinks that traceRuntime might GC by calling a GC callback. JS::AutoSuppressGCAnalysis nogc; if (!rt->isBeingDestroyed()) - rt->gc.traceRuntime(this, lock); + rt->gc.traceRuntime(this, session); while (!stack.empty() && !oom) { WorkItem item = stack.back(); if (item.processed) { stack.popBack(); } else { parentIndex = stack.length() - 1; stack.back().processed = true; @@ -602,17 +602,17 @@ HeapCheckTracerBase::dumpCellPath() #endif // defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG) #ifdef JSGC_HASH_TABLE_CHECKS class CheckHeapTracer final : public HeapCheckTracerBase { public: explicit CheckHeapTracer(JSRuntime* rt); - void check(AutoLockForExclusiveAccess& lock); + void check(AutoTraceSession& session); private: void checkCell(Cell* cell) override; }; CheckHeapTracer::CheckHeapTracer(JSRuntime* rt) : HeapCheckTracerBase(rt, TraceWeakMapKeysValues) {} @@ -629,44 +629,44 @@ CheckHeapTracer::checkCell(Cell* cell) if (!IsValidGCThingPointer(cell) || !IsGCThingValidAfterMovingGC(cell)) { failures++; fprintf(stderr, "Bad pointer %p\n", cell); dumpCellPath(); } } void -CheckHeapTracer::check(AutoLockForExclusiveAccess& lock) +CheckHeapTracer::check(AutoTraceSession& session) { - if (!traceHeap(lock)) + if (!traceHeap(session)) return; if (failures) fprintf(stderr, "Heap check: %zu failure(s)\n", failures); MOZ_RELEASE_ASSERT(failures == 0); } void js::gc::CheckHeapAfterGC(JSRuntime* rt) { AutoTraceSession session(rt, JS::HeapState::Tracing); CheckHeapTracer tracer(rt); if (tracer.init()) - tracer.check(session.lock); + tracer.check(session); } #endif /* JSGC_HASH_TABLE_CHECKS */ #if defined(JS_GC_ZEAL) || defined(DEBUG) class CheckGrayMarkingTracer final : public HeapCheckTracerBase { public: explicit CheckGrayMarkingTracer(JSRuntime* rt); - bool check(AutoLockForExclusiveAccess& lock); + bool check(AutoTraceSession& session); private: void checkCell(Cell* cell) override; }; CheckGrayMarkingTracer::CheckGrayMarkingTracer(JSRuntime* rt) : HeapCheckTracerBase(rt, DoNotTraceWeakMaps) { @@ -694,19 +694,19 @@ CheckGrayMarkingTracer::checkCell(Cell* fprintf(stderr, "\n"); DumpObject(cell->as<JSObject>(), stderr); } #endif } } bool -CheckGrayMarkingTracer::check(AutoLockForExclusiveAccess& lock) +CheckGrayMarkingTracer::check(AutoTraceSession& session) { - if (!traceHeap(lock)) + if (!traceHeap(session)) return true; // Ignore failure. return failures == 0; } JS_FRIEND_API(bool) js::CheckGrayMarkingState(JSRuntime* rt) { @@ -716,12 +716,12 @@ js::CheckGrayMarkingState(JSRuntime* rt) return true; gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP); AutoTraceSession session(rt, JS::HeapState::Tracing); CheckGrayMarkingTracer tracer(rt); if (!tracer.init()) return true; // Ignore failure - return tracer.check(session.lock); + return tracer.check(session); } #endif // defined(JS_GC_ZEAL) || defined(DEBUG)
--- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -31,17 +31,16 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup* arenas(rt, group), types(this), gcWeakMapList_(group), compartments_(), gcGrayRoots_(group), gcWeakRefs_(group), weakCaches_(group), gcWeakKeys_(group, SystemAllocPolicy(), rt->randomHashCodeScrambler()), - gcSweepGroupEdges_(group), typeDescrObjects_(group, this), regExps(this), markedAtoms_(group), atomCache_(group), externalStringCache_(group), functionToStringCache_(group), usage(&rt->gc.usage), threshold(), @@ -55,17 +54,17 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup* #ifdef DEBUG gcLastSweepGroupIndex(group, 0), #endif jitZone_(group, nullptr), gcScheduled_(false), gcScheduledSaved_(false), gcPreserveCode_(group, false), keepShapeTables_(group, false), - listNext_(group, NotOnList) + listNext_(NotOnList) { /* Ensure that there are no vtables to mess us up here. */ MOZ_ASSERT(reinterpret_cast<JS::shadow::Zone*>(this) == static_cast<JS::shadow::Zone*>(this)); AutoLockGC lock(rt); threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables, rt->gc.schedulingState, lock); setGCMaxMallocBytes(rt->gc.tunables.maxMallocBytes(), lock);
--- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -63,21 +63,21 @@ class ZoneHeapThreshold static size_t computeZoneTriggerBytes(double growthFactor, size_t lastBytes, JSGCInvocationKind gckind, const GCSchedulingTunables& tunables, const AutoLockGC& lock); }; struct ZoneComponentFinder : public ComponentFinder<JS::Zone, ZoneComponentFinder> { - ZoneComponentFinder(uintptr_t sl, AutoLockForExclusiveAccess& lock) - : ComponentFinder<JS::Zone, ZoneComponentFinder>(sl), lock(lock) + ZoneComponentFinder(uintptr_t sl, JS::Zone* maybeAtomsZone) + : ComponentFinder<JS::Zone, ZoneComponentFinder>(sl), maybeAtomsZone(maybeAtomsZone) {} - AutoLockForExclusiveAccess& lock; + JS::Zone* maybeAtomsZone; }; struct UniqueIdGCPolicy { static bool needsSweep(Cell** cell, uint64_t* value); }; // Maps a Cell* to a unique, 64bit id. using UniqueIdMap = GCHashMap<Cell*, @@ -395,17 +395,17 @@ struct Zone : public JS::shadow::Zone, public: js::gc::WeakKeyTable& gcWeakKeys() { return gcWeakKeys_.ref(); } private: // A set of edges from this zone to other zones. // // This is used during GC while calculating sweep groups to record edges // that can't be determined by examining this zone by itself. - js::ZoneGroupData<ZoneSet> gcSweepGroupEdges_; + js::ActiveThreadData<ZoneSet> gcSweepGroupEdges_; public: ZoneSet& gcSweepGroupEdges() { return gcSweepGroupEdges_.ref(); } // Keep track of all TypeDescr and related objects in this compartment. // This is used by the GC to trace them all first when compacting, since the // TypedObject trace hook may access these objects. // @@ -716,17 +716,17 @@ struct Zone : public JS::shadow::Zone, js::ActiveThreadData<bool> gcScheduled_; js::ActiveThreadData<bool> gcScheduledSaved_; js::ZoneGroupData<bool> gcPreserveCode_; js::ZoneGroupData<bool> keepShapeTables_; // Allow zones to be linked into a list friend class js::gc::ZoneList; static Zone * const NotOnList; - js::ZoneGroupOrGCTaskData<Zone*> listNext_; + js::ActiveThreadOrGCTaskData<Zone*> listNext_; bool isOnList() const; Zone* nextZone() const; friend bool js::CurrentThreadCanAccessZone(Zone* zone); friend class js::gc::GCRuntime; }; } // namespace JS
--- a/js/src/gdb/tests/test-JSString.cpp +++ b/js/src/gdb/tests/test-JSString.cpp @@ -1,10 +1,9 @@ #include "gdb-tests.h" -#include "jsatom.h" #include "jscntxt.h" // When JSGC_ANALYSIS is #defined, Rooted<JSFlatString*> needs the definition // of JSFlatString in order to figure out its ThingRootKind #include "vm/String.h" FRAGMENT(JSString, simple) { JS::Rooted<JSString*> empty(cx, JS_NewStringCopyN(cx, nullptr, 0));
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/wasm/ion-error-throw.js @@ -0,0 +1,44 @@ +const options = getJitCompilerOptions(); + +// These tests need at least baseline to make sense. +if (!options['baseline.enable']) + quit(); + +const { assertStackTrace, startProfiling, endProfiling, assertEqPreciseStacks } = WasmHelpers; + +enableGeckoProfiling(); + +let { add } = wasmEvalText(`(module + (func $add (export "add") (result i32) (param i32) (param i32) + get_local 0 + i32.const 42 + i32.eq + if + unreachable + end + + get_local 0 + get_local 1 + i32.add + ) +)`).exports; + +const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', '']; +const FAST_ENTRY_STACK = ['', '>', '0,>', '>', '']; + +function main() { + for (let i = 0; i < 50; i++) { + startProfiling(); + try { + assertEq(add(i, i+1), 2*i+1); + } catch (e) { + assertEq(i, 42); + assertEq(e.message.includes("unreachable"), true); + assertStackTrace(e, ['wasm-function[0]', 'main', '']); + } + let stack = endProfiling(); + assertEqPreciseStacks(stack, [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); + } +} + +main();
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/wasm/regress/caller-property.js @@ -0,0 +1,7 @@ +const { g } = wasmEvalText(`(module (func $f) (export "g" $f))`).exports; + +function testCaller() { + return g.caller; +} + +assertErrorMessage(testCaller, TypeError, /caller/);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/wasm/regress/ion-error-gc-fakeexitframe.js @@ -0,0 +1,81 @@ +var lfLogBuffer = ` +//corefuzz-dcd-endofdata +for (var i = 0; gczeal(4,10); g(buffer)) + assertEq(assignParameterGetElement(42), 17); +//corefuzz-dcd-endofdata +//corefuzz-dcd-endofdata +//corefuzz-dcd-endofdata +g = newGlobal(); +g.parent = this +g.eval("Debugger(parent).onExceptionUnwind=(function(){})") +`; +lfLogBuffer = lfLogBuffer.split('\n'); + +gcPreserveCode(); + +var letext =`(module + (type $type0 (func (param i32 i64))) + (type $type1 (func (param i32) (result i64))) + (type $type2 (func (result i32))) + (memory 1) + (export "store" $func0) + (export "load" $func1) + (export "assert_0" $func2) + (func $func0 (param $var0 i32) (param $var1 i64) + get_local $var0 + get_local $var1 + i64.store16 offset=16 + ) + (func $func1 (param $var0 i32) (result i64) + get_local $var0 + i64.load16_s offset=16 + ) + (func $func2 (result i32) + i32.const 65519 + i64.const -32768 + call $func0 + i32.const 1 + ) + (data (i32.const 0) + "\\00\\01\\02\\03\\04\\05\\06\\07\\08\t\n\\0b\\0c\\0d\\0e\\0f" + ) + (data (i32.const 16) + "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff" + ) +)`; + +var binary = wasmTextToBinary(letext); +var module = new WebAssembly.Module(binary); + +var lfCodeBuffer = ""; +while (true) { + var line = lfLogBuffer.shift(); + if (line == null) { + break; + } else if (line == "//corefuzz-dcd-endofdata") { + processCode(lfCodeBuffer); + } else { + lfCodeBuffer += line + "\n"; + } +} + +if (lfCodeBuffer) processCode(lfCodeBuffer); + +function processCode(code) { + evaluate(code); + while (true) { + imports = {} + try { + instance = new WebAssembly.Instance(module, imports); + break; + } catch (exc) {} + } + for (let descriptor of WebAssembly.Module.exports(module)) { + switch (descriptor.kind) { + case "function": + try { + print(instance.exports[descriptor.name]()) + } catch (exc1) {} + } + } +}
--- a/js/src/jit/BacktrackingAllocator.cpp +++ b/js/src/jit/BacktrackingAllocator.cpp @@ -969,35 +969,47 @@ BacktrackingAllocator::tryMergeBundles(L // Move all ranges from bundle1 into bundle0. while (LiveRange* range = bundle1->popFirstRange()) bundle0->addRange(range); return true; } static inline LDefinition* -FindReusingDefOrTemp(LNode* ins, LAllocation* alloc) +FindReusingDefOrTemp(LNode* node, LAllocation* alloc) { + if (node->isPhi()) { + MOZ_ASSERT(node->toPhi()->numDefs() == 1); + MOZ_ASSERT(node->toPhi()->getDef(0)->policy() != LDefinition::MUST_REUSE_INPUT); + return nullptr; + } + + LInstruction* ins = node->toInstruction(); + for (size_t i = 0; i < ins->numDefs(); i++) { LDefinition* def = ins->getDef(i); if (def->policy() == LDefinition::MUST_REUSE_INPUT && ins->getOperand(def->getReusedInput()) == alloc) + { return def; + } } for (size_t i = 0; i < ins->numTemps(); i++) { LDefinition* def = ins->getTemp(i); if (def->policy() == LDefinition::MUST_REUSE_INPUT && ins->getOperand(def->getReusedInput()) == alloc) + { return def; + } } return nullptr; } static inline size_t -NumReusingDefs(LNode* ins) +NumReusingDefs(LInstruction* ins) { size_t num = 0; for (size_t i = 0; i < ins->numDefs(); i++) { LDefinition* def = ins->getDef(i); if (def->policy() == LDefinition::MUST_REUSE_INPUT) num++; } return num; @@ -2044,17 +2056,17 @@ BacktrackingAllocator::reifyAllocations( if (LDefinition* def = FindReusingDefOrTemp(ins, alloc)) { LiveRange* outputRange = vreg(def).rangeFor(outputOf(ins)); LAllocation res = outputRange->bundle()->allocation(); LAllocation sourceAlloc = range->bundle()->allocation(); if (res != *alloc) { if (!this->alloc().ensureBallast()) return false; - if (NumReusingDefs(ins) <= 1) { + if (NumReusingDefs(ins->toInstruction()) <= 1) { LMoveGroup* group = getInputMoveGroup(ins->toInstruction()); if (!group->addAfter(sourceAlloc, res, reg.type())) return false; } else { LMoveGroup* group = getFixReuseMoveGroup(ins->toInstruction()); if (!group->add(sourceAlloc, res, reg.type())) return false; }
--- a/js/src/jit/C1Spewer.cpp +++ b/js/src/jit/C1Spewer.cpp @@ -85,17 +85,20 @@ DumpLIR(GenericPrinter& out, LNode* ins) ins->dump(out); out.printf(" <|@\n"); } void C1Spewer::spewRanges(GenericPrinter& out, BacktrackingAllocator* regalloc, LNode* ins) { for (size_t k = 0; k < ins->numDefs(); k++) { - uint32_t id = ins->getDef(k)->virtualRegister(); + const LDefinition* def = ins->isPhi() + ? ins->toPhi()->getDef(k) + : ins->toInstruction()->getDef(k); + uint32_t id = def->virtualRegister(); VirtualRegister* vreg = ®alloc->vregs[id]; for (LiveRange::RegisterLinkIterator iter = vreg->rangesBegin(); iter; iter++) { LiveRange* range = LiveRange::get(*iter); out.printf("%d object \"", id); out.printf("%s", range->bundle()->allocation().toString().get()); out.printf("\" %d -1", id); out.printf(" [%u, %u[", range->from().bits(), range->to().bits());
--- a/js/src/jit/JSONSpewer.cpp +++ b/js/src/jit/JSONSpewer.cpp @@ -189,18 +189,22 @@ JSONSpewer::spewLIns(LNode* ins) property("id", ins->id()); propertyName("opcode"); out_.printf("\""); ins->dump(out_); out_.printf("\""); beginListProperty("defs"); - for (size_t i = 0; i < ins->numDefs(); i++) - value(ins->getDef(i)->virtualRegister()); + for (size_t i = 0; i < ins->numDefs(); i++) { + if (ins->isPhi()) + value(ins->toPhi()->getDef(i)->virtualRegister()); + else + value(ins->toInstruction()->getDef(i)->virtualRegister()); + } endList(); endObject(); } void JSONSpewer::spewLIR(MIRGraph* mir) {
--- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -831,18 +831,17 @@ TraceThisAndArguments(JSTracer* trc, con if (!CalleeTokenIsFunction(layout->calleeToken())) return; size_t nargs = layout->numActualArgs(); size_t nformals = 0; JSFunction* fun = CalleeTokenToFunction(layout->calleeToken()); if (frame.type() != JitFrame_JSJitToWasm && - !frame.isExitFrameLayout<LazyLinkExitFrameLayout>() && - !frame.isExitFrameLayout<InterpreterStubExitFrameLayout>() && + !frame.isExitFrameLayout<CalledFromJitExitFrameLayout>() && !fun->nonLazyScript()->mayReadFrameArgsDirectly()) { nformals = fun->nargs(); } size_t newTargetOffset = Max(nargs, fun->nargs()); Value* argv = layout->argv(); @@ -1128,26 +1127,18 @@ TraceJitExitFrame(JSTracer* trc, const J Value* vp = method->vp(); TraceRootRange(trc, len, vp, "ion-dom-args"); } else { TraceRoot(trc, dom->vp(), "ion-dom-args"); } return; } - if (frame.isExitFrameLayout<LazyLinkExitFrameLayout>()) { - auto* layout = frame.exitFrame()->as<LazyLinkExitFrameLayout>(); - JitFrameLayout* jsLayout = layout->jsFrame(); - jsLayout->replaceCalleeToken(TraceCalleeToken(trc, jsLayout->calleeToken())); - TraceThisAndArguments(trc, frame, jsLayout); - return; - } - - if (frame.isExitFrameLayout<InterpreterStubExitFrameLayout>()) { - auto* layout = frame.exitFrame()->as<InterpreterStubExitFrameLayout>(); + if (frame.isExitFrameLayout<CalledFromJitExitFrameLayout>()) { + auto* layout = frame.exitFrame()->as<CalledFromJitExitFrameLayout>(); JitFrameLayout* jsLayout = layout->jsFrame(); jsLayout->replaceCalleeToken(TraceCalleeToken(trc, jsLayout->calleeToken())); TraceThisAndArguments(trc, frame, jsLayout); return; } if (frame.isBareExit()) { // Nothing to trace. Fake exit frame pushed for VM functions with
--- a/js/src/jit/JitFrames.h +++ b/js/src/jit/JitFrames.h @@ -819,75 +819,69 @@ ExitFrameLayout::as<IonDOMExitFrameLayou struct IonDOMMethodExitFrameLayoutTraits { static const size_t offsetOfArgcFromArgv = offsetof(IonDOMMethodExitFrameLayout, argc_) - offsetof(IonDOMMethodExitFrameLayout, argv_); }; // Cannot inherit implementation since we need to extend the top of // ExitFrameLayout. -class LazyLinkExitFrameLayout +class CalledFromJitExitFrameLayout { protected: // silence clang warning about unused private fields ExitFooterFrame footer_; JitFrameLayout exit_; public: - static ExitFrameType Type() { return ExitFrameType::LazyLink; } - static inline size_t Size() { - return sizeof(LazyLinkExitFrameLayout); - } - - inline JitFrameLayout* jsFrame() { - return &exit_; - } - static size_t offsetOfExitFrame() { - return offsetof(LazyLinkExitFrameLayout, exit_); - } -}; - -template <> -inline LazyLinkExitFrameLayout* -ExitFrameLayout::as<LazyLinkExitFrameLayout>() -{ - MOZ_ASSERT(is<LazyLinkExitFrameLayout>()); - uint8_t* sp = reinterpret_cast<uint8_t*>(this); - sp -= LazyLinkExitFrameLayout::offsetOfExitFrame(); - return reinterpret_cast<LazyLinkExitFrameLayout*>(sp); -} - -class InterpreterStubExitFrameLayout -{ - protected: // silence clang warning about unused private fields - ExitFooterFrame footer_; - JitFrameLayout exit_; - - public: - static ExitFrameType Type() { return ExitFrameType::InterpreterStub; } - - static inline size_t Size() { - return sizeof(InterpreterStubExitFrameLayout); + return sizeof(CalledFromJitExitFrameLayout); } inline JitFrameLayout* jsFrame() { return &exit_; } static size_t offsetOfExitFrame() { - return offsetof(InterpreterStubExitFrameLayout, exit_); + return offsetof(CalledFromJitExitFrameLayout, exit_); } }; -template <> -inline InterpreterStubExitFrameLayout* -ExitFrameLayout::as<InterpreterStubExitFrameLayout>() +class LazyLinkExitFrameLayout : public CalledFromJitExitFrameLayout +{ + public: + static ExitFrameType Type() { return ExitFrameType::LazyLink; } +}; + +class InterpreterStubExitFrameLayout : public CalledFromJitExitFrameLayout +{ + public: + static ExitFrameType Type() { return ExitFrameType::InterpreterStub; } +}; + +class WasmExitFrameLayout : CalledFromJitExitFrameLayout { - MOZ_ASSERT(is<InterpreterStubExitFrameLayout>()); + public: + static ExitFrameType Type() { return ExitFrameType::WasmJitEntry; } +}; + +template<> +inline bool +ExitFrameLayout::is<CalledFromJitExitFrameLayout>() +{ + return is<InterpreterStubExitFrameLayout>() || + is<LazyLinkExitFrameLayout>() || + is<WasmExitFrameLayout>(); +} + +template <> +inline CalledFromJitExitFrameLayout* +ExitFrameLayout::as<CalledFromJitExitFrameLayout>() +{ + MOZ_ASSERT(is<CalledFromJitExitFrameLayout>()); uint8_t* sp = reinterpret_cast<uint8_t*>(this); - sp -= InterpreterStubExitFrameLayout::offsetOfExitFrame(); - return reinterpret_cast<InterpreterStubExitFrameLayout*>(sp); + sp -= CalledFromJitExitFrameLayout::offsetOfExitFrame(); + return reinterpret_cast<CalledFromJitExitFrameLayout*>(sp); } class ICStub; class JitStubFrameLayout : public CommonFrameLayout { // Info on the stack //
--- a/js/src/jit/LIR.cpp +++ b/js/src/jit/LIR.cpp @@ -512,34 +512,39 @@ LInstruction::assignSnapshot(LSnapshot* } void LNode::dump(GenericPrinter& out) { if (numDefs() != 0) { out.printf("{"); for (size_t i = 0; i < numDefs(); i++) { - out.printf("%s", getDef(i)->toString().get()); + const LDefinition* def = isPhi() ? toPhi()->getDef(i) : toInstruction()->getDef(i); + out.printf("%s", def->toString().get()); if (i != numDefs() - 1) out.printf(", "); } out.printf("} <- "); } printName(out); printOperands(out); - if (numTemps()) { - out.printf(" t=("); - for (size_t i = 0; i < numTemps(); i++) { - out.printf("%s", getTemp(i)->toString().get()); - if (i != numTemps() - 1) - out.printf(", "); + if (isInstruction()) { + LInstruction* ins = toInstruction(); + size_t numTemps = ins->numTemps(); + if (numTemps > 0) { + out.printf(" t=("); + for (size_t i = 0; i < numTemps; i++) { + out.printf("%s", ins->getTemp(i)->toString().get()); + if (i != numTemps - 1) + out.printf(", "); + } + out.printf(")"); } - out.printf(")"); } if (numSuccessors()) { out.printf(" s=("); for (size_t i = 0; i < numSuccessors(); i++) { out.printf("block%u", getSuccessor(i)->id()); if (i != numSuccessors() - 1) out.printf(", ");
--- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -720,36 +720,25 @@ class LNode inline LInstruction* toInstruction(); inline const LInstruction* toInstruction() const; // Returns the number of outputs of this instruction. If an output is // unallocated, it is an LDefinition, defining a virtual register. size_t numDefs() const { return numDefs_; } - virtual LDefinition* getDef(size_t index) = 0; - virtual void setDef(size_t index, const LDefinition& def) = 0; // Returns information about operands. virtual LAllocation* getOperand(size_t index) = 0; virtual void setOperand(size_t index, const LAllocation& a) = 0; - // Returns information about temporary registers needed. Each temporary - // register is an LDefinition with a fixed or virtual register and - // either GENERAL, FLOAT32, or DOUBLE type. - size_t numTemps() const { - return numTemps_; - } - inline LDefinition* getTemp(size_t index); - // Returns the number of successors of this instruction, if it is a control // transfer instruction, or zero otherwise. virtual size_t numSuccessors() const = 0; virtual MBasicBlock* getSuccessor(size_t i) const = 0; - virtual void setSuccessor(size_t i, MBasicBlock* successor) = 0; bool isCall() const { return isCall_; } // Does this call preserve the given register? // By default, it is assumed that all registers are clobbered by a call. inline bool isCallPreserved(AnyRegister reg) const; @@ -836,16 +825,30 @@ class LInstruction movesAfter_(nullptr) { } void setIsCall() { isCall_ = true; } public: + inline LDefinition* getDef(size_t index); + + void setDef(size_t index, const LDefinition& def) { + *getDef(index) = def; + } + + // Returns information about temporary registers needed. Each temporary + // register is an LDefinition with a fixed or virtual register and + // either GENERAL, FLOAT32, or DOUBLE type. + size_t numTemps() const { + return numTemps_; + } + inline LDefinition* getTemp(size_t index); + LSnapshot* snapshot() const { return snapshot_; } LSafepoint* safepoint() const { return safepoint_; } LMoveGroup* inputMoves() const { return inputMoves_; @@ -943,21 +946,21 @@ class LPhi final : public LNode : LNode(/* nonPhiNumOperands = */ 0, /* numDefs = */ 1, /* numTemps = */ 0), inputs_(inputs) { setMir(ins); } - LDefinition* getDef(size_t index) override { + LDefinition* getDef(size_t index) { MOZ_ASSERT(index == 0); return &def_; } - void setDef(size_t index, const LDefinition& def) override { + void setDef(size_t index, const LDefinition& def) { MOZ_ASSERT(index == 0); def_ = def; } size_t numOperands() const { return mir_->toPhi()->numOperands(); } LAllocation* getOperand(size_t index) override { MOZ_ASSERT(index < numOperands()); @@ -973,19 +976,16 @@ class LPhi final : public LNode LDefinition* getTemp(size_t index) = delete; size_t numSuccessors() const override { return 0; } MBasicBlock* getSuccessor(size_t i) const override { MOZ_CRASH("no successors"); } - void setSuccessor(size_t i, MBasicBlock*) override { - MOZ_CRASH("no successors"); - } }; class LMoveGroup; class LBlock { MBasicBlock* block_; FixedList<LPhi> phis_; InlineList<LInstruction> instructions_; @@ -1087,26 +1087,26 @@ namespace details { mozilla::Array<LDefinition, Defs + Temps> defsAndTemps_; protected: explicit LInstructionFixedDefsTempsHelper(uint32_t numOperands) : LInstruction(numOperands, Defs, Temps) {} public: - LDefinition* getDef(size_t index) final override { + LDefinition* getDef(size_t index) { MOZ_ASSERT(index < Defs); return &defsAndTemps_[index]; } LDefinition* getTemp(size_t index) { MOZ_ASSERT(index < Temps); return &defsAndTemps_[Defs + index]; } - void setDef(size_t index, const LDefinition& def) final override { + void setDef(size_t index, const LDefinition& def) { MOZ_ASSERT(index < Defs); defsAndTemps_[index] = def; } void setTemp(size_t index, const LDefinition& a) { MOZ_ASSERT(index < Temps); defsAndTemps_[Defs + index] = a; } void setInt64Temp(size_t index, const LInt64Definition& a) { @@ -1119,38 +1119,48 @@ namespace details { } size_t numSuccessors() const override { return 0; } MBasicBlock* getSuccessor(size_t i) const override { MOZ_CRASH("no successors"); } - void setSuccessor(size_t i, MBasicBlock* successor) override { - MOZ_CRASH("no successors"); - } // Default accessors, assuming a single input and output, respectively. const LAllocation* input() { MOZ_ASSERT(numOperands() == 1); return getOperand(0); } const LDefinition* output() { MOZ_ASSERT(numDefs() == 1); return getDef(0); } + static size_t offsetOfDef(size_t index) { + using T = LInstructionFixedDefsTempsHelper<0, 0>; + return offsetof(T, defsAndTemps_) + index * sizeof(LDefinition); + } static size_t offsetOfTemp(uint32_t numDefs, uint32_t index) { using T = LInstructionFixedDefsTempsHelper<0, 0>; return offsetof(T, defsAndTemps_) + (numDefs + index) * sizeof(LDefinition); } }; } // namespace details inline LDefinition* -LNode::getTemp(size_t index) +LInstruction::getDef(size_t index) +{ + MOZ_ASSERT(index < numDefs()); + using T = details::LInstructionFixedDefsTempsHelper<0, 0>; + uint8_t* p = reinterpret_cast<uint8_t*>(this) + T::offsetOfDef(index); + return reinterpret_cast<LDefinition*>(p); +} + +inline LDefinition* +LInstruction::getTemp(size_t index) { MOZ_ASSERT(index < numTemps()); using T = details::LInstructionFixedDefsTempsHelper<0, 0>; uint8_t* p = reinterpret_cast<uint8_t*>(this) + T::offsetOfTemp(numDefs(), index); return reinterpret_cast<LDefinition*>(p); } template <size_t Defs, size_t Operands, size_t Temps>
--- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -20,17 +20,16 @@ #include "jit/AtomicOperations.h" #include "jit/BaselineInspector.h" #include "jit/IonBuilder.h" #include "jit/JitSpewer.h" #include "jit/MIRGraph.h" #include "jit/RangeAnalysis.h" #include "js/Conversions.h" -#include "jsatominlines.h" #include "jsboolinlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit;
--- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -978,17 +978,17 @@ class LControlInstructionHelper : public public: virtual size_t numSuccessors() const final override { return Succs; } virtual MBasicBlock* getSuccessor(size_t i) const final override { return successors_[i]; } - virtual void setSuccessor(size_t i, MBasicBlock* successor) final override { + void setSuccessor(size_t i, MBasicBlock* successor) { successors_[i] = successor; } }; // Jumps to the start of a basic block. class LGoto : public LControlInstructionHelper<1, 0, 0> { public:
--- a/js/src/jsapi-tests/testAssemblerBuffer.cpp +++ b/js/src/jsapi-tests/testAssemblerBuffer.cpp @@ -1,16 +1,14 @@ /* 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 <stdlib.h> -#include "jsatom.h" - #include "jit/shared/IonAssemblerBufferWithConstantPools.h" #include "jsapi-tests/tests.h" // Tests for classes in: // // jit/shared/IonAssemblerBuffer.h // jit/shared/IonAssemblerBufferWithConstantPools.h
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -19,17 +19,16 @@ #ifdef __linux__ # include <dlfcn.h> #endif #include <stdarg.h> #include <string.h> #include <sys/stat.h> #include "jsarray.h" -#include "jsatom.h" #include "jsbool.h" #include "jscntxt.h" #include "jsdate.h" #include "jsexn.h" #include "jsfriendapi.h" #include "jsfun.h" #include "jsiter.h" #include "jsmath.h" @@ -88,17 +87,16 @@ #include "vm/String.h" #include "vm/StringBuffer.h" #include "vm/Symbol.h" #include "vm/WrapperObject.h" #include "vm/Xdr.h" #include "wasm/AsmJS.h" #include "wasm/WasmModule.h" -#include "jsatominlines.h" #include "jsfuninlines.h" #include "jsscriptinlines.h" #include "vm/Interpreter-inl.h" #include "vm/NativeObject-inl.h" #include "vm/SavedStacks-inl.h" #include "vm/String-inl.h"
--- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -10,17 +10,16 @@ #include "mozilla/CheckedInt.h" #include "mozilla/DebugOnly.h" #include "mozilla/FloatingPoint.h" #include "mozilla/MathAlgorithms.h" #include <algorithm> #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsfriendapi.h" #include "jsfun.h" #include "jsiter.h" #include "jsnum.h" #include "jsobj.h" #include "jstypes.h" #include "jsutil.h" @@ -33,18 +32,16 @@ #include "vm/ArgumentsObject.h" #include "vm/Interpreter.h" #include "vm/SelfHosting.h" #include "vm/Shape.h" #include "vm/StringBuffer.h" #include "vm/TypedArrayObject.h" #include "vm/WrapperObject.h" -#include "jsatominlines.h" - #include "vm/ArgumentsObject-inl.h" #include "vm/ArrayObject-inl.h" #include "vm/Caches-inl.h" #include "vm/GeckoProfiler-inl.h" #include "vm/Interpreter-inl.h" #include "vm/NativeObject-inl.h" #include "vm/UnboxedObject-inl.h"
--- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -35,16 +35,90 @@ using namespace js; using namespace js::gc; using mozilla::ArrayEnd; using mozilla::ArrayLength; using mozilla::Maybe; using mozilla::Nothing; using mozilla::RangedPtr; +struct js::AtomHasher::Lookup +{ + union { + const JS::Latin1Char* latin1Chars; + const char16_t* twoByteChars; + }; + bool isLatin1; + size_t length; + const JSAtom* atom; /* Optional. */ + JS::AutoCheckCannotGC nogc; + + HashNumber hash; + + MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length) + : twoByteChars(chars), isLatin1(false), length(length), atom(nullptr), + hash(mozilla::HashString(chars, length)) + {} + + MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length) + : latin1Chars(chars), isLatin1(true), length(length), atom(nullptr), + hash(mozilla::HashString(chars, length)) + {} + + inline explicit Lookup(const JSAtom* atom) + : isLatin1(atom->hasLatin1Chars()), length(atom->length()), atom(atom), + hash(atom->hash()) + { + if (isLatin1) { + latin1Chars = atom->latin1Chars(nogc); + MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash); + } else { + twoByteChars = atom->twoByteChars(nogc); + MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash); + } + } +}; + +inline HashNumber +js::AtomHasher::hash(const Lookup& l) +{ + return l.hash; +} + +MOZ_ALWAYS_INLINE bool +js::AtomHasher::match(const AtomStateEntry& entry, const Lookup& lookup) +{ + JSAtom* key = entry.asPtrUnbarriered(); + if (lookup.atom) + return lookup.atom == key; + if (key->length() != lookup.length || key->hash() != lookup.hash) + return false; + + if (key->hasLatin1Chars()) { + const Latin1Char* keyChars = key->latin1Chars(lookup.nogc); + if (lookup.isLatin1) + return mozilla::PodEqual(keyChars, lookup.latin1Chars, lookup.length); + return EqualChars(keyChars, lookup.twoByteChars, lookup.length); + } + + const char16_t* keyChars = key->twoByteChars(lookup.nogc); + if (lookup.isLatin1) + return EqualChars(lookup.latin1Chars, keyChars, lookup.length); + return mozilla::PodEqual(keyChars, lookup.twoByteChars, lookup.length); +} + +inline JSAtom* +js::AtomStateEntry::asPtr(JSContext* cx) const +{ + JSAtom* atom = asPtrUnbarriered(); + if (!cx->helperThread()) + JSString::readBarrier(atom); + return atom; +} + const char* js::AtomToPrintableString(JSContext* cx, JSAtom* atom, JSAutoByteString* bytes) { JSString* str = QuoteString(cx, atom, 0); if (!str) return nullptr; return bytes->encodeLatin1(cx, str); } @@ -705,8 +779,29 @@ js::XDRAtom(XDRState<mode>* xdr, Mutable return true; } template bool js::XDRAtom(XDRState<XDR_ENCODE>* xdr, MutableHandleAtom atomp); template bool js::XDRAtom(XDRState<XDR_DECODE>* xdr, MutableHandleAtom atomp); + +Handle<PropertyName*> +js::ClassName(JSProtoKey key, JSContext* cx) +{ + return ClassName(key, cx->names()); +} + +void +js::gc::MergeAtomsAddedWhileSweeping(JSRuntime* rt) +{ + // Add atoms that were added to the secondary table while we were sweeping + // the main table. + + AutoEnterOOMUnsafeRegion oomUnsafe; + AtomSet* atomsTable = rt->atomsForSweeping(); + MOZ_ASSERT(atomsTable); + for (auto r = rt->atomsAddedWhileSweeping()->all(); !r.empty(); r.popFront()) { + if (!atomsTable->putNew(AtomHasher::Lookup(r.front().asPtrUnbarriered()), r.front())) + oomUnsafe.crash("Adding atom from secondary table after sweep"); + } +}
--- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -2,23 +2,19 @@ * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 jsatom_h #define jsatom_h -#include "mozilla/HashFunctions.h" #include "mozilla/Maybe.h" -#include "jsalloc.h" - #include "gc/Rooting.h" -#include "js/GCHashTable.h" #include "vm/CommonPropertyNames.h" class JSAtom; class JSAutoByteString; namespace JS { class Value; struct Zone; @@ -28,86 +24,16 @@ namespace js { /* * Return a printable, lossless char[] representation of a string-type atom. * The lifetime of the result matches the lifetime of bytes. */ extern const char* AtomToPrintableString(JSContext* cx, JSAtom* atom, JSAutoByteString* bytes); -class AtomStateEntry -{ - uintptr_t bits; - - static const uintptr_t NO_TAG_MASK = uintptr_t(-1) - 1; - - public: - AtomStateEntry() : bits(0) {} - AtomStateEntry(const AtomStateEntry& other) : bits(other.bits) {} - AtomStateEntry(JSAtom* ptr, bool tagged) - : bits(uintptr_t(ptr) | uintptr_t(tagged)) - { - MOZ_ASSERT((uintptr_t(ptr) & 0x1) == 0); - } - - bool isPinned() const { - return bits & 0x1; - } - - /* - * Non-branching code sequence. Note that the const_cast is safe because - * the hash function doesn't consider the tag to be a portion of the key. - */ - void setPinned(bool pinned) const { - const_cast<AtomStateEntry*>(this)->bits |= uintptr_t(pinned); - } - - JSAtom* asPtr(JSContext* cx) const; - JSAtom* asPtrUnbarriered() const; - - bool needsSweep() { - JSAtom* atom = asPtrUnbarriered(); - return gc::IsAboutToBeFinalizedUnbarriered(&atom); - } -}; - -struct AtomHasher -{ - struct Lookup; - static inline HashNumber hash(const Lookup& l); - static MOZ_ALWAYS_INLINE bool match(const AtomStateEntry& entry, const Lookup& lookup); - static void rekey(AtomStateEntry& k, const AtomStateEntry& newKey) { k = newKey; } -}; - -using AtomSet = JS::GCHashSet<AtomStateEntry, AtomHasher, SystemAllocPolicy>; - -// This class is a wrapper for AtomSet that is used to ensure the AtomSet is -// not modified. It should only expose read-only methods from AtomSet. -// Note however that the atoms within the table can be marked during GC. -class FrozenAtomSet -{ - AtomSet* mSet; - -public: - // This constructor takes ownership of the passed-in AtomSet. - explicit FrozenAtomSet(AtomSet* set) { mSet = set; } - - ~FrozenAtomSet() { js_delete(mSet); } - - MOZ_ALWAYS_INLINE AtomSet::Ptr readonlyThreadsafeLookup(const AtomSet::Lookup& l) const; - - size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { - return mSet->sizeOfIncludingThis(mallocSizeOf); - } - - typedef AtomSet::Range Range; - - AtomSet::Range all() const { return mSet->all(); } -}; - class PropertyName; } /* namespace js */ extern bool AtomIsPinned(JSContext* cx, JSAtom* atom); #ifdef DEBUG @@ -182,16 +108,23 @@ enum XDRMode { template <XDRMode mode> class XDRState; template<XDRMode mode> bool XDRAtom(XDRState<mode>* xdr, js::MutableHandleAtom atomp); +extern JS::Handle<PropertyName*> +ClassName(JSProtoKey key, JSContext* cx); + +namespace gc { +void MergeAtomsAddedWhileSweeping(JSRuntime* rt); +} // namespace gc + #ifdef DEBUG bool AtomIsMarked(JS::Zone* zone, JSAtom* atom); bool AtomIsMarked(JS::Zone* zone, jsid id); bool AtomIsMarked(JS::Zone* zone, const JS::Value& value); #endif // DEBUG
--- a/js/src/jsatominlines.h +++ b/js/src/jsatominlines.h @@ -4,74 +4,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef jsatominlines_h #define jsatominlines_h #include "jsatom.h" -#include "mozilla/PodOperations.h" #include "mozilla/RangedPtr.h" -#include "jscntxt.h" #include "jsnum.h" +#include "vm/Runtime.h" #include "vm/String.h" -inline JSAtom* -js::AtomStateEntry::asPtr(JSContext* cx) const -{ - JSAtom* atom = asPtrUnbarriered(); - if (!cx->helperThread()) - JSString::readBarrier(atom); - return atom; -} - -inline JSAtom* -js::AtomStateEntry::asPtrUnbarriered() const -{ - MOZ_ASSERT(bits != 0); - return reinterpret_cast<JSAtom*>(bits & NO_TAG_MASK); -} - namespace js { -struct AtomHasher::Lookup -{ - union { - const JS::Latin1Char* latin1Chars; - const char16_t* twoByteChars; - }; - bool isLatin1; - size_t length; - const JSAtom* atom; /* Optional. */ - JS::AutoCheckCannotGC nogc; - - HashNumber hash; - - MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length) - : twoByteChars(chars), isLatin1(false), length(length), atom(nullptr) - { - hash = mozilla::HashString(chars, length); - } - MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length) - : latin1Chars(chars), isLatin1(true), length(length), atom(nullptr) - { - hash = mozilla::HashString(chars, length); - } - inline explicit Lookup(const JSAtom* atom); -}; - -inline HashNumber -AtomHasher::hash(const Lookup& l) -{ - return l.hash; -} - inline jsid AtomToId(JSAtom* atom) { JS_STATIC_ASSERT(JSID_INT_MIN == 0); uint32_t index; if (atom->isIndex(&index) && index <= JSID_INT_MAX) return INT_TO_JSID(int32_t(index)); @@ -194,52 +145,16 @@ IdToString(JSContext* cx, jsid id) RootedValue idv(cx, IdToValue(id)); JSString* str = ToStringSlow<CanGC>(cx, idv); if (!str) return nullptr; return str->ensureFlat(cx); } -inline -AtomHasher::Lookup::Lookup(const JSAtom* atom) - : isLatin1(atom->hasLatin1Chars()), length(atom->length()), atom(atom) -{ - hash = atom->hash(); - if (isLatin1) { - latin1Chars = atom->latin1Chars(nogc); - MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash); - } else { - twoByteChars = atom->twoByteChars(nogc); - MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash); - } -} - -MOZ_ALWAYS_INLINE bool -AtomHasher::match(const AtomStateEntry& entry, const Lookup& lookup) -{ - JSAtom* key = entry.asPtrUnbarriered(); - if (lookup.atom) - return lookup.atom == key; - if (key->length() != lookup.length || key->hash() != lookup.hash) - return false; - - if (key->hasLatin1Chars()) { - const Latin1Char* keyChars = key->latin1Chars(lookup.nogc); - if (lookup.isLatin1) - return mozilla::PodEqual(keyChars, lookup.latin1Chars, lookup.length); - return EqualChars(keyChars, lookup.twoByteChars, lookup.length); - } - - const char16_t* keyChars = key->twoByteChars(lookup.nogc); - if (lookup.isLatin1) - return EqualChars(lookup.latin1Chars, keyChars, lookup.length); - return mozilla::PodEqual(keyChars, lookup.twoByteChars, lookup.length); -} - inline Handle<PropertyName*> TypeName(JSType type, const JSAtomState& names) { MOZ_ASSERT(type < JSTYPE_LIMIT); JS_STATIC_ASSERT(offsetof(JSAtomState, undefined) + JSTYPE_LIMIT * sizeof(ImmutablePropertyNamePtr) <= sizeof(JSAtomState)); JS_STATIC_ASSERT(JSTYPE_UNDEFINED == 0); @@ -252,17 +167,11 @@ ClassName(JSProtoKey key, JSAtomState& a MOZ_ASSERT(key < JSProto_LIMIT); JS_STATIC_ASSERT(offsetof(JSAtomState, Null) + JSProto_LIMIT * sizeof(ImmutablePropertyNamePtr) <= sizeof(JSAtomState)); JS_STATIC_ASSERT(JSProto_Null == 0); return (&atomState.Null)[key]; } -inline Handle<PropertyName*> -ClassName(JSProtoKey key, JSContext* cx) -{ - return ClassName(key, cx->names()); -} - } // namespace js #endif /* jsatominlines_h */
--- a/js/src/jsbool.cpp +++ b/js/src/jsbool.cpp @@ -6,17 +6,16 @@ /* * JS boolean implementation. */ #include "jsboolinlines.h" #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsobj.h" #include "jstypes.h" #include "jit/InlinableNatives.h" #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" #include "vm/StringBuffer.h"
--- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -23,17 +23,16 @@ # include <android/log.h> # include <fstream> # include <string> #endif // ANDROID #ifdef XP_WIN #include <processthreadsapi.h> #endif // XP_WIN -#include "jsatom.h" #include "jscompartment.h" #include "jsdtoa.h" #include "jsexn.h" #include "jsfun.h" #include "jsiter.h" #include "jsnativestack.h" #include "jsobj.h" #include "jsopcode.h"
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -21,17 +21,16 @@ #include "jit/JitOptions.h" #include "js/Date.h" #include "js/Proxy.h" #include "js/RootingAPI.h" #include "proxy/DeadObjectProxy.h" #include "vm/Debugger.h" #include "vm/WrapperObject.h" -#include "jsatominlines.h" #include "jsfuninlines.h" #include "jsgcinlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #include "gc/Marking-inl.h" #include "vm/NativeObject-inl.h" #include "vm/UnboxedObject-inl.h"
--- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -1224,17 +1224,17 @@ js::DumpHeap(JSContext* cx, FILE* fp, js DumpHeapTracer dtrc(fp, cx); fprintf(dtrc.output, "# Roots.\n"); { JSRuntime* rt = cx->runtime(); js::gc::AutoPrepareForTracing prep(cx, WithAtoms); gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP); - rt->gc.traceRuntime(&dtrc, prep.session().lock); + rt->gc.traceRuntime(&dtrc, prep.session()); } fprintf(dtrc.output, "# Weak maps.\n"); WeakMapBase::traceAllMappings(&dtrc); fprintf(dtrc.output, "==========\n"); dtrc.prefix = "> ";
--- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -15,17 +15,16 @@ #include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Range.h" #include <string.h> #include "jsapi.h" #include "jsarray.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsobj.h" #include "jsscript.h" #include "jsstr.h" #include "jstypes.h" #include "jswrapper.h" #include "builtin/Eval.h"
--- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -252,17 +252,17 @@ class JSFunction : public js::NativeObje return nonLazyScript()->hasBaselineScript() || nonLazyScript()->hasIonScript(); } bool hasJitEntry() const { return hasScript() || isNativeWithJitEntry(); } /* Compound attributes: */ bool isBuiltin() const { - return isBuiltinNative() || isSelfHostedBuiltin(); + return isBuiltinNative() || isNativeWithJitEntry() || isSelfHostedBuiltin(); } bool isNamedLambda() const { return isLambda() && displayAtom() && !hasCompileTimeName() && !hasGuessedAtom(); } bool hasLexicalThis() const { return isArrow();
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -201,17 +201,16 @@ #include <initializer_list> #include <string.h> #ifndef XP_WIN # include <sys/mman.h> # include <unistd.h> #endif #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jscompartment.h" #include "jsfriendapi.h" #include "jsobj.h" #include "jsprf.h" #include "jsscript.h" #include "jstypes.h" #include "jsutil.h" @@ -2816,17 +2815,17 @@ GCRuntime::updateAllCellPointers(MovingT /* * Update pointers to relocated cells in a single zone by doing a traversal of * that zone's arenas and calling per-zone sweep hooks. * * The latter is necessary to update weak references which are not marked as * part of the traversal. */ void -GCRuntime::updateZonePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock) +GCRuntime::updateZonePointersToRelocatedCells(Zone* zone) { MOZ_ASSERT(!rt->isBeingDestroyed()); MOZ_ASSERT(zone->isGCCompacting()); gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT_UPDATE); MovingTracer trc(rt); zone->fixupAfterMovingGC(); @@ -2857,28 +2856,28 @@ GCRuntime::updateZonePointersToRelocated for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) callWeakPointerCompartmentCallbacks(comp); } /* * Update runtime-wide pointers to relocated cells. */ void -GCRuntime::updateRuntimePointersToRelocatedCells(AutoLockForExclusiveAccess& lock) +GCRuntime::updateRuntimePointersToRelocatedCells(AutoTraceSession& session) { MOZ_ASSERT(!rt->isBeingDestroyed()); gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::COMPACT_UPDATE); MovingTracer trc(rt); JSCompartment::fixupCrossCompartmentWrappersAfterMovingGC(&trc); rt->geckoProfiler().fixupStringsMapAfterMovingGC(); - traceRuntimeForMajorGC(&trc, lock); + traceRuntimeForMajorGC(&trc, session); // Mark roots to update them. { gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::MARK_ROOTS); Debugger::traceAllForMovingGC(&trc); Debugger::traceIncomingCrossCompartmentEdges(&trc); // Mark all gray roots, making sure we call the trace callback to get the @@ -3924,17 +3923,17 @@ class MOZ_RAII js::gc::AutoRunParallelTa } void run() override { func_(runtime()); } }; void -GCRuntime::purgeRuntime(AutoLockForExclusiveAccess& lock) +GCRuntime::purgeRuntime() { gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::PURGE); for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) comp->purge(); for (GCZonesIter zone(rt); !zone.done(); zone.next()) { zone->atomCache().clearAndShrink(); @@ -4243,26 +4242,32 @@ UnmarkCollectedZones(JSRuntime* rt) static void BufferGrayRoots(JSRuntime* rt) { rt->gc.bufferGrayRoots(); } bool -GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock) -{ +GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoTraceSession& session) +{ + MOZ_ASSERT(session.maybeLock.isSome()); + #ifdef DEBUG if (fullCompartmentChecks) checkForCompartmentMismatches(); #endif - if (!prepareZonesForCollection(reason, &isFull.ref(), lock)) + if (!prepareZonesForCollection(reason, &isFull.ref(), session.lock())) return false; + /* If we're not collecting the atoms zone we can release the lock now. */ + if (!atomsZone->isCollecting()) + session.maybeLock.reset(); + /* * Ensure that after the start of a collection we don't allocate into any * existing arenas, as this can cause unreachable things to be marked. */ if (isIncremental) { for (GCZonesIter zone(rt); !zone.done(); zone.next()) zone->arenas.prepareForIncrementalGC(); } @@ -4316,24 +4321,24 @@ GCRuntime::beginMarkPhase(JS::gcreason:: * We must purge the runtime at the beginning of an incremental GC. The * danger if we purge later is that the snapshot invariant of * incremental GC will be broken, as follows. If some object is * reachable only through some cache (say the dtoaCache) then it will * not be part of the snapshot. If we purge after root marking, then * the mutator could obtain a pointer to the object and start using * it. This object might never be marked, so a GC hazard would exist. */ - purgeRuntime(lock); + purgeRuntime(); } /* * Mark phase. */ gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK); - traceRuntimeForMajorGC(gcmarker, lock); + traceRuntimeForMajorGC(gcmarker, session); if (isIncremental) markCompartments(); updateMallocCountersOnGCStart(); /* * Process any queued source compressions during the start of a major @@ -4523,17 +4528,17 @@ struct GCChunkHasher { } }; class js::gc::MarkingValidator { public: explicit MarkingValidator(GCRuntime* gc); ~MarkingValidator(); - void nonIncrementalMark(AutoLockForExclusiveAccess& lock); + void nonIncrementalMark(AutoTraceSession& session); void validate(); private: GCRuntime* gc; bool initialized; typedef HashMap<Chunk*, ChunkBitmap*, GCChunkHasher, SystemAllocPolicy> BitmapMap; BitmapMap map; @@ -4549,17 +4554,17 @@ js::gc::MarkingValidator::~MarkingValida if (!map.initialized()) return; for (BitmapMap::Range r(map.all()); !r.empty(); r.popFront()) js_delete(r.front().value()); } void -js::gc::MarkingValidator::nonIncrementalMark(AutoLockForExclusiveAccess& lock) +js::gc::MarkingValidator::nonIncrementalMark(AutoTraceSession& session) { /* * Perform a non-incremental mark for all collecting zones and record * the results for later comparison. * * Currently this does not validate gray marking. */ @@ -4643,17 +4648,17 @@ js::gc::MarkingValidator::nonIncremental for (auto chunk = gc->allNonEmptyChunks(lock); !chunk.done(); chunk.next()) chunk->bitmap.clear(); } } { gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::MARK); - gc->traceRuntimeForMajorGC(gcmarker, lock); + gc->traceRuntimeForMajorGC(gcmarker, session); gc->incrementalState = State::Mark; auto unlimited = SliceBudget::unlimited(); MOZ_RELEASE_ASSERT(gc->marker.drainMarkStack(unlimited)); } gc->incrementalState = State::Sweep; { @@ -4764,24 +4769,24 @@ js::gc::MarkingValidator::validate() } } } } #endif // JS_GC_ZEAL void -GCRuntime::computeNonIncrementalMarkingForValidation(AutoLockForExclusiveAccess& lock) +GCRuntime::computeNonIncrementalMarkingForValidation(AutoTraceSession& session) { #ifdef JS_GC_ZEAL MOZ_ASSERT(!markingValidator); if (isIncremental && hasZealMode(ZealMode::IncrementalMarkingValidator)) markingValidator = js_new<MarkingValidator>(this); if (markingValidator) - markingValidator->nonIncrementalMark(lock); + markingValidator->nonIncrementalMark(session); #endif } void GCRuntime::validateIncrementalMarking() { #ifdef JS_GC_ZEAL if (markingValidator) @@ -4872,20 +4877,20 @@ JSCompartment::findOutgoingEdges(ZoneCom void Zone::findOutgoingEdges(ZoneComponentFinder& finder) { /* * Any compartment may have a pointer to an atom in the atoms * compartment, and these aren't in the cross compartment map. */ - JSRuntime* rt = runtimeFromActiveCooperatingThread(); - Zone* atomsZone = rt->atomsCompartment(finder.lock)->zone(); - if (atomsZone->isGCMarking()) - finder.addEdgeTo(atomsZone); + if (Zone* zone = finder.maybeAtomsZone) { + MOZ_ASSERT(zone->isCollecting()); + finder.addEdgeTo(zone); + } for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next()) comp->findOutgoingEdges(finder); for (ZoneSet::Range r = gcSweepGroupEdges().all(); !r.empty(); r.popFront()) { if (r.front()->isGCMarking()) finder.addEdgeTo(r.front()); } @@ -4910,25 +4915,26 @@ GCRuntime::findInterZoneEdges() if (!WeakMapBase::findInterZoneEdges(zone)) return false; } return true; } void -GCRuntime::groupZonesForSweeping(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock) +GCRuntime::groupZonesForSweeping(JS::gcreason::Reason reason) { #ifdef DEBUG for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) MOZ_ASSERT(zone->gcSweepGroupEdges().empty()); #endif JSContext* cx = TlsContext.get(); - ZoneComponentFinder finder(cx->nativeStackLimit[JS::StackForSystemCode], lock); + Zone* maybeAtomsZone = atomsZone->wasGCStarted() ? atomsZone.ref() : nullptr; + ZoneComponentFinder finder(cx->nativeStackLimit[JS::StackForSystemCode], maybeAtomsZone); if (!isIncremental || !findInterZoneEdges()) finder.useOneComponent(); #ifdef JS_GC_ZEAL // Use one component for IncrementalSweepThenFinish zeal mode. if (isIncremental && reason == JS::gcreason::DEBUG_GC && hasZealMode(ZealMode::IncrementalSweepThenFinish)) { @@ -5730,45 +5736,45 @@ GCRuntime::endSweepingSweepGroup(FreeOp* arenasAllocatedDuringSweep = arena->getNextAllocDuringSweep(); arena->unsetAllocDuringSweep(); } return Finished; } void -GCRuntime::beginSweepPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock) +GCRuntime::beginSweepPhase(JS::gcreason::Reason reason, AutoTraceSession& session) { /* * Sweep phase. * * Finalize as we sweep, outside of lock but with CurrentThreadIsHeapBusy() * true so that any attempt to allocate a GC-thing from a finalizer will * fail, rather than nest badly and leave the unmarked newborn to be swept. */ MOZ_ASSERT(!abortSweepAfterCurrentGroup); AutoSetThreadIsSweeping threadIsSweeping; releaseHeldRelocatedArenas(); - computeNonIncrementalMarkingForValidation(lock); + computeNonIncrementalMarkingForValidation(session); gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP); sweepOnBackgroundThread = reason != JS::gcreason::DESTROY_RUNTIME && !TraceEnabled() && CanUseExtraThreads(); releaseObservedTypes = shouldReleaseObservedTypes(); AssertNoWrappersInGrayList(rt); DropStringWrappers(rt); - groupZonesForSweeping(reason, lock); + groupZonesForSweeping(reason); sweepActions->assertFinished(); // We must not yield after this point until we start sweeping the first sweep // group. safeToYield = false; } @@ -5935,24 +5941,17 @@ GCRuntime::sweepAtomsTable(FreeOp* fop, return NotFinished; JSAtom* atom = atomsToSweep.front().asPtrUnbarriered(); if (IsAboutToBeFinalizedUnbarriered(&atom)) atomsToSweep.removeFront(); atomsToSweep.popFront(); } - // Add any new atoms from the secondary table. - AutoEnterOOMUnsafeRegion oomUnsafe; - AtomSet* atomsTable = rt->atomsForSweeping(); - MOZ_ASSERT(atomsTable); - for (auto r = rt->atomsAddedWhileSweeping()->all(); !r.empty(); r.popFront()) { - if (!atomsTable->putNew(AtomHasher::Lookup(r.front().asPtrUnbarriered()), r.front())) - oomUnsafe.crash("Adding atom from secondary table after sweep"); - } + MergeAtomsAddedWhileSweeping(rt); rt->destroyAtomsAddedWhileSweepingTable(); maybeAtoms.reset(); return Finished; } class js::gc::WeakCacheSweepIterator { @@ -6440,17 +6439,17 @@ GCRuntime::initSweepActions() Call(&GCRuntime::sweepShapeTree), Call(&GCRuntime::releaseSweptEmptyArenas))), Call(&GCRuntime::endSweepingSweepGroup))); return sweepActions != nullptr; } IncrementalProgress -GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess& lock) +GCRuntime::performSweepActions(SliceBudget& budget) { AutoSetThreadIsSweeping threadIsSweeping; gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP); FreeOp fop(rt); // Drain the mark stack, except in the first sweep slice where we must not // yield to the mutator until we've starting sweeping a sweep group. @@ -6488,17 +6487,17 @@ GCRuntime::allCCVisibleZonesWereCollecte return false; } } return true; } void -GCRuntime::endSweepPhase(bool destroyingRuntime, AutoLockForExclusiveAccess& lock) +GCRuntime::endSweepPhase(bool destroyingRuntime) { sweepActions->assertFinished(); AutoSetThreadIsSweeping threadIsSweeping; gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP); FreeOp fop(rt); @@ -6565,17 +6564,17 @@ GCRuntime::beginCompactPhase() } MOZ_ASSERT(!relocatedArenasToRelease); startedCompacting = true; } IncrementalProgress GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget, - AutoLockForExclusiveAccess& lock) + AutoTraceSession& session) { assertBackgroundSweepingFinished(); MOZ_ASSERT(startedCompacting); gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT); // TODO: JSScripts can move. If the sampler interrupts the GC in the // middle of relocating an arena, invalid JSScript pointers may be @@ -6589,28 +6588,28 @@ GCRuntime::compactPhase(JS::gcreason::Re Zone* zone = zonesToMaybeCompact.ref().front(); zonesToMaybeCompact.ref().removeFront(); MOZ_ASSERT(zone->group()->nursery().isEmpty()); zone->changeGCState(Zone::Finished, Zone::Compact); if (relocateArenas(zone, reason, relocatedArenas, sliceBudget)) { - updateZonePointersToRelocatedCells(zone, lock); + updateZonePointersToRelocatedCells(zone); relocatedZones.append(zone); } else { zone->changeGCState(Zone::Compact, Zone::Finished); } if (sliceBudget.isOverBudget()) break; } if (!relocatedZones.isEmpty()) { - updateRuntimePointersToRelocatedCells(lock); + updateRuntimePointersToRelocatedCells(session); do { Zone* zone = relocatedZones.front(); relocatedZones.removeFront(); zone->changeGCState(Zone::Compact, Zone::Finished); } while (!relocatedZones.isEmpty()); } @@ -6691,24 +6690,27 @@ AllNurseriesAreEmpty(JSRuntime* rt) return false; } return true; } #endif /* Start a new heap session. */ AutoTraceSession::AutoTraceSession(JSRuntime* rt, JS::HeapState heapState) - : lock(rt), - runtime(rt), + : runtime(rt), prevState(TlsContext.get()->heapState), pseudoFrame(TlsContext.get(), HeapStateToLabel(heapState), ProfileEntry::Category::GC) { MOZ_ASSERT(prevState == JS::HeapState::Idle); MOZ_ASSERT(heapState != JS::HeapState::Idle); MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, AllNurseriesAreEmpty(rt)); + + // Session always begins with lock held, see comment in class definition. + maybeLock.emplace(rt); + TlsContext.get()->heapState = heapState; } AutoTraceSession::~AutoTraceSession() { MOZ_ASSERT(JS::CurrentThreadIsHeapBusy()); TlsContext.get()->heapState = prevState; } @@ -6729,17 +6731,17 @@ GCRuntime::canChangeActiveContext(JSCont && !cx->suppressGC && !cx->inUnsafeRegion && !cx->generationalDisabled && !cx->compactingDisabledCount && !cx->keepAtoms; } GCRuntime::IncrementalResult -GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoLockForExclusiveAccess& lock) +GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoTraceSession& session) { MOZ_ASSERT(reason != gc::AbortReason::None); switch (incrementalState) { case State::NotActive: return IncrementalResult::Ok; case State::MarkRoots: @@ -6778,17 +6780,17 @@ GCRuntime::resetIncrementalGC(gc::AbortR /* Finish sweeping the current sweep group, then abort. */ abortSweepAfterCurrentGroup = true; /* Don't perform any compaction after sweeping. */ bool wasCompacting = isCompacting; isCompacting = false; auto unlimited = SliceBudget::unlimited(); - incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock); + incrementalCollectSlice(unlimited, JS::gcreason::RESET, session); isCompacting = wasCompacting; { gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD); rt->gc.waitBackgroundSweepOrAllocEnd(); } break; @@ -6799,40 +6801,40 @@ GCRuntime::resetIncrementalGC(gc::AbortR gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD); rt->gc.waitBackgroundSweepOrAllocEnd(); } bool wasCompacting = isCompacting; isCompacting = false; auto unlimited = SliceBudget::unlimited(); - incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock); + incrementalCollectSlice(unlimited, JS::gcreason::RESET, session); isCompacting = wasCompacting; break; } case State::Compact: { bool wasCompacting = isCompacting; isCompacting = true; startedCompacting = true; zonesToMaybeCompact.ref().clear(); auto unlimited = SliceBudget::unlimited(); - incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock); + incrementalCollectSlice(unlimited, JS::gcreason::RESET, session); isCompacting = wasCompacting; break; } case State::Decommit: { auto unlimited = SliceBudget::unlimited(); - incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock); + incrementalCollectSlice(unlimited, JS::gcreason::RESET, session); break; } } stats().reset(reason); #ifdef DEBUG assertBackgroundSweepingFinished(); @@ -6914,18 +6916,25 @@ ShouldCleanUpEverything(JS::gcreason::Re // During shutdown, we must clean everything up, for the sake of leak // detection. When a runtime has no contexts, or we're doing a GC before a // shutdown CC, those are strong indications that we're shutting down. return IsShutdownGC(reason) || gckind == GC_SHRINK; } void GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason, - AutoLockForExclusiveAccess& lock) -{ + AutoTraceSession& session) +{ + /* + * Drop the exclusive access lock if we are in an incremental collection + * that does not touch the atoms zone. + */ + if (isIncrementalGCInProgress() && !atomsZone->isCollecting()) + session.maybeLock.reset(); + AutoGCSlice slice(rt); bool destroyingRuntime = (reason == JS::gcreason::DESTROY_RUNTIME); initialState = incrementalState; #ifdef JS_GC_ZEAL /* @@ -6960,17 +6969,17 @@ GCRuntime::incrementalCollectSlice(Slice lastMarkSlice = false; rootsRemoved = false; incrementalState = State::MarkRoots; MOZ_FALLTHROUGH; case State::MarkRoots: - if (!beginMarkPhase(reason, lock)) { + if (!beginMarkPhase(reason, session)) { incrementalState = State::NotActive; return; } if (!destroyingRuntime) pushZealSelectedObjects(); incrementalState = State::Mark; @@ -7014,25 +7023,25 @@ GCRuntime::incrementalCollectSlice(Slice (useZeal && hasZealMode(ZealMode::IncrementalMarkAllThenFinish)))) { lastMarkSlice = true; break; } incrementalState = State::Sweep; - beginSweepPhase(reason, lock); + beginSweepPhase(reason, session); MOZ_FALLTHROUGH; case State::Sweep: - if (performSweepActions(budget, lock) == NotFinished) + if (performSweepActions(budget) == NotFinished) break; - endSweepPhase(destroyingRuntime, lock); + endSweepPhase(destroyingRuntime); incrementalState = State::Finalize; MOZ_FALLTHROUGH; case State::Finalize: { gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD); @@ -7067,17 +7076,17 @@ GCRuntime::incrementalCollectSlice(Slice MOZ_FALLTHROUGH; case State::Compact: if (isCompacting) { if (!startedCompacting) beginCompactPhase(); - if (compactPhase(reason, budget, lock) == NotFinished) + if (compactPhase(reason, budget, session) == NotFinished) break; endCompactPhase(reason); } startDecommit(); incrementalState = State::Decommit; @@ -7135,50 +7144,50 @@ CheckZoneIsScheduled(Zone* zone, JS::gcr } fflush(stderr); MOZ_CRASH("Zone not scheduled"); #endif } GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC(bool nonincrementalByAPI, JS::gcreason::Reason reason, - SliceBudget& budget, AutoLockForExclusiveAccess& lock) + SliceBudget& budget, AutoTraceSession& session) { if (nonincrementalByAPI) { stats().nonincremental(gc::AbortReason::NonIncrementalRequested); budget.makeUnlimited(); // Reset any in progress incremental GC if this was triggered via the // API. This isn't required for correctness, but sometimes during tests // the caller expects this GC to collect certain objects, and we need // to make sure to collect everything possible. if (reason != JS::gcreason::ALLOC_TRIGGER) - return resetIncrementalGC(gc::AbortReason::NonIncrementalRequested, lock); + return resetIncrementalGC(gc::AbortReason::NonIncrementalRequested, session); return IncrementalResult::Ok; } if (reason == JS::gcreason::ABORT_GC) { budget.makeUnlimited(); stats().nonincremental(gc::AbortReason::AbortRequested); - return resetIncrementalGC(gc::AbortReason::AbortRequested, lock); + return resetIncrementalGC(gc::AbortReason::AbortRequested, session); } AbortReason unsafeReason = IsIncrementalGCUnsafe(rt); if (unsafeReason == AbortReason::None) { if (reason == JS::gcreason::COMPARTMENT_REVIVED) unsafeReason = gc::AbortReason::CompartmentRevived; else if (mode != JSGC_MODE_INCREMENTAL) unsafeReason = gc::AbortReason::ModeChange; } if (unsafeReason != AbortReason::None) { budget.makeUnlimited(); stats().nonincremental(unsafeReason); - return resetIncrementalGC(unsafeReason, lock); + return resetIncrementalGC(unsafeReason, session); } if (mallocCounter.shouldTriggerGC(tunables) == NonIncrementalTrigger) { budget.makeUnlimited(); stats().nonincremental(AbortReason::MallocBytesTrigger); } bool reset = false; @@ -7198,17 +7207,17 @@ GCRuntime::budgetIncrementalGC(bool noni stats().nonincremental(AbortReason::MallocBytesTrigger); } if (isIncrementalGCInProgress() && zone->isGCScheduled() != zone->wasGCStarted()) reset = true; } if (reset) - return resetIncrementalGC(AbortReason::ZoneChange, lock); + return resetIncrementalGC(AbortReason::ZoneChange, session); return IncrementalResult::Ok; } namespace { class AutoScheduleZonesForGC { @@ -7344,27 +7353,27 @@ GCRuntime::gcCycle(bool nonincrementalBy // for it at the start of every slice. allocTask.cancel(GCParallelTask::CancelAndWait); } // We don't allow off-thread parsing to start while we're doing an // incremental GC. MOZ_ASSERT_IF(rt->activeGCInAtomsZone(), !rt->hasHelperThreadZones()); - auto result = budgetIncrementalGC(nonincrementalByAPI, reason, budget, session.lock); + auto result = budgetIncrementalGC(nonincrementalByAPI, reason, budget, session); // If an ongoing incremental GC was reset, we may need to restart. if (result == IncrementalResult::Reset) { MOZ_ASSERT(!isIncrementalGCInProgress()); return result; } TraceMajorGCStart(); - incrementalCollectSlice(budget, reason, session.lock); + incrementalCollectSlice(budget, reason, session); chunkAllocationSinceLastGC = false; #ifdef JS_GC_ZEAL /* Keeping these around after a GC is dangerous. */ clearSelectedForMarking(); #endif
--- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -11,17 +11,16 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/Unused.h" #include "jsarray.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsobj.h" #include "jsopcode.h" #include "jsscript.h" #include "jstypes.h" #include "jsutil.h" #include "ds/Sort.h"
--- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -24,17 +24,16 @@ #include "fdlibm.h" #ifdef XP_WIN # include "jswin.h" #endif #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jscompartment.h" #include "jslibmath.h" #include "jstypes.h" #include "jit/InlinableNatives.h" #include "js/Class.h" #include "vm/Time.h"
--- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -16,29 +16,26 @@ #include "mozilla/RangedPtr.h" #ifdef HAVE_LOCALECONV #include <locale.h> #endif #include <math.h> #include <string.h> -#include "jsatom.h" #include "jscntxt.h" #include "jsdtoa.h" #include "jsobj.h" #include "jsstr.h" #include "jstypes.h" #include "js/Conversions.h" #include "vm/GlobalObject.h" #include "vm/StringBuffer.h" -#include "jsatominlines.h" - #include "vm/NativeObject-inl.h" #include "vm/NumberObject-inl.h" #include "vm/String-inl.h" using namespace js; using mozilla::Abs; using mozilla::ArrayLength;
--- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -14,17 +14,16 @@ #include "mozilla/MathAlgorithms.h" #include "mozilla/MemoryReporting.h" #include "mozilla/TemplateLib.h" #include <string.h> #include "jsapi.h" #include "jsarray.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsexn.h" #include "jsfriendapi.h" #include "jsfun.h" #include "jsiter.h" #include "jsnum.h" #include "jsopcode.h" #include "jsprf.h" @@ -47,17 +46,16 @@ #include "js/UniquePtr.h" #include "vm/ArgumentsObject.h" #include "vm/Interpreter.h" #include "vm/ProxyObject.h" #include "vm/RegExpStaticsObject.h" #include "vm/Shape.h" #include "vm/TypedArrayObject.h" -#include "jsatominlines.h" #include "jsboolinlines.h" #include "jscntxtinlines.h" #include "jscompartmentinlines.h" #include "builtin/TypedObject-inl.h" #include "gc/Marking-inl.h" #include "vm/ArrayObject-inl.h" #include "vm/BooleanObject-inl.h"
--- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -6,30 +6,28 @@ #include "json.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Range.h" #include "mozilla/ScopeExit.h" #include "jsarray.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsnum.h" #include "jsobj.h" #include "jsstr.h" #include "jstypes.h" #include "jsutil.h" #include "vm/Interpreter.h" #include "vm/JSONParser.h" #include "vm/StringBuffer.h" #include "jsarrayinlines.h" -#include "jsatominlines.h" #include "jsboolinlines.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::gc; using mozilla::IsFinite;
--- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -18,17 +18,16 @@ #include <algorithm> #include <ctype.h> #include <inttypes.h> #include <stdio.h> #include <string.h> #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jscompartment.h" #include "jsfun.h" #include "jsnum.h" #include "jsobj.h" #include "jsprf.h" #include "jsscript.h" #include "jsstr.h"
--- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -18,17 +18,16 @@ #include "mozilla/Sprintf.h" #include "mozilla/Unused.h" #include "mozilla/Vector.h" #include <algorithm> #include <string.h> #include "jsapi.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsfun.h" #include "jsobj.h" #include "jsopcode.h" #include "jsprf.h" #include "jstypes.h" #include "jsutil.h" #include "jswrapper.h"
--- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -10,17 +10,16 @@ #define jsscript_h #include "mozilla/Atomics.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/Variant.h" -#include "jsatom.h" #include "jsopcode.h" #include "jstypes.h" #include "frontend/NameAnalysisTypes.h" #include "gc/Barrier.h" #include "gc/Rooting.h" #include "jit/IonCode.h" #include "js/UbiNode.h"
--- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -17,17 +17,16 @@ #include "mozilla/Unused.h" #include <ctype.h> #include <limits> #include <string.h> #include "jsapi.h" #include "jsarray.h" -#include "jsatom.h" #include "jsbool.h" #include "jscntxt.h" #include "jsnum.h" #include "jsobj.h" #include "jsopcode.h" #include "jstypes.h" #include "jsutil.h"
--- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -14,17 +14,16 @@ #include "jscntxt.h" #include "jsfun.h" #include "jswrapper.h" #include "proxy/DeadObjectProxy.h" #include "proxy/ScriptedProxyHandler.h" #include "vm/WrapperObject.h" -#include "jsatominlines.h" #include "jsobjinlines.h" #include "gc/Marking-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::gc;
--- a/js/src/proxy/SecurityWrapper.cpp +++ b/js/src/proxy/SecurityWrapper.cpp @@ -1,18 +1,21 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "jsapi.h" +#include "jsfriendapi.h" #include "jswrapper.h" -#include "jsatominlines.h" +#include "NamespaceImports.h" + +#include "vm/String.h" using namespace js; template <class Base> bool SecurityWrapper<Base>::enter(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act, bool mayThrow, bool* bp) const {
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -47,17 +47,16 @@ # include <sys/mman.h> # include <sys/stat.h> # include <sys/wait.h> # include <unistd.h> #endif #include "jsapi.h" #include "jsarray.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsfriendapi.h" #include "jsfun.h" #include "jsobj.h" #include "jsprf.h" #include "jsscript.h" #include "jstypes.h" #include "jsutil.h"
new file mode 100644 --- /dev/null +++ b/js/src/tests/non262/Intl/tolower-ascii-equivalent.js @@ -0,0 +1,28 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Language tags are processed case-insensitive, but unconditionally calling +// the built-in String.prototype.toLowerCase() or toUpperCase() function +// before parsing a language tag can map non-ASCII characters into the ASCII +// range. +// +// Validate the BCP47 language tag parser handles this case (pun intended) +// correctly by passing language tags which contain U+212A (KELVIN SIGN) and +// U+0131 (LATIN SMALL LETTER DOTLESS I) to Intl.getCanonicalLocales(). + +// The lower-case form of "i-ha\u212A" is "i-hak". +assertEq("i-hak", "i-ha\u212A".toLowerCase()); + +// The upper-case form of "\u0131-hak" is "I-HAK". +assertEq("I-HAK", "\u0131-hak".toUpperCase()); + +// "i-hak" is a valid language tag. +assertEqArray(Intl.getCanonicalLocales("i-hak"), ["hak"]); + +// But "i-ha\u212A" is not a valid language tag. +assertThrowsInstanceOf(() => Intl.getCanonicalLocales("i-ha\u212A"), RangeError); + +// And "\u0131-hak" is also not a valid language tag. +assertThrowsInstanceOf(() => Intl.getCanonicalLocales("\u0131-hak"), RangeError); + +if (typeof reportCompare === 'function') + reportCompare(0, 0);
new file mode 100644 --- /dev/null +++ b/js/src/tests/non262/Intl/uppercase-privateuse.js @@ -0,0 +1,8 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// privateuse subtags can start with upper-case 'X'. +assertEqArray(Intl.getCanonicalLocales("de-X-a-a"), ["de-x-a-a"]); +assertEqArray(Intl.getCanonicalLocales("X-a-a"), ["x-a-a"]); + +if (typeof reportCompare === 'function') + reportCompare(0, 0);
--- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -45,18 +45,16 @@ #include "js/MemoryMetrics.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/SharedArrayObject.h" #include "vm/WrapperObject.h" #include "wasm/WasmSignalHandlers.h" #include "wasm/WasmTypes.h" -#include "jsatominlines.h" - #include "gc/Marking-inl.h" #include "gc/Nursery-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Shape-inl.h" using JS::ToInt32; using mozilla::DebugOnly;
new file mode 100644 --- /dev/null +++ b/js/src/vm/AtomsTable.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/. */ + +/* + * Implementation details of the atoms table. + */ + +#ifndef vm_AtomsTable_h +#define vm_AtomsTable_h + +#include "js/GCHashTable.h" + +class JSAtom; + +namespace js { + +class AtomStateEntry +{ + uintptr_t bits; + + static const uintptr_t NO_TAG_MASK = uintptr_t(-1) - 1; + + public: + AtomStateEntry() : bits(0) {} + AtomStateEntry(const AtomStateEntry& other) : bits(other.bits) {} + AtomStateEntry(JSAtom* ptr, bool tagged) + : bits(uintptr_t(ptr) | uintptr_t(tagged)) + { + MOZ_ASSERT((uintptr_t(ptr) & 0x1) == 0); + } + + bool isPinned() const { + return bits & 0x1; + } + + /* + * Non-branching code sequence. Note that the const_cast is safe because + * the hash function doesn't consider the tag to be a portion of the key. + */ + void setPinned(bool pinned) const { + const_cast<AtomStateEntry*>(this)->bits |= uintptr_t(pinned); + } + + JSAtom* asPtrUnbarriered() const { + MOZ_ASSERT(bits); + return reinterpret_cast<JSAtom*>(bits & NO_TAG_MASK); + } + + JSAtom* asPtr(JSContext* cx) const; + + bool needsSweep() { + JSAtom* atom = asPtrUnbarriered(); + return gc::IsAboutToBeFinalizedUnbarriered(&atom); + } +}; + +struct AtomHasher +{ + struct Lookup; + static inline HashNumber hash(const Lookup& l); + static MOZ_ALWAYS_INLINE bool match(const AtomStateEntry& entry, const Lookup& lookup); + static void rekey(AtomStateEntry& k, const AtomStateEntry& newKey) { k = newKey; } +}; + +using AtomSet = JS::GCHashSet<AtomStateEntry, AtomHasher, SystemAllocPolicy>; + +// This class is a wrapper for AtomSet that is used to ensure the AtomSet is +// not modified. It should only expose read-only methods from AtomSet. +// Note however that the atoms within the table can be marked during GC. +class FrozenAtomSet +{ + AtomSet* mSet; + + public: + // This constructor takes ownership of the passed-in AtomSet. + explicit FrozenAtomSet(AtomSet* set) { mSet = set; } + + ~FrozenAtomSet() { js_delete(mSet); } + + MOZ_ALWAYS_INLINE AtomSet::Ptr readonlyThreadsafeLookup(const AtomSet::Lookup& l) const; + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mSet->sizeOfIncludingThis(mallocSizeOf); + } + + typedef AtomSet::Range Range; + + AtomSet::Range all() const { return mSet->all(); } +}; + +} // namespace js + +#endif /* vm_AtomTables_h */
--- a/js/src/vm/Caches.h +++ b/js/src/vm/Caches.h @@ -2,17 +2,16 @@ * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 vm_Caches_h #define vm_Caches_h -#include "jsatom.h" #include "jsbytecode.h" #include "jsmath.h" #include "jsobj.h" #include "jsscript.h" #include "frontend/SourceNotes.h" #include "gc/Tracer.h" #include "js/RootingAPI.h"
--- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -17,17 +17,16 @@ #include "vm/ArgumentsObject.h" #include "vm/AsyncFunction.h" #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" #include "vm/Shape.h" #include "vm/Xdr.h" #include "wasm/WasmInstance.h" -#include "jsatominlines.h" #include "jsscriptinlines.h" #include "gc/Marking-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Stack-inl.h" #include "vm/TypeInference-inl.h" using namespace js;
--- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -3,17 +3,16 @@ * 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 "vm/GeneratorObject.h" #include "jsobj.h" -#include "jsatominlines.h" #include "jsscriptinlines.h" #include "vm/ArrayObject-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Stack-inl.h" using namespace js;
--- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -2086,18 +2086,26 @@ js::StartOffThreadPromiseHelperTask(Prom if (!HelperThreadState().promiseHelperTasks(lock).append(task)) return false; HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock); return true; } void -GlobalHelperThreadState::trace(JSTracer* trc) +GlobalHelperThreadState::trace(JSTracer* trc, gc::AutoTraceSession& session) { + // There's an assertion that requires the exclusive access lock when tracing + // atoms (see AtomIsPinnedInRuntime). Due to mutex ordering requirements we + // need to take that lock before the helper thread lock, if we don't have it + // already. + Maybe<AutoLockForExclusiveAccess> exclusiveLock; + if (!session.maybeLock.isSome()) + exclusiveLock.emplace(trc->runtime()); + AutoLockHelperThreadState lock; for (auto builder : ionWorklist(lock)) builder->trace(trc); for (auto builder : ionFinishedList(lock)) builder->trace(trc); if (HelperThreadState().threads) { for (auto& helper : *HelperThreadState().threads) {
--- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -293,17 +293,17 @@ class GlobalHelperThreadState bool finishParseTask(JSContext* cx, ParseTaskKind kind, void* token, MutableHandle<ScriptVector> scripts); void cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token); void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask, Handle<GlobalObject*> global, JSCompartment* dest); - void trace(JSTracer* trc); + void trace(JSTracer* trc, js::gc::AutoTraceSession& session); JSScript* finishScriptParseTask(JSContext* cx, void* token); JSScript* finishScriptDecodeTask(JSContext* cx, void* token); bool finishMultiScriptsDecodeTask(JSContext* cx, void* token, MutableHandle<ScriptVector> scripts); JSObject* finishModuleParseTask(JSContext* cx, void* token); bool hasActiveThreads(const AutoLockHelperThreadState&); void waitForAllThreadsLocked(AutoLockHelperThreadState&);
--- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -11,17 +11,16 @@ #include "jscompartment.h" #include "jsnum.h" #include "jsstr.h" #include "jit/Ion.h" #include "vm/ArgumentsObject.h" -#include "jsatominlines.h" #include "jsobjinlines.h" #include "vm/EnvironmentObject-inl.h" #include "vm/Stack-inl.h" #include "vm/String-inl.h" #include "vm/UnboxedObject-inl.h" namespace js {
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -15,17 +15,16 @@ #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Sprintf.h" #include <string.h> #include "jsarray.h" -#include "jsatom.h" #include "jscntxt.h" #include "jsfun.h" #include "jsiter.h" #include "jslibmath.h" #include "jsnum.h" #include "jsobj.h" #include "jsopcode.h" #include "jsprf.h" @@ -44,17 +43,16 @@ #include "vm/GeneratorObject.h" #include "vm/Opcodes.h" #include "vm/Scope.h" #include "vm/Shape.h" #include "vm/Stopwatch.h" #include "vm/StringBuffer.h" #include "vm/TraceLogging.h" -#include "jsatominlines.h" #include "jsboolinlines.h" #include "jsfuninlines.h" #include "jsscriptinlines.h" #include "jit/JitFrames-inl.h" #include "vm/Debugger-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/GeckoProfiler-inl.h"
--- a/js/src/vm/RegExpShared.h +++ b/js/src/vm/RegExpShared.h @@ -11,17 +11,16 @@ #ifndef vm_RegExpShared_h #define vm_RegExpShared_h #include "mozilla/Assertions.h" #include "mozilla/MemoryReporting.h" #include "jsalloc.h" -#include "jsatom.h" #include "builtin/SelfHostingDefines.h" #include "gc/Barrier.h" #include "gc/Heap.h" #include "gc/Marking.h" #include "js/UbiNode.h" #include "js/Vector.h" #include "vm/ArrayObject.h"
--- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -19,17 +19,16 @@ #include <locale.h> #include <string.h> #ifdef JS_CAN_CHECK_THREADSAFE_ACCESSES # include <sys/mman.h> #endif -#include "jsatom.h" #include "jsmath.h" #include "jsobj.h" #include "jsscript.h" #include "jswin.h" #include "jswrapper.h" #include "builtin/Promise.h" #include "gc/FreeOp.h"
--- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -16,17 +16,16 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/Scoped.h" #include "mozilla/ThreadLocal.h" #include "mozilla/Vector.h" #include <setjmp.h> -#include "jsatom.h" #include "jsscript.h" #include "builtin/AtomicsObject.h" #include "builtin/intl/SharedIntlData.h" #include "builtin/Promise.h" #include "frontend/NameCollections.h" #include "gc/GCRuntime.h" #include "gc/Tracer.h"
--- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -51,17 +51,16 @@ #include "vm/Interpreter.h" #include "vm/Printer.h" #include "vm/RegExpObject.h" #include "vm/String.h" #include "vm/StringBuffer.h" #include "vm/TypedArrayObject.h" #include "vm/WrapperObject.h" -#include "jsatominlines.h" #include "jsfuninlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #include "gc/Iteration-inl.h" #include "vm/BooleanObject-inl.h" #include "vm/NativeObject-inl.h" #include "vm/NumberObject-inl.h"
--- a/js/src/vm/Shape-inl.h +++ b/js/src/vm/Shape-inl.h @@ -12,17 +12,16 @@ #include "mozilla/TypeTraits.h" #include "jsobj.h" #include "gc/Allocator.h" #include "vm/Interpreter.h" #include "vm/TypedArrayObject.h" -#include "jsatominlines.h" #include "jscntxtinlines.h" #include "gc/Marking-inl.h" namespace js { inline AutoKeepShapeTables::AutoKeepShapeTables(JSContext* cx) : cx_(cx),
--- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -7,17 +7,16 @@ /* JS symbol tables. */ #include "vm/Shape-inl.h" #include "mozilla/DebugOnly.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/PodOperations.h" -#include "jsatom.h" #include "jscntxt.h" #include "jshashutil.h" #include "jsobj.h" #include "gc/FreeOp.h" #include "gc/Policy.h" #include "js/HashTable.h"
--- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -29,17 +29,16 @@ #include "js/MemoryMetrics.h" #include "vm/HelperThreads.h" #include "vm/Opcodes.h" #include "vm/Printer.h" #include "vm/Shape.h" #include "vm/Time.h" #include "vm/UnboxedObject.h" -#include "jsatominlines.h" #include "jsscriptinlines.h" #include "gc/Iteration-inl.h" #include "gc/Marking-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::gc;
--- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -39,18 +39,16 @@ #include "vm/ArrayBufferObject.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/PIC.h" #include "vm/SelfHosting.h" #include "vm/SharedMem.h" #include "vm/WrapperObject.h" -#include "jsatominlines.h" - #include "gc/Nursery-inl.h" #include "gc/StoreBuffer-inl.h" #include "vm/ArrayBufferObject-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Shape-inl.h" using namespace js; using namespace js::gc; @@ -314,16 +312,21 @@ NewArray(JSContext* cx, uint32_t nelemen namespace { enum class SpeciesConstructorOverride { None, ArrayBuffer }; +enum class CreateSingleton { + Yes, + No +}; + template<typename NativeType> class TypedArrayObjectTemplate : public TypedArrayObject { friend class TypedArrayObject; public: static constexpr Scalar::Type ArrayTypeID() { return TypeIDOfType<NativeType>::id; } static bool ArrayTypeIsUnsigned() { return TypeIsUnsigned<NativeType>(); } @@ -402,20 +405,21 @@ class TypedArrayObjectTemplate : public { MOZ_ASSERT(proto); JSObject* obj = NewObjectWithClassProto(cx, instanceClass(), proto, allocKind); return obj ? &obj->as<TypedArrayObject>() : nullptr; } static TypedArrayObject* - makeTypedInstance(JSContext* cx, uint32_t len, gc::AllocKind allocKind) + makeTypedInstance(JSContext* cx, uint32_t len, CreateSingleton createSingleton, + gc::AllocKind allocKind) { const Class* clasp = instanceClass(); - if (len * sizeof(NativeType) >= TypedArrayObject::SINGLETON_BYTE_LENGTH) { + if (createSingleton == CreateSingleton::Yes) { JSObject* obj = NewBuiltinClassInstance(cx, clasp, allocKind, SingletonObject); if (!obj) return nullptr; return &obj->as<TypedArrayObject>(); } jsbytecode* pc; RootedScript script(cx, cx->currentScript(&pc)); @@ -431,18 +435,19 @@ class TypedArrayObjectTemplate : public { return nullptr; } return &obj->as<TypedArrayObject>(); } static TypedArrayObject* - makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, - uint32_t len, HandleObject proto) + makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, + CreateSingleton createSingleton, uint32_t byteOffset, uint32_t len, + HandleObject proto) { MOZ_ASSERT_IF(!buffer, byteOffset == 0); MOZ_ASSERT_IF(buffer, !buffer->isDetached()); MOZ_ASSERT(len < INT32_MAX / sizeof(NativeType)); gc::AllocKind allocKind = buffer ? GetGCObjectKind(instanceClass()) : AllocKindForLazyBuffer(len * sizeof(NativeType)); @@ -458,17 +463,17 @@ class TypedArrayObjectTemplate : public return nullptr; } AutoSetNewObjectMetadata metadata(cx); Rooted<TypedArrayObject*> obj(cx); if (proto && proto != checkProto) obj = makeProtoInstance(cx, proto, allocKind); else - obj = makeTypedInstance(cx, len, allocKind); + obj = makeTypedInstance(cx, len, createSingleton, allocKind); if (!obj) return nullptr; bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get()); obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer)); // This is invariant. Self-hosting code that sets BUFFER_SLOT // (if it does) must maintain it, should it need to. @@ -831,18 +836,22 @@ class TypedArrayObjectTemplate : public fromBufferSameCompartment(JSContext* cx, HandleArrayBufferObjectMaybeShared buffer, uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto) { // Steps 9-12. uint32_t length; if (!computeAndCheckLength(cx, buffer, byteOffset, lengthIndex, &length)) return nullptr; + CreateSingleton createSingleton = CreateSingleton::No; + if (length * sizeof(NativeType) >= TypedArrayObject::SINGLETON_BYTE_LENGTH) + createSingleton = CreateSingleton::Yes; + // Steps 13-17. - return makeInstance(cx, buffer, uint32_t(byteOffset), length, proto); + return makeInstance(cx, buffer, createSingleton, uint32_t(byteOffset), length, proto); } // Create a TypedArray object in another compartment. // // ES6 supports creating a TypedArray in global A (using global A's // TypedArray constructor) backed by an ArrayBuffer created in global B. // // Our TypedArrayObject implementation doesn't support a TypedArray in @@ -890,17 +899,18 @@ class TypedArrayObjectTemplate : public { JSAutoCompartment ac(cx, unwrappedBuffer); RootedObject wrappedProto(cx, protoRoot); if (!cx->compartment()->wrap(cx, &wrappedProto)) return nullptr; typedArray = - makeInstance(cx, unwrappedBuffer, uint32_t(byteOffset), length, wrappedProto); + makeInstance(cx, unwrappedBuffer, CreateSingleton::No, uint32_t(byteOffset), + length, wrappedProto); if (!typedArray) return nullptr; } if (!cx->compartment()->wrap(cx, &typedArray)) return nullptr; return typedArray; @@ -965,17 +975,17 @@ class TypedArrayObjectTemplate : public JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return nullptr; } Rooted<ArrayBufferObject*> buffer(cx); if (!maybeCreateArrayBuffer(cx, uint32_t(nelements), BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; - return makeInstance(cx, buffer, 0, uint32_t(nelements), proto); + return makeInstance(cx, buffer, CreateSingleton::No, 0, uint32_t(nelements), proto); } static bool AllocateArrayBuffer(JSContext* cx, HandleObject ctor, uint32_t count, uint32_t unit, MutableHandle<ArrayBufferObject*> buffer); static JSObject* @@ -1221,17 +1231,18 @@ TypedArrayObjectTemplate<T>::fromTypedAr // Step 19.b or 24.1.1.4 step 4. if (srcArray->hasDetachedBuffer()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return nullptr; } // Steps 3-4 (remaining part), 20-23. - Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, elementLength, proto)); + Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, CreateSingleton::No, 0, + elementLength, proto)); if (!obj) return nullptr; // Steps 19.c-f or 24.1.1.4 steps 5-7. MOZ_ASSERT(!obj->isSharedMemory()); if (isShared) { if (!ElementSpecific<T, SharedOps>::setFromTypedArray(obj, srcArray, 0)) return nullptr; @@ -1281,17 +1292,18 @@ TypedArrayObjectTemplate<T>::fromObject( // Step 6.b. uint32_t len = array->getDenseInitializedLength(); // Step 6.c. Rooted<ArrayBufferObject*> buffer(cx); if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; - Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto)); + Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, CreateSingleton::No, 0, + len, proto)); if (!obj) return nullptr; // Steps 6.d-e. MOZ_ASSERT(!obj->isSharedMemory()); if (!ElementSpecific<T, UnsharedOps>::initFromIterablePackedArray(cx, obj, array)) return nullptr; @@ -1348,17 +1360,18 @@ TypedArrayObjectTemplate<T>::fromObject( if (!GetLengthProperty(cx, arrayLike, &len)) return nullptr; // Step 10. Rooted<ArrayBufferObject*> buffer(cx); if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; - Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto)); + Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, CreateSingleton::No, 0, len, + proto)); if (!obj) return nullptr; // Steps 11-12. MOZ_ASSERT(!obj->isSharedMemory()); if (!ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(cx, obj, arrayLike, len)) return nullptr;
--- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -181,18 +181,19 @@ class TypedArrayObject : public NativeOb void notifyBufferDetached(JSContext* cx, void* newData); static bool GetTemplateObjectForNative(JSContext* cx, Native native, uint32_t len, MutableHandleObject res); /* - * Byte length above which created typed arrays and data views will have - * singleton types regardless of the context in which they are created. + * Byte length above which created typed arrays will have singleton types + * regardless of the context in which they are created. This only applies to + * typed arrays created with an existing ArrayBuffer. */ static const uint32_t SINGLETON_BYTE_LENGTH = 1024 * 1024 * 10; static bool isOriginalLengthGetter(Native native); ArrayBufferObject* bufferUnshared() const { MOZ_ASSERT(!isSharedMemory()); JSObject* obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
--- a/js/src/wasm/WasmFrameIter.cpp +++ b/js/src/wasm/WasmFrameIter.cpp @@ -935,16 +935,20 @@ js::wasm::StartUnwinding(const RegisterS // frame is incomplete. During profiling frame iteration, it means // that the jit profiling frame iterator won't be able to unwind // this frame; drop it. return false; } #endif fixedFP = offsetFromEntry < SetJitEntryFP ? (Frame*) sp : fp; fixedPC = nullptr; + + // On the error return path, FP might be set to FailFP. Ignore these transient frames. + if (intptr_t(fixedFP) == (FailFP & ~JitActivation::ExitFpWasmBit)) + return false; break; case CodeRange::Throw: // The throw stub executes a small number of instructions before popping // the entire activation. To simplify testing, we simply pretend throw // stubs have already popped the entire stack. return false; case CodeRange::Interrupt: // When the PC is in the async interrupt stub, the fp may be garbage and
--- a/js/src/wasm/WasmModule.cpp +++ b/js/src/wasm/WasmModule.cpp @@ -25,18 +25,16 @@ #include "jit/JitOptions.h" #include "threading/LockGuard.h" #include "wasm/WasmCompile.h" #include "wasm/WasmInstance.h" #include "wasm/WasmJS.h" #include "wasm/WasmSerialize.h" -#include "jsatominlines.h" - #include "vm/ArrayBufferObject-inl.h" #include "vm/Debugger-inl.h" using namespace js; using namespace js::jit; using namespace js::wasm; using mozilla::IsNaN;
--- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -617,21 +617,19 @@ xpc::IsSandbox(JSObject* obj) nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() { } nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() { } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox) -NS_INTERFACE_MAP_END +NS_IMPL_QUERY_INTERFACE(nsXPCComponents_utils_Sandbox, + nsIXPCComponents_utils_Sandbox, + nsIXPCScriptable) NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox) NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox) // We use the nsIXPScriptable macros to generate lots of stuff for us. #define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \
--- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -182,25 +182,20 @@ nsXPCComponents_Interfaces::nsXPCCompone } nsXPCComponents_Interfaces::~nsXPCComponents_Interfaces() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Interfaces) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Interfaces) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Interfaces) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_Interfaces) -NS_IMPL_RELEASE(nsXPCComponents_Interfaces) +NS_IMPL_ISUPPORTS(nsXPCComponents_Interfaces, + nsIXPCComponents_Interfaces, + nsIXPCScriptable, + nsIClassInfo); // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_Interfaces #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Interfaces" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \ XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) #include "xpc_map_end.h" /* This will #undef the above */ @@ -386,25 +381,20 @@ nsXPCComponents_InterfacesByID::nsXPCCom { } nsXPCComponents_InterfacesByID::~nsXPCComponents_InterfacesByID() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_InterfacesByID) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_InterfacesByID) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_InterfacesByID) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_InterfacesByID) -NS_IMPL_RELEASE(nsXPCComponents_InterfacesByID) +NS_IMPL_ISUPPORTS(nsXPCComponents_InterfacesByID, + nsIXPCComponents_InterfacesByID, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_InterfacesByID #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_InterfacesByID" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \ XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) #include "xpc_map_end.h" /* This will #undef the above */ @@ -590,25 +580,20 @@ nsXPCComponents_Classes::nsXPCComponents { } nsXPCComponents_Classes::~nsXPCComponents_Classes() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Classes) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Classes) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Classes) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_Classes) -NS_IMPL_RELEASE(nsXPCComponents_Classes) +NS_IMPL_ISUPPORTS(nsXPCComponents_Classes, + nsIXPCComponents_Classes, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_Classes #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Classes" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \ XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) #include "xpc_map_end.h" /* This will #undef the above */ @@ -784,25 +769,20 @@ nsXPCComponents_ClassesByID::nsXPCCompon { } nsXPCComponents_ClassesByID::~nsXPCComponents_ClassesByID() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_ClassesByID) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_ClassesByID) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_ClassesByID) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_ClassesByID) -NS_IMPL_RELEASE(nsXPCComponents_ClassesByID) +NS_IMPL_ISUPPORTS(nsXPCComponents_ClassesByID, + nsIXPCComponents_ClassesByID, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_ClassesByID #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ClassesByID" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \ XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) #include "xpc_map_end.h" /* This will #undef the above */ @@ -995,25 +975,20 @@ nsXPCComponents_Results::nsXPCComponents { } nsXPCComponents_Results::~nsXPCComponents_Results() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Results) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Results) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Results) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_Results) -NS_IMPL_RELEASE(nsXPCComponents_Results) +NS_IMPL_ISUPPORTS(nsXPCComponents_Results, + nsIXPCComponents_Results, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_Results #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Results" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \ XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) #include "xpc_map_end.h" /* This will #undef the above */ @@ -1162,25 +1137,20 @@ nsXPCComponents_ID::nsXPCComponents_ID() { } nsXPCComponents_ID::~nsXPCComponents_ID() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_ID) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_ID) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_ID) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_ID) -NS_IMPL_RELEASE(nsXPCComponents_ID) +NS_IMPL_ISUPPORTS(nsXPCComponents_ID, + nsIXPCComponents_ID, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_ID #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ID" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \ XPC_SCRIPTABLE_WANT_CONSTRUCT | \ XPC_SCRIPTABLE_WANT_HASINSTANCE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) @@ -1338,25 +1308,20 @@ nsXPCComponents_Exception::nsXPCComponen { } nsXPCComponents_Exception::~nsXPCComponents_Exception() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Exception) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Exception) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Exception) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_Exception) -NS_IMPL_RELEASE(nsXPCComponents_Exception) +NS_IMPL_ISUPPORTS(nsXPCComponents_Exception, + nsIXPCComponents_Exception, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_Exception #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Exception" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \ XPC_SCRIPTABLE_WANT_CONSTRUCT | \ XPC_SCRIPTABLE_WANT_HASINSTANCE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) @@ -1703,25 +1668,20 @@ nsXPCConstructor::GetInterfaceID(nsIJSII } NS_IMETHODIMP nsXPCConstructor::GetInitializer(char * *aInitializer) { XPC_STRING_GETTER_BODY(aInitializer, mInitializer); } -NS_INTERFACE_MAP_BEGIN(nsXPCConstructor) - NS_INTERFACE_MAP_ENTRY(nsIXPCConstructor) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCConstructor) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCConstructor) -NS_IMPL_RELEASE(nsXPCConstructor) +NS_IMPL_ISUPPORTS(nsXPCConstructor, + nsIXPCConstructor, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCConstructor #define XPC_MAP_QUOTED_CLASSNAME "nsXPCConstructor" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \ XPC_SCRIPTABLE_WANT_CONSTRUCT) #include "xpc_map_end.h" /* This will #undef the above */ @@ -1876,25 +1836,20 @@ nsXPCComponents_Constructor::nsXPCCompon { } nsXPCComponents_Constructor::~nsXPCComponents_Constructor() { // empty } -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Constructor) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Constructor) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Constructor) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_Constructor) -NS_IMPL_RELEASE(nsXPCComponents_Constructor) +NS_IMPL_ISUPPORTS(nsXPCComponents_Constructor, + nsIXPCComponents_Constructor, + nsIXPCScriptable, + nsIClassInfo) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_Constructor #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Constructor" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \ XPC_SCRIPTABLE_WANT_CONSTRUCT | \ XPC_SCRIPTABLE_WANT_HASINSTANCE | \ XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) @@ -2073,24 +2028,19 @@ public: public: nsXPCComponents_Utils() { } private: virtual ~nsXPCComponents_Utils() { } nsCOMPtr<nsIXPCComponents_utils_Sandbox> mSandbox; }; -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Utils) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Utils) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Utils) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(nsXPCComponents_Utils) -NS_IMPL_RELEASE(nsXPCComponents_Utils) +NS_IMPL_ISUPPORTS(nsXPCComponents_Utils, + nsIXPCComponents_Utils, + nsIXPCScriptable) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsXPCComponents_Utils #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Utils" #define XPC_MAP_FLAGS XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE #include "xpc_map_end.h" /* This will #undef the above */ NS_IMETHODIMP @@ -3346,20 +3296,17 @@ private: }; ComponentsSH ComponentsSH::singleton(0); // Singleton refcounting. NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::AddRef(void) { return 1; } NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::Release(void) { return 1; } -NS_INTERFACE_MAP_BEGIN(ComponentsSH) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END +NS_IMPL_QUERY_INTERFACE(ComponentsSH, nsIXPCScriptable) #define NSXPCCOMPONENTSBASE_CID \ { 0xc62998e5, 0x95f1, 0x4058, \ { 0xa5, 0x09, 0xec, 0x21, 0x66, 0x18, 0x92, 0xb9 } } #define NSXPCCOMPONENTS_CID \ { 0x3649f405, 0xf0ec, 0x4c28, \ { 0xae, 0xb0, 0xaf, 0x9a, 0x51, 0xe4, 0x4c, 0x81 } }
--- a/js/xpconnect/src/XPCJSID.cpp +++ b/js/xpconnect/src/XPCJSID.cpp @@ -209,23 +209,18 @@ class SharedScriptableHelperForJSIID fin { ~SharedScriptableHelperForJSIID() {} public: NS_DECL_ISUPPORTS NS_DECL_NSIXPCSCRIPTABLE SharedScriptableHelperForJSIID() {} }; -NS_INTERFACE_MAP_BEGIN(SharedScriptableHelperForJSIID) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCScriptable) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(SharedScriptableHelperForJSIID) -NS_IMPL_RELEASE(SharedScriptableHelperForJSIID) +NS_IMPL_ISUPPORTS(SharedScriptableHelperForJSIID, + nsIXPCScriptable) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME SharedScriptableHelperForJSIID #define XPC_MAP_QUOTED_CLASSNAME "JSIID" #define XPC_MAP_FLAGS XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE #include "xpc_map_end.h" /* This will #undef the above */ static mozilla::StaticRefPtr<nsIXPCScriptable> gSharedScriptableHelperForJSIID; @@ -271,23 +266,20 @@ void xpc_DestroyJSxIDClassObjects() gSharedScriptableHelperForJSIID = nullptr; gClassObjectsWereInited = false; } } /***************************************************************************/ -NS_INTERFACE_MAP_BEGIN(nsJSIID) - NS_INTERFACE_MAP_ENTRY(nsIJSID) - NS_INTERFACE_MAP_ENTRY(nsIJSIID) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSID) - NS_IMPL_QUERY_CLASSINFO(nsJSIID) -NS_INTERFACE_MAP_END +NS_IMPL_QUERY_INTERFACE_CI(nsJSIID, + nsIJSID, + nsIJSIID, + nsIXPCScriptable) NS_IMPL_ADDREF(nsJSIID) NS_IMPL_RELEASE(nsJSIID) NS_IMPL_CI_INTERFACE_GETTER(nsJSIID, nsIJSID, nsIJSIID) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsJSIID #define XPC_MAP_QUOTED_CLASSNAME "nsJSIID" @@ -528,23 +520,20 @@ nsJSIID::HasInstance(nsIXPConnectWrapped const nsIID* iid; mInfo->GetIIDShared(&iid); return xpc::HasInstance(cx, obj, iid, bp); } /***************************************************************************/ -NS_INTERFACE_MAP_BEGIN(nsJSCID) - NS_INTERFACE_MAP_ENTRY(nsIJSID) - NS_INTERFACE_MAP_ENTRY(nsIJSCID) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSID) - NS_IMPL_QUERY_CLASSINFO(nsJSCID) -NS_INTERFACE_MAP_END +NS_IMPL_QUERY_INTERFACE_CI(nsJSCID, + nsIJSID, + nsIJSCID, + nsIXPCScriptable) NS_IMPL_ADDREF(nsJSCID) NS_IMPL_RELEASE(nsJSCID) NS_IMPL_CI_INTERFACE_GETTER(nsJSCID, nsIJSID, nsIJSCID) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME nsJSCID #define XPC_MAP_QUOTED_CLASSNAME "nsJSCID"
--- a/js/xpconnect/src/XPCRuntimeService.cpp +++ b/js/xpconnect/src/XPCRuntimeService.cpp @@ -7,27 +7,22 @@ #include "xpcprivate.h" #include "nsContentUtils.h" #include "BackstagePass.h" #include "nsDOMClassInfo.h" #include "nsIPrincipal.h" #include "mozilla/dom/BindingUtils.h" -NS_INTERFACE_MAP_BEGIN(BackstagePass) - NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY(nsIClassInfo) - NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) - NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCScriptable) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(BackstagePass) -NS_IMPL_RELEASE(BackstagePass) +NS_IMPL_ISUPPORTS(BackstagePass, + nsIXPCScriptable, + nsIGlobalObject, + nsIClassInfo, + nsIScriptObjectPrincipal, + nsISupportsWeakReference) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME BackstagePass #define XPC_MAP_QUOTED_CLASSNAME "BackstagePass" #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \ XPC_SCRIPTABLE_WANT_ENUMERATE | \ XPC_SCRIPTABLE_WANT_FINALIZE | \ XPC_SCRIPTABLE_WANT_PRECREATE | \
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm +++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm @@ -52,17 +52,25 @@ class GeckoViewContent extends GeckoView switch (aEvent) { case "GeckoViewContent:ExitFullScreen": this.messageManager.sendAsyncMessage("GeckoView:DOMFullscreenExited"); break; case "GeckoView:ZoomToInput": this.messageManager.sendAsyncMessage(aEvent); break; case "GeckoView:SetActive": - this.browser.docShellIsActive = aData.active; + if (aData.active) { + this.browser.setAttribute("primary", "true"); + this.browser.focus(); + this.browser.docShellIsActive = true; + } else { + this.browser.removeAttribute("primary"); + this.browser.docShellIsActive = false; + this.browser.blur(); + } break; } } unregister() { this.window.removeEventListener("MozDOMFullScreen:Entered", this, /* capture */ true); this.window.removeEventListener("MozDOMFullScreen:Exited", this,
--- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -1398,25 +1398,20 @@ nsPrefBranch::~nsPrefBranch() nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); } } -NS_IMPL_ADDREF(nsPrefBranch) -NS_IMPL_RELEASE(nsPrefBranch) - -NS_INTERFACE_MAP_BEGIN(nsPrefBranch) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefBranch) - NS_INTERFACE_MAP_ENTRY(nsIPrefBranch) - NS_INTERFACE_MAP_ENTRY(nsIObserver) - NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) -NS_INTERFACE_MAP_END +NS_IMPL_ISUPPORTS(nsPrefBranch, + nsIPrefBranch, + nsIObserver, + nsISupportsWeakReference) NS_IMETHODIMP nsPrefBranch::GetRoot(nsACString& aRoot) { aRoot = mPrefRoot; return NS_OK; } @@ -2235,24 +2230,19 @@ nsPrefBranch::GetPrefName(const char* aP //---------------------------------------------------------------------------- // nsPrefLocalizedString //---------------------------------------------------------------------------- nsPrefLocalizedString::nsPrefLocalizedString() = default; nsPrefLocalizedString::~nsPrefLocalizedString() = default; -NS_IMPL_ADDREF(nsPrefLocalizedString) -NS_IMPL_RELEASE(nsPrefLocalizedString) - -NS_INTERFACE_MAP_BEGIN(nsPrefLocalizedString) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefLocalizedString) - NS_INTERFACE_MAP_ENTRY(nsIPrefLocalizedString) - NS_INTERFACE_MAP_ENTRY(nsISupportsString) -NS_INTERFACE_MAP_END +NS_IMPL_ISUPPORTS(nsPrefLocalizedString, + nsIPrefLocalizedString, + nsISupportsString) nsresult nsPrefLocalizedString::Init() { nsresult rv; mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); return rv; @@ -2952,26 +2942,21 @@ Preferences::~Preferences() gHashTable = nullptr; delete gTelemetryLoadData; gTelemetryLoadData = nullptr; gPrefNameArena.Clear(); } -NS_IMPL_ADDREF(Preferences) -NS_IMPL_RELEASE(Preferences) - -NS_INTERFACE_MAP_BEGIN(Preferences) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefService)