void SelectionAdjuster::adjustSelectionInDOMTree(VisibleSelection* selection, const VisibleSelectionInFlatTree& selectionInFlatTree) { if (selectionInFlatTree.isNone()) { *selection = VisibleSelection(); return; } const Position& base = toPositionInDOMTree(selectionInFlatTree.base()); const Position& extent = toPositionInDOMTree(selectionInFlatTree.extent()); if (isCrossingShadowBoundaries(selectionInFlatTree)) { *selection = VisibleSelection(base, extent); return; } const Position& position1 = toPositionInDOMTree(selectionInFlatTree.start()); const Position& position2 = toPositionInDOMTree(selectionInFlatTree.end()); selection->m_base = base; selection->m_extent = extent; selection->m_affinity = selectionInFlatTree.m_affinity; selection->m_isDirectional = selectionInFlatTree.m_isDirectional; selection->m_granularity = selectionInFlatTree.m_granularity; selection->m_hasTrailingWhitespace = selectionInFlatTree.m_hasTrailingWhitespace; selection->m_baseIsFirst = base.isNull() || base.compareTo(extent) <= 0; if (position1.compareTo(position2) <= 0) { selection->m_start = position1; selection->m_end = position2; } else { selection->m_start = position2; selection->m_end = position1; } selection->updateSelectionType(); selection->didChange(); }
TEST_F(DocumentMarkerControllerTest, SetMarkerActiveTest) { setBodyInnerHTML("<b>foo</b>"); Element* bElement = toElement(document().body()->firstChild()); EphemeralRange ephemeralRange = EphemeralRange::rangeOfContents(*bElement); Position startBElement = toPositionInDOMTree(ephemeralRange.startPosition()); Position endBElement = toPositionInDOMTree(ephemeralRange.endPosition()); Range* range = Range::create(document(), startBElement, endBElement); // Try to make active a marker that doesn't exist. EXPECT_FALSE(markerController().setMarkersActive(range, true)); // Add a marker and try it once more. markerController().addTextMatchMarker(range, false); EXPECT_EQ(1u, markerController().markers().size()); EXPECT_TRUE(markerController().setMarkersActive(range, true)); }
// TODO(yosin): We should make |adjustSelectionInDOMTree()| to return // |VisibleSelection| once |VisibleSelection| constructor doesn't call // |validate()|. void SelectionAdjuster::adjustSelectionInDOMTree( VisibleSelection* selection, const VisibleSelectionInFlatTree& selectionInFlatTree) { if (selectionInFlatTree.isNone()) { *selection = VisibleSelection(); return; } const Position& base = toPositionInDOMTree(selectionInFlatTree.base()); const Position& extent = toPositionInDOMTree(selectionInFlatTree.extent()); if (isCrossingShadowBoundaries(selectionInFlatTree)) { DCHECK(base.document()); // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets // needs to be audited. See http://crbug.com/590369 for more details. // This layout update call cannot be hoisted out of the |if|, otherwise it's // going to cause performance regression (http://crbug.com/652301). // TODO(yosin): Implement and apply lazy visible selection validation so // that we don't need to update layout here. base.document()->updateStyleAndLayoutIgnorePendingStylesheets(); *selection = createVisibleSelection( SelectionInDOMTree::Builder().setBaseAndExtent(base, extent).build()); return; } const Position& position1 = toPositionInDOMTree(selectionInFlatTree.start()); const Position& position2 = toPositionInDOMTree(selectionInFlatTree.end()); selection->m_base = base; selection->m_extent = extent; selection->m_affinity = selectionInFlatTree.m_affinity; selection->m_isDirectional = selectionInFlatTree.m_isDirectional; selection->m_granularity = selectionInFlatTree.m_granularity; selection->m_hasTrailingWhitespace = selectionInFlatTree.m_hasTrailingWhitespace; selection->m_baseIsFirst = base.isNull() || base.compareTo(extent) <= 0; if (position1.compareTo(position2) <= 0) { selection->m_start = position1; selection->m_end = position2; } else { selection->m_start = position2; selection->m_end = position1; } selection->updateSelectionType(); }
RenderedPosition::RenderedPosition(const PositionInComposedTree& position, TextAffinity affinity) : RenderedPosition(toPositionInDOMTree(position), affinity) { }
void TextFinder::scopeStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset) { // TODO(dglazkov): The reset/continue cases need to be untangled into two // separate functions. This collation of logic is unnecessary and adds to // overall complexity of the code. if (reset) { // This is a brand new search, so we need to reset everything. // Scoping is just about to begin. m_scopingInProgress = true; // Need to keep the current identifier locally in order to finish the // request in case the frame is detached during the process. m_findRequestIdentifier = identifier; // Clear highlighting for this frame. unmarkAllTextMatches(); // Clear the tickmarks and results cache. clearFindMatchesCache(); // Clear the counters from last operation. m_lastMatchCount = 0; m_nextInvalidateAfter = 0; m_resumeScopingFromRange = nullptr; // The view might be null on detached frames. LocalFrame* frame = ownerFrame().frame(); if (frame && frame->page()) m_frameScoping = true; // Now, defer scoping until later to allow find operation to finish quickly. scopeStringMatchesSoon( identifier, searchText, options, false); // false means just reset, so don't do it again. return; } if (!shouldScopeMatches(searchText, options)) { finishCurrentScopingEffort(identifier); return; } PositionInFlatTree searchStart = PositionInFlatTree::firstPositionInNode(ownerFrame().frame()->document()); PositionInFlatTree searchEnd = PositionInFlatTree::lastPositionInNode(ownerFrame().frame()->document()); DCHECK_EQ(searchStart.document(), searchEnd.document()); if (m_resumeScopingFromRange) { // This is a continuation of a scoping operation that timed out and didn't // complete last time around, so we should start from where we left off. DCHECK(m_resumeScopingFromRange->collapsed()); searchStart = fromPositionInDOMTree<EditingInFlatTreeStrategy>( m_resumeScopingFromRange->endPosition()); if (searchStart.document() != searchEnd.document()) return; } // TODO(dglazkov): The use of updateStyleAndLayoutIgnorePendingStylesheets // needs to be audited. see http://crbug.com/590369 for more details. searchStart.document()->updateStyleAndLayoutIgnorePendingStylesheets(); // This timeout controls how long we scope before releasing control. This // value does not prevent us from running for longer than this, but it is // periodically checked to see if we have exceeded our allocated time. const double maxScopingDuration = 0.1; // seconds int matchCount = 0; bool timedOut = false; double startTime = currentTime(); do { // Find next occurrence of the search string. // FIXME: (http://crbug.com/6818) This WebKit operation may run for longer // than the timeout value, and is not interruptible as it is currently // written. We may need to rewrite it with interruptibility in mind, or // find an alternative. const EphemeralRangeInFlatTree result = findPlainText(EphemeralRangeInFlatTree(searchStart, searchEnd), searchText, options.matchCase ? 0 : CaseInsensitive); if (result.isCollapsed()) { // Not found. break; } Range* resultRange = Range::create( result.document(), toPositionInDOMTree(result.startPosition()), toPositionInDOMTree(result.endPosition())); if (resultRange->collapsed()) { // resultRange will be collapsed if the matched text spans over multiple // TreeScopes. FIXME: Show such matches to users. searchStart = result.endPosition(); continue; } ++matchCount; // Catch a special case where Find found something but doesn't know what // the bounding box for it is. In this case we set the first match we find // as the active rect. IntRect resultBounds = resultRange->boundingBox(); IntRect activeSelectionRect; if (m_locatingActiveRect) { activeSelectionRect = m_activeMatch.get() ? m_activeMatch->boundingBox() : resultBounds; } // If the Find function found a match it will have stored where the // match was found in m_activeSelectionRect on the current frame. If we // find this rect during scoping it means we have found the active // tickmark. bool foundActiveMatch = false; if (m_locatingActiveRect && (activeSelectionRect == resultBounds)) { // We have found the active tickmark frame. m_currentActiveMatchFrame = true; foundActiveMatch = true; // We also know which tickmark is active now. m_activeMatchIndex = matchCount - 1; // To stop looking for the active tickmark, we set this flag. m_locatingActiveRect = false; // Notify browser of new location for the selected rectangle. reportFindInPageSelection( ownerFrame().frameView()->contentsToRootFrame(resultBounds), m_activeMatchIndex + 1, identifier); } ownerFrame().frame()->document()->markers().addTextMatchMarker( EphemeralRange(resultRange), foundActiveMatch); m_findMatchesCache.append( FindMatch(resultRange, m_lastMatchCount + matchCount)); // Set the new start for the search range to be the end of the previous // result range. There is no need to use a VisiblePosition here, // since findPlainText will use a TextIterator to go over the visible // text nodes. searchStart = result.endPosition(); m_resumeScopingFromRange = Range::create( result.document(), toPositionInDOMTree(result.endPosition()), toPositionInDOMTree(result.endPosition())); timedOut = (currentTime() - startTime) >= maxScopingDuration; } while (!timedOut); // Remember what we search for last time, so we can skip searching if more // letters are added to the search string (and last outcome was 0). m_lastSearchString = searchText; if (matchCount > 0) { ownerFrame().frame()->editor().setMarkedTextMatchesAreHighlighted(true); m_lastMatchCount += matchCount; // Let the frame know how many matches we found during this pass. ownerFrame().increaseMatchCount(matchCount, identifier); } if (timedOut) { // If we found anything during this pass, we should redraw. However, we // don't want to spam too much if the page is extremely long, so if we // reach a certain point we start throttling the redraw requests. if (matchCount > 0) invalidateIfNecessary(); // Scoping effort ran out of time, lets ask for another time-slice. scopeStringMatchesSoon(identifier, searchText, options, false); // don't reset. return; // Done for now, resume work later. } finishCurrentScopingEffort(identifier); }