diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index 56eda105c18..9ac84b7afc8 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -2753,6 +2753,10 @@ onget="return this.mCurrentBrowser.currentURI;" readonly="true"/> + + diff --git a/browser/base/content/test/browser_zbug569342.js b/browser/base/content/test/browser_zbug569342.js index 5395989086e..564cff447c4 100644 --- a/browser/base/content/test/browser_zbug569342.js +++ b/browser/base/content/test/browser_zbug569342.js @@ -41,7 +41,11 @@ function nextTest() { testFindDisabled(url, nextTest); } else { // Make sure the find bar is re-enabled after disabled page is closed. - testFindEnabled("about:blank", finish); + testFindEnabled("about:blank", function () { + EventUtils.synthesizeKey("VK_ESCAPE", { }); + ok(gFindBar.hidden, "Find bar should now be hidden"); + finish(); + }); } } diff --git a/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp index bc6124797b6..fbeaf6a448d 100644 --- a/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp +++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp @@ -919,15 +919,17 @@ nsTypeAheadFind::Find(const nsAString& aSearchString, bool aLinksOnly, *aResult = FIND_NOTFOUND; nsCOMPtr presShell (GetPresShell()); - if (!presShell) { + if (!presShell) { nsCOMPtr ds (do_QueryReferent(mDocShell)); NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE); presShell = ds->GetPresShell(); - mPresShell = do_GetWeakReference(presShell); - } + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + mPresShell = do_GetWeakReference(presShell); + } + nsCOMPtr selection; - nsCOMPtr selectionController = + nsCOMPtr selectionController = do_QueryReferent(mSelectionController); if (!selectionController) { GetSelection(presShell, getter_AddRefs(selectionController), diff --git a/toolkit/content/tests/chrome/findbar_window.xul b/toolkit/content/tests/chrome/findbar_window.xul index e54e2ed7342..8e902964dc7 100644 --- a/toolkit/content/tests/chrome/findbar_window.xul +++ b/toolkit/content/tests/chrome/findbar_window.xul @@ -300,7 +300,8 @@ var searchStr = "Link Test"; enterStringIntoFindField(searchStr); - ok(gFindBar._foundLink, "testQuickFindLink: failed to find sample link"); + ok(gBrowser.contentWindow.getSelection() == searchStr, + "testQuickFindLink: failed to find sample link"); } function testQuickFindText() { diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index cc0083861d3..44608c2b149 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -296,11 +296,24 @@ + null + + + + + null - - - + - + ]]> + null + false + diff --git a/toolkit/content/widgets/findbar.xml b/toolkit/content/widgets/findbar.xml index 32f8f5aaea6..2c26a000dd3 100644 --- a/toolkit/content/widgets/findbar.xml +++ b/toolkit/content/widgets/findbar.xml @@ -31,7 +31,7 @@ @@ -58,15 +53,12 @@ @@ -86,11 +78,7 @@ ]]> 2 0 - null - "0" - false - null - null 0 6 0 - - - - @@ -299,16 +255,19 @@ if (this._browser) { this._browser.removeEventListener("keypress", this, false); this._browser.removeEventListener("mouseup", this, false); - this._foundLink = null; - this._foundEditable = null; - this._currentWindow = null; + let finder = this._browser.finder; + if (finder) + finder.removeResultListener(this); } this._browser = val; if (this._browser) { this._browser.addEventListener("keypress", this, false); this._browser.addEventListener("mouseup", this, false); - this._findField.value = this._browser.fastFind.searchString; + this._browser.finder.addResultListener(this); + + this._findField.value = this._browser._lastSearchString; + this.toggleHighlight(this.browser._lastSearchHighlight); } return val; ]]> @@ -330,7 +289,7 @@ if (aTopic != "nsPref:changed") return; - var prefsvc = + let prefsvc = aSubject.QueryInterface(Components.interfaces.nsIPrefBranch); switch (aPrefName) { @@ -358,11 +317,9 @@ this._findStatusIcon = this.getElement("find-status-icon"); this._findStatusDesc = this.getElement("find-status"); - this._foundLink = null; - this._foundEditable = null; - this._currentWindow = null; + this._foundURL = null; - var prefsvc = + let prefsvc = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); @@ -416,13 +373,13 @@ // Thus, to avoid leaking, if we are listening to any editors, unhook // ourselves now, and remove our cached copies if (this._editors) { - for (var x = this._editors.length - 1; x >= 0; --x) + for (let x = this._editors.length - 1; x >= 0; --x) this._unhookListenersAtIndex(x); } this.browser = null; - var prefsvc = + let prefsvc = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); prefsvc.removeObserver("accessibility.typeaheadfind", @@ -477,29 +434,6 @@ ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - = 0; i--) { - if (shouldDelete[i]) { - var r = fSelection.getRangeAt(i); - fSelection.removeRange(r); - } - } - - // Remove listeners if no more highlights left - if (fSelection.rangeCount == 0) - this._removeEditorListeners(editor); - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - = 0; --x) { - if (this._editors[x].document == doc) { - sel = this._editors[x].selectionController - .getSelection(this._findSelection); - sel.removeAllRanges(); - // We don't need to listen to this editor any more - this._unhookListenersAtIndex(x); - } - } - } - return true; - } - - return textFound; - ]]> - - - - - - - @@ -1080,25 +488,24 @@ @@ -1110,7 +517,7 @@ @@ -1251,14 +635,11 @@ @@ -1269,7 +650,7 @@ if (!aTarget) return; - var event = document.createEvent("KeyEvents"); + let event = document.createEvent("KeyEvents"); event.initKeyEvent(aEvent.type, aEvent.bubbles, aEvent.cancelable, aEvent.view, aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey, aEvent.metaKey, aEvent.keyCode, @@ -1280,6 +661,7 @@ null + - - - - - @@ -1417,7 +732,7 @@ aEvent.defaultPrevented) return false; - var elt = document.commandDispatcher.focusedElement; + let elt = document.commandDispatcher.focusedElement; if (elt) { if (elt instanceof HTMLInputElement && elt.mozIsTextField(false)) return false; @@ -1429,12 +744,12 @@ return false; } - var win = document.commandDispatcher.focusedWindow; + let win = document.commandDispatcher.focusedWindow; if (win && !this._mimeTypeIsTextBased(win.document.contentType)) return false; // disable FAYT in about:blank to prevent FAYT opening unexpectedly. - var url = this.browser.currentURI; + let url = this.browser.currentURI; if (url.spec == "about:blank") return false; @@ -1446,7 +761,7 @@ if (win) { try { - var editingSession = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + let editingSession = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIEditingSession); @@ -1494,12 +809,12 @@ return; } - var key = aEvent.charCode ? String.fromCharCode(aEvent.charCode) : null; - var manualstartFAYT = (key == TAF_LINKS_KEY || key == TAF_TEXT_KEY); - var autostartFAYT = !manualstartFAYT && this._useTypeAheadFind && + let key = aEvent.charCode ? String.fromCharCode(aEvent.charCode) : null; + let manualstartFAYT = (key == TAF_LINKS_KEY || key == TAF_TEXT_KEY); + let autostartFAYT = !manualstartFAYT && this._useTypeAheadFind && key && key != " "; if (manualstartFAYT || autostartFAYT) { - var mode = (key == TAF_LINKS_KEY || + let mode = (key == TAF_LINKS_KEY || (autostartFAYT && this._typeAheadLinksOnly)) ? this.FIND_LINKS : this.FIND_TYPEAHEAD; @@ -1628,36 +943,23 @@ ]]> - - - - - { + this._findFailedString = null; + this._findResetTimeout = -1; + }, 1000); ]]> @@ -1728,16 +1019,8 @@ @@ -1781,11 +1064,10 @@ ]]> - this._selectionMaxLen) { - var pattern = new RegExp("^(?:\\s*.){0," + this._selectionMaxLen + "}"); + let pattern = new RegExp("^(?:\\s*.){0," + this._selectionMaxLen + "}"); pattern.test(selText); selText = RegExp.lastMatch; } @@ -1846,15 +1128,14 @@ this._flash(), 500); prefsvc.setIntPref("accessibility.typeaheadfind.flashBar", --this._flashFindBar); } @@ -1863,7 +1144,7 @@ userWantsPrefill = prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection"); - var initialString = (this.prefillWithSelection && userWantsPrefill) ? + let initialString = (this.prefillWithSelection && userWantsPrefill) ? this._getInitialSelection() : null; if (initialString) this._findField.value = initialString; @@ -1895,7 +1176,7 @@ + + + + + + + + + diff --git a/toolkit/modules/Finder.jsm b/toolkit/modules/Finder.jsm new file mode 100644 index 00000000000..e8c83dd3c5d --- /dev/null +++ b/toolkit/modules/Finder.jsm @@ -0,0 +1,578 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +this.EXPORTED_SYMBOLS = ["Finder"]; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +const Services = Cu.import("resource://gre/modules/Services.jsm").Services; + +function Finder(docShell) { + this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind); + this._fastFind.init(docShell); + + this._docShell = docShell; + this._document = docShell.getInterface(Ci.nsIDOMWindow).document; + this._listeners = []; + + this._previousLink = null; + this._drewOutline = false; + + this._searchString = null; +} + +Finder.prototype = { + addResultListener: function (aListener) { + if (this._listeners.indexOf(aListener) === -1) + this._listeners.push(aListener); + }, + + removeResultListener: function (aListener) { + this._listeners = this._listeners.filter(l => l != aListener); + }, + + _notify: function (aResult, aFindBackwards, aLinksOnly) { + this._outlineLink(aLinksOnly); + + let foundLink = this._fastFind.foundLink; + let linkURL = null; + if (aLinksOnly && foundLink) { + let docCharset = null; + let ownerDoc = foundLink.ownerDocument; + if (ownerDoc) + docCharset = ownerDoc.characterSet; + + if (!this._textToSubURIService) { + this._textToSubURIService = Cc["@mozilla.org/intl/texttosuburi;1"] + .getService(Ci.nsITextToSubURI); + } + + linkURL = this._textToSubURIService.unEscapeURIForUI(docCharset, foundLink.href); + } + + for (let l of this._listeners) { + l.onFindResult(aResult, aFindBackwards, linkURL); + } + }, + + get searchString() { + return this._fastFind.searchString; + }, + + set caseSensitive(aSensitive) { + this._fastFind.caseSensitive = aSensitive; + }, + + fastFind: function (aSearchString, aLinksOnly) { + let result = this._fastFind.find(aSearchString, aLinksOnly); + this._notify(result, false, aLinksOnly); + }, + + findAgain: function (aFindBackwards, aLinksOnly) { + let result = this._fastFind.findAgain(aFindBackwards, aLinksOnly); + this._notify(result, aFindBackwards, aLinksOnly); + }, + + highlight: function (aHighlight, aWord) { + this._searchString = aWord; + this._highlight(aHighlight, aWord, null); + }, + + removeSelection: function() { + let fastFind = this._fastFind; + + fastFind.collapseSelection(); + fastFind.setSelectionModeAndRepaint(Ci.nsISelectionController.SELECTION_ON); + + // We also drew our own outline, remove that as well. + if (this._previousLink && this._drewOutline) { + this._previousLink.style.outline = this._tmpOutline; + this._previousLink.style.outlineOffset = this._tmpOutlineOffset; + } + }, + + focusContent: function() { + let fastFind = this._fastFind; + + try { + // Try to find the best possible match that should receive focus. + if (fastFind.foundLink) { + fastFind.foundLink.focus(); + } else if (fastFind.foundEditable) { + fastFind.foundEditable.focus(); + fastFind.collapseSelection(); + } else { + this._getWindow().focus() + } + } catch (e) {} + }, + + keyPress: function (aEvent) { + let controller = this._getSelectionController(this._getWindow()); + + switch (aEvent.keyCode) { + case Ci.nsIDOMKeyEvent.DOM_VK_RETURN: + if (this._fastFind.foundLink) // Todo: Handle ctrl click. + this._fastFind.foundLink.click(); + break; + case Ci.nsIDOMKeyEvent.DOM_VK_TAB: + if (aEvent.shiftKey) + this._document.commandDispatcher.rewindFocus(); + else + this._document.commandDispatcher.advanceFocus(); + break; + case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP: + controller.scrollPage(false); + break; + case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN: + controller.scrollPage(true); + break; + case Ci.nsIDOMKeyEvent.DOM_VK_UP: + controller.scrollLine(false); + break; + case Ci.nsIDOMKeyEvent.DOM_VK_DOWN: + controller.scrollLine(true); + break; + } + }, + + _getWindow: function () { + return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); + }, + + _outlineLink: function (aLinksOnly) { + let foundLink = this._fastFind.foundLink; + + if (foundLink == this._previousLink) + return; + + if (this._previousLink && this._drewOutline) { + // restore original outline + this._previousLink.style.outline = this._tmpOutline; + this._previousLink.style.outlineOffset = this._tmpOutlineOffset; + } + + this._drewOutline = (foundLink && aLinksOnly); + if (this._drewOutline) { + // Backup original outline + this._tmpOutline = foundLink.style.outline; + this._tmpOutlineOffset = foundLink.style.outlineOffset; + + // Draw pseudo focus rect + // XXX Should we change the following style for FAYT pseudo focus? + // XXX Shouldn't we change default design if outline is visible + // already? + // Don't set the outline-color, we should always use initial value. + foundLink.style.outline = "1px dotted"; + foundLink.style.outlineOffset = "0"; + } + + this._previousLink = foundLink; + }, + + _highlight: function (aHighlight, aWord, aWindow) { + let win = aWindow || this._getWindow(); + + let result = Ci.nsITypeAheadFind.FIND_NOTFOUND; + for (let i = 0; win.frames && i < win.frames.length; i++) { + if (this._highlight(aHighlight, aWord, win.frames[i])) + result = Ci.nsITypeAheadFind.FIND_FOUND; + } + + let controller = this._getSelectionController(win); + let doc = win.document; + if (!controller || !doc || !doc.documentElement) { + // Without the selection controller, + // we are unable to (un)highlight any matches + this._notify(result) + return; + } + + let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ? + doc.body : doc.documentElement; + + if (aHighlight) { + let searchRange = doc.createRange(); + searchRange.selectNodeContents(body); + + let startPt = searchRange.cloneRange(); + startPt.collapse(true); + + let endPt = searchRange.cloneRange(); + endPt.collapse(false); + + let retRange = null; + let finder = Cc["@mozilla.org/embedcomp/rangefind;1"] + .createInstance() + .QueryInterface(Ci.nsIFind); + + finder.caseSensitive = this._fastFind.caseSensitive; + + while ((retRange = finder.Find(aWord, searchRange, + startPt, endPt))) { + this._highlightRange(retRange, controller); + startPt = retRange.cloneRange(); + startPt.collapse(false); + + result = Ci.nsITypeAheadFind.FIND_FOUND; + } + } else { + // First, attempt to remove highlighting from main document + let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND); + sel.removeAllRanges(); + + // Next, check our editor cache, for editors belonging to this + // document + if (this._editors) { + for (let x = this._editors.length - 1; x >= 0; --x) { + if (this._editors[x].document == doc) { + sel = this._editors[x].selectionController + .getSelection(Ci.nsISelectionController.SELECTION_FIND); + sel.removeAllRanges(); + // We don't need to listen to this editor any more + this._unhookListenersAtIndex(x); + } + } + } + return true; + } + + this._notify(result); + }, + + _highlightRange: function(aRange, aController) { + let node = aRange.startContainer; + let controller = aController; + + let editableNode = this._getEditableNode(node); + if (editableNode) + controller = editableNode.editor.selectionController; + + let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND); + findSelection.addRange(aRange); + + if (editableNode) { + // Highlighting added, so cache this editor, and hook up listeners + // to ensure we deal properly with edits within the highlighting + if (!this._editors) { + this._editors = []; + this._stateListeners = []; + } + + let existingIndex = this._editors.indexOf(editableNode.editor); + if (existingIndex == -1) { + let x = this._editors.length; + this._editors[x] = editableNode.editor; + this._stateListeners[x] = this._createStateListener(); + this._editors[x].addEditActionListener(this); + this._editors[x].addDocumentStateListener(this._stateListeners[x]); + } + } + }, + + _getSelectionController: function(aWindow) { + // display: none iframes don't have a selection controller, see bug 493658 + if (!aWindow.innerWidth || !aWindow.innerHeight) + return null; + + // Yuck. See bug 138068. + let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + return controller; + }, + + _getEditableNode: function (aNode) { + while (aNode) { + if (aNode instanceof Ci.nsIDOMNSEditableElement) + return aNode.editor ? aNode : null; + + aNode = aNode.parentNode; + } + return null; + }, + + /* + * Helper method to unhook listeners, remove cached editors + * and keep the relevant arrays in sync + * + * @param aIndex the index into the array of editors/state listeners + * we wish to remove + */ + _unhookListenersAtIndex: function (aIndex) { + this._editors[aIndex].removeEditActionListener(this); + this._editors[aIndex] + .removeDocumentStateListener(this._stateListeners[aIndex]); + this._editors.splice(aIndex, 1); + this._stateListeners.splice(aIndex, 1); + if (!this._editors.length) { + delete this._editors; + delete this._stateListeners; + } + }, + + /* + * Remove ourselves as an nsIEditActionListener and + * nsIDocumentStateListener from a given cached editor + * + * @param aEditor the editor we no longer wish to listen to + */ + _removeEditorListeners: function (aEditor) { + // aEditor is an editor that we listen to, so therefore must be + // cached. Find the index of this editor + let idx = this._editors.indexOf(aEditor); + if (idx == -1) + return; + // Now unhook ourselves, and remove our cached copy + this._unhookListenersAtIndex(idx); + }, + + /* + * nsIEditActionListener logic follows + * + * We implement this interface to allow us to catch the case where + * the findbar found a match in a HTML or