Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(55)

Side by Side Diff: third_party/WebKit/Source/core/html/HTMLMarqueeElement.cpp

Issue 2549443003: Move <marquee> implementation to HTMLMarqueeElement.cpp (Closed)
Patch Set: Add asserts for didParse Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2003, 2007, 2010 Apple Inc. All rights reserved. 4 * Copyright (C) 2003, 2007, 2010 Apple Inc. All rights reserved.
5 * 5 *
6 * This library is free software; you can redistribute it and/or 6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public 7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either 8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version. 9 * version 2 of the License, or (at your option) any later version.
10 * 10 *
11 * This library is distributed in the hope that it will be useful, 11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details. 14 * Library General Public License for more details.
15 * 15 *
16 * You should have received a copy of the GNU Library General Public License 16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to 17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA. 19 * Boston, MA 02110-1301, USA.
20 * 20 *
21 */ 21 */
22 22
23 #include "core/html/HTMLMarqueeElement.h" 23 #include "core/html/HTMLMarqueeElement.h"
24 24
25 #include "bindings/core/v8/PrivateScriptRunner.h" 25 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
26 #include "bindings/core/v8/V8HTMLMarqueeElement.h" 26 #include "bindings/core/v8/V8HTMLMarqueeElement.h"
27 #include "core/CSSPropertyNames.h"
27 #include "core/HTMLNames.h" 28 #include "core/HTMLNames.h"
29 #include "core/animation/DocumentTimeline.h"
30 #include "core/animation/KeyframeEffect.h"
31 #include "core/animation/KeyframeEffectModel.h"
32 #include "core/animation/KeyframeEffectOptions.h"
33 #include "core/animation/StringKeyframe.h"
34 #include "core/animation/TimingInput.h"
35 #include "core/css/CSSStyleDeclaration.h"
36 #include "core/css/StylePropertySet.h"
28 #include "core/dom/Document.h" 37 #include "core/dom/Document.h"
38 #include "core/dom/shadow/ShadowRoot.h"
39 #include "core/frame/LocalDOMWindow.h"
29 #include "core/frame/UseCounter.h" 40 #include "core/frame/UseCounter.h"
30 #include "platform/ScriptForbiddenScope.h" 41 #include "core/html/HTMLContentElement.h"
42 #include "core/html/HTMLDimension.h"
43 #include "core/html/HTMLDivElement.h"
44 #include "core/html/HTMLStyleElement.h"
45 #include <cstdlib>
31 46
32 namespace blink { 47 namespace blink {
33 48
49 namespace {
50
51 String convertHTMLLengthToCSSLength(const String& htmlLength) {
52 HTMLDimension dimension;
53 parseDimensionValue(htmlLength, dimension);
54 if (dimension.isRelative())
55 return String();
56 CSSPrimitiveValue* cssValue = CSSPrimitiveValue::create(
57 dimension.value(), dimension.isPercentage()
58 ? CSSPrimitiveValue::UnitType::Percentage
59 : CSSPrimitiveValue::UnitType::Pixels);
60 return cssValue->customCSSText();
61 }
62
63 } // namespace
64
34 inline HTMLMarqueeElement::HTMLMarqueeElement(Document& document) 65 inline HTMLMarqueeElement::HTMLMarqueeElement(Document& document)
35 : HTMLElement(HTMLNames::marqueeTag, document) { 66 : HTMLElement(HTMLNames::marqueeTag, document) {
36 if (document.contextDocument()) {
37 ScriptForbiddenScope::AllowUserAgentScript script;
38 v8::Local<v8::Value> classObject =
39 PrivateScriptRunner::installClassIfNeeded(&document,
40 "HTMLMarqueeElement");
41 RELEASE_ASSERT(!classObject.IsEmpty());
42 }
43 UseCounter::count(document, UseCounter::HTMLMarqueeElement); 67 UseCounter::count(document, UseCounter::HTMLMarqueeElement);
68 ShadowRoot* shadow =
69 createShadowRootInternal(ShadowRootType::V0, ASSERT_NO_EXCEPTION);
70 Element* style = HTMLStyleElement::create(document, false);
71 style->setTextContent(
72 ":host { display: inline-block; width: -webkit-fill-available; overflow: "
73 "hidden; text-align: initial; white-space: nowrap; }"
74 ":host([direction=\"up\"]), :host([direction=\"down\"]) { overflow: "
75 "initial; overflow-y: hidden; white-space: initial; }"
76 ":host > div { will-change: transform; }");
77 shadow->appendChild(style);
78
79 Element* mover = HTMLDivElement::create(document);
80 shadow->appendChild(mover);
81
82 mover->appendChild(HTMLContentElement::create(document));
83 m_mover = mover;
44 } 84 }
45 85
46 HTMLMarqueeElement* HTMLMarqueeElement::create(Document& document) { 86 HTMLMarqueeElement* HTMLMarqueeElement::create(Document& document) {
47 HTMLMarqueeElement* marqueeElement = new HTMLMarqueeElement(document); 87 return new HTMLMarqueeElement(document);
48 V8HTMLMarqueeElement::PrivateScript::createdCallbackMethod(document.frame(),
49 marqueeElement);
50 return marqueeElement;
51 } 88 }
52 89
53 void HTMLMarqueeElement::attributeChanged(const QualifiedName& name, 90 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
54 const AtomicString& oldValue, 91 const AtomicString& oldValue,
55 const AtomicString& newValue, 92 const AtomicString& newValue,
56 AttributeModificationReason reason) { 93 AttributeModificationReason reason) {
57 HTMLElement::attributeChanged(name, oldValue, newValue, reason); 94 HTMLElement::attributeChanged(name, oldValue, newValue, reason);
58 V8HTMLMarqueeElement::PrivateScript::attributeChangedCallbackMethod( 95 attributeChangedCallback(name, newValue);
59 document().frame(), this, name.toString(), oldValue, newValue);
60 } 96 }
61 97
62 Node::InsertionNotificationRequest HTMLMarqueeElement::insertedInto( 98 Node::InsertionNotificationRequest HTMLMarqueeElement::insertedInto(
63 ContainerNode* insertionPoint) { 99 ContainerNode* insertionPoint) {
64 HTMLElement::insertedInto(insertionPoint); 100 HTMLElement::insertedInto(insertionPoint);
101
65 if (isConnected()) { 102 if (isConnected()) {
66 V8HTMLMarqueeElement::PrivateScript::attachedCallbackMethod( 103 static const QualifiedName* presentationalAttributes[] = {
67 document().frame(), this); 104 &HTMLNames::bgcolorAttr, &HTMLNames::heightAttr, &HTMLNames::hspaceAttr,
68 } 105 &HTMLNames::vspaceAttr, &HTMLNames::widthAttr};
106 for (const auto* attr : presentationalAttributes) {
107 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
108 if (value.isNull())
109 continue;
110 attributeChangedCallback(*attr, value);
111 }
112
113 start();
114 }
115
69 return InsertionDone; 116 return InsertionDone;
70 } 117 }
71 118
72 void HTMLMarqueeElement::removedFrom(ContainerNode* insertionPoint) { 119 void HTMLMarqueeElement::removedFrom(ContainerNode* insertionPoint) {
73 HTMLElement::removedFrom(insertionPoint); 120 HTMLElement::removedFrom(insertionPoint);
74 if (insertionPoint->isConnected()) { 121 if (insertionPoint->isConnected()) {
75 V8HTMLMarqueeElement::PrivateScript::detachedCallbackMethod( 122 stop();
76 insertionPoint->document().frame(), this);
77 } 123 }
78 } 124 }
79 125
80 bool HTMLMarqueeElement::isHorizontal() const { 126 bool HTMLMarqueeElement::isHorizontal() const {
81 AtomicString direction = getAttribute(HTMLNames::directionAttr); 127 Direction direction = this->direction();
82 return direction != "down" && direction != "up"; 128 return direction != Up && direction != Down;
129 }
130
131 int HTMLMarqueeElement::scrollAmount() const {
132 bool ok;
133 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
134 if (!ok || scrollAmount < 0)
135 return kDefaultScrollAmount;
136 return scrollAmount;
137 }
138
139 void HTMLMarqueeElement::setScrollAmount(int value,
140 ExceptionState& exceptionState) {
141 if (value < 0) {
142 exceptionState.throwDOMException(
143 IndexSizeError,
144 "The provided value (" + String::number(value) + ") is negative.");
145 return;
146 }
147 setIntegralAttribute(HTMLNames::scrollamountAttr, value);
148 }
149
150 int HTMLMarqueeElement::scrollDelay() const {
151 bool ok;
152 int scrollDelay = getAttribute(HTMLNames::scrolldelayAttr).toInt(&ok);
153 if (!ok || scrollDelay < 0)
154 return kDefaultScrollDelayMS;
155 return scrollDelay;
156 }
157
158 void HTMLMarqueeElement::setScrollDelay(int value,
159 ExceptionState& exceptionState) {
160 if (value < 0) {
161 exceptionState.throwDOMException(
162 IndexSizeError,
163 "The provided value (" + String::number(value) + ") is negative.");
164 return;
165 }
166 setIntegralAttribute(HTMLNames::scrolldelayAttr, value);
167 }
168
169 int HTMLMarqueeElement::loop() const {
170 bool ok;
171 int loop = getAttribute(HTMLNames::loopAttr).toInt(&ok);
172 if (!ok || loop <= 0)
173 return kDefaultLoopLimit;
174 return loop;
175 }
176
177 void HTMLMarqueeElement::setLoop(int value, ExceptionState& exceptionState) {
178 if (value <= 0 && value != -1) {
179 exceptionState.throwDOMException(
180 IndexSizeError, "The provided value (" + String::number(value) +
181 ") is neither positive nor -1.");
182 return;
183 }
184 setIntegralAttribute(HTMLNames::loopAttr, value);
185 }
186
187 void HTMLMarqueeElement::start() {
188 if (m_continueCallbackRequestId)
189 return;
190
191 RequestAnimationFrameCallback* callback =
192 new RequestAnimationFrameCallback(this);
193 m_continueCallbackRequestId = document().requestAnimationFrame(callback);
194 }
195
196 void HTMLMarqueeElement::stop() {
197 if (m_continueCallbackRequestId) {
198 document().cancelAnimationFrame(m_continueCallbackRequestId);
199 m_continueCallbackRequestId = 0;
200 return;
201 }
202
203 if (m_player)
204 m_player->pause();
205 }
206
207 void HTMLMarqueeElement::attributeChangedCallback(const QualifiedName& attr,
208 const String& newValue) {
209 if (attr == HTMLNames::bgcolorAttr) {
210 style()->setProperty("background-color", newValue, String(),
211 ASSERT_NO_EXCEPTION);
212 } else if (attr == HTMLNames::heightAttr) {
213 style()->setProperty("height", convertHTMLLengthToCSSLength(newValue),
214 String(), ASSERT_NO_EXCEPTION);
215 } else if (attr == HTMLNames::hspaceAttr) {
216 style()->setProperty("margin-left", convertHTMLLengthToCSSLength(newValue),
217 String(), ASSERT_NO_EXCEPTION);
218 style()->setProperty("margin-right", convertHTMLLengthToCSSLength(newValue),
219 String(), ASSERT_NO_EXCEPTION);
220 } else if (attr == HTMLNames::vspaceAttr) {
221 style()->setProperty("margin-top", convertHTMLLengthToCSSLength(newValue),
222 String(), ASSERT_NO_EXCEPTION);
223 style()->setProperty("margin-bottom",
224 convertHTMLLengthToCSSLength(newValue), String(),
225 ASSERT_NO_EXCEPTION);
226 } else if (attr == HTMLNames::widthAttr) {
227 style()->setProperty("width", convertHTMLLengthToCSSLength(newValue),
228 String(), ASSERT_NO_EXCEPTION);
229 }
230 }
231
232 void HTMLMarqueeElement::RequestAnimationFrameCallback::handleEvent(double) {
233 m_marquee->m_continueCallbackRequestId = 0;
234 m_marquee->continueAnimation();
235 }
236
237 void HTMLMarqueeElement::AnimationFinished::handleEvent(
238 ExecutionContext* context,
239 Event* event) {
240 ++m_marquee->m_loopCount;
241 m_marquee->start();
242 }
243
244 StringKeyframeEffectModel* HTMLMarqueeElement::createEffectModel(
245 AnimationParameters& parameters) {
tkent 2016/12/06 22:43:11 The argument type should be |const AnimationParame
246 StyleSheetContents* styleSheetContents =
247 m_mover->document().elementSheet().contents();
248 MutableStylePropertySet::SetResult setResult;
249
250 RefPtr<StringKeyframe> keyframe1 = StringKeyframe::create();
251 setResult = keyframe1->setCSSPropertyValue(
252 CSSPropertyTransform, parameters.transformBegin, styleSheetContents);
253 DCHECK(setResult.didParse);
254
255 RefPtr<StringKeyframe> keyframe2 = StringKeyframe::create();
256 setResult = keyframe2->setCSSPropertyValue(
257 CSSPropertyTransform, parameters.transformEnd, styleSheetContents);
258 DCHECK(setResult.didParse);
259
260 return StringKeyframeEffectModel::create(
261 {std::move(keyframe1), std::move(keyframe2)},
262 LinearTimingFunction::shared());
263 }
264
265 void HTMLMarqueeElement::continueAnimation() {
266 if (!shouldContinue())
267 return;
268
269 if (m_player && m_player->playState() == "paused") {
270 m_player->play();
271 return;
272 }
273
274 AnimationParameters parameters = getAnimationParameters();
275 int scrollDelay = this->scrollDelay();
276 int scrollAmount = this->scrollAmount();
277
278 if (scrollDelay < kMinimumScrollDelayMS && !trueSpeed())
279 scrollDelay = kDefaultScrollDelayMS;
280 double duration = 0;
281 if (scrollAmount)
282 duration = parameters.distance * scrollDelay / scrollAmount;
283 if (!duration)
284 return;
285
286 StringKeyframeEffectModel* effectModel = createEffectModel(parameters);
287 Timing timing;
288 timing.fillMode = Timing::FillMode::FORWARDS;
289 TimingInput::setIterationDuration(
290 timing, UnrestrictedDoubleOrString::fromUnrestrictedDouble(duration),
291 ASSERT_NO_EXCEPTION);
292
293 KeyframeEffect* keyframeEffect =
294 KeyframeEffect::create(m_mover, effectModel, timing);
295 Animation* player = m_mover->document().timeline().play(keyframeEffect);
296 player->setId(emptyString());
297 player->setOnfinish(new AnimationFinished(this));
298
299 m_player = player;
300 }
301
302 bool HTMLMarqueeElement::shouldContinue() {
303 int loopCount = loop();
304
305 // By default, slide loops only once.
306 if (loopCount <= 0 && behavior() == Slide)
307 loopCount = 1;
308
309 if (loopCount <= 0)
310 return true;
311 return m_loopCount < loopCount;
312 }
313
314 HTMLMarqueeElement::Behavior HTMLMarqueeElement::behavior() const {
315 const AtomicString& behavior = getAttribute(HTMLNames::behaviorAttr);
316 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.
317 return Alternate;
318 if (behavior == "slide")
319 return Slide;
320 return Scroll;
321 }
322
323 HTMLMarqueeElement::Direction HTMLMarqueeElement::direction() const {
324 const AtomicString& direction = getAttribute(HTMLNames::directionAttr);
325 if (direction == "down")
326 return Down;
327 if (direction == "up")
328 return Up;
329 if (direction == "right")
330 return Right;
331 return Left;
332 }
333
334 bool HTMLMarqueeElement::trueSpeed() const {
335 return hasAttribute(HTMLNames::truespeedAttr);
tkent 2016/12/06 22:43:11 hasAttribute -> fastHasAttribute
336 }
337
338 HTMLMarqueeElement::Metrics HTMLMarqueeElement::getMetrics() {
339 Metrics metrics;
340 CSSStyleDeclaration* marqueeStyle =
341 document().domWindow()->getComputedStyle(this, String());
342 // For marquees that are declared inline, getComputedStyle returns "auto" for
343 // width and height. Setting all the metrics to zero disables animation for
344 // inline marquees.
345 if (marqueeStyle->getPropertyValue("width") == "auto" &&
346 marqueeStyle->getPropertyValue("height") == "auto") {
347 metrics.contentHeight = 0;
348 metrics.contentWidth = 0;
349 metrics.marqueeWidth = 0;
350 metrics.marqueeHeight = 0;
351 return metrics;
352 }
353
354 if (isHorizontal()) {
355 m_mover->style()->setProperty("width", "-webkit-max-content", "important",
356 ASSERT_NO_EXCEPTION);
357 } else {
358 m_mover->style()->setProperty("height", "-webkit-max-content", "important",
359 ASSERT_NO_EXCEPTION);
360 }
361 CSSStyleDeclaration* moverStyle =
362 document().domWindow()->getComputedStyle(m_mover, String());
363
364 metrics.contentWidth = moverStyle->getPropertyValue("width").toDouble();
365 metrics.contentHeight = moverStyle->getPropertyValue("height").toDouble();
366 metrics.marqueeWidth = marqueeStyle->getPropertyValue("width").toDouble();
367 metrics.marqueeHeight = marqueeStyle->getPropertyValue("height").toDouble();
368
369 if (isHorizontal()) {
370 m_mover->style()->setProperty("width", "", "important",
371 ASSERT_NO_EXCEPTION);
372 } else {
373 m_mover->style()->setProperty("height", "", "important",
374 ASSERT_NO_EXCEPTION);
375 }
376
377 return metrics;
378 }
379
380 HTMLMarqueeElement::AnimationParameters
381 HTMLMarqueeElement::getAnimationParameters() {
382 AnimationParameters parameters;
383 Metrics metrics = getMetrics();
384
385 double totalWidth = metrics.marqueeWidth + metrics.contentWidth;
386 double totalHeight = metrics.marqueeHeight + metrics.contentHeight;
387
388 double innerWidth = metrics.marqueeWidth - metrics.contentWidth;
389 double innerHeight = metrics.marqueeHeight - metrics.contentHeight;
390
391 switch (behavior()) {
392 case Alternate:
393 switch (direction()) {
394 case Right:
395 parameters.transformBegin =
396 createTransform(innerWidth >= 0 ? 0 : innerWidth);
397 parameters.transformEnd =
398 createTransform(innerWidth >= 0 ? innerWidth : 0);
399 parameters.distance = std::abs(innerWidth);
400 break;
401 case Up:
402 parameters.transformBegin =
403 createTransform(innerHeight >= 0 ? innerHeight : 0);
404 parameters.transformEnd =
405 createTransform(innerHeight >= 0 ? 0 : innerHeight);
406 parameters.distance = std::abs(innerHeight);
407 break;
408 case Down:
409 parameters.transformBegin =
410 createTransform(innerHeight >= 0 ? 0 : innerHeight);
411 parameters.transformEnd =
412 createTransform(innerHeight >= 0 ? innerHeight : 0);
413 parameters.distance = std::abs(innerHeight);
414 break;
415 case Left:
416 default:
417 parameters.transformBegin =
418 createTransform(innerWidth >= 0 ? innerWidth : 0);
419 parameters.transformEnd =
420 createTransform(innerWidth >= 0 ? 0 : innerWidth);
421 parameters.distance = std::abs(innerWidth);
422 }
423
424 if (m_loopCount % 2)
425 std::swap(parameters.transformBegin, parameters.transformEnd);
426 break;
427 case Slide:
428 switch (direction()) {
429 case Right:
430 parameters.transformBegin = createTransform(-metrics.contentWidth);
431 parameters.transformEnd = createTransform(innerWidth);
432 parameters.distance = metrics.marqueeWidth;
433 break;
434 case Up:
435 parameters.transformBegin = createTransform(metrics.marqueeHeight);
436 parameters.transformEnd = "translateY(0)";
437 parameters.distance = metrics.marqueeHeight;
438 break;
439 case Down:
440 parameters.transformBegin = createTransform(-metrics.contentHeight);
441 parameters.transformEnd = createTransform(innerHeight);
442 parameters.distance = metrics.marqueeHeight;
443 break;
444 case Left:
445 default:
446 parameters.transformBegin = createTransform(metrics.marqueeWidth);
447 parameters.transformEnd = "translateX(0)";
448 parameters.distance = metrics.marqueeWidth;
449 }
450 break;
451 case Scroll:
452 default:
453 switch (direction()) {
454 case Right:
455 parameters.transformBegin = createTransform(-metrics.contentWidth);
456 parameters.transformEnd = createTransform(metrics.marqueeWidth);
457 parameters.distance = totalWidth;
458 break;
459 case Up:
460 parameters.transformBegin = createTransform(metrics.marqueeHeight);
461 parameters.transformEnd = createTransform(-metrics.contentHeight);
462 parameters.distance = totalHeight;
463 break;
464 case Down:
465 parameters.transformBegin = createTransform(-metrics.contentHeight);
466 parameters.transformEnd = createTransform(metrics.marqueeHeight);
467 parameters.distance = totalHeight;
468 break;
469 case Left:
470 default:
471 parameters.transformBegin = createTransform(metrics.marqueeWidth);
472 parameters.transformEnd = createTransform(-metrics.contentWidth);
473 parameters.distance = totalWidth;
474 }
475 break;
476 }
477
478 return parameters;
479 }
480
481 AtomicString HTMLMarqueeElement::createTransform(double value) const {
482 char axis = isHorizontal() ? 'X' : 'Y';
483 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
484 }
485
486 DEFINE_TRACE(HTMLMarqueeElement) {
487 visitor->trace(m_mover);
488 visitor->trace(m_player);
489 HTMLElement::trace(visitor);
83 } 490 }
84 491
85 } // namespace blink 492 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698