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 | }
|
---|