Bug 451040 ? Passwords Manager Empty after convert to MozStorage. r=gavin
[wine-gecko.git] / toolkit / components / typeaheadfind / src / nsTypeAheadFind.cpp
blobaa49d5d1f5db7b1f771953e93db2186319c3f00e
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
13 * License.
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.
22 * Contributor(s):
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 ***** */
42 #include "nsCOMPtr.h"
43 #include "nsMemory.h"
44 #include "nsIServiceManager.h"
45 #include "nsIGenericFactory.h"
46 #include "nsIWebBrowserChrome.h"
47 #include "nsCURILoader.h"
48 #include "nsNetUtil.h"
49 #include "nsIURL.h"
50 #include "nsIURI.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"
60 #include "nsString.h"
61 #include "nsCRT.h"
63 #include "nsIDOMNode.h"
64 #include "nsIContent.h"
65 #include "nsIFrame.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"
79 #include "nsILink.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)
104 NS_INTERFACE_MAP_END
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));
124 if (prefInternal) {
125 prefInternal->RemoveObserver("accessibility.typeaheadfind", this);
126 prefInternal->RemoveObserver("accessibility.browsewithcaret", this);
130 nsresult
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 ----------
148 PrefsReset();
150 // ----------- Set search options ---------------
151 mFind->SetCaseSensitive(PR_FALSE);
152 mFind->SetWordBreaker(nsnull);
154 return rv;
157 nsresult
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",
164 &mLinksOnlyPref);
166 prefBranch->GetBoolPref("accessibility.typeaheadfind.startlinksonly",
167 &mStartLinksOnlyPref);
169 PRBool isSoundEnabled = PR_TRUE;
170 prefBranch->GetBoolPref("accessibility.typeaheadfind.enablesound",
171 &isSoundEnabled);
172 nsXPIDLCString soundStr;
173 if (isSoundEnabled)
174 prefBranch->GetCharPref("accessibility.typeaheadfind.soundURL", getter_Copies(soundStr));
176 mNotFoundSoundURL = soundStr;
178 prefBranch->GetBoolPref("accessibility.browsewithcaret",
179 &mCaretBrowsingOn);
181 return NS_OK;
184 NS_IMETHODIMP
185 nsTypeAheadFind::SetCaseSensitive(PRBool isCaseSensitive)
187 mFind->SetCaseSensitive(isCaseSensitive);
188 return NS_OK;
191 NS_IMETHODIMP
192 nsTypeAheadFind::GetCaseSensitive(PRBool* isCaseSensitive)
194 mFind->GetCaseSensitive(isCaseSensitive);
195 return NS_OK;
198 NS_IMETHODIMP
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);
214 mFoundLink = nsnull;
215 mFoundEditable = nsnull;
216 mCurrentWindow = nsnull;
218 mSelectionController = nsnull;
220 return NS_OK;
223 NS_IMETHODIMP
224 nsTypeAheadFind::SetSelectionModeAndRepaint(PRInt16 aToggle)
226 nsCOMPtr<nsISelectionController> selectionController =
227 do_QueryReferent(mSelectionController);
228 if (!selectionController) {
229 return NS_OK;
232 selectionController->SetDisplaySelection(aToggle);
233 selectionController->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
235 return NS_OK;
238 NS_IMETHODIMP
239 nsTypeAheadFind::CollapseSelection()
241 nsCOMPtr<nsISelectionController> selectionController =
242 do_QueryReferent(mSelectionController);
243 if (!selectionController) {
244 return NS_OK;
247 nsCOMPtr<nsISelection> selection;
248 selectionController->GetSelection(nsISelectionController::SELECTION_NORMAL,
249 getter_AddRefs(selection));
250 if (selection)
251 selection->CollapseToStart();
253 return NS_OK;
256 NS_IMETHODIMP
257 nsTypeAheadFind::Observe(nsISupports *aSubject, const char *aTopic,
258 const PRUnichar *aData)
260 if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID))
261 return PrefsReset();
263 return NS_OK;
266 void
267 nsTypeAheadFind::SaveFind()
269 if (mWebBrowserFind)
270 mWebBrowserFind->SetSearchString(mTypeAheadBuffer.get());
272 // save the length of this find for "not found" sound
273 mLastFindLength = mTypeAheadBuffer.Length();
276 void
277 nsTypeAheadFind::PlayNotFoundSound()
279 if (mNotFoundSoundURL.IsEmpty()) // no sound
280 return;
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();
290 return;
293 nsCOMPtr<nsIURI> soundURI;
294 if (mNotFoundSoundURL.Equals("default"))
295 NS_NewURI(getter_AddRefs(soundURI), NS_LITERAL_CSTRING(TYPEAHEADFIND_NOTFOUND_WAV_URL));
296 else
297 NS_NewURI(getter_AddRefs(soundURI), mNotFoundSoundURL);
299 nsCOMPtr<nsIURL> soundURL(do_QueryInterface(soundURI));
300 if (soundURL)
301 mSoundInterface->Play(soundURL);
305 nsresult
306 nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, PRBool aIsLinksOnly,
307 PRBool aIsFirstVisiblePreferred, PRBool aFindPrev,
308 PRUint16* aResult)
310 *aResult = FIND_NOTFOUND;
311 mFoundLink = nsnull;
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);
325 if (!presShell) {
326 presShell = startingPresShell; // this is the current document
328 if (!presShell)
329 return NS_ERROR_FAILURE;
332 nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
334 if (!presContext)
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);
344 } else {
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]");
352 if (!treeItem)
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
376 // dealing with
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)
383 break;
386 // ------------ Get ranges ready ----------------
387 nsCOMPtr<nsIDOMRange> returnRange;
388 nsCOMPtr<nsIPresShell> focusedPS;
389 if (NS_FAILED(GetSearchContainers(currentContainer,
390 (!aIsFirstVisiblePreferred ||
391 mStartFindRange) ?
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));
414 if (!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;
420 if (aIsLinksOnly) {
421 // Don't check if inside link when searching all text
422 RangeStartsInsideLink(returnRange, presShell, &isInsideLink,
423 &isStartingLink);
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));
437 // Collapse to end
438 mStartPointRange->Collapse(aFindPrev);
440 continue;
443 // ------ Success! -------
444 // Hide old selection (new one may be on a different controller)
445 if (selection) {
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!");
459 if (!document)
460 return NS_ERROR_UNEXPECTED;
462 nsCOMPtr<nsPIDOMWindow> window = document->GetWindow();
463 NS_ASSERTION(window, "document has no window");
464 if (!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));
496 while (node) {
497 nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(node);
498 if (editable) {
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!");
504 if (!editor) {
505 break;
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)
517 break;
519 // Otherwise move focus/caret to editable element
520 nsCOMPtr<nsIContent> content = do_QueryInterface(mFoundEditable);
521 if (content) {
522 content->SetFocus(presContext);
523 presContext->EventStateManager()->MoveCaretToFocus();
525 break;
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
536 // and search again.
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
550 if (selection) {
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;
587 return NS_OK;
590 // ======= end-inner-while (go through a single document) ==========
592 // ---------- Nothing found yet, try next document -------------
593 PRBool hasTriedFirstDoc = PR_FALSE;
594 do {
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);
602 if (currentDocShell)
603 break;
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
622 // for any match
623 aIsFirstVisiblePreferred = PR_FALSE;
624 hasWrapped = PR_TRUE;
625 continueLoop = PR_TRUE; // Go through all docs again
628 if (continueLoop) {
629 if (NS_FAILED(GetSearchContainers(currentContainer, nsnull,
630 aIsFirstVisiblePreferred, aFindPrev,
631 getter_AddRefs(presShell),
632 getter_AddRefs(presContext)))) {
633 continue;
636 if (aFindPrev) {
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;
645 continue;
648 // ------------- Failed --------------
649 break;
650 } // end-outer-while: go through all docs
652 return NS_ERROR_FAILURE;
655 NS_IMETHODIMP
656 nsTypeAheadFind::GetSearchString(nsAString& aSearchString)
658 aSearchString = mTypeAheadBuffer;
659 return NS_OK;
662 NS_IMETHODIMP
663 nsTypeAheadFind::GetFoundLink(nsIDOMElement** aFoundLink)
665 NS_ENSURE_ARG_POINTER(aFoundLink);
666 *aFoundLink = mFoundLink;
667 NS_IF_ADDREF(*aFoundLink);
668 return NS_OK;
671 NS_IMETHODIMP
672 nsTypeAheadFind::GetFoundEditable(nsIDOMElement** aFoundEditable)
674 NS_ENSURE_ARG_POINTER(aFoundEditable);
675 *aFoundEditable = mFoundEditable;
676 NS_IF_ADDREF(*aFoundEditable);
677 return NS_OK;
680 NS_IMETHODIMP
681 nsTypeAheadFind::GetCurrentWindow(nsIDOMWindow** aCurrentWindow)
683 NS_ENSURE_ARG_POINTER(aCurrentWindow);
684 *aCurrentWindow = mCurrentWindow;
685 NS_IF_ADDREF(*aCurrentWindow);
686 return NS_OK;
689 nsresult
690 nsTypeAheadFind::GetSearchContainers(nsISupports *aContainer,
691 nsISelectionController *aSelectionController,
692 PRBool aIsFirstVisiblePreferred,
693 PRBool aFindPrev,
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));
705 if (!docShell)
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();
719 if (!doc)
720 return NS_ERROR_FAILURE;
722 nsCOMPtr<nsIContent> rootContent;
723 nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(doc));
724 if (htmlDoc) {
725 nsCOMPtr<nsIDOMHTMLElement> bodyEl;
726 htmlDoc->GetBody(getter_AddRefs(bodyEl));
727 rootContent = do_QueryInterface(bodyEl);
730 if (!rootContent)
731 rootContent = doc->GetRootContent();
733 nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootContent));
735 if (!rootNode)
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));
753 if (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);
765 else {
766 PRInt32 startOffset;
767 nsCOMPtr<nsIDOMNode> startNode;
768 if (aFindPrev) {
769 currentSelectionRange->GetStartContainer(getter_AddRefs(startNode));
770 currentSelectionRange->GetStartOffset(&startOffset);
771 } else {
772 currentSelectionRange->GetEndContainer(getter_AddRefs(startNode));
773 currentSelectionRange->GetEndOffset(&startOffset);
775 if (!startNode)
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);
791 return NS_OK;
794 void
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));
807 PRInt32 startOffset;
808 aRange->GetStartOffset(&startOffset);
810 startContent = do_QueryInterface(startNode);
811 if (!startContent) {
812 NS_NOTREACHED("startContent should never be null");
813 return;
815 origContent = startContent;
817 if (startContent->IsNodeOfType(nsINode::eELEMENT)) {
818 nsIContent *childContent = startContent->GetChildAt(startOffset);
819 if (childContent) {
820 startContent = childContent;
823 else if (startOffset > 0) {
824 const nsTextFragment *textFrag = startContent->GetText();
825 if (textFrag) {
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
831 break;
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"));
845 while (PR_TRUE) {
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));
851 if (link) {
852 // Check to see if inside HTML link
853 *aIsInsideLink = startContent->HasAttr(kNameSpaceID_None, hrefAtom);
854 return;
857 else {
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"),
863 eCaseMatters)) {
864 *aIsInsideLink = PR_FALSE; // Xlink must be type="simple"
867 return;
871 // Get the parent
872 nsCOMPtr<nsIContent> parent = startContent->GetParent();
873 if (!parent)
874 break;
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. */
896 NS_IMETHODIMP
897 nsTypeAheadFind::FindAgain(PRBool aFindBackwards, PRBool aLinksOnly,
898 PRUint16* aResult)
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);
909 return NS_OK;
912 NS_IMETHODIMP
913 nsTypeAheadFind::Find(const nsAString& aSearchString, PRBool aLinksOnly,
914 PRUint16* aResult)
916 *aResult = FIND_NOTFOUND;
918 nsCOMPtr<nsIPresShell> presShell (GetPresShell());
919 if (!presShell) {
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);
933 } else {
934 selectionController->GetSelection(
935 nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
938 if (selection)
939 selection->CollapseToStart();
941 if (aSearchString.IsEmpty()) {
942 mTypeAheadBuffer.Truncate();
944 // These will be initialized to their true values after the first character
945 // is typed
946 mStartFindRange = nsnull;
947 mSelectionController = nsnull;
949 *aResult = FIND_FOUND;
950 return NS_OK;
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))
958 atEnd = PR_TRUE;
960 const nsAString& newStr2 = Substring(aSearchString, 0, aSearchString.Length());
961 const nsAString& oldStr2 = Substring(mTypeAheadBuffer, 0, aSearchString.Length());
962 if (oldStr2.Equals(newStr2))
963 atEnd = PR_TRUE;
965 if (!atEnd)
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;
982 #ifdef XP_WIN
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;
987 #endif
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
999 if (!mLinksOnly)
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;
1006 if (selection)
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,
1033 PR_FALSE, aResult);
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;
1042 if (selection) {
1043 nsCOMPtr<nsIDOMRange> startFindRange;
1044 selection->GetRangeAt(0, getter_AddRefs(startFindRange));
1045 if (startFindRange)
1046 startFindRange->CloneRange(getter_AddRefs(mStartFindRange));
1050 else {
1051 // Error sound
1052 if (mTypeAheadBuffer.Length() > mLastFindLength)
1053 PlayNotFoundSound();
1056 SaveFind();
1057 return NS_OK;
1060 void
1061 nsTypeAheadFind::GetSelection(nsIPresShell *aPresShell,
1062 nsISelectionController **aSelCon,
1063 nsISelection **aDOMSel)
1065 if (!aPresShell)
1066 return;
1068 // if aCurrentNode is nsnull, get selection for document
1069 *aDOMSel = nsnull;
1071 nsPresContext* presContext = aPresShell->GetPresContext();
1073 nsIFrame *frame = aPresShell->GetRootFrame();
1075 if (presContext && frame) {
1076 frame->GetSelectionController(presContext, aSelCon);
1077 if (*aSelCon) {
1078 (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL,
1079 aDOMSel);
1085 PRBool
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));
1107 if (!content)
1108 return PR_FALSE;
1110 nsIFrame *frame = aPresShell->GetPrimaryFrameFor(content);
1111 if (!frame)
1112 return PR_FALSE; // No frame! Not visible then.
1114 if (!frame->GetStyleVisibility()->IsVisible())
1115 return PR_FALSE;
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);
1131 while (PR_TRUE) {
1132 frame->GetOffsets(startFrameOffset, endFrameOffset);
1133 if (startRangeOffset < endFrameOffset)
1134 break;
1136 nsIFrame *nextContinuationFrame = frame->GetNextContinuation();
1137 if (nextContinuationFrame)
1138 frame = nextContinuationFrame;
1139 else
1140 break;
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();
1148 if (!viewManager)
1149 return PR_TRUE;
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) {
1173 return PR_TRUE;
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));
1182 if (trav)
1183 trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
1184 aPresContext, frame,
1185 eLeaf,
1186 PR_FALSE, // aVisual
1187 PR_FALSE, // aLockInScrollView
1188 PR_FALSE // aFollowOOFs
1191 if (!frameTraversal)
1192 return PR_FALSE;
1194 while (rectVisibility == nsRectVisibility_kAboveViewport || rectVisibility == nsRectVisibility_kZeroAreaRect) {
1195 frameTraversal->Next();
1196 nsISupports* currentItem;
1197 frameTraversal->CurrentItem(&currentItem);
1198 frame = static_cast<nsIFrame*>(currentItem);
1199 if (!frame)
1200 return PR_FALSE;
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);
1212 if (frame) {
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
1223 return PR_FALSE;
1226 already_AddRefed<nsIPresShell>
1227 nsTypeAheadFind::GetPresShell()
1229 if (!mPresShell)
1230 return nsnull;
1232 nsIPresShell *shell = nsnull;
1233 CallQueryReferent(mPresShell.get(), &shell);
1234 if (shell) {
1235 nsPresContext *pc = shell->GetPresContext();
1236 if (!pc || !nsCOMPtr<nsISupports>(pc->GetContainer())) {
1237 NS_RELEASE(shell);
1241 return shell;