blob: db0e8e77d8c354a2198876047d568129ff5db7ee [file] [log] [blame]
/*
* Copyright (C) 2011 Google 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT
* OWNER 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 "third_party/blink/renderer/core/editing/compute_layer_selection.h"
#include "cc/layers/picture_layer.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/local_caret_rect.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/editing/visible_selection.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include <unicode/ubidi.h>
namespace blink {
// Note: If the layout object has a scrolling contents layer, the selection
// will be relative to that.
static GraphicsLayer* GetGraphicsLayerBacking(
const LayoutObject& layout_object) {
const LayoutBoxModelObject& paint_invalidation_container =
layout_object.ContainerForPaintInvalidation();
DCHECK(paint_invalidation_container.Layer());
if (paint_invalidation_container.Layer()->GetCompositingState() ==
kNotComposited)
return nullptr;
return paint_invalidation_container.Layer()->GraphicsLayerBacking(
&layout_object);
}
// Convert a local point into the coordinate system of backing coordinates.
static gfx::Point LocalToInvalidationBackingPoint(
const LayoutPoint& local_point,
const LayoutObject& layout_object) {
const LayoutBoxModelObject& paint_invalidation_container =
layout_object.ContainerForPaintInvalidation();
DCHECK(paint_invalidation_container.Layer());
FloatPoint container_point = layout_object.LocalToAncestorPoint(
FloatPoint(local_point), &paint_invalidation_container,
kTraverseDocumentBoundaries);
// A layoutObject can have no invalidation backing if it is from a detached
// frame, or when forced compositing is disabled.
if (paint_invalidation_container.Layer()->GetCompositingState() ==
kNotComposited)
return RoundedIntPoint(container_point);
PaintLayer::MapPointInPaintInvalidationContainerToBacking(
paint_invalidation_container, container_point);
if (GraphicsLayer* graphics_layer = GetGraphicsLayerBacking(layout_object))
container_point.Move(-graphics_layer->OffsetFromLayoutObject());
// Ensure the coordinates are in the scrolling contents space, if the object
// is a scroller.
if (paint_invalidation_container.UsesCompositedScrolling()) {
container_point.Move(paint_invalidation_container.Layer()
->GetScrollableArea()
->GetScrollOffset());
}
return RoundedIntPoint(container_point);
}
std::pair<LayoutPoint, LayoutPoint> static GetLocalSelectionStartpoints(
const LocalCaretRect& local_caret_rect) {
const LayoutRect rect = local_caret_rect.rect;
if (local_caret_rect.layout_object->Style()->IsHorizontalWritingMode())
return {rect.MinXMinYCorner(), rect.MinXMaxYCorner()};
// When text is vertical, it looks better for the start handle baseline to
// be at the starting edge, to enclose the selection fully between the
// handles.
return {rect.MaxXMinYCorner(), rect.MinXMinYCorner()};
}
std::pair<LayoutPoint, LayoutPoint> static GetLocalSelectionEndpoints(
const LocalCaretRect& local_caret_rect) {
const LayoutRect rect = local_caret_rect.rect;
if (local_caret_rect.layout_object->Style()->IsHorizontalWritingMode())
return {rect.MinXMinYCorner(), rect.MinXMaxYCorner()};
return {rect.MinXMinYCorner(), rect.MaxXMinYCorner()};
}
static LayoutPoint GetSamplePointForVisibility(
const LayoutPoint& edge_top_in_layer,
const LayoutPoint& edge_bottom_in_layer,
float zoom_factor) {
FloatSize diff(edge_top_in_layer - edge_bottom_in_layer);
// Adjust by ~1px to avoid integer snapping error. This logic is the same
// as that in ComputeViewportSelectionBound in cc.
diff.Scale(zoom_factor / diff.DiagonalLength());
LayoutPoint sample_point = edge_bottom_in_layer;
sample_point.Move(LayoutSize(diff));
return sample_point;
}
// Returns whether this position is not visible on the screen (because
// clipped out).
static bool IsVisible(const LayoutObject& rect_layout_object,
const LayoutPoint& edge_top_in_layer,
const LayoutPoint& edge_bottom_in_layer) {
Node* const node = rect_layout_object.GetNode();
if (!node)
return true;
TextControlElement* text_control = EnclosingTextControl(node);
if (!text_control)
return true;
if (!IsHTMLInputElement(text_control))
return true;
LayoutObject* layout_object = text_control->GetLayoutObject();
if (!layout_object || !layout_object->IsBox())
return true;
const LayoutPoint sample_point =
GetSamplePointForVisibility(edge_top_in_layer, edge_bottom_in_layer,
rect_layout_object.View()->ZoomFactor());
LayoutBox* const text_control_object = ToLayoutBox(layout_object);
const LayoutPoint position_in_input(rect_layout_object.LocalToAncestorPoint(
FloatPoint(sample_point), text_control_object,
kTraverseDocumentBoundaries));
return text_control_object->BorderBoxRect().Contains(position_in_input);
}
static base::Optional<cc::LayerSelectionBound> ComputeSelectionBound(
const LayoutObject& layout_object,
const LayoutPoint& edge_top_in_layer,
const LayoutPoint& edge_bottom_in_layer) {
cc::LayerSelectionBound bound;
bound.edge_top =
LocalToInvalidationBackingPoint(edge_top_in_layer, layout_object);
bound.edge_bottom =
LocalToInvalidationBackingPoint(edge_bottom_in_layer, layout_object);
GraphicsLayer* const layer = GetGraphicsLayerBacking(layout_object);
if (!layer)
return base::nullopt;
bound.layer_id = layer->CcLayer()->id();
bound.hidden =
!IsVisible(layout_object, edge_top_in_layer, edge_bottom_in_layer);
return bound;
}
// TODO(yoichio): Fix following weird implementation:
// 1. IsTextDirectionRTL is computed from original Node and
// LayoutObject from LocalCaretRectOfPosition.
// 2. Current code can make both selection handles "LEFT".
static inline bool IsTextDirectionRTL(const Node& node,
const LayoutObject& layout_object) {
return layout_object.HasFlippedBlocksWritingMode() ||
PrimaryDirectionOf(node) == TextDirection::kRtl;
}
static base::Optional<cc::LayerSelectionBound>
StartPositionInGraphicsLayerBacking(const SelectionInDOMTree& selection) {
const PositionWithAffinity position(selection.ComputeStartPosition(),
selection.Affinity());
const LocalCaretRect& local_caret_rect = LocalCaretRectOfPosition(position);
const LayoutObject* const layout_object = local_caret_rect.layout_object;
if (!layout_object)
return base::nullopt;
LayoutPoint edge_top_in_layer, edge_bottom_in_layer;
std::tie(edge_top_in_layer, edge_bottom_in_layer) =
GetLocalSelectionStartpoints(local_caret_rect);
const auto& maybe = ComputeSelectionBound(*layout_object, edge_top_in_layer,
edge_bottom_in_layer);
if (!maybe.has_value())
return base::nullopt;
cc::LayerSelectionBound bound = maybe.value();
if (selection.IsRange()) {
bound.type = IsTextDirectionRTL(*position.AnchorNode(), *layout_object)
? gfx::SelectionBound::Type::RIGHT
: gfx::SelectionBound::Type::LEFT;
} else {
bound.type = gfx::SelectionBound::Type::CENTER;
}
return bound;
}
static base::Optional<cc::LayerSelectionBound>
EndPositionInGraphicsLayerBacking(const SelectionInDOMTree& selection) {
const PositionWithAffinity position(selection.ComputeEndPosition(),
selection.Affinity());
const LocalCaretRect& local_caret_rect = LocalCaretRectOfPosition(position);
const LayoutObject* const layout_object = local_caret_rect.layout_object;
if (!layout_object)
return base::nullopt;
LayoutPoint edge_top_in_layer, edge_bottom_in_layer;
std::tie(edge_top_in_layer, edge_bottom_in_layer) =
GetLocalSelectionEndpoints(local_caret_rect);
const auto& maybe = ComputeSelectionBound(*layout_object, edge_top_in_layer,
edge_bottom_in_layer);
if (!maybe.has_value())
return base::nullopt;
cc::LayerSelectionBound bound = maybe.value();
if (selection.IsRange()) {
bound.type = IsTextDirectionRTL(*position.AnchorNode(), *layout_object)
? gfx::SelectionBound::Type::LEFT
: gfx::SelectionBound::Type::RIGHT;
} else {
bound.type = gfx::SelectionBound::Type::CENTER;
}
return bound;
}
cc::LayerSelection ComputeLayerSelection(
const FrameSelection& frame_selection) {
if (!frame_selection.IsHandleVisible() || frame_selection.IsHidden())
return {};
// TODO(yoichio): Compute SelectionInDOMTree w/o VS canonicalization.
// crbug.com/789870 for detail.
const SelectionInDOMTree& selection =
frame_selection.ComputeVisibleSelectionInDOMTree().AsSelection();
if (selection.IsNone())
return {};
// Non-editable caret selections lack any kind of UI affordance, and
// needn't be tracked by the client.
if (selection.IsCaret() &&
!IsEditablePosition(selection.ComputeStartPosition()))
return {};
const auto& maybe_start_bound =
StartPositionInGraphicsLayerBacking(selection);
if (!maybe_start_bound.has_value())
return {};
const auto& maybe_end_bound = EndPositionInGraphicsLayerBacking(selection);
if (!maybe_end_bound.has_value())
return {};
return {maybe_start_bound.value(), maybe_end_bound.value()};
}
} // namespace blink