Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2012 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  */
     30 
     31 /**
     32  * @interface
     33  */
     34 WebInspector.OutputStreamDelegate = function()
     35 {
     36 }
     37 
     38 WebInspector.OutputStreamDelegate.prototype = {
     39     onTransferStarted: function() { },
     40 
     41     onTransferFinished: function() { },
     42 
     43     /**
     44      * @param {WebInspector.ChunkedReader} reader
     45      */
     46     onChunkTransferred: function(reader) { },
     47 
     48     /**
     49      * @param {WebInspector.ChunkedReader} reader
     50      */
     51     onError: function(reader, event) { },
     52 }
     53 
     54 /**
     55  * @interface
     56  */
     57 WebInspector.OutputStream = function()
     58 {
     59 }
     60 
     61 WebInspector.OutputStream.prototype = {
     62     /**
     63      * @param {string} data
     64      * @param {function(WebInspector.OutputStream)=} callback
     65      */
     66     write: function(data, callback) { },
     67 
     68     close: function() { }
     69 }
     70 
     71 /**
     72  * @interface
     73  */
     74 WebInspector.ChunkedReader = function()
     75 {
     76 }
     77 
     78 WebInspector.ChunkedReader.prototype = {
     79     /**
     80      * @return {number}
     81      */
     82     fileSize: function() { },
     83 
     84     /**
     85      * @return {number}
     86      */
     87     loadedSize: function() { },
     88 
     89     /**
     90      * @return {string}
     91      */
     92     fileName: function() { },
     93 
     94     cancel: function() { }
     95 }
     96 
     97 /**
     98  * @constructor
     99  * @implements {WebInspector.ChunkedReader}
    100  * @param {!File} file
    101  * @param {number} chunkSize
    102  * @param {!WebInspector.OutputStreamDelegate} delegate
    103  */
    104 WebInspector.ChunkedFileReader = function(file, chunkSize, delegate)
    105 {
    106     this._file = file;
    107     this._fileSize = file.size;
    108     this._loadedSize = 0;
    109     this._chunkSize = chunkSize;
    110     this._delegate = delegate;
    111     this._isCanceled = false;
    112 }
    113 
    114 WebInspector.ChunkedFileReader.prototype = {
    115     /**
    116      * @param {!WebInspector.OutputStream} output
    117      */
    118     start: function(output)
    119     {
    120         this._output = output;
    121 
    122         this._reader = new FileReader();
    123         this._reader.onload = this._onChunkLoaded.bind(this);
    124         this._reader.onerror = this._delegate.onError.bind(this._delegate, this);
    125         this._delegate.onTransferStarted();
    126         this._loadChunk();
    127     },
    128 
    129     cancel: function()
    130     {
    131         this._isCanceled = true;
    132     },
    133 
    134     /**
    135      * @return {number}
    136      */
    137     loadedSize: function()
    138     {
    139         return this._loadedSize;
    140     },
    141 
    142     /**
    143      * @return {number}
    144      */
    145     fileSize: function()
    146     {
    147         return this._fileSize;
    148     },
    149 
    150     /**
    151      * @return {string}
    152      */
    153     fileName: function()
    154     {
    155         return this._file.name;
    156     },
    157 
    158     /**
    159      * @param {Event} event
    160      */
    161     _onChunkLoaded: function(event)
    162     {
    163         if (this._isCanceled)
    164             return;
    165 
    166         if (event.target.readyState !== FileReader.DONE)
    167             return;
    168 
    169         var data = event.target.result;
    170         this._loadedSize += data.length;
    171 
    172         this._output.write(data);
    173         if (this._isCanceled)
    174             return;
    175         this._delegate.onChunkTransferred(this);
    176 
    177         if (this._loadedSize === this._fileSize) {
    178             this._file = null;
    179             this._reader = null;
    180             this._output.close();
    181             this._delegate.onTransferFinished();
    182             return;
    183         }
    184 
    185         this._loadChunk();
    186     },
    187 
    188     _loadChunk: function()
    189     {
    190         var chunkStart = this._loadedSize;
    191         var chunkEnd = Math.min(this._fileSize, chunkStart + this._chunkSize)
    192         var nextPart = this._file.slice(chunkStart, chunkEnd);
    193         this._reader.readAsText(nextPart);
    194     }
    195 }
    196 
    197 /**
    198  * @constructor
    199  * @implements {WebInspector.ChunkedReader}
    200  * @param {string} url
    201  * @param {!WebInspector.OutputStreamDelegate} delegate
    202  */
    203 WebInspector.ChunkedXHRReader = function(url, delegate)
    204 {
    205     this._url = url;
    206     this._delegate = delegate;
    207     this._fileSize = 0;
    208     this._loadedSize = 0;
    209     this._isCanceled = false;
    210 }
    211 
    212 WebInspector.ChunkedXHRReader.prototype = {
    213     /**
    214      * @param {!WebInspector.OutputStream} output
    215      */
    216     start: function(output)
    217     {
    218         this._output = output;
    219 
    220         this._xhr = new XMLHttpRequest();
    221         this._xhr.open("GET", this._url, true);
    222         this._xhr.onload = this._onLoad.bind(this);
    223         this._xhr.onprogress = this._onProgress.bind(this);
    224         this._xhr.onerror = this._delegate.onError.bind(this._delegate, this);
    225         this._xhr.send(null);
    226 
    227         this._delegate.onTransferStarted();
    228     },
    229 
    230     cancel: function()
    231     {
    232         this._isCanceled = true;
    233         this._xhr.abort();
    234     },
    235 
    236     /**
    237      * @return {number}
    238      */
    239     loadedSize: function()
    240     {
    241         return this._loadedSize;
    242     },
    243 
    244     /**
    245      * @return {number}
    246      */
    247     fileSize: function()
    248     {
    249         return this._fileSize;
    250     },
    251 
    252     /**
    253      * @return {string}
    254      */
    255     fileName: function()
    256     {
    257         return this._url;
    258     },
    259 
    260     /**
    261      * @param {Event} event
    262      */
    263     _onProgress: function(event)
    264     {
    265         if (this._isCanceled)
    266             return;
    267 
    268         if (event.lengthComputable)
    269             this._fileSize = event.total;
    270 
    271         var data = this._xhr.responseText.substring(this._loadedSize);
    272         if (!data.length)
    273             return;
    274 
    275         this._loadedSize += data.length;
    276         this._output.write(data);
    277         if (this._isCanceled)
    278             return;
    279         this._delegate.onChunkTransferred(this);
    280     },
    281 
    282     /**
    283      * @param {Event} event
    284      */
    285     _onLoad: function(event)
    286     {
    287         this._onProgress(event);
    288 
    289         if (this._isCanceled)
    290             return;
    291 
    292         this._output.close();
    293         this._delegate.onTransferFinished();
    294     }
    295 }
    296 
    297 /**
    298  * @param {function(!File)} callback
    299  * @return {Node}
    300  */
    301 WebInspector.createFileSelectorElement = function(callback) {
    302     var fileSelectorElement = document.createElement("input");
    303     fileSelectorElement.type = "file";
    304     fileSelectorElement.style.display = "none";
    305     fileSelectorElement.setAttribute("tabindex", -1);
    306     fileSelectorElement.onchange = onChange;
    307     function onChange(event)
    308     {
    309         callback(fileSelectorElement.files[0]);
    310     };
    311     return fileSelectorElement;
    312 }
    313 
    314 /**
    315  * @param {string} source
    316  * @param {number=} startIndex
    317  * @param {number=} lastIndex
    318  */
    319 WebInspector.findBalancedCurlyBrackets = function(source, startIndex, lastIndex) {
    320     lastIndex = lastIndex || source.length;
    321     startIndex = startIndex || 0;
    322     var counter = 0;
    323     var inString = false;
    324 
    325     for (var index = startIndex; index < lastIndex; ++index) {
    326         var character = source[index];
    327         if (inString) {
    328             if (character === "\\")
    329                 ++index;
    330             else if (character === "\"")
    331                 inString = false;
    332         } else {
    333             if (character === "\"")
    334                 inString = true;
    335             else if (character === "{")
    336                 ++counter;
    337             else if (character === "}") {
    338                 if (--counter === 0)
    339                     return index + 1;
    340             }
    341         }
    342     }
    343     return -1;
    344 }
    345 
    346 /**
    347  * @constructor
    348  * @implements {WebInspector.OutputStream}
    349  */
    350 WebInspector.FileOutputStream = function()
    351 {
    352 }
    353 
    354 WebInspector.FileOutputStream.prototype = {
    355     /**
    356      * @param {string} fileName
    357      * @param {function(WebInspector.FileOutputStream, string=)} callback
    358      */
    359     open: function(fileName, callback)
    360     {
    361         this._closed = false;
    362         this._writeCallbacks = [];
    363         this._fileName = fileName;
    364         function callbackWrapper()
    365         {
    366             WebInspector.fileManager.removeEventListener(WebInspector.FileManager.EventTypes.SavedURL, callbackWrapper, this);
    367             WebInspector.fileManager.addEventListener(WebInspector.FileManager.EventTypes.AppendedToURL, this._onAppendDone, this);
    368             callback(this);
    369         }
    370         WebInspector.fileManager.addEventListener(WebInspector.FileManager.EventTypes.SavedURL, callbackWrapper, this);
    371         WebInspector.fileManager.save(this._fileName, "", true);
    372     },
    373 
    374     /**
    375      * @param {string} data
    376      * @param {function(WebInspector.OutputStream)=} callback
    377      */
    378     write: function(data, callback)
    379     {
    380         this._writeCallbacks.push(callback);
    381         WebInspector.fileManager.append(this._fileName, data);
    382     },
    383 
    384     close: function()
    385     {
    386         this._closed = true;
    387         if (this._writeCallbacks.length)
    388             return;
    389         WebInspector.fileManager.removeEventListener(WebInspector.FileManager.EventTypes.AppendedToURL, this._onAppendDone, this);
    390         WebInspector.fileManager.close(this._fileName);
    391     },
    392 
    393     /**
    394      * @param {Event} event
    395      */
    396     _onAppendDone: function(event)
    397     {
    398         if (event.data !== this._fileName)
    399             return;
    400         if (!this._writeCallbacks.length) {
    401             if (this._closed) {
    402                 WebInspector.fileManager.removeEventListener(WebInspector.FileManager.EventTypes.AppendedToURL, this._onAppendDone, this);
    403                 WebInspector.fileManager.close(this._fileName);
    404             }
    405             return;
    406         }
    407         var callback = this._writeCallbacks.shift();
    408         if (callback)
    409             callback(this);
    410     }
    411 }
    412