VirtualBox

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

Last change on this file since 87241 was 86917, checked in by vboxsync, 4 years ago

testmanager/common.js: Sync up to xtracker copy. Can skip seconds part now. bugref:9787

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.4 KB
Line 
1/* $Id: common.js 86917 2020-11-19 11:07:13Z 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/** Days of the week in Date() style with Sunday first. */
35var g_kasDaysOfTheWeek = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ];
36
37
38/**
39 * Detects the firefox browser.
40 */
41function isBrowserFirefox()
42{
43 return typeof InstallTrigger !== 'undefined';
44}
45
46/**
47 * Detects the google chrome browser.
48 * @note Might be confused with edge chromium
49 */
50function isBrowserChrome()
51{
52 var oChrome = window.chrome;
53 if (!oChrome)
54 return false;
55 return !!oChrome.runtime || !oChrome.webstore;
56}
57
58/**
59 * Detects the chromium-based edge browser.
60 */
61function isBrowserEdgeChromium()
62{
63 if (!isBrowserChrome())
64 return false;
65 return navigation.userAgent.indexOf('Edg') >= 0
66}
67
68/**
69 * Detects the chromium-based edge browser.
70 */
71function isBrowserInternetExplorer()
72{
73 /* documentMode is an IE only property. Values are 5,7,8,9,10 or 11
74 according to google results. */
75 if (typeof document.documentMode !== 'undefined')
76 {
77 if (document.documentMode)
78 return true;
79 }
80 /* IE only conditional compiling feature. Here, the 'true || ' part
81 will be included in the if when executing in IE: */
82 if (/*@cc_on true || @*/false)
83 return true;
84 return false;
85}
86
87/**
88 * Detects the safari browser (v3+).
89 */
90function isBrowserSafari()
91{
92 /* Check if window.HTMLElement is a function named 'HTMLElementConstructor()'?
93 Should work for older safari versions. */
94 var sStr = window.HTMLElement.toString();
95 if (/constructor/i.test(sStr))
96 return true;
97
98 /* Check the class name of window.safari.pushNotification. This works for current. */
99 var oSafari = window['safari'];
100 if (oSafari)
101 {
102 if (typeof oSafari !== 'undefined')
103 {
104 var oPushNotify = oSafari.pushNotification;
105 if (oPushNotify)
106 {
107 sStr = oPushNotify.toString();
108 if (/\[object Safari.*Notification\]/.test(sStr))
109 return true;
110 }
111 }
112 }
113 return false;
114}
115
116/**
117 * Checks if the given value is a decimal integer value.
118 *
119 * @returns true if it is, false if it's isn't.
120 * @param sValue The value to inspect.
121 */
122function isInteger(sValue)
123{
124 if (typeof sValue != 'undefined')
125 {
126 var intRegex = /^\d+$/;
127 if (intRegex.test(sValue))
128 {
129 return true;
130 }
131 }
132 return false;
133}
134
135/**
136 * Checks if @a oMemmber is present in aoArray.
137 *
138 * @returns true/false.
139 * @param aoArray The array to check.
140 * @param oMember The member to check for.
141 */
142function isMemberOfArray(aoArray, oMember)
143{
144 var i;
145 for (i = 0; i < aoArray.length; i++)
146 if (aoArray[i] == oMember)
147 return true;
148 return false;
149}
150
151/**
152 * Parses a typical ISO timestamp, returing a Date object, reasonably
153 * forgiving, but will throw weird indexing/conversion errors if the input
154 * is malformed.
155 *
156 * @returns Date object.
157 * @param sTs The timestamp to parse.
158 * @sa parseIsoTimestamp() in utils.py.
159 */
160function parseIsoTimestamp(sTs)
161{
162 /* YYYY-MM-DD */
163 var iYear = parseInt(sTs.substring(0, 4), 10);
164 console.assert(sTs.charAt(4) == '-');
165 var iMonth = parseInt(sTs.substring(5, 7), 10);
166 console.assert(sTs.charAt(7) == '-');
167 var iDay = parseInt(sTs.substring(8, 10), 10);
168
169 /* Skip separator */
170 var sTime = sTs.substring(10);
171 while ('Tt \t\n\r'.includes(sTime.charAt(0))) {
172 sTime = sTime.substring(1);
173 }
174
175 /* HH:MM[:SS[.fraction] */
176 var iHour = parseInt(sTime.substring(0, 2), 10);
177 console.assert(sTime.charAt(2) == ':');
178 var iMin = parseInt(sTime.substring(3, 5), 10);
179 var iSec = 0;
180 var iMicroseconds = 0;
181 var offTime = 5;
182 if (sTime.charAt(5) == ':')
183 {
184 iSec = parseInt(sTime.substring(6, 8), 10);
185
186 /* Fraction? */
187 offTime = 8;
188 if (offTime < sTime.length && '.,'.includes(sTime.charAt(offTime)))
189 {
190 offTime += 1;
191 var cchFraction = 0;
192 while (offTime + cchFraction < sTime.length && '0123456789'.includes(sTime.charAt(offTime + cchFraction)))
193 cchFraction += 1;
194 if (cchFraction > 0)
195 {
196 iMicroseconds = parseInt(sTime.substring(offTime, offTime + cchFraction), 10);
197 offTime += cchFraction;
198 while (cchFraction < 6)
199 {
200 iMicroseconds *= 10;
201 cchFraction += 1;
202 }
203 while (cchFraction > 6)
204 {
205 iMicroseconds = iMicroseconds / 10;
206 cchFraction -= 1;
207 }
208 }
209 }
210 }
211 var iMilliseconds = (iMicroseconds + 499) / 1000;
212
213 /* Naive? */
214 var oDate = new Date(Date.UTC(iYear, iMonth - 1, iDay, iHour, iMin, iSec, iMilliseconds));
215 if (offTime >= sTime.length)
216 return oDate;
217
218 /* Zulu? */
219 if (offTime >= sTime.length || 'Zz'.includes(sTime.charAt(offTime)))
220 return oDate;
221
222 /* Some kind of offset afterwards. */
223 var chSign = sTime.charAt(offTime);
224 if ('+-'.includes(chSign))
225 {
226 offTime += 1;
227 var cMinTz = parseInt(sTime.substring(offTime, offTime + 2), 10) * 60;
228 offTime += 2;
229 if (offTime < sTime.length && sTime.charAt(offTime) == ':')
230 offTime += 1;
231 if (offTime + 2 <= sTime.length)
232 {
233 cMinTz += parseInt(sTime.substring(offTime, offTime + 2), 10);
234 offTime += 2;
235 }
236 console.assert(offTime == sTime.length);
237 if (chSign == '-')
238 cMinTz = -cMinTz;
239
240 return new Date(oDate.getTime() - cMinTz * 60000);
241 }
242 console.assert(false);
243 return oDate;
244}
245
246/**
247 * @param oDate Date object.
248 */
249function formatTimeHHMM(oDate, fNbsp)
250{
251 var sTime = oDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit'} );
252 if (fNbsp === true)
253 sTime = sTime.replace(' ', '\u00a0');
254
255 /* Workaround for single digit hours in firefox with en_US (minutes works fine): */
256 var iHours = oDate.getHours();
257 if ((iHours % 12) < 10)
258 {
259 var ch1 = sTime.substr(0, 1);
260 var ch2 = sTime.substr(1, 1);
261 if ( ch1 == (iHours % 12).toString()
262 && !(ch2 >= '0' && ch2 <= '9'))
263 sTime = '0' + sTime;
264 }
265 return sTime;
266}
267
268/**
269 * Escapes special characters to HTML-safe sequences, for element use.
270 *
271 * @returns Escaped string suitable for HTML.
272 * @param sText Plain text to escape.
273 */
274function escapeElem(sText)
275{
276 sText = sText.replace(/&/g, '&amp;');
277 sText = sText.replace(/>/g, '&lt;');
278 return sText.replace(/</g, '&gt;');
279}
280
281/**
282 * Escapes special characters to HTML-safe sequences, for double quoted
283 * attribute use.
284 *
285 * @returns Escaped string suitable for HTML.
286 * @param sText Plain text to escape.
287 */
288function escapeAttr(sText)
289{
290 sText = sText.replace(/&/g, '&amp;');
291 sText = sText.replace(/</g, '&lt;');
292 sText = sText.replace(/>/g, '&gt;');
293 return sText.replace(/"/g, '&quot;');
294}
295
296/**
297 * Removes the element with the specified ID.
298 */
299function removeHtmlNode(sContainerId)
300{
301 var oElement = document.getElementById(sContainerId);
302 if (oElement)
303 {
304 oElement.parentNode.removeChild(oElement);
305 }
306}
307
308/**
309 * Sets the value of the element with id @a sInputId to the keys of aoItems
310 * (comma separated).
311 */
312function setElementValueToKeyList(sInputId, aoItems)
313{
314 var sKey;
315 var oElement = document.getElementById(sInputId);
316 oElement.value = '';
317
318 for (sKey in aoItems)
319 {
320 if (oElement.value.length > 0)
321 {
322 oElement.value += ',';
323 }
324
325 oElement.value += sKey;
326 }
327}
328
329/**
330 * Get the Window.devicePixelRatio in a safe way.
331 *
332 * @returns Floating point ratio. 1.0 means it's a 1:1 ratio.
333 */
334function getDevicePixelRatio()
335{
336 var fpRatio = 1.0;
337 if (window.devicePixelRatio)
338 {
339 fpRatio = window.devicePixelRatio;
340 if (fpRatio < 0.5 || fpRatio > 10.0)
341 fpRatio = 1.0;
342 }
343 return fpRatio;
344}
345
346/**
347 * Tries to figure out the DPI of the device in the X direction.
348 *
349 * @returns DPI on success, null on failure.
350 */
351function getDeviceXDotsPerInch()
352{
353 if (window.deviceXDPI && window.deviceXDPI > 48 && window.deviceXDPI < 2048)
354 {
355 return window.deviceXDPI;
356 }
357 else if (window.devicePixelRatio && window.devicePixelRatio >= 0.5 && window.devicePixelRatio <= 10.0)
358 {
359 cDotsPerInch = Math.round(96 * window.devicePixelRatio);
360 }
361 else
362 {
363 cDotsPerInch = null;
364 }
365 return cDotsPerInch;
366}
367
368/**
369 * Gets the width of the given element (downscaled).
370 *
371 * Useful when using the element to figure the size of a image
372 * or similar.
373 *
374 * @returns Number of pixels. null if oElement is bad.
375 * @param oElement The element (not ID).
376 */
377function getElementWidth(oElement)
378{
379 if (oElement && oElement.offsetWidth)
380 return oElement.offsetWidth;
381 return null;
382}
383
384/** By element ID version of getElementWidth. */
385function getElementWidthById(sElementId)
386{
387 return getElementWidth(document.getElementById(sElementId));
388}
389
390/**
391 * Gets the real unscaled width of the given element.
392 *
393 * Useful when using the element to figure the size of a image
394 * or similar.
395 *
396 * @returns Number of screen pixels. null if oElement is bad.
397 * @param oElement The element (not ID).
398 */
399function getUnscaledElementWidth(oElement)
400{
401 if (oElement && oElement.offsetWidth)
402 return Math.round(oElement.offsetWidth * getDevicePixelRatio());
403 return null;
404}
405
406/** By element ID version of getUnscaledElementWidth. */
407function getUnscaledElementWidthById(sElementId)
408{
409 return getUnscaledElementWidth(document.getElementById(sElementId));
410}
411
412/**
413 * Gets the part of the URL needed for a RedirectTo parameter.
414 *
415 * @returns URL string.
416 */
417function getCurrentBrowerUrlPartForRedirectTo()
418{
419 var sWhere = window.location.href;
420 var offTmp;
421 var offPathKeep;
422
423 /* Find the end of that URL 'path' component. */
424 var offPathEnd = sWhere.indexOf('?');
425 if (offPathEnd < 0)
426 offPathEnd = sWhere.indexOf('#');
427 if (offPathEnd < 0)
428 offPathEnd = sWhere.length;
429
430 /* Go backwards from the end of the and find the start of the last component. */
431 offPathKeep = sWhere.lastIndexOf("/", offPathEnd);
432 offTmp = sWhere.lastIndexOf(":", offPathEnd);
433 if (offPathKeep < offTmp)
434 offPathKeep = offTmp;
435 offTmp = sWhere.lastIndexOf("\\", offPathEnd);
436 if (offPathKeep < offTmp)
437 offPathKeep = offTmp;
438
439 return sWhere.substring(offPathKeep + 1);
440}
441
442/**
443 * Adds the given sorting options to the URL and reloads.
444 *
445 * This will preserve previous sorting columns except for those
446 * given in @a aiColumns.
447 *
448 * @param sParam Sorting parameter.
449 * @param aiColumns Array of sorting columns.
450 */
451function ahrefActionSortByColumns(sParam, aiColumns)
452{
453 var sWhere = window.location.href;
454
455 var offHash = sWhere.indexOf('#');
456 if (offHash < 0)
457 offHash = sWhere.length;
458
459 var offQm = sWhere.indexOf('?');
460 if (offQm > offHash)
461 offQm = -1;
462
463 var sNew = '';
464 if (offQm > 0)
465 sNew = sWhere.substring(0, offQm);
466
467 sNew += '?' + sParam + '=' + aiColumns[0];
468 var i;
469 for (i = 1; i < aiColumns.length; i++)
470 sNew += '&' + sParam + '=' + aiColumns[i];
471
472 if (offQm >= 0 && offQm + 1 < offHash)
473 {
474 var sArgs = '&' + sWhere.substring(offQm + 1, offHash);
475 var off = 0;
476 while (off < sArgs.length)
477 {
478 var offMatch = sArgs.indexOf('&' + sParam + '=', off);
479 if (offMatch >= 0)
480 {
481 if (off < offMatch)
482 sNew += sArgs.substring(off, offMatch);
483
484 var offValue = offMatch + 1 + sParam.length + 1;
485 offEnd = sArgs.indexOf('&', offValue);
486 if (offEnd < offValue)
487 offEnd = sArgs.length;
488
489 var iColumn = parseInt(sArgs.substring(offValue, offEnd));
490 if (!isMemberOfArray(aiColumns, iColumn) && !isMemberOfArray(aiColumns, -iColumn))
491 sNew += sArgs.substring(offMatch, offEnd);
492
493 off = offEnd;
494 }
495 else
496 {
497 sNew += sArgs.substring(off);
498 break;
499 }
500 }
501 }
502
503 if (offHash < sWhere.length)
504 sNew = sWhere.substr(offHash);
505
506 window.location.href = sNew;
507}
508
509/**
510 * Sets the value of an input field element (give by ID).
511 *
512 * @returns Returns success indicator (true/false).
513 * @param sFieldId The field ID (required for updating).
514 * @param sValue The field value.
515 */
516function setInputFieldValue(sFieldId, sValue)
517{
518 var oInputElement = document.getElementById(sFieldId);
519 if (oInputElement)
520 {
521 oInputElement.value = sValue;
522 return true;
523 }
524 return false;
525}
526
527/**
528 * Adds a hidden input field to a form.
529 *
530 * @returns The new input field element.
531 * @param oFormElement The form to append it to.
532 * @param sName The field name.
533 * @param sValue The field value.
534 * @param sFieldId The field ID (optional).
535 */
536function addHiddenInputFieldToForm(oFormElement, sName, sValue, sFieldId)
537{
538 var oNew = document.createElement('input');
539 oNew.type = 'hidden';
540 oNew.name = sName;
541 oNew.value = sValue;
542 if (sFieldId)
543 oNew.id = sFieldId;
544 oFormElement.appendChild(oNew);
545 return oNew;
546}
547
548/** By element ID version of addHiddenInputFieldToForm. */
549function addHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId)
550{
551 return addHiddenInputFieldToForm(document.getElementById(sFormId), sName, sValue, sFieldId);
552}
553
554/**
555 * Adds or updates a hidden input field to/on a form.
556 *
557 * @returns The new input field element.
558 * @param sFormId The ID of the form to amend.
559 * @param sName The field name.
560 * @param sValue The field value.
561 * @param sFieldId The field ID (required for updating).
562 */
563function addUpdateHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId)
564{
565 var oInputElement = null;
566 if (sFieldId)
567 {
568 oInputElement = document.getElementById(sFieldId);
569 }
570 if (oInputElement)
571 {
572 oInputElement.name = sName;
573 oInputElement.value = sValue;
574 }
575 else
576 {
577 oInputElement = addHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId);
578 }
579 return oInputElement;
580}
581
582/**
583 * Adds a width and a dpi input to the given form element if possible to
584 * determine the values.
585 *
586 * This is normally employed in an onlick hook, but then you must specify IDs or
587 * the browser may end up adding it several times.
588 *
589 * @param sFormId The ID of the form to amend.
590 * @param sWidthSrcId The ID of the element to calculate the width
591 * value from.
592 * @param sWidthName The name of the width value.
593 * @param sDpiName The name of the dpi value.
594 */
595function addDynamicGraphInputs(sFormId, sWidthSrcId, sWidthName, sDpiName)
596{
597 var cx = getUnscaledElementWidthById(sWidthSrcId);
598 var cDotsPerInch = getDeviceXDotsPerInch();
599
600 if (cx)
601 {
602 addUpdateHiddenInputFieldToFormById(sFormId, sWidthName, cx, sFormId + '-' + sWidthName + '-id');
603 }
604
605 if (cDotsPerInch)
606 {
607 addUpdateHiddenInputFieldToFormById(sFormId, sDpiName, cDotsPerInch, sFormId + '-' + sDpiName + '-id');
608 }
609
610}
611
612/**
613 * Adds the RedirecTo field with the current URL to the form.
614 *
615 * This is a 'onsubmit' action.
616 *
617 * @returns Returns success indicator (true/false).
618 * @param oForm The form being submitted.
619 */
620function addRedirectToInputFieldWithCurrentUrl(oForm)
621{
622 /* Constant used here is duplicated in WuiDispatcherBase.ksParamRedirectTo */
623 return addHiddenInputFieldToForm(oForm, 'RedirectTo', getCurrentBrowerUrlPartForRedirectTo(), null);
624}
625
626/**
627 * Adds the RedirecTo parameter to the href of the given anchor.
628 *
629 * This is a 'onclick' action.
630 *
631 * @returns Returns success indicator (true/false).
632 * @param oAnchor The anchor element being clicked on.
633 */
634function addRedirectToAnchorHref(oAnchor)
635{
636 var sRedirectToParam = g_ksParamRedirectTo + '=' + encodeURIComponent(getCurrentBrowerUrlPartForRedirectTo());
637 var sHref = oAnchor.href;
638 if (sHref.indexOf(sRedirectToParam) < 0)
639 {
640 var sHash;
641 var offHash = sHref.indexOf('#');
642 if (offHash >= 0)
643 sHash = sHref.substring(offHash);
644 else
645 {
646 sHash = '';
647 offHash = sHref.length;
648 }
649 sHref = sHref.substring(0, offHash)
650 if (sHref.indexOf('?') >= 0)
651 sHref += '&';
652 else
653 sHref += '?';
654 sHref += sRedirectToParam;
655 sHref += sHash;
656 oAnchor.href = sHref;
657 }
658 return true;
659}
660
661
662
663/**
664 * Clears one input element.
665 *
666 * @param oInput The input to clear.
667 */
668function resetInput(oInput)
669{
670 switch (oInput.type)
671 {
672 case 'checkbox':
673 case 'radio':
674 oInput.checked = false;
675 break;
676
677 case 'text':
678 oInput.value = 0;
679 break;
680 }
681}
682
683
684/**
685 * Clears a form.
686 *
687 * @param sIdForm The ID of the form
688 */
689function clearForm(sIdForm)
690{
691 var oForm = document.getElementById(sIdForm);
692 if (oForm)
693 {
694 var aoInputs = oForm.getElementsByTagName('INPUT');
695 var i;
696 for (i = 0; i < aoInputs.length; i++)
697 resetInput(aoInputs[i])
698
699 /* HTML5 allows inputs outside <form>, so scan the document. */
700 aoInputs = document.getElementsByTagName('INPUT');
701 for (i = 0; i < aoInputs.length; i++)
702 if (aoInputs.hasOwnProperty("form"))
703 if (aoInputs.form == sIdForm)
704 resetInput(aoInputs[i])
705 }
706
707 return true;
708}
709
710
711/**
712 * Used by the time navigation to update the hidden efficient date field when
713 * either of the date or time fields changes.
714 *
715 * @param oForm The form.
716 */
717function timeNavigationUpdateHiddenEffDate(oForm, sIdSuffix)
718{
719 var sDate = document.getElementById('EffDate' + sIdSuffix).value;
720 var sTime = document.getElementById('EffTime' + sIdSuffix).value;
721
722 var oField = document.getElementById('EffDateTime' + sIdSuffix);
723 oField.value = sDate + 'T' + sTime + '.00Z';
724}
725
726
727/** @name Collapsible / Expandable items
728 * @{
729 */
730
731
732/**
733 * Toggles the collapsible / expandable state of a parent DD and DT uncle.
734 *
735 * @returns true
736 * @param oAnchor The anchor object.
737 */
738function toggleCollapsibleDtDd(oAnchor)
739{
740 var oParent = oAnchor.parentElement;
741 var sClass = oParent.className;
742
743 /* Find the DD sibling tag */
744 var oDdElement = oParent.nextSibling;
745 while (oDdElement != null && oDdElement.tagName != 'DD')
746 oDdElement = oDdElement.nextSibling;
747
748 /* Determin the new class and arrow char. */
749 var sNewClass;
750 var sNewChar;
751 if ( sClass.substr(-11) == 'collapsible')
752 {
753 sNewClass = sClass.substr(0, sClass.length - 11) + 'expandable';
754 sNewChar = '\u25B6'; /* black right-pointing triangle */
755 }
756 else if (sClass.substr(-10) == 'expandable')
757 {
758 sNewClass = sClass.substr(0, sClass.length - 10) + 'collapsible';
759 sNewChar = '\u25BC'; /* black down-pointing triangle */
760 }
761 else
762 {
763 console.log('toggleCollapsibleParent: Invalid class: ' + sClass);
764 return true;
765 }
766
767 /* Update the parent (DT) class and anchor text. */
768 oParent.className = sNewClass;
769 oAnchor.firstChild.textContent = sNewChar + oAnchor.firstChild.textContent.substr(1);
770
771 /* Update the uncle (DD) class. */
772 if (oDdElement)
773 oDdElement.className = sNewClass;
774 return true;
775}
776
777/**
778 * Shows/hides a sub-category UL according to checkbox status.
779 *
780 * The checkbox is expected to be within a label element or something.
781 *
782 * @returns true
783 * @param oInput The input checkbox.
784 */
785function toggleCollapsibleCheckbox(oInput)
786{
787 var oParent = oInput.parentElement;
788
789 /* Find the UL sibling element. */
790 var oUlElement = oParent.nextSibling;
791 while (oUlElement != null && oUlElement.tagName != 'UL')
792 oUlElement = oUlElement.nextSibling;
793
794 /* Change the visibility. */
795 if (oInput.checked)
796 oUlElement.className = oUlElement.className.replace('expandable', 'collapsible');
797 else
798 {
799 oUlElement.className = oUlElement.className.replace('collapsible', 'expandable');
800
801 /* Make sure all sub-checkboxes are now unchecked. */
802 var aoSubInputs = oUlElement.getElementsByTagName('input');
803 var i;
804 for (i = 0; i < aoSubInputs.length; i++)
805 aoSubInputs[i].checked = false;
806 }
807 return true;
808}
809
810/**
811 * Toggles the sidebar size so filters can more easily manipulated.
812 */
813function toggleSidebarSize()
814{
815 var sLinkText;
816 if (document.body.className != 'tm-wide-side-menu')
817 {
818 document.body.className = 'tm-wide-side-menu';
819 sLinkText = '\u00ab\u00ab';
820 }
821 else
822 {
823 document.body.className = '';
824 sLinkText = '\u00bb\u00bb';
825 }
826
827 var aoToggleLink = document.getElementsByClassName('tm-sidebar-size-link');
828 var i;
829 for (i = 0; i < aoToggleLink.length; i++)
830 if ( aoToggleLink[i].textContent.indexOf('\u00bb') >= 0
831 || aoToggleLink[i].textContent.indexOf('\u00ab') >= 0)
832 aoToggleLink[i].textContent = sLinkText;
833}
834
835/** @} */
836
837
838/** @name Custom Tooltips
839 * @{
840 */
841
842/** Enables non-iframe tooltip code. */
843var g_fNewTooltips = true;
844
845/** Where we keep tooltip elements when not displayed. */
846var g_dTooltips = {};
847var g_oCurrentTooltip = null;
848var g_idTooltipShowTimer = null;
849var g_idTooltipHideTimer = null;
850var g_cTooltipSvnRevisions = 12;
851
852/**
853 * Cancel showing/replacing/repositing a tooltip.
854 */
855function tooltipResetShowTimer()
856{
857 if (g_idTooltipShowTimer)
858 {
859 clearTimeout(g_idTooltipShowTimer);
860 g_idTooltipShowTimer = null;
861 }
862}
863
864/**
865 * Cancel hiding of the current tooltip.
866 */
867function tooltipResetHideTimer()
868{
869 if (g_idTooltipHideTimer)
870 {
871 clearTimeout(g_idTooltipHideTimer);
872 g_idTooltipHideTimer = null;
873 }
874}
875
876/**
877 * Really hide the tooltip.
878 */
879function tooltipReallyHide()
880{
881 if (g_oCurrentTooltip)
882 {
883 //console.log('tooltipReallyHide: ' + g_oCurrentTooltip);
884 g_oCurrentTooltip.oElm.style.display = 'none';
885 g_oCurrentTooltip = null;
886 }
887}
888
889/**
890 * Schedule the tooltip for hiding.
891 */
892function tooltipHide()
893{
894 function tooltipDelayedHide()
895 {
896 tooltipResetHideTimer();
897 tooltipReallyHide();
898 }
899
900 /*
901 * Cancel any pending show and schedule hiding if necessary.
902 */
903 tooltipResetShowTimer();
904 if (g_oCurrentTooltip && !g_idTooltipHideTimer)
905 {
906 g_idTooltipHideTimer = setTimeout(tooltipDelayedHide, 700);
907 }
908
909 return true;
910}
911
912/**
913 * Function that is repositions the tooltip when it's shown.
914 *
915 * Used directly, via onload, and hackish timers to catch all browsers and
916 * whatnot.
917 *
918 * Will set several tooltip member variables related to position and space.
919 */
920function tooltipRepositionOnLoad()
921{
922 //console.log('tooltipRepositionOnLoad');
923 if (g_oCurrentTooltip)
924 {
925 var oRelToRect = g_oCurrentTooltip.oRelToRect;
926 var cxNeeded = g_oCurrentTooltip.oElm.offsetWidth + 8;
927 var cyNeeded = g_oCurrentTooltip.oElm.offsetHeight + 8;
928
929 var cyWindow = window.innerHeight;
930 var yScroll = window.pageYOffset || document.documentElement.scrollTop;
931 var yScrollBottom = yScroll + cyWindow;
932 var cxWindow = window.innerWidth;
933 var xScroll = window.pageXOffset || document.documentElement.scrollLeft;
934 var xScrollRight = xScroll + cxWindow;
935
936 var cyAbove = Math.max(oRelToRect.top, 0);
937 var cyBelow = Math.max(cyWindow - oRelToRect.bottom, 0);
938 var cxLeft = Math.max(oRelToRect.left, 0);
939 var cxRight = Math.max(cxWindow - oRelToRect.right, 0);
940
941 var xPos;
942 var yPos;
943
944 //console.log('tooltipRepositionOnLoad: rect: x,y=' + oRelToRect.x + ',' + oRelToRect.y
945 // + ' cx,cy=' + oRelToRect.width + ',' + oRelToRect.height + ' top=' + oRelToRect.top
946 // + ' bottom=' + oRelToRect.bottom + ' left=' + oRelToRect.left + ' right=' + oRelToRect.right);
947 //console.log('tooltipRepositionOnLoad: yScroll=' + yScroll + ' yScrollBottom=' + yScrollBottom);
948 //console.log('tooltipRepositionOnLoad: cyAbove=' + cyAbove + ' cyBelow=' + cyBelow + ' cyNeeded=' + cyNeeded);
949 //console.log('tooltipRepositionOnLoad: xScroll=' + xScroll + ' xScrollRight=' + xScrollRight);
950 //console.log('tooltipRepositionOnLoad: cxLeft=' + cxLeft + ' cxRight=' + cxRight + ' cxNeeded=' + cxNeeded);
951
952 /*
953 * Decide where to put the thing.
954 */
955 if (cyNeeded < cyBelow)
956 {
957 yPos = yScroll + oRelToRect.top;
958 g_oCurrentTooltip.cyMax = cyBelow;
959 //console.log('tooltipRepositionOnLoad: #1');
960 }
961 else if (cyBelow >= cyAbove)
962 {
963 yPos = yScrollBottom - cyNeeded;
964 g_oCurrentTooltip.cyMax = yScrollBottom - yPos;
965 //console.log('tooltipRepositionOnLoad: #2');
966 }
967 else
968 {
969 yPos = yScroll + oRelToRect.bottom - cyNeeded;
970 g_oCurrentTooltip.cyMax = yScrollBottom - yPos;
971 //console.log('tooltipRepositionOnLoad: #3');
972 }
973 if (yPos < yScroll)
974 {
975 yPos = yScroll;
976 g_oCurrentTooltip.cyMax = yScrollBottom - yPos;
977 //console.log('tooltipRepositionOnLoad: #4');
978 }
979 g_oCurrentTooltip.yPos = yPos;
980 g_oCurrentTooltip.yScroll = yScroll;
981 g_oCurrentTooltip.cyMaxUp = yPos - yScroll;
982 //console.log('tooltipRepositionOnLoad: yPos=' + yPos + ' yScroll=' + yScroll + ' cyMaxUp=' + g_oCurrentTooltip.cyMaxUp);
983
984 if (cxNeeded < cxRight)
985 {
986 xPos = xScroll + oRelToRect.right;
987 g_oCurrentTooltip.cxMax = cxRight;
988 //console.log('tooltipRepositionOnLoad: #5');
989 }
990 else
991 {
992 xPos = xScroll + oRelToRect.left - cxNeeded;
993 if (xPos < xScroll)
994 xPos = xScroll;
995 g_oCurrentTooltip.cxMax = cxNeeded;
996 //console.log('tooltipRepositionOnLoad: #6');
997 }
998 g_oCurrentTooltip.xPos = xPos;
999 g_oCurrentTooltip.xScroll = xScroll;
1000 //console.log('tooltipRepositionOnLoad: xPos=' + xPos + ' xScroll=' + xScroll);
1001
1002 g_oCurrentTooltip.oElm.style.top = yPos + 'px';
1003 g_oCurrentTooltip.oElm.style.left = xPos + 'px';
1004 }
1005 return true;
1006}
1007
1008
1009/**
1010 * Really show the tooltip.
1011 *
1012 * @param oTooltip The tooltip object.
1013 * @param oRelTo What to put the tooltip adjecent to.
1014 */
1015function tooltipReallyShow(oTooltip, oRelTo)
1016{
1017 var oRect;
1018
1019 tooltipResetShowTimer();
1020 tooltipResetHideTimer();
1021
1022 if (g_oCurrentTooltip == oTooltip)
1023 {
1024 //console.log('moving tooltip');
1025 }
1026 else if (g_oCurrentTooltip)
1027 {
1028 //console.log('removing current tooltip and showing new');
1029 tooltipReallyHide();
1030 }
1031 else
1032 {
1033 //console.log('showing tooltip');
1034 }
1035
1036 //oTooltip.oElm.setAttribute('style', 'display: block; position: absolute;');
1037 oTooltip.oElm.style.position = 'absolute';
1038 oTooltip.oElm.style.display = 'block';
1039 oRect = oRelTo.getBoundingClientRect();
1040 oTooltip.oRelToRect = oRect;
1041
1042 g_oCurrentTooltip = oTooltip;
1043
1044 /*
1045 * Do repositioning (again).
1046 */
1047 tooltipRepositionOnLoad();
1048}
1049
1050/**
1051 * Tooltip onmouseenter handler .
1052 */
1053function tooltipElementOnMouseEnter()
1054{
1055 /*console.log('tooltipElementOnMouseEnter: arguments.length='+arguments.length+' [0]='+arguments[0]);
1056 console.log('ENT: currentTarget='+arguments[0].currentTarget+' id='+arguments[0].currentTarget.id+' class='+arguments[0].currentTarget.className); */
1057 tooltipResetShowTimer();
1058 tooltipResetHideTimer();
1059 return true;
1060}
1061
1062/**
1063 * Tooltip onmouseout handler.
1064 *
1065 * @remarks We only use this and onmouseenter for one tooltip element (iframe
1066 * for svn, because chrome is sending onmouseout events after
1067 * onmouseneter for the next element, which would confuse this simple
1068 * code.
1069 */
1070function tooltipElementOnMouseOut()
1071{
1072 var oEvt = arguments[0];
1073 /*console.log('tooltipElementOnMouseOut: arguments.length='+arguments.length+' [0]='+oEvt);
1074 console.log('OUT: currentTarget='+oEvt.currentTarget+' id='+oEvt.currentTarget.id+' class='+oEvt.currentTarget.className);*/
1075
1076 /* Ignore the event if leaving to a child element. */
1077 var oElm = oEvt.toElement || oEvt.relatedTarget;
1078 if (oElm != this && oElm)
1079 {
1080 for (;;)
1081 {
1082 oElm = oElm.parentNode;
1083 if (!oElm || oElm == window)
1084 break;
1085 if (oElm == this)
1086 {
1087 console.log('OUT: was to child! - ignore');
1088 return false;
1089 }
1090 }
1091 }
1092
1093 tooltipHide();
1094 return true;
1095}
1096
1097/**
1098 * iframe.onload hook that repositions and resizes the tooltip.
1099 *
1100 * This is a little hacky and we're calling it one or three times too many to
1101 * work around various browser differences too.
1102 */
1103function svnHistoryTooltipOldOnLoad()
1104{
1105 //console.log('svnHistoryTooltipOldOnLoad');
1106
1107 /*
1108 * Resize the tooltip to better fit the content.
1109 */
1110 tooltipRepositionOnLoad(); /* Sets cxMax and cyMax. */
1111 if (g_oCurrentTooltip && g_oCurrentTooltip.oIFrame.contentWindow)
1112 {
1113 var oIFrameElement = g_oCurrentTooltip.oIFrame;
1114 var cxSpace = Math.max(oIFrameElement.offsetLeft * 2, 0); /* simplified */
1115 var cySpace = Math.max(oIFrameElement.offsetTop * 2, 0); /* simplified */
1116 var cxNeeded = oIFrameElement.contentWindow.document.body.scrollWidth + cxSpace;
1117 var cyNeeded = oIFrameElement.contentWindow.document.body.scrollHeight + cySpace;
1118 var cx = Math.min(cxNeeded, g_oCurrentTooltip.cxMax);
1119 var cy;
1120
1121 g_oCurrentTooltip.oElm.width = cx + 'px';
1122 oIFrameElement.width = (cx - cxSpace) + 'px';
1123 if (cx >= cxNeeded)
1124 {
1125 //console.log('svnHistoryTooltipOldOnLoad: overflowX -> hidden');
1126 oIFrameElement.style.overflowX = 'hidden';
1127 }
1128 else
1129 {
1130 oIFrameElement.style.overflowX = 'scroll';
1131 }
1132
1133 cy = Math.min(cyNeeded, g_oCurrentTooltip.cyMax);
1134 if (cyNeeded > g_oCurrentTooltip.cyMax && g_oCurrentTooltip.cyMaxUp > 0)
1135 {
1136 var cyMove = Math.min(cyNeeded - g_oCurrentTooltip.cyMax, g_oCurrentTooltip.cyMaxUp);
1137 g_oCurrentTooltip.cyMax += cyMove;
1138 g_oCurrentTooltip.yPos -= cyMove;
1139 g_oCurrentTooltip.oElm.style.top = g_oCurrentTooltip.yPos + 'px';
1140 cy = Math.min(cyNeeded, g_oCurrentTooltip.cyMax);
1141 }
1142
1143 g_oCurrentTooltip.oElm.height = cy + 'px';
1144 oIFrameElement.height = (cy - cySpace) + 'px';
1145 if (cy >= cyNeeded)
1146 {
1147 //console.log('svnHistoryTooltipOldOnLoad: overflowY -> hidden');
1148 oIFrameElement.style.overflowY = 'hidden';
1149 }
1150 else
1151 {
1152 oIFrameElement.style.overflowY = 'scroll';
1153 }
1154
1155 //console.log('cyNeeded='+cyNeeded+' cyMax='+g_oCurrentTooltip.cyMax+' cySpace='+cySpace+' cy='+cy);
1156 //console.log('oIFrameElement.offsetTop='+oIFrameElement.offsetTop);
1157 //console.log('svnHistoryTooltipOldOnLoad: cx='+cx+'cxMax='+g_oCurrentTooltip.cxMax+' cxNeeded='+cxNeeded+' cy='+cy+' cyMax='+g_oCurrentTooltip.cyMax);
1158
1159 tooltipRepositionOnLoad();
1160 }
1161 return true;
1162}
1163
1164/**
1165 * iframe.onload hook that repositions and resizes the tooltip.
1166 *
1167 * This is a little hacky and we're calling it one or three times too many to
1168 * work around various browser differences too.
1169 */
1170function svnHistoryTooltipNewOnLoad()
1171{
1172 //console.log('svnHistoryTooltipNewOnLoad');
1173
1174 /*
1175 * Resize the tooltip to better fit the content.
1176 */
1177 tooltipRepositionOnLoad(); /* Sets cxMax and cyMax. */
1178 oTooltip = g_oCurrentTooltip;
1179 if (oTooltip)
1180 {
1181 var oElmInner = oTooltip.oInnerElm;
1182 var cxSpace = Math.max(oElmInner.offsetLeft * 2, 0); /* simplified */
1183 var cySpace = Math.max(oElmInner.offsetTop * 2, 0); /* simplified */
1184 var cxNeeded = oElmInner.scrollWidth + cxSpace;
1185 var cyNeeded = oElmInner.scrollHeight + cySpace;
1186 var cx = Math.min(cxNeeded, oTooltip.cxMax);
1187
1188 oTooltip.oElm.width = cx + 'px';
1189 oElmInner.width = (cx - cxSpace) + 'px';
1190 if (cx >= cxNeeded)
1191 {
1192 //console.log('svnHistoryTooltipNewOnLoad: overflowX -> hidden');
1193 oElmInner.style.overflowX = 'hidden';
1194 }
1195 else
1196 {
1197 oElmInner.style.overflowX = 'scroll';
1198 }
1199
1200 var cy = Math.min(cyNeeded, oTooltip.cyMax);
1201 if (cyNeeded > oTooltip.cyMax && oTooltip.cyMaxUp > 0)
1202 {
1203 var cyMove = Math.min(cyNeeded - oTooltip.cyMax, oTooltip.cyMaxUp);
1204 oTooltip.cyMax += cyMove;
1205 oTooltip.yPos -= cyMove;
1206 oTooltip.oElm.style.top = oTooltip.yPos + 'px';
1207 cy = Math.min(cyNeeded, oTooltip.cyMax);
1208 }
1209
1210 oTooltip.oElm.height = cy + 'px';
1211 oElmInner.height = (cy - cySpace) + 'px';
1212 if (cy >= cyNeeded)
1213 {
1214 //console.log('svnHistoryTooltipNewOnLoad: overflowY -> hidden');
1215 oElmInner.style.overflowY = 'hidden';
1216 }
1217 else
1218 {
1219 oElmInner.style.overflowY = 'scroll';
1220 }
1221
1222 //console.log('cyNeeded='+cyNeeded+' cyMax='+oTooltip.cyMax+' cySpace='+cySpace+' cy='+cy);
1223 //console.log('oElmInner.offsetTop='+oElmInner.offsetTop);
1224 //console.log('svnHistoryTooltipNewOnLoad: cx='+cx+'cxMax='+oTooltip.cxMax+' cxNeeded='+cxNeeded+' cy='+cy+' cyMax='+oTooltip.cyMax);
1225
1226 tooltipRepositionOnLoad();
1227 }
1228 return true;
1229}
1230
1231
1232function svnHistoryTooltipNewOnReadState(oTooltip, oRestReq, oParent)
1233{
1234 /*console.log('svnHistoryTooltipNewOnReadState: status=' + oRestReq.status + ' readyState=' + oRestReq.readyState);*/
1235 if (oRestReq.readyState != oRestReq.DONE)
1236 {
1237 oTooltip.oInnerElm.innerHTML = '<p>Loading ...(' + oRestReq.readyState + ')</p>';
1238 return true;
1239 }
1240
1241 /*
1242 * Check the result and translate it to a javascript object (oResp).
1243 */
1244 var oResp = null;
1245 var sHtml;
1246 if (oRestReq.status != 200)
1247 {
1248 console.log('svnHistoryTooltipNewOnReadState: status=' + oRestReq.status);
1249 sHtml = '<p>error: status=' + oRestReq.status + '</p>';
1250 }
1251 else
1252 {
1253 try
1254 {
1255 oResp = JSON.parse(oRestReq.responseText);
1256 }
1257 catch (oEx)
1258 {
1259 console.log('JSON.parse threw: ' + oEx.toString());
1260 console.log(oRestReq.responseText);
1261 sHtml = '<p>error: JSON.parse threw: ' + oEx.toString() + '</p>';
1262 }
1263 }
1264
1265 /*
1266 * Generate the HTML.
1267 *
1268 * Note! Make sure the highlighting code in svnHistoryTooltipNewDelayedShow
1269 * continues to work after modifying this code.
1270 */
1271 if (oResp)
1272 {
1273 sHtml = '<div class="tmvcstimeline tmvcstimelinetooltip">\n';
1274
1275 var aoCommits = oResp.aoCommits;
1276 var cCommits = oResp.aoCommits.length;
1277 var iCurDay = null;
1278 var i;
1279 for (i = 0; i < cCommits; i++)
1280 {
1281 var oCommit = aoCommits[i];
1282 var tsCreated = parseIsoTimestamp(oCommit.tsCreated);
1283 var iCommitDay = Math.floor((tsCreated.getTime() + tsCreated.getTimezoneOffset()) / (24 * 60 * 60 * 1000));
1284 if (iCurDay === null || iCurDay != iCommitDay)
1285 {
1286 if (iCurDay !== null)
1287 sHtml += ' </dl>\n';
1288 iCurDay = iCommitDay;
1289 sHtml += ' <h2>' + tsCreated.toISOString().split('T')[0] + ' ' + g_kasDaysOfTheWeek[tsCreated.getDay()] + '</h2>\n';
1290 sHtml += ' <dl>\n';
1291 }
1292 Date
1293
1294 var sHighligh = '';
1295 if (oCommit.iRevision == oTooltip.iRevision)
1296 sHighligh += ' class="tmvcstimeline-highlight"';
1297
1298 sHtml += ' <dt id="r' + oCommit.iRevision + '"' + sHighligh + '>';
1299 sHtml += '<a href="' + oResp.sTracChangesetUrlFmt.replace('%(iRevision)s', oCommit.iRevision.toString());
1300 sHtml += '" target="_blank">';
1301 sHtml += '<span class="tmvcstimeline-time">' + escapeElem(formatTimeHHMM(tsCreated, true)) + '</span>'
1302 sHtml += ' Changeset <span class="tmvcstimeline-rev">[' + oCommit.iRevision + ']</span>';
1303 sHtml += ' by <span class="tmvcstimeline-author">' + escapeElem(oCommit.sAuthor) + '</span>';
1304 sHtml += '</a></dt>\n';
1305 sHtml += ' <dd' + sHighligh + '>' + escapeElem(oCommit.sMessage) + '</dd>\n';
1306 }
1307
1308 if (iCurDay !== null)
1309 sHtml += ' </dl>\n';
1310 sHtml += '</div>';
1311 }
1312
1313 /*console.log('svnHistoryTooltipNewOnReadState: sHtml=' + sHtml);*/
1314 oTooltip.oInnerElm.innerHTML = sHtml;
1315
1316 tooltipReallyShow(oTooltip, oParent);
1317 svnHistoryTooltipNewOnLoad();
1318}
1319
1320/**
1321 * Calculates the last revision to get when showing a tooltip for @a iRevision.
1322 *
1323 * A tooltip covers several change log entries, both to limit the number of
1324 * tooltips to load and to give context. The exact number is defined by
1325 * g_cTooltipSvnRevisions.
1326 *
1327 * @returns Last revision in a tooltip.
1328 * @param iRevision The revision number.
1329 */
1330function svnHistoryTooltipCalcLastRevision(iRevision)
1331{
1332 var iFirstRev = Math.floor(iRevision / g_cTooltipSvnRevisions) * g_cTooltipSvnRevisions;
1333 return iFirstRev + g_cTooltipSvnRevisions - 1;
1334}
1335
1336/**
1337 * Calculates a unique ID for the tooltip element.
1338 *
1339 * This is also used as dictionary index.
1340 *
1341 * @returns tooltip ID value (string).
1342 * @param sRepository The repository name.
1343 * @param iRevision The revision number.
1344 */
1345function svnHistoryTooltipCalcId(sRepository, iRevision)
1346{
1347 return 'svnHistoryTooltip_' + sRepository + '_' + svnHistoryTooltipCalcLastRevision(iRevision);
1348}
1349
1350/**
1351 * The onmouseenter event handler for creating the tooltip.
1352 *
1353 * @param oEvt The event.
1354 * @param sRepository The repository name.
1355 * @param iRevision The revision number.
1356 * @param sUrlPrefix URL prefix for non-testmanager use.
1357 *
1358 * @remarks onmouseout must be set to call tooltipHide.
1359 */
1360function svnHistoryTooltipShowEx(oEvt, sRepository, iRevision, sUrlPrefix)
1361{
1362 var sKey = svnHistoryTooltipCalcId(sRepository, iRevision);
1363 var oParent = oEvt.currentTarget;
1364 //console.log('svnHistoryTooltipShow ' + sRepository);
1365
1366 function svnHistoryTooltipOldDelayedShow()
1367 {
1368 var sSrc;
1369
1370 var oTooltip = g_dTooltips[sKey];
1371 //console.log('svnHistoryTooltipOldDelayedShow ' + sRepository + ' ' + oTooltip);
1372 if (!oTooltip)
1373 {
1374 /*
1375 * Create a new tooltip element.
1376 */
1377 //console.log('creating ' + sKey);
1378 oTooltip = {};
1379 oTooltip.oElm = document.createElement('div');
1380 oTooltip.oElm.setAttribute('id', sKey);
1381 oTooltip.oElm.className = 'tmvcstooltip';
1382 //oTooltip.oElm.setAttribute('style', 'display:none; position: absolute;');
1383 oTooltip.oElm.style.display = 'none'; /* Note! Must stay hidden till loaded, or parent jumps with #rXXXX.*/
1384 oTooltip.oElm.style.position = 'absolute';
1385 oTooltip.oElm.style.zIndex = 6001;
1386 oTooltip.xPos = 0;
1387 oTooltip.yPos = 0;
1388 oTooltip.cxMax = 0;
1389 oTooltip.cyMax = 0;
1390 oTooltip.cyMaxUp = 0;
1391 oTooltip.xScroll = 0;
1392 oTooltip.yScroll = 0;
1393 oTooltip.iRevision = iRevision; /**< For :target/highlighting */
1394
1395 var oIFrameElement = document.createElement('iframe');
1396 oIFrameElement.setAttribute('id', sKey + '_iframe');
1397 oIFrameElement.style.position = 'relative';
1398 oIFrameElement.onmouseenter = tooltipElementOnMouseEnter;
1399 //oIFrameElement.onmouseout = tooltipElementOnMouseOut;
1400 oTooltip.oElm.appendChild(oIFrameElement);
1401 oTooltip.oIFrame = oIFrameElement;
1402 g_dTooltips[sKey] = oTooltip;
1403
1404 document.body.appendChild(oTooltip.oElm);
1405
1406 oIFrameElement.onload = function() { /* A slight delay here to give time for #rXXXX scrolling before we show it. */
1407 setTimeout(function(){
1408 /*console.log('iframe/onload');*/
1409 tooltipReallyShow(oTooltip, oParent);
1410 svnHistoryTooltipOldOnLoad();
1411 }, isBrowserInternetExplorer() ? 256 : 128);
1412 };
1413
1414 var sUrl = sUrlPrefix + 'index.py?Action=VcsHistoryTooltip&repo=' + sRepository
1415 + '&rev=' + svnHistoryTooltipCalcLastRevision(iRevision)
1416 + '&cEntries=' + g_cTooltipSvnRevisions
1417 + '#r' + iRevision;
1418 oIFrameElement.src = sUrl;
1419 }
1420 else
1421 {
1422 /*
1423 * Show the existing one, possibly with different :target/highlighting.
1424 */
1425 if (oTooltip.iRevision != iRevision)
1426 {
1427 //console.log('Changing revision ' + oTooltip.iRevision + ' -> ' + iRevision);
1428 oTooltip.oIFrame.contentWindow.location.hash = '#r' + iRevision;
1429 if (!isBrowserFirefox()) /* Chrome updates stuff like expected; Firefox OTOH doesn't change anything. */
1430 {
1431 setTimeout(function() { /* Slight delay to make sure it scrolls before it's shown. */
1432 tooltipReallyShow(oTooltip, oParent);
1433 svnHistoryTooltipOldOnLoad();
1434 }, isBrowserInternetExplorer() ? 256 : 64);
1435 }
1436 else
1437 oTooltip.oIFrame.contentWindow.location.reload();
1438 }
1439 else
1440 {
1441 tooltipReallyShow(oTooltip, oParent);
1442 svnHistoryTooltipOldOnLoad();
1443 }
1444 }
1445 }
1446
1447 function svnHistoryTooltipNewDelayedShow()
1448 {
1449 var sSrc;
1450
1451 var oTooltip = g_dTooltips[sKey];
1452 /*console.log('svnHistoryTooltipNewDelayedShow: ' + sRepository + ' ' + oTooltip);*/
1453 if (!oTooltip)
1454 {
1455 /*
1456 * Create a new tooltip element.
1457 */
1458 /*console.log('creating ' + sKey);*/
1459
1460 var oElm = document.createElement('div');
1461 oElm.setAttribute('id', sKey);
1462 oElm.className = 'tmvcstooltipnew';
1463 //oElm.setAttribute('style', 'display:none; position: absolute;');
1464 oElm.style.display = 'none'; /* Note! Must stay hidden till loaded, or parent jumps with #rXXXX.*/
1465 oElm.style.position = 'absolute';
1466 oElm.style.zIndex = 6001;
1467 oElm.onmouseenter = tooltipElementOnMouseEnter;
1468 oElm.onmouseout = tooltipElementOnMouseOut;
1469
1470 var oInnerElm = document.createElement('div');
1471 oInnerElm.className = 'tooltip-inner';
1472 oElm.appendChild(oInnerElm);
1473
1474 oTooltip = {};
1475 oTooltip.oElm = oElm;
1476 oTooltip.oInnerElm = oInnerElm;
1477 oTooltip.xPos = 0;
1478 oTooltip.yPos = 0;
1479 oTooltip.cxMax = 0;
1480 oTooltip.cyMax = 0;
1481 oTooltip.cyMaxUp = 0;
1482 oTooltip.xScroll = 0;
1483 oTooltip.yScroll = 0;
1484 oTooltip.iRevision = iRevision; /**< For :target/highlighting */
1485
1486 oRestReq = new XMLHttpRequest();
1487 oRestReq.onreadystatechange = function() { svnHistoryTooltipNewOnReadState(oTooltip, this, oParent); }
1488 oRestReq.open('GET', sUrlPrefix + 'rest.py?sPath=vcs/changelog/' + sRepository
1489 + '/' + svnHistoryTooltipCalcLastRevision(iRevision) + '/' + g_cTooltipSvnRevisions);
1490 oRestReq.setRequestHeader('Content-type', 'application/json');
1491
1492 document.body.appendChild(oTooltip.oElm);
1493 g_dTooltips[sKey] = oTooltip;
1494
1495 oRestReq.send('');
1496 }
1497 else
1498 {
1499 /*
1500 * Show the existing one, possibly with different highlighting.
1501 * Note! Update this code when changing svnHistoryTooltipNewOnReadState.
1502 */
1503 if (oTooltip.iRevision != iRevision)
1504 {
1505 //console.log('Changing revision ' + oTooltip.iRevision + ' -> ' + iRevision);
1506 var oElmTimelineDiv = oTooltip.oInnerElm.firstElementChild;
1507 var i;
1508 for (i = 0; i < oElmTimelineDiv.children.length; i++)
1509 {
1510 var oElm = oElmTimelineDiv.children[i];
1511 //console.log('oElm='+oElm+' id='+oElm.id+' nodeName='+oElm.nodeName);
1512 if (oElm.nodeName == 'DL')
1513 {
1514 var iCurRev = iRevision - 64;
1515 var j;
1516 for (j = 0; i < oElm.children.length; i++)
1517 {
1518 var oDlSubElm = oElm.children[i];
1519 //console.log(' oDlSubElm='+oDlSubElm+' id='+oDlSubElm.id+' nodeName='+oDlSubElm.nodeName+' className='+oDlSubElm.className);
1520 if (oDlSubElm.id.length > 2)
1521 iCurRev = parseInt(oDlSubElm.id.substring(1), 10);
1522 if (iCurRev == iRevision)
1523 oDlSubElm.className = 'tmvcstimeline-highlight';
1524 else
1525 oDlSubElm.className = '';
1526 }
1527 }
1528 }
1529 oTooltip.iRevision = iRevision;
1530 }
1531
1532 tooltipReallyShow(oTooltip, oParent);
1533 svnHistoryTooltipNewOnLoad();
1534 }
1535 }
1536
1537
1538 /*
1539 * Delay the change (in case the mouse moves on).
1540 */
1541 tooltipResetShowTimer();
1542 if (g_fNewTooltips)
1543 g_idTooltipShowTimer = setTimeout(svnHistoryTooltipNewDelayedShow, 512);
1544 else
1545 g_idTooltipShowTimer = setTimeout(svnHistoryTooltipOldDelayedShow, 512);
1546}
1547
1548/**
1549 * The onmouseenter event handler for creating the tooltip.
1550 *
1551 * @param oEvt The event.
1552 * @param sRepository The repository name.
1553 * @param iRevision The revision number.
1554 *
1555 * @remarks onmouseout must be set to call tooltipHide.
1556 */
1557function svnHistoryTooltipShow(oEvt, sRepository, iRevision)
1558{
1559 return svnHistoryTooltipShowEx(oEvt, sRepository, iRevision, '');
1560}
1561
1562/** @} */
1563
1564
1565/** @name Debugging and Introspection
1566 * @{
1567 */
1568
1569/**
1570 * Python-like dir() implementation.
1571 *
1572 * @returns Array of names associated with oObj.
1573 * @param oObj The object under inspection. If not specified we'll
1574 * look at the window object.
1575 */
1576function pythonlikeDir(oObj, fDeep)
1577{
1578 var aRet = [];
1579 var dTmp = {};
1580
1581 if (!oObj)
1582 {
1583 oObj = window;
1584 }
1585
1586 for (var oCur = oObj; oCur; oCur = Object.getPrototypeOf(oCur))
1587 {
1588 var aThis = Object.getOwnPropertyNames(oCur);
1589 for (var i = 0; i < aThis.length; i++)
1590 {
1591 if (!(aThis[i] in dTmp))
1592 {
1593 dTmp[aThis[i]] = 1;
1594 aRet.push(aThis[i]);
1595 }
1596 }
1597 }
1598
1599 return aRet;
1600}
1601
1602
1603/**
1604 * Python-like dir() implementation, shallow version.
1605 *
1606 * @returns Array of names associated with oObj.
1607 * @param oObj The object under inspection. If not specified we'll
1608 * look at the window object.
1609 */
1610function pythonlikeShallowDir(oObj, fDeep)
1611{
1612 var aRet = [];
1613 var dTmp = {};
1614
1615 if (oObj)
1616 {
1617 for (var i in oObj)
1618 {
1619 aRet.push(i);
1620 }
1621 }
1622
1623 return aRet;
1624}
1625
1626
1627
1628function dbgGetObjType(oObj)
1629{
1630 var sType = typeof oObj;
1631 if (sType == "object" && oObj !== null)
1632 {
1633 if (oObj.constructor && oObj.constructor.name)
1634 {
1635 sType = oObj.constructor.name;
1636 }
1637 else
1638 {
1639 var fnToString = Object.prototype.toString;
1640 var sTmp = fnToString.call(oObj);
1641 if (sTmp.indexOf('[object ') === 0)
1642 {
1643 sType = sTmp.substring(8, sTmp.length);
1644 }
1645 }
1646 }
1647 return sType;
1648}
1649
1650
1651/**
1652 * Dumps the given object to the console.
1653 *
1654 * @param oObj The object under inspection.
1655 * @param sPrefix What to prefix the log output with.
1656 */
1657function dbgDumpObj(oObj, sName, sPrefix)
1658{
1659 var aMembers;
1660 var sType;
1661
1662 /*
1663 * Defaults
1664 */
1665 if (!oObj)
1666 {
1667 oObj = window;
1668 }
1669
1670 if (!sPrefix)
1671 {
1672 if (sName)
1673 {
1674 sPrefix = sName + ':';
1675 }
1676 else
1677 {
1678 sPrefix = 'dbgDumpObj:';
1679 }
1680 }
1681
1682 if (!sName)
1683 {
1684 sName = '';
1685 }
1686
1687 /*
1688 * The object itself.
1689 */
1690 sPrefix = sPrefix + ' ';
1691 console.log(sPrefix + sName + ' ' + dbgGetObjType(oObj));
1692
1693 /*
1694 * The members.
1695 */
1696 sPrefix = sPrefix + ' ';
1697 aMembers = pythonlikeDir(oObj);
1698 for (i = 0; i < aMembers.length; i++)
1699 {
1700 console.log(sPrefix + aMembers[i]);
1701 }
1702
1703 return true;
1704}
1705
1706function dbgDumpObjWorker(sType, sName, oObj, sPrefix)
1707{
1708 var sRet;
1709 switch (sType)
1710 {
1711 case 'function':
1712 {
1713 sRet = sPrefix + 'function ' + sName + '()' + '\n';
1714 break;
1715 }
1716
1717 case 'object':
1718 {
1719 sRet = sPrefix + 'var ' + sName + '(' + dbgGetObjType(oObj) + ') =';
1720 if (oObj !== null)
1721 {
1722 sRet += '\n';
1723 }
1724 else
1725 {
1726 sRet += ' null\n';
1727 }
1728 break;
1729 }
1730
1731 case 'string':
1732 {
1733 sRet = sPrefix + 'var ' + sName + '(string, ' + oObj.length + ')';
1734 if (oObj.length < 80)
1735 {
1736 sRet += ' = "' + oObj + '"\n';
1737 }
1738 else
1739 {
1740 sRet += '\n';
1741 }
1742 break;
1743 }
1744
1745 case 'Oops!':
1746 sRet = sPrefix + sName + '(??)\n';
1747 break;
1748
1749 default:
1750 sRet = sPrefix + 'var ' + sName + '(' + sType + ')\n';
1751 break;
1752 }
1753 return sRet;
1754}
1755
1756
1757function dbgObjInArray(aoObjs, oObj)
1758{
1759 var i = aoObjs.length;
1760 while (i > 0)
1761 {
1762 i--;
1763 if (aoObjs[i] === oObj)
1764 {
1765 return true;
1766 }
1767 }
1768 return false;
1769}
1770
1771function dbgDumpObjTreeWorker(oObj, sPrefix, aParentObjs, cMaxDepth)
1772{
1773 var sRet = '';
1774 var aMembers = pythonlikeShallowDir(oObj);
1775 var i;
1776
1777 for (i = 0; i < aMembers.length; i++)
1778 {
1779 //var sName = i;
1780 var sName = aMembers[i];
1781 var oMember;
1782 var sType;
1783 var oEx;
1784
1785 try
1786 {
1787 oMember = oObj[sName];
1788 sType = typeof oObj[sName];
1789 }
1790 catch (oEx)
1791 {
1792 oMember = null;
1793 sType = 'Oops!';
1794 }
1795
1796 //sRet += '[' + i + '/' + aMembers.length + ']';
1797 sRet += dbgDumpObjWorker(sType, sName, oMember, sPrefix);
1798
1799 if ( sType == 'object'
1800 && oObj !== null)
1801 {
1802
1803 if (dbgObjInArray(aParentObjs, oMember))
1804 {
1805 sRet += sPrefix + '! parent recursion\n';
1806 }
1807 else if ( sName == 'previousSibling'
1808 || sName == 'previousElement'
1809 || sName == 'lastChild'
1810 || sName == 'firstElementChild'
1811 || sName == 'lastElementChild'
1812 || sName == 'nextElementSibling'
1813 || sName == 'prevElementSibling'
1814 || sName == 'parentElement'
1815 || sName == 'ownerDocument')
1816 {
1817 sRet += sPrefix + '! potentially dangerous element name\n';
1818 }
1819 else if (aParentObjs.length >= cMaxDepth)
1820 {
1821 sRet = sRet.substring(0, sRet.length - 1);
1822 sRet += ' <too deep>!\n';
1823 }
1824 else
1825 {
1826
1827 aParentObjs.push(oMember);
1828 if (i + 1 < aMembers.length)
1829 {
1830 sRet += dbgDumpObjTreeWorker(oMember, sPrefix + '| ', aParentObjs, cMaxDepth);
1831 }
1832 else
1833 {
1834 sRet += dbgDumpObjTreeWorker(oMember, sPrefix.substring(0, sPrefix.length - 2) + ' | ', aParentObjs, cMaxDepth);
1835 }
1836 aParentObjs.pop();
1837 }
1838 }
1839 }
1840 return sRet;
1841}
1842
1843/**
1844 * Dumps the given object and all it's subobjects to the console.
1845 *
1846 * @returns String dump of the object.
1847 * @param oObj The object under inspection.
1848 * @param sName The object name (optional).
1849 * @param sPrefix What to prefix the log output with (optional).
1850 * @param cMaxDepth The max depth, optional.
1851 */
1852function dbgDumpObjTree(oObj, sName, sPrefix, cMaxDepth)
1853{
1854 var sType;
1855 var sRet;
1856 var oEx;
1857
1858 /*
1859 * Defaults
1860 */
1861 if (!sPrefix)
1862 {
1863 sPrefix = '';
1864 }
1865
1866 if (!sName)
1867 {
1868 sName = '??';
1869 }
1870
1871 if (!cMaxDepth)
1872 {
1873 cMaxDepth = 2;
1874 }
1875
1876 /*
1877 * The object itself.
1878 */
1879 try
1880 {
1881 sType = typeof oObj;
1882 }
1883 catch (oEx)
1884 {
1885 sType = 'Oops!';
1886 }
1887 sRet = dbgDumpObjWorker(sType, sName, oObj, sPrefix);
1888 if (sType == 'object' && oObj !== null)
1889 {
1890 var aParentObjs = Array();
1891 aParentObjs.push(oObj);
1892 sRet += dbgDumpObjTreeWorker(oObj, sPrefix + '| ', aParentObjs, cMaxDepth);
1893 }
1894
1895 return sRet;
1896}
1897
1898function dbgLogString(sLongString)
1899{
1900 var aStrings = sLongString.split("\n");
1901 var i;
1902 for (i = 0; i < aStrings.length; i++)
1903 {
1904 console.log(aStrings[i]);
1905 }
1906 console.log('dbgLogString - end - ' + aStrings.length + '/' + sLongString.length);
1907 return true;
1908}
1909
1910function dbgLogObjTree(oObj, sName, sPrefix, cMaxDepth)
1911{
1912 return dbgLogString(dbgDumpObjTree(oObj, sName, sPrefix, cMaxDepth));
1913}
1914
1915/** @} */
1916
Note: See TracBrowser for help on using the repository browser.

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