toolkit/components/typeaheadfind/nsTypeAheadFind.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:141e2d56101f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "nsCOMPtr.h"
7 #include "nsMemory.h"
8 #include "nsIServiceManager.h"
9 #include "mozilla/ModuleUtils.h"
10 #include "nsIWebBrowserChrome.h"
11 #include "nsCURILoader.h"
12 #include "nsCycleCollectionParticipant.h"
13 #include "nsNetUtil.h"
14 #include "nsIURL.h"
15 #include "nsIURI.h"
16 #include "nsIDocShell.h"
17 #include "nsIDocShellTreeOwner.h"
18 #include "nsISimpleEnumerator.h"
19 #include "nsPIDOMWindow.h"
20 #include "nsIPrefBranch.h"
21 #include "nsIPrefService.h"
22 #include "nsString.h"
23 #include "nsCRT.h"
24
25 #include "nsIDOMNode.h"
26 #include "mozilla/dom/Element.h"
27 #include "nsIFrame.h"
28 #include "nsFrameTraversal.h"
29 #include "nsIImageDocument.h"
30 #include "nsIDOMHTMLDocument.h"
31 #include "nsIDOMHTMLElement.h"
32 #include "nsIDocument.h"
33 #include "nsISelection.h"
34 #include "nsTextFragment.h"
35 #include "nsIDOMNSEditableElement.h"
36 #include "nsIEditor.h"
37
38 #include "nsIDocShellTreeItem.h"
39 #include "nsIWebNavigation.h"
40 #include "nsIInterfaceRequestor.h"
41 #include "nsIInterfaceRequestorUtils.h"
42 #include "nsContentCID.h"
43 #include "nsLayoutCID.h"
44 #include "nsWidgetsCID.h"
45 #include "nsIFormControl.h"
46 #include "nsNameSpaceManager.h"
47 #include "nsIWindowWatcher.h"
48 #include "nsIObserverService.h"
49 #include "nsFocusManager.h"
50 #include "mozilla/dom/Element.h"
51 #include "mozilla/dom/Link.h"
52 #include "nsRange.h"
53
54 #include "nsTypeAheadFind.h"
55
56 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTypeAheadFind)
57 NS_INTERFACE_MAP_ENTRY(nsITypeAheadFind)
58 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITypeAheadFind)
59 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
60 NS_INTERFACE_MAP_ENTRY(nsIObserver)
61 NS_INTERFACE_MAP_END
62
63 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypeAheadFind)
64 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypeAheadFind)
65
66 NS_IMPL_CYCLE_COLLECTION(nsTypeAheadFind, mFoundLink, mFoundEditable,
67 mCurrentWindow, mStartFindRange, mSearchRange,
68 mStartPointRange, mEndPointRange, mSoundInterface,
69 mFind)
70
71 static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
72
73 #define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1"
74
75 nsTypeAheadFind::nsTypeAheadFind():
76 mStartLinksOnlyPref(false),
77 mCaretBrowsingOn(false),
78 mLastFindLength(0),
79 mIsSoundInitialized(false),
80 mCaseSensitive(false)
81 {
82 }
83
84 nsTypeAheadFind::~nsTypeAheadFind()
85 {
86 nsCOMPtr<nsIPrefBranch> prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID));
87 if (prefInternal) {
88 prefInternal->RemoveObserver("accessibility.typeaheadfind", this);
89 prefInternal->RemoveObserver("accessibility.browsewithcaret", this);
90 }
91 }
92
93 nsresult
94 nsTypeAheadFind::Init(nsIDocShell* aDocShell)
95 {
96 nsCOMPtr<nsIPrefBranch> prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID));
97
98 mSearchRange = nullptr;
99 mStartPointRange = nullptr;
100 mEndPointRange = nullptr;
101 if (!prefInternal || !EnsureFind())
102 return NS_ERROR_FAILURE;
103
104 SetDocShell(aDocShell);
105
106 // ----------- Listen to prefs ------------------
107 nsresult rv = prefInternal->AddObserver("accessibility.browsewithcaret", this, true);
108 NS_ENSURE_SUCCESS(rv, rv);
109
110 // ----------- Get initial preferences ----------
111 PrefsReset();
112
113 return rv;
114 }
115
116 nsresult
117 nsTypeAheadFind::PrefsReset()
118 {
119 nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
120 NS_ENSURE_TRUE(prefBranch, NS_ERROR_FAILURE);
121
122 prefBranch->GetBoolPref("accessibility.typeaheadfind.startlinksonly",
123 &mStartLinksOnlyPref);
124
125 bool isSoundEnabled = true;
126 prefBranch->GetBoolPref("accessibility.typeaheadfind.enablesound",
127 &isSoundEnabled);
128 nsXPIDLCString soundStr;
129 if (isSoundEnabled)
130 prefBranch->GetCharPref("accessibility.typeaheadfind.soundURL", getter_Copies(soundStr));
131
132 mNotFoundSoundURL = soundStr;
133
134 prefBranch->GetBoolPref("accessibility.browsewithcaret",
135 &mCaretBrowsingOn);
136
137 return NS_OK;
138 }
139
140 NS_IMETHODIMP
141 nsTypeAheadFind::SetCaseSensitive(bool isCaseSensitive)
142 {
143 mCaseSensitive = isCaseSensitive;
144
145 if (mFind) {
146 mFind->SetCaseSensitive(mCaseSensitive);
147 }
148
149 return NS_OK;
150 }
151
152 NS_IMETHODIMP
153 nsTypeAheadFind::GetCaseSensitive(bool* isCaseSensitive)
154 {
155 *isCaseSensitive = mCaseSensitive;
156
157 return NS_OK;
158 }
159
160 NS_IMETHODIMP
161 nsTypeAheadFind::SetDocShell(nsIDocShell* aDocShell)
162 {
163 mDocShell = do_GetWeakReference(aDocShell);
164
165 mWebBrowserFind = do_GetInterface(aDocShell);
166 NS_ENSURE_TRUE(mWebBrowserFind, NS_ERROR_FAILURE);
167
168 nsCOMPtr<nsIPresShell> presShell;
169 presShell = aDocShell->GetPresShell();
170 mPresShell = do_GetWeakReference(presShell);
171
172 mStartFindRange = nullptr;
173 mStartPointRange = nullptr;
174 mSearchRange = nullptr;
175 mEndPointRange = nullptr;
176
177 mFoundLink = nullptr;
178 mFoundEditable = nullptr;
179 mCurrentWindow = nullptr;
180
181 mSelectionController = nullptr;
182
183 mFind = nullptr;
184
185 return NS_OK;
186 }
187
188 NS_IMETHODIMP
189 nsTypeAheadFind::SetSelectionModeAndRepaint(int16_t aToggle)
190 {
191 nsCOMPtr<nsISelectionController> selectionController =
192 do_QueryReferent(mSelectionController);
193 if (!selectionController) {
194 return NS_OK;
195 }
196
197 selectionController->SetDisplaySelection(aToggle);
198 selectionController->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
199
200 return NS_OK;
201 }
202
203 NS_IMETHODIMP
204 nsTypeAheadFind::CollapseSelection()
205 {
206 nsCOMPtr<nsISelectionController> selectionController =
207 do_QueryReferent(mSelectionController);
208 if (!selectionController) {
209 return NS_OK;
210 }
211
212 nsCOMPtr<nsISelection> selection;
213 selectionController->GetSelection(nsISelectionController::SELECTION_NORMAL,
214 getter_AddRefs(selection));
215 if (selection)
216 selection->CollapseToStart();
217
218 return NS_OK;
219 }
220
221 NS_IMETHODIMP
222 nsTypeAheadFind::Observe(nsISupports *aSubject, const char *aTopic,
223 const char16_t *aData)
224 {
225 if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID))
226 return PrefsReset();
227
228 return NS_OK;
229 }
230
231 void
232 nsTypeAheadFind::SaveFind()
233 {
234 if (mWebBrowserFind)
235 mWebBrowserFind->SetSearchString(mTypeAheadBuffer.get());
236
237 // save the length of this find for "not found" sound
238 mLastFindLength = mTypeAheadBuffer.Length();
239 }
240
241 void
242 nsTypeAheadFind::PlayNotFoundSound()
243 {
244 if (mNotFoundSoundURL.IsEmpty()) // no sound
245 return;
246
247 if (!mSoundInterface)
248 mSoundInterface = do_CreateInstance("@mozilla.org/sound;1");
249
250 if (mSoundInterface) {
251 mIsSoundInitialized = true;
252
253 if (mNotFoundSoundURL.Equals("beep")) {
254 mSoundInterface->Beep();
255 return;
256 }
257
258 nsCOMPtr<nsIURI> soundURI;
259 if (mNotFoundSoundURL.Equals("default"))
260 NS_NewURI(getter_AddRefs(soundURI), NS_LITERAL_CSTRING(TYPEAHEADFIND_NOTFOUND_WAV_URL));
261 else
262 NS_NewURI(getter_AddRefs(soundURI), mNotFoundSoundURL);
263
264 nsCOMPtr<nsIURL> soundURL(do_QueryInterface(soundURI));
265 if (soundURL)
266 mSoundInterface->Play(soundURL);
267 }
268 }
269
270 nsresult
271 nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly,
272 bool aIsFirstVisiblePreferred, bool aFindPrev,
273 uint16_t* aResult)
274 {
275 *aResult = FIND_NOTFOUND;
276 mFoundLink = nullptr;
277 mFoundEditable = nullptr;
278 mCurrentWindow = nullptr;
279 nsCOMPtr<nsIPresShell> startingPresShell (GetPresShell());
280 if (!startingPresShell) {
281 nsCOMPtr<nsIDocShell> ds = do_QueryReferent(mDocShell);
282 NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
283
284 startingPresShell = ds->GetPresShell();
285 mPresShell = do_GetWeakReference(startingPresShell);
286 }
287
288 nsCOMPtr<nsIPresShell> presShell(aPresShell);
289
290 if (!presShell) {
291 presShell = startingPresShell; // this is the current document
292
293 if (!presShell)
294 return NS_ERROR_FAILURE;
295 }
296
297 nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
298
299 if (!presContext)
300 return NS_ERROR_FAILURE;
301
302 nsCOMPtr<nsISelection> selection;
303 nsCOMPtr<nsISelectionController> selectionController =
304 do_QueryReferent(mSelectionController);
305 if (!selectionController) {
306 GetSelection(presShell, getter_AddRefs(selectionController),
307 getter_AddRefs(selection)); // cache for reuse
308 mSelectionController = do_GetWeakReference(selectionController);
309 } else {
310 selectionController->GetSelection(
311 nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
312 }
313
314 nsCOMPtr<nsIDocShell> startingDocShell(presContext->GetDocShell());
315 NS_ASSERTION(startingDocShell, "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]");
316 if (!startingDocShell)
317 return NS_ERROR_FAILURE;
318
319 nsCOMPtr<nsIDocShellTreeItem> rootContentTreeItem;
320 nsCOMPtr<nsIDocShell> currentDocShell;
321
322 startingDocShell->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem));
323 nsCOMPtr<nsIDocShell> rootContentDocShell =
324 do_QueryInterface(rootContentTreeItem);
325
326 if (!rootContentDocShell)
327 return NS_ERROR_FAILURE;
328
329 nsCOMPtr<nsISimpleEnumerator> docShellEnumerator;
330 rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent,
331 nsIDocShell::ENUMERATE_FORWARDS,
332 getter_AddRefs(docShellEnumerator));
333
334 // Default: can start at the current document
335 nsCOMPtr<nsISupports> currentContainer =
336 do_QueryInterface(rootContentDocShell);
337
338 // Iterate up to current shell, if there's more than 1 that we're
339 // dealing with
340 bool hasMoreDocShells;
341
342 while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) && hasMoreDocShells) {
343 docShellEnumerator->GetNext(getter_AddRefs(currentContainer));
344 currentDocShell = do_QueryInterface(currentContainer);
345 if (!currentDocShell || currentDocShell == startingDocShell || aIsFirstVisiblePreferred)
346 break;
347 }
348
349 // ------------ Get ranges ready ----------------
350 nsCOMPtr<nsIDOMRange> returnRange;
351 nsCOMPtr<nsIPresShell> focusedPS;
352 if (NS_FAILED(GetSearchContainers(currentContainer,
353 (!aIsFirstVisiblePreferred ||
354 mStartFindRange) ?
355 selectionController.get() : nullptr,
356 aIsFirstVisiblePreferred, aFindPrev,
357 getter_AddRefs(presShell),
358 getter_AddRefs(presContext)))) {
359 return NS_ERROR_FAILURE;
360 }
361
362 int16_t rangeCompareResult = 0;
363 if (!mStartPointRange) {
364 mStartPointRange = new nsRange(presShell->GetDocument());
365 }
366
367 mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, mSearchRange, &rangeCompareResult);
368 // No need to wrap find in doc if starting at beginning
369 bool hasWrapped = (rangeCompareResult < 0);
370
371 if (mTypeAheadBuffer.IsEmpty() || !EnsureFind())
372 return NS_ERROR_FAILURE;
373
374 mFind->SetFindBackwards(aFindPrev);
375
376 while (true) { // ----- Outer while loop: go through all docs -----
377 while (true) { // === Inner while loop: go through a single doc ===
378 mFind->Find(mTypeAheadBuffer.get(), mSearchRange, mStartPointRange,
379 mEndPointRange, getter_AddRefs(returnRange));
380
381 if (!returnRange)
382 break; // Nothing found in this doc, go to outer loop (try next doc)
383
384 // ------- Test resulting found range for success conditions ------
385 bool isInsideLink = false, isStartingLink = false;
386
387 if (aIsLinksOnly) {
388 // Don't check if inside link when searching all text
389 RangeStartsInsideLink(returnRange, presShell, &isInsideLink,
390 &isStartingLink);
391 }
392
393 bool usesIndependentSelection;
394 if (!IsRangeVisible(presShell, presContext, returnRange,
395 aIsFirstVisiblePreferred, false,
396 getter_AddRefs(mStartPointRange),
397 &usesIndependentSelection) ||
398 (aIsLinksOnly && !isInsideLink) ||
399 (mStartLinksOnlyPref && aIsLinksOnly && !isStartingLink)) {
400 // ------ Failure ------
401 // At this point mStartPointRange got updated to the first
402 // visible range in the viewport. We _may_ be able to just
403 // start there, if it's not taking us in the wrong direction.
404 if (aFindPrev) {
405 // We can continue at the end of mStartPointRange if its end is before
406 // the start of returnRange or coincides with it. Otherwise, we need
407 // to continue at the start of returnRange.
408 int16_t compareResult;
409 nsresult rv =
410 mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_END,
411 returnRange, &compareResult);
412 if (NS_SUCCEEDED(rv) && compareResult <= 0) {
413 // OK to start at the end of mStartPointRange
414 mStartPointRange->Collapse(false);
415 } else {
416 // Start at the beginning of returnRange
417 returnRange->CloneRange(getter_AddRefs(mStartPointRange));
418 mStartPointRange->Collapse(true);
419 }
420 } else {
421 // We can continue at the start of mStartPointRange if its start is
422 // after the end of returnRange or coincides with it. Otherwise, we
423 // need to continue at the end of returnRange.
424 int16_t compareResult;
425 nsresult rv =
426 mStartPointRange->CompareBoundaryPoints(nsIDOMRange::END_TO_START,
427 returnRange, &compareResult);
428 if (NS_SUCCEEDED(rv) && compareResult >= 0) {
429 // OK to start at the start of mStartPointRange
430 mStartPointRange->Collapse(true);
431 } else {
432 // Start at the end of returnRange
433 returnRange->CloneRange(getter_AddRefs(mStartPointRange));
434 mStartPointRange->Collapse(false);
435 }
436 }
437 continue;
438 }
439
440 // ------ Success! -------
441 // Hide old selection (new one may be on a different controller)
442 if (selection) {
443 selection->CollapseToStart();
444 SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON);
445 }
446
447 // Make sure new document is selected
448 if (presShell != startingPresShell) {
449 // We are in a new document (because of frames/iframes)
450 mPresShell = do_GetWeakReference(presShell);
451 }
452
453 nsCOMPtr<nsIDocument> document =
454 do_QueryInterface(presShell->GetDocument());
455 NS_ASSERTION(document, "Wow, presShell doesn't have document!");
456 if (!document)
457 return NS_ERROR_UNEXPECTED;
458
459 nsCOMPtr<nsPIDOMWindow> window = document->GetWindow();
460 NS_ASSERTION(window, "document has no window");
461 if (!window)
462 return NS_ERROR_UNEXPECTED;
463
464 nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
465 if (usesIndependentSelection) {
466 /* If a search result is found inside an editable element, we'll focus
467 * the element only if focus is in our content window, i.e.
468 * |if (focusedWindow.top == ourWindow.top)| */
469 bool shouldFocusEditableElement = false;
470 if (fm) {
471 nsCOMPtr<nsIDOMWindow> focusedWindow;
472 nsresult rv = fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
473 if (NS_SUCCEEDED(rv)) {
474 nsCOMPtr<nsPIDOMWindow> fwPI(do_QueryInterface(focusedWindow, &rv));
475 if (NS_SUCCEEDED(rv)) {
476 nsCOMPtr<nsIDocShellTreeItem> fwTreeItem
477 (do_QueryInterface(fwPI->GetDocShell(), &rv));
478 if (NS_SUCCEEDED(rv)) {
479 nsCOMPtr<nsIDocShellTreeItem> fwRootTreeItem;
480 rv = fwTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(fwRootTreeItem));
481 if (NS_SUCCEEDED(rv) && fwRootTreeItem == rootContentTreeItem)
482 shouldFocusEditableElement = true;
483 }
484 }
485 }
486 }
487
488 // We may be inside an editable element, and therefore the selection
489 // may be controlled by a different selection controller. Walk up the
490 // chain of parent nodes to see if we find one.
491 nsCOMPtr<nsIDOMNode> node;
492 returnRange->GetStartContainer(getter_AddRefs(node));
493 while (node) {
494 nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(node);
495 if (editable) {
496 // Inside an editable element. Get the correct selection
497 // controller and selection.
498 nsCOMPtr<nsIEditor> editor;
499 editable->GetEditor(getter_AddRefs(editor));
500 NS_ASSERTION(editor, "Editable element has no editor!");
501 if (!editor) {
502 break;
503 }
504 editor->GetSelectionController(
505 getter_AddRefs(selectionController));
506 if (selectionController) {
507 selectionController->GetSelection(
508 nsISelectionController::SELECTION_NORMAL,
509 getter_AddRefs(selection));
510 }
511 mFoundEditable = do_QueryInterface(node);
512
513 if (!shouldFocusEditableElement)
514 break;
515
516 // Otherwise move focus/caret to editable element
517 if (fm)
518 fm->SetFocus(mFoundEditable, 0);
519 break;
520 }
521 nsIDOMNode* tmp = node;
522 tmp->GetParentNode(getter_AddRefs(node));
523 }
524
525 // If we reach here without setting mFoundEditable, then something
526 // besides editable elements can cause us to have an independent
527 // selection controller. I don't know whether this is possible.
528 // Currently, we simply fall back to grabbing the document's selection
529 // controller in this case. Perhaps we should reject this find match
530 // and search again.
531 NS_ASSERTION(mFoundEditable, "Independent selection controller on "
532 "non-editable element!");
533 }
534
535 if (!mFoundEditable) {
536 // Not using a separate selection controller, so just get the
537 // document's controller and selection.
538 GetSelection(presShell, getter_AddRefs(selectionController),
539 getter_AddRefs(selection));
540 }
541 mSelectionController = do_GetWeakReference(selectionController);
542
543 // Select the found text
544 if (selection) {
545 selection->RemoveAllRanges();
546 selection->AddRange(returnRange);
547 }
548
549 if (!mFoundEditable && fm) {
550 nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(window);
551 fm->MoveFocus(win, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
552 nsIFocusManager::FLAG_NOSCROLL | nsIFocusManager::FLAG_NOSWITCHFRAME,
553 getter_AddRefs(mFoundLink));
554 }
555
556 // Change selection color to ATTENTION and scroll to it. Careful: we
557 // must wait until after we goof with focus above before changing to
558 // ATTENTION, or when we MoveFocus() and the selection is not on a
559 // link, we'll blur, which will lose the ATTENTION.
560 if (selectionController) {
561 // Beware! This may flush notifications via synchronous
562 // ScrollSelectionIntoView.
563 SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ATTENTION);
564 selectionController->ScrollSelectionIntoView(
565 nsISelectionController::SELECTION_NORMAL,
566 nsISelectionController::SELECTION_WHOLE_SELECTION,
567 nsISelectionController::SCROLL_CENTER_VERTICALLY |
568 nsISelectionController::SCROLL_SYNCHRONOUS);
569 }
570
571 mCurrentWindow = window;
572 *aResult = hasWrapped ? FIND_WRAPPED : FIND_FOUND;
573 return NS_OK;
574 }
575
576 // ======= end-inner-while (go through a single document) ==========
577
578 // ---------- Nothing found yet, try next document -------------
579 bool hasTriedFirstDoc = false;
580 do {
581 // ==== Second inner loop - get another while ====
582 if (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells))
583 && hasMoreDocShells) {
584 docShellEnumerator->GetNext(getter_AddRefs(currentContainer));
585 NS_ASSERTION(currentContainer, "HasMoreElements lied to us!");
586 currentDocShell = do_QueryInterface(currentContainer);
587
588 if (currentDocShell)
589 break;
590 }
591 else if (hasTriedFirstDoc) // Avoid potential infinite loop
592 return NS_ERROR_FAILURE; // No content doc shells
593
594 // Reached last doc shell, loop around back to first doc shell
595 rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent,
596 nsIDocShell::ENUMERATE_FORWARDS,
597 getter_AddRefs(docShellEnumerator));
598 hasTriedFirstDoc = true;
599 } while (docShellEnumerator); // ==== end second inner while ===
600
601 bool continueLoop = false;
602 if (currentDocShell != startingDocShell)
603 continueLoop = true; // Try next document
604 else if (!hasWrapped || aIsFirstVisiblePreferred) {
605 // Finished searching through docshells:
606 // If aFirstVisiblePreferred == true, we may need to go through all
607 // docshells twice -once to look for visible matches, the second time
608 // for any match
609 aIsFirstVisiblePreferred = false;
610 hasWrapped = true;
611 continueLoop = true; // Go through all docs again
612 }
613
614 if (continueLoop) {
615 if (NS_FAILED(GetSearchContainers(currentContainer, nullptr,
616 aIsFirstVisiblePreferred, aFindPrev,
617 getter_AddRefs(presShell),
618 getter_AddRefs(presContext)))) {
619 continue;
620 }
621
622 if (aFindPrev) {
623 // Reverse mode: swap start and end points, so that we start
624 // at end of document and go to beginning
625 nsCOMPtr<nsIDOMRange> tempRange;
626 mStartPointRange->CloneRange(getter_AddRefs(tempRange));
627 if (!mEndPointRange) {
628 mEndPointRange = new nsRange(presShell->GetDocument());
629 }
630
631 mStartPointRange = mEndPointRange;
632 mEndPointRange = tempRange;
633 }
634
635 continue;
636 }
637
638 // ------------- Failed --------------
639 break;
640 } // end-outer-while: go through all docs
641
642 return NS_ERROR_FAILURE;
643 }
644
645 NS_IMETHODIMP
646 nsTypeAheadFind::GetSearchString(nsAString& aSearchString)
647 {
648 aSearchString = mTypeAheadBuffer;
649 return NS_OK;
650 }
651
652 NS_IMETHODIMP
653 nsTypeAheadFind::GetFoundLink(nsIDOMElement** aFoundLink)
654 {
655 NS_ENSURE_ARG_POINTER(aFoundLink);
656 *aFoundLink = mFoundLink;
657 NS_IF_ADDREF(*aFoundLink);
658 return NS_OK;
659 }
660
661 NS_IMETHODIMP
662 nsTypeAheadFind::GetFoundEditable(nsIDOMElement** aFoundEditable)
663 {
664 NS_ENSURE_ARG_POINTER(aFoundEditable);
665 *aFoundEditable = mFoundEditable;
666 NS_IF_ADDREF(*aFoundEditable);
667 return NS_OK;
668 }
669
670 NS_IMETHODIMP
671 nsTypeAheadFind::GetCurrentWindow(nsIDOMWindow** aCurrentWindow)
672 {
673 NS_ENSURE_ARG_POINTER(aCurrentWindow);
674 *aCurrentWindow = mCurrentWindow;
675 NS_IF_ADDREF(*aCurrentWindow);
676 return NS_OK;
677 }
678
679 nsresult
680 nsTypeAheadFind::GetSearchContainers(nsISupports *aContainer,
681 nsISelectionController *aSelectionController,
682 bool aIsFirstVisiblePreferred,
683 bool aFindPrev,
684 nsIPresShell **aPresShell,
685 nsPresContext **aPresContext)
686 {
687 NS_ENSURE_ARG_POINTER(aContainer);
688 NS_ENSURE_ARG_POINTER(aPresShell);
689 NS_ENSURE_ARG_POINTER(aPresContext);
690
691 *aPresShell = nullptr;
692 *aPresContext = nullptr;
693
694 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
695 if (!docShell)
696 return NS_ERROR_FAILURE;
697
698 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
699
700 nsRefPtr<nsPresContext> presContext;
701 docShell->GetPresContext(getter_AddRefs(presContext));
702
703 if (!presShell || !presContext)
704 return NS_ERROR_FAILURE;
705
706 nsIDocument* doc = presShell->GetDocument();
707
708 if (!doc)
709 return NS_ERROR_FAILURE;
710
711 nsCOMPtr<nsIContent> rootContent;
712 nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(doc));
713 if (htmlDoc) {
714 nsCOMPtr<nsIDOMHTMLElement> bodyEl;
715 htmlDoc->GetBody(getter_AddRefs(bodyEl));
716 rootContent = do_QueryInterface(bodyEl);
717 }
718
719 if (!rootContent)
720 rootContent = doc->GetRootElement();
721
722 nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootContent));
723
724 if (!rootNode)
725 return NS_ERROR_FAILURE;
726
727 uint32_t childCount = rootContent->GetChildCount();
728
729 if (!mSearchRange) {
730 mSearchRange = new nsRange(rootContent);
731 }
732
733 if (!mEndPointRange) {
734 mEndPointRange = new nsRange(rootContent);
735 }
736
737 mSearchRange->SelectNodeContents(rootNode);
738
739 mEndPointRange->SetEnd(rootNode, childCount);
740 mEndPointRange->Collapse(false); // collapse to end
741
742 // Consider current selection as null if
743 // it's not in the currently focused document
744 nsCOMPtr<nsIDOMRange> currentSelectionRange;
745 nsCOMPtr<nsIPresShell> selectionPresShell = GetPresShell();
746 if (aSelectionController && selectionPresShell && selectionPresShell == presShell) {
747 nsCOMPtr<nsISelection> selection;
748 aSelectionController->GetSelection(
749 nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
750 if (selection)
751 selection->GetRangeAt(0, getter_AddRefs(currentSelectionRange));
752 }
753
754 if (!mStartPointRange) {
755 mStartPointRange = new nsRange(doc);
756 }
757
758 if (!currentSelectionRange) {
759 // Ensure visible range, move forward if necessary
760 // This uses ignores the return value, but usese the side effect of
761 // IsRangeVisible. It returns the first visible range after searchRange
762 IsRangeVisible(presShell, presContext, mSearchRange,
763 aIsFirstVisiblePreferred, true,
764 getter_AddRefs(mStartPointRange), nullptr);
765 }
766 else {
767 int32_t startOffset;
768 nsCOMPtr<nsIDOMNode> startNode;
769 if (aFindPrev) {
770 currentSelectionRange->GetStartContainer(getter_AddRefs(startNode));
771 currentSelectionRange->GetStartOffset(&startOffset);
772 } else {
773 currentSelectionRange->GetEndContainer(getter_AddRefs(startNode));
774 currentSelectionRange->GetEndOffset(&startOffset);
775 }
776 if (!startNode)
777 startNode = rootNode;
778
779 // We need to set the start point this way, other methods haven't worked
780 mStartPointRange->SelectNode(startNode);
781 mStartPointRange->SetStart(startNode, startOffset);
782 }
783
784 mStartPointRange->Collapse(true); // collapse to start
785
786 *aPresShell = presShell;
787 NS_ADDREF(*aPresShell);
788
789 *aPresContext = presContext;
790 NS_ADDREF(*aPresContext);
791
792 return NS_OK;
793 }
794
795 void
796 nsTypeAheadFind::RangeStartsInsideLink(nsIDOMRange *aRange,
797 nsIPresShell *aPresShell,
798 bool *aIsInsideLink,
799 bool *aIsStartingLink)
800 {
801 *aIsInsideLink = false;
802 *aIsStartingLink = true;
803
804 // ------- Get nsIContent to test -------
805 nsCOMPtr<nsIDOMNode> startNode;
806 nsCOMPtr<nsIContent> startContent, origContent;
807 aRange->GetStartContainer(getter_AddRefs(startNode));
808 int32_t startOffset;
809 aRange->GetStartOffset(&startOffset);
810
811 startContent = do_QueryInterface(startNode);
812 if (!startContent) {
813 NS_NOTREACHED("startContent should never be null");
814 return;
815 }
816 origContent = startContent;
817
818 if (startContent->IsElement()) {
819 nsIContent *childContent = startContent->GetChildAt(startOffset);
820 if (childContent) {
821 startContent = childContent;
822 }
823 }
824 else if (startOffset > 0) {
825 const nsTextFragment *textFrag = startContent->GetText();
826 if (textFrag) {
827 // look for non whitespace character before start offset
828 for (int32_t index = 0; index < startOffset; index++) {
829 // FIXME: take content language into account when deciding whitespace.
830 if (!mozilla::dom::IsSpaceCharacter(textFrag->CharAt(index))) {
831 *aIsStartingLink = false; // not at start of a node
832
833 break;
834 }
835 }
836 }
837 }
838
839 // ------- Check to see if inside link ---------
840
841 // We now have the correct start node for the range
842 // Search for links, starting with startNode, and going up parent chain
843
844 nsCOMPtr<nsIAtom> tag, hrefAtom(do_GetAtom("href"));
845 nsCOMPtr<nsIAtom> typeAtom(do_GetAtom("type"));
846
847 while (true) {
848 // Keep testing while startContent is equal to something,
849 // eventually we'll run out of ancestors
850
851 if (startContent->IsHTML()) {
852 nsCOMPtr<mozilla::dom::Link> link(do_QueryInterface(startContent));
853 if (link) {
854 // Check to see if inside HTML link
855 *aIsInsideLink = startContent->HasAttr(kNameSpaceID_None, hrefAtom);
856 return;
857 }
858 }
859 else {
860 // Any xml element can be an xlink
861 *aIsInsideLink = startContent->HasAttr(kNameSpaceID_XLink, hrefAtom);
862 if (*aIsInsideLink) {
863 if (!startContent->AttrValueIs(kNameSpaceID_XLink, typeAtom,
864 NS_LITERAL_STRING("simple"),
865 eCaseMatters)) {
866 *aIsInsideLink = false; // Xlink must be type="simple"
867 }
868
869 return;
870 }
871 }
872
873 // Get the parent
874 nsCOMPtr<nsIContent> parent = startContent->GetParent();
875 if (!parent)
876 break;
877
878 nsIContent* parentsFirstChild = parent->GetFirstChild();
879
880 // We don't want to look at a whitespace-only first child
881 if (parentsFirstChild && parentsFirstChild->TextIsOnlyWhitespace()) {
882 parentsFirstChild = parentsFirstChild->GetNextSibling();
883 }
884
885 if (parentsFirstChild != startContent) {
886 // startContent wasn't a first child, so we conclude that
887 // if this is inside a link, it's not at the beginning of it
888 *aIsStartingLink = false;
889 }
890
891 startContent = parent;
892 }
893
894 *aIsStartingLink = false;
895 }
896
897 /* Find another match in the page. */
898 NS_IMETHODIMP
899 nsTypeAheadFind::FindAgain(bool aFindBackwards, bool aLinksOnly,
900 uint16_t* aResult)
901
902 {
903 *aResult = FIND_NOTFOUND;
904
905 if (!mTypeAheadBuffer.IsEmpty())
906 // Beware! This may flush notifications via synchronous
907 // ScrollSelectionIntoView.
908 FindItNow(nullptr, aLinksOnly, false, aFindBackwards, aResult);
909
910 return NS_OK;
911 }
912
913 NS_IMETHODIMP
914 nsTypeAheadFind::Find(const nsAString& aSearchString, bool aLinksOnly,
915 uint16_t* aResult)
916 {
917 *aResult = FIND_NOTFOUND;
918
919 nsCOMPtr<nsIPresShell> presShell (GetPresShell());
920 if (!presShell) {
921 nsCOMPtr<nsIDocShell> ds (do_QueryReferent(mDocShell));
922 NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
923
924 presShell = ds->GetPresShell();
925 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
926 mPresShell = do_GetWeakReference(presShell);
927 }
928
929 nsCOMPtr<nsISelection> selection;
930 nsCOMPtr<nsISelectionController> selectionController =
931 do_QueryReferent(mSelectionController);
932 if (!selectionController) {
933 GetSelection(presShell, getter_AddRefs(selectionController),
934 getter_AddRefs(selection)); // cache for reuse
935 mSelectionController = do_GetWeakReference(selectionController);
936 } else {
937 selectionController->GetSelection(
938 nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
939 }
940
941 if (selection)
942 selection->CollapseToStart();
943
944 if (aSearchString.IsEmpty()) {
945 mTypeAheadBuffer.Truncate();
946
947 // These will be initialized to their true values after the first character
948 // is typed
949 mStartFindRange = nullptr;
950 mSelectionController = nullptr;
951
952 *aResult = FIND_FOUND;
953 return NS_OK;
954 }
955
956 bool atEnd = false;
957 if (mTypeAheadBuffer.Length()) {
958 const nsAString& oldStr = Substring(mTypeAheadBuffer, 0, mTypeAheadBuffer.Length());
959 const nsAString& newStr = Substring(aSearchString, 0, mTypeAheadBuffer.Length());
960 if (oldStr.Equals(newStr))
961 atEnd = true;
962
963 const nsAString& newStr2 = Substring(aSearchString, 0, aSearchString.Length());
964 const nsAString& oldStr2 = Substring(mTypeAheadBuffer, 0, aSearchString.Length());
965 if (oldStr2.Equals(newStr2))
966 atEnd = true;
967
968 if (!atEnd)
969 mStartFindRange = nullptr;
970 }
971
972 if (!mIsSoundInitialized && !mNotFoundSoundURL.IsEmpty()) {
973 // This makes sure system sound library is loaded so that
974 // there's no lag before the first sound is played
975 // by waiting for the first keystroke, we still get the startup time benefits.
976 mIsSoundInitialized = true;
977 mSoundInterface = do_CreateInstance("@mozilla.org/sound;1");
978 if (mSoundInterface && !mNotFoundSoundURL.Equals(NS_LITERAL_CSTRING("beep"))) {
979 mSoundInterface->Init();
980 }
981 }
982
983 #ifdef XP_WIN
984 // After each keystroke, ensure sound object is destroyed, to free up memory
985 // allocated for error sound, otherwise Windows' nsISound impl
986 // holds onto the last played sound, using up memory.
987 mSoundInterface = nullptr;
988 #endif
989
990 int32_t bufferLength = mTypeAheadBuffer.Length();
991
992 mTypeAheadBuffer = aSearchString;
993
994 bool isFirstVisiblePreferred = false;
995
996 // --------- Initialize find if 1st char ----------
997 if (bufferLength == 0) {
998 // If you can see the selection (not collapsed or thru caret browsing),
999 // or if already focused on a page element, start there.
1000 // Otherwise we're going to start at the first visible element
1001 bool isSelectionCollapsed = true;
1002 if (selection)
1003 selection->GetIsCollapsed(&isSelectionCollapsed);
1004
1005 // If true, we will scan from top left of visible area
1006 // If false, we will scan from start of selection
1007 isFirstVisiblePreferred = !atEnd && !mCaretBrowsingOn && isSelectionCollapsed;
1008 if (isFirstVisiblePreferred) {
1009 // Get the focused content. If there is a focused node, ensure the
1010 // selection is at that point. Otherwise, we will just want to start
1011 // from the caret position or the beginning of the document.
1012 nsPresContext* presContext = presShell->GetPresContext();
1013 NS_ENSURE_TRUE(presContext, NS_OK);
1014
1015 nsCOMPtr<nsIDocument> document =
1016 do_QueryInterface(presShell->GetDocument());
1017 if (!document)
1018 return NS_ERROR_UNEXPECTED;
1019
1020 nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(document->GetWindow());
1021
1022 nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
1023 if (fm) {
1024 nsCOMPtr<nsIDOMElement> focusedElement;
1025 nsCOMPtr<nsIDOMWindow> focusedWindow;
1026 fm->GetFocusedElementForWindow(window, false, getter_AddRefs(focusedWindow),
1027 getter_AddRefs(focusedElement));
1028 // If the root element is focused, then it's actually the document
1029 // that has the focus, so ignore this.
1030 if (focusedElement &&
1031 !SameCOMIdentity(focusedElement, document->GetRootElement())) {
1032 fm->MoveCaretToFocus(window);
1033 isFirstVisiblePreferred = false;
1034 }
1035 }
1036 }
1037 }
1038
1039 // ----------- Find the text! ---------------------
1040 // Beware! This may flush notifications via synchronous
1041 // ScrollSelectionIntoView.
1042 nsresult rv = FindItNow(nullptr, aLinksOnly, isFirstVisiblePreferred,
1043 false, aResult);
1044
1045 // ---------Handle success or failure ---------------
1046 if (NS_SUCCEEDED(rv)) {
1047 if (mTypeAheadBuffer.Length() == 1) {
1048 // If first letter, store where the first find succeeded
1049 // (mStartFindRange)
1050
1051 mStartFindRange = nullptr;
1052 if (selection) {
1053 nsCOMPtr<nsIDOMRange> startFindRange;
1054 selection->GetRangeAt(0, getter_AddRefs(startFindRange));
1055 if (startFindRange)
1056 startFindRange->CloneRange(getter_AddRefs(mStartFindRange));
1057 }
1058 }
1059 }
1060 else {
1061 // Error sound
1062 if (mTypeAheadBuffer.Length() > mLastFindLength)
1063 PlayNotFoundSound();
1064 }
1065
1066 SaveFind();
1067 return NS_OK;
1068 }
1069
1070 void
1071 nsTypeAheadFind::GetSelection(nsIPresShell *aPresShell,
1072 nsISelectionController **aSelCon,
1073 nsISelection **aDOMSel)
1074 {
1075 if (!aPresShell)
1076 return;
1077
1078 // if aCurrentNode is nullptr, get selection for document
1079 *aDOMSel = nullptr;
1080
1081 nsPresContext* presContext = aPresShell->GetPresContext();
1082
1083 nsIFrame *frame = aPresShell->GetRootFrame();
1084
1085 if (presContext && frame) {
1086 frame->GetSelectionController(presContext, aSelCon);
1087 if (*aSelCon) {
1088 (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL,
1089 aDOMSel);
1090 }
1091 }
1092 }
1093
1094
1095 bool
1096 nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell,
1097 nsPresContext *aPresContext,
1098 nsIDOMRange *aRange, bool aMustBeInViewPort,
1099 bool aGetTopVisibleLeaf,
1100 nsIDOMRange **aFirstVisibleRange,
1101 bool *aUsesIndependentSelection)
1102 {
1103 NS_ASSERTION(aPresShell && aPresContext && aRange && aFirstVisibleRange,
1104 "params are invalid");
1105
1106 // We need to know if the range start is visible.
1107 // Otherwise, return the first visible range start
1108 // in aFirstVisibleRange
1109
1110 aRange->CloneRange(aFirstVisibleRange);
1111 nsCOMPtr<nsIDOMNode> node;
1112 aRange->GetStartContainer(getter_AddRefs(node));
1113
1114 nsCOMPtr<nsIContent> content(do_QueryInterface(node));
1115 if (!content)
1116 return false;
1117
1118 nsIFrame *frame = content->GetPrimaryFrame();
1119 if (!frame)
1120 return false; // No frame! Not visible then.
1121
1122 if (!frame->StyleVisibility()->IsVisible())
1123 return false;
1124
1125 // Detect if we are _inside_ a text control, or something else with its own
1126 // selection controller.
1127 if (aUsesIndependentSelection) {
1128 *aUsesIndependentSelection =
1129 (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION);
1130 }
1131
1132 // ---- We have a frame ----
1133 if (!aMustBeInViewPort)
1134 return true; // Don't need it to be on screen, just in rendering tree
1135
1136 // Get the next in flow frame that contains the range start
1137 int32_t startRangeOffset, startFrameOffset, endFrameOffset;
1138 aRange->GetStartOffset(&startRangeOffset);
1139 while (true) {
1140 frame->GetOffsets(startFrameOffset, endFrameOffset);
1141 if (startRangeOffset < endFrameOffset)
1142 break;
1143
1144 nsIFrame *nextContinuationFrame = frame->GetNextContinuation();
1145 if (nextContinuationFrame)
1146 frame = nextContinuationFrame;
1147 else
1148 break;
1149 }
1150
1151 // Set up the variables we need, return true if we can't get at them all
1152 const uint16_t kMinPixels = 12;
1153 nscoord minDistance = nsPresContext::CSSPixelsToAppUnits(kMinPixels);
1154
1155 // Get the bounds of the current frame, relative to the current view.
1156 // We don't use the more accurate AccGetBounds, because that is
1157 // more expensive and the STATE_OFFSCREEN flag that this is used
1158 // for only needs to be a rough indicator
1159 nsRectVisibility rectVisibility = nsRectVisibility_kAboveViewport;
1160
1161 if (!aGetTopVisibleLeaf && !frame->GetRect().IsEmpty()) {
1162 rectVisibility =
1163 aPresShell->GetRectVisibility(frame,
1164 nsRect(nsPoint(0,0), frame->GetSize()),
1165 minDistance);
1166
1167 if (rectVisibility != nsRectVisibility_kAboveViewport) {
1168 return true;
1169 }
1170 }
1171
1172 // We know that the target range isn't usable because it's not in the
1173 // view port. Move range forward to first visible point,
1174 // this speeds us up a lot in long documents
1175 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
1176 nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID));
1177 if (trav)
1178 trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
1179 aPresContext, frame,
1180 eLeaf,
1181 false, // aVisual
1182 false, // aLockInScrollView
1183 false // aFollowOOFs
1184 );
1185
1186 if (!frameTraversal)
1187 return false;
1188
1189 while (rectVisibility == nsRectVisibility_kAboveViewport) {
1190 frameTraversal->Next();
1191 frame = frameTraversal->CurrentItem();
1192 if (!frame)
1193 return false;
1194
1195 if (!frame->GetRect().IsEmpty()) {
1196 rectVisibility =
1197 aPresShell->GetRectVisibility(frame,
1198 nsRect(nsPoint(0,0), frame->GetSize()),
1199 minDistance);
1200 }
1201 }
1202
1203 if (frame) {
1204 nsCOMPtr<nsIDOMNode> firstVisibleNode = do_QueryInterface(frame->GetContent());
1205
1206 if (firstVisibleNode) {
1207 frame->GetOffsets(startFrameOffset, endFrameOffset);
1208 (*aFirstVisibleRange)->SetStart(firstVisibleNode, startFrameOffset);
1209 (*aFirstVisibleRange)->SetEnd(firstVisibleNode, endFrameOffset);
1210 }
1211 }
1212
1213 return false;
1214 }
1215
1216 already_AddRefed<nsIPresShell>
1217 nsTypeAheadFind::GetPresShell()
1218 {
1219 if (!mPresShell)
1220 return nullptr;
1221
1222 nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShell);
1223 if (shell) {
1224 nsPresContext *pc = shell->GetPresContext();
1225 if (!pc || !pc->GetContainerWeak()) {
1226 return nullptr;
1227 }
1228 }
1229
1230 return shell.forget();
1231 }

mercurial