diff --git a/.hgtags b/.hgtags index c4b3d1840d78..fb0245d14024 100644 --- a/.hgtags +++ b/.hgtags @@ -145,3 +145,4 @@ ccfd7b716a91241ddbc084cb7116ec561e56d5d1 FIREFOX_BETA_61_BASE 93443d36d4bd53dba004f7b73430879f96daa681 FIREFOX_NIGHTLY_61_END 9b74b9f2939a7ae3a0ea6e711dc32ed5203e03ff FIREFOX_BETA_62_BASE 4f6e597104dabedfecfafa2ab63dc79fd7f8bc7a FIREFOX_NIGHTLY_62_END +190b827aaa2b5e6fb9af7a0defb238ccc35f8b9e FIREFOX_BETA_63_BASE diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini index d46c8d475c98..c0535471ddf3 100644 --- a/browser/base/content/test/siteIdentity/browser.ini +++ b/browser/base/content/test/siteIdentity/browser.ini @@ -56,7 +56,7 @@ support-files = !/toolkit/components/passwordmgr/test/browser/insecure_test.html !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html [browser_mcb_redirect.js] -skip-if = verify && !debug && os == 'mac' +skip-if = (verify && !debug && os == 'mac') || (os == 'linux') || (os == 'mac') # Bug 1376771 tags = mcb support-files = test_mcb_redirect.html diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini index 509606f3376a..9e3dc559a5fa 100644 --- a/browser/base/content/test/tabs/browser.ini +++ b/browser/base/content/test/tabs/browser.ini @@ -52,6 +52,7 @@ skip-if = !e10s # Pref and test only relevant for e10s. [browser_newwindow_tabstrip_overflow.js] [browser_open_newtab_start_observer_notification.js] [browser_opened_file_tab_navigated_to_web.js] +skip-if = (os == 'mac' && debug) || (os == 'linux' && debug) # Bug 1356347 [browser_overflowScroll.js] [browser_pinnedTabs_clickOpen.js] [browser_pinnedTabs_closeByKeyboard.js] diff --git a/build/valgrind/cross-architecture.sup b/build/valgrind/cross-architecture.sup index a66c90c15161..f2df52d83acd 100644 --- a/build/valgrind/cross-architecture.sup +++ b/build/valgrind/cross-architecture.sup @@ -25,6 +25,13 @@ fun:_ZL13SaveWordToEnvPKcRK12nsTSubstringIcE ... } +{ + PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793549.) + Memcheck:Leak + ... + fun:SaveWordToEnv + ... +} { PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 944133.) Memcheck:Leak diff --git a/devtools/client/aboutdebugging-new/aboutdebugging.css b/devtools/client/aboutdebugging-new/aboutdebugging.css index ae9111ef76cd..e677c486cb8f 100644 --- a/devtools/client/aboutdebugging-new/aboutdebugging.css +++ b/devtools/client/aboutdebugging-new/aboutdebugging.css @@ -5,12 +5,13 @@ @import "chrome://global/skin/in-content/common.css"; @import "resource://devtools/client/aboutdebugging-new/src/components/App.css"; @import "resource://devtools/client/aboutdebugging-new/src/components/ConnectPage.css"; -@import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css"; -@import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetList.css"; @import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css"; @import "resource://devtools/client/aboutdebugging-new/src/components/Sidebar.css"; @import "resource://devtools/client/aboutdebugging-new/src/components/SidebarItem.css"; +@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css"; +@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css"; @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css"; +@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css"; :root { /* Import css variables from common.css */ diff --git a/devtools/client/aboutdebugging-new/src/actions/runtime.js b/devtools/client/aboutdebugging-new/src/actions/runtime.js index e67822bbe065..936a62d8a68e 100644 --- a/devtools/client/aboutdebugging-new/src/actions/runtime.js +++ b/devtools/client/aboutdebugging-new/src/actions/runtime.js @@ -10,6 +10,7 @@ const { BrowserToolboxProcess } = const { Cc, Ci } = require("chrome"); const { DebuggerClient } = require("devtools/shared/client/debugger-client"); const { DebuggerServer } = require("devtools/server/main"); +const { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser"); const { CONNECT_RUNTIME_FAILURE, @@ -25,6 +26,9 @@ const { REQUEST_TABS_FAILURE, REQUEST_TABS_START, REQUEST_TABS_SUCCESS, + REQUEST_WORKERS_FAILURE, + REQUEST_WORKERS_START, + REQUEST_WORKERS_SUCCESS, } = require("../constants"); let browserToolboxProcess = null; @@ -43,6 +47,7 @@ function connectRuntime() { dispatch({ type: CONNECT_RUNTIME_SUCCESS, client }); dispatch(requestExtensions()); dispatch(requestTabs()); + dispatch(requestWorkers()); } catch (e) { dispatch({ type: CONNECT_RUNTIME_FAILURE, error: e.message }); } @@ -67,26 +72,39 @@ function disconnectRuntime() { } function inspectDebugTarget(type, id) { - if (type === DEBUG_TARGETS.TAB) { - window.open(`about:devtools-toolbox?type=tab&id=${ id }`); - } else if (type === DEBUG_TARGETS.EXTENSION) { - // Close previous addon debugging toolbox. - if (browserToolboxProcess) { - browserToolboxProcess.close(); - } - - browserToolboxProcess = BrowserToolboxProcess.init({ - addonID: id, - onClose: () => { - browserToolboxProcess = null; + return async (_, getState) => { + switch (type) { + case DEBUG_TARGETS.TAB: { + // Open tab debugger in new window. + window.open(`about:devtools-toolbox?type=tab&id=${ id }`); + break; } - }); - } else { - console.error(`Failed to inspect the debug target of type: ${ type } id: ${ id }`); - } + case DEBUG_TARGETS.EXTENSION: { + // Close current debugging toolbox and open a new one. + if (browserToolboxProcess) { + browserToolboxProcess.close(); + } - // We cancel the redux flow here since the inspection does not need to update the state. - return () => {}; + browserToolboxProcess = BrowserToolboxProcess.init({ + addonID: id, + onClose: () => { + browserToolboxProcess = null; + } + }); + break; + } + case DEBUG_TARGETS.WORKER: { + // Open worker toolbox in new window. + gDevToolsBrowser.openWorkerToolbox(getState().runtime.client, id); + break; + } + + default: { + console.error("Failed to inspect the debug target of " + + `type: ${ type } id: ${ id }`); + } + } + }; } function installTemporaryExtension() { @@ -116,6 +134,18 @@ function installTemporaryExtension() { return () => {}; } +function pushServiceWorker(actor) { + return async (_, getState) => { + const client = getState().runtime.client; + + try { + await client.request({ to: actor, type: "push" }); + } catch (e) { + console.error(e); + } + }; +} + function reloadTemporaryExtension(actor) { return async (_, getState) => { const client = getState().runtime.client; @@ -181,13 +211,53 @@ function requestExtensions() { }; } +function requestWorkers() { + return async (dispatch, getState) => { + dispatch({ type: REQUEST_WORKERS_START }); + + const client = getState().runtime.client; + + try { + const { + other: otherWorkers, + service: serviceWorkers, + shared: sharedWorkers, + } = await client.mainRoot.listAllWorkers(); + + dispatch({ + type: REQUEST_WORKERS_SUCCESS, + otherWorkers, + serviceWorkers, + sharedWorkers, + }); + } catch (e) { + dispatch({ type: REQUEST_WORKERS_FAILURE, error: e.message }); + } + }; +} + +function startServiceWorker(actor) { + return async (_, getState) => { + const client = getState().runtime.client; + + try { + await client.request({ to: actor, type: "start" }); + } catch (e) { + console.error(e); + } + }; +} + module.exports = { connectRuntime, disconnectRuntime, inspectDebugTarget, installTemporaryExtension, + pushServiceWorker, reloadTemporaryExtension, removeTemporaryExtension, requestTabs, requestExtensions, + requestWorkers, + startServiceWorker, }; diff --git a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js index 66bd8aed1fae..bc3adcf32d91 100644 --- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js +++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js @@ -9,10 +9,16 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/ const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const DebugTargetPane = createFactory(require("./DebugTargetPane")); +const DebugTargetPane = createFactory(require("./debugtarget/DebugTargetPane")); +const ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail")); +const InspectAction = createFactory(require("./debugtarget/InspectAction")); const RuntimeInfo = createFactory(require("./RuntimeInfo")); +const ServiceWorkerAction = createFactory(require("./debugtarget/ServiceWorkerAction")); +const TabDetail = createFactory(require("./debugtarget/TabDetail")); +const TemporaryExtensionAction = createFactory(require("./debugtarget/TemporaryExtensionAction")); const TemporaryExtensionInstaller = createFactory(require("./debugtarget/TemporaryExtensionInstaller")); +const WorkerDetail = createFactory(require("./debugtarget/WorkerDetail")); const Services = require("Services"); @@ -21,13 +27,24 @@ class RuntimePage extends PureComponent { return { dispatch: PropTypes.func.isRequired, installedExtensions: PropTypes.arrayOf(PropTypes.object).isRequired, + otherWorkers: PropTypes.arrayOf(PropTypes.object).isRequired, + serviceWorkers: PropTypes.arrayOf(PropTypes.object).isRequired, + sharedWorkers: PropTypes.arrayOf(PropTypes.object).isRequired, tabs: PropTypes.arrayOf(PropTypes.object).isRequired, temporaryExtensions: PropTypes.arrayOf(PropTypes.object).isRequired, }; } render() { - const { dispatch, installedExtensions, tabs, temporaryExtensions } = this.props; + const { + dispatch, + installedExtensions, + otherWorkers, + serviceWorkers, + sharedWorkers, + tabs, + temporaryExtensions, + } = this.props; return dom.article( { @@ -40,20 +57,47 @@ class RuntimePage extends PureComponent { }), TemporaryExtensionInstaller({ dispatch }), DebugTargetPane({ + actionComponent: TemporaryExtensionAction, + detailComponent: ExtensionDetail, dispatch, name: "Temporary Extensions", targets: temporaryExtensions, }), DebugTargetPane({ + actionComponent: InspectAction, + detailComponent: ExtensionDetail, dispatch, name: "Extensions", targets: installedExtensions, }), DebugTargetPane({ + actionComponent: InspectAction, + detailComponent: TabDetail, dispatch, name: "Tabs", targets: tabs }), + DebugTargetPane({ + actionComponent: ServiceWorkerAction, + detailComponent: WorkerDetail, + dispatch, + name: "Service Workers", + targets: serviceWorkers + }), + DebugTargetPane({ + actionComponent: InspectAction, + detailComponent: WorkerDetail, + dispatch, + name: "Shared Workers", + targets: sharedWorkers + }), + DebugTargetPane({ + actionComponent: InspectAction, + detailComponent: WorkerDetail, + dispatch, + name: "Other Workers", + targets: otherWorkers + }), ); } } @@ -61,6 +105,9 @@ class RuntimePage extends PureComponent { const mapStateToProps = state => { return { installedExtensions: state.runtime.installedExtensions, + otherWorkers: state.runtime.otherWorkers, + serviceWorkers: state.runtime.serviceWorkers, + sharedWorkers: state.runtime.sharedWorkers, tabs: state.runtime.tabs, temporaryExtensions: state.runtime.temporaryExtensions, }; diff --git a/devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css similarity index 100% rename from devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css diff --git a/devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js similarity index 50% rename from devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js index 565e239cc92f..5050bacdbfc1 100644 --- a/devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js @@ -4,63 +4,31 @@ "use strict"; -const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react"); +const { PureComponent } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail")); -const TabDetail = createFactory(require("./debugtarget/TabDetail")); -const TemporaryExtensionAction = - createFactory(require("./debugtarget/TemporaryExtensionAction")); - -const Actions = require("../actions/index"); -const { DEBUG_TARGETS } = require("../constants"); - /** * This component displays debug target. */ class DebugTargetItem extends PureComponent { static get propTypes() { return { + actionComponent: PropTypes.any.isRequired, + detailComponent: PropTypes.any.isRequired, dispatch: PropTypes.func.isRequired, target: PropTypes.object.isRequired, }; } - inspect() { - const { dispatch, target } = this.props; - dispatch(Actions.inspectDebugTarget(target.type, target.id)); - } - renderAction() { - const { dispatch, target } = this.props; - - return dom.div( - {}, - dom.button( - { - onClick: e => this.inspect(), - className: "aboutdebugging-button", - }, - "Inspect" - ), - target.details.temporarilyInstalled - ? TemporaryExtensionAction({ dispatch, target }) : null, - ); + const { actionComponent, dispatch, target } = this.props; + return actionComponent({ dispatch, target }); } renderDetail() { - const { target } = this.props; - - switch (target.type) { - case DEBUG_TARGETS.EXTENSION: - return ExtensionDetail({ target }); - case DEBUG_TARGETS.TAB: - return TabDetail({ target }); - - default: - return null; - } + const { detailComponent, target } = this.props; + return detailComponent({ target }); } renderIcon() { @@ -71,8 +39,6 @@ class DebugTargetItem extends PureComponent { } renderInfo() { - const { target } = this.props; - return dom.div( { className: "debug-target-item__info", @@ -80,9 +46,9 @@ class DebugTargetItem extends PureComponent { dom.div( { className: "debug-target-item__info__name ellipsis-text", - title: target.name, + title: this.props.target.name, }, - target.name + this.props.target.name ), this.renderDetail(), ); diff --git a/devtools/client/aboutdebugging-new/src/components/DebugTargetList.css b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css similarity index 100% rename from devtools/client/aboutdebugging-new/src/components/DebugTargetList.css rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css diff --git a/devtools/client/aboutdebugging-new/src/components/DebugTargetList.js b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.js similarity index 76% rename from devtools/client/aboutdebugging-new/src/components/DebugTargetList.js rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.js index bda2d3e48c5c..4d7467f697a1 100644 --- a/devtools/client/aboutdebugging-new/src/components/DebugTargetList.js +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.js @@ -16,19 +16,22 @@ const DebugTargetItem = createFactory(require("./DebugTargetItem")); class DebugTargetList extends PureComponent { static get propTypes() { return { + actionComponent: PropTypes.any.isRequired, + detailComponent: PropTypes.any.isRequired, dispatch: PropTypes.func.isRequired, targets: PropTypes.arrayOf(PropTypes.object).isRequired, }; } render() { - const { dispatch, targets } = this.props; + const { actionComponent, detailComponent, dispatch, targets } = this.props; return dom.ul( { className: "debug-target-list", }, - targets.map(target => DebugTargetItem({ dispatch, target })), + targets.map(target => + DebugTargetItem({ actionComponent, detailComponent, dispatch, target })), ); } } diff --git a/devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js similarity index 78% rename from devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js index bc47db7c8585..87d2d8efc6ec 100644 --- a/devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js @@ -16,6 +16,8 @@ const DebugTargetList = createFactory(require("./DebugTargetList")); class DebugTargetPane extends PureComponent { static get propTypes() { return { + actionComponent: PropTypes.any.isRequired, + detailComponent: PropTypes.any.isRequired, dispatch: PropTypes.func.isRequired, name: PropTypes.string.isRequired, targets: PropTypes.arrayOf(PropTypes.Object).isRequired, @@ -23,12 +25,12 @@ class DebugTargetPane extends PureComponent { } render() { - const { dispatch, name, targets } = this.props; + const { actionComponent, detailComponent, dispatch, name, targets } = this.props; return dom.section( {}, dom.h2({}, name), - DebugTargetList({ dispatch, targets }), + DebugTargetList({ actionComponent, detailComponent, dispatch, targets }), ); } } diff --git a/devtools/client/aboutdebugging-new/src/components/debugtarget/InspectAction.js b/devtools/client/aboutdebugging-new/src/components/debugtarget/InspectAction.js new file mode 100644 index 000000000000..cb82380f49c0 --- /dev/null +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/InspectAction.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { PureComponent } = require("devtools/client/shared/vendor/react"); +const dom = require("devtools/client/shared/vendor/react-dom-factories"); +const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); + +const Actions = require("../../actions/index"); + +/** + * This component provides inspect button. + */ +class InspectAction extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + target: PropTypes.object.isRequired, + }; + } + + inspect() { + const { dispatch, target } = this.props; + dispatch(Actions.inspectDebugTarget(target.type, target.id)); + } + + render() { + return dom.button( + { + onClick: e => this.inspect(), + className: "aboutdebugging-button", + }, + "Inspect" + ); + } +} + +module.exports = InspectAction; diff --git a/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js b/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js new file mode 100644 index 000000000000..0f8134a626e4 --- /dev/null +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react"); +const dom = require("devtools/client/shared/vendor/react-dom-factories"); +const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); + +const InspectAction = createFactory(require("./InspectAction")); + +const Actions = require("../../actions/index"); + +/** + * This component displays buttons for service worker. + */ +class ServiceWorkerAction extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + target: PropTypes.object.isRequired, + }; + } + + push() { + const { dispatch, target } = this.props; + dispatch(Actions.pushServiceWorker(target.id)); + } + + start() { + const { dispatch, target } = this.props; + dispatch(Actions.startServiceWorker(target.details.registrationActor)); + } + + _renderAction() { + const { dispatch, target } = this.props; + const { isActive, isRunning } = target.details; + + if (!isRunning) { + return this._renderButton("Start", this.start.bind(this)); + } + + if (!isActive) { + // Only debug button is available if the service worker is not active. + return InspectAction({ dispatch, target }); + } + + return [ + this._renderButton("Push", this.push.bind(this)), + InspectAction({ dispatch, target }), + ]; + } + + _renderButton(label, onClick) { + return dom.button( + { + className: "aboutdebugging-button", + onClick: e => onClick(), + }, + label, + ); + } + + render() { + return dom.div({}, this._renderAction()); + } +} + +module.exports = ServiceWorkerAction; diff --git a/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js b/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js index 4346c3b232ab..e1a324b7f5df 100644 --- a/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js @@ -4,14 +4,16 @@ "use strict"; -const { PureComponent } = require("devtools/client/shared/vendor/react"); +const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); +const InspectAction = createFactory(require("./InspectAction")); + const Actions = require("../../actions/index"); /** - * This component provides components that reload/remove temporary extension. + * This component provides components that inspect/reload/remove temporary extension. */ class TemporaryExtensionAction extends PureComponent { static get propTypes() { @@ -32,7 +34,11 @@ class TemporaryExtensionAction extends PureComponent { } render() { - return [ + const { dispatch, target } = this.props; + + return dom.div( + {}, + InspectAction({ dispatch, target }), dom.button( { className: "aboutdebugging-button", @@ -47,7 +53,7 @@ class TemporaryExtensionAction extends PureComponent { }, "Remove", ), - ]; + ); } } diff --git a/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css new file mode 100644 index 000000000000..9543049264f2 --- /dev/null +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css @@ -0,0 +1,52 @@ +/* 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/. */ + +.worker-detail { + --worker-status-font-size: 10px; +} + +/* + * The current layout of worker detail is + * + * +----------------+--------------------+ + * | detail name dt | detail value dd | + * | (60px ) | (auto) | + * +----------------+--------------------+ + * | detail name dt | detail value dd | + * +----------------+--------------------+ + * | detail name dt | detail value dd | + * +----------------+--------------------+ + */ +.worker-detail { + display: grid; + grid-template-columns: 60px auto; + margin-block-start: 4px; +} + +/* + * worker-detail__status has a ui like badge and the color change by the status. + * For now, the background-color of running status is palegreen, stopped is lightgrey + * though, might be changed since this is not Photon color. + */ +.worker-detail__status { + border-style: solid; + border-width: 1px; + box-sizing: border-box; + display: inline-block; + font-size: var(--worker-status-font-size); + margin-block-start: 6px; + padding-block-start: 2px; + padding-block-end: 2px; + text-align: center; +} + +.worker-detail__status--running { + border-color: limegreen; + background-color: palegreen; +} + +.worker-detail__status--stopped { + border-color: grey; + background-color: lightgrey; +} diff --git a/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.js b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.js new file mode 100644 index 000000000000..d64bffd7ab70 --- /dev/null +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.js @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { PureComponent } = require("devtools/client/shared/vendor/react"); +const dom = require("devtools/client/shared/vendor/react-dom-factories"); +const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); + +const { + SERVICE_WORKER_FETCH_STATES, +} = require("../../constants"); + +/** + * This component displays detail information for worker. + */ +class WorkerDetail extends PureComponent { + static get propTypes() { + return { + target: PropTypes.object.isRequired, + }; + } + + renderFetch() { + const { fetch } = this.props.target.details; + const label = fetch === SERVICE_WORKER_FETCH_STATES.LISTENING + ? "Listening for fetch events" + : "Not listening for fetch events"; + return this.renderField("Fetch", label); + } + + renderField(name, value) { + return [ + dom.dt({}, name), + dom.dd( + { + className: "ellipsis-text", + title: value, + }, + value, + ), + ]; + } + + renderStatus() { + const status = this.props.target.details.status.toLowerCase(); + + return dom.div( + { + className: `worker-detail__status worker-detail__status--${ status }`, + }, + status + ); + } + + render() { + const { fetch, scope, status } = this.props.target.details; + + return dom.dl( + { + className: "worker-detail", + }, + fetch ? this.renderFetch() : null, + scope ? this.renderField("Scope", scope) : null, + status ? this.renderStatus() : null, + ); + } +} + +module.exports = WorkerDetail; diff --git a/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build index 3070d2e879c2..4672eb0076a9 100644 --- a/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build +++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build @@ -3,9 +3,18 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'DebugTargetItem.css', + 'DebugTargetItem.js', + 'DebugTargetList.css', + 'DebugTargetList.js', + 'DebugTargetPane.js', 'ExtensionDetail.css', 'ExtensionDetail.js', + 'InspectAction.js', + 'ServiceWorkerAction.js', 'TabDetail.js', 'TemporaryExtensionAction.js', 'TemporaryExtensionInstaller.js', + 'WorkerDetail.css', + 'WorkerDetail.js', ) diff --git a/devtools/client/aboutdebugging-new/src/components/moz.build b/devtools/client/aboutdebugging-new/src/components/moz.build index d43450465aaf..b61217005c8c 100644 --- a/devtools/client/aboutdebugging-new/src/components/moz.build +++ b/devtools/client/aboutdebugging-new/src/components/moz.build @@ -11,11 +11,6 @@ DevToolsModules( 'App.js', 'ConnectPage.css', 'ConnectPage.js', - 'DebugTargetItem.css', - 'DebugTargetItem.js', - 'DebugTargetList.css', - 'DebugTargetList.js', - 'DebugTargetPane.js', 'RuntimeInfo.css', 'RuntimeInfo.js', 'RuntimePage.js', diff --git a/devtools/client/aboutdebugging-new/src/constants.js b/devtools/client/aboutdebugging-new/src/constants.js index 5ee610c4958d..c80d70091973 100644 --- a/devtools/client/aboutdebugging-new/src/constants.js +++ b/devtools/client/aboutdebugging-new/src/constants.js @@ -19,11 +19,15 @@ const actionTypes = { REQUEST_TABS_FAILURE: "REQUEST_TABS_FAILURE", REQUEST_TABS_START: "REQUEST_TABS_START", REQUEST_TABS_SUCCESS: "REQUEST_TABS_SUCCESS", + REQUEST_WORKERS_FAILURE: "REQUEST_WORKERS_FAILURE", + REQUEST_WORKERS_START: "REQUEST_WORKERS_START", + REQUEST_WORKERS_SUCCESS: "REQUEST_WORKERS_SUCCESS", }; const DEBUG_TARGETS = { EXTENSION: "EXTENSION", TAB: "TAB", + WORKER: "WORKER", }; const PAGES = { @@ -31,8 +35,21 @@ const PAGES = { CONNECT: "connect", }; +const SERVICE_WORKER_FETCH_STATES = { + LISTENING: "LISTENING", + NOT_LISTENING: "NOT_LISTENING", +}; + +const SERVICE_WORKER_STATUSES = { + RUNNING: "RUNNING", + REGISTERING: "REGISTERING", + STOPPED: "STOPPED", +}; + // flatten constants module.exports = Object.assign({}, { DEBUG_TARGETS, PAGES, + SERVICE_WORKER_FETCH_STATES, + SERVICE_WORKER_STATUSES, }, actionTypes); diff --git a/devtools/client/aboutdebugging-new/src/create-store.js b/devtools/client/aboutdebugging-new/src/create-store.js index f98805549f41..9ab6e0dcfd59 100644 --- a/devtools/client/aboutdebugging-new/src/create-store.js +++ b/devtools/client/aboutdebugging-new/src/create-store.js @@ -11,6 +11,9 @@ const rootReducer = require("./reducers/index"); const { RuntimeState } = require("./reducers/runtime-state"); const { UiState } = require("./reducers/ui-state"); const debugTargetListenerMiddleware = require("./middleware/debug-target-listener"); +const extensionComponentDataMiddleware = require("./middleware/extension-component-data"); +const tabComponentDataMiddleware = require("./middleware/tab-component-data"); +const workerComponentDataMiddleware = require("./middleware/worker-component-data"); const { getNetworkLocations } = require("./modules/network-locations"); function configureStore() { @@ -19,7 +22,11 @@ function configureStore() { ui: getUiState() }; - const middleware = applyMiddleware(thunk, debugTargetListenerMiddleware); + const middleware = applyMiddleware(thunk, + debugTargetListenerMiddleware, + extensionComponentDataMiddleware, + tabComponentDataMiddleware, + workerComponentDataMiddleware); return createStore(rootReducer, initialState, middleware); } diff --git a/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js b/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js index d04bad392504..4071f5c4a8aa 100644 --- a/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js +++ b/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js @@ -12,13 +12,13 @@ const { } = require("../constants"); const Actions = require("../actions/index"); -function debugTargetListenerMiddleware(state) { +function debugTargetListenerMiddleware(store) { const onExtensionsUpdated = () => { - state.dispatch(Actions.requestExtensions()); + store.dispatch(Actions.requestExtensions()); }; const onTabsUpdated = () => { - state.dispatch(Actions.requestTabs()); + store.dispatch(Actions.requestTabs()); }; const extensionsListener = { @@ -47,16 +47,32 @@ function debugTargetListenerMiddleware(state) { }, }; + const onWorkersUpdated = () => { + store.dispatch(Actions.requestWorkers()); + }; + return next => action => { switch (action.type) { case CONNECT_RUNTIME_SUCCESS: { - action.client.addListener("tabListChanged", onTabsUpdated); + const { client } = action; + client.addListener("tabListChanged", onTabsUpdated); AddonManager.addAddonListener(extensionsListener); + client.addListener("workerListChanged", onWorkersUpdated); + client.addListener("serviceWorkerRegistrationListChanged", onWorkersUpdated); + client.addListener("processListChanged", onWorkersUpdated); + client.addListener("registration-changed", onWorkersUpdated); + client.addListener("push-subscription-modified", onWorkersUpdated); break; } case DISCONNECT_RUNTIME_START: { - state.getState().runtime.client.removeListener("tabListChanged", onTabsUpdated); + const { client } = store.getState().runtime; + client.removeListener("tabListChanged", onTabsUpdated); AddonManager.removeAddonListener(extensionsListener); + client.removeListener("workerListChanged", onWorkersUpdated); + client.removeListener("serviceWorkerRegistrationListChanged", onWorkersUpdated); + client.removeListener("processListChanged", onWorkersUpdated); + client.removeListener("registration-changed", onWorkersUpdated); + client.removeListener("push-subscription-modified", onWorkersUpdated); break; } } diff --git a/devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js b/devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js new file mode 100644 index 000000000000..45b1f1572ef7 --- /dev/null +++ b/devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + DEBUG_TARGETS, + REQUEST_EXTENSIONS_SUCCESS, +} = require("../constants"); + +/** + * This middleware converts extensions object that get from DebuggerClient.listAddons() + * to data which is used in DebugTargetItem. + */ +const extensionComponentDataMiddleware = store => next => action => { + switch (action.type) { + case REQUEST_EXTENSIONS_SUCCESS: { + action.installedExtensions = toComponentData(action.installedExtensions); + action.temporaryExtensions = toComponentData(action.temporaryExtensions); + break; + } + } + + return next(action); +}; + +function getFilePath(extension) { + // Only show file system paths, and only for temporarily installed add-ons. + if (!extension.temporarilyInstalled || + !extension.url || + !extension.url.startsWith("file://")) { + return null; + } + + // Strip a leading slash from Windows drive letter URIs. + // file:///home/foo ~> /home/foo + // file:///C:/foo ~> C:/foo + const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/; + + if (windowsRegex.test(extension.url)) { + return windowsRegex.exec(extension.url)[1]; + } + + return extension.url.slice("file://".length); +} + +function toComponentData(extensions) { + return extensions.map(extension => { + const type = DEBUG_TARGETS.EXTENSION; + const { actor, iconURL, id, manifestURL, name } = extension; + const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg"; + const location = getFilePath(extension); + const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null; + return { + name, + icon, + id, + type, + details: { + actor, + location, + manifestURL, + uuid, + }, + }; + }); +} + +module.exports = extensionComponentDataMiddleware; diff --git a/devtools/client/aboutdebugging-new/src/middleware/moz.build b/devtools/client/aboutdebugging-new/src/middleware/moz.build index c2f07da5fb1b..f06967a8bc73 100644 --- a/devtools/client/aboutdebugging-new/src/middleware/moz.build +++ b/devtools/client/aboutdebugging-new/src/middleware/moz.build @@ -4,4 +4,7 @@ DevToolsModules( 'debug-target-listener.js', + 'extension-component-data.js', + 'tab-component-data.js', + 'worker-component-data.js', ) diff --git a/devtools/client/aboutdebugging-new/src/middleware/tab-component-data.js b/devtools/client/aboutdebugging-new/src/middleware/tab-component-data.js new file mode 100644 index 000000000000..0b4835d6cec0 --- /dev/null +++ b/devtools/client/aboutdebugging-new/src/middleware/tab-component-data.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + DEBUG_TARGETS, + REQUEST_TABS_SUCCESS, +} = require("../constants"); + +/** + * This middleware converts tabs object that get from DebuggerClient.listTabs() to data + * which is used in DebugTargetItem. + */ +const tabComponentDataMiddleware = store => next => action => { + switch (action.type) { + case REQUEST_TABS_SUCCESS: { + action.tabs = toComponentData(action.tabs); + break; + } + } + + return next(action); +}; + +function toComponentData(tabs) { + return tabs.map(tab => { + const type = DEBUG_TARGETS.TAB; + const id = tab.outerWindowID; + const icon = tab.favicon + ? `data:image/png;base64,${ btoa(String.fromCharCode.apply(String, tab.favicon)) }` + : "chrome://devtools/skin/images/globe.svg"; + const name = tab.title || tab.url; + const url = tab.url; + return { + name, + icon, + id, + type, + details: { + url, + }, + }; + }); +} + +module.exports = tabComponentDataMiddleware; diff --git a/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js b/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js new file mode 100644 index 000000000000..42c886b41d52 --- /dev/null +++ b/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + DEBUG_TARGETS, + REQUEST_WORKERS_SUCCESS, + SERVICE_WORKER_FETCH_STATES, + SERVICE_WORKER_STATUSES, +} = require("../constants"); + +/** + * This middleware converts workers object that get from DebuggerClient.listAllWorkers() + * to data which is used in DebugTargetItem. + */ +const workerComponentDataMiddleware = store => next => action => { + switch (action.type) { + case REQUEST_WORKERS_SUCCESS: { + action.otherWorkers = toComponentData(action.otherWorkers); + action.serviceWorkers = toComponentData(action.serviceWorkers, true); + action.sharedWorkers = toComponentData(action.sharedWorkers); + break; + } + } + + return next(action); +}; + +function getServiceWorkerStatus(isActive, isRunning) { + if (isActive && isRunning) { + return SERVICE_WORKER_STATUSES.RUNNING; + } else if (isActive) { + return SERVICE_WORKER_STATUSES.STOPPED; + } + // We cannot get service worker registrations unless the registration is in + // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we + // display a custom state "registering" for now. See Bug 1153292. + return SERVICE_WORKER_STATUSES.REGISTERING; +} + +function toComponentData(workers, isServiceWorker) { + return workers.map(worker => { + const type = DEBUG_TARGETS.WORKER; + const id = worker.workerTargetActor; + const icon = "chrome://devtools/skin/images/debugging-workers.svg"; + let { fetch, name, registrationActor, scope } = worker; + let isActive = false; + let isRunning = false; + let status = null; + + if (isServiceWorker) { + fetch = fetch ? SERVICE_WORKER_FETCH_STATES.LISTENING + : SERVICE_WORKER_FETCH_STATES.NOT_LISTENING; + isActive = worker.active; + isRunning = !!worker.workerTargetActor; + status = getServiceWorkerStatus(isActive, isRunning); + } + + return { + name, + icon, + id, + type, + details: { + fetch, + isActive, + isRunning, + registrationActor, + scope, + status, + }, + }; + }); +} + +module.exports = workerComponentDataMiddleware; diff --git a/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js b/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js index 5ebd59ddc2b4..e5c612d1f4d1 100644 --- a/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js +++ b/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js @@ -6,16 +6,19 @@ const { CONNECT_RUNTIME_SUCCESS, - DEBUG_TARGETS, DISCONNECT_RUNTIME_SUCCESS, REQUEST_EXTENSIONS_SUCCESS, REQUEST_TABS_SUCCESS, + REQUEST_WORKERS_SUCCESS, } = require("../constants"); function RuntimeState() { return { client: null, installedExtensions: [], + otherWorkers: [], + serviceWorkers: [], + sharedWorkers: [], tabs: [], temporaryExtensions: [], }; @@ -32,14 +35,15 @@ function runtimeReducer(state = RuntimeState(), action) { } case REQUEST_EXTENSIONS_SUCCESS: { const { installedExtensions, temporaryExtensions } = action; - return Object.assign({}, state, { - installedExtensions: toExtensionComponentData(installedExtensions), - temporaryExtensions: toExtensionComponentData(temporaryExtensions), - }); + return Object.assign({}, state, { installedExtensions, temporaryExtensions }); } case REQUEST_TABS_SUCCESS: { const { tabs } = action; - return Object.assign({}, state, { tabs: toTabComponentData(tabs) }); + return Object.assign({}, state, { tabs }); + } + case REQUEST_WORKERS_SUCCESS: { + const { otherWorkers, serviceWorkers, sharedWorkers } = action; + return Object.assign({}, state, { otherWorkers, serviceWorkers, sharedWorkers }); } default: @@ -47,70 +51,6 @@ function runtimeReducer(state = RuntimeState(), action) { } } -function getExtensionFilePath(extension) { - // Only show file system paths, and only for temporarily installed add-ons. - if (!extension.temporarilyInstalled || - !extension.url || - !extension.url.startsWith("file://")) { - return null; - } - - // Strip a leading slash from Windows drive letter URIs. - // file:///home/foo ~> /home/foo - // file:///C:/foo ~> C:/foo - const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/; - - if (windowsRegex.test(extension.url)) { - return windowsRegex.exec(extension.url)[1]; - } - - return extension.url.slice("file://".length); -} - -function toExtensionComponentData(extensions) { - return extensions.map(extension => { - const type = DEBUG_TARGETS.EXTENSION; - const { actor, iconURL, id, manifestURL, name, temporarilyInstalled } = extension; - const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg"; - const location = getExtensionFilePath(extension); - const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null; - return { - name, - icon, - id, - type, - details: { - actor, - location, - manifestURL, - temporarilyInstalled, - uuid, - }, - }; - }); -} - -function toTabComponentData(tabs) { - return tabs.map(tab => { - const type = DEBUG_TARGETS.TAB; - const id = tab.outerWindowID; - const icon = tab.favicon - ? `data:image/png;base64,${ btoa(String.fromCharCode.apply(String, tab.favicon)) }` - : "chrome://devtools/skin/images/globe.svg"; - const name = tab.title || tab.url; - const url = tab.url; - return { - name, - icon, - id, - type, - details: { - url, - }, - }; - }); -} - module.exports = { RuntimeState, runtimeReducer, diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index 50499051695b..d4d08f10fe3c 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -4884,6 +4884,7 @@ exports.CSS_PROPERTIES = { "margin-box", "none", "padding-box", + "path", "polygon", "stroke-box", "unset", @@ -8269,6 +8270,7 @@ exports.CSS_PROPERTIES = { "margin-box", "none", "padding-box", + "path", "polygon", "radial-gradient", "repeating-linear-gradient", diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index 86588889e998..6a52b443aa9b 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -11,7 +11,6 @@ #include "nsIPresShell.h" #include "nsView.h" #include "nsCaret.h" -#include "nsEditorCID.h" #include "nsLayoutCID.h" #include "nsITextControlFrame.h" #include "nsContentCreatorFunctions.h" diff --git a/dom/security/fuzztest/csp_fuzzer.cpp b/dom/security/fuzztest/csp_fuzzer.cpp new file mode 100644 index 000000000000..73affaf47adf --- /dev/null +++ b/dom/security/fuzztest/csp_fuzzer.cpp @@ -0,0 +1,45 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "FuzzingInterface.h" +#include "nsCSPContext.h" +#include "nsNetUtil.h" +#include "nsStringFwd.h" + +static int +LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + nsresult ret; + nsCOMPtr selfURI; + ret = NS_NewURI(getter_AddRefs(selfURI), "http://selfuri.com"); + if (ret != NS_OK) + return 0; + + mozilla::OriginAttributes attrs; + nsCOMPtr selfURIPrincipal = + mozilla::BasePrincipal::CreateCodebasePrincipal(selfURI, attrs); + if (!selfURIPrincipal) + return 0; + + nsCOMPtr csp = + do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &ret); + if (ret != NS_OK) + return 0; + + ret = csp->SetRequestContext(nullptr, selfURIPrincipal); + if (ret != NS_OK) + return 0; + + NS_ConvertASCIItoUTF16 policy(reinterpret_cast(data), size); + if (!policy.get()) + return 0; + csp->AppendPolicy(policy, false, false); + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(nullptr, LLVMFuzzerTestOneInput, ContentSecurityPolicyParser); + diff --git a/dom/security/fuzztest/csp_fuzzer.dict b/dom/security/fuzztest/csp_fuzzer.dict new file mode 100644 index 000000000000..37a167122282 --- /dev/null +++ b/dom/security/fuzztest/csp_fuzzer.dict @@ -0,0 +1,95 @@ +### dom/security/nsCSPParser.cpp +# tokens +":" +";" +"/" +"+" +"-" +"." +"_" +"~" +"*" +"'" +"#" +"?" +"%" +"!" +"$" +"&" +"(" +")" +"=" +"@" + +### https://www.w3.org/TR/{CSP,CSP2,CSP3}/ +# directive names +"default-src" +"script-src" +"object-src" +"style-src" +"img-src" +"media-src" +"frame-src" +"font-src" +"connect-src" +"report-uri" +"frame-ancestors" +"reflected-xss" +"base-uri" +"form-action" +"manifest-src" +"upgrade-insecure-requests" +"child-src" +"block-all-mixed-content" +"require-sri-for" +"sandbox" +"worker-src" +"plugin-types" +"disown-opener" +"report-to" + +# directive values +"'self'" +"'unsafe-inline'" +"'unsafe-eval'" +"'none'" +"'strict-dynamic'" +"'unsafe-hashed-attributes'" +"'nonce-AA=='" +"'sha256-fw=='" +"'sha384-/w=='" +"'sha512-//8='" + +# subresources +"a" +"audio" +"embed" +"iframe" +"img" +"link" +"object" +"script" +"source" +"style" +"track" +"video" + +# sandboxing flags +"allow-forms" +"allow-pointer-lock" +"allow-popups" +"allow-same-origin" +"allow-scripts" +"allow-top-navigation" + +# URI components +"https:" +"ws:" +"blob:" +"data:" +"filesystem:" +"javascript:" +"http://" +"selfuri.com" +"127.0.0.1" +"::1" diff --git a/dom/security/fuzztest/moz.build b/dom/security/fuzztest/moz.build new file mode 100644 index 000000000000..400f8d032efb --- /dev/null +++ b/dom/security/fuzztest/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library('FuzzingDOMSecurity') + +LOCAL_INCLUDES += [ + '/dom/security', + '/netwerk/base', +] + +include('/tools/fuzzing/libfuzzer-config.mozbuild') + +SOURCES += [ + 'csp_fuzzer.cpp' +] + +FINAL_LIBRARY = 'xul-gtest' + diff --git a/dom/security/moz.build b/dom/security/moz.build index ebfb490a23f2..bf7b75214f02 100644 --- a/dom/security/moz.build +++ b/dom/security/moz.build @@ -51,3 +51,11 @@ LOCAL_INCLUDES += [ '/netwerk/base', '/netwerk/protocol/data', # for nsDataHandler.h ] + +include('/tools/fuzzing/libfuzzer-config.mozbuild') + +if CONFIG['FUZZING_INTERFACES']: + TEST_DIRS += [ + 'fuzztest' + ] + diff --git a/dom/serviceworkers/ServiceWorkerRegistration.cpp b/dom/serviceworkers/ServiceWorkerRegistration.cpp index 16d1c8e3b0db..14ab1e4e4a0a 100644 --- a/dom/serviceworkers/ServiceWorkerRegistration.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp @@ -222,17 +222,6 @@ ServiceWorkerRegistration::Update(ErrorResult& aRv) return nullptr; } - if (RefPtr serviceWorkerGlobal = - do_QueryObject(global)) { - WorkerPrivate* wp; - if (serviceWorkerGlobal->Registration() == this && - (wp = GetCurrentThreadWorkerPrivate()) && - wp->IsLoadingWorkerScript()) { - outer->MaybeResolve(*this); - return outer.forget(); - } - } - RefPtr self = this; mPendingUpdatePromises += 1; diff --git a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp index d9f5a5b92507..78ec762daf21 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp @@ -826,8 +826,13 @@ ServiceWorkerRegistrationWorkerThread::Update(ServiceWorkerRegistrationCallback& return; } - // This is ensured by the binding layer. - MOZ_ASSERT(!workerRef->Private()->IsLoadingWorkerScript()); + // Avoid infinite update loops by ignoring update() calls during top + // level script evaluation. See: + // https://github.com/slightlyoff/ServiceWorker/issues/800 + if (workerRef->Private()->IsLoadingWorkerScript()) { + aSuccessCB(mDescriptor); + return; + } auto promise = MakeRefPtr(__func__); auto holder = diff --git a/dom/svg/SVGPathData.cpp b/dom/svg/SVGPathData.cpp index c56182400494..79b76a43961e 100644 --- a/dom/svg/SVGPathData.cpp +++ b/dom/svg/SVGPathData.cpp @@ -560,7 +560,8 @@ SVGPathData::BuildPathForMeasuring() const SVGPathData::BuildPath(const nsTArray& aPath, PathBuilder* aBuilder, uint8_t aStrokeLineCap, - Float aStrokeWidth) + Float aStrokeWidth, + float aZoomFactor) { if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) { return nullptr; // paths without an initial moveto are invalid @@ -592,6 +593,10 @@ SVGPathData::BuildPath(const nsTArray& aPath, Point cp1, cp2; // previous bezier's control points Point tcp1, tcp2; // temporaries + auto scale = [aZoomFactor](const Point& p) { + return Point(p.x * aZoomFactor, p.y * aZoomFactor); + }; + // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, // then cp2 is its second control point. If the previous segment was a // quadratic curve, then cp1 is its (only) control point. @@ -610,7 +615,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; const Point& p = toGfxPoint(cmd.move_to.point); pathStart = segEnd = cmd.move_to.absolute ? p : segStart + p; - aBuilder->MoveTo(segEnd); + aBuilder->MoveTo(scale(segEnd)); subpathHasLength = false; break; } @@ -619,7 +624,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, segEnd = cmd.line_to.absolute ? p : segStart + p; if (segEnd != segStart) { subpathHasLength = true; - aBuilder->LineTo(segEnd); + aBuilder->LineTo(scale(segEnd)); } break; } @@ -636,7 +641,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { subpathHasLength = true; - aBuilder->BezierTo(cp1, cp2, segEnd); + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); } break; @@ -655,7 +660,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, if (segEnd != segStart || segEnd != cp1) { subpathHasLength = true; - aBuilder->BezierTo(tcp1, tcp2, segEnd); + aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); } break; @@ -669,12 +674,12 @@ SVGPathData::BuildPath(const nsTArray& aPath, if (segEnd != segStart) { subpathHasLength = true; if (radii.x == 0.0f || radii.y == 0.0f) { - aBuilder->LineTo(segEnd); + aBuilder->LineTo(scale(segEnd)); } else { nsSVGArcConverter converter(segStart, segEnd, radii, arc.angle, arc.large_arc_flag, arc.sweep_flag); while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { - aBuilder->BezierTo(cp1, cp2, segEnd); + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); } } } @@ -689,7 +694,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, if (segEnd != segStart) { subpathHasLength = true; - aBuilder->LineTo(segEnd); + aBuilder->LineTo(scale(segEnd)); } break; @@ -702,7 +707,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, if (segEnd != segStart) { subpathHasLength = true; - aBuilder->LineTo(segEnd); + aBuilder->LineTo(scale(segEnd)); } break; @@ -718,7 +723,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { subpathHasLength = true; - aBuilder->BezierTo(cp1, cp2, segEnd); + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); } break; @@ -734,7 +739,7 @@ SVGPathData::BuildPath(const nsTArray& aPath, if (segEnd != segStart || segEnd != cp1) { subpathHasLength = true; - aBuilder->BezierTo(tcp1, tcp2, segEnd); + aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); } break; } diff --git a/dom/svg/SVGPathData.h b/dom/svg/SVGPathData.h index 25a2cdb88498..45e8bc86c24f 100644 --- a/dom/svg/SVGPathData.h +++ b/dom/svg/SVGPathData.h @@ -178,7 +178,8 @@ public: BuildPath(const nsTArray& aPath, PathBuilder* aBuilder, uint8_t aCapStyle, - Float aStrokeWidth); + Float aStrokeWidth, + float aZoomFactor = 1.0); const_iterator begin() const { return mData.Elements(); } const_iterator end() const { return mData.Elements() + mData.Length(); } diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 93aa66e7ba01..c46fd01cd3f3 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -677,7 +677,6 @@ SharedWorkerGlobalScope::Close() NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope, mClients, mRegistration) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerGlobalScope) - NS_INTERFACE_MAP_ENTRY_CONCRETE(ServiceWorkerGlobalScope) NS_INTERFACE_MAP_END_INHERITING(WorkerGlobalScope) NS_IMPL_ADDREF_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope) diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 15ae99bcad05..c0712ed6ac4d 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -302,9 +302,6 @@ public: IMPL_EVENT_HANDLER(connect) }; -#define NS_DOM_SERVICEWORKERGLOBALSCOPE_IID \ - {0x552bfa7e, 0x0dd5, 0x4e94, {0xa0, 0x43, 0xff, 0x34, 0x6b, 0x6e, 0x04, 0x46}} - class ServiceWorkerGlobalScope final : public WorkerGlobalScope { const nsString mScope; @@ -314,7 +311,6 @@ class ServiceWorkerGlobalScope final : public WorkerGlobalScope ~ServiceWorkerGlobalScope(); public: - NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_SERVICEWORKERGLOBALSCOPE_IID) NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope) @@ -359,8 +355,6 @@ public: void EventListenerAdded(nsAtom* aType) override; }; -NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerGlobalScope, NS_DOM_SERVICEWORKERGLOBALSCOPE_IID) - class WorkerDebuggerGlobalScope final : public DOMEventTargetHelper, public nsIGlobalObject { diff --git a/editor/moz.build b/editor/moz.build index 35f5100f9fcf..9423893ccdfb 100644 --- a/editor/moz.build +++ b/editor/moz.build @@ -31,10 +31,6 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'editor' -EXPORTS += [ - 'nsEditorCID.h', -] - TESTING_JS_MODULES += [ 'AsyncSpellCheckTestHelper.jsm', ] diff --git a/editor/nsEditorCID.h b/editor/nsEditorCID.h deleted file mode 100644 index 4b288771ee4a..000000000000 --- a/editor/nsEditorCID.h +++ /dev/null @@ -1,25 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef NSEDITORCID_H___ - -#define NS_EDITOR_CID \ -{/* {D3DE3431-8A75-11d2-918C-0080C8E44DB5}*/ \ -0xd3de3431, 0x8a75, 0x11d2, \ -{ 0x91, 0x8c, 0x0, 0x80, 0xc8, 0xe4, 0x4d, 0xb5 } } - -#define NS_TEXTEDITOR_CID \ -{/* {e197cc01-cfe1-11d4-8eb0-87ae406dfd3f}*/ \ -0xe197cc01, 0xcfe1, 0x11d4, \ -{ 0x8e, 0xb0, 0x87, 0xae, 0x40, 0x6d, 0xfd, 0x3f } } - -#define NS_HTMLEDITOR_CID \ -{/* {ed0244e0-c144-11d2-8f4c-006008159b0c}*/ \ -0xed0244e0, 0xc144, 0x11d2, \ -{ 0x8f, 0x4c, 0x0, 0x60, 0x08, 0x15, 0x9b, 0x0c } } - -#endif //NSEDITORCID_H___ - - - diff --git a/js/src/jit-test/tests/wasm/ion-args.js b/js/src/jit-test/tests/wasm/ion-args.js index 2bfa17468995..194de924840c 100644 --- a/js/src/jit-test/tests/wasm/ion-args.js +++ b/js/src/jit-test/tests/wasm/ion-args.js @@ -10,10 +10,17 @@ let { exports } = wasmEvalText(`(module (func (export "f64") (result f64) (param f64) get_local 0 ) + + (func (export "mixed_args") (result f64) + (param i32) (param i32) (param i32) (param i32) (param i32) ;; 5 i32 + (param $f64 f64) ;; 1 f64 + (param i32) + get_local $f64 + ) )`); const options = getJitCompilerOptions(); -const jitThreshold = options['ion.warmup.trigger'] * 2; +const jitThreshold = options['ion.warmup.trigger'] * 2 + 2; let coercions = { i32(x) { return x|0; }, @@ -38,23 +45,93 @@ function call(func, coercion, arg) { } } -const inputs = [ - 42, - 3.5, - -0, - -Infinity, - 2**32, - true, - Symbol(), - undefined, - null, - {}, - { valueOf() { return 13.37; } }, - "bonjour" -]; +// Test misc kinds of arguments. +(function() { + const inputs = [ + 42, + 3.5, + -0, + -Infinity, + 2**32, + true, + Symbol(), + undefined, + null, + {}, + { valueOf() { return 13.37; } }, + "bonjour" + ]; -for (let arg of inputs) { - for (let func of ['i32', 'f32', 'f64']) { - call(exports[func], coercions[func], arg); + for (let arg of inputs) { + for (let func of ['i32', 'f32', 'f64']) { + call(exports[func], coercions[func], arg); + } } +})(); + +// Test mixup of float and int arguments. +(function() { + for (let i = 0; i < 10; i++) { + assertEq(exports.mixed_args(i, i+1, i+2, i+3, i+4, i+0.5, i+5), i+0.5); + } +})(); + +// Test high number of arguments. +// All integers. +let {func} = wasmEvalText(`(module + (func (export "func") (result i32) + ${Array(32).join('(param i32)')} + (param $last i32) + get_local $last + ) +)`).exports; + +(function() { + for (let i = 0; i < 10; i++) { + assertEq(func(i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9, i+10, i+11, i+12, i+13, i+14, i+15, + i+16, i+17, i+18, i+19, i+20, i+21, i+22, i+23, i+24, i+25, i+26, i+27, i+28, i+29, i+30, i+31 + ), i+31); + } +})(); + +// All floats. +func = wasmEvalText(`(module + (func (export "func") (result i32) + ${Array(32).join('(param f64)')} + (param $last i32) + get_local $last + ) +)`).exports.func; + +(function() { + for (let i = 0; i < 10; i++) { + assertEq(func(i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9, i+10, i+11, i+12, i+13, i+14, i+15, + i+16, i+17, i+18, i+19, i+20, i+21, i+22, i+23, i+24, i+25, i+26, i+27, i+28, i+29, i+30, i+31 + ), i+31); + } +})(); + +// Mix em up! 1 i32, then 1 f32, then 1 f64, and again up to 32 args. +let params = []; +for (let i = 0; i < 32; i++) { + params.push((i % 3 == 0) ? 'i32' : + (i % 3 == 1) ? 'f32' : + 'f64' + ); } + +func = wasmEvalText(`(module + (func (export "func") (result i32) + ${Array(32).join('(param f64)')} + (param $last i32) + get_local $last + ) +)`).exports.func; + +(function() { + for (let i = 0; i < 10; i++) { + assertEq(func(i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9, i+10, i+11, i+12, i+13, i+14, i+15, + i+16, i+17, i+18, i+19, i+20, i+21, i+22, i+23, i+24, i+25, i+26, i+27, i+28, i+29, i+30, i+31 + ), i+31); + } +})(); diff --git a/js/src/jit-test/tests/wasm/ion-error-i64.js b/js/src/jit-test/tests/wasm/ion-error-i64.js index 867930f70f27..831bf47a9ef7 100644 --- a/js/src/jit-test/tests/wasm/ion-error-i64.js +++ b/js/src/jit-test/tests/wasm/ion-error-i64.js @@ -10,8 +10,6 @@ const TRIGGER = options['ion.warmup.trigger'] + 10; const ITER = 2 * TRIGGER; const EXCEPTION_ITER = ITER - 2; -enableGeckoProfiling(); - var instance = wasmEvalText(`(module (func $add (export "add") (result i32) (param i32) (param i32) get_local 0 @@ -25,8 +23,22 @@ var instance = wasmEvalText(`(module call $add i64.extend_s/i32 ) + + (func $add_two_i64 (export "add_two_i64") (result i64) (param i64) (param i64) + get_local 0 + get_local 1 + i64.add + ) )`).exports; +(function() { + // In ion-eager mode, make sure we don't try to inline a function that + // takes or returns i64 arguments. + assertErrorMessage(() => instance.add_two_i64(0, 1), TypeError, /cannot pass i64 to or from JS/); +})(); + +enableGeckoProfiling(); + var callToMain; function main() { @@ -73,7 +85,9 @@ function main() { assertEq(+lines[0], arrayCallLine); assertEq(+lines[1], callToMain); } else if ((i % 2) == 0) { + // Regular call to wasm add on 32 bits integers. assertEqPreciseStacks(profilingStack, [ + ['', '0', ''], // supa-dupa fast path ['', '>', '0,>', '>', ''], // fast path ['', '!>', '0,!>', '!>', ''], // slow path ]); diff --git a/js/src/jit-test/tests/wasm/ion-error-ool.js b/js/src/jit-test/tests/wasm/ion-error-ool.js index 1316d4f4c116..6b8162f7289b 100644 --- a/js/src/jit-test/tests/wasm/ion-error-ool.js +++ b/js/src/jit-test/tests/wasm/ion-error-ool.js @@ -12,6 +12,7 @@ const EXCEPTION_ITER = TRIGGER + 5; const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', '']; const FAST_ENTRY_STACK = ['', '>', '0,>', '>', '']; +const INLINED_CALL_STACK = ['', '0', '']; const FAST_OOL_ENTRY_STACK = ['', '>', '<,>', 'ool>,>', '<,>', '>', '0,>', '>', '']; const EXCEPTION_ENTRY_STACK = ['', '>', '<,>', 'ool>,>', '<,>', '>', '']; @@ -44,7 +45,7 @@ for (let type of ['i32', 'f32', 'f64']) { for (var i = 0; i < ITER; i++) { startProfiling(); loopBody(i + 1, i + EXCEPTION_ITER + 1); - assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); + assertEqPreciseStacks(endProfiling(), [INLINED_CALL_STACK, FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); if (i === EXCEPTION_ITER) { x = { valueOf: function innerValueOf() { throw new Error("ph34r"); }}; diff --git a/js/src/jit-test/tests/wasm/ion-error-throw.js b/js/src/jit-test/tests/wasm/ion-error-throw.js index fe6c6ec553e5..1ff9e16bf945 100644 --- a/js/src/jit-test/tests/wasm/ion-error-throw.js +++ b/js/src/jit-test/tests/wasm/ion-error-throw.js @@ -25,6 +25,7 @@ let { add } = wasmEvalText(`(module const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', '']; const FAST_ENTRY_STACK = ['', '>', '0,>', '>', '']; +const INLINED_CALL_STACK = ['', '0', '']; function main() { for (let i = 0; i < 50; i++) { @@ -37,7 +38,7 @@ function main() { assertStackTrace(e, ['wasm-function[0]', 'main', '']); } let stack = endProfiling(); - assertEqPreciseStacks(stack, [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); + assertEqPreciseStacks(stack, [INLINED_CALL_STACK, FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); } } diff --git a/js/src/jit-test/tests/wasm/ion-lazy-tables.js b/js/src/jit-test/tests/wasm/ion-lazy-tables.js index c37d7051ac91..c6bbb7d1e7d6 100644 --- a/js/src/jit-test/tests/wasm/ion-lazy-tables.js +++ b/js/src/jit-test/tests/wasm/ion-lazy-tables.js @@ -12,6 +12,8 @@ const EXCEPTION_ITER = TRIGGER + 5; const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', '']; const FAST_ENTRY_STACK = ['', '>', '0,>', '>', '']; +const INLINED_CALL_STACK = ['', '0', '']; +const EXPECTED_STACKS = [SLOW_ENTRY_STACK, FAST_ENTRY_STACK, INLINED_CALL_STACK]; function main() { var { table } = wasmEvalText(`(module @@ -27,7 +29,7 @@ function main() { for (var i = 0; i < ITER; i++) { startProfiling(); assertEq(table.get(0)(i, i+1), i*2+1); - assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); + assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS); } } @@ -50,13 +52,13 @@ function withTier2() { i++; startProfiling(); assertEq(table.get(0)(i, i+1), i*2+1); - assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); + assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS); } while (!wasmHasTier2CompilationCompleted(module)); for (i = 0; i < ITER; i++) { startProfiling(); assertEq(table.get(0)(i, i+1), i*2+1); - assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]); + assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS); } setJitCompilerOption('wasm.delay-tier2', 0); diff --git a/js/src/jit-test/tests/wasm/profiling.js b/js/src/jit-test/tests/wasm/profiling.js index c37964600966..dbb54e739218 100644 --- a/js/src/jit-test/tests/wasm/profiling.js +++ b/js/src/jit-test/tests/wasm/profiling.js @@ -5,7 +5,12 @@ const Module = WebAssembly.Module; const Instance = WebAssembly.Instance; const Table = WebAssembly.Table; -const { assertEqImpreciseStacks, startProfiling, endProfiling } = WasmHelpers; +const { + assertEqImpreciseStacks, + assertEqPreciseStacks, + startProfiling, + endProfiling +} = WasmHelpers; function test(code, importObj, expectedStacks) { @@ -380,3 +385,33 @@ for (let type of ['f32', 'f64']) { disableSingleStepProfiling(); disableGeckoProfiling(); })(); + +// Ion->wasm calls. +let func = wasmEvalText(`(module + (func $inner (result i32) (param i32) (param i32) + get_local 0 + get_local 1 + i32.add + ) + (func (export "add") (result i32) (param i32) (param i32) + get_local 0 + get_local 1 + call $inner + ) +)`).exports.add; + +(function() { + enableGeckoProfiling(); + // 10 is enough in ion eager mode. + for (let i = 0; i < 10; i++) { + enableSingleStepProfiling(); + let res = func(i - 1, i + 1); + assertEqPreciseStacks(disableSingleStepProfiling(), [ + ['', '>', '1,>', '0,1,>' , '1,>', '>', ''], // slow entry + ['', '!>', '1,!>', '0,1,!>' , '1,!>', '!>', ''], // fast entry + ['', '1', '0,1' , '1', ''], // inlined jit call + ]); + assertEq(res, i+i); + } + disableGeckoProfiling(); +})(); diff --git a/js/src/jit/Bailouts.h b/js/src/jit/Bailouts.h index dc5a3d1e56a8..eee583b920c9 100644 --- a/js/src/jit/Bailouts.h +++ b/js/src/jit/Bailouts.h @@ -108,7 +108,7 @@ static const uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2; static uint8_t* const FAKE_EXITFP_FOR_BAILOUT = reinterpret_cast(FAKE_EXITFP_FOR_BAILOUT_ADDR); -static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & JitActivation::ExitFpWasmBit), +static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitOrJitEntryFPTag), "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged wasm exit fp"); // BailoutStack is an architecture specific pointer to the stack, given by the diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 0d3c3a9a7783..00f39ca17cb1 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -54,6 +54,7 @@ #include "vm/TraceLogging.h" #include "vm/TypedArrayObject.h" #include "vtune/VTuneWrapper.h" +#include "wasm/WasmStubs.h" #include "builtin/Boolean-inl.h" #include "jit/MacroAssembler-inl.h" @@ -13556,6 +13557,133 @@ CodeGenerator::visitGetPrototypeOf(LGetPrototypeOf* lir) masm.bind(ool->rejoin()); } +template +void +CodeGenerator::emitIonToWasmCallBase(LIonToWasmCallBase* lir) +{ + wasm::JitCallStackArgVector stackArgs; + masm.propagateOOM(stackArgs.reserve(lir->numOperands())); + if (masm.oom()) + return; + + const wasm::FuncExport& funcExport = lir->mir()->funcExport(); + const wasm::FuncType& sig = funcExport.funcType(); + + ABIArgGenerator abi; + for (size_t i = 0; i < lir->numOperands(); i++) { + MIRType argMir; + switch (sig.args()[i].code()) { + case wasm::ValType::I32: + case wasm::ValType::F32: + case wasm::ValType::F64: + argMir = ToMIRType(sig.args()[i]); + break; + case wasm::ValType::I64: + case wasm::ValType::Ref: + case wasm::ValType::AnyRef: + // Don't forget to trace GC type arguments in TraceJitExitFrames + // when they're enabled. + MOZ_CRASH("unexpected argument type when calling from ion to wasm"); + } + + ABIArg arg = abi.next(argMir); + switch (arg.kind()) { + case ABIArg::GPR: + case ABIArg::FPU: { + MOZ_ASSERT(ToAnyRegister(lir->getOperand(i)) == arg.reg()); + stackArgs.infallibleEmplaceBack(wasm::JitCallStackArg()); + break; + } + case ABIArg::Stack: { + const LAllocation* larg = lir->getOperand(i); + if (larg->isConstant()) + stackArgs.infallibleEmplaceBack(ToInt32(larg)); + else if (larg->isGeneralReg()) + stackArgs.infallibleEmplaceBack(ToRegister(larg)); + else if (larg->isFloatReg()) + stackArgs.infallibleEmplaceBack(ToFloatRegister(larg)); + else + stackArgs.infallibleEmplaceBack(ToAddress(larg)); + break; + } +#ifdef JS_CODEGEN_REGISTER_PAIR + case ABIArg::GPR_PAIR: { + MOZ_CRASH("no way to pass i64, and wasm uses hardfp for function calls"); + } +#endif + case ABIArg::Uninitialized: { + MOZ_CRASH("Uninitialized ABIArg kind"); + } + } + } + + switch (sig.ret().code()) { + case wasm::ExprType::Void: + MOZ_ASSERT(lir->mir()->type() == MIRType::Value); + break; + case wasm::ExprType::I32: + MOZ_ASSERT(lir->mir()->type() == MIRType::Int32); + MOZ_ASSERT(ToRegister(lir->output()) == ReturnReg); + break; + case wasm::ExprType::F32: + MOZ_ASSERT(lir->mir()->type() == MIRType::Float32); + MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnFloat32Reg); + break; + case wasm::ExprType::F64: + MOZ_ASSERT(lir->mir()->type() == MIRType::Double); + MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnDoubleReg); + break; + case wasm::ExprType::Ref: + case wasm::ExprType::AnyRef: + case wasm::ExprType::I64: + // Don't forget to trace GC type return value in TraceJitExitFrames + // when they're enabled. + MOZ_CRASH("unexpected return type when calling from ion to wasm"); + case wasm::ExprType::Limit: + MOZ_CRASH("Limit"); + } + + bool profilingEnabled = isProfilerInstrumentationEnabled(); + WasmInstanceObject* instObj = lir->mir()->instanceObject(); + + bool wasmGcEnabled = false; +#ifdef ENABLE_WASM_GC + wasmGcEnabled = gen->options.wasmGcEnabled(); +#endif + + Register scratch = ToRegister(lir->temp()); + + uint32_t callOffset; + GenerateDirectCallFromJit(masm, + funcExport, + instObj->instance(), + stackArgs, + profilingEnabled, + wasmGcEnabled, + scratch, + &callOffset); + + // Add the instance object to the constant pool, so it is transferred to + // the owning IonScript and so that it gets traced as long as the IonScript + // lives. + + uint32_t unused; + masm.propagateOOM(graph.addConstantToPool(ObjectValue(*instObj), &unused)); + + markSafepointAt(callOffset, lir); +} + +void +CodeGenerator::visitIonToWasmCall(LIonToWasmCall* lir) +{ + emitIonToWasmCallBase(lir); +} +void +CodeGenerator::visitIonToWasmCallV(LIonToWasmCallV* lir) +{ + emitIonToWasmCallBase(lir); +} + static_assert(!std::is_polymorphic::value, "CodeGenerator should not have any virtual methods"); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 561a1b93baf7..6de8676900bf 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -219,6 +219,9 @@ class CodeGenerator final : public CodeGeneratorSpecific void emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck); + template + void emitIonToWasmCallBase(LIonToWasmCallBase* lir); + IonScriptCounts* maybeCreateScriptCounts(); // This function behaves like testValueTruthy with the exception that it can diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 478410bba9a7..0dac97b67805 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -322,6 +322,9 @@ JitCompileOptions::JitCompileOptions() : cloneSingletons_(false), profilerSlowAssertionsEnabled_(false), offThreadCompilationAvailable_(false) +#ifdef ENABLE_WASM_GC + , wasmGcEnabled_(false) +#endif { } @@ -331,4 +334,7 @@ JitCompileOptions::JitCompileOptions(JSContext* cx) profilerSlowAssertionsEnabled_ = cx->runtime()->geckoProfiler().enabled() && cx->runtime()->geckoProfiler().slowAssertionsEnabled(); offThreadCompilationAvailable_ = OffThreadCompilationAvailable(cx); +#ifdef ENABLE_WASM_GC + wasmGcEnabled_ = cx->options().wasmGc(); +#endif } diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index af3b1bafff4d..a1f13e5a885e 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -138,10 +138,19 @@ class JitCompileOptions return offThreadCompilationAvailable_; } +#ifdef ENABLE_WASM_GC + bool wasmGcEnabled() const { + return wasmGcEnabled_; + } +#endif + private: bool cloneSingletons_; bool profilerSlowAssertionsEnabled_; bool offThreadCompilationAvailable_; +#ifdef ENABLE_WASM_GC + bool wasmGcEnabled_; +#endif }; } // namespace jit diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index e6b14f61af33..926b01e502ee 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -11703,7 +11703,7 @@ IonBuilder::setPropTryTypedObject(bool* emitted, MDefinition* obj, TypedObjectPrediction fieldPrediction; size_t fieldOffset; size_t fieldIndex; - bool fieldMutable; + bool fieldMutable = false; if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction, &fieldIndex, &fieldMutable)) return Ok(); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 91ba2e4f4d14..cca90ee6a58a 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -750,6 +750,7 @@ class IonBuilder InliningResult inlineObjectHasPrototype(CallInfo& callInfo); InliningResult inlineFinishBoundFunctionInit(CallInfo& callInfo); InliningResult inlineIsPackedArray(CallInfo& callInfo); + InliningResult inlineWasmCall(CallInfo& callInfo, JSFunction* target); // Testing functions. InliningResult inlineBailout(CallInfo& callInfo); diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index c094b6c8beb4..6ff3f68ee6a3 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -418,7 +418,7 @@ enum class IntConversionInputKind { // The ordering of this enumeration is important: Anything < Value is a // specialized type. Furthermore, anything < String has trivial conversion to // a number. -enum class MIRType +enum class MIRType: uint8_t { Undefined, Null, @@ -462,7 +462,7 @@ enum class MIRType static inline bool IsSimdType(MIRType type) { - return ((unsigned(type) >> VECTOR_SCALE_SHIFT) & VECTOR_SCALE_MASK) != 0; + return ((uint8_t(type) >> VECTOR_SCALE_SHIFT) & VECTOR_SCALE_MASK) != 0; } static inline MIRType diff --git a/js/src/jit/JSJitFrameIter.cpp b/js/src/jit/JSJitFrameIter.cpp index b1182f1da64c..2a3a72aa48a8 100644 --- a/js/src/jit/JSJitFrameIter.cpp +++ b/js/src/jit/JSJitFrameIter.cpp @@ -32,14 +32,15 @@ JSJitFrameIter::JSJitFrameIter(const JitActivation* activation) } } -JSJitFrameIter::JSJitFrameIter(const JitActivation* activation, uint8_t* fp) +JSJitFrameIter::JSJitFrameIter(const JitActivation* activation, FrameType frameType, uint8_t* fp) : current_(fp), - type_(JitFrame_JSJitToWasm), + type_(frameType), returnAddressToFp_(nullptr), frameSize_(0), cachedSafepointIndex_(nullptr), activation_(activation) { + MOZ_ASSERT(type_ == JitFrame_JSJitToWasm || type_ == JitFrame_Exit); MOZ_ASSERT(!activation_->bailoutData()); MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI); } diff --git a/js/src/jit/JSJitFrameIter.h b/js/src/jit/JSJitFrameIter.h index 07b1013fbe9b..e222b3e1a832 100644 --- a/js/src/jit/JSJitFrameIter.h +++ b/js/src/jit/JSJitFrameIter.h @@ -119,7 +119,7 @@ class JSJitFrameIter // A constructor specialized for jit->wasm frames, which starts at a // specific FP. - JSJitFrameIter(const JitActivation* activation, uint8_t* fp); + JSJitFrameIter(const JitActivation* activation, FrameType frameType, uint8_t* fp); // Used only by DebugModeOSRVolatileJitFrameIter. void exchangeReturnAddressIfMatch(uint8_t* oldAddr, uint8_t* newAddr) { diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 7e9f8778ee42..eff16406be10 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -1170,6 +1170,12 @@ TraceJitExitFrame(JSTracer* trc, const JSJitFrameIter& frame) return; } + if (frame.isExitFrameLayout()) { + // Nothing to do: we can't have object arguments (yet!) and the callee + // is traced elsewhere. + return; + } + if (frame.isBareExit()) { // Nothing to trace. Fake exit frame pushed for VM functions with // nothing to trace on the stack. diff --git a/js/src/jit/JitFrames.h b/js/src/jit/JitFrames.h index 5aa8fecbf037..20e1b1731f0a 100644 --- a/js/src/jit/JitFrames.h +++ b/js/src/jit/JitFrames.h @@ -471,18 +471,19 @@ class IonICCallFrameLayout : public CommonFrameLayout enum class ExitFrameType : uint8_t { - CallNative = 0x0, - ConstructNative = 0x1, - IonDOMGetter = 0x2, - IonDOMSetter = 0x3, - IonDOMMethod = 0x4, - IonOOLNative = 0x5, - IonOOLProxy = 0x6, - WasmJitEntry = 0x7, - InterpreterStub = 0xFC, - VMFunction = 0xFD, - LazyLink = 0xFE, - Bare = 0xFF, + CallNative = 0x0, + ConstructNative = 0x1, + IonDOMGetter = 0x2, + IonDOMSetter = 0x3, + IonDOMMethod = 0x4, + IonOOLNative = 0x5, + IonOOLProxy = 0x6, + WasmGenericJitEntry = 0x7, + DirectWasmJitCall = 0x8, + InterpreterStub = 0xFC, + VMFunction = 0xFD, + LazyLink = 0xFE, + Bare = 0xFF, }; // GC related data used to keep alive data surrounding the Exit frame. @@ -855,10 +856,10 @@ class InterpreterStubExitFrameLayout : public CalledFromJitExitFrameLayout static ExitFrameType Type() { return ExitFrameType::InterpreterStub; } }; -class WasmExitFrameLayout : CalledFromJitExitFrameLayout +class WasmGenericJitEntryFrameLayout : CalledFromJitExitFrameLayout { public: - static ExitFrameType Type() { return ExitFrameType::WasmJitEntry; } + static ExitFrameType Type() { return ExitFrameType::WasmGenericJitEntry; } }; template<> @@ -867,7 +868,7 @@ ExitFrameLayout::is() { return is() || is() || - is(); + is(); } template <> @@ -880,6 +881,15 @@ ExitFrameLayout::as() return reinterpret_cast(sp); } +class DirectWasmJitCallFrameLayout +{ + protected: // silence clang warning about unused private fields + ExitFooterFrame footer_; + ExitFrameLayout exit_; + public: + static ExitFrameType Type() { return ExitFrameType::DirectWasmJitCall; } +}; + class ICStub; class JitStubFrameLayout : public CommonFrameLayout diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index ff35b60b0d05..d02379fa0abf 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -5343,5 +5343,58 @@ LIRGenerator::visitUnknownValue(MUnknownValue* ins) MOZ_CRASH("Can not lower unknown value."); } +void +LIRGenerator::visitIonToWasmCall(MIonToWasmCall* ins) +{ + // The instruction needs a temp register: + // - that's not the FramePointer, since wasm is going to use it in the + // function. + // - that's not aliasing an input register. + LDefinition scratch = tempFixed(ABINonArgReg0); + + // Also prevent register allocation from using wasm's FramePointer, in + // non-profiling mode. + LDefinition fp = gen->isProfilerInstrumentationEnabled() + ? LDefinition::BogusTemp() + : tempFixed(FramePointer); + + // Note that since this is a LIR call instruction, regalloc will prevent + // the use*AtStart below from reusing any of the temporaries. + + LInstruction* lir; + if (ins->type() == MIRType::Value) + lir = allocateVariadic(ins->numOperands(), scratch, fp); + else + lir = allocateVariadic(ins->numOperands(), scratch, fp); + if (!lir) { + abort(AbortReason::Alloc, "Couldn't allocate for LIonToWasmCallBase"); + return; + } + + ABIArgGenerator abi; + for (unsigned i = 0; i < ins->numOperands(); i++) { + MDefinition* argDef = ins->getOperand(i); + ABIArg arg = abi.next(ToMIRType(argDef->type())); + switch (arg.kind()) { + case ABIArg::GPR: + case ABIArg::FPU: + lir->setOperand(i, useFixedAtStart(argDef, arg.reg())); + break; + case ABIArg::Stack: + lir->setOperand(i, useAtStart(argDef)); + break; +#ifdef JS_CODEGEN_REGISTER_PAIR + case ABIArg::GPR_PAIR: + MOZ_CRASH("no way to pass i64, and wasm uses hardfp for function calls"); +#endif + case ABIArg::Uninitialized: + MOZ_CRASH("Uninitialized ABIArg kind"); + } + } + + defineReturn(lir, ins); + assignSafepoint(lir, ins); +} + static_assert(!std::is_polymorphic::value, "LIRGenerator should not have any virtual methods"); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index ae317ed4a0d7..bb7418918499 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -31,6 +31,7 @@ #include "vm/SelfHosting.h" #include "vm/SharedArrayObject.h" #include "vm/TypedArrayObject.h" +#include "wasm/WasmInstance.h" #include "jit/shared/Lowering-shared-inl.h" #include "vm/JSScript-inl.h" @@ -40,6 +41,7 @@ using mozilla::ArrayLength; using mozilla::AssertedCast; +using mozilla::Maybe; using JS::DoubleNaNValue; using JS::TrackedOutcome; @@ -57,7 +59,11 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) return InliningStatus_NotInlined; } - if (!target->hasJitInfo() || target->jitInfo()->type() != JSJitInfo::InlinableNative) { + bool isWasmCall = target->isWasmOptimized(); + if (!isWasmCall && + (!target->hasJitInfo() || + target->jitInfo()->type() != JSJitInfo::InlinableNative)) + { // Reaching here means we tried to inline a native for which there is no // Ion specialization. trackOptimizationOutcome(TrackedOutcome::CantInlineNativeNoSpecialization); @@ -81,6 +87,9 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) return InliningStatus_NotInlined; } + if (isWasmCall) + return inlineWasmCall(callInfo, target); + switch (InlinableNative inlNative = target->jitInfo()->inlinableNative) { // Array natives. case InlinableNative::Array: @@ -3773,6 +3782,83 @@ IonBuilder::inlineConstructTypedObject(CallInfo& callInfo, TypeDescr* descr) return InliningStatus_Inlined; } +IonBuilder::InliningResult +IonBuilder::inlineWasmCall(CallInfo& callInfo, JSFunction* target) +{ + MOZ_ASSERT(target->isWasmOptimized()); + MOZ_ASSERT(target->realm() == script()->realm()); + + // Don't inline wasm constructors. + if (callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + wasm::Instance& inst = wasm::ExportedFunctionToInstance(target); + uint32_t funcIndex = inst.code().getFuncIndex(target); + + auto bestTier = inst.code().bestTier(); + const wasm::FuncExport& funcExport = inst.metadata(bestTier).lookupFuncExport(funcIndex); + const wasm::FuncType& sig = funcExport.funcType(); + + // Check that the function doesn't take or return non-compatible JS + // argument types before adding nodes to the MIR graph, otherwise they'd be + // dead code. + if (sig.hasI64ArgOrRet() || sig.temporarilyUnsupportedAnyRef()) + return InliningStatus_NotInlined; + + // If there are too many arguments, don't inline (we won't be able to store + // the arguments in the LIR node). + static constexpr size_t MaxNumInlinedArgs = 8; + static_assert(MaxNumInlinedArgs <= MaxNumLInstructionOperands, + "inlined arguments can all be LIR operands"); + if (sig.args().length() > MaxNumInlinedArgs) + return InliningStatus_NotInlined; + + auto* call = MIonToWasmCall::New(alloc(), inst.object(), funcExport); + if (!call) + return abort(AbortReason::Alloc); + + Maybe undefined; + for (size_t i = 0; i < sig.args().length(); i++) { + if (!alloc().ensureBallast()) + return abort(AbortReason::Alloc); + + // Add undefined if an argument is missing. + if (i >= callInfo.argc() && !undefined) + undefined.emplace(constant(UndefinedValue())); + + MDefinition* arg = i >= callInfo.argc() ? *undefined : callInfo.getArg(i); + + MInstruction* conversion = nullptr; + switch (sig.args()[i].code()) { + case wasm::ValType::I32: + conversion = MTruncateToInt32::New(alloc(), arg); + break; + case wasm::ValType::F32: + conversion = MToFloat32::New(alloc(), arg); + break; + case wasm::ValType::F64: + conversion = MToDouble::New(alloc(), arg); + break; + case wasm::ValType::I64: + case wasm::ValType::AnyRef: + case wasm::ValType::Ref: + MOZ_CRASH("impossible per above check"); + } + + current->add(conversion); + call->initArg(i, conversion); + } + + current->push(call); + current->add(call); + + callInfo.setImplicitlyUsedUnchecked(); + + return InliningStatus_Inlined; +} + #define ADD_NATIVE(native) const JSJitInfo JitInfo_##native { \ { nullptr }, { uint16_t(InlinableNative::native) }, { 0 }, JSJitInfo::InlinableNative }; INLINABLE_NATIVE_LIST(ADD_NATIVE) diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 479dbce18713..48f08d490614 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -26,6 +26,7 @@ #include "jit/RangeAnalysis.h" #include "js/Conversions.h" #include "util/Text.h" +#include "wasm/WasmCode.h" #include "builtin/Boolean-inl.h" @@ -6305,3 +6306,33 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* *pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true); return false; } + +MIonToWasmCall* +MIonToWasmCall::New(TempAllocator& alloc, WasmInstanceObject* instanceObj, + const wasm::FuncExport& funcExport) +{ + wasm::ExprType retType = funcExport.funcType().ret(); + + MIRType resultType = retType.code() == wasm::ExprType::Void + ? MIRType::Value + : ToMIRType(retType); + + auto* ins = new(alloc) MIonToWasmCall(instanceObj, resultType, funcExport); + if (!ins->init(alloc, funcExport.funcType().args().length())) + return nullptr; + return ins; +} + +bool +MIonToWasmCall::appendRoots(MRootList& roots) const +{ + return roots.append(instanceObj_); +} + +#ifdef DEBUG +bool +MIonToWasmCall::isConsistentFloat32Use(MUse* use) const +{ + return funcExport_.funcType().args()[use->index()].code() == wasm::ValType::F32; +} +#endif diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index e424a98fa234..6981e1abc935 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -36,6 +36,10 @@ namespace js { +namespace wasm { +class FuncExport; +} + class StringObject; namespace jit { @@ -13966,6 +13970,47 @@ class MUnknownValue : public MNullaryInstruction TRIVIAL_NEW_WRAPPERS }; +class MIonToWasmCall final : + public MVariadicInstruction, + public NoTypePolicy::Data +{ + CompilerGCPointer instanceObj_; + const wasm::FuncExport& funcExport_; + + MIonToWasmCall(WasmInstanceObject* instanceObj, MIRType resultType, + const wasm::FuncExport& funcExport) + : MVariadicInstruction(classOpcode), + instanceObj_(instanceObj), + funcExport_(funcExport) + { + setResultType(resultType); + } + + public: + INSTRUCTION_HEADER(IonToWasmCall); + + static MIonToWasmCall* New(TempAllocator& alloc, WasmInstanceObject* instanceObj, + const wasm::FuncExport& funcExport); + + void initArg(size_t i, MDefinition* arg) { + initOperand(i, arg); + } + + WasmInstanceObject* instanceObject() const { + return instanceObj_; + } + const wasm::FuncExport& funcExport() const { + return funcExport_; + } + bool possiblyCalls() const override { + return true; + } + bool appendRoots(MRootList& roots) const override; +#ifdef DEBUG + bool isConsistentFloat32Use(MUse* use) const override; +#endif +}; + #undef INSTRUCTION_HEADER void MUse::init(MDefinition* producer, MNode* consumer) diff --git a/js/src/jit/MacroAssembler-inl.h b/js/src/jit/MacroAssembler-inl.h index 004069842c89..f8fdee4b1841 100644 --- a/js/src/jit/MacroAssembler-inl.h +++ b/js/src/jit/MacroAssembler-inl.h @@ -218,6 +218,14 @@ MacroAssembler::callJit(TrampolinePtr code) return currentOffset(); } +uint32_t +MacroAssembler::callJit(ImmPtr callee) +{ + AutoProfilerCallInstrumentation profiler(*this); + call(callee); + return currentOffset(); +} + void MacroAssembler::makeFrameDescriptor(Register frameSizeReg, FrameType type, uint32_t headerSize) { diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index d75320703c42..b4630371f8d3 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -597,6 +597,7 @@ class MacroAssembler : public MacroAssemblerSpecific inline uint32_t callJit(Register callee); inline uint32_t callJit(JitCode* code); inline uint32_t callJit(TrampolinePtr code); + inline uint32_t callJit(ImmPtr callee); // The frame descriptor is the second field of all Jit frames, pushed before // calling the Jit function. It is a composite value defined in JitFrames.h diff --git a/js/src/jit/RegisterAllocator.h b/js/src/jit/RegisterAllocator.h index 092c932d320e..c4e41eb23e77 100644 --- a/js/src/jit/RegisterAllocator.h +++ b/js/src/jit/RegisterAllocator.h @@ -254,6 +254,15 @@ class InstructionDataMap } }; +inline void +TakeJitRegisters(bool isProfiling, AllocatableRegisterSet* set) +{ +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) + if (isProfiling) + set->take(AnyRegister(FramePointer)); +#endif +} + // Common superclass for register allocators. class RegisterAllocator { @@ -280,14 +289,10 @@ class RegisterAllocator graph(graph), allRegisters_(RegisterSet::All()) { - if (mir->compilingWasm()) { + if (mir->compilingWasm()) takeWasmRegisters(allRegisters_); - } else { -#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) - if (mir->instrumentedProfiling()) - allRegisters_.take(AnyRegister(FramePointer)); -#endif - } + else + TakeJitRegisters(mir->instrumentedProfiling(), &allRegisters_); } MOZ_MUST_USE bool init(); diff --git a/js/src/jit/RegisterSets.h b/js/src/jit/RegisterSets.h index 1857bbc71a5f..f0f00320a81a 100644 --- a/js/src/jit/RegisterSets.h +++ b/js/src/jit/RegisterSets.h @@ -19,10 +19,13 @@ namespace js { namespace jit { struct AnyRegister { - typedef uint32_t Code; + typedef uint8_t Code; - static const uint32_t Total = Registers::Total + FloatRegisters::Total; - static const uint32_t Invalid = UINT_MAX; + static const uint8_t Total = Registers::Total + FloatRegisters::Total; + static const uint8_t Invalid = UINT8_MAX; + + static_assert(size_t(Registers::Total) + FloatRegisters::Total <= UINT8_MAX, + "Number of registers must fit in uint8_t"); private: Code code_; @@ -36,7 +39,7 @@ struct AnyRegister { explicit AnyRegister(FloatRegister fpu) { code_ = fpu.code() + Registers::Total; } - static AnyRegister FromCode(uint32_t i) { + static AnyRegister FromCode(uint8_t i) { MOZ_ASSERT(i < Total); AnyRegister r; r.code_ = i; @@ -72,7 +75,7 @@ struct AnyRegister { bool volatile_() const { return isFloat() ? fpu().volatile_() : gpr().volatile_(); } - AnyRegister aliased(uint32_t aliasIdx) const { + AnyRegister aliased(uint8_t aliasIdx) const { AnyRegister ret; if (isFloat()) ret = AnyRegister(fpu().aliased(aliasIdx)); @@ -81,7 +84,7 @@ struct AnyRegister { MOZ_ASSERT_IF(aliasIdx == 0, ret == *this); return ret; } - uint32_t numAliased() const { + uint8_t numAliased() const { if (isFloat()) return fpu().numAliased(); return gpr().numAliased(); diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index d170b0ee3c41..0e4a26ba3fa7 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -781,7 +781,7 @@ GetIndexFromString(JSString* str) if (!str->isFlat()) return -1; - uint32_t index; + uint32_t index = UINT32_MAX; if (!str->asFlat().isIndex(&index) || index > INT32_MAX) return -1; diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 8b87e71f3cfd..6d22640d6e26 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -9835,6 +9835,45 @@ class LGetPrototypeOf : public LInstructionHelper } }; +template +class LIonToWasmCallBase : public LVariadicInstruction +{ + using Base = LVariadicInstruction; + public: + explicit LIonToWasmCallBase(LNode::Opcode classOpcode, uint32_t numOperands, + const LDefinition& temp, const LDefinition& fp) + : Base(classOpcode, numOperands) + { + this->setIsCall(); + this->setTemp(0, temp); + this->setTemp(1, fp); + } + MIonToWasmCall* mir() const { + return this->mir_->toIonToWasmCall(); + } + const LDefinition* temp() { + return this->getTemp(0); + } +}; + +class LIonToWasmCall : public LIonToWasmCallBase<1> +{ + public: + LIR_HEADER(IonToWasmCall); + LIonToWasmCall(uint32_t numOperands, const LDefinition& temp, const LDefinition& fp) + : LIonToWasmCallBase<1>(classOpcode, numOperands, temp, fp) + {} +}; + +class LIonToWasmCallV : public LIonToWasmCallBase +{ + public: + LIR_HEADER(IonToWasmCallV); + LIonToWasmCallV(uint32_t numOperands, const LDefinition& temp, const LDefinition& fp) + : LIonToWasmCallBase(classOpcode, numOperands, temp, fp) + {} +}; + } // namespace jit } // namespace js diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index ce0f1ac705b6..bc7851fa7626 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -36,6 +36,7 @@ #include "builtin/String.h" #include "js/Conversions.h" #include "js/Date.h" +#include "js/LocaleSensitive.h" #include "js/Wrapper.h" #include "util/StringBuffer.h" #include "util/Text.h" diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 8ff311c69b20..365022479f64 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -614,12 +614,13 @@ JitFrameIter::settle() MOZ_ASSERT(wasmFrame.done()); uint8_t* prevFP = wasmFrame.unwoundIonCallerFP(); + jit::FrameType prevFrameType = wasmFrame.unwoundIonFrameType(); if (mustUnwindActivation_) act_->setJSExitFP(prevFP); iter_.destroy(); - iter_.construct(act_, prevFP); + iter_.construct(act_, prevFrameType, prevFP); MOZ_ASSERT(!asJSJit().done()); return; } diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index ddc2cd2b210d..038bb8b6fbf3 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -1622,13 +1622,10 @@ class BailoutFrameInfo; // A JitActivation is used for frames running in Baseline or Ion. class JitActivation : public Activation { - public: - static const uintptr_t ExitFpWasmBit = 0x1; - - private: // If Baseline, Ion or Wasm code is on the stack, and has called into C++, // this will be aligned to an ExitFrame. The last bit indicates if it's a - // wasm frame (bit set to ExitFpWasmBit) or not (bit set to !ExitFpWasmBit). + // wasm frame (bit set to wasm::ExitOrJitEntryFPTag) or not + // (bit set to ~wasm::ExitOrJitEntryFPTag). uint8_t* packedExitFP_; // When hasWasmExitFP(), encodedWasmExitReason_ holds ExitReason. @@ -1704,14 +1701,14 @@ class JitActivation : public Activation return !!packedExitFP_; } uint8_t* jsOrWasmExitFP() const { - return (uint8_t*)(uintptr_t(packedExitFP_) & ~ExitFpWasmBit); + return (uint8_t*)(uintptr_t(packedExitFP_) & ~wasm::ExitOrJitEntryFPTag); } static size_t offsetOfPackedExitFP() { return offsetof(JitActivation, packedExitFP_); } bool hasJSExitFP() const { - return !(uintptr_t(packedExitFP_) & ExitFpWasmBit); + return !(uintptr_t(packedExitFP_) & wasm::ExitOrJitEntryFPTag); } uint8_t* jsExitFP() const { MOZ_ASSERT(hasJSExitFP()); @@ -1802,16 +1799,16 @@ class JitActivation : public Activation // WebAssembly specific attributes. bool hasWasmExitFP() const { - return uintptr_t(packedExitFP_) & ExitFpWasmBit; + return uintptr_t(packedExitFP_) & wasm::ExitOrJitEntryFPTag; } wasm::Frame* wasmExitFP() const { MOZ_ASSERT(hasWasmExitFP()); - return (wasm::Frame*)(uintptr_t(packedExitFP_) & ~ExitFpWasmBit); + return (wasm::Frame*)(uintptr_t(packedExitFP_) & ~wasm::ExitOrJitEntryFPTag); } void setWasmExitFP(const wasm::Frame* fp) { if (fp) { - MOZ_ASSERT(!(uintptr_t(fp) & ExitFpWasmBit)); - packedExitFP_ = (uint8_t*)(uintptr_t(fp) | ExitFpWasmBit); + MOZ_ASSERT(!(uintptr_t(fp) & wasm::ExitOrJitEntryFPTag)); + packedExitFP_ = (uint8_t*)(uintptr_t(fp) | wasm::ExitOrJitEntryFPTag); MOZ_ASSERT(hasWasmExitFP()); } else { packedExitFP_ = nullptr; diff --git a/js/src/wasm/WasmFrameIter.cpp b/js/src/wasm/WasmFrameIter.cpp index b76eeafc453d..49ed3a3a1592 100644 --- a/js/src/wasm/WasmFrameIter.cpp +++ b/js/src/wasm/WasmFrameIter.cpp @@ -40,6 +40,7 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp) lineOrBytecode_(0), fp_(fp ? fp : activation->wasmExitFP()), unwoundIonCallerFP_(nullptr), + unwoundIonFrameType_(jit::FrameType(-1)), unwind_(Unwind::False), unwoundAddressOfReturnAddress_(nullptr) { @@ -112,7 +113,39 @@ WasmFrameIter::popFrame() { Frame* prevFP = fp_; fp_ = prevFP->callerFP; - MOZ_ASSERT(!(uintptr_t(fp_) & JitActivation::ExitFpWasmBit)); + + if (uintptr_t(fp_) & ExitOrJitEntryFPTag) { + // We just unwound a frame pointer which has the low bit set, + // indicating this is a direct call from the jit into the wasm + // function's body. The call stack resembles this at this point: + // + // |---------------------| + // | JIT FRAME | + // | JIT FAKE EXIT FRAME | <-- tagged fp_ + // | WASM FRAME | <-- prevFP (already unwound) + // |---------------------| + // + // fp_ points to the fake exit frame set up by the jit caller, and the + // return-address-to-fp is in JIT code, thus doesn't belong to any wasm + // instance's code (in particular, there's no associated CodeRange). + // Mark the frame as such and untag FP. + MOZ_ASSERT(!LookupCode(prevFP->returnAddress)); + + unwoundIonCallerFP_ = (uint8_t*)(uintptr_t(fp_) & ~uintptr_t(ExitOrJitEntryFPTag)); + unwoundIonFrameType_ = JitFrame_Exit; + + fp_ = nullptr; + code_ = nullptr; + codeRange_ = nullptr; + + if (unwind_ == Unwind::True) { + activation_->setJSExitFP(unwoundIonCallerFP_); + unwoundAddressOfReturnAddress_ = &prevFP->returnAddress; + } + + MOZ_ASSERT(done()); + return; + } if (!fp_) { code_ = nullptr; @@ -135,7 +168,20 @@ WasmFrameIter::popFrame() MOZ_ASSERT(codeRange_); if (codeRange_->isJitEntry()) { + // This wasm function has been called through the generic JIT entry by + // a JIT caller, so the call stack resembles this: + // + // |---------------------| + // | JIT FRAME | + // | JSJIT TO WASM EXIT | <-- fp_ + // | WASM JIT ENTRY | <-- prevFP (already unwound) + // | WASM FRAME | (already unwound) + // |---------------------| + // + // The next value of FP is just a regular jit frame used marked to + // know that we should transition to a JSJit frame iterator. unwoundIonCallerFP_ = (uint8_t*) fp_; + unwoundIonFrameType_ = JitFrame_JSJitToWasm; fp_ = nullptr; code_ = nullptr; @@ -275,6 +321,14 @@ WasmFrameIter::debugFrame() const return DebugFrame::from(fp_); } +jit::FrameType +WasmFrameIter::unwoundIonFrameType() const +{ + MOZ_ASSERT(unwoundIonCallerFP_); + MOZ_ASSERT(unwoundIonFrameType_ != jit::FrameType(-1)); + return unwoundIonFrameType_; +} + /*****************************************************************************/ // Prologue/epilogue code generation @@ -358,9 +412,9 @@ wasm::SetExitFP(MacroAssembler& masm, ExitReason reason, Register scratch) masm.store32(Imm32(reason.encode()), Address(scratch, JitActivation::offsetOfEncodedWasmExitReason())); - masm.orPtr(Imm32(JitActivation::ExitFpWasmBit), FramePointer); + masm.orPtr(Imm32(ExitOrJitEntryFPTag), FramePointer); masm.storePtr(FramePointer, Address(scratch, JitActivation::offsetOfPackedExitFP())); - masm.andPtr(Imm32(int32_t(~JitActivation::ExitFpWasmBit)), FramePointer); + masm.andPtr(Imm32(int32_t(~ExitOrJitEntryFPTag)), FramePointer); } void @@ -629,7 +683,7 @@ AssertNoWasmExitFPInJitExit(MacroAssembler& masm) Label ok; masm.branchTestPtr(Assembler::Zero, Address(scratch, JitActivation::offsetOfPackedExitFP()), - Imm32(uintptr_t(JitActivation::ExitFpWasmBit)), + Imm32(uintptr_t(ExitOrJitEntryFPTag)), &ok); masm.breakpoint(); masm.bind(&ok); @@ -734,6 +788,18 @@ ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation, initFromExitFP(fp); } +static inline void +AssertDirectJitCall(const void* fp) +{ + // Called via an inlined fast JIT to wasm call: in this case, FP is + // pointing in the middle of the exit frame, right before the exit + // footer; ensure the exit frame type is the expected one. +#ifdef DEBUG + auto* jitCaller = (ExitFrameLayout*)(uintptr_t(fp) & ~ExitOrJitEntryFPTag); + MOZ_ASSERT(jitCaller->footer()->type() == jit::ExitFrameType::DirectWasmJitCall); +#endif +} + static inline void AssertMatchesCallSite(void* callerPC, Frame* callerFP) { @@ -741,7 +807,11 @@ AssertMatchesCallSite(void* callerPC, Frame* callerFP) const CodeRange* callerCodeRange; const Code* code = LookupCode(callerPC, &callerCodeRange); - MOZ_ASSERT(code); + if (!code) { + AssertDirectJitCall(callerFP); + return; + } + MOZ_ASSERT(callerCodeRange); if (callerCodeRange->isInterpEntry()) { @@ -768,7 +838,18 @@ ProfilingFrameIterator::initFromExitFP(const Frame* fp) void* pc = fp->returnAddress; code_ = LookupCode(pc, &codeRange_); - MOZ_ASSERT(code_); + + if (!code_) { + // This is a direct call from the JIT, the caller FP is pointing to a + // tagged JIT caller's frame. + MOZ_ASSERT(uintptr_t(fp->callerFP) & ExitOrJitEntryFPTag); + AssertDirectJitCall(fp->callerFP); + + unwoundIonCallerFP_ = (uint8_t*)(uintptr_t(fp->callerFP) & ~ExitOrJitEntryFPTag); + MOZ_ASSERT(done()); + return; + } + MOZ_ASSERT(codeRange_); // Since we don't have the pc for fp, start unwinding at the caller of fp. @@ -808,6 +889,15 @@ ProfilingFrameIterator::initFromExitFP(const Frame* fp) MOZ_ASSERT(!done()); } +static void +AssertCallerFP(DebugOnly fpWasTagged, Frame* const fp, void** const sp) +{ + MOZ_ASSERT_IF(!fpWasTagged.value, + fp == reinterpret_cast(sp)->callerFP); + MOZ_ASSERT_IF(fpWasTagged.value, + (Frame*)(uintptr_t(fp) | 0x1) == reinterpret_cast(sp)->callerFP); +} + bool js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindState, bool* unwoundCaller) @@ -816,9 +906,12 @@ js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindStat uint8_t* const pc = (uint8_t*) registers.pc; void** const sp = (void**) registers.sp; - // The frame pointer might be in the process of tagging/untagging; make - // sure it's untagged. - Frame* const fp = (Frame*) (intptr_t(registers.fp) & ~JitActivation::ExitFpWasmBit); + // The frame pointer might be: + // - in the process of tagging/untagging when calling into the JITs; + // make sure it's untagged. + // - tagged by an direct JIT call. + DebugOnly fpWasTagged = uintptr_t(registers.fp) & ExitOrJitEntryFPTag; + Frame* const fp = (Frame*) (intptr_t(registers.fp) & ~ExitOrJitEntryFPTag); // Get the CodeRange describing pc and the base address to which the // CodeRange is relative. If the pc is not in a wasm module or a builtin @@ -927,7 +1020,7 @@ js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindStat AssertMatchesCallSite(fixedPC, fixedFP); } else if (offsetFromEntry == PushedFP) { // The full Frame has been pushed; fp is still the caller's fp. - MOZ_ASSERT(fp == reinterpret_cast(sp)->callerFP); + AssertCallerFP(fpWasTagged, fp, sp); fixedPC = reinterpret_cast(sp)->returnAddress; fixedFP = fp; AssertMatchesCallSite(fixedPC, fixedFP); @@ -939,7 +1032,7 @@ js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindStat // The fixedFP field of the Frame has been loaded into fp. // The ra and TLS might also be loaded, but the Frame structure is // still on stack, so we can acess the ra form there. - MOZ_ASSERT(fp == reinterpret_cast(sp)->callerFP); + AssertCallerFP(fpWasTagged, fp, sp); fixedPC = reinterpret_cast(sp)->returnAddress; fixedFP = fp; AssertMatchesCallSite(fixedPC, fixedFP); @@ -1017,7 +1110,7 @@ js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindStat fixedPC = nullptr; // On the error return path, FP might be set to FailFP. Ignore these transient frames. - if (intptr_t(fixedFP) == (FailFP & ~JitActivation::ExitFpWasmBit)) + if (intptr_t(fixedFP) == (FailFP & ~ExitOrJitEntryFPTag)) return false; break; case CodeRange::Throw: @@ -1063,13 +1156,24 @@ ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation, if (unwoundCaller) { callerFP_ = unwindState.fp; callerPC_ = unwindState.pc; + // If the original FP value is tagged, then we're being called through + // a direct JIT call. We can't observe transient tagged values of FP + // (during wasm::SetExitFP) here because StartUnwinding would not have + // unwound for us in this case. + if ((uintptr_t(state.fp) & ExitOrJitEntryFPTag)) + unwoundIonCallerFP_ = (uint8_t*) callerFP_; } else { callerFP_ = unwindState.fp->callerFP; callerPC_ = unwindState.fp->returnAddress; + // See comment above. + if ((uintptr_t(callerFP_) & ExitOrJitEntryFPTag)) + unwoundIonCallerFP_ = (uint8_t*)(uintptr_t(callerFP_) & ~ExitOrJitEntryFPTag); } - if (unwindState.codeRange->isJitEntry()) + if (unwindState.codeRange->isJitEntry()) { + MOZ_ASSERT(!unwoundIonCallerFP_); unwoundIonCallerFP_ = (uint8_t*) callerFP_; + } if (unwindState.codeRange->isInterpEntry()) { unwindState.codeRange = nullptr; @@ -1086,15 +1190,15 @@ void ProfilingFrameIterator::operator++() { if (!exitReason_.isNone()) { - DebugOnly prevExitReason = exitReason_; + DebugOnly wasInterpEntry = exitReason_.isInterpEntry(); exitReason_ = ExitReason::None(); - MOZ_ASSERT(!codeRange_ == prevExitReason.value.isInterpEntry()); - MOZ_ASSERT(done() == prevExitReason.value.isInterpEntry()); + MOZ_ASSERT((!codeRange_) == wasInterpEntry); + MOZ_ASSERT(done() == wasInterpEntry); return; } if (unwoundIonCallerFP_) { - MOZ_ASSERT(codeRange_->isJitEntry()); + MOZ_ASSERT(codeRange_->isFunction() || codeRange_->isJitEntry()); callerPC_ = nullptr; callerFP_ = nullptr; codeRange_ = nullptr; @@ -1120,6 +1224,17 @@ ProfilingFrameIterator::operator++() } code_ = LookupCode(callerPC_, &codeRange_); + + if (!code_ && uintptr_t(callerFP_) & ExitOrJitEntryFPTag) { + // The parent frame is an inlined wasm call, the tagged FP points to + // the fake exit frame. + MOZ_ASSERT(!codeRange_); + AssertDirectJitCall(callerFP_); + unwoundIonCallerFP_ = (uint8_t*) (uintptr_t(callerFP_) & ~uintptr_t(ExitOrJitEntryFPTag)); + MOZ_ASSERT(done()); + return; + } + MOZ_ASSERT(codeRange_); if (codeRange_->isJitEntry()) { diff --git a/js/src/wasm/WasmFrameIter.h b/js/src/wasm/WasmFrameIter.h index 7693261e2914..844204d01415 100644 --- a/js/src/wasm/WasmFrameIter.h +++ b/js/src/wasm/WasmFrameIter.h @@ -19,6 +19,7 @@ #ifndef wasm_frame_iter_h #define wasm_frame_iter_h +#include "jit/JSJitFrameIter.h" #include "js/ProfilingFrameIterator.h" #include "js/TypeDecls.h" #include "wasm/WasmTypes.h" @@ -64,6 +65,7 @@ class WasmFrameIter unsigned lineOrBytecode_; Frame* fp_; uint8_t* unwoundIonCallerFP_; + jit::FrameType unwoundIonFrameType_; Unwind unwind_; void** unwoundAddressOfReturnAddress_; @@ -88,6 +90,7 @@ class WasmFrameIter void** unwoundAddressOfReturnAddress() const; bool debugEnabled() const; DebugFrame* debugFrame() const; + jit::FrameType unwoundIonFrameType() const; uint8_t* unwoundIonCallerFP() const { return unwoundIonCallerFP_; } }; @@ -264,6 +267,19 @@ bool StartUnwinding(const RegisterState& registers, UnwindState* unwindState, bool* unwoundCaller); +// Bit set as the lowest bit of a frame pointer, used in two different mutually +// exclusive situations: +// - either it's a low bit tag in a FramePointer value read from the +// Frame::callerFP of an inner wasm frame. This indicates the previous call +// frame has been set up by a JIT caller that directly called into a wasm +// function's body. This is only stored in Frame::callerFP for a wasm frame +// called from JIT code, and thus it can not appear in a JitActivation's +// exitFP. +// - or it's the low big tag set when exiting wasm code in JitActivation's +// exitFP. + +constexpr uintptr_t ExitOrJitEntryFPTag = 0x1; + } // namespace wasm } // namespace js diff --git a/js/src/wasm/WasmStubs.cpp b/js/src/wasm/WasmStubs.cpp index 045fe453f945..d4454c9aff7e 100644 --- a/js/src/wasm/WasmStubs.cpp +++ b/js/src/wasm/WasmStubs.cpp @@ -20,6 +20,7 @@ #include "mozilla/ArrayUtils.h" +#include "jit/RegisterAllocator.h" #include "wasm/WasmCode.h" #include "wasm/WasmGenerator.h" #include "wasm/WasmInstance.h" @@ -501,7 +502,8 @@ GenerateJitEntryThrow(MacroAssembler& masm, unsigned frameSize) MoveSPForJitABI(masm); masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, cx)), ScratchIonEntry); - masm.enterFakeExitFrameForWasm(ScratchIonEntry, ScratchIonEntry, ExitFrameType::WasmJitEntry); + masm.enterFakeExitFrameForWasm(ScratchIonEntry, ScratchIonEntry, + ExitFrameType::WasmGenericJitEntry); masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, instance)), ScratchIonEntry); masm.loadPtr(Address(ScratchIonEntry, Instance::offsetOfJSJitExceptionHandler()), @@ -844,6 +846,164 @@ GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex, const FuncExport& return FinishOffsets(masm, offsets); } +void +wasm::GenerateDirectCallFromJit(MacroAssembler& masm, + const FuncExport& fe, + const Instance& inst, + const JitCallStackArgVector& stackArgs, + bool profilingEnabled, + bool wasmGcEnabled, + Register scratch, + uint32_t* callOffset) +{ + MOZ_ASSERT(!IsCompilingWasm()); + + size_t framePushedAtStart = masm.framePushed(); + + if (profilingEnabled) { + // FramePointer isn't volatile, manually preserve it because it will be + // clobbered below. + masm.Push(FramePointer); + } else { +#ifdef DEBUG + // Ensure that the FramePointer is actually Ion-volatile. This might + // assert when bug 1426134 lands. + AllocatableRegisterSet set(RegisterSet::All()); + TakeJitRegisters(/* profiling */ false, &set); + MOZ_ASSERT(set.has(FramePointer), + "replace the whole if branch by the then body when this fails"); +#endif + } + + // Push a special frame descriptor that indicates the frame size so we can + // directly iterate from the current JIT frame without an extra call. + *callOffset = masm.buildFakeExitFrame(scratch); + masm.loadJSContext(scratch); + + masm.moveStackPtrTo(FramePointer); + masm.enterFakeExitFrame(scratch, scratch, ExitFrameType::DirectWasmJitCall); + masm.orPtr(Imm32(ExitOrJitEntryFPTag), FramePointer); + + // Move stack arguments to their final locations. + unsigned bytesNeeded = StackArgBytes(fe.funcType().args()); + bytesNeeded = StackDecrementForCall(WasmStackAlignment, masm.framePushed(), bytesNeeded); + if (bytesNeeded) + masm.reserveStack(bytesNeeded); + + for (ABIArgValTypeIter iter(fe.funcType().args()); !iter.done(); iter++) { + MOZ_ASSERT_IF(iter->kind() == ABIArg::GPR, iter->gpr() != scratch); + MOZ_ASSERT_IF(iter->kind() == ABIArg::GPR, iter->gpr() != FramePointer); + if (iter->kind() != ABIArg::Stack) + continue; + + Address dst(masm.getStackPointer(), iter->offsetFromArgBase()); + + const JitCallStackArg& stackArg = stackArgs[iter.index()]; + switch (stackArg.tag()) { + case JitCallStackArg::Tag::Imm32: + masm.storePtr(ImmWord(stackArg.imm32()), dst); + break; + case JitCallStackArg::Tag::GPR: + MOZ_ASSERT(stackArg.gpr() != scratch); + MOZ_ASSERT(stackArg.gpr() != FramePointer); + masm.storePtr(stackArg.gpr(), dst); + break; + case JitCallStackArg::Tag::FPU: + switch (iter.mirType()) { + case MIRType::Double: + masm.storeDouble(stackArg.fpu(), dst); + break; + case MIRType::Float32: + masm.storeFloat32(stackArg.fpu(), dst); + break; + default: + MOZ_CRASH("unexpected MIR type for a float register in wasm fast call"); + } + break; + case JitCallStackArg::Tag::Address: { + // The address offsets were valid *before* we pushed our frame. + Address src = stackArg.addr(); + src.offset += masm.framePushed() - framePushedAtStart; + switch (iter.mirType()) { + case MIRType::Double: + masm.loadDouble(src, ScratchDoubleReg); + masm.storeDouble(ScratchDoubleReg, dst); + break; + case MIRType::Float32: + masm.loadFloat32(src, ScratchFloat32Reg); + masm.storeFloat32(ScratchFloat32Reg, dst); + break; + case MIRType::Int32: + masm.loadPtr(src, scratch); + masm.storePtr(scratch, dst); + break; + default: + MOZ_CRASH("unexpected MIR type for a stack slot in wasm fast call"); + } + break; + } + case JitCallStackArg::Tag::Undefined: { + MOZ_CRASH("can't happen because of arg.kind() check"); + } + } + } + + // Load tls; from now on, WasmTlsReg is live. + masm.movePtr(ImmPtr(inst.tlsData()), WasmTlsReg); + masm.loadWasmPinnedRegsFromTls(); + +#ifdef ENABLE_WASM_GC + if (wasmGcEnabled) + SuppressGC(masm, 1, ABINonArgReg0); +#endif + + // Actual call. + const wasm::CodeTier& codeTier = inst.code().codeTier(inst.code().bestTier()); + uint32_t offset = codeTier.metadata().codeRanges[fe.funcCodeRangeIndex()].funcNormalEntry(); + void* callee = codeTier.segment().base() + offset; + + masm.assertStackAlignment(WasmStackAlignment); + masm.callJit(ImmPtr(callee)); + masm.assertStackAlignment(WasmStackAlignment); + +#ifdef ENABLE_WASM_GC + if (wasmGcEnabled) + SuppressGC(masm, -1, WasmTlsReg); +#endif + + masm.branchPtr(Assembler::Equal, FramePointer, Imm32(wasm::FailFP), masm.exceptionLabel()); + + // Store the return value in the appropriate place. + switch (fe.funcType().ret().code()) { + case wasm::ExprType::Void: + masm.moveValue(UndefinedValue(), JSReturnOperand); + break; + case wasm::ExprType::I32: + break; + case wasm::ExprType::F32: + masm.canonicalizeFloat(ReturnFloat32Reg); + break; + case wasm::ExprType::F64: + masm.canonicalizeDouble(ReturnDoubleReg); + break; + case wasm::ExprType::Ref: + case wasm::ExprType::AnyRef: + case wasm::ExprType::I64: + MOZ_CRASH("unexpected return type when calling from ion to wasm"); + case wasm::ExprType::Limit: + MOZ_CRASH("Limit"); + } + + // Free args + frame descriptor. + masm.leaveExitFrame(bytesNeeded + ExitFrameLayout::Size()); + + // If we pushed it, free FramePointer. + if (profilingEnabled) + masm.Pop(FramePointer); + + MOZ_ASSERT(framePushedAtStart == masm.framePushed()); +} + static void StackCopy(MacroAssembler& masm, MIRType type, Register scratch, Address src, Address dst) { diff --git a/js/src/wasm/WasmStubs.h b/js/src/wasm/WasmStubs.h index 6dc3d0b20a11..20655577ec82 100644 --- a/js/src/wasm/WasmStubs.h +++ b/js/src/wasm/WasmStubs.h @@ -41,6 +41,87 @@ GenerateEntryStubs(jit::MacroAssembler& masm, size_t funcExportIndex, const FuncExport& funcExport, const Maybe& callee, bool isAsmJS, HasGcTypes gcTypesEnabled, CodeRangeVector* codeRanges); +// An argument that will end up on the stack according to the system ABI, to be +// passed to GenerateDirectCallFromJit. Since the direct JIT call creates its +// own frame, it is its responsibility to put stack arguments to their expected +// locations; so the caller of GenerateDirectCallFromJit can put them anywhere. + +class JitCallStackArg +{ + public: + enum class Tag { + Imm32, + GPR, + FPU, + Address, + Undefined, + }; + + private: + Tag tag_; + union U { + int32_t imm32_; + jit::Register gpr_; + jit::FloatRegister fpu_; + jit::Address addr_; + U() {} + } arg; + + public: + JitCallStackArg() + : tag_(Tag::Undefined) + {} + explicit JitCallStackArg(int32_t imm32) + : tag_(Tag::Imm32) + { + arg.imm32_ = imm32; + } + explicit JitCallStackArg(jit::Register gpr) + : tag_(Tag::GPR) + { + arg.gpr_ = gpr; + } + explicit JitCallStackArg(jit::FloatRegister fpu) + : tag_(Tag::FPU) + { + new (&arg) jit::FloatRegister(fpu); + } + explicit JitCallStackArg(const jit::Address& addr) + : tag_(Tag::Address) + { + new (&arg) jit::Address(addr); + } + + Tag tag() const { return tag_; } + int32_t imm32() const { MOZ_ASSERT(tag_ == Tag::Imm32); return arg.imm32_; } + jit::Register gpr() const { MOZ_ASSERT(tag_ == Tag::GPR); return arg.gpr_; } + jit::FloatRegister fpu() const { MOZ_ASSERT(tag_ == Tag::FPU); return arg.fpu_; } + const jit::Address& addr() const { MOZ_ASSERT(tag_ == Tag::Address); return arg.addr_; } +}; + +using JitCallStackArgVector = Vector; + +// Generates an inline wasm call (during jit compilation) to a specific wasm +// function (as specifed by the given FuncExport). +// This call doesn't go through a wasm entry, but rather creates its own +// inlined exit frame. +// Assumes: +// - all the registers have been preserved by the caller, +// - all arguments passed in registers have been set up at the expected +// locations, +// - all arguments passed on stack slot are alive as defined by a corresponding +// JitCallStackArg. + +extern void +GenerateDirectCallFromJit(jit::MacroAssembler& masm, + const FuncExport& fe, + const Instance& inst, + const JitCallStackArgVector& stackArgs, + bool profilingEnabled, + bool wasmGcEnabled, + jit::Register scratch, + uint32_t* callOffset); + } // namespace wasm } // namespace js diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index a8ec89d1b65d..054bff18624a 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -94,9 +94,7 @@ using mozilla::dom::PushNotifier; using mozilla::dom::AudioChannelAgent; // Editor stuff -#include "nsEditorCID.h" #include "mozilla/EditorController.h" //CID -#include "mozilla/HTMLEditor.h" #include "nsScriptSecurityManager.h" #include "ExpandedPrincipal.h" @@ -179,12 +177,8 @@ using mozilla::gmp::GeckoMediaPluginService; { 0x1f15dbc8, 0xbfaa, 0x45de, \ { 0x8a, 0x46, 0x08, 0xe2, 0xe2, 0x63, 0x26, 0xb0 } } -NS_GENERIC_FACTORY_CONSTRUCTOR(TextEditor) - NS_GENERIC_FACTORY_CONSTRUCTOR(nsParserUtils) -NS_GENERIC_FACTORY_CONSTRUCTOR(HTMLEditor) - // PresentationDeviceManager /* e1e79dec-4085-4994-ac5b-744b016697e6 */ #define PRESENTATION_DEVICE_MANAGER_CID \ @@ -533,7 +527,6 @@ NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURIMUTATOR_CID); NS_DEFINE_NAMED_CID(NS_SDBCONNECTION_CID); NS_DEFINE_NAMED_CID(NS_DOMSESSIONSTORAGEMANAGER_CID); NS_DEFINE_NAMED_CID(NS_DOMLOCALSTORAGEMANAGER_CID); -NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID); NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID); NS_DEFINE_NAMED_CID(QUOTAMANAGER_SERVICE_CID); NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID); @@ -542,7 +535,6 @@ NS_DEFINE_NAMED_CID(NOTIFICATIONTELEMETRYSERVICE_CID); NS_DEFINE_NAMED_CID(PUSHNOTIFIER_CID); NS_DEFINE_NAMED_CID(WORKERDEBUGGERMANAGER_CID); NS_DEFINE_NAMED_CID(NS_AUDIOCHANNELAGENT_CID); -NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID); NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_CID); NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER_CID); NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID); @@ -770,7 +762,6 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kNS_SDBCONNECTION_CID, false, nullptr, SDBConnection::Create }, { &kNS_DOMSESSIONSTORAGEMANAGER_CID, false, nullptr, SessionStorageManagerConstructor }, { &kNS_DOMLOCALSTORAGEMANAGER_CID, false, nullptr, LocalStorageManagerConstructor }, - { &kNS_TEXTEDITOR_CID, false, nullptr, TextEditorConstructor }, { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor }, { &kQUOTAMANAGER_SERVICE_CID, false, nullptr, QuotaManagerServiceConstructor }, { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor }, @@ -779,7 +770,6 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kPUSHNOTIFIER_CID, false, nullptr, PushNotifierConstructor }, { &kWORKERDEBUGGERMANAGER_CID, true, nullptr, WorkerDebuggerManagerConstructor }, { &kNS_AUDIOCHANNELAGENT_CID, true, nullptr, AudioChannelAgentConstructor }, - { &kNS_HTMLEDITOR_CID, false, nullptr, HTMLEditorConstructor }, { &kNS_EDITORCONTROLLER_CID, false, nullptr, EditorControllerConstructor }, { &kNS_EDITINGCONTROLLER_CID, false, nullptr, nsEditingControllerConstructor }, { &kNS_EDITORCOMMANDTABLE_CID, false, nullptr, nsEditorCommandTableConstructor }, @@ -872,7 +862,6 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { // Keeping the old ContractID for backward compatibility { "@mozilla.org/dom/storagemanager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID }, { "@mozilla.org/dom/sessionStorage-manager;1", &kNS_DOMSESSIONSTORAGEMANAGER_CID }, - { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID }, { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID }, { QUOTAMANAGER_SERVICE_CONTRACTID, &kQUOTAMANAGER_SERVICE_CID }, { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID }, @@ -881,7 +870,6 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { PUSHNOTIFIER_CONTRACTID, &kPUSHNOTIFIER_CID }, { WORKERDEBUGGERMANAGER_CONTRACTID, &kWORKERDEBUGGERMANAGER_CID }, { NS_AUDIOCHANNELAGENT_CONTRACTID, &kNS_AUDIOCHANNELAGENT_CID }, - { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID }, { "@mozilla.org/editor/editorcontroller;1", &kNS_EDITORCONTROLLER_CID }, { "@mozilla.org/editor/editingcontroller;1", &kNS_EDITINGCONTROLLER_CID }, { "@mozilla.org/geolocation;1", &kNS_GEOLOCATION_CID }, diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 984f75e93ac7..e3d26d0f888a 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -2713,8 +2713,8 @@ ComputeClipForMaskItem(nsDisplayListBuilder* aBuilder, nsIFrame* aMaskedFrame, aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame); Maybe combinedClip; - if (maskUsage.shouldApplyBasicShape) { - Rect result = nsCSSClipPathInstance::GetBoundingRectForBasicShapeClip( + if (maskUsage.shouldApplyBasicShapeOrPath) { + Rect result = nsCSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip( aMaskedFrame, svgReset->mClipPath); combinedClip = Some(ThebesRect(result)); } else if (maskUsage.shouldApplyClipPath) { diff --git a/layout/inspector/tests/test_bug877690.html b/layout/inspector/tests/test_bug877690.html index 52da3d8aaa33..8fd8399657b1 100644 --- a/layout/inspector/tests/test_bug877690.html +++ b/layout/inspector/tests/test_bug877690.html @@ -190,7 +190,7 @@ function do_test() { // Regression test for bug 1255379. var expected = [ "inherit", "initial", "unset", "none", "url", - "polygon", "circle", "ellipse", "inset", + "polygon", "circle", "ellipse", "inset", "path", "fill-box", "stroke-box", "view-box", "margin-box", "border-box", "padding-box", "content-box" ]; var values = InspectorUtils.getCSSValuesForProperty("clip-path"); diff --git a/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001-ref.html b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001-ref.html new file mode 100644 index 000000000000..afc1d18f204e --- /dev/null +++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001-ref.html @@ -0,0 +1,29 @@ + + + + + CSS Masking: Reference for clip-path's path function with nonzero + + + +

