VirtualBox

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

Last change on this file since 106061 was 106061, checked in by vboxsync, 4 months ago

Copyright year updates by scm.

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