/* * Copyright (C) 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "DebuggerDocument.h" #include "ServerConnection.h" #include #include #include #include DebuggerDocument::DebuggerDocument(ServerConnection* serverConn) : m_server(serverConn) { ASSERT(m_server); } //-- Callbacks JSValueRef DebuggerDocument::breakpointEditorHTMLCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef /*thisObject*/, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/) { RetainPtr htmlURLRef(AdoptCF, ::CFBundleCopyResourceURL(::CFBundleGetBundleWithIdentifier(CFSTR("org.webkit.drosera")), CFSTR("breakpointEditor"), CFSTR("html"), 0)); if (!htmlURLRef) return JSValueMakeUndefined(context); // FIXME: I'm open to a better way to do this. We convert from UInt8 to CFString to JSString (3 string types!) RetainPtr readStreamRef(AdoptCF, CFReadStreamCreateWithFile(0, htmlURLRef.get())); CFReadStreamRef readStream = readStreamRef.get(); if (!CFReadStreamOpen(readStream)) return JSValueMakeUndefined(context); // Large enough for current BreakPointEditor.html but won't need to be changed if that file changes // because we loop over the entire file and read it in bufferLength pieces at a time const CFIndex bufferLength = 740; UInt8 buffer[bufferLength]; Vector charBuffer; CFIndex readResult = bufferLength; while (readResult == bufferLength) { readResult = CFReadStreamRead(readStream, buffer, bufferLength); // Error condition (-1) will not copy any data for (int i = 0; i < readResult; i++) charBuffer.append(buffer[i]); } CFReadStreamClose(readStream); if (readResult == -1) return JSValueMakeUndefined(context); // FIXME: Is there a way to determine the encoding? RetainPtr fileContents(AdoptCF, CFStringCreateWithBytes(0, charBuffer.data(), charBuffer.size(), kCFStringEncodingUTF8, true)); JSRetainPtr fileContentsJS(Adopt, JSStringCreateWithCFString(fileContents.get())); JSValueRef ret = JSValueMakeString(context, fileContentsJS.get()); return ret; } JSValueRef DebuggerDocument::pauseCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/) { DebuggerDocument* debuggerDocument = reinterpret_cast(JSObjectGetPrivate(thisObject)); debuggerDocument->platformPause(); return JSValueMakeUndefined(context); } JSValueRef DebuggerDocument::resumeCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/) { DebuggerDocument* debuggerDocument = reinterpret_cast(JSObjectGetPrivate(thisObject)); debuggerDocument->platformResume(); return JSValueMakeUndefined(context); } JSValueRef DebuggerDocument::stepIntoCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/) { DebuggerDocument* debuggerDocument = reinterpret_cast(JSObjectGetPrivate(thisObject)); debuggerDocument->platformStepInto(); return JSValueMakeUndefined(context); } JSValueRef DebuggerDocument::evaluateScriptCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { DebuggerDocument* debuggerDocument = reinterpret_cast(JSObjectGetPrivate(thisObject)); if (argumentCount < 2) return JSValueMakeUndefined(context); if (!JSValueIsNumber(context, arguments[1])) return JSValueMakeUndefined(context); double callFrame = JSValueToNumber(context, arguments[1], exception); ASSERT(!*exception); JSRetainPtr script(Adopt, JSValueToStringCopy(context, arguments[0], exception)); ASSERT(!*exception); JSValueRef ret = debuggerDocument->platformEvaluateScript(context, script.get(), (int)callFrame); return ret; } JSValueRef DebuggerDocument::currentFunctionStackCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* exception) { DebuggerDocument* debuggerDocument = reinterpret_cast(JSObjectGetPrivate(thisObject)); Vector stack; debuggerDocument->getPlatformCurrentFunctionStack(context, stack); return DebuggerDocument::toJSArray(context, stack, exception); } JSValueRef DebuggerDocument::localScopeVariableNamesForCallFrameCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { DebuggerDocument* debuggerDocument = reinterpret_cast(JSObjectGetPrivate(thisObject)); if (argumentCount < 1) return JSValueMakeUndefined(context); if (!JSValueIsNumber(context, arguments[0])) return JSValueMakeUndefined(context); double callFrame = JSValueToNumber(context, arguments[0], exception); ASSERT(!*exception); // Get the variable names Vector localVariableNames; debuggerDocument->getPlatformLocalScopeVariableNamesForCallFrame(context, static_cast(callFrame), localVariableNames); return DebuggerDocument::toJSArray(context, localVariableNames, exception); } JSValueRef DebuggerDocument::valueForScopeVariableNamedCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { DebuggerDocument* debuggerDocument = reinterpret_cast(JSObjectGetPrivate(thisObject)); if (argumentCount < 2) return JSValueMakeUndefined(context); if (!JSValueIsString(context, arguments[0])) return JSValueMakeUndefined(context); JSRetainPtr key(Adopt, JSValueToStringCopy(context, arguments[0], exception)); ASSERT(!*exception); if (!JSValueIsNumber(context, arguments[1])) return JSValueMakeUndefined(context); double callFrame = JSValueToNumber(context, arguments[1], exception); ASSERT(!*exception); return debuggerDocument->platformValueForScopeVariableNamed(context, key.get(), (int)callFrame); } JSValueRef DebuggerDocument::logCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef /*thisObject*/, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 1) return JSValueMakeUndefined(context); if (!JSValueIsString(context, arguments[0])) return JSValueMakeUndefined(context); JSRetainPtr msg(Adopt, JSValueToStringCopy(context, arguments[0], exception)); ASSERT(!*exception); DebuggerDocument::platformLog(msg.get()); return JSValueMakeUndefined(context); } //-- These are the calls into the JS. --// bool DebuggerDocument::isPaused(JSContextRef context) const { JSObjectRef globalObject = JSContextGetGlobalObject(context); JSRetainPtr string(Adopt, JSStringCreateWithUTF8CString("isPaused")); JSValueRef objectProperty = JSObjectGetProperty(context, globalObject, string.get(), 0); return JSValueToBoolean(context, objectProperty); } void DebuggerDocument::updateFileSource(JSContextRef context, JSStringRef documentSource, JSStringRef url) { JSValueRef documentSourceValue = JSValueMakeString(context, documentSource); JSValueRef urlValue = JSValueMakeString(context, url); JSValueRef forceValue = JSValueMakeBoolean(context, true); JSValueRef arguments[] = { documentSourceValue, urlValue, forceValue }; int argumentsSize = sizeof(arguments)/sizeof(arguments[0]); DebuggerDocument::callGlobalFunction(context, "updateFileSource", argumentsSize, arguments); } void DebuggerDocument::didParseScript(JSContextRef context, JSStringRef source, JSStringRef documentSource, JSStringRef url, JSValueRef sourceId, JSValueRef baseLine) { JSValueRef sourceValue = JSValueMakeString(context, source); JSValueRef documentSourceValue = JSValueMakeString(context, documentSource); JSValueRef urlValue = JSValueMakeString(context, url); JSValueRef arguments[] = { sourceValue, documentSourceValue, urlValue, sourceId, baseLine }; int argumentsSize = sizeof(arguments)/sizeof(arguments[0]); DebuggerDocument::callGlobalFunction(context, "didParseScript", argumentsSize, arguments); } void DebuggerDocument::willExecuteStatement(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception) { JSValueRef arguments[] = { sourceId, lineno }; int argumentsSize = sizeof(arguments)/sizeof(arguments[0]); DebuggerDocument::callGlobalFunction(context, "willExecuteStatement", argumentsSize, arguments, exception); if (exception && *exception) logException(context, *exception); } void DebuggerDocument::didEnterCallFrame(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception) { JSValueRef arguments[] = { sourceId, lineno }; int argumentsSize = sizeof(arguments)/sizeof(arguments[0]); DebuggerDocument::callGlobalFunction(context, "didEnterCallFrame", argumentsSize, arguments, exception); if (exception && *exception) logException(context, *exception); } void DebuggerDocument::willLeaveCallFrame(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception) { JSValueRef arguments[] = { sourceId, lineno }; int argumentsSize = sizeof(arguments)/sizeof(arguments[0]); DebuggerDocument::callGlobalFunction(context, "willLeaveCallFrame", argumentsSize, arguments, exception); if (exception && *exception) logException(context, *exception); } void DebuggerDocument::exceptionWasRaised(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception) { JSValueRef arguments[] = { sourceId, lineno }; int argumentsSize = sizeof(arguments)/sizeof(arguments[0]); DebuggerDocument::callGlobalFunction(context, "exceptionWasRaised", argumentsSize, arguments, exception); } void DebuggerDocument::windowScriptObjectAvailable(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception) { JSRetainPtr droseraStr(Adopt, JSStringCreateWithUTF8CString("DebuggerDocument")); JSValueRef droseraObject = JSObjectMake(context, getDroseraJSClass(), this); JSObjectSetProperty(context, windowObject, droseraStr.get(), droseraObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception); if (exception && *exception) logException(context, *exception); } JSValueRef DebuggerDocument::toJSArray(JSContextRef context, Vector& vectorValues, JSValueRef* exception) { JSObjectRef globalObject = JSContextGetGlobalObject(context); JSRetainPtr constructorString(Adopt, JSStringCreateWithUTF8CString("Array")); JSValueRef constructorProperty = JSObjectGetProperty(context, globalObject, constructorString.get(), exception); ASSERT(!*exception); JSObjectRef arrayConstructor = JSValueToObject(context, constructorProperty, exception); ASSERT(!*exception); JSObjectRef array = JSObjectCallAsConstructor(context, arrayConstructor, 0, 0, exception); ASSERT(!*exception); JSRetainPtr pushString(Adopt, JSStringCreateWithUTF8CString("push")); JSValueRef pushValue = JSObjectGetProperty(context, array, pushString.get(), exception); ASSERT(!*exception); JSObjectRef push = JSValueToObject(context, pushValue, exception); ASSERT(!*exception); for (Vector::iterator it = vectorValues.begin(); it != vectorValues.end(); ++it) { JSObjectCallAsFunction(context, push, array, 1, it, exception); ASSERT(!*exception); } return array; } // Private JSValueRef DebuggerDocument::callGlobalFunction(JSContextRef context, const char* functionName, int argumentCount, JSValueRef arguments[], JSValueRef* exception) { JSObjectRef globalObject = JSContextGetGlobalObject(context); return callFunctionOnObject(context, globalObject, functionName, argumentCount, arguments, exception); } JSValueRef DebuggerDocument::callFunctionOnObject(JSContextRef context, JSObjectRef object, const char* functionName, int argumentCount, JSValueRef arguments[], JSValueRef* exception) { JSRetainPtr string(Adopt, JSStringCreateWithUTF8CString(functionName)); JSValueRef objectProperty = JSObjectGetProperty(context, object, string.get(), exception); JSObjectRef function = JSValueToObject(context, objectProperty, exception); ASSERT(JSObjectIsFunction(context, function)); JSValueRef returnValue = JSObjectCallAsFunction(context, function, 0, argumentCount, arguments, exception); if (exception && *exception) logException(context, *exception); return returnValue; } JSClassRef DebuggerDocument::getDroseraJSClass() { static JSClassRef droseraClass = 0; if (!droseraClass) { JSClassDefinition classDefinition = {0}; classDefinition.staticFunctions = DebuggerDocument::staticFunctions(); droseraClass = JSClassCreate(&classDefinition); } return droseraClass; } JSStaticFunction* DebuggerDocument::staticFunctions() { static JSStaticFunction staticFunctions[] = { { "breakpointEditorHTML", DebuggerDocument::breakpointEditorHTMLCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "currentFunctionStack", DebuggerDocument::currentFunctionStackCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "evaluateScript", DebuggerDocument::evaluateScriptCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "localScopeVariableNamesForCallFrame", DebuggerDocument::localScopeVariableNamesForCallFrameCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "pause", DebuggerDocument::pauseCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "resume", DebuggerDocument::resumeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "stepInto", DebuggerDocument::stepIntoCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "valueForScopeVariableNamed", DebuggerDocument::valueForScopeVariableNamedCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "log", DebuggerDocument::logCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { 0, 0, 0 } }; return staticFunctions; } void DebuggerDocument::logException(JSContextRef context, JSValueRef exception) { if (!exception) return; JSRetainPtr msg(Adopt, JSValueToStringCopy(context, exception, 0)); DebuggerDocument::platformLog(msg.get()); }