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