aboutsummaryrefslogtreecommitdiffstats
path: root/gmperator.js
blob: df11af25e029e1b0bcfcdae8076e223e016b8347 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Vimperator plugin: "Update mixi echo"
// Last Change: 21-Oct-2008. Jan 2008
// License: Creative Commons
// Maintainer: mattn <mattn.jp@gmail.com> - http://mattn.kaoriya.net/

(function(){
	var ucnv = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
		.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
	ucnv.charset = "EUC-JP";
	function sprintf(format){
		var i = 1, re = /%s/, result = "" + format;
		while (re.test(result) && i < arguments.length) result = result.replace(re, arguments[i++]);
		return result;
	}
	function parseHTML(str, ignoreTags) {
		var exp = "^[\\s\\S]*?<html(?:\\s[^>]*)?>|</html\\s*>[\\S\\s]*$";
		if (ignoreTags) {
			if (typeof ignoreTags == "string") ignoreTags = [ignoreTags];
			var stripTags = [];
			ignoreTags = ignoreTags.filter(function(tag) tag[tag.length - 1] == "/" || !stripTags.push(tag))
			                       .map(function(tag) tag.replace(/\/$/, ""));
			if (stripTags.length > 0) {
				stripTags = stripTags.length > 1
						  ? "(?:" + stripTags.join("|") + ")"
						  : String(stripTags);
				exp += "|<" + stripTags + "(?:\\s[^>]*|/)?>|</" + stripTags + "\\s*>";
			}
		}
		str = str.replace(new RegExp(exp, "ig"), "");
		var res = document.implementation.createDocument(null, "html", null);
		var range = document.createRange();
		range.setStartAfter(window.content.document.body);
		res.documentElement.appendChild(res.importNode(range.createContextualFragment(str), true));
		if (ignoreTags) ignoreTags.forEach(function(tag) {
			var elements = res.getElementsByTagName(tag);
			for (var i = elements.length, el; el = elements.item(--i); el.parentNode.removeChild(el));
		});
		return res;
	}
	function getElementsByXPath(xpath, node){
		node = node || document;
		var nodesSnapshot = (node.ownerDocument || node).evaluate(xpath, node, null,
				XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		var data = [];
		for(var i = 0, l = nodesSnapshot.snapshotLength; i < l;
				data.pushvar PLUGIN_INFO =
<VimperatorPlugin>
<name>{NAME}</name>
<description>Vimperator plugin for Greasemonkey</description>
<author mail="teramako@gmail.com" homepage="http://d.hatena.ne.jp/teramako/">teramako</author>
<version>0.6c</version>
<minVersion>2.0pre</minVersion>
<maxVersion>2.0pre</maxVersion>
<updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/gmperator.js</updateURL>
<detail><![CDATA[

== Command ==

=== gmlist ===
:gmli[st] {filter}:
    show user scripts matches {filter}
:gmli[st]!:
    show all user scripts
:gmli[st] full:
    same as :gmli[st]!

=== gmload ===
:gmlo[ad] {name|filename}:
    load the user script to the current page
    but, do not dispatch load event
    so maybe you should edit the scripts before load
:gmlo[ad]! {name|filename}:
    force load the user script

=== gmset ===
:gmset!:
    toggle enable/disable Greasemonkey
:gmset! {filename}:
    toggle enable/disable the script
:gmset {filename} {options}:
    change the {filename} script attributes.
    {options}:
        -n[name] {value}:  change name to {value}
        -i[nclude] {expr[,expr,...]}: change includes to expr list ("," demiliter)
        -e[xclude] {expr[,expr,...]}: change excludes to expr list ("," demiliter)
    Caution:
        This change is permanent, not only the session.
        And cannot get back.


==== example ====
    :gmset! {filename} -n fooScriptName -i http://*,https://* -e http://example.com/*:
         - toggle enable or disable,
         - name to "fooScriptName",
         - includes to "http://*" and "https://*",
         - and excludes to "http://example.com/*"


=== gmcommand ===
:gmcommand {command name}:
    run Greasemonkey Command

== AutoCommand ==

Available events

GMInjectedScript:
    when open either foreground or background
GMActiveScript:
    when TabSelect or open foreground
    example:
        autocmd GMActiveScript scriptName\.user\.js$ :echo "scriptName is executing"
        when any URL and scriptName.user.js is executing


== Dialog ==

:dialog userscriptmanager:
    open Greasemonkey UserScript Manager

== for JavaScripter ==
you can access to the sandbox of Greasemonkey !!!

liberator.plugins.gmperator:
    allItem:
        return object of
            key:
                {panalID}
            value:
                {GmContainer}

        {panelID}:
            @see gBrowser.mTags[].linkedPanel

    currentPanel:
        currentContainer  :
            return the current {GmContainer} object
        currentSandbox    :
            return the current sandbox object
        gmScripts         :
             return array of {userScripts}
                              {userScripts} => (:
                                 - filename   : {String}
                                 - name       : {String}
                                 - namespace  : {String}
                                 - description: {String}
                                 - enabled    : {Boolean}
                                 - includes   : {String[]}
                                 - encludes   : {String[]}
                              )

]]></detail>
</VimperatorPlugin>;

(function(){

const Cc = Components.classes;
const Ci = Components.interfaces;
const gmID = '@greasemonkey.mozdev.org/greasemonkey-service;1';
if (!Cc[gmID]){
    liberator.log('Greasemonkey is not installed',0);
    return;
}

liberator.plugins.gmperator = (function(){ //{{{
    // -----------------------
    // PUBLIC section
    // -----------------------
    // {{{
    var manager = {
        register: function (uri,sandbox,script){
            var panelID = getPanelID(sandbox.window);
            if (!panelID) return;
            var gmCon;
            if (containers[panelID] && containers[panelID].uri == uri){
                gmCon = containers[panelID];
            } else {
                gmCon = new GmContainer(uri,sandbox);
                containers[panelID] = gmCon;
                this.__defineGetter__(panelID,function() gmCon);
                //liberator.log('gmpeartor: Registered: ' + panelID + ' - ' + uri,8);
            }
            gmCon.sandbox = sandbox;
            gmCon.addScript(script);
            gmCon.uri = uri;
            triggerGMEvent('GMInjectedScript',uri,script._filename);
            if (panelID == this.currentPanel){
                triggerGMEvent('GMActiveScript',uri,script._filename);
            }
        },
        get gmScripts() GM_getConfig().scripts,
        get allItem() containers,
        get currentPanel() getBrowser().mCurrentTab.linkedPanel,
        get currentContainer() containers[this.currentPanel] || null,
        get currentSandbox(){
            var id = this.currentPanel;
            return containers[id] ? containers[id].sandbox : null;
        },
        getSandboxFromWindow: function(win){
            for each (let c in containers){
                if (c.sandbox.window == win) return sandbox;
            }
            return null;
        },
        getContainersFromURI: function(uri){
            var list = [];
            for each (let c in containers){
                if (c.uri == uri) list.push(c);
            }
            return list.length > 0 ? list : null;
        }
    };
    // }}}
    // -----------------------
    // PRIVATE section
    // -----------------------
    // {{{
    var containers = {};
    var gmSvc = Cc[gmID].getService().wrappedJSObject;

    function appendCode(target,name,func){
        var original = target[name];
        target[name] = function(){
            var tmp = original.apply(target,arguments);
            func.apply(this,arguments);
            return tmp;
        };
    }
    appendCode(gmSvc,'evalInSandbox',function(code,uri,sandbox,script){
        liberator.plugins.gmperator.register(uri,sandbox,script);
    });
    function getPanelID(win){
        var tabs = getBrowser().mTabs;
        for (let i=0,l=tabs.length; i<l; i++){
            let tab = tabs.item(i);
            if (tab.linkedBrowser.contentWindow == win){
                return tab.linkedPanel;
            }
        }
    }
    function updateGmContainerList(event){
        var t = event.target;
        if (t && t.localName == 'tab' && t.linkedPanel){
            delete containers[t.linkedPanel];
            delete liberator.plugins.gmperator[t.linkedPanel];
        }
    }
    function dispatchGMTabSelect(event){
        var panelID = event.originalTarget.linkedPanel;
        var container;
        if (container = containers[panelID]){
            liberator.log(panelID + '\n' + container.uri +'\n'+ container.scripts.length,8);
            container.scripts.forEach(function(script){
                triggerGMEvent('GMActiveScript',container.uri,script._filename);
            });
        }
    }
    /**
     * trigger autocommand
     * @param {String} name Event name
     * @param {String} uri
     * @param {String} filename script filename
     */
    function triggerGMEvent(name,uri,filename){
        autocommands.trigger(name, {url: uri+'\n'+filename });
        liberator.log('gmpeartor: '+ name + ' ' + uri+'\n'+filename,8);
    }
    getBrowser().mTabContainer.addEventListener('TabClose',updateGmContainerList,false);
    getBrowser().mTabBox.addEventListener('TabSelect',dispatchGMTabSelect,false);

    config.autocommands.push(['GMInjectedScript','Triggered when UserScript is injected']);
    config.autocommands.push(['GMActiveScript','Triggered when location is changed and injected UserScripts are exist']);
    config.dialogs.push(['userscriptmanager','Greasemonkey Manager',function(){GM_openUserScriptManager();}]);
    // }}}
    return manager;
})(); //}}}

// ---------------------------
// User Command
// ---------------------------
commands.addUserCommand(['gmli[st]','lsgm'],'list Greasemonkey scripts', //{{{
    function(args){
        var xml = <></>;
        var scripts = GM_getConfig().scripts;
        var reg;
        if (args.bang || args.string == 'full'){
            reg = new RegExp();
        } else if (args.string){
            reg = new RegExp(args.string,'i');
        }
        if (reg){
            for each (let s in scripts){
                if (reg.test(s.name) || reg.test(s._filename)){
                    xml += scriptToString(s);
                }
            }
        } else {
            let table = <table/>;
            let tr;
            for each (let script in scripts){
                tr = <tr/>;
                if (script.enabled){
                    tr.* += <td style="font-weight:bold;">{script.name}</td>;
                } else {
                    tr.* += <td>{script.name}</td>;
                }
                tr.* += <td>({script._filename})</td>;
                table.* += tr;
            }
            xml += table;
        }
        liberator.echo(xml,true);
        function scriptToString(script){
            var table = <table>
                <caption class="hl-Title" style="text-align:left">{script.name}</caption>
            </table>;
            [['FileName','_filename'],['NameSpace','namespace'],['Description','description'],
             ['Includes','includes'],['Excludes','excludes'],['Enabled','enabled']].forEach(function(prop){
                let tr = <tr>
                    <th style="font-weight:bold;text-align:left;vertical-align:top">{prop[0]}</th>
                </tr>;
                let contents = script[prop[1]];
                if (typeof contents == 'string' || typeof contents == 'boolean'){
                    tr.* += <td>{contents}</td>;
                } else {
                    let td = <td/>;
                    for (let i=0,l=contents.length; i<l; i++){
                        td.* += contents[i];
                        if (contents[i+1]) td.* += <br/>;
                    }
                    tr.* += td;
                }
                table.* += tr;
            });
            return table;
        }
    },{
        bang:true
    }
); //}}}
commands.addUserCommand(['gmlo[ad]'],'load Greasemonkey scripts', //{{{
    function(args){
        if (!args.string){
            liberator.echoerr('Usage: :gmlo[ad][!] {name|filename}');
            return;
        }
        var scripts = GM_getConfig().scripts;
        var script;
        for (let i=0,l=scripts.length; i<l; i++){
            if (scripts[i]._filename == args.string || scripts[i].name == args.string){
                script = scripts[i];
                break;
            }
        }
        if (!script){
            liberator.echoerr('no such a user script');
            return;
        } else if (liberator.plugins.gmperator.currentContainer.hasScript(script._filename) && !args.bang){
            liberator.echoerr(script._filename + ' is already loaded!');
            return;
        } else {
            liberator.echo('loading: ' +script._filename);
        }
        var href,unsafewin;
        try {
            href = buffer.URL;
            unsafewin = window.content.document.defaultView.wrappedJSObject;
            GM_BrowserUI.gmSvc.wrappedJSObject.injectScripts([script],href,unsafewin,window);
        } catch (e){
            liberator.log(e);
            liberator.echoerr(e);
        }
        /*
        // do you have idea how to dispatch load event to only the script ?
        window.setTimeout(function(){
            var loadEvent = document.createEvent('Event');
            loadEvent.initEvent('load',true,true,window.content.document,1);
            window.content.document.dispatchEvent(loadEvent);
        },100);
        */
    },{
        completer: function(context) scriptsCompleter(context.filter,true)
    }
); //}}}
commands.addUserCommand(['gmset'],'change settings for Greasemonkey scripts', //{{{
    function(args){
        if (args.length == 0){
            if (args.bang) GM_setEnabled(!GM_getEnabled()); // toggle enable/disable Greasemonkey
            return;
        }
        var filename = args[0];
        var config = GM_getConfig();
        var script;
        for (let i=0,l=config.scripts.length; i<l; i++){
            if (config.scripts[i]._filename == filename){
                script = config.scripts[i];
                break;
            }
        }
        if (!script) return;
        if (args.bang){ // toggle enable/disable the script if {filename} is exist
            script.enabled = !script.enabled;
        }
        if (args['-name']) script.name = args['-name'];
        if (args['-include']) script.include = args['-include'];
        if (args['-exclude']) script.exclude = args['-exclude'];
        config._save();
    },{
        completer: function(context) scriptsCompleter(context.filter,false),
        options: [
            [['-name','-n'],    commands.OPTION_STRING],
            [['-include','-i'], commands.OPTION_LIST],
            [['-exclude','-e'], commands.OPTION_LIST]
        ],
        bang:true
    }
); //}}}
commands.addUserCommand(['gmcommand','gmcmd'],'run Greasemonkey Command', //{{{
    function(args){
        var commander = GM_BrowserUI.getCommander(content);
        var commandName = args[0];
        for (let i=0,l=commander.menuItems.length; i<l; i++){
            let menuItem = commander.menuItems[i];
            if (menuItem.getAttribute('label') == commandName){
                menuItem._commandFunc();
                return;
            }
        }
        liberator.echoerr(commandName + ' is not defined userscript command.');
    },
    {
        completer: function(context){
            var items = GM_BrowserUI.getCommander(content).menuItems;
            var completions = [];
            var exp = new RegExp(context.filter,'i');
            context.title = ["UserScript's Commands"];
            context.completions = [[items[i].getAttribute('label'),'-'] for (i in items)].filter(function(item){
                return this.test(item[0]);
            },exp);
        },
        argCount: "1"
    }
); //}}}

// ---------------------------
// Utils
// ---------------------------
/** Grasemonkey sandbox container {{{
 * @param {String} uri
 * @param {Sandbox} sandbox
 * @param {Array} scripts
 */
function GmContainer(uri,sandbox){
    this.uri = uri;
    this.sandbox = sandbox;
    this.scripts = [];
}
GmContainer.prototype = {
    addScript : function(script){
        if (!this.hasScript(script)){
            return this.scripts.push(script);
        }
        return false;
    },
    hasScript : function(script){
        var filename;
        switch (typeof script){
            case 'object': filename = script._filename; break;
            case 'string': filename = script; break;
            default: return null;
        }
        return this.scripts.some(function(s) s._filename == filename);
    }
}; // }}}
function scriptsCompleter(filter,flag){ //{{{
    var candidates = [];
    var scripts = GM_getConfig().scripts;
    var isAll = false;
    if (!filter) isAll=true;
    if (flag){
        for each (let s in scripts){
            if (isAll || s.name.toLowerCase().indexOf(filter) == 0 ||
                s._filename.indexOf(filter) == 0)
            {
                candidates.push([s.name,s.description]);
                candidates.push([s._filename,s.description]);
            }
        }
    } else {
        for each (let s in scripts){
            if (isAll || s._filename.indexOf(filter) == 0)
            {
                candidates.push([s._filename,s.description]);
            }
        }
    }
    return [0,candidates];
} //}}}

})();

// vim: fdm=marker sw=4 ts=4 et: