From 85c315f68e2d0325b8c0366cce84f4cbdf09282a Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Tue, 5 May 2015 14:44:35 -0700 Subject: [PATCH] Make about:tabs useful for cleaning up large numbers of tabs --- abouttabs.js | 173 ++++++++++++++++++++++++++++++++++++++++++++++----- install.rdf | 2 +- 2 files changed, 157 insertions(+), 18 deletions(-) diff --git a/abouttabs.js b/abouttabs.js index a5f6a46..67022b6 100644 --- a/abouttabs.js +++ b/abouttabs.js @@ -4,6 +4,10 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); +function is_loaded(tab) { + return !"__SS_restoreState" in tab.linkedBrowser || tab.linkedBrowser.__SS_restoreState != 1; +} + function close(dict, key, keep_one) { if (key === undefined) { for (key in dict) @@ -36,13 +40,112 @@ function create_close_link(dict, key, keep_one, label) { var a = document.createElement("a"); a.href = '#'; a.onclick = function () { - close(dict, key, keep_one); + if (a.parentNode.closeSelectedOnly) { + for (var check of a.parentNode.getElementsByTagName("input")) { + if (check.checked) { + var [tab, browser] = check.tab; + browser.removeTab(tab); + } + } + } else if (dict) { + close(dict, key, keep_one); + } refresh(); - } + }; + var span = document.createElement("span"); + span.className = "closelink"; + span.appendChild(document.createTextNode(label)); + a.appendChild(span); + return a; +} + +function create_close_checkbox(tab) { + var check = document.createElement("input"); + check.type = "checkbox"; + check.tab = tab; + return check; +} + +function create_tabs_link(uris) { + var label = uris.length + " tabs"; + + var a = document.createElement("a"); + a.href = '#'; + a.onclick_open = function () { + var list = document.createElement("ul"); + list.className = "tablist"; + for (var [t, b] of uris) { + var uri = t.linkedBrowser.currentURI; + var li = document.createElement("li"); + var tablink = document.createElement("a"); + tablink.href = "#"; + tablink.title = "Switch to tab"; + tablink.onclick = (function (t,b) { return function () { b.selectedTab = t } })(t, b); + tablink.appendChild(document.createTextNode(uri.spec)); + li.appendChild(create_close_checkbox([t, b])); + li.appendChild(document.createTextNode(" ")); + li.appendChild(tablink); + li.appendChild(document.createTextNode(" - ")); + if (is_loaded(t)) { + li.appendChild(document.createTextNode(t.label)); + } else { + var em = document.createElement("em"); + em.appendChild(document.createTextNode(t.label)); + li.appendChild(em); + } + list.appendChild(li); + } + a.parentNode.appendChild(list); + a.parentNode.closeSelectedOnly = true; + var closespan = a.parentNode.getElementsByClassName("closelink")[0]; + closespan.textContent = "[Close Selected]"; + a.onclick = a.onclick_close; + }; + a.onclick_close = function () { + for (var deadmeat of document.getElementsByClassName("tablist")) { + deadmeat.remove(); + } + a.parentNode.closeSelectedOnly = false; + var closespan = a.parentNode.getElementsByClassName("closelink")[0]; + closespan.textContent = "[Close]"; + a.onclick = a.onclick_open; + }; + a.onclick = a.onclick_open; a.appendChild(document.createTextNode(label)); return a; } +function plural(n, noun) { + noun = noun.replace(/__/g, " "); + if (n == 1) + return noun; + if (noun.endsWith("s")) + return noun + "es"; + if (noun.endsWith("sh")) + return noun; + if (noun.endsWith("ch")) + return noun + "es"; + if (noun.endsWith("x")) + return noun + "es"; + return noun + "s"; +} + +// en`${n} dog ${k} horse` => "2 dogs 1 horse" +// en`${n} happy__dog` => "4 happy dogs" +// en`${n} dog ${n} fleas` => "1 dog has fleas" +function en(strings, ...values) { + var s = strings[0]; + for (var i = 0; i < values.length; i++) { + var n = values[i]; + var fragment = strings[i+1]; + if (fragment.charAt(0) == '<') + s += fragment.replace(/<(.*?)\|(.*)>/, (_, a, b) => (n == 1) ? a : b); + else + s += n + fragment.replace(/(\w+)/, (_, word) => plural(n, word)); + } + return s; +} + function refresh() { var ul = document.getElementById("stats"); while (ul.firstChild) @@ -70,9 +173,11 @@ function refresh() { } } var li = document.createElement("li"); - li.appendChild(document.createTextNode(tabs.length + " tab"+ (tabs.length > 1 ? "s" : "") + " across " + tabGroupsCount + " group" + (tabGroupsCount > 1 ? "s" : "") + " in " + windowsCount + " window" + (windowsCount > 1 ? "s" : ""))); + li.appendChild(document.createTextNode(en`${tabs.length} tab across ${tabGroupsCount} group in ${windowsCount} window`)); ul.appendChild(li); + var loaded_only = document.location.href.indexOf("loaded=1") != -1; + var uris = {}; var hosts = {}; var urihosts = {}; @@ -85,8 +190,10 @@ function refresh() { blankTabs++ return; } - if (!"__SS_restoreState" in tab.linkedBrowser || tab.linkedBrowser.__SS_restoreState != 1) + if (is_loaded(tab)) loadedTabs++; + else if (loaded_only) + return; tab = [tab, win.gBrowser]; if (uri.spec in uris) uris[uri.spec].push(tab); @@ -119,33 +226,38 @@ function refresh() { uniqueHosts++; li = document.createElement("li"); - li.appendChild(document.createTextNode(loadedTabs + " tab" + (loadedTabs > 1 ? "s" : "") + " ha" + (loadedTabs > 1 ? "ve" : "s") + " been loaded")); + li.appendChild(document.createTextNode(en`${loadedTabs} tab ${loadedTabs} been loaded` + (loaded_only ? " (showing only info for these tabs)" : ""))); ul.appendChild(li); li = document.createElement("li"); - li.appendChild(document.createTextNode(uniqueUris + " unique address" + (uniqueUris > 1 ? "es" : ""))); + li.appendChild(document.createTextNode(en`${uniqueUris} unique__address`)); ul.appendChild(li); li = document.createElement("li"); - li.appendChild(document.createTextNode(uniqueHosts + " unique host" + (uniqueHosts > 1 ? "s" : ""))); + li.appendChild(document.createTextNode(en`${uniqueHosts} unique__host`)); ul.appendChild(li); if (blankTabs) { li = document.createElement("li"); - li.appendChild(document.createTextNode(blankTabs + " empty tab" + (blankTabs > 1 ? "s" : ""))); + li.appendChild(document.createTextNode(en`${blankTabs} empty__tab`)); ul.appendChild(li); } + li = document.createElement("li"); + li.appendChild(document.createTextNode(en`${Object.keys(schemes).length} URI__scheme`)); + var sub_ul = document.createElement("ul"); for (key in schemes) { - li = document.createElement("li"); - li.appendChild(document.createTextNode(schemes[key]+ " " + key + ":")); - ul.appendChild(li); + var sub_li = document.createElement("li"); + sub_li.appendChild(document.createTextNode(schemes[key]+ " " + key + ":")); + sub_ul.appendChild(sub_li); } + li.appendChild(sub_ul); + ul.appendChild(li); var uris_ = [uri for (uri in uris) if (uris[uri].length > 1)]; if (uris_.length) { li = document.createElement("li"); - li.appendChild(document.createTextNode(uris_.length + " address" + (uris_.length > 1 ? "es" : "") + " in more than 1 tab: ")); + li.appendChild(document.createTextNode(en`${uris_.length} address` + " in more than 1 tab: ")); li.appendChild(create_close_link(uris, undefined, true, "[Dedup]")); li.appendChild(document.createTextNode(" ")); li.appendChild(create_close_link(uris, undefined, false, "[Close]")); @@ -156,10 +268,14 @@ function refresh() { return 1; if (uris[a].length > uris[b].length) return -1; - return 0; + if (uris[a] == uris[b]) + return 0; + return uris[a] < uris[b] ? -1 : 1; }).forEach(function(uri) { sub_li = document.createElement("li"); - sub_li.appendChild(document.createTextNode(uri+" ("+uris[uri].length + " tabs) ")); + sub_li.appendChild(document.createTextNode(uri+" (")); + sub_li.appendChild(create_tabs_link(uris[uri])); + sub_li.appendChild(document.createTextNode(") ")); sub_li.appendChild(create_close_link(uris, uri, true, "[Dedup]")); sub_li.appendChild(document.createTextNode(" ")); sub_li.appendChild(create_close_link(uris, uri, false, "[Close]")); @@ -169,10 +285,17 @@ function refresh() { ul.appendChild(li); } - var hosts_ = [host for (host in hosts) if (hosts[host].length > 1)]; + var hosts_ = [], singles = []; + for (var host in hosts) { + if (hosts[host].length == 1) + singles.push(hosts[host][0]); + else + hosts_.push(host); + } + if (hosts_.length) { li = document.createElement("li"); - li.appendChild(document.createTextNode(hosts_.length + " host" + (hosts_.length > 1 ? "s" : "") + " in more than 1 tab:")); + li.appendChild(document.createTextNode(en`${hosts_.length} host in more than 1 tab:`)); var sub_ul = document.createElement("ul"); var sub_li; hosts_.sort(function cmp(a, b) { @@ -183,7 +306,9 @@ function refresh() { return 0; }).forEach(function(host) { sub_li = document.createElement("li"); - var text = host+" (" + hosts[host].length + " tabs"; + sub_li.appendChild(document.createTextNode(host + " (")); + sub_li.appendChild(create_tabs_link(hosts[host])); + var text = ""; var keys = Object.keys(urihosts[host]); if (keys.length < hosts[host].length) text += ", " + keys.length + " unique"; @@ -192,8 +317,22 @@ function refresh() { sub_li.appendChild(create_close_link(hosts, host, false, "[Close]")); sub_ul.appendChild(sub_li); }); + li.appendChild(sub_ul); ul.appendChild(li); + + if (singles.length > 0) { + li = document.createElement("li"); + li.appendChild(document.createTextNode("Hosts in a single tab (")); + li.appendChild(create_tabs_link(singles.sort((a, b) => { + var [ta, ba] = a; + var [tb, bb] = b; + return ta._lastAccessed - tb._lastAccessed; + }))); + li.appendChild(document.createTextNode(") ")); + li.appendChild(create_close_link(undefined, undefined, false, "[Close]")); + ul.appendChild(li); + } } } diff --git a/install.rdf b/install.rdf index b280ba0..9c94f9d 100644 --- a/install.rdf +++ b/install.rdf @@ -1,7 +1,7 @@ tabstats@glandium.org - 0.0.5 + 0.0.6 2 true