1 <?xml version="1.0"?>
2
3 <bindings id="textBindings"
4 xmlns="http://www.mozilla.org/xbl"
5 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
6 xmlns:html="http://www.w3.org/1999/xhtml">
7
8 <!-- bound to <description>s -->
9 <binding id="text-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
10 <implementation implements="nsIDOMXULDescriptionElement, nsIAccessibleProvider">
11 <property name="accessibleType" readonly="true">
12 <getter>
13 <![CDATA[
14 return Components.interfaces.nsIAccessibleProvider.XULText;
15 ]]>
16 </getter>
17 </property>
18 <property name="value" onget="return this.getAttribute('value');"
19 onset="this.setAttribute('value', val); return val;"/>
20 <property name="crop" onget="return this.getAttribute('crop');"
21 onset="this.setAttribute('crop', val); return val;"/>
22 </implementation>
23 </binding>
24
25 <binding id="text-label" extends="chrome://global/content/bindings/text.xml#text-base">
26 <implementation implements="nsIDOMXULLabelElement">
27 <property name="accessKey">
28 <getter>
29 <![CDATA[
30 var accessKey = this.getAttribute('accesskey');
31 return accessKey ? accessKey[0] : null;
32 ]]>
33 </getter>
34 <setter>
35 <![CDATA[
36 this.setAttribute('accesskey', val);
37 return val;
38 ]]>
39 </setter>
40 </property>
41
42 <property name="control" onget="return getAttribute('control');">
43 <setter>
44 <![CDATA[
45 // After this gets set, the label will use the binding #label-control
46 this.setAttribute('control', val);
47 return val;
48 ]]>
49 </setter>
50 </property>
51 </implementation>
52 </binding>
53
54 <binding id="label-control" extends="chrome://global/content/bindings/text.xml#text-label">
55 <content>
56 <html:span anonid="accessKeyParens"><children/></html:span>
57 </content>
58 <implementation implements="nsIDOMXULLabelElement">
59 <constructor>
60 <![CDATA[
61 try {
62 var prefs = Components.classes["@mozilla.org/preferences-service;1"].
63 getService(Components.interfaces.nsIPrefBranch);
64 this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);
65
66 const nsIPrefLocalizedString =
67 Components.interfaces.nsIPrefLocalizedString;
68
69 const prefNameInsertSeparator =
70 "intl.menuitems.insertseparatorbeforeaccesskeys";
71 const prefNameAlwaysAppendAccessKey =
72 "intl.menuitems.alwaysappendaccesskeys";
73
74 var val = prefs.getComplexValue(prefNameInsertSeparator,
75 nsIPrefLocalizedString).data;
76 this.mInsertSeparator = (val == "true");
77
78 val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
79 nsIPrefLocalizedString).data;
80 this.mAlwaysAppendAccessKey = (val == "true");
81 }
82 catch (e) { }
83 this.formatAccessKey();
84 ]]>
85 </constructor>
86
87 <method name="formatAccessKey">
88 <body>
89 <![CDATA[
90 if (!this.mUnderlineAccesskey)
91 return;
92
93 var control = this.labeledControlElement;
94 if (!control) {
95 var bindingParent = document.getBindingParent(this);
96 if (bindingParent instanceof Components.interfaces.nsIDOMXULLabeledControlElement) {
97 control = bindingParent; // For controls that make the <label> an anon child
98 }
99 }
100 if (control) {
101 control.labelElement = this;
102 }
103
104 var afterLabel = document.getAnonymousElementByAttribute(this, "anonid", "accessKeyParens");
105 afterLabel.textContent = ""; // This does not clear real nodes!
106
107 var oldAccessKey = this.getElementsByAttribute('class', 'accesskey').item(0);
108 if (oldAccessKey) { // Clear old accesskey
109 this.mergeElement(oldAccessKey);
110 }
111
112 var oldHiddenSpan =
113 this.getElementsByAttribute('class', 'hiddenColon').item(0);
114 if (oldHiddenSpan) {
115 this.mergeElement(oldHiddenSpan);
116 }
117
118 var accessKey = this.accessKey;
119 var labelText = this.textContent;
120 if (!accessKey || !labelText || !control) {
121 return;
122 }
123 var accessKeyIndex = -1;
124 if (!this.mAlwaysAppendAccessKey) {
125 accessKeyIndex = labelText.indexOf(accessKey);
126 if (accessKeyIndex < 0) { // Try again in upper case
127 accessKeyIndex =
128 labelText.toUpperCase().indexOf(accessKey.toUpperCase());
129 }
130 }
131
132 const HTML_NS = "http://www.w3.org/1999/xhtml";
133 var span = document.createElementNS(HTML_NS, "span");
134 span.className = "accesskey";
135
136 // Note that if you change the following code, see the comment of
137 // nsTextBoxFrame::UpdateAccessTitle.
138
139 // If accesskey is not in string, append in parentheses
140 if (accessKeyIndex < 0) {
141 // If end is colon, we should insert before colon.
142 // i.e., "label:" -> "label(X):"
143 var colonHidden = false;
144 if (/:$/.test(labelText)) {
145 labelText = labelText.slice(0, -1);
146 var hiddenSpan = document.createElementNS(HTML_NS, "span");
147 hiddenSpan.className = "hiddenColon";
148 hiddenSpan.style.display = "none";
149 // Hide the last colon by using span element.
150 // I.e., label<span style="display:none;">:</span>
151 this.wrapChar(hiddenSpan, labelText.length);
152 colonHidden = true;
153 }
154 // If end is space(U+20),
155 // we should not add space before parentheses.
156 var endIsSpace = false;
157 if (/ $/.test(labelText)) {
158 endIsSpace = true;
159 }
160 if (this.mInsertSeparator && !endIsSpace)
161 afterLabel.textContent = " (";
162 else
163 afterLabel.textContent = "(";
164 span.textContent = accessKey.toUpperCase();
165 afterLabel.appendChild(span);
166 if (!colonHidden)
167 afterLabel.appendChild(document.createTextNode(")"));
168 else
169 afterLabel.appendChild(document.createTextNode("):"));
170 return;
171 }
172 this.wrapChar(span, accessKeyIndex);
173 ]]>
174 </body>
175 </method>
176
177 <method name="wrapChar">
178 <parameter name="element"/>
179 <parameter name="index"/>
180 <body>
181 <![CDATA[
182 var treeWalker = document.createTreeWalker(this,
183 NodeFilter.SHOW_TEXT,
184 null,
185 true);
186 var node = treeWalker.nextNode();
187 while (index >= node.length) {
188 index -= node.length;
189 node = treeWalker.nextNode();
190 }
191 if (index) {
192 node = node.splitText(index);
193 }
194 node.parentNode.insertBefore(element, node);
195 if (node.length > 1) {
196 node.splitText(1);
197 }
198 element.appendChild(node);
199 ]]>
200 </body>
201 </method>
202
203 <method name="mergeElement">
204 <parameter name="element"/>
205 <body>
206 <![CDATA[
207 if (element.previousSibling instanceof Text) {
208 element.previousSibling.appendData(element.textContent)
209 }
210 else {
211 element.parentNode.insertBefore(element.firstChild, element);
212 }
213 element.parentNode.removeChild(element);
214 ]]>
215 </body>
216 </method>
217
218 <field name="mUnderlineAccesskey">
219 !/Mac/.test(navigator.platform)
220 </field>
221 <field name="mInsertSeparator">true</field>
222 <field name="mAlwaysAppendAccessKey">false</field>
223
224 <property name="accessKey">
225 <getter>
226 <![CDATA[
227 var accessKey = null;
228 var labeledEl = this.labeledControlElement;
229 if (labeledEl) {
230 accessKey = labeledEl.getAttribute('accesskey');
231 }
232 if (!accessKey) {
233 accessKey = this.getAttribute('accesskey');
234 }
235 return accessKey ? accessKey[0] : null;
236 ]]>
237 </getter>
238 <setter>
239 <![CDATA[
240 // If this label already has an accesskey attribute store it here as well
241 if (this.hasAttribute('accesskey')) {
242 this.setAttribute('accesskey', val);
243 }
244 var control = this.labeledControlElement;
245 if (control) {
246 control.setAttribute('accesskey', val);
247 }
248 this.formatAccessKey();
249 return val;
250 ]]>
251 </setter>
252 </property>
253
254 <property name="labeledControlElement" readonly="true"
255 onget="var control = this.control; return control ? document.getElementById(control) : null;" />
256
257 <property name="control" onget="return this.getAttribute('control');">
258 <setter>
259 <![CDATA[
260 var control = this.labeledControlElement;
261 if (control) {
262 control.labelElement = null; // No longer pointed to be this label
263 }
264 this.setAttribute('control', val);
265 this.formatAccessKey();
266 return val;
267 ]]>
268 </setter>
269 </property>
270
271 </implementation>
272
273 <handlers>
274 <handler event="click" action="if (this.disabled) return;
275 var controlElement = this.labeledControlElement;
276 if(controlElement)
277 controlElement.focus();
278 "/>
279 </handlers>
280 </binding>
281
282 <binding id="text-link" extends="chrome://global/content/bindings/text.xml#text-label">
283 <implementation implements="nsIAccessibleProvider">
284 <property name="accessibleType" readonly="true">
285 <getter>
286 <![CDATA[
287 return Components.interfaces.nsIAccessibleProvider.XULLink;
288 ]]>
289 </getter>
290 </property>
291 <property name="href" onget="return this.getAttribute('href');"
292 onset="this.setAttribute('href', val); return val;" />
293 <method name="open">
294 <parameter name="aEvent"/>
295 <body>
296 <![CDATA[
297 var href = this.href;
298 if (!href || this.disabled || aEvent.getPreventDefault())
299 return;
300
301 var uri = null;
302 try {
303 const nsISSM = Components.interfaces.nsIScriptSecurityManager;
304 const secMan =
305 Components.classes["@mozilla.org/scriptsecuritymanager;1"]
306 .getService(nsISSM);
307
308 const ioService =
309 Components.classes["@mozilla.org/network/io-service;1"]
310 .getService(Components.interfaces.nsIIOService);
311
312 uri = ioService.newURI(href, null, null);
313
314 var nullPrincipal =
315 Components.classes["@mozilla.org/nullprincipal;1"]
316 .createInstance(Components.interfaces.nsIPrincipal);
317 try {
318 secMan.checkLoadURIWithPrincipal(nullPrincipal, uri,
319 nsISSM.DISALLOW_INHERIT_PRINCIPAL)
320 }
321 catch (ex) {
322 var msg = "Error: Cannot open a " + uri.scheme + ": link using \
323 the text-link binding.";
324 Components.utils.reportError(msg);
325 return;
326 }
327
328 const cID = "@mozilla.org/uriloader/external-protocol-service;1";
329 const nsIEPS = Components.interfaces.nsIExternalProtocolService;
330 var protocolSvc = Components.classes[cID].getService(nsIEPS);
331
332 // if the scheme is not an exposed protocol, then opening this link
333 // should be deferred to the system's external protocol handler
334 if (!protocolSvc.isExposedProtocol(uri.scheme)) {
335 protocolSvc.loadUrl(uri);
336 aEvent.preventDefault()
337 return;
338 }
339
340 }
341 catch (ex) {
342 Components.utils.reportError(ex);
343 }
344
345 // otherwise, fall back to opening the anchor directly
346 var win = window;
347 if (window instanceof Components.interfaces.nsIDOMChromeWindow) {
348 while (win.opener && !win.opener.closed)
349 win = win.opener;
350 }
351
352 if (uri)
353 win.open(uri.spec);
354 else
355 win.open(href);
356
357 aEvent.preventDefault();
358 ]]>
359 </body>
360 </method>
361 </implementation>
362
363 <handlers>
364 <handler event="click" phase="capturing" button="0" action="this.open(event)"/>
365 <handler event="keypress" preventdefault="true" keycode="VK_ENTER" action="this.click()" />
366 <handler event="keypress" preventdefault="true" keycode="VK_RETURN" action="this.click()" />
367 </handlers>
368 </binding>
369
370 </bindings>