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