Chromium Code Reviews| 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 |