VirtualBox

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

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

scm attempt #3

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