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