diff --git a/build/plotcss.js b/build/plotcss.js
index 169edfce295..556adf30e3b 100644
--- a/build/plotcss.js
+++ b/build/plotcss.js
@@ -1,6 +1,5 @@
'use strict';
-var Plotly = require('../src/plotly');
var rules = {
"X,X div": "font-family:'Open Sans', verdana, arial, sans-serif;margin:0;padding:0;",
"X input,X button": "font-family:'Open Sans', verdana, arial, sans-serif;",
@@ -54,9 +53,4 @@ var rules = {
"Y .notifier-close:hover": "color:#444;text-decoration:none;cursor:pointer;"
};
-for(var selector in rules) {
- var fullSelector = selector.replace(/^,/,' ,')
- .replace(/X/g, '.js-plotly-plot .plotly')
- .replace(/Y/g, '.plotly-notifier');
- Plotly.Lib.addStyleRule(fullSelector, rules[selector]);
-}
+module.exports = rules;
diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js
index a57a0038248..44a7687fa62 100644
--- a/src/components/dragelement/index.js
+++ b/src/components/dragelement/index.js
@@ -86,7 +86,7 @@ dragElement.init = function init(options) {
if(options.prepFn) options.prepFn(e, startX, startY);
- dragCover = coverSlip();
+ dragCover = coverSlip(gd);
dragCover.onmousemove = onMove;
dragCover.onmouseup = onDone;
@@ -139,7 +139,7 @@ dragElement.init = function init(options) {
if(options.doneFn) options.doneFn(gd._dragged, numClicks);
if(!gd._dragged) {
- var e2 = document.createEvent('MouseEvents');
+ var e2 = gd._document.createEvent('MouseEvents');
e2.initEvent('click', true, true);
initialTarget.dispatchEvent(e2);
}
@@ -159,8 +159,8 @@ dragElement.init = function init(options) {
options.element.style.pointerEvents = 'all';
};
-function coverSlip() {
- var cover = document.createElement('div');
+function coverSlip(gd) {
+ var cover = gd._document.createElement('div');
cover.className = 'dragcover';
var cStyle = cover.style;
@@ -172,7 +172,7 @@ function coverSlip() {
cStyle.zIndex = 999999999;
cStyle.background = 'none';
- document.body.appendChild(cover);
+ gd._document.body.appendChild(cover);
return cover;
}
diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js
index 23b6ae8a72c..a43279afdef 100644
--- a/src/components/modebar/buttons.js
+++ b/src/components/modebar/buttons.js
@@ -50,19 +50,19 @@ modeBarButtons.toImage = {
click: function(gd) {
var format = 'png';
- Lib.notifier('Taking snapshot - this may take a few seconds', 'long');
+ Lib.notifier(gd, 'Taking snapshot - this may take a few seconds', 'long');
if(Lib.isIE()) {
- Lib.notifier('IE only supports svg. Changing format to svg.', 'long');
+ Lib.notifier(gd, 'IE only supports svg. Changing format to svg.', 'long');
format = 'svg';
}
downloadImage(gd, {'format': format})
.then(function(filename) {
- Lib.notifier('Snapshot succeeded - ' + filename, 'long');
+ Lib.notifier(gd, 'Snapshot succeeded - ' + filename, 'long');
})
.catch(function() {
- Lib.notifier('Sorry there was a problem downloading your snapshot!', 'long');
+ Lib.notifier(gd, 'Sorry there was a problem downloading your snapshot!', 'long');
});
}
};
diff --git a/src/components/rangeslider/create_slider.js b/src/components/rangeslider/create_slider.js
index 83caa2ad7eb..00a9b3d06b1 100644
--- a/src/components/rangeslider/create_slider.js
+++ b/src/components/rangeslider/create_slider.js
@@ -33,7 +33,7 @@ module.exports = function createSlider(gd) {
var minStart = 0,
maxStart = width;
- var slider = document.createElementNS(svgNS, 'g');
+ var slider = gd._document.createElementNS(svgNS, 'g');
helpers.setAttributes(slider, {
'class': 'range-slider',
'data-min': minStart,
@@ -43,7 +43,7 @@ module.exports = function createSlider(gd) {
});
- var sliderBg = document.createElementNS(svgNS, 'rect'),
+ var sliderBg = gd._document.createElementNS(svgNS, 'rect'),
borderCorrect = options.borderwidth % 2 === 0 ? options.borderwidth : options.borderwidth - 1;
helpers.setAttributes(sliderBg, {
'fill': options.bgcolor,
@@ -56,7 +56,7 @@ module.exports = function createSlider(gd) {
});
- var maskMin = document.createElementNS(svgNS, 'rect');
+ var maskMin = gd._document.createElementNS(svgNS, 'rect');
helpers.setAttributes(maskMin, {
'x': 0,
'width': minStart,
@@ -65,7 +65,7 @@ module.exports = function createSlider(gd) {
});
- var maskMax = document.createElementNS(svgNS, 'rect');
+ var maskMax = gd._document.createElementNS(svgNS, 'rect');
helpers.setAttributes(maskMax, {
'x': maxStart,
'width': width - maxStart,
@@ -74,9 +74,9 @@ module.exports = function createSlider(gd) {
});
- var grabberMin = document.createElementNS(svgNS, 'g'),
- grabAreaMin = document.createElementNS(svgNS, 'rect'),
- handleMin = document.createElementNS(svgNS, 'rect');
+ var grabberMin = gd._document.createElementNS(svgNS, 'g'),
+ grabAreaMin = gd._document.createElementNS(svgNS, 'rect'),
+ handleMin = gd._document.createElementNS(svgNS, 'rect');
helpers.setAttributes(grabberMin, { 'transform': 'translate(' + (minStart - handleWidth - 1) + ')' });
helpers.setAttributes(grabAreaMin, {
'width': 10,
@@ -97,9 +97,9 @@ module.exports = function createSlider(gd) {
helpers.appendChildren(grabberMin, [handleMin, grabAreaMin]);
- var grabberMax = document.createElementNS(svgNS, 'g'),
- grabAreaMax = document.createElementNS(svgNS, 'rect'),
- handleMax = document.createElementNS(svgNS, 'rect');
+ var grabberMax = gd._document.createElementNS(svgNS, 'g'),
+ grabAreaMax = gd._document.createElementNS(svgNS, 'rect'),
+ handleMax = gd._document.createElementNS(svgNS, 'rect');
helpers.setAttributes(grabberMax, { 'transform': 'translate(' + maxStart + ')' });
helpers.setAttributes(grabAreaMax, {
'width': 10,
@@ -120,7 +120,7 @@ module.exports = function createSlider(gd) {
helpers.appendChildren(grabberMax, [handleMax, grabAreaMax]);
- var slideBox = document.createElementNS(svgNS, 'rect');
+ var slideBox = gd._document.createElementNS(svgNS, 'rect');
helpers.setAttributes(slideBox, {
'x': minStart,
'width': maxStart - minStart,
@@ -137,8 +137,8 @@ module.exports = function createSlider(gd) {
minVal = slider.getAttribute('data-min'),
maxVal = slider.getAttribute('data-max');
- window.addEventListener('mousemove', mouseMove);
- window.addEventListener('mouseup', mouseUp);
+ gd._document.defaultView.addEventListener('mousemove', mouseMove);
+ gd._document.defaultView.addEventListener('mouseup', mouseUp);
function mouseMove(e) {
var delta = +e.clientX - startX,
@@ -189,8 +189,8 @@ module.exports = function createSlider(gd) {
}
function mouseUp() {
- window.removeEventListener('mousemove', mouseMove);
- window.removeEventListener('mouseup', mouseUp);
+ gd._document.defaultView.removeEventListener('mousemove', mouseMove);
+ gd._document.defaultView.removeEventListener('mouseup', mouseUp);
slider.style.cursor = 'auto';
}
});
@@ -222,8 +222,8 @@ module.exports = function createSlider(gd) {
function setDataRange(dataMin, dataMax) {
- if(window.requestAnimationFrame) {
- window.requestAnimationFrame(function() {
+ if(gd._document.defaultView.requestAnimationFrame) {
+ gd._document.defaultView.requestAnimationFrame(function() {
Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]);
});
} else {
diff --git a/src/css/helpers.js b/src/css/helpers.js
new file mode 100644
index 00000000000..ebf4c6470a0
--- /dev/null
+++ b/src/css/helpers.js
@@ -0,0 +1,38 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+
+'use strict';
+
+// expands a plotcss selector
+exports.buildFullSelector = function buildFullSelector(selector) {
+ var fullSelector = selector.replace(/,/, ', ')
+ .replace(/:after/g, '::after')
+ .replace(/:before/g, '::before')
+ .replace(/X/g, '.js-plotly-plot .plotly')
+ .replace(/Y/g, '.plotly-notifier');
+
+ return fullSelector;
+};
+
+// Gets all the rules currently attached to the document
+exports.getAllRuleSelectors = function getAllRuleSelectors(sourceDocument) {
+ var allSelectors = [];
+
+ for(var i = 0; i < sourceDocument.styleSheets.length; i++) {
+ var styleSheet = sourceDocument.styleSheets[i];
+
+ for(var j = 0; j < styleSheet.cssRules.length; j++) {
+ var cssRule = styleSheet.cssRules[j];
+
+ allSelectors.push(cssRule.selectorText);
+ }
+ }
+
+ return allSelectors;
+};
diff --git a/src/css/plotcss_injector.js b/src/css/plotcss_injector.js
new file mode 100644
index 00000000000..4f4e54051ec
--- /dev/null
+++ b/src/css/plotcss_injector.js
@@ -0,0 +1,52 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+
+'use strict';
+
+var helpers = require('./helpers');
+var lib = require('../lib');
+var plotcss = require('../../build/plotcss');
+
+// Inject styling information into the document containing the graph div
+module.exports = function injectStyles(gd) {
+ // If the graph div has already been styled, bail
+ if(gd._plotCSSLoaded) return;
+
+ var targetSelectors = helpers.getAllRuleSelectors(gd._document);
+ var targetStyleSheet = null;
+
+ if(gd._document.getElementsByTagName('style').length === 0) {
+ var style = gd._document.createElement('style');
+ // WebKit hack :(
+ style.appendChild(gd._document.createTextNode(''));
+ gd._document.head.appendChild(style);
+ targetStyleSheet = style.sheet;
+ }
+ else {
+ // Just grab the first style element to append to
+ targetStyleSheet = gd._document.getElementsByTagName('style')[0].sheet;
+ }
+
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ // Don't duplicate selectors
+ if(targetSelectors.indexOf(fullSelector) === -1) {
+ if(targetStyleSheet.insertRule) {
+ targetStyleSheet.insertRule(fullSelector + '{' + plotcss[selector] + '}', 0);
+ }
+ else if(targetStyleSheet.addRule) {
+ targetStyleSheet.addRule(fullSelector, plotcss[selector], 0);
+ }
+ else lib.warn('injectStyles failed');
+ }
+ }
+
+ gd._plotCSSLoaded = true;
+};
diff --git a/src/lib/index.js b/src/lib/index.js
index 0a3d8bf7854..7a46be4e59c 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -388,30 +388,6 @@ lib.removeElement = function(el) {
if(elParent) elParent.removeChild(el);
};
-/**
- * for dynamically adding style rules
- * makes one stylesheet that contains all rules added
- * by all calls to this function
- */
-lib.addStyleRule = function(selector, styleString) {
- if(!lib.styleSheet) {
- var style = document.createElement('style');
- // WebKit hack :(
- style.appendChild(document.createTextNode(''));
- document.head.appendChild(style);
- lib.styleSheet = style.sheet;
- }
- var styleSheet = lib.styleSheet;
-
- if(styleSheet.insertRule) {
- styleSheet.insertRule(selector + '{' + styleString + '}', 0);
- }
- else if(styleSheet.addRule) {
- styleSheet.addRule(selector, styleString, 0);
- }
- else lib.warn('addStyleRule failed');
-};
-
lib.getTranslate = function(element) {
var re = /.*\btranslate\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/,
diff --git a/src/lib/notifier.js b/src/lib/notifier.js
index a1bfbfcc14f..ae6a741783f 100644
--- a/src/lib/notifier.js
+++ b/src/lib/notifier.js
@@ -16,12 +16,13 @@ var NOTEDATA = [];
/**
* notifier
+ * @param {object} gd figure Object
* @param {String} text The person's user name
* @param {Number} [delay=1000] The delay time in milliseconds
* or 'long' which provides 2000 ms delay time.
* @return {undefined} this function does not return a value
*/
-module.exports = function(text, displayLength) {
+module.exports = function(gd, text, displayLength) {
if(NOTEDATA.indexOf(text) !== -1) return;
NOTEDATA.push(text);
@@ -30,7 +31,7 @@ module.exports = function(text, displayLength) {
if(isNumeric(displayLength)) ts = displayLength;
else if(displayLength === 'long') ts = 3000;
- var notifierContainer = d3.select('body')
+ var notifierContainer = d3.select(gd._document.body)
.selectAll('.plotly-notifier')
.data([0]);
notifierContainer.enter()
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 3cf9366e167..24f981a5abf 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -18,6 +18,8 @@ var Lib = require('../lib');
var Events = require('../lib/events');
var Queue = require('../lib/queue');
+var injectStyles = require('../css/plotcss_injector');
+
var Plots = require('../plots/plots');
var Fx = require('../plots/cartesian/graph_interact');
@@ -54,6 +56,14 @@ Plotly.plot = function(gd, data, layout, config) {
gd = getGraphDiv(gd);
+ // Get the document the graph div lives in, so we can make sure things like
+ // drag covers are attached to the correct document
+ gd._document = gd.ownerDocument || window.document;
+
+ // Inject the plot styles into the document where we're plotting, bails if
+ // already styled
+ injectStyles(gd);
+
// Events.init is idempotent and bails early if gd has already been init'd
Events.init(gd);
@@ -2541,12 +2551,12 @@ function plotAutoSize(gd, aobj) {
// embedded in an iframe - just take the full iframe size
// if we get to this point, with no aspect ratio restrictions
if(gd._context.fillFrame) {
- newWidth = window.innerWidth;
- newHeight = window.innerHeight;
+ newWidth = gd._document.defaultView.innerWidth;
+ newHeight = gd._document.defaultView.innerHeight;
// somehow we get a few extra px height sometimes...
// just hide it
- document.body.style.overflow = 'hidden';
+ gd._document.body.style.overflow = 'hidden';
}
else if(isNumeric(context.frameMargins) && context.frameMargins > 0) {
var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins),
@@ -2563,7 +2573,7 @@ function plotAutoSize(gd, aobj) {
// provide height and width for the container div,
// specify size in layout, or take the defaults,
// but don't enforce any ratio restrictions
- computedStyle = window.getComputedStyle(gd);
+ computedStyle = gd._document.defaultView.getComputedStyle(gd);
newHeight = parseFloat(computedStyle.height) || fullLayout.height;
newWidth = parseFloat(computedStyle.width) || fullLayout.width;
}
diff --git a/src/plotly.js b/src/plotly.js
index 5ceb2019839..491c0ed5a77 100644
--- a/src/plotly.js
+++ b/src/plotly.js
@@ -26,9 +26,6 @@ exports.Lib = require('./lib');
exports.util = require('./lib/svg_text_utils');
exports.Queue = require('./lib/queue');
-// plot css
-require('../build/plotcss');
-
// configuration
exports.MathJaxConfig = require('./fonts/mathjax_config');
exports.defaultConfig = require('./plot_api/plot_config');
diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js
index ff6f7bc52bf..8e99adb1ab0 100644
--- a/src/plots/cartesian/dragbox.js
+++ b/src/plots/cartesian/dragbox.js
@@ -305,7 +305,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
dragTail(zoomMode);
if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
- Lib.notifier('Double-click to
zoom back out', 'long');
+ Lib.notifier(gd, 'Double-click to
zoom back out', 'long');
SHOWZOOMOUTTIP = false;
}
}
diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js
index 565c4ce53b3..5f34cde15e9 100644
--- a/src/plots/cartesian/set_convert.js
+++ b/src/plots/cartesian/set_convert.js
@@ -114,6 +114,7 @@ module.exports = function setConvert(ax) {
if(!isFinite(ax._m) || !isFinite(ax._b)) {
Lib.notifier(
+ ax._gd,
'Something went wrong with axis scaling',
'long');
ax._gd._replotting = false;
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 4b814bfd85c..307c4d38547 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -809,6 +809,10 @@ plots.purge = function(gd) {
// remove modebar
if(fullLayout._modeBar) fullLayout._modeBar.destroy();
+ // styling
+ delete gd._document;
+ delete gd._plotCSSLoaded;
+
// data and layout
delete gd.data;
delete gd.layout;
diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js
index 1fd6d7094f7..9a93f376205 100644
--- a/src/plots/ternary/ternary.js
+++ b/src/plots/ternary/ternary.js
@@ -570,7 +570,7 @@ proto.initInteractions = function() {
Plotly.relayout(gd, attrs);
if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
- Lib.notifier('Double-click to
zoom back out', 'long');
+ Lib.notifier(gd, 'Double-click to
zoom back out', 'long');
SHOWZOOMOUTTIP = false;
}
}
diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js
index e8611ccc82e..828c3288db9 100644
--- a/src/traces/heatmap/calc.js
+++ b/src/traces/heatmap/calc.js
@@ -73,7 +73,7 @@ module.exports = function calc(gd, trace) {
function noZsmooth(msg) {
zsmooth = trace._input.zsmooth = trace.zsmooth = false;
- Lib.notifier('cannot fast-zsmooth: ' + msg);
+ Lib.notifier(gd, 'cannot fast-zsmooth: ' + msg);
}
// check whether we really can smooth (ie all boxes are about the same size)
diff --git a/tasks/util/pull_css.js b/tasks/util/pull_css.js
index 1f3cb6def53..ff5fefea671 100644
--- a/tasks/util/pull_css.js
+++ b/tasks/util/pull_css.js
@@ -38,15 +38,9 @@ module.exports = function pullCSS(data, pathOut) {
var outStr = [
'\'use strict\';',
'',
- 'var Plotly = require(\'../src/plotly\');',
'var rules = ' + rulesStr + ';',
'',
- 'for(var selector in rules) {',
- ' var fullSelector = selector.replace(/^,/,\' ,\')',
- ' .replace(/X/g, \'.js-plotly-plot .plotly\')',
- ' .replace(/Y/g, \'.plotly-notifier\');',
- ' Plotly.Lib.addStyleRule(fullSelector, rules[selector]);',
- '}',
+ 'module.exports = rules;',
''
].join('\n');
diff --git a/test/jasmine/tests/dragelement_test.js b/test/jasmine/tests/dragelement_test.js
index 924f7f3bcaf..ad6abd29eb1 100644
--- a/test/jasmine/tests/dragelement_test.js
+++ b/test/jasmine/tests/dragelement_test.js
@@ -15,6 +15,7 @@ describe('dragElement', function() {
this.element = document.createElement('div');
this.gd.className = 'js-plotly-plot';
+ this.gd._document = document;
this.gd._fullLayout = {
_hoverlayer: d3.select(this.hoverlayer)
};
diff --git a/test/jasmine/tests/plot_css_test.js b/test/jasmine/tests/plot_css_test.js
new file mode 100644
index 00000000000..204d420f100
--- /dev/null
+++ b/test/jasmine/tests/plot_css_test.js
@@ -0,0 +1,152 @@
+var Plotly = require('@lib/index');
+
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+
+describe('css injection', function() {
+ var helpers = require('@src/css/helpers');
+ var plotcss = require('@build/plotcss');
+
+ // create a graph div in a child window
+ function createGraphDivInChildWindow() {
+ var childWindow = window.open('about:blank', 'popoutWindow', '');
+
+ var gd = childWindow.document.createElement('div');
+ gd.id = 'graph';
+ childWindow.document.body.appendChild(gd);
+
+ // force the graph to be at position 0,0 no matter what
+ gd.style.position = 'fixed';
+ gd.style.left = 0;
+ gd.style.top = 0;
+
+ return gd;
+ }
+
+ // the most basic of basic plots
+ function plot(target) {
+ Plotly.plot(target, [{
+ x: [1, 2, 3, 4, 5],
+ y: [1, 2, 4, 8, 16]
+ }], {
+ margin: {
+ t: 0
+ }
+ });
+ }
+
+ // deletes all rules defined in plotcss
+ function deletePlotCSSRules(sourceDocument) {
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ for(var i = 0; i < sourceDocument.styleSheets.length; i++) {
+ var styleSheet = sourceDocument.styleSheets[i];
+ var selectors = [];
+
+ for(var j = 0; j < styleSheet.cssRules.length; j++) {
+ var cssRule = styleSheet.cssRules[j];
+
+ selectors.push(cssRule.selectorText);
+ }
+
+ var selectorIndex = selectors.indexOf(fullSelector);
+
+ if(selectorIndex !== -1) {
+ styleSheet.deleteRule(selectorIndex);
+ break;
+ }
+ }
+ }
+ }
+
+ it('inserts styles on initial plot', function() {
+ deletePlotCSSRules(document); // clear the rules
+
+ // fix scope errors
+ var selector = null;
+ var fullSelector = null;
+
+ // make sure the rules are cleared
+ var allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(selector in plotcss) {
+ fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).toEqual(-1);
+ }
+
+ // plot
+ var gd = createGraphDiv();
+ plot(gd);
+
+ // check for styles
+ allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(selector in plotcss) {
+ fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1);
+ }
+
+ // clean up
+ destroyGraphDiv();
+ });
+
+ it('inserts styles in a child window document', function() {
+ var gd = createGraphDivInChildWindow();
+ var childWindow = gd.ownerDocument.defaultView;
+
+ // plot
+ plot(gd);
+
+ // check for styles
+ var allSelectors = helpers.getAllRuleSelectors(gd.ownerDocument);
+
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1);
+ }
+
+ // clean up
+ childWindow.close();
+ });
+
+ it('does not insert duplicate styles', function() {
+ deletePlotCSSRules(document); // clear the rules
+
+ // fix scope errors
+ var selector = null;
+ var fullSelector = null;
+
+ // make sure the rules are cleared
+ var allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(selector in plotcss) {
+ fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).toEqual(-1);
+ }
+
+ // plot
+ var gd = createGraphDiv();
+ plot(gd);
+ plot(gd); // plot again so injectStyles gets called again
+
+ // check for styles
+ allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(selector in plotcss) {
+ fullSelector = helpers.buildFullSelector(selector);
+
+ var firstIndex = allSelectors.indexOf(fullSelector);
+
+ // there should be no occurences after the initial one
+ expect(allSelectors.indexOf(fullSelector, firstIndex + 1)).toEqual(-1);
+ }
+
+ // clean up
+ destroyGraphDiv();
+ });
+});
diff --git a/test/jasmine/tests/plot_interact_test.js b/test/jasmine/tests/plot_interact_test.js
index aa9df30fc62..68ffdc4ee95 100644
--- a/test/jasmine/tests/plot_interact_test.js
+++ b/test/jasmine/tests/plot_interact_test.js
@@ -625,3 +625,144 @@ describe('plot svg clip paths', function() {
});
});
});
+
+describe('css injection', function() {
+ var helpers = require('../../../src/css/helpers');
+ var plotcss = require('../../../build/plotcss')
+
+ // create a graph div in a child window
+ function createGraphDivInChildWindow() {
+ var childWindow = window.open('about:blank', 'popoutWindow', '');
+
+ var gd = childWindow.document.createElement('div');
+ gd.id = 'graph';
+ childWindow.document.body.appendChild(gd);
+
+ // force the graph to be at position 0,0 no matter what
+ gd.style.position = 'fixed';
+ gd.style.left = 0;
+ gd.style.top = 0;
+
+ return gd;
+ }
+
+ // the most basic of basic plots
+ function plot(target) {
+ Plotly.plot(target, [{
+ x: [1, 2, 3, 4, 5],
+ y: [1, 2, 4, 8, 16]
+ }], {
+ margin: {
+ t: 0
+ }
+ });
+ }
+
+ // deletes all rules defined in plotcss
+ function deletePlotCSSRules(sourceDocument) {
+ for(var selector in plotcss) {
+ var ruleDeleted = false;
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ for(var i = 0; i < sourceDocument.styleSheets.length; i++) {
+ var styleSheet = sourceDocument.styleSheets[i];
+ var selectors = []
+
+ for(var j = 0; j < styleSheet.cssRules.length; j++) {
+ var cssRule = styleSheet.cssRules[j];
+
+ selectors.push(cssRule.selectorText);
+ }
+
+ var selectorIndex = selectors.indexOf(fullSelector);
+
+ if(selectorIndex !== -1) {
+ styleSheet.deleteRule(selectorIndex);
+ break;
+ }
+ }
+ }
+ }
+
+ it('inserts styles on initial plot', function() {
+ deletePlotCSSRules(document); // clear the rules
+
+ // make sure the rules are clared
+ var allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).toEqual(-1);
+ }
+
+ // plot
+ var gd = createGraphDiv();
+ plot(gd);
+
+ // check for styles
+ allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1);
+ }
+
+ // clean up
+ destroyGraphDiv();
+ });
+
+ it('inserts styles in a child window document', function() {
+ var gd = createGraphDivInChildWindow();
+ var childWindow = gd.ownerDocument.defaultView;
+
+ // plot
+ plot(gd);
+
+ // check for styles
+ allSelectors = helpers.getAllRuleSelectors(gd.ownerDocument);
+
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1);
+ }
+
+ // clean up
+ childWindow.close();
+ });
+
+ it('does not insert duplicate styles', function() {
+ deletePlotCSSRules(document); // clear the rules
+
+ // make sure the rules are clared
+ var allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ expect(allSelectors.indexOf(fullSelector)).toEqual(-1);
+ }
+
+ // plot
+ var gd = createGraphDiv();
+ plot(gd);
+ plot(gd); // plot again so injectStyles gets called again
+
+ // check for styles
+ allSelectors = helpers.getAllRuleSelectors(document);
+
+ for(var selector in plotcss) {
+ var fullSelector = helpers.buildFullSelector(selector);
+
+ var firstIndex = allSelectors.indexOf(fullSelector);
+
+ // there should be no occurences after the initial one
+ expect(allSelectors.indexOf(fullSelector, firstIndex + 1)).toEqual(-1);
+ }
+
+ // clean up
+ destroyGraphDiv();
+ });
+});