OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. | 2 * Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 497 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
508 template <typename Strategy> | 508 template <typename Strategy> |
509 bool PositionAlgorithm<Strategy>::atEndOfTree() const | 509 bool PositionAlgorithm<Strategy>::atEndOfTree() const |
510 { | 510 { |
511 if (isNull()) | 511 if (isNull()) |
512 return true; | 512 return true; |
513 // TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead of | 513 // TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead of |
514 // DOM tree version. | 514 // DOM tree version. |
515 return !Strategy::parent(*anchorNode()) && m_offset >= EditingStrategy::last
OffsetForEditing(anchorNode()); | 515 return !Strategy::parent(*anchorNode()) && m_offset >= EditingStrategy::last
OffsetForEditing(anchorNode()); |
516 } | 516 } |
517 | 517 |
518 // Whether or not [node, 0] and [node, lastOffsetForEditing(node)] are their own
VisiblePositions. | 518 template <typename Strategy> |
519 // If true, adjacent candidates are visually distinct. | 519 PositionAlgorithm<Strategy> PositionAlgorithm<Strategy>::upstream(EditingBoundar
yCrossingRule rule) const |
520 // FIXME: Disregard nodes with layoutObjects that have no height, as we do in is
Candidate. | |
521 // FIXME: Share code with isCandidate, if possible. | |
522 static bool endsOfNodeAreVisuallyDistinctPositions(Node* node) | |
523 { | 520 { |
524 if (!node || !node->layoutObject()) | 521 return mostForwardCaretPosition(*this, rule); |
525 return false; | |
526 | |
527 if (!node->layoutObject()->isInline()) | |
528 return true; | |
529 | |
530 // Don't include inline tables. | |
531 if (isHTMLTableElement(*node)) | |
532 return false; | |
533 | |
534 // A Marquee elements are moving so we should assume their ends are always | |
535 // visibily distinct. | |
536 if (isHTMLMarqueeElement(*node)) | |
537 return true; | |
538 | |
539 // There is a VisiblePosition inside an empty inline-block container. | |
540 return node->layoutObject()->isReplaced() && canHaveChildrenForEditing(node)
&& toLayoutBox(node->layoutObject())->size().height() != 0 && !node->hasChildre
n(); | |
541 } | 522 } |
542 | 523 |
543 template <typename Strategy> | 524 template <typename Strategy> |
544 static Node* enclosingVisualBoundary(Node* node) | |
545 { | |
546 while (node && !endsOfNodeAreVisuallyDistinctPositions(node)) | |
547 node = Strategy::parent(*node); | |
548 | |
549 return node; | |
550 } | |
551 | |
552 // upstream() and downstream() want to return positions that are either in a | |
553 // text node or at just before a non-text node. This method checks for that. | |
554 template <typename Strategy> | |
555 static bool isStreamer(const PositionIteratorAlgorithm<Strategy>& pos) | |
556 { | |
557 if (!pos.node()) | |
558 return true; | |
559 | |
560 if (isAtomicNode(pos.node())) | |
561 return true; | |
562 | |
563 return pos.atStartOfNode(); | |
564 } | |
565 | |
566 // This function and downstream() are used for moving back and forth between vis
ually equivalent candidates. | |
567 // For example, for the text node "foo bar" where whitespace is collapsible,
there are two candidates | |
568 // that map to the VisiblePosition between 'b' and the space. This function wil
l return the left candidate | |
569 // and downstream() will return the right one. | |
570 // Also, upstream() will return [boundary, 0] for any of the positions from [bou
ndary, 0] to the first candidate | |
571 // in boundary, where endsOfNodeAreVisuallyDistinctPositions(boundary) is true. | |
572 template <typename Strategy> | |
573 PositionAlgorithm<Strategy> PositionAlgorithm<Strategy>::upstream(EditingBoundar
yCrossingRule rule) const | |
574 { | |
575 TRACE_EVENT0("blink", "Position::upstream"); | |
576 | |
577 Node* startNode = anchorNode(); | |
578 if (!startNode) | |
579 return PositionAlgorithm<Strategy>(); | |
580 | |
581 // iterate backward from there, looking for a qualified position | |
582 Node* boundary = enclosingVisualBoundary<Strategy>(startNode); | |
583 // FIXME: PositionIterator should respect Before and After positions. | |
584 PositionIteratorAlgorithm<Strategy> lastVisible(isAfterAnchor() ? editingPos
itionOf(m_anchorNode.get(), Strategy::caretMaxOffset(*m_anchorNode)) : PositionA
lgorithm<Strategy>(*this)); | |
585 PositionIteratorAlgorithm<Strategy> currentPos = lastVisible; | |
586 bool startEditable = startNode->hasEditableStyle(); | |
587 Node* lastNode = startNode; | |
588 bool boundaryCrossed = false; | |
589 for (; !currentPos.atStart(); currentPos.decrement()) { | |
590 Node* currentNode = currentPos.node(); | |
591 // Don't check for an editability change if we haven't moved to a differ
ent node, | |
592 // to avoid the expense of computing hasEditableStyle(). | |
593 if (currentNode != lastNode) { | |
594 // Don't change editability. | |
595 bool currentEditable = currentNode->hasEditableStyle(); | |
596 if (startEditable != currentEditable) { | |
597 if (rule == CannotCrossEditingBoundary) | |
598 break; | |
599 boundaryCrossed = true; | |
600 } | |
601 lastNode = currentNode; | |
602 } | |
603 | |
604 // If we've moved to a position that is visually distinct, return the la
st saved position. There | |
605 // is code below that terminates early if we're *about* to move to a vis
ually distinct position. | |
606 if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode !
= boundary) | |
607 return lastVisible.deprecatedComputePosition(); | |
608 | |
609 // skip position in non-laid out or invisible node | |
610 LayoutObject* layoutObject = currentNode->layoutObject(); | |
611 if (!layoutObject || layoutObject->style()->visibility() != VISIBLE) | |
612 continue; | |
613 | |
614 if (rule == CanCrossEditingBoundary && boundaryCrossed) { | |
615 lastVisible = currentPos; | |
616 break; | |
617 } | |
618 | |
619 // track last visible streamer position | |
620 if (isStreamer<Strategy>(currentPos)) | |
621 lastVisible = currentPos; | |
622 | |
623 // Don't move past a position that is visually distinct. We could rely
on code above to terminate and | |
624 // return lastVisible on the next iteration, but we terminate early to a
void doing a nodeIndex() call. | |
625 if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentPos.at
StartOfNode()) | |
626 return lastVisible.deprecatedComputePosition(); | |
627 | |
628 // Return position after tables and nodes which have content that can be
ignored. | |
629 if (Strategy::editingIgnoresContent(currentNode) || isRenderedHTMLTableE
lement(currentNode)) { | |
630 if (currentPos.atEndOfNode()) | |
631 return afterNode(currentNode); | |
632 continue; | |
633 } | |
634 | |
635 // return current position if it is in laid out text | |
636 if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()
) { | |
637 if (currentNode != startNode) { | |
638 // This assertion fires in layout tests in the case-transform.ht
ml test because | |
639 // of a mix-up between offsets in the text in the DOM tree with
text in the | |
640 // layout tree which can have a different length due to case tra
nsformation. | |
641 // Until we resolve that, disable this so we can run the layout
tests! | |
642 // ASSERT(currentOffset >= layoutObject->caretMaxOffset()); | |
643 return PositionAlgorithm<Strategy>(currentNode, layoutObject->ca
retMaxOffset()); | |
644 } | |
645 | |
646 unsigned textOffset = currentPos.offsetInLeafNode(); | |
647 LayoutText* textLayoutObject = toLayoutText(layoutObject); | |
648 InlineTextBox* lastTextBox = textLayoutObject->lastTextBox(); | |
649 for (InlineTextBox* box = textLayoutObject->firstTextBox(); box; box
= box->nextTextBox()) { | |
650 if (textOffset <= box->start() + box->len()) { | |
651 if (textOffset > box->start()) | |
652 return currentPos.computePosition(); | |
653 continue; | |
654 } | |
655 | |
656 if (box == lastTextBox || textOffset != box->start() + box->len(
) + 1) | |
657 continue; | |
658 | |
659 // The text continues on the next line only if the last text box
is not on this line and | |
660 // none of the boxes on this line have a larger start offset. | |
661 | |
662 bool continuesOnNextLine = true; | |
663 InlineBox* otherBox = box; | |
664 while (continuesOnNextLine) { | |
665 otherBox = otherBox->nextLeafChild(); | |
666 if (!otherBox) | |
667 break; | |
668 if (otherBox == lastTextBox || (otherBox->layoutObject() ==
textLayoutObject && toInlineTextBox(otherBox)->start() > textOffset)) | |
669 continuesOnNextLine = false; | |
670 } | |
671 | |
672 otherBox = box; | |
673 while (continuesOnNextLine) { | |
674 otherBox = otherBox->prevLeafChild(); | |
675 if (!otherBox) | |
676 break; | |
677 if (otherBox == lastTextBox || (otherBox->layoutObject() ==
textLayoutObject && toInlineTextBox(otherBox)->start() > textOffset)) | |
678 continuesOnNextLine = false; | |
679 } | |
680 | |
681 if (continuesOnNextLine) | |
682 return currentPos.computePosition(); | |
683 } | |
684 } | |
685 } | |
686 return lastVisible.deprecatedComputePosition(); | |
687 } | |
688 | |
689 // This function and upstream() are used for moving back and forth between visua
lly equivalent candidates. | |
690 // For example, for the text node "foo bar" where whitespace is collapsible,
there are two candidates | |
691 // that map to the VisiblePosition between 'b' and the space. This function wil
l return the right candidate | |
692 // and upstream() will return the left one. | |
693 // Also, downstream() will return the last position in the last atomic node in b
oundary for all of the positions | |
694 // in boundary after the last candidate, where endsOfNodeAreVisuallyDistinctPosi
tions(boundary). | |
695 // FIXME: This function should never be called when the line box tree is dirty.
See https://bugs.webkit.org/show_bug.cgi?id=97264 | |
696 template <typename Strategy> | |
697 PositionAlgorithm<Strategy> PositionAlgorithm<Strategy>::downstream(EditingBound
aryCrossingRule rule) const | 525 PositionAlgorithm<Strategy> PositionAlgorithm<Strategy>::downstream(EditingBound
aryCrossingRule rule) const |
698 { | 526 { |
699 TRACE_EVENT0("blink", "Position::downstream"); | 527 return mostBackwardCaretPosition(*this, rule); |
700 | |
701 Node* startNode = anchorNode(); | |
702 if (!startNode) | |
703 return PositionAlgorithm<Strategy>(); | |
704 | |
705 // iterate forward from there, looking for a qualified position | |
706 Node* boundary = enclosingVisualBoundary<Strategy>(startNode); | |
707 // FIXME: PositionIterator should respect Before and After positions. | |
708 PositionIteratorAlgorithm<Strategy> lastVisible(isAfterAnchor() ? editingPos
itionOf(m_anchorNode.get(), Strategy::caretMaxOffset(*m_anchorNode)) : PositionA
lgorithm<Strategy>(*this)); | |
709 PositionIteratorAlgorithm<Strategy> currentPos = lastVisible; | |
710 bool startEditable = startNode->hasEditableStyle(); | |
711 Node* lastNode = startNode; | |
712 bool boundaryCrossed = false; | |
713 for (; !currentPos.atEnd(); currentPos.increment()) { | |
714 Node* currentNode = currentPos.node(); | |
715 // Don't check for an editability change if we haven't moved to a differ
ent node, | |
716 // to avoid the expense of computing hasEditableStyle(). | |
717 if (currentNode != lastNode) { | |
718 // Don't change editability. | |
719 bool currentEditable = currentNode->hasEditableStyle(); | |
720 if (startEditable != currentEditable) { | |
721 if (rule == CannotCrossEditingBoundary) | |
722 break; | |
723 boundaryCrossed = true; | |
724 } | |
725 | |
726 lastNode = currentNode; | |
727 } | |
728 | |
729 // stop before going above the body, up into the head | |
730 // return the last visible streamer position | |
731 if (isHTMLBodyElement(*currentNode) && currentPos.atEndOfNode()) | |
732 break; | |
733 | |
734 // Do not move to a visually distinct position. | |
735 if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode !
= boundary) | |
736 return lastVisible.deprecatedComputePosition(); | |
737 // Do not move past a visually disinct position. | |
738 // Note: The first position after the last in a node whose ends are visu
ally distinct | |
739 // positions will be [boundary->parentNode(), originalBlock->nodeIndex()
+ 1]. | |
740 if (boundary && Strategy::parent(*boundary) == currentNode) | |
741 return lastVisible.deprecatedComputePosition(); | |
742 | |
743 // skip position in non-laid out or invisible node | |
744 LayoutObject* layoutObject = currentNode->layoutObject(); | |
745 if (!layoutObject || layoutObject->style()->visibility() != VISIBLE) | |
746 continue; | |
747 | |
748 if (rule == CanCrossEditingBoundary && boundaryCrossed) { | |
749 lastVisible = currentPos; | |
750 break; | |
751 } | |
752 | |
753 // track last visible streamer position | |
754 if (isStreamer<Strategy>(currentPos)) | |
755 lastVisible = currentPos; | |
756 | |
757 // Return position before tables and nodes which have content that can b
e ignored. | |
758 if (Strategy::editingIgnoresContent(currentNode) || isRenderedHTMLTableE
lement(currentNode)) { | |
759 if (currentPos.offsetInLeafNode() <= layoutObject->caretMinOffset()) | |
760 return editingPositionOf(currentNode, layoutObject->caretMinOffs
et()); | |
761 continue; | |
762 } | |
763 | |
764 // return current position if it is in laid out text | |
765 if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()
) { | |
766 if (currentNode != startNode) { | |
767 ASSERT(currentPos.atStartOfNode()); | |
768 return PositionAlgorithm<Strategy>(currentNode, layoutObject->ca
retMinOffset()); | |
769 } | |
770 | |
771 unsigned textOffset = currentPos.offsetInLeafNode(); | |
772 LayoutText* textLayoutObject = toLayoutText(layoutObject); | |
773 InlineTextBox* lastTextBox = textLayoutObject->lastTextBox(); | |
774 for (InlineTextBox* box = textLayoutObject->firstTextBox(); box; box
= box->nextTextBox()) { | |
775 if (textOffset <= box->end()) { | |
776 if (textOffset >= box->start()) | |
777 return currentPos.computePosition(); | |
778 continue; | |
779 } | |
780 | |
781 if (box == lastTextBox || textOffset != box->start() + box->len(
)) | |
782 continue; | |
783 | |
784 // The text continues on the next line only if the last text box
is not on this line and | |
785 // none of the boxes on this line have a larger start offset. | |
786 | |
787 bool continuesOnNextLine = true; | |
788 InlineBox* otherBox = box; | |
789 while (continuesOnNextLine) { | |
790 otherBox = otherBox->nextLeafChild(); | |
791 if (!otherBox) | |
792 break; | |
793 if (otherBox == lastTextBox || (otherBox->layoutObject() ==
textLayoutObject && toInlineTextBox(otherBox)->start() >= textOffset)) | |
794 continuesOnNextLine = false; | |
795 } | |
796 | |
797 otherBox = box; | |
798 while (continuesOnNextLine) { | |
799 otherBox = otherBox->prevLeafChild(); | |
800 if (!otherBox) | |
801 break; | |
802 if (otherBox == lastTextBox || (otherBox->layoutObject() ==
textLayoutObject && toInlineTextBox(otherBox)->start() >= textOffset)) | |
803 continuesOnNextLine = false; | |
804 } | |
805 | |
806 if (continuesOnNextLine) | |
807 return currentPos.computePosition(); | |
808 } | |
809 } | |
810 } | |
811 | |
812 return lastVisible.deprecatedComputePosition(); | |
813 } | 528 } |
814 | 529 |
815 static int boundingBoxLogicalHeight(LayoutObject *o, const IntRect &rect) | 530 static int boundingBoxLogicalHeight(LayoutObject *o, const IntRect &rect) |
816 { | 531 { |
817 return o->style()->isHorizontalWritingMode() ? rect.height() : rect.width(); | 532 return o->style()->isHorizontalWritingMode() ? rect.height() : rect.width(); |
818 } | 533 } |
819 | 534 |
820 // TODO(yosin) We should move |hasRenderedNonAnonymousDescendantsWithHeight| | 535 // TODO(yosin) We should move |hasRenderedNonAnonymousDescendantsWithHeight| |
821 // to "VisibleUnits.cpp" to reduce |LayoutObject| dependency in "Position.cpp" | 536 // to "VisibleUnits.cpp" to reduce |LayoutObject| dependency in "Position.cpp" |
822 bool hasRenderedNonAnonymousDescendantsWithHeight(LayoutObject* layoutObject) | 537 bool hasRenderedNonAnonymousDescendantsWithHeight(LayoutObject* layoutObject) |
(...skipping 537 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1360 | 1075 |
1361 void showTree(const blink::Position* pos) | 1076 void showTree(const blink::Position* pos) |
1362 { | 1077 { |
1363 if (pos) | 1078 if (pos) |
1364 pos->showTreeForThis(); | 1079 pos->showTreeForThis(); |
1365 else | 1080 else |
1366 fprintf(stderr, "Cannot showTree for (nil)\n"); | 1081 fprintf(stderr, "Cannot showTree for (nil)\n"); |
1367 } | 1082 } |
1368 | 1083 |
1369 #endif | 1084 #endif |
OLD | NEW |