michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsMemory.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "mozilla/ModuleUtils.h" michael@0: #include "nsIWebBrowserChrome.h" michael@0: #include "nsCURILoader.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeOwner.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsString.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include "nsIDOMNode.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsFrameTraversal.h" michael@0: #include "nsIImageDocument.h" michael@0: #include "nsIDOMHTMLDocument.h" michael@0: #include "nsIDOMHTMLElement.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsISelection.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsIDOMNSEditableElement.h" michael@0: #include "nsIEditor.h" michael@0: michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsContentCID.h" michael@0: #include "nsLayoutCID.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsIFormControl.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsIWindowWatcher.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsFocusManager.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/Link.h" michael@0: #include "nsRange.h" michael@0: michael@0: #include "nsTypeAheadFind.h" michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTypeAheadFind) michael@0: NS_INTERFACE_MAP_ENTRY(nsITypeAheadFind) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITypeAheadFind) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsIObserver) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypeAheadFind) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypeAheadFind) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsTypeAheadFind, mFoundLink, mFoundEditable, michael@0: mCurrentWindow, mStartFindRange, mSearchRange, michael@0: mStartPointRange, mEndPointRange, mSoundInterface, michael@0: mFind) michael@0: michael@0: static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); michael@0: michael@0: #define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1" michael@0: michael@0: nsTypeAheadFind::nsTypeAheadFind(): michael@0: mStartLinksOnlyPref(false), michael@0: mCaretBrowsingOn(false), michael@0: mLastFindLength(0), michael@0: mIsSoundInitialized(false), michael@0: mCaseSensitive(false) michael@0: { michael@0: } michael@0: michael@0: nsTypeAheadFind::~nsTypeAheadFind() michael@0: { michael@0: nsCOMPtr prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID)); michael@0: if (prefInternal) { michael@0: prefInternal->RemoveObserver("accessibility.typeaheadfind", this); michael@0: prefInternal->RemoveObserver("accessibility.browsewithcaret", this); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTypeAheadFind::Init(nsIDocShell* aDocShell) michael@0: { michael@0: nsCOMPtr prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID)); michael@0: michael@0: mSearchRange = nullptr; michael@0: mStartPointRange = nullptr; michael@0: mEndPointRange = nullptr; michael@0: if (!prefInternal || !EnsureFind()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: SetDocShell(aDocShell); michael@0: michael@0: // ----------- Listen to prefs ------------------ michael@0: nsresult rv = prefInternal->AddObserver("accessibility.browsewithcaret", this, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // ----------- Get initial preferences ---------- michael@0: PrefsReset(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsTypeAheadFind::PrefsReset() michael@0: { michael@0: nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); michael@0: NS_ENSURE_TRUE(prefBranch, NS_ERROR_FAILURE); michael@0: michael@0: prefBranch->GetBoolPref("accessibility.typeaheadfind.startlinksonly", michael@0: &mStartLinksOnlyPref); michael@0: michael@0: bool isSoundEnabled = true; michael@0: prefBranch->GetBoolPref("accessibility.typeaheadfind.enablesound", michael@0: &isSoundEnabled); michael@0: nsXPIDLCString soundStr; michael@0: if (isSoundEnabled) michael@0: prefBranch->GetCharPref("accessibility.typeaheadfind.soundURL", getter_Copies(soundStr)); michael@0: michael@0: mNotFoundSoundURL = soundStr; michael@0: michael@0: prefBranch->GetBoolPref("accessibility.browsewithcaret", michael@0: &mCaretBrowsingOn); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::SetCaseSensitive(bool isCaseSensitive) michael@0: { michael@0: mCaseSensitive = isCaseSensitive; michael@0: michael@0: if (mFind) { michael@0: mFind->SetCaseSensitive(mCaseSensitive); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::GetCaseSensitive(bool* isCaseSensitive) michael@0: { michael@0: *isCaseSensitive = mCaseSensitive; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::SetDocShell(nsIDocShell* aDocShell) michael@0: { michael@0: mDocShell = do_GetWeakReference(aDocShell); michael@0: michael@0: mWebBrowserFind = do_GetInterface(aDocShell); michael@0: NS_ENSURE_TRUE(mWebBrowserFind, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr presShell; michael@0: presShell = aDocShell->GetPresShell(); michael@0: mPresShell = do_GetWeakReference(presShell); michael@0: michael@0: mStartFindRange = nullptr; michael@0: mStartPointRange = nullptr; michael@0: mSearchRange = nullptr; michael@0: mEndPointRange = nullptr; michael@0: michael@0: mFoundLink = nullptr; michael@0: mFoundEditable = nullptr; michael@0: mCurrentWindow = nullptr; michael@0: michael@0: mSelectionController = nullptr; michael@0: michael@0: mFind = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::SetSelectionModeAndRepaint(int16_t aToggle) michael@0: { michael@0: nsCOMPtr selectionController = michael@0: do_QueryReferent(mSelectionController); michael@0: if (!selectionController) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: selectionController->SetDisplaySelection(aToggle); michael@0: selectionController->RepaintSelection(nsISelectionController::SELECTION_NORMAL); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::CollapseSelection() michael@0: { michael@0: nsCOMPtr selectionController = michael@0: do_QueryReferent(mSelectionController); michael@0: if (!selectionController) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr selection; michael@0: selectionController->GetSelection(nsISelectionController::SELECTION_NORMAL, michael@0: getter_AddRefs(selection)); michael@0: if (selection) michael@0: selection->CollapseToStart(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::Observe(nsISupports *aSubject, const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) michael@0: return PrefsReset(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTypeAheadFind::SaveFind() michael@0: { michael@0: if (mWebBrowserFind) michael@0: mWebBrowserFind->SetSearchString(mTypeAheadBuffer.get()); michael@0: michael@0: // save the length of this find for "not found" sound michael@0: mLastFindLength = mTypeAheadBuffer.Length(); michael@0: } michael@0: michael@0: void michael@0: nsTypeAheadFind::PlayNotFoundSound() michael@0: { michael@0: if (mNotFoundSoundURL.IsEmpty()) // no sound michael@0: return; michael@0: michael@0: if (!mSoundInterface) michael@0: mSoundInterface = do_CreateInstance("@mozilla.org/sound;1"); michael@0: michael@0: if (mSoundInterface) { michael@0: mIsSoundInitialized = true; michael@0: michael@0: if (mNotFoundSoundURL.Equals("beep")) { michael@0: mSoundInterface->Beep(); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr soundURI; michael@0: if (mNotFoundSoundURL.Equals("default")) michael@0: NS_NewURI(getter_AddRefs(soundURI), NS_LITERAL_CSTRING(TYPEAHEADFIND_NOTFOUND_WAV_URL)); michael@0: else michael@0: NS_NewURI(getter_AddRefs(soundURI), mNotFoundSoundURL); michael@0: michael@0: nsCOMPtr soundURL(do_QueryInterface(soundURI)); michael@0: if (soundURL) michael@0: mSoundInterface->Play(soundURL); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly, michael@0: bool aIsFirstVisiblePreferred, bool aFindPrev, michael@0: uint16_t* aResult) michael@0: { michael@0: *aResult = FIND_NOTFOUND; michael@0: mFoundLink = nullptr; michael@0: mFoundEditable = nullptr; michael@0: mCurrentWindow = nullptr; michael@0: nsCOMPtr startingPresShell (GetPresShell()); michael@0: if (!startingPresShell) { michael@0: nsCOMPtr ds = do_QueryReferent(mDocShell); michael@0: NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE); michael@0: michael@0: startingPresShell = ds->GetPresShell(); michael@0: mPresShell = do_GetWeakReference(startingPresShell); michael@0: } michael@0: michael@0: nsCOMPtr presShell(aPresShell); michael@0: michael@0: if (!presShell) { michael@0: presShell = startingPresShell; // this is the current document michael@0: michael@0: if (!presShell) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr presContext = presShell->GetPresContext(); michael@0: michael@0: if (!presContext) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr selection; michael@0: nsCOMPtr selectionController = michael@0: do_QueryReferent(mSelectionController); michael@0: if (!selectionController) { michael@0: GetSelection(presShell, getter_AddRefs(selectionController), michael@0: getter_AddRefs(selection)); // cache for reuse michael@0: mSelectionController = do_GetWeakReference(selectionController); michael@0: } else { michael@0: selectionController->GetSelection( michael@0: nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: } michael@0: michael@0: nsCOMPtr startingDocShell(presContext->GetDocShell()); michael@0: NS_ASSERTION(startingDocShell, "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]"); michael@0: if (!startingDocShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr rootContentTreeItem; michael@0: nsCOMPtr currentDocShell; michael@0: michael@0: startingDocShell->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem)); michael@0: nsCOMPtr rootContentDocShell = michael@0: do_QueryInterface(rootContentTreeItem); michael@0: michael@0: if (!rootContentDocShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr docShellEnumerator; michael@0: rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, michael@0: nsIDocShell::ENUMERATE_FORWARDS, michael@0: getter_AddRefs(docShellEnumerator)); michael@0: michael@0: // Default: can start at the current document michael@0: nsCOMPtr currentContainer = michael@0: do_QueryInterface(rootContentDocShell); michael@0: michael@0: // Iterate up to current shell, if there's more than 1 that we're michael@0: // dealing with michael@0: bool hasMoreDocShells; michael@0: michael@0: while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) && hasMoreDocShells) { michael@0: docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); michael@0: currentDocShell = do_QueryInterface(currentContainer); michael@0: if (!currentDocShell || currentDocShell == startingDocShell || aIsFirstVisiblePreferred) michael@0: break; michael@0: } michael@0: michael@0: // ------------ Get ranges ready ---------------- michael@0: nsCOMPtr returnRange; michael@0: nsCOMPtr focusedPS; michael@0: if (NS_FAILED(GetSearchContainers(currentContainer, michael@0: (!aIsFirstVisiblePreferred || michael@0: mStartFindRange) ? michael@0: selectionController.get() : nullptr, michael@0: aIsFirstVisiblePreferred, aFindPrev, michael@0: getter_AddRefs(presShell), michael@0: getter_AddRefs(presContext)))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int16_t rangeCompareResult = 0; michael@0: if (!mStartPointRange) { michael@0: mStartPointRange = new nsRange(presShell->GetDocument()); michael@0: } michael@0: michael@0: mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, mSearchRange, &rangeCompareResult); michael@0: // No need to wrap find in doc if starting at beginning michael@0: bool hasWrapped = (rangeCompareResult < 0); michael@0: michael@0: if (mTypeAheadBuffer.IsEmpty() || !EnsureFind()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mFind->SetFindBackwards(aFindPrev); michael@0: michael@0: while (true) { // ----- Outer while loop: go through all docs ----- michael@0: while (true) { // === Inner while loop: go through a single doc === michael@0: mFind->Find(mTypeAheadBuffer.get(), mSearchRange, mStartPointRange, michael@0: mEndPointRange, getter_AddRefs(returnRange)); michael@0: michael@0: if (!returnRange) michael@0: break; // Nothing found in this doc, go to outer loop (try next doc) michael@0: michael@0: // ------- Test resulting found range for success conditions ------ michael@0: bool isInsideLink = false, isStartingLink = false; michael@0: michael@0: if (aIsLinksOnly) { michael@0: // Don't check if inside link when searching all text michael@0: RangeStartsInsideLink(returnRange, presShell, &isInsideLink, michael@0: &isStartingLink); michael@0: } michael@0: michael@0: bool usesIndependentSelection; michael@0: if (!IsRangeVisible(presShell, presContext, returnRange, michael@0: aIsFirstVisiblePreferred, false, michael@0: getter_AddRefs(mStartPointRange), michael@0: &usesIndependentSelection) || michael@0: (aIsLinksOnly && !isInsideLink) || michael@0: (mStartLinksOnlyPref && aIsLinksOnly && !isStartingLink)) { michael@0: // ------ Failure ------ michael@0: // At this point mStartPointRange got updated to the first michael@0: // visible range in the viewport. We _may_ be able to just michael@0: // start there, if it's not taking us in the wrong direction. michael@0: if (aFindPrev) { michael@0: // We can continue at the end of mStartPointRange if its end is before michael@0: // the start of returnRange or coincides with it. Otherwise, we need michael@0: // to continue at the start of returnRange. michael@0: int16_t compareResult; michael@0: nsresult rv = michael@0: mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_END, michael@0: returnRange, &compareResult); michael@0: if (NS_SUCCEEDED(rv) && compareResult <= 0) { michael@0: // OK to start at the end of mStartPointRange michael@0: mStartPointRange->Collapse(false); michael@0: } else { michael@0: // Start at the beginning of returnRange michael@0: returnRange->CloneRange(getter_AddRefs(mStartPointRange)); michael@0: mStartPointRange->Collapse(true); michael@0: } michael@0: } else { michael@0: // We can continue at the start of mStartPointRange if its start is michael@0: // after the end of returnRange or coincides with it. Otherwise, we michael@0: // need to continue at the end of returnRange. michael@0: int16_t compareResult; michael@0: nsresult rv = michael@0: mStartPointRange->CompareBoundaryPoints(nsIDOMRange::END_TO_START, michael@0: returnRange, &compareResult); michael@0: if (NS_SUCCEEDED(rv) && compareResult >= 0) { michael@0: // OK to start at the start of mStartPointRange michael@0: mStartPointRange->Collapse(true); michael@0: } else { michael@0: // Start at the end of returnRange michael@0: returnRange->CloneRange(getter_AddRefs(mStartPointRange)); michael@0: mStartPointRange->Collapse(false); michael@0: } michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: // ------ Success! ------- michael@0: // Hide old selection (new one may be on a different controller) michael@0: if (selection) { michael@0: selection->CollapseToStart(); michael@0: SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON); michael@0: } michael@0: michael@0: // Make sure new document is selected michael@0: if (presShell != startingPresShell) { michael@0: // We are in a new document (because of frames/iframes) michael@0: mPresShell = do_GetWeakReference(presShell); michael@0: } michael@0: michael@0: nsCOMPtr document = michael@0: do_QueryInterface(presShell->GetDocument()); michael@0: NS_ASSERTION(document, "Wow, presShell doesn't have document!"); michael@0: if (!document) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsCOMPtr window = document->GetWindow(); michael@0: NS_ASSERTION(window, "document has no window"); michael@0: if (!window) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsCOMPtr fm = do_GetService(FOCUSMANAGER_CONTRACTID); michael@0: if (usesIndependentSelection) { michael@0: /* If a search result is found inside an editable element, we'll focus michael@0: * the element only if focus is in our content window, i.e. michael@0: * |if (focusedWindow.top == ourWindow.top)| */ michael@0: bool shouldFocusEditableElement = false; michael@0: if (fm) { michael@0: nsCOMPtr focusedWindow; michael@0: nsresult rv = fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr fwPI(do_QueryInterface(focusedWindow, &rv)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr fwTreeItem michael@0: (do_QueryInterface(fwPI->GetDocShell(), &rv)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr fwRootTreeItem; michael@0: rv = fwTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(fwRootTreeItem)); michael@0: if (NS_SUCCEEDED(rv) && fwRootTreeItem == rootContentTreeItem) michael@0: shouldFocusEditableElement = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We may be inside an editable element, and therefore the selection michael@0: // may be controlled by a different selection controller. Walk up the michael@0: // chain of parent nodes to see if we find one. michael@0: nsCOMPtr node; michael@0: returnRange->GetStartContainer(getter_AddRefs(node)); michael@0: while (node) { michael@0: nsCOMPtr editable = do_QueryInterface(node); michael@0: if (editable) { michael@0: // Inside an editable element. Get the correct selection michael@0: // controller and selection. michael@0: nsCOMPtr editor; michael@0: editable->GetEditor(getter_AddRefs(editor)); michael@0: NS_ASSERTION(editor, "Editable element has no editor!"); michael@0: if (!editor) { michael@0: break; michael@0: } michael@0: editor->GetSelectionController( michael@0: getter_AddRefs(selectionController)); michael@0: if (selectionController) { michael@0: selectionController->GetSelection( michael@0: nsISelectionController::SELECTION_NORMAL, michael@0: getter_AddRefs(selection)); michael@0: } michael@0: mFoundEditable = do_QueryInterface(node); michael@0: michael@0: if (!shouldFocusEditableElement) michael@0: break; michael@0: michael@0: // Otherwise move focus/caret to editable element michael@0: if (fm) michael@0: fm->SetFocus(mFoundEditable, 0); michael@0: break; michael@0: } michael@0: nsIDOMNode* tmp = node; michael@0: tmp->GetParentNode(getter_AddRefs(node)); michael@0: } michael@0: michael@0: // If we reach here without setting mFoundEditable, then something michael@0: // besides editable elements can cause us to have an independent michael@0: // selection controller. I don't know whether this is possible. michael@0: // Currently, we simply fall back to grabbing the document's selection michael@0: // controller in this case. Perhaps we should reject this find match michael@0: // and search again. michael@0: NS_ASSERTION(mFoundEditable, "Independent selection controller on " michael@0: "non-editable element!"); michael@0: } michael@0: michael@0: if (!mFoundEditable) { michael@0: // Not using a separate selection controller, so just get the michael@0: // document's controller and selection. michael@0: GetSelection(presShell, getter_AddRefs(selectionController), michael@0: getter_AddRefs(selection)); michael@0: } michael@0: mSelectionController = do_GetWeakReference(selectionController); michael@0: michael@0: // Select the found text michael@0: if (selection) { michael@0: selection->RemoveAllRanges(); michael@0: selection->AddRange(returnRange); michael@0: } michael@0: michael@0: if (!mFoundEditable && fm) { michael@0: nsCOMPtr win = do_QueryInterface(window); michael@0: fm->MoveFocus(win, nullptr, nsIFocusManager::MOVEFOCUS_CARET, michael@0: nsIFocusManager::FLAG_NOSCROLL | nsIFocusManager::FLAG_NOSWITCHFRAME, michael@0: getter_AddRefs(mFoundLink)); michael@0: } michael@0: michael@0: // Change selection color to ATTENTION and scroll to it. Careful: we michael@0: // must wait until after we goof with focus above before changing to michael@0: // ATTENTION, or when we MoveFocus() and the selection is not on a michael@0: // link, we'll blur, which will lose the ATTENTION. michael@0: if (selectionController) { michael@0: // Beware! This may flush notifications via synchronous michael@0: // ScrollSelectionIntoView. michael@0: SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ATTENTION); michael@0: selectionController->ScrollSelectionIntoView( michael@0: nsISelectionController::SELECTION_NORMAL, michael@0: nsISelectionController::SELECTION_WHOLE_SELECTION, michael@0: nsISelectionController::SCROLL_CENTER_VERTICALLY | michael@0: nsISelectionController::SCROLL_SYNCHRONOUS); michael@0: } michael@0: michael@0: mCurrentWindow = window; michael@0: *aResult = hasWrapped ? FIND_WRAPPED : FIND_FOUND; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // ======= end-inner-while (go through a single document) ========== michael@0: michael@0: // ---------- Nothing found yet, try next document ------------- michael@0: bool hasTriedFirstDoc = false; michael@0: do { michael@0: // ==== Second inner loop - get another while ==== michael@0: if (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) michael@0: && hasMoreDocShells) { michael@0: docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); michael@0: NS_ASSERTION(currentContainer, "HasMoreElements lied to us!"); michael@0: currentDocShell = do_QueryInterface(currentContainer); michael@0: michael@0: if (currentDocShell) michael@0: break; michael@0: } michael@0: else if (hasTriedFirstDoc) // Avoid potential infinite loop michael@0: return NS_ERROR_FAILURE; // No content doc shells michael@0: michael@0: // Reached last doc shell, loop around back to first doc shell michael@0: rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, michael@0: nsIDocShell::ENUMERATE_FORWARDS, michael@0: getter_AddRefs(docShellEnumerator)); michael@0: hasTriedFirstDoc = true; michael@0: } while (docShellEnumerator); // ==== end second inner while === michael@0: michael@0: bool continueLoop = false; michael@0: if (currentDocShell != startingDocShell) michael@0: continueLoop = true; // Try next document michael@0: else if (!hasWrapped || aIsFirstVisiblePreferred) { michael@0: // Finished searching through docshells: michael@0: // If aFirstVisiblePreferred == true, we may need to go through all michael@0: // docshells twice -once to look for visible matches, the second time michael@0: // for any match michael@0: aIsFirstVisiblePreferred = false; michael@0: hasWrapped = true; michael@0: continueLoop = true; // Go through all docs again michael@0: } michael@0: michael@0: if (continueLoop) { michael@0: if (NS_FAILED(GetSearchContainers(currentContainer, nullptr, michael@0: aIsFirstVisiblePreferred, aFindPrev, michael@0: getter_AddRefs(presShell), michael@0: getter_AddRefs(presContext)))) { michael@0: continue; michael@0: } michael@0: michael@0: if (aFindPrev) { michael@0: // Reverse mode: swap start and end points, so that we start michael@0: // at end of document and go to beginning michael@0: nsCOMPtr tempRange; michael@0: mStartPointRange->CloneRange(getter_AddRefs(tempRange)); michael@0: if (!mEndPointRange) { michael@0: mEndPointRange = new nsRange(presShell->GetDocument()); michael@0: } michael@0: michael@0: mStartPointRange = mEndPointRange; michael@0: mEndPointRange = tempRange; michael@0: } michael@0: michael@0: continue; michael@0: } michael@0: michael@0: // ------------- Failed -------------- michael@0: break; michael@0: } // end-outer-while: go through all docs michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::GetSearchString(nsAString& aSearchString) michael@0: { michael@0: aSearchString = mTypeAheadBuffer; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::GetFoundLink(nsIDOMElement** aFoundLink) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFoundLink); michael@0: *aFoundLink = mFoundLink; michael@0: NS_IF_ADDREF(*aFoundLink); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::GetFoundEditable(nsIDOMElement** aFoundEditable) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFoundEditable); michael@0: *aFoundEditable = mFoundEditable; michael@0: NS_IF_ADDREF(*aFoundEditable); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::GetCurrentWindow(nsIDOMWindow** aCurrentWindow) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCurrentWindow); michael@0: *aCurrentWindow = mCurrentWindow; michael@0: NS_IF_ADDREF(*aCurrentWindow); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTypeAheadFind::GetSearchContainers(nsISupports *aContainer, michael@0: nsISelectionController *aSelectionController, michael@0: bool aIsFirstVisiblePreferred, michael@0: bool aFindPrev, michael@0: nsIPresShell **aPresShell, michael@0: nsPresContext **aPresContext) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aContainer); michael@0: NS_ENSURE_ARG_POINTER(aPresShell); michael@0: NS_ENSURE_ARG_POINTER(aPresContext); michael@0: michael@0: *aPresShell = nullptr; michael@0: *aPresContext = nullptr; michael@0: michael@0: nsCOMPtr docShell(do_QueryInterface(aContainer)); michael@0: if (!docShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr presShell = docShell->GetPresShell(); michael@0: michael@0: nsRefPtr presContext; michael@0: docShell->GetPresContext(getter_AddRefs(presContext)); michael@0: michael@0: if (!presShell || !presContext) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIDocument* doc = presShell->GetDocument(); michael@0: michael@0: if (!doc) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr rootContent; michael@0: nsCOMPtr htmlDoc(do_QueryInterface(doc)); michael@0: if (htmlDoc) { michael@0: nsCOMPtr bodyEl; michael@0: htmlDoc->GetBody(getter_AddRefs(bodyEl)); michael@0: rootContent = do_QueryInterface(bodyEl); michael@0: } michael@0: michael@0: if (!rootContent) michael@0: rootContent = doc->GetRootElement(); michael@0: michael@0: nsCOMPtr rootNode(do_QueryInterface(rootContent)); michael@0: michael@0: if (!rootNode) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: uint32_t childCount = rootContent->GetChildCount(); michael@0: michael@0: if (!mSearchRange) { michael@0: mSearchRange = new nsRange(rootContent); michael@0: } michael@0: michael@0: if (!mEndPointRange) { michael@0: mEndPointRange = new nsRange(rootContent); michael@0: } michael@0: michael@0: mSearchRange->SelectNodeContents(rootNode); michael@0: michael@0: mEndPointRange->SetEnd(rootNode, childCount); michael@0: mEndPointRange->Collapse(false); // collapse to end michael@0: michael@0: // Consider current selection as null if michael@0: // it's not in the currently focused document michael@0: nsCOMPtr currentSelectionRange; michael@0: nsCOMPtr selectionPresShell = GetPresShell(); michael@0: if (aSelectionController && selectionPresShell && selectionPresShell == presShell) { michael@0: nsCOMPtr selection; michael@0: aSelectionController->GetSelection( michael@0: nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: if (selection) michael@0: selection->GetRangeAt(0, getter_AddRefs(currentSelectionRange)); michael@0: } michael@0: michael@0: if (!mStartPointRange) { michael@0: mStartPointRange = new nsRange(doc); michael@0: } michael@0: michael@0: if (!currentSelectionRange) { michael@0: // Ensure visible range, move forward if necessary michael@0: // This uses ignores the return value, but usese the side effect of michael@0: // IsRangeVisible. It returns the first visible range after searchRange michael@0: IsRangeVisible(presShell, presContext, mSearchRange, michael@0: aIsFirstVisiblePreferred, true, michael@0: getter_AddRefs(mStartPointRange), nullptr); michael@0: } michael@0: else { michael@0: int32_t startOffset; michael@0: nsCOMPtr startNode; michael@0: if (aFindPrev) { michael@0: currentSelectionRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: currentSelectionRange->GetStartOffset(&startOffset); michael@0: } else { michael@0: currentSelectionRange->GetEndContainer(getter_AddRefs(startNode)); michael@0: currentSelectionRange->GetEndOffset(&startOffset); michael@0: } michael@0: if (!startNode) michael@0: startNode = rootNode; michael@0: michael@0: // We need to set the start point this way, other methods haven't worked michael@0: mStartPointRange->SelectNode(startNode); michael@0: mStartPointRange->SetStart(startNode, startOffset); michael@0: } michael@0: michael@0: mStartPointRange->Collapse(true); // collapse to start michael@0: michael@0: *aPresShell = presShell; michael@0: NS_ADDREF(*aPresShell); michael@0: michael@0: *aPresContext = presContext; michael@0: NS_ADDREF(*aPresContext); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTypeAheadFind::RangeStartsInsideLink(nsIDOMRange *aRange, michael@0: nsIPresShell *aPresShell, michael@0: bool *aIsInsideLink, michael@0: bool *aIsStartingLink) michael@0: { michael@0: *aIsInsideLink = false; michael@0: *aIsStartingLink = true; michael@0: michael@0: // ------- Get nsIContent to test ------- michael@0: nsCOMPtr startNode; michael@0: nsCOMPtr startContent, origContent; michael@0: aRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: int32_t startOffset; michael@0: aRange->GetStartOffset(&startOffset); michael@0: michael@0: startContent = do_QueryInterface(startNode); michael@0: if (!startContent) { michael@0: NS_NOTREACHED("startContent should never be null"); michael@0: return; michael@0: } michael@0: origContent = startContent; michael@0: michael@0: if (startContent->IsElement()) { michael@0: nsIContent *childContent = startContent->GetChildAt(startOffset); michael@0: if (childContent) { michael@0: startContent = childContent; michael@0: } michael@0: } michael@0: else if (startOffset > 0) { michael@0: const nsTextFragment *textFrag = startContent->GetText(); michael@0: if (textFrag) { michael@0: // look for non whitespace character before start offset michael@0: for (int32_t index = 0; index < startOffset; index++) { michael@0: // FIXME: take content language into account when deciding whitespace. michael@0: if (!mozilla::dom::IsSpaceCharacter(textFrag->CharAt(index))) { michael@0: *aIsStartingLink = false; // not at start of a node michael@0: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // ------- Check to see if inside link --------- michael@0: michael@0: // We now have the correct start node for the range michael@0: // Search for links, starting with startNode, and going up parent chain michael@0: michael@0: nsCOMPtr tag, hrefAtom(do_GetAtom("href")); michael@0: nsCOMPtr typeAtom(do_GetAtom("type")); michael@0: michael@0: while (true) { michael@0: // Keep testing while startContent is equal to something, michael@0: // eventually we'll run out of ancestors michael@0: michael@0: if (startContent->IsHTML()) { michael@0: nsCOMPtr link(do_QueryInterface(startContent)); michael@0: if (link) { michael@0: // Check to see if inside HTML link michael@0: *aIsInsideLink = startContent->HasAttr(kNameSpaceID_None, hrefAtom); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: // Any xml element can be an xlink michael@0: *aIsInsideLink = startContent->HasAttr(kNameSpaceID_XLink, hrefAtom); michael@0: if (*aIsInsideLink) { michael@0: if (!startContent->AttrValueIs(kNameSpaceID_XLink, typeAtom, michael@0: NS_LITERAL_STRING("simple"), michael@0: eCaseMatters)) { michael@0: *aIsInsideLink = false; // Xlink must be type="simple" michael@0: } michael@0: michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Get the parent michael@0: nsCOMPtr parent = startContent->GetParent(); michael@0: if (!parent) michael@0: break; michael@0: michael@0: nsIContent* parentsFirstChild = parent->GetFirstChild(); michael@0: michael@0: // We don't want to look at a whitespace-only first child michael@0: if (parentsFirstChild && parentsFirstChild->TextIsOnlyWhitespace()) { michael@0: parentsFirstChild = parentsFirstChild->GetNextSibling(); michael@0: } michael@0: michael@0: if (parentsFirstChild != startContent) { michael@0: // startContent wasn't a first child, so we conclude that michael@0: // if this is inside a link, it's not at the beginning of it michael@0: *aIsStartingLink = false; michael@0: } michael@0: michael@0: startContent = parent; michael@0: } michael@0: michael@0: *aIsStartingLink = false; michael@0: } michael@0: michael@0: /* Find another match in the page. */ michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::FindAgain(bool aFindBackwards, bool aLinksOnly, michael@0: uint16_t* aResult) michael@0: michael@0: { michael@0: *aResult = FIND_NOTFOUND; michael@0: michael@0: if (!mTypeAheadBuffer.IsEmpty()) michael@0: // Beware! This may flush notifications via synchronous michael@0: // ScrollSelectionIntoView. michael@0: FindItNow(nullptr, aLinksOnly, false, aFindBackwards, aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTypeAheadFind::Find(const nsAString& aSearchString, bool aLinksOnly, michael@0: uint16_t* aResult) michael@0: { michael@0: *aResult = FIND_NOTFOUND; michael@0: michael@0: nsCOMPtr presShell (GetPresShell()); michael@0: if (!presShell) { michael@0: nsCOMPtr ds (do_QueryReferent(mDocShell)); michael@0: NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE); michael@0: michael@0: presShell = ds->GetPresShell(); michael@0: NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); michael@0: mPresShell = do_GetWeakReference(presShell); michael@0: } michael@0: michael@0: nsCOMPtr selection; michael@0: nsCOMPtr selectionController = michael@0: do_QueryReferent(mSelectionController); michael@0: if (!selectionController) { michael@0: GetSelection(presShell, getter_AddRefs(selectionController), michael@0: getter_AddRefs(selection)); // cache for reuse michael@0: mSelectionController = do_GetWeakReference(selectionController); michael@0: } else { michael@0: selectionController->GetSelection( michael@0: nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: } michael@0: michael@0: if (selection) michael@0: selection->CollapseToStart(); michael@0: michael@0: if (aSearchString.IsEmpty()) { michael@0: mTypeAheadBuffer.Truncate(); michael@0: michael@0: // These will be initialized to their true values after the first character michael@0: // is typed michael@0: mStartFindRange = nullptr; michael@0: mSelectionController = nullptr; michael@0: michael@0: *aResult = FIND_FOUND; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool atEnd = false; michael@0: if (mTypeAheadBuffer.Length()) { michael@0: const nsAString& oldStr = Substring(mTypeAheadBuffer, 0, mTypeAheadBuffer.Length()); michael@0: const nsAString& newStr = Substring(aSearchString, 0, mTypeAheadBuffer.Length()); michael@0: if (oldStr.Equals(newStr)) michael@0: atEnd = true; michael@0: michael@0: const nsAString& newStr2 = Substring(aSearchString, 0, aSearchString.Length()); michael@0: const nsAString& oldStr2 = Substring(mTypeAheadBuffer, 0, aSearchString.Length()); michael@0: if (oldStr2.Equals(newStr2)) michael@0: atEnd = true; michael@0: michael@0: if (!atEnd) michael@0: mStartFindRange = nullptr; michael@0: } michael@0: michael@0: if (!mIsSoundInitialized && !mNotFoundSoundURL.IsEmpty()) { michael@0: // This makes sure system sound library is loaded so that michael@0: // there's no lag before the first sound is played michael@0: // by waiting for the first keystroke, we still get the startup time benefits. michael@0: mIsSoundInitialized = true; michael@0: mSoundInterface = do_CreateInstance("@mozilla.org/sound;1"); michael@0: if (mSoundInterface && !mNotFoundSoundURL.Equals(NS_LITERAL_CSTRING("beep"))) { michael@0: mSoundInterface->Init(); michael@0: } michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: // After each keystroke, ensure sound object is destroyed, to free up memory michael@0: // allocated for error sound, otherwise Windows' nsISound impl michael@0: // holds onto the last played sound, using up memory. michael@0: mSoundInterface = nullptr; michael@0: #endif michael@0: michael@0: int32_t bufferLength = mTypeAheadBuffer.Length(); michael@0: michael@0: mTypeAheadBuffer = aSearchString; michael@0: michael@0: bool isFirstVisiblePreferred = false; michael@0: michael@0: // --------- Initialize find if 1st char ---------- michael@0: if (bufferLength == 0) { michael@0: // If you can see the selection (not collapsed or thru caret browsing), michael@0: // or if already focused on a page element, start there. michael@0: // Otherwise we're going to start at the first visible element michael@0: bool isSelectionCollapsed = true; michael@0: if (selection) michael@0: selection->GetIsCollapsed(&isSelectionCollapsed); michael@0: michael@0: // If true, we will scan from top left of visible area michael@0: // If false, we will scan from start of selection michael@0: isFirstVisiblePreferred = !atEnd && !mCaretBrowsingOn && isSelectionCollapsed; michael@0: if (isFirstVisiblePreferred) { michael@0: // Get the focused content. If there is a focused node, ensure the michael@0: // selection is at that point. Otherwise, we will just want to start michael@0: // from the caret position or the beginning of the document. michael@0: nsPresContext* presContext = presShell->GetPresContext(); michael@0: NS_ENSURE_TRUE(presContext, NS_OK); michael@0: michael@0: nsCOMPtr document = michael@0: do_QueryInterface(presShell->GetDocument()); michael@0: if (!document) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsCOMPtr window = do_QueryInterface(document->GetWindow()); michael@0: michael@0: nsCOMPtr fm = do_GetService(FOCUSMANAGER_CONTRACTID); michael@0: if (fm) { michael@0: nsCOMPtr focusedElement; michael@0: nsCOMPtr focusedWindow; michael@0: fm->GetFocusedElementForWindow(window, false, getter_AddRefs(focusedWindow), michael@0: getter_AddRefs(focusedElement)); michael@0: // If the root element is focused, then it's actually the document michael@0: // that has the focus, so ignore this. michael@0: if (focusedElement && michael@0: !SameCOMIdentity(focusedElement, document->GetRootElement())) { michael@0: fm->MoveCaretToFocus(window); michael@0: isFirstVisiblePreferred = false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // ----------- Find the text! --------------------- michael@0: // Beware! This may flush notifications via synchronous michael@0: // ScrollSelectionIntoView. michael@0: nsresult rv = FindItNow(nullptr, aLinksOnly, isFirstVisiblePreferred, michael@0: false, aResult); michael@0: michael@0: // ---------Handle success or failure --------------- michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (mTypeAheadBuffer.Length() == 1) { michael@0: // If first letter, store where the first find succeeded michael@0: // (mStartFindRange) michael@0: michael@0: mStartFindRange = nullptr; michael@0: if (selection) { michael@0: nsCOMPtr startFindRange; michael@0: selection->GetRangeAt(0, getter_AddRefs(startFindRange)); michael@0: if (startFindRange) michael@0: startFindRange->CloneRange(getter_AddRefs(mStartFindRange)); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // Error sound michael@0: if (mTypeAheadBuffer.Length() > mLastFindLength) michael@0: PlayNotFoundSound(); michael@0: } michael@0: michael@0: SaveFind(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTypeAheadFind::GetSelection(nsIPresShell *aPresShell, michael@0: nsISelectionController **aSelCon, michael@0: nsISelection **aDOMSel) michael@0: { michael@0: if (!aPresShell) michael@0: return; michael@0: michael@0: // if aCurrentNode is nullptr, get selection for document michael@0: *aDOMSel = nullptr; michael@0: michael@0: nsPresContext* presContext = aPresShell->GetPresContext(); michael@0: michael@0: nsIFrame *frame = aPresShell->GetRootFrame(); michael@0: michael@0: if (presContext && frame) { michael@0: frame->GetSelectionController(presContext, aSelCon); michael@0: if (*aSelCon) { michael@0: (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL, michael@0: aDOMSel); michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell, michael@0: nsPresContext *aPresContext, michael@0: nsIDOMRange *aRange, bool aMustBeInViewPort, michael@0: bool aGetTopVisibleLeaf, michael@0: nsIDOMRange **aFirstVisibleRange, michael@0: bool *aUsesIndependentSelection) michael@0: { michael@0: NS_ASSERTION(aPresShell && aPresContext && aRange && aFirstVisibleRange, michael@0: "params are invalid"); michael@0: michael@0: // We need to know if the range start is visible. michael@0: // Otherwise, return the first visible range start michael@0: // in aFirstVisibleRange michael@0: michael@0: aRange->CloneRange(aFirstVisibleRange); michael@0: nsCOMPtr node; michael@0: aRange->GetStartContainer(getter_AddRefs(node)); michael@0: michael@0: nsCOMPtr content(do_QueryInterface(node)); michael@0: if (!content) michael@0: return false; michael@0: michael@0: nsIFrame *frame = content->GetPrimaryFrame(); michael@0: if (!frame) michael@0: return false; // No frame! Not visible then. michael@0: michael@0: if (!frame->StyleVisibility()->IsVisible()) michael@0: return false; michael@0: michael@0: // Detect if we are _inside_ a text control, or something else with its own michael@0: // selection controller. michael@0: if (aUsesIndependentSelection) { michael@0: *aUsesIndependentSelection = michael@0: (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION); michael@0: } michael@0: michael@0: // ---- We have a frame ---- michael@0: if (!aMustBeInViewPort) michael@0: return true; // Don't need it to be on screen, just in rendering tree michael@0: michael@0: // Get the next in flow frame that contains the range start michael@0: int32_t startRangeOffset, startFrameOffset, endFrameOffset; michael@0: aRange->GetStartOffset(&startRangeOffset); michael@0: while (true) { michael@0: frame->GetOffsets(startFrameOffset, endFrameOffset); michael@0: if (startRangeOffset < endFrameOffset) michael@0: break; michael@0: michael@0: nsIFrame *nextContinuationFrame = frame->GetNextContinuation(); michael@0: if (nextContinuationFrame) michael@0: frame = nextContinuationFrame; michael@0: else michael@0: break; michael@0: } michael@0: michael@0: // Set up the variables we need, return true if we can't get at them all michael@0: const uint16_t kMinPixels = 12; michael@0: nscoord minDistance = nsPresContext::CSSPixelsToAppUnits(kMinPixels); michael@0: michael@0: // Get the bounds of the current frame, relative to the current view. michael@0: // We don't use the more accurate AccGetBounds, because that is michael@0: // more expensive and the STATE_OFFSCREEN flag that this is used michael@0: // for only needs to be a rough indicator michael@0: nsRectVisibility rectVisibility = nsRectVisibility_kAboveViewport; michael@0: michael@0: if (!aGetTopVisibleLeaf && !frame->GetRect().IsEmpty()) { michael@0: rectVisibility = michael@0: aPresShell->GetRectVisibility(frame, michael@0: nsRect(nsPoint(0,0), frame->GetSize()), michael@0: minDistance); michael@0: michael@0: if (rectVisibility != nsRectVisibility_kAboveViewport) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // We know that the target range isn't usable because it's not in the michael@0: // view port. Move range forward to first visible point, michael@0: // this speeds us up a lot in long documents michael@0: nsCOMPtr frameTraversal; michael@0: nsCOMPtr trav(do_CreateInstance(kFrameTraversalCID)); michael@0: if (trav) michael@0: trav->NewFrameTraversal(getter_AddRefs(frameTraversal), michael@0: aPresContext, frame, michael@0: eLeaf, michael@0: false, // aVisual michael@0: false, // aLockInScrollView michael@0: false // aFollowOOFs michael@0: ); michael@0: michael@0: if (!frameTraversal) michael@0: return false; michael@0: michael@0: while (rectVisibility == nsRectVisibility_kAboveViewport) { michael@0: frameTraversal->Next(); michael@0: frame = frameTraversal->CurrentItem(); michael@0: if (!frame) michael@0: return false; michael@0: michael@0: if (!frame->GetRect().IsEmpty()) { michael@0: rectVisibility = michael@0: aPresShell->GetRectVisibility(frame, michael@0: nsRect(nsPoint(0,0), frame->GetSize()), michael@0: minDistance); michael@0: } michael@0: } michael@0: michael@0: if (frame) { michael@0: nsCOMPtr firstVisibleNode = do_QueryInterface(frame->GetContent()); michael@0: michael@0: if (firstVisibleNode) { michael@0: frame->GetOffsets(startFrameOffset, endFrameOffset); michael@0: (*aFirstVisibleRange)->SetStart(firstVisibleNode, startFrameOffset); michael@0: (*aFirstVisibleRange)->SetEnd(firstVisibleNode, endFrameOffset); michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsTypeAheadFind::GetPresShell() michael@0: { michael@0: if (!mPresShell) michael@0: return nullptr; michael@0: michael@0: nsCOMPtr shell = do_QueryReferent(mPresShell); michael@0: if (shell) { michael@0: nsPresContext *pc = shell->GetPresContext(); michael@0: if (!pc || !pc->GetContainerWeak()) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: return shell.forget(); michael@0: }