toolkit/components/typeaheadfind/nsTypeAheadFind.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial