VirtualBox

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

Last change on this file since 83455 was 83455, checked in by vboxsync, 5 years ago

TestManger/tooltip: debug. disable div/onload.

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