1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Aaron Leventhal (aaronl@netscape.com)
24 * Blake Ross (blake@cs.stanford.edu)
25 * Masayuki Nakano (masayuki@d-toybox.com)
26 * Asaf Romano (mano@mozilla.com)
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
44 #include "nsIServiceManager.h"
45 #include "nsIGenericFactory.h"
46 #include "nsIWebBrowserChrome.h"
47 #include "nsCURILoader.h"
48 #include "nsNetUtil.h"
51 #include "nsIDocShell.h"
52 #include "nsIDocShellTreeOwner.h"
53 #include "nsIEditorDocShell.h"
54 #include "nsISimpleEnumerator.h"
55 #include "nsPIDOMWindow.h"
56 #include "nsIDOMNSEvent.h"
57 #include "nsIPrefBranch.h"
58 #include "nsIPrefBranch2.h"
59 #include "nsIPrefService.h"
63 #include "nsIDOMNode.h"
64 #include "nsIContent.h"
66 #include "nsFrameTraversal.h"
67 #include "nsIDOMDocument.h"
68 #include "nsIImageDocument.h"
69 #include "nsIDOMHTMLDocument.h"
70 #include "nsIDOMNSHTMLDocument.h"
71 #include "nsIDOMHTMLElement.h"
72 #include "nsIEventStateManager.h"
73 #include "nsIFocusController.h"
74 #include "nsIViewManager.h"
75 #include "nsIScrollableView.h"
76 #include "nsIDocument.h"
77 #include "nsISelection.h"
78 #include "nsISelectElement.h"
80 #include "nsTextFragment.h"
81 #include "nsIDOMNSEditableElement.h"
82 #include "nsIDOMNSHTMLElement.h"
83 #include "nsIEditor.h"
85 #include "nsIDocShellTreeItem.h"
86 #include "nsIWebNavigation.h"
87 #include "nsIInterfaceRequestor.h"
88 #include "nsIInterfaceRequestorUtils.h"
89 #include "nsContentCID.h"
90 #include "nsLayoutCID.h"
91 #include "nsWidgetsCID.h"
92 #include "nsIFormControl.h"
93 #include "nsINameSpaceManager.h"
94 #include "nsIWindowWatcher.h"
95 #include "nsIObserverService.h"
97 #include "nsTypeAheadFind.h"
99 NS_INTERFACE_MAP_BEGIN(nsTypeAheadFind
)
100 NS_INTERFACE_MAP_ENTRY(nsITypeAheadFind
)
101 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsITypeAheadFind
)
102 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
103 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
106 NS_IMPL_ADDREF(nsTypeAheadFind
)
107 NS_IMPL_RELEASE(nsTypeAheadFind
)
109 static NS_DEFINE_IID(kRangeCID
, NS_RANGE_CID
);
110 static NS_DEFINE_CID(kFrameTraversalCID
, NS_FRAMETRAVERSAL_CID
);
112 #define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1"
114 nsTypeAheadFind::nsTypeAheadFind():
115 mLinksOnlyPref(PR_FALSE
), mStartLinksOnlyPref(PR_FALSE
),
116 mLinksOnly(PR_FALSE
), mCaretBrowsingOn(PR_FALSE
), mLastFindLength(0),
117 mIsSoundInitialized(PR_FALSE
)
121 nsTypeAheadFind::~nsTypeAheadFind()
123 nsCOMPtr
<nsIPrefBranch2
> prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID
));
125 prefInternal
->RemoveObserver("accessibility.typeaheadfind", this);
126 prefInternal
->RemoveObserver("accessibility.browsewithcaret", this);
131 nsTypeAheadFind::Init(nsIDocShell
* aDocShell
)
133 nsCOMPtr
<nsIPrefBranch2
> prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID
));
134 mSearchRange
= do_CreateInstance(kRangeCID
);
135 mStartPointRange
= do_CreateInstance(kRangeCID
);
136 mEndPointRange
= do_CreateInstance(kRangeCID
);
137 mFind
= do_CreateInstance(NS_FIND_CONTRACTID
);
138 if (!prefInternal
|| !mSearchRange
|| !mStartPointRange
|| !mEndPointRange
|| !mFind
)
139 return NS_ERROR_FAILURE
;
141 SetDocShell(aDocShell
);
143 // ----------- Listen to prefs ------------------
144 nsresult rv
= prefInternal
->AddObserver("accessibility.browsewithcaret", this, PR_FALSE
);
145 NS_ENSURE_SUCCESS(rv
, rv
);
147 // ----------- Get initial preferences ----------
150 // ----------- Set search options ---------------
151 mFind
->SetCaseSensitive(PR_FALSE
);
152 mFind
->SetWordBreaker(nsnull
);
158 nsTypeAheadFind::PrefsReset()
160 nsCOMPtr
<nsIPrefBranch
> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID
));
161 NS_ENSURE_TRUE(prefBranch
, NS_ERROR_FAILURE
);
163 prefBranch
->GetBoolPref("accessibility.typeaheadfind.linksonly",
166 prefBranch
->GetBoolPref("accessibility.typeaheadfind.startlinksonly",
167 &mStartLinksOnlyPref
);
169 PRBool isSoundEnabled
= PR_TRUE
;
170 prefBranch
->GetBoolPref("accessibility.typeaheadfind.enablesound",
172 nsXPIDLCString soundStr
;
174 prefBranch
->GetCharPref("accessibility.typeaheadfind.soundURL", getter_Copies(soundStr
));
176 mNotFoundSoundURL
= soundStr
;
178 prefBranch
->GetBoolPref("accessibility.browsewithcaret",
185 nsTypeAheadFind::SetCaseSensitive(PRBool isCaseSensitive
)
187 mFind
->SetCaseSensitive(isCaseSensitive
);
192 nsTypeAheadFind::GetCaseSensitive(PRBool
* isCaseSensitive
)
194 mFind
->GetCaseSensitive(isCaseSensitive
);
199 nsTypeAheadFind::SetDocShell(nsIDocShell
* aDocShell
)
201 mDocShell
= do_GetWeakReference(aDocShell
);
203 mWebBrowserFind
= do_GetInterface(aDocShell
);
204 NS_ENSURE_TRUE(mWebBrowserFind
, NS_ERROR_FAILURE
);
206 nsCOMPtr
<nsIPresShell
> presShell
;
207 aDocShell
->GetPresShell(getter_AddRefs(presShell
));
208 mPresShell
= do_GetWeakReference(presShell
);
210 mStartFindRange
= nsnull
;
211 mStartPointRange
= do_CreateInstance(kRangeCID
);
212 mSearchRange
= do_CreateInstance(kRangeCID
);
215 mFoundEditable
= nsnull
;
216 mCurrentWindow
= nsnull
;
218 mSelectionController
= nsnull
;
224 nsTypeAheadFind::SetSelectionModeAndRepaint(PRInt16 aToggle
)
226 nsCOMPtr
<nsISelectionController
> selectionController
=
227 do_QueryReferent(mSelectionController
);
228 if (!selectionController
) {
232 selectionController
->SetDisplaySelection(aToggle
);
233 selectionController
->RepaintSelection(nsISelectionController::SELECTION_NORMAL
);
239 nsTypeAheadFind::CollapseSelection()
241 nsCOMPtr
<nsISelectionController
> selectionController
=
242 do_QueryReferent(mSelectionController
);
243 if (!selectionController
) {
247 nsCOMPtr
<nsISelection
> selection
;
248 selectionController
->GetSelection(nsISelectionController::SELECTION_NORMAL
,
249 getter_AddRefs(selection
));
251 selection
->CollapseToStart();
257 nsTypeAheadFind::Observe(nsISupports
*aSubject
, const char *aTopic
,
258 const PRUnichar
*aData
)
260 if (!nsCRT::strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
))
267 nsTypeAheadFind::SaveFind()
270 mWebBrowserFind
->SetSearchString(mTypeAheadBuffer
.get());
272 // save the length of this find for "not found" sound
273 mLastFindLength
= mTypeAheadBuffer
.Length();
277 nsTypeAheadFind::PlayNotFoundSound()
279 if (mNotFoundSoundURL
.IsEmpty()) // no sound
282 if (!mSoundInterface
)
283 mSoundInterface
= do_CreateInstance("@mozilla.org/sound;1");
285 if (mSoundInterface
) {
286 mIsSoundInitialized
= PR_TRUE
;
288 if (mNotFoundSoundURL
.Equals("beep")) {
289 mSoundInterface
->Beep();
293 nsCOMPtr
<nsIURI
> soundURI
;
294 if (mNotFoundSoundURL
.Equals("default"))
295 NS_NewURI(getter_AddRefs(soundURI
), NS_LITERAL_CSTRING(TYPEAHEADFIND_NOTFOUND_WAV_URL
));
297 NS_NewURI(getter_AddRefs(soundURI
), mNotFoundSoundURL
);
299 nsCOMPtr
<nsIURL
> soundURL(do_QueryInterface(soundURI
));
301 mSoundInterface
->Play(soundURL
);
306 nsTypeAheadFind::FindItNow(nsIPresShell
*aPresShell
, PRBool aIsLinksOnly
,
307 PRBool aIsFirstVisiblePreferred
, PRBool aFindPrev
,
310 *aResult
= FIND_NOTFOUND
;
312 mFoundEditable
= nsnull
;
313 mCurrentWindow
= nsnull
;
314 nsCOMPtr
<nsIPresShell
> startingPresShell (GetPresShell());
315 if (!startingPresShell
) {
316 nsCOMPtr
<nsIDocShell
> ds
= do_QueryReferent(mDocShell
);
317 NS_ENSURE_TRUE(ds
, NS_ERROR_FAILURE
);
319 ds
->GetPresShell(getter_AddRefs(startingPresShell
));
320 mPresShell
= do_GetWeakReference(startingPresShell
);
323 nsCOMPtr
<nsIPresShell
> presShell(aPresShell
);
326 presShell
= startingPresShell
; // this is the current document
329 return NS_ERROR_FAILURE
;
332 nsRefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
335 return NS_ERROR_FAILURE
;
337 nsCOMPtr
<nsISelection
> selection
;
338 nsCOMPtr
<nsISelectionController
> selectionController
=
339 do_QueryReferent(mSelectionController
);
340 if (!selectionController
) {
341 GetSelection(presShell
, getter_AddRefs(selectionController
),
342 getter_AddRefs(selection
)); // cache for reuse
343 mSelectionController
= do_GetWeakReference(selectionController
);
345 selectionController
->GetSelection(
346 nsISelectionController::SELECTION_NORMAL
, getter_AddRefs(selection
));
349 nsCOMPtr
<nsISupports
> startingContainer
= presContext
->GetContainer();
350 nsCOMPtr
<nsIDocShellTreeItem
> treeItem(do_QueryInterface(startingContainer
));
351 NS_ASSERTION(treeItem
, "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]");
353 return NS_ERROR_FAILURE
;
355 nsCOMPtr
<nsIDocShellTreeItem
> rootContentTreeItem
;
356 nsCOMPtr
<nsIDocShell
> currentDocShell
;
357 nsCOMPtr
<nsIDocShell
> startingDocShell(do_QueryInterface(startingContainer
));
359 treeItem
->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem
));
360 nsCOMPtr
<nsIDocShell
> rootContentDocShell
=
361 do_QueryInterface(rootContentTreeItem
);
363 if (!rootContentDocShell
)
364 return NS_ERROR_FAILURE
;
366 nsCOMPtr
<nsISimpleEnumerator
> docShellEnumerator
;
367 rootContentDocShell
->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent
,
368 nsIDocShell::ENUMERATE_FORWARDS
,
369 getter_AddRefs(docShellEnumerator
));
371 // Default: can start at the current document
372 nsCOMPtr
<nsISupports
> currentContainer
= startingContainer
=
373 do_QueryInterface(rootContentDocShell
);
375 // Iterate up to current shell, if there's more than 1 that we're
377 PRBool hasMoreDocShells
;
379 while (NS_SUCCEEDED(docShellEnumerator
->HasMoreElements(&hasMoreDocShells
)) && hasMoreDocShells
) {
380 docShellEnumerator
->GetNext(getter_AddRefs(currentContainer
));
381 currentDocShell
= do_QueryInterface(currentContainer
);
382 if (!currentDocShell
|| currentDocShell
== startingDocShell
|| aIsFirstVisiblePreferred
)
386 // ------------ Get ranges ready ----------------
387 nsCOMPtr
<nsIDOMRange
> returnRange
;
388 nsCOMPtr
<nsIPresShell
> focusedPS
;
389 if (NS_FAILED(GetSearchContainers(currentContainer
,
390 (!aIsFirstVisiblePreferred
||
392 selectionController
.get() : nsnull
,
393 aIsFirstVisiblePreferred
, aFindPrev
,
394 getter_AddRefs(presShell
),
395 getter_AddRefs(presContext
)))) {
396 return NS_ERROR_FAILURE
;
399 PRInt16 rangeCompareResult
= 0;
400 mStartPointRange
->CompareBoundaryPoints(nsIDOMRange::START_TO_START
, mSearchRange
, &rangeCompareResult
);
401 // No need to wrap find in doc if starting at beginning
402 PRBool hasWrapped
= (rangeCompareResult
< 0);
404 if (mTypeAheadBuffer
.IsEmpty())
405 return NS_ERROR_FAILURE
;
407 mFind
->SetFindBackwards(aFindPrev
);
409 while (PR_TRUE
) { // ----- Outer while loop: go through all docs -----
410 while (PR_TRUE
) { // === Inner while loop: go through a single doc ===
411 mFind
->Find(mTypeAheadBuffer
.get(), mSearchRange
, mStartPointRange
,
412 mEndPointRange
, getter_AddRefs(returnRange
));
415 break; // Nothing found in this doc, go to outer loop (try next doc)
417 // ------- Test resulting found range for success conditions ------
418 PRBool isInsideLink
= PR_FALSE
, isStartingLink
= PR_FALSE
;
421 // Don't check if inside link when searching all text
422 RangeStartsInsideLink(returnRange
, presShell
, &isInsideLink
,
426 PRBool usesIndependentSelection
;
427 if (!IsRangeVisible(presShell
, presContext
, returnRange
,
428 aIsFirstVisiblePreferred
, PR_FALSE
,
429 getter_AddRefs(mStartPointRange
),
430 &usesIndependentSelection
) ||
431 (aIsLinksOnly
&& !isInsideLink
) ||
432 (mStartLinksOnlyPref
&& aIsLinksOnly
&& !isStartingLink
)) {
433 // ------ Failure ------
434 // Start find again from here
435 returnRange
->CloneRange(getter_AddRefs(mStartPointRange
));
438 mStartPointRange
->Collapse(aFindPrev
);
443 // ------ Success! -------
444 // Hide old selection (new one may be on a different controller)
446 selection
->CollapseToStart();
447 SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON
);
450 // Make sure new document is selected
451 if (presShell
!= startingPresShell
) {
452 // We are in a new document (because of frames/iframes)
453 mPresShell
= do_GetWeakReference(presShell
);
456 nsCOMPtr
<nsIDocument
> document
=
457 do_QueryInterface(presShell
->GetDocument());
458 NS_ASSERTION(document
, "Wow, presShell doesn't have document!");
460 return NS_ERROR_UNEXPECTED
;
462 nsCOMPtr
<nsPIDOMWindow
> window
= document
->GetWindow();
463 NS_ASSERTION(window
, "document has no window");
465 return NS_ERROR_UNEXPECTED
;
467 if (usesIndependentSelection
) {
468 /* If a search result is found inside an editable element, we'll focus
469 * the element only if focus is in our content window, i.e.
470 * |if (focusedWindow.top == ourWindow.top)| */
471 PRBool shouldFocusEditableElement
= false;
472 nsIFocusController
* focusController
= window
->GetRootFocusController();
473 if (focusController
) {
474 nsCOMPtr
<nsIDOMWindowInternal
> focusedWindow
;
475 nsresult rv
= focusController
->GetFocusedWindow(getter_AddRefs(focusedWindow
));
476 if (NS_SUCCEEDED(rv
)) {
477 nsCOMPtr
<nsPIDOMWindow
> fwPI(do_QueryInterface(focusedWindow
, &rv
));
478 if (NS_SUCCEEDED(rv
)) {
479 nsCOMPtr
<nsIDocShellTreeItem
> fwTreeItem
480 (do_QueryInterface(fwPI
->GetDocShell(), &rv
));
481 if (NS_SUCCEEDED(rv
)) {
482 nsCOMPtr
<nsIDocShellTreeItem
> fwRootTreeItem
;
483 rv
= fwTreeItem
->GetSameTypeRootTreeItem(getter_AddRefs(fwRootTreeItem
));
484 if (NS_SUCCEEDED(rv
) && fwRootTreeItem
== rootContentTreeItem
)
485 shouldFocusEditableElement
= PR_TRUE
;
491 // We may be inside an editable element, and therefore the selection
492 // may be controlled by a different selection controller. Walk up the
493 // chain of parent nodes to see if we find one.
494 nsCOMPtr
<nsIDOMNode
> node
;
495 returnRange
->GetStartContainer(getter_AddRefs(node
));
497 nsCOMPtr
<nsIDOMNSEditableElement
> editable
= do_QueryInterface(node
);
499 // Inside an editable element. Get the correct selection
500 // controller and selection.
501 nsCOMPtr
<nsIEditor
> editor
;
502 editable
->GetEditor(getter_AddRefs(editor
));
503 NS_ASSERTION(editor
, "Editable element has no editor!");
507 editor
->GetSelectionController(
508 getter_AddRefs(selectionController
));
509 if (selectionController
) {
510 selectionController
->GetSelection(
511 nsISelectionController::SELECTION_NORMAL
,
512 getter_AddRefs(selection
));
514 mFoundEditable
= do_QueryInterface(node
);
516 if (!shouldFocusEditableElement
)
519 // Otherwise move focus/caret to editable element
520 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mFoundEditable
);
522 content
->SetFocus(presContext
);
523 presContext
->EventStateManager()->MoveCaretToFocus();
527 nsIDOMNode
* tmp
= node
;
528 tmp
->GetParentNode(getter_AddRefs(node
));
531 // If we reach here without setting mFoundEditable, then something
532 // besides editable elements can cause us to have an independent
533 // selection controller. I don't know whether this is possible.
534 // Currently, we simply fall back to grabbing the document's selection
535 // controller in this case. Perhaps we should reject this find match
537 NS_ASSERTION(mFoundEditable
, "Independent selection controller on "
538 "non-editable element!");
541 if (!mFoundEditable
) {
542 // Not using a separate selection controller, so just get the
543 // document's controller and selection.
544 GetSelection(presShell
, getter_AddRefs(selectionController
),
545 getter_AddRefs(selection
));
547 mSelectionController
= do_GetWeakReference(selectionController
);
549 // Select the found text
551 selection
->RemoveAllRanges();
552 selection
->AddRange(returnRange
);
555 if (!mFoundEditable
) {
556 currentDocShell
->SetHasFocus(PR_TRUE
); // What does this do?
558 // Keep track of whether we've found a link, so we can focus it, jump
559 // to its target, etc.
560 nsIEventStateManager
*esm
= presContext
->EventStateManager();
561 PRBool isSelectionWithFocus
;
562 esm
->MoveFocusToCaret(PR_TRUE
, &isSelectionWithFocus
);
563 if (isSelectionWithFocus
) {
564 nsCOMPtr
<nsIContent
> lastFocusedContent
;
565 esm
->GetLastFocusedContent(getter_AddRefs(lastFocusedContent
));
566 nsCOMPtr
<nsIDOMElement
>
567 lastFocusedElement(do_QueryInterface(lastFocusedContent
));
568 mFoundLink
= lastFocusedElement
;
572 // Change selection color to ATTENTION and scroll to it. Careful: we
573 // must wait until after we goof with focus above before changing to
574 // ATTENTION, or when we MoveFocusToCaret() and the selection is not on a
575 // link, we'll blur, which will lose the ATTENTION.
576 if (selectionController
) {
577 // Beware! This may flush notifications via synchronous
578 // ScrollSelectionIntoView.
579 SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ATTENTION
);
580 selectionController
->ScrollSelectionIntoView(
581 nsISelectionController::SELECTION_NORMAL
,
582 nsISelectionController::SELECTION_FOCUS_REGION
, PR_TRUE
);
585 mCurrentWindow
= window
;
586 *aResult
= hasWrapped
? FIND_WRAPPED
: FIND_FOUND
;
590 // ======= end-inner-while (go through a single document) ==========
592 // ---------- Nothing found yet, try next document -------------
593 PRBool hasTriedFirstDoc
= PR_FALSE
;
595 // ==== Second inner loop - get another while ====
596 if (NS_SUCCEEDED(docShellEnumerator
->HasMoreElements(&hasMoreDocShells
))
597 && hasMoreDocShells
) {
598 docShellEnumerator
->GetNext(getter_AddRefs(currentContainer
));
599 NS_ASSERTION(currentContainer
, "HasMoreElements lied to us!");
600 currentDocShell
= do_QueryInterface(currentContainer
);
605 else if (hasTriedFirstDoc
) // Avoid potential infinite loop
606 return NS_ERROR_FAILURE
; // No content doc shells
608 // Reached last doc shell, loop around back to first doc shell
609 rootContentDocShell
->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent
,
610 nsIDocShell::ENUMERATE_FORWARDS
,
611 getter_AddRefs(docShellEnumerator
));
612 hasTriedFirstDoc
= PR_TRUE
;
613 } while (docShellEnumerator
); // ==== end second inner while ===
615 PRBool continueLoop
= PR_FALSE
;
616 if (currentDocShell
!= startingDocShell
)
617 continueLoop
= PR_TRUE
; // Try next document
618 else if (!hasWrapped
|| aIsFirstVisiblePreferred
) {
619 // Finished searching through docshells:
620 // If aFirstVisiblePreferred == PR_TRUE, we may need to go through all
621 // docshells twice -once to look for visible matches, the second time
623 aIsFirstVisiblePreferred
= PR_FALSE
;
624 hasWrapped
= PR_TRUE
;
625 continueLoop
= PR_TRUE
; // Go through all docs again
629 if (NS_FAILED(GetSearchContainers(currentContainer
, nsnull
,
630 aIsFirstVisiblePreferred
, aFindPrev
,
631 getter_AddRefs(presShell
),
632 getter_AddRefs(presContext
)))) {
637 // Reverse mode: swap start and end points, so that we start
638 // at end of document and go to beginning
639 nsCOMPtr
<nsIDOMRange
> tempRange
;
640 mStartPointRange
->CloneRange(getter_AddRefs(tempRange
));
641 mStartPointRange
= mEndPointRange
;
642 mEndPointRange
= tempRange
;
648 // ------------- Failed --------------
650 } // end-outer-while: go through all docs
652 return NS_ERROR_FAILURE
;
656 nsTypeAheadFind::GetSearchString(nsAString
& aSearchString
)
658 aSearchString
= mTypeAheadBuffer
;
663 nsTypeAheadFind::GetFoundLink(nsIDOMElement
** aFoundLink
)
665 NS_ENSURE_ARG_POINTER(aFoundLink
);
666 *aFoundLink
= mFoundLink
;
667 NS_IF_ADDREF(*aFoundLink
);
672 nsTypeAheadFind::GetFoundEditable(nsIDOMElement
** aFoundEditable
)
674 NS_ENSURE_ARG_POINTER(aFoundEditable
);
675 *aFoundEditable
= mFoundEditable
;
676 NS_IF_ADDREF(*aFoundEditable
);
681 nsTypeAheadFind::GetCurrentWindow(nsIDOMWindow
** aCurrentWindow
)
683 NS_ENSURE_ARG_POINTER(aCurrentWindow
);
684 *aCurrentWindow
= mCurrentWindow
;
685 NS_IF_ADDREF(*aCurrentWindow
);
690 nsTypeAheadFind::GetSearchContainers(nsISupports
*aContainer
,
691 nsISelectionController
*aSelectionController
,
692 PRBool aIsFirstVisiblePreferred
,
694 nsIPresShell
**aPresShell
,
695 nsPresContext
**aPresContext
)
697 NS_ENSURE_ARG_POINTER(aContainer
);
698 NS_ENSURE_ARG_POINTER(aPresShell
);
699 NS_ENSURE_ARG_POINTER(aPresContext
);
701 *aPresShell
= nsnull
;
702 *aPresContext
= nsnull
;
704 nsCOMPtr
<nsIDocShell
> docShell(do_QueryInterface(aContainer
));
706 return NS_ERROR_FAILURE
;
708 nsCOMPtr
<nsIPresShell
> presShell
;
709 docShell
->GetPresShell(getter_AddRefs(presShell
));
711 nsRefPtr
<nsPresContext
> presContext
;
712 docShell
->GetPresContext(getter_AddRefs(presContext
));
714 if (!presShell
|| !presContext
)
715 return NS_ERROR_FAILURE
;
717 nsIDocument
* doc
= presShell
->GetDocument();
720 return NS_ERROR_FAILURE
;
722 nsCOMPtr
<nsIContent
> rootContent
;
723 nsCOMPtr
<nsIDOMHTMLDocument
> htmlDoc(do_QueryInterface(doc
));
725 nsCOMPtr
<nsIDOMHTMLElement
> bodyEl
;
726 htmlDoc
->GetBody(getter_AddRefs(bodyEl
));
727 rootContent
= do_QueryInterface(bodyEl
);
731 rootContent
= doc
->GetRootContent();
733 nsCOMPtr
<nsIDOMNode
> rootNode(do_QueryInterface(rootContent
));
736 return NS_ERROR_FAILURE
;
738 PRUint32 childCount
= rootContent
->GetChildCount();
740 mSearchRange
->SelectNodeContents(rootNode
);
742 mEndPointRange
->SetEnd(rootNode
, childCount
);
743 mEndPointRange
->Collapse(PR_FALSE
); // collapse to end
745 // Consider current selection as null if
746 // it's not in the currently focused document
747 nsCOMPtr
<nsIDOMRange
> currentSelectionRange
;
748 nsCOMPtr
<nsIPresShell
> selectionPresShell
= GetPresShell();
749 if (aSelectionController
&& selectionPresShell
&& selectionPresShell
== presShell
) {
750 nsCOMPtr
<nsISelection
> selection
;
751 aSelectionController
->GetSelection(
752 nsISelectionController::SELECTION_NORMAL
, getter_AddRefs(selection
));
754 selection
->GetRangeAt(0, getter_AddRefs(currentSelectionRange
));
757 if (!currentSelectionRange
) {
758 // Ensure visible range, move forward if necessary
759 // This uses ignores the return value, but usese the side effect of
760 // IsRangeVisible. It returns the first visible range after searchRange
761 IsRangeVisible(presShell
, presContext
, mSearchRange
,
762 aIsFirstVisiblePreferred
, PR_TRUE
,
763 getter_AddRefs(mStartPointRange
), nsnull
);
767 nsCOMPtr
<nsIDOMNode
> startNode
;
769 currentSelectionRange
->GetStartContainer(getter_AddRefs(startNode
));
770 currentSelectionRange
->GetStartOffset(&startOffset
);
772 currentSelectionRange
->GetEndContainer(getter_AddRefs(startNode
));
773 currentSelectionRange
->GetEndOffset(&startOffset
);
776 startNode
= rootNode
;
778 // We need to set the start point this way, other methods haven't worked
779 mStartPointRange
->SelectNode(startNode
);
780 mStartPointRange
->SetStart(startNode
, startOffset
);
783 mStartPointRange
->Collapse(PR_TRUE
); // collapse to start
785 *aPresShell
= presShell
;
786 NS_ADDREF(*aPresShell
);
788 *aPresContext
= presContext
;
789 NS_ADDREF(*aPresContext
);
795 nsTypeAheadFind::RangeStartsInsideLink(nsIDOMRange
*aRange
,
796 nsIPresShell
*aPresShell
,
797 PRBool
*aIsInsideLink
,
798 PRBool
*aIsStartingLink
)
800 *aIsInsideLink
= PR_FALSE
;
801 *aIsStartingLink
= PR_TRUE
;
803 // ------- Get nsIContent to test -------
804 nsCOMPtr
<nsIDOMNode
> startNode
;
805 nsCOMPtr
<nsIContent
> startContent
, origContent
;
806 aRange
->GetStartContainer(getter_AddRefs(startNode
));
808 aRange
->GetStartOffset(&startOffset
);
810 startContent
= do_QueryInterface(startNode
);
812 NS_NOTREACHED("startContent should never be null");
815 origContent
= startContent
;
817 if (startContent
->IsNodeOfType(nsINode::eELEMENT
)) {
818 nsIContent
*childContent
= startContent
->GetChildAt(startOffset
);
820 startContent
= childContent
;
823 else if (startOffset
> 0) {
824 const nsTextFragment
*textFrag
= startContent
->GetText();
826 // look for non whitespace character before start offset
827 for (PRInt32 index
= 0; index
< startOffset
; index
++) {
828 if (!XP_IS_SPACE(textFrag
->CharAt(index
))) {
829 *aIsStartingLink
= PR_FALSE
; // not at start of a node
837 // ------- Check to see if inside link ---------
839 // We now have the correct start node for the range
840 // Search for links, starting with startNode, and going up parent chain
842 nsCOMPtr
<nsIAtom
> tag
, hrefAtom(do_GetAtom("href"));
843 nsCOMPtr
<nsIAtom
> typeAtom(do_GetAtom("type"));
846 // Keep testing while startContent is equal to something,
847 // eventually we'll run out of ancestors
849 if (startContent
->IsNodeOfType(nsINode::eHTML
)) {
850 nsCOMPtr
<nsILink
> link(do_QueryInterface(startContent
));
852 // Check to see if inside HTML link
853 *aIsInsideLink
= startContent
->HasAttr(kNameSpaceID_None
, hrefAtom
);
858 // Any xml element can be an xlink
859 *aIsInsideLink
= startContent
->HasAttr(kNameSpaceID_XLink
, hrefAtom
);
860 if (*aIsInsideLink
) {
861 if (!startContent
->AttrValueIs(kNameSpaceID_XLink
, typeAtom
,
862 NS_LITERAL_STRING("simple"),
864 *aIsInsideLink
= PR_FALSE
; // Xlink must be type="simple"
872 nsCOMPtr
<nsIContent
> parent
= startContent
->GetParent();
876 nsIContent
*parentsFirstChild
= parent
->GetChildAt(0);
878 // We don't want to look at a whitespace-only first child
879 if (parentsFirstChild
&& parentsFirstChild
->TextIsOnlyWhitespace()) {
880 parentsFirstChild
= parent
->GetChildAt(1);
883 if (parentsFirstChild
!= startContent
) {
884 // startContent wasn't a first child, so we conclude that
885 // if this is inside a link, it's not at the beginning of it
886 *aIsStartingLink
= PR_FALSE
;
889 startContent
= parent
;
892 *aIsStartingLink
= PR_FALSE
;
895 /* Find another match in the page. */
897 nsTypeAheadFind::FindAgain(PRBool aFindBackwards
, PRBool aLinksOnly
,
901 *aResult
= FIND_NOTFOUND
;
903 mLinksOnly
= aLinksOnly
;
904 if (!mTypeAheadBuffer
.IsEmpty())
905 // Beware! This may flush notifications via synchronous
906 // ScrollSelectionIntoView.
907 FindItNow(nsnull
, mLinksOnly
, PR_FALSE
, aFindBackwards
, aResult
);
913 nsTypeAheadFind::Find(const nsAString
& aSearchString
, PRBool aLinksOnly
,
916 *aResult
= FIND_NOTFOUND
;
918 nsCOMPtr
<nsIPresShell
> presShell (GetPresShell());
920 nsCOMPtr
<nsIDocShell
> ds (do_QueryReferent(mDocShell
));
921 NS_ENSURE_TRUE(ds
, NS_ERROR_FAILURE
);
923 ds
->GetPresShell(getter_AddRefs(presShell
));
924 mPresShell
= do_GetWeakReference(presShell
);
926 nsCOMPtr
<nsISelection
> selection
;
927 nsCOMPtr
<nsISelectionController
> selectionController
=
928 do_QueryReferent(mSelectionController
);
929 if (!selectionController
) {
930 GetSelection(presShell
, getter_AddRefs(selectionController
),
931 getter_AddRefs(selection
)); // cache for reuse
932 mSelectionController
= do_GetWeakReference(selectionController
);
934 selectionController
->GetSelection(
935 nsISelectionController::SELECTION_NORMAL
, getter_AddRefs(selection
));
939 selection
->CollapseToStart();
941 if (aSearchString
.IsEmpty()) {
942 mTypeAheadBuffer
.Truncate();
944 // These will be initialized to their true values after the first character
946 mStartFindRange
= nsnull
;
947 mSelectionController
= nsnull
;
949 *aResult
= FIND_FOUND
;
953 PRBool atEnd
= PR_FALSE
;
954 if (mTypeAheadBuffer
.Length()) {
955 const nsAString
& oldStr
= Substring(mTypeAheadBuffer
, 0, mTypeAheadBuffer
.Length());
956 const nsAString
& newStr
= Substring(aSearchString
, 0, mTypeAheadBuffer
.Length());
957 if (oldStr
.Equals(newStr
))
960 const nsAString
& newStr2
= Substring(aSearchString
, 0, aSearchString
.Length());
961 const nsAString
& oldStr2
= Substring(mTypeAheadBuffer
, 0, aSearchString
.Length());
962 if (oldStr2
.Equals(newStr2
))
966 mStartFindRange
= nsnull
;
969 if (!mIsSoundInitialized
&& !mNotFoundSoundURL
.IsEmpty()) {
970 // This makes sure system sound library is loaded so that
971 // there's no lag before the first sound is played
972 // by waiting for the first keystroke, we still get the startup time benefits.
973 mIsSoundInitialized
= PR_TRUE
;
974 mSoundInterface
= do_CreateInstance("@mozilla.org/sound;1");
975 if (mSoundInterface
&& !mNotFoundSoundURL
.Equals(NS_LITERAL_CSTRING("beep"))) {
976 mSoundInterface
->Init();
980 mLinksOnly
= aLinksOnly
;
983 // After each keystroke, ensure sound object is destroyed, to free up memory
984 // allocated for error sound, otherwise Windows' nsISound impl
985 // holds onto the last played sound, using up memory.
986 mSoundInterface
= nsnull
;
989 PRInt32 bufferLength
= mTypeAheadBuffer
.Length();
991 mTypeAheadBuffer
= aSearchString
;
993 PRBool isFirstVisiblePreferred
= PR_FALSE
;
995 // --------- Initialize find if 1st char ----------
996 if (bufferLength
== 0) {
997 // Reset links only to default, if not manually set
998 // by the user via ' or / keypress at beginning
1000 mLinksOnly
= mLinksOnlyPref
;
1002 // If you can see the selection (not collapsed or thru caret browsing),
1003 // or if already focused on a page element, start there.
1004 // Otherwise we're going to start at the first visible element
1005 PRBool isSelectionCollapsed
= PR_TRUE
;
1007 selection
->GetIsCollapsed(&isSelectionCollapsed
);
1009 // If true, we will scan from top left of visible area
1010 // If false, we will scan from start of selection
1011 isFirstVisiblePreferred
= !atEnd
&& !mCaretBrowsingOn
&& isSelectionCollapsed
;
1012 if (isFirstVisiblePreferred
) {
1013 // Get focused content from esm. If it's null, the document is focused.
1014 // If not, make sure the selection is in sync with the focus, so we can
1015 // start our search from there.
1016 nsCOMPtr
<nsIContent
> focusedContent
;
1017 nsPresContext
* presContext
= presShell
->GetPresContext();
1018 NS_ENSURE_TRUE(presContext
, NS_OK
);
1020 nsIEventStateManager
*esm
= presContext
->EventStateManager();
1021 esm
->GetFocusedContent(getter_AddRefs(focusedContent
));
1022 if (focusedContent
) {
1023 esm
->MoveCaretToFocus();
1024 isFirstVisiblePreferred
= PR_FALSE
;
1029 // ----------- Find the text! ---------------------
1030 // Beware! This may flush notifications via synchronous
1031 // ScrollSelectionIntoView.
1032 nsresult rv
= FindItNow(nsnull
, mLinksOnly
, isFirstVisiblePreferred
,
1035 // ---------Handle success or failure ---------------
1036 if (NS_SUCCEEDED(rv
)) {
1037 if (mTypeAheadBuffer
.Length() == 1) {
1038 // If first letter, store where the first find succeeded
1039 // (mStartFindRange)
1041 mStartFindRange
= nsnull
;
1043 nsCOMPtr
<nsIDOMRange
> startFindRange
;
1044 selection
->GetRangeAt(0, getter_AddRefs(startFindRange
));
1046 startFindRange
->CloneRange(getter_AddRefs(mStartFindRange
));
1052 if (mTypeAheadBuffer
.Length() > mLastFindLength
)
1053 PlayNotFoundSound();
1061 nsTypeAheadFind::GetSelection(nsIPresShell
*aPresShell
,
1062 nsISelectionController
**aSelCon
,
1063 nsISelection
**aDOMSel
)
1068 // if aCurrentNode is nsnull, get selection for document
1071 nsPresContext
* presContext
= aPresShell
->GetPresContext();
1073 nsIFrame
*frame
= aPresShell
->GetRootFrame();
1075 if (presContext
&& frame
) {
1076 frame
->GetSelectionController(presContext
, aSelCon
);
1078 (*aSelCon
)->GetSelection(nsISelectionController::SELECTION_NORMAL
,
1086 nsTypeAheadFind::IsRangeVisible(nsIPresShell
*aPresShell
,
1087 nsPresContext
*aPresContext
,
1088 nsIDOMRange
*aRange
, PRBool aMustBeInViewPort
,
1089 PRBool aGetTopVisibleLeaf
,
1090 nsIDOMRange
**aFirstVisibleRange
,
1091 PRBool
*aUsesIndependentSelection
)
1093 NS_ENSURE_ARG_POINTER(aPresShell
);
1094 NS_ENSURE_ARG_POINTER(aPresContext
);
1095 NS_ENSURE_ARG_POINTER(aRange
);
1096 NS_ENSURE_ARG_POINTER(aFirstVisibleRange
);
1098 // We need to know if the range start is visible.
1099 // Otherwise, return a the first visible range start
1100 // in aFirstVisibleRange
1102 aRange
->CloneRange(aFirstVisibleRange
);
1103 nsCOMPtr
<nsIDOMNode
> node
;
1104 aRange
->GetStartContainer(getter_AddRefs(node
));
1106 nsCOMPtr
<nsIContent
> content(do_QueryInterface(node
));
1110 nsIFrame
*frame
= aPresShell
->GetPrimaryFrameFor(content
);
1112 return PR_FALSE
; // No frame! Not visible then.
1114 if (!frame
->GetStyleVisibility()->IsVisible())
1117 // Detect if we are _inside_ a text control, or something else with its own
1118 // selection controller.
1119 if (aUsesIndependentSelection
) {
1120 *aUsesIndependentSelection
=
1121 (frame
->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION
);
1124 // ---- We have a frame ----
1125 if (!aMustBeInViewPort
)
1126 return PR_TRUE
; // Don't need it to be on screen, just in rendering tree
1128 // Get the next in flow frame that contains the range start
1129 PRInt32 startRangeOffset
, startFrameOffset
, endFrameOffset
;
1130 aRange
->GetStartOffset(&startRangeOffset
);
1132 frame
->GetOffsets(startFrameOffset
, endFrameOffset
);
1133 if (startRangeOffset
< endFrameOffset
)
1136 nsIFrame
*nextContinuationFrame
= frame
->GetNextContinuation();
1137 if (nextContinuationFrame
)
1138 frame
= nextContinuationFrame
;
1143 // Set up the variables we need, return true if we can't get at them all
1144 const PRUint16 kMinPixels
= 12;
1145 PRUint16 minPixels
= nsPresContext::CSSPixelsToAppUnits(kMinPixels
);
1147 nsIViewManager
* viewManager
= aPresShell
->GetViewManager();
1151 // Get the bounds of the current frame, relative to the current view.
1152 // We don't use the more accurate AccGetBounds, because that is
1153 // more expensive and the STATE_OFFSCREEN flag that this is used
1154 // for only needs to be a rough indicator
1155 nsIView
*containingView
= nsnull
;
1156 nsPoint frameOffset
;
1157 nsRectVisibility rectVisibility
= nsRectVisibility_kAboveViewport
;
1159 if (!aGetTopVisibleLeaf
) {
1160 nsRect relFrameRect
= frame
->GetRect();
1161 frame
->GetOffsetFromView(frameOffset
, &containingView
);
1162 if (!containingView
)
1163 return PR_FALSE
; // no view -- not visible
1165 relFrameRect
.x
= frameOffset
.x
;
1166 relFrameRect
.y
= frameOffset
.y
;
1168 viewManager
->GetRectVisibility(containingView
, relFrameRect
,
1169 minPixels
, &rectVisibility
);
1171 if (rectVisibility
!= nsRectVisibility_kAboveViewport
&&
1172 rectVisibility
!= nsRectVisibility_kZeroAreaRect
) {
1177 // We know that the target range isn't usable because it's not in the
1178 // view port. Move range forward to first visible point,
1179 // this speeds us up a lot in long documents
1180 nsCOMPtr
<nsIBidirectionalEnumerator
> frameTraversal
;
1181 nsCOMPtr
<nsIFrameTraversal
> trav(do_CreateInstance(kFrameTraversalCID
));
1183 trav
->NewFrameTraversal(getter_AddRefs(frameTraversal
),
1184 aPresContext
, frame
,
1186 PR_FALSE
, // aVisual
1187 PR_FALSE
, // aLockInScrollView
1188 PR_FALSE
// aFollowOOFs
1191 if (!frameTraversal
)
1194 while (rectVisibility
== nsRectVisibility_kAboveViewport
|| rectVisibility
== nsRectVisibility_kZeroAreaRect
) {
1195 frameTraversal
->Next();
1196 nsISupports
* currentItem
;
1197 frameTraversal
->CurrentItem(¤tItem
);
1198 frame
= static_cast<nsIFrame
*>(currentItem
);
1202 nsRect relFrameRect
= frame
->GetRect();
1203 frame
->GetOffsetFromView(frameOffset
, &containingView
);
1204 if (containingView
) {
1205 relFrameRect
.x
= frameOffset
.x
;
1206 relFrameRect
.y
= frameOffset
.y
;
1207 viewManager
->GetRectVisibility(containingView
, relFrameRect
,
1208 minPixels
, &rectVisibility
);
1213 nsCOMPtr
<nsIDOMNode
> firstVisibleNode
= do_QueryInterface(frame
->GetContent());
1215 if (firstVisibleNode
) {
1216 (*aFirstVisibleRange
)->SelectNode(firstVisibleNode
);
1217 frame
->GetOffsets(startFrameOffset
, endFrameOffset
);
1218 (*aFirstVisibleRange
)->SetStart(firstVisibleNode
, startFrameOffset
);
1219 (*aFirstVisibleRange
)->Collapse(PR_TRUE
); // Collapse to start
1226 already_AddRefed
<nsIPresShell
>
1227 nsTypeAheadFind::GetPresShell()
1232 nsIPresShell
*shell
= nsnull
;
1233 CallQueryReferent(mPresShell
.get(), &shell
);
1235 nsPresContext
*pc
= shell
->GetPresContext();
1236 if (!pc
|| !nsCOMPtr
<nsISupports
>(pc
->GetContainer())) {