diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/components/typeaheadfind | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
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 | 95 | ||||
-rw-r--r-- | toolkit/components/typeaheadfind/nsTypeAheadFind.cpp | 1325 | ||||
-rw-r--r-- | toolkit/components/typeaheadfind/nsTypeAheadFind.h | 134 |
6 files changed, 1582 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 000000000..c6fd5cb86 --- /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 000000000..173caf77a --- /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 000000000..990c1d27c --- /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 000000000..379d2c2a2 --- /dev/null +++ b/toolkit/components/typeaheadfind/nsITypeAheadFind.idl @@ -0,0 +1,95 @@ +/* -*- 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 "domstubs.idl" // nsIDOMElement, nsIDOMWindow +#include "nsISupports.idl" // nsISupports + + +/******************************** Declarations *******************************/ + +interface mozIDOMWindow; +interface nsIDocShell; + + +/****************************** 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); + + /* Find another match in the page. */ + unsigned short findAgain(in boolean findBackwards, in boolean aLinksOnly); + + /* Return the range of the most recent match. */ + nsIDOMRange 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 */ + boolean isRangeVisible(in nsIDOMRange aRange, in boolean aMustBeInViewPort); + + /******************************* Attributes ******************************/ + + readonly attribute AString searchString; + // Most recent search string + attribute boolean caseSensitive; // Searches are case sensitive + attribute boolean entireWord; // Search for whole words only + readonly attribute nsIDOMElement foundLink; + // Most recent elem found, if a link + readonly attribute nsIDOMElement foundEditable; + // Most recent elem found, if editable + readonly attribute mozIDOMWindow currentWindow; + // Window of most recent match + + + /******************************* Constants *******************************/ + + /* 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 000000000..674681581 --- /dev/null +++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp @@ -0,0 +1,1325 @@ +/* -*- 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 "nsMemory.h" +#include "nsIServiceManager.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/Services.h" +#include "nsIWebBrowserChrome.h" +#include "nsCURILoader.h" +#include "nsCycleCollectionParticipant.h" +#include "nsNetUtil.h" +#include "nsIURL.h" +#include "nsIURI.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsISimpleEnumerator.h" +#include "nsPIDOMWindow.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsString.h" +#include "nsCRT.h" + +#include "nsIDOMNode.h" +#include "mozilla/dom/Element.h" +#include "nsIFrame.h" +#include "nsFrameTraversal.h" +#include "nsIImageDocument.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDocument.h" +#include "nsISelection.h" +#include "nsTextFragment.h" +#include "nsIDOMNSEditableElement.h" +#include "nsIEditor.h" + +#include "nsIDocShellTreeItem.h" +#include "nsIWebNavigation.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsContentCID.h" +#include "nsLayoutCID.h" +#include "nsWidgetsCID.h" +#include "nsIFormControl.h" +#include "nsNameSpaceManager.h" +#include "nsIWindowWatcher.h" +#include "nsIObserverService.h" +#include "nsFocusManager.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Link.h" +#include "nsRange.h" +#include "nsXBLBinding.h" + +#include "nsTypeAheadFind.h" + +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(nsTypeAheadFind, mFoundLink, mFoundEditable, + mCurrentWindow, mStartFindRange, mSearchRange, + mStartPointRange, mEndPointRange, mSoundInterface, + mFind, mFoundRange) + +static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); + +#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1" + +nsTypeAheadFind::nsTypeAheadFind(): + mStartLinksOnlyPref(false), + mCaretBrowsingOn(false), + mDidAddObservers(false), + mLastFindLength(0), + mIsSoundInitialized(false), + mCaseSensitive(false), + mEntireWord(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); + + // ----------- 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); + nsXPIDLCString soundStr; + if (isSoundEnabled) + prefBranch->GetCharPref("accessibility.typeaheadfind.soundURL", getter_Copies(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::SetDocShell(nsIDocShell* aDocShell) +{ + mDocShell = do_GetWeakReference(aDocShell); + + mWebBrowserFind = do_GetInterface(aDocShell); + NS_ENSURE_TRUE(mWebBrowserFind, NS_ERROR_FAILURE); + + nsCOMPtr<nsIPresShell> presShell; + presShell = aDocShell->GetPresShell(); + mPresShell = do_GetWeakReference(presShell); + + 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; +} + +NS_IMETHODIMP +nsTypeAheadFind::CollapseSelection() +{ + nsCOMPtr<nsISelectionController> selectionController = + do_QueryReferent(mSelectionController); + if (!selectionController) { + return NS_OK; + } + + nsCOMPtr<nsISelection> selection; + selectionController->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + if (selection) + selection->CollapseToStart(); + + 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(); + } else if (!nsCRT::strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) && + SameCOMIdentity(aSubject, mCurrentWindow)) { + ReleaseStrongMemberVariables(); + } + + return NS_OK; +} + +void +nsTypeAheadFind::SaveFind() +{ + if (mWebBrowserFind) + mWebBrowserFind->SetSearchString(mTypeAheadBuffer.get()); + + // save the length of this find for "not found" sound + mLastFindLength = mTypeAheadBuffer.Length(); +} + +void +nsTypeAheadFind::PlayNotFoundSound() +{ + if (mNotFoundSoundURL.IsEmpty()) // no sound + return; + + if (!mSoundInterface) + mSoundInterface = do_CreateInstance("@mozilla.org/sound;1"); + + if (mSoundInterface) { + mIsSoundInitialized = true; + + if (mNotFoundSoundURL.EqualsLiteral("beep")) { + mSoundInterface->Beep(); + return; + } + + nsCOMPtr<nsIURI> soundURI; + if (mNotFoundSoundURL.EqualsLiteral("default")) + NS_NewURI(getter_AddRefs(soundURI), NS_LITERAL_CSTRING(TYPEAHEADFIND_NOTFOUND_WAV_URL)); + else + NS_NewURI(getter_AddRefs(soundURI), mNotFoundSoundURL); + + nsCOMPtr<nsIURL> soundURL(do_QueryInterface(soundURI)); + if (soundURL) + mSoundInterface->Play(soundURL); + } +} + +nsresult +nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly, + bool aIsFirstVisiblePreferred, bool aFindPrev, + uint16_t* aResult) +{ + *aResult = FIND_NOTFOUND; + mFoundLink = nullptr; + mFoundEditable = nullptr; + mFoundRange = nullptr; + mCurrentWindow = nullptr; + nsCOMPtr<nsIPresShell> startingPresShell (GetPresShell()); + if (!startingPresShell) { + nsCOMPtr<nsIDocShell> ds = do_QueryReferent(mDocShell); + NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE); + + startingPresShell = ds->GetPresShell(); + mPresShell = do_GetWeakReference(startingPresShell); + } + + nsCOMPtr<nsIPresShell> presShell(aPresShell); + + if (!presShell) { + presShell = startingPresShell; // this is the current document + + if (!presShell) + return NS_ERROR_FAILURE; + } + + // There could be unflushed notifications which hide textareas or other + // elements that we don't want to find text in. + presShell->FlushPendingNotifications(Flush_Layout); + + RefPtr<nsPresContext> presContext = presShell->GetPresContext(); + + if (!presContext) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsISelection> selection; + nsCOMPtr<nsISelectionController> selectionController = + do_QueryReferent(mSelectionController); + if (!selectionController) { + GetSelection(presShell, getter_AddRefs(selectionController), + getter_AddRefs(selection)); // cache for reuse + mSelectionController = do_GetWeakReference(selectionController); + } else { + selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); + } + + nsCOMPtr<nsIDocShell> startingDocShell(presContext->GetDocShell()); + NS_ASSERTION(startingDocShell, "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]"); + if (!startingDocShell) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDocShellTreeItem> rootContentTreeItem; + nsCOMPtr<nsIDocShell> currentDocShell; + + startingDocShell->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem)); + nsCOMPtr<nsIDocShell> rootContentDocShell = + do_QueryInterface(rootContentTreeItem); + + if (!rootContentDocShell) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsISimpleEnumerator> docShellEnumerator; + rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(docShellEnumerator)); + + // Default: can start at the current document + nsCOMPtr<nsISupports> currentContainer = + do_QueryInterface(rootContentDocShell); + + // Iterate up to current shell, if there's more than 1 that we're + // dealing with + bool hasMoreDocShells; + + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) && hasMoreDocShells) { + docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); + currentDocShell = do_QueryInterface(currentContainer); + if (!currentDocShell || currentDocShell == startingDocShell || aIsFirstVisiblePreferred) + break; + } + + // ------------ Get ranges ready ---------------- + nsCOMPtr<nsIDOMRange> returnRange; + if (NS_FAILED(GetSearchContainers(currentContainer, + (!aIsFirstVisiblePreferred || + mStartFindRange) ? + selectionController.get() : nullptr, + aIsFirstVisiblePreferred, aFindPrev, + getter_AddRefs(presShell), + getter_AddRefs(presContext)))) { + return NS_ERROR_FAILURE; + } + + int16_t rangeCompareResult = 0; + if (!mStartPointRange) { + mStartPointRange = new nsRange(presShell->GetDocument()); + } + + mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, mSearchRange, &rangeCompareResult); + // No need to wrap find in doc if starting at beginning + bool hasWrapped = (rangeCompareResult < 0); + + if (mTypeAheadBuffer.IsEmpty() || !EnsureFind()) + return NS_ERROR_FAILURE; + + mFind->SetFindBackwards(aFindPrev); + + while (true) { // ----- Outer while loop: go through all docs ----- + while (true) { // === Inner while loop: go through a single doc === + mFind->Find(mTypeAheadBuffer.get(), mSearchRange, mStartPointRange, + mEndPointRange, getter_AddRefs(returnRange)); + + if (!returnRange) + break; // Nothing found in this doc, go to outer loop (try next doc) + + // ------- Test resulting found range for success conditions ------ + bool isInsideLink = false, isStartingLink = false; + + if (aIsLinksOnly) { + // Don't check if inside link when searching all text + RangeStartsInsideLink(returnRange, presShell, &isInsideLink, + &isStartingLink); + } + + bool usesIndependentSelection; + if (!IsRangeVisible(presShell, presContext, returnRange, + aIsFirstVisiblePreferred, false, + getter_AddRefs(mStartPointRange), + &usesIndependentSelection) || + (aIsLinksOnly && !isInsideLink) || + (mStartLinksOnlyPref && aIsLinksOnly && !isStartingLink)) { + // ------ Failure ------ + // At this point mStartPointRange got updated to the first + // visible range in the viewport. We _may_ be able to just + // start there, if it's not taking us in the wrong direction. + if (aFindPrev) { + // We can continue at the end of mStartPointRange if its end is before + // the start of returnRange or coincides with it. Otherwise, we need + // to continue at the start of returnRange. + int16_t compareResult; + nsresult rv = + mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_END, + returnRange, &compareResult); + if (NS_SUCCEEDED(rv) && compareResult <= 0) { + // OK to start at the end of mStartPointRange + mStartPointRange->Collapse(false); + } else { + // Start at the beginning of returnRange + returnRange->CloneRange(getter_AddRefs(mStartPointRange)); + mStartPointRange->Collapse(true); + } + } else { + // We can continue at the start of mStartPointRange if its start is + // after the end of returnRange or coincides with it. Otherwise, we + // need to continue at the end of returnRange. + int16_t compareResult; + nsresult rv = + mStartPointRange->CompareBoundaryPoints(nsIDOMRange::END_TO_START, + returnRange, &compareResult); + if (NS_SUCCEEDED(rv) && compareResult >= 0) { + // OK to start at the start of mStartPointRange + mStartPointRange->Collapse(true); + } else { + // Start at the end of returnRange + returnRange->CloneRange(getter_AddRefs(mStartPointRange)); + mStartPointRange->Collapse(false); + } + } + continue; + } + + mFoundRange = returnRange; + + // ------ Success! ------- + // Hide old selection (new one may be on a different controller) + if (selection) { + selection->CollapseToStart(); + SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON); + } + + // Make sure new document is selected + if (presShell != startingPresShell) { + // We are in a new document (because of frames/iframes) + mPresShell = do_GetWeakReference(presShell); + } + + nsCOMPtr<nsIDocument> document = + do_QueryInterface(presShell->GetDocument()); + NS_ASSERTION(document, "Wow, presShell doesn't have document!"); + if (!document) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow(); + NS_ASSERTION(window, "document has no window"); + if (!window) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (usesIndependentSelection) { + /* If a search result is found inside an editable element, we'll focus + * the element only if focus is in our content window, i.e. + * |if (focusedWindow.top == ourWindow.top)| */ + bool shouldFocusEditableElement = false; + if (fm) { + nsCOMPtr<mozIDOMWindowProxy> focusedWindow; + nsresult rv = fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); + if (NS_SUCCEEDED(rv) && focusedWindow) { + auto* fwPI = nsPIDOMWindowOuter::From(focusedWindow); + nsCOMPtr<nsIDocShellTreeItem> fwTreeItem + (do_QueryInterface(fwPI->GetDocShell(), &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIDocShellTreeItem> fwRootTreeItem; + rv = fwTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(fwRootTreeItem)); + if (NS_SUCCEEDED(rv) && fwRootTreeItem == rootContentTreeItem) + shouldFocusEditableElement = true; + } + } + } + + // We may be inside an editable element, and therefore the selection + // may be controlled by a different selection controller. Walk up the + // chain of parent nodes to see if we find one. + nsCOMPtr<nsIDOMNode> node; + returnRange->GetStartContainer(getter_AddRefs(node)); + while (node) { + nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(node); + if (editable) { + // Inside an editable element. Get the correct selection + // controller and selection. + nsCOMPtr<nsIEditor> editor; + editable->GetEditor(getter_AddRefs(editor)); + NS_ASSERTION(editor, "Editable element has no editor!"); + if (!editor) { + break; + } + editor->GetSelectionController( + getter_AddRefs(selectionController)); + if (selectionController) { + selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + } + mFoundEditable = do_QueryInterface(node); + + if (!shouldFocusEditableElement) + break; + + // Otherwise move focus/caret to editable element + if (fm) + fm->SetFocus(mFoundEditable, 0); + break; + } + nsIDOMNode* tmp = node; + tmp->GetParentNode(getter_AddRefs(node)); + } + + // If we reach here without setting mFoundEditable, then something + // besides editable elements can cause us to have an independent + // selection controller. I don't know whether this is possible. + // Currently, we simply fall back to grabbing the document's selection + // controller in this case. Perhaps we should reject this find match + // and search again. + NS_ASSERTION(mFoundEditable, "Independent selection controller on " + "non-editable element!"); + } + + if (!mFoundEditable) { + // Not using a separate selection controller, so just get the + // document's controller and selection. + GetSelection(presShell, getter_AddRefs(selectionController), + getter_AddRefs(selection)); + } + mSelectionController = do_GetWeakReference(selectionController); + + // Select the found text + if (selection) { + selection->RemoveAllRanges(); + selection->AddRange(returnRange); + } + + if (!mFoundEditable && fm) { + 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) ========== + + // ---------- Nothing found yet, try next document ------------- + bool hasTriedFirstDoc = false; + do { + // ==== Second inner loop - get another while ==== + if (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) + && hasMoreDocShells) { + docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); + NS_ASSERTION(currentContainer, "HasMoreElements lied to us!"); + currentDocShell = do_QueryInterface(currentContainer); + + if (currentDocShell) + break; + } + else if (hasTriedFirstDoc) // Avoid potential infinite loop + return NS_ERROR_FAILURE; // No content doc shells + + // Reached last doc shell, loop around back to first doc shell + rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(docShellEnumerator)); + hasTriedFirstDoc = true; + } while (docShellEnumerator); // ==== end second inner while === + + bool continueLoop = false; + if (currentDocShell != startingDocShell) + continueLoop = true; // Try next document + else if (!hasWrapped || aIsFirstVisiblePreferred) { + // Finished searching through docshells: + // If aFirstVisiblePreferred == true, we may need to go through all + // docshells twice -once to look for visible matches, the second time + // for any match + aIsFirstVisiblePreferred = false; + hasWrapped = true; + continueLoop = true; // Go through all docs again + } + + if (continueLoop) { + if (NS_FAILED(GetSearchContainers(currentContainer, nullptr, + aIsFirstVisiblePreferred, aFindPrev, + getter_AddRefs(presShell), + getter_AddRefs(presContext)))) { + continue; + } + + if (aFindPrev) { + // Reverse mode: swap start and end points, so that we start + // at end of document and go to beginning + nsCOMPtr<nsIDOMRange> tempRange; + mStartPointRange->CloneRange(getter_AddRefs(tempRange)); + if (!mEndPointRange) { + mEndPointRange = new nsRange(presShell->GetDocument()); + } + + mStartPointRange = mEndPointRange; + mEndPointRange = tempRange; + } + + continue; + } + + // ------------- Failed -------------- + break; + } // end-outer-while: go through all docs + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetSearchString(nsAString& aSearchString) +{ + aSearchString = mTypeAheadBuffer; + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetFoundLink(nsIDOMElement** aFoundLink) +{ + NS_ENSURE_ARG_POINTER(aFoundLink); + *aFoundLink = mFoundLink; + NS_IF_ADDREF(*aFoundLink); + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::GetFoundEditable(nsIDOMElement** 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, + nsIPresShell **aPresShell, + nsPresContext **aPresContext) +{ + NS_ENSURE_ARG_POINTER(aContainer); + NS_ENSURE_ARG_POINTER(aPresShell); + NS_ENSURE_ARG_POINTER(aPresContext); + + *aPresShell = nullptr; + *aPresContext = nullptr; + + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer)); + if (!docShell) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + + RefPtr<nsPresContext> presContext; + docShell->GetPresContext(getter_AddRefs(presContext)); + + if (!presShell || !presContext) + return NS_ERROR_FAILURE; + + nsIDocument* doc = presShell->GetDocument(); + + if (!doc) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIContent> rootContent; + nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(doc)); + if (htmlDoc) { + nsCOMPtr<nsIDOMHTMLElement> bodyEl; + htmlDoc->GetBody(getter_AddRefs(bodyEl)); + rootContent = do_QueryInterface(bodyEl); + } + + if (!rootContent) + rootContent = doc->GetRootElement(); + + nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootContent)); + + if (!rootNode) + return NS_ERROR_FAILURE; + + if (!mSearchRange) { + mSearchRange = new nsRange(doc); + } + nsCOMPtr<nsIDOMNode> searchRootNode = rootNode; + + // Hack for XMLPrettyPrinter. nsFind can't handle complex anonymous content. + // If the root node has an XBL binding then there's not much we can do in + // in general, but we can try searching the binding's first child, which + // in the case of XMLPrettyPrinter contains the visible pretty-printed + // content. + nsXBLBinding* binding = rootContent->GetXBLBinding(); + if (binding) { + nsIContent* anonContent = binding->GetAnonymousContent(); + if (anonContent) { + searchRootNode = do_QueryInterface(anonContent->GetFirstChild()); + } + } + mSearchRange->SelectNodeContents(searchRootNode); + + if (!mStartPointRange) { + mStartPointRange = new nsRange(doc); + } + mStartPointRange->SetStart(searchRootNode, 0); + mStartPointRange->Collapse(true); // collapse to start + + if (!mEndPointRange) { + mEndPointRange = new nsRange(doc); + } + nsCOMPtr<nsINode> searchRootTmp = do_QueryInterface(searchRootNode); + mEndPointRange->SetEnd(searchRootNode, searchRootTmp->Length()); + mEndPointRange->Collapse(false); // collapse to end + + // Consider current selection as null if + // it's not in the currently focused document + nsCOMPtr<nsIDOMRange> currentSelectionRange; + nsCOMPtr<nsIPresShell> selectionPresShell = GetPresShell(); + if (aSelectionController && selectionPresShell && selectionPresShell == presShell) { + nsCOMPtr<nsISelection> selection; + aSelectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); + if (selection) + selection->GetRangeAt(0, getter_AddRefs(currentSelectionRange)); + } + + if (!currentSelectionRange) { + // Ensure visible range, move forward if necessary + // This uses ignores the return value, but usese the side effect of + // IsRangeVisible. It returns the first visible range after searchRange + IsRangeVisible(presShell, presContext, mSearchRange, + aIsFirstVisiblePreferred, true, + getter_AddRefs(mStartPointRange), nullptr); + } + else { + int32_t startOffset; + nsCOMPtr<nsIDOMNode> startNode; + if (aFindPrev) { + currentSelectionRange->GetStartContainer(getter_AddRefs(startNode)); + currentSelectionRange->GetStartOffset(&startOffset); + } else { + currentSelectionRange->GetEndContainer(getter_AddRefs(startNode)); + currentSelectionRange->GetEndOffset(&startOffset); + } + if (!startNode) + startNode = rootNode; + + // We need to set the start point this way, other methods haven't worked + mStartPointRange->SelectNode(startNode); + mStartPointRange->SetStart(startNode, startOffset); + } + + mStartPointRange->Collapse(true); // collapse to start + + presShell.forget(aPresShell); + presContext.forget(aPresContext); + + return NS_OK; +} + +void +nsTypeAheadFind::RangeStartsInsideLink(nsIDOMRange *aRange, + nsIPresShell *aPresShell, + bool *aIsInsideLink, + bool *aIsStartingLink) +{ + *aIsInsideLink = false; + *aIsStartingLink = true; + + // ------- Get nsIContent to test ------- + nsCOMPtr<nsIDOMNode> startNode; + nsCOMPtr<nsIContent> startContent, origContent; + aRange->GetStartContainer(getter_AddRefs(startNode)); + int32_t startOffset; + aRange->GetStartOffset(&startOffset); + + startContent = do_QueryInterface(startNode); + if (!startContent) { + NS_NOTREACHED("startContent should never be null"); + return; + } + origContent = startContent; + + if (startContent->IsElement()) { + nsIContent *childContent = startContent->GetChildAt(startOffset); + if (childContent) { + startContent = childContent; + } + } + else if (startOffset > 0) { + const nsTextFragment *textFrag = startContent->GetText(); + if (textFrag) { + // look for non whitespace character before start offset + for (int32_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 + + nsCOMPtr<nsIAtom> hrefAtom(NS_Atomize("href")); + nsCOMPtr<nsIAtom> typeAtom(NS_Atomize("type")); + + 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->HasAttr(kNameSpaceID_None, hrefAtom); + return; + } + } + else { + // Any xml element can be an xlink + *aIsInsideLink = startContent->HasAttr(kNameSpaceID_XLink, hrefAtom); + if (*aIsInsideLink) { + if (!startContent->AttrValueIs(kNameSpaceID_XLink, typeAtom, + NS_LITERAL_STRING("simple"), + 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; +} + +/* Find another match in the page. */ +NS_IMETHODIMP +nsTypeAheadFind::FindAgain(bool aFindBackwards, bool aLinksOnly, + uint16_t* aResult) + +{ + *aResult = FIND_NOTFOUND; + + if (!mTypeAheadBuffer.IsEmpty()) + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + FindItNow(nullptr, aLinksOnly, false, aFindBackwards, aResult); + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::Find(const nsAString& aSearchString, bool aLinksOnly, + uint16_t* aResult) +{ + *aResult = FIND_NOTFOUND; + + nsCOMPtr<nsIPresShell> presShell (GetPresShell()); + if (!presShell) { + nsCOMPtr<nsIDocShell> ds (do_QueryReferent(mDocShell)); + NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE); + + presShell = ds->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + mPresShell = do_GetWeakReference(presShell); + } + + nsCOMPtr<nsISelection> selection; + nsCOMPtr<nsISelectionController> selectionController = + do_QueryReferent(mSelectionController); + if (!selectionController) { + GetSelection(presShell, getter_AddRefs(selectionController), + getter_AddRefs(selection)); // cache for reuse + mSelectionController = do_GetWeakReference(selectionController); + } else { + selectionController->GetSelection( + nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); + } + + if (selection) + selection->CollapseToStart(); + + 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; + 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; + } + + if (!mIsSoundInitialized && !mNotFoundSoundURL.IsEmpty()) { + // This makes sure system sound library is loaded so that + // there's no lag before the first sound is played + // by waiting for the first keystroke, we still get the startup time benefits. + mIsSoundInitialized = true; + mSoundInterface = do_CreateInstance("@mozilla.org/sound;1"); + if (mSoundInterface && !mNotFoundSoundURL.EqualsLiteral("beep")) { + mSoundInterface->Init(); + } + } + +#ifdef XP_WIN + // After each keystroke, ensure sound object is destroyed, to free up memory + // allocated for error sound, otherwise Windows' nsISound impl + // holds onto the last played sound, using up memory. + mSoundInterface = nullptr; +#endif + + int32_t bufferLength = mTypeAheadBuffer.Length(); + + mTypeAheadBuffer = aSearchString; + + bool isFirstVisiblePreferred = false; + + // --------- Initialize find if 1st char ---------- + if (bufferLength == 0) { + // 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 = true; + if (selection) + selection->GetIsCollapsed(&isSelectionCollapsed); + + // 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<nsIDocument> document = + do_QueryInterface(presShell->GetDocument()); + if (!document) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + nsPIDOMWindowOuter* window = document->GetWindow(); + nsCOMPtr<nsIDOMElement> 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 && + !SameCOMIdentity(focusedElement, document->GetRootElement())) { + fm->MoveCaretToFocus(window); + isFirstVisiblePreferred = false; + } + } + } + } + + // ----------- Find the text! --------------------- + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + nsresult rv = FindItNow(nullptr, aLinksOnly, isFirstVisiblePreferred, + false, 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) { + nsCOMPtr<nsIDOMRange> startFindRange; + selection->GetRangeAt(0, getter_AddRefs(startFindRange)); + if (startFindRange) + startFindRange->CloneRange(getter_AddRefs(mStartFindRange)); + } + } + } + else { + // Error sound + if (mTypeAheadBuffer.Length() > mLastFindLength) + PlayNotFoundSound(); + } + + SaveFind(); + return NS_OK; +} + +void +nsTypeAheadFind::GetSelection(nsIPresShell *aPresShell, + nsISelectionController **aSelCon, + nsISelection **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) { + (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL, + aDOMSel); + } + } +} + +NS_IMETHODIMP +nsTypeAheadFind::GetFoundRange(nsIDOMRange** aFoundRange) +{ + NS_ENSURE_ARG_POINTER(aFoundRange); + if (mFoundRange == nullptr) { + *aFoundRange = nullptr; + return NS_OK; + } + + mFoundRange->CloneRange(aFoundRange); + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadFind::IsRangeVisible(nsIDOMRange *aRange, + bool aMustBeInViewPort, + bool *aResult) +{ + // Jump through hoops to extract the docShell from the range. + nsCOMPtr<nsIDOMNode> node; + aRange->GetStartContainer(getter_AddRefs(node)); + nsCOMPtr<nsIDOMDocument> document; + node->GetOwnerDocument(getter_AddRefs(document)); + nsCOMPtr<mozIDOMWindowProxy> window; + document->GetDefaultView(getter_AddRefs(window)); + nsCOMPtr<nsIWebNavigation> navNav (do_GetInterface(window)); + nsCOMPtr<nsIDocShell> docShell (do_GetInterface(navNav)); + + // Set up the arguments needed to check if a range is visible. + nsCOMPtr<nsIPresShell> presShell (docShell->GetPresShell()); + RefPtr<nsPresContext> presContext = presShell->GetPresContext(); + nsCOMPtr<nsIDOMRange> startPointRange = new nsRange(presShell->GetDocument()); + *aResult = IsRangeVisible(presShell, presContext, aRange, + aMustBeInViewPort, false, + getter_AddRefs(startPointRange), + nullptr); + return NS_OK; +} + +bool +nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell, + nsPresContext *aPresContext, + nsIDOMRange *aRange, bool aMustBeInViewPort, + bool aGetTopVisibleLeaf, + nsIDOMRange **aFirstVisibleRange, + bool *aUsesIndependentSelection) +{ + NS_ASSERTION(aPresShell && aPresContext && aRange && aFirstVisibleRange, + "params are invalid"); + + // We need to know if the range start is visible. + // Otherwise, return the first visible range start + // in aFirstVisibleRange + + aRange->CloneRange(aFirstVisibleRange); + nsCOMPtr<nsIDOMNode> node; + aRange->GetStartContainer(getter_AddRefs(node)); + + nsCOMPtr<nsIContent> content(do_QueryInterface(node)); + 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->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION); + } + + // ---- We have a frame ---- + if (!aMustBeInViewPort) + return true; // Don't need it to be on screen, just in rendering tree + + // Get the next in flow frame that contains the range start + int32_t startRangeOffset, startFrameOffset, endFrameOffset; + aRange->GetStartOffset(&startRangeOffset); + while (true) { + frame->GetOffsets(startFrameOffset, endFrameOffset); + if (startRangeOffset < endFrameOffset) + break; + + nsIFrame *nextContinuationFrame = frame->GetNextContinuation(); + if (nextContinuationFrame) + frame = nextContinuationFrame; + else + break; + } + + // Set up the variables we need, return true if we can't get at them all + const uint16_t kMinPixels = 12; + nscoord minDistance = nsPresContext::CSSPixelsToAppUnits(kMinPixels); + + // Get the bounds of the current frame, relative to the current view. + // We don't use the more accurate AccGetBounds, because that is + // more expensive and the STATE_OFFSCREEN flag that this is used + // for only needs to be a rough indicator + nsRectVisibility rectVisibility = nsRectVisibility_kAboveViewport; + + if (!aGetTopVisibleLeaf && !frame->GetRect().IsEmpty()) { + rectVisibility = + aPresShell->GetRectVisibility(frame, + nsRect(nsPoint(0,0), frame->GetSize()), + minDistance); + + if (rectVisibility != nsRectVisibility_kAboveViewport) { + return true; + } + } + + // We know that the target range isn't usable because it's not in the + // view port. Move range forward to first visible point, + // this speeds us up a lot in long documents + nsCOMPtr<nsIFrameEnumerator> frameTraversal; + nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID)); + if (trav) + trav->NewFrameTraversal(getter_AddRefs(frameTraversal), + aPresContext, frame, + eLeaf, + false, // aVisual + false, // aLockInScrollView + false, // aFollowOOFs + false // aSkipPopupChecks + ); + + if (!frameTraversal) + return false; + + while (rectVisibility == nsRectVisibility_kAboveViewport) { + frameTraversal->Next(); + frame = frameTraversal->CurrentItem(); + if (!frame) + return false; + + if (!frame->GetRect().IsEmpty()) { + rectVisibility = + aPresShell->GetRectVisibility(frame, + nsRect(nsPoint(0,0), frame->GetSize()), + minDistance); + } + } + + if (frame) { + nsCOMPtr<nsIDOMNode> firstVisibleNode = do_QueryInterface(frame->GetContent()); + + if (firstVisibleNode) { + frame->GetOffsets(startFrameOffset, endFrameOffset); + (*aFirstVisibleRange)->SetStart(firstVisibleNode, startFrameOffset); + (*aFirstVisibleRange)->SetEnd(firstVisibleNode, endFrameOffset); + } + } + + return false; +} + +already_AddRefed<nsIPresShell> +nsTypeAheadFind::GetPresShell() +{ + if (!mPresShell) + return nullptr; + + nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShell); + if (shell) { + nsPresContext *pc = shell->GetPresContext(); + if (!pc || !pc->GetContainerWeak()) { + return nullptr; + } + } + + return shell.forget(); +} diff --git a/toolkit/components/typeaheadfind/nsTypeAheadFind.h b/toolkit/components/typeaheadfind/nsTypeAheadFind.h new file mode 100644 index 000000000..8ff5ad1bf --- /dev/null +++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.h @@ -0,0 +1,134 @@ +/* -*- 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 "nsCycleCollectionParticipant.h" +#include "nsISelectionController.h" +#include "nsIController.h" +#include "nsIControllers.h" +#include "nsIObserver.h" +#include "nsUnicharUtils.h" +#include "nsIFind.h" +#include "nsIWebBrowserFind.h" +#include "nsWeakReference.h" +#include "nsISelection.h" +#include "nsIDOMRange.h" +#include "nsIDocShellTreeItem.h" +#include "nsITypeAheadFind.h" +#include "nsISound.h" + +class nsPIDOMWindowInner; +class nsIPresShell; +class nsPresContext; + +#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); + + void RangeStartsInsideLink(nsIDOMRange *aRange, nsIPresShell *aPresShell, + bool *aIsInsideLink, bool *aIsStartingLink); + + void GetSelection(nsIPresShell *aPresShell, nsISelectionController **aSelCon, + nsISelection **aDomSel); + // *aNewRange may not be collapsed. If you want to collapse it in a + // particular way, you need to do it yourself. + bool IsRangeVisible(nsIPresShell *aPresShell, nsPresContext *aPresContext, + nsIDOMRange *aRange, bool aMustBeVisible, + bool aGetTopVisibleLeaf, nsIDOMRange **aNewRange, + bool *aUsesIndependentSelection); + nsresult FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly, + bool aIsFirstVisiblePreferred, bool aFindPrev, + uint16_t* aResult); + nsresult GetSearchContainers(nsISupports *aContainer, + nsISelectionController *aSelectionController, + bool aIsFirstVisiblePreferred, + bool aFindPrev, nsIPresShell **aPresShell, + nsPresContext **aPresContext); + + // Get the pres shell from mPresShell and return it only if it is still + // attached to the DOM window. + already_AddRefed<nsIPresShell> GetPresShell(); + + 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<nsIDOMElement> mFoundLink; // Most recent elem found, if a link + nsCOMPtr<nsIDOMElement> mFoundEditable; // Most recent elem found, if editable + nsCOMPtr<nsIDOMRange> 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; + + // Sound is played asynchronously on some platforms. + // If we destroy mSoundInterface before sound has played, it won't play + nsCOMPtr<nsISound> mSoundInterface; + bool mIsSoundInitialized; + + // where selection was when user started the find + nsCOMPtr<nsIDOMRange> mStartFindRange; + nsCOMPtr<nsIDOMRange> mSearchRange; + nsCOMPtr<nsIDOMRange> mStartPointRange; + nsCOMPtr<nsIDOMRange> mEndPointRange; + + // Cached useful interfaces + nsCOMPtr<nsIFind> mFind; + + bool mCaseSensitive; + bool mEntireWord; + + bool EnsureFind() { + if (mFind) { + return true; + } + + mFind = do_CreateInstance("@mozilla.org/embedcomp/rangefind;1"); + if (!mFind) { + return false; + } + + mFind->SetCaseSensitive(mCaseSensitive); + mFind->SetEntireWord(mEntireWord); + + return true; + } + + nsCOMPtr<nsIWebBrowserFind> mWebBrowserFind; + + // The focused content window that we're listening to and its cached objects + nsWeakPtr mDocShell; + nsWeakPtr mPresShell; + nsWeakPtr mSelectionController; + // Most recent match's controller +}; |