dom inspector

  • Thread starter Robert Skidmore
  • Start date
R

Robert Skidmore

I made this javascript to help me debug some javascript one
time and I have just been adding to it over the years. It
is IE specific (sure it would not be to hard to change that).
It is not ever meant to be rolled to a production environment.

What it does:
After you displaying the debug window (see tip 1) you can
move your mouse over any element on the page and the window
will populate with all the properties, events, and objects
(yea, I know they are all really properties) of that element.

Instructions:
Place the following line in your page you wish to debug:
<script language="JavaScript" src="/Debugger.js"></script>
(this assumes your saved down the code to a file named
debugger.js in the root of your site)


Tips:
1. You can make the debug window appear by pressing "ctrl+shift+d"
2. There are three icons on the top they are as followed:
Parent object - Loads the parent object of the current object
Lock Object - Click to lock in the current object
Close - Closes the dom inspector window
3. You can press and hold CTRL and it will also lock the object in
3. You can click on a object listed in the object tag and
the debug window will populate with that objects data
4. You can click on the property name, or event name to goto
the msdn definition of that property or event.
5. You can click on the field headers to sort


You can freely use this with only one condition, credit goes
to me.
That mean if you tell your boss you wrote this, I will hunt
you down and feed your ears to my pet sloth.


PS: Please post any impovement to this thread. Thx


document.onmousemove = document_onmousemove;
document.onmouseup = dragDropRelease;
document.onkeypress = document_onkeypress;


var lastobject;
var currenttab = "Properties";
var keyspressed = false;
var lastx;
var lasty;
var currentlyDragging = false;
var offsetTopY = 0;
var offsetLeftX = 0;
var tabsChanged = false;
var DebuggerObjectList = new Array();

var debugger_selected_border_top = '';
var debugger_selected_border_bottom = '';
var debugger_selected_border_right = '';
var debugger_selected_border_left = '';

var debugger_Lock_Current_object = false;


//----------------------------------------------------
//--------------------------------------Building Table
//----------------------------------------------------

function WriteTable(x, y){
var InnerTbl1
try{
var InnerTbl1 =
document.getElementById("dubuggerInnerTbl1");
}catch(ex){}

if (InnerTbl1 == null){
OuterDiv1 = builddebugOuterDiv1(x,y);
OuterTbl1 = builddebugOuterTbl1();
var tb = document.createElement('tbody');
tb.appendChild(buildBar());
tr=document.createElement('tr');
tr.appendChild(createTab("Properties"));
tr.appendChild(createTab("Events"));
tr.appendChild(createTab("Objects"));
tb.appendChild(tr);
tr=document.createElement('tr');
var td=document.createElement('td');
td.innerText=currenttab;
td.style.textAlign='center';
td.style.padding='2px';
td.colSpan = '3';
td.id = 'innerTblTitle1';
tr.appendChild(td);
tb.appendChild(tr);
tr=document.createElement('tr');
var td=document.createElement('td');
td.colSpan = '3';
InnerTbl1 = document.createElement('table');
InnerTbl1.id = 'dubuggerInnerTbl1';
InnerTbl1.border = '0';
InnerTbl1.cellPadding = '1';
InnerTbl1.style.width = '100%';
var th2=document.createElement('thead');
var tr2=document.createElement('tr');
tr2.appendChild(debugger_createSortButton("Name"));
tr2.appendChild(debugger_createSortButton("Value"));
th2.appendChild(tr2);
InnerTbl1.appendChild(th2);

var div1 = document.createElement('div');
div1.style.overflow = 'auto';
div1.style.height = '250px';
div1.appendChild(InnerTbl1);

td.appendChild(div1);
tr.appendChild(td);
tb.appendChild(tr);
OuterTbl1.appendChild(tb);
OuterDiv1.appendChild(OuterTbl1);
document.body.appendChild(OuterDiv1);
return document.getElementById("dubuggerInnerTbl1");
}else{
var InnerTable1RowLength = InnerTbl1.rows.length - 1;
for (var i = 1; i <= InnerTable1RowLength; i++){
InnerTbl1.deleteRow(1);
}
return InnerTbl1;
}
}

