aboutsummaryrefslogtreecommitdiff
path: root/node_modules/jsdom/lib
diff options
context:
space:
mode:
authorHumairAK <humair88@hotmail.com>2016-07-25 03:29:23 +0000
committerHumairAK <humair88@hotmail.com>2016-07-25 03:29:23 +0000
commit7c222d2418ba44bd4f2f9ffd844f97f43974029a (patch)
treea8282098bea1a18312fda510d8f635f3dd97eee1 /node_modules/jsdom/lib
parentfc3cc6aee7ce7a28539d332902c0f9923d298c71 (diff)
parent3ca0e78b313a1bded021cbb8b2dad241d7cad049 (diff)
fixed conflict
Diffstat (limited to 'node_modules/jsdom/lib')
-rw-r--r--node_modules/jsdom/lib/README.md17
-rw-r--r--node_modules/jsdom/lib/jsdom.js379
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/Window.js291
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/documentAdapter.js62
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/documentfeatures.js52
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/domtohtml.js24
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/history.js94
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/htmltodom.js231
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/index.js85
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/location.js103
-rw-r--r--node_modules/jsdom/lib/jsdom/browser/utils.js15
-rw-r--r--node_modules/jsdom/lib/jsdom/contextify-shim.js53
-rw-r--r--node_modules/jsdom/lib/jsdom/level1/core.js1794
-rw-r--r--node_modules/jsdom/lib/jsdom/level2/core.js306
-rw-r--r--node_modules/jsdom/lib/jsdom/level2/events.js441
-rw-r--r--node_modules/jsdom/lib/jsdom/level2/html.js2140
-rw-r--r--node_modules/jsdom/lib/jsdom/level2/languages/javascript.js13
-rw-r--r--node_modules/jsdom/lib/jsdom/level2/style.js263
-rw-r--r--node_modules/jsdom/lib/jsdom/level3/core.js597
-rw-r--r--node_modules/jsdom/lib/jsdom/level3/ls.js211
-rw-r--r--node_modules/jsdom/lib/jsdom/level3/xpath.js1866
-rw-r--r--node_modules/jsdom/lib/jsdom/living/character-data.js90
-rw-r--r--node_modules/jsdom/lib/jsdom/living/comment.js13
-rw-r--r--node_modules/jsdom/lib/jsdom/living/document-type.js19
-rw-r--r--node_modules/jsdom/lib/jsdom/living/document.js26
-rw-r--r--node_modules/jsdom/lib/jsdom/living/dom-implementation.js85
-rw-r--r--node_modules/jsdom/lib/jsdom/living/helpers/validate-names.js63
-rw-r--r--node_modules/jsdom/lib/jsdom/living/index.js23
-rw-r--r--node_modules/jsdom/lib/jsdom/living/node-filter.js47
-rw-r--r--node_modules/jsdom/lib/jsdom/living/node.js261
-rw-r--r--node_modules/jsdom/lib/jsdom/living/processing-instruction.js15
-rw-r--r--node_modules/jsdom/lib/jsdom/living/selectors.js32
-rw-r--r--node_modules/jsdom/lib/jsdom/living/text.js41
-rw-r--r--node_modules/jsdom/lib/jsdom/utils.js223
-rw-r--r--node_modules/jsdom/lib/jsdom/virtual-console.js24
35 files changed, 9999 insertions, 0 deletions
diff --git a/node_modules/jsdom/lib/README.md b/node_modules/jsdom/lib/README.md
new file mode 100644
index 0000000..8f0856c
--- /dev/null
+++ b/node_modules/jsdom/lib/README.md
@@ -0,0 +1,17 @@
+# Note: this codebase is in a transitional state
+
+We're slowly moving from a historical model based on layered "levels" of specs, to the [living standard model](https://wiki.whatwg.org/wiki/FAQ#What_does_.22Living_Standard.22_mean.3F) actually implemented by browsers. As such, the code is kind of a mess.
+
+**Summary**: new features go in `lib/jsdom/living` and follow the code style there; modifications to existing features will require some spelunking to find out what to modify in-place.
+
+---
+
+A lot of the main implementation is in `lib/jsdom/level1` and `lib/jsdom/level2`. (That includes things that didn't appear in the original DOM Level 1 and Level 2 specs, just because the code was located there and we had to patch it.) We're trying to avoid adding new code there, but patching old code is often still required.
+
+New features generally go in the `lib/jsdom/living` folder, in nice small files, with a clear coding style enforced by JSHint.
+
+We're planning to fix this whole situation with a multi-stage process:
+
+- First, consolidate any leftovers in `lib/jsdom/browser` and `lib/jsdom/level3`, as well as the more substantial body of code in `lib/jsdom/level2`, into `lib/jsdom/level1`. This will contain the "historical" portion of the jsdom codebase.
+- Then, embark on a major cleanup and refactoring effort, splitting out small pieces from `lib/jsdom/level1` and into `lib/jsdom/living`, cleaning up the code style and spec compliance as we go.
+- Finally, collapse the silly directory hierarchy into something less nested.
diff --git a/node_modules/jsdom/lib/jsdom.js b/node_modules/jsdom/lib/jsdom.js
new file mode 100644
index 0000000..e801634
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom.js
@@ -0,0 +1,379 @@
+var fs = require('fs');
+var path = require('path');
+var URL = require('url');
+
+var toFileUrl = require('./jsdom/utils').toFileUrl;
+var defineGetter = require('./jsdom/utils').defineGetter;
+var defineSetter = require('./jsdom/utils').defineSetter;
+var features = require('./jsdom/browser/documentfeatures');
+var dom = require('./jsdom/living');
+var browserAugmentation = require('./jsdom/browser/index').browserAugmentation;
+var domToHtml = require('./jsdom/browser/domtohtml').domToHtml;
+var VirtualConsole = require('./jsdom/virtual-console');
+
+var canReadFilesFromFS = !!fs.readFile; // in a browserify environment, this isn't present
+
+var request = function() { // lazy loading request
+ request = require('request');
+ return request.apply(undefined, arguments);
+}
+
+exports.getVirtualConsole = function (window) {
+ return window._virtualConsole;
+};
+exports.debugMode = false;
+
+// Proxy feature functions to features module.
+['availableDocumentFeatures',
+ 'defaultDocumentFeatures',
+ 'applyDocumentFeatures'].forEach(function (propName) {
+ defineGetter(exports, propName, function () {
+ return features[propName];
+ });
+ defineSetter(exports, propName, function (val) {
+ return features[propName] = val;
+ });
+});
+
+exports.jsdom = function (html, options) {
+ if (options === undefined) {
+ options = {};
+ }
+ if (options.parsingMode === undefined || options.parsingMode === 'auto') {
+ options.parsingMode = 'html';
+ }
+
+ var browser = browserAugmentation(dom, options);
+ var doc = new browser.HTMLDocument(options);
+
+ if (options.created) {
+ options.created(null, doc.parentWindow);
+ }
+
+ features.applyDocumentFeatures(doc, options.features);
+
+ if (html === undefined) {
+ html = '';
+ }
+ html = String(html);
+ doc.write(html);
+
+ if (doc.close && !options.deferClose) {
+ doc.close();
+ }
+
+ return doc;
+};
+
+exports.jQueryify = exports.jsdom.jQueryify = function (window, jqueryUrl, callback) {
+ if (!window || !window.document) {
+ return;
+ }
+
+ var features = window.document.implementation._features;
+ window.document.implementation._addFeature('FetchExternalResources', ['script']);
+ window.document.implementation._addFeature('ProcessExternalResources', ['script']);
+ window.document.implementation._addFeature('MutationEvents', ['2.0']);
+
+ var scriptEl = window.document.createElement('script');
+ scriptEl.className = 'jsdom';
+ scriptEl.src = jqueryUrl;
+ scriptEl.onload = scriptEl.onerror = function () {
+ window.document.implementation._features = features;
+
+ if (callback) {
+ callback(window, window.jQuery);
+ }
+ };
+
+ window.document.body.appendChild(scriptEl);
+};
+
+exports.env = exports.jsdom.env = function () {
+ var config = getConfigFromArguments(arguments);
+
+ if (config.file && canReadFilesFromFS) {
+ fs.readFile(config.file, 'utf-8', function (err, text) {
+ if (err) {
+ if (config.created) {
+ config.created(err);
+ }
+ if (config.done) {
+ config.done([err]);
+ }
+ return;
+ }
+
+ setParsingModeFromExtension(config, config.file);
+
+ config.html = text;
+ processHTML(config);
+ });
+ } else if (config.html !== undefined) {
+ processHTML(config);
+ } else if (config.url) {
+ handleUrl(config);
+ } else if (config.somethingToAutodetect !== undefined) {
+ var url = URL.parse(config.somethingToAutodetect);
+ if (url.protocol && url.hostname) {
+ config.url = config.somethingToAutodetect;
+ handleUrl(config.somethingToAutodetect);
+ } else if (canReadFilesFromFS) {
+ fs.readFile(config.somethingToAutodetect, 'utf-8', function (err, text) {
+ if (err) {
+ // the toString() test is because in Node.js, there is no proper code for this.
+ // This is fixed in io.js: https://github.com/iojs/io.js/issues/517 so:
+ // TODO: remove when we start requiring io.js
+ if (err.code === 'ENOENT' || err.code === 'ENAMETOOLONG'
+ || (err.toString() == 'Error: Path must be a string without null bytes.')
+ ) {
+ config.html = config.somethingToAutodetect;
+ processHTML(config);
+ } else {
+ if (config.created) {
+ config.created(err);
+ }
+ if (config.done) {
+ config.done([err]);
+ }
+ }
+ } else {
+ setParsingModeFromExtension(config, config.somethingToAutodetect);
+
+ config.html = text;
+ config.url = toFileUrl(config.somethingToAutodetect);
+ processHTML(config);
+ }
+ });
+ } else {
+ config.html = config.somethingToAutodetect;
+ processHTML(config);
+ }
+ }
+
+ function handleUrl() {
+ var options = {
+ uri: config.url,
+ encoding: config.encoding || 'utf8',
+ headers: config.headers || {},
+ proxy: config.proxy || null,
+ jar: config.jar !== undefined ? config.jar : true
+ };
+
+ request(options, function (err, res, responseText) {
+ if (err) {
+ if (config.created) {
+ config.created(err);
+ }
+ if (config.done) {
+ config.done([err]);
+ }
+ return;
+ }
+
+ // The use of `res.request.uri.href` ensures that `window.location.href`
+ // is updated when `request` follows redirects.
+ config.html = responseText;
+ config.url = res.request.uri.href;
+
+ if (config.parsingMode === "auto" && (
+ res.headers["content-type"] === "application/xml" ||
+ res.headers["content-type"] === "text/xml" ||
+ res.headers["content-type"] === "application/xhtml+xml")) {
+ config.parsingMode = "xml";
+ }
+
+ processHTML(config);
+ });
+ }
+};
+
+exports.serializeDocument = function (doc) {
+ return domToHtml(doc, true);
+};
+
+function processHTML(config) {
+ var options = {
+ features: config.features,
+ url: config.url,
+ parser: config.parser,
+ parsingMode: config.parsingMode,
+ created: config.created,
+ resourceLoader: config.resourceLoader
+ };
+
+ if (config.document) {
+ options.referrer = config.document.referrer;
+ options.cookie = config.document.cookie;
+ options.cookieDomain = config.document.cookieDomain;
+ }
+
+ var window = exports.jsdom(config.html, options).parentWindow;
+ var features = JSON.parse(JSON.stringify(window.document.implementation._features));
+
+ var docsLoaded = 0;
+ var totalDocs = config.scripts.length + config.src.length;
+ var readyState = null;
+ var errors = [];
+
+ if (!window || !window.document) {
+ if (config.created) {
+ config.created(new Error('JSDOM: a window object could not be created.'));
+ }
+ if (config.done) {
+ config.done([new Error('JSDOM: a window object could not be created.')]);
+ }
+ return;
+ }
+
+ window.document.implementation._addFeature('FetchExternalResources', ['script']);
+ window.document.implementation._addFeature('ProcessExternalResources', ['script']);
+ window.document.implementation._addFeature('MutationEvents', ['2.0']);
+
+ function scriptComplete() {
+ docsLoaded++;
+
+ if (docsLoaded >= totalDocs) {
+ window.document.implementation._features = features;
+
+ errors = errors.concat(window.document.errors || []);
+ if (errors.length === 0) {
+ errors = null;
+ }
+
+ process.nextTick(function() {
+ if (config.loaded) {
+ config.loaded(errors, window);
+ }
+ if (config.done) {
+ config.done(errors, window);
+ }
+ });
+ }
+ }
+
+ function handleScriptError(e) {
+ if (!errors) {
+ errors = [];
+ }
+ errors.push(e.error || e.message);
+
+ // nextTick so that an exception within scriptComplete won't cause
+ // another script onerror (which would be an infinite loop)
+ process.nextTick(scriptComplete);
+ }
+
+ if (config.scripts.length > 0 || config.src.length > 0) {
+ config.scripts.forEach(function (scriptSrc) {
+ var script = window.document.createElement('script');
+ script.className = 'jsdom';
+ script.onload = scriptComplete;
+ script.onerror = handleScriptError;
+ script.src = scriptSrc;
+
+ try {
+ // protect against invalid dom
+ // ex: http://www.google.com/foo#bar
+ window.document.documentElement.appendChild(script);
+ } catch (e) {
+ handleScriptError(e);
+ }
+ });
+
+ config.src.forEach(function (scriptText) {
+ var script = window.document.createElement('script');
+ script.onload = scriptComplete;
+ script.onerror = handleScriptError;
+ script.text = scriptText;
+
+ window.document.documentElement.appendChild(script);
+ window.document.documentElement.removeChild(script);
+ });
+ } else {
+ if (window.document.readyState === 'complete') {
+ scriptComplete();
+ } else {
+ window.addEventListener('load', function() {
+ scriptComplete();
+ });
+ }
+ }
+}
+
+function getConfigFromArguments(args, callback) {
+ var config = {};
+ if (typeof args[0] === 'object') {
+ var configToClone = args[0];
+ Object.keys(configToClone).forEach(function (key) {
+ config[key] = configToClone[key];
+ });
+ } else {
+ var stringToAutodetect = null;
+
+ Array.prototype.forEach.call(args, function (arg) {
+ switch (typeof arg) {
+ case 'string':
+ config.somethingToAutodetect = arg;
+ break;
+ case 'function':
+ config.done = arg;
+ break;
+ case 'object':
+ if (Array.isArray(arg)) {
+ config.scripts = arg;
+ } else {
+ extend(config, arg);
+ }
+ break;
+ }
+ });
+ }
+
+ if (!config.done && !config.created && !config.loaded) {
+ throw new Error('Must pass a "created", "loaded", "done" option or a callback to jsdom.env.');
+ }
+
+ if (config.somethingToAutodetect === undefined &&
+ config.html === undefined && !config.file && !config.url) {
+ throw new Error('Must pass a "html", "file", or "url" option, or a string, to jsdom.env');
+ }
+
+ config.scripts = ensureArray(config.scripts);
+ config.src = ensureArray(config.src);
+ config.parsingMode = config.parsingMode || "auto";
+
+ config.features = config.features || {
+ FetchExternalResources: false,
+ ProcessExternalResources: false,
+ SkipExternalResources: false
+ };
+
+ if (!config.url && config.file) {
+ config.url = toFileUrl(config.file);
+ }
+
+ return config;
+}
+
+function ensureArray(value) {
+ var array = value || [];
+ if (typeof array === 'string') {
+ array = [array];
+ }
+ return array;
+}
+
+function extend(config, overrides) {
+ Object.keys(overrides).forEach(function (key) {
+ config[key] = overrides[key];
+ });
+}
+
+function setParsingModeFromExtension(config, filename) {
+ if (config.parsingMode === "auto") {
+ var ext = path.extname(filename);
+ if (ext === ".xhtml" || ext === ".xml") {
+ config.parsingMode = "xml";
+ }
+ }
+}
diff --git a/node_modules/jsdom/lib/jsdom/browser/Window.js b/node_modules/jsdom/lib/jsdom/browser/Window.js
new file mode 100644
index 0000000..1cee5cb
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/Window.js
@@ -0,0 +1,291 @@
+"use strict";
+
+var URL = require("url");
+var CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
+var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
+var dom = require("../level1/core");
+var NOT_IMPLEMENTED = require("./utils").NOT_IMPLEMENTED;
+var createFrom = require("../utils").createFrom;
+var History = require("./history");
+var VirtualConsole = require("../virtual-console");
+
+var cssSelectorSplitRE = /((?:[^,"']|"[^"]*"|'[^']*')+)/;
+
+function matchesDontThrow(el, selector) {
+ try {
+ return el.matches(selector);
+ } catch (e) {
+ return false;
+ }
+}
+
+function startTimer(window, startFn, stopFn, callback, ms) {
+ var res = startFn(callback, ms);
+ window.__timers.push([res, stopFn]);
+ return res;
+}
+
+function stopTimer(window, id) {
+ if (typeof id === "undefined") {
+ return;
+ }
+ for (var i in window.__timers) {
+ if (window.__timers[i][0] === id) {
+ window.__timers[i][1].call(window, id);
+ window.__timers.splice(i, 1);
+ break;
+ }
+ }
+}
+
+function stopAllTimers(window) {
+ window.__timers.forEach(function (t) {
+ t[1].call(window, t[0]);
+ });
+ window.__timers = [];
+}
+
+function Window(document) {
+ this.__timers = [];
+
+ // TODO: very little of this belongs on the instance; they should be prototype methods instead.
+
+ var window = this;
+ this._document = document;
+ this.history = new History(this);
+
+ this.addEventListener = function () {
+ dom.Node.prototype.addEventListener.apply(window, arguments);
+ };
+ this.removeEventListener = function () {
+ dom.Node.prototype.removeEventListener.apply(window, arguments);
+ };
+ this.dispatchEvent = function () {
+ dom.Node.prototype.dispatchEvent.apply(window, arguments);
+ };
+ this.raise = function () {
+ dom.Node.prototype.raise.apply(window.document, arguments);
+ };
+
+ this.setTimeout = function (fn, ms) { return startTimer(window, setTimeout, clearTimeout, fn, ms); };
+ this.setInterval = function (fn, ms) { return startTimer(window, setInterval, clearInterval, fn, ms); };
+ this.clearInterval = stopTimer.bind(this, window);
+ this.clearTimeout = stopTimer.bind(this, window);
+ this.__stopAllTimers = stopAllTimers.bind(this, window);
+ this.Image = function (width, height) {
+ var element = window._document.createElement("img");
+ element.width = width;
+ element.height = height;
+ return element;
+ };
+
+ this._virtualConsole = new VirtualConsole();
+
+ function wrapConsoleMethod(method) {
+ return function () {
+ var args = Array.prototype.slice.call(arguments);
+ window._virtualConsole.emit.apply(window._virtualConsole, [method].concat(args));
+ };
+ }
+
+ this.console = {
+ assert: wrapConsoleMethod("assert"),
+ clear: wrapConsoleMethod("clear"),
+ count: wrapConsoleMethod("count"),
+ debug: wrapConsoleMethod("debug"),
+ error: wrapConsoleMethod("error"),
+ group: wrapConsoleMethod("group"),
+ groupCollapse: wrapConsoleMethod("groupCollapse"),
+ groupEnd: wrapConsoleMethod("groupEnd"),
+ info: wrapConsoleMethod("info"),
+ log: wrapConsoleMethod("log"),
+ table: wrapConsoleMethod("table"),
+ time: wrapConsoleMethod("time"),
+ timeEnd: wrapConsoleMethod("timeEnd"),
+ trace: wrapConsoleMethod("trace"),
+ warn: wrapConsoleMethod("warn")
+ };
+
+ this.XMLHttpRequest = function () {
+ var xhr = new XMLHttpRequest();
+ var lastUrl = "";
+ xhr._open = xhr.open;
+ xhr.open = function (method, url, async, user, password) {
+ url = URL.resolve(window.document.URL, url);
+ lastUrl = url;
+ return xhr._open(method, url, async, user, password);
+ };
+ xhr._send = xhr.send;
+ xhr.send = function (data) {
+ if (window.document.cookie) {
+ var cookieDomain = window.document._cookieDomain;
+ var url = URL.parse(lastUrl);
+ var host = url.host.split(":")[0];
+ if (host.indexOf(cookieDomain, host.length - cookieDomain.length) !== -1) {
+ xhr.setDisableHeaderCheck(true);
+ xhr.setRequestHeader("cookie", window.document.cookie);
+ xhr.setDisableHeaderCheck(false);
+ }
+ }
+ return xhr._send(data);
+ };
+ return xhr;
+ };
+}
+
+Window.prototype = createFrom(dom || null, {
+ constructor: Window,
+ // This implements window.frames.length, since window.frames returns a
+ // self reference to the window object. This value is incremented in the
+ // HTMLFrameElement init function (see: level2/html.js).
+ _length: 0,
+ get length() {
+ return this._length;
+ },
+ get document() {
+ return this._document;
+ },
+ get location() {
+ return this._document._location;
+ },
+ close: function () {
+ // Recursively close child frame windows, then ourselves.
+ var currentWindow = this;
+ (function windowCleaner(window) {
+ var i;
+ // We could call window.frames.length etc, but window.frames just points
+ // back to window.
+ if (window.length > 0) {
+ for (i = 0; i < window.length; i++) {
+ windowCleaner(window[i]);
+ }
+ }
+ // We"re already in our own window.close().
+ if (window !== currentWindow) {
+ window.close();
+ }
+ })(this);
+
+ if (this._document) {
+ if (this._document.body) {
+ this._document.body.innerHTML = "";
+ }
+
+ if (this._document.close) {
+ // We need to empty out the event listener array because
+ // document.close() causes "load" event to re-fire.
+ this._document._listeners = [];
+ this._document.close();
+ }
+ delete this._document;
+ }
+
+ stopAllTimers(currentWindow);
+ // Clean up the window"s execution context.
+ // dispose() is added by Contextify.
+ this.dispose();
+ },
+ getComputedStyle: function (node) {
+ var s = node.style,
+ cs = new CSSStyleDeclaration(),
+ forEach = Array.prototype.forEach;
+
+ function setPropertiesFromRule(rule) {
+ if (!rule.selectorText) {
+ return;
+ }
+
+ var selectors = rule.selectorText.split(cssSelectorSplitRE);
+ var matched = false;
+ selectors.forEach(function (selectorText) {
+ if (selectorText !== "" && selectorText !== "," && !matched && matchesDontThrow(node, selectorText)) {
+ matched = true;
+ forEach.call(rule.style, function (property) {
+ cs.setProperty(property, rule.style.getPropertyValue(property), rule.style.getPropertyPriority(property));
+ });
+ }
+ });
+ }
+
+ forEach.call(node.ownerDocument.styleSheets, function (sheet) {
+ forEach.call(sheet.cssRules, function (rule) {
+ if (rule.media) {
+ if (Array.prototype.indexOf.call(rule.media, "screen") !== -1) {
+ forEach.call(rule.cssRules, setPropertiesFromRule);
+ }
+ } else {
+ setPropertiesFromRule(rule);
+ }
+ });
+ });
+
+ forEach.call(s, function (property) {
+ cs.setProperty(property, s.getPropertyValue(property), s.getPropertyPriority(property));
+ });
+
+ return cs;
+ },
+
+ // TODO: all of the below data properties should be getters; right now they are shared between Window instances
+ // which is of course bad.
+ navigator: {
+ get userAgent() { return "Node.js (" + process.platform + "; U; rv:" + process.version + ")"; },
+ get appName() { return "Node.js jsDom"; },
+ get platform() { return process.platform; },
+ get appVersion() { return process.version; },
+ noUI: true,
+ get cookieEnabled() { return true; }
+ },
+
+ name: "nodejs",
+ innerWidth: 1024,
+ innerHeight: 768,
+ outerWidth: 1024,
+ outerHeight: 768,
+ pageXOffset: 0,
+ pageYOffset: 0,
+ screenX: 0,
+ screenY: 0,
+ screenLeft: 0,
+ screenTop: 0,
+ scrollX: 0,
+ scrollY: 0,
+ scrollTop: 0,
+ scrollLeft: 0,
+ alert: NOT_IMPLEMENTED("window.alert"),
+ blur: NOT_IMPLEMENTED("window.blur"),
+ confirm: NOT_IMPLEMENTED("window.confirm"),
+ createPopup: NOT_IMPLEMENTED("window.createPopup"),
+ focus: NOT_IMPLEMENTED("window.focus"),
+ moveBy: NOT_IMPLEMENTED("window.moveBy"),
+ moveTo: NOT_IMPLEMENTED("window.moveTo"),
+ open: NOT_IMPLEMENTED("window.open"),
+ print: NOT_IMPLEMENTED("window.print"),
+ prompt: NOT_IMPLEMENTED("window.prompt"),
+ resizeBy: NOT_IMPLEMENTED("window.resizeBy"),
+ resizeTo: NOT_IMPLEMENTED("window.resizeTo"),
+ scroll: NOT_IMPLEMENTED("window.scroll"),
+ scrollBy: NOT_IMPLEMENTED("window.scrollBy"),
+ scrollTo: NOT_IMPLEMENTED("window.scrollTo"),
+ screen: {
+ width: 0,
+ height: 0
+ },
+
+ // Note: these will not be necessary for newer Node.js versions, which have
+ // typed arrays in V8 and thus on every global object. (That is, in newer
+ // versions we"ll get `ArrayBuffer` just as automatically as we get
+ // `Array`.) But to support older versions, we explicitly set them here.
+ Int8Array: global.Int8Array,
+ Int16Array: global.Int16Array,
+ Int32Array: global.Int32Array,
+ Float32Array: global.Float32Array,
+ Float64Array: global.Float64Array,
+ Uint8Array: global.Uint8Array,
+ Uint8ClampedArray: global.Uint8ClampedArray,
+ Uint16Array: global.Uint16Array,
+ Uint32Array: global.Uint32Array,
+ ArrayBuffer: global.ArrayBuffer
+});
+
+module.exports = Window;
diff --git a/node_modules/jsdom/lib/jsdom/browser/documentAdapter.js b/node_modules/jsdom/lib/jsdom/browser/documentAdapter.js
new file mode 100644
index 0000000..7d66168
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/documentAdapter.js
@@ -0,0 +1,62 @@
+//Tree traversing
+exports.getFirstChild = function (node) {
+ return node.childNodes[0];
+};
+
+exports.getChildNodes = function (node) {
+ return node.childNodes;
+};
+
+exports.getParentNode = function (node) {
+ return node.parentNode;
+};
+
+exports.getAttrList = function (node) {
+ return node.attributes;
+};
+
+//Node data
+exports.getTagName = function (element) {
+ return element.tagName.toLowerCase();
+};
+
+exports.getNamespaceURI = function (element) {
+ return element.namespaceURI || 'http://www.w3.org/1999/xhtml';
+};
+
+exports.getTextNodeContent = function (textNode) {
+ return textNode.nodeValue;
+};
+
+exports.getCommentNodeContent = function (commentNode) {
+ return commentNode.nodeValue;
+};
+
+exports.getDocumentTypeNodeName = function (doctypeNode) {
+ return doctypeNode.name;
+};
+
+exports.getDocumentTypeNodePublicId = function (doctypeNode) {
+ return doctypeNode.publicId || null;
+};
+
+exports.getDocumentTypeNodeSystemId = function (doctypeNode) {
+ return doctypeNode.systemId || null;
+};
+
+//Node types
+exports.isTextNode = function (node) {
+ return node.nodeName === '#text';
+};
+
+exports.isCommentNode = function (node) {
+ return node.nodeName === '#comment';
+};
+
+exports.isDocumentTypeNode = function (node) {
+ return node.nodeType === 10;
+};
+
+exports.isElementNode = function (node) {
+ return !!node.tagName;
+};
diff --git a/node_modules/jsdom/lib/jsdom/browser/documentfeatures.js b/node_modules/jsdom/lib/jsdom/browser/documentfeatures.js
new file mode 100644
index 0000000..2e562cb
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/documentfeatures.js
@@ -0,0 +1,52 @@
+exports.availableDocumentFeatures = [
+ 'FetchExternalResources',
+ 'ProcessExternalResources',
+ 'MutationEvents',
+ 'SkipExternalResources'
+];
+
+exports.defaultDocumentFeatures = {
+ "FetchExternalResources": ['script', 'link'/*, 'img', 'css', 'frame'*/],
+ "ProcessExternalResources": ['script'/*, 'frame', 'iframe'*/],
+ "MutationEvents": '2.0',
+ "SkipExternalResources": false
+};
+
+exports.applyDocumentFeatures = function(doc, features) {
+ var i, maxFeatures = exports.availableDocumentFeatures.length,
+ defaultFeatures = exports.defaultDocumentFeatures,
+ j,
+ k,
+ featureName,
+ featureSource;
+
+ features = features || {};
+
+ for (i=0; i<maxFeatures; i++) {
+ featureName = exports.availableDocumentFeatures[i];
+ if (typeof features[featureName] !== 'undefined') {
+ featureSource = features[featureName];
+ // We have to check the lowercase version also because the Document feature
+ // methods convert everything to lowercase.
+ } else if (typeof features[featureName.toLowerCase()] !== 'undefined') {
+ featureSource = features[featureName.toLowerCase()];
+ } else if (defaultFeatures[featureName]) {
+ featureSource = defaultFeatures[featureName];
+ } else {
+ continue;
+ }
+
+ doc.implementation._removeFeature(featureName);
+
+ if (typeof featureSource !== 'undefined') {
+ if (featureSource instanceof Array) {
+ k = featureSource.length;
+ for (j=0; j<k; j++) {
+ doc.implementation._addFeature(featureName, featureSource[j]);
+ }
+ } else {
+ doc.implementation._addFeature(featureName, featureSource);
+ }
+ }
+ }
+};
diff --git a/node_modules/jsdom/lib/jsdom/browser/domtohtml.js b/node_modules/jsdom/lib/jsdom/browser/domtohtml.js
new file mode 100644
index 0000000..0c256c4
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/domtohtml.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var parse5 = require('parse5');
+var serializer = new parse5.TreeSerializer(require('./documentAdapter'));
+
+exports.domToHtml = function(dom) {
+ if (dom._toArray) {
+ // node list
+ dom = dom._toArray();
+ }
+ if (typeof dom.length !== "undefined") {
+ var ret = "";
+ for (var i = 0, len = dom.length; i < len; i++) {
+ ret += dom[i].nodeType === dom.DOCUMENT_NODE ?
+ serializer.serialize(dom[i]) :
+ serializer.serialize({ childNodes: [dom[i]] });
+ }
+ return ret;
+ } else {
+ return dom.nodeType === dom.DOCUMENT_NODE ?
+ serializer.serialize(dom) :
+ serializer.serialize({ childNodes: [dom] });
+ }
+};
diff --git a/node_modules/jsdom/lib/jsdom/browser/history.js b/node_modules/jsdom/lib/jsdom/browser/history.js
new file mode 100644
index 0000000..674b4ad
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/history.js
@@ -0,0 +1,94 @@
+"use strict";
+var URL = require("url");
+var resolveHref = require("../utils").resolveHref;
+
+function StateEntry(data, title, url) {
+ this.data = data;
+ this.title = title;
+ this.url = url;
+}
+
+module.exports = History;
+
+function History(window) {
+ this._states = [new StateEntry(null, "", window.location._url.href)];
+ this._index = 0;
+ this._window = window;
+ this._location = window.location;
+}
+
+History.prototype = {
+ constructor: History,
+
+ get length() {
+ return this._states.length;
+ },
+
+ get state() {
+ var state = this._states[this._index];
+ return state ? state.data : null;
+ },
+
+ back: function () {
+ this.go(-1);
+ },
+
+ forward: function () {
+ this.go(1);
+ },
+
+ go: function (delta) {
+ if (typeof delta === "undefined" || delta === 0) {
+ this._location.reload();
+ return;
+ }
+
+ var newIndex = this._index + delta;
+
+ if (newIndex < 0 || newIndex >= this.length) {
+ return;
+ }
+
+ this._index = newIndex;
+
+ var state = this._states[newIndex];
+
+ this._applyState(state);
+ this._signalPopstate(state);
+ },
+
+ pushState: function (data, title, url) {
+ var state = new StateEntry(data, title, url);
+ if (this._index + 1 !== this._states.length) {
+ this._states = this._states.slice(0, this._index + 1);
+ }
+ this._states.push(state);
+ this._applyState(state);
+ this._index++;
+ },
+
+ replaceState: function (data, title, url) {
+ var state = new StateEntry(data, title, url);
+ this._states[this._index] = state;
+ this._applyState(state);
+ },
+
+ _applyState: function (state) {
+ this._location._url = URL.parse(resolveHref(this._location._url.href, state.url));
+ },
+
+ _signalPopstate: function(state) {
+ if (this._window.document) {
+ var ev = this._window.document.createEvent("HTMLEvents");
+ ev.initEvent("popstate", false, false);
+ ev.state = state.data;
+ process.nextTick(function () {
+ this._window.dispatchEvent(ev);
+ }.bind(this));
+ }
+ },
+
+ toString: function () {
+ return "[object History]";
+ }
+};
diff --git a/node_modules/jsdom/lib/jsdom/browser/htmltodom.js b/node_modules/jsdom/lib/jsdom/browser/htmltodom.js
new file mode 100644
index 0000000..88908dc
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/htmltodom.js
@@ -0,0 +1,231 @@
+var parse5 = require('parse5');
+var htmlparser2 = require('htmlparser2');
+
+function HtmlToDom(parser, parsingMode) {
+ if (!parser) {
+ if (parsingMode === "xml") {
+ parser = htmlparser2;
+ } else {
+ parser = parse5;
+ }
+ }
+
+ if (parser.DefaultHandler || (parser.Parser && parser.TreeAdapters)) {
+
+ // Forgiving HTML parser
+
+ if (parser.DefaultHandler){
+ // fb55/htmlparser2
+
+ parser.ParseHtml = function(rawHtml) {
+ var handler = new parser.DefaultHandler();
+ // Check if document is XML
+ var isXML = parsingMode === "xml";
+ var parserInstance = new parser.Parser(handler, {
+ xmlMode: isXML,
+ lowerCaseTags: !isXML,
+ lowerCaseAttributeNames: !isXML,
+ decodeEntities: true
+ });
+
+ parserInstance.includeLocation = false;
+ parserInstance.parseComplete(rawHtml);
+ return handler.dom;
+ };
+ } else if (parser.Parser && parser.TreeAdapters) {
+ parser.ParseHtml = function (rawHtml) {
+ if (parsingMode === 'xml') {
+ throw new Error('Can\'t parse XML with parse5, please use htmlparser2 instead.');
+ }
+ var instance = new parser.Parser(parser.TreeAdapters.htmlparser2);
+ var dom = instance.parse(rawHtml);
+ return dom.children;
+ };
+ }
+
+ this.appendHtmlToElement = function(html, element) {
+
+ if (typeof html !== 'string') {
+ html +='';
+ }
+
+ var parsed = parser.ParseHtml(html);
+
+ for (var i = 0; i < parsed.length; i++) {
+ setChild(element, parsed[i]);
+ }
+
+ return element;
+ };
+ this.appendHtmlToDocument = this.appendHtmlToElement;
+
+ if (parser.Parser && parser.TreeAdapters) {
+ this.appendHtmlToElement = function (html, element) {
+
+ if (typeof html !== 'string') {
+ html += '';
+ }
+
+ var instance = new parser.Parser(parser.TreeAdapters.htmlparser2);
+ var parentElement = parser.TreeAdapters.htmlparser2.createElement(element.tagName.toLowerCase(), element.namespaceURI, []);
+ var dom = instance.parseFragment(html, parentElement);
+ var parsed = dom.children;
+
+ for (var i = 0; i < parsed.length; i++) {
+ setChild(element, parsed[i]);
+ }
+
+ return element;
+ };
+ }
+
+ } else if (parser.moduleName == 'HTML5') { /* HTML5 parser */
+ this.appendHtmlToElement = function(html, element) {
+
+ if (typeof html !== 'string') {
+ html += '';
+ }
+ if (html.length > 0) {
+ if (element.nodeType == 9) {
+ new parser.Parser({document: element}).parse(html);
+ }
+ else {
+ var p = new parser.Parser({document: element.ownerDocument});
+ p.parse_fragment(html, element);
+ }
+ }
+ };
+ } else {
+ this.appendHtmlToElement = function () {
+ console.log('');
+ console.log('###########################################################');
+ console.log('# WARNING: No compatible HTML parser was given.');
+ console.log('# Element.innerHTML setter support has been disabled');
+ console.log('# Element.innerHTML getter support will still function');
+ console.log('###########################################################');
+ console.log('');
+ };
+ }
+};
+
+// utility function for forgiving parser
+function setChild(parent, node) {
+
+ var c, newNode, currentDocument = parent._ownerDocument || parent;
+
+ switch (node.type)
+ {
+ case 'tag':
+ case 'script':
+ case 'style':
+ try {
+ newNode = currentDocument._createElementNoTagNameValidation(node.name);
+ newNode._namespaceURI = node.namespace || "http://www.w3.org/1999/xhtml";
+ if (node.location) {
+ newNode.sourceLocation = node.location;
+ newNode.sourceLocation.file = parent.sourceLocation.file;
+ }
+ } catch (err) {
+ currentDocument.raise('error', 'invalid markup', {
+ exception: err,
+ node : node
+ });
+
+ return null;
+ }
+ break;
+
+ case 'root':
+ // If we are in <template> then implicitly create #document-fragment for it's content
+ if(parent.tagName === 'TEMPLATE' && parent._namespaceURI === 'http://www.w3.org/1999/xhtml') {
+ newNode = currentDocument.createDocumentFragment();
+ // Mark fragment as parser-created template content, so it will be accepted by appendChild()
+ newNode._templateContent = true;
+ }
+ break;
+
+ case 'text':
+ // HTML entities should already be decoded by the parser, so no need to decode them
+ newNode = currentDocument.createTextNode(node.data);
+ break;
+
+ case 'comment':
+ newNode = currentDocument.createComment(node.data);
+ break;
+
+ case 'directive':
+ if (node.name[0] === '?' && node.name.toLowerCase() !== '?xml') {
+ var data = node.data.slice(node.name.length + 1, -1);
+ newNode = currentDocument.createProcessingInstruction(node.name.substring(1), data);
+ } else if (node.name.toLowerCase() === '!doctype') {
+ newNode = parseDocType(currentDocument, '<' + node.data + '>');
+ }
+ break;
+
+ default:
+ return null;
+ break;
+ }
+
+ if (!newNode)
+ return null;
+
+ newNode._localName = node.name;
+
+ if (node.attribs) {
+ for (c in node.attribs) {
+ var prefix = node['x-attribsPrefix'] && node['x-attribsPrefix'][c] ? node['x-attribsPrefix'][c] + ':' : '';
+ // catchin errors here helps with improperly escaped attributes
+ // but properly fixing parent should (can only?) be done in the htmlparser itself
+ try {
+ newNode._setAttributeNoValidation(prefix + c, node.attribs[c]);
+ newNode.attributes[prefix + c]._namespaceURI = node['x-attribsNamespace'][c] || null;
+ newNode.attributes[prefix + c]._prefix = node['x-attribsPrefix'][c] || null;
+ newNode.attributes[prefix + c]._localName = c;
+ } catch(e2) { /* noop */ }
+ }
+ }
+
+ if (node.children) {
+ for (c = 0; c < node.children.length; c++) {
+ setChild(newNode, node.children[c]);
+ }
+ }
+
+ try{
+ return parent.appendChild(newNode);
+ }catch(err){
+ currentDocument.raise('error', err.message, {
+ exception: err,
+ node : node
+ });
+ return null;
+ }
+}
+
+var HTML5_DOCTYPE = /<!doctype html>/i;
+var PUBLIC_DOCTYPE = /<!doctype\s+([^\s]+)\s+public\s+"([^"]+)"\s+"([^"]+)"/i;
+var SYSTEM_DOCTYPE = /<!doctype\s+([^\s]+)\s+system\s+"([^"]+)"/i;
+
+function parseDocType(doc, html) {
+ if (HTML5_DOCTYPE.test(html)) {
+ return doc.implementation.createDocumentType("html", "", "");
+ }
+
+ var publicPieces = PUBLIC_DOCTYPE.exec(html);
+ if (publicPieces) {
+ return doc.implementation.createDocumentType(publicPieces[1], publicPieces[2], publicPieces[3]);
+ }
+
+ var systemPieces = SYSTEM_DOCTYPE.exec(html);
+ if (systemPieces) {
+ return doc.implementation.createDocumentType(systemPieces[1], systemPieces[2], "");
+ }
+
+ // Shouldn't get here (the parser shouldn't let us know about invalid doctypes), but our logic likely isn't
+ // real-world perfect, so let's fallback.
+ return doc.implementation.createDocumentType("html", "", "");
+}
+
+
+exports.HtmlToDom = HtmlToDom;
diff --git a/node_modules/jsdom/lib/jsdom/browser/index.js b/node_modules/jsdom/lib/jsdom/browser/index.js
new file mode 100644
index 0000000..0d44dc1
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/index.js
@@ -0,0 +1,85 @@
+var HtmlToDom = require('./htmltodom').HtmlToDom,
+ jsdom = require('../../jsdom'),
+ defineGetter = require('../utils').defineGetter,
+ defineSetter = require('../utils').defineSetter,
+ Contextify = require('contextify'),
+ Window = require('./Window');
+
+function windowAugmentation(document) {
+ var window = createWindow(document);
+
+ if (document.addEventListener) {
+ if (document.readyState == 'complete') {
+ var ev = document.createEvent('HTMLEvents');
+ ev.initEvent('load', false, false);
+ process.nextTick(function () {
+ window.dispatchEvent(ev);
+ });
+ }
+ else {
+ document.addEventListener('load', function(ev) {
+ window.dispatchEvent(ev);
+ });
+ }
+ }
+
+ return window;
+};
+
+function createWindow(document) {
+ var window = new Window(document);
+
+ Contextify(window);
+
+ // We need to set up self references using Contextify's getGlobal() so that
+ // the global object identity is correct (window === this).
+ // See Contextify README for more info.
+ var windowGlobal = window.getGlobal();
+
+ // Set up the window as if it's a top level window.
+ // If it's not, then references will be corrected by frame/iframe code.
+ // Note: window.frames is maintained in the HTMLFrameElement init function.
+ window.window = window.frames
+ = window.self
+ = window.parent
+ = window.top = windowGlobal;
+
+ return window;
+};
+
+/**
+ * Augments the given DOM by adding browser-specific properties and methods (BOM).
+ * Returns the augmented DOM.
+ */
+// TODO: this function is HORIBBLE. It modifies the *shared* `dom` variable with document-specific stuff.
+// We call it in `jsdom.jsdom`, i.e. per-document. The checks `if (dom._augment && ...)` just mean that it won't modify
+// the global twice *for the same options*. Bad stuff.
+//
+// None of the properties set here should be on `dom`, really.
+exports.browserAugmentation = function (dom, options) {
+ if (!options) {
+ options = {};
+ }
+
+ var parser = options.parser;
+
+ if (dom._augmented && dom._parser === parser && dom._parsingMode === options.parsingMode) {
+ return dom;
+ }
+
+ dom._parser = parser;
+ dom._parsingMode = options.parsingMode;
+ var htmltodom = new HtmlToDom(parser, options.parsingMode);
+ dom.Document.prototype._htmlToDom = htmltodom;
+ dom.Document.prototype._domImpl = dom.DOMImplementation;
+
+ defineGetter(dom.Document.prototype, 'parentWindow', function() {
+ if (!this._parentWindow) {
+ this.parentWindow = windowAugmentation(this);
+ }
+ return this._parentWindow;
+ });
+
+ dom._augmented = true;
+ return dom;
+};
diff --git a/node_modules/jsdom/lib/jsdom/browser/location.js b/node_modules/jsdom/lib/jsdom/browser/location.js
new file mode 100644
index 0000000..92ece8c
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/location.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var URL = require("url");
+var resolveHref = require("../utils").resolveHref;
+var NOT_IMPLEMENTED = require("./utils").NOT_IMPLEMENTED;
+
+module.exports = Location;
+
+function Location(urlString, document) {
+ this._url = URL.parse(urlString);
+ this._document = document;
+}
+
+Location.prototype = {
+ constructor: Location,
+ reload: function () {
+ NOT_IMPLEMENTED("location.reload", this._document)();
+ },
+ get protocol() { return this._url.protocol || ":"; },
+ get host() { return this._url.host || ""; },
+ get auth() { return this._url.auth; },
+ get hostname() { return this._url.hostname || ""; },
+ get origin() {
+ return ((this._url.protocol !== undefined && this._url.protocol !== null) ?
+ this._url.protocol + "//" :
+ this._url.protocol) +
+ this._url.host || "";
+ },
+ get port() { return this._url.port || ""; },
+ get pathname() { return this._url.pathname || ""; },
+ get href() { return this._url.href; },
+ get hash() { return this._url.hash || ""; },
+ get search() { return this._url.search || ""; },
+
+ set href(val) {
+ var oldUrl = this._url.href;
+ var oldProtocol = this._url.protocol;
+ var oldHost = this._url.host;
+ var oldPathname = this._url.pathname;
+ var oldHash = this._url.hash || "";
+
+ this._url = URL.parse(resolveHref(oldUrl, val));
+ var newUrl = this._url.href;
+ var newProtocol = this._url.protocol;
+ var newHost = this._url.host;
+ var newPathname = this._url.pathname;
+ var newHash = this._url.hash || "";
+
+ if (oldProtocol === newProtocol && oldHost === newHost && oldPathname === newPathname && oldHash !== newHash) {
+ this._signalHashChange(oldUrl, newUrl);
+ } else {
+ NOT_IMPLEMENTED("location.href (no reload)", this._document)();
+ }
+ },
+
+ set hash(val) {
+ var oldUrl = this._url.href;
+ var oldHash = this._url.hash || "";
+
+ if (val.lastIndexOf("#", 0) !== 0) {
+ val = "#" + val;
+ }
+
+ this._url = URL.parse(resolveHref(oldUrl, val));
+ var newUrl = this._url.href;
+ var newHash = this._url.hash || "";
+
+ if (oldHash !== newHash) {
+ this._signalHashChange(oldUrl, newUrl);
+ }
+ },
+
+ set search(val) {
+ var oldUrl = this._url.href;
+ var oldHash = this._url.hash || "";
+ if (val.length) {
+ if (val.lastIndexOf("?", 0) !== 0) {
+ val = "?" + val;
+ }
+ this._url = URL.parse(resolveHref(oldUrl, val + oldHash));
+ } else {
+ this._url = URL.parse(oldUrl.replace(/\?([^#]+)/, ""));
+ }
+ },
+
+ replace: function (val) {
+ this.href = val;
+ },
+
+ toString: function () {
+ return this._url.href;
+ },
+
+ _signalHashChange: function (oldUrl, newUrl) {
+ var ev = this._document.createEvent("HTMLEvents");
+ ev.initEvent("hashchange", false, false);
+ ev.oldUrl = oldUrl;
+ ev.newUrl = newUrl;
+ process.nextTick(function () {
+ this._document.parentWindow.dispatchEvent(ev);
+ }.bind(this));
+ }
+};
diff --git a/node_modules/jsdom/lib/jsdom/browser/utils.js b/node_modules/jsdom/lib/jsdom/browser/utils.js
new file mode 100644
index 0000000..87e952e
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/browser/utils.js
@@ -0,0 +1,15 @@
+"use strict";
+
+exports.NOT_IMPLEMENTED = function (nameForErrorMessage, target) {
+ return function () {
+ if (target === undefined) {
+ target = this;
+ }
+
+ if (target && target.raise) {
+ target.raise("error", "NOT_IMPLEMENTED: " + nameForErrorMessage);
+ } else if (typeof console !== "undefined" && console.log) {
+ console.log(new Error("Called NOT_IMPLEMENTED without an element to raise on: " + nameForErrorMessage));
+ }
+ };
+};
diff --git a/node_modules/jsdom/lib/jsdom/contextify-shim.js b/node_modules/jsdom/lib/jsdom/contextify-shim.js
new file mode 100644
index 0000000..0bf22a9
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/contextify-shim.js
@@ -0,0 +1,53 @@
+"use strict";
+
+var acorn = require("acorn");
+var findGlobals = require("acorn-globals");
+var escodegen = require("escodegen");
+
+module.exports = function (o) {
+ o.getGlobal = function () {
+ return o;
+ };
+
+ o.run = function (code, filename) {
+ var comments = [], tokens = [];
+ var ast = acorn.parse(code, {
+ ecmaVersion: 6,
+ allowReturnOutsideFunction: true,
+ ranges: true,
+ // collect comments in Esprima's format
+ onComment: comments,
+ // collect token ranges
+ onToken: tokens
+ });
+
+ // make sure we keep comments
+ escodegen.attachComments(ast, comments, tokens);
+
+ var globals = findGlobals(ast);
+ for (var i = 0; i < globals.length; ++i) {
+ if (globals[i].name === "window") {
+ continue;
+ }
+
+ var nodes = globals[i].nodes;
+ for (var j = 0; j < nodes.length; ++j) {
+ var type = nodes[j].type;
+ var name = nodes[j].name;
+ nodes[j].type = "MemberExpression";
+ nodes[j].property = {
+ name: name,
+ type: type
+ };
+ nodes[j].computed = false;
+ nodes[j].object = {
+ name: "window",
+ type: "Identifier"
+ };
+ }
+ }
+
+ code = escodegen.generate(ast, { comment: true });
+ new Function("window", code + "\n//# sourceURL=" + filename).bind(o)(o); // jshint ignore:line
+ };
+};
diff --git a/node_modules/jsdom/lib/jsdom/level1/core.js b/node_modules/jsdom/lib/jsdom/level1/core.js
new file mode 100644
index 0000000..26c8484
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level1/core.js
@@ -0,0 +1,1794 @@
+/*
+ ServerJS Javascript DOM Level 1
+*/
+var inheritFrom = require("../utils").inheritFrom;
+var domToHtml = require("../browser/domtohtml").domToHtml;
+var defineGetter = require("../utils").defineGetter;
+var memoizeQuery = require("../utils").memoizeQuery;
+var validateName = require('../living/helpers/validate-names').name;
+var Location = require("../browser/location");
+
+// utility functions
+var attachId = function(id,elm,doc) {
+ if (id && elm && doc) {
+ if (!doc._ids[id]) {
+ doc._ids[id] = [];
+ }
+ doc._ids[id].push(elm);
+ }
+};
+var detachId = function(id,elm,doc) {
+ var elms, i;
+ if (id && elm && doc) {
+ if (doc._ids && doc._ids[id]) {
+ elms = doc._ids[id];
+ for (i=0;i<elms.length;i++) {
+ if (elms[i] === elm) {
+ elms.splice(i,1);
+ i--;
+ }
+ }
+ if (elms.length === 0) {
+ delete doc._ids[id];
+ }
+ }
+ }
+};
+
+function setInnerHTML(dom, node, html) {
+ //Clear the children first:
+ var child;
+ while ((child = node._childNodes[0])) {
+ node.removeChild(child);
+ }
+
+ var isDoc = node.nodeName === '#document';
+ if (html !== "" && html != null) {
+ if (isDoc) {
+ dom._htmlToDom.appendHtmlToDocument(html, node);
+ } else {
+ dom._htmlToDom.appendHtmlToElement(html, node);
+ }
+ }
+}
+
+// TODO: move all of these to utils.js. Right now they are exposed on window, which is bizarre.
+var core = module.exports = {
+ mapper: function(parent, filter, recursive) {
+ return function() {
+ return core.mapDOMNodes(parent, recursive !== false, filter);
+ };
+ },
+
+ // Returns Array
+ mapDOMNodes : function(parent, recursive, callback) {
+ function visit(parent, result) {
+ return parent._childNodes.reduce(reducer, result);
+ }
+
+ function reducer(array, child) {
+ if (callback(child)) {
+ array.push(child);
+ }
+ if (recursive && child._childNodes) {
+ visit(child, array);
+ }
+ return array;
+ }
+
+ return visit(parent, []);
+ },
+
+ visitTree: function(root, callback) {
+ var cur = root; // TODO: Unroll this.
+
+ function visit(el) {
+ if (el) {
+ callback(el);
+ if (el._childNodes) {
+ var i = 0,
+ children = el._childNodes,
+ l = children.length;
+
+ for (i; i<l; i++) {
+ visit(children[i]);
+ }
+ }
+ }
+ }
+ visit(root);
+ },
+
+ markTreeReadonly: function(node) {
+ function markLevel(el) {
+ el._readonly = true;
+ // also mark attributes and their children read-only
+ if (el.attributes) {
+ var attributes = el.attributes, l = attributes.length, i=0;
+ attributes._readonly = true;
+
+ for (i; i<l; i++) {
+ core.visitTree(attributes[i], markLevel);
+ }
+ }
+ }
+
+ core.visitTree(node, markLevel);
+ }
+};
+
+// ExceptionCode
+var INDEX_SIZE_ERR = core.INDEX_SIZE_ERR = 1,
+ DOMSTRING_SIZE_ERR = core.DOMSTRING_SIZE_ERR = 2,
+ HIERARCHY_REQUEST_ERR = core.HIERARCHY_REQUEST_ERR = 3,
+ WRONG_DOCUMENT_ERR = core.WRONG_DOCUMENT_ERR = 4,
+ INVALID_CHARACTER_ERR = core.INVALID_CHARACTER_ERR = 5,
+ NO_DATA_ALLOWED_ERR = core.NO_DATA_ALLOWED_ERR = 6,
+ NO_MODIFICATION_ALLOWED_ERR = core.NO_MODIFICATION_ALLOWED_ERR = 7,
+ NOT_FOUND_ERR = core.NOT_FOUND_ERR = 8,
+ NOT_SUPPORTED_ERR = core.NOT_SUPPORTED_ERR = 9,
+ INUSE_ATTRIBUTE_ERR = core.INUSE_ATTRIBUTE_ERR = 10,
+ INVALID_STATE_ERR = core.INVALID_STATE_ERR = 11,
+ SYNTAX_ERR = core.SYNTAX_ERR = 12,
+ INVALID_MODIFICATION_ERR = core.INVALID_MODIFICATION_ERR = 13,
+ NAMESPACE_ERR = core.NAMESPACE_ERR = 14,
+ INVALID_ACCESS_ERR = core.INVALID_ACCESS_ERR = 15,
+// Node Types
+ ELEMENT_NODE = 1,
+ ATTRIBUTE_NODE = 2,
+ TEXT_NODE = 3,
+ CDATA_SECTION_NODE = 4,
+ ENTITY_REFERENCE_NODE = 5,
+ ENTITY_NODE = 6,
+ PROCESSING_INSTRUCTION_NODE = 7,
+ COMMENT_NODE = 8,
+ DOCUMENT_NODE = 9,
+ DOCUMENT_TYPE_NODE = 10,
+ DOCUMENT_FRAGMENT_NODE = 11,
+ NOTATION_NODE = 12;
+
+var messages = core.exceptionMessages = { };
+messages[INDEX_SIZE_ERR] = "Index size error";
+messages[DOMSTRING_SIZE_ERR] = "DOMString size error";
+messages[HIERARCHY_REQUEST_ERR] = "Hierarchy request error";
+messages[WRONG_DOCUMENT_ERR] = "Wrong document";
+messages[INVALID_CHARACTER_ERR] = "Invalid character";
+messages[NO_DATA_ALLOWED_ERR] = "No data allowed";
+messages[NO_MODIFICATION_ALLOWED_ERR] = "No modification allowed";
+messages[NOT_FOUND_ERR] = "Not found";
+messages[NOT_SUPPORTED_ERR] = "Not supported";
+messages[INUSE_ATTRIBUTE_ERR] = "Attribute in use";
+messages[NAMESPACE_ERR] = "Invalid namespace";
+
+core.DOMException = function DOMException(code, message) {
+ Error.call(this, core.exceptionMessages[code]);
+ this.message = core.exceptionMessages[code];
+ this.code = code;
+
+ if (message) {
+ this.message = this.message + ": " + message;
+ }
+
+ if (Error.captureStackTrace) {
+
+ Error.captureStackTrace(this, DOMException);
+ }
+};
+
+core.DOMException.INDEX_SIZE_ERR = INDEX_SIZE_ERR;
+core.DOMException.DOMSTRING_SIZE_ERR = DOMSTRING_SIZE_ERR;
+core.DOMException.HIERARCHY_REQUEST_ERR = HIERARCHY_REQUEST_ERR;
+core.DOMException.WRONG_DOCUMENT_ERR = WRONG_DOCUMENT_ERR;
+core.DOMException.INVALID_CHARACTER_ERR = INVALID_CHARACTER_ERR;
+core.DOMException.NO_DATA_ALLOWED_ERR = NO_DATA_ALLOWED_ERR;
+core.DOMException.NO_MODIFICATION_ALLOWED_ERR = NO_MODIFICATION_ALLOWED_ERR;
+core.DOMException.NOT_FOUND_ERR = NOT_FOUND_ERR;
+core.DOMException.NOT_SUPPORTED_ERR = NOT_SUPPORTED_ERR;
+core.DOMException.INUSE_ATTRIBUTE_ERR = INUSE_ATTRIBUTE_ERR;
+core.DOMException.INVALID_STATE_ERR = INVALID_STATE_ERR;
+core.DOMException.SYNTAX_ERR = SYNTAX_ERR;
+core.DOMException.INVALID_MODIFICATION_ERR = INVALID_MODIFICATION_ERR;
+core.DOMException.NAMESPACE_ERR = NAMESPACE_ERR;
+core.DOMException.INVALID_ACCESS_ERR = INVALID_ACCESS_ERR;
+
+inheritFrom(Error, core.DOMException, {
+ name: "DOMException",
+ INDEX_SIZE_ERR : INDEX_SIZE_ERR,
+ DOMSTRING_SIZE_ERR : DOMSTRING_SIZE_ERR,
+ HIERARCHY_REQUEST_ERR : HIERARCHY_REQUEST_ERR,
+ WRONG_DOCUMENT_ERR : WRONG_DOCUMENT_ERR,
+ INVALID_CHARACTER_ERR : INVALID_CHARACTER_ERR,
+ NO_DATA_ALLOWED_ERR : NO_DATA_ALLOWED_ERR,
+ NO_MODIFICATION_ALLOWED_ERR : NO_MODIFICATION_ALLOWED_ERR,
+ NOT_FOUND_ERR : NOT_FOUND_ERR,
+ NOT_SUPPORTED_ERR : NOT_SUPPORTED_ERR,
+ INUSE_ATTRIBUTE_ERR : INUSE_ATTRIBUTE_ERR,
+ INVALID_STATE_ERR : INVALID_STATE_ERR,
+ SYNTAX_ERR : SYNTAX_ERR,
+ INVALID_MODIFICATION_ERR : INVALID_MODIFICATION_ERR,
+ NAMESPACE_ERR : NAMESPACE_ERR,
+ INVALID_ACCESS_ERR : INVALID_ACCESS_ERR
+});
+
+core.NodeList = function NodeList(element, query) {
+ if (!query) {
+ // Non-live NodeList
+ if (Array.isArray(element)) {
+ Array.prototype.push.apply(this, element);
+ }
+ Object.defineProperties(this, {
+ _length: {value: element ? element.length : 0, writable:true}
+ });
+ } else {
+ Object.defineProperties(this, {
+ _element: {value: element},
+ _query: {value: query},
+ _snapshot: {writable: true},
+ _length: {value: 0, writable: true},
+ _version: {value: -1, writable: true}
+ });
+ this._update();
+ }
+};
+
+function lengthFromProperties(arrayLike) {
+ var max = -1;
+ var keys = Object.keys(arrayLike);
+ var highestKeyIndex = keys.length - 1;
+
+ // abuses a v8 implementation detail for a very fast case
+ // (if this implementation detail changes, this method will still
+ // return correct results)
+ if (highestKeyIndex == keys[highestKeyIndex]) { // not ===
+ return keys.length;
+ }
+
+ for (var i = highestKeyIndex; i >= 0 ; --i) {
+ var asNumber = + keys[i];
+
+ if (!isNaN(asNumber) && asNumber > max) {
+ max = asNumber;
+ }
+ }
+ return max + 1;
+}
+core.NodeList.prototype = {
+ _update: function() {
+ var i;
+
+ if (!this._element) {
+ this._length = lengthFromProperties(this);
+ } else {
+ if (this._version < this._element._version) {
+ var nodes = this._snapshot = this._query();
+ this._resetTo(nodes);
+ this._version = this._element._version;
+ }
+ }
+ },
+ _resetTo: function(array) {
+ var startingLength = lengthFromProperties(this);
+ for (var i = 0; i < startingLength; ++i) {
+ delete this[i];
+ }
+
+ for (var j = 0; j < array.length; ++j) {
+ this[j] = array[j];
+ }
+ this._length = array.length;
+ },
+ _toArray: function() {
+ if (this._element) {
+ this._update();
+ return this._snapshot;
+ }
+
+ return Array.prototype.slice.call(this);
+ },
+ get length() {
+ this._update();
+ return this._length || 0;
+ },
+ set length(length) {
+ return this._length;
+ },
+ item: function(index) {
+ this._update();
+ return this[index] || null;
+ },
+ toString: function() {
+ return '[ jsdom NodeList ]: contains ' + this.length + ' items';
+ }
+};
+Object.defineProperty(core.NodeList.prototype, 'constructor', {
+ value: core.NodeList,
+ writable: true,
+ configurable: true
+});
+
+core.DOMImplementation = function DOMImplementation(document, /* Object */ features) {
+ this._ownerDocument = document;
+ this._features = {};
+
+ if (features) {
+ for (var feature in features) {
+ if (features.hasOwnProperty(feature)) {
+ this._addFeature(feature.toLowerCase(), features[feature]);
+ }
+ }
+ }
+};
+
+core.DOMImplementation.prototype = {
+ // All of these are legacy, left because jsdom uses them internally :(. jsdom confused the idea of browser features
+ // and jsdom features
+ _removeFeature : function(feature, version) {
+ feature = feature.toLowerCase();
+ if (this._features[feature]) {
+ if (version) {
+ var j = 0,
+ versions = this._features[feature],
+ l = versions.length;
+
+ for (j; j<l; j++) {
+ if (versions[j] === version) {
+ versions.splice(j,1);
+ return;
+ }
+ }
+ } else {
+ delete this._features[feature];
+ }
+ }
+ },
+
+ _addFeature: function(feature, version) {
+ feature = feature.toLowerCase();
+
+ if (version) {
+
+ if (!this._features[feature]) {
+ this._features[feature] = [];
+ }
+
+ if (version instanceof Array) {
+ Array.prototype.push.apply(this._features[feature], version);
+ } else {
+ this._features[feature].push(version);
+ }
+ }
+ },
+
+ // The real hasFeature is in living/dom-implementation.js, and returns true always.
+ // This one is used internally
+ _hasFeature: function(/* string */ feature, /* string */ version) {
+ feature = (feature) ? feature.toLowerCase() : '';
+ var versions = (this._features[feature]) ?
+ this._features[feature] :
+ false;
+
+ if (!version && versions.length && versions.length > 0) {
+ return true;
+ } else if (typeof versions === 'string') {
+ return versions === version;
+ } else if (versions.indexOf && versions.length > 0) {
+ for (var i = 0; i < versions.length; i++) {
+ var found = versions[i] instanceof RegExp ?
+ versions[i].test(version) :
+ versions[i] === version;
+ if (found) { return true; }
+ }
+ return false;
+ } else {
+ return false;
+ }
+ }
+};
+
+
+var attrCopy = function(src, dest, fn) {
+ if (src.attributes) {
+ var attrs = src.attributes, i, l = attrs.length, attr, copied;
+ for (i=0;i<l;i++) {
+ attr = attrs[i];
+ // skip over default attributes
+ if (!attr.specified) {
+ continue;
+ }
+ // TODO: consider duplicating this code and moving it into level2/core
+ if (attr.namespaceURI) {
+ dest.setAttributeNS(attr.namespaceURI,
+ attr.name,
+ attr.value);
+ var localName = attr.name.split(':').pop();
+ copied = dest.getAttributeNodeNS(attr.namespaceURI, localName);
+ } else {
+ dest.setAttribute(attr.name, attr.value);
+ copied = dest.getAttributeNode(attr.name);
+ }
+ if (typeof fn == "function") {
+ fn(attr, copied);
+ }
+
+ }
+ }
+ return dest;
+};
+
+core.Node = function Node(ownerDocument) {
+ this._childNodes = [];
+ this._childNodesList = null;
+ this._ownerDocument = ownerDocument;
+ this._attributes = new AttributeList(ownerDocument, this);
+ this._childrenList = null;
+ this._version = 0;
+ this._parentNode = null;
+ this._memoizedQueries = {};
+ this._readonly = false;
+};
+
+core.Node.ELEMENT_NODE = ELEMENT_NODE;
+core.Node.ATTRIBUTE_NODE = ATTRIBUTE_NODE;
+core.Node.TEXT_NODE = TEXT_NODE;
+core.Node.CDATA_SECTION_NODE = CDATA_SECTION_NODE;
+core.Node.ENTITY_REFERENCE_NODE = ENTITY_REFERENCE_NODE;
+core.Node.ENTITY_NODE = ENTITY_NODE;
+core.Node.PROCESSING_INSTRUCTION_NODE = PROCESSING_INSTRUCTION_NODE;
+core.Node.COMMENT_NODE = COMMENT_NODE;
+core.Node.DOCUMENT_NODE = DOCUMENT_NODE;
+core.Node.DOCUMENT_TYPE_NODE = DOCUMENT_TYPE_NODE;
+core.Node.DOCUMENT_FRAGMENT_NODE = DOCUMENT_FRAGMENT_NODE;
+core.Node.NOTATION_NODE = NOTATION_NODE;
+
+core.Node.prototype = {
+ ELEMENT_NODE : ELEMENT_NODE,
+ ATTRIBUTE_NODE : ATTRIBUTE_NODE,
+ TEXT_NODE : TEXT_NODE,
+ CDATA_SECTION_NODE : CDATA_SECTION_NODE,
+ ENTITY_REFERENCE_NODE : ENTITY_REFERENCE_NODE,
+ ENTITY_NODE : ENTITY_NODE,
+ PROCESSING_INSTRUCTION_NODE : PROCESSING_INSTRUCTION_NODE,
+ COMMENT_NODE : COMMENT_NODE,
+ DOCUMENT_NODE : DOCUMENT_NODE,
+ DOCUMENT_TYPE_NODE : DOCUMENT_TYPE_NODE,
+ DOCUMENT_FRAGMENT_NODE : DOCUMENT_FRAGMENT_NODE,
+ NOTATION_NODE : NOTATION_NODE,
+
+ get children() {
+ if (!this._childrenList) {
+ var self = this;
+ this._childrenList = new core.NodeList(this, function() {
+ return self._childNodes.filter(function(node) {
+ return node.tagName;
+ });
+ });
+ }
+ this._childrenList._update();
+ return this._childrenList;
+ },
+ get nodeValue() {
+ if (this.nodeType === core.Node.TEXT_NODE ||
+ this.nodeType === core.Node.COMMENT_NODE ||
+ this.nodeType === core.Node.PROCESSING_INSTRUCTION_NODE) {
+ return this._data;
+ }
+
+ return null;
+ },
+ set nodeValue(value) {
+ if (this.nodeType === core.Node.TEXT_NODE ||
+ this.nodeType === core.Node.COMMENT_NODE ||
+ this.nodeType === core.Node.PROCESSING_INSTRUCTION_NODE) {
+ this.replaceData(0, this.length, value);
+ }
+ },
+ get parentNode() { return this._parentNode;},
+
+ get nodeName() {
+ switch (this.nodeType) {
+ case ELEMENT_NODE:
+ return this.tagName;
+ case TEXT_NODE:
+ return "#text";
+ case PROCESSING_INSTRUCTION_NODE:
+ return this.target;
+ case COMMENT_NODE:
+ return "#comment";
+ case DOCUMENT_NODE:
+ return "#document";
+ case DOCUMENT_TYPE_NODE:
+ return this.name;
+ case DOCUMENT_FRAGMENT_NODE:
+ return "#document-fragment";
+ case ATTRIBUTE_NODE:
+ // TODO: remove this; attributes should not be nodes and should not have a nodeName property
+ // Removing it breaks some legit-seeming xpath tests :-/
+ return this.name;
+ }
+ },
+ set nodeName(unused) { throw new core.DOMException();},
+ get firstChild() {
+ return this._childNodes.length > 0 ? this._childNodes[0] : null;
+ },
+ get ownerDocument() { return this._ownerDocument;},
+ get readonly() { return this._readonly;},
+
+ get lastChild() {
+ var len = this._childNodes.length;
+ return len > 0 ? this._childNodes[len -1] : null;
+ },
+
+ get childNodes() {
+ if (!this._childNodesList) {
+ var self = this;
+ this._childNodesList = new core.NodeList(this, function() {
+ return self._childNodes.slice();
+ });
+ }
+ this._childNodesList._update();
+ return this._childNodesList;
+ },
+ set childNodes(unused) { throw new core.DOMException();},
+
+ _indexOf: function(/*Node*/ child) {
+ return this._childNodes.indexOf(child);
+ },
+
+ get nextSibling() {
+ // find this node's index in the parentNode, add one and call it a day
+ if (!this._parentNode || !this._parentNode._indexOf) {
+ return null;
+ }
+
+ var index = this._parentNode._indexOf(this);
+
+ if (index == -1 || index+1 >= this._parentNode._childNodes.length) {
+ return null;
+ }
+
+ return this._parentNode._childNodes[index+1] || null;
+ },
+ set nextSibling(unused) { throw new core.DOMException();},
+
+ get previousSibling() {
+ if (!this._parentNode || !this._parentNode._indexOf) {
+ return null;
+ }
+
+ var index = this._parentNode._indexOf(this);
+
+ if (index == -1 || index-1 < 0) {
+ return null;
+ }
+
+ return this._parentNode._childNodes[index-1] || null;
+ },
+ set previousSibling(unused) { throw new core.DOMException();},
+
+ /* returns Node */
+ insertBefore : function(/* Node */ newChild, /* Node*/ refChild) {
+ if (this._readonly === true) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR, 'Attempting to modify a read-only node');
+ }
+
+ // Adopt unowned children, for weird nodes like DocumentType
+ if (!newChild._ownerDocument) newChild._ownerDocument = this._ownerDocument;
+
+ // TODO - if (!newChild) then?
+ if (!(this instanceof core.Document) && newChild._ownerDocument !== this._ownerDocument) {
+ throw new core.DOMException(WRONG_DOCUMENT_ERR);
+ }
+
+ if (newChild.nodeType && newChild.nodeType === ATTRIBUTE_NODE) {
+ throw new core.DOMException(HIERARCHY_REQUEST_ERR);
+ }
+
+ // search for parents matching the newChild
+ var current = this;
+ do {
+ if (current === newChild) {
+ throw new core.DOMException(HIERARCHY_REQUEST_ERR);
+ }
+ } while((current = current._parentNode));
+
+ // fragments are merged into the element (except parser-created fragments in <template>)
+ if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE && !newChild._templateContent) {
+ var tmpNode, i = newChild._childNodes.length;
+ while (i-- > 0) {
+ tmpNode = newChild.removeChild(newChild.firstChild);
+ this.insertBefore(tmpNode, refChild);
+ }
+ } else if (newChild === refChild) {
+ return newChild;
+ } else {
+ // if the newChild is already in the tree elsewhere, remove it first
+ if (newChild._parentNode) {
+ newChild._parentNode.removeChild(newChild);
+ }
+
+ if (refChild == null) {
+ this._childNodes.push(newChild);
+ } else {
+ var refChildIndex = this._indexOf(refChild);
+ if (refChildIndex == -1) {
+ throw new core.DOMException(NOT_FOUND_ERR);
+ }
+ this._childNodes.splice(refChildIndex, 0, newChild);
+ }
+
+ newChild._parentNode = this;
+ if (this._attached && newChild._attach) {
+ newChild._attach();
+ }
+
+ this._modified();
+ this._descendantAdded(this, newChild);
+ }
+
+ return newChild;
+ }, // raises(DOMException);
+
+ _modified: function() {
+ this._version++;
+ if (this._ownerDocument) {
+ this._ownerDocument._version++;
+ }
+
+ if (this._childrenList) {
+ this._childrenList._update();
+ }
+ this._clearMemoizedQueries()
+ },
+
+ _clearMemoizedQueries: function() {
+ this._memoizedQueries = {};
+ if (this._parentNode && this._parentNode !== this) {
+ this._parentNode._clearMemoizedQueries();
+ }
+ },
+
+ _descendantRemoved: function(parent, child) {
+ if (this._parentNode && this._parentNode !== this) {
+ this._parentNode._descendantRemoved(parent, child);
+ }
+ },
+
+ _descendantAdded: function(parent, child) {
+ if (this._parentNode && this._parentNode !== this) {
+ this._parentNode._descendantAdded(parent, child);
+ }
+ },
+
+ _attrModified: function(name, value, oldValue) {
+ if (name == 'id' && this._attached) {
+ var doc = this._ownerDocument;
+ detachId(oldValue,this,doc);
+ attachId(value,this,doc);
+ }
+
+ // Check for inline event handlers.
+ // We can't set these like other attributes then look it up in
+ // dispatchEvent() because that would create 2 'traditional' event handlers
+ // in the case where there's an inline event handler attribute, plus one
+ // set using element.on* in a script.
+ //
+ // @see http://www.w3.org/TR/2011/WD-html5-20110405/webappapis.html#event-handler-content-attributes
+ if ((name.length > 2) && (name[0] == 'o') && (name[1] == 'n')) {
+ if (value) {
+ var self = this;
+ // Check whether we're the window. This can happen because inline
+ // handlers on the body are proxied to the window.
+ var w = (typeof self.run !== 'undefined') ? self : self._ownerDocument.parentWindow;
+ self[name] = function (event) {
+ // The handler code probably refers to functions declared in the
+ // window context, so we need to call run().
+
+ // Use awesome hacks to get the correct `this` context for the
+ // inline event handler. This would only be necessary if we're an
+ // element, but for the sake of simplicity we also do it on window.
+
+ // Also set event variable and support `return false`.
+ w.__tempContextForInlineEventHandler = self;
+ w.__tempEvent = event;
+ w.run("if ((function (event) {" + value + "}).call(" +
+ "window.__tempContextForInlineEventHandler, window.__tempEvent) === false) {" +
+ "window.__tempEvent.preventDefault()}");
+ delete w.__tempContextForInlineEventHandler;
+ delete w.__tempEvent;
+ };
+ } else {
+ this[name] = null;
+ }
+ }
+ },
+
+ /* returns Node */
+ replaceChild : function(/* Node */ newChild, /* Node */ oldChild){
+ this.insertBefore(newChild, oldChild);
+ return this.removeChild(oldChild);
+ }, //raises(DOMException);
+
+ /* returns void */
+ _attach : function() {
+ this._attached = true;
+ if (this.id) {
+ attachId(this.id,this,this._ownerDocument);
+ }
+ for (var i = 0, len = this._childNodes.length; i < len; i++) {
+ if (this._childNodes[i]._attach) {
+ this._childNodes[i]._attach();
+ }
+ }
+ },
+ /* returns void */
+ _detach : function() {
+ var i, elms;
+ this._attached = false;
+ if (this.id) {
+ detachId(this.id,this,this._ownerDocument);
+ }
+ for (var i = 0, len = this._childNodes.length; i < len; i++) {
+ this._childNodes[i]._detach();
+ }
+ },
+
+ /* returns Node */
+ removeChild : function(/* Node */ oldChild){
+ if (this._readonly === true) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ // TODO - if (!oldChild) then?
+
+ // Use lastIndexOf so that removing all the children by
+ // going backwards through childNodes is fast
+ // (because of splice)
+ var oldChildIndex = this._childNodes.lastIndexOf(oldChild);
+ if (oldChildIndex == -1) {
+ throw new core.DOMException(NOT_FOUND_ERR);
+ }
+
+ this._childNodes.splice(oldChildIndex, 1);
+ oldChild._parentNode = null;
+ this._modified();
+ oldChild._detach();
+ this._descendantRemoved(this, oldChild);
+ return oldChild;
+ }, // raises(DOMException);
+
+ /* returns Node */
+ appendChild : function(/* Node */ newChild) {
+ return this.insertBefore(newChild, null);
+ }, // raises(DOMException);
+
+ /* returns boolean */
+ hasChildNodes : function() {
+ return this._childNodes.length > 0;
+ },
+
+ /* returns Node */
+ cloneNode : function(/* bool */ deep, fn) {
+
+ var object = null;
+ switch (this.nodeType) {
+
+ case this.ELEMENT_NODE:
+ object = attrCopy(this,this._ownerDocument.createElementNS(this.namespaceURI, this.nodeName), fn);
+ // Using this.nodeName isn't always exact because of uppercasing-related stuff
+ object._prefix = this._prefix;
+ object._localName = this._localName;
+ break;
+
+ case this.TEXT_NODE:
+ object = attrCopy(this,this._ownerDocument.createTextNode(this.tagName));
+ object.nodeValue = this.nodeValue;
+ break;
+ case this.ATTRIBUTE_NODE:
+ object = this._ownerDocument.createAttribute(this.name);
+ break;
+ break;
+ case this.PROCESSING_INSTRUCTION_NODE:
+ var pi = this._ownerDocument.createProcessingInstruction(this._target,
+ this._data);
+ object = attrCopy(this, pi);
+ break;
+ case this.COMMENT_NODE:
+ object = this._ownerDocument.createComment(this.tagName);
+ object.nodeValue = this.nodeValue;
+ break;
+ case this.DOCUMENT_NODE:
+ object = attrCopy(this, new this.constructor({ parsingMode: this._parsingMode }));
+ // TODO: clone the doctype?
+ break;
+ case this.DOCUMENT_TYPE_NODE:
+ object = new core.DocumentType(this._ownerDocument, this._name, this._publicId, this._systemId);
+ break;
+ case this.DOCUMENT_FRAGMENT_NODE:
+ object = this._ownerDocument.createDocumentFragment();
+ break;
+ default:
+ throw new core.DOMException(NOT_FOUND_ERR);
+ break;
+ }
+
+ if (typeof fn === "function") {
+ fn(this, object);
+ }
+
+ if (deep || this.nodeType === ATTRIBUTE_NODE) {
+ var clone = null;
+ for (var i=0,len=this._childNodes.length;i<len;i++)
+ {
+ clone = this._childNodes[i].cloneNode(true);
+ if (clone.nodeType === ATTRIBUTE_NODE) {
+ object.setAttributeNode(clone);
+ } else {
+ var readonly = object._readonly;
+ object._readonly = false;
+ object.appendChild(clone);
+ object._readonly = readonly;
+ }
+ }
+ }
+
+ return object;
+ },
+
+ /* returns void */
+ normalize: function() {
+ var prevChild, child, attr,i;
+
+ if (this._attributes && this._attributes.length) {
+ for (i=0;i<this._attributes.length;i++)
+ {
+ if (this._attributes[i]) {
+ attr = this._attributes[i].normalize();
+ }
+ }
+ }
+
+ for (i=0;i<this._childNodes.length;i++)
+ {
+ child = this._childNodes[i];
+
+ if (child.normalize) {
+ child.normalize();
+ }
+
+ // Level2/core clean off empty nodes
+ if (child.nodeValue === "") {
+ this.removeChild(child);
+ i--;
+ continue;
+ }
+
+ if (i>0) {
+ prevChild = this._childNodes[i-1];
+
+ if (child.nodeType === TEXT_NODE &&
+ prevChild.nodeType === TEXT_NODE)
+ {
+
+ // remove the child and decrement i
+ prevChild.appendData(child.nodeValue);
+
+ this.removeChild(child);
+ i--;
+ }
+ }
+ }
+ },
+ toString: function() {
+ var id = '';
+ if (this.id) {
+ id = '#' + this.id;
+ }
+ if (this.className) {
+ var classes = this.className.split(/\s+/);
+ for (var i = 0, len = classes.length; i < len; i++) {
+ id += '.' + classes[i];
+ }
+ }
+ return '[ ' + this.tagName + id + ' ]';
+ },
+ raise: function(type, message, data) {
+ var text = type + ": " + message;
+
+ if (data) {
+ if (data.exception) {
+ text = data.exception.stack;
+ } else {
+ text += ' - More:\n' + data;
+ }
+ }
+
+ if (type === "error") {
+ if (!this.errors) {
+ this.errors = [];
+ }
+ // TODO: consider using actual `Error` objects or `DOMException`s even..
+ var err = {
+ type : type,
+ message : message || "No message",
+ data : data || null
+ };
+
+ this.errors.push(err);
+
+ if (this._ownerDocument &&
+ this._ownerDocument.raise &&
+ this !== this._ownerDocument)
+ {
+ this._ownerDocument.raise(type, message, data);
+ }
+ }
+ }
+};
+
+
+core.NamedNodeMap = function NamedNodeMap(document) {
+ this._nodes = Object.create(null);
+ this._nsStore = {};
+ this.length = 0;
+ this._ownerDocument = document;
+ this._readonly = false;
+};
+core.NamedNodeMap.prototype = {
+ get readonly() { return this._readonly;},
+ get ownerDocument() { this._ownerDocument;},
+
+ exists : function(name) {
+ return (this._nodes[name] || this._nodes[name] === null) ? true : false;
+ },
+
+ /* returns Node */
+ getNamedItem: function(/* string */ name) {
+ return this._nodes[name] || null;
+ },
+
+ /* returns Node */
+ setNamedItem: function(/* Node */ arg) {
+
+ // readonly
+ if (this._readonly === true) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ // arg is from a different document
+ if (arg && arg._ownerDocument !== this._ownerDocument) {
+ throw new core.DOMException(WRONG_DOCUMENT_ERR);
+ }
+
+ // if this argument is already in use..
+ if (arg && arg._ownerElement) {
+ throw new core.DOMException(INUSE_ATTRIBUTE_ERR);
+ }
+
+ var name = arg.name || arg.tagName;
+ var ret = this._nodes[name];
+ if (!ret) {
+ this.length++;
+ ret = null;
+ }
+ this._nodes[name] = arg;
+
+ // Avoid overwriting prototype methods etc.:
+ if (this.hasOwnProperty(name) || !(name in this)) {
+ this[name] = arg;
+ }
+ return ret;
+ }, // raises: function(DOMException) {},
+
+ /* returns Node */
+ removeNamedItem: function(/* string */ name) {
+
+ // readonly
+ if (this._readonly === true) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ if (!this._nodes[name]) {
+ throw new core.DOMException(NOT_FOUND_ERR);
+ }
+
+ var prev = this._nodes[name] || null;
+ delete this._nodes[name];
+ delete this[name];
+
+ this.length--;
+ return prev;
+ }, // raises: function(DOMException) {},
+
+ /* returns Node */
+ item: function(/* int */ index) {
+ var current = 0;
+ for (var member in this._nodes) {
+ if (current === index && this._nodes[member]) {
+ return this._nodes[member];
+ }
+ current++;
+ }
+ return null;
+ }
+};
+
+//
+// For historical reasons, AttributeList objects must allow accessing
+// attributes as if the object were an associative array. For
+// instance, if `attributes` is an AttributeList object then
+// `attributes.x` should evaluate to the attribute named `x` (which is
+// not in any namespace). The AttributeList class uses the dollar
+// symbol ($) to reduce the possibility of a clash between its field
+// names and possible attribute names. For instance, if the method
+// currently named `$set` were instead named `set` then it would not
+// be possible to access an attribute named `set` through
+// `attributes.set`. The dollar symbol is not valid in attribute names
+// so `$set` cannot clash.
+//
+// Some fields do not get the $ because:
+//
+// * They are part of the API (e.g. `setNamedItem`, `length`), so they
+// must be visible under a specific name.
+//
+// * Jsdom's code which traverses the DOM tree expects regularly named
+// fields (e.g. `_parentNode`).
+//
+function AttributeList(document, parentNode) {
+ this._ownerDocument = document;
+ this._parentNode = parentNode;
+ this._readonly = false;
+ this._$ns_to_attrs = Object.create(null);
+ this._$name_to_attrs = Object.create(null);
+ this.length = 0;
+}
+
+AttributeList.prototype = {
+ _$reserved: [], // Initialized later
+
+
+ //
+ // Code internal to jsdom and which manipulates an AttributeList
+ // object should use the following methods rather than the methods
+ // that provide the NamedNodeMap interface.
+ //
+
+ // This method *ignores* namespaces. This is *not* the same thing as
+ // requesting an attribute with a null namespace.
+ $getNoNS: function (name) {
+ var attrs = this._$name_to_attrs[name];
+ if (!attrs) {
+ return null;
+ }
+
+ return attrs[0] || null;
+ },
+
+ $getNode: function (namespace, localName) {
+ var attrs = this._$ns_to_attrs[namespace];
+ if (!attrs) {
+ return null;
+ }
+
+ var ret = attrs[localName];
+ if (!ret) {
+ return null;
+ }
+
+ return ret;
+ },
+
+ // This method *ignores* namespaces. This is *not* the same thing as
+ // requesting an attribute with a null namespace.
+ $setNoNS: function (name, value, dontValidate) {
+ var attr = this.$getNoNS(name);
+ if (!attr) {
+ this.$set(name, value, undefined, undefined, undefined, dontValidate);
+ return;
+ }
+
+ var prev_val = attr.value;
+ attr.value = value;
+
+ this._parentNode._attrModified(attr.name, attr.value, prev_val);
+ this._parentNode._modified();
+ },
+
+ $set: function (localName, value, name, prefix, namespace, dontValidate) {
+ if (this._readonly) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ if (name === undefined) {
+ name = localName;
+ }
+
+ if (prefix === undefined) {
+ prefix = null;
+ }
+
+ if (namespace === undefined) {
+ namespace = null;
+ }
+
+ var prev_attr = this.$getNode(namespace, localName);
+ var attr;
+
+ var prev_val = null;
+ if (prev_attr) {
+ prev_val = prev_attr.value;
+ prev_attr._prefix = prefix;
+ prev_attr.value = value;
+ attr = prev_attr;
+
+ this._parentNode._attrModified(attr.name, attr.value, prev_val);
+ this._parentNode._modified();
+ }
+ else {
+ var method = dontValidate ? '_createAttributeNoNameValidation' : 'createAttribute';
+ attr = this._ownerDocument[method](name);
+ attr._ownerElement = this._parentNode;
+ attr.value = value;
+ attr._namespaceURI = namespace;
+ attr._prefix = prefix;
+ attr._localName = localName;
+ attr._parentNode = this._parentNode;
+ this.$setNode(attr);
+ // $setNode calls the parent node methods.
+ }
+ },
+
+ $setNode: function (attr) {
+ if (this._readonly) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ if (attr.nodeType !== ATTRIBUTE_NODE) {
+ throw new core.DOMException(HIERARCHY_REQUEST_ERR);
+ }
+
+ if (attr._ownerDocument !== this._ownerDocument) {
+ throw new core.DOMException(WRONG_DOCUMENT_ERR);
+ }
+
+ if (attr._parentNode && attr._parentNode !== this._parentNode) {
+ throw new core.DOMException(INUSE_ATTRIBUTE_ERR);
+ }
+
+ var localName = attr._localName;
+ var name = attr.name;
+ var prefix = attr._prefix;
+ var namespace = attr._namespaceURI;
+
+ if (name === undefined) {
+ name = localName;
+ }
+
+ if (prefix === undefined) {
+ prefix = null;
+ }
+
+ if (namespace === undefined) {
+ namespace = null;
+ }
+
+ var prev_attr = this.$getNode(namespace, localName);
+
+ var prev_val = null;
+ if (prev_attr) {
+ prev_val = prev_attr.value;
+ // Remove the old attribute
+ this._$onlyRemoveNode(prev_attr);
+ }
+
+ attr._parentNode = this._parentNode;
+ attr._ownerElement = this._parentNode;
+
+ var attrs = this._$ns_to_attrs[namespace];
+ if (!attrs) {
+ attrs = this._$ns_to_attrs[namespace] = Object.create(null);
+ }
+ attrs[localName] = attr;
+
+ attrs = this._$name_to_attrs[name];
+ if (!attrs) {
+ attrs = this._$name_to_attrs[name] = [attr];
+ }
+ else {
+ attrs.push(attr);
+ }
+
+ // Only attributes in the null namespace can be set this way.
+ if (namespace === null) {
+ // Make the node a field on this object but ONLY if it does not
+ // clash with the reserved names.
+ if (this._$reserved.indexOf(name) === -1) {
+ this[name] = attr;
+ }
+ }
+
+ this[this.length] = attr;
+ this.length++;
+
+ this._parentNode._attrModified(attr.name, attr.value, prev_val);
+ this._parentNode._modified();
+
+ return prev_attr;
+ },
+
+ // This method *ignores* namespaces. This is *not* the same thing as
+ // requesting an attribute with a null namespace.
+ $removeNoNS: function (name) {
+ var attr = this.$getNoNS(name);
+ return attr ? this.$removeNode(attr) : null;
+ },
+
+ $remove: function (namespace, localName) {
+ var attr = this.$getNode(namespace, localName);
+ return attr ? this.$removeNode(attr) : null;
+ },
+
+ /* Only removes the node, and does not add a default value. */
+ _$onlyRemoveNode: function (attr) {
+ var namespace = attr._namespaceURI;
+ var localName = attr._localName;
+
+ var attrs = this._$ns_to_attrs[namespace];
+ if (!attrs) {
+ return null;
+ }
+
+ var found_attr = attrs[localName];
+ if (found_attr !== attr) {
+ return null;
+ }
+
+ if (this._readonly) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ attr._ownerElement = null;
+ attr._parentNode = null;
+ delete attrs[localName];
+
+ attrs = this._$name_to_attrs[attr.name];
+ attrs.splice(attrs.indexOf(attr), 1);
+
+ var ix = Array.prototype.indexOf.call(this, attr);
+ // Splice also modifies length.
+ Array.prototype.splice.call(this, ix, 1);
+
+ if (this[attr.name] === attr) {
+ delete this[attr.name];
+ }
+
+ this._parentNode._attrModified(attr.name);
+ this._parentNode._modified();
+
+ return attr;
+ },
+
+ $removeNode: function (attr) {
+ if (!this._$onlyRemoveNode(attr)) {
+ return null;
+ }
+ return attr;
+ },
+
+ // Although http://dom.spec.whatwg.org/#concept-element-attribute
+ // does not specify that the attributes field on an Element should
+ // support NamedNodeMap, in practice browsers still support this
+ // interface so we should support it for compatibility.
+
+ getNamedItem: function (name) {
+ return this.getNamedItemNS(null, name);
+ },
+ removeNamedItem: function (name) {
+ return this.removeNamedItemNS(null, name);
+ },
+ item: function (i) {
+ return this[i];
+ },
+ getNamedItemNS: function (namespaceURI, localName) {
+ if (namespaceURI === "") {
+ namespaceURI = null;
+ }
+
+ return this.$getNode(namespaceURI, localName);
+ },
+ removeNamedItemNS: function (namespaceURI, localName) {
+ var ret = this.$remove(namespaceURI, localName);
+
+ if (ret === null) {
+ throw new core.DOMException(NOT_FOUND_ERR);
+ }
+
+ return ret;
+ }
+};
+
+// Alias these methods.
+AttributeList.prototype.setNamedItem = AttributeList.prototype.$setNode;
+AttributeList.prototype.setNamedItemNS = AttributeList.prototype.$setNode;
+
+(function () {
+ // Construct the list of reserved attribute names from a temporarily
+ // created AttributeList and from the chain of prototypes. We need
+ // this because JavaScript code running an a browser expects to be
+ // able to do el.attributes.x to get the value of the attribute "x"
+ // on an element. Unfortunately, JavaScript *currently* does not
+ // allow us to elegantly provide such functionality without risking
+ // a clash with the fields and methods set on the AttributeList
+ // object. Hence we need a list of reserved field names.
+
+ var reserved = Object.keys(new AttributeList());
+ var prototype = AttributeList.prototype;
+ while (prototype) {
+ reserved = reserved.concat(Object.getOwnPropertyNames(prototype));
+ prototype = Object.getPrototypeOf(prototype);
+ }
+ AttributeList.prototype._$reserved = reserved;
+})();
+
+core.AttributeList = AttributeList;
+
+core.Element = function Element(document, localName) {
+ core.Node.call(this, document);
+ this._namespaceURI = null;
+ this._prefix = null;
+ this._localName = localName;
+};
+
+inheritFrom(core.Node, core.Element, {
+ get namespaceURI() {
+ return this._namespaceURI;
+ },
+ get prefix() {
+ return this._prefix;
+ },
+ get localName() {
+ return this._localName;
+ },
+ get tagName() {
+ var qualifiedName = this._prefix !== null ? this._prefix + ":" + this._localName : this._localName;
+ if (this.namespaceURI === "http://www.w3.org/1999/xhtml" && this._ownerDocument._parsingMode === "html") {
+ qualifiedName = qualifiedName.toUpperCase();
+ }
+ return qualifiedName;
+ },
+
+ get id() {
+ var idAttr = this.getAttribute("id");
+ if (idAttr === null) {
+ return "";
+ }
+ return idAttr;
+ },
+
+ nodeType : ELEMENT_NODE,
+ get attributes() {
+ return this._attributes;
+ },
+
+ get sourceIndex() {
+ /*
+ * According to QuirksMode:
+ * Get the sourceIndex of element x. This is also the index number for
+ * the element in the document.getElementsByTagName('*') array.
+ * http://www.quirksmode.org/dom/w3c_core.html#t77
+ */
+ var items = this.ownerDocument.getElementsByTagName('*'),
+ len = items.length;
+
+ for (var i = 0; i < len; i++) {
+ if (items[i] === this) {
+ return i;
+ }
+ }
+ },
+
+ get outerHTML() {
+ return domToHtml(this, true);
+ },
+
+ get innerHTML() {
+ var tagName = this.tagName;
+ if (tagName === 'SCRIPT' || tagName === 'STYLE') {
+ var type = this.getAttribute('type');
+ if (!type || /^text\//i.test(type) || /\/javascript$/i.test(type)) {
+ return domToHtml(this._childNodes, true, true);
+ }
+ }
+
+ // In case of <template> we should pass it's content fragment as a serialization root if we have one
+ if(tagName === 'TEMPLATE' &&
+ this._namespaceURI === 'http://www.w3.org/1999/xhtml' &&
+ this._childNodes[0] && this._childNodes[0]._templateContent) {
+ return domToHtml(this._childNodes[0]._childNodes, true);
+ }
+
+ return domToHtml(this._childNodes, true);
+ },
+
+ set innerHTML(html) {
+ setInnerHTML(this.ownerDocument, this, html);
+ },
+
+ scrollTop: 0,
+ scrollLeft: 0,
+
+ hasAttributes: function () {
+ return this._attributes.length > 0;
+ },
+
+ /* returns Attr */
+ setAttributeNode: function(/* Attr */ newAttr) {
+ var prevNode = this._attributes.$getNode(null, newAttr.name);
+ if (prevNode) {
+ prevNode._ownerElement = null;
+ }
+
+ newAttr._ownerElement = this;
+ this._attributes.$setNode(newAttr);
+
+ return (prevNode && prevNode.specified) ? prevNode : null;
+ }, // raises: function(DOMException) {},
+
+ /* returns Attr */
+ removeAttributeNode: function(/* Attr */ oldAttr) {
+ var ret = this._attributes.$removeNode(oldAttr);
+
+ if (ret !== null) {
+ return ret;
+ }
+
+ throw new core.DOMException(NOT_FOUND_ERR);
+ }, //raises: function(DOMException) {},
+
+ /* returns NodeList */
+ getElementsByTagName: memoizeQuery(function(/* string */ name) {
+ name = name.toLowerCase();
+
+ function filterByTagName(child) {
+ if (child.nodeName && child.nodeType === ELEMENT_NODE) {
+ return name === "*" || (child.nodeName.toLowerCase() === name);
+ }
+
+ return false;
+ }
+ return new core.NodeList(this._ownerDocument || this, core.mapper(this, filterByTagName, true));
+ }),
+
+ getElementsByClassName: function (className) {
+
+ function filterByClassName(child) {
+ if (!child) {
+ return false;
+ }
+
+ var classString = child.className;
+ if (classString) {
+ var s = classString.split(" ");
+ for (var i = 0; i < s.length; i++) {
+ if (s[i] === className) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ return new core.NodeList(this.ownerDocument || this, core.mapper(this, filterByClassName));
+ }
+});
+
+core.DocumentFragment = function DocumentFragment(document) {
+ core.Node.call(this, document);
+};
+inheritFrom(core.Node, core.DocumentFragment, {
+ nodeType : DOCUMENT_FRAGMENT_NODE
+});
+
+core.Document = function Document(options) {
+ if (!options || !options.parsingMode || (options.parsingMode !== "html" && options.parsingMode !== "xml")) {
+ throw new Error("options must exist and contain a parsingMode of html or xml");
+ }
+
+ core.Node.call(this, "#document");
+ this._parsingMode = options.parsingMode;
+ this._implementation = new core.DOMImplementation(this);
+ this._documentElement = null;
+ this._ids = Object.create(null);
+ this._attached = true;
+ this._ownerDocument = this;
+ this._readonly = false;
+
+ this._contentType = options.contentType;
+ if (this._contentType === undefined) {
+ this._contentType = this._parsingMode === "xml" ? "application/xml" : "text/html";
+ }
+
+ this._URL = options.url;
+ if (this._URL === undefined) {
+ this._URL = "about:blank";
+ }
+ this._location = new Location(this._URL, this);
+};
+
+
+var tagRegEx = /[^\w:\d_\.-]+/i;
+var entRegEx = /[^\w\d_\-&;]+/;
+var invalidAttrRegEx = /[\s"'>/=\u0000-\u001A]/;
+
+inheritFrom(core.Node, core.Document, {
+ nodeType : DOCUMENT_NODE,
+ _elementBuilders : { },
+ _defaultElementBuilder: function(document, tagName) {
+ return new core.Element(document, tagName);
+ },
+ get contentType() { return this._contentType;},
+ get compatMode() { return (this._parsingMode === "xml" || this.doctype) ? "CSS1Compat" : "BackCompat"; },
+ get characterSet() { return "UTF-8"; },
+ get inputEncoding() { return "UTF-8"; },
+ get doctype() {
+ for (var i = 0; i < this._childNodes.length; ++i) {
+ if (this._childNodes[i].nodeType === DOCUMENT_TYPE_NODE) {
+ return this._childNodes[i];
+ }
+ }
+ return null;
+ },
+ get URL() {
+ return this._URL;
+ },
+ get documentURI() {
+ return this._URL;
+ },
+ get location() {
+ return this._location;
+ },
+ get documentElement() {
+ if (this._documentElement) {
+ return this._documentElement;
+ } else {
+ for (var i = 0; i < this._childNodes.length; ++i) {
+ if (this._childNodes[i].nodeType === ELEMENT_NODE) {
+ this._documentElement = this._childNodes[i];
+ return this._documentElement;
+ }
+ }
+ return null;
+ }
+ },
+
+ get implementation() { return this._implementation;},
+ set implementation(implementation) { this._implementation = implementation;},
+ get ownerDocument() { return null;},
+ get readonly() { return this._readonly;},
+
+ set parentWindow(window) {
+ // Contextify does not support getters and setters, so we have to set them
+ // on the original object instead.
+ window._frame = function (name, frame) {
+ if (typeof frame === 'undefined') {
+ delete window[name];
+ } else {
+ defineGetter(window, name, function () { return frame.contentWindow; });
+ }
+ };
+ this._parentWindow = window.getGlobal();
+ },
+
+ get defaultView() {
+ return this.parentWindow;
+ },
+
+ toString: function () {
+ return '[object HTMLDocument]';
+ },
+
+ _createElementNoTagNameValidation: function (tagName) {
+ var element = (this._elementBuilders[tagName.toLowerCase()] || this._defaultElementBuilder)(this, tagName);
+ element._namespaceURI = "http://www.w3.org/1999/xhtml";
+ return element;
+ },
+
+ createElement: function (localName) {
+ localName = String(localName);
+ validateName(localName, core);
+ if (this._parsingMode === "html") {
+ localName = localName.toLowerCase();
+ }
+
+ return this._createElementNoTagNameValidation(localName);
+ },
+
+ /* returns DocumentFragment */
+ createDocumentFragment: function() {
+ return new core.DocumentFragment(this);
+ },
+
+ /* returns Attr */
+ createAttribute: function (localName) {
+ localName = String(localName);
+ validateName(localName, core);
+
+ return this._createAttributeNoNameValidation(localName);
+ }, // raises: function(DOMException) {},
+
+ _createAttributeNoNameValidation: function (localName) {
+ return new core.Attr(this, localName, "");
+ },
+
+ appendChild : function(/* Node */ arg) {
+ if (this.documentElement && arg.nodeType == ELEMENT_NODE) {
+ throw new core.DOMException(HIERARCHY_REQUEST_ERR);
+ }
+ return core.Node.prototype.appendChild.call(this, arg);
+ },
+
+ removeChild : function(/* Node */ arg) {
+ var ret = core.Node.prototype.removeChild.call(this, arg);
+ if (arg == this._documentElement) {
+ this._documentElement = null;// force a recalculation
+ }
+ return ret;
+ },
+
+ /* returns NodeList */
+ getElementsByTagName: memoizeQuery(function(/* string */ name) {
+ function filterByTagName(child) {
+ if (child.nodeName && child.nodeType === ELEMENT_NODE)
+ {
+ if (name === "*") {
+ return true;
+
+ // case insensitivity for html
+ } else if (child._ownerDocument && child._ownerDocument._doctype &&
+ //child._ownerDocument._doctype.name === "html" &&
+ child.nodeName.toLowerCase() === name.toLowerCase())
+ {
+ return true;
+ } else if (child.nodeName.toLowerCase() === name.toLowerCase()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return new core.NodeList(this.documentElement || this, core.mapper(this, filterByTagName, true));
+ }),
+
+ getElementsByClassName: function (className) {
+
+ function filterByClassName(child) {
+ if (!child) {
+ return false;
+ }
+
+ var classString = child.className;
+ if (classString) {
+ var s = classString.split(" ");
+ for (var i = 0; i < s.length; i++) {
+ if (s[i] === className) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ return new core.NodeList(this.ownerDocument || this, core.mapper(this, filterByClassName));
+ },
+
+ write: function (text) {
+ if (this._writeAfterElement) {
+ // If called from an script element directly (during the first tick),
+ // the new elements are inserted right after that element.
+ var tempDiv = this.createElement('div');
+ setInnerHTML(this, tempDiv, text);
+
+ var child = tempDiv.firstChild;
+ var previous = this._writeAfterElement;
+ var parent = this._writeAfterElement.parentNode;
+
+ while (child) {
+ var node = child;
+ child = child.nextSibling;
+ parent.insertBefore(node, previous.nextSibling);
+ previous = node;
+ }
+ } else if (this.readyState === "loading") {
+ // During page loading, document.write appends to the current element
+ // Find the last child that has been added to the document.
+ var node = this;
+ while (node.lastChild && node.lastChild.nodeType === this.ELEMENT_NODE) {
+ node = node.lastChild;
+ }
+ setInnerHTML(this, node, text || "<html><head></head><body></body></html>");
+ } else if (text) {
+ setInnerHTML(this, this, text);
+ }
+ }
+});
+
+core.Attr = function Attr(document, name, value) {
+ core.Node.call(this, document);
+ this._valueForAttrModified = value;
+ this._name = name;
+ this._ownerElement = null;
+ this._namespaceURI = null;
+ this._localName = name;
+ this._prefix = null;
+};
+inheritFrom(core.Node, core.Attr, {
+ nodeType : ATTRIBUTE_NODE,
+ get namespaceURI() {
+ return this._namespaceURI;
+ },
+ get prefix() {
+ return this._prefix;
+ },
+ get localName() {
+ return this._localName;
+ },
+ get name() {
+ return this._name;
+ },
+ get ownerElement() {
+ return this._ownerElement;
+ },
+ get nodeValue() {
+ var val = '';
+ for (var i=0,len=this._childNodes.length;i<len;i++) {
+ var child = this._childNodes[i];
+ val += child.nodeValue;
+ }
+ return val;
+ },
+ set nodeValue(value) {
+ // readonly
+ if (this._readonly) {
+ throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ this._childNodes.length = 1;
+ this._childNodes[0] = this._ownerDocument.createTextNode(value);
+ this._modified();
+ var prev = this._valueForAttrModified;
+ this._nodeValue = value;
+ if (this._ownerElement) {
+ this._ownerElement._attrModified(this._name, value, prev);
+ }
+ },
+ get specified() {
+ return true;
+ },
+ get value() {
+ return this.nodeValue;
+ },
+ set value(value) {
+ this.nodeValue = value;
+ },
+ get parentNode() { return null;},
+
+ insertBefore : function(/* Node */ newChild, /* Node*/ refChild){
+ if (newChild.nodeType === CDATA_SECTION_NODE ||
+ newChild.nodeType === ELEMENT_NODE)
+ {
+ throw new core.DOMException(HIERARCHY_REQUEST_ERR);
+ }
+
+ return core.Node.prototype.insertBefore.call(this, newChild, refChild);
+ },
+
+ appendChild : function(/* Node */ arg) {
+
+ if (arg.nodeType === CDATA_SECTION_NODE ||
+ arg.nodeType === ELEMENT_NODE)
+ {
+ throw new core.DOMException(HIERARCHY_REQUEST_ERR);
+ }
+
+ return core.Node.prototype.appendChild.call(this, arg);
+ }
+
+});
diff --git a/node_modules/jsdom/lib/jsdom/level2/core.js b/node_modules/jsdom/lib/jsdom/level2/core.js
new file mode 100644
index 0000000..6a52baf
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level2/core.js
@@ -0,0 +1,306 @@
+var core = require("../level1/core");
+var defineGetter = require('../utils').defineGetter;
+var defineSetter = require('../utils').defineSetter;
+var memoizeQuery = require('../utils').memoizeQuery;
+var validateQname = require('../living/helpers/validate-names').qname;
+var validateAndExtract = require('../living/helpers/validate-names').validateAndExtract;
+
+core.NamedNodeMap.prototype.getNamedItemNS = function(/* string */ namespaceURI,
+ /* string */ localName)
+{
+ if (this._nsStore[namespaceURI] && this._nsStore[namespaceURI][localName]) {
+ return this._nsStore[namespaceURI][localName];
+ }
+ return null;
+};
+
+core.NamedNodeMap.prototype.setNamedItemNS = function(/* Node */ arg)
+{
+ if (this._readonly) {
+ throw new core.DOMException(core.NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+ if (this._ownerDocument !== arg.ownerDocument) {
+ throw new core.DOMException(core.WRONG_DOCUMENT_ERR);
+ }
+
+ if (arg._ownerElement) {
+ throw new core.DOMException(core.INUSE_ATTRIBUTE_ERR);
+ }
+
+ // readonly
+ if (this._readonly === true) {
+ throw new core.DOMException(core.NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+
+ if (!this._nsStore[arg.namespaceURI]) {
+ this._nsStore[arg.namespaceURI] = {};
+ }
+ var existing = null;
+ if (this._nsStore[arg.namespaceURI][arg.localName]) {
+ var existing = this._nsStore[arg.namespaceURI][arg.localName];
+ }
+
+ this._nsStore[arg.namespaceURI][arg.localName] = arg;
+
+ arg._ownerDocument = this._ownerDocument;
+
+ return this.setNamedItem(arg);
+};
+
+core.NamedNodeMap.prototype.removeNamedItemNS = function(/*string */ namespaceURI,
+ /* string */ localName)
+{
+
+ if (this.readonly) {
+ throw new core.DOMException(core.NO_MODIFICATION_ALLOWED_ERR);
+ }
+
+
+ var parent = this._parentNode,
+ found = null,
+ defaults,
+ clone,
+ defaultEl,
+ defaultAttr;
+
+ if (this._nsStore[namespaceURI] &&
+ this._nsStore[namespaceURI][localName])
+ {
+ found = this._nsStore[namespaceURI][localName];
+ this.removeNamedItem(found.qualifiedName);
+ delete this._nsStore[namespaceURI][localName];
+ }
+
+ if (!found) {
+ throw new core.DOMException(core.NOT_FOUND_ERR);
+ }
+
+ return found;
+};
+
+core.NamedNodeMap.prototype._map = function(fn) {
+ var ret = [], l = this.length, i = 0, node;
+ for(i; i<l; i++) {
+ node = this.item(i);
+ if (fn && fn(node)) {
+ ret.push(node);
+ }
+ }
+ return ret;
+};
+
+core.Element.prototype.getAttribute = function(/* string */ name)
+{
+ var attr = this.getAttributeNode(name);
+ return attr && attr.value;
+};
+
+core.Element.prototype.getAttributeNode = function(/* string */ name)
+{
+ return this._attributes.$getNoNS(name);
+};
+
+core.Element.prototype.removeAttribute = function(/* string */ name)
+{
+ return this._attributes.$removeNoNS(name);
+};
+
+core.Element.prototype.getAttributeNS = function(/* string */ namespaceURI,
+ /* string */ localName)
+{
+ if (namespaceURI === "") {
+ namespaceURI = null;
+ }
+
+ var attr = this._attributes.$getNode(namespaceURI, localName);
+ return attr && attr.value;
+};
+
+core.Element.prototype.setAttribute = function(/* string */ name,
+ /* string */ value)
+{
+ this._attributes.$setNoNS(name, value);
+};
+
+core.Element.prototype._setAttributeNoValidation = function (name, value) {
+ this._attributes.$setNoNS(name, value, true);
+};
+
+core.Element.prototype.setAttributeNS = function (namespace, name, value) {
+ namespace = namespace !== null ? String(namespace) : namespace;
+ name = String(name);
+ value = String(value);
+
+ var extracted = validateAndExtract(namespace, name, core);
+
+ this._attributes.$set(extracted.localName, value, extracted.qualifiedName, extracted.prefix, extracted.namespace);
+};
+
+core.Element.prototype.removeAttributeNS = function(/* string */ namespaceURI,
+ /* string */ localName)
+{
+ if (namespaceURI === "") {
+ namespaceURI = null;
+ }
+
+ this._attributes.$remove(namespaceURI, localName);
+};
+
+core.Element.prototype.getAttributeNodeNS = function(/* string */ namespaceURI,
+ /* string */ localName)
+{
+ if (namespaceURI === "") {
+ namespaceURI = null;
+ }
+
+ return this._attributes.$getNode(namespaceURI, localName);
+};
+
+core.Element.prototype.setAttributeNodeNS = function(/* Attr */ newAttr)
+{
+ if (newAttr.ownerElement) {
+ throw new core.DOMException(core.INUSE_ATTRIBUTE_ERR);
+ }
+
+ return this._attributes.$setNode(newAttr);
+};
+
+core.Element.prototype.getElementsByTagNameNS = memoizeQuery(function(/* String */ namespaceURI,
+ /* String */ localName)
+{
+ var nsPrefixCache = {};
+
+ function filterByTagName(child) {
+ var localMatch = child.localName === localName,
+ nsMatch = child.namespaceURI === namespaceURI;
+
+ if ((localMatch || localName === "*") &&
+ (nsMatch || namespaceURI === "*"))
+ {
+ if (child.nodeType === child.ELEMENT_NODE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return new core.NodeList(this.ownerDocument || this,
+ core.mapper(this, filterByTagName));
+});
+
+core.Element.prototype.hasAttribute = function(/* string */name)
+{
+ if (!this._attributes) {
+ return false;
+ }
+
+ // Note: you might think you only need the latter condition. However, it makes a test case fail.
+ // HOWEVER, that test case is for a XML DTD feature called "default attributes" that never was implemented by
+ // browsers, so when we remove default attributes, we should be able to fix this code too.
+ return !!this._attributes.$getNoNS(name) || !!this._attributes.$getNoNS(name.toLowerCase());
+};
+
+core.Element.prototype.hasAttributeNS = function(/* string */namespaceURI,
+ /* string */localName)
+{
+ if (namespaceURI === "") {
+ namespaceURI = null;
+ }
+
+ return (this._attributes.getNamedItemNS(namespaceURI, localName) ||
+ this.hasAttribute(localName));
+};
+
+core.Document.prototype.importNode = function(/* Node */ importedNode,
+ /* bool */ deep)
+{
+ if (importedNode && importedNode.nodeType) {
+ if (importedNode.nodeType === this.DOCUMENT_NODE ||
+ importedNode.nodeType === this.DOCUMENT_TYPE_NODE) {
+ throw new core.DOMException(core.NOT_SUPPORTED_ERR);
+ }
+ }
+
+ var self = this,
+ newNode = importedNode.cloneNode(deep, function(a, b) {
+ b._prefix = a._prefix;
+ b._namespaceURI = a._namespaceURI;
+ b._localName = a._localName;
+ });
+
+ function lastChance(el) {
+ var attr, defaultEl, i, len;
+
+ el._ownerDocument = self;
+ if (el.id) {
+ if (!self._ids) {self._ids = {};}
+ if (!self._ids[el.id]) {self._ids[el.id] = [];}
+ self._ids[el.id].push(el);
+ }
+ if (el._attributes) {
+ var drop = [];
+ el._attributes._ownerDocument = self;
+ for (i=0,len=el._attributes.length; i < len; i++) {
+ attr = el._attributes[i];
+ attr._ownerDocument = self;
+ }
+
+ // Remove obsolete default nodes.
+ for(i = 0; i < drop.length; ++i) {
+ el._attributes.$removeNode(drop[i]);
+ }
+
+ }
+ }
+
+ if (deep) {
+ core.visitTree(newNode, lastChance);
+ }
+ else {
+ lastChance(newNode);
+ }
+
+ return newNode;
+};
+
+core.Document.prototype.createElementNS = function (namespace, qualifiedName) {
+ namespace = namespace !== null ? String(namespace) : namespace;
+ qualifiedName = String(qualifiedName);
+
+ var extracted = validateAndExtract(namespace, qualifiedName, core);
+ var element = this.createElement(extracted.localName);
+ element._namespaceURI = extracted.namespace;
+ element._prefix = extracted.prefix;
+
+ return element;
+};
+
+core.Document.prototype.createAttributeNS = function (namespace, qualifiedName) {
+ namespace = namespace !== null ? String(namespace) : namespace;
+ qualifiedName = String(qualifiedName);
+
+ var extracted = validateAndExtract(namespace, qualifiedName, core);
+ attribute = this.createAttribute(extracted.qualifiedName);
+
+ attribute._namespaceURI = extracted.namespace;
+ attribute._prefix = extracted.prefix;
+ attribute._localName = extracted.localName;
+ attribute._name = extracted.qualifiedName;
+
+ return attribute;
+};
+
+core.Document.prototype.getElementsByTagNameNS = function(/* String */ namespaceURI,
+ /* String */ localName)
+{
+ return core.Element.prototype.getElementsByTagNameNS.call(this,
+ namespaceURI,
+ localName);
+};
+
+core.Document.prototype.getElementById = function(id) {
+ // return the first element
+ return (this._ids && this._ids[id] && this._ids[id].length > 0 ? this._ids[id][0] : null);
+};
diff --git a/node_modules/jsdom/lib/jsdom/level2/events.js b/node_modules/jsdom/lib/jsdom/level2/events.js
new file mode 100644
index 0000000..2b19033
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level2/events.js
@@ -0,0 +1,441 @@
+/* DOM Level2 Events implemented as described here:
+ *
+ * http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
+ *
+ */
+var core = require("../level1/core"),
+ utils = require("../utils"),
+ defineGetter = utils.defineGetter,
+ defineSetter = utils.defineSetter,
+ inheritFrom = utils.inheritFrom;
+
+core.EventException = function() {
+ if (arguments.length > 0) {
+ this._code = arguments[0];
+ } else {
+ this._code = 0;
+ }
+ if (arguments.length > 1) {
+ this._message = arguments[1];
+ } else {
+ this._message = "Unspecified event type";
+ }
+ Error.call(this, this._message);
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, core.EventException);
+ }
+};
+inheritFrom(Error, core.EventException, {
+ UNSPECIFIED_EVENT_TYPE_ERR : 0,
+ get code() { return this._code;}
+});
+
+core.Event = function(eventType) {
+ this._eventType = eventType;
+ this._type = null;
+ this._bubbles = null;
+ this._cancelable = null;
+ this._target = null;
+ this._currentTarget = null;
+ this._eventPhase = 0;
+ this._timeStamp = null;
+ this._preventDefault = false;
+ this._stopPropagation = false;
+};
+core.Event.prototype = {
+ initEvent: function(type, bubbles, cancelable) {
+ this._type = type;
+ this._bubbles = bubbles;
+ this._cancelable = cancelable;
+ },
+ preventDefault: function() {
+ if (this._cancelable) {
+ this._preventDefault = true;
+ }
+ },
+ stopPropagation: function() {
+ this._stopPropagation = true;
+ },
+ NONE : 0,
+ CAPTURING_PHASE : 1,
+ AT_TARGET : 2,
+ BUBBLING_PHASE : 3,
+ get eventType() { return this._eventType; },
+ get type() { return this._type; },
+ get bubbles() { return this._bubbles; },
+ get cancelable() { return this._cancelable; },
+ get target() { return this._target; },
+ get currentTarget() { return this._currentTarget; },
+ get eventPhase() { return this._eventPhase; },
+ get timeStamp() { return this._timeStamp; }
+};
+
+
+core.UIEvent = function(eventType) {
+ core.Event.call(this, eventType);
+ this.view = null;
+ this.detail = null;
+};
+inheritFrom(core.Event, core.UIEvent, {
+ initUIEvent: function(type, bubbles, cancelable, view, detail) {
+ this.initEvent(type, bubbles, cancelable);
+ this.view = view;
+ this.detail = detail;
+ },
+});
+
+
+core.MouseEvent = function(eventType) {
+ core.UIEvent.call(this, eventType);
+ this.screenX = null;
+ this.screenY = null;
+ this.clientX = null;
+ this.clientY = null;
+ this.ctrlKey = null;
+ this.shiftKey = null;
+ this.altKey = null;
+ this.metaKey = null;
+ this.button = null;
+ this.relatedTarget = null;
+};
+inheritFrom(core.UIEvent, core.MouseEvent, {
+ initMouseEvent: function(type,
+ bubbles,
+ cancelable,
+ view,
+ detail,
+ screenX,
+ screenY,
+ clientX,
+ clientY,
+ ctrlKey,
+ altKey,
+ shiftKey,
+ metaKey,
+ button,
+ relatedTarget) {
+ this.initUIEvent(type, bubbles, cancelable, view, detail);
+ this.screenX = screenX
+ this.screenY = screenY
+ this.clientX = clientX
+ this.clientY = clientY
+ this.ctrlKey = ctrlKey
+ this.shiftKey = shiftKey
+ this.altKey = altKey
+ this.metaKey = metaKey
+ this.button = button
+ this.relatedTarget = relatedTarget
+ }
+});
+
+
+core.MutationEvent = function(eventType) {
+ core.Event.call(this, eventType);
+ this.relatedNode = null;
+ this.prevValue = null;
+ this.newValue = null;
+ this.attrName = null;
+ this.attrChange = null;
+};
+inheritFrom(core.Event, core.MutationEvent, {
+ initMutationEvent: function(type,
+ bubbles,
+ cancelable,
+ relatedNode,
+ prevValue,
+ newValue,
+ attrName,
+ attrChange) {
+ this.initEvent(type, bubbles, cancelable);
+ this.relatedNode = relatedNode;
+ this.prevValue = prevValue;
+ this.newValue = newValue;
+ this.attrName = attrName;
+ this.attrChange = attrChange;
+ },
+ MODIFICATION : 1,
+ ADDITION : 2,
+ REMOVAL : 3
+});
+
+core.EventTarget = function() {};
+
+function getListeners(target, type, capturing) {
+ var listeners = target._listeners
+ && target._listeners[type]
+ && target._listeners[type][capturing] || [];
+ if (!capturing) {
+ var traditionalHandler = target['on' + type];
+ if (traditionalHandler) {
+ var implementation = (target._ownerDocument ? target._ownerDocument.implementation
+ : target.document.implementation);
+
+ if (implementation._hasFeature('ProcessExternalResources', 'script')) {
+ if (listeners.indexOf(traditionalHandler) < 0) {
+ listeners.push(traditionalHandler);
+ }
+ }
+ }
+ }
+ return listeners;
+}
+
+function dispatchPhase(event, iterator) {
+ var target = iterator();
+
+ while (target && !event._stopPropagation) {
+ if (event._eventPhase === event.CAPTURING_PHASE || event._eventPhase === event.AT_TARGET) {
+ callListeners(event, target, getListeners(target, event._type, true));
+ }
+ if (event._eventPhase === event.AT_TARGET || event._eventPhase === event.BUBBLING_PHASE) {
+ callListeners(event, target, getListeners(target, event._type, false));
+ }
+ target = iterator();
+ }
+}
+
+function callListeners(event, target, listeners) {
+ var currentListener = listeners.length;
+ while (currentListener--) {
+ event._currentTarget = target;
+ try {
+ listeners[currentListener].call(target, event);
+ } catch (e) {
+ target.raise(
+ 'error', "Dispatching event '" + event._type + "' failed",
+ {error: e, event: event}
+ );
+ }
+ }
+}
+
+function forwardIterator(list) {
+ var i = 0, len = list.length;
+ return function iterator() { return i < len ? list[i++] : null };
+}
+
+function backwardIterator(list) {
+ var i = list.length;
+ return function iterator() { return i >=0 ? list[--i] : null };
+}
+
+function singleIterator(obj) {
+ var i = 1;
+ return function iterator() { return i-- ? obj : null };
+}
+
+core.EventTarget.prototype = {
+ addEventListener: function(type, listener, capturing) {
+ this._listeners = this._listeners || {};
+ var listeners = this._listeners[type] || {};
+ capturing = (capturing === true);
+ var capturingListeners = listeners[capturing] || [];
+ for (var i=0; i < capturingListeners.length; i++) {
+ if (capturingListeners[i] === listener) {
+ return;
+ }
+ }
+ capturingListeners.push(listener);
+ listeners[capturing] = capturingListeners;
+ this._listeners[type] = listeners;
+ },
+
+ removeEventListener: function(type, listener, capturing) {
+ var listeners = this._listeners && this._listeners[type];
+ if (!listeners) return;
+ var capturingListeners = listeners[(capturing === true)];
+ if (!capturingListeners) return;
+ for (var i=0; i < capturingListeners.length; i++) {
+ if (capturingListeners[i] === listener) {
+ capturingListeners.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ dispatchEvent: function(event) {
+ if (event == null) {
+ throw new core.EventException(0, "Null event");
+ }
+ if (event._type == null || event._type == "") {
+ throw new core.EventException(0, "Uninitialized event");
+ }
+
+ var targetList = [];
+
+ event._target = this;
+
+ //per the spec we gather the list of targets first to ensure
+ //against dom modifications during actual event dispatch
+ var target = this,
+ targetParent = target._parentNode;
+ while (targetParent) {
+ targetList.push(targetParent);
+ target = targetParent;
+ targetParent = target._parentNode;
+ }
+ targetParent = target._parentWindow;
+ if (targetParent) {
+ targetList.push(targetParent);
+ }
+
+ var iterator = backwardIterator(targetList);
+
+ event._eventPhase = event.CAPTURING_PHASE;
+ dispatchPhase(event, iterator);
+
+ iterator = singleIterator(event._target);
+ event._eventPhase = event.AT_TARGET;
+ dispatchPhase(event, iterator);
+
+ if (event._bubbles) {
+ iterator = forwardIterator(targetList);
+ event._eventPhase = event.BUBBLING_PHASE;
+ dispatchPhase(event, iterator);
+ }
+
+ event._currentTarget = null;
+ event._eventPhase = event.NONE;
+
+ return !event._preventDefault;
+ }
+
+};
+
+// Reinherit class heirarchy with EventTarget at its root
+inheritFrom(core.EventTarget, core.Node, core.Node.prototype);
+
+// Node
+inheritFrom(core.Node, core.Attr, core.Attr.prototype);
+inheritFrom(core.Node, core.Document, core.Document.prototype);
+inheritFrom(core.Node, core.DocumentFragment, core.DocumentFragment.prototype);
+inheritFrom(core.Node, core.Element, core.Element.prototype);
+
+
+function getDocument(el) {
+ return el.nodeType == core.Node.DOCUMENT_NODE ? el : el._ownerDocument;
+}
+
+function mutationEventsEnabled(el) {
+ return el.nodeType != core.Node.ATTRIBUTE_NODE &&
+ getDocument(el).implementation._hasFeature('MutationEvents');
+}
+
+var insertBefore_super = core.Node.prototype.insertBefore;
+core.Node.prototype.insertBefore = function(newChild, refChild) {
+ var ret = insertBefore_super.apply(this, arguments);
+ if (mutationEventsEnabled(this)) {
+ var doc = getDocument(this),
+ ev = doc.createEvent("MutationEvents");
+
+ ev.initMutationEvent("DOMNodeInserted", true, false, this, null, null, null, null);
+ newChild.dispatchEvent(ev);
+ if (this.nodeType == core.Node.DOCUMENT_NODE || this._attachedToDocument) {
+ ev = doc.createEvent("MutationEvents");
+ ev.initMutationEvent("DOMNodeInsertedIntoDocument", false, false, null, null, null, null, null);
+ core.visitTree(newChild, function(el) {
+ if (el.nodeType == core.Node.ELEMENT_NODE) {
+ el.dispatchEvent(ev);
+ el._attachedToDocument = true;
+ }
+ });
+ }
+ }
+ return ret;
+};
+
+var removeChild_super = core.Node.prototype.removeChild;
+core.Node.prototype.removeChild = function (oldChild) {
+ if (mutationEventsEnabled(this)) {
+ var doc = getDocument(this),
+ ev = doc.createEvent("MutationEvents");
+
+ ev.initMutationEvent("DOMNodeRemoved", true, false, this, null, null, null, null);
+ oldChild.dispatchEvent(ev);
+
+ ev = doc.createEvent("MutationEvents");
+ ev.initMutationEvent("DOMNodeRemovedFromDocument", false, false, null, null, null, null, null);
+ core.visitTree(oldChild, function(el) {
+ if (el.nodeType == core.Node.ELEMENT_NODE) {
+ el.dispatchEvent(ev);
+ el._attachedToDocument = false;
+ }
+ });
+ }
+ return removeChild_super.apply(this, arguments);
+};
+
+function dispatchAttrEvent(doc, target, prevVal, newVal, attrName, attrChange) {
+ if (!newVal || newVal != prevVal) {
+ var ev = doc.createEvent("MutationEvents");
+ ev.initMutationEvent("DOMAttrModified", true, false, target, prevVal,
+ newVal, attrName, attrChange);
+ target.dispatchEvent(ev);
+ }
+}
+
+function attrNodeInterceptor(_super, change) {
+ return function(node) {
+ var target = this._parentNode,
+ prev = _super.apply(this, arguments);
+
+ if (mutationEventsEnabled(target)) {
+ dispatchAttrEvent(target._ownerDocument,
+ target,
+ prev && prev.value || null,
+ change == 'ADDITION' ? node.value : null,
+ prev && prev.name || node.name,
+ core.MutationEvent.prototype[change]);
+ }
+
+ return prev;
+ };
+}
+
+function attrInterceptor(_super, ns) {
+ return function(localName, value, _name, _prefix, _namespace) {
+ var target = this._parentNode,
+ namespace = _namespace; // do not reassign parameters when using "arguments" (performance)
+
+ if (!mutationEventsEnabled(target)) {
+ _super.apply(this, arguments);
+ return;
+ }
+
+ if (namespace === undefined) {
+ namespace = null;
+ }
+
+ var prev =
+ ns ? this.$getNode(namespace, localName) : this.$getNoNS(localName);
+ var prevVal = prev && prev.value || null;
+
+ _super.apply(this, arguments);
+
+ var node = ns ? this.$getNode(namespace, localName):
+ this.$getNoNS(localName);
+
+ dispatchAttrEvent(target._ownerDocument,
+ target,
+ prevVal,
+ node.value,
+ node.name,
+ core.MutationEvent.prototype.ADDITION);
+ };
+}
+
+
+core.AttributeList.prototype.$removeNode = attrNodeInterceptor(core.AttributeList.prototype.$removeNode, 'REMOVAL');
+core.AttributeList.prototype.$setNode = attrNodeInterceptor(core.AttributeList.prototype.$setNode, 'ADDITION');
+core.AttributeList.prototype.$set = attrInterceptor(core.AttributeList.prototype.$set, true);
+core.AttributeList.prototype.$setNoNS = attrInterceptor(core.AttributeList.prototype.$setNoNS, false);
+
+core.Document.prototype.createEvent = function(eventType) {
+ switch (eventType) {
+ case "MutationEvents": return new core.MutationEvent(eventType);
+ case "UIEvents": return new core.UIEvent(eventType);
+ case "MouseEvents": return new core.MouseEvent(eventType);
+ case "HTMLEvents": return new core.Event(eventType);
+ }
+ return new core.Event(eventType);
+};
diff --git a/node_modules/jsdom/lib/jsdom/level2/html.js b/node_modules/jsdom/lib/jsdom/level2/html.js
new file mode 100644
index 0000000..752f575
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level2/html.js
@@ -0,0 +1,2140 @@
+var core = require("../level1/core"),
+ applyDocumentFeatures = require('../browser/documentfeatures').applyDocumentFeatures,
+ defineGetter = require('../utils').defineGetter,
+ defineSetter = require('../utils').defineSetter,
+ inheritFrom = require("../utils").inheritFrom,
+ resolveHref = require("../utils").resolveHref,
+ URL = require("url"),
+ Path = require('path'),
+ fs = require("fs"),
+ http = require('http'),
+ https = require('https');
+
+var isBrowser = Object.prototype.toString.call(process) !== "[object process]";
+
+// Setup the javascript language processor
+core.languageProcessors = {
+ javascript : require("./languages/javascript").javascript
+};
+
+core.resourceLoader = {
+ load: function(element, href, callback) {
+ var ownerDoc = element._ownerDocument;
+ var ownerImplementation = ownerDoc.implementation;
+
+ if (ownerImplementation._hasFeature('FetchExternalResources', element.tagName.toLowerCase())) {
+ var full = this.resolve(ownerDoc, href);
+ var url = URL.parse(full);
+ if (ownerImplementation._hasFeature('SkipExternalResources', full)) {
+ return false;
+ }
+
+ var cookie = ownerDoc.cookie;
+ var cookieDomain = ownerDoc._cookieDomain;
+ var baseUrl = this.baseUrl(ownerDoc);
+ var enqueued = this.enqueue(element, callback, full);
+
+ if (typeof ownerDoc._resourceLoader == 'function') {
+ var fetch = this.fetch.bind(this);
+ ownerDoc._resourceLoader.call(null, {
+ url: url,
+ cookie: cookie,
+ cookieDomain: cookieDomain,
+ baseUrl: baseUrl,
+ defaultFetch: function(callback) {
+ fetch(this.url, this.cookie, this.cookieDomain, this.baseUrl, callback);
+ }
+ }, enqueued);
+ } else {
+ this.fetch(url, cookie, cookieDomain, baseUrl, enqueued);
+ }
+ }
+ },
+ enqueue: function(element, callback, filename) {
+ var loader = this,
+ doc = element.nodeType === core.Node.DOCUMENT_NODE ?
+ element :
+ element._ownerDocument;
+
+ if (!doc._queue) {
+ return function() {};
+ }
+
+ return doc._queue.push(function(err, data) {
+ var ev = doc.createEvent('HTMLEvents');
+
+ if (!err) {
+ try {
+ callback.call(element, data, filename || doc.URL);
+ ev.initEvent('load', false, false);
+ }
+ catch(e) {
+ err = e;
+ }
+ }
+
+ if (err) {
+ ev.initEvent('error', false, false);
+ ev.error = err;
+ }
+
+ element.dispatchEvent(ev);
+ });
+ },
+
+ baseUrl: function(document) {
+ var baseElements = document.getElementsByTagName('base');
+ var baseUrl = document.URL;
+
+ if (baseElements.length > 0) {
+ var baseHref = baseElements.item(0).href;
+ if (baseHref) {
+ baseUrl = resolveHref(baseUrl, baseHref);
+ }
+ }
+
+ return baseUrl;
+ },
+ resolve: function(document, href) {
+ // if getAttribute returns null, there is no href
+ // lets resolve to an empty string (nulls are not expected farther up)
+ if (href === null) {
+ return '';
+ }
+
+ var baseUrl = this.baseUrl(document);
+
+ return resolveHref(baseUrl, href);
+ },
+ fetch: function(url, cookie, cookieDomain, referrer, callback) {
+ if (url.hostname) {
+ this.download(url, cookie, cookieDomain, referrer, callback);
+ } else {
+ this.readFile(url.pathname, callback);
+ }
+ },
+ download: function(url, cookie, cookieDomain, referrer, callback) {
+ var path = (url.pathname || '') + (url.search || ''),
+ options = {'method': 'GET', 'host': url.hostname, 'path': path},
+ request;
+ if (url.protocol === 'https:') {
+ options.port = url.port || 443;
+ request = https.request(options);
+ } else {
+ options.port = url.port || 80;
+ request = http.request(options);
+ }
+
+ // set header; accomodate browserify
+ if (referrer && !isBrowser) {
+ request.setHeader('Referer', referrer);
+ }
+ if (cookie) {
+ var host = url.host.split(':')[0];
+ if (host.indexOf(cookieDomain, host.length - cookieDomain.length) !== -1) {
+ request.setHeader('cookie', cookie);
+ }
+ }
+
+ request.on('response', function (response) {
+ var data = '';
+ function success () {
+ if ([301, 302, 303, 307].indexOf(response.statusCode) > -1) {
+ var redirect = URL.resolve(url, response.headers["location"]);
+ core.resourceLoader.download(URL.parse(redirect), cookie, cookieDomain, referrer, callback);
+ } else {
+ callback(null, data);
+ }
+ }
+
+ // Accomodate browserify
+ if (response.setEncoding) {
+ response.setEncoding('utf8');
+ }
+
+ response.on('data', function (chunk) {
+ data += chunk.toString();
+ });
+ response.on('end', function() {
+ // According to node docs, 'close' can fire after 'end', but not
+ // vice versa. Remove 'close' listener so we don't call success twice.
+ response.removeAllListeners('close');
+ success();
+ });
+ response.on('close', function (err) {
+ if (err) {
+ callback(err);
+ } else {
+ success();
+ }
+ });
+ });
+
+ request.on('error', callback);
+ request.end();
+ },
+ readFile: function(url, callback) {
+ fs.readFile(url.replace(/^file:\/\//, "").replace(/^\/([a-z]):\//i, '$1:/').replace(/%20/g, ' '), 'utf8', callback);
+ }
+};
+
+function define(elementClass, def) {
+ var tagName = def.tagName,
+ tagNames = def.tagNames || (tagName? [tagName] : []),
+ parentClass = def.parentClass || core.HTMLElement,
+ attrs = def.attributes || [],
+ proto = def.proto || {};
+
+ var elem = core[elementClass] = function(document, name) {
+ parentClass.call(this, document, name || tagName.toUpperCase());
+ if (elem._init) {
+ elem._init.call(this);
+ }
+ };
+ elem._init = def.init;
+
+ inheritFrom(parentClass, elem, proto);
+
+ attrs.forEach(function(n) {
+ var prop = n.prop || n,
+ attr = n.attr || prop.toLowerCase();
+
+ if (!n.prop || n.read !== false) {
+ defineGetter(elem.prototype, prop, function() {
+ var s = this.getAttribute(attr);
+ if (n.type && n.type === 'boolean') {
+ return s !== null;
+ }
+ if (n.type && n.type === 'long') {
+ return +s;
+ }
+ if (typeof n === 'object' && n.normalize) { // see GH-491
+ return n.normalize(s);
+ }
+ if (s === null) {
+ s = '';
+ }
+ return s;
+ });
+ }
+
+ if (!n.prop || n.write !== false) {
+ defineSetter(elem.prototype, prop, function(val) {
+ if (!val) {
+ this.removeAttribute(attr);
+ }
+ else {
+ var s = val.toString();
+ if (typeof n === 'object' && n.normalize) {
+ s = n.normalize(s);
+ }
+ this.setAttribute(attr, s);
+ }
+ });
+ }
+ });
+
+ tagNames.forEach(function(tag) {
+ core.Document.prototype._elementBuilders[tag.toLowerCase()] = function(doc, s) {
+ var el = new elem(doc, s);
+
+ if (def.elementBuilder) {
+ return def.elementBuilder(el, doc, s);
+ }
+
+ return el;
+ };
+ });
+}
+
+
+
+core.HTMLCollection = function HTMLCollection(element, query) {
+ this._keys = [];
+ core.NodeList.call(this, element, query);
+};
+inheritFrom(core.NodeList, core.HTMLCollection, {
+ namedItem: function(name) {
+ // Try property shortcut; should work in most cases
+ if (Object.prototype.hasOwnProperty.call(this, name)) {
+ return this[name];
+ }
+
+ var results = this._toArray(),
+ l = results.length,
+ node,
+ matchingName = null;
+
+ for (var i=0; i<l; i++) {
+ node = results[i];
+ if (node.getAttribute('id') === name) {
+ return node;
+ } else if (node.getAttribute('name') === name) {
+ matchingName = node;
+ }
+ }
+ return matchingName;
+ },
+ toString: function() {
+ return '[ jsdom HTMLCollection ]: contains ' + this.length + ' items';
+ },
+ _resetTo: function(array) {
+ var i, _this = this;
+
+ for (i = 0; i < this._keys.length; ++i) {
+ delete this[this._keys[i]];
+ }
+ this._keys = [];
+
+ core.NodeList.prototype._resetTo.apply(this, arguments);
+
+ function testAttr(node, attr) {
+ var val = node.getAttribute(attr);
+ if (val && !Object.prototype.hasOwnProperty.call(_this, val)) {
+ _this[val] = node;
+ _this._keys.push(val);
+ }
+ }
+ for (i = 0; i < array.length; ++i) {
+ testAttr(array[i], 'id');
+ }
+ for (i = 0; i < array.length; ++i) {
+ testAttr(array[i], 'name');
+ }
+ }
+});
+Object.defineProperty(core.HTMLCollection.prototype, 'constructor', {
+ value: core.NodeList,
+ writable: true,
+ configurable: true
+});
+
+core.HTMLOptionsCollection = core.HTMLCollection;
+
+function closest(e, tagName) {
+ tagName = tagName.toUpperCase();
+ while (e) {
+ if (e.nodeName.toUpperCase() === tagName ||
+ (e.tagName && e.tagName.toUpperCase() === tagName))
+ {
+ return e;
+ }
+ e = e._parentNode;
+ }
+ return null;
+}
+
+function descendants(e, tagName, recursive) {
+ var owner = recursive ? e._ownerDocument || e : e;
+ return new core.HTMLCollection(owner, core.mapper(e, function(n) {
+ return n.tagName === tagName;
+ }, recursive));
+}
+
+function firstChild(e, tagName) {
+ if (!e) {
+ return null;
+ }
+ var c = descendants(e, tagName, false);
+ return c.length > 0 ? c[0] : null;
+}
+
+function ResourceQueue(paused) {
+ this.paused = !!paused;
+}
+ResourceQueue.prototype = {
+ push: function(callback) {
+ var q = this;
+ var item = {
+ prev: q.tail,
+ check: function() {
+ if (!q.paused && !this.prev && this.fired){
+ callback(this.err, this.data);
+ if (this.next) {
+ this.next.prev = null;
+ this.next.check();
+ }else{//q.tail===this
+ q.tail = null;
+ }
+ }
+ }
+ };
+ if (q.tail) {
+ q.tail.next = item;
+ }
+ q.tail = item;
+ return function(err, data) {
+ item.fired = 1;
+ item.err = err;
+ item.data = data;
+ item.check();
+ };
+ },
+ resume: function() {
+ if(!this.paused){
+ return;
+ }
+ this.paused = false;
+ var head = this.tail;
+ while(head && head.prev){
+ head = head.prev;
+ }
+ if(head){
+ head.check();
+ }
+ }
+};
+
+core.HTMLDocument = function HTMLDocument(options) {
+ core.Document.call(this, options);
+ this._referrer = options.referrer;
+ this._cookie = options.cookie;
+ this._cookieDomain = options.cookieDomain || '127.0.0.1';
+ this._documentRoot = options.documentRoot || Path.dirname(this._URL);
+ this._queue = new ResourceQueue(options.deferClose);
+ this._resourceLoader = options.resourceLoader;
+ this.readyState = 'loading';
+
+ // Add level2 features
+ this.implementation._addFeature('core' , '2.0');
+ this.implementation._addFeature('html' , '2.0');
+ this.implementation._addFeature('xhtml' , '2.0');
+ this.implementation._addFeature('xml' , '2.0');
+};
+
+inheritFrom(core.Document, core.HTMLDocument, {
+ _referrer : "",
+ get referrer() {
+ return this._referrer || '';
+ },
+ get domain() {
+ return "";
+ },
+ get images() {
+ return this.getElementsByTagName('IMG');
+ },
+ get applets() {
+ return new core.HTMLCollection(this, core.mapper(this, function(el) {
+ if (el && el.tagName) {
+ var upper = el.tagName.toUpperCase();
+ if (upper === "APPLET") {
+ return true;
+ } else if (upper === "OBJECT" &&
+ el.getElementsByTagName('APPLET').length > 0)
+ {
+ return true;
+ }
+ }
+ }));
+ },
+ get links() {
+ return new core.HTMLCollection(this, core.mapper(this, function(el) {
+ if (el && el.tagName) {
+ var upper = el.tagName.toUpperCase();
+ if (upper === "AREA" || (upper === "A" && el.href)) {
+ return true;
+ }
+ }
+ }));
+ },
+ get forms() {
+ return this.getElementsByTagName('FORM');
+ },
+ get anchors() {
+ return this.getElementsByTagName('A');
+ },
+ open : function() {
+ this._childNodes = [];
+ this._documentElement = null;
+ this._modified();
+ },
+ close : function() {
+ this._queue.resume();
+ // Set the readyState to 'complete' once all resources are loaded.
+ // As a side-effect the document's load-event will be dispatched.
+ core.resourceLoader.enqueue(this, function() {
+ this.readyState = 'complete';
+ var ev = this.createEvent('HTMLEvents');
+ ev.initEvent('DOMContentLoaded', false, false);
+ this.dispatchEvent(ev);
+ })(null, true);
+ },
+
+ // document.write is defined in browser/index.js.
+
+ writeln : function(text) {
+ this.write(text + '\n');
+ },
+
+ getElementsByName : function(elementName) {
+ return new core.HTMLCollection(this, core.mapper(this, function(el) {
+ return (el.getAttribute && el.getAttribute("name") === elementName);
+ }));
+ },
+
+ get title() {
+ var head = this.head,
+ title = head ? firstChild(head, 'TITLE') : null;
+ return title ? title.textContent : '';
+ },
+
+ set title(val) {
+ var title = firstChild(this.head, 'TITLE');
+ if (!title) {
+ title = this.createElement('TITLE');
+ var head = this.head;
+ if (!head) {
+ head = this.createElement('HEAD');
+ this.documentElement.insertBefore(head, this.documentElement.firstChild);
+ }
+ head.appendChild(title);
+ }
+ title.textContent = val;
+ },
+
+ get head() {
+ return firstChild(this.documentElement, 'HEAD');
+ },
+
+ set head(unused) { /* noop */ },
+
+ get body() {
+ var body = firstChild(this.documentElement, 'BODY');
+ if (!body) {
+ body = firstChild(this.documentElement, 'FRAMESET');
+ }
+ return body;
+ },
+
+ _cookie : "",
+ get cookie() {
+ var cookies = Array.isArray(this._cookie) ?
+ this._cookie :
+ (this._cookie && this._cookie.length > 0 ? [this._cookie] : []);
+
+ return cookies.map(function (x) {
+ return x.split(';')[0];
+ }).join('; ');
+ },
+ set cookie(val) {
+ if (val == null) return val;
+ var key = val.split('=')[0];
+ var cookies = Array.isArray(this._cookie) ?
+ this._cookie :
+ (this._cookie && this._cookie.length > 0 ? [this._cookie] : []);
+ for (var i = 0; i < cookies.length; i++) {
+ if (cookies[i].lastIndexOf(key + '=', 0) === 0) {
+ cookies[i] = val;
+ key = null;
+ break;
+ }
+ }
+ if (key) {
+ cookies.push(val);
+ }
+ if (cookies.length === 1) {
+ this._cookie = cookies[0];
+ } else {
+ this._cookie = cookies;
+ }
+ return val;
+ }
+});
+
+define('HTMLElement', {
+ parentClass: core.Element,
+ proto : {
+ // Add default event behavior (click link to navigate, click button to submit
+ // form, etc). We start by wrapping dispatchEvent so we can forward events to
+ // the element's _eventDefault function (only events that did not incur
+ // preventDefault).
+ dispatchEvent : function (event) {
+ var outcome = core.Node.prototype.dispatchEvent.call(this, event)
+
+ if (!event._preventDefault &&
+ event.target._eventDefaults[event.type] &&
+ typeof event.target._eventDefaults[event.type] === 'function')
+ {
+ event.target._eventDefaults[event.type](event)
+ }
+ return outcome;
+ },
+ getBoundingClientRect: function () {
+ return {
+ bottom: 0,
+ height: 0,
+ left: 0,
+ right: 0,
+ top: 0,
+ width: 0
+ };
+ },
+ focus : function() {
+ this._ownerDocument.activeElement = this;
+ },
+ blur : function() {
+ this._ownerDocument.activeElement = this._ownerDocument.body;
+ },
+ _eventDefaults : {}
+ },
+ attributes: [
+ 'id',
+ 'title',
+ 'lang',
+ 'dir',
+ {prop: 'className', attr: 'class', normalize: function(s) { return s || ''; }}
+ ]
+});
+
+core.Document.prototype._defaultElementBuilder = function(document, tagName) {
+ return new core.HTMLElement(document, tagName);
+};
+
+// http://www.whatwg.org/specs/web-apps/current-work/#category-listed
+var listedElements = /button|fieldset|input|keygen|object|select|textarea/i;
+
+define('HTMLFormElement', {
+ tagName: 'FORM',
+ proto: {
+ _descendantAdded: function(parent, child) {
+ var form = this;
+ core.visitTree(child, function(el) {
+ if (typeof el._changedFormOwner === 'function') {
+ el._changedFormOwner(form);
+ }
+ });
+
+ core.HTMLElement.prototype._descendantAdded.apply(this, arguments);
+ },
+ _descendantRemoved: function(parent, child) {
+ core.visitTree(child, function(el) {
+ if (typeof el._changedFormOwner === 'function') {
+ el._changedFormOwner(null);
+ }
+ });
+
+ core.HTMLElement.prototype._descendantRemoved.apply(this, arguments);
+ },
+ get elements() {
+ return new core.HTMLCollection(this._ownerDocument, core.mapper(this, function(e) {
+ return listedElements.test(e.nodeName) ; // TODO exclude <input type="image">
+ }));
+ },
+ get length() {
+ return this.elements.length;
+ },
+ _dispatchSubmitEvent: function() {
+ var ev = this._ownerDocument.createEvent('HTMLEvents');
+ ev.initEvent('submit', true, true);
+ if (!this.dispatchEvent(ev)) {
+ this.submit();
+ };
+ },
+ submit: function() {
+ },
+ reset: function() {
+ this.elements._toArray().forEach(function(el) {
+ if (typeof el._formReset === 'function') {
+ el._formReset();
+ }
+ });
+ }
+ },
+ attributes: [
+ 'name',
+ {prop: 'acceptCharset', attr: 'accept-charset'},
+ 'action',
+ 'enctype',
+ 'method',
+ 'target'
+ ]
+});
+
+define('HTMLLinkElement', {
+ tagName: 'LINK',
+ proto: {
+ get href() {
+ return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('href'));
+ }
+ },
+ attributes: [
+ {prop: 'disabled', type: 'boolean'},
+ 'charset',
+ 'href',
+ 'hreflang',
+ 'media',
+ 'rel',
+ 'rev',
+ 'target',
+ 'type'
+ ]
+});
+
+define('HTMLMetaElement', {
+ tagName: 'META',
+ attributes: [
+ 'content',
+ {prop: 'httpEquiv', attr: 'http-equiv'},
+ 'name',
+ 'scheme'
+ ]
+});
+
+define('HTMLHtmlElement', {
+ tagName: 'HTML',
+ attributes: [
+ 'version'
+ ]
+});
+
+define('HTMLHeadElement', {
+ tagName: 'HEAD',
+ attributes: [
+ 'profile'
+ ]
+});
+
+define('HTMLTitleElement', {
+ tagName: 'TITLE',
+ proto: {
+ get text() {
+ return this.innerHTML;
+ },
+ set text(s) {
+ this.innerHTML = s;
+ }
+ }
+});
+
+define('HTMLBaseElement', {
+ tagName: 'BASE',
+ attributes: [
+ 'href',
+ 'target'
+ ]
+});
+
+
+//**Deprecated**
+define('HTMLIsIndexElement', {
+ tagName : 'ISINDEX',
+ parentClass : core.Element,
+ proto : {
+ get form() {
+ return closest(this, 'FORM');
+ }
+ },
+ attributes : [
+ 'prompt'
+ ]
+});
+
+
+define('HTMLStyleElement', {
+ tagName: 'STYLE',
+ attributes: [
+ {prop: 'disabled', type: 'boolean'},
+ 'media',
+ 'type',
+ ]
+});
+
+define('HTMLBodyElement', {
+ proto: (function() {
+ var proto = {};
+ // The body element's "traditional" event handlers are proxied to the
+ // window object.
+ // See: http://www.whatwg.org/specs/web-apps/current-work/#the-body-element
+ ['onafterprint', 'onbeforeprint', 'onbeforeunload', 'onblur', 'onerror',
+ 'onfocus', 'onhashchange', 'onload', 'onmessage', 'onoffline', 'ononline',
+ 'onpagehide', 'onpageshow', 'onpopstate', 'onresize', 'onscroll',
+ 'onstorage', 'onunload'].forEach(function (name) {
+ defineSetter(proto, name, function (handler) {
+ this._ownerDocument.parentWindow[name] = handler;
+ });
+ defineGetter(proto, name, function () {
+ return this._ownerDocument.parentWindow[name];
+ });
+ });
+ return proto;
+ })(),
+ tagName: 'BODY',
+ attributes: [
+ 'aLink',
+ 'background',
+ 'bgColor',
+ 'link',
+ 'text',
+ 'vLink'
+ ]
+});
+
+define('HTMLSelectElement', {
+ tagName: 'SELECT',
+ proto: {
+ _formReset: function() {
+ this.options._toArray().forEach(function(option, i) {
+ option._selectedness = option.defaultSelected;
+ option._dirtyness = false;
+ });
+ this._askedForAReset();
+ },
+ _askedForAReset: function() {
+ if (this.hasAttribute('multiple')) {
+ return;
+ }
+
+ var options = this.options._toArray();
+ var selected = options.filter(function(option){
+ return option._selectedness;
+ });
+
+ // size = 1 is default if not multiple
+ if ((!this.size || this.size === 1) && !selected.length) {
+ // select the first option that is not disabled
+ for (var i = 0; i < options.length; ++i) {
+ var option = options[i];
+ var disabled = option.disabled;
+ if (option._parentNode &&
+ option._parentNode.nodeName.toUpperCase() === 'OPTGROUP' &&
+ option._parentNode.disabled) {
+ disabled = true;
+ }
+
+ if (!disabled) {
+ // (do not set dirty)
+ option._selectedness = true;
+ break;
+ }
+ }
+ } else if (selected.length >= 2) {
+ // select the last selected option
+ selected.forEach(function(option, index) {
+ option._selectedness = index === selected.length - 1;
+ });
+ }
+ },
+ _descendantAdded: function(parent, child) {
+ if (child.nodeType === core.Node.ELEMENT_NODE) {
+ this._askedForAReset();
+ }
+
+ core.HTMLElement.prototype._descendantAdded.apply(this, arguments);
+ },
+ _descendantRemoved: function(parent, child) {
+ if (child.nodeType === core.Node.ELEMENT_NODE) {
+ this._askedForAReset();
+ }
+
+ core.HTMLElement.prototype._descendantRemoved.apply(this, arguments);
+ },
+ _attrModified: function(name, value) {
+ if (name === 'multiple' || name === 'size') {
+ this._askedForAReset();
+ }
+ core.HTMLElement.prototype._attrModified.apply(this, arguments);
+ },
+ get options() {
+ return new core.HTMLOptionsCollection(this, core.mapper(this, function(n) {
+ return n.nodeName === 'OPTION';
+ }));
+ },
+
+ get length() {
+ return this.options.length;
+ },
+
+ get selectedIndex() {
+ return this.options._toArray().reduceRight(function(prev, option, i) {
+ return option.selected ? i : prev;
+ }, -1);
+ },
+
+ set selectedIndex(index) {
+ this.options._toArray().forEach(function(option, i) {
+ option.selected = i === index;
+ });
+ },
+
+ get value() {
+ var i = this.selectedIndex;
+ if (this.options.length && (i === -1)) {
+ i = 0;
+ }
+ if (i === -1) {
+ return '';
+ }
+ return this.options[i].value;
+ },
+
+ set value(val) {
+ var self = this;
+ this.options._toArray().forEach(function(option) {
+ if (option.value === val) {
+ option.selected = true;
+ } else {
+ if (!self.hasAttribute('multiple')) {
+ // Remove the selected bit from all other options in this group
+ // if the multiple attr is not present on the select
+ option.selected = false;
+ }
+ }
+ });
+ },
+
+ get form() {
+ return closest(this, 'FORM');
+ },
+
+ get type() {
+ return this.multiple ? 'select-multiple' : 'select-one';
+ },
+
+ add: function(opt, before) {
+ if (before) {
+ this.insertBefore(opt, before);
+ }
+ else {
+ this.appendChild(opt);
+ }
+ },
+
+ remove: function(index) {
+ var opts = this.options._toArray();
+ if (index >= 0 && index < opts.length) {
+ var el = opts[index];
+ el._parentNode.removeChild(el);
+ }
+ }
+
+ },
+ attributes: [
+ {prop: 'disabled', type: 'boolean'},
+ {prop: 'multiple', type: 'boolean'},
+ 'name',
+ {prop: 'size', type: 'long'},
+ {prop: 'tabIndex', type: 'long'},
+ ]
+});
+
+define('HTMLOptGroupElement', {
+ tagName: 'OPTGROUP',
+ attributes: [
+ {prop: 'disabled', type: 'boolean'},
+ 'label'
+ ]
+});
+
+define('HTMLOptionElement', {
+ tagName: 'OPTION',
+ proto: {
+ // whenever selectedness is set to true, make sure all
+ // other options set selectedness to false
+ _selectedness: false,
+ _dirtyness: false,
+ _removeOtherSelectedness: function() {
+ //Remove the selectedness flag from all other options in this select
+ var select = this._selectNode;
+
+ if (select && !select.multiple) {
+ var o = select.options;
+ for (var i = 0; i < o.length; i++) {
+ if (o[i] !== this) {
+ o[i]._selectedness = false;
+ }
+ }
+ }
+ },
+ _askForAReset: function() {
+ var select = this._selectNode;
+ if (select) {
+ select._askedForAReset();
+ }
+ },
+ _attrModified: function(name, value) {
+ if (!this._dirtyness && name === 'selected') {
+ this._selectedness = this.defaultSelected;
+ if (this._selectedness) {
+ this._removeOtherSelectedness();
+ }
+ this._askForAReset();
+ }
+ core.HTMLElement.prototype._attrModified.apply(this, arguments);
+ },
+ get _selectNode() {
+ var select = this._parentNode;
+ if (!select) return null;
+ if (select.nodeName.toUpperCase() !== 'SELECT') {
+ select = select._parentNode;
+ if (!select) return null;
+ if (select.nodeName.toUpperCase() !== 'SELECT') return null;
+ }
+ return select;
+ },
+ get form() {
+ return closest(this, 'FORM');
+ },
+ get defaultSelected() {
+ return this.getAttribute('selected') !== null;
+ },
+ set defaultSelected(s) {
+ if (s) this.setAttribute('selected', 'selected');
+ else this.removeAttribute('selected');
+ },
+ get text() {
+ return this.innerHTML;
+ },
+ get value() {
+ return (this.hasAttribute('value')) ? this.getAttribute('value') : this.innerHTML;
+ },
+ set value(val) {
+ this.setAttribute('value', val);
+ },
+ get index() {
+ return closest(this, 'SELECT').options._toArray().indexOf(this);
+ },
+ get selected() {
+ return this._selectedness;
+ },
+ set selected(s) {
+ this._dirtyness = true;
+ this._selectedness = !!s;
+ if (this._selectedness) {
+ this._removeOtherSelectedness();
+ }
+ this._askForAReset();
+ }
+ },
+ attributes: [
+ {prop: 'disabled', type: 'boolean'},
+ 'label'
+ ]
+});
+
+define('HTMLInputElement', {
+ tagName: 'INPUT',
+ init: function() {
+ if (!this.type) {
+ this.type = 'text';
+ }
+ },
+ proto: {
+ _value: null,
+ _dirtyValue: false,
+ _checkedness: false,
+ _dirtyCheckedness: false,
+ _attrModified: function(name, value) {
+ if (!this._dirtyValue && name === 'value') {
+ this._value = this.defaultValue;
+ }
+ if (!this._dirtyCheckedness && name === 'checked') {
+ this._checkedness = this.defaultChecked;
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ }
+
+ if (name === 'name' || name === 'type') {
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ }
+
+ core.HTMLElement.prototype._attrModified.apply(this, arguments);
+ },
+ _formReset: function() {
+ this._value = this.defaultValue;
+ this._dirtyValue = false;
+ this._checkedness = this.defaultChecked;
+ this._dirtyCheckedness = false;
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ },
+ _changedFormOwner: function(newForm) {
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ },
+ _removeOtherRadioCheckedness: function() {
+ var root = this._radioButtonGroupRoot;
+ if (!root) {
+ return;
+ }
+
+ var name = this.name.toLowerCase();
+ var radios = new core.HTMLCollection(this, core.mapper(root, function(el) {
+ return el.type === 'radio' &&
+ el.name &&
+ el.name.toLowerCase() === name &&
+ el._radioButtonGroupRoot === root;
+ }));
+
+ radios._toArray().forEach(function(radio) {
+ if (radio !== this) {
+ radio._checkedness = false;
+ }
+ }, this);
+ },
+ get _radioButtonGroupRoot() {
+ if (this.type !== 'radio' || !this.name) {
+ return null;
+ }
+
+ var e = this._parentNode;
+ while (e) {
+ // root node of this home sub tree
+ // or the form element we belong to
+ if (!e._parentNode || e.nodeName.toUpperCase() === 'FORM') {
+ return e;
+ }
+ e = e._parentNode;
+ }
+ return null;
+ },
+ get form() {
+ return closest(this, 'FORM');
+ },
+ get defaultValue() {
+ var val = this.getAttribute('value');
+ return val !== null ? val : "";
+ },
+ set defaultValue(val) {
+ this.setAttribute('value', String(val));
+ },
+ get defaultChecked() {
+ return this.getAttribute('checked') !== null;
+ },
+ set defaultChecked(s) {
+ if (s) this.setAttribute('checked', 'checked');
+ else this.removeAttribute('checked');
+ },
+ get checked() {
+ return this._checkedness;
+ },
+ set checked(checked) {
+ this._checkedness = !!checked;
+ this._dirtyCheckedness = true;
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ },
+ get value() {
+ if (this._value === null) {
+ return '';
+ }
+ return this._value;
+ },
+ set value(val) {
+ this._dirtyValue = true;
+ if (val === null) {
+ this._value = null;
+ } else {
+ this._value = String(val);
+ }
+ },
+ get type() {
+ var type = this.getAttribute('type');
+ return type ? type : 'text';
+ },
+ set type(type) {
+ this.setAttribute('type', type);
+ },
+ select: function() {
+ },
+
+ _dispatchClickEvent: function() {
+ var event = this._ownerDocument.createEvent("HTMLEvents");
+ event.initEvent("click", true, true);
+ this.dispatchEvent(event);
+ },
+
+ click: function() {
+ if (this.type === 'checkbox') {
+ this.checked = !this.checked;
+ }
+ else if (this.type === 'radio') {
+ this.checked = true;
+ }
+ else if (this.type === 'submit') {
+ var form = this.form;
+ if (form) {
+ form._dispatchSubmitEvent();
+ }
+ }
+ this._dispatchClickEvent();
+ }
+ },
+ attributes: [
+ 'accept',
+ 'accessKey',
+ 'align',
+ 'alt',
+ {prop: 'disabled', type: 'boolean'},
+ {prop: 'maxLength', type: 'long'},
+ 'name',
+ {prop: 'readOnly', type: 'boolean'},
+ {prop: 'size', type: 'long'},
+ 'src',
+ {prop: 'tabIndex', type: 'long'},
+ {prop: 'type', normalize: function(val) {
+ return val ? val.toLowerCase() : 'text';
+ }},
+ 'useMap'
+ ]
+});
+
+define('HTMLTextAreaElement', {
+ tagName: 'TEXTAREA',
+ proto: {
+ _apiValue: null,
+ _dirtyValue: false,
+ // "raw value" and "value" are not used here because jsdom has no GUI
+ _formReset: function() {
+ this._apiValue = null;
+ this._dirtyValue = false;
+ },
+ get form() {
+ return closest(this, 'FORM');
+ },
+ get defaultValue() {
+ return this.textContent;
+ },
+ set defaultValue(val) {
+ this.textContent = val;
+ },
+ get value() {
+ // The WHATWG specifies that when "textContent" changes, the "raw value"
+ // (just the API value in jsdom) must also be updated.
+ // This slightly different solution has identical results, but is a lot less complex.
+ if (this._dirtyValue) {
+ if (this._apiValue === null) {
+ return '';
+ }
+ return this._apiValue;
+ }
+
+ var val = this.defaultValue;
+ val = val.replace(/\r\n|\r/g, '\n'); // API value normalizes line breaks per WHATWG
+ return val;
+ },
+ set value(val) {
+ if (val) {
+ val = val.replace(/\r\n|\r/g, '\n'); // API value normalizes line breaks per WHATWG
+ }
+
+ this._dirtyValue = true;
+ this._apiValue = val;
+ },
+ get textLength() {
+ return this.value.length; // code unit length (16 bit)
+ },
+ get type() {
+ return 'textarea';
+ },
+ select: function() {
+ }
+ },
+ attributes: [
+ 'accessKey',
+ {prop: 'cols', type: 'long'},
+ {prop: 'disabled', type: 'boolean'},
+ {prop: 'maxLength', type: 'long'},
+ 'name',
+ {prop: 'readOnly', type: 'boolean'},
+ {prop: 'rows', type: 'long'},
+ {prop: 'tabIndex', type: 'long'}
+ ]
+});
+
+define('HTMLButtonElement', {
+ tagName: 'BUTTON',
+ proto: {
+ get form() {
+ return closest(this, 'FORM');
+ }
+ },
+ attributes: [
+ 'accessKey',
+ {prop: 'disabled', type: 'boolean'},
+ 'name',
+ {prop: 'tabIndex', type: 'long'},
+ 'type',
+ 'value'
+ ]
+});
+
+define('HTMLLabelElement', {
+ tagName: 'LABEL',
+ proto: {
+ get form() {
+ return closest(this, 'FORM');
+ }
+ },
+ attributes: [
+ 'accessKey',
+ {prop: 'htmlFor', attr: 'for'}
+ ]
+});
+
+define('HTMLFieldSetElement', {
+ tagName: 'FIELDSET',
+ proto: {
+ get form() {
+ return closest(this, 'FORM');
+ }
+ }
+});
+
+define('HTMLLegendElement', {
+ tagName: 'LEGEND',
+ proto: {
+ get form() {
+ return closest(this, 'FORM');
+ }
+ },
+ attributes: [
+ 'accessKey',
+ 'align'
+ ]
+});
+
+define('HTMLUListElement', {
+ tagName: 'UL',
+ attributes: [
+ {prop: 'compact', type: 'boolean'},
+ 'type'
+ ]
+});
+
+define('HTMLOListElement', {
+ tagName: 'OL',
+ attributes: [
+ {prop: 'compact', type: 'boolean'},
+ {prop: 'start', type: 'long'},
+ 'type'
+ ]
+});
+
+define('HTMLDListElement', {
+ tagName: 'DL',
+ attributes: [
+ {prop: 'compact', type: 'boolean'}
+ ]
+});
+
+define('HTMLDirectoryElement', {
+ tagName: 'DIR',
+ attributes: [
+ {prop: 'compact', type: 'boolean'}
+ ]
+});
+
+define('HTMLMenuElement', {
+ tagName: 'MENU',
+ attributes: [
+ {prop: 'compact', type: 'boolean'}
+ ]
+});
+
+define('HTMLLIElement', {
+ tagName: 'LI',
+ attributes: [
+ 'type',
+ {prop: 'value', type: 'long'}
+ ]
+});
+
+define('HTMLCanvasElement', {
+ tagName: 'CANVAS',
+ attributes: [
+ 'align'
+ ],
+ elementBuilder: function (element) {
+ // require node-canvas and catch the error if it blows up
+ try {
+ var canvas = new (require('canvas'))(0,0);
+ for (var attr in element) {
+ if (!canvas[attr]) {
+ canvas[attr] = element[attr];
+ }
+ }
+ return canvas;
+ } catch (e) {
+ return element;
+ }
+ }
+});
+
+define('HTMLDivElement', {
+ tagName: 'DIV',
+ attributes: [
+ 'align'
+ ],
+ proto: {
+ toString: function() { return '[object HTMLDivElement]'; }
+ }
+});
+
+define('HTMLParagraphElement', {
+ tagName: 'P',
+ attributes: [
+ 'align'
+ ]
+});
+
+define('HTMLHeadingElement', {
+ tagNames: ['H1','H2','H3','H4','H5','H6'],
+ attributes: [
+ 'align'
+ ]
+});
+
+define('HTMLQuoteElement', {
+ tagNames: ['Q','BLOCKQUOTE'],
+ attributes: [
+ 'cite'
+ ]
+});
+
+define('HTMLPreElement', {
+ tagName: 'PRE',
+ attributes: [
+ {prop: 'width', type: 'long'}
+ ]
+});
+
+define('HTMLBRElement', {
+ tagName: 'BR',
+ attributes: [
+ 'clear'
+ ]
+});
+
+define('HTMLBaseFontElement', {
+ tagName: 'BASEFONT',
+ attributes: [
+ 'color',
+ 'face',
+ {prop: 'size', type: 'long'}
+ ]
+});
+
+define('HTMLFontElement', {
+ tagName: 'FONT',
+ attributes: [
+ 'color',
+ 'face',
+ 'size'
+ ]
+});
+
+define('HTMLHRElement', {
+ tagName: 'HR',
+ attributes: [
+ 'align',
+ {prop: 'noShade', type: 'boolean'},
+ 'size',
+ 'width'
+ ]
+});
+
+define('HTMLModElement', {
+ tagNames: ['INS', 'DEL'],
+ attributes: [
+ 'cite',
+ 'dateTime'
+ ]
+});
+
+define('HTMLAnchorElement', {
+ tagName: 'A',
+
+ proto: {
+ get href() {
+ return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('href'));
+ },
+ get hostname() {
+ return URL.parse(this.href).hostname || '';
+ },
+ get host() {
+ return URL.parse(this.href).host || '';
+ },
+ get origin() {
+ var proto = URL.parse(this.href).protocol;
+
+ if (proto !== undefined && proto !== null) {
+ proto += '//';
+ }
+
+ return proto + URL.parse(this.href).host || '';
+ },
+ get port() {
+ return URL.parse(this.href).port || '';
+ },
+ get protocol() {
+ var protocol = URL.parse(this.href).protocol;
+ return (protocol == null) ? ':' : protocol;
+ },
+ get password() {
+ var auth = URL.parse(this.href).auth;
+ return auth.substr(auth.indexOf(':') + 1);
+ },
+ get pathname() {
+ return URL.parse(this.href).pathname || '';
+ },
+ get username() {
+ var auth = URL.parse(this.href).auth;
+ return auth.substr(0, auth.indexOf(':'));
+ },
+ get search() {
+ return URL.parse(this.href).search || '';
+ },
+ get hash() {
+ return URL.parse(this.href).hash || '';
+ }
+ },
+ attributes: [
+ 'accessKey',
+ 'charset',
+ 'coords',
+ {prop: 'href', type: 'string', read: false},
+ 'hreflang',
+ 'name',
+ 'rel',
+ 'rev',
+ 'shape',
+ {prop: 'tabIndex', type: 'long'},
+ 'target',
+ 'type'
+ ]
+});
+
+define('HTMLImageElement', {
+ tagName: 'IMG',
+ proto: {
+ _attrModified: function(name, value, oldVal) {
+ if (name == 'src' && value !== oldVal) {
+ core.resourceLoader.enqueue(this, function() {})();
+ }
+ },
+ get src() {
+ return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('src'));
+ }
+ },
+ attributes: [
+ 'name',
+ 'align',
+ 'alt',
+ 'border',
+ {prop: 'height', type: 'long'},
+ {prop: 'hspace', type: 'long'},
+ {prop: 'isMap', type: 'boolean'},
+ 'longDesc',
+ {prop: 'src', type: 'string', read: false},
+ 'useMap',
+ {prop: 'vspace', type: 'long'},
+ {prop: 'width', type: 'long'}
+ ]
+});
+
+define('HTMLObjectElement', {
+ tagName: 'OBJECT',
+ proto: {
+ get form() {
+ return closest(this, 'FORM');
+ },
+ get contentDocument() {
+ return null;
+ }
+ },
+ attributes: [
+ 'code',
+ 'align',
+ 'archive',
+ 'border',
+ 'codeBase',
+ 'codeType',
+ 'data',
+ {prop: 'declare', type: 'boolean'},
+ {prop: 'height', type: 'long'},
+ {prop: 'hspace', type: 'long'},
+ 'name',
+ 'standby',
+ {prop: 'tabIndex', type: 'long'},
+ 'type',
+ 'useMap',
+ {prop: 'vspace', type: 'long'},
+ {prop: 'width', type: 'long'}
+ ]
+});
+
+define('HTMLParamElement', {
+ tagName: 'PARAM',
+ attributes: [
+ 'name',
+ 'type',
+ 'value',
+ 'valueType'
+ ]
+});
+
+define('HTMLAppletElement', {
+ tagName: 'APPLET',
+ attributes: [
+ 'align',
+ 'alt',
+ 'archive',
+ 'code',
+ 'codeBase',
+ 'height',
+ {prop: 'hspace', type: 'long'},
+ 'name',
+ 'object',
+ {prop: 'vspace', type: 'long'},
+ 'width'
+ ]
+});
+
+define('HTMLMapElement', {
+ tagName: 'MAP',
+ proto: {
+ get areas() {
+ return this.getElementsByTagName("AREA");
+ }
+ },
+ attributes: [
+ 'name'
+ ]
+});
+
+define('HTMLAreaElement', {
+ tagName: 'AREA',
+ attributes: [
+ 'accessKey',
+ 'alt',
+ 'coords',
+ 'href',
+ {prop: 'noHref', type: 'boolean'},
+ 'shape',
+ {prop: 'tabIndex', type: 'long'},
+ 'target'
+ ]
+});
+
+define('HTMLScriptElement', {
+ tagName: 'SCRIPT',
+ init: function() {
+ this.addEventListener('DOMNodeInsertedIntoDocument', function() {
+ if (this.src) {
+ core.resourceLoader.load(this, this.src, this._eval);
+ }
+ else {
+ var src = this.sourceLocation || {},
+ filename = src.file || this._ownerDocument.URL;
+
+ if (src) {
+ filename += ':' + src.line + ':' + src.col;
+ }
+ filename += '<script>';
+
+ core.resourceLoader.enqueue(this, this._eval, filename)(null, this.text);
+ }
+ });
+ },
+ proto: {
+ _eval: function(text, filename) {
+ if (this._ownerDocument.implementation._hasFeature("ProcessExternalResources", "script") &&
+ this.language &&
+ core.languageProcessors[this.language])
+ {
+ this._ownerDocument._writeAfterElement = this;
+ core.languageProcessors[this.language](this, text, filename);
+ delete this._ownerDocument._writeAfterElement;
+ }
+ },
+ get language() {
+ var type = this.type || "text/javascript";
+ return type.split("/").pop().toLowerCase();
+ },
+ get text() {
+ var i=0, children = this._childNodes, l = children.length, ret = [];
+
+ for (i; i<l; i++) {
+ ret.push(children[i].nodeValue);
+ }
+
+ return ret.join("");
+ },
+ set text(text) {
+ while (this._childNodes.length) {
+ this.removeChild(this._childNodes[this._childNodes.length-1]);
+ }
+ this.appendChild(this._ownerDocument.createTextNode(text));
+ }
+ },
+ attributes : [
+ {prop: 'defer', 'type': 'boolean'},
+ 'htmlFor',
+ 'event',
+ 'charset',
+ 'type',
+ 'src'
+ ]
+})
+
+define('HTMLTableElement', {
+ tagName: 'TABLE',
+ proto: {
+ get caption() {
+ return firstChild(this, 'CAPTION');
+ },
+ get tHead() {
+ return firstChild(this, 'THEAD');
+ },
+ get tFoot() {
+ return firstChild(this, 'TFOOT');
+ },
+ get rows() {
+ if (!this._rows) {
+ var table = this;
+ this._rows = new core.HTMLCollection(this._ownerDocument, function() {
+ var sections = [table.tHead].concat(table.tBodies._toArray(), table.tFoot).filter(function(s) { return !!s });
+
+ if (sections.length === 0) {
+ return core.mapDOMNodes(table, false, function(el) {
+ return el.tagName === 'TR';
+ });
+ }
+
+ return sections.reduce(function(prev, s) {
+ return prev.concat(s.rows._toArray());
+ }, []);
+
+ });
+ }
+ return this._rows;
+ },
+ get tBodies() {
+ if (!this._tBodies) {
+ this._tBodies = descendants(this, 'TBODY', false);
+ }
+ return this._tBodies;
+ },
+ createTHead: function() {
+ var el = this.tHead;
+ if (!el) {
+ el = this._ownerDocument.createElement('THEAD');
+ this.appendChild(el);
+ }
+ return el;
+ },
+ deleteTHead: function() {
+ var el = this.tHead;
+ if (el) {
+ el._parentNode.removeChild(el);
+ }
+ },
+ createTFoot: function() {
+ var el = this.tFoot;
+ if (!el) {
+ el = this._ownerDocument.createElement('TFOOT');
+ this.appendChild(el);
+ }
+ return el;
+ },
+ deleteTFoot: function() {
+ var el = this.tFoot;
+ if (el) {
+ el._parentNode.removeChild(el);
+ }
+ },
+ createCaption: function() {
+ var el = this.caption;
+ if (!el) {
+ el = this._ownerDocument.createElement('CAPTION');
+ this.appendChild(el);
+ }
+ return el;
+ },
+ deleteCaption: function() {
+ var c = this.caption;
+ if (c) {
+ c._parentNode.removeChild(c);
+ }
+ },
+ insertRow: function(index) {
+ var tr = this._ownerDocument.createElement('TR');
+ if (this._childNodes.length === 0) {
+ this.appendChild(this._ownerDocument.createElement('TBODY'));
+ }
+ var rows = this.rows._toArray();
+ if (index < -1 || index > rows.length) {
+ throw new core.DOMException(core.INDEX_SIZE_ERR);
+ }
+ if (index === -1 || (index === 0 && rows.length === 0)) {
+ this.tBodies.item(0).appendChild(tr);
+ }
+ else if (index === rows.length) {
+ var ref = rows[index-1];
+ ref._parentNode.appendChild(tr);
+ }
+ else {
+ var ref = rows[index];
+ ref._parentNode.insertBefore(tr, ref);
+ }
+ return tr;
+ },
+ deleteRow: function(index) {
+ var rows = this.rows._toArray(), l = rows.length;
+ if (index === -1) {
+ index = l-1;
+ }
+ if (index < 0 || index >= l) {
+ throw new core.DOMException(core.INDEX_SIZE_ERR);
+ }
+ var tr = rows[index];
+ tr._parentNode.removeChild(tr);
+ }
+ },
+ attributes: [
+ 'align',
+ 'bgColor',
+ 'border',
+ 'cellPadding',
+ 'cellSpacing',
+ 'frame',
+ 'rules',
+ 'summary',
+ 'width'
+ ]
+});
+
+define('HTMLTableCaptionElement', {
+ tagName: 'CAPTION',
+ attributes: [
+ 'align'
+ ]
+});
+
+define('HTMLTableColElement', {
+ tagNames: ['COL','COLGROUP'],
+ attributes: [
+ 'align',
+ {prop: 'ch', attr: 'char'},
+ {prop: 'chOff', attr: 'charoff'},
+ {prop: 'span', type: 'long'},
+ 'vAlign',
+ 'width',
+ ]
+});
+
+define('HTMLTableSectionElement', {
+ tagNames: ['THEAD','TBODY','TFOOT'],
+ proto: {
+ get rows() {
+ if (!this._rows) {
+ this._rows = descendants(this, 'TR', false);
+ }
+ return this._rows;
+ },
+ insertRow: function(index) {
+ var tr = this._ownerDocument.createElement('TR');
+ var rows = this.rows._toArray();
+ if (index < -1 || index > rows.length) {
+ throw new core.DOMException(core.INDEX_SIZE_ERR);
+ }
+ if (index === -1 || index === rows.length) {
+ this.appendChild(tr);
+ }
+ else {
+ var ref = rows[index];
+ this.insertBefore(tr, ref);
+ }
+ return tr;
+ },
+ deleteRow: function(index) {
+ var rows = this.rows._toArray();
+ if (index === -1) {
+ index = rows.length-1;
+ }
+ if (index < 0 || index >= rows.length) {
+ throw new core.DOMException(core.INDEX_SIZE_ERR);
+ }
+ var tr = this.rows[index];
+ this.removeChild(tr);
+ }
+ },
+ attributes: [
+ 'align',
+ {prop: 'ch', attr: 'char'},
+ {prop: 'chOff', attr: 'charoff'},
+ {prop: 'span', type: 'long'},
+ 'vAlign',
+ 'width',
+ ]
+});
+
+define('HTMLTableRowElement', {
+ tagName: 'TR',
+ proto: {
+ get cells() {
+ if (!this._cells) {
+ this._cells = new core.HTMLCollection(this, core.mapper(this, function(n) {
+ return n.nodeName === 'TD' || n.nodeName === 'TH';
+ }, false));
+ }
+ return this._cells;
+ },
+ get rowIndex() {
+ var table = closest(this, 'TABLE');
+ return table ? table.rows._toArray().indexOf(this) : -1;
+ },
+
+ get sectionRowIndex() {
+ return this._parentNode.rows._toArray().indexOf(this);
+ },
+ insertCell: function(index) {
+ var td = this._ownerDocument.createElement('TD');
+ var cells = this.cells._toArray();
+ if (index < -1 || index > cells.length) {
+ throw new core.DOMException(core.INDEX_SIZE_ERR);
+ }
+ if (index === -1 || index === cells.length) {
+ this.appendChild(td);
+ }
+ else {
+ var ref = cells[index];
+ this.insertBefore(td, ref);
+ }
+ return td;
+ },
+ deleteCell: function(index) {
+ var cells = this.cells._toArray();
+ if (index === -1) {
+ index = cells.length-1;
+ }
+ if (index < 0 || index >= cells.length) {
+ throw new core.DOMException(core.INDEX_SIZE_ERR);
+ }
+ var td = this.cells[index];
+ this.removeChild(td);
+ }
+ },
+ attributes: [
+ 'align',
+ 'bgColor',
+ {prop: 'ch', attr: 'char'},
+ {prop: 'chOff', attr: 'charoff'},
+ 'vAlign'
+ ]
+});
+
+define('HTMLTableCellElement', {
+ tagNames: ['TH','TD'],
+ proto: {
+ _headers: null,
+ set headers(h) {
+ if (h === '') {
+ //Handle resetting headers so the dynamic getter returns a query
+ this._headers = null;
+ return;
+ }
+ if (!(h instanceof Array)) {
+ h = [h];
+ }
+ this._headers = h;
+ },
+ get headers() {
+ if (this._headers) {
+ return this._headers.join(' ');
+ }
+ var cellIndex = this.cellIndex,
+ headings = [],
+ siblings = this._parentNode.getElementsByTagName(this.tagName);
+
+ for (var i=0; i<siblings.length; i++) {
+ if (siblings.item(i).cellIndex >= cellIndex) {
+ break;
+ }
+ headings.push(siblings.item(i).id);
+ }
+ this._headers = headings;
+ return headings.join(' ');
+ },
+ get cellIndex() {
+ return closest(this, 'TR').cells._toArray().indexOf(this);
+ }
+ },
+ attributes: [
+ 'abbr',
+ 'align',
+ 'axis',
+ 'bgColor',
+ {prop: 'ch', attr: 'char'},
+ {prop: 'chOff', attr: 'charoff'},
+ {prop: 'colSpan', type: 'long'},
+ 'height',
+ {prop: 'noWrap', type: 'boolean'},
+ {prop: 'rowSpan', type: 'long'},
+ 'scope',
+ 'vAlign',
+ 'width'
+ ]
+});
+
+define('HTMLFrameSetElement', {
+ tagName: 'FRAMESET',
+ attributes: [
+ 'cols',
+ 'rows'
+ ]
+});
+
+function loadFrame (frame) {
+ if (frame._contentDocument) {
+ // We don't want to access document.parentWindow, since the getter will
+ // cause a new window to be allocated if it doesn't exist. Probe the
+ // private variable instead.
+ if (frame._contentDocument._parentWindow) {
+ // close calls delete on its document.
+ frame._contentDocument.parentWindow.close();
+ } else {
+ delete frame._contentDocument;
+ }
+ }
+
+ var src = frame.src.trim() === '' ? 'about:blank' : frame.src;
+ var parentDoc = frame._ownerDocument;
+
+ // If the URL can't be resolved or the src attribute is missing / blank,
+ // then url should be set to the string "about:blank".
+ // (http://www.whatwg.org/specs/web-apps/current-work/#the-iframe-element)
+ var url = core.resourceLoader.resolve(parentDoc, src);
+ var contentDoc = frame._contentDocument = new core.HTMLDocument({
+ parsingMode: 'html',
+ url: url,
+ documentRoot: Path.dirname(url)
+ });
+ applyDocumentFeatures(contentDoc, parentDoc.implementation._features);
+
+ var parent = parentDoc.parentWindow;
+ var contentWindow = contentDoc.parentWindow;
+ contentWindow.parent = parent;
+ contentWindow.top = parent.top;
+
+ // Handle about:blank with a simulated load of an empty document.
+ if(url === 'about:blank') {
+ core.resourceLoader.enqueue(frame, function() {
+ contentDoc.write();
+ contentDoc.close();
+ })();
+ } else {
+ core.resourceLoader.load(frame, url, function(html, filename) {
+ contentDoc.write(html);
+ contentDoc.close();
+ });
+ }
+}
+
+define('HTMLFrameElement', {
+ tagName: 'FRAME',
+ init : function () {
+ // Set up the frames array. window.frames really just returns a reference
+ // to the window object, so the frames array is just implemented as indexes
+ // on the window.
+ var parent = this._ownerDocument.parentWindow;
+ var frameID = parent._length++;
+ var self = this;
+ defineGetter(parent, frameID, function () {
+ return self.contentWindow;
+ });
+
+ // The contentDocument/contentWindow shouldn't be created until the frame
+ // is inserted:
+ // "When an iframe element is first inserted into a document, the user
+ // agent must create a nested browsing context, and then process the
+ // iframe attributes for the first time."
+ // (http://www.whatwg.org/specs/web-apps/current-work/#the-iframe-element)
+ this._initInsertListener = function () {
+ loadFrame(self);
+ };
+ this.addEventListener('DOMNodeInsertedIntoDocument', this._initInsertListener, false);
+ },
+ proto: {
+ _attrModified: function(name, value, oldVal) {
+ core.HTMLElement.prototype._attrModified.call(this, name, value, oldVal);
+ var self = this;
+ if (name === 'name') {
+ // Remove named frame access.
+ if (oldVal) {
+ this._ownerDocument.parentWindow._frame(oldVal);
+ }
+ // Set up named frame access.
+ if (value) {
+ this._ownerDocument.parentWindow._frame(value, this);
+ }
+ } else if (name === 'src') {
+ // Page we don't fetch the page until the node is inserted. This at
+ // least seems to be the way Chrome does it.
+ if (!this._attachedToDocument) {
+ if (!this._waitingOnInsert) {
+ // First, remove the listener added in 'init'.
+ this.removeEventListener('DOMNodeInsertedIntoDocument',
+ this._initInsertListener, false)
+
+ // If we aren't already waiting on an insert, add a listener.
+ // This guards against src being set multiple times before the frame
+ // is inserted into the document - we don't want to register multiple
+ // callbacks.
+ this.addEventListener('DOMNodeInsertedIntoDocument', function loader () {
+ self.removeEventListener('DOMNodeInsertedIntoDocument', loader, false);
+ this._waitingOnInsert = false;
+ loadFrame(self);
+ }, false);
+ this._waitingOnInsert = true;
+ }
+ } else {
+ loadFrame(self);
+ }
+ }
+ },
+ _contentDocument : null,
+ get contentDocument() {
+ if (this._contentDocument == null) {
+ this._contentDocument = new core.HTMLDocument({ parsingMode: "html" });
+ }
+ return this._contentDocument;
+ },
+ get contentWindow() {
+ return this.contentDocument.parentWindow;
+ }
+ },
+ attributes: [
+ 'frameBorder',
+ 'longDesc',
+ 'marginHeight',
+ 'marginWidth',
+ 'name',
+ {prop: 'noResize', type: 'boolean'},
+ 'scrolling',
+ {prop: 'src', type: 'string', write: false}
+ ]
+});
+
+define('HTMLIFrameElement', {
+ tagName: 'IFRAME',
+ parentClass: core.HTMLFrameElement,
+ attributes: [
+ 'align',
+ 'frameBorder',
+ 'height',
+ 'longDesc',
+ 'marginHeight',
+ 'marginWidth',
+ 'name',
+ 'scrolling',
+ 'src',
+ 'width'
+ ]
+});
diff --git a/node_modules/jsdom/lib/jsdom/level2/languages/javascript.js b/node_modules/jsdom/lib/jsdom/level2/languages/javascript.js
new file mode 100644
index 0000000..308724d
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level2/languages/javascript.js
@@ -0,0 +1,13 @@
+exports.javascript = function(element, code, filename) {
+ var doc = element.ownerDocument, window = doc && doc.parentWindow;
+ if (window) {
+ try {
+ window.run(code, filename);
+ } catch (e) {
+ element.raise(
+ 'error', 'Running ' + filename + ' failed.',
+ {error: e, filename: filename}
+ );
+ }
+ }
+};
diff --git a/node_modules/jsdom/lib/jsdom/level2/style.js b/node_modules/jsdom/lib/jsdom/level2/style.js
new file mode 100644
index 0000000..ab63fcc
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level2/style.js
@@ -0,0 +1,263 @@
+var core = require("../level1/core"),
+ utils = require("../utils"),
+ defineGetter = utils.defineGetter,
+ defineSetter = utils.defineSetter,
+ inheritFrom = utils.inheritFrom,
+ cssom = require("cssom"),
+ cssstyle = require("cssstyle"),
+ assert = require('assert');
+
+// What works now:
+// - Accessing the rules defined in individual stylesheets
+// - Modifications to style content attribute are reflected in style property
+// - Modifications to style property are reflected in style content attribute
+// TODO
+// - Modifications to style element's textContent are reflected in sheet property.
+// - Modifications to style element's sheet property are reflected in textContent.
+// - Modifications to link.href property are reflected in sheet property.
+// - Less-used features of link: disabled
+// - Less-used features of style: disabled, scoped, title
+// - CSSOM-View
+// - getComputedStyle(): requires default stylesheet, cascading, inheritance,
+// filtering by @media (screen? print?), layout for widths/heights
+// - Load events are not in the specs, but apparently some browsers
+// implement something. Should onload only fire after all @imports have been
+// loaded, or only the primary sheet?
+
+core.StyleSheet = cssom.StyleSheet;
+core.MediaList = cssom.MediaList;
+core.CSSStyleSheet = cssom.CSSStyleSheet;
+core.CSSRule = cssom.CSSRule;
+core.CSSStyleRule = cssom.CSSStyleRule;
+core.CSSMediaRule = cssom.CSSMediaRule;
+core.CSSImportRule = cssom.CSSImportRule;
+core.CSSStyleDeclaration = cssstyle.CSSStyleDeclaration;
+
+// Relavant specs
+// http://www.w3.org/TR/DOM-Level-2-Style (2000)
+// http://www.w3.org/TR/cssom-view/ (2008)
+// http://dev.w3.org/csswg/cssom/ (2010) Meant to replace DOM Level 2 Style
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/ HTML5, of course
+// http://dev.w3.org/csswg/css-style-attr/ not sure what's new here
+
+// Objects that aren't in cssom library but should be:
+// CSSRuleList (cssom just uses array)
+// CSSFontFaceRule
+// CSSPageRule
+
+// These rules don't really make sense to implement, so CSSOM draft makes them
+// obsolete.
+// CSSCharsetRule
+// CSSUnknownRule
+
+// These objects are considered obsolete by CSSOM draft, although modern
+// browsers implement them.
+// CSSValue
+// CSSPrimitiveValue
+// CSSValueList
+// RGBColor
+// Rect
+// Counter
+
+// StyleSheetList -
+// http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheetList
+// added a push method to help manage the length
+core.StyleSheetList = function() {
+ this._length = 0;
+};
+core.StyleSheetList.prototype = {
+ item: function (i) {
+ return this[i];
+ },
+ push: function (sheet) {
+ this[this._length] = sheet;
+ this._length++;
+ },
+ get length() {
+ return this._length;
+ }
+};
+
+defineGetter(core.Document.prototype, 'styleSheets', function() {
+ if (!this._styleSheets) {
+ this._styleSheets = new core.StyleSheetList();
+ }
+ // TODO: each style and link element should register its sheet on creation
+ // and remove it on removal.
+ return this._styleSheets;
+});
+
+
+/**
+ * @this {core.HTMLLinkElement|core.HTMLStyleElement}
+ * @param {string} url
+ * @param {cssom.CSSStyleSheet} sheet
+ * @see http://dev.w3.org/csswg/cssom/#requirements-on-user-agents-implementing0
+ */
+function fetchStylesheet(url, sheet) {
+ core.resourceLoader.load(this, url, function(data, filename) {
+ // TODO: abort if the content-type is not text/css, and the document is
+ // in strict mode
+ sheet.href = core.resourceLoader.resolve(this.ownerDocument, url);
+ evaluateStylesheet.call(this, data, sheet, url);
+ });
+}
+/**
+ * @this {core.HTMLLinkElement|core.HTMLStyleElement}
+ * @param {string} data
+ * @param {cssom.CSSStyleSheet} sheet
+ * @param {string} baseUrl
+ */
+function evaluateStylesheet(data, sheet, baseUrl) {
+ // this is the element
+ var newStyleSheet = cssom.parse(data);
+ var spliceArgs = newStyleSheet.cssRules;
+ spliceArgs.unshift(0, sheet.cssRules.length);
+ Array.prototype.splice.apply(sheet.cssRules, spliceArgs);
+ scanForImportRules.call(this, sheet.cssRules, baseUrl);
+ this.ownerDocument.styleSheets.push(sheet);
+}
+/**
+ * @this {core.HTMLLinkElement|core.HTMLStyleElement}
+ * @param {cssom.CSSStyleSheet} sheet
+ * @param {string} baseUrl
+ */
+function scanForImportRules(cssRules, baseUrl) {
+ if (!cssRules) return;
+ for (var i = 0; i < cssRules.length; ++i) {
+ if (cssRules[i].cssRules) {
+ // @media rule: keep searching inside it.
+ scanForImportRules.call(this, cssRules[i].cssRules, baseUrl);
+ } else if (cssRules[i].href) {
+ // @import rule: fetch the resource and evaluate it.
+ // See http://dev.w3.org/csswg/cssom/#css-import-rule
+ // If loading of the style sheet fails its cssRules list is simply
+ // empty. I.e. an @import rule always has an associated style sheet.
+ fetchStylesheet.call(this, cssRules[i].href, this.sheet);
+ }
+ }
+}
+
+/**
+ * @param {string} data
+ * @param {cssstyle.CSSStyleDeclaration} style
+ */
+function evaluateStyleAttribute(data) {
+ // this is the element.
+
+}
+
+/**
+ * Subclass of core.Attr that reflects the current cssText.
+ */
+function StyleAttr(node, value) {
+ this._node = node;
+ core.Attr.call(this, node.ownerDocument, 'style');
+ if (!this._node._ignoreValueOfStyleAttr) {
+ this.nodeValue = value;
+ }
+}
+inheritFrom(core.Attr, StyleAttr, {
+ get nodeValue() {
+ if (typeof this._node._style === 'string') {
+ return this._node._style;
+ } else {
+ return this._node.style.cssText;
+ }
+ },
+ set nodeValue(value) {
+ this._node._style = value;
+ }
+});
+
+var $setNode_super = core.AttributeList.prototype.$setNode;
+/**
+ * Overwrite core.AttrNodeMap#setNamedItem to create a StyleAttr instance
+ * instead of a core.Attr if the name equals 'style'.
+ */
+core.AttributeList.prototype.$setNode = function(attr) {
+ if (attr.name == 'style') {
+ attr = new StyleAttr(this._parentNode, attr.nodeValue);
+ }
+ return $setNode_super.call(this, attr);
+};
+
+/**
+ * Lazily create a CSSStyleDeclaration.
+ */
+defineGetter(core.HTMLElement.prototype, 'style', function() {
+ if (typeof this._style === 'string') {
+ // currently, cssom's parse doesn't really work if you pass in
+ // {state: 'name'}, so instead we just build a dummy sheet.
+ var styleSheet = cssom.parse('dummy{' + this._style + '}');
+ this._style = new cssstyle.CSSStyleDeclaration();
+ if (styleSheet.cssRules.length > 0 && styleSheet.cssRules[0].style) {
+ var newStyle = styleSheet.cssRules[0].style;
+ for (var i = 0; i < newStyle.length; ++i) {
+ var prop = newStyle[i];
+ this._style.setProperty(
+ prop,
+ newStyle.getPropertyValue(prop),
+ newStyle.getPropertyPriority(prop));
+ }
+ }
+ }
+ if (!this._style) {
+ this._style = new cssstyle.CSSStyleDeclaration();
+
+ }
+ if (!this.getAttributeNode('style')) {
+ // Tell the StyleAttr constructor to not overwrite this._style
+ this._ignoreValueOfStyleAttr = true;
+ this.setAttribute('style');
+ this._ignoreValueOfStyleAttr = false;
+ }
+ return this._style;
+});
+
+assert.equal(undefined, core.HTMLLinkElement._init);
+core.HTMLLinkElement._init = function() {
+ this.addEventListener('DOMNodeInsertedIntoDocument', function() {
+ if (!/(?:[ \t\n\r\f]|^)stylesheet(?:[ \t\n\r\f]|$)/i.test(this.rel)) {
+ // rel is a space-separated list of tokens, and the original rel types
+ // are case-insensitive.
+ return;
+ }
+ if (this.href) {
+ fetchStylesheet.call(this, this.href, this.sheet);
+ }
+ });
+ this.addEventListener('DOMNodeRemovedFromDocument', function() {
+ });
+};
+/**
+ * @this {HTMLStyleElement|HTMLLinkElement}
+ */
+var getOrCreateSheet = function() {
+ if (!this._cssStyleSheet) {
+ this._cssStyleSheet = new cssom.CSSStyleSheet();
+ }
+ return this._cssStyleSheet;
+};
+defineGetter(core.HTMLLinkElement.prototype, 'sheet', getOrCreateSheet);
+
+assert.equal(undefined, core.HTMLStyleElement._init);
+core.HTMLStyleElement._init = function() {
+ //console.log('init style')
+ this.addEventListener('DOMNodeInsertedIntoDocument', function() {
+ //console.log('style inserted')
+ //console.log('sheet: ', this.sheet);
+ if (this.type && this.type !== 'text/css') {
+ //console.log('bad type: ' + this.type)
+ return;
+ }
+ var content = '';
+ this._childNodes.forEach(function (child) {
+ if (child.nodeType === child.TEXT_NODE) { // text node
+ content += child.nodeValue;
+ }
+ });
+ evaluateStylesheet.call(this, content, this.sheet, this._ownerDocument.URL);
+ });
+};
+defineGetter(core.HTMLStyleElement.prototype, 'sheet', getOrCreateSheet);
diff --git a/node_modules/jsdom/lib/jsdom/level3/core.js b/node_modules/jsdom/lib/jsdom/level3/core.js
new file mode 100644
index 0000000..0d0820e
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level3/core.js
@@ -0,0 +1,597 @@
+var core = require("../level1/core"),
+ defineGetter = require('../utils').defineGetter,
+ defineSetter = require('../utils').defineSetter,
+ HtmlToDom = require('../browser/htmltodom').HtmlToDom,
+ domToHtml = require('../browser/domtohtml').domToHtml;
+
+/*
+ valuetype DOMString sequence<unsigned short>;
+ typedef unsigned long long DOMTimeStamp;
+ typedef any DOMUserData;
+ typedef Object DOMObject;
+
+*/
+// ExceptionCode
+core.VALIDATION_ERR = 16;
+core.TYPE_MISMATCH_ERR = 17;
+
+/*
+ // Introduced in DOM Level 3:
+ interface NameList {
+ DOMString getName(in unsigned long index);
+ DOMString getNamespaceURI(in unsigned long index);
+ readonly attribute unsigned long length;
+ boolean contains(in DOMString str);
+ boolean containsNS(in DOMString namespaceURI,
+ in DOMString name);
+ };
+
+ // Introduced in DOM Level 3:
+ interface DOMImplementationList {
+ DOMImplementation item(in unsigned long index);
+ readonly attribute unsigned long length;
+ };
+
+ // Introduced in DOM Level 3:
+ interface DOMImplementationSource {
+ DOMImplementation getDOMImplementation(in DOMString features);
+ DOMImplementationList getDOMImplementationList(in DOMString features);
+ };
+*/
+
+
+core.DOMImplementation.prototype.getFeature = function(feature, version) {
+
+};
+
+/*
+ interface Node {
+ // Modified in DOM Level 3:
+ Node insertBefore(in Node newChild,
+ in Node refChild)
+ raises(DOMException);
+ // Modified in DOM Level 3:
+ Node replaceChild(in Node newChild,
+ in Node oldChild)
+ raises(DOMException);
+ // Modified in DOM Level 3:
+ Node removeChild(in Node oldChild)
+ raises(DOMException);
+ // Modified in DOM Level 3:
+ Node appendChild(in Node newChild)
+ raises(DOMException);
+ boolean hasChildNodes();
+ Node cloneNode(in boolean deep);
+ // Modified in DOM Level 3:
+ void normalize();
+ // Introduced in DOM Level 3:
+ readonly attribute DOMString baseURI;
+*/
+
+// Compare Document Position
+var DOCUMENT_POSITION_DISCONNECTED = core.Node.DOCUMENT_POSITION_DISCONNECTED =
+ core.Node.prototype.DOCUMENT_POSITION_DISCONNECTED = 0x01;
+
+var DOCUMENT_POSITION_PRECEDING = core.Node.DOCUMENT_POSITION_PRECEDING =
+ core.Node.prototype.DOCUMENT_POSITION_PRECEDING = 0x02;
+
+var DOCUMENT_POSITION_FOLLOWING = core.Node.DOCUMENT_POSITION_FOLLOWING =
+ core.Node.prototype.DOCUMENT_POSITION_FOLLOWING = 0x04;
+
+var DOCUMENT_POSITION_CONTAINS = core.Node.DOCUMENT_POSITION_CONTAINS =
+ core.Node.prototype.DOCUMENT_POSITION_CONTAINS = 0x08;
+
+var DOCUMENT_POSITION_CONTAINED_BY = core.Node.DOCUMENT_POSITION_CONTAINED_BY =
+ core.Node.prototype.DOCUMENT_POSITION_CONTAINED_BY = 0x10;
+
+var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = core.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC =
+ core.Node.prototype.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
+
+var DOCUMENT_TYPE_NODE = core.Node.prototype.DOCUMENT_TYPE_NODE;
+
+core.Node.prototype.compareDocumentPosition = function compareDocumentPosition( otherNode ) {
+ if( !(otherNode instanceof core.Node) ) {
+ throw Error("Comparing position against non-Node values is not allowed")
+ }
+ var thisOwner, otherOwner;
+
+ if( this.nodeType === this.DOCUMENT_NODE)
+ thisOwner = this
+ else
+ thisOwner = this.ownerDocument
+
+ if( otherNode.nodeType === this.DOCUMENT_NODE)
+ otherOwner = otherNode
+ else
+ otherOwner = otherNode.ownerDocument
+
+ if( this === otherNode ) return 0
+ if( this === otherNode.ownerDocument ) return DOCUMENT_POSITION_FOLLOWING + DOCUMENT_POSITION_CONTAINED_BY
+ if( this.ownerDocument === otherNode ) return DOCUMENT_POSITION_PRECEDING + DOCUMENT_POSITION_CONTAINS
+ if( thisOwner !== otherOwner ) return DOCUMENT_POSITION_DISCONNECTED
+
+ // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
+ if( this.nodeType === this.ATTRIBUTE_NODE && this._childNodes && this._childNodes.indexOf(otherNode) !== -1)
+ return DOCUMENT_POSITION_FOLLOWING + DOCUMENT_POSITION_CONTAINED_BY
+
+ if( otherNode.nodeType === this.ATTRIBUTE_NODE && otherNode._childNodes && otherNode._childNodes.indexOf(this) !== -1)
+ return DOCUMENT_POSITION_PRECEDING + DOCUMENT_POSITION_CONTAINS
+
+ var point = this
+ var parents = [ ]
+ var previous = null
+ while( point ) {
+ if( point == otherNode ) return DOCUMENT_POSITION_PRECEDING + DOCUMENT_POSITION_CONTAINS
+ parents.push( point )
+ point = point._parentNode
+ }
+ point = otherNode
+ previous = null
+ while( point ) {
+ if( point == this ) return DOCUMENT_POSITION_FOLLOWING + DOCUMENT_POSITION_CONTAINED_BY
+ var location_index = parents.indexOf( point )
+ if( location_index !== -1) {
+ var smallest_common_ancestor = parents[ location_index ]
+ var this_index = smallest_common_ancestor._childNodes.indexOf( parents[location_index - 1] )
+ var other_index = smallest_common_ancestor._childNodes.indexOf( previous )
+ if( this_index > other_index ) {
+ return DOCUMENT_POSITION_PRECEDING
+ }
+ else {
+ return DOCUMENT_POSITION_FOLLOWING
+ }
+ }
+ previous = point
+ point = point._parentNode
+ }
+ return DOCUMENT_POSITION_DISCONNECTED
+};
+
+// @see http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-textContent
+defineGetter(core.Node.prototype, 'textContent', function() {
+ switch (this.nodeType) {
+ case this.COMMENT_NODE:
+ case this.CDATA_SECTION_NODE:
+ case this.PROCESSING_INSTRUCTION_NODE:
+ case this.TEXT_NODE:
+ return this.nodeValue;
+
+ case this.ATTRIBUTE_NODE:
+ case this.DOCUMENT_FRAGMENT_NODE:
+ case this.ELEMENT_NODE:
+ var out = '';
+ for (var i = 0 ; i < this._childNodes.length ; ++i) {
+ if (this._childNodes[i].nodeType !== this.COMMENT_NODE &&
+ this._childNodes[i].nodeType !== this.PROCESSING_INSTRUCTION_NODE) {
+ out += this._childNodes[i].textContent || '';
+ }
+ }
+ return out;
+
+ default:
+ return null;
+ }
+});
+
+defineSetter(core.Node.prototype, 'textContent', function(txt) {
+ switch (this.nodeType) {
+ case this.COMMENT_NODE:
+ case this.CDATA_SECTION_NODE:
+ case this.PROCESSING_INSTRUCTION_NODE:
+ case this.TEXT_NODE:
+ return this.nodeValue = String(txt);
+ }
+
+ for (var i = this._childNodes.length; --i >=0;) {
+ this.removeChild(this._childNodes[i]);
+ }
+ if (txt !== "" && txt != null) {
+ this.appendChild(this._ownerDocument.createTextNode(txt));
+ }
+ return txt;
+});
+
+/*
+ // Introduced in DOM Level 3:
+ DOMString lookupPrefix(in DOMString namespaceURI);
+ // Introduced in DOM Level 3:
+ boolean isDefaultNamespace(in DOMString namespaceURI);
+ // Introduced in DOM Level 3:
+ DOMString lookupNamespaceURI(in DOMString prefix);
+ // Introduced in DOM Level 3:
+ DOMObject getFeature(in DOMString feature,
+ in DOMString version);
+*/
+// Introduced in DOM Level 3:
+core.Node.prototype.setUserData = function(key, data, handler) {
+ var r = this[key] || null;
+ this[key] = data;
+ return(r);
+};
+
+// Introduced in DOM Level 3:
+core.Node.prototype.getUserData = function(key) {
+ var r = this[key] || null;
+ return(r);
+};
+/*
+ interface NodeList {
+ Node item(in unsigned long index);
+ readonly attribute unsigned long length;
+ };
+
+ interface NamedNodeMap {
+ Node getNamedItem(in DOMString name);
+ Node setNamedItem(in Node arg)
+ raises(DOMException);
+ Node removeNamedItem(in DOMString name)
+ raises(DOMException);
+ Node item(in unsigned long index);
+ readonly attribute unsigned long length;
+ // Introduced in DOM Level 2:
+ Node getNamedItemNS(in DOMString namespaceURI,
+ in DOMString localName)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ Node setNamedItemNS(in Node arg)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ Node removeNamedItemNS(in DOMString namespaceURI,
+ in DOMString localName)
+ raises(DOMException);
+ };
+
+ interface CharacterData : Node {
+ attribute DOMString data;
+ // raises(DOMException) on setting
+ // raises(DOMException) on retrieval
+
+ readonly attribute unsigned long length;
+ DOMString substringData(in unsigned long offset,
+ in unsigned long count)
+ raises(DOMException);
+ void appendData(in DOMString arg)
+ raises(DOMException);
+ void insertData(in unsigned long offset,
+ in DOMString arg)
+ raises(DOMException);
+ void deleteData(in unsigned long offset,
+ in unsigned long count)
+ raises(DOMException);
+ void replaceData(in unsigned long offset,
+ in unsigned long count,
+ in DOMString arg)
+ raises(DOMException);
+ };
+
+ interface Attr : Node {
+ readonly attribute DOMString name;
+ readonly attribute boolean specified;
+ attribute DOMString value;
+ // raises(DOMException) on setting
+
+ // Introduced in DOM Level 2:
+ readonly attribute Element ownerElement;
+ // Introduced in DOM Level 3:
+ readonly attribute TypeInfo schemaTypeInfo;
+
+*/
+ // Introduced in DOM Level 3:
+defineGetter(core.Attr.prototype, 'isId', function() {
+ return (this.name.toLowerCase() === 'id');
+});
+/*
+ };
+
+ interface Element : Node {
+ readonly attribute DOMString tagName;
+ DOMString getAttribute(in DOMString name);
+ void setAttribute(in DOMString name,
+ in DOMString value)
+ raises(DOMException);
+ void removeAttribute(in DOMString name)
+ raises(DOMException);
+ Attr getAttributeNode(in DOMString name);
+ Attr setAttributeNode(in Attr newAttr)
+ raises(DOMException);
+ Attr removeAttributeNode(in Attr oldAttr)
+ raises(DOMException);
+ NodeList getElementsByTagName(in DOMString name);
+ // Introduced in DOM Level 2:
+ DOMString getAttributeNS(in DOMString namespaceURI,
+ in DOMString localName)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ void setAttributeNS(in DOMString namespaceURI,
+ in DOMString qualifiedName,
+ in DOMString value)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ void removeAttributeNS(in DOMString namespaceURI,
+ in DOMString localName)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ Attr getAttributeNodeNS(in DOMString namespaceURI,
+ in DOMString localName)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ Attr setAttributeNodeNS(in Attr newAttr)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ NodeList getElementsByTagNameNS(in DOMString namespaceURI,
+ in DOMString localName)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ boolean hasAttribute(in DOMString name);
+ // Introduced in DOM Level 2:
+ boolean hasAttributeNS(in DOMString namespaceURI,
+ in DOMString localName)
+ raises(DOMException);
+ // Introduced in DOM Level 3:
+ readonly attribute TypeInfo schemaTypeInfo;
+ // Introduced in DOM Level 3:
+ void setIdAttribute(in DOMString name,
+ in boolean isId)
+ raises(DOMException);
+ // Introduced in DOM Level 3:
+ void setIdAttributeNS(in DOMString namespaceURI,
+ in DOMString localName,
+ in boolean isId)
+ raises(DOMException);
+ // Introduced in DOM Level 3:
+ void setIdAttributeNode(in Attr idAttr,
+ in boolean isId)
+ raises(DOMException);
+ };
+
+ interface Text : CharacterData {
+ Text splitText(in unsigned long offset)
+ raises(DOMException);
+ // Introduced in DOM Level 3:
+ readonly attribute boolean isElementContentWhitespace;
+ // Introduced in DOM Level 3:
+ readonly attribute DOMString wholeText;
+ // Introduced in DOM Level 3:
+ Text replaceWholeText(in DOMString content)
+ raises(DOMException);
+ };
+
+ interface Comment : CharacterData {
+ };
+
+ // Introduced in DOM Level 3:
+ interface TypeInfo {
+ readonly attribute DOMString typeName;
+ readonly attribute DOMString typeNamespace;
+
+ // DerivationMethods
+ const unsigned long DERIVATION_RESTRICTION = 0x00000001;
+ const unsigned long DERIVATION_EXTENSION = 0x00000002;
+ const unsigned long DERIVATION_UNION = 0x00000004;
+ const unsigned long DERIVATION_LIST = 0x00000008;
+
+ boolean isDerivedFrom(in DOMString typeNamespaceArg,
+ in DOMString typeNameArg,
+ in unsigned long derivationMethod);
+ };
+*/
+// Introduced in DOM Level 3:
+core.UserDataHandler = function() {};
+core.UserDataHandler.prototype.NODE_CLONED = 1;
+core.UserDataHandler.prototype.NODE_IMPORTED = 2;
+core.UserDataHandler.prototype.NODE_DELETED = 3;
+core.UserDataHandler.prototype.NODE_RENAMED = 4;
+core.UserDataHandler.prototype.NODE_ADOPTED = 5;
+core.UserDataHandler.prototype.handle = function(operation, key, data, src, dst) {};
+
+// Introduced in DOM Level 3:
+core.DOMError = function(severity, message, type, relatedException, relatedData, location) {
+ this._severity = severity;
+ this._message = message;
+ this._type = type;
+ this._relatedException = relatedException;
+ this._relatedData = relatedData;
+ this._location = location;
+};
+core.DOMError.prototype = {};
+core.DOMError.prototype.SEVERITY_WARNING = 1;
+core.DOMError.prototype.SEVERITY_ERROR = 2;
+core.DOMError.prototype.SEVERITY_FATAL_ERROR = 3;
+defineGetter(core.DOMError.prototype, 'severity', function() {
+ return this._severity;
+});
+defineGetter(core.DOMError.prototype, 'message', function() {
+ return this._message;
+});
+defineGetter(core.DOMError.prototype, 'type', function() {
+ return this._type;
+});
+defineGetter(core.DOMError.prototype, 'relatedException', function() {
+ return this._relatedException;
+});
+defineGetter(core.DOMError.prototype, 'relatedData', function() {
+ return this._relatedData;
+});
+defineGetter(core.DOMError.prototype, 'location', function() {
+ return this._location;
+});
+
+/*
+ // Introduced in DOM Level 3:
+ interface DOMErrorHandler {
+ boolean handleError(in DOMError error);
+ };
+
+ // Introduced in DOM Level 3:
+ interface DOMLocator {
+ readonly attribute long lineNumber;
+ readonly attribute long columnNumber;
+ readonly attribute long byteOffset;
+ readonly attribute long utf16Offset;
+ readonly attribute Node relatedNode;
+ readonly attribute DOMString uri;
+ };
+*/
+
+// Introduced in DOM Level 3:
+core.DOMConfiguration = function(){
+ var possibleParameterNames = {
+ 'canonical-form': [false, true], // extra rules for true
+ 'cdata-sections': [true, false],
+ 'check-character-normalization': [false, true],
+ 'comments': [true, false],
+ 'datatype-normalization': [false, true],
+ 'element-content-whitespace': [true, false],
+ 'entities': [true, false],
+ // 'error-handler': [],
+ 'infoset': [undefined, true, false], // extra rules for true
+ 'namespaces': [true, false],
+ 'namespace-declarations': [true, false], // only checked if namespaces is true
+ 'normalize-characters': [false, true],
+ // 'schema-location': [],
+ // 'schema-type': [],
+ 'split-cdata-sections': [true, false],
+ 'validate': [false, true],
+ 'validate-if-schema': [false, true],
+ 'well-formed': [true, false]
+ }
+};
+
+core.DOMConfiguration.prototype = {
+ setParameter: function(name, value) {},
+ getParameter: function(name) {},
+ canSetParameter: function(name, value) {},
+ parameterNames: function() {}
+};
+
+//core.Document.prototype._domConfig = new core.DOMConfiguration();
+defineGetter(core.Document.prototype, 'domConfig', function() {
+ return this._domConfig || new core.DOMConfiguration();;
+});
+
+// Introduced in DOM Level 3:
+core.DOMStringList = function() {};
+
+core.DOMStringList.prototype = {
+ item: function() {},
+ length: function() {},
+ contains: function() {}
+};
+
+
+/*
+ interface CDATASection : Text {
+ };
+
+ interface DocumentType : Node {
+ readonly attribute DOMString name;
+ readonly attribute NamedNodeMap entities;
+ readonly attribute NamedNodeMap notations;
+ // Introduced in DOM Level 2:
+ readonly attribute DOMString publicId;
+ // Introduced in DOM Level 2:
+ readonly attribute DOMString systemId;
+ // Introduced in DOM Level 2:
+ readonly attribute DOMString internalSubset;
+ };
+
+ interface Notation : Node {
+ readonly attribute DOMString publicId;
+ readonly attribute DOMString systemId;
+ };
+
+ interface Entity : Node {
+ readonly attribute DOMString publicId;
+ readonly attribute DOMString systemId;
+ readonly attribute DOMString notationName;
+ // Introduced in DOM Level 3:
+ readonly attribute DOMString inputEncoding;
+ // Introduced in DOM Level 3:
+ readonly attribute DOMString xmlEncoding;
+ // Introduced in DOM Level 3:
+ readonly attribute DOMString xmlVersion;
+ };
+
+ interface EntityReference : Node {
+ };
+
+ interface ProcessingInstruction : Node {
+ readonly attribute DOMString target;
+ attribute DOMString data;
+ // raises(DOMException) on setting
+
+ };
+
+ interface DocumentFragment : Node {
+ };
+
+ interface Document : Node {
+ // Modified in DOM Level 3:
+ readonly attribute DocumentType doctype;
+ readonly attribute DOMImplementation implementation;
+ readonly attribute Element documentElement;
+ Element createElement(in DOMString tagName)
+ raises(DOMException);
+ DocumentFragment createDocumentFragment();
+ Text createTextNode(in DOMString data);
+ Comment createComment(in DOMString data);
+ CDATASection createCDATASection(in DOMString data)
+ raises(DOMException);
+ ProcessingInstruction createProcessingInstruction(in DOMString target,
+ in DOMString data)
+ raises(DOMException);
+ Attr createAttribute(in DOMString name)
+ raises(DOMException);
+ EntityReference createEntityReference(in DOMString name)
+ raises(DOMException);
+ NodeList getElementsByTagName(in DOMString tagname);
+ // Introduced in DOM Level 2:
+ Node importNode(in Node importedNode,
+ in boolean deep)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ Element createElementNS(in DOMString namespaceURI,
+ in DOMString qualifiedName)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ Attr createAttributeNS(in DOMString namespaceURI,
+ in DOMString qualifiedName)
+ raises(DOMException);
+ // Introduced in DOM Level 2:
+ NodeList getElementsByTagNameNS(in DOMString namespaceURI,
+ in DOMString localName);
+ // Introduced in DOM Level 2:
+ Element getElementById(in DOMString elementId);
+*/
+/*
+ // Introduced in DOM Level 3:
+ readonly attribute DOMString xmlEncoding;
+ // Introduced in DOM Level 3:
+ attribute boolean xmlStandalone;
+ // raises(DOMException) on setting
+
+ // Introduced in DOM Level 3:
+ attribute DOMString xmlVersion;
+ // raises(DOMException) on setting
+
+ // Introduced in DOM Level 3:
+ attribute boolean strictErrorChecking;
+ // Introduced in DOM Level 3:
+ attribute DOMString documentURI;
+ // Introduced in DOM Level 3:
+ Node adoptNode(in Node source)
+ raises(DOMException);
+ // Introduced in DOM Level 3:
+ readonly attribute DOMConfiguration domConfig;
+ // Introduced in DOM Level 3:
+ void normalizeDocument();
+ // Introduced in DOM Level 3:
+ Node renameNode(in Node n,
+ in DOMString namespaceURI,
+ in DOMString qualifiedName)
+ raises(DOMException);
+ };
+};
+
+#endif // _DOM_IDL_
+*/
diff --git a/node_modules/jsdom/lib/jsdom/level3/ls.js b/node_modules/jsdom/lib/jsdom/level3/ls.js
new file mode 100644
index 0000000..5cc44f4
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level3/ls.js
@@ -0,0 +1,211 @@
+// w3c Load/Save functionality: http://www.w3.org/TR/2004/REC-DOM-Level-3-LS-20040407/
+
+var core = require('../level1/core');
+var createFrom = require('../utils').createFrom;
+
+var ls = {};
+
+// TODO: what is this?
+//typedef dom::DOMConfiguration DOMConfiguration;
+
+ls.LSException = function LSException(code) {
+ this.code = code;
+};
+
+ls.LSException.prototype = {
+ // LSExceptionCode
+ PARSE_ERR : 81,
+ SERIALIZE_ERR : 82
+};
+
+ls.DOMImplementationLS = function DOMImplementationLS() {
+
+};
+
+var DOMImplementationExtension = {
+
+ // DOMImplementationLSMode
+ MODE_SYNCHRONOUS : 1,
+ MODE_ASYNCHRONOUS : 2,
+
+ // raises(dom::DOMException);
+ createLSParser : function(/* int */ mode, /* string */ schemaType) {
+ return new ls.LSParser(mode, schemaType);
+ },
+
+ createLSSerializer : function() {
+ return new ls.LSSerializer();
+ },
+
+ createLSInput : function() {
+ return new ls.LSInput();
+ },
+
+ createLSOutput : function() {
+ return new ls.LSOutput();
+ }
+};
+
+Object.keys(DOMImplementationExtension).forEach(function(k, v) {
+ core.DOMImplementation.prototype[k] = DOMImplementationExtension[k];
+});
+
+ls.DOMImplementationLS.prototype = DOMImplementationExtension;
+
+core.Document.getFeature = function() {
+ return DOMImplementationExtension;
+};
+
+ls.LSParser = function LSParser() {
+ this._domConfig = new core.DOMConfiguration();
+};
+ls.LSParser.prototype = {
+ get domConfig() { return this._domConfig; },
+ get filter() { return this._filter || null; },
+ set filter(value) { this._filter = value; },
+ get async() { return this._async; },
+ get busy() { return this._busy; },
+
+ // raises(dom::DOMException, LSException);
+ parse : function (/* LSInput */ input) {
+ var doc = new core.Document();
+ doc._inputEncoding = 'UTF-16';
+ return doc;
+ },
+
+ // raises(dom::DOMException, LSException);
+ parseURI : function(/* string */ uri) {
+ return new core.Document();
+ },
+
+ // ACTION_TYPES
+ ACTION_APPEND_AS_CHILDREN : 1,
+ ACTION_REPLACE_CHILDREN : 2,
+ ACTION_INSERT_BEFORE : 3,
+ ACTION_INSERT_AFTER : 4,
+ ACTION_REPLACE : 5,
+
+ // @returns Node
+ // @raises DOMException, LSException
+ parseWithContext : function(/* LSInput */ input, /* Node */ contextArg, /* int */ action) {
+ return new core.Node();
+ },
+
+ abort : function() {
+ // TODO: implement
+ }
+};
+
+ls.LSInput = function LSInput() {};
+ls.LSInput.prototype = {
+ get characterStream() { return this._characterStream || null; },
+ set characterStream(value) { this._characterStream = value; },
+ get byteStream() { return this._byteStream || null; },
+ set byteStream(value) { this._byteStream = value; },
+ get stringData() { return this._stringData || null; },
+ set stringData(value) { this._stringData = value; },
+ get systemId() { return this._systemId || null; },
+ set systemId(value) { this._systemId = value; },
+ get publicId() { return this._publicId || null; },
+ set publicId(value) { this._publicId = value; },
+ get baseURI() { return this._baseURI || null; },
+ set baseURI(value) { this._baseURI = value; },
+ get encoding() { return this._encoding || null; },
+ set encoding(value) { this._encoding = value; },
+ get certifiedText() { return this._certifiedText || null; },
+ set certifiedText(value) { this._certifiedText = value; },
+};
+
+ls.LSResourceResolver = function LSResourceResolver() {};
+
+// @returns LSInput
+ls.LSResourceResolver.prototype.resolveResource = function(type, namespaceURI, publicId, systemId, baseURI) {
+ return new ls.LSInput();
+};
+
+ls.LSParserFilter = function LSParserFilter() {};
+ls.LSParserFilter.prototype = {
+
+ // Constants returned by startElement and acceptNode
+ FILTER_ACCEPT : 1,
+ FILTER_REJECT : 2,
+ FILTER_SKIP : 3,
+ FILTER_INTERRUPT : 4,
+
+ get whatToShow() { return this._whatToShow; },
+
+ // @returns int
+ startElement : function(/* Element */ elementArg) {
+ return 0;
+ },
+
+ // @returns int
+ acceptNode : function(/* Node */ nodeArg) {
+ return nodeArg;
+ }
+};
+
+ls.LSSerializer = function LSSerializer() {
+ this._domConfig = new core.DOMConfiguration();
+};
+ls.LSSerializer.prototype = {
+ get domConfig() { return this._domConfig; },
+ get newLine() { return this._newLine || null; },
+ set newLine(value) { this._newLine = value; },
+ get filter() { return this._filter || null; },
+ set filter(value) { this._filter = value; },
+
+ // @returns boolean
+ // @raises LSException
+ write : function(/* Node */ nodeArg, /* LSOutput */ destination) {
+ return true;
+ },
+
+ // @returns boolean
+ // @raises LSException
+ writeToURI : function(/* Node */ nodeArg, /* string */ uri) {
+ return true;
+ },
+
+ // @returns string
+ // @raises DOMException, LSException
+ writeToString : function(/* Node */ nodeArg) {
+ return "";
+ }
+};
+
+ls.LSOutput = function LSOutput() {};
+ls.LSOutput.prototype = {
+ get characterStream() { return this._characterStream || null; },
+ set characterStream(value) { this._characterStream = value; },
+ get byteStream() { return this._byteStream || null; },
+ set byteStream(value) { this._byteStream = value; },
+ get systemId() { return this._systemId || null; },
+ set systemId(value) { this._systemId = value; },
+ get encoding() { return this._encoding || null; },
+ set encoding(value) { this._encoding = value; },
+};
+
+ls.LSProgressEvent = function LSProgressEvent() {};
+ls.LSProgressEvent.prototype = createFrom(core.Event, {
+ constructor: ls.LSProgressEvent,
+ get input() { return this._input; },
+ get position() { return this._position; },
+ get totalSize() { return this._totalSize; },
+});
+
+ls.LSLoadEvent = function LSLoadEvent() {};
+ls.LSLoadEvent.prototype = createFrom(core.Event, {
+ get newDocument() { return this._newDocument; },
+ get input() { return this._input; },
+});
+
+
+// TODO: do traversal
+ls.LSSerializerFilter = function LSSerializerFilter() {};
+ls.LSSerializerFilter.prototype = {
+ get whatToShow() { return this._whatToShow; },
+};
+
+// ls.LSSerializerFilter.prototype.__proto__ = level2.traversal.NodeFiler;
+
diff --git a/node_modules/jsdom/lib/jsdom/level3/xpath.js b/node_modules/jsdom/lib/jsdom/level3/xpath.js
new file mode 100644
index 0000000..98e3a7d
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/level3/xpath.js
@@ -0,0 +1,1866 @@
+/** Here is yet another implementation of XPath 1.0 in Javascript.
+ *
+ * My goal was to make it relatively compact, but as I fixed all the axis bugs
+ * the axes became more and more complicated. :-(.
+ *
+ * I have not implemented namespaces or case-sensitive axes for XML yet.
+ *
+ * How to test it in Chrome: You can make a Chrome extension that replaces
+ * the WebKit XPath parser with this one. But it takes a bit of effort to
+ * get around isolated world and same-origin restrictions:
+ * manifest.json:
+ {
+ "name": "XPathTest",
+ "version": "0.1",
+ "content_scripts": [{
+ "matches": ["http://localhost/*"], // or wildcard host
+ "js": ["xpath.js", "injection.js"],
+ "all_frames": true, "run_at": "document_start"
+ }]
+ }
+ * injection.js:
+ // goal: give my xpath object to the website's JS context.
+ var script = document.createElement('script');
+ script.textContent =
+ "document.addEventListener('xpathextend', function(e) {\n" +
+ " console.log('extending document with xpath...');\n" +
+ " e.detail(window);" +
+ "});";
+ document.documentElement.appendChild(script);
+ document.documentElement.removeChild(script);
+ var evt = document.createEvent('CustomEvent');
+ evt.initCustomEvent('xpathextend', true, true, this.xpath.extend);
+ document.dispatchEvent(evt);
+ */
+(function() {
+ var xpath;
+ var core;
+ if ('function' === typeof require) {
+ xpath = exports; // the tests go through this
+ core = require("../level1/core");
+ } else {
+ xpath = {};
+ core = this;
+ }
+
+
+ /***************************************************************************
+ * Tokenization *
+ ***************************************************************************/
+ /**
+ * The XPath lexer is basically a single regular expression, along with
+ * some helper functions to pop different types.
+ */
+ var Stream = xpath.Stream = function Stream(str) {
+ this.original = this.str = str;
+ this.peeked = null;
+ // TODO: not really needed, but supposedly tokenizer also disambiguates
+ // a * b vs. node test *
+ this.prev = null; // for debugging
+ this.prevprev = null;
+ }
+ Stream.prototype = {
+ peek: function() {
+ if (this.peeked) return this.peeked;
+ var m = this.re.exec(this.str);
+ if (!m) return null;
+ this.str = this.str.substr(m[0].length);
+ return this.peeked = m[1];
+ },
+ /** Peek 2 tokens ahead. */
+ peek2: function() {
+ this.peek(); // make sure this.peeked is set
+ var m = this.re.exec(this.str);
+ if (!m) return null;
+ return m[1];
+ },
+ pop: function() {
+ var r = this.peek();
+ this.peeked = null;
+ this.prevprev = this.prev;
+ this.prev = r;
+ return r;
+ },
+ trypop: function(tokens) {
+ var tok = this.peek();
+ if (tok === tokens) return this.pop();
+ if (Array.isArray(tokens)) {
+ for (var i = 0; i < tokens.length; ++i) {
+ var t = tokens[i];
+ if (t == tok) return this.pop();;
+ }
+ }
+ },
+ trypopfuncname: function() {
+ var tok = this.peek();
+ if (!this.isQnameRe.test(tok))
+ return null;
+ switch (tok) {
+ case 'comment': case 'text': case 'processing-instruction': case 'node':
+ return null;
+ }
+ if ('(' != this.peek2()) return null;
+ return this.pop();
+ },
+ trypopaxisname: function() {
+ var tok = this.peek();
+ switch (tok) {
+ case 'ancestor': case 'ancestor-or-self': case 'attribute':
+ case 'child': case 'descendant': case 'descendant-or-self':
+ case 'following': case 'following-sibling': case 'namespace':
+ case 'parent': case 'preceding': case 'preceding-sibling': case 'self':
+ if ('::' == this.peek2()) return this.pop();
+ }
+ return null;
+ },
+ trypopnametest: function() {
+ var tok = this.peek();
+ if ('*' === tok || this.startsWithNcNameRe.test(tok)) return this.pop();
+ return null;
+ },
+ trypopliteral: function() {
+ var tok = this.peek();
+ if (null == tok) return null;
+ var first = tok.charAt(0);
+ var last = tok.charAt(tok.length - 1);
+ if ('"' === first && '"' === last ||
+ "'" === first && "'" === last) {
+ this.pop();
+ return tok.substr(1, tok.length - 2);
+ }
+ },
+ trypopnumber: function() {
+ var tok = this.peek();
+ if (this.isNumberRe.test(tok)) return parseFloat(this.pop());
+ else return null;
+ },
+ trypopvarref: function() {
+ var tok = this.peek();
+ if (null == tok) return null;
+ if ('$' === tok.charAt(0)) return this.pop().substr(1);
+ else return null;
+ },
+ position: function() {
+ return this.original.length - this.str.length;
+ }
+ };
+ (function() {
+ // http://www.w3.org/TR/REC-xml-names/#NT-NCName
+ var nameStartCharsExceptColon =
+ 'A-Z_a-z\xc0-\xd6\xd8-\xf6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF' +
+ '\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF' +
+ '\uFDF0-\uFFFD'; // JS doesn't support [#x10000-#xEFFFF]
+ var nameCharExceptColon = nameStartCharsExceptColon +
+ '\\-\\.0-9\xb7\u0300-\u036F\u203F-\u2040';
+ var ncNameChars = '[' + nameStartCharsExceptColon +
+ '][' + nameCharExceptColon + ']*'
+ // http://www.w3.org/TR/REC-xml-names/#NT-QName
+ var qNameChars = ncNameChars + '(?::' + ncNameChars + ')?';
+ var otherChars = '\\.\\.|[\\(\\)\\[\\].@,]|::'; // .. must come before [.]
+ var operatorChars =
+ 'and|or|mod|div|' +
+ '//|!=|<=|>=|[*/|+\\-=<>]'; // //, !=, <=, >= before individual ones.
+ var literal = '"[^"]*"|' + "'[^']*'";
+ var numberChars = '[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+';
+ var variableReference = '\\$' + qNameChars;
+ var nameTestChars = '\\*|' + ncNameChars + ':\\*|' + qNameChars;
+ var optionalSpace = '[ \t\r\n]*'; // stricter than regexp \s.
+ var nodeType = 'comment|text|processing-instruction|node';
+ var re = new RegExp(
+ // numberChars before otherChars so that leading-decimal doesn't become .
+ '^' + optionalSpace + '(' + numberChars + '|' + otherChars + '|' +
+ nameTestChars + '|' + operatorChars + '|' + literal + '|' +
+ variableReference + ')'
+ // operatorName | nodeType | functionName | axisName are lumped into
+ // qName for now; we'll check them on pop.
+ );
+ Stream.prototype.re = re;
+ Stream.prototype.startsWithNcNameRe = new RegExp('^' + ncNameChars);
+ Stream.prototype.isQnameRe = new RegExp('^' + qNameChars + '$');
+ Stream.prototype.isNumberRe = new RegExp('^' + numberChars + '$');
+ })();
+
+ /***************************************************************************
+ * Parsing *
+ ***************************************************************************/
+ var parse = xpath.parse = function parse(stream, a) {
+ var r = orExpr(stream,a);
+ var x, unparsed = [];
+ while (x = stream.pop()) {
+ unparsed.push(x);
+ }
+ if (unparsed.length)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Unparsed tokens: ' + unparsed.join(' '));
+ return r;
+ }
+
+ /**
+ * binaryL ::= subExpr
+ * | binaryL op subExpr
+ * so a op b op c becomes ((a op b) op c)
+ */
+ function binaryL(subExpr, stream, a, ops) {
+ var lhs = subExpr(stream, a);
+ if (lhs == null) return null;
+ var op;
+ while (op = stream.trypop(ops)) {
+ var rhs = subExpr(stream, a);
+ if (rhs == null)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected something after ' + op);
+ lhs = a.node(op, lhs, rhs);
+ }
+ return lhs;
+ }
+ /**
+ * Too bad this is never used. If they made a ** operator (raise to power),
+ ( we would use it.
+ * binaryR ::= subExpr
+ * | subExpr op binaryR
+ * so a op b op c becomes (a op (b op c))
+ */
+ function binaryR(subExpr, stream, a, ops) {
+ var lhs = subExpr(stream, a);
+ if (lhs == null) return null;
+ var op = stream.trypop(ops);
+ if (op) {
+ var rhs = binaryR(stream, a);
+ if (rhs == null)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected something after ' + op);
+ return a.node(op, lhs, rhs);
+ } else {
+ return lhs;// TODO
+ }
+ }
+ /** [1] LocationPath::= RelativeLocationPath | AbsoluteLocationPath
+ * e.g. a, a/b, //a/b
+ */
+ function locationPath(stream, a) {
+ return absoluteLocationPath(stream, a) ||
+ relativeLocationPath(null, stream, a);
+ }
+ /** [2] AbsoluteLocationPath::= '/' RelativeLocationPath? | AbbreviatedAbsoluteLocationPath
+ * [10] AbbreviatedAbsoluteLocationPath::= '//' RelativeLocationPath
+ */
+ function absoluteLocationPath(stream, a) {
+ var op = stream.peek();
+ if ('/' === op || '//' === op) {
+ var lhs = a.node('Root');
+ return relativeLocationPath(lhs, stream, a, true);
+ } else {
+ return null;
+ }
+ }
+ /** [3] RelativeLocationPath::= Step | RelativeLocationPath '/' Step |
+ * | AbbreviatedRelativeLocationPath
+ * [11] AbbreviatedRelativeLocationPath::= RelativeLocationPath '//' Step
+ * e.g. p/a, etc.
+ */
+ function relativeLocationPath(lhs, stream, a, isOnlyRootOk) {
+ if (null == lhs) {
+ lhs = step(stream, a);
+ if (null == lhs) return lhs;
+ }
+ var op;
+ while (op = stream.trypop(['/', '//'])) {
+ if ('//' === op) {
+ lhs = a.node('/', lhs,
+ a.node('Axis', 'descendant-or-self', 'node', undefined));
+ }
+ var rhs = step(stream, a);
+ if (null == rhs && '/' === op && isOnlyRootOk) return lhs;
+ else isOnlyRootOk = false;
+ if (null == rhs)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected step after ' + op);
+ lhs = a.node('/', lhs, rhs);
+ }
+ return lhs;
+ }
+ /** [4] Step::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
+ * [12] AbbreviatedStep::= '.' | '..'
+ * e.g. @href, self::p, p, a[@href], ., ..
+ */
+ function step(stream, a) {
+ var abbrStep = stream.trypop(['.', '..']);
+ if ('.' === abbrStep) // A location step of . is short for self::node().
+ return a.node('Axis', 'self', 'node');
+ if ('..' === abbrStep) // A location step of .. is short for parent::node()
+ return a.node('Axis', 'parent', 'node');
+
+ var axis = axisSpecifier(stream, a);
+ var nodeType = nodeTypeTest(stream, a);
+ var nodeName;
+ if (null == nodeType) nodeName = nodeNameTest(stream, a);
+ if (null == axis && null == nodeType && null == nodeName) return null;
+ if (null == nodeType && null == nodeName)
+ throw new XPathException(
+ XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected nodeTest after axisSpecifier ' + axis);
+ if (null == axis) axis = 'child';
+ if (null == nodeType) {
+ // When there's only a node name, then the node type is forced to be the
+ // principal node type of the axis.
+ // see http://www.w3.org/TR/xpath/#dt-principal-node-type
+ if ('attribute' === axis) nodeType = 'attribute';
+ else if ('namespace' === axis) nodeType = 'namespace';
+ else nodeType = 'element';
+ }
+ var lhs = a.node('Axis', axis, nodeType, nodeName);
+ var pred;
+ while (null != (pred = predicate(lhs, stream, a))) {
+ lhs = pred;
+ }
+ return lhs;
+ }
+ /** [5] AxisSpecifier::= AxisName '::' | AbbreviatedAxisSpecifier
+ * [6] AxisName::= 'ancestor' | 'ancestor-or-self' | 'attribute' | 'child'
+ * | 'descendant' | 'descendant-or-self' | 'following'
+ * | 'following-sibling' | 'namespace' | 'parent' |
+ * | 'preceding' | 'preceding-sibling' | 'self'
+ * [13] AbbreviatedAxisSpecifier::= '@'?
+ */
+ function axisSpecifier(stream, a) {
+ var attr = stream.trypop('@');
+ if (null != attr) return 'attribute';
+ var axisName = stream.trypopaxisname();
+ if (null != axisName) {
+ var coloncolon = stream.trypop('::');
+ if (null == coloncolon)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Should not happen. Should be ::.');
+ return axisName;
+ }
+ }
+ /** [7] NodeTest::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')'
+ * [38] NodeType::= 'comment' | 'text' | 'processing-instruction' | 'node'
+ * I've split nodeTypeTest from nodeNameTest for convenience.
+ */
+ function nodeTypeTest(stream, a) {
+ if ('(' !== stream.peek2()) {
+ return null;
+ }
+ var type = stream.trypop(['comment', 'text', 'processing-instruction', 'node']);
+ if (null != type) {
+ if (null == stream.trypop('('))
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Should not happen.');
+ var param = undefined;
+ if (type == 'processing-instruction') {
+ param = stream.trypopliteral();
+ }
+ if (null == stream.trypop(')'))
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected close parens.');
+ return type
+ }
+ }
+ function nodeNameTest(stream, a) {
+ var name = stream.trypopnametest();
+ if (name != null) return name;
+ else return null;
+ }
+ /** [8] Predicate::= '[' PredicateExpr ']'
+ * [9] PredicateExpr::= Expr
+ */
+ function predicate(lhs, stream, a) {
+ if (null == stream.trypop('[')) return null;
+ var expr = orExpr(stream, a);
+ if (null == expr)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected expression after [');
+ if (null == stream.trypop(']'))
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected ] after expression.');
+ return a.node('Predicate', lhs, expr);
+ }
+ /** [14] Expr::= OrExpr
+ */
+ /** [15] PrimaryExpr::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
+ * e.g. $x, (3+4), "hi", 32, f(x)
+ */
+ function primaryExpr(stream, a) {
+ var x = stream.trypopliteral();
+ if (null == x)
+ x = stream.trypopnumber();
+ if (null != x) {
+ return x;
+ }
+ var varRef = stream.trypopvarref();
+ if (null != varRef) return a.node('VariableReference', varRef);
+ var funCall = functionCall(stream, a);
+ if (null != funCall) {
+ return funCall;
+ }
+ if (stream.trypop('(')) {
+ var e = orExpr(stream, a);
+ if (null == e)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected expression after (.');
+ if (null == stream.trypop(')'))
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected ) after expression.');
+ return e;
+ }
+ return null;
+ }
+ /** [16] FunctionCall::= FunctionName '(' ( Argument ( ',' Argument )* )? ')'
+ * [17] Argument::= Expr
+ */
+ function functionCall(stream, a) {
+ var name = stream.trypopfuncname(stream, a);
+ if (null == name) return null;
+ if (null == stream.trypop('('))
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected ( ) after function name.');
+ var params = [];
+ var first = true;
+ while (null == stream.trypop(')')) {
+ if (!first && null == stream.trypop(','))
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected , between arguments of the function.');
+ first = false;
+ var param = orExpr(stream, a);
+ if (param == null)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected expression as argument of function.');
+ params.push(param);
+ }
+ return a.node('FunctionCall', name, params);
+ }
+
+ /** [18] UnionExpr::= PathExpr | UnionExpr '|' PathExpr
+ */
+ function unionExpr(stream, a) { return binaryL(pathExpr, stream, a, '|'); }
+ /** [19] PathExpr ::= LocationPath
+ * | FilterExpr
+ * | FilterExpr '/' RelativeLocationPath
+ * | FilterExpr '//' RelativeLocationPath
+ * Unlike most other nodes, this one always generates a node because
+ * at this point all reverse nodesets must turn into a forward nodeset
+ */
+ function pathExpr(stream, a) {
+ // We have to do FilterExpr before LocationPath because otherwise
+ // LocationPath will eat up the name from a function call.
+ var filter = filterExpr(stream, a);
+ if (null == filter) {
+ var loc = locationPath(stream, a);
+ if (null == loc) {
+ throw new Error
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': The expression shouldn\'t be empty...');
+ }
+ return a.node('PathExpr', loc);
+ }
+ var rel = relativeLocationPath(filter, stream, a, false);
+ if (filter === rel) return rel;
+ else return a.node('PathExpr', rel);
+ }
+ /** [20] FilterExpr::= PrimaryExpr | FilterExpr Predicate
+ * aka. FilterExpr ::= PrimaryExpr Predicate*
+ */
+ function filterExpr(stream, a) {
+ var primary = primaryExpr(stream, a);
+ if (primary == null) return null;
+ var pred, lhs = primary;
+ while (null != (pred = predicate(lhs, stream, a))) {
+ lhs = pred;
+ }
+ return lhs;
+ }
+
+ /** [21] OrExpr::= AndExpr | OrExpr 'or' AndExpr
+ */
+ function orExpr(stream, a) {
+ var orig = (stream.peeked || '') + stream.str
+ var r = binaryL(andExpr, stream, a, 'or');
+ var now = (stream.peeked || '') + stream.str;
+ return r;
+ }
+ /** [22] AndExpr::= EqualityExpr | AndExpr 'and' EqualityExpr
+ */
+ function andExpr(stream, a) { return binaryL(equalityExpr, stream, a, 'and'); }
+ /** [23] EqualityExpr::= RelationalExpr | EqualityExpr '=' RelationalExpr
+ * | EqualityExpr '!=' RelationalExpr
+ */
+ function equalityExpr(stream, a) { return binaryL(relationalExpr, stream, a, ['=','!=']); }
+ /** [24] RelationalExpr::= AdditiveExpr | RelationalExpr '<' AdditiveExpr
+ * | RelationalExpr '>' AdditiveExpr
+ * | RelationalExpr '<=' AdditiveExpr
+ * | RelationalExpr '>=' AdditiveExpr
+ */
+ function relationalExpr(stream, a) { return binaryL(additiveExpr, stream, a, ['<','>','<=','>=']); }
+ /** [25] AdditiveExpr::= MultiplicativeExpr
+ * | AdditiveExpr '+' MultiplicativeExpr
+ * | AdditiveExpr '-' MultiplicativeExpr
+ */
+ function additiveExpr(stream, a) { return binaryL(multiplicativeExpr, stream, a, ['+','-']); }
+ /** [26] MultiplicativeExpr::= UnaryExpr
+ * | MultiplicativeExpr MultiplyOperator UnaryExpr
+ * | MultiplicativeExpr 'div' UnaryExpr
+ * | MultiplicativeExpr 'mod' UnaryExpr
+ */
+ function multiplicativeExpr(stream, a) { return binaryL(unaryExpr, stream, a, ['*','div','mod']); }
+ /** [27] UnaryExpr::= UnionExpr | '-' UnaryExpr
+ */
+ function unaryExpr(stream, a) {
+ if (stream.trypop('-')) {
+ var e = unaryExpr(stream, a);
+ if (null == e)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Expected unary expression after -');
+ return a.node('UnaryMinus', e);
+ }
+ else return unionExpr(stream, a);
+ }
+ var astFactory = {
+ node: function() {return Array.prototype.slice.call(arguments);}
+ };
+
+
+ /***************************************************************************
+ * Optimizations (TODO) *
+ ***************************************************************************/
+ /**
+ * Some things I've been considering:
+ * 1) a//b becomes a/descendant::b if there's no predicate that uses
+ * position() or last()
+ * 2) axis[pred]: when pred doesn't use position, evaluate it just once per
+ * node in the node-set rather than once per (node, position, last).
+ * For more optimizations, look up Gecko's optimizer:
+ * http://mxr.mozilla.org/mozilla-central/source/content/xslt/src/xpath/txXPathOptimizer.cpp
+ */
+ // TODO
+ function optimize(ast) {
+ }
+
+ /***************************************************************************
+ * Evaluation: axes *
+ ***************************************************************************/
+
+ /**
+ * Data types: For string, number, boolean, we just use Javascript types.
+ * Node-sets have the form
+ * {nodes: [node, ...]}
+ * or {nodes: [node, ...], pos: [[1], [2], ...], lasts: [[1], [2], ...]}
+ *
+ * Most of the time, only the node is used and the position information is
+ * discarded. But if you use a predicate, we need to try every value of
+ * position and last in case the predicate calls position() or last().
+ */
+
+ /**
+ * The NodeMultiSet is a helper class to help generate
+ * {nodes:[], pos:[], lasts:[]} structures. It is useful for the
+ * descendant, descendant-or-self, following-sibling, and
+ * preceding-sibling axes for which we can use a stack to organize things.
+ */
+ function NodeMultiSet(isReverseAxis) {
+ this.nodes = [];
+ this.pos = [];
+ this.lasts = [];
+ this.nextPos = [];
+ this.seriesIndexes = []; // index within nodes that each series begins.
+ this.isReverseAxis = isReverseAxis;
+ this._pushToNodes = isReverseAxis ? Array.prototype.unshift : Array.prototype.push;
+ }
+ NodeMultiSet.prototype = {
+ pushSeries: function pushSeries() {
+ this.nextPos.push(1);
+ this.seriesIndexes.push(this.nodes.length);
+ },
+ popSeries: function popSeries() {
+ console.assert(0 < this.nextPos.length, this.nextPos);
+ var last = this.nextPos.pop() - 1,
+ indexInPos = this.nextPos.length,
+ seriesBeginIndex = this.seriesIndexes.pop(),
+ seriesEndIndex = this.nodes.length;
+ for (var i = seriesBeginIndex; i < seriesEndIndex; ++i) {
+ console.assert(indexInPos < this.lasts[i].length);
+ console.assert(undefined === this.lasts[i][indexInPos]);
+ this.lasts[i][indexInPos] = last;
+ }
+ },
+ finalize: function() {
+ if (null == this.nextPos) return this;
+ console.assert(0 === this.nextPos.length);
+ for (var i = 0; i < this.lasts.length; ++i) {
+ for (var j = 0; j < this.lasts[i].length; ++j) {
+ console.assert(null != this.lasts[i][j], i + ',' + j + ':' + JSON.stringify(this.lasts));
+ }
+ }
+ this.pushSeries = this.popSeries = this.addNode = function() {
+ throw new Error('Already finalized.');
+ };
+ return this;
+ },
+ addNode: function addNode(node) {
+ console.assert(node);
+ this._pushToNodes.call(this.nodes, node)
+ this._pushToNodes.call(this.pos, this.nextPos.slice());
+ this._pushToNodes.call(this.lasts, new Array(this.nextPos.length));
+ for (var i = 0; i < this.nextPos.length; ++i) this.nextPos[i]++;
+ },
+ simplify: function() {
+ this.finalize();
+ return {nodes:this.nodes, pos:this.pos, lasts:this.lasts};
+ }
+ };
+ function eachContext(nodeMultiSet) {
+ var r = [];
+ for (var i = 0; i < nodeMultiSet.nodes.length; i++) {
+ var node = nodeMultiSet.nodes[i];
+ if (!nodeMultiSet.pos) {
+ r.push({nodes:[node], pos: [[i + 1]], lasts: [[nodeMultiSet.nodes.length]]});
+ } else {
+ for (var j = 0; j < nodeMultiSet.pos[i].length; ++j) {
+ r.push({nodes:[node], pos: [[nodeMultiSet.pos[i][j]]], lasts: [[nodeMultiSet.lasts[i][j]]]});
+ }
+ }
+ }
+ return r;
+ }
+ /** Matcher used in the axes.
+ */
+ function NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase) {
+ this.nodeTypeNum = nodeTypeNum;
+ this.nodeName = nodeName;
+ this.shouldLowerCase = shouldLowerCase;
+ this.nodeNameTest =
+ null == nodeName ? this._alwaysTrue :
+ shouldLowerCase ? this._nodeNameLowerCaseEquals :
+ this._nodeNameEquals;
+ }
+ NodeMatcher.prototype = {
+ matches: function matches(node) {
+ return (0 === this.nodeTypeNum || node.nodeType === this.nodeTypeNum) &&
+ this.nodeNameTest(node.nodeName);
+ },
+ _alwaysTrue: function(name) {return true;},
+ _nodeNameEquals: function _nodeNameEquals(name) {
+ return this.nodeName === name;
+ },
+ _nodeNameLowerCaseEquals: function _nodeNameLowerCaseEquals(name) {
+ return this.nodeName === name.toLowerCase();
+ }
+ };
+
+ function followingSiblingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, shift, peek, followingNode, andSelf, isReverseAxis) {
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var nodeMultiSet = new NodeMultiSet(isReverseAxis);
+ while (0 < nodeList.length) { // can be if for following, preceding
+ var node = shift.call(nodeList);
+ console.assert(node != null);
+ node = followingNode(node);
+ nodeMultiSet.pushSeries();
+ var numPushed = 1;
+ while (null != node) {
+ if (! andSelf && matcher.matches(node))
+ nodeMultiSet.addNode(node);
+ if (node === peek.call(nodeList)) {
+ shift.call(nodeList);
+ nodeMultiSet.pushSeries();
+ numPushed++;
+ }
+ if (andSelf && matcher.matches(node))
+ nodeMultiSet.addNode(node);
+ node = followingNode(node);
+ }
+ while (0 < numPushed--)
+ nodeMultiSet.popSeries();
+ }
+ return nodeMultiSet;
+ }
+
+ /** Returns the next non-descendant node in document order.
+ * This is the first node in following::node(), if node is the context.
+ */
+ function followingNonDescendantNode(node) {
+ if (node.ownerElement) {
+ if (node.ownerElement.firstChild)
+ return node.ownerElement.firstChild;
+ node = node.ownerElement;
+ }
+ do {
+ if (node.nextSibling) return node.nextSibling;
+ } while (node = node.parentNode);
+ return null;
+ }
+
+ /** Returns the next node in a document-order depth-first search.
+ * See the definition of document order[1]:
+ * 1) element
+ * 2) namespace nodes
+ * 3) attributes
+ * 4) children
+ * [1]: http://www.w3.org/TR/xpath/#dt-document-order
+ */
+ function followingNode(node) {
+ if (node.ownerElement) // attributes: following node of element.
+ node = node.ownerElement;
+ if (null != node.firstChild)
+ return node.firstChild;
+ do {
+ if (null != node.nextSibling) {
+ return node.nextSibling;
+ }
+ node = node.parentNode;
+ } while (node);
+ return null;
+ }
+ /** Returns the previous node in document order (excluding attributes
+ * and namespace nodes).
+ */
+ function precedingNode(node) {
+ if (node.ownerElement)
+ return node.ownerElement;
+ if (null != node.previousSibling) {
+ node = node.previousSibling;
+ while (null != node.lastChild) {
+ node = node.lastChild;
+ }
+ return node;
+ }
+ if (null != node.parentNode) {
+ return node.parentNode;
+ }
+ return null;
+ }
+ /** This axis is inefficient if there are many nodes in the nodeList.
+ * But I think it's a pretty useless axis so it's ok. */
+ function followingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var nodeMultiSet = new NodeMultiSet(false);
+ var cursor = nodeList[0];
+ var unorderedFollowingStarts = [];
+ for (var i = 0; i < nodeList.length; i++) {
+ var node = nodeList[i];
+ var start = followingNonDescendantNode(node);
+ if (start)
+ unorderedFollowingStarts.push(start);
+ }
+ if (0 === unorderedFollowingStarts.length)
+ return {nodes:[]};
+ var pos = [], nextPos = [];
+ var started = 0;
+ while (cursor = followingNode(cursor)) {
+ for (var i = unorderedFollowingStarts.length - 1; i >= 0; i--){
+ if (cursor === unorderedFollowingStarts[i]) {
+ nodeMultiSet.pushSeries();
+ unorderedFollowingStarts.splice(i,i+1);
+ started++;
+ }
+ }
+ if (started && matcher.matches(cursor)) {
+ nodeMultiSet.addNode(cursor);
+ }
+ }
+ console.assert(0 === unorderedFollowingStarts.length);
+ for (var i = 0; i < started; i++)
+ nodeMultiSet.popSeries();
+ return nodeMultiSet.finalize();
+ }
+ function precedingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var cursor = nodeList.pop();
+ if (null == cursor) return {nodes:{}};
+ var r = {nodes:[], pos:[], lasts:[]};
+ var nextParents = [cursor.parentNode || cursor.ownerElement], nextPos = [1];
+ while (cursor = precedingNode(cursor)) {
+ if (cursor === nodeList[nodeList.length - 1]) {
+ nextParents.push(nodeList.pop());
+ nextPos.push(1);
+ }
+ var matches = matcher.matches(cursor);
+ var pos, someoneUsed = false;
+ if (matches)
+ pos = nextPos.slice();
+
+ for (var i = 0; i < nextParents.length; ++i) {
+ if (cursor === nextParents[i]) {
+ nextParents[i] = cursor.parentNode || cursor.ownerElement;
+ if (matches) {
+ pos[i] = null;
+ }
+ } else {
+ if (matches) {
+ pos[i] = nextPos[i]++;
+ someoneUsed = true;
+ }
+ }
+ }
+ if (someoneUsed) {
+ r.nodes.unshift(cursor);
+ r.pos.unshift(pos);
+ }
+ }
+ for (var i = 0; i < r.pos.length; ++i) {
+ var lasts = [];
+ r.lasts.push(lasts);
+ for (var j = r.pos[i].length - 1; j >= 0; j--) {
+ if (null == r.pos[i][j]) {
+ r.pos[i].splice(j, j+1);
+ } else {
+ lasts.unshift(nextPos[j] - 1);
+ }
+ }
+ }
+ return r;
+ }
+
+ /** node-set, axis -> node-set */
+ function descendantDfs(nodeMultiSet, node, remaining, matcher, andSelf, attrIndices, attrNodes) {
+ while (0 < remaining.length && null != remaining[0].ownerElement) {
+ var attr = remaining.shift();
+ if (andSelf && matcher.matches(attr)) {
+ attrNodes.push(attr);
+ attrIndices.push(nodeMultiSet.nodes.length);
+ }
+ }
+ if (null != node && !andSelf) {
+ if (matcher.matches(node))
+ nodeMultiSet.addNode(node);
+ }
+ var pushed = false;
+ if (null == node) {
+ if (0 === remaining.length) return;
+ node = remaining.shift();
+ nodeMultiSet.pushSeries();
+ pushed = true;
+ } else if (0 < remaining.length && node === remaining[0]) {
+ nodeMultiSet.pushSeries();
+ pushed = true;
+ remaining.shift();
+ }
+ if (andSelf) {
+ if (matcher.matches(node))
+ nodeMultiSet.addNode(node);
+ }
+ // TODO: use optimization. Also try element.getElementsByTagName
+ // var nodeList = 1 === nodeTypeNum && null != node.children ? node.children : node.childNodes;
+ var nodeList = node.childNodes;
+ for (var j = 0; j < nodeList.length; ++j) {
+ var child = nodeList[j];
+ descendantDfs(nodeMultiSet, child, remaining, matcher, andSelf, attrIndices, attrNodes);
+ }
+ if (pushed) {
+ nodeMultiSet.popSeries();
+ }
+ }
+ function descenantHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, andSelf) {
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var nodeMultiSet = new NodeMultiSet(false);
+ var attrIndices = [], attrNodes = [];
+ while (0 < nodeList.length) {
+ // var node = nodeList.shift();
+ descendantDfs(nodeMultiSet, null, nodeList, matcher, andSelf, attrIndices, attrNodes);
+ }
+ nodeMultiSet.finalize();
+ for (var i = attrNodes.length-1; i >= 0; --i) {
+ nodeMultiSet.nodes.splice(attrIndices[i], attrIndices[i], attrNodes[i]);
+ nodeMultiSet.pos.splice(attrIndices[i], attrIndices[i], [1]);
+ nodeMultiSet.lasts.splice(attrIndices[i], attrIndices[i], [1]);
+ }
+ return nodeMultiSet;
+ }
+ /**
+ */
+ function ancestorHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, andSelf) {
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var ancestors = []; // array of non-empty arrays of matching ancestors
+ for (var i = 0; i < nodeList.length; ++i) {
+ var node = nodeList[i];
+ var isFirst = true;
+ var a = [];
+ while (null != node) {
+ if (!isFirst || andSelf) {
+ if (matcher.matches(node))
+ a.push(node);
+ }
+ isFirst = false;
+ node = node.parentNode || node.ownerElement;
+ }
+ if (0 < a.length)
+ ancestors.push(a);
+ }
+ var lasts = [];
+ for (var i = 0; i < ancestors.length; ++i) lasts.push(ancestors[i].length);
+ var nodeMultiSet = new NodeMultiSet(true);
+ var newCtx = {nodes:[], pos:[], lasts:[]};
+ while (0 < ancestors.length) {
+ var pos = [ancestors[0].length];
+ var last = [lasts[0]];
+ var node = ancestors[0].pop();
+ for (var i = ancestors.length - 1; i > 0; --i) {
+ if (node === ancestors[i][ancestors[i].length - 1]) {
+ pos.push(ancestors[i].length);
+ last.push(lasts[i]);
+ ancestors[i].pop();
+ if (0 === ancestors[i].length) {
+ ancestors.splice(i, i+1);
+ lasts.splice(i, i+1);
+ }
+ }
+ }
+ if (0 === ancestors[0].length) {
+ ancestors.shift();
+ lasts.shift();
+ }
+ newCtx.nodes.push(node);
+ newCtx.pos.push(pos);
+ newCtx.lasts.push(last);
+ }
+ return newCtx;
+ }
+ /** Helper function for sortDocumentOrder. Returns a list of indices, from the
+ * node to the root, of positions within parent.
+ * For convenience, the node is the first element of the array.
+ */
+ function addressVector(node) {
+ var r = [node];
+ if (null != node.ownerElement) {
+ node = node.ownerElement;
+ r.push(-1);
+ }
+ while (null != node) {
+ var i = 0;
+ while (null != node.previousSibling) {
+ node = node.previousSibling;
+ i++;
+ }
+ r.push(i);
+ node = node.parentNode
+ }
+ return r;
+ }
+ function addressComparator(a, b) {
+ var minlen = Math.min(a.length - 1, b.length - 1), // not including [0]=node
+ alen = a.length,
+ blen = b.length;
+ if (a[0] === b[0]) return 0;
+ var c;
+ for (var i = 0; i < minlen; ++i) {
+ c = a[alen - i - 1] - b[blen - i - 1];
+ if (0 !== c)
+ break;
+ }
+ if (null == c || 0 === c) {
+ // All equal until one of the nodes. The longer one is the descendant.
+ c = alen - blen;
+ }
+ if (0 === c)
+ c = a.nodeName - b.nodeName;
+ if (0 === c)
+ c = 1;
+ return c;
+ }
+ var sortUniqDocumentOrder = xpath.sortUniqDocumentOrder = function(nodes) {
+ var a = [];
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ var v = addressVector(node);
+ a.push(v);
+ }
+ a.sort(addressComparator);
+ var b = [];
+ for (var i = 0; i < a.length; i++) {
+ if (0 < i && a[i][0] === a[i - 1][0])
+ continue;
+ b.push(a[i][0]);
+ }
+ return b;
+ }
+ /** Sort node multiset. Does not do any de-duping. */
+ function sortNodeMultiSet(nodeMultiSet) {
+ var a = [];
+ for (var i = 0; i < nodeMultiSet.nodes.length; i++) {
+ var v = addressVector(nodeMultiSet.nodes[i]);
+ a.push({v:v, n:nodeMultiSet.nodes[i],
+ p:nodeMultiSet.pos[i], l:nodeMultiSet.lasts[i]});
+ }
+ a.sort(compare);
+ var r = {nodes:[], pos:[], lasts:[]};
+ for (var i = 0; i < a.length; ++i) {
+ r.nodes.push(a[i].n);
+ r.pos.push(a[i].p);
+ r.lasts.push(a[i].l);
+ }
+ function compare(x, y) {
+ return addressComparator(x.v, y.v);
+ }
+ return r;
+ }
+ /** Returns an array containing all the ancestors down to a node.
+ * The array starts with document.
+ */
+ function nodeAndAncestors(node) {
+ var ancestors = [node];
+ var p = node;
+ while (p = p.parentNode || p.ownerElement) {
+ ancestors.unshift(p);
+ }
+ return ancestors;
+ }
+ function compareSiblings(a, b) {
+ if (a === b) return 0;
+ var c = a;
+ while (c = c.previousSibling) {
+ if (c === b)
+ return 1; // b < a
+ }
+ c = b;
+ while (c = c.previousSibling) {
+ if (c === a)
+ return -1; // a < b
+ }
+ throw new Error('a and b are not siblings: ' + xpath.stringifyObject(a) + ' vs ' + xpath.stringifyObject(b));
+ }
+ /** The merge in merge-sort.*/
+ function mergeNodeLists(x, y) {
+ var a, b, aanc, banc, r = [];
+ if ('object' !== typeof x)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Invalid LHS for | operator ' +
+ '(expected node-set): ' + x);
+ if ('object' !== typeof y)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Invalid LHS for | operator ' +
+ '(expected node-set): ' + y);
+ while (true) {
+ if (null == a) {
+ a = x.shift();
+ if (null != a)
+ aanc = addressVector(a);
+ }
+ if (null == b) {
+ b = y.shift();
+ if (null != b)
+ banc = addressVector(b);
+ }
+ if (null == a || null == b) break;
+ var c = addressComparator(aanc, banc);
+ if (c < 0) {
+ r.push(a);
+ a = null;
+ aanc = null;
+ } else if (c > 0) {
+ r.push(b);
+ b = null;
+ banc = null;
+ } else if (a.nodeName < b.nodeName) { // attributes
+ r.push(a);
+ a = null;
+ aanc = null;
+ } else if (a.nodeName > b.nodeName) { // attributes
+ r.push(b);
+ b = null;
+ banc = null;
+ } else if (a !== b) {
+ // choose b arbitrarily
+ r.push(b);
+ b = null;
+ banc = null;
+ } else {
+ console.assert(a === b, c);
+ // just skip b without pushing it.
+ b = null;
+ banc = null;
+ }
+ }
+ while (a) {
+ r.push(a);
+ a = x.shift();
+ }
+ while (b) {
+ r.push(b);
+ b = y.shift();
+ }
+ return r;
+ }
+ function comparisonHelper(test, x, y, isNumericComparison) {
+ var coersion;
+ if (isNumericComparison)
+ coersion = fn.number;
+ else coersion =
+ 'boolean' === typeof x || 'boolean' === typeof y ? fn['boolean'] :
+ 'number' === typeof x || 'number' === typeof y ? fn.number :
+ fn.string;
+ if ('object' === typeof x && 'object' === typeof y) {
+ var aMap = {};
+ for (var i = 0; i < x.nodes.length; ++i) {
+ var xi = coersion({nodes:[x.nodes[i]]});
+ for (var j = 0; j < y.nodes.length; ++j) {
+ var yj = coersion({nodes:[y.nodes[j]]});
+ if (test(xi, yj)) return true;
+ }
+ }
+ return false;
+ } else if ('object' === typeof x && x.nodes && x.nodes.length) {
+ for (var i = 0; i < x.nodes.length; ++i) {
+ var xi = coersion({nodes:[x.nodes[i]]}), yc = coersion(y);
+ if (test(xi, yc))
+ return true;
+ }
+ return false;
+ } else if ('object' === typeof y && x.nodes && x.nodes.length) {
+ for (var i = 0; i < x.nodes.length; ++i) {
+ var yi = coersion({nodes:[y.nodes[i]]}), xc = coersion(x);
+ if (test(xc, yi))
+ return true;
+ }
+ return false;
+ } else {
+ var xc = coersion(x), yc = coersion(y);
+ return test(xc, yc);
+ }
+ }
+ var axes = xpath.axes = {
+ 'ancestor':
+ function ancestor(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return ancestorHelper(
+ nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, false);
+ },
+ 'ancestor-or-self':
+ function ancestorOrSelf(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return ancestorHelper(
+ nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, true);
+ },
+ 'attribute':
+ function attribute(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ // TODO: figure out whether positions should be undefined here.
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var nodeMultiSet = new NodeMultiSet(false);
+ if (null != nodeName) {
+ // TODO: with namespace
+ for (var i = 0; i < nodeList.length; ++i) {
+ var node = nodeList[i];
+ if (null == node.getAttributeNode)
+ continue; // only Element has .getAttributeNode
+ var attr = node.getAttributeNode(nodeName);
+ if (null != attr && matcher.matches(attr)) {
+ nodeMultiSet.pushSeries();
+ nodeMultiSet.addNode(attr);
+ nodeMultiSet.popSeries();
+ }
+ }
+ } else {
+ for (var i = 0; i < nodeList.length; ++i) {
+ var node = nodeList[i];
+ if (null != node.attributes) {
+ nodeMultiSet.pushSeries();
+ for (var j = 0; j < node.attributes.length; j++) { // all nodes have .attributes
+ var attr = node.attributes[j];
+ if (matcher.matches(attr)) // TODO: I think this check is unnecessary
+ nodeMultiSet.addNode(attr);
+ }
+ nodeMultiSet.popSeries();
+ }
+ }
+ }
+ return nodeMultiSet.finalize();
+ },
+ 'child':
+ function child(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var nodeMultiSet = new NodeMultiSet(false);
+ for (var i = 0; i < nodeList.length; ++i) {
+ var n = nodeList[i];
+ if (n.ownerElement) // skip attribute nodes' text child.
+ continue;
+ if (n.childNodes) {
+ nodeMultiSet.pushSeries();
+ var childList = 1 === nodeTypeNum && null != n.children ?
+ n.children : n.childNodes;
+ for (var j = 0; j < childList.length; ++j) {
+ var child = childList[j];
+ if (matcher.matches(child)) {
+ nodeMultiSet.addNode(child);
+ }
+ // don't have to do de-duping because children have parent,
+ // which are current context.
+ }
+ nodeMultiSet.popSeries();
+ }
+ }
+ nodeMultiSet.finalize();
+ return sortNodeMultiSet(nodeMultiSet);
+ },
+ 'descendant':
+ function descenant(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return descenantHelper(
+ nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, false);
+ },
+ 'descendant-or-self':
+ function descenantOrSelf(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return descenantHelper(
+ nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, true);
+ },
+ 'following':
+ function following(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return followingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
+ },
+ 'following-sibling':
+ function followingSibling(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return followingSiblingHelper(
+ nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase,
+ Array.prototype.shift, function() {return this[0];},
+ function(node) {return node.nextSibling;});
+ },
+ 'namespace':
+ function namespace(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ // TODO
+ },
+ 'parent':
+ function parent(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ var nodes = [], pos = [];
+ for (var i = 0; i < nodeList.length; ++i) {
+ var parent = nodeList[i].parentNode || nodeList[i].ownerElement;
+ if (null == parent)
+ continue;
+ if (!matcher.matches(parent))
+ continue;
+ if (nodes.length > 0 && parent === nodes[nodes.length-1])
+ continue;
+ nodes.push(parent);
+ pos.push([1]);
+ }
+ return {nodes:nodes, pos:pos, lasts:pos};
+ },
+ 'preceding':
+ function preceding(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return precedingHelper(
+ nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
+ },
+ 'preceding-sibling':
+ function precedingSibling(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ return followingSiblingHelper(
+ nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase,
+ Array.prototype.pop, function() {return this[this.length-1];},
+ function(node) {return node.previousSibling},
+ false, true);
+ },
+ 'self':
+ function self(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
+ var nodes = [], pos = [];
+ var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
+ for (var i = 0; i < nodeList.length; ++i) {
+ if (matcher.matches(nodeList[i])) {
+ nodes.push(nodeList[i]);
+ pos.push([1]);
+ }
+ }
+ return {nodes: nodes, pos: pos, lasts: pos}
+ }
+ };
+
+ /***************************************************************************
+ * Evaluation: functions *
+ ***************************************************************************/
+ var fn = {
+ 'number': function number(optObject) {
+ if ('number' === typeof optObject)
+ return optObject;
+ if ('string' === typeof optObject)
+ return parseFloat(optObject); // note: parseFloat(' ') -> NaN, unlike +' ' -> 0.
+ if ('boolean' === typeof optObject)
+ return +optObject;
+ return fn.number(fn.string.call(this, optObject)); // for node-sets
+ },
+ 'string': function string(optObject) {
+ if (null == optObject)
+ return fn.string(this);
+ if ('string' === typeof optObject || 'boolean' === typeof optObject ||
+ 'number' === typeof optObject)
+ return '' + optObject;
+ if (0 == optObject.nodes.length) return '';
+ if (null != optObject.nodes[0].textContent)
+ return optObject.nodes[0].textContent;
+ return optObject.nodes[0].nodeValue;
+ },
+ 'boolean': function booleanVal(x) {
+ return 'object' === typeof x ? x.nodes.length > 0 : !!x;
+ },
+ 'last': function last() {
+ console.assert(Array.isArray(this.pos));
+ console.assert(Array.isArray(this.lasts));
+ console.assert(1 === this.pos.length);
+ console.assert(1 === this.lasts.length);
+ console.assert(1 === this.lasts[0].length);
+ return this.lasts[0][0];
+ },
+ 'position': function position() {
+ console.assert(Array.isArray(this.pos));
+ console.assert(Array.isArray(this.lasts));
+ console.assert(1 === this.pos.length);
+ console.assert(1 === this.lasts.length);
+ console.assert(1 === this.pos[0].length);
+ return this.pos[0][0];
+ },
+ 'count': function count(nodeSet) {
+ if ('object' !== typeof nodeSet)
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Position ' + stream.position() +
+ ': Function count(node-set) ' +
+ 'got wrong argument type: ' + nodeSet);
+ return nodeSet.nodes.length;
+ },
+ 'id': function id(object) {
+ var r = {nodes: []};
+ var doc = this.nodes[0].ownerDocument || this.nodes[0];
+ console.assert(doc);
+ var ids;
+ if ('object' === typeof object) {
+ // for node-sets, map id over each node value.
+ ids = [];
+ for (var i = 0; i < object.nodes.length; ++i) {
+ var idNode = object.nodes[i];
+ var idsString = fn.string({nodes:[idNode]});
+ var a = idsString.split(/[ \t\r\n]+/g);
+ Array.prototype.push.apply(ids, a);
+ }
+ } else {
+ var idsString = fn.string(object);
+ var a = idsString.split(/[ \t\r\n]+/g);
+ ids = a;
+ }
+ for (var i = 0; i < ids.length; ++i) {
+ var id = ids[i];
+ if (0 === id.length)
+ continue;
+ var node = doc.getElementById(id);
+ if (null != node)
+ r.nodes.push(node);
+ }
+ r.nodes = sortUniqDocumentOrder(r.nodes);
+ return r;
+ },
+ 'local-name': function(nodeSet) {
+ if (null == nodeSet)
+ return fn.name(this);
+ if (null == nodeSet.nodes) {
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'argument to name() must be a node-set. got ' + nodeSet);
+ }
+ // TODO: namespaced version
+ return nodeSet.nodes[0].nodeName.toLowerCase(); // TODO: no toLowerCase for xml
+ },
+ 'namespace-uri': function(nodeSet) {
+ // TODO
+ throw new Error('not implemented yet');
+ },
+ 'name': function(nodeSet) {
+ if (null == nodeSet)
+ return fn.name(this);
+ if (null == nodeSet.nodes) {
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'argument to name() must be a node-set. got ' + nodeSet);
+ }
+ return nodeSet.nodes[0].nodeName.toLowerCase(); // TODO: no toLowerCase for xml
+ },
+ 'concat': function concat(x) {
+ var l = [];
+ for (var i = 0; i < arguments.length; ++i) {
+ l.push(fn.string(arguments[i]));
+ }
+ return l.join('');
+ },
+ 'starts-with': function startsWith(a, b) {
+ var as = fn.string(a), bs = fn.string(b);
+ return as.substr(0, bs.length) === bs;
+ },
+ 'contains': function contains(a, b) {
+ var as = fn.string(a), bs = fn.string(b);
+ var i = as.indexOf(bs);
+ if (-1 === i) return false;
+ return true;
+ },
+ 'substring-before': function substringBefore(a, b) {
+ var as = fn.string(a), bs = fn.string(b);
+ var i = as.indexOf(bs);
+ if (-1 === i) return '';
+ return as.substr(0, i);
+ },
+ 'substring-after': function substringBefore(a, b) {
+ var as = fn.string(a), bs = fn.string(b);
+ var i = as.indexOf(bs);
+ if (-1 === i) return '';
+ return as.substr(i + bs.length);
+ },
+ 'substring': function substring(string, start, optEnd) {
+ if (null == string || null == start) {
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Must be at least 2 arguments to string()');
+ }
+ var sString = fn.string(string),
+ iStart = fn.round(start),
+ iEnd = optEnd == null ? null : fn.round(optEnd);
+ // Note that xpath string positions user 1-based index
+ if (iEnd == null)
+ return sString.substr(iStart - 1);
+ else
+ return sString.substr(iStart - 1, iEnd);
+ },
+ 'string-length': function stringLength(optString) {
+ return fn.string.call(this, optString).length;
+ },
+ 'normalize-space': function normalizeSpace(optString) {
+ var s = fn.string.call(this, optString);
+ return s.replace(/[ \t\r\n]+/g, ' ').replace(/^ | $/g, '');
+ },
+ 'translate': function translate(string, from, to) {
+ var sString = fn.string.call(this, string),
+ sFrom = fn.string(from),
+ sTo = fn.string(to);
+ var eachCharRe = [];
+ var map = {};
+ for (var i = 0; i < sFrom.length; ++i) {
+ var c = sFrom.charAt(i);
+ map[c] = sTo.charAt(i); // returns '' if beyond length of sTo.
+ // copied from goog.string.regExpEscape in the Closure library.
+ eachCharRe.push(
+ c.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
+ replace(/\x08/g, '\\x08'));
+ }
+ var re = new RegExp(eachCharRe.join('|'), 'g');
+ return sString.replace(re, function(c) {return map[c];});
+ },
+ /// Boolean functions
+ 'not': function not(x) {
+ var bx = fn['boolean'](x);
+ return !bx;
+ },
+ 'true': function trueVal() { return true; },
+ 'false': function falseVal() { return false; },
+ // TODO
+ 'lang': function lang(string) { throw new Error('Not implemented');},
+ 'sum': function sum(optNodeSet) {
+ if (null == optNodeSet) return fn.sum(this);
+ // for node-sets, map id over each node value.
+ var sum = 0;
+ for (var i = 0; i < optNodeSet.nodes.length; ++i) {
+ var node = optNodeSet.nodes[i];
+ var x = fn.number({nodes:[node]});
+ sum += x;
+ }
+ return sum;
+ },
+ 'floor': function floor(number) {
+ return Math.floor(fn.number(number));
+ },
+ 'ceiling': function ceiling(number) {
+ return Math.ceil(fn.number(number));
+ },
+ 'round': function round(number) {
+ return Math.round(fn.number(number));
+ }
+ };
+ /***************************************************************************
+ * Evaluation: operators *
+ ***************************************************************************/
+ var more = {
+ UnaryMinus: function(x) { return -fn.number(x); },
+ '+': function(x, y) { return fn.number(x) + fn.number(y); },
+ '-': function(x, y) { return fn.number(x) - fn.number(y); },
+ '*': function(x, y) { return fn.number(x) * fn.number(y); },
+ 'div': function(x, y) { return fn.number(x) / fn.number(y); },
+ 'mod': function(x, y) { return fn.number(x) % fn.number(y); },
+ '<': function(x, y) {
+ return comparisonHelper(function(x, y) { return fn.number(x) < fn.number(y);}, x, y, true);
+ },
+ '<=': function(x, y) {
+ return comparisonHelper(function(x, y) { return fn.number(x) <= fn.number(y);}, x, y, true);
+ },
+ '>': function(x, y) {
+ return comparisonHelper(function(x, y) { return fn.number(x) > fn.number(y);}, x, y, true);
+ },
+ '>=': function(x, y) {
+ return comparisonHelper(function(x, y) { return fn.number(x) >= fn.number(y);}, x, y, true);
+ },
+ 'and': function(x, y) { return fn['boolean'](x) && fn['boolean'](y); },
+ 'or': function(x, y) { return fn['boolean'](x) || fn['boolean'](y); },
+ '|': function(x, y) { return {nodes: mergeNodeLists(x.nodes, y.nodes)}; },
+ '=': function(x, y) {
+ // optimization for two node-sets case: avoid n^2 comparisons.
+ if ('object' === typeof x && 'object' === typeof y) {
+ var aMap = {};
+ for (var i = 0; i < x.nodes.length; ++i) {
+ var s = fn.string({nodes:[x.nodes[i]]});
+ aMap[s] = true;
+ }
+ for (var i = 0; i < y.nodes.length; ++i) {
+ var s = fn.string({nodes:[y.nodes[i]]});
+ if (aMap[s]) return true;
+ }
+ return false;
+ } else {
+ return comparisonHelper(function(x, y) {return x === y;}, x, y);
+ }
+ },
+ '!=': function(x, y) {
+ // optimization for two node-sets case: avoid n^2 comparisons.
+ if ('object' === typeof x && 'object' === typeof y) {
+ if (0 === x.nodes.length || 0 === y.nodes.length) return false;
+ var aMap = {};
+ for (var i = 0; i < x.nodes.length; ++i) {
+ var s = fn.string({nodes:[x.nodes[i]]});
+ aMap[s] = true;
+ }
+ for (var i = 0; i < y.nodes.length; ++i) {
+ var s = fn.string({nodes:[y.nodes[i]]});
+ if (!aMap[s]) return true;
+ }
+ return false;
+ } else {
+ return comparisonHelper(function(x, y) {return x !== y;}, x, y);
+ }
+ }
+ };
+ var nodeTypes = xpath.nodeTypes = {
+ 'node': 0,
+ 'attribute': 2,
+ 'comment': 8, // this.doc.COMMENT_NODE,
+ 'text': 3, // this.doc.TEXT_NODE,
+ 'processing-instruction': 7, // this.doc.PROCESSING_INSTRUCTION_NODE,
+ 'element': 1 //this.doc.ELEMENT_NODE
+ };
+ /** For debugging and unit tests: returnjs a stringified version of the
+ * argument. */
+ var stringifyObject = xpath.stringifyObject = function stringifyObject(ctx) {
+ var seenKey = 'seen' + Math.floor(Math.random()*1000000000);
+ return JSON.stringify(helper(ctx));
+
+ function helper(ctx) {
+ if (Array.isArray(ctx)) {
+ return ctx.map(function(x) {return helper(x);});
+ }
+ if ('object' !== typeof ctx) return ctx;
+ if (null == ctx) return ctx;
+ // if (ctx.toString) return ctx.toString();
+ if (null != ctx.outerHTML) return ctx.outerHTML;
+ if (null != ctx.nodeValue) return ctx.nodeName + '=' + ctx.nodeValue;
+ if (ctx[seenKey]) return '[circular]';
+ ctx[seenKey] = true;
+ var nicer = {};
+ for (var key in ctx) {
+ if (seenKey === key)
+ continue;
+ try {
+ nicer[key] = helper(ctx[key]);
+ } catch (e) {
+ nicer[key] = '[exception: ' + e.message + ']';
+ }
+ }
+ delete ctx[seenKey];
+ return nicer;
+ }
+ }
+ var Evaluator = xpath.Evaluator = function Evaluator(doc) {
+ this.doc = doc;
+ }
+ Evaluator.prototype = {
+ val: function val(ast, ctx) {
+ console.assert(ctx.nodes);
+
+ if ('number' === typeof ast || 'string' === typeof ast) return ast;
+ if (more[ast[0]]) {
+ var evaluatedParams = [];
+ for (var i = 1; i < ast.length; ++i) {
+ evaluatedParams.push(this.val(ast[i], ctx));
+ }
+ var r = more[ast[0]].apply(ctx, evaluatedParams);
+ return r;
+ }
+ switch (ast[0]) {
+ case 'Root': return {nodes: [this.doc]};
+ case 'FunctionCall':
+ var functionName = ast[1], functionParams = ast[2];
+ if (null == fn[functionName])
+ throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
+ 'Unknown function: ' + functionName);
+ var evaluatedParams = [];
+ for (var i = 0; i < functionParams.length; ++i) {
+ evaluatedParams.push(this.val(functionParams[i], ctx));
+ }
+ var r = fn[functionName].apply(ctx, evaluatedParams);
+ return r;
+ case 'Predicate':
+ var lhs = this.val(ast[1], ctx);
+ var ret = {nodes: []};
+ var contexts = eachContext(lhs);
+ for (var i = 0; i < contexts.length; ++i) {
+ var singleNodeSet = contexts[i];
+ var rhs = this.val(ast[2], singleNodeSet);
+ var success;
+ if ('number' === typeof rhs) {
+ success = rhs === singleNodeSet.pos[0][0];
+ } else {
+ success = fn['boolean'](rhs);
+ }
+ if (success) {
+ var node = singleNodeSet.nodes[0];
+ ret.nodes.push(node);
+ // skip over all the rest of the same node.
+ while (i+1 < contexts.length && node === contexts[i+1].nodes[0]) {
+ i++;
+ }
+ }
+ }
+ return ret;
+ case 'PathExpr':
+ // turn the path into an expressoin; i.e., remove the position
+ // information of the last axis.
+ var x = this.val(ast[1], ctx);
+ // Make the nodeset a forward-direction-only one.
+ if (x.finalize) { // it is a NodeMultiSet
+ for (var i = 0; i < x.nodes.length; ++i) {
+ console.assert(null != x.nodes[i].nodeType);
+ }
+ return {nodes: x.nodes};
+ } else {
+ return x;
+ }
+ case '/':
+ // TODO: don't generate '/' nodes, just Axis nodes.
+ var lhs = this.val(ast[1], ctx);
+ console.assert(null != lhs);
+ var r = this.val(ast[2], lhs);
+ console.assert(null != r);
+ return r;
+ case 'Axis':
+ // All the axis tests from Step. We only get AxisSpecifier NodeTest,
+ // not the predicate (which is applied later)
+ var axis = ast[1],
+ nodeType = ast[2],
+ nodeTypeNum = nodeTypes[nodeType],
+ shouldLowerCase = true, // TODO: give option
+ nodeName = ast[3] && shouldLowerCase ? ast[3].toLowerCase() : ast[3];
+ nodeName = nodeName === '*' ? null : nodeName;
+ if ('object' !== typeof ctx) return {nodes:[], pos:[]};
+ var nodeList = ctx.nodes.slice(); // TODO: is copy needed?
+ var r = axes[axis](nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
+ return r;
+ }
+ }
+ };
+ var evaluate = xpath.evaluate = function evaluate(expr, doc, context) {
+ //var astFactory = new AstEvaluatorFactory(doc, context);
+ var stream = new Stream(expr);
+ var ast = parse(stream, astFactory);
+ var val = new Evaluator(doc).val(ast, {nodes: [context]});
+ return val;
+ }
+
+ /***************************************************************************
+ * DOM interface *
+ ***************************************************************************/
+ var XPathException = xpath.XPathException = function XPathException(code, message) {
+ var e = new Error(message);
+ e.name = 'XPathException';
+ e.code = code;
+ return e;
+ }
+ XPathException.INVALID_EXPRESSION_ERR = 51;
+ XPathException.TYPE_ERR = 52;
+
+
+ var XPathEvaluator = xpath.XPathEvaluator = function XPathEvaluator() {}
+ XPathEvaluator.prototype = {
+ createExpression: function(expression, resolver) {
+ return new XPathExpression(expression, resolver);
+ },
+ createNSResolver: function(nodeResolver) {
+ // TODO
+ },
+ evaluate: function evaluate(expression, contextNode, resolver, type, result) {
+ var expr = new XPathExpression(expression, resolver);
+ return expr.evaluate(contextNode, type, result);
+ }
+ };
+
+
+ var XPathExpression = xpath.XPathExpression = function XPathExpression(expression, resolver, optDoc) {
+ var stream = new Stream(expression);
+ this._ast = parse(stream, astFactory);
+ this._doc = optDoc;
+ }
+ XPathExpression.prototype = {
+ evaluate: function evaluate(contextNode, type, result) {
+ if (null == contextNode.nodeType)
+ throw new Error('bad argument (expected context node): ' + contextNode);
+ var doc = contextNode.ownerDocument || contextNode;
+ if (null != this._doc && this._doc !== doc) {
+ throw new core.DOMException(
+ core.WRONG_DOCUMENT_ERR,
+ 'The document must be the same as the context node\'s document.');
+ }
+ var evaluator = new Evaluator(doc);
+ var value = evaluator.val(this._ast, {nodes: [contextNode]});
+ if (XPathResult.NUMBER_TYPE === type)
+ value = fn.number(value);
+ else if (XPathResult.STRING_TYPE === type)
+ value = fn.string(value);
+ else if (XPathResult.BOOLEAN_TYPE === type)
+ value = fn['boolean'](value);
+ else if (XPathResult.ANY_TYPE !== type &&
+ XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== type &&
+ XPathResult.ORDERED_NODE_ITERATOR_TYPE !== type &&
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== type &&
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== type &&
+ XPathResult.ANY_UNORDERED_NODE_TYPE !== type &&
+ XPathResult.FIRST_ORDERED_NODE_TYPE !== type)
+ throw new core.DOMException(
+ core.NOT_SUPPORTED_ERR,
+ 'You must provide an XPath result type (0=any).');
+ else if (XPathResult.ANY_TYPE !== type &&
+ 'object' !== typeof value)
+ throw new XPathException(
+ XPathException.TYPE_ERR,
+ 'Value should be a node-set: ' + value);
+ return new XPathResult(doc, value, type);
+ }
+ }
+
+ var XPathResult = xpath.XPathResult = function XPathResult(doc, value, resultType) {
+ this._value = value;
+ this._resultType = resultType;
+ this._i = 0;
+ this._invalidated = false;
+ if (this.resultType === XPathResult.UNORDERED_NODE_ITERATOR_TYPE ||
+ this.resultType === XPathResult.ORDERED_NODE_ITERATOR_TYPE) {
+ doc.addEventListener('DOMSubtreeModified', invalidate, true);
+ var self = this;
+ function invalidate() {
+ self._invalidated = true;
+ doc.removeEventListener('DOMSubtreeModified', invalidate, true);
+ }
+ }
+ }
+ XPathResult.ANY_TYPE = 0;
+ XPathResult.NUMBER_TYPE = 1;
+ XPathResult.STRING_TYPE = 2;
+ XPathResult.BOOLEAN_TYPE = 3;
+ XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4;
+ XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5;
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE = 6;
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7;
+ XPathResult.ANY_UNORDERED_NODE_TYPE = 8;
+ XPathResult.FIRST_ORDERED_NODE_TYPE = 9;
+ var proto = {
+ // XPathResultType
+ get resultType() {
+ if (this._resultType) return this._resultType;
+ switch (typeof this._value) {
+ case 'number': return XPathResult.NUMBER_TYPE;
+ case 'string': return XPathResult.STRING_TYPE;
+ case 'boolean': return XPathResult.BOOLEAN_TYPE;
+ default: return XPathResult.UNORDERED_NODE_ITERATOR_TYPE;
+ }
+ },
+ get numberValue() {
+ if (XPathResult.NUMBER_TYPE !== this.resultType)
+ throw new XPathException(XPathException.TYPE_ERR,
+ 'You should have asked for a NUMBER_TYPE.');
+ return this._value;
+ },
+ get stringValue() {
+ if (XPathResult.STRING_TYPE !== this.resultType)
+ throw new XPathException(XPathException.TYPE_ERR,
+ 'You should have asked for a STRING_TYPE.');
+ return this._value;
+ },
+ get booleanValue() {
+ if (XPathResult.BOOLEAN_TYPE !== this.resultType)
+ throw new XPathException(XPathException.TYPE_ERR,
+ 'You should have asked for a BOOLEAN_TYPE.');
+ return this._value;
+ },
+ get singleNodeValue() {
+ if (XPathResult.ANY_UNORDERED_NODE_TYPE !== this.resultType &&
+ XPathResult.FIRST_ORDERED_NODE_TYPE !== this.resultType)
+ throw new XPathException(
+ XPathException.TYPE_ERR,
+ 'You should have asked for a FIRST_ORDERED_NODE_TYPE.');
+ return this._value.nodes[0] || null;
+ },
+ get invalidIteratorState() {
+ if (XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== this.resultType &&
+ XPathResult.ORDERED_NODE_ITERATOR_TYPE !== this.resultType)
+ return false;
+ return !!this._invalidated;
+ },
+ get snapshotLength() {
+ if (XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== this.resultType &&
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== this.resultType)
+ throw new XPathException(
+ XPathException.TYPE_ERR,
+ 'You should have asked for a ORDERED_NODE_SNAPSHOT_TYPE.');
+ return this._value.nodes.length;
+ },
+ iterateNext: function iterateNext() {
+ if (XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== this.resultType &&
+ XPathResult.ORDERED_NODE_ITERATOR_TYPE !== this.resultType)
+ throw new XPathException(
+ XPathException.TYPE_ERR,
+ 'You should have asked for a ORDERED_NODE_ITERATOR_TYPE.');
+ if (this.invalidIteratorState)
+ throw new core.DOMException(
+ core.INVALID_STATE_ERR,
+ 'The document has been mutated since the result was returned');
+ return this._value.nodes[this._i++] || null;
+ },
+ snapshotItem: function snapshotItem(index) {
+ if (XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== this.resultType &&
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== this.resultType)
+ throw new XPathException(
+ XPathException.TYPE_ERR,
+ 'You should have asked for a ORDERED_NODE_SNAPSHOT_TYPE.');
+ return this._value.nodes[index] || null;
+ }
+ };
+ // so you can access ANY_TYPE etc. from the instances:
+ XPathResult.prototype = Object.create(XPathResult,
+ Object.keys(proto).reduce(function (descriptors, name) {
+ descriptors[name] = Object.getOwnPropertyDescriptor(proto, name);
+ return descriptors;
+ }, {
+ constructor: {
+ value: XPathResult,
+ writable: true,
+ configurable: true
+ }
+ }));
+
+ core.XPathException = XPathException;
+ core.XPathExpression = XPathExpression;
+ core.XPathResult = XPathResult;
+ core.XPathEvaluator = XPathEvaluator;
+
+ core.Document.prototype.createExpression =
+ XPathEvaluator.prototype.createExpression;
+
+ core.Document.prototype.createNSResolver =
+ XPathEvaluator.prototype.createNSResolver;
+
+ core.Document.prototype.evaluate = XPathEvaluator.prototype.evaluate;
+
+})();
diff --git a/node_modules/jsdom/lib/jsdom/living/character-data.js b/node_modules/jsdom/lib/jsdom/living/character-data.js
new file mode 100644
index 0000000..89b2eaa
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/character-data.js
@@ -0,0 +1,90 @@
+"use strict";
+var inheritFrom = require("../utils").inheritFrom;
+
+module.exports = function (core) {
+ core.CharacterData = function CharacterData(ownerDocument, data) {
+ core.Node.call(this, ownerDocument);
+
+ this._data = data;
+ };
+
+ inheritFrom(core.Node, core.CharacterData, {
+ get data() { return this._data; },
+ set data(data) {
+ if (data === null) {
+ data = "";
+ }
+ data = String(data);
+
+ this._setData(data);
+ },
+
+ get length() {
+ return this._data.length;
+ },
+
+ substringData: function (offset, count) {
+ offset = offset >>> 0;
+ count = count >>> 0;
+
+ var length = this.length;
+
+ if (offset > length) {
+ throw new core.DOMException(core.DOMException.INDEX_SIZE_ERR);
+ }
+
+ if (offset + count > length) {
+ return this._data.substring(offset);
+ }
+
+ return this._data.substring(offset, offset + count);
+ },
+
+ appendData: function (data) {
+ this.replaceData(this.length, 0, data);
+ },
+
+ insertData: function (offset, data) {
+ this.replaceData(offset, 0, data);
+ },
+
+ deleteData: function (offset, count) {
+ this.replaceData(offset, count, "");
+ },
+
+ replaceData: function (offset, count, data) {
+ offset = offset >>> 0;
+ count = count >>> 0;
+ data = String(data);
+
+ var length = this.length;
+
+ if (offset > length) {
+ throw new core.DOMException(core.DOMException.INDEX_SIZE_ERR);
+ }
+
+ if (offset + count > length) {
+ count = length - offset;
+ }
+
+ var start = this._data.substring(0, offset);
+ var end = this._data.substring(offset + count);
+
+ this._setData(start + data + end);
+
+ // TODO: range stuff
+ },
+
+ _setData: function (newData) {
+ // TODO: remove this once we no longer rely on mutation events internally, since they are nonstandard
+ var oldData = this._data;
+ this._data = newData;
+
+ if (this._ownerDocument && this._parentNode && this._ownerDocument.implementation._hasFeature("MutationEvents")) {
+ var ev = this._ownerDocument.createEvent("MutationEvents");
+ ev.initMutationEvent("DOMCharacterDataModified", true, false, this, oldData, newData, null, null);
+ this.dispatchEvent(ev);
+ }
+ }
+ });
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/comment.js b/node_modules/jsdom/lib/jsdom/living/comment.js
new file mode 100644
index 0000000..471eb5d
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/comment.js
@@ -0,0 +1,13 @@
+"use strict";
+var inheritFrom = require("../utils").inheritFrom;
+
+module.exports = function (core) {
+ // TODO: constructor should not take ownerDocument
+ core.Comment = function Comment(ownerDocument, data) {
+ core.CharacterData.call(this, ownerDocument, data);
+ };
+
+ inheritFrom(core.CharacterData, core.Comment, {
+ nodeType: core.Node.COMMENT_NODE, // TODO should be on prototype, not here
+ });
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/document-type.js b/node_modules/jsdom/lib/jsdom/living/document-type.js
new file mode 100644
index 0000000..abd80aa
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/document-type.js
@@ -0,0 +1,19 @@
+"use strict";
+var inheritFrom = require("../utils").inheritFrom;
+
+module.exports = function (core) {
+ core.DocumentType = function DocumentType(ownerDocument, name, publicId, systemId) {
+ core.Node.call(this, ownerDocument);
+
+ this._name = name;
+ this._publicId = publicId;
+ this._systemId = systemId;
+ };
+
+ inheritFrom(core.Node, core.DocumentType, {
+ nodeType: core.Node.DOCUMENT_TYPE_NODE, // TODO should be on prototype, not here
+ get name() { return this._name; },
+ get publicId() { return this._publicId; },
+ get systemId() { return this._systemId; }
+ });
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/document.js b/node_modules/jsdom/lib/jsdom/living/document.js
new file mode 100644
index 0000000..9943519
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/document.js
@@ -0,0 +1,26 @@
+"use strict";
+var validateNames = require("./helpers/validate-names");
+
+module.exports = function (core) {
+ core.Document.prototype.createProcessingInstruction = function (target, data) {
+ target = String(target);
+ data = String(data);
+
+ validateNames.name(target, core);
+
+ if (data.indexOf("?>") !== -1) {
+ throw new core.DOMException(core.DOMException.INVALID_CHARACTER_ERR,
+ "Processing instruction data cannot contain the string \"?>\"");
+ }
+
+ return new core.ProcessingInstruction(this._ownerDocument, target, data);
+ };
+
+ core.Document.prototype.createTextNode = function (data) {
+ return new core.Text(this, String(data));
+ };
+
+ core.Document.prototype.createComment = function (data) {
+ return new core.Comment(this, String(data));
+ };
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/dom-implementation.js b/node_modules/jsdom/lib/jsdom/living/dom-implementation.js
new file mode 100644
index 0000000..5ca8989
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/dom-implementation.js
@@ -0,0 +1,85 @@
+"use strict";
+var validateNames = require("./helpers/validate-names");
+
+module.exports = function (core) {
+ core.DOMImplementation.prototype.hasFeature = function () {
+ return true;
+ };
+
+ core.DOMImplementation.prototype.createDocumentType = function (qualifiedName, publicId, systemId) {
+ qualifiedName = String(qualifiedName);
+ publicId = String(publicId);
+ systemId = String(systemId);
+
+ validateNames.qname(qualifiedName, core);
+
+ return new core.DocumentType(this._ownerDocument, qualifiedName, publicId, systemId);
+ };
+
+ core.DOMImplementation.prototype.createDocument = function (namespace, qualifiedName, doctype) {
+ namespace = namespace !== null ? String(namespace) : namespace;
+ qualifiedName = qualifiedName === null ? "" : String(qualifiedName);
+ if (doctype === undefined) {
+ doctype = null;
+ }
+
+ var document = new core.Document({ parsingMode: "xml" });
+
+ var element = null;
+ if (qualifiedName !== "") {
+ element = document.createElementNS(namespace, qualifiedName);
+ }
+
+ if (doctype !== null) {
+ document.appendChild(doctype);
+ }
+
+ if (element !== null) {
+ document.appendChild(element);
+ }
+
+ return document;
+ };
+
+ core.DOMImplementation.prototype.createHTMLDocument = function (title) {
+ // Let doc be a new document that is an HTML document.
+ // Set doc's content type to "text/html".
+ var document = new core.HTMLDocument({ parsingMode: "html" });
+
+ // Create a doctype, with "html" as its name and with its node document set
+ // to doc. Append the newly created node to doc.
+ var doctype = this.createDocumentType("html", "", "");
+ document.appendChild(doctype);
+
+ // Create an html element in the HTML namespace, and append it to doc.
+ var htmlElement = document.createElementNS("http://www.w3.org/1999/xhtml", "html");
+ document.appendChild(htmlElement);
+
+ // Create a head element in the HTML namespace, and append it to the html
+ // element created in the previous step.
+ var headElement = document.createElement("head");
+ htmlElement.appendChild(headElement);
+
+ // If the title argument is not omitted:
+ if (title !== undefined) {
+ // Create a title element in the HTML namespace, and append it to the head
+ // element created in the previous step.
+ var titleElement = document.createElement("title");
+ headElement.appendChild(titleElement);
+
+ // Create a Text node, set its data to title (which could be the empty
+ // string), and append it to the title element created in the previous step.
+ titleElement.appendChild(document.createTextNode(title));
+ }
+
+ // Create a body element in the HTML namespace, and append it to the html
+ // element created in the earlier step.
+ htmlElement.appendChild(document.createElement("body"));
+
+ // doc's origin is an alias to the origin of the context object's associated
+ // document, and doc's effective script origin is an alias to the effective
+ // script origin of the context object's associated document.
+
+ return document;
+ };
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/helpers/validate-names.js b/node_modules/jsdom/lib/jsdom/living/helpers/validate-names.js
new file mode 100644
index 0000000..5f4622f
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/helpers/validate-names.js
@@ -0,0 +1,63 @@
+"use strict";
+var xnv = require("xml-name-validator");
+
+// https://dom.spec.whatwg.org/#validate
+
+exports.name = function (name, core) {
+ try {
+ xnv.name(name);
+ } catch (e) {
+ throw new core.DOMException(core.DOMException.INVALID_CHARACTER_ERR,
+ "\"" + name + "\" did not match the Name production: " + e.message);
+ }
+};
+
+exports.qname = function (qname, core) {
+ exports.name(qname, core);
+
+ try {
+ xnv.qname(qname);
+ } catch (e) {
+ throw new core.DOMException(core.DOMException.NAMESPACE_ERR,
+ "\"" + qname + "\" did not match the QName production: " + e.message);
+ }
+};
+
+exports.validateAndExtract = function (namespace, qualifiedName, core) {
+ if (namespace === "") {
+ namespace = null;
+ }
+
+ exports.qname(qualifiedName, core);
+
+ var prefix = null;
+ var localName = qualifiedName;
+
+ var colonIndex = qualifiedName.indexOf(":");
+ if (colonIndex !== -1) {
+ prefix = qualifiedName.substring(0, colonIndex);
+ localName = qualifiedName.substring(colonIndex + 1);
+ }
+
+ if (prefix !== null && namespace === null) {
+ throw new core.DOMException(core.DOMException.NAMESPACE_ERR,
+ "A namespace was given but a prefix was also extracted from the qualifiedName");
+ }
+
+ if (prefix === "xml" && namespace !== "http://www.w3.org/XML/1998/namespace") {
+ throw new core.DOMException(core.DOMException.NAMESPACE_ERR,
+ "A prefix of \"xml\" was given but the namespace was not the XML namespace");
+ }
+
+ if ((qualifiedName === "xmlns" || prefix === "xmlns") && namespace !== "http://www.w3.org/2000/xmlns/") {
+ throw new core.DOMException(core.DOMException.NAMESPACE_ERR,
+ "A prefix or qualifiedName of \"xmlns\" was given but the namespace was not the XMLNS namespace");
+ }
+
+ if (namespace === "http://www.w3.org/2000/xmlns/" && qualifiedName !== "xmlns" && prefix !== "xmlns") {
+ throw new core.DOMException(core.DOMException.NAMESPACE_ERR,
+ "The XMLNS namespace was given but neither the prefix nor qualifiedName was \"xmlns\"");
+ }
+
+ return { namespace: namespace, prefix: prefix, localName: localName, qualifiedName: qualifiedName };
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/index.js b/node_modules/jsdom/lib/jsdom/living/index.js
new file mode 100644
index 0000000..e4b67a0
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/index.js
@@ -0,0 +1,23 @@
+"use strict";
+var core = module.exports = require("../level1/core");
+
+// These (because of how they were written) directly include level1/core and modify it.
+// ORDER IS IMPORTANT
+require("../level2/core");
+require("../level2/events");
+require("../level2/html");
+require("../level2/style");
+require("../level3/core");
+require("../level3/ls");
+require("../level3/xpath");
+
+require("./document-type")(core);
+require("./character-data")(core);
+require("./processing-instruction")(core);
+require("./comment")(core);
+require("./text")(core);
+require("./dom-implementation")(core);
+require("./document")(core);
+require("./node-filter")(core);
+require("./node")(core);
+require("./selectors")(core);
diff --git a/node_modules/jsdom/lib/jsdom/living/node-filter.js b/node_modules/jsdom/lib/jsdom/living/node-filter.js
new file mode 100644
index 0000000..f945b84
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/node-filter.js
@@ -0,0 +1,47 @@
+"use strict";
+var addConstants = require("../utils").addConstants;
+
+module.exports = function (core) {
+ // https://dom.spec.whatwg.org/#interface-nodefilter
+ core.NodeFilter = function () {
+ throw new TypeError("Illegal constructor");
+ };
+
+ /**
+ * Returns an unsigned short that will be used to tell if a given Node must
+ * be accepted or not by the NodeIterator or TreeWalker iteration
+ * algorithm. This method is expected to be written by the user of a
+ * NodeFilter.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter
+ * @interface
+ *
+ * @param {Node} node DOM Node
+ * @return {FILTER_ACCEPT|FILTER_REJECT|FILTER_SKIP}
+ */
+ core.NodeFilter.acceptNode = function(/* node */) {
+ throw new Error("This method is expected to be written by the user of a NodeFilter.");
+ };
+
+ addConstants(core.NodeFilter, {
+ // Constants for whatToShow
+ SHOW_ALL : 0xFFFFFFFF,
+ SHOW_ELEMENT : 0x00000001,
+ SHOW_ATTRIBUTE : 0x00000002,
+ SHOW_TEXT : 0x00000004,
+ SHOW_CDATA_SECTION : 0x00000008,
+ SHOW_ENTITY_REFERENCE : 0x00000010,
+ SHOW_ENTITY : 0x00000020,
+ SHOW_PROCESSING_INSTRUCTION : 0x00000040,
+ SHOW_COMMENT : 0x00000080,
+ SHOW_DOCUMENT : 0x00000100,
+ SHOW_DOCUMENT_TYPE : 0x00000200,
+ SHOW_DOCUMENT_FRAGMENT : 0x00000400,
+ SHOW_NOTATION : 0x00000800,
+
+ // Constants returned by acceptNode
+ FILTER_ACCEPT : 1,
+ FILTER_REJECT : 2,
+ FILTER_SKIP : 3
+ });
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/node.js b/node_modules/jsdom/lib/jsdom/living/node.js
new file mode 100644
index 0000000..0ff38f9
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/node.js
@@ -0,0 +1,261 @@
+"use strict";
+var defineGetter = require("../utils").defineGetter;
+
+module.exports = function (core) {
+ var DOCUMENT_POSITION_DISCONNECTED = core.Node.DOCUMENT_POSITION_DISCONNECTED;
+ var DOCUMENT_POSITION_PRECEDING = core.Node.DOCUMENT_POSITION_PRECEDING;
+ var DOCUMENT_POSITION_FOLLOWING = core.Node.DOCUMENT_POSITION_FOLLOWING;
+ var DOCUMENT_POSITION_CONTAINS = core.Node.DOCUMENT_POSITION_CONTAINS;
+ var DOCUMENT_POSITION_CONTAINED_BY = core.Node.DOCUMENT_POSITION_CONTAINED_BY;
+ var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = core.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
+
+ /**
+ * Return true if node is of a type obsoleted by the WHATWG living standard
+ * @param {Node} node
+ * @return {Boolean}
+ */
+ function isObsoleteNodeType(node) {
+ return node.nodeType === core.Node.ENTITY_NODE ||
+ node.nodeType === core.Node.ENTITY_REFERENCE_NODE ||
+ node.nodeType === core.Node.NOTATION_NODE ||
+ node.nodeType === core.Node.CDATA_SECTION_NODE;
+ }
+
+ /**
+ * Return the parent node of node, whatever its nodeType is
+ * @param {Node} node
+ * @return {Node or null}
+ */
+ function getNodeParent(node) {
+
+ if(!node) {
+ return node;
+ }
+
+ switch (node.nodeType) {
+
+ case core.Node.DOCUMENT_NODE:
+ case core.Node.DOCUMENT_FRAGMENT_NODE:
+ return null;
+
+ case core.Node.COMMENT_NODE:
+ case core.Node.DOCUMENT_TYPE_NODE:
+ case core.Node.ELEMENT_NODE:
+ case core.Node.PROCESSING_INSTRUCTION_NODE:
+ case core.Node.TEXT_NODE:
+ return node.parentNode;
+
+ case core.Node.ATTRIBUTE_NODE:
+
+ return node._parentNode;
+
+ default:
+ throw new Error("Unknown node type:" + node.nodeType);
+ }
+ }
+
+ /**
+ * Walk up the node tree and return the nodes root node
+ * @param {Node} node
+ * @return {Node}
+ */
+ function findNodeRoot(node) {
+ if (!getNodeParent(node)) {
+ return node;
+ }
+
+ return findNodeRoot(getNodeParent(node));
+ }
+
+ /**
+ * Walk up the node tree returning true if otherNode is an ancestor of node
+ * @param {Node} node
+ * @param {Node} otherNode
+ * @return {Boolean}
+ */
+ function isAncestor(node, otherNode) {
+ var parentNode = node.nodeType === node.ATTRIBUTE_NODE ? node._parentNode : node.parentNode;
+
+ if (!parentNode) {
+ return false;
+ }
+
+ if(parentNode === otherNode) {
+ return true;
+ }
+
+ return isAncestor(parentNode, otherNode);
+ }
+
+ /**
+ * Traverse the node tree starting at current. Return DOCUMENT_POSITION_FOLLOWING if otherNode follows node. Return
+ * DOCUMENT_POSITION_PRECEDING if otherNode precedes node
+ * @param {Node} current
+ * @param {Node} node
+ * @param {Node} otherNode
+ * @return {Number}
+ */
+ function followingOrPreceding(current, node, otherNode) {
+ if (current === node) {
+ return core.Node.DOCUMENT_POSITION_FOLLOWING;
+ }
+
+ if (current === otherNode) {
+ return core.Node.DOCUMENT_POSITION_PRECEDING;
+ }
+
+ var i = 0, len = current._childNodes.length, child, result;
+
+ for(; i < len; i += 1) {
+
+ child = current._childNodes[i];
+
+ if((result = followingOrPreceding(child, node, otherNode)) !== 0) {
+ return result;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns a bitmask Number composed of DOCUMENT_POSITION constants based upon the rules defined in
+ * http://dom.spec.whatwg.org/#dom-node-comparedocumentposition
+ * @param {Node} other
+ * @return {Number}
+ */
+ core.Node.prototype.compareDocumentPosition = function compareDocumentPosition (other) {
+ // Let reference be the context object.
+ var reference = this;
+
+ if(!(other instanceof core.Node)) {
+ throw Error("Comparing position against non-Node values is not allowed");
+ }
+
+ if (isObsoleteNodeType(reference) || isObsoleteNodeType(other)) {
+ throw new Error("Obsolete node type");
+ }
+
+ // If other and reference are the same object, return zero.
+ if (reference === other) {
+ return 0;
+ }
+
+ // If other and reference are not in the same tree, return the result of adding DOCUMENT_POSITION_DISCONNECTED,
+ // DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING,
+ // with the constraint that this is to be consistent, together.
+ if (findNodeRoot(reference) !== findNodeRoot(other)) {
+ return DOCUMENT_POSITION_DISCONNECTED + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_FOLLOWING;
+ }
+
+ // If other is an ancestor of reference, return the result of adding DOCUMENT_POSITION_CONTAINS to
+ // DOCUMENT_POSITION_PRECEDING.
+ if (isAncestor(reference, other)) {
+ return DOCUMENT_POSITION_CONTAINS + DOCUMENT_POSITION_PRECEDING;
+ }
+
+ // If other is a descendant of reference, return the result of adding DOCUMENT_POSITION_CONTAINED_BY to
+ // DOCUMENT_POSITION_FOLLOWING.
+ if (isAncestor(other, reference)) {
+ return DOCUMENT_POSITION_CONTAINED_BY + DOCUMENT_POSITION_FOLLOWING;
+ }
+
+ // If other is preceding reference return DOCUMENT_POSITION_PRECEDING, otherwise return DOCUMENT_POSITION_FOLLOWING
+ return followingOrPreceding(findNodeRoot(reference), reference, other);
+ };
+
+ /**
+ * The contains(other) method returns true if other is an inclusive descendant of the context object,
+ * and false otherwise (including when other is null).
+ * @param {[Node]} other [the node to test]
+ * @return {[boolean]} [whether other is an inclusive descendant of this]
+ */
+ core.Node.prototype.contains = function (other) {
+ return other instanceof core.Node &&
+ (this === other || this.compareDocumentPosition(other) & DOCUMENT_POSITION_CONTAINED_BY);
+ };
+
+ // http://dom.spec.whatwg.org/#dom-node-parentelement
+ defineGetter(core.Node.prototype, "parentElement", function () {
+ return this._parentNode !== null && this._parentNode.nodeType === core.Node.ELEMENT_NODE ? this._parentNode : null;
+ });
+
+ function nodeEquals(a, b) {
+ if (a.nodeType !== b.nodeType) {
+ return false;
+ }
+
+ switch (a.nodeType) {
+ case core.Node.DOCUMENT_TYPE_NODE:
+ if (a._name !== b._name || a._publicId !== b._publicId || a._systemId !== b._systemId) {
+ return false;
+ }
+ break;
+ case core.Node.ELEMENT_NODE:
+ if (a._namespaceURI !== b._namespaceURI || a._prefix !== b._prefix || a._localName !== b._localName ||
+ a._attributes.length !== b._attributes.length) {
+ return false;
+ }
+ break;
+ case core.Node.PROCESSING_INSTRUCTION_NODE:
+ if (a._target !== b._target || a._data !== b._data) {
+ return false;
+ }
+ break;
+ case core.Node.TEXT_NODE:
+ case core.Node.COMMENT_NODE:
+ if (a._data !== b._data) {
+ return false;
+ }
+ break;
+ }
+
+ if (a.nodeType === core.Node.ELEMENT_NODE) {
+ for (var i = 0; i < a._attributes.length; ++i) {
+ var aAttr = a._attributes[i];
+ var bAttr = b._attributes.$getNode(aAttr._namespaceURI, aAttr._localName);
+ if (!bAttr) {
+ return false;
+ }
+
+ // TODO: once Attr is simplified, check the internal property instead of the public getter
+ if (aAttr.value !== bAttr.value) {
+ return false;
+ }
+ }
+ }
+
+ if (a._childNodes.length !== b._childNodes.length) {
+ return false;
+ }
+
+ for (var j = 0; j < a._childNodes.length; ++j) {
+ if (!nodeEquals(a._childNodes[j], b._childNodes[j])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // https://dom.spec.whatwg.org/#dom-node-isequalnode
+ core.Node.prototype.isEqualNode = function (node) {
+ if (node === undefined) {
+ // this is what Node? means in the IDL
+ node = null;
+ }
+
+ if (node === null) {
+ return false;
+ }
+
+ // Fast-path, not in the spec
+ if (this === node) {
+ return true;
+ }
+
+ return nodeEquals(this, node);
+ };
+};
+
+
diff --git a/node_modules/jsdom/lib/jsdom/living/processing-instruction.js b/node_modules/jsdom/lib/jsdom/living/processing-instruction.js
new file mode 100644
index 0000000..ce9c4b7
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/processing-instruction.js
@@ -0,0 +1,15 @@
+"use strict";
+var inheritFrom = require("../utils").inheritFrom;
+
+module.exports = function (core) {
+ core.ProcessingInstruction = function ProcessingInstruction(ownerDocument, target, data) {
+ core.CharacterData.call(this, ownerDocument, data);
+
+ this._target = target;
+ };
+
+ inheritFrom(core.CharacterData, core.ProcessingInstruction, {
+ nodeType: core.Node.PROCESSING_INSTRUCTION_NODE, // TODO should be on prototype, not here
+ get target() { return this._target; }
+ });
+};
diff --git a/node_modules/jsdom/lib/jsdom/living/selectors.js b/node_modules/jsdom/lib/jsdom/living/selectors.js
new file mode 100644
index 0000000..d159f5a
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/selectors.js
@@ -0,0 +1,32 @@
+"use strict";
+var nwmatcher = require("nwmatcher/src/nwmatcher-noqsa");
+var memoizeQuery = require("../utils").memoizeQuery;
+
+module.exports = function (core) {
+ [core.Document, core.DocumentFragment, core.Element].forEach(function (Class) {
+ Class.prototype.querySelector = memoizeQuery(function (selectors) {
+ return addNwmatcher(this).first(String(selectors), this);
+ });
+
+ Class.prototype.querySelectorAll = memoizeQuery(function (selectors) {
+ return new core.NodeList(addNwmatcher(this).select(String(selectors), this));
+ });
+ });
+
+ core.Element.prototype.matches = memoizeQuery(function (selectors) {
+ return addNwmatcher(this).match(this, selectors);
+ });
+};
+
+// nwmatcher gets `document.documentElement` at creation-time, so we have to initialize lazily, since in the initial
+// stages of Document initialization, there is no documentElement present yet.
+function addNwmatcher(parentNode) {
+ var document = parentNode._ownerDocument;
+
+ if (!document._nwmatcher) {
+ document._nwmatcher = nwmatcher({ document: document });
+ document._nwmatcher.configure({ UNIQUE_ID: false });
+ }
+
+ return document._nwmatcher;
+}
diff --git a/node_modules/jsdom/lib/jsdom/living/text.js b/node_modules/jsdom/lib/jsdom/living/text.js
new file mode 100644
index 0000000..ecd8c63
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/text.js
@@ -0,0 +1,41 @@
+"use strict";
+var inheritFrom = require("../utils").inheritFrom;
+
+module.exports = function (core) {
+ // TODO: constructor should not take ownerDocument
+ core.Text = function Text(ownerDocument, data) {
+ core.CharacterData.call(this, ownerDocument, data);
+ };
+
+ inheritFrom(core.CharacterData, core.Text, {
+ nodeType: core.Node.TEXT_NODE, // TODO should be on prototype, not here
+ splitText: function (offset) {
+ offset = offset >>> 0;
+
+ var length = this.length;
+
+ if (offset > length) {
+ throw new core.DOMException(core.DOMException.INDEX_SIZE_ERR);
+ }
+
+ var count = length - offset;
+ var newData = this.substringData(offset, count);
+
+ var newNode = this._ownerDocument.createTextNode(newData);
+
+ var parent = this._parentNode;
+
+ if (parent !== null) {
+ parent.insertBefore(newNode, this.nextSibling);
+ }
+
+ this.replaceData(offset, count, "");
+
+ return newNode;
+
+ // TODO: range stuff
+ },
+
+ // TODO: wholeText property
+ });
+};
diff --git a/node_modules/jsdom/lib/jsdom/utils.js b/node_modules/jsdom/lib/jsdom/utils.js
new file mode 100644
index 0000000..1894611
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/utils.js
@@ -0,0 +1,223 @@
+"use strict";
+var path = require("path");
+var url = require("url");
+
+exports.toFileUrl = function (fileName) {
+ // Beyond just the `path.resolve`, this is mostly for the benefit of Windows,
+ // where we need to convert "\" to "/" and add an extra "/" prefix before the
+ // drive letter.
+ var pathname = path.resolve(process.cwd(), fileName).replace(/\\/g, "/");
+ if (pathname[0] !== "/") {
+ pathname = "/" + pathname;
+ }
+
+ return "file://" + pathname;
+};
+
+/**
+ * Define a setter on an object
+ *
+ * This method replaces any existing setter but leaves getters in place.
+ *
+ * - `object` {Object} the object to define the setter on
+ * - `property` {String} the name of the setter
+ * - `setterFn` {Function} the setter
+ */
+exports.defineSetter = function defineSetter(object, property, setterFn) {
+ var descriptor = Object.getOwnPropertyDescriptor(object, property) || {
+ configurable: true,
+ enumerable: true
+ };
+
+ descriptor.set = setterFn;
+
+ Object.defineProperty(object, property, descriptor);
+};
+
+/**
+ * Define a getter on an object
+ *
+ * This method replaces any existing getter but leaves setters in place.
+ *
+ * - `object` {Object} the object to define the getter on
+ * - `property` {String} the name of the getter
+ * - `getterFn` {Function} the getter
+ */
+exports.defineGetter = function defineGetter(object, property, getterFn) {
+ var descriptor = Object.getOwnPropertyDescriptor(object, property) || {
+ configurable: true,
+ enumerable: true
+ };
+
+ descriptor.get = getterFn;
+
+ Object.defineProperty(object, property, descriptor);
+};
+
+/**
+ * Create an object with the given prototype
+ *
+ * Optionally augment the created object.
+ *
+ * - `prototype` {Object} the created object's prototype
+ * - `[properties]` {Object} properties to attach to the created object
+ */
+exports.createFrom = function createFrom(prototype, properties) {
+ properties = properties || {};
+
+ var descriptors = {};
+ Object.getOwnPropertyNames(properties).forEach(function (name) {
+ descriptors[name] = Object.getOwnPropertyDescriptor(properties, name);
+ });
+
+ return Object.create(prototype, descriptors);
+};
+
+/**
+ * Create an inheritance relationship between two classes
+ *
+ * Optionally augment the inherited prototype.
+ *
+ * - `Superclass` {Function} the inherited class
+ * - `Subclass` {Function} the inheriting class
+ * - `[properties]` {Object} properties to attach to the inherited prototype
+ */
+exports.inheritFrom = function inheritFrom(Superclass, Subclass, properties) {
+ properties = properties || {};
+
+ Object.defineProperty(properties, "constructor", {
+ value: Subclass,
+ writable: true,
+ configurable: true
+ });
+
+ Subclass.prototype = exports.createFrom(Superclass.prototype, properties);
+};
+
+/**
+ * Define a list of constants on a constructor and its .prototype
+ *
+ * - `Constructor` {Function} the constructor to define the constants on
+ * - `propertyMap` {Object} key/value map of properties to define
+ */
+exports.addConstants = function addConstants(Constructor, propertyMap) {
+ for (var property in propertyMap) {
+ var value = propertyMap[property];
+ addConstant(Constructor, property, value);
+ addConstant(Constructor.prototype, property, value);
+ }
+};
+
+function addConstant(object, property, value) {
+ Object.defineProperty(object, property, {
+ configurable: false,
+ enumerable: true,
+ value: value,
+ writable: false
+ });
+}
+
+var memoizeQueryTypeCounter = 0;
+
+/**
+ * Returns a version of a method that memoizes specific types of calls on the object
+ *
+ * - `fn` {Function} the method to be memozied
+ */
+exports.memoizeQuery = function memoizeQuery(fn) {
+ // Only memoize query functions with arity <= 2
+ if (fn.length > 2) {
+ return fn;
+ }
+
+ var type = memoizeQueryTypeCounter++;
+
+ return function () {
+ if (!this._memoizedQueries) {
+ return fn.apply(this, arguments);
+ }
+
+ if (!this._memoizedQueries[type]) {
+ this._memoizedQueries[type] = Object.create(null);
+ }
+
+ var key;
+ if (arguments.length === 1 && typeof arguments[0] === "string") {
+ key = arguments[0];
+ } else if (arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "string") {
+ key = arguments[0] + "::" + arguments[1];
+ } else {
+ return fn.apply(this, arguments);
+ }
+
+ if (!(key in this._memoizedQueries[type])) {
+ this._memoizedQueries[type][key] = fn.apply(this, arguments);
+ }
+ return this._memoizedQueries[type][key];
+ };
+};
+
+/**
+* A slightly-more-compliant version of `url.resolve`, taking care of a few Node bugs.
+*/
+exports.resolveHref = function resolveHref(baseUrl, href) {
+ var about = "about:blank";
+
+ // if we're redirecting to another site on about:blank, just let it through
+ if (href.substring(0, about.length) === about) {
+ return href;
+ }
+
+ // if we have to resolve from about:blank we need a bit of special logic
+ if (baseUrl.substring(0, about.length) === about) {
+ if (href.search(/^([A-Za-z0-9]+):/) === 0) { // we have an absolute url
+ baseUrl = href;
+ href = "";
+ } else if (href[0] === "#") { // we have a location hash on about:blank
+ return baseUrl.split(/#/)[0] + href;
+ } else if (!href) { // we just have some base url
+ return baseUrl;
+ } else { // must be a file url
+ baseUrl = "file://" + href;
+ href = "";
+ }
+ }
+
+ if (baseUrl === resolveHref.memoizedUrl && resolveHref.cache && resolveHref.cache[href]) {
+ return resolveHref.cache[href];
+ }
+
+ // When switching protocols, the path doesn't get canonicalized (i.e. .s and ..s are still left):
+ // https://github.com/joyent/node/issues/5453
+ var intermediate = url.resolve(baseUrl, href);
+
+ // This canonicalizes the path, at the cost of overwriting the hash.
+ var nextStep = url.resolve(intermediate, "#");
+
+ // But it breaks file URLs by removing their colon O_o, so put that back.
+ nextStep = nextStep.replace(/^file:\/\/([a-z])\//i, "file:///$1:/");
+
+ // So, insert the hash back in, if there was one.
+ var parsed = url.parse(intermediate);
+ var fixed = nextStep.slice(0, -1) + (parsed.hash || "");
+
+ // Finally, fix file:/// URLs on Windows, where Node removes their drive letters:
+ // https://github.com/joyent/node/issues/5452
+ if (/^file\:\/\/\/[a-z]\:\//i.test(baseUrl) && /^file\:\/\/\//.test(fixed) &&
+ !/^file\:\/\/\/[a-z]\:\//i.test(fixed)) {
+ fixed = fixed.replace(/^file\:\/\/\//, baseUrl.substring(0, 11));
+ }
+
+ // HORRIBLE HACK: encode \u00E4 correctly just so that we pass
+ // https://github.com/w3c/web-platform-tests/blob/e75f01a689a3481f5c773315c2c2527712cf8c2c/dom/nodes/DOMImplementation-createHTMLDocument.html#L71-L72
+ // Eventually we should replace this with a real URL parser based on the URL standard.
+ fixed = fixed.replace(/\u00E4/, "%C3%A4");
+
+ if (baseUrl !== resolveHref.memoizedUrl) {
+ resolveHref.memoizedUrl = baseUrl;
+ resolveHref.cache = {};
+ }
+ resolveHref.cache[href] = fixed;
+
+ return fixed;
+};
diff --git a/node_modules/jsdom/lib/jsdom/virtual-console.js b/node_modules/jsdom/lib/jsdom/virtual-console.js
new file mode 100644
index 0000000..1cc7723
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/virtual-console.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var EventEmitter = require("events").EventEmitter;
+var utils = require("./utils");
+
+function VirtualConsole() {
+ // If "error" event has no listeners,
+ // EventEmitter throws an exception
+ this.on("error", function () {});
+}
+
+utils.inheritFrom(EventEmitter, VirtualConsole, {
+ sendTo: function (anyConsole) {
+ Object.keys(anyConsole).forEach(function (method) {
+ if (typeof anyConsole[method] === "function") {
+ this.on(method, function () {
+ anyConsole[method].apply(anyConsole, arguments);
+ });
+ }
+ }, this);
+ }
+});
+
+module.exports = VirtualConsole;