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, info)
165{
166    var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement;
167    var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment);
168
169    var locations = Debug.findBreakPointActualLocations(breakId);
170    if (!locations.length)
171        return undefined;
172    info.lineNumber = locations[0].line;
173    info.columnNumber = locations[0].column;
174    return breakId.toString();
175}
176
177DebuggerScript.removeBreakpoint = function(execState, info)
178{
179    Debug.findBreakPoint(info.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, callFrame)
221{
222    var frameMirror = callFrame ? callFrame.frameMirror : undefined;
223    execState.prepareStep(Debug.StepAction.StepNext, 1, frameMirror);
224}
225
226DebuggerScript.stepOutOfFunction = function(execState, callFrame)
227{
228    var frameMirror = callFrame ? callFrame.frameMirror : undefined;
229    execState.prepareStep(Debug.StepAction.StepOut, 1, frameMirror);
230}
231
232// Returns array in form:
233//      [ 0, <v8_result_report> ] in case of success
234//   or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based.
235// or throws exception with message.
236DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview)
237{
238    var scripts = Debug.scripts();
239    var scriptToEdit = null;
240    for (var i = 0; i < scripts.length; i++) {
241        if (scripts[i].id == scriptId) {
242            scriptToEdit = scripts[i];
243            break;
244        }
245    }
246    if (!scriptToEdit)
247        throw("Script not found");
248
249    var changeLog = [];
250    try {
251        var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog);
252        return [0, result];
253    } catch (e) {
254        if (e instanceof Debug.LiveEdit.Failure && "details" in e) {
255            var details = e.details;
256            if (details.type === "liveedit_compile_error") {
257                var startPosition = details.position.start;
258                return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)];
259            }
260        }
261        throw e;
262    }
263}
264
265DebuggerScript.clearBreakpoints = function(execState, info)
266{
267    Debug.clearAllBreakPoints();
268}
269
270DebuggerScript.setBreakpointsActivated = function(execState, info)
271{
272    Debug.debuggerFlags().breakPointsActive.setValue(info.enabled);
273}
274
275DebuggerScript.getScriptSource = function(eventData)
276{
277    return eventData.script().source();
278}
279
280DebuggerScript.setScriptSource = function(eventData, source)
281{
282    if (eventData.script().data() === "injected-script")
283        return;
284    eventData.script().setSource(source);
285}
286
287DebuggerScript.getScriptName = function(eventData)
288{
289    return eventData.script().script_.nameOrSourceURL();
290}
291
292DebuggerScript.getBreakpointNumbers = function(eventData)
293{
294    var breakpoints = eventData.breakPointsHit();
295    var numbers = [];
296    if (!breakpoints)
297        return numbers;
298
299    for (var i = 0; i < breakpoints.length; i++) {
300        var breakpoint = breakpoints[i];
301        var scriptBreakPoint = breakpoint.script_break_point();
302        numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number());
303    }
304    return numbers;
305}
306
307DebuggerScript.isEvalCompilation = function(eventData)
308{
309    var script = eventData.script();
310    return (script.compilationType() === Debug.ScriptCompilationType.Eval);
311}
312
313DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame)
314{
315    // Get function name and display name.
316    var funcMirror;
317    var displayName;
318    try {
319        funcMirror = frameMirror.func();
320        if (funcMirror) {
321            var valueMirror = funcMirror.property("displayName").value();
322            if (valueMirror && valueMirror.isString())
323                displayName = valueMirror.value();
324        }
325    } catch(e) {
326    }
327    var functionName;
328    if (funcMirror)
329        functionName = displayName || funcMirror.name() || funcMirror.inferredName();
330
331    // Get script ID.
332    var script = funcMirror.script();
333    var sourceID = script && script.id();
334
335    // Get location.
336    var location  = frameMirror.sourceLocation();
337
338    // Get this object.
339    var thisObject = frameMirror.details_.receiver();
340
341    var isAtReturn = !!frameMirror.details_.isAtReturn();
342    var returnValue = isAtReturn ? frameMirror.details_.returnValue() : undefined;
343
344    var scopeChain = [];
345    var scopeType = [];
346    for (var i = 0; i < frameMirror.scopeCount(); i++) {
347        var scopeMirror = frameMirror.scope(i);
348        scopeType.push(scopeMirror.scopeType());
349        scopeChain.push(DebuggerScript._buildScopeObject(scopeMirror));
350    }
351
352    function evaluate(expression)
353    {
354        return frameMirror.evaluate(expression, false).value();
355    }
356
357    function restart()
358    {
359        return Debug.LiveEdit.RestartFrame(frameMirror);
360    }
361
362    function setVariableValue(scopeNumber, variableName, newValue)
363    {
364        return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue);
365    }
366
367    function stepInPositions()
368    {
369        var stepInPositionsV8 = frameMirror.stepInPositions();
370        var stepInPositionsProtocol;
371        if (stepInPositionsV8) {
372            stepInPositionsProtocol = [];
373            var script = frameMirror.func().script();
374            if (script) {
375                var scriptId = String(script.id());
376                for (var i = 0; i < stepInPositionsV8.length; i++) {
377                    var item = {
378                        scriptId: scriptId,
379                        lineNumber: stepInPositionsV8[i].position.line,
380                        columnNumber: stepInPositionsV8[i].position.column
381                    };
382                    stepInPositionsProtocol.push(item);
383                }
384            }
385        }
386        return JSON.stringify(stepInPositionsProtocol);
387    }
388
389    return {
390        "sourceID": sourceID,
391        "line": location ? location.line : 0,
392        "column": location ? location.column : 0,
393        "functionName": functionName,
394        "thisObject": thisObject,
395        "scopeChain": scopeChain,
396        "scopeType": scopeType,
397        "evaluate": evaluate,
398        "caller": callerFrame,
399        "restart": restart,
400        "setVariableValue": setVariableValue,
401        "stepInPositions": stepInPositions,
402        "isAtReturn": isAtReturn,
403        "returnValue": returnValue,
404        "frameMirror": frameMirror
405    };
406}
407
408DebuggerScript._buildScopeObject = function(scopeMirror) {
409    var scopeObject;
410    switch (scopeMirror.scopeType()) {
411    case ScopeType.Local:
412    case ScopeType.Closure:
413    case ScopeType.Catch:
414        // For transient objects we create a "persistent" copy that contains
415        // the same properties.
416        scopeObject = {};
417        // Reset scope object prototype to null so that the proto properties
418        // don't appear in the local scope section.
419        scopeObject.__proto__ = null;
420        var scopeObjectMirror = scopeMirror.scopeObject();
421        var properties = scopeObjectMirror.properties();
422        for (var j = 0; j < properties.length; j++) {
423            var name = properties[j].name();
424            if (name.charAt(0) === ".")
425                continue; // Skip internal variables like ".arguments"
426            scopeObject[name] = properties[j].value_;
427        }
428        break;
429    case ScopeType.Global:
430    case ScopeType.With:
431        scopeObject = scopeMirror.details_.object();
432        break;
433    case ScopeType.Block:
434        // Unsupported yet. Mustn't be reachable.
435        break;
436    }
437    return scopeObject;
438}
439
440return DebuggerScript;
441})();
442