FileSaver.js 8.4 KB

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