The test passes if there are a green filled rect.

+
+ + + + + + + + + diff --git a/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001.html b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001.html new file mode 100644 index 000000000000..4f9dafefb517 --- /dev/null +++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001.html @@ -0,0 +1,24 @@ + + + + + CSS Masking: Test clip-path property and path function with nonzero + + + + + + +

The test passes if there are a green filled rect.

+
+ + diff --git a/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002-ref.html b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002-ref.html new file mode 100644 index 000000000000..d9ea5183fb1e --- /dev/null +++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002-ref.html @@ -0,0 +1,29 @@ + + + + + CSS Masking: Reference for clip-path's path function with evenodd + + + +

The test passes if there are a green hollow rect.

+
+ + + + + + + + + diff --git a/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002.html b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002.html new file mode 100644 index 000000000000..fc2b5a0b4a6c --- /dev/null +++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002.html @@ -0,0 +1,24 @@ + + + + + CSS Masking: Test clip-path property and path function with evenodd + + + + + + +

The test passes if there are a green hollow rect.

+
+ + diff --git a/layout/reftests/svg/svg-integration/clip-path/path.css b/layout/reftests/svg/svg-integration/clip-path/path.css new file mode 100644 index 000000000000..190a6f69435e --- /dev/null +++ b/layout/reftests/svg/svg-integration/clip-path/path.css @@ -0,0 +1,7 @@ +.path_nonzero_rect { + clip-path: path(nonzero, 'M10,10h80v80h-80zM25,25h50v50h-50z'); +} + +.path_evenodd_rect { + clip-path: path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z'); +} diff --git a/layout/reftests/svg/svg-integration/clip-path/reftest.list b/layout/reftests/svg/svg-integration/clip-path/reftest.list index f28ec5cc354c..ca4245336ff7 100644 --- a/layout/reftests/svg/svg-integration/clip-path/reftest.list +++ b/layout/reftests/svg/svg-integration/clip-path/reftest.list @@ -59,3 +59,6 @@ fuzzy(0-64,0-340) fuzzy-if(webrender,104-104,311-311) == clip-path-inset-003.htm == clip-path-stroke-001.html clip-path-stroke-001-ref.html == clip-path-transform-001.html clip-path-transform-001-ref.html + +== clip-path-path-001.html clip-path-path-001-ref.html +== clip-path-path-002.html clip-path-path-002-ref.html diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index d1c7b8142cce..662de8297f39 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1978,9 +1978,14 @@ struct StyleSVGPath final return mPath; } + StyleFillRule FillRule() const + { + return mFillRule; + } + bool operator==(const StyleSVGPath& aOther) const { - return mPath == aOther.mPath; + return mPath == aOther.mPath && mFillRule == aOther.mFillRule; } bool operator!=(const StyleSVGPath& aOther) const @@ -1990,6 +1995,7 @@ struct StyleSVGPath final private: nsTArray mPath; + StyleFillRule mFillRule = StyleFillRule::Nonzero; }; struct StyleShapeSource final diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index eba7883cc5ca..8b111721c9d3 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -8101,6 +8101,19 @@ if (false) { other_values: [ "green", "#fc3" ], invalid_values: [ "000000", "ff00ff" ] }; + + // |clip-path: path()| is chrome-only. + gCSSProperties["clip-path"].other_values.push( + "path(nonzero, 'M 10 10 h 100 v 100 h-100 v-100 z')", + "path(evenodd, 'M 10 10 h 100 v 100 h-100 v-100 z')", + "path('M10,30A20,20 0,0,1 50,30A20,20 0,0,1 90,30Q90,60 50,90Q10,60 10,30z')", + ); + + gCSSProperties["clip-path"].invalid_values.push( + "path(nonzero)", + "path(evenodd, '')", + "path(abs, 'M 10 10 L 10 10 z')", + ); } if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) { diff --git a/layout/svg/nsCSSClipPathInstance.cpp b/layout/svg/nsCSSClipPathInstance.cpp index 4edcbba21fa7..2c97aa5d4990 100644 --- a/layout/svg/nsCSSClipPathInstance.cpp +++ b/layout/svg/nsCSSClipPathInstance.cpp @@ -10,6 +10,7 @@ #include "gfx2DGlue.h" #include "gfxContext.h" #include "gfxPlatform.h" +#include "mozilla/dom/SVGPathData.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/PathHelpers.h" #include "mozilla/ShapeUtils.h" @@ -25,16 +26,17 @@ using namespace mozilla::dom; using namespace mozilla::gfx; /* static*/ void -nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext, - nsIFrame* aFrame) +nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(gfxContext& aContext, + nsIFrame* aFrame) { auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; #ifdef DEBUG StyleShapeSourceType type = clipPathStyle.GetType(); MOZ_ASSERT(type == StyleShapeSourceType::Shape || - type == StyleShapeSourceType::Box, - "This function is used with basic-shape and geometry-box only."); + type == StyleShapeSourceType::Box || + type == StyleShapeSourceType::Path, + "This is used with basic-shape, geometry-box, and path() only"); #endif nsCSSClipPathInstance instance(aFrame, clipPathStyle); @@ -46,8 +48,8 @@ nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext, } /* static*/ bool -nsCSSClipPathInstance::HitTestBasicShapeClip(nsIFrame* aFrame, - const gfxPoint& aPoint) +nsCSSClipPathInstance::HitTestBasicShapeOrPathClip(nsIFrame* aFrame, + const gfxPoint& aPoint) { auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; StyleShapeSourceType type = clipPathStyle.GetType(); @@ -69,11 +71,13 @@ nsCSSClipPathInstance::HitTestBasicShapeClip(nsIFrame* aFrame, } /* static */ Rect -nsCSSClipPathInstance::GetBoundingRectForBasicShapeClip(nsIFrame* aFrame, - const StyleShapeSource& aClipPathStyle) +nsCSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip( + nsIFrame* aFrame, + const StyleShapeSource& aClipPathStyle) { MOZ_ASSERT(aClipPathStyle.GetType() == StyleShapeSourceType::Shape || - aClipPathStyle.GetType() == StyleShapeSourceType::Box); + aClipPathStyle.GetType() == StyleShapeSourceType::Box || + aClipPathStyle.GetType() == StyleShapeSourceType::Path); nsCSSClipPathInstance instance(aFrame, aClipPathStyle); @@ -86,6 +90,10 @@ nsCSSClipPathInstance::GetBoundingRectForBasicShapeClip(nsIFrame* aFrame, already_AddRefed nsCSSClipPathInstance::CreateClipPath(DrawTarget* aDrawTarget) { + if (mClipPathStyle.GetType() == StyleShapeSourceType::Path) { + return CreateClipPathPath(aDrawTarget); + } + nsRect r = nsLayoutUtils::ComputeGeometryBox(mTargetFrame, mClipPathStyle.GetReferenceBox()); @@ -213,3 +221,18 @@ nsCSSClipPathInstance::CreateClipPathInset(DrawTarget* aDrawTarget, } return builder->Finish(); } + +already_AddRefed +nsCSSClipPathInstance::CreateClipPathPath(DrawTarget* aDrawTarget) +{ + const StyleSVGPath* path = mClipPathStyle.GetPath(); + MOZ_ASSERT(path); + + RefPtr builder = aDrawTarget->CreatePathBuilder( + path->FillRule() == StyleFillRule::Nonzero ? FillRule::FILL_WINDING + : FillRule::FILL_EVEN_ODD); + float scale = float(AppUnitsPerCSSPixel()) / + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + return SVGPathData::BuildPath( + path->Path(), builder, NS_STYLE_STROKE_LINECAP_BUTT, 0.0, scale); +} diff --git a/layout/svg/nsCSSClipPathInstance.h b/layout/svg/nsCSSClipPathInstance.h index bfe1ad136e5c..bc7381d84c8f 100644 --- a/layout/svg/nsCSSClipPathInstance.h +++ b/layout/svg/nsCSSClipPathInstance.h @@ -23,14 +23,16 @@ class nsCSSClipPathInstance typedef mozilla::gfx::Rect Rect; public: - static void ApplyBasicShapeClip(gfxContext& aContext, - nsIFrame* aFrame); + static void ApplyBasicShapeOrPathClip(gfxContext& aContext, + nsIFrame* aFrame); // aPoint is in CSS pixels. - static bool HitTestBasicShapeClip(nsIFrame* aFrame, - const gfxPoint& aPoint); + static bool HitTestBasicShapeOrPathClip(nsIFrame* aFrame, + const gfxPoint& aPoint); + + static Rect GetBoundingRectForBasicShapeOrPathClip( + nsIFrame* aFrame, + const StyleShapeSource& aClipPathStyle); - static Rect GetBoundingRectForBasicShapeClip(nsIFrame* aFrame, - const StyleShapeSource& aClipPathStyle); private: explicit nsCSSClipPathInstance(nsIFrame* aFrame, const StyleShapeSource aClipPathStyle) @@ -53,6 +55,7 @@ private: already_AddRefed CreateClipPathInset(DrawTarget* aDrawTarget, const nsRect& aRefBox); + already_AddRefed CreateClipPathPath(DrawTarget* aDrawTarget); /** * The frame for the element that is currently being clipped. diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp index 328f111bc1ed..051059822e54 100644 --- a/layout/svg/nsSVGIntegrationUtils.cpp +++ b/layout/svg/nsSVGIntegrationUtils.cpp @@ -809,13 +809,13 @@ nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams) // Paint clip-path-basic-shape onto ctx gfxContextAutoSaveRestore basicShapeSR; - if (maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyBasicShapeOrPath) { matSR.SetContext(&ctx); MoveContextOriginToUserSpace(firstFrame, aParams); basicShapeSR.SetContext(&ctx); - nsCSSClipPathInstance::ApplyBasicShapeClip(ctx, frame); + nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame); if (!maskUsage.shouldGenerateMaskLayer) { // Only have basic-shape clip-path effect. Fill clipped region by // opaque white. @@ -997,17 +997,17 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams) /* If this frame has only a trivial clipPath, set up cairo's clipping now so * we can just do normal painting and get it clipped appropriately. */ - if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { gfxContextMatrixAutoSaveRestore matSR(&context); MoveContextOriginToUserSpace(firstFrame, aParams); MOZ_ASSERT(!maskUsage.shouldApplyClipPath || - !maskUsage.shouldApplyBasicShape); + !maskUsage.shouldApplyBasicShapeOrPath); if (maskUsage.shouldApplyClipPath) { clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix); } else { - nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame); + nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame); } } @@ -1036,7 +1036,7 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams) maskUsage.shouldGenerateClipMaskLayer) { overlayColor.g = 1.0f; // green represents clip-path:. } - if (maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyBasicShapeOrPath) { overlayColor.b = 1.0f; // blue represents // clip-path:||. } @@ -1045,7 +1045,7 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams) context.Fill(); } - if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { context.PopClip(); } diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp index 151af89a2c51..b94316d69772 100644 --- a/layout/svg/nsSVGUtils.cpp +++ b/layout/svg/nsSVGUtils.cpp @@ -523,11 +523,13 @@ nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, break; case StyleShapeSourceType::Shape: case StyleShapeSourceType::Box: - aUsage.shouldApplyBasicShape = true; + case StyleShapeSourceType::Path: + aUsage.shouldApplyBasicShapeOrPath = true; break; case StyleShapeSourceType::None: MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer && - !aUsage.shouldApplyClipPath && !aUsage.shouldApplyBasicShape); + !aUsage.shouldApplyClipPath && + !aUsage.shouldApplyBasicShapeOrPath); break; default: MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type."); @@ -807,11 +809,11 @@ nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, /* If this frame has only a trivial clipPath, set up cairo's clipping now so * we can just do normal painting and get it clipped appropriately. */ - if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { if (maskUsage.shouldApplyClipPath) { clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform); } else { - nsCSSClipPathInstance::ApplyBasicShapeClip(aContext, aFrame); + nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame); } } @@ -856,7 +858,7 @@ nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect); } - if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { aContext.PopClip(); } @@ -878,7 +880,7 @@ nsSVGUtils::HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint) if (!props.mClipPath) { const nsStyleSVGReset *style = aFrame->StyleSVGReset(); if (style->HasClipPath()) { - return nsCSSClipPathInstance::HitTestBasicShapeClip(aFrame, aPoint); + return nsCSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint); } return true; } diff --git a/layout/svg/nsSVGUtils.h b/layout/svg/nsSVGUtils.h index 44784ce5d63d..d42849a8f81a 100644 --- a/layout/svg/nsSVGUtils.h +++ b/layout/svg/nsSVGUtils.h @@ -606,19 +606,22 @@ public: bool shouldGenerateMaskLayer; bool shouldGenerateClipMaskLayer; bool shouldApplyClipPath; - bool shouldApplyBasicShape; + bool shouldApplyBasicShapeOrPath; float opacity; MaskUsage() - : shouldGenerateMaskLayer(false), shouldGenerateClipMaskLayer(false), - shouldApplyClipPath(false), shouldApplyBasicShape(false), opacity(0.0) + : shouldGenerateMaskLayer(false) + , shouldGenerateClipMaskLayer(false) + , shouldApplyClipPath(false) + , shouldApplyBasicShapeOrPath(false) + , opacity(0.0) { } bool shouldDoSomething() { return shouldGenerateMaskLayer || shouldGenerateClipMaskLayer || shouldApplyClipPath - || shouldApplyBasicShape + || shouldApplyBasicShapeOrPath || opacity != 1.0; } }; diff --git a/layout/tools/reftest/jar.mn b/layout/tools/reftest/jar.mn index c5431eac5ea1..1f2dd0bf3d76 100644 --- a/layout/tools/reftest/jar.mn +++ b/layout/tools/reftest/jar.mn @@ -8,6 +8,7 @@ reftest.jar: content/PerTestCoverageUtils.jsm (../../../tools/code-coverage/PerTestCoverageUtils.jsm) content/input.css (../../../editor/reftests/xul/input.css) content/moz-bool-pref.css (../../../layout/reftests/css-parsing/moz-bool-pref.css) + content/path.css (../../../layout/reftests/svg/svg-integration/clip-path/path.css) content/progress.css (../../../layout/reftests/forms/progress/style.css) * content/manifest.jsm (manifest.jsm) * content/reftest.jsm (reftest.jsm) diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt index c5d620d0db7f..288d51b18563 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt @@ -47,7 +47,7 @@ class SessionLifecycleTest : BaseSessionTest() { @Test fun open_allowCallsWhileClosed() { sessionRule.session.close() - sessionRule.session.loadUri(HELLO_HTML_PATH) + sessionRule.session.loadTestPath(HELLO_HTML_PATH) sessionRule.session.reload() sessionRule.session.open() diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp index 17e26603a915..2cbe6f92a809 100644 --- a/netwerk/base/RedirectChannelRegistrar.cpp +++ b/netwerk/base/RedirectChannelRegistrar.cpp @@ -3,10 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RedirectChannelRegistrar.h" +#include "mozilla/StaticPtr.h" namespace mozilla { namespace net { +namespace { +StaticRefPtr gSingleton; +} + NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar) RedirectChannelRegistrar::RedirectChannelRegistrar() @@ -15,6 +20,26 @@ RedirectChannelRegistrar::RedirectChannelRegistrar() , mId(1) , mLock("RedirectChannelRegistrar") { + MOZ_ASSERT(!gSingleton); +} + +// static +already_AddRefed +RedirectChannelRegistrar::GetOrCreate() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!gSingleton) { + gSingleton = new RedirectChannelRegistrar(); + } + return do_AddRef(gSingleton); +} + +// static +void +RedirectChannelRegistrar::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + gSingleton = nullptr; } NS_IMETHODIMP diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h index 2bf8c5a6c227..438123b5e00e 100644 --- a/netwerk/base/RedirectChannelRegistrar.h +++ b/netwerk/base/RedirectChannelRegistrar.h @@ -26,6 +26,13 @@ class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar private: ~RedirectChannelRegistrar() = default; +public: + // Singleton accessor + static already_AddRefed GetOrCreate(); + + // Cleanup the singleton instance on shutdown + static void Shutdown(); + protected: typedef nsInterfaceHashtable ChannelHashtable; diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index f15746302ea0..5e972b076929 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -176,6 +176,7 @@ EXPORTS.mozilla.net += [ 'MemoryDownloader.h', 'PartiallySeekableInputStream.h', 'Predictor.h', + 'RedirectChannelRegistrar.h', 'ReferrerPolicy.h', 'SimpleChannelParent.h', 'TCPFastOpen.h', diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index 98cbd6b3b412..76a8b281a624 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -47,7 +47,7 @@ #include "nsIPrivateBrowsingChannel.h" #include "nsIPropertyBag2.h" #include "nsIProtocolProxyService.h" -#include "nsIRedirectChannelRegistrar.h" +#include "mozilla/net/RedirectChannelRegistrar.h" #include "nsIRequestObserverProxy.h" #include "nsIScriptSecurityManager.h" #include "nsISensitiveInfoHiddenURI.h" @@ -2757,11 +2757,9 @@ NS_LinkRedirectChannels(uint32_t channelId, nsIParentChannel *parentChannel, nsIChannel **_result) { - nsresult rv; - nsCOMPtr registrar = - do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); + RedirectChannelRegistrar::GetOrCreate(); + MOZ_ASSERT(registrar); return registrar->LinkChannels(channelId, parentChannel, diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index dfdad224afe3..fed218d5a04c 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -474,17 +474,6 @@ #define NS_URICLASSIFIERSERVICE_CONTRACTID \ "@mozilla.org/uriclassifierservice" -// Redirect channel registrar used for redirect to various protocols -#define NS_REDIRECTCHANNELREGISTRAR_CONTRACTID \ - "@mozilla.org/redirectchannelregistrar;1" -#define NS_REDIRECTCHANNELREGISTRAR_CID \ -{ /* {b69043a6-8929-4d60-8d17-a27e44a8393e} */ \ - 0xb69043a6, \ - 0x8929, \ - 0x4d60, \ - { 0x8d, 0x17, 0xa2, 0x7e, 0x44, 0xa8, 0x39, 0x3e } \ -} - // service implementing nsINetworkPredictor #define NS_NETWORKPREDICTOR_CONTRACTID \ "@mozilla.org/network/predictor;1" diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index 9aac5c67891b..f0e1cab622bf 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -39,6 +39,7 @@ #include "Predictor.h" #include "nsIThreadPool.h" #include "mozilla/net/NeckoChild.h" +#include "RedirectChannelRegistrar.h" #include "nsNetCID.h" @@ -131,10 +132,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsEffectiveTLDService, Init) #include "nsSerializationHelper.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsSerializationHelper) -#include "RedirectChannelRegistrar.h" -typedef mozilla::net::RedirectChannelRegistrar RedirectChannelRegistrar; -NS_GENERIC_FACTORY_CONSTRUCTOR(RedirectChannelRegistrar) - #include "CacheStorageService.h" typedef mozilla::net::CacheStorageService CacheStorageService; NS_GENERIC_FACTORY_CONSTRUCTOR(CacheStorageService) @@ -652,6 +649,8 @@ static void nsNetShutdown() mozilla::net::Http2CompressionCleanup(); + mozilla::net::RedirectChannelRegistrar::Shutdown(); + delete gNetSniffers; gNetSniffers = nullptr; delete gDataSniffers; @@ -768,7 +767,6 @@ NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #endif NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID); -NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID); NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_NSILOADCONTEXTINFOFACTORY_CID); NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID); @@ -893,7 +891,6 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor }, #endif { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor }, - { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor }, { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor }, { &kNS_NSILOADCONTEXTINFOFACTORY_CID, false, nullptr, LoadContextInfoFactoryConstructor }, { &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create }, @@ -1017,7 +1014,6 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = { { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #endif { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID }, - { NS_REDIRECTCHANNELREGISTRAR_CONTRACTID, &kNS_REDIRECTCHANNELREGISTRAR_CID }, { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID }, { NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID }, { NS_NSILOADCONTEXTINFOFACTORY_CONTRACTID, &kNS_NSILOADCONTEXTINFOFACTORY_CID }, diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index 347570deb112..21b2ace5dff2 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -45,7 +45,7 @@ #include "nsCORSListenerProxy.h" #include "nsIIPCSerializableInputStream.h" #include "nsIPrompt.h" -#include "nsIRedirectChannelRegistrar.h" +#include "mozilla/net/RedirectChannelRegistrar.h" #include "nsIWindowWatcher.h" #include "nsIDocument.h" #include "nsStreamUtils.h" @@ -972,7 +972,7 @@ HttpChannelParent::RecvRedirect2Verify(const nsresult& aResult, // Wait for background channel ready on target channel nsCOMPtr redirectReg = - do_GetService(NS_REDIRECTCHANNELREGISTRAR_CONTRACTID); + RedirectChannelRegistrar::GetOrCreate(); MOZ_ASSERT(redirectReg); nsCOMPtr redirectParentChannel; diff --git a/netwerk/protocol/http/HttpChannelParentListener.cpp b/netwerk/protocol/http/HttpChannelParentListener.cpp index 7be910841fb6..0ab01cb98887 100644 --- a/netwerk/protocol/http/HttpChannelParentListener.cpp +++ b/netwerk/protocol/http/HttpChannelParentListener.cpp @@ -15,7 +15,7 @@ #include "nsIAuthPrompt.h" #include "nsIAuthPrompt2.h" #include "nsIHttpHeaderVisitor.h" -#include "nsIRedirectChannelRegistrar.h" +#include "mozilla/net/RedirectChannelRegistrar.h" #include "nsIPromptFactory.h" #include "nsIWindowWatcher.h" #include "nsQueryObject.h" @@ -179,8 +179,8 @@ HttpChannelParentListener::AsyncOnChannelRedirect( // Register the new channel and obtain id for it nsCOMPtr registrar = - do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); + RedirectChannelRegistrar::GetOrCreate(); + MOZ_ASSERT(registrar); rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId); NS_ENSURE_SUCCESS(rv, rv); @@ -208,8 +208,8 @@ HttpChannelParentListener::OnRedirectResult(bool succeeded) nsCOMPtr redirectChannel; if (mRedirectChannelId) { nsCOMPtr registrar = - do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); + RedirectChannelRegistrar::GetOrCreate(); + MOZ_ASSERT(registrar); rv = registrar->GetParentChannel(mRedirectChannelId, getter_AddRefs(redirectChannel)); diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index ca77764042a2..5cff8c365d66 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -336,6 +336,7 @@ nsHttpChannel::nsHttpChannel() , mStronglyFramed(false) , mUsedNetwork(0) , mAuthConnectionRestartable(0) + , mTrackingProtectionCancellationPending(0) , mPushedStream(nullptr) , mLocalBlocklist(false) , mOnTailUnblock(nullptr) @@ -6058,10 +6059,18 @@ nsHttpChannel::CancelForTrackingProtection() if (mSuspendCount) { LOG(("Waiting until resume in Cancel [this=%p]\n", this)); MOZ_ASSERT(!mCallOnResume); + mTrackingProtectionCancellationPending = 1; mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection; return NS_OK; } + // Check to see if we should redirect this channel elsewhere by + // nsIHttpChannel.redirectTo API request + if (mAPIRedirectToURI) { + mTrackingProtectionCancellationPending = 1; + return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect); + } + return CancelInternal(NS_ERROR_TRACKING_URI); } @@ -6079,12 +6088,25 @@ nsHttpChannel::ContinueCancelledByTrackingProtection() return; } + // Check to see if we should redirect this channel elsewhere by + // nsIHttpChannel.redirectTo API request + if (mAPIRedirectToURI) { + Unused << AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect); + return; + } + Unused << CancelInternal(NS_ERROR_TRACKING_URI); } nsresult nsHttpChannel::CancelInternal(nsresult status) { + bool trackingProtectionCancellationPending = + !!mTrackingProtectionCancellationPending; + if (status == NS_ERROR_TRACKING_URI) { + mTrackingProtectionCancellationPending = 0; + } + mCanceled = true; mStatus = status; if (mProxyRequest) @@ -6102,6 +6124,10 @@ nsHttpChannel::CancelInternal(nsresult status) mRequestContext->CancelTailedRequest(this); CloseCacheEntry(false); Unused << AsyncAbort(status); + } else if (trackingProtectionCancellationPending) { + // If we're coming from an asynchronous path when canceling a channel due + // to tracking protection, we need to AsyncAbort the channel now. + Unused << AsyncAbort(status); } return NS_OK; } @@ -6563,6 +6589,14 @@ nsHttpChannel::BeginConnectActual() return mStatus; } + if (mTrackingProtectionCancellationPending) { + LOG(("Waiting for tracking protection cancellation in BeginConnectActual [this=%p]\n", this)); + MOZ_ASSERT(!mCallOnResume || + mCallOnResume == &nsHttpChannel::HandleContinueCancelledByTrackingProtection, + "We should be paused waiting for cancellation from tracking protection"); + return NS_OK; + } + if (!mConnectionInfo->UsingHttpProxy() && !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) { // Start a DNS lookup very early in case the real open is queued the DNS can diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 2f04fc9b74cc..32eadd0bbd64 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -628,6 +628,11 @@ private: // the next authentication request can be sent on a whole new connection uint32_t mAuthConnectionRestartable : 1; + // True if the channel classifier has marked the channel to be cancelled + // due to the tracking protection rules, but the asynchronous cancellation + // process hasn't finished yet. + uint32_t mTrackingProtectionCancellationPending : 1; + nsTArray mRedirectFuncStack; // Needed for accurate DNS timing diff --git a/python/mozbuild/mozbuild/compilation/database.py b/python/mozbuild/mozbuild/compilation/database.py index 8277daa9b2ba..bbb634f712aa 100644 --- a/python/mozbuild/mozbuild/compilation/database.py +++ b/python/mozbuild/mozbuild/compilation/database.py @@ -126,6 +126,12 @@ class CompileDBBackend(CommonBackend): json.dump(db, jsonout, indent=0) def _process_unified_sources(self, obj): + if not obj.have_unified_mapping: + for f in list(sorted(obj.files)): + self._build_db_line(obj.objdir, obj.relsrcdir, obj.config, f, + obj.canonical_suffix) + return + # For unified sources, only include the unified source file. # Note that unified sources are never used for host sources. for f in obj.unified_source_mapping: diff --git a/servo/components/style/gecko/conversions.rs b/servo/components/style/gecko/conversions.rs index 4dafe457371c..e85bc8ec3103 100644 --- a/servo/components/style/gecko/conversions.rs +++ b/servo/components/style/gecko/conversions.rs @@ -670,10 +670,11 @@ pub mod basic_shape { use values::computed::position; use values::computed::url::ComputedUrl; use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon}; - use values::generics::basic_shape::{Circle, Ellipse, FillRule, PolygonCoord}; + use values::generics::basic_shape::{Circle, Ellipse, FillRule, Path, PolygonCoord}; use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource}; use values::generics::border::BorderRadius as GenericBorderRadius; use values::generics::rect::Rect; + use values::specified::SVGPathData; impl StyleShapeSource { /// Convert StyleShapeSource to ShapeSource except URL and Image @@ -698,7 +699,34 @@ pub mod basic_shape { Some(ShapeSource::Shape(shape, reference_box)) }, StyleShapeSourceType::URL | StyleShapeSourceType::Image => None, - StyleShapeSourceType::Path => None, + StyleShapeSourceType::Path => { + let path = self.to_svg_path().expect("expect an SVGPathData"); + let gecko_path = unsafe { &*self.__bindgen_anon_1.mSVGPath.as_ref().mPtr }; + let fill = if gecko_path.mFillRule == StyleFillRule::Evenodd { + FillRule::Evenodd + } else { + FillRule::Nonzero + }; + Some(ShapeSource::Path(Path { fill, path })) + }, + } + } + + /// Generate a SVGPathData from StyleShapeSource if possible. + fn to_svg_path(&self) -> Option { + use gecko_bindings::structs::StylePathCommand; + use values::specified::svg_path::PathCommand; + match self.mType { + StyleShapeSourceType::Path => { + let gecko_path = unsafe { &*self.__bindgen_anon_1.mSVGPath.as_ref().mPtr }; + let result: Vec = + gecko_path.mPath.iter().map(|gecko: &StylePathCommand| { + // unsafe: cbindgen ensures the representation is the same. + unsafe{ ::std::mem::transmute(*gecko) } + }).collect(); + Some(SVGPathData::new(result.into_boxed_slice())) + }, + _ => None, } } } @@ -742,17 +770,9 @@ pub mod basic_shape { impl<'a> From<&'a StyleShapeSource> for OffsetPath { fn from(other: &'a StyleShapeSource) -> Self { - use gecko_bindings::structs::StylePathCommand; - use values::specified::motion::{SVGPathData, PathCommand}; match other.mType { StyleShapeSourceType::Path => { - let gecko_path = unsafe { &*other.__bindgen_anon_1.mSVGPath.as_ref().mPtr }; - let result: Vec = - gecko_path.mPath.iter().map(|gecko: &StylePathCommand| { - // unsafe: cbindgen ensures the representation is the same. - unsafe{ ::std::mem::transmute(*gecko) } - }).collect(); - OffsetPath::Path(SVGPathData::new(result.into_boxed_slice())) + OffsetPath::Path(other.to_svg_path().expect("Cannot convert to SVGPathData")) }, StyleShapeSourceType::None => OffsetPath::none(), StyleShapeSourceType::Shape | diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index 76d61838708a..9e9a6835a426 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -3683,27 +3683,16 @@ fn static_assert() { ${impl_simple_type_with_conversion("touch_action")} pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) { - use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_NewStyleSVGPath}; - use gecko_bindings::bindings::Gecko_SetStyleMotion; + use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_SetStyleMotion}; use gecko_bindings::structs::StyleShapeSourceType; + use values::generics::basic_shape::FillRule; use values::specified::OffsetPath; let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() }; match v { OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None, - OffsetPath::Path(servo_path) => { - motion.mOffsetPath.mType = StyleShapeSourceType::Path; - let gecko_path = unsafe { - let ref mut source = motion.mOffsetPath; - Gecko_NewStyleSVGPath(source); - &mut source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap().mPath - }; - unsafe { gecko_path.set_len(servo_path.commands().len() as u32) }; - debug_assert_eq!(gecko_path.len(), servo_path.commands().len()); - for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.iter_mut()) { - // unsafe: cbindgen ensures the representation is the same. - *gecko = unsafe { transmute(*servo) }; - } + OffsetPath::Path(p) => { + set_style_svg_path(&mut motion.mOffsetPath, &p, FillRule::Nonzero) }, } unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) }; @@ -4982,6 +4971,35 @@ fn static_assert() { } +// Set SVGPathData to StyleShapeSource. +fn set_style_svg_path( + shape_source: &mut structs::mozilla::StyleShapeSource, + servo_path: &values::specified::svg_path::SVGPathData, + fill: values::generics::basic_shape::FillRule, +) { + use gecko_bindings::bindings::Gecko_NewStyleSVGPath; + use gecko_bindings::structs::StyleShapeSourceType; + + // Setup type. + shape_source.mType = StyleShapeSourceType::Path; + + // Setup path. + let gecko_path = unsafe { + Gecko_NewStyleSVGPath(shape_source); + &mut shape_source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap() + }; + unsafe { gecko_path.mPath.set_len(servo_path.commands().len() as u32) }; + debug_assert_eq!(gecko_path.mPath.len(), servo_path.commands().len()); + for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.mPath.iter_mut()) { + // unsafe: cbindgen ensures the representation is the same. + *gecko = unsafe { transmute(*servo) }; + } + + // Setup fill-rule. + // unsafe: cbindgen ensures the representation is the same. + gecko_path.mFillRule = unsafe { transmute(fill) }; +} + <%def name="impl_shape_source(ident, gecko_ffi_name)"> pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { use gecko_bindings::bindings::{Gecko_NewBasicShape, Gecko_DestroyShapeSource}; @@ -5021,6 +5039,7 @@ fn static_assert() { ${ident}.mReferenceBox = reference.into(); ${ident}.mType = StyleShapeSourceType::Box; } + ShapeSource::Path(p) => set_style_svg_path(${ident}, &p.path, p.fill), ShapeSource::Shape(servo_shape, maybe_box) => { fn init_shape(${ident}: &mut StyleShapeSource, basic_shape_type: StyleBasicShapeType) -> &mut StyleBasicShape { diff --git a/servo/components/style/values/generics/basic_shape.rs b/servo/components/style/values/generics/basic_shape.rs index ee7455e777de..0eccf011c716 100644 --- a/servo/components/style/values/generics/basic_shape.rs +++ b/servo/components/style/values/generics/basic_shape.rs @@ -12,6 +12,7 @@ use values::distance::{ComputeSquaredDistance, SquaredDistance}; use values::generics::border::BorderRadius; use values::generics::position::Position; use values::generics::rect::Rect; +use values::specified::SVGPathData; /// A clipping shape, for `clip-path`. pub type ClippingShape = ShapeSource; @@ -54,6 +55,9 @@ pub enum ShapeSource { #[animation(error)] Box(ReferenceBox), #[animation(error)] + #[css(function)] + Path(Path), + #[animation(error)] None, } @@ -144,6 +148,19 @@ pub enum FillRule { Evenodd, } +/// The path function defined in css-shape-2. +/// +/// https://drafts.csswg.org/css-shapes-2/#funcdef-path +#[css(comma)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)] +pub struct Path { + /// The filling rule for the svg path. + #[css(skip_if = "fill_is_default")] + pub fill: FillRule, + /// The svg path data. + pub path: SVGPathData, +} + // FIXME(nox): Implement ComputeSquaredDistance for T types and stop // using PartialEq here, this will let us derive this impl. impl ComputeSquaredDistance for ShapeSource diff --git a/servo/components/style/values/specified/basic_shape.rs b/servo/components/style/values/specified/basic_shape.rs index 2fa693a1978a..824c13f1f014 100644 --- a/servo/components/style/values/specified/basic_shape.rs +++ b/servo/components/style/values/specified/basic_shape.rs @@ -14,9 +14,11 @@ use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use values::computed::Percentage; use values::generics::basic_shape as generic; -use values::generics::basic_shape::{FillRule, GeometryBox, PolygonCoord, ShapeBox, ShapeSource}; +use values::generics::basic_shape::{FillRule, GeometryBox, Path, PolygonCoord}; +use values::generics::basic_shape::{ShapeBox, ShapeSource}; use values::generics::rect::Rect; use values::specified::LengthOrPercentage; +use values::specified::SVGPathData; use values::specified::border::BorderRadius; use values::specified::image::Image; use values::specified::position::{HorizontalPosition, Position, PositionComponent}; @@ -47,12 +49,42 @@ pub type ShapeRadius = generic::ShapeRadius; /// The specified value of `Polygon` pub type Polygon = generic::Polygon; -impl Parse for ShapeSource +impl Parse for ClippingShape { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + // |clip-path:path()| is a chrome-only property value support for now. `path()` is + // defined in css-shape-2, but the spec is not stable enough, and we haven't decided + // to make it public yet. However, it has some benefits for the front-end, so we + // implement it. + if context.chrome_rules_enabled() { + if let Ok(p) = input.try(|i| Path::parse(context, i)) { + return Ok(ShapeSource::Path(p)); + } + } + Self::parse_internal(context, input) + } +} + +impl Parse for FloatAreaShape { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Self::parse_internal(context, input) + } +} + +impl ShapeSource where ReferenceBox: Parse, ImageOrUrl: Parse, { - fn parse<'i, 't>( + /// The internal parser for ShapeSource. + fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { @@ -393,3 +425,29 @@ impl Polygon { }) } } + +impl Parse for Path { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + input.expect_function_matching("path")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl Path { + /// Parse the inner arguments of a `path` function. + fn parse_function_arguments<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let fill = input.try(|i| -> Result<_, ParseError> { + let fill = FillRule::parse(i)?; + i.expect_comma()?; + Ok(fill) + }).unwrap_or_default(); + let path = SVGPathData::parse(context, input)?; + Ok(Path { fill, path }) + } +} diff --git a/servo/components/style/values/specified/mod.rs b/servo/components/style/values/specified/mod.rs index 1bd52e916d07..31e37b3041d7 100644 --- a/servo/components/style/values/specified/mod.rs +++ b/servo/components/style/values/specified/mod.rs @@ -68,6 +68,7 @@ pub use self::position::{PositionComponent, ZIndex}; pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind}; pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; pub use self::svg::MozContextProperties; +pub use self::svg_path::SVGPathData; pub use self::table::XSpan; pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize, TextAlign}; pub use self::text::{TextEmphasisPosition, TextEmphasisStyle}; @@ -109,6 +110,7 @@ pub mod rect; pub mod resolution; pub mod source_size_list; pub mod svg; +pub mod svg_path; pub mod table; pub mod text; pub mod time; diff --git a/servo/components/style/values/specified/motion.rs b/servo/components/style/values/specified/motion.rs index ffaaf7cff17b..87ff39d9a3ae 100644 --- a/servo/components/style/values/specified/motion.rs +++ b/servo/components/style/values/specified/motion.rs @@ -6,12 +6,8 @@ use cssparser::Parser; use parser::{Parse, ParserContext}; -use std::fmt::{self, Write}; -use std::iter::Peekable; -use std::str::Chars; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; -use style_traits::values::SequenceWriter; -use values::CSSFloat; +use style_traits::{ParseError, StyleParseErrorKind}; +use values::specified::SVGPathData; /// The offset-path value. /// @@ -63,496 +59,3 @@ impl Parse for OffsetPath { }) } } - -/// SVG Path parser. -struct PathParser<'a> { - chars: Peekable>, - path: Vec, -} - -macro_rules! parse_arguments { - ( - $parser:ident, - $abs:ident, - $enum:ident, - [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ] - ) => { - { - loop { - let $para = $func(&mut $parser.chars)?; - $( - skip_comma_wsp(&mut $parser.chars); - let $other_para = $other_func(&mut $parser.chars)?; - )* - $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs }); - - // End of string or the next character is a possible new command. - if !skip_wsp(&mut $parser.chars) || - $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) { - break; - } - skip_comma_wsp(&mut $parser.chars); - } - Ok(()) - } - } -} - -impl<'a> PathParser<'a> { - /// Parse a sub-path. - fn parse_subpath(&mut self) -> Result<(), ()> { - // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path - // (i.e. not a valid moveto-drawto-command-group). - self.parse_moveto()?; - - // Handle other commands. - loop { - skip_wsp(&mut self.chars); - if self.chars.peek().map_or(true, |m| *m == 'M' || *m == 'm') { - break; - } - - match self.chars.next() { - Some(command) => { - let abs = command.is_uppercase(); - macro_rules! parse_command { - ( $($($p:pat)|+ => $parse_func:ident,)* ) => { - match command { - $( - $($p)|+ => { - skip_wsp(&mut self.chars); - self.$parse_func(abs)?; - }, - )* - _ => return Err(()), - } - } - } - parse_command!( - 'Z' | 'z' => parse_closepath, - 'L' | 'l' => parse_lineto, - 'H' | 'h' => parse_h_lineto, - 'V' | 'v' => parse_v_lineto, - 'C' | 'c' => parse_curveto, - 'S' | 's' => parse_smooth_curveto, - 'Q' | 'q' => parse_quadratic_bezier_curveto, - 'T' | 't' => parse_smooth_quadratic_bezier_curveto, - 'A' | 'a' => parse_elliprical_arc, - ); - }, - _ => break, // no more commands. - } - } - Ok(()) - } - - /// Parse "moveto" command. - fn parse_moveto(&mut self) -> Result<(), ()> { - let command = match self.chars.next() { - Some(c) if c == 'M' || c == 'm' => c, - _ => return Err(()), - }; - - skip_wsp(&mut self.chars); - let point = parse_coord(&mut self.chars)?; - let absolute = command == 'M'; - self.path.push(PathCommand::MoveTo { point, absolute } ); - - // End of string or the next character is a possible new command. - if !skip_wsp(&mut self.chars) || - self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) { - return Ok(()); - } - skip_comma_wsp(&mut self.chars); - - // If a moveto is followed by multiple pairs of coordinates, the subsequent - // pairs are treated as implicit lineto commands. - self.parse_lineto(absolute) - } - - /// Parse "closepath" command. - fn parse_closepath(&mut self, _absolute: bool) -> Result<(), ()> { - self.path.push(PathCommand::ClosePath); - Ok(()) - } - - /// Parse "lineto" command. - fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> { - parse_arguments!(self, absolute, LineTo, [ point => parse_coord ]) - } - - /// Parse horizontal "lineto" command. - fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> { - parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ]) - } - - /// Parse vertical "lineto" command. - fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> { - parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ]) - } - - /// Parse cubic Bézier curve command. - fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> { - parse_arguments!(self, absolute, CurveTo, [ - control1 => parse_coord, control2 => parse_coord, point => parse_coord - ]) - } - - /// Parse smooth "curveto" command. - fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> { - parse_arguments!(self, absolute, SmoothCurveTo, [ - control2 => parse_coord, point => parse_coord - ]) - } - - /// Parse quadratic Bézier curve command. - fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { - parse_arguments!(self, absolute, QuadBezierCurveTo, [ - control1 => parse_coord, point => parse_coord - ]) - } - - /// Parse smooth quadratic Bézier curveto command. - fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { - parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ]) - } - - /// Parse elliptical arc curve command. - fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> { - // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). - let parse_flag = |iter: &mut Peekable| -> Result { - let value = match iter.peek() { - Some(c) if *c == '0' || *c == '1' => *c == '1', - _ => return Err(()), - }; - iter.next(); - Ok(value) - }; - parse_arguments!(self, absolute, EllipticalArc, [ - rx => parse_number, - ry => parse_number, - angle => parse_number, - large_arc_flag => parse_flag, - sweep_flag => parse_flag, - point => parse_coord - ]) - } -} - -/// The SVG path data. -/// -/// https://www.w3.org/TR/SVG11/paths.html#PathData -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)] -pub struct SVGPathData(Box<[PathCommand]>); - -impl SVGPathData { - /// Return SVGPathData by a slice of PathCommand. - #[inline] - pub fn new(cmd: Box<[PathCommand]>) -> Self { - debug_assert!(!cmd.is_empty()); - SVGPathData(cmd) - } - - /// Get the array of PathCommand. - #[inline] - pub fn commands(&self) -> &[PathCommand] { - debug_assert!(!self.0.is_empty()); - &self.0 - } -} - -impl ToCss for SVGPathData { - #[inline] - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write - { - dest.write_char('"')?; - { - let mut writer = SequenceWriter::new(dest, " "); - for command in self.0.iter() { - writer.item(command)?; - } - } - dest.write_char('"') - } -} - -impl Parse for SVGPathData { - // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make - // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.) - // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident - // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable - // str::Char iterator to check each character. - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't> - ) -> Result> { - let location = input.current_source_location(); - let path_string = input.expect_string()?.as_ref(); - if path_string.is_empty() { - // Treat an empty string as invalid, so we will not set it. - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - // Parse the svg path string as multiple sub-paths. - let mut path_parser = PathParser { - chars: path_string.chars().peekable(), - path: Vec::new(), - }; - while skip_wsp(&mut path_parser.chars) { - if path_parser.parse_subpath().is_err() { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - - Ok(SVGPathData::new(path_parser.path.into_boxed_slice())) - } -} - - -/// The SVG path command. -/// The fields of these commands are self-explanatory, so we skip the documents. -/// Note: the index of the control points, e.g. control1, control2, are mapping to the control -/// points of the Bézier curve in the spec. -/// -/// https://www.w3.org/TR/SVG11/paths.html#PathData -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)] -#[allow(missing_docs)] -#[repr(C, u8)] -pub enum PathCommand { - /// The unknown type. - /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN - Unknown, - /// The "moveto" command. - MoveTo { point: CoordPair, absolute: bool }, - /// The "lineto" command. - LineTo { point: CoordPair, absolute: bool }, - /// The horizontal "lineto" command. - HorizontalLineTo { x: CSSFloat, absolute: bool }, - /// The vertical "lineto" command. - VerticalLineTo { y: CSSFloat, absolute: bool }, - /// The cubic Bézier curve command. - CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool }, - /// The smooth curve command. - SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool }, - /// The quadratic Bézier curve command. - QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool }, - /// The smooth quadratic Bézier curve command. - SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool }, - /// The elliptical arc curve command. - EllipticalArc { - rx: CSSFloat, - ry: CSSFloat, - angle: CSSFloat, - large_arc_flag: bool, - sweep_flag: bool, - point: CoordPair, - absolute: bool - }, - /// The "closepath" command. - ClosePath, -} - -impl ToCss for PathCommand { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write - { - use self::PathCommand::*; - match *self { - Unknown => dest.write_str("X"), - ClosePath => dest.write_str("Z"), - MoveTo { point, absolute } => { - dest.write_char(if absolute { 'M' } else { 'm' })?; - dest.write_char(' ')?; - point.to_css(dest) - } - LineTo { point, absolute } => { - dest.write_char(if absolute { 'L' } else { 'l' })?; - dest.write_char(' ')?; - point.to_css(dest) - } - CurveTo { control1, control2, point, absolute } => { - dest.write_char(if absolute { 'C' } else { 'c' })?; - dest.write_char(' ')?; - control1.to_css(dest)?; - dest.write_char(' ')?; - control2.to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - QuadBezierCurveTo { control1, point, absolute } => { - dest.write_char(if absolute { 'Q' } else { 'q' })?; - dest.write_char(' ')?; - control1.to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => { - dest.write_char(if absolute { 'A' } else { 'a' })?; - dest.write_char(' ')?; - rx.to_css(dest)?; - dest.write_char(' ')?; - ry.to_css(dest)?; - dest.write_char(' ')?; - angle.to_css(dest)?; - dest.write_char(' ')?; - (large_arc_flag as i32).to_css(dest)?; - dest.write_char(' ')?; - (sweep_flag as i32).to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - HorizontalLineTo { x, absolute } => { - dest.write_char(if absolute { 'H' } else { 'h' })?; - dest.write_char(' ')?; - x.to_css(dest) - }, - VerticalLineTo { y, absolute } => { - dest.write_char(if absolute { 'V' } else { 'v' })?; - dest.write_char(' ')?; - y.to_css(dest) - }, - SmoothCurveTo { control2, point, absolute } => { - dest.write_char(if absolute { 'S' } else { 's' })?; - dest.write_char(' ')?; - control2.to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - SmoothQuadBezierCurveTo { point, absolute } => { - dest.write_char(if absolute { 'T' } else { 't' })?; - dest.write_char(' ')?; - point.to_css(dest) - }, - } - } -} - -/// The path coord type. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)] -#[repr(C)] -pub struct CoordPair(CSSFloat, CSSFloat); - -impl CoordPair { - /// Create a CoordPair. - #[inline] - pub fn new(x: CSSFloat, y: CSSFloat) -> Self { - CoordPair(x, y) - } -} - -/// Parse a pair of numbers into CoordPair. -fn parse_coord(iter: &mut Peekable) -> Result { - let x = parse_number(iter)?; - skip_comma_wsp(iter); - let y = parse_number(iter)?; - Ok(CoordPair::new(x, y)) -} - -/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed -/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating -/// point number. In other words, the logic here is similar with that of -/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the -/// input is a Peekable and we only accept an integer of a floating point number. -/// -/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF -fn parse_number(iter: &mut Peekable) -> Result { - // 1. Check optional sign. - let sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') { - if iter.next().unwrap() == '-' { -1. } else { 1. } - } else { - 1. - }; - - // 2. Check integer part. - let mut integral_part: f64 = 0.; - let got_dot = if !iter.peek().map_or(false, |&n: &char| n == '.') { - // If the first digit in integer part is neither a dot nor a digit, this is not a number. - if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) { - return Err(()); - } - - while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { - integral_part = - integral_part * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64; - } - - iter.peek().map_or(false, |&n: &char| n == '.') - } else { - true - }; - - // 3. Check fractional part. - let mut fractional_part: f64 = 0.; - if got_dot { - // Consume '.'. - iter.next(); - // If the first digit in fractional part is not a digit, this is not a number. - if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) { - return Err(()); - } - - let mut factor = 0.1; - while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { - fractional_part += iter.next().unwrap().to_digit(10).unwrap() as f64 * factor; - factor *= 0.1; - } - } - - let mut value = sign * (integral_part + fractional_part); - - // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to - // treat the numbers after 'E' or 'e' are in the exponential part. - if iter.peek().map_or(false, |&exp: &char| exp == 'E' || exp == 'e') { - // Consume 'E' or 'e'. - iter.next(); - let exp_sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') { - if iter.next().unwrap() == '-' { -1. } else { 1. } - } else { - 1. - }; - - let mut exp: f64 = 0.; - while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { - exp = exp * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64; - } - - value *= f64::powf(10., exp * exp_sign); - } - - if value.is_finite() { - Ok(value.min(::std::f32::MAX as f64).max(::std::f32::MIN as f64) as CSSFloat) - } else { - Err(()) - } -} - -/// Skip all svg whitespaces, and return true if |iter| hasn't finished. -#[inline] -fn skip_wsp(iter: &mut Peekable) -> bool { - // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}. - // However, SVG 2 has one extra whitespace: \u{C}. - // Therefore, we follow the newest spec for the definition of whitespace, - // i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}, by is_ascii_whitespace(). - while iter.peek().map_or(false, |c: &char| c.is_ascii_whitespace()) { - iter.next(); - } - iter.peek().is_some() -} - -/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished. -#[inline] -fn skip_comma_wsp(iter: &mut Peekable) -> bool { - if !skip_wsp(iter) { - return false; - } - - if *iter.peek().unwrap() != ',' { - return true; - } - iter.next(); - - skip_wsp(iter) -} diff --git a/servo/components/style/values/specified/svg_path.rs b/servo/components/style/values/specified/svg_path.rs new file mode 100644 index 000000000000..8d92e94aee0c --- /dev/null +++ b/servo/components/style/values/specified/svg_path.rs @@ -0,0 +1,517 @@ +/* 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/. */ + +//! Specified types for SVG Path. + +use cssparser::Parser; +use parser::{Parse, ParserContext}; +use std::fmt::{self, Write}; +use std::iter::Peekable; +use std::str::Chars; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use style_traits::values::SequenceWriter; +use values::CSSFloat; + + +/// The SVG path data. +/// +/// https://www.w3.org/TR/SVG11/paths.html#PathData +#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)] +pub struct SVGPathData(Box<[PathCommand]>); + +impl SVGPathData { + /// Return SVGPathData by a slice of PathCommand. + #[inline] + pub fn new(cmd: Box<[PathCommand]>) -> Self { + debug_assert!(!cmd.is_empty()); + SVGPathData(cmd) + } + + /// Get the array of PathCommand. + #[inline] + pub fn commands(&self) -> &[PathCommand] { + debug_assert!(!self.0.is_empty()); + &self.0 + } +} + +impl ToCss for SVGPathData { + #[inline] + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write + { + dest.write_char('"')?; + { + let mut writer = SequenceWriter::new(dest, " "); + for command in self.0.iter() { + writer.item(command)?; + } + } + dest.write_char('"') + } +} + +impl Parse for SVGPathData { + // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make + // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.) + // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident + // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable + // str::Char iterator to check each character. + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + let location = input.current_source_location(); + let path_string = input.expect_string()?.as_ref(); + if path_string.is_empty() { + // Treat an empty string as invalid, so we will not set it. + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + // Parse the svg path string as multiple sub-paths. + let mut path_parser = PathParser::new(path_string); + while skip_wsp(&mut path_parser.chars) { + if path_parser.parse_subpath().is_err() { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } + + Ok(SVGPathData::new(path_parser.path.into_boxed_slice())) + } +} + + +/// The SVG path command. +/// The fields of these commands are self-explanatory, so we skip the documents. +/// Note: the index of the control points, e.g. control1, control2, are mapping to the control +/// points of the Bézier curve in the spec. +/// +/// https://www.w3.org/TR/SVG11/paths.html#PathData +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)] +#[allow(missing_docs)] +#[repr(C, u8)] +pub enum PathCommand { + /// The unknown type. + /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN + Unknown, + /// The "moveto" command. + MoveTo { point: CoordPair, absolute: bool }, + /// The "lineto" command. + LineTo { point: CoordPair, absolute: bool }, + /// The horizontal "lineto" command. + HorizontalLineTo { x: CSSFloat, absolute: bool }, + /// The vertical "lineto" command. + VerticalLineTo { y: CSSFloat, absolute: bool }, + /// The cubic Bézier curve command. + CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool }, + /// The smooth curve command. + SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool }, + /// The quadratic Bézier curve command. + QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool }, + /// The smooth quadratic Bézier curve command. + SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool }, + /// The elliptical arc curve command. + EllipticalArc { + rx: CSSFloat, + ry: CSSFloat, + angle: CSSFloat, + large_arc_flag: bool, + sweep_flag: bool, + point: CoordPair, + absolute: bool + }, + /// The "closepath" command. + ClosePath, +} + +impl ToCss for PathCommand { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write + { + use self::PathCommand::*; + match *self { + Unknown => dest.write_str("X"), + ClosePath => dest.write_str("Z"), + MoveTo { point, absolute } => { + dest.write_char(if absolute { 'M' } else { 'm' })?; + dest.write_char(' ')?; + point.to_css(dest) + } + LineTo { point, absolute } => { + dest.write_char(if absolute { 'L' } else { 'l' })?; + dest.write_char(' ')?; + point.to_css(dest) + } + CurveTo { control1, control2, point, absolute } => { + dest.write_char(if absolute { 'C' } else { 'c' })?; + dest.write_char(' ')?; + control1.to_css(dest)?; + dest.write_char(' ')?; + control2.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + QuadBezierCurveTo { control1, point, absolute } => { + dest.write_char(if absolute { 'Q' } else { 'q' })?; + dest.write_char(' ')?; + control1.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => { + dest.write_char(if absolute { 'A' } else { 'a' })?; + dest.write_char(' ')?; + rx.to_css(dest)?; + dest.write_char(' ')?; + ry.to_css(dest)?; + dest.write_char(' ')?; + angle.to_css(dest)?; + dest.write_char(' ')?; + (large_arc_flag as i32).to_css(dest)?; + dest.write_char(' ')?; + (sweep_flag as i32).to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + HorizontalLineTo { x, absolute } => { + dest.write_char(if absolute { 'H' } else { 'h' })?; + dest.write_char(' ')?; + x.to_css(dest) + }, + VerticalLineTo { y, absolute } => { + dest.write_char(if absolute { 'V' } else { 'v' })?; + dest.write_char(' ')?; + y.to_css(dest) + }, + SmoothCurveTo { control2, point, absolute } => { + dest.write_char(if absolute { 'S' } else { 's' })?; + dest.write_char(' ')?; + control2.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + SmoothQuadBezierCurveTo { point, absolute } => { + dest.write_char(if absolute { 'T' } else { 't' })?; + dest.write_char(' ')?; + point.to_css(dest) + }, + } + } +} + + +/// The path coord type. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)] +#[repr(C)] +pub struct CoordPair(CSSFloat, CSSFloat); + +impl CoordPair { + /// Create a CoordPair. + #[inline] + pub fn new(x: CSSFloat, y: CSSFloat) -> Self { + CoordPair(x, y) + } +} + + +/// SVG Path parser. +struct PathParser<'a> { + chars: Peekable>, + path: Vec, +} + +macro_rules! parse_arguments { + ( + $parser:ident, + $abs:ident, + $enum:ident, + [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ] + ) => { + { + loop { + let $para = $func(&mut $parser.chars)?; + $( + skip_comma_wsp(&mut $parser.chars); + let $other_para = $other_func(&mut $parser.chars)?; + )* + $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs }); + + // End of string or the next character is a possible new command. + if !skip_wsp(&mut $parser.chars) || + $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) { + break; + } + skip_comma_wsp(&mut $parser.chars); + } + Ok(()) + } + } +} + +impl<'a> PathParser<'a> { + /// Return a PathParser. + #[inline] + fn new(string: &'a str) -> Self { + PathParser { + chars: string.chars().peekable(), + path: Vec::new(), + } + } + + /// Parse a sub-path. + fn parse_subpath(&mut self) -> Result<(), ()> { + // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path + // (i.e. not a valid moveto-drawto-command-group). + self.parse_moveto()?; + + // Handle other commands. + loop { + skip_wsp(&mut self.chars); + if self.chars.peek().map_or(true, |m| *m == 'M' || *m == 'm') { + break; + } + + match self.chars.next() { + Some(command) => { + let abs = command.is_uppercase(); + macro_rules! parse_command { + ( $($($p:pat)|+ => $parse_func:ident,)* ) => { + match command { + $( + $($p)|+ => { + skip_wsp(&mut self.chars); + self.$parse_func(abs)?; + }, + )* + _ => return Err(()), + } + } + } + parse_command!( + 'Z' | 'z' => parse_closepath, + 'L' | 'l' => parse_lineto, + 'H' | 'h' => parse_h_lineto, + 'V' | 'v' => parse_v_lineto, + 'C' | 'c' => parse_curveto, + 'S' | 's' => parse_smooth_curveto, + 'Q' | 'q' => parse_quadratic_bezier_curveto, + 'T' | 't' => parse_smooth_quadratic_bezier_curveto, + 'A' | 'a' => parse_elliprical_arc, + ); + }, + _ => break, // no more commands. + } + } + Ok(()) + } + + /// Parse "moveto" command. + fn parse_moveto(&mut self) -> Result<(), ()> { + let command = match self.chars.next() { + Some(c) if c == 'M' || c == 'm' => c, + _ => return Err(()), + }; + + skip_wsp(&mut self.chars); + let point = parse_coord(&mut self.chars)?; + let absolute = command == 'M'; + self.path.push(PathCommand::MoveTo { point, absolute } ); + + // End of string or the next character is a possible new command. + if !skip_wsp(&mut self.chars) || + self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) { + return Ok(()); + } + skip_comma_wsp(&mut self.chars); + + // If a moveto is followed by multiple pairs of coordinates, the subsequent + // pairs are treated as implicit lineto commands. + self.parse_lineto(absolute) + } + + /// Parse "closepath" command. + fn parse_closepath(&mut self, _absolute: bool) -> Result<(), ()> { + self.path.push(PathCommand::ClosePath); + Ok(()) + } + + /// Parse "lineto" command. + fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> { + parse_arguments!(self, absolute, LineTo, [ point => parse_coord ]) + } + + /// Parse horizontal "lineto" command. + fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> { + parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ]) + } + + /// Parse vertical "lineto" command. + fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> { + parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ]) + } + + /// Parse cubic Bézier curve command. + fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> { + parse_arguments!(self, absolute, CurveTo, [ + control1 => parse_coord, control2 => parse_coord, point => parse_coord + ]) + } + + /// Parse smooth "curveto" command. + fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> { + parse_arguments!(self, absolute, SmoothCurveTo, [ + control2 => parse_coord, point => parse_coord + ]) + } + + /// Parse quadratic Bézier curve command. + fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { + parse_arguments!(self, absolute, QuadBezierCurveTo, [ + control1 => parse_coord, point => parse_coord + ]) + } + + /// Parse smooth quadratic Bézier curveto command. + fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { + parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ]) + } + + /// Parse elliptical arc curve command. + fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> { + // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). + let parse_flag = |iter: &mut Peekable| -> Result { + let value = match iter.peek() { + Some(c) if *c == '0' || *c == '1' => *c == '1', + _ => return Err(()), + }; + iter.next(); + Ok(value) + }; + parse_arguments!(self, absolute, EllipticalArc, [ + rx => parse_number, + ry => parse_number, + angle => parse_number, + large_arc_flag => parse_flag, + sweep_flag => parse_flag, + point => parse_coord + ]) + } +} + + +/// Parse a pair of numbers into CoordPair. +fn parse_coord(iter: &mut Peekable) -> Result { + let x = parse_number(iter)?; + skip_comma_wsp(iter); + let y = parse_number(iter)?; + Ok(CoordPair::new(x, y)) +} + +/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed +/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating +/// point number. In other words, the logic here is similar with that of +/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the +/// input is a Peekable and we only accept an integer of a floating point number. +/// +/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF +fn parse_number(iter: &mut Peekable) -> Result { + // 1. Check optional sign. + let sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') { + if iter.next().unwrap() == '-' { -1. } else { 1. } + } else { + 1. + }; + + // 2. Check integer part. + let mut integral_part: f64 = 0.; + let got_dot = if !iter.peek().map_or(false, |&n: &char| n == '.') { + // If the first digit in integer part is neither a dot nor a digit, this is not a number. + if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) { + return Err(()); + } + + while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { + integral_part = + integral_part * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64; + } + + iter.peek().map_or(false, |&n: &char| n == '.') + } else { + true + }; + + // 3. Check fractional part. + let mut fractional_part: f64 = 0.; + if got_dot { + // Consume '.'. + iter.next(); + // If the first digit in fractional part is not a digit, this is not a number. + if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) { + return Err(()); + } + + let mut factor = 0.1; + while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { + fractional_part += iter.next().unwrap().to_digit(10).unwrap() as f64 * factor; + factor *= 0.1; + } + } + + let mut value = sign * (integral_part + fractional_part); + + // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to + // treat the numbers after 'E' or 'e' are in the exponential part. + if iter.peek().map_or(false, |&exp: &char| exp == 'E' || exp == 'e') { + // Consume 'E' or 'e'. + iter.next(); + let exp_sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') { + if iter.next().unwrap() == '-' { -1. } else { 1. } + } else { + 1. + }; + + let mut exp: f64 = 0.; + while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { + exp = exp * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64; + } + + value *= f64::powf(10., exp * exp_sign); + } + + if value.is_finite() { + Ok(value.min(::std::f32::MAX as f64).max(::std::f32::MIN as f64) as CSSFloat) + } else { + Err(()) + } +} + +/// Skip all svg whitespaces, and return true if |iter| hasn't finished. +#[inline] +fn skip_wsp(iter: &mut Peekable) -> bool { + // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}. + // However, SVG 2 has one extra whitespace: \u{C}. + // Therefore, we follow the newest spec for the definition of whitespace, + // i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}, by is_ascii_whitespace(). + while iter.peek().map_or(false, |c: &char| c.is_ascii_whitespace()) { + iter.next(); + } + iter.peek().is_some() +} + +/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished. +#[inline] +fn skip_comma_wsp(iter: &mut Peekable) -> bool { + if !skip_wsp(iter) { + return false; + } + + if *iter.peek().unwrap() != ',' { + return true; + } + iter.next(); + + skip_wsp(iter) +} diff --git a/taskcluster/ci/valgrind/kind.yml b/taskcluster/ci/valgrind/kind.yml index 758ff5305ce9..04315c18c5f4 100644 --- a/taskcluster/ci/valgrind/kind.yml +++ b/taskcluster/ci/valgrind/kind.yml @@ -31,7 +31,6 @@ jobs: max-run-time: 72000 env: PERFHERDER_EXTRA_OPTIONS: valgrind - FORCE_GCC: '1' run: using: mozharness actions: [get-secrets build valgrind-test] diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py index 343f950be20f..a02eb5eb318d 100644 --- a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py @@ -167,9 +167,15 @@ class TestCapabilityMatching(MarionetteTestCase): self.marionette.start_session({"setWindowRect": True}) def test_timeouts(self): - timeouts = {u"implicit": 123, u"pageLoad": 456, u"script": 789} - caps = {"timeouts": timeouts} - self.marionette.start_session(caps) + for value in ["", 2.5, {}, []]: + print(" type {}".format(type(value))) + with self.assertRaises(SessionNotCreatedException): + self.marionette.start_session({"timeouts": {"pageLoad": value}}) + + self.delete_session() + + timeouts = {"implicit": 0, "pageLoad": 2.0, "script": 2**53 - 1} + self.marionette.start_session({"timeouts": timeouts}) self.assertIn("timeouts", self.marionette.session_capabilities) self.assertDictEqual(self.marionette.session_capabilities["timeouts"], timeouts) self.assertDictEqual(self.marionette._send_message("WebDriver:GetTimeouts"), timeouts) diff --git a/testing/marionette/test/unit/test_capabilities.js b/testing/marionette/test/unit/test_capabilities.js index 8d8422753486..082f55aee25f 100644 --- a/testing/marionette/test/unit/test_capabilities.js +++ b/testing/marionette/test/unit/test_capabilities.js @@ -40,9 +40,9 @@ add_test(function test_Timeouts_toJSON() { add_test(function test_Timeouts_fromJSON() { let json = { - implicit: 10, - pageLoad: 20, - script: 30, + implicit: 0, + pageLoad: 2.0, + script: Number.MAX_SAFE_INTEGER, }; let ts = Timeouts.fromJSON(json); equal(ts.implicit, json.implicit); @@ -55,7 +55,6 @@ add_test(function test_Timeouts_fromJSON() { add_test(function test_Timeouts_fromJSON_unrecognised_field() { let json = { sessionId: "foobar", - script: 42, }; try { Timeouts.fromJSON(json); @@ -67,23 +66,17 @@ add_test(function test_Timeouts_fromJSON_unrecognised_field() { run_next_test(); }); -add_test(function test_Timeouts_fromJSON_invalid_type() { - try { - Timeouts.fromJSON({script: "foobar"}); - } catch (e) { - equal(e.name, InvalidArgumentError.name); - equal(e.message, "Expected [object String] \"script\" to be a positive integer, got [object String] \"foobar\""); +add_test(function test_Timeouts_fromJSON_invalid_types() { + for (let value of [null, [], {}, false, "10", 2.5]) { + Assert.throws(() => Timeouts.fromJSON({"script": value}), /InvalidArgumentError/); } run_next_test(); }); add_test(function test_Timeouts_fromJSON_bounds() { - try { - Timeouts.fromJSON({script: -42}); - } catch (e) { - equal(e.name, InvalidArgumentError.name); - equal(e.message, "Expected [object String] \"script\" to be a positive integer, got [object Number] -42"); + for (let value of [-1, Number.MAX_SAFE_INTEGER + 1]) { + Assert.throws(() => Timeouts.fromJSON({"script": value}), /InvalidArgumentError/); } run_next_test(); diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index bec02b294855..ea0cbe189434 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -300278,11 +300278,6 @@ {} ] ], - "service-workers/service-worker/resources/update-top-level-worker.py": [ - [ - {} - ] - ], "service-workers/service-worker/resources/update-worker.py": [ [ {} @@ -390582,12 +390577,6 @@ {} ] ], - "service-workers/service-worker/update-top-level.https.html": [ - [ - "/service-workers/service-worker/update-top-level.https.html", - {} - ] - ], "service-workers/service-worker/update.https.html": [ [ "/service-workers/service-worker/update.https.html", @@ -423120,6 +423109,14 @@ {} ] ], + "webdriver/tests/set_timeouts/user_prompts.py": [ + [ + "/webdriver/tests/set_timeouts/user_prompts.py", + { + "timeout": "long" + } + ] + ], "webdriver/tests/set_window_rect/set.py": [ [ "/webdriver/tests/set_window_rect/set.py", @@ -559724,7 +559721,7 @@ "support" ], "css/css-values/reference/vh_not_refreshing_on_chrome-ref.html": [ - "32ce9ada155649e9a4935e1abdddc3d3aadfca73", + "279d1c69b9f7fbe60edb55b7e8a5d507eda24936", "support" ], "css/css-values/reference/vh_not_refreshing_on_chrome_iframe-ref.html": [ @@ -559916,7 +559913,7 @@ "support" ], "css/css-values/support/vh_not_refreshing_on_chrome_iframe.html": [ - "c58ec57a58f29d4774b199ca82dd6d16607d4735", + "8d8e9b49d4aa3d9804a176852288e32ccaaa47d8", "support" ], "css/css-values/unset-value-storage.html": [ @@ -559984,7 +559981,7 @@ "support" ], "css/css-values/vh_not_refreshing_on_chrome.html": [ - "b4e0a413ab975dea429ca35642dc075a54127117", + "52a45a114c85bf96a175ca583d8a2b6f54b9ab6c", "reftest" ], "css/css-values/viewport-relative-lengths-scaled-viewport.html": [ @@ -632495,10 +632492,6 @@ "8aaa5ca934457714ee0e529ad4b2b1740d9758dd", "support" ], - "service-workers/service-worker/resources/update-top-level-worker.py": [ - "f77ef284ac0745bd6d31e642742438766f14e32e", - "support" - ], "service-workers/service-worker/resources/update-worker.py": [ "bc9b32ad3e68870d9f540524e70cd7947346e5c8", "support" @@ -632679,10 +632672,6 @@ "d8ed94f776650c8a40ba82df9ca5e909b460bb79", "testharness" ], - "service-workers/service-worker/update-top-level.https.html": [ - "e382028b44a9d19b26b3c15a3bba17fa6a0d9bcb", - "testharness" - ], "service-workers/service-worker/update.https.html": [ "6717d4d7ac289c8a18b1500e21795fd16c5321e7", "testharness" @@ -648604,7 +648593,7 @@ "wdspec" ], "webdriver/tests/new_session/invalid_capabilities.py": [ - "83f93ea22f7ed28fa28ab05d36387df828716026", + "f31ce3b8b6fd5f8e4a9ff4d0137debdb7dacdea4", "wdspec" ], "webdriver/tests/new_session/merge.py": [ @@ -648628,7 +648617,7 @@ "support" ], "webdriver/tests/new_session/support/create.py": [ - "85ae1cd4ea85e0a1e0d712b1a7803d6066ab8739", + "475fe5a424fe609f0a7e55164e56378e229e4885", "support" ], "webdriver/tests/new_session/timeouts.py": [ @@ -648668,7 +648657,11 @@ "support" ], "webdriver/tests/set_timeouts/set.py": [ - "e603e217ec7d73bf7bc59f1d2e8687a89c818c47", + "a78ab2e68e82ba28c15748bb98239b3d232dc9f1", + "wdspec" + ], + "webdriver/tests/set_timeouts/user_prompts.py": [ + "a98d87e9b2e2ca252a3ed7cf215a20bd1c299818", "wdspec" ], "webdriver/tests/set_window_rect/__init__.py": [ diff --git a/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-element-0/legend-position-relative.html.ini b/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-element-0/legend-position-relative.html.ini new file mode 100644 index 000000000000..1b8ab9c5dc98 --- /dev/null +++ b/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-element-0/legend-position-relative.html.ini @@ -0,0 +1,3 @@ +[legend-position-relative.html] + expected: + if webrender: FAIL diff --git a/testing/web-platform/meta/webdriver/tests/maximize_window/user_prompts.py.ini b/testing/web-platform/meta/webdriver/tests/maximize_window/user_prompts.py.ini deleted file mode 100644 index 4ec512f4915c..000000000000 --- a/testing/web-platform/meta/webdriver/tests/maximize_window/user_prompts.py.ini +++ /dev/null @@ -1,3 +0,0 @@ -[user_prompts.py] - disabled: - if os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1399633 diff --git a/testing/web-platform/meta/webdriver/tests/new_session/create_alwaysMatch.py.ini b/testing/web-platform/meta/webdriver/tests/new_session/create_alwaysMatch.py.ini deleted file mode 100644 index 8a8f63934dce..000000000000 --- a/testing/web-platform/meta/webdriver/tests/new_session/create_alwaysMatch.py.ini +++ /dev/null @@ -1,4 +0,0 @@ -[create_alwaysMatch.py] - [test_valid[timeouts-value10\]] - expected: FAIL - diff --git a/testing/web-platform/meta/webdriver/tests/new_session/create_firstMatch.py.ini b/testing/web-platform/meta/webdriver/tests/new_session/create_firstMatch.py.ini deleted file mode 100644 index afc8153db3f3..000000000000 --- a/testing/web-platform/meta/webdriver/tests/new_session/create_firstMatch.py.ini +++ /dev/null @@ -1,4 +0,0 @@ -[create_firstMatch.py] - [test_valid[timeouts-value10\]] - expected: FAIL - diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update-top-level-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/update-top-level-worker.py deleted file mode 100644 index f77ef284ac07..000000000000 --- a/testing/web-platform/tests/service-workers/service-worker/resources/update-top-level-worker.py +++ /dev/null @@ -1,18 +0,0 @@ -import time - -def main(request, response): - # no-cache itself to ensure the user agent finds a new version for each update. - headers = [('Cache-Control', 'no-cache, must-revalidate'), - ('Pragma', 'no-cache')] - content_type = 'application/javascript' - - headers.append(('Content-Type', content_type)) - - body = ''' -let promise = self.registration.update() -onmessage = (evt) => { - promise.then(r => { - evt.source.postMessage(self.registration === r ? 'PASS' : 'FAIL'); - }); -};''' - return headers, '/* %s %s */ %s' % (time.time(), time.clock(), body) diff --git a/testing/web-platform/tests/service-workers/service-worker/update-top-level.https.html b/testing/web-platform/tests/service-workers/service-worker/update-top-level.https.html deleted file mode 100644 index e382028b44a9..000000000000 --- a/testing/web-platform/tests/service-workers/service-worker/update-top-level.https.html +++ /dev/null @@ -1,32 +0,0 @@ - -Service Worker: Registration update() - - - - diff --git a/testing/web-platform/tests/webdriver/tests/new_session/invalid_capabilities.py b/testing/web-platform/tests/webdriver/tests/new_session/invalid_capabilities.py index 83f93ea22f7e..f31ce3b8b6fd 100644 --- a/testing/web-platform/tests/webdriver/tests/new_session/invalid_capabilities.py +++ b/testing/web-platform/tests/webdriver/tests/new_session/invalid_capabilities.py @@ -1,8 +1,10 @@ import pytest -from tests.support.asserts import assert_error from conftest import product, flatten +from tests.new_session.support.create import invalid_data, invalid_extensions +from tests.support.asserts import assert_error + @pytest.mark.parametrize("value", [None, 1, "{}", []]) def test_invalid_capabilites(new_session, value): @@ -26,29 +28,6 @@ def test_invalid_first_match(new_session, add_browser_capabilities, value): assert_error(response, "invalid argument") -invalid_data = [ - ("acceptInsecureCerts", [1, [], {}, "false"]), - ("browserName", [1, [], {}, False]), - ("browserVersion", [1, [], {}, False]), - ("platformName", [1, [], {}, False]), - ("pageLoadStrategy", [1, [], {}, False, "invalid", "NONE", "Eager", "eagerblah", "interactive", - " eager", "eager "]), - ("proxy", [1, [], "{}", {"proxyType": "SYSTEM"}, {"proxyType": "systemSomething"}, - {"proxy type": "pac"}, {"proxy-Type": "system"}, {"proxy_type": "system"}, - {"proxytype": "system"}, {"PROXYTYPE": "system"}, {"proxyType": None}, - {"proxyType": 1}, {"proxyType": []}, {"proxyType": {"value": "system"}}, - {" proxyType": "system"}, {"proxyType ": "system"}, {"proxyType ": " system"}, - {"proxyType": "system "}]), - ("timeouts", [1, [], "{}", False, {"pageLOAD": 10}, {"page load": 10}, - {"page load": 10}, {"pageLoad": "10"}, {"pageLoad": {"value": 10}}, - {"invalid": 10}, {"pageLoad": -1}, {"pageLoad": 2**64}, - {"pageLoad": None}, {"pageLoad": 1.1}, {"pageLoad": 10, "invalid": 10}, - {" pageLoad": 10}, {"pageLoad ": 10}]), - ("unhandledPromptBehavior", [1, [], {}, False, "DISMISS", "dismissABC", "Accept", - " dismiss", "dismiss "]) -] - - @pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}}, lambda key, value: {"firstMatch": [{key: value}]}]) @pytest.mark.parametrize("key,value", flatten(product(*item) for item in invalid_data)) @@ -63,31 +42,6 @@ def test_invalid_values(new_session, add_browser_capabilities, body, key, value) assert_error(response, "invalid argument") -invalid_extensions = [ - "firefox", - "firefox_binary", - "firefoxOptions", - "chromeOptions", - "automaticInspection", - "automaticProfiling", - "platform", - "version", - "browser", - "platformVersion", - "javascriptEnabled", - "nativeEvents", - "seleniumProtocol", - "profile", - "trustAllSSLCertificates", - "initialBrowserUrl", - "requireWindowFocus", - "logFile", - "logLevel", - "safari.options", - "ensureCleanSession", -] - - @pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}}, lambda key, value: {"firstMatch": [{key: value}]}]) @pytest.mark.parametrize("key", invalid_extensions) diff --git a/testing/web-platform/tests/webdriver/tests/new_session/support/create.py b/testing/web-platform/tests/webdriver/tests/new_session/support/create.py index 85ae1cd4ea85..475fe5a424fe 100644 --- a/testing/web-platform/tests/webdriver/tests/new_session/support/create.py +++ b/testing/web-platform/tests/webdriver/tests/new_session/support/create.py @@ -1,15 +1,130 @@ # Note that we can only test things here all implementations must support valid_data = [ - ("acceptInsecureCerts", [False, None]), - ("browserName", [None]), - ("browserVersion", [None]), - ("platformName", [None]), - ("pageLoadStrategy", ["none", "eager", "normal", None]), - ("proxy", [None]), - ("timeouts", [{"script": 0, "pageLoad": 2.0, "implicit": 2**53 - 1}, - {"script": 50, "pageLoad": 25}, - {"script": 500}, - {}]), - ("unhandledPromptBehavior", ["dismiss", "accept", None]), - ("test:extension", [True, "abc", 123, [], {"key": "value"}, None]), + ("acceptInsecureCerts", [ + False, None, + ]), + ("browserName", [ + None, + ]), + ("browserVersion", [ + None, + ]), + ("platformName", [ + None, + ]), + ("pageLoadStrategy", [ + None, + "none", + "eager", + "normal", + ]), + ("proxy", [ + None, + ]), + ("timeouts", [ + None, {}, + {"script": 0, "pageLoad": 2.0, "implicit": 2**53 - 1}, + {"script": 50, "pageLoad": 25}, + {"script": 500}, + ]), + ("unhandledPromptBehavior", [ + "dismiss", + "accept", + None, + ]), + ("test:extension", [ + None, False, "abc", 123, [], + {"key": "value"}, + ]), +] + +invalid_data = [ + ("acceptInsecureCerts", [ + 1, [], {}, "false", + ]), + ("browserName", [ + 1, [], {}, False, + ]), + ("browserVersion", [ + 1, [], {}, False, + ]), + ("platformName", [ + 1, [], {}, False, + ]), + ("pageLoadStrategy", [ + 1, [], {}, False, + "invalid", + "NONE", + "Eager", + "eagerblah", + "interactive", + " eager", + "eager "]), + ("proxy", [ + 1, [], "{}", + {"proxyType": "SYSTEM"}, + {"proxyType": "systemSomething"}, + {"proxy type": "pac"}, + {"proxy-Type": "system"}, + {"proxy_type": "system"}, + {"proxytype": "system"}, + {"PROXYTYPE": "system"}, + {"proxyType": None}, + {"proxyType": 1}, + {"proxyType": []}, + {"proxyType": {"value": "system"}}, + {" proxyType": "system"}, + {"proxyType ": "system"}, + {"proxyType ": " system"}, + {"proxyType": "system "}, + ]), + ("timeouts", [ + 1, [], "{}", False, + {"invalid": 10}, + {"PAGELOAD": 10}, + {"page load": 10}, + {" pageLoad": 10}, + {"pageLoad ": 10}, + {"pageLoad": None}, + {"pageLoad": False}, + {"pageLoad": []}, + {"pageLoad": "10"}, + {"pageLoad": 2.5}, + {"pageLoad": -1}, + {"pageLoad": 2**53}, + {"pageLoad": {"value": 10}}, + {"pageLoad": 10, "invalid": 10}, + ]), + ("unhandledPromptBehavior", [ + 1, [], {}, False, + "DISMISS", + "dismissABC", + "Accept", + " dismiss", + "dismiss ", + ]) +] + +invalid_extensions = [ + "firefox", + "firefox_binary", + "firefoxOptions", + "chromeOptions", + "automaticInspection", + "automaticProfiling", + "platform", + "version", + "browser", + "platformVersion", + "javascriptEnabled", + "nativeEvents", + "seleniumProtocol", + "profile", + "trustAllSSLCertificates", + "initialBrowserUrl", + "requireWindowFocus", + "logFile", + "logLevel", + "safari.options", + "ensureCleanSession", ] diff --git a/testing/web-platform/tests/webdriver/tests/set_timeouts/set.py b/testing/web-platform/tests/webdriver/tests/set_timeouts/set.py index e603e217ec7d..a78ab2e68e82 100644 --- a/testing/web-platform/tests/webdriver/tests/set_timeouts/set.py +++ b/testing/web-platform/tests/webdriver/tests/set_timeouts/set.py @@ -1,3 +1,5 @@ +import pytest + from webdriver.transport import Response from tests.support.asserts import assert_error, assert_success @@ -16,14 +18,60 @@ def test_null_parameter_value(session, http): def test_null_response_value(session): - response = set_timeouts(session, {"implicit": 1000}) + timeouts = {"implicit": 10, "pageLoad": 10, "script": 10} + response = set_timeouts(session, timeouts) value = assert_success(response) assert value is None - response = set_timeouts(session, {"pageLoad": 1000}) - value = assert_success(response) - assert value is None - response = set_timeouts(session, {"script": 1000}) - value = assert_success(response) - assert value is None +@pytest.mark.parametrize("value", [1, "{}", False, []]) +def test_parameters_invalid(session, value): + response = set_timeouts(session, value) + assert_error(response, "invalid argument") + + +def test_parameters_empty_no_change(session): + original = session.timeouts._get() + + response = set_timeouts(session, {}) + assert_success(response) + + assert session.timeouts._get() == original + + +def test_key_invalid(session): + response = set_timeouts(session, {"foo": 1000}) + assert_error(response, "invalid argument") + + +@pytest.mark.parametrize("typ", ["implicit", "pageLoad", "script"]) +@pytest.mark.parametrize("value", [0, 2.0, 2**53 - 1]) +def test_positive_integer(session, typ, value): + response = set_timeouts(session, {typ: value}) + assert_success(response) + + assert session.timeouts._get(typ) == value + + +@pytest.mark.parametrize("typ", ["implicit", "pageLoad", "script"]) +@pytest.mark.parametrize("value", [None, [], {}, False, "10"]) +def test_value_invalid_types(session, typ, value): + response = set_timeouts(session, {typ: value}) + assert_error(response, "invalid argument") + + +@pytest.mark.parametrize("typ", ["implicit", "pageLoad", "script"]) +@pytest.mark.parametrize("value", [-1, 2.5, 2**53]) +def test_value_positive_integer(session, typ, value): + response = set_timeouts(session, {typ: value}) + assert_error(response, "invalid argument") + + +def test_set_all_fields(session): + timeouts = {"implicit": 10, "pageLoad": 20, "script": 30} + response = set_timeouts(session, timeouts) + assert_success(response) + + assert session.timeouts.implicit == 10 + assert session.timeouts.page_load == 20 + assert session.timeouts.script == 30 diff --git a/testing/web-platform/tests/webdriver/tests/set_timeouts/user_prompts.py b/testing/web-platform/tests/webdriver/tests/set_timeouts/user_prompts.py new file mode 100644 index 000000000000..a98d87e9b2e2 --- /dev/null +++ b/testing/web-platform/tests/webdriver/tests/set_timeouts/user_prompts.py @@ -0,0 +1,62 @@ +# META: timeout=long + +import pytest + +from tests.support.asserts import assert_success + + +def set_timeouts(session, timeouts): + return session.transport.send( + "POST", "session/{session_id}/timeouts".format(**vars(session)), + timeouts) + + +@pytest.fixture +def check_user_prompt_not_closed(session, create_dialog): + def check_user_prompt_not_closed(dialog_type): + create_dialog(dialog_type, text=dialog_type) + + response = set_timeouts(session, {"script": 100}) + assert_success(response) + + assert session.alert.text == dialog_type + session.alert.dismiss() + + assert session.timeouts.script == 100 + + return check_user_prompt_not_closed + + +@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"}) +@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"]) +def test_accept(check_user_prompt_not_closed, dialog_type): + check_user_prompt_not_closed(dialog_type) + + +@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"}) +@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"]) +def test_accept_and_notify(check_user_prompt_not_closed, dialog_type): + check_user_prompt_not_closed(dialog_type) + + +@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"}) +@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"]) +def test_dismiss(check_user_prompt_not_closed, dialog_type): + check_user_prompt_not_closed(dialog_type) + + +@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"}) +@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"]) +def test_dismiss_and_notify(check_user_prompt_not_closed, dialog_type): + check_user_prompt_not_closed(dialog_type) + + +@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"}) +@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"]) +def test_ignore(check_user_prompt_not_closed, dialog_type): + check_user_prompt_not_closed(dialog_type) + + +@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"]) +def test_default(check_user_prompt_not_closed, dialog_type): + check_user_prompt_not_closed(dialog_type) diff --git a/testing/webdriver/src/capabilities.rs b/testing/webdriver/src/capabilities.rs index 92813568cd28..eeea7eb1c002 100644 --- a/testing/webdriver/src/capabilities.rs +++ b/testing/webdriver/src/capabilities.rs @@ -1,3 +1,4 @@ +use common::MAX_SAFE_INTEGER; use error::{ErrorStatus, WebDriverError, WebDriverResult}; use serde_json::{Map, Value}; use std::convert::From; @@ -324,14 +325,26 @@ impl SpecNewSessionParameters { match &**key { x @ "script" | x @ "pageLoad" | x @ "implicit" => { let timeout = try_opt!( - value.as_i64(), + value.as_f64(), ErrorStatus::InvalidArgument, - format!("{} timeouts value is not an integer: {}", x, value) + format!("{} timeouts value is not a number: {}", x, value) ); - if timeout < 0 { + if timeout < 0.0 || timeout.fract() != 0.0 { return Err(WebDriverError::new( ErrorStatus::InvalidArgument, - format!("{} timeouts value is negative: {}", x, timeout), + format!( + "'{}' timeouts value is not a positive Integer: {}", + x, timeout + ), + )); + } + if (timeout as u64) > MAX_SAFE_INTEGER { + return Err(WebDriverError::new( + ErrorStatus::InvalidArgument, + format!( + "'{}' timeouts value is greater than maximum safe integer: {}", + x, timeout + ), )); } } diff --git a/testing/webdriver/src/command.rs b/testing/webdriver/src/command.rs index 64874292daef..3592f0695ce6 100644 --- a/testing/webdriver/src/command.rs +++ b/testing/webdriver/src/command.rs @@ -1,7 +1,7 @@ use actions::ActionSequence; use capabilities::{BrowserCapabilities, Capabilities, CapabilitiesMatching, LegacyNewSessionParameters, SpecNewSessionParameters}; -use common::{Date, FrameId, LocatorStrategy, WebElement}; +use common::{Date, FrameId, LocatorStrategy, WebElement, MAX_SAFE_INTEGER}; use error::{ErrorStatus, WebDriverError, WebDriverResult}; use httpapi::{Route, VoidWebDriverExtensionRoute, WebDriverExtensionRoute}; use regex::Captures; @@ -492,15 +492,56 @@ pub struct TakeScreenshotParameters { } #[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct TimeoutsParameters { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, skip_serializing_if = "Option::is_none", deserialize_with = "deserialize_to_u64" + )] pub implicit: Option, - #[serde(rename = "pageLoad", skip_serializing_if = "Option::is_none")] + #[serde( + default, + rename = "pageLoad", + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_to_u64" + )] pub page_load: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, skip_serializing_if = "Option::is_none", deserialize_with = "deserialize_to_u64" + )] pub script: Option, } +fn deserialize_to_u64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt = Option::deserialize(deserializer)?.map(|value: f64| value); + let value = match opt { + Some(n) => { + if n < 0.0 || n.fract() != 0.0 { + return Err(de::Error::custom(format!( + "'{}' is not a positive Integer", + n + ))); + } + if (n as u64) > MAX_SAFE_INTEGER { + return Err(de::Error::custom(format!( + "'{}' is greater than maximum safe integer", + n + ))); + } + Some(n as u64) + } + None => { + return Err(de::Error::custom(format!( + "'null' is not a positive Integer" + ))); + } + }; + + Ok(value) +} + /// A top-level browsing context’s window rect is a dictionary of the /// [`screenX`], [`screenY`], `width`, and `height` attributes of the /// `WindowProxy`. @@ -972,26 +1013,27 @@ mod tests { #[test] fn test_json_timeout_parameters_with_values() { - let json = r#"{"implicit":1,"pageLoad":2,"script":3}"#; + let json = r#"{"implicit":0,"pageLoad":2.0,"script":9007199254740991}"#; let data = TimeoutsParameters { - implicit: Some(1u64), + implicit: Some(0u64), page_load: Some(2u64), - script: Some(3u64), + script: Some(9007199254740991u64), }; check_deserialize(&json, &data); } + #[test] + fn test_json_timeout_parameters_with_invalid_values() { + let json = r#"{"implicit":-1,"pageLoad":2.5,"script":9007199254740992}"#; + assert!(serde_json::from_str::(&json).is_err()); + } + #[test] fn test_json_timeout_parameters_with_optional_null_field() { let json = r#"{"implicit":null,"pageLoad":null,"script":null}"#; - let data = TimeoutsParameters { - implicit: None, - page_load: None, - script: None, - }; - check_deserialize(&json, &data); + assert!(serde_json::from_str::(&json).is_err()); } #[test] @@ -1006,24 +1048,6 @@ mod tests { check_deserialize(&json, &data); } - #[test] - fn test_json_timeout_parameters_with_invalid_implicit_value() { - let json = r#"{"implicit":1.1}"#; - assert!(serde_json::from_str::(&json).is_err()); - } - - #[test] - fn test_json_timeout_parameters_with_invalid_page_load_value() { - let json = r#"{"pageLoad":1.2}"#; - assert!(serde_json::from_str::(&json).is_err()); - } - - #[test] - fn test_json_timeout_parameters_with_invalid_script_value() { - let json = r#"{"script":1.3}"#; - assert!(serde_json::from_str::(&json).is_err()); - } - #[test] fn test_json_window_rect_parameters_with_values() { let json = r#"{"x":0,"y":1,"width":2,"height":3}"#; diff --git a/testing/webdriver/src/common.rs b/testing/webdriver/src/common.rs index ae1286c8eb81..09af2b2ae3e3 100644 --- a/testing/webdriver/src/common.rs +++ b/testing/webdriver/src/common.rs @@ -4,6 +4,8 @@ pub static ELEMENT_KEY: &'static str = "element-6066-11e4-a52e-4f735466cecf"; pub static FRAME_KEY: &'static str = "frame-075b-4da1-b6ba-e579c2d3230a"; pub static WINDOW_KEY: &'static str = "window-fcc6-11e5-b4f8-330a88ab9d7f"; +pub static MAX_SAFE_INTEGER: u64 = 9007199254740991; + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Cookie { pub name: String, diff --git a/toolkit/components/antitracking/AntiTrackingCommon.cpp b/toolkit/components/antitracking/AntiTrackingCommon.cpp index 8632f9b618c3..f0a69331acb0 100644 --- a/toolkit/components/antitracking/AntiTrackingCommon.cpp +++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp @@ -445,7 +445,7 @@ AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsIHttpChannel* aChannel return false; } - int32_t behavior = CookiesBehavior(channelPrincipal); + int32_t behavior = CookiesBehavior(toplevelPrincipal); if (behavior == nsICookieService::BEHAVIOR_ACCEPT) { LOG(("The cookie behavior pref mandates accepting all cookies!")); return true; @@ -495,7 +495,7 @@ AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsIHttpChannel* aChannel return false; } - parentPrincipal = loadInfo->TriggeringPrincipal(); + parentPrincipal = toplevelPrincipal; if (NS_WARN_IF(!parentPrincipal)) { LOG(("No triggering principal, this shouldn't be happening! Bail out early")); // Why we are here?!? diff --git a/toolkit/components/antitracking/test/browser/.eslintrc.js b/toolkit/components/antitracking/test/browser/.eslintrc.js index b1c842d8a6db..6594a5079b66 100644 --- a/toolkit/components/antitracking/test/browser/.eslintrc.js +++ b/toolkit/components/antitracking/test/browser/.eslintrc.js @@ -3,5 +3,9 @@ module.exports = { "extends": [ "plugin:mozilla/browser-test" - ] + ], + + "env": { + "webextensions": true, + }, }; diff --git a/toolkit/components/antitracking/test/browser/browser.ini b/toolkit/components/antitracking/test/browser/browser.ini index 1cf1cde38780..35fd653382de 100644 --- a/toolkit/components/antitracking/test/browser/browser.ini +++ b/toolkit/components/antitracking/test/browser/browser.ini @@ -23,6 +23,7 @@ support-files = server.sjs [browser_existingCookiesForSubresources.js] [browser_imageCache.js] support-files = image.sjs +[browser_onBeforeRequestNotificationForTrackingResources.js] [browser_onModifyRequestNotificationForTrackingResources.js] [browser_subResources.js] support-files = subResources.sjs diff --git a/toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js b/toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js new file mode 100644 index 000000000000..8fa8bb6a6b58 --- /dev/null +++ b/toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js @@ -0,0 +1,91 @@ +ChromeUtils.import("resource://gre/modules/Services.jsm"); + +/** + * This test ensures that onBeforeRequest is dispatched for webRequest loads that + * are blocked by tracking protection. It sets up a page with a third-party script + * resource on it that is blocked by TP, and sets up an onBeforeRequest listener + * which waits to be notified about that resource. The test would time out if the + * onBeforeRequest listener isn't called dispatched before the load is canceled. + */ + +let extension; +add_task(async function() { + extension = ExtensionTestUtils.loadExtension({ + manifest: {permissions: ["webRequest", "webRequestBlocking", "*://*/*"]}, + async background() { + let gExpectedResourcesSeen = 0; + function onBeforeRequest(details) { + let spec = details.url; + browser.test.log("Observed channel for " + spec); + // We would use TEST_3RD_PARTY_DOMAIN_TP here, but the variable is inaccessible + // since it is defined in head.js! + if (!spec.startsWith("https://tracking.example.com/")) { + return undefined; + } + if (spec.endsWith("empty.js")) { + browser.test.succeed("Correct resource observed"); + ++gExpectedResourcesSeen; + } else if (spec.endsWith("empty.js?redirect")) { + return {redirectUrl: spec.replace("empty.js?redirect", "head.js")}; + } else if (spec.endsWith("head.js")) { + ++gExpectedResourcesSeen; + } + if (gExpectedResourcesSeen == 2) { + browser.webRequest.onBeforeRequest.removeListener(onBeforeRequest); + browser.test.sendMessage("finish"); + } + return undefined; + } + + browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, + {urls: ["*://*/*"]}, + ["blocking"]); + browser.test.sendMessage("ready"); + }, + }); + await extension.startup(); + await extension.awaitMessage("ready"); +}); + +add_task(async function() { + info("Starting subResources test"); + + await SpecialPowers.flushPrefEnv(); + await SpecialPowers.pushPrefEnv({"set": [ + ["browser.contentblocking.enabled", true], + ["privacy.trackingprotection.enabled", true], + // the test doesn't open a private window, so we don't care about this pref's value + ["privacy.trackingprotection.pbmode.enabled", false], + // tracking annotations aren't needed in this test, only TP is needed + ["privacy.trackingprotection.annotate_channels", false], + // prevent the content blocking on-boarding UI to start mid-way through the test! + ["privacy.trackingprotection.introCount", ContentBlocking.MAX_INTROS], + ]}); + + await UrlClassifierTestUtils.addTestTrackers(); + + let promise = extension.awaitMessage("finish"); + + info("Creating a new tab"); + let tab = BrowserTestUtils.addTab(gBrowser, TEST_EMBEDDER_PAGE); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + await promise; + + info("Verify the number of tracking nodes found"); + await ContentTask.spawn(browser, + { expected: 3, + }, + async function(obj) { + is(content.document.blockedTrackingNodeCount, obj.expected, "Expected tracking nodes found"); + }); + + info("Removing the tab"); + BrowserTestUtils.removeTab(tab); + + UrlClassifierTestUtils.cleanupTestTrackers(); + await extension.unload(); +}); diff --git a/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js b/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js index 9c4d340509d4..f153befc9f3b 100644 --- a/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js +++ b/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js @@ -1,3 +1,4 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ ChromeUtils.import("resource://gre/modules/Services.jsm"); /** @@ -9,6 +10,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm"); * canceled. */ +let gExpectedResourcesSeen = 0; async function onModifyRequest() { return new Promise((resolve, reject) => { Services.obs.addObserver(function observer(subject, topic, data) { @@ -18,9 +20,24 @@ async function onModifyRequest() { if (httpChannel.URI.prePath + "/" != TEST_3RD_PARTY_DOMAIN_TP) { return; } - ok(spec.endsWith("empty.js"), "Correct resource observed"); - Services.obs.removeObserver(observer, "http-on-modify-request"); - resolve(); + if (spec.endsWith("empty.js")) { + ok(true, "Correct resource observed"); + ++gExpectedResourcesSeen; + } else if (spec.endsWith("empty.js?redirect")) { + httpChannel.redirectTo(Services.io.newURI(spec.replace("empty.js?redirect", "head.js"))); + } else if (spec.endsWith("empty.js?redirect2")) { + httpChannel.suspend(); + setTimeout(() => { + httpChannel.redirectTo(Services.io.newURI(spec.replace("empty.js?redirect2", "head.js"))); + httpChannel.resume(); + }, 100); + } else if (spec.endsWith("head.js")) { + ++gExpectedResourcesSeen; + } + if (gExpectedResourcesSeen == 3) { + Services.obs.removeObserver(observer, "http-on-modify-request"); + resolve(); + } }, "http-on-modify-request"); }); } @@ -53,6 +70,14 @@ add_task(async function() { await promise; + info("Verify the number of tracking nodes found"); + await ContentTask.spawn(browser, + { expected: gExpectedResourcesSeen, + }, + async function(obj) { + is(content.document.blockedTrackingNodeCount, obj.expected, "Expected tracking nodes found"); + }); + info("Removing the tab"); BrowserTestUtils.removeTab(tab); diff --git a/toolkit/components/antitracking/test/browser/embedder.html b/toolkit/components/antitracking/test/browser/embedder.html index 33d713794fdd..1a517079e0ae 100644 --- a/toolkit/components/antitracking/test/browser/embedder.html +++ b/toolkit/components/antitracking/test/browser/embedder.html @@ -1,2 +1,4 @@ + + diff --git a/toolkit/components/thumbnails/test/browser.ini b/toolkit/components/thumbnails/test/browser.ini index c5eeb9d418c6..f023b56132d7 100644 --- a/toolkit/components/thumbnails/test/browser.ini +++ b/toolkit/components/thumbnails/test/browser.ini @@ -37,7 +37,7 @@ skip-if = verify [browser_thumbnails_bug818225.js] skip-if = (verify && debug && (os == 'linux')) [browser_thumbnails_capture.js] -skip-if = os == "mac" && !debug # bug 1314039 +skip-if = (os == "mac" && !debug) || (os == 'win') # bug 1314039 # Bug 1345418 [browser_thumbnails_expiration.js] [browser_thumbnails_privacy.js] [browser_thumbnails_redirect.js] diff --git a/toolkit/themes/osx/global/tree.css b/toolkit/themes/osx/global/tree.css index 24790a1f61ba..d2ec96798d32 100644 --- a/toolkit/themes/osx/global/tree.css +++ b/toolkit/themes/osx/global/tree.css @@ -4,15 +4,6 @@ %include ../../shared/tree.inc.css -/* ::::: tree focusring ::::: */ - -.focusring > .tree-stack > .tree-rows > .tree-bodybox { - border: 1px solid transparent; -} - -.focusring:focus > .tree-stack > .tree-rows > .tree-bodybox { - border: 1px solid -moz-mac-focusring; -} /* ::::: editable tree ::::: */ diff --git a/toolkit/themes/shared/tree.inc.css b/toolkit/themes/shared/tree.inc.css index de5027e3ba12..3e425a4c2061 100644 --- a/toolkit/themes/shared/tree.inc.css +++ b/toolkit/themes/shared/tree.inc.css @@ -17,17 +17,6 @@ tree { -moz-appearance: listbox; } -/* ::::: tree focusring ::::: */ - -.focusring > .tree-stack > .tree-rows > .tree-bodybox { - border: 1px solid transparent; -} - -.focusring:focus > .tree-stack > .tree-rows > .tree-bodybox { - border: 1px solid #000000; -} - - /* ::::: tree rows ::::: */ treechildren::-moz-tree-row {