diff --git a/dom/html/HTMLMarqueeElement.cpp b/dom/html/HTMLMarqueeElement.cpp
index ff428547d4478..8721086064cf9 100644
--- a/dom/html/HTMLMarqueeElement.cpp
+++ b/dom/html/HTMLMarqueeElement.cpp
@@ -107,19 +107,6 @@ bool HTMLMarqueeElement::ParseAttribute(int32_t aNamespaceID,
aMaybeScriptedPrincipal, aResult);
}
-void HTMLMarqueeElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
- const nsAttrValue* aValue,
- const nsAttrValue* aOldValue,
- nsIPrincipal* aMaybeScriptedPrincipal,
- bool aNotify) {
- if (IsInComposedDoc() && aNameSpaceID == kNameSpaceID_None &&
- aName == nsGkAtoms::direction) {
- NotifyUAWidgetSetupOrChange();
- }
- return nsGenericHTMLElement::AfterSetAttr(
- aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
-}
-
void HTMLMarqueeElement::MapAttributesIntoRule(
MappedDeclarationsBuilder& aBuilder) {
nsGenericHTMLElement::MapImageMarginAttributeInto(aBuilder);
diff --git a/dom/html/HTMLMarqueeElement.h b/dom/html/HTMLMarqueeElement.h
index 6c491c548b56a..2abebdcd9d23c 100644
--- a/dom/html/HTMLMarqueeElement.h
+++ b/dom/html/HTMLMarqueeElement.h
@@ -102,10 +102,6 @@ class HTMLMarqueeElement final : public nsGenericHTMLElement {
const nsAString& aValue,
nsIPrincipal* aMaybeScriptedPrincipal,
nsAttrValue& aResult) override;
- void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
- const nsAttrValue* aValue, const nsAttrValue* aOldValue,
- nsIPrincipal* aMaybeScriptedPrincipal,
- bool aNotify) override;
NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp
index 6ff392376bed4..2b0c53efe1b06 100644
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1265,17 +1265,7 @@ nsMargin nsHTMLScrollFrame::ComputeStableScrollbarGutter(
// Legacy, this sucks!
static bool IsMarqueeScrollbox(const nsIFrame& aScrollFrame) {
- if (!aScrollFrame.GetContent()) {
- return false;
- }
- if (MOZ_LIKELY(!aScrollFrame.GetContent()->HasBeenInUAWidget())) {
- return false;
- }
- MOZ_ASSERT(aScrollFrame.GetParent() &&
- aScrollFrame.GetParent()->GetContent());
- return aScrollFrame.GetParent() &&
- HTMLMarqueeElement::FromNodeOrNull(
- aScrollFrame.GetParent()->GetContent());
+ return HTMLMarqueeElement::FromNodeOrNull(aScrollFrame.GetContent());
}
/* virtual */
diff --git a/layout/painting/crashtests/crashtests.list b/layout/painting/crashtests/crashtests.list
index af2df7830cfd8..954e2de6e22d7 100644
--- a/layout/painting/crashtests/crashtests.list
+++ b/layout/painting/crashtests/crashtests.list
@@ -18,7 +18,7 @@ load 1469472.html
load 1477831-1.html
load 1504033.html
load 1514544-1.html
-asserts(0-1) asserts-if(Android,0-2) load 1547420-1.html
+asserts(0-5) load 1547420-1.html
load 1549909.html
load 1551389-1.html
asserts(0-2) load 1555819-1.html
diff --git a/layout/reftests/bugs/404553-1-ref.html b/layout/reftests/bugs/404553-1-ref.html
index cfa0dc8147587..ac35a9e4b5f22 100644
--- a/layout/reftests/bugs/404553-1-ref.html
+++ b/layout/reftests/bugs/404553-1-ref.html
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/layout/reftests/bugs/404553-1.html b/layout/reftests/bugs/404553-1.html
index 692c63c67c1f2..ab63f1396ef68 100644
--- a/layout/reftests/bugs/404553-1.html
+++ b/layout/reftests/bugs/404553-1.html
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/layout/reftests/marquee/336736-1a-ref.html b/layout/reftests/marquee/336736-1a-ref.html
new file mode 100644
index 0000000000000..690992b8a5849
--- /dev/null
+++ b/layout/reftests/marquee/336736-1a-ref.html
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/layout/reftests/marquee/336736-1-ref.html b/layout/reftests/marquee/336736-1b-ref.html
similarity index 100%
rename from layout/reftests/marquee/336736-1-ref.html
rename to layout/reftests/marquee/336736-1b-ref.html
diff --git a/layout/reftests/marquee/reftest.list b/layout/reftests/marquee/reftest.list
index ac6772f6c9985..c7d2fafd30907 100644
--- a/layout/reftests/marquee/reftest.list
+++ b/layout/reftests/marquee/reftest.list
@@ -1,6 +1,6 @@
== 166591-dynamic-1.html 166591-dynamic-1-ref.html
-fuzzy-if(Android,0-8,0-50) == 336736-1a.html 336736-1-ref.html
-fuzzy-if(Android,0-8,0-50) == 336736-1b.html 336736-1-ref.html
+fuzzy-if(Android,0-8,0-50) == 336736-1a.html 336736-1a-ref.html
+fuzzy-if(Android,0-8,0-50) == 336736-1b.html 336736-1b-ref.html
== 406073-1.html 406073-1-ref.html
== 407016-2.html 407016-2-ref.html
fuzzy-if(Android,0-8,0-220) == 413027-4.html 413027-4-ref.html
diff --git a/layout/style/res/html.css b/layout/style/res/html.css
index bb1afcbfd17cd..18dd1c4855e14 100644
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -816,15 +816,21 @@ dialog::backdrop {
background: rgba(0, 0, 0, 0.1);
}
+/* https://html.spec.whatwg.org/#the-marquee-element-2 */
marquee {
- inline-size: -moz-available;
display: inline-block;
+ text-align: initial;
+ overflow: hidden !important;
+
+ /* See https://github.com/whatwg/html/issues/10249 */
+ inline-size: -moz-available;
vertical-align: text-bottom;
- text-align: start;
+ white-space: nowrap;
}
marquee:is([direction="up"], [direction="down"]) {
block-size: 200px;
+ white-space: unset;
}
/* Ruby */
diff --git a/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-loop.html.ini b/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-loop.html.ini
deleted file mode 100644
index 0a4bff7fe8657..0000000000000
--- a/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-loop.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[marquee-loop.html]
- expected:
- if (os == "android") and fission: [OK, TIMEOUT]
diff --git a/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrollamount.html.ini b/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrollamount.html.ini
deleted file mode 100644
index 003dfbe1f71ea..0000000000000
--- a/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrollamount.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[marquee-scrollamount.html]
- expected:
- if (os == "android") and fission: [OK, TIMEOUT]
diff --git a/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrolldelay.html.ini b/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrolldelay.html.ini
deleted file mode 100644
index b7ec124418fe1..0000000000000
--- a/testing/web-platform/meta/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrolldelay.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[marquee-scrolldelay.html]
- expected:
- if (os == "android") and fission: [OK, TIMEOUT]
diff --git a/testing/web-platform/tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-overflow.html b/testing/web-platform/tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-overflow.html
new file mode 100644
index 0000000000000..44b149952cdc9
--- /dev/null
+++ b/testing/web-platform/tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-overflow.html
@@ -0,0 +1,18 @@
+
+
+Marquee forces overflow: hidden
+
+
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/html/rendering/non-replaced-elements/form-controls/resets.html b/testing/web-platform/tests/html/rendering/non-replaced-elements/form-controls/resets.html
index db21188ee37bc..1a17aeac2d993 100644
--- a/testing/web-platform/tests/html/rendering/non-replaced-elements/form-controls/resets.html
+++ b/testing/web-platform/tests/html/rendering/non-replaced-elements/form-controls/resets.html
@@ -50,6 +50,7 @@
}
input[type=hidden i] { display: none !important; }
marquee {
+ overflow: hidden;
text-align: initial;
}
table { display: table; box-sizing: border-box; }
diff --git a/toolkit/content/widgets/marquee.css b/toolkit/content/widgets/marquee.css
index b898cd0dce607..6cdb52ca0201e 100644
--- a/toolkit/content/widgets/marquee.css
+++ b/toolkit/content/widgets/marquee.css
@@ -2,25 +2,19 @@
* 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/. */
-.outerDiv {
- overflow: hidden;
- width: -moz-available;
+slot {
+ display: block;
+ will-change: translate;
}
-.horizontal > .innerDiv {
- width: max-content;
- /* We want to create overflow of twice our available space. */
- padding: 0 100%;
-}
-
-/* disable scrolling in contenteditable */
-:host(:read-write) .innerDiv {
- padding: 0 !important;
+/* Disable the animation on contenteditable */
+:host(:read-write) > slot {
+ translate: none !important;
}
/* When printing or when the user doesn't want movement, we disable scrolling */
@media print, (prefers-reduced-motion) {
- .innerDiv {
- padding: 0 !important;
+ slot {
+ translate: none !important;
}
}
diff --git a/toolkit/content/widgets/marquee.js b/toolkit/content/widgets/marquee.js
index 6afdb25def6de..a694ffdca1d0b 100644
--- a/toolkit/content/widgets/marquee.js
+++ b/toolkit/content/widgets/marquee.js
@@ -4,87 +4,25 @@
"use strict";
-/*
- * This is the class of entry. It will construct the actual implementation
- * according to the value of the "direction" property.
- */
this.MarqueeWidget = class {
- constructor(shadowRoot) {
- this.shadowRoot = shadowRoot;
- this.element = shadowRoot.host;
- }
-
- /*
- * Callback called by UAWidgets right after constructor.
- */
- onsetup() {
- this.switchImpl();
- }
-
- /*
- * Callback called by UAWidgetsChild wheen the direction property
- * changes.
- */
- onchange() {
- this.switchImpl();
- }
-
- switchImpl() {
- let newImpl;
- switch (this.element.direction) {
- case "up":
- case "down":
- newImpl = MarqueeVerticalImplWidget;
- break;
- case "left":
- case "right":
- newImpl = MarqueeHorizontalImplWidget;
- break;
- }
-
- // Skip if we are asked to load the same implementation.
- // This can happen if the property is set again w/o value change.
- if (this.impl && this.impl.constructor == newImpl) {
- return;
- }
- this.teardown();
- if (newImpl) {
- this.impl = new newImpl(this.shadowRoot);
- this.impl.onsetup();
- }
- }
-
- teardown() {
- if (!this.impl) {
- return;
- }
- this.impl.teardown();
- this.shadowRoot.firstChild.remove();
- delete this.impl;
- }
-};
-
-this.MarqueeBaseImplWidget = class {
constructor(shadowRoot) {
this.shadowRoot = shadowRoot;
this.element = shadowRoot.host;
this.document = this.element.ownerDocument;
this.window = this.document.defaultView;
+ // This needed for behavior=alternate, in order to know in which of the two
+ // directions we're going.
+ this.dirsign = 1;
+ this._currentLoop = this.element.loop;
+ this.animation = null;
+ this._restartScheduled = null;
}
onsetup() {
- this.generateContent();
-
- // Set up state.
- this._currentDirection = this.element.direction || "left";
- this._currentLoop = this.element.loop;
- this.dirsign = 1;
- this.startAt = 0;
- this.stopAt = 0;
- this.newPosition = 0;
- this.runId = 0;
- this.originalHeight = 0;
- this.invalidateCache = true;
+ // White-space isn't allowed because a marquee could be
+ // inside 'white-space: pre'
+ this.shadowRoot.innerHTML = ``;
this._mutationObserver = new this.window.MutationObserver(aMutations =>
this._mutationActor(aMutations)
@@ -92,7 +30,7 @@ this.MarqueeBaseImplWidget = class {
this._mutationObserver.observe(this.element, {
attributes: true,
attributeOldValue: true,
- attributeFilter: ["loop", "", "behavior", "direction", "width", "height"],
+ attributeFilter: ["loop", "direction", "behavior"],
});
// init needs to be run after the page has loaded in order to calculate
@@ -108,12 +46,13 @@ this.MarqueeBaseImplWidget = class {
}
teardown() {
+ this.doStop();
this._mutationObserver.disconnect();
- this.window.clearTimeout(this.runId);
this.window.removeEventListener("load", this);
this.shadowRoot.removeEventListener("marquee-start", this);
this.shadowRoot.removeEventListener("marquee-stop", this);
+ this.shadowRoot.replaceChildren();
}
handleEvent(aEvent) {
@@ -131,15 +70,26 @@ this.MarqueeBaseImplWidget = class {
case "marquee-stop":
this.doStop();
break;
+ case "finish":
+ this._animationFinished();
+ break;
}
}
- get outerDiv() {
- return this.shadowRoot.firstChild;
- }
-
- get innerDiv() {
- return this.shadowRoot.getElementById("innerDiv");
+ _animationFinished() {
+ let behavior = this.element.behavior;
+ let shouldLoop =
+ this._currentLoop > 1 || (this._currentLoop == -1 && behavior != "slide");
+ if (shouldLoop) {
+ if (this._currentLoop > 0) {
+ this._currentLoop--;
+ }
+ if (behavior == "alternate") {
+ this.dirsign = -this.dirsign;
+ }
+ this.doStop();
+ this.doStart();
+ }
}
get scrollDelayWithTruespeed() {
@@ -149,255 +99,249 @@ this.MarqueeBaseImplWidget = class {
return this.element.scrollDelay;
}
- doStart() {
- if (this.runId == 0) {
- var lambda = () => this._doMove(false);
- this.runId = this.window.setTimeout(
- lambda,
- this.scrollDelayWithTruespeed - this._deltaStartStop
- );
- this._deltaStartStop = 0;
- }
+ get slot() {
+ return this.shadowRoot.lastChild;
}
- doStop() {
- if (this.runId != 0) {
- this._deltaStartStop = Date.now() - this._lastMoveDate;
- this.window.clearTimeout(this.runId);
- }
-
- this.runId = 0;
+ /**
+ * Computes CSS-derived values needed to compute the transform of the
+ * contents.
+ *
+ * In particular, it measures the auto width and height of the contents,
+ * and the effective width and height of the marquee itself, along with its
+ * css directionality (which affects the effective direction).
+ */
+ getMetrics() {
+ let slot = this.slot;
+ slot.style.width = "max-content";
+ let slotCS = this.window.getComputedStyle(slot);
+ let marqueeCS = this.window.getComputedStyle(this.element);
+ let contentWidth = parseFloat(slotCS.width) || 0;
+ let contentHeight = parseFloat(slotCS.height) || 0;
+ let marqueeWidth = parseFloat(marqueeCS.width) || 0;
+ let marqueeHeight = parseFloat(marqueeCS.height) || 0;
+ slot.style.width = "";
+ return {
+ contentWidth,
+ contentHeight,
+ marqueeWidth,
+ marqueeHeight,
+ cssDirection: marqueeCS.direction,
+ };
}
- _doMove(aResetPosition) {
- this._lastMoveDate = Date.now();
-
- // invalidateCache is true at first load and whenever an attribute
- // is changed
- if (this.invalidateCache) {
- this.invalidateCache = false; // we only want this to run once every scroll direction change
-
- var corrvalue = 0;
-
- switch (this._currentDirection) {
- case "up":
- case "down": {
- let height = this.window.getComputedStyle(this.element).height;
- this.outerDiv.style.height = height;
- if (this.originalHeight > this.outerDiv.offsetHeight) {
- corrvalue = this.originalHeight - this.outerDiv.offsetHeight;
+ /**
+ * Gets the layout metrics from getMetrics(), and returns an object
+ * describing the start, end, and axis of the animation for the given marquee
+ * behavior and direction.
+ */
+ getTransformParameters({
+ contentWidth,
+ contentHeight,
+ marqueeWidth,
+ marqueeHeight,
+ cssDirection,
+ }) {
+ const innerWidth = marqueeWidth - contentWidth;
+ const innerHeight = marqueeHeight - contentHeight;
+ const dir = this.element.direction;
+
+ let start = 0;
+ let end = 0;
+ const axis = dir == "up" || dir == "down" ? "y" : "x";
+ switch (this.element.behavior) {
+ case "alternate":
+ switch (dir) {
+ case "up":
+ case "down": {
+ if (innerHeight >= 0) {
+ start = innerHeight;
+ end = 0;
+ } else {
+ start = 0;
+ end = innerHeight;
+ }
+ if (dir == "down") {
+ [start, end] = [end, start];
+ }
+ if (this.dirsign == -1) {
+ [start, end] = [end, start];
+ }
+ break;
}
- this.innerDiv.style.padding = height + " 0";
- let isUp = this._currentDirection == "up";
- if (isUp) {
- this.dirsign = 1;
- this.startAt =
- this.element.behavior == "alternate"
- ? this.originalHeight - corrvalue
- : 0;
- this.stopAt =
- this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? parseInt(height) + corrvalue
- : this.originalHeight + parseInt(height);
- } else {
- this.dirsign = -1;
- this.startAt =
- this.element.behavior == "alternate"
- ? parseInt(height) + corrvalue
- : this.originalHeight + parseInt(height);
- this.stopAt =
- this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? this.originalHeight - corrvalue
- : 0;
+ case "right":
+ case "left":
+ default: {
+ if (innerWidth >= 0) {
+ start = innerWidth;
+ end = 0;
+ } else {
+ start = 0;
+ end = innerWidth;
+ }
+ if (dir == "right") {
+ [start, end] = [end, start];
+ }
+ if (cssDirection == "rtl") {
+ [start, end] = [end, start];
+ }
+ if (this.dirsign == -1) {
+ [start, end] = [end, start];
+ }
+ break;
}
- break;
}
- case "left":
- case "right":
- default: {
- let isRight = this._currentDirection == "right";
- // NOTE: It's important to use getComputedStyle() to not account for the padding.
- let innerWidth = parseInt(
- this.window.getComputedStyle(this.innerDiv).width
- );
- if (innerWidth > this.outerDiv.offsetWidth) {
- corrvalue = innerWidth - this.outerDiv.offsetWidth;
+ break;
+ case "slide":
+ switch (dir) {
+ case "up": {
+ start = marqueeHeight;
+ end = 0;
+ break;
}
- let rtl =
- this.window.getComputedStyle(this.element).direction == "rtl";
- if (isRight != rtl) {
- this.dirsign = -1;
- this.stopAt =
- this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? innerWidth - corrvalue
- : 0;
- this.startAt =
- this.outerDiv.offsetWidth +
- (this.element.behavior == "alternate"
- ? corrvalue
- : innerWidth + this.stopAt);
- } else {
- this.dirsign = 1;
- this.startAt =
- this.element.behavior == "alternate" ? innerWidth - corrvalue : 0;
- this.stopAt =
- this.outerDiv.offsetWidth +
- (this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? corrvalue
- : innerWidth + this.startAt);
+ case "down": {
+ start = -contentHeight;
+ end = innerHeight;
+ break;
}
- if (rtl) {
- this.startAt = -this.startAt;
- this.stopAt = -this.stopAt;
- this.dirsign = -this.dirsign;
+ case "right":
+ default: {
+ let isRight = dir == "right";
+ if (cssDirection == "rtl") {
+ isRight = !isRight;
+ }
+ if (isRight) {
+ start = -contentWidth;
+ end = innerWidth;
+ } else {
+ start = marqueeWidth;
+ end = 0;
+ }
+ break;
}
- break;
}
- }
-
- if (aResetPosition) {
- this.newPosition = this.startAt;
- }
- } // end if
-
- this.newPosition =
- this.newPosition + this.dirsign * this.element.scrollAmount;
-
- if (
- (this.dirsign == 1 && this.newPosition > this.stopAt) ||
- (this.dirsign == -1 && this.newPosition < this.stopAt)
- ) {
- switch (this.element.behavior) {
- case "alternate":
- // lets start afresh
- this.invalidateCache = true;
-
- // swap direction
- const swap = { left: "right", down: "up", up: "down", right: "left" };
- this._currentDirection = swap[this._currentDirection] || "left";
- this.newPosition = this.stopAt;
-
- if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.newPosition;
- } else {
- this.outerDiv.scrollLeft = this.newPosition;
+ break;
+ case "scroll":
+ default:
+ switch (dir) {
+ case "up":
+ case "down": {
+ start = marqueeHeight;
+ end = -contentHeight;
+ if (dir == "down") {
+ [start, end] = [end, start];
+ }
+ break;
}
-
- break;
-
- case "slide":
- if (this._currentLoop > 1) {
- this.newPosition = this.startAt;
+ case "right":
+ case "left":
+ default: {
+ start = marqueeWidth;
+ end = -contentWidth;
+ if (dir == "right") {
+ [start, end] = [end, start];
+ }
+ if (cssDirection == "rtl") {
+ [start, end] = [end, start];
+ }
+ break;
}
- break;
-
- default:
- this.newPosition = this.startAt;
+ }
+ break;
+ }
+ return { start, end, axis };
+ }
- if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.newPosition;
- } else {
- this.outerDiv.scrollLeft = this.newPosition;
- }
+ /**
+ * Measures the marquee contents, and starts the marquee animation if needed.
+ * The translate animation is applied to the element.
+ * Bouncing and looping is implemented in the finish event handler for the
+ * given animation (see _animationFinished()).
+ */
+ doStart() {
+ if (this.animation) {
+ return;
+ }
+ let scrollAmount = this.element.scrollAmount;
+ if (!scrollAmount) {
+ return;
+ }
+ let metrics = this.getMetrics();
+ let { axis, start, end } = this.getTransformParameters(metrics);
+ let duration =
+ (Math.abs(end - start) * this.scrollDelayWithTruespeed) / scrollAmount;
+ let startValue = start + "px";
+ let endValue = end + "px";
+ if (axis == "y") {
+ startValue = "0 " + startValue;
+ endValue = "0 " + endValue;
+ }
+ // NOTE(emilio): It seems tempting to use `iterations` here, but doing so
+ // wouldn't be great because this uses current layout values (via
+ // getMetrics()), so sizes wouldn't update. This way we update once per
+ // animation iteration.
+ //
+ // fill: forwards is needed so that behavior=slide doesn't jump back to the
+ // start after the animation finishes.
+ this.animation = this.slot.animate(
+ {
+ translate: [startValue, endValue],
+ },
+ {
+ duration,
+ easing: "linear",
+ fill: "forwards",
}
+ );
+ this.animation.addEventListener("finish", this, { once: true });
+ }
- if (this._currentLoop > 1) {
- this._currentLoop--;
- } else if (this._currentLoop == 1) {
- if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.stopAt;
- } else {
- this.outerDiv.scrollLeft = this.stopAt;
- }
- this.element.stop();
- return;
- }
- } else if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.newPosition;
- } else {
- this.outerDiv.scrollLeft = this.newPosition;
+ doStop() {
+ if (!this.animation) {
+ return;
}
-
- var myThis = this;
- var lambda = function myTimeOutFunction() {
- myThis._doMove(false);
- };
- this.runId = this.window.setTimeout(lambda, this.scrollDelayWithTruespeed);
+ if (this._restartScheduled) {
+ this.window.cancelAnimationFrame(this._restartScheduled);
+ this._restartScheduled = null;
+ }
+ this.animation.removeEventListener("finish", this);
+ this.animation.cancel();
+ this.animation = null;
}
init() {
this.element.stop();
-
- if (this._currentDirection == "up" || this._currentDirection == "down") {
- // store the original height before we add padding
- this.innerDiv.style.padding = 0;
- this.originalHeight = this.innerDiv.offsetHeight;
- }
-
- this._doMove(true);
+ this.doStart();
}
_mutationActor(aMutations) {
while (aMutations.length) {
- var mutation = aMutations.shift();
- var attrName = mutation.attributeName.toLowerCase();
- var oldValue = mutation.oldValue;
- var target = mutation.target;
- var newValue = target.getAttribute(attrName);
-
- if (oldValue != newValue) {
- this.invalidateCache = true;
- switch (attrName) {
- case "loop":
- this._currentLoop = target.loop;
- break;
- case "direction":
- this._currentDirection = target.direction;
- break;
- }
+ let mutation = aMutations.shift();
+ let attrName = mutation.attributeName.toLowerCase();
+ let oldValue = mutation.oldValue;
+ let newValue = this.element.getAttribute(attrName);
+ if (oldValue == newValue) {
+ continue;
+ }
+ if (attrName == "loop") {
+ this._currentLoop = this.element.loop;
+ }
+ if (attrName == "direction" || attrName == "behavior") {
+ this._scheduleRestartIfNeeded();
}
}
}
-};
-this.MarqueeHorizontalImplWidget = class extends MarqueeBaseImplWidget {
- generateContent() {
- // White-space isn't allowed because a marquee could be
- // inside 'white-space: pre'
- this.shadowRoot.innerHTML = ``;
- }
-};
-
-this.MarqueeVerticalImplWidget = class extends MarqueeBaseImplWidget {
- generateContent() {
- // White-space isn't allowed because a marquee could be
- // inside 'white-space: pre'
- this.shadowRoot.innerHTML = ``;
+ // Schedule a restart with the new parameters if we're running.
+ _scheduleRestartIfNeeded() {
+ if (!this.animation || this._restartScheduled != null) {
+ return;
+ }
+ this._restartScheduled = this.window.requestAnimationFrame(() => {
+ if (this.animation) {
+ this.doStop();
+ this.doStart();
+ }
+ });
}
};