Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 this.EXPORTED_SYMBOLS = ["Finder"];
7 const Ci = Components.interfaces;
8 const Cc = Components.classes;
9 const Cu = Components.utils;
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12 Cu.import("resource://gre/modules/Geometry.jsm");
13 Cu.import("resource://gre/modules/Services.jsm");
15 XPCOMUtils.defineLazyServiceGetter(this, "TextToSubURIService",
16 "@mozilla.org/intl/texttosuburi;1",
17 "nsITextToSubURI");
18 XPCOMUtils.defineLazyServiceGetter(this, "Clipboard",
19 "@mozilla.org/widget/clipboard;1",
20 "nsIClipboard");
21 XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
22 "@mozilla.org/widget/clipboardhelper;1",
23 "nsIClipboardHelper");
25 function Finder(docShell) {
26 this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
27 this._fastFind.init(docShell);
29 this._docShell = docShell;
30 this._listeners = [];
31 this._previousLink = null;
32 this._searchString = null;
34 docShell.QueryInterface(Ci.nsIInterfaceRequestor)
35 .getInterface(Ci.nsIWebProgress)
36 .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
37 }
39 Finder.prototype = {
40 addResultListener: function (aListener) {
41 if (this._listeners.indexOf(aListener) === -1)
42 this._listeners.push(aListener);
43 },
45 removeResultListener: function (aListener) {
46 this._listeners = this._listeners.filter(l => l != aListener);
47 },
49 _notify: function (aSearchString, aResult, aFindBackwards, aDrawOutline, aStoreResult = true) {
50 if (aStoreResult) {
51 this._searchString = aSearchString;
52 this.clipboardSearchString = aSearchString
53 }
54 this._outlineLink(aDrawOutline);
56 let foundLink = this._fastFind.foundLink;
57 let linkURL = null;
58 if (foundLink) {
59 let docCharset = null;
60 let ownerDoc = foundLink.ownerDocument;
61 if (ownerDoc)
62 docCharset = ownerDoc.characterSet;
64 linkURL = TextToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
65 }
67 let data = {
68 result: aResult,
69 findBackwards: aFindBackwards,
70 linkURL: linkURL,
71 rect: this._getResultRect(),
72 searchString: this._searchString,
73 storeResult: aStoreResult
74 };
76 for (let l of this._listeners) {
77 l.onFindResult(data);
78 }
79 },
81 get searchString() {
82 if (!this._searchString && this._fastFind.searchString)
83 this._searchString = this._fastFind.searchString;
84 return this._searchString;
85 },
87 get clipboardSearchString() {
88 let searchString = "";
89 if (!Clipboard.supportsFindClipboard())
90 return searchString;
92 try {
93 let trans = Cc["@mozilla.org/widget/transferable;1"]
94 .createInstance(Ci.nsITransferable);
95 trans.init(this._getWindow()
96 .QueryInterface(Ci.nsIInterfaceRequestor)
97 .getInterface(Ci.nsIWebNavigation)
98 .QueryInterface(Ci.nsILoadContext));
99 trans.addDataFlavor("text/unicode");
101 Clipboard.getData(trans, Ci.nsIClipboard.kFindClipboard);
103 let data = {};
104 let dataLen = {};
105 trans.getTransferData("text/unicode", data, dataLen);
106 if (data.value) {
107 data = data.value.QueryInterface(Ci.nsISupportsString);
108 searchString = data.toString();
109 }
110 } catch (ex) {}
112 return searchString;
113 },
115 set clipboardSearchString(aSearchString) {
116 if (!aSearchString || !Clipboard.supportsFindClipboard())
117 return;
119 ClipboardHelper.copyStringToClipboard(aSearchString,
120 Ci.nsIClipboard.kFindClipboard,
121 this._getWindow().document);
122 },
124 set caseSensitive(aSensitive) {
125 this._fastFind.caseSensitive = aSensitive;
126 },
128 /**
129 * Used for normal search operations, highlights the first match.
130 *
131 * @param aSearchString String to search for.
132 * @param aLinksOnly Only consider nodes that are links for the search.
133 * @param aDrawOutline Puts an outline around matched links.
134 */
135 fastFind: function (aSearchString, aLinksOnly, aDrawOutline) {
136 let result = this._fastFind.find(aSearchString, aLinksOnly);
137 let searchString = this._fastFind.searchString;
138 this._notify(searchString, result, false, aDrawOutline);
139 },
141 /**
142 * Repeat the previous search. Should only be called after a previous
143 * call to Finder.fastFind.
144 *
145 * @param aFindBackwards Controls the search direction:
146 * true: before current match, false: after current match.
147 * @param aLinksOnly Only consider nodes that are links for the search.
148 * @param aDrawOutline Puts an outline around matched links.
149 */
150 findAgain: function (aFindBackwards, aLinksOnly, aDrawOutline) {
151 let result = this._fastFind.findAgain(aFindBackwards, aLinksOnly);
152 let searchString = this._fastFind.searchString;
153 this._notify(searchString, result, aFindBackwards, aDrawOutline);
154 },
156 /**
157 * Forcibly set the search string of the find clipboard to the currently
158 * selected text in the window, on supported platforms (i.e. OSX).
159 */
160 setSearchStringToSelection: function() {
161 // Find the selected text.
162 let selection = this._getWindow().getSelection();
163 // Don't go for empty selections.
164 if (!selection.rangeCount)
165 return null;
166 let searchString = (selection.toString() || "").trim();
167 // Empty strings are rather useless to search for.
168 if (!searchString.length)
169 return null;
171 this.clipboardSearchString = searchString;
172 return searchString;
173 },
175 highlight: function (aHighlight, aWord) {
176 let found = this._highlight(aHighlight, aWord, null);
177 if (aHighlight) {
178 let result = found ? Ci.nsITypeAheadFind.FIND_FOUND
179 : Ci.nsITypeAheadFind.FIND_NOTFOUND;
180 this._notify(aWord, result, false, false, false);
181 }
182 },
184 enableSelection: function() {
185 this._fastFind.setSelectionModeAndRepaint(Ci.nsISelectionController.SELECTION_ON);
186 this._restoreOriginalOutline();
187 },
189 removeSelection: function() {
190 this._fastFind.collapseSelection();
191 this.enableSelection();
192 },
194 focusContent: function() {
195 // Allow Finder listeners to cancel focusing the content.
196 for (let l of this._listeners) {
197 if (!l.shouldFocusContent())
198 return;
199 }
201 let fastFind = this._fastFind;
202 const fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
203 try {
204 // Try to find the best possible match that should receive focus and
205 // block scrolling on focus since find already scrolls. Further
206 // scrolling is due to user action, so don't override this.
207 if (fastFind.foundLink) {
208 fm.setFocus(fastFind.foundLink, fm.FLAG_NOSCROLL);
209 } else if (fastFind.foundEditable) {
210 fm.setFocus(fastFind.foundEditable, fm.FLAG_NOSCROLL);
211 fastFind.collapseSelection();
212 } else {
213 this._getWindow().focus()
214 }
215 } catch (e) {}
216 },
218 keyPress: function (aEvent) {
219 let controller = this._getSelectionController(this._getWindow());
221 switch (aEvent.keyCode) {
222 case Ci.nsIDOMKeyEvent.DOM_VK_RETURN:
223 if (this._fastFind.foundLink) {
224 let view = this._fastFind.foundLink.ownerDocument.defaultView;
225 this._fastFind.foundLink.dispatchEvent(new view.MouseEvent("click", {
226 view: view,
227 cancelable: true,
228 bubbles: true,
229 ctrlKey: aEvent.ctrlKey,
230 altKey: aEvent.altKey,
231 shiftKey: aEvent.shiftKey,
232 metaKey: aEvent.metaKey
233 }));
234 }
235 break;
236 case Ci.nsIDOMKeyEvent.DOM_VK_TAB:
237 let direction = Services.focus.MOVEFOCUS_FORWARD;
238 if (aEvent.shiftKey) {
239 direction = Services.focus.MOVEFOCUS_BACKWARD;
240 }
241 Services.focus.moveFocus(this._getWindow(), null, direction, 0);
242 break;
243 case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP:
244 controller.scrollPage(false);
245 break;
246 case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN:
247 controller.scrollPage(true);
248 break;
249 case Ci.nsIDOMKeyEvent.DOM_VK_UP:
250 controller.scrollLine(false);
251 break;
252 case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
253 controller.scrollLine(true);
254 break;
255 }
256 },
258 _getWindow: function () {
259 return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
260 },
262 /**
263 * Get the bounding selection rect in CSS px relative to the origin of the
264 * top-level content document.
265 */
266 _getResultRect: function () {
267 let topWin = this._getWindow();
268 let win = this._fastFind.currentWindow;
269 if (!win)
270 return null;
272 let selection = win.getSelection();
273 if (!selection.rangeCount || selection.isCollapsed) {
274 // The selection can be into an input or a textarea element.
275 let nodes = win.document.querySelectorAll("input, textarea");
276 for (let node of nodes) {
277 if (node instanceof Ci.nsIDOMNSEditableElement && node.editor) {
278 let sc = node.editor.selectionController;
279 selection = sc.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
280 if (selection.rangeCount && !selection.isCollapsed) {
281 break;
282 }
283 }
284 }
285 }
287 let utils = topWin.QueryInterface(Ci.nsIInterfaceRequestor)
288 .getInterface(Ci.nsIDOMWindowUtils);
290 let scrollX = {}, scrollY = {};
291 utils.getScrollXY(false, scrollX, scrollY);
293 for (let frame = win; frame != topWin; frame = frame.parent) {
294 let rect = frame.frameElement.getBoundingClientRect();
295 let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
296 let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
297 scrollX.value += rect.left + parseInt(left, 10);
298 scrollY.value += rect.top + parseInt(top, 10);
299 }
300 let rect = Rect.fromRect(selection.getRangeAt(0).getBoundingClientRect());
301 return rect.translate(scrollX.value, scrollY.value);
302 },
304 _outlineLink: function (aDrawOutline) {
305 let foundLink = this._fastFind.foundLink;
307 // Optimization: We are drawing outlines and we matched
308 // the same link before, so don't duplicate work.
309 if (foundLink == this._previousLink && aDrawOutline)
310 return;
312 this._restoreOriginalOutline();
314 if (foundLink && aDrawOutline) {
315 // Backup original outline
316 this._tmpOutline = foundLink.style.outline;
317 this._tmpOutlineOffset = foundLink.style.outlineOffset;
319 // Draw pseudo focus rect
320 // XXX Should we change the following style for FAYT pseudo focus?
321 // XXX Shouldn't we change default design if outline is visible
322 // already?
323 // Don't set the outline-color, we should always use initial value.
324 foundLink.style.outline = "1px dotted";
325 foundLink.style.outlineOffset = "0";
327 this._previousLink = foundLink;
328 }
329 },
331 _restoreOriginalOutline: function () {
332 // Removes the outline around the last found link.
333 if (this._previousLink) {
334 this._previousLink.style.outline = this._tmpOutline;
335 this._previousLink.style.outlineOffset = this._tmpOutlineOffset;
336 this._previousLink = null;
337 }
338 },
340 _highlight: function (aHighlight, aWord, aWindow) {
341 let win = aWindow || this._getWindow();
343 let found = false;
344 for (let i = 0; win.frames && i < win.frames.length; i++) {
345 if (this._highlight(aHighlight, aWord, win.frames[i]))
346 found = true;
347 }
349 let controller = this._getSelectionController(win);
350 let doc = win.document;
351 if (!controller || !doc || !doc.documentElement) {
352 // Without the selection controller,
353 // we are unable to (un)highlight any matches
354 return found;
355 }
357 let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
358 doc.body : doc.documentElement;
360 if (aHighlight) {
361 let searchRange = doc.createRange();
362 searchRange.selectNodeContents(body);
364 let startPt = searchRange.cloneRange();
365 startPt.collapse(true);
367 let endPt = searchRange.cloneRange();
368 endPt.collapse(false);
370 let retRange = null;
371 let finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
372 .createInstance()
373 .QueryInterface(Ci.nsIFind);
375 finder.caseSensitive = this._fastFind.caseSensitive;
377 while ((retRange = finder.Find(aWord, searchRange,
378 startPt, endPt))) {
379 this._highlightRange(retRange, controller);
380 startPt = retRange.cloneRange();
381 startPt.collapse(false);
383 found = true;
384 }
385 } else {
386 // First, attempt to remove highlighting from main document
387 let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
388 sel.removeAllRanges();
390 // Next, check our editor cache, for editors belonging to this
391 // document
392 if (this._editors) {
393 for (let x = this._editors.length - 1; x >= 0; --x) {
394 if (this._editors[x].document == doc) {
395 sel = this._editors[x].selectionController
396 .getSelection(Ci.nsISelectionController.SELECTION_FIND);
397 sel.removeAllRanges();
398 // We don't need to listen to this editor any more
399 this._unhookListenersAtIndex(x);
400 }
401 }
402 }
404 // Removing the highlighting always succeeds, so return true.
405 found = true;
406 }
408 return found;
409 },
411 _highlightRange: function(aRange, aController) {
412 let node = aRange.startContainer;
413 let controller = aController;
415 let editableNode = this._getEditableNode(node);
416 if (editableNode)
417 controller = editableNode.editor.selectionController;
419 let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
420 findSelection.addRange(aRange);
422 if (editableNode) {
423 // Highlighting added, so cache this editor, and hook up listeners
424 // to ensure we deal properly with edits within the highlighting
425 if (!this._editors) {
426 this._editors = [];
427 this._stateListeners = [];
428 }
430 let existingIndex = this._editors.indexOf(editableNode.editor);
431 if (existingIndex == -1) {
432 let x = this._editors.length;
433 this._editors[x] = editableNode.editor;
434 this._stateListeners[x] = this._createStateListener();
435 this._editors[x].addEditActionListener(this);
436 this._editors[x].addDocumentStateListener(this._stateListeners[x]);
437 }
438 }
439 },
441 _getSelectionController: function(aWindow) {
442 // display: none iframes don't have a selection controller, see bug 493658
443 if (!aWindow.innerWidth || !aWindow.innerHeight)
444 return null;
446 // Yuck. See bug 138068.
447 let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
448 .getInterface(Ci.nsIWebNavigation)
449 .QueryInterface(Ci.nsIDocShell);
451 let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
452 .getInterface(Ci.nsISelectionDisplay)
453 .QueryInterface(Ci.nsISelectionController);
454 return controller;
455 },
457 /*
458 * For a given node, walk up it's parent chain, to try and find an
459 * editable node.
460 *
461 * @param aNode the node we want to check
462 * @returns the first node in the parent chain that is editable,
463 * null if there is no such node
464 */
465 _getEditableNode: function (aNode) {
466 while (aNode) {
467 if (aNode instanceof Ci.nsIDOMNSEditableElement)
468 return aNode.editor ? aNode : null;
470 aNode = aNode.parentNode;
471 }
472 return null;
473 },
475 /*
476 * Helper method to unhook listeners, remove cached editors
477 * and keep the relevant arrays in sync
478 *
479 * @param aIndex the index into the array of editors/state listeners
480 * we wish to remove
481 */
482 _unhookListenersAtIndex: function (aIndex) {
483 this._editors[aIndex].removeEditActionListener(this);
484 this._editors[aIndex]
485 .removeDocumentStateListener(this._stateListeners[aIndex]);
486 this._editors.splice(aIndex, 1);
487 this._stateListeners.splice(aIndex, 1);
488 if (!this._editors.length) {
489 delete this._editors;
490 delete this._stateListeners;
491 }
492 },
494 /*
495 * Remove ourselves as an nsIEditActionListener and
496 * nsIDocumentStateListener from a given cached editor
497 *
498 * @param aEditor the editor we no longer wish to listen to
499 */
500 _removeEditorListeners: function (aEditor) {
501 // aEditor is an editor that we listen to, so therefore must be
502 // cached. Find the index of this editor
503 let idx = this._editors.indexOf(aEditor);
504 if (idx == -1)
505 return;
506 // Now unhook ourselves, and remove our cached copy
507 this._unhookListenersAtIndex(idx);
508 },
510 /*
511 * nsIEditActionListener logic follows
512 *
513 * We implement this interface to allow us to catch the case where
514 * the findbar found a match in a HTML <input> or <textarea>. If the
515 * user adjusts the text in some way, it will no longer match, so we
516 * want to remove the highlight, rather than have it expand/contract
517 * when letters are added or removed.
518 */
520 /*
521 * Helper method used to check whether a selection intersects with
522 * some highlighting
523 *
524 * @param aSelectionRange the range from the selection to check
525 * @param aFindRange the highlighted range to check against
526 * @returns true if they intersect, false otherwise
527 */
528 _checkOverlap: function (aSelectionRange, aFindRange) {
529 // The ranges overlap if one of the following is true:
530 // 1) At least one of the endpoints of the deleted selection
531 // is in the find selection
532 // 2) At least one of the endpoints of the find selection
533 // is in the deleted selection
534 if (aFindRange.isPointInRange(aSelectionRange.startContainer,
535 aSelectionRange.startOffset))
536 return true;
537 if (aFindRange.isPointInRange(aSelectionRange.endContainer,
538 aSelectionRange.endOffset))
539 return true;
540 if (aSelectionRange.isPointInRange(aFindRange.startContainer,
541 aFindRange.startOffset))
542 return true;
543 if (aSelectionRange.isPointInRange(aFindRange.endContainer,
544 aFindRange.endOffset))
545 return true;
547 return false;
548 },
550 /*
551 * Helper method to determine if an edit occurred within a highlight
552 *
553 * @param aSelection the selection we wish to check
554 * @param aNode the node we want to check is contained in aSelection
555 * @param aOffset the offset into aNode that we want to check
556 * @returns the range containing (aNode, aOffset) or null if no ranges
557 * in the selection contain it
558 */
559 _findRange: function (aSelection, aNode, aOffset) {
560 let rangeCount = aSelection.rangeCount;
561 let rangeidx = 0;
562 let foundContainingRange = false;
563 let range = null;
565 // Check to see if this node is inside one of the selection's ranges
566 while (!foundContainingRange && rangeidx < rangeCount) {
567 range = aSelection.getRangeAt(rangeidx);
568 if (range.isPointInRange(aNode, aOffset)) {
569 foundContainingRange = true;
570 break;
571 }
572 rangeidx++;
573 }
575 if (foundContainingRange)
576 return range;
578 return null;
579 },
581 // Start of nsIWebProgressListener implementation.
583 onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
584 if (!aWebProgress.isTopLevel)
585 return;
587 // Avoid leaking if we change the page.
588 this._previousLink = null;
589 },
591 // Start of nsIEditActionListener implementations
593 WillDeleteText: function (aTextNode, aOffset, aLength) {
594 let editor = this._getEditableNode(aTextNode).editor;
595 let controller = editor.selectionController;
596 let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
597 let range = this._findRange(fSelection, aTextNode, aOffset);
599 if (range) {
600 // Don't remove the highlighting if the deleted text is at the
601 // end of the range
602 if (aTextNode != range.endContainer ||
603 aOffset != range.endOffset) {
604 // Text within the highlight is being removed - the text can
605 // no longer be a match, so remove the highlighting
606 fSelection.removeRange(range);
607 if (fSelection.rangeCount == 0)
608 this._removeEditorListeners(editor);
609 }
610 }
611 },
613 DidInsertText: function (aTextNode, aOffset, aString) {
614 let editor = this._getEditableNode(aTextNode).editor;
615 let controller = editor.selectionController;
616 let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
617 let range = this._findRange(fSelection, aTextNode, aOffset);
619 if (range) {
620 // If the text was inserted before the highlight
621 // adjust the highlight's bounds accordingly
622 if (aTextNode == range.startContainer &&
623 aOffset == range.startOffset) {
624 range.setStart(range.startContainer,
625 range.startOffset+aString.length);
626 } else if (aTextNode != range.endContainer ||
627 aOffset != range.endOffset) {
628 // The edit occurred within the highlight - any addition of text
629 // will result in the text no longer being a match,
630 // so remove the highlighting
631 fSelection.removeRange(range);
632 if (fSelection.rangeCount == 0)
633 this._removeEditorListeners(editor);
634 }
635 }
636 },
638 WillDeleteSelection: function (aSelection) {
639 let editor = this._getEditableNode(aSelection.getRangeAt(0)
640 .startContainer).editor;
641 let controller = editor.selectionController;
642 let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
644 let selectionIndex = 0;
645 let findSelectionIndex = 0;
646 let shouldDelete = {};
647 let numberOfDeletedSelections = 0;
648 let numberOfMatches = fSelection.rangeCount;
650 // We need to test if any ranges in the deleted selection (aSelection)
651 // are in any of the ranges of the find selection
652 // Usually both selections will only contain one range, however
653 // either may contain more than one.
655 for (let fIndex = 0; fIndex < numberOfMatches; fIndex++) {
656 shouldDelete[fIndex] = false;
657 let fRange = fSelection.getRangeAt(fIndex);
659 for (let index = 0; index < aSelection.rangeCount; index++) {
660 if (shouldDelete[fIndex])
661 continue;
663 let selRange = aSelection.getRangeAt(index);
664 let doesOverlap = this._checkOverlap(selRange, fRange);
665 if (doesOverlap) {
666 shouldDelete[fIndex] = true;
667 numberOfDeletedSelections++;
668 }
669 }
670 }
672 // OK, so now we know what matches (if any) are in the selection
673 // that is being deleted. Time to remove them.
674 if (numberOfDeletedSelections == 0)
675 return;
677 for (let i = numberOfMatches - 1; i >= 0; i--) {
678 if (shouldDelete[i])
679 fSelection.removeRange(fSelection.getRangeAt(i));
680 }
682 // Remove listeners if no more highlights left
683 if (fSelection.rangeCount == 0)
684 this._removeEditorListeners(editor);
685 },
687 /*
688 * nsIDocumentStateListener logic follows
689 *
690 * When attaching nsIEditActionListeners, there are no guarantees
691 * as to whether the findbar or the documents in the browser will get
692 * destructed first. This leads to the potential to either leak, or to
693 * hold on to a reference an editable element's editor for too long,
694 * preventing it from being destructed.
695 *
696 * However, when an editor's owning node is being destroyed, the editor
697 * sends out a DocumentWillBeDestroyed notification. We can use this to
698 * clean up our references to the object, to allow it to be destroyed in a
699 * timely fashion.
700 */
702 /*
703 * Unhook ourselves when one of our state listeners has been called.
704 * This can happen in 4 cases:
705 * 1) The document the editor belongs to is navigated away from, and
706 * the document is not being cached
707 *
708 * 2) The document the editor belongs to is expired from the cache
709 *
710 * 3) The tab containing the owning document is closed
711 *
712 * 4) The <input> or <textarea> that owns the editor is explicitly
713 * removed from the DOM
714 *
715 * @param the listener that was invoked
716 */
717 _onEditorDestruction: function (aListener) {
718 // First find the index of the editor the given listener listens to.
719 // The listeners and editors arrays must always be in sync.
720 // The listener will be in our array of cached listeners, as this
721 // method could not have been called otherwise.
722 let idx = 0;
723 while (this._stateListeners[idx] != aListener)
724 idx++;
726 // Unhook both listeners
727 this._unhookListenersAtIndex(idx);
728 },
730 /*
731 * Creates a unique document state listener for an editor.
732 *
733 * It is not possible to simply have the findbar implement the
734 * listener interface itself, as it wouldn't have sufficient information
735 * to work out which editor was being destroyed. Therefore, we create new
736 * listeners on the fly, and cache them in sync with the editors they
737 * listen to.
738 */
739 _createStateListener: function () {
740 return {
741 findbar: this,
743 QueryInterface: function(aIID) {
744 if (aIID.equals(Ci.nsIDocumentStateListener) ||
745 aIID.equals(Ci.nsISupports))
746 return this;
748 throw Components.results.NS_ERROR_NO_INTERFACE;
749 },
751 NotifyDocumentWillBeDestroyed: function() {
752 this.findbar._onEditorDestruction(this);
753 },
755 // Unimplemented
756 notifyDocumentCreated: function() {},
757 notifyDocumentStateChanged: function(aDirty) {}
758 };
759 },
761 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
762 Ci.nsISupportsWeakReference])
763 };