Source code

Revision control

Other Tools

1
<?xml version="1.0"?>
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/. -->
5
6
<bindings id="autocompleteBindings"
10
xmlns:xbl="http://www.mozilla.org/xbl">
11
12
<binding id="autocomplete"
14
<content sizetopopup="pref">
15
<xul:moz-input-box anonid="moz-input-box" flex="1">
16
<html:input anonid="input" class="textbox-input"
17
allowevents="true"
18
autocomplete="off"
19
xbl:inherits="value,type=inputtype,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/>
20
</xul:moz-input-box>
21
<xul:popupset anonid="popupset" class="autocomplete-result-popupset"/>
22
</content>
23
24
<implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement">
25
<field name="mController">null</field>
26
<field name="mSearchNames">null</field>
27
<field name="mIgnoreInput">false</field>
28
<field name="noRollupOnEmptySearch">false</field>
29
30
<field name="_searchBeginHandler">null</field>
31
<field name="_searchCompleteHandler">null</field>
32
<field name="_textEnteredHandler">null</field>
33
<field name="_textRevertedHandler">null</field>
34
35
<constructor><![CDATA[
36
this.mController = Cc["@mozilla.org/autocomplete/controller;1"].
37
getService(Ci.nsIAutoCompleteController);
38
39
this._searchBeginHandler = this.initEventHandler("searchbegin");
40
this._searchCompleteHandler = this.initEventHandler("searchcomplete");
41
this._textEnteredHandler = this.initEventHandler("textentered");
42
this._textRevertedHandler = this.initEventHandler("textreverted");
43
]]></constructor>
44
45
<!-- =================== nsIAutoCompleteInput =================== -->
46
47
<field name="_popup">null</field>
48
<property name="popup" readonly="true">
49
<getter><![CDATA[
50
// Memoize the result in a field rather than replacing this property,
51
// so that it can be reset along with the binding.
52
if (this._popup) {
53
return this._popup;
54
}
55
56
let popup = null;
57
let popupId = this.getAttribute("autocompletepopup");
58
if (popupId) {
59
popup = document.getElementById(popupId);
60
}
61
if (!popup) {
62
popup = document.createXULElement("panel", { is: "autocomplete-richlistbox-popup" });
63
popup.setAttribute("type", "autocomplete-richlistbox");
64
popup.setAttribute("noautofocus", "true");
65
66
let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
67
popupset.appendChild(popup);
68
}
69
popup.mInput = this;
70
71
return this._popup = popup;
72
]]></getter>
73
</property>
74
<property name="popupElement" readonly="true"
75
onget="return this.popup;"/>
76
77
<property name="controller" onget="return this.mController;" readonly="true"/>
78
79
<property name="popupOpen"
80
onget="return this.popup.popupOpen;"
81
onset="if (val) this.openPopup(); else this.closePopup();"/>
82
83
<property name="disableAutoComplete"
84
onset="this.setAttribute('disableautocomplete', val); return val;"
85
onget="return this.getAttribute('disableautocomplete') == 'true';"/>
86
87
<property name="completeDefaultIndex"
88
onset="this.setAttribute('completedefaultindex', val); return val;"
89
onget="return this.getAttribute('completedefaultindex') == 'true';"/>
90
91
<property name="completeSelectedIndex"
92
onset="this.setAttribute('completeselectedindex', val); return val;"
93
onget="return this.getAttribute('completeselectedindex') == 'true';"/>
94
95
<property name="forceComplete"
96
onset="this.setAttribute('forcecomplete', val); return val;"
97
onget="return this.getAttribute('forcecomplete') == 'true';"/>
98
99
<property name="minResultsForPopup"
100
onset="this.setAttribute('minresultsforpopup', val); return val;"
101
onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>
102
103
<property name="timeout"
104
onset="this.setAttribute('timeout', val); return val;"
105
onget="var t = parseInt(this.getAttribute('timeout')); return isNaN(t) ? 50 : t;"/>
106
107
<property name="searchParam"
108
onget="return this.getAttribute('autocompletesearchparam') || '';"
109
onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
110
111
<property name="searchCount" readonly="true"
112
onget="this.initSearchNames(); return this.mSearchNames.length;"/>
113
114
<property name="PrivateBrowsingUtils" readonly="true">
115
<getter><![CDATA[
116
let module = {};
117
ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", module);
118
Object.defineProperty(this, "PrivateBrowsingUtils", {
119
configurable: true,
120
enumerable: true,
121
writable: true,
122
value: module.PrivateBrowsingUtils,
123
});
124
return module.PrivateBrowsingUtils;
125
]]></getter>
126
</property>
127
128
<property name="inPrivateContext" readonly="true"
129
onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/>
130
131
<property name="noRollupOnCaretMove" readonly="true"
132
onget="return this.popup.getAttribute('norolluponanchor') == 'true'"/>
133
134
<!-- This is the maximum number of drop-down rows we get when we
135
hit the drop marker beside fields that have it (like the URLbar).-->
136
<field name="maxDropMarkerRows" readonly="true">14</field>
137
138
<method name="getSearchAt">
139
<parameter name="aIndex"/>
140
<body><![CDATA[
141
this.initSearchNames();
142
return this.mSearchNames[aIndex];
143
]]></body>
144
</method>
145
146
<method name="setTextValueWithReason">
147
<parameter name="aValue"/>
148
<parameter name="aReason"/>
149
<body><![CDATA[
150
if (aReason == Ci.nsIAutoCompleteInput
151
.TEXTVALUE_REASON_COMPLETEDEFAULT) {
152
this._textValueSetByCompleteDefault = true;
153
}
154
this.textValue = aValue;
155
this._textValueSetByCompleteDefault = false;
156
]]></body>
157
</method>
158
159
<property name="textValue">
160
<getter><![CDATA[
161
if (typeof this.onBeforeTextValueGet == "function") {
162
let result = this.onBeforeTextValueGet();
163
if (result) {
164
return result.value;
165
}
166
}
167
return this.value;
168
]]></getter>
169
<setter><![CDATA[
170
if (typeof this.onBeforeTextValueSet == "function" &&
171
!this._textValueSetByCompleteDefault) {
172
val = this.onBeforeTextValueSet(val);
173
}
174
175
// "input" event is automatically dispatched by the editor if
176
// necessary.
177
this._setValueInternal(val, true);
178
179
return this.value;
180
]]></setter>
181
</property>
182
183
<method name="selectTextRange">
184
<parameter name="aStartIndex"/>
185
<parameter name="aEndIndex"/>
186
<body><![CDATA[
187
this.inputField.setSelectionRange(aStartIndex, aEndIndex);
188
]]></body>
189
</method>
190
191
<method name="onSearchBegin">
192
<body><![CDATA[
193
if (this.popup && typeof this.popup.onSearchBegin == "function")
194
this.popup.onSearchBegin();
195
if (this._searchBeginHandler)
196
this._searchBeginHandler();
197
]]></body>
198
</method>
199
200
<method name="onSearchComplete">
201
<body><![CDATA[
202
if (this.mController.matchCount == 0)
203
this.setAttribute("nomatch", "true");
204
else
205
this.removeAttribute("nomatch");
206
207
if (this.ignoreBlurWhileSearching && !this.focused) {
208
this.handleEnter();
209
this.detachController();
210
}
211
212
if (this._searchCompleteHandler)
213
this._searchCompleteHandler();
214
]]></body>
215
</method>
216
217
<method name="onTextEntered">
218
<parameter name="event"/>
219
<body><![CDATA[
220
let rv = false;
221
if (this._textEnteredHandler) {
222
rv = this._textEnteredHandler(event);
223
}
224
return rv;
225
]]></body>
226
</method>
227
228
<method name="onTextReverted">
229
<body><![CDATA[
230
if (this._textRevertedHandler)
231
return this._textRevertedHandler();
232
return false;
233
]]></body>
234
</method>
235
236
<!-- =================== nsIDOMXULMenuListElement =================== -->
237
238
<property name="editable" readonly="true"
239
onget="return true;" />
240
241
<property name="crop"
242
onset="this.setAttribute('crop',val); return val;"
243
onget="return this.getAttribute('crop');"/>
244
245
<property name="open"
246
onget="return this.getAttribute('open') == 'true';">
247
<setter><![CDATA[
248
if (val)
249
this.showHistoryPopup();
250
else
251
this.closePopup();
252
]]></setter>
253
</property>
254
255
<!-- =================== PUBLIC MEMBERS =================== -->
256
257
<field name="valueIsTyped">false</field>
258
<field name="_textValueSetByCompleteDefault">false</field>
259
<property name="value"
260
onset="return this._setValueInternal(val, false);">
261
<getter><![CDATA[
262
if (typeof this.onBeforeValueGet == "function") {
263
var result = this.onBeforeValueGet();
264
if (result)
265
return result.value;
266
}
267
return this.inputField.value;
268
]]></getter>
269
</property>
270
271
<property name="focused" readonly="true"
272
onget="return this.getAttribute('focused') == 'true';"/>
273
274
<!-- maximum number of rows to display at a time -->
275
<property name="maxRows"
276
onset="this.setAttribute('maxrows', val); return val;"
277
onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
278
279
<!-- option to allow scrolling through the list via the tab key, rather than
280
tab moving focus out of the textbox -->
281
<property name="tabScrolling"
282
onset="this.setAttribute('tabscrolling', val); return val;"
283
onget="return this.getAttribute('tabscrolling') == 'true';"/>
284
285
<!-- option to completely ignore any blur events while searches are
286
still going on. -->
287
<property name="ignoreBlurWhileSearching"
288
onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
289
onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
290
291
<!-- option to highlight entries that don't have any matches -->
292
<property name="highlightNonMatches"
293
onset="this.setAttribute('highlightnonmatches', val); return val;"
294
onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
295
296
<!-- =================== PRIVATE MEMBERS =================== -->
297
298
<!-- ::::::::::::: autocomplete controller ::::::::::::: -->
299
300
<method name="attachController">
301
<body><![CDATA[
302
this.mController.input = this;
303
]]></body>
304
</method>
305
306
<method name="detachController">
307
<body><![CDATA[
308
if (this.mController.input == this)
309
this.mController.input = null;
310
]]></body>
311
</method>
312
313
<!-- ::::::::::::: popup opening ::::::::::::: -->
314
315
<method name="openPopup">
316
<body><![CDATA[
317
if (this.focused)
318
this.popup.openAutocompletePopup(this, this);
319
]]></body>
320
</method>
321
322
<method name="closePopup">
323
<body><![CDATA[
324
this.popup.closePopup();
325
]]></body>
326
</method>
327
328
<method name="showHistoryPopup">
329
<body><![CDATA[
330
// Store our "normal" maxRows on the popup, so that it can reset the
331
// value when the popup is hidden.
332
this.popup._normalMaxRows = this.maxRows;
333
334
// Increase our maxRows temporarily, since we want the dropdown to
335
// be bigger in this case. The popup's popupshowing/popuphiding
336
// handlers will take care of resetting this.
337
this.maxRows = this.maxDropMarkerRows;
338
339
// Ensure that we have focus.
340
if (!this.focused)
341
this.focus();
342
this.attachController();
343
this.mController.startSearch("");
344
]]></body>
345
</method>
346
347
<method name="toggleHistoryPopup">
348
<body><![CDATA[
349
if (!this.popup.popupOpen)
350
this.showHistoryPopup();
351
else
352
this.closePopup();
353
]]></body>
354
</method>
355
356
<!-- ::::::::::::: event dispatching ::::::::::::: -->
357
358
<method name="initEventHandler">
359
<parameter name="aEventType"/>
360
<body><![CDATA[
361
let handlerString = this.getAttribute("on" + aEventType);
362
if (handlerString) {
363
return (new Function("eventType", "param", handlerString)).bind(this, aEventType);
364
}
365
return null;
366
]]></body>
367
</method>
368
369
<!-- ::::::::::::: key handling ::::::::::::: -->
370
371
<field name="_selectionDetails">null</field>
372
<method name="onKeyPress">
373
<parameter name="aEvent"/>
374
<body><![CDATA[
375
return this.handleKeyPress(aEvent);
376
]]></body>
377
</method>
378
379
<method name="handleKeyPress">
380
<parameter name="aEvent"/>
381
<parameter name="aOptions"/>
382
<body><![CDATA[
383
if (aEvent.target.localName != "textbox")
384
return true; // Let child buttons of autocomplete take input
385
386
// Re: urlbarDeferred, see the comment in urlbarBindings.xml.
387
if (aEvent.defaultPrevented && !aEvent.urlbarDeferred) {
388
return false;
389
}
390
391
const isMac = /Mac/.test(navigator.platform);
392
var cancel = false;
393
394
// Catch any keys that could potentially move the caret. Ctrl can be
395
// used in combination with these keys on Windows and Linux; and Alt
396
// can be used on OS X, so make sure the unused one isn't used.
397
let metaKey = isMac ? aEvent.ctrlKey : aEvent.altKey;
398
if (!metaKey) {
399
switch (aEvent.keyCode) {
400
case KeyEvent.DOM_VK_LEFT:
401
case KeyEvent.DOM_VK_RIGHT:
402
case KeyEvent.DOM_VK_HOME:
403
cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
404
break;
405
}
406
}
407
408
// Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
409
if (!aEvent.ctrlKey && !aEvent.altKey) {
410
switch (aEvent.keyCode) {
411
case KeyEvent.DOM_VK_TAB:
412
if (this.tabScrolling && this.popup.popupOpen)
413
cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
414
KeyEvent.DOM_VK_UP :
415
KeyEvent.DOM_VK_DOWN);
416
else if (this.forceComplete && this.mController.matchCount >= 1)
417
this.mController.handleTab();
418
break;
419
case KeyEvent.DOM_VK_UP:
420
case KeyEvent.DOM_VK_DOWN:
421
case KeyEvent.DOM_VK_PAGE_UP:
422
case KeyEvent.DOM_VK_PAGE_DOWN:
423
cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
424
break;
425
}
426
}
427
428
// Handle readline/emacs-style navigation bindings on Mac.
429
if (isMac &&
430
this.popup.popupOpen &&
431
aEvent.ctrlKey &&
432
(aEvent.key === "n" || aEvent.key === "p")) {
433
const effectiveKey = (aEvent.key === "p") ?
434
KeyEvent.DOM_VK_UP :
435
KeyEvent.DOM_VK_DOWN;
436
cancel = this.mController.handleKeyNavigation(effectiveKey);
437
}
438
439
// Handle keys we know aren't part of a shortcut, even with Alt or
440
// Ctrl.
441
switch (aEvent.keyCode) {
442
case KeyEvent.DOM_VK_ESCAPE:
443
cancel = this.mController.handleEscape();
444
break;
445
case KeyEvent.DOM_VK_RETURN:
446
if (isMac) {
447
// Prevent the default action, since it will beep on Mac
448
if (aEvent.metaKey)
449
aEvent.preventDefault();
450
}
451
if (this.popup.selectedIndex >= 0) {
452
this._selectionDetails = {
453
index: this.popup.selectedIndex,
454
kind: "key",
455
};
456
}
457
cancel = this.handleEnter(aEvent, aOptions);
458
break;
459
case KeyEvent.DOM_VK_DELETE:
460
if (isMac && !aEvent.shiftKey) {
461
break;
462
}
463
cancel = this.handleDelete();
464
break;
465
case KeyEvent.DOM_VK_BACK_SPACE:
466
if (isMac && aEvent.shiftKey) {
467
cancel = this.handleDelete();
468
}
469
break;
470
case KeyEvent.DOM_VK_DOWN:
471
case KeyEvent.DOM_VK_UP:
472
if (aEvent.altKey)
473
this.toggleHistoryPopup();
474
break;
475
case KeyEvent.DOM_VK_F4:
476
if (!isMac) {
477
this.toggleHistoryPopup();
478
}
479
break;
480
}
481
482
if (cancel) {
483
aEvent.stopPropagation();
484
aEvent.preventDefault();
485
}
486
487
return true;
488
]]></body>
489
</method>
490
491
<method name="handleEnter">
492
<parameter name="event"/>
493
<body><![CDATA[
494
return this.mController.handleEnter(false, event || null);
495
]]></body>
496
</method>
497
498
<method name="handleDelete">
499
<body><![CDATA[
500
return this.mController.handleDelete();
501
]]></body>
502
</method>
503
504
<!-- ::::::::::::: miscellaneous ::::::::::::: -->
505
506
<method name="initSearchNames">
507
<body><![CDATA[
508
if (!this.mSearchNames) {
509
var names = this.getAttribute("autocompletesearch");
510
if (!names)
511
this.mSearchNames = [];
512
else
513
this.mSearchNames = names.split(" ");
514
}
515
]]></body>
516
</method>
517
518
<method name="_focus">
519
<!-- doesn't reset this.mController -->
520
<body><![CDATA[
521
this._dontBlur = true;
522
this.focus();
523
this._dontBlur = false;
524
]]></body>
525
</method>
526
527
<method name="resetActionType">
528
<body><![CDATA[
529
if (this.mIgnoreInput)
530
return;
531
this.removeAttribute("actiontype");
532
]]></body>
533
</method>
534
535
<method name="_setValueInternal">
536
<parameter name="aValue"/>
537
<parameter name="aIsUserInput"/>
538
<body><![CDATA[
539
this.mIgnoreInput = true;
540
541
if (typeof this.onBeforeValueSet == "function")
542
aValue = this.onBeforeValueSet(aValue);
543
544
if (typeof this.trimValue == "function" &&
545
!this._textValueSetByCompleteDefault)
546
aValue = this.trimValue(aValue);
547
548
this.valueIsTyped = false;
549
if (aIsUserInput) {
550
this.inputField.setUserInput(aValue);
551
} else {
552
this.inputField.value = aValue;
553
}
554
555
if (typeof this.formatValue == "function")
556
this.formatValue();
557
558
this.mIgnoreInput = false;
559
var event = document.createEvent("Events");
560
event.initEvent("ValueChange", true, true);
561
this.inputField.dispatchEvent(event);
562
return aValue;
563
]]></body>
564
</method>
565
566
<method name="onInput">
567
<parameter name="aEvent"/>
568
<body><![CDATA[
569
if (!this.mIgnoreInput && this.mController.input == this) {
570
this.valueIsTyped = true;
571
this.mController.handleText();
572
}
573
this.resetActionType();
574
]]></body>
575
</method>
576
</implementation>
577
578
<handlers>
579
<handler event="input"><![CDATA[
580
this.onInput(event);
581
]]></handler>
582
583
<handler event="keypress" phase="capturing" group="system"
584
action="return this.onKeyPress(event);"/>
585
586
<handler event="compositionstart" phase="capturing"
587
action="if (this.mController.input == this) this.mController.handleStartComposition();"/>
588
589
<handler event="compositionend" phase="capturing"
590
action="if (this.mController.input == this) this.mController.handleEndComposition();"/>
591
592
<handler event="focus" phase="capturing"><![CDATA[
593
this.attachController();
594
if (window.gBrowser && window.gBrowser.selectedBrowser.hasAttribute("usercontextid")) {
595
this.userContextId = parseInt(window.gBrowser.selectedBrowser.getAttribute("usercontextid"));
596
} else {
597
this.userContextId = 0;
598
}
599
]]></handler>
600
601
<handler event="blur" phase="capturing"><![CDATA[
602
if (!this._dontBlur) {
603
if (this.forceComplete && this.mController.matchCount >= 1) {
604
// If forceComplete is requested, we need to call the enter processing
605
// on blur so the input will be forced to the closest match.
606
// Thunderbird is the only consumer of forceComplete and this is used
607
// to force an recipient's email to the exact address book entry.
608
this.mController.handleEnter(true);
609
}
610
if (!this.ignoreBlurWhileSearching)
611
this.detachController();
612
}
613
]]></handler>
614
</handlers>
615
</binding>
616
</bindings>