2 * Copyright 2007 Jesse Andrews and Manish Singh
4 * This file may be used under the terms of of the
5 * GNU General Public License Version 2 or later (the "GPL"),
6 * http://www.gnu.org/licenses/gpl.html
8 * Software distributed under the License is distributed on an "AS IS" basis,
9 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10 * for the specific language governing rights and limitations under the
14 const TB_CONTRACTID = '@oy/tablets;1';
15 const TB_CLASSID = Components.ID('{962a9516-b177-4083-bbe8-e10f47cf8570}');
16 const TB_CLASSNAME = 'Tablets Service';
19 const Cc = Components.classes;
20 const Ci = Components.interfaces;
21 const Cr = Components.results;
22 const Cu = Components.utils;
24 /* from nspr's prio.h */
25 const PR_RDONLY = 0x01;
26 const PR_WRONLY = 0x02;
28 const PR_CREATE_FILE = 0x08;
29 const PR_APPEND = 0x10;
30 const PR_TRUNCATE = 0x20;
35 function getObserverService() {
36 return Cc['@mozilla.org/observer-service;1']
37 .getService(Ci.nsIObserverService);
42 function hex_md5_stream(stream) {
43 var hasher = Components.classes["@mozilla.org/security/hash;1"]
44 .createInstance(Components.interfaces.nsICryptoHash);
45 hasher.init(hasher.MD5);
47 hasher.updateFromStream(stream, stream.available());
48 var hash = hasher.finish(false);
51 for (var i = 0; i < hash.length; ++i) {
52 var hexChar = hash.charCodeAt(i).toString(16);
53 if (hexChar.length == 1)
62 var stream = Components.classes["@mozilla.org/io/string-input-stream;1"]
63 .createInstance(Components.interfaces.nsIStringInputStream);
64 stream.setData(s, s.length);
66 return hex_md5_stream(stream);
71 * Tablet Info Instance
74 function TabletInfo() {
77 TabletInfo.prototype = {
84 QueryInterface: function(iid) {
85 if (!iid.equals(Ci.nsISupports) &&
86 !iid.equals(Ci.oyITabletInfo)) {
87 throw Cr.NS_ERROR_NO_INTERFACE;
94 * Tablets Service Component
99 var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
100 var win = wm.getMostRecentWindow('navigator:browser');
101 var content = win.content;
103 var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
105 var realW = content.innerWidth;
106 var realH = content.innerHeight;
108 var pW = 125.0/realW;
109 var pH = 125.0/realH;
120 canvas.setAttribute("width", Math.floor(w));
121 canvas.setAttribute("height", Math.floor(h));
123 var ctx = canvas.getContext("2d");
125 ctx.drawWindow(content, content.scrollX, content.scrollY, content.innerWidth, content.innerHeight, "rgb(0,0,0)");
127 return [win, canvas];
130 function TabletStorageFS() {
131 this._tabletsDir = Cc['@mozilla.org/file/directory_service;1']
132 .getService(Ci.nsIProperties).get('ProfD', Ci.nsILocalFile);
133 this._tabletsDir.append('tablets');
135 if (!this._tabletsDir.exists())
136 this._tabletsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
138 this._tabletsFile = this._tabletsDir.clone();
139 this._tabletsFile.append('tablets.js');
144 TabletStorageFS.prototype = {
145 save: function TSFS_save(url, data, preview) {
146 this._data[url] = data;
150 var file = this._getPreviewFile(url);
152 var ostream = Cc['@mozilla.org/network/file-output-stream;1']
153 .createInstance(Ci.nsIFileOutputStream);
154 ostream.init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, 0);
156 ostream.write(preview, preview.length);
161 delete: function TSFS_delete(url) {
163 var file = this._getPreviewFile(url);
168 delete this._data[url];
171 retrieve: function TSFS_retrieve(url) {
172 var file = this._getPreviewFile(url);
174 var ios = Cc['@mozilla.org/network/io-service;1']
175 .getService(Ci.nsIIOService);
176 var fileHandler = ios.getProtocolHandler('file')
177 .QueryInterface(Ci.nsIFileProtocolHandler);
178 var previewURL = fileHandler.getURLSpecFromFile(file);
180 return [this._data[url], previewURL];
182 getURLs: function TSFS_getURLs() {
184 for (var url in this._data)
188 _getPreviewFile: function TSFS__getPreviewFile(url) {
189 var id = hex_md5(url);
190 var file = this._tabletsDir.clone();
191 file.append(id + '.png');
194 _saveState: function TSFS__saveState() {
196 var file = this._tabletsFile;
198 var ostream = Cc['@mozilla.org/network/safe-file-output-stream;1']
199 .createInstance(Ci.nsIFileOutputStream);
200 ostream.init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, 0);
202 var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
203 .createInstance(Ci.nsIScriptableUnicodeConverter);
204 converter.charset = 'UTF-8';
206 var data = this._data.toSource();
207 var convdata = converter.ConvertFromUnicode(data) + converter.Finish();
209 ostream.write(convdata, convdata.length);
211 if (ostream instanceof Ci.nsISafeOutputStream) {
219 _loadState: function TSFS__loadState() {
221 var file = this._tabletsFile;
223 var stream = Cc['@mozilla.org/network/file-input-stream;1']
224 .createInstance(Ci.nsIFileInputStream);
225 stream.init(file, PR_RDONLY, 0, 0);
227 var cvstream = Cc['@mozilla.org/intl/converter-input-stream;1']
228 .createInstance(Ci.nsIConverterInputStream);
229 cvstream.init(stream, 'UTF-8', 1024,
230 Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
234 while (cvstream.readString(4096, data)) {
235 content += data.value;
240 content = content.replace(/\r\n?/g, '\n');
242 var sandbox = new Cu.Sandbox('about:blank');
243 this._data = Cu.evalInSandbox(content, sandbox);
252 function TabletsService() {
253 var obs = getObserverService();
254 obs.addObserver(this, 'profile-after-change', false);
257 TabletsService.prototype = {
258 _init: function TB__init() {
259 this._storage = new TabletStorageFS();
261 observe: function TB_observe(subject, topic, state) {
262 var obs = getObserverService();
265 case 'profile-after-change':
266 obs.removeObserver(this, 'profile-after-change');
272 save: function TB_save(aDescription) {
274 [win, canvas] = snapshot();
276 var tabbrowser = win.getBrowser();
277 var selectedBrowser = tabbrowser.selectedBrowser;
280 var browsers = tabbrowser.browsers;
281 for (var i = 0; i < browsers.length; i++) {
282 if (browsers[i] == selectedBrowser)
286 if (currentTab == -1)
289 var ss = Cc['@mozilla.org/browser/sessionstore;1']
290 .getService(Ci.nsISessionStore);
291 var winJSON = "(" + ss.getWindowState(win) + ")";
293 var sandbox = new Cu.Sandbox('about:blank');
294 var winState = Cu.evalInSandbox(winJSON, sandbox);
296 var state = winState.windows[0].tabs[currentTab];
298 var previewData = canvas.toDataURL();
299 var preview = win.atob(previewData.substr('data:image/png;base64,'.length));
301 var url = selectedBrowser.currentURI.spec;
303 this._storage.save(url, state, preview);
307 delete: function TB_delete(aURL) {
308 this._storage.delete(aURL);
310 open: function TB_open(aURL, aWhere) {
312 getTablets: function TB_getTablets() {
313 var urls = this._storage.getURLs();
317 _storage: this._storage,
318 getNext: function() {
319 var url = this._urls.shift();
322 [data, imageURL] = this._storage.retrieve(url);
324 var tab = new TabletInfo();
326 tab.title = data.entries[data.index - 1].title;
327 tab.imageURL = imageURL;
330 hasMoreElements: function() {
331 return this._urls.length > 0;
338 getInterfaces: function TB_getInterfaces(countRef) {
339 var interfaces = [Ci.oyITablets, Ci.nsIObserver, Ci.nsISupports];
340 countRef.value = interfaces.length;
343 getHelperForLanguage: function TB_getHelperForLanguage(language) {
346 contractID: TB_CONTRACTID,
347 classDescription: TB_CLASSNAME,
349 implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
350 flags: Ci.nsIClassInfo.SINGLETON,
352 QueryInterface: function TB_QueryInterface(iid) {
353 if (iid.equals(Ci.oyITablets) ||
354 iid.equals(Ci.nsIObserver) ||
355 iid.equals(Ci.nsISupports))
357 throw Cr.NS_ERROR_NO_INTERFACE;
361 function GenericComponentFactory(ctor) {
365 GenericComponentFactory.prototype = {
370 createInstance: function(outer, iid) {
372 throw Cr.NS_ERROR_NO_AGGREGATION;
373 return (new this._ctor()).QueryInterface(iid);
377 QueryInterface: function(iid) {
378 if (iid.equals(Ci.nsIFactory) ||
379 iid.equals(Ci.nsISupports))
381 throw Cr.NS_ERROR_NO_INTERFACE;
386 QueryInterface: function(iid) {
387 if (iid.equals(Ci.nsIModule) ||
388 iid.equals(Ci.nsISupports))
391 throw Cr.NS_ERROR_NO_INTERFACE;
394 getClassObject: function(cm, cid, iid) {
395 if (!iid.equals(Ci.nsIFactory))
396 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
398 if (cid.equals(TB_CLASSID))
399 return new GenericComponentFactory(TabletsService)
401 throw Cr.NS_ERROR_NO_INTERFACE;
404 registerSelf: function(cm, file, location, type) {
405 var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
406 cr.registerFactoryLocation(TB_CLASSID, TB_CLASSNAME, TB_CONTRACTID,
407 file, location, type);
409 var catman = Cc['@mozilla.org/categorymanager;1']
410 .getService(Ci.nsICategoryManager);
411 catman.addCategoryEntry('app-startup', TB_CLASSNAME,
412 'service,' + TB_CONTRACTID,
416 unregisterSelf: function(cm, location, type) {
417 var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
418 cr.unregisterFactoryLocation(TB_CLASSID, location);
421 canUnload: function(cm) {
426 function NSGetModule(compMgr, fileSpec)