Merge inbound to mozilla-central a=merge

This commit is contained in:
arthur.iakab 2018-08-25 01:08:22 +03:00
commit 5527acb8d8
137 changed files with 3347 additions and 1152 deletions

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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 */

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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(),
);

View File

@ -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 })),
);
}
}

View File

@ -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 }),
);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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",
),
];
);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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',
)

View File

@ -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',

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -4,4 +4,7 @@
DevToolsModules(
'debug-target-listener.js',
'extension-component-data.js',
'tab-component-data.js',
'worker-component-data.js',
)

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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",

View File

@ -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"

View File

@ -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<nsIURI> selfURI;
ret = NS_NewURI(getter_AddRefs(selfURI), "http://selfuri.com");
if (ret != NS_OK)
return 0;
mozilla::OriginAttributes attrs;
nsCOMPtr<nsIPrincipal> selfURIPrincipal =
mozilla::BasePrincipal::CreateCodebasePrincipal(selfURI, attrs);
if (!selfURIPrincipal)
return 0;
nsCOMPtr<nsIContentSecurityPolicy> 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<const char*>(data), size);
if (!policy.get())
return 0;
csp->AppendPolicy(policy, false, false);
return 0;
}
MOZ_FUZZING_INTERFACE_RAW(nullptr, LLVMFuzzerTestOneInput, ContentSecurityPolicyParser);

View File

@ -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"

View File

@ -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'

View File

@ -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'
]

View File

@ -222,17 +222,6 @@ ServiceWorkerRegistration::Update(ErrorResult& aRv)
return nullptr;
}
if (RefPtr<ServiceWorkerGlobalScope> serviceWorkerGlobal =
do_QueryObject(global)) {
WorkerPrivate* wp;
if (serviceWorkerGlobal->Registration() == this &&
(wp = GetCurrentThreadWorkerPrivate()) &&
wp->IsLoadingWorkerScript()) {
outer->MaybeResolve(*this);
return outer.forget();
}
}
RefPtr<ServiceWorkerRegistration> self = this;
mPendingUpdatePromises += 1;

View File

@ -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<ServiceWorkerRegistrationPromise::Private>(__func__);
auto holder =

View File

@ -560,7 +560,8 @@ SVGPathData::BuildPathForMeasuring() const
SVGPathData::BuildPath(const nsTArray<StylePathCommand>& 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<StylePathCommand>& 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<StylePathCommand>& 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<StylePathCommand>& 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<StylePathCommand>& 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<StylePathCommand>& 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<StylePathCommand>& 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<StylePathCommand>& aPath,
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
aBuilder->LineTo(scale(segEnd));
}
break;
@ -702,7 +707,7 @@ SVGPathData::BuildPath(const nsTArray<StylePathCommand>& aPath,
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
aBuilder->LineTo(scale(segEnd));
}
break;
@ -718,7 +723,7 @@ SVGPathData::BuildPath(const nsTArray<StylePathCommand>& 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<StylePathCommand>& aPath,
if (segEnd != segStart || segEnd != cp1) {
subpathHasLength = true;
aBuilder->BezierTo(tcp1, tcp2, segEnd);
aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
}
break;
}

View File

@ -178,7 +178,8 @@ public:
BuildPath(const nsTArray<StylePathCommand>& 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(); }

View File

@ -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)

View File

@ -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
{

View File

@ -31,10 +31,6 @@ XPIDL_SOURCES += [
XPIDL_MODULE = 'editor'
EXPORTS += [
'nsEditorCID.h',
]
TESTING_JS_MODULES += [
'AsyncSpellCheckTestHelper.jsm',
]

View File

@ -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___

View File

@ -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);
}
})();

View File

@ -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
]);

View File

@ -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"); }};

View File

@ -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]);
}
}

View File

@ -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);

View File

@ -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();
})();

View File

@ -108,7 +108,7 @@ static const uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
reinterpret_cast<uint8_t*>(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

View File

@ -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 <size_t NumDefs>
void
CodeGenerator::emitIonToWasmCallBase(LIonToWasmCallBase<NumDefs>* 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<CodeGenerator>::value,
"CodeGenerator should not have any virtual methods");

View File

@ -219,6 +219,9 @@ class CodeGenerator final : public CodeGeneratorSpecific
void emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck);
template<size_t NumDefs>
void emitIonToWasmCallBase(LIonToWasmCallBase<NumDefs>* lir);
IonScriptCounts* maybeCreateScriptCounts();
// This function behaves like testValueTruthy with the exception that it can

View File

@ -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
}

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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) {

View File

@ -1170,6 +1170,12 @@ TraceJitExitFrame(JSTracer* trc, const JSJitFrameIter& frame)
return;
}
if (frame.isExitFrameLayout<DirectWasmJitCallFrameLayout>()) {
// 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.

View File

@ -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<CalledFromJitExitFrameLayout>()
{
return is<InterpreterStubExitFrameLayout>() ||
is<LazyLinkExitFrameLayout>() ||
is<WasmExitFrameLayout>();
is<WasmGenericJitEntryFrameLayout>();
}
template <>
@ -880,6 +881,15 @@ ExitFrameLayout::as<CalledFromJitExitFrameLayout>()
return reinterpret_cast<CalledFromJitExitFrameLayout*>(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

View File

@ -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<LIonToWasmCallV>(ins->numOperands(), scratch, fp);
else
lir = allocateVariadic<LIonToWasmCall>(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<LIRGenerator>::value,
"LIRGenerator should not have any virtual methods");

View File

@ -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<MDefinition*> 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)

View File

@ -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

View File

@ -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<WasmInstanceObject*> 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)

View File

@ -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)
{

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -9835,6 +9835,45 @@ class LGetPrototypeOf : public LInstructionHelper<BOX_PIECES, 1, 0>
}
};
template<size_t NumDefs>
class LIonToWasmCallBase : public LVariadicInstruction<NumDefs, 2>
{
using Base = LVariadicInstruction<NumDefs, 2>;
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<BOX_PIECES>
{
public:
LIR_HEADER(IonToWasmCallV);
LIonToWasmCallV(uint32_t numOperands, const LDefinition& temp, const LDefinition& fp)
: LIonToWasmCallBase<BOX_PIECES>(classOpcode, numOperands, temp, fp)
{}
};
} // namespace jit
} // namespace js

View File

@ -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"

View File

@ -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<jit::JSJitFrameIter>(act_, prevFP);
iter_.construct<jit::JSJitFrameIter>(act_, prevFrameType, prevFP);
MOZ_ASSERT(!asJSJit().done());
return;
}

View File

@ -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;

View File

@ -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<bool> fpWasTagged, Frame* const fp, void** const sp)
{
MOZ_ASSERT_IF(!fpWasTagged.value,
fp == reinterpret_cast<Frame*>(sp)->callerFP);
MOZ_ASSERT_IF(fpWasTagged.value,
(Frame*)(uintptr_t(fp) | 0x1) == reinterpret_cast<Frame*>(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<bool> 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<Frame*>(sp)->callerFP);
AssertCallerFP(fpWasTagged, fp, sp);
fixedPC = reinterpret_cast<Frame*>(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<Frame*>(sp)->callerFP);
AssertCallerFP(fpWasTagged, fp, sp);
fixedPC = reinterpret_cast<Frame*>(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<ExitReason> prevExitReason = exitReason_;
DebugOnly<bool> 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()) {

View File

@ -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

View File

@ -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)
{

View File

@ -41,6 +41,87 @@ GenerateEntryStubs(jit::MacroAssembler& masm, size_t funcExportIndex,
const FuncExport& funcExport, const Maybe<jit::ImmPtr>& 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<JitCallStackArg, 4, SystemAllocPolicy>;
// 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

View File

@ -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 },

View File

@ -2713,8 +2713,8 @@ ComputeClipForMaskItem(nsDisplayListBuilder* aBuilder, nsIFrame* aMaskedFrame,
aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame);
Maybe<gfxRect> 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) {

View File

@ -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");

View File

@ -0,0 +1,29 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE html>
<html>
<head>
<title>CSS Masking: Reference for clip-path's path function with nonzero</title>
<style type="text/css">
#rect {
width: 100px;
height: 100px;
background-color: green;
clip-path: url("#clip");
}
</style>
</head>
<body>
<p>The test passes if there are a green filled rect.</p>
<div id="rect"></div>
<svg height="0" width="0">
<defs>
<clipPath id="clip">
<path clip-rule="nonzero" d="M10,10h80v80h-80zM25,25h50v50h-50z"/>
</clipPath>
</defs>
</svg>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE html>
<html>
<head>
<title>CSS Masking: Test clip-path property and path function with nonzero</title>
<link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-path">
<link rel="stylesheet" href="chrome://reftest/content/path.css">
<link rel="match" href="clip-path-path-001-ref.html">
</head>
<style>
#rect {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<body>
<p>The test passes if there are a green filled rect.</p>
<div id="rect" class="path_nonzero_rect"></div>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE html>
<html>
<head>
<title>CSS Masking: Reference for clip-path's path function with evenodd</title>
<style type="text/css">
#rect {
width: 100px;
height: 100px;
background-color: green;
clip-path: url("#clip");
}
</style>
</head>
<body>
<p>The test passes if there are a green hollow rect.</p>
<div id="rect"></div>
<svg height="0" width="0">
<defs>
<clipPath id="clip">
<path clip-rule="evenodd" d="M10,10h80v80h-80zM25,25h50v50h-50z"/>
</clipPath>
</defs>
</svg>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE html>
<html>
<head>
<title>CSS Masking: Test clip-path property and path function with evenodd</title>
<link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-path">
<link rel="stylesheet" href="chrome://reftest/content/path.css">
<link rel="match" href="clip-path-path-002-ref.html">
</head>
<style>
#rect {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<body>
<p>The test passes if there are a green hollow rect.</p>
<div id="rect" class="path_evenodd_rect"></div>
</body>
</html>

View File

@ -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');
}

