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._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions;
42Debug.clearBreakOnException();
43Debug.clearBreakOnUncaughtException();
44
45DebuggerScript.getAfterCompileScript = function(eventData)
46{
47    return DebuggerScript._formatScript(eventData.script_.script_);
48}
49
50DebuggerScript.getWorkerScripts = function()
51{
52    var result = [];
53    var scripts = Debug.scripts();
54    for (var i = 0; i < scripts.length; ++i) {
55        var script = scripts[i];
56        // Workers don't share same V8 heap now so there is no need to complicate stuff with
57        // the context id like we do to discriminate between scripts from different pages.
58        // However we need to filter out v8 native scripts.
59        if (script.context_data && script.context_data === "worker")
60            result.push(DebuggerScript._formatScript(script));
61    }
62    return result;
63}
64
65DebuggerScript.getFunctionScopes = function(fun)
66{
67    var mirror = MakeMirror(fun);
68    var count = mirror.scopeCount();
69    if (count == 0)
70        return null;
71    var result = [];
72    for (var i = 0; i < count; i++) {
73        var scopeMirror = mirror.scope(i);
74        result[i] = {
75            type: scopeMirror.scopeType(),
76            object: DebuggerScript._buildScopeObject(scopeMirror)
77        };
78    }
79    return result;
80}
81
82DebuggerScript.getInternalProperties = function(value)
83{
84    var properties = ObjectMirror.GetInternalProperties(value);
85    var result = [];
86    for (var i = 0; i < properties.length; i++) {
87        var mirror = properties[i];
88        result.push({
89            name: mirror.name(),
90            value: mirror.value().value()
91        });
92    }
93    return result;
94}
95
96DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue)
97{
98    var mirror = MakeMirror(functionValue);
99    if (!mirror.isFunction())
100        throw new Error("Function value has incorrect type");
101    return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue);
102}
103
104DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue)
105{
106    var scopeMirror = scopeHolder.scope(scopeIndex);
107    if (!scopeMirror)
108        throw new Error("Incorrect scope index");
109    scopeMirror.setVariableValue(variableName, newValue);
110    return undefined;
111}
112
113DebuggerScript.getScripts = function(contextData)
114{
115    var result = [];
116
117    if (!contextData)
118        return result;
119    var comma = contextData.indexOf(",");
120    if (comma === -1)
121        return result;
122    // Context data is a string in the following format:
123    // ("page"|"injected")","<page id>
124    var idSuffix = contextData.substring(comma); // including the comma
125
126    var scripts = Debug.scripts();
127    for (var i = 0; i < scripts.length; ++i) {
128        var script = scripts[i];
129        if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1)
130            result.push(DebuggerScript._formatScript(script));
131    }
132    return result;
133}
134
135DebuggerScript._formatScript = function(script)
136{
137    var lineEnds = script.line_ends;
138    var lineCount = lineEnds.length;
139    var endLine = script.line_offset + lineCount - 1;
140    var endColumn;
141    // V8 will not count last line if script source ends with \n.
142    if (script.source[script.source.length - 1] === '\n') {
143        endLine += 1;
144        endColumn = 0;
145    } else {
146        if (lineCount === 1)
147            endColumn = script.source.length + script.column_offset;
148        else
149            endColumn = script.source.length - (lineEnds[lineCount - 2] + 1);
150    }
151
152    return {
153        id: script.id,
154        name: script.nameOrSourceURL(),
155        source: script.source,
156        startLine: script.line_offset,
157        startColumn: script.column_offset,
158        endLine: endLine,
159        endColumn: endColumn,
160        isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0
161    };
162}
163
164DebuggerScript.setBreakpoint = function(execState, args)
165{
166    var positionAlignment = args.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement;
167    var breakId = Debug.setScriptBreakPointById(args.sourceID, args.lineNumber, args.columnNumber, args.condition, undefined, positionAlignment);
168
169    var locations = Debug.findBreakPointActualLocations(breakId);
170    if (!locations.length)
171        return undefined;
172    args.lineNumber = locations[0].line;
173    args.columnNumber = locations[0].column;
174    return breakId.toString();
175}
176
177DebuggerScript.removeBreakpoint = function(execState, args)
178{
179    Debug.findBreakPoint(args.breakpointId, true);
180}
181
182DebuggerScript.pauseOnExceptionsState = function()
183{
184    return DebuggerScript._pauseOnExceptionsState;
185}
186
187DebuggerScript.setPauseOnExceptionsState = function(newState)
188{
189    DebuggerScript._pauseOnExceptionsState = newState;
190
191    if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState)
192        Debug.setBreakOnException();
193    else
194        Debug.clearBreakOnException();
195
196    if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState)
197        Debug.setBreakOnUncaughtException();
198    else
199        Debug.clearBreakOnUncaughtException();
200}
201
202DebuggerScript.currentCallFrame = function(execState, maximumLimit)
203{
204    var frameCount = execState.frameCount();
205    if (maximumLimit >= 0 && maximumLimit < frameCount)
206        frameCount = maximumLimit;
207    var topFrame = undefined;
208    for (var i = frameCount - 1; i >= 0; i--) {
209        var frameMirror = execState.frame(i);
210        topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame);
211    }
212    return topFrame;
213}
214
215DebuggerScript.stepIntoStatement = function(execState)
216{
217    execState.prepareStep(Debug.StepAction.StepIn, 1);
218}
219
220DebuggerScript.stepOverStatement = function(execState)
221{
222    execState.prepareStep(Debug.StepAction.StepNext, 1);
223}
224
225DebuggerScript.stepOutOfFunction = function(execState)
226{
227    execState.prepareStep(Debug.StepAction.StepOut, 1);
228}
229
230// Returns array in form:
231//      [ 0, <v8_result_report> ] in case of success
232//   or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based.
233// or throws exception with message.
234DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview)
235{
236    var scripts = Debug.scripts();
237    var scriptToEdit = null;
238    for (var i = 0; i < scripts.length; i++) {
239        if (scripts[i].id == scriptId) {
240            scriptToEdit = scripts[i];
241            break;
242        }
243    }
244    if (!scriptToEdit)
245        throw("Script not found");
246
247    var changeLog = [];
248    try {
249        var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog);
250        return [0, result];
251    } catch (e) {
252        if (e instanceof Debug.LiveEdit.Failure && "details" in e) {
253            var details = e.details;
254            if (details.type === "liveedit_compile_error") {
255                var startPosition = details.position.start;
256                return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)];
257            }
258        }
259        throw e;
260    }
261}
262
263DebuggerScript.clearBreakpoints = function(execState, args)
264{
265    Debug.clearAllBreakPoints();
266}
267
268DebuggerScript.setBreakpointsActivated = function(execState, args)
269{
270    Debug.debuggerFlags().breakPointsActive.setValue(args.enabled);
271}
272
273DebuggerScript.getScriptSource = function(eventData)
274{
275    return eventData.script().source();
276}
277
278DebuggerScript.setScriptSource = function(eventData, source)
279{
280    if (eventData.script().data() === "injected-script")
281        return;
282    eventData.script().setSource(source);
283}
284
285DebuggerScript.getScriptName = function(eventData)
286{
287    return eventData.script().script_.nameOrSourceURL();
288}
289
290DebuggerScript.getBreakpointNumbers = function(eventData)
291{
292    var breakpoints = eventData.breakPointsHit();
293    var numbers = [];
294    if (!breakpoints)
295        return numbers;
296
297    for (var i = 0; i < breakpoints.length; i++) {
298        var breakpoint = breakpoints[i];
299        var scriptBreakPoint = breakpoint.script_break_point();
300        numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number());
301    }
302    return numbers;
303}
304
305DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame)
306{
307    // Get function name.
308    var func;
309    try {
310        func = frameMirror.func();
311    } catch(e) {
312    }
313    var functionName;
314    if (func)
315        functionName = func.name() || func.inferredName();
316
317    // Get script ID.
318    var script = func.script();
319    var sourceID = script && script.id();
320
321    // Get location.
322    var location  = frameMirror.sourceLocation();
323
324    // Get this object.
325    var thisObject = frameMirror.details_.receiver();
326
327    var scopeChain = [];
328    var scopeType = [];
329    for (var i = 0; i < frameMirror.scopeCount(); i++) {
330        var scopeMirror = frameMirror.scope(i);
331        scopeType.push(scopeMirror.scopeType());
332        scopeChain.push(DebuggerScript._buildScopeObject(scopeMirror));
333    }
334
335    function evaluate(expression)
336    {
337        return frameMirror.evaluate(expression, false).value();
338    }
339
340    function restart()
341    {
342        return Debug.LiveEdit.RestartFrame(frameMirror);
343    }
344
345    function setVariableValue(scopeNumber, variableName, newValue)
346    {
347        return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue);
348    }
349
350    function stepInPositions()
351    {
352        var stepInPositionsV8 = frameMirror.stepInPositions();
353        var stepInPositionsProtocol;
354        if (stepInPositionsV8) {
355            stepInPositionsProtocol = [];
356            var script = frameMirror.func().script();
357            if (script) {
358                var scriptId = String(script.id());
359                for (var i = 0; i < stepInPositionsV8.length; i++) {
360                    var item = {
361                        scriptId: scriptId,
362                        lineNumber: stepInPositionsV8[i].position.line,
363                        columnNumber: stepInPositionsV8[i].position.column
364                    };
365                    stepInPositionsProtocol.push(item);
366                }
367            }
368        }
369        return JSON.stringify(stepInPositionsProtocol);
370    }
371
372    return {
373        "sourceID": sourceID,
374        "line": location ? location.line : 0,
375        "column": location ? location.column : 0,
376        "functionName": functionName,
377        "thisObject": thisObject,
378        "scopeChain": scopeChain,
379        "scopeType": scopeType,
380        "evaluate": evaluate,
381        "caller": callerFrame,
382        "restart": restart,
383        "setVariableValue": setVariableValue,
384        "stepInPositions": stepInPositions
385    };
386}
387
388DebuggerScript._buildScopeObject = function(scopeMirror) {
389    var scopeObject;
390    switch (scopeMirror.scopeType()) {
391    case ScopeType.Local:
392    case ScopeType.Closure:
393    case ScopeType.Catch:
394        // For transient objects we create a "persistent" copy that contains
395        // the same properties.
396        scopeObject = {};
397        // Reset scope object prototype to null so that the proto properties
398        // don't appear in the local scope section.
399        scopeObject.__proto__ = null;
400        var scopeObjectMirror = scopeMirror.scopeObject();
401        var properties = scopeObjectMirror.properties();
402        for (var j = 0; j < properties.length; j++) {
403            var name = properties[j].name();
404            if (name.charAt(0) === ".")
405                continue; // Skip internal variables like ".arguments"
406            scopeObject[name] = properties[j].value_;
407        }
408        break;
409    case ScopeType.Global:
410    case ScopeType.With:
411        scopeObject = scopeMirror.details_.object();
412        break;
413    case ScopeType.Block:
414        // Unsupported yet. Mustn't be reachable.
415        break;
416    }
417    return scopeObject;
418}
419
420return DebuggerScript;
421})();
422