Bug 1669129 - [devtools] Enable devtools.overflow.debugging.enabled. r=jdescottes
[gecko.git] / toolkit / modules / Finder.jsm
blob4e1c599bb7adb180fbc8fcc7c366a12208a045e0
1 // vim: set ts=2 sw=2 sts=2 tw=80:
2 // This Source Code Form is subject to the terms of the Mozilla Public
3 // License, v. 2.0. If a copy of the MPL was not distributed with this
4 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 var EXPORTED_SYMBOLS = [
7   "Finder",
8   "GetClipboardSearchString",
9   "SetClipboardSearchString",
12 const { XPCOMUtils } = ChromeUtils.import(
13   "resource://gre/modules/XPCOMUtils.jsm"
15 const { Rect } = ChromeUtils.import("resource://gre/modules/Geometry.jsm");
16 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
18 ChromeUtils.defineModuleGetter(
19   this,
20   "BrowserUtils",
21   "resource://gre/modules/BrowserUtils.jsm"
23 ChromeUtils.defineModuleGetter(
24   this,
25   "FinderIterator",
26   "resource://gre/modules/FinderIterator.jsm"
29 XPCOMUtils.defineLazyServiceGetter(
30   this,
31   "Clipboard",
32   "@mozilla.org/widget/clipboard;1",
33   "nsIClipboard"
35 XPCOMUtils.defineLazyServiceGetter(
36   this,
37   "ClipboardHelper",
38   "@mozilla.org/widget/clipboardhelper;1",
39   "nsIClipboardHelper"
42 const kSelectionMaxLen = 150;
43 const kMatchesCountLimitPref = "accessibility.typeaheadfind.matchesCountLimit";
45 function Finder(docShell) {
46   this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(
47     Ci.nsITypeAheadFind
48   );
49   this._fastFind.init(docShell);
51   this._currentFoundRange = null;
52   this._docShell = docShell;
53   this._listeners = [];
54   this._previousLink = null;
55   this._searchString = null;
56   this._highlighter = null;
58   docShell
59     .QueryInterface(Ci.nsIInterfaceRequestor)
60     .getInterface(Ci.nsIWebProgress)
61     .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
62   docShell.domWindow.addEventListener(
63     "unload",
64     this.onLocationChange.bind(this, { isTopLevel: true })
65   );
68 Finder.prototype = {
69   get iterator() {
70     if (!this._iterator) {
71       this._iterator = new FinderIterator();
72     }
73     return this._iterator;
74   },
76   destroy() {
77     if (this._iterator) {
78       this._iterator.reset();
79     }
80     let window = this._getWindow();
81     if (this._highlighter && window) {
82       // if we clear all the references before we hide the highlights (in both
83       // highlighting modes), we simply can't use them to find the ranges we
84       // need to clear from the selection.
85       this._highlighter.hide(window);
86       this._highlighter.clear(window);
87     }
88     this.listeners = [];
89     this._docShell
90       .QueryInterface(Ci.nsIInterfaceRequestor)
91       .getInterface(Ci.nsIWebProgress)
92       .removeProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
93     this._listeners = [];
94     this._currentFoundRange = this._fastFind = this._docShell = this._previousLink = this._highlighter = null;
95   },
97   addResultListener(aListener) {
98     if (!this._listeners.includes(aListener)) {
99       this._listeners.push(aListener);
100     }
101   },
103   removeResultListener(aListener) {
104     this._listeners = this._listeners.filter(l => l != aListener);
105   },
107   _setResults(options, mode) {
108     if (typeof options.storeResult != "boolean") {
109       options.storeResult = true;
110     }
112     if (options.storeResult) {
113       this._searchString = options.searchString;
114       this.clipboardSearchString = options.searchString;
115     }
117     let foundLink = this._fastFind.foundLink;
118     let linkURL = null;
119     if (foundLink) {
120       linkURL = Services.textToSubURI.unEscapeURIForUI(foundLink.href);
121     }
123     options.linkURL = linkURL;
124     options.rect = this._getResultRect();
125     options.searchString = this._searchString;
127     this._outlineLink(options.drawOutline);
129     for (let l of this._listeners) {
130       try {
131         l.onFindResult(options);
132       } catch (ex) {}
133     }
134   },
136   get searchString() {
137     if (!this._searchString && this._fastFind.searchString) {
138       this._searchString = this._fastFind.searchString;
139     }
140     return this._searchString;
141   },
143   get clipboardSearchString() {
144     return GetClipboardSearchString(
145       this._getWindow().docShell.QueryInterface(Ci.nsILoadContext)
146     );
147   },
149   set clipboardSearchString(aSearchString) {
150     SetClipboardSearchString(aSearchString);
151   },
153   set caseSensitive(aSensitive) {
154     if (this._fastFind.caseSensitive === aSensitive) {
155       return;
156     }
157     this._fastFind.caseSensitive = aSensitive;
158     this.iterator.reset();
159   },
161   set matchDiacritics(aMatchDiacritics) {
162     if (this._fastFind.matchDiacritics === aMatchDiacritics) {
163       return;
164     }
165     this._fastFind.matchDiacritics = aMatchDiacritics;
166     this.iterator.reset();
167   },
169   set entireWord(aEntireWord) {
170     if (this._fastFind.entireWord === aEntireWord) {
171       return;
172     }
173     this._fastFind.entireWord = aEntireWord;
174     this.iterator.reset();
175   },
177   get highlighter() {
178     if (this._highlighter) {
179       return this._highlighter;
180     }
182     const { FinderHighlighter } = ChromeUtils.import(
183       "resource://gre/modules/FinderHighlighter.jsm"
184     );
185     return (this._highlighter = new FinderHighlighter(this));
186   },
188   get matchesCountLimit() {
189     if (typeof this._matchesCountLimit == "number") {
190       return this._matchesCountLimit;
191     }
193     this._matchesCountLimit =
194       Services.prefs.getIntPref(kMatchesCountLimitPref) || 0;
195     return this._matchesCountLimit;
196   },
198   _lastFindResult: null,
200   /**
201    * Used for normal search operations, highlights the first match.
202    * This method is used only for compatibility with non-remote browsers.
203    *
204    * @param aSearchString String to search for.
205    * @param aLinksOnly Only consider nodes that are links for the search.
206    * @param aDrawOutline Puts an outline around matched links.
207    */
208   fastFind(aSearchString, aLinksOnly, aDrawOutline) {
209     this._lastFindResult = this._fastFind.find(
210       aSearchString,
211       aLinksOnly,
212       Ci.nsITypeAheadFind.FIND_INITIAL,
213       false
214     );
215     let searchString = this._fastFind.searchString;
217     let results = {
218       searchString,
219       result: this._lastFindResult,
220       findBackwards: false,
221       findAgain: false,
222       drawOutline: aDrawOutline,
223       linksOnly: aLinksOnly,
224       useSubFrames: true,
225     };
227     this._setResults(results);
228     this.updateHighlightAndMatchCount(results);
230     return this._lastFindResult;
231   },
233   /**
234    * Repeat the previous search. Should only be called after a previous
235    * call to Finder.fastFind.
236    * This method is used only for compatibility with non-remote browsers.
237    *
238    * @param aSearchString String to search for.
239    * @param aFindBackwards Controls the search direction:
240    *    true: before current match, false: after current match.
241    * @param aLinksOnly Only consider nodes that are links for the search.
242    * @param aDrawOutline Puts an outline around matched links.
243    */
244   findAgain(aSearchString, aFindBackwards, aLinksOnly, aDrawOutline) {
245     let mode = aFindBackwards
246       ? Ci.nsITypeAheadFind.FIND_PREVIOUS
247       : Ci.nsITypeAheadFind.FIND_NEXT;
248     this._lastFindResult = this._fastFind.find(
249       aFindBackwards,
250       aLinksOnly,
251       mode,
252       false
253     );
254     let searchString = this._fastFind.searchString;
256     let results = {
257       searchString,
258       result: this._lastFindResult,
259       findBackwards: aFindBackwards,
260       findAgain: true,
261       drawOutline: aDrawOutline,
262       linksOnly: aLinksOnly,
263       useSubFrames: true,
264     };
265     this._setResults(results);
266     this.updateHighlightAndMatchCount(results);
268     return this._lastFindResult;
269   },
271   /**
272    * Used for normal search operations, highlights the first or
273    * subsequent match depending on the mode.
274    *
275    * Options are:
276    *  searchString String to search for.
277    *  findAgain True if this a find again operation.
278    *  mode Search mode from nsITypeAheadFind.
279    *  linksOnly Only consider nodes that are links for the search.
280    *  drawOutline Puts an outline around matched links.
281    *  useSubFrames True to iterate over subframes.
282    *  caseSensitive True for case sensitive searching.
283    *  entireWord True to match entire words.
284    *  matchDiacritics True to match diacritics.
285    */
286   find(options) {
287     this.caseSensitive = options.caseSensitive;
288     this.entireWord = options.entireWord;
289     this.matchDiacritics = options.matchDiacritics;
291     this._lastFindResult = this._fastFind.find(
292       options.searchString,
293       options.linksOnly,
294       options.mode,
295       !options.useSubFrames
296     );
297     let searchString = this._fastFind.searchString;
298     let results = {
299       searchString,
300       result: this._lastFindResult,
301       findBackwards:
302         options.mode == Ci.nsITypeAheadFind.FIND_PREVIOUS ||
303         options.mode == Ci.nsITypeAheadFind.FIND_LAST,
304       findAgain: options.findAgain,
305       drawOutline: options.drawOutline,
306       linksOnly: options.linksOnly,
307       entireWord: this._fastFind.entireWord,
308       useSubFrames: options.useSubFrames,
309     };
310     this._setResults(results, options.mode);
311     return new Promise(resolve => resolve(results));
312   },
314   /**
315    * Forcibly set the search string of the find clipboard to the currently
316    * selected text in the window, on supported platforms (i.e. OSX).
317    */
318   setSearchStringToSelection() {
319     let searchInfo = this.getActiveSelectionText();
321     // If an empty string is returned or a subframe is focused, don't
322     // assign the search string.
323     if (searchInfo.selectedText) {
324       this.clipboardSearchString = searchInfo.selectedText;
325     }
327     return searchInfo;
328   },
330   async highlight(aHighlight, aWord, aLinksOnly, aUseSubFrames = true) {
331     return this.highlighter.highlight(
332       aHighlight,
333       aWord,
334       aLinksOnly,
335       false,
336       aUseSubFrames
337     );
338   },
340   async updateHighlightAndMatchCount(aArgs) {
341     this._lastFindResult = aArgs;
343     if (
344       !this.iterator.continueRunning({
345         caseSensitive: this._fastFind.caseSensitive,
346         entireWord: this._fastFind.entireWord,
347         linksOnly: aArgs.linksOnly,
348         matchDiacritics: this._fastFind.matchDiacritics,
349         word: aArgs.searchString,
350         useSubFrames: aArgs.useSubFrames,
351       })
352     ) {
353       this.iterator.stop();
354     }
356     let highlightPromise = this.highlighter.update(
357       aArgs,
358       aArgs.useSubFrames ? false : aArgs.foundInThisFrame
359     );
360     let matchCountPromise = this.requestMatchesCount(
361       aArgs.searchString,
362       aArgs.linksOnly,
363       aArgs.useSubFrames
364     );
366     let results = await Promise.all([highlightPromise, matchCountPromise]);
367     if (results[1]) {
368       return Object.assign(results[1], results[0]);
369     } else if (results[0]) {
370       return results[0];
371     }
373     return null;
374   },
376   getInitialSelection() {
377     this._getWindow().setTimeout(() => {
378       let initialSelection = this.getActiveSelectionText().selectedText;
379       for (let l of this._listeners) {
380         try {
381           l.onCurrentSelection(initialSelection, true);
382         } catch (ex) {}
383       }
384     }, 0);
385   },
387   getActiveSelectionText() {
388     let focusedWindow = {};
389     let focusedElement = Services.focus.getFocusedElementForWindow(
390       this._getWindow(),
391       true,
392       focusedWindow
393     );
394     focusedWindow = focusedWindow.value;
396     let selText;
398     // If this is a remote subframe, return an empty string but
399     // indiciate which browsing context was focused.
400     if (
401       focusedElement &&
402       "frameLoader" in focusedElement &&
403       focusedElement.browsingContext instanceof BrowsingContext
404     ) {
405       return {
406         focusedChildBrowserContextId: focusedElement.browsingContext.id,
407         selectedText: "",
408       };
409     }
411     if (focusedElement && focusedElement.editor) {
412       // The user may have a selection in an input or textarea.
413       selText = focusedElement.editor.selectionController
414         .getSelection(Ci.nsISelectionController.SELECTION_NORMAL)
415         .toString();
416     } else {
417       // Look for any selected text on the actual page.
418       selText = focusedWindow.getSelection().toString();
419     }
421     if (!selText) {
422       return { selectedText: "" };
423     }
425     // Process our text to get rid of unwanted characters.
426     selText = selText.trim().replace(/\s+/g, " ");
427     let truncLength = kSelectionMaxLen;
428     if (selText.length > truncLength) {
429       let truncChar = selText.charAt(truncLength).charCodeAt(0);
430       if (truncChar >= 0xdc00 && truncChar <= 0xdfff) {
431         truncLength++;
432       }
433       selText = selText.substr(0, truncLength);
434     }
436     return { selectedText: selText };
437   },
439   enableSelection() {
440     this._fastFind.setSelectionModeAndRepaint(
441       Ci.nsISelectionController.SELECTION_ON
442     );
443     this._restoreOriginalOutline();
444   },
446   removeSelection(keepHighlight) {
447     this._fastFind.collapseSelection();
448     this.enableSelection();
449     let window = this._getWindow();
450     if (keepHighlight) {
451       this.highlighter.clearCurrentOutline(window);
452     } else {
453       this.highlighter.clear(window);
454     }
455   },
457   focusContent() {
458     // Allow Finder listeners to cancel focusing the content.
459     for (let l of this._listeners) {
460       try {
461         if ("shouldFocusContent" in l && !l.shouldFocusContent()) {
462           return;
463         }
464       } catch (ex) {
465         Cu.reportError(ex);
466       }
467     }
469     let fastFind = this._fastFind;
470     try {
471       // Try to find the best possible match that should receive focus and
472       // block scrolling on focus since find already scrolls. Further
473       // scrolling is due to user action, so don't override this.
474       if (fastFind.foundLink) {
475         Services.focus.setFocus(
476           fastFind.foundLink,
477           Services.focus.FLAG_NOSCROLL
478         );
479       } else if (fastFind.foundEditable) {
480         Services.focus.setFocus(
481           fastFind.foundEditable,
482           Services.focus.FLAG_NOSCROLL
483         );
484         fastFind.collapseSelection();
485       } else {
486         this._getWindow().focus();
487       }
488     } catch (e) {}
489   },
491   onFindbarClose() {
492     this.enableSelection();
493     this.highlighter.highlight(false);
494     this.iterator.reset();
495     BrowserUtils.trackToolbarVisibility(this._docShell, "findbar", false);
496   },
498   onFindbarOpen() {
499     BrowserUtils.trackToolbarVisibility(this._docShell, "findbar", true);
500   },
502   onModalHighlightChange(useModalHighlight) {
503     if (this._highlighter) {
504       this._highlighter.onModalHighlightChange(useModalHighlight);
505     }
506   },
508   onHighlightAllChange(highlightAll) {
509     if (this._highlighter) {
510       this._highlighter.onHighlightAllChange(highlightAll);
511     }
512     if (this._iterator) {
513       this._iterator.reset();
514     }
515   },
517   keyPress(aEvent) {
518     let controller = this._getSelectionController(this._getWindow());
520     switch (aEvent.keyCode) {
521       case aEvent.DOM_VK_RETURN:
522         if (this._fastFind.foundLink) {
523           let view = this._fastFind.foundLink.ownerGlobal;
524           this._fastFind.foundLink.dispatchEvent(
525             new view.MouseEvent("click", {
526               view,
527               cancelable: true,
528               bubbles: true,
529               ctrlKey: aEvent.ctrlKey,
530               altKey: aEvent.altKey,
531               shiftKey: aEvent.shiftKey,
532               metaKey: aEvent.metaKey,
533             })
534           );
535         }
536         break;
537       case aEvent.DOM_VK_TAB:
538         let direction = Services.focus.MOVEFOCUS_FORWARD;
539         if (aEvent.shiftKey) {
540           direction = Services.focus.MOVEFOCUS_BACKWARD;
541         }
542         Services.focus.moveFocus(this._getWindow(), null, direction, 0);
543         break;
544       case aEvent.DOM_VK_PAGE_UP:
545         controller.scrollPage(false);
546         break;
547       case aEvent.DOM_VK_PAGE_DOWN:
548         controller.scrollPage(true);
549         break;
550       case aEvent.DOM_VK_UP:
551         controller.scrollLine(false);
552         break;
553       case aEvent.DOM_VK_DOWN:
554         controller.scrollLine(true);
555         break;
556     }
557   },
559   _notifyMatchesCount(aWord, result = this._currentMatchesCountResult) {
560     // The `_currentFound` property is only used for internal bookkeeping.
561     delete result._currentFound;
562     result.searchString = aWord;
563     result.limit = this.matchesCountLimit;
564     if (result.total == result.limit) {
565       result.total = -1;
566     }
568     for (let l of this._listeners) {
569       try {
570         l.onMatchesCountResult(result);
571       } catch (ex) {}
572     }
574     this._currentMatchesCountResult = null;
575     return result;
576   },
578   async requestMatchesCount(aWord, aLinksOnly, aUseSubFrames = true) {
579     if (
580       this._lastFindResult == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
581       this.searchString == "" ||
582       !aWord ||
583       !this.matchesCountLimit
584     ) {
585       return this._notifyMatchesCount(aWord, {
586         total: 0,
587         current: 0,
588       });
589     }
591     this._currentFoundRange = this._fastFind.getFoundRange();
593     let params = {
594       caseSensitive: this._fastFind.caseSensitive,
595       entireWord: this._fastFind.entireWord,
596       linksOnly: aLinksOnly,
597       matchDiacritics: this._fastFind.matchDiacritics,
598       word: aWord,
599       useSubFrames: aUseSubFrames,
600     };
601     if (!this.iterator.continueRunning(params)) {
602       this.iterator.stop();
603     }
605     await this.iterator.start(
606       Object.assign(params, {
607         finder: this,
608         limit: this.matchesCountLimit,
609         listener: this,
610         useCache: true,
611         useSubFrames: aUseSubFrames,
612       })
613     );
615     // Without a valid result, there's nothing to notify about. This happens
616     // when the iterator was started before and won the race.
617     if (!this._currentMatchesCountResult) {
618       return null;
619     }
621     return this._notifyMatchesCount(aWord);
622   },
624   // FinderIterator listener implementation
626   onIteratorRangeFound(range) {
627     let result = this._currentMatchesCountResult;
628     if (!result) {
629       return;
630     }
632     ++result.total;
633     if (!result._currentFound) {
634       ++result.current;
635       result._currentFound =
636         this._currentFoundRange &&
637         range.startContainer == this._currentFoundRange.startContainer &&
638         range.startOffset == this._currentFoundRange.startOffset &&
639         range.endContainer == this._currentFoundRange.endContainer &&
640         range.endOffset == this._currentFoundRange.endOffset;
641     }
642   },
644   onIteratorReset() {},
646   onIteratorRestart({ word, linksOnly, useSubFrames }) {
647     this.requestMatchesCount(word, linksOnly, useSubFrames);
648   },
650   onIteratorStart() {
651     this._currentMatchesCountResult = {
652       total: 0,
653       current: 0,
654       _currentFound: false,
655     };
656   },
658   _getWindow() {
659     if (!this._docShell) {
660       return null;
661     }
662     return this._docShell.domWindow;
663   },
665   /**
666    * Get the bounding selection rect in CSS px relative to the origin of the
667    * top-level content document.
668    */
669   _getResultRect() {
670     let topWin = this._getWindow();
671     let win = this._fastFind.currentWindow;
672     if (!win) {
673       return null;
674     }
676     let selection = win.getSelection();
677     if (!selection.rangeCount || selection.isCollapsed) {
678       // The selection can be into an input or a textarea element.
679       let nodes = win.document.querySelectorAll("input, textarea");
680       for (let node of nodes) {
681         if (node.editor) {
682           try {
683             let sc = node.editor.selectionController;
684             selection = sc.getSelection(
685               Ci.nsISelectionController.SELECTION_NORMAL
686             );
687             if (selection.rangeCount && !selection.isCollapsed) {
688               break;
689             }
690           } catch (e) {
691             // If this textarea is hidden, then its selection controller might
692             // not be intialized. Ignore the failure.
693           }
694         }
695       }
696     }
698     if (!selection.rangeCount || selection.isCollapsed) {
699       return null;
700     }
702     let utils = topWin.windowUtils;
704     let scrollX = {},
705       scrollY = {};
706     utils.getScrollXY(false, scrollX, scrollY);
708     for (let frame = win; frame != topWin; frame = frame.parent) {
709       let rect = frame.frameElement.getBoundingClientRect();
710       let left = frame.getComputedStyle(frame.frameElement).borderLeftWidth;
711       let top = frame.getComputedStyle(frame.frameElement).borderTopWidth;
712       scrollX.value += rect.left + parseInt(left, 10);
713       scrollY.value += rect.top + parseInt(top, 10);
714     }
715     let rect = Rect.fromRect(selection.getRangeAt(0).getBoundingClientRect());
716     return rect.translate(scrollX.value, scrollY.value);
717   },
719   _outlineLink(aDrawOutline) {
720     let foundLink = this._fastFind.foundLink;
722     // Optimization: We are drawing outlines and we matched
723     // the same link before, so don't duplicate work.
724     if (foundLink == this._previousLink && aDrawOutline) {
725       return;
726     }
728     this._restoreOriginalOutline();
730     if (foundLink && aDrawOutline) {
731       // Backup original outline
732       this._tmpOutline = foundLink.style.outline;
733       this._tmpOutlineOffset = foundLink.style.outlineOffset;
735       // Draw pseudo focus rect
736       // XXX Should we change the following style for FAYT pseudo focus?
737       // XXX Shouldn't we change default design if outline is visible
738       //     already?
739       // Don't set the outline-color, we should always use initial value.
740       foundLink.style.outline = "1px dotted";
741       foundLink.style.outlineOffset = "0";
743       this._previousLink = foundLink;
744     }
745   },
747   _restoreOriginalOutline() {
748     // Removes the outline around the last found link.
749     if (this._previousLink) {
750       this._previousLink.style.outline = this._tmpOutline;
751       this._previousLink.style.outlineOffset = this._tmpOutlineOffset;
752       this._previousLink = null;
753     }
754   },
756   _getSelectionController(aWindow) {
757     // display: none iframes don't have a selection controller, see bug 493658
758     try {
759       if (!aWindow.innerWidth || !aWindow.innerHeight) {
760         return null;
761       }
762     } catch (e) {
763       // If getting innerWidth or innerHeight throws, we can't get a selection
764       // controller.
765       return null;
766     }
768     // Yuck. See bug 138068.
769     let docShell = aWindow.docShell;
771     let controller = docShell
772       .QueryInterface(Ci.nsIInterfaceRequestor)
773       .getInterface(Ci.nsISelectionDisplay)
774       .QueryInterface(Ci.nsISelectionController);
775     return controller;
776   },
778   // Start of nsIWebProgressListener implementation.
780   onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
781     if (!aWebProgress.isTopLevel) {
782       return;
783     }
784     // Ignore events that don't change the document.
785     if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
786       return;
787     }
789     // Avoid leaking if we change the page.
790     this._lastFindResult = this._previousLink = this._currentFoundRange = null;
791     this.highlighter.onLocationChange();
792     this.iterator.reset();
793   },
795   QueryInterface: ChromeUtils.generateQI([
796     "nsIWebProgressListener",
797     "nsISupportsWeakReference",
798   ]),
801 function GetClipboardSearchString(aLoadContext) {
802   let searchString = "";
803   if (!Clipboard.supportsFindClipboard()) {
804     return searchString;
805   }
807   try {
808     let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
809       Ci.nsITransferable
810     );
811     trans.init(aLoadContext);
812     trans.addDataFlavor("text/unicode");
814     Clipboard.getData(trans, Ci.nsIClipboard.kFindClipboard);
816     let data = {};
817     trans.getTransferData("text/unicode", data);
818     if (data.value) {
819       data = data.value.QueryInterface(Ci.nsISupportsString);
820       searchString = data.toString();
821     }
822   } catch (ex) {}
824   return searchString;
827 function SetClipboardSearchString(aSearchString) {
828   if (!aSearchString || !Clipboard.supportsFindClipboard()) {
829     return;
830   }
832   ClipboardHelper.copyStringToClipboard(
833     aSearchString,
834     Ci.nsIClipboard.kFindClipboard
835   );