Skip to content

Commit b88abdb

Browse files
authoredJun 6, 2018
Merge pull request #2697 from plotly/fix-no-webgl-msg
Make all gl traces show "no webgl message" and fail gracefully
2 parents 387db82 + fef3980 commit b88abdb

File tree

10 files changed

+197
-23
lines changed

10 files changed

+197
-23
lines changed
 

‎src/lib/prepare_regl.js

+23-9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
'use strict';
1010

11+
var showNoWebGlMsg = require('./show_no_webgl_msg');
12+
1113
// Note that this module should be ONLY required into
1214
// files corresponding to regl trace modules
1315
// so that bundles with non-regl only don't include
@@ -21,23 +23,35 @@ var createRegl = require('regl');
2123
*
2224
* @param {DOM node or object} gd : graph div object
2325
* @param {array} extensions : list of extension to pass to createRegl
26+
*
27+
* @return {boolean} true if all createRegl calls succeeded, false otherwise
2428
*/
2529
module.exports = function prepareRegl(gd, extensions) {
2630
var fullLayout = gd._fullLayout;
31+
var success = true;
2732

2833
fullLayout._glcanvas.each(function(d) {
2934
if(d.regl) return;
3035
// only parcoords needs pick layer
3136
if(d.pick && !fullLayout._has('parcoords')) return;
3237

33-
d.regl = createRegl({
34-
canvas: this,
35-
attributes: {
36-
antialias: !d.pick,
37-
preserveDrawingBuffer: true
38-
},
39-
pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio,
40-
extensions: extensions || []
41-
});
38+
try {
39+
d.regl = createRegl({
40+
canvas: this,
41+
attributes: {
42+
antialias: !d.pick,
43+
preserveDrawingBuffer: true
44+
},
45+
pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio,
46+
extensions: extensions || []
47+
});
48+
} catch(e) {
49+
success = false;
50+
}
4251
});
52+
53+
if(!success) {
54+
showNoWebGlMsg({container: fullLayout._glcontainer.node()});
55+
}
56+
return success;
4357
};