function builddebugOuterDiv1(x,y){
var d = document.createElement('div');
d.id = 'debugOuterDiv1';
d.style.position = 'absolute';
d.style.top = x;
d.style.left = y;
//d.style.overflow = 'auto';
d.style.width = '318px';
d.style.height = '321px';
d.style.backgroundColor = 'white';
d.style.borderWidth = '4';
d.style.borderColor = '#3300ff';
d.style.borderStyle = 'groove';
return d;
}

function builddebugOuterTbl1(){
var OuterTbl1 = document.createElement('table');
OuterTbl1.style.width='310px';
OuterTbl1.cellPadding = '0';
OuterTbl1.cellSpacing = '0';
return OuterTbl1;
}

function debugger_createSortButton(debugger_title){
var td=document.createElement('td');
td.innerText = debugger_title;//'Name';
td.id = 'sort' + debugger_title;
td.style.backgroundColor = 'silver';
td.style.fontFamily = 'arial'
td.style.fontSize = '8pt';
td.style.cursor = 'hand';
td.style.paddingTop = '0';
td.style.paddingBottom = '0';
td.style.paddingLeft = '2px';
td.style.height = '16px';
td.style.borderTop = '1px solid buttonhighlight';
td.style.borderLeft = '1px solid buttonhighlight';
td.style.borderRight = '1px solid buttonshadow';
td.style.borderBottom = '1px solid buttonshadow';
td.onclick = new Function("sortRow(this)");
return td;
}

function getObjectName(o){
var n = "";
n =
isnull(o.tagName,
isnull(o.id,
isnull(o.name,
isnull(o.value, new String(o)))));
return n;
}

function isnull(checkthis, usethisifso){
if ((checkthis == null) ||
(checkthis == 'undefined') ||
(checkthis == '') || (checkthis == '0')){
return usethisifso;
}else{
return checkthis;
}
}


//---------------------------------------------------------
//-------------------------------------Build Object Results
//---------------------------------------------------------

function buildObjectResults(theobject, x, y){
if ((theobject == 'undefined') ||
(theobject == null) ||
(hasParent(theobject, 'debugOuterDiv1')))
return;
if ((x == 'undefined') || (x == null))
x = lastx;
if ((y == 'undefined') || (y == null))
y = lasty;
var protbl = WriteTable(x, y);
var TitleBarLink = document.createElement("a");
TitleBarLink.href =
"http://msdn.microsoft.com/library/default.asp?" +
"url=/workshop/author/dhtml/reference/objects/" +
getObjectName(theobject) + ".asp";
TitleBarLink.target = "_blank";
TitleBarLink.style.color = "white";
TitleBarLink.innerText = getObjectName(theobject);
document.getElementById("debuggerTitleCell1").innerHTML
= "";
document.getElementById(
"debuggerTitleCell1").appendChild(TitleBarLink);
DebuggerObjectList = new Array();
var i = 0;
var objectCount = 0;
for (p in theobject){
i++;
var prop = new String(p);
var results = "";
try{
results = new String(
eval("theobject." + prop + ";")
);
}catch(ex){results = ""}
if (results != ""){
switch(currenttab){
case "Properties":
if ((prop.substring(0, 2) != 'on')
&& (results != '[object]'))
newRow(protbl, prop, results);
break;
case "Events":
if (prop.substring(0, 2) == 'on')
newRow(protbl, prop, results);
break;
case "Objects":
if (results == '[object]'){
DebuggerObjectList[objectCount]
= eval("theobject." + prop + ";");
objectCount++;
newRow(protbl, prop, results);
}
break;
}
}
if (i > 200)
break;
}
setDefaultArrows();
objSortTable(document.getElementById('dubuggerInnerTbl1'));
}




function newRow(protbl, prop, prop_value){
aRow = protbl.insertRow();
aRow.id = 'debuggerDataRow1' + prop;
newCell(aRow, prop, true, "right", "40px");
newCell(aRow, prop_value, false, "left", "180px");
}

