でも
使ってみたら逆に不便だった><
勉強になったからいいや。
破棄!
もったいないので、今手元にあるソースを貼っときます
このソースを Firefox インストールディレクトリ以下の components に入れると textbox 要素で autocomplete="delicious" が使えるようになります。
開発中のものなのでバグありまくりです。
ちなみに、僕が一日に書けるコードはちょうどこのくらいです↓
const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const CLASS_ID = Components.ID('{aa892eb4-ffbf-477d-9f9a-06c995ae9f85}'); const CONSTRACT_ID = '@mozilla.org/autocomplete/search;1?name=delicious'; var SocialBookmark = function(href, description, hash, tags, date) { this.href = href; this.description = description; this.hash = hash; this.tags = tags.split(/\s+/); this.date = date; }; var SocialBookmarkAPI = { username: null, password: null, _parseBookmarkXML: function(data) { var posts = data.getElementsByTagName('post'); var bookmarks = []; for (var i = 0, l = posts.length; i < l; i ++) { var post = posts[i]; bookmarks.push( new SocialBookmark( post.getAttribute('href'), post.getAttribute('description'), post.getAttribute('hash'), post.getAttribute('tag'), post.getAttribute('time'))); } return bookmarks; }, _parseCountXML: function(date, data) { var dates = data.getElementsByTagName('date'); if (!date) { date = dates[dates.length - 1].getAttribute('date'); } else { date = date.split(/T/)[0]; } var count = 0; for (var i = 0, l = dates.length; i < l; i ++) { count += +dates[i].getAttribute('count'); if (date == dates[i].getAttribute('date')) { break; } } return count; }, getBookmarks: function(callback) { var self = this; this._get('https://api.del.icio.us/v1/posts/all', function(data) { //this._get('https://api.del.icio.us/v1/posts/recent?count=100', function(data) { if (data) { callback(self._parseBookmarkXML(data)); } else { callback(null); } }); }, getBookmarksFrom: function(date, callback) { var self = this; this._get('https://api.del.icio.us/v1/posts/dates', function(data) { if (data) { var count = self._parseCountXML(date, data); self._get('https://api.del.icio.us/v1/posts/recent?count=' + count, function(data) { if (data) { callback(self._parseBookmarkXML(data)); } else { callback(null); } }); } else { callback(null); } }); }, getRecentHash: function(callback) { var self = this; this._get('https://api.del.icio.us/v1/posts/recent?count=1', function(data) { if (data) { callback(self._parseBookmarkXML(data)[0].hash); } else { callback(null); } }); }, abort: function() { if (this._request) { this._request.abort(); this._request = null; } }, _get: function(uri, callback) { dump('_get(' + uri + ')\n'); var self = this; var request = this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); request.open('GET',uri,true,this.username,this.password); request.send(null); request.onreadystatechange = function() { if (!self._request || self._request.readyState != 4) { return; } try { var request = self._request; self._request = null; switch (request.status) { case 503: // Del.icio.us のサーバから蹴られた場合 dump(request.status + '\n'); callback(request.responseXML); break; case 200: // OK の場合 callback(request.responseXML); break; default: dump(request.status + '\n'); callback(request.responseXML); break; } } catch(e) { callback(null); } }; }, _setBasicData: function(request, username, password) { request.setRequestHeader('Authorization', 'Basic ' + this._toBase64(username + ':' + password)); }, _setWsseData: function(request, username, password) { var created = this._iso8601Date(new Date()); var nonce = (Math.random() + "").substr(2, 32); var data = nonce + created + (password || ''); var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter'].createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = 'UTF-8'; data = converter.convertToByteArray(data, {}); var ch = Cc['@mozilla.org/security/hash;1'].createInstance(Ci.nsICryptoHash); ch.init(ch.SHA1); ch.update(data, data.length); var data = ch.finish(false); var passwordDigest = this._toBase64(data); nonce = this._toBase64(nonce); var wsse = 'UsernameToken Username="' + username + '", PasswordDigest="' + passwordDigest + '", Created="' + created + '", Nonce="' + nonce + '"'; request.setRequestHeader('Authorization', 'WSSE profile="UsernameToken"'); request.setRequestHeader('X-WSSE', wsse); }, _iso8601Date: function(date) { var datetime = date.getUTCFullYear(); var month = String(date.getUTCMonth() + 1); datetime += (month.length == 1 ? '0' + month : month); var day = date.getUTCDate(); datetime += (day < 10 ? '0' + day : day); datetime += 'T'; var hour = date.getUTCHours(); datetime += (hour < 10 ? '0' + hour : hour) + ':'; var minutes = date.getUTCMinutes(); datetime += (minutes < 10 ? '0' + minutes : minutes) + ':'; var seconds = date.getUTCSeconds(); datetime += (seconds < 10 ? '0' + seconds : seconds); return datetime; }, _toBase64: function(data) { const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; const base64Pad = '='; var result = ''; var length = data.length; for (var i = 0; i < (length - 2); i += 3) { result += toBase64Table[data.charCodeAt(i) >> 2]; result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i+1) >> 4)]; result += toBase64Table[((data.charCodeAt(i+1) & 0x0f) << 2) + (data.charCodeAt(i+2) >> 6)]; result += toBase64Table[data.charCodeAt(i+2) & 0x3f]; } if (length%3) { i = length - (length%3); result += toBase64Table[data.charCodeAt(i) >> 2]; if ((length%3) == 2) { result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i+1) >> 4)]; result += toBase64Table[(data.charCodeAt(i+1) & 0x0f) << 2]; result += base64Pad; } else { result += toBase64Table[(data.charCodeAt(i) & 0x03) << 4]; result += base64Pad + base64Pad; } } return result; } }; var SocialBookmarkUrlCache = function(username, password, callback) { SocialBookmarkAPI.username = username; SocialBookmarkAPI.password = password; var self = this; this.bookmarks = []; this.load(); this.hashes = {}; this.update(function(result) { callback(result); }); }; SocialBookmarkUrlCache.prototype = { updateAll: function(callback) { var self = this; SocialBookmarkAPI.abort(); SocialBookmarkAPI.getBookmarks(function(bookmarks) { if (bookmarks) { self._appendBookmarks(bookmarks); callback(true); } else { callback(false); } }); }, update: function(callback) { if (!this.loaded) { return this.updateAll(callback) } var self = this; SocialBookmarkAPI.abort(); SocialBookmarkAPI.getRecentHash(function(hash) { if (hash && (!self.recentHash || self.recentHash != hash)) { // 更新されていたら SocialBookmarkAPI.getBookmarksFrom(self.recentDate, function(bookmarks) { if (bookmarks) { self._appendBookmarks(bookmarks); callback(true); } else { callback(false); } }); } else { callback(true); } }); }, _file: function() { var file = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("ProfD", Ci.nsILocalFile); file.append("social_bookmark_autocomplete_cache.js"); return file; }, load: function() { var file = this._file(); if (!file.exists()) { return; } var data = ""; var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); var sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); fstream.init(file, -1, 0, 0); sstream.init(fstream); var str = sstream.read(4096); while (str.length > 0) { data += str; str = sstream.read(4096); } sstream.close(); fstream.close(); this.bookmarks = eval(data); if (this.bookmarks[0]) { this.recentHash = this.bookmarks[0].hash; this.recentDate = this.bookmarks[0].date; } this.loaded = true; }, save: function() { var file = this._file(); if (file.exists()) { file.remove(false); } var stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); stream.init(file, 0x02 | 0x08 | 0x20, 420, -1); var data = this.bookmarks.toSource(); stream.write(data, data.length); stream.close(); this.loaded = true; }, createSuggestAutoCompleteResult: function(searchString) { var self = this; var results = []; var comments = []; this.bookmarks.forEach(function(bm) { if (bm.href.indexOf(searchString, 0) != -1) { results.push(bm.href); comments.push(bm.tags.join(' ')); } else if (bm.tags.some(function(tag) { return tag.indexOf(searchString) != -1 })) { results.push(bm.href); comments.push(bm.tags.join(' ')); } }); return new SocialBookmarkSuggestAutoCompleteResult( searchString, Ci.nsIAutoCompleteResult.RESULT_SUCCESS, 0, '', results, comments); }, _appendBookmarks: function(bookmarks) { bookmarks = bookmarks.reverse(); for (var i = 0, l = bookmarks.length; i < l; i ++) { this._appendBookmark(bookmarks[i]); } this.save(); this.recentHash = this.bookmarks[0].hash; this.recentDate = this.bookmarks[0].date; }, _appendBookmark: function(bookmark) { if (!this.hashes[bookmark.hash]) { this.hashes[bookmark.hash] = true; this.bookmarks.unshift(bookmark); } } }; var SocialBookmarkSuggestAutoCompleteResult = function( searchString, searchResult, defaultIndex, errorDescription, results, comments) { this._searchString = searchString; this._searchResult = searchResult; this._defaultIndex = defaultIndex; this._errorDescription = errorDescription; this._matchCount = results.length; this._results = results; this._comments = comments; }; SocialBookmarkSuggestAutoCompleteResult.prototype = { _className: 'SocialBookmarkSuggestAutoCompleteResult', get searchString() { return this._searchString; }, get searchResult() { return this._searchResult; }, get defaultIndex() { return this._defaultIndex; }, get errorDescription() { return this._errorDescription; }, get matchCount() { return this._matchCount; }, getValueAt: function(index) { return this._results[index]; }, getCommentAt: function(index) { return this._comments[index]; }, getStyleAt: function(index) { return 'hoge'; }, removeValueAt: function(rowIndex, removeFrom) { }, QueryInterface: function(iid) { if (!iid.equals(Ci.nsIAutoCompleteResult) && !iid.equals(Ci.nsISupports)) { throw Cr.NS_ERROR_NO_INTERFACE; } return this; } } var SocialBookmarkSuggestAutoComplete = function() { this._busy = true; this._available = true; self = this; this._cache = new SocialBookmarkUrlCache('amachang', '*********', function(result) { if (!result) { self._available = false; } self._busy = false; }); }; SocialBookmarkSuggestAutoComplete.prototype = { _className: 'SocialBookmarkSuggestAutoComplete', startSearch: function(searchString, searchParam, previousResult, listener) { if (!this._busy && this._available) { var cache = this._cache; var self = this; cache.update(function(result) { if (!result) { self._available = false; } else { listener.onSearchResult(self, cache.createSuggestAutoCompleteResult(searchString)); } }); } }, stopSearch: function() { }, QueryInterface: function(iid) { if (!iid.equals(Ci.nsIAutoCompleteSearch) && !iid.equals(Ci.nsIAutoCompleteObserver) && !iid.equals(Ci.nsISupports)) { throw Cr.NS_ERROR_NO_INTERFACE; } return this; } }; function NSGetModule(compMgr, fileSpec) { dump('HA: Call NSGetModule\n'); return { registerSelf: function(compMgr, fileSpec, location, type) { dump('HA: Call registerSelf\n'); compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar); compMgr.registerFactoryLocation( CLASS_ID, 'Social Bookmark Suggestions', CONSTRACT_ID, fileSpec, location, type); }, getClassObject: function(compMgr, cid, iid) { return { createInstance: function(outer, iid) { if (outer != null) { throw Cr.NS_ERROR_NO_AGGREGATION; } return new SocialBookmarkSuggestAutoComplete().QueryInterface(iid); } }; } }; } dump('HA: Load\n');