User:Kephir/gadgets/cksyntax.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Kephir/gadgets/cksyntax. |
/*****
* THIS SCRIPT IS OBSOLETE
*
* It has been replaced by an update to the CodeEditor extension. This page is kept for historical interest only.
*****/
mw.loader.using(['ext.wikiEditor'], function () {
"use strict";
if ((wgAction !== 'edit') && (wgAction !== 'submit'))
return;
var wpTextbox1 = document.getElementById('wpTextbox1');
if ((window.wgPageContentModel === 'javascript') || (window.wgPageContentModel === 'css') || (window.wgPageContentModel === 'lua')) {
var editCheckboxes = document.getElementsByClassName('editCheckboxes')[0];
var label = document.createElement('label');
var inputStrip = document.createElement('input');
inputStrip.type = 'checkbox';
inputStrip.checked = (window.wgPageContentModel !== 'lua');
label.textContent = "Strip trailing whitespace when saving";
editCheckboxes.appendChild(inputStrip);
editCheckboxes.appendChild(label);
inputStrip.id = 'cksyntax-strip-whitespace';
label.setAttribute('for', inputStrip.id);
}
if (window.wgPageContentModel !== void(0)) {
if (window.wgPageContentModel !== 'javascript')
return;
} else { /* fallback check */
if (((wgNamespaceNumber !== 2) && (wgNamespaceNumber !== 8)) || !/\.js$/.test(wgPageName))
return;
}
var wpSave = document.getElementById('wpSave');
if (!wpSave || !wpTextbox1)
return;
var dirty = true;
// is Function(code) equivalent to eval("function () {\n"+code+"\n}")? if yes, we are in trouble
/* Testing results:
* IE10 : secure, throws a SyntaxError
* KJS (Konqueror) : secure, throws a SyntaxError
* SpiderMonkey (Mozilla) : secure, throws a SyntaxError
* Carakan (Opera 12) : secure, throws a SyntaxError
* Rhino : secure, although allows some invalid inputs, including the first one tested below; silently ignores + and - and any following expression; throws a SyntaxError otherwise (no browser uses Rhino, though)
* V8 (Chrom*) : insecure, throws an error if the expression does not evaluate to a function (which is avoided easily)
* JavaScriptCore : crashes spectacularly ("WTFCrash"); does not seem to be a security vulnerability otherwise
*/
try {
new Function('){/*', '*///');
// if we got here, this is a buggy JavaScriptCore, and the tests that follow may crash the browser.
// the immediate culprit is at <https://trac.webkit.org/browser/trunk/Source/JavaScriptCore/runtime/CodeCache.cpp?rev=167313#L158>
// but it seems the fix should be somewhere earlier.
// even the flawed (see below) versions of V8 will reject the above
// see <https://code.google.com/p/v8/source/diff?spec=svn13867&r=13867&path=/branches/bleeding_edge/src/v8natives.js>
if (!confirm('Your browser has a buggy implementation of the Function constructor; a specially crafted invalid script may crash your browser. Perform syntax checks?'))
return;
} catch (e) {
// OK, an error is expected here.
try { // code.google.com/p/v8/issues/detail?id=2470
new Function("return false; } && void(window._insecure = true) || function () { return true;");
} catch (ee) {
// OK, an error is expected here.
}
if (window._insecure)
if (!confirm('Your browser has an insecure implementation of the Function constructor; a specially crafted invalid script may trick your browser into executing arbitrary JavaScript. Perform syntax checks?'))
return;
}
mw.hook("codeEditor.configure").add(function (sess) {
sess.on("change", function () {
dirty = true;
});
});
wpTextbox1.addEventListener('input', function (ev) {
dirty = true;
}, false);
wpTextbox1.addEventListener('change', function (ev) {
dirty = true;
}, false);
function checkSyntax() {
var fixup = 0;
// DO NOT prettify. this HAS TO to be on one line (minifiers should be okay).
try { eval("(") /* xkcd.com/859/ */ } catch (e) { fixup = e.lineNumber }; try { (function($, $, mw, mediaWiki, jQuery, top, self, parent, window, document, navigator, location) { new Function(wpTextbox1.value); }).call({});
} catch (e) {
// SpiderMonkey workaround: it sums the line number of the evaluated script
// with the line number of the Function call in the containing script.
// this may make sense in some contrived situations, but not here.
if (fixup)
e.lineNumber -= fixup - 1;
else if (!e.line && !e.lineNumber && window.opera) {
// Carakan reports the line and column when calling eval(), but not Function().
// since we already know the code is invalid, we can safely eval it again
// and extract the error info from there.
try {
// the \n at the start is necessary to have the message in the format below
// otherwise we get "at index n"; we can parse that format too, but why bother?
// also appending an unmatched (, in case for some reason it starts parsing correctly here;
// the error location will be bogus, but at least we avoided executing a potentially
// malicious script.
eval('\n' + wpTextbox1.value + '\n(');
} catch (ee) {
var m = /^at line (\d+), column (\d+)/.exec(ee.message);
if (m) {
e.lineNumber = parseInt(m[1], 10) - 1;
e.columnNumber = parseInt(m[2], 10);
}
}
}
return e;
}
return null;
}
function scrollEditor(line, col) {
// line is 1-based, while col is 0-based
var codeEditor = $(wpTextbox1).data('wikiEditorContext').codeEditor;
if (codeEditor) {
codeEditor.gotoLine(line, col || 0, true);
codeEditor.focus();
return;
}
var position = 0;
for (var i = 1; i < line; ++i) {
position = wpTextbox1.value.indexOf('\n', position) + 1;
}
var lastcol = wpTextbox1.value.indexOf('\n', position) - position;
position += col ? (col < lastcol ? col : lastcol) : 0;
if (wpTextbox1.createTextRange) { // MSIE
var range = textBox.createTextRange();
range.collapse(true);
range.moveEnd('character', position);
range.moveStart('character', position);
range.select();
} else if (wpTextbox1.setSelectionRange) { // browsers
wpTextbox1.focus();
wpTextbox1.setSelectionRange(position, position);
var phantom = document.createElement('div');
var style = window.getComputedStyle(wpTextbox1, '');
phantom.style.padding = '0';
phantom.style.lineHeight = style.lineHeight;
phantom.style.fontFamily = style.fontFamily;
phantom.style.fontSize = style.fontSize;
phantom.style.fontStyle = style.fontStyle;
phantom.style.fontVariant = style.fontVariant;
phantom.style.letterSpacing = style.letterSpacing;
phantom.style.border = style.border;
phantom.style.outline = style.outline;
try { phantom.style.whiteSpace = "-moz-pre-wrap" } catch(e) {}
try { phantom.style.whiteSpace = "-o-pre-wrap" } catch(e) {}
try { phantom.style.whiteSpace = "-pre-wrap" } catch(e) {}
try { phantom.style.whiteSpace = "pre-wrap" } catch(e) {}
phantom.textContent = wpTextbox1.value.substr(0, position);
document.body.appendChild(phantom); // XXX: do I need this?
wpTextbox1.scrollTop = phantom.scrollHeight - (wpTextbox1.clientHeight / 2);
document.body.removeChild(phantom);
}
}
function scrollToError(e) {
if ((typeof e.lineNumber === 'number') && (typeof e.columnNumber === 'number')) { // SpiderMonkey (Firefox) and Opera
scrollEditor(e.lineNumber, e.columnNumber);
return "Line: " + e.lineNumber + ", column: " + (e.columnNumber + 1);
} else if ((typeof e.line === 'number')) { // JavaScriptCore (WebKit)
scrollEditor(e.line, 1 / 0); // most errors happen on the end of the line
return "Line: " + e.line;
}
// nothing for V8 (Chrom*)
// unknown whether we can do anything on Trident (MSIE)
return "Clicking \"Show changes\" may help you locate the error.";
}
wpSave.addEventListener('click', function (ev) {
if (dirty) {
$(mw).trigger('LivePreviewPrepare'); // XXX: force CodeEditor (and everything else) to update wpTextbox1.value
if (inputStrip.checked) {
wpTextbox1.value = wpTextbox1.value.replace(/[ \t]+$/mg, '');
}
var err;
dirty = false;
if (err = checkSyntax()) {
mw.util.jsMessage("There is an error in the script you were trying to save:<br/><br/><b>" + err.toString() +
"</b><br/><br/>" + scrollToError(err) + "<br/><br/>If you click the \"Save\" button again, this error will be ignored.");
ev.preventDefault();
ev.stopPropagation();
return false;
}
}
}, false);
$(wpTextbox1).wikiEditor('addToToolbar', {
section: 'main',
group: 'format',
tools: {
cksyntax: {
label: "Check syntax",
type: 'button',
icon: '//upload.wikimedia.org/wikipedia/commons/thumb/1/15/Tick-red.png/22px-Tick-red.png',
action: {
type: 'callback',
execute: function () {
var err;
$(mw).trigger('LivePreviewPrepare'); // XXX: force CodeEditor (and everything else) to update wpTextbox1.value
if (err = checkSyntax()) {
mw.util.jsMessage("<b>" + err.toString() + "</b><br/><br/>" + scrollToError(err));
scrollToError(err);
} else {
mw.util.jsMessage("No syntax errors found");
}
}
}
}
}
});
});