| 1 | /* |
| 2 | * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. |
| 3 | * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
| 4 | * |
| 5 | * Portions are Copyright (C) 1998 Netscape Communications Corporation. |
| 6 | * |
| 7 | * Other contributors: |
| 8 | * Robert O'Callahan <roc+@cs.cmu.edu> |
| 9 | * David Baron <dbaron@fas.harvard.edu> |
| 10 | * Christian Biesinger <cbiesinger@web.de> |
| 11 | * Randall Jesup <rjesup@wgate.com> |
| 12 | * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> |
| 13 | * Josh Soref <timeless@mac.com> |
| 14 | * Boris Zbarsky <bzbarsky@mit.edu> |
| 15 | * |
| 16 | * This library is free software; you can redistribute it and/or |
| 17 | * modify it under the terms of the GNU Lesser General Public |
| 18 | * License as published by the Free Software Foundation; either |
| 19 | * version 2.1 of the License, or (at your option) any later version. |
| 20 | * |
| 21 | * This library is distributed in the hope that it will be useful, |
| 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 24 | * Lesser General Public License for more details. |
| 25 | * |
| 26 | * You should have received a copy of the GNU Lesser General Public |
| 27 | * License along with this library; if not, write to the Free Software |
| 28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 29 | * |
| 30 | * Alternatively, the contents of this file may be used under the terms |
| 31 | * of either the Mozilla Public License Version 1.1, found at |
| 32 | * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public |
| 33 | * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html |
| 34 | * (the "GPL"), in which case the provisions of the MPL or the GPL are |
| 35 | * applicable instead of those above. If you wish to allow use of your |
| 36 | * version of this file only under the terms of one of those two |
| 37 | * licenses (the MPL or the GPL) and not to allow others to use your |
| 38 | * version of this file under the LGPL, indicate your decision by |
| 39 | * deletingthe provisions above and replace them with the notice and |
| 40 | * other provisions required by the MPL or the GPL, as the case may be. |
| 41 | * If you do not delete the provisions above, a recipient may use your |
| 42 | * version of this file under any of the LGPL, the MPL or the GPL. |
| 43 | */ |
| 44 | |
| 45 | #include "config.h" |
| 46 | |
| 47 | #include "RenderMarquee.h" |
| 48 | |
| 49 | #include "FrameView.h" |
| 50 | #include "HTMLMarqueeElement.h" |
| 51 | #include "HTMLNames.h" |
| 52 | #include "RenderLayer.h" |
| 53 | #include "RenderView.h" |
| 54 | |
| 55 | namespace WebCore { |
| 56 | |
| 57 | using namespace HTMLNames; |
| 58 | |
| 59 | RenderMarquee::RenderMarquee(RenderLayer* layer) |
| 60 | : m_layer(layer) |
| 61 | , m_timer(*this, &RenderMarquee::timerFired) |
| 62 | { |
| 63 | layer->setConstrainsScrollingToContentEdge(false); |
| 64 | } |
| 65 | |
| 66 | RenderMarquee::~RenderMarquee() = default; |
| 67 | |
| 68 | int RenderMarquee::marqueeSpeed() const |
| 69 | { |
| 70 | int result = m_layer->renderer().style().marqueeSpeed(); |
| 71 | Element* element = m_layer->renderer().element(); |
| 72 | if (is<HTMLMarqueeElement>(element)) |
| 73 | result = std::max(result, downcast<HTMLMarqueeElement>(*element).minimumDelay()); |
| 74 | return result; |
| 75 | } |
| 76 | |
| 77 | static MarqueeDirection reverseDirection(MarqueeDirection direction) |
| 78 | { |
| 79 | switch (direction) { |
| 80 | case MarqueeDirection::Auto: |
| 81 | return MarqueeDirection::Auto; |
| 82 | case MarqueeDirection::Left: |
| 83 | return MarqueeDirection::Right; |
| 84 | case MarqueeDirection::Right: |
| 85 | return MarqueeDirection::Left; |
| 86 | case MarqueeDirection::Up: |
| 87 | return MarqueeDirection::Down; |
| 88 | case MarqueeDirection::Down: |
| 89 | return MarqueeDirection::Up; |
| 90 | case MarqueeDirection::Backward: |
| 91 | return MarqueeDirection::Forward; |
| 92 | case MarqueeDirection::Forward: |
| 93 | return MarqueeDirection::Backward; |
| 94 | } |
| 95 | return MarqueeDirection::Auto; |
| 96 | } |
| 97 | |
| 98 | MarqueeDirection RenderMarquee::direction() const |
| 99 | { |
| 100 | // FIXME: Support the CSS3 "auto" value for determining the direction of the marquee. |
| 101 | // For now just map MarqueeDirection::Auto to MarqueeDirection::Backward |
| 102 | MarqueeDirection result = m_layer->renderer().style().marqueeDirection(); |
| 103 | TextDirection dir = m_layer->renderer().style().direction(); |
| 104 | if (result == MarqueeDirection::Auto) |
| 105 | result = MarqueeDirection::Backward; |
| 106 | if (result == MarqueeDirection::Forward) |
| 107 | result = (dir == TextDirection::LTR) ? MarqueeDirection::Right : MarqueeDirection::Left; |
| 108 | if (result == MarqueeDirection::Backward) |
| 109 | result = (dir == TextDirection::LTR) ? MarqueeDirection::Left : MarqueeDirection::Right; |
| 110 | |
| 111 | // Now we have the real direction. Next we check to see if the increment is negative. |
| 112 | // If so, then we reverse the direction. |
| 113 | Length increment = m_layer->renderer().style().marqueeIncrement(); |
| 114 | if (increment.isNegative()) |
| 115 | result = reverseDirection(result); |
| 116 | |
| 117 | return result; |
| 118 | } |
| 119 | |
| 120 | bool RenderMarquee::isHorizontal() const |
| 121 | { |
| 122 | return direction() == MarqueeDirection::Left || direction() == MarqueeDirection::Right; |
| 123 | } |
| 124 | |
| 125 | int RenderMarquee::computePosition(MarqueeDirection dir, bool stopAtContentEdge) |
| 126 | { |
| 127 | RenderBox* box = m_layer->renderBox(); |
| 128 | ASSERT(box); |
| 129 | auto& boxStyle = box->style(); |
| 130 | if (isHorizontal()) { |
| 131 | bool ltr = boxStyle.isLeftToRightDirection(); |
| 132 | LayoutUnit clientWidth = box->clientWidth(); |
| 133 | LayoutUnit contentWidth = ltr ? box->maxPreferredLogicalWidth() : box->minPreferredLogicalWidth(); |
| 134 | if (ltr) |
| 135 | contentWidth += (box->paddingRight() - box->borderLeft()); |
| 136 | else { |
| 137 | contentWidth = box->width() - contentWidth; |
| 138 | contentWidth += (box->paddingLeft() - box->borderRight()); |
| 139 | } |
| 140 | if (dir == MarqueeDirection::Right) { |
| 141 | if (stopAtContentEdge) |
| 142 | return std::max<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); |
| 143 | |
| 144 | return ltr ? contentWidth : clientWidth; |
| 145 | } |
| 146 | |
| 147 | if (stopAtContentEdge) |
| 148 | return std::min<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); |
| 149 | |
| 150 | return ltr ? -clientWidth : -contentWidth; |
| 151 | } |
| 152 | |
| 153 | // Vertical |
| 154 | int contentHeight = box->layoutOverflowRect().maxY() - box->borderTop() + box->paddingBottom(); |
| 155 | int clientHeight = roundToInt(box->clientHeight()); |
| 156 | if (dir == MarqueeDirection::Up) { |
| 157 | if (stopAtContentEdge) |
| 158 | return std::min(contentHeight - clientHeight, 0); |
| 159 | |
| 160 | return -clientHeight; |
| 161 | } |
| 162 | |
| 163 | if (stopAtContentEdge) |
| 164 | return std::max(contentHeight - clientHeight, 0); |
| 165 | |
| 166 | return contentHeight; |
| 167 | } |
| 168 | |
| 169 | void RenderMarquee::start() |
| 170 | { |
| 171 | if (m_timer.isActive() || m_layer->renderer().style().marqueeIncrement().isZero()) |
| 172 | return; |
| 173 | |
| 174 | if (!m_suspended && !m_stopped) { |
| 175 | if (isHorizontal()) |
| 176 | m_layer->scrollToOffset(ScrollOffset(m_start, 0), ScrollType::Programmatic, ScrollClamping::Unclamped); |
| 177 | else |
| 178 | m_layer->scrollToOffset(ScrollOffset(0, m_start), ScrollType::Programmatic, ScrollClamping::Unclamped); |
| 179 | } else { |
| 180 | m_suspended = false; |
| 181 | m_stopped = false; |
| 182 | } |
| 183 | |
| 184 | m_timer.startRepeating(1_ms * speed()); |
| 185 | } |
| 186 | |
| 187 | void RenderMarquee::suspend() |
| 188 | { |
| 189 | m_timer.stop(); |
| 190 | m_suspended = true; |
| 191 | } |
| 192 | |
| 193 | void RenderMarquee::stop() |
| 194 | { |
| 195 | m_timer.stop(); |
| 196 | m_stopped = true; |
| 197 | } |
| 198 | |
| 199 | void RenderMarquee::updateMarqueePosition() |
| 200 | { |
| 201 | bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); |
| 202 | if (activate) { |
| 203 | MarqueeBehavior behavior = m_layer->renderer().style().marqueeBehavior(); |
| 204 | m_start = computePosition(direction(), behavior == MarqueeBehavior::Alternate); |
| 205 | m_end = computePosition(reverseDirection(direction()), behavior == MarqueeBehavior::Alternate || behavior == MarqueeBehavior::Slide); |
| 206 | if (!m_stopped) |
| 207 | start(); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | void RenderMarquee::updateMarqueeStyle() |
| 212 | { |
| 213 | auto& style = m_layer->renderer().style(); |
| 214 | |
| 215 | if (m_direction != style.marqueeDirection() || (m_totalLoops != style.marqueeLoopCount() && m_currentLoop >= m_totalLoops)) |
| 216 | m_currentLoop = 0; // When direction changes or our loopCount is a smaller number than our current loop, reset our loop. |
| 217 | |
| 218 | m_totalLoops = style.marqueeLoopCount(); |
| 219 | m_direction = style.marqueeDirection(); |
| 220 | |
| 221 | if (m_layer->renderer().isHTMLMarquee()) { |
| 222 | // Hack for WinIE. In WinIE, a value of 0 or lower for the loop count for SLIDE means to only do |
| 223 | // one loop. |
| 224 | if (m_totalLoops <= 0 && style.marqueeBehavior() == MarqueeBehavior::Slide) |
| 225 | m_totalLoops = 1; |
| 226 | } |
| 227 | |
| 228 | if (speed() != marqueeSpeed()) { |
| 229 | m_speed = marqueeSpeed(); |
| 230 | if (m_timer.isActive()) |
| 231 | m_timer.startRepeating(1_ms * speed()); |
| 232 | } |
| 233 | |
| 234 | // Check the loop count to see if we should now stop. |
| 235 | bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); |
| 236 | if (activate && !m_timer.isActive()) |
| 237 | m_layer->renderer().setNeedsLayout(); |
| 238 | else if (!activate && m_timer.isActive()) |
| 239 | m_timer.stop(); |
| 240 | } |
| 241 | |
| 242 | void RenderMarquee::timerFired() |
| 243 | { |
| 244 | if (m_layer->renderer().view().needsLayout()) |
| 245 | return; |
| 246 | |
| 247 | if (m_reset) { |
| 248 | m_reset = false; |
| 249 | if (isHorizontal()) |
| 250 | m_layer->scrollToXOffset(m_start); |
| 251 | else |
| 252 | m_layer->scrollToYOffset(m_start); |
| 253 | return; |
| 254 | } |
| 255 | |
| 256 | const RenderStyle& style = m_layer->renderer().style(); |
| 257 | |
| 258 | int endPoint = m_end; |
| 259 | int range = m_end - m_start; |
| 260 | int newPos; |
| 261 | if (range == 0) |
| 262 | newPos = m_end; |
| 263 | else { |
| 264 | bool addIncrement = direction() == MarqueeDirection::Up || direction() == MarqueeDirection::Left; |
| 265 | bool isReversed = style.marqueeBehavior() == MarqueeBehavior::Alternate && m_currentLoop % 2; |
| 266 | if (isReversed) { |
| 267 | // We're going in the reverse direction. |
| 268 | endPoint = m_start; |
| 269 | range = -range; |
| 270 | addIncrement = !addIncrement; |
| 271 | } |
| 272 | bool positive = range > 0; |
| 273 | int clientSize = (isHorizontal() ? roundToInt(m_layer->renderBox()->clientWidth()) : roundToInt(m_layer->renderBox()->clientHeight())); |
| 274 | int increment = abs(intValueForLength(m_layer->renderer().style().marqueeIncrement(), clientSize)); |
| 275 | int currentPos = (isHorizontal() ? m_layer->scrollOffset().x() : m_layer->scrollOffset().y()); |
| 276 | newPos = currentPos + (addIncrement ? increment : -increment); |
| 277 | if (positive) |
| 278 | newPos = std::min(newPos, endPoint); |
| 279 | else |
| 280 | newPos = std::max(newPos, endPoint); |
| 281 | } |
| 282 | |
| 283 | if (newPos == endPoint) { |
| 284 | m_currentLoop++; |
| 285 | if (m_totalLoops > 0 && m_currentLoop >= m_totalLoops) |
| 286 | m_timer.stop(); |
| 287 | else if (style.marqueeBehavior() != MarqueeBehavior::Alternate) |
| 288 | m_reset = true; |
| 289 | } |
| 290 | |
| 291 | if (isHorizontal()) |
| 292 | m_layer->scrollToXOffset(newPos); |
| 293 | else |
| 294 | m_layer->scrollToYOffset(newPos); |
| 295 | } |
| 296 | |
| 297 | } // namespace WebCore |
| 298 | |