When quit and restarted Firefox today I received an unwelcomed shock. All my tab groups, which I maintained using the Tab Groups by Quicksaver plugin, were gone! This happened because it upgraded to Firefox Quantum (57), whose API does not maintain backward compatibility with the one used by the plugin. Although I knew the plugin would one day stop working, I thought there would be some last-minute warning and chance to export the tab groups.
Frighteningly, I couldn’t find a way to obtain the list of the pages I had opened in my tab groups and a web search didn’t yield any useful ideas. Currently there is no plugin with similar functionality, let alone one that can import the Tab Group tabs. Installing an older Firefox version seemed an inelegant hassle, so I decided to recover the tab groups from the browser’s saved session data. This is stored in a file named sessionstore.js
, located in the Firefox profile folder. The file is updated every time you restart Firefox. Consequently, the method I’ll outline works if you haven’t restarted the new version, or if you have an old version of the file available on backup. (For me both were true.)
The file is stored in JSON format. As a first step I formatted it for human consumption with the following command.
python -m json.tool sessionstore.js
I then explored it by opening it in vim, changing the file type to obtain proper syntax coloring (set filetype=javascript
), and navigating its structure with the [{
command. I then used the amazing jq tool and a few Unix utilities to extract the data I wanted into an HTML page.
I got the titles of the lost tab groups, which are stored in a extData.tabview-group
entry, with the following command.
jq -j '.windows[0].extData."tabview-group" |
fromjson |
.[] |
.id, "\t", .title, "\n"' sessionstore.js
This obtains from the JSON file the first entry of the windows
array, and from it the extData.tabview-group
entry. The fromjson
operator is required, because the data is stored in JSON format as a string, and needs to be converted to JSON again for further processing. I thus got a list similar to the following.
3 READ
5 MSR Ethics
6 Main
7 dgsh
8 IEEE Software
9 Leadership
10 Bootcamp
11 MOOC
13 LI vs hacking
14 Software in science
19 SWEng
20 eGov
I then extract the titles and URLs of the tabs in each group with the following command.
jq -j '.windows[0].tabs[] |
( .extData."tabview-tab" | fromjson| .groupID), "\t",
.entries[-1].title, "\t",
.entries[-1].url, "\n"' sessionstore.js
This gave me another list, similar to this.
19 SWeng students - Google Sheets https://docs.google.com/spreadsheets/...
3 Why the internet of things ... | Technology | The Guardian https://www.theguardian.com/...
10 Testing Flask Applications http://flask.pocoo.org/docs/0.11/testing/
I then sorted the two and joined them with the Unix join command to produce a list of tabs and their group titles.
join -t$'\t' <(
jq -j '.windows[0].extData."tabview-group" |
fromjson |
.[] |
.id, "\t", .title, "\n"' sessionstore.js | sort) <(
jq -j '.windows[0].tabs[] |
( .extData."tabview-tab" | fromjson| .groupID), "\t",
.entries[-1].title, "\t",
.entries[-1].url, "\n"' sessionstore.js | sort)
Although it is possible to perform a relation join within jq, I decided to use the Unix join command, because this approach was quicker and easier to debug.
Finally, I used awk to convert the result into an HTML file, which I could open in my browser.
join -t$'\t' <(
jq -j '.windows[0].extData."tabview-group" |
fromjson |
.[] |
.id, "\t", .title, "\n"' sessionstore.js | sort) <(
jq -j '.windows[0].tabs[] |
( .extData."tabview-tab" | fromjson| .groupID), "\t",
.entries[-1].title, "\t",
.entries[-1].url, "\n"' sessionstore.js | sort) |
awk -F$'\t' '
BEGIN {
print "<html<head>"
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">"
print "</head><body>\n"
}
$2 != title {
title = $2
print "<h1>" title "</h1>\n"
}
{ print "<a href=\"" $4 "\">" $3 "</a><br>\n"}
END { print "</body>\n" }' >tab-groups.html
Last modified: Saturday, November 18, 2017 10:04 am
Unless otherwise expressly stated, all original material on this page created by Diomidis Spinellis is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.