Skip to content

Commit cbbba35

Browse files
authored
Merge pull request #2986 from plotly/webgl-oncontext-loss
Emit 'plotly_webglcontextlost' event on WebGL context lost
2 parents 1fceea8 + 3a38d73 commit cbbba35

File tree

5 files changed

+120
-5
lines changed

5 files changed

+120
-5
lines changed

src/lib/prepare_regl.js

+11
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ module.exports = function prepareRegl(gd, extensions) {
4848
} catch(e) {
4949
success = false;
5050
}
51+
52+
if(success) {
53+
this.addEventListener('webglcontextlost', function(event) {
54+
if(gd && gd.emit) {
55+
gd.emit('plotly_webglcontextlost', {
56+
event: event,
57+
layer: d.key
58+
});
59+
}
60+
}, false);
61+
}
5162
});
5263

5364
if(!success) {

src/plots/gl3d/scene.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ function render(scene) {
170170
}
171171

172172
function initializeGLPlot(scene, fullLayout, canvas, gl) {
173+
var gd = scene.graphDiv;
174+
173175
var glplotOptions = {
174176
canvas: canvas,
175177
gl: gl,
@@ -220,18 +222,22 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) {
220222

221223
var update = {};
222224
update[scene.id + '.camera'] = getLayoutCamera(scene.camera);
223-
scene.saveCamera(scene.graphDiv.layout);
225+
scene.saveCamera(gd.layout);
224226
scene.graphDiv.emit('plotly_relayout', update);
225227
};
226228

227229
scene.glplot.canvas.addEventListener('mouseup', relayoutCallback.bind(null, scene));
228230
scene.glplot.canvas.addEventListener('wheel', relayoutCallback.bind(null, scene), passiveSupported ? {passive: false} : false);
229231

230232
if(!scene.staticMode) {
231-
scene.glplot.canvas.addEventListener('webglcontextlost', function(ev) {
232-
Lib.warn('Lost WebGL context.');
233-
ev.preventDefault();
234-
});
233+
scene.glplot.canvas.addEventListener('webglcontextlost', function(event) {
234+
if(gd && gd.emit) {
235+
gd.emit('plotly_webglcontextlost', {
236+
event: event,
237+
layer: scene.id
238+
});
239+
}
240+
}, false);
235241
}
236242

237243
if(!scene.camera) {

test/jasmine/tests/gl2d_plot_interact_test.js

+47
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,53 @@ describe('Test gl plot side effects', function() {
214214
.catch(failTest)
215215
.then(done);
216216
});
217+
218+
it('@gl should fire *plotly_webglcontextlost* when on webgl context lost', function(done) {
219+
var _mock = Lib.extendDeep({}, require('@mocks/gl2d_12.json'));
220+
221+
function _trigger(name) {
222+
var ev = new window.WebGLContextEvent('webglcontextlost');
223+
var canvas = gd.querySelector('.gl-canvas-' + name);
224+
canvas.dispatchEvent(ev);
225+
}
226+
227+
Plotly.plot(gd, _mock).then(function() {
228+
return new Promise(function(resolve, reject) {
229+
gd.once('plotly_webglcontextlost', resolve);
230+
setTimeout(reject, 10);
231+
_trigger('context');
232+
});
233+
})
234+
.then(function(eventData) {
235+
expect((eventData || {}).event).toBeDefined();
236+
expect((eventData || {}).layer).toBe('contextLayer');
237+
})
238+
.then(function() {
239+
return new Promise(function(resolve, reject) {
240+
gd.once('plotly_webglcontextlost', resolve);
241+
setTimeout(reject, 10);
242+
_trigger('focus');
243+
});
244+
})
245+
.then(function(eventData) {
246+
expect((eventData || {}).event).toBeDefined();
247+
expect((eventData || {}).layer).toBe('focusLayer');
248+
})
249+
.then(function() {
250+
return new Promise(function(resolve, reject) {
251+
gd.once('plotly_webglcontextlost', reject);
252+
setTimeout(resolve, 10);
253+
_trigger('pick');
254+
});
255+
})
256+
.then(function(eventData) {
257+
// should add event listener on pick canvas which
258+
// isn't used for scattergl traces
259+
expect(eventData).toBeUndefined();
260+
})
261+
.catch(failTest)
262+
.then(done);
263+
});
217264
});
218265

219266
describe('Test gl2d plots', function() {

test/jasmine/tests/gl3d_plot_interact_test.js

+21
Original file line numberDiff line numberDiff line change
@@ -1425,4 +1425,25 @@ describe('Test removal of gl contexts', function() {
14251425
})
14261426
.then(done);
14271427
});
1428+
1429+
it('@gl should fire *plotly_webglcontextlost* when on webgl context lost', function(done) {
1430+
var _mock = Lib.extendDeep({}, require('@mocks/gl3d_marker-arrays.json'));
1431+
1432+
Plotly.plot(gd, _mock).then(function() {
1433+
return new Promise(function(resolve, reject) {
1434+
gd.on('plotly_webglcontextlost', resolve);
1435+
setTimeout(reject, 10);
1436+
1437+
var ev = new window.WebGLContextEvent('webglcontextlost');
1438+
var canvas = gd.querySelector('div#scene > canvas');
1439+
canvas.dispatchEvent(ev);
1440+
});
1441+
})
1442+
.then(function(eventData) {
1443+
expect((eventData || {}).event).toBeDefined();
1444+
expect((eventData || {}).layer).toBe('scene');
1445+
})
1446+
.catch(failTest)
1447+
.then(done);
1448+
});
14281449
});

test/jasmine/tests/parcoords_test.js

+30
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,36 @@ describe('parcoords basic use', function() {
10521052
.catch(failTest)
10531053
.then(done);
10541054
});
1055+
1056+
it('@gl should fire *plotly_webglcontextlost* when on webgl context lost', function() {
1057+
var eventData;
1058+
var cnt = 0;
1059+
gd.on('plotly_webglcontextlost', function(d) {
1060+
eventData = d;
1061+
cnt++;
1062+
});
1063+
1064+
function trigger(name) {
1065+
var ev = new window.WebGLContextEvent('webglcontextlost');
1066+
var canvas = gd.querySelector('.gl-canvas-' + name);
1067+
canvas.dispatchEvent(ev);
1068+
}
1069+
1070+
function _assert(d, c) {
1071+
expect((eventData || {}).event).toBeDefined();
1072+
expect((eventData || {}).layer).toBe(d);
1073+
expect(cnt).toBe(c);
1074+
}
1075+
1076+
trigger('context');
1077+
_assert('contextLayer', 1);
1078+
1079+
trigger('focus');
1080+
_assert('focusLayer', 2);
1081+
1082+
trigger('pick');
1083+
_assert('pickLayer', 3);
1084+
});
10551085
});
10561086

10571087
describe('@noCI parcoords constraint interactions', function() {

0 commit comments

Comments
 (0)