View File

@ -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

View File

@ -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<StylePathCommand> mPath;
StyleFillRule mFillRule = StyleFillRule::Nonzero;
};
struct StyleShapeSource final

View File

@ -8129,6 +8129,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")) {

View File

@ -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<Path>
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<Path>
nsCSSClipPathInstance::CreateClipPathPath(DrawTarget* aDrawTarget)
{
const StyleSVGPath* path = mClipPathStyle.GetPath();
MOZ_ASSERT(path);
RefPtr<PathBuilder> 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);
}

View File

@ -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<Path> CreateClipPathInset(DrawTarget* aDrawTarget,
const nsRect& aRefBox);
already_AddRefed<Path> CreateClipPathPath(DrawTarget* aDrawTarget);
/**
* The frame for the element that is currently being clipped.

View File

@ -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:<clip-source>.
}
if (maskUsage.shouldApplyBasicShape) {
if (maskUsage.shouldApplyBasicShapeOrPath) {
overlayColor.b = 1.0f; // blue represents
// clip-path:<basic-shape>||<geometry-box>.
}
@ -1045,7 +1045,7 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
context.Fill();
}
if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
context.PopClip();
}

View File

@ -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;
}

View File

@ -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;
}
};

View File

@ -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)

View File

@ -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()

View File

@ -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<RedirectChannelRegistrar> gSingleton;
}
NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar)
RedirectChannelRegistrar::RedirectChannelRegistrar()
@ -15,6 +20,26 @@ RedirectChannelRegistrar::RedirectChannelRegistrar()
, mId(1)
, mLock("RedirectChannelRegistrar")
{
MOZ_ASSERT(!gSingleton);
}
// static
already_AddRefed<nsIRedirectChannelRegistrar>
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

View File

@ -26,6 +26,13 @@ class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar
private:
~RedirectChannelRegistrar() = default;
public:
// Singleton accessor
static already_AddRefed<nsIRedirectChannelRegistrar> GetOrCreate();
// Cleanup the singleton instance on shutdown
static void Shutdown();
protected:
typedef nsInterfaceHashtable<nsUint32HashKey, nsIChannel>
ChannelHashtable;

View File

@ -176,6 +176,7 @@ EXPORTS.mozilla.net += [
'MemoryDownloader.h',
'PartiallySeekableInputStream.h',
'Predictor.h',
'RedirectChannelRegistrar.h',
'ReferrerPolicy.h',
'SimpleChannelParent.h',
'TCPFastOpen.h',

View File

@ -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<nsIRedirectChannelRegistrar> registrar =
do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
RedirectChannelRegistrar::GetOrCreate();
MOZ_ASSERT(registrar);
return registrar->LinkChannels(channelId,
parentChannel,

View File

@ -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"

View File

@ -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 },

View File

@ -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<nsIRedirectChannelRegistrar> redirectReg =
do_GetService(NS_REDIRECTCHANNELREGISTRAR_CONTRACTID);
RedirectChannelRegistrar::GetOrCreate();
MOZ_ASSERT(redirectReg);
nsCOMPtr<nsIParentChannel> redirectParentChannel;

Some files were not shown because too many files have changed in this diff Show More