blob: f5dbf87c729e448c4f7e12d0bc8dfb05a3e7b48b [file] [log] [blame]
/*
* 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 <JavaScriptCore/JSContextRef.h>
#include <JavaScriptCore/JSRetainPtr.h>
#include <JavaScriptCore/JSStringRefCF.h>
#include <JavaScriptCore/RetainPtr.h>
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<CFURLRef> 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<CFReadStreamRef> 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<UInt8, bufferLength> 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<CFStringRef> fileContents(AdoptCF, CFStringCreateWithBytes(0, charBuffer.data(), charBuffer.size(), kCFStringEncodingUTF8, true));
JSRetainPtr<JSStringRef> 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<DebuggerDocument*>(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<DebuggerDocument*>(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<DebuggerDocument*>(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<DebuggerDocument*>(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<JSStringRef> 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<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
Vector<JSValueRef> 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<DebuggerDocument*>(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<JSValueRef> localVariableNames;
debuggerDocument->getPlatformLocalScopeVariableNamesForCallFrame(context, static_cast<int>(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<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
if (argumentCount < 2)
return JSValueMakeUndefined(context);
if (!JSValueIsString(context, arguments[0]))
return JSValueMakeUndefined(context);
JSRetainPtr<JSStringRef> 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<JSStringRef> 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<JSStringRef> 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<JSStringRef> 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<JSValueRef>& vectorValues, JSValueRef* exception)
{
JSObjectRef globalObject = JSContextGetGlobalObject(context);
JSRetainPtr<JSStringRef> 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<JSStringRef> pushString(Adopt, JSStringCreateWithUTF8CString("push"));
JSValueRef pushValue = JSObjectGetProperty(context, array, pushString.get(), exception);
ASSERT(!*exception);
JSObjectRef push = JSValueToObject(context, pushValue, exception);
ASSERT(!*exception);
for (Vector<JSValueRef>::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<JSStringRef> 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<JSStringRef> msg(Adopt, JSValueToStringCopy(context, exception, 0));
DebuggerDocument::platformLog(msg.get());
}