diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/typeaheadfind | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/typeaheadfind')
-rw-r--r-- | toolkit/components/typeaheadfind/content/notfound.wav | bin | 0 -> 1422 bytes | |||
-rw-r--r-- | toolkit/components/typeaheadfind/jar.mn | 6 | ||||
-rw-r--r-- | toolkit/components/typeaheadfind/moz.build | 22 | ||||
-rw-r--r-- | toolkit/components/typeaheadfind/nsITypeAheadFind.idl | 108 | ||||
-rw-r--r-- | toolkit/components/typeaheadfind/nsTypeAheadFind.cpp | 1171 | ||||
-rw-r--r-- | toolkit/components/typeaheadfind/nsTypeAheadFind.h | 142 |
6 files changed, 1449 insertions, 0 deletions
diff --git a/toolkit/components/typeaheadfind/content/notfound.wav b/toolkit/components/typeaheadfind/content/notfound.wav Binary files differnew file mode 100644 index 0000000000..c6fd5cb869 --- /dev/null +++ b/toolkit/components/typeaheadfind/content/notfound.wav diff --git a/toolkit/components/typeaheadfind/jar.mn b/toolkit/components/typeaheadfind/jar.mn new file mode 100644 index 0000000000..173caf77ad --- /dev/null +++ b/toolkit/components/typeaheadfind/jar.mn @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +toolkit.jar: + content/global/notfound.wav (content/notfound.wav) diff --git a/toolkit/components/typeaheadfind/moz.build b/toolkit/components/typeaheadfind/moz.build new file mode 100644 index 0000000000..820e6281f9 --- /dev/null +++ b/toolkit/components/typeaheadfind/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + "nsITypeAheadFind.idl", +] + +XPIDL_MODULE = "fastfind" + +SOURCES += [ + "nsTypeAheadFind.cpp", +] + +FINAL_LIBRARY = "xul" + +JAR_MANIFESTS += ["jar.mn"] + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Find Toolbar") diff --git a/toolkit/components/typeaheadfind/nsITypeAheadFind.idl b/toolkit/components/typeaheadfind/nsITypeAheadFind.idl new file mode 100644 index 0000000000..2f94136e72 --- /dev/null +++ b/toolkit/components/typeaheadfind/nsITypeAheadFind.idl @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +/********************************* #includes *********************************/ + +#include "nsISupports.idl" // nsISupports + + +/******************************** Declarations *******************************/ + +interface mozIDOMWindow; +interface nsIDocShell; + +webidl Element; +webidl Range; + + +/****************************** nsTypeAheadFind ******************************/ + +[scriptable, uuid(ae501e28-c57f-4692-ac74-410e1bed98b7)] +interface nsITypeAheadFind : nsISupports +{ + /****************************** Initializer ******************************/ + + /* Necessary initialization that can't happen in the constructor, either + * because function calls here may fail, or because the docShell is + * required. */ + void init(in nsIDocShell aDocShell); + + + /***************************** Core functions ****************************/ + + /* Find aSearchString in page. If aLinksOnly is true, only search the page's + * hyperlinks for the string. */ + unsigned short find(in AString aSearchString, + in boolean aLinksOnly, + in unsigned long aMode, + in boolean aDontIterateFrames); + + /* Return the range of the most recent match. */ + Range getFoundRange(); + + + /**************************** Helper functions ***************************/ + + /* Change searched docShell. This happens when e.g. we use the same + * nsITypeAheadFind object to search different tabs. */ + void setDocShell(in nsIDocShell aDocShell); + + /* Change the look of the the "found match" selection to aToggle, and repaint + * the selection. */ + void setSelectionModeAndRepaint(in short toggle); + + /* Collapse the "found match" selection to its start. Because not all + * matches are owned by the same selection controller, this doesn't + * necessarily happen automatically. */ + void collapseSelection(); + + /* Check if a range is visible using heuristics */ + boolean isRangeVisible(in Range aRange, in boolean aMustBeInViewPort); + + /* Check if a range is actually rendered (out of viewport always false) */ + boolean isRangeRendered(in Range aRange); + + /******************************* Attributes ******************************/ + + readonly attribute AString searchString; + // Most recent search string + attribute boolean caseSensitive; // Searches are case sensitive + attribute boolean matchDiacritics; // Searches preserve diacritics + attribute boolean entireWord; // Search for whole words only + readonly attribute Element foundLink; + // Most recent elem found, if a link + readonly attribute Element foundEditable; + // Most recent elem found, if editable + readonly attribute mozIDOMWindow currentWindow; + // Window of most recent match + + + /******************************* Constants *******************************/ + + /* Modes for Find() */ + const unsigned long FIND_INITIAL = 0; + const unsigned long FIND_NEXT = 1; + const unsigned long FIND_PREVIOUS = 2; + const unsigned long FIND_FIRST = 3; + const unsigned long FIND_LAST = 4; + + /* Find return codes */ + const unsigned short FIND_FOUND = 0; + // Successful find + const unsigned short FIND_NOTFOUND = 1; + // Unsuccessful find + const unsigned short FIND_WRAPPED = 2; + // Successful find, but wrapped around + const unsigned short FIND_PENDING = 3; + // Unknown status, find has not finished + + + /*************************************************************************/ + +}; + + +/*****************************************************************************/ diff --git a/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp new file mode 100644 index 0000000000..ed6d9a740c --- /dev/null +++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp @@ -0,0 +1,1171 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsDocShell.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/PresShell.h" +#include "mozilla/Services.h" +#include "nsCycleCollectionParticipant.h" +#include "nsNetUtil.h" +#include "nsIURL.h" +#include "nsIURI.h" +#include "nsIDocShell.h" +#include "nsISimpleEnumerator.h" +#include "nsPIDOMWindow.h" +#include "nsIPrefBranch.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsGenericHTMLElement.h" + +#include "nsIFrame.h" +#include "mozilla/dom/Document.h" +#include "nsIContent.h" +#include "nsTextFragment.h" +#include "nsIEditor.h" + +#include "nsIDocShellTreeItem.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIObserverService.h" +#include "nsISound.h" +#include "nsFocusManager.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLTextAreaElement.h" +#include "mozilla/dom/Link.h" +#include "mozilla/dom/RangeBinding.h" +#include "mozilla/dom/Selection.h" +#include "nsLayoutUtils.h" +#include "nsRange.h" + +#include "nsTypeAheadFind.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTypeAheadFind) + NS_INTERFACE_MAP_ENTRY(nsITypeAheadFind) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITypeAheadFind) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypeAheadFind) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypeAheadFind) + +NS_IMPL_CYCLE_COLLECTION_WEAK(nsTypeAheadFind, mFoundLink, mFoundEditable, + mCurrentWindow, mStartFindRange, mSearchRange, + mStartPointRange, mEndPointRange, mFind, + mFoundRange) + +#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1" + +nsTypeAheadFind::nsTypeAheadFind() + : mStartLinksOnlyPref(false), + mCaretBrowsingOn(false), + mDidAddObservers(false), + mLastFindLength(0), + mCaseSensitive(false), + mEntireWord(false), + mMatchDiacritics(false) {} + +nsTypeAheadFind::~nsTypeAheadFind() { + nsCOMPtr<nsIPrefBranch> prefInternal( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefInternal) { + prefInternal->RemoveObserver("accessibility.typeaheadfind", this); + prefInternal->RemoveObserver("accessibility.browsewithcaret", this); + } +} + +nsresult nsTypeAheadFind::Init(nsIDocShell* aDocShell) { + nsCOMPtr<nsIPrefBranch> prefInternal( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + + mSearchRange = nullptr; + mStartPointRange = nullptr; + mEndPointRange = nullptr; + if (!prefInternal || !EnsureFind()) return NS_ERROR_FAILURE; + + SetDocShell(aDocShell); + + if (!mDidAddObservers) { + mDidAddObservers = true; + // ----------- Listen to prefs ------------------ + nsresult rv = + prefInternal->AddObserver("accessibility.browsewithcaret", this, true); + NS_ENSURE_SUCCESS(rv, rv); + rv = prefInternal->AddObserver("accessibility.typeaheadfind", this, true); + NS_ENSURE_SUCCESS(rv, rv); + + // ----------- Get initial preferences ---------- + PrefsReset(); + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true); + } + } + + return NS_OK; +} + +nsresult nsTypeAheadFind::PrefsReset() { + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(prefBranch, NS_ERROR_FAILURE); + + prefBranch->GetBoolPref("accessibility.typeaheadfind.startlinksonly", + &mStartLinksOnlyPref); + + bool isSoundEnabled = true; + prefBranch->GetBoolPref("accessibility.typeaheadfind.enablesound", + &isSoundEnabled); + nsAutoCString soundStr; + if (isSoundEnabled) + prefBranch->GetCharPref("accessibility.typeaheadfind.soundURL", soundStr); + + mNotFoundSoundURL = soundStr; + + prefBranch->GetBoolPref("accessibility.browsewithcaret", &mCaretBrowsingOn); + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::SetCaseSensitive(bool isCaseSensitive) { + mCaseSensitive = isCaseSensitive; + + if (mFind) { + mFind->SetCaseSensitive(mCaseSensitive); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetCaseSensitive(bool* isCaseSensitive) { + *isCaseSensitive = mCaseSensitive; + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::SetEntireWord(bool isEntireWord) { + mEntireWord = isEntireWord; + + if (mFind) { + mFind->SetEntireWord(mEntireWord); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetEntireWord(bool* isEntireWord) { + *isEntireWord = mEntireWord; + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::SetMatchDiacritics(bool matchDiacritics) { + mMatchDiacritics = matchDiacritics; + + if (mFind) { + mFind->SetMatchDiacritics(mMatchDiacritics); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetMatchDiacritics(bool* matchDiacritics) { + *matchDiacritics = mMatchDiacritics; + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::SetDocShell(nsIDocShell* aDocShell) { + mDocShell = do_GetWeakReference(aDocShell); + + mWebBrowserFind = do_GetInterface(aDocShell); + NS_ENSURE_TRUE(mWebBrowserFind, NS_ERROR_FAILURE); + + mDocument = aDocShell->GetExtantDocument(); + + ReleaseStrongMemberVariables(); + return NS_OK; +} + +void nsTypeAheadFind::ReleaseStrongMemberVariables() { + mStartFindRange = nullptr; + mStartPointRange = nullptr; + mSearchRange = nullptr; + mEndPointRange = nullptr; + + mFoundLink = nullptr; + mFoundEditable = nullptr; + mFoundRange = nullptr; + mCurrentWindow = nullptr; + + mSelectionController = nullptr; + + mFind = nullptr; +} + +NS_IMETHODIMP +nsTypeAheadFind::SetSelectionModeAndRepaint(int16_t aToggle) { + nsCOMPtr<nsISelectionController> selectionController = + do_QueryReferent(mSelectionController); + if (!selectionController) { + return NS_OK; + } + + selectionController->SetDisplaySelection(aToggle); + selectionController->RepaintSelection( + nsISelectionController::SELECTION_NORMAL); + + return NS_OK; +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsTypeAheadFind::CollapseSelection() { + nsCOMPtr<nsISelectionController> selectionController = + do_QueryReferent(mSelectionController); + if (!selectionController) { + return NS_OK; + } + + RefPtr<Selection> selection = selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL); + if (selection) { + selection->CollapseToStart(IgnoreErrors()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + return PrefsReset(); + } + if (!nsCRT::strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) && + SameCOMIdentity(aSubject, mCurrentWindow)) { + ReleaseStrongMemberVariables(); + } + + return NS_OK; +} + +void nsTypeAheadFind::SaveFind() { + if (mWebBrowserFind) mWebBrowserFind->SetSearchString(mTypeAheadBuffer); + + // save the length of this find for "not found" sound + mLastFindLength = mTypeAheadBuffer.Length(); +} + +void nsTypeAheadFind::PlayNotFoundSound() { + if (mNotFoundSoundURL.IsEmpty()) // no sound + return; + + nsCOMPtr<nsISound> soundInterface = do_GetService("@mozilla.org/sound;1"); + + if (soundInterface) { + if (mNotFoundSoundURL.EqualsLiteral("beep")) { + soundInterface->Beep(); + return; + } + + nsCOMPtr<nsIURI> soundURI; + if (mNotFoundSoundURL.EqualsLiteral("default")) + NS_NewURI(getter_AddRefs(soundURI), + nsLiteralCString(TYPEAHEADFIND_NOTFOUND_WAV_URL)); + else + NS_NewURI(getter_AddRefs(soundURI), mNotFoundSoundURL); + + nsCOMPtr<nsIURL> soundURL(do_QueryInterface(soundURI)); + if (soundURL) soundInterface->Play(soundURL); + } +} + +nsresult nsTypeAheadFind::FindItNow(uint32_t aMode, bool aIsLinksOnly, + bool aIsFirstVisiblePreferred, + bool aDontIterateFrames, + uint16_t* aResult) { + *aResult = FIND_NOTFOUND; + mFoundLink = nullptr; + mFoundEditable = nullptr; + mFoundRange = nullptr; + mCurrentWindow = nullptr; + RefPtr<Document> startingDocument = GetDocument(); + NS_ENSURE_TRUE(startingDocument, NS_ERROR_FAILURE); + + // There could be unflushed notifications which hide textareas or other + // elements that we don't want to find text in. + startingDocument->FlushPendingNotifications(mozilla::FlushType::Layout); + + RefPtr<PresShell> presShell = startingDocument->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + RefPtr<nsPresContext> presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + RefPtr<Selection> 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 { + selection = selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL); + } + + nsCOMPtr<nsIDocShell> startingDocShell(presContext->GetDocShell()); + NS_ASSERTION( + startingDocShell, + "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]"); + if (!startingDocShell) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDocShell> currentDocShell; + nsCOMPtr<nsISupports> currentContainer; + nsCOMPtr<nsIDocShellTreeItem> rootContentTreeItem; + nsCOMPtr<nsIDocShell> rootContentDocShell; + typedef nsTArray<RefPtr<nsIDocShell>> DocShells; + DocShells docShells; + DocShells::const_iterator it, it_end; + if (!aDontIterateFrames) { + // The use of GetInProcessSameTypeRootTreeItem (and later in this method) is + // OK here as out-of-process frames are handled externally by + // FinderParent.sys.mjs, which will end up only calling this method with + // aDontIterateFrames set to true. + startingDocShell->GetInProcessSameTypeRootTreeItem( + getter_AddRefs(rootContentTreeItem)); + rootContentDocShell = do_QueryInterface(rootContentTreeItem); + + if (!rootContentDocShell) return NS_ERROR_FAILURE; + + rootContentDocShell->GetAllDocShellsInSubtree( + nsIDocShellTreeItem::typeContent, nsIDocShell::ENUMERATE_FORWARDS, + docShells); + + // Default: can start at the current document + currentContainer = do_QueryInterface(rootContentDocShell); + + // Iterate up to current shell, if there's more than 1 that we're + // dealing with + for (it = docShells.begin(), it_end = docShells.end(); it != it_end; ++it) { + currentDocShell = *it; + if (!currentDocShell || currentDocShell == startingDocShell || + aIsFirstVisiblePreferred) + break; + } + } else { + currentContainer = currentDocShell = startingDocShell; + } + + bool findPrev = (aMode == FIND_PREVIOUS || aMode == FIND_LAST); + + // ------------ Get ranges ready ---------------- + + bool useSelection = (aMode != FIND_FIRST && aMode != FIND_LAST) && + (!aIsFirstVisiblePreferred || mStartFindRange); + + RefPtr<nsRange> returnRange; + if (NS_FAILED(GetSearchContainers( + currentContainer, useSelection ? selectionController.get() : nullptr, + aIsFirstVisiblePreferred, findPrev, getter_AddRefs(presShell), + getter_AddRefs(presContext)))) { + return NS_ERROR_FAILURE; + } + + if (!mStartPointRange) { + mStartPointRange = nsRange::Create(presShell->GetDocument()); + } + + // XXXbz Should this really be ignoring errors? + int16_t rangeCompareResult = mStartPointRange->CompareBoundaryPoints( + Range_Binding::START_TO_START, *mSearchRange, IgnoreErrors()); + // 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(findPrev); + + while (true) { // ----- Outer while loop: go through all docs ----- + while (true) { // === Inner while loop: go through a single doc === + mFind->Find(mTypeAheadBuffer, 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, &isInsideLink, &isStartingLink); + } + + bool usesIndependentSelection = false; + // Check actual visibility of the range, and generate some + // side effects (like updating mStartPointRange and + // setting usesIndependentSelection) that we'll need whether + // or not the range is visible. + bool canSeeRange = IsRangeVisible(returnRange, aIsFirstVisiblePreferred, + false, &usesIndependentSelection); + + mStartPointRange = returnRange->CloneRange(); + + // If we can't see the range, we still might be able to scroll + // it into view if usesIndependentSelection is true. If both are + // false, then we treat it as a failure condition. + if ((!canSeeRange && !usesIndependentSelection) || + (aIsLinksOnly && !isInsideLink) || + (mStartLinksOnlyPref && aIsLinksOnly && !isStartingLink)) { + // We want to jump over this range, so collapse to the start if we're + // finding backwards and vice versa. + mStartPointRange->Collapse(findPrev); + continue; + } + + mFoundRange = returnRange; + + // ------ Success! ------- + // Hide old selection (new one may be on a different controller) + if (selection) { + selection->CollapseToStart(IgnoreErrors()); + SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON); + } + + RefPtr<Document> document = presShell->GetDocument(); + NS_ASSERTION(document, "Wow, presShell doesn't have document!"); + if (!document) { + return NS_ERROR_UNEXPECTED; + } + + // Make sure new document is selected + if (document != startingDocument) { + // We are in a new document (because of frames/iframes) + mDocument = document; + } + + nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow(); + NS_ASSERTION(window, "document has no window"); + if (!window) return NS_ERROR_UNEXPECTED; + + RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); + 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<mozIDOMWindowProxy> focusedWindow; + nsresult rv = fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); + if (NS_SUCCEEDED(rv) && focusedWindow) { + auto* fwPI = nsPIDOMWindowOuter::From(focusedWindow); + nsCOMPtr<nsIDocShellTreeItem> fwTreeItem(fwPI->GetDocShell()); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIDocShellTreeItem> fwRootTreeItem; + rv = fwTreeItem->GetInProcessSameTypeRootTreeItem( + 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. + nsINode* node = returnRange->GetStartContainer(); + while (node) { + nsCOMPtr<nsIEditor> editor; + if (RefPtr<HTMLInputElement> input = + HTMLInputElement::FromNode(node)) { + editor = input->GetTextEditor(); + } else if (RefPtr<HTMLTextAreaElement> textarea = + HTMLTextAreaElement::FromNode(node)) { + editor = textarea->GetTextEditor(); + } else { + node = node->GetParentNode(); + continue; + } + + // Inside an editable element. Get the correct selection + // controller and selection. + NS_ASSERTION(editor, "Editable element has no editor!"); + if (!editor) { + break; + } + editor->GetSelectionController(getter_AddRefs(selectionController)); + if (selectionController) { + selection = selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL); + } + mFoundEditable = node->AsElement(); + + if (!shouldFocusEditableElement) { + break; + } + + // Otherwise move focus/caret to editable element + if (fm) { + nsCOMPtr<Element> newFocusElement = mFoundEditable; + fm->SetFocus(newFocusElement, 0); + } + break; + } + + // If we reach here without setting mFoundEditable, then something + // besides editable elements gave us an independent selection + // controller. List controls with multiple visible elements can do + // this (nsAreaSelectsFrame), and possibly others. We fall back to + // grabbing the document's selection controller in this case. + } + + 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(IgnoreErrors()); + selection->AddRangeAndSelectFramesAndNotifyListeners(*returnRange, + IgnoreErrors()); + } + + if (!mFoundEditable && fm) { + fm->MoveFocus(window->GetOuterWindow(), 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) ========== + if (aDontIterateFrames) { + return NS_OK; + } + + // ---------- Nothing found yet, try next document ------------- + bool hasTriedFirstDoc = false; + do { + // ==== Second inner loop - get another while ==== + if (it != it_end) { + currentContainer = *it; + ++it; + NS_ASSERTION(currentContainer, "We're not at the end yet!"); + 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->GetAllDocShellsInSubtree( + nsIDocShellTreeItem::typeContent, nsIDocShell::ENUMERATE_FORWARDS, + docShells); + it = docShells.begin(); + it_end = docShells.end(); + hasTriedFirstDoc = true; + } while (it != it_end); // ==== 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, findPrev, + getter_AddRefs(presShell), getter_AddRefs(presContext)))) { + continue; + } + + if (findPrev) { + // Reverse mode: swap start and end points, so that we start + // at end of document and go to beginning + RefPtr<nsRange> tempRange = mStartPointRange->CloneRange(); + if (!mEndPointRange) { + mEndPointRange = nsRange::Create(presShell->GetDocument()); + } + + mStartPointRange = mEndPointRange; + mEndPointRange = tempRange; + } + + continue; + } + + // ------------- Failed -------------- + break; + } // end-outer-while: go through all docs + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetSearchString(nsAString& aSearchString) { + aSearchString = mTypeAheadBuffer; + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetFoundLink(Element** aFoundLink) { + NS_ENSURE_ARG_POINTER(aFoundLink); + *aFoundLink = mFoundLink; + NS_IF_ADDREF(*aFoundLink); + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetFoundEditable(Element** aFoundEditable) { + NS_ENSURE_ARG_POINTER(aFoundEditable); + *aFoundEditable = mFoundEditable; + NS_IF_ADDREF(*aFoundEditable); + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetCurrentWindow(mozIDOMWindow** aCurrentWindow) { + NS_ENSURE_ARG_POINTER(aCurrentWindow); + *aCurrentWindow = mCurrentWindow; + NS_IF_ADDREF(*aCurrentWindow); + return NS_OK; +} + +nsresult nsTypeAheadFind::GetSearchContainers( + nsISupports* aContainer, nsISelectionController* aSelectionController, + bool aIsFirstVisiblePreferred, bool aFindPrev, PresShell** 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; + + RefPtr<PresShell> presShell = docShell->GetPresShell(); + + RefPtr<nsPresContext> presContext = docShell->GetPresContext(); + + if (!presShell || !presContext) return NS_ERROR_FAILURE; + + Document* doc = presShell->GetDocument(); + + if (!doc) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIContent> rootContent; + if (doc->IsHTMLOrXHTML()) { + rootContent = doc->GetBody(); + } + + if (!rootContent) { + rootContent = doc->GetRootElement(); + if (!rootContent) { + return NS_ERROR_FAILURE; + } + } + + if (!mSearchRange) { + mSearchRange = nsRange::Create(doc); + } + nsCOMPtr<nsINode> searchRootNode(rootContent); + + mSearchRange->SelectNodeContents(*searchRootNode, IgnoreErrors()); + + if (!mStartPointRange) { + mStartPointRange = nsRange::Create(doc); + } + mStartPointRange->SetStartAndEnd(searchRootNode, 0, searchRootNode, 0); + + if (!mEndPointRange) { + mEndPointRange = nsRange::Create(doc); + } + mEndPointRange->SetStartAndEnd(searchRootNode, searchRootNode->Length(), + searchRootNode, searchRootNode->Length()); + + // Consider current selection as null if + // it's not in the currently focused document + RefPtr<const nsRange> currentSelectionRange; + RefPtr<Document> selectionDocument = GetDocument(); + if (aSelectionController && selectionDocument && selectionDocument == doc) { + RefPtr<Selection> selection = aSelectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL); + if (selection) { + currentSelectionRange = selection->GetRangeAt(0); + } + } + + if (!currentSelectionRange) { + mStartPointRange = mSearchRange->CloneRange(); + // We want to search in the visible selection range. That means that the + // start point needs to be the end if we're looking backwards, or vice + // versa. + mStartPointRange->Collapse(!aFindPrev); + } else { + uint32_t startOffset; + nsCOMPtr<nsINode> startNode; + if (aFindPrev) { + startNode = currentSelectionRange->GetStartContainer(); + startOffset = currentSelectionRange->StartOffset(); + } else { + startNode = currentSelectionRange->GetEndContainer(); + startOffset = currentSelectionRange->EndOffset(); + } + + if (!startNode) { + startNode = rootContent; + } + + // We need to set the start point this way, other methods haven't worked + mStartPointRange->SelectNode(*startNode, IgnoreErrors()); + mStartPointRange->SetStart(*startNode, startOffset, IgnoreErrors()); + mStartPointRange->Collapse(true); // collapse to start + } + + presShell.forget(aPresShell); + presContext.forget(aPresContext); + + return NS_OK; +} + +void nsTypeAheadFind::RangeStartsInsideLink(nsRange* aRange, + bool* aIsInsideLink, + bool* aIsStartingLink) { + *aIsInsideLink = false; + *aIsStartingLink = true; + + // ------- Get nsIContent to test ------- + uint32_t startOffset = aRange->StartOffset(); + + nsCOMPtr<nsIContent> startContent = + nsIContent::FromNodeOrNull(aRange->GetStartContainer()); + if (!startContent) { + MOZ_ASSERT_UNREACHABLE("startContent should never be null"); + return; + } + nsCOMPtr<nsIContent> origContent = startContent; + + if (startContent->IsElement()) { + nsIContent* childContent = aRange->GetChildAtStartOffset(); + if (childContent) { + startContent = childContent; + } + } else if (startOffset > 0) { + const nsTextFragment* textFrag = startContent->GetText(); + if (textFrag) { + // look for non whitespace character before start offset + for (uint32_t index = 0; index < startOffset; index++) { + // FIXME: take content language into account when deciding whitespace. + if (!mozilla::dom::IsSpaceCharacter(textFrag->CharAt(index))) { + *aIsStartingLink = false; // not at start of a node + break; + } + } + } + } + + // ------- Check to see if inside link --------- + + // We now have the correct start node for the range + // Search for links, starting with startNode, and going up parent chain + + while (true) { + // Keep testing while startContent is equal to something, + // eventually we'll run out of ancestors + + if (startContent->IsHTMLElement()) { + nsCOMPtr<mozilla::dom::Link> link(do_QueryInterface(startContent)); + if (link) { + // Check to see if inside HTML link + *aIsInsideLink = startContent->AsElement()->HasAttr(nsGkAtoms::href); + return; + } + } else { + // Any xml element can be an xlink + *aIsInsideLink = + startContent->IsElement() && startContent->AsElement()->HasAttr( + kNameSpaceID_XLink, nsGkAtoms::href); + if (*aIsInsideLink) { + if (!startContent->AsElement()->AttrValueIs( + kNameSpaceID_XLink, nsGkAtoms::type, u"simple"_ns, + eCaseMatters)) { + *aIsInsideLink = false; // Xlink must be type="simple" + } + + return; + } + } + + // Get the parent + nsCOMPtr<nsIContent> parent = startContent->GetParent(); + if (!parent) break; + + nsIContent* parentsFirstChild = parent->GetFirstChild(); + + // We don't want to look at a whitespace-only first child + if (parentsFirstChild && parentsFirstChild->TextIsOnlyWhitespace()) { + parentsFirstChild = parentsFirstChild->GetNextSibling(); + } + + if (parentsFirstChild != startContent) { + // startContent wasn't a first child, so we conclude that + // if this is inside a link, it's not at the beginning of it + *aIsStartingLink = false; + } + + startContent = parent; + } + + *aIsStartingLink = false; +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsTypeAheadFind::Find( + const nsAString& aSearchString, bool aLinksOnly, uint32_t aMode, + bool aDontIterateFrames, uint16_t* aResult) { + if (aMode == nsITypeAheadFind::FIND_PREVIOUS || + aMode == nsITypeAheadFind::FIND_NEXT) { + if (mTypeAheadBuffer.IsEmpty()) { + *aResult = FIND_NOTFOUND; + } else { + FindItNow(aMode, aLinksOnly, false, aDontIterateFrames, aResult); + } + + return NS_OK; + } + + // Find again ignores error return values, so do so here as well. + nsresult rv = FindInternal(aMode, aSearchString, aLinksOnly, + aDontIterateFrames, aResult); + return (aMode == nsITypeAheadFind::FIND_INITIAL) ? rv : NS_OK; +} + +nsresult nsTypeAheadFind::FindInternal(uint32_t aMode, + const nsAString& aSearchString, + bool aLinksOnly, bool aDontIterateFrames, + uint16_t* aResult) { + *aResult = FIND_NOTFOUND; + + RefPtr<Document> doc = GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + RefPtr<PresShell> presShell = doc->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + RefPtr<Selection> 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 { + selection = selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL); + } + + if (selection) { + selection->CollapseToStart(IgnoreErrors()); + } + + if (aSearchString.IsEmpty()) { + mTypeAheadBuffer.Truncate(); + + // These will be initialized to their true values after the first character + // is typed + mStartFindRange = nullptr; + mSelectionController = nullptr; + + *aResult = FIND_FOUND; + return NS_OK; + } + + bool atEnd = false; + bool isInitial = aMode == nsITypeAheadFind::FIND_INITIAL; + if (isInitial) { + if (mTypeAheadBuffer.Length()) { + const nsAString& oldStr = + Substring(mTypeAheadBuffer, 0, mTypeAheadBuffer.Length()); + const nsAString& newStr = + Substring(aSearchString, 0, mTypeAheadBuffer.Length()); + if (oldStr.Equals(newStr)) atEnd = true; + + const nsAString& newStr2 = + Substring(aSearchString, 0, aSearchString.Length()); + const nsAString& oldStr2 = + Substring(mTypeAheadBuffer, 0, aSearchString.Length()); + if (oldStr2.Equals(newStr2)) atEnd = true; + + if (!atEnd) mStartFindRange = nullptr; + } + } + + int32_t bufferLength = mTypeAheadBuffer.Length(); + + mTypeAheadBuffer = aSearchString; + + bool isFirstVisiblePreferred = false; + + // --------- Initialize find if 1st char ---------- + if (bufferLength == 0 && isInitial) { + // If you can see the selection (not collapsed or thru caret browsing), + // or if already focused on a page element, start there. + // Otherwise we're going to start at the first visible element + bool isSelectionCollapsed = !selection || selection->IsCollapsed(); + + // If true, we will scan from top left of visible area + // If false, we will scan from start of selection + isFirstVisiblePreferred = + !atEnd && !mCaretBrowsingOn && isSelectionCollapsed; + if (isFirstVisiblePreferred) { + // Get the focused content. If there is a focused node, ensure the + // selection is at that point. Otherwise, we will just want to start + // from the caret position or the beginning of the document. + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_OK); + + nsCOMPtr<Document> document = presShell->GetDocument(); + if (!document) return NS_ERROR_UNEXPECTED; + + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow(); + RefPtr<Element> focusedElement; + nsCOMPtr<mozIDOMWindowProxy> focusedWindow; + fm->GetFocusedElementForWindow(window, false, + getter_AddRefs(focusedWindow), + getter_AddRefs(focusedElement)); + // If the root element is focused, then it's actually the document + // that has the focus, so ignore this. + if (focusedElement && focusedElement != document->GetRootElement()) { + fm->MoveCaretToFocus(window); + isFirstVisiblePreferred = false; + } + } + } + } + + // ----------- Find the text! --------------------- + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + nsresult rv = FindItNow(aMode, aLinksOnly, isFirstVisiblePreferred, + aDontIterateFrames, aResult); + + // ---------Handle success or failure --------------- + if (NS_SUCCEEDED(rv)) { + if (mTypeAheadBuffer.Length() == 1) { + // If first letter, store where the first find succeeded + // (mStartFindRange) + + mStartFindRange = nullptr; + if (selection) { + RefPtr<const nsRange> startFindRange = selection->GetRangeAt(0); + if (startFindRange) { + mStartFindRange = startFindRange->CloneRange(); + } + } + } + } else if (isInitial) { + // Error sound, except when whole word matching is ON. + if (!mEntireWord && mTypeAheadBuffer.Length() > mLastFindLength) + PlayNotFoundSound(); + } + + SaveFind(); + return NS_OK; +} + +void nsTypeAheadFind::GetSelection(PresShell* aPresShell, + nsISelectionController** aSelCon, + Selection** aDOMSel) { + if (!aPresShell) return; + + // if aCurrentNode is nullptr, get selection for document + *aDOMSel = nullptr; + + nsPresContext* presContext = aPresShell->GetPresContext(); + + nsIFrame* frame = aPresShell->GetRootFrame(); + + if (presContext && frame) { + frame->GetSelectionController(presContext, aSelCon); + if (*aSelCon) { + RefPtr<Selection> sel = + (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL); + sel.forget(aDOMSel); + } + } +} + +NS_IMETHODIMP +nsTypeAheadFind::GetFoundRange(nsRange** aFoundRange) { + NS_ENSURE_ARG_POINTER(aFoundRange); + if (mFoundRange == nullptr) { + *aFoundRange = nullptr; + return NS_OK; + } + + *aFoundRange = mFoundRange->CloneRange().take(); + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::IsRangeVisible(nsRange* aRange, bool aMustBeInViewPort, + bool* aResult) { + *aResult = IsRangeVisible(aRange, aMustBeInViewPort, false, nullptr); + return NS_OK; +} + +bool nsTypeAheadFind::IsRangeVisible(nsRange* aRange, bool aMustBeInViewPort, + bool aGetTopVisibleLeaf, + bool* aUsesIndependentSelection) { + // We need to know if the range start is visible. + // Otherwise, return the first visible range start in aFirstVisibleRange + nsCOMPtr<nsIContent> content = + nsIContent::FromNodeOrNull(aRange->GetStartContainer()); + if (!content) { + return false; + } + + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + return false; // No frame! Not visible then. + } + + if (!frame->StyleVisibility()->IsVisible()) { + return false; + } + + // Detect if we are _inside_ a text control, or something else with its own + // selection controller. + if (aUsesIndependentSelection) { + *aUsesIndependentSelection = + frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION); + } + + return aMustBeInViewPort ? IsRangeRendered(aRange) : true; +} + +NS_IMETHODIMP +nsTypeAheadFind::IsRangeRendered(nsRange* aRange, bool* aResult) { + *aResult = IsRangeRendered(aRange); + return NS_OK; +} + +bool nsTypeAheadFind::IsRangeRendered(nsRange* aRange) { + using FrameForPointOption = nsLayoutUtils::FrameForPointOption; + nsCOMPtr<nsIContent> content = + nsIContent::FromNodeOrNull(aRange->GetClosestCommonInclusiveAncestor()); + if (!content) { + return false; + } + + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + return false; // No frame! Not visible then. + } + + if (!frame->StyleVisibility()->IsVisible()) { + return false; + } + + // Having a primary frame doesn't mean that the range is visible inside the + // viewport. Do a hit-test to determine that quickly and properly. + AutoTArray<nsIFrame*, 8> frames; + nsIFrame* rootFrame = frame->PresShell()->GetRootFrame(); + RefPtr<nsRange> range = static_cast<nsRange*>(aRange); + + // NOTE(emilio): This used to flush layout, _after_ checking style above. + // Instead, don't flush. + RefPtr<mozilla::dom::DOMRectList> rects = + range->GetClientRects(true, /* aFlushLayout = */ false); + for (uint32_t i = 0; i < rects->Length(); ++i) { + RefPtr<mozilla::dom::DOMRect> rect = rects->Item(i); + nsRect r(nsPresContext::CSSPixelsToAppUnits((float)rect->X()), + nsPresContext::CSSPixelsToAppUnits((float)rect->Y()), + nsPresContext::CSSPixelsToAppUnits((float)rect->Width()), + nsPresContext::CSSPixelsToAppUnits((float)rect->Height())); + // Append visible frames to frames array. + nsLayoutUtils::GetFramesForArea( + RelativeTo{rootFrame}, r, frames, + {{FrameForPointOption::IgnorePaintSuppression, + FrameForPointOption::IgnoreRootScrollFrame, + FrameForPointOption::OnlyVisible}}); + + // See if any of the frames contain the content. If they do, then the range + // is visible. We search for the content rather than the original frame, + // because nsTextContinuation frames might be returned instead of the + // original frame. + for (const auto& f : frames) { + if (f->GetContent() == content) { + return true; + } + } + + frames.ClearAndRetainStorage(); + } + + return false; +} + +already_AddRefed<Document> nsTypeAheadFind::GetDocument() { + // Try the last document we found and ensure it's sane. + RefPtr<Document> doc(mDocument); + if (doc && doc->GetPresShell() && doc->GetDocShell()) { + return doc.forget(); + } + + // Otherwise fall back to the document from which we were initialized (the one + // from mDocShell). + mDocument = nullptr; + nsCOMPtr<nsIDocShell> ds = do_QueryReferent(mDocShell); + if (!ds) { + return nullptr; + } + doc = ds->GetExtantDocument(); + mDocument = doc; + return doc.forget(); +} diff --git a/toolkit/components/typeaheadfind/nsTypeAheadFind.h b/toolkit/components/typeaheadfind/nsTypeAheadFind.h new file mode 100644 index 0000000000..ed0a4c1c2c --- /dev/null +++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/WeakPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsCycleCollectionParticipant.h" +#include "nsISelectionController.h" +#include "nsIDocShell.h" +#include "nsIObserver.h" +#include "nsIFind.h" +#include "nsIWebBrowserFind.h" +#include "nsWeakReference.h" +#include "nsITypeAheadFind.h" + +class nsPIDOMWindowInner; +class nsPresContext; +class nsRange; + +namespace mozilla { +class PresShell; +namespace dom { +class Document; +class Element; +class Selection; +} // namespace dom +} // namespace mozilla + +#define TYPEAHEADFIND_NOTFOUND_WAV_URL "chrome://global/content/notfound.wav" + +class nsTypeAheadFind : public nsITypeAheadFind, + public nsIObserver, + public nsSupportsWeakReference { + public: + nsTypeAheadFind(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSITYPEAHEADFIND + NS_DECL_NSIOBSERVER + + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTypeAheadFind, nsITypeAheadFind) + + protected: + virtual ~nsTypeAheadFind(); + + nsresult PrefsReset(); + + void SaveFind(); + void PlayNotFoundSound(); + nsresult GetWebBrowserFind(nsIDocShell* aDocShell, + nsIWebBrowserFind** aWebBrowserFind); + + MOZ_CAN_RUN_SCRIPT nsresult FindInternal(uint32_t aMode, + const nsAString& aSearchString, + bool aLinksOnly, + bool aDontIterateFrames, + uint16_t* aResult); + + void RangeStartsInsideLink(nsRange* aRange, bool* aIsInsideLink, + bool* aIsStartingLink); + + void GetSelection(mozilla::PresShell* aPresShell, + nsISelectionController** aSelCon, + mozilla::dom::Selection** aDomSel); + bool IsRangeVisible(nsRange* aRange, bool aMustBeVisible, + bool aGetTopVisibleLeaf, bool* aUsesIndependentSelection); + bool IsRangeRendered(nsRange* aRange); + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsresult FindItNow(uint32_t aMode, bool aIsLinksOnly, + bool aIsFirstVisiblePreferred, bool aDontIterateFrames, + uint16_t* aResult); + nsresult GetSearchContainers(nsISupports* aContainer, + nsISelectionController* aSelectionController, + bool aIsFirstVisiblePreferred, bool aFindPrev, + mozilla::PresShell** aPresShell, + nsPresContext** aPresContext); + + // Get the document we should search on. + already_AddRefed<mozilla::dom::Document> GetDocument(); + + void ReleaseStrongMemberVariables(); + + // Current find state + nsString mTypeAheadBuffer; + nsCString mNotFoundSoundURL; + + // PRBools are used instead of PRPackedBools because the address of the + // boolean variable is getting passed into a method. + bool mStartLinksOnlyPref; + bool mCaretBrowsingOn; + bool mDidAddObservers; + nsCOMPtr<mozilla::dom::Element> + mFoundLink; // Most recent elem found, if a link + nsCOMPtr<mozilla::dom::Element> + mFoundEditable; // Most recent elem found, if editable + RefPtr<nsRange> mFoundRange; // Most recent range found + nsCOMPtr<nsPIDOMWindowInner> mCurrentWindow; + // mLastFindLength is the character length of the last find string. It is + // used for disabling the "not found" sound when using backspace or delete + uint32_t mLastFindLength; + + // where selection was when user started the find + RefPtr<nsRange> mStartFindRange; + RefPtr<nsRange> mSearchRange; + RefPtr<nsRange> mStartPointRange; + RefPtr<nsRange> mEndPointRange; + + // Cached useful interfaces + nsCOMPtr<nsIFind> mFind; + + bool mCaseSensitive; + bool mEntireWord; + bool mMatchDiacritics; + + bool EnsureFind() { + if (mFind) { + return true; + } + + mFind = do_CreateInstance("@mozilla.org/embedcomp/rangefind;1"); + if (!mFind) { + return false; + } + + mFind->SetCaseSensitive(mCaseSensitive); + mFind->SetEntireWord(mEntireWord); + mFind->SetMatchDiacritics(mMatchDiacritics); + + return true; + } + + nsCOMPtr<nsIWebBrowserFind> mWebBrowserFind; + + // The focused content window that we're listening to and its cached objects. + // This is always the root of the subtree we're finding. + nsWeakPtr mDocShell; + // The document where we're currently searching. + mozilla::WeakPtr<mozilla::dom::Document> mDocument; + nsWeakPtr mSelectionController; + // Most recent match's controller +}; |