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  * @constructor
     33  * @implements {WebInspector.SourceMapping}
     34  * @param {WebInspector.CSSStyleModel} cssModel
     35  * @param {WebInspector.Workspace} workspace
     36  * @param {WebInspector.SimpleWorkspaceProvider} networkWorkspaceProvider
     37  */
     38 WebInspector.SASSSourceMapping = function(cssModel, workspace, networkWorkspaceProvider)
     39 {
     40     this.pollPeriodMs = 5000;
     41     this.pollIntervalMs = 200;
     42 
     43     this._cssModel = cssModel;
     44     this._workspace = workspace;
     45     this._networkWorkspaceProvider = networkWorkspaceProvider;
     46     this._addingRevisionCounter = 0;
     47     this._reset();
     48     WebInspector.fileManager.addEventListener(WebInspector.FileManager.EventTypes.SavedURL, this._fileSaveFinished, this);
     49     WebInspector.settings.cssSourceMapsEnabled.addChangeListener(this._toggleSourceMapSupport, this)
     50     this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
     51     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
     52     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, this._uiSourceCodeContentCommitted, this);
     53     this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._reset, this);
     54 }
     55 
     56 WebInspector.SASSSourceMapping.prototype = {
     57     /**
     58      * @param {WebInspector.Event} event
     59      */
     60     _styleSheetChanged: function(event)
     61     {
     62         var id = /** @type {!CSSAgent.StyleSheetId} */ (event.data.styleSheetId);
     63         if (this._addingRevisionCounter) {
     64             --this._addingRevisionCounter;
     65             return;
     66         }
     67         var header = this._cssModel.styleSheetHeaderForId(id);
     68         if (!header)
     69             return;
     70 
     71         this.removeHeader(header);
     72     },
     73 
     74     /**
     75      * @param {WebInspector.Event} event
     76      */
     77     _toggleSourceMapSupport: function(event)
     78     {
     79         var enabled = /** @type {boolean} */ (event.data);
     80         var headers = this._cssModel.styleSheetHeaders();
     81         for (var i = 0; i < headers.length; ++i) {
     82             if (enabled)
     83                 this.addHeader(headers[i]);
     84             else
     85                 this.removeHeader(headers[i]);
     86         }
     87     },
     88 
     89     /**
     90      * @param {WebInspector.Event} event
     91      */
     92     _fileSaveFinished: function(event)
     93     {
     94         var sassURL = /** @type {string} */ (event.data);
     95         this._sassFileSaved(sassURL, false);
     96     },
     97 
     98     /**
     99      * @param {string} headerName
    100      * @param {NetworkAgent.Headers} headers
    101      * @return {?string}
    102      */
    103     _headerValue: function(headerName, headers)
    104     {
    105         headerName = headerName.toLowerCase();
    106         var value = null;
    107         for (var name in headers) {
    108             if (name.toLowerCase() === headerName) {
    109                 value = headers[name];
    110                 break;
    111             }
    112         }
    113         return value;
    114     },
    115 
    116     /**
    117      * @param {NetworkAgent.Headers} headers
    118      * @return {?Date}
    119      */
    120     _lastModified: function(headers)
    121     {
    122         var lastModifiedHeader = this._headerValue("last-modified", headers);
    123         if (!lastModifiedHeader)
    124             return null;
    125         var lastModified = new Date(lastModifiedHeader);
    126         if (isNaN(lastModified.getTime()))
    127             return null;
    128         return lastModified;
    129     },
    130 
    131     /**
    132      * @param {NetworkAgent.Headers} headers
    133      * @param {string} url
    134      * @return {?Date}
    135      */
    136     _checkLastModified: function(headers, url)
    137     {
    138         var lastModified = this._lastModified(headers);
    139         if (lastModified)
    140             return lastModified;
    141 
    142         var etagMessage = this._headerValue("etag", headers) ? ", \"ETag\" response header found instead" : "";
    143         var message = String.sprintf("The \"Last-Modified\" response header is missing or invalid for %s%s. The CSS auto-reload functionality will not work correctly.", url, etagMessage);
    144         WebInspector.log(message);
    145         return null;
    146     },
    147 
    148     /**
    149      * @param {string} sassURL
    150      * @param {boolean} wasLoadedFromFileSystem
    151      */
    152     _sassFileSaved: function(sassURL, wasLoadedFromFileSystem)
    153     {
    154         var cssURLs = this._cssURLsForSASSURL[sassURL];
    155         if (!cssURLs)
    156             return;
    157         if (!WebInspector.settings.cssReloadEnabled.get())
    158             return;
    159 
    160         var sassFile = this._workspace.uiSourceCodeForURL(sassURL);
    161         console.assert(sassFile);
    162         if (wasLoadedFromFileSystem)
    163             sassFile.requestMetadata(metadataReceived.bind(this));
    164         else
    165             NetworkAgent.loadResourceForFrontend(WebInspector.resourceTreeModel.mainFrame.id, sassURL, undefined, sassLoadedViaNetwork.bind(this));
    166 
    167         /**
    168          * @param {?Protocol.Error} error
    169          * @param {number} statusCode
    170          * @param {NetworkAgent.Headers} headers
    171          * @param {string} content
    172          */
    173         function sassLoadedViaNetwork(error, statusCode, headers, content)
    174         {
    175             if (error || statusCode >= 400) {
    176                 console.error("Could not load content for " + sassURL + " : " + (error || ("HTTP status code: " + statusCode)));
    177                 return;
    178             }
    179             var lastModified = this._checkLastModified(headers, sassURL);
    180             if (!lastModified)
    181                 return;
    182             metadataReceived.call(this, lastModified);
    183         }
    184 
    185         /**
    186          * @param {?Date} timestamp
    187          */
    188         function metadataReceived(timestamp)
    189         {
    190             if (!timestamp)
    191                 return;
    192 
    193             var now = Date.now();
    194             var deadlineMs = now + this.pollPeriodMs;
    195             var pollData = this._pollDataForSASSURL[sassURL];
    196             if (pollData) {
    197                 var dataByURL = pollData.dataByURL;
    198                 for (var url in dataByURL)
    199                     clearTimeout(dataByURL[url].timer);
    200             }
    201             pollData = { dataByURL: {}, deadlineMs: deadlineMs, sassTimestamp: timestamp };
    202             this._pollDataForSASSURL[sassURL] = pollData;
    203             for (var i = 0; i < cssURLs.length; ++i) {
    204                 pollData.dataByURL[cssURLs[i]] = { previousPoll: now };
    205                 this._pollCallback(cssURLs[i], sassURL, false);
    206             }
    207         }
    208     },
    209 
    210     /**
    211      * @param {string} cssURL
    212      * @param {string} sassURL
    213      * @param {boolean} stopPolling
    214      */
    215     _pollCallback: function(cssURL, sassURL, stopPolling)
    216     {
    217         var now;
    218         var pollData = this._pollDataForSASSURL[sassURL];
    219         if (!pollData)
    220             return;
    221 
    222         if (stopPolling || (now = new Date().getTime()) > pollData.deadlineMs) {
    223             delete pollData.dataByURL[cssURL];
    224             if (!Object.keys(pollData.dataByURL).length)
    225                 delete this._pollDataForSASSURL[sassURL];
    226             return;
    227         }
    228         var nextPoll = this.pollIntervalMs + pollData.dataByURL[cssURL].previousPoll;
    229         var remainingTimeoutMs = Math.max(0, nextPoll - now);
    230         pollData.dataByURL[cssURL].previousPoll = now + remainingTimeoutMs;
    231         pollData.dataByURL[cssURL].timer = setTimeout(this._reloadCSS.bind(this, cssURL, sassURL, this._pollCallback.bind(this)), remainingTimeoutMs);
    232     },
    233 
    234     /**
    235      * @param {string} cssURL
    236      * @param {string} sassURL
    237      * @param {function(string, string, boolean)} callback
    238      */
    239     _reloadCSS: function(cssURL, sassURL, callback)
    240     {
    241         var cssUISourceCode = this._workspace.uiSourceCodeForURL(cssURL);
    242         if (!cssUISourceCode) {
    243             WebInspector.log(cssURL + " resource missing. Please reload the page.");
    244             callback(cssURL, sassURL, true);
    245             return;
    246         }
    247 
    248         if (this._workspace.hasMappingForURL(sassURL))
    249             this._reloadCSSFromFileSystem(cssUISourceCode, sassURL, callback);
    250         else
    251             this._reloadCSSFromNetwork(cssUISourceCode, sassURL, callback);
    252     },
    253 
    254     /**
    255      * @param {WebInspector.UISourceCode} cssUISourceCode
    256      * @param {string} sassURL
    257      * @param {function(string, string, boolean)} callback
    258      */
    259     _reloadCSSFromNetwork: function(cssUISourceCode, sassURL, callback)
    260     {
    261         var cssURL = cssUISourceCode.url;
    262         var data = this._pollDataForSASSURL[sassURL];
    263         if (!data) {
    264             callback(cssURL, sassURL, true);
    265             return;
    266         }
    267         var headers = { "if-modified-since": new Date(data.sassTimestamp.getTime() - 1000).toUTCString() };
    268         NetworkAgent.loadResourceForFrontend(WebInspector.resourceTreeModel.mainFrame.id, cssURL, headers, contentLoaded.bind(this));
    269 
    270         /**
    271          * @param {?Protocol.Error} error
    272          * @param {number} statusCode
    273          * @param {NetworkAgent.Headers} headers
    274          * @param {string} content
    275          */
    276         function contentLoaded(error, statusCode, headers, content)
    277         {
    278             if (error || statusCode >= 400) {
    279                 console.error("Could not load content for " + cssURL + " : " + (error || ("HTTP status code: " + statusCode)));
    280                 callback(cssURL, sassURL, true);
    281                 return;
    282             }
    283             if (!this._pollDataForSASSURL[sassURL]) {
    284                 callback(cssURL, sassURL, true);
    285                 return;
    286             }
    287             if (statusCode === 304) {
    288                 callback(cssURL, sassURL, false);
    289                 return;
    290             }
    291             var lastModified = this._checkLastModified(headers, cssURL);
    292             if (!lastModified) {
    293                 callback(cssURL, sassURL, true);
    294                 return;
    295             }
    296             if (lastModified.getTime() < data.sassTimestamp.getTime()) {
    297                 callback(cssURL, sassURL, false);
    298                 return;
    299             }
    300             this._updateCSSRevision(cssUISourceCode, content, sassURL, callback);
    301         }
    302     },
    303 
    304     /**
    305      * @param {WebInspector.UISourceCode} cssUISourceCode
    306      * @param {string} content
    307      * @param {string} sassURL
    308      * @param {function(string, string, boolean)} callback
    309      */
    310     _updateCSSRevision: function(cssUISourceCode, content, sassURL, callback)
    311     {
    312         ++this._addingRevisionCounter;
    313         cssUISourceCode.addRevision(content);
    314         this._cssUISourceCodeUpdated(cssUISourceCode.url, sassURL, callback);
    315     },
    316 
    317     /**
    318      * @param {WebInspector.UISourceCode} cssUISourceCode
    319      * @param {string} sassURL
    320      * @param {function(string, string, boolean)} callback
    321      */
    322     _reloadCSSFromFileSystem: function(cssUISourceCode, sassURL, callback)
    323     {
    324         cssUISourceCode.requestMetadata(metadataCallback.bind(this));
    325 
    326         /**
    327          * @param {?Date} timestamp
    328          */
    329         function metadataCallback(timestamp)
    330         {
    331             var cssURL = cssUISourceCode.url;
    332             if (!timestamp) {
    333                 callback(cssURL, sassURL, false);
    334                 return;
    335             }
    336             var cssTimestamp = timestamp.getTime();
    337             var pollData = this._pollDataForSASSURL[sassURL];
    338             if (!pollData) {
    339                 callback(cssURL, sassURL, true);
    340                 return;
    341             }
    342 
    343             if (cssTimestamp < pollData.sassTimestamp.getTime()) {
    344                 callback(cssURL, sassURL, false);
    345                 return;
    346             }
    347 
    348             cssUISourceCode.requestOriginalContent(contentCallback.bind(this));
    349             function contentCallback(content)
    350             {
    351                 // Empty string is a valid value, null means error.
    352                 if (content === null)
    353                     return;
    354                 this._updateCSSRevision(cssUISourceCode, content, sassURL, callback);
    355             }
    356         }
    357     },
    358 
    359     /**
    360      * @param {string} cssURL
    361      * @param {string} sassURL
    362      * @param {function(string, string, boolean)} callback
    363      */
    364     _cssUISourceCodeUpdated: function(cssURL, sassURL, callback)
    365     {
    366         var completeSourceMapURL = this._completeSourceMapURLForCSSURL[cssURL];
    367         if (!completeSourceMapURL)
    368             return;
    369         var ids = this._cssModel.styleSheetIdsForURL(cssURL);
    370         if (!ids)
    371             return;
    372         var headers = [];
    373         for (var i = 0; i < ids.length; ++i)
    374             headers.push(this._cssModel.styleSheetHeaderForId(ids[i]));
    375         for (var i = 0; i < ids.length; ++i)
    376             this._loadSourceMapAndBindUISourceCode(headers, true, completeSourceMapURL);
    377         callback(cssURL, sassURL, true);
    378     },
    379 
    380     /**
    381      * @param {WebInspector.CSSStyleSheetHeader} header
    382      */
    383     addHeader: function(header)
    384     {
    385         if (!header.sourceMapURL || !header.sourceURL || header.isInline || !WebInspector.settings.cssSourceMapsEnabled.get())
    386             return;
    387         var completeSourceMapURL = WebInspector.ParsedURL.completeURL(header.sourceURL, header.sourceMapURL);
    388         if (!completeSourceMapURL)
    389             return;
    390         this._completeSourceMapURLForCSSURL[header.sourceURL] = completeSourceMapURL;
    391         this._loadSourceMapAndBindUISourceCode([header], false, completeSourceMapURL);
    392     },
    393 
    394     /**
    395      * @param {WebInspector.CSSStyleSheetHeader} header
    396      */
    397     removeHeader: function(header)
    398     {
    399         var sourceURL = header.sourceURL;
    400         if (!sourceURL || !header.sourceMapURL || header.isInline || !this._completeSourceMapURLForCSSURL[sourceURL])
    401             return;
    402         delete this._sourceMapByStyleSheetURL[sourceURL];
    403         delete this._completeSourceMapURLForCSSURL[sourceURL];
    404         for (var sassURL in this._cssURLsForSASSURL) {
    405             var urls = this._cssURLsForSASSURL[sassURL];
    406             urls.remove(sourceURL);
    407             if (!urls.length)
    408                 delete this._cssURLsForSASSURL[sassURL];
    409         }
    410         var completeSourceMapURL = WebInspector.ParsedURL.completeURL(sourceURL, header.sourceMapURL);
    411         if (completeSourceMapURL)
    412             delete this._sourceMapByURL[completeSourceMapURL];
    413         header.updateLocations();
    414     },
    415 
    416     /**
    417      * @param {Array.<WebInspector.CSSStyleSheetHeader>} headersWithSameSourceURL
    418      * @param {boolean} forceRebind
    419      * @param {string} completeSourceMapURL
    420      */
    421     _loadSourceMapAndBindUISourceCode: function(headersWithSameSourceURL, forceRebind, completeSourceMapURL)
    422     {
    423         console.assert(headersWithSameSourceURL.length);
    424         var sourceURL = headersWithSameSourceURL[0].sourceURL;
    425         this._loadSourceMapForStyleSheet(completeSourceMapURL, sourceURL, forceRebind, sourceMapLoaded.bind(this));
    426 
    427         /**
    428          * @param {?WebInspector.SourceMap} sourceMap
    429          */
    430         function sourceMapLoaded(sourceMap)
    431         {
    432             if (!sourceMap)
    433                 return;
    434 
    435             this._sourceMapByStyleSheetURL[sourceURL] = sourceMap;
    436             for (var i = 0; i < headersWithSameSourceURL.length; ++i) {
    437                 if (forceRebind)
    438                     headersWithSameSourceURL[i].updateLocations();
    439                 else
    440                     this._bindUISourceCode(headersWithSameSourceURL[i], sourceMap);
    441             }
    442         }
    443     },
    444 
    445     /**
    446      * @param {string} cssURL
    447      * @param {string} sassURL
    448      */
    449     _addCSSURLforSASSURL: function(cssURL, sassURL)
    450     {
    451         var cssURLs;
    452         if (this._cssURLsForSASSURL.hasOwnProperty(sassURL))
    453             cssURLs = this._cssURLsForSASSURL[sassURL];
    454         else {
    455             cssURLs = [];
    456             this._cssURLsForSASSURL[sassURL] = cssURLs;
    457         }
    458         if (cssURLs.indexOf(cssURL) === -1)
    459             cssURLs.push(cssURL);
    460     },
    461 
    462     /**
    463      * @param {string} completeSourceMapURL
    464      * @param {string} completeStyleSheetURL
    465      * @param {boolean} forceReload
    466      * @param {function(?WebInspector.SourceMap)} callback
    467      */
    468     _loadSourceMapForStyleSheet: function(completeSourceMapURL, completeStyleSheetURL, forceReload, callback)
    469     {
    470         var sourceMap = this._sourceMapByURL[completeSourceMapURL];
    471         if (sourceMap && !forceReload) {
    472             callback(sourceMap);
    473             return;
    474         }
    475 
    476         var pendingCallbacks = this._pendingSourceMapLoadingCallbacks[completeSourceMapURL];
    477         if (pendingCallbacks) {
    478             pendingCallbacks.push(callback);
    479             return;
    480         }
    481 
    482         pendingCallbacks = [callback];
    483         this._pendingSourceMapLoadingCallbacks[completeSourceMapURL] = pendingCallbacks;
    484 
    485         WebInspector.SourceMap.load(completeSourceMapURL, completeStyleSheetURL, sourceMapLoaded.bind(this));
    486 
    487         /**
    488          * @param {?WebInspector.SourceMap} sourceMap
    489          */
    490         function sourceMapLoaded(sourceMap)
    491         {
    492             var callbacks = this._pendingSourceMapLoadingCallbacks[completeSourceMapURL];
    493             delete this._pendingSourceMapLoadingCallbacks[completeSourceMapURL];
    494             if (!callbacks)
    495                 return;
    496             if (sourceMap)
    497                 this._sourceMapByURL[completeSourceMapURL] = sourceMap;
    498             else
    499                 delete this._sourceMapByURL[completeSourceMapURL];
    500             for (var i = 0; i < callbacks.length; ++i)
    501                 callbacks[i](sourceMap);
    502         }
    503     },
    504 
    505     /**
    506      * @param {WebInspector.CSSStyleSheetHeader} header
    507      * @param {WebInspector.SourceMap} sourceMap
    508      */
    509     _bindUISourceCode: function(header, sourceMap)
    510     {
    511         header.pushSourceMapping(this);
    512         var rawURL = header.sourceURL;
    513         var sources = sourceMap.sources();
    514         for (var i = 0; i < sources.length; ++i) {
    515             var url = sources[i];
    516             this._addCSSURLforSASSURL(rawURL, url);
    517             if (!this._workspace.hasMappingForURL(url) && !this._workspace.uiSourceCodeForURL(url)) {
    518                 var contentProvider = sourceMap.sourceContentProvider(url, WebInspector.resourceTypes.Stylesheet);
    519                 var uiSourceCode = this._networkWorkspaceProvider.addFileForURL(url, contentProvider, true);
    520                 uiSourceCode.setSourceMapping(this);
    521             }
    522         }
    523     },
    524 
    525     /**
    526      * @param {WebInspector.RawLocation} rawLocation
    527      * @return {WebInspector.UILocation}
    528      */
    529     rawLocationToUILocation: function(rawLocation)
    530     {
    531         var location = /** @type WebInspector.CSSLocation */ (rawLocation);
    532         var entry;
    533         var sourceMap = this._sourceMapByStyleSheetURL[location.url];
    534         if (!sourceMap)
    535             return null;
    536         entry = sourceMap.findEntry(location.lineNumber, location.columnNumber);
    537         if (!entry || entry.length === 2)
    538             return null;
    539         var uiSourceCode = this._workspace.uiSourceCodeForURL(entry[2]);
    540         if (!uiSourceCode)
    541             return null;
    542         return new WebInspector.UILocation(uiSourceCode, entry[3], entry[4]);
    543     },
    544 
    545     /**
    546      * @param {WebInspector.UISourceCode} uiSourceCode
    547      * @param {number} lineNumber
    548      * @param {number} columnNumber
    549      * @return {WebInspector.RawLocation}
    550      */
    551     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
    552     {
    553         // FIXME: Implement this when ui -> raw mapping has clients.
    554         return new WebInspector.CSSLocation(uiSourceCode.url || "", lineNumber, columnNumber);
    555     },
    556 
    557     /**
    558      * @return {boolean}
    559      */
    560     isIdentity: function()
    561     {
    562         return false;
    563     },
    564 
    565     /**
    566      * @param {WebInspector.Event} event
    567      */
    568     _uiSourceCodeAdded: function(event)
    569     {
    570         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data);
    571         var cssURLs = this._cssURLsForSASSURL[uiSourceCode.url];
    572         if (!cssURLs)
    573             return;
    574         uiSourceCode.setSourceMapping(this);
    575         for (var i = 0; i < cssURLs.length; ++i) {
    576             var ids = this._cssModel.styleSheetIdsForURL(cssURLs[i]);
    577             for (var j = 0; j < ids.length; ++j) {
    578                 var header = this._cssModel.styleSheetHeaderForId(ids[j]);
    579                 console.assert(header);
    580                 header.updateLocations();
    581             }
    582         }
    583     },
    584 
    585     /**
    586      * @param {WebInspector.Event} event
    587      */
    588     _uiSourceCodeContentCommitted: function(event)
    589     {
    590         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data.uiSourceCode);
    591         if (uiSourceCode.project().type() === WebInspector.projectTypes.FileSystem)
    592             this._sassFileSaved(uiSourceCode.url, true);
    593     },
    594 
    595     _reset: function()
    596     {
    597         this._addingRevisionCounter = 0;
    598         this._completeSourceMapURLForCSSURL = {};
    599         this._cssURLsForSASSURL = {};
    600         /** @type {Object.<string, Array.<function(?WebInspector.SourceMap)>>} */
    601         this._pendingSourceMapLoadingCallbacks = {};
    602         /** @type {Object.<string, {deadlineMs: number, dataByURL: Object.<string, {timer: number, previousPoll: number}>}>} */
    603         this._pollDataForSASSURL = {};
    604         /** @type {Object.<string, WebInspector.SourceMap>} */
    605         this._sourceMapByURL = {};
    606         this._sourceMapByStyleSheetURL = {};
    607     }
    608 }
    609