author | Noemi Erli <nerli@mozilla.com> |
Wed, 15 Nov 2017 11:57:12 +0200 | |
changeset 391888 | 45715ece25fcb064eee4f977ebd842d44a87f22b |
parent 391887 | 0b135d7d8cef2858767e1384de7df75e0085d7c0 (current diff) |
parent 391767 | da16d74f97e28c68cf18c80e2e61b2c441956cfa (diff) |
child 391889 | 76d469663b3c54093aad44d58dd932cb659465a6 |
child 391996 | 32de83b016c917b2a7a3df840e6c1bde294a56d5 |
child 392055 | e568a5c3cadcde50510fe640065c4c7b1b6dfd6a |
push id | 97352 |
push user | nerli@mozilla.com |
push date | Wed, 15 Nov 2017 10:16:19 +0000 |
treeherder | mozilla-inbound@76d469663b3c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge, merge |
milestone | 59.0a1 |
first release with | nightly linux32
45715ece25fc
/
59.0a1
/
20171115100050
/
files
nightly linux64
45715ece25fc
/
59.0a1
/
20171115100050
/
files
nightly mac
45715ece25fc
/
59.0a1
/
20171115100050
/
files
nightly win32
45715ece25fc
/
59.0a1
/
20171115100050
/
files
nightly win64
45715ece25fc
/
59.0a1
/
20171115100050
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
59.0a1
/
20171115100050
/
pushlog to previous
nightly linux64
59.0a1
/
20171115100050
/
pushlog to previous
nightly mac
59.0a1
/
20171115100050
/
pushlog to previous
nightly win32
59.0a1
/
20171115100050
/
pushlog to previous
nightly win64
59.0a1
/
20171115100050
/
pushlog to previous
|
build/moz.configure/toolchain.configure | file | annotate | diff | comparison | revisions | |
js/src/gc/Verifier.cpp | file | annotate | diff | comparison | revisions |
--- a/build/moz.configure/toolchain.configure +++ b/build/moz.configure/toolchain.configure @@ -1325,19 +1325,19 @@ js_option(env='RUSTC_OPT_LEVEL', nargs=1, help='Rust compiler optimization level (-C opt-level=%s)') # --enable-release kicks in full optimizations. imply_option('RUSTC_OPT_LEVEL', '2', when='--enable-release') @depends('RUSTC_OPT_LEVEL', debug_rust, '--enable-debug-symbols', - moz_optimize) + moz_optimize, host, target) def rust_compiler_flags(opt_level_option, debug_rust, debug_symbols, - moz_optimize): + moz_optimize, host, target): optimize = moz_optimize.optimize # Cargo currently supports only two interesting profiles for building: # development and release. Those map (roughly) to --enable-debug and # --disable-debug in Gecko, respectively. # # But we'd also like to support an additional axis of control for # optimization level. Since Cargo only supports 2 profiles, we're in @@ -1357,17 +1357,21 @@ def rust_compiler_flags(opt_level_option opt_level = '1' if optimize else '0' # opt-level=0 implies -C debug-assertions, which may not be desired # unless Rust debugging is enabled. if opt_level == '0' and not debug_rust: debug_assertions = False if debug_symbols: - debug_info = '2' + if host.kernel == 'Linux' and target.kernel == 'Darwin': + # hack to work around dsymutil failing on cross-OSX builds (bug 1410148) + debug_info = '1' + else: + debug_info = '2' opts = [] if opt_level is not None: opts.append('opt-level=%s' % opt_level) if debug_assertions is not None: opts.append('debug-assertions=%s' % ('yes' if debug_assertions else 'no'))
--- a/devtools/client/debugger/new/README.mozilla +++ b/devtools/client/debugger/new/README.mozilla @@ -1,11 +1,11 @@ This is the debugger.html project output. See https://github.com/devtools-html/debugger.html -Taken from upstream commit: 09d6d4f93135367b2639d78ad884434f73ab449c +Taken from upstream commit: d2e91e574acbe3d5b546508d028bd278eaabd286 Packages: - babel-plugin-transform-es2015-modules-commonjs @6.26.0 - babel-preset-react @6.24.1 - react @15.6.2 - react-dom @15.6.2 - webpack @3.8.1
--- a/devtools/client/debugger/new/debugger.css +++ b/devtools/client/debugger/new/debugger.css @@ -599,16 +599,40 @@ button:focus { /* Utils */ .absolute-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } + +.d-flex { + display: flex; +} + +.align-items-center { + align-items: center; +} + +.rounded-circle { + border-radius: 50%; +} + +.text-white { + color: white; +} + +.text-center { + text-align: center; +} + +.min-width-0 { + min-width: 0; +} /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ menupopup { position: fixed; z-index: 10000; background: white; @@ -2870,16 +2894,32 @@ html .breakpoints-list .breakpoint.pause .frames ul .frames-group .frames-list li { padding-left: 30px; } .frames ul .frames-group .frames-list { border-top: 1px solid var(--theme-splitter-color); border-bottom: 1px solid var(--theme-splitter-color); } + +.frames ul .frames-group.expanded .badge { + color: var(--theme-highlight-blue); +} +.badge { + --size: 17px; + --radius: calc(var(--size) / 2); + height: var(--size); + min-width: var(--size); + line-height: var(--size); + background: var(--theme-toolbar-background-hover); + color: var(--theme-body-color); + border-radius: var(--radius); + padding: 0 4px; + font-size: 0.9em; +} /* 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/>. */ .why-paused { background-color: var(--theme-body-background); color: var(--theme-body-color); padding: 10px 10px 10px 20px; @@ -2913,53 +2953,59 @@ html .breakpoints-list .breakpoint.pause .frames ul { list-style: none; margin: 0; padding: 0; } .frames ul li { - padding: 7px 10px 7px 21px; + padding: 0 10px 0 21px; overflow: hidden; display: flex; justify-content: space-between; flex-direction: row; align-items: center; margin: 0; } .frames ul li * { -moz-user-select: none; user-select: none; } +.frames .badge { + flex-shrink: 0; + margin-right: 4px; +} + .frames .location { font-weight: lighter; display: flex; justify-content: space-between; flex-direction: row; align-items: center; margin: 0; + flex-shrink: 0; } .theme-light .frames .location, .theme-firebug .frames .location { color: var(--theme-comment); } :root.theme-dark .frames .location { color: var(--theme-body-color); opacity: 0.6; } .frames .title { text-overflow: ellipsis; overflow: hidden; - margin-right: 1em; + margin: 7px 0.5em 7px 0; } .frames ul li:hover, .frames ul li:focus { background-color: var(--theme-toolbar-background-alt); outline: none; } @@ -3589,17 +3635,17 @@ html .welcomebox .toggle-button-end.coll border: 1px solid var(--theme-splitter-color); box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent); max-height: 300px; position: absolute; right: 0; top: 23px; width: var(--width); z-index: 1000; - overflow: scroll; + overflow: auto; } html[dir="rtl"] .dropdown { right: calc((var(--width) - 11px) * (-1)); } .dropdown-block { padding: 0px 2px;
--- a/devtools/client/debugger/new/debugger.js +++ b/devtools/client/debugger/new/debugger.js @@ -26981,17 +26981,17 @@ FrameComponent.displayName = "Frame"; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = FrameMenu; -var _devtoolsLaunchpad = __webpack_require__(1362); +var _devtoolsContextmenu = __webpack_require__(1413); var _clipboard = __webpack_require__(1388); var _lodash = __webpack_require__(2); const blackboxString = "sourceFooter.blackbox"; /* 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/>. */ @@ -27047,17 +27047,17 @@ function FrameMenu(frame, frameworkGroup menuOptions.push(copySourceUri2); menuOptions.push(blackBoxSource(source, callbacks.toggleBlackBox)); } const copyStackTraceItem = copyStackTraceElement(callbacks.copyStackTrace); menuOptions.push(copyStackTraceItem); - (0, _devtoolsLaunchpad.showMenu)(event, menuOptions); + (0, _devtoolsContextmenu.showMenu)(event, menuOptions); } /***/ }), /* 1455 */, /* 1456 */, /* 1457 */, /* 1458 */, /* 1459 */ @@ -35312,17 +35312,17 @@ var _ManagedTree2 = _interopRequireDefau var _Svg = __webpack_require__(1359); var _Svg2 = _interopRequireDefault(_Svg); var _sourcesTree = __webpack_require__(1442); var _immutable = __webpack_require__(146); -var _devtoolsLaunchpad = __webpack_require__(1362); +var _devtoolsContextmenu = __webpack_require__(1413); var _clipboard = __webpack_require__(1388); var _utils = __webpack_require__(1366); var _prefs = __webpack_require__(226); var _ui = __webpack_require__(1385); @@ -35490,17 +35490,17 @@ class SourcesTree extends _react.Compone menuOptions.push({ id: "node-set-directory-root", label: setDirectoryRootLabel, accesskey: setDirectoryRootKey, disabled: false, click: () => (0, _ui.setProjectDirectoryRoot)(item.path) }); } - (0, _devtoolsLaunchpad.showMenu)(event, menuOptions); + (0, _devtoolsContextmenu.showMenu)(event, menuOptions); } renderItem(item, depth, focused, _, expanded, { setExpanded }) { const arrow = (0, _sourcesTree.nodeHasChildren)(item) ? _react2.default.createElement("img", { className: (0, _classnames2.default)("arrow", { expanded: expanded }), onClick: e => { @@ -40603,17 +40603,17 @@ Object.defineProperty(exports, "__esModu var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* 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/>. */ exports.gutterMenu = gutterMenu; var _react = __webpack_require__(0); -var _devtoolsLaunchpad = __webpack_require__(1362); +var _devtoolsContextmenu = __webpack_require__(1413); var _redux = __webpack_require__(3); var _reactRedux = __webpack_require__(1189); var _editor = __webpack_require__(1358); var _selectors = __webpack_require__(1352); @@ -40704,17 +40704,17 @@ function gutterMenu({ const disableBreakpoint = _extends({ accesskey: L10N.getStr("editor.disableBreakpoint.accesskey"), disabled: false, click: () => toggleDisabledBreakpoint(line) }, breakpoint.disabled ? gutterItems.enableBreakpoint : gutterItems.disableBreakpoint); items.push(disableBreakpoint); } - (0, _devtoolsLaunchpad.showMenu)(event, items); + (0, _devtoolsContextmenu.showMenu)(event, items); } class GutterContextMenuComponent extends _react.PureComponent { constructor() { super(); } @@ -40768,17 +40768,17 @@ exports.default = (0, _reactRedux.connec Object.defineProperty(exports, "__esModule", { value: true }); var _react = __webpack_require__(0); -var _devtoolsLaunchpad = __webpack_require__(1362); +var _devtoolsContextmenu = __webpack_require__(1413); var _devtoolsSourceMap = __webpack_require__(1360); var _clipboard = __webpack_require__(1388); var _source = __webpack_require__(1356); var _editor = __webpack_require__(1358); @@ -40853,23 +40853,25 @@ function getMenuItems(event, { const sourceLocation = (0, _editor.getSourceLocationFromMouseEvent)(editor, selectedLocation, event); const isOriginal = (0, _devtoolsSourceMap.isOriginalId)(selectedLocation.sourceId); const hasSourceMap = selectedSource.get("sourceMapURL"); const isPrettyPrinted = (0, _source.isPretty)(selectedSource.toJS()); const jumpLabel = { + id: "node-menu-jump", accesskey: L10N.getStr("editor.jumpToMappedLocation1.accesskey"), disabled: _devtoolsSourceMap.isGeneratedId && !hasSourceMap, label: L10N.getFormatStr("editor.jumpToMappedLocation1", isOriginal ? L10N.getStr("generated") : L10N.getStr("original")), click: () => jumpToMappedLocation(sourceLocation) }; const watchExpressionLabel = { + id: "node-menu-add-watch-expression", accesskey: L10N.getStr("expressions.accesskey"), label: L10N.getStr("expressions.label"), click: () => addExpression(editor.codeMirror.getSelection()) }; const blackBoxMenuItem = { id: "node-menu-blackbox", label: toggleBlackBoxLabel, @@ -40930,17 +40932,17 @@ class EditorMenu extends _react.PureComp this.props.setContextMenu("", null); return this.showMenu(nextProps); } showMenu(nextProps) { const { contextMenu } = nextProps, options = _objectWithoutProperties(nextProps, ["contextMenu"]); const { event } = contextMenu; - (0, _devtoolsLaunchpad.showMenu)(event, getMenuItems(event, options)); + (0, _devtoolsContextmenu.showMenu)(event, getMenuItems(event, options)); } render() { return null; } } exports.default = (0, _reactRedux.connect)(state => { @@ -41169,20 +41171,16 @@ var _react = __webpack_require__(0); var _react2 = _interopRequireDefault(_react); var _reactRedux = __webpack_require__(1189); var _redux = __webpack_require__(3); var _prefs = __webpack_require__(226); -var _reactImmutableProptypes = __webpack_require__(150); - -var _reactImmutableProptypes2 = _interopRequireDefault(_reactImmutableProptypes); - var _actions = __webpack_require__(1354); var _actions2 = _interopRequireDefault(_actions); var _selectors = __webpack_require__(1352); var _devtoolsConfig = __webpack_require__(1355); @@ -41233,21 +41231,19 @@ var _ChromeScopes2 = _interopRequireDefa var _Scopes2 = __webpack_require__(1611); var _Scopes3 = _interopRequireDefault(_Scopes2); __webpack_require__(1342); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/* 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/>. */ - -const Scopes = (0, _devtoolsConfig.isEnabled)("chromeScopes") ? _ChromeScopes2.default : _Scopes3.default; +const Scopes = (0, _devtoolsConfig.isEnabled)("chromeScopes") ? _ChromeScopes2.default : _Scopes3.default; /* 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/>. */ function debugBtn(onClick, type, className, tooltip) { return _react2.default.createElement( "button", { onClick: onClick, className: `${type} ${className}`, key: type, @@ -41428,17 +41424,17 @@ class SecondaryPanes extends _react.Comp ); } } SecondaryPanes.propTypes = { evaluateExpressions: _propTypes2.default.func.isRequired, pauseData: _propTypes2.default.object, horizontal: _propTypes2.default.bool, - breakpoints: _reactImmutableProptypes2.default.map.isRequired, + breakpoints: _propTypes2.default.object, breakpointsDisabled: _propTypes2.default.bool, breakpointsLoading: _propTypes2.default.bool, toggleAllBreakpoints: _propTypes2.default.func.isRequired, toggleShortcutsModal: _propTypes2.default.func }; SecondaryPanes.contextTypes = { shortcuts: _propTypes2.default.object @@ -41493,17 +41489,17 @@ var _actions2 = _interopRequireDefault(_ var _selectors = __webpack_require__(1352); var _breakpoint = __webpack_require__(1364); var _utils = __webpack_require__(1366); var _source = __webpack_require__(1356); -var _devtoolsLaunchpad = __webpack_require__(1362); +var _devtoolsContextmenu = __webpack_require__(1413); var _Close = __webpack_require__(1374); var _Close2 = _interopRequireDefault(_Close); __webpack_require__(1334); var _lodash = __webpack_require__(2); @@ -41728,17 +41724,17 @@ class Breakpoints extends _react.PureCom }, { item: editCondition, hidden: () => !breakpoint.condition }, { item: removeCondition, hidden: () => !breakpoint.condition }]; - (0, _devtoolsLaunchpad.showMenu)(e, (0, _devtoolsLaunchpad.buildMenu)(items)); + (0, _devtoolsContextmenu.showMenu)(e, (0, _devtoolsContextmenu.buildMenu)(items)); } selectBreakpoint(breakpoint) { const sourceId = breakpoint.location.sourceId; const { location } = breakpoint; this.props.selectSource(sourceId, { location }); } @@ -42296,33 +42292,39 @@ var _FrameMenu = __webpack_require__(145 var _FrameMenu2 = _interopRequireDefault(_FrameMenu); __webpack_require__(1336); var _Frame = __webpack_require__(1453); var _Frame2 = _interopRequireDefault(_Frame); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +var _Badge = __webpack_require__(1704); + +var _Badge2 = _interopRequireDefault(_Badge); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* 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/>. */ function FrameLocation({ frame }) { const library = (0, _frame.getLibraryFromUrl)(frame); if (!library) { return null; } return _react2.default.createElement( "div", { className: "location" }, library, _react2.default.createElement(_Svg2.default, { name: library.toLowerCase(), className: "annotation-logo" }) ); -} /* 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/>. */ +} FrameLocation.displayName = "FrameLocation"; class Group extends _react.Component { constructor(...args) { super(...args); @@ -42387,18 +42389,27 @@ class Group extends _react.Component { { key: frame.id, className: (0, _classnames2.default)("group"), onClick: this.toggleFrames, tabIndex: 0 }, _react2.default.createElement( "div", - { className: "title" }, - displayName + { className: "d-flex align-items-center min-width-0" }, + _react2.default.createElement( + "div", + { className: "title" }, + displayName + ), + _react2.default.createElement( + _Badge2.default, + null, + this.props.group.length + ) ), _react2.default.createElement(FrameLocation, { frame: frame }) ); } render() { const { expanded } = this.state; return _react2.default.createElement( @@ -43770,17 +43781,17 @@ var _classnames2 = _interopRequireDefaul var _actions = __webpack_require__(1354); var _actions2 = _interopRequireDefault(_actions); var _Close = __webpack_require__(1374); var _Close2 = _interopRequireDefault(_Close); -var _devtoolsLaunchpad = __webpack_require__(1362); +var _devtoolsContextmenu = __webpack_require__(1413); var _lodash = __webpack_require__(2); __webpack_require__(1344); var _PaneToggle = __webpack_require__(1407); var _PaneToggle2 = _interopRequireDefault(_PaneToggle); @@ -43988,17 +43999,17 @@ class SourceTabs extends _react.PureComp hidden: () => tabs.some((t, i) => t === tab && tabs.size - 1 === i) }, { item: closeAllTabsMenuItem }, { item: { type: "separator" } }, { item: copySourceUri2 }]; if (!isPrettySource) { items.push({ item: showSourceMenuItem }); items.push({ item: prettyPrint }); } - (0, _devtoolsLaunchpad.showMenu)(e, (0, _devtoolsLaunchpad.buildMenu)(items)); + (0, _devtoolsContextmenu.showMenu)(e, (0, _devtoolsContextmenu.buildMenu)(items)); } /* * Updates the hiddenSourceTabs state, by * finding the source tabs which are wrapped and are not on the top row. */ updateHiddenSourceTabs() { if (!this.refs.sourceTabs) { @@ -45933,11 +45944,84 @@ function timing(store) { mark(`${action.type}_start`); const result = next(action); mark(`${action.type}_end`); measure(`${action.type}`, `${action.type}_start`, `${action.type}_end`); return result; }; } +/***/ }), +/* 1664 */, +/* 1665 */, +/* 1666 */, +/* 1667 */, +/* 1668 */, +/* 1669 */, +/* 1670 */, +/* 1671 */, +/* 1672 */, +/* 1673 */, +/* 1674 */, +/* 1675 */, +/* 1676 */, +/* 1677 */, +/* 1678 */, +/* 1679 */, +/* 1680 */, +/* 1681 */, +/* 1682 */, +/* 1683 */, +/* 1684 */, +/* 1685 */, +/* 1686 */, +/* 1687 */, +/* 1688 */, +/* 1689 */, +/* 1690 */, +/* 1691 */, +/* 1692 */, +/* 1693 */, +/* 1694 */, +/* 1695 */, +/* 1696 */, +/* 1697 */, +/* 1698 */, +/* 1699 */, +/* 1700 */, +/* 1701 */, +/* 1702 */, +/* 1703 */, +/* 1704 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = __webpack_require__(0); + +var _react2 = _interopRequireDefault(_react); + +__webpack_require__(1705); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const Badge = ({ children }) => _react2.default.createElement( + "div", + { className: "badge text-white text-center" }, + children +); + +exports.default = Badge; + +/***/ }), +/* 1705 */ +/***/ (function(module, exports) { + +// removed by extract-text-webpack-plugin + /***/ }) /******/ ]); }); \ No newline at end of file
--- a/devtools/client/framework/components/toolbox-controller.js +++ b/devtools/client/framework/components/toolbox-controller.js @@ -142,16 +142,20 @@ class ToolboxController extends Componen this.updateButtonIds(); } setPanelDefinitions(panelDefinitions) { this.setState({ panelDefinitions }); this.updateButtonIds(); } + get panelDefinitions() { + return this.state.panelDefinitions; + } + setToolboxButtons(toolboxButtons) { // Listen for updates of the checked attribute. this.state.toolboxButtons.forEach(button => { button.off("updatechecked", this.state.checkedButtonsUpdated); }); toolboxButtons.forEach(button => { button.on("updatechecked", this.state.checkedButtonsUpdated); });
--- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -1920,35 +1920,35 @@ Toolbox.prototype = { reloadTarget: function (force) { this.target.activeTab.reload({ force: force }); }, /** * Loads the tool next to the currently selected tool. */ selectNextTool: function () { - const index = this.panelDefinitions.findIndex(({id}) => id === this.currentToolId); - let definition = this.panelDefinitions[index + 1]; + let definitions = this.component.panelDefinitions; + const index = definitions.findIndex(({id}) => id === this.currentToolId); + let definition = definitions[index + 1]; if (!definition) { - definition = index === -1 - ? this.panelDefinitions[0] - : this.optionsDefinition; + definition = index === -1 ? definitions[0] : this.optionsDefinition; } return this.selectTool(definition.id); }, /** * Loads the tool just left to the currently selected tool. */ selectPreviousTool: function () { - const index = this.panelDefinitions.findIndex(({id}) => id === this.currentToolId); - let definition = this.panelDefinitions[index - 1]; + let definitions = this.component.panelDefinitions; + const index = definitions.findIndex(({id}) => id === this.currentToolId); + let definition = definitions[index - 1]; if (!definition) { definition = index === -1 - ? this.panelDefinitions[this.panelDefinitions.length - 1] + ? definitions[definitions.length - 1] : this.optionsDefinition; } return this.selectTool(definition.id); }, /** * Highlights the tool's tab if it is not the currently selected tool. *
--- a/devtools/server/actors/highlighters/css-grid.js +++ b/devtools/server/actors/highlighters/css-grid.js @@ -164,17 +164,17 @@ class CssGridHighlighter extends AutoRef this.onPageHide = this.onPageHide.bind(this); this.onWillNavigate = this.onWillNavigate.bind(this); this.highlighterEnv.on("will-navigate", this.onWillNavigate); let { pageListenerTarget } = highlighterEnv; pageListenerTarget.addEventListener("pagehide", this.onPageHide); - // Initialize the <canvas> position to the top left corner of the page + // Initialize the <canvas> position to the top left corner of the page. this._canvasPosition = { x: 0, y: 0 }; // Calling `updateCanvasPosition` anyway since the highlighter could be initialized // on a page that has scrolled already. updateCanvasPosition(this._canvasPosition, this._scroll, this.win, @@ -208,17 +208,17 @@ class CssGridHighlighter extends AutoRef "class": "canvas", "hidden": "true", "width": CANVAS_SIZE, "height": CANVAS_SIZE }, prefix: this.ID_CLASS_PREFIX }); - // Build the SVG element + // Build the SVG element. let svg = createSVGNode(this.win, { nodeType: "svg", parent: root, attributes: { "id": "elements", "width": "100%", "height": "100%", "hidden": "true" @@ -250,17 +250,17 @@ class CssGridHighlighter extends AutoRef parent: regions, attributes: { "class": "cells", "id": "cells" }, prefix: this.ID_CLASS_PREFIX }); - // Building the grid area infobar markup + // Build the grid area infobar markup. let areaInfobarContainer = createNode(this.win, { parent: container, attributes: { "class": "area-infobar-container", "id": "area-infobar-container", "position": "top", "hidden": "true" }, @@ -296,17 +296,17 @@ class CssGridHighlighter extends AutoRef parent: areaTextbox, attributes: { "class": "area-infobar-dimensions", "id": "area-infobar-dimensions" }, prefix: this.ID_CLASS_PREFIX }); - // Building the grid cell infobar markup + // Build the grid cell infobar markup. let cellInfobarContainer = createNode(this.win, { parent: container, attributes: { "class": "cell-infobar-container", "id": "cell-infobar-container", "position": "top", "hidden": "true" }, @@ -342,17 +342,17 @@ class CssGridHighlighter extends AutoRef parent: cellTextbox, attributes: { "class": "cell-infobar-dimensions", "id": "cell-infobar-dimensions" }, prefix: this.ID_CLASS_PREFIX }); - // Building the grid line infobar markup + // Build the grid line infobar markup. let lineInfobarContainer = createNode(this.win, { parent: container, attributes: { "class": "line-infobar-container", "id": "line-infobar-container", "position": "top", "hidden": "true" }, @@ -391,48 +391,76 @@ class CssGridHighlighter extends AutoRef "id": "line-infobar-names" }, prefix: this.ID_CLASS_PREFIX }); return container; } + clearCache() { + gCachedGridPattern.clear(); + } + + /** + * Clear the grid area highlights. + */ + clearGridAreas() { + let areas = this.getElement("areas"); + areas.setAttribute("d", ""); + } + + /** + * Clear the grid cell highlights. + */ + clearGridCell() { + let cells = this.getElement("cells"); + cells.setAttribute("d", ""); + } + destroy() { let { highlighterEnv } = this; highlighterEnv.off("will-navigate", this.onWillNavigate); let { pageListenerTarget } = highlighterEnv; if (pageListenerTarget) { pageListenerTarget.removeEventListener("pagehide", this.onPageHide); } this.markup.destroy(); // Clear the pattern cache to avoid dead object exceptions (Bug 1342051). - this._clearCache(); + this.clearCache(); AutoRefreshHighlighter.prototype.destroy.call(this); } - getElement(id) { - return this.markup.getElement(this.ID_CLASS_PREFIX + id); - } - - get ctx() { - return this.canvas.getCanvasContext("2d"); - } - get canvas() { return this.getElement("canvas"); } get color() { return this.options.color || DEFAULT_COLOR; } + get ctx() { + return this.canvas.getCanvasContext("2d"); + } + + getElement(id) { + return this.markup.getElement(this.ID_CLASS_PREFIX + id); + } + + getFirstColLinePos(fragment) { + return fragment.cols.lines[0].start; + } + + getFirstRowLinePos(fragment) { + return fragment.rows.lines[0].start; + } + /** * Gets the grid gap pattern used to render the gap regions based on the device * pixel ratio given. * * @param {Number} devicePixelRatio * The device pixel ratio we want the pattern for. * @param {Object} dimension * Refers to the Map key for the grid dimension type which is either the @@ -479,82 +507,179 @@ class CssGridHighlighter extends AutoRef let pattern = ctx.createPattern(canvas, "repeat"); gridPatternMap.set(dimension, pattern); gCachedGridPattern.set(devicePixelRatio, gridPatternMap); return pattern; } - onPageHide({ target }) { - // If a page hide event is triggered for current window's highlighter, hide the - // highlighter. - if (target.defaultView === this.win) { - this.hide(); + getLastColLinePos(fragment) { + return fragment.cols.lines[fragment.cols.lines.length - 1].start; + } + + /** + * Get the GridLine index of the last edge of the explicit grid for a grid dimension. + * + * @param {GridTracks} tracks + * The grid track of a given grid dimension. + * @return {Number} index of the last edge of the explicit grid for a grid dimension. + */ + getLastEdgeLineIndex(tracks) { + let trackIndex = tracks.length - 1; + + // Traverse the grid track backwards until we find an explicit track. + while (trackIndex >= 0 && tracks[trackIndex].type != "explicit") { + trackIndex--; } + + // The grid line index is the grid track index + 1. + return trackIndex + 1; + } + + getLastRowLinePos(fragment) { + return fragment.rows.lines[fragment.rows.lines.length - 1].start; + } + + /** + * The AutoRefreshHighlighter's _hasMoved method returns true only if the + * element's quads have changed. Override it so it also returns true if the + * element's grid has changed (which can happen when you change the + * grid-template-* CSS properties with the highlighter displayed). + */ + _hasMoved() { + let hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this); + + let oldGridData = stringifyGridFragments(this.gridData); + this.gridData = this.currentNode.getGridFragments(); + let newGridData = stringifyGridFragments(this.gridData); + + return hasMoved || oldGridData !== newGridData; } /** - * Called when the page will-navigate. Used to hide the grid highlighter and clear - * the cached gap patterns and avoid using DeadWrapper obejcts as gap patterns the - * next time. + * Hide the highlighter, the canvas and the infobars. */ - onWillNavigate({ isTopLevel }) { - this._clearCache(); + _hide() { + setIgnoreLayoutChanges(true); + this._hideGrid(); + this._hideGridElements(); + this._hideGridAreaInfoBar(); + this._hideGridCellInfoBar(); + this._hideGridLineInfoBar(); + setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement); + } + + _hideGrid() { + this.getElement("canvas").setAttribute("hidden", "true"); + } + + _hideGridAreaInfoBar() { + this.getElement("area-infobar-container").setAttribute("hidden", "true"); + } + + _hideGridCellInfoBar() { + this.getElement("cell-infobar-container").setAttribute("hidden", "true"); + } + + _hideGridElements() { + this.getElement("elements").setAttribute("hidden", "true"); + } + + _hideGridLineInfoBar() { + this.getElement("line-infobar-container").setAttribute("hidden", "true"); + } - if (isTopLevel) { - this.hide(); + /** + * Checks if the current node has a CSS Grid layout. + * + * @return {Boolean} true if the current node has a CSS grid layout, false otherwise. + */ + isGrid() { + return this.currentNode.getGridFragments().length > 0; + } + + /** + * Is a given grid fragment valid? i.e. does it actually have tracks? In some cases, we + * may have a fragment that defines column tracks but doesn't have any rows (or vice + * versa). In which case we do not want to draw anything for that fragment. + * + * @param {Object} fragment + * @return {Boolean} + */ + isValidFragment(fragment) { + return fragment.cols.tracks.length && fragment.rows.tracks.length; + } + + /** + * The <canvas>'s position needs to be updated if the page scrolls too much, in order + * to give the illusion that it always covers the viewport. + */ + _scrollUpdate() { + let hasUpdated = updateCanvasPosition(this._canvasPosition, this._scroll, this.win, + this._winDimensions); + + if (hasUpdated) { + this._update(); } } _show() { if (!this.isGrid()) { this.hide(); return false; } // The grid pattern cache should be cleared in case the color changed. - this._clearCache(); + this.clearCache(); // Hide the canvas, grid element highlights and infobar. this._hide(); return this._update(); } - _clearCache() { - gCachedGridPattern.clear(); + _showGrid() { + this.getElement("canvas").removeAttribute("hidden"); + } + + _showGridAreaInfoBar() { + this.getElement("area-infobar-container").removeAttribute("hidden"); + } + + _showGridCellInfoBar() { + this.getElement("cell-infobar-container").removeAttribute("hidden"); + } + + _showGridElements() { + this.getElement("elements").removeAttribute("hidden"); + } + + _showGridLineInfoBar() { + this.getElement("line-infobar-container").removeAttribute("hidden"); + } + + /** + * Shows all the grid area highlights for the current grid. + */ + showAllGridAreas() { + this.renderGridArea(); } /** * Shows the grid area highlight for the given area name. * * @param {String} areaName * Grid area name. */ showGridArea(areaName) { this.renderGridArea(areaName); } /** - * Shows all the grid area highlights for the current grid. - */ - showAllGridAreas() { - this.renderGridArea(); - } - - /** - * Clear the grid area highlights. - */ - clearGridAreas() { - let areas = this.getElement("areas"); - areas.setAttribute("d", ""); - } - - /** * Shows the grid cell highlight for the given grid cell options. * * @param {Number} options.gridFragmentIndex * Index of the grid fragment to render the grid cell highlight. * @param {Number} options.rowNumber * Row number of the grid cell to highlight. * @param {Number} options.columnNumber * Column number of the grid cell to highlight. @@ -573,64 +698,756 @@ class CssGridHighlighter extends AutoRef * @param {String} options.type * The dimension type of the grid line. */ showGridLineNames({ gridFragmentIndex, lineNumber, type }) { this.renderGridLineNames(gridFragmentIndex, lineNumber, type); } /** - * Clear the grid cell highlights. + * If a page hide event is triggered for current window's highlighter, hide the + * highlighter. + */ + onPageHide({ target }) { + if (target.defaultView === this.win) { + this.hide(); + } + } + + /** + * Called when the page will-navigate. Used to hide the grid highlighter and clear + * the cached gap patterns and avoid using DeadWrapper obejcts as gap patterns the + * next time. */ - clearGridCell() { - let cells = this.getElement("cells"); - cells.setAttribute("d", ""); + onWillNavigate({ isTopLevel }) { + this.clearCache(); + + if (isTopLevel) { + this.hide(); + } + } + + renderFragment(fragment) { + if (!this.isValidFragment(fragment)) { + return; + } + + this.renderLines(fragment.cols, COLUMNS, this.getFirstRowLinePos(fragment), + this.getLastRowLinePos(fragment)); + this.renderLines(fragment.rows, ROWS, this.getFirstColLinePos(fragment), + this.getLastColLinePos(fragment)); + + if (this.options.showGridAreasOverlay) { + this.renderGridAreaOverlay(); + } + + // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines. + if (this.options.showGridLineNumbers) { + this.renderLineNumbers(fragment.cols, COLUMNS, this.getFirstRowLinePos(fragment)); + this.renderLineNumbers(fragment.rows, ROWS, this.getFirstColLinePos(fragment)); + + if (Services.prefs.getBoolPref(NEGATIVE_LINE_NUMBERS_PREF)) { + this.renderNegativeLineNumbers(fragment.cols, COLUMNS, + this.getLastRowLinePos(fragment)); + this.renderNegativeLineNumbers(fragment.rows, ROWS, + this.getLastColLinePos(fragment)); + } + } + } + + /** + * Render the grid area highlight for the given area name or for all the grid areas. + * + * @param {String} areaName + * Name of the grid area to be highlighted. If no area name is provided, all + * the grid areas should be highlighted. + */ + renderGridArea(areaName) { + let { devicePixelRatio } = this.win; + let displayPixelRatio = getDisplayPixelRatio(this.win); + let paths = []; + + for (let i = 0; i < this.gridData.length; i++) { + let fragment = this.gridData[i]; + + for (let area of fragment.areas) { + if (areaName && areaName != area.name) { + continue; + } + + let rowStart = fragment.rows.lines[area.rowStart - 1]; + let rowEnd = fragment.rows.lines[area.rowEnd - 1]; + let columnStart = fragment.cols.lines[area.columnStart - 1]; + let columnEnd = fragment.cols.lines[area.columnEnd - 1]; + + let x1 = columnStart.start + columnStart.breadth; + let y1 = rowStart.start + rowStart.breadth; + let x2 = columnEnd.start; + let y2 = rowEnd.start; + + let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix); + + // Scale down by `devicePixelRatio` since SVG element already take them into + // account. + let svgPoints = points.map(point => ({ + x: Math.round(point.x / devicePixelRatio), + y: Math.round(point.y / devicePixelRatio) + })); + + // Scale down by `displayPixelRatio` since infobar's HTML elements already take it + // into account; and the zoom scaling is handled by `moveInfobar`. + let bounds = getBoundsFromPoints(points.map(point => ({ + x: Math.round(point.x / displayPixelRatio), + y: Math.round(point.y / displayPixelRatio) + }))); + + paths.push(getPathDescriptionFromPoints(svgPoints)); + + // Update and show the info bar when only displaying a single grid area. + if (areaName) { + this._showGridAreaInfoBar(); + this._updateGridAreaInfobar(area, bounds); + } + } + } + + let areas = this.getElement("areas"); + areas.setAttribute("d", paths.join(" ")); } /** - * Checks if the current node has a CSS Grid layout. + * Render grid area name on the containing grid area cell. * - * @return {Boolean} true if the current node has a CSS grid layout, false otherwise. + * @param {Object} fragment + * The grid fragment of the grid container. + * @param {Object} area + * The area overlay to render on the CSS highlighter canvas. + */ + renderGridAreaName(fragment, area) { + let { rowStart, rowEnd, columnStart, columnEnd } = area; + let { devicePixelRatio } = this.win; + let displayPixelRatio = getDisplayPixelRatio(this.win); + let offset = (displayPixelRatio / 2) % 1; + let fontSize = GRID_AREA_NAME_FONT_SIZE * displayPixelRatio; + let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio); + let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio); + + this.ctx.save(); + this.ctx.translate(offset - canvasX, offset - canvasY); + this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY; + this.ctx.strokeStyle = this.color; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + + // Draw the text for the grid area name. + for (let rowNumber = rowStart; rowNumber < rowEnd; rowNumber++) { + for (let columnNumber = columnStart; columnNumber < columnEnd; columnNumber++) { + let row = fragment.rows.tracks[rowNumber - 1]; + let column = fragment.cols.tracks[columnNumber - 1]; + + // Check if the font size is exceeds the bounds of the containing grid cell. + if (fontSize > (column.breadth * displayPixelRatio) || + fontSize > (row.breadth * displayPixelRatio)) { + fontSize = (column.breadth + row.breadth) / 2; + this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY; + } + + let textWidth = this.ctx.measureText(area.name).width; + // The width of the character 'm' approximates the height of the text. + let textHeight = this.ctx.measureText("m").width; + // Padding in pixels for the line number text inside of the line number container. + let padding = 3 * displayPixelRatio; + + let boxWidth = textWidth + 2 * padding; + let boxHeight = textHeight + 2 * padding; + + let x = column.start + column.breadth / 2; + let y = row.start + row.breadth / 2; + + [x, y] = apply(this.currentMatrix, [x, y]); + + let rectXPos = x - boxWidth / 2; + let rectYPos = y - boxHeight / 2; + + // Draw a rounded rectangle with a border width of 1 pixel, + // a border color matching the grid color, and a white background. + this.ctx.lineWidth = 1 * displayPixelRatio; + this.ctx.strokeStyle = this.color; + this.ctx.fillStyle = "white"; + let radius = 2 * displayPixelRatio; + drawRoundedRect(this.ctx, rectXPos, rectYPos, boxWidth, boxHeight, radius); + + this.ctx.fillStyle = this.color; + this.ctx.fillText(area.name, x, y + padding); + } + } + + this.ctx.restore(); + } + + /** + * Renders the grid area overlay on the css grid highlighter canvas. */ - isGrid() { - return this.currentNode.getGridFragments().length > 0; + renderGridAreaOverlay() { + let padding = 1; + + for (let i = 0; i < this.gridData.length; i++) { + let fragment = this.gridData[i]; + + for (let area of fragment.areas) { + let { rowStart, rowEnd, columnStart, columnEnd, type } = area; + + if (type === "implicit") { + continue; + } + + // Draw the line edges for the grid area. + const areaColStart = fragment.cols.lines[columnStart - 1]; + const areaColEnd = fragment.cols.lines[columnEnd - 1]; + + const areaRowStart = fragment.rows.lines[rowStart - 1]; + const areaRowEnd = fragment.rows.lines[rowEnd - 1]; + + const areaColStartLinePos = areaColStart.start + areaColStart.breadth; + const areaRowStartLinePos = areaRowStart.start + areaRowStart.breadth; + + this.renderLine(areaColStartLinePos + padding, areaRowStartLinePos, + areaRowEnd.start, COLUMNS, "areaEdge"); + this.renderLine(areaColEnd.start - padding, areaRowStartLinePos, + areaRowEnd.start, COLUMNS, "areaEdge"); + + this.renderLine(areaRowStartLinePos + padding, areaColStartLinePos, + areaColEnd.start, ROWS, "areaEdge"); + this.renderLine(areaRowEnd.start - padding, areaColStartLinePos, areaColEnd.start, + ROWS, "areaEdge"); + + this.renderGridAreaName(fragment, area); + } + } + } + + /** + * Render the grid cell highlight for the given grid fragment index, row and column + * number. + * + * @param {Number} gridFragmentIndex + * Index of the grid fragment to render the grid cell highlight. + * @param {Number} rowNumber + * Row number of the grid cell to highlight. + * @param {Number} columnNumber + * Column number of the grid cell to highlight. + */ + renderGridCell(gridFragmentIndex, rowNumber, columnNumber) { + let fragment = this.gridData[gridFragmentIndex]; + + if (!fragment) { + return; + } + + let row = fragment.rows.tracks[rowNumber - 1]; + let column = fragment.cols.tracks[columnNumber - 1]; + + if (!row || !column) { + return; + } + + let x1 = column.start; + let y1 = row.start; + let x2 = column.start + column.breadth; + let y2 = row.start + row.breadth; + + let { devicePixelRatio } = this.win; + let displayPixelRatio = getDisplayPixelRatio(this.win); + let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix); + + // Scale down by `devicePixelRatio` since SVG element already take them into account. + let svgPoints = points.map(point => ({ + x: Math.round(point.x / devicePixelRatio), + y: Math.round(point.y / devicePixelRatio) + })); + + // Scale down by `displayPixelRatio` since infobar's HTML elements already take it + // into account, and the zoom scaling is handled by `moveInfobar`. + let bounds = getBoundsFromPoints(points.map(point => ({ + x: Math.round(point.x / displayPixelRatio), + y: Math.round(point.y / displayPixelRatio) + }))); + + let cells = this.getElement("cells"); + cells.setAttribute("d", getPathDescriptionFromPoints(svgPoints)); + + this._showGridCellInfoBar(); + this._updateGridCellInfobar(rowNumber, columnNumber, bounds); + } + + /** + * Render the grid gap area on the css grid highlighter canvas. + * + * @param {Number} linePos + * The line position along the x-axis for a column grid line and + * y-axis for a row grid line. + * @param {Number} startPos + * The start position of the cross side of the grid line. + * @param {Number} endPos + * The end position of the cross side of the grid line. + * @param {Number} breadth + * The grid line breadth value. + * @param {String} dimensionType + * The grid dimension type which is either the constant COLUMNS or ROWS. + */ + renderGridGap(linePos, startPos, endPos, breadth, dimensionType) { + let { devicePixelRatio } = this.win; + let displayPixelRatio = getDisplayPixelRatio(this.win); + let offset = (displayPixelRatio / 2) % 1; + let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio); + let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio); + + linePos = Math.round(linePos); + startPos = Math.round(startPos); + breadth = Math.round(breadth); + + this.ctx.save(); + this.ctx.fillStyle = this.getGridGapPattern(devicePixelRatio, dimensionType); + this.ctx.translate(offset - canvasX, offset - canvasY); + + if (dimensionType === COLUMNS) { + if (isFinite(endPos)) { + endPos = Math.round(endPos); + } else { + endPos = this._winDimensions.height; + startPos = -endPos; + } + drawRect(this.ctx, linePos, startPos, linePos + breadth, endPos, + this.currentMatrix); + } else { + if (isFinite(endPos)) { + endPos = Math.round(endPos); + } else { + endPos = this._winDimensions.width; + startPos = -endPos; + } + drawRect(this.ctx, startPos, linePos, endPos, linePos + breadth, + this.currentMatrix); + } + + this.ctx.fill(); + this.ctx.restore(); } /** - * Is a given grid fragment valid? i.e. does it actually have tracks? In some cases, we - * may have a fragment that defines column tracks but doesn't have any rows (or vice - * versa). In which case we do not want to draw anything for that fragment. + * Render the grid line name highlight for the given grid fragment index, lineNumber, + * and dimensionType. + * + * @param {Number} gridFragmentIndex + * Index of the grid fragment to render the grid line highlight. + * @param {Number} lineNumber + * Line number of the grid line to highlight. + * @param {String} dimensionType + * The dimension type of the grid line. + */ + renderGridLineNames(gridFragmentIndex, lineNumber, dimensionType) { + let fragment = this.gridData[gridFragmentIndex]; + + if (!fragment || !lineNumber || !dimensionType) { + return; + } + + const { names } = fragment[dimensionType].lines[lineNumber - 1]; + let linePos; + + if (dimensionType === ROWS) { + linePos = fragment.rows.lines[lineNumber - 1]; + } else if (dimensionType === COLUMNS) { + linePos = fragment.cols.lines[lineNumber - 1]; + } + + if (!linePos) { + return; + } + + let currentZoom = getCurrentZoom(this.win); + let { bounds } = this.currentQuads.content[gridFragmentIndex]; + + const rowYPosition = fragment.rows.lines[0]; + const colXPosition = fragment.rows.lines[0]; + + let x = dimensionType === COLUMNS + ? linePos.start + (bounds.left / currentZoom) + : colXPosition.start + (bounds.left / currentZoom); + + let y = dimensionType === ROWS + ? linePos.start + (bounds.top / currentZoom) + : rowYPosition.start + (bounds.top / currentZoom); + + this._showGridLineInfoBar(); + this._updateGridLineInfobar(names.join(", "), lineNumber, x, y); + } + + /** + * Render the grid line number on the css grid highlighter canvas. * - * @param {Object} fragment - * @return {Boolean} + * @param {Number} lineNumber + * The grid line number. + * @param {Number} linePos + * The line position along the x-axis for a column grid line and + * y-axis for a row grid line. + * @param {Number} startPos + * The start position of the cross side of the grid line. + * @param {Number} breadth + * The grid line breadth value. + * @param {String} dimensionType + * The grid dimension type which is either the constant COLUMNS or ROWS. + * @param {Number||undefined} stackedLineIndex + * The line index position of the stacked line. */ - isValidFragment(fragment) { - return fragment.cols.tracks.length && fragment.rows.tracks.length; + renderGridLineNumber(lineNumber, linePos, startPos, breadth, dimensionType, + stackedLineIndex) { + let displayPixelRatio = getDisplayPixelRatio(this.win); + let { devicePixelRatio } = this.win; + let offset = (displayPixelRatio / 2) % 1; + let fontSize = GRID_FONT_SIZE * displayPixelRatio; + let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio); + let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio); + + linePos = Math.round(linePos); + startPos = Math.round(startPos); + breadth = Math.round(breadth); + + if (linePos + breadth < 0) { + // Don't render the line number since the line is not visible on screen. + return; + } + + this.ctx.save(); + this.ctx.translate(offset - canvasX, offset - canvasY); + this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY; + + // For a general grid box, the height of the character "m" will be its minimum width + // and height. If line number's text width is greater, then use the grid box's text + // width instead. + let textHeight = this.ctx.measureText("m").width; + let textWidth = Math.max(textHeight, this.ctx.measureText(lineNumber).width); + + // Padding in pixels for the line number text inside of the line number container. + let padding = 3 * displayPixelRatio; + let offsetFromEdge = 2 * displayPixelRatio; + + let boxWidth = textWidth + 2 * padding; + let boxHeight = textHeight + 2 * padding; + + // Calculate the x & y coordinates for the line number container, so that its arrow + // tip is centered on the line (or the gap if there is one), and is offset by the + // calculated padding value from the grid container edge. + let x, y; + + if (dimensionType === COLUMNS) { + x = linePos + breadth / 2; + y = startPos; + + if (lineNumber > 0) { + y -= offsetFromEdge; + } else { + y += offsetFromEdge; + } + } else if (dimensionType === ROWS) { + x = startPos; + y = linePos + breadth / 2; + + if (lineNumber > 0) { + x -= offsetFromEdge; + } else { + x += offsetFromEdge; + } + } + + [x, y] = apply(this.currentMatrix, [x, y]); + + if (stackedLineIndex) { + // Offset the stacked line number by half of the box's width/height. + const xOffset = boxWidth / 4; + const yOffset = boxHeight / 4; + + if (lineNumber > 0) { + x -= xOffset; + y -= yOffset; + } else { + x += xOffset; + y += yOffset; + } + } + + if (!this.hasNodeTransformations) { + x = Math.max(x, padding); + y = Math.max(y, padding); + } + + // Draw a bubble rectanglular arrow with a border width of 2 pixels, a border color + // matching the grid color and a white background (the line number will be written in + // black). + this.ctx.lineWidth = 2 * displayPixelRatio; + this.ctx.strokeStyle = this.color; + this.ctx.fillStyle = "white"; + + // See param definitions of drawBubbleRect. + let radius = 2 * displayPixelRatio; + let margin = 2 * displayPixelRatio; + let arrowSize = 8 * displayPixelRatio; + + let minBoxSize = arrowSize * 2 + padding; + boxWidth = Math.max(boxWidth, minBoxSize); + boxHeight = Math.max(boxHeight, minBoxSize); + + if (dimensionType === COLUMNS) { + if (lineNumber > 0) { + drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, + "top"); + // After drawing the number box, we need to center the x/y coordinates of the + // number text written it. + y -= (boxHeight + arrowSize + radius) - boxHeight / 2; + } else { + drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, + "bottom"); + y += (boxHeight + arrowSize + radius) - boxHeight / 2; + } + } else if (dimensionType === ROWS) { + if (lineNumber > 0) { + drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, + "left"); + x -= (boxWidth + arrowSize + radius) - boxWidth / 2; + } else { + drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, + "right"); + x += (boxWidth + arrowSize + radius) - boxWidth / 2; + } + } + + // Write the line number inside of the rectangle. + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + this.ctx.fillStyle = "black"; + const numberText = stackedLineIndex ? "" : lineNumber; + this.ctx.fillText(numberText, x, y); + this.ctx.restore(); } /** - * The AutoRefreshHighlighter's _hasMoved method returns true only if the - * element's quads have changed. Override it so it also returns true if the - * element's grid has changed (which can happen when you change the - * grid-template-* CSS properties with the highlighter displayed). + * Render the grid line on the css grid highlighter canvas. + * + * @param {Number} linePos + * The line position along the x-axis for a column grid line and + * y-axis for a row grid line. + * @param {Number} startPos + * The start position of the cross side of the grid line. + * @param {Number} endPos + * The end position of the cross side of the grid line. + * @param {String} dimensionType + * The grid dimension type which is either the constant COLUMNS or ROWS. + * @param {String} lineType + * The grid line type - "edge", "explicit", or "implicit". + */ + renderLine(linePos, startPos, endPos, dimensionType, lineType) { + let { devicePixelRatio } = this.win; + let lineWidth = getDisplayPixelRatio(this.win); + let offset = (lineWidth / 2) % 1; + let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio); + let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio); + + linePos = Math.round(linePos); + startPos = Math.round(startPos); + endPos = Math.round(endPos); + + this.ctx.save(); + this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash); + this.ctx.beginPath(); + this.ctx.translate(offset - canvasX, offset - canvasY); + + let lineOptions = { + matrix: this.currentMatrix + }; + + if (this.options.showInfiniteLines) { + lineOptions.extendToBoundaries = [canvasX, canvasY, canvasX + CANVAS_SIZE, + canvasY + CANVAS_SIZE]; + } + + if (dimensionType === COLUMNS) { + drawLine(this.ctx, linePos, startPos, linePos, endPos, lineOptions); + } else { + drawLine(this.ctx, startPos, linePos, endPos, linePos, lineOptions); + } + + this.ctx.strokeStyle = this.color; + this.ctx.globalAlpha = GRID_LINES_PROPERTIES[lineType].alpha; + + if (GRID_LINES_PROPERTIES[lineType].lineWidth) { + this.ctx.lineWidth = GRID_LINES_PROPERTIES[lineType].lineWidth * devicePixelRatio; + } else { + this.ctx.lineWidth = lineWidth; + } + + this.ctx.stroke(); + this.ctx.restore(); + } + + /** + * Render the grid lines given the grid dimension information of the + * column or row lines. + * + * @param {GridDimension} gridDimension + * Column or row grid dimension object. + * @param {Object} quad.bounds + * The content bounds of the box model region quads. + * @param {String} dimensionType + * The grid dimension type which is either the constant COLUMNS or ROWS. + * @param {Number} startPos + * The start position of the cross side ("left" for ROWS and "top" for COLUMNS) + * of the grid dimension. + * @param {Number} endPos + * The end position of the cross side ("left" for ROWS and "top" for COLUMNS) + * of the grid dimension. */ - _hasMoved() { - let hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this); + renderLines(gridDimension, dimensionType, startPos, endPos) { + const { lines, tracks } = gridDimension; + const lastEdgeLineIndex = this.getLastEdgeLineIndex(tracks); + + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + let linePos = line.start; + + if (i == 0 || i == lastEdgeLineIndex) { + this.renderLine(linePos, startPos, endPos, dimensionType, "edge"); + } else { + this.renderLine(linePos, startPos, endPos, dimensionType, tracks[i - 1].type); + } + + // Render a second line to illustrate the gutter for non-zero breadth. + if (line.breadth > 0) { + this.renderGridGap(linePos, startPos, endPos, line.breadth, dimensionType); + this.renderLine(linePos + line.breadth, startPos, endPos, dimensionType, + tracks[i].type); + } + } + } + + /** + * Render the grid lines given the grid dimension information of the + * column or row lines. + * + * @param {GridDimension} gridDimension + * Column or row grid dimension object. + * @param {String} dimensionType + * The grid dimension type which is either the constant COLUMNS or ROWS. + * @param {Number} startPos + * The start position of the cross side ("left" for ROWS and "top" for COLUMNS) + * of the grid dimension. + */ + renderLineNumbers(gridDimension, dimensionType, startPos) { + const { lines, tracks } = gridDimension; + // Keep track of the number of collapsed lines per line position. + let stackedLines = []; + + for (let i = 0, line; (line = lines[i++]);) { + // If you place something using negative numbers, you can trigger some implicit + // grid creation above and to the left of the explicit grid (assuming a + // horizontal-tb writing mode). + // + // The first explicit grid line gets the number of 1, and any implicit grid lines + // before 1 get negative numbers. Since here we're rendering only the positive line + // numbers, we have to skip any implicit grid lines before the first one that is + // explicit. The API returns a 0 as the line's number for these implicit lines that + // occurs before the first explicit line. + if (line.number === 0) { + continue; + } + + // Check for overlapping lines. We render a second box beneath the last overlapping + // line number to indicate there are lines beneath it. + const gridLine = tracks[line.number - 1]; + + if (gridLine) { + const { breadth } = gridLine; + + if (breadth === 0) { + stackedLines.push(lines[i].number); - let oldGridData = stringifyGridFragments(this.gridData); - this.gridData = this.currentNode.getGridFragments(); - let newGridData = stringifyGridFragments(this.gridData); + if (stackedLines.length > 0) { + this.renderGridLineNumber(line.number, line.start, startPos, line.breadth, + dimensionType, 1); + } + + continue; + } + } + + this.renderGridLineNumber(line.number, line.start, startPos, line.breadth, + dimensionType); + } + } + + /** + * Render the negative grid lines given the grid dimension information of the + * column or row lines. + * + * @param {GridDimension} gridDimension + * Column or row grid dimension object. + * @param {String} dimensionType + * The grid dimension type which is either the constant COLUMNS or ROWS. + * @param {Number} startPos + * The start position of the cross side ("left" for ROWS and "top" for COLUMNS) + * of the grid dimension. + */ + renderNegativeLineNumbers(gridDimension, dimensionType, startPos) { + const { lines, tracks } = gridDimension; + // Keep track of the number of collapsed lines per line position. + let stackedLines = []; + + for (let i = 0, line; (line = lines[i++]);) { + let linePos = line.start; + let negativeLineNumber = line.negativeNumber; - return hasMoved || oldGridData !== newGridData; + // Don't render any negative line number greater than -1. + if (negativeLineNumber == 0) { + break; + } + + // Check for overlapping lines. We render a second box beneath the last overlapping + // line number to indicate there are lines beneath it. + const gridLine = tracks[line.number - 1]; + + if (gridLine) { + const { breadth } = gridLine; + + if (breadth === 0) { + stackedLines.push(negativeLineNumber); + + if (stackedLines.length > 0) { + this.renderGridLineNumber(negativeLineNumber, linePos, startPos, + line.breadth, dimensionType, 1); + } + + continue; + } + } + + // For negative line numbers, we want to display the smallest + // value at the front of the stack. + if (stackedLines.length) { + negativeLineNumber = stackedLines[0]; + stackedLines = []; + } + + this.renderGridLineNumber(negativeLineNumber, linePos, startPos, line.breadth, + dimensionType); + } } /** * Update the highlighter on the current highlighted node (the one that was - * passed as an argument to show(node)). - * Should be called whenever node's geometry or grid changes. + * passed as an argument to show(node)). Should be called whenever node's geometry + * or grid changes. */ _update() { setIgnoreLayoutChanges(true); let root = this.getElement("root"); let cells = this.getElement("cells"); let areas = this.getElement("areas"); @@ -648,17 +1465,17 @@ class CssGridHighlighter extends AutoRef // Updates the <canvas> element's position and size. // It also clear the <canvas>'s drawing context. updateCanvasElement(this.canvas, this._canvasPosition, this.win.devicePixelRatio); // Clear the grid area highlights. this.clearGridAreas(); this.clearGridCell(); - // Update the current matrix used in our canvas' rendering + // Update the current matrix used in our canvas' rendering. let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode, this.win); this.currentMatrix = currentMatrix; this.hasNodeTransformations = hasNodeTransformations; // Start drawing the grid fragments. for (let i = 0; i < this.gridData.length; i++) { this.renderFragment(this.gridData[i]); @@ -725,18 +1542,18 @@ class CssGridHighlighter extends AutoRef * @param {Object} bounds * A DOMRect-like object represent the grid cell rectangle. */ _updateGridCellInfobar(rowNumber, columnNumber, bounds) { let { width, height } = bounds; let dim = parseFloat(width.toPrecision(6)) + " \u00D7 " + parseFloat(height.toPrecision(6)); - let position = LAYOUT_L10N.getFormatStr("layout.rowColumnPositions", - rowNumber, columnNumber); + let position = LAYOUT_L10N.getFormatStr("layout.rowColumnPositions", rowNumber, + columnNumber); this.getElement("cell-infobar-position").setTextContent(position); this.getElement("cell-infobar-dimensions").setTextContent(dim); let container = this.getElement("cell-infobar-container"); moveInfobar(container, bounds, this.win, { position: "top", hideIfOffscreen: true @@ -755,850 +1572,14 @@ class CssGridHighlighter extends AutoRef * @param {Number} y * The y-coordinate of the grid line. */ _updateGridLineInfobar(gridLineNames, gridLineNumber, x, y) { this.getElement("line-infobar-number").setTextContent(gridLineNumber); this.getElement("line-infobar-names").setTextContent(gridLineNames); let container = this.getElement("line-infobar-container"); - moveInfobar(container, - getBoundsFromPoints([{x, y}, {x, y}, {x, y}, {x, y}]), this.win); - } - - /** - * The <canvas>'s position needs to be updated if the page scrolls too much, in order - * to give the illusion that it always covers the viewport. - */ - _scrollUpdate() { - let hasUpdated = updateCanvasPosition(this._canvasPosition, this._scroll, this.win, - this._winDimensions); - - if (hasUpdated) { - this._update(); - } - } - - getFirstRowLinePos(fragment) { - return fragment.rows.lines[0].start; - } - - getLastRowLinePos(fragment) { - return fragment.rows.lines[fragment.rows.lines.length - 1].start; - } - - getFirstColLinePos(fragment) { - return fragment.cols.lines[0].start; - } - - getLastColLinePos(fragment) { - return fragment.cols.lines[fragment.cols.lines.length - 1].start; - } - - /** - * Get the GridLine index of the last edge of the explicit grid for a grid dimension. - * - * @param {GridTracks} tracks - * The grid track of a given grid dimension. - * @return {Number} index of the last edge of the explicit grid for a grid dimension. - */ - getLastEdgeLineIndex(tracks) { - let trackIndex = tracks.length - 1; - - // Traverse the grid track backwards until we find an explicit track. - while (trackIndex >= 0 && tracks[trackIndex].type != "explicit") { - trackIndex--; - } - - // The grid line index is the grid track index + 1. - return trackIndex + 1; - } - - renderFragment(fragment) { - if (!this.isValidFragment(fragment)) { - return; - } - - this.renderLines(fragment.cols, COLUMNS, "left", "top", "height", - this.getFirstRowLinePos(fragment), - this.getLastRowLinePos(fragment)); - this.renderLines(fragment.rows, ROWS, "top", "left", "width", - this.getFirstColLinePos(fragment), - this.getLastColLinePos(fragment)); - - if (this.options.showGridAreasOverlay) { - this.renderGridAreaOverlay(); - } - - // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines. - if (this.options.showGridLineNumbers) { - this.renderLineNumbers(fragment.cols, COLUMNS, "left", "top", - this.getFirstRowLinePos(fragment)); - this.renderLineNumbers(fragment.rows, ROWS, "top", "left", - this.getFirstColLinePos(fragment)); - - if (Services.prefs.getBoolPref(NEGATIVE_LINE_NUMBERS_PREF)) { - this.renderNegativeLineNumbers(fragment.cols, COLUMNS, "left", "top", - this.getLastRowLinePos(fragment)); - this.renderNegativeLineNumbers(fragment.rows, ROWS, "top", "left", - this.getLastColLinePos(fragment)); - } - } - } - - /** - * Render the negative grid lines given the grid dimension information of the - * column or row lines. - * - * See @param for renderLines. - */ - renderNegativeLineNumbers(gridDimension, dimensionType, mainSide, crossSide, - startPos) { - let lineStartPos = startPos; - - // Keep track of the number of collapsed lines per line position - let stackedLines = []; - - const { lines } = gridDimension; - - for (let i = 0, line; (line = lines[i++]);) { - let linePos = line.start; - let negativeLineNumber = i - lines.length - 1; - - // Check for overlapping lines. We render a second box beneath the last overlapping - // line number to indicate there are lines beneath it. - const gridLine = gridDimension.tracks[line.number - 1]; - - if (gridLine) { - const { breadth } = gridLine; - - if (breadth === 0) { - stackedLines.push(negativeLineNumber); - - if (stackedLines.length > 0) { - this.renderGridLineNumber(negativeLineNumber, linePos, lineStartPos, - line.breadth, dimensionType, 1); - } - - continue; - } - } - - // For negative line numbers, we want to display the smallest - // value at the front of the stack. - if (stackedLines.length) { - negativeLineNumber = stackedLines[0]; - stackedLines = []; - } - - this.renderGridLineNumber(negativeLineNumber, linePos, lineStartPos, line.breadth, - dimensionType); - } - } - - /** - * Renders the grid area overlay on the css grid highlighter canvas. - */ - renderGridAreaOverlay() { - let padding = 1; - - for (let i = 0; i < this.gridData.length; i++) { - let fragment = this.gridData[i]; - - for (let area of fragment.areas) { - let { rowStart, rowEnd, columnStart, columnEnd, type } = area; - - if (type === "implicit") { - continue; - } - - // Draw the line edges for the grid area - const areaColStart = fragment.cols.lines[columnStart - 1]; - const areaColEnd = fragment.cols.lines[columnEnd - 1]; - - const areaRowStart = fragment.rows.lines[rowStart - 1]; - const areaRowEnd = fragment.rows.lines[rowEnd - 1]; - - const areaColStartLinePos = areaColStart.start + areaColStart.breadth; - const areaRowStartLinePos = areaRowStart.start + areaRowStart.breadth; - - this.renderLine(areaColStartLinePos + padding, - areaRowStartLinePos, areaRowEnd.start, - COLUMNS, "areaEdge"); - this.renderLine(areaColEnd.start - padding, - areaRowStartLinePos, areaRowEnd.start, - COLUMNS, "areaEdge"); - - this.renderLine(areaRowStartLinePos + padding, - areaColStartLinePos, areaColEnd.start, - ROWS, "areaEdge"); - this.renderLine(areaRowEnd.start - padding, - areaColStartLinePos, areaColEnd.start, - ROWS, "areaEdge"); - - this.renderGridAreaName(fragment, area); - } - } - - this.ctx.restore(); - } - - /** - * Render grid area name on the containing grid area cell. - * - * @param {Object} fragment - * The grid fragment of the grid container. - * @param {Object} area - * The area overlay to render on the CSS highlighter canvas. - */ - renderGridAreaName(fragment, area) { - let { rowStart, rowEnd, columnStart, columnEnd } = area; - let { devicePixelRatio } = this.win; - let displayPixelRatio = getDisplayPixelRatio(this.win); - let offset = (displayPixelRatio / 2) % 1; - let fontSize = (GRID_AREA_NAME_FONT_SIZE * displayPixelRatio); - - this.ctx.save(); - - let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio); - let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio); - this.ctx.translate(offset - canvasX, offset - canvasY); - - this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY; - this.ctx.strokeStyle = this.color; - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "middle"; - - // Draw the text for the grid area name. - for (let rowNumber = rowStart; rowNumber < rowEnd; rowNumber++) { - for (let columnNumber = columnStart; columnNumber < columnEnd; columnNumber++) { - let row = fragment.rows.tracks[rowNumber - 1]; - let column = fragment.cols.tracks[columnNumber - 1]; - - // Check if the font size is exceeds the bounds of the containing grid cell. - if (fontSize > (column.breadth * displayPixelRatio) || - fontSize > (row.breadth * displayPixelRatio)) { - fontSize = (column.breadth + row.breadth) / 2; - this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY; - } - - let textWidth = this.ctx.measureText(area.name).width; - - // The width of the character 'm' approximates the height of the text. - let textHeight = this.ctx.measureText("m").width; - - // Padding in pixels for the line number text inside of the line number container. - let padding = 3 * displayPixelRatio; - - let boxWidth = textWidth + 2 * padding; - let boxHeight = textHeight + 2 * padding; - - let x = column.start + column.breadth / 2; - let y = row.start + row.breadth / 2; - - [x, y] = apply(this.currentMatrix, [x, y]); - - let rectXPos = x - boxWidth / 2; - let rectYPos = y - boxHeight / 2; - - // Draw a rounded rectangle with a border width of 1 pixel, - // a border color matching the grid color, and a white background - this.ctx.lineWidth = 1 * displayPixelRatio; - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = "white"; - let radius = 2 * displayPixelRatio; - drawRoundedRect(this.ctx, rectXPos, rectYPos, boxWidth, boxHeight, radius); - - this.ctx.fillStyle = this.color; - this.ctx.fillText(area.name, x, y + padding); - } - } - - this.ctx.restore(); - } - - /** - * Render the grid lines given the grid dimension information of the - * column or row lines. - * - * @param {GridDimension} gridDimension - * Column or row grid dimension object. - * @param {Object} quad.bounds - * The content bounds of the box model region quads. - * @param {String} dimensionType - * The grid dimension type which is either the constant COLUMNS or ROWS. - * @param {String} mainSide - * The main side of the given grid dimension - "top" for rows and - * "left" for columns. - * @param {String} crossSide - * The cross side of the given grid dimension - "left" for rows and - * "top" for columns. - * @param {String} mainSize - * The main size of the given grid dimension - "width" for rows and - * "height" for columns. - * @param {Number} startPos - * The start position of the cross side of the grid dimension. - * @param {Number} endPos - * The end position of the cross side of the grid dimension. - */ - renderLines(gridDimension, dimensionType, mainSide, crossSide, - mainSize, startPos, endPos) { - let lineStartPos = startPos; - let lineEndPos = endPos; - - let lastEdgeLineIndex = this.getLastEdgeLineIndex(gridDimension.tracks); - - for (let i = 0; i < gridDimension.lines.length; i++) { - let line = gridDimension.lines[i]; - let linePos = line.start; - - if (i == 0 || i == lastEdgeLineIndex) { - this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType, "edge"); - } else { - this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType, - gridDimension.tracks[i - 1].type); - } - - // Render a second line to illustrate the gutter for non-zero breadth. - if (line.breadth > 0) { - this.renderGridGap(linePos, lineStartPos, lineEndPos, line.breadth, - dimensionType); - this.renderLine(linePos + line.breadth, lineStartPos, lineEndPos, dimensionType, - gridDimension.tracks[i].type); - } - } - } - - /** - * Render the grid lines given the grid dimension information of the - * column or row lines. - * - * see @param for renderLines. - */ - renderLineNumbers(gridDimension, dimensionType, mainSide, crossSide, - startPos) { - let lineStartPos = startPos; - - // Keep track of the number of collapsed lines per line position - let stackedLines = []; - - const { lines } = gridDimension; - - for (let i = 0, line; (line = lines[i++]);) { - let linePos = line.start; - - // If you place something using negative numbers, you can trigger some implicit grid - // creation above and to the left of the explicit grid (assuming a horizontal-tb - // writing mode). - // The first explicit grid line gets the number of 1; any implicit grid lines - // before 1 get negative numbers, but do not get any positivity numbers. - // Since here we're rendering only the positive line numbers, we have to skip any - // implicit grid lines before the first tha is explicit. - // For such lines the API returns always 0 as line's number. - if (line.number === 0) { - continue; - } - - // Check for overlapping lines. We render a second box beneath the last overlapping - // line number to indicate there are lines beneath it. - const gridLine = gridDimension.tracks[line.number - 1]; - - if (gridLine) { - const { breadth } = gridLine; - - if (breadth === 0) { - stackedLines.push(gridDimension.lines[i].number); - - if (stackedLines.length > 0) { - this.renderGridLineNumber(line.number, linePos, lineStartPos, line.breadth, - dimensionType, 1); - } - - continue; - } - } - - this.renderGridLineNumber(line.number, linePos, lineStartPos, line.breadth, - dimensionType); - } - } - - /** - * Render the grid line on the css grid highlighter canvas. - * - * @param {Number} linePos - * The line position along the x-axis for a column grid line and - * y-axis for a row grid line. - * @param {Number} startPos - * The start position of the cross side of the grid line. - * @param {Number} endPos - * The end position of the cross side of the grid line. - * @param {String} dimensionType - * The grid dimension type which is either the constant COLUMNS or ROWS. - * @param {String} lineType - * The grid line type - "edge", "explicit", or "implicit". - */ - renderLine(linePos, startPos, endPos, dimensionType, lineType) { - let { devicePixelRatio } = this.win; - let lineWidth = getDisplayPixelRatio(this.win); - let offset = (lineWidth / 2) % 1; - - let x = Math.round(this._canvasPosition.x * devicePixelRatio); - let y = Math.round(this._canvasPosition.y * devicePixelRatio); - - linePos = Math.round(linePos); - startPos = Math.round(startPos); - endPos = Math.round(endPos); - - this.ctx.save(); - this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash); - this.ctx.beginPath(); - this.ctx.translate(offset - x, offset - y); - - let lineOptions = { - matrix: this.currentMatrix - }; - - if (this.options.showInfiniteLines) { - lineOptions.extendToBoundaries = [x, y, x + CANVAS_SIZE, y + CANVAS_SIZE]; - } - - if (dimensionType === COLUMNS) { - drawLine(this.ctx, linePos, startPos, linePos, endPos, lineOptions); - } else { - drawLine(this.ctx, startPos, linePos, endPos, linePos, lineOptions); - } - - this.ctx.strokeStyle = this.color; - this.ctx.globalAlpha = GRID_LINES_PROPERTIES[lineType].alpha; - - if (GRID_LINES_PROPERTIES[lineType].lineWidth) { - this.ctx.lineWidth = GRID_LINES_PROPERTIES[lineType].lineWidth * devicePixelRatio; - } else { - this.ctx.lineWidth = lineWidth; - } - - this.ctx.stroke(); - this.ctx.restore(); - } - - /** - * Render the grid line number on the css grid highlighter canvas. - * - * @param {Number} lineNumber - * The grid line number. - * @param {Number} linePos - * The line position along the x-axis for a column grid line and - * y-axis for a row grid line. - * @param {Number} startPos - * The start position of the cross side of the grid line. - * @param {Number} breadth - * The grid line breadth value. - * @param {String} dimensionType - * The grid dimension type which is either the constant COLUMNS or ROWS. - * @param {Number||undefined} stackedLineIndex - * The line index position of the stacked line. - */ - renderGridLineNumber(lineNumber, linePos, startPos, breadth, dimensionType, - stackedLineIndex) { - let displayPixelRatio = getDisplayPixelRatio(this.win); - let { devicePixelRatio } = this.win; - let offset = (displayPixelRatio / 2) % 1; - - linePos = Math.round(linePos); - startPos = Math.round(startPos); - breadth = Math.round(breadth); - - if (linePos + breadth < 0) { - // The line is not visible on screen, don't render the line number - return; - } - - this.ctx.save(); - let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio); - let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio); - this.ctx.translate(offset - canvasX, offset - canvasY); - - let fontSize = (GRID_FONT_SIZE * displayPixelRatio); - this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY; - - // For a general grid box, the height of the character "m" will be its minimum width - // and height. If line number's text width is greater then grid box's text width - // will use that instead. - let textHeight = this.ctx.measureText("m").width; - let textWidth = Math.max(textHeight, this.ctx.measureText(lineNumber).width); - - // Padding in pixels for the line number text inside of the line number container. - let padding = 3 * displayPixelRatio; - let offsetFromEdge = 2 * displayPixelRatio; - - let boxWidth = textWidth + 2 * padding; - let boxHeight = textHeight + 2 * padding; - - // Calculate the x & y coordinates for the line number container, so that its arrow - // tip is centered on the line (or the gap if there is one), and is offset by the - // calculated padding value from the grid container edge. - let x, y; - - if (dimensionType === COLUMNS) { - x = linePos + breadth / 2; - y = startPos; - - if (lineNumber > 0) { - y -= offsetFromEdge; - } else { - y += offsetFromEdge; - } - } else if (dimensionType === ROWS) { - x = startPos; - y = linePos + breadth / 2; - - if (lineNumber > 0) { - x -= offsetFromEdge; - } else { - x += offsetFromEdge; - } - } - - [x, y] = apply(this.currentMatrix, [x, y]); - - if (stackedLineIndex) { - // Offset the stacked line number by half of the box's width/height - const xOffset = boxWidth / 4; - const yOffset = boxHeight / 4; - - if (lineNumber > 0) { - x -= xOffset; - y -= yOffset; - } else { - x += xOffset; - y += yOffset; - } - } - - if (!this.hasNodeTransformations) { - x = Math.max(x, padding); - y = Math.max(y, padding); - } - - // Draw a bubble rectanglular arrow with a border width of 2 pixels, a border color - // matching the grid color and a white background (the line number will be written in - // black). - this.ctx.lineWidth = 2 * displayPixelRatio; - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = "white"; - - // See param definitions of drawBubbleRect - let radius = 2 * displayPixelRatio; - let margin = 2 * displayPixelRatio; - let arrowSize = 8 * displayPixelRatio; - - let minBoxSize = arrowSize * 2 + padding; - boxWidth = Math.max(boxWidth, minBoxSize); - boxHeight = Math.max(boxHeight, minBoxSize); - - if (dimensionType === COLUMNS) { - if (lineNumber > 0) { - drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, - "top"); - // After drawing the number box, we need to center the x/y coordinates of the - // number text written it. - y -= (boxHeight + arrowSize + radius) - boxHeight / 2; - } else { - drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, - "bottom"); - y += (boxHeight + arrowSize + radius) - boxHeight / 2; - } - } else if (dimensionType === ROWS) { - if (lineNumber > 0) { - drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, - "left"); - x -= (boxWidth + arrowSize + radius) - boxWidth / 2; - } else { - drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize, - "right"); - x += (boxWidth + arrowSize + radius) - boxWidth / 2; - } - } - - // Write the line number inside of the rectangle. - this.ctx.textAlign = "center"; - this.ctx.textBaseline = "middle"; - this.ctx.fillStyle = "black"; - const numberText = stackedLineIndex ? "" : lineNumber; - this.ctx.fillText(numberText, x, y); - - this.ctx.restore(); - } - - /** - * Render the grid gap area on the css grid highlighter canvas. - * - * @param {Number} linePos - * The line position along the x-axis for a column grid line and - * y-axis for a row grid line. - * @param {Number} startPos - * The start position of the cross side of the grid line. - * @param {Number} endPos - * The end position of the cross side of the grid line. - * @param {Number} breadth - * The grid line breadth value. - * @param {String} dimensionType - * The grid dimension type which is either the constant COLUMNS or ROWS. - */ - renderGridGap(linePos, startPos, endPos, breadth, dimensionType) { - let { devicePixelRatio } = this.win; - let displayPixelRatio = getDisplayPixelRatio(this.win); - let offset = (displayPixelRatio / 2) % 1; - - let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio); - let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio); - - linePos = Math.round(linePos); - startPos = Math.round(startPos); - breadth = Math.round(breadth); - - this.ctx.save(); - this.ctx.fillStyle = this.getGridGapPattern(devicePixelRatio, dimensionType); - this.ctx.translate(offset - canvasX, offset - canvasY); - - if (dimensionType === COLUMNS) { - if (isFinite(endPos)) { - endPos = Math.round(endPos); - } else { - endPos = this._winDimensions.height; - startPos = -endPos; - } - drawRect(this.ctx, linePos, startPos, linePos + breadth, endPos, - this.currentMatrix); - } else { - if (isFinite(endPos)) { - endPos = Math.round(endPos); - } else { - endPos = this._winDimensions.width; - startPos = -endPos; - } - drawRect(this.ctx, startPos, linePos, endPos, linePos + breadth, - this.currentMatrix); - } - this.ctx.fill(); - this.ctx.restore(); - } - - /** - * Render the grid area highlight for the given area name or for all the grid areas. - * - * @param {String} areaName - * Name of the grid area to be highlighted. If no area name is provided, all - * the grid areas should be highlighted. - */ - renderGridArea(areaName) { - let paths = []; - let { devicePixelRatio } = this.win; - let displayPixelRatio = getDisplayPixelRatio(this.win); - - for (let i = 0; i < this.gridData.length; i++) { - let fragment = this.gridData[i]; - - for (let area of fragment.areas) { - if (areaName && areaName != area.name) { - continue; - } - - let rowStart = fragment.rows.lines[area.rowStart - 1]; - let rowEnd = fragment.rows.lines[area.rowEnd - 1]; - let columnStart = fragment.cols.lines[area.columnStart - 1]; - let columnEnd = fragment.cols.lines[area.columnEnd - 1]; - - let x1 = columnStart.start + columnStart.breadth; - let y1 = rowStart.start + rowStart.breadth; - let x2 = columnEnd.start; - let y2 = rowEnd.start; - - let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix); - - // Scale down by `devicePixelRatio` since SVG element already take them into - // account. - let svgPoints = points.map(point => ({ - x: Math.round(point.x / devicePixelRatio), - y: Math.round(point.y / devicePixelRatio) - })); - - // Scale down by `displayPixelRatio` since infobar's HTML elements already take it - // into account; and the zoom scaling is handled by `moveInfobar`. - let bounds = getBoundsFromPoints(points.map(point => ({ - x: Math.round(point.x / displayPixelRatio), - y: Math.round(point.y / displayPixelRatio) - }))); - - paths.push(getPathDescriptionFromPoints(svgPoints)); - - // Update and show the info bar when only displaying a single grid area. - if (areaName) { - this._showGridAreaInfoBar(); - this._updateGridAreaInfobar(area, bounds); - } - } - } - - let areas = this.getElement("areas"); - areas.setAttribute("d", paths.join(" ")); - } - - /** - * Render the grid cell highlight for the given grid fragment index, row and column - * number. - * - * @param {Number} gridFragmentIndex - * Index of the grid fragment to render the grid cell highlight. - * @param {Number} rowNumber - * Row number of the grid cell to highlight. - * @param {Number} columnNumber - * Column number of the grid cell to highlight. - */ - renderGridCell(gridFragmentIndex, rowNumber, columnNumber) { - let fragment = this.gridData[gridFragmentIndex]; - - if (!fragment) { - return; - } - - let row = fragment.rows.tracks[rowNumber - 1]; - let column = fragment.cols.tracks[columnNumber - 1]; - - if (!row || !column) { - return; - } - - let x1 = column.start; - let y1 = row.start; - let x2 = column.start + column.breadth; - let y2 = row.start + row.breadth; - - let { devicePixelRatio } = this.win; - let displayPixelRatio = getDisplayPixelRatio(this.win); - - let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix); - - // Scale down by `devicePixelRatio` since SVG element already take them into account. - let svgPoints = points.map(point => ({ - x: Math.round(point.x / devicePixelRatio), - y: Math.round(point.y / devicePixelRatio) - })); - - // Scale down by `displayPixelRatio` since infobar's HTML elements already take it - // into account, and the zoom scaling is handled by `moveInfobar`. - let bounds = getBoundsFromPoints(points.map(point => ({ - x: Math.round(point.x / displayPixelRatio), - y: Math.round(point.y / displayPixelRatio) - }))); - - let cells = this.getElement("cells"); - cells.setAttribute("d", getPathDescriptionFromPoints(svgPoints)); - - this._showGridCellInfoBar(); - this._updateGridCellInfobar(rowNumber, columnNumber, bounds); - } - - /** - * Render the grid line name highlight for the given grid fragment index, lineNumber, - * and dimensionType. - * - * @param {Number} gridFragmentIndex - * Index of the grid fragment to render the grid line highlight. - * @param {Number} lineNumber - * Line number of the grid line to highlight. - * @param {String} dimensionType - * The dimension type of the grid line. - */ - renderGridLineNames(gridFragmentIndex, lineNumber, dimensionType) { - let fragment = this.gridData[gridFragmentIndex]; - - if (!fragment || !lineNumber || !dimensionType) { - return; - } - - const { names } = fragment[dimensionType].lines[lineNumber - 1]; - let linePos; - - if (dimensionType === ROWS) { - linePos = fragment.rows.lines[lineNumber - 1]; - } else if (dimensionType === COLUMNS) { - linePos = fragment.cols.lines[lineNumber - 1]; - } - - if (!linePos) { - return; - } - - let currentZoom = getCurrentZoom(this.win); - let { bounds } = this.currentQuads.content[gridFragmentIndex]; - - const rowYPosition = fragment.rows.lines[0]; - const colXPosition = fragment.rows.lines[0]; - - let x = dimensionType === COLUMNS - ? linePos.start + (bounds.left / currentZoom) - : colXPosition.start + (bounds.left / currentZoom); - - let y = dimensionType === ROWS - ? linePos.start + (bounds.top / currentZoom) - : rowYPosition.start + (bounds.top / currentZoom); - - this._showGridLineInfoBar(); - this._updateGridLineInfobar(names.join(", "), lineNumber, x, y); - } - - /** - * Hide the highlighter, the canvas and the infobars. - */ - _hide() { - setIgnoreLayoutChanges(true); - this._hideGrid(); - this._hideGridElements(); - this._hideGridAreaInfoBar(); - this._hideGridCellInfoBar(); - this._hideGridLineInfoBar(); - setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement); - } - - _hideGrid() { - this.getElement("canvas").setAttribute("hidden", "true"); - } - - _showGrid() { - this.getElement("canvas").removeAttribute("hidden"); - } - - _hideGridElements() { - this.getElement("elements").setAttribute("hidden", "true"); - } - - _showGridElements() { - this.getElement("elements").removeAttribute("hidden"); - } - - _hideGridAreaInfoBar() { - this.getElement("area-infobar-container").setAttribute("hidden", "true"); - } - - _showGridAreaInfoBar() { - this.getElement("area-infobar-container").removeAttribute("hidden"); - } - - _hideGridCellInfoBar() { - this.getElement("cell-infobar-container").setAttribute("hidden", "true"); - } - - _showGridCellInfoBar() { - this.getElement("cell-infobar-container").removeAttribute("hidden"); - } - - _hideGridLineInfoBar() { - this.getElement("line-infobar-container").setAttribute("hidden", "true"); - } - - _showGridLineInfoBar() { - this.getElement("line-infobar-container").removeAttribute("hidden"); + moveInfobar(container, getBoundsFromPoints([{x, y}, {x, y}, {x, y}, {x, y}]), + this.win); } } exports.CssGridHighlighter = CssGridHighlighter;
--- a/dom/animation/KeyframeEffectReadOnly.cpp +++ b/dom/animation/KeyframeEffectReadOnly.cpp @@ -1965,21 +1965,28 @@ KeyframeEffectReadOnly::UpdateEffectSet( EffectSet* effectSet = aEffectSet ? aEffectSet : EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); if (!effectSet) { return; } + nsIFrame* frame = GetAnimationFrame(); if (HasAnimationOfProperty(eCSSProperty_opacity)) { effectSet->SetMayHaveOpacityAnimation(); + if (frame) { + frame->SetMayHaveOpacityAnimation(); + } } if (HasAnimationOfProperty(eCSSProperty_transform)) { effectSet->SetMayHaveTransformAnimation(); + if (frame) { + frame->SetMayHaveTransformAnimation(); + } } } template void KeyframeEffectReadOnly::ComposeStyle<RefPtr<AnimValuesStyleRule>&>( RefPtr<AnimValuesStyleRule>& aAnimationRule, const nsCSSPropertyIDSet& aPropertiesToSkip);
--- a/dom/clients/manager/ClientHandle.cpp +++ b/dom/clients/manager/ClientHandle.cpp @@ -84,16 +84,22 @@ ClientHandle::Activate(PClientManagerChi if (!actor) { Shutdown(); return; } ActivateThing(static_cast<ClientHandleChild*>(actor)); } +void +ClientHandle::ExecutionReady(const ClientInfo& aClientInfo) +{ + mClientInfo = aClientInfo; +} + const ClientInfo& ClientHandle::Info() const { return mClientInfo; } } // namespace dom } // namespace mozilla
--- a/dom/clients/manager/ClientHandle.h +++ b/dom/clients/manager/ClientHandle.h @@ -27,29 +27,34 @@ class PClientManagerChild; // convert it into a live actor-backed object attached to a particular // ClientSource somewhere in the browser. If the ClientSource is // destroyed then the ClientHandle will simply begin to reject operations. // We do not currently provide a way to be notified when the ClientSource // is destroyed, but this could be added in the future. class ClientHandle final : public ClientThing<ClientHandleChild> { friend class ClientManager; + friend class ClientHandleChild; RefPtr<ClientManager> mManager; nsCOMPtr<nsISerialEventTarget> mSerialEventTarget; ClientInfo mClientInfo; ~ClientHandle(); void Shutdown(); already_AddRefed<ClientOpPromise> StartOp(const ClientOpConstructorArgs& aArgs); + // Private methods called by ClientHandleChild + void + ExecutionReady(const ClientInfo& aClientInfo); + // Private methods called by ClientManager ClientHandle(ClientManager* aManager, nsISerialEventTarget* aSerialEventTarget, const ClientInfo& aClientInfo); void Activate(PClientManagerChild* aActor);
--- a/dom/clients/manager/ClientHandleChild.cpp +++ b/dom/clients/manager/ClientHandleChild.cpp @@ -9,16 +9,25 @@ #include "ClientHandleOpChild.h" #include "mozilla/dom/ClientIPCTypes.h" namespace mozilla { namespace dom { using mozilla::ipc::IPCResult; +IPCResult +ClientHandleChild::RecvExecutionReady(const IPCClientInfo& aClientInfo) +{ + if (mHandle) { + mHandle->ExecutionReady(ClientInfo(aClientInfo)); + } + return IPC_OK(); +} + void ClientHandleChild::ActorDestroy(ActorDestroyReason aReason) { if (mHandle) { mHandle->RevokeActor(this); // Revoking the actor link should automatically cause the owner // to call RevokeOwner() as well. @@ -45,25 +54,25 @@ ClientHandleChild::ClientHandleChild() , mTeardownStarted(false) { } void ClientHandleChild::SetOwner(ClientThing<ClientHandleChild>* aThing) { MOZ_DIAGNOSTIC_ASSERT(!mHandle); - mHandle = aThing; + mHandle = static_cast<ClientHandle*>(aThing); MOZ_DIAGNOSTIC_ASSERT(mHandle); } void ClientHandleChild::RevokeOwner(ClientThing<ClientHandleChild>* aThing) { MOZ_DIAGNOSTIC_ASSERT(mHandle); - MOZ_DIAGNOSTIC_ASSERT(mHandle == aThing); + MOZ_DIAGNOSTIC_ASSERT(mHandle == static_cast<ClientHandle*>(aThing)); mHandle = nullptr; } void ClientHandleChild::MaybeStartTeardown() { if (mTeardownStarted) { return;
--- a/dom/clients/manager/ClientHandleChild.h +++ b/dom/clients/manager/ClientHandleChild.h @@ -14,20 +14,23 @@ namespace dom { class ClientHandle; class ClientInfo; template <typename ActorType> class ClientThing; class ClientHandleChild final : public PClientHandleChild { - ClientThing<ClientHandleChild>* mHandle; + ClientHandle* mHandle; bool mTeardownStarted; // PClientHandleChild interface + mozilla::ipc::IPCResult + RecvExecutionReady(const IPCClientInfo& aClientInfo) override; + void ActorDestroy(ActorDestroyReason aReason) override; PClientHandleOpChild* AllocPClientHandleOpChild(const ClientOpConstructorArgs& aArgs) override; bool DeallocPClientHandleOpChild(PClientHandleOpChild* aActor) override;
--- a/dom/clients/manager/ClientIPCTypes.ipdlh +++ b/dom/clients/manager/ClientIPCTypes.ipdlh @@ -45,16 +45,22 @@ struct IPCClientWorkerState }; union IPCClientState { IPCClientWindowState; IPCClientWorkerState; }; +struct ClientSourceExecutionReadyArgs +{ + nsCString url; + FrameType frameType; +}; + struct ClientOpenWindowArgs { }; struct ClientOpConstructorArgs { };
--- a/dom/clients/manager/ClientManager.cpp +++ b/dom/clients/manager/ClientManager.cpp @@ -4,16 +4,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ClientManager.h" #include "ClientHandle.h" #include "ClientManagerChild.h" #include "ClientManagerOpChild.h" +#include "ClientPrefs.h" #include "ClientSource.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/workers/bindings/WorkerHolderToken.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/PBackgroundChild.h" #include "prthread.h" namespace mozilla { @@ -198,16 +199,18 @@ void ClientManager::Startup() { MOZ_ASSERT(NS_IsMainThread()); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED PRStatus status = #endif PR_NewThreadPrivateIndex(&sClientManagerThreadLocalIndex, nullptr); MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS); + + ClientPrefsInit(); } // static UniquePtr<ClientSource> ClientManager::CreateSource(ClientType aType, nsIPrincipal* aPrincipal) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal);
--- a/dom/clients/manager/ClientManagerParent.cpp +++ b/dom/clients/manager/ClientManagerParent.cpp @@ -96,16 +96,25 @@ ClientManagerParent::AllocPClientSourceP bool ClientManagerParent::DeallocPClientSourceParent(PClientSourceParent* aActor) { delete aActor; return true; } +IPCResult +ClientManagerParent::RecvPClientSourceConstructor(PClientSourceParent* aActor, + const ClientSourceConstructorArgs& aArgs) +{ + ClientSourceParent* actor = static_cast<ClientSourceParent*>(aActor); + actor->Init(); + return IPC_OK(); +} + ClientManagerParent::ClientManagerParent() : mService(ClientManagerService::GetOrCreateInstance()) { } ClientManagerParent::~ClientManagerParent() { }
--- a/dom/clients/manager/ClientManagerParent.h +++ b/dom/clients/manager/ClientManagerParent.h @@ -51,16 +51,20 @@ class ClientManagerParent final : public DeallocPClientNavigateOpParent(PClientNavigateOpParent* aActor) override; PClientSourceParent* AllocPClientSourceParent(const ClientSourceConstructorArgs& aArgs) override; bool DeallocPClientSourceParent(PClientSourceParent* aActor) override; + mozilla::ipc::IPCResult + RecvPClientSourceConstructor(PClientSourceParent* aActor, + const ClientSourceConstructorArgs& aArgs) override; + public: ClientManagerParent(); ~ClientManagerParent(); }; } // namespace dom } // namespace mozilla
--- a/dom/clients/manager/ClientManagerService.cpp +++ b/dom/clients/manager/ClientManagerService.cpp @@ -78,34 +78,43 @@ ClientManagerService::GetOrCreateInstanc if (!sClientManagerServiceInstance) { sClientManagerServiceInstance = new ClientManagerService(); } RefPtr<ClientManagerService> ref(sClientManagerServiceInstance); return ref.forget(); } -void +bool ClientManagerService::AddSource(ClientSourceParent* aSource) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aSource); auto entry = mSourceTable.LookupForAdd(aSource->Info().Id()); - MOZ_DIAGNOSTIC_ASSERT(!entry); + // Do not permit overwriting an existing ClientSource with the same + // UUID. This would allow a spoofed ClientParentSource actor to + // intercept postMessage() intended for the real actor. + if (NS_WARN_IF(!!entry)) { + return false; + } entry.OrInsert([&] { return aSource; }); + return true; } -void +bool ClientManagerService::RemoveSource(ClientSourceParent* aSource) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aSource); auto entry = mSourceTable.Lookup(aSource->Info().Id()); - MOZ_DIAGNOSTIC_ASSERT(entry); + if (NS_WARN_IF(!entry)) { + return false; + } entry.Remove(); + return true; } ClientSourceParent* ClientManagerService::FindSource(const nsID& aID, const PrincipalInfo& aPrincipalInfo) { AssertIsOnBackgroundThread(); auto entry = mSourceTable.Lookup(aID);
--- a/dom/clients/manager/ClientManagerService.h +++ b/dom/clients/manager/ClientManagerService.h @@ -25,20 +25,20 @@ class ClientManagerService final ClientManagerService(); ~ClientManagerService(); public: static already_AddRefed<ClientManagerService> GetOrCreateInstance(); - void + bool AddSource(ClientSourceParent* aSource); - void + bool RemoveSource(ClientSourceParent* aSource); ClientSourceParent* FindSource(const nsID& aID, const mozilla::ipc::PrincipalInfo& aPrincipalInfo); NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService) };
new file mode 100644 --- /dev/null +++ b/dom/clients/manager/ClientPrefs.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientPrefs.h" + +namespace mozilla { +namespace dom { + +namespace { + +bool gDataURLUniqueOpaqueOrigin = false; + +} // anonymous namespace + +void +ClientPrefsInit() +{ + Preferences::AddBoolVarCache(&gDataURLUniqueOpaqueOrigin, + "security.data_uri.unique_opaque_origin", + false); +} + +bool +ClientPrefsGetDataURLUniqueOpaqueOrigin() +{ + return gDataURLUniqueOpaqueOrigin; +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/clients/manager/ClientPrefs.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _mozilla_dom_ClientPrefs_h +#define _mozilla_dom_ClientPrefs_h + +namespace mozilla { +namespace dom { + +void +ClientPrefsInit(); + +bool +ClientPrefsGetAllowUniqueOpaqueOrigin(); + +} // namespace dom +} // namespace mozilla + +#endif // _mozilla_dom_ClientPrefs_h
--- a/dom/clients/manager/ClientSource.cpp +++ b/dom/clients/manager/ClientSource.cpp @@ -4,54 +4,109 @@ * 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 "ClientSource.h" #include "ClientManager.h" #include "ClientManagerChild.h" #include "ClientSourceChild.h" +#include "ClientValidation.h" #include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "nsIDocShell.h" +#include "nsPIDOMWindow.h" namespace mozilla { namespace dom { +using mozilla::dom::workers::WorkerPrivate; using mozilla::ipc::PrincipalInfo; void ClientSource::Shutdown() { NS_ASSERT_OWNINGTHREAD(ClientSource); if (IsShutdown()) { return; } ShutdownThing(); mManager = nullptr; } +void +ClientSource::ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) +{ + // Fast fail if we don't understand this particular principal/URL combination. + // This can happen since we use MozURL for validation which does not handle + // some of the more obscure internal principal/url combinations. Normal + // content pages will pass this check. + if (NS_WARN_IF(!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(), + aArgs.url()))) { + Shutdown(); + return; + } + + mClientInfo.SetURL(aArgs.url()); + mClientInfo.SetFrameType(aArgs.frameType()); + MaybeExecute([aArgs](PClientSourceChild* aActor) { + aActor->SendExecutionReady(aArgs); + }); +} + +WorkerPrivate* +ClientSource::GetWorkerPrivate() const +{ + NS_ASSERT_OWNINGTHREAD(ClientSource); + if (!mOwner.is<WorkerPrivate*>()) { + return nullptr; + } + return mOwner.as<WorkerPrivate*>(); +} + +nsIDocShell* +ClientSource::GetDocShell() const +{ + NS_ASSERT_OWNINGTHREAD(ClientSource); + if (!mOwner.is<nsCOMPtr<nsIDocShell>>()) { + return nullptr; + } + return mOwner.as<nsCOMPtr<nsIDocShell>>(); +} + ClientSource::ClientSource(ClientManager* aManager, const ClientSourceConstructorArgs& aArgs) : mManager(aManager) + , mOwner(AsVariant(Nothing())) , mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime()) { MOZ_ASSERT(mManager); } void ClientSource::Activate(PClientManagerChild* aActor) { NS_ASSERT_OWNINGTHREAD(ClientSource); MOZ_ASSERT(!GetActor()); if (IsShutdown()) { return; } + // Fast fail if we don't understand this particular kind of PrincipalInfo. + // This can happen since we use MozURL for validation which does not handle + // some of the more obscure internal principal/url combinations. Normal + // content pages will pass this check. + if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) { + Shutdown(); + return; + } + ClientSourceConstructorArgs args(mClientInfo.Id(), mClientInfo.Type(), mClientInfo.PrincipalInfo(), mClientInfo.CreationTime()); PClientSourceChild* actor = aActor->SendPClientSourceConstructor(args); if (!actor) { Shutdown(); return; } @@ -59,10 +114,123 @@ ClientSource::Activate(PClientManagerChi ActivateThing(static_cast<ClientSourceChild*>(actor)); } ClientSource::~ClientSource() { Shutdown(); } +nsPIDOMWindowInner* +ClientSource::GetInnerWindow() const +{ + NS_ASSERT_OWNINGTHREAD(ClientSource); + if (!mOwner.is<RefPtr<nsPIDOMWindowInner>>()) { + return nullptr; + } + return mOwner.as<RefPtr<nsPIDOMWindowInner>>(); +} + +void +ClientSource::WorkerExecutionReady(WorkerPrivate* aWorkerPrivate) +{ + MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + // Its safe to store the WorkerPrivate* here because the ClientSource + // is explicitly destroyed by WorkerPrivate before exiting its run loop. + MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>()); + mOwner = AsVariant(aWorkerPrivate); + + ClientSourceExecutionReadyArgs args( + aWorkerPrivate->GetLocationInfo().mHref, + FrameType::None); + + ExecutionReady(args); +} + +nsresult +ClientSource::WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aInnerWindow); + MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->IsCurrentInnerWindow()); + MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->HasActiveDocument()); + + nsIDocument* doc = aInnerWindow->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + return NS_ERROR_UNEXPECTED; + } + + // Don't use nsAutoCString here since IPC requires a full nsCString anyway. + nsCString spec; + + nsIURI* uri = doc->GetOriginalURI(); + if (uri) { + nsresult rv = uri->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsPIDOMWindowOuter* outer = aInnerWindow->GetOuterWindow(); + if (NS_WARN_IF(!outer)) { + return NS_ERROR_UNEXPECTED; + } + + FrameType frameType = FrameType::Top_level; + if (!outer->IsTopLevelWindow()) { + frameType = FrameType::Nested; + } else if(outer->HadOriginalOpener()) { + frameType = FrameType::Auxiliary; + } + + // We should either be setting a window execution ready for the + // first time or setting the same window execution ready again. + // The secondary calls are due to initial about:blank replacement. + MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>() || + mOwner.is<nsCOMPtr<nsIDocShell>>() || + GetInnerWindow() == aInnerWindow); + + // This creates a cycle with the window. It is broken when + // nsGlobalWindow::FreeInnerObjects() deletes the ClientSource. + mOwner = AsVariant(RefPtr<nsPIDOMWindowInner>(aInnerWindow)); + + ClientSourceExecutionReadyArgs args(spec, frameType); + ExecutionReady(args); + + return NS_OK; +} + +nsresult +ClientSource::DocShellExecutionReady(nsIDocShell* aDocShell) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aDocShell); + + nsPIDOMWindowOuter* outer = aDocShell->GetWindow(); + if (NS_WARN_IF(!outer)) { + return NS_ERROR_UNEXPECTED; + } + + // TODO: dedupe this with WindowExecutionReady + FrameType frameType = FrameType::Top_level; + if (!outer->IsTopLevelWindow()) { + frameType = FrameType::Nested; + } else if(outer->HadOriginalOpener()) { + frameType = FrameType::Auxiliary; + } + + MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>()); + + // This creates a cycle with the docshell. It is broken when + // nsDocShell::Destroy() deletes the ClientSource. + mOwner = AsVariant(nsCOMPtr<nsIDocShell>(aDocShell)); + + ClientSourceExecutionReadyArgs args(NS_LITERAL_CSTRING("about:blank"), + frameType); + ExecutionReady(args); + + return NS_OK; +} + } // namespace dom } // namespace mozilla
--- a/dom/clients/manager/ClientSource.h +++ b/dom/clients/manager/ClientSource.h @@ -4,22 +4,26 @@ * 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_ClientSource_h #define _mozilla_dom_ClientSource_h #include "mozilla/dom/ClientInfo.h" #include "mozilla/dom/ClientThing.h" +class nsIDocShell; +class nsPIDOMWindowInner; + namespace mozilla { namespace dom { class ClientManager; class ClientSourceChild; class ClientSourceConstructorArgs; +class ClientSourceExecutionReadyArgs; class PClientManagerChild; namespace workers { class WorkerPrivate; } // workers namespace // ClientSource is an RAII style class that is designed to be held via // a UniquePtr<>. When created ClientSource will register the existence @@ -30,28 +34,54 @@ class WorkerPrivate; class ClientSource final : public ClientThing<ClientSourceChild> { friend class ClientManager; NS_DECL_OWNINGTHREAD RefPtr<ClientManager> mManager; + Variant<Nothing, + RefPtr<nsPIDOMWindowInner>, + nsCOMPtr<nsIDocShell>, + mozilla::dom::workers::WorkerPrivate*> mOwner; + ClientInfo mClientInfo; void Shutdown(); + void + ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs); + + mozilla::dom::workers::WorkerPrivate* + GetWorkerPrivate() const; + + nsIDocShell* + GetDocShell() const; + // Private methods called by ClientManager ClientSource(ClientManager* aManager, const ClientSourceConstructorArgs& aArgs); void Activate(PClientManagerChild* aActor); public: ~ClientSource(); + + nsPIDOMWindowInner* + GetInnerWindow() const; + + void + WorkerExecutionReady(mozilla::dom::workers::WorkerPrivate* aWorkerPrivate); + + nsresult + WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow); + + nsresult + DocShellExecutionReady(nsIDocShell* aDocShell); }; } // namespace dom } // namespace mozilla #endif // _mozilla_dom_ClientSource_h
--- a/dom/clients/manager/ClientSourceParent.cpp +++ b/dom/clients/manager/ClientSourceParent.cpp @@ -4,36 +4,123 @@ * 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 "ClientSourceParent.h" #include "ClientHandleParent.h" #include "ClientManagerService.h" #include "ClientSourceOpParent.h" +#include "ClientValidation.h" #include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/PClientManagerParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/SystemGroup.h" #include "mozilla/Unused.h" namespace mozilla { namespace dom { +using mozilla::ipc::BackgroundParent; using mozilla::ipc::IPCResult; using mozilla::ipc::PrincipalInfo; +namespace { + +// It would be nice to use a lambda instead of this class, but we cannot +// move capture in lambdas yet and ContentParent cannot be AddRef'd off +// the main thread. +class KillContentParentRunnable final : public Runnable +{ + RefPtr<ContentParent> mContentParent; + +public: + explicit KillContentParentRunnable(RefPtr<ContentParent>&& aContentParent) + : Runnable("KillContentParentRunnable") + , mContentParent(Move(aContentParent)) + { + MOZ_ASSERT(mContentParent); + } + + NS_IMETHOD + Run() + { + MOZ_ASSERT(NS_IsMainThread()); + mContentParent->KillHard("invalid ClientSourceParent actor"); + mContentParent = nullptr; + return NS_OK; + } +}; + +} // anonymous namespace + +void +ClientSourceParent::KillInvalidChild() +{ + // Try to get the content process before we destroy the actor below. + RefPtr<ContentParent> process = + BackgroundParent::GetContentParent(Manager()->Manager()); + + // First, immediately teardown the ClientSource actor. No matter what + // we want to start this process as soon as possible. + Unused << ClientSourceParent::Send__delete__(this); + + // If we are running in non-e10s, then there is nothing else to do here. + // There is no child process and we don't want to crash the entire browser + // in release builds. In general, though, this should not happen in non-e10s + // so we do assert this condition. + if (!process) { + MOZ_DIAGNOSTIC_ASSERT(false, "invalid ClientSourceParent in non-e10s"); + return; + } + + // In e10s mode we also want to kill the child process. Validation failures + // typically mean someone sent us bogus data over the IPC link. We can't + // trust that process any more. We have to do this on the main thread, so + // there is a small window of time before we kill the process. This is why + // we start the actor destruction immediately above. + nsCOMPtr<nsIRunnable> r = new KillContentParentRunnable(Move(process)); + MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget())); +} + IPCResult ClientSourceParent::RecvTeardown() { Unused << Send__delete__(this); return IPC_OK(); } +IPCResult +ClientSourceParent::RecvExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) +{ + // Now that we have the creation URL for the Client we can do some validation + // to make sure the child actor is not giving us garbage. Since we validate + // on the child side as well we treat a failure here as fatal. + if (!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(), aArgs.url())) { + KillInvalidChild(); + return IPC_OK(); + } + + mClientInfo.SetURL(aArgs.url()); + mClientInfo.SetFrameType(aArgs.frameType()); + mExecutionReady = true; + + for (ClientHandleParent* handle : mHandleList) { + Unused << handle->SendExecutionReady(mClientInfo.ToIPC()); + } + + return IPC_OK(); +}; + void ClientSourceParent::ActorDestroy(ActorDestroyReason aReason) { - mService->RemoveSource(this); + DebugOnly<bool> removed = mService->RemoveSource(this); + MOZ_ASSERT(removed); nsTArray<ClientHandleParent*> handleList(mHandleList); for (ClientHandleParent* handle : handleList) { // This should trigger DetachHandle() to be called removing // the entry from the mHandleList. Unused << ClientHandleParent::Send__delete__(handle); } MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty()); @@ -51,25 +138,45 @@ ClientSourceParent::DeallocPClientSource { delete aActor; return true; } ClientSourceParent::ClientSourceParent(const ClientSourceConstructorArgs& aArgs) : mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime()) , mService(ClientManagerService::GetOrCreateInstance()) + , mExecutionReady(false) { - mService->AddSource(this); } ClientSourceParent::~ClientSourceParent() { MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty()); } +void +ClientSourceParent::Init() +{ + // Ensure the principal is reasonable before adding ourself to the service. + // Since we validate the principal on the child side as well, any failure + // here is treated as fatal. + if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) { + KillInvalidChild(); + return; + } + + // Its possible for AddSource() to fail if there is already an entry for + // our UUID. This should not normally happen, but could if someone is + // spoofing IPC messages. + if (NS_WARN_IF(!mService->AddSource(this))) { + KillInvalidChild(); + return; + } +} + const ClientInfo& ClientSourceParent::Info() const { return mClientInfo; } void ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle)
--- a/dom/clients/manager/ClientSourceParent.h +++ b/dom/clients/manager/ClientSourceParent.h @@ -15,34 +15,44 @@ namespace dom { class ClientHandleParent; class ClientManagerService; class ClientSourceParent final : public PClientSourceParent { ClientInfo mClientInfo; RefPtr<ClientManagerService> mService; nsTArray<ClientHandleParent*> mHandleList; + bool mExecutionReady; + + void + KillInvalidChild(); // PClientSourceParent IPCResult RecvTeardown() override; + IPCResult + RecvExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) override; + void ActorDestroy(ActorDestroyReason aReason) override; PClientSourceOpParent* AllocPClientSourceOpParent(const ClientOpConstructorArgs& aArgs) override; bool DeallocPClientSourceOpParent(PClientSourceOpParent* aActor) override; public: explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs); ~ClientSourceParent(); + void + Init(); + const ClientInfo& Info() const; void AttachHandle(ClientHandleParent* aClientSource); void DetachHandle(ClientHandleParent* aClientSource);
new file mode 100644 --- /dev/null +++ b/dom/clients/manager/ClientValidation.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientValidation.h" + +#include "ClientPrefs.h" +#include "mozilla/net/MozURL.h" + +namespace mozilla { +namespace dom { + +using mozilla::ipc::ContentPrincipalInfo; +using mozilla::ipc::PrincipalInfo; +using mozilla::net::MozURL; + +bool +ClientIsValidPrincipalInfo(const PrincipalInfo& aPrincipalInfo) +{ + // Ideally we would verify that the source process has permission to + // create a window or worker with the given principal, but we don't + // currently have any such restriction in place. Instead, at least + // verify the PrincipalInfo is an expected type and has a parsable + // origin/spec. + switch (aPrincipalInfo.type()) { + // Any system and null principal is acceptable. + case PrincipalInfo::TSystemPrincipalInfo: + case PrincipalInfo::TNullPrincipalInfo: + { + return true; + } + + // Validate content principals to ensure that the origin and spec are sane. + case PrincipalInfo::TContentPrincipalInfo: + { + const ContentPrincipalInfo& content = + aPrincipalInfo.get_ContentPrincipalInfo(); + + // Verify the principal spec parses. + RefPtr<MozURL> specURL; + nsresult rv = MozURL::Init(getter_AddRefs(specURL), content.spec()); + NS_ENSURE_SUCCESS(rv, false); + + // Verify the principal originNoSuffix parses. + RefPtr<MozURL> originURL; + rv = MozURL::Init(getter_AddRefs(originURL), + content.originNoSuffix().get_nsCString()); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString originOrigin; + rv = originURL->GetOrigin(originOrigin); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString specOrigin; + rv = specURL->GetOrigin(specOrigin); + NS_ENSURE_SUCCESS(rv, false); + + // For now require Clients to have a principal where both its + // originNoSuffix and spec have the same origin. This will + // exclude a variety of unusual combinations within the browser + // but its adequate for the features need to support right now. + // If necessary we could expand this function to handle more + // cases in the future. + return specOrigin == originOrigin; + } + default: + { + break; + } + } + + // Windows and workers should not have expanded URLs, etc. + return false; +} + +bool +ClientIsValidCreationURL(const PrincipalInfo& aPrincipalInfo, + const nsACString& aURL) +{ + RefPtr<MozURL> url; + nsresult rv = MozURL::Init(getter_AddRefs(url), aURL); + NS_ENSURE_SUCCESS(rv, false); + + switch (aPrincipalInfo.type()) { + case PrincipalInfo::TContentPrincipalInfo: + { + // Any origin can create an about:blank or about:srcdoc Client. + if (aURL.LowerCaseEqualsLiteral("about:blank") || + aURL.LowerCaseEqualsLiteral("about:srcdoc")) { + return true; + } + + const ContentPrincipalInfo& content = + aPrincipalInfo.get_ContentPrincipalInfo(); + + // Parse the principal origin URL as well. This ensures any MozURL + // parser issues effect both URLs equally. + RefPtr<MozURL> principalURL; + rv = MozURL::Init(getter_AddRefs(principalURL), + content.originNoSuffix().get_nsCString()); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString origin; + rv = url->GetOrigin(origin); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString principalOrigin; + rv = principalURL->GetOrigin(principalOrigin); + NS_ENSURE_SUCCESS(rv, false); + + // The vast majority of sites should simply result in the same principal + // and URL origin. + if (principalOrigin == origin) { + return true; + } + + nsAutoCString scheme; + rv = url->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, false); + + // Generally any origin can also open javascript: windows and workers. + if (scheme.LowerCaseEqualsLiteral("javascript")) { + return true; + } + + // We have some tests that use data: URL windows without an opaque + // origin. This should only happen when a pref is set. + if (!ClientPrefsGetDataURLUniqueOpaqueOrigin() && + scheme.LowerCaseEqualsLiteral("data")) { + return true; + } + + nsAutoCString principalScheme; + rv = principalURL->GetScheme(principalScheme); + NS_ENSURE_SUCCESS(rv, false); + + // Otherwise don't support this URL type in the clients sub-system for + // now. This will exclude a variety of internal browser clients, but + // currently we don't need to support those. This function can be + // expanded to handle more cases as necessary. + return false; + } + case PrincipalInfo::TSystemPrincipalInfo: + { + nsAutoCString scheme; + rv = url->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, false); + + // While many types of documents can be created with a system principal, + // there are only a few that can reasonably become windows. We attempt + // to validate the list of known cases here with a simple scheme check. + return scheme.LowerCaseEqualsLiteral("about") || + scheme.LowerCaseEqualsLiteral("chrome") || + scheme.LowerCaseEqualsLiteral("resource") || + scheme.LowerCaseEqualsLiteral("blob") || + scheme.LowerCaseEqualsLiteral("javascript") || + scheme.LowerCaseEqualsLiteral("view-source") || + + (!ClientPrefsGetDataURLUniqueOpaqueOrigin() && + scheme.LowerCaseEqualsLiteral("data")); + } + case PrincipalInfo::TNullPrincipalInfo: + { + // A wide variety of clients can have a null principal. For example, + // sandboxed iframes can have a normal content URL. For now allow + // any parsable URL for null principals. This is relatively safe since + // null principals have unique origins and won't most ClientManagerService + // queries anyway. + return true; + } + default: + { + break; + } + } + + // Clients (windows/workers) should never have an expanded principal type. + return false; +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/clients/manager/ClientValidation.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _mozilla_dom_ClientValidation_h +#define _mozilla_dom_ClientValidation_h + +namespace mozilla { + +namespace ipc { +class PrincipalInfo; +} // namespace ipc + +namespace dom { + +bool +ClientIsValidPrincipalInfo(const mozilla::ipc::PrincipalInfo& aPrincipalInfo); + +bool +ClientIsValidCreationURL(const mozilla::ipc::PrincipalInfo& aPrincipalInfo, + const nsACString& aURL); + +} // namespace dom +} // namespace mozilla + +#endif // _mozilla_dom_ClientValidation_h
--- a/dom/clients/manager/PClientHandle.ipdl +++ b/dom/clients/manager/PClientHandle.ipdl @@ -20,13 +20,15 @@ protocol PClientHandle manages PClientHandleOp; parent: async Teardown(); async PClientHandleOp(ClientOpConstructorArgs aArgs); child: + async ExecutionReady(IPCClientInfo aClientInfo); + async __delete__(); }; } // namespace dom } // namespace mozilla
--- a/dom/clients/manager/PClientSource.ipdl +++ b/dom/clients/manager/PClientSource.ipdl @@ -16,16 +16,17 @@ namespace dom { sync protocol PClientSource { manager PClientManager; manages PClientSourceOp; parent: async Teardown(); + async ExecutionReady(ClientSourceExecutionReadyArgs aArgs); child: async PClientSourceOp(ClientOpConstructorArgs aArgs); async __delete__(); }; } // namespace dom
--- a/dom/clients/manager/moz.build +++ b/dom/clients/manager/moz.build @@ -31,22 +31,24 @@ UNIFIED_SOURCES += [ 'ClientManagerOpParent.cpp', 'ClientManagerParent.cpp', 'ClientManagerService.cpp', 'ClientNavigateOpChild.cpp', 'ClientNavigateOpParent.cpp', 'ClientOpenWindowOpActors.cpp', 'ClientOpenWindowOpChild.cpp', 'ClientOpenWindowOpParent.cpp', + 'ClientPrefs.cpp', 'ClientSource.cpp', 'ClientSourceChild.cpp', 'ClientSourceOpChild.cpp', 'ClientSourceOpParent.cpp', 'ClientSourceParent.cpp', 'ClientState.cpp', + 'ClientValidation.cpp', ] IPDL_SOURCES += [ 'ClientIPCTypes.ipdlh', 'PClientHandle.ipdl', 'PClientHandleOp.ipdl', 'PClientManager.ipdl', 'PClientManagerOp.ipdl',
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -1291,17 +1291,21 @@ ContentParent::Init() { nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); if (obs) { size_t length = ArrayLength(sObserverTopics); for (size_t i = 0; i < length; ++i) { obs->AddObserver(this, sObserverTopics[i], false); } } + + // Register ContentParent as an observer for changes to any pref whose prefix + // matches the empty string, i.e. all of them. Preferences::AddStrongObserver(this, ""); + if (obs) { nsAutoString cpId; cpId.AppendInt(static_cast<uint64_t>(this->ChildID())); obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-created", cpId.get()); } #ifdef ACCESSIBILITY // If accessibility is running in chrome process then start it in content @@ -2768,24 +2772,24 @@ ContentParent::Observe(nsISupports* aSub return NS_OK; // listening for memory pressure event if (!strcmp(aTopic, "memory-pressure") && !StringEndsWith(nsDependentString(aData), NS_LITERAL_STRING("-no-forward"))) { Unused << SendFlushMemory(nsDependentString(aData)); } - // listening for remotePrefs... else if (!strcmp(aTopic, "nsPref:changed")) { - // Some prefs are not useful in the content process. + // A pref changed. If it's not on the blacklist, inform child processes. #define BLACKLIST_ENTRY(s) { s, (sizeof(s)/sizeof(char16_t)) - 1 } struct BlacklistEntry { const char16_t* mPrefBranch; size_t mLen; }; + // These prefs are not useful in child processes. static const BlacklistEntry sContentPrefBranchBlacklist[] = { BLACKLIST_ENTRY(u"app.update.lastUpdateTime."), BLACKLIST_ENTRY(u"datareporting.policy."), BLACKLIST_ENTRY(u"browser.safebrowsing.provider."), BLACKLIST_ENTRY(u"extensions.getAddons.cache."), BLACKLIST_ENTRY(u"media.gmp-manager."), BLACKLIST_ENTRY(u"media.gmp-gmpopenh264."), };
--- a/editor/composer/nsEditorSpellCheck.cpp +++ b/editor/composer/nsEditorSpellCheck.cpp @@ -656,21 +656,27 @@ nsEditorSpellCheck::SetCurrentDictionary printf("***** Clearing content preferences for |%s|\n", NS_ConvertUTF16toUTF8(aDictionary).get()); #endif } // Also store it in as a preference, so we can use it as a fallback. // We don't want this for mail composer because it uses // "spellchecker.dictionary" as a preference. - Preferences::SetString("spellchecker.dictionary", aDictionary); + // + // XXX: Prefs can only be set in the parent process, so this condition is + // necessary to stop libpref from throwing errors. But this should + // probably be handled in a better way. + if (XRE_IsParentProcess()) { + Preferences::SetString("spellchecker.dictionary", aDictionary); #ifdef DEBUG_DICT - printf("***** Storing spellchecker.dictionary |%s|\n", - NS_ConvertUTF16toUTF8(aDictionary).get()); + printf("***** Possibly storing spellchecker.dictionary |%s|\n", + NS_ConvertUTF16toUTF8(aDictionary).get()); #endif + } } } return mSpellChecker->SetCurrentDictionary(aDictionary); } NS_IMETHODIMP nsEditorSpellCheck::UninitSpellChecker() {
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic +++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic @@ -1,9 +1,9 @@ -52373 +52374 0/nm 0th/pt 1/n1 1st/p 1th/tc 2/nm 2nd/p 2th/tc @@ -27173,16 +27173,17 @@ fuck/SMGDRZ! fucker/M! fuckhead/SM! fuddle/DSMG fudge/DSMG fuehrer/MS fuel's fuel/ADGS fug +fugacious/YP fugal fuggy fugitive/MS fugue/SM fuhrer/SM fulcrum/MS fulfill/LDGS fulfilled/U
--- a/gfx/layers/ipc/ImageBridgeParent.cpp +++ b/gfx/layers/ipc/ImageBridgeParent.cpp @@ -384,16 +384,19 @@ ImageBridgeParent::NotifyImageComposites MOZ_ASSERT(aNotifications[i].mNotification.compositable()); ProcessId pid = aNotifications[i].mImageBridgeProcessId; while (end < aNotifications.Length() && aNotifications[end].mImageBridgeProcessId == pid) { notifications.AppendElement(aNotifications[end].mNotification); ++end; } RefPtr<ImageBridgeParent> bridge = GetInstance(pid); + if (!bridge || bridge->mClosed) { + continue; + } bridge->SendPendingAsyncMessages(); if (!bridge->SendDidComposite(notifications)) { ok = false; } i = end; } return ok; }
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp +++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp @@ -132,22 +132,21 @@ AsyncImagePipelineManager::UpdateAsyncIm if (mDestroyed) { return; } AsyncImagePipeline* pipeline = mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId)); if (!pipeline) { return; } pipeline->mInitialised = true; - pipeline->mIsChanged = true; - pipeline->mScBounds = aScBounds; - pipeline->mScTransform = aScTransform; - pipeline->mScaleToSize = aScaleToSize; - pipeline->mFilter = aFilter; - pipeline->mMixBlendMode = aMixBlendMode; + pipeline->Update(aScBounds, + aScTransform, + aScaleToSize, + aFilter, + aMixBlendMode); } Maybe<TextureHost::ResourceUpdateOp> AsyncImagePipelineManager::UpdateImageKeys(wr::ResourceUpdateQueue& aResources, AsyncImagePipeline* aPipeline, nsTArray<wr::ImageKey>& aKeys) { MOZ_ASSERT(aKeys.IsEmpty());
--- a/gfx/layers/wr/AsyncImagePipelineManager.h +++ b/gfx/layers/wr/AsyncImagePipelineManager.h @@ -115,16 +115,33 @@ private: struct PipelineTexturesHolder { // Holds forwarding WebRenderTextureHosts. std::queue<ForwardingTextureHost> mTextureHosts; Maybe<wr::Epoch> mDestroyedEpoch; }; struct AsyncImagePipeline { AsyncImagePipeline(); + void Update(const LayoutDeviceRect& aScBounds, + const gfx::Matrix4x4& aScTransform, + const gfx::MaybeIntSize& aScaleToSize, + const wr::ImageRendering& aFilter, + const wr::MixBlendMode& aMixBlendMode) + { + mIsChanged |= !mScBounds.IsEqualEdges(aScBounds) || + mScTransform != aScTransform || + mScaleToSize != aScaleToSize || + mFilter != aFilter || + mMixBlendMode != aMixBlendMode; + mScBounds = aScBounds; + mScTransform = aScTransform; + mScaleToSize = aScaleToSize; + mFilter = aFilter; + mMixBlendMode = aMixBlendMode; + } bool mInitialised; bool mIsChanged; bool mUseExternalImage; LayoutDeviceRect mScBounds; gfx::Matrix4x4 mScTransform; gfx::MaybeIntSize mScaleToSize; wr::ImageRendering mFilter;
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -665,21 +665,33 @@ WebRenderBridgeParent::RecvEmptyTransact mAsyncImageManager->SetCompositionTime(TimeStamp::Now()); ProcessWebRenderParentCommands(aCommands); mCompositorScheduler->ScheduleComposition(); } mScrollData.SetFocusTarget(aFocusTarget); UpdateAPZ(false); - // XXX Call DidComposite at correct timing. - TimeStamp now = TimeStamp::Now(); - HoldPendingTransactionId(mWrEpoch, aTransactionId, aTxnStartTime, aFwdTime); - mCompositorBridge->DidComposite(wr::AsUint64(mPipelineId), now, now); - + if (!aCommands.IsEmpty()) { + uint32_t wrEpoch = GetNextWrEpoch(); + // Send empty UpdatePipelineResources to WebRender just to notify a new epoch. + // The epoch is used to know a timing of calling DidComposite(). + // This is much simpler than tracking an epoch of AsyncImagePipeline. + wr::ResourceUpdateQueue resourceUpdates; + mApi->UpdatePipelineResources(resourceUpdates, mPipelineId, wr::NewEpoch(wrEpoch)); + HoldPendingTransactionId(wrEpoch, aTransactionId, aTxnStartTime, aFwdTime); + } else { + HoldPendingTransactionId(mWrEpoch, aTransactionId, aTxnStartTime, aFwdTime); + // If WebRenderBridgeParent does not have pending DidComposites, + // send DidComposite now. + if (mPendingTransactionIds.empty()) { + TimeStamp now = TimeStamp::Now(); + mCompositorBridge->DidComposite(wr::AsUint64(mPipelineId), now, now); + } + } return IPC_OK(); } mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetFocusTarget(const FocusTarget& aFocusTarget) { mScrollData.SetFocusTarget(aFocusTarget); UpdateAPZ(false);
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp +++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp @@ -3,16 +3,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gfxUtils.h" #include "mozilla/Range.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/InlineTranslator.h" +#include "mozilla/gfx/Logging.h" #include "mozilla/gfx/RecordedEvent.h" #include "WebRenderTypes.h" #include "webrender_ffi.h" #include <iostream> #include <unordered_map> #ifdef XP_MACOSX @@ -119,20 +120,25 @@ GetUnscaledFont(Translator *aTranslator, FontType::FONTCONFIG; #endif // makes a copy of the data RefPtr<NativeFontResource> fontResource = Factory::CreateNativeFontResource((uint8_t*)data.mData, data.mSize, aTranslator->GetReferenceDrawTarget()->GetBackendType(), type, aTranslator->GetFontContext()); RefPtr<UnscaledFont> unscaledFont; - if (fontResource) { + if (!fontResource) { + gfxDevCrash(LogReason::NativeFontResourceNotFound) << "Failed to creative NativeFontResource for FontKey " << key.mHandle; + } else { // Instance data is only needed for GDI fonts which webrender does not // support. unscaledFont = fontResource->CreateUnscaledFont(data.mIndex, nullptr, 0); + if (!unscaledFont) { + gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to create UnscaledFont for FontKey " << key.mHandle; + } } data.mUnscaledFont = unscaledFont; return unscaledFont; } static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob, gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
--- a/gfx/webrender_bindings/WebRenderTypes.h +++ b/gfx/webrender_bindings/WebRenderTypes.h @@ -56,16 +56,17 @@ inline DebugFlags NewDebugFlags(uint32_t return flags; } inline Maybe<wr::ImageFormat> SurfaceFormatToImageFormat(gfx::SurfaceFormat aFormat) { switch (aFormat) { case gfx::SurfaceFormat::R8G8B8X8: // TODO: use RGBA + opaque flag + case gfx::SurfaceFormat::R8G8B8A8: return Some(wr::ImageFormat::BGRA8); case gfx::SurfaceFormat::B8G8R8X8: // TODO: WebRender will have a BGRA + opaque flag for this but does not // have it yet (cf. issue #732). case gfx::SurfaceFormat::B8G8R8A8: return Some(wr::ImageFormat::BGRA8); case gfx::SurfaceFormat::B8G8R8: return Some(wr::ImageFormat::RGB8);
--- a/ipc/mscom/Interceptor.cpp +++ b/ipc/mscom/Interceptor.cpp @@ -11,16 +11,17 @@ #include "mozilla/mscom/DispatchForwarder.h" #include "mozilla/mscom/FastMarshaler.h" #include "mozilla/mscom/Interceptor.h" #include "mozilla/mscom/InterceptorLog.h" #include "mozilla/mscom/MainThreadInvoker.h" #include "mozilla/mscom/Objref.h" #include "mozilla/mscom/Registration.h" #include "mozilla/mscom/Utils.h" +#include "mozilla/ThreadLocal.h" #include "MainThreadUtils.h" #include "mozilla/Assertions.h" #include "mozilla/DebugOnly.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsRefPtrHashtable.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" @@ -126,16 +127,121 @@ public: LiveSetAutoLock(LiveSetAutoLock&& aOther) = delete; LiveSetAutoLock& operator=(const LiveSetAutoLock& aOther) = delete; LiveSetAutoLock& operator=(LiveSetAutoLock&& aOther) = delete; private: LiveSet* mLiveSet; }; +class MOZ_RAII ReentrySentinel final +{ +public: + explicit ReentrySentinel(Interceptor* aCurrent) + : mCurInterceptor(aCurrent) + { + static const bool kHasTls = tlsSentinelStackTop.init(); + MOZ_RELEASE_ASSERT(kHasTls); + + mPrevSentinel = tlsSentinelStackTop.get(); + tlsSentinelStackTop.set(this); + } + + ~ReentrySentinel() + { + tlsSentinelStackTop.set(mPrevSentinel); + } + + bool IsOutermost() const + { + return !(mPrevSentinel && mPrevSentinel->IsMarshaling(mCurInterceptor)); + } + + ReentrySentinel(const ReentrySentinel&) = delete; + ReentrySentinel(ReentrySentinel&&) = delete; + ReentrySentinel& operator=(const ReentrySentinel&) = delete; + ReentrySentinel& operator=(ReentrySentinel&&) = delete; + +private: + bool IsMarshaling(Interceptor* aTopInterceptor) const + { + return aTopInterceptor == mCurInterceptor || + (mPrevSentinel && mPrevSentinel->IsMarshaling(aTopInterceptor)); + } + +private: + Interceptor* mCurInterceptor; + ReentrySentinel* mPrevSentinel; + + static MOZ_THREAD_LOCAL(ReentrySentinel*) tlsSentinelStackTop; +}; + +MOZ_THREAD_LOCAL(ReentrySentinel*) ReentrySentinel::tlsSentinelStackTop; + +class MOZ_RAII LoggedQIResult final +{ +public: + explicit LoggedQIResult(REFIID aIid) + : mIid(aIid) + , mHr(E_UNEXPECTED) + , mTarget(nullptr) + , mInterceptor(nullptr) + , mBegin(TimeStamp::Now()) + { + } + + ~LoggedQIResult() + { + if (!mTarget) { + return; + } + + TimeStamp end(TimeStamp::Now()); + TimeDuration total(end - mBegin); + TimeDuration overhead(total - mNonOverheadDuration); + + InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead, + &mNonOverheadDuration); + } + + void Log(IUnknown* aTarget, IUnknown* aInterceptor) + { + mTarget = aTarget; + mInterceptor = aInterceptor; + } + + void operator=(HRESULT aHr) + { + mHr = aHr; + } + + operator HRESULT() + { + return mHr; + } + + operator TimeDuration*() + { + return &mNonOverheadDuration; + } + + LoggedQIResult(const LoggedQIResult&) = delete; + LoggedQIResult(LoggedQIResult&&) = delete; + LoggedQIResult& operator=(const LoggedQIResult&) = delete; + LoggedQIResult& operator=(LoggedQIResult&&) = delete; + +private: + REFIID mIid; + HRESULT mHr; + IUnknown* mTarget; + IUnknown* mInterceptor; + TimeDuration mNonOverheadDuration; + TimeStamp mBegin; +}; + } // namespace detail static detail::LiveSet& GetLiveSet() { static detail::LiveSet sLiveSet; return sLiveSet; } @@ -235,19 +341,21 @@ Interceptor::GetUnmarshalClass(REFIID ri pvDestContext, mshlflags, pCid); } HRESULT Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags, DWORD* pSize) { + detail::ReentrySentinel sentinel(this); + HRESULT hr = mStdMarshal->GetMarshalSizeMax(MarshalAs(riid), pv, dwDestContext, pvDestContext, mshlflags, pSize); - if (FAILED(hr)) { + if (FAILED(hr) || !sentinel.IsOutermost()) { return hr; } DWORD payloadSize = 0; hr = mEventSink->GetHandlerPayloadSize(WrapNotNull(this), WrapNotNull(&payloadSize)); if (hr == E_NOTIMPL) { return S_OK; @@ -259,16 +367,18 @@ Interceptor::GetMarshalSizeMax(REFIID ri return hr; } HRESULT Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv, DWORD dwDestContext, void* pvDestContext, DWORD mshlflags) { + detail::ReentrySentinel sentinel(this); + HRESULT hr; #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) // Save the current stream position LARGE_INTEGER seekTo; seekTo.QuadPart = 0; ULARGE_INTEGER objrefPos; @@ -277,17 +387,17 @@ Interceptor::MarshalInterface(IStream* p if (FAILED(hr)) { return hr; } #endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) hr = mStdMarshal->MarshalInterface(pStm, MarshalAs(riid), pv, dwDestContext, pvDestContext, mshlflags); - if (FAILED(hr)) { + if (FAILED(hr) || !sentinel.IsOutermost()) { return hr; } #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) if (XRE_IsContentProcess() && IsCallerExternalProcess()) { // The caller isn't our chrome process, so do not provide a handler. // First, save the current position that marks the current end of the @@ -483,89 +593,28 @@ Interceptor::GetInitialInterceptorForIID return hr; } hr = GetInterceptorForIID(aTargetIid, aOutInterceptor); ENSURE_HR_SUCCEEDED(hr); return hr; } -class MOZ_RAII LoggedQIResult final -{ -public: - explicit LoggedQIResult(REFIID aIid) - : mIid(aIid) - , mHr(E_UNEXPECTED) - , mTarget(nullptr) - , mInterceptor(nullptr) - , mBegin(TimeStamp::Now()) - { - } - - ~LoggedQIResult() - { - if (!mTarget) { - return; - } - - TimeStamp end(TimeStamp::Now()); - TimeDuration total(end - mBegin); - TimeDuration overhead(total - mNonOverheadDuration); - - InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead, - &mNonOverheadDuration); - } - - void Log(IUnknown* aTarget, IUnknown* aInterceptor) - { - mTarget = aTarget; - mInterceptor = aInterceptor; - } - - void operator=(HRESULT aHr) - { - mHr = aHr; - } - - operator HRESULT() - { - return mHr; - } - - operator TimeDuration*() - { - return &mNonOverheadDuration; - } - - LoggedQIResult(const LoggedQIResult&) = delete; - LoggedQIResult(LoggedQIResult&&) = delete; - LoggedQIResult& operator=(const LoggedQIResult&) = delete; - LoggedQIResult& operator=(LoggedQIResult&&) = delete; - -private: - REFIID mIid; - HRESULT mHr; - IUnknown* mTarget; - IUnknown* mInterceptor; - TimeDuration mNonOverheadDuration; - TimeStamp mBegin; -}; - /** * This method contains the core guts of the handling of QueryInterface calls * that are delegated to us from the ICallInterceptor. * * @param aIid ID of the desired interface * @param aOutInterceptor The resulting emulated vtable that corresponds to * the interface specified by aIid. */ HRESULT Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) { - LoggedQIResult result(aIid); + detail::LoggedQIResult result(aIid); if (!aOutInterceptor) { return E_INVALIDARG; } if (aIid == IID_IUnknown) { // Special case: When we see IUnknown, we just provide a reference to this RefPtr<IInterceptor> intcpt(this); @@ -691,29 +740,35 @@ Interceptor::QueryInterfaceTarget(REFIID *aOutDuration = invoker.GetDuration(); } return hr; } HRESULT Interceptor::QueryInterface(REFIID riid, void** ppv) { + if (riid == IID_INoMarshal) { + // This entire library is designed around marshaling, so there's no point + // propagating this QI request all over the place! + return E_NOINTERFACE; + } + return WeakReferenceSupport::QueryInterface(riid, ppv); } HRESULT Interceptor::ThreadSafeQueryInterface(REFIID aIid, IUnknown** aOutInterface) { - if (aIid == IID_INoMarshal) { - // This entire library is designed around marshaling, so there's no point - // propagating this QI request all over the place! - return E_NOINTERFACE; - } + if (aIid == IID_IStdMarshalInfo) { + detail::ReentrySentinel sentinel(this); - if (aIid == IID_IStdMarshalInfo) { + if (!sentinel.IsOutermost()) { + return E_NOINTERFACE; + } + // Do not indicate that this interface is available unless we actually // support it. We'll check that by looking for a successful call to // IInterceptorSink::GetHandler() CLSID dummy; if (FAILED(mEventSink->GetHandler(WrapNotNull(&dummy)))) { return E_NOINTERFACE; }
--- a/js/src/jit/IonOptimizationLevels.cpp +++ b/js/src/jit/IonOptimizationLevels.cpp @@ -13,16 +13,19 @@ using namespace js; using namespace js::jit; namespace js { namespace jit { OptimizationLevelInfo IonOptimizations; +const uint32_t OptimizationInfo::CompilerWarmupThreshold = 1000; +const uint32_t OptimizationInfo::CompilerSmallFunctionWarmupThreshold = CompilerWarmupThreshold; + void OptimizationInfo::initNormalOptimizationInfo() { level_ = OptimizationLevel::Normal; autoTruncate_ = true; eaa_ = true; eagerSimdUnbox_ = true; @@ -78,24 +81,22 @@ OptimizationInfo::initWasmOptimizationIn uint32_t OptimizationInfo::compilerWarmUpThreshold(JSScript* script, jsbytecode* pc) const { MOZ_ASSERT(pc == nullptr || pc == script->code() || JSOp(*pc) == JSOP_LOOPENTRY); if (pc == script->code()) pc = nullptr; - uint32_t warmUpThreshold = compilerWarmUpThreshold_; - if (JitOptions.forcedDefaultIonWarmUpThreshold.isSome()) - warmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold.ref(); + uint32_t warmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold + .valueOr(compilerWarmUpThreshold_); if (JitOptions.isSmallFunction(script)) { - warmUpThreshold = compilerSmallFunctionWarmUpThreshold_; - if (JitOptions.forcedDefaultIonSmallFunctionWarmUpThreshold.isSome()) - warmUpThreshold = JitOptions.forcedDefaultIonSmallFunctionWarmUpThreshold.ref(); + warmUpThreshold = JitOptions.forcedDefaultIonSmallFunctionWarmUpThreshold + .valueOr(compilerSmallFunctionWarmUpThreshold_); } // If the script is too large to compile on the active thread, we can still // compile it off thread. In these cases, increase the warm-up counter // threshold to improve the compilation's type information and hopefully // avoid later recompilation. if (script->length() > MAX_ACTIVE_THREAD_SCRIPT_SIZE)
--- a/js/src/jit/IonOptimizationLevels.h +++ b/js/src/jit/IonOptimizationLevels.h @@ -127,24 +127,24 @@ class OptimizationInfo // Actually it is only needed to make sure we don't blow out the stack. uint32_t smallFunctionMaxInlineDepth_; // How many invocations or loop iterations are needed before functions // are compiled. uint32_t compilerWarmUpThreshold_; // Default compiler warmup threshold, unless it is overridden. - static const uint32_t CompilerWarmupThreshold = 1000; + static const uint32_t CompilerWarmupThreshold; // How many invocations or loop iterations are needed before small functions // are compiled. uint32_t compilerSmallFunctionWarmUpThreshold_; // Default small function compiler warmup threshold, unless it is overridden. - static const uint32_t CompilerSmallFunctionWarmupThreshold = CompilerWarmupThreshold; + static const uint32_t CompilerSmallFunctionWarmupThreshold; // How many invocations or loop iterations are needed before calls // are inlined, as a fraction of compilerWarmUpThreshold. double inliningWarmUpThresholdFactor_; // How many invocations or loop iterations are needed before a function // is hot enough to recompile the outerScript to inline that function, // as a multiplication of inliningWarmUpThreshold. @@ -222,19 +222,18 @@ class OptimizationInfo return eliminateRedundantChecks_; } bool flowAliasAnalysisEnabled() const { return !JitOptions.disableFlowAA; } IonRegisterAllocator registerAllocator() const { - if (JitOptions.forcedRegisterAllocator.isSome()) - return JitOptions.forcedRegisterAllocator.ref(); - return registerAllocator_; + return JitOptions.forcedRegisterAllocator + .valueOr(registerAllocator_); } bool scalarReplacementEnabled() const { return scalarReplacement_ && !JitOptions.disableScalarReplacement; } uint32_t smallFunctionMaxInlineDepth() const { return smallFunctionMaxInlineDepth_; @@ -260,19 +259,18 @@ class OptimizationInfo return inlineMaxTotalBytecodeLength_; } uint32_t inliningMaxCallerBytecodeLength() const { return inliningMaxCallerBytecodeLength_; } uint32_t inliningWarmUpThreshold() const { - uint32_t compilerWarmUpThreshold = compilerWarmUpThreshold_; - if (JitOptions.forcedDefaultIonWarmUpThreshold.isSome()) - compilerWarmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold.ref(); + uint32_t compilerWarmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold + .valueOr(compilerWarmUpThreshold_); return compilerWarmUpThreshold * inliningWarmUpThresholdFactor_; } uint32_t inliningRecompileThreshold() const { return inliningWarmUpThreshold() * inliningRecompileThresholdFactor_; } };
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -7277,19 +7277,18 @@ JS_GetGlobalJitCompilerOption(JSContext* MOZ_ASSERT(valueOut); #ifndef JS_CODEGEN_NONE JSRuntime* rt = cx->runtime(); switch (opt) { case JSJITCOMPILER_BASELINE_WARMUP_TRIGGER: *valueOut = jit::JitOptions.baselineWarmUpThreshold; break; case JSJITCOMPILER_ION_WARMUP_TRIGGER: - *valueOut = jit::JitOptions.forcedDefaultIonWarmUpThreshold.isSome() - ? jit::JitOptions.forcedDefaultIonWarmUpThreshold.ref() - : jit::OptimizationInfo::CompilerWarmupThreshold; + *valueOut = jit::JitOptions.forcedDefaultIonWarmUpThreshold + .valueOr(jit::OptimizationInfo::CompilerWarmupThreshold); break; case JSJITCOMPILER_ION_FORCE_IC: *valueOut = jit::JitOptions.forceInlineCaches; break; case JSJITCOMPILER_ION_CHECK_RANGE_ANALYSIS: *valueOut = jit::JitOptions.checkRangeAnalysis; break; case JSJITCOMPILER_ION_ENABLE:
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -470,16 +470,30 @@ MayHaveAnimationOfProperty(EffectSet* ef if (aProperty == eCSSProperty_opacity && !effects->MayHaveOpacityAnimation()) { return false; } return true; } +static bool +MayHaveAnimationOfProperty(const nsIFrame* aFrame, nsCSSPropertyID aProperty) +{ + switch (aProperty) { + case eCSSProperty_transform: + return aFrame->MayHaveTransformAnimation(); + case eCSSProperty_opacity: + return aFrame->MayHaveOpacityAnimation(); + default: + MOZ_ASSERT_UNREACHABLE("unexpected property"); + return false; + } +} + bool nsLayoutUtils::HasAnimationOfProperty(EffectSet* aEffectSet, nsCSSPropertyID aProperty) { if (!aEffectSet || !MayHaveAnimationOfProperty(aEffectSet, aProperty)) { return false; } @@ -491,17 +505,28 @@ nsLayoutUtils::HasAnimationOfProperty(Ef } ); } bool nsLayoutUtils::HasAnimationOfProperty(const nsIFrame* aFrame, nsCSSPropertyID aProperty) { - return HasAnimationOfProperty(EffectSet::GetEffectSet(aFrame), aProperty); + if (!MayHaveAnimationOfProperty(aFrame, aProperty)) { + return false; + } + + return HasMatchingAnimations(aFrame, + [&aProperty](KeyframeEffectReadOnly& aEffect) + { + return (aEffect.IsInEffect() || aEffect.IsCurrent()) && + aEffect.HasAnimationOfProperty(aProperty); + } + ); + } bool nsLayoutUtils::HasEffectiveAnimation(const nsIFrame* aFrame, nsCSSPropertyID aProperty) { EffectSet* effects = EffectSet::GetEffectSet(aFrame); if (!effects || !MayHaveAnimationOfProperty(effects, aProperty)) {
--- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -664,16 +664,27 @@ nsFrame::Init(nsIContent* aContent NS_FRAME_IN_POPUP | NS_FRAME_IS_NONDISPLAY)); if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) { // Assume all frames in popups are visible. IncApproximateVisibleCount(); } } + if (aPrevInFlow) { + mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation(); + mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation(); + } else if (mContent) { + EffectSet* effectSet = EffectSet::GetEffectSet(this); + if (effectSet) { + mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation(); + mMayHaveTransformAnimation = effectSet->MayHaveTransformAnimation(); + } + } + const nsStyleDisplay *disp = StyleDisplay(); if (disp->HasTransform(this) || (IsFrameOfType(eSupportsCSSTransforms) && nsLayoutUtils::HasAnimationOfProperty(this, eCSSProperty_transform))) { // The frame gets reconstructed if we toggle the -moz-transform // property, so we can set this bit here and then ignore it. AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); } @@ -1498,41 +1509,37 @@ nsIFrame::GetMarginRectRelativeToSelf() nsMargin m = GetUsedMargin(); m.ApplySkipSides(GetSkipSides()); nsRect r(0, 0, mRect.width, mRect.height); r.Inflate(m); return r; } bool -nsIFrame::IsTransformed(const nsStyleDisplay* aStyleDisplay, - EffectSet* aEffectSet) const -{ - return IsCSSTransformed(aStyleDisplay, aEffectSet) || +nsIFrame::IsTransformed(const nsStyleDisplay* aStyleDisplay) const +{ + return IsCSSTransformed(aStyleDisplay) || IsSVGTransformed(); } bool -nsIFrame::IsCSSTransformed(const nsStyleDisplay* aStyleDisplay, - EffectSet* aEffectSet) const +nsIFrame::IsCSSTransformed(const nsStyleDisplay* aStyleDisplay) const { MOZ_ASSERT(aStyleDisplay == StyleDisplay()); return ((mState & NS_FRAME_MAY_BE_TRANSFORMED) && (aStyleDisplay->HasTransform(this) || - HasAnimationOfTransform(aEffectSet))); + HasAnimationOfTransform())); } bool -nsIFrame::HasAnimationOfTransform(EffectSet* aEffectSet) const -{ - EffectSet* effects = - aEffectSet ? aEffectSet : EffectSet::GetEffectSet(this); +nsIFrame::HasAnimationOfTransform() const +{ return IsPrimaryFrame() && - nsLayoutUtils::HasAnimationOfProperty(effects, eCSSProperty_transform) && + nsLayoutUtils::HasAnimationOfProperty(this, eCSSProperty_transform) && IsFrameOfType(eSupportsCSSTransforms); } bool nsIFrame::HasOpacityInternal(float aThreshold, EffectSet* aEffectSet) const { MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument"); @@ -1580,43 +1587,42 @@ nsIFrame::Extend3DContext(const nsStyleD const nsStyleEffects* effects = StyleEffects(); return !nsFrame::ShouldApplyOverflowClipping(this, disp) && !GetClipPropClipRect(disp, effects, GetSize()) && !nsSVGIntegrationUtils::UsingEffectsForFrame(this); } bool -nsIFrame::Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay, - EffectSet* aEffectSet) const +nsIFrame::Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay) const { MOZ_ASSERT(aStyleDisplay == StyleDisplay()); nsIFrame* parent = GetFlattenedTreeParentPrimaryFrame(); if (!parent || !parent->Extend3DContext()) { return false; } - return IsCSSTransformed(aStyleDisplay, aEffectSet) || + return IsCSSTransformed(aStyleDisplay) || BackfaceIsHidden(aStyleDisplay); } bool -nsIFrame::In3DContextAndBackfaceIsHidden(EffectSet* aEffectSet) const +nsIFrame::In3DContextAndBackfaceIsHidden() const { // While both tests fail most of the time, test BackfaceIsHidden() // first since it's likely to fail faster. const nsStyleDisplay* disp = StyleDisplay(); return BackfaceIsHidden(disp) && - Combines3DTransformWithAncestors(disp, aEffectSet); + Combines3DTransformWithAncestors(disp); } bool -nsIFrame::HasPerspective(const nsStyleDisplay* aStyleDisplay, EffectSet* aEffectSet) const +nsIFrame::HasPerspective(const nsStyleDisplay* aStyleDisplay) const { MOZ_ASSERT(aStyleDisplay == StyleDisplay()); - if (!IsTransformed(aStyleDisplay, aEffectSet)) { + if (!IsTransformed(aStyleDisplay)) { return false; } nsIFrame* containingBlock = GetContainingBlock(SKIP_SCROLLED_FRAME, aStyleDisplay); if (!containingBlock) { return false; } return containingBlock->ChildrenHavePerspective(); } @@ -2746,18 +2752,18 @@ nsIFrame::BuildDisplayListForStackingCon // For preserves3d, use the dirty rect already installed on the // builder, since aDirtyRect maybe distorted for transforms along // the chain. nsRect visibleRect = aBuilder->GetVisibleRect(); nsRect dirtyRect = aBuilder->GetDirtyRect(); bool inTransform = aBuilder->IsInTransform(); - bool isTransformed = IsTransformed(disp, effectSet); - bool hasPerspective = HasPerspective(effectSet); + bool isTransformed = IsTransformed(disp); + bool hasPerspective = HasPerspective(disp); // reset blend mode so we can keep track if this stacking context needs have // a nsDisplayBlendContainer. Set the blend mode back when the routine exits // so we keep track if the parent stacking context needs a container too. AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder); aBuilder->SetContainsBlendMode(false); nsRect visibleRectOutsideTransform = visibleRect; bool allowAsyncAnimation = false; @@ -9356,17 +9362,17 @@ nsIFrame::FinishAndStoreOverflow(nsOverf nsSize aNewSize, nsSize* aOldSize, const nsStyleDisplay* aStyleDisplay) { MOZ_ASSERT(FrameMaintainsOverflow(), "Don't call - overflow rects not maintained on these SVG frames"); const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay); EffectSet* effectSet = EffectSet::GetEffectSet(this); - bool hasTransform = IsTransformed(disp, effectSet); + bool hasTransform = IsTransformed(disp); nsRect bounds(nsPoint(0, 0), aNewSize); // Store the passed in overflow area if we are a preserve-3d frame or we have // a transform, and it's not just the frame bounds. if (hasTransform || Combines3DTransformWithAncestors(disp)) { if (!aOverflowAreas.VisualOverflow().IsEqualEdges(bounds) || !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) { nsOverflowAreas* initial = @@ -9474,17 +9480,17 @@ nsIFrame::FinishAndStoreOverflow(nsOverf nsRect& o = aOverflowAreas.Overflow(otype); o.IntersectRect(o, *clipPropClipRect); } } /* If we're transformed, transform the overflow rect by the current transformation. */ if (ChildrenHavePerspective(disp) && sizeChanged) { nsRect newBounds(nsPoint(0, 0), aNewSize); - RecomputePerspectiveChildrenOverflow(this, effectSet); + RecomputePerspectiveChildrenOverflow(this); } if (hasTransform) { SetProperty(nsIFrame::PreTransformOverflowAreasProperty(), new nsOverflowAreas(aOverflowAreas)); if (Combines3DTransformWithAncestors(disp)) { /* If we're a preserve-3d leaf frame, then our pre-transform overflow should be correct. Our @@ -9524,28 +9530,27 @@ nsIFrame::FinishAndStoreOverflow(nsOverf if (anyOverflowChanged) { SVGObserverUtils::InvalidateDirectRenderingObservers(this); } return anyOverflowChanged; } void -nsIFrame::RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame, - EffectSet* aEffectSet) +nsIFrame::RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame) { nsIFrame::ChildListIterator lists(this); for (; !lists.IsDone(); lists.Next()) { nsFrameList::Enumerator childFrames(lists.CurrentList()); for (; !childFrames.AtEnd(); childFrames.Next()) { nsIFrame* child = childFrames.get(); if (!child->FrameMaintainsOverflow()) { continue; // frame does not maintain overflow rects } - if (child->HasPerspective(aEffectSet)) { + if (child->HasPerspective()) { nsOverflowAreas* overflow = child->GetProperty(nsIFrame::InitialOverflowProperty()); nsRect bounds(nsPoint(0, 0), child->GetSize()); if (overflow) { nsOverflowAreas overflowCopy = *overflow; child->FinishAndStoreOverflow(overflowCopy, bounds.Size()); } else { nsOverflowAreas boundsOverflow; @@ -9553,17 +9558,17 @@ nsIFrame::RecomputePerspectiveChildrenOv child->FinishAndStoreOverflow(boundsOverflow, bounds.Size()); } } else if (child->GetContainingBlock(SKIP_SCROLLED_FRAME) == aStartFrame) { // If a frame is using perspective, then the size used to compute // perspective-origin is the size of the frame belonging to its parent // style context. We must find any descendant frames using our size // (by recursing into frames that have the same containing block) // to update their overflow rects too. - child->RecomputePerspectiveChildrenOverflow(aStartFrame, aEffectSet); + child->RecomputePerspectiveChildrenOverflow(aStartFrame); } } } } void nsIFrame::ComputePreserve3DChildrenOverflow(nsOverflowAreas& aOverflowAreas) {
--- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -628,16 +628,18 @@ public: , mReflowRequestedForCharDataChange(false) , mForceDescendIntoIfVisible(false) , mBuiltDisplayList(false) , mFrameIsModified(false) , mHasOverrideDirtyRegion(false) , mMayHaveWillChangeBudget(false) , mBuiltBlendContainer(false) , mIsPrimaryFrame(false) + , mMayHaveTransformAnimation(false) + , mMayHaveOpacityAnimation(false) { mozilla::PodZero(&mOverflow); } nsPresContext* PresContext() const { return StyleContext()->PresContext(); } @@ -1758,39 +1760,33 @@ public: /** * Returns true if this frame is transformed (e.g. has CSS or SVG transforms) * or if its parent is an SVG frame that has children-only transforms (e.g. * an SVG viewBox attribute) or if its transform-style is preserve-3d or * the frame has transform animations. * * @param aStyleDisplay: If the caller has this->StyleDisplay(), providing * it here will improve performance. - * @param aEffectSet: This function may need to look up EffectSet property. - * If a caller already have one, pass it in can save property look up - * time; otherwise, just left it as nullptr. - */ - bool IsTransformed(const nsStyleDisplay* aStyleDisplay, mozilla::EffectSet* aEffectSet = nullptr) const; - bool IsTransformed(mozilla::EffectSet* aEffectSet = nullptr) const { - return IsTransformed(StyleDisplay(), aEffectSet); + */ + bool IsTransformed(const nsStyleDisplay* aStyleDisplay) const; + bool IsTransformed() const { + return IsTransformed(StyleDisplay()); } /** * Same as IsTransformed, except that it doesn't take SVG transforms * into account. */ - bool IsCSSTransformed(const nsStyleDisplay* aStyleDisplay, mozilla::EffectSet* aEffectSet = nullptr) const; + bool IsCSSTransformed(const nsStyleDisplay* aStyleDisplay) const; /** * True if this frame has any animation of transform in effect. * - * @param aEffectSet: This function may need to look up EffectSet property. - * If a caller already have one, pass it in can save property look up - * time; otherwise, just left it as nullptr. - */ - bool HasAnimationOfTransform(mozilla::EffectSet* aEffectSet = nullptr) const; + */ + bool HasAnimationOfTransform() const; /** * Returns true if the frame is translucent or the frame has opacity * animations for the purposes of creating a stacking context. * * @param aEffectSet: This function may need to look up EffectSet property. * If a caller already have one, pass it in can save property look up * time; otherwise, just left it as nullptr. @@ -1851,68 +1847,59 @@ public: /** * Returns whether this frame has a parent that Extend3DContext() and has * its own transform (or hidden backface) to be combined with the parent's * transform. * * @param aStyleDisplay: If the caller has this->StyleDisplay(), providing * it here will improve performance. - * @param aEffectSet: This function may need to look up EffectSet property. - * If a caller already have one, pass it in can save property look up - * time; otherwise, just left it as nullptr. - */ - bool Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay, - mozilla::EffectSet* aEffectSet = nullptr) const; - bool Combines3DTransformWithAncestors(mozilla::EffectSet* aEffectSet = nullptr) const { - return Combines3DTransformWithAncestors(StyleDisplay(), aEffectSet); + */ + bool Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay) const; + bool Combines3DTransformWithAncestors() const { + return Combines3DTransformWithAncestors(StyleDisplay()); } /** * Returns whether this frame has a hidden backface and has a parent that * Extend3DContext(). This is useful because in some cases the hidden * backface can safely be ignored if it could not be visible anyway. * - * @param aEffectSet: This function may need to look up EffectSet property. - * If a caller already have one, pass it in can save property look up - * time; otherwise, just left it as nullptr. - */ - bool In3DContextAndBackfaceIsHidden(mozilla::EffectSet* aEffectSet = nullptr) const; + */ + bool In3DContextAndBackfaceIsHidden() const; bool IsPreserve3DLeaf(const nsStyleDisplay* aStyleDisplay, mozilla::EffectSet* aEffectSet = nullptr) const { return Combines3DTransformWithAncestors(aStyleDisplay) && !Extend3DContext(aStyleDisplay, aEffectSet); } bool IsPreserve3DLeaf(mozilla::EffectSet* aEffectSet = nullptr) const { return IsPreserve3DLeaf(StyleDisplay(), aEffectSet); } - bool HasPerspective(const nsStyleDisplay* aStyleDisplay, - mozilla::EffectSet* aEffectSet = nullptr) const; - bool HasPerspective(mozilla::EffectSet* aEffectSet = nullptr) const { - return HasPerspective(StyleDisplay(), aEffectSet); + bool HasPerspective(const nsStyleDisplay* aStyleDisplay) const; + bool HasPerspective() const { + return HasPerspective(StyleDisplay()); } bool ChildrenHavePerspective(const nsStyleDisplay* aStyleDisplay) const { MOZ_ASSERT(aStyleDisplay == StyleDisplay()); return aStyleDisplay->HasPerspectiveStyle(); } bool ChildrenHavePerspective() const { return ChildrenHavePerspective(StyleDisplay()); } /** * Includes the overflow area of all descendants that participate in the current * 3d context into aOverflowAreas. */ void ComputePreserve3DChildrenOverflow(nsOverflowAreas& aOverflowAreas); - void RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame, - mozilla::EffectSet* aEffectSet = nullptr); + void RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame); /** * Returns the number of ancestors between this and the root of our frame tree */ uint32_t GetDepthInFrameTree() const; /** * Event handling of GUI events. @@ -4093,16 +4080,29 @@ public: */ bool IsWrapperAnonBoxNeedingRestyle() const { return mIsWrapperBoxNeedingRestyle; } void SetIsWrapperAnonBoxNeedingRestyle(bool aNeedsRestyle) { mIsWrapperBoxNeedingRestyle = aNeedsRestyle; } + bool MayHaveTransformAnimation() const { + return mMayHaveTransformAnimation; + } + void SetMayHaveTransformAnimation() { + mMayHaveTransformAnimation = true; + } + bool MayHaveOpacityAnimation() const { + return mMayHaveOpacityAnimation; + } + void SetMayHaveOpacityAnimation() { + mMayHaveOpacityAnimation = true; + } + /** * If this returns true, the frame it's called on should get the * NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly * if it's already in reflow, or via calling FrameNeedsReflow() to schedule a * reflow. */ virtual bool RenumberFrameAndDescendants(int32_t* aOrdinal, int32_t aDepth, @@ -4340,19 +4340,22 @@ protected: bool mBuiltBlendContainer : 1; private: /** * True if this is the primary frame for mContent. */ bool mIsPrimaryFrame : 1; + bool mMayHaveTransformAnimation : 1; + bool mMayHaveOpacityAnimation : 1; + protected: - // There is a 3-bit gap left here. + // There is a 1-bit gap left here. // Helpers /** * Can we stop inside this frame when we're skipping non-rendered whitespace? * @param aForward [in] Are we moving forward (or backward) in content order. * @param aOffset [in/out] At what offset into the frame to start looking. * on output - what offset was reached (whether or not we found a place to stop). * @return STOP: An appropriate offset was found within this frame,
--- a/media/audioipc/server/src/lib.rs +++ b/media/audioipc/server/src/lib.rs @@ -688,16 +688,17 @@ impl Server { Ok(()) } pub fn poll(&mut self, poll: &mut mio::Poll) -> Result<()> { let mut events = mio::Events::with_capacity(16); match poll.poll(&mut events, None) { Ok(_) => {}, + Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => return Ok(()), Err(e) => warn!("server poll error: {}", e), } for event in events.iter() { match event.token() { SERVER => { if let Err(e) = self.accept(poll) { warn!("server accept error: {}", e);
--- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -2,18 +2,16 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <ctype.h> #include <stdlib.h> #include <string.h> -#include <string> -#include <vector> #include "base/basictypes.h" #include "GeckoProfiler.h" #include "MainThreadUtils.h" #include "mozilla/ArenaAllocatorExtensions.h" #include "mozilla/ArenaAllocator.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" @@ -29,16 +27,17 @@ #include "mozilla/ScopeExit.h" #include "mozilla/Services.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/SyncRunnable.h" #include "mozilla/Telemetry.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/URLPreloader.h" #include "mozilla/Variant.h" +#include "mozilla/Vector.h" #include "nsAppDirectoryServiceDefs.h" #include "nsAutoPtr.h" #include "nsCategoryManagerUtils.h" #include "nsClassHashtable.h" #include "nsCOMArray.h" #include "nsCOMPtr.h" #include "nsCRT.h" #include "nsDataHashtable.h" @@ -100,40 +99,23 @@ using namespace mozilla; "ENSURE_MAIN_PROCESS: called %s on %s in a non-main process", \ func, \ pref); \ NS_ERROR(msg.get()); \ return NS_ERROR_NOT_AVAILABLE; \ } \ } while (0) -#define ENSURE_MAIN_PROCESS_WITH_WARNING(func, pref) \ - do { \ - if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \ - nsPrintfCString msg( \ - "ENSURE_MAIN_PROCESS: called %s on %s in a non-main process", \ - func, \ - pref); \ - NS_WARNING(msg.get()); \ - return NS_ERROR_NOT_AVAILABLE; \ - } \ - } while (0) - #else // DEBUG #define ENSURE_MAIN_PROCESS(func, pref) \ if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \ return NS_ERROR_NOT_AVAILABLE; \ } -#define ENSURE_MAIN_PROCESS_WITH_WARNING(func, pref) \ - if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \ - return NS_ERROR_NOT_AVAILABLE; \ - } - #endif // DEBUG //=========================================================================== // The old low-level prefs API //=========================================================================== struct PrefHashEntry; @@ -753,32 +735,34 @@ static nsresult PREF_ClearAllUserPrefs() { MOZ_ASSERT(NS_IsMainThread()); if (!gHashTable) { return NS_ERROR_NOT_INITIALIZED; } - std::vector<std::string> prefStrings; + Vector<const char*> prefNames; for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) { auto pref = static_cast<PrefHashEntry*>(iter.Get()); if (pref->mPrefFlags.HasUserValue()) { - prefStrings.push_back(std::string(pref->mKey)); + if (!prefNames.append(pref->mKey)) { + return NS_ERROR_OUT_OF_MEMORY; + } pref->mPrefFlags.SetHasUserValue(false); if (!pref->mPrefFlags.HasDefault()) { iter.Remove(); } } } - for (std::string& prefString : prefStrings) { - pref_DoCallback(prefString.c_str()); + for (const char* prefName : prefNames) { + pref_DoCallback(prefName); } Preferences::HandleDirty(); return NS_OK; } // Function that sets whether or not the preference is locked and therefore // cannot be changed. @@ -901,24 +885,24 @@ public: #define WATCHING_PREF_RAII() #endif // DEBUG static PrefHashEntry* pref_HashTableLookup(const char* aKey) { MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal()); - MOZ_ASSERT((!XRE_IsContentProcess() || gPhase != START), + MOZ_ASSERT((XRE_IsParentProcess() || gPhase != START), "pref access before commandline prefs set"); // If you're hitting this assertion, you've added a pref access to start up. // Consider moving it later or add it to the whitelist in ContentPrefs.cpp - // and get review from a DOM peer + // and get review from a DOM peer. #ifdef DEBUG - if (XRE_IsContentProcess() && gPhase <= END_INIT_PREFS && !gWatchingPref && + if (!XRE_IsParentProcess() && gPhase <= END_INIT_PREFS && !gWatchingPref && !InInitArray(aKey)) { MOZ_CRASH_UNSAFE_PRINTF( "accessing non-init pref %s before the rest of the prefs are sent", aKey); } #endif return static_cast<PrefHashEntry*>(gHashTable->Search(aKey)); } @@ -2379,17 +2363,17 @@ nsPrefBranch::GetComplexValue(const char const nsIID& aType, void** aRetVal) { NS_ENSURE_ARG(aPrefName); nsresult rv; nsAutoCString utf8String; - // we have to do this one first because it's different than all the rest + // We have to do this one first because it's different to all the rest. if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) { nsCOMPtr<nsIPrefLocalizedString> theString( do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv)); if (NS_FAILED(rv)) { return rv; } const PrefName& pref = GetPrefName(aPrefName); @@ -2428,38 +2412,32 @@ nsPrefBranch::GetComplexValue(const char // if we can't get the pref, there's no point in being here rv = GetCharPref(aPrefName, utf8String); if (NS_FAILED(rv)) { return rv; } if (aType.Equals(NS_GET_IID(nsIFile))) { - if (XRE_IsContentProcess()) { - NS_ERROR("cannot get nsIFile pref from content process"); - return NS_ERROR_NOT_AVAILABLE; - } + ENSURE_MAIN_PROCESS("GetComplexValue(nsIFile)", aPrefName); nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { rv = file->SetPersistentDescriptor(utf8String); if (NS_SUCCEEDED(rv)) { file.forget(reinterpret_cast<nsIFile**>(aRetVal)); return NS_OK; } } return rv; } if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) { - if (XRE_IsContentProcess()) { - NS_ERROR("cannot get nsIRelativeFilePref from content process"); - return NS_ERROR_NOT_AVAILABLE; - } + ENSURE_MAIN_PROCESS("GetComplexValue(nsIRelativeFilePref)", aPrefName); nsACString::const_iterator keyBegin, strEnd; utf8String.BeginReading(keyBegin); utf8String.EndReading(strEnd); // The pref has the format: [fromKey]a/b/c if (*keyBegin++ != '[') { return NS_ERROR_FAILURE; @@ -3094,19 +3072,19 @@ namespace mozilla { #define INITIAL_PREF_FILES 10 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); void Preferences::HandleDirty() { if (!XRE_IsParentProcess()) { - // TODO: this should really assert because you can't set prefs in a - // content process. But so much code currently does this that we just - // ignore it for now. + // This path is hit a lot when setting up prefs for content processes. Just + // ignore it in that case, because content processes aren't responsible for + // saving prefs. return; } if (!gHashTable || !sPreferences) { return; } if (sPreferences->mProfileShutdown) { @@ -3161,16 +3139,17 @@ static const char kPrefFileHeader[] = " * To make a manual change to preferences, you can visit the URL " "about:config" NS_LINEBREAK " */" NS_LINEBREAK NS_LINEBREAK; // clang-format on +// Note: if sShutdown is true, sPreferences will be nullptr. StaticRefPtr<Preferences> Preferences::sPreferences; bool Preferences::sShutdown = false; // This globally enables or disables OMT pref writing, both sync and async. static int32_t sAllowOMTPrefWrite = -1; // Write the preference data to a file. class PreferencesWriter final @@ -3562,17 +3541,17 @@ Preferences::GetInstanceForService() Result<Ok, const char*> res = pref_InitInitialObjects(); if (res.isErr()) { sPreferences = nullptr; gCacheDataDesc = res.unwrapErr(); return nullptr; } - if (XRE_IsContentProcess()) { + if (!XRE_IsParentProcess()) { MOZ_ASSERT(gInitPrefs); for (unsigned int i = 0; i < gInitPrefs->Length(); i++) { Preferences::SetPreference(gInitPrefs->ElementAt(i)); } delete gInitPrefs; gInitPrefs = nullptr; } else { @@ -3631,17 +3610,21 @@ Preferences::IsServiceAvailable() return !!sPreferences; } /* static */ bool Preferences::InitStaticMembers() { MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal()); - if (!sShutdown && !sPreferences) { + if (MOZ_LIKELY(sPreferences)) { + return true; + } + + if (!sShutdown) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID); } return sPreferences != nullptr; } @@ -3781,52 +3764,43 @@ Preferences::Observe(nsISupports* aSubje } return rv; } NS_IMETHODIMP Preferences::ReadUserPrefsFromFile(nsIFile* aFile) { - if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { - NS_ERROR("must load prefs from parent process"); - return NS_ERROR_NOT_AVAILABLE; - } + ENSURE_MAIN_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs"); if (!aFile) { NS_ERROR("ReadUserPrefsFromFile requires a parameter"); return NS_ERROR_INVALID_ARG; } return openPrefFile(aFile); } NS_IMETHODIMP Preferences::ResetPrefs() { - if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { - NS_ERROR("must reset prefs from parent process"); - return NS_ERROR_NOT_AVAILABLE; - } + ENSURE_MAIN_PROCESS("Preferences::ResetPrefs", "all prefs"); NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID); gHashTable->ClearAndPrepareForLength(PREF_HASHTABLE_INITIAL_LENGTH); gPrefNameArena.Clear(); return pref_InitInitialObjects().isOk() ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP Preferences::ResetUserPrefs() { - if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { - NS_ERROR("must reset user prefs from parent process"); - return NS_ERROR_NOT_AVAILABLE; - } + ENSURE_MAIN_PROCESS("Preferences::ResetUserPrefs", "all prefs"); PREF_ClearAllUserPrefs(); return NS_OK; } bool Preferences::AllowOffMainThreadSave() { @@ -4076,20 +4050,17 @@ Preferences::MakeBackupPrefFile(nsIFile* NS_ENSURE_SUCCESS(rv, rv); return rv; } nsresult Preferences::SavePrefFileInternal(nsIFile* aFile, SaveMethod aSaveMethod) { - if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { - NS_ERROR("must save pref file from parent process"); - return NS_ERROR_NOT_AVAILABLE; - } + ENSURE_MAIN_PROCESS("Preferences::SavePrefFileInternal", "all prefs"); // We allow different behavior here when aFile argument is not null, but it // happens to be the same as the current file. It is not clear that we // should, but it does give us a "force" save on the unmodified pref file // (see the original bug 160377 when we added this.) if (nullptr == aFile) { mSavePending = false; @@ -4649,57 +4620,57 @@ Preferences::GetComplex(const char* aPre { NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return sPreferences->mRootBranch->GetComplexValue(aPref, aType, aResult); } /* static */ nsresult Preferences::SetCString(const char* aPref, const char* aValue) { - ENSURE_MAIN_PROCESS_WITH_WARNING("SetCString", aPref); + ENSURE_MAIN_PROCESS("SetCString", aPref); NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return PREF_SetCStringPref(aPref, nsDependentCString(aValue), false); } /* static */ nsresult Preferences::SetCString(const char* aPref, const nsACString& aValue) { - ENSURE_MAIN_PROCESS_WITH_WARNING("SetCString", aPref); + ENSURE_MAIN_PROCESS("SetCString", aPref); NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return PREF_SetCStringPref(aPref, aValue, false); } /* static */ nsresult Preferences::SetString(const char* aPref, const char16ptr_t aValue) { - ENSURE_MAIN_PROCESS_WITH_WARNING("SetString", aPref); + ENSURE_MAIN_PROCESS("SetString", aPref); NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return PREF_SetCStringPref(aPref, NS_ConvertUTF16toUTF8(aValue), false); } /* static */ nsresult Preferences::SetString(const char* aPref, const nsAString& aValue) { - ENSURE_MAIN_PROCESS_WITH_WARNING("SetString", aPref); + ENSURE_MAIN_PROCESS("SetString", aPref); NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return PREF_SetCStringPref(aPref, NS_ConvertUTF16toUTF8(aValue), false); } /* static */ nsresult Preferences::SetBool(const char* aPref, bool aValue) { - ENSURE_MAIN_PROCESS_WITH_WARNING("SetBool", aPref); + ENSURE_MAIN_PROCESS("SetBool", aPref); NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return PREF_SetBoolPref(aPref, aValue, false); } /* static */ nsresult Preferences::SetInt(const char* aPref, int32_t aValue) { - ENSURE_MAIN_PROCESS_WITH_WARNING("SetInt", aPref); + ENSURE_MAIN_PROCESS("SetInt", aPref); NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return PREF_SetIntPref(aPref, aValue, false); } /* static */ nsresult Preferences::SetFloat(const char* aPref, float aValue) { return SetCString(aPref, nsPrintfCString("%f", aValue).get()); @@ -4712,17 +4683,17 @@ Preferences::SetComplex(const char* aPre { NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return sPreferences->mRootBranch->SetComplexValue(aPref, aType, aValue); } /* static */ nsresult Preferences::ClearUser(const char* aPref) { - ENSURE_MAIN_PROCESS_WITH_WARNING("ClearUser", aPref); + ENSURE_MAIN_PROCESS("ClearUser", aPref); NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return PREF_ClearUserPref(aPref); } /* static */ bool Preferences::HasUserValue(const char* aPref) { NS_ENSURE_TRUE(InitStaticMembers(), false); @@ -4754,17 +4725,18 @@ Preferences::AddWeakObserver(nsIObserver NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); return sPreferences->mRootBranch->AddObserver(aPref, aObserver, true); } /* static */ nsresult Preferences::RemoveObserver(nsIObserver* aObserver, const char* aPref) { MOZ_ASSERT(aObserver); - if (!sPreferences && sShutdown) { + if (sShutdown) { + MOZ_ASSERT(!sPreferences); return NS_OK; // Observers have been released automatically. } NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); return sPreferences->mRootBranch->RemoveObserver(aPref, aObserver); } /* static */ nsresult Preferences::AddStrongObservers(nsIObserver* aObserver, const char** aPrefs) @@ -4787,17 +4759,18 @@ Preferences::AddWeakObservers(nsIObserve } return NS_OK; } /* static */ nsresult Preferences::RemoveObservers(nsIObserver* aObserver, const char** aPrefs) { MOZ_ASSERT(aObserver); - if (!sPreferences && sShutdown) { + if (sShutdown) { + MOZ_ASSERT(!sPreferences); return NS_OK; // Observers have been released automatically. } NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); for (uint32_t i = 0; aPrefs[i]; i++) { nsresult rv = RemoveObserver(aObserver, aPrefs[i]); NS_ENSURE_SUCCESS(rv, rv); } @@ -4850,17 +4823,18 @@ Preferences::RegisterCallbackAndCall(Pre /* static */ nsresult Preferences::UnregisterCallback(PrefChangedFunc aCallback, const char* aPref, void* aClosure, MatchKind aMatchKind) { MOZ_ASSERT(aCallback); - if (!sPreferences && sShutdown) { + if (sShutdown) { + MOZ_ASSERT(!sPreferences); return NS_OK; // Observers have been released automatically. } NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); return PREF_UnregisterCallback(aPref, aCallback, aClosure, aMatchKind); } static void @@ -5092,17 +5066,16 @@ Preferences::GetDefaultType(const char* sPreferences->mDefaultRootBranch->GetPrefType(aPref, &result)) ? result : nsIPrefBranch::PREF_INVALID; } } // namespace mozilla #undef ENSURE_MAIN_PROCESS -#undef ENSURE_MAIN_PROCESS_WITH_WARNING //=========================================================================== // Module and factory stuff //=========================================================================== NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Preferences, Preferences::GetInstanceForService) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefLocalizedString, Init)
--- a/modules/libpref/Preferences.h +++ b/modules/libpref/Preferences.h @@ -284,23 +284,26 @@ public: template<MemoryOrdering Order> static nsresult AddAtomicUintVarCache(Atomic<uint32_t, Order>* aVariable, const char* aPref, uint32_t aDefault = 0); static nsresult AddFloatVarCache(float* aVariable, const char* aPref, float aDefault = 0.0f); - // Used to synchronise preferences between chrome and content processes. + // When a content process is created these methods are used to pass prefs in + // bulk from the parent process. static void GetPreferences(InfallibleTArray<PrefSetting>* aPrefs); + static void SetInitPreferences(nsTArray<PrefSetting>* aPrefs); + + // When a pref is changed in the parent process, these methods are used to + // pass the update to content processes. static void GetPreference(PrefSetting* aPref); static void SetPreference(const PrefSetting& aPref); - static void SetInitPreferences(nsTArray<PrefSetting>* aPrefs); - #ifdef DEBUG static void SetInitPhase(pref_initPhase phase); static pref_initPhase InitPhase(); #endif static int64_t SizeOfIncludingThisAndOtherStuff( mozilla::MallocSizeOf aMallocSizeOf);
--- a/netwerk/srtp/src/moz.build +++ b/netwerk/srtp/src/moz.build @@ -47,17 +47,17 @@ for var in ('HAVE_STDLIB_H', 'HAVE_UINT8 'HAVE_UINT32_T', 'HAVE_UINT64_T'): DEFINES[var] = 1 # XXX while arm is not a CISC architecture, the code guarded by CPU_RISC makes # (at least) the AES ciphers fail their self-tests on ARM, so for now we're # falling back to the (presumably) slower-on-this-architecture but working # code path. https://bugzilla.mozilla.org/show_bug.cgi?id=822380 has been filed # to make the right and more performant fix and push it back upstream. -if CONFIG['CPU_ARCH'] in ('arm', 'x86', 'x86_64'): +if CONFIG['CPU_ARCH'] in ('arm', 'x86', 'x86_64', 'mips', 'mips64'): DEFINES['CPU_CISC'] = 1 else: # best guess DEFINES['CPU_RISC'] = 1 if CONFIG['CPU_ARCH'] in ('x86', 'x86_64'): DEFINES['HAVE_X86'] = True
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MinidumpAnalyzerUtils_h +#define MinidumpAnalyzerUtils_h + +#ifdef XP_WIN +#include <windows.h> +#endif + +#include <vector> +#include <map> +#include <string> +#include <algorithm> + +namespace CrashReporter { + +struct MinidumpAnalyzerOptions { + bool fullMinidump; + std::string forceUseModule; +}; + +extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions; + +#ifdef XP_WIN + +#if !defined(_MSC_VER) +static inline std::string +WideToMBCP(const std::wstring& wide, unsigned int cp, bool* success = nullptr) +{ + char* buffer = nullptr; + int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), + -1, nullptr, 0, nullptr, nullptr); + if (buffer_size == 0) { + if (success) { + *success = false; + } + + return ""; + } + + buffer = new char[buffer_size]; + if (buffer == nullptr) { + if (success) { + *success = false; + } + + return ""; + } + + WideCharToMultiByte(cp, 0, wide.c_str(), + -1, buffer, buffer_size, nullptr, nullptr); + std::string mb = buffer; + delete [] buffer; + + if (success) { + *success = true; + } + + return mb; +} +#endif /* !defined(_MSC_VER) */ + +static inline std::wstring +UTF8ToWide(const std::string& aUtf8Str, bool *aSuccess = nullptr) +{ + wchar_t* buffer = nullptr; + int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), + -1, nullptr, 0); + if (buffer_size == 0) { + if (aSuccess) { + *aSuccess = false; + } + + return L""; + } + + buffer = new wchar_t[buffer_size]; + + if (buffer == nullptr) { + if (aSuccess) { + *aSuccess = false; + } + + return L""; + } + + MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), + -1, buffer, buffer_size); + std::wstring str = buffer; + delete [] buffer; + + if (aSuccess) { + *aSuccess = true; + } + + return str; +} + +static inline std::string +WideToMBCS(const std::wstring &inp) { + int buffer_size = WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1, + nullptr, 0, NULL, NULL); + if (buffer_size == 0) { + return ""; + } + + std::vector<char> buffer(buffer_size); + buffer[0] = 0; + + WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1, + buffer.data(), buffer_size, NULL, NULL); + + return buffer.data(); +} + +static inline std::string +UTF8toMBCS(const std::string &inp) { + std::wstring wide = UTF8ToWide(inp); + std::string ret = WideToMBCS(wide); + return ret; +} + +#endif // XP_WIN + +// Check if a file exists at the specified path + +static inline bool +FileExists(const std::string& aPath) +{ +#if defined(XP_WIN) + DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str()); + return (attrs != INVALID_FILE_ATTRIBUTES); +#else // Non-Windows + struct stat sb; + int ret = stat(aPath.c_str(), &sb); + if (ret == -1 || !(sb.st_mode & S_IFREG)) { + return false; + } + + return true; +#endif // XP_WIN +} + +} // namespace + +#endif // MinidumpAnalyzerUtils_h
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if XP_WIN && HAVE_64BIT_BUILD + +#include "MozStackFrameSymbolizer.h" + +#include "MinidumpAnalyzerUtils.h" + +#include "processor/cfi_frame_info.h" + +#include <iostream> +#include <sstream> +#include <fstream> + +namespace CrashReporter { + + extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions; + + using google_breakpad::CFIFrameInfo; + +MozStackFrameSymbolizer::MozStackFrameSymbolizer() : + StackFrameSymbolizer(nullptr, nullptr) +{ +} + +MozStackFrameSymbolizer::SymbolizerResult +MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules, + const SystemInfo* system_info, + StackFrame* stack_frame) +{ + SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo( + modules, system_info, stack_frame); + + if (ret == kNoError && this->HasImplementation() && + stack_frame->function_name.empty()) { + // Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an + // address valid if it has associated symbols. + // + // This makes sense for complete & accurate symbols, but ours may be + // incomplete or wrong. Returning a function name tells Breakpad we + // recognize this address as code, so it's OK to use in stack scanning. + // This function is only called with addresses that land in this module. + // + // This allows us to fall back to stack scanning in the case where we were + // unable to provide CFI. + stack_frame->function_name = "<unknown code>"; + } + return ret; +} + +CFIFrameInfo* +MozStackFrameSymbolizer::FindCFIFrameInfo(const StackFrame* frame) +{ + std::string modulePath; + + // For unit testing, support loading a specified module instead of + // the real one. + bool moduleHasBeenReplaced = false; + if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) { + modulePath = gMinidumpAnalyzerOptions.forceUseModule; + moduleHasBeenReplaced = true; + } else { + if (!frame->module) { + return nullptr; + } + modulePath = frame->module->code_file(); + } + + // Get/create the unwind parser. + auto itMod = mModuleMap.find(modulePath); + std::shared_ptr<ModuleUnwindParser> unwindParser; + if (itMod != mModuleMap.end()) { + unwindParser = itMod->second; + } else { + unwindParser.reset(new ModuleUnwindParser(modulePath)); + mModuleMap[modulePath] = unwindParser; + } + + UnwindCFI cfi; + DWORD offsetAddr; + + if (moduleHasBeenReplaced) { + // If we are replacing a module, addresses will never line up. + // So just act like the 1st entry is correct. + offsetAddr = unwindParser->GetAnyOffsetAddr(); + } else { + offsetAddr = frame->instruction - frame->module->base_address(); + } + + if (!unwindParser->GetCFI(offsetAddr, cfi)) { + return nullptr; + } + + std::unique_ptr<CFIFrameInfo> rules(new CFIFrameInfo()); + + static const size_t exprSize = 50; + char expr[exprSize]; + if (cfi.stackSize == 0) { + snprintf(expr, exprSize, "$rsp"); + } else { + snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize); + } + rules->SetCFARule(expr); + + if (cfi.ripOffset == 0) { + snprintf(expr, exprSize, ".cfa ^"); + } else { + snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset); + } + rules->SetRARule(expr); + + return rules.release(); +} + +} // namespace CrashReporter + +#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MozStackFrameSymbolizer_h +#define MozStackFrameSymbolizer_h + +#if XP_WIN && HAVE_64BIT_BUILD + +#include "Win64ModuleUnwindMetadata.h" + +#include "google_breakpad/processor/stack_frame_symbolizer.h" +#include "google_breakpad/processor/stack_frame.h" + +#include <memory> + +namespace CrashReporter { + +using google_breakpad::CodeModule; +using google_breakpad::CodeModules; +using google_breakpad::SourceLineResolverInterface; +using google_breakpad::StackFrame; +using google_breakpad::StackFrameSymbolizer; +using google_breakpad::SymbolSupplier; +using google_breakpad::SystemInfo; + +class MozStackFrameSymbolizer : public StackFrameSymbolizer { + using google_breakpad::StackFrameSymbolizer::SymbolizerResult; + + std::map<std::string, std::shared_ptr<ModuleUnwindParser>> mModuleMap; + +public: + MozStackFrameSymbolizer(); + + virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules, + const SystemInfo* system_info, + StackFrame* stack_frame); + + virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo( + const StackFrame* frame); +}; + +} // namespace CrashReporter + +#endif // XP_WIN && HAVE_64BIT_BUILD + +#endif // MozStackFrameSymbolizer_h
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp @@ -0,0 +1,265 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if XP_WIN && HAVE_64BIT_BUILD + +#include "Win64ModuleUnwindMetadata.h" + +#include "MinidumpAnalyzerUtils.h" + +#include <windows.h> +#include <winnt.h> +#include <ImageHlp.h> +#include <iostream> +#include <sstream> +#include <string> + +namespace CrashReporter { + +union UnwindCode { + struct { + uint8_t offset_in_prolog; + uint8_t unwind_operation_code : 4; + uint8_t operation_info : 4; + }; + USHORT frame_offset; +}; + +enum UnwindOperationCodes { + UWOP_PUSH_NONVOL = 0, // info == register number + UWOP_ALLOC_LARGE = 1, // no info, alloc size in next 2 slots + UWOP_ALLOC_SMALL = 2, // info == size of allocation / 8 - 1 + UWOP_SET_FPREG = 3, // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 + UWOP_SAVE_NONVOL = 4, // info == register number, offset in next slot + UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots + UWOP_SAVE_XMM = 6, // Version 1; undocumented + UWOP_EPILOG = 6, // Version 2; undocumented + UWOP_SAVE_XMM_FAR = 7, // Version 1; undocumented + UWOP_SPARE = 7, // Version 2; undocumented + UWOP_SAVE_XMM128 = 8, // info == XMM reg number, offset in next slot + UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots + UWOP_PUSH_MACHFRAME = 10 // info == 0: no error-code, 1: error-code +}; + +struct UnwindInfo { + uint8_t version : 3; + uint8_t flags : 5; + uint8_t size_of_prolog; + uint8_t count_of_codes; + uint8_t frame_register : 4; + uint8_t frame_offset : 4; + UnwindCode unwind_code[1]; +}; + +ModuleUnwindParser::~ModuleUnwindParser() +{ + if (mImg) { + ImageUnload(mImg); + } +} + +void* +ModuleUnwindParser::RvaToVa(ULONG aRva) +{ + return ImageRvaToVa( + mImg->FileHeader, mImg->MappedAddress, aRva, &mImg->LastRvaSection); +} + +ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath) + : mPath(aPath) +{ + // Convert wchar to native charset because ImageLoad only takes + // a PSTR as input. + std::string code_file = UTF8toMBCS(aPath); + + mImg = ImageLoad((PSTR)code_file.c_str(), NULL); + if (!mImg || !mImg->FileHeader) { + return; + } + + PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader; + if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + return; + } + + DWORD exception_rva = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; + + DWORD exception_size = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; + + auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva); + if (!funcs) { + return; + } + + for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) { + mUnwindMap[funcs[i].BeginAddress] = &funcs[i]; + } +} + +bool +ModuleUnwindParser::GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc, + UnwindCFI& aRet) +{ + DWORD unwind_rva = aFunc.UnwindInfoAddress; + // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid + // circular references. + std::set<DWORD> visited; + + // Follow chained function entries + while (unwind_rva & 0x1) { + unwind_rva ^= 0x1; + + if (visited.end() != visited.find(unwind_rva)) { + return false; + } + visited.insert(unwind_rva); + + auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva); + if (!chained_func) { + return false; + } + unwind_rva = chained_func->UnwindInfoAddress; + } + + visited.insert(unwind_rva); + + auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva); + if (!unwind_info) { + return false; + } + + DWORD stack_size = 8; // minimal stack size is 8 for RIP + DWORD rip_offset = 8; + do { + for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) { + UnwindCode* unwind_code = &unwind_info->unwind_code[c]; + switch (unwind_code->unwind_operation_code) { + case UWOP_PUSH_NONVOL: { + stack_size += 8; + break; + } + case UWOP_ALLOC_LARGE: { + if (unwind_code->operation_info == 0) { + c++; + if (c < unwind_info->count_of_codes) { + stack_size += (unwind_code + 1)->frame_offset * 8; + } + } else { + c += 2; + if (c < unwind_info->count_of_codes) { + stack_size += (unwind_code + 1)->frame_offset | + ((unwind_code + 2)->frame_offset << 16); + } + } + break; + } + case UWOP_ALLOC_SMALL: { + stack_size += unwind_code->operation_info * 8 + 8; + break; + } + case UWOP_SET_FPREG: + // To correctly track RSP when it's been transferred to another + // register, we would need to emit CFI records for every unwind op. + // For simplicity, don't emit CFI records for this function as + // we know it will be incorrect after this point. + return false; + case UWOP_SAVE_NONVOL: + case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG + case UWOP_SAVE_XMM128: { + c++; // skip slot with offset + break; + } + case UWOP_SAVE_NONVOL_FAR: + case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE + case UWOP_SAVE_XMM128_FAR: { + c += 2; // skip 2 slots with offset + break; + } + case UWOP_PUSH_MACHFRAME: { + if (unwind_code->operation_info) { + stack_size += 88; + } else { + stack_size += 80; + } + rip_offset += 80; + break; + } + default: { + return false; + } + } + } + + if (unwind_info->flags & UNW_FLAG_CHAININFO) { + auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)( + (unwind_info->unwind_code + + ((unwind_info->count_of_codes + 1) & ~1))); + + if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) { + return false; // Circular reference + } + + visited.insert(chained_func->UnwindInfoAddress); + + unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress); + } else { + unwind_info = nullptr; + } + } while (unwind_info); + + aRet.beginAddress = aFunc.BeginAddress; + aRet.size = aFunc.EndAddress - aFunc.BeginAddress; + aRet.stackSize = stack_size; + aRet.ripOffset = rip_offset; + return true; +} + +// For unit testing we sometimes need any address that's valid in this module. +// Just return the first address we know of. +DWORD +ModuleUnwindParser::GetAnyOffsetAddr() const { + if (mUnwindMap.size() < 1) { + return 0; + } + return mUnwindMap.begin()->first; +} + +bool +ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet) +{ + // Figure out the begin address of the requested address. + auto itUW = mUnwindMap.lower_bound(aAddress + 1); + if (itUW == mUnwindMap.begin()) { + return false; // address before this module. + } + --itUW; + + // Ensure that the function entry is big enough to contain this address. + IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second; + if (aAddress > func.EndAddress) { + return false; + } + + // Do we have CFI for this function already? + auto itCFI = mCFIMap.find(aAddress); + if (itCFI != mCFIMap.end()) { + aRet = itCFI->second; + return true; + } + + // No, generate it. + if (!GenerateCFIForFunction(func, aRet)) { + return false; + } + + mCFIMap[func.BeginAddress] = aRet; + return true; +} + +} // namespace + +#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Win64ModuleUnwindMetadata_h +#define Win64ModuleUnwindMetadata_h + +#if XP_WIN && HAVE_64BIT_BUILD + +#include <functional> +#include <map> +#include <string> + +#include <windows.h> +#include <winnt.h> +#include <ImageHlp.h> + +namespace CrashReporter { + +struct UnwindCFI +{ + uint32_t beginAddress; + uint32_t size; + uint32_t stackSize; + uint32_t ripOffset; +}; + +// Does lazy-parsing of unwind info. +class ModuleUnwindParser { + PLOADED_IMAGE mImg; + std::string mPath; + + // Maps begin address to exception record. + // Populated upon construction. + std::map<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap; + + // Maps begin address to CFI. + // Populated as needed. + std::map<DWORD, UnwindCFI> mCFIMap; + + bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc, + UnwindCFI& aRet); + void* RvaToVa(ULONG aRva); + +public: + explicit ModuleUnwindParser(const std::string& aPath); + ~ModuleUnwindParser(); + bool GetCFI(DWORD aAddress, UnwindCFI& aRet); + DWORD GetAnyOffsetAddr() const; +}; + +} // namespace + +#endif // XP_WIN && HAVE_64BIT_BUILD + +#endif // Win64ModuleUnwindMetadata_h
--- a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp +++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp @@ -27,22 +27,21 @@ #elif defined(XP_UNIX) || defined(XP_MACOSX) #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #endif -// Path of the minidump to be analyzed. -static string gMinidumpPath; +#include "MinidumpAnalyzerUtils.h" -// When set to true print out the full minidump analysis, otherwise only -// include the crashing thread in the output. -static bool gFullMinidump = false; +#if XP_WIN && HAVE_64BIT_BUILD +#include "MozStackFrameSymbolizer.h" +#endif namespace CrashReporter { using std::ios; using std::ios_base; using std::hex; using std::ofstream; using std::map; @@ -57,87 +56,20 @@ using google_breakpad::CodeModule; using google_breakpad::CodeModules; using google_breakpad::Minidump; using google_breakpad::MinidumpProcessor; using google_breakpad::PathnameStripper; using google_breakpad::ProcessResult; using google_breakpad::ProcessState; using google_breakpad::StackFrame; -#ifdef XP_WIN - -#if !defined(_MSC_VER) -static string WideToMBCP(const wstring& wide, - unsigned int cp, - bool* success = nullptr) -{ - char* buffer = nullptr; - int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), - -1, nullptr, 0, nullptr, nullptr); - if(buffer_size == 0) { - if (success) - *success = false; - return ""; - } - - buffer = new char[buffer_size]; - if(buffer == nullptr) { - if (success) - *success = false; - return ""; - } - - WideCharToMultiByte(cp, 0, wide.c_str(), - -1, buffer, buffer_size, nullptr, nullptr); - string mb = buffer; - delete [] buffer; - - if (success) - *success = true; - - return mb; -} -#endif /* !defined(_MSC_VER) */ +MinidumpAnalyzerOptions gMinidumpAnalyzerOptions; -static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr) -{ - wchar_t* buffer = nullptr; - int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), - -1, nullptr, 0); - if (buffer_size == 0) { - if (aSuccess) { - *aSuccess = false; - } - - return L""; - } - - buffer = new wchar_t[buffer_size]; - - if (buffer == nullptr) { - if (aSuccess) { - *aSuccess = false; - } - - return L""; - } - - MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), - -1, buffer, buffer_size); - wstring str = buffer; - delete [] buffer; - - if (aSuccess) { - *aSuccess = true; - } - - return str; -} - -#endif +// Path of the minidump to be analyzed. +static string gMinidumpPath; struct ModuleCompare { bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const { return aLhs->base_address() < aRhs->base_address(); } }; typedef map<const CodeModule*, unsigned int, ModuleCompare> OrderedModulesMap; @@ -312,17 +244,18 @@ ConvertProcessStateToJSON(const ProcessS if (aProcessState.crashed()) { crashInfo["type"] = aProcessState.crash_reason(); crashInfo["address"] = ToHex(aProcessState.crash_address()); if (requestingThread != -1) { // Record the crashing thread index only if this is a full minidump // and all threads' stacks are present, otherwise only the crashing // thread stack is written out and this field is set to 0. - crashInfo["crashing_thread"] = gFullMinidump ? requestingThread : 0; + crashInfo["crashing_thread"] = + gMinidumpAnalyzerOptions.fullMinidump ? requestingThread : 0; } } else { crashInfo["type"] = Json::Value(Json::nullValue); // Add assertion info, if available string assertion = aProcessState.assertion(); if (!assertion.empty()) { crashInfo["assertion"] = assertion; @@ -340,17 +273,17 @@ ConvertProcessStateToJSON(const ProcessS } aRoot["modules"] = modules; // Threads Json::Value threads(Json::arrayValue); int threadCount = aProcessState.threads()->size(); - if (!gFullMinidump && (requestingThread != -1)) { + if (!gMinidumpAnalyzerOptions.fullMinidump && (requestingThread != -1)) { // Only add the crashing thread Json::Value thread; Json::Value stack(Json::arrayValue); const CallStack* rawStack = aProcessState.threads()->at(requestingThread); ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack); thread["frames"] = stack; threads.append(thread); @@ -369,19 +302,24 @@ ConvertProcessStateToJSON(const ProcessS aRoot["threads"] = threads; } // Process the minidump file and append the JSON-formatted stack traces to // the node specified in |aRoot| static bool ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) { +#if XP_WIN && HAVE_64BIT_BUILD + MozStackFrameSymbolizer symbolizer; + MinidumpProcessor minidumpProcessor(&symbolizer, false); +#else BasicSourceLineResolver resolver; // We don't have a valid symbol resolver so we pass nullptr instead. MinidumpProcessor minidumpProcessor(nullptr, &resolver); +#endif // Process the minidump. Minidump dump(aDumpFile); if (!dump.Read()) { return false; } ProcessResult rv; @@ -410,35 +348,16 @@ OpenAppend(const string& aFilename) new ofstream(WideToMBCP(UTF8ToWide(aFilename), CP_ACP).c_str(), mode); #endif // _MSC_VER #else // Non-Windows ofstream* file = new ofstream(aFilename.c_str(), mode); #endif // XP_WIN return file; } -// Check if a file exists at the specified path - -static bool -FileExists(const string& aPath) -{ -#if defined(XP_WIN) - DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str()); - return (attrs != INVALID_FILE_ATTRIBUTES); -#else // Non-Windows - struct stat sb; - int ret = stat(aPath.c_str(), &sb); - if (ret == -1 || !(sb.st_mode & S_IFREG)) { - return false; - } - - return true; -#endif // XP_WIN -} - // Update the extra data file by adding the StackTraces field holding the // JSON output of this program. static void UpdateExtraDataFile(const string &aDumpPath, const Json::Value& aRoot) { string extraDataPath(aDumpPath); int dot = extraDataPath.rfind('.'); @@ -468,17 +387,20 @@ using namespace CrashReporter; static void ParseArguments(int argc, char** argv) { if (argc <= 1) { exit(EXIT_FAILURE); } for (int i = 1; i < argc - 1; i++) { if (strcmp(argv[i], "--full") == 0) { - gFullMinidump = true; + gMinidumpAnalyzerOptions.fullMinidump = true; + } else if ((strcmp(argv[i], "--force-use-module") == 0) && (i < argc - 2)) { + gMinidumpAnalyzerOptions.forceUseModule = argv[i + 1]; + ++i; } else { exit(EXIT_FAILURE); } } gMinidumpPath = argv[argc - 1]; }
--- a/toolkit/crashreporter/minidump-analyzer/moz.build +++ b/toolkit/crashreporter/minidump-analyzer/moz.build @@ -22,13 +22,25 @@ if CONFIG['OS_TARGET'] != 'Android': LOCAL_INCLUDES += [ '/toolkit/components/jsoncpp/include', ] if CONFIG['OS_TARGET'] == 'Darwin': DIST_SUBDIR = 'crashreporter.app/Contents/MacOS' +if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64': + UNIFIED_SOURCES += [ + 'MozStackFrameSymbolizer.cpp', + 'Win64ModuleUnwindMetadata.cpp', + ] + + OS_LIBS += [ + 'Dbghelp', + 'Imagehlp' + ] + + # Don't use the STL wrappers in the crashreporter clients; they don't # link with -lmozalloc, and it really doesn't matter here anyway. DisableStlWrapping() include('/toolkit/crashreporter/crashreporter.mozbuild')
--- a/toolkit/crashreporter/test/CrashTestUtils.jsm +++ b/toolkit/crashreporter/test/CrashTestUtils.jsm @@ -3,26 +3,39 @@ this.EXPORTED_SYMBOLS = ["CrashTestUtils"]; this.CrashTestUtils = { // These will be defined using ctypes APIs below. crash: null, dumpHasStream: null, dumpHasInstructionPointerMemory: null, + dumpWin64CFITestSymbols: null, // Constants for crash() // Keep these in sync with nsTestCrasher.cpp! CRASH_INVALID_POINTER_DEREF: 0, CRASH_PURE_VIRTUAL_CALL: 1, CRASH_RUNTIMEABORT: 2, CRASH_OOM: 3, CRASH_MOZ_CRASH: 4, CRASH_ABORT: 5, CRASH_UNCAUGHT_EXCEPTION: 6, + CRASH_X64CFI_NO_MANS_LAND: 7, + CRASH_X64CFI_LAUNCHER: 8, + CRASH_X64CFI_UNKNOWN_OPCODE: 9, + CRASH_X64CFI_PUSH_NONVOL: 10, + CRASH_X64CFI_ALLOC_SMALL: 11, + CRASH_X64CFI_ALLOC_LARGE: 12, + CRASH_X64CFI_SAVE_NONVOL: 15, + CRASH_X64CFI_SAVE_NONVOL_FAR: 16, + CRASH_X64CFI_SAVE_XMM128: 17, + CRASH_X64CFI_SAVE_XMM128_FAR: 18, + CRASH_X64CFI_EPILOG: 19, + CRASH_X64CFI_EOF: 20, // Constants for dumpHasStream() // From google_breakpad/common/minidump_format.h MD_THREAD_LIST_STREAM: 3, MD_MEMORY_INFO_LIST_STREAM: 16 }; // Grab APIs from the testcrasher shared library @@ -58,8 +71,14 @@ CrashTestUtils.dumpHasInstructionPointer ctypes.default_abi, ctypes.bool, ctypes.char.ptr); CrashTestUtils.dumpCheckMemory = lib.declare("DumpCheckMemory", ctypes.default_abi, ctypes.bool, ctypes.char.ptr); + +CrashTestUtils.getWin64CFITestFnAddrOffset = + lib.declare("GetWin64CFITestFnAddrOffset", + ctypes.default_abi, + ctypes.int32_t, + ctypes.int16_t);
--- a/toolkit/crashreporter/test/moz.build +++ b/toolkit/crashreporter/test/moz.build @@ -19,16 +19,21 @@ UNIFIED_SOURCES += [ 'dumputils.cpp', 'nsTestCrasher.cpp', ] SOURCES += [ 'ExceptionThrower.cpp', ] +if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64': + SOURCES += [ + 'win64UnwindInfoTests.asm', + ] + if CONFIG['CLANG_CL']: SOURCES['ExceptionThrower.cpp'].flags += [ '-Xclang', '-fcxx-exceptions', ] elif not CONFIG['_MSC_VER']: SOURCES['ExceptionThrower.cpp'].flags += [ '-fexceptions',
--- a/toolkit/crashreporter/test/nsTestCrasher.cpp +++ b/toolkit/crashreporter/test/nsTestCrasher.cpp @@ -2,16 +2,17 @@ #include <stdio.h> #include "nscore.h" #include "mozilla/Unused.h" #include "ExceptionThrower.h" #ifdef XP_WIN +#include <malloc.h> #include <windows.h> #endif /* * This pure virtual call example is from MSDN */ class A; @@ -38,23 +39,90 @@ void fcn( A* p ) void PureVirtualCall() { // generates a pure virtual function call B b; b.use(); // make sure b's actually used } +extern "C" { +#if XP_WIN && HAVE_64BIT_BUILD + // Implementation in win64unwindInfoTests.asm + uint64_t x64CrashCFITest_NO_MANS_LAND(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_Launcher(uint64_t returnpfn, void* testProc); + uint64_t x64CrashCFITest_UnknownOpcode(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_PUSH_NONVOL(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_ALLOC_SMALL(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_ALLOC_LARGE(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_SAVE_NONVOL(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_SAVE_XMM128(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_SAVE_XMM128_FAR(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_EPILOG(uint64_t returnpfn, void*); + uint64_t x64CrashCFITest_EOF(uint64_t returnpfn, void*); +#endif // XP_WIN && HAVE_64BIT_BUILD +} + // Keep these in sync with CrashTestUtils.jsm! const int16_t CRASH_INVALID_POINTER_DEREF = 0; const int16_t CRASH_PURE_VIRTUAL_CALL = 1; const int16_t CRASH_OOM = 3; const int16_t CRASH_MOZ_CRASH = 4; const int16_t CRASH_ABORT = 5; const int16_t CRASH_UNCAUGHT_EXCEPTION = 6; +const int16_t CRASH_X64CFI_NO_MANS_LAND = 7; +const int16_t CRASH_X64CFI_LAUNCHER = 8; +const int16_t CRASH_X64CFI_UNKNOWN_OPCODE = 9; +const int16_t CRASH_X64CFI_PUSH_NONVOL = 10; +const int16_t CRASH_X64CFI_ALLOC_SMALL = 11; +const int16_t CRASH_X64CFI_ALLOC_LARGE = 12; +const int16_t CRASH_X64CFI_SAVE_NONVOL = 15; +const int16_t CRASH_X64CFI_SAVE_NONVOL_FAR = 16; +const int16_t CRASH_X64CFI_SAVE_XMM128 = 17; +const int16_t CRASH_X64CFI_SAVE_XMM128_FAR = 18; +const int16_t CRASH_X64CFI_EPILOG = 19; +const int16_t CRASH_X64CFI_EOF = 20; + +#if XP_WIN && HAVE_64BIT_BUILD + +typedef decltype(&x64CrashCFITest_UnknownOpcode) win64CFITestFnPtr_t; + +static std::map<int16_t, win64CFITestFnPtr_t> +GetWin64CFITestMap() { + std::map<int16_t, win64CFITestFnPtr_t> ret = { + { CRASH_X64CFI_NO_MANS_LAND, x64CrashCFITest_NO_MANS_LAND}, + { CRASH_X64CFI_LAUNCHER, x64CrashCFITest_Launcher}, + { CRASH_X64CFI_UNKNOWN_OPCODE, x64CrashCFITest_UnknownOpcode}, + { CRASH_X64CFI_PUSH_NONVOL, x64CrashCFITest_PUSH_NONVOL}, + { CRASH_X64CFI_ALLOC_SMALL, x64CrashCFITest_ALLOC_SMALL }, + { CRASH_X64CFI_ALLOC_LARGE, x64CrashCFITest_ALLOC_LARGE }, + { CRASH_X64CFI_SAVE_NONVOL, x64CrashCFITest_SAVE_NONVOL }, + { CRASH_X64CFI_SAVE_NONVOL_FAR, x64CrashCFITest_SAVE_NONVOL_FAR }, + { CRASH_X64CFI_SAVE_XMM128, x64CrashCFITest_SAVE_XMM128 }, + { CRASH_X64CFI_SAVE_XMM128_FAR, x64CrashCFITest_SAVE_XMM128_FAR }, + { CRASH_X64CFI_EPILOG, x64CrashCFITest_EPILOG }, + { CRASH_X64CFI_EOF, x64CrashCFITest_EOF } + }; + // ret values point to jump table entries, not the actual function bodies. + // Get the correct pointer by calling the function with returnpfn=1 + for (auto it = ret.begin(); it != ret.end(); ++ it) { + it->second = (win64CFITestFnPtr_t)it->second(1, nullptr); + } + return ret; +} + +void ReserveStack() { + // This ensures our tests have enough reserved stack space. + uint8_t* p = (uint8_t*)alloca(1024000); + // This ensures we don't optimized away this meaningless code at build time. + mozilla::Unused << (int)(uint64_t)p; +} + +#endif // XP_WIN && HAVE_64BIT_BUILD extern "C" NS_EXPORT void Crash(int16_t how) { switch (how) { case CRASH_INVALID_POINTER_DEREF: { volatile int* foo = (int*)0x42; *foo = 0; @@ -79,16 +147,37 @@ void Crash(int16_t how) case CRASH_ABORT: { abort(); break; } case CRASH_UNCAUGHT_EXCEPTION: { ThrowException(); break; } +#if XP_WIN && HAVE_64BIT_BUILD + case CRASH_X64CFI_UNKNOWN_OPCODE: + case CRASH_X64CFI_PUSH_NONVOL: + case CRASH_X64CFI_ALLOC_SMALL: + case CRASH_X64CFI_ALLOC_LARGE: + case CRASH_X64CFI_SAVE_NONVOL: + case CRASH_X64CFI_SAVE_NONVOL_FAR: + case CRASH_X64CFI_SAVE_XMM128: + case CRASH_X64CFI_SAVE_XMM128_FAR: + case CRASH_X64CFI_EPILOG: { + ReserveStack(); + auto m = GetWin64CFITestMap(); + if (m.find(how) == m.end()) { + break; + } + auto pfnTest = m[how]; + auto pfnLauncher = m[CRASH_X64CFI_LAUNCHER]; + pfnLauncher(0, pfnTest); + break; + } +#endif // XP_WIN && HAVE_64BIT_BUILD default: break; } } char testData[32]; extern "C" NS_EXPORT @@ -114,8 +203,25 @@ static LONG WINAPI HandleException(EXCEP } extern "C" NS_EXPORT void TryOverrideExceptionHandler() { SetUnhandledExceptionFilter(HandleException); } #endif + +extern "C" NS_EXPORT uint32_t +GetWin64CFITestFnAddrOffset(int16_t fnid) { +#if XP_WIN && HAVE_64BIT_BUILD + // fnid uses the same constants as Crash(). + // Returns the RVA of the requested function. + // Returns 0 on failure. + auto m = GetWin64CFITestMap(); + if (m.find(fnid) == m.end()) { + return 0; + } + uint64_t moduleBase = (uint64_t)GetModuleHandleW(L"testcrasher.dll"); + return ((uint64_t)m[fnid]) - moduleBase; +#else + return 0; +#endif // XP_WIN && HAVE_64BIT_BUILD +}
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js +++ b/toolkit/crashreporter/test/unit/head_crashreporter.js @@ -1,13 +1,14 @@ -var {utils: Cu} = Components; +var {utils: Cu, classes: Cc, interfaces: Ci} = Components; Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://testing-common/AppData.jsm", this); +Cu.import("resource://gre/modules/AppConstants.jsm"); function getEventDir() { return OS.Path.join(do_get_tempdir().path, "crash-events"); } /* * Run an xpcshell subprocess and crash it. * @@ -19,25 +20,27 @@ function getEventDir() { * This code will be evaluted between crasher_subprocess_head.js * and crasher_subprocess_tail.js, so it will have access * to everything defined in crasher_subprocess_head.js, * which includes "crashReporter", a variable holding * the crash reporter service. * * @param callback * A JavaScript function to be called after the subprocess - * crashes. It will be passed (minidump, extra), where - * minidump is an nsIFile of the minidump file produced, - * and extra is an object containing the key,value pairs from - * the .extra file. + * crashes. It will be passed (minidump, extra, extrafile), where + * - minidump is an nsIFile of the minidump file produced, + * - extra is an object containing the key,value pairs from + * the .extra file. + * - extrafile is an nsIFile of the extra file * * @param canReturnZero * If true, the subprocess may return with a zero exit code. * Certain types of crashes may not cause the process to * exit with an error. + * */ function do_crash(setup, callback, canReturnZero) { // get current process filename (xpcshell) let ds = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties); let bin = ds.get("XREExeF", Components.interfaces.nsIFile); if (!bin.exists()) { // weird, can't find xpcshell binary? @@ -95,16 +98,42 @@ function getMinidump() { if (f.leafName.substr(-4) == ".dmp") { return f; } } return null; } +function runMinidumpAnalyzer(dumpFile, additionalArgs) { + if (AppConstants.platform !== "win") { + return; + } + + // find minidump-analyzer executable. + let ds = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + let bin = ds.get("XREExeF", Ci.nsIFile); + ok(bin && bin.exists()); + bin = bin.parent; + ok(bin && bin.exists()); + bin.append("minidump-analyzer.exe"); + ok(bin.exists()); + + let process = Cc["@mozilla.org/process/util;1"] + .createInstance(Ci.nsIProcess); + process.init(bin); + let args = []; + if (additionalArgs) { + args = args.concat(additionalArgs); + } + args.push(dumpFile.path); + process.run(true /* blocking */, args, args.length); +} + function handleMinidump(callback) { // find minidump let minidump = getMinidump(); if (minidump == null) { do_throw("No minidump found!"); } @@ -126,17 +155,17 @@ function handleMinidump(callback) { memoryfile.remove(false); } }); do_check_true(extrafile.exists()); let extra = parseKeyValuePairsFromFile(extrafile); if (callback) { - callback(minidump, extra); + callback(minidump, extra, extrafile); } if (minidump.exists()) { minidump.remove(false); } if (extrafile.exists()) { extrafile.remove(false); }
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/head_win64cfi.js @@ -0,0 +1,193 @@ +/* import-globals-from head_crashreporter.js */ + +let gTestCrasherSyms = null; +let gModules = null; + +// Returns the offset (int) of an IP with a given base address. +// This is effectively (ip - base), except a bit more complication due to +// Javascript's shaky handling of 64-bit integers. +// base & ip are passed as hex strings. +function getModuleOffset(base, ip) { + let i = 0; + // Find where the two addresses diverge, which enables us to perform a 32-bit + // subtraction. + // e.g. "0x1111111111112222" + // - "0x1111111111111111" + // becomes 2222 - 1111 + for (; i < base.length; ++i) { + if (base[i] != ip[i]) { + break; + } + } + if (i == base.length) { + return 0; + } + let lhs2 = "0x" + base.substring(i); + let rhs2 = "0x" + ip.substring(i); + return parseInt(rhs2) - parseInt(lhs2); +} + +// Uses gTestCrasherSyms to convert an address to a symbol. +function findNearestTestCrasherSymbol(addr) { + addr += 1; // Breakpad sometimes offsets addresses; correct for this. + let closestDistance = null; + let closestSym = null; + for (let sym in gTestCrasherSyms) { + if (addr >= gTestCrasherSyms[sym]) { + let thisDistance = addr - gTestCrasherSyms[sym]; + if (closestDistance === null || thisDistance < closestDistance) { + closestDistance = thisDistance; + closestSym = sym; + } + } + } + if (closestSym === null) { + return null; + } + return { symbol: closestSym, offset: closestDistance } +} + +// Populate known symbols for testcrasher.dll. +// Use the same prop names as from CrashTestUtils to avoid the need for mapping. +function initTestCrasherSymbols() { + gTestCrasherSyms = { }; + for (let k in CrashTestUtils) { + // Not all keys here are valid symbol names. getWin64CFITestFnAddrOffset + // will return 0 in those cases, no need to filter here. + if (Number.isInteger(CrashTestUtils[k])) { + let t = CrashTestUtils.getWin64CFITestFnAddrOffset(CrashTestUtils[k]); + if (t > 0) { + gTestCrasherSyms[k] = t; + } + } + } +} + +function stackFrameToString(frameIndex, frame) { + // Calculate the module offset. + let ip = frame.ip; + let symbol = ""; + let moduleOffset = "unknown_offset"; + let filename = "unknown_module"; + + if (typeof frame.module_index !== "undefined" && (frame.module_index >= 0) + && (frame.module_index < gModules.length)) { + + let base = gModules[frame.module_index].base_addr; + moduleOffset = getModuleOffset(base, ip); + filename = gModules[frame.module_index].filename; + + if (filename === "testcrasher.dll") { + let nearestSym = findNearestTestCrasherSymbol(moduleOffset); + if (nearestSym !== null) { + symbol = nearestSym.symbol + "+" + nearestSym.offset.toString(16); + } + } + } + + let ret = "frames[" + frameIndex + "] ip=" + ip + + " " + symbol + + ", module:" + filename + + ", trust:" + frame.trust + + ", moduleOffset:" + moduleOffset.toString(16); + return ret; +} + +function dumpStackFrames(frames, maxFrames) { + for (let i = 0; i < Math.min(maxFrames, frames.length); ++i) { + do_print(stackFrameToString(i, frames[i])); + } +} + +// Test that the top of the given stack (from extra data) matches the given +// expected frames. +// +// expected is { symbol: "", trust: "" } +function assertStack(stack, expected) { + for (let i = 0; i < stack.length; ++i) { + if (i >= expected.length) { + ok("Top stack frames were expected"); + return; + } + let frame = stack[i]; + let expectedFrame = expected[i]; + let dumpThisFrame = function() { + do_print(" Actual frame: " + stackFrameToString(i, frame)); + do_print("Expected { symbol: " + expectedFrame.symbol + ", trust: " + expectedFrame.trust + "}"); + }; + + if (expectedFrame.trust) { + if (frame.trust !== expectedFrame.trust) { + dumpThisFrame(); + do_print("Expected frame trust did not match."); + ok(false); + } + } + + if (expectedFrame.symbol) { + if (typeof frame.module_index === "undefined") { + // Without a module_index, it happened in an unknown module. Currently + // you can't specify an expected "unknown" module. + do_print("Unknown symbol in unknown module."); + ok(false); + } + if (frame.module_index < 0 || frame.module_index >= gModules.length) { + dumpThisFrame(); + do_print("Unknown module."); + ok(false); + return; + } + let base = gModules[frame.module_index].base_addr; + let moduleOffset = getModuleOffset(base, frame.ip); + let filename = gModules[frame.module_index].filename; + if (filename == "testcrasher.dll") { + let nearestSym = findNearestTestCrasherSymbol(moduleOffset); + if (nearestSym === null) { + dumpThisFrame(); + do_print("Unknown symbol."); + ok(false); + return; + } + + if (nearestSym.symbol !== expectedFrame.symbol) { + dumpThisFrame(); + do_print("Mismatching symbol."); + ok(false); + } + } + } + } +} + +// Performs a crash, runs minidump-analyzer, and checks expected stack analysis. +// +// how: The crash to perform. Constants defined in both CrashTestUtils.jsm +// and nsTestCrasher.cpp (i.e. CRASH_X64CFI_PUSH_NONVOL) +// expectedStack: An array of {"symbol", "trust"} where trust is "cfi", +// "context", "scan", et al. May be null if you don't need to check the stack. +// minidumpAnalyzerArgs: An array of additional arguments to pass to +// minidump-analyzer.exe. +function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) { + + // Setup is run in the subprocess so we cannot use any closures. + let setupFn = "crashType = CrashTestUtils." + how + ";"; + + let callbackFn = function(minidumpFile, extra, extraFile) { + runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs); + + // Refresh updated extra data + extra = parseKeyValuePairsFromFile(extraFile); + + initTestCrasherSymbols(); + let stackTraces = JSON.parse(extra.StackTraces); + let crashingThreadIndex = stackTraces.crash_info.crashing_thread; + gModules = stackTraces.modules; + let crashingFrames = stackTraces.threads[crashingThreadIndex].frames; + + dumpStackFrames(crashingFrames, 10); + + assertStack(crashingFrames, expectedStack); + }; + + do_crash(setupFn, callbackFn, true, true); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [ + { symbol: "CRASH_X64CFI_ALLOC_LARGE", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_EPILOG", [ + { symbol: "CRASH_X64CFI_EPILOG", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5283cc5df7e0a7abb20dd540cc203c4266702fff GIT binary patch literal 1440 zc$|e)-%C?b9RJ>J*K9U7hQ@m8E>1tF7@^aLo)*o#()__DM0UwdH*~Sxjd?8<9~#R* zFM*Z58SGD}NRgfdjVL#Z48*LLelgI89(xFG=iGCz+pbVw_T_uNzrW{uK6jmcvtR}Q z3uZ+DSO!Wy_&=>+u0Q^=9$p+Sx|f+i(cL>7AK}uO)KDgp<OU<6n3A}G7?&9pxwy#r zyF*+u6^(gpHY?I<Y}GBketc_ZPUo(9$K0%X{xtUpON;lz{6kf4&l5cu9~>re`V2j7 z0MYwq_)-X7*KNPx1k{=8!NFj^iP}D6>7!>KHiJXs8ZAecd3HYz?<Jh2QLrx#>QzT@ z;rufddK&8p4pJB|J^k<XR#A_+x{r}OQfy4Z($3KN9ZdNiI&rwIClifG5j>}f*5ou< zNk07Zq-h+|?r=eq#yEajK(+j|gdErS>>h`^&TQX%Bh}{6Nj1j4G>JI-l7#wq56!HX zii)CKS|$%5Z^;`<V-cTnzZL0w;K2eS*&Xr?M~6J(>X5(nuNDdgwf;3ew->fA3CK;# z-P~nMKgxAB<#$Tslvd{_FAFHm=hnhFTg%Pv<<&t1GRPzzge`qFq+ij~pE0Gg*jLqS z+z@<B4{l!J<puN<t9*8i^ypuO9>b0Z>DV;UoxyuE<x1ZX%ng_w7Rr@A8|F34$U?c& zS8wPQ4EaMFcR#+ht}nJvo$Gp)ZwaY*_g@r}<ARtP74O8ws4&**6@sxuEHV<?H{vHB z0DaC%YQNkYAXp;UQgMcoBC+e3^u!5PPz$l?xYlt-?&)H59ZgJ3L<fejzht49@{DPC zWa<62T1}+Z`D$CUiaEBbr&pZ=+gxQ^D?s_X({b!}vfzxK2B-HFI2i<Wcu2--QRlEe zuSqjD7Hr<5U}GAfCQbY3)W;tDMsza1_olgXG~tim2DsW5>}m@%H+iCoMA=XF_{2o- UpT}|s^B)WSf9l3PY7O=N0y)qbQvd(}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js @@ -0,0 +1,17 @@ + +function run_test() { + // Test that minidump-analyzer gracefully handles chained + // unwind code entries that form a circular reference + // (infinite loop). + let exe = do_get_file("test_crash_win64cfi_infinite_code_chain.exe"); + ok(exe); + + // Perform a crash. We won't get unwind info, but make sure the stack scan + // still works. + do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null } + ], + ["--force-use-module", exe.path]); +}
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..de48576f6536cc6895f23297752690da1a694a10 GIT binary patch literal 1440 zc$|e)-Ahwp7=O+_)MncbLt|ZajJFRWM(FaQt3{hlnjcI-s?j#x(8a!*M^h7GtpYs+ zRvKLx_zwsbCDN6k5T&cg3o*O04+eVCWf#HedCxho`w;43Kc4q}zTfx#owZ$=0zCj2 z5V8!g2&%Z?|FVRz9C>4bR|oP9i<~dt&^-_v6w;Yge<qv|`ohU%N)&pdLS`r_#FB!$ zqf1DnB2kCgY$95XjoR05A79^^W@w+`r>B(lm+42S4bIOq4;8&RL-+nz-vEtcGjw_Y zBKP$WeDA-^Y`@?r)aoq2bLiKpwy&sN>e_{7;5AffIZV$P_u}wgLRmEm_SF7;*%IuS zf3jqs#yx@k62ePQ|NDB&q*Gtsr&1hZbVNjL<<$9ku6Pb60=9KzB4II%bL!NZf~HHw zg+E7HjU(C_&S<(a&N~qxHQou4@IBt_u7F)9H}Ad^YjWh65@TH$CxUfBB*C5P$<;z$ zmgVz{^Z}#|>4RLK$5rmPEPnSrm?bp3Rl3TzN`v-R>05AlZEa1d-{Z~ghO7$#(m=~y z+c6e?6zi-@ALaUcTAiO>DL~TR+)4<uwcPCPoH9s&4l<5|7z^7K=`Ae%1y?wQzOr7U z6~TvDaMO}knkCP1>&>pv9>Hbkthgg8jGH?3&ft??MXB!~q7K0uRFwM6h!q4es3`SW zDth_5++CjA?(JK{Ph0ModRFJ3%=45t?sI{}XdsyyO5ThmBY}}-XTTqgN5g~BJtIDO zFR(dF<>G~IALSC|mclcVNK!lFq$5VTLe5ef<2B<MJ*S;xdongQ7U}Irf5D(~$}ytx z(WstZtJO$roi4XEDV#Hw^>iw8pv_*iH3JmCJH}(qRt>hu39vbjgN-9li$iiI1DKo^ zFuklwb7mu$orl28)j?HS?Zf1XasM~Mr1HJj^=(6OcWfBolE>ff@ijF%BJp_9Pw(*= W;s)l;q25CLF{uAf9oVC$qTXLgycI0~
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js @@ -0,0 +1,17 @@ + +function run_test() { + // Test that minidump-analyzer gracefully handles chained + // IMAGE_RUNTIME_FUNCTION_ENTRY items that form a circular reference + // (infinite loop). + let exe = do_get_file("test_crash_win64cfi_infinite_entry_chain.exe"); + ok(exe); + + // Perform a crash. We won't get unwind info, but make sure the stack scan + // still works. + do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null } + ], + ["--force-use-module", exe.path]); +}
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ab4ce326bdf34b9e417107d2d22adb3d01b006ee GIT binary patch literal 1440 zc$|e)-Ahwp7=O+_)Moo&XsoM_@%BN*2%TPZwP@y%<_DV~*&#RG(8YE(=CM?~Xe<Lg z1Xg-yus@+9MY<9+qI4D+h#6h<#Xv8*>>^n2d*1W9?FjX-hv$91pZ9rxXB~aBU<3dY z=3)_G87O(-|FnYHcH*TCUL46cEOY*RL(gz*giocDL+NmW9}G)UQsxJue0o&kV-oM{ z3i63$B<i+WEl8`eUAy@D@vYrC#+~zyxmor6Y3>o0CeMfYhpOC}Cwd?@I85T$4Bf2& zk^4q)d<kA>wqI}(Y7I7UaM*94w$E64>Di0T;Lx~A%V9ESK8VA631w&$9Eihu#SvUM z|7@9F#Dw55h49i-|6XqebsH=D=*cZd$7C$+9G&06mF~fa$8Fu|NLUWzISsTXugP-q z;-5Q3<H+nOe5Z?=)W-?aBB~LlW#s4;GW$I4I<s@{ja-vOr_>nx(j?;TOET);tDade z<ncN#FOvsQww29deIB2(zl-vB|APfYvfGs#j&^0l)vkQ&U)|W)Q0w;!+5M1xNkk2# z+|506;YX>?mh!GxKc&_ADJUXJ3E8y}&en1>`#E(Gkqk142Qe4E>e8>W^k-b*9QIZ8 z8rKCMXTeP?g0g^~VpYhjkskf4(5>4MA%;x@-5I<$QZDx$#au^p6XkNB74sTqWTIT| zv*~&Tg1+G9-H&fA>x*qu=R05Jnu96~e3!(;xF{t@r8_YxB966q#6UD24Ua?*jQGh1 zfX!J>ZC85yqzJ*5iZhguh@D~59V1vl&BVrV&2UEU>Ef7<CMG5#14GzfFi}jo$22^e z>HW1@jilCvN?VhPIder%k2(joxk|Pc(6CCd%W%v&tHBvL15VFraB>K0@sOOw1Qw4C zEYGV_oYf3g&oQuabx@U}eHitb4}T+!^zXf8>==#vVz&XVwFWv{{Y{PTNIYKhlRZ8$ V(EI1G+{OIIME{?<aE}^Yy}vLX7<&K!
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js @@ -0,0 +1,16 @@ + +function run_test() { + // Test that minidump-analyzer gracefully handles an invalid pointer to the + // exception unwind information. + let exe = do_get_file("test_crash_win64cfi_invalid_exception_rva.exe"); + ok(exe); + + // Perform a crash. We won't get unwind info, but make sure the stack scan + // still works. + do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null } + ], + ["--force-use-module", exe.path]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe @@ -0,0 +1,1 @@ +this is not a valid PE file. \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js @@ -0,0 +1,15 @@ + +function run_test() { + // Test that minidump-analyzer gracefully handles corrupt PE files. + let exe = do_get_file("test_crash_win64cfi_not_a_pe.exe"); + ok(exe); + + // Perform a crash. We won't get unwind info, but make sure the stack scan + // still works. + do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", + [ + { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" }, + { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null } + ], + ["--force-use-module", exe.path]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [ + { symbol: "CRASH_X64CFI_PUSH_NONVOL", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [ + { symbol: "CRASH_X64CFI_SAVE_NONVOL", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [ + { symbol: "CRASH_X64CFI_SAVE_NONVOL_FAR", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [ + { symbol: "CRASH_X64CFI_SAVE_XMM128", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js @@ -0,0 +1,7 @@ + +function run_test() { + do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [ + { symbol: "CRASH_X64CFI_SAVE_XMM128_FAR", trust: "context" }, + { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" } + ]); +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js @@ -0,0 +1,13 @@ + +function run_test() { + // In the case of an unknown unwind code or missing CFI, + // make certain we can still walk the stack via stack scan. The crashing + // function places NO_MANS_LAND on the stack so it will get picked up via + // stack scan. + do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [ + { symbol: "CRASH_X64CFI_UNKNOWN_OPCODE", trust: "context" }, + // Trust may either be scan or frame_pointer; we don't really care as + // long as the address is expected. + { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null } + ]); +}
--- a/toolkit/crashreporter/test/unit/xpcshell.ini +++ b/toolkit/crashreporter/test/unit/xpcshell.ini @@ -32,8 +32,70 @@ skip-if = os != 'win' [test_crashreporter_appmem.js] # we need to skip this due to bug 838613 skip-if = (os != 'win' && os != 'linux') || (os=='linux' && bits==32) [test_crash_AsyncShutdown.js] [test_event_files.js] [test_crash_terminator.js] + +[test_crash_win64cfi_unknown_op.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_push_nonvol.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_alloc_small.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_alloc_large.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_save_nonvol.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_save_nonvol_far.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_save_xmm128.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_save_xmm128_far.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_epilog.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 + +[test_crash_win64cfi_infinite_entry_chain.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 +support-files = test_crash_win64cfi_infinite_entry_chain.exe + +[test_crash_win64cfi_infinite_code_chain.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 +support-files = test_crash_win64cfi_infinite_code_chain.exe + +[test_crash_win64cfi_invalid_exception_rva.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 +support-files = test_crash_win64cfi_invalid_exception_rva.exe + +[test_crash_win64cfi_not_a_pe.js] +head = head_crashreporter.js head_win64cfi.js +skip-if = os != 'win' || bits != 64 +support-files = test_crash_win64cfi_not_a_pe.exe + + + + + +
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/test/win64UnwindInfoTests.asm @@ -0,0 +1,382 @@ +; 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/. + +; Comments indicate stack memory layout during execution. +; For example at the top of a function, where RIP just points to the return +; address, the stack looks like +; rip = [ra] +; And after pushing rax to the stack, +; rip = [rax][ra] +; And then, after allocating 20h bytes on the stack, +; rip = [..20..][rax][ra] +; And then, after pushing a function pointer, +; rip = [pfn][..20..][rax][ra] + +include ksamd64.inc + +.code + +; It helps to add padding between functions so they're not right up against +; each other. Adds clarity to debugging, and gives a bit of leeway when +; searching for symbols (e.g. a function whose last instruction is CALL +; would push a return address that's in the next function.) +PaddingBetweenFunctions macro + repeat 10h + int 3 + endm +endm + +DoCrash macro + mov rax, 7 + mov byte ptr [rax], 9 +endm + +PaddingBetweenFunctions + +; There is no rip addressing mode in x64. The only way to get the value +; of rip is to call a function, and pop it from the stack. +WhoCalledMe proc + pop rax ; rax is now ra + push rax ; Restore ra so this function can return. + sub rax, 5 ; Correct for the size of the call instruction + ret +WhoCalledMe endp + +PaddingBetweenFunctions + +; Any function that we expect to test against on the stack, we'll need its +; real address. If we use function pointers in C, we'll get the address to jump +; table entries. This bit of code at the beginning of each function will +; return the real address we'd expect to see in stack traces. +; +; rcx (1st arg) = mode +; rax (return) = address of either NO_MANS_LAND or this function. +; +; When mode is 0, we place the address of NO_MANS_LAND in RAX, for the function +; to use as it wants. This is just for convenience because almost all functions +; here need this address at some point. +; +; When mode is 1, the address of this function is returned. +TestHeader macro + call WhoCalledMe + test rcx, rcx + je continue_test + ret +continue_test: + inc rcx + call x64CrashCFITest_NO_MANS_LAND + xor rcx, rcx +endm + +; The point of this is to add a stack frame to test against. +; void* x64CrashCFITest_Launcher(int getAddress, void* pTestFn) +x64CrashCFITest_Launcher proc frame + TestHeader + + .endprolog + call rdx + ret +x64CrashCFITest_Launcher endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_NO_MANS_LAND(uint64_t mode); +; Not meant to be called. Only when mode = 1 in order to return its address. +; Place this function's address on the stack so the stack scanning algorithm +; thinks this is a return address, and places it on the stack trace. +x64CrashCFITest_NO_MANS_LAND proc frame + TestHeader + .endprolog + ret +x64CrashCFITest_NO_MANS_LAND endp + +PaddingBetweenFunctions + +; Test that we: +; - handle unknown opcodes gracefully +; - fall back to other stack unwind strategies if CFI doesn't work +; +; In order to properly unwind this frame, we'd need to fully support +; SET_FPREG with offsets, plus restoring registers via PUSH_NONVOL. +; To do this, sprinkle the stack with bad return addresses +; and stack pointers. +x64CrashCFITest_UnknownOpcode proc frame + TestHeader + + push rax + .allocstack 8 + + push rbp + .pushreg rbp + + push rax + push rsp + push rax + push rsp + .allocstack 20h + ; rsp = [rsp][pfn][rsp][pfn][rbp][pfn][ra] + + lea rbp, [rsp+10h] + .setframe rbp, 10h + ; rsp = [rsp][pfn] [rsp][pfn][rbp][pfn][ra] + ; rbp = ^ + + .endprolog + + ; Now modify RSP so measuring stack size from unwind ops will not help + ; finding the return address. + push rax + push rsp + ; rsp = [rsp][pfn][rsp][pfn] [rsp][pfn][rbp][pfn][ra] + + DoCrash + +x64CrashCFITest_UnknownOpcode endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_PUSH_NONVOL(uint64_t mode); +; +; Test correct handling of PUSH_NONVOL unwind code. +; +x64CrashCFITest_PUSH_NONVOL proc frame + TestHeader + + push r10 + .pushreg r10 + push r15 + .pushreg r15 + push rbx + .pushreg rbx + push rsi + .pushreg rsi + push rbp + .pushreg rbp + ; rsp = [rbp][rsi][rbx][r15][r10][ra] + + push rax + .allocstack 8 + ; rsp = [pfn][rbp][rsi][rbx][r15][r10][ra] + + .endprolog + + DoCrash + +x64CrashCFITest_PUSH_NONVOL endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_ALLOC_SMALL(uint64_t mode); +; +; Small allocations are between 8bytes and 512kb-8bytes +; +x64CrashCFITest_ALLOC_SMALL proc frame + TestHeader + + ; Trash rbp to force stack scan. This will force + ; correct behavior for test_crash_win64cfi_not_a_pe, et al. + xor rbp, rbp + + push rax + push rax + push rax + push rax + .allocstack 20h + ; rsp = [pfn][pfn][pfn][pfn][ra] + + .endprolog + + DoCrash + +x64CrashCFITest_ALLOC_SMALL endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_ALLOC_LARGE(uint64_t mode); +; +; Allocations between 512kb and 4gb +; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack +; space for this. +x64CrashCFITest_ALLOC_LARGE proc frame + TestHeader + + sub rsp, 0a000h + .allocstack 0a000h + ; rsp = [..640kb..][ra] + + mov qword ptr [rsp], rax + ; rsp = [pfn][..640kb-8..][ra] + + .endprolog + + DoCrash + +x64CrashCFITest_ALLOC_LARGE endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_SAVE_NONVOL(uint64_t mode); +; +; Test correct handling of SAVE_NONVOL unwind code. +; +x64CrashCFITest_SAVE_NONVOL proc frame + TestHeader + + sub rsp, 30h + .allocstack 30h + ; rsp = [..30..][ra] + + mov qword ptr [rsp+28h], r10 + .savereg r10, 28h + mov qword ptr [rsp+20h], rbp + .savereg rbp, 20h + mov qword ptr [rsp+18h], rsi + .savereg rsi, 18h + mov qword ptr [rsp+10h], rbx + .savereg rbx, 10h + mov qword ptr [rsp+8], r15 + .savereg r15, 8 + ; rsp = [r15][rbx][rsi][rbp][r10][ra] + + mov qword ptr [rsp], rax + + ; rsp = [pfn][r15][rbx][rsi][rbp][r10][ra] + + .endprolog + + DoCrash + +x64CrashCFITest_SAVE_NONVOL endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t mode); +; +; Similar to the test above but adding 640kb to most offsets. +; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack +; space for this. +x64CrashCFITest_SAVE_NONVOL_FAR proc frame + TestHeader + + sub rsp, 0a0030h + .allocstack 0a0030h + ; rsp = [..640k..][..30..][ra] + + mov qword ptr [rsp+28h+0a0000h], r10 + .savereg r10, 28h+0a0000h + mov qword ptr [rsp+20h+0a0000h], rbp + .savereg rbp, 20h+0a0000h + mov qword ptr [rsp+18h+0a0000h], rsi + .savereg rsi, 18h+0a0000h + mov qword ptr [rsp+10h+0a0000h], rbx + .savereg rbx, 10h+0a0000h + mov qword ptr [rsp+8+0a0000h], r15 + .savereg r15, 8+0a0000h + ; rsp = [..640k..][..8..][r15][rbx][rsi][rbp][r10][ra] + + mov qword ptr [rsp], rax + + ; rsp = [pfn][..640k..][r15][rbx][rsi][rbp][r10][ra] + + .endprolog + + DoCrash + +x64CrashCFITest_SAVE_NONVOL_FAR endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode); +; +; Test correct handling of SAVE_XMM128 unwind code. +x64CrashCFITest_SAVE_XMM128 proc frame + TestHeader + + sub rsp, 30h + .allocstack 30h + ; rsp = [..30..][ra] + + movdqu [rsp+20h], xmm6 + .savexmm128 xmm6, 20h + ; rsp = [..20..][xmm6][ra] + + movdqu [rsp+10h], xmm15 + .savexmm128 xmm15, 10h + ; rsp = [..10..][xmm15][xmm6][ra] + + mov qword ptr [rsp], rax + ; rsp = [pfn][..8..][xmm15][xmm6][ra] + + .endprolog + + DoCrash + +x64CrashCFITest_SAVE_XMM128 endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode); +; +; Similar to the test above but adding 640kb to most offsets. +; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack +; space for this. +x64CrashCFITest_SAVE_XMM128_FAR proc frame + TestHeader + + sub rsp, 0a0030h + .allocstack 0a0030h + ; rsp = [..640kb..][..30..][ra] + + movdqu [rsp+20h+0a0000h], xmm6 + .savexmm128 xmm6, 20h+0a0000h + ; rsp = [..640kb..][..20..][xmm6][ra] + + movdqu [rsp+10h+0a0000h], xmm6 + .savexmm128 xmm15, 10h+0a0000h + ; rsp = [..640kb..][..10..][xmm15][xmm6][ra] + + mov qword ptr [rsp], rax + ; rsp = [pfn][..640kb..][..8..][xmm15][xmm6][ra] + + .endprolog + + DoCrash + +x64CrashCFITest_SAVE_XMM128_FAR endp + +PaddingBetweenFunctions + +; void* x64CrashCFITest_EPILOG(uint64_t mode); +; +; The epilog unwind op will also set the unwind version to 2. +; Test that we don't choke on UWOP_EPILOG or version 2 unwind info. +x64CrashCFITest_EPILOG proc frame + TestHeader + + push rax + .allocstack 8 + ; rsp = [pfn][ra] + + .endprolog + + DoCrash + + .beginepilog + + ret + +x64CrashCFITest_EPILOG endp + +PaddingBetweenFunctions + +; Having an EOF symbol at the end of this file contains symbolication to this +; file. So addresses beyond this file don't get mistakenly symbolicated as a +; meaningful function name. +x64CrashCFITest_EOF proc frame + TestHeader + .endprolog + ret +x64CrashCFITest_EOF endp + +end
--- a/tools/profiler/core/platform.cpp +++ b/tools/profiler/core/platform.cpp @@ -102,17 +102,18 @@ // Android builds use the ARM Exception Handling ABI to unwind. #if defined(GP_PLAT_arm_android) # define HAVE_NATIVE_UNWIND # define USE_EHABI_STACKWALK # include "EHABIStackWalk.h" #endif // Linux builds use LUL, which uses DWARF info to unwind stacks. -#if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) || defined(GP_PLAT_mips64_linux) +#if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) || \ + defined(GP_PLAT_mips64_linux) # define HAVE_NATIVE_UNWIND # define USE_LUL_STACKWALK # include "lul/LulMain.h" # include "lul/platform-linux-lul.h" // On linux we use LUL for periodic samples and synchronous samples, but we use // FramePointerStackWalk for backtrace samples when MOZ_PROFILING is enabled. // (See the comment at the top of the file for a definition of @@ -1128,16 +1129,20 @@ DoLULBacktrace(PSLockRef aLock, const Th startRegs.r13 = lul::TaggedUWord(mc->arm_sp); startRegs.r12 = lul::TaggedUWord(mc->arm_ip); startRegs.r11 = lul::TaggedUWord(mc->arm_fp); startRegs.r7 = lul::TaggedUWord(mc->arm_r7); #elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android) startRegs.xip = lul::TaggedUWord(mc->gregs[REG_EIP]); startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_ESP]); startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_EBP]); +#elif defined(GP_PLAT_mips64_linux) + startRegs.pc = lul::TaggedUWord(mc->pc); + startRegs.sp = lul::TaggedUWord(mc->gregs[29]); + startRegs.fp = lul::TaggedUWord(mc->gregs[30]); #else # error "Unknown plat" #endif // Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not going past the // stack's registered top point. Do some basic sanity checks too. This // assumes that the TaggedUWord holding the stack pointer value is valid, but // it should be, since it was constructed that way in the code just above. @@ -1175,16 +1180,19 @@ DoLULBacktrace(PSLockRef aLock, const Th uintptr_t rEDZONE_SIZE = 128; uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE; #elif defined(GP_PLAT_arm_android) uintptr_t rEDZONE_SIZE = 0; uintptr_t start = startRegs.r13.Value() - rEDZONE_SIZE; #elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android) uintptr_t rEDZONE_SIZE = 0; uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE; +#elif defined(GP_PLAT_mips64_linux) + uintptr_t rEDZONE_SIZE = 0; + uintptr_t start = startRegs.sp.Value() - rEDZONE_SIZE; #else # error "Unknown plat" #endif uintptr_t end = reinterpret_cast<uintptr_t>(aThreadInfo.StackTop()); uintptr_t ws = sizeof(void*); start &= ~(ws-1); end &= ~(ws-1); uintptr_t nToCopy = 0;