зеркало из https://github.com/mozilla/gecko-dev.git
Bug 666816 - Support findbar in e10s. r=mikedeboer
This commit is contained in:
Родитель
86e50bc815
Коммит
e161bba0b5
|
@ -2753,6 +2753,10 @@
|
|||
onget="return this.mCurrentBrowser.currentURI;"
|
||||
readonly="true"/>
|
||||
|
||||
<property name="finder"
|
||||
onget="return this.mCurrentBrowser.finder"
|
||||
readonly="true"/>
|
||||
|
||||
<property name="docShell"
|
||||
onget="return this.mCurrentBrowser.docShell"
|
||||
readonly="true"/>
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -924,8 +924,10 @@ nsTypeAheadFind::Find(const nsAString& aSearchString, bool aLinksOnly,
|
|||
NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
|
||||
|
||||
presShell = ds->GetPresShell();
|
||||
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
|
||||
mPresShell = do_GetWeakReference(presShell);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISelection> selection;
|
||||
nsCOMPtr<nsISelectionController> selectionController =
|
||||
do_QueryReferent(mSelectionController);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -296,11 +296,25 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<field name="_finder">null</field>
|
||||
|
||||
<property name="finder" readonly="true">
|
||||
<getter><![CDATA[
|
||||
if (!this._finder) {
|
||||
if (!this.docShell)
|
||||
return null;
|
||||
|
||||
let Finder = Components.utils.import("resource://gre/modules/Finder.jsm", {}).Finder;
|
||||
this._finder = new Finder(this.docShell);
|
||||
}
|
||||
return this._finder;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<field name="_fastFind">null</field>
|
||||
<property name="fastFind"
|
||||
readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
<getter><![CDATA[
|
||||
if (!this._fastFind) {
|
||||
if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
|
||||
return null;
|
||||
|
@ -317,10 +331,12 @@
|
|||
this._fastFind.init(this.docShell);
|
||||
}
|
||||
return this._fastFind;
|
||||
]]>
|
||||
</getter>
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<field name="_lastSearchString">null</field>
|
||||
<field name="_lastSearchHighlight">false</field>
|
||||
|
||||
<property name="webProgress"
|
||||
readonly="true"
|
||||
onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);"/>
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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 <input> or <textarea>. If the
|
||||
* user adjusts the text in some way, it will no longer match, so we
|
||||
* want to remove the highlight, rather than have it expand/contract
|
||||
* when letters are added or removed.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Helper method used to check whether a selection intersects with
|
||||
* some highlighting
|
||||
*
|
||||
* @param aSelectionRange the range from the selection to check
|
||||
* @param aFindRange the highlighted range to check against
|
||||
* @returns true if they intersect, false otherwise
|
||||
*/
|
||||
_checkOverlap: function (aSelectionRange, aFindRange) {
|
||||
// The ranges overlap if one of the following is true:
|
||||
// 1) At least one of the endpoints of the deleted selection
|
||||
// is in the find selection
|
||||
// 2) At least one of the endpoints of the find selection
|
||||
// is in the deleted selection
|
||||
if (aFindRange.isPointInRange(aSelectionRange.startContainer,
|
||||
aSelectionRange.startOffset))
|
||||
return true;
|
||||
if (aFindRange.isPointInRange(aSelectionRange.endContainer,
|
||||
aSelectionRange.endOffset))
|
||||
return true;
|
||||
if (aSelectionRange.isPointInRange(aFindRange.startContainer,
|
||||
aFindRange.startOffset))
|
||||
return true;
|
||||
if (aSelectionRange.isPointInRange(aFindRange.endContainer,
|
||||
aFindRange.endOffset))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Helper method to determine if an edit occurred within a highlight
|
||||
*
|
||||
* @param aSelection the selection we wish to check
|
||||
* @param aNode the node we want to check is contained in aSelection
|
||||
* @param aOffset the offset into aNode that we want to check
|
||||
* @returns the range containing (aNode, aOffset) or null if no ranges
|
||||
* in the selection contain it
|
||||
*/
|
||||
_findRange: function (aSelection, aNode, aOffset) {
|
||||
let rangeCount = aSelection.rangeCount;
|
||||
let rangeidx = 0;
|
||||
let foundContainingRange = false;
|
||||
let range = null;
|
||||
|
||||
// Check to see if this node is inside one of the selection's ranges
|
||||
while (!foundContainingRange && rangeidx < rangeCount) {
|
||||
range = aSelection.getRangeAt(rangeidx);
|
||||
if (range.isPointInRange(aNode, aOffset)) {
|
||||
foundContainingRange = true;
|
||||
break;
|
||||
}
|
||||
rangeidx++;
|
||||
}
|
||||
|
||||
if (foundContainingRange)
|
||||
return range;
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Start of nsIEditActionListener implementations
|
||||
|
||||
WillDeleteText: function (aTextNode, aOffset, aLength) {
|
||||
let editor = this._getEditableNode(aTextNode).editor;
|
||||
let controller = editor.selectionController;
|
||||
let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
||||
let range = this._findRange(fSelection, aTextNode, aOffset);
|
||||
|
||||
if (range) {
|
||||
// Don't remove the highlighting if the deleted text is at the
|
||||
// end of the range
|
||||
if (aTextNode != range.endContainer ||
|
||||
aOffset != range.endOffset) {
|
||||
// Text within the highlight is being removed - the text can
|
||||
// no longer be a match, so remove the highlighting
|
||||
fSelection.removeRange(range);
|
||||
if (fSelection.rangeCount == 0)
|
||||
this._removeEditorListeners(editor);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
DidInsertText: function (aTextNode, aOffset, aString) {
|
||||
let editor = this._getEditableNode(aTextNode).editor;
|
||||
let controller = editor.selectionController;
|
||||
let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
||||
let range = this._findRange(fSelection, aTextNode, aOffset);
|
||||
|
||||
if (range) {
|
||||
// If the text was inserted before the highlight
|
||||
// adjust the highlight's bounds accordingly
|
||||
if (aTextNode == range.startContainer &&
|
||||
aOffset == range.startOffset) {
|
||||
range.setStart(range.startContainer,
|
||||
range.startOffset+aString.length);
|
||||
} else if (aTextNode != range.endContainer ||
|
||||
aOffset != range.endOffset) {
|
||||
// The edit occurred within the highlight - any addition of text
|
||||
// will result in the text no longer being a match,
|
||||
// so remove the highlighting
|
||||
fSelection.removeRange(range);
|
||||
if (fSelection.rangeCount == 0)
|
||||
this._removeEditorListeners(editor);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
WillDeleteSelection: function (aSelection) {
|
||||
let editor = this._getEditableNode(aSelection.getRangeAt(0)
|
||||
.startContainer).editor;
|
||||
let controller = editor.selectionController;
|
||||
let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
||||
|
||||
let selectionIndex = 0;
|
||||
let findSelectionIndex = 0;
|
||||
let shouldDelete = {};
|
||||
let numberOfDeletedSelections = 0;
|
||||
let numberOfMatches = fSelection.rangeCount;
|
||||
|
||||
// We need to test if any ranges in the deleted selection (aSelection)
|
||||
// are in any of the ranges of the find selection
|
||||
// Usually both selections will only contain one range, however
|
||||
// either may contain more than one.
|
||||
|
||||
for (let fIndex = 0; fIndex < numberOfMatches; fIndex++) {
|
||||
shouldDelete[fIndex] = false;
|
||||
let fRange = fSelection.getRangeAt(fIndex);
|
||||
|
||||
for (let index = 0; index < aSelection.rangeCount; index++) {
|
||||
if (shouldDelete[fIndex])
|
||||
continue;
|
||||
|
||||
let selRange = aSelection.getRangeAt(index);
|
||||
let doesOverlap = this._checkOverlap(selRange, fRange);
|
||||
if (doesOverlap) {
|
||||
shouldDelete[fIndex] = true;
|
||||
numberOfDeletedSelections++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OK, so now we know what matches (if any) are in the selection
|
||||
// that is being deleted. Time to remove them.
|
||||
if (numberOfDeletedSelections == 0)
|
||||
return;
|
||||
|
||||
for (let i = numberOfMatches - 1; i >= 0; i--) {
|
||||
if (shouldDelete[i])
|
||||
fSelection.removeRange(fSelection.getRangeAt(i));
|
||||
}
|
||||
|
||||
// Remove listeners if no more highlights left
|
||||
if (fSelection.rangeCount == 0)
|
||||
this._removeEditorListeners(editor);
|
||||
},
|
||||
|
||||
/*
|
||||
* nsIDocumentStateListener logic follows
|
||||
*
|
||||
* When attaching nsIEditActionListeners, there are no guarantees
|
||||
* as to whether the findbar or the documents in the browser will get
|
||||
* destructed first. This leads to the potential to either leak, or to
|
||||
* hold on to a reference an editable element's editor for too long,
|
||||
* preventing it from being destructed.
|
||||
*
|
||||
* However, when an editor's owning node is being destroyed, the editor
|
||||
* sends out a DocumentWillBeDestroyed notification. We can use this to
|
||||
* clean up our references to the object, to allow it to be destroyed in a
|
||||
* timely fashion.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Unhook ourselves when one of our state listeners has been called.
|
||||
* This can happen in 4 cases:
|
||||
* 1) The document the editor belongs to is navigated away from, and
|
||||
* the document is not being cached
|
||||
*
|
||||
* 2) The document the editor belongs to is expired from the cache
|
||||
*
|
||||
* 3) The tab containing the owning document is closed
|
||||
*
|
||||
* 4) The <input> or <textarea> that owns the editor is explicitly
|
||||
* removed from the DOM
|
||||
*
|
||||
* @param the listener that was invoked
|
||||
*/
|
||||
_onEditorDestruction: function (aListener) {
|
||||
// First find the index of the editor the given listener listens to.
|
||||
// The listeners and editors arrays must always be in sync.
|
||||
// The listener will be in our array of cached listeners, as this
|
||||
// method could not have been called otherwise.
|
||||
let idx = 0;
|
||||
while (this._stateListeners[idx] != aListener)
|
||||
idx++;
|
||||
|
||||
// Unhook both listeners
|
||||
this._unhookListenersAtIndex(idx);
|
||||
},
|
||||
|
||||
/*
|
||||
* Creates a unique document state listener for an editor.
|
||||
*
|
||||
* It is not possible to simply have the findbar implement the
|
||||
* listener interface itself, as it wouldn't have sufficient information
|
||||
* to work out which editor was being destroyed. Therefore, we create new
|
||||
* listeners on the fly, and cache them in sync with the editors they
|
||||
* listen to.
|
||||
*/
|
||||
_createStateListener: function () {
|
||||
return {
|
||||
findbar: this,
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.nsIDocumentStateListener) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
return this;
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
NotifyDocumentWillBeDestroyed: function() {
|
||||
this.findbar._onEditorDestruction(this);
|
||||
},
|
||||
|
||||
// Unimplemented
|
||||
notifyDocumentCreated: function() {},
|
||||
notifyDocumentStateChanged: function(aDirty) {}
|
||||
};
|
||||
}
|
||||
};
|
|
@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [
|
|||
'Deprecated.jsm',
|
||||
'Dict.jsm',
|
||||
'FileUtils.jsm',
|
||||
'Finder.jsm',
|
||||
'Geometry.jsm',
|
||||
'Http.jsm',
|
||||
'InlineSpellChecker.jsm',
|
||||
|
|
Загрузка…
Ссылка в новой задаче