/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Mozilla browser. * * The Initial Developer of the Original Code is * Netscape Communications, Inc. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Conrad Carlen * Simon Fraser * Akkana Peck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsWebBrowserFind.h" // Only need this for NS_FIND_CONTRACTID, // else we could use nsIDOMRange.h and nsIFind.h. #include "nsFind.h" #include "nsIComponentManager.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptSecurityManager.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIDOMWindow.h" #include "nsIDOMWindowInternal.h" #include "nsPIDOMWindow.h" #include "nsIURI.h" #include "nsIDocShell.h" #include "nsIEnumerator.h" #include "nsIDocShellTreeItem.h" #include "nsIPresShell.h" #include "nsPresContext.h" #include "nsIEventStateManager.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsIFocusController.h" #include "nsISelectionController.h" #include "nsISelection.h" #include "nsIFrame.h" #include "nsITextControlFrame.h" #include "nsReadableUtils.h" #include "nsIDOMHTMLElement.h" #include "nsIDOMHTMLDocument.h" #include "nsIContent.h" #include "nsContentCID.h" #include "nsIServiceManager.h" #include "nsIObserverService.h" #include "nsISupportsPrimitives.h" #include "nsITimelineService.h" #if DEBUG #include "nsIWebNavigation.h" #include "nsXPIDLString.h" #endif #ifdef XP_MACOSX #include "nsAutoPtr.h" #include #endif static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); //***************************************************************************** // nsWebBrowserFind //***************************************************************************** nsWebBrowserFind::nsWebBrowserFind() : mFindBackwards(PR_FALSE), mWrapFind(PR_FALSE), mEntireWord(PR_FALSE), mMatchCase(PR_FALSE), mSearchSubFrames(PR_TRUE), mSearchParentFrames(PR_TRUE) { } nsWebBrowserFind::~nsWebBrowserFind() { } NS_IMPL_ISUPPORTS2(nsWebBrowserFind, nsIWebBrowserFind, nsIWebBrowserFindInFrames) /* boolean findNext (); */ NS_IMETHODIMP nsWebBrowserFind::FindNext(PRBool *outDidFind) { NS_ENSURE_ARG_POINTER(outDidFind); *outDidFind = PR_FALSE; NS_ENSURE_TRUE(CanFindNext(), NS_ERROR_NOT_INITIALIZED); nsresult rv = NS_OK; nsCOMPtr searchFrame = do_QueryReferent(mCurrentSearchFrame); NS_ENSURE_TRUE(searchFrame, NS_ERROR_NOT_INITIALIZED); nsCOMPtr rootFrame = do_QueryReferent(mRootSearchFrame); NS_ENSURE_TRUE(rootFrame, NS_ERROR_NOT_INITIALIZED); // first, if there's a "cmd_findagain" observer around, check to see if it // wants to perform the find again command . If it performs the find again // it will return true, in which case we exit ::FindNext() early. // Otherwise, nsWebBrowserFind needs to perform the find again command itself // this is used by nsTypeAheadFind, which controls find again when it was // the last executed find in the current window. nsCOMPtr observerSvc = do_GetService("@mozilla.org/observer-service;1"); if (observerSvc) { nsCOMPtr windowSupportsData = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr searchWindowSupports = do_QueryInterface(rootFrame); windowSupportsData->SetData(searchWindowSupports); NS_NAMED_LITERAL_STRING(dnStr, "down"); NS_NAMED_LITERAL_STRING(upStr, "up"); observerSvc->NotifyObservers(windowSupportsData, "nsWebBrowserFind_FindAgain", mFindBackwards? upStr.get(): dnStr.get()); windowSupportsData->GetData(getter_AddRefs(searchWindowSupports)); // findnext performed if search window data cleared out *outDidFind = searchWindowSupports == nsnull; if (*outDidFind) return NS_OK; } // next, look in the current frame. If found, return. rv = SearchInFrame(searchFrame, PR_FALSE, outDidFind); if (NS_FAILED(rv)) return rv; if (*outDidFind) return OnFind(searchFrame); // we are done // if we are not searching other frames, return if (!mSearchSubFrames && !mSearchParentFrames) return NS_OK; nsIDocShell *rootDocShell = GetDocShellFromWindow(rootFrame); if (!rootDocShell) return NS_ERROR_FAILURE; PRInt32 enumDirection; if (mFindBackwards) enumDirection = nsIDocShell::ENUMERATE_BACKWARDS; else enumDirection = nsIDocShell::ENUMERATE_FORWARDS; nsCOMPtr docShellEnumerator; rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, enumDirection, getter_AddRefs(docShellEnumerator)); if (NS_FAILED(rv)) return rv; // remember where we started nsCOMPtr startingItem = do_QueryInterface(GetDocShellFromWindow(searchFrame), &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr curItem; // XXX We should avoid searching in frameset documents here. // We also need to honour mSearchSubFrames and mSearchParentFrames. PRBool hasMore, doFind = PR_FALSE; while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr curSupports; rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); if (NS_FAILED(rv)) break; curItem = do_QueryInterface(curSupports, &rv); if (NS_FAILED(rv)) break; if (doFind) { searchFrame = do_GetInterface(curItem, &rv); if (NS_FAILED(rv)) break; OnStartSearchFrame(searchFrame); rv = SearchInFrame(searchFrame, PR_FALSE, outDidFind); if (NS_FAILED(rv)) return rv; if (*outDidFind) return OnFind(searchFrame); // we are done OnEndSearchFrame(searchFrame); } if (curItem.get() == startingItem.get()) doFind = PR_TRUE; // start looking in frames after this one }; if (!mWrapFind) { // remember where we left off SetCurrentSearchFrame(searchFrame); return NS_OK; } // From here on, we're wrapping, first through the other frames, // then finally from the beginning of the starting frame back to // the starting point. // because nsISimpleEnumerator is totally lame and isn't resettable, I // have to make a new one docShellEnumerator = nsnull; rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, enumDirection, getter_AddRefs(docShellEnumerator)); if (NS_FAILED(rv)) return rv; while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr curSupports; rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); if (NS_FAILED(rv)) break; curItem = do_QueryInterface(curSupports, &rv); if (NS_FAILED(rv)) break; if (curItem.get() == startingItem.get()) { rv = SearchInFrame(searchFrame, PR_TRUE, outDidFind); if (NS_FAILED(rv)) return rv; if (*outDidFind) return OnFind(searchFrame); // we are done break; } searchFrame = do_GetInterface(curItem, &rv); if (NS_FAILED(rv)) break; OnStartSearchFrame(searchFrame); rv = SearchInFrame(searchFrame, PR_FALSE, outDidFind); if (NS_FAILED(rv)) return rv; if (*outDidFind) return OnFind(searchFrame); // we are done OnEndSearchFrame(searchFrame); } // remember where we left off SetCurrentSearchFrame(searchFrame); NS_ASSERTION(NS_SUCCEEDED(rv), "Something failed"); return rv; } /* attribute wstring searchString; */ NS_IMETHODIMP nsWebBrowserFind::GetSearchString(PRUnichar * *aSearchString) { NS_ENSURE_ARG_POINTER(aSearchString); #ifdef XP_MACOSX OSStatus err; ScrapRef scrap; err = ::GetScrapByName(kScrapFindScrap, kScrapGetNamedScrap, &scrap); if (err == noErr) { Size byteCount; err = ::GetScrapFlavorSize(scrap, kScrapFlavorTypeUnicode, &byteCount); if (err == noErr) { NS_ASSERTION(byteCount%2 == 0, "byteCount not a multiple of 2"); nsAutoArrayPtr buffer(new PRUnichar[byteCount/2 + 1]); NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); err = ::GetScrapFlavorData(scrap, kScrapFlavorTypeUnicode, &byteCount, buffer.get()); if (err == noErr) { buffer[byteCount/2] = PRUnichar('\0'); mSearchString.Assign(buffer); } } } #endif *aSearchString = ToNewUnicode(mSearchString); return NS_OK; } NS_IMETHODIMP nsWebBrowserFind::SetSearchString(const PRUnichar * aSearchString) { mSearchString.Assign(aSearchString); #ifdef XP_MACOSX OSStatus err; ScrapRef scrap; err = ::GetScrapByName(kScrapFindScrap, kScrapClearNamedScrap, &scrap); if (err == noErr) { ::PutScrapFlavor(scrap, kScrapFlavorTypeUnicode, kScrapFlavorMaskNone, (mSearchString.Length()*2), aSearchString); } #endif return NS_OK; } /* attribute boolean findBackwards; */ NS_IMETHODIMP nsWebBrowserFind::GetFindBackwards(PRBool *aFindBackwards) { NS_ENSURE_ARG_POINTER(aFindBackwards); *aFindBackwards = mFindBackwards; return NS_OK; } NS_IMETHODIMP nsWebBrowserFind::SetFindBackwards(PRBool aFindBackwards) { mFindBackwards = aFindBackwards; return NS_OK; } /* attribute boolean wrapFind; */ NS_IMETHODIMP nsWebBrowserFind::GetWrapFind(PRBool *aWrapFind) { NS_ENSURE_ARG_POINTER(aWrapFind); *aWrapFind = mWrapFind; return NS_OK; } NS_IMETHODIMP nsWebBrowserFind::SetWrapFind(PRBool aWrapFind) { mWrapFind = aWrapFind; return NS_OK; } /* attribute boolean entireWord; */ NS_IMETHODIMP nsWebBrowserFind::GetEntireWord(PRBool *aEntireWord) { NS_ENSURE_ARG_POINTER(aEntireWord); *aEntireWord = mEntireWord; return NS_OK; } NS_IMETHODIMP nsWebBrowserFind::SetEntireWord(PRBool aEntireWord) { mEntireWord = aEntireWord; return NS_OK; } /* attribute boolean matchCase; */ NS_IMETHODIMP nsWebBrowserFind::GetMatchCase(PRBool *aMatchCase) { NS_ENSURE_ARG_POINTER(aMatchCase); *aMatchCase = mMatchCase; return NS_OK; } NS_IMETHODIMP nsWebBrowserFind::SetMatchCase(PRBool aMatchCase) { mMatchCase = aMatchCase; return NS_OK; } // Same as the tail-end of nsEventStateManager::FocusElementButNotDocument. // Used here because nsEventStateManager::MoveFocusToCaret() doesn't // support text input controls. static void FocusElementButNotDocument(nsIDocument* aDocument, nsIContent* aContent) { nsIFocusController *focusController = nsnull; nsCOMPtr ourWindow = do_QueryInterface(aDocument->GetScriptGlobalObject()); if (ourWindow) focusController = ourWindow->GetRootFocusController(); if (!focusController) return; // Get previous focus nsCOMPtr oldFocusedElement; focusController->GetFocusedElement(getter_AddRefs(oldFocusedElement)); nsCOMPtr oldFocusedContent = do_QueryInterface(oldFocusedElement); // Notify focus controller of new focus for this document nsCOMPtr newFocusedElement(do_QueryInterface(aContent)); focusController->SetFocusedElement(newFocusedElement); nsIPresShell* presShell = aDocument->GetShellAt(0); nsIEventStateManager* esm = presShell->GetPresContext()->EventStateManager(); // Temporarily set esm::mCurrentFocus so that esm::GetContentState() tells // layout system to show focus on this element. esm->SetFocusedContent(aContent); // Reset back to null at the end. aDocument->BeginUpdate(UPDATE_CONTENT_STATE); aDocument->ContentStatesChanged(oldFocusedContent, aContent, NS_EVENT_STATE_FOCUS); aDocument->EndUpdate(UPDATE_CONTENT_STATE); // Reset esm::mCurrentFocus = nsnull for this doc, so when this document // does get focus next time via preHandleEvent() NS_GOTFOCUS, // the old document gets blurred esm->SetFocusedContent(nsnull); } static PRBool IsNativeAnonymous(nsIContent* aContent) { while (aContent) { nsIContent* bindingParent = aContent->GetBindingParent(); if (bindingParent == aContent) { return PR_TRUE; } aContent = bindingParent; } return PR_FALSE; } void nsWebBrowserFind::SetSelectionAndScroll(nsIDOMWindow* aWindow, nsIDOMRange* aRange) { nsCOMPtr domDoc; aWindow->GetDocument(getter_AddRefs(domDoc)); if (!domDoc) return; nsCOMPtr doc(do_QueryInterface(domDoc)); nsIPresShell* presShell = doc->GetShellAt(0); if (!presShell) return; // since the match could be an anonymous textnode inside a //