From e459b11dc17b36abb799408533dc9d30beed7ff9 Mon Sep 17 00:00:00 2001 From: Jan Beich Date: Tue, 21 Aug 2018 16:02:00 +0300 Subject: [PATCH 01/16] Bug 1484535 - Allow C++ files to check MOZ_SYSTEM_ICU. r=froydnj --- browser/installer/Makefile.in | 3 --- build/autoconf/icu.m4 | 1 + config/moz.build | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/browser/installer/Makefile.in b/browser/installer/Makefile.in index 2e34a7746459..3d9389f829ed 100644 --- a/browser/installer/Makefile.in +++ b/browser/installer/Makefile.in @@ -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 diff --git a/build/autoconf/icu.m4 b/build/autoconf/icu.m4 index 32fc84f30dd9..f1607575f3b9 100644 --- a/build/autoconf/icu.m4 +++ b/build/autoconf/icu.m4 @@ -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) diff --git a/config/moz.build b/config/moz.build index a990abb81b6d..d9c28fe54d05 100644 --- a/config/moz.build +++ b/config/moz.build @@ -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', ] From ce0d96216eac6fbbfc66b9a49b5078a67ae454fd Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Wed, 22 Aug 2018 10:01:34 +0900 Subject: [PATCH 02/16] Bug 1483188 - Fix the assertion for reaction record fields. r=anba --- js/src/builtin/Promise.cpp | 66 +++++++++++++++---- .../jit-test/tests/auto-regress/bug1483188.js | 13 ++++ 2 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 js/src/jit-test/tests/auto-regress/bug1483188.js diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 761e6b63e49a..407dd01a7581 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -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() && resultPromise->is()) { Handle promise = nextPromiseObj.as(); 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 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()) { - 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()) { + // 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()); + 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 promise if (promise->state() != JS::PromiseState::Pending) return true; + // `dependentPromise` should be a maybe-wrapped Promise. + MOZ_ASSERT(UncheckedUnwrap(dependentPromise)->is()); + // Leave resolve and reject as null. Rooted capability(cx); capability.promise().set(dependentPromise); diff --git a/js/src/jit-test/tests/auto-regress/bug1483188.js b/js/src/jit-test/tests/auto-regress/bug1483188.js new file mode 100644 index 000000000000..0aebdba25c09 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1483188.js @@ -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); + From 10a0917d53c6e14461513fab119e0f92c9580ad2 Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Tue, 21 Aug 2018 22:03:09 -0400 Subject: [PATCH 03/16] Bug 1485125 - Lazy load the Device Modal in RDM. r=miker --- .../client/responsive.html/components/App.js | 25 +++++++++++-------- .../responsive.html/components/DeviceAdder.js | 25 +++++++++---------- .../responsive.html/components/DeviceModal.js | 25 ++++--------------- .../test/browser/browser_device_modal_exit.js | 11 +++----- .../browser/browser_device_modal_submit.js | 14 +++++------ .../responsive.html/test/browser/head.js | 12 ++++----- 6 files changed, 46 insertions(+), 66 deletions(-) diff --git a/devtools/client/responsive.html/components/App.js b/devtools/client/responsive.html/components/App.js index ab67cc6dccad..98df6862c3df 100644 --- a/devtools/client/responsive.html/components/App.js +++ b/devtools/client/responsive.html/components/App.js @@ -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 ); } } diff --git a/devtools/client/responsive.html/components/DeviceAdder.js b/devtools/client/responsive.html/components/DeviceAdder.js index 159ba6ce5e7e..98c8b9eb8e7e 100644 --- a/devtools/client/responsive.html/components/DeviceAdder.js +++ b/devtools/client/responsive.html/components/DeviceAdder.js @@ -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, diff --git a/devtools/client/responsive.html/components/DeviceModal.js b/devtools/client/responsive.html/components/DeviceModal.js index b82b046b2eee..d4ffd2140330 100644 --- a/devtools/client/responsive.html/components/DeviceModal.js +++ b/devtools/client/responsive.html/components/DeviceModal.js @@ -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); } diff --git a/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js b/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js index 76ea56b7400d..883f0c6dd2df 100644 --- a/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js +++ b/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js @@ -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."); }); diff --git a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js index 044a585db193..6caaf42800cf 100644 --- a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js +++ b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js @@ -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 diff --git a/devtools/client/responsive.html/test/browser/head.js b/devtools/client/responsive.html/test/browser/head.js index 5745224b7ddd..fd921c930ade 100644 --- a/devtools/client/responsive.html/test/browser/head.js +++ b/devtools/client/responsive.html/test/browser/head.js @@ -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 ] = From d9c9ac71d981c29d95928edae6a423ab0bee391e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20C=C3=A1ceres?= Date: Tue, 21 Aug 2018 20:16:00 +0300 Subject: [PATCH 04/16] Bug 1483156 - Currency of PaymentItem total should be 'USD', but got BOB r=edenchuang --HG-- extra : amend_source : 8bfb03ef649733c587b1b04bfea6260fb5b2b4fd --- .../CurrencyAmountValidationChromeScript.js | 60 +- .../test/test_currency_amount_validation.html | 667 +++++++++--------- 2 files changed, 355 insertions(+), 372 deletions(-) diff --git a/dom/payments/test/CurrencyAmountValidationChromeScript.js b/dom/payments/test/CurrencyAmountValidationChromeScript.js index 586e0f703c06..3a24c813c4bb 100644 --- a/dom/payments/test/CurrencyAmountValidationChromeScript.js +++ b/dom/payments/test/CurrencyAmountValidationChromeScript.js @@ -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")); diff --git a/dom/payments/test/test_currency_amount_validation.html b/dom/payments/test/test_currency_amount_validation.html index 471c63b8ff01..a7913ddcf10c 100644 --- a/dom/payments/test/test_currency_amount_validation.html +++ b/dom/payments/test/test_currency_amount_validation.html @@ -1,376 +1,353 @@ - + - - - Test for PaymentRequest API currency amount validation - - - + - +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 + ); +}); + Mozilla Bug 1367669 -Mozilla Bug 1388661 - - +Mozilla Bug 1388661 \ No newline at end of file From 89dfab93fc9f992ac94a4608ef274688ed919577 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Wed, 22 Aug 2018 08:58:08 +0900 Subject: [PATCH 05/16] Bug 1485210 - Add --sym-offsets=yes to valgrind command line. r=njn When valgrind prints out backtraces, it prints raw addresses and symbol names, but that doesn't help find the exact code that caused the errors, because we don't know where the libraries are loaded. With --sym-offsets=yes, it adds the offset from the symbol, which allows to find the relevant code in the binary. --- build/valgrind/mach_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build/valgrind/mach_commands.py b/build/valgrind/mach_commands.py index ac5e86249933..2bcb6346e79b 100644 --- a/build/valgrind/mach_commands.py +++ b/build/valgrind/mach_commands.py @@ -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', From f096eb50f27637ec9a586ab9796d2e2108833bf7 Mon Sep 17 00:00:00 2001 From: sotaro Date: Wed, 22 Aug 2018 13:48:53 +0900 Subject: [PATCH 06/16] Bug 1457390 - Forward rust log to android_log on android r=bholley --- toolkit/library/rust/shared/lib.rs | 33 +++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index 55c4e97a4d85..4fcf1b08db94 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -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) { } From a3bac789059e581d132e219e0211ffb49ae2a503 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 6 Jul 2018 13:37:51 -0700 Subject: [PATCH 07/16] Bug 1429298 - Part 1: Define the preference for motion-path. r=emilio Define the preference. I will enable it only for debug usage and test coverage in a different patch. Differential Revision: https://phabricator.services.mozilla.com/D2962 --- modules/libpref/init/all.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 1bb5deed4df1..45b05c12e840 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3073,6 +3073,9 @@ 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? +pref("layout.css.motion-path.enabled", false); + // pref for which side vertical scrollbars should be on // 0 = end-side in UI direction // 1 = end-side in document/content direction From a8bd6dfc8a7e8c6dfbccdecf8127af127e885399 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 6 Jul 2018 14:31:52 -0700 Subject: [PATCH 08/16] Bug 1429298 - Part 2: Define offset-path and implement it in style system. r=emilio Define OffsetPath & SVGPathData on the servo-side, and StyleMotion & StyleSVGPath on the gecko-side. We parse the SVG Path string into a vector of PathCommand. To build the gfx::Path, we will convert it into gfx::Path later in a different patch. The basic flow is: * Parse SVG Path String into SVGPathData (in Rust). * Use cbindgen to make sure the layout of PathCommand and StylePathCommand, and then set the Box[PathCommand] into nsTArray. * Try to convert nsTArray into gfx::Path. (This part will be implemented in a different patch.) Finally, we use the gfx::Path to create a motion path transform. The layout implementation is in the later patch. Differential Revision: https://phabricator.services.mozilla.com/D2963 --- .../server/actors/animation-type-longhand.js | 1 + .../shared/css/generated/properties-db.js | 5 + layout/generic/nsFloatManager.cpp | 4 + layout/style/ServoBindings.cpp | 29 + layout/style/ServoBindings.h | 6 + layout/style/ServoBindings.toml | 1 + layout/style/ServoCSSPropList.mako.py | 1 + layout/style/nsComputedDOMStyle.cpp | 5 + layout/style/nsStyleConsts.h | 1 + layout/style/nsStyleStruct.cpp | 46 ++ layout/style/nsStyleStruct.h | 62 +- layout/style/test/property_database.js | 22 + servo/components/style/cbindgen.toml | 5 +- servo/components/style/gecko/conversions.rs | 25 + .../components/style/properties/gecko.mako.rs | 47 +- .../style/properties/longhands/box.mako.rs | 11 + servo/components/style/values/computed/mod.rs | 2 + .../style/values/computed/motion.rs | 10 + .../components/style/values/specified/mod.rs | 2 + .../style/values/specified/motion.rs | 643 ++++++++++++++++++ 20 files changed, 923 insertions(+), 5 deletions(-) create mode 100644 servo/components/style/values/computed/motion.rs create mode 100644 servo/components/style/values/specified/motion.rs diff --git a/devtools/server/actors/animation-type-longhand.js b/devtools/server/actors/animation-type-longhand.js index 58df958dc45b..66d9cfda1dd6 100644 --- a/devtools/server/actors/animation-type-longhand.js +++ b/devtools/server/actors/animation-type-longhand.js @@ -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", diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index 0312775a542b..3df6c676a7ac 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -2949,6 +2949,7 @@ exports.CSS_PROPERTIES = { "rotate", "scale", "translate", + "offset-path", "scroll-behavior", "scroll-snap-type-x", "scroll-snap-type-y", @@ -9331,6 +9332,10 @@ exports.PREFERENCES = [ "font-variation-settings", "layout.css.font-variations.enabled" ], + [ + "offset-path", + "layout.css.motion-path.enabled" + ], [ "rotate", "layout.css.individual-transform.enabled" diff --git a/layout/generic/nsFloatManager.cpp b/layout/generic/nsFloatManager.cpp index 686d9552ad9a..34abb4b8db2e 100644 --- a/layout/generic/nsFloatManager.cpp +++ b/layout/generic/nsFloatManager.cpp @@ -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(), diff --git a/layout/style/ServoBindings.cpp b/layout/style/ServoBindings.cpp index 954b2d4a775a..7bd727bae2ee 100644 --- a/layout/style/ServoBindings.cpp +++ b/layout/style/ServoBindings.cpp @@ -1950,6 +1950,35 @@ Gecko_NewShapeImage(mozilla::StyleShapeSource* aShape) aShape->SetShapeImage(MakeUnique()); } +void +Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* aShape) +{ + MOZ_ASSERT(aShape); + aShape->SetPath(MakeUnique()); +} + +void +Gecko_SetStyleMotion(UniquePtr* aMotion, + mozilla::StyleMotion* aValue) +{ + MOZ_ASSERT(aMotion); + aMotion->reset(aValue); +} + +mozilla::StyleMotion* +Gecko_NewStyleMotion() +{ + return new StyleMotion(); +} + +void +Gecko_CopyStyleMotions(mozilla::UniquePtr* aMotion, + const mozilla::StyleMotion* aOther) +{ + MOZ_ASSERT(aMotion); + *aMotion = aOther ? MakeUnique(*aOther) : nullptr; +} + void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len) { diff --git a/layout/style/ServoBindings.h b/layout/style/ServoBindings.h index c195a89cc0e0..4da68350c022 100644 --- a/layout/style/ServoBindings.h +++ b/layout/style/ServoBindings.h @@ -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* aMotion, + mozilla::StyleMotion* aValue); +mozilla::StyleMotion* Gecko_NewStyleMotion(); +void Gecko_CopyStyleMotions(mozilla::UniquePtr* motion, + const mozilla::StyleMotion* other); void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len); void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest); diff --git a/layout/style/ServoBindings.toml b/layout/style/ServoBindings.toml index 665f56468c8e..fb8e7828b6bd 100644 --- a/layout/style/ServoBindings.toml +++ b/layout/style/ServoBindings.toml @@ -473,6 +473,7 @@ structs-types = [ "mozilla::FontWeight", "mozilla::MallocSizeOf", "mozilla::OriginFlags", + "mozilla::StyleMotion", "mozilla::UniquePtr", "mozilla::StyleDisplayMode", "ServoRawOffsetArc", diff --git a/layout/style/ServoCSSPropList.mako.py b/layout/style/ServoCSSPropList.mako.py index 6de1fe1dbe01..08438c1590b5 100644 --- a/layout/style/ServoCSSPropList.mako.py +++ b/layout/style/ServoCSSPropList.mako.py @@ -94,6 +94,7 @@ SERIALIZED_PREDEFINED_TYPES = [ "NonNegativeLength", "NonNegativeLengthOrPercentage", "ListStyleType", + "OffsetPath", "Opacity", "Resize", "url::ImageUrlOrNone", diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 0590e3ea0f1b..073a088dcd94 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -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; } diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index 6af07429d5ef..4b0cdcee9f1e 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -198,6 +198,7 @@ enum class StyleShapeSourceType : uint8_t { Image, // shape-outside only Shape, Box, + Path, // SVG path function }; // -moz-stack-sizing diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index d82ccd87db8f..dd437c759afb 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -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 aBasicShape, mType = StyleShapeSourceType::Shape; } +void +StyleShapeSource::SetPath(UniquePtr aPath) +{ + MOZ_ASSERT(aPath); + DoDestroy(); + new (&mSVGPath) UniquePtr(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(*aOther.GetPath())); + break; } } @@ -1137,6 +1153,9 @@ StyleShapeSource::DoDestroy() case StyleShapeSourceType::URL: mShapeImage.~UniquePtr(); break; + case StyleShapeSourceType::Path: + mSVGPath.~UniquePtr(); + break; case StyleShapeSourceType::None: case StyleShapeSourceType::Box: // Not a union type, so do nothing. @@ -3617,6 +3636,9 @@ nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource) , mSpecifiedRotate(aSource.mSpecifiedRotate) , mSpecifiedTranslate(aSource.mSpecifiedTranslate) , mSpecifiedScale(aSource.mSpecifiedScale) + , mMotion(aSource.mMotion + ? MakeUnique(*aSource.mMotion) + : nullptr) , mCombinedTransform(aSource.mCombinedTransform) , mTransformOrigin{ aSource.mTransformOrigin[0], aSource.mTransformOrigin[1], @@ -3735,6 +3757,29 @@ CompareTransformValues(const RefPtr& 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 +3911,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; diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 2b3c8787d0c4..1f69997897db 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1971,6 +1971,27 @@ private: nsStyleCorners mRadius; }; +struct StyleSVGPath final +{ + const nsTArray& 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 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 aPath); + private: void* operator new(size_t) = delete; @@ -2044,13 +2072,41 @@ private: union { mozilla::UniquePtr mBasicShape; mozilla::UniquePtr mShapeImage; - // TODO: Bug 1429298, implement SVG Path function. + mozilla::UniquePtr 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,6 +2181,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay RefPtr mSpecifiedRotate; RefPtr mSpecifiedTranslate; RefPtr mSpecifiedScale; + mozilla::UniquePtr mMotion; // Used to store the final combination of mSpecifiedTranslate, // mSpecifiedRotate, mSpecifiedScale and mSpecifiedTransform. @@ -2380,7 +2437,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 { diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 9d9a7519536b..e38f81cf8e9a 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -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", diff --git a/servo/components/style/cbindgen.toml b/servo/components/style/cbindgen.toml index 5a0e5e19ea74..c0eeee9b968a 100644 --- a/servo/components/style/cbindgen.toml +++ b/servo/components/style/cbindgen.toml @@ -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"] diff --git a/servo/components/style/gecko/conversions.rs b/servo/components/style/gecko/conversions.rs index 3b1f31a1b08d..154ad95bc439 100644 --- a/servo/components/style/gecko/conversions.rs +++ b/servo/components/style/gecko/conversions.rs @@ -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 = + 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 { diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index 33a66498b2d9..596dc29fb19b 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -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); + } + <%def name="simple_image_array_property(name, shorthand, field_name)"> diff --git a/servo/components/style/properties/longhands/box.mako.rs b/servo/components/style/properties/longhands/box.mako.rs index 6b6bcf9cbe3f..4ef45a502bcc 100644 --- a/servo/components/style/properties/longhands/box.mako.rs +++ b/servo/components/style/properties/longhands/box.mako.rs @@ -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", diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs index 9a6b5fd76b58..d8cc938c1f3c 100644 --- a/servo/components/style/values/computed/mod.rs +++ b/servo/components/style/values/computed/mod.rs @@ -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; diff --git a/servo/components/style/values/computed/motion.rs b/servo/components/style/values/computed/motion.rs new file mode 100644 index 000000000000..935ba57f8453 --- /dev/null +++ b/servo/components/style/values/computed/motion.rs @@ -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; diff --git a/servo/components/style/values/specified/mod.rs b/servo/components/style/values/specified/mod.rs index 2da4c7e93d3e..1bd52e916d07 100644 --- a/servo/components/style/values/specified/mod.rs +++ b/servo/components/style/values/specified/mod.rs @@ -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; diff --git a/servo/components/style/values/specified/motion.rs b/servo/components/style/values/specified/motion.rs new file mode 100644 index 000000000000..8b370369b852 --- /dev/null +++ b/servo/components/style/values/specified/motion.rs @@ -0,0 +1,643 @@ +/* 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(). + #[css(function)] + Path(SVGPathData), + /// None value. + None, + // Bug 1186329: Implement ray(), , , and . +} + +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> { + // 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(), , , + // and . + "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>, + path: Vec, +} + +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(); + match command { + 'Z' | 'z' => { + // Note: A "closepath" coulbe be followed immediately by "moveto" or + // any other command, so we don't break this loop. + self.path.push(PathCommand::ClosePath); + }, + 'L' | 'l' => { + skip_wsp(&mut self.chars); + self.parse_lineto(abs)?; + }, + 'H' | 'h' => { + skip_wsp(&mut self.chars); + self.parse_h_lineto(abs)?; + }, + 'V' | 'v' => { + skip_wsp(&mut self.chars); + self.parse_v_lineto(abs)?; + }, + 'C' | 'c' => { + skip_wsp(&mut self.chars); + self.parse_curveto(abs)?; + }, + 'S' | 's' => { + skip_wsp(&mut self.chars); + self.parse_smooth_curveto(abs)?; + }, + 'Q' | 'q' => { + skip_wsp(&mut self.chars); + self.parse_quadratic_bezier_curveto(abs)?; + }, + 'T' | 't' => { + skip_wsp(&mut self.chars); + self.parse_smooth_quadratic_bezier_curveto(abs)?; + }, + 'A' | 'a' => { + skip_wsp(&mut self.chars); + self.parse_elliprical_arc(abs)?; + }, + _ => return Err(()), + } + }, + _ => 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 "lineto" command. + fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> { + loop { + let point = parse_coord(&mut self.chars)?; + self.path.push(PathCommand::LineTo { 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } + + /// Parse horizontal "lineto" command. + fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> { + loop { + let x = parse_number(&mut self.chars)?; + self.path.push(PathCommand::HorizontalLineTo { x, 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } + + /// Parse vertical "lineto" command. + fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> { + loop { + let y = parse_number(&mut self.chars)?; + self.path.push(PathCommand::VerticalLineTo { y, 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } + + /// Parse cubic Bézier curve command. + fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> { + loop { + let control1 = parse_coord(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let control2 = parse_coord(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let point = parse_coord(&mut self.chars)?; + + self.path.push(PathCommand::CurveTo { control1, control2, 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } + + /// Parse smooth "curveto" command. + fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> { + loop { + let control2 = parse_coord(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let point = parse_coord(&mut self.chars)?; + + self.path.push(PathCommand::SmoothCurveTo { control2, 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } + + /// Parse quadratic Bézier curve command. + fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { + loop { + let control1 = parse_coord(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let point = parse_coord(&mut self.chars)?; + + self.path.push(PathCommand::QuadBezierCurveTo { control1, 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } + + /// Parse smooth quadratic Bézier curveto command. + fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { + loop { + let point = parse_coord(&mut self.chars)?; + + self.path.push(PathCommand::SmoothQuadBezierCurveTo { 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } + + /// Parse elliptical arc curve command. + fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> { + // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). + let parse_flag = |iter: &mut Peekable| -> Result { + let value = match iter.peek() { + Some(c) if *c == '0' || *c == '1' => *c == '1', + _ => return Err(()), + }; + iter.next(); + Ok(value) + }; + + loop { + let rx = parse_number(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let ry = parse_number(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let angle = parse_number(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let large_arc_flag = parse_flag(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let sweep_flag = parse_flag(&mut self.chars)?; + skip_comma_wsp(&mut self.chars); + let point = parse_coord(&mut self.chars)?; + + self.path.push( + PathCommand::EllipticalArc { + rx, ry, angle, large_arc_flag, sweep_flag, 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()) { + break; + } + skip_comma_wsp(&mut self.chars); + } + Ok(()) + } +} + +/// The SVG path data. +/// +/// https://www.w3.org/TR/SVG11/paths.html#PathData +#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)] +pub struct SVGPathData(Box<[PathCommand]>); + +impl SVGPathData { + /// Return SVGPathData by a slice of PathCommand. + #[inline] + pub fn new(cmd: Box<[PathCommand]>) -> Self { + debug_assert!(!cmd.is_empty()); + SVGPathData(cmd) + } + + /// Get the array of PathCommand. + #[inline] + pub fn commands(&self) -> &[PathCommand] { + debug_assert!(!self.0.is_empty()); + &self.0 + } +} + +impl ToCss for SVGPathData { + #[inline] + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write + { + dest.write_char('"')?; + { + let mut writer = SequenceWriter::new(dest, " "); + for command in self.0.iter() { + writer.item(command)?; + } + } + dest.write_char('"') + } +} + +impl Parse for SVGPathData { + // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make + // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.) + // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident + // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable + // str::Char iterator to check each character. + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + let location = input.current_source_location(); + let path_string = input.expect_string()?.as_ref(); + if path_string.is_empty() { + // Treat an empty string as invalid, so we will not set it. + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + // Parse the svg path string as multiple sub-paths. + let mut path_parser = PathParser { + chars: path_string.chars().peekable(), + path: Vec::new(), + }; + while skip_wsp(&mut path_parser.chars) { + if path_parser.parse_subpath().is_err() { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } + + Ok(SVGPathData::new(path_parser.path.into_boxed_slice())) + } +} + + +/// The SVG path command. +/// The fields of these commands are self-explanatory, so we skip the documents. +/// Note: the index of the control points, e.g. control1, control2, are mapping to the control +/// points of the Bézier curve in the spec. +/// +/// https://www.w3.org/TR/SVG11/paths.html#PathData +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)] +#[allow(missing_docs)] +#[repr(C, u8)] +pub enum PathCommand { + /// The unknown type. + /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN + Unknown, + /// The "moveto" command. + MoveTo { point: CoordPair, absolute: bool }, + /// The "lineto" command. + LineTo { point: CoordPair, absolute: bool }, + /// The horizontal "lineto" command. + HorizontalLineTo { x: CSSFloat, absolute: bool }, + /// The vertical "lineto" command. + VerticalLineTo { y: CSSFloat, absolute: bool }, + /// The cubic Bézier curve command. + CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool }, + /// The smooth curve command. + SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool }, + /// The quadratic Bézier curve command. + QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool }, + /// The smooth quadratic Bézier curve command. + SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool }, + /// The elliptical arc curve command. + EllipticalArc { + rx: CSSFloat, + ry: CSSFloat, + angle: CSSFloat, + large_arc_flag: bool, + sweep_flag: bool, + point: CoordPair, + absolute: bool + }, + /// The "closepath" command. + ClosePath, +} + +impl ToCss for PathCommand { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write + { + use self::PathCommand::*; + match *self { + Unknown => dest.write_str("X"), + ClosePath => dest.write_str("Z"), + MoveTo { point, absolute } => { + dest.write_char(if absolute { 'M' } else { 'm' })?; + dest.write_char(' ')?; + point.to_css(dest) + } + LineTo { point, absolute } => { + dest.write_char(if absolute { 'L' } else { 'l' })?; + dest.write_char(' ')?; + point.to_css(dest) + } + CurveTo { control1, control2, point, absolute } => { + dest.write_char(if absolute { 'C' } else { 'c' })?; + dest.write_char(' ')?; + control1.to_css(dest)?; + dest.write_char(' ')?; + control2.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + QuadBezierCurveTo { control1, point, absolute } => { + dest.write_char(if absolute { 'Q' } else { 'q' })?; + dest.write_char(' ')?; + control1.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => { + dest.write_char(if absolute { 'A' } else { 'a' })?; + dest.write_char(' ')?; + rx.to_css(dest)?; + dest.write_char(' ')?; + ry.to_css(dest)?; + dest.write_char(' ')?; + angle.to_css(dest)?; + dest.write_char(' ')?; + (large_arc_flag as i32).to_css(dest)?; + dest.write_char(' ')?; + (sweep_flag as i32).to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + HorizontalLineTo { x, absolute } => { + dest.write_char(if absolute { 'H' } else { 'h' })?; + dest.write_char(' ')?; + x.to_css(dest) + }, + VerticalLineTo { y, absolute } => { + dest.write_char(if absolute { 'V' } else { 'v' })?; + dest.write_char(' ')?; + y.to_css(dest) + }, + SmoothCurveTo { control2, point, absolute } => { + dest.write_char(if absolute { 'S' } else { 's' })?; + dest.write_char(' ')?; + control2.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + SmoothQuadBezierCurveTo { point, absolute } => { + dest.write_char(if absolute { 'T' } else { 't' })?; + dest.write_char(' ')?; + point.to_css(dest) + }, + } + } +} + +/// The path coord type. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)] +#[repr(C)] +pub struct CoordPair(CSSFloat, CSSFloat); + +impl CoordPair { + /// Create a CoordPair. + #[inline] + pub fn new(x: CSSFloat, y: CSSFloat) -> Self { + CoordPair(x, y) + } +} + +/// Parse a pair of numbers into CoordPair. +fn parse_coord(iter: &mut Peekable) -> Result { + let x = parse_number(iter)?; + skip_comma_wsp(iter); + let y = parse_number(iter)?; + Ok(CoordPair::new(x, y)) +} + +/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed +/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating +/// point number. In other words, the logic here is similar with that of +/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the +/// input is a Peekable and we only accept an integer of a floating point number. +/// +/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF +fn parse_number(iter: &mut Peekable) -> Result { + // 1. Check optional sign. + let sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') { + if iter.next().unwrap() == '-' { -1. } else { 1. } + } else { + 1. + }; + + // 2. Check integer part. + let mut integral_part: f64 = 0.; + let got_dot = if !iter.peek().map_or(false, |&n: &char| n == '.') { + // If the first digit in integer part is neither a dot nor a digit, this is not a number. + if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) { + return Err(()); + } + + while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { + integral_part = + integral_part * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64; + } + + iter.peek().map_or(false, |&n: &char| n == '.') + } else { + true + }; + + // 3. Check fractional part. + let mut fractional_part: f64 = 0.; + if got_dot { + // Consume '.'. + iter.next(); + // If the first digit in fractional part is not a digit, this is not a number. + if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) { + return Err(()); + } + + let mut factor = 0.1; + while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { + fractional_part += iter.next().unwrap().to_digit(10).unwrap() as f64 * factor; + factor *= 0.1; + } + } + + let mut value = sign * (integral_part + fractional_part); + + // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to + // treat the numbers after 'E' or 'e' are in the exponential part. + if iter.peek().map_or(false, |&exp: &char| exp == 'E' || exp == 'e') { + // Consume 'E' or 'e'. + iter.next(); + let exp_sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') { + if iter.next().unwrap() == '-' { -1. } else { 1. } + } else { + 1. + }; + + let mut exp: f64 = 0.; + while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) { + exp = exp * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64; + } + + value *= f64::powf(10., exp * exp_sign); + } + + if value.is_finite() { + Ok(value.min(::std::f32::MAX as f64).max(::std::f32::MIN as f64) as CSSFloat) + } else { + Err(()) + } +} + +/// Skip all svg whitespaces, and return true if |iter| hasn't finished. +#[inline] +fn skip_wsp(iter: &mut Peekable) -> bool { + // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}. + // However, SVG 2 has one extra whitespace: \u{C}. + // Therefore, we follow the newest spec for the definition of whitespace, + // i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}, by is_ascii_whitespace(). + while iter.peek().map_or(false, |c: &char| c.is_ascii_whitespace()) { + iter.next(); + } + iter.peek().is_some() +} + +/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished. +#[inline] +fn skip_comma_wsp(iter: &mut Peekable) -> bool { + if !skip_wsp(iter) { + return false; + } + + if *iter.peek().unwrap() != ',' { + return true; + } + iter.next(); + + skip_wsp(iter) +} From d935ea329c8e3bb07f8e17f28190f4dd8ca3f386 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 13 Jul 2018 16:04:37 -0700 Subject: [PATCH 09/16] Bug 1429298 - Part 3: Use macro for path parser. r=emilio There are a lot of duplicates, so we use macro to refine them. Differential Revision: https://phabricator.services.mozilla.com/D2966 --- .../style/values/specified/motion.rs | 241 ++++++------------ 1 file changed, 78 insertions(+), 163 deletions(-) diff --git a/servo/components/style/values/specified/motion.rs b/servo/components/style/values/specified/motion.rs index 8b370369b852..ffaaf7cff17b 100644 --- a/servo/components/style/values/specified/motion.rs +++ b/servo/components/style/values/specified/motion.rs @@ -70,6 +70,34 @@ struct PathParser<'a> { path: Vec, } +macro_rules! parse_arguments { + ( + $parser:ident, + $abs:ident, + $enum:ident, + [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ] + ) => { + { + loop { + let $para = $func(&mut $parser.chars)?; + $( + skip_comma_wsp(&mut $parser.chars); + let $other_para = $other_func(&mut $parser.chars)?; + )* + $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs }); + + // End of string or the next character is a possible new command. + if !skip_wsp(&mut $parser.chars) || + $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) { + break; + } + skip_comma_wsp(&mut $parser.chars); + } + Ok(()) + } + } +} + impl<'a> PathParser<'a> { /// Parse a sub-path. fn parse_subpath(&mut self) -> Result<(), ()> { @@ -87,46 +115,30 @@ impl<'a> PathParser<'a> { match self.chars.next() { Some(command) => { let abs = command.is_uppercase(); - match command { - 'Z' | 'z' => { - // Note: A "closepath" coulbe be followed immediately by "moveto" or - // any other command, so we don't break this loop. - self.path.push(PathCommand::ClosePath); - }, - 'L' | 'l' => { - skip_wsp(&mut self.chars); - self.parse_lineto(abs)?; - }, - 'H' | 'h' => { - skip_wsp(&mut self.chars); - self.parse_h_lineto(abs)?; - }, - 'V' | 'v' => { - skip_wsp(&mut self.chars); - self.parse_v_lineto(abs)?; - }, - 'C' | 'c' => { - skip_wsp(&mut self.chars); - self.parse_curveto(abs)?; - }, - 'S' | 's' => { - skip_wsp(&mut self.chars); - self.parse_smooth_curveto(abs)?; - }, - 'Q' | 'q' => { - skip_wsp(&mut self.chars); - self.parse_quadratic_bezier_curveto(abs)?; - }, - 'T' | 't' => { - skip_wsp(&mut self.chars); - self.parse_smooth_quadratic_bezier_curveto(abs)?; - }, - 'A' | 'a' => { - skip_wsp(&mut self.chars); - self.parse_elliprical_arc(abs)?; - }, - _ => return Err(()), + 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. } @@ -158,128 +170,51 @@ impl<'a> PathParser<'a> { 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<(), ()> { - loop { - let point = parse_coord(&mut self.chars)?; - self.path.push(PathCommand::LineTo { 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + parse_arguments!(self, absolute, LineTo, [ point => parse_coord ]) } /// Parse horizontal "lineto" command. fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> { - loop { - let x = parse_number(&mut self.chars)?; - self.path.push(PathCommand::HorizontalLineTo { x, 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ]) } /// Parse vertical "lineto" command. fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> { - loop { - let y = parse_number(&mut self.chars)?; - self.path.push(PathCommand::VerticalLineTo { y, 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ]) } /// Parse cubic Bézier curve command. fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> { - loop { - let control1 = parse_coord(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let control2 = parse_coord(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let point = parse_coord(&mut self.chars)?; - - self.path.push(PathCommand::CurveTo { control1, control2, 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + 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<(), ()> { - loop { - let control2 = parse_coord(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let point = parse_coord(&mut self.chars)?; - - self.path.push(PathCommand::SmoothCurveTo { control2, 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + 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<(), ()> { - loop { - let control1 = parse_coord(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let point = parse_coord(&mut self.chars)?; - - self.path.push(PathCommand::QuadBezierCurveTo { control1, 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + 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<(), ()> { - loop { - let point = parse_coord(&mut self.chars)?; - - self.path.push(PathCommand::SmoothQuadBezierCurveTo { 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ]) } /// Parse elliptical arc curve command. @@ -293,34 +228,14 @@ impl<'a> PathParser<'a> { iter.next(); Ok(value) }; - - loop { - let rx = parse_number(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let ry = parse_number(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let angle = parse_number(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let large_arc_flag = parse_flag(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let sweep_flag = parse_flag(&mut self.chars)?; - skip_comma_wsp(&mut self.chars); - let point = parse_coord(&mut self.chars)?; - - self.path.push( - PathCommand::EllipticalArc { - rx, ry, angle, large_arc_flag, sweep_flag, 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()) { - break; - } - skip_comma_wsp(&mut self.chars); - } - Ok(()) + 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 + ]) } } From b2e2f913eba1b6ad100fba6c264b512a6e8961bf Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Tue, 21 Aug 2018 14:21:09 -0700 Subject: [PATCH 10/16] Bug 1429298 - Part 4: Rename builder as aBuilder in SVGPathData.cpp. r=TYLin Follow the rule of naming for the function parameters. Differential Revision: https://phabricator.services.mozilla.com/D3922 --- dom/svg/SVGPathData.cpp | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/dom/svg/SVGPathData.cpp b/dom/svg/SVGPathData.cpp index 85a14bbbb8f6..dbb5c6983eb6 100644 --- a/dom/svg/SVGPathData.cpp +++ b/dom/svg/SVGPathData.cpp @@ -262,18 +262,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 && \ + SVGPathSegUtils::IsValidType(prevSegType) && \ + (!IsMoveto(prevSegType) || segType == PATHSEG_CLOSEPATH)) { \ + ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, aStrokeWidth);\ + } \ } while(0) already_AddRefed -SVGPathData::BuildPath(PathBuilder* builder, +SVGPathData::BuildPath(PathBuilder* aBuilder, uint8_t aStrokeLineCap, Float aStrokeWidth) const { @@ -309,20 +309,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 +330,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 +338,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 +348,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 +358,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 +370,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 +382,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 +397,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 +413,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 +421,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 +429,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 +437,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 +447,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 +457,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 +469,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 +481,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 +503,7 @@ SVGPathData::BuildPath(PathBuilder* builder, MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; - return builder->Finish(); + return aBuilder->Finish(); } already_AddRefed From cfadc280920fd93f0c5818fc4dca50c79378e9bc Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Tue, 21 Aug 2018 13:59:15 -0700 Subject: [PATCH 11/16] Bug 1429298 - Part 5: Implement BuildPath for offset-path. r=jwatt Implement one variant of BuildPath to accept nsTArray, which is used by (and clip-path in the future). Differential Revision: https://phabricator.services.mozilla.com/D2967 --- dom/svg/SVGPathData.cpp | 238 +++++++++++++++++++++++++++++++++++++++- dom/svg/SVGPathData.h | 10 ++ 2 files changed, 245 insertions(+), 3 deletions(-) diff --git a/dom/svg/SVGPathData.cpp b/dom/svg/SVGPathData.cpp index dbb5c6983eb6..c56182400494 100644 --- a/dom/svg/SVGPathData.cpp +++ b/dom/svg/SVGPathData.cpp @@ -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) { @@ -266,8 +295,8 @@ ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB, do { \ if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \ subpathContainsNonMoveTo && \ - SVGPathSegUtils::IsValidType(prevSegType) && \ - (!IsMoveto(prevSegType) || segType == PATHSEG_CLOSEPATH)) { \ + IsValidType(prevSegType) && \ + (!IsMoveto(prevSegType) || IsClosePath(segType))) { \ ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, aStrokeWidth);\ } \ } while(0) @@ -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 +SVGPathData::BuildPath(const nsTArray& 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) { diff --git a/dom/svg/SVGPathData.h b/dom/svg/SVGPathData.h index 60dffc253333..25a2cdb88498 100644 --- a/dom/svg/SVGPathData.h +++ b/dom/svg/SVGPathData.h @@ -169,6 +169,16 @@ public: already_AddRefed 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 + BuildPath(const nsTArray& aPath, + PathBuilder* aBuilder, + uint8_t aCapStyle, + Float aStrokeWidth); const_iterator begin() const { return mData.Elements(); } const_iterator end() const { return mData.Elements() + mData.Length(); } From cc2e8fb32970b6a4057479160ef21ed7e2d15742 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Tue, 7 Aug 2018 18:07:01 -0700 Subject: [PATCH 12/16] Bug 1429298 - Part 6: Apply motion path transform matrix. r=nical We implement the layout part of offset-path. Now we don't have offset-distance, so use the default value, 0%, for it. Note: rename mCombinedTransform as mIndividualTransform, which only stores the combined individual transforms. We apply the individual transforms, motion path transform, and specified transform in ReadTransforms. (We have to follow the order, so we don't combine the specified transform in FinishStyle.) Differential Revision: https://phabricator.services.mozilla.com/D2968 --- dom/svg/moz.build | 1 + layout/base/nsLayoutUtils.cpp | 92 +++++++++++++++++++++++++ layout/base/nsLayoutUtils.h | 11 +++ layout/painting/ActiveLayerTracker.cpp | 24 ++++--- layout/painting/nsDisplayList.cpp | 25 ++++--- layout/painting/nsDisplayList.h | 13 ++++ layout/style/nsStyleStruct.cpp | 34 ++++----- layout/style/nsStyleStruct.h | 23 +++---- layout/style/nsStyleTransformMatrix.cpp | 60 +++++++++++++--- layout/style/nsStyleTransformMatrix.h | 14 ++++ 10 files changed, 239 insertions(+), 58 deletions(-) diff --git a/dom/svg/moz.build b/dom/svg/moz.build index 6f79fc07d6e3..74162ff1131b 100644 --- a/dom/svg/moz.build +++ b/dom/svg/moz.build @@ -81,6 +81,7 @@ EXPORTS.mozilla.dom += [ 'SVGMatrix.h', 'SVGMetadataElement.h', 'SVGMPathElement.h', + 'SVGPathData.h', 'SVGPathElement.h', 'SVGPatternElement.h', 'SVGPolygonElement.h', diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 7aae26eca05a..ee9841af61aa 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -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 +nsLayoutUtils::ResolveMotionPath(const nsIFrame* aFrame) +{ + MOZ_ASSERT(aFrame); + + const nsStyleDisplay* display = aFrame->StyleDisplay(); + if (!display->mMotion || !display->mMotion->HasPath()) { + return Nothing(); + } + + const UniquePtr& 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 = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + RefPtr 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 }); +} diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 3f7715911d3c..bfd437dbc298 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -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 + ResolveMotionPath(const nsIFrame* aFrame); + private: static uint32_t sFontSizeInflationEmPerLine; static uint32_t sFontSizeInflationMinTwips; diff --git a/layout/painting/ActiveLayerTracker.cpp b/layout/painting/ActiveLayerTracker.cpp index ae2b2d4cc141..4d23ac2b6a26 100644 --- a/layout/painting/ActiveLayerTracker.cpp +++ b/layout/painting/ActiveLayerTracker.cpp @@ -249,22 +249,30 @@ static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity) { const nsStyleDisplay* display = aFrame->StyleDisplay(); - RefPtr 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. diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index 2988ca57c0f4..f68ad6705ce6 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -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() / diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h index 7012d7353399..1faa5e806627 100644 --- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -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&& aTransformList, const Point3D& aToTransformOrigin) @@ -6585,7 +6591,14 @@ public: , mToTransformOrigin(aToTransformOrigin) {} + bool HasTransform() const + { + return mIndividualTransformList || mTransformList || mMotion.isSome(); + } + const nsIFrame* mFrame; + const RefPtr mIndividualTransformList; + const mozilla::Maybe mMotion; const RefPtr mTransformList; const Point3D mToTransformOrigin; }; diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index dd437c759afb..f8f93d7892fb 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -3636,10 +3636,10 @@ nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource) , mSpecifiedRotate(aSource.mSpecifiedRotate) , mSpecifiedTranslate(aSource.mSpecifiedTranslate) , mSpecifiedScale(aSource.mSpecifiedScale) + , mIndividualTransform(aSource.mIndividualTransform) , mMotion(aSource.mMotion ? MakeUnique(*aSource.mMotion) : nullptr) - , mCombinedTransform(aSource.mCombinedTransform) , mTransformOrigin{ aSource.mTransformOrigin[0], aSource.mTransformOrigin[1], aSource.mTransformOrigin[2] } @@ -3704,9 +3704,8 @@ nsStyleDisplay::~nsStyleDisplay() mSpecifiedTranslate); ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedScale", mSpecifiedScale); - ReleaseSharedListOnMainThread("nsStyleDisplay::mCombinedTransform", - mCombinedTransform); - + ReleaseSharedListOnMainThread("nsStyleDisplay::mIndividualTransform", + mIndividualTransform); MOZ_COUNT_DTOR(nsStyleDisplay); } @@ -3734,7 +3733,7 @@ nsStyleDisplay::FinishStyle( } } - GenerateCombinedTransform(); + GenerateCombinedIndividualTransform(); } static inline nsChangeHint @@ -4031,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 shareLists; + AutoTArray shareLists; if (mSpecifiedTranslate) { shareLists.AppendElement(mSpecifiedTranslate.get()); } @@ -4051,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 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 valueLists; for (auto list: shareLists) { if (list) { valueLists.AppendElement(list->mHead->Clone()); @@ -4083,8 +4078,9 @@ nsStyleDisplay::GenerateCombinedTransform() valueLists[i]->mNext = valueLists[i + 1]; } - mCombinedTransform = new nsCSSValueSharedList(valueLists[0]); + mIndividualTransform = new nsCSSValueSharedList(valueLists[0]); } + // -------------------- // nsStyleVisibility // diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 1f69997897db..d1c7b8142cce 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -2181,14 +2181,11 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay RefPtr mSpecifiedRotate; RefPtr mSpecifiedTranslate; RefPtr mSpecifiedScale; + // Used to store the final combination of mSpecifiedRotate, + // mSpecifiedTranslate, and mSpecifiedScale. + RefPtr mIndividualTransform; mozilla::UniquePtr mMotion; - // 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 mCombinedTransform; - nsStyleCoord mTransformOrigin[3]; // percent, coord, calc, 3rd param is coord, calc only nsStyleCoord mChildPerspective; // none, coord nsStyleCoord mPerspectiveOrigin[2]; // percent, coord, calc @@ -2528,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 GetCombinedTransform() const { - if (mCombinedTransform) { - return do_AddRef(mCombinedTransform); - } - - // backward compatible to gecko-backed style system. - return mSpecifiedTransform ? do_AddRef(mSpecifiedTransform) : nullptr; + already_AddRefed 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 diff --git a/layout/style/nsStyleTransformMatrix.cpp b/layout/style/nsStyleTransformMatrix.cpp index fe67064f40ad..8b6ed4b18d9e 100644 --- a/layout/style/nsStyleTransformMatrix.cpp +++ b/layout/style/nsStyleTransformMatrix.cpp @@ -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& 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); diff --git a/layout/style/nsStyleTransformMatrix.h b/layout/style/nsStyleTransformMatrix.h index cbe0cf2bcc86..3cc88f8b0393 100644 --- a/layout/style/nsStyleTransformMatrix.h +++ b/layout/style/nsStyleTransformMatrix.h @@ -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& 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. From dbe6b4f2dba9b34147615b62282839c19313999a Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 10 Aug 2018 13:38:13 -0700 Subject: [PATCH 13/16] Bug 1429298 - Part 7: Tests. r=emilio In wpt, now we support "offset-path: none | path()", so parsing none or path function should be correct. Animations which animate "from none" or "to none" will pass because we could serialize "none", even if we don't support animations on offset-path. Differential Revision: https://phabricator.services.mozilla.com/D2969 --HG-- rename : testing/web-platform/tests/css/motion/offset-path-string.html => testing/web-platform/tests/css/motion/offset-path-string-001.html --- .../shared/css/generated/properties-db.js | 14 ++++++++ modules/libpref/init/all.js | 4 +++ testing/web-platform/meta/MANIFEST.json | 32 ++++++++++++----- .../web-platform/meta/css/motion/__dir__.ini | 1 + .../offset-path-interpolation-001.html.ini | 36 ------------------- .../offset-path-interpolation-002.html.ini | 18 ---------- .../offset-path-interpolation-003.html.ini | 24 ------------- .../offset-path-interpolation-004.html.ini | 24 ------------- .../offset-path-interpolation-005.html.ini | 18 ---------- .../motion/offset-path-string-001.html.ini | 3 ++ .../css/motion/offset-path-string.html.ini | 2 -- .../offset-path-parsing-valid.html.ini | 18 ---------- ...tring.html => offset-path-string-001.html} | 0 .../css/motion/offset-path-string-002.html | 24 +++++++++++++ .../parsing/offset-path-parsing-invalid.html | 2 +- .../parsing/offset-path-parsing-valid.html | 4 +-- 16 files changed, 73 insertions(+), 151 deletions(-) create mode 100644 testing/web-platform/meta/css/motion/__dir__.ini create mode 100644 testing/web-platform/meta/css/motion/offset-path-string-001.html.ini delete mode 100644 testing/web-platform/meta/css/motion/offset-path-string.html.ini rename testing/web-platform/tests/css/motion/{offset-path-string.html => offset-path-string-001.html} (100%) create mode 100644 testing/web-platform/tests/css/motion/offset-path-string-002.html diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index 3df6c676a7ac..50499051695b 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -7416,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": [ diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 45b05c12e840..223a1b10a34f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3074,7 +3074,11 @@ pref("layout.css.ruby.intercharacter.enabled", false); 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 diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index 57b0babad628..69a3c759927f 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -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": [ diff --git a/testing/web-platform/meta/css/motion/__dir__.ini b/testing/web-platform/meta/css/motion/__dir__.ini new file mode 100644 index 000000000000..6e20392137ad --- /dev/null +++ b/testing/web-platform/meta/css/motion/__dir__.ini @@ -0,0 +1 @@ +prefs: [layout.css.motion-path.enabled:true] diff --git a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-001.html.ini b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-001.html.ini index 74fd5abb8621..54b010f20be5 100644 --- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-001.html.ini +++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-001.html.ini @@ -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 diff --git a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-002.html.ini b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-002.html.ini index 6cc072ce9819..0da0fa965620 100644 --- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-002.html.ini +++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-002.html.ini @@ -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 diff --git a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-003.html.ini b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-003.html.ini index b4677ff62c5e..8f754cdc1d55 100644 --- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-003.html.ini +++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-003.html.ini @@ -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 diff --git a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-004.html.ini b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-004.html.ini index 9540cff46808..6d85a6a7e42c 100644 --- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-004.html.ini +++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-004.html.ini @@ -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 diff --git a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-005.html.ini b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-005.html.ini index 189aa04a4d8a..42ed84a3bb0e 100644 --- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-005.html.ini +++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-005.html.ini @@ -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 diff --git a/testing/web-platform/meta/css/motion/offset-path-string-001.html.ini b/testing/web-platform/meta/css/motion/offset-path-string-001.html.ini new file mode 100644 index 000000000000..5931951b8baf --- /dev/null +++ b/testing/web-platform/meta/css/motion/offset-path-string-001.html.ini @@ -0,0 +1,3 @@ +[offset-path-string-001.html] + expected: FAIL + bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1429299 diff --git a/testing/web-platform/meta/css/motion/offset-path-string.html.ini b/testing/web-platform/meta/css/motion/offset-path-string.html.ini deleted file mode 100644 index e91dc7c30033..000000000000 --- a/testing/web-platform/meta/css/motion/offset-path-string.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[offset-path-string.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/motion/parsing/offset-path-parsing-valid.html.ini b/testing/web-platform/meta/css/motion/parsing/offset-path-parsing-valid.html.ini index ba450f8f0005..770115aa1d6f 100644 --- a/testing/web-platform/meta/css/motion/parsing/offset-path-parsing-valid.html.ini +++ b/testing/web-platform/meta/css/motion/parsing/offset-path-parsing-valid.html.ini @@ -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 diff --git a/testing/web-platform/tests/css/motion/offset-path-string.html b/testing/web-platform/tests/css/motion/offset-path-string-001.html similarity index 100% rename from testing/web-platform/tests/css/motion/offset-path-string.html rename to testing/web-platform/tests/css/motion/offset-path-string-001.html diff --git a/testing/web-platform/tests/css/motion/offset-path-string-002.html b/testing/web-platform/tests/css/motion/offset-path-string-002.html new file mode 100644 index 000000000000..0d2fcbbb661c --- /dev/null +++ b/testing/web-platform/tests/css/motion/offset-path-string-002.html @@ -0,0 +1,24 @@ + + + + CSS Motion Path: path(string) paths + + + + + + +
+ + diff --git a/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-invalid.html b/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-invalid.html index c0a32486922b..7fbd06a508a3 100644 --- a/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-invalid.html +++ b/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-invalid.html @@ -14,7 +14,7 @@