Commit 9c3df312 authored by Mike Bostock's avatar Mike Bostock

Demote JSDOM to development dependency; fix #2190.

Code that previously assumed a global document or window now uses the related
node’s ownerDocument or ownerDocument.defaultView as appropriate.

If no related node is available, the corresponding code will crash; however, the
rest of D3 will work just fine. For example, you can’t use d3.select(string)
unless a global document is available; it just doesn’t make sense. Use
d3.select(node) instead, followed by selection.select(string).

Code that previously checked for a global on the window (e.g., XDomainRequest)
now uses the global context (`this`) rather than the window.
parent ca0b7cb5
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
var document = require("jsdom").jsdom(),
globals = {};
var globals = {};
// Stash old globals.
// Stash old global.
if ("d3" in global) globals.d3 = global.d3;
if ("window" in global) globals.window = global.window;
if ("document" in global) globals.document = global.document;
// Set temporary globals to pretend we’re in a browser.
global.window = document.parentWindow;
global.document = document;
module.exports = require("./d3");
// Restore old globals.
// Restore old global.
if ("d3" in globals) global.d3 = globals.d3; else delete global.d3;
if ("window" in globals) global.window = globals.window; else delete global.window;
if ("document" in globals) global.document = globals.document; else delete global.document;
......@@ -50,14 +50,12 @@
"spm": {
"main": "d3.js"
},
"dependencies": {
"jsdom": "1.0.0"
},
"devDependencies": {
"smash": "~0.0.12",
"uglify-js": "2.4.0",
"vows": "0.7.0",
"seedrandom": "2.3.1"
"jsdom": "3",
"seedrandom": "2",
"smash": "0.0",
"uglify-js": "2.4",
"vows": "0.8"
},
"scripts": {
"test": "vows; echo"
......
import "../core/document";
import "../core/identity";
import "../core/rebind";
import "../event/drag";
import "../event/event";
......@@ -9,8 +10,8 @@ import "behavior";
d3.behavior.drag = function() {
var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"),
origin = null,
mousedown = dragstart(d3_noop, d3.mouse, d3_behavior_dragMouseSubject, "mousemove", "mouseup"),
touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_behavior_dragTouchSubject, "touchmove", "touchend");
mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"),
touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");
function drag() {
this.on("mousedown.drag", mousedown)
......@@ -27,8 +28,8 @@ d3.behavior.drag = function() {
dragId = id(),
dragName = ".drag" + (dragId == null ? "" : "-" + dragId),
dragOffset,
dragSubject = d3.select(subject()).on(move + dragName, moved).on(end + dragName, ended),
dragRestore = d3_event_dragSuppress(),
dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended),
dragRestore = d3_event_dragSuppress(target),
position0 = position(parent, dragId);
if (origin) {
......@@ -85,11 +86,3 @@ d3.behavior.drag = function() {
function d3_behavior_dragTouchId() {
return d3.event.changedTouches[0].identifier;
}
function d3_behavior_dragTouchSubject() {
return d3.event.target;
}
function d3_behavior_dragMouseSubject() {
return d3_window;
}
......@@ -29,6 +29,14 @@ d3.behavior.zoom = function() {
y0,
y1;
// Lazily determine the DOM’s support for Wheel events.
// https://developer.mozilla.org/en-US/docs/Mozilla_event_reference/wheel
if (!d3_behavior_zoomWheel) {
d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); }, "wheel")
: "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { return d3.event.wheelDelta; }, "mousewheel")
: (d3_behavior_zoomDelta = function() { return -d3.event.detail; }, "MozMousePixelScroll");
}
function zoom(g) {
g .on(mousedown, mousedowned)
.on(d3_behavior_zoomWheel + ".zoom", mousewheeled)
......@@ -183,9 +191,9 @@ d3.behavior.zoom = function() {
target = d3.event.target,
dispatch = event.of(that, arguments),
dragged = 0,
subject = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended),
subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended),
location0 = location(d3.mouse(that)),
dragRestore = d3_event_dragSuppress();
dragRestore = d3_event_dragSuppress(that);
d3_selection_interrupt.call(that);
zoomstarted(dispatch);
......@@ -215,7 +223,7 @@ d3.behavior.zoom = function() {
touchend = "touchend" + zoomName,
targets = [],
subject = d3.select(that),
dragRestore = d3_event_dragSuppress();
dragRestore = d3_event_dragSuppress(that);
started();
zoomstarted(dispatch);
......@@ -336,10 +344,6 @@ d3.behavior.zoom = function() {
return d3.rebind(zoom, event, "on");
};
var d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent
// https://developer.mozilla.org/en-US/docs/Mozilla_event_reference/wheel
var d3_behavior_zoomDelta, d3_behavior_zoomWheel
= "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); }, "wheel")
: "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { return d3.event.wheelDelta; }, "mousewheel")
: (d3_behavior_zoomDelta = function() { return -d3.event.detail; }, "MozMousePixelScroll");
var d3_behavior_zoomInfinity = [0, Infinity], // default scale extent
d3_behavior_zoomDelta, // initialized lazily
d3_behavior_zoomWheel;
import "../core/array";
import "../core/document";
// Redefine d3_array if the browser doesn’t support slice-based conversion.
if (d3_document) {
try {
d3_array(d3_document.documentElement.childNodes)[0].nodeType;
} catch (e) {
d3_array = function(list) {
var i = list.length, array = new Array(i);
while (i--) array[i] = list[i];
return array;
};
}
}
import "array";
import "date";
import "style";
import "../core/document";
try {
d3_document.createElement("div").style.setProperty("opacity", 0, "");
} catch (error) {
var d3_element_prototype = d3_window.Element.prototype,
d3_element_setAttribute = d3_element_prototype.setAttribute,
d3_element_setAttributeNS = d3_element_prototype.setAttributeNS,
d3_style_prototype = d3_window.CSSStyleDeclaration.prototype,
d3_style_setProperty = d3_style_prototype.setProperty;
d3_element_prototype.setAttribute = function(name, value) {
d3_element_setAttribute.call(this, name, value + "");
};
d3_element_prototype.setAttributeNS = function(space, local, value) {
d3_element_setAttributeNS.call(this, space, local, value + "");
};
d3_style_prototype.setProperty = function(name, value, priority) {
d3_style_setProperty.call(this, name, value + "", priority);
};
// Redefine style.setProperty et al. if the browser doesn’t coerce arguments.
if (d3_document) {
try {
d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
} catch (error) {
var d3_element_prototype = this.Element.prototype,
d3_element_setAttribute = d3_element_prototype.setAttribute,
d3_element_setAttributeNS = d3_element_prototype.setAttributeNS,
d3_style_prototype = this.CSSStyleDeclaration.prototype,
d3_style_setProperty = d3_style_prototype.setProperty;
d3_element_prototype.setAttribute = function(name, value) {
d3_element_setAttribute.call(this, name, value + "");
};
d3_element_prototype.setAttributeNS = function(space, local, value) {
d3_element_setAttributeNS.call(this, space, local, value + "");
};
d3_style_prototype.setProperty = function(name, value, priority) {
d3_style_setProperty.call(this, name, value + "", priority);
};
}
}
import "array";
var d3_document = this.document;
var d3_document = document,
d3_documentElement = d3_document.documentElement,
d3_window = window;
function d3_documentElement(node) {
return node && node.ownerDocument.documentElement;
}
// Redefine d3_array if the browser doesn’t support slice-based conversion.
try {
d3_array(d3_documentElement.childNodes)[0].nodeType;
} catch(e) {
d3_array = function(list) {
var i = list.length, array = new Array(i);
while (i--) array[i] = list[i];
return array;
};
function d3_window(node) {
return node && node.ownerDocument.defaultView;
}
......@@ -2,21 +2,28 @@ import "../core/document";
import "../core/vendor";
import "../selection/on";
var d3_event_dragSelect = "onselectstart" in d3_document ? null : d3_vendorSymbol(d3_documentElement.style, "userSelect"),
var d3_event_dragSelect,
d3_event_dragId = 0;
function d3_event_dragSuppress() {
function d3_event_dragSuppress(node) {
var name = ".dragsuppress-" + ++d3_event_dragId,
click = "click" + name,
w = d3.select(d3_window)
w = d3.select(d3_window(node))
.on("touchmove" + name, d3_eventPreventDefault)
.on("dragstart" + name, d3_eventPreventDefault)
.on("selectstart" + name, d3_eventPreventDefault);
if (d3_event_dragSelect == null) {
d3_event_dragSelect = "onselectstart" in node ? false
: d3_vendorSymbol(node.style, "userSelect");
}
if (d3_event_dragSelect) {
var style = d3_documentElement.style,
var style = d3_documentElement(node).style,
select = style[d3_event_dragSelect];
style[d3_event_dragSelect] = "none";
}
return function(suppressClick) {
w.on(name, null);
if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
......
......@@ -6,25 +6,28 @@ d3.mouse = function(container) {
};
// https://bugs.webkit.org/show_bug.cgi?id=44083
var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0;
var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
function d3_mousePoint(container, e) {
if (e.changedTouches) e = e.changedTouches[0];
var svg = container.ownerSVGElement || container;
if (svg.createSVGPoint) {
var point = svg.createSVGPoint();
if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) {
svg = d3.select("body").append("svg").style({
position: "absolute",
top: 0,
left: 0,
margin: 0,
padding: 0,
border: "none"
}, "important");
var ctm = svg[0][0].getScreenCTM();
d3_mouse_bug44083 = !(ctm.f || ctm.e);
svg.remove();
if (d3_mouse_bug44083 < 0) {
var window = d3_window(container);
if (window.scrollX || window.scrollY) {
svg = d3.select("body").append("svg").style({
position: "absolute",
top: 0,
left: 0,
margin: 0,
padding: 0,
border: "none"
}, "important");
var ctm = svg[0][0].getScreenCTM();
d3_mouse_bug44083 = !(ctm.f || ctm.e);
svg.remove();
}
}
if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY;
else point.x = e.clientX, point.y = e.clientY;
......
......@@ -6,7 +6,7 @@ var d3_timer_queueHead,
d3_timer_interval, // is an interval (or frame) active?
d3_timer_timeout, // is a timeout active?
d3_timer_active, // active timer object
d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) { setTimeout(callback, 17); };
d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) { setTimeout(callback, 17); };
// The timer will continue to fire until callback returns true.
d3.timer = function(callback, delay, then) {
......
......@@ -76,9 +76,11 @@ var d3_selection_onFilters = d3.map({
mouseleave: "mouseout"
});
d3_selection_onFilters.forEach(function(k) {
if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
});
if (d3_document) {
d3_selection_onFilters.forEach(function(k) {
if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
});
}
function d3_selection_onListener(listener, argumentz) {
return function(e) {
......
......@@ -10,8 +10,13 @@ function d3_selection(groups) {
var d3_select = function(s, n) { return n.querySelector(s); },
d3_selectAll = function(s, n) { return n.querySelectorAll(s); },
d3_selectMatcher = d3_documentElement.matches || d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")],
d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); };
d3_selectMatches = function(n, s) {
var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
d3_selectMatches = function(n, s) {
return d3_selectMatcher.call(n, s);
};
return d3_selectMatches(n, s);
};
// Prefer Sizzle, if available.
if (typeof Sizzle === "function") {
......@@ -21,7 +26,7 @@ if (typeof Sizzle === "function") {
}
d3.selection = function() {
return d3_selectionRoot;
return d3.select(d3_document.documentElement);
};
var d3_selectionPrototype = d3.selection.prototype = [];
......@@ -52,15 +57,25 @@ import "enter";
// TODO fast singleton implementation?
d3.select = function(node) {
var group = [typeof node === "string" ? d3_select(node, d3_document) : node];
group.parentNode = d3_documentElement;
var group;
if (typeof node === "string") {
group = [d3_select(node, d3_document)];
group.parentNode = d3_document.documentElement;
} else {
group = [node];
group.parentNode = d3_documentElement(node);
}
return d3_selection([group]);
};
d3.selectAll = function(nodes) {
var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes);
group.parentNode = d3_documentElement;
var group;
if (typeof nodes === "string") {
group = d3_array(d3_selectAll(nodes, d3_document));
group.parentNode = d3_document.documentElement;
} else {
group = nodes;
group.parentNode = null;
}
return d3_selection([group]);
};
var d3_selectionRoot = d3.select(d3_documentElement);
......@@ -16,7 +16,10 @@ d3_selectionPrototype.style = function(name, value, priority) {
}
// For style(string), return the computed style value for the first node.
if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name);
if (n < 2) {
var node = this.node();
return node.ownerDocument.defaultView.getComputedStyle(node, null).getPropertyValue(name);
}
// For style(string, string) or style(string, function), use the default
// priority. The priority is ignored for style(string, null).
......
......@@ -151,12 +151,12 @@ d3.svg.brush = function() {
resizingX = !/^(n|s)$/.test(resizing) && x,
resizingY = !/^(e|w)$/.test(resizing) && y,
dragging = eventTarget.classed("extent"),
dragRestore = d3_event_dragSuppress(),
dragRestore = d3_event_dragSuppress(target),
center,
origin = d3.mouse(target),
offset;
var w = d3.select(d3_window)
var w = d3.select(d3_window(target))
.on("keydown.brush", keydown)
.on("keyup.brush", keyup);
......
......@@ -33,7 +33,7 @@ d3_transitionPrototype.style = function(name, value, priority) {
// Otherwise, a name, value and priority are specified, and handled as below.
function styleString(b) {
return b == null ? styleNull : (b += "", function() {
var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i;
var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i;
return a !== b && (i = d3_interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); });
});
}
......@@ -45,7 +45,7 @@ d3_transitionPrototype.styleTween = function(name, tween, priority) {
if (arguments.length < 3) priority = "";
function styleTween(d, i) {
var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name));
var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
return f && function(t) { this.style.setProperty(name, f(t), priority); };
}
......
......@@ -30,7 +30,7 @@ d3_transitionPrototype.size = d3_selectionPrototype.size;
d3.transition = function(selection, name) {
return selection && selection.transition
? (d3_transitionInheritId ? selection.transition(name) : selection)
: d3_selectionRoot.transition(selection);
: d3.selection().transition(selection);
};
d3.transition.prototype = d3_transitionPrototype;
......
......@@ -21,7 +21,7 @@ function d3_xhr(url, mimeType, response, callback) {
responseType = null;
// If IE does not support CORS, use XDomainRequest.
if (d3_window.XDomainRequest
if (this.XDomainRequest
&& !("withCredentials" in request)
&& /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest;
......
......@@ -20,7 +20,7 @@ suite.addBatch({
},
"appends an SVG element": function(body) {
var svg = body.append("svg:svg");
assert.equal(svg[0][0].tagName, "SVG");
assert.equal(svg[0][0].tagName, "svg");
assert.equal(svg[0][0].namespaceURI, "http://www.w3.org/2000/svg");
assert.isTrue(svg[0][0].parentNode === body.node());
assert.isTrue(svg[0][0] === body.node().lastChild);
......@@ -68,8 +68,8 @@ suite.addBatch({
"appends an SVG element": function(div) {
var svg = div.append("svg:svg");
assert.equal(svg[0].length, 2);
assert.equal(svg[0][0].tagName, "SVG");
assert.equal(svg[0][1].tagName, "SVG");
assert.equal(svg[0][0].tagName, "svg");
assert.equal(svg[0][1].tagName, "svg");
assert.equal(svg[0][0].namespaceURI, "http://www.w3.org/2000/svg");
assert.equal(svg[0][1].namespaceURI, "http://www.w3.org/2000/svg");
assert.isTrue(svg[0][0].parentNode === div[0][0]);
......
......@@ -35,7 +35,7 @@ suite.addBatch({
},
"appends an SVG element": function(body) {
var svg = body.insert("svg:svg");
assert.equal(svg[0][0].tagName, "SVG");
assert.equal(svg[0][0].tagName, "svg");
assert.equal(svg[0][0].namespaceURI, "http://www.w3.org/2000/svg");
assert.domEqual(svg[0][0].parentNode, body.node());
assert.domEqual(svg[0][0], body.node().lastChild);
......@@ -77,8 +77,8 @@ suite.addBatch({
"appends an SVG element": function(div) {
var svg = div.insert("svg:svg");
assert.equal(svg[0].length, 2);
assert.equal(svg[0][0].tagName, "SVG");
assert.equal(svg[0][1].tagName, "SVG");
assert.equal(svg[0][0].tagName, "svg");
assert.equal(svg[0][1].tagName, "svg");
assert.equal(svg[0][0].namespaceURI, "http://www.w3.org/2000/svg");
assert.equal(svg[0][1].namespaceURI, "http://www.w3.org/2000/svg");
assert.domEqual(svg[0][0].parentNode, div[0][0]);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment