From fb73e52718f654cf18c84fda170c2143594155f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 31 May 2019 17:13:40 -0400 Subject: [PATCH 1/7] clean up download_test suite --- test/jasmine/tests/download_test.js | 35 +++++++++++++++++------------ 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/test/jasmine/tests/download_test.js b/test/jasmine/tests/download_test.js index 23203718160..2aa33dbbe78 100644 --- a/test/jasmine/tests/download_test.js +++ b/test/jasmine/tests/download_test.js @@ -9,7 +9,6 @@ var Lib = require('@src/lib'); var LONG_TIMEOUT_INTERVAL = 2 * jasmine.DEFAULT_TIMEOUT_INTERVAL; describe('Plotly.downloadImage', function() { - 'use strict'; var gd; var createElement = document.createElement; @@ -32,7 +31,7 @@ describe('Plotly.downloadImage', function() { afterEach(function() { destroyGraphDiv(); - delete navigator.msSaveBlob; + delete window.navigator.msSaveBlob; }); it('should be attached to Plotly', function() { @@ -40,17 +39,23 @@ describe('Plotly.downloadImage', function() { }); it('should create link, remove link, accept options', function(done) { - downloadTest(gd, 'jpeg', done); + downloadTest(gd, 'jpeg') + .catch(failTest) + .then(done); }, LONG_TIMEOUT_INTERVAL); it('should create link, remove link, accept options', function(done) { - downloadTest(gd, 'png', done); + downloadTest(gd, 'png') + .catch(failTest) + .then(done); }, LONG_TIMEOUT_INTERVAL); it('should create link, remove link, accept options', function(done) { checkWebp(function(supported) { if(supported) { - downloadTest(gd, 'webp', done); + downloadTest(gd, 'webp') + .catch(failTest) + .then(done); } else { done(); } @@ -58,11 +63,15 @@ describe('Plotly.downloadImage', function() { }, LONG_TIMEOUT_INTERVAL); it('should create link, remove link, accept options', function(done) { - downloadTest(gd, 'svg', done); + downloadTest(gd, 'svg') + .catch(failTest) + .then(done); }, LONG_TIMEOUT_INTERVAL); it('should work when passing graph div id', function(done) { - downloadTest('graph', 'svg', done); + downloadTest('graph', 'svg') + .catch(failTest) + .then(done); }, LONG_TIMEOUT_INTERVAL); it('should work when passing a figure object', function(done) { @@ -87,7 +96,7 @@ describe('Plotly.downloadImage', function() { .replace(/(\(#)([^")]*)(\))/gi, '(\"#$2\")'); }); var savedBlob; - navigator.msSaveBlob = function(blob) { savedBlob = blob; }; + window.navigator.msSaveBlob = function(blob) { savedBlob = blob; }; var expectedStart = ' Date: Fri, 31 May 2019 17:16:08 -0400 Subject: [PATCH 2/7] :recycle: a few snapshot helpers --- src/plot_api/to_image.js | 6 ++---- src/snapshot/download.js | 7 +++++-- src/snapshot/helpers.js | 8 ++++++++ src/snapshot/svgtoimg.js | 6 ++++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js index c1a8c8733e1..1c5890ce528 100644 --- a/src/plot_api/to_image.js +++ b/src/plot_api/to_image.js @@ -74,8 +74,6 @@ var attrs = { } }; -var IMAGE_URL_PREFIX = /^data:image\/\w+;base64,/; - /** Plotly.toImage * * @param {object | string | HTML div} gd @@ -179,7 +177,7 @@ function toImage(gd, opts) { if(imageDataOnly) { return resolve(svg); } else { - return resolve('data:image/svg+xml,' + encodeURIComponent(svg)); + return resolve(helpers.encodeSVG(svg)); } } @@ -206,7 +204,7 @@ function toImage(gd, opts) { function urlToImageData(url) { if(imageDataOnly) { - return url.replace(IMAGE_URL_PREFIX, ''); + return url.replace(helpers.IMAGE_URL_PREFIX, ''); } else { return url; } diff --git a/src/snapshot/download.js b/src/snapshot/download.js index 64c3302a159..bc74b446dfb 100644 --- a/src/snapshot/download.js +++ b/src/snapshot/download.js @@ -8,9 +8,12 @@ 'use strict'; -var toImage = require('../plot_api/to_image'); var Lib = require('../lib'); + +var toImage = require('../plot_api/to_image'); + var fileSaver = require('./filesaver'); +var helpers = require('./helpers'); /** Plotly.downloadImage * @@ -41,7 +44,7 @@ function downloadImage(gd, opts) { // does not allow toDataURL // svg format will work though if(Lib.isIE() && opts.format !== 'svg') { - reject(new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.')); + reject(new Error(helpers.MSG_IE_BAD_FORMAT)); } if(_gd) _gd._snapshotInProgress = true; diff --git a/src/snapshot/helpers.js b/src/snapshot/helpers.js index 8b10678872b..ad308476686 100644 --- a/src/snapshot/helpers.js +++ b/src/snapshot/helpers.js @@ -31,3 +31,11 @@ exports.getRedrawFunc = function(gd) { } }; }; + +exports.encodeSVG = function(svg) { + return 'data:image/svg+xml,' + encodeURIComponent(svg); +}; + +exports.IMAGE_URL_PREFIX = /^data:image\/\w+;base64,/; + +exports.MSG_IE_BAD_FORMAT = 'Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.'; diff --git a/src/snapshot/svgtoimg.js b/src/snapshot/svgtoimg.js index d137c57b106..6abf1f8b241 100644 --- a/src/snapshot/svgtoimg.js +++ b/src/snapshot/svgtoimg.js @@ -11,6 +11,8 @@ var Lib = require('../lib'); var EventEmitter = require('events').EventEmitter; +var helpers = require('./helpers'); + function svgToImg(opts) { var ev = opts.emitter || new EventEmitter(); @@ -21,7 +23,7 @@ function svgToImg(opts) { // IE only support svg if(Lib.isIE() && format !== 'svg') { - var ieSvgError = new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.'); + var ieSvgError = new Error(helpers.MSG_IE_BAD_FORMAT); reject(ieSvgError); // eventually remove the ev // in favor of promises @@ -45,7 +47,7 @@ function svgToImg(opts) { // for Safari support, eliminate createObjectURL // this decision could cause problems if content // is not restricted to svg - var url = 'data:image/svg+xml,' + encodeURIComponent(svg); + var url = helpers.encodeSVG(svg); canvas.width = w1; canvas.height = h1; From cf7783cf3c300ca4a635ad2c58f9d6e8c4036831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 31 May 2019 17:16:59 -0400 Subject: [PATCH 3/7] add Lib.isSafari --- src/lib/index.js | 5 +++++ src/snapshot/filesaver.js | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 639360bb294..45d2f2ec0f7 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -733,6 +733,11 @@ lib.isIE = function() { return typeof window.navigator.msSaveBlob !== 'undefined'; }; +var IS_SAFARI_REGEX = /Version\/[\d\.]+.*Safari/; +lib.isSafari = function() { + return IS_SAFARI_REGEX.test(window.navigator.userAgent); +}; + /** * Duck typing to recognize a d3 selection, mostly for IE9's benefit * because it doesn't handle instanceof like modern browsers diff --git a/src/snapshot/filesaver.js b/src/snapshot/filesaver.js index e4132a1aa04..72c5e3450d6 100644 --- a/src/snapshot/filesaver.js +++ b/src/snapshot/filesaver.js @@ -24,7 +24,6 @@ var fileSaver = function(url, name) { var saveLink = document.createElement('a'); var canUseSaveLink = 'download' in saveLink; - var isSafari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent); var promise = new Promise(function(resolve, reject) { // IE <10 is explicitly unsupported if(typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) { @@ -32,10 +31,10 @@ var fileSaver = function(url, name) { } // First try a.download, then web filesystem, then object URLs - if(isSafari) { // Safari doesn't allow downloading of blob urls document.location.href = 'data:application/octet-stream' + url.slice(url.search(/[,;]/)); resolve(name); + if(Lib.isSafari()) { } if(!name) { From f2044f292385d1b2684367d97abad48797994313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 31 May 2019 17:21:47 -0400 Subject: [PATCH 4/7] use createObjectURL during Plotly.downloadImage ... to workaround Chrome URL length limit (#3771) and probably (not tested) have a perf boost. N.B. createObjectURL expects Blob object which itself expect data string w/o data:image prefix, so use imageDataOnly in the Plotly.toImage call --- src/snapshot/download.js | 10 ++-- src/snapshot/filesaver.js | 83 ++++++++++++++++++++--------- test/jasmine/tests/download_test.js | 8 +-- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/snapshot/download.js b/src/snapshot/download.js index bc74b446dfb..55c9187319d 100644 --- a/src/snapshot/download.js +++ b/src/snapshot/download.js @@ -15,23 +15,23 @@ var toImage = require('../plot_api/to_image'); var fileSaver = require('./filesaver'); var helpers = require('./helpers'); -/** Plotly.downloadImage +/** + * Plotly.downloadImage * * @param {object | string | HTML div} gd * can either be a data/layout/config object * or an existing graph
* or an id to an existing graph
- * @param {object} opts (see ../plot_api/to_image) + * @param {object} opts (see Plotly.toImage in ../plot_api/to_image) * @return {promise} */ function downloadImage(gd, opts) { var _gd; if(!Lib.isPlainObject(gd)) _gd = Lib.getGraphDiv(gd); - // check for undefined opts opts = opts || {}; - // default to png opts.format = opts.format || 'png'; + opts.imageDataOnly = true; return new Promise(function(resolve, reject) { if(_gd && _gd._snapshotInProgress) { @@ -55,7 +55,7 @@ function downloadImage(gd, opts) { promise.then(function(result) { if(_gd) _gd._snapshotInProgress = false; - return fileSaver(result, filename); + return fileSaver(result, filename, opts.format); }).then(function(name) { resolve(name); }).catch(function(err) { diff --git a/src/snapshot/filesaver.js b/src/snapshot/filesaver.js index 72c5e3450d6..49e0ea5548b 100644 --- a/src/snapshot/filesaver.js +++ b/src/snapshot/filesaver.js @@ -6,6 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict'; + +var Lib = require('../lib'); + /* * substantial portions of this code from FileSaver.js * https://github.com/eligrey/FileSaver.js @@ -18,52 +22,83 @@ * License: MIT * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md */ - -'use strict'; - -var fileSaver = function(url, name) { +function fileSaver(url, name, format) { var saveLink = document.createElement('a'); var canUseSaveLink = 'download' in saveLink; + var promise = new Promise(function(resolve, reject) { - // IE <10 is explicitly unsupported - if(typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) { + var blob; + var objectUrl; + + if(isIE9orBelow()) { reject(new Error('IE < 10 unsupported')); } - // First try a.download, then web filesystem, then object URLs - // Safari doesn't allow downloading of blob urls - document.location.href = 'data:application/octet-stream' + url.slice(url.search(/[,;]/)); - resolve(name); + // Safari doesn't allow downloading of blob urls if(Lib.isSafari()) { + var prefix = format === 'svg' ? ',' : ';base64,'; + document.location.href = 'data:application/octet-stream' + prefix + encodeURIComponent(url); + return resolve(); } - if(!name) { - name = 'download'; + // IE 10+ (native saveAs) + if(Lib.isIE()) { + // At this point we are only dealing with a decoded SVG as + // a data URL (since IE only supports SVG) + blob = createBlob(url, format); + window.navigator.msSaveBlob(blob, name); + blob = null; + return resolve(name); } if(canUseSaveLink) { - saveLink.href = url; + blob = createBlob(url, format); + objectUrl = window.URL.createObjectURL(blob); + + saveLink.href = objectUrl; saveLink.download = name; document.body.appendChild(saveLink); saveLink.click(); + document.body.removeChild(saveLink); - resolve(name); - } + window.URL.revokeObjectURL(objectUrl); + blob = null; - // IE 10+ (native saveAs) - if(typeof navigator !== 'undefined' && navigator.msSaveBlob) { - // At this point we are only dealing with a SVG encoded as - // a data URL (since IE only supports SVG) - var encoded = url.split(/^data:image\/svg\+xml,/)[1]; - var svg = decodeURIComponent(encoded); - navigator.msSaveBlob(new Blob([svg]), name); - resolve(name); + return resolve(name); } reject(new Error('download error')); }); return promise; -}; +} + +function isIE9orBelow() { + return ( + Lib.isIE() && + typeof window.navigator !== 'undefined' && + /MSIE [1-9]\./.test(window.navigator.userAgent) + ); +} + +// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752 +function fixBinary(b) { + var len = b.length; + var buf = new ArrayBuffer(len); + var arr = new Uint8Array(buf); + for(var i = 0; i < len; i++) { + arr[i] = b.charCodeAt(i); + } + return buf; +} + +function createBlob(url, format) { + if(format === 'svg') { + return new window.Blob([url]); + } else { + var binary = fixBinary(window.atob(url)); + return new window.Blob([binary], {type: 'image/' + format}); + } +} module.exports = fileSaver; diff --git a/test/jasmine/tests/download_test.js b/test/jasmine/tests/download_test.js index 2aa33dbbe78..6c5e5693edf 100644 --- a/test/jasmine/tests/download_test.js +++ b/test/jasmine/tests/download_test.js @@ -180,12 +180,8 @@ function downloadTest(gd, format) { var linkadded = domchanges[domchanges.length - 2].addedNodes[0]; var linkdeleted = domchanges[domchanges.length - 1].removedNodes[0]; - // check for a Date: Fri, 21 Jun 2019 16:41:59 -0400 Subject: [PATCH 5/7] move (create|revoke)ObjectURL & createBlob to helpers - so that when we can fallback to window.webkitURL if window.URL isn't defined --- src/snapshot/filesaver.js | 29 +++++------------------------ src/snapshot/helpers.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/snapshot/filesaver.js b/src/snapshot/filesaver.js index 49e0ea5548b..e133fd4f12c 100644 --- a/src/snapshot/filesaver.js +++ b/src/snapshot/filesaver.js @@ -9,6 +9,7 @@ 'use strict'; var Lib = require('../lib'); +var helpers = require('./helpers'); /* * substantial portions of this code from FileSaver.js @@ -45,15 +46,15 @@ function fileSaver(url, name, format) { if(Lib.isIE()) { // At this point we are only dealing with a decoded SVG as // a data URL (since IE only supports SVG) - blob = createBlob(url, format); + blob = helpers.createBlob(url, 'svg'); window.navigator.msSaveBlob(blob, name); blob = null; return resolve(name); } if(canUseSaveLink) { - blob = createBlob(url, format); - objectUrl = window.URL.createObjectURL(blob); + blob = helpers.createBlob(url, format); + objectUrl = helpers.createObjectURL(blob); saveLink.href = objectUrl; saveLink.download = name; @@ -61,7 +62,7 @@ function fileSaver(url, name, format) { saveLink.click(); document.body.removeChild(saveLink); - window.URL.revokeObjectURL(objectUrl); + helpers.revokeObjectURL(objectUrl); blob = null; return resolve(name); @@ -81,24 +82,4 @@ function isIE9orBelow() { ); } -// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752 -function fixBinary(b) { - var len = b.length; - var buf = new ArrayBuffer(len); - var arr = new Uint8Array(buf); - for(var i = 0; i < len; i++) { - arr[i] = b.charCodeAt(i); - } - return buf; -} - -function createBlob(url, format) { - if(format === 'svg') { - return new window.Blob([url]); - } else { - var binary = fixBinary(window.atob(url)); - return new window.Blob([binary], {type: 'image/' + format}); - } -} - module.exports = fileSaver; diff --git a/src/snapshot/helpers.js b/src/snapshot/helpers.js index ad308476686..ba8d824ced0 100644 --- a/src/snapshot/helpers.js +++ b/src/snapshot/helpers.js @@ -36,6 +36,36 @@ exports.encodeSVG = function(svg) { return 'data:image/svg+xml,' + encodeURIComponent(svg); }; +var DOM_URL = window.URL || window.webkitURL; + +exports.createObjectURL = function(blob) { + return DOM_URL.createObjectURL(blob); +}; + +exports.revokeObjectURL = function(url) { + return DOM_URL.revokeObjectURL(url); +}; + +exports.createBlob = function(url, format) { + if(format === 'svg') { + return new window.Blob([url], {type: 'image/svg+xml;charset=utf-8'}); + } else { + var binary = fixBinary(window.atob(url)); + return new window.Blob([binary], {type: 'image/' + format}); + } +}; + +// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752 +function fixBinary(b) { + var len = b.length; + var buf = new ArrayBuffer(len); + var arr = new Uint8Array(buf); + for(var i = 0; i < len; i++) { + arr[i] = b.charCodeAt(i); + } + return buf; +} + exports.IMAGE_URL_PREFIX = /^data:image\/\w+;base64,/; exports.MSG_IE_BAD_FORMAT = 'Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.'; From 47a25a2b7017a58c774fb40e626a45331eaa1260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 21 Jun 2019 16:42:59 -0400 Subject: [PATCH 6/7] use createObjectURL during svgToImg - which speeds up things for loooong SVG strings - must (still) use encodeSVG in Safari and IE9 --- src/lib/index.js | 5 +++++ src/snapshot/filesaver.js | 9 +-------- src/snapshot/svgtoimg.js | 17 +++++++++++++---- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 45d2f2ec0f7..a4475bf3aee 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -733,6 +733,11 @@ lib.isIE = function() { return typeof window.navigator.msSaveBlob !== 'undefined'; }; +var IS_IE9_OR_BELOW_REGEX = /MSIE [1-9]\./; +lib.isIE9orBelow = function() { + return lib.isIE() && IS_IE9_OR_BELOW_REGEX.test(window.navigator.userAgent); +}; + var IS_SAFARI_REGEX = /Version\/[\d\.]+.*Safari/; lib.isSafari = function() { return IS_SAFARI_REGEX.test(window.navigator.userAgent); diff --git a/src/snapshot/filesaver.js b/src/snapshot/filesaver.js index e133fd4f12c..235c36e4a9b 100644 --- a/src/snapshot/filesaver.js +++ b/src/snapshot/filesaver.js @@ -31,7 +31,7 @@ function fileSaver(url, name, format) { var blob; var objectUrl; - if(isIE9orBelow()) { + if(Lib.isIE9orBelow()) { reject(new Error('IE < 10 unsupported')); } @@ -74,12 +74,5 @@ function fileSaver(url, name, format) { return promise; } -function isIE9orBelow() { - return ( - Lib.isIE() && - typeof window.navigator !== 'undefined' && - /MSIE [1-9]\./.test(window.navigator.userAgent) - ); -} module.exports = fileSaver; diff --git a/src/snapshot/svgtoimg.js b/src/snapshot/svgtoimg.js index 6abf1f8b241..6117f04a3f9 100644 --- a/src/snapshot/svgtoimg.js +++ b/src/snapshot/svgtoimg.js @@ -43,11 +43,14 @@ function svgToImg(opts) { var ctx = canvas.getContext('2d'); var img = new Image(); + var svgBlob, url; - // for Safari support, eliminate createObjectURL - // this decision could cause problems if content - // is not restricted to svg - var url = helpers.encodeSVG(svg); + if(format === 'svg' || Lib.isIE9orBelow() || Lib.isSafari()) { + url = helpers.encodeSVG(svg); + } else { + svgBlob = helpers.createBlob(svg, 'svg'); + url = helpers.createObjectURL(svgBlob); + } canvas.width = w1; canvas.height = h1; @@ -55,6 +58,9 @@ function svgToImg(opts) { img.onload = function() { var imgData; + svgBlob = null; + helpers.revokeObjectURL(url); + // don't need to draw to canvas if svg // save some time and also avoid failure on IE if(format !== 'svg') { @@ -92,6 +98,9 @@ function svgToImg(opts) { }; img.onerror = function(err) { + svgBlob = null; + helpers.revokeObjectURL(url); + reject(err); // eventually remove the ev // in favor of promises From ecf3ac7c739c2e1a4ae0a840c83cfd6c1731a2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 2 Jul 2019 14:54:52 -0400 Subject: [PATCH 7/7] add test for isSafari downloadImag logic --- src/snapshot/filesaver.js | 4 ++-- src/snapshot/helpers.js | 4 ++++ test/jasmine/tests/download_test.js | 28 +++++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/snapshot/filesaver.js b/src/snapshot/filesaver.js index 235c36e4a9b..00e6435670a 100644 --- a/src/snapshot/filesaver.js +++ b/src/snapshot/filesaver.js @@ -38,8 +38,8 @@ function fileSaver(url, name, format) { // Safari doesn't allow downloading of blob urls if(Lib.isSafari()) { var prefix = format === 'svg' ? ',' : ';base64,'; - document.location.href = 'data:application/octet-stream' + prefix + encodeURIComponent(url); - return resolve(); + helpers.octetStream(prefix + encodeURIComponent(url)); + return resolve(name); } // IE 10+ (native saveAs) diff --git a/src/snapshot/helpers.js b/src/snapshot/helpers.js index ba8d824ced0..47672dfdc00 100644 --- a/src/snapshot/helpers.js +++ b/src/snapshot/helpers.js @@ -55,6 +55,10 @@ exports.createBlob = function(url, format) { } }; +exports.octetStream = function(s) { + document.location.href = 'data:application/octet-stream' + s; +}; + // Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752 function fixBinary(b) { var len = b.length; diff --git a/test/jasmine/tests/download_test.js b/test/jasmine/tests/download_test.js index 6c5e5693edf..adee0c911ef 100644 --- a/test/jasmine/tests/download_test.js +++ b/test/jasmine/tests/download_test.js @@ -1,11 +1,13 @@ var Plotly = require('@lib/index'); +var Lib = require('@src/lib'); + +var helpers = require('@src/snapshot/helpers'); + var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var textchartMock = require('@mocks/text_chart_arrays.json'); var failTest = require('../assets/fail_test'); -var Lib = require('@src/lib'); - +var textchartMock = require('@mocks/text_chart_arrays.json'); var LONG_TIMEOUT_INTERVAL = 2 * jasmine.DEFAULT_TIMEOUT_INTERVAL; describe('Plotly.downloadImage', function() { @@ -136,6 +138,26 @@ describe('Plotly.downloadImage', function() { .catch(failTest) .then(done); }, LONG_TIMEOUT_INTERVAL); + + it('should produce right output in Safari', function(done) { + spyOn(Lib, 'isSafari').and.callFake(function() { return true; }); + spyOn(helpers, 'octetStream'); + + Plotly.plot(gd, textchartMock.data, textchartMock.layout) + .then(function() { return Plotly.downloadImage(gd, {format: 'svg'}); }) + .then(function() { return Plotly.downloadImage(gd, {format: 'png'}); }) + .then(function() { return Plotly.downloadImage(gd, {format: 'jpeg'}); }) + .then(function() { return Plotly.downloadImage(gd, {format: 'webp'}); }) + .then(function() { + var args = helpers.octetStream.calls.allArgs(); + expect(args[0][0].slice(0, 15)).toBe(',%3Csvg%20class', 'format:svg'); + expect(args[1][0].slice(0, 8)).toBe(';base64,', 'format:png'); + expect(args[2][0].slice(0, 8)).toBe(';base64,', 'format:jpeg'); + expect(args[3][0].slice(0, 8)).toBe(';base64,', 'format:webp'); + }) + .catch(failTest) + .then(done); + }); }); function downloadTest(gd, format) {