Index: third_party/WebKit/Source/core/html/HTMLMarqueeElement.cpp |
diff --git a/third_party/WebKit/Source/core/html/HTMLMarqueeElement.cpp b/third_party/WebKit/Source/core/html/HTMLMarqueeElement.cpp |
index 1da81e76d6b184d3c023742cc8a3cde9eea043b3..b4e536ab825a0c4f68b2c765424866666311b3c9 100644 |
--- a/third_party/WebKit/Source/core/html/HTMLMarqueeElement.cpp |
+++ b/third_party/WebKit/Source/core/html/HTMLMarqueeElement.cpp |
@@ -22,32 +22,69 @@ |
#include "core/html/HTMLMarqueeElement.h" |
-#include "bindings/core/v8/PrivateScriptRunner.h" |
+#include "bindings/core/v8/ExceptionStatePlaceholder.h" |
#include "bindings/core/v8/V8HTMLMarqueeElement.h" |
+#include "core/CSSPropertyNames.h" |
#include "core/HTMLNames.h" |
+#include "core/animation/DocumentTimeline.h" |
+#include "core/animation/KeyframeEffect.h" |
+#include "core/animation/KeyframeEffectModel.h" |
+#include "core/animation/KeyframeEffectOptions.h" |
+#include "core/animation/StringKeyframe.h" |
+#include "core/animation/TimingInput.h" |
+#include "core/css/CSSStyleDeclaration.h" |
+#include "core/css/StylePropertySet.h" |
#include "core/dom/Document.h" |
+#include "core/dom/shadow/ShadowRoot.h" |
+#include "core/frame/LocalDOMWindow.h" |
#include "core/frame/UseCounter.h" |
-#include "platform/ScriptForbiddenScope.h" |
+#include "core/html/HTMLContentElement.h" |
+#include "core/html/HTMLDimension.h" |
+#include "core/html/HTMLDivElement.h" |
+#include "core/html/HTMLStyleElement.h" |
+#include <cstdlib> |
namespace blink { |
+namespace { |
+ |
+String convertHTMLLengthToCSSLength(const String& htmlLength) { |
+ HTMLDimension dimension; |
+ parseDimensionValue(htmlLength, dimension); |
+ if (dimension.isRelative()) |
+ return String(); |
+ CSSPrimitiveValue* cssValue = CSSPrimitiveValue::create( |
+ dimension.value(), dimension.isPercentage() |
+ ? CSSPrimitiveValue::UnitType::Percentage |
+ : CSSPrimitiveValue::UnitType::Pixels); |
+ return cssValue->customCSSText(); |
+} |
+ |
+} // namespace |
+ |
inline HTMLMarqueeElement::HTMLMarqueeElement(Document& document) |
: HTMLElement(HTMLNames::marqueeTag, document) { |
- if (document.contextDocument()) { |
- ScriptForbiddenScope::AllowUserAgentScript script; |
- v8::Local<v8::Value> classObject = |
- PrivateScriptRunner::installClassIfNeeded(&document, |
- "HTMLMarqueeElement"); |
- RELEASE_ASSERT(!classObject.IsEmpty()); |
- } |
UseCounter::count(document, UseCounter::HTMLMarqueeElement); |
+ ShadowRoot* shadow = |
+ createShadowRootInternal(ShadowRootType::V0, ASSERT_NO_EXCEPTION); |
+ Element* style = HTMLStyleElement::create(document, false); |
+ style->setTextContent( |
+ ":host { display: inline-block; width: -webkit-fill-available; overflow: " |
+ "hidden; text-align: initial; white-space: nowrap; }" |
+ ":host([direction=\"up\"]), :host([direction=\"down\"]) { overflow: " |
+ "initial; overflow-y: hidden; white-space: initial; }" |
+ ":host > div { will-change: transform; }"); |
+ shadow->appendChild(style); |
+ |
+ Element* mover = HTMLDivElement::create(document); |
+ shadow->appendChild(mover); |
+ |
+ mover->appendChild(HTMLContentElement::create(document)); |
+ m_mover = mover; |
} |
HTMLMarqueeElement* HTMLMarqueeElement::create(Document& document) { |
- HTMLMarqueeElement* marqueeElement = new HTMLMarqueeElement(document); |
- V8HTMLMarqueeElement::PrivateScript::createdCallbackMethod(document.frame(), |
- marqueeElement); |
- return marqueeElement; |
+ return new HTMLMarqueeElement(document); |
} |
void HTMLMarqueeElement::attributeChanged(const QualifiedName& name, |
tkent
2016/12/06 22:43:11
Overriding attributeChanged looks weird. Doesn't
esprehn
2016/12/07 00:10:00
Yes see the discussion for follow up patches
|
@@ -55,31 +92,401 @@ void HTMLMarqueeElement::attributeChanged(const QualifiedName& name, |
const AtomicString& newValue, |
AttributeModificationReason reason) { |
HTMLElement::attributeChanged(name, oldValue, newValue, reason); |
- V8HTMLMarqueeElement::PrivateScript::attributeChangedCallbackMethod( |
- document().frame(), this, name.toString(), oldValue, newValue); |
+ attributeChangedCallback(name, newValue); |
} |
Node::InsertionNotificationRequest HTMLMarqueeElement::insertedInto( |
ContainerNode* insertionPoint) { |
HTMLElement::insertedInto(insertionPoint); |
+ |
if (isConnected()) { |
- V8HTMLMarqueeElement::PrivateScript::attachedCallbackMethod( |
- document().frame(), this); |
+ static const QualifiedName* presentationalAttributes[] = { |
+ &HTMLNames::bgcolorAttr, &HTMLNames::heightAttr, &HTMLNames::hspaceAttr, |
+ &HTMLNames::vspaceAttr, &HTMLNames::widthAttr}; |
+ for (const auto* attr : presentationalAttributes) { |
+ const AtomicString& value = getAttribute(*attr); |
tkent
2016/12/06 22:43:11
getAttribute -> fastGetAttribute
esprehn
2016/12/07 00:10:00
We were trying to avoid using private hooks that a
|
+ if (value.isNull()) |
+ continue; |
+ attributeChangedCallback(*attr, value); |
+ } |
+ |
+ start(); |
} |
+ |
return InsertionDone; |
} |
void HTMLMarqueeElement::removedFrom(ContainerNode* insertionPoint) { |
HTMLElement::removedFrom(insertionPoint); |
if (insertionPoint->isConnected()) { |
- V8HTMLMarqueeElement::PrivateScript::detachedCallbackMethod( |
- insertionPoint->document().frame(), this); |
+ stop(); |
} |
} |
bool HTMLMarqueeElement::isHorizontal() const { |
- AtomicString direction = getAttribute(HTMLNames::directionAttr); |
- return direction != "down" && direction != "up"; |
+ Direction direction = this->direction(); |
+ return direction != Up && direction != Down; |
+} |
+ |
+int HTMLMarqueeElement::scrollAmount() const { |
+ bool ok; |
+ int scrollAmount = getAttribute(HTMLNames::scrollamountAttr).toInt(&ok); |
tkent
2016/12/06 22:43:11
Please do not use AtomicString::toInt(). It isn't
esprehn
2016/12/07 00:10:00
Please fix the toInt() to not have an overflow. :)
adithyas
2016/12/08 15:12:11
I'm not really sure about the overflow bug, I coul
|
+ if (!ok || scrollAmount < 0) |
+ return kDefaultScrollAmount; |
+ return scrollAmount; |
+} |
+ |
+void HTMLMarqueeElement::setScrollAmount(int value, |
+ ExceptionState& exceptionState) { |
+ if (value < 0) { |
+ exceptionState.throwDOMException( |
+ IndexSizeError, |
+ "The provided value (" + String::number(value) + ") is negative."); |
+ return; |
+ } |
+ setIntegralAttribute(HTMLNames::scrollamountAttr, value); |
+} |
+ |
+int HTMLMarqueeElement::scrollDelay() const { |
+ bool ok; |
+ int scrollDelay = getAttribute(HTMLNames::scrolldelayAttr).toInt(&ok); |
+ if (!ok || scrollDelay < 0) |
+ return kDefaultScrollDelayMS; |
+ return scrollDelay; |
+} |
+ |
+void HTMLMarqueeElement::setScrollDelay(int value, |
+ ExceptionState& exceptionState) { |
+ if (value < 0) { |
+ exceptionState.throwDOMException( |
+ IndexSizeError, |
+ "The provided value (" + String::number(value) + ") is negative."); |
+ return; |
+ } |
+ setIntegralAttribute(HTMLNames::scrolldelayAttr, value); |
+} |
+ |
+int HTMLMarqueeElement::loop() const { |
+ bool ok; |
+ int loop = getAttribute(HTMLNames::loopAttr).toInt(&ok); |
+ if (!ok || loop <= 0) |
+ return kDefaultLoopLimit; |
+ return loop; |
+} |
+ |
+void HTMLMarqueeElement::setLoop(int value, ExceptionState& exceptionState) { |
+ if (value <= 0 && value != -1) { |
+ exceptionState.throwDOMException( |
+ IndexSizeError, "The provided value (" + String::number(value) + |
+ ") is neither positive nor -1."); |
+ return; |
+ } |
+ setIntegralAttribute(HTMLNames::loopAttr, value); |
+} |
+ |
+void HTMLMarqueeElement::start() { |
+ if (m_continueCallbackRequestId) |
+ return; |
+ |
+ RequestAnimationFrameCallback* callback = |
+ new RequestAnimationFrameCallback(this); |
+ m_continueCallbackRequestId = document().requestAnimationFrame(callback); |
+} |
+ |
+void HTMLMarqueeElement::stop() { |
+ if (m_continueCallbackRequestId) { |
+ document().cancelAnimationFrame(m_continueCallbackRequestId); |
+ m_continueCallbackRequestId = 0; |
+ return; |
+ } |
+ |
+ if (m_player) |
+ m_player->pause(); |
+} |
+ |
+void HTMLMarqueeElement::attributeChangedCallback(const QualifiedName& attr, |
+ const String& newValue) { |
+ if (attr == HTMLNames::bgcolorAttr) { |
+ style()->setProperty("background-color", newValue, String(), |
+ ASSERT_NO_EXCEPTION); |
+ } else if (attr == HTMLNames::heightAttr) { |
+ style()->setProperty("height", convertHTMLLengthToCSSLength(newValue), |
+ String(), ASSERT_NO_EXCEPTION); |
+ } else if (attr == HTMLNames::hspaceAttr) { |
+ style()->setProperty("margin-left", convertHTMLLengthToCSSLength(newValue), |
+ String(), ASSERT_NO_EXCEPTION); |
+ style()->setProperty("margin-right", convertHTMLLengthToCSSLength(newValue), |
+ String(), ASSERT_NO_EXCEPTION); |
+ } else if (attr == HTMLNames::vspaceAttr) { |
+ style()->setProperty("margin-top", convertHTMLLengthToCSSLength(newValue), |
+ String(), ASSERT_NO_EXCEPTION); |
+ style()->setProperty("margin-bottom", |
+ convertHTMLLengthToCSSLength(newValue), String(), |
+ ASSERT_NO_EXCEPTION); |
+ } else if (attr == HTMLNames::widthAttr) { |
+ style()->setProperty("width", convertHTMLLengthToCSSLength(newValue), |
+ String(), ASSERT_NO_EXCEPTION); |
+ } |
+} |
+ |
+void HTMLMarqueeElement::RequestAnimationFrameCallback::handleEvent(double) { |
+ m_marquee->m_continueCallbackRequestId = 0; |
+ m_marquee->continueAnimation(); |
+} |
+ |
+void HTMLMarqueeElement::AnimationFinished::handleEvent( |
+ ExecutionContext* context, |
+ Event* event) { |
+ ++m_marquee->m_loopCount; |
+ m_marquee->start(); |
+} |
+ |
+StringKeyframeEffectModel* HTMLMarqueeElement::createEffectModel( |
+ AnimationParameters& parameters) { |
tkent
2016/12/06 22:43:11
The argument type should be |const AnimationParame
|
+ StyleSheetContents* styleSheetContents = |
+ m_mover->document().elementSheet().contents(); |
+ MutableStylePropertySet::SetResult setResult; |
+ |
+ RefPtr<StringKeyframe> keyframe1 = StringKeyframe::create(); |
+ setResult = keyframe1->setCSSPropertyValue( |
+ CSSPropertyTransform, parameters.transformBegin, styleSheetContents); |
+ DCHECK(setResult.didParse); |
+ |
+ RefPtr<StringKeyframe> keyframe2 = StringKeyframe::create(); |
+ setResult = keyframe2->setCSSPropertyValue( |
+ CSSPropertyTransform, parameters.transformEnd, styleSheetContents); |
+ DCHECK(setResult.didParse); |
+ |
+ return StringKeyframeEffectModel::create( |
+ {std::move(keyframe1), std::move(keyframe2)}, |
+ LinearTimingFunction::shared()); |
+} |
+ |
+void HTMLMarqueeElement::continueAnimation() { |
+ if (!shouldContinue()) |
+ return; |
+ |
+ if (m_player && m_player->playState() == "paused") { |
+ m_player->play(); |
+ return; |
+ } |
+ |
+ AnimationParameters parameters = getAnimationParameters(); |
+ int scrollDelay = this->scrollDelay(); |
+ int scrollAmount = this->scrollAmount(); |
+ |
+ if (scrollDelay < kMinimumScrollDelayMS && !trueSpeed()) |
+ scrollDelay = kDefaultScrollDelayMS; |
+ double duration = 0; |
+ if (scrollAmount) |
+ duration = parameters.distance * scrollDelay / scrollAmount; |
+ if (!duration) |
+ return; |
+ |
+ StringKeyframeEffectModel* effectModel = createEffectModel(parameters); |
+ Timing timing; |
+ timing.fillMode = Timing::FillMode::FORWARDS; |
+ TimingInput::setIterationDuration( |
+ timing, UnrestrictedDoubleOrString::fromUnrestrictedDouble(duration), |
+ ASSERT_NO_EXCEPTION); |
+ |
+ KeyframeEffect* keyframeEffect = |
+ KeyframeEffect::create(m_mover, effectModel, timing); |
+ Animation* player = m_mover->document().timeline().play(keyframeEffect); |
+ player->setId(emptyString()); |
+ player->setOnfinish(new AnimationFinished(this)); |
+ |
+ m_player = player; |
+} |
+ |
+bool HTMLMarqueeElement::shouldContinue() { |
+ int loopCount = loop(); |
+ |
+ // By default, slide loops only once. |
+ if (loopCount <= 0 && behavior() == Slide) |
+ loopCount = 1; |
+ |
+ if (loopCount <= 0) |
+ return true; |
+ return m_loopCount < loopCount; |
+} |
+ |
+HTMLMarqueeElement::Behavior HTMLMarqueeElement::behavior() const { |
+ const AtomicString& behavior = getAttribute(HTMLNames::behaviorAttr); |
+ if (behavior == "alternate") |
tkent
2016/12/06 22:43:11
Should it be case-sensitive match?
adithyas
2016/12/08 15:12:11
Nope, this is a bug, thanks for catching it.
|
+ return Alternate; |
+ if (behavior == "slide") |
+ return Slide; |
+ return Scroll; |
+} |
+ |
+HTMLMarqueeElement::Direction HTMLMarqueeElement::direction() const { |
+ const AtomicString& direction = getAttribute(HTMLNames::directionAttr); |
+ if (direction == "down") |
+ return Down; |
+ if (direction == "up") |
+ return Up; |
+ if (direction == "right") |
+ return Right; |
+ return Left; |
+} |
+ |
+bool HTMLMarqueeElement::trueSpeed() const { |
+ return hasAttribute(HTMLNames::truespeedAttr); |
tkent
2016/12/06 22:43:11
hasAttribute -> fastHasAttribute
|
+} |
+ |
+HTMLMarqueeElement::Metrics HTMLMarqueeElement::getMetrics() { |
+ Metrics metrics; |
+ CSSStyleDeclaration* marqueeStyle = |
+ document().domWindow()->getComputedStyle(this, String()); |
+ // For marquees that are declared inline, getComputedStyle returns "auto" for |
+ // width and height. Setting all the metrics to zero disables animation for |
+ // inline marquees. |
+ if (marqueeStyle->getPropertyValue("width") == "auto" && |
+ marqueeStyle->getPropertyValue("height") == "auto") { |
+ metrics.contentHeight = 0; |
+ metrics.contentWidth = 0; |
+ metrics.marqueeWidth = 0; |
+ metrics.marqueeHeight = 0; |
+ return metrics; |
+ } |
+ |
+ if (isHorizontal()) { |
+ m_mover->style()->setProperty("width", "-webkit-max-content", "important", |
+ ASSERT_NO_EXCEPTION); |
+ } else { |
+ m_mover->style()->setProperty("height", "-webkit-max-content", "important", |
+ ASSERT_NO_EXCEPTION); |
+ } |
+ CSSStyleDeclaration* moverStyle = |
+ document().domWindow()->getComputedStyle(m_mover, String()); |
+ |
+ metrics.contentWidth = moverStyle->getPropertyValue("width").toDouble(); |
+ metrics.contentHeight = moverStyle->getPropertyValue("height").toDouble(); |
+ metrics.marqueeWidth = marqueeStyle->getPropertyValue("width").toDouble(); |
+ metrics.marqueeHeight = marqueeStyle->getPropertyValue("height").toDouble(); |
+ |
+ if (isHorizontal()) { |
+ m_mover->style()->setProperty("width", "", "important", |
+ ASSERT_NO_EXCEPTION); |
+ } else { |
+ m_mover->style()->setProperty("height", "", "important", |
+ ASSERT_NO_EXCEPTION); |
+ } |
+ |
+ return metrics; |
+} |
+ |
+HTMLMarqueeElement::AnimationParameters |
+HTMLMarqueeElement::getAnimationParameters() { |
+ AnimationParameters parameters; |
+ Metrics metrics = getMetrics(); |
+ |
+ double totalWidth = metrics.marqueeWidth + metrics.contentWidth; |
+ double totalHeight = metrics.marqueeHeight + metrics.contentHeight; |
+ |
+ double innerWidth = metrics.marqueeWidth - metrics.contentWidth; |
+ double innerHeight = metrics.marqueeHeight - metrics.contentHeight; |
+ |
+ switch (behavior()) { |
+ case Alternate: |
+ switch (direction()) { |
+ case Right: |
+ parameters.transformBegin = |
+ createTransform(innerWidth >= 0 ? 0 : innerWidth); |
+ parameters.transformEnd = |
+ createTransform(innerWidth >= 0 ? innerWidth : 0); |
+ parameters.distance = std::abs(innerWidth); |
+ break; |
+ case Up: |
+ parameters.transformBegin = |
+ createTransform(innerHeight >= 0 ? innerHeight : 0); |
+ parameters.transformEnd = |
+ createTransform(innerHeight >= 0 ? 0 : innerHeight); |
+ parameters.distance = std::abs(innerHeight); |
+ break; |
+ case Down: |
+ parameters.transformBegin = |
+ createTransform(innerHeight >= 0 ? 0 : innerHeight); |
+ parameters.transformEnd = |
+ createTransform(innerHeight >= 0 ? innerHeight : 0); |
+ parameters.distance = std::abs(innerHeight); |
+ break; |
+ case Left: |
+ default: |
+ parameters.transformBegin = |
+ createTransform(innerWidth >= 0 ? innerWidth : 0); |
+ parameters.transformEnd = |
+ createTransform(innerWidth >= 0 ? 0 : innerWidth); |
+ parameters.distance = std::abs(innerWidth); |
+ } |
+ |
+ if (m_loopCount % 2) |
+ std::swap(parameters.transformBegin, parameters.transformEnd); |
+ break; |
+ case Slide: |
+ switch (direction()) { |
+ case Right: |
+ parameters.transformBegin = createTransform(-metrics.contentWidth); |
+ parameters.transformEnd = createTransform(innerWidth); |
+ parameters.distance = metrics.marqueeWidth; |
+ break; |
+ case Up: |
+ parameters.transformBegin = createTransform(metrics.marqueeHeight); |
+ parameters.transformEnd = "translateY(0)"; |
+ parameters.distance = metrics.marqueeHeight; |
+ break; |
+ case Down: |
+ parameters.transformBegin = createTransform(-metrics.contentHeight); |
+ parameters.transformEnd = createTransform(innerHeight); |
+ parameters.distance = metrics.marqueeHeight; |
+ break; |
+ case Left: |
+ default: |
+ parameters.transformBegin = createTransform(metrics.marqueeWidth); |
+ parameters.transformEnd = "translateX(0)"; |
+ parameters.distance = metrics.marqueeWidth; |
+ } |
+ break; |
+ case Scroll: |
+ default: |
+ switch (direction()) { |
+ case Right: |
+ parameters.transformBegin = createTransform(-metrics.contentWidth); |
+ parameters.transformEnd = createTransform(metrics.marqueeWidth); |
+ parameters.distance = totalWidth; |
+ break; |
+ case Up: |
+ parameters.transformBegin = createTransform(metrics.marqueeHeight); |
+ parameters.transformEnd = createTransform(-metrics.contentHeight); |
+ parameters.distance = totalHeight; |
+ break; |
+ case Down: |
+ parameters.transformBegin = createTransform(-metrics.contentHeight); |
+ parameters.transformEnd = createTransform(metrics.marqueeHeight); |
+ parameters.distance = totalHeight; |
+ break; |
+ case Left: |
+ default: |
+ parameters.transformBegin = createTransform(metrics.marqueeWidth); |
+ parameters.transformEnd = createTransform(-metrics.contentWidth); |
+ parameters.distance = totalWidth; |
+ } |
+ break; |
+ } |
+ |
+ return parameters; |
+} |
+ |
+AtomicString HTMLMarqueeElement::createTransform(double value) const { |
+ char axis = isHorizontal() ? 'X' : 'Y'; |
+ return AtomicString(String::format("translate%c(%fpx)", axis, value)); |
tkent
2016/12/06 22:43:11
Does this work in French locale, which use ',' for
adithyas
2016/12/08 15:12:11
I don't think it does, thanks for pointing this ou
|
+} |
+ |
+DEFINE_TRACE(HTMLMarqueeElement) { |
+ visitor->trace(m_mover); |
+ visitor->trace(m_player); |
+ HTMLElement::trace(visitor); |
} |
} // namespace blink |