1 /*** BEGIN LICENSE BLOCK {{{
2 Copyright (c) 2008 suVene<suvene@zeromemory.info>
3 Copyright (c) 2008-2011 anekos<anekos@snca.net>
5 distributable under the terms of an MIT-style license.
6 http://www.opensource.jp/licenses/mit-license.html
7 }}} END LICENSE BLOCK ***/
10 var PLUGIN_INFO = xml`
12 <name>libly(filename _libly.js)</name>
13 <description>Vimperator plugins library?</description>
14 <description lang="ja">適当なライブラリっぽいものたち。</description>
15 <author mail="suvene@zeromemory.info" homepage="http://zeromemory.sblo.jp/">suVene</author>
16 <author mail="anekos@snca.net" homepage="http://snca.net/">anekos</author>
17 <license>MIT</license>
18 <version>0.1.38</version>
19 <minVersion>2.3pre</minVersion>
20 <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/_libly.js</updateURL>
23 - liberator.plugins.libly.$U
24 - liberator.plugins.libly.Request
25 - liberator.plugins.libly.Response
26 - liberator.plugins.libly.Wedata
30 log(msg, level), echo(msg, flg), echoerr(msg)
31 のメソッドを持つ logger インスタンスを取得します。
32 ログの書式は prefix + ': ' + yyyy/MM/dd + msg となります。
39 around(obj, name, func, autoRestore):
40 obj がもつ name 関数を、func に置き換えます。
42 function (next, args) {...}
44 next はオリジナルの関数を呼び出すための関数、
46 通常、next には引数を渡す必要はありません。
47 (任意の引数を渡したい場合は配列で渡します。)
48 また、autoRestore が真であれば、プラグインの再ロードなどで around が再実行されたときに、関数の置き換え前にオリジナル状態に書き戻します。
49 (多重に置き換えられなくなるので、auto_source.js などを使ったプラグイン開発で便利です)
59 func に obj を bind します。
60 func内からは this で obj が参照できるようになります。
62 Sandbox による、window.eval を極力利用するようにします。
63 Snadbox が利用できない場合は、unsafe な window の eval が直接利用されます。
64 evalJson(str, toRemove):
66 toRemove が true の場合、文字列の前後を1文字削除します。
67 "(key:value)" 形式の場合などに true を指定して下さい。
69 Date型インスタンスを、指定されたフォーマットで文字列に変換します。
70 fmt を省略した場合、"%y/%M/%d %h:%m:%s" となります。
72 gererator を実行し、再帰的に resume する為の引数を渡します。
77 getUserAndPassword(hostname, formSubmitURL, username):
78 login-manager から [username, password] を取得します。
79 引数の username が省略された場合、検索された 1件目を返却します。
80 データが存在しない場合は、null を返却します。
83 readDirectory(path, fileter, func):
84 path で指定したディレクトリから、filter で指定された正規表現に match する場合、
85 func をファイル名を引数にコールバックします。
86 filter は Function を指定することも可能です。
88 == HTML, XML, DOM, E4X ==
89 pathToURL(a, baseURL, doc):
91 getHTMLFragment(html):
95 str から tags で指定されたタグを取り除いて返却します。
96 tags は文字列、または配列で指定して下さい。
97 createHTMLDocument(str, xmlns):
98 引数 str より、HTMLFragment を作成します。
99 getFirstNodeFromXPath(xpath, context):
100 xpath を評価しオブジェクトをを返却します。
101 getNodesFromXPath(xpath, context, callback, thisObj):
102 xpath を評価し snapshot の配列を返却します。
105 xmlToDom(node, doc, nodes):
107 @see vimperator2.0pre util.
108 getElementPosition(elem):
109 elem の offset を返却します。
110 return {top: 0, left: 0}
120 Request(url, headers, options):
125 以下のようにHTTPヘッダの値を指定できる(省略可)
128 'Referer' : 'http://example.com/'
131 以下の値はデフォルトで設定される('Content-type'はPOST時のみ)
134 'Accept': 'text/javascript, application/javascript, text/html, application/xhtml+xml, application/xml, text/xml, * /*;q=0.1',
135 'Content-type': 'application/x-www-form-urlencoded; charset=' + options.encodingの値
140 オプションとして以下のようなオブジェクトを指定できる(省略可)
142 true: 非同期モード/false: 同期モード(デフォルト:true)
144 エンコーディング(デフォルト: 'UTF-8')
151 addEventListener(name, func):
162 引数として以下Responseオブジェクトが渡される
164 GETメソッドによりHTTPリクエストを発行する。
166 POSTメソッドによりHTTPリクエストを発行する。
168 == Object Response ==
171 レスポンスと対となるRequestオブジェクト
173 レスポンスから生成されたHTMLDocumentオブジェクト
175 ステータスコードが成功を表していればtrue、失敗であればfalse
180 getHTMLDocument(xpath, xmlns, ignoreTags, callback, thisObj):
181 レスポンスからHTMLDocumentオブジェクトを生成し、xpath を評価した結果の snapshot の配列を返す
184 ~/vimperator/info/profile_name/plugins-libly-wedata-?????
186 getItems(expire, itemCallback, finalCallback):
187 インスタンス作成時に指定した dbname から、item を読込みます。
190 wedata 読込み成功したら、強制的にキャッシュと置き換えるの作って!
193 </VimperatorPlugin>`;
196 //if (!liberator.plugins.libly) {
198 liberator.plugins.libly = {};
199 var libly = liberator.plugins.libly;
201 // XXX for backward compatibillity
202 function fixEventName(name) {
203 return name.replace(/^on/, '').toLowerCase();
208 getLogger: function(prefix) {
209 return new function() {
210 this.log = function(msg, level) {
211 if (typeof msg == 'object') msg = util.objectToString(msg);
212 liberator.log(libly.$U.dateFormat(new Date()) + ': ' + (prefix || '') + ': ' + msg, (level || 0));
214 this.echo = function(msg, flg) {
215 flg = flg || commandline.FORCE_MULTILINE;
217 liberator.echo(msg, flg);
219 this.echoerr = function(msg) {
220 this.log('error: ' + msg);
221 liberator.echoerr(msg);
226 // Object Utility {{{
227 extend: function(dst, src) {
228 for (let prop in src)
229 dst[prop] = src[prop];
232 A: function(iterable) {
234 if (!iterable) return ret;
235 if (typeof iterable == 'string') return [iterable];
236 if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
237 iterable.toArray) return iterable.toArray();
238 if (typeof iterable.length != 'undefined') {
239 for (let i = 0, len = iterable.length; i < len; ret.push(iterable[i++]));
241 for each (let item in iterable) ret.push(item);
245 around: (function () {
246 function getPluginPath () {
248 Error('hoge').stack.split(/\n/).some(
250 let m = s.match(/@chrome:\/\/liberator\/content\/liberator\.js -> file:\/\/\/(.+):\d+$/);
251 (m && (pluginPath = m[1].replace(/\?.*$/, '')));
259 return function (obj, name, func, autoRestore) {
261 let restore = function () obj[name] = original;
263 let pluginPath = getPluginPath();
265 restores[pluginPath] =
266 (restores[pluginPath] || []).filter(
270 (res.restore() && false)
273 restores[pluginPath].push({
279 liberator.echoerr('getPluginPath failed');
282 original = obj[name];
283 let current = obj[name] = function () {
284 let self = this, args = Array.prototype.slice.call(arguments);
285 return func.call(self, function (_args) original.apply(self, _args || args), args);
287 libly.$U.extend(current, {original: original && original.original || original, restore: restore});
288 return libly.$U.extend({
292 }, [original, current]);
295 bind: function(obj, func) {
297 return func.apply(obj, arguments);
300 eval: function(text) {
301 var fnc = window.eval;
304 sandbox = new Components.utils.Sandbox("about:blank");
305 if (Components.utils.evalInSandbox('true', sandbox) === true) {
306 fnc = function(text) { return Components.utils.evalInSandbox(text, sandbox); };
308 } catch (e) { liberator.log('warning: _libly.js is working with unsafe sandbox.'); }
312 evalJson: function(str, toRemove) {
315 if (toRemove) str = str.substring(1, str.length - 1);
316 return JSON.parse(str);
317 } catch (e) { return null; }
319 dateFormat: function(dtm, fmt) {
321 y: dtm.getFullYear(),
322 M: dtm.getMonth() + 1,
329 for (let [n, v] in Iterator(d)) {
333 return (fmt || '%y/%M/%d %h:%m:%s').replace(/%([yMdhms%])/g, function (_, n) d[n]);
337 * $U.runnable(function(resume) {
338 * // execute asynchronous function.
339 * // goto next yield;
340 * var val = yield setTimeout(function() { resume('value!'), 1000) });
341 * alert(val); // value!
345 runnable: function(generator) {
346 var it = generator(function(value) {
347 try { it.send(value); } catch (e) {}
353 getSelectedString: function() {
354 return (new XPCNativeWrapper(window.content.window)).getSelection().toString();
356 getUserAndPassword: function(hostname, formSubmitURL, username) {
357 var passwordManager, logins;
359 passwordManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
360 logins = passwordManager.findLogins({}, hostname, formSubmitURL, null);
363 for (let i = 0, len = logins.lengh; i < len; i++) {
364 if (logins[i].username == username)
365 return [logins[i].username, logins[i].password]
367 liberator.log(this.dateFormat(new Date()) +': [getUserAndPassword] username notfound');
368 //throw 'username notfound.';
371 return [logins[0].username, logins[0].password];
374 liberator.log(this.dateFormat(new Date()) + ': [getUserAndPassword] account notfound');
378 liberator.log(this.dateFormat(new Date()) + ': [getUserAndPassword] error: ' + e, 0);
384 readDirectory: function(path, filter, func) {
385 var d = io.File(path);
386 if (d.exists() && d.isDirectory()) {
387 let enm = d.directoryEntries;
389 while (enm.hasMoreElements()) {
390 let item = enm.getNext();
391 item.QueryInterface(Components.interfaces.nsIFile);
393 if (typeof filter == 'string') {
394 if ((new RegExp(filter)).test(item.leafName)) flg = true;
395 } else if (typeof filter == 'function') {
403 // HTML, XML, DOM, E4X {{{
404 pathToURL: function(a, baseURL, doc) {
406 var XHTML_NS = "http://www.w3.org/1999/xhtml";
407 var XML_NS = "http://www.w3.org/XML/1998/namespace";
408 //var path = (a.href || a.getAttribute('src') || a.action || a.value || a);
409 var path = (a.getAttribute('href') || a.getAttribute('src') || a.action || a.value || a);
410 if (/^https?:\/\//.test(path)) return path;
411 var link = (doc || window.content.documtent).createElementNS(XHTML_NS, 'a');
412 link.setAttributeNS(XML_NS, 'xml:base', baseURL);
416 getHTMLFragment: function(html) {
417 if (!html) return html;
418 return html.replace(/^[\s\S]*?<html(?:[ \t\n\r][^>]*)?>|<\/html[ \t\r\n]*>[\S\s]*$/ig, '');
420 stripTags: function(str, tags) {
421 var ignoreTags = '(?:' + [].concat(tags).join('|') + ')';
422 return str.replace(new RegExp('<' + ignoreTags + '(?:[ \\t\\n\\r][^>]*|/)?>([\\S\\s]*?)<\/' + ignoreTags + '[ \\t\\r\\n]*>', 'ig'), '');
424 createHTMLDocument: function(str, xmlns, doc) {
425 let root = document.createElementNS("http://www.w3.org/1999/xhtml", "html");
426 let uhService = Cc["@mozilla.org/feed-unescapehtml;1"].getService(Ci.nsIScriptableUnescapeHTML);
427 let text = str.replace(/^[\s\S]*?<body([ \t\n\r][^>]*)?>[\s]*|<\/body[ \t\r\n]*>[\S\s]*$/ig, '');
428 let fragment = uhService.parseFragment(text, false, null, root);
429 let doctype = document.implementation.createDocumentType('html', '-//W3C//DTD HTML 4.01//EN', 'http://www.w3.org/TR/html4/strict.dtd');
430 let htmlFragment = document.implementation.createDocument(null, 'html', doctype);
431 htmlFragment.documentElement.appendChild(htmlFragment.importNode(fragment,true));
433 /* うまく動いていない場合はこちらに戻してください
434 doc = doc || window.content.document;
435 var htmlFragment = doc.implementation.createDocument(null, 'html', null);
436 var range = doc.createRange();
437 range.setStartAfter(doc.body);
438 htmlFragment.documentElement.appendChild(htmlFragment.importNode(range.createContextualFragment(str), true));
442 getFirstNodeFromXPath: function(xpath, context) {
443 if (!xpath) return null;
444 context = context || window.content.document;
445 var result = (context.ownerDocument || context).evaluate(xpath, context, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
446 return result.singleNodeValue || null;
448 getNodesFromXPath: function(xpath, context, callback, thisObj) {
450 if (!xpath) return ret;
451 context = context || window.content.document;
452 var nodesSnapshot = (context.ownerDocument || context).evaluate(xpath, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
453 for (let i = 0, l = nodesSnapshot.snapshotLength; i < l; i++) {
454 if (typeof callback == 'function') callback.call(thisObj, nodesSnapshot.snapshotItem(i), i);
455 ret.push(nodesSnapshot.snapshotItem(i));
459 xmlSerialize: function(xml) {
461 return (new XMLSerializer()).serializeToString(xml)
462 .replace(/<!--(?:[^-]|-(?!->))*-->/g, '')
463 .replace(/<\s*\/?\s*\w+/g, function(all) all.toLowerCase());
464 } catch (e) { return '' }
466 xmlToDom: function xmlToDom(node, doc, nodes)
468 return util.xmlToDom(node, doc, nodes);
470 getElementPosition: function(elem) {
471 var offsetTrail = elem;
474 while (offsetTrail) {
475 offsetLeft += offsetTrail.offsetLeft;
476 offsetTop += offsetTrail.offsetTop;
477 offsetTrail = offsetTrail.offsetParent;
479 offsetTop = offsetTop || null;
480 offsetLeft = offsetLeft || null;
481 return {top: offsetTop, left: offsetLeft};
483 toStyleText: function(style) {
485 for (let name in style) {
486 result += name.replace(/[A-Z]/g, function (c) ('-' + c.toLowerCase())) +
497 libly.Request = function() {//{{{
498 this.initialize.apply(this, arguments);
500 libly.Request.EVENTS = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
501 libly.Request.requestCount = 0;
502 libly.Request.prototype = {
503 initialize: function(url, headers, options) {
505 this.headers = headers || {};
506 this.options = libly.$U.extend({
512 addEventListener: function(name, func) {
513 name = fixEventName(name);
515 if (typeof this.observers[name] == 'undefined') this.observers[name] = [];
516 this.observers[name].push(func);
518 if (!this.fireEvent('exception', new libly.Response(this), e)) throw e;
521 fireEvent: function(name, args, asynchronous) {
522 name = fixEventName(name);
523 if (!(this.observers[name] instanceof Array)) return false;
524 this.observers[name].forEach(function(event) {
526 setTimeout(event, 10, args);
534 _request: function(method) {
537 libly.Request.requestCount++;
539 this.method = method;
540 this.transport = new XMLHttpRequest();
541 this.transport.open(method, this.url, this.options.asynchronous, this.options.username, this.options.password);
543 var stateChangeException;
544 this.transport.onreadystatechange = libly.$U.bind(this, function () {
546 this._onStateChange();
548 stateChangeException = e;
551 this.setRequestHeaders();
552 this.transport.overrideMimeType(this.options.mimetype || 'text/html; charset=' + this.options.encoding);
554 this.body = this.method == 'POST' ? this.options.postBody : null;
556 this.transport.send(this.body);
558 if (!this.options.asynchronous && stateChangeException) throw stateChangeException;
560 // Force Firefox to handle ready state 4 for synchronous requests
561 if (!this.options.asynchronous && this.transport.overrideMimeType)
562 this._onStateChange();
565 if (!this.fireEvent('exception', new libly.Response(this), e)) throw e;
568 _onStateChange: function() {
569 var readyState = this.transport.readyState;
570 if (readyState > 1 && !(readyState == 4 && this._complete))
571 this.respondToReadyState(this.transport.readyState);
573 getStatus: function() {
575 return this.transport.status || 0;
576 } catch (e) { return 0; }
578 isSuccess: function() {
579 var status = this.getStatus();
580 return !status || (status >= 200 && status < 300);
582 respondToReadyState: function(readyState) {
583 var state = libly.Request.EVENTS[readyState];
584 var res = new libly.Response(this);
586 if (state == 'Complete') {
587 libly.Request.requestCount--;
589 this._complete = true;
590 this.fireEvent(this.isSuccess() ? 'success' : 'failure', res, this.options.asynchronous);
592 if (!this.fireEvent('exception', res, e)) throw e;
596 setRequestHeaders: function() {
598 'Accept': 'text/javascript, application/javascript, text/html, application/xhtml+xml, application/xml, text/xml, */*;q=0.1'
601 if (this.method == 'POST') {
602 headers['Content-type'] = 'application/x-www-form-urlencoded' +
603 (this.options.encoding ? '; charset=' + this.options.encoding : '');
605 if (this.transport.overrideMimeType) {
606 let year = parseInt((navigator.userAgent.match(/\bGecko\/(\d{4})/) || [0, 2005])[1], 10);
607 if (0 < year && year < 2005)
608 headers['Connection'] = 'close';
612 for (let key in this.headers)
613 if (this.headers.hasOwnProperty(key)) headers[key] = this.headers[key];
615 for (let name in headers)
616 this.transport.setRequestHeader(name, headers[name]);
619 this._request('GET');
622 this._request('POST');
626 libly.Response = function() {//{{{
627 this.initialize.apply(this, arguments);
629 libly.Response.prototype = {
630 initialize: function(req) {
632 this.transport = req.transport;
633 this.isSuccess = req.isSuccess;
634 this.readyState = this.transport.readyState;
636 if (this.readyState == 4) {
637 this.status = this.getStatus();
638 this.statusText = this.getStatusText();
639 this.responseText = (this.transport.responseText == null) ? '' : this.transport.responseText;
640 this.responseXML = this.transport.responseXML;
644 this.htmlFragmentstr = '';
648 getStatus: libly.Request.prototype.getStatus,
649 getStatusText: function() {
651 return this.transport.statusText || '';
652 } catch (e) { return ''; }
654 getHTMLDocument: function(xpath, xmlns, ignoreTags, callback, thisObj) {
656 //if (doc.documentElement.nodeName != 'HTML') {
657 // return new DOMParser().parseFromString(str, 'application/xhtml+xml');
659 this.htmlFragmentstr = libly.$U.getHTMLFragment(this.responseText);
660 this.htmlStripScriptFragmentstr = libly.$U.stripTags(this.htmlFragmentstr, ignoreTags);
661 this.doc = libly.$U.createHTMLDocument(this.htmlStripScriptFragmentstr, xmlns);
663 if (!xpath) xpath = '//*';
664 return libly.$U.getNodesFromXPath(xpath, this.doc, callback, thisObj);
669 libly.Wedata = function(dbname) { // {{{
670 this.initialize.apply(this, arguments);
672 libly.Wedata.prototype = {
673 initialize: function(dbname) {
674 this.HOST_NAME = 'http://wedata.net/';
675 this.dbname = dbname;
676 this.logger = libly.$U.getLogger('libly.Wedata');
678 getItems: function(expire, itemCallback, finalCallback) {
680 var logger = this.logger;
681 var STORE_KEY = 'plugins-libly-wedata-' + encodeURIComponent(this.dbname) + '-items';
682 var store = storage.newMap(STORE_KEY, {store: true});
683 var cache = store && store.get('data');
685 if (store && cache && new Date(store.get('expire')) > new Date()) {
686 logger.log('return cache. ');
687 cache.forEach(function(item) { if (typeof itemCallback == 'function') itemCallback(item); });
688 if (typeof finalCallback == 'function')
689 finalCallback(true, cache);
693 expire = expire || 0;
695 function errDispatcher(msg, cache) {
697 logger.log('return cache. -> ' + msg);
698 cache.forEach(function(item) { if (typeof itemCallback == 'function') itemCallback(item); });
699 if (typeof finalCallback == 'function')
700 finalCallback(true, cache);
702 logger.log(msg + ': cache notfound.');
703 if (typeof finalCallback == 'function')
704 finalCallback(false, msg);
708 var req = new libly.Request(this.HOST_NAME + 'databases/' + encodeURIComponent(this.dbname) + '/items.json');
709 req.addEventListener('success', libly.$U.bind(this, function(res) {
710 var text = res.responseText;
712 errDispatcher('response is null.', cache);
715 var json = libly.$U.evalJson(text);
717 errDispatcher('failed eval json.', cache);
720 logger.log('success get wedata.');
721 store.set('expire', new Date(new Date().getTime() + expire).toString());
722 store.set('data', json);
724 json.forEach(function(item) { if (typeof itemCallback == 'function') itemCallback(item); });
725 if (typeof finalCallback == 'function')
726 finalCallback(true, json);
728 req.addEventListener('failure', function() errDispatcher('onFailure', cache));
729 req.addEventListener('exception', function() errDispatcher('onException', cache));
736 // vim: set fdm=marker sw=4 ts=4 sts=0 et: