PlainTextRange PlainTextRange::create(const ContainerNode& scope, const Range& range) { if (!range.startContainer()) return PlainTextRange(); // The critical assumption is that this only gets called with ranges that // concentrate on a given area containing the selection root. This is done // because of text fields and textareas. The DOM for those is not // directly in the document DOM, so ensure that the range does not cross a // boundary of one of those. if (range.startContainer() != &scope && !range.startContainer()->isDescendantOf(&scope)) return PlainTextRange(); if (range.endContainer() != scope && !range.endContainer()->isDescendantOf(&scope)) return PlainTextRange(); RefPtrWillBeRawPtr<Range> testRange = Range::create(scope.document(), const_cast<ContainerNode*>(&scope), 0, range.startContainer(), range.startOffset()); ASSERT(testRange->startContainer() == &scope); size_t start = TextIterator::rangeLength(testRange->startPosition(), testRange->endPosition()); testRange->setEnd(range.endContainer(), range.endOffset(), IGNORE_EXCEPTION); ASSERT(testRange->startContainer() == &scope); size_t end = TextIterator::rangeLength(testRange->startPosition(), testRange->endPosition()); return PlainTextRange(start, end); }
void visibleTextQuads(const Range& range, Vector<FloatQuad>& quads, bool useSelectionHeight) { // Range::textQuads includes hidden text, which we don't want. // To work around this, this is a copy of it which skips hidden elements. Node* startContainer = range.startContainer(); Node* endContainer = range.endContainer(); if (!startContainer || !endContainer) return; Node* stopNode = range.pastLastNode(); for (Node* node = range.firstNode(); node != stopNode; node = node->traverseNextNode()) { RenderObject* r = node->renderer(); if (!r || !r->isText()) continue; if (r->style()->visibility() != VISIBLE) continue; RenderText* renderText = toRenderText(r); int startOffset = node == startContainer ? range.startOffset() : 0; int endOffset = node == endContainer ? range.endOffset() : std::numeric_limits<int>::max(); renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight); } }
JSValue* JSRange::getValueProperty(ExecState* exec, int token) const { switch (token) { case StartContainerAttrNum: { ExceptionCode ec = 0; Range* imp = static_cast<Range*>(impl()); KJS::JSValue* result = toJS(exec, WTF::getPtr(imp->startContainer(ec))); setDOMException(exec, ec); return result; } case StartOffsetAttrNum: { ExceptionCode ec = 0; Range* imp = static_cast<Range*>(impl()); KJS::JSValue* result = jsNumber(imp->startOffset(ec)); setDOMException(exec, ec); return result; } case EndContainerAttrNum: { ExceptionCode ec = 0; Range* imp = static_cast<Range*>(impl()); KJS::JSValue* result = toJS(exec, WTF::getPtr(imp->endContainer(ec))); setDOMException(exec, ec); return result; } case EndOffsetAttrNum: { ExceptionCode ec = 0; Range* imp = static_cast<Range*>(impl()); KJS::JSValue* result = jsNumber(imp->endOffset(ec)); setDOMException(exec, ec); return result; } case CollapsedAttrNum: { ExceptionCode ec = 0; Range* imp = static_cast<Range*>(impl()); KJS::JSValue* result = jsBoolean(imp->collapsed(ec)); setDOMException(exec, ec); return result; } case CommonAncestorContainerAttrNum: { ExceptionCode ec = 0; Range* imp = static_cast<Range*>(impl()); KJS::JSValue* result = toJS(exec, WTF::getPtr(imp->commonAncestorContainer(ec))); setDOMException(exec, ec); return result; } case ConstructorAttrNum: return getConstructor(exec); } return 0; }
TEST_F(TextFinderTest, FindTextJavaScriptUpdatesDOM) { document().body()->setInnerHTML("<b>XXXXFindMeYYYY</b><i></i>"); document().updateStyleAndLayout(); int identifier = 0; WebString searchText(String("FindMe")); WebFindOptions findOptions; // Default. bool wrapWithinFrame = true; bool activeNow; textFinder().resetMatchCount(); textFinder().startScopingStringMatches(identifier, searchText, findOptions); while (textFinder().scopingInProgress()) runPendingTasks(); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, &activeNow)); EXPECT_TRUE(activeNow); ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, &activeNow)); EXPECT_TRUE(activeNow); // Add new text to DOM and try FindNext. Element* iElement = toElement(document().body()->lastChild()); ASSERT_TRUE(iElement); iElement->setInnerHTML("ZZFindMe"); document().updateStyleAndLayout(); ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, &activeNow)); Range* activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_FALSE(activeNow); EXPECT_EQ(2, activeMatch->startOffset()); EXPECT_EQ(8, activeMatch->endOffset()); // Restart full search and check that added text is found. findOptions.findNext = false; textFinder().resetMatchCount(); textFinder().cancelPendingScopingEffort(); textFinder().startScopingStringMatches(identifier, searchText, findOptions); while (textFinder().scopingInProgress()) runPendingTasks(); EXPECT_EQ(2, textFinder().totalMatchCount()); WebVector<WebFloatRect> matchRects; textFinder().findMatchRects(matchRects); ASSERT_EQ(2u, matchRects.size()); Node* textInBElement = document().body()->firstChild()->firstChild(); Node* textInIElement = document().body()->lastChild()->firstChild(); EXPECT_EQ(findInPageRect(textInBElement, 4, textInBElement, 10), matchRects[0]); EXPECT_EQ(findInPageRect(textInIElement, 2, textInIElement, 8), matchRects[1]); }
bool operator==(const Range &a, const Range &b) { RangeImpl *ai = a.handle(); RangeImpl *bi = b.handle(); if (ai == bi) return true; if (!ai || !bi) return false; bool ad = ai->isDetached(); bool bd = bi->isDetached(); if (ad && bd) return true; if (ad || bd) return false; return a.startContainer() == b.startContainer() && a.endContainer() == b.endContainer() && a.startOffset() == b.startOffset() && a.endOffset() == b.endOffset(); }
TEST_F(InputMethodControllerTest, SetCompositionFromExistingText) { Element* div = insertHTMLElement( "<div id='sample' contenteditable='true'>hello world</div>", "sample"); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0)); controller().setCompositionFromExistingText(underlines, 0, 5); Range* range = controller().compositionRange(); EXPECT_EQ(0, range->startOffset()); EXPECT_EQ(5, range->endOffset()); PlainTextRange plainTextRange(PlainTextRange::create(*div, *range)); EXPECT_EQ(0u, plainTextRange.start()); EXPECT_EQ(5u, plainTextRange.end()); }
TEST_F(InputMethodControllerTest, SetCompositionFromExistingTextWithCollapsedWhiteSpace) { // Creates a div with one leading new line char. The new line char is hidden // from the user and IME, but is visible to InputMethodController. Element* div = insertHTMLElement( "<div id='sample' contenteditable='true'>\nhello world</div>", "sample"); Vector<CompositionUnderline> underlines; underlines.append(CompositionUnderline(0, 5, Color(255, 0, 0), false, 0)); controller().setCompositionFromExistingText(underlines, 0, 5); Range* range = controller().compositionRange(); EXPECT_EQ(1, range->startOffset()); EXPECT_EQ(6, range->endOffset()); PlainTextRange plainTextRange(PlainTextRange::create(*div, *range)); EXPECT_EQ(0u, plainTextRange.start()); EXPECT_EQ(5u, plainTextRange.end()); }
VisiblePosition endVisiblePosition(const Range &r, EAffinity affinity) { return VisiblePosition(r.endContainer().handle(), r.endOffset(), affinity); }
void DOMSelection::addRange(Range* newRange) { if (!m_frame) return; // FIXME: Should we throw DOMException for error cases below? if (!newRange) { addConsoleError("The given range is null."); return; } if (!newRange->startContainer()) { addConsoleError("The given range has no container. Perhaps 'detach()' has been invoked on it?"); return; } FrameSelection& selection = m_frame->selection(); if (selection.isNone()) { selection.setSelectedRange(newRange, VP_DEFAULT_AFFINITY); return; } RefPtrWillBeRawPtr<Range> originalRange = selection.firstRange(); if (originalRange->startContainer()->document() != newRange->startContainer()->document()) { addConsoleError("The given range does not belong to the current selection's document."); return; } if (originalRange->startContainer()->treeScope() != newRange->startContainer()->treeScope()) { addConsoleError("The given range and the current selection belong to two different document fragments."); return; } if (originalRange->compareBoundaryPoints(Range::START_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0 || newRange->compareBoundaryPoints(Range::START_TO_END, originalRange.get(), ASSERT_NO_EXCEPTION) < 0) { addConsoleError("Discontiguous selection is not supported."); return; } // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; other browsers supporting discontiguous // selection (obviously) keep each Range added and return it in getRangeAt(). But it's unclear if we can really // do the same, since we don't support discontiguous selection. Further discussions at // <https://code.google.com/p/chromium/issues/detail?id=353069>. Range* start = originalRange->compareBoundaryPoints(Range::START_TO_START, newRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange.get() : newRange; Range* end = originalRange->compareBoundaryPoints(Range::END_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange.get(); RefPtrWillBeRawPtr<Range> merged = Range::create(originalRange->startContainer()->document(), start->startContainer(), start->startOffset(), end->endContainer(), end->endOffset()); EAffinity affinity = selection.selection().affinity(); selection.setSelectedRange(merged.get(), affinity); }
Position endPosition(const Range &r) { if (r.isNull() || r.isDetached()) return Position(); return Position(r.endContainer().handle(), r.endOffset()); }
TEST_F(StaticRangeTest, SplitTextNodeRangeOutsideText) { document().body()->setInnerHTML("<span id=\"outer\">0<span id=\"inner-left\">1</span>SPLITME<span id=\"inner-right\">2</span>3</span>", ASSERT_NO_EXCEPTION); Element* outer = document().getElementById(AtomicString::fromUTF8("outer")); Element* innerLeft = document().getElementById(AtomicString::fromUTF8("inner-left")); Element* innerRight = document().getElementById(AtomicString::fromUTF8("inner-right")); Text* oldText = toText(outer->childNodes()->item(2)); StaticRange* staticRangeOuterOutside = StaticRange::create(document(), outer, 0, outer, 5); StaticRange* staticRangeOuterInside = StaticRange::create(document(), outer, 1, outer, 4); StaticRange* staticRangeOuterSurroundingText = StaticRange::create(document(), outer, 2, outer, 3); StaticRange* staticRangeInnerLeft = StaticRange::create(document(), innerLeft, 0, innerLeft, 1); StaticRange* staticRangeInnerRight = StaticRange::create(document(), innerRight, 0, innerRight, 1); StaticRange* staticRangeFromTextToMiddleOfElement = StaticRange::create(document(), oldText, 6, outer, 3); Range* rangeOuterOutside = staticRangeOuterOutside->toRange(ASSERT_NO_EXCEPTION); Range* rangeOuterInside = staticRangeOuterInside->toRange(ASSERT_NO_EXCEPTION); Range* rangeOuterSurroundingText = staticRangeOuterSurroundingText->toRange(ASSERT_NO_EXCEPTION); Range* rangeInnerLeft = staticRangeInnerLeft->toRange(ASSERT_NO_EXCEPTION); Range* rangeInnerRight = staticRangeInnerRight->toRange(ASSERT_NO_EXCEPTION); Range* rangeFromTextToMiddleOfElement = staticRangeFromTextToMiddleOfElement->toRange(ASSERT_NO_EXCEPTION); oldText->splitText(3, ASSERT_NO_EXCEPTION); Text* newText = toText(oldText->nextSibling()); // Range should mutate. EXPECT_TRUE(rangeOuterOutside->boundaryPointsValid()); EXPECT_EQ(outer, rangeOuterOutside->startContainer()); EXPECT_EQ(0, rangeOuterOutside->startOffset()); EXPECT_EQ(outer, rangeOuterOutside->endContainer()); EXPECT_EQ(6, rangeOuterOutside->endOffset()); // Increased by 1 since a new node is inserted. EXPECT_TRUE(rangeOuterInside->boundaryPointsValid()); EXPECT_EQ(outer, rangeOuterInside->startContainer()); EXPECT_EQ(1, rangeOuterInside->startOffset()); EXPECT_EQ(outer, rangeOuterInside->endContainer()); EXPECT_EQ(5, rangeOuterInside->endOffset()); EXPECT_TRUE(rangeOuterSurroundingText->boundaryPointsValid()); EXPECT_EQ(outer, rangeOuterSurroundingText->startContainer()); EXPECT_EQ(2, rangeOuterSurroundingText->startOffset()); EXPECT_EQ(outer, rangeOuterSurroundingText->endContainer()); EXPECT_EQ(4, rangeOuterSurroundingText->endOffset()); EXPECT_TRUE(rangeInnerLeft->boundaryPointsValid()); EXPECT_EQ(innerLeft, rangeInnerLeft->startContainer()); EXPECT_EQ(0, rangeInnerLeft->startOffset()); EXPECT_EQ(innerLeft, rangeInnerLeft->endContainer()); EXPECT_EQ(1, rangeInnerLeft->endOffset()); EXPECT_TRUE(rangeInnerRight->boundaryPointsValid()); EXPECT_EQ(innerRight, rangeInnerRight->startContainer()); EXPECT_EQ(0, rangeInnerRight->startOffset()); EXPECT_EQ(innerRight, rangeInnerRight->endContainer()); EXPECT_EQ(1, rangeInnerRight->endOffset()); EXPECT_TRUE(rangeFromTextToMiddleOfElement->boundaryPointsValid()); EXPECT_EQ(newText, rangeFromTextToMiddleOfElement->startContainer()); EXPECT_EQ(3, rangeFromTextToMiddleOfElement->startOffset()); EXPECT_EQ(outer, rangeFromTextToMiddleOfElement->endContainer()); EXPECT_EQ(4, rangeFromTextToMiddleOfElement->endOffset()); // StaticRange shouldn't mutate. EXPECT_EQ(outer, staticRangeOuterOutside->startContainer()); EXPECT_EQ(0, staticRangeOuterOutside->startOffset()); EXPECT_EQ(outer, staticRangeOuterOutside->endContainer()); EXPECT_EQ(5, staticRangeOuterOutside->endOffset()); EXPECT_EQ(outer, staticRangeOuterInside->startContainer()); EXPECT_EQ(1, staticRangeOuterInside->startOffset()); EXPECT_EQ(outer, staticRangeOuterInside->endContainer()); EXPECT_EQ(4, staticRangeOuterInside->endOffset()); EXPECT_EQ(outer, staticRangeOuterSurroundingText->startContainer()); EXPECT_EQ(2, staticRangeOuterSurroundingText->startOffset()); EXPECT_EQ(outer, staticRangeOuterSurroundingText->endContainer()); EXPECT_EQ(3, staticRangeOuterSurroundingText->endOffset()); EXPECT_EQ(innerLeft, staticRangeInnerLeft->startContainer()); EXPECT_EQ(0, staticRangeInnerLeft->startOffset()); EXPECT_EQ(innerLeft, staticRangeInnerLeft->endContainer()); EXPECT_EQ(1, staticRangeInnerLeft->endOffset()); EXPECT_EQ(innerRight, staticRangeInnerRight->startContainer()); EXPECT_EQ(0, staticRangeInnerRight->startOffset()); EXPECT_EQ(innerRight, staticRangeInnerRight->endContainer()); EXPECT_EQ(1, staticRangeInnerRight->endOffset()); EXPECT_EQ(oldText, staticRangeFromTextToMiddleOfElement->startContainer()); EXPECT_EQ(6, staticRangeFromTextToMiddleOfElement->startOffset()); EXPECT_EQ(outer, staticRangeFromTextToMiddleOfElement->endContainer()); EXPECT_EQ(3, staticRangeFromTextToMiddleOfElement->endOffset()); }
TEST_F(TextFinderTest, FindTextSimple) { document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION); Node* textNode = document().body()->firstChild(); int identifier = 0; WebString searchText(String("FindMe")); WebFindOptions findOptions; // Default. bool wrapWithinFrame = true; WebRect* selectionRect = nullptr; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); Range* activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(4, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(10, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(14, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(20, activeMatch->endOffset()); // Should wrap to the first match. ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(4, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(10, activeMatch->endOffset()); // Search in the reverse order. identifier = 1; findOptions = WebFindOptions(); findOptions.forward = false; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(14, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(20, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(4, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(10, activeMatch->endOffset()); // Wrap to the first match (last occurence in the document). ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(14, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(20, activeMatch->endOffset()); }
TEST_F(TextFinderTest, FindTextInShadowDOM) { document().body()->setInnerHTML("<b>FOO</b><i>foo</i>", ASSERT_NO_EXCEPTION); RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = document().body()->createShadowRootInternal(ShadowRootType::V0, ASSERT_NO_EXCEPTION); shadowRoot->setInnerHTML("<content select=\"i\"></content><u>Foo</u><content></content>", ASSERT_NO_EXCEPTION); Node* textInBElement = document().body()->firstChild()->firstChild(); Node* textInIElement = document().body()->lastChild()->firstChild(); Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild(); int identifier = 0; WebString searchText(String("foo")); WebFindOptions findOptions; // Default. bool wrapWithinFrame = true; WebRect* selectionRect = nullptr; // TextIterator currently returns the matches in the composed treeorder, so // in this case the matches will be returned in the order of // <i> -> <u> -> <b>. ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); Range* activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInIElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInIElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInUElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInUElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInBElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInBElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); // Should wrap to the first match. ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInIElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInIElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); // Fresh search in the reverse order. identifier = 1; findOptions = WebFindOptions(); findOptions.forward = false; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInBElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInBElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInUElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInUElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInIElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInIElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); // And wrap. ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInBElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInBElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); }