diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx
index 83b27d4c3252cccd83c1f1567d73d486c4790b5d..39e0419acdd720dec654f37bf9244dfe2014e573 100644
--- a/src/containers/costume-tab.jsx
+++ b/src/containers/costume-tab.jsx
@@ -13,7 +13,7 @@ import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
 import DragConstants from '../lib/drag-constants';
 import {emptyCostume} from '../lib/empty-assets';
 import sharedMessages from '../lib/shared-messages';
-import download from '../lib/download-url';
+import downloadBlob from '../lib/download-blob';
 
 import {
     closeCameraCapture,
@@ -154,7 +154,8 @@ class CostumeTab extends React.Component {
     }
     handleExportCostume (costumeIndex) {
         const item = this.props.vm.editingTarget.sprite.costumes[costumeIndex];
-        download(`${item.name}.${item.asset.dataFormat}`, item.asset.encodeDataURI());
+        const blob = new Blob([item.asset.data], {type: item.asset.assetType.contentType});
+        downloadBlob(`${item.name}.${item.asset.dataFormat}`, blob);
     }
     handleNewCostume (costume, fromCostumeLibrary) {
         if (fromCostumeLibrary) {
diff --git a/src/containers/monitor.jsx b/src/containers/monitor.jsx
index 039fa060d616d2df878f75eff8140fcbdcb51ecb..b35a04bb9c2056b74f276d213ac45fa42a2a343f 100644
--- a/src/containers/monitor.jsx
+++ b/src/containers/monitor.jsx
@@ -8,7 +8,7 @@ import MonitorComponent, {monitorModes} from '../components/monitor/monitor.jsx'
 import {addMonitorRect, getInitialPosition, resizeMonitorRect, removeMonitorRect} from '../reducers/monitor-layout';
 import {getVariable, setVariableValue} from '../lib/variable-utils';
 import importCSV from '../lib/import-csv';
-import download from '../lib/download-url';
+import downloadBlob from '../lib/download-blob';
 
 import {connect} from 'react-redux';
 import {Map} from 'immutable';
@@ -156,7 +156,8 @@ class Monitor extends React.Component {
         const {vm, targetId, id: variableId} = this.props;
         const variable = getVariable(vm, targetId, variableId);
         const text = variable.value.join('\r\n');
-        download(`${variable.name}.txt`, `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`);
+        const blob = new Blob([text], {type: 'text/plain;charset=utf-8'});
+        downloadBlob(`${variable.name}.txt`, blob);
     }
     render () {
         const monitorProps = monitorAdapter(this.props);
diff --git a/src/containers/sb3-downloader.jsx b/src/containers/sb3-downloader.jsx
index 47df7bacaebe341abc571d3c9ca7e22e2d024e34..536934a270cfdbaace177fca5e4046a47b38f8ff 100644
--- a/src/containers/sb3-downloader.jsx
+++ b/src/containers/sb3-downloader.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import {connect} from 'react-redux';
 import {projectTitleInitialState} from '../reducers/project-title';
-
+import downloadBlob from '../lib/download-blob';
 /**
  * Project saver component passes a downloadProject function to its child.
  * It expects this child to be a function with the signature
@@ -26,25 +26,11 @@ class SB3Downloader extends React.Component {
         ]);
     }
     downloadProject () {
-        const downloadLink = document.createElement('a');
-        document.body.appendChild(downloadLink);
-
         this.props.saveProjectSb3().then(content => {
             if (this.props.onSaveFinished) {
                 this.props.onSaveFinished();
             }
-            // Use special ms version if available to get it working on Edge.
-            if (navigator.msSaveOrOpenBlob) {
-                navigator.msSaveOrOpenBlob(content, this.props.projectFilename);
-                return;
-            }
-
-            const url = window.URL.createObjectURL(content);
-            downloadLink.href = url;
-            downloadLink.download = this.props.projectFilename;
-            downloadLink.click();
-            window.URL.revokeObjectURL(url);
-            document.body.removeChild(downloadLink);
+            downloadBlob(this.props.projectFilename, content);
         });
     }
     render () {
diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx
index 40e08dfb737321de558b278c011c4a2e9e980142..3880de2e5349d3df811651a96846cb5c9b06e1a8 100644
--- a/src/containers/sound-tab.jsx
+++ b/src/containers/sound-tab.jsx
@@ -21,7 +21,7 @@ import soundLibraryContent from '../lib/libraries/sounds.json';
 import {handleFileUpload, soundUpload} from '../lib/file-uploader.js';
 import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
 import DragConstants from '../lib/drag-constants';
-import download from '../lib/download-url';
+import downloadBlob from '../lib/download-blob';
 
 import {connect} from 'react-redux';
 
@@ -90,8 +90,8 @@ class SoundTab extends React.Component {
 
     handleExportSound (soundIndex) {
         const item = this.props.vm.editingTarget.sprite.sounds[soundIndex];
-        const soundDataURL = item.asset.encodeDataURI();
-        download(`${item.name}.${item.asset.dataFormat}`, soundDataURL);
+        const blob = new Blob([item.asset.data], {type: item.asset.assetType.contentType});
+        downloadBlob(`${item.name}.${item.asset.dataFormat}`, blob);
     }
 
     handleDuplicateSound (soundIndex) {
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 16017423c903b881176569fc9255d13daea02d77..25382b9054c43de3e007be12fae4202c954b8860 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -21,6 +21,7 @@ import {emptySprite} from '../lib/empty-assets';
 import {highlightTarget} from '../reducers/targets';
 import {fetchSprite, fetchCode} from '../lib/backpack-api';
 import randomizeSpritePosition from '../lib/randomize-sprite-position';
+import downloadBlob from '../lib/download-blob';
 
 class TargetPane extends React.Component {
     constructor (props) {
@@ -94,20 +95,7 @@ class TargetPane extends React.Component {
         document.body.appendChild(saveLink);
 
         this.props.vm.exportSprite(id).then(content => {
-            const filename = `${spriteName}.sprite3`;
-
-            // Use special ms version if available to get it working on Edge.
-            if (navigator.msSaveOrOpenBlob) {
-                navigator.msSaveOrOpenBlob(content, filename);
-                return;
-            }
-
-            const url = window.URL.createObjectURL(content);
-            saveLink.href = url;
-            saveLink.download = filename;
-            saveLink.click();
-            window.URL.revokeObjectURL(url);
-            document.body.removeChild(saveLink);
+            downloadBlob(`${spriteName}.sprite3`, content);
         });
     }
     handleSelectSprite (id) {
diff --git a/src/lib/download-blob.js b/src/lib/download-blob.js
new file mode 100644
index 0000000000000000000000000000000000000000..13d53538c60107d59ce6be9de143ad5d5b5120e7
--- /dev/null
+++ b/src/lib/download-blob.js
@@ -0,0 +1,17 @@
+export default (filename, blob) => {
+    const downloadLink = document.createElement('a');
+    document.body.appendChild(downloadLink);
+
+    // Use special ms version if available to get it working on Edge.
+    if (navigator.msSaveOrOpenBlob) {
+        navigator.msSaveOrOpenBlob(blob, filename);
+        return;
+    }
+
+    const url = window.URL.createObjectURL(blob);
+    downloadLink.href = url;
+    downloadLink.download = filename;
+    downloadLink.click();
+    window.URL.revokeObjectURL(url);
+    document.body.removeChild(downloadLink);
+};
diff --git a/src/lib/download-url.js b/src/lib/download-url.js
deleted file mode 100644
index e01cb1f0890bf103d704c661b0795bce721418bd..0000000000000000000000000000000000000000
--- a/src/lib/download-url.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default (filename, url) => {
-    const pom = document.createElement('a');
-    pom.setAttribute('href', url);
-    pom.setAttribute('download', filename);
-
-    if (document.createEvent) {
-        const event = document.createEvent('MouseEvents');
-        event.initEvent('click', true, true);
-        pom.dispatchEvent(event);
-    } else {
-        pom.click();
-    }
-};