NS_IMETHODIMP nsTypeAheadFind::IsRangeVisible(nsIDOMRange *aRange, bool aMustBeInViewPort, bool *aResult) { // Jump through hoops to extract the docShell from the range. nsCOMPtr<nsIDOMNode> node; aRange->GetStartContainer(getter_AddRefs(node)); nsCOMPtr<nsIDOMDocument> document; node->GetOwnerDocument(getter_AddRefs(document)); nsCOMPtr<nsIDOMWindow> window; document->GetDefaultView(getter_AddRefs(window)); nsCOMPtr<nsIWebNavigation> navNav (do_GetInterface(window)); nsCOMPtr<nsIDocShell> docShell (do_GetInterface(navNav)); // Set up the arguments needed to check if a range is visible. nsCOMPtr<nsIPresShell> presShell (docShell->GetPresShell()); nsRefPtr<nsPresContext> presContext = presShell->GetPresContext(); nsCOMPtr<nsIDOMRange> startPointRange = new nsRange(presShell->GetDocument()); *aResult = IsRangeVisible(presShell, presContext, aRange, aMustBeInViewPort, false, getter_AddRefs(startPointRange), nullptr); return NS_OK; }
nsresult nsTypeAheadFind::GetSearchContainers(nsISupports *aContainer, nsISelectionController *aSelectionController, bool aIsFirstVisiblePreferred, bool aFindPrev, nsIPresShell **aPresShell, nsPresContext **aPresContext) { NS_ENSURE_ARG_POINTER(aContainer); NS_ENSURE_ARG_POINTER(aPresShell); NS_ENSURE_ARG_POINTER(aPresContext); *aPresShell = nullptr; *aPresContext = nullptr; nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer)); if (!docShell) return NS_ERROR_FAILURE; nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); nsRefPtr<nsPresContext> presContext; docShell->GetPresContext(getter_AddRefs(presContext)); if (!presShell || !presContext) return NS_ERROR_FAILURE; nsIDocument* doc = presShell->GetDocument(); if (!doc) return NS_ERROR_FAILURE; nsCOMPtr<nsIContent> rootContent; nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(doc)); if (htmlDoc) { nsCOMPtr<nsIDOMHTMLElement> bodyEl; htmlDoc->GetBody(getter_AddRefs(bodyEl)); rootContent = do_QueryInterface(bodyEl); } if (!rootContent) rootContent = doc->GetRootElement(); nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootContent)); if (!rootNode) return NS_ERROR_FAILURE; if (!mSearchRange) { mSearchRange = new nsRange(doc); } nsCOMPtr<nsIDOMNode> searchRootNode = rootNode; // Hack for XMLPrettyPrinter. nsFind can't handle complex anonymous content. // If the root node has an XBL binding then there's not much we can do in // in general, but we can try searching the binding's first child, which // in the case of XMLPrettyPrinter contains the visible pretty-printed // content. nsXBLBinding* binding = rootContent->GetXBLBinding(); if (binding) { nsIContent* anonContent = binding->GetAnonymousContent(); if (anonContent) { searchRootNode = do_QueryInterface(anonContent->GetFirstChild()); } } mSearchRange->SelectNodeContents(searchRootNode); if (!mStartPointRange) { mStartPointRange = new nsRange(doc); } mStartPointRange->SetStart(searchRootNode, 0); mStartPointRange->Collapse(true); // collapse to start if (!mEndPointRange) { mEndPointRange = new nsRange(doc); } nsCOMPtr<nsINode> searchRootTmp = do_QueryInterface(searchRootNode); mEndPointRange->SetEnd(searchRootNode, searchRootTmp->Length()); mEndPointRange->Collapse(false); // collapse to end // Consider current selection as null if // it's not in the currently focused document nsCOMPtr<nsIDOMRange> currentSelectionRange; nsCOMPtr<nsIPresShell> selectionPresShell = GetPresShell(); if (aSelectionController && selectionPresShell && selectionPresShell == presShell) { nsCOMPtr<nsISelection> selection; aSelectionController->GetSelection( nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (selection) selection->GetRangeAt(0, getter_AddRefs(currentSelectionRange)); } if (!currentSelectionRange) { // Ensure visible range, move forward if necessary // This uses ignores the return value, but usese the side effect of // IsRangeVisible. It returns the first visible range after searchRange IsRangeVisible(presShell, presContext, mSearchRange, aIsFirstVisiblePreferred, true, getter_AddRefs(mStartPointRange), nullptr); } else { int32_t startOffset; nsCOMPtr<nsIDOMNode> startNode; if (aFindPrev) { currentSelectionRange->GetStartContainer(getter_AddRefs(startNode)); currentSelectionRange->GetStartOffset(&startOffset); } else { currentSelectionRange->GetEndContainer(getter_AddRefs(startNode)); currentSelectionRange->GetEndOffset(&startOffset); } if (!startNode) startNode = rootNode; // We need to set the start point this way, other methods haven't worked mStartPointRange->SelectNode(startNode); mStartPointRange->SetStart(startNode, startOffset); } mStartPointRange->Collapse(true); // collapse to start presShell.forget(aPresShell); presContext.forget(aPresContext); return NS_OK; }
nsresult nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly, bool aIsFirstVisiblePreferred, bool aFindPrev, uint16_t* aResult) { *aResult = FIND_NOTFOUND; mFoundLink = nullptr; mFoundEditable = nullptr; mFoundRange = nullptr; mCurrentWindow = nullptr; nsCOMPtr<nsIPresShell> startingPresShell (GetPresShell()); if (!startingPresShell) { nsCOMPtr<nsIDocShell> ds = do_QueryReferent(mDocShell); NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE); startingPresShell = ds->GetPresShell(); mPresShell = do_GetWeakReference(startingPresShell); } nsCOMPtr<nsIPresShell> presShell(aPresShell); if (!presShell) { presShell = startingPresShell; // this is the current document if (!presShell) return NS_ERROR_FAILURE; } nsRefPtr<nsPresContext> presContext = presShell->GetPresContext(); if (!presContext) return NS_ERROR_FAILURE; nsCOMPtr<nsISelection> selection; nsCOMPtr<nsISelectionController> selectionController = do_QueryReferent(mSelectionController); if (!selectionController) { GetSelection(presShell, getter_AddRefs(selectionController), getter_AddRefs(selection)); // cache for reuse mSelectionController = do_GetWeakReference(selectionController); } else { selectionController->GetSelection( nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); } nsCOMPtr<nsIDocShell> startingDocShell(presContext->GetDocShell()); NS_ASSERTION(startingDocShell, "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]"); if (!startingDocShell) return NS_ERROR_FAILURE; nsCOMPtr<nsIDocShellTreeItem> rootContentTreeItem; nsCOMPtr<nsIDocShell> currentDocShell; startingDocShell->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem)); nsCOMPtr<nsIDocShell> rootContentDocShell = do_QueryInterface(rootContentTreeItem); if (!rootContentDocShell) return NS_ERROR_FAILURE; nsCOMPtr<nsISimpleEnumerator> docShellEnumerator; rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, nsIDocShell::ENUMERATE_FORWARDS, getter_AddRefs(docShellEnumerator)); // Default: can start at the current document nsCOMPtr<nsISupports> currentContainer = do_QueryInterface(rootContentDocShell); // Iterate up to current shell, if there's more than 1 that we're // dealing with bool hasMoreDocShells; while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) && hasMoreDocShells) { docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); currentDocShell = do_QueryInterface(currentContainer); if (!currentDocShell || currentDocShell == startingDocShell || aIsFirstVisiblePreferred) break; } // ------------ Get ranges ready ---------------- nsCOMPtr<nsIDOMRange> returnRange; nsCOMPtr<nsIPresShell> focusedPS; if (NS_FAILED(GetSearchContainers(currentContainer, (!aIsFirstVisiblePreferred || mStartFindRange) ? selectionController.get() : nullptr, aIsFirstVisiblePreferred, aFindPrev, getter_AddRefs(presShell), getter_AddRefs(presContext)))) { return NS_ERROR_FAILURE; } int16_t rangeCompareResult = 0; if (!mStartPointRange) { mStartPointRange = new nsRange(presShell->GetDocument()); } mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, mSearchRange, &rangeCompareResult); // No need to wrap find in doc if starting at beginning bool hasWrapped = (rangeCompareResult < 0); if (mTypeAheadBuffer.IsEmpty() || !EnsureFind()) return NS_ERROR_FAILURE; mFind->SetFindBackwards(aFindPrev); while (true) { // ----- Outer while loop: go through all docs ----- while (true) { // === Inner while loop: go through a single doc === mFind->Find(mTypeAheadBuffer.get(), mSearchRange, mStartPointRange, mEndPointRange, getter_AddRefs(returnRange)); if (!returnRange) break; // Nothing found in this doc, go to outer loop (try next doc) // ------- Test resulting found range for success conditions ------ bool isInsideLink = false, isStartingLink = false; if (aIsLinksOnly) { // Don't check if inside link when searching all text RangeStartsInsideLink(returnRange, presShell, &isInsideLink, &isStartingLink); } bool usesIndependentSelection; if (!IsRangeVisible(presShell, presContext, returnRange, aIsFirstVisiblePreferred, false, getter_AddRefs(mStartPointRange), &usesIndependentSelection) || (aIsLinksOnly && !isInsideLink) || (mStartLinksOnlyPref && aIsLinksOnly && !isStartingLink)) { // ------ Failure ------ // At this point mStartPointRange got updated to the first // visible range in the viewport. We _may_ be able to just // start there, if it's not taking us in the wrong direction. if (aFindPrev) { // We can continue at the end of mStartPointRange if its end is before // the start of returnRange or coincides with it. Otherwise, we need // to continue at the start of returnRange. int16_t compareResult; nsresult rv = mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_END, returnRange, &compareResult); if (NS_SUCCEEDED(rv) && compareResult <= 0) { // OK to start at the end of mStartPointRange mStartPointRange->Collapse(false); } else { // Start at the beginning of returnRange returnRange->CloneRange(getter_AddRefs(mStartPointRange)); mStartPointRange->Collapse(true); } } else { // We can continue at the start of mStartPointRange if its start is // after the end of returnRange or coincides with it. Otherwise, we // need to continue at the end of returnRange. int16_t compareResult; nsresult rv = mStartPointRange->CompareBoundaryPoints(nsIDOMRange::END_TO_START, returnRange, &compareResult); if (NS_SUCCEEDED(rv) && compareResult >= 0) { // OK to start at the start of mStartPointRange mStartPointRange->Collapse(true); } else { // Start at the end of returnRange returnRange->CloneRange(getter_AddRefs(mStartPointRange)); mStartPointRange->Collapse(false); } } continue; } mFoundRange = returnRange; // ------ Success! ------- // Hide old selection (new one may be on a different controller) if (selection) { selection->CollapseToStart(); SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON); } // Make sure new document is selected if (presShell != startingPresShell) { // We are in a new document (because of frames/iframes) mPresShell = do_GetWeakReference(presShell); } nsCOMPtr<nsIDocument> document = do_QueryInterface(presShell->GetDocument()); NS_ASSERTION(document, "Wow, presShell doesn't have document!"); if (!document) return NS_ERROR_UNEXPECTED; nsCOMPtr<nsPIDOMWindow> window = document->GetInnerWindow(); NS_ASSERTION(window, "document has no window"); if (!window) return NS_ERROR_UNEXPECTED; nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); if (usesIndependentSelection) { /* If a search result is found inside an editable element, we'll focus * the element only if focus is in our content window, i.e. * |if (focusedWindow.top == ourWindow.top)| */ bool shouldFocusEditableElement = false; if (fm) { nsCOMPtr<nsIDOMWindow> focusedWindow; nsresult rv = fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsPIDOMWindow> fwPI(do_QueryInterface(focusedWindow, &rv)); if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIDocShellTreeItem> fwTreeItem (do_QueryInterface(fwPI->GetDocShell(), &rv)); if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIDocShellTreeItem> fwRootTreeItem; rv = fwTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(fwRootTreeItem)); if (NS_SUCCEEDED(rv) && fwRootTreeItem == rootContentTreeItem) shouldFocusEditableElement = true; } } } } // We may be inside an editable element, and therefore the selection // may be controlled by a different selection controller. Walk up the // chain of parent nodes to see if we find one. nsCOMPtr<nsIDOMNode> node; returnRange->GetStartContainer(getter_AddRefs(node)); while (node) { nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(node); if (editable) { // Inside an editable element. Get the correct selection // controller and selection. nsCOMPtr<nsIEditor> editor; editable->GetEditor(getter_AddRefs(editor)); NS_ASSERTION(editor, "Editable element has no editor!"); if (!editor) { break; } editor->GetSelectionController( getter_AddRefs(selectionController)); if (selectionController) { selectionController->GetSelection( nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); } mFoundEditable = do_QueryInterface(node); if (!shouldFocusEditableElement) break; // Otherwise move focus/caret to editable element if (fm) fm->SetFocus(mFoundEditable, 0); break; } nsIDOMNode* tmp = node; tmp->GetParentNode(getter_AddRefs(node)); } // If we reach here without setting mFoundEditable, then something // besides editable elements can cause us to have an independent // selection controller. I don't know whether this is possible. // Currently, we simply fall back to grabbing the document's selection // controller in this case. Perhaps we should reject this find match // and search again. NS_ASSERTION(mFoundEditable, "Independent selection controller on " "non-editable element!"); } if (!mFoundEditable) { // Not using a separate selection controller, so just get the // document's controller and selection. GetSelection(presShell, getter_AddRefs(selectionController), getter_AddRefs(selection)); } mSelectionController = do_GetWeakReference(selectionController); // Select the found text if (selection) { selection->RemoveAllRanges(); selection->AddRange(returnRange); } if (!mFoundEditable && fm) { nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(window); fm->MoveFocus(win, nullptr, nsIFocusManager::MOVEFOCUS_CARET, nsIFocusManager::FLAG_NOSCROLL | nsIFocusManager::FLAG_NOSWITCHFRAME, getter_AddRefs(mFoundLink)); } // Change selection color to ATTENTION and scroll to it. Careful: we // must wait until after we goof with focus above before changing to // ATTENTION, or when we MoveFocus() and the selection is not on a // link, we'll blur, which will lose the ATTENTION. if (selectionController) { // Beware! This may flush notifications via synchronous // ScrollSelectionIntoView. SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ATTENTION); selectionController->ScrollSelectionIntoView( nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_WHOLE_SELECTION, nsISelectionController::SCROLL_CENTER_VERTICALLY | nsISelectionController::SCROLL_SYNCHRONOUS); } mCurrentWindow = window; *aResult = hasWrapped ? FIND_WRAPPED : FIND_FOUND; return NS_OK; } // ======= end-inner-while (go through a single document) ========== // ---------- Nothing found yet, try next document ------------- bool hasTriedFirstDoc = false; do { // ==== Second inner loop - get another while ==== if (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) && hasMoreDocShells) { docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); NS_ASSERTION(currentContainer, "HasMoreElements lied to us!"); currentDocShell = do_QueryInterface(currentContainer); if (currentDocShell) break; } else if (hasTriedFirstDoc) // Avoid potential infinite loop return NS_ERROR_FAILURE; // No content doc shells // Reached last doc shell, loop around back to first doc shell rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, nsIDocShell::ENUMERATE_FORWARDS, getter_AddRefs(docShellEnumerator)); hasTriedFirstDoc = true; } while (docShellEnumerator); // ==== end second inner while === bool continueLoop = false; if (currentDocShell != startingDocShell) continueLoop = true; // Try next document else if (!hasWrapped || aIsFirstVisiblePreferred) { // Finished searching through docshells: // If aFirstVisiblePreferred == true, we may need to go through all // docshells twice -once to look for visible matches, the second time // for any match aIsFirstVisiblePreferred = false; hasWrapped = true; continueLoop = true; // Go through all docs again } if (continueLoop) { if (NS_FAILED(GetSearchContainers(currentContainer, nullptr, aIsFirstVisiblePreferred, aFindPrev, getter_AddRefs(presShell), getter_AddRefs(presContext)))) { continue; } if (aFindPrev) { // Reverse mode: swap start and end points, so that we start // at end of document and go to beginning nsCOMPtr<nsIDOMRange> tempRange; mStartPointRange->CloneRange(getter_AddRefs(tempRange)); if (!mEndPointRange) { mEndPointRange = new nsRange(presShell->GetDocument()); } mStartPointRange = mEndPointRange; mEndPointRange = tempRange; } continue; } // ------------- Failed -------------- break; } // end-outer-while: go through all docs return NS_ERROR_FAILURE; }
nsresult nsTypeAheadFind::GetSearchContainers(nsISupports *aContainer, nsISelectionController *aSelectionController, PRBool aIsFirstVisiblePreferred, PRBool aFindPrev, nsIPresShell **aPresShell, nsPresContext **aPresContext) { NS_ENSURE_ARG_POINTER(aContainer); NS_ENSURE_ARG_POINTER(aPresShell); NS_ENSURE_ARG_POINTER(aPresContext); *aPresShell = nsnull; *aPresContext = nsnull; nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer)); if (!docShell) return NS_ERROR_FAILURE; nsCOMPtr<nsIPresShell> presShell; docShell->GetPresShell(getter_AddRefs(presShell)); nsRefPtr<nsPresContext> presContext; docShell->GetPresContext(getter_AddRefs(presContext)); if (!presShell || !presContext) return NS_ERROR_FAILURE; nsIDocument* doc = presShell->GetDocument(); if (!doc) return NS_ERROR_FAILURE; nsCOMPtr<nsIContent> rootContent; nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(doc)); if (htmlDoc) { nsCOMPtr<nsIDOMHTMLElement> bodyEl; htmlDoc->GetBody(getter_AddRefs(bodyEl)); rootContent = do_QueryInterface(bodyEl); } if (!rootContent) rootContent = doc->GetRootElement(); nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootContent)); if (!rootNode) return NS_ERROR_FAILURE; PRUint32 childCount = rootContent->GetChildCount(); mSearchRange->SelectNodeContents(rootNode); mEndPointRange->SetEnd(rootNode, childCount); mEndPointRange->Collapse(PR_FALSE); // collapse to end // Consider current selection as null if // it's not in the currently focused document nsCOMPtr<nsIDOMRange> currentSelectionRange; nsCOMPtr<nsIPresShell> selectionPresShell = GetPresShell(); if (aSelectionController && selectionPresShell && selectionPresShell == presShell) { nsCOMPtr<nsISelection> selection; aSelectionController->GetSelection( nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (selection) selection->GetRangeAt(0, getter_AddRefs(currentSelectionRange)); } if (!currentSelectionRange) { // Ensure visible range, move forward if necessary // This uses ignores the return value, but usese the side effect of // IsRangeVisible. It returns the first visible range after searchRange IsRangeVisible(presShell, presContext, mSearchRange, aIsFirstVisiblePreferred, PR_TRUE, getter_AddRefs(mStartPointRange), nsnull); } else { PRInt32 startOffset; nsCOMPtr<nsIDOMNode> startNode; if (aFindPrev) { currentSelectionRange->GetStartContainer(getter_AddRefs(startNode)); currentSelectionRange->GetStartOffset(&startOffset); } else { currentSelectionRange->GetEndContainer(getter_AddRefs(startNode)); currentSelectionRange->GetEndOffset(&startOffset); } if (!startNode) startNode = rootNode; // We need to set the start point this way, other methods haven't worked mStartPointRange->SelectNode(startNode); mStartPointRange->SetStart(startNode, startOffset); } mStartPointRange->Collapse(PR_TRUE); // collapse to start *aPresShell = presShell; NS_ADDREF(*aPresShell); *aPresContext = presContext; NS_ADDREF(*aPresContext); return NS_OK; }