stack.js (5394B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /** 8 * A helper class that stores stack frame objects. Each frame is 9 * assigned an index, and if a frame is added more than once, the same 10 * index is used. Users of the class can get an array of all frames 11 * that have been added. 12 */ 13 class StackFrameCache { 14 /** 15 * Initialize this object. 16 */ 17 constructor() { 18 this._framesToIndices = null; 19 this._framesToForms = null; 20 this._lastEventSize = 0; 21 } 22 23 /** 24 * Prepare to accept frames. 25 */ 26 initFrames() { 27 if (this._framesToIndices) { 28 // The maps are already initialized. 29 return; 30 } 31 32 this._framesToIndices = new Map(); 33 this._framesToForms = new Map(); 34 this._lastEventSize = 0; 35 } 36 37 /** 38 * Forget all stored frames and reset to the initialized state. 39 */ 40 clearFrames() { 41 this._framesToIndices.clear(); 42 this._framesToIndices = null; 43 this._framesToForms.clear(); 44 this._framesToForms = null; 45 this._lastEventSize = 0; 46 } 47 48 /** 49 * Add a frame to this stack frame cache, and return the index of 50 * the frame. 51 */ 52 addFrame(frame) { 53 this._assignFrameIndices(frame); 54 this._createFrameForms(frame); 55 return this._framesToIndices.get(frame); 56 } 57 58 /** 59 * A helper method for the memory actor. This populates the packet 60 * object with "frames" property. Each of these 61 * properties will be an array indexed by frame ID. "frames" will 62 * contain frame objects (see makeEvent). 63 * 64 * @param packet 65 * The packet to update. 66 * 67 * @returns packet 68 */ 69 updateFramePacket(packet) { 70 // Now that we are guaranteed to have a form for every frame, we know the 71 // size the "frames" property's array must be. We use that information to 72 // create dense arrays even though we populate them out of order. 73 const size = this._framesToForms.size; 74 packet.frames = Array(size).fill(null); 75 76 // Populate the "frames" properties. 77 for (const [stack, index] of this._framesToIndices) { 78 packet.frames[index] = this._framesToForms.get(stack); 79 } 80 81 return packet; 82 } 83 84 /** 85 * If any new stack frames have been added to this cache since the 86 * last call to makeEvent (clearing the cache also resets the "last 87 * call"), then return a new array describing the new frames. If no 88 * new frames are available, return null. 89 * 90 * The frame cache assumes that the user of the cache keeps track of 91 * all previously-returned arrays and, in theory, concatenates them 92 * all to form a single array holding all frames added to the cache 93 * since the last reset. This concatenated array can be indexed by 94 * the frame ID. The array returned by this function, though, is 95 * dense and starts at 0. 96 * 97 * Each element in the array is an object of the form: 98 * { 99 * line: <line number for this frame>, 100 * column: <column number for this frame>, 101 * source: <filename string for this frame>, 102 * functionDisplayName: <this frame's inferred function name function or null>, 103 * parent: <frame ID -- an index into the concatenated array mentioned above> 104 * asyncCause: the async cause, or null 105 * asyncParent: <frame ID -- an index into the concatenated array mentioned above> 106 * } 107 * 108 * The intent of this approach is to make it simpler to efficiently 109 * send frame information over the debugging protocol, by only 110 * sending new frames. 111 * 112 * @returns array or null 113 */ 114 makeEvent() { 115 const size = this._framesToForms.size; 116 if (!size || size <= this._lastEventSize) { 117 return null; 118 } 119 120 const packet = Array(size - this._lastEventSize).fill(null); 121 for (const [stack, index] of this._framesToIndices) { 122 if (index >= this._lastEventSize) { 123 packet[index - this._lastEventSize] = this._framesToForms.get(stack); 124 } 125 } 126 127 this._lastEventSize = size; 128 129 return packet; 130 } 131 132 /** 133 * Assigns an index to the given frame and its parents, if an index is not 134 * already assigned. 135 * 136 * @param SavedFrame frame 137 * A frame to assign an index to. 138 */ 139 _assignFrameIndices(frame) { 140 if (this._framesToIndices.has(frame)) { 141 return; 142 } 143 144 if (frame) { 145 this._assignFrameIndices(frame.parent); 146 this._assignFrameIndices(frame.asyncParent); 147 } 148 149 const index = this._framesToIndices.size; 150 this._framesToIndices.set(frame, index); 151 } 152 153 /** 154 * Create the form for the given frame, if one doesn't already exist. 155 * 156 * @param SavedFrame frame 157 * A frame to create a form for. 158 */ 159 _createFrameForms(frame) { 160 if (this._framesToForms.has(frame)) { 161 return; 162 } 163 164 let form = null; 165 if (frame) { 166 form = { 167 line: frame.line, 168 column: frame.column, 169 source: frame.source, 170 functionDisplayName: frame.functionDisplayName, 171 parent: this._framesToIndices.get(frame.parent), 172 asyncParent: this._framesToIndices.get(frame.asyncParent), 173 asyncCause: frame.asyncCause, 174 }; 175 this._createFrameForms(frame.parent); 176 this._createFrameForms(frame.asyncParent); 177 } 178 179 this._framesToForms.set(frame, form); 180 } 181 } 182 183 exports.StackFrameCache = StackFrameCache;