FileSaver.js (8586B)
1 /* FileSaver.js 2 * A saveAs() FileSaver implementation. 3 * 1.1.20151003 4 * 5 * By Eli Grey, http://eligrey.com 6 * License: X11/MIT 7 * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md 8 */ 9 10 /*global self */ 11 /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 12 13 /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 14 15 var saveAs = saveAs || (function(view) { 16 "use strict"; 17 // IE <10 is explicitly unsupported 18 if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { 19 return; 20 } 21 var 22 doc = view.document 23 // only get URL when necessary in case Blob.js hasn't overridden it yet 24 , get_URL = function() { 25 return view.URL || view.webkitURL || view; 26 } 27 , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") 28 , can_use_save_link = "download" in save_link 29 , click = function(node) { 30 var event = new MouseEvent("click"); 31 node.dispatchEvent(event); 32 } 33 , is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent) 34 , webkit_req_fs = view.webkitRequestFileSystem 35 , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem 36 , throw_outside = function(ex) { 37 (view.setImmediate || view.setTimeout)(function() { 38 throw ex; 39 }, 0); 40 } 41 , force_saveable_type = "application/octet-stream" 42 , fs_min_size = 0 43 // See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and 44 // https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047 45 // for the reasoning behind the timeout and revocation flow 46 , arbitrary_revoke_timeout = 500 // in ms 47 , revoke = function(file) { 48 var revoker = function() { 49 if (typeof file === "string") { // file is an object URL 50 get_URL().revokeObjectURL(file); 51 } else { // file is a File 52 file.remove(); 53 } 54 }; 55 if (view.chrome) { 56 revoker(); 57 } else { 58 setTimeout(revoker, arbitrary_revoke_timeout); 59 } 60 } 61 , dispatch = function(filesaver, event_types, event) { 62 event_types = [].concat(event_types); 63 var i = event_types.length; 64 while (i--) { 65 var listener = filesaver["on" + event_types[i]]; 66 if (typeof listener === "function") { 67 try { 68 listener.call(filesaver, event || filesaver); 69 } catch (ex) { 70 throw_outside(ex); 71 } 72 } 73 } 74 } 75 , auto_bom = function(blob) { 76 // prepend BOM for UTF-8 XML and text/* types (including HTML) 77 if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 78 return new Blob(["\ufeff", blob], {type: blob.type}); 79 } 80 return blob; 81 } 82 , FileSaver = function(blob, name, no_auto_bom) { 83 if (!no_auto_bom) { 84 blob = auto_bom(blob); 85 } 86 // First try a.download, then web filesystem, then object URLs 87 var 88 filesaver = this 89 , type = blob.type 90 , blob_changed = false 91 , object_url 92 , target_view 93 , dispatch_all = function() { 94 dispatch(filesaver, "writestart progress write writeend".split(" ")); 95 } 96 // on any filesys errors revert to saving with object URLs 97 , fs_error = function() { 98 if (target_view && is_safari && typeof FileReader !== "undefined") { 99 // Safari doesn't allow downloading of blob urls 100 var reader = new FileReader(); 101 reader.onloadend = function() { 102 var base64Data = reader.result; 103 target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/)); 104 filesaver.readyState = filesaver.DONE; 105 dispatch_all(); 106 }; 107 reader.readAsDataURL(blob); 108 filesaver.readyState = filesaver.INIT; 109 return; 110 } 111 // don't create more object URLs than needed 112 if (blob_changed || !object_url) { 113 object_url = get_URL().createObjectURL(blob); 114 } 115 if (target_view) { 116 target_view.location.href = object_url; 117 } else { 118 var new_tab = view.open(object_url, "_blank"); 119 if (new_tab == undefined && is_safari) { 120 //Apple do not allow window.open, see http://bit.ly/1kZffRI 121 view.location.href = object_url 122 } 123 } 124 filesaver.readyState = filesaver.DONE; 125 dispatch_all(); 126 revoke(object_url); 127 } 128 , abortable = function(func) { 129 return function() { 130 if (filesaver.readyState !== filesaver.DONE) { 131 return func.apply(this, arguments); 132 } 133 }; 134 } 135 , create_if_not_found = {create: true, exclusive: false} 136 , slice 137 ; 138 filesaver.readyState = filesaver.INIT; 139 if (!name) { 140 name = "download"; 141 } 142 if (can_use_save_link) { 143 object_url = get_URL().createObjectURL(blob); 144 save_link.href = object_url; 145 save_link.download = name; 146 setTimeout(function() { 147 click(save_link); 148 dispatch_all(); 149 revoke(object_url); 150 filesaver.readyState = filesaver.DONE; 151 }); 152 return; 153 } 154 // Object and web filesystem URLs have a problem saving in Google Chrome when 155 // viewed in a tab, so I force save with application/octet-stream 156 // http://code.google.com/p/chromium/issues/detail?id=91158 157 // Update: Google errantly closed 91158, I submitted it again: 158 // https://code.google.com/p/chromium/issues/detail?id=389642 159 if (view.chrome && type && type !== force_saveable_type) { 160 slice = blob.slice || blob.webkitSlice; 161 blob = slice.call(blob, 0, blob.size, force_saveable_type); 162 blob_changed = true; 163 } 164 // Since I can't be sure that the guessed media type will trigger a download 165 // in WebKit, I append .download to the filename. 166 // https://bugs.webkit.org/show_bug.cgi?id=65440 167 if (webkit_req_fs && name !== "download") { 168 name += ".download"; 169 } 170 if (type === force_saveable_type || webkit_req_fs) { 171 target_view = view; 172 } 173 if (!req_fs) { 174 fs_error(); 175 return; 176 } 177 fs_min_size += blob.size; 178 req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { 179 fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { 180 var save = function() { 181 dir.getFile(name, create_if_not_found, abortable(function(file) { 182 file.createWriter(abortable(function(writer) { 183 writer.onwriteend = function(event) { 184 target_view.location.href = file.toURL(); 185 filesaver.readyState = filesaver.DONE; 186 dispatch(filesaver, "writeend", event); 187 revoke(file); 188 }; 189 writer.onerror = function() { 190 var error = writer.error; 191 if (error.code !== error.ABORT_ERR) { 192 fs_error(); 193 } 194 }; 195 "writestart progress write abort".split(" ").forEach(function(event) { 196 writer["on" + event] = filesaver["on" + event]; 197 }); 198 writer.write(blob); 199 filesaver.abort = function() { 200 writer.abort(); 201 filesaver.readyState = filesaver.DONE; 202 }; 203 filesaver.readyState = filesaver.WRITING; 204 }), fs_error); 205 }), fs_error); 206 }; 207 dir.getFile(name, {create: false}, abortable(function(file) { 208 // delete file if it already exists 209 file.remove(); 210 save(); 211 }), abortable(function(ex) { 212 if (ex.code === ex.NOT_FOUND_ERR) { 213 save(); 214 } else { 215 fs_error(); 216 } 217 })); 218 }), fs_error); 219 }), fs_error); 220 } 221 , FS_proto = FileSaver.prototype 222 , saveAs = function(blob, name, no_auto_bom) { 223 return new FileSaver(blob, name, no_auto_bom); 224 } 225 ; 226 // IE 10+ (native saveAs) 227 if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { 228 return function(blob, name, no_auto_bom) { 229 if (!no_auto_bom) { 230 blob = auto_bom(blob); 231 } 232 return navigator.msSaveOrOpenBlob(blob, name || "download"); 233 }; 234 } 235 236 FS_proto.abort = function() { 237 var filesaver = this; 238 filesaver.readyState = filesaver.DONE; 239 dispatch(filesaver, "abort"); 240 }; 241 FS_proto.readyState = FS_proto.INIT = 0; 242 FS_proto.WRITING = 1; 243 FS_proto.DONE = 2; 244 245 FS_proto.error = 246 FS_proto.onwritestart = 247 FS_proto.onprogress = 248 FS_proto.onwrite = 249 FS_proto.onabort = 250 FS_proto.onerror = 251 FS_proto.onwriteend = 252 null; 253 254 return saveAs; 255 }( 256 typeof self !== "undefined" && self 257 || typeof window !== "undefined" && window 258 || this.content 259 )); 260 // `self` is undefined in Firefox for Android content script context 261 // while `this` is nsIContentFrameMessageManager 262 // with an attribute `content` that corresponds to the window 263 264 if (typeof module !== "undefined" && module.exports) { 265 module.exports.saveAs = saveAs; 266 } else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) { 267 define([], function() { 268 return saveAs; 269 }); 270 }