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