mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-28 05:10:49 +00:00
Merge inbound to mozilla-central. a=merge
--HG-- rename : testing/web-platform/tests/css/motion/offset-path-string.html => testing/web-platform/tests/css/motion/offset-path-string-001.html
This commit is contained in:
commit
a3eb003ecf
@ -141,9 +141,6 @@ endif
|
||||
endif
|
||||
DEFINES += -DLPROJ_ROOT=$(LPROJ_ROOT)
|
||||
|
||||
ifdef MOZ_SYSTEM_ICU
|
||||
DEFINES += -DMOZ_SYSTEM_ICU
|
||||
endif
|
||||
ifdef CLANG_CXX
|
||||
DEFINES += -DCLANG_CXX
|
||||
endif
|
||||
|
@ -17,6 +17,7 @@ if test -n "$MOZ_SYSTEM_ICU"; then
|
||||
PKG_CHECK_MODULES(MOZ_ICU, icu-i18n >= 59.1)
|
||||
CFLAGS="$CFLAGS $MOZ_ICU_CFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $MOZ_ICU_CFLAGS"
|
||||
AC_DEFINE(MOZ_SYSTEM_ICU)
|
||||
fi
|
||||
|
||||
AC_SUBST(MOZ_SYSTEM_ICU)
|
||||
|
@ -114,6 +114,7 @@ class MachCommands(MachCommandBase):
|
||||
|
||||
valgrind_args = [
|
||||
valgrind,
|
||||
'--sym-offsets=yes',
|
||||
'--smc-check=all-non-file',
|
||||
'--vex-iropt-register-updates=allregs-at-mem-access',
|
||||
'--gen-suppressions=all',
|
||||
|
@ -26,9 +26,6 @@ if CONFIG['HOST_OS_ARCH'] != 'WINNT':
|
||||
]
|
||||
HostProgram('nsinstall_real')
|
||||
|
||||
if CONFIG['MOZ_SYSTEM_ICU']:
|
||||
DEFINES['MOZ_SYSTEM_ICU'] = True
|
||||
|
||||
PYTHON_UNITTEST_MANIFESTS += [
|
||||
'tests/python.ini',
|
||||
]
|
||||
|
@ -53,6 +53,7 @@ support-files =
|
||||
[browser_animation_inspector_exists.js]
|
||||
[browser_animation_keyframes-graph_computed-value-path-01.js]
|
||||
[browser_animation_keyframes-graph_computed-value-path-02.js]
|
||||
[browser_animation_keyframes-graph_computed-value-path-03.js]
|
||||
[browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
|
||||
skip-if = (verify && !debug)
|
||||
[browser_animation_keyframes-graph_keyframe-marker.js]
|
||||
|
@ -3,11 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following ComputedValuePath component:
|
||||
// * element existence
|
||||
// * path segments
|
||||
// * fill color by animation type
|
||||
// * stop color if the animation type is color
|
||||
// Test for ComputedValuePath of animations that consist by multi types of animated
|
||||
// properties.
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
@ -160,180 +157,6 @@ const TEST_DATA = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClass: "middle-keyframe",
|
||||
properties: [
|
||||
{
|
||||
name: "background-color",
|
||||
computedValuePathClass: "color-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
expectedStopColors: [
|
||||
{ offset: 0, color: "rgb(255, 0, 0)" },
|
||||
{ offset: 0.5, color: "rgb(0, 0, 255)" },
|
||||
{ offset: 1, color: "rgb(0, 255, 0)" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "background-repeat",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 249.999, y: 0 },
|
||||
{ x: 250, y: 100 },
|
||||
{ x: 749.999, y: 100 },
|
||||
{ x: 750, y: 0 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "font-size",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "margin-left",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "opacity",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "text-align",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 249.999, y: 0 },
|
||||
{ x: 250, y: 100 },
|
||||
{ x: 749.999, y: 100 },
|
||||
{ x: 750, y: 0 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "transform",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClass: "steps-keyframe",
|
||||
properties: [
|
||||
{
|
||||
name: "background-color",
|
||||
computedValuePathClass: "color-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
expectedStopColors: [
|
||||
{ offset: 0, color: "rgb(255, 0, 0)" },
|
||||
{ offset: 0.499, color: "rgb(255, 0, 0)" },
|
||||
{ offset: 0.5, color: "rgb(128, 128, 0)" },
|
||||
{ offset: 0.999, color: "rgb(128, 128, 0)" },
|
||||
{ offset: 1, color: "rgb(0, 255, 0)" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "background-repeat",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "font-size",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 500, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 1000, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "margin-left",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 999.999, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "opacity",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 999.999, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "text-align",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "transform",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 500, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 1000, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function() {
|
||||
|
@ -3,9 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following ComputedValuePath component:
|
||||
// * element existence
|
||||
// * path segments
|
||||
// Test for ComputedValuePath of animations that consist by one animated property
|
||||
// on complexed keyframes.
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
|
@ -0,0 +1,190 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for ComputedValuePath of animations that consist by multi types of animated
|
||||
// properties on complexed keyframes.
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
targetClass: "middle-keyframe",
|
||||
properties: [
|
||||
{
|
||||
name: "background-color",
|
||||
computedValuePathClass: "color-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
expectedStopColors: [
|
||||
{ offset: 0, color: "rgb(255, 0, 0)" },
|
||||
{ offset: 0.5, color: "rgb(0, 0, 255)" },
|
||||
{ offset: 1, color: "rgb(0, 255, 0)" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "background-repeat",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 249.999, y: 0 },
|
||||
{ x: 250, y: 100 },
|
||||
{ x: 749.999, y: 100 },
|
||||
{ x: 750, y: 0 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "font-size",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "margin-left",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "opacity",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "text-align",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 249.999, y: 0 },
|
||||
{ x: 250, y: 100 },
|
||||
{ x: 749.999, y: 100 },
|
||||
{ x: 750, y: 0 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "transform",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 250, y: 50 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 750, y: 50 },
|
||||
{ x: 1000, y: 0 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClass: "steps-keyframe",
|
||||
properties: [
|
||||
{
|
||||
name: "background-color",
|
||||
computedValuePathClass: "color-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
expectedStopColors: [
|
||||
{ offset: 0, color: "rgb(255, 0, 0)" },
|
||||
{ offset: 0.499, color: "rgb(255, 0, 0)" },
|
||||
{ offset: 0.5, color: "rgb(128, 128, 0)" },
|
||||
{ offset: 0.999, color: "rgb(128, 128, 0)" },
|
||||
{ offset: 1, color: "rgb(0, 255, 0)" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "background-repeat",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "font-size",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 500, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 1000, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "margin-left",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 999.999, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "opacity",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 999.999, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "text-align",
|
||||
computedValuePathClass: "discrete-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 499.999, y: 0 },
|
||||
{ x: 500, y: 100 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "transform",
|
||||
computedValuePathClass: "distance-path",
|
||||
expectedPathSegments: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 500, y: 0 },
|
||||
{ x: 500, y: 50 },
|
||||
{ x: 1000, y: 50 },
|
||||
{ x: 1000, y: 100 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function() {
|
||||
await testKeyframesGraphComputedValuePath(TEST_DATA);
|
||||
});
|
@ -11,10 +11,12 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const DeviceModal = createFactory(require("./DeviceModal"));
|
||||
const Toolbar = createFactory(require("./Toolbar"));
|
||||
const Viewports = createFactory(require("./Viewports"));
|
||||
|
||||
loader.lazyGetter(this, "DeviceModal",
|
||||
() => createFactory(require("./DeviceModal")));
|
||||
|
||||
const {
|
||||
addCustomDevice,
|
||||
removeCustomDevice,
|
||||
@ -255,15 +257,18 @@ class App extends Component {
|
||||
onRemoveDeviceAssociation,
|
||||
onResizeViewport,
|
||||
}),
|
||||
DeviceModal({
|
||||
deviceAdderViewportTemplate,
|
||||
devices,
|
||||
onAddCustomDevice,
|
||||
onDeviceListUpdate,
|
||||
onRemoveCustomDevice,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModal,
|
||||
})
|
||||
devices.isModalOpen ?
|
||||
DeviceModal({
|
||||
deviceAdderViewportTemplate,
|
||||
devices,
|
||||
onAddCustomDevice,
|
||||
onDeviceListUpdate,
|
||||
onRemoveCustomDevice,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModal,
|
||||
})
|
||||
:
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -27,25 +27,22 @@ class DeviceAdder extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
const {
|
||||
height,
|
||||
width,
|
||||
} = this.props.viewportTemplate;
|
||||
|
||||
this.state = {
|
||||
deviceAdderDisplayed: false,
|
||||
height,
|
||||
width,
|
||||
};
|
||||
|
||||
this.onChangeSize = this.onChangeSize.bind(this);
|
||||
this.onDeviceAdderShow = this.onDeviceAdderShow.bind(this);
|
||||
this.onDeviceAdderSave = this.onDeviceAdderSave.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
} = nextProps.viewportTemplate;
|
||||
|
||||
this.setState({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
onChangeSize(_, width, height) {
|
||||
this.setState({
|
||||
width,
|
||||
@ -68,6 +65,7 @@ class DeviceAdder extends PureComponent {
|
||||
if (!this.pixelRatioInput.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (devices.custom.find(device => device.name == this.nameInput.value)) {
|
||||
this.nameInput.setCustomValidity("Device name already in use");
|
||||
return;
|
||||
@ -76,6 +74,7 @@ class DeviceAdder extends PureComponent {
|
||||
this.setState({
|
||||
deviceAdderDisplayed: false,
|
||||
});
|
||||
|
||||
onAddCustomDevice({
|
||||
name: this.nameInput.value,
|
||||
width: this.state.width,
|
||||
|
@ -32,6 +32,11 @@ class DeviceModal extends PureComponent {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
for (const type of this.props.devices.types) {
|
||||
for (const device of this.props.devices[type]) {
|
||||
this.state[device.name] = device.displayed;
|
||||
}
|
||||
}
|
||||
|
||||
this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
|
||||
this.onDeviceCheckboxChange = this.onDeviceCheckboxChange.bind(this);
|
||||
@ -43,26 +48,6 @@ class DeviceModal extends PureComponent {
|
||||
window.addEventListener("keydown", this.onKeyDown, true);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {
|
||||
devices: oldDevices,
|
||||
} = this.props;
|
||||
const {
|
||||
devices,
|
||||
} = nextProps;
|
||||
|
||||
// Refresh component state only when model transitions from closed to open
|
||||
if (!oldDevices.isModalOpen && devices.isModalOpen) {
|
||||
for (const type of devices.types) {
|
||||
for (const device of devices[type]) {
|
||||
this.setState({
|
||||
[device.name]: device.displayed,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("keydown", this.onKeyDown, true);
|
||||
}
|
||||
|
@ -9,9 +9,7 @@ const TEST_URL = "data:text/html;charset=utf-8,";
|
||||
const Types = require("devtools/client/responsive.html/types");
|
||||
|
||||
addRDMTask(TEST_URL, async function({ ui }) {
|
||||
const { store, document } = ui.toolWindow;
|
||||
const modal = document.querySelector("#device-modal-wrapper");
|
||||
const closeButton = document.querySelector("#device-close-button");
|
||||
const { document, store } = ui.toolWindow;
|
||||
|
||||
// Wait until the viewport has been added and the device list has been loaded
|
||||
await waitUntilState(store, state => state.viewports.length == 1
|
||||
@ -26,20 +24,17 @@ addRDMTask(TEST_URL, async function({ ui }) {
|
||||
.filter(cb => !cb.checked)[0];
|
||||
const value = uncheckedCb.value;
|
||||
uncheckedCb.click();
|
||||
closeButton.click();
|
||||
document.getElementById("device-close-button").click();
|
||||
|
||||
ok(modal.classList.contains("closed") && !modal.classList.contains("opened"),
|
||||
"The device modal is closed on exit.");
|
||||
ok(!store.getState().devices.isModalOpen, "The device modal is closed on exit.");
|
||||
|
||||
info("Check that the device list remains unchanged after exitting.");
|
||||
const preferredDevicesAfter = _loadPreferredDevices();
|
||||
|
||||
is(preferredDevicesBefore.added.size, preferredDevicesAfter.added.size,
|
||||
"Got expected number of added devices.");
|
||||
|
||||
is(preferredDevicesBefore.removed.size, preferredDevicesAfter.removed.size,
|
||||
"Got expected number of removed devices.");
|
||||
|
||||
ok(!preferredDevicesAfter.removed.has(value),
|
||||
value + " was not added to removed device list.");
|
||||
});
|
||||
|
@ -23,10 +23,8 @@ const Types = require("devtools/client/responsive.html/types");
|
||||
|
||||
addRDMTask(TEST_URL, async function({ ui }) {
|
||||
const { toolWindow } = ui;
|
||||
const { store, document } = toolWindow;
|
||||
const { document, store } = toolWindow;
|
||||
const deviceSelector = document.getElementById("device-selector");
|
||||
const modal = document.getElementById("device-modal-wrapper");
|
||||
const submitButton = document.getElementById("device-submit-button");
|
||||
|
||||
// Wait until the viewport has been added and the device list has been loaded
|
||||
await waitUntilState(store, state => state.viewports.length == 1
|
||||
@ -60,10 +58,9 @@ addRDMTask(TEST_URL, async function({ ui }) {
|
||||
.filter(cb => !cb.checked)[0];
|
||||
const value = uncheckedCb.value;
|
||||
uncheckedCb.click();
|
||||
submitButton.click();
|
||||
document.getElementById("device-submit-button").click();
|
||||
|
||||
ok(modal.classList.contains("closed") && !modal.classList.contains("opened"),
|
||||
"The device modal is closed on submit.");
|
||||
ok(!store.getState().devices.isModalOpen, "The device modal is closed on submit.");
|
||||
|
||||
info("Checking that the new device is added to the user preference list.");
|
||||
let preferredDevices = _loadPreferredDevices();
|
||||
@ -80,6 +77,7 @@ addRDMTask(TEST_URL, async function({ ui }) {
|
||||
|
||||
info("Reopen device modal and check new device is correctly checked");
|
||||
await openDeviceModal(ui);
|
||||
|
||||
ok([...document.querySelectorAll(".device-input-checkbox")]
|
||||
.filter(cb => cb.checked && cb.value === value)[0],
|
||||
value + " is checked in the device modal.");
|
||||
@ -90,7 +88,7 @@ addRDMTask(TEST_URL, async function({ ui }) {
|
||||
.filter(cb => cb.checked && cb.value != value)[0];
|
||||
const checkedVal = checkedCb.value;
|
||||
checkedCb.click();
|
||||
submitButton.click();
|
||||
document.getElementById("device-submit-button").click();
|
||||
|
||||
info("Checking that the device is removed from the user preference list.");
|
||||
preferredDevices = _loadPreferredDevices();
|
||||
@ -117,7 +115,7 @@ addRDMTask(TEST_URL, async function({ ui }) {
|
||||
|
||||
addRDMTask(TEST_URL, async function({ ui }) {
|
||||
const { toolWindow } = ui;
|
||||
const { store, document } = toolWindow;
|
||||
const { document, store } = toolWindow;
|
||||
|
||||
// Wait until the viewport has been added and the device list has been loaded
|
||||
await waitUntilState(store, state => state.viewports.length == 1
|
||||
|
@ -228,16 +228,14 @@ async function testViewportResize(ui, selector, moveBy,
|
||||
}
|
||||
|
||||
async function openDeviceModal(ui) {
|
||||
const { document } = ui.toolWindow;
|
||||
const modal = document.getElementById("device-modal-wrapper");
|
||||
|
||||
info("Checking initial device modal state");
|
||||
ok(modal.classList.contains("closed") && !modal.classList.contains("opened"),
|
||||
"The device modal is closed by default.");
|
||||
const { document, store } = ui.toolWindow;
|
||||
|
||||
info("Opening device modal through device selector.");
|
||||
const onModalOpen = waitUntilState(store, state => state.devices.isModalOpen);
|
||||
await selectMenuItem(ui, "#device-selector", getStr("responsive.editDeviceList2"));
|
||||
await onModalOpen;
|
||||
|
||||
const modal = document.getElementById("device-modal-wrapper");
|
||||
ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
|
||||
"The device modal is displayed.");
|
||||
}
|
||||
@ -414,7 +412,7 @@ async function testUserAgentFromBrowser(browser, expected) {
|
||||
function addDeviceInModal(ui, device) {
|
||||
const { Simulate } =
|
||||
ui.toolWindow.require("devtools/client/shared/vendor/react-dom-test-utils");
|
||||
const { store, document } = ui.toolWindow;
|
||||
const { document, store } = ui.toolWindow;
|
||||
|
||||
const nameInput = document.querySelector("#device-adder-name input");
|
||||
const [ widthInput, heightInput ] =
|
||||
|
@ -210,6 +210,7 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
|
||||
"min-block-size",
|
||||
"-moz-min-font-size-ratio",
|
||||
"min-inline-size",
|
||||
"offset-path",
|
||||
"padding-block-end",
|
||||
"padding-block-start",
|
||||
"padding-inline-end",
|
||||
|
@ -2949,6 +2949,7 @@ exports.CSS_PROPERTIES = {
|
||||
"rotate",
|
||||
"scale",
|
||||
"translate",
|
||||
"offset-path",
|
||||
"scroll-behavior",
|
||||
"scroll-snap-type-x",
|
||||
"scroll-snap-type-y",
|
||||
@ -7415,6 +7416,20 @@ exports.CSS_PROPERTIES = {
|
||||
"unset"
|
||||
]
|
||||
},
|
||||
"offset-path": {
|
||||
"isInherited": false,
|
||||
"subproperties": [
|
||||
"offset-path"
|
||||
],
|
||||
"supports": [],
|
||||
"values": [
|
||||
"inherit",
|
||||
"initial",
|
||||
"none",
|
||||
"path",
|
||||
"unset"
|
||||
]
|
||||
},
|
||||
"opacity": {
|
||||
"isInherited": false,
|
||||
"subproperties": [
|
||||
@ -9331,6 +9346,10 @@ exports.PREFERENCES = [
|
||||
"font-variation-settings",
|
||||
"layout.css.font-variations.enabled"
|
||||
],
|
||||
[
|
||||
"offset-path",
|
||||
"layout.css.motion-path.enabled"
|
||||
],
|
||||
[
|
||||
"rotate",
|
||||
"layout.css.individual-transform.enabled"
|
||||
|
@ -3,46 +3,52 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"].getService(Ci.nsIPaymentRequestService);
|
||||
const paymentSrv = Cc[
|
||||
"@mozilla.org/dom/payments/payment-request-service;1"
|
||||
].getService(Ci.nsIPaymentRequestService);
|
||||
|
||||
const InvalidDetailsUIService = {
|
||||
showPayment: function(requestId) {
|
||||
showPayment(requestId) {
|
||||
paymentSrv.changeShippingOption(requestId, "");
|
||||
},
|
||||
abortPayment: function(requestId) {
|
||||
let abortResponse = Cc["@mozilla.org/dom/payments/payment-abort-action-response;1"].
|
||||
createInstance(Ci.nsIPaymentAbortActionResponse);
|
||||
abortPayment(requestId) {
|
||||
const abortResponse = Cc[
|
||||
"@mozilla.org/dom/payments/payment-abort-action-response;1"
|
||||
].createInstance(Ci.nsIPaymentAbortActionResponse);
|
||||
abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED);
|
||||
paymentSrv.respondPayment(abortResponse.QueryInterface(Ci.nsIPaymentActionResponse));
|
||||
},
|
||||
completePayment: function(requestId) {
|
||||
},
|
||||
updatePayment: function(requestId) {
|
||||
paymentSrv.respondPayment(
|
||||
abortResponse.QueryInterface(Ci.nsIPaymentActionResponse)
|
||||
);
|
||||
},
|
||||
completePayment(requestId) {},
|
||||
updatePayment(requestId) {},
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIPaymentUIService]),
|
||||
|
||||
};
|
||||
|
||||
function emitTestFail(message) {
|
||||
sendAsyncMessage("test-fail", message);
|
||||
}
|
||||
|
||||
function checkLowerCaseCurrency() {
|
||||
const paymentEnum = paymentSrv.enumerate();
|
||||
if (!paymentEnum.hasMoreElements()) {
|
||||
emitTestFail("PaymentRequestService should have at least one payment request.");
|
||||
const msg =
|
||||
"PaymentRequestService should have at least one payment request.";
|
||||
sendAsyncMessage("test-fail", msg);
|
||||
}
|
||||
while (paymentEnum.hasMoreElements()) {
|
||||
let payRequest = paymentEnum.getNext().QueryInterface(Ci.nsIPaymentRequest);
|
||||
const payRequest = paymentEnum
|
||||
.getNext()
|
||||
.QueryInterface(Ci.nsIPaymentRequest);
|
||||
if (!payRequest) {
|
||||
emitTestFail("Fail to get existing payment request.");
|
||||
sendAsyncMessage("test-fail", "Fail to get existing payment request.");
|
||||
break;
|
||||
}
|
||||
if (payRequest.paymentDetails.totalItem.amount.currency != "USD") {
|
||||
emitTestFail("Currency of PaymentItem total should be 'USD', but got " +
|
||||
payRequest.paymentDetails.totalItem.amount.currency + ".");
|
||||
const { currency } = payRequest.paymentDetails.totalItem.amount;
|
||||
if (currency != "USD") {
|
||||
const msg =
|
||||
"Currency of PaymentItem total should be 'USD', but got ${currency}";
|
||||
sendAsyncMessage("check-complete");
|
||||
}
|
||||
}
|
||||
paymentSrv.cleanup();
|
||||
@ -51,10 +57,10 @@ function checkLowerCaseCurrency() {
|
||||
|
||||
addMessageListener("check-lower-case-currency", checkLowerCaseCurrency);
|
||||
|
||||
addMessageListener("set-update-with-invalid-details-ui-service", function() {
|
||||
paymentSrv.setTestingUIService(InvalidDetailsUIService.QueryInterface(Ci.nsIPaymentUIService));
|
||||
addMessageListener("set-update-with-invalid-details-ui-service", () => {
|
||||
paymentSrv.setTestingUIService(
|
||||
InvalidDetailsUIService.QueryInterface(Ci.nsIPaymentUIService)
|
||||
);
|
||||
});
|
||||
|
||||
addMessageListener("teardown", function() {
|
||||
sendAsyncMessage("teardown-complete");
|
||||
});
|
||||
addMessageListener("teardown", () => sendAsyncMessage("teardown-complete"));
|
||||
|
@ -1,376 +1,353 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<meta charset="utf-8">
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1367669
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1388661
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for PaymentRequest API currency amount validation</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript">
|
||||
<title>Test for PaymentRequest API currency amount validation</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
"use strict";
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
const gUrl = SimpleTest.getTestFileURL(
|
||||
"CurrencyAmountValidationChromeScript.js"
|
||||
);
|
||||
const gScript = SpecialPowers.loadChromeScript(gUrl);
|
||||
|
||||
var gUrl = SimpleTest.getTestFileURL('CurrencyAmountValidationChromeScript.js');
|
||||
var gScript = SpecialPowers.loadChromeScript(gUrl);
|
||||
function testFailHandler(message) {
|
||||
ok(false, message);
|
||||
}
|
||||
gScript.addMessageListener("test-fail", testFailHandler);
|
||||
|
||||
function testFailHandler(message) {
|
||||
ok(false, message);
|
||||
}
|
||||
gScript.addMessageListener("test-fail", testFailHandler);
|
||||
|
||||
const defaultMethods = [{
|
||||
const defaultMethods = [
|
||||
{
|
||||
supportedMethods: "basic-card",
|
||||
}];
|
||||
const defaultDetails = {
|
||||
total: {
|
||||
label: "total",
|
||||
amount: {
|
||||
currency: "usd",
|
||||
value: "1.00",
|
||||
},
|
||||
},
|
||||
];
|
||||
const defaultDetails = {
|
||||
total: {
|
||||
label: "total",
|
||||
amount: {
|
||||
currency: "usd",
|
||||
value: "1.00",
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const specialAmountDetails = {
|
||||
total: {
|
||||
label: "total",
|
||||
amount: {
|
||||
currency: "usd",
|
||||
value: {
|
||||
toString() {
|
||||
throw "42";
|
||||
},
|
||||
const specialAmountDetails = {
|
||||
total: {
|
||||
label: "total",
|
||||
amount: {
|
||||
currency: "usd",
|
||||
value: {
|
||||
toString() {
|
||||
throw "42";
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const wellFormedCurrencyCodes = [
|
||||
"BOB",
|
||||
"EUR",
|
||||
"usd", // currency codes are case-insensitive
|
||||
"XdR",
|
||||
"xTs",
|
||||
];
|
||||
const wellFormedCurrencyCodes = [
|
||||
"BOB",
|
||||
"EUR",
|
||||
"usd", // currency codes are case-insensitive
|
||||
"XdR",
|
||||
"xTs",
|
||||
];
|
||||
|
||||
const invalidCurrencyCodes = [
|
||||
"",
|
||||
"€",
|
||||
"$",
|
||||
"SFr.",
|
||||
"DM",
|
||||
"KR₩",
|
||||
"702",
|
||||
"ßP",
|
||||
"ınr",
|
||||
"invalid",
|
||||
"in",
|
||||
"123",
|
||||
];
|
||||
const invalidCurrencyCodes = [
|
||||
"",
|
||||
"€",
|
||||
"$",
|
||||
"SFr.",
|
||||
"DM",
|
||||
"KR₩",
|
||||
"702",
|
||||
"ßP",
|
||||
"ınr",
|
||||
"invalid",
|
||||
"in",
|
||||
"123",
|
||||
];
|
||||
|
||||
const updatedInvalidCurrencyDetails = {
|
||||
total: {
|
||||
label: "Total",
|
||||
amount: {
|
||||
currency: "Invalid",
|
||||
value: "1.00"
|
||||
}
|
||||
const updatedInvalidCurrencyDetails = {
|
||||
total: {
|
||||
label: "Total",
|
||||
amount: {
|
||||
currency: "Invalid",
|
||||
value: "1.00",
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const updatedInvalidAmountDetails = {
|
||||
total: {
|
||||
label: "Total",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: "-1.00",
|
||||
},
|
||||
const updatedInvalidAmountDetails = {
|
||||
total: {
|
||||
label: "Total",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: "-1.00",
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const invalidAmounts = [
|
||||
"-",
|
||||
"notdigits",
|
||||
"ALSONOTDIGITS",
|
||||
"10.",
|
||||
".99",
|
||||
"-10.",
|
||||
"-.99",
|
||||
"10-",
|
||||
"1-0",
|
||||
"1.0.0",
|
||||
"1/3",
|
||||
"",
|
||||
null,
|
||||
" 1.0 ",
|
||||
" 1.0 ",
|
||||
"1.0 ",
|
||||
"USD$1.0",
|
||||
"$1.0",
|
||||
{
|
||||
toString() {
|
||||
return " 1.0";
|
||||
},
|
||||
const invalidAmounts = [
|
||||
"-",
|
||||
"notdigits",
|
||||
"ALSONOTDIGITS",
|
||||
"10.",
|
||||
".99",
|
||||
"-10.",
|
||||
"-.99",
|
||||
"10-",
|
||||
"1-0",
|
||||
"1.0.0",
|
||||
"1/3",
|
||||
"",
|
||||
null,
|
||||
" 1.0 ",
|
||||
" 1.0 ",
|
||||
"1.0 ",
|
||||
"USD$1.0",
|
||||
"$1.0",
|
||||
{
|
||||
toString() {
|
||||
return " 1.0";
|
||||
},
|
||||
undefined,
|
||||
];
|
||||
const invalidTotalAmounts = invalidAmounts.concat([
|
||||
"-1",
|
||||
"-1.0",
|
||||
"-1.00",
|
||||
"-1000.000",
|
||||
]);
|
||||
},
|
||||
undefined,
|
||||
];
|
||||
const invalidTotalAmounts = invalidAmounts.concat([
|
||||
"-1",
|
||||
"-1.0",
|
||||
"-1.00",
|
||||
"-1000.000",
|
||||
]);
|
||||
|
||||
function updateWithInvalidCurrency() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(updatedInvalidCurrencyDetails);
|
||||
});
|
||||
}
|
||||
function updateWithInvalidAmount() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(updatedInvalidAmountDetails);
|
||||
});
|
||||
}
|
||||
|
||||
function updateWithInvalidAmount() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(updatedInvalidAmountDetails);
|
||||
});
|
||||
}
|
||||
|
||||
function testWithLowerCaseCurrency() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
|
||||
ok(payRequest, "PaymentRequest should be created");
|
||||
gScript.addMessageListener("check-complete", function checkCompleteHandler() {
|
||||
async function testWithLowerCaseCurrency() {
|
||||
const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
|
||||
return new Promise(resolve => {
|
||||
gScript.addMessageListener(
|
||||
"check-complete",
|
||||
function checkCompleteHandler() {
|
||||
gScript.removeMessageListener("check-complete", checkCompleteHandler);
|
||||
resolve();
|
||||
});
|
||||
gScript.sendAsyncMessage("check-lower-case-currency");
|
||||
});
|
||||
}
|
||||
|
||||
function testWithWellFormedCurrencyCodes() {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const currency of wellFormedCurrencyCodes) {
|
||||
let details = {
|
||||
total: {
|
||||
label: "Well Formed Currency",
|
||||
amount: {
|
||||
currency: currency,
|
||||
value: "1.00",
|
||||
},
|
||||
},
|
||||
};
|
||||
try {
|
||||
const payRequest = new PaymentRequest(defaultMethods, details);
|
||||
} catch (e) {
|
||||
ok(false, "Unexpected error while creating payment request with well formed currency("
|
||||
+ currency + ") " + e.name + ".");
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function testWithInvalidCurrencyCodes() {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const invalidCurrency of invalidCurrencyCodes) {
|
||||
let invalidDetails = {
|
||||
total: {
|
||||
label: "Invalid Currency",
|
||||
amount: {
|
||||
currency: invalidCurrency,
|
||||
value: "1.00",
|
||||
},
|
||||
},
|
||||
};
|
||||
try {
|
||||
const payRequest = new PaymentRequest(defaultMethods, invalidDetails);
|
||||
ok(false, "Expected fail to create PaymentRequest with invalid currency(" + invalidCurrency + ").");
|
||||
} catch (e) {
|
||||
is(e.name, "RangeError", "Expected rejected with 'RangeError', but got " + e.name + ".");
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function testUpdateWithInvalidCurrency() {
|
||||
const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true);
|
||||
|
||||
gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
|
||||
return new Promise((resolve, reject) => {
|
||||
const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
|
||||
payRequest.addEventListener("shippingaddresschange", event => {
|
||||
event.updateWith(updateWithInvalidCurrency());
|
||||
});
|
||||
payRequest.addEventListener("shippingoptionchange", event => {
|
||||
event.updateWith(updateWithInvalidCurrency());
|
||||
});
|
||||
payRequest.show().then((result) => {
|
||||
ok(false, "Should be rejected with 'RangeError', but got resolved");
|
||||
resolve();
|
||||
}, (result) => {
|
||||
is(result.name, "RangeError", "Should be rejected with 'RangeError', but got " + result.name + ".");
|
||||
resolve();
|
||||
}).catch(e => {
|
||||
ok(false, "Unexpected error: " + e.name);
|
||||
resolve();
|
||||
}).finally(handler.destruct);
|
||||
});
|
||||
}
|
||||
|
||||
function testUpdateWithInvalidAmount() {
|
||||
const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true);
|
||||
|
||||
gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
|
||||
return new Promise((resolve, reject) => {
|
||||
const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
|
||||
payRequest.addEventListener("shippingaddresschange", event => {
|
||||
event.updateWith(updateWithInvalidAmount());
|
||||
});
|
||||
payRequest.addEventListener("shippingoptionchange", event => {
|
||||
event.updateWith(updateWithInvalidAmount());
|
||||
});
|
||||
payRequest.show().then((result) => {
|
||||
ok(false, "Should be rejected with 'TypeError', but got resolved");
|
||||
resolve();
|
||||
}, (result) => {
|
||||
is(result.name, "TypeError", "Should be rejected with 'TypeError', but got " + result.name + ".");
|
||||
resolve();
|
||||
}).catch(e => {
|
||||
ok(false, "Unexpected error: " + e.name);
|
||||
resolve();
|
||||
}).finally(handler.destruct);
|
||||
});
|
||||
}
|
||||
|
||||
function testSpecialAmount() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
new PaymentRequest([{supportedMethods: "basic-card"}],
|
||||
specialAmountDetails);
|
||||
ok(false, "Should throw '42', but got resolved.");
|
||||
resolve();
|
||||
} catch (e) {
|
||||
is(e, "42", "Expected throw '42'. but got " + e);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testInvalidTotalAmounts() {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const amount of invalidTotalAmounts) {
|
||||
try {
|
||||
new PaymentRequest(
|
||||
[
|
||||
{
|
||||
supportedMethods: "basic-card",
|
||||
},
|
||||
],
|
||||
{
|
||||
total: {
|
||||
label: "",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: amount,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
ok(false, "Should throw 'TypeError', but got resolved.");
|
||||
resolve();
|
||||
}
|
||||
catch (err) {
|
||||
is(err.name, "TypeError",
|
||||
"Expected 'TypeError', but got '" + err.name + "'");
|
||||
resolve();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testInvalidAmounts() {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const amount of invalidAmounts) {
|
||||
try {
|
||||
new PaymentRequest(
|
||||
[
|
||||
{
|
||||
supportedMethods: "basic-card",
|
||||
},
|
||||
],
|
||||
{
|
||||
total: {
|
||||
label: "",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: "1.00",
|
||||
},
|
||||
},
|
||||
displayItems: [
|
||||
{
|
||||
label: "",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: amount,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
ok(false, "Should throw 'TypeError', but got resolved.");
|
||||
resolve();
|
||||
}
|
||||
catch (err) {
|
||||
is(err.name, "TypeError",
|
||||
"Expected 'TypeError', but got '" + err.name + "'");
|
||||
resolve();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() {
|
||||
gScript.removeMessageListener("teardown-complete", teardownCompleteHandler);
|
||||
gScript.removeMessageListener("test-fail", testFailHandler)
|
||||
gScript.destroy();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
gScript.sendAsyncMessage("teardown");
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
testInvalidTotalAmounts()
|
||||
.then(testSpecialAmount)
|
||||
.then(testInvalidAmounts)
|
||||
.then(testWithLowerCaseCurrency)
|
||||
.then(testWithWellFormedCurrencyCodes)
|
||||
.then(testWithInvalidCurrencyCodes)
|
||||
.then(testUpdateWithInvalidAmount)
|
||||
.then(testUpdateWithInvalidCurrency)
|
||||
.then(teardown)
|
||||
.catch( e => {
|
||||
ok(false, "Unexpected error: " + e.name);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
SpecialPowers.pushPrefEnv({
|
||||
'set': [
|
||||
['dom.payments.request.enabled', true],
|
||||
]
|
||||
}, runTests);
|
||||
);
|
||||
gScript.sendAsyncMessage("check-lower-case-currency");
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
function testWithWellFormedCurrencyCodes() {
|
||||
for (const currency of wellFormedCurrencyCodes) {
|
||||
const details = {
|
||||
total: {
|
||||
label: "Well Formed Currency",
|
||||
amount: {
|
||||
currency: currency,
|
||||
value: "1.00",
|
||||
},
|
||||
},
|
||||
};
|
||||
try {
|
||||
const payRequest = new PaymentRequest(defaultMethods, details);
|
||||
} catch (e) {
|
||||
const msg = `Unexpected error while creating payment request with well-formed currency (${currency}) ${
|
||||
e.name
|
||||
}`;
|
||||
ok(false, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testWithInvalidCurrencyCodes() {
|
||||
for (const invalidCurrency of invalidCurrencyCodes) {
|
||||
const invalidDetails = {
|
||||
total: {
|
||||
label: "Invalid Currency",
|
||||
amount: {
|
||||
currency: invalidCurrency,
|
||||
value: "1.00",
|
||||
},
|
||||
},
|
||||
};
|
||||
try {
|
||||
const payRequest = new PaymentRequest(defaultMethods, invalidDetails);
|
||||
ok(
|
||||
false,
|
||||
`Creating a Payment Request with invalid currency (${invalidCurrency}) must throw.`
|
||||
);
|
||||
} catch (e) {
|
||||
is(
|
||||
e.name,
|
||||
"RangeError",
|
||||
`Expected rejected with 'RangeError', but got '${e.name}'.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function testUpdateWithInvalidCurrency() {
|
||||
const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(
|
||||
true
|
||||
);
|
||||
gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
|
||||
const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
|
||||
payRequest.addEventListener("shippingaddresschange", event => {
|
||||
event.updateWith(Promise.resolve(updatedInvalidCurrencyDetails));
|
||||
});
|
||||
payRequest.addEventListener("shippingoptionchange", event => {
|
||||
event.updateWith(updatedInvalidCurrencyDetails);
|
||||
});
|
||||
try {
|
||||
await payRequest.show();
|
||||
ok(false, "Should have rejected with 'RangeError'");
|
||||
} catch (err) {
|
||||
is(
|
||||
err.name,
|
||||
"RangeError",
|
||||
`Should be rejected with 'RangeError', but got '${err.name}'.`
|
||||
);
|
||||
}
|
||||
handler.destruct();
|
||||
}
|
||||
|
||||
async function testUpdateWithInvalidAmount() {
|
||||
const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(
|
||||
true
|
||||
);
|
||||
gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
|
||||
const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
|
||||
payRequest.addEventListener("shippingaddresschange", event => {
|
||||
event.updateWith(updateWithInvalidAmount());
|
||||
});
|
||||
payRequest.addEventListener("shippingoptionchange", event => {
|
||||
event.updateWith(updateWithInvalidAmount());
|
||||
});
|
||||
try {
|
||||
await payRequest.show();
|
||||
ok(false, "Should be rejected with 'TypeError'");
|
||||
} catch (err) {
|
||||
is(
|
||||
err.name,
|
||||
"TypeError",
|
||||
`Should be rejected with 'TypeError', but got ${err.name}.`
|
||||
);
|
||||
}
|
||||
handler.destruct();
|
||||
}
|
||||
|
||||
function testSpecialAmount() {
|
||||
try {
|
||||
new PaymentRequest(defaultMethods, specialAmountDetails);
|
||||
ok(false, "Should throw '42', but got resolved.");
|
||||
} catch (e) {
|
||||
is(e, "42", "Expected throw '42'. but got " + e);
|
||||
}
|
||||
}
|
||||
|
||||
function testInvalidTotalAmounts() {
|
||||
for (const invalidAmount of invalidTotalAmounts) {
|
||||
try {
|
||||
const invalidDetails = {
|
||||
total: {
|
||||
label: "",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: invalidAmount,
|
||||
},
|
||||
},
|
||||
};
|
||||
new PaymentRequest(defaultMethods, invalidDetails);
|
||||
ok(false, "Should throw 'TypeError', but got resolved.");
|
||||
} catch (err) {
|
||||
is(err.name, "TypeError", `Expected 'TypeError', but got '${err.name}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testInvalidAmounts() {
|
||||
for (const invalidAmount of invalidAmounts) {
|
||||
try {
|
||||
new PaymentRequest(defaultMethods, {
|
||||
total: {
|
||||
label: "",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: "1.00",
|
||||
},
|
||||
},
|
||||
displayItems: [
|
||||
{
|
||||
label: "",
|
||||
amount: {
|
||||
currency: "USD",
|
||||
value: invalidAmount,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
ok(false, "Should throw 'TypeError', but got resolved.");
|
||||
} catch (err) {
|
||||
is(err.name, "TypeError", `Expected 'TypeError', but got '${err.name}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
return new Promise(resolve => {
|
||||
gScript.addMessageListener(
|
||||
"teardown-complete",
|
||||
function teardownCompleteHandler() {
|
||||
gScript.removeMessageListener(
|
||||
"teardown-complete",
|
||||
teardownCompleteHandler
|
||||
);
|
||||
gScript.removeMessageListener("test-fail", testFailHandler);
|
||||
gScript.destroy();
|
||||
SimpleTest.finish();
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
gScript.sendAsyncMessage("teardown");
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
try {
|
||||
testInvalidTotalAmounts();
|
||||
testSpecialAmount();
|
||||
testInvalidAmounts();
|
||||
testWithWellFormedCurrencyCodes();
|
||||
testWithInvalidCurrencyCodes();
|
||||
await testUpdateWithInvalidAmount();
|
||||
await testUpdateWithInvalidCurrency();
|
||||
await testWithLowerCaseCurrency();
|
||||
await teardown();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ok(false, "Unexpected error: " + e.name);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{
|
||||
set: [["dom.payments.request.enabled", true]],
|
||||
},
|
||||
runTests
|
||||
);
|
||||
});
|
||||
</script>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1367669">Mozilla Bug 1367669</a>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1388661">Mozilla Bug 1388661</a>
|
||||
</body>
|
||||
</html>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1388661">Mozilla Bug 1388661</a>
|
@ -26,12 +26,41 @@ using namespace mozilla;
|
||||
using namespace mozilla::dom::SVGPathSeg_Binding;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
static bool IsMoveto(uint16_t aSegType)
|
||||
static inline bool IsMoveto(uint16_t aSegType)
|
||||
{
|
||||
return aSegType == PATHSEG_MOVETO_ABS ||
|
||||
aSegType == PATHSEG_MOVETO_REL;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsMoveto(StylePathCommand::Tag aSegType)
|
||||
{
|
||||
return aSegType == StylePathCommand::Tag::MoveTo;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsValidType(uint16_t aSegType)
|
||||
{
|
||||
return SVGPathSegUtils::IsValidType(aSegType);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsValidType(StylePathCommand::Tag aSegType)
|
||||
{
|
||||
return aSegType != StylePathCommand::Tag::Unknown;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsClosePath(uint16_t aSegType) {
|
||||
return aSegType == PATHSEG_CLOSEPATH;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsClosePath(StylePathCommand::Tag aSegType)
|
||||
{
|
||||
return aSegType == StylePathCommand::Tag::ClosePath;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SVGPathData::CopyFrom(const SVGPathData& rhs)
|
||||
{
|
||||
@ -262,18 +291,18 @@ ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB,
|
||||
aPB->MoveTo(aPoint);
|
||||
}
|
||||
|
||||
#define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
|
||||
do { \
|
||||
if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
|
||||
subpathContainsNonMoveTo && \
|
||||
SVGPathSegUtils::IsValidType(prevSegType) && \
|
||||
(!IsMoveto(prevSegType) || segType == PATHSEG_CLOSEPATH)) { \
|
||||
ApproximateZeroLengthSubpathSquareCaps(builder, segStart, aStrokeWidth);\
|
||||
} \
|
||||
#define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
|
||||
do { \
|
||||
if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
|
||||
subpathContainsNonMoveTo && \
|
||||
IsValidType(prevSegType) && \
|
||||
(!IsMoveto(prevSegType) || IsClosePath(segType))) { \
|
||||
ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, aStrokeWidth);\
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
already_AddRefed<Path>
|
||||
SVGPathData::BuildPath(PathBuilder* builder,
|
||||
SVGPathData::BuildPath(PathBuilder* aBuilder,
|
||||
uint8_t aStrokeLineCap,
|
||||
Float aStrokeWidth) const
|
||||
{
|
||||
@ -309,20 +338,20 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
subpathContainsNonMoveTo = true;
|
||||
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
|
||||
segEnd = pathStart;
|
||||
builder->Close();
|
||||
aBuilder->Close();
|
||||
break;
|
||||
|
||||
case PATHSEG_MOVETO_ABS:
|
||||
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
|
||||
pathStart = segEnd = Point(mData[i], mData[i+1]);
|
||||
builder->MoveTo(segEnd);
|
||||
aBuilder->MoveTo(segEnd);
|
||||
subpathHasLength = false;
|
||||
break;
|
||||
|
||||
case PATHSEG_MOVETO_REL:
|
||||
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
|
||||
pathStart = segEnd = segStart + Point(mData[i], mData[i+1]);
|
||||
builder->MoveTo(segEnd);
|
||||
aBuilder->MoveTo(segEnd);
|
||||
subpathHasLength = false;
|
||||
break;
|
||||
|
||||
@ -330,7 +359,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = Point(mData[i], mData[i+1]);
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
builder->LineTo(segEnd);
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -338,7 +367,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = segStart + Point(mData[i], mData[i+1]);
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
builder->LineTo(segEnd);
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -348,7 +377,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = Point(mData[i+4], mData[i+5]);
|
||||
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(cp1, cp2, segEnd);
|
||||
aBuilder->BezierTo(cp1, cp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -358,7 +387,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = segStart + Point(mData[i+4], mData[i+5]);
|
||||
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(cp1, cp2, segEnd);
|
||||
aBuilder->BezierTo(cp1, cp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -370,7 +399,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
tcp2 = cp1 + (segEnd - cp1) / 3;
|
||||
if (segEnd != segStart || segEnd != cp1) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(tcp1, tcp2, segEnd);
|
||||
aBuilder->BezierTo(tcp1, tcp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -382,7 +411,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
tcp2 = cp1 + (segEnd - cp1) / 3;
|
||||
if (segEnd != segStart || segEnd != cp1) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(tcp1, tcp2, segEnd);
|
||||
aBuilder->BezierTo(tcp1, tcp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -397,12 +426,12 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
if (radii.x == 0.0f || radii.y == 0.0f) {
|
||||
builder->LineTo(segEnd);
|
||||
aBuilder->LineTo(segEnd);
|
||||
} else {
|
||||
nsSVGArcConverter converter(segStart, segEnd, radii, mData[i+2],
|
||||
mData[i+3] != 0, mData[i+4] != 0);
|
||||
while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
|
||||
builder->BezierTo(cp1, cp2, segEnd);
|
||||
aBuilder->BezierTo(cp1, cp2, segEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -413,7 +442,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = Point(mData[i], segStart.y);
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
builder->LineTo(segEnd);
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -421,7 +450,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = segStart + Point(mData[i], 0.0f);
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
builder->LineTo(segEnd);
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -429,7 +458,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = Point(segStart.x, mData[i]);
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
builder->LineTo(segEnd);
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -437,7 +466,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = segStart + Point(0.0f, mData[i]);
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
builder->LineTo(segEnd);
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -447,7 +476,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = Point(mData[i+2], mData[i+3]);
|
||||
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(cp1, cp2, segEnd);
|
||||
aBuilder->BezierTo(cp1, cp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -457,7 +486,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
segEnd = segStart + Point(mData[i+2], mData[i+3]);
|
||||
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(cp1, cp2, segEnd);
|
||||
aBuilder->BezierTo(cp1, cp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -469,7 +498,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
tcp2 = cp1 + (segEnd - cp1) / 3;
|
||||
if (segEnd != segStart || segEnd != cp1) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(tcp1, tcp2, segEnd);
|
||||
aBuilder->BezierTo(tcp1, tcp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -481,7 +510,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
tcp2 = cp1 + (segEnd - cp1) / 3;
|
||||
if (segEnd != segStart || segEnd != cp1) {
|
||||
subpathHasLength = true;
|
||||
builder->BezierTo(tcp1, tcp2, segEnd);
|
||||
aBuilder->BezierTo(tcp1, tcp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -503,7 +532,7 @@ SVGPathData::BuildPath(PathBuilder* builder,
|
||||
|
||||
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
|
||||
|
||||
return builder->Finish();
|
||||
return aBuilder->Finish();
|
||||
}
|
||||
|
||||
already_AddRefed<Path>
|
||||
@ -524,6 +553,209 @@ SVGPathData::BuildPathForMeasuring() const
|
||||
return BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
|
||||
}
|
||||
|
||||
// We could simplify this function because this is only used by CSS motion path
|
||||
// and clip-path, which don't render the SVG Path. i.e. The returned path is
|
||||
// used as a reference.
|
||||
/* static */ already_AddRefed<Path>
|
||||
SVGPathData::BuildPath(const nsTArray<StylePathCommand>& aPath,
|
||||
PathBuilder* aBuilder,
|
||||
uint8_t aStrokeLineCap,
|
||||
Float aStrokeWidth)
|
||||
{
|
||||
if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) {
|
||||
return nullptr; // paths without an initial moveto are invalid
|
||||
}
|
||||
|
||||
auto toGfxPoint = [](const StyleCoordPair& aPair) {
|
||||
return Point(aPair._0, aPair._1);
|
||||
};
|
||||
|
||||
auto isCubicType = [](StylePathCommand::Tag aType) {
|
||||
return aType == StylePathCommand::Tag::CurveTo ||
|
||||
aType == StylePathCommand::Tag::SmoothCurveTo;
|
||||
};
|
||||
|
||||
auto isQuadraticType = [](StylePathCommand::Tag aType) {
|
||||
return aType == StylePathCommand::Tag::QuadBezierCurveTo ||
|
||||
aType == StylePathCommand::Tag::SmoothQuadBezierCurveTo;
|
||||
};
|
||||
|
||||
bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT;
|
||||
bool subpathHasLength = false; // visual length
|
||||
bool subpathContainsNonMoveTo = false;
|
||||
|
||||
StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
|
||||
StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown;
|
||||
Point pathStart(0.0, 0.0); // start point of [sub]path
|
||||
Point segStart(0.0, 0.0);
|
||||
Point segEnd;
|
||||
Point cp1, cp2; // previous bezier's control points
|
||||
Point tcp1, tcp2; // temporaries
|
||||
|
||||
// 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.
|
||||
|
||||
for (const StylePathCommand& cmd: aPath) {
|
||||
segType = cmd.tag;
|
||||
switch (segType) {
|
||||
case StylePathCommand::Tag::ClosePath:
|
||||
// set this early to allow drawing of square caps for "M{x},{y} Z":
|
||||
subpathContainsNonMoveTo = true;
|
||||
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
|
||||
segEnd = pathStart;
|
||||
aBuilder->Close();
|
||||
break;
|
||||
case StylePathCommand::Tag::MoveTo: {
|
||||
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);
|
||||
subpathHasLength = false;
|
||||
break;
|
||||
}
|
||||
case StylePathCommand::Tag::LineTo: {
|
||||
const Point& p = toGfxPoint(cmd.line_to.point);
|
||||
segEnd = cmd.line_to.absolute ? p : segStart + p;
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case StylePathCommand::Tag::CurveTo:
|
||||
cp1 = toGfxPoint(cmd.curve_to.control1);
|
||||
cp2 = toGfxPoint(cmd.curve_to.control2);
|
||||
segEnd = toGfxPoint(cmd.curve_to.point);
|
||||
|
||||
if (!cmd.curve_to.absolute) {
|
||||
cp1 += segStart;
|
||||
cp2 += segStart;
|
||||
segEnd += segStart;
|
||||
}
|
||||
|
||||
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
|
||||
subpathHasLength = true;
|
||||
aBuilder->BezierTo(cp1, cp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
case StylePathCommand::Tag::QuadBezierCurveTo:
|
||||
cp1 = toGfxPoint(cmd.quad_bezier_curve_to.control1);
|
||||
segEnd = toGfxPoint(cmd.quad_bezier_curve_to.point);
|
||||
|
||||
if (!cmd.quad_bezier_curve_to.absolute) {
|
||||
cp1 += segStart;
|
||||
segEnd += segStart; // set before setting tcp2!
|
||||
}
|
||||
|
||||
// Convert quadratic curve to cubic curve:
|
||||
tcp1 = segStart + (cp1 - segStart) * 2 / 3;
|
||||
tcp2 = cp1 + (segEnd - cp1) / 3;
|
||||
|
||||
if (segEnd != segStart || segEnd != cp1) {
|
||||
subpathHasLength = true;
|
||||
aBuilder->BezierTo(tcp1, tcp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
case StylePathCommand::Tag::EllipticalArc: {
|
||||
const auto& arc = cmd.elliptical_arc;
|
||||
Point radii(arc.rx, arc.ry);
|
||||
segEnd = toGfxPoint(arc.point);
|
||||
if (!arc.absolute) {
|
||||
segEnd += segStart;
|
||||
}
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
if (radii.x == 0.0f || radii.y == 0.0f) {
|
||||
aBuilder->LineTo(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case StylePathCommand::Tag::HorizontalLineTo:
|
||||
if (cmd.horizontal_line_to.absolute) {
|
||||
segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
|
||||
} else {
|
||||
segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
|
||||
}
|
||||
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
case StylePathCommand::Tag::VerticalLineTo:
|
||||
if (cmd.vertical_line_to.absolute) {
|
||||
segEnd = Point(segStart.x, cmd.vertical_line_to.y);
|
||||
} else {
|
||||
segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
|
||||
}
|
||||
|
||||
if (segEnd != segStart) {
|
||||
subpathHasLength = true;
|
||||
aBuilder->LineTo(segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
case StylePathCommand::Tag::SmoothCurveTo:
|
||||
cp1 = isCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
|
||||
cp2 = toGfxPoint(cmd.smooth_curve_to.control2);
|
||||
segEnd = toGfxPoint(cmd.smooth_curve_to.point);
|
||||
|
||||
if (!cmd.smooth_curve_to.absolute) {
|
||||
cp2 += segStart;
|
||||
segEnd += segStart;
|
||||
}
|
||||
|
||||
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
|
||||
subpathHasLength = true;
|
||||
aBuilder->BezierTo(cp1, cp2, segEnd);
|
||||
}
|
||||
break;
|
||||
|
||||
case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
|
||||
cp1 = isQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
|
||||
// Convert quadratic curve to cubic curve:
|
||||
tcp1 = segStart + (cp1 - segStart) * 2 / 3;
|
||||
|
||||
const Point& p = toGfxPoint(cmd.smooth_quad_bezier_curve_to.point);
|
||||
// set before setting tcp2!
|
||||
segEnd = cmd.smooth_quad_bezier_curve_to.absolute ? p : segStart + p;
|
||||
tcp2 = cp1 + (segEnd - cp1) / 3;
|
||||
|
||||
if (segEnd != segStart || segEnd != cp1) {
|
||||
subpathHasLength = true;
|
||||
aBuilder->BezierTo(tcp1, tcp2, segEnd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case StylePathCommand::Tag::Unknown:
|
||||
MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
subpathContainsNonMoveTo = !IsMoveto(segType);
|
||||
prevSegType = segType;
|
||||
segStart = segEnd;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(prevSegType == segType,
|
||||
"prevSegType should be left at the final segType");
|
||||
|
||||
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
|
||||
|
||||
return aBuilder->Finish();
|
||||
}
|
||||
|
||||
static double
|
||||
AngleOfVector(const Point& aVector)
|
||||
{
|
||||
|
@ -169,6 +169,16 @@ public:
|
||||
already_AddRefed<Path> BuildPath(PathBuilder* aBuilder,
|
||||
uint8_t aCapStyle,
|
||||
Float aStrokeWidth) const;
|
||||
/**
|
||||
* This function tries to build the path by an array of StylePathCommand,
|
||||
* which is generated by cbindgen from Rust (see ServoStyleConsts.h).
|
||||
* Basically, this is a variant of the above BuildPath() functions.
|
||||
*/
|
||||
static already_AddRefed<Path>
|
||||
BuildPath(const nsTArray<StylePathCommand>& aPath,
|
||||
PathBuilder* aBuilder,
|
||||
uint8_t aCapStyle,
|
||||
Float aStrokeWidth);
|
||||
|
||||
const_iterator begin() const { return mData.Elements(); }
|
||||
const_iterator end() const { return mData.Elements() + mData.Length(); }
|
||||
|
@ -81,6 +81,7 @@ EXPORTS.mozilla.dom += [
|
||||
'SVGMatrix.h',
|
||||
'SVGMetadataElement.h',
|
||||
'SVGMPathElement.h',
|
||||
'SVGPathData.h',
|
||||
'SVGPathElement.h',
|
||||
'SVGPatternElement.h',
|
||||
'SVGPolygonElement.h',
|
||||
|
@ -2562,10 +2562,11 @@ CommonPerformPromiseAllRace(JSContext *cx, PromiseForOfIterator& iterator, Handl
|
||||
return false;
|
||||
}
|
||||
|
||||
// If either the object to depend on or the object that gets
|
||||
// blocked isn't a, maybe-wrapped, Promise instance, we ignore it.
|
||||
// All this does is lose some small amount of debug information in
|
||||
// scenarios that are highly unlikely to occur in useful code.
|
||||
// If either the object to depend on (`nextPromiseObj`) or the
|
||||
// object that gets blocked (`resultPromise`) isn't a,
|
||||
// maybe-wrapped, Promise instance, we ignore it. All this does is
|
||||
// lose some small amount of debug information in scenarios that
|
||||
// are highly unlikely to occur in useful code.
|
||||
if (nextPromiseObj->is<PromiseObject>() && resultPromise->is<PromiseObject>()) {
|
||||
Handle<PromiseObject*> promise = nextPromiseObj.as<PromiseObject>();
|
||||
if (!AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise))
|
||||
@ -2999,7 +3000,12 @@ Promise_static_species(JSContext* cx, unsigned argc, Value* vp)
|
||||
// ES2016, 25.4.5.1, implemented in Promise.js.
|
||||
|
||||
enum class IncumbentGlobalObject {
|
||||
Yes, No
|
||||
// Do not use the incumbent global, this is a special case used by the
|
||||
// debugger.
|
||||
No,
|
||||
|
||||
// Use incumbent global, this is the normal operation.
|
||||
Yes
|
||||
};
|
||||
|
||||
static PromiseReactionRecord*
|
||||
@ -3007,16 +3013,45 @@ NewReactionRecord(JSContext* cx, Handle<PromiseCapability> resultCapability,
|
||||
HandleValue onFulfilled, HandleValue onRejected,
|
||||
IncumbentGlobalObject incumbentGlobalObjectOption)
|
||||
{
|
||||
// Either of the following conditions must be met:
|
||||
// * resultCapability.promise is a PromiseObject
|
||||
// * resultCapability.resolve and resultCapability.resolve are callable
|
||||
// except for Async Generator, there resultPromise can be nullptr.
|
||||
#ifdef DEBUG
|
||||
if (resultCapability.promise() && !resultCapability.promise()->is<PromiseObject>()) {
|
||||
MOZ_ASSERT(resultCapability.resolve());
|
||||
MOZ_ASSERT(IsCallable(resultCapability.resolve()));
|
||||
MOZ_ASSERT(resultCapability.reject());
|
||||
MOZ_ASSERT(IsCallable(resultCapability.reject()));
|
||||
if (resultCapability.promise()) {
|
||||
if (incumbentGlobalObjectOption == IncumbentGlobalObject::Yes) {
|
||||
if (resultCapability.promise()->is<PromiseObject>()) {
|
||||
// If `resultCapability.promise` is a Promise object,
|
||||
// `resultCapability.{resolve,reject}` may be optimized out,
|
||||
// but if they're not, they should be callable.
|
||||
MOZ_ASSERT_IF(resultCapability.resolve(),
|
||||
IsCallable(resultCapability.resolve()));
|
||||
MOZ_ASSERT_IF(resultCapability.reject(),
|
||||
IsCallable(resultCapability.reject()));
|
||||
} else {
|
||||
// If `resultCapability.promise` is a non-Promise object
|
||||
// (including wrapped Promise object),
|
||||
// `resultCapability.{resolve,reject}` should be callable.
|
||||
MOZ_ASSERT(resultCapability.resolve());
|
||||
MOZ_ASSERT(IsCallable(resultCapability.resolve()));
|
||||
MOZ_ASSERT(resultCapability.reject());
|
||||
MOZ_ASSERT(IsCallable(resultCapability.reject()));
|
||||
}
|
||||
} else {
|
||||
// For debugger usage, `resultCapability.promise` should be a
|
||||
// maybe-wrapped Promise object. The other fields are not used.
|
||||
//
|
||||
// This is the only case where we allow `resolve` and `reject` to
|
||||
// be null when the `promise` field is not a PromiseObject.
|
||||
JSObject* unwrappedPromise = UncheckedUnwrap(resultCapability.promise());
|
||||
MOZ_ASSERT(unwrappedPromise->is<PromiseObject>());
|
||||
MOZ_ASSERT(!resultCapability.resolve());
|
||||
MOZ_ASSERT(!resultCapability.reject());
|
||||
}
|
||||
} else {
|
||||
// `resultCapability.promise` is null for the following cases:
|
||||
// * resulting Promise is known to be unused
|
||||
// * Async Generator
|
||||
// In any case, other fields are also not used.
|
||||
MOZ_ASSERT(!resultCapability.resolve());
|
||||
MOZ_ASSERT(!resultCapability.reject());
|
||||
MOZ_ASSERT(incumbentGlobalObjectOption == IncumbentGlobalObject::Yes);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -4018,6 +4053,9 @@ AddDummyPromiseReactionForDebugger(JSContext* cx, Handle<PromiseObject*> promise
|
||||
if (promise->state() != JS::PromiseState::Pending)
|
||||
return true;
|
||||
|
||||
// `dependentPromise` should be a maybe-wrapped Promise.
|
||||
MOZ_ASSERT(UncheckedUnwrap(dependentPromise)->is<PromiseObject>());
|
||||
|
||||
// Leave resolve and reject as null.
|
||||
Rooted<PromiseCapability> capability(cx);
|
||||
capability.promise().set(dependentPromise);
|
||||
|
@ -28,7 +28,7 @@ SparseBitmap::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
|
||||
SparseBitmap::BitBlock&
|
||||
SparseBitmap::createBlock(Data::AddPtr p, size_t blockId, AutoEnterOOMUnsafeRegion& oomUnsafe)
|
||||
{
|
||||
MOZ_ASSERT(!p && p.isValid());
|
||||
MOZ_ASSERT(!p);
|
||||
BitBlock* block = js_new<BitBlock>();
|
||||
if (!block || !data.add(p, blockId, block))
|
||||
oomUnsafe.crash("Bitmap OOM");
|
||||
|
13
js/src/jit-test/tests/auto-regress/bug1483188.js
Normal file
13
js/src/jit-test/tests/auto-regress/bug1483188.js
Normal file
@ -0,0 +1,13 @@
|
||||
var P = newGlobal().eval(`
|
||||
(class extends Promise {
|
||||
static resolve(o) {
|
||||
return o;
|
||||
}
|
||||
});
|
||||
`);
|
||||
var alwaysPending = new Promise(() => {});
|
||||
function neverCalled() {
|
||||
assertEq(true, false);
|
||||
}
|
||||
P.race([alwaysPending]).then(neverCalled, neverCalled);
|
||||
|
15
js/src/jit-test/tests/basic/bug1483182.js
Normal file
15
js/src/jit-test/tests/basic/bug1483182.js
Normal file
@ -0,0 +1,15 @@
|
||||
var lfLogBuffer = `
|
||||
function testOuterForInVar() {
|
||||
return eval("for (var x in {}); (function() { return delete x; })");
|
||||
}
|
||||
testOuterForInVar();
|
||||
`;
|
||||
loadFile(lfLogBuffer);
|
||||
loadFile(lfLogBuffer);
|
||||
function loadFile(lfVarx) {
|
||||
try {
|
||||
oomTest(function() {
|
||||
eval(lfVarx);
|
||||
});
|
||||
} catch (lfVare) {}
|
||||
}
|
@ -448,8 +448,34 @@ BEGIN_TEST(testHashLazyStorage)
|
||||
set.compact();
|
||||
CHECK(set.capacity() == 0);
|
||||
|
||||
// lookupForAdd() instantiates, even if not followed by add().
|
||||
set.lookupForAdd(1);
|
||||
auto p = set.lookupForAdd(1);
|
||||
CHECK(set.capacity() == 0);
|
||||
CHECK(set.add(p, 1));
|
||||
CHECK(set.capacity() == minCap);
|
||||
CHECK(set.has(1));
|
||||
|
||||
set.clear();
|
||||
set.compact();
|
||||
CHECK(set.capacity() == 0);
|
||||
|
||||
p = set.lookupForAdd(1);
|
||||
CHECK(set.putNew(2));
|
||||
CHECK(set.capacity() == minCap);
|
||||
CHECK(set.relookupOrAdd(p, 1, 1));
|
||||
CHECK(set.capacity() == minCap);
|
||||
CHECK(set.has(1));
|
||||
|
||||
set.clear();
|
||||
set.compact();
|
||||
CHECK(set.capacity() == 0);
|
||||
|
||||
CHECK(set.putNew(1));
|
||||
p = set.lookupForAdd(1);
|
||||
set.clear();
|
||||
set.compact();
|
||||
CHECK(set.count() == 0);
|
||||
CHECK(set.relookupOrAdd(p, 1, 1));
|
||||
CHECK(set.count() == 1);
|
||||
CHECK(set.capacity() == minCap);
|
||||
|
||||
set.clear();
|
||||
|
@ -76,6 +76,7 @@
|
||||
#include "mozilla/dom/DOMRect.h"
|
||||
#include "mozilla/dom/DOMStringList.h"
|
||||
#include "mozilla/dom/KeyframeEffect.h"
|
||||
#include "mozilla/dom/SVGPathData.h"
|
||||
#include "mozilla/layers/APZCCallbackHelper.h"
|
||||
#include "imgIRequest.h"
|
||||
#include "nsIImageLoadingContent.h"
|
||||
@ -10266,3 +10267,94 @@ nsLayoutUtils::StyleForScrollbar(nsIFrame* aScrollbarPart)
|
||||
// held strongly by the element.
|
||||
return style.get();
|
||||
}
|
||||
|
||||
static float
|
||||
ResolveTransformOrigin(const nsStyleCoord& aCoord,
|
||||
TransformReferenceBox& aRefBox,
|
||||
TransformReferenceBox::DimensionGetter aGetter)
|
||||
{
|
||||
float result = 0.0;
|
||||
const float scale = mozilla::AppUnitsPerCSSPixel();
|
||||
if (aCoord.GetUnit() == eStyleUnit_Calc) {
|
||||
const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
|
||||
result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) *
|
||||
calc->mPercent +
|
||||
NSAppUnitsToFloatPixels(calc->mLength, scale);
|
||||
} else if (aCoord.GetUnit() == eStyleUnit_Percent) {
|
||||
result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) *
|
||||
aCoord.GetPercentValue();
|
||||
} else {
|
||||
MOZ_ASSERT(aCoord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
|
||||
result = NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), scale);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* static */ Maybe<MotionPathData>
|
||||
nsLayoutUtils::ResolveMotionPath(const nsIFrame* aFrame)
|
||||
{
|
||||
MOZ_ASSERT(aFrame);
|
||||
|
||||
const nsStyleDisplay* display = aFrame->StyleDisplay();
|
||||
if (!display->mMotion || !display->mMotion->HasPath()) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
const UniquePtr<StyleMotion>& motion = display->mMotion;
|
||||
// Bug 1429299 - Implement offset-distance for motion path. For now, we use
|
||||
// the default value, i.e. 0%.
|
||||
float distance = 0.0;
|
||||
float angle = 0.0;
|
||||
Point point;
|
||||
if (motion->OffsetPath().GetType() == StyleShapeSourceType::Path) {
|
||||
// Build the path and compute the point and angle for creating the
|
||||
// equivalent translate and rotate.
|
||||
// Here we only need to build a valid path for motion path, so
|
||||
// using the default values of stroke-width, stoke-linecap, and fill-rule
|
||||
// is fine for now because what we want is get the point and its normal
|
||||
// vector along the path, instead of rendering it.
|
||||
// FIXME: Bug 1484780, we should cache the path to avoid rebuilding it here
|
||||
// at every restyle. (Caching the path avoids the cost of flattening it
|
||||
// again each time.)
|
||||
RefPtr<DrawTarget> drawTarget =
|
||||
gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
|
||||
RefPtr<PathBuilder> builder =
|
||||
drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
|
||||
RefPtr<gfx::Path> gfxPath =
|
||||
SVGPathData::BuildPath(motion->OffsetPath().GetPath()->Path(),
|
||||
builder,
|
||||
NS_STYLE_STROKE_LINECAP_BUTT,
|
||||
0.0);
|
||||
if (!gfxPath) {
|
||||
return Nothing();
|
||||
}
|
||||
float pathLength = gfxPath->ComputeLength();
|
||||
float computedDistance = distance * pathLength;
|
||||
Point tangent;
|
||||
point = gfxPath->ComputePointAtLength(computedDistance, &tangent);
|
||||
// Bug 1429301 - Implement offset-rotate for motion path.
|
||||
// After implement offset-rotate, |angle| will be adjusted more.
|
||||
// For now, the default value of offset-rotate is "auto", so we use the
|
||||
// directional tangent vector.
|
||||
angle = atan2(tangent.y, tangent.x);
|
||||
} else {
|
||||
// Bug 1480665: Implement ray() function.
|
||||
NS_WARNING("Unsupported offset-path value");
|
||||
}
|
||||
|
||||
// Compute the offset for motion path translate.
|
||||
// We need to resolve transform-origin here to calculate the correct path
|
||||
// translate. (i.e. Center transform-origin on the path.)
|
||||
TransformReferenceBox refBox(aFrame);
|
||||
Point origin(
|
||||
ResolveTransformOrigin(display->mTransformOrigin[0],
|
||||
refBox,
|
||||
&TransformReferenceBox::Width),
|
||||
ResolveTransformOrigin(display->mTransformOrigin[1],
|
||||
refBox,
|
||||
&TransformReferenceBox::Height)
|
||||
);
|
||||
// Bug 1186329: the translate parameters will be adjusted more after we
|
||||
// implement offset-position and offset-anchor.
|
||||
return Some(MotionPathData { point - origin, angle });
|
||||
}
|
||||
|
@ -119,6 +119,11 @@ struct DisplayPortMarginsPropertyData {
|
||||
uint32_t mPriority;
|
||||
};
|
||||
|
||||
struct MotionPathData {
|
||||
gfx::Point mTranslate;
|
||||
float mRotate;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
// For GetDisplayPort
|
||||
@ -3113,6 +3118,12 @@ public:
|
||||
*/
|
||||
static ComputedStyle* StyleForScrollbar(nsIFrame* aScrollbarPart);
|
||||
|
||||
/**
|
||||
* Generate the motion path transform result.
|
||||
**/
|
||||
static mozilla::Maybe<mozilla::MotionPathData>
|
||||
ResolveMotionPath(const nsIFrame* aFrame);
|
||||
|
||||
private:
|
||||
static uint32_t sFontSizeInflationEmPerLine;
|
||||
static uint32_t sFontSizeInflationMinTwips;
|
||||
|
@ -2409,6 +2409,10 @@ nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame,
|
||||
MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have URL source type!");
|
||||
return;
|
||||
|
||||
case StyleShapeSourceType::Path:
|
||||
MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have Path source type!");
|
||||
return;
|
||||
|
||||
case StyleShapeSourceType::Image: {
|
||||
float shapeImageThreshold = styleDisplay->mShapeImageThreshold;
|
||||
mShapeInfo = ShapeInfo::CreateImageShape(shapeOutside.GetShapeImage(),
|
||||
|
@ -249,22 +249,30 @@ static void
|
||||
IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity)
|
||||
{
|
||||
const nsStyleDisplay* display = aFrame->StyleDisplay();
|
||||
RefPtr<nsCSSValueSharedList> transformList = display->GetCombinedTransform();
|
||||
if (!transformList) {
|
||||
if (!display->mSpecifiedTransform &&
|
||||
!display->HasIndividualTransform() &&
|
||||
!(display->mMotion && display->mMotion->HasPath())) {
|
||||
// The transform was removed.
|
||||
aActivity->mPreviousTransformScale = Nothing();
|
||||
IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
|
||||
IncrementMutationCount(
|
||||
&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the new scale due to the CSS transform property.
|
||||
bool dummyBool;
|
||||
nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
|
||||
Matrix4x4 transform =
|
||||
nsStyleTransformMatrix::ReadTransforms(transformList->mHead,
|
||||
refBox,
|
||||
AppUnitsPerCSSPixel(),
|
||||
&dummyBool);
|
||||
Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
|
||||
display->mIndividualTransform
|
||||
? display->mIndividualTransform->mHead
|
||||
: nullptr,
|
||||
nsLayoutUtils::ResolveMotionPath(aFrame),
|
||||
display->mSpecifiedTransform
|
||||
? display->mSpecifiedTransform->mHead
|
||||
: nullptr,
|
||||
refBox,
|
||||
AppUnitsPerCSSPixel(),
|
||||
&dummyBool);
|
||||
Matrix transform2D;
|
||||
if (!transform.Is2D(&transform2D)) {
|
||||
// We don't attempt to handle 3D transforms; just assume the scale changed.
|
||||
|
@ -7849,11 +7849,14 @@ nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame,
|
||||
return true;
|
||||
}
|
||||
|
||||
nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(const nsIFrame* aFrame,
|
||||
float aAppUnitsPerPixel,
|
||||
const nsRect* aBoundsOverride)
|
||||
nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(
|
||||
const nsIFrame* aFrame,
|
||||
float aAppUnitsPerPixel,
|
||||
const nsRect* aBoundsOverride)
|
||||
: mFrame(aFrame)
|
||||
, mTransformList(aFrame->StyleDisplay()->GetCombinedTransform())
|
||||
, mIndividualTransformList(aFrame->StyleDisplay()->mIndividualTransform)
|
||||
, mMotion(nsLayoutUtils::ResolveMotionPath(aFrame))
|
||||
, mTransformList(aFrame->StyleDisplay()->mSpecifiedTransform)
|
||||
, mToTransformOrigin(GetDeltaToTransformOrigin(aFrame, aAppUnitsPerPixel, aBoundsOverride))
|
||||
{
|
||||
}
|
||||
@ -7922,10 +7925,16 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp
|
||||
frame && frame->IsSVGTransformed(&svgTransform,
|
||||
&parentsChildrenOnlyTransform);
|
||||
/* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */
|
||||
if (aProperties.mTransformList) {
|
||||
result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList->mHead,
|
||||
refBox, aAppUnitsPerPixel,
|
||||
&dummyBool);
|
||||
if (aProperties.HasTransform()) {
|
||||
result = nsStyleTransformMatrix::ReadTransforms(
|
||||
aProperties.mIndividualTransformList
|
||||
? aProperties.mIndividualTransformList->mHead
|
||||
: nullptr,
|
||||
aProperties.mMotion,
|
||||
aProperties.mTransformList
|
||||
? aProperties.mTransformList->mHead
|
||||
: nullptr,
|
||||
refBox, aAppUnitsPerPixel, &dummyBool);
|
||||
} else if (hasSVGTransforms) {
|
||||
// Correct the translation components for zoom:
|
||||
float pixelsPerCSSPx = AppUnitsPerCSSPixel() /
|
||||
|
@ -68,6 +68,7 @@ enum class nsDisplayOwnLayerFlags;
|
||||
|
||||
namespace mozilla {
|
||||
class FrameLayerBuilder;
|
||||
struct MotionPathData;
|
||||
namespace layers {
|
||||
class Layer;
|
||||
class ImageLayer;
|
||||
@ -6577,6 +6578,11 @@ public:
|
||||
FrameTransformProperties(const nsIFrame* aFrame,
|
||||
float aAppUnitsPerPixel,
|
||||
const nsRect* aBoundsOverride);
|
||||
// This constructor is used on the compositor (for animations).
|
||||
// Bug 1186329, Bug 1425837, If we want to support compositor animationsf
|
||||
// or individual transforms and motion path, we may need to update this.
|
||||
// For now, let mIndividualTransformList and mMotion as nullptr and
|
||||
// Nothing().
|
||||
FrameTransformProperties(RefPtr<const nsCSSValueSharedList>&&
|
||||
aTransformList,
|
||||
const Point3D& aToTransformOrigin)
|
||||
@ -6585,7 +6591,14 @@ public:
|
||||
, mToTransformOrigin(aToTransformOrigin)
|
||||
{}
|
||||
|
||||
bool HasTransform() const
|
||||
{
|
||||
return mIndividualTransformList || mTransformList || mMotion.isSome();
|
||||
}
|
||||
|
||||
const nsIFrame* mFrame;
|
||||
const RefPtr<const nsCSSValueSharedList> mIndividualTransformList;
|
||||
const mozilla::Maybe<mozilla::MotionPathData> mMotion;
|
||||
const RefPtr<const nsCSSValueSharedList> mTransformList;
|
||||
const Point3D mToTransformOrigin;
|
||||
};
|
||||
|
@ -1950,6 +1950,35 @@ Gecko_NewShapeImage(mozilla::StyleShapeSource* aShape)
|
||||
aShape->SetShapeImage(MakeUnique<nsStyleImage>());
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* aShape)
|
||||
{
|
||||
MOZ_ASSERT(aShape);
|
||||
aShape->SetPath(MakeUnique<mozilla::StyleSVGPath>());
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_SetStyleMotion(UniquePtr<mozilla::StyleMotion>* aMotion,
|
||||
mozilla::StyleMotion* aValue)
|
||||
{
|
||||
MOZ_ASSERT(aMotion);
|
||||
aMotion->reset(aValue);
|
||||
}
|
||||
|
||||
mozilla::StyleMotion*
|
||||
Gecko_NewStyleMotion()
|
||||
{
|
||||
return new StyleMotion();
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
|
||||
const mozilla::StyleMotion* aOther)
|
||||
{
|
||||
MOZ_ASSERT(aMotion);
|
||||
*aMotion = aOther ? MakeUnique<StyleMotion>(*aOther) : nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len)
|
||||
{
|
||||
|
@ -524,6 +524,12 @@ void Gecko_NewBasicShape(mozilla::StyleShapeSource* shape,
|
||||
mozilla::StyleBasicShapeType type);
|
||||
void Gecko_NewShapeImage(mozilla::StyleShapeSource* shape);
|
||||
void Gecko_StyleShapeSource_SetURLValue(mozilla::StyleShapeSource* shape, mozilla::css::URLValue* uri);
|
||||
void Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* shape);
|
||||
void Gecko_SetStyleMotion(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
|
||||
mozilla::StyleMotion* aValue);
|
||||
mozilla::StyleMotion* Gecko_NewStyleMotion();
|
||||
void Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* motion,
|
||||
const mozilla::StyleMotion* other);
|
||||
|
||||
void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len);
|
||||
void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest);
|
||||
|
@ -473,6 +473,7 @@ structs-types = [
|
||||
"mozilla::FontWeight",
|
||||
"mozilla::MallocSizeOf",
|
||||
"mozilla::OriginFlags",
|
||||
"mozilla::StyleMotion",
|
||||
"mozilla::UniquePtr",
|
||||
"mozilla::StyleDisplayMode",
|
||||
"ServoRawOffsetArc",
|
||||
|
@ -94,6 +94,7 @@ SERIALIZED_PREDEFINED_TYPES = [
|
||||
"NonNegativeLength",
|
||||
"NonNegativeLengthOrPercentage",
|
||||
"ListStyleType",
|
||||
"OffsetPath",
|
||||
"Opacity",
|
||||
"Resize",
|
||||
"url::ImageUrlOrNone",
|
||||
|
@ -5048,6 +5048,11 @@ nsComputedDOMStyle::GetShapeSource(
|
||||
SetValueToStyleImage(*aShapeSource.GetShapeImage(), val);
|
||||
return val.forget();
|
||||
}
|
||||
case StyleShapeSourceType::Path: {
|
||||
// Bug 1246764: we have to support this for clip-path. For now, no one
|
||||
// uses this.
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected SVG Path type.");
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -198,6 +198,7 @@ enum class StyleShapeSourceType : uint8_t {
|
||||
Image, // shape-outside only
|
||||
Shape,
|
||||
Box,
|
||||
Path, // SVG path function
|
||||
};
|
||||
|
||||
// -moz-stack-sizing
|
||||
|
@ -1039,6 +1039,9 @@ StyleShapeSource::operator==(const StyleShapeSource& aOther) const
|
||||
|
||||
case StyleShapeSourceType::Box:
|
||||
return mReferenceBox == aOther.mReferenceBox;
|
||||
|
||||
case StyleShapeSourceType::Path:
|
||||
return *mSVGPath == *aOther.mSVGPath;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected shape source type!");
|
||||
@ -1090,6 +1093,15 @@ StyleShapeSource::SetBasicShape(UniquePtr<StyleBasicShape> aBasicShape,
|
||||
mType = StyleShapeSourceType::Shape;
|
||||
}
|
||||
|
||||
void
|
||||
StyleShapeSource::SetPath(UniquePtr<StyleSVGPath> aPath)
|
||||
{
|
||||
MOZ_ASSERT(aPath);
|
||||
DoDestroy();
|
||||
new (&mSVGPath) UniquePtr<StyleSVGPath>(std::move(aPath));
|
||||
mType = StyleShapeSourceType::Path;
|
||||
}
|
||||
|
||||
void
|
||||
StyleShapeSource::SetReferenceBox(StyleGeometryBox aReferenceBox)
|
||||
{
|
||||
@ -1123,6 +1135,10 @@ StyleShapeSource::DoCopy(const StyleShapeSource& aOther)
|
||||
case StyleShapeSourceType::Box:
|
||||
SetReferenceBox(aOther.GetReferenceBox());
|
||||
break;
|
||||
|
||||
case StyleShapeSourceType::Path:
|
||||
SetPath(MakeUnique<StyleSVGPath>(*aOther.GetPath()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1137,6 +1153,9 @@ StyleShapeSource::DoDestroy()
|
||||
case StyleShapeSourceType::URL:
|
||||
mShapeImage.~UniquePtr<nsStyleImage>();
|
||||
break;
|
||||
case StyleShapeSourceType::Path:
|
||||
mSVGPath.~UniquePtr<StyleSVGPath>();
|
||||
break;
|
||||
case StyleShapeSourceType::None:
|
||||
case StyleShapeSourceType::Box:
|
||||
// Not a union type, so do nothing.
|
||||
@ -3617,7 +3636,10 @@ nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
|
||||
, mSpecifiedRotate(aSource.mSpecifiedRotate)
|
||||
, mSpecifiedTranslate(aSource.mSpecifiedTranslate)
|
||||
, mSpecifiedScale(aSource.mSpecifiedScale)
|
||||
, mCombinedTransform(aSource.mCombinedTransform)
|
||||
, mIndividualTransform(aSource.mIndividualTransform)
|
||||
, mMotion(aSource.mMotion
|
||||
? MakeUnique<StyleMotion>(*aSource.mMotion)
|
||||
: nullptr)
|
||||
, mTransformOrigin{ aSource.mTransformOrigin[0],
|
||||
aSource.mTransformOrigin[1],
|
||||
aSource.mTransformOrigin[2] }
|
||||
@ -3682,9 +3704,8 @@ nsStyleDisplay::~nsStyleDisplay()
|
||||
mSpecifiedTranslate);
|
||||
ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedScale",
|
||||
mSpecifiedScale);
|
||||
ReleaseSharedListOnMainThread("nsStyleDisplay::mCombinedTransform",
|
||||
mCombinedTransform);
|
||||
|
||||
ReleaseSharedListOnMainThread("nsStyleDisplay::mIndividualTransform",
|
||||
mIndividualTransform);
|
||||
MOZ_COUNT_DTOR(nsStyleDisplay);
|
||||
}
|
||||
|
||||
@ -3712,7 +3733,7 @@ nsStyleDisplay::FinishStyle(
|
||||
}
|
||||
}
|
||||
|
||||
GenerateCombinedTransform();
|
||||
GenerateCombinedIndividualTransform();
|
||||
}
|
||||
|
||||
static inline nsChangeHint
|
||||
@ -3735,6 +3756,29 @@ CompareTransformValues(const RefPtr<nsCSSValueSharedList>& aList,
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline nsChangeHint
|
||||
CompareMotionValues(const StyleMotion* aMotion,
|
||||
const StyleMotion* aNewMotion)
|
||||
{
|
||||
nsChangeHint result = nsChangeHint(0);
|
||||
|
||||
// TODO: Bug 1482737: This probably doesn't need to UpdateOverflow
|
||||
// (or UpdateTransformLayer) if there's already a transform.
|
||||
if (!aMotion != !aNewMotion ||
|
||||
(aMotion && *aMotion != *aNewMotion)) {
|
||||
// Set the same hints as what we use for transform because motion path is
|
||||
// a kind of transform and will be combined with other transforms.
|
||||
result |= nsChangeHint_UpdateTransformLayer;
|
||||
if ((aMotion && aMotion->HasPath()) &&
|
||||
(aNewMotion && aNewMotion->HasPath())) {
|
||||
result |= nsChangeHint_UpdatePostTransformOverflow;
|
||||
} else {
|
||||
result |= nsChangeHint_UpdateOverflow;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
nsChangeHint
|
||||
nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
|
||||
{
|
||||
@ -3866,6 +3910,7 @@ nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
|
||||
aNewData.mSpecifiedTranslate);
|
||||
transformHint |= CompareTransformValues(mSpecifiedScale,
|
||||
aNewData.mSpecifiedScale);
|
||||
transformHint |= CompareMotionValues(mMotion.get(), aNewData.mMotion.get());
|
||||
|
||||
const nsChangeHint kUpdateOverflowAndRepaintHint =
|
||||
nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
|
||||
@ -3985,17 +4030,17 @@ nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
|
||||
}
|
||||
|
||||
void
|
||||
nsStyleDisplay::GenerateCombinedTransform()
|
||||
nsStyleDisplay::GenerateCombinedIndividualTransform()
|
||||
{
|
||||
// FIXME(emilio): This should probably be called from somewhere like what we
|
||||
// do for image layers, instead of FinishStyle.
|
||||
//
|
||||
// This does and undoes the work a ton of times in Stylo.
|
||||
mCombinedTransform = nullptr;
|
||||
mIndividualTransform = nullptr;
|
||||
|
||||
// Follow the order defined in the spec to append transform functions.
|
||||
// https://drafts.csswg.org/css-transforms-2/#ctm
|
||||
AutoTArray<nsCSSValueSharedList*, 4> shareLists;
|
||||
AutoTArray<nsCSSValueSharedList*, 3> shareLists;
|
||||
if (mSpecifiedTranslate) {
|
||||
shareLists.AppendElement(mSpecifiedTranslate.get());
|
||||
}
|
||||
@ -4005,24 +4050,20 @@ nsStyleDisplay::GenerateCombinedTransform()
|
||||
if (mSpecifiedScale) {
|
||||
shareLists.AppendElement(mSpecifiedScale.get());
|
||||
}
|
||||
if (mSpecifiedTransform) {
|
||||
shareLists.AppendElement(mSpecifiedTransform.get());
|
||||
}
|
||||
|
||||
if (shareLists.Length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shareLists.Length() == 1) {
|
||||
mCombinedTransform = shareLists[0];
|
||||
mIndividualTransform = shareLists[0];
|
||||
return;
|
||||
}
|
||||
|
||||
// In common, we may have 3 transform functions(for rotate, translate and
|
||||
// scale) in mSpecifiedTransform, one rotate function in mSpecifiedRotate,
|
||||
// one translate function in mSpecifiedTranslate, and one scale function in
|
||||
// mSpecifiedScale. So 6 slots are enough for the most cases.
|
||||
AutoTArray<nsCSSValueList*, 6> valueLists;
|
||||
// In common, we may have 3 transform functions:
|
||||
// 1. one rotate function in mSpecifiedRotate,
|
||||
// 2. one translate function in mSpecifiedTranslate,
|
||||
// 3. one scale function in mSpecifiedScale.
|
||||
AutoTArray<nsCSSValueList*, 3> valueLists;
|
||||
for (auto list: shareLists) {
|
||||
if (list) {
|
||||
valueLists.AppendElement(list->mHead->Clone());
|
||||
@ -4037,8 +4078,9 @@ nsStyleDisplay::GenerateCombinedTransform()
|
||||
valueLists[i]->mNext = valueLists[i + 1];
|
||||
}
|
||||
|
||||
mCombinedTransform = new nsCSSValueSharedList(valueLists[0]);
|
||||
mIndividualTransform = new nsCSSValueSharedList(valueLists[0]);
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// nsStyleVisibility
|
||||
//
|
||||
|
@ -1971,6 +1971,27 @@ private:
|
||||
nsStyleCorners mRadius;
|
||||
};
|
||||
|
||||
struct StyleSVGPath final
|
||||
{
|
||||
const nsTArray<StylePathCommand>& Path() const
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
bool operator==(const StyleSVGPath& aOther) const
|
||||
{
|
||||
return mPath == aOther.mPath;
|
||||
}
|
||||
|
||||
bool operator!=(const StyleSVGPath& aOther) const
|
||||
{
|
||||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
private:
|
||||
nsTArray<StylePathCommand> mPath;
|
||||
};
|
||||
|
||||
struct StyleShapeSource final
|
||||
{
|
||||
StyleShapeSource();
|
||||
@ -2035,6 +2056,13 @@ struct StyleShapeSource final
|
||||
|
||||
void SetReferenceBox(StyleGeometryBox aReferenceBox);
|
||||
|
||||
const StyleSVGPath* GetPath() const
|
||||
{
|
||||
MOZ_ASSERT(mType == StyleShapeSourceType::Path, "Wrong shape source type!");
|
||||
return mSVGPath.get();
|
||||
}
|
||||
void SetPath(UniquePtr<StyleSVGPath> aPath);
|
||||
|
||||
private:
|
||||
void* operator new(size_t) = delete;
|
||||
|
||||
@ -2044,13 +2072,41 @@ private:
|
||||
union {
|
||||
mozilla::UniquePtr<StyleBasicShape> mBasicShape;
|
||||
mozilla::UniquePtr<nsStyleImage> mShapeImage;
|
||||
// TODO: Bug 1429298, implement SVG Path function.
|
||||
mozilla::UniquePtr<StyleSVGPath> mSVGPath;
|
||||
// TODO: Bug 1480665, implement ray() function.
|
||||
};
|
||||
StyleShapeSourceType mType = StyleShapeSourceType::None;
|
||||
StyleGeometryBox mReferenceBox = StyleGeometryBox::NoBox;
|
||||
};
|
||||
|
||||
struct StyleMotion final
|
||||
{
|
||||
bool operator==(const StyleMotion& aOther) const
|
||||
{
|
||||
return mOffsetPath == aOther.mOffsetPath;
|
||||
}
|
||||
|
||||
bool operator!=(const StyleMotion& aOther) const
|
||||
{
|
||||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
const StyleShapeSource& OffsetPath() const
|
||||
{
|
||||
return mOffsetPath;
|
||||
}
|
||||
|
||||
bool HasPath() const
|
||||
{
|
||||
// Bug 1186329: We have to check other acceptable types after supporting
|
||||
// different values of offset-path. e.g. basic-shapes, ray.
|
||||
return mOffsetPath.GetType() == StyleShapeSourceType::Path;
|
||||
}
|
||||
|
||||
private:
|
||||
StyleShapeSource mOffsetPath;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
|
||||
@ -2125,12 +2181,10 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
|
||||
RefPtr<nsCSSValueSharedList> mSpecifiedRotate;
|
||||
RefPtr<nsCSSValueSharedList> mSpecifiedTranslate;
|
||||
RefPtr<nsCSSValueSharedList> mSpecifiedScale;
|
||||
|
||||
// Used to store the final combination of mSpecifiedTranslate,
|
||||
// mSpecifiedRotate, mSpecifiedScale and mSpecifiedTransform.
|
||||
// Use GetCombinedTransform() to get the final transform, instead of
|
||||
// accessing mCombinedTransform directly.
|
||||
RefPtr<nsCSSValueSharedList> mCombinedTransform;
|
||||
// Used to store the final combination of mSpecifiedRotate,
|
||||
// mSpecifiedTranslate, and mSpecifiedScale.
|
||||
RefPtr<nsCSSValueSharedList> mIndividualTransform;
|
||||
mozilla::UniquePtr<mozilla::StyleMotion> mMotion;
|
||||
|
||||
nsStyleCoord mTransformOrigin[3]; // percent, coord, calc, 3rd param is coord, calc only
|
||||
nsStyleCoord mChildPerspective; // none, coord
|
||||
@ -2380,7 +2434,8 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
|
||||
return mSpecifiedTransform || mSpecifiedRotate || mSpecifiedTranslate ||
|
||||
mSpecifiedScale ||
|
||||
mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
|
||||
(mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM);
|
||||
(mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) ||
|
||||
(mMotion && mMotion->HasPath());
|
||||
}
|
||||
|
||||
bool HasIndividualTransform() const {
|
||||
@ -2470,21 +2525,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
|
||||
inline bool IsFixedPosContainingBlockForTransformSupportingFrames() const;
|
||||
|
||||
/**
|
||||
* Returns the final combined transform.
|
||||
* Returns the final combined individual transform.
|
||||
**/
|
||||
already_AddRefed<nsCSSValueSharedList> GetCombinedTransform() const {
|
||||
if (mCombinedTransform) {
|
||||
return do_AddRef(mCombinedTransform);
|
||||
}
|
||||
|
||||
// backward compatible to gecko-backed style system.
|
||||
return mSpecifiedTransform ? do_AddRef(mSpecifiedTransform) : nullptr;
|
||||
already_AddRefed<nsCSSValueSharedList> GetCombinedTransform() const
|
||||
{
|
||||
return mIndividualTransform ? do_AddRef(mIndividualTransform) : nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
// Helpers for above functions, which do some but not all of the tests
|
||||
// for them (since transform must be tested separately for each).
|
||||
void GenerateCombinedTransform();
|
||||
void GenerateCombinedIndividualTransform();
|
||||
};
|
||||
|
||||
struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTable
|
||||
|
@ -926,14 +926,12 @@ SetIdentityMatrix(nsCSSValue::Array* aMatrix)
|
||||
}
|
||||
}
|
||||
|
||||
Matrix4x4
|
||||
ReadTransforms(const nsCSSValueList* aList,
|
||||
TransformReferenceBox& aRefBox,
|
||||
float aAppUnitsPerMatrixUnit,
|
||||
bool* aContains3dTransform)
|
||||
static void
|
||||
ReadTransformsImpl(Matrix4x4& aMatrix,
|
||||
const nsCSSValueList* aList,
|
||||
TransformReferenceBox& aRefBox,
|
||||
bool* aContains3dTransform)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
|
||||
for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
|
||||
const nsCSSValue &currElem = curr->mValue;
|
||||
if (currElem.GetUnit() != eCSSUnit_Function) {
|
||||
@ -947,9 +945,55 @@ ReadTransforms(const nsCSSValueList* aList,
|
||||
"Incoming function is too short!");
|
||||
|
||||
/* Read in a single transform matrix. */
|
||||
MatrixForTransformFunction(result, currElem.GetArrayValue(), aRefBox,
|
||||
MatrixForTransformFunction(aMatrix, currElem.GetArrayValue(), aRefBox,
|
||||
aContains3dTransform);
|
||||
}
|
||||
}
|
||||
|
||||
Matrix4x4
|
||||
ReadTransforms(const nsCSSValueList* aList,
|
||||
TransformReferenceBox& aRefBox,
|
||||
float aAppUnitsPerMatrixUnit,
|
||||
bool* aContains3dTransform)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
ReadTransformsImpl(result, aList, aRefBox, aContains3dTransform);
|
||||
|
||||
float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
|
||||
result.PreScale(1/scale, 1/scale, 1/scale);
|
||||
result.PostScale(scale, scale, scale);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Matrix4x4
|
||||
ReadTransforms(const nsCSSValueList* aIndividualTransforms,
|
||||
const Maybe<MotionPathData>& aMotion,
|
||||
const nsCSSValueList* aTransform,
|
||||
TransformReferenceBox& aRefBox,
|
||||
float aAppUnitsPerMatrixUnit,
|
||||
bool* aContains3dTransform)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
|
||||
if (aIndividualTransforms) {
|
||||
ReadTransformsImpl(result, aIndividualTransforms, aRefBox,
|
||||
aContains3dTransform);
|
||||
}
|
||||
|
||||
if (aMotion.isSome()) {
|
||||
// Create the equivalent translate and rotate function, according to the
|
||||
// order in spec. We combine the translate and then the rotate.
|
||||
// https://drafts.fxtf.org/motion-1/#calculating-path-transform
|
||||
result.PreTranslate(aMotion->mTranslate.x, aMotion->mTranslate.y, 0.0);
|
||||
if (aMotion->mRotate != 0.0) {
|
||||
result.RotateZ(aMotion->mRotate);
|
||||
}
|
||||
}
|
||||
|
||||
if (aTransform) {
|
||||
ReadTransformsImpl(result, aTransform, aRefBox, aContains3dTransform);
|
||||
}
|
||||
|
||||
float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
|
||||
result.PreScale(1/scale, 1/scale, 1/scale);
|
||||
|
@ -24,6 +24,10 @@ class nsPresContext;
|
||||
struct gfxQuaternion;
|
||||
struct nsRect;
|
||||
|
||||
namespace mozilla {
|
||||
struct MotionPathData;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to generate gfxMatrixes from css transform functions.
|
||||
*/
|
||||
@ -200,6 +204,16 @@ namespace nsStyleTransformMatrix {
|
||||
float aAppUnitsPerMatrixUnit,
|
||||
bool* aContains3dTransform);
|
||||
|
||||
// Generate the gfx::Matrix for CSS Transform Module Level 2.
|
||||
// https://drafts.csswg.org/css-transforms-2/#ctm
|
||||
mozilla::gfx::Matrix4x4
|
||||
ReadTransforms(const nsCSSValueList* aIndividualTransforms,
|
||||
const mozilla::Maybe<mozilla::MotionPathData>& aMotion,
|
||||
const nsCSSValueList* aTransform,
|
||||
TransformReferenceBox& aRefBox,
|
||||
float aAppUnitsPerMatrixUnit,
|
||||
bool* aContains3dTransform);
|
||||
|
||||
/**
|
||||
* Given two nsStyleCoord values, compute the 2d position with respect to the
|
||||
* given TransformReferenceBox that these values describe, in device pixels.
|
||||
|
@ -8173,6 +8173,28 @@ if (IsCSSPropertyPrefEnabled("layout.css.scrollbar-width.enabled")) {
|
||||
};
|
||||
}
|
||||
|
||||
if (IsCSSPropertyPrefEnabled("layout.css.motion-path.enabled")) {
|
||||
gCSSProperties["offset-path"] = {
|
||||
domProp: "offsetPath",
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: [ "none" ],
|
||||
other_values: [
|
||||
"path('M 10 10 20 20 H 90 V 90 Z')",
|
||||
"path('M10 10 20,20H90V90Z')",
|
||||
"path('M 10 10 C 20 20, 40 20, 50 10')",
|
||||
"path('M 10 80 C 40 10, 65 10, 95 80 S 1.5e2 150, 180 80')",
|
||||
"path('M 10 80 Q 95 10 180 80')",
|
||||
"path('M 10 80 Q 52.5 10, 95 80 T 180 80')",
|
||||
"path('M 80 80 A 45 45, 0, 0, 0, 1.25e2 1.25e2 L 125 80 Z')",
|
||||
"path('M100-200h20z')",
|
||||
"path('M10,10L20.6.5z')"
|
||||
],
|
||||
invalid_values: [ "path('')", "path()", "path(a)", "path('M 10 Z')" ,
|
||||
"path('M 10-10 20')", "path('M 10 10 C 20 20 40 20')" ]
|
||||
};
|
||||
}
|
||||
|
||||
const OVERFLOW_MOZKWS = [
|
||||
"-moz-scrollbars-none",
|
||||
"-moz-scrollbars-horizontal",
|
||||
|
@ -1209,6 +1209,18 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
// This constructor is used only by AddPtr() within lookupForAdd().
|
||||
explicit Ptr(const HashTable& aTable)
|
||||
: mEntry(nullptr)
|
||||
#ifdef DEBUG
|
||||
, mTable(&aTable)
|
||||
, mGeneration(aTable.generation())
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
bool isValid() const { return !!mEntry; }
|
||||
|
||||
public:
|
||||
Ptr()
|
||||
: mEntry(nullptr)
|
||||
@ -1219,8 +1231,6 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
bool isValid() const { return !!mEntry; }
|
||||
|
||||
bool found() const
|
||||
{
|
||||
if (!isValid()) {
|
||||
@ -1286,6 +1296,21 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
// This constructor is used when lookupForAdd() is performed on a table
|
||||
// lacking entry storage; it leaves mEntry null but initializes everything
|
||||
// else.
|
||||
AddPtr(const HashTable& aTable, HashNumber aHashNumber)
|
||||
: Ptr(aTable)
|
||||
, mKeyHash(aHashNumber)
|
||||
#ifdef DEBUG
|
||||
, mMutationCount(aTable.mMutationCount)
|
||||
#endif
|
||||
{
|
||||
MOZ_ASSERT(isLive());
|
||||
}
|
||||
|
||||
bool isLive() const { return isLiveHash(mKeyHash); }
|
||||
|
||||
public:
|
||||
AddPtr()
|
||||
: mKeyHash(0)
|
||||
@ -2030,7 +2055,7 @@ public:
|
||||
return true; // Capacity is already sufficient.
|
||||
}
|
||||
|
||||
RebuildStatus status = changeTableSize(bestCapacity, DontReportFailure);
|
||||
RebuildStatus status = changeTableSize(bestCapacity, ReportFailure);
|
||||
MOZ_ASSERT(status != NotOverloaded);
|
||||
return status != RehashFailed;
|
||||
}
|
||||
@ -2107,16 +2132,12 @@ public:
|
||||
return AddPtr();
|
||||
}
|
||||
|
||||
HashNumber keyHash = prepareHash(aLookup);
|
||||
|
||||
if (!mTable) {
|
||||
uint32_t newCapacity = rawCapacity();
|
||||
RebuildStatus status = changeTableSize(newCapacity, ReportFailure);
|
||||
MOZ_ASSERT(status != NotOverloaded);
|
||||
if (status == RehashFailed) {
|
||||
return AddPtr();
|
||||
}
|
||||
return AddPtr(*this, keyHash);
|
||||
}
|
||||
|
||||
HashNumber keyHash = prepareHash(aLookup);
|
||||
// Directly call the constructor in the return statement to avoid
|
||||
// excess copying when building with Visual Studio 2017.
|
||||
// See bug 1385181.
|
||||
@ -2133,7 +2154,7 @@ public:
|
||||
MOZ_ASSERT(!(aPtr.mKeyHash & sCollisionBit));
|
||||
|
||||
// Check for error from ensureHash() here.
|
||||
if (!aPtr.isValid()) {
|
||||
if (!aPtr.isLive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2142,14 +2163,25 @@ public:
|
||||
MOZ_ASSERT(aPtr.mMutationCount == mMutationCount);
|
||||
#endif
|
||||
|
||||
// Changing an entry from removed to live does not affect whether we
|
||||
// are overloaded and can be handled separately.
|
||||
if (aPtr.mEntry->isRemoved()) {
|
||||
if (!aPtr.isValid()) {
|
||||
MOZ_ASSERT(!mTable && mEntryCount == 0);
|
||||
uint32_t newCapacity = rawCapacity();
|
||||
RebuildStatus status = changeTableSize(newCapacity, ReportFailure);
|
||||
MOZ_ASSERT(status != NotOverloaded);
|
||||
if (status == RehashFailed) {
|
||||
return false;
|
||||
}
|
||||
aPtr.mEntry = &findNonLiveEntry(aPtr.mKeyHash);
|
||||
|
||||
} else if (aPtr.mEntry->isRemoved()) {
|
||||
// Changing an entry from removed to live does not affect whether we are
|
||||
// overloaded and can be handled separately.
|
||||
if (!this->checkSimulatedOOM()) {
|
||||
return false;
|
||||
}
|
||||
mRemovedCount--;
|
||||
aPtr.mKeyHash |= sCollisionBit;
|
||||
|
||||
} else {
|
||||
// Preserve the validity of |aPtr.mEntry|.
|
||||
RebuildStatus status = rehashIfOverloaded();
|
||||
@ -2210,20 +2242,27 @@ public:
|
||||
Args&&... aArgs)
|
||||
{
|
||||
// Check for error from ensureHash() here.
|
||||
if (!aPtr.isValid()) {
|
||||
if (!aPtr.isLive()) {
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
aPtr.mGeneration = generation();
|
||||
aPtr.mMutationCount = mMutationCount;
|
||||
#endif
|
||||
{
|
||||
if (mTable) {
|
||||
ReentrancyGuard g(*this);
|
||||
// Check that aLookup has not been destroyed.
|
||||
MOZ_ASSERT(prepareHash(aLookup) == aPtr.mKeyHash);
|
||||
aPtr.mEntry = &lookup<ForAdd>(aLookup, aPtr.mKeyHash);
|
||||
if (aPtr.found()) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Clear aPtr so it's invalid; add() will allocate storage and redo the
|
||||
// lookup.
|
||||
aPtr.mEntry = nullptr;
|
||||
}
|
||||
return aPtr.found() || add(aPtr, std::forward<Args>(aArgs)...);
|
||||
return add(aPtr, std::forward<Args>(aArgs)...);
|
||||
}
|
||||
|
||||
void remove(Ptr aPtr)
|
||||
|
@ -3073,6 +3073,13 @@ pref("layout.css.ruby.intercharacter.enabled", false);
|
||||
// Is support for overscroll-behavior enabled?
|
||||
pref("layout.css.overscroll-behavior.enabled", true);
|
||||
|
||||
// Is support for motion-path enabled?
|
||||
#ifdef RELEASE_OR_BETA
|
||||
pref("layout.css.motion-path.enabled", false);
|
||||
#else
|
||||
pref("layout.css.motion-path.enabled", true);
|
||||
#endif
|
||||
|
||||
// pref for which side vertical scrollbars should be on
|
||||
// 0 = end-side in UI direction
|
||||
// 1 = end-side in document/content direction
|
||||
|
@ -7,6 +7,7 @@ autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated usi
|
||||
* a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
|
||||
* 2. Run `rustup run nightly cbindgen toolkit/library/rust/ --lockfile Cargo.lock --crate style -o layout/style/ServoStyleConsts.h`
|
||||
*/"""
|
||||
include_guard = "mozilla_ServoStyleConsts_h"
|
||||
include_version = true
|
||||
braces = "SameLine"
|
||||
line_length = 80
|
||||
@ -22,5 +23,5 @@ derive_helper_methods = true
|
||||
|
||||
[export]
|
||||
prefix = "Style"
|
||||
include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"]
|
||||
item_types = ["enums"]
|
||||
include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode", "StylePathCommand"]
|
||||
item_types = ["enums", "structs", "typedefs"]
|
||||
|
@ -638,6 +638,7 @@ pub mod basic_shape {
|
||||
use values::computed::basic_shape::{BasicShape, ClippingShape, FloatAreaShape, ShapeRadius};
|
||||
use values::computed::border::{BorderCornerRadius, BorderRadius};
|
||||
use values::computed::length::LengthOrPercentage;
|
||||
use values::computed::motion::OffsetPath;
|
||||
use values::computed::position;
|
||||
use values::computed::url::ComputedUrl;
|
||||
use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon};
|
||||
@ -669,6 +670,7 @@ pub mod basic_shape {
|
||||
Some(ShapeSource::Shape(shape, reference_box))
|
||||
},
|
||||
StyleShapeSourceType::URL | StyleShapeSourceType::Image => None,
|
||||
StyleShapeSourceType::Path => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -710,6 +712,29 @@ pub mod basic_shape {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a StyleShapeSource> for OffsetPath {
|
||||
fn from(other: &'a StyleShapeSource) -> Self {
|
||||
use gecko_bindings::structs::StylePathCommand;
|
||||
use values::specified::motion::{SVGPathData, PathCommand};
|
||||
match other.mType {
|
||||
StyleShapeSourceType::Path => {
|
||||
let gecko_path = unsafe { &*other.__bindgen_anon_1.mSVGPath.as_ref().mPtr };
|
||||
let result: Vec<PathCommand> =
|
||||
gecko_path.mPath.iter().map(|gecko: &StylePathCommand| {
|
||||
// unsafe: cbindgen ensures the representation is the same.
|
||||
unsafe{ ::std::mem::transmute(*gecko) }
|
||||
}).collect();
|
||||
OffsetPath::Path(SVGPathData::new(result.into_boxed_slice()))
|
||||
},
|
||||
StyleShapeSourceType::None => OffsetPath::none(),
|
||||
StyleShapeSourceType::Shape |
|
||||
StyleShapeSourceType::Box |
|
||||
StyleShapeSourceType::URL |
|
||||
StyleShapeSourceType::Image => unreachable!("Unsupported offset-path type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a StyleBasicShape> for BasicShape {
|
||||
fn from(other: &'a StyleBasicShape) -> Self {
|
||||
match other.mType {
|
||||
|
@ -3053,7 +3053,7 @@ fn static_assert() {
|
||||
scroll-snap-points-x scroll-snap-points-y
|
||||
scroll-snap-type-x scroll-snap-type-y scroll-snap-coordinate
|
||||
perspective-origin -moz-binding will-change
|
||||
overscroll-behavior-x overscroll-behavior-y
|
||||
offset-path overscroll-behavior-x overscroll-behavior-y
|
||||
overflow-clip-box-inline overflow-clip-box-block
|
||||
perspective-origin -moz-binding will-change
|
||||
shape-outside contain touch-action translate
|
||||
@ -3681,6 +3681,51 @@ fn static_assert() {
|
||||
${impl_simple_copy("contain", "mContain")}
|
||||
|
||||
${impl_simple_type_with_conversion("touch_action")}
|
||||
|
||||
pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) {
|
||||
use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_NewStyleSVGPath};
|
||||
use gecko_bindings::bindings::Gecko_SetStyleMotion;
|
||||
use gecko_bindings::structs::StyleShapeSourceType;
|
||||
use values::specified::OffsetPath;
|
||||
|
||||
let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() };
|
||||
match v {
|
||||
OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None,
|
||||
OffsetPath::Path(servo_path) => {
|
||||
motion.mOffsetPath.mType = StyleShapeSourceType::Path;
|
||||
let gecko_path = unsafe {
|
||||
let ref mut source = motion.mOffsetPath;
|
||||
Gecko_NewStyleSVGPath(source);
|
||||
&mut source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap().mPath
|
||||
};
|
||||
unsafe { gecko_path.set_len(servo_path.commands().len() as u32) };
|
||||
debug_assert_eq!(gecko_path.len(), servo_path.commands().len());
|
||||
for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.iter_mut()) {
|
||||
// unsafe: cbindgen ensures the representation is the same.
|
||||
*gecko = unsafe { transmute(*servo) };
|
||||
}
|
||||
},
|
||||
}
|
||||
unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) };
|
||||
}
|
||||
|
||||
pub fn clone_offset_path(&self) -> longhands::offset_path::computed_value::T {
|
||||
use values::specified::OffsetPath;
|
||||
match unsafe { self.gecko.mMotion.mPtr.as_ref() } {
|
||||
None => OffsetPath::none(),
|
||||
Some(v) => (&v.mOffsetPath).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_offset_path_from(&mut self, other: &Self) {
|
||||
use gecko_bindings::bindings::Gecko_CopyStyleMotions;
|
||||
unsafe { Gecko_CopyStyleMotions(&mut self.gecko.mMotion, other.gecko.mMotion.mPtr) };
|
||||
}
|
||||
|
||||
pub fn reset_offset_path(&mut self, other: &Self) {
|
||||
self.copy_offset_path_from(other);
|
||||
}
|
||||
|
||||
</%self:impl_trait>
|
||||
|
||||
<%def name="simple_image_array_property(name, shorthand, field_name)">
|
||||
|
@ -356,6 +356,17 @@ ${helpers.predefined_type(
|
||||
servo_restyle_damage="reflow_out_of_flow"
|
||||
)}
|
||||
|
||||
// Motion Path Module Level 1
|
||||
${helpers.predefined_type(
|
||||
"offset-path",
|
||||
"OffsetPath",
|
||||
"computed::OffsetPath::none()",
|
||||
animation_value_type="none",
|
||||
gecko_pref="layout.css.motion-path.enabled",
|
||||
flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
|
||||
spec="https://drafts.fxtf.org/motion-1/#offset-path-property"
|
||||
)}
|
||||
|
||||
// CSSOM View Module
|
||||
// https://www.w3.org/TR/cssom-view-1/
|
||||
${helpers.single_keyword("scroll-behavior",
|
||||
|
@ -65,6 +65,7 @@ pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercent
|
||||
pub use self::list::Quotes;
|
||||
#[cfg(feature = "gecko")]
|
||||
pub use self::list::ListStyleType;
|
||||
pub use self::motion::OffsetPath;
|
||||
pub use self::outline::OutlineStyle;
|
||||
pub use self::percentage::{Percentage, NonNegativePercentage};
|
||||
pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, ZIndex};
|
||||
@ -100,6 +101,7 @@ pub mod gecko;
|
||||
pub mod image;
|
||||
pub mod length;
|
||||
pub mod list;
|
||||
pub mod motion;
|
||||
pub mod outline;
|
||||
pub mod percentage;
|
||||
pub mod position;
|
||||
|
10
servo/components/style/values/computed/motion.rs
Normal file
10
servo/components/style/values/computed/motion.rs
Normal file
@ -0,0 +1,10 @@
|
||||
/* 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/. */
|
||||
|
||||
//! Computed types for CSS values that are related to motion path.
|
||||
|
||||
/// A computed offset-path. The computed value is as specified value.
|
||||
///
|
||||
/// https://drafts.fxtf.org/motion-1/#offset-path-property
|
||||
pub use values::specified::motion::OffsetPath as OffsetPath;
|
@ -58,6 +58,7 @@ pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercent
|
||||
pub use self::list::Quotes;
|
||||
#[cfg(feature = "gecko")]
|
||||
pub use self::list::ListStyleType;
|
||||
pub use self::motion::OffsetPath;
|
||||
pub use self::outline::OutlineStyle;
|
||||
pub use self::rect::LengthOrNumberRect;
|
||||
pub use self::resolution::Resolution;
|
||||
@ -101,6 +102,7 @@ pub mod image;
|
||||
pub mod length;
|
||||
pub mod list;
|
||||
pub mod outline;
|
||||
pub mod motion;
|
||||
pub mod percentage;
|
||||
pub mod position;
|
||||
pub mod rect;
|
||||
|
558
servo/components/style/values/specified/motion.rs
Normal file
558
servo/components/style/values/specified/motion.rs
Normal file
@ -0,0 +1,558 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Specified types for CSS values that are related to motion path.
|
||||
|
||||
use cssparser::Parser;
|
||||
use parser::{Parse, ParserContext};
|
||||
use std::fmt::{self, Write};
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
use style_traits::values::SequenceWriter;
|
||||
use values::CSSFloat;
|
||||
|
||||
/// The offset-path value.
|
||||
///
|
||||
/// https://drafts.fxtf.org/motion-1/#offset-path-property
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
|
||||
pub enum OffsetPath {
|
||||
// We could merge SVGPathData into ShapeSource, so we could reuse them. However,
|
||||
// we don't want to support other value for offset-path, so use SVGPathData only for now.
|
||||
/// Path value for path(<string>).
|
||||
#[css(function)]
|
||||
Path(SVGPathData),
|
||||
/// None value.
|
||||
None,
|
||||
// Bug 1186329: Implement ray(), <basic-shape>, <geometry-box>, and <url>.
|
||||
}
|
||||
|
||||
impl OffsetPath {
|
||||
/// Return None.
|
||||
#[inline]
|
||||
pub fn none() -> Self {
|
||||
OffsetPath::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for OffsetPath {
|
||||
fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
// Parse none.
|
||||
if input.try(|i| i.expect_ident_matching("none")).is_ok() {
|
||||
return Ok(OffsetPath::none());
|
||||
}
|
||||
|
||||
// Parse possible functions.
|
||||
let location = input.current_source_location();
|
||||
let function = input.expect_function()?.clone();
|
||||
input.parse_nested_block(move |i| {
|
||||
match_ignore_ascii_case! { &function,
|
||||
// Bug 1186329: Implement the parser for ray(), <basic-shape>, <geometry-box>,
|
||||
// and <url>.
|
||||
"path" => SVGPathData::parse(context, i).map(OffsetPath::Path),
|
||||
_ => {
|
||||
Err(location.new_custom_error(
|
||||
StyleParseErrorKind::UnexpectedFunction(function.clone())
|
||||
))
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// SVG Path parser.
|
||||
struct PathParser<'a> {
|
||||
chars: Peekable<Chars<'a>>,
|
||||
path: Vec<PathCommand>,
|
||||
}
|
||||
|
||||
macro_rules! parse_arguments {
|
||||
(
|
||||
$parser:ident,
|
||||
$abs:ident,
|
||||
$enum:ident,
|
||||
[ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
|
||||
) => {
|
||||
{
|
||||
loop {
|
||||
let $para = $func(&mut $parser.chars)?;
|
||||
$(
|
||||
skip_comma_wsp(&mut $parser.chars);
|
||||
let $other_para = $other_func(&mut $parser.chars)?;
|
||||
)*
|
||||
$parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs });
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut $parser.chars) ||
|
||||
$parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
break;
|
||||
}
|
||||
skip_comma_wsp(&mut $parser.chars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathParser<'a> {
|
||||
/// Parse a sub-path.
|
||||
fn parse_subpath(&mut self) -> Result<(), ()> {
|
||||
// Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
|
||||
// (i.e. not a valid moveto-drawto-command-group).
|
||||
self.parse_moveto()?;
|
||||
|
||||
// Handle other commands.
|
||||
loop {
|
||||
skip_wsp(&mut self.chars);
|
||||
if self.chars.peek().map_or(true, |m| *m == 'M' || *m == 'm') {
|
||||
break;
|
||||
}
|
||||
|
||||
match self.chars.next() {
|
||||
Some(command) => {
|
||||
let abs = command.is_uppercase();
|
||||
macro_rules! parse_command {
|
||||
( $($($p:pat)|+ => $parse_func:ident,)* ) => {
|
||||
match command {
|
||||
$(
|
||||
$($p)|+ => {
|
||||
skip_wsp(&mut self.chars);
|
||||
self.$parse_func(abs)?;
|
||||
},
|
||||
)*
|
||||
_ => return Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_command!(
|
||||
'Z' | 'z' => parse_closepath,
|
||||
'L' | 'l' => parse_lineto,
|
||||
'H' | 'h' => parse_h_lineto,
|
||||
'V' | 'v' => parse_v_lineto,
|
||||
'C' | 'c' => parse_curveto,
|
||||
'S' | 's' => parse_smooth_curveto,
|
||||
'Q' | 'q' => parse_quadratic_bezier_curveto,
|
||||
'T' | 't' => parse_smooth_quadratic_bezier_curveto,
|
||||
'A' | 'a' => parse_elliprical_arc,
|
||||
);
|
||||
},
|
||||
_ => break, // no more commands.
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse "moveto" command.
|
||||
fn parse_moveto(&mut self) -> Result<(), ()> {
|
||||
let command = match self.chars.next() {
|
||||
Some(c) if c == 'M' || c == 'm' => c,
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
skip_wsp(&mut self.chars);
|
||||
let point = parse_coord(&mut self.chars)?;
|
||||
let absolute = command == 'M';
|
||||
self.path.push(PathCommand::MoveTo { point, absolute } );
|
||||
|
||||
// End of string or the next character is a possible new command.
|
||||
if !skip_wsp(&mut self.chars) ||
|
||||
self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
|
||||
return Ok(());
|
||||
}
|
||||
skip_comma_wsp(&mut self.chars);
|
||||
|
||||
// If a moveto is followed by multiple pairs of coordinates, the subsequent
|
||||
// pairs are treated as implicit lineto commands.
|
||||
self.parse_lineto(absolute)
|
||||
}
|
||||
|
||||
/// Parse "closepath" command.
|
||||
fn parse_closepath(&mut self, _absolute: bool) -> Result<(), ()> {
|
||||
self.path.push(PathCommand::ClosePath);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse "lineto" command.
|
||||
fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
parse_arguments!(self, absolute, LineTo, [ point => parse_coord ])
|
||||
}
|
||||
|
||||
/// Parse horizontal "lineto" command.
|
||||
fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ])
|
||||
}
|
||||
|
||||
/// Parse vertical "lineto" command.
|
||||
fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ])
|
||||
}
|
||||
|
||||
/// Parse cubic Bézier curve command.
|
||||
fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
parse_arguments!(self, absolute, CurveTo, [
|
||||
control1 => parse_coord, control2 => parse_coord, point => parse_coord
|
||||
])
|
||||
}
|
||||
|
||||
/// Parse smooth "curveto" command.
|
||||
fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
parse_arguments!(self, absolute, SmoothCurveTo, [
|
||||
control2 => parse_coord, point => parse_coord
|
||||
])
|
||||
}
|
||||
|
||||
/// Parse quadratic Bézier curve command.
|
||||
fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
parse_arguments!(self, absolute, QuadBezierCurveTo, [
|
||||
control1 => parse_coord, point => parse_coord
|
||||
])
|
||||
}
|
||||
|
||||
/// Parse smooth quadratic Bézier curveto command.
|
||||
fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ])
|
||||
}
|
||||
|
||||
/// Parse elliptical arc curve command.
|
||||
fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> {
|
||||
// Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
|
||||
let parse_flag = |iter: &mut Peekable<Chars>| -> Result<bool, ()> {
|
||||
let value = match iter.peek() {
|
||||
Some(c) if *c == '0' || *c == '1' => *c == '1',
|
||||
_ => return Err(()),
|
||||
};
|
||||
iter.next();
|
||||
Ok(value)
|
||||
};
|
||||
parse_arguments!(self, absolute, EllipticalArc, [
|
||||
rx => parse_number,
|
||||
ry => parse_number,
|
||||
angle => parse_number,
|
||||
large_arc_flag => parse_flag,
|
||||
sweep_flag => parse_flag,
|
||||
point => parse_coord
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// The SVG path data.
|
||||
///
|
||||
/// https://www.w3.org/TR/SVG11/paths.html#PathData
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)]
|
||||
pub struct SVGPathData(Box<[PathCommand]>);
|
||||
|
||||
impl SVGPathData {
|
||||
/// Return SVGPathData by a slice of PathCommand.
|
||||
#[inline]
|
||||
pub fn new(cmd: Box<[PathCommand]>) -> Self {
|
||||
debug_assert!(!cmd.is_empty());
|
||||
SVGPathData(cmd)
|
||||
}
|
||||
|
||||
/// Get the array of PathCommand.
|
||||
#[inline]
|
||||
pub fn commands(&self) -> &[PathCommand] {
|
||||
debug_assert!(!self.0.is_empty());
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for SVGPathData {
|
||||
#[inline]
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write
|
||||
{
|
||||
dest.write_char('"')?;
|
||||
{
|
||||
let mut writer = SequenceWriter::new(dest, " ");
|
||||
for command in self.0.iter() {
|
||||
writer.item(command)?;
|
||||
}
|
||||
}
|
||||
dest.write_char('"')
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for SVGPathData {
|
||||
// We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
|
||||
// the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.)
|
||||
// e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
|
||||
// is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
|
||||
// str::Char iterator to check each character.
|
||||
fn parse<'i, 't>(
|
||||
_context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let location = input.current_source_location();
|
||||
let path_string = input.expect_string()?.as_ref();
|
||||
if path_string.is_empty() {
|
||||
// Treat an empty string as invalid, so we will not set it.
|
||||
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
// Parse the svg path string as multiple sub-paths.
|
||||
let mut path_parser = PathParser {
|
||||
chars: path_string.chars().peekable(),
|
||||
path: Vec::new(),
|
||||
};
|
||||
while skip_wsp(&mut path_parser.chars) {
|
||||
if path_parser.parse_subpath().is_err() {
|
||||
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SVGPathData::new(path_parser.path.into_boxed_slice()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The SVG path command.
|
||||
/// The fields of these commands are self-explanatory, so we skip the documents.
|
||||
/// Note: the index of the control points, e.g. control1, control2, are mapping to the control
|
||||
/// points of the Bézier curve in the spec.
|
||||
///
|
||||
/// https://www.w3.org/TR/SVG11/paths.html#PathData
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
|
||||
#[allow(missing_docs)]
|
||||
#[repr(C, u8)]
|
||||
pub enum PathCommand {
|
||||
/// The unknown type.
|
||||
/// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN
|
||||
Unknown,
|
||||
/// The "moveto" command.
|
||||
MoveTo { point: CoordPair, absolute: bool },
|
||||
/// The "lineto" command.
|
||||
LineTo { point: CoordPair, absolute: bool },
|
||||
/// The horizontal "lineto" command.
|
||||
HorizontalLineTo { x: CSSFloat, absolute: bool },
|
||||
/// The vertical "lineto" command.
|
||||
VerticalLineTo { y: CSSFloat, absolute: bool },
|
||||
/// The cubic Bézier curve command.
|
||||
CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool },
|
||||
/// The smooth curve command.
|
||||
SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool },
|
||||
/// The quadratic Bézier curve command.
|
||||
QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool },
|
||||
/// The smooth quadratic Bézier curve command.
|
||||
SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool },
|
||||
/// The elliptical arc curve command.
|
||||
EllipticalArc {
|
||||
rx: CSSFloat,
|
||||
ry: CSSFloat,
|
||||
angle: CSSFloat,
|
||||
large_arc_flag: bool,
|
||||
sweep_flag: bool,
|
||||
point: CoordPair,
|
||||
absolute: bool
|
||||
},
|
||||
/// The "closepath" command.
|
||||
ClosePath,
|
||||
}
|
||||
|
||||
impl ToCss for PathCommand {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write
|
||||
{
|
||||
use self::PathCommand::*;
|
||||
match *self {
|
||||
Unknown => dest.write_str("X"),
|
||||
ClosePath => dest.write_str("Z"),
|
||||
MoveTo { point, absolute } => {
|
||||
dest.write_char(if absolute { 'M' } else { 'm' })?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
}
|
||||
LineTo { point, absolute } => {
|
||||
dest.write_char(if absolute { 'L' } else { 'l' })?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
}
|
||||
CurveTo { control1, control2, point, absolute } => {
|
||||
dest.write_char(if absolute { 'C' } else { 'c' })?;
|
||||
dest.write_char(' ')?;
|
||||
control1.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
control2.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
QuadBezierCurveTo { control1, point, absolute } => {
|
||||
dest.write_char(if absolute { 'Q' } else { 'q' })?;
|
||||
dest.write_char(' ')?;
|
||||
control1.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => {
|
||||
dest.write_char(if absolute { 'A' } else { 'a' })?;
|
||||
dest.write_char(' ')?;
|
||||
rx.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
ry.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
angle.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
(large_arc_flag as i32).to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
(sweep_flag as i32).to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
HorizontalLineTo { x, absolute } => {
|
||||
dest.write_char(if absolute { 'H' } else { 'h' })?;
|
||||
dest.write_char(' ')?;
|
||||
x.to_css(dest)
|
||||
},
|
||||
VerticalLineTo { y, absolute } => {
|
||||
dest.write_char(if absolute { 'V' } else { 'v' })?;
|
||||
dest.write_char(' ')?;
|
||||
y.to_css(dest)
|
||||
},
|
||||
SmoothCurveTo { control2, point, absolute } => {
|
||||
dest.write_char(if absolute { 'S' } else { 's' })?;
|
||||
dest.write_char(' ')?;
|
||||
control2.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
SmoothQuadBezierCurveTo { point, absolute } => {
|
||||
dest.write_char(if absolute { 'T' } else { 't' })?;
|
||||
dest.write_char(' ')?;
|
||||
point.to_css(dest)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The path coord type.
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
#[repr(C)]
|
||||
pub struct CoordPair(CSSFloat, CSSFloat);
|
||||
|
||||
impl CoordPair {
|
||||
/// Create a CoordPair.
|
||||
#[inline]
|
||||
pub fn new(x: CSSFloat, y: CSSFloat) -> Self {
|
||||
CoordPair(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a pair of numbers into CoordPair.
|
||||
fn parse_coord(iter: &mut Peekable<Chars>) -> Result<CoordPair, ()> {
|
||||
let x = parse_number(iter)?;
|
||||
skip_comma_wsp(iter);
|
||||
let y = parse_number(iter)?;
|
||||
Ok(CoordPair::new(x, y))
|
||||
}
|
||||
|
||||
/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed
|
||||
/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating
|
||||
/// point number. In other words, the logic here is similar with that of
|
||||
/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the
|
||||
/// input is a Peekable and we only accept an integer of a floating point number.
|
||||
///
|
||||
/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF
|
||||
fn parse_number(iter: &mut Peekable<Chars>) -> Result<CSSFloat, ()> {
|
||||
// 1. Check optional sign.
|
||||
let sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
|
||||
if iter.next().unwrap() == '-' { -1. } else { 1. }
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
|
||||
// 2. Check integer part.
|
||||
let mut integral_part: f64 = 0.;
|
||||
let got_dot = if !iter.peek().map_or(false, |&n: &char| n == '.') {
|
||||
// If the first digit in integer part is neither a dot nor a digit, this is not a number.
|
||||
if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
|
||||
integral_part =
|
||||
integral_part * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
|
||||
}
|
||||
|
||||
iter.peek().map_or(false, |&n: &char| n == '.')
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
// 3. Check fractional part.
|
||||
let mut fractional_part: f64 = 0.;
|
||||
if got_dot {
|
||||
// Consume '.'.
|
||||
iter.next();
|
||||
// If the first digit in fractional part is not a digit, this is not a number.
|
||||
if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut factor = 0.1;
|
||||
while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
|
||||
fractional_part += iter.next().unwrap().to_digit(10).unwrap() as f64 * factor;
|
||||
factor *= 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut value = sign * (integral_part + fractional_part);
|
||||
|
||||
// 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to
|
||||
// treat the numbers after 'E' or 'e' are in the exponential part.
|
||||
if iter.peek().map_or(false, |&exp: &char| exp == 'E' || exp == 'e') {
|
||||
// Consume 'E' or 'e'.
|
||||
iter.next();
|
||||
let exp_sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
|
||||
if iter.next().unwrap() == '-' { -1. } else { 1. }
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
|
||||
let mut exp: f64 = 0.;
|
||||
while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
|
||||
exp = exp * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
|
||||
}
|
||||
|
||||
value *= f64::powf(10., exp * exp_sign);
|
||||
}
|
||||
|
||||
if value.is_finite() {
|
||||
Ok(value.min(::std::f32::MAX as f64).max(::std::f32::MIN as f64) as CSSFloat)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip all svg whitespaces, and return true if |iter| hasn't finished.
|
||||
#[inline]
|
||||
fn skip_wsp(iter: &mut Peekable<Chars>) -> bool {
|
||||
// Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}.
|
||||
// However, SVG 2 has one extra whitespace: \u{C}.
|
||||
// Therefore, we follow the newest spec for the definition of whitespace,
|
||||
// i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}, by is_ascii_whitespace().
|
||||
while iter.peek().map_or(false, |c: &char| c.is_ascii_whitespace()) {
|
||||
iter.next();
|
||||
}
|
||||
iter.peek().is_some()
|
||||
}
|
||||
|
||||
/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished.
|
||||
#[inline]
|
||||
fn skip_comma_wsp(iter: &mut Peekable<Chars>) -> bool {
|
||||
if !skip_wsp(iter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if *iter.peek().unwrap() != ',' {
|
||||
return true;
|
||||
}
|
||||
iter.next();
|
||||
|
||||
skip_wsp(iter)
|
||||
}
|
@ -165103,9 +165103,21 @@
|
||||
{}
|
||||
]
|
||||
],
|
||||
"css/motion/offset-path-string.html": [
|
||||
"css/motion/offset-path-string-001.html": [
|
||||
[
|
||||
"/css/motion/offset-path-string.html",
|
||||
"/css/motion/offset-path-string-001.html",
|
||||
[
|
||||
[
|
||||
"/css/motion/offset-path-string-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"css/motion/offset-path-string-002.html": [
|
||||
[
|
||||
"/css/motion/offset-path-string-002.html",
|
||||
[
|
||||
[
|
||||
"/css/motion/offset-path-string-ref.html",
|
||||
@ -568451,14 +568463,18 @@
|
||||
"6c39e7b8f4cfafe05c07d166eb65570432912b7a",
|
||||
"reftest"
|
||||
],
|
||||
"css/motion/offset-path-string-001.html": [
|
||||
"79d957d82b8e3c603ed16598f461a805c90681dd",
|
||||
"reftest"
|
||||
],
|
||||
"css/motion/offset-path-string-002.html": [
|
||||
"0d2fcbbb661c2fe0e5b57ff780d78b2f8b6f627b",
|
||||
"reftest"
|
||||
],
|
||||
"css/motion/offset-path-string-ref.html": [
|
||||
"5c5ff5f6f2ddc4696f2d51266199fe052464d9e6",
|
||||
"support"
|
||||
],
|
||||
"css/motion/offset-path-string.html": [
|
||||
"79d957d82b8e3c603ed16598f461a805c90681dd",
|
||||
"reftest"
|
||||
],
|
||||
"css/motion/offset-rotate-001.html": [
|
||||
"55147698a7f2f02a57f0fe3adc8b33257d1e212f",
|
||||
"reftest"
|
||||
@ -568500,11 +568516,11 @@
|
||||
"testharness"
|
||||
],
|
||||
"css/motion/parsing/offset-path-parsing-invalid.html": [
|
||||
"c0a32486922b4b1b482817f409571e1e6c4219f7",
|
||||
"7fbd06a508a322ac0969eb11c4299de50fd254e7",
|
||||
"testharness"
|
||||
],
|
||||
"css/motion/parsing/offset-path-parsing-valid.html": [
|
||||
"c1e229e1a05a4c85845384ace9b884125f579415",
|
||||
"e7797686e4ac524ac9dc9f8525dbd5a24adeec29",
|
||||
"testharness"
|
||||
],
|
||||
"css/motion/parsing/offset-position-parsing-invalid.html": [
|
||||
|
1
testing/web-platform/meta/css/motion/__dir__.ini
Normal file
1
testing/web-platform/meta/css/motion/__dir__.ini
Normal file
@ -0,0 +1 @@
|
||||
prefs: [layout.css.motion-path.enabled:true]
|
@ -1,7 +1,4 @@
|
||||
[offset-path-interpolation-001.html]
|
||||
["path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -20,9 +17,6 @@
|
||||
[Animation between "path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 1 2 L 3 4 Z')" and "none" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -32,18 +26,6 @@
|
||||
[Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 0.125]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 0.875]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 1]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 10 0 H 11')" and "path('M 20 0 V 2')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 10 0 H 11')" and "path('M 20 0 V 2')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -62,9 +44,6 @@
|
||||
[Animation between "path('M 10 0 H 11')" and "path('M 20 0 V 2')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -83,9 +62,6 @@
|
||||
[Animation between "path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 0 0 Z')" and "path('M 0 0 Z')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 0 0 Z')" and "path('M 0 0 Z')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -104,9 +80,6 @@
|
||||
[Animation between "path('M 0 0 Z')" and "path('M 0 0 Z')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 20 70')" and "path('M 100 30')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 20 70')" and "path('M 100 30')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -125,9 +98,6 @@
|
||||
[Animation between "path('M 20 70')" and "path('M 100 30')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 20 70')" and "path('m 100 30')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 20 70')" and "path('m 100 30')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -146,9 +116,6 @@
|
||||
[Animation between "path('m 20 70')" and "path('m 100 30')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -167,9 +134,6 @@
|
||||
[Animation between "path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 100 200 l 20 70')" and "path('m 100 200 l 100 30')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 100 200 l 20 70')" and "path('m 100 200 l 100 30')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
[offset-path-interpolation-002.html]
|
||||
["path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -20,9 +17,6 @@
|
||||
[Animation between "path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -41,9 +35,6 @@
|
||||
[Animation between "path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -62,9 +53,6 @@
|
||||
[Animation between "path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -83,9 +71,6 @@
|
||||
[Animation between "path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -104,9 +89,6 @@
|
||||
[Animation between "path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 100 400 a 10 20 30 1 0 40 50')" and "path('m 300 200 a 50 60 70 0 1 80 90')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 100 400 a 10 20 30 1 0 40 50')" and "path('m 300 200 a 50 60 70 0 1 80 90')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
[offset-path-interpolation-003.html]
|
||||
["path('M 50 60 H 70')" and "path('M 10 140 H 270')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 50 60 H 70')" and "path('M 10 140 H 270')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -20,9 +17,6 @@
|
||||
[Animation between "path('M 50 60 H 70')" and "path('M 10 140 H 270')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 50 60 h 20')" and "path('m 10 140 h 260')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 50 60 h 20')" and "path('m 10 140 h 260')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -41,9 +35,6 @@
|
||||
[Animation between "path('m 50 60 h 20')" and "path('m 10 140 h 260')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 50 60 V 70')" and "path('M 10 140 V 270')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 50 60 V 70')" and "path('M 10 140 V 270')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -62,9 +53,6 @@
|
||||
[Animation between "path('M 50 60 V 70')" and "path('M 10 140 V 270')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 50 60 v 10')" and "path('m 10 140 v 130')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 50 60 v 10')" and "path('m 10 140 v 130')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -83,9 +71,6 @@
|
||||
[Animation between "path('m 50 60 v 10')" and "path('m 10 140 v 130')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -104,9 +89,6 @@
|
||||
[Animation between "path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -125,9 +107,6 @@
|
||||
[Animation between "path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -146,9 +125,6 @@
|
||||
[Animation between "path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 12 34 t 33 33')" and "path('m 20 26 t 41 25')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 12 34 t 33 33')" and "path('m 20 26 t 41 25')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
[offset-path-interpolation-004.html]
|
||||
["path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -20,9 +17,6 @@
|
||||
[Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -41,9 +35,6 @@
|
||||
[Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -62,9 +53,6 @@
|
||||
[Animation between "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -83,9 +71,6 @@
|
||||
[Animation between "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -104,9 +89,6 @@
|
||||
[Animation between "path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -125,9 +107,6 @@
|
||||
[Animation between "path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
@ -146,9 +125,6 @@
|
||||
[Animation between "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["path('m 10 20 a 10 20 30 1 0 40 50 a 110 120 30 1 1 140 50')" and "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "path('m 10 20 a 10 20 30 1 0 40 50 a 110 120 30 1 1 140 50')" and "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -32,27 +32,9 @@
|
||||
[Animation between "ray(0deg closest-corner)" and "none" at progress 0.125]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "ray(0deg closest-corner)" and "none" at progress 0.875]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "ray(0deg closest-corner)" and "none" at progress 1]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "ray(0deg closest-corner)" and "none" at progress 2]
|
||||
expected: FAIL
|
||||
|
||||
["none" and "ray(20deg closest-side)" are valid offset-path values]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "none" and "ray(20deg closest-side)" at progress -1]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "none" and "ray(20deg closest-side)" at progress 0]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "none" and "ray(20deg closest-side)" at progress 0.125]
|
||||
expected: FAIL
|
||||
|
||||
[Animation between "none" and "ray(20deg closest-side)" at progress 0.875]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
[offset-path-string-001.html]
|
||||
expected: FAIL
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1429299
|
@ -1,2 +0,0 @@
|
||||
[offset-path-string.html]
|
||||
expected: FAIL
|
@ -5,12 +5,6 @@
|
||||
[Serialization should round-trip after setting e.style['offset-path'\] = "fill-box ellipse(50% 60%)"]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['offset-path'\] = "none" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[Serialization should round-trip after setting e.style['offset-path'\] = "none"]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['offset-path'\] = "ray(0rad closest-side)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
@ -44,18 +38,6 @@
|
||||
[e.style['offset-path'\] = "ray(calc(180deg - 45deg) farthest-side)" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['offset-path'\] = "path('m 0 0 h -100')" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[Serialization should round-trip after setting e.style['offset-path'\] = "path('m 0 0 h -100')"]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['offset-path'\] = "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z')" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
[Serialization should round-trip after setting e.style['offset-path'\] = "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z')"]
|
||||
expected: FAIL
|
||||
|
||||
[e.style['offset-path'\] = "url(\\"http://www.example.com/index.html#polyline1\\")" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Motion Path: path(string) paths</title>
|
||||
<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-path-property">
|
||||
<link rel="match" href="offset-path-string-ref.html">
|
||||
<meta name="assert" content="This tests that path(<string>) generates a rotation and translation.">
|
||||
<style>
|
||||
#target {
|
||||
position: absolute;
|
||||
left: 300px;
|
||||
top: 0px;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
background-color: lime;
|
||||
transform-origin: 0px 0px;
|
||||
offset-path: path('m 0 120 v 200');
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="target"></div>
|
||||
</body>
|
||||
</html>
|
@ -14,7 +14,7 @@
|
||||
<script>
|
||||
// arc path segments must have at least 7 arguments.
|
||||
// https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
|
||||
test_invalid_value("offset-path", "path('M 20 30 A 60 70 80')");
|
||||
test_invalid_value("offset-path", 'path("M 20 30 A 60 70 80")');
|
||||
|
||||
test_invalid_value("offset-path", "ray(0 sides)");
|
||||
test_invalid_value("offset-path", "ray(0deg)");
|
||||
|
@ -21,8 +21,8 @@ test_valid_value("offset-path", "ray(270deg farthest-corner contain)");
|
||||
test_valid_value("offset-path", "ray(-720deg sides)");
|
||||
test_valid_value("offset-path", "ray(calc(180deg - 45deg) farthest-side)", "ray(calc(135deg) farthest-side)");
|
||||
|
||||
test_valid_value("offset-path", "path('m 0 0 h -100')");
|
||||
test_valid_value("offset-path", "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z')");
|
||||
test_valid_value("offset-path", 'path("m 0 0 h -100")');
|
||||
test_valid_value("offset-path", 'path("M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z")');
|
||||
|
||||
test_valid_value("offset-path", 'url("http://www.example.com/index.html#polyline1")');
|
||||
|
||||
|
@ -36,10 +36,18 @@ use std::boxed::Box;
|
||||
use std::env;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
#[cfg(target_os = "android")]
|
||||
use std::os::raw::c_int;
|
||||
#[cfg(target_os = "android")]
|
||||
use log::Level;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use log::Log;
|
||||
use std::panic;
|
||||
|
||||
extern "C" {
|
||||
fn gfx_critical_note(msg: *const c_char);
|
||||
#[cfg(target_os = "android")]
|
||||
fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
|
||||
}
|
||||
|
||||
struct GeckoLogger {
|
||||
@ -84,6 +92,29 @@ impl GeckoLogger {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn log_out(&self, record: &log::Record) {
|
||||
self.logger.log(record);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn log_out(&self, record: &log::Record) {
|
||||
let msg = CString::new(format!("{}", record.args())).unwrap();
|
||||
let tag = CString::new(record.module_path().unwrap()).unwrap();
|
||||
let prio = match record.metadata().level() {
|
||||
Level::Error => 6 /* ERROR */,
|
||||
Level::Warn => 5 /* WARN */,
|
||||
Level::Info => 4 /* INFO */,
|
||||
Level::Debug => 3 /* DEBUG */,
|
||||
Level::Trace => 2 /* VERBOSE */,
|
||||
};
|
||||
// Output log directly to android log, since env_logger can output log
|
||||
// only to stderr or stdout.
|
||||
unsafe {
|
||||
__android_log_write(prio, tag.as_ptr(), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl log::Log for GeckoLogger {
|
||||
@ -94,7 +125,7 @@ impl log::Log for GeckoLogger {
|
||||
fn log(&self, record: &log::Record) {
|
||||
// Forward log to gfxCriticalNote, if the log should be in gfx crash log.
|
||||
self.maybe_log_to_gfx_critical_note(record);
|
||||
self.logger.log(record);
|
||||
self.log_out(record);
|
||||
}
|
||||
|
||||
fn flush(&self) { }
|
||||
|
Loading…
x
Reference in New Issue
Block a user