xtool/contrib/mORMot/SyNode/SyNodeRemoteDebugger.pas

891 lines
28 KiB
ObjectPascal

/// SyNodeRemoteDebugger - Remote debugger protocol for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeRemoteDebugger;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - http://synopse.info
SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel
pavel.mash at gmail.com
Some ideas taken from
http://code.google.com/p/delphi-javascript
http://delphi.mozdev.org/javascript_bridge/
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Initial Developer of the Original Code is
Pavel Mashlyakovsky.
Portions created by the Initial Developer are Copyright (C) 2014
the Initial Developer. All Rights Reserved.
Contributor(s):
- Arnaud Bouchez
- Vadim Orel
- Pavel Mashlyakovsky
- win2014
- George
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.18
- initial release. Use SpiderMonkey 45
}
interface
{$I Synopse.inc} // define BRANCH_WIN_WEB_SOCKET
uses
Classes, SynCrtSock, SynTable {for TJSONWriter},
SynCommons, SyNode, SpiderMonkey;
type
/// Thread for mozilla Remote Debugging Protocol
// https://wiki.mozilla.org/Remote_Debugging_Protocol
// https://wiki.mozilla.org/Remote_Debugging_Protocol_Stream_Transport
TSMRemoteDebuggerThread = class(TThread)
private
fThreadInWork: Integer;
fDebuggers: TSynObjectListLocked;
fCommunicationThreads: TSynObjectListLocked;
fCurThreadIndex: integer;
fPort: SockString;
fManager: TSMEngineManager;
FNeedPauseOnFirstStep: boolean;
protected
procedure Execute; override;
public
procedure SetTerminated;
/// Create thread and start listening on custom port
// - expects the port to be specified as Ansi string, e.g. '1234'
// - you can optionally specify a server address to bind to, e.g.
// '1.2.3.4:1234'
constructor Create(aManager: TSMEngineManager; const aPort: SockString = '6000');
destructor Destroy; override;
procedure startDebugCurrentThread(aEng: TSMEngine);
procedure stopDebugCurrentThread(aEng: TSMEngine);
/// Write log to current thread engine
procedure doLog(const Text: RawUTF8);
property NeedPauseOnFirstStep: boolean read FNeedPauseOnFirstStep write FNeedPauseOnFirstStep;
end;
function SyNodeBindingProc_debugger(const Engine: TSMEngine; const bindingNamespaceName: SynUnicode): jsval;
implementation
uses
{$ifdef MSWINDOWS}
Windows,
SynWinSock,
{$else}
Types,
BaseUnix,
Sockets,
SynFPCSock,
SynFPCLinux,
{$endif}
SysUtils;
type
TSMDebugger = class;
TSMRemoteDebuggerCommunicationThread = class(TThread)
private
fParent: TSMRemoteDebuggerThread;
fNeedClose: boolean;
fDebugger: TSMDebugger;
fCommunicationSock: TCrtSocket;
// read a packages in format package-length:JSON
function sockRead(out packet: RawUTF8): boolean;
procedure sockWrite(const packet: RawUTF8);
procedure HandleMessage(const request: Variant);
protected
procedure Execute; override;
procedure SetTerminated;
public
constructor Create(aParent: TSMRemoteDebuggerThread);
destructor Destroy; override;
procedure Send(const packet: RawUTF8);
procedure startListening(socket: TCrtSocket);
end;
TSMDebugger = class
private
fIndex: Integer;
fIsPaused: boolean;
fMessagesQueue: TRawUTF8List;
fLogQueue: TRawUTF8List;
{$IFNDEF SM52}
fOldInterruptCallback: JSInterruptCallback;
{$ENDIF}
fSmThreadID: TThreadID;
fNameForDebug: RawUTF8;
fCommunicationThread: TSMRemoteDebuggerCommunicationThread;
fIsJustInited: boolean;
fDebuggerName: RawUTF8;
fWebAppRootPath: RawUTF8;
/// Debugger create his own compartmnet (his own global object & scripting context)
// Here we initialize a new compartment
procedure InitializeDebuggerCompartment(aEng: TSMEngine; aNeedPauseOnFirstStep: boolean);
protected
// writer for serialize outgiong JSON's
fJsonWriter: TJSONWriter;
public
constructor Create(aParent: TSMRemoteDebuggerThread; aEng: TSMEngine);
destructor Destroy; override;
procedure Send(const packet: RawUTF8);
procedure attach(aThread: TSMRemoteDebuggerCommunicationThread);
end;
{ TSMRemoteDebuggerThread }
constructor TSMRemoteDebuggerThread.Create(aManager: TSMEngineManager; const aPort: SockString);
begin
fDebuggers := TSynObjectListLocked.Create(true);
fCommunicationThreads := TSynObjectListLocked.Create(false);
FNeedPauseOnFirstStep := false;
fCurThreadIndex := 0;
fThreadInWork := 0;
fPort := aPort;
fManager := aManager;
FreeOnTerminate := true;
inherited Create(False);
end;
destructor TSMRemoteDebuggerThread.Destroy;
var
i: Integer;
begin
fCommunicationThreads.Safe.Lock;
try
i := fCommunicationThreads.Count;
while i > 0 do begin
Dec(i);
TSMRemoteDebuggerCommunicationThread(fCommunicationThreads.List[i]).Terminate;
fCommunicationThreads.Delete(i);
end;
finally
fCommunicationThreads.Safe.UnLock;
end;
fCommunicationThreads.Free;
fCommunicationThreads := nil;
fDebuggers.Safe.Lock;
try
while fDebuggers.Count>0 do
fDebuggers.Delete(fDebuggers.Count-1);
finally
fDebuggers.Safe.UnLock;
end;
fDebuggers.Free;
fDebuggers := nil;
inherited;
end;
procedure TSMRemoteDebuggerThread.doLog(const Text: RawUTF8);
var
Debugger: TSMDebugger;
eng: TSMEngine;
curThreadID: TThreadID;
begin
curThreadID := GetCurrentThreadId;
eng := fManager.EngineForThread(curThreadID);
if eng<>nil then begin
Debugger := eng.PrivateDataForDebugger;
Debugger.fLogQueue.Add(Text);
if eng.cx.IsRunning then
{$IFDEF SM52}
eng.cx.RequestInterruptCallback
{$ELSE}
eng.rt.RequestInterruptCallback
{$ENDIF}
else
{$IFDEF SM52}
begin
eng.cx.RequestInterruptCallback;
eng.cx.CheckForInterrupt;
end;
{$ELSE}
eng.rt.InterruptCallback(eng.cx);
{$ENDIF}
end;
end;
procedure TSMRemoteDebuggerThread.Execute;
var
ServerSock: TCrtSocket;
AcceptedSocket: TCrtSocket;
thread: TSMRemoteDebuggerCommunicationThread;
threadsCnt: integer;
begin
AcceptedSocket := nil;
ServerSock := TCrtSocket.Bind(fPort);
try
repeat
AcceptedSocket := ServerSock.AcceptIncoming();
if (AcceptedSocket <> nil) then begin
if Terminated then begin
fCommunicationThreads.Safe.Lock;
try
while fCommunicationThreads.count > 0 do begin
threadsCnt := fCommunicationThreads.Count;
thread := TSMRemoteDebuggerCommunicationThread(fCommunicationThreads.List[threadsCnt - 1]);
fCommunicationThreads.Delete(threadsCnt - 1);
thread.SetTerminated;
end;
finally
fCommunicationThreads.Safe.UnLock;
end;
while fThreadInWork>0 do
SleepHiRes(10);
exit;
end;
fCommunicationThreads.Safe.Lock;
try
threadsCnt := fCommunicationThreads.Count;
if threadsCnt = 0 then begin //no free threads;
AcceptedSocket.Close;
end else begin
thread := TSMRemoteDebuggerCommunicationThread(fCommunicationThreads[threadsCnt - 1]);
fCommunicationThreads.Delete(threadsCnt - 1);
thread.startListening(AcceptedSocket);
end;
finally
fCommunicationThreads.Safe.UnLock;
end;
end;
until Terminated;
finally
AcceptedSocket.Free;
ServerSock.Free;
end;
end;
procedure TSMRemoteDebuggerThread.startDebugCurrentThread(aEng: TSMEngine);
var
i: integer;
curThreadID: TThreadID;
begin
curThreadID := GetCurrentThreadId;
if not Terminated and (fDebuggers <> nil) then begin
fDebuggers.Safe.Lock;
try
if aEng <> nil then begin
for I := 0 to fDebuggers.Count - 1 do
if TSMDebugger(fDebuggers.List[i]).fNameForDebug = aEng.nameForDebug then begin
// todo
TSMDebugger(fDebuggers.List[i]).fSmThreadID := curThreadID;
TSMDebugger(fDebuggers.List[i]).InitializeDebuggerCompartment(aEng, FNeedPauseOnFirstStep);
exit;
end;
fDebuggers.Add(TSMDebugger.Create(self, aEng));
end else
raise ESMException.Create('Can''t start debugger for non-existed engine');
finally
fDebuggers.Safe.UnLock;
end;
end;
end;
procedure TSMRemoteDebuggerThread.stopDebugCurrentThread(aEng: TSMEngine);
var
i: Integer;
cx: PJSContext;
cmpDbg: PJSCompartment;
curThreadID: TThreadID;
dbgObject: PJSRootedObject;
begin
curThreadID := GetCurrentThreadId;
if not Terminated and (fDebuggers <> nil) then begin
fDebuggers.Safe.Lock;
try
for I := 0 to fDebuggers.Count - 1 do
if TSMDebugger(fDebuggers.List[i]).fSmThreadID = curThreadID then begin
if aEng<>nil then begin
cx := aEng.cx;
cmpDbg := cx.EnterCompartment(aEng.GlobalObjectDbg.ptr);
try
dbgObject := cx.NewRootedObject(aEng.GlobalObjectDbg.ptr.GetPropValue(cx, 'process').asObject.GetPropValue(cx, 'dbg').asObject);
try
if dbgObject.ptr.HasProperty(cx, 'uninit') then
aEng.CallObjectFunction(dbgObject, 'uninit', []);
finally
cx.FreeRootedObject(dbgObject);
end;
finally
cx.LeaveCompartment(cmpDbg);
end;
aEng.CancelExecution;
end else
raise ESMException.Create('internal error: no engine');
TSMDebugger(fDebuggers.List[i]).fSmThreadID := 0;
exit;
end;
finally
fDebuggers.Safe.UnLock;
end;
end;
end;
procedure TSMRemoteDebuggerThread.SetTerminated;
var
socket: TCrtSocket;
begin
if not Terminated then begin
Terminate;
socket := Open('127.0.0.1', fPort);
if socket<>nil then
socket.Free;
while fThreadInWork>0 do
SleepHiRes(10);
end;
end;
{ TSMRemoteDebuggerCommunicationThread }
constructor TSMRemoteDebuggerCommunicationThread.Create(aParent: TSMRemoteDebuggerThread);
begin
inherited Create(true);
fParent := aParent;
InterlockedIncrement(fParent.fThreadInWork);
FreeOnTerminate := true;
end;
destructor TSMRemoteDebuggerCommunicationThread.Destroy;
begin
InterlockedDecrement(fParent.fThreadInWork);
inherited;
end;
procedure TSMRemoteDebuggerCommunicationThread.Execute;
const
timeForSelectThreadInSeconds = 300;
var
packet: RawUTF8;
request: Variant;
tickCountsForSelectEngine: Int64;
begin
inherited;
repeat
Send('{"from":"root","applicationType":"synode","traits" : {"debuggerSourceActors":true, "conditionalBreakpoints": true}}');
tickCountsForSelectEngine := GetTickCount64 + timeForSelectThreadInSeconds * 1000;
fNeedClose := false;
repeat
if sockRead(packet) then
begin
request := _JsonFast(packet);
SynSMLog.Add.Log(sllCustom4, packet);
HandleMessage(request);
end;
if (fDebugger = nil) and (GetTickCount64 > tickCountsForSelectEngine) then begin
fNeedClose := true
end;
if fParent.Terminated then
SetTerminated;
until fNeedClose or (fCommunicationSock.Sock = -1) or Terminated;
fCommunicationSock.Free;
fCommunicationSock := nil;
if not Terminated then begin
fParent.fCommunicationThreads.Safe.Lock;
try
if fDebugger <> nil then begin
fDebugger.fCommunicationThread := nil;
fDebugger := nil;
end;
fParent.fCommunicationThreads.Add(Self);
finally
fParent.fCommunicationThreads.Safe.UnLock;
end;
Suspended := true;
end;
until Terminated;
end;
procedure TSMRemoteDebuggerCommunicationThread.HandleMessage(const request: Variant);
var
data: RawUTF8;
i: integer;
debuggerIndex: integer;
debugger: TSMDebugger;
Writer: TTextWriter;
engine: TSMEngine;
begin
if {$IFDEF FPC}request.&to{$ELSE}request.to{$ENDIF} = 'root' then begin
Writer := TTextWriter.CreateOwnedStream;
try
if {$IFDEF FPC}request.&type{$ELSE}request.type{$ENDIF} = 'listAddons' then begin
Writer.AddShort('{"from":"root","addons":[');
fParent.fDebuggers.Safe.Lock;
try
for I := 0 to fParent.fDebuggers.Count - 1 do begin
debugger := TSMDebugger(fParent.fDebuggers.List[i]);
engine := fParent.fManager.EngineForThread(debugger.fSmThreadID);
if engine <> nil then begin
// Actor represent debug thread here, setting proper name with coxtext thread id
// Writer.AddShort('{"actor":"server1.conn1.addon');
// Writer.Add(TSMDebugger(fParent.fDebuggers[i]).fIndex);
Writer.AddShort('{"actor":"');
Writer.AddShort(debugger.fDebuggerName);
Writer.AddShort('.conn1.thread_');
{ TODO : check that in multithread mode this field equal thread id with js context that we debug, otherwire replace with proper assigment }
Writer.Add(debugger.fSmThreadID);
// id should be addon id, value from DoOnGetEngineName event
// Writer.AddShort('","id":"server1.conn1.addon');
// Writer.Add(TSMDebugger(fParent.fDebuggers[i]).fIndex);
Writer.AddShort('","id":"');
Writer.AddString(debugger.fNameForDebug);
Writer.AddShort('","name":"');
Writer.AddString(debugger.fNameForDebug);
// url most likly should be addon folder in format: file:///drive:/path/
// Writer.AddShort('","url":"server1.conn1.addon');
// Writer.Add(TSMDebugger(fParent.fDebuggers[i]).fIndex);
{ TODO : replace with path generation, should be context home dir in format file:///drive:/path/ }
Writer.AddShort('","url":"file:///' + StringReplaceAll(debugger.fWebAppRootPath, '\', '/'));
Writer.AddShort('","debuggable":');
Writer.Add(debugger.fCommunicationThread = nil);
Writer.AddShort(',"consoleActor":"console');
Writer.Add(debugger.fIndex);
Writer.AddShort('"},');
end;
end;
finally
fParent.fDebuggers.Safe.UnLock;
end;
Writer.CancelLastComma;
Writer.AddShort(']}');
end else if {$IFDEF FPC}request.&type{$ELSE}request.type{$ENDIF} = 'listTabs' then begin
// VSCode FireFox Debug extension https://github.com/hbenl/vscode-firefox-debug
// require at last one tab
Writer.AddShort('{"from":"root","tabs":[{}],"selected":0}');
end else
exit;
Send(Writer.Text);
finally
Writer.Free;
end;
end else begin
if fDebugger = nil then begin
data := VariantToUTF8({$IFDEF FPC}request.&to{$ELSE}request.to{$ENDIF});
debuggerIndex := GetInteger(@data[8]);
fParent.fDebuggers.Safe.Lock;
try
for I := 0 to fParent.fDebuggers.Count-1 do
if TSMDebugger(fParent.fDebuggers.List[i]).fIndex = debuggerIndex then begin
fDebugger := TSMDebugger(fParent.fDebuggers.List[i]);
break;
end;
if (fDebugger = nil) or (fDebugger.fCommunicationThread <> nil) then begin
fDebugger := nil;
fNeedClose := true;
exit;
end;
finally
fParent.fDebuggers.Safe.UnLock;
end;
fDebugger.attach(Self);
end;
engine := fParent.fManager.EngineForThread(fDebugger.fSmThreadID);
if (engine <> nil) then begin
fDebugger.fMessagesQueue.Add(VariantToUTF8(request));
if not fDebugger.fIsPaused then begin
if (not engine.cx.IsRunning) then begin
if not Assigned(engine.doInteruptInOwnThread) then
raise ESMException.Create('not Assigned(engine.doInteruptInOwnThread)');
engine.doInteruptInOwnThread;
end;
{$IFDEF SM52}
engine.cx.RequestInterruptCallback;
{$ELSE}
engine.rt.RequestInterruptCallback;
{$ENDIF}
end;
end;
end;
end;
procedure TSMRemoteDebuggerCommunicationThread.Send(const packet: RawUTF8);
begin
sockWrite(packet);
SynSMLog.Add.Log(sllCustom4, packet);
end;
function TSMRemoteDebuggerCommunicationThread.sockRead(out packet: RawUTF8): boolean;
const
bufSize = 8;
var
buf: array [0..bufSize] of Byte;
ch: PUTF8Char;
len, head, bytesToRead: integer;
begin
bytesToRead := bufSize;
FillChar(buf, Length(buf), #0);
Result := (fCommunicationSock <> nil) and fCommunicationSock.TrySockRecv(@buf[1], bytesToRead);
if not Result then
exit;
ch := @buf[1];
len := GetNextItemCardinal(ch, ':');
SetLength(packet, len);
head := bufSize - (ch - @buf[1]);
Move(ch^, packet[1], head);
bytesToRead := len - head;
Result := fCommunicationSock.TrySockRecv(@packet[head + 1], bytesToRead);
end;
procedure TSMRemoteDebuggerCommunicationThread.sockWrite(const packet: RawUTF8);
var
tmp: shortstring;
const
sep: shortstring = ':';
begin
if fCommunicationSock = nil then
exit;
Str(Length(packet), tmp);
fCommunicationSock.SockSend(@tmp[1], length(tmp));
fCommunicationSock.SockSend(@sep[1], length(sep));
fCommunicationSock.SockSend(@packet[1], length(packet));
fCommunicationSock.SockSendFlush('');
end;
procedure TSMRemoteDebuggerCommunicationThread.startListening(socket: TCrtSocket);
begin
fCommunicationSock := socket;
SynSMLog.Add.Log(sllCustom4, 'Accepted');
Suspended := false;
end;
procedure TSMRemoteDebuggerCommunicationThread.SetTerminated;
begin
Terminate;
Suspended := false;
end;
{ TSMDebugger }
procedure TSMDebugger.attach(aThread: TSMRemoteDebuggerCommunicationThread);
begin
fCommunicationThread := aThread;
fMessagesQueue.Clear;
fLogQueue.Clear;
end;
constructor TSMDebugger.Create(aParent: TSMRemoteDebuggerThread; aEng: TSMEngine);
begin
fIsPaused := false;
aParent.fCommunicationThreads.Safe.Lock;
try
aParent.fCommunicationThreads.Add(TSMRemoteDebuggerCommunicationThread.Create(aParent));
finally
aParent.fCommunicationThreads.Safe.UnLock;
end;
fIndex := aParent.fCurThreadIndex;
inc(aParent.fCurThreadIndex);
fSmThreadID := GetCurrentThreadId;
fMessagesQueue := TRawUTF8List.Create();
fLogQueue := TRawUTF8List.Create();
fNameForDebug := aEng.nameForDebug;
fDebuggerName := 'synode_debPort_' + aParent.fPort;
fWebAppRootPath := aEng.webAppRootDir;
fJsonWriter := TJSONWriter.CreateOwnedStream(1024*50);
InitializeDebuggerCompartment(aEng, aParent.FNeedPauseOnFirstStep);
end;
destructor TSMDebugger.Destroy;
begin
if fCommunicationThread <> nil then
fCommunicationThread.SetTerminated;
fMessagesQueue.Free;
fMessagesQueue := nil;
fLogQueue.Free;
fLogQueue := nil;
fJsonWriter.Free;
inherited;
end;
function doInterupt(cx: PJSContext): Boolean; cdecl;
var
cmpDbg: PJSCompartment;
debugger: TSMDebugger;
engine: TSMEngine;
dbgObject: PJSRootedObject;
begin
engine := TSMEngine(cx.PrivateData);
debugger := engine.PrivateDataForDebugger;
try
if (debugger.fMessagesQueue <> nil) and not debugger.fIsPaused and (debugger.fCommunicationThread <> nil) then begin
cmpDbg := cx.EnterCompartment(engine.GlobalObjectDbg.ptr);
try
dbgObject := cx.NewRootedObject(engine.GlobalObjectDbg.ptr.GetPropValue(cx, 'process').asObject.GetPropValue(cx, 'dbg').asObject);
try
engine.CallObjectFunction(dbgObject, 'doInterupt', []);
finally
cx.FreeRootedObject(dbgObject);
end;
finally
cx.LeaveCompartment(cmpDbg);
end;
end;
finally
{$IFDEF SM52}
result := True;
{$ELSE}
result := debugger.fOldInterruptCallback(cx);
{$ENDIF}
end;
end;
procedure TSMDebugger.InitializeDebuggerCompartment(aEng: TSMEngine; aNeedPauseOnFirstStep: boolean);
var
cx: PJSContext;
cmpDbg: PJSCompartment;
rval: jsval;
dbgObject: PJSRootedObject;
res: Boolean;
begin
fMessagesQueue.Clear;
fLogQueue.Clear;
cx := aEng.cx;
cmpDbg := cx.EnterCompartment(aEng.GlobalObjectDbg.ptr);
try
if not aEng.GlobalObjectDbg.ptr.GetProperty(cx, 'Debugger', rval) or rval.isVoid then begin
aEng.PrivateDataForDebugger := self;
res := cx.InitStandardClasses(aEng.GlobalObjectDbg.ptr); Assert(res);
res := cx.DefineDebuggerObject(aEng.GlobalObjectDbg.ptr); Assert(res);
res := cx.InitModuleClasses(aEng.GlobalObjectDbg.ptr); Assert(res);
aEng.DefineProcessBinding;
aEng.DefineModuleLoader;
aEng.EvaluateModule('DevTools/Debugger.js');
dbgObject := cx.NewRootedObject(aEng.GlobalObjectDbg.ptr.GetPropValue(cx, 'process').asObject.GetPropValue(cx, 'dbg').asObject);
try
aEng.CallObjectFunction(dbgObject, 'init', [
SimpleVariantToJSval(cx, fIndex),
SimpleVariantToJSval(cx, aNeedPauseOnFirstStep)
]);
finally
cx.FreeRootedObject(dbgObject);
end;
if Assigned(aEng.Manager.OnDebuggerInit) then
aEng.Manager.OnDebuggerInit(aEng);
{$IFDEF SM52}
aEng.cx.AddInterruptCallback(doInterupt);
{$ELSE}
foldInterruptCallback := aEng.rt.InterruptCallback;
aEng.rt.InterruptCallback := doInterupt;
{$ENDIF}
end;
finally
cx.LeaveCompartment(cmpDbg);
end;
fIsJustInited := true;
end;
procedure TSMDebugger.Send(const packet: RawUTF8);
begin
if fCommunicationThread <> nil then
fCommunicationThread.Send(packet);
end;
function debugger_send(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
val: jsval;
msg: RawUTF8;
debugger: TSMDebugger;
begin
result := true;
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
if debugger.fCommunicationThread = nil then
exit;
val := vp.argv[0];
if val.isString then
msg := val.asJSString.ToUTF8(cx)
else begin
debugger.fJsonWriter.CancelAll;
val.AddJSON(cx,debugger.fJsonWriter);
debugger.fJsonWriter.SetText(msg);
end;
debugger.Send(msg);
end;
function debugger_err(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
val: jsval;
msg: RawUTF8;
begin
val := vp.argv[0];
if val.isString then
msg := val.asJSString.ToUTF8(cx)
else
msg := val.asJson[cx];
SynSMLog.Add.Log(sllError, msg);
result := true;
end;
function debugger_read(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
msg: RawUTF8;
Queue: TRawUTF8List;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
if (argc = 0) or vp.argv[0].asBoolean then
Queue := debugger.fMessagesQueue
else
Queue := debugger.fLogQueue;
msg := '';
while ((Queue <> nil) and (debugger.fCommunicationThread <> nil) and
(not Queue.PopFirst(msg))) and (argc = 0) do
SleepHiRes(10);
result := true;
if (Queue <> nil) and (debugger.fCommunicationThread <> nil) then
vp.rval := SimpleVariantToJSval(cx, msg)
else // debugger.js will detach current debugee if msg === null
vp.rval := JSVAL_NULL;
end;
function debugger_listen(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
vp.rval := SimpleVariantToJSval(cx, Assigned(debugger.fCommunicationThread));
result := true;
end;
function debugger_setPaused(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
debugger.fIsPaused := vp.argv[0].asBoolean;
if debugger.fIsJustInited and not debugger.fIsPaused then begin
debugger.fIsJustInited := false;
if (debugger.fCommunicationThread <> nil) and Assigned(debugger.fCommunicationThread.fParent.fManager.OnDebuggerConnected) then
debugger.fCommunicationThread.fParent.fManager.OnDebuggerConnected(TSMEngine(cx.PrivateData));
end;
result := true;
end;
function debugger_isPaused(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
vp.rval := SimpleVariantToJSval(cx, debugger.fIsPaused);
result := true;
end;
function debugger_debuggerName(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
vp.rval := SimpleVariantToJSval(cx, debugger.fDebuggerName);
result := true;
end;
function debugger_nameForDebug(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
vp.rval := SimpleVariantToJSval(cx, debugger.fNameForDebug);
result := true;
end;
function debugger_threadId(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
{ TODO : check that in multithread mode this field equal thread id with js context that we debug, otherwire replace with proper assigment }
vp.rval := SimpleVariantToJSval(cx, ToUTF8(debugger.fSmThreadID));
// TSMDebugger(fParent.fDebuggers[i]).fSmThreadID
result := true;
end;
function debugger_webAppRootPath(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
debugger: TSMDebugger;
begin
debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger;
vp.rval := SimpleVariantToJSval(cx, debugger.fWebAppRootPath);
result := true;
end;
function SyNodeBindingProc_debugger(const Engine: TSMEngine;
const bindingNamespaceName: SynUnicode): jsval;
var
obj: PJSRootedObject;
cx: PJSContext;
res: Boolean;
begin
cx := Engine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
try
res := cx.WrapObject(Engine.GlobalObject.ptr); Assert(res);
obj.ptr.DefineFunction(cx, 'send', debugger_send, 1);
obj.ptr.DefineFunction(cx, 'logError', debugger_err, 1);
obj.ptr.DefineFunction(cx, 'read', debugger_read, 0);
obj.ptr.DefineProperty(cx, 'listen', JSVAL_NULL, 0, debugger_listen);
obj.ptr.DefineProperty(cx, 'paused', JSVAL_NULL, 0, debugger_isPaused, debugger_setPaused);
obj.ptr.DefineProperty(cx, 'debuggerName', JSVAL_NULL, 0, debugger_debuggerName);
obj.ptr.DefineProperty(cx, 'addonID', JSVAL_NULL, 0, debugger_nameForDebug);
obj.ptr.DefineProperty(cx, 'threadId', JSVAL_NULL, 0, debugger_threadId);
obj.ptr.DefineProperty(cx, 'webAppRootPath', JSVAL_NULL, 0, debugger_webAppRootPath);
obj.ptr.DefineProperty(cx, 'global', Engine.GlobalObject.ptr.ToJSValue);
Result := obj.ptr.ToJSValue;
finally
cx.FreeRootedObject(obj);
end;
end;
initialization
TSMEngineManager.RegisterBinding('debugger', SyNodeBindingProc_debugger);
end.