Greasemonkey Shell

By DavidJCobb Last update Apr 4, 2009 — Installed 1,292 times.

There are 2 previous versions of this script.

// ==UserScript==
// @name           Greasemonkey Shell
// @namespace      http://davidjcobb.deviantart.com/
// @description    Allows you to run code from a Greasemonkey sandbox.
// @include        *
// ==/UserScript==

(function() { // anon function so the shell's command line can't mess with the shell itself

var CSS=""+
"body{padding:0;margin:0;border:0}\n"+
"#DJC-GM-GMShell{position:fixed;z-index:999;border:3px solid #000;-moz-border-radius:15px;background:rgb(180,120,0) none;color:#FFF0CF;width:600px;text-align:center;font-family:tahoma,arial,sans-serif}\n"+
"   #DRAGGER{position:absolute;right:19px;top:6px}\n"+
"   #DJC-GM-GMShell>h1{margin:5px 0 0 0;font-weight:normal;border:0;margin:0}\n"+
"   #DJC-GM-GMShell>p{margin:5px 10px 1em 5px}\n"+
"   #DJC-GM-GMShell>div{width:564px;margin:0 auto}\n"+
"      #DJC-GM-GMShell>div>div:first-child{float:left;text-align:left;width:280px;height:6em;background:#FFE1A2;border:1px solid #000;color:#000;overflow-y:auto}\n"+
"         #DJC-GM-GMShell>div>div:first-child>div{display:inline-block;min-width:266px;border:0 solid #666;border-width:1px 0;padding:0 7px;background:#FFF0CF}/*history item*/\n"+
"            #DJC-GM-GMShell>div>div:first-child>div>pre:first-child{font-weight:bold;color:#04F;margin:0}/*history input*/\n"+
"            #DJC-GM-GMShell>div>div:first-child>div>pre:first-child+pre{margin:0}/*history output*/\n"+
"            #DJC-GM-GMShell>div>div:first-child>div>pre:first-child+pre.error{white-space:pre-wrap;color:#F00}/*history output (errors)*/\n"+
"      #DJC-GM-GMShell>div>div:first-child+div{float:left;width:282px}\n"+
"         #DJC-GM-GMShell>div>div:first-child+div>textarea{-moz-user-modify:read-write!important;font-size:0.8125em;width:281px;height:76px;background:#FFF0CF;border:1px solid #000;border-width:1px 1px 1px 0;margin:0;padding:0}\n"+
"         #DJC-GM-GMShell>div>div:first-child+div>ul{display:block;list-style:none;padding:0;margin:0}\n"+
"            #DJC-GM-GMShell>div>div:first-child+div>ul>li{cursor:pointer;background:#FFE1A2;border:1px solid #000;height:21px;border-width:0 1px 1px 0;margin:0;padding:0;float:left;color:#000}\n"+
"            #DJC-GM-GMShell>div>div:first-child+div>ul>li.DIS{color:#653;cursor:default}\n"+
"   #DJC-GM-GMShell>input{margin:1em 0;padding:0 6px;background:#FFE1A2;border:1px solid #000;-moz-border-radius:8px;border-color:#5F3E00 #5F3E00 #000 #5F3E00;font-family:verdana,arial,sans-serif}";

var Base64_DragIcon="data:image/gif;base64,R0lGODlhHwAfAPcAAAQCBLR2BIRWBEQuBJRmBPzipEQuDKRuBLR6BJxmBEQyDAAAAAALMAAA6xUAEgAAABECAQAAAAAAAAAAAAACHwIAAAAAAAAAAAAAHwMAAAAAAAAAAEAFwOcA9hIAEgAAAOkVH+UAAIEAAHwAAACQAAABAAEAAAAAAFabAABgAADxAAB3AEjEVubAABL9AQB/AXMAAADwAAD9AAB/AGgATOcA6RIAEgAAABgAb+4ALpAA13wAWnB5VgV6AJHxAXx3Af+IAP96AP/xAP93AG1W/wUA/5EB/3wB/4UAAecAnoEAgHwAfAAFVgAAABUAAQAAAWATXwMAAAAAAAAAAMgQBvPpABkSAAAAAMBsoGXqABUSAAAAAABAFgAHAADhAAAAAH4AAAAAAAAAAMAAAADAAADoAAASAAAAAP86Af/hAP9BAP9+AP9WAP8AAP8BAP8BAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAB/AADdABVBAAB+AHgAAOcAABIAAAAAANJWAOYAAIEBAHwBAMBfAGUAABUAAAAAAEoGB+MAAIEAAHwAAGCgANoAAFAAAAAAAMAWAGUAAAEAFQAAAGwoAAAAAAAAAAAAALTDAOYcABIAAAAAAACQAADoAAASAAAAAAiFAPwrABKDAAB8ABgAOO4AAJAAvnwAAHAAAAUAAJEAAHwAAP8AAP8AAP8AAP8AAG0pagW3AJGSAHx8AEoYavQAAIAAAHwAAAA0hABk7hWDEgB8AAAA/wAA/wAA/wAA/8AAAGUAABUAAAAAAAB0vAHo6QASEgAAAAA0BgBknQCDTAB8AFcInPT86YASEnwAAKAYd+jucBKQTwB8AMAA0GW36RWSEgB8AEj/NH7/ZPH/g3f/fCAYukAA6/UAEncAAAA0lABk/wCD/wB8f5S4UITp6/ESEncAAAHnAABkAACDAAB8AByINOhkZBKDgwB8fBQAAOAAAEEAAH4AAFYwwwAAHAEAAAEAAMgAPOcAABIAvgAAAFgfgeoARRIASAAAACH5BAAAAAAALAAAAAAfAB8ABwjXABEIHEhwYAABAAQEKMiwYcODAAokXOiwIkOEBTImtMgRAcSMICd2dIgRZEgBIxsiBMBSIsuNKR0eiAjgQEyOMyXavFkxZ02ePWnuTHkQJUOfQwkKUHiRZUOkDVkaFfgRwFOhUSUy/ajzgNevCWgm+Eo2olauLl+qFVBgpdqXJ82anEu37tyEct+ubasXrkaFJQ2Q/UpA7GCvZkV+VHBVZ1aRAxEOaPyToVSGRSknjcw0JlSgmkGHFl3wM2m3NF9O5Vny7uqbaP9SFN0aJmmPGCHfxq3bYkAAOw==";

var DJC={}; // replacement for window

EV=DJC.EV=
{
   node:null,
   cb:{},
   scheduled_for_add:[],
   init:
      function(node) {
         if(!node)return;
         this.node=node;
         while(this.scheduled_for_add.length) {
            this.addEventListener.apply(this,this.scheduled_for_add[0]);
            this.scheduled_for_add.splice(0,1);
         }
         this.init=function(){};
      },
   EL:
      function(e) {
         var E=e.type.toLowerCase().replace(/^on/i,""),i=0,n=e.target,X,Y=0,x,c;
         if(!DJC.EV.cb[E])return;
         for(;i<DJC.EV.cb[E].length;i++) {
            if(DJC.EV.cb[E][i][0]==n) return DJC.EV.cb[E][i][1].call(n,e)
         }// no callbacks found
      },
   addEventListener:
      function(n,E,f) {
         if(!n+!f)return !1;
         if(!this.node) {
            this.scheduled_for_add.push([n,E,f]);
         }
         if(!this.cb[E]){this.cb[E]=[];this.node.addEventListener(E,function(e){DJC.EV.EL(e)},!0)}
         this.cb[E].push([n,f]);
         return !0;
      },
   removeEventListener:
      function(n,E,f) {
         if(!n+!E+!f+!this.cb[E])return;
         for(var i=0;i<this.cb[E].length;i++) {
            if(this.cb[E][i][0]==n&&this.cb[E][i][1]==f)return !(this.cb[E].splice(i,1))||undefined;
         }
         if(this.cb[E].length) {
            delete this.cb[E];
            this.node.removeEventListener(E,function(e){DJC.EV.EL(e)},!0);
         }
      }
};

var Trigger={key:Math.floor(Math.random()*Number.MAX_VALUE)};
// code in the textarea runs at GM-level privs -- we don't want page 
// scripts modding the TA's value and faking a click event.
// this tells us if the event handler itself is real. Telling whether 
// or not the click event is real is done in the event handler -- 
// see below.

DJC.GUI=
{
   $:
      {
         updateIframe:
            function() {
               DJC.iframe.style.width=DJC.node.offsetWidth+"px";
               DJC.iframe.style.height=DJC.node.offsetHeight+"px";
               DJC.iframe.style.marginLeft=(-(DJC.iframe.offsetWidth/2)+DJC.Dragger.offsetX)+"px";
               DJC.iframe.style.marginTop=(-(DJC.iframe.offsetHeight/2)+DJC.Dragger.offsetY)+"px";
            }
      },
   Run:
      function(T) {
         var C,R,P,ERROR;
         if(T===Trigger){
            C=DJC.node.getElementsByTagName("textarea")[0].value;
            if(!C.replace(/\s/g,""))return;

            try{r=R=eval(C)}catch(e){r=R="Error: "+e.message+((e.line)?"\nLine: "+e.line:"");ERROR=true;}
            try{if(!ERROR)R=uneval(R)}catch(e){R="undefined"}
            if(R==="(void 0)")R="undefined";
            if(R==="{}")R=r.toString();
            if(R==="null"&&r!=null)R=r.toString(); // fix erroneous "null"s, which happen with uneval(any XPCNativeWrapper).

            P=document.createElement("div");
            P.appendChild(document.createElement("pre")).innerHTML=C;
            P.appendChild(document.createElement("pre")).innerHTML=R.replace(/</g,"&lt;");
            if(ERROR)P.childNodes[1].className="error";

            DJC.resultNode.appendChild(P);
         }
      },
   Clear:
      function(T) {
         if(T===Trigger) {
            while(DJC.resultNode.firstChild) {
               DJC.resultNode.removeChild(DJC.resultNode.firstChild);
            }
         }
      },
   Enlarge:
      function(T) {
         if(T===Trigger) {
            var TA=DJC.node.getElementsByTagName("textarea")[0],RN=DJC.resultNode,
                curTAHt=Number(DJC.iframe.contentWindow.getComputedStyle(TA,"").getPropertyValue("height").replace(/\D/g,"")),
                curRNHt=Number(DJC.iframe.contentWindow.getComputedStyle(RN,"").getPropertyValue("height").replace(/\D/g,"")),
                EngBtn=DJC.node.getElementsByTagName("li")[2],ShrBtn=DJC.node.getElementsByTagName("li")[3];
            if(curTAHt<=124) {
               TA.style.height=(curTAHt+22)+"px";
               RN.style.height=(curRNHt+20)+"px";
            }
            EngBtn.className=(curTAHt<=104)?"":"DIS";
            ShrBtn.className=(curTAHt>=74)?"":"DIS";
            DJC.GUI.$.updateIframe();
         }
      },
   Shrink:
      function(T) {
         if(T===Trigger) {
            var TA=DJC.node.getElementsByTagName("textarea")[0],RN=DJC.resultNode,
                curTAHt=Number(DJC.iframe.contentWindow.getComputedStyle(TA,"").getPropertyValue("height").replace(/\D/g,"")),
                curRNHt=Number(DJC.iframe.contentWindow.getComputedStyle(RN,"").getPropertyValue("height").replace(/\D/g,""))
                EngBtn=DJC.node.getElementsByTagName("li")[2],ShrBtn=DJC.node.getElementsByTagName("li")[3];
            if(curTAHt>=94) {
               TA.style.height=(curTAHt-18)+"px";
               RN.style.height=(curRNHt-20)+"px";
            }
            EngBtn.className=(curTAHt<=144)?"":"DIS";
            ShrBtn.className=(curTAHt>94)?"":"DIS";
            DJC.GUI.$.updateIframe();
         }
      }
};

DJC.killConsole=
function(){
   DJC.iframe.style.display="none";
};

DJC.Dragger=
{
   event_mousedown:
      function(E) {
         DJC.Dragger.start(E);
         E.preventDefault();
         return false;
      },
   start:
      function(E) {
         DJC.iframe.contentDocument.body.addEventListener("mousemove",DJC.Dragger.move,false);
         document.body.addEventListener("mousemove",DJC.Dragger.move,false);
         DJC.iframe.contentDocument.getElementById("DRAGGER").style.cursor="move";
         DJC.Dragger.dragStartX=E.screenX;
         DJC.Dragger.dragStartY=E.screenY;
         DJC.Dragger.originalX=DJC.Dragger.offsetX;
         DJC.Dragger.originalY=DJC.Dragger.offsetY;
      },
   cancel:
      function() {
         DJC.clearTimeout(DJC.Dragger.startTimeout);
      },
   move:
      function(E) {
         if(DJC.Dragger.dragStartX==-1)return;
         var WLimit=(window.innerWidth-DJC.iframe.offsetWidth)/2,HLimit=(window.innerHeight-DJC.iframe.offsetHeight)/2;
         DJC.Dragger.offsetX=Math.max(-WLimit,Math.min(E.screenX-DJC.Dragger.dragStartX+DJC.Dragger.originalX,WLimit));
         DJC.Dragger.offsetY=Math.max(-HLimit,Math.min(E.screenY-DJC.Dragger.dragStartY+DJC.Dragger.originalY,HLimit));
         DJC.Dragger._updateIframePosition();
      },
   stop:
      function() {
         if(DJC.Dragger.dragStartX==-1)return;
         DJC.iframe.contentDocument.body.removeEventListener("mousemove",DJC.Dragger.move,false);
         document.body.removeEventListener("mousemove",DJC.Dragger.move,false);
         DJC.iframe.contentDocument.getElementById("DRAGGER").style.cursor="default";
         DJC.Dragger.dragStartX=DJC.Dragger.dragStartY=-1;
      },
   _updateIframePosition:
      function() {
         DJC.iframe.style.marginLeft=(-(DJC.iframe.offsetWidth/2)+DJC.Dragger.offsetX)+"px";
         DJC.iframe.style.marginTop=(-(DJC.iframe.offsetHeight/2)+DJC.Dragger.offsetY)+"px";
      },
   offsetX:0,
   offsetY:0,
   dragStartX:-1,
   dragStartY:-1,
   originalX:-1,
   originalY:-1,
   startTimeout:null
};

a=document.createElement("iframe");
a.style.position="fixed";a.style.top=a.style.left="50%";a.style.width="600px";a.style.zIndex=9999;a.style.border="none";a.style.display="none";
DJC.iframe=a;
DJC.node=null;
DJC.iframe.addEventListener("load",
   function() {
      DJC.iframe.contentDocument.body.appendChild(document.createElement("style")).innerHTML=CSS;
      DJC.node=DJC.iframe.contentDocument.createElement("div");
      DJC.node.innerHTML='<div>\n   <img src="'+Base64_DragIcon+'" id="DRAGGER" ondragstart="return false" onselectstart="return false">\n   <h1>Greasemonkey Shell</h1>\n   <p>Please enter the code you would like to run \n   from a Greasemonkey sandbox.</p>\n   <div>\n      <div></div>\n      <div>\n         <textarea></textarea>\n         <ul>\n            <li style="width:70px">Run</li>\n            <li style="width:69px">Clear</li>\n            <li style="width:69px">Enlarge</li>\n            <li style="width:69px">Shrink</li>\n         </ul>\n      </div>\n      <div style="clear:both"></div>\n   </div>\n   <input type="button" value="Close Shell">\n</div>';
      DJC.node=DJC.node.firstChild;
      DJC.node.id="DJC-GM-GMShell";
      DJC.node.parentNode.removeChild(DJC.node);
      DJC.iframe.contentDocument.body.appendChild(DJC.node);
      DJC.resultNode=DJC.node.getElementsByTagName("div")[0].getElementsByTagName("div")[0];

      EV.init(DJC.iframe.contentDocument);

      EV.addEventListener(DJC.node.getElementsByTagName("li")[0],"click",
         function(e){
            if(e.layerX)DJC.GUI.Run(Trigger) // "faked" events can't set layerX or layerY in FF3 or below.
         }
      );

      EV.addEventListener(DJC.node.getElementsByTagName("li")[1],"click",
         function(e){
            if(e.layerX&&DJC.resultNode.firstChild&&confirm("Clear all of the output from the GM Shell?"))DJC.GUI.Clear(Trigger) // "faked" events can't set layerX or layerY in FF3 or below.
         }
      );

      EV.addEventListener(DJC.node.getElementsByTagName("li")[2],"click",
         function(e){
            if(e.layerX&&this.className.indexOf("DIS")<0)DJC.GUI.Enlarge(Trigger) // "faked" events can't set layerX or layerY in FF3 or below.
         }
      );

      EV.addEventListener(DJC.node.getElementsByTagName("li")[3],"click",
         function(e){
            if(e.layerX&&this.className.indexOf("DIS")<0)DJC.GUI.Shrink(Trigger) // "faked" events can't set layerX or layerY in FF3 or below.
         }
      );

      EV.addEventListener(DJC.iframe.contentDocument.getElementById("DRAGGER").wrappedJSObject,"mousedown",DJC.Dragger.event_mousedown);
      DJC.iframe.contentDocument.addEventListener("mouseup",DJC.Dragger.stop,true);
      document.addEventListener("mouseup",DJC.Dragger.stop,true);

      EV.addEventListener(DJC.node.getElementsByTagName("input")[0],"click",DJC.killConsole);

    },false);

document.body.appendChild(DJC.iframe);

DJC.invoke=
function(){
   DJC.iframe.style.display="";
   if(!DJC.node)DJC.node=DJC.iframe.contentDocument.getElementById("DJC-GM-GMShell");
   DJC.GUI.$.updateIframe();
};

GM_registerMenuCommand("Open Greasemonkey Shell",DJC.invoke);

})();