1 <?xml version="1.0"?>
2
3 <!-- ***** BEGIN LICENSE BLOCK *****
4 - Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 -
6 - The contents of this file are subject to the Mozilla Public License Version
7 - 1.1 (the "License"); you may not use this file except in compliance with
8 - the License. You may obtain a copy of the License at
9 - http://www.mozilla.org/MPL/
10 -
11 - Software distributed under the License is distributed on an "AS IS" basis,
12 - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 - for the specific language governing rights and limitations under the
14 - License.
15 -
16 - The Original Code is mozilla.org viewsource frontend.
17 -
18 - The Initial Developer of the Original Code is
19 - Netscape Communications Corporation.
20 - Portions created by the Initial Developer are Copyright (C) 2003
21 - the Initial Developer. All Rights Reserved.
22 -
23 - Contributor(s):
24 - Blake Ross <blake@cs.stanford.edu> (Original Author)
25 - Masayuki Nakano <masayuki@d-toybox.com>
26 - Ben Basson <contact@cusser.net>
27 - Jason Barnabe <jason_barnabe@fastmail.fm>
28 - Asaf Romano <mano@mozilla.com>
29 - Ehsan Akhgari <ehsan.akhgari@gmail.com>
30 -
31 - Alternatively, the contents of this file may be used under the terms of
32 - either the GNU General Public License Version 2 or later (the "GPL"), or
33 - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34 - in which case the provisions of the GPL or the LGPL are applicable instead
35 - of those above. If you wish to allow use of your version of this file only
36 - under the terms of either the GPL or the LGPL, and not to allow others to
37 - use your version of this file under the terms of the MPL, indicate your
38 - decision by deleting the provisions above and replace them with the notice
39 - and other provisions required by the GPL or the LGPL. If you do not delete
40 - the provisions above, a recipient may use your version of this file under
41 - the terms of any one of the MPL, the GPL or the LGPL.
42 -
43 - ***** END LICENSE BLOCK ***** -->
44
45 <!DOCTYPE bindings [
46 <!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
47 %findBarDTD;
48 <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
49 %globalDTD;
50 ]>
51
52 <bindings id="findbarBindings"
53 xmlns="http://www.mozilla.org/xbl"
54 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
55 xmlns:xbl="http://www.mozilla.org/xbl">
56
57 <!-- Private binding -->
58 <binding id="findbar-textbox"
59 extends="chrome://global/content/bindings/textbox.xml#textbox">
60 <implementation>
61
62 <field name="_findbar">null</field>
63 <property name="findbar" readonly="true">
64 <getter>
65 return this._findbar ?
66 this._findbar : this._findbar = document.getBindingParent(this);
67 </getter>
68 </property>
69
70 <method name="getSupportedFlavours">
71 <body><![CDATA[
72 var flavourSet = new FlavourSet();
73 flavourSet.appendFlavour("text/unicode");
74 return flavourSet;
75 ]]></body>
76 </method>
77
78 <method name="onDrop">
79 <parameter name="aEvent"/>
80 <parameter name="aXferData"/>
81 <parameter name="aDragSession"/>
82 <body><![CDATA[
83 this.value = aXferData.data;
84 this.findbar._find(aXferData.data);
85 ]]></body>
86 </method>
87
88 <method name="_handleEnter">
89 <parameter name="aEvent"/>
90 <body><![CDATA[
91 if (this.findbar._findMode == this.findbar.FIND_NORMAL) {
92 var findString = this.findbar._findField;
93 if (!findString.value)
94 return;
95 #ifdef XP_MACOSX
96 if (aEvent.metaKey) {
97 #else
98 if (aEvent.ctrlKey) {
99 #endif
100 this.findbar.getElement("highlight").click();
101 return;
102 }
103
104 this.findbar.onFindAgainCommand(aEvent.shiftKey);
105 }
106 else {
107 // We need to keep a reference to _foundLink because
108 // _finishFAYT resets it to null.
109 var tmpLink = this._findbar._foundLink;
110 if (tmpLink && this.findbar._finishFAYT(aEvent))
111 this.findbar._dispatchKeypressEvent(tmpLink, aEvent);
112 }
113 ]]></body>
114 </method>
115
116 <method name="_handleTab">
117 <parameter name="aEvent"/>
118 <body><![CDATA[
119 var shouldHandle = !aEvent.altKey && !aEvent.ctrlKey &&
120 !aEvent.metaKey;
121 if (shouldHandle &&
122 this.findbar._findMode != this.findbar.FIND_NORMAL &&
123 this.findbar._finishFAYT(aEvent)) {
124 if (aEvent.shiftKey)
125 document.commandDispatcher.rewindFocus();
126 else
127 document.commandDispatcher.advanceFocus();
128 }
129 ]]></body>
130 </method>
131 </implementation>
132
133 <handlers>
134 <handler event="input"><![CDATA[
135 this.findbar._find(this.value);
136 ]]></handler>
137
138 <handler event="keypress"><![CDATA[
139 var win = this.findbar._currentWindow ||
140 this.findbar.browser.contentWindow;
141
142 switch (event.keyCode) {
143 case KeyEvent.DOM_VK_RETURN:
144 this._handleEnter(event);
145 break;
146 case KeyEvent.DOM_VK_TAB:
147 this._handleTab(event);
148 break;
149 case KeyEvent.DOM_VK_PAGE_UP:
150 win.scrollByPages(-1);
151 event.preventDefault();
152 break;
153 case KeyEvent.DOM_VK_PAGE_DOWN:
154 win.scrollByPages(1);
155 event.preventDefault();
156 break;
157 case KeyEvent.DOM_VK_UP:
158 win.scrollByLines(-1);
159 event.preventDefault();
160 break;
161 case KeyEvent.DOM_VK_DOWN:
162 win.scrollByLines(1);
163 event.preventDefault();
164 break;
165 }
166 ]]></handler>
167
168 <handler event="blur"><![CDATA[
169 var findbar = this.findbar;
170 var fastFind = findbar.browser.fastFind;
171 if (findbar._foundEditable)
172 fastFind.collapseSelection();
173 else {
174 fastFind.setSelectionModeAndRepaint
175 (findbar.nsISelectionController.SELECTION_ON);
176 }
177 findbar._setFoundLink(null);
178 findbar._foundEditable = null;
179 findbar._currentWindow = null;
180 ]]></handler>
181
182 <handler event="compositionstart"><![CDATA[
183 // Don't close the find toolbar while IME is composing.
184 var findbar = this.findbar;
185 findbar._isIMEComposing = true;
186 if (findbar._quickFindTimeout) {
187 clearTimeout(findbar._quickFindTimeout);
188 findbar._quickFindTimeout = null;
189 }
190 ]]></handler>
191
192 <handler event="compositionend"><![CDATA[
193 var findbar = this.findbar;
194 findbar._isIMEComposing = false;
195 if (findbar._findMode != findbar.FIND_NORMAL &&
196 !findbar.hidden)
197 findbar._setFindCloseTimeout();
198 ]]></handler>
199
200 <handler event="dragdrop" phase="capturing"><![CDATA[
201 nsDragAndDrop.drop(event, this);
202 ]]></handler>
203 </handlers>
204 </binding>
205
206 <binding id="findbar"
207 extends="chrome://global/content/bindings/toolbar.xml#toolbar">
208 <resources>
209 <stylesheet src="chrome://global/skin/findBar.css"/>
210 </resources>
211
212 <content align="center" hidden="true">
213 <xul:toolbarbutton anonid="find-closebutton"
214 class="findbar-closebutton"
215 tooltiptext="&findCloseButton.tooltip;"
216 oncommand="close();"/>
217 <xul:label anonid="find-label" class="findbar-find-fast" control="findbar-textbox"/>
218 <xul:hbox anonid="find-field-container"
219 class="find-field-container findbar-find-fast">
220 <xul:textbox class="findbar-textbox" anonid="findbar-textbox"/>
221 </xul:hbox>
222 <xul:hbox anonid="find-buttons-container" class="find-buttons-container">
223 <xul:toolbarbutton anonid="find-next"
224 class="findbar-find-next tabbable"
225 label="&next.label;"
226 accesskey="&next.accesskey;"
227 tooltiptext="&next.tooltip;"
228 oncommand="onFindAgainCommand(false);"
229 disabled="true"
230 chromedir="&locale.dir;"
231 xbl:inherits="accesskey=findnextaccesskey"/>
232 <xul:toolbarbutton anonid="find-previous"
233 class="findbar-find-previous tabbable"
234 label="&previous.label;"
235 accesskey="&previous.accesskey;"
236 tooltiptext="&previous.tooltip;"
237 oncommand="onFindAgainCommand(true);"
238 disabled="true"
239 chromedir="&locale.dir;"
240 xbl:inherits="accesskey=findpreviousaccesskey"/>
241 </xul:hbox>
242 <xul:toolbarbutton anonid="highlight"
243 class="findbar-highlight tabbable"
244 label="&highlight.label;"
245 accesskey="&highlight.accesskey;"
246 tooltiptext="&highlight.tooltiptext;"
247 oncommand="toggleHighlight(this.checked);"
248 type="checkbox"
249 disabled="true"
250 xbl:inherits="accesskey=highlightaccesskey"/>
251 <xul:checkbox anonid="find-case-sensitive"
252 oncommand="_setCaseSensitivity(this.checked);"
253 label="&caseSensitiveCheckbox.label;"
254 accesskey="&caseSensitiveCheckbox.accesskey;"
255 xbl:inherits="accesskey=matchcaseaccesskey"/>
256 <xul:label anonid="match-case-status" class="findbar-find-fast"/>
257 <xul:image anonid="find-status-icon" class="findbar-find-fast find-status-icon"/>
258 <xul:description anonid="find-status" class="findbar-find-fast findbar-find-status"
259 control="findbar-textbox">
260 <!-- Do not use value, first child is used because it provides a11y with text change events -->
261 </xul:description>
262 </content>
263
264 <implementation implements="nsIDOMEventListener">
265 <field name="FIND_NORMAL">0</field>
266 <field name="FIND_TYPEAHEAD">1</field>
267 <field name="FIND_LINKS">2</field>
268
269 <field name="_findMode">0</field>
270 <field name="_tmpOutline">null</field>
271 <field name="_tmpOutlineOffset">"0"</field>
272 <field name="_drawOutline">false</field>
273 <field name="_foundLink">null</field>
274
275 <field name="_flashFindBar">0</field>
276 <field name="_initialFlashFindBarCount">6</field>
277
278 <property name="prefillWithSelection"
279 onget="return this.getAttribute('prefillwithselection') != 'false'"
280 onset="this.setAttribute('prefillwithselection', val); return val;"/>
281 <field name="_selectionMaxLen">150</field>
282
283 <method name="getElement">
284 <parameter name="aAnonymousID"/>
285 <body><![CDATA[
286 return document.getAnonymousElementByAttribute(this,
287 "anonid",
288 aAnonymousID)
289 ]]></body>
290 </method>
291
292 <property name="findMode"
293 readonly="true"
294 onget="return this._findMode;"/>
295
296 <field name="_browser">null</field>
297 <property name="browser">
298 <getter><![CDATA[
299 if (!this._browser) {
300 this._browser =
301 document.getElementById(this.getAttribute("browserid"));
302 }
303 return this._browser;
304 ]]></getter>
305 <setter><![CDATA[
306 if (this._browser) {
307 this._browser.removeEventListener("keypress", this, false);
308 this._browser.removeEventListener("mouseup", this, false);
309 this._foundLink = null;
310 this._foundEditable = null;
311 this._currentWindow = null;
312 }
313
314 this._browser = val;
315 if (this._browser) {
316 this._browser.addEventListener("keypress", this, false);
317 this._browser.addEventListener("mouseup", this, false);
318 this._findField.value = this._browser.fastFind.searchString;
319 }
320 return val;
321 ]]></setter>
322 </property>
323
324 <field name="_observer"><![CDATA[({
325 _self: this,
326
327 QueryInterface: function(aIID) {
328 if (aIID.equals(Components.interfaces.nsIObserver) ||
329 aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
330 aIID.equals(Components.interfaces.nsISupports))
331 return this;
332
333 throw Components.results.NS_ERROR_NO_INTERFACE;
334 },
335
336 observe: function(aSubject, aTopic, aPrefName) {
337 if (aTopic != "nsPref:changed")
338 return;
339
340 var prefsvc =
341 aSubject.QueryInterface(Components.interfaces.nsIPrefBranch2);
342
343 switch (aPrefName) {
344 case "accessibility.typeaheadfind":
345 this._self._useTypeAheadFind = prefsvc.getBoolPref(aPrefName);
346 break;
347 case "accessibility.typeaheadfind.linksonly":
348 this._self._typeAheadLinksOnly = prefsvc.getBoolPref(aPrefName);
349 break;
350 case "accessibility.typeaheadfind.casesensitive":
351 this._self._typeAheadCaseSensitive = prefsvc.getIntPref(aPrefName);
352 this._self._updateCaseSensitivity();
353 if (this._self.getElement("highlight").checked)
354 this._self._setHighlightTimeout();
355 break;
356 }
357 }
358 })]]></field>
359
360 <constructor><![CDATA[
361 // These elements are accessed frequently and are therefore cached
362 this._findField = this.getElement("findbar-textbox");
363 this._findStatusIcon = this.getElement("find-status-icon");
364 this._findStatusDesc = this.getElement("find-status");
365
366 var prefsvc =
367 Components.classes["@mozilla.org/preferences-service;1"]
368 .getService(Components.interfaces.nsIPrefBranch2);
369
370 this._quickFindTimeoutLength =
371 prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
372 this._flashFindBar =
373 prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
374
375 prefsvc.addObserver("accessibility.typeaheadfind",
376 this._observer, false);
377 prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
378 this._observer, false);
379 prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
380 this._observer, false);
381
382 this._useTypeAheadFind =
383 prefsvc.getBoolPref("accessibility.typeaheadfind");
384 this._typeAheadLinksOnly =
385 prefsvc.getBoolPref("accessibility.typeaheadfind.linksonly");
386 this._typeAheadCaseSensitive =
387 prefsvc.getIntPref("accessibility.typeaheadfind.casesensitive");
388
389 // Convenience
390 this.nsITypeAheadFind = Components.interfaces.nsITypeAheadFind;
391 this.nsISelectionController = Components.interfaces.nsISelectionController;
392
393 // Make sure the FAYT keypress listener is attached by initializing the
394 // browser property
395 setTimeout(function(aSelf) { aSelf.browser = aSelf.browser; }, 0, this);
396 ]]></constructor>
397
398 <destructor><![CDATA[
399 this.browser = null;
400
401 var prefsvc =
402 Components.classes["@mozilla.org/preferences-service;1"]
403 .getService(Components.interfaces.nsIPrefBranch2);
404 prefsvc.removeObserver("accessibility.typeaheadfind",
405 this._observer);
406 prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
407 this._observer);
408 prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
409 this._observer);
410 ]]></destructor>
411
412 <method name="_setFindCloseTimeout">
413 <body><![CDATA[
414 if (this._quickFindTimeout)
415 clearTimeout(this._quickFindTimeout);
416
417 // Don't close the find toolbar while IME is composing.
418 if (this._isIMEComposing) {
419 this._quickFindTimeout = null;
420 return;
421 }
422
423 this._quickFindTimeout =
424 setTimeout(function(aSelf) {
425 if (aSelf._findMode != aSelf.FIND_NORMAL)
426 aSelf.close();
427 }, this._quickFindTimeoutLength, this);
428 ]]></body>
429 </method>
430
431 <!--
432 - Turns highlight on or off.
433 - @param aHighlight (boolean)
434 - Whether to turn on highlight
435 -->
436 <method name="toggleHighlight">
437 <parameter name="aHighlight"/>
438 <body><![CDATA[
439 var word = this._findField.value;
440 if (aHighlight) {
441 // We have to update the status because we might still have the status
442 // of another tab
443 if (this._highlightDoc("yellow", "black", word))
444 this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
445 else
446 this._updateStatusUI(this.nsITypeAheadFind.FIND_NOTFOUND);
447 }
448 else {
449 this._highlightDoc(null, null, null);
450 this._lastHighlightString = null;
451 }
452 ]]></body>
453 </method>
454
455 <!--
456 - (Un)highlights each instance of the searched word in the passed
457 - window's content.
458 - @param aHighBackColor
459 - the background color for the highlight
460 - @param aHighTextColor
461 - the text color for the highlight, or null to make it
462 - unhighlight
463 - @param aWord
464 - the word to search for
465 - @param aWindow
466 - the window to search in. Passing undefined will search the
467 - current content window.
468 - @returns true if aWord was found
469 -->
470 <method name="_highlightDoc">
471 <parameter name="aHighBackColor"/>
472 <parameter name="aHighTextColor"/>
473 <parameter name="aWord"/>
474 <parameter name="aWindow"/>
475 <body><![CDATA[
476 var win = aWindow || this.browser.contentWindow;
477
478 var textFound = false;
479 for (var i = 0; win.frames && i < win.frames.length; i++) {
480 if (this._highlightDoc(aHighBackColor, aHighTextColor, aWord, win.frames[i]))
481 textFound = true;
482 }
483
484 var doc = win.document;
485 if (!doc || !(doc instanceof HTMLDocument))
486 return textFound;
487
488 var body = doc.body;
489
490 var count = body.childNodes.length;
491 this._searchRange = doc.createRange();
492 this._startPt = doc.createRange();
493 this._endPt = doc.createRange();
494
495 this._searchRange.setStart(body, 0);
496 this._searchRange.setEnd(body, count);
497
498 this._startPt.setStart(body, 0);
499 this._startPt.setEnd(body, 0);
500 this._endPt.setStart(body, count);
501 this._endPt.setEnd(body, count);
502
503 if (!aHighBackColor) {
504 // Remove highlighting. We use the find API again rather than
505 // searching for our span elements so that we gain access to the
506 // anonymous content that nsIFind searches.
507
508 if (!this._lastHighlightString)
509 return textFound;
510
511 var retRange = null;
512 var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
513 .createInstance(Components.interfaces.nsIFind);
514
515 while ((retRange = finder.Find(this._lastHighlightString,
516 this._searchRange, this._startPt,
517 this._endPt))) {
518 var startContainer = retRange.startContainer;
519 var elem = null;
520 try {
521 elem = startContainer.parentNode;
522 }
523 catch (ex) { }
524
525 if (elem && elem.className == "__mozilla-findbar-search") {
526 var child = null;
527 var docfrag = doc.createDocumentFragment();
528 var next = elem.nextSibling;
529 var parent = elem.parentNode;
530
531 while ((child = elem.firstChild)) {
532 docfrag.appendChild(child);
533 }
534
535 this._startPt = doc.createRange();
536 this._startPt.setStartAfter(elem);
537
538 parent.removeChild(elem);
539 parent.insertBefore(docfrag, next);
540 parent.normalize();
541 }
542 else {
543 // Somehow we didn't highlight this instance; just skip it.
544 this._startPt = doc.createRange();
545 this._startPt.setStart(retRange.endContainer,
546 retRange.endOffset);
547 }
548
549 this._startPt.collapse(true);
550
551 textFound = true;
552 }
553 return textFound;
554 }
555
556 var baseNode = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
557 baseNode.style.backgroundColor = aHighBackColor;
558 baseNode.style.color = aHighTextColor;
559 baseNode.style.display = "inline";
560 baseNode.style.fontSize = "inherit";
561 baseNode.style.padding = "0";
562 baseNode.className = "__mozilla-findbar-search";
563
564 return this._highlightText(aWord, baseNode) || textFound;
565 ]]></body>
566 </method>
567
568 <!--
569 - Highlights each instance of the searched word in the current range.
570 -
571 - @param aWord
572 - the word to search for.
573 - @param aBaseNode
574 - a node to use as a template for what will replace the searched
575 - word.
576 - @returns true if aWord was found
577 -->
578 <method name="_highlightText">
579 <parameter name="aWord"/>
580 <parameter name="aBaseNode"/>
581 <body><![CDATA[
582 var retRange = null;
583 var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
584 .createInstance()
585 .QueryInterface(Components.interfaces.nsIFind);
586
587 finder.caseSensitive = this._shouldBeCaseSensitive(aWord);
588
589 var textFound = false;
590 while((retRange = finder.Find(aWord, this._searchRange,
591 this._startPt, this._endPt))) {
592 // Highlight
593 var nodeSurround = aBaseNode.cloneNode(true);
594 var node = this._highlight(retRange, nodeSurround);
595 this._startPt = node.ownerDocument.createRange();
596 this._startPt.setStart(node, node.childNodes.length);
597 this._startPt.setEnd(node, node.childNodes.length);
598
599 textFound = true;
600 }
601
602 this._lastHighlightString = aWord;
603
604 return textFound;
605 ]]></body>
606 </method>
607
608 <!--
609 - Highlights the word in the passed range.
610 -
611 - @param aRange
612 - the range that contains the word to highlight
613 - @param aNode
614 - the node replace the searched word with
615 - @returns the node that replaced the searched word
616 -->
617 <method name="_highlight">
618 <parameter name="aRange"/>
619 <parameter name="aNode"/>
620 <body><![CDATA[
621 var startContainer = aRange.startContainer;
622 var startOffset = aRange.startOffset;
623 var endOffset = aRange.endOffset;
624 var docfrag = aRange.extractContents();
625 var before = startContainer.splitText(startOffset);
626 var parent = before.parentNode;
627 aNode.appendChild(docfrag);
628 parent.insertBefore(aNode, before);
629 return aNode;
630 ]]></body>
631 </method>
632
633 <!--
634 - Updates the case-sensitivity mode of the findbar and its UI.
635 - @param [optional] aString
636 - The string for which case sensitivity might be turned on.
637 - This only used when case-sensitivity is in auto mode,
638 - @see _shouldBeCaseSensitive. The default value for this
639 - parameter is the find-field value.
640 -->
641 <method name="_updateCaseSensitivity">
642 <parameter name="aString"/>
643 <body><![CDATA[
644 var val = aString || this._findField.value;
645
646 var caseSensitive = this._shouldBeCaseSensitive(val);
647 var checkbox = this.getElement("find-case-sensitive");
648 var statusLabel = this.getElement("match-case-status");
649 checkbox.checked = caseSensitive;
650
651 statusLabel.value = caseSensitive ? this._caseSensitiveStr : "";
652
653 // Show the checkbox on the full Find bar in non-auto mode.
654 // Show the label in all other cases.
655 var hideCheckbox = this._findMode != this.FIND_NORMAL ||
656 (this._typeAheadCaseSensitive != 0 &&
657 this._typeAheadCaseSensitive != 1);
658 checkbox.hidden = hideCheckbox;
659 statusLabel.hidden = !hideCheckbox;
660
661 var fastFind = this.browser.fastFind;
662 fastFind.caseSensitive = caseSensitive;
663 ]]></body>
664 </method>
665
666 <!--
667 - Sets the findbar case-sensitivity mode
668 - @param aCaseSensitive (boolean)
669 - Whether or not case-sensitivity should be turned on.
670 -->
671 <method name="_setCaseSensitivity">
672 <parameter name="aCaseSensitive"/>
673 <body><![CDATA[
674 var prefsvc =
675 Components.classes["@mozilla.org/preferences-service;1"]
676 .getService(Components.interfaces.nsIPrefBranch2);
677
678 // Just set the pref; our observer will change the find bar behavior
679 prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive",
680 aCaseSensitive ? 1 : 0);
681 ]]></body>
682 </method>
683
684 <!--
685 - Opens and displays the find bar.
686 -
687 - @param aMode
688 - the find mode to be used, which is either FIND_NORMAL,
689 - FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
690 - find mode if any or FIND_NORMAL.
691 - @returns true if the find bar wasn't previously open, false otherwise.
692 -->
693 <method name="open">
694 <parameter name="aMode"/>
695 <body><![CDATA[
696 if (aMode != undefined)
697 this._findMode = aMode;
698
699 if (!this._notFoundStr) {
700 var stringsBundle =
701 Components.classes["@mozilla.org/intl/stringbundle;1"]
702 .getService(Components.interfaces.nsIStringBundleService)
703 .createBundle("chrome://global/locale/findbar.properties");
704 this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
705 this._wrappedToTopStr =
706 stringsBundle.GetStringFromName("WrappedToTop");
707 this._wrappedToBottomStr =
708 stringsBundle.GetStringFromName("WrappedToBottom");
709 this._normalFindStr =
710 stringsBundle.GetStringFromName("NormalFindLabel");
711 this._fastFindStr =
712 stringsBundle.GetStringFromName("FastFindLabel");
713 this._fastFindLinksStr =
714 stringsBundle.GetStringFromName("FastFindLinksLabel");
715 this._caseSensitiveStr =
716 stringsBundle.GetStringFromName("CaseSensitive");
717 }
718
719 this._updateFindUI();
720 if (this.hidden) {
721 this.hidden = false;
722
723 this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
724 return true;
725 }
726 return false;
727 ]]></body>
728 </method>
729
730 <!--
731 - Closes the findbar.
732 -->
733 <method name="close">
734 <body><![CDATA[
735 var ww =
736 Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
737 .getService(Components.interfaces.nsIWindowWatcher);
738 if (window == ww.activeWindow) {
739 var focusedElement = document.commandDispatcher.focusedElement;
740 if (focusedElement) {
741 var bindingParent = document.getBindingParent(focusedElement);
742 if (bindingParent == this || bindingParent == this._findField) {
743 // block scrolling on focus since find already scrolls, further
744 // scrolling is due to user action, so don't override this
745 var suppressedScroll = document.commandDispatcher.suppressFocusScroll;
746 document.commandDispatcher.suppressFocusScroll = true;
747 // We MUST reset suppressFocusScroll.
748 try {
749 if (this._foundLink)
750 this._foundLink.focus();
751 else if (this._foundEditable)
752 this._foundEditable.focus();
753 else if (this._currentWindow)
754 this._currentWindow.focus();
755 else
756 this.browser.contentWindow.focus();
757 }
758 catch(ex) {
759 // Retry to set focus.
760 try {
761 this.browser.contentWindow.focus();
762 }
763 catch(e) { /* We lose focused element! */ }
764 }
765 document.commandDispatcher.suppressFocusScroll = suppressedScroll;
766 }
767 }
768 }
769
770 this.hidden = true;
771 var fastFind = this.browser.fastFind;
772 fastFind.setSelectionModeAndRepaint
773 (this.nsISelectionController.SELECTION_ON);
774 this._setFoundLink(null);
775 this._foundEditable = null;
776 this._currentWindow = null;
777 if (this._quickFindTimeout) {
778 clearTimeout(this._quickFindTimeout);
779 this._quickFindTimeout = null;
780 }
781 ]]></body>
782 </method>
783
784 <method name="_dispatchKeypressEvent">
785 <parameter name="aTarget"/>
786 <parameter name="aEvent"/>
787 <body><![CDATA[
788 if (!aTarget)
789 return;
790
791 var event = document.createEvent("KeyEvents");
792 event.initKeyEvent(aEvent.type, aEvent.bubbles, aEvent.cancelable,
793 aEvent.view, aEvent.ctrlKey, aEvent.altKey,
794 aEvent.shiftKey, aEvent.metaKey, aEvent.keyCode,
795 aEvent.charCode);
796 aTarget.dispatchEvent(event);
797 ]]></body>
798 </method>
799
800 <field name="_xulBrowserWindow">null</field>
801 <method name="_updateStatusUIBar">
802 <body><![CDATA[
803 if (!this._xulBrowserWindow) {
804 try {
805 this._xulBrowserWindow =
806 window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
807 .getInterface(Components.interfaces.nsIWebNavigation)
808 .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
809 .treeOwner
810 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
811 .getInterface(Components.interfaces.nsIXULWindow)
812 .XULBrowserWindow;
813 }
814 catch(ex) { }
815 if (!this._xulBrowserWindow)
816 return false;
817 }
818
819 if (!this._foundLink || !this._foundLink.href ||
820 this._foundLink.href == "") {
821 this._xulBrowserWindow.setOverLink("", null);
822 return true;
823 }
824
825 var docCharset = "";
826 var ownerDoc = this._foundLink.ownerDocument;
827 if (ownerDoc)
828 docCharset = ownerDoc.characterSet;
829
830 if (!this._textToSubURIService) {
831 this._textToSubURIService =
832 Components.classes["@mozilla.org/intl/texttosuburi;1"]
833 .getService(Components.interfaces.nsITextToSubURI);
834 }
835 var url =
836 this._textToSubURIService.unEscapeURIForUI(docCharset,
837 this._foundLink.href);
838 this._xulBrowserWindow.setOverLink(url, null);
839
840 return true;
841 ]]></body>
842 </method>
843
844 <method name="_setFoundLink">
845 <parameter name="aFoundLink"/>
846 <body><![CDATA[
847 if (this._foundLink == aFoundLink)
848 return;
849
850 if (this._foundLink && this._drawOutline) {
851 // restore original outline
852 this._foundLink.style.outline = this._tmpOutline;
853 this._foundLink.style.outlineOffset = this._tmpOutlineOffset;
854 }
855 this._drawOutline = (aFoundLink && this._findMode != this.FIND_NORMAL);
856 if (this._drawOutline) {
857 // backup original outline
858 this._tmpOutline = aFoundLink.style.outline;
859 this._tmpOutlineOffset = aFoundLink.style.outlineOffset;
860
861 // draw pseudo focus rect
862 // XXX Should we change the following style for FAYT pseudo focus?
863 // XXX Shouldn't we change default design if outline is visible
864 // already?
865 // Don't set the outline-color, we should always use initial value.
866 aFoundLink.style.outline = "1px dotted";
867 aFoundLink.style.outlineOffset = "0";
868 }
869
870 this._foundLink = aFoundLink;
871
872 // If the mouse cursor is on the document, the status bar text is
873 // changed by a mouse event which is dispatched by a scroll event.
874 // Thus we should change it only after the mouse event is dispatched.
875 if (this._findMode != this.FIND_NORMAL)
876 setTimeout(function(aSelf) { aSelf._updateStatusUIBar(); }, 0, this);
877 ]]></body>
878 </method>
879
880 <method name="_finishFAYT">
881 <parameter name="aKeypressEvent"/>
882 <body><![CDATA[
883 try {
884 if (this._foundLink)
885 this._foundLink.focus();
886 else if (this._foundEditable) {
887 this._foundEditable.focus();
888 var fastFind = this.browser.fastFind;
889 fastFind.collapseSelection();
890 }
891 else if (this._currentWindow)
892 this._currentWindow.focus();
893 else
894 return false;
895 }
896 catch(e) {
897 return false;
898 }
899
900 if (aKeypressEvent)
901 aKeypressEvent.preventDefault();
902
903 this.close();
904 return true;
905 ]]></body>
906 </method>
907
908 <!--
909 - Returns true if |aMimeType| is text-based, or false otherwise.
910 -
911 - @param aMimeType
912 - The MIME type to check.
913 -
914 - if adding types to this function, please see the similar function
915 - in browser/base/content/browser.js
916 -->
917 <method name="_mimeTypeIsTextBased">
918 <parameter name="aMimeType"/>
919 <body><![CDATA[
920 return /^text\/|\+xml$/.test(aMimeType) ||
921 aMimeType == "application/x-javascript" ||
922 aMimeType == "application/javascript" ||
923 aMimeType == "application/xml";
924 ]]></body>
925 </method>
926
927 <!--
928 - Returns whether FAYT can be used for the given event in
929 - the current content state.
930 -->
931 <method name="_shouldFastFind">
932 <parameter name="aEvent"/>
933 <body><![CDATA[
934 if (aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey ||
935 aEvent.getPreventDefault())
936 return false;
937
938 var win = document.commandDispatcher.focusedWindow;
939 if (win)
940 if (!this._mimeTypeIsTextBased(win.document.contentType))
941 return false;
942
943 var elt = document.commandDispatcher.focusedElement;
944 if (elt) {
945 if (elt instanceof HTMLInputElement) {
946 // block FAYT when an <input> textfield element is focused
947 var inputType = elt.type;
948 switch (inputType) {
949 case "text":
950 case "password":
951 case "file":
952 return false;
953 }
954 }
955 else if (elt instanceof HTMLTextAreaElement ||
956 elt instanceof HTMLSelectElement ||
957 elt instanceof HTMLIsIndexElement ||
958 elt instanceof HTMLObjectElement ||
959 elt instanceof HTMLEmbedElement)
960 return false;
961 }
962
963 // disable FAYT in about:config and about:blank to prevent FAYT
964 // opening unexpectedly - to fix bugs 264562, 267150, 269712
965 var url = this.browser.currentURI.spec;
966 if (url == "about:blank" || url == "about:config")
967 return false;
968
969 if (win) {
970 try {
971 var editingSession = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
972 .getInterface(Components.interfaces.nsIWebNavigation)
973 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
974 .getInterface(Components.interfaces.nsIEditingSession);
975 if (editingSession.windowIsEditable(win))
976 return false;
977 }
978 catch (e) {
979 // If someone built with composer disabled, we can't get an editing session.
980 }
981 }
982
983 return true;
984 ]]></body>
985 </method>
986
987 <method name="_shouldBeCaseSensitive">
988 <parameter name="aString"/>
989 <body><![CDATA[
990 if (this._typeAheadCaseSensitive == 0)
991 return false;
992 if (this._typeAheadCaseSensitive == 1)
993 return true;
994
995 return aString != aString.toLowerCase();
996 ]]></body>
997 </method>
998
999 <method name="_onBrowserKeypress">
1000 <parameter name="aEvent"/>
1001 <body><![CDATA[
1002 const TAF_LINKS_KEY = "'";
1003 const TAF_TEXT_KEY = "/";
1004
1005 if (!this._shouldFastFind(aEvent))
1006 return;
1007
1008 if (this._findMode != this.FIND_NORMAL && this._quickFindTimeout) {
1009 if (!aEvent.charCode)
1010 return;
1011
1012 this._findField.select();
1013 this._findField.focus();
1014 this._dispatchKeypressEvent(this._findField.inputField, aEvent);
1015 aEvent.preventDefault();
1016 return;
1017 }
1018
1019 var key = aEvent.charCode ? String.fromCharCode(aEvent.charCode) : null;
1020 var manualstartFAYT = (key == TAF_LINKS_KEY || key == TAF_TEXT_KEY);
1021 var autostartFAYT = !manualstartFAYT && this._useTypeAheadFind &&
1022 key && key != " ";
1023 if (manualstartFAYT || autostartFAYT) {
1024 var mode = (key == TAF_LINKS_KEY ||
1025 (autostartFAYT && this._typeAheadLinksOnly)) ?
1026 this.FIND_LINKS : this.FIND_TYPEAHEAD;
1027
1028 // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
1029 // it doesn't get confused by a lingering value
1030 this._findField.value = "";
1031
1032 this.open(mode);
1033 this._setFindCloseTimeout();
1034 this._findField.select();
1035 this._findField.focus();
1036
1037 if (autostartFAYT)
1038 this._dispatchKeypressEvent(this._findField.inputField, aEvent);
1039 else
1040 this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
1041
1042 aEvent.preventDefault();
1043 }
1044 ]]></body>
1045 </method>
1046
1047 <!-- See nsIDOMEventListener -->
1048 <method name="handleEvent">
1049 <parameter name="aEvent"/>
1050 <body><![CDATA[
1051 switch (aEvent.type) {
1052 case "mouseup":
1053 if (!this.hidden && this._findMode != this.FIND_NORMAL)
1054 this.close();
1055
1056 break;
1057 case "keypress":
1058 this._onBrowserKeypress(aEvent);
1059 break;
1060 }
1061 ]]></body>
1062 </method>
1063
1064 <method name="_enableFindButtons">
1065 <parameter name="aEnable"/>
1066 <body><![CDATA[
1067 this.getElement("find-next").disabled =
1068 this.getElement("find-previous").disabled =
1069 this.getElement("highlight").disabled = !aEnable;
1070 ]]></body>
1071 </method>
1072
1073 <!--
1074 - Determines whether minimalist or general-purpose search UI is to be
1075 - displayed when the find bar is activated.
1076 -->
1077 <method name="_updateFindUI">
1078 <body><![CDATA[
1079 var showMinimalUI = this._findMode != this.FIND_NORMAL;
1080
1081 var nodes = document.getAnonymousNodes(this);
1082 for (var i = 0; i < nodes.length; i++) {
1083 if (nodes[i].className.indexOf("findbar-find-fast") != -1)
1084 continue;
1085
1086 nodes[i].hidden = showMinimalUI;
1087 }
1088 this._updateCaseSensitivity();
1089
1090 if (this._findMode == this.FIND_TYPEAHEAD)
1091 this.getElement("find-label").value = this._fastFindStr;
1092 else if (this._findMode == this.FIND_LINKS)
1093 this.getElement("find-label").value = this._fastFindLinksStr;
1094 else
1095 this.getElement("find-label").value = this._normalFindStr;
1096 ]]></body>
1097 </method>
1098
1099 <method name="_updateFoundLink">
1100 <parameter name="res"/>
1101 <body><![CDATA[
1102 var val = this._findField.value;
1103 if (res == this.nsITypeAheadFind.FIND_NOTFOUND || !val) {
1104 this._setFoundLink(null);
1105 this._foundEditable = null;
1106 this._currentWindow = null;
1107 }
1108 else {
1109 this._setFoundLink(this.browser.fastFind.foundLink);
1110 this._foundEditable = this.browser.fastFind.foundEditable;
1111 this._currentWindow = this.browser.fastFind.currentWindow;
1112 }
1113 ]]></body>
1114 </method>
1115
1116 <method name="_find">
1117 <parameter name="aValue"/>
1118 <body><![CDATA[
1119 var val = aValue || this._findField.value
1120
1121 this._enableFindButtons(val);
1122 if (this.getElement("highlight").checked)
1123 this._setHighlightTimeout();
1124
1125 this._updateCaseSensitivity(val);
1126
1127 var fastFind = this.browser.fastFind;
1128 var res = fastFind.find(val, this._findMode == this.FIND_LINKS);
1129 this._updateFoundLink(res);
1130 this._updateStatusUI(res, false);
1131
1132 if (this._findMode != this.FIND_NORMAL)
1133 this._setFindCloseTimeout();
1134
1135 return res;
1136 ]]></body>
1137 </method>
1138
1139 <method name="_flash">
1140 <body><![CDATA[
1141 if (this._flashFindBarCount === undefined)
1142 this._flashFindBarCount = this._initialFlashFindBarCount;
1143
1144 if (this._flashFindBarCount-- == 0) {
1145 clearInterval(this._flashFindBarTimeout);
1146 this.removeAttribute("flash");
1147 this._flashFindBarCount = 6;
1148 return;
1149 }
1150
1151 this.setAttribute("flash",
1152 (this._flashFindBarCount % 2 == 0) ?
1153 "false" : "true");
1154 ]]></body>
1155 </method>
1156
1157 <method name="_setHighlightTimeout">
1158 <body><![CDATA[
1159 if (this._highlightTimeout)
1160 clearTimeout(this._highlightTimeout);
1161 this._highlightTimeout =
1162 setTimeout(function(aSelf) {
1163 aSelf.toggleHighlight(false);
1164 aSelf.toggleHighlight(true);
1165 }, 500, this);
1166 ]]></body>
1167 </method>
1168
1169 <method name="_findAgain">
1170 <parameter name="aFindPrevious"/>
1171 <body><![CDATA[
1172 var fastFind = this.browser.fastFind;
1173 var res = fastFind.findAgain(aFindPrevious,
1174 this._findMode == this.FIND_LINKS);
1175 this._updateFoundLink(res);
1176 this._updateStatusUI(res, aFindPrevious);
1177
1178 if (this._findMode != this.FIND_NORMAL && !this.hidden)
1179 this._setFindCloseTimeout();
1180
1181 return res;
1182 ]]></body>
1183 </method>
1184
1185 <method name="_updateStatusUI">
1186 <parameter name="res"/>
1187 <parameter name="aFindPrevious"/>
1188 <body><![CDATA[
1189 switch (res) {
1190 case this.nsITypeAheadFind.FIND_WRAPPED:
1191 this._findStatusIcon.setAttribute("status", "wrapped");
1192 this._findStatusDesc.textContent =
1193 aFindPrevious ? this._wrappedToBottomStr : this._wrappedToTopStr;
1194 this._findField.removeAttribute("status");
1195 break;
1196 case this.nsITypeAheadFind.FIND_NOTFOUND:
1197 this._findStatusIcon.setAttribute("status", "notfound");
1198 this._findStatusDesc.textContent = this._notFoundStr;
1199 this._findField.setAttribute("status", "notfound");
1200 break;
1201 case this.nsITypeAheadFind.FIND_FOUND:
1202 default:
1203 this._findStatusIcon.removeAttribute("status");
1204 this._findStatusDesc.textContent = "";
1205 this._findField.removeAttribute("status");
1206 break;
1207 }
1208 ]]></body>
1209 </method>
1210
1211 <method name="_getInitialSelection">
1212 <body><![CDATA[
1213 var focusedElement = document.commandDispatcher.focusedElement;
1214 var selText;
1215
1216 if (focusedElement instanceof Components.interfaces.nsIDOMNSEditableElement &&
1217 focusedElement.ownerDocument.defaultView.top == this._browser.contentWindow)
1218 {
1219 // The user may have a selection in an input or textarea
1220 selText = focusedElement.editor.selectionController
1221 .getSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL)
1222 .toString();
1223 }
1224 else {
1225 // Look for any selected text on the actual page
1226 var focusedWindow = document.commandDispatcher.focusedWindow;
1227 if (focusedWindow.top == this._browser.contentWindow)
1228 selText = focusedWindow.getSelection().toString();
1229 }
1230
1231 if (!selText)
1232 return "";
1233
1234 // Process our text to get rid of unwanted characters
1235 if (selText.length > this._selectionMaxLen) {
1236 var pattern = new RegExp("^(?:\\s*.){0," + this._selectionMaxLen + "}");
1237 pattern.test(selText);
1238 selText = RegExp.lastMatch;
1239 }
1240 return selText.replace(/^\s+/, "")
1241 .replace(/\s+$/, "")
1242 .replace(/\s+/g, " ")
1243 .substr(0, this._selectionMaxLen);
1244 ]]></body>
1245 </method>
1246
1247 <!--
1248 - Opens the findbar, focuses the findfield and selects its contents.
1249 - Also flashes the findbar the first time it's used.
1250 - @param aMode
1251 - the find mode to be used, which is either FIND_NORMAL,
1252 - FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
1253 - find mode if any or FIND_NORMAL.
1254 -->
1255 <method name="startFind">
1256 <parameter name="aMode"/>
1257 <body><![CDATA[
1258 var prefsvc =
1259 Components.classes["@mozilla.org/preferences-service;1"]
1260 .getService(Components.interfaces.nsIPrefBranch2);
1261 var userWantsPrefill = true;
1262 this.open(aMode);
1263
1264 if (this._flashFindBar) {
1265 this._flashFindBarTimeout =
1266 setInterval(function(aSelf) { aSelf._flash(); }, 500, this);
1267 prefsvc.setIntPref("accessibility.typeaheadfind.flashBar",
1268 --this._flashFindBar);
1269 }
1270
1271 if (this.prefillWithSelection)
1272 userWantsPrefill =
1273 prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");
1274
1275 var initialString = (this.prefillWithSelection && userWantsPrefill) ?
1276 this._getInitialSelection() : null;
1277 if (initialString) {
1278 this._findField.value = initialString;
1279 this._enableFindButtons(true);
1280 }
1281 else if (!this._findField.value)
1282 this._enableFindButtons(false);
1283
1284 this._findField.select();
1285 this._findField.focus();
1286 ]]></body>
1287 </method>
1288
1289 <!--
1290 - Convenient alias to startFind(gFindBar.FIND_NORMAL);
1291 -
1292 - You should generally map the window's find command to this method.
1293 - e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
1294 -->
1295 <method name="onFindCommand">
1296 <body><![CDATA[
1297 this.startFind(this.FIND_NORMAL);
1298 ]]></body>
1299 </method>
1300
1301 <!--
1302 - Stub for find-next and find-previous commands
1303 - @param aFindPrevious
1304 - true for find-previous, false otherwise.
1305 -->
1306 <method name="onFindAgainCommand">
1307 <parameter name="aFindPrevious"/>
1308 <body><![CDATA[
1309 var findString = this._browser.fastFind.searchString || this._findField.value;
1310 if (!findString) {
1311 this.startFind();
1312 return;
1313 }
1314
1315 var res;
1316 // Ensure the stored SearchString is in sync with what we want to find
1317 if (this._findField.value != this._browser.fastFind.searchString &&
1318 !this.hidden)
1319 res = this._find(this._findField.value);
1320 else
1321 res = this._findAgain(aFindPrevious);
1322
1323 if (res == this.nsITypeAheadFind.FIND_NOTFOUND) {
1324 if (this.open()) {
1325 if (this._findMode != this.FIND_NORMAL)
1326 this._setFindCloseTimeout();
1327 this._findField.focus();
1328 this._findField.focus();
1329 this._updateStatusUI(res, aFindPrevious);
1330 }
1331 }
1332 ]]></body>
1333 </method>
1334 </implementation>
1335
1336 <handlers>
1337 <handler event="keypress" keycode="VK_ESCAPE" phase="capturing" action="this.close();" preventdefault="true"/>
1338 </handlers>
1339 </binding>
1340 </bindings>