| /* |
| * 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 |