1/*
2 * Copyright (C) 2010 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(function () {
32
33var DebuggerScript = {};
34
35DebuggerScript.PauseOnExceptionsState = {
36    DontPauseOnExceptions: 0,
37    PauseOnAllExceptions: 1,
38    PauseOnUncaughtExceptions: 2
39};
40
41DebuggerScript.ScopeInfoDetails = {
42    AllScopes: 0,
43    FastAsyncScopes: 1,
44    NoScopes: 2
45};
46
47DebuggerScript._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions;
48Debug.clearBreakOnException();
49Debug.clearBreakOnUncaughtException();
50
51DebuggerScript.getAfterCompileScript = function(eventData)
52{
53    return DebuggerScript._formatScript(eventData.script_.script_);
54}
55
56DebuggerScript.getWorkerScripts = function()
57{
58    var result = [];
59    var scripts = Debug.scripts();
60    for (var i = 0; i < scripts.length; ++i) {
61        var script = scripts[i];
62        // Workers don't share same V8 heap now so there is no need to complicate stuff with
63        // the context id like we do to discriminate between scripts from different pages.
64        // However we need to filter out v8 native scripts.
65        if (script.context_data && script.context_data === "worker")
66            result.push(DebuggerScript._formatScript(script));
67    }
68    return result;
69}
70
71DebuggerScript.getFunctionScopes = function(fun)
72{
73    var mirror = MakeMirror(fun);
74    var count = mirror.scopeCount();
75    if (count == 0)
76        return null;
77    var result = [];
78    for (var i = 0; i < count; i++) {
79        var scopeDetails = mirror.scope(i).details();
80        result[i] = {
81            type: scopeDetails.type(),
82            object: DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object())
83        };
84    }
85    return result;
86}
87
88DebuggerScript.getInternalProperties = function(value)
89{
90    var properties = ObjectMirror.GetInternalProperties(value);
91    var result = [];
92    for (var i = 0; i < properties.length; i++) {
93        var mirror = properties[i];
94        result.push({
95            name: mirror.name(),
96            value: mirror.value().value()
97        });
98    }
99    return result;
100}
101
102DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue)
103{
104    var mirror = MakeMirror(functionValue);
105    if (!mirror.isFunction())
106        throw new Error("Function value has incorrect type");
107    return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue);
108}
109
110DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue)
111{
112    var scopeMirror = scopeHolder.scope(scopeIndex);
113    if (!scopeMirror)
114        throw new Error("Incorrect scope index");
115    scopeMirror.setVariableValue(variableName, newValue);
116    return undefined;
117}
118
119DebuggerScript.getScripts = function(contextData)
120{
121    var result = [];
122
123    if (!contextData)
124        return result;
125    var comma = contextData.indexOf(",");
126    if (comma === -1)
127        return result;
128    // Context data is a string in the following format:
129    // ("page"|"injected")","<page id>
130    var idSuffix = contextData.substring(comma); // including the comma
131
132    var scripts = Debug.scripts();
133    for (var i = 0; i < scripts.length; ++i) {
134        var script = scripts[i];
135        if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1)
136            result.push(DebuggerScript._formatScript(script));
137    }
138    return result;
139}
140
141DebuggerScript._formatScript = function(script)
142{
143    var lineEnds = script.line_ends;
144    var lineCount = lineEnds.length;
145    var endLine = script.line_offset + lineCount - 1;
146    var endColumn;
147    // V8 will not count last line if script source ends with \n.
148    if (script.source[script.source.length - 1] === '\n') {
149        endLine += 1;
150        endColumn = 0;
151    } else {
152        if (lineCount === 1)
153            endColumn = script.source.length + script.column_offset;
154        else
155            endColumn = script.source.length - (lineEnds[lineCount - 2] + 1);
156    }
157
158    return {
159        id: script.id,
160        name: script.nameOrSourceURL(),
161        source: script.source,
162        startLine: script.line_offset,
163        startColumn: script.column_offset,
164        endLine: endLine,
165        endColumn: endColumn,
166        isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0
167    };
168}
169
170DebuggerScript.setBreakpoint = function(execState, info)
171{
172    var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement;
173    var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment);
174
175    var locations = Debug.findBreakPointActualLocations(breakId);
176    if (!locations.length)
177        return undefined;
178    info.lineNumber = locations[0].line;
179    info.columnNumber = locations[0].column;
180    return breakId.toString();
181}
182
183DebuggerScript.removeBreakpoint = function(execState, info)
184{
185    Debug.findBreakPoint(info.breakpointId, true);
186}
187
188DebuggerScript.pauseOnExceptionsState = function()
189{
190    return DebuggerScript._pauseOnExceptionsState;
191}
192
193DebuggerScript.setPauseOnExceptionsState = function(newState)
194{
195    DebuggerScript._pauseOnExceptionsState = newState;
196
197    if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState)
198        Debug.setBreakOnException();
199    else
200        Debug.clearBreakOnException();
201
202    if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState)
203        Debug.setBreakOnUncaughtException();
204    else
205        Debug.clearBreakOnUncaughtException();
206}
207
208DebuggerScript.frameCount = function(execState)
209{
210    return execState.frameCount();
211}
212
213DebuggerScript.currentCallFrame = function(execState, data)
214{
215    var maximumLimit = data >> 2;
216    var scopeDetailsLevel = data & 3;
217
218    var frameCount = execState.frameCount();
219    if (maximumLimit && maximumLimit < frameCount)
220        frameCount = maximumLimit;
221    var topFrame = undefined;
222    for (var i = frameCount - 1; i >= 0; i--) {
223        var frameMirror = execState.frame(i);
224        topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame, scopeDetailsLevel);
225    }
226    return topFrame;
227}
228
229DebuggerScript.stepIntoStatement = function(execState)
230{
231    execState.prepareStep(Debug.StepAction.StepIn, 1);
232}
233
234DebuggerScript.stepOverStatement = function(execState, callFrame)
235{
236    execState.prepareStep(Debug.StepAction.StepNext, 1);
237}
238
239DebuggerScript.stepOutOfFunction = function(execState, callFrame)
240{
241    execState.prepareStep(Debug.StepAction.StepOut, 1);
242}
243
244// Returns array in form:
245//      [ 0, <v8_result_report> ] in case of success
246//   or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based.
247// or throws exception with message.
248DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview)
249{
250    var scripts = Debug.scripts();
251    var scriptToEdit = null;
252    for (var i = 0; i < scripts.length; i++) {
253        if (scripts[i].id == scriptId) {
254            scriptToEdit = scripts[i];
255            break;
256        }
257    }
258    if (!scriptToEdit)
259        throw("Script not found");
260
261    var changeLog = [];
262    try {
263        var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog);
264        return [0, result];
265    } catch (e) {
266        if (e instanceof Debug.LiveEdit.Failure && "details" in e) {
267            var details = e.details;
268            if (details.type === "liveedit_compile_error") {
269                var startPosition = details.position.start;
270                return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)];
271            }
272        }
273        throw e;
274    }
275}
276
277DebuggerScript.clearBreakpoints = function(execState, info)
278{
279    Debug.clearAllBreakPoints();
280}
281
282DebuggerScript.setBreakpointsActivated = function(execState, info)
283{
284    Debug.debuggerFlags().breakPointsActive.setValue(info.enabled);
285}
286
287DebuggerScript.getScriptSource = function(eventData)
288{
289    return eventData.script().source();
290}
291
292DebuggerScript.setScriptSource = function(eventData, source)
293{
294    if (eventData.script().data() === "injected-script")
295        return;
296    eventData.script().setSource(source);
297}
298
299DebuggerScript.getScriptName = function(eventData)
300{
301    return eventData.script().script_.nameOrSourceURL();
302}
303
304DebuggerScript.getBreakpointNumbers = function(eventData)
305{
306    var breakpoints = eventData.breakPointsHit();
307    var numbers = [];
308    if (!breakpoints)
309        return numbers;
310
311    for (var i = 0; i < breakpoints.length; i++) {
312        var breakpoint = breakpoints[i];
313        var scriptBreakPoint = breakpoint.script_break_point();
314        numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number());
315    }
316    return numbers;
317}
318
319DebuggerScript.isEvalCompilation = function(eventData)
320{
321    var script = eventData.script();
322    return (script.compilationType() === Debug.ScriptCompilationType.Eval);
323}
324
325// NOTE: This function is performance critical, as it can be run on every
326// statement that generates an async event (like addEventListener) to support
327// asynchronous call stacks. Thus, when possible, initialize the data lazily.
328DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, scopeDetailsLevel)
329{
330    // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id).
331    // The frameMirror and scopeMirror can be accessed only while paused on the debugger.
332    var frameDetails = frameMirror.details();
333
334    var funcObject = frameDetails.func();
335    var sourcePosition = frameDetails.sourcePosition();
336    var thisObject = frameDetails.receiver();
337
338    var isAtReturn = !!frameDetails.isAtReturn();
339    var returnValue = isAtReturn ? frameDetails.returnValue() : undefined;
340
341    var scopeMirrors = (scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.NoScopes ? [] : frameMirror.allScopes(scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.FastAsyncScopes));
342    var scopeTypes = new Array(scopeMirrors.length);
343    var scopeObjects = new Array(scopeMirrors.length);
344    for (var i = 0; i < scopeMirrors.length; ++i) {
345        var scopeDetails = scopeMirrors[i].details();
346        scopeTypes[i] = scopeDetails.type();
347        scopeObjects[i] = scopeDetails.object();
348    }
349
350    // Calculated lazily.
351    var scopeChain;
352    var funcMirror;
353    var location;
354
355    function lazyScopeChain()
356    {
357        if (!scopeChain) {
358            scopeChain = [];
359            for (var i = 0; i < scopeObjects.length; ++i)
360                scopeChain.push(DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]));
361            scopeObjects = null; // Free for GC.
362        }
363        return scopeChain;
364    }
365
366    function ensureFuncMirror()
367    {
368        if (!funcMirror) {
369            funcMirror = MakeMirror(funcObject);
370            if (!funcMirror.isFunction())
371                funcMirror = new UnresolvedFunctionMirror(funcObject);
372        }
373        return funcMirror;
374    }
375
376    function ensureLocation()
377    {
378        if (!location) {
379            var script = ensureFuncMirror().script();
380            if (script)
381                location = script.locationFromPosition(sourcePosition, true);
382            if (!location)
383                location = { line: 0, column: 0 };
384        }
385        return location;
386    }
387
388    function line()
389    {
390        return ensureLocation().line;
391    }
392
393    function column()
394    {
395        return ensureLocation().column;
396    }
397
398    function sourceID()
399    {
400        var script = ensureFuncMirror().script();
401        return script && script.id();
402    }
403
404    function functionName()
405    {
406        var func = ensureFuncMirror();
407        if (!func.resolved())
408            return undefined;
409        var displayName;
410        var valueMirror = func.property("displayName").value();
411        if (valueMirror && valueMirror.isString())
412            displayName = valueMirror.value();
413        return displayName || func.name() || func.inferredName();
414    }
415
416    function evaluate(expression)
417    {
418        return frameMirror.evaluate(expression, false).value();
419    }
420
421    function restart()
422    {
423        return Debug.LiveEdit.RestartFrame(frameMirror);
424    }
425
426    function setVariableValue(scopeNumber, variableName, newValue)
427    {
428        return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue);
429    }
430
431    function stepInPositions()
432    {
433        var stepInPositionsV8 = frameMirror.stepInPositions();
434        var stepInPositionsProtocol;
435        if (stepInPositionsV8) {
436            stepInPositionsProtocol = [];
437            var script = ensureFuncMirror().script();
438            if (script) {
439                var scriptId = String(script.id());
440                for (var i = 0; i < stepInPositionsV8.length; i++) {
441                    var item = {
442                        scriptId: scriptId,
443                        lineNumber: stepInPositionsV8[i].position.line,
444                        columnNumber: stepInPositionsV8[i].position.column
445                    };
446                    stepInPositionsProtocol.push(item);
447                }
448            }
449        }
450        return JSON.stringify(stepInPositionsProtocol);
451    }
452
453    return {
454        "sourceID": sourceID,
455        "line": line,
456        "column": column,
457        "functionName": functionName,
458        "thisObject": thisObject,
459        "scopeChain": lazyScopeChain,
460        "scopeType": scopeTypes,
461        "evaluate": evaluate,
462        "caller": callerFrame,
463        "restart": restart,
464        "setVariableValue": setVariableValue,
465        "stepInPositions": stepInPositions,
466        "isAtReturn": isAtReturn,
467        "returnValue": returnValue
468    };
469}
470
471DebuggerScript._buildScopeObject = function(scopeType, scopeObject)
472{
473    var result;
474    switch (scopeType) {
475    case ScopeType.Local:
476    case ScopeType.Closure:
477    case ScopeType.Catch:
478        // For transient objects we create a "persistent" copy that contains
479        // the same properties.
480        // Reset scope object prototype to null so that the proto properties
481        // don't appear in the local scope section.
482        result = { __proto__: null };
483        var properties = MakeMirror(scopeObject, true /* transient */).properties();
484        for (var j = 0; j < properties.length; j++) {
485            var name = properties[j].name();
486            if (name.charAt(0) === ".")
487                continue; // Skip internal variables like ".arguments"
488            result[name] = properties[j].value_;
489        }
490        break;
491    case ScopeType.Global:
492    case ScopeType.With:
493        result = scopeObject;
494        break;
495    case ScopeType.Block:
496        // Unsupported yet. Mustn't be reachable.
497        break;
498    }
499    return result;
500}
501
502// We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it.
503ToggleMirrorCache(false);
504
505return DebuggerScript;
506})();
507