var cellCounter = 0;
function newCell(aRow, prop, link, align, width){
cellCounter++;
DataCell1 = aRow.insertCell();
DataCell1.id = 'debuggerDataCell1' + cellCounter;
DataCell1.style.width = width;
DataCell1.style.fontFamily = 'Arial';
DataCell1.style.fontSize = '8pt';
DataCell1.style.backgroundColor = '#f5f5f5';

if (link){
if (currenttab == 'Objects')
DataCell1.onclick = new Function(
"buildObjectResults2(" +
(DebuggerObjectList.length - 1) +
")");
else if (currenttab == 'Properties')
DataCell1.onclick = new Function (
"window.open(" +
"'http://msdn.microsoft.com/workshop/author/dhtml/" +
"reference/properties/" + prop + ".asp')");
else if (currenttab == 'Events')
DataCell1.onclick = new Function (
"window.open(" +
"'http://msdn.microsoft.com/workshop/author/dhtml" +
"/reference/properties/" + prop + ".asp')");
DataCell1.style.cursor = 'hand';
}
DataCell1.textAlign = align;
DataCell1.style.verticalAlign = "top";

var replaceRegExprece2 = />/g;
prop = prop.replace(replaceRegExprece2,">\n\r")
if (prop.length >= 50){
DataDiv1 = document.createElement('div');
DataDiv1.id = 'debuggerDataDiv1' + cellCounter;
DataDiv1.style.overflow = 'auto';
DataDiv1.style.width = width;
DataDiv1.style.height = '120px';
DataDiv1.innerText = prop;
DataCell1.appendChild(DataDiv1)
}else{
DataCell1.noWrap = 'nowrap';
DataCell1.innerText = prop;
}

if (!link){
var replaceRegExprece1 = /\'/g;
DataButtonCopy = document.createElement('a');
DataButtonCopy.innerText = " - Copy";
DataButtonCopy.style.padding='2px';
DataButtonCopy.href = "javascript:void(" +
"window.clipboardData.setData('Text', '" +
prop.replace(replaceRegExprece1, "\\'") + "'));";
DataCell1.appendChild(DataButtonCopy)
}
}


function buildObjectResults2(i){
var DebuggerTempObject = DebuggerObjectList;
buildObjectResults(DebuggerTempObject);
lastobject = DebuggerTempObject;
}

//------------------------------------------------------
//----------------------------------Dom Parent Functions
//------------------------------------------------------

function getParent(o, tagName){
if (o == null)
return null;
else if (o.nodeType == 1 && o.tagName == tagName)
return o;

if (o.parentNode != null){
return getParent(o.parentNode, tagName);
}else{
return null;
}
}


function hasParent(theobject, parentid) {
try{
if (theobject == null)
return false;
else if (theobject.nodeType == 1 && theobject.id == parentid)
return true;
}catch(ex){
window.alert(ex.message);
}
try{
if (theobject.parentNode != null){
return hasParent(theobject.parentNode, parentid);
}else{
return false;
}
}catch(ex){
window.alert(ex.message);
return false;
}
}

//------------------------------------------------------
//---------------------------------------------Key Mouse
//------------------------------------------------------

function document_onkeypress(){
if ((event.ctrlKey)
&& (event.shiftKey)
&& (event.keyCode == 4)){
if (keyspressed == true){
try{
document.getElementById(
"debugOuterDiv1").style.display = "none";
keyspressed = false;
}catch(ex){}
}else{
keyspressed = true;
try{
document.getElementById(
"debugOuterDiv1").style.display = "block";
}catch(ex){}
}
}
}

function document_onmousemove(){
if (!event.ctrlKey){
if (keyspressed == true){
lastx = event.clientX + 10;
lasty = event.clientY + 10;
var theobject =
document.elementFromPoint(lastx - 10, lasty - 10);
if ((!debugger_Lock_Current_object) &&
(theobject != null) &&
(theobject != lastobject) &&
(!hasParent(theobject, 'debugOuterDiv1'))){
buildObjectResults(theobject, lastx, lasty);
lastobject = theobject;
}
}
if (currentlyDragging){
OuterDiv1 = document.getElementById(
"debugOuterDiv1");
OuterDiv1.style.top = event.clientY - offsetTopY;
OuterDiv1.style.left = event.clientX - offsetLeftX;
}
}
}


//------------------------------------------------------
//--------------------------------------------------Tabs
//------------------------------------------------------

function createTab(tabName){
var td=document.createElement('td');
td.style.borderRight = 'gray 1px solid';
td.style.borderTop = 'gray 1px solid';
td.style.borderLeft = 'gray 1px solid';
td.style.width = '104px';
if (tabName != currenttab)
td.style.borderBottom = 'gray 1px solid';
td.style.cursor = 'hand';
switch(tabName){
case "Properties":
td.onclick = changeTabsProperties;
break;
case "Events":
td.onclick = changeTabsEvents;
break;
case "Objects":
td.onclick = changeTabsObjects;
break;
}
td.innerText=tabName;
td.style.textAlign='center';
td.style.padding='2px';
td.id = 'debuggerTabCell1' + tabName;
return td;
}

function changeTabsProperties(){
if (currenttab != "Properties"){
try{
var a = document.getElementById("debuggerTabCell1"
+ currenttab);
a.style.borderBottom = 'gray 1px solid';
a = document.getElementById(
"debuggerTabCell1Properties");
a.style.borderBottom = '';
a = document.getElementById("innerTblTitle1")
a.innerText = 'Properties';
}catch(ex){}
currenttab = "Properties";
buildObjectResults(lastobject,null,null);
}
}

function changeTabsEvents(){
if (currenttab != "Events"){
try{
var a = document.getElementById("debuggerTabCell1"
+ currenttab);
a.style.borderBottom = 'gray 1px solid';
a = document.getElementById(
"debuggerTabCell1Events");
a.style.borderBottom = '';
a = document.getElementById("innerTblTitle1")
a.innerText = 'Events';
}catch(ex){}
currenttab = "Events";
buildObjectResults(lastobject,null,null);
}
}

function changeTabsObjects(){
if (currenttab != "Objects"){
try{
var a = document.getElementById("debuggerTabCell1"
+ currenttab);
a.style.borderBottom = 'gray 1px solid';
a = document.getElementById(
"debuggerTabCell1Objects");
a.style.borderBottom = '';
a = document.getElementById("innerTblTitle1")
a.innerText = 'Objects';
}catch(ex){}
currenttab = "Objects";
buildObjectResults(lastobject,null,null);
}
}

//------------------------------------------------------
//---------------------------------------------Title Bar
//------------------------------------------------------


function buildBar(){
var tr=document.createElement('tr');
tr.id = 'debuggerTitleRow1';
var td=document.createElement('td');

td.colSpan = '2';
td.style.filter = "progid:DXImageTransform.Microsoft." +
"Gradient(startColorStr='#3568CC', endColorStr='#98B2E6'" +
", gradientType='1')";
td.onmousedown = dragDropClick;
td.onmouseup = dragDropClick;
td.innerText = "Magic Debugger";
td.style.fontSize = '10pt';
td.style.color = 'white';
td.style.cursor = 'hand';
td.style.backgroundColor = '#FFFF99';
td.id = "debuggerTitleCell1";
tr.appendChild(td);

td=document.createElement('td');

var sss=document.createElement('span');
sss.innerText = 'p';
sss.style.fontFamily = 'Wingdings 3';
sss.onclick = dubuggerParentObject;
sss.style.fontSize = '10pt';
sss.style.cursor = 'hand';
sss.id = 'debuggerCloseSpan3';
td.appendChild(sss);

var ss=document.createElement('span');
ss.innerText = 'R';
ss.style.fontFamily = 'Wingdings 3';
ss.onclick = dubuggerLockObject;
ss.style.fontSize = '10pt';
ss.style.cursor = 'hand';
ss.id = 'debuggerCloseSpan2';
td.appendChild(ss);

var s=document.createElement('span');
s.innerText = 'V';
s.style.fontFamily = 'Wingdings 2';
s.onclick = dubuggerClose;
s.style.fontSize = '10pt';
s.style.cursor = 'hand';
s.id = 'debuggerCloseSpan1';
td.appendChild(s);

td.style.textAlign = 'right';
td.style.cursor = 'hand';
td.onmousedown = dragDropClick;
td.onmouseup = dragDropClick;
td.style.backgroundColor = '#98B2E6';
td.style.color = 'white';
td.id = 'debuggerCloseCell1';
tr.appendChild(td);
return tr;
}

function dubuggerClose(){
document.getElementById("debugOuterDiv1").style.display =
"none";
keyspressed = false;
}

function dubuggerLockObject(){
if (debugger_Lock_Current_object)
document.getElementById("debuggerCloseSpan2").innerText
= "R";
else
document.getElementById("debuggerCloseSpan2").innerText
= "Q";
debugger_Lock_Current_object = !debugger_Lock_Current_object;
}

function dubuggerParentObject(){
buildObjectResults(lastobject.parentNode, null, null);
lastobject = lastobject.parentNode;
}




//------------------------------------------------------
//---------------------------------------------Drag Drop
//------------------------------------------------------

function dragDropClick(){
var OuterDiv1 = document.getElementById("debugOuterDiv1");
offsetTopY =
lasty -
(new Number(new String(
OuterDiv1.style.top).replace('px',''))) - 10;
offsetLeftX = lastx - (new Number(new String(
OuterDiv1.style.left).replace('px',''))) - 10;
currentlyDragging = true;
}

function dragDropRelease(){
currentlyDragging = false;
}

//------------------------------------------------------
//---------------------------------------------TableSort
//------------------------------------------------------

var dom = (document.getElementsByTagName) ? true : false;
var ie5 = (document.getElementsByTagName && document.all) ? true :
false;

var bDate = false;
var sType = "s";
var tbody_array = new Array();

var arrowUp, arrowDown;
if (ie5 || dom)
initSortTable();

function objSortTable(objTable){
var ethead = getChild(objTable, "thead");
if (ethead == null) return null;
var etheadtr = getChild(ethead, "tr");
if (etheadtr == null) return null;
var etbody = getChild(objTable, "tbody")
if (etbody == null) return null;



this.eTable = objTable;
this.eTHead = ethead;
this.eTBody = etbody;
this.arrTBody = new Array();
this.arrIDIndex = new Array();

this.desc = true;

this.sort = sortRow;

for (var i=0; i<etbody.childNodes.length; i++) {
this.arrTBody.push(etbody.childNodes);
}

for (var i=0; i<etheadtr.childNodes.length; i++){
this.arrIDIndex[etheadtr.childNodes.id] = i;
}
}

function getChild(el, pTagName) {
if (el == null)
return null;
else if (el.nodeType == 1 && el.tagName.toLowerCase() ==
pTagName.toLowerCase())
return el;
else
for (var i=0; i<el.childNodes.length; i++) {
if (getChild(el.childNodes, pTagName) != null){
return getChild(el.childNodes, pTagName);
}
}
}


function sortRow(e2sort){
try{
var bDate =
(e2sort.id.toLowerCase().indexOf("date") >= 0)? 1:0;
var doreverse = false;

e2sort._descending =
(e2sort._descending) ? false : true;
if (this.eTHead.arrow != null) {
if (this.eTHead.arrow.parentNode != e2sort)
this.eTHead.arrow.parentNode._descending = null;
doreverse = (this.eTHead.arrow.parentNode ==
e2sort) ? true : false ;
this.eTHead.arrow.parentNode.removeChild(
this.eTHead.arrow);
}
this.eTHead.arrow = (e2sort._descending) ?
arrowDown.cloneNode(true) : arrowUp.cloneNode(true);
e2sort.appendChild(this.eTHead.arrow);

var eIndex = this.arrIDIndex[e2sort.id]
if (doreverse){
this.arrTBody.reverse();
}else{
var type_of_sort = (bDate == 1) ? 'dat' : null;
this.arrTBody.sort(compareByColumn(
this.arrIDIndex[e2sort.id], type_of_sort));
}

for (var i=0; i<this.arrTBody.length; i++) {
this.eTBody.appendChild(this.arrTBody);
}
}catch(ex){

}
}

function compareByColumn(nCol, type_of_sort) {
var c = nCol;

function _compare(n1, n2) {
var it1 = n1.cells[c].innerText;
var it2 = n2.cells[c].innerText;

if (it1 < it2){
return -1;
}else if (it1 > it2){
return +1;
}else{
return 0;
}
}

function _compare_date(n1, n2) {
var v;
var it1 = getChild(n1.cells[c], 'div').innerText;
var it2 = getChild(n2.cells[c], 'div').innerText;

if ((it1 != '') || (it1 != 'Jan 1 1900 12:00AM')){
if ((it2 != '') || (it1 != 'Jan 1 1900 12:00AM')){
if (new Date(it1) < new Date(it2)){
return -1;
}else if (new Date(it1) > new Date(it2)){
return +1;
}else{
return 0;
}
}else{
return -1;
}
}else{
return +1;
}
}

function _compare_num(n1, n2){
var it1 = parseInt(n1.cells[c].innerText);
var it2 = parseInt(n2.cells[c].innerText);

if (it1 < it2){
return -1;
}else if (it1 > it2){
return +1;
}else{
return 0;
}
}

if (type_of_sort == 'num'){
return _compare_num;
}else if (type_of_sort == 'dat'){
return _compare_date;
}else{
return _compare;
}
}

function remove_non_num(sNum){
var newnum;
for (var i=0; i<sNum.length;i++){
window.alert(char(sNum.charAt(i)));
}
}

function initSortTable() {
arrowUp = document.createElement("SPAN");
var tn = document.createTextNode("6");
arrowUp.appendChild(tn);
arrowUp.style.fontFamily = 'webdings'
arrowUp.style.fontColor = 'black';
arrowUp.style.padding = '0';
arrowUp.style.fontSize = '15pt';
arrowUp.style.width = '15px';
arrowUp.style.verticalAlign = 'bottom';
arrowUp.style.marginTop = '-10px';
arrowUp.style.marginBottom = '-4px';

arrowDown = document.createElement("SPAN");
var tn = document.createTextNode("5");
arrowDown.appendChild(tn);
arrowDown.className = "arrow";
arrowDown.appendChild(tn);
arrowDown.style.fontFamily = 'webdings'
arrowDown.style.fontColor = 'black';
arrowDown.style.padding = '0';
arrowDown.style.fontSize = '15pt';
arrowDown.style.width = '15px';
arrowDown.style.verticalAlign = 'bottom';
arrowDown.style.marginTop = '-10px';
arrowDown.style.marginBottom = '-4px';
}


function setDefaultArrows(srcEl, sDir){
try{
this.eTHead.arrow.parentNode._descending = null;
this.eTHead.arrow.parentNode.removeChild(
this.eTHead.arrow);
}catch(ex){
}
}
 
W

web.dev

Robert said:
I made this javascript to help me debug some javascript one
time and I have just been adding to it over the years. It
is IE specific (sure it would not be to hard to change that).
It is not ever meant to be rolled to a production environment.

What it does:
After you displaying the debug window (see tip 1) you can
move your mouse over any element on the page and the window
will populate with all the properties, events, and objects
(yea, I know they are all really properties) of that element.

Instructions:
Place the following line in your page you wish to debug:
<script language="JavaScript" src="/Debugger.js"></script>

The language attribute is deprecated, use the type attribute instead:

(this assumes your saved down the code to a file named
debugger.js in the root of your site)


Tips:
1. You can make the debug window appear by pressing "ctrl+shift+d"
2. There are three icons on the top they are as followed:
Parent object - Loads the parent object of the current object
Lock Object - Click to lock in the current object
Close - Closes the dom inspector window
3. You can press and hold CTRL and it will also lock the object in
3. You can click on a object listed in the object tag and
the debug window will populate with that objects data
4. You can click on the property name, or event name to goto
the msdn definition of that property or event.
5. You can click on the field headers to sort


You can freely use this with only one condition, credit goes
to me.
That mean if you tell your boss you wrote this, I will hunt
you down and feed your ears to my pet sloth.


PS: Please post any impovement to this thread. Thx


document.onmousemove = document_onmousemove;
document.onmouseup = dragDropRelease;
document.onkeypress = document_onkeypress;

Taking note that it shouldn't be used for production. What if I have a
script that also does something upon document.onmousemove, etc. ?
Depending on the order of which the script was placed, either your
function reference will overwrite mine, or my function reference will
overwrite yours. Since this is IE specific, either use the
attacheEvent() method or try to implement the attachEventListener()
method.
[snip]

function remove_non_num(sNum){
var newnum;
for (var i=0; i<sNum.length;i++){
window.alert(char(sNum.charAt(i)));
}
}

You have no function named char(), plus char is a reserved word.

Don't want to burst your bubble but the DOM Inspector that comes with
FireFox seems to be more helpful, IMO.
 
R

Robert Skidmore

yea, I don't know if this is just googles site doing that or what,
But just remove the "-" right after "\'script\"
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top