‎src/lib/show_no_webgl_msg.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var noop = function() {};
2121
* Expects 'scene' to have property 'container'
2222
*
2323
*/
24-
module.exports = function showWebGlMsg(scene) {
24+
module.exports = function showNoWebGlMsg(scene) {
2525
for(var prop in scene) {
2626
if(typeof scene[prop] === 'function') scene[prop] = noop;
2727
}
@@ -31,11 +31,26 @@ module.exports = function showWebGlMsg(scene) {
3131
};
3232

3333
var div = document.createElement('div');
34-
div.textContent = 'Webgl is not supported by your browser - visit https://get.webgl.org for more info';
34+
div.className = 'no-webgl';
3535
div.style.cursor = 'pointer';
3636
div.style.fontSize = '24px';
3737
div.style.color = Color.defaults[0];
38-
38+
div.style.position = 'absolute';
39+
div.style.left = div.style.top = '0px';
40+
div.style.width = div.style.height = '100%';
41+
div.style['background-color'] = Color.lightLine;
42+
div.style['z-index'] = 30;
43+
44+
var p = document.createElement('p');
45+
p.textContent = 'WebGL is not supported by your browser - visit https://get.webgl.org for more info';
46+
p.style.position = 'relative';
47+
p.style.top = '50%';
48+
p.style.left = '50%';
49+
p.style.height = '30%';
50+
p.style.width = '50%';
51+
p.style.margin = '-15% 0 0 -25%';
52+
53+
div.appendChild(p);
3954
scene.container.appendChild(div);
4055
scene.container.style.background = '#FFFFFF';
4156
scene.container.onclick = function() {

‎src/plots/gl2d/scene2d.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function Scene2D(options, fullLayout) {
4545
this.updateRefs(fullLayout);
4646

4747
this.makeFramework();
48+
if(this.stopped) return;
4849

4950
// update options
5051
this.glplotOptions = createOptions(this);
@@ -121,7 +122,11 @@ proto.makeFramework = function() {
121122
premultipliedAlpha: true
122123
});
123124

124-
if(!gl) showNoWebGlMsg(this);
125+
if(!gl) {
126+
showNoWebGlMsg(this);
127+
this.stopped = true;
128+
return;
129+
}
125130

126131
this.canvas = liveCanvas;
127132
this.gl = gl;

‎src/plots/gl3d/scene.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) {
204204
* The destroy method - which will remove the container from the DOM
205205
* is overridden with a function that removes the container only.
206206
*/
207-
showNoWebGlMsg(scene);
207+
return showNoWebGlMsg(scene);
208208
}
209209

210210
var relayoutCallback = function(scene) {

‎src/plots/plots.js

+1
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,7 @@ plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayou
725725
if(hadGl && !hasGl) {
726726
if(oldFullLayout._glcontainer !== undefined) {
727727
oldFullLayout._glcontainer.selectAll('.gl-canvas').remove();
728+
oldFullLayout._glcontainer.selectAll('.no-webgl').remove();
728729
oldFullLayout._glcanvas = null;
729730
}
730731
}

‎src/traces/parcoords/plot.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ module.exports = function plot(gd, cdparcoords) {
1717
var root = fullLayout._paperdiv;
1818
var container = fullLayout._glcontainer;
1919

20-
prepareRegl(gd);
20+
var success = prepareRegl(gd);
21+
if(!success) return;
2122

2223
var gdDimensions = {};
2324
var gdDimensionsOriginalOrder = {};

‎src/traces/scattergl/index.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ function sceneUpdate(gd, subplot) {
166166
var scene = subplot._scene;
167167
var fullLayout = gd._fullLayout;
168168

169-
var reset = {
169+
var resetOpts = {
170170
// number of traces in subplot, since scene:subplot → 1:1
171171
count: 0,
172172
// whether scene requires init hook in plot call (dirty plot call)
@@ -181,7 +181,7 @@ function sceneUpdate(gd, subplot) {
181181
errorYOptions: []
182182
};
183183

184-
var first = {
184+
var initOpts = {
185185
selectBatch: null,
186186
unselectBatch: null,
187187
// regl- component stubs, initialized in dirty plot call
@@ -193,7 +193,13 @@ function sceneUpdate(gd, subplot) {
193193
};
194194

195195
if(!subplot._scene) {
196-
scene = subplot._scene = Lib.extendFlat({}, reset, first);
196+
scene = subplot._scene = {};
197+
198+
scene.init = function init() {
199+
Lib.extendFlat(scene, initOpts, resetOpts);
200+
};
201+
202+
scene.init();
197203

198204
// apply new option to all regl components (used on drag)
199205
scene.update = function update(opt) {
@@ -306,7 +312,7 @@ function sceneUpdate(gd, subplot) {
306312

307313
// In case if we have scene from the last calc - reset data
308314
if(!scene.dirty) {
309-
Lib.extendFlat(scene, reset);
315+
Lib.extendFlat(scene, resetOpts);
310316
}
311317

312318
return scene;
@@ -326,7 +332,12 @@ function plot(gd, subplot, cdata) {
326332
var width = fullLayout.width;
327333
var height = fullLayout.height;
328334

329-
prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']);
335+
var success = prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']);
336+
if(!success) {
337+
scene.init();
338+
return;
339+
}
340+
330341
var regl = fullLayout._glcanvas.data()[0].regl;
331342

332343
// that is needed for fills

‎src/traces/splom/base_plot.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ function plot(gd) {
2424
var _module = Registry.getModule(SPLOM);
2525
var splomCalcData = getModuleCalcData(gd.calcdata, _module)[0];
2626

27-
prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']);
27+
var success = prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']);
28+
if(!success) return;
2829

2930
if(fullLayout._hasOnlyLargeSploms) {
3031
drawGrid(gd);
@@ -209,7 +210,10 @@ function clean(newFullData, newFullLayout, oldFullData, oldFullLayout, oldCalcda
209210
var trace = cd0.trace;
210211
var scene = cd0.t._scene;
211212

212-
if(trace.type === 'splom' && scene && scene.matrix) {
213+
if(
214+
trace.type === 'splom' &&
215+
scene && scene.matrix && scene.matrix.destroy
216+
) {
213217
scene.matrix.destroy();
214218
cd0.t._scene = null;
215219
}
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
var Plotly = require('@lib');
2+
3+
var createGraphDiv = require('../assets/create_graph_div');
4+
var destroyGraphDiv = require('../assets/destroy_graph_div');
5+
var failTest = require('../assets/fail_test');
6+
7+
describe('Plotly w/o WebGL support:', function() {
8+
var gd;
9+
10+
beforeEach(function() {
11+
gd = createGraphDiv();
12+
});
13+
14+
afterEach(function() {
15+
Plotly.purge(gd);
16+
destroyGraphDiv();
17+
});
18+
19+
function checkNoWebGLMsg(visible) {
20+
var msg = gd.querySelector('div.no-webgl > p');
21+
if(visible) {
22+
expect(msg.innerHTML.substr(0, 22)).toBe('WebGL is not supported');
23+
} else {
24+
expect(msg).toBe(null);
25+
}
26+
}
27+
28+
it('gl3d subplots', function(done) {
29+
Plotly.react(gd, require('@mocks/gl3d_autocolorscale.json'))
30+
.then(function() {
31+
checkNoWebGLMsg(true);
32+
return Plotly.react(gd, require('@mocks/10.json'));
33+
})
34+
.then(function() {
35+
checkNoWebGLMsg(false);
36+
})
37+
.catch(failTest)
38+
.then(done);
39+
});
40+
41+
it('gl2d subplots', function(done) {
42+
Plotly.react(gd, require('@mocks/gl2d_pointcloud-basic.json'))
43+
.then(function() {
44+
checkNoWebGLMsg(true);
45+
return Plotly.react(gd, require('@mocks/10.json'));
46+
})
47+
.then(function() {
48+
checkNoWebGLMsg(false);
49+
})
50+
.catch(failTest)
51+
.then(done);
52+
});
53+
54+
it('scattergl subplots', function(done) {
55+
Plotly.react(gd, require('@mocks/gl2d_12.json'))
56+
.then(function() {
57+
checkNoWebGLMsg(true);
58+
return Plotly.react(gd, require('@mocks/10.json'));
59+
})
60+
.then(function() {
61+
checkNoWebGLMsg(false);
62+
63+
// one with all regl2d modules
64+
return Plotly.react(gd, [{
65+
type: 'scattergl',
66+
mode: 'lines+markers',
67+
fill: 'tozerox',
68+
y: [1, 2, 1],
69+
error_x: { value: 10 },
70+
error_y: { value: 10 }
71+
}]);
72+
})
73+
.then(function() {
74+
checkNoWebGLMsg(true);
75+
return Plotly.react(gd, require('@mocks/10.json'));
76+
})
77+
.then(function() {
78+
checkNoWebGLMsg(false);
79+
})
80+
.catch(failTest)
81+
.then(done);
82+
});
83+
84+
it('scatterpolargl subplots', function(done) {
85+
Plotly.react(gd, require('@mocks/glpolar_scatter.json'))
86+
.then(function() {
87+
checkNoWebGLMsg(true);
88+
return Plotly.react(gd, require('@mocks/10.json'));
89+
})
90+
.then(function() {
91+
checkNoWebGLMsg(false);
92+
})
93+
.catch(failTest)
94+
.then(done);
95+
});
96+
97+
it('splom subplots', function(done) {
98+
Plotly.react(gd, require('@mocks/splom_0.json'))
99+
.then(function() {
100+
checkNoWebGLMsg(true);
101+
return Plotly.react(gd, require('@mocks/10.json'));
102+
})
103+
.then(function() {
104+
checkNoWebGLMsg(false);
105+
})
106+
.catch(failTest)
107+
.then(done);
108+
});
109+
110+
it('parcoords subplots', function(done) {
111+
Plotly.react(gd, require('@mocks/gl2d_parcoords_2.json'))
112+
.then(function() {
113+
checkNoWebGLMsg(true);
114+
return Plotly.react(gd, require('@mocks/10.json'));
115+
})
116+
.then(function() {
117+
checkNoWebGLMsg(false);
118+
})
119+
.catch(failTest)
120+
.then(done);
121+
});
122+
});

‎test/jasmine/karma.conf.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ func.defaultConfig = {
188188
flags: [
189189
'--touch-events',
190190
'--window-size=' + argv.width + ',' + argv.height,
191-
isCI ? '--ignore-gpu-blacklist' : ''
191+
isCI ? '--ignore-gpu-blacklist' : '',
192+
(isBundleTest && basename(testFileGlob) === 'no_webgl') ? '--disable-webgl' : ''
192193
]
193194
},
194195
_Firefox: {

0 commit comments

Comments
 (0)
Please sign in to comment.