|
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="popupBindings" |
|
8 xmlns="http://www.mozilla.org/xbl" |
|
9 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" |
|
10 xmlns:xbl="http://www.mozilla.org/xbl"> |
|
11 |
|
12 <binding id="popup-base"> |
|
13 <resources> |
|
14 <stylesheet src="chrome://global/skin/popup.css"/> |
|
15 </resources> |
|
16 |
|
17 <implementation implements="nsIDOMXULPopupElement"> |
|
18 <property name="label" onget="return this.getAttribute('label');" |
|
19 onset="this.setAttribute('label', val); return val;"/> |
|
20 <property name="position" onget="return this.getAttribute('position');" |
|
21 onset="this.setAttribute('position', val); return val;"/> |
|
22 <property name="popupBoxObject"> |
|
23 <getter> |
|
24 return this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject); |
|
25 </getter> |
|
26 </property> |
|
27 |
|
28 <property name="state" readonly="true" |
|
29 onget="return this.popupBoxObject.popupState"/> |
|
30 |
|
31 <property name="triggerNode" readonly="true" |
|
32 onget="return this.popupBoxObject.triggerNode"/> |
|
33 |
|
34 <property name="anchorNode" readonly="true" |
|
35 onget="return this.popupBoxObject.anchorNode"/> |
|
36 |
|
37 <method name="openPopup"> |
|
38 <parameter name="aAnchorElement"/> |
|
39 <parameter name="aPosition"/> |
|
40 <parameter name="aX"/> |
|
41 <parameter name="aY"/> |
|
42 <parameter name="aIsContextMenu"/> |
|
43 <parameter name="aAttributesOverride"/> |
|
44 <parameter name="aTriggerEvent"/> |
|
45 <body> |
|
46 <![CDATA[ |
|
47 try { |
|
48 var popupBox = this.popupBoxObject; |
|
49 if (popupBox) |
|
50 popupBox.openPopup(aAnchorElement, aPosition, aX, aY, |
|
51 aIsContextMenu, aAttributesOverride, aTriggerEvent); |
|
52 } catch(e) {} |
|
53 ]]> |
|
54 </body> |
|
55 </method> |
|
56 |
|
57 <method name="openPopupAtScreen"> |
|
58 <parameter name="aX"/> |
|
59 <parameter name="aY"/> |
|
60 <parameter name="aIsContextMenu"/> |
|
61 <parameter name="aTriggerEvent"/> |
|
62 <body> |
|
63 <![CDATA[ |
|
64 try { |
|
65 var popupBox = this.popupBoxObject; |
|
66 if (popupBox) |
|
67 popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent); |
|
68 } catch(e) {} |
|
69 ]]> |
|
70 </body> |
|
71 </method> |
|
72 |
|
73 <method name="showPopup"> |
|
74 <parameter name="element"/> |
|
75 <parameter name="xpos"/> |
|
76 <parameter name="ypos"/> |
|
77 <parameter name="popuptype"/> |
|
78 <parameter name="anchoralignment"/> |
|
79 <parameter name="popupalignment"/> |
|
80 <body> |
|
81 <![CDATA[ |
|
82 var popupBox = null; |
|
83 var menuBox = null; |
|
84 try { |
|
85 popupBox = this.popupBoxObject; |
|
86 } catch(e) {} |
|
87 try { |
|
88 menuBox = this.parentNode.boxObject; |
|
89 } catch(e) {} |
|
90 if (menuBox instanceof Components.interfaces.nsIMenuBoxObject) |
|
91 menuBox.openMenu(true); |
|
92 else if (popupBox) |
|
93 popupBox.showPopup(element, this, xpos, ypos, popuptype, anchoralignment, popupalignment); |
|
94 ]]> |
|
95 </body> |
|
96 </method> |
|
97 |
|
98 <method name="hidePopup"> |
|
99 <body> |
|
100 <![CDATA[ |
|
101 var popupBox = null; |
|
102 var menuBox = null; |
|
103 try { |
|
104 popupBox = this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject); |
|
105 } catch(e) {} |
|
106 try { |
|
107 menuBox = this.parentNode.boxObject; |
|
108 } catch(e) {} |
|
109 if (menuBox instanceof Components.interfaces.nsIMenuBoxObject) |
|
110 menuBox.openMenu(false); |
|
111 else if (popupBox) |
|
112 popupBox.hidePopup(); |
|
113 ]]> |
|
114 </body> |
|
115 </method> |
|
116 |
|
117 <property name="autoPosition"> |
|
118 <getter> |
|
119 <![CDATA[ |
|
120 return this.popupBoxObject.autoPosition; |
|
121 ]]> |
|
122 </getter> |
|
123 <setter> |
|
124 <![CDATA[ |
|
125 return this.popupBoxObject.autoPosition = val; |
|
126 ]]> |
|
127 </setter> |
|
128 </property> |
|
129 |
|
130 <property name="alignmentPosition" readonly="true"> |
|
131 <getter> |
|
132 <![CDATA[ |
|
133 return this.popupBoxObject.alignmentPosition; |
|
134 ]]> |
|
135 </getter> |
|
136 </property> |
|
137 |
|
138 <property name="alignmentOffset" readonly="true"> |
|
139 <getter> |
|
140 <![CDATA[ |
|
141 return this.popupBoxObject.alignmentOffset; |
|
142 ]]> |
|
143 </getter> |
|
144 </property> |
|
145 |
|
146 <method name="enableKeyboardNavigator"> |
|
147 <parameter name="aEnableKeyboardNavigator"/> |
|
148 <body> |
|
149 <![CDATA[ |
|
150 this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator); |
|
151 ]]> |
|
152 </body> |
|
153 </method> |
|
154 |
|
155 <method name="enableRollup"> |
|
156 <parameter name="aEnableRollup"/> |
|
157 <body> |
|
158 <![CDATA[ |
|
159 this.popupBoxObject.enableRollup(aEnableRollup); |
|
160 ]]> |
|
161 </body> |
|
162 </method> |
|
163 |
|
164 <method name="sizeTo"> |
|
165 <parameter name="aWidth"/> |
|
166 <parameter name="aHeight"/> |
|
167 <body> |
|
168 <![CDATA[ |
|
169 this.popupBoxObject.sizeTo(aWidth, aHeight); |
|
170 ]]> |
|
171 </body> |
|
172 </method> |
|
173 |
|
174 <method name="moveTo"> |
|
175 <parameter name="aLeft"/> |
|
176 <parameter name="aTop"/> |
|
177 <body> |
|
178 <![CDATA[ |
|
179 this.popupBoxObject.moveTo(aLeft, aTop); |
|
180 ]]> |
|
181 </body> |
|
182 </method> |
|
183 |
|
184 <method name="moveToAnchor"> |
|
185 <parameter name="aAnchorElement"/> |
|
186 <parameter name="aPosition"/> |
|
187 <parameter name="aX"/> |
|
188 <parameter name="aY"/> |
|
189 <parameter name="aAttributesOverride"/> |
|
190 <body> |
|
191 <![CDATA[ |
|
192 this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride); |
|
193 ]]> |
|
194 </body> |
|
195 </method> |
|
196 |
|
197 <method name="getOuterScreenRect"> |
|
198 <body> |
|
199 <![CDATA[ |
|
200 return this.popupBoxObject.getOuterScreenRect(); |
|
201 ]]> |
|
202 </body> |
|
203 </method> |
|
204 </implementation> |
|
205 |
|
206 </binding> |
|
207 |
|
208 <binding id="popup" role="xul:menupopup" |
|
209 extends="chrome://global/content/bindings/popup.xml#popup-base"> |
|
210 |
|
211 <content> |
|
212 <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical" |
|
213 smoothscroll="false"> |
|
214 <children/> |
|
215 </xul:arrowscrollbox> |
|
216 </content> |
|
217 |
|
218 <handlers> |
|
219 <handler event="popupshowing" phase="target"> |
|
220 <![CDATA[ |
|
221 var array = []; |
|
222 var width = 0; |
|
223 for (var menuitem = this.firstChild; menuitem; menuitem = menuitem.nextSibling) { |
|
224 if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) { |
|
225 var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel"); |
|
226 if (accel && accel.boxObject) { |
|
227 array.push(accel); |
|
228 if (accel.boxObject.width > width) |
|
229 width = accel.boxObject.width; |
|
230 } |
|
231 } |
|
232 } |
|
233 for (var i = 0; i < array.length; i++) |
|
234 array[i].width = width; |
|
235 ]]> |
|
236 </handler> |
|
237 </handlers> |
|
238 </binding> |
|
239 |
|
240 <binding id="panel" role="xul:panel" |
|
241 extends="chrome://global/content/bindings/popup.xml#popup-base"> |
|
242 <implementation implements="nsIDOMXULPopupElement"> |
|
243 <field name="_prevFocus">0</field> |
|
244 <field name="_dragBindingAlive">true</field> |
|
245 <constructor> |
|
246 <![CDATA[ |
|
247 if (this.getAttribute("backdrag") == "true" && !this._draggableStarted) { |
|
248 this._draggableStarted = true; |
|
249 try { |
|
250 let tmp = {}; |
|
251 Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp); |
|
252 let draghandle = new tmp.WindowDraggingElement(this); |
|
253 draghandle.mouseDownCheck = function () this._dragBindingAlive; |
|
254 } catch (e) {} |
|
255 } |
|
256 ]]> |
|
257 </constructor> |
|
258 </implementation> |
|
259 |
|
260 <handlers> |
|
261 <handler event="popupshowing"><![CDATA[ |
|
262 // Capture the previous focus before has a chance to get set inside the panel |
|
263 try { |
|
264 this._prevFocus = document.commandDispatcher.focusedElement; |
|
265 if (!this._prevFocus) // Content window has focus |
|
266 this._prevFocus = document.commandDispatcher.focusedWindow; |
|
267 } catch (ex) { |
|
268 this._prevFocus = document.activeElement; |
|
269 } |
|
270 ]]></handler> |
|
271 <handler event="popupshown"><![CDATA[ |
|
272 // Fire event for accessibility APIs |
|
273 var alertEvent = document.createEvent("Events"); |
|
274 alertEvent.initEvent("AlertActive", true, true); |
|
275 this.dispatchEvent(alertEvent); |
|
276 ]]></handler> |
|
277 <handler event="popuphiding"><![CDATA[ |
|
278 try { |
|
279 this._currentFocus = document.commandDispatcher.focusedElement; |
|
280 } catch (e) { |
|
281 this._currentFocus = document.activeElement; |
|
282 } |
|
283 ]]></handler> |
|
284 <handler event="popuphidden"><![CDATA[ |
|
285 var currentFocus = this._currentFocus; |
|
286 var prevFocus = this._prevFocus; |
|
287 this._currentFocus = null; |
|
288 this._prevFocus = null; |
|
289 if (prevFocus && currentFocus && this.getAttribute("norestorefocus") != "true") { |
|
290 // Try to restore focus |
|
291 try { |
|
292 if (document.commandDispatcher.focusedWindow != window) |
|
293 return; // Focus has already been set to a window outside of this panel |
|
294 } catch(ex) {} |
|
295 while (currentFocus) { |
|
296 if (currentFocus == this) { |
|
297 // Focus was set on an element inside this panel, |
|
298 // so we need to move it back to where it was previously |
|
299 try { |
|
300 let fm = Components.classes["@mozilla.org/focus-manager;1"] |
|
301 .getService(Components.interfaces.nsIFocusManager); |
|
302 fm.setFocus(prevFocus, fm.FLAG_NOSCROLL); |
|
303 } catch(e) { |
|
304 prevFocus.focus(); |
|
305 } |
|
306 return; |
|
307 } |
|
308 currentFocus = currentFocus.parentNode; |
|
309 } |
|
310 } |
|
311 ]]></handler> |
|
312 </handlers> |
|
313 </binding> |
|
314 |
|
315 <binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel"> |
|
316 <content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false"> |
|
317 <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1" |
|
318 xbl:inherits="side,panelopen"> |
|
319 <xul:box anonid="arrowbox" class="panel-arrowbox"> |
|
320 <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/> |
|
321 </xul:box> |
|
322 <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1"> |
|
323 <children/> |
|
324 <xul:box class="panel-inner-arrowcontentfooter" xbl:inherits="footertype" hidden="true"/> |
|
325 </xul:box> |
|
326 </xul:vbox> |
|
327 </content> |
|
328 <implementation> |
|
329 <field name="_fadeTimer">null</field> |
|
330 <method name="sizeTo"> |
|
331 <parameter name="aWidth"/> |
|
332 <parameter name="aHeight"/> |
|
333 <body> |
|
334 <![CDATA[ |
|
335 this.popupBoxObject.sizeTo(aWidth, aHeight); |
|
336 if (this.state == "open") |
|
337 this.adjustArrowPosition(); |
|
338 ]]> |
|
339 </body> |
|
340 </method> |
|
341 <method name="moveTo"> |
|
342 <parameter name="aLeft"/> |
|
343 <parameter name="aTop"/> |
|
344 <body> |
|
345 <![CDATA[ |
|
346 this.popupBoxObject.moveTo(aLeft, aTop); |
|
347 if (this.state == "open") |
|
348 this.adjustArrowPosition(); |
|
349 ]]> |
|
350 </body> |
|
351 </method> |
|
352 <method name="moveToAnchor"> |
|
353 <parameter name="aAnchorElement"/> |
|
354 <parameter name="aPosition"/> |
|
355 <parameter name="aX"/> |
|
356 <parameter name="aY"/> |
|
357 <parameter name="aAttributesOverride"/> |
|
358 <body> |
|
359 <![CDATA[ |
|
360 this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride); |
|
361 if (this.state == "open") |
|
362 this.adjustArrowPosition(); |
|
363 ]]> |
|
364 </body> |
|
365 </method> |
|
366 <method name="adjustArrowPosition"> |
|
367 <body> |
|
368 <![CDATA[ |
|
369 var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow"); |
|
370 |
|
371 var anchor = this.anchorNode; |
|
372 if (!anchor) { |
|
373 arrow.hidden = true; |
|
374 return; |
|
375 } |
|
376 |
|
377 var container = document.getAnonymousElementByAttribute(this, "anonid", "container"); |
|
378 var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox"); |
|
379 |
|
380 var position = this.alignmentPosition; |
|
381 var offset = this.alignmentOffset; |
|
382 |
|
383 // if this panel has a "sliding" arrow, we may have previously set margins... |
|
384 arrowbox.style.removeProperty("transform"); |
|
385 if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) { |
|
386 container.orient = "horizontal"; |
|
387 arrowbox.orient = "vertical"; |
|
388 if (position.indexOf("_after") > 0) { |
|
389 arrowbox.pack = "end"; |
|
390 } else { |
|
391 arrowbox.pack = "start"; |
|
392 } |
|
393 arrowbox.style.transform = "translate(0, " + -offset + "px)"; |
|
394 |
|
395 // The assigned side stays the same regardless of direction. |
|
396 var isRTL = (window.getComputedStyle(this).direction == "rtl"); |
|
397 |
|
398 if (position.indexOf("start_") == 0) { |
|
399 container.dir = "reverse"; |
|
400 this.setAttribute("side", isRTL ? "left" : "right"); |
|
401 } |
|
402 else { |
|
403 container.dir = ""; |
|
404 this.setAttribute("side", isRTL ? "right" : "left"); |
|
405 } |
|
406 } |
|
407 else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) { |
|
408 container.orient = ""; |
|
409 arrowbox.orient = ""; |
|
410 if (position.indexOf("_end") > 0) { |
|
411 arrowbox.pack = "end"; |
|
412 } else { |
|
413 arrowbox.pack = "start"; |
|
414 } |
|
415 arrowbox.style.transform = "translate(" + -offset + "px, 0)"; |
|
416 |
|
417 if (position.indexOf("before_") == 0) { |
|
418 container.dir = "reverse"; |
|
419 this.setAttribute("side", "bottom"); |
|
420 } |
|
421 else { |
|
422 container.dir = ""; |
|
423 this.setAttribute("side", "top"); |
|
424 } |
|
425 } |
|
426 |
|
427 arrow.hidden = false; |
|
428 ]]> |
|
429 </body> |
|
430 </method> |
|
431 </implementation> |
|
432 <handlers> |
|
433 <handler event="popupshowing" phase="target"> |
|
434 <![CDATA[ |
|
435 this.adjustArrowPosition(); |
|
436 |
|
437 // set fading |
|
438 var fade = this.getAttribute("fade"); |
|
439 var fadeDelay = (fade == "fast") ? 1 : fade == "slow" ? 4000 : 0; |
|
440 if (fadeDelay) { |
|
441 this._fadeTimer = setTimeout(function (self) { |
|
442 self.style.opacity = 0.2; |
|
443 }, fadeDelay, this); |
|
444 } |
|
445 ]]> |
|
446 </handler> |
|
447 <handler event="popuphiding" phase="target"> |
|
448 clearTimeout(this._fadeTimer); |
|
449 this.style.removeProperty("opacity"); |
|
450 </handler> |
|
451 <handler event="transitionend" phase="target"> |
|
452 <![CDATA[ |
|
453 if (event.propertyName == "opacity" && |
|
454 event.originalTarget == this) { |
|
455 this.hidePopup(); |
|
456 this.style.removeProperty("opacity"); |
|
457 } |
|
458 ]]> |
|
459 </handler> |
|
460 <handler event="popupshown" phase="target"> |
|
461 this.setAttribute("panelopen", "true"); |
|
462 </handler> |
|
463 <handler event="popuphidden" phase="target"> |
|
464 this.removeAttribute("panelopen"); |
|
465 </handler> |
|
466 </handlers> |
|
467 </binding> |
|
468 |
|
469 <binding id="tooltip" role="xul:tooltip" |
|
470 extends="chrome://global/content/bindings/popup.xml#popup-base"> |
|
471 <content> |
|
472 <children> |
|
473 <xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/> |
|
474 </children> |
|
475 </content> |
|
476 |
|
477 <implementation> |
|
478 <field name="_mouseOutCount">0</field> |
|
479 <field name="_isMouseOver">false</field> |
|
480 |
|
481 <property name="label" |
|
482 onget="return this.getAttribute('label');" |
|
483 onset="this.setAttribute('label', val); return val;"/> |
|
484 |
|
485 <property name="page" onset="if (val) this.setAttribute('page', 'true'); |
|
486 else this.removeAttribute('page'); |
|
487 return val;" |
|
488 onget="return this.getAttribute('page') == 'true';"/> |
|
489 |
|
490 <!-- Given the supplied element within a page, set the tooltip's text to the text |
|
491 for that element. Returns true if text was assigned, and false if the no text |
|
492 is set, which normally would be used to cancel tooltip display. |
|
493 |
|
494 Note that DefaultTooltipTextProvider::GetNodeText() from nsDocShellTreeOwner.cpp |
|
495 also performs the same function, but for embedded clients that don't use a XUL/JS |
|
496 layer. These two should be kept synchronized. |
|
497 --> |
|
498 <method name="fillInPageTooltip"> |
|
499 <parameter name="tipElement"/> |
|
500 <body> |
|
501 <![CDATA[ |
|
502 // Don't show the tooltip if the tooltip node is a document or disconnected. |
|
503 if (!tipElement || !tipElement.ownerDocument || |
|
504 (tipElement.ownerDocument.compareDocumentPosition(tipElement) & document.DOCUMENT_POSITION_DISCONNECTED)) { |
|
505 return false; |
|
506 } |
|
507 |
|
508 var defView = tipElement.ownerDocument.defaultView; |
|
509 // XXX Work around bug 350679: |
|
510 // "Tooltips can be fired in documents with no view". |
|
511 if (!defView) |
|
512 return false; |
|
513 |
|
514 const XLinkNS = "http://www.w3.org/1999/xlink"; |
|
515 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
516 |
|
517 var titleText = null; |
|
518 var XLinkTitleText = null; |
|
519 var SVGTitleText = null; |
|
520 var XULtooltiptextText = null; |
|
521 var lookingForSVGTitle = true; |
|
522 var direction = tipElement.ownerDocument.dir; |
|
523 |
|
524 // If the element is invalid per HTML5 Forms specifications and has no title, |
|
525 // show the constraint validation error message. |
|
526 if ((tipElement instanceof HTMLInputElement || |
|
527 tipElement instanceof HTMLTextAreaElement || |
|
528 tipElement instanceof HTMLSelectElement || |
|
529 tipElement instanceof HTMLButtonElement) && |
|
530 !tipElement.hasAttribute('title') && |
|
531 (!tipElement.form || !tipElement.form.noValidate)) { |
|
532 // If the element is barred from constraint validation or valid, |
|
533 // the validation message will be the empty string. |
|
534 titleText = tipElement.validationMessage || null; |
|
535 } |
|
536 |
|
537 // If the element is an <input type='file'> without a title, we should show |
|
538 // the current file selection. |
|
539 if (!titleText && |
|
540 tipElement instanceof HTMLInputElement && |
|
541 tipElement.type == 'file' && |
|
542 !tipElement.hasAttribute('title')) { |
|
543 let files = tipElement.files; |
|
544 |
|
545 try { |
|
546 var bundle = Components.classes['@mozilla.org/intl/stringbundle;1'] |
|
547 .getService(Components.interfaces.nsIStringBundleService) |
|
548 .createBundle("chrome://global/locale/layout/HtmlForm.properties"); |
|
549 if (files.length == 0) { |
|
550 if (tipElement.multiple) { |
|
551 titleText = bundle.GetStringFromName("NoFilesSelected"); |
|
552 } else { |
|
553 titleText = bundle.GetStringFromName("NoFileSelected"); |
|
554 } |
|
555 } else { |
|
556 titleText = files[0].name; |
|
557 // For UX and performance (jank) reasons we cap the number of |
|
558 // files that we list in the tooltip to 20 plus a "and xxx more" |
|
559 // line, or to 21 if exactly 21 files were picked. |
|
560 const TRUNCATED_FILE_COUNT = 20; |
|
561 let count = Math.min(files.length, TRUNCATED_FILE_COUNT); |
|
562 for (let i = 1; i < count; ++i) { |
|
563 titleText += "\n" + files[i].name; |
|
564 } |
|
565 if (files.length == TRUNCATED_FILE_COUNT + 1) { |
|
566 titleText += "\n" + files[TRUNCATED_FILE_COUNT].name; |
|
567 } else if (files.length > TRUNCATED_FILE_COUNT + 1) { |
|
568 let xmoreStr = bundle.GetStringFromName("AndNMoreFiles"); |
|
569 let xmoreNum = files.length - TRUNCATED_FILE_COUNT; |
|
570 let tmp = {}; |
|
571 Components.utils.import("resource://gre/modules/PluralForm.jsm", tmp); |
|
572 let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("#1", xmoreNum); |
|
573 titleText += "\n" + andXMoreStr; |
|
574 } |
|
575 } |
|
576 } catch(e) {} |
|
577 } |
|
578 |
|
579 // Check texts against null so that title="" can be used to undefine a |
|
580 // title on a child element. |
|
581 while (tipElement && |
|
582 (titleText == null) && (XLinkTitleText == null) && |
|
583 (SVGTitleText == null) && (XULtooltiptextText == null)) { |
|
584 |
|
585 if (tipElement.nodeType == Node.ELEMENT_NODE) { |
|
586 if (tipElement.namespaceURI == XULNS) |
|
587 XULtooltiptextText = tipElement.getAttribute("tooltiptext"); |
|
588 else |
|
589 titleText = tipElement.getAttribute("title"); |
|
590 |
|
591 if ((tipElement instanceof HTMLAnchorElement || |
|
592 tipElement instanceof HTMLAreaElement || |
|
593 tipElement instanceof HTMLLinkElement || |
|
594 tipElement instanceof SVGAElement) && tipElement.href) { |
|
595 XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title"); |
|
596 } |
|
597 if (lookingForSVGTitle && |
|
598 (!(tipElement instanceof SVGElement) || |
|
599 tipElement.parentNode.nodeType == Node.DOCUMENT_NODE)) { |
|
600 lookingForSVGTitle = false; |
|
601 } |
|
602 if (lookingForSVGTitle) { |
|
603 for (let childNode of tipElement.childNodes) { |
|
604 if (childNode instanceof SVGTitleElement) { |
|
605 SVGTitleText = childNode.textContent; |
|
606 break; |
|
607 } |
|
608 } |
|
609 } |
|
610 |
|
611 direction = defView.getComputedStyle(tipElement, "") |
|
612 .getPropertyValue("direction"); |
|
613 } |
|
614 |
|
615 tipElement = tipElement.parentNode; |
|
616 } |
|
617 |
|
618 this.style.direction = direction; |
|
619 |
|
620 return [titleText, XLinkTitleText, SVGTitleText, XULtooltiptextText].some(function (t) { |
|
621 if (t && /\S/.test(t)) { |
|
622 // Make CRLF and CR render one line break each. |
|
623 this.label = t.replace(/\r\n?/g, '\n'); |
|
624 return true; |
|
625 } |
|
626 |
|
627 return false; |
|
628 }, this); |
|
629 |
|
630 return false; |
|
631 ]]> |
|
632 </body> |
|
633 </method> |
|
634 </implementation> |
|
635 |
|
636 <handlers> |
|
637 <handler event="mouseover"><![CDATA[ |
|
638 var rel = event.relatedTarget; |
|
639 //dump("ENTERING " + (rel ? rel.localName : "null") + "\n"); |
|
640 if (!rel) |
|
641 return; |
|
642 |
|
643 // find out if the node we entered from is one of our anonymous children |
|
644 while (rel) { |
|
645 if (rel == this) |
|
646 break; |
|
647 rel = rel.parentNode; |
|
648 } |
|
649 |
|
650 // if the exited node is not a descendant of ours, we are entering for the first time |
|
651 if (rel != this) |
|
652 this._isMouseOver = true; |
|
653 ]]></handler> |
|
654 |
|
655 <handler event="mouseout"><![CDATA[ |
|
656 var rel = event.relatedTarget; |
|
657 //dump("LEAVING " + (rel ? rel.localName : "null") + "\n"); |
|
658 |
|
659 // relatedTarget is null when the titletip is first shown: a mouseout event fires |
|
660 // because the mouse is exiting the main window and entering the titletip "window". |
|
661 // relatedTarget is also null when the mouse exits the main window completely, |
|
662 // so count how many times relatedTarget was null after titletip is first shown |
|
663 // and hide popup the 2nd time |
|
664 if (!rel) { |
|
665 ++this._mouseOutCount; |
|
666 if (this._mouseOutCount > 1) |
|
667 this.hidePopup(); |
|
668 return; |
|
669 } |
|
670 |
|
671 // find out if the node we are entering is one of our anonymous children |
|
672 while (rel) { |
|
673 if (rel == this) |
|
674 break; |
|
675 rel = rel.parentNode; |
|
676 } |
|
677 |
|
678 // if the entered node is not a descendant of ours, hide the tooltip |
|
679 if (rel != this && this._isMouseOver) { |
|
680 this.hidePopup(); |
|
681 } |
|
682 ]]></handler> |
|
683 |
|
684 <handler event="popupshowing"><![CDATA[ |
|
685 if (this.page && !this.fillInPageTooltip(this.triggerNode)) { |
|
686 event.preventDefault(); |
|
687 } |
|
688 ]]></handler> |
|
689 |
|
690 <handler event="popuphiding"><![CDATA[ |
|
691 this._isMouseOver = false; |
|
692 this._mouseOutCount = 0; |
|
693 ]]></handler> |
|
694 </handlers> |
|
695 </binding> |
|
696 |
|
697 <binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup"> |
|
698 <content> |
|
699 <xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;"> |
|
700 <children/> |
|
701 </xul:hbox> |
|
702 </content> |
|
703 </binding> |
|
704 |
|
705 </bindings> |