VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/htdocs/js/common.js@ 65145

Last change on this file since 65145 was 65145, checked in by vboxsync, 8 years ago

TestManager: Added >> link for making the sidebar wider to more easily manipulate the filters.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.2 KB
Line 
1/* $Id: common.js 65145 2017-01-05 11:44:01Z vboxsync $ */
2/** @file
3 * Common JavaScript functions
4 */
5
6/*
7 *
8 * Copyright (C) 2012-2015 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * The contents of this file may alternatively be used under the terms
19 * of the Common Development and Distribution License Version 1.0
20 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
21 * VirtualBox OSE distribution, in which case the provisions of the
22 * CDDL are applicable instead of those of the GPL.
23 *
24 * You may elect to license modified versions of this file under the
25 * terms and conditions of either the GPL or the CDDL or both.
26 */
27
28
29/*********************************************************************************************************************************
30* Global Variables *
31*********************************************************************************************************************************/
32/** Same as WuiDispatcherBase.ksParamRedirectTo. */
33var g_ksParamRedirectTo = 'RedirectTo';
34
35
36/**
37 * Checks if the given value is a decimal integer value.
38 *
39 * @returns true if it is, false if it's isn't.
40 * @param sValue The value to inspect.
41 */
42function isInteger(sValue)
43{
44 if (typeof sValue != 'undefined')
45 {
46 var intRegex = /^\d+$/;
47 if (intRegex.test(sValue))
48 {
49 return true;
50 }
51 }
52 return false;
53}
54
55/**
56 * Removes the element with the specified ID.
57 */
58function removeHtmlNode(sContainerId)
59{
60 var oElement = document.getElementById(sContainerId);
61 if (oElement)
62 {
63 oElement.parentNode.removeChild(oElement);
64 }
65}
66
67/**
68 * Sets the value of the element with id @a sInputId to the keys of aoItems
69 * (comma separated).
70 */
71function setElementValueToKeyList(sInputId, aoItems)
72{
73 var sKey;
74 var oElement = document.getElementById(sInputId);
75 oElement.value = '';
76
77 for (sKey in aoItems)
78 {
79 if (oElement.value.length > 0)
80 {
81 oElement.value += ',';
82 }
83
84 oElement.value += sKey;
85 }
86}
87
88/**
89 * Get the Window.devicePixelRatio in a safe way.
90 *
91 * @returns Floating point ratio. 1.0 means it's a 1:1 ratio.
92 */
93function getDevicePixelRatio()
94{
95 var fpRatio = 1.0;
96 if (window.devicePixelRatio)
97 {
98 fpRatio = window.devicePixelRatio;
99 if (fpRatio < 0.5 || fpRatio > 10.0)
100 fpRatio = 1.0;
101 }
102 return fpRatio;
103}
104
105/**
106 * Tries to figure out the DPI of the device in the X direction.
107 *
108 * @returns DPI on success, null on failure.
109 */
110function getDeviceXDotsPerInch()
111{
112 if (window.deviceXDPI && window.deviceXDPI > 48 && window.deviceXDPI < 2048)
113 {
114 return window.deviceXDPI;
115 }
116 else if (window.devicePixelRatio && window.devicePixelRatio >= 0.5 && window.devicePixelRatio <= 10.0)
117 {
118 cDotsPerInch = Math.round(96 * window.devicePixelRatio);
119 }
120 else
121 {
122 cDotsPerInch = null;
123 }
124 return cDotsPerInch;
125}
126
127/**
128 * Gets the width of the given element (downscaled).
129 *
130 * Useful when using the element to figure the size of a image
131 * or similar.
132 *
133 * @returns Number of pixels. null if oElement is bad.
134 * @param oElement The element (not ID).
135 */
136function getElementWidth(oElement)
137{
138 if (oElement && oElement.offsetWidth)
139 return oElement.offsetWidth;
140 return null;
141}
142
143/** By element ID version of getElementWidth. */
144function getElementWidthById(sElementId)
145{
146 return getElementWidth(document.getElementById(sElementId));
147}
148
149/**
150 * Gets the real unscaled width of the given element.
151 *
152 * Useful when using the element to figure the size of a image
153 * or similar.
154 *
155 * @returns Number of screen pixels. null if oElement is bad.
156 * @param oElement The element (not ID).
157 */
158function getUnscaledElementWidth(oElement)
159{
160 if (oElement && oElement.offsetWidth)
161 return Math.round(oElement.offsetWidth * getDevicePixelRatio());
162 return null;
163}
164
165/** By element ID version of getUnscaledElementWidth. */
166function getUnscaledElementWidthById(sElementId)
167{
168 return getUnscaledElementWidth(document.getElementById(sElementId));
169}
170
171/**
172 * Gets the part of the URL needed for a RedirectTo parameter.
173 *
174 * @returns URL string.
175 */
176function getCurrentBrowerUrlPartForRedirectTo()
177{
178 var sWhere = window.location.href;
179 var offTmp;
180 var offPathKeep;
181
182 /* Find the end of that URL 'path' component. */
183 var offPathEnd = sWhere.indexOf('?');
184 if (offPathEnd < 0)
185 offPathEnd = sWhere.indexOf('#');
186 if (offPathEnd < 0)
187 offPathEnd = sWhere.length;
188
189 /* Go backwards from the end of the and find the start of the last component. */
190 offPathKeep = sWhere.lastIndexOf("/", offPathEnd);
191 offTmp = sWhere.lastIndexOf(":", offPathEnd);
192 if (offPathKeep < offTmp)
193 offPathKeep = offTmp;
194 offTmp = sWhere.lastIndexOf("\\", offPathEnd);
195 if (offPathKeep < offTmp)
196 offPathKeep = offTmp;
197
198 return sWhere.substring(offPathKeep + 1);
199}
200
201
202/**
203 * Sets the value of an input field element (give by ID).
204 *
205 * @returns Returns success indicator (true/false).
206 * @param sFieldId The field ID (required for updating).
207 * @param sValue The field value.
208 */
209function setInputFieldValue(sFieldId, sValue)
210{
211 var oInputElement = document.getElementById(sFieldId);
212 if (oInputElement)
213 {
214 oInputElement.value = sValue;
215 return true;
216 }
217 return false;
218}
219
220/**
221 * Adds a hidden input field to a form.
222 *
223 * @returns The new input field element.
224 * @param oFormElement The form to append it to.
225 * @param sName The field name.
226 * @param sValue The field value.
227 * @param sFieldId The field ID (optional).
228 */
229function addHiddenInputFieldToForm(oFormElement, sName, sValue, sFieldId)
230{
231 var oNew = document.createElement('input');
232 oNew.type = 'hidden';
233 oNew.name = sName;
234 oNew.value = sValue;
235 if (sFieldId)
236 oNew.id = sFieldId;
237 oFormElement.appendChild(oNew);
238 return oNew;
239}
240
241/** By element ID version of addHiddenInputFieldToForm. */
242function addHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId)
243{
244 return addHiddenInputFieldToForm(document.getElementById(sFormId), sName, sValue, sFieldId);
245}
246
247/**
248 * Adds or updates a hidden input field to/on a form.
249 *
250 * @returns The new input field element.
251 * @param sFormId The ID of the form to amend.
252 * @param sName The field name.
253 * @param sValue The field value.
254 * @param sFieldId The field ID (required for updating).
255 */
256function addUpdateHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId)
257{
258 var oInputElement = null;
259 if (sFieldId)
260 {
261 oInputElement = document.getElementById(sFieldId);
262 }
263 if (oInputElement)
264 {
265 oInputElement.name = sName;
266 oInputElement.value = sValue;
267 }
268 else
269 {
270 oInputElement = addHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId);
271 }
272 return oInputElement;
273}
274
275/**
276 * Adds a width and a dpi input to the given form element if possible to
277 * determine the values.
278 *
279 * This is normally employed in an onlick hook, but then you must specify IDs or
280 * the browser may end up adding it several times.
281 *
282 * @param sFormId The ID of the form to amend.
283 * @param sWidthSrcId The ID of the element to calculate the width
284 * value from.
285 * @param sWidthName The name of the width value.
286 * @param sDpiName The name of the dpi value.
287 */
288function addDynamicGraphInputs(sFormId, sWidthSrcId, sWidthName, sDpiName)
289{
290 var cx = getUnscaledElementWidthById(sWidthSrcId);
291 var cDotsPerInch = getDeviceXDotsPerInch();
292
293 if (cx)
294 {
295 addUpdateHiddenInputFieldToFormById(sFormId, sWidthName, cx, sFormId + '-' + sWidthName + '-id');
296 }
297
298 if (cDotsPerInch)
299 {
300 addUpdateHiddenInputFieldToFormById(sFormId, sDpiName, cDotsPerInch, sFormId + '-' + sDpiName + '-id');
301 }
302
303}
304
305/**
306 * Adds the RedirecTo field with the current URL to the form.
307 *
308 * This is a 'onsubmit' action.
309 *
310 * @returns Returns success indicator (true/false).
311 * @param oForm The form being submitted.
312 */
313function addRedirectToInputFieldWithCurrentUrl(oForm)
314{
315 /* Constant used here is duplicated in WuiDispatcherBase.ksParamRedirectTo */
316 return addHiddenInputFieldToForm(oForm, 'RedirectTo', getCurrentBrowerUrlPartForRedirectTo(), null);
317}
318
319/**
320 * Adds the RedirecTo parameter to the href of the given anchor.
321 *
322 * This is a 'onclick' action.
323 *
324 * @returns Returns success indicator (true/false).
325 * @param oAnchor The anchor element being clicked on.
326 */
327function addRedirectToAnchorHref(oAnchor)
328{
329 var sRedirectToParam = g_ksParamRedirectTo + '=' + encodeURIComponent(getCurrentBrowerUrlPartForRedirectTo());
330 var sHref = oAnchor.href;
331 if (sHref.indexOf(sRedirectToParam) < 0)
332 {
333 var sHash;
334 var offHash = sHref.indexOf('#');
335 if (offHash >= 0)
336 sHash = sHref.substring(offHash);
337 else
338 {
339 sHash = '';
340 offHash = sHref.length;
341 }
342 sHref = sHref.substring(0, offHash)
343 if (sHref.indexOf('?') >= 0)
344 sHref += '&';
345 else
346 sHref += '?';
347 sHref += sRedirectToParam;
348 sHref += sHash;
349 oAnchor.href = sHref;
350 }
351 return true;
352}
353
354
355/** @name Collapsible / Expandable items
356 * @{
357 */
358
359
360/**
361 * Toggles the collapsible / expandable state of a parent DD and DT uncle.
362 *
363 * @returns true
364 * @param oAnchor The anchor object.
365 */
366function toggleCollapsibleDtDd(oAnchor)
367{
368 var oParent = oAnchor.parentElement;
369 var sClass = oParent.className;
370
371 /* Find the DD sibling tag */
372 var oDdElement = oParent.nextSibling;
373 while (oDdElement != null && oDdElement.tagName != 'DD')
374 oDdElement = oDdElement.nextSibling;
375
376 /* Determin the new class and arrow char. */
377 var sNewClass;
378 var sNewChar;
379 if ( sClass.substr(-11) == 'collapsible')
380 {
381 sNewClass = sClass.substr(0, sClass.length - 11) + 'expandable';
382 sNewChar = '\u25B6'; /* black right-pointing triangle */
383 }
384 else if (sClass.substr(-10) == 'expandable')
385 {
386 sNewClass = sClass.substr(0, sClass.length - 10) + 'collapsible';
387 sNewChar = '\u25BC'; /* black down-pointing triangle */
388 }
389 else
390 {
391 console.log('toggleCollapsibleParent: Invalid class: ' + sClass);
392 return true;
393 }
394
395 /* Update the parent (DT) class and anchor text. */
396 oParent.className = sNewClass;
397 oAnchor.firstChild.textContent = sNewChar + oAnchor.firstChild.textContent.substr(1);
398
399 /* Update the uncle (DD) class. */
400 if (oDdElement)
401 oDdElement.className = sNewClass;
402 return true;
403}
404
405/**
406 * Shows/hides a sub-category UL according to checkbox status.
407 *
408 * The checkbox is expected to be within a label element or something.
409 *
410 * @returns true
411 * @param oInput The input checkbox.
412 */
413function toggleCollapsibleCheckbox(oInput)
414{
415 var oParent = oInput.parentElement;
416
417 /* Find the UL sibling element. */
418 var oUlElement = oParent.nextSibling;
419 while (oUlElement != null && oUlElement.tagName != 'UL')
420 oUlElement = oUlElement.nextSibling;
421
422 /* Change the visibility. */
423 if (oInput.checked)
424 oUlElement.className = oUlElement.className.replace('expandable', 'collapsible');
425 else
426 {
427 oUlElement.className = oUlElement.className.replace('collapsible', 'expandable');
428
429 /* Make sure all sub-checkboxes are now unchecked. */
430 var aoSubInputs = oUlElement.getElementsByTagName('input');
431 var i;
432 for (i = 0; i < aoSubInputs.length; i++)
433 aoSubInputs[i].checked = false;
434 }
435 return true;
436}
437
438/**
439 * Toggles the sidebar size so filters can more easily manipulated.
440 */
441function toggleSidebarSize()
442{
443 var sLinkText;
444 if (document.body.className != 'tm-wide-side-menu')
445 {
446 document.body.className = 'tm-wide-side-menu';
447 sLinkText = '\u00ab\u00ab';
448 }
449 else
450 {
451 document.body.className = '';
452 sLinkText = '\u00bb\u00bb';
453 }
454
455 var aoToggleLink = document.getElementsByClassName('tm-sidebar-size-link');
456 var i;
457 for (i = 0; i < aoToggleLink.length; i++)
458 if (aoToggleLink[i].textContent.indexOf('\u00bb') >= 0 || aoToggleLink[i].textContent.indexOf('\u00ab') >= 0)
459 aoToggleLink[i].textContent = sLinkText;
460}
461
462/** @} */
463
464
465/** @name Custom Tooltips
466 * @{
467 */
468
469/** Where we keep tooltip elements when not displayed. */
470var g_dTooltips = {};
471var g_oCurrentTooltip = null;
472var g_idTooltipShowTimer = null;
473var g_idTooltipHideTimer = null;
474var g_cTooltipSvnRevisions = 12;
475
476/**
477 * Cancel showing/replacing/repositing a tooltip.
478 */
479function tooltipResetShowTimer()
480{
481 if (g_idTooltipShowTimer)
482 {
483 clearTimeout(g_idTooltipShowTimer);
484 g_idTooltipShowTimer = null;
485 }
486}
487
488/**
489 * Cancel hiding of the current tooltip.
490 */
491function tooltipResetHideTimer()
492{
493 if (g_idTooltipHideTimer)
494 {
495 clearTimeout(g_idTooltipHideTimer);
496 g_idTooltipHideTimer = null;
497 }
498}
499
500/**
501 * Really hide the tooltip.
502 */
503function tooltipReallyHide()
504{
505 if (g_oCurrentTooltip)
506 {
507 //console.log('tooltipReallyHide: ' + g_oCurrentTooltip);
508 g_oCurrentTooltip.oElm.style.display = 'none';
509 g_oCurrentTooltip = null;
510 }
511}
512
513/**
514 * Schedule the tooltip for hiding.
515 */
516function tooltipHide()
517{
518 function tooltipDelayedHide()
519 {
520 tooltipResetHideTimer();
521 tooltipReallyHide();
522 }
523
524 /*
525 * Cancel any pending show and schedule hiding if necessary.
526 */
527 tooltipResetShowTimer();
528 if (g_oCurrentTooltip && !g_idTooltipHideTimer)
529 {
530 g_idTooltipHideTimer = setTimeout(tooltipDelayedHide, 700);
531 }
532
533 return true;
534}
535
536/**
537 * Function that is repositions the tooltip when it's shown.
538 *
539 * Used directly, via onload, and hackish timers to catch all browsers and
540 * whatnot.
541 *
542 * Will set several tooltip member variables related to position and space.
543 */
544function tooltipRepositionOnLoad()
545{
546 if (g_oCurrentTooltip)
547 {
548 var oRelToRect = g_oCurrentTooltip.oRelToRect;
549 var cxNeeded = g_oCurrentTooltip.oElm.offsetWidth + 8;
550 var cyNeeded = g_oCurrentTooltip.oElm.offsetHeight + 8;
551
552 var yScroll = window.pageYOffset || document.documentElement.scrollTop;
553 var yScrollBottom = yScroll + window.innerHeight;
554 var xScroll = window.pageXOffset || document.documentElement.scrollLeft;
555 var xScrollRight = xScroll + window.innerWidth;
556
557 var cyAbove = Math.max(oRelToRect.top - yScroll, 0);
558 var cyBelow = Math.max(yScrollBottom - oRelToRect.bottom, 0);
559 var cxLeft = Math.max(oRelToRect.left - xScroll, 0);
560 var cxRight = Math.max(xScrollRight - oRelToRect.right, 0);
561
562 var xPos;
563 var yPos;
564
565 /*
566 * Decide where to put the thing.
567 */
568 if (cyNeeded < cyBelow)
569 {
570 yPos = oRelToRect.bottom;
571 g_oCurrentTooltip.cyMax = cyBelow;
572 }
573 else if (cyBelow >= cyAbove)
574 {
575 yPos = yScrollBottom - cyNeeded;
576 g_oCurrentTooltip.cyMax = yScrollBottom - yPos;
577 }
578 else
579 {
580 yPos = oRelToRect.top - cyNeeded;
581 g_oCurrentTooltip.cyMax = yScrollBottom - yPos;
582 }
583 if (yPos < yScroll)
584 {
585 yPos = yScroll;
586 g_oCurrentTooltip.cyMax = yScrollBottom - yPos;
587 }
588 g_oCurrentTooltip.yPos = yPos;
589 g_oCurrentTooltip.yScroll = yScroll;
590 g_oCurrentTooltip.cyMaxUp = yPos - yScroll;
591
592 if (cxNeeded < cxRight || cxNeeded > cxRight)
593 {
594 xPos = oRelToRect.right;
595 g_oCurrentTooltip.cxMax = cxRight;
596 }
597 else
598 {
599 xPos = oRelToRect.left - cxNeeded;
600 g_oCurrentTooltip.cxMax = cxNeeded;
601 }
602 g_oCurrentTooltip.xPos = xPos;
603 g_oCurrentTooltip.xScroll = xScroll;
604
605 g_oCurrentTooltip.oElm.style.top = yPos + 'px';
606 g_oCurrentTooltip.oElm.style.left = xPos + 'px';
607 }
608 return true;
609}
610
611
612/**
613 * Really show the tooltip.
614 *
615 * @param oTooltip The tooltip object.
616 * @param oRelTo What to put the tooltip adjecent to.
617 */
618function tooltipReallyShow(oTooltip, oRelTo)
619{
620 var oRect;
621
622 tooltipResetShowTimer();
623 tooltipResetHideTimer();
624
625 if (g_oCurrentTooltip == oTooltip)
626 {
627 //console.log('moving tooltip');
628 }
629 else if (g_oCurrentTooltip)
630 {
631 //console.log('removing current tooltip and showing new');
632 tooltipReallyHide();
633 }
634 else
635 {
636 //console.log('showing tooltip');
637 }
638
639 oTooltip.oElm.style.display = 'block';
640 oTooltip.oElm.style.position = 'absolute';
641 oRect = oRelTo.getBoundingClientRect();
642 oTooltip.oRelToRect = oRect;
643 oTooltip.oElm.style.left = oRect.right + 'px';
644 oTooltip.oElm.style.top = oRect.bottom + 'px';
645
646 g_oCurrentTooltip = oTooltip;
647
648 /*
649 * This function does the repositioning at some point.
650 */
651 tooltipRepositionOnLoad();
652 if (oTooltip.oElm.onload === null)
653 {
654 oTooltip.oElm.onload = function(){ tooltipRepositionOnLoad(); setTimeout(tooltipRepositionOnLoad, 0); };
655 }
656}
657
658/**
659 * Tooltip onmouseenter handler .
660 */
661function tooltipElementOnMouseEnter()
662{
663 //console.log('tooltipElementOnMouseEnter: arguments.length='+arguments.length+' [0]='+arguments[0]);
664 //console.log('ENT: currentTarget='+arguments[0].currentTarget);
665 tooltipResetShowTimer();
666 tooltipResetHideTimer();
667 return true;
668}
669
670/**
671 * Tooltip onmouseout handler.
672 *
673 * @remarks We only use this and onmouseenter for one tooltip element (iframe
674 * for svn, because chrome is sending onmouseout events after
675 * onmouseneter for the next element, which would confuse this simple
676 * code.
677 */
678function tooltipElementOnMouseOut()
679{
680 //console.log('tooltipElementOnMouseOut: arguments.length='+arguments.length+' [0]='+arguments[0]);
681 //console.log('OUT: currentTarget='+arguments[0].currentTarget);
682 tooltipHide();
683 return true;
684}
685
686/**
687 * iframe.onload hook that repositions and resizes the tooltip.
688 *
689 * This is a little hacky and we're calling it one or three times too many to
690 * work around various browser differences too.
691 */
692function svnHistoryTooltipOnLoad()
693{
694 //console.log('svnHistoryTooltipOnLoad');
695
696 /*
697 * Resize the tooltip to better fit the content.
698 */
699 tooltipRepositionOnLoad(); /* Sets cxMax and cyMax. */
700 if (g_oCurrentTooltip && g_oCurrentTooltip.oIFrame.contentWindow)
701 {
702 var oSubElement = g_oCurrentTooltip.oIFrame;
703 var cxSpace = Math.max(oSubElement.offsetLeft * 2, 0); /* simplified */
704 var cySpace = Math.max(oSubElement.offsetTop * 2, 0); /* simplified */
705 var cxNeeded = oSubElement.contentWindow.document.body.scrollWidth + cxSpace;
706 var cyNeeded = oSubElement.contentWindow.document.body.scrollHeight + cySpace;
707 var cx = Math.min(cxNeeded, g_oCurrentTooltip.cxMax);
708 var cy;
709
710 g_oCurrentTooltip.oElm.width = cx + 'px';
711 oSubElement.width = (cx - cxSpace) + 'px';
712 if (cx >= cxNeeded)
713 {
714 //console.log('svnHistoryTooltipOnLoad: overflowX -> hidden');
715 oSubElement.style.overflowX = 'hidden';
716 }
717 else
718 {
719 oSubElement.style.overflowX = 'scroll';
720 }
721
722 cy = Math.min(cyNeeded, g_oCurrentTooltip.cyMax);
723 if (cyNeeded > g_oCurrentTooltip.cyMax && g_oCurrentTooltip.cyMaxUp > 0)
724 {
725 var cyMove = Math.min(cyNeeded - g_oCurrentTooltip.cyMax, g_oCurrentTooltip.cyMaxUp);
726 g_oCurrentTooltip.cyMax += cyMove;
727 g_oCurrentTooltip.yPos -= cyMove;
728 g_oCurrentTooltip.oElm.style.top = g_oCurrentTooltip.yPos + 'px';
729 cy = Math.min(cyNeeded, g_oCurrentTooltip.cyMax);
730 }
731
732 g_oCurrentTooltip.oElm.height = cy + 'px';
733 oSubElement.height = (cy - cySpace) + 'px';
734 if (cy >= cyNeeded)
735 {
736 //console.log('svnHistoryTooltipOnLoad: overflowY -> hidden');
737 oSubElement.style.overflowY = 'hidden';
738 }
739 else
740 {
741 oSubElement.style.overflowY = 'scroll';
742 }
743
744 //console.log('cyNeeded='+cyNeeded+' cyMax='+g_oCurrentTooltip.cyMax+' cySpace='+cySpace+' cy='+cy);
745 //console.log('oSubElement.offsetTop='+oSubElement.offsetTop);
746 //console.log('svnHistoryTooltipOnLoad: cx='+cx+'cxMax='+g_oCurrentTooltip.cxMax+' cxNeeded='+cxNeeded+' cy='+cy+' cyMax='+g_oCurrentTooltip.cyMax);
747
748 tooltipRepositionOnLoad();
749 }
750 return true;
751}
752
753/**
754 * Calculates the last revision to get when showing a tooltip for @a iRevision.
755 *
756 * A tooltip covers several change log entries, both to limit the number of
757 * tooltips to load and to give context. The exact number is defined by
758 * g_cTooltipSvnRevisions.
759 *
760 * @returns Last revision in a tooltip.
761 * @param iRevision The revision number.
762 */
763function svnHistoryTooltipCalcLastRevision(iRevision)
764{
765 var iFirstRev = Math.floor(iRevision / g_cTooltipSvnRevisions) * g_cTooltipSvnRevisions;
766 return iFirstRev + g_cTooltipSvnRevisions - 1;
767}
768
769/**
770 * Calculates a unique ID for the tooltip element.
771 *
772 * This is also used as dictionary index.
773 *
774 * @returns tooltip ID value (string).
775 * @param sRepository The repository name.
776 * @param iRevision The revision number.
777 */
778function svnHistoryTooltipCalcId(sRepository, iRevision)
779{
780 return 'svnHistoryTooltip_' + sRepository + '_' + svnHistoryTooltipCalcLastRevision(iRevision);
781}
782
783/**
784 * The onmouseenter event handler for creating the tooltip.
785 *
786 * @param oEvt The event.
787 * @param sRepository The repository name.
788 * @param iRevision The revision number.
789 *
790 * @remarks onmouseout must be set to call tooltipHide.
791 */
792function svnHistoryTooltipShow(oEvt, sRepository, iRevision)
793{
794 var sKey = svnHistoryTooltipCalcId(sRepository, iRevision);
795 var oTooltip = g_dTooltips[sKey];
796 var oParent = oEvt.currentTarget;
797 //console.log('svnHistoryTooltipShow ' + sRepository);
798
799 function svnHistoryTooltipDelayedShow()
800 {
801 var oSubElement;
802 var sSrc;
803
804 oTooltip = g_dTooltips[sKey];
805 //console.log('svnHistoryTooltipDelayedShow ' + sRepository + ' ' + oTooltip);
806 if (!oTooltip)
807 {
808 /*
809 * Create a new tooltip element.
810 */
811 //console.log('creating ' + sKey);
812 oTooltip = {};
813 oTooltip.oElm = document.createElement('div');
814 oTooltip.oElm.setAttribute('id', sKey);
815 oTooltip.oElm.setAttribute('class', 'tmvcstooltip');
816 oTooltip.oElm.style.position = 'absolute';
817 oTooltip.oElm.style.zIndex = 6001;
818 oTooltip.xPos = 0;
819 oTooltip.yPos = 0;
820 oTooltip.cxMax = 0;
821 oTooltip.cyMax = 0;
822 oTooltip.cyMaxUp = 0;
823 oTooltip.xScroll = 0;
824 oTooltip.yScroll = 0;
825
826 oSubElement = document.createElement('iframe');
827 oSubElement.setAttribute('id', sKey + '_iframe');
828 oSubElement.setAttribute('style', 'position: relative;"');
829 oSubElement.onload = function() {svnHistoryTooltipOnLoad(); setTimeout(svnHistoryTooltipOnLoad,0);};
830 oSubElement.onmouseenter = tooltipElementOnMouseEnter;
831 oSubElement.onmouseout = tooltipElementOnMouseOut;
832 oTooltip.oElm.appendChild(oSubElement);
833 oTooltip.oIFrame = oSubElement;
834 g_dTooltips[sKey] = oTooltip;
835
836 document.body.appendChild(oTooltip.oElm);
837 }
838 else
839 {
840 oSubElement = oTooltip.oIFrame;
841 }
842
843 oSubElement.setAttribute('src', 'index.py?Action=VcsHistoryTooltip&repo=' + sRepository
844 + '&rev=' + svnHistoryTooltipCalcLastRevision(iRevision)
845 + '&cEntries=' + g_cTooltipSvnRevisions
846 + '#r' + iRevision);
847 tooltipReallyShow(oTooltip, oParent);
848 /* Resize and repositioning hacks. */
849 svnHistoryTooltipOnLoad();
850 setTimeout(svnHistoryTooltipOnLoad, 0);
851 }
852
853 /*
854 * Delay the change.
855 */
856 tooltipResetShowTimer();
857 g_idTooltipShowTimer = setTimeout(svnHistoryTooltipDelayedShow, 512);
858}
859
860/** @} */
861
862
863/** @name Debugging and Introspection
864 * @{
865 */
866
867/**
868 * Python-like dir() implementation.
869 *
870 * @returns Array of names associated with oObj.
871 * @param oObj The object under inspection. If not specified we'll
872 * look at the window object.
873 */
874function pythonlikeDir(oObj, fDeep)
875{
876 var aRet = [];
877 var dTmp = {};
878
879 if (!oObj)
880 {
881 oObj = window;
882 }
883
884 for (var oCur = oObj; oCur; oCur = Object.getPrototypeOf(oCur))
885 {
886 var aThis = Object.getOwnPropertyNames(oCur);
887 for (var i = 0; i < aThis.length; i++)
888 {
889 if (!(aThis[i] in dTmp))
890 {
891 dTmp[aThis[i]] = 1;
892 aRet.push(aThis[i]);
893 }
894 }
895 }
896
897 return aRet;
898}
899
900
901/**
902 * Python-like dir() implementation, shallow version.
903 *
904 * @returns Array of names associated with oObj.
905 * @param oObj The object under inspection. If not specified we'll
906 * look at the window object.
907 */
908function pythonlikeShallowDir(oObj, fDeep)
909{
910 var aRet = [];
911 var dTmp = {};
912
913 if (oObj)
914 {
915 for (var i in oObj)
916 {
917 aRet.push(i);
918 }
919 }
920
921 return aRet;
922}
923
924
925
926function dbgGetObjType(oObj)
927{
928 var sType = typeof oObj;
929 if (sType == "object" && oObj !== null)
930 {
931 if (oObj.constructor && oObj.constructor.name)
932 {
933 sType = oObj.constructor.name;
934 }
935 else
936 {
937 var fnToString = Object.prototype.toString;
938 var sTmp = fnToString.call(oObj);
939 if (sTmp.indexOf('[object ') === 0)
940 {
941 sType = sTmp.substring(8, sTmp.length);
942 }
943 }
944 }
945 return sType;
946}
947
948
949/**
950 * Dumps the given object to the console.
951 *
952 * @param oObj The object under inspection.
953 * @param sPrefix What to prefix the log output with.
954 */
955function dbgDumpObj(oObj, sName, sPrefix)
956{
957 var aMembers;
958 var sType;
959
960 /*
961 * Defaults
962 */
963 if (!oObj)
964 {
965 oObj = window;
966 }
967
968 if (!sPrefix)
969 {
970 if (sName)
971 {
972 sPrefix = sName + ':';
973 }
974 else
975 {
976 sPrefix = 'dbgDumpObj:';
977 }
978 }
979
980 if (!sName)
981 {
982 sName = '';
983 }
984
985 /*
986 * The object itself.
987 */
988 sPrefix = sPrefix + ' ';
989 console.log(sPrefix + sName + ' ' + dbgGetObjType(oObj));
990
991 /*
992 * The members.
993 */
994 sPrefix = sPrefix + ' ';
995 aMembers = pythonlikeDir(oObj);
996 for (i = 0; i < aMembers.length; i++)
997 {
998 console.log(sPrefix + aMembers[i]);
999 }
1000
1001 return true;
1002}
1003
1004function dbgDumpObjWorker(sType, sName, oObj, sPrefix)
1005{
1006 var sRet;
1007 switch (sType)
1008 {
1009 case 'function':
1010 {
1011 sRet = sPrefix + 'function ' + sName + '()' + '\n';
1012 break;
1013 }
1014
1015 case 'object':
1016 {
1017 sRet = sPrefix + 'var ' + sName + '(' + dbgGetObjType(oObj) + ') =';
1018 if (oObj !== null)
1019 {
1020 sRet += '\n';
1021 }
1022 else
1023 {
1024 sRet += ' null\n';
1025 }
1026 break;
1027 }
1028
1029 case 'string':
1030 {
1031 sRet = sPrefix + 'var ' + sName + '(string, ' + oObj.length + ')';
1032 if (oObj.length < 80)
1033 {
1034 sRet += ' = "' + oObj + '"\n';
1035 }
1036 else
1037 {
1038 sRet += '\n';
1039 }
1040 break;
1041 }
1042
1043 case 'Oops!':
1044 sRet = sPrefix + sName + '(??)\n';
1045 break;
1046
1047 default:
1048 sRet = sPrefix + 'var ' + sName + '(' + sType + ')\n';
1049 break;
1050 }
1051 return sRet;
1052}
1053
1054
1055function dbgObjInArray(aoObjs, oObj)
1056{
1057 var i = aoObjs.length;
1058 while (i > 0)
1059 {
1060 i--;
1061 if (aoObjs[i] === oObj)
1062 {
1063 return true;
1064 }
1065 }
1066 return false;
1067}
1068
1069function dbgDumpObjTreeWorker(oObj, sPrefix, aParentObjs, cMaxDepth)
1070{
1071 var sRet = '';
1072 var aMembers = pythonlikeShallowDir(oObj);
1073 var i;
1074
1075 for (i = 0; i < aMembers.length; i++)
1076 {
1077 //var sName = i;
1078 var sName = aMembers[i];
1079 var oMember;
1080 var sType;
1081 var oEx;
1082
1083 try
1084 {
1085 oMember = oObj[sName];
1086 sType = typeof oObj[sName];
1087 }
1088 catch (oEx)
1089 {
1090 oMember = null;
1091 sType = 'Oops!';
1092 }
1093
1094 //sRet += '[' + i + '/' + aMembers.length + ']';
1095 sRet += dbgDumpObjWorker(sType, sName, oMember, sPrefix);
1096
1097 if ( sType == 'object'
1098 && oObj !== null)
1099 {
1100
1101 if (dbgObjInArray(aParentObjs, oMember))
1102 {
1103 sRet += sPrefix + '! parent recursion\n';
1104 }
1105 else if ( sName == 'previousSibling'
1106 || sName == 'previousElement'
1107 || sName == 'lastChild'
1108 || sName == 'firstElementChild'
1109 || sName == 'lastElementChild'
1110 || sName == 'nextElementSibling'
1111 || sName == 'prevElementSibling'
1112 || sName == 'parentElement'
1113 || sName == 'ownerDocument')
1114 {
1115 sRet += sPrefix + '! potentially dangerous element name\n';
1116 }
1117 else if (aParentObjs.length >= cMaxDepth)
1118 {
1119 sRet = sRet.substring(0, sRet.length - 1);
1120 sRet += ' <too deep>!\n';
1121 }
1122 else
1123 {
1124
1125 aParentObjs.push(oMember);
1126 if (i + 1 < aMembers.length)
1127 {
1128 sRet += dbgDumpObjTreeWorker(oMember, sPrefix + '| ', aParentObjs, cMaxDepth);
1129 }
1130 else
1131 {
1132 sRet += dbgDumpObjTreeWorker(oMember, sPrefix.substring(0, sPrefix.length - 2) + ' | ', aParentObjs, cMaxDepth);
1133 }
1134 aParentObjs.pop();
1135 }
1136 }
1137 }
1138 return sRet;
1139}
1140
1141/**
1142 * Dumps the given object and all it's subobjects to the console.
1143 *
1144 * @returns String dump of the object.
1145 * @param oObj The object under inspection.
1146 * @param sName The object name (optional).
1147 * @param sPrefix What to prefix the log output with (optional).
1148 * @param cMaxDepth The max depth, optional.
1149 */
1150function dbgDumpObjTree(oObj, sName, sPrefix, cMaxDepth)
1151{
1152 var sType;
1153 var sRet;
1154 var oEx;
1155
1156 /*
1157 * Defaults
1158 */
1159 if (!sPrefix)
1160 {
1161 sPrefix = '';
1162 }
1163
1164 if (!sName)
1165 {
1166 sName = '??';
1167 }
1168
1169 if (!cMaxDepth)
1170 {
1171 cMaxDepth = 2;
1172 }
1173
1174 /*
1175 * The object itself.
1176 */
1177 try
1178 {
1179 sType = typeof oObj;
1180 }
1181 catch (oEx)
1182 {
1183 sType = 'Oops!';
1184 }
1185 sRet = dbgDumpObjWorker(sType, sName, oObj, sPrefix);
1186 if (sType == 'object' && oObj !== null)
1187 {
1188 var aParentObjs = Array();
1189 aParentObjs.push(oObj);
1190 sRet += dbgDumpObjTreeWorker(oObj, sPrefix + '| ', aParentObjs, cMaxDepth);
1191 }
1192
1193 return sRet;
1194}
1195
1196function dbgLogString(sLongString)
1197{
1198 var aStrings = sLongString.split("\n");
1199 var i;
1200 for (i = 0; i < aStrings.length; i++)
1201 {
1202 console.log(aStrings[i]);
1203 }
1204 console.log('dbgLogString - end - ' + aStrings.length + '/' + sLongString.length);
1205 return true;
1206}
1207
1208function dbgLogObjTree(oObj, sName, sPrefix, cMaxDepth)
1209{
1210 return dbgLogString(dbgDumpObjTree(oObj, sName, sPrefix, cMaxDepth));
1211}
1212
1213/** @} */
1214
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette