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
13 * Portions are derived from the Mozilla nsSessionStore component:
15 * Copyright (C) 2006 Simon Bünzli <zeniko@gmail.com>
18 * Dietrich Ayala <autonome@gmail.com>
20 * Other portions derived from Firefox bookmarks code.
22 * Copyright (C) 1998 Netscape Communications Corporation.
25 * Ben Goodger <ben@netscape.com> (Original Author)
26 * Joey Minta <jminta@gmail.com>
28 * Other portions derived from Flock favorites code.
30 * Copyright (C) 2005-2007 Flock Inc.
33 const TB_CONTRACTID
= '@oy/taboo;1';
34 const TB_CLASSID
= Components
.ID('{962a9516-b177-4083-bbe8-e10f47cf8570}');
35 const TB_CLASSNAME
= 'Taboo Service';
38 const Cc
= Components
.classes
;
39 const Ci
= Components
.interfaces
;
40 const Cr
= Components
.results
;
41 const Cu
= Components
.utils
;
43 /* from nspr's prio.h */
44 const PR_RDONLY
= 0x01;
45 const PR_WRONLY
= 0x02;
47 const PR_CREATE_FILE
= 0x08;
48 const PR_APPEND
= 0x10;
49 const PR_TRUNCATE
= 0x20;
53 const CAPABILITIES
= [
54 "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images"
57 const IMAGE_FULL_WIDTH
= 500;
58 const IMAGE_FULL_HEIGHT
= 500;
60 const IMAGE_THUMB_WIDTH
= 125;
61 const IMAGE_THUMB_HEIGHT
= 125;
64 function getObserverService() {
65 return Cc
['@mozilla.org/observer-service;1']
66 .getService(Ci
.nsIObserverService
);
69 function getBoolPref(prefName
, defaultValue
) {
71 var prefs
= Cc
['@mozilla.org/preferences-service;1']
72 .getService(Ci
.nsIPrefBranch
);
73 return prefs
.getBoolPref(prefName
);
82 function hex_md5_stream(stream
) {
83 var hasher
= Components
.classes
["@mozilla.org/security/hash;1"]
84 .createInstance(Components
.interfaces
.nsICryptoHash
);
85 hasher
.init(hasher
.MD5
);
87 hasher
.updateFromStream(stream
, stream
.available());
88 var hash
= hasher
.finish(false);
91 for (var i
= 0; i
< hash
.length
; ++i
) {
92 var hexChar
= hash
.charCodeAt(i
).toString(16);
93 if (hexChar
.length
== 1)
101 function hex_md5(s
) {
102 var stream
= Components
.classes
["@mozilla.org/io/string-input-stream;1"]
103 .createInstance(Components
.interfaces
.nsIStringInputStream
);
104 stream
.setData(s
, s
.length
);
106 return hex_md5_stream(stream
);
111 * Taboo Info Instance
114 function TabooInfo(url
, title
, description
, favicon
, imageURL
, thumbURL
,
115 created
, updated
, data
) {
118 this.description
= description
;
119 this.favicon
= favicon
;
120 this.imageURL
= imageURL
;
121 this.thumbURL
= thumbURL
;
122 this.created
= new Date(created
);
123 this.updated
= new Date(updated
);
127 TabooInfo
.prototype = {
128 QueryInterface: function(iid
) {
129 if (!iid
.equals(Ci
.nsISupports
) &&
130 !iid
.equals(Ci
.oyITabooInfo
)) {
131 throw Cr
.NS_ERROR_NO_INTERFACE
;
138 * Taboo Service Component
142 function snapshot(win
, outputWidth
, outputHeight
) {
143 var content
= win
.content
;
145 var canvas
= win
.document
.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
147 var realW
= content
.document
.body
? content
.document
.body
.clientWidth
148 : content
.innerWidth
;
149 var realH
= content
.innerHeight
;
151 var pW
= outputWidth
* 1.0 / realW
;
152 var pH
= outputHeight
* 1.0 / realH
;
163 canvas
.setAttribute("width", Math
.floor(w
));
164 canvas
.setAttribute("height", Math
.floor(h
));
166 var ctx
= canvas
.getContext("2d");
168 ctx
.drawWindow(content
, content
.scrollX
, content
.scrollY
, realW
, realH
, "rgb(0,0,0)");
170 var imageData
= canvas
.toDataURL();
171 return win
.atob(imageData
.substr('data:image/png;base64,'.length
));
175 function TabooStorageSQL() {
176 this._tabooDir
= Cc
['@mozilla.org/file/directory_service;1']
177 .getService(Ci
.nsIProperties
).get('ProfD', Ci
.nsILocalFile
);
178 this._tabooDir
.append('taboo');
180 if (!this._tabooDir
.exists())
181 this._tabooDir
.create(Ci
.nsIFile
.DIRECTORY_TYPE
, 0700);
183 var dbfile
= this._tabooDir
.clone();
184 dbfile
.append('taboo.sqlite');
186 var DB
= loadSubScript('chrome://taboo/content/sqlite.js').DB
;
187 this._db
= new DB(dbfile
);
189 this._db
.Table('taboo_data', {
190 url
: 'TEXT PRIMARY KEY',
192 description
: 'TEXT',
201 this._store
= this._db
.taboo_data
;
204 TabooStorageSQL
.prototype = {
205 save
: function TSSQL_save(url
, description
, data
, fullImage
, thumbImage
) {
206 var title
= data
.entries
[data
.index
- 1].title
;
209 var ios
= Cc
['@mozilla.org/network/io-service;1']
210 .getService(Ci
.nsIIOService
);
211 var uri
= ios
.newURI(url
, null, null);
213 if (uri
.path
.length
> 1) {
214 var parts
= uri
.path
.split('/');
215 while (!title
&& parts
.length
)
223 var updated
= Date
.now();
225 var entry
= this._store
.find(url
);
228 entry
= this._store
.new();
230 entry
.md5
= hex_md5(url
);
231 entry
.created
= updated
;
235 entry
.description
= description
;
239 entry
.updated
= updated
;
240 entry
.deleted
= null;
241 entry
.full
= data
.toSource();
245 this._saveImage(fullImage
, this._getImageFile(entry
.md5
));
246 this._saveImage(thumbImage
, this._getThumbFile(entry
.md5
));
248 saveFavicon
: function TSSQL_saveFavicon(url
, favicon
) {
249 var entry
= this._store
.find(url
);
251 entry
.favicon
= favicon
;
255 exists
: function TSSQL_exists(url
) {
256 return Boolean(this._store
.find(url
));
258 delete: function TSSQL_delete(url
) {
259 this._deleteOp(url
, Date
.now());
261 undelete
: function TSSQL_undelete(url
) {
262 this._deleteOp(url
, null);
264 reallyDelete
: function TSSQL_reallyDelete(url
) {
265 var entry
= this._store
.find(url
);
271 var file
, md5
= hex_md5(url
);
273 file
= this._getImageFile(md5
);
276 file
= this._getThumbFile(md5
);
281 retrieve
: function TSSQL_retrieve(url
) {
282 var entry
= this._store
.find(url
);
286 var ios
= Cc
['@mozilla.org/network/io-service;1']
287 .getService(Ci
.nsIIOService
);
288 var fileHandler
= ios
.getProtocolHandler('file')
289 .QueryInterface(Ci
.nsIFileProtocolHandler
);
291 var imageFile
= this._getImageFile(entry
.md5
);
292 var imageURL
= fileHandler
.getURLSpecFromFile(imageFile
);
294 var thumbFile
= this._getThumbFile(entry
.md5
);
296 if (thumbFile
.exists()) {
297 thumbURL
= fileHandler
.getURLSpecFromFile(thumbFile
);
302 var data
= entry
.full
.replace(/\r\n?/g, '\n');
303 var sandbox
= new Cu
.Sandbox('about:blank');
304 var state
= Cu
.evalInSandbox(data
, sandbox
);
306 return new TabooInfo(url
, entry
.title
, entry
.description
, entry
.favicon
,
307 imageURL
, thumbURL
, entry
.created
, entry
.updated
,
310 getURLs
: function TSSQL_getURLs(filter
, deleted
) {
313 var sortkey
, sql
= '';
316 sql
+= '(url LIKE ?1 or title LIKE ?1 or description LIKE ?1) and ';
317 // TODO: escape %'s before passing in
318 condition
.push('%' + filter
+ '%');
322 sql
+= 'deleted IS NOT NULL';
323 sortkey
= 'deleted DESC';
325 sql
+= 'deleted IS NULL';
326 sortkey
= 'updated DESC';
329 condition
.unshift(sql
);
331 var results
= this._store
.find(condition
, sortkey
);
332 return results
.map(function(entry
) { return entry
.url
});
334 _getImageFile
: function TSSQL__getImageFile(id
) {
335 var file
= this._tabooDir
.clone();
336 file
.append(id
+ '.png');
339 _getThumbFile
: function TSSQL__getPreviewFile(id
) {
340 var file
= this._tabooDir
.clone();
341 file
.append(id
+ '-' + IMAGE_THUMB_WIDTH
+ '.png');
344 _saveImage
: function TSSQL__saveImage(imageData
, file
) {
346 var ostream
= Cc
['@mozilla.org/network/file-output-stream;1']
347 .createInstance(Ci
.nsIFileOutputStream
);
348 ostream
.init(file
, PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
, 0600, 0);
350 ostream
.write(imageData
, imageData
.length
);
355 _deleteOp
: function TSSQL__deleteOp(url
, deleted
) {
356 var entry
= this._store
.find(url
);
358 entry
.deleted
= deleted
;
365 function TabooService() {
366 var obs
= getObserverService();
367 obs
.addObserver(this, 'profile-after-change', false);
370 TabooService
.prototype = {
371 _init
: function TB__init() {
372 this._storage
= new TabooStorageSQL();
374 observe
: function TB_observe(subject
, topic
, state
) {
375 var obs
= getObserverService();
378 case 'profile-after-change':
379 obs
.removeObserver(this, 'profile-after-change');
385 save
: function TB_save(aDescription
) {
386 var wm
= Cc
["@mozilla.org/appshell/window-mediator;1"]
387 .getService(Ci
.nsIWindowMediator
);
388 var win
= wm
.getMostRecentWindow('navigator:browser');
390 var tabbrowser
= win
.getBrowser();
391 var selectedBrowser
= tabbrowser
.selectedBrowser
;
392 var selectedTab
= tabbrowser
.selectedTab
;
395 var browsers
= tabbrowser
.browsers
;
396 for (var i
= 0; i
< browsers
.length
; i
++) {
397 if (browsers
[i
] == selectedBrowser
)
401 if (currentTab
== -1)
404 var ss
= Cc
['@mozilla.org/browser/sessionstore;1']
405 .getService(Ci
.nsISessionStore
);
406 var winJSON
= "(" + ss
.getWindowState(win
) + ")";
408 if (getBoolPref('extensions.taboo.debug', false))
409 dump(winJSON
+ "\n");
411 var sandbox
= new Cu
.Sandbox('about:blank');
412 var winState
= Cu
.evalInSandbox(winJSON
, sandbox
);
414 var state
= winState
.windows
[0].tabs
[currentTab
];
416 var url
= state
.entries
[state
.index
- 1].url
;
417 url
= url
.replace(/#.*$/, '');
419 var fullImage
= snapshot(win
, IMAGE_FULL_WIDTH
, IMAGE_FULL_HEIGHT
);
420 var thumbImage
= snapshot(win
, IMAGE_THUMB_WIDTH
, IMAGE_THUMB_HEIGHT
);
422 this._storage
.save(url
, aDescription
, state
, fullImage
, thumbImage
);
424 var faviconURL
= selectedTab
.getAttribute('image');
426 var ios
= Cc
['@mozilla.org/network/io-service;1']
427 .getService(Ci
.nsIIOService
);
428 var chan
= ios
.newChannel(faviconURL
, null, null);
429 var listener
= new tabooFavIconLoadListener(url
, faviconURL
, chan
,
431 chan
.notificationCallbacks
= listener
;
432 chan
.asyncOpen(listener
, null);
437 isSaved
: function TB_isSaved(aURL
) {
438 return this._storage
.exists(aURL
);
440 delete: function TB_delete(aURL
) {
441 this._storage
.delete(aURL
);
443 undelete
: function TB_undelete(aURL
) {
444 this._storage
.undelete(aURL
);
446 reallyDelete
: function TB_reallyDelete(aURL
) {
447 this._storage
.reallyDelete(aURL
);
449 get: function TB_get(filter
, deleted
) {
450 var urls
= this._storage
.getURLs(filter
, deleted
);
454 _storage
: this._storage
,
455 getNext: function() {
456 var url
= this._urls
.shift();
457 return this._storage
.retrieve(url
);
459 hasMoreElements: function() {
460 return this._urls
.length
> 0;
467 /* Because sessionstore doesn't let us restore a single tab, we cut'n'paste
468 * a bunch of code here
470 open
: function TB_open(aURL
, aWhere
) {
471 var info
= this._storage
.retrieve(aURL
);
472 var tabData
= info
.data
;
474 // helper hash for ensuring unique frame IDs
475 var idMap
= { used
: {} };
477 var wm
= Cc
['@mozilla.org/appshell/window-mediator;1']
478 .getService(Ci
.nsIWindowMediator
);
479 var win
= wm
.getMostRecentWindow('navigator:browser');
481 var loadInBackground
= getBoolPref("browser.tabs.loadBookmarksInBackground", false);
483 var tabbrowser
= win
.getBrowser();
488 tab
= tabbrowser
.mCurrentTab
;
491 loadInBackground
= !loadInBackground
;
494 tab
= tabbrowser
.loadOneTab('about:blank', null, null, null,
495 loadInBackground
, false);
503 var browser
= win
.getBrowser().getBrowserForTab(tab
);
504 var history
= browser
.webNavigation
.sessionHistory
;
506 if (history
.count
> 0) {
507 history
.PurgeHistory(history
.count
);
509 history
.QueryInterface(Ci
.nsISHistoryInternal
);
511 browser
.markupDocumentViewer
.textZoom
= parseFloat(tabData
.zoom
|| 1);
513 for (var i
= 0; i
< tabData
.entries
.length
; i
++) {
514 history
.addEntry(this._deserializeHistoryEntry(tabData
.entries
[i
], idMap
), true);
517 // make sure to reset the capabilities and attributes, in case this tab gets reused
518 var disallow
= (tabData
.disallow
)?tabData
.disallow
.split(","):[];
519 CAPABILITIES
.forEach(function(aCapability
) {
520 browser
.docShell
["allow" + aCapability
] = disallow
.indexOf(aCapability
) == -1;
522 Array
.filter(tab
.attributes
, function(aAttr
) {
523 return (_this
.xulAttributes
.indexOf(aAttr
.name
) > -1);
524 }).forEach(tab
.removeAttribute
, tab
);
525 if (tabData
.xultab
) {
526 tabData
.xultab
.split(" ").forEach(function(aAttr
) {
527 if (/^([^\s=]+)=(.*)/.test(aAttr
)) {
528 tab
.setAttribute(RegExp
.$1, decodeURI(RegExp
.$2));
533 // notify the tabbrowser that the tab chrome has been restored
534 var event
= win
.document
.createEvent("Events");
535 event
.initEvent("SSTabRestoring", true, false);
536 tab
.dispatchEvent(event
);
538 var activeIndex
= (tabData
.index
|| tabData
.entries
.length
) - 1;
540 browser
.webNavigation
.gotoIndex(activeIndex
);
542 catch (ex
) { } // ignore an invalid tabData.index
544 // restore those aspects of the currently active documents
545 // which are not preserved in the plain history entries
546 // (mainly scroll state and text data)
547 browser
.__SS_restore_data
= tabData
.entries
[activeIndex
] || {};
548 browser
.__SS_restore_text
= tabData
.text
|| "";
549 browser
.__SS_restore_tab
= tab
;
550 browser
.__SS_restore
= this.restoreDocument_proxy
;
551 browser
.addEventListener("load", browser
.__SS_restore
, true);
553 _deserializeHistoryEntry
: function TB__deserializeHistoryEntry(aEntry
, aIdMap
) {
554 var shEntry
= Cc
["@mozilla.org/browser/session-history-entry;1"].
555 createInstance(Ci
.nsISHEntry
);
557 var ioService
= Cc
["@mozilla.org/network/io-service;1"].
558 getService(Ci
.nsIIOService
);
559 shEntry
.setURI(ioService
.newURI(aEntry
.url
, null, null));
560 shEntry
.setTitle(aEntry
.title
|| aEntry
.url
);
561 shEntry
.setIsSubFrame(aEntry
.subframe
|| false);
562 shEntry
.loadType
= Ci
.nsIDocShellLoadInfo
.loadHistory
;
564 if (aEntry
.cacheKey
) {
565 var cacheKey
= Cc
["@mozilla.org/supports-PRUint32;1"].
566 createInstance(Ci
.nsISupportsPRUint32
);
567 cacheKey
.data
= aEntry
.cacheKey
;
568 shEntry
.cacheKey
= cacheKey
;
571 // get a new unique ID for this frame (since the one from the last
572 // start might already be in use)
573 var id
= aIdMap
[aEntry
.ID
] || 0;
575 for (id
= Date
.now(); aIdMap
.used
[id
]; id
++);
576 aIdMap
[aEntry
.ID
] = id
;
577 aIdMap
.used
[id
] = true;
582 var scrollPos
= (aEntry
.scroll
|| "0,0").split(",");
583 scrollPos
= [parseInt(scrollPos
[0]) || 0, parseInt(scrollPos
[1]) || 0];
584 shEntry
.setScrollPosition(scrollPos
[0], scrollPos
[1]);
586 if (aEntry
.postdata
) {
587 var stream
= Cc
["@mozilla.org/io/string-input-stream;1"].
588 createInstance(Ci
.nsIStringInputStream
);
589 stream
.setData(aEntry
.postdata
, -1);
590 shEntry
.postData
= stream
;
593 if (aEntry
.children
&& shEntry
instanceof Ci
.nsISHContainer
) {
594 for (var i
= 0; i
< aEntry
.children
.length
; i
++) {
595 shEntry
.AddChild(this._deserializeHistoryEntry(aEntry
.children
[i
], aIdMap
), i
);
601 restoreDocument_proxy
: function TB_restoreDocument_proxy(aEvent
) {
602 // wait for the top frame to be loaded completely
603 if (!aEvent
|| !aEvent
.originalTarget
|| !aEvent
.originalTarget
.defaultView
|| aEvent
.originalTarget
.defaultView
!= aEvent
.originalTarget
.defaultView
.top
) {
607 var textArray
= this.__SS_restore_text
? this.__SS_restore_text
.split(" ") : [];
608 function restoreTextData(aContent
, aPrefix
) {
609 textArray
.forEach(function(aEntry
) {
610 if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry
) && (!RegExp
.$1 || RegExp
.$1 == aPrefix
)) {
611 var document
= aContent
.document
;
612 var node
= RegExp
.$2 ? document
.getElementById(RegExp
.$3) : document
.getElementsByName(RegExp
.$3)[0] || null;
613 if (node
&& "value" in node
) {
614 node
.value
= decodeURI(RegExp
.$4);
616 var event
= document
.createEvent("UIEvents");
617 event
.initUIEvent("input", true, true, aContent
, 0);
618 node
.dispatchEvent(event
);
624 function restoreTextDataAndScrolling(aContent
, aData
, aPrefix
) {
625 restoreTextData(aContent
, aPrefix
);
626 if (aData
.innerHTML
) {
627 aContent
.setTimeout(function(aHTML
) { if (this.document
.designMode
== "on") { this.document
.body
.innerHTML
= aHTML
; } }, 0, aData
.innerHTML
);
629 if (aData
.scroll
&& /(\d+),(\d+)/.test(aData
.scroll
)) {
630 aContent
.scrollTo(RegExp
.$1, RegExp
.$2);
632 for (var i
= 0; i
< aContent
.frames
.length
; i
++) {
633 if (aData
.children
&& aData
.children
[i
]) {
634 restoreTextDataAndScrolling(aContent
.frames
[i
], aData
.children
[i
], i
+ "|" + aPrefix
);
639 var content
= XPCNativeWrapper(aEvent
.originalTarget
).defaultView
;
640 if (this.currentURI
.spec
== "about:config") {
641 // unwrap the document for about:config because otherwise the properties
642 // of the XBL bindings - as the textbox - aren't accessible (see bug 350718)
643 content
= content
.wrappedJSObject
;
645 restoreTextDataAndScrolling(content
, this.__SS_restore_data
, "");
647 // notify the tabbrowser that this document has been completely restored
648 var event
= this.ownerDocument
.createEvent("Events");
649 event
.initEvent("SSTabRestored", true, false);
650 this.__SS_restore_tab
.dispatchEvent(event
);
652 this.removeEventListener("load", this.__SS_restore
, true);
653 delete this.__SS_restore_data
;
654 delete this.__SS_restore_text
;
655 delete this.__SS_restore_tab
;
659 getInterfaces
: function TB_getInterfaces(countRef
) {
660 var interfaces
= [Ci
.oyITaboo
, Ci
.nsIObserver
, Ci
.nsISupports
];
661 countRef
.value
= interfaces
.length
;
664 getHelperForLanguage
: function TB_getHelperForLanguage(language
) {
667 contractID
: TB_CONTRACTID
,
668 classDescription
: TB_CLASSNAME
,
670 implementationLanguage
: Ci
.nsIProgrammingLanguage
.JAVASCRIPT
,
671 flags
: Ci
.nsIClassInfo
.SINGLETON
,
673 QueryInterface
: function TB_QueryInterface(iid
) {
674 if (iid
.equals(Ci
.oyITaboo
) ||
675 iid
.equals(Ci
.nsIObserver
) ||
676 iid
.equals(Ci
.nsISupports
))
678 throw Cr
.NS_ERROR_NO_INTERFACE
;
683 /* This is swiped from bookmarks.js in Firefox. In Firefox 3, this *should*
684 * be easier, and not require cut'n'pasting
686 function tabooFavIconLoadListener(url
, faviconurl
, channel
, storage
) {
688 this.mFavIconURL
= faviconurl
;
690 this.mChannel
= channel
;
691 this.mStorage
= storage
;
694 tabooFavIconLoadListener
.prototype = {
702 QueryInterface: function (iid
) {
703 if (!iid
.equals(Components
.interfaces
.nsISupports
) &&
704 !iid
.equals(Components
.interfaces
.nsIInterfaceRequestor
) &&
705 !iid
.equals(Components
.interfaces
.nsIRequestObserver
) &&
706 !iid
.equals(Components
.interfaces
.nsIChannelEventSink
) &&
707 !iid
.equals(Components
.interfaces
.nsIProgressEventSink
) && // see below
708 !iid
.equals(Components
.interfaces
.nsIStreamListener
)) {
709 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
714 // nsIInterfaceRequestor
715 getInterface: function (iid
) {
717 return this.QueryInterface(iid
);
719 throw Components
.results
.NS_NOINTERFACE
;
723 // nsIRequestObserver
724 onStartRequest : function (aRequest
, aContext
) {
725 this.mStream
= Components
.classes
['@mozilla.org/binaryinputstream;1'].createInstance(Components
.interfaces
.nsIBinaryInputStream
);
728 onStopRequest : function (aRequest
, aContext
, aStatusCode
) {
729 var httpChannel
= this.mChannel
.QueryInterface(Components
.interfaces
.nsIHttpChannel
);
730 if ((httpChannel
&& httpChannel
.requestSucceeded
) &&
731 Components
.isSuccessCode(aStatusCode
) &&
735 // XXX - arbitrary size beyond which we won't store a favicon. This is /extremely/
736 // generous, and is probably too high.
737 if (this.mCountRead
> 16384) {
738 dataurl
= "data:"; // hack meaning "pretend this doesn't exist"
740 // get us a mime type for this
743 const nsICategoryManager
= Components
.interfaces
.nsICategoryManager
;
744 const nsIContentSniffer
= Components
.interfaces
.nsIContentSniffer
;
746 var catMgr
= Components
.classes
["@mozilla.org/categorymanager;1"].getService(nsICategoryManager
);
747 var sniffers
= catMgr
.enumerateCategory("content-sniffing-services");
748 while (mimeType
== null && sniffers
.hasMoreElements()) {
749 var snifferCID
= sniffers
.getNext().QueryInterface(Components
.interfaces
.nsISupportsCString
).toString();
750 var sniffer
= Components
.classes
[snifferCID
].getService(nsIContentSniffer
);
753 mimeType
= sniffer
.getMIMETypeFromContent (this.mBytes
, this.mCountRead
);
761 if (this.mBytes
&& this.mCountRead
> 0 && mimeType
!= null) {
766 var iconData
= String
.fromCharCode
.apply(null, this.mBytes
);
767 data
+= base64Encode(iconData
);
769 this.mStorage
.saveFavicon(this.mURL
, data
);
773 this.mChannel
= null;
777 onDataAvailable : function (aRequest
, aContext
, aInputStream
, aOffset
, aCount
) {
778 // we could get a different aInputStream, so we don't save this;
779 // it's unlikely we'll get more than one onDataAvailable for a
781 this.mStream
.setInputStream(aInputStream
);
783 var chunk
= this.mStream
.readByteArray(aCount
);
784 this.mBytes
= this.mBytes
.concat(chunk
);
785 this.mCountRead
+= aCount
;
788 // nsIChannelEventSink
789 onChannelRedirect : function (aOldChannel
, aNewChannel
, aFlags
) {
790 this.mChannel
= aNewChannel
;
793 // nsIProgressEventSink: the only reason we support
794 // nsIProgressEventSink is to shut up a whole slew of xpconnect
795 // warnings in debug builds. (see bug #253127)
796 onProgress : function (aRequest
, aContext
, aProgress
, aProgressMax
) { },
797 onStatus : function (aRequest
, aContext
, aStatus
, aStatusArg
) { }
800 // From flockFavoritesService.js
801 function base64Encode(aInput
) {
802 var chars
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
804 while (aInput
.length
> 0) {
805 output
+= chars
[aInput
.charCodeAt(0) >> 2];
806 output
+= chars
[((aInput
.charCodeAt(0) & 0x03) << 4) |
807 (aInput
.length
> 1 ? ((aInput
.charCodeAt(1) & 0xF0) >> 4) : 0)];
808 output
+= chars
[aInput
.length
> 1 ?
809 ((aInput
.charCodeAt(1) & 0x0F) << 2) |
810 (aInput
.length
> 2 ? ((aInput
.charCodeAt(2) & 0xC0) >> 6) : 0) : 64];
811 output
+= chars
[aInput
.length
> 2 ?
812 (aInput
.charCodeAt(2) & 0x3F) : 64];
813 if (aInput
.length
> 3) {
814 aInput
= aInput
.substr(3);
823 function GenericComponentFactory(ctor
) {
827 GenericComponentFactory
.prototype = {
832 createInstance: function(outer
, iid
) {
834 throw Cr
.NS_ERROR_NO_AGGREGATION
;
835 return (new this._ctor()).QueryInterface(iid
);
839 QueryInterface: function(iid
) {
840 if (iid
.equals(Ci
.nsIFactory
) ||
841 iid
.equals(Ci
.nsISupports
))
843 throw Cr
.NS_ERROR_NO_INTERFACE
;
848 QueryInterface: function(iid
) {
849 if (iid
.equals(Ci
.nsIModule
) ||
850 iid
.equals(Ci
.nsISupports
))
853 throw Cr
.NS_ERROR_NO_INTERFACE
;
856 getClassObject: function(cm
, cid
, iid
) {
857 if (!iid
.equals(Ci
.nsIFactory
))
858 throw Cr
.NS_ERROR_NOT_IMPLEMENTED
;
860 if (cid
.equals(TB_CLASSID
))
861 return new GenericComponentFactory(TabooService
)
863 throw Cr
.NS_ERROR_NO_INTERFACE
;
866 registerSelf: function(cm
, file
, location
, type
) {
867 var cr
= cm
.QueryInterface(Ci
.nsIComponentRegistrar
);
868 cr
.registerFactoryLocation(TB_CLASSID
, TB_CLASSNAME
, TB_CONTRACTID
,
869 file
, location
, type
);
871 var catman
= Cc
['@mozilla.org/categorymanager;1']
872 .getService(Ci
.nsICategoryManager
);
873 catman
.addCategoryEntry('app-startup', TB_CLASSNAME
,
874 'service,' + TB_CONTRACTID
,
878 unregisterSelf: function(cm
, location
, type
) {
879 var cr
= cm
.QueryInterface(Ci
.nsIComponentRegistrar
);
880 cr
.unregisterFactoryLocation(TB_CLASSID
, location
);
883 canUnload: function(cm
) {
888 function NSGetModule(compMgr
, fileSpec
)
894 function loadSubScript(spec
) {
895 var loader
= Cc
['@mozilla.org/moz/jssubscript-loader;1']
896 .getService(Ci
.mozIJSSubScriptLoader
);
898 loader
.loadSubScript(spec
, context
);