Replaced noscript.slowparse.js with tree-inspectors.js. The new library just contains utility functions for inspecting the DOM tree generated by slowparse, and has no hard dependencies on slowparse--clients of it, like the editor, can trivially use the .parseInfo property of nodes returned by an inspector to map back to the original source code if necessary.

This is nice because it means that Slowparse doesn't need to expose DOMBuilder; instead, the TreeWalkers use the DOM API to do everything they need.
This commit is contained in:
Atul Varma 2012-04-30 11:40:31 -04:00
Родитель ea30180665
Коммит 155cc6d903
10 изменённых файлов: 138 добавлений и 126 удалений

Просмотреть файл

@ -14,7 +14,7 @@ For more information, see:
* A [live demo][] to see how Slowparse interprets your code in real-time.
* The [error reporting specification][] to get an idea of what kinds of errors can be reported, and what their human-friendly descriptions are.
* The annotated source code for [slowparse.js][] and [noscript.slowparse.js][].
* The annotated source code for [slowparse.js][] and [tree-inspectors.js][].
* The [test suite][].
* The blog post [Learning and Grammatical Forgiveness][learning], which explains why Slowparse isn't an HTML/CSS validator.
@ -26,7 +26,7 @@ For more information, see:
[error reporting specification]: http://toolness.github.com/slowparse/demo/spec.html
[live demo]: http://toolness.github.com/slowparse/demo/
[slowparse.js]: http://labs.toolness.com/temp/slowparse/docs/slowparse.html
[noscript.slowparse.js]: http://labs.toolness.com/temp/slowparse/docs/noscript.slowparse.html
[tree-inspectors.js]: http://labs.toolness.com/temp/slowparse/docs/tree-inspectors.html
[test suite]: http://toolness.github.com/slowparse/test/
[learning]: http://www.toolness.com/wp/2012/04/learning-and-grammatical-forgiveness/

Просмотреть файл

@ -180,6 +180,7 @@ var MDN_URLS = {
// the user.
function reportError(error) {
var template = $("#templates .error-msg." + error.type);
$(".help").hide();
$(".error").html(_.template(template.html(), error)).show()
.find("[data-highlight]").each(setErrorHighlight);
if (error.type == "INVALID_TAG_NAME") {
@ -232,17 +233,28 @@ function updatePreview(html) {
doc.close();
}
// Ensure that no JavaScript is in the given document fragment.
// Return true if this is the case; otherwise, display an error and
// return false.
function ensureNoJS(doc) {
var js = TreeInspectors.findJS(doc);
if (!js.length)
return true;
reportError(jQuery.extend(js[0].node.parseInfo, {
type: js[0].type
}));
return false;
}
// Called whenever content of the editor area changes.
function onChange() {
var html = editor.getValue();
var builder = new Slowparse.NoscriptDOMBuilder(document);
var result = Slowparse.HTML(builder, html);
var result = Slowparse.HTML(document, html);
helpIndex = [];
clearErrorHighlights();
if (result.error) {
$(".help").hide();
if (result.error)
reportError(result.error);
} else {
else if (ensureNoJS(result.document)) {
buildHelpIndex(result.document, helpIndex);
updatePreview(html);
}

Просмотреть файл

@ -106,7 +106,7 @@
<script src="../codemirror2/mode/css/css.js"></script>
<script src="../codemirror2/mode/htmlmixed/htmlmixed.js"></script>
<script src="../../slowparse.js"></script>
<script src="../../noscript.slowparse.js"></script>
<script src="../../tree-inspectors.js"></script>
<script src="hacktionary-data.js"></script>
<script src="editor.js"></script>
<script src="publish.js"></script>

Просмотреть файл

@ -124,14 +124,14 @@
</div>
<!-- NOSCRIPT ERRORS -->
<div class="error-msg SCRIPT_ELEMENT_NOT_ALLOWED">
<div class="error-msg SCRIPT_ELEMENT">
<p>Sorry, but security restrictions on this site prevent you from
using <code>&lt;script&gt;</code> tags
<em data-highlight="{{start}},{{end}}">here</em>. If you really need to
<em data-highlight="{{openTag.start}},{{closeTag.end}}">here</em>. If you really need to
use JavaScript, consider using <a href="http://jsbin.com/">jsbin</a>
or <a href="http://jsfiddle.net/">jsfiddle</a>.</p>
</div>
<div class="error-msg EVENT_HANDLER_ATTR_NOT_ALLOWED">
<div class="error-msg EVENT_HANDLER_ATTR">
<p>Sorry, but security restrictions on this site prevent you from
using the JavaScript event handler attribute
<em data-highlight="{{name.start}},{{name.end}}">here</em>.
@ -139,7 +139,7 @@
<a href="http://jsbin.com/">jsbin</a>
or <a href="http://jsfiddle.net/">jsfiddle</a>.</p>
</div>
<div class="error-msg JAVASCRIPT_URL_NOT_ALLOWED">
<div class="error-msg JAVASCRIPT_URL">
<p>Sorry, but security restrictions on this site prevent you from
using the <code>javascript:</code> URL
<em data-highlight="{{value.start}},{{value.end}}">here</em>.

Просмотреть файл

@ -1,67 +0,0 @@
// `Slowparse.NoscriptDOMBuilder` is a subclass of `DOMBuilder`
// which provides additional error reporting on the use of JavaScript.
// It's intended for clients who want to inform users in real-time that
// their JavaScript code can't be published or shared.
//
// Note that this is *not* a sanitizer. It's merely intended as a mechanism
// to pre-emptively warn users that their JS will be stripped by a
// remote server or other agent.
//
// You can use this DOM builder like this:
//
// var builder = new Slowparse.NoscriptDOMBuilder(document);
// var result = Slowparse.HTML(builder, someHTMLString);
(function(Slowparse) {
Slowparse.NoscriptDOMBuilder = function(document) {
Slowparse.DOMBuilder.call(this, document);
// This helper calls a method on our DOMBuilder superclass.
this._super = function(name, args) {
Slowparse.DOMBuilder.prototype[name].apply(this, args);
}
this.pushElement = function(tagName, parseInfo) {
// We want to blanketly disallow any `script` tags.
if (tagName == "script")
throw {
parseInfo: {
type: "SCRIPT_ELEMENT_NOT_ALLOWED",
start: parseInfo.openTag.start,
end: parseInfo.openTag.start + "<script".length
}
};
this._super("pushElement", [tagName, parseInfo]);
};
this.attribute = function(name, value, parseInfo) {
// If the attribute value starts with `javascript:`, regardless
// of the attribute name, raise an error. We can change this in
// the future if we only want to make this check on specific
// attributes.
if (value.match(/^javascript:/i))
throw {
parseInfo: {
type: "JAVASCRIPT_URL_NOT_ALLOWED",
name: parseInfo.name,
value: parseInfo.value
}
};
// If the attribute name begins with `on`, we can safely assume
// it's an event handler attribute. We can change this in the
// future if there are valid non-event-handler attributes that
// start with `on`.
if (name.match(/^on/))
throw {
parseInfo: {
type: "EVENT_HANDLER_ATTR_NOT_ALLOWED",
name: parseInfo.name,
value: parseInfo.value
}
};
this._super("attribute", [name, value, parseInfo]);
};
};
Slowparse.NoscriptDOMBuilder.prototype = Slowparse.DOMBuilder.prototype;
})(Slowparse);

Просмотреть файл

@ -1192,10 +1192,6 @@ var Slowparse = (function() {
HTMLParser.prototype.obsoleteHtmlElements)),
CSS_PROPERTY_NAMES: CSSParser.prototype.cssProperties,
// We export our DOMBuilder class in case a client wants to subclass
// it.
DOMBuilder: DOMBuilder,
// We also export a few internal symbols for use by Slowparse's
// testing suite.
replaceEntityRefs: replaceEntityRefs,

Просмотреть файл

@ -17,9 +17,9 @@
<script src="qunit.js"></script>
<script src="../slowparse.js"></script>
<script src="../noscript.slowparse.js"></script>
<script src="../tree-inspectors.js"></script>
<script src="testing-utils.js"></script>
<script src="test-slowparse.js"></script>
<script src="test-noscript.js"></script>
<script src="test-tree-inspectors.js"></script>
<script src="test-spec.js"></script>
</html>

Просмотреть файл

@ -1,41 +0,0 @@
module("NoscriptDOMBuilder");
test("works on script-less HTML", function() {
var ndb = new Slowparse.NoscriptDOMBuilder(document);
var html = '<p class="hi">hello</p><!-- hi -->';
var result = Slowparse.HTML(ndb, html);
equal(result.error, null);
equal(documentFragmentHTML(result.document), html);
});
test("SCRIPT_ELEMENT_NOT_ALLOWED error is reported", function() {
var ndb = new Slowparse.NoscriptDOMBuilder(document);
var html = '<script>alert("yo");</script>';
var result = Slowparse.HTML(ndb, html);
equal(result.error.type, "SCRIPT_ELEMENT_NOT_ALLOWED");
assertParseIntervals(html, result, "result", {
'error': '<script'
});
});
test("EVENT_HANDLER_ATTR_NOT_ALLOWED error is reported", function() {
var ndb = new Slowparse.NoscriptDOMBuilder(document);
var html = '<p onclick="alert(\'yo\');">hi</p>';
var result = Slowparse.HTML(ndb, html);
equal(result.error.type, "EVENT_HANDLER_ATTR_NOT_ALLOWED");
assertParseIntervals(html, result.error, "error", {
'name': 'onclick',
'value': '"alert(\'yo\');"'
});
});
test("JAVASCRIPT_URL_NOT_ALLOWED error is reported", function() {
var ndb = new Slowparse.NoscriptDOMBuilder(document);
var html = '<a href="javascript:alert(\'yo\');">hi</a>';
var result = Slowparse.HTML(ndb, html);
equal(result.error.type, "JAVASCRIPT_URL_NOT_ALLOWED");
assertParseIntervals(html, result.error, "error", {
'name': 'href',
'value': '"javascript:alert(\'yo\');"'
});
});

Просмотреть файл

@ -0,0 +1,44 @@
(function() {
module("TreeInspectors.findJS()");
function findJS(html) {
var doc = Slowparse.HTML(document, html).document;
return TreeInspectors.findJS(doc);
}
test("works on script-less HTML", function() {
deepEqual(findJS('<p class="hi">hello</p><!-- hi -->'), []);
});
test("SCRIPT_ELEMENT is reported", function() {
var html = '<script>alert("yo");</script>';
var js = findJS(html);
equal(js.length, 1);
equal(js[0].type, "SCRIPT_ELEMENT");
assertParseIntervals(html, js[0].node, "elementNode", {
'parseInfo.openTag': '<script>'
});
});
test("EVENT_HANDLER_ATTR is reported", function() {
var html = '<p onclick="alert(\'yo\');">hi</p>';
var js = findJS(html);
equal(js.length, 1);
equal(js[0].type, "EVENT_HANDLER_ATTR");
assertParseIntervals(html, js[0].node, "attributeNode", {
'parseInfo.name': 'onclick',
'parseInfo.value': '"alert(\'yo\');"'
});
});
test("JAVASCRIPT_URL is reported", function() {
var html = '<a href="javascript:alert(\'yo\');">hi</a>';
var js = findJS(html);
equal(js.length, 1);
equal(js[0].type, "JAVASCRIPT_URL");
assertParseIntervals(html, js[0].node, "attributeNode", {
'parseInfo.name': 'href',
'parseInfo.value': '"javascript:alert(\'yo\');"'
});
});
})();

68
tree-inspectors.js Normal file
Просмотреть файл

@ -0,0 +1,68 @@
// `TreeInspectors` contains functions that inspect a document
// fragment and report interesting things about it.
//
// This library has no dependencies.
var TreeInspectors = (function() {
// ## Utility Functions
var utils = {
// This function calls the given function for every element
// that matches the given selector.
each: function each(doc, selector, fn) {
var all = doc.querySelectorAll(selector);
for (var i = 0; i < all.length; i++)
fn(all[i]);
},
// This function calls the given function for every attribute
// in the given DOM node, as well as for every attribute in all
// its children.
eachAttr: function eachAttr(node, fn) {
var i;
if (node.attributes)
for (i = 0; i < node.attributes.length; i++)
fn(node.attributes[i]);
for (i = 0; i < node.childNodes.length; i++)
if (node.childNodes[i].nodeType == node.ELEMENT_NODE)
eachAttr(node.childNodes[i], fn);
}
};
var TreeInspectors = {
// We export our utility functions in case any other scripts want
// to extend the `TreeInspectors` object with more inspectors.
utils: utils,
// ## Finding JavaScript Usage
//
// Given a document fragment, this function finds all occurrences
// of the most common kinds of JavaScript use in it and returns
// an array containing `{type, node}` objects that describe each
// use of JavaScript.
findJS: function(doc) {
var js = [];
// We want to find `script` elements.
utils.each(doc, "script", function(script) {
js.push({type: "SCRIPT_ELEMENT", node: script});
});
utils.eachAttr(doc, function(attr) {
// If the attribute name begins with `on`, we can safely assume
// it's an event handler attribute. We can change this in the
// future if there are valid non-event-handler attributes that
// start with `on`.
if (attr.nodeName.match(/^on/i))
js.push({type: "EVENT_HANDLER_ATTR", node: attr});
// If the attribute value starts with `javascript:`, regardless
// of the attribute name, raise an error. We can change this in
// the future if we only want to make this check on specific
// attributes.
if (attr.nodeValue.match(/^javascript:/i))
js.push({type: "JAVASCRIPT_URL", node: attr});
});
return js;
}
};
return TreeInspectors;
})();