Bug 1845599 - Skip tests for private identifiers in decorators; r=mgaudet
[gecko.git] / toolkit / modules / Finder.sys.mjs
blob0b54b2caad454f57f9393995522fab9de5ed4fe7
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 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
8 import { Rect } from "resource://gre/modules/Geometry.sys.mjs";
10 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
12 const lazy = {};
14 ChromeUtils.defineESModuleGetters(lazy, {
15   FinderIterator: "resource://gre/modules/FinderIterator.sys.mjs",
16   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
17 });
19 XPCOMUtils.defineLazyServiceGetter(
20   lazy,
21   "ClipboardHelper",
22   "@mozilla.org/widget/clipboardhelper;1",
23   "nsIClipboardHelper"
26 const kSelectionMaxLen = 150;
27 const kMatchesCountLimitPref = "accessibility.typeaheadfind.matchesCountLimit";
29 const activeFinderRoots = new WeakSet();
31 export function Finder(docShell) {
32   this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(
33     Ci.nsITypeAheadFind
34   );
35   this._fastFind.init(docShell);
37   this._currentFoundRange = null;
38   this._docShell = docShell;
39   this._listeners = [];
40   this._previousLink = null;
41   this._searchString = null;
42   this._highlighter = null;
44   docShell
45     .QueryInterface(Ci.nsIInterfaceRequestor)
46     .getInterface(Ci.nsIWebProgress)
47     .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
48   docShell.domWindow.addEventListener(
49     "unload",
50     this.onLocationChange.bind(this, { isTopLevel: true })
51   );
54 Finder.isFindbarVisible = function (docShell) {
55   return activeFinderRoots.has(docShell.browsingContext.top);
58 Finder.prototype = {
59   get iterator() {
60     if (!this._iterator) {
61       this._iterator = new lazy.FinderIterator();
62     }
63     return this._iterator;
64   },
66   destroy() {
67     if (this._iterator) {
68       this._iterator.reset();
69     }
70     let window = this._getWindow();
71     if (this._highlighter && window) {
72       // if we clear all the references before we hide the highlights (in both
73       // highlighting modes), we simply can't use them to find the ranges we
74       // need to clear from the selection.
75       this._highlighter.hide(window);
76       this._highlighter.clear(window);
77       this.highlighter.removeScrollMarks();
78     }
79     this.listeners = [];
80     this._docShell
81       .QueryInterface(Ci.nsIInterfaceRequestor)
82       .getInterface(Ci.nsIWebProgress)
83       .removeProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
84     this._listeners = [];
85     this._currentFoundRange =
86       this._fastFind =
87       this._docShell =
88       this._previousLink =
89       this._highlighter =
90         null;
91   },
93   addResultListener(aListener) {
94     if (!this._listeners.includes(aListener)) {
95       this._listeners.push(aListener);
96     }
97   },
99   removeResultListener(aListener) {
100     this._listeners = this._listeners.filter(l => l != aListener);
101   },
103   _setResults(options, mode) {
104     if (typeof options.storeResult != "boolean") {
105       options.storeResult = true;
106     }
108     if (options.storeResult) {
109       this._searchString = options.searchString;
110       this.clipboardSearchString = options.searchString;
111     }
113     let foundLink = this._fastFind.foundLink;
114     let linkURL = null;
115     if (foundLink) {
116       linkURL = Services.textToSubURI.unEscapeURIForUI(foundLink.href);
117     }
119     options.linkURL = linkURL;
120     options.rect = this._getResultRect();
121     options.searchString = this._searchString;
123     this._outlineLink(options.drawOutline);
125     for (let l of this._listeners) {
126       try {
127         l.onFindResult(options);
128       } catch (ex) {}
129     }
130   },
132   get searchString() {
133     if (!this._searchString && this._fastFind.searchString) {
134       this._searchString = this._fastFind.searchString;
135     }
136     return this._searchString;
137   },
139   get clipboardSearchString() {
140     return GetClipboardSearchString(
141       this._getWindow().docShell.QueryInterface(Ci.nsILoadContext)
142     );
143   },
145   set clipboardSearchString(aSearchString) {
146     if (!lazy.PrivateBrowsingUtils.isContentWindowPrivate(this._getWindow())) {
147       SetClipboardSearchString(aSearchString);
148     }
149   },
151   set caseSensitive(aSensitive) {
152     if (this._fastFind.caseSensitive === aSensitive) {
153       return;
154     }
155     this._fastFind.caseSensitive = aSensitive;
156     this.iterator.reset();
157   },
159   set matchDiacritics(aMatchDiacritics) {
160     if (this._fastFind.matchDiacritics === aMatchDiacritics) {
161       return;
162     }
163     this._fastFind.matchDiacritics = aMatchDiacritics;
164     this.iterator.reset();
165   },
167   set entireWord(aEntireWord) {
168     if (this._fastFind.entireWord === aEntireWord) {
169       return;
170     }
171     this._fastFind.entireWord = aEntireWord;
172     this.iterator.reset();
173   },
175   get highlighter() {
176     if (this._highlighter) {
177       return this._highlighter;
178     }
180     const { FinderHighlighter } = ChromeUtils.importESModule(
181       "resource://gre/modules/FinderHighlighter.sys.mjs"
182     );
183     return (this._highlighter = new FinderHighlighter(this));
184   },
186   get matchesCountLimit() {
187     if (typeof this._matchesCountLimit == "number") {
188       return this._matchesCountLimit;
189     }
191     this._matchesCountLimit =
192       Services.prefs.getIntPref(kMatchesCountLimitPref) || 0;
193     return this._matchesCountLimit;
194   },
196   _lastFindResult: null,
198   /**
199    * Used for normal search operations, highlights the first match.
200    * This method is used only for compatibility with non-remote browsers.
201    *
202    * @param aSearchString String to search for.
203    * @param aLinksOnly Only consider nodes that are links for the search.
204    * @param aDrawOutline Puts an outline around matched links.
205    */
206   fastFind(aSearchString, aLinksOnly, aDrawOutline) {
207     this._lastFindResult = this._fastFind.find(
208       aSearchString,
209       aLinksOnly,
210       Ci.nsITypeAheadFind.FIND_INITIAL,
211       false
212     );
213     let searchString = this._fastFind.searchString;
215     let results = {
216       searchString,
217       result: this._lastFindResult,
218       findBackwards: false,
219       findAgain: false,
220       drawOutline: aDrawOutline,
221       linksOnly: aLinksOnly,
222       useSubFrames: true,
223     };
225     this._setResults(results);
226     this.updateHighlightAndMatchCount(results);
228     return this._lastFindResult;
229   },
231   /**
232    * Repeat the previous search. Should only be called after a previous
233    * call to Finder.fastFind.
234    * This method is used only for compatibility with non-remote browsers.
235    *
236    * @param aSearchString String to search for.
237    * @param aFindBackwards Controls the search direction:
238    *    true: before current match, false: after current match.
239    * @param aLinksOnly Only consider nodes that are links for the search.
240    * @param aDrawOutline Puts an outline around matched links.
241    */
242   findAgain(aSearchString, aFindBackwards, aLinksOnly, aDrawOutline) {
243     let mode = aFindBackwards
244       ? Ci.nsITypeAheadFind.FIND_PREVIOUS
245       : Ci.nsITypeAheadFind.FIND_NEXT;
246     this._lastFindResult = this._fastFind.find(
247       aFindBackwards,
248       aLinksOnly,
249       mode,
250       false
251     );
252     let searchString = this._fastFind.searchString;
254     let results = {
255       searchString,
256       result: this._lastFindResult,
257       findBackwards: aFindBackwards,
258       findAgain: true,
259       drawOutline: aDrawOutline,
260       linksOnly: aLinksOnly,
261       useSubFrames: true,
262     };
263     this._setResults(results);
264     this.updateHighlightAndMatchCount(results);
266     return this._lastFindResult;
267   },
269   /**
270    * Used for normal search operations, highlights the first or
271    * subsequent match depending on the mode.
272    *
273    * Options are:
274    *  searchString String to search for.
275    *  findAgain True if this a find again operation.
276    *  mode Search mode from nsITypeAheadFind.
277    *  linksOnly Only consider nodes that are links for the search.
278    *  drawOutline Puts an outline around matched links.
279    *  useSubFrames True to iterate over subframes.
280    *  caseSensitive True for case sensitive searching.
281    *  entireWord True to match entire words.
282    *  matchDiacritics True to match diacritics.
283    */
284   find(options) {
285     this.caseSensitive = options.caseSensitive;
286     this.entireWord = options.entireWord;
287     this.matchDiacritics = options.matchDiacritics;
289     this._lastFindResult = this._fastFind.find(
290       options.searchString,
291       options.linksOnly,
292       options.mode,
293       !options.useSubFrames
294     );
295     let searchString = this._fastFind.searchString;
296     let results = {
297       searchString,
298       result: this._lastFindResult,
299       findBackwards:
300         options.mode == Ci.nsITypeAheadFind.FIND_PREVIOUS ||
301         options.mode == Ci.nsITypeAheadFind.FIND_LAST,
302       findAgain: options.findAgain,
303       drawOutline: options.drawOutline,
304       linksOnly: options.linksOnly,
305       entireWord: this._fastFind.entireWord,
306       useSubFrames: options.useSubFrames,
307     };
308     this._setResults(results, options.mode);
309     return new Promise(resolve => resolve(results));
310   },
312   /**
313    * Forcibly set the search string of the find clipboard to the currently
314    * selected text in the window, on supported platforms (i.e. OSX).
315    */
316   setSearchStringToSelection() {
317     let searchInfo = this.getActiveSelectionText();
319     // If an empty string is returned or a subframe is focused, don't
320     // assign the search string.
321     if (searchInfo.selectedText) {
322       this.clipboardSearchString = searchInfo.selectedText;
323     }
325     return searchInfo;
326   },
328   async highlight(aHighlight, aWord, aLinksOnly, aUseSubFrames = true) {
329     return this.highlighter.highlight(
330       aHighlight,
331       aWord,
332       aLinksOnly,
333       false,
334       aUseSubFrames
335     );
336   },
338   async updateHighlightAndMatchCount(aArgs) {
339     this._lastFindResult = aArgs;
341     if (
342       !this.iterator.continueRunning({
343         caseSensitive: this._fastFind.caseSensitive,
344         entireWord: this._fastFind.entireWord,
345         linksOnly: aArgs.linksOnly,
346         matchDiacritics: this._fastFind.matchDiacritics,
347         word: aArgs.searchString,
348         useSubFrames: aArgs.useSubFrames,
349       })
350     ) {
351       this.iterator.stop();
352     }
354     let highlightPromise = this.highlighter.update(
355       aArgs,
356       aArgs.useSubFrames ? false : aArgs.foundInThisFrame
357     );
358     let matchCountPromise = this.requestMatchesCount(
359       aArgs.searchString,
360       aArgs.linksOnly,
361       aArgs.useSubFrames
362     );
364     let results = await Promise.all([highlightPromise, matchCountPromise]);
366     this.highlighter.updateScrollMarks();
368     if (results[1]) {
369       return Object.assign(results[1], results[0]);
370     } else if (results[0]) {
371       return results[0];
372     }
374     return null;
375   },
377   getInitialSelection() {
378     let initialSelection = this.getActiveSelectionText().selectedText;
379     this._getWindow().setTimeout(() => {
380       for (let l of this._listeners) {
381         try {
382           l.onCurrentSelection(initialSelection, true);
383         } catch (ex) {}
384       }
385     }, 0);
386   },
388   getActiveSelectionText() {
389     let focusedWindow = {};
390     let focusedElement = Services.focus.getFocusedElementForWindow(
391       this._getWindow(),
392       true,
393       focusedWindow
394     );
395     focusedWindow = focusedWindow.value;
397     let selText;
399     // If this is a remote subframe, return an empty string but
400     // indiciate which browsing context was focused.
401     if (
402       focusedElement &&
403       "frameLoader" in focusedElement &&
404       BrowsingContext.isInstance(focusedElement.browsingContext)
405     ) {
406       return {
407         focusedChildBrowserContextId: focusedElement.browsingContext.id,
408         selectedText: "",
409       };
410     }
412     if (focusedElement && focusedElement.editor) {
413       // The user may have a selection in an input or textarea.
414       selText = focusedElement.editor.selectionController
415         .getSelection(Ci.nsISelectionController.SELECTION_NORMAL)
416         .toString();
417     } else {
418       // Look for any selected text on the actual page.
419       selText = focusedWindow.getSelection().toString();
420     }
422     if (!selText) {
423       return { selectedText: "" };
424     }
426     // Process our text to get rid of unwanted characters.
427     selText = selText.trim().replace(/\s+/g, " ");
428     let truncLength = kSelectionMaxLen;
429     if (selText.length > truncLength) {
430       let truncChar = selText.charAt(truncLength).charCodeAt(0);
431       if (truncChar >= 0xdc00 && truncChar <= 0xdfff) {
432         truncLength++;
433       }
434       selText = selText.substr(0, truncLength);
435     }
437     return { selectedText: selText };
438   },
440   enableSelection() {
441     this._fastFind.setSelectionModeAndRepaint(
442       Ci.nsISelectionController.SELECTION_ON
443     );
444     this._restoreOriginalOutline();
445   },
447   removeSelection(keepHighlight) {
448     this._fastFind.collapseSelection();
449     this.enableSelection();
450     let window = this._getWindow();
451     if (keepHighlight) {
452       this.highlighter.clearCurrentOutline(window);
453     } else {
454       this.highlighter.clear(window);
455       this.highlighter.removeScrollMarks();
456     }
457   },
459   focusContent() {
460     // Allow Finder listeners to cancel focusing the content.
461     for (let l of this._listeners) {
462       try {
463         if ("shouldFocusContent" in l && !l.shouldFocusContent()) {
464           return;
465         }
466       } catch (ex) {
467         console.error(ex);
468       }
469     }
471     let fastFind = this._fastFind;
472     try {
473       // Try to find the best possible match that should receive focus and
474       // block scrolling on focus since find already scrolls. Further
475       // scrolling is due to user action, so don't override this.
476       if (fastFind.foundLink) {
477         Services.focus.setFocus(
478           fastFind.foundLink,
479           Services.focus.FLAG_NOSCROLL
480         );
481       } else if (fastFind.foundEditable) {
482         Services.focus.setFocus(
483           fastFind.foundEditable,
484           Services.focus.FLAG_NOSCROLL
485         );
486         fastFind.collapseSelection();
487       } else {
488         this._getWindow().focus();
489       }
490     } catch (e) {}
491   },
493   onFindbarClose() {
494     this.enableSelection();
495     this.highlighter.highlight(false);
496     this.highlighter.removeScrollMarks();
497     this.iterator.reset();
498     activeFinderRoots.delete(this._docShell.browsingContext.top);
499   },
501   onFindbarOpen() {
502     activeFinderRoots.add(this._docShell.browsingContext.top);
503   },
505   onModalHighlightChange(useModalHighlight) {
506     if (this._highlighter) {
507       this._highlighter.onModalHighlightChange(useModalHighlight);
508     }
509   },
511   onHighlightAllChange(highlightAll) {
512     if (this._highlighter) {
513       this._highlighter.onHighlightAllChange(highlightAll);
514     }
515     if (this._iterator) {
516       this._iterator.reset();
517     }
518   },
520   keyPress(aEvent) {
521     let controller = this._getSelectionController(this._getWindow());
522     let accelKeyPressed =
523       AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;
525     switch (aEvent.keyCode) {
526       case aEvent.DOM_VK_RETURN:
527         if (this._fastFind.foundLink) {
528           let view = this._fastFind.foundLink.ownerGlobal;
529           this._fastFind.foundLink.dispatchEvent(
530             new view.MouseEvent("click", {
531               view,
532               cancelable: true,
533               bubbles: true,
534               ctrlKey: aEvent.ctrlKey,
535               altKey: aEvent.altKey,
536               shiftKey: aEvent.shiftKey,
537               metaKey: aEvent.metaKey,
538             })
539           );
540         }
541         break;
542       case aEvent.DOM_VK_TAB:
543         let direction = Services.focus.MOVEFOCUS_FORWARD;
544         if (aEvent.shiftKey) {
545           direction = Services.focus.MOVEFOCUS_BACKWARD;
546         }
547         Services.focus.moveFocus(this._getWindow(), null, direction, 0);
548         break;
549       case aEvent.DOM_VK_PAGE_UP:
550         controller.scrollPage(false);
551         break;
552       case aEvent.DOM_VK_PAGE_DOWN:
553         controller.scrollPage(true);
554         break;
555       case aEvent.DOM_VK_UP:
556         if (accelKeyPressed) {
557           controller.completeScroll(false);
558         } else {
559           controller.scrollLine(false);
560         }
561         break;
562       case aEvent.DOM_VK_DOWN:
563         if (accelKeyPressed) {
564           controller.completeScroll(true);
565         } else {
566           controller.scrollLine(true);
567         }
568         break;
569     }
570   },
572   _notifyMatchesCount(aWord, result = this._currentMatchesCountResult) {
573     // The `_currentFound` property is only used for internal bookkeeping.
574     delete result._currentFound;
575     result.searchString = aWord;
576     result.limit = this.matchesCountLimit;
577     if (result.total == result.limit) {
578       result.total = -1;
579     }
581     for (let l of this._listeners) {
582       try {
583         l.onMatchesCountResult(result);
584       } catch (ex) {}
585     }
587     this._currentMatchesCountResult = null;
588     return result;
589   },
591   async requestMatchesCount(aWord, aLinksOnly, aUseSubFrames = true) {
592     if (
593       this._lastFindResult == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
594       this.searchString == "" ||
595       !aWord ||
596       !this.matchesCountLimit
597     ) {
598       return this._notifyMatchesCount(aWord, {
599         total: 0,
600         current: 0,
601       });
602     }
604     this._currentFoundRange = this._fastFind.getFoundRange();
606     let params = {
607       caseSensitive: this._fastFind.caseSensitive,
608       entireWord: this._fastFind.entireWord,
609       linksOnly: aLinksOnly,
610       matchDiacritics: this._fastFind.matchDiacritics,
611       word: aWord,
612       useSubFrames: aUseSubFrames,
613     };
614     if (!this.iterator.continueRunning(params)) {
615       this.iterator.stop();
616     }
618     await this.iterator.start(
619       Object.assign(params, {
620         finder: this,
621         limit: this.matchesCountLimit,
622         listener: this,
623         useCache: true,
624         useSubFrames: aUseSubFrames,
625       })
626     );
628     // Without a valid result, there's nothing to notify about. This happens
629     // when the iterator was started before and won the race.
630     if (!this._currentMatchesCountResult) {
631       return null;
632     }
634     return this._notifyMatchesCount(aWord);
635   },
637   // FinderIterator listener implementation
639   onIteratorRangeFound(range) {
640     let result = this._currentMatchesCountResult;
641     if (!result) {
642       return;
643     }
645     ++result.total;
646     if (!result._currentFound) {
647       ++result.current;
648       result._currentFound =
649         this._currentFoundRange &&
650         range.startContainer == this._currentFoundRange.startContainer &&
651         range.startOffset == this._currentFoundRange.startOffset &&
652         range.endContainer == this._currentFoundRange.endContainer &&
653         range.endOffset == this._currentFoundRange.endOffset;
654     }
655   },
657   onIteratorReset() {},
659   onIteratorRestart({ word, linksOnly, useSubFrames }) {
660     this.requestMatchesCount(word, linksOnly, useSubFrames);
661   },
663   onIteratorStart() {
664     this._currentMatchesCountResult = {
665       total: 0,
666       current: 0,
667       _currentFound: false,
668     };
669   },
671   _getWindow() {
672     if (!this._docShell) {
673       return null;
674     }
675     return this._docShell.domWindow;
676   },
678   /**
679    * Get the bounding selection rect in CSS px relative to the origin of the
680    * top-level content document.
681    */
682   _getResultRect() {
683     let topWin = this._getWindow();
684     let win = this._fastFind.currentWindow;
685     if (!win) {
686       return null;
687     }
689     let selection = win.getSelection();
690     if (!selection.rangeCount || selection.isCollapsed) {
691       // The selection can be into an input or a textarea element.
692       let nodes = win.document.querySelectorAll("input, textarea");
693       for (let node of nodes) {
694         if (node.editor) {
695           try {
696             let sc = node.editor.selectionController;
697             selection = sc.getSelection(
698               Ci.nsISelectionController.SELECTION_NORMAL
699             );
700             if (selection.rangeCount && !selection.isCollapsed) {
701               break;
702             }
703           } catch (e) {
704             // If this textarea is hidden, then its selection controller might
705             // not be intialized. Ignore the failure.
706           }
707         }
708       }
709     }
711     if (!selection.rangeCount || selection.isCollapsed) {
712       return null;
713     }
715     let utils = topWin.windowUtils;
717     let scrollX = {},
718       scrollY = {};
719     utils.getScrollXY(false, scrollX, scrollY);
721     for (let frame = win; frame != topWin; frame = frame.parent) {
722       let rect = frame.frameElement.getBoundingClientRect();
723       let left = frame.getComputedStyle(frame.frameElement).borderLeftWidth;
724       let top = frame.getComputedStyle(frame.frameElement).borderTopWidth;
725       scrollX.value += rect.left + parseInt(left, 10);
726       scrollY.value += rect.top + parseInt(top, 10);
727     }
728     let rect = Rect.fromRect(selection.getRangeAt(0).getBoundingClientRect());
729     return rect.translate(scrollX.value, scrollY.value);
730   },
732   _outlineLink(aDrawOutline) {
733     let foundLink = this._fastFind.foundLink;
735     // Optimization: We are drawing outlines and we matched
736     // the same link before, so don't duplicate work.
737     if (foundLink == this._previousLink && aDrawOutline) {
738       return;
739     }
741     this._restoreOriginalOutline();
743     if (foundLink && aDrawOutline) {
744       // Backup original outline
745       this._tmpOutline = foundLink.style.outline;
746       this._tmpOutlineOffset = foundLink.style.outlineOffset;
748       // Draw pseudo focus rect
749       // XXX Should we change the following style for FAYT pseudo focus?
750       // XXX Shouldn't we change default design if outline is visible
751       //     already?
752       // Don't set the outline-color, we should always use initial value.
753       foundLink.style.outline = "1px dotted";
754       foundLink.style.outlineOffset = "0";
756       this._previousLink = foundLink;
757     }
758   },
760   _restoreOriginalOutline() {
761     // Removes the outline around the last found link.
762     if (this._previousLink) {
763       this._previousLink.style.outline = this._tmpOutline;
764       this._previousLink.style.outlineOffset = this._tmpOutlineOffset;
765       this._previousLink = null;
766     }
767   },
769   _getSelectionController(aWindow) {
770     // display: none iframes don't have a selection controller, see bug 493658
771     try {
772       if (!aWindow.innerWidth || !aWindow.innerHeight) {
773         return null;
774       }
775     } catch (e) {
776       // If getting innerWidth or innerHeight throws, we can't get a selection
777       // controller.
778       return null;
779     }
781     // Yuck. See bug 138068.
782     let docShell = aWindow.docShell;
784     let controller = docShell
785       .QueryInterface(Ci.nsIInterfaceRequestor)
786       .getInterface(Ci.nsISelectionDisplay)
787       .QueryInterface(Ci.nsISelectionController);
788     return controller;
789   },
791   // Start of nsIWebProgressListener implementation.
793   onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
794     if (!aWebProgress.isTopLevel) {
795       return;
796     }
797     // Ignore events that don't change the document.
798     if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
799       return;
800     }
802     // Avoid leaking if we change the page.
803     this._lastFindResult = this._previousLink = this._currentFoundRange = null;
804     this.highlighter.onLocationChange();
805     this.iterator.reset();
806   },
808   QueryInterface: ChromeUtils.generateQI([
809     "nsIWebProgressListener",
810     "nsISupportsWeakReference",
811   ]),
814 export function GetClipboardSearchString(aLoadContext) {
815   let searchString = "";
816   if (
817     !Services.clipboard.isClipboardTypeSupported(
818       Services.clipboard.kFindClipboard
819     )
820   ) {
821     return searchString;
822   }
824   try {
825     let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
826       Ci.nsITransferable
827     );
828     trans.init(aLoadContext);
829     trans.addDataFlavor("text/plain");
831     Services.clipboard.getData(trans, Ci.nsIClipboard.kFindClipboard);
833     let data = {};
834     trans.getTransferData("text/plain", data);
835     if (data.value) {
836       data = data.value.QueryInterface(Ci.nsISupportsString);
837       searchString = data.toString();
838     }
839   } catch (ex) {}
841   return searchString;
844 export function SetClipboardSearchString(aSearchString) {
845   if (
846     !aSearchString ||
847     !Services.clipboard.isClipboardTypeSupported(
848       Services.clipboard.kFindClipboard
849     )
850   ) {
851     return;
852   }
854   lazy.ClipboardHelper.copyStringToClipboard(
855     aSearchString,
856     Ci.nsIClipboard.kFindClipboard
857   );