Home | History | Annotate | Download | only in bindings
      1 /*
      2  * Copyright (C) 2011 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  * @extends {WebInspector.Object}
     34  * @implements {WebInspector.TargetManager.Observer}
     35  * @param {!WebInspector.Setting} breakpointStorage
     36  * @param {!WebInspector.Workspace} workspace
     37  * @param {!WebInspector.TargetManager} targetManager
     38  * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding
     39  */
     40 WebInspector.BreakpointManager = function(breakpointStorage, workspace, targetManager, debuggerWorkspaceBinding)
     41 {
     42     this._storage = new WebInspector.BreakpointManager.Storage(this, breakpointStorage);
     43     this._workspace = workspace;
     44     this._targetManager = targetManager;
     45     this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
     46 
     47     this._breakpointsActive = true;
     48     this._breakpointsForUISourceCode = new Map();
     49     this._breakpointsForPrimaryUISourceCode = new Map();
     50     /** @type {!StringMultimap.<!WebInspector.BreakpointManager.Breakpoint>} */
     51     this._provisionalBreakpoints = new StringMultimap();
     52 
     53     this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
     54     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
     55     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
     56 }
     57 
     58 WebInspector.BreakpointManager.Events = {
     59     BreakpointAdded: "breakpoint-added",
     60     BreakpointRemoved: "breakpoint-removed",
     61     BreakpointsActiveStateChanged: "BreakpointsActiveStateChanged"
     62 }
     63 
     64 WebInspector.BreakpointManager._sourceFileId = function(uiSourceCode)
     65 {
     66     if (!uiSourceCode.url)
     67         return "";
     68     return uiSourceCode.uri();
     69 }
     70 
     71 /**
     72  * @param {string} sourceFileId
     73  * @param {number} lineNumber
     74  * @param {number} columnNumber
     75  * @return {string}
     76  */
     77 WebInspector.BreakpointManager._breakpointStorageId = function(sourceFileId, lineNumber, columnNumber)
     78 {
     79     if (!sourceFileId)
     80         return "";
     81     return sourceFileId + ":" + lineNumber + ":" + columnNumber;
     82 }
     83 
     84 WebInspector.BreakpointManager.prototype = {
     85     /**
     86      * @param {!WebInspector.Target} target
     87      */
     88     targetAdded: function(target) {
     89         if (!this._breakpointsActive)
     90             target.debuggerAgent().setBreakpointsActive(this._breakpointsActive);
     91     },
     92 
     93     /**
     94      * @param {!WebInspector.Target} target
     95      */
     96     targetRemoved: function(target) { },
     97 
     98     /**
     99      * @param {string} sourceFileId
    100      * @return {!StringMap.<!WebInspector.BreakpointManager.Breakpoint>}
    101      */
    102     _provisionalBreakpointsForSourceFileId: function(sourceFileId)
    103     {
    104         var result = new StringMap();
    105         var breakpoints = this._provisionalBreakpoints.get(sourceFileId).values();
    106         for (var i = 0; i < breakpoints.length; ++i)
    107             result.set(breakpoints[i]._breakpointStorageId(), breakpoints[i]);
    108         return result;
    109     },
    110 
    111     removeProvisionalBreakpointsForTest: function()
    112     {
    113         var breakpoints = this._provisionalBreakpoints.values();
    114         for (var i = 0; i < breakpoints.length; ++i)
    115             breakpoints[i].remove();
    116         this._provisionalBreakpoints.clear();
    117     },
    118 
    119     /**
    120      * @param {!WebInspector.UISourceCode} uiSourceCode
    121      */
    122     _restoreBreakpoints: function(uiSourceCode)
    123     {
    124         var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode);
    125         if (!sourceFileId)
    126             return;
    127 
    128         this._storage.mute();
    129         var breakpointItems = this._storage.breakpointItems(uiSourceCode);
    130         var provisionalBreakpoints = this._provisionalBreakpointsForSourceFileId(sourceFileId);
    131         for (var i = 0; i < breakpointItems.length; ++i) {
    132             var breakpointItem = breakpointItems[i];
    133             var itemStorageId = WebInspector.BreakpointManager._breakpointStorageId(breakpointItem.sourceFileId, breakpointItem.lineNumber, breakpointItem.columnNumber);
    134             var provisionalBreakpoint = provisionalBreakpoints.get(itemStorageId);
    135             if (provisionalBreakpoint) {
    136                 if (!this._breakpointsForPrimaryUISourceCode.get(uiSourceCode))
    137                     this._breakpointsForPrimaryUISourceCode.set(uiSourceCode, []);
    138                 this._breakpointsForPrimaryUISourceCode.get(uiSourceCode).push(provisionalBreakpoint);
    139                 provisionalBreakpoint._updateBreakpoint();
    140             } else {
    141                 this._innerSetBreakpoint(uiSourceCode, breakpointItem.lineNumber, breakpointItem.columnNumber, breakpointItem.condition, breakpointItem.enabled);
    142             }
    143         }
    144         this._provisionalBreakpoints.removeAll(sourceFileId);
    145         this._storage.unmute();
    146     },
    147 
    148     /**
    149      * @param {!WebInspector.Event} event
    150      */
    151     _uiSourceCodeAdded: function(event)
    152     {
    153         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    154         this._restoreBreakpoints(uiSourceCode);
    155         if (uiSourceCode.contentType() === WebInspector.resourceTypes.Script || uiSourceCode.contentType() === WebInspector.resourceTypes.Document)
    156             uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this);
    157     },
    158 
    159     /**
    160      * @param {!WebInspector.Event} event
    161      */
    162     _uiSourceCodeRemoved: function(event)
    163     {
    164         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    165         this._removeUISourceCode(uiSourceCode);
    166     },
    167 
    168     /**
    169      * @param {!WebInspector.Event} event
    170      */
    171     _uiSourceCodeMappingChanged: function(event)
    172     {
    173         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    174         var isIdentity = /** @type {boolean} */ (event.data.isIdentity);
    175         var target = /** @type {!WebInspector.Target} */ (event.data.target);
    176         if (isIdentity)
    177             return;
    178         var breakpoints = this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || [];
    179         for (var i = 0; i < breakpoints.length; ++i)
    180             breakpoints[i]._updateInDebuggerForTarget(target);
    181     },
    182 
    183     /**
    184      * @param {!WebInspector.UISourceCode} uiSourceCode
    185      */
    186     _removeUISourceCode: function(uiSourceCode)
    187     {
    188         var breakpoints = this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || [];
    189         var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode);
    190         for (var i = 0; i < breakpoints.length; ++i) {
    191             breakpoints[i]._resetLocations();
    192             if (breakpoints[i].enabled())
    193                 this._provisionalBreakpoints.set(sourceFileId, breakpoints[i]);
    194         }
    195         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this);
    196         this._breakpointsForPrimaryUISourceCode.remove(uiSourceCode);
    197     },
    198 
    199     /**
    200      * @param {!WebInspector.UISourceCode} uiSourceCode
    201      * @param {number} lineNumber
    202      * @param {number} columnNumber
    203      * @param {string} condition
    204      * @param {boolean} enabled
    205      * @return {!WebInspector.BreakpointManager.Breakpoint}
    206      */
    207     setBreakpoint: function(uiSourceCode, lineNumber, columnNumber, condition, enabled)
    208     {
    209         this.setBreakpointsActive(true);
    210         return this._innerSetBreakpoint(uiSourceCode, lineNumber, columnNumber, condition, enabled);
    211     },
    212 
    213     /**
    214      * @param {!WebInspector.UISourceCode} uiSourceCode
    215      * @param {number} lineNumber
    216      * @param {number} columnNumber
    217      * @param {string} condition
    218      * @param {boolean} enabled
    219      * @return {!WebInspector.BreakpointManager.Breakpoint}
    220      */
    221     _innerSetBreakpoint: function(uiSourceCode, lineNumber, columnNumber, condition, enabled)
    222     {
    223         var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber, columnNumber);
    224         if (breakpoint) {
    225             breakpoint._updateState(condition, enabled);
    226             return breakpoint;
    227         }
    228         var projectId = uiSourceCode.project().id();
    229         var path = uiSourceCode.path();
    230         var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode);
    231         breakpoint = new WebInspector.BreakpointManager.Breakpoint(this, projectId, path, sourceFileId, lineNumber, columnNumber, condition, enabled);
    232         if (!this._breakpointsForPrimaryUISourceCode.get(uiSourceCode))
    233             this._breakpointsForPrimaryUISourceCode.set(uiSourceCode, []);
    234         this._breakpointsForPrimaryUISourceCode.get(uiSourceCode).push(breakpoint);
    235         return breakpoint;
    236     },
    237 
    238     /**
    239      * @param {!WebInspector.UISourceCode} uiSourceCode
    240      * @param {number} lineNumber
    241      * @param {number} columnNumber
    242      * @return {?WebInspector.BreakpointManager.Breakpoint}
    243      */
    244     findBreakpoint: function(uiSourceCode, lineNumber, columnNumber)
    245     {
    246         var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
    247         var lineBreakpoints = breakpoints ? breakpoints.get(String(lineNumber)) : null;
    248         var columnBreakpoints = lineBreakpoints ? lineBreakpoints.get(String(columnNumber)) : null;
    249         return columnBreakpoints ? columnBreakpoints[0] : null;
    250     },
    251 
    252     /**
    253      * @param {!WebInspector.UISourceCode} uiSourceCode
    254      * @param {number} lineNumber
    255      * @return {?WebInspector.BreakpointManager.Breakpoint}
    256      */
    257     findBreakpointOnLine: function(uiSourceCode, lineNumber)
    258     {
    259         var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
    260         var lineBreakpoints = breakpoints ? breakpoints.get(String(lineNumber)) : null;
    261         return lineBreakpoints ? lineBreakpoints.values()[0][0] : null;
    262     },
    263 
    264     /**
    265      * @param {!WebInspector.UISourceCode} uiSourceCode
    266      * @return {!Array.<!WebInspector.BreakpointManager.Breakpoint>}
    267      */
    268     breakpointsForUISourceCode: function(uiSourceCode)
    269     {
    270         var result = [];
    271         var uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
    272         var breakpoints = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.values() : [];
    273         for (var i = 0; i < breakpoints.length; ++i) {
    274             var lineBreakpoints = breakpoints[i];
    275             var columnBreakpointArrays = lineBreakpoints ? lineBreakpoints.values() : [];
    276             result = result.concat.apply(result, columnBreakpointArrays);
    277         }
    278         return result;
    279     },
    280 
    281     /**
    282      * @return {!Array.<!WebInspector.BreakpointManager.Breakpoint>}
    283      */
    284     allBreakpoints: function()
    285     {
    286         var result = [];
    287         var uiSourceCodes = this._breakpointsForUISourceCode.keys();
    288         for (var i = 0; i < uiSourceCodes.length; ++i)
    289             result = result.concat(this.breakpointsForUISourceCode(uiSourceCodes[i]));
    290         return result;
    291     },
    292 
    293     /**
    294      * @param {!WebInspector.UISourceCode} uiSourceCode
    295      * @return {!Array.<!{breakpoint: !WebInspector.BreakpointManager.Breakpoint, uiLocation: !WebInspector.UILocation}>}
    296      */
    297     breakpointLocationsForUISourceCode: function(uiSourceCode)
    298     {
    299         var uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
    300         var lineNumbers = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.keys() : [];
    301         var result = [];
    302         for (var i = 0; i < lineNumbers.length; ++i) {
    303             var lineBreakpoints = uiSourceCodeBreakpoints.get(lineNumbers[i]);
    304             var columnNumbers = lineBreakpoints.keys();
    305             for (var j = 0; j < columnNumbers.length; ++j) {
    306                 var columnBreakpoints = lineBreakpoints.get(columnNumbers[j]);
    307                 var lineNumber = parseInt(lineNumbers[i], 10);
    308                 var columnNumber = parseInt(columnNumbers[j], 10);
    309                 for (var k = 0; k < columnBreakpoints.length; ++k) {
    310                     var breakpoint = columnBreakpoints[k];
    311                     var uiLocation = uiSourceCode.uiLocation(lineNumber, columnNumber);
    312                     result.push({breakpoint: breakpoint, uiLocation: uiLocation});
    313                 }
    314             }
    315         }
    316         return result;
    317     },
    318 
    319     /**
    320      * @return {!Array.<!{breakpoint: !WebInspector.BreakpointManager.Breakpoint, uiLocation: !WebInspector.UILocation}>}
    321      */
    322     allBreakpointLocations: function()
    323     {
    324         var result = [];
    325         var uiSourceCodes = this._breakpointsForUISourceCode.keys();
    326         for (var i = 0; i < uiSourceCodes.length; ++i)
    327             result = result.concat(this.breakpointLocationsForUISourceCode(uiSourceCodes[i]));
    328         return result;
    329     },
    330 
    331     /**
    332      * @param {boolean} toggleState
    333      */
    334     toggleAllBreakpoints: function(toggleState)
    335     {
    336         var breakpoints = this.allBreakpoints();
    337         for (var i = 0; i < breakpoints.length; ++i)
    338             breakpoints[i].setEnabled(toggleState);
    339     },
    340 
    341     removeAllBreakpoints: function()
    342     {
    343         var breakpoints = this.allBreakpoints();
    344         for (var i = 0; i < breakpoints.length; ++i)
    345             breakpoints[i].remove();
    346     },
    347 
    348     _projectRemoved: function(event)
    349     {
    350         var project = /** @type {!WebInspector.Project} */ (event.data);
    351         var uiSourceCodes = project.uiSourceCodes();
    352         for (var i = 0; i < uiSourceCodes.length; ++i)
    353             this._removeUISourceCode(uiSourceCodes[i]);
    354     },
    355 
    356     /**
    357      * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint
    358      * @param {boolean} removeFromStorage
    359      */
    360     _removeBreakpoint: function(breakpoint, removeFromStorage)
    361     {
    362         var uiSourceCode = breakpoint.uiSourceCode();
    363         var breakpoints = uiSourceCode ? this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || [] : [];
    364         breakpoints.remove(breakpoint);
    365         if (removeFromStorage)
    366             this._storage._removeBreakpoint(breakpoint);
    367         this._provisionalBreakpoints.remove(breakpoint._sourceFileId, breakpoint);
    368     },
    369 
    370     /**
    371      * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint
    372      * @param {!WebInspector.UILocation} uiLocation
    373      */
    374     _uiLocationAdded: function(breakpoint, uiLocation)
    375     {
    376         var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode);
    377         if (!breakpoints) {
    378             breakpoints = new StringMap();
    379             this._breakpointsForUISourceCode.set(uiLocation.uiSourceCode, breakpoints);
    380         }
    381         var lineBreakpoints = breakpoints.get(String(uiLocation.lineNumber));
    382         if (!lineBreakpoints) {
    383             lineBreakpoints = new StringMap();
    384             breakpoints.set(String(uiLocation.lineNumber), lineBreakpoints);
    385         }
    386         var columnBreakpoints = lineBreakpoints.get(String(uiLocation.columnNumber));
    387         if (!columnBreakpoints) {
    388             columnBreakpoints = [];
    389             lineBreakpoints.set(String(uiLocation.columnNumber), columnBreakpoints);
    390         }
    391         columnBreakpoints.push(breakpoint);
    392         this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointAdded, {breakpoint: breakpoint, uiLocation: uiLocation});
    393     },
    394 
    395     /**
    396      * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint
    397      * @param {!WebInspector.UILocation} uiLocation
    398      */
    399     _uiLocationRemoved: function(breakpoint, uiLocation)
    400     {
    401         var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode);
    402         if (!breakpoints)
    403             return;
    404 
    405         var lineBreakpoints = breakpoints.get(String(uiLocation.lineNumber));
    406         if (!lineBreakpoints)
    407             return;
    408         var columnBreakpoints = lineBreakpoints.get(String(uiLocation.columnNumber));
    409         if (!columnBreakpoints)
    410             return;
    411         columnBreakpoints.remove(breakpoint);
    412         if (!columnBreakpoints.length)
    413             lineBreakpoints.remove(String(uiLocation.columnNumber));
    414         if (!lineBreakpoints.size)
    415             breakpoints.remove(String(uiLocation.lineNumber));
    416         if (!breakpoints.size)
    417             this._breakpointsForUISourceCode.remove(uiLocation.uiSourceCode);
    418         this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointRemoved, {breakpoint: breakpoint, uiLocation: uiLocation});
    419     },
    420 
    421     /**
    422      * @param {boolean} active
    423      */
    424     setBreakpointsActive: function(active)
    425     {
    426         if (this._breakpointsActive === active)
    427             return;
    428 
    429         this._breakpointsActive = active;
    430         var targets = WebInspector.targetManager.targets();
    431         for (var i = 0; i < targets.length; ++i)
    432             targets[i].debuggerAgent().setBreakpointsActive(active);
    433 
    434         this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointsActiveStateChanged, active);
    435     },
    436 
    437     /**
    438      * @return {boolean}
    439      */
    440     breakpointsActive: function()
    441     {
    442         return this._breakpointsActive;
    443     },
    444 
    445     __proto__: WebInspector.Object.prototype
    446 }
    447 
    448 /**
    449  * @constructor
    450  * @implements {WebInspector.TargetManager.Observer}
    451  * @param {!WebInspector.BreakpointManager} breakpointManager
    452  * @param {string} projectId
    453  * @param {string} path
    454  * @param {string} sourceFileId
    455  * @param {number} lineNumber
    456  * @param {number} columnNumber
    457  * @param {string} condition
    458  * @param {boolean} enabled
    459  */
    460 WebInspector.BreakpointManager.Breakpoint = function(breakpointManager, projectId, path, sourceFileId, lineNumber, columnNumber, condition, enabled)
    461 {
    462     this._breakpointManager = breakpointManager;
    463     this._projectId = projectId;
    464     this._path = path;
    465     this._lineNumber = lineNumber;
    466     this._columnNumber = columnNumber;
    467     this._sourceFileId = sourceFileId;
    468 
    469     /** @type {!Object.<string, number>} */
    470     this._numberOfDebuggerLocationForUILocation = {};
    471 
    472     // Force breakpoint update.
    473     /** @type {string} */ this._condition;
    474     /** @type {boolean} */ this._enabled;
    475     /** @type {boolean} */ this._isRemoved;
    476     /** @type {!WebInspector.UILocation|undefined} */ this._fakePrimaryLocation;
    477 
    478     this._currentState = null;
    479     /** @type {!Map.<!WebInspector.Target, !WebInspector.BreakpointManager.TargetBreakpoint>}*/
    480     this._targetBreakpoints = new Map();
    481     this._updateState(condition, enabled);
    482     this._breakpointManager._targetManager.observeTargets(this);
    483 }
    484 
    485 WebInspector.BreakpointManager.Breakpoint.prototype = {
    486     /**
    487      * @param {!WebInspector.Target} target
    488      */
    489     targetAdded: function(target)
    490     {
    491         this._targetBreakpoints.set(target, new WebInspector.BreakpointManager.TargetBreakpoint(target, this, this._breakpointManager._debuggerWorkspaceBinding));
    492     },
    493 
    494     /**
    495      * @param {!WebInspector.Target} target
    496      */
    497     targetRemoved: function(target)
    498     {
    499         var targetBreakpoint = this._targetBreakpoints.remove(target);
    500         targetBreakpoint._cleanUpAfterDebuggerIsGone();
    501         targetBreakpoint._removeEventListeners();
    502     },
    503 
    504     /**
    505      * @return {string}
    506      */
    507     projectId: function()
    508     {
    509         return this._projectId;
    510     },
    511 
    512     /**
    513      * @return {string}
    514      */
    515     path: function()
    516     {
    517         return this._path;
    518     },
    519 
    520     /**
    521      * @return {number}
    522      */
    523     lineNumber: function()
    524     {
    525         return this._lineNumber;
    526     },
    527 
    528     /**
    529      * @return {number}
    530      */
    531     columnNumber: function()
    532     {
    533         return this._columnNumber;
    534     },
    535 
    536     /**
    537      * @return {?WebInspector.UISourceCode}
    538      */
    539     uiSourceCode: function()
    540     {
    541         return this._breakpointManager._workspace.uiSourceCode(this._projectId, this._path);
    542     },
    543 
    544     /**
    545      * @param {?WebInspector.UILocation} oldUILocation
    546      * @param {!WebInspector.UILocation} newUILocation
    547      */
    548     _replaceUILocation: function(oldUILocation, newUILocation)
    549     {
    550         if (this._isRemoved)
    551             return;
    552 
    553         this._removeUILocation(oldUILocation, true);
    554         this._removeFakeBreakpointAtPrimaryLocation();
    555 
    556         if (!this._numberOfDebuggerLocationForUILocation[newUILocation.id()])
    557             this._numberOfDebuggerLocationForUILocation[newUILocation.id()] = 0;
    558 
    559         if (++this._numberOfDebuggerLocationForUILocation[newUILocation.id()] === 1)
    560             this._breakpointManager._uiLocationAdded(this, newUILocation);
    561     },
    562 
    563     /**
    564      * @param {?WebInspector.UILocation} uiLocation
    565      * @param {boolean=} muteCreationFakeBreakpoint
    566      */
    567     _removeUILocation: function(uiLocation, muteCreationFakeBreakpoint)
    568     {
    569         if (!uiLocation || --this._numberOfDebuggerLocationForUILocation[uiLocation.id()] !== 0)
    570             return;
    571 
    572         delete this._numberOfDebuggerLocationForUILocation[uiLocation.id()];
    573         this._breakpointManager._uiLocationRemoved(this, uiLocation);
    574         if (!muteCreationFakeBreakpoint)
    575             this._fakeBreakpointAtPrimaryLocation();
    576     },
    577 
    578     /**
    579      * @return {boolean}
    580      */
    581     enabled: function()
    582     {
    583         return this._enabled;
    584     },
    585 
    586     /**
    587      * @param {boolean} enabled
    588      */
    589     setEnabled: function(enabled)
    590     {
    591         this._updateState(this._condition, enabled);
    592     },
    593 
    594     /**
    595      * @return {string}
    596      */
    597     condition: function()
    598     {
    599         return this._condition;
    600     },
    601 
    602     /**
    603      * @param {string} condition
    604      */
    605     setCondition: function(condition)
    606     {
    607         this._updateState(condition, this._enabled);
    608     },
    609 
    610     /**
    611      * @param {string} condition
    612      * @param {boolean} enabled
    613      */
    614     _updateState: function(condition, enabled)
    615     {
    616         if (this._enabled === enabled && this._condition === condition)
    617             return;
    618         this._enabled = enabled;
    619         this._condition = condition;
    620         this._breakpointManager._storage._updateBreakpoint(this);
    621         this._updateBreakpoint();
    622     },
    623 
    624     _updateBreakpoint: function()
    625     {
    626         this._removeFakeBreakpointAtPrimaryLocation();
    627         this._fakeBreakpointAtPrimaryLocation();
    628         var targetBreakpoints = this._targetBreakpoints.values();
    629         for (var i = 0; i < targetBreakpoints.length; ++i)
    630             targetBreakpoints[i]._scheduleUpdateInDebugger();
    631     },
    632 
    633     /**
    634      * @param {boolean=} keepInStorage
    635      */
    636     remove: function(keepInStorage)
    637     {
    638 
    639         this._isRemoved = true;
    640         var removeFromStorage = !keepInStorage;
    641         this._removeFakeBreakpointAtPrimaryLocation();
    642         var targetBreakpoints = this._targetBreakpoints.values();
    643         for (var i = 0; i < targetBreakpoints.length; ++i) {
    644             targetBreakpoints[i]._scheduleUpdateInDebugger();
    645             targetBreakpoints[i]._removeEventListeners();
    646         }
    647 
    648         this._breakpointManager._removeBreakpoint(this, removeFromStorage);
    649         this._breakpointManager._targetManager.unobserveTargets(this);
    650     },
    651 
    652     /**
    653      * @param {!WebInspector.Target} target
    654      */
    655     _updateInDebuggerForTarget: function(target)
    656     {
    657         this._targetBreakpoints.get(target)._scheduleUpdateInDebugger();
    658     },
    659 
    660     /**
    661      * @return {string}
    662      */
    663     _breakpointStorageId: function()
    664     {
    665         return WebInspector.BreakpointManager._breakpointStorageId(this._sourceFileId, this._lineNumber, this._columnNumber);
    666     },
    667 
    668     _fakeBreakpointAtPrimaryLocation: function()
    669     {
    670         if (this._isRemoved || !Object.isEmpty(this._numberOfDebuggerLocationForUILocation) || this._fakePrimaryLocation)
    671             return;
    672 
    673         var uiSourceCode = this._breakpointManager._workspace.uiSourceCode(this._projectId, this._path);
    674         if (!uiSourceCode)
    675             return;
    676 
    677         this._fakePrimaryLocation = uiSourceCode.uiLocation(this._lineNumber, this._columnNumber);
    678         this._breakpointManager._uiLocationAdded(this, this._fakePrimaryLocation);
    679     },
    680 
    681     _removeFakeBreakpointAtPrimaryLocation: function()
    682     {
    683         if (this._fakePrimaryLocation) {
    684             this._breakpointManager._uiLocationRemoved(this, this._fakePrimaryLocation);
    685             delete this._fakePrimaryLocation;
    686         }
    687     },
    688 
    689     _resetLocations: function()
    690     {
    691         this._removeFakeBreakpointAtPrimaryLocation();
    692         var targetBreakpoints = this._targetBreakpoints.values();
    693         for (var i = 0; i < targetBreakpoints.length; ++i)
    694             targetBreakpoints[i]._resetLocations();
    695     }
    696 }
    697 
    698 /**
    699  * @constructor
    700  * @extends {WebInspector.SDKObject}
    701  * @param {!WebInspector.Target} target
    702  * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint
    703  * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding
    704  */
    705 WebInspector.BreakpointManager.TargetBreakpoint = function(target, breakpoint, debuggerWorkspaceBinding)
    706 {
    707     WebInspector.SDKObject.call(this, target);
    708     this._breakpoint = breakpoint;
    709     this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
    710 
    711     /** @type {!Array.<!WebInspector.DebuggerWorkspaceBinding.Location>} */
    712     this._liveLocations = [];
    713 
    714     /** @type {!Object.<string, !WebInspector.UILocation>} */
    715     this._uiLocations = {};
    716     target.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this);
    717     target.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this);
    718     this._hasPendingUpdate = false;
    719     this._isUpdating = false;
    720     this._cancelCallback = false;
    721     this._currentState = null;
    722     if (target.debuggerModel.debuggerEnabled())
    723         this._scheduleUpdateInDebugger();
    724 }
    725 
    726 WebInspector.BreakpointManager.TargetBreakpoint.prototype = {
    727 
    728     /**
    729      * @return {!WebInspector.DebuggerModel}
    730      */
    731     _debuggerModel: function()
    732     {
    733         return this.target().debuggerModel;
    734     },
    735 
    736     _resetLocations: function()
    737     {
    738         var uiLocations = Object.values(this._uiLocations);
    739         for (var i = 0; i < uiLocations.length; ++i)
    740             this._breakpoint._removeUILocation(uiLocations[i]);
    741 
    742         this._uiLocations = {};
    743 
    744         for (var i = 0; i < this._liveLocations.length; ++i)
    745             this._liveLocations[i].dispose();
    746         this._liveLocations = [];
    747     },
    748 
    749     _scheduleUpdateInDebugger: function()
    750     {
    751         if (this._isUpdating) {
    752             this._hasPendingUpdate = true;
    753             return;
    754         }
    755 
    756         this._isUpdating = true;
    757         this._updateInDebugger(this._didUpdateInDebugger.bind(this));
    758     },
    759 
    760     _didUpdateInDebugger: function()
    761     {
    762         this._isUpdating = false;
    763         if (this._hasPendingUpdate) {
    764             this._hasPendingUpdate = false;
    765             this._scheduleUpdateInDebugger();
    766         }
    767     },
    768 
    769     /**
    770      * @return {boolean}
    771      */
    772     _scriptDiverged: function()
    773     {
    774         var uiSourceCode = this._breakpoint.uiSourceCode();
    775         if (!uiSourceCode)
    776             return false;
    777         var scriptFile = this._debuggerWorkspaceBinding.scriptFile(uiSourceCode, this.target());
    778         return !!scriptFile && scriptFile.hasDivergedFromVM();
    779 
    780     },
    781 
    782     /**
    783      * @param {function()} callback
    784      */
    785     _updateInDebugger: function(callback)
    786     {
    787         if (this.target().isDetached()) {
    788             this._cleanUpAfterDebuggerIsGone();
    789             callback();
    790             return;
    791         }
    792 
    793         var uiSourceCode = this._breakpoint.uiSourceCode();
    794         var lineNumber = this._breakpoint._lineNumber;
    795         var columnNumber = this._breakpoint._columnNumber;
    796         var condition = this._breakpoint.condition();
    797 
    798         var debuggerLocation = uiSourceCode ? this._debuggerWorkspaceBinding.uiLocationToRawLocation(this.target(), uiSourceCode, lineNumber, columnNumber) : null;
    799         var newState;
    800         if (this._breakpoint._isRemoved || !this._breakpoint.enabled() || this._scriptDiverged())
    801             newState = null;
    802         else if (debuggerLocation) {
    803             var script = debuggerLocation.script();
    804             if (script.sourceURL)
    805                 newState = new WebInspector.BreakpointManager.Breakpoint.State(script.sourceURL, null, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition);
    806             else
    807                 newState = new WebInspector.BreakpointManager.Breakpoint.State(null, debuggerLocation.scriptId, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition)
    808         } else if (this._breakpoint._currentState && this._breakpoint._currentState.url) {
    809             var position = this._breakpoint._currentState;
    810             newState = new WebInspector.BreakpointManager.Breakpoint.State(position.url, null, position.lineNumber, position.columnNumber, condition);
    811         } else if (uiSourceCode && uiSourceCode.url)
    812             newState = new WebInspector.BreakpointManager.Breakpoint.State(uiSourceCode.url, null, lineNumber, columnNumber, condition);
    813 
    814         if (this._debuggerId && WebInspector.BreakpointManager.Breakpoint.State.equals(newState, this._currentState)) {
    815             callback();
    816             return;
    817         }
    818 
    819         this._breakpoint._currentState = newState;
    820 
    821         if (this._debuggerId) {
    822             this._resetLocations();
    823             this._debuggerModel().removeBreakpoint(this._debuggerId, this._didRemoveFromDebugger.bind(this, callback));
    824             this._scheduleUpdateInDebugger();
    825             this._currentState = null;
    826             return;
    827         }
    828 
    829         if (!newState) {
    830             callback();
    831             return;
    832         }
    833 
    834         var updateCallback = this._didSetBreakpointInDebugger.bind(this, callback);
    835         if (newState.url)
    836             this._debuggerModel().setBreakpointByURL(newState.url, newState.lineNumber, newState.columnNumber, this._breakpoint.condition(), updateCallback);
    837         else if (newState.scriptId)
    838             this._debuggerModel().setBreakpointBySourceId(/** @type {!WebInspector.DebuggerModel.Location} */ (debuggerLocation), condition, updateCallback);
    839 
    840         this._currentState = newState;
    841     },
    842 
    843     /**
    844      * @param {function()} callback
    845      * @param {?DebuggerAgent.BreakpointId} breakpointId
    846      * @param {!Array.<!WebInspector.DebuggerModel.Location>} locations
    847      */
    848     _didSetBreakpointInDebugger: function(callback, breakpointId, locations)
    849     {
    850         if (this._cancelCallback) {
    851             this._cancelCallback = false;
    852             callback();
    853             return;
    854         }
    855 
    856         if (!breakpointId) {
    857             this._breakpoint.remove(true);
    858             callback();
    859             return;
    860         }
    861 
    862         this._debuggerId = breakpointId;
    863         this.target().debuggerModel.addBreakpointListener(this._debuggerId, this._breakpointResolved, this);
    864         for (var i = 0; i < locations.length; ++i) {
    865             if (!this._addResolvedLocation(locations[i]))
    866                 break;
    867         }
    868         callback();
    869     },
    870 
    871     /**
    872      * @param {function()} callback
    873      */
    874     _didRemoveFromDebugger: function(callback)
    875     {
    876         if (this._cancelCallback) {
    877             this._cancelCallback = false;
    878             callback();
    879             return;
    880         }
    881 
    882         this._resetLocations();
    883         this.target().debuggerModel.removeBreakpointListener(this._debuggerId, this._breakpointResolved, this);
    884         delete this._debuggerId;
    885         callback();
    886     },
    887 
    888     /**
    889      * @param {!WebInspector.Event} event
    890      */
    891     _breakpointResolved: function(event)
    892     {
    893         this._addResolvedLocation(/** @type {!WebInspector.DebuggerModel.Location}*/ (event.data));
    894     },
    895 
    896     /**
    897      * @param {!WebInspector.DebuggerModel.Location} location
    898      * @param {!WebInspector.UILocation} uiLocation
    899      */
    900     _locationUpdated: function(location, uiLocation)
    901     {
    902         var oldUILocation = this._uiLocations[location.id()] || null;
    903         this._uiLocations[location.id()] = uiLocation;
    904         this._breakpoint._replaceUILocation(oldUILocation, uiLocation);
    905     },
    906 
    907     /**
    908      * @param {!WebInspector.DebuggerModel.Location} location
    909      * @return {boolean}
    910      */
    911     _addResolvedLocation: function(location)
    912     {
    913         var uiLocation = this._debuggerWorkspaceBinding.rawLocationToUILocation(location);
    914         var breakpoint = this._breakpoint._breakpointManager.findBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber);
    915         if (breakpoint && breakpoint !== this._breakpoint) {
    916             // location clash
    917             this._breakpoint.remove();
    918             return false;
    919         }
    920         this._liveLocations.push(this._debuggerWorkspaceBinding.createLiveLocation(location, this._locationUpdated.bind(this, location)));
    921         return true;
    922     },
    923 
    924     _cleanUpAfterDebuggerIsGone: function()
    925     {
    926         if (this._isUpdating)
    927             this._cancelCallback = true;
    928 
    929         this._resetLocations();
    930         this._currentState = null;
    931         if (this._debuggerId)
    932             this._didRemoveFromDebugger(function() {});
    933     },
    934 
    935     _removeEventListeners: function()
    936     {
    937         this.target().debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this);
    938         this.target().debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this);
    939     },
    940 
    941     __proto__: WebInspector.SDKObject.prototype
    942 }
    943 
    944 /**
    945  * @constructor
    946  * @param {?string} url
    947  * @param {?string} scriptId
    948  * @param {number} lineNumber
    949  * @param {number} columnNumber
    950  * @param {string} condition
    951  */
    952 WebInspector.BreakpointManager.Breakpoint.State = function(url, scriptId, lineNumber, columnNumber, condition)
    953 {
    954     this.url = url;
    955     this.scriptId = scriptId;
    956     this.lineNumber = lineNumber;
    957     this.columnNumber = columnNumber;
    958     this.condition = condition;
    959 }
    960 
    961 /**
    962  * @param {?WebInspector.BreakpointManager.Breakpoint.State|undefined} stateA
    963  * @param {?WebInspector.BreakpointManager.Breakpoint.State|undefined} stateB
    964  * @return {boolean}
    965  */
    966 WebInspector.BreakpointManager.Breakpoint.State.equals = function(stateA, stateB)
    967 {
    968     if (!stateA || !stateB)
    969         return false;
    970 
    971     if (stateA.scriptId || stateB.scriptId)
    972         return false;
    973 
    974     return stateA.url === stateB.url && stateA.lineNumber === stateB.lineNumber && stateA.columnNumber === stateB.columnNumber && stateA.condition === stateB.condition;
    975 }
    976 
    977 /**
    978  * @constructor
    979  * @param {!WebInspector.BreakpointManager} breakpointManager
    980  * @param {!WebInspector.Setting} setting
    981  */
    982 WebInspector.BreakpointManager.Storage = function(breakpointManager, setting)
    983 {
    984     this._breakpointManager = breakpointManager;
    985     this._setting = setting;
    986     var breakpoints = this._setting.get();
    987     /** @type {!Object.<string, !WebInspector.BreakpointManager.Storage.Item>} */
    988     this._breakpoints = {};
    989     for (var i = 0; i < breakpoints.length; ++i) {
    990         var breakpoint = /** @type {!WebInspector.BreakpointManager.Storage.Item} */ (breakpoints[i]);
    991         breakpoint.columnNumber = breakpoint.columnNumber || 0;
    992         this._breakpoints[breakpoint.sourceFileId + ":" + breakpoint.lineNumber + ":" + breakpoint.columnNumber] = breakpoint;
    993     }
    994 }
    995 
    996 WebInspector.BreakpointManager.Storage.prototype = {
    997     mute: function()
    998     {
    999         this._muted = true;
   1000     },
   1001 
   1002     unmute: function()
   1003     {
   1004         delete this._muted;
   1005     },
   1006 
   1007     /**
   1008      * @param {!WebInspector.UISourceCode} uiSourceCode
   1009      * @return {!Array.<!WebInspector.BreakpointManager.Storage.Item>}
   1010      */
   1011     breakpointItems: function(uiSourceCode)
   1012     {
   1013         var result = [];
   1014         var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode);
   1015         for (var id in this._breakpoints) {
   1016             var breakpoint = this._breakpoints[id];
   1017             if (breakpoint.sourceFileId === sourceFileId)
   1018                 result.push(breakpoint);
   1019         }
   1020         return result;
   1021     },
   1022 
   1023     /**
   1024      * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint
   1025      */
   1026     _updateBreakpoint: function(breakpoint)
   1027     {
   1028         if (this._muted || !breakpoint._breakpointStorageId())
   1029             return;
   1030         this._breakpoints[breakpoint._breakpointStorageId()] = new WebInspector.BreakpointManager.Storage.Item(breakpoint);
   1031         this._save();
   1032     },
   1033 
   1034     /**
   1035      * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint
   1036      */
   1037     _removeBreakpoint: function(breakpoint)
   1038     {
   1039         if (this._muted)
   1040             return;
   1041         delete this._breakpoints[breakpoint._breakpointStorageId()];
   1042         this._save();
   1043     },
   1044 
   1045     _save: function()
   1046     {
   1047         var breakpointsArray = [];
   1048         for (var id in this._breakpoints)
   1049             breakpointsArray.push(this._breakpoints[id]);
   1050         this._setting.set(breakpointsArray);
   1051     }
   1052 }
   1053 
   1054 /**
   1055  * @constructor
   1056  * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint
   1057  */
   1058 WebInspector.BreakpointManager.Storage.Item = function(breakpoint)
   1059 {
   1060     this.sourceFileId = breakpoint._sourceFileId;
   1061     this.lineNumber = breakpoint.lineNumber();
   1062     this.columnNumber = breakpoint.columnNumber();
   1063     this.condition = breakpoint.condition();
   1064     this.enabled = breakpoint.enabled();
   1065 }
   1066 
   1067 /** @type {!WebInspector.BreakpointManager} */
   1068 WebInspector.breakpointManager;
   1069