diff options
author | Matt A. Tobin <email@mattatobin.com> | 2021-11-29 18:49:29 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2021-11-29 18:49:29 -0500 |
commit | ccf8865c32d9cdf5bdd15943d6ee6103db4968bf (patch) | |
tree | 364f82db2622ccc4b42912196408e3bac4fe7673 /components | |
parent | 99bd779137fd7e3d8055e6a55cd0bbfaaed8addf (diff) | |
download | aura-central-ccf8865c32d9cdf5bdd15943d6ee6103db4968bf.tar.gz |
Issue %3005 - Merge embedding/components/find with components/find
Diffstat (limited to 'components')
-rw-r--r-- | components/find/moz.build | 10 | ||||
-rw-r--r-- | components/find/public/nsFind.cpp | 1388 | ||||
-rw-r--r-- | components/find/public/nsFind.h | 82 | ||||
-rw-r--r-- | components/find/public/nsIFind.idl | 34 | ||||
-rw-r--r-- | components/find/public/nsIFindService.idl (renamed from components/find/nsIFindService.idl) | 0 | ||||
-rw-r--r-- | components/find/public/nsIWebBrowserFind.idl | 145 | ||||
-rw-r--r-- | components/find/public/nsWebBrowserFind.cpp | 867 | ||||
-rw-r--r-- | components/find/public/nsWebBrowserFind.h | 94 | ||||
-rw-r--r-- | components/find/src/nsFindService.cpp (renamed from components/find/nsFindService.cpp) | 0 | ||||
-rw-r--r-- | components/find/src/nsFindService.h (renamed from components/find/nsFindService.h) | 0 |
10 files changed, 2617 insertions, 3 deletions
diff --git a/components/find/moz.build b/components/find/moz.build index de83e39c3..7c43d8f52 100644 --- a/components/find/moz.build +++ b/components/find/moz.build @@ -4,13 +4,17 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. XPIDL_SOURCES += [ - 'nsIFindService.idl', + 'public/nsIFindService.idl', + 'public/nsIFind.idl', + 'public/nsIWebBrowserFind.idl', ] -XPIDL_MODULE = 'mozfind' SOURCES += [ - 'nsFindService.cpp', + 'src/nsFindService.cpp', + 'src/nsFind.cpp', + 'src/nsWebBrowserFind.cpp', ] +XPIDL_MODULE = 'mozfind' FINAL_LIBRARY = 'xul' diff --git a/components/find/public/nsFind.cpp b/components/find/public/nsFind.cpp new file mode 100644 index 000000000..82a928f49 --- /dev/null +++ b/components/find/public/nsFind.cpp @@ -0,0 +1,1388 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +//#define DEBUG_FIND 1 + +#include "nsFind.h" +#include "nsContentCID.h" +#include "nsIContent.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsISelection.h" +#include "nsISelectionController.h" +#include "nsIFrame.h" +#include "nsITextControlFrame.h" +#include "nsIFormControl.h" +#include "nsIEditor.h" +#include "nsIPlaintextEditor.h" +#include "nsTextFragment.h" +#include "nsString.h" +#include "nsIAtom.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicharUtils.h" +#include "nsIDOMElement.h" +#include "nsIWordBreaker.h" +#include "nsCRT.h" +#include "nsRange.h" +#include "nsContentUtils.h" +#include "mozilla/DebugOnly.h" + +using namespace mozilla; + +// Yikes! Casting a char to unichar can fill with ones! +#define CHAR_TO_UNICHAR(c) ((char16_t)(unsigned char)c) + +static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); +static NS_DEFINE_CID(kCPreContentIteratorCID, NS_PRECONTENTITERATOR_CID); + +#define CH_QUOTE ((char16_t)0x22) +#define CH_APOSTROPHE ((char16_t)0x27) +#define CH_LEFT_SINGLE_QUOTE ((char16_t)0x2018) +#define CH_RIGHT_SINGLE_QUOTE ((char16_t)0x2019) +#define CH_LEFT_DOUBLE_QUOTE ((char16_t)0x201C) +#define CH_RIGHT_DOUBLE_QUOTE ((char16_t)0x201D) + +#define CH_SHY ((char16_t)0xAD) + +// nsFind::Find casts CH_SHY to char before calling StripChars +// This works correctly if and only if CH_SHY <= 255 +static_assert(CH_SHY <= 255, "CH_SHY is not an ascii character"); + +// nsFindContentIterator is a special iterator that also goes through any +// existing <textarea>'s or text <input>'s editor to lookup the anonymous DOM +// content there. +// +// Details: +// 1) We use two iterators: The "outer-iterator" goes through the normal DOM. +// The "inner-iterator" goes through the anonymous DOM inside the editor. +// +// 2) [MaybeSetupInnerIterator] As soon as the outer-iterator's current node is +// changed, a check is made to see if the node is a <textarea> or a text <input> +// node. If so, an inner-iterator is created to lookup the anynomous contents of +// the editor underneath the text control. +// +// 3) When the inner-iterator is created, we position the outer-iterator 'after' +// (or 'before' in backward search) the text control to avoid revisiting that +// control. +// +// 4) As a consequence of searching through text controls, we can be called via +// FindNext with the current selection inside a <textarea> or a text <input>. +// This means that we can be given an initial search range that stretches across +// the anonymous DOM and the normal DOM. To cater for this situation, we split +// the anonymous part into the inner-iterator and then reposition the outer- +// iterator outside. +// +// 5) The implementation assumes that First() and Next() are only called in +// find-forward mode, while Last() and Prev() are used in find-backward. + +class nsFindContentIterator final : public nsIContentIterator +{ +public: + explicit nsFindContentIterator(bool aFindBackward) + : mStartOffset(0) + , mEndOffset(0) + , mFindBackward(aFindBackward) + { + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsFindContentIterator) + + // nsIContentIterator + virtual nsresult Init(nsINode* aRoot) override + { + NS_NOTREACHED("internal error"); + return NS_ERROR_NOT_IMPLEMENTED; + } + virtual nsresult Init(nsIDOMRange* aRange) override + { + NS_NOTREACHED("internal error"); + return NS_ERROR_NOT_IMPLEMENTED; + } + // Not a range because one of the endpoints may be anonymous. + nsresult Init(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset); + virtual void First() override; + virtual void Last() override; + virtual void Next() override; + virtual void Prev() override; + virtual nsINode* GetCurrentNode() override; + virtual bool IsDone() override; + virtual nsresult PositionAt(nsINode* aCurNode) override; + +protected: + virtual ~nsFindContentIterator() {} + +private: + static already_AddRefed<nsIDOMRange> CreateRange(nsINode* aNode) + { + RefPtr<nsRange> range = new nsRange(aNode); + range->SetMaySpanAnonymousSubtrees(true); + return range.forget(); + } + + nsCOMPtr<nsIContentIterator> mOuterIterator; + nsCOMPtr<nsIContentIterator> mInnerIterator; + // Can't use a range here, since we want to represent part of the flattened + // tree, including native anonymous content. + nsCOMPtr<nsIDOMNode> mStartNode; + int32_t mStartOffset; + nsCOMPtr<nsIDOMNode> mEndNode; + int32_t mEndOffset; + + nsCOMPtr<nsIContent> mStartOuterContent; + nsCOMPtr<nsIContent> mEndOuterContent; + bool mFindBackward; + + void Reset(); + void MaybeSetupInnerIterator(); + void SetupInnerIterator(nsIContent* aContent); +}; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFindContentIterator) + NS_INTERFACE_MAP_ENTRY(nsIContentIterator) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFindContentIterator) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFindContentIterator) + +NS_IMPL_CYCLE_COLLECTION(nsFindContentIterator, mOuterIterator, mInnerIterator, + mStartOuterContent, mEndOuterContent, mEndNode, + mStartNode) + +nsresult +nsFindContentIterator::Init(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset) +{ + NS_ENSURE_ARG_POINTER(aStartNode); + NS_ENSURE_ARG_POINTER(aEndNode); + if (!mOuterIterator) { + if (mFindBackward) { + // Use post-order in the reverse case, so we get parents before children + // in case we want to prevent descending into a node. + mOuterIterator = do_CreateInstance(kCContentIteratorCID); + } else { + // Use pre-order in the forward case, so we get parents before children in + // case we want to prevent descending into a node. + mOuterIterator = do_CreateInstance(kCPreContentIteratorCID); + } + NS_ENSURE_ARG_POINTER(mOuterIterator); + } + + // Set up the search "range" that we will examine + mStartNode = aStartNode; + mStartOffset = aStartOffset; + mEndNode = aEndNode; + mEndOffset = aEndOffset; + + return NS_OK; +} + +void +nsFindContentIterator::First() +{ + Reset(); +} + +void +nsFindContentIterator::Last() +{ + Reset(); +} + +void +nsFindContentIterator::Next() +{ + if (mInnerIterator) { + mInnerIterator->Next(); + if (!mInnerIterator->IsDone()) { + return; + } + + // by construction, mOuterIterator is already on the next node + } else { + mOuterIterator->Next(); + } + MaybeSetupInnerIterator(); +} + +void +nsFindContentIterator::Prev() +{ + if (mInnerIterator) { + mInnerIterator->Prev(); + if (!mInnerIterator->IsDone()) { + return; + } + + // by construction, mOuterIterator is already on the previous node + } else { + mOuterIterator->Prev(); + } + MaybeSetupInnerIterator(); +} + +nsINode* +nsFindContentIterator::GetCurrentNode() +{ + if (mInnerIterator && !mInnerIterator->IsDone()) { + return mInnerIterator->GetCurrentNode(); + } + return mOuterIterator->GetCurrentNode(); +} + +bool +nsFindContentIterator::IsDone() +{ + if (mInnerIterator && !mInnerIterator->IsDone()) { + return false; + } + return mOuterIterator->IsDone(); +} + +nsresult +nsFindContentIterator::PositionAt(nsINode* aCurNode) +{ + nsINode* oldNode = mOuterIterator->GetCurrentNode(); + nsresult rv = mOuterIterator->PositionAt(aCurNode); + if (NS_SUCCEEDED(rv)) { + MaybeSetupInnerIterator(); + } else { + mOuterIterator->PositionAt(oldNode); + if (mInnerIterator) { + rv = mInnerIterator->PositionAt(aCurNode); + } + } + return rv; +} + +void +nsFindContentIterator::Reset() +{ + mInnerIterator = nullptr; + mStartOuterContent = nullptr; + mEndOuterContent = nullptr; + + // As a consequence of searching through text controls, we may have been + // initialized with a selection inside a <textarea> or a text <input>. + + // see if the start node is an anonymous text node inside a text control + nsCOMPtr<nsIContent> startContent(do_QueryInterface(mStartNode)); + if (startContent) { + mStartOuterContent = startContent->FindFirstNonChromeOnlyAccessContent(); + } + + // see if the end node is an anonymous text node inside a text control + nsCOMPtr<nsIContent> endContent(do_QueryInterface(mEndNode)); + if (endContent) { + mEndOuterContent = endContent->FindFirstNonChromeOnlyAccessContent(); + } + + // Note: OK to just set up the outer iterator here; if our range has a native + // anonymous endpoint we'll end up setting up an inner iterator, and reset the + // outer one in the process. + nsCOMPtr<nsINode> node = do_QueryInterface(mStartNode); + NS_ENSURE_TRUE_VOID(node); + + nsCOMPtr<nsIDOMRange> range = CreateRange(node); + range->SetStart(mStartNode, mStartOffset); + range->SetEnd(mEndNode, mEndOffset); + mOuterIterator->Init(range); + + if (!mFindBackward) { + if (mStartOuterContent != startContent) { + // the start node was an anonymous text node + SetupInnerIterator(mStartOuterContent); + if (mInnerIterator) { + mInnerIterator->First(); + } + } + if (!mOuterIterator->IsDone()) { + mOuterIterator->First(); + } + } else { + if (mEndOuterContent != endContent) { + // the end node was an anonymous text node + SetupInnerIterator(mEndOuterContent); + if (mInnerIterator) { + mInnerIterator->Last(); + } + } + if (!mOuterIterator->IsDone()) { + mOuterIterator->Last(); + } + } + + // if we didn't create an inner-iterator, the boundary node could still be + // a text control, in which case we also need an inner-iterator straightaway + if (!mInnerIterator) { + MaybeSetupInnerIterator(); + } +} + +void +nsFindContentIterator::MaybeSetupInnerIterator() +{ + mInnerIterator = nullptr; + + nsCOMPtr<nsIContent> content = + do_QueryInterface(mOuterIterator->GetCurrentNode()); + if (!content || !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) { + return; + } + + nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content)); + if (!formControl->IsTextControl(true)) { + return; + } + + SetupInnerIterator(content); + if (mInnerIterator) { + if (!mFindBackward) { + mInnerIterator->First(); + // finish setup: position mOuterIterator on the actual "next" node (this + // completes its re-init, @see SetupInnerIterator) + if (!mOuterIterator->IsDone()) { + mOuterIterator->First(); + } + } else { + mInnerIterator->Last(); + // finish setup: position mOuterIterator on the actual "previous" node + // (this completes its re-init, @see SetupInnerIterator) + if (!mOuterIterator->IsDone()) { + mOuterIterator->Last(); + } + } + } +} + +void +nsFindContentIterator::SetupInnerIterator(nsIContent* aContent) +{ + if (!aContent) { + return; + } + NS_ASSERTION(!aContent->IsRootOfNativeAnonymousSubtree(), "invalid call"); + + nsITextControlFrame* tcFrame = do_QueryFrame(aContent->GetPrimaryFrame()); + if (!tcFrame) { + return; + } + + nsCOMPtr<nsIEditor> editor; + tcFrame->GetEditor(getter_AddRefs(editor)); + if (!editor) { + return; + } + + // don't mess with disabled input fields + uint32_t editorFlags = 0; + editor->GetFlags(&editorFlags); + if (editorFlags & nsIPlaintextEditor::eEditorDisabledMask) { + return; + } + + nsCOMPtr<nsIDOMElement> rootElement; + editor->GetRootElement(getter_AddRefs(rootElement)); + + nsCOMPtr<nsIDOMRange> innerRange = CreateRange(aContent); + nsCOMPtr<nsIDOMRange> outerRange = CreateRange(aContent); + if (!innerRange || !outerRange) { + return; + } + + // now create the inner-iterator + mInnerIterator = do_CreateInstance(kCPreContentIteratorCID); + + if (mInnerIterator) { + innerRange->SelectNodeContents(rootElement); + + // fix up the inner bounds, we may have to only lookup a portion + // of the text control if the current node is a boundary point + if (aContent == mStartOuterContent) { + innerRange->SetStart(mStartNode, mStartOffset); + } + if (aContent == mEndOuterContent) { + innerRange->SetEnd(mEndNode, mEndOffset); + } + // Note: we just init here. We do First() or Last() later. + mInnerIterator->Init(innerRange); + + // make sure to place the outer-iterator outside the text control so that we + // don't go there again. + nsresult res1, res2; + nsCOMPtr<nsIDOMNode> outerNode(do_QueryInterface(aContent)); + if (!mFindBackward) { // find forward + // cut the outer-iterator after the current node + res1 = outerRange->SetEnd(mEndNode, mEndOffset); + res2 = outerRange->SetStartAfter(outerNode); + } else { // find backward + // cut the outer-iterator before the current node + res1 = outerRange->SetStart(mStartNode, mStartOffset); + res2 = outerRange->SetEndBefore(outerNode); + } + if (NS_FAILED(res1) || NS_FAILED(res2)) { + // we are done with the outer-iterator, the inner-iterator will traverse + // what we want + outerRange->Collapse(true); + } + + // Note: we just re-init here, using the segment of our search range that + // is yet to be visited. Thus when we later do mOuterIterator->First() [or + // mOuterIterator->Last()], we will effectively be on the next node [or + // the previous node] _with respect to_ the search range. + mOuterIterator->Init(outerRange); + } +} + +nsresult +NS_NewFindContentIterator(bool aFindBackward, nsIContentIterator** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsFindContentIterator* it = new nsFindContentIterator(aFindBackward); + if (!it) { + return NS_ERROR_OUT_OF_MEMORY; + } + return it->QueryInterface(NS_GET_IID(nsIContentIterator), (void**)aResult); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFind) + NS_INTERFACE_MAP_ENTRY(nsIFind) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFind) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFind) + +NS_IMPL_CYCLE_COLLECTION(nsFind, mLastBlockParent, mIterNode, mIterator) + +nsFind::nsFind() + : mFindBackward(false) + , mCaseSensitive(false) + , mIterOffset(0) +{ +} + +nsFind::~nsFind() +{ +} + +#ifdef DEBUG_FIND +static void +DumpNode(nsIDOMNode* aNode) +{ + if (!aNode) { + printf(">>>> Node: NULL\n"); + return; + } + nsAutoString nodeName; + aNode->GetNodeName(nodeName); + nsCOMPtr<nsIContent> textContent(do_QueryInterface(aNode)); + if (textContent && textContent->IsNodeOfType(nsINode::eTEXT)) { + nsAutoString newText; + textContent->AppendTextTo(newText); + printf(">>>> Text node (node name %s): '%s'\n", + NS_LossyConvertUTF16toASCII(nodeName).get(), + NS_LossyConvertUTF16toASCII(newText).get()); + } else { + printf(">>>> Node: %s\n", NS_LossyConvertUTF16toASCII(nodeName).get()); + } +} +#endif + +nsresult +nsFind::InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset) +{ + if (!mIterator) { + mIterator = new nsFindContentIterator(mFindBackward); + NS_ENSURE_TRUE(mIterator, NS_ERROR_OUT_OF_MEMORY); + } + + NS_ENSURE_ARG_POINTER(aStartNode); + NS_ENSURE_ARG_POINTER(aEndNode); + +#ifdef DEBUG_FIND + printf("InitIterator search range:\n"); + printf(" -- start %d, ", aStartOffset); + DumpNode(aStartNode); + printf(" -- end %d, ", aEndOffset); + DumpNode(aEndNode); +#endif + + nsresult rv = mIterator->Init(aStartNode, aStartOffset, aEndNode, aEndOffset); + NS_ENSURE_SUCCESS(rv, rv); + if (mFindBackward) { + mIterator->Last(); + } else { + mIterator->First(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsFind::GetFindBackwards(bool* aFindBackward) +{ + if (!aFindBackward) { + return NS_ERROR_NULL_POINTER; + } + + *aFindBackward = mFindBackward; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetFindBackwards(bool aFindBackward) +{ + mFindBackward = aFindBackward; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::GetCaseSensitive(bool* aCaseSensitive) +{ + if (!aCaseSensitive) { + return NS_ERROR_NULL_POINTER; + } + + *aCaseSensitive = mCaseSensitive; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetCaseSensitive(bool aCaseSensitive) +{ + mCaseSensitive = aCaseSensitive; + return NS_OK; +} + +/* attribute boolean entireWord; */ +NS_IMETHODIMP +nsFind::GetEntireWord(bool *aEntireWord) +{ + if (!aEntireWord) + return NS_ERROR_NULL_POINTER; + + *aEntireWord = !!mWordBreaker; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetEntireWord(bool aEntireWord) +{ + mWordBreaker = aEntireWord ? nsContentUtils::WordBreaker() : nullptr; + return NS_OK; +} + +// Here begins the find code. A ten-thousand-foot view of how it works: Find +// needs to be able to compare across inline (but not block) nodes, e.g. find +// for "abc" should match a<b>b</b>c. So after we've searched a node, we're not +// done with it; in the case of a partial match we may need to reset the +// iterator to go back to a previously visited node, so we always save the +// "match anchor" node and offset. +// +// Text nodes store their text in an nsTextFragment, which is effectively a +// union of a one-byte string or a two-byte string. Single and double strings +// are intermixed in the dom. We don't have string classes which can deal with +// intermixed strings, so all the handling is done explicitly here. + +nsresult +nsFind::NextNode(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + bool aContinueOk) +{ + nsresult rv; + + nsCOMPtr<nsIContent> content; + + if (!mIterator || aContinueOk) { + // If we are continuing, that means we have a match in progress. In that + // case, we want to continue from the end point (where we are now) to the + // beginning/end of the search range. + nsCOMPtr<nsIDOMNode> startNode; + nsCOMPtr<nsIDOMNode> endNode; + uint32_t startOffset, endOffset; + if (aContinueOk) { +#ifdef DEBUG_FIND + printf("Match in progress: continuing past endpoint\n"); +#endif + if (mFindBackward) { + aSearchRange->GetStartContainer(getter_AddRefs(startNode)); + aSearchRange->GetStartOffset(&startOffset); + aEndPoint->GetStartContainer(getter_AddRefs(endNode)); + aEndPoint->GetStartOffset(&endOffset); + } else { // forward + aEndPoint->GetEndContainer(getter_AddRefs(startNode)); + aEndPoint->GetEndOffset(&startOffset); + aSearchRange->GetEndContainer(getter_AddRefs(endNode)); + aSearchRange->GetEndOffset(&endOffset); + } + } else { // Normal, not continuing + if (mFindBackward) { + aSearchRange->GetStartContainer(getter_AddRefs(startNode)); + aSearchRange->GetStartOffset(&startOffset); + aStartPoint->GetEndContainer(getter_AddRefs(endNode)); + aStartPoint->GetEndOffset(&endOffset); + // XXX Needs work: Problem with this approach: if there is a match which + // starts just before the current selection and continues into the + // selection, we will miss it, because our search algorithm only starts + // searching from the end of the word, so we would have to search the + // current selection but discount any matches that fall entirely inside + // it. + } else { // forward + aStartPoint->GetStartContainer(getter_AddRefs(startNode)); + aStartPoint->GetStartOffset(&startOffset); + aEndPoint->GetEndContainer(getter_AddRefs(endNode)); + aEndPoint->GetEndOffset(&endOffset); + } + } + + rv = InitIterator(startNode, static_cast<int32_t>(startOffset), + endNode, static_cast<int32_t>(endOffset)); + NS_ENSURE_SUCCESS(rv, rv); + if (!aStartPoint) { + aStartPoint = aSearchRange; + } + + content = do_QueryInterface(mIterator->GetCurrentNode()); +#ifdef DEBUG_FIND + nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content)); + printf(":::::: Got the first node "); + DumpNode(dnode); +#endif + if (content && content->IsNodeOfType(nsINode::eTEXT) && + !SkipNode(content)) { + mIterNode = do_QueryInterface(content); + // Also set mIterOffset if appropriate: + nsCOMPtr<nsIDOMNode> node; + if (mFindBackward) { + aStartPoint->GetEndContainer(getter_AddRefs(node)); + if (mIterNode.get() == node.get()) { + uint32_t endOffset; + aStartPoint->GetEndOffset(&endOffset); + mIterOffset = static_cast<int32_t>(endOffset); + } else { + mIterOffset = -1; // sign to start from end + } + } else { + aStartPoint->GetStartContainer(getter_AddRefs(node)); + if (mIterNode.get() == node.get()) { + uint32_t startOffset; + aStartPoint->GetStartOffset(&startOffset); + mIterOffset = static_cast<int32_t>(startOffset); + } else { + mIterOffset = 0; + } + } +#ifdef DEBUG_FIND + printf("Setting initial offset to %d\n", mIterOffset); +#endif + return NS_OK; + } + } + + while (true) { + if (mFindBackward) { + mIterator->Prev(); + } else { + mIterator->Next(); + } + + content = do_QueryInterface(mIterator->GetCurrentNode()); + if (!content) { + break; + } + +#ifdef DEBUG_FIND + nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content)); + printf(":::::: Got another node "); + DumpNode(dnode); +#endif + + // If we ever cross a block node, we might want to reset the match anchor: + // we don't match patterns extending across block boundaries. But we can't + // depend on this test here now, because the iterator doesn't give us the + // parent going in and going out, and we need it both times to depend on + // this. + //if (IsBlockNode(content)) + + // Now see if we need to skip this node -- e.g. is it part of a script or + // other invisible node? Note that we don't ask for CSS information; a node + // can be invisible due to CSS, and we'd still find it. + if (SkipNode(content)) { + continue; + } + + if (content->IsNodeOfType(nsINode::eTEXT)) { + break; + } +#ifdef DEBUG_FIND + dnode = do_QueryInterface(content); + printf("Not a text node: "); + DumpNode(dnode); +#endif + } + + if (content) { + mIterNode = do_QueryInterface(content); + } else { + mIterNode = nullptr; + } + mIterOffset = -1; + +#ifdef DEBUG_FIND + printf("Iterator gave: "); + DumpNode(mIterNode); +#endif + return NS_OK; +} + +class MOZ_STACK_CLASS PeekNextCharRestoreState final +{ +public: + explicit PeekNextCharRestoreState(nsFind* aFind) + : mIterOffset(aFind->mIterOffset), + mIterNode(aFind->mIterNode), + mCurrNode(aFind->mIterator->GetCurrentNode()), + mFind(aFind) + { + } + + ~PeekNextCharRestoreState() + { + mFind->mIterOffset = mIterOffset; + mFind->mIterNode = mIterNode; + mFind->mIterator->PositionAt(mCurrNode); + } + +private: + int32_t mIterOffset; + nsCOMPtr<nsIDOMNode> mIterNode; + nsCOMPtr<nsINode> mCurrNode; + RefPtr<nsFind> mFind; +}; + +char16_t +nsFind::PeekNextChar(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint) +{ + // We need to restore the necessary member variables before this function + // returns. + PeekNextCharRestoreState restoreState(this); + + nsCOMPtr<nsIContent> tc; + nsresult rv; + const nsTextFragment *frag; + int32_t fragLen; + + // Loop through non-block nodes until we find one that's not empty. + do { + tc = nullptr; + NextNode(aSearchRange, aStartPoint, aEndPoint, false); + + // Get the text content: + tc = do_QueryInterface(mIterNode); + + // Get the block parent. + nsCOMPtr<nsIDOMNode> blockParent; + rv = GetBlockParent(mIterNode, getter_AddRefs(blockParent)); + if (NS_FAILED(rv)) + return L'\0'; + + // If out of nodes or in new parent. + if (!mIterNode || !tc || (blockParent != mLastBlockParent)) + return L'\0'; + + frag = tc->GetText(); + fragLen = frag->GetLength(); + } while (fragLen <= 0); + + const char16_t *t2b = nullptr; + const char *t1b = nullptr; + + if (frag->Is2b()) { + t2b = frag->Get2b(); + } else { + t1b = frag->Get1b(); + } + + // Index of char to return. + int32_t index = mFindBackward ? fragLen - 1 : 0; + + return t1b ? CHAR_TO_UNICHAR(t1b[index]) : t2b[index]; +} + +bool +nsFind::IsBlockNode(nsIContent* aContent) +{ + if (aContent->IsAnyOfHTMLElements(nsGkAtoms::img, + nsGkAtoms::hr, + nsGkAtoms::th, + nsGkAtoms::td)) { + return true; + } + + return nsContentUtils::IsHTMLBlock(aContent); +} + +bool +nsFind::IsTextNode(nsIDOMNode* aNode) +{ + uint16_t nodeType; + aNode->GetNodeType(&nodeType); + + return nodeType == nsIDOMNode::TEXT_NODE || + nodeType == nsIDOMNode::CDATA_SECTION_NODE; +} + +bool +nsFind::IsVisibleNode(nsIDOMNode* aDOMNode) +{ + nsCOMPtr<nsIContent> content(do_QueryInterface(aDOMNode)); + if (!content) { + return false; + } + + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + // No frame! Not visible then. + return false; + } + + return frame->StyleVisibility()->IsVisible(); +} + +bool +nsFind::SkipNode(nsIContent* aContent) +{ +#ifdef HAVE_BIDI_ITERATOR + // We may not need to skip comment nodes, now that IsTextNode distinguishes + // them from real text nodes. + return aContent->IsNodeOfType(nsINode::eCOMMENT) || + aContent->IsAnyOfHTMLElements(sScriptAtom, sNoframesAtom, sSelectAtom); + +#else /* HAVE_BIDI_ITERATOR */ + // Temporary: eventually we will have an iterator to do this, but for now, we + // have to climb up the tree for each node and see whether any parent is a + // skipped node, and take the performance hit. + + nsIContent* content = aContent; + while (content) { + if (aContent->IsNodeOfType(nsINode::eCOMMENT) || + content->IsAnyOfHTMLElements(nsGkAtoms::script, + nsGkAtoms::noframes, + nsGkAtoms::select)) { +#ifdef DEBUG_FIND + printf("Skipping node: "); + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content)); + DumpNode(node); +#endif + + return true; + } + + // Only climb to the nearest block node + if (IsBlockNode(content)) { + return false; + } + + content = content->GetParent(); + } + + return false; +#endif /* HAVE_BIDI_ITERATOR */ +} + +nsresult +nsFind::GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent) +{ + while (aNode) { + nsCOMPtr<nsIDOMNode> parent; + nsresult rv = aNode->GetParentNode(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIContent> content(do_QueryInterface(parent)); + if (content && IsBlockNode(content)) { + *aParent = parent; + NS_ADDREF(*aParent); + return NS_OK; + } + aNode = parent; + } + return NS_ERROR_FAILURE; +} + +// Call ResetAll before returning, to remove all references to external objects. +void +nsFind::ResetAll() +{ + mIterator = nullptr; + mLastBlockParent = nullptr; +} + +#define NBSP_CHARCODE (CHAR_TO_UNICHAR(160)) +#define IsSpace(c) (nsCRT::IsAsciiSpace(c) || (c) == NBSP_CHARCODE) +#define OVERFLOW_PINDEX (mFindBackward ? pindex < 0 : pindex > patLen) +#define DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen) +#define ALMOST_DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen - 1) + +// Take nodes out of the tree with NextNode, until null (NextNode will return 0 +// at the end of our range). +NS_IMETHODIMP +nsFind::Find(const nsAString& aPatText, nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + nsIDOMRange** aRangeRet) +{ +#ifdef DEBUG_FIND + printf("============== nsFind::Find('%s'%s, %p, %p, %p)\n", + NS_LossyConvertUTF16toASCII(aPatText).get(), + mFindBackward ? " (backward)" : " (forward)", + (void*)aSearchRange, (void*)aStartPoint, (void*)aEndPoint); +#endif + + NS_ENSURE_ARG(aSearchRange); + NS_ENSURE_ARG(aStartPoint); + NS_ENSURE_ARG(aEndPoint); + NS_ENSURE_ARG_POINTER(aRangeRet); + *aRangeRet = 0; + + ResetAll(); + + nsAutoString patAutoStr(aPatText); + if (!mCaseSensitive) { + ToLowerCase(patAutoStr); + } + + // Ignore soft hyphens in the pattern + static const char kShy[] = { char(CH_SHY), 0 }; + patAutoStr.StripChars(kShy); + + const char16_t* patStr = patAutoStr.get(); + int32_t patLen = patAutoStr.Length() - 1; + + // If this function is called with an empty string, we should early exit. + if (patLen < 0) { + return NS_OK; + } + + // current offset into the pattern -- reset to beginning/end: + int32_t pindex = (mFindBackward ? patLen : 0); + + // Current offset into the fragment + int32_t findex = 0; + + // Direction to move pindex and ptr* + int incr = (mFindBackward ? -1 : 1); + + nsCOMPtr<nsIContent> tc; + const nsTextFragment* frag = nullptr; + int32_t fragLen = 0; + + // Pointers into the current fragment: + const char16_t* t2b = nullptr; + const char* t1b = nullptr; + + // Keep track of when we're in whitespace: + // (only matters when we're matching) + bool inWhitespace = false; + // Keep track of whether the previous char was a word-breaking one. + bool wordBreakPrev = false; + + // Place to save the range start point in case we find a match: + nsCOMPtr<nsIDOMNode> matchAnchorNode; + int32_t matchAnchorOffset = 0; + + // Get the end point, so we know when to end searches: + nsCOMPtr<nsIDOMNode> endNode; + uint32_t endOffset; + aEndPoint->GetEndContainer(getter_AddRefs(endNode)); + aEndPoint->GetEndOffset(&endOffset); + + char16_t c = 0; + char16_t patc = 0; + char16_t prevChar = 0; + char16_t prevCharInMatch = 0; + while (1) { +#ifdef DEBUG_FIND + printf("Loop ...\n"); +#endif + + // If this is our first time on a new node, reset the pointers: + if (!frag) { + + tc = nullptr; + NextNode(aSearchRange, aStartPoint, aEndPoint, false); + if (!mIterNode) { // Out of nodes + // Are we in the middle of a match? If so, try again with continuation. + if (matchAnchorNode) { + NextNode(aSearchRange, aStartPoint, aEndPoint, true); + } + + // Reset the iterator, so this nsFind will be usable if the user wants + // to search again (from beginning/end). + ResetAll(); + return NS_OK; + } + + // We have a new text content. If its block parent is different from the + // block parent of the last text content, then we need to clear the match + // since we don't want to find across block boundaries. + nsCOMPtr<nsIDOMNode> blockParent; + GetBlockParent(mIterNode, getter_AddRefs(blockParent)); +#ifdef DEBUG_FIND + printf("New node: old blockparent = %p, new = %p\n", + (void*)mLastBlockParent.get(), (void*)blockParent.get()); +#endif + if (blockParent != mLastBlockParent) { +#ifdef DEBUG_FIND + printf("Different block parent!\n"); +#endif + mLastBlockParent = blockParent; + // End any pending match: + matchAnchorNode = nullptr; + matchAnchorOffset = 0; + pindex = (mFindBackward ? patLen : 0); + inWhitespace = false; + } + + // Get the text content: + tc = do_QueryInterface(mIterNode); + if (!tc || !(frag = tc->GetText())) { // Out of nodes + mIterator = nullptr; + mLastBlockParent = nullptr; + ResetAll(); + return NS_OK; + } + + fragLen = frag->GetLength(); + + // Set our starting point in this node. If we're going back to the anchor + // node, which means that we just ended a partial match, use the saved + // offset: + if (mIterNode == matchAnchorNode) { + findex = matchAnchorOffset + (mFindBackward ? 1 : 0); + } + + // mIterOffset, if set, is the range's idea of an offset, and points + // between characters. But when translated to a string index, it points to + // a character. If we're going backward, this is one character too late + // and we'll match part of our previous pattern. + else if (mIterOffset >= 0) { + findex = mIterOffset - (mFindBackward ? 1 : 0); + } + + // Otherwise, just start at the appropriate end of the fragment: + else if (mFindBackward) { + findex = fragLen - 1; + } else { + findex = 0; + } + + // Offset can only apply to the first node: + mIterOffset = -1; + + // If this is outside the bounds of the string, then skip this node: + if (findex < 0 || findex > fragLen - 1) { +#ifdef DEBUG_FIND + printf("At the end of a text node -- skipping to the next\n"); +#endif + frag = 0; + continue; + } + +#ifdef DEBUG_FIND + printf("Starting from offset %d\n", findex); +#endif + if (frag->Is2b()) { + t2b = frag->Get2b(); + t1b = nullptr; +#ifdef DEBUG_FIND + nsAutoString str2(t2b, fragLen); + printf("2 byte, '%s'\n", NS_LossyConvertUTF16toASCII(str2).get()); +#endif + } else { + t1b = frag->Get1b(); + t2b = nullptr; +#ifdef DEBUG_FIND + nsAutoCString str1(t1b, fragLen); + printf("1 byte, '%s'\n", str1.get()); +#endif + } + } else { + // Still on the old node. Advance the pointers, then see if we need to + // pull a new node. + findex += incr; +#ifdef DEBUG_FIND + printf("Same node -- (%d, %d)\n", pindex, findex); +#endif + if (mFindBackward ? (findex < 0) : (findex >= fragLen)) { +#ifdef DEBUG_FIND + printf("Will need to pull a new node: mAO = %d, frag len=%d\n", + matchAnchorOffset, fragLen); +#endif + // Done with this node. Pull a new one. + frag = nullptr; + continue; + } + } + + // Have we gone past the endpoint yet? If we have, and we're not in the + // middle of a match, return. + if (mIterNode == endNode && + ((mFindBackward && findex < static_cast<int32_t>(endOffset)) || + (!mFindBackward && findex > static_cast<int32_t>(endOffset)))) { + ResetAll(); + return NS_OK; + } + + // Save the previous character for word boundary detection + prevChar = c; + // The two characters we'll be comparing: + c = (t2b ? t2b[findex] : CHAR_TO_UNICHAR(t1b[findex])); + patc = patStr[pindex]; + +#ifdef DEBUG_FIND + printf("Comparing '%c'=%x to '%c' (%d of %d), findex=%d%s\n", + (char)c, (int)c, patc, pindex, patLen, findex, + inWhitespace ? " (inWhitespace)" : ""); +#endif + + // Do we need to go back to non-whitespace mode? If inWhitespace, then this + // space in the pat str has already matched at least one space in the + // document. + if (inWhitespace && !IsSpace(c)) { + inWhitespace = false; + pindex += incr; +#ifdef DEBUG + // This shouldn't happen -- if we were still matching, and we were at the + // end of the pat string, then we should have caught it in the last + // iteration and returned success. + if (OVERFLOW_PINDEX) { + NS_ASSERTION(false, "Missed a whitespace match"); + } +#endif + patc = patStr[pindex]; + } + if (!inWhitespace && IsSpace(patc)) { + inWhitespace = true; + } else if (!inWhitespace && !mCaseSensitive && IsUpperCase(c)) { + c = ToLowerCase(c); + } + + if (c == CH_SHY) { + // ignore soft hyphens in the document + continue; + } + + if (!mCaseSensitive) { + switch (c) { + // treat curly and straight quotes as identical + case CH_LEFT_SINGLE_QUOTE: + case CH_RIGHT_SINGLE_QUOTE: + c = CH_APOSTROPHE; + break; + case CH_LEFT_DOUBLE_QUOTE: + case CH_RIGHT_DOUBLE_QUOTE: + c = CH_QUOTE; + break; + } + + switch (patc) { + // treat curly and straight quotes as identical + case CH_LEFT_SINGLE_QUOTE: + case CH_RIGHT_SINGLE_QUOTE: + patc = CH_APOSTROPHE; + break; + case CH_LEFT_DOUBLE_QUOTE: + case CH_RIGHT_DOUBLE_QUOTE: + patc = CH_QUOTE; + break; + } + } + + // a '\n' between CJ characters is ignored + if (pindex != (mFindBackward ? patLen : 0) && c != patc && !inWhitespace) { + if (c == '\n' && t2b && IS_CJ_CHAR(prevCharInMatch)) { + int32_t nindex = findex + incr; + if (mFindBackward ? (nindex >= 0) : (nindex < fragLen)) { + if (IS_CJ_CHAR(t2b[nindex])) { + continue; + } + } + } + } + + wordBreakPrev = false; + if (mWordBreaker) { + if (prevChar == NBSP_CHARCODE) + prevChar = CHAR_TO_UNICHAR(' '); + wordBreakPrev = mWordBreaker->BreakInBetween(&prevChar, 1, &c, 1); + } + + // Compare. Match if we're in whitespace and c is whitespace, or if the + // characters match and at least one of the following is true: + // a) we're not matching the entire word + // b) a match has already been stored + // c) the previous character is a different "class" than the current character. + if ((c == patc && (!mWordBreaker || matchAnchorNode || wordBreakPrev)) || + (inWhitespace && IsSpace(c))) + { + prevCharInMatch = c; +#ifdef DEBUG_FIND + if (inWhitespace) { + printf("YES (whitespace)(%d of %d)\n", pindex, patLen); + } else { + printf("YES! '%c' == '%c' (%d of %d)\n", c, patc, pindex, patLen); + } +#endif + + // Save the range anchors if we haven't already: + if (!matchAnchorNode) { + matchAnchorNode = mIterNode; + matchAnchorOffset = findex; + } + + // Are we done? + if (DONE_WITH_PINDEX) { + // Matched the whole string! +#ifdef DEBUG_FIND + printf("Found a match!\n"); +#endif + + // Make the range: + nsCOMPtr<nsIDOMNode> startParent; + nsCOMPtr<nsIDOMNode> endParent; + + // Check for word break (if necessary) + if (mWordBreaker) { + int32_t nextfindex = findex + incr; + + char16_t nextChar; + // If still in array boundaries, get nextChar. + if (mFindBackward ? (nextfindex >= 0) : (nextfindex < fragLen)) + nextChar = (t2b ? t2b[nextfindex] : CHAR_TO_UNICHAR(t1b[nextfindex])); + // Get next character from the next node. + else + nextChar = PeekNextChar(aSearchRange, aStartPoint, aEndPoint); + + if (nextChar == NBSP_CHARCODE) + nextChar = CHAR_TO_UNICHAR(' '); + + // If a word break isn't there when it needs to be, reset search. + if (!mWordBreaker->BreakInBetween(&c, 1, &nextChar, 1)) { + matchAnchorNode = nullptr; + continue; + } + } + + nsCOMPtr<nsIDOMRange> range = new nsRange(tc); + if (range) { + int32_t matchStartOffset, matchEndOffset; + // convert char index to range point: + int32_t mao = matchAnchorOffset + (mFindBackward ? 1 : 0); + if (mFindBackward) { + startParent = do_QueryInterface(tc); + endParent = matchAnchorNode; + matchStartOffset = findex; + matchEndOffset = mao; + } else { + startParent = matchAnchorNode; + endParent = do_QueryInterface(tc); + matchStartOffset = mao; + matchEndOffset = findex + 1; + } + if (startParent && endParent && + IsVisibleNode(startParent) && IsVisibleNode(endParent)) { + range->SetStart(startParent, matchStartOffset); + range->SetEnd(endParent, matchEndOffset); + *aRangeRet = range.get(); + NS_ADDREF(*aRangeRet); + } else { + // This match is no good -- invisible or bad range + startParent = nullptr; + } + } + + if (startParent) { + // If startParent == nullptr, we didn't successfully make range + // or, we didn't make a range because the start or end node were + // invisible. Reset the offset to the other end of the found string: + mIterOffset = findex + (mFindBackward ? 1 : 0); +#ifdef DEBUG_FIND + printf("mIterOffset = %d, mIterNode = ", mIterOffset); + DumpNode(mIterNode); +#endif + + ResetAll(); + return NS_OK; + } + // This match is no good, continue on in document + matchAnchorNode = nullptr; + } + + if (matchAnchorNode) { + // Not done, but still matching. Advance and loop around for the next + // characters. But don't advance from a space to a non-space: + if (!inWhitespace || DONE_WITH_PINDEX || + IsSpace(patStr[pindex + incr])) { + pindex += incr; + inWhitespace = false; +#ifdef DEBUG_FIND + printf("Advancing pindex to %d\n", pindex); +#endif + } + + continue; + } + } + +#ifdef DEBUG_FIND + printf("NOT: %c == %c\n", c, patc); +#endif + + // If we didn't match, go back to the beginning of patStr, and set findex + // back to the next char after we started the current match. + if (matchAnchorNode) { // we're ending a partial match + findex = matchAnchorOffset; + mIterOffset = matchAnchorOffset; + // +incr will be added to findex when we continue + + // Are we going back to a previous node? + if (matchAnchorNode != mIterNode) { + nsCOMPtr<nsIContent> content(do_QueryInterface(matchAnchorNode)); + DebugOnly<nsresult> rv = NS_ERROR_UNEXPECTED; + if (content) { + rv = mIterator->PositionAt(content); + } + frag = 0; + NS_ASSERTION(NS_SUCCEEDED(rv), "Text content wasn't nsIContent!"); +#ifdef DEBUG_FIND + printf("Repositioned anchor node\n"); +#endif + } +#ifdef DEBUG_FIND + printf("Ending a partial match; findex -> %d, mIterOffset -> %d\n", + findex, mIterOffset); +#endif + } + matchAnchorNode = nullptr; + matchAnchorOffset = 0; + inWhitespace = false; + pindex = (mFindBackward ? patLen : 0); +#ifdef DEBUG_FIND + printf("Setting findex back to %d, pindex to %d\n", findex, pindex); + +#endif + } + + // Out of nodes, and didn't match. + ResetAll(); + return NS_OK; +} diff --git a/components/find/public/nsFind.h b/components/find/public/nsFind.h new file mode 100644 index 000000000..9ed447466 --- /dev/null +++ b/components/find/public/nsFind.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef nsFind_h__ +#define nsFind_h__ + +#include "nsIFind.h" + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDOMNode.h" +#include "nsIDOMRange.h" +#include "nsIContentIterator.h" +#include "nsIWordBreaker.h" + +class nsIContent; + +#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1" + +#define NS_FIND_CID \ + {0x471f4944, 0x1dd2, 0x11b2, {0x87, 0xac, 0x90, 0xbe, 0x0a, 0x51, 0xd6, 0x09}} + +class nsFindContentIterator; + +class nsFind : public nsIFind +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIFIND + NS_DECL_CYCLE_COLLECTION_CLASS(nsFind) + + nsFind(); + +protected: + virtual ~nsFind(); + + // Parameters set from the interface: + //nsCOMPtr<nsIDOMRange> mRange; // search only in this range + bool mFindBackward; + bool mCaseSensitive; + + // Use "find entire words" mode by setting to a word breaker or null, to + // disable "entire words" mode. + nsCOMPtr<nsIWordBreaker> mWordBreaker; + + int32_t mIterOffset; + nsCOMPtr<nsIDOMNode> mIterNode; + + // Last block parent, so that we will notice crossing block boundaries: + nsCOMPtr<nsIDOMNode> mLastBlockParent; + nsresult GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent); + + // Utility routines: + bool IsTextNode(nsIDOMNode* aNode); + bool IsBlockNode(nsIContent* aNode); + bool SkipNode(nsIContent* aNode); + bool IsVisibleNode(nsIDOMNode* aNode); + + // Move in the right direction for our search: + nsresult NextNode(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + bool aContinueOk); + + // Get the first character from the next node (last if mFindBackward). + char16_t PeekNextChar(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint); + + // Reset variables before returning -- don't hold any references. + void ResetAll(); + + // The iterator we use to move through the document: + nsresult InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset); + RefPtr<nsFindContentIterator> mIterator; + + friend class PeekNextCharRestoreState; +}; + +#endif // nsFind_h__ diff --git a/components/find/public/nsIFind.idl b/components/find/public/nsIFind.idl new file mode 100644 index 000000000..ce02c9b7d --- /dev/null +++ b/components/find/public/nsIFind.idl @@ -0,0 +1,34 @@ +/* -*- 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 "nsISupports.idl" + +interface nsIDOMRange; +interface nsIWordBreaker; + +[scriptable, uuid(40aba110-2a56-4678-be90-e2c17a9ae7d7)] +interface nsIFind : nsISupports +{ + attribute boolean findBackwards; + attribute boolean caseSensitive; + attribute boolean entireWord; + + /** + * Find some text in the current context. The implementation is + * responsible for performing the find and highlighting the text. + * + * @param aPatText The text to search for. + * @param aSearchRange A Range specifying domain of search. + * @param aStartPoint A Range specifying search start point. + * If not collapsed, we'll start from + * end (forward) or start (backward). + * @param aEndPoint A Range specifying search end point. + * If not collapsed, we'll end at + * end (forward) or start (backward). + * @retval A range spanning the match that was found (or null). + */ + nsIDOMRange Find(in AString aPatText, in nsIDOMRange aSearchRange, + in nsIDOMRange aStartPoint, in nsIDOMRange aEndPoint); +}; diff --git a/components/find/nsIFindService.idl b/components/find/public/nsIFindService.idl index c5fb96f76..c5fb96f76 100644 --- a/components/find/nsIFindService.idl +++ b/components/find/public/nsIFindService.idl diff --git a/components/find/public/nsIWebBrowserFind.idl b/components/find/public/nsIWebBrowserFind.idl new file mode 100644 index 000000000..a00763f78 --- /dev/null +++ b/components/find/public/nsIWebBrowserFind.idl @@ -0,0 +1,145 @@ +/* -*- Mode: IDL; tab-width: 4; 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 "nsISupports.idl" + +#include "domstubs.idl" + +interface mozIDOMWindowProxy; + +/* THIS IS A PUBLIC EMBEDDING API */ + + +/** + * nsIWebBrowserFind + * + * Searches for text in a web browser. + * + * Get one by doing a GetInterface on an nsIWebBrowser. + * + * By default, the implementation will search the focussed frame, or + * if there is no focussed frame, the web browser content area. It + * does not by default search subframes or iframes. To change this + * behaviour, and to explicitly set the frame to search, + * QueryInterface to nsIWebBrowserFindInFrames. + */ + +[scriptable, uuid(e4920136-b3e0-49e0-b1cd-6c783d2591a8)] +interface nsIWebBrowserFind : nsISupports +{ + /** + * findNext + * + * Finds, highlights, and scrolls into view the next occurrence of the + * search string, using the current search settings. Fails if the + * search string is empty. + * + * @return Whether an occurrence was found + */ + boolean findNext(); + + /** + * searchString + * + * The string to search for. This must be non-empty to search. + */ + attribute wstring searchString; + + /** + * findBackwards + * + * Whether to find backwards (towards the beginning of the document). + * Default is false (search forward). + */ + attribute boolean findBackwards; + + /** + * wrapFind + * + * Whether the search wraps around to the start (or end) of the document + * if no match was found between the current position and the end (or + * beginning). Works correctly when searching backwards. Default is + * false. + */ + attribute boolean wrapFind; + + /** + * entireWord + * + * Whether to match entire words only. Default is false. + */ + attribute boolean entireWord; + + /** + * matchCase + * + * Whether to match case (case sensitive) when searching. Default is false. + */ + attribute boolean matchCase; + + /** + * searchFrames + * + * Whether to search through all frames in the content area. Default is true. + * + * Note that you can control whether the search propagates into child or + * parent frames explicitly using nsIWebBrowserFindInFrames, but if one, + * but not both, of searchSubframes and searchParentFrames are set, this + * returns false. + */ + attribute boolean searchFrames; +}; + + + +/** + * nsIWebBrowserFindInFrames + * + * Controls how find behaves when multiple frames or iframes are present. + * + * Get by doing a QueryInterface from nsIWebBrowserFind. + */ + +[scriptable, uuid(e0f5d182-34bc-11d5-be5b-b760676c6ebc)] +interface nsIWebBrowserFindInFrames : nsISupports +{ + /** + * currentSearchFrame + * + * Frame at which to start the search. Once the search is done, this will + * be set to be the last frame searched, whether or not a result was found. + * Has to be equal to or contained within the rootSearchFrame. + */ + attribute mozIDOMWindowProxy currentSearchFrame; + + /** + * rootSearchFrame + * + * Frame within which to confine the search (normally the content area frame). + * Set this to only search a subtree of the frame hierarchy. + */ + attribute mozIDOMWindowProxy rootSearchFrame; + + /** + * searchSubframes + * + * Whether to recurse down into subframes while searching. Default is true. + * + * Setting nsIWebBrowserfind.searchFrames to true sets this to true. + */ + attribute boolean searchSubframes; + + /** + * searchParentFrames + * + * Whether to allow the search to propagate out of the currentSearchFrame into its + * parent frame(s). Search is always confined within the rootSearchFrame. Default + * is true. + * + * Setting nsIWebBrowserfind.searchFrames to true sets this to true. + */ + attribute boolean searchParentFrames; + +}; diff --git a/components/find/public/nsWebBrowserFind.cpp b/components/find/public/nsWebBrowserFind.cpp new file mode 100644 index 000000000..ca9883075 --- /dev/null +++ b/components/find/public/nsWebBrowserFind.cpp @@ -0,0 +1,867 @@ +/* -*- Mode: C++; tab-width: 8; 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 "nsWebBrowserFind.h" + +// Only need this for NS_FIND_CONTRACTID, +// else we could use nsIDOMRange.h and nsIFind.h. +#include "nsFind.h" + +#include "nsIComponentManager.h" +#include "nsIScriptSecurityManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIURI.h" +#include "nsIDocShell.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsISelectionController.h" +#include "nsISelection.h" +#include "nsIFrame.h" +#include "nsITextControlFrame.h" +#include "nsReadableUtils.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIContent.h" +#include "nsContentCID.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "nsFind.h" +#include "nsError.h" +#include "nsFocusManager.h" +#include "mozilla/Services.h" +#include "mozilla/dom/Element.h" +#include "nsISimpleEnumerator.h" +#include "nsContentUtils.h" + +#if DEBUG +#include "nsIWebNavigation.h" +#include "nsXPIDLString.h" +#endif + +nsWebBrowserFind::nsWebBrowserFind() + : mFindBackwards(false) + , mWrapFind(false) + , mEntireWord(false) + , mMatchCase(false) + , mSearchSubFrames(true) + , mSearchParentFrames(true) +{ +} + +nsWebBrowserFind::~nsWebBrowserFind() +{ +} + +NS_IMPL_ISUPPORTS(nsWebBrowserFind, nsIWebBrowserFind, + nsIWebBrowserFindInFrames) + +NS_IMETHODIMP +nsWebBrowserFind::FindNext(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + NS_ENSURE_TRUE(CanFindNext(), NS_ERROR_NOT_INITIALIZED); + + nsresult rv = NS_OK; + nsCOMPtr<nsPIDOMWindowOuter> searchFrame = do_QueryReferent(mCurrentSearchFrame); + NS_ENSURE_TRUE(searchFrame, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsPIDOMWindowOuter> rootFrame = do_QueryReferent(mRootSearchFrame); + NS_ENSURE_TRUE(rootFrame, NS_ERROR_NOT_INITIALIZED); + + // first, if there's a "cmd_findagain" observer around, check to see if it + // wants to perform the find again command . If it performs the find again + // it will return true, in which case we exit ::FindNext() early. + // Otherwise, nsWebBrowserFind needs to perform the find again command itself + // this is used by nsTypeAheadFind, which controls find again when it was + // the last executed find in the current window. + nsCOMPtr<nsIObserverService> observerSvc = + mozilla::services::GetObserverService(); + if (observerSvc) { + nsCOMPtr<nsISupportsInterfacePointer> windowSupportsData = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISupports> searchWindowSupports = do_QueryInterface(rootFrame); + windowSupportsData->SetData(searchWindowSupports); + NS_NAMED_LITERAL_STRING(dnStr, "down"); + NS_NAMED_LITERAL_STRING(upStr, "up"); + observerSvc->NotifyObservers(windowSupportsData, + "nsWebBrowserFind_FindAgain", + mFindBackwards ? upStr.get() : dnStr.get()); + windowSupportsData->GetData(getter_AddRefs(searchWindowSupports)); + // findnext performed if search window data cleared out + *aResult = searchWindowSupports == nullptr; + if (*aResult) { + return NS_OK; + } + } + + // next, look in the current frame. If found, return. + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + // if we are not searching other frames, return + if (!mSearchSubFrames && !mSearchParentFrames) { + return NS_OK; + } + + nsIDocShell* rootDocShell = rootFrame->GetDocShell(); + if (!rootDocShell) { + return NS_ERROR_FAILURE; + } + + int32_t enumDirection = mFindBackwards ? nsIDocShell::ENUMERATE_BACKWARDS : + nsIDocShell::ENUMERATE_FORWARDS; + + nsCOMPtr<nsISimpleEnumerator> docShellEnumerator; + rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll, + enumDirection, + getter_AddRefs(docShellEnumerator)); + if (NS_FAILED(rv)) { + return rv; + } + + // remember where we started + nsCOMPtr<nsIDocShellTreeItem> startingItem = + do_QueryInterface(searchFrame->GetDocShell(), &rv); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShellTreeItem> curItem; + + // XXX We should avoid searching in frameset documents here. + // We also need to honour mSearchSubFrames and mSearchParentFrames. + bool hasMore, doFind = false; + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr<nsISupports> curSupports; + rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); + if (NS_FAILED(rv)) { + break; + } + curItem = do_QueryInterface(curSupports, &rv); + if (NS_FAILED(rv)) { + break; + } + + if (doFind) { + searchFrame = curItem->GetWindow(); + if (!searchFrame) { + break; + } + + OnStartSearchFrame(searchFrame); + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + OnEndSearchFrame(searchFrame); + } + + if (curItem.get() == startingItem.get()) { + doFind = true; // start looking in frames after this one + } + } + + if (!mWrapFind) { + // remember where we left off + SetCurrentSearchFrame(searchFrame); + return NS_OK; + } + + // From here on, we're wrapping, first through the other frames, then finally + // from the beginning of the starting frame back to the starting point. + + // because nsISimpleEnumerator is totally lame and isn't resettable, I have to + // make a new one + docShellEnumerator = nullptr; + rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll, + enumDirection, + getter_AddRefs(docShellEnumerator)); + if (NS_FAILED(rv)) { + return rv; + } + + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr<nsISupports> curSupports; + rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); + if (NS_FAILED(rv)) { + break; + } + curItem = do_QueryInterface(curSupports, &rv); + if (NS_FAILED(rv)) { + break; + } + + searchFrame = curItem->GetWindow(); + if (!searchFrame) { + rv = NS_ERROR_FAILURE; + break; + } + + if (curItem.get() == startingItem.get()) { + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, true, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + break; + } + + OnStartSearchFrame(searchFrame); + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + OnEndSearchFrame(searchFrame); + } + + // remember where we left off + SetCurrentSearchFrame(searchFrame); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Something failed"); + return rv; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchString(char16_t** aSearchString) +{ + NS_ENSURE_ARG_POINTER(aSearchString); + *aSearchString = ToNewUnicode(mSearchString); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchString(const char16_t* aSearchString) +{ + mSearchString.Assign(aSearchString); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetFindBackwards(bool* aFindBackwards) +{ + NS_ENSURE_ARG_POINTER(aFindBackwards); + *aFindBackwards = mFindBackwards; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetFindBackwards(bool aFindBackwards) +{ + mFindBackwards = aFindBackwards; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetWrapFind(bool* aWrapFind) +{ + NS_ENSURE_ARG_POINTER(aWrapFind); + *aWrapFind = mWrapFind; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetWrapFind(bool aWrapFind) +{ + mWrapFind = aWrapFind; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetEntireWord(bool* aEntireWord) +{ + NS_ENSURE_ARG_POINTER(aEntireWord); + *aEntireWord = mEntireWord; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetEntireWord(bool aEntireWord) +{ + mEntireWord = aEntireWord; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetMatchCase(bool* aMatchCase) +{ + NS_ENSURE_ARG_POINTER(aMatchCase); + *aMatchCase = mMatchCase; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetMatchCase(bool aMatchCase) +{ + mMatchCase = aMatchCase; + return NS_OK; +} + +static bool +IsInNativeAnonymousSubtree(nsIContent* aContent) +{ + while (aContent) { + nsIContent* bindingParent = aContent->GetBindingParent(); + if (bindingParent == aContent) { + return true; + } + + aContent = bindingParent; + } + + return false; +} + +void +nsWebBrowserFind::SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow, + nsIDOMRange* aRange) +{ + nsCOMPtr<nsIDocument> doc = aWindow->GetDoc(); + if (!doc) { + return; + } + + nsIPresShell* presShell = doc->GetShell(); + if (!presShell) { + return; + } + + nsCOMPtr<nsIDOMNode> node; + aRange->GetStartContainer(getter_AddRefs(node)); + nsCOMPtr<nsIContent> content(do_QueryInterface(node)); + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + return; + } + nsCOMPtr<nsISelectionController> selCon; + frame->GetSelectionController(presShell->GetPresContext(), + getter_AddRefs(selCon)); + + // since the match could be an anonymous textnode inside a + // <textarea> or text <input>, we need to get the outer frame + nsITextControlFrame* tcFrame = nullptr; + for (; content; content = content->GetParent()) { + if (!IsInNativeAnonymousSubtree(content)) { + nsIFrame* f = content->GetPrimaryFrame(); + if (!f) { + return; + } + tcFrame = do_QueryFrame(f); + break; + } + } + + nsCOMPtr<nsISelection> selection; + + selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + if (selection) { + selection->RemoveAllRanges(); + selection->AddRange(aRange); + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + if (tcFrame) { + nsCOMPtr<nsIDOMElement> newFocusedElement(do_QueryInterface(content)); + fm->SetFocus(newFocusedElement, nsIFocusManager::FLAG_NOSCROLL); + } else { + nsCOMPtr<nsIDOMElement> result; + fm->MoveFocus(aWindow, nullptr, nsIFocusManager::MOVEFOCUS_CARET, + nsIFocusManager::FLAG_NOSCROLL, getter_AddRefs(result)); + } + } + + // Scroll if necessary to make the selection visible: + // Must be the last thing to do - bug 242056 + + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + selCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_WHOLE_SELECTION, + nsISelectionController::SCROLL_CENTER_VERTICALLY | + nsISelectionController::SCROLL_SYNCHRONOUS); + } +} + +// Adapted from nsTextServicesDocument::GetDocumentContentRootNode +nsresult +nsWebBrowserFind::GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode) +{ + nsresult rv; + + NS_ENSURE_ARG_POINTER(aNode); + *aNode = 0; + + nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(aDomDoc); + if (htmlDoc) { + // For HTML documents, the content root node is the body. + nsCOMPtr<nsIDOMHTMLElement> bodyElement; + rv = htmlDoc->GetBody(getter_AddRefs(bodyElement)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(bodyElement); + bodyElement.forget(aNode); + return NS_OK; + } + + // For non-HTML documents, the content root node will be the doc element. + nsCOMPtr<nsIDOMElement> docElement; + rv = aDomDoc->GetDocumentElement(getter_AddRefs(docElement)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(docElement); + docElement.forget(aNode); + return NS_OK; +} + +nsresult +nsWebBrowserFind::SetRangeAroundDocument(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPt, + nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc) +{ + nsCOMPtr<nsIDOMNode> bodyNode; + nsresult rv = GetRootNode(aDoc, getter_AddRefs(bodyNode)); + nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(bodyContent); + + uint32_t childCount = bodyContent->GetChildCount(); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + + if (mFindBackwards) { + aStartPt->SetStart(bodyNode, childCount); + aStartPt->SetEnd(bodyNode, childCount); + aEndPt->SetStart(bodyNode, 0); + aEndPt->SetEnd(bodyNode, 0); + } else { + aStartPt->SetStart(bodyNode, 0); + aStartPt->SetEnd(bodyNode, 0); + aEndPt->SetStart(bodyNode, childCount); + aEndPt->SetEnd(bodyNode, childCount); + } + + return NS_OK; +} + +// Set the range to go from the end of the current selection to the end of the +// document (forward), or beginning to beginning (reverse). or around the whole +// document if there's no selection. +nsresult +nsWebBrowserFind::GetSearchLimits(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPt, nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc, nsISelection* aSel, + bool aWrap) +{ + NS_ENSURE_ARG_POINTER(aSel); + + // There is a selection. + int32_t count = -1; + nsresult rv = aSel->GetRangeCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + if (count < 1) { + return SetRangeAroundDocument(aSearchRange, aStartPt, aEndPt, aDoc); + } + + // Need bodyNode, for the start/end of the document + nsCOMPtr<nsIDOMNode> bodyNode; + rv = GetRootNode(aDoc, getter_AddRefs(bodyNode)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode)); + NS_ENSURE_ARG_POINTER(bodyContent); + + uint32_t childCount = bodyContent->GetChildCount(); + + // There are four possible range endpoints we might use: + // DocumentStart, SelectionStart, SelectionEnd, DocumentEnd. + + nsCOMPtr<nsIDOMRange> range; + nsCOMPtr<nsIDOMNode> node; + uint32_t offset; + + // Forward, not wrapping: SelEnd to DocEnd + if (!mFindBackwards && !aWrap) { + // This isn't quite right, since the selection's ranges aren't + // necessarily in order; but they usually will be. + aSel->GetRangeAt(count - 1, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndOffset(&offset); + + aSearchRange->SetStart(node, offset); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(node, offset); + aStartPt->SetEnd(node, offset); + aEndPt->SetStart(bodyNode, childCount); + aEndPt->SetEnd(bodyNode, childCount); + } + // Backward, not wrapping: DocStart to SelStart + else if (mFindBackwards && !aWrap) { + aSel->GetRangeAt(0, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(node, offset); + aStartPt->SetEnd(node, offset); + aEndPt->SetStart(bodyNode, 0); + aEndPt->SetEnd(bodyNode, 0); + } + // Forward, wrapping: DocStart to SelEnd + else if (!mFindBackwards && aWrap) { + aSel->GetRangeAt(count - 1, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(bodyNode, 0); + aStartPt->SetEnd(bodyNode, 0); + aEndPt->SetStart(node, offset); + aEndPt->SetEnd(node, offset); + } + // Backward, wrapping: SelStart to DocEnd + else if (mFindBackwards && aWrap) { + aSel->GetRangeAt(0, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(bodyNode, childCount); + aStartPt->SetEnd(bodyNode, childCount); + aEndPt->SetStart(node, offset); + aEndPt->SetEnd(node, offset); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchFrames(bool* aSearchFrames) +{ + NS_ENSURE_ARG_POINTER(aSearchFrames); + // this only returns true if we are searching both sub and parent frames. + // There is ambiguity if the caller has previously set one, but not both of + // these. + *aSearchFrames = mSearchSubFrames && mSearchParentFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchFrames(bool aSearchFrames) +{ + mSearchSubFrames = aSearchFrames; + mSearchParentFrames = aSearchFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetCurrentSearchFrame(mozIDOMWindowProxy** aCurrentSearchFrame) +{ + NS_ENSURE_ARG_POINTER(aCurrentSearchFrame); + nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mCurrentSearchFrame); + searchFrame.forget(aCurrentSearchFrame); + return (*aCurrentSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetCurrentSearchFrame(mozIDOMWindowProxy* aCurrentSearchFrame) +{ + // is it ever valid to set this to null? + NS_ENSURE_ARG(aCurrentSearchFrame); + mCurrentSearchFrame = do_GetWeakReference(aCurrentSearchFrame); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetRootSearchFrame(mozIDOMWindowProxy** aRootSearchFrame) +{ + NS_ENSURE_ARG_POINTER(aRootSearchFrame); + nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mRootSearchFrame); + searchFrame.forget(aRootSearchFrame); + return (*aRootSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetRootSearchFrame(mozIDOMWindowProxy* aRootSearchFrame) +{ + // is it ever valid to set this to null? + NS_ENSURE_ARG(aRootSearchFrame); + mRootSearchFrame = do_GetWeakReference(aRootSearchFrame); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchSubframes(bool* aSearchSubframes) +{ + NS_ENSURE_ARG_POINTER(aSearchSubframes); + *aSearchSubframes = mSearchSubFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchSubframes(bool aSearchSubframes) +{ + mSearchSubFrames = aSearchSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchParentFrames(bool* aSearchParentFrames) +{ + NS_ENSURE_ARG_POINTER(aSearchParentFrames); + *aSearchParentFrames = mSearchParentFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchParentFrames(bool aSearchParentFrames) +{ + mSearchParentFrames = aSearchParentFrames; + return NS_OK; +} + +/* + This method handles finding in a single window (aka frame). + +*/ +nsresult +nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping, + bool* aDidFind) +{ + NS_ENSURE_ARG(aWindow); + NS_ENSURE_ARG_POINTER(aDidFind); + + *aDidFind = false; + + // Do security check, to ensure that the frame we're searching is + // acccessible from the frame where the Find is being run. + + // get a uri for the window + nsCOMPtr<nsIDocument> theDoc = aWindow->GetDoc(); + if (!theDoc) { + return NS_ERROR_FAILURE; + } + + if (!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + nsresult rv; + nsCOMPtr<nsIFind> find = do_CreateInstance(NS_FIND_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + (void)find->SetCaseSensitive(mMatchCase); + (void)find->SetFindBackwards(mFindBackwards); + + (void)find->SetEntireWord(mEntireWord); + + // Now make sure the content (for actual finding) and frame (for + // selection) models are up to date. + theDoc->FlushPendingNotifications(Flush_Frames); + + nsCOMPtr<nsISelection> sel = GetFrameSelection(aWindow); + NS_ENSURE_ARG_POINTER(sel); + + nsCOMPtr<nsIDOMRange> searchRange = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(searchRange); + nsCOMPtr<nsIDOMRange> startPt = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(startPt); + nsCOMPtr<nsIDOMRange> endPt = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(endPt); + + nsCOMPtr<nsIDOMRange> foundRange; + + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(theDoc); + MOZ_ASSERT(domDoc); + + // If !aWrapping, search from selection to end + if (!aWrapping) + rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, false); + + // If aWrapping, search the part of the starting frame + // up to the point where we left off. + else + rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, true); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = find->Find(mSearchString, searchRange, startPt, endPt, + getter_AddRefs(foundRange)); + + if (NS_SUCCEEDED(rv) && foundRange) { + *aDidFind = true; + sel->RemoveAllRanges(); + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + SetSelectionAndScroll(aWindow, foundRange); + } + + return rv; +} + +// called when we start searching a frame that is not the initial focussed +// frame. Prepare the frame to be searched. we clear the selection, so that the +// search starts from the top of the frame. +nsresult +nsWebBrowserFind::OnStartSearchFrame(nsPIDOMWindowOuter* aWindow) +{ + return ClearFrameSelection(aWindow); +} + +// called when we are done searching a frame and didn't find anything, and about +// about to start searching the next frame. +nsresult +nsWebBrowserFind::OnEndSearchFrame(nsPIDOMWindowOuter* aWindow) +{ + return NS_OK; +} + +already_AddRefed<nsISelection> +nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow) +{ + nsCOMPtr<nsIDocument> doc = aWindow->GetDoc(); + if (!doc) { + return nullptr; + } + + nsIPresShell* presShell = doc->GetShell(); + if (!presShell) { + return nullptr; + } + + // text input controls have their independent selection controllers that we + // must use when they have focus. + nsPresContext* presContext = presShell->GetPresContext(); + + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + nsCOMPtr<nsIContent> focusedContent = nsFocusManager::GetFocusedDescendant( + aWindow, false, getter_AddRefs(focusedWindow)); + + nsIFrame* frame = + focusedContent ? focusedContent->GetPrimaryFrame() : nullptr; + + nsCOMPtr<nsISelectionController> selCon; + nsCOMPtr<nsISelection> sel; + if (frame) { + frame->GetSelectionController(presContext, getter_AddRefs(selCon)); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(sel)); + if (sel) { + int32_t count = -1; + sel->GetRangeCount(&count); + if (count > 0) { + return sel.forget(); + } + } + } + + selCon = do_QueryInterface(presShell); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(sel)); + return sel.forget(); +} + +nsresult +nsWebBrowserFind::ClearFrameSelection(nsPIDOMWindowOuter* aWindow) +{ + NS_ENSURE_ARG(aWindow); + nsCOMPtr<nsISelection> selection = GetFrameSelection(aWindow); + if (selection) { + selection->RemoveAllRanges(); + } + + return NS_OK; +} + +nsresult +nsWebBrowserFind::OnFind(nsPIDOMWindowOuter* aFoundWindow) +{ + SetCurrentSearchFrame(aFoundWindow); + + // We don't want a selection to appear in two frames simultaneously + nsCOMPtr<nsPIDOMWindowOuter> lastFocusedWindow = + do_QueryReferent(mLastFocusedWindow); + if (lastFocusedWindow && lastFocusedWindow != aFoundWindow) { + ClearFrameSelection(lastFocusedWindow); + } + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + // get the containing frame and focus it. For top-level windows, the right + // window should already be focused. + nsCOMPtr<nsIDOMElement> frameElement = + do_QueryInterface(aFoundWindow->GetFrameElementInternal()); + if (frameElement) { + fm->SetFocus(frameElement, 0); + } + + mLastFocusedWindow = do_GetWeakReference(aFoundWindow); + } + + return NS_OK; +} diff --git a/components/find/public/nsWebBrowserFind.h b/components/find/public/nsWebBrowserFind.h new file mode 100644 index 000000000..df2d2d89c --- /dev/null +++ b/components/find/public/nsWebBrowserFind.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef nsWebBrowserFindImpl_h__ +#define nsWebBrowserFindImpl_h__ + +#include "nsIWebBrowserFind.h" + +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +#include "nsIFind.h" + +#include "nsString.h" + +#define NS_WEB_BROWSER_FIND_CONTRACTID "@mozilla.org/embedcomp/find;1" + +#define NS_WEB_BROWSER_FIND_CID \ + {0x57cf9383, 0x3405, 0x11d5, {0xbe, 0x5b, 0xaa, 0x20, 0xfa, 0x2c, 0xf3, 0x7c}} + +class nsISelection; +class nsIDOMWindow; + +class nsIDocShell; + +//***************************************************************************** +// class nsWebBrowserFind +//***************************************************************************** + +class nsWebBrowserFind + : public nsIWebBrowserFind + , public nsIWebBrowserFindInFrames +{ +public: + nsWebBrowserFind(); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIWebBrowserFind + NS_DECL_NSIWEBBROWSERFIND + + // nsIWebBrowserFindInFrames + NS_DECL_NSIWEBBROWSERFINDINFRAMES + +protected: + virtual ~nsWebBrowserFind(); + + bool CanFindNext() { return mSearchString.Length() != 0; } + + nsresult SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping, + bool* aDidFind); + + nsresult OnStartSearchFrame(nsPIDOMWindowOuter* aWindow); + nsresult OnEndSearchFrame(nsPIDOMWindowOuter* aWindow); + + already_AddRefed<nsISelection> GetFrameSelection(nsPIDOMWindowOuter* aWindow); + nsresult ClearFrameSelection(nsPIDOMWindowOuter* aWindow); + + nsresult OnFind(nsPIDOMWindowOuter* aFoundWindow); + + void SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow, nsIDOMRange* aRange); + + nsresult GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode); + nsresult GetSearchLimits(nsIDOMRange* aRange, + nsIDOMRange* aStartPt, nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc, nsISelection* aSel, + bool aWrap); + nsresult SetRangeAroundDocument(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint, + nsIDOMDocument* aDoc); + +protected: + nsString mSearchString; + + bool mFindBackwards; + bool mWrapFind; + bool mEntireWord; + bool mMatchCase; + + bool mSearchSubFrames; + bool mSearchParentFrames; + + // These are all weak because who knows if windows can go away during our + // lifetime. + nsWeakPtr mCurrentSearchFrame; + nsWeakPtr mRootSearchFrame; + nsWeakPtr mLastFocusedWindow; +}; + +#endif diff --git a/components/find/nsFindService.cpp b/components/find/src/nsFindService.cpp index 3e80823ce..3e80823ce 100644 --- a/components/find/nsFindService.cpp +++ b/components/find/src/nsFindService.cpp diff --git a/components/find/nsFindService.h b/components/find/src/nsFindService.h index 6b391d5bf..6b391d5bf 100644 --- a/components/find/nsFindService.h +++ b/components/find/src/nsFindService.h |