/**
* This file is part of the Tracy (https://tracy.nette.org)
*/
'use strict';
(function(){
let nonce, contentId, ajaxCounter = 1;
let baseUrl = location.href.split('#')[0];
baseUrl += (baseUrl.indexOf('?') < 0 ? '?' : '&');
class Panel
{
constructor(id) {
this.id = id;
this.elem = document.getElementById(this.id);
this.elem.Tracy = this.elem.Tracy || {};
}
init() {
let elem = this.elem;
this.init = function() {};
elem.innerHTML = addNonces(elem.dataset.tracyContent);
Tracy.Dumper.init(Debug.layer);
delete elem.dataset.tracyContent;
evalScripts(elem);
draggable(elem, {
handles: elem.querySelectorAll('h1'),
start: () => {
if (!this.is(Panel.FLOAT)) {
this.toFloat();
}
this.focus();
this.peekPosition = false;
}
});
elem.addEventListener('mousedown', () => {
this.focus();
});
elem.addEventListener('mouseenter', () => {
clearTimeout(elem.Tracy.displayTimeout);
});
elem.addEventListener('mouseleave', () => {
this.blur();
});
elem.addEventListener('mousemove', (e) => {
if (e.buttons && !this.is(Panel.RESIZED) && (elem.style.width || elem.style.height)) {
elem.classList.add(Panel.RESIZED);
}
});
elem.addEventListener('tracy-toggle', () => {
this.reposition();
});
elem.querySelectorAll('.tracy-icons a').forEach((link) => {
link.addEventListener('click', (e) => {
if (link.dataset.tracyAction === 'close') {
this.toPeek();
} else if (link.dataset.tracyAction === 'window') {
this.toWindow();
}
e.preventDefault();
e.stopImmediatePropagation();
});
});
if (this.is('tracy-panel-persist')) {
Tracy.Toggle.persist(elem);
}
}
is(mode) {
return this.elem.classList.contains(mode);
}
focus() {
let elem = this.elem;
if (this.is(Panel.WINDOW)) {
elem.Tracy.window.focus();
} else if (!this.is(Panel.FOCUSED)) {
for (let id in Debug.panels) {
Debug.panels[id].elem.classList.remove(Panel.FOCUSED);
}
elem.classList.add(Panel.FOCUSED);
elem.style.zIndex = Tracy.panelZIndex + Panel.zIndexCounter++;
}
}
blur() {
let elem = this.elem;
if (this.is(Panel.PEEK)) {
clearTimeout(elem.Tracy.displayTimeout);
elem.Tracy.displayTimeout = setTimeout(() => {
elem.classList.remove(Panel.FOCUSED);
}, 50);
}
}
toFloat() {
this.elem.classList.remove(Panel.WINDOW);
this.elem.classList.remove(Panel.PEEK);
this.elem.classList.add(Panel.FLOAT);
this.elem.classList.remove(Panel.RESIZED);
this.reposition();
}
toPeek() {
this.elem.classList.remove(Panel.WINDOW);
this.elem.classList.remove(Panel.FLOAT);
this.elem.classList.remove(Panel.FOCUSED);
this.elem.classList.add(Panel.PEEK);
this.elem.style.width = '';
this.elem.style.height = '';
this.elem.classList.remove(Panel.RESIZED);
}
toWindow() {
let offset = getOffset(this.elem);
offset.left += typeof window.screenLeft === 'number' ? window.screenLeft : (window.screenX + 10);
offset.top += typeof window.screenTop === 'number' ? window.screenTop : (window.screenY + 50);
let win = window.open('', this.id.replace(/-/g, '_'), 'left=' + offset.left + ',top=' + offset.top
+ ',width=' + this.elem.offsetWidth + ',height=' + this.elem.offsetHeight + ',resizable=yes,scrollbars=yes');
if (!win) {
return false;
}
let doc = win.document;
doc.write('<!DOCTYPE html><meta charset="utf-8">'
+ '<script src="' + (baseUrl.replace('&', '&').replace('"', '"')) + '_tracy_bar=js&XDEBUG_SESSION_STOP=1" onload="Tracy.Dumper.init()" async></script>'
+ '<body id="tracy-debug">'
);
doc.body.innerHTML = '<div class="tracy-panel tracy-mode-window" id="' + this.elem.id + '">' + this.elem.innerHTML + '</div>';
evalScripts(doc.body);
if (this.elem.querySelector('h1')) {
doc.title = this.elem.querySelector('h1').textContent;
}
win.addEventListener('beforeunload', () => {
this.toPeek();
win.close(); // forces closing, can be invoked by F5
});
doc.addEventListener('keyup', (e) => {
if (e.keyCode === 27 && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) {
win.close();
}
});
this.elem.classList.remove(Panel.FLOAT);
this.elem.classList.remove(Panel.PEEK);
this.elem.classList.remove(Panel.FOCUSED);
this.elem.classList.remove(Panel.RESIZED);
this.elem.classList.add(Panel.WINDOW);
this.elem.Tracy.window = win;
return true;
}
reposition(deltaX, deltaY) {
let pos = getPosition(this.elem);
if (pos.width) { // is visible?
setPosition(this.elem, {left: pos.left + (deltaX || 0), top: pos.top + (deltaY || 0)});
if (this.is(Panel.RESIZED)) {
let size = getWindowSize();
this.elem.style.width = Math.min(size.width, pos.width) + 'px';
this.elem.style.height = Math.min(size.height, pos.height) + 'px';
}
}
}
savePosition() {
let key = this.id.split(':')[0]; // remove :contentId part
let pos = getPosition(this.elem);
if (this.is(Panel.WINDOW)) {
localStorage.setItem(key, JSON.stringify({window: true}));
} else if (pos.width) { // is visible?
localStorage.setItem(key, JSON.stringify({right: pos.right, bottom: pos.bottom, width: pos.width, height: pos.height, zIndex: this.elem.style.zIndex - Tracy.panelZIndex, resized: this.is(Panel.RESIZED)}));
} else {
localStorage.removeItem(key);
}
}
restorePosition() {
let key = this.id.split(':')[0];
let pos = JSON.parse(localStorage.getItem(key));
if (!pos) {
this.elem.classList.add(Panel.PEEK);
} else if (pos.window) {
this.init();
this.toWindow() || this.toFloat();
} else if (this.elem.dataset.tracyContent) {
this.init();
this.toFloat();
if (pos.resized) {
this.elem.classList.add(Panel.RESIZED);
this.elem.style.width = pos.width + 'px';
this.elem.style.height = pos.height + 'px';
}
setPosition(this.elem, pos);
this.elem.style.zIndex = Tracy.panelZIndex + (pos.zIndex || 1);
Panel.zIndexCounter = Math.max(Panel.zIndexCounter, (pos.zIndex || 1)) + 1;
}
}
}
Panel.PEEK = 'tracy-mode-peek';
Panel.FLOAT = 'tracy-mode-float';
Panel.WINDOW = 'tracy-mode-window';
Panel.FOCUSED = 'tracy-focused';
Panel.RESIZED = 'tracy-panel-resized';
Panel.zIndexCounter = 1;
class Bar
{
init() {
this.id = 'tracy-debug-bar';
this.elem = document.getElementById(this.id);
draggable(this.elem, {
handles: this.elem.querySelectorAll('li:first-child'),
draggedClass: 'tracy-dragged',
stop: () => {
this.savePosition();
}
});
this.elem.addEventListener('mousedown', (e) => {
e.preventDefault();
});
this.initTabs(this.elem);
this.restorePosition();
(new MutationObserver(() => {
this.restorePosition();
})).observe(this.elem, {childList: true, characterData: true, subtree: true});
}
initTabs(elem) {
elem.querySelectorAll('a').forEach((link) => {
link.addEventListener('click', (e) => {
if (link.dataset.tracyAction === 'close') {
this.close();
} else if (link.rel) {
let panel = Debug.panels[link.rel];
panel.init();
if (e.shiftKey) {
panel.toFloat();
panel.toWindow();
} else if (panel.is(Panel.FLOAT)) {
panel.toPeek();
} else {
panel.toFloat();
if (panel.peekPosition) {
panel.reposition(-Math.round(Math.random() * 100) - 20, (Math.round(Math.random() * 100) + 20) * (this.isAtTop() ? 1 : -1));
panel.peekPosition = false;
}
}
}
e.preventDefault();
e.stopImmediatePropagation();
});
link.addEventListener('mouseenter', (e) => {
if (e.buttons || !link.rel || elem.classList.contains('tracy-dragged')) {
return;
}
clearTimeout(this.displayTimeout);
this.displayTimeout = setTimeout(() => {
let panel = Debug.panels[link.rel];
panel.focus();
if (panel.is(Panel.PEEK)) {
panel.init();
let pos = getPosition(panel.elem);
setPosition(panel.elem, {
left: getOffset(link).left + getPosition(link).width + 4 - pos.width,
top: this.isAtTop()
? getOffset(this.elem).top + getPosition(this.elem).height + 4
: getOffset(this.elem).top - pos.height - 4
});
panel.peekPosition = true;
}
}, 50);
});
link.addEventListener('mouseleave', () => {
clearTimeout(this.displayTimeout);
if (link.rel && !elem.classList.contains('tracy-dragged')) {
Debug.panels[link.rel].blur();
}
});
});
this.autoHideLabels();
}
autoHideLabels() {
let width = getWindowSize().width;
this.elem.querySelectorAll('.tracy-row').forEach((row) => {
let i, labels = row.querySelectorAll('.tracy-label');
for (i = 0; i < labels.length && row.clientWidth < width; i++) {
labels.item(i).hidden = false;
}
for (i = labels.length - 1; i >= 0 && row.clientWidth >= width; i--) {
labels.item(i).hidden = true;
}
});
}
close() {
document.getElementById('tracy-debug').style.display = 'none';
}
reposition(deltaX, deltaY) {
let pos = getPosition(this.elem);
if (pos.width) { // is visible?
setPosition(this.elem, {left: pos.left + (deltaX || 0), top: pos.top + (deltaY || 0)});
this.savePosition();
}
}
savePosition() {
let pos = getPosition(this.elem);
if (pos.width) { // is visible?
localStorage.setItem(this.id, JSON.stringify(this.isAtTop() ? {right: pos.right, top: pos.top} : {right: pos.right, bottom: pos.bottom}));
}
}
restorePosition() {
let pos = JSON.parse(localStorage.getItem(this.id));
setPosition(this.elem, pos || {right: 0, bottom: 0});
this.savePosition();
}
isAtTop() {
let pos = getPosition(this.elem);
return pos.top < 100 && pos.bottom > pos.top;
}
}
class Debug
{
static init(content) {
Debug.layer = document.createElement('div');
Debug.layer.setAttribute('id', 'tracy-debug');
Debug.layer.innerHTML = addNonces(content);
(document.body || document.documentElement).appendChild(Debug.layer);
evalScripts(Debug.layer);
Tracy.Dumper.init(); // for common dump()
Debug.layer.style.display = 'block';
Debug.bar.init();
Debug.layer.querySelectorAll('.tracy-panel').forEach((panel) => {
Debug.panels[panel.id] = new Panel(panel.id);
Debug.panels[panel.id].restorePosition();
});
Debug.captureWindow();
Debug.captureAjax();
Tracy.TableSort.init();
}
static loadAjax(content) {
let rows = Debug.bar.elem.querySelectorAll('.tracy-row[data-tracy-group=ajax]');
rows = Array.from(rows).reverse();
let max = window.TracyMaxAjaxRows || 3;
rows.forEach((row) => {
if (--max > 0) {
return;
}
row.querySelectorAll('a[rel]').forEach((tab) => {
let panel = Debug.panels[tab.rel];
if (panel.is(Panel.PEEK)) {
delete Debug.panels[tab.rel];
panel.elem.parentNode.removeChild(panel.elem);
}
});
row.parentNode.removeChild(row);
});
if (rows[0]) { // update content in first-row panels
rows[0].querySelectorAll('a[rel]').forEach((tab) => {
Debug.panels[tab.rel].savePosition();
Debug.panels[tab.rel].toPeek();
});
}
Debug.layer.insertAdjacentHTML('beforeend', content.panels);
evalScripts(Debug.layer);
Debug.bar.elem.insertAdjacentHTML('beforeend', content.bar);
let ajaxBar = Debug.bar.elem.querySelector('.tracy-row:last-child');
Debug.layer.querySelectorAll('.tracy-panel').forEach((panel) => {
if (!Debug.panels[panel.id]) {
Debug.panels[panel.id] = new Panel(panel.id);
Debug.panels[panel.id].restorePosition();
}
});
Debug.bar.initTabs(ajaxBar);
}
static captureWindow() {
let size = getWindowSize();
window.addEventListener('resize', () => {
let newSize = getWindowSize();
Debug.bar.reposition(newSize.width - size.width, newSize.height - size.height);
Debug.bar.autoHideLabels();
for (let id in Debug.panels) {
Debug.panels[id].reposition(newSize.width - size.width, newSize.height - size.height);
}
size = newSize;
});
window.addEventListener('unload', () => {
for (let id in Debug.panels) {
Debug.panels[id].savePosition();
}
});
}
static captureAjax() {
let header = Tracy.getAjaxHeader();
if (!header) {
return;
}
let oldOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
oldOpen.apply(this, arguments);
if (window.TracyAutoRefresh !== false && new URL(arguments[1], location.origin).host === location.host) {
let reqId = header + '_' + ajaxCounter++;
this.setRequestHeader('X-Tracy-Ajax', reqId);
this.addEventListener('load', function() {
if (this.getAllResponseHeaders().match(/^X-Tracy-Ajax: 1/mi)) {
Debug.loadScript(baseUrl + '_tracy_bar=content-ajax.' + reqId + '&XDEBUG_SESSION_STOP=1&v=' + Math.random());
}
});
}
};
let oldFetch = window.fetch;
window.fetch = function(request, options) {
request = request instanceof Request ? request : new Request(request, options || {});
if (window.TracyAutoRefresh !== false && new URL(request.url, location.origin).host === location.host) {
let reqId = header + '_' + ajaxCounter++;
request.headers.set('X-Tracy-Ajax', reqId);
return oldFetch(request).then((response) => {
if (response.headers.has('X-Tracy-Ajax') && response.headers.get('X-Tracy-Ajax')[0] === '1') {
Debug.loadScript(baseUrl + '_tracy_bar=content-ajax.' + reqId + '&XDEBUG_SESSION_STOP=1&v=' + Math.random());
}
return response;
});
}
return oldFetch(request);
};
}
static loadScript(url) {
if (Debug.scriptElem) {
Debug.scriptElem.parentNode.removeChild(Debug.scriptElem);
}
Debug.scriptElem = document.createElement('script');
Debug.scriptElem.src = url;
Debug.scriptElem.setAttribute('nonce', nonce);
(document.body || document.documentElement).appendChild(Debug.scriptElem);
}
}
function evalScripts(elem) {
elem.querySelectorAll('script').forEach((script) => {
if ((!script.hasAttribute('type') || script.type === 'text/javascript' || script.type === 'application/javascript') && !script.tracyEvaluated) {
let document = script.ownerDocument;
let dolly = document.createElement('script');
dolly.textContent = script.textContent;
dolly.setAttribute('nonce', nonce);
(document.body || document.documentElement).appendChild(dolly);
script.tracyEvaluated = true;
}
});
}
let dragging;
function draggable(elem, options) {
let dE = document.documentElement, started, deltaX, deltaY, clientX, clientY;
options = options || {};
let redraw = function () {
if (dragging) {
setPosition(elem, {left: clientX + deltaX, top: clientY + deltaY});
requestAnimationFrame(redraw);
}
};
let onMove = function(e) {
if (e.buttons === 0) {
return onEnd(e);
}
if (!started) {
if (options.draggedClass) {
elem.classList.add(options.draggedClass);
}
if (options.start) {
options.start(e, elem);
}
started = true;
}
clientX = e.touches ? e.touches[0].clientX : e.clientX;
clientY = e.touches ? e.touches[0].clientY : e.clientY;
return false;
};
let onEnd = function(e) {
if (started) {
if (options.draggedClass) {
elem.classList.remove(options.draggedClass);
}
if (options.stop) {
options.stop(e, elem);
}
}
dragging = null;
dE.removeEventListener('mousemove', onMove);
dE.removeEventListener('mouseup', onEnd);
dE.removeEventListener('touchmove', onMove);
dE.removeEventListener('touchend', onEnd);
return false;
};
let onStart = function(e) {
e.preventDefault();
e.stopPropagation();
if (dragging) { // missed mouseup out of window?
return onEnd(e);
}
let pos = getPosition(elem);
clientX = e.touches ? e.touches[0].clientX : e.clientX;
clientY = e.touches ? e.touches[0].clientY : e.clientY;
deltaX = pos.left - clientX;
deltaY = pos.top - clientY;
dragging = true;
started = false;
dE.addEventListener('mousemove', onMove);
dE.addEventListener('mouseup', onEnd);
dE.addEventListener('touchmove', onMove);
dE.addEventListener('touchend', onEnd);
requestAnimationFrame(redraw);
if (options.start) {
options.start(e, elem);
}
};
options.handles.forEach((handle) => {
handle.addEventListener('mousedown', onStart);
handle.addEventListener('touchstart', onStart);
handle.addEventListener('click', (e) => {
if (started) {
e.stopImmediatePropagation();
}
});
});
}
// returns total offset for element
function getOffset(elem) {
let res = {left: elem.offsetLeft, top: elem.offsetTop};
while (elem = elem.offsetParent) { // eslint-disable-line no-cond-assign
res.left += elem.offsetLeft; res.top += elem.offsetTop;
}
return res;
}
function getWindowSize() {
return {
width: document.documentElement.clientWidth,
height: document.compatMode === 'BackCompat' ? window.innerHeight : document.documentElement.clientHeight
};
}
// move to new position
function setPosition(elem, coords) {
let win = getWindowSize();
if (typeof coords.right !== 'undefined') {
coords.left = win.width - elem.offsetWidth - coords.right;
}
if (typeof coords.bottom !== 'undefined') {
coords.top = win.height - elem.offsetHeight - coords.bottom;
}
elem.style.left = Math.max(0, Math.min(coords.left, win.width - elem.offsetWidth)) + 'px';
elem.style.top = Math.max(0, Math.min(coords.top, win.height - elem.offsetHeight)) + 'px';
}
// returns current position
function getPosition(elem) {
let win = getWindowSize();
return {
left: elem.offsetLeft,
top: elem.offsetTop,
right: win.width - elem.offsetWidth - elem.offsetLeft,
bottom: win.height - elem.offsetHeight - elem.offsetTop,
width: elem.offsetWidth,
height: elem.offsetHeight
};
}
function addNonces(html) {
let el = document.createElement('div');
el.innerHTML = html;
el.querySelectorAll('style').forEach((style) => {
style.setAttribute('nonce', nonce);
});
return el.innerHTML;
}
if (document.currentScript) {
nonce = document.currentScript.getAttribute('nonce') || document.currentScript.nonce;
contentId = document.currentScript.dataset.id;
}
let Tracy = window.Tracy = window.Tracy || {};
Tracy.panelZIndex = Tracy.panelZIndex || 20000;
Tracy.DebugPanel = Panel;
Tracy.DebugBar = Bar;
Tracy.Debug = Debug;
Tracy.getAjaxHeader = () => contentId;
Debug.bar = new Bar;
Debug.panels = {};
})();
|