Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
michael@0 | 2 | |
michael@0 | 3 | <html onclick="keepFocusInTextbox(event)"> |
michael@0 | 4 | <head> |
michael@0 | 5 | <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> |
michael@0 | 6 | <title>JavaScript Shell 1.4</title> |
michael@0 | 7 | |
michael@0 | 8 | <script type="text/javascript"> |
michael@0 | 9 | var |
michael@0 | 10 | histList = [""], |
michael@0 | 11 | histPos = 0, |
michael@0 | 12 | _scope = {}, |
michael@0 | 13 | _win, // a top-level context |
michael@0 | 14 | question, // {String} the input command that's being evaluated. Accessed via |
michael@0 | 15 | // |Shell.question| from the target window for evaluation. |
michael@0 | 16 | _in, // {HTMLTextAreaElement} the textarea containing the input |
michael@0 | 17 | _out, // {HTMLDivElement} the output is printed to this element |
michael@0 | 18 | tooManyMatches = null, |
michael@0 | 19 | lastError = null, |
michael@0 | 20 | _jsVer = 0 // determines the way to execute the commands, see run() |
michael@0 | 21 | ; |
michael@0 | 22 | |
michael@0 | 23 | function refocus() |
michael@0 | 24 | { |
michael@0 | 25 | _in.blur(); // Needed for Mozilla to scroll correctly. |
michael@0 | 26 | _in.focus(); |
michael@0 | 27 | } |
michael@0 | 28 | |
michael@0 | 29 | function init() |
michael@0 | 30 | { |
michael@0 | 31 | _in = document.getElementById("input"); |
michael@0 | 32 | _out = document.getElementById("output"); |
michael@0 | 33 | |
michael@0 | 34 | _win = window; |
michael@0 | 35 | |
michael@0 | 36 | if (opener && !opener.closed) |
michael@0 | 37 | { |
michael@0 | 38 | println("Using bookmarklet version of shell: commands will run in opener's context.", "message"); |
michael@0 | 39 | _win = opener; |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | /* Run a series of (potentially async, but quick) tests to determine the |
michael@0 | 43 | * way to run code in this browser (for run()). Sets window._jsVer based |
michael@0 | 44 | * on the tests. */ |
michael@0 | 45 | _jsVer = 0; |
michael@0 | 46 | for (var jsVerToTry=19; jsVerToTry>=15; jsVerToTry--) { |
michael@0 | 47 | run(window, "if(_jsVer < " + jsVerToTry + ") { " + |
michael@0 | 48 | "_jsVer=" + jsVerToTry + "; }", jsVerToTry); |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | initTarget(); |
michael@0 | 52 | |
michael@0 | 53 | recalculateInputHeight(); |
michael@0 | 54 | refocus(); |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | /** |
michael@0 | 58 | * Runs |code| in |_win|'s context. |
michael@0 | 59 | * @param overridenJSVer {int} (optional) overrides the default (specified by _jsVer) |
michael@0 | 60 | * way to run the code. |
michael@0 | 61 | */ |
michael@0 | 62 | function run(_win, code, overridenJSVer) { |
michael@0 | 63 | var jsVerToUse = overridenJSVer ? overridenJSVer : _jsVer; |
michael@0 | 64 | if (jsVerToUse <= 15) { |
michael@0 | 65 | _win.location.href = "javascript:" + code + "; void 0"; |
michael@0 | 66 | } else { |
michael@0 | 67 | var sc = _win.document.createElement("script"); |
michael@0 | 68 | sc.type="application/javascript;version=" + jsVerToUse/10; |
michael@0 | 69 | sc.src="data:application/x-javascript," + code; |
michael@0 | 70 | _win.document.body.appendChild(sc); // runs the script asynchronously |
michael@0 | 71 | } |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | function initTarget() |
michael@0 | 75 | { |
michael@0 | 76 | _win.Shell = window; |
michael@0 | 77 | _win.print = shellCommands.print; |
michael@0 | 78 | } |
michael@0 | 79 | |
michael@0 | 80 | |
michael@0 | 81 | // Unless the user is selected something, refocus the textbox. |
michael@0 | 82 | // (requested by caillon, brendan, asa) |
michael@0 | 83 | function keepFocusInTextbox(e) |
michael@0 | 84 | { |
michael@0 | 85 | var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard |
michael@0 | 86 | |
michael@0 | 87 | while (!g.tagName) |
michael@0 | 88 | g = g.parentNode; |
michael@0 | 89 | var t = g.tagName.toUpperCase(); |
michael@0 | 90 | if (t=="A" || t=="INPUT") |
michael@0 | 91 | return; |
michael@0 | 92 | |
michael@0 | 93 | if (window.getSelection) { |
michael@0 | 94 | // Mozilla |
michael@0 | 95 | if (String(window.getSelection())) |
michael@0 | 96 | return; |
michael@0 | 97 | } |
michael@0 | 98 | else { |
michael@0 | 99 | // IE |
michael@0 | 100 | if ( document.selection.createRange().text ) |
michael@0 | 101 | return; |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | refocus(); |
michael@0 | 105 | } |
michael@0 | 106 | |
michael@0 | 107 | function inputKeydown(e) { |
michael@0 | 108 | // Use onkeydown because IE doesn't support onkeypress for arrow keys |
michael@0 | 109 | |
michael@0 | 110 | //alert(e.keyCode + " ^ " + e.keycode); |
michael@0 | 111 | |
michael@0 | 112 | if (e.shiftKey && e.keyCode == 13) { // shift-enter |
michael@0 | 113 | // don't do anything; allow the shift-enter to insert a line break as normal |
michael@0 | 114 | } else if (e.keyCode == 13) { // enter |
michael@0 | 115 | // execute the input on enter |
michael@0 | 116 | try { go(); } catch(er) { alert(er); }; |
michael@0 | 117 | setTimeout(function() { _in.value = ""; }, 0); // can't preventDefault on input, so clear it later |
michael@0 | 118 | } else if (e.keyCode == 38) { // up |
michael@0 | 119 | // go up in history if at top or ctrl-up |
michael@0 | 120 | if (e.ctrlKey || caretInFirstLine(_in)) |
michael@0 | 121 | hist(true); |
michael@0 | 122 | } else if (e.keyCode == 40) { // down |
michael@0 | 123 | // go down in history if at end or ctrl-down |
michael@0 | 124 | if (e.ctrlKey || caretInLastLine(_in)) |
michael@0 | 125 | hist(false); |
michael@0 | 126 | } else if (e.keyCode == 9) { // tab |
michael@0 | 127 | tabcomplete(); |
michael@0 | 128 | setTimeout(function() { refocus(); }, 0); // refocus because tab was hit |
michael@0 | 129 | } else { } |
michael@0 | 130 | |
michael@0 | 131 | setTimeout(recalculateInputHeight, 0); |
michael@0 | 132 | |
michael@0 | 133 | //return true; |
michael@0 | 134 | }; |
michael@0 | 135 | |
michael@0 | 136 | function caretInFirstLine(textbox) |
michael@0 | 137 | { |
michael@0 | 138 | // IE doesn't support selectionStart/selectionEnd |
michael@0 | 139 | if (textbox.selectionStart == undefined) |
michael@0 | 140 | return true; |
michael@0 | 141 | |
michael@0 | 142 | var firstLineBreak = textbox.value.indexOf("\n"); |
michael@0 | 143 | |
michael@0 | 144 | return ((firstLineBreak == -1) || (textbox.selectionStart <= firstLineBreak)); |
michael@0 | 145 | } |
michael@0 | 146 | |
michael@0 | 147 | function caretInLastLine(textbox) |
michael@0 | 148 | { |
michael@0 | 149 | // IE doesn't support selectionStart/selectionEnd |
michael@0 | 150 | if (textbox.selectionEnd == undefined) |
michael@0 | 151 | return true; |
michael@0 | 152 | |
michael@0 | 153 | var lastLineBreak = textbox.value.lastIndexOf("\n"); |
michael@0 | 154 | |
michael@0 | 155 | return (textbox.selectionEnd > lastLineBreak); |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | function recalculateInputHeight() |
michael@0 | 159 | { |
michael@0 | 160 | var rows = _in.value.split(/\n/).length |
michael@0 | 161 | + 1 // prevent scrollbar flickering in Mozilla |
michael@0 | 162 | + (window.opera ? 1 : 0); // leave room for scrollbar in Opera |
michael@0 | 163 | |
michael@0 | 164 | if (_in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0. |
michael@0 | 165 | _in.rows = rows; |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | function println(s, type) |
michael@0 | 169 | { |
michael@0 | 170 | if((s=String(s))) |
michael@0 | 171 | { |
michael@0 | 172 | var newdiv = document.createElement("div"); |
michael@0 | 173 | newdiv.appendChild(document.createTextNode(s)); |
michael@0 | 174 | newdiv.className = type; |
michael@0 | 175 | _out.appendChild(newdiv); |
michael@0 | 176 | return newdiv; |
michael@0 | 177 | } |
michael@0 | 178 | return false; |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | function printWithRunin(h, s, type) |
michael@0 | 182 | { |
michael@0 | 183 | var div = println(s, type); |
michael@0 | 184 | if (div) { |
michael@0 | 185 | var head = document.createElement("strong"); |
michael@0 | 186 | head.appendChild(document.createTextNode(h + ": ")); |
michael@0 | 187 | div.insertBefore(head, div.firstChild); |
michael@0 | 188 | } |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | |
michael@0 | 192 | var shellCommands = |
michael@0 | 193 | { |
michael@0 | 194 | load : function load(url) |
michael@0 | 195 | { |
michael@0 | 196 | var s = _win.document.createElement("script"); |
michael@0 | 197 | s.type = "text/javascript"; |
michael@0 | 198 | s.src = url; |
michael@0 | 199 | _win.document.getElementsByTagName("head")[0].appendChild(s); |
michael@0 | 200 | println("Loading " + url + "...", "message"); |
michael@0 | 201 | }, |
michael@0 | 202 | |
michael@0 | 203 | clear : function clear() |
michael@0 | 204 | { |
michael@0 | 205 | var CHILDREN_TO_PRESERVE = 3; |
michael@0 | 206 | while (_out.childNodes[CHILDREN_TO_PRESERVE]) |
michael@0 | 207 | _out.removeChild(_out.childNodes[CHILDREN_TO_PRESERVE]); |
michael@0 | 208 | }, |
michael@0 | 209 | |
michael@0 | 210 | print : function print(s) { println(s, "print"); }, |
michael@0 | 211 | |
michael@0 | 212 | // the normal function, "print", shouldn't return a value |
michael@0 | 213 | // (suggested by brendan; later noticed it was a problem when showing others) |
michael@0 | 214 | pr : function pr(s) |
michael@0 | 215 | { |
michael@0 | 216 | shellCommands.print(s); // need to specify shellCommands so it doesn't try window.print()! |
michael@0 | 217 | return s; |
michael@0 | 218 | }, |
michael@0 | 219 | |
michael@0 | 220 | props : function props(e, onePerLine) |
michael@0 | 221 | { |
michael@0 | 222 | if (e === null) { |
michael@0 | 223 | println("props called with null argument", "error"); |
michael@0 | 224 | return; |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | if (e === undefined) { |
michael@0 | 228 | println("props called with undefined argument", "error"); |
michael@0 | 229 | return; |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | var ns = ["Methods", "Fields", "Unreachables"]; |
michael@0 | 233 | var as = [[], [], []]; // array of (empty) arrays of arrays! |
michael@0 | 234 | var p, j, i; // loop variables, several used multiple times |
michael@0 | 235 | |
michael@0 | 236 | var protoLevels = 0; |
michael@0 | 237 | |
michael@0 | 238 | for (p = e; p; p = p.__proto__) |
michael@0 | 239 | { |
michael@0 | 240 | for (i=0; i<ns.length; ++i) |
michael@0 | 241 | as[i][protoLevels] = []; |
michael@0 | 242 | ++protoLevels; |
michael@0 | 243 | } |
michael@0 | 244 | |
michael@0 | 245 | for(var a in e) |
michael@0 | 246 | { |
michael@0 | 247 | // Shortcoming: doesn't check that VALUES are the same in object and prototype. |
michael@0 | 248 | |
michael@0 | 249 | var protoLevel = -1; |
michael@0 | 250 | try |
michael@0 | 251 | { |
michael@0 | 252 | for (p = e; p && (a in p); p = p.__proto__) |
michael@0 | 253 | ++protoLevel; |
michael@0 | 254 | } |
michael@0 | 255 | catch(er) { protoLevel = 0; } // "in" operator throws when param to props() is a string |
michael@0 | 256 | |
michael@0 | 257 | var type = 1; |
michael@0 | 258 | try |
michael@0 | 259 | { |
michael@0 | 260 | if ((typeof e[a]) == "function") |
michael@0 | 261 | type = 0; |
michael@0 | 262 | } |
michael@0 | 263 | catch (er) { type = 2; } |
michael@0 | 264 | |
michael@0 | 265 | as[type][protoLevel].push(a); |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | function times(s, n) { return n ? s + times(s, n-1) : ""; } |
michael@0 | 269 | |
michael@0 | 270 | for (j=0; j<protoLevels; ++j) |
michael@0 | 271 | for (i=0;i<ns.length;++i) |
michael@0 | 272 | if (as[i][j].length) |
michael@0 | 273 | printWithRunin( |
michael@0 | 274 | ns[i] + times(" of prototype", j), |
michael@0 | 275 | (onePerLine ? "\n\n" : "") + as[i][j].sort().join(onePerLine ? "\n" : ", ") + (onePerLine ? "\n\n" : ""), |
michael@0 | 276 | "propList" |
michael@0 | 277 | ); |
michael@0 | 278 | }, |
michael@0 | 279 | |
michael@0 | 280 | blink : function blink(node) |
michael@0 | 281 | { |
michael@0 | 282 | if (!node) throw("blink: argument is null or undefined."); |
michael@0 | 283 | if (node.nodeType == null) throw("blink: argument must be a node."); |
michael@0 | 284 | if (node.nodeType == 3) throw("blink: argument must not be a text node"); |
michael@0 | 285 | if (node.documentElement) throw("blink: argument must not be the document object"); |
michael@0 | 286 | |
michael@0 | 287 | function setOutline(o) { |
michael@0 | 288 | return function() { |
michael@0 | 289 | if (node.style.outline != node.style.bogusProperty) { |
michael@0 | 290 | // browser supports outline (Firefox 1.1 and newer, CSS3, Opera 8). |
michael@0 | 291 | node.style.outline = o; |
michael@0 | 292 | } |
michael@0 | 293 | else if (node.style.MozOutline != node.style.bogusProperty) { |
michael@0 | 294 | // browser supports MozOutline (Firefox 1.0.x and older) |
michael@0 | 295 | node.style.MozOutline = o; |
michael@0 | 296 | } |
michael@0 | 297 | else { |
michael@0 | 298 | // browser only supports border (IE). border is a fallback because it moves things around. |
michael@0 | 299 | node.style.border = o; |
michael@0 | 300 | } |
michael@0 | 301 | } |
michael@0 | 302 | } |
michael@0 | 303 | |
michael@0 | 304 | function focusIt(a) { |
michael@0 | 305 | return function() { |
michael@0 | 306 | a.focus(); |
michael@0 | 307 | } |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | if (node.ownerDocument) { |
michael@0 | 311 | var windowToFocusNow = (node.ownerDocument.defaultView || node.ownerDocument.parentWindow); // Moz vs. IE |
michael@0 | 312 | if (windowToFocusNow) |
michael@0 | 313 | setTimeout(focusIt(windowToFocusNow.top), 0); |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | for(var i=1;i<7;++i) |
michael@0 | 317 | setTimeout(setOutline((i%2)?'3px solid red':'none'), i*100); |
michael@0 | 318 | |
michael@0 | 319 | setTimeout(focusIt(window), 800); |
michael@0 | 320 | setTimeout(focusIt(_in), 810); |
michael@0 | 321 | }, |
michael@0 | 322 | |
michael@0 | 323 | scope : function scope(sc) |
michael@0 | 324 | { |
michael@0 | 325 | if (!sc) sc = {}; |
michael@0 | 326 | _scope = sc; |
michael@0 | 327 | println("Scope is now " + sc + ". If a variable is not found in this scope, window will also be searched. New variables will still go on window.", "message"); |
michael@0 | 328 | }, |
michael@0 | 329 | |
michael@0 | 330 | mathHelp : function mathHelp() |
michael@0 | 331 | { |
michael@0 | 332 | printWithRunin("Math constants", "E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2", "propList"); |
michael@0 | 333 | printWithRunin("Math methods", "abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan", "propList"); |
michael@0 | 334 | }, |
michael@0 | 335 | |
michael@0 | 336 | ans : undefined |
michael@0 | 337 | }; |
michael@0 | 338 | |
michael@0 | 339 | |
michael@0 | 340 | function hist(up) |
michael@0 | 341 | { |
michael@0 | 342 | // histList[0] = first command entered, [1] = second, etc. |
michael@0 | 343 | // type something, press up --> thing typed is now in "limbo" |
michael@0 | 344 | // (last item in histList) and should be reachable by pressing |
michael@0 | 345 | // down again. |
michael@0 | 346 | |
michael@0 | 347 | var L = histList.length; |
michael@0 | 348 | |
michael@0 | 349 | if (L == 1) |
michael@0 | 350 | return; |
michael@0 | 351 | |
michael@0 | 352 | if (up) |
michael@0 | 353 | { |
michael@0 | 354 | if (histPos == L-1) |
michael@0 | 355 | { |
michael@0 | 356 | // Save this entry in case the user hits the down key. |
michael@0 | 357 | histList[histPos] = _in.value; |
michael@0 | 358 | } |
michael@0 | 359 | |
michael@0 | 360 | if (histPos > 0) |
michael@0 | 361 | { |
michael@0 | 362 | histPos--; |
michael@0 | 363 | // Use a timeout to prevent up from moving cursor within new text |
michael@0 | 364 | // Set to nothing first for the same reason |
michael@0 | 365 | setTimeout( |
michael@0 | 366 | function() { |
michael@0 | 367 | _in.value = ''; |
michael@0 | 368 | _in.value = histList[histPos]; |
michael@0 | 369 | var caretPos = _in.value.length; |
michael@0 | 370 | if (_in.setSelectionRange) |
michael@0 | 371 | _in.setSelectionRange(caretPos, caretPos); |
michael@0 | 372 | }, |
michael@0 | 373 | 0 |
michael@0 | 374 | ); |
michael@0 | 375 | } |
michael@0 | 376 | } |
michael@0 | 377 | else // down |
michael@0 | 378 | { |
michael@0 | 379 | if (histPos < L-1) |
michael@0 | 380 | { |
michael@0 | 381 | histPos++; |
michael@0 | 382 | _in.value = histList[histPos]; |
michael@0 | 383 | } |
michael@0 | 384 | else if (histPos == L-1) |
michael@0 | 385 | { |
michael@0 | 386 | // Already on the current entry: clear but save |
michael@0 | 387 | if (_in.value) |
michael@0 | 388 | { |
michael@0 | 389 | histList[histPos] = _in.value; |
michael@0 | 390 | ++histPos; |
michael@0 | 391 | _in.value = ""; |
michael@0 | 392 | } |
michael@0 | 393 | } |
michael@0 | 394 | } |
michael@0 | 395 | } |
michael@0 | 396 | |
michael@0 | 397 | function tabcomplete() |
michael@0 | 398 | { |
michael@0 | 399 | /* |
michael@0 | 400 | * Working backwards from s[from], find the spot |
michael@0 | 401 | * where this expression starts. It will scan |
michael@0 | 402 | * until it hits a mismatched ( or a space, |
michael@0 | 403 | * but it skips over quoted strings. |
michael@0 | 404 | * If stopAtDot is true, stop at a '.' |
michael@0 | 405 | */ |
michael@0 | 406 | function findbeginning(s, from, stopAtDot) |
michael@0 | 407 | { |
michael@0 | 408 | /* |
michael@0 | 409 | * Complicated function. |
michael@0 | 410 | * |
michael@0 | 411 | * Return true if s[i] == q BUT ONLY IF |
michael@0 | 412 | * s[i-1] is not a backslash. |
michael@0 | 413 | */ |
michael@0 | 414 | function equalButNotEscaped(s,i,q) |
michael@0 | 415 | { |
michael@0 | 416 | if(s.charAt(i) != q) // not equal go no further |
michael@0 | 417 | return false; |
michael@0 | 418 | |
michael@0 | 419 | if(i==0) // beginning of string |
michael@0 | 420 | return true; |
michael@0 | 421 | |
michael@0 | 422 | if(s.charAt(i-1) == '\\') // escaped? |
michael@0 | 423 | return false; |
michael@0 | 424 | |
michael@0 | 425 | return true; |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | var nparens = 0; |
michael@0 | 429 | var i; |
michael@0 | 430 | for(i=from; i>=0; i--) |
michael@0 | 431 | { |
michael@0 | 432 | if(s.charAt(i) == ' ') |
michael@0 | 433 | break; |
michael@0 | 434 | |
michael@0 | 435 | if(stopAtDot && s.charAt(i) == '.') |
michael@0 | 436 | break; |
michael@0 | 437 | |
michael@0 | 438 | if(s.charAt(i) == ')') |
michael@0 | 439 | nparens++; |
michael@0 | 440 | else if(s.charAt(i) == '(') |
michael@0 | 441 | nparens--; |
michael@0 | 442 | |
michael@0 | 443 | if(nparens < 0) |
michael@0 | 444 | break; |
michael@0 | 445 | |
michael@0 | 446 | // skip quoted strings |
michael@0 | 447 | if(s.charAt(i) == '\'' || s.charAt(i) == '\"') |
michael@0 | 448 | { |
michael@0 | 449 | //dump("skipping quoted chars: "); |
michael@0 | 450 | var quot = s.charAt(i); |
michael@0 | 451 | i--; |
michael@0 | 452 | while(i >= 0 && !equalButNotEscaped(s,i,quot)) { |
michael@0 | 453 | //dump(s.charAt(i)); |
michael@0 | 454 | i--; |
michael@0 | 455 | } |
michael@0 | 456 | //dump("\n"); |
michael@0 | 457 | } |
michael@0 | 458 | } |
michael@0 | 459 | return i; |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | // XXX should be used more consistently (instead of using selectionStart/selectionEnd throughout code) |
michael@0 | 463 | // XXX doesn't work in IE, even though it contains IE-specific code |
michael@0 | 464 | function getcaretpos(inp) |
michael@0 | 465 | { |
michael@0 | 466 | if(inp.selectionEnd != null) |
michael@0 | 467 | return inp.selectionEnd; |
michael@0 | 468 | |
michael@0 | 469 | if(inp.createTextRange) |
michael@0 | 470 | { |
michael@0 | 471 | var docrange = _win.Shell.document.selection.createRange(); |
michael@0 | 472 | var inprange = inp.createTextRange(); |
michael@0 | 473 | if (inprange.setEndPoint) |
michael@0 | 474 | { |
michael@0 | 475 | inprange.setEndPoint('EndToStart', docrange); |
michael@0 | 476 | return inprange.text.length; |
michael@0 | 477 | } |
michael@0 | 478 | } |
michael@0 | 479 | |
michael@0 | 480 | return inp.value.length; // sucks, punt |
michael@0 | 481 | } |
michael@0 | 482 | |
michael@0 | 483 | function setselectionto(inp,pos) |
michael@0 | 484 | { |
michael@0 | 485 | if(inp.selectionStart) { |
michael@0 | 486 | inp.selectionStart = inp.selectionEnd = pos; |
michael@0 | 487 | } |
michael@0 | 488 | else if(inp.createTextRange) { |
michael@0 | 489 | var docrange = _win.Shell.document.selection.createRange(); |
michael@0 | 490 | var inprange = inp.createTextRange(); |
michael@0 | 491 | inprange.move('character',pos); |
michael@0 | 492 | inprange.select(); |
michael@0 | 493 | } |
michael@0 | 494 | else { // err... |
michael@0 | 495 | /* |
michael@0 | 496 | inp.select(); |
michael@0 | 497 | if(_win.Shell.document.getSelection()) |
michael@0 | 498 | _win.Shell.document.getSelection() = ""; |
michael@0 | 499 | */ |
michael@0 | 500 | } |
michael@0 | 501 | } |
michael@0 | 502 | // get position of cursor within the input box |
michael@0 | 503 | var caret = getcaretpos(_in); |
michael@0 | 504 | |
michael@0 | 505 | if(caret) { |
michael@0 | 506 | //dump("----\n"); |
michael@0 | 507 | var dotpos, spacepos, complete, obj; |
michael@0 | 508 | //dump("caret pos: " + caret + "\n"); |
michael@0 | 509 | // see if there's a dot before here |
michael@0 | 510 | dotpos = findbeginning(_in.value, caret-1, true); |
michael@0 | 511 | //dump("dot pos: " + dotpos + "\n"); |
michael@0 | 512 | if(dotpos == -1 || _in.value.charAt(dotpos) != '.') { |
michael@0 | 513 | dotpos = caret; |
michael@0 | 514 | //dump("changed dot pos: " + dotpos + "\n"); |
michael@0 | 515 | } |
michael@0 | 516 | |
michael@0 | 517 | // look backwards for a non-variable-name character |
michael@0 | 518 | spacepos = findbeginning(_in.value, dotpos-1, false); |
michael@0 | 519 | //dump("space pos: " + spacepos + "\n"); |
michael@0 | 520 | // get the object we're trying to complete on |
michael@0 | 521 | if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret) |
michael@0 | 522 | { |
michael@0 | 523 | // try completing function args |
michael@0 | 524 | if(_in.value.charAt(dotpos) == '(' || |
michael@0 | 525 | (_in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos)) |
michael@0 | 526 | { |
michael@0 | 527 | var fn,fname; |
michael@0 | 528 | var from = (_in.value.charAt(dotpos) == '(') ? dotpos : spacepos; |
michael@0 | 529 | spacepos = findbeginning(_in.value, from-1, false); |
michael@0 | 530 | |
michael@0 | 531 | fname = _in.value.substr(spacepos+1,from-(spacepos+1)); |
michael@0 | 532 | //dump("fname: " + fname + "\n"); |
michael@0 | 533 | try { |
michael@0 | 534 | with(_win.Shell._scope) |
michael@0 | 535 | with(_win) |
michael@0 | 536 | with(Shell.shellCommands) |
michael@0 | 537 | fn = eval(fname); |
michael@0 | 538 | } |
michael@0 | 539 | catch(er) { |
michael@0 | 540 | //dump('fn is not a valid object\n'); |
michael@0 | 541 | return; |
michael@0 | 542 | } |
michael@0 | 543 | if(fn == undefined) { |
michael@0 | 544 | //dump('fn is undefined'); |
michael@0 | 545 | return; |
michael@0 | 546 | } |
michael@0 | 547 | if(fn instanceof Function) |
michael@0 | 548 | { |
michael@0 | 549 | // Print function definition, including argument names, but not function body |
michael@0 | 550 | if(!fn.toString().match(/function .+?\(\) +\{\n +\[native code\]\n\}/)) |
michael@0 | 551 | println(fn.toString().match(/function .+?\(.*?\)/), "tabcomplete"); |
michael@0 | 552 | } |
michael@0 | 553 | |
michael@0 | 554 | return; |
michael@0 | 555 | } |
michael@0 | 556 | else |
michael@0 | 557 | obj = _win; |
michael@0 | 558 | } |
michael@0 | 559 | else |
michael@0 | 560 | { |
michael@0 | 561 | var objname = _in.value.substr(spacepos+1,dotpos-(spacepos+1)); |
michael@0 | 562 | //dump("objname: |" + objname + "|\n"); |
michael@0 | 563 | try { |
michael@0 | 564 | with(_win.Shell._scope) |
michael@0 | 565 | with(_win) |
michael@0 | 566 | obj = eval(objname); |
michael@0 | 567 | } |
michael@0 | 568 | catch(er) { |
michael@0 | 569 | printError(er); |
michael@0 | 570 | return; |
michael@0 | 571 | } |
michael@0 | 572 | if(obj == undefined) { |
michael@0 | 573 | // sometimes this is tabcomplete's fault, so don't print it :( |
michael@0 | 574 | // e.g. completing from "print(document.getElements" |
michael@0 | 575 | // println("Can't complete from null or undefined expression " + objname, "error"); |
michael@0 | 576 | return; |
michael@0 | 577 | } |
michael@0 | 578 | } |
michael@0 | 579 | //dump("obj: " + obj + "\n"); |
michael@0 | 580 | // get the thing we're trying to complete |
michael@0 | 581 | if(dotpos == caret) |
michael@0 | 582 | { |
michael@0 | 583 | if(spacepos+1 == dotpos || spacepos == dotpos) |
michael@0 | 584 | { |
michael@0 | 585 | // nothing to complete |
michael@0 | 586 | //dump("nothing to complete\n"); |
michael@0 | 587 | return; |
michael@0 | 588 | } |
michael@0 | 589 | |
michael@0 | 590 | complete = _in.value.substr(spacepos+1,dotpos-(spacepos+1)); |
michael@0 | 591 | } |
michael@0 | 592 | else { |
michael@0 | 593 | complete = _in.value.substr(dotpos+1,caret-(dotpos+1)); |
michael@0 | 594 | } |
michael@0 | 595 | //dump("complete: " + complete + "\n"); |
michael@0 | 596 | // ok, now look at all the props/methods of this obj |
michael@0 | 597 | // and find ones starting with 'complete' |
michael@0 | 598 | var matches = []; |
michael@0 | 599 | var bestmatch = null; |
michael@0 | 600 | for(var a in obj) |
michael@0 | 601 | { |
michael@0 | 602 | //a = a.toString(); |
michael@0 | 603 | //XXX: making it lowercase could help some cases, |
michael@0 | 604 | // but screws up my general logic. |
michael@0 | 605 | if(a.substr(0,complete.length) == complete) { |
michael@0 | 606 | matches.push(a); |
michael@0 | 607 | ////dump("match: " + a + "\n"); |
michael@0 | 608 | // if no best match, this is the best match |
michael@0 | 609 | if(bestmatch == null) |
michael@0 | 610 | { |
michael@0 | 611 | bestmatch = a; |
michael@0 | 612 | } |
michael@0 | 613 | else { |
michael@0 | 614 | // the best match is the longest common string |
michael@0 | 615 | function min(a,b){ return ((a<b)?a:b); } |
michael@0 | 616 | var i; |
michael@0 | 617 | for(i=0; i< min(bestmatch.length, a.length); i++) |
michael@0 | 618 | { |
michael@0 | 619 | if(bestmatch.charAt(i) != a.charAt(i)) |
michael@0 | 620 | break; |
michael@0 | 621 | } |
michael@0 | 622 | bestmatch = bestmatch.substr(0,i); |
michael@0 | 623 | ////dump("bestmatch len: " + i + "\n"); |
michael@0 | 624 | } |
michael@0 | 625 | ////dump("bestmatch: " + bestmatch + "\n"); |
michael@0 | 626 | } |
michael@0 | 627 | } |
michael@0 | 628 | bestmatch = (bestmatch || ""); |
michael@0 | 629 | ////dump("matches: " + matches + "\n"); |
michael@0 | 630 | var objAndComplete = (objname || obj) + "." + bestmatch; |
michael@0 | 631 | //dump("matches.length: " + matches.length + ", tooManyMatches: " + tooManyMatches + ", objAndComplete: " + objAndComplete + "\n"); |
michael@0 | 632 | if(matches.length > 1 && (tooManyMatches == objAndComplete || matches.length <= 10)) { |
michael@0 | 633 | |
michael@0 | 634 | printWithRunin("Matches: ", matches.join(', '), "tabcomplete"); |
michael@0 | 635 | tooManyMatches = null; |
michael@0 | 636 | } |
michael@0 | 637 | else if(matches.length > 10) |
michael@0 | 638 | { |
michael@0 | 639 | println(matches.length + " matches. Press tab again to see them all", "tabcomplete"); |
michael@0 | 640 | tooManyMatches = objAndComplete; |
michael@0 | 641 | } |
michael@0 | 642 | else { |
michael@0 | 643 | tooManyMatches = null; |
michael@0 | 644 | } |
michael@0 | 645 | if(bestmatch != "") |
michael@0 | 646 | { |
michael@0 | 647 | var sstart; |
michael@0 | 648 | if(dotpos == caret) { |
michael@0 | 649 | sstart = spacepos+1; |
michael@0 | 650 | } |
michael@0 | 651 | else { |
michael@0 | 652 | sstart = dotpos+1; |
michael@0 | 653 | } |
michael@0 | 654 | _in.value = _in.value.substr(0, sstart) |
michael@0 | 655 | + bestmatch |
michael@0 | 656 | + _in.value.substr(caret); |
michael@0 | 657 | setselectionto(_in,caret + (bestmatch.length - complete.length)); |
michael@0 | 658 | } |
michael@0 | 659 | } |
michael@0 | 660 | } |
michael@0 | 661 | |
michael@0 | 662 | function printQuestion(q) |
michael@0 | 663 | { |
michael@0 | 664 | println(q, "input"); |
michael@0 | 665 | } |
michael@0 | 666 | |
michael@0 | 667 | function printAnswer(a) |
michael@0 | 668 | { |
michael@0 | 669 | if (a !== undefined) { |
michael@0 | 670 | println(a, "normalOutput"); |
michael@0 | 671 | shellCommands.ans = a; |
michael@0 | 672 | } |
michael@0 | 673 | } |
michael@0 | 674 | |
michael@0 | 675 | function printError(er) |
michael@0 | 676 | { |
michael@0 | 677 | var lineNumberString; |
michael@0 | 678 | |
michael@0 | 679 | lastError = er; // for debugging the shell |
michael@0 | 680 | if (er.name) |
michael@0 | 681 | { |
michael@0 | 682 | // lineNumberString should not be "", to avoid a very wacky bug in IE 6. |
michael@0 | 683 | lineNumberString = (er.lineNumber != undefined) ? (" on line " + er.lineNumber + ": ") : ": "; |
michael@0 | 684 | println(er.name + lineNumberString + er.message, "error"); // Because IE doesn't have error.toString. |
michael@0 | 685 | } |
michael@0 | 686 | else |
michael@0 | 687 | println(er, "error"); // Because security errors in Moz /only/ have toString. |
michael@0 | 688 | } |
michael@0 | 689 | |
michael@0 | 690 | /** |
michael@0 | 691 | * Evaluates |s| or current input (_in.value) in the previously set up context. |
michael@0 | 692 | * @param {String} s - (optional) command to evaluate. |
michael@0 | 693 | */ |
michael@0 | 694 | function go(s) |
michael@0 | 695 | { |
michael@0 | 696 | // save the command to eval in |question|, so that the target window can access |
michael@0 | 697 | // it when evaluating. |
michael@0 | 698 | _in.value = question = s ? s : _in.value; |
michael@0 | 699 | |
michael@0 | 700 | if (question == "") |
michael@0 | 701 | return; |
michael@0 | 702 | |
michael@0 | 703 | histList[histList.length-1] = question; |
michael@0 | 704 | histList[histList.length] = ""; |
michael@0 | 705 | histPos = histList.length - 1; |
michael@0 | 706 | |
michael@0 | 707 | // Unfortunately, this has to happen *before* the JavaScript is run, so that |
michael@0 | 708 | // print() output will go in the right place. |
michael@0 | 709 | _in.value=''; |
michael@0 | 710 | recalculateInputHeight(); |
michael@0 | 711 | printQuestion(question); |
michael@0 | 712 | |
michael@0 | 713 | if (_win.closed) { |
michael@0 | 714 | printError("Target window has been closed."); |
michael@0 | 715 | return; |
michael@0 | 716 | } |
michael@0 | 717 | |
michael@0 | 718 | try { ("Shell" in _win) } |
michael@0 | 719 | catch(er) { |
michael@0 | 720 | printError("The JavaScript Shell cannot access variables in the target window. The most likely reason is that the target window now has a different page loaded and that page has a different hostname than the original page."); |
michael@0 | 721 | return; |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | if (!("Shell" in _win)) |
michael@0 | 725 | initTarget(); // silent |
michael@0 | 726 | |
michael@0 | 727 | // Evaluate Shell.question using _win's eval (this is why eval isn't in the |with|, IIRC). |
michael@0 | 728 | run(_win, "try{ Shell.printAnswer(eval('with(Shell._scope) with(Shell.shellCommands) {' + Shell.question + String.fromCharCode(10) + '}')); } catch(er) { Shell.printError(er); }; setTimeout(Shell.refocus, 0);"); |
michael@0 | 729 | } |
michael@0 | 730 | |
michael@0 | 731 | </script> |
michael@0 | 732 | |
michael@0 | 733 | <!-- for http://ted.mielczarek.org/code/mozilla/extensiondev/ --> |
michael@0 | 734 | <script type="text/javascript" src="chrome://extensiondev/content/chromeShellExtras.js"></script> |
michael@0 | 735 | |
michael@0 | 736 | <style type="text/css"> |
michael@0 | 737 | body { background: white; color: black; } |
michael@0 | 738 | |
michael@0 | 739 | #output { |
michael@0 | 740 | /* Preserve line breaks, but wrap too if browser supports it */ |
michael@0 | 741 | white-space: pre; |
michael@0 | 742 | white-space: -moz-pre-wrap; |
michael@0 | 743 | white-space: pre-wrap; |
michael@0 | 744 | } |
michael@0 | 745 | |
michael@0 | 746 | h3 { margin-top: 0; margin-bottom: 0em; } |
michael@0 | 747 | h3 + div { margin: 0; } |
michael@0 | 748 | |
michael@0 | 749 | form { margin: 0; padding: 0; } |
michael@0 | 750 | #input { width: 100%; border: none; padding: 0; overflow: auto; } |
michael@0 | 751 | |
michael@0 | 752 | .input { color: blue; background: white; font: inherit; font-weight: bold; margin-top: .5em; /* background: #E6E6FF; */ } |
michael@0 | 753 | .normalOutput { color: black; background: white; } |
michael@0 | 754 | .print { color: brown; background: white; } |
michael@0 | 755 | .error { color: red; background: white; } |
michael@0 | 756 | .propList { color: green; background: white; } |
michael@0 | 757 | .message { color: green; background: white; } |
michael@0 | 758 | .tabcomplete { color: purple; background: white; } |
michael@0 | 759 | </style> |
michael@0 | 760 | </head> |
michael@0 | 761 | |
michael@0 | 762 | <body onload="init()"> |
michael@0 | 763 | |
michael@0 | 764 | <div id="output"><h3>JavaScript Shell 1.4</h3><div>Features: autocompletion of property names with Tab, multiline input with Shift+Enter, input history with (Ctrl+) Up/Down, <a accesskey="M" href="javascript:go('scope(Math); mathHelp();');" title="Accesskey: M">Math</a>, <a accesskey="H" href="http://www.squarefree.com/shell/?ignoreReferrerFrom=shell1.4" title="Accesskey: H">help</a></div><div>Values and functions: ans, print(string), <a accesskey="P" href="javascript:go('props(ans)')" title="Accesskey: P">props(object)</a>, <a accesskey="B" href="javascript:go('blink(ans)')" title="Accesskey: B">blink(node)</a>, <a accesskey="C" href="javascript:go('clear()')" title="Accesskey: C">clear()</a>, load(scriptURL), scope(object)</div></div> |
michael@0 | 765 | |
michael@0 | 766 | <div><textarea id="input" class="input" wrap="off" spellcheck="false" onkeydown="inputKeydown(event)" rows="1"></textarea></div> |
michael@0 | 767 | |
michael@0 | 768 | </body> |
michael@0 | 769 | |
michael@0 | 770 | </html> |