info@54: /* Blob.js info@54: * A Blob implementation. info@54: * 2013-06-20 info@54: * info@54: * By Eli Grey, http://eligrey.com info@54: * By Devin Samarin, https://github.com/eboyjr info@54: * License: X11/MIT info@54: * See LICENSE.md info@54: */ info@54: info@54: /*global self, unescape */ info@54: /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, info@54: plusplus: true */ info@54: info@54: /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ info@54: info@54: if (!(typeof Blob === "function" || typeof Blob === "object") || typeof URL === "undefined") info@54: if ((typeof Blob === "function" || typeof Blob === "object") && typeof webkitURL !== "undefined") self.URL = webkitURL; info@54: else var Blob = (function (view) { info@54: "use strict"; info@54: info@54: var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) { info@54: var info@54: get_class = function(object) { info@54: return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; info@54: } info@54: , FakeBlobBuilder = function BlobBuilder() { info@54: this.data = []; info@54: } info@54: , FakeBlob = function Blob(data, type, encoding) { info@54: this.data = data; info@54: this.size = data.length; info@54: this.type = type; info@54: this.encoding = encoding; info@54: } info@54: , FBB_proto = FakeBlobBuilder.prototype info@54: , FB_proto = FakeBlob.prototype info@54: , FileReaderSync = view.FileReaderSync info@54: , FileException = function(type) { info@54: this.code = this[this.name = type]; info@54: } info@54: , file_ex_codes = ( info@54: "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " info@54: + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" info@54: ).split(" ") info@54: , file_ex_code = file_ex_codes.length info@54: , real_URL = view.URL || view.webkitURL || view info@54: , real_create_object_URL = real_URL.createObjectURL info@54: , real_revoke_object_URL = real_URL.revokeObjectURL info@54: , URL = real_URL info@54: , btoa = view.btoa info@54: , atob = view.atob info@54: info@54: , ArrayBuffer = view.ArrayBuffer info@54: , Uint8Array = view.Uint8Array info@54: ; info@54: FakeBlob.fake = FB_proto.fake = true; info@54: while (file_ex_code--) { info@54: FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; info@54: } info@54: if (!real_URL.createObjectURL) { info@54: URL = view.URL = {}; info@54: } info@54: URL.createObjectURL = function(blob) { info@54: var info@54: type = blob.type info@54: , data_URI_header info@54: ; info@54: if (type === null) { info@54: type = "application/octet-stream"; info@54: } info@54: if (blob instanceof FakeBlob) { info@54: data_URI_header = "data:" + type; info@54: if (blob.encoding === "base64") { info@54: return data_URI_header + ";base64," + blob.data; info@54: } else if (blob.encoding === "URI") { info@54: return data_URI_header + "," + decodeURIComponent(blob.data); info@54: } if (btoa) { info@54: return data_URI_header + ";base64," + btoa(blob.data); info@54: } else { info@54: return data_URI_header + "," + encodeURIComponent(blob.data); info@54: } info@54: } else if (real_create_object_URL) { info@54: return real_create_object_URL.call(real_URL, blob); info@54: } info@54: }; info@54: URL.revokeObjectURL = function(object_URL) { info@54: if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { info@54: real_revoke_object_URL.call(real_URL, object_URL); info@54: } info@54: }; info@54: FBB_proto.append = function(data/*, endings*/) { info@54: var bb = this.data; info@54: // decode data to a binary string info@54: if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { info@54: var info@54: str = "" info@54: , buf = new Uint8Array(data) info@54: , i = 0 info@54: , buf_len = buf.length info@54: ; info@54: for (; i < buf_len; i++) { info@54: str += String.fromCharCode(buf[i]); info@54: } info@54: bb.push(str); info@54: } else if (get_class(data) === "Blob" || get_class(data) === "File") { info@54: if (FileReaderSync) { info@54: var fr = new FileReaderSync; info@54: bb.push(fr.readAsBinaryString(data)); info@54: } else { info@54: // async FileReader won't work as BlobBuilder is sync info@54: throw new FileException("NOT_READABLE_ERR"); info@54: } info@54: } else if (data instanceof FakeBlob) { info@54: if (data.encoding === "base64" && atob) { info@54: bb.push(atob(data.data)); info@54: } else if (data.encoding === "URI") { info@54: bb.push(decodeURIComponent(data.data)); info@54: } else if (data.encoding === "raw") { info@54: bb.push(data.data); info@54: } info@54: } else { info@54: if (typeof data !== "string") { info@54: data += ""; // convert unsupported types to strings info@54: } info@54: // decode UTF-16 to binary string info@54: bb.push(unescape(encodeURIComponent(data))); info@54: } info@54: }; info@54: FBB_proto.getBlob = function(type) { info@54: if (!arguments.length) { info@54: type = null; info@54: } info@54: return new FakeBlob(this.data.join(""), type, "raw"); info@54: }; info@54: FBB_proto.toString = function() { info@54: return "[object BlobBuilder]"; info@54: }; info@54: FB_proto.slice = function(start, end, type) { info@54: var args = arguments.length; info@54: if (args < 3) { info@54: type = null; info@54: } info@54: return new FakeBlob( info@54: this.data.slice(start, args > 1 ? end : this.data.length) info@54: , type info@54: , this.encoding info@54: ); info@54: }; info@54: FB_proto.toString = function() { info@54: return "[object Blob]"; info@54: }; info@54: return FakeBlobBuilder; info@54: }(view)); info@54: info@54: return function Blob(blobParts, options) { info@54: var type = options ? (options.type || "") : ""; info@54: var builder = new BlobBuilder(); info@54: if (blobParts) { info@54: for (var i = 0, len = blobParts.length; i < len; i++) { info@54: builder.append(blobParts[i]); info@54: } info@54: } info@54: return builder.getBlob(type); info@54: }; info@54: }(self)); info@54: info@54: /* FileSaver.js info@54: * A saveAs() FileSaver implementation. info@54: * 2013-10-21 info@54: * info@54: * By Eli Grey, http://eligrey.com info@54: * License: X11/MIT info@54: * See LICENSE.md info@54: */ info@54: info@54: /*global self */ info@54: /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, info@54: plusplus: true */ info@54: info@54: /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ info@54: info@54: var saveAs = saveAs info@54: || (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator)) info@54: || (function(view) { info@54: "use strict"; info@54: var info@54: doc = view.document info@54: // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet info@54: , get_URL = function() { info@54: return view.URL || view.webkitURL || view; info@54: } info@54: , URL = view.URL || view.webkitURL || view info@54: , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") info@54: , can_use_save_link = !view.externalHost && "download" in save_link info@54: , click = function(node) { info@54: var event = doc.createEvent("MouseEvents"); info@54: event.initMouseEvent( info@54: "click", true, false, view, 0, 0, 0, 0, 0 info@54: , false, false, false, false, 0, null info@54: ); info@54: node.dispatchEvent(event); info@54: } info@54: , webkit_req_fs = view.webkitRequestFileSystem info@54: , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem info@54: , throw_outside = function (ex) { info@54: (view.setImmediate || view.setTimeout)(function() { info@54: throw ex; info@54: }, 0); info@54: } info@54: , force_saveable_type = "application/octet-stream" info@54: , fs_min_size = 0 info@54: , deletion_queue = [] info@54: , process_deletion_queue = function() { info@54: var i = deletion_queue.length; info@54: while (i--) { info@54: var file = deletion_queue[i]; info@54: if (typeof file === "string") { // file is an object URL info@54: URL.revokeObjectURL(file); info@54: } else { // file is a File info@54: file.remove(); info@54: } info@54: } info@54: deletion_queue.length = 0; // clear queue info@54: } info@54: , dispatch = function(filesaver, event_types, event) { info@54: event_types = [].concat(event_types); info@54: var i = event_types.length; info@54: while (i--) { info@54: var listener = filesaver["on" + event_types[i]]; info@54: if (typeof listener === "function") { info@54: try { info@54: listener.call(filesaver, event || filesaver); info@54: } catch (ex) { info@54: throw_outside(ex); info@54: } info@54: } info@54: } info@54: } info@54: , FileSaver = function(blob, name) { info@54: // First try a.download, then web filesystem, then object URLs info@54: var info@54: filesaver = this info@54: , type = blob.type info@54: , blob_changed = false info@54: , object_url info@54: , target_view info@54: , get_object_url = function() { info@54: var object_url = get_URL().createObjectURL(blob); info@54: deletion_queue.push(object_url); info@54: return object_url; info@54: } info@54: , dispatch_all = function() { info@54: dispatch(filesaver, "writestart progress write writeend".split(" ")); info@54: } info@54: // on any filesys errors revert to saving with object URLs info@54: , fs_error = function() { info@54: // don't create more object URLs than needed info@54: if (blob_changed || !object_url) { info@54: object_url = get_object_url(blob); info@54: } info@54: if (target_view) { info@54: target_view.location.href = object_url; info@54: } else { info@54: window.open(object_url, "_blank"); info@54: } info@54: filesaver.readyState = filesaver.DONE; info@54: dispatch_all(); info@54: } info@54: , abortable = function(func) { info@54: return function() { info@54: if (filesaver.readyState !== filesaver.DONE) { info@54: return func.apply(this, arguments); info@54: } info@54: }; info@54: } info@54: , create_if_not_found = {create: true, exclusive: false} info@54: , slice info@54: ; info@54: filesaver.readyState = filesaver.INIT; info@54: if (!name) { info@54: name = "download"; info@54: } info@54: if (can_use_save_link) { info@54: object_url = get_object_url(blob); info@54: // FF for Android has a nasty garbage collection mechanism info@54: // that turns all objects that are not pure javascript into 'deadObject' info@54: // this means `doc` and `save_link` are unusable and need to be recreated info@54: // `view` is usable though: info@54: doc = view.document; info@54: save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"); info@54: save_link.href = object_url; info@54: save_link.download = name; info@54: var event = doc.createEvent("MouseEvents"); info@54: event.initMouseEvent( info@54: "click", true, false, view, 0, 0, 0, 0, 0 info@54: , false, false, false, false, 0, null info@54: ); info@54: save_link.dispatchEvent(event); info@54: filesaver.readyState = filesaver.DONE; info@54: dispatch_all(); info@54: return; info@54: } info@54: // Object and web filesystem URLs have a problem saving in Google Chrome when info@54: // viewed in a tab, so I force save with application/octet-stream info@54: // http://code.google.com/p/chromium/issues/detail?id=91158 info@54: if (view.chrome && type && type !== force_saveable_type) { info@54: slice = blob.slice || blob.webkitSlice; info@54: blob = slice.call(blob, 0, blob.size, force_saveable_type); info@54: blob_changed = true; info@54: } info@54: // Since I can't be sure that the guessed media type will trigger a download info@54: // in WebKit, I append .download to the filename. info@54: // https://bugs.webkit.org/show_bug.cgi?id=65440 info@54: if (webkit_req_fs && name !== "download") { info@54: name += ".download"; info@54: } info@54: if (type === force_saveable_type || webkit_req_fs) { info@54: target_view = view; info@54: } info@54: if (!req_fs) { info@54: fs_error(); info@54: return; info@54: } info@54: fs_min_size += blob.size; info@54: req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { info@54: fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { info@54: var save = function() { info@54: dir.getFile(name, create_if_not_found, abortable(function(file) { info@54: file.createWriter(abortable(function(writer) { info@54: writer.onwriteend = function(event) { info@54: target_view.location.href = file.toURL(); info@54: deletion_queue.push(file); info@54: filesaver.readyState = filesaver.DONE; info@54: dispatch(filesaver, "writeend", event); info@54: }; info@54: writer.onerror = function() { info@54: var error = writer.error; info@54: if (error.code !== error.ABORT_ERR) { info@54: fs_error(); info@54: } info@54: }; info@54: "writestart progress write abort".split(" ").forEach(function(event) { info@54: writer["on" + event] = filesaver["on" + event]; info@54: }); info@54: writer.write(blob); info@54: filesaver.abort = function() { info@54: writer.abort(); info@54: filesaver.readyState = filesaver.DONE; info@54: }; info@54: filesaver.readyState = filesaver.WRITING; info@54: }), fs_error); info@54: }), fs_error); info@54: }; info@54: dir.getFile(name, {create: false}, abortable(function(file) { info@54: // delete file if it already exists info@54: file.remove(); info@54: save(); info@54: }), abortable(function(ex) { info@54: if (ex.code === ex.NOT_FOUND_ERR) { info@54: save(); info@54: } else { info@54: fs_error(); info@54: } info@54: })); info@54: }), fs_error); info@54: }), fs_error); info@54: } info@54: , FS_proto = FileSaver.prototype info@54: , saveAs = function(blob, name) { info@54: return new FileSaver(blob, name); info@54: } info@54: ; info@54: FS_proto.abort = function() { info@54: var filesaver = this; info@54: filesaver.readyState = filesaver.DONE; info@54: dispatch(filesaver, "abort"); info@54: }; info@54: FS_proto.readyState = FS_proto.INIT = 0; info@54: FS_proto.WRITING = 1; info@54: FS_proto.DONE = 2; info@54: info@54: FS_proto.error = info@54: FS_proto.onwritestart = info@54: FS_proto.onprogress = info@54: FS_proto.onwrite = info@54: FS_proto.onabort = info@54: FS_proto.onerror = info@54: FS_proto.onwriteend = info@54: null; info@54: info@54: view.addEventListener("unload", process_deletion_queue, false); info@54: return saveAs; info@54: }(this.self || this.window || this.content)); info@54: // `self` is undefined in Firefox for Android content script context info@54: // while `this` is nsIContentFrameMessageManager info@54: // with an attribute `content` that corresponds to the window info@54: info@54: if (typeof module !== 'undefined') module.exports = saveAs;