Friday, June 17, 2011

Closed windows from a crashed Firefox: Dig [out of] your own grave and save!

Well, I don't know what happened. The Windows box went down for a reboot due to the usual bundle of patches for Windows bugs. Somewhere along the line, Firefox managed to close the window with all my (way too many) tabs, and when I rebooted, it opened up on the Mozilla homepage. Burn.

No problem, going to about:sessionrestore displays a list of... empty. Oh.

Ok then. A quick look in the profile dir (%APPDATA%\Mozilla\Firefox\Profiles\*.default) shows that the sessionstore.js and backup are over 4 megs in size - that looks promising. So I copy them and quit Firefox.
Inside sessionstore.js is a gigantic JSON string. Inside that, I find a closed tab (the session restore tab which I had given up on), with apparently another full JSON string in the #sessionData field. After decoding that, I find an empty list of windows, and a list of _closedWindows including my main window with many many tabs. Yay, I can just swap them then.

Here's a little program in Lua which might help resurrect your tabs/windows if something similar happens to you. It uses the very fast dkjson library (the whole read/decode/futz/encode/write process for a 4.5mb file takes about 1.25 seconds on my run-of-the-mill desktop using LuaJIT).

In this case, there was an open window with one closed tab containing an about:sessionrestore form...
  json = require('dkjson')

  print('Reading file...')
  local session = assert(io.open('sessionstore.js', 'r'))
  local session_data = session:read('*all')
  print('Parsing ('..#session_data..' bytes)...')
  local obj, pos, err = json.decode(session_data, 1, json.null)
  if err then error(err) end
  print('Success! table size: '..#obj, pos, err)

  -- I closed the session restore tab since the list was empty.
  -- Turns out it still had the closed window data, so
  -- we can extract that. You may have to change this.
  local data = obj.windows[1]._closedTabs[1].state.
      entries[1].formdata['#sessionData']

  -- parse it, swap open/closed windows
  local data_p = json.decode(data, 1, json.null)
  print('Decoded, swapping closed and open windows')
  local old_windows = data_p.windows
  data_p.windows = data_p._closedWindows
  data_p._closedWindows = old_windows

  print('Encoding')
  local outf = assert(io.open('sessionstore-rec-fixed.js', 'w'))
  outf:write(json.encode(data_p))
  outf:close()

In this case, the open windows had suddenly become closed windows... (this just happened now, the hell Aurora?)
  json = require('dkjson')
  print('Reading file...')
  local session = assert(io.open('sessionstore.js', 'r'))
  local session_data = session:read('*all')
  print('Parsing ('..#session_data..' bytes)...')
  local obj, pos, err = json.decode(session_data, 1, json.null)
  if err then error(err) end

  print('Success! table size: '..#obj, pos, err)
  print('Decoded, swapping closed and open windows')
  local old_windows = obj.windows
  obj.windows = obj._closedWindows
  obj._closedWindows = old_windows

  print('Encoding')
  local outf = assert(io.open('sessionstore-rec-fixed.js', 'w'))
  outf:write(json.encode(obj))
  outf:close()