| /* |
| * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/layout/LayoutScrollbar.h" |
| |
| #include "core/css/PseudoStyleRequest.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/layout/LayoutPart.h" |
| #include "core/layout/LayoutScrollbarPart.h" |
| #include "core/layout/LayoutScrollbarTheme.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/api/LayoutAPIShim.h" |
| #include "core/layout/api/LayoutPartItem.h" |
| #include "core/paint/ObjectPaintInvalidator.h" |
| #include "platform/graphics/GraphicsContext.h" |
| |
| namespace blink { |
| |
| Scrollbar* LayoutScrollbar::createCustomScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Node* ownerNode, LocalFrame* owningFrame) |
| { |
| return new LayoutScrollbar(scrollableArea, orientation, ownerNode, owningFrame); |
| } |
| |
| LayoutScrollbar::LayoutScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, Node* ownerNode, LocalFrame* owningFrame) |
| : Scrollbar(scrollableArea, orientation, RegularScrollbar, nullptr, LayoutScrollbarTheme::layoutScrollbarTheme()) |
| , m_owner(ownerNode) |
| , m_owningFrame(owningFrame) |
| { |
| ASSERT(ownerNode || owningFrame); |
| |
| // FIXME: We need to do this because LayoutScrollbar::styleChanged is called as soon as the scrollbar is created. |
| |
| // Update the scrollbar size. |
| IntRect rect(0, 0, 0, 0); |
| updateScrollbarPart(ScrollbarBGPart); |
| if (LayoutScrollbarPart* part = m_parts.get(ScrollbarBGPart)) { |
| part->layout(); |
| rect.setSize(flooredIntSize(part->size())); |
| } else if (this->orientation() == HorizontalScrollbar) { |
| rect.setWidth(this->width()); |
| } else { |
| rect.setHeight(this->height()); |
| } |
| |
| setFrameRect(rect); |
| |
| } |
| |
| LayoutScrollbar::~LayoutScrollbar() |
| { |
| if (m_parts.isEmpty()) |
| return; |
| |
| // When a scrollbar is detached from its parent (causing all parts removal) and |
| // ready to be destroyed, its destruction can be delayed because of RefPtr |
| // maintained in other classes such as EventHandler (m_lastScrollbarUnderMouse). |
| // Meanwhile, we can have a call to updateScrollbarPart which recreates the |
| // scrollbar part. So, we need to destroy these parts since we don't want them |
| // to call on a destroyed scrollbar. See webkit bug 68009. |
| updateScrollbarParts(true); |
| } |
| |
| DEFINE_TRACE(LayoutScrollbar) |
| { |
| visitor->trace(m_owner); |
| visitor->trace(m_owningFrame); |
| Scrollbar::trace(visitor); |
| } |
| |
| LayoutBox* LayoutScrollbar::owningLayoutObject() const |
| { |
| if (m_owningFrame) |
| return toLayoutBox(LayoutAPIShim::layoutObjectFrom(m_owningFrame->ownerLayoutItem())); |
| return m_owner && m_owner->layoutObject() ? m_owner->layoutObject()->enclosingBox() : 0; |
| } |
| |
| LayoutBox* LayoutScrollbar::owningLayoutObjectWithinFrame() const |
| { |
| if (m_owningFrame) |
| return m_owningFrame->contentLayoutObject(); |
| return owningLayoutObject(); |
| } |
| |
| void LayoutScrollbar::setParent(Widget* parent) |
| { |
| Scrollbar::setParent(parent); |
| if (!parent) { |
| // Destroy all of the scrollbar's LayoutBoxes. |
| updateScrollbarParts(true); |
| } |
| } |
| |
| void LayoutScrollbar::setEnabled(bool e) |
| { |
| bool wasEnabled = enabled(); |
| Scrollbar::setEnabled(e); |
| if (wasEnabled != e) |
| updateScrollbarParts(); |
| } |
| |
| void LayoutScrollbar::styleChanged() |
| { |
| updateScrollbarParts(); |
| } |
| |
| void LayoutScrollbar::setHoveredPart(ScrollbarPart part) |
| { |
| if (part == m_hoveredPart) |
| return; |
| |
| ScrollbarPart oldPart = m_hoveredPart; |
| m_hoveredPart = part; |
| |
| updateScrollbarPart(oldPart); |
| updateScrollbarPart(m_hoveredPart); |
| |
| updateScrollbarPart(ScrollbarBGPart); |
| updateScrollbarPart(TrackBGPart); |
| } |
| |
| void LayoutScrollbar::setPressedPart(ScrollbarPart part) |
| { |
| ScrollbarPart oldPart = m_pressedPart; |
| Scrollbar::setPressedPart(part); |
| |
| updateScrollbarPart(oldPart); |
| updateScrollbarPart(part); |
| |
| updateScrollbarPart(ScrollbarBGPart); |
| updateScrollbarPart(TrackBGPart); |
| } |
| |
| PassRefPtr<ComputedStyle> LayoutScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId) |
| { |
| if (!owningLayoutObject()) |
| return nullptr; |
| |
| RefPtr<ComputedStyle> result = owningLayoutObject()->getUncachedPseudoStyle(PseudoStyleRequest(pseudoId, this, partType), owningLayoutObject()->style()); |
| // Scrollbars for root frames should always have background color |
| // unless explicitly specified as transparent. So we force it. |
| // This is because WebKit assumes scrollbar to be always painted and missing background |
| // causes visual artifact like non-paint invalidated dirty region. |
| if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground()) |
| result->setBackgroundColor(StyleColor(Color::white)); |
| |
| return result; |
| } |
| |
| void LayoutScrollbar::updateScrollbarParts(bool destroy) |
| { |
| updateScrollbarPart(ScrollbarBGPart, destroy); |
| updateScrollbarPart(BackButtonStartPart, destroy); |
| updateScrollbarPart(ForwardButtonStartPart, destroy); |
| updateScrollbarPart(BackTrackPart, destroy); |
| updateScrollbarPart(ThumbPart, destroy); |
| updateScrollbarPart(ForwardTrackPart, destroy); |
| updateScrollbarPart(BackButtonEndPart, destroy); |
| updateScrollbarPart(ForwardButtonEndPart, destroy); |
| updateScrollbarPart(TrackBGPart, destroy); |
| |
| if (destroy) |
| return; |
| |
| // See if the scrollbar's thickness changed. If so, we need to mark our owning object as needing a layout. |
| bool isHorizontal = orientation() == HorizontalScrollbar; |
| int oldThickness = isHorizontal ? height() : width(); |
| int newThickness = 0; |
| LayoutScrollbarPart* part = m_parts.get(ScrollbarBGPart); |
| if (part) { |
| part->layout(); |
| newThickness = (isHorizontal ? part->size().height() : part->size().width()).toInt(); |
| } |
| |
| if (newThickness != oldThickness) { |
| setFrameRect(IntRect(location(), IntSize(isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height()))); |
| if (LayoutBox* box = owningLayoutObjectWithinFrame()) { |
| if (box->isLayoutBlock()) |
| toLayoutBlock(box)->notifyScrollbarThicknessChanged(); |
| box->setChildNeedsLayout(); |
| if (m_scrollableArea) |
| m_scrollableArea->setScrollCornerNeedsPaintInvalidation(); |
| } |
| } |
| } |
| |
| static PseudoId pseudoForScrollbarPart(ScrollbarPart part) |
| { |
| switch (part) { |
| case BackButtonStartPart: |
| case ForwardButtonStartPart: |
| case BackButtonEndPart: |
| case ForwardButtonEndPart: |
| return PseudoIdScrollbarButton; |
| case BackTrackPart: |
| case ForwardTrackPart: |
| return PseudoIdScrollbarTrackPiece; |
| case ThumbPart: |
| return PseudoIdScrollbarThumb; |
| case TrackBGPart: |
| return PseudoIdScrollbarTrack; |
| case ScrollbarBGPart: |
| return PseudoIdScrollbar; |
| case NoPart: |
| case AllParts: |
| break; |
| } |
| ASSERT_NOT_REACHED(); |
| return PseudoIdScrollbar; |
| } |
| |
| void LayoutScrollbar::updateScrollbarPart(ScrollbarPart partType, bool destroy) |
| { |
| if (partType == NoPart) |
| return; |
| |
| RefPtr<ComputedStyle> partStyle = !destroy ? getScrollbarPseudoStyle(partType, pseudoForScrollbarPart(partType)) : PassRefPtr<ComputedStyle>(nullptr); |
| |
| bool needLayoutObject = !destroy && partStyle && partStyle->display() != EDisplay::None; |
| |
| if (needLayoutObject && partStyle->display() != EDisplay::Block) { |
| // See if we are a button that should not be visible according to OS settings. |
| WebScrollbarButtonsPlacement buttonsPlacement = theme().buttonsPlacement(); |
| switch (partType) { |
| case BackButtonStartPart: |
| needLayoutObject = (buttonsPlacement == WebScrollbarButtonsPlacementSingle || buttonsPlacement == WebScrollbarButtonsPlacementDoubleStart |
| || buttonsPlacement == WebScrollbarButtonsPlacementDoubleBoth); |
| break; |
| case ForwardButtonStartPart: |
| needLayoutObject = (buttonsPlacement == WebScrollbarButtonsPlacementDoubleStart || buttonsPlacement == WebScrollbarButtonsPlacementDoubleBoth); |
| break; |
| case BackButtonEndPart: |
| needLayoutObject = (buttonsPlacement == WebScrollbarButtonsPlacementDoubleEnd || buttonsPlacement == WebScrollbarButtonsPlacementDoubleBoth); |
| break; |
| case ForwardButtonEndPart: |
| needLayoutObject = (buttonsPlacement == WebScrollbarButtonsPlacementSingle || buttonsPlacement == WebScrollbarButtonsPlacementDoubleEnd |
| || buttonsPlacement == WebScrollbarButtonsPlacementDoubleBoth); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| LayoutScrollbarPart* partLayoutObject = m_parts.get(partType); |
| if (!partLayoutObject && needLayoutObject && m_scrollableArea) { |
| partLayoutObject = LayoutScrollbarPart::createAnonymous(&owningLayoutObject()->document(), m_scrollableArea, this, partType); |
| m_parts.set(partType, partLayoutObject); |
| } else if (partLayoutObject && !needLayoutObject) { |
| m_parts.remove(partType); |
| partLayoutObject->destroy(); |
| partLayoutObject = 0; |
| } |
| |
| if (partLayoutObject) |
| partLayoutObject->setStyleWithWritingModeOfParent(partStyle.release()); |
| } |
| |
| IntRect LayoutScrollbar::buttonRect(ScrollbarPart partType) const |
| { |
| LayoutScrollbarPart* partLayoutObject = m_parts.get(partType); |
| if (!partLayoutObject) |
| return IntRect(); |
| |
| partLayoutObject->layout(); |
| |
| bool isHorizontal = orientation() == HorizontalScrollbar; |
| if (partType == BackButtonStartPart) |
| return IntRect(location(), IntSize(isHorizontal ? partLayoutObject->pixelSnappedWidth() : width(), isHorizontal ? height() : partLayoutObject->pixelSnappedHeight())); |
| if (partType == ForwardButtonEndPart) { |
| return IntRect(isHorizontal ? x() + width() - partLayoutObject->pixelSnappedWidth() : x(), |
| isHorizontal ? y() : y() + height() - partLayoutObject->pixelSnappedHeight(), |
| isHorizontal ? partLayoutObject->pixelSnappedWidth() : width(), |
| isHorizontal ? height() : partLayoutObject->pixelSnappedHeight()); |
| } |
| |
| if (partType == ForwardButtonStartPart) { |
| IntRect previousButton = buttonRect(BackButtonStartPart); |
| return IntRect(isHorizontal ? x() + previousButton.width() : x(), |
| isHorizontal ? y() : y() + previousButton.height(), |
| isHorizontal ? partLayoutObject->pixelSnappedWidth() : width(), |
| isHorizontal ? height() : partLayoutObject->pixelSnappedHeight()); |
| } |
| |
| IntRect followingButton = buttonRect(ForwardButtonEndPart); |
| return IntRect(isHorizontal ? x() + width() - followingButton.width() - partLayoutObject->pixelSnappedWidth() : x(), |
| isHorizontal ? y() : y() + height() - followingButton.height() - partLayoutObject->pixelSnappedHeight(), |
| isHorizontal ? partLayoutObject->pixelSnappedWidth() : width(), |
| isHorizontal ? height() : partLayoutObject->pixelSnappedHeight()); |
| } |
| |
| IntRect LayoutScrollbar::trackRect(int startLength, int endLength) const |
| { |
| LayoutScrollbarPart* part = m_parts.get(TrackBGPart); |
| if (part) |
| part->layout(); |
| |
| if (orientation() == HorizontalScrollbar) { |
| int marginLeft = part ? part->marginLeft().toInt() : 0; |
| int marginRight = part ? part->marginRight().toInt() : 0; |
| startLength += marginLeft; |
| endLength += marginRight; |
| int totalLength = startLength + endLength; |
| return IntRect(x() + startLength, y(), width() - totalLength, height()); |
| } |
| |
| int marginTop = part ? part->marginTop().toInt() : 0; |
| int marginBottom = part ? part->marginBottom().toInt() : 0; |
| startLength += marginTop; |
| endLength += marginBottom; |
| int totalLength = startLength + endLength; |
| |
| return IntRect(x(), y() + startLength, width(), height() - totalLength); |
| } |
| |
| IntRect LayoutScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect) const |
| { |
| LayoutScrollbarPart* partLayoutObject = m_parts.get(partType); |
| if (!partLayoutObject) |
| return oldRect; |
| |
| partLayoutObject->layout(); |
| |
| IntRect rect = oldRect; |
| if (orientation() == HorizontalScrollbar) { |
| rect.setX((rect.x() + partLayoutObject->marginLeft()).toInt()); |
| rect.setWidth((rect.width() - partLayoutObject->marginWidth()).toInt()); |
| } else { |
| rect.setY((rect.y() + partLayoutObject->marginTop()).toInt()); |
| rect.setHeight((rect.height() - partLayoutObject->marginHeight()).toInt()); |
| } |
| return rect; |
| } |
| |
| int LayoutScrollbar::minimumThumbLength() const |
| { |
| LayoutScrollbarPart* partLayoutObject = m_parts.get(ThumbPart); |
| if (!partLayoutObject) |
| return 0; |
| partLayoutObject->layout(); |
| return (orientation() == HorizontalScrollbar ? partLayoutObject->size().width() : partLayoutObject->size().height()).toInt(); |
| } |
| |
| void LayoutScrollbar::invalidateDisplayItemClientsOfScrollbarParts() |
| { |
| for (auto& part : m_parts) |
| ObjectPaintInvalidator(*part.value).invalidateDisplayItemClientsIncludingNonCompositingDescendants(PaintInvalidationScroll); |
| } |
| |
| } // namespace blink |