Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / bindings / TempFile.js
blob60334c47fa26fb3897152b5dd129eb406cdba642
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
31 window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
33 /**
34  * @constructor
35  */
36 WebInspector.TempFile = function()
38     this._fileEntry = null;
39     this._writer = null;
42 /**
43  * @param {string} dirPath
44  * @param {string} name
45  * @return {!Promise.<!WebInspector.TempFile>}
46  */
47 WebInspector.TempFile.create = function(dirPath, name)
49     var file = new WebInspector.TempFile();
51     function requestTempFileSystem()
52     {
53         return new Promise(window.requestFileSystem.bind(window, window.TEMPORARY, 10));
54     }
56     /**
57      * @param {!FileSystem} fs
58      */
59     function getDirectoryEntry(fs)
60     {
61         return new Promise(fs.root.getDirectory.bind(fs.root, dirPath, { create: true }));
62     }
64     /**
65      * @param {!DirectoryEntry} dir
66      */
67     function getFileEntry(dir)
68     {
69         return new Promise(dir.getFile.bind(dir, name, { create: true }));
70     }
72     /**
73      * @param {!FileEntry} fileEntry
74      */
75     function createFileWriter(fileEntry)
76     {
77         file._fileEntry = fileEntry;
78         return new Promise(fileEntry.createWriter.bind(fileEntry));
79     }
81     /**
82      * @param {!FileWriter} writer
83      */
84     function truncateFile(writer)
85     {
86         if (!writer.length) {
87             file._writer = writer;
88             return Promise.resolve(file);
89         }
91         /**
92          * @param {function(?)} fulfill
93          * @param {function(*)} reject
94          */
95         function truncate(fulfill, reject)
96         {
97             writer.onwriteend = fulfill;
98             writer.onerror = reject;
99             writer.truncate(0);
100         }
102         function didTruncate()
103         {
104             file._writer = writer;
105             writer.onwriteend = null;
106             writer.onerror = null;
107             return Promise.resolve(file);
108         }
110         function onTruncateError(e)
111         {
112             writer.onwriteend = null;
113             writer.onerror = null;
114             throw e;
115         }
117         return new Promise(truncate).then(didTruncate, onTruncateError);
118     }
120     return WebInspector.TempFile.ensureTempStorageCleared()
121         .then(requestTempFileSystem)
122         .then(getDirectoryEntry)
123         .then(getFileEntry)
124         .then(createFileWriter)
125         .then(truncateFile);
128 WebInspector.TempFile.prototype = {
129     /**
130      * @param {!Array.<string>} strings
131      * @param {function(number)} callback
132      */
133     write: function(strings, callback)
134     {
135         var blob = new Blob(strings, {type: 'text/plain'});
136         this._writer.onerror = function(e)
137         {
138             WebInspector.console.error("Failed to write into a temp file: " + e.target.error.message);
139             callback(-1);
140         }
141         this._writer.onwriteend = function(e)
142         {
143             callback(e.target.length);
144         }
145         this._writer.write(blob);
146     },
148     finishWriting: function()
149     {
150         this._writer = null;
151     },
153     /**
154      * @param {function(?string)} callback
155      */
156     read: function(callback)
157     {
158         this.readRange(undefined, undefined, callback);
159     },
161     /**
162      * @param {number|undefined} startOffset
163      * @param {number|undefined} endOffset
164      * @param {function(?string)} callback
165      */
166     readRange: function(startOffset, endOffset, callback)
167     {
168         /**
169          * @param {!Blob} file
170          */
171         function didGetFile(file)
172         {
173             var reader = new FileReader();
175             if (typeof startOffset === "number" || typeof endOffset === "number")
176                 file = file.slice(/** @type {number} */ (startOffset), /** @type {number} */ (endOffset));
177             /**
178              * @this {FileReader}
179              */
180             reader.onloadend = function(e)
181             {
182                 callback(/** @type {?string} */ (this.result));
183             };
184             reader.onerror = function(error)
185             {
186                 WebInspector.console.error("Failed to read from temp file: " + error.message);
187             };
188             reader.readAsText(file);
189         }
190         function didFailToGetFile(error)
191         {
192             WebInspector.console.error("Failed to load temp file: " + error.message);
193             callback(null);
194         }
195         this._fileEntry.file(didGetFile, didFailToGetFile);
196     },
198     /**
199      * @param {!WebInspector.OutputStream} outputStream
200      * @param {!WebInspector.OutputStreamDelegate} delegate
201      */
202     writeToOutputSteam: function(outputStream, delegate)
203     {
204         /**
205          * @param {!File} file
206          */
207         function didGetFile(file)
208         {
209             var reader = new WebInspector.ChunkedFileReader(file, 10*1000*1000, delegate);
210             reader.start(outputStream);
211         }
213         function didFailToGetFile(error)
214         {
215             WebInspector.console.error("Failed to load temp file: " + error.message);
216             outputStream.close();
217         }
219         this._fileEntry.file(didGetFile, didFailToGetFile);
220     },
222     remove: function()
223     {
224         if (this._fileEntry)
225             this._fileEntry.remove(function() {});
226     }
230  * @constructor
231  * @param {string} dirPath
232  * @param {string} name
233  */
234 WebInspector.DeferredTempFile = function(dirPath, name)
236     /** @type {!Array.<!{strings: !Array.<string>, callback: function(number)}>} */
237     this._chunks = [];
238     this._tempFile = null;
239     this._isWriting = false;
240     this._finishCallback = null;
241     this._finishedWriting = false;
242     this._callsPendingOpen = [];
243     this._pendingReads = [];
244     WebInspector.TempFile.create(dirPath, name)
245         .then(this._didCreateTempFile.bind(this), this._failedToCreateTempFile.bind(this));
248 WebInspector.DeferredTempFile.prototype = {
249     /**
250      * @param {!Array.<string>} strings
251      * @param {function(number)=} callback
252      */
253     write: function(strings, callback)
254     {
255         if (!this._chunks)
256             return;
257         if (this._finishCallback)
258             throw new Error("No writes are allowed after close.");
259         this._chunks.push({strings: strings, callback: callback});
260         if (this._tempFile && !this._isWriting)
261             this._writeNextChunk();
262     },
264     /**
265      * @param {function(?WebInspector.TempFile)} callback
266      */
267     finishWriting: function(callback)
268     {
269         this._finishCallback = callback;
270         if (this._finishedWriting)
271             callback(this._tempFile);
272         else if (!this._isWriting && !this._chunks.length)
273             this._notifyFinished();
274     },
276     /**
277      * @param {*} e
278      */
279     _failedToCreateTempFile: function(e)
280     {
281         WebInspector.console.error("Failed to create temp file " + e.code + " : " + e.message);
282         this._notifyFinished();
283     },
285     /**
286      * @param {!WebInspector.TempFile} tempFile
287      */
288     _didCreateTempFile: function(tempFile)
289     {
290         this._tempFile = tempFile;
291         var callsPendingOpen = this._callsPendingOpen;
292         this._callsPendingOpen = null;
293         for (var i = 0; i < callsPendingOpen.length; ++i)
294             callsPendingOpen[i]();
295         if (this._chunks.length)
296             this._writeNextChunk();
297     },
299     _writeNextChunk: function()
300     {
301         // File was deleted while create or write was in-flight.
302         if (!this._tempFile)
303             return;
304         var chunk = this._chunks.shift();
305         this._isWriting = true;
306         this._tempFile.write(/** @type {!Array.<string>} */(chunk.strings), this._didWriteChunk.bind(this, chunk.callback));
307     },
309     /**
310      * @param {?function(number)} callback
311      * @param {number} size
312      */
313     _didWriteChunk: function(callback, size)
314     {
315         this._isWriting = false;
316         if (size === -1) {
317             this._tempFile = null;
318             this._notifyFinished();
319             return;
320         }
321         if (callback)
322             callback(size);
323         if (this._chunks.length)
324             this._writeNextChunk();
325         else if (this._finishCallback)
326             this._notifyFinished();
327     },
329     _notifyFinished: function()
330     {
331         this._finishedWriting = true;
332         if (this._tempFile)
333             this._tempFile.finishWriting();
334         var chunks = this._chunks;
335         this._chunks = [];
336         for (var i = 0; i < chunks.length; ++i) {
337             if (chunks[i].callback)
338                 chunks[i].callback(-1);
339         }
340         if (this._finishCallback)
341             this._finishCallback(this._tempFile);
342         var pendingReads = this._pendingReads;
343         this._pendingReads = [];
344         for (var i = 0; i < pendingReads.length; ++i)
345             pendingReads[i]();
346     },
348     /**
349      * @param {number|undefined} startOffset
350      * @param {number|undefined} endOffset
351      * @param {function(string?)} callback
352      */
353     readRange: function(startOffset, endOffset, callback)
354     {
355         if (!this._finishedWriting) {
356             this._pendingReads.push(this.readRange.bind(this, startOffset, endOffset, callback));
357             return;
358         }
359         if (!this._tempFile) {
360             callback(null);
361             return;
362         }
363         this._tempFile.readRange(startOffset, endOffset, callback);
364     },
366     /**
367      * @param {!WebInspector.OutputStream} outputStream
368      * @param {!WebInspector.OutputStreamDelegate} delegate
369      */
370     writeToOutputStream: function(outputStream, delegate)
371     {
372         if (this._callsPendingOpen) {
373             this._callsPendingOpen.push(this.writeToOutputStream.bind(this, outputStream, delegate));
374             return;
375         }
376         if (this._tempFile)
377             this._tempFile.writeToOutputSteam(outputStream, delegate);
378     },
380     remove: function()
381     {
382         if (this._callsPendingOpen) {
383             this._callsPendingOpen.push(this.remove.bind(this));
384             return;
385         }
386         if (this._tempFile)
387             this._tempFile.remove();
388         this._tempFile = null;
389     }
393  * @param {function(?)} fulfill
394  * @param {function(*)} reject
395  */
396 WebInspector.TempFile._clearTempStorage = function(fulfill, reject)
398     /**
399      * @param {!Event} event
400      */
401     function handleError(event)
402     {
403         WebInspector.console.error(WebInspector.UIString("Failed to clear temp storage: %s", event.data));
404         reject(event.data);
405     }
407     /**
408      * @param {!Event} event
409      */
410     function handleMessage(event)
411     {
412         if (event.data.type === "tempStorageCleared") {
413             if (event.data.error)
414                 WebInspector.console.error(event.data.error);
415             else
416                 fulfill(undefined);
417             return;
418         }
419         reject(event.data);
420     }
422     try {
423         var worker = new WorkerRuntime.Worker("temp_storage_shared_worker", "TempStorageCleaner");
424         worker.onerror = handleError;
425         worker.port.onmessage = handleMessage;
426         worker.port.onerror = handleError;
427     } catch (e) {
428         if (e.name === "URLMismatchError")
429             console.log("Shared worker wasn't started due to url difference. " + e);
430         else
431             throw e;
432     }
436  * @return {!Promise.<undefined>}
437  */
438 WebInspector.TempFile.ensureTempStorageCleared = function()
440     if (!WebInspector.TempFile._storageCleanerPromise)
441         WebInspector.TempFile._storageCleanerPromise = new Promise(WebInspector.TempFile._clearTempStorage);
442     return WebInspector.TempFile._storageCleanerPromise;
446  * @constructor
447  * @implements {WebInspector.BackingStorage}
448  * @param {string} dirName
449  */
450 WebInspector.TempFileBackingStorage = function(dirName)
452     this._dirName = dirName;
453     this.reset();
457  * @typedef {{
458  *      string: ?string,
459  *      startOffset: number,
460  *      endOffset: number
461  * }}
462  */
463 WebInspector.TempFileBackingStorage.Chunk;
465 WebInspector.TempFileBackingStorage.prototype = {
466     /**
467      * @override
468      * @param {string} string
469      */
470     appendString: function(string)
471     {
472         this._strings.push(string);
473         this._stringsLength += string.length;
474         var flushStringLength = 10 * 1024 * 1024;
475         if (this._stringsLength > flushStringLength)
476             this._flush(false);
477     },
479     /**
480      * @override
481      * @param {string} string
482      * @return {function():!Promise.<?string>}
483      */
484     appendAccessibleString: function(string)
485     {
486         this._flush(false);
487         this._strings.push(string);
488         var chunk = /** @type {!WebInspector.TempFileBackingStorage.Chunk} */ (this._flush(true));
490         /**
491          * @param {!WebInspector.TempFileBackingStorage.Chunk} chunk
492          * @param {!WebInspector.DeferredTempFile} file
493          * @return {!Promise.<?string>}
494          */
495         function readString(chunk, file)
496         {
497             if (chunk.string)
498                 return /** @type {!Promise.<?string>} */ (Promise.resolve(chunk.string));
500             console.assert(chunk.endOffset);
501             if (!chunk.endOffset)
502                 return Promise.reject("Nor string nor offset to the string in the file were found.");
504             /**
505              * @param {function(?string)} fulfill
506              * @param {function(*)} reject
507              */
508             function readRange(fulfill, reject)
509             {
510                 // FIXME: call reject for null strings.
511                 file.readRange(chunk.startOffset, chunk.endOffset, fulfill);
512             }
514             return new Promise(readRange);
515         }
517         return readString.bind(null, chunk, this._file);
518     },
520     /**
521      * @param {boolean} createChunk
522      * @return {?WebInspector.TempFileBackingStorage.Chunk}
523      */
524     _flush: function(createChunk)
525     {
526         if (!this._strings.length)
527             return null;
529         var chunk = null;
530         if (createChunk) {
531             console.assert(this._strings.length === 1);
532             chunk = {
533                 string: this._strings[0],
534                 startOffset: 0,
535                 endOffset: 0
536             };
537         }
539         /**
540          * @this {WebInspector.TempFileBackingStorage}
541          * @param {?WebInspector.TempFileBackingStorage.Chunk} chunk
542          * @param {number} fileSize
543          */
544         function didWrite(chunk, fileSize)
545         {
546             if (fileSize === -1)
547                 return;
548             if (chunk) {
549                 chunk.startOffset = this._fileSize;
550                 chunk.endOffset = fileSize;
551                 chunk.string = null;
552             }
553             this._fileSize = fileSize;
554         }
556         this._file.write(this._strings, didWrite.bind(this, chunk));
557         this._strings = [];
558         this._stringsLength = 0;
559         return chunk;
560     },
562     /**
563      * @override
564      */
565     finishWriting: function()
566     {
567         this._flush(false);
568         this._file.finishWriting(function() {});
569     },
571     /**
572      * @override
573      */
574     reset: function()
575     {
576         if (this._file)
577             this._file.remove();
578         this._file = new WebInspector.DeferredTempFile(this._dirName, String(Date.now()));
579         /**
580          * @type {!Array.<string>}
581          */
582         this._strings = [];
583         this._stringsLength = 0;
584         this._fileSize = 0;
585     },
587     /**
588      * @param {!WebInspector.OutputStream} outputStream
589      * @param {!WebInspector.OutputStreamDelegate} delegate
590      */
591     writeToStream: function(outputStream, delegate)
592     {
593         this._file.writeToOutputStream(outputStream, delegate);
594     }