source upload

This commit is contained in:
Razor12911
2022-01-17 22:16:47 +02:00
parent 12936d065b
commit 098e8c48de
1778 changed files with 1206749 additions and 0 deletions

2
contrib/mORMot/SyNode/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.resources/
tools/core_res

View File

@@ -0,0 +1,110 @@
/// Netscape Portable Runtime *.h header port to Delphi
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit NSPRAPI;
interface
{ NSPR library APIs }
const
NSPRLib = 'nspr4'{$IFDEF MSWINDOWS} + '.dll'{$ENDIF};
const
/// numbers of micro secs per second
PRMJ_USEC_PER_SEC = 1000000;
/// stipulate that the process should wait no time as defined by NSPR
// - i.e. will return immediately
// - defined in the PRIntervalTime namespace
PR_INTERVAL_NO_WAIT =$0;
/// stipulate that the process should wait forever as defined by NSPR
// - i.e. will never time out
// - defined in the PRIntervalTime namespace
PR_INTERVAL_NO_TIMEOUT =$ffffffff;
type
/// unsigned 32 bit integer type as defined by NSPR
PRUint32 = Cardinal;
/// interval time type as defined by NSPR
PRIntervalTime = PRUint32;
/// a mutex/lock resource as defined by NSPR
PRLock = Pointer;
/// a event resource as defined by NSPR
PRCondVar = Pointer;
/// a thread resource as defined by NSPR
PRThread = Pointer;
/// status codes as defined by NSPR
PRStatus = (PR_FAILURE = -1, PR_SUCCESS = 0);
/// thread type as defined by NSPR
PRThreadType = (PR_USER_THREAD, PR_SYSTEM_THREAD);
/// thread priority as defined by NSPR
// - PR_PRIORITY_LOW is the lowest possible priority
// - PR_PRIORITY_NORMAL is the most common expected priority
// - PR_PRIORITY_HIGH is the slightly more aggressive scheduling
// - PR_PRIORITY_URGENT is there because it does little good to have one
// more priority value
PRThreadPriority = (
PR_PRIORITY_FIRST = 0,
PR_PRIORITY_LOW = 0,
PR_PRIORITY_NORMAL = 1,
PR_PRIORITY_HIGH = 2,
PR_PRIORITY_URGENT = 3,
PR_PRIORITY_LAST = 3);
/// thread scope as defined by NSPR
PRThreadScope = (PR_LOCAL_THREAD, PR_GLOBAL_THREAD, PR_GLOBAL_BOUND_THREAD);
/// thread state as defined by NSPR
PRThreadState = (PR_JOINABLE_THREAD, PR_UNJOINABLE_THREAD);
/// allocates a new NSPR mutex/lock
function PR_NewLock: PRLock; cdecl; external NSPRLib;
/// allocates a new NSPR event
function PR_NewCondVar(lock: PRLock): PRCondVar; cdecl; external NSPRLib;
/// free a previously allocated NSPR event
procedure PR_DestroyCondVar(cvar: PRCondVar); cdecl; external NSPRLib;
/// notify a previously allocated NSPR event
function PR_NotifyCondVar(cvar: PRCondVar): PRStatus; cdecl; external NSPRLib;
/// notify all previously allocated NSPR event
function PR_NotifyAllCondVar(cvar: PRCondVar): PRStatus; cdecl; external NSPRLib;
/// wait until a previously allocated NSPR event is notified
function PR_WaitCondVar(cvar: PRCondVar; timeout: PRIntervalTime): PRStatus;
cdecl; external NSPRLib;
/// enter a previously allocated NSPR mutex/lock
procedure PR_Lock(lock: PRLock); cdecl; external NSPRLib;
/// leave a previously allocated NSPR mutex/lock
function PR_Unlock(lock: PRLock): PRStatus; cdecl; external NSPRLib;
/// free a previously allocated NSPR lock
procedure PR_DestroyLock(lock: PRLock); cdecl; external NSPRLib;
/// join a NSPR thread
function PR_JoinThread(thred: PRThread): PRStatus; cdecl; external NSPRLib;
/// initializes a NSPR thread
function PR_CreateThread(
type_: PRThreadType; start: pointer; arg: pointer;
priority: PRThreadPriority; scope: PRThreadScope;
state: PRThreadState; stackSize: PRUint32): PRThread; cdecl; external NSPRLib;
/// change the current NSPR thread name
function PR_SetCurrentThreadName(name: PAnsiChar): PRStatus;
cdecl; external NSPRLib;
/// returns the number of ticks per seconds as expected by NSPR
function PR_TicksPerSecond(): PRUint32; cdecl; external NSPRLib;
implementation
end.

View File

@@ -0,0 +1,54 @@
# The server-side JavaScript execution using the SpiderMonkey library with nodeJS modules support
- win32/64 target (FPC 3.1.1, Delphi), linux 64 (FPC 3.1.1)
- based on SpiderMonkey52, almost full support of ES6
- a remote debugger protocol, can be debugged remotely using Firefox - see `SyNode\Samples\02 - Bindings`
- CommonJS modules, compatible with NPM modules
- native modules can be implemented using Delphi/FPC (as a dll/so) - see `SyNode\Samples\01 - Dll Modules`
- JavaScript prototype definition based on Delphi RTTI (supported both "new" and old)
## SpiderMonkey library
### SpiderMonkey 52 (recommended)
Precompiled binary can be downloaded here:
- Win x32: https://unitybase.info/media/files/synmozjs52x32dlls.zip
- Win x64: https://unitybase.info/media/files/synmozjs52x64dlls.zip
- Linux x64: https://unitybase.info/media/files/libsynmozjs52.zip
Or compiled from sources as described [in instructions inside mozjs folder](/mozjs)
### SpiderMonkey 45 (not supported)
Precompiled binary can be downloaded here:
- x32: https://unitybase.info/downloads/mozjs-45.zip
- x64: https://unitybase.info/downloads/mozjs-45-x64.zip
### Embadding SyNode JavaScript files into executable
Files from `core_modules` folder can be embadded into executable by enabling `CORE_MODULES_IN_RES`
define in the `SyNode.inc` file (enabled by default).
Compiled `core_modules.res` resources is commited into the git repositiry.
To create your own resource pack use a `/tool/core_res` tool to prepare files for embadding.
To compile rc -> res we use mingq tools under linux
```
sudo apt install mingw-w64 mingw-w64-tools
```
```bash
cd SyNode
./tools/core_res -i ./core_modules/ -o ./.resources/
cd ./resources
x86_64-w64-mingw32-windres ./core_res.rc ../core_modules.res
```
From inside JS files embadded into resources can be evaluated using `process.binding('modules').runInThisContextRes` function
Form Pascal code using `TSMEngine.EvaluateRes()` function

View File

@@ -0,0 +1,14 @@
{
"name": "math-module",
"version": "1.0.0",
"description": "Native (dll) module sample for SyNode",
"main": "./build/mathModule.dll",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"SyNode"
],
"author": "pavel.mash <pavel.mash@gmail.com> (https://unitybase.info)",
"license": "MIT"
}

View File

@@ -0,0 +1,54 @@
library MathModule;
uses
FastMM4,
SysUtils,
Classes,
Windows,
SpiderMonkey,
SyNodePluginIntf,
uMathModule in 'uMathModule.pas';
const
MAX_THREADS = 256;
// just copy Empty plugin,
// create unit with your plugin realization(descendant of the TCustomSMPlugin)
// and override methods Init and UnInit is needed
PluginType: TCustomSMPluginType = TSMMathPlugin; //In real realization you must declare you own class child of TCustomSMPlugin and override methods Init and UnInit
var
ThreadRecs: array[0..MAX_THREADS] of TThreadRec;
threadCounter: integer;
function InitPlugin(cx: PJSContext; exports_: PJSRootedObject; require: PJSRootedObject; module: PJSRootedObject; __filename: PWideChar; __dirname: PWideChar): boolean; cdecl;
var
l: integer;
begin
l := InterlockedIncrement(threadCounter);
if l>=MAX_THREADS then
raise Exception.Create('Too many thread. Max is 256');
ThreadRecs[l].threadID := GetCurrentThreadId;
ThreadRecs[l].plugin := PluginType.Create(cx, exports_, require, module, __filename, __dirname);
result := true;
end;
function UnInitPlugin(): boolean; cdecl;
var
i: integer;
begin
for I := 0 to MAX_THREADS - 1 do
if ThreadRecs[i].threadID = GetCurrentThreadId then begin
ThreadRecs[i].threadID := 0;
FreeAndNil(ThreadRecs[i].plugin);
end;
result := true;
end;
exports InitPlugin;
exports UnInitPlugin;
begin
IsMultiThread := True; //!!IMPORTANT for FastMM
threadCounter := -1;
FillMemory(@ThreadRecs[0], SizeOf(ThreadRecs), 0);
end.

View File

@@ -0,0 +1,125 @@
unit uMathModule;
interface
uses
SyNodePluginIntf;
type
TSMMathPlugin = class(TCustomSMPlugin)
procedure UnInit; override;
procedure Init(const rec: TSMPluginRec); override;
end;
implementation
uses
SpiderMonkey, SysUtils;
{ TSMMathPlugin }
type
TMathFun = function (const X, Y : Extended) : Extended;
function DoMathFun(cx: PJSContext; argc: uintN; var vp: JSArgRec; fun: TMathFun): Boolean; cdecl;
var
in_argv: PjsvalVector;
aVal1, aVal2, aRes: Double;
val: jsval;
begin
try
in_argv := vp.argv;
if (argc<>2) then
raise ESMException.Create('invalid args count for math function. Required (Number, Number)')
else if not in_argv[0].isNumber or not in_argv[1].isNumber then
raise ESMException.CreateUTF8('invalid args for math function. Required (Number, Number) but got (%, %)', [integer(in_argv[0].ValType(cx)), integer(in_argv[1].ValType(cx))]);
if in_argv[0].isInteger then
aVal1 := in_argv[0].asInteger
else
aVal1 := in_argv[0].asDouble;
if in_argv[1].isInteger then
aVal2 := in_argv[1].asInteger
else
aVal2 := in_argv[1].asDouble;
aRes := fun(aVal1, aVal2);
val.asDouble := aRes;
vp.rval := val;
Result := True;
except
on E: Exception do begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function DoAdd(const X, Y : Extended) : Extended;
begin
result := X+Y;
end;
function DoSub(const X, Y : Extended) : Extended;
begin
result := X-Y;
end;
function DoMul(const X, Y : Extended) : Extended;
begin
result := X*Y;
end;
function DoDiv(const X, Y : Extended) : Extended;
begin
if Y = 0 then
raise ESMException.Create('division by zero');
result := X/Y;
end;
function math_add(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
begin
result := DoMathFun(cx, argc, vp, DoAdd);
end;
function math_sub(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
begin
result := DoMathFun(cx, argc, vp, DoSub);
end;
function math_mul(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
begin
result := DoMathFun(cx, argc, vp, DoMul);
end;
function math_div(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
begin
result := DoMathFun(cx, argc, vp, DoDiv);
end;
procedure TSMMathPlugin.Init(const rec: TSMPluginRec);
var val: jsval;
begin
inherited;
// this method is called when SM Engine required this plugin
// here you can define exports (rec.Exp) properties
// or redefime module (rec.Module) property 'exports'
rec.Exp.ptr.DefineFunction(rec.cx, 'add', math_add, 2, StaticROAttrs);
rec.Exp.ptr.DefineFunction(rec.cx, 'sub', math_sub, 2, StaticROAttrs);
rec.Exp.ptr.DefineFunction(rec.cx, 'mul', math_mul, 2, StaticROAttrs);
rec.Exp.ptr.DefineFunction(rec.cx, 'div', math_div, 2, StaticROAttrs);
val.asDouble := pi;
rec.Exp.ptr.DefineProperty(rec.cx, 'pi', val, StaticROAttrs, nil, nil);
end;
procedure TSMMathPlugin.UnInit;
begin
inherited;
// this method is called when SM Engine destroyed
// here you can finaliza your plugin if it is needed
end;
end.

View File

@@ -0,0 +1 @@
lib/

View File

@@ -0,0 +1,69 @@
import {setConsoleCommands, setConsoleMessageResolver} from 'DevTools/Debugger.js';
let dbg_binding = process.binding('debugger'),
global = dbg_binding.global;
setConsoleCommands({
help: () => 'Welcome to SyNode console.',
'$cwd': {
command: () => {return global.process.cwd(); },
description: 'Show current working dir'
},
'$mormot': {
command: () => 'http://synopse.info',
description:
`Show where the mORMot live.
Remember - RTFM before write to forum :)`
}
});
setConsoleMessageResolver((msg) => {
let timestamp = (new Date(msg.substr(0,4)+'-'+msg.substr(4,2)+'-'+msg.substr(6,2)+'T'+msg.substr(9,2)+':'+msg.substr(11,2)+':'+msg.substr(13,2)+'.'+msg.substr(15,2)+'0Z')).getTime(),
logLevel = msg.substr(20,7),
level,
category;
if (timestamp) {
msg = msg.substr(20).replace(/\t/gi, ' ');
const logLevelMap = {
' ': {category: 'webdev', level: 'log'},
' info ': {category: 'webdev', level: 'log'},
' debug ': {category: 'server', level: 'info'},
' trace ': {category: 'webdev', level: 'log'},
' warn ': {category: 'webdev', level: 'warn'},
' ERROR ': {category: 'webdev', level: 'error'},
' + ': {category: 'webdev', level: 'log'},
' - ': {category: 'webdev', level: 'log'},
' OSERR ': {category: 'webdev', level: 'error'},
' EXC ': {category: 'webdev', level: 'error'},
' EXCOS ': {category: 'webdev', level: 'error'},
' mem ': {category: 'webdev', level: 'log'},
' stack ': {category: 'webdev', level: 'log'},
' fail ': {category: 'webdev', level: 'error'},
' SQL ': {category: 'webdev', level: 'info'},
' cache ': {category: 'webdev', level: 'log'},
' res ': {category: 'webdev', level: 'log'},
' DB ': {category: 'webdev', level: 'log'},
' http ': {category: 'network', level: 'log'},
' clnt ': {category: 'network', level: 'warn'},
' srvr ': {category: 'network', level: 'warn'},
' call ': {category: 'webdev', level: 'log'},
' ret ': {category: 'webdev', level: 'log'},
' auth ': {category: 'server', level: 'log'},
' cust1 ': {category: 'js', level: 'warn'},
' cust2 ': {category: 'server', level: 'warn'},
' cust3 ': {category: 'webdev', level: 'log'},
' cust4 ': {category: 'webdev', level: 'log'},
' rotat ': {category: 'webdev', level: 'log'},
' dddER ': {category: 'webdev', level: 'log'},
' dddIN ': {category: 'webdev', level: 'log'},
' mon ': {category: 'webdev', level: 'log'}
};
level = logLevelMap[logLevel].level;
category = logLevelMap[logLevel].category;
} else {
timestamp = Date.now();
category = 'webdev';
level = 'log';
}
return {level: level, category: category, msg: msg, timeStamp: timestamp}
});

View File

@@ -0,0 +1,34 @@
program SpiderMonkey45Binding;
uses
FastMM4,
Forms,
ufrmSM45Demo in 'ufrmSM45Demo.pas' {frmSM45Demo},
NSPRAPI in '..\..\NSPRAPI.pas',
SpiderMonkey in '..\..\SpiderMonkey.pas',
SyNode in '..\..\SyNode.pas',
SyNodeBinding_fs in '..\..\SyNodeBinding_fs.pas',
SyNodeBinding_HTTPClient in '..\..\SyNodeBinding_HTTPClient.pas',
SyNodeProto in '..\..\SyNodeProto.pas',
SyNodeRemoteDebugger in '..\..\SyNodeRemoteDebugger.pas',
SyNodeSimpleProto in '..\..\SyNodeSimpleProto.pas',
SyNodeBinding_worker in '..\..\SyNodeBinding_worker.pas',
SyNodeBinding_const in '..\..\SyNodeBinding_const.pas',
SyNodeBinding_buffer in '..\..\SyNodeBinding_buffer.pas',
SyNodeBinding_util in '..\..\SyNodeBinding_util.pas',
SyNodeBinding_uv in '..\..\SyNodeBinding_uv.pas',
SyNodeReadWrite in '..\..\SyNodeReadWrite.pas';
{$R *.res}
begin
InitJS;
try
Application.Initialize;
Application.CreateForm(TfrmSM45Demo, frmSM45Demo);
Application.Run;
finally
frmSM45Demo.Free;
ShutDownJS;
end;
end.

View File

@@ -0,0 +1,160 @@
 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{CF53FAB5-8ADD-444D-A0C2-ACC4AF9E90D6}</ProjectGuid>
<MainSource>SpiderMonkey45Binding.dpr</MainSource>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<TargetedPlatforms>1</TargetedPlatforms>
<AppType>Application</AppType>
<FrameworkType>VCL</FrameworkType>
<ProjectVersion>13.4</ProjectVersion>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
<Base_Win64>true</Base_Win64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
<Cfg_1>true</Cfg_1>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
<Cfg_2>true</Cfg_2>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
<Cfg_2_Win32>true</Cfg_2_Win32>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_UnitSearchPath>..\..\..\..\FastMM4;..\..\..;..\..\..\SQLite3;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<VerInfo_Locale>1049</VerInfo_Locale>
<DCC_S>false</DCC_S>
<DCC_K>false</DCC_K>
<DCC_F>false</DCC_F>
<DCC_ImageBase>00400000</DCC_ImageBase>
<DCC_E>false</DCC_E>
<DCC_N>false</DCC_N>
<DCC_Namespace>Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace)</DCC_Namespace>
<VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win64)'!=''">
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
<Icon_MainIcon>SpiderMonkey45Binding_Icon1.ico</Icon_MainIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<DCC_DcuOutput>obj\win32</DCC_DcuOutput>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<Icon_MainIcon>SpiderMonkey45Binding_Icon1.ico</Icon_MainIcon>
<DCC_Namespace>System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
<DCC_DebugInformation>false</DCC_DebugInformation>
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
<DCC_Optimize>false</DCC_Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<DCC_Define>SM52;$(DCC_Define)</DCC_Define>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="ufrmSM45Demo.pas">
<Form>frmSM45Demo</Form>
</DCCReference>
<DCCReference Include="..\..\NSPRAPI.pas"/>
<DCCReference Include="..\..\SpiderMonkey.pas"/>
<DCCReference Include="..\..\SyNode.pas"/>
<DCCReference Include="..\..\SyNodeBinding_fs.pas"/>
<DCCReference Include="..\..\SyNodeBinding_HTTPClient.pas"/>
<DCCReference Include="..\..\SyNodeProto.pas"/>
<DCCReference Include="..\..\SyNodeRemoteDebugger.pas"/>
<DCCReference Include="..\..\SyNodeSimpleProto.pas"/>
<DCCReference Include="..\..\SyNodeBinding_worker.pas"/>
<DCCReference Include="..\..\SyNodeBinding_const.pas"/>
<DCCReference Include="..\..\SyNodeBinding_buffer.pas"/>
<DCCReference Include="..\..\SyNodeBinding_util.pas"/>
<DCCReference Include="..\..\SyNodeBinding_uv.pas"/>
<DCCReference Include="..\..\SyNodeReadWrite.pas"/>
<BuildConfiguration Include="Debug">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
<BuildConfiguration Include="Release">
<Key>Cfg_1</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
<Borland.ProjectType/>
<BorlandProject>
<Delphi.Personality>
<Source>
<Source Name="MainSource">SpiderMonkey45Binding.dpr</Source>
</Source>
<VersionInfo>
<VersionInfo Name="IncludeVerInfo">False</VersionInfo>
<VersionInfo Name="AutoIncBuild">False</VersionInfo>
<VersionInfo Name="MajorVer">1</VersionInfo>
<VersionInfo Name="MinorVer">0</VersionInfo>
<VersionInfo Name="Release">0</VersionInfo>
<VersionInfo Name="Build">0</VersionInfo>
<VersionInfo Name="Debug">False</VersionInfo>
<VersionInfo Name="PreRelease">False</VersionInfo>
<VersionInfo Name="Special">False</VersionInfo>
<VersionInfo Name="Private">False</VersionInfo>
<VersionInfo Name="DLL">False</VersionInfo>
<VersionInfo Name="Locale">1049</VersionInfo>
<VersionInfo Name="CodePage">1251</VersionInfo>
</VersionInfo>
<VersionInfoKeys>
<VersionInfoKeys Name="CompanyName"/>
<VersionInfoKeys Name="FileDescription"/>
<VersionInfoKeys Name="FileVersion">1.0.0.0</VersionInfoKeys>
<VersionInfoKeys Name="InternalName"/>
<VersionInfoKeys Name="LegalCopyright"/>
<VersionInfoKeys Name="LegalTrademarks"/>
<VersionInfoKeys Name="OriginalFilename"/>
<VersionInfoKeys Name="ProductName"/>
<VersionInfoKeys Name="ProductVersion">1.0.0.0</VersionInfoKeys>
<VersionInfoKeys Name="Comments"/>
</VersionInfoKeys>
</Delphi.Personality>
<Platforms>
<Platform value="Win64">False</Platform>
<Platform value="Win32">True</Platform>
</Platforms>
</BorlandProject>
<ProjectFileVersion>12</ProjectFileVersion>
</ProjectExtensions>
<Import Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')" Project="$(BDS)\Bin\CodeGear.Delphi.Targets"/>
<Import Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')" Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj"/>
</Project>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="10"/>
<PathDelim Value="\"/>
<General>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="SpiderMonkey45Binding"/>
<ResourceType Value="res"/>
<UseXPManifest Value="True"/>
</General>
<i18n>
<EnableI18N LFM="False"/>
</i18n>
<VersionInfo>
<StringTable ProductVersion=""/>
</VersionInfo>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<local>
<FormatVersion Value="1"/>
</local>
</RunParams>
<RequiredPackages Count="1">
<Item1>
<PackageName Value="LCL"/>
</Item1>
</RequiredPackages>
<Units Count="2">
<Unit0>
<Filename Value="SpiderMonkey45Binding.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="ufrmSM45Demo.pas"/>
<IsPartOfProject Value="True"/>
<ComponentName Value="frmSM45Demo"/>
<HasResources Value="True"/>
<ResourceBaseClass Value="Form"/>
</Unit1>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="SpiderMonkey45Binding"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir);../../..;../.."/>
<OtherUnitFiles Value="../../..;../..;../../../SQLite3"/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<Conditionals Value="if TargetOS='darwin' then
CustomOptions := ' -Cg-';"/>
<Parsing>
<SyntaxOptions>
<SyntaxMode Value="Delphi"/>
</SyntaxOptions>
</Parsing>
<Linking>
<Options>
<Win32>
<GraphicApplication Value="True"/>
</Win32>
</Options>
</Linking>
<Other>
<CustomOptions Value="-dSM52"/>
</Other>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,31 @@
program SpiderMonkey45Binding;
uses
{$I SynDprUses.inc}
Forms,
{$ifdef FPC}
Interfaces,
{$endif}
ufrmSM45Demo in 'ufrmSM45Demo.pas' {frmSM45Demo},
NSPRApi in '..\..\NSPRAPI.pas',
SpiderMonkey in '..\..\SpiderMonkey.pas',
SyNode in '..\..\SyNode.pas',
SyNodeBinding_fs in '..\..\SyNodeBinding_fs.pas',
SyNodeBinding_HTTPClient in '..\..\SyNodeBinding_HTTPClient.pas',
SyNodeProto in '..\..\SyNodeProto.pas',
SyNodeRemoteDebugger in '..\..\SyNodeRemoteDebugger.pas',
SyNodeSimpleProto in '..\..\SyNodeSimpleProto.pas';
{$R *.res}
begin
InitJS;
try
Application.Initialize;
Application.CreateForm(TfrmSM45Demo, frmSM45Demo);
Application.Run;
finally
frmSM45Demo.Free;
ShutDownJS;
end;
end.

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View File

@@ -0,0 +1,81 @@
object frmSM45Demo: TfrmSM45Demo
Left = 362
Height = 516
Top = 176
Width = 771
Caption = 'SyNode (Synopse SpiderMonkey with NodeJS modules) demo'
ClientHeight = 516
ClientWidth = 771
Color = clBtnFace
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
OnCreate = FormCreate
OnDestroy = FormDestroy
LCLVersion = '2.0.0.2'
object mSource: TMemo
Left = 16
Height = 414
Top = 24
Width = 442
Lines.Strings = (
'/*'
'1) We define a property global.mainForm = binding to this Deplhi form'
'2) JavaScript debugger is listeninng on the localhost:6000'
' - to debug run Firefox with -chrome flag as below'
' "C:\Program Files\Firefox Developer Edition\firefox.exe" -chrome chrome://devtools/content/framework/connect/connect.xhtml'
'OR'
' - enable a remote debugger: https://developer.mozilla.org/ru/docs/Tools/Remote_Debugging#On_the_desktop_2'
' - go to chrome://devtools/content/framework/connect/connect.xhtml'
'FF58 for windows has known bug with remote debugger - use either FF57 or FF59 (developer edition)'
'*/'
''
'mainForm.caption = new Date().toLocaleString();'
'mainForm.top = 10;'
''
'const fs = require(''fs'');'
'const path = require(''path'');'
'let content = fs.readFileSync(path.join(process.cwd(), ''ExtendDebuggerConsole.js''), ''utf8'');'
'mainForm.toLog(content);'
'/*'
' * You can evaluate script below if you compiled math-module sample'
' * to SyNode\Samples\01 - Dll Modules\math-module\build\mathModule.dll'
' */'
''
'/*'
'const mathModule = require(''''../../Samples/01 - Dll Modules/math-module'''');'
'mainForm.toLog(''''PI='''' + mathModule.pi);'
'mainForm.toLog(`(1+ 2)=${mathModule.add(1, 2)}`);'
'mainForm.toLog(`16/3=${mathModule.div(16, 3)}`);'
'*/'
''
'const http = require(''http'');'
'const assert = require(''assert'');'
'// set global proxy settings if client is behind a proxy'
'// http.setGlobalProxyConfiguration(''''proxy.main:3249'''', ''''localhost'''');'
'let resp = http.get(''https://synopse.info/fossil/wiki/Synopse+OpenSource'');'
'// check we are actually behind a proxy'
'// assert.ok(resp.headers(''''via'''').startsWith(''''1.1 proxy.main''''), ''''proxy used'''');'
'let index = resp.read();'
'mainForm.toLog(index);'
)
TabOrder = 0
WordWrap = False
end
object mResult: TMemo
Left = 464
Height = 414
Top = 24
Width = 249
TabOrder = 1
end
object btnEvaluate: TButton
Left = 16
Height = 25
Top = 444
Width = 129
Caption = 'Evaluate selected'
OnClick = btnEvaluateClick
TabOrder = 2
end
end

View File

@@ -0,0 +1,255 @@
unit ufrmSM45Demo;
interface
uses
{$IFNDEF LCL}Windows,{$ELSE}LclIntf, LMessages, LclType, LResources,{$ENDIF}
Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls,
SynCommons,
SpiderMonkey,
SyNode,
SyNodeProto,
SyNodeSimpleProto;
const
WM_DEBUG_INTERRUPT = WM_USER + 1;
type
{ TfrmSM45Demo }
TfrmSM45Demo = class(TForm)
mSource: TMemo;
mResult: TMemo;
btnEvaluate: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnEvaluateClick(Sender: TObject);
private
procedure cmd_Itenterupt(var aMessage: TMessage); message WM_DEBUG_INTERRUPT;
protected
FSMManager: TSMEngineManager;
FEngine: TSMEngine;
procedure DoOnCreateNewEngine(const aEngine: TSMEngine);
function DoOnGetEngineName(const aEngine: TSMEngine): RawUTF8;
// a handler, called from a debugger thread to interrupt a current thread
// in this example will send a WM_DEBUG_INTERRUPT message to a main window
// main application thread catch a message and call FEngine.InterruptCallback
procedure doInteruptInOwnThread;
/// here we add features to debugger console
// type ? in firefox console to get a feature help
procedure DoOnJSDebuggerInit(const aEngine: TSMEngine);
published
property sources: TMemo read mSource;
property results: TMemo read mResult;
property evaluateButton: TButton read btnEvaluate;
function toLog(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
end;
var
frmSM45Demo: TfrmSM45Demo;
implementation
{$R *.dfm}
{$I Synopse.inc}
{$I SynSM.inc} // define SM_DEBUG JS_THREADSAFE CONSIDER_TIME_IN_Z
{$I SyNode.inc} // define SM_DEBUG CONSIDER_TIME_IN_Z
procedure TfrmSM45Demo.FormCreate(Sender: TObject);
begin
// create a JavaScript angine manager
FSMManager := TSMEngineManager.Create(
{$IFDEF CORE_MODULES_IN_RES}''{$ELSE}StringToUTF8(RelToAbs(ExeVersion.ProgramFilePath, '../../core_modules')){$ENDIF});
// optionaly increase a max engine memory
FSMManager.MaxPerEngineMemory := 512 * 1024 * 1024;
// add a handler called every time new engine is created
// inside a handler we can add a binding's to a native functions (implemented in Delphi)
// and evaluate some initial JavaScripts
FSMManager.OnNewEngine := DoOnCreateNewEngine;
FSMManager.OnGetName := DoOnGetEngineName;
FSMManager.OnDebuggerInit := DoOnJSDebuggerInit;
// start a JavaScript debugger on the localhost:6000
FSMManager.startDebugger('6000');
// debugger can see engines created after startDebugger() call,
// so we create a main engine after debugger is started
// in this example we need only one engine (we are single-thread)
FEngine := FSMManager.ThreadSafeEngine(nil);
end;
procedure TfrmSM45Demo.FormDestroy(Sender: TObject);
begin
FSMManager.ReleaseCurrentThreadEngine;
FSMManager.Free;
end;
procedure TfrmSM45Demo.btnEvaluateClick(Sender: TObject);
var
res: jsval;
begin
if FEngine = nil then
raise Exception.Create('JS engine not initialized');
// evaluate a text from mSource memo
if mSource.SelText <> '' then
FEngine.Evaluate(mSource.SelText, 'mSourceSelected.js', 1, res)
else
FEngine.Evaluate(mSource.lines.Text, 'mSource.js', 1, res);
end;
procedure TfrmSM45Demo.cmd_Itenterupt(var aMessage: TMessage);
begin
if FEngine = nil then
raise Exception.Create('JS engine not initialized');
{$IFDEF SM52}
FEngine.cx.RequestInterruptCallback;
FEngine.cx.CheckForInterrupt;
{$ELSE}
FEngine.rt.InterruptCallback(FEngine.cx);
{$ENDIF}
end;
procedure TfrmSM45Demo.doInteruptInOwnThread;
begin
PostMessage(Self.Handle, WM_DEBUG_INTERRUPT, 0, 0);
{$IFNDEF FPC}
Application.ProcessMessages;
{$ENDIF}
end;
function TfrmSM45Demo.toLog(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
begin
try
if (vp.argv[0].isString) then
mResult.lines.add(vp.argv[0].asJSString.ToString(cx))
else
raise ESMException.Create('toLog accept only String type of arg');
result := true;
except
on E: Exception do
begin
Result := False;
JSError(cx, E);
end;
end;
end;
type
TStringsProto = class(TSMSimpleRTTIProtoObject)
protected
procedure InitObject(aParent: PJSRootedObject); override;
end;
procedure TfrmSM45Demo.DoOnCreateNewEngine(const aEngine: TSMEngine);
begin
// for main thread only. Worker threads do not need this
if GetCurrentThreadId = MainThreadID then begin
aEngine.doInteruptInOwnThread := doInteruptInOwnThread;
// in XE actual class of TMemo.lines is TMemoStrings - let's force it to be a TStrings like
aEngine.defineClass(mSource.lines.ClassType, TStringsProto, aEngine.GlobalObject);
// define a propery mainForm in the JavaScript
aEngine.GlobalObject.ptr.DefineProperty(aEngine.cx, 'mainForm',
// proeprty value is a wrapper around the Self
CreateJSInstanceObjForSimpleRTTI(aEngine.cx, Self, aEngine.GlobalObject),
// we can enumerate this property, it read-only and can not be deleted
JSPROP_ENUMERATE or JSPROP_READONLY or JSPROP_PERMANENT
);
end;
end;
function TfrmSM45Demo.DoOnGetEngineName(const aEngine: TSMEngine): RawUTF8;
begin
if GetCurrentThreadId = MainThreadID then
result := 'FormEngine';
end;
procedure TfrmSM45Demo.DoOnJSDebuggerInit(const aEngine: TSMEngine);
begin
// aEngine.EvaluateModule(
// {$IFDEF MSWINDOWS}
// '..\..\..\..\DebuggerInit.js'
// {$ELSE}
// '../../../../DebuggerInit.js'
// {$ENDIF}
// );
end;
{ TStringsProto }
function TStringsTextWrite(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
this: PJSObject;
proto: TSMCustomProtoObject;
Instance: PSMInstanceRecord;
begin
try
this := vp.thisObject[cx];
if IsProtoObject(cx, this, proto) then begin
vp.rval := JSVAL_NULL;
Result := True;
exit;
end;
if not IsInstanceObject(cx, this, Instance) then
raise ESMException.Create('No privat data!');
TStrings(Instance.instance).Text := vp.argv[0].asJSString.ToString(cx);
Result := True;
except
on E: Exception do
begin
Result := False;
JSError(cx, E);
end;
end;
end;
function TStringsTextRead(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
this: PJSObject;
proto: TSMCustomProtoObject;
Instance: PSMInstanceRecord;
begin
try
this := vp.thisObject[cx];
if IsProtoObject(cx, this, proto) then begin
vp.rval := JSVAL_NULL;
Result := True;
exit;
end;
if not IsInstanceObject(cx, this, Instance) then
raise ESMException.Create('No privat data!');
vp.rval := SimpleVariantToJSval(cx, TStrings(Instance.instance).Text);
Result := True;
except
on E: Exception do
begin
Result := False;
JSError(cx, E);
end;
end;
end;
procedure TStringsProto.InitObject(aParent: PJSRootedObject);
var
idx: Integer;
begin
inherited;
idx := Length(FJSProps);
SetLength(FJSProps, idx + 1);
FJSProps[idx].flags := JSPROP_ENUMERATE or JSPROP_PERMANENT or JSPROP_SHARED;
FJSProps[idx].Name := 'text';
FJSProps[idx].setter.native.info := nil;
FJSProps[idx].setter.native.op := TStringsTextWrite;
FJSProps[idx].getter.native.info := nil;
FJSProps[idx].getter.native.op := TStringsTextRead;
end;
end.

View File

@@ -0,0 +1,6 @@
program HelloSpiderMonkey52;
uses
SpiderMonkey;
begin
// TODO: implementation for Delphi
end.

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="10"/>
<PathDelim Value="\"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="HelloSpiderMonkey52"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<i18n>
<EnableI18N LFM="False"/>
</i18n>
<VersionInfo>
<StringTable ProductVersion=""/>
</VersionInfo>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<local>
<FormatVersion Value="1"/>
</local>
</RunParams>
<Units Count="1">
<Unit0>
<Filename Value="HelloSpiderMonkey52.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="HelloSpiderMonkey52"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir);../..;../../.."/>
<Libraries Value="../../mozjs"/>
<OtherUnitFiles Value="../..;../../.."/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<Other>
<CustomOptions Value="-dSM52"/>
</Other>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,100 @@
program HelloSpiderMonkey52;
{
This example has translated from C++ hello world example taken on
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/How_to_embed_the_JavaScript_engine
}
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes,
SysUtils,
SpiderMonkey;
const
script = '''Hello '' + ''world, it is ''+new Date()';
filename = 'noname';
var
global_ops: JSClassOps = (
addProperty: nil;
delProperty: nil;
getProperty: nil;
setProperty: nil;
enumerate: nil;
resolve: nil;
mayResolve: nil;
finalize: nil;
call: nil;
hasInstance: nil;
construct: nil;
trace: nil;//@JS_GlobalObjectTraceHook;
);
// The class of the global object.
global_class: JSClass = (
name: 'global';
flags: JSCLASS_GLOBAL_FLAGS;
cOps: @global_ops;
);
cx: PJSContext;
options: JS_CompartmentOptions;
global: PJSRootedObject;
val: jsval;
rval: PJSRootedValue;
oldCprt: PJSCompartment;
lineno: Integer = 1;
opts: PJSCompileOptions;
ok: Boolean;
str: PJSString;
I: Integer;
Frames: PPointer;
begin
try
JS_Init();
cx := JSContext.CreateNew(8 * 1024 * 1024);
try
cx^.BeginRequest(); // In practice, you would want to exit this any
try // time you're spinning the event loop
// Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
// out of scope before we JS_DestroyContext.
global := cx^.NewRootedObject(cx^.NewGlobalObject(@global_class));
if (global = nil) then
Halt(1);
rval := cx^.NewRootedValue(val);
oldCprt := cx^.EnterCompartment(global^.ptr);
try // Scope for JSAutoCompartment
cx^.InitStandardClasses(global^.ptr);
opts := cx^.NewCompileOptions();
opts^.filename := filename;
//opts^.?? := lineno;
ok := cx^.EvaluateScript(opts, script, Length(script), rval^.ptr);
if (not ok) then
Halt(1);
finally
cx^.LeaveCompartment(oldCprt);
end;
str := rval^.ptr.asJSString;
WriteLn(str^.ToAnsi(cx));
finally
cx^.EndRequest();
end;
finally
cx^.Destroy();
JS_ShutDown();
end;
Halt(0);
except
on E: Exception do begin
Writeln(E.Message);
Writeln(BackTraceStrFunc(ExceptAddr));
Frames := ExceptFrames;
for I := 0 to ExceptFrameCount - 1 do
Writeln(BackTraceStrFunc(Frames[I]));
end;
end;
end.

View File

@@ -0,0 +1,23 @@
#------------------------------------------------------------------------------
VERSION = BWS.01
#------------------------------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#------------------------------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#------------------------------------------------------------------------------
PROJECTS = SpiderMonkey45Binding.exe mathModule.dll
#------------------------------------------------------------------------------
default: $(PROJECTS)
#------------------------------------------------------------------------------
SpiderMonkey45Binding.exe: 02 - Bindings\SpiderMonkey45Binding.dpr
$(DCC)
mathModule.dll: 01 - Dll Modules\math-module\src\mathModule.dpr
$(DCC)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse Informatique - http://synopse.info
Scripting support for mORMot Copyright (C) 2020 Pavel Mashlyakovsky
pavel.mash at gmail.com
*** 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) 2020
the Initial Developer. All Rights Reserved.
Contributor(s):
- Arnaud Bouchez
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 1.8.5
- defines some global conditionals used by SynSMAPI.pas and SynSM.pas
}
{$DEFINE IS_LITTLE_ENDIAN}
//TODO add processor architecture check here or remove define and corresponding code below if FPC only for x32/64?
{$DEFINE JS_THREADSAFE}
// we MUST compile mozjs with JS_THREADSAFE directive
{.$DEFINE SM_DEBUG}
// for debuging SynSM
{$DEFINE CONSIDER_TIME_IN_Z}
// let serve all date-time as in GMT0 (UTC) timezone
{.$DEFINE WITHASSERT}
// ensure *TO_JSVAL() macros will check explicitly for the target type
{$DEFINE CORE_MODULES_IN_RES} // core_modules is compiled into resources
{$ifndef FPC}
{$IFDEF CONDITIONALEXPRESSIONS}
{$if CompilerVersion = 24.0}
// see http://synopse.info/forum/viewtopic.php?pid=12598#p12598
{$define FIXBUGXE3}
{$ifend}
{$ELSE}
Error: SyNode requires Delphi 7 of higher
{$ENDIF}
{$endif}
{$IFNDEF SM52}
{$DEFINE SM45}
{$ENDIF}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,276 @@
/// `http` module support bindings for SyNode
// TODO - current implemenattion is not filly compartible with nodeJS
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
//
// Contributor(s):
// - Pavel Mashlyakovsky
unit SyNodeBinding_HTTPClient;
interface
{$I Synopse.inc}
{$I SyNode.inc}
uses
SysUtils, SynCrtSock, SynCommons, SpiderMonkey;
type
{$M+}
THTTPClient = class
private
fClient: THttpRequest;
fInHeaders: RawUTF8;
FMethod: RawUTF8;
FWriter: TTextWriter;
FInData: RawByteString;
FRespHeaders: SockString;
FRespText: SockString;
FKeepAlive: Cardinal;
FResponseStatus: integer;
fConnectTimeout: integer;
fSendTimeout: integer;
fReceiveTimeout: integer;
protected
function GetRespText: RawUTF8;
function GetRespHeaders: RawUTF8;
function sendFile(const URL: RawUTF8; aFileName: string): boolean;
public
destructor Destroy; override;
published
// for timeout explain see http://msdn.microsoft.com/en-us/library/windows/desktop/aa384116%28v=vs.85%29.aspx
constructor Create();
function initialize(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
function write(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
function writeEnd(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
function read(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
//function doRequest(const URL: RawUTF8; const ContentType: RawUTF8 = ''): boolean;
function doRequest(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
property method: RawUTF8 read FMethod write FMethod;
property keepAlive: cardinal read FKeepAlive write FKeepAlive default 1;
property headers: RawUTF8 read fInHeaders write fInHeaders;
property responseText: RawUTF8 read GetRespText;
property responseHeaders: RawUTF8 read GetRespHeaders;
property responseStatus: integer read FResponseStatus;
end;
{$M-}
implementation
uses
SynZip, SyNode, SyNodeSimpleProto, SyNodeReadWrite;
{ THTTPClient }
constructor THTTPClient.Create();
begin
inherited Create;
FMethod := 'POST';
FKeepAlive := 1;
end;
function THTTPClient.initialize(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
in_argv: PjsvalVector;
aServer, aPort: AnsiString;
aHttps: boolean;
aProxyName, aProxyByPass: RawUTF8;
begin
in_argv := vp.argv;
Result := true;
try
if (fClient <> nil) then raise ESMException.Create('already initialized');
if (argc < 2) or (not in_argv[0].isString) or (not in_argv[1].isString) then
raise ESMException.Create('invalid usage');
aServer := in_argv[0].asJSString.ToAnsi(cx);
aPort := in_argv[1].asJSString.ToAnsi(cx);
if (argc > 2) and (in_argv[2].isBoolean) then
aHttps := in_argv[2].asBoolean
else
aHttps := false;
if (argc > 4) and (in_argv[4].isString) then
aProxyName := in_argv[4].asJSString.ToUTF8(cx)
else
aProxyName := '';
if (argc > 5) and (in_argv[5].isString) then
aProxyByPass := in_argv[5].asJSString.ToUTF8(cx)
else
aProxyByPass := '';
if (argc > 6) and (in_argv[6].isInteger) then
fConnectTimeout := in_argv[6].asInteger
else
fConnectTimeout := HTTP_DEFAULT_CONNECTTIMEOUT;
if (argc > 7) and (in_argv[7].isInteger) then
fSendTimeout := in_argv[7].asInteger
else
fSendTimeout := HTTP_DEFAULT_SENDTIMEOUT;
if (argc > 8) and (in_argv[8].isInteger) then
fReceiveTimeout := in_argv[8].asInteger
else
fReceiveTimeout := HTTP_DEFAULT_RECEIVETIMEOUT;
fClient := {$IFDEF MSWINDOWS}TWinHTTP{$ELSE}TCurlHTTP{$ENDIF}
.Create(aServer, aPort, aHttps, aProxyName, aProxyByPass, fConnectTimeout, fSendTimeout, fReceiveTimeout);
if (argc > 3) and (in_argv[3].isBoolean) then
fClient.RegisterCompress(CompressGZip);
if aHttps then
fClient.IgnoreSSLCertificateErrors := true;
except
on E: Exception do begin
Result := False;
JSError(cx, E);
end;
end;
end;
destructor THTTPClient.Destroy;
begin
fClient.Free;
inherited Destroy;
end;
//function THTTPClient.doRequest(const URL: RawUTF8; const ContentType: RawUTF8 = ''): boolean;
function THTTPClient.doRequest(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
URL: SockString;
in_argv: PjsvalVector;
begin
FRespHeaders := '';
FRespText := '';
in_argv := vp.argv;
Result := true;
try
if (argc <> 1) or (not in_argv[0].isString) then
raise ESMException.Create('usage doRequest(URL: string)');
URL := in_argv[0].asJSString.ToAnsi(cx);
try
FResponseStatus := fClient.Request(URL, FMethod, keepAlive, fInHeaders, FInData, '', FRespHeaders, FRespText);
except
on E: EOSError do
FResponseStatus := E.ErrorCode;
end;
except
on E: Exception do begin
Result := False;
JSError(cx, E);
end;
end;
FInData := '';
FInHeaders := '';
end;
function THTTPClient.read(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
begin
Result := true;
try
vp.rval := SyNodeReadWrite.SMRead_impl(cx, argc, vp.argv, FRespText);
except
on E: Exception do begin
Result := False;
JSError(cx, E);
end;
end;
end;
//function THTTPClient.request(const URL, data: RawUTF8): boolean;
//begin
// FInData := data;
// Result := doRequest(URL);
//end;
function THTTPClient.sendFile(const URL: RawUTF8; aFileName: string): boolean;
var
buffer: RawByteString;
begin
FResponseStatus := 500;
try
if not FileExists(aFileName) then begin
FRespText := 'File not found';
FResponseStatus := 404;
end;
try
buffer := StringFromFile(aFileName);
FResponseStatus := fClient.Request(URL, FMethod, FKeepAlive, fInHeaders, buffer, '', FRespHEaders, FRespText);
except
on E: EOSError do begin
FResponseStatus := E.ErrorCode;
FRespText := StringToUTF8(E.Message);
end;
end;
finally
Result := (FResponseStatus = 200);
end;
end;
function THTTPClient.write(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
begin
if (FWriter = nil) then
FWriter := TTextWriter.CreateOwnedStream;
Result := True;
try
vp.rval := SyNodeReadWrite.SMWrite_impl(cx, argc, vp.argv, FWriter);
except
on E: Exception do begin
Result := False;
JSError(cx, E);
end;
end;
end;
function THTTPClient.writeEnd(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
data: RawUTF8;
begin
Result := True;
if (argc > 0 ) then
Result := write(cx, argc, vp);
if Result then
FWriter.SetText(data);
FInData := data;
FreeAndNil(FWriter);
end;
function THTTPClient.GetRespText: RawUTF8;
begin
Result := FRespText;
end;
function THTTPClient.GetRespHeaders: RawUTF8;
begin
Result := FRespHeaders;
end;
function SyNodeBindingProc_synode_http(const aEngine: TSMEngine; const bindingNamespaceName: SynUnicode): jsval;
var
obj: PJSRootedObject;
cx: PJSContext;
begin
cx := aEngine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
try
aEngine.defineClass(THTTPClient, TSMSimpleRTTIProtoObject, obj);
Result := obj.ptr.ToJSValue;
finally
cx.FreeRootedObject(obj);
end;
end;
initialization
TSMEngineManager.RegisterBinding('synode_http', SyNodeBindingProc_synode_http);
end.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,232 @@
/// `util` module support bindings for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeBinding_const;
interface
{$I Synopse.inc}
{$I SyNode.inc}
uses
SysUtils,
SynCommons,
SyNode, SpiderMonkey;
implementation
uses
SynZip,
{$IFDEF MSWINDOWS}
Windows
{$ELSE}
BaseUnix
{$ENDIF}
;
{$IFDEF UNIX}
const
O_DSYNC = &0010000;
O_NOATIME = &1000000;
// fs open() flags supported on other platforms
O_RANDOM = 0;
O_SHORT_LIVED = 0;
O_SEQUENTIAL = 0;
O_TEMPORARY = 0;
O_EXLOCK = 0;
O_SYMLINK = 0;
{$ENDIF}
{$IFDEF MSWINDOWS}
const
F_OK = 0;
R_OK = 4;
W_OK = 2;
X_OK = 1;
// fs open() flags supported on Windows:
O_APPEND = $0008;
O_CREAT = $0100;
O_EXCL = $0400;
O_RANDOM = $0010;
O_RDONLY = 0;
O_RDWR = 2;
O_SEQUENTIAL = $0020;
O_SHORT_LIVED = $1000;
O_TEMPORARY = $0040;
O_TRUNC = $0200;
O_WRONLY = 1;
// fs open() flags supported on other platforms (or mapped on Windows):
O_DIRECT = $02000000; // FILE_FLAG_NO_BUFFERING
O_DIRECTORY = 0;
O_DSYNC = $04000000; // FILE_FLAG_WRITE_THROUGH
O_EXLOCK = $10000000; // EXCLUSIVE SHARING MODE
O_NOATIME = 0;
O_NOCTTY = 0;
O_NOFOLLOW = 0;
O_NONBLOCK = 0;
O_SYMLINK = 0;
O_SYNC = $08000000; // FILE_FLAG_WRITE_THROUGH
{ File types }
S_IFMT = $F000; // type of file mask
S_IFIFO = $1000; // named pipe (fifo)
S_IFCHR = $2000; // character special
S_IFDIR = $4000; // directory
S_IFBLK = $3000; // block special
S_IFREG = $8000; // regular
S_IFLNK = 0; // symbolic link
S_IFSOCK = 0; // socket
{$ENDIF}
Z_MIN_WINDOWBITS = 8;
Z_MAX_WINDOWBITS = 15;
Z_DEFAULT_WINDOWBITS = 15;
// Fewer than 64 bytes per chunk is not recommended.
// Technically it could work with as few as 8, but even 64 bytes
// is low. Usually a MB or more is best.
Z_MIN_CHUNK = 64;
Z_MAX_CHUNK = $0FFFFFFF;
Z_DEFAULT_CHUNK = 16 * 1024;
Z_MIN_MEMLEVEL = 1;
Z_MAX_MEMLEVEL = 9;
Z_DEFAULT_MEMLEVEL = 8;
Z_MIN_LEVEL = -1;
Z_MAX_LEVEL = 9;
Z_DEFAULT_LEVEL = Z_DEFAULT_COMPRESSION;
type
node_zlib_mode = (nzlmNONE, nzlmDEFLATE, nzlmINFLATE, nzlmGZIP, nzlmGUNZIP,
nzlmDEFLATERAW, nzlmINFLATERAW, nzlmUNZIP);
function SyNodeBindingProc_consts(const aEngine: TSMEngine; const bindingNamespaceName: SynUnicode): jsval;
var
obj, obj_fs, obj_zlib: PJSRootedObject;
jsv: jsval;
cx: PJSContext;
const
attrs = JSPROP_ENUMERATE or JSPROP_READONLY or JSPROP_PERMANENT;
begin
cx := aEngine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
obj_fs := cx.NewRootedObject(cx.NewObject(nil));
obj_zlib := cx.NewRootedObject(cx.NewObject(nil));
try
// constansts.fs
jsv.asInteger := F_OK; obj_fs.ptr.DefineProperty(cx, 'F_OK', jsv, attrs);
jsv.asInteger := R_OK; obj_fs.ptr.DefineProperty(cx, 'R_OK', jsv, attrs);
jsv.asInteger := W_OK; obj_fs.ptr.DefineProperty(cx, 'W_OK', jsv, attrs);
jsv.asInteger := X_OK; obj_fs.ptr.DefineProperty(cx, 'X_OK', jsv, attrs);
jsv.asInteger := O_APPEND; obj_fs.ptr.DefineProperty(cx, 'O_APPEND', jsv, attrs);
jsv.asInteger := O_CREAT; obj_fs.ptr.DefineProperty(cx, 'O_CREAT', jsv, attrs);
jsv.asInteger := O_EXCL; obj_fs.ptr.DefineProperty(cx, 'O_EXCL', jsv, attrs);
jsv.asInteger := O_RANDOM; obj_fs.ptr.DefineProperty(cx, 'O_RANDOM', jsv, attrs);
jsv.asInteger := O_RDONLY; obj_fs.ptr.DefineProperty(cx, 'O_RDONLY', jsv, attrs);
jsv.asInteger := O_RDWR; obj_fs.ptr.DefineProperty(cx, 'O_RDWR', jsv, attrs);
jsv.asInteger := O_SEQUENTIAL; obj_fs.ptr.DefineProperty(cx, 'O_SEQUENTIAL', jsv, attrs);
jsv.asInteger := O_SHORT_LIVED; obj_fs.ptr.DefineProperty(cx, 'O_SHORT_LIVED', jsv, attrs);
jsv.asInteger := O_TEMPORARY; obj_fs.ptr.DefineProperty(cx, '', jsv, attrs);
jsv.asInteger := O_TRUNC; obj_fs.ptr.DefineProperty(cx, 'O_TRUNC', jsv, attrs);
jsv.asInteger := O_WRONLY; obj_fs.ptr.DefineProperty(cx, 'O_WRONLY', jsv, attrs);
jsv.asInteger := O_DIRECT; obj_fs.ptr.DefineProperty(cx, 'O_DIRECT', jsv, attrs);
jsv.asInteger := O_DIRECTORY; obj_fs.ptr.DefineProperty(cx, 'O_DIRECTORY', jsv, attrs);
jsv.asInteger := O_DSYNC; obj_fs.ptr.DefineProperty(cx, 'O_DSYNC', jsv, attrs);
jsv.asInteger := O_EXLOCK; obj_fs.ptr.DefineProperty(cx, 'O_EXLOCK', jsv, attrs);
jsv.asInteger := O_NOATIME; obj_fs.ptr.DefineProperty(cx, 'O_NOATIME', jsv, attrs);
jsv.asInteger := O_NOCTTY; obj_fs.ptr.DefineProperty(cx, 'O_NOCTTY', jsv, attrs);
jsv.asInteger := O_NOFOLLOW; obj_fs.ptr.DefineProperty(cx, 'O_NOFOLLOW', jsv, attrs);
jsv.asInteger := O_NONBLOCK; obj_fs.ptr.DefineProperty(cx, 'O_NONBLOCK', jsv, attrs);
jsv.asInteger := O_SYMLINK; obj_fs.ptr.DefineProperty(cx, 'O_SYMLINK', jsv, attrs);
jsv.asInteger := O_SYNC; obj_fs.ptr.DefineProperty(cx, 'O_SYNC', jsv, attrs);
jsv.asInteger := S_IFMT; obj_fs.ptr.DefineProperty(cx, 'S_IFMT', jsv, attrs);
jsv.asInteger := S_IFIFO; obj_fs.ptr.DefineProperty(cx, 'S_IFIFO', jsv, attrs);
jsv.asInteger := S_IFCHR; obj_fs.ptr.DefineProperty(cx, 'S_IFCHR', jsv, attrs);
jsv.asInteger := S_IFDIR; obj_fs.ptr.DefineProperty(cx, 'S_IFDIR', jsv, attrs);
jsv.asInteger := S_IFBLK; obj_fs.ptr.DefineProperty(cx, 'S_IFBLK', jsv, attrs);
jsv.asInteger := S_IFREG; obj_fs.ptr.DefineProperty(cx, 'S_IFREG', jsv, attrs);
jsv.asInteger := S_IFLNK; obj_fs.ptr.DefineProperty(cx, 'S_IFLNK', jsv, attrs);
jsv.asInteger := S_IFSOCK; obj_fs.ptr.DefineProperty(cx, 'S_IFSOCK', jsv, attrs);
{
jsv.asInteger := S_IRWXU; obj_??.ptr.DefineProperty(cx, 'S_IRWXU', jsv, attrs);
jsv.asInteger := S_IRUSR; obj_??.ptr.DefineProperty(cx, 'S_IRUSR', jsv, attrs);
jsv.asInteger := S_IWUSR; obj_??.ptr.DefineProperty(cx, 'S_IWUSR', jsv, attrs);
jsv.asInteger := S_IXUSR; obj_??.ptr.DefineProperty(cx, 'S_IXUSR', jsv, attrs);
jsv.asInteger := S_IRWXG; obj_??.ptr.DefineProperty(cx, 'S_IRWXG', jsv, attrs);
jsv.asInteger := S_IRGRP; obj_??.ptr.DefineProperty(cx, 'S_IRGRP', jsv, attrs);
jsv.asInteger := S_IWGRP; obj_??.ptr.DefineProperty(cx, 'S_IWGRP', jsv, attrs);
jsv.asInteger := S_IXGRP; obj_??.ptr.DefineProperty(cx, 'S_IXGRP', jsv, attrs);
jsv.asInteger := S_IRWXO; obj_??.ptr.DefineProperty(cx, 'S_IRWXO', jsv, attrs);
jsv.asInteger := S_IROTH; obj_??.ptr.DefineProperty(cx, 'S_IROTH', jsv, attrs);
jsv.asInteger := S_IWOTH; obj_??.ptr.DefineProperty(cx, 'S_IWOTH', jsv, attrs);
jsv.asInteger := S_IXOTH; obj_??.ptr.DefineProperty(cx, 'S_IXOTH', jsv, attrs);
}
obj.ptr.DefineProperty(cx, 'fs', obj_fs.ptr.ToJSValue, attrs);
//constants zlib
jsv.asInteger := Z_NO_FLUSH; obj_zlib.ptr.DefineProperty(cx, 'Z_NO_FLUSH', jsv, attrs);
jsv.asInteger := Z_PARTIAL_FLUSH; obj_zlib.ptr.DefineProperty(cx, 'Z_PARTIAL_FLUSH', jsv, attrs);
jsv.asInteger := Z_SYNC_FLUSH; obj_zlib.ptr.DefineProperty(cx, 'Z_SYNC_FLUSH', jsv, attrs);
jsv.asInteger := Z_FULL_FLUSH; obj_zlib.ptr.DefineProperty(cx, 'Z_FULL_FLUSH', jsv, attrs);
jsv.asInteger := Z_FINISH; obj_zlib.ptr.DefineProperty(cx, 'Z_FINISH', jsv, attrs);
jsv.asInteger := Z_BLOCK; obj_zlib.ptr.DefineProperty(cx, 'Z_BLOCK', jsv, attrs);
// return/error codes
jsv.asInteger := Z_OK; obj_zlib.ptr.DefineProperty(cx, 'Z_OK', jsv, attrs);
jsv.asInteger := Z_STREAM_END; obj_zlib.ptr.DefineProperty(cx, 'Z_STREAM_END', jsv, attrs);
jsv.asInteger := Z_NEED_DICT; obj_zlib.ptr.DefineProperty(cx, 'Z_NEED_DICT', jsv, attrs);
jsv.asInteger := Z_ERRNO; obj_zlib.ptr.DefineProperty(cx, 'Z_ERRNO', jsv, attrs);
jsv.asInteger := Z_STREAM_ERROR; obj_zlib.ptr.DefineProperty(cx, 'Z_STREAM_ERROR', jsv, attrs);
jsv.asInteger := Z_DATA_ERROR; obj_zlib.ptr.DefineProperty(cx, 'Z_DATA_ERROR', jsv, attrs);
jsv.asInteger := Z_MEM_ERROR; obj_zlib.ptr.DefineProperty(cx, 'Z_MEM_ERROR', jsv, attrs);
jsv.asInteger := Z_BUF_ERROR; obj_zlib.ptr.DefineProperty(cx, 'Z_BUF_ERROR', jsv, attrs);
jsv.asInteger := Z_VERSION_ERROR; obj_zlib.ptr.DefineProperty(cx, 'Z_VERSION_ERROR', jsv, attrs);
jsv.asInteger := Z_NO_COMPRESSION; obj_zlib.ptr.DefineProperty(cx, 'Z_NO_COMPRESSION', jsv, attrs);
jsv.asInteger := Z_BEST_SPEED; obj_zlib.ptr.DefineProperty(cx, 'Z_BEST_SPEED', jsv, attrs);
jsv.asInteger := Z_BEST_COMPRESSION; obj_zlib.ptr.DefineProperty(cx, 'Z_BEST_COMPRESSION', jsv, attrs);
jsv.asInteger := Z_DEFAULT_COMPRESSION; obj_zlib.ptr.DefineProperty(cx, 'Z_DEFAULT_COMPRESSION', jsv, attrs);
jsv.asInteger := Z_FILTERED; obj_zlib.ptr.DefineProperty(cx, 'Z_FILTERED', jsv, attrs);
jsv.asInteger := Z_HUFFMAN_ONLY; obj_zlib.ptr.DefineProperty(cx, 'Z_HUFFMAN_ONLY', jsv, attrs);
jsv.asInteger := Z_RLE; obj_zlib.ptr.DefineProperty(cx, 'Z_RLE', jsv, attrs);
jsv.asInteger := Z_FIXED; obj_zlib.ptr.DefineProperty(cx, 'Z_FIXED', jsv, attrs);
jsv.asInteger := Z_DEFAULT_STRATEGY; obj_zlib.ptr.DefineProperty(cx, 'Z_DEFAULT_STRATEGY', jsv, attrs);
jsv.asInteger := ZLIB_VERNUM; obj_zlib.ptr.DefineProperty(cx, 'ZLIB_VERNUM', jsv, attrs);
// modes
jsv.asInteger := ord(nzlmDEFLATE); obj_zlib.ptr.DefineProperty(cx, 'DEFLATE', jsv, attrs);
jsv.asInteger := ord(nzlmINFLATE); obj_zlib.ptr.DefineProperty(cx, 'INFLATE', jsv, attrs);
jsv.asInteger := ord(nzlmGZIP); obj_zlib.ptr.DefineProperty(cx, 'GZIP', jsv, attrs);
jsv.asInteger := ord(nzlmGUNZIP); obj_zlib.ptr.DefineProperty(cx, 'GUNZIP', jsv, attrs);
jsv.asInteger := ord(nzlmDEFLATERAW); obj_zlib.ptr.DefineProperty(cx, 'DEFLATERAW', jsv, attrs);
jsv.asInteger := ord(nzlmINFLATERAW); obj_zlib.ptr.DefineProperty(cx, 'INFLATERAW', jsv, attrs);
jsv.asInteger := ord(nzlmUNZIP); obj_zlib.ptr.DefineProperty(cx, 'UNZIP', jsv, attrs);
// other consts (not used by SynZip yet)
jsv.asInteger := Z_MIN_WINDOWBITS; obj_zlib.ptr.DefineProperty(cx, 'Z_MIN_WINDOWBITS', jsv, attrs);
jsv.asInteger := Z_MAX_WINDOWBITS; obj_zlib.ptr.DefineProperty(cx, 'Z_MAX_WINDOWBITS', jsv, attrs);
jsv.asInteger := Z_DEFAULT_WINDOWBITS; obj_zlib.ptr.DefineProperty(cx, 'Z_DEFAULT_WINDOWBITS', jsv, attrs);
jsv.asInteger := Z_MIN_CHUNK; obj_zlib.ptr.DefineProperty(cx, 'Z_MIN_CHUNK', jsv, attrs);
jsv.asInteger := Z_MAX_CHUNK; obj_zlib.ptr.DefineProperty(cx, 'Z_MAX_CHUNK', jsv, attrs);
jsv.asInteger := Z_DEFAULT_CHUNK; obj_zlib.ptr.DefineProperty(cx, 'Z_DEFAULT_CHUNK', jsv, attrs);
jsv.asInteger := Z_MIN_MEMLEVEL; obj_zlib.ptr.DefineProperty(cx, 'Z_MIN_MEMLEVEL', jsv, attrs);
jsv.asInteger := Z_MAX_MEMLEVEL; obj_zlib.ptr.DefineProperty(cx, 'Z_MAX_MEMLEVEL', jsv, attrs);
jsv.asInteger := Z_DEFAULT_MEMLEVEL; obj_zlib.ptr.DefineProperty(cx, 'Z_DEFAULT_MEMLEVEL', jsv, attrs);
jsv.asInteger := Z_MIN_LEVEL; obj_zlib.ptr.DefineProperty(cx, 'Z_MIN_LEVEL', jsv, attrs);
jsv.asInteger := Z_MAX_LEVEL; obj_zlib.ptr.DefineProperty(cx, 'Z_MAX_LEVEL', jsv, attrs);
jsv.asInteger := Z_DEFAULT_LEVEL; obj_zlib.ptr.DefineProperty(cx, 'Z_DEFAULT_LEVEL', jsv, attrs);
obj.ptr.DefineProperty(cx, 'zlib', obj_zlib.ptr.ToJSValue, attrs);
Result := obj.ptr.ToJSValue;
finally
cx.FreeRootedObject(obj_zlib);
cx.FreeRootedObject(obj_fs);
cx.FreeRootedObject(obj);
end;
end;
initialization
TSMEngineManager.RegisterBinding('constants', SyNodeBindingProc_consts);
end.

View File

@@ -0,0 +1,818 @@
/// `fs` module support bindings for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeBinding_fs;
interface
{$I Synopse.inc}
{$I SyNode.inc}
uses
SysUtils,
SynCommons,
SpiderMonkey,
SyNode;
function os_realpath(const FileName: TFileName; var TargetName: TFileName): Boolean;
implementation
{$IFNDEF SM_DEBUG}
{$UNDEF SM_DEBUG_FSTRACEFILEREAD}
{$UNDEF SM_DEBUG_FSDUMPFILEEXCERPT}
{$ENDIF}
uses
{$IFDEF MSWINDOWS}
Windows,
{$ELSE}
Unix, BaseUnix, DateUtils,
{$ENDIF}
{$IFDEF FPC}
LazFileUtils,
{$ENDIF}
SyNodeReadWrite,
Classes,
SynLog;
/// decode text file to string using BOM
// if BOM not fount - use current system code page to convert ANSI content to unicode
// if file not found - return empty string
// internaly use AnyTextFileToRawUTF8
// accept one parameter - "path to file"
function fs_loadFile(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
const
USAGE = 'usage loadFile(pathToFile: String, [forceUFT8: boolean])';
var
in_argv: PjsvalVector;
forceUTF8: boolean;
name: String;
begin
forceUTF8 := true;
result := true;
try
in_argv := vp.argv;
if (argc < 1) or not in_argv[0].isString or ((argc > 1) and not in_argv[1].isBoolean )then
raise ESMException.Create(USAGE);
if argc > 1 then
forceUTF8 := in_argv[1].asBoolean;
name := in_argv[0].asJSString.ToString(cx);
{$ifdef SM_DEBUG_FSTRACEFILEREAD}
SynSMLog.Add.Log(sllDebug, 'fs_loadFile (%) called', name);
{$ENDIF}
if not FileExists(name) then
raise ESMException.Create('file does not exist');
// implementation below dont work if called in the same time from differnt thread
// TFileStream.Create(name, fmOpenRead).Free; // Check that file exists and can be opened;
vp.rval := cx.NewJSString(AnyTextFileToRawUTF8(name, forceUTF8)).ToJSVal;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// call RelToAbs
function fs_relToAbs(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
const
USAGE = 'usage: relToAbs(ABase, ATail: string;)';
var
in_argv: PjsvalVector;
baseDir, fileName, resPath: TFileName;
begin
try
in_argv := vp.argv;
if (argc <> 2) or not in_argv[0].isString or not in_argv[1].isString then
raise ESMException.Create(USAGE);
baseDir := in_argv[0].asJSString.ToString(cx);
fileName := in_argv[1].asJSString.ToString(cx);
resPath := RelToAbs(baseDir, fileName);
vp.rval := cx.NewJSString(StringToSynUnicode(resPath)).ToJSVal;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// return object{
// atime: Date, //access time
// mtime: Date, //modify time
// ctime: Date // create time
// size: Number
// }
// or null is file does not exist
function fs_fileStat(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
USAGE = 'usage: fileStat(filePath: string;)';
var
in_argv: PjsvalVector;
fn: TFileName;
obj: PJSRootedObject;
val: jsval;
{$IFNDEF MSWINDOWS}
info: stat;
{$ELSE}
{$ifdef ISDELPHIXE2}
infoRec: TDateTimeInfoRec;
{$else}
fad: TWin32FileAttributeData;
function fileTimeToDateTime(ft: TFileTime): TDateTime;
var ST,LT: TSystemTime;
begin
if FileTimeToSystemTime(ft,ST) and
SystemTimeToTzSpecificLocalTime(nil,ST,LT) then
result := SystemTimeToDateTime(LT) else
result := 0;
end;
{$endif}
{$ENDIF}
begin
try
in_argv := vp.argv;
if (argc < 1) or not in_argv[0].isString then
raise ESMException.Create(USAGE);
fn := in_argv[0].asJSString.ToString(cx);
obj := cx.NewRootedObject(cx.NewObject(nil));
try
{$IFNDEF MSWINDOWS}
if fpstat(PChar(fn), info) = 0 then begin
val.asDate[cx] := UnixToDateTime(info.st_atime);
obj.ptr.DefineProperty(cx, 'atime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
val.asDate[cx] := UnixToDateTime(info.st_mtime);
obj.ptr.DefineProperty(cx, 'mtime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
val.asDate[cx] := UnixToDateTime(info.st_ctime);
obj.ptr.DefineProperty(cx, 'ctime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
val.asInt64 := info.st_size;
obj.ptr.DefineProperty(cx, 'size', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
vp.rval := obj.ptr.ToJSValue;
end else begin
{$ifdef SM_DEBUG}
SynSMLog.Add.Log(sllDebug, 'fstat(%) failed with error %',
[fn, SysErrorMessage(errno)]);
{$endif}
vp.rval := JSVAL_NULL;
end;
{$ELSE}
{$ifdef ISDELPHIXE2}
if FileGetDateTimeInfo(fn, infoRec, true) then begin
val.asDate[cx] := infoRec.LastAccessTime;
obj.ptr.DefineProperty(cx, 'atime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
val.asDate[cx] := infoRec.TimeStamp;
obj.ptr.DefineProperty(cx, 'mtime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
val.asDate[cx] := infoRec.CreationTime;
obj.ptr.DefineProperty(cx, 'ctime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
if TWin32FindData(infoRec).nFileSizeHigh > 0 then
val := JSVAL_NAN
else
val.asInt64 := TWin32FindData(infoRec).nFileSizeLow;
obj.ptr.DefineProperty(cx, 'size', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
vp.rval := obj.ptr.ToJSValue;
end else
vp.rval := JSVAL_NULL;
{$else ISDELPHIXE2}
if GetFileAttributesEx(PChar(fn), GetFileExInfoStandard, @fad) then begin
val.asDate[cx] := fileTimeToDateTime(fad.ftLastAccessTime);
obj.ptr.DefineProperty(cx, 'atime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
val.asDate[cx] := fileTimeToDateTime(fad.ftLastWriteTime);
obj.ptr.DefineProperty(cx, 'mtime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
val.asDate[cx] := fileTimeToDateTime(fad.ftCreationTime);
obj.ptr.DefineProperty(cx, 'ctime', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
if fad.nFileSizeHigh > 0 then
val := JSVAL_NAN
else
val.asInt64 := fad.nFileSizeLow;
obj.ptr.DefineProperty(cx, 'size', val, JSPROP_ENUMERATE or JSPROP_READONLY, nil, nil);
vp.rval := obj.ptr.ToJSValue;
end else
vp.rval := JSVAL_NULL;
{$endif ISDELPHIXE2}
{$ENDIF MSWINDOWS}
finally
cx.FreeRootedObject(obj);
end;
Result := True;
except
on E: Exception do begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// check directory exist
// accept one parameter - "path to dir"
function fs_directoryExists(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
USAGE = 'usage: directoryExists(dirPath: string;)';
var
in_argv: PjsvalVector;
filePath: TFileName;
val: jsval;
begin
try
in_argv := vp.argv;
if (argc < 1) or not in_argv[0].isString then
raise ESMException.Create(USAGE);
filePath := in_argv[0].asJSString.ToString(cx);
val.asBoolean := DirectoryExists(filePath);
vp.rval := val;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// check file exist
// accept one parameter - "path to file"
function fs_fileExists(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
USAGE = 'usage: fileExists(filePath: string;)';
var
in_argv: PjsvalVector;
filePath: TFileName;
val: jsval;
begin
try
in_argv := vp.argv;
if (argc < 1) or not in_argv[0].isString then
raise ESMException.Create(USAGE);
filePath := in_argv[0].asJSString.ToString(cx);
val.asBoolean := FileExists(filePath);
vp.rval := val;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// Used to speed up module loading. Returns the contents of the file as
// a string or undefined when the file cannot be opened. The speedup
// comes from not creating Error objects on failure.
function fs_internalModuleReadFile(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
USAGE = 'usage: internalModuleReadFile(filePath: string;)';
var
in_argv: PjsvalVector;
filePath: TFileName;
fileContent: jsval;
{$ifdef SM_DEBUG_FSDUMPFILEEXCERPT}
len: size_t;
content: RawUTF8;
{$endif}
begin
try
in_argv := vp.argv;
if (argc < 1) or not in_argv[0].isString then
raise ESMException.Create(USAGE);
filePath := in_argv[0].asJSString.ToString(cx);
{$ifdef SM_DEBUG_FSTRACEFILEREAD}
SynSMLog.Add.Log(sllDebug, 'fs_internalModuleReadFile (%) called', filePath);
{$ENDIF}
if FileExists(filePath) then begin
fileContent := cx.NewJSString(AnyTextFileToRawUTF8(filePath, true)).ToJSVal;
vp.rval := fileContent;
{$ifdef SM_DEBUG_FSDUMPFILEEXCERPT}
len := fileContent.asJSString.Length;
content := fileContent.asJSString.ToUTF8(cx);
if len > 120 then
content := Format('%s .. %s', [Copy(content, 1, 58), Copy(content, len-58, 58)]);
SynSMLog.Add.Log(sllDebug,
'fs_internalModuleReadFile (%): content loaded, length = % content:'#13'%',
[filePath, len, content]);
{$endif}
end else begin
vp.rval := JSVAL_VOID;
{$ifdef SM_DEBUG}
SynSMLog.Add.Log(sllDebug,
'fs_internalModuleReadFile (%): file not found', StringToUTF8(filePath));
{$endif}
end;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// Used to speed up module loading. Returns 0 if the path refers to
// a file, 1 when it's a directory or < 0 on error (usually -ENOENT.)
// The speedup comes from not creating thousands of Stat and Error objects.
function fs_internalModuleStat(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
USAGE = 'usage: internalModuleStat(filePath: string;)';
var
in_argv: PjsvalVector;
fn: TFileName;
Code: Integer;
val: jsval;
begin
try
in_argv := vp.argv;
if (argc < 1) or not in_argv[0].isString then
raise ESMException.Create(USAGE);
try
fn := in_argv[0].asJSString.ToString(cx);
except
raise
end;
{$IFDEF MSWINDOWS}
Code := GetFileAttributes(PChar(fn));
if Code <> -1 then
if Code and FILE_ATTRIBUTE_DIRECTORY = 0 then
Code := 0
else
Code := 1;
{$ELSE} // this can be used in Windows too
Code := FileGetAttr(fn);
if Code <> -1 then
if Code and faDirectory = 0 then
Code := 0
else
Code := 1;
{$ENDIF}
val.asInteger := Code;
vp.rval := val;
Result := True;
except
on E: Exception do begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// read content of directory. return array of file names witout started from dot '.'
/// in case of includeDirNames - return sub direcrory with trailing slash
function fs_readDir(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
dir: TFileName;
F: TSearchRec;
res: PJSRootedObject;
cNum, searchAttr: integer;
includeFolders: boolean;
const
USAGE = 'usage: readDir(dirPath: String; [includeFolders: boolean = false]): Array';
begin
try
in_argv := vp.argv;
if (argc < 1) or not in_argv[0].isString then
raise ESMException.Create(USAGE);
if (argc = 2) and in_argv[1].isBoolean then
includeFolders := in_argv[1].asBoolean
else
includeFolders := false;
dir := in_argv[0].asJSString.ToString(cx);
if not DirectoryExists(Dir) then
begin
vp.rval := JSVAL_NULL;
Result := true;
Exit;
end;
Dir := IncludeTrailingPathDelimiter(Dir);
res := cx.NewRootedObject(cx.NewArrayObject(0));
try
{$WARN SYMBOL_PLATFORM OFF}
searchAttr := faAnyFile;
if not includeFolders then
searchAttr := searchAttr - faDirectory;
if FindFirst(Dir + '*', searchAttr, F) = 0 then begin
cNum := 0;
repeat
if (F.Name <> '.') and (F.Name <> '..') then begin
res.ptr.SetElement(cx, cNum, cx.NewJSString(F.Name).ToJSVal);
inc(cNum);
end;
until FindNext(F) <> 0;
end;
{$WARN SYMBOL_PLATFORM ON}
{$ifdef ISDELPHIXE2}System.{$ENDIF}SysUtils.FindClose(F);
vp.rval := res.ptr.ToJSValue;
finally
cx.FreeRootedObject(res);
end;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
{$ifdef MSWINDOWS}
{$if defined(FPC) or not defined(ISDELPHIXE2)}
const VOLUME_NAME_DOS = $0;
// Only Wide version return a value
function GetFinalPathNameByHandleW(hFile: THandle; lpszFilePath: LPWSTR;
cchFilePath: DWORD; dwFlags: DWORD): DWORD; stdcall; external kernel32;
{$ifend}
const LONG_PATH_PREFIX: PChar = '\\?\';
const UNC_PATH_PREFIX: PChar = '\\?\UNC\';
{$else}
function realpath(name: PChar; resolved: PChar): PChar; cdecl; external 'c' name 'realpath';
{$endif}
/// Implementation of realpath as in libuv
function os_realpath(const FileName: TFileName; var TargetName: TFileName): Boolean;
{$ifdef MSWINDOWS}
{$WARN SYMBOL_PLATFORM OFF}
var
Handle: THandle;
w_realpath_len: Cardinal;
Res: WideString;
begin
Result := False;
if not CheckWin32Version(6, 0) then
exit;
Handle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_BACKUP_SEMANTICS, 0);
if Handle = INVALID_HANDLE_VALUE then
exit;
try
w_realpath_len := GetFinalPathNameByHandleW(Handle, nil, 0, VOLUME_NAME_DOS);
if (w_realpath_len = 0) then
exit;
SetLength(Res, w_realpath_len);
if GetFinalPathNameByHandleW(Handle, PWideChar(Res), w_realpath_len, VOLUME_NAME_DOS) > 0 then begin
WideCharToStrVar(PWideChar(Res), String(TargetName));
if StrLComp(PChar(TargetName), UNC_PATH_PREFIX, length(UNC_PATH_PREFIX)) = 0 then begin
// convert \\?\UNC\host\folder -> \\host\folder
TargetName := Copy(TargetName, 7, length(TargetName)-7);
TargetName[1] := '\';
end else if StrLComp(PChar(TargetName), LONG_PATH_PREFIX, length(LONG_PATH_PREFIX)) = 0 then
TargetName := Copy(TargetName, length(LONG_PATH_PREFIX)+1, length(TargetName)-length(LONG_PATH_PREFIX))
else
Exit; // error
Result := True;
end;
finally
CloseHandle(Handle);
end;
end;
{$WARN SYMBOL_PLATFORM ON}
{$else}
var
Buf: TFileName;
begin
SetLength(Buf, PATH_MAX);
Result := realpath(PChar(FileName), PChar(Buf)) <> nil;
if Result then
TargetName := PChar(Buf)
else
TargetName := '';
end;
{$endif}
/// return the canonicalized absolute pathname
// expands all symbolic links and resolves references to /./,
// /../ and extra '/' characters in the string named by path
// to produce a canonicalized absolute pathname
function fs_realPath(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
dir, target: TFileName;
const
USAGE = 'usage: realpath(dirPath: String): string';
begin
try
if (argc < 1) or not vp.argv[0].isString then
raise ESMException.Create(USAGE);
dir := vp.argv[0].asJSString.ToString(cx);
//if not FileGetSymLinkTarget(dir, target) then
if not os_realpath(dir, target) then
target := dir;
vp.rval := cx.NewJSString(target).ToJSVal;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function fs_rename(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
USAGE = 'usage: rename(fromPath, toPath: String)';
var
fromPath, toPath: TFileName;
//f : file;
begin
try
if (argc <> 2) or not vp.argv[0].isString or not vp.argv[1].isString then
raise ESMException.Create(USAGE);
fromPath := vp.argv[0].asJSString.ToString(cx);
toPath := vp.argv[1].asJSString.ToString(cx);
{$IFDEF MSWINDOWS} // libc rename implementation rewrites destination if it's already exist
if FileExists(toPath) then
if not SysUtils.DeleteFile(toPath) then
RaiseLastOSError;
{$ENDIF}
if not SysUtils.RenameFile(fromPath, toPath) then
RaiseLastOSError;
Result := True;
except
on E: Exception do begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// Create UInt8Array & load file into it
function fs_loadFileToBuffer(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
filePath: string;
size: Int64;
arr_data: Puint8Vector;
fFileStream: TFileStream;
arr: PJSObject;
val: jsval;
begin
try
if (argc <> 1) then
raise ESMException.Create('usage loadFileToBuffer(pathToFile: String): ArrayBuffer');
in_argv := vp.argv;
filePath := in_argv[0].asJSString.ToSynUnicode(cx);
{$IFNDEF FPC}
if IsRelativePath(filePath) then
{$ELSE}
if not FilenameIsAbsolute(filePath) then
{$ENDIF}
raise ESMException.Create('no relative path allowed in loadFile');
if not FileExists(filePath) then
begin
vp.rval := JSVAL_NULL;
end
else
begin
size := FileSize(filePath);
if PInt64Rec(@size)^.Hi > 0 then
raise ESMException.Create('file to large');
arr := cx.NewArrayBuffer(PInt64Rec(@size)^.Lo);
arr_data := arr.GetArrayBufferData;
fFileStream := TFileStream.Create(filePath, fmOpenRead or fmShareDenyNone);
try
fFileStream.Read(arr_data[0], size);
finally
fFileStream.Free;
end;
val.asObject := arr;
vp.rval := val;
end;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// write Object to file using specifien encoding.
// internaly use SyNodeReadWrite.write
function fs_writeFile(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
filePath: string;
stream: TFileStream;
writer: SynCommons.TTextWriter;
begin
try
if (argc < 2) then
raise ESMException.Create('usage writeFile(filePath: String, fileContent: String|Object|ArrayBuffer [,encoding]');
in_argv := vp.argv;
filePath := in_argv[0].asJSString.ToSynUnicode(cx);
stream := TFileStream.Create(filePath, fmCreate);
writer := SynCommons.TTextWriter.Create(stream, 65536);
try
vp.rval := SyNodeReadWrite.SMWrite_impl(cx, argc - 1, @in_argv[1], writer);
result := True;
finally
writer.FlushFinal;
writer.Free;
stream.Free;
end;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// append Object to file using specifien encoding.
// internaly use SyNodeReadWrite.write
function fs_appendFile(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
filePath: string;
FFile: THandle;
stream: TFileStream;
writer: SynCommons.TTextWriter;
begin
try
if (argc < 2) then
raise ESMException.Create('usage appendFile(filePath: String, fileContent: String|Object|ArrayBuffer [,encoding]');
in_argv := vp.argv;
filePath := in_argv[0].asJSString.ToSynUnicode(cx);
if not FileExists(filePath) then begin
FFile := FileCreate(filePath);
if PtrInt(FFile) > 0 then
FileClose(FFile);
end;
stream := TFileStream.Create(filePath, fmOpenReadWrite);
stream.Seek(0, soFromEnd);
writer := SynCommons.TTextWriter.Create(stream, 65536);
try
vp.rval := SyNodeReadWrite.SMWrite_impl(cx, argc - 1, @in_argv[1], writer);
result := True;
finally
writer.FlushFinal;
writer.Free;
stream.Free;
end;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// delete file AFileName: TFileName - full file path
function fs_deleteFile(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
filePath: string;
val: jsval;
begin
try
if (argc <> 1) then
raise ESMException.Create('Invalid number of args for function nsm_deleteFile. Requied 1 - filePath');
in_argv := vp.argv;
filePath := in_argv[0].asJSString.ToSynUnicode(cx);
if SysUtils.DeleteFile(filePath) then
val.asBoolean := True
else if fileExists(filePath) then
raise Exception.Create('can''t delete file')
else
val.asBoolean := True;
vp.rval := val;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// the same as SysUtils.ForceDirectories
function fs_forceDirectories(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
dir: TFileName;
val: jsval;
const
f_usage = 'usage: forceDirectories(dirPath: String): Boolean';
f_invalidPath = 'empty or relative path is not allowed';
begin
try
in_argv := vp.argv;
if (argc <> 1) or not in_argv[0].isString then
raise ESMException.Create(f_usage);
dir := in_argv[0].asJSString.ToSynUnicode(cx);
if (dir = '') or {$IFNDEF FPC}IsRelativePath(dir){$ELSE}not FilenameIsAbsolute(dir){$ENDIF} then
raise ESMException.Create(f_invalidPath);
val.asBoolean := ForceDirectories(dir);
vp.rval := val;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function fs_removeDir(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
dir: TFileName;
val: jsval;
const
f_usage = 'usage: removeDir(dirPath: String): Boolean';
f_invalidPath = 'empty or relative path is not allowed';
begin
try
in_argv := vp.argv;
if (argc <> 1) or not in_argv[0].isString then
raise ESMException.Create(f_usage);
dir := in_argv[0].asJSString.ToSynUnicode(cx);
if (dir = '') or {$IFNDEF FPC}IsRelativePath(dir){$ELSE}not FilenameIsAbsolute(dir){$ENDIF} then
raise ESMException.Create(f_invalidPath);
val.asBoolean := RemoveDir(dir);
vp.rval := val;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function SyNodeBindingProc_fs(const Engine: TSMEngine;
const bindingNamespaceName: SynUnicode): jsval;
const
attrs = JSPROP_READONLY or JSPROP_PERMANENT;
var
obj: PJSRootedObject;
cx: PJSContext;
begin
cx := Engine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
try
obj.ptr.DefineFunction(cx, 'loadFile', fs_loadFile, 1, attrs);
obj.ptr.DefineFunction(cx, 'relToAbs', fs_relToAbs, 2, attrs);
obj.ptr.DefineFunction(cx, 'fileStat', fs_fileStat, 1, attrs);
obj.ptr.DefineFunction(cx, 'directoryExists', fs_directoryExists, 1, attrs);
obj.ptr.DefineFunction(cx, 'fileExists', fs_fileExists, 1, attrs);
obj.ptr.DefineFunction(cx, 'internalModuleReadFile', fs_internalModuleReadFile, 1, attrs);
obj.ptr.DefineFunction(cx, 'internalModuleStat', fs_internalModuleStat, 1, attrs);
obj.ptr.DefineFunction(cx, 'readDir', fs_readDir, 2, attrs);
obj.ptr.DefineFunction(cx, 'realpath', fs_realPath, 1, attrs);
obj.ptr.DefineFunction(cx, 'rename', fs_rename, 2, attrs);
obj.ptr.DefineFunction(cx, 'loadFileToBuffer', fs_loadFileToBuffer, 1, attrs);
obj.ptr.DefineFunction(cx, 'writeFile', fs_writeFile, 3, attrs);
obj.ptr.DefineFunction(cx, 'appendFile', fs_appendFile, 3, attrs);
obj.ptr.DefineFunction(cx, 'forceDirectories', fs_forceDirectories, 1, attrs);
obj.ptr.DefineFunction(cx, 'removeDir', fs_removeDir, 1, attrs);
obj.ptr.DefineFunction(cx, 'deleteFile', fs_deleteFile, 1, attrs);
Result := obj.ptr.ToJSValue;
finally
cx.FreeRootedObject(obj);
end;
end;
initialization
TSMEngineManager.RegisterBinding('fs', SyNodeBindingProc_fs);
end.

View File

@@ -0,0 +1,174 @@
/// `util` module support bindings for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeBinding_util;
interface
{$I Synopse.inc}
{$I SyNode.inc}
uses
SysUtils,
SynCommons,
SyNode, SpiderMonkey;
function HasInstance(cx: PJSContext; argc: uintN; var vp: jsargRec; proto: JSProtoKey): Boolean; {$IFNDEF FPC}{$ifdef HASINLINE}inline;{$endif}{$ENDIF}// << this couses internal error on FPC 3.0.2
implementation
///This is dirty hack using Spider Monkey internals
function HasInstance(cx: PJSContext; argc: uintN; var vp: jsargRec; proto: JSProtoKey): Boolean; {$IFNDEF FPC}{$ifdef HASINLINE}inline;{$endif}{$ENDIF}// << this couses internal error on FPC 3.0.2
var
in_argv: PjsvalVector;
slotIndex: uint32;
const
sInvalidCall = 'One argunent required';
begin
Result := False;
try
// {$IFNDEF SM45}
// raise ENotImplemented.Create('Check that slots order is correct and you can continue using this internal');
// {$ENDIF}
in_argv := vp.argv;
if (argc <> 1) then
raise ESMException.Create(sInvalidCall);
slotIndex := JSCLASS_GLOBAL_APPLICATION_SLOTS + Ord(proto);
vp.rval := SimpleVariantToJSval(cx, cx.CurrentGlobalOrNull.ReservedSlot[slotIndex].asObject.HasInstance(cx, in_argv[0]));
Result := True;
except
on E: Exception do begin
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function isArrayBuffer(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := HasInstance(cx, argc, vp, JSProto_ArrayBuffer);
end;
function isDataView(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := HasInstance(cx, argc, vp, JSProto_DataView);
end;
function isDate(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := HasInstance(cx, argc, vp, JSProto_Date);
end;
function isMap(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := HasInstance(cx, argc, vp, JSProto_Map);
end;
///This is dirty hack using Spider Monkey internals
function isMapIterator(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
sInvalidCall = 'One argunent required';
begin
Result := False;
try
// {$IFNDEF SM45}
// raise ENotImplemented.Create('Check that Class_ name is correct and you can continue using this internal');
// {$ENDIF}
if (argc <> 1) then
raise ESMException.Create(sInvalidCall);
vp.rval := SimpleVariantToJSval(cx,
vp.argv[0].isObject and (vp.argv[0].asObject.Class_.name = 'Map Iterator')
);
Result := True;
except
on E: Exception do begin
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// SyNode not use prommise yet
function isPromise(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := True;
vp.rval := SimpleVariantToJSval(cx,false);
end;
function isRegExp(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := HasInstance(cx, argc, vp, JSProto_RegExp);
end;
function isTypedArray(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := HasInstance(cx, argc, vp, JSProto_Set);
end;
///This is dirty hack using Spider Monkey internals
function isSetIterator(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const
sInvalidCall = 'One argunent required';
begin
Result := False;
try
// {$IFNDEF SM45}
// raise ENotImplemented.Create('Check that Class_ name is correct and you can continue using this internal');
// {$ENDIF}
if (argc <> 1) then
raise ESMException.Create(sInvalidCall);
vp.rval := SimpleVariantToJSval(cx,
vp.argv[0].isObject and (vp.argv[0].asObject.Class_.name = 'Set Iterator')
);
Result := True;
except
on E: Exception do begin
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function isSet(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
Result := HasInstance(cx, argc, vp, JSProto_TypedArray);
end;
function SyNodeBindingProc_util(const aEngine: TSMEngine; const bindingNamespaceName: SynUnicode): jsval;
var
obj: PJSRootedObject;
cx: PJSContext;
const
props = JSPROP_ENUMERATE or JSPROP_ENUMERATE or JSPROP_PERMANENT;
begin
cx := aEngine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
try
obj.ptr.DefineFunction(cx, 'isArrayBuffer', isArrayBuffer, 1, props);
obj.ptr.DefineFunction(cx, 'isDataView', isDataView, 1, props);
obj.ptr.DefineFunction(cx, 'isDate', isDate, 1, props);
obj.ptr.DefineFunction(cx, 'isMap', isMap, 1, props);
obj.ptr.DefineFunction(cx, 'isMapIterator', isMapIterator, 1, props);
obj.ptr.DefineFunction(cx, 'isPromise', isPromise, 1, props);
obj.ptr.DefineFunction(cx, 'isRegExp', isRegExp, 1, props);
obj.ptr.DefineFunction(cx, 'isSet', isArrayBuffer, 1, props);
obj.ptr.DefineFunction(cx, 'isSetIterator', isSetIterator, 1, props);
obj.ptr.DefineFunction(cx, 'isTypedArray', isTypedArray, 1, props);
// todo
// obj.ptr.DefineFunction(cx, 'getHiddenValue', isArrayBuffer, 1, props);
// obj.ptr.DefineFunction(cx, 'setHiddenValue', isArrayBuffer, 1, props);
// obj.ptr.DefineFunction(cx, 'getProxyDetails', isArrayBuffer, 1, props);
//
// obj.ptr.DefineFunction(cx, 'startSigintWatchdog', isArrayBuffer, 1, props);
// obj.ptr.DefineFunction(cx, 'stopSigintWatchdog', isArrayBuffer, 1, props);
// obj.ptr.DefineFunction(cx, 'watchdogHasPendingSigint', isArrayBuffer, 1, props);
Result := obj.ptr.ToJSValue;
finally
cx.FreeRootedObject(obj);
end;
end;
initialization
TSMEngineManager.RegisterBinding('util', SyNodeBindingProc_util);
end.

View File

@@ -0,0 +1,67 @@
/// `uv` module support bindings for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeBinding_uv;
interface
{$I Synopse.inc}
{$I SyNode.inc}
implementation
uses
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
SynCommons,
SpiderMonkey,
SyNode;
//See https://github.com/libuv/libuv#documentation for documentation
// todo: normal realization
function errname(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
err: Int32;
const
sInvalidCall = 'usage: errname(err: Integer);';
begin
try
Result := True;
in_argv := vp.argv;
if (argc < 1) or (in_argv[0].isInteger) then
raise ESMException.Create(sInvalidCall);
err := in_argv[0].asInteger;
if err >= 0 then
raise ESMException.Create('err >= 0');
//todo uv_err_name(err)
vp.rval := cx.NewJSString('Fake').ToJSVal;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function SyNodeBindingProc_uv(const Engine: TSMEngine;
const bindingNamespaceName: SynUnicode): jsval;
var
obj: PJSRootedObject;
cx: PJSContext;
begin
cx := Engine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
try
obj.ptr.DefineFunction(cx, 'errname', errname, 1, JSPROP_READONLY or JSPROP_PERMANENT);
//todo
// obj.ptr.DefineProperty(cx, 'UV_err', SimpleVariantToJSval(cx, UV_err), JSPROP_READONLY or JSPROP_PERMANENT);
Result := obj.ptr.ToJSValue;
finally
cx.FreeRootedObject(obj);
end;
end;
initialization
TSMEngineManager.RegisterBinding('uv', SyNodeBindingProc_uv);
end.

View File

@@ -0,0 +1,701 @@
/// `worker` module support bindings for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeBinding_worker;
interface
{$I SyNode.inc}
uses
SynCommons,
Classes,
SpiderMonkey;
type
TJSWorkersManager = class
private
FPool: TObjectListLocked;
FWorkerCounter: integer;
FIsInDestroyState: Boolean;
{$IFDEF SM52}
{$ELSE}
function getOldErrorReporterForCurrentThread: JSErrorReporter;
{$ENDIF}
function getCurrentWorkerThreadIndex: integer;
function getCurrentWorkerID: integer;
function getWorkerThreadIndexByID(ID: Integer): integer;
function GetDoInteruptInOwnThreadhandlerForCurThread: TThreadMethod;
public
constructor Create;
destructor Destroy; override;
function Add(cx: PJSContext; const workerName: RawUTF8; const script: SynUnicode): Integer;
function curThreadIsWorker: Boolean;
function getCurrentWorkerThreadName: RawUTF8;
procedure EnqueueOutMessageToCurrentThread(const mess: RawUTF8);
function DequeueOutMessageFromThread(aID: Integer; out mess: RawUTF8): Boolean;
procedure EnqueueInMessageToThread(const mess: RawUTF8; aID: Integer);
function DequeueInMessageFromCurrentThread(out mess: RawUTF8): Boolean;
procedure TerminateThread(aID: Integer; aNeedCancelExecution: Boolean = false);
{$IFDEF SM52}
{$ELSE}
property OldErrorReporterForCurrentThread: JSErrorReporter read getOldErrorReporterForCurrentThread;
{$ENDIF}
property DoInteruptInOwnThreadhandlerForCurThread: TThreadMethod read GetDoInteruptInOwnThreadhandlerForCurThread;
end;
implementation
uses
SyNode,
{$IFDEF MSWINDOWS}
Windows,
{$ENDIF}
SysUtils;
type
TJSWorkerThread = class(TThread)
private
FEng: TSMEngine;
fSMManager: TSMEngineManager;
fModuleObj: PJSRootedObject;
FNeedCallInterrupt: boolean;
fInMessages: TRawUTF8List;
fOutMessages: TRawUTF8List;
{$IFDEF SM52}
{$ELSE}
oldErrorReporter: JSErrorReporter;
{$ENDIF}
fID: Integer;
FName: RawUTF8;
FScript: SynUnicode;
protected
procedure Execute; override;
procedure doInteruptInOwnThread;
public
constructor Create(aSMManager: TSMEngineManager; workerName: RawUTF8; script: SynUnicode);
destructor Destroy; override;
end;
{$IFDEF SM52}
{$ELSE}
const
LAST_ERROR_PROP_NAME = '__workerLastError';
{$ENDIF}
constructor TJSWorkerThread.Create(aSMManager: TSMEngineManager; workerName: RawUTF8; script: SynUnicode);
begin
inherited Create(true);
fSMManager := aSMManager;
fID := InterlockedIncrement(fSMManager.WorkersManager.FWorkerCounter);
FName := workerName;
FScript := script;
fInMessages := TRawUTF8List.Create();
fOutMessages := TRawUTF8List.Create();
FEng := nil;
Suspended := False;
end;
destructor TJSWorkerThread.Destroy;
begin
FreeAndNil(fInMessages);
FreeAndNil(fOutMessages);
if not Terminated then begin
{$IFDEF SM52}
{$ELSE}
FEng.rt.ErrorReporter := oldErrorReporter;
{$ENDIF}
if FEng.cx.IsRunning then
FEng.CancelExecution(false);
end;
inherited;
end;
procedure TJSWorkerThread.doInteruptInOwnThread;
begin
FNeedCallInterrupt := true;
Suspended := false;
end;
/// Post message from worker thread. aviable only in worker thread
function fromWorker_postMessage(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
mes: RawUTF8;
const
sInvalidCall = 'usage: postMessage(message: *)';
begin
try
in_argv := vp.argv;
if (argc <> 1) then
raise ESMException.Create(sInvalidCall);
mes := in_argv[0].asJson[cx];
TSMEngine(cx.PrivateData).Manager.WorkersManager.EnqueueOutMessageToCurrentThread(mes);
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// Terminate worker from thread. aviable only in worker thread
function fromWorker_terminate(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
fManager: TJSWorkersManager;
begin
try
fManager := TSMEngine(cx.PrivateData).Manager.WorkersManager;
fManager.TerminateThread(fManager.getCurrentWorkerID);
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
{$IFDEF SM52}
{$ELSE}
procedure WorkerThreadErrorReporter(cx: PJSContext; _message: PCChar; report: PJSErrorReport); cdecl;
var
exc: jsval;
begin
if cx.GetPendingException(exc) then
cx.CurrentGlobalOrNull.SetProperty(cx, LAST_ERROR_PROP_NAME, exc);
if not TSMEngine(cx.PrivateData).Manager.WorkersManager.FIsInDestroyState then
TSMEngine(cx.PrivateData).Manager.WorkersManager.OldErrorReporterForCurrentThread(cx, _message, report);
end;
{$ENDIF}
procedure TJSWorkerThread.Execute;
var
isEmpty: Boolean;
mess: RawUTF8;
val, rval: jsval;
cx: PJSContext;
msg, exc: PJSRootedValue;
begin
FNeedCallInterrupt := false;
FEng := fSMManager.ThreadSafeEngine(nil);
try
cx := FEng.cx;
FEng.GlobalObject.ptr.DefineFunction(cx, 'postMessage', fromWorker_postMessage, 1, JSPROP_ENUMERATE or JSPROP_READONLY or JSPROP_PERMANENT);
FEng.GlobalObject.ptr.DefineFunction(cx, 'terminate', fromWorker_terminate, 0, JSPROP_ENUMERATE or JSPROP_READONLY or JSPROP_PERMANENT);
{$IFDEF SM52}
{$ELSE}
oldErrorReporter := FEng.rt.ErrorReporter;
FEng.rt.ErrorReporter := WorkerThreadErrorReporter;
{$ENDIF}
val.asInteger := ThreadID;
FEng.GlobalObject.ptr.defineProperty(cx, 'threadID', val);
try
FEng.Evaluate(FScript, '<initialization>', 0, rval);
if rval.isObject then
fModuleObj := cx.NewRootedObject(rval.asObject)
else begin
Terminate;
exit;
end;
except
Terminate;
exit;
end;
while not Terminated do begin
if FNeedCallInterrupt then begin
{$IFDEF SM52}
FEng.cx.RequestInterruptCallback;
FEng.cx.CheckForInterrupt;
{$ELSE}
FEng.rt.InterruptCallback(cx);
{$ENDIF}
FNeedCallInterrupt := false;
end;
isEmpty := fSMManager.WorkersManager.FIsInDestroyState or not fSMManager.WorkersManager.DequeueInMessageFromCurrentThread(mess);
if not isEmpty then begin
val.asJson[cx] := mess;
msg := cx.NewRootedValue(val);
try
try
if not Terminated then begin
{$IFNDEF SM52}
FEng.GlobalObject.ptr.SetProperty(cx, LAST_ERROR_PROP_NAME, JSVAL_VOID);
{$ENDIF}
FEng.CallObjectFunction(fModuleObj, 'onmessage', [msg.ptr]);
end;
except
{$IFDEF SM52}
if not (JS_IsExceptionPending(cx) and JS_GetPendingException(cx, val)) then begin
val.setVoid;
end;
exc := cx.NewRootedValue(val);
{$ELSE}
exc := cx.NewRootedValue(FEng.GlobalObject.ptr.GetPropValue(cx, LAST_ERROR_PROP_NAME));
{$ENDIF}
try
if not fSMManager.WorkersManager.FIsInDestroyState and not exc.ptr.isVoid then begin
try
FEng.CallObjectFunction(fModuleObj, 'onerror', [msg.ptr, exc.ptr]);
except
end;
end;
finally
cx.FreeRootedValue(exc);
end;
end;
finally
cx.FreeRootedValue(msg);
end;
end else if not Terminated and not FNeedCallInterrupt then
Suspended := True;
end;
try
if not fSMManager.WorkersManager.FIsInDestroyState
and fModuleObj.ptr.HasProperty(cx, 'onterminate') then
FEng.CallObjectFunction(fModuleObj, 'onterminate', []);
except
;
end;
finally
if fModuleObj <> nil then
cx.FreeRootedObject(fModuleObj);
fSMManager.ReleaseCurrentThreadEngine;
FEng := nil;
end;
end;
{ TJSWorkersManager }
function TJSWorkersManager.Add(cx: PJSContext; const workerName: RawUTF8; const script: SynUnicode): Integer;
var
thread: TJSWorkerThread;
i: Integer;
begin
FPool.Safe.Lock;
try
// delete unused threads
for i := FPool.Count - 1 downto 0 do begin
thread := TJSWorkerThread(FPool[i]);
if thread.Terminated and
(thread.fOutMessages.Count = 0) then
FPool.Delete(i);
end;
thread := TJSWorkerThread.Create(TSMEngine(cx.PrivateData).Manager, workerName, script);
FPool.Add(thread);
Result := thread.fID;
finally
FPool.Safe.UnLock;
end;
end;
constructor TJSWorkersManager.Create;
begin
FWorkerCounter := 0;
FIsInDestroyState := false;
FPool := TObjectListLocked.Create();
end;
function TJSWorkersManager.curThreadIsWorker: Boolean;
begin
FPool.Safe.Lock;
try
Result := getCurrentWorkerThreadIndex <> -1;
finally
FPool.Safe.UnLock;
end;
end;
function TJSWorkersManager.DequeueInMessageFromCurrentThread(out mess: RawUTF8): Boolean;
var
curThreadIndex: Integer;
begin
Result := false;
FPool.Safe.Lock;
try
curThreadIndex := getCurrentWorkerThreadIndex;
if curThreadIndex <> -1 then begin
Result := TJSWorkerThread(FPool[curThreadIndex]).fInMessages.PopFirst(mess);
end;
finally
FPool.Safe.UnLock;
end;
end;
function TJSWorkersManager.DequeueOutMessageFromThread(aID: Integer;
out mess: RawUTF8): Boolean;
var
ThreadIndex: Integer;
thread: TJSWorkerThread;
begin
Result := false;
FPool.Safe.Lock;
try
ThreadIndex := getWorkerThreadIndexByID(aID);
if ThreadIndex <> -1 then begin
thread := TJSWorkerThread(FPool[ThreadIndex]);
Result := thread.fOutMessages.PopFirst(mess);
if thread.Terminated and
(thread.fOutMessages.Count = 0) then
FPool.Delete(ThreadIndex);
end;
finally
FPool.Safe.UnLock;
end;
end;
destructor TJSWorkersManager.Destroy;
var i: Integer;
begin
FIsInDestroyState := True;
FPool.Safe.Lock;
try
for i := 0 to FPool.Count - 1 do begin
TerminateThread(TJSWorkerThread(FPool[i]).FID, True);
end;
finally
FPool.Safe.UnLock;
end;
FreeAndNil(FPool);
inherited;
end;
procedure TJSWorkersManager.EnqueueInMessageToThread(const mess: RawUTF8;
aID: Integer);
var
ThreadIndex: Integer;
thread: TJSWorkerThread;
begin
FPool.Safe.Lock;
try
ThreadIndex := getWorkerThreadIndexByID(aID);
if ThreadIndex <> -1 then begin
thread := TJSWorkerThread(FPool[ThreadIndex]);
if not thread.Terminated then begin
thread.fInMessages.Add(mess);
thread.Suspended := False;
end;
end;
finally
FPool.Safe.UnLock;
end;
end;
procedure TJSWorkersManager.EnqueueOutMessageToCurrentThread(
const mess: RawUTF8);
var
curThreadIndex: Integer;
begin
FPool.Safe.Lock;
try
curThreadIndex := getCurrentWorkerThreadIndex;
if curThreadIndex <> -1 then begin
TJSWorkerThread(FPool[curThreadIndex]).fOutMessages.Add(mess);
end;
finally
FPool.Safe.UnLock;
end;
end;
function TJSWorkersManager.getCurrentWorkerID: integer;
var
i: Integer;
curThreadID: TThreadID;
begin
Result := -1;
curThreadID := GetCurrentThreadId;
for i := 0 to FPool.Count - 1 do begin
if TJSWorkerThread(FPool[i]).ThreadID = curThreadID then begin
Result := TJSWorkerThread(FPool[i]).fID;
Exit;
end;
end;
end;
function TJSWorkersManager.getCurrentWorkerThreadIndex: integer;
var
i: Integer;
curThreadID: TThreadID;
begin
Result := -1;
curThreadID := GetCurrentThreadId;
for i := 0 to FPool.Count - 1 do begin
if TJSWorkerThread(FPool[i]).ThreadID = curThreadID then begin
Result := i;
Exit;
end;
end;
end;
function TJSWorkersManager.getCurrentWorkerThreadName: RawUTF8;
var
curThreadIndex: Integer;
begin
Result := '';
FPool.Safe.Lock;
try
curThreadIndex := getCurrentWorkerThreadIndex;
if curThreadIndex <> -1 then
Result := TJSWorkerThread(FPool[curThreadIndex]).FName;
finally
FPool.Safe.UnLock;
end;
end;
{$IFDEF SM52}
{$ELSE}
function TJSWorkersManager.getOldErrorReporterForCurrentThread: JSErrorReporter;
var
curThreadIndex: Integer;
begin
Result := nil;
if FIsInDestroyState then begin
Result := nil;
Exit;
end;
FPool.Safe.Lock;
try
curThreadIndex := getCurrentWorkerThreadIndex;
if curThreadIndex <> - 1 then
Result := TJSWorkerThread(FPool[curThreadIndex]).oldErrorReporter;
finally
FPool.Safe.UnLock;
end;
end;
{$ENDIF}
function TJSWorkersManager.getWorkerThreadIndexByID(ID: Integer): integer;
var
i: Integer;
begin
Result := -1;
for i := 0 to FPool.Count - 1 do begin
if TJSWorkerThread(FPool[i]).fID = ID then begin
Result := i;
Exit;
end;
end;
end;
function TJSWorkersManager.GetDoInteruptInOwnThreadhandlerForCurThread: TThreadMethod;
var
curThreadIndex: Integer;
begin
Result := nil;
FPool.Safe.Lock;
try
curThreadIndex := getCurrentWorkerThreadIndex;
if curThreadIndex <> -1 then
Result := TJSWorkerThread(FPool[curThreadIndex]).doInteruptInOwnThread;
finally
FPool.Safe.UnLock;
end;
end;
procedure TJSWorkersManager.TerminateThread(aID: Integer; aNeedCancelExecution: Boolean = false);
var
ThreadIndex: Integer;
thread: TJSWorkerThread;
begin
FPool.Safe.Lock;
try
ThreadIndex := getWorkerThreadIndexByID(aID);
if ThreadIndex <> -1 then begin
thread := TJSWorkerThread(FPool[ThreadIndex]);
if aNeedCancelExecution and (thread.FEng <> nil) then
thread.FEng.CancelExecution(False);
thread.Terminate;
thread.Suspended := False;
end;
finally
FPool.Safe.UnLock;
end;
end;
/// create new worker thread. return worker ID
// new thread require module. Module must export 3 function:
// - onmessage
// - onterminate
// - onerror
function worker_createThread(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
moduleStr: SynUnicode;
workerName: RawUTF8;
params: PJSObject;
IsInvalidCall: Boolean;
name, module: jsval;
val: jsval;
FEng: TSMEngine;
const
sInvalidCall = 'usage: createThread({name: String, moduleName: string[, message: *]})';
begin
try
in_argv := vp.argv;
IsInvalidCall := (argc < 1) and in_argv[0].isObject;
if not IsInvalidCall then begin
params := in_argv[0].asObject;
params.GetProperty(cx, 'name', name);
if name.isString then
workerName := name.asJSString.ToUTF8(cx)
else
IsInvalidCall := True;
if not IsInvalidCall then begin
params.GetProperty(cx, 'moduleName', module);
if not module.isString then
IsInvalidCall := True
else
moduleStr := module.asJSString.ToSynUnicode(cx)
end;
end;
if IsInvalidCall then
raise ESMException.Create(sInvalidCall);
FEng := TSMEngine(cx.PrivateData);
val.asInteger := FEng.Manager.WorkersManager.Add(cx, workerName,
'require("' + moduleStr + '")');
vp.rval := val;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// Post message to worker thread
function worker_postMessage(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
threadID: Integer;
mes: RawUTF8;
const
sInvalidCall = 'usage: postMessage(threadID: Number, message: String);';
begin
try
in_argv := vp.argv;
if (argc < 2) or not (in_argv[0].isNumber) or not (in_argv[1].isString) then
raise ESMException.Create(sInvalidCall);
if in_argv[0].isInteger then
threadID := in_argv[0].asInteger
else
threadID := Round(in_argv[0].asDouble);
mes := in_argv[1].asJSString.ToUTF8(cx);
TSMEngine(cx.PrivateData).Manager.WorkersManager.EnqueueInMessageToThread(mes, threadID);
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// Get message from worker thread
function worker_getMessage(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
threadID: Integer;
mes: RawUTF8;
const
sInvalidCall = 'usage: getMessage(threadID: Number);';
begin
try
in_argv := vp.argv;
if (argc < 1) or not (in_argv[0].isNumber) then
raise ESMException.Create(sInvalidCall);
if in_argv[0].isInteger then
threadID := in_argv[0].asInteger
else
threadID := Round(in_argv[0].asDouble);
if TSMEngine(cx.PrivateData).Manager.WorkersManager.DequeueOutMessageFromThread(threadID, mes) then
vp.rval := cx.NewJSString(mes).ToJSVal
else
vp.rval := JSVAL_VOID;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
/// Terminate worker thread.
function worker_terminate(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
in_argv: PjsvalVector;
threadID: Integer;
const
sInvalidCall = 'usage: terminate(threadID: Number);';
begin
try
in_argv := vp.argv;
if (argc < 1) or not (in_argv[0].isNumber) then
raise ESMException.Create(sInvalidCall);
if in_argv[0].isInteger then
threadID := in_argv[0].asInteger
else
threadID := Round(in_argv[0].asDouble);
TSMEngine(cx.PrivateData).Manager.WorkersManager.TerminateThread(threadID, true);
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function SyNodeBindingProc_worker(const aEngine: TSMEngine; const bindingNamespaceName: SynUnicode): jsval;
var
obj: PJSRootedObject;
cx: PJSContext;
const
props = JSPROP_ENUMERATE or JSPROP_ENUMERATE or JSPROP_PERMANENT;
begin
cx := aEngine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
obj.ptr.DefineFunction(cx, 'createThread', worker_createThread, 1, props);
obj.ptr.DefineFunction(cx, 'postMessage', worker_postMessage, 2, props);
obj.ptr.DefineFunction(cx, 'getMessage', worker_getMessage, 1, props);
obj.ptr.DefineFunction(cx, 'terminate', worker_terminate, 1, props);
Result := obj.ptr.ToJSValue;
cx.FreeRootedObject(obj);
end;
initialization
TSMEngineManager.RegisterBinding('worker', SyNodeBindingProc_worker);
end.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,202 @@
unit SyNodePluginIntf;
interface
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
{$I SyNode.inc} //define WITHASSERT
uses
SynCommons,
SpiderMonkey;
type
{$ifdef USERECORDWITHMETHODS}TSMPluginRec = record
{$else}TSMPluginRec = object{$endif}
cx: PJSContext;
Exp: PJSRootedObject;
Req: PJSRootedObject;
Module: PJSRootedObject;
filename: PWideChar;
dirname: PWideChar;
constructor Create(aCx: PJSContext; aExports_: PJSRootedObject; aRequire: PJSRootedObject; aModule: PJSRootedObject; _filename: PWideChar; _dirname: PWideChar);
function require(aFileName: string): PJSObject;
end;
TCustomSMPlugin = class
protected
fCx: PJSContext;
procedure UnInit; virtual;
procedure Init(const rec: TSMPluginRec); virtual;
public
constructor Create(aCx: PJSContext; aExports_: PJSRootedObject; aRequire: PJSRootedObject; aModule: PJSRootedObject; _filename: PWideChar; _dirname: PWideChar);
destructor Destroy; override;
end;
TThreadRec = record
threadID: TThreadID;
plugin: TCustomSMPlugin;
end;
TCustomSMPluginType = class of TCustomSMPlugin;
type
TNsmFunction = function(cx: PJSContext; argc: uintN; vals: PjsvalVector; thisObj, calleeObj: PJSObject): jsval; cdecl;
const
ptVoid = 0;
ptInt = 1;
ptStr = 2;
ptObj = 3;
ptBuffer = 100;
ptAny = 500;
type
PNSMCallInfo = ^TNSMCallInfo;
TNSMCallInfo = record
func: TNsmFunction;
argc: Integer;
argt: PInt64Array;
end;
TNSMCallInfoArray = array of TNSMCallInfo;
/// Helper to call a native function depending on JS function arguments.
// As a side effect will verify JS function arguments types. Can be used e.g. as:
// ! const
// ! overload1Args: array [0..0] of uintN = ( ptInt );
// ! overload2Args: array [0..1] of uintN = ( ptInt, SyNodePluginIntf.ptStr );
// ! overloads: array [0..1] of TNSMCallInfo = (
// ! (func: @SLReadLn_impl; argc: Length(overload1Args); argt: @overload1Args),
// ! (func: @SLReadLn_impl; argc: Length(overload2Args); argt: @overload2Args));
// ! begin
// ! Result := nsmCallFunc(cx, argc, vp, @overloads, Length(overloads));
// ! end;
function nsmCallFunc(cx: PJSContext; argc: uintN; var vp: JSArgRec; const overloads: TNSMCallInfoArray; overloadsCount: Integer = 1; isConstructor: Boolean = false): Boolean; cdecl;
const
StaticROAttrs = JSPROP_ENUMERATE or JSPROP_READONLY or JSPROP_PERMANENT;
implementation
uses
SysUtils;
function isParamCorrect(paramType: uintN; val: jsval): Boolean;
begin
case paramType of
ptVoid: result := val.isVoid;
ptInt: result := val.isInteger;
ptStr: result := val.isString;
ptObj: result := val.isObject;
ptBuffer: result := val.isObject and (val.asObject.IsArrayBufferObject or val.asObject.IsArrayBufferViewObject);
ptAny: result := True;
else result := false;
end;
end;
function nsmCallFunc(cx: PJSContext; argc: uintN; var vp: JSArgRec; const overloads: TNSMCallInfoArray; overloadsCount: Integer = 1; isConstructor: Boolean = false): Boolean; cdecl;
var
thisObj, calleeObj: PJSObject;
vals: PjsvalVector;
i,j: Integer;
overloadCase: PNSMCallInfo;
IsCalled, IsCorrect: Boolean;
begin
Result := False;
try
if isConstructor xor vp.IsConstructing then
raise ESMException.Create('JS_IS_CONSTRUCTING');
thisObj := vp.thisObject[cx];
calleeObj := vp.calleObject;
vals := vp.argv;
IsCalled := false;
for i := 0 to overloadsCount - 1 do begin
overloadCase := @overloads[i];
if (overloadCase <> nil) then begin
if argc = overloadCase.argc then begin
IsCorrect := true;
for j := 0 to overloadCase.argc - 1 do begin
IsCorrect := isParamCorrect(overloadCase.argt[j], vals[j]);
if not IsCorrect then Break;
end;
if IsCorrect then begin
vp.rval := overloadCase.func(cx, argc, vals, thisObj, calleeObj);
IsCalled := True;
Break;
end;
end;
end;
end;
if not IsCalled then
raise ESMException.CreateUTF8('There is no overloaded function "%" with such a list of arguments', [calleeObj.GetFunctionId().ToSynUnicode(cx)]);
Result := True;
except
on E: Exception do
begin
JSError(cx, E);
end;
end;
end;
{ TCustomSMPlugin }
destructor TCustomSMPlugin.Destroy;
begin
fCx.BeginRequest;
try
UnInit;
finally
fCx.EndRequest;
end;
inherited;
end;
procedure TCustomSMPlugin.Init(const rec: TSMPluginRec);
begin
end;
procedure TCustomSMPlugin.UnInit;
begin
end;
constructor TCustomSMPlugin.Create(aCx: PJSContext; aExports_, aRequire,
aModule: PJSRootedObject; _filename, _dirname: PWideChar);
var
rec: TSMPluginRec;
begin
fCx := aCx;
rec.Create(aCx, aExports_, aRequire, aModule, _filename, _dirname);
fCx.BeginRequest;
try
Init(rec);
finally
fCx.EndRequest;
end;
end;
constructor TSMPluginRec.Create(aCx: PJSContext; aExports_: PJSRootedObject; aRequire: PJSRootedObject; aModule: PJSRootedObject; _filename: PWideChar; _dirname: PWideChar);
begin
cx := aCx;
Exp := aExports_;
Req := aRequire;
Module := aModule;
filename := _filename;
dirname := _dirname;
end;
function TSMPluginRec.require(aFileName: string): PJSObject;
var
arg, rval: jsval;
begin
arg := cx.NewJSString(aFileName).ToJSVal;
if not Module.ptr.CallFunction(cx, req.ptr, 1, @arg, rval) then
raise ESMException.Create('Error require '+aFileName);
Result := rval.asObject;
end;
end.

View File

@@ -0,0 +1,713 @@
/// SyNodeProto - create a JS prototypes for Delphi classes based on Delphi7 RTTI
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeProto;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse Informatique - http://synopse.info
SyNode for mORMot Copyright (C) 2020 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
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 HASINLINE
{$I SyNode.inc}
uses
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
mORMot,
SynCommons,
SynLog,
SpiderMonkey;
type
TSMRTTIPropCache = record
mbr: pointer;//TRttiMember;
jsName: AnsiString;
isReadOnly: boolean;
DeterministicIndex: integer; // if not deterministic then -1
typeInfo: Pointer;//PTypeInfo;
end;
PSMRTTIPropCache = ^TSMRTTIPropCache;
TPropArray = array of TSMRTTIPropCache;
{$ifdef UNICODE}
TSMMethodRec = record
{$else}
TSMMethodRec = object
{$endif}
ujsName: SynUnicode;
method: pointer;
isNativeCall: boolean;
call: JSNative;
nargs: uintN;
flags: TJSPropertyAttrs;
end;
PSMMethodRec = ^TSMMethodRec;
TSMMethodDynArray = array of TSMMethodRec;
TSMObjectType = (otProto, otInstance, otOther);
{$ifdef UNICODE}
TSMObjectRecord = record
{$else}
TSMObjectRecord = object
{$endif}
Magic: Word;
DataType: TSMObjectType;
Data: Pointer;
procedure init(aDataType: TSMObjectType; aData: Pointer);
function IsMagicCorrect: boolean;
end;
PSMObjectRecord = ^TSMObjectRecord;
TSMCustomProtoObject = class;
TSMCustomProtoObjectClass = class of TSMCustomProtoObject;
PSMInstanceRecord = ^TSMInstanceRecord;
TFreeInstanceRecord = procedure (aInstanceRecord: PSMInstanceRecord);
{$ifdef UNICODE}
TSMInstanceRecord = record
{$else}
TSMInstanceRecord = object
{$endif}
private
function InternalCreate(cx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObject): jsval;
public
proto: TSMCustomProtoObject;
instance: TObject;
OwnInstance: Boolean;
AddData: pointer;
onFree: TFreeInstanceRecord;
procedure freeNative;
function CreateNew(acx: PJSContext; AProto: TSMCustomProtoObject; argc: uintN; var vp: JSArgRec): jsval;
function CreateForObj(acx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): jsval; overload;
function CreateForObj(acx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObjectClass; aParentProto: TSMCustomProtoObject): jsval; overload;
end;
TSMCustomProtoObject = class
private
fFirstDeterministicSlotIndex: uint32;
FJSClass: JSClass;
FJSClassProto: JSClass;
fSlotIndex: integer;
function getRTTIPropsCache(index: integer): TSMRTTIPropCache;
protected
fCx: PJSContext;
FjsObjName: AnsiString;
fRttiCls: TClass;
FJSProps: TJSPropertySpecDynArray;
fDeterministicCnt: uint32;
FRTTIPropsCache: TPropArray;
FMethods: TSMMethodDynArray;
FMethodsDA: TDynArrayHashed;
function GetJSClass: JSClass; virtual;
procedure InitObject(aParent: PJSRootedObject); virtual;
/// Add method to internal FMethods array for future define it into JS prototype
// to be called only inside InitObject method!
procedure definePrototypeMethod(const ajsName: SynUnicode; const aCall: JSNative; aNargs: uintN; aFlags: TJSPropertyAttrs);
property SlotIndex: integer read fSlotIndex;
public
property RTTIPropsCache[index: integer]: TSMRTTIPropCache read getRTTIPropsCache;
property jsObjName: AnsiString read FjsObjName;
property DeterministicCnt: Cardinal read fDeterministicCnt;
property FirstDeterministicSlotIndex: Cardinal read fFirstDeterministicSlotIndex;
function getMethod(const aJSFunction: PJSFunction; var obj: PJSObject): PSMMethodRec; //overload;
constructor Create(Cx: PJSContext; aRttiCls: TClass; aParent: PJSRootedObject; slotIndex: integer); virtual;
function NewSMInstance(aCx: PJSContext; argc: uintN; var vp: JSArgRec): TObject; virtual;
end;
TSMFastNativeCall = function(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean of object;
function IsInstanceObject(cx: PJSContext; jsobj: PJSObject; var asmInstance: PSMInstanceRecord): boolean; overload;
function IsInstanceObject(cx: PJSContext; jval: jsval; var asmInstance: PSMInstanceRecord): boolean; overload;
function IsProtoObject(cx: PJSContext; jsobj: PJSObject; var asmProto: TSMCustomProtoObject): boolean; overload;
function IsProtoObject(cx: PJSContext; jval: jsval; var asmProto: TSMCustomProtoObject): boolean; overload;
procedure defineEnum(cx: PJSContext; ti: PTypeInfo; aParent: PJSRootedObject);
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): TSMCustomProtoObject; overload;
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParentProto: TSMCustomProtoObject): TSMCustomProtoObject; overload;
function camelize(const S: AnsiString): AnsiString;
const
SM_NOT_A_NATIVE_OBJECT = 'Not a native object';
function strComparePropGetterSetter(prop_name, jsName: AnsiString; isGetter: boolean): Boolean; {$ifdef HASINLINE}inline;{$endif}
// for can make strComparePropGetterSetter inlined
const prefix: array[boolean] of TShort4 = ('set ','get ');
// called when the interpreter wants to create an object through a new TMyObject ()
function SMCustomObjectConstruct(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
// called when the interpreter destroys the object
{$IFDEF SM52}
procedure SMCustomObjectDestroy(var fop: JSFreeOp; obj: PJSObject); cdecl;
{$ELSE}
procedure SMCustomObjectDestroy(var rt: PJSRuntime; obj: PJSObject); cdecl;
{$ENDIF}
implementation
function strComparePropGetterSetter(prop_name, jsName: AnsiString; isGetter: boolean): Boolean;
var jsNameLen: integer;
ch1, ch2: PAnsiChar;
begin
jsNameLen := Length(jsName);
Result := (jsNameLen > 0) and (Length(prop_name) = jsNameLen + 4) and
((PInteger(@prop_name[1]))^ = (PInteger(@prefix[isGetter][1]))^);
if Result then begin
ch1 := @jsName[1];
ch2 := @prop_name[5];
while jsNameLen >= 4 do begin
if PInteger(ch1)^ <> PInteger(ch2)^ then begin
result := False;
exit;
end;
inc(ch1, 4);
inc(ch2, 4);
Dec(jsNameLen, 4);
end;
if jsNameLen >= 2 then begin
if PWord(ch1)^ <> PWord(ch2)^ then begin
result := False;
exit;
end;
inc(ch1, 2);
inc(ch2, 2);
Dec(jsNameLen, 2);
end;
if jsNameLen = 1 then begin
if ch1^ <> ch2^ then begin
result := False;
exit;
end;
end;
end;
end;
function camelize(const S: AnsiString): AnsiString;
var
Ch: AnsiChar;
begin
result := '';
if S='' then
exit;
SetString(result, PAnsiChar(S), length(s));
Ch := PAnsiChar(s)^;
case Ch of
'A' .. 'Z':
Ch := AnsiChar(byte(Ch) or $20);
end;
PAnsiChar(Result)^ := Ch;
end;
const
// Magic constant for TSMObjectRecord
SMObjectRecordMagic: Word = 43857;
{$IFDEF SM52}
jsdef_classOpts: JSClassOps = (
finalize: SMCustomObjectDestroy; // call then JS object GC}
construct: SMCustomObjectConstruct
);
jsdef_class: JSClass = (name: '';
flags: uint32(JSCLASS_HAS_PRIVATE);
cOps: @jsdef_classOpts
);
{$ELSE}
jsdef_class: JSClass = (name: '';
flags: uint32(JSCLASS_HAS_PRIVATE);
finalize: SMCustomObjectDestroy; // call then JS object GC}
construct: SMCustomObjectConstruct
);
{$ENDIF}
// create object var obj = new TMyObject();
function SMCustomObjectConstruct(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
jsobj: PJSObject;
Proto: TSMCustomProtoObject;
Inst: PSMInstanceRecord;
begin
Result := false;
try
if not vp.IsConstructing then
raise ESMException.Create('Construct: not JS_IS_CONSTRUCTING');
jsobj := vp.calleObject;
if not IsProtoObject(cx, jsobj, Proto) then
raise ESMException.Create('Construct: no private data');
new(Inst);
vp.rval := Inst.CreateNew(cx, Proto, argc, vp);
Result := true;
except
on E: Exception do
JSError(cx, E);
end;
end;
{$IFDEF SM52}
procedure SMCustomObjectDestroy(var fop: JSFreeOp; obj: PJSObject); cdecl;
{$ELSE}
procedure SMCustomObjectDestroy(var rt: PJSRuntime; obj: PJSObject); cdecl;
{$ENDIF}
var
ObjRec: PSMObjectRecord;
Inst: PSMInstanceRecord;
proto: TSMCustomProtoObject;
begin
ObjRec := obj.PrivateData;
if Assigned(ObjRec) and (ObjRec.IsMagicCorrect) then
begin
if (ObjRec.DataType=otInstance) and Assigned(ObjRec.Data) then begin
Inst := ObjRec.Data;
Inst.freeNative;
Dispose(Inst);
end else if (ObjRec.DataType=otProto) and Assigned(ObjRec.Data) then begin
proto := ObjRec.Data;
FreeAndNil(proto);
end else begin
Dispose(ObjRec.Data); // ObjRec.Data is a PSMIdxPropReader from SyNoideNewProto
end;
Dispose(ObjRec);
obj.PrivateData := nil;
end;
end;
function IsSMObject(cx: PJSContext; jsobj: PJSObject; var aObj: PSMObjectRecord): boolean; overload;
var
P: PSMObjectRecord;
CLS: PJSClass;
begin
Result := false;
CLS := jsobj.Class_;
if CLS.flags and JSCLASS_HAS_PRIVATE = 0 then
Exit;
p := jsobj.PrivateData;
if (p <> nil) then
if (P.IsMagicCorrect) then begin
aObj := p;
Result := true;
end else
raise ESMException.Create('Incorrect IsMagicCorrect');
end;
function IsSMObject(cx: PJSContext; jval: jsval; var aObj: PSMObjectRecord): boolean; overload;
var
obj: PJSObject;
begin
Result := jval.isObject;
if not Result then exit;
obj := jval.asObject;
Result := IsSMObject(cx, obj, aObj);
end;
function IsInstanceObject(cx: PJSContext; jval: jsval; var asmInstance: PSMInstanceRecord): boolean; overload;
var
obj: PJSObject;
begin
Result := jval.isObject;
if not Result then exit;
obj := jval.asObject;
Result := IsInstanceObject(cx, obj, asmInstance);
end;
function IsInstanceObject(cx: PJSContext; jsobj: PJSObject; var asmInstance: PSMInstanceRecord): boolean;
var
aObj: PSMObjectRecord;
begin
Result := IsSMObject(cx, jsobj, aObj);
if Result then
Result := (aObj.DataType = otInstance);
if not Result then
asmInstance := nil
else
asmInstance := aObj.Data;
end;
function IsProtoObject(cx: PJSContext; jsobj: PJSObject; var asmProto: TSMCustomProtoObject): boolean;
var
aObj: PSMObjectRecord;
begin
Result := IsSMObject(cx, jsobj, aObj);
if Result then
Result := (aObj.DataType = otProto);
if not Result then
asmProto := nil
else
asmProto := aObj.Data;
end;
function IsProtoObject(cx: PJSContext; jval: jsval; var asmProto: TSMCustomProtoObject): boolean;
var
obj: PJSObject;
begin
Result := jval.isObject;
if not Result then exit;
obj := jval.asObject;
Result := Assigned(obj) and IsProtoObject(cx, obj, asmProto);
end;
procedure defineEnum(cx: PJSContext; ti: PTypeInfo; aParent: PJSRootedObject);
var
i: integer;
s: SynUnicode;
found: Boolean;
val: jsval;
obj_: PJSRootedObject;
begin
if (ti^.Name = 'Boolean') then
Exit;
s := UTF8ToSynUnicode(ShortStringToUTF8(ti^.Name));
if (aParent.ptr.HasUCProperty(cx, Pointer(s), Length(s), found)) and found then
exit; //enum already defined
obj_ := cx.NewRootedObject(cx.NewObject(nil));
try
aParent.ptr.DefineUCProperty(cx, Pointer(s), Length(s), obj_.ptr.ToJSValue, JSPROP_ENUMERATE or JSPROP_PERMANENT, nil, nil);
with ti^.EnumBaseType^ do begin
for i := MinValue to MaxValue do begin
s := UTF8ToSynUnicode(GetEnumNameTrimed(i));
val.asInteger := i;
obj_.ptr.DefineUCProperty(cx, Pointer(s), Length(s), val, JSPROP_ENUMERATE or JSPROP_PERMANENT, nil, nil);
end;
end;
finally
cx.FreeRootedObject(obj_);
end;
//TODO freez (seal) created JS object to not allow it modification
end;
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): TSMCustomProtoObject;
var
global: PJSObject;
i: integer;
val: jsval;
begin
global := cx.CurrentGlobalOrNull;
for I := JSCLASS_GLOBAL_SLOT_COUNT to 255 do begin
val := global.ReservedSlot[i];
if val.isVoid then begin
//create new
result := AProto.Create(Cx, AForClass, aParent, i);
exit;
end else if IsProtoObject(cx, val, Result) then begin
if Result.fRttiCls = AForClass then
exit; // The class prototype has already created
end else if val.isString then begin
if val.asJSString.ToString(cx) = AForClass.ClassName then
exit; // The class prototype is being created right now
end else
raise ESMException.Create('Slot value is not ProtoObject');
end;
raise Exception.Create('defineClass Error: many proto' + AForClass.ClassName);
end;
function defineClass(cx: PJSContext; AForClass: TClass; AProto: TSMCustomProtoObjectClass; aParentProto: TSMCustomProtoObject): TSMCustomProtoObject; overload;
var
global: PJSObject;
i: integer;
val: jsval;
aParent: PJSRootedObject;
begin
global := cx.CurrentGlobalOrNull;
for I := JSCLASS_GLOBAL_SLOT_COUNT to 255 do begin
val := global.ReservedSlot[i];
if val.isVoid then begin
//create new
aParent := cx.NewRootedObject(global.ReservedSlot[aParentProto.fSlotIndex].asObject.ReservedSlot[aParentProto.FirstDeterministicSlotIndex].asObject);
try
result := AProto.Create(Cx, AForClass, aParent, i);
finally
cx.FreeRootedObject(aParent);
end;
exit;
end else begin
if IsProtoObject(cx, val, Result) then begin
if Result.fRttiCls = AForClass then
exit;
end else
raise ESMException.Create('Slot value is not ProtoObject');
end;
end;
raise Exception.Create('defineClass Error: many proto' + AForClass.ClassName);
end;
{ TSMObjectRecord }
procedure TSMObjectRecord.init(aDataType: TSMObjectType; aData: Pointer);
begin
Self.Magic := SMObjectRecordMagic;
Self.DataType := aDataType;
Self.Data := aData;
end;
function TSMObjectRecord.IsMagicCorrect: boolean;
begin
Result := Magic = SMObjectRecordMagic;
end;
{ TSMInstanceRecord }
function TSMInstanceRecord.CreateForObj(acx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObjectClass; aParent: PJSRootedObject): jsval;
begin
Result := InternalCreate(acx, AInstance, defineClass(acx, AInstance.ClassType, AProto, aParent));
OwnInstance := false;
end;
function TSMInstanceRecord.CreateForObj(acx: PJSContext; AInstance: TObject;
AProto: TSMCustomProtoObjectClass;
aParentProto: TSMCustomProtoObject): jsval;
begin
Result := InternalCreate(acx, AInstance, defineClass(acx, AInstance.ClassType, AProto, aParentProto));
OwnInstance := false;
end;
function TSMInstanceRecord.CreateNew(acx: PJSContext; AProto: TSMCustomProtoObject; argc: uintN;
var vp: JSArgRec): jsval;
begin
result := InternalCreate(aCx, AProto.NewSMInstance(acx, argc, vp), AProto );
OwnInstance := True;
end;
procedure TSMInstanceRecord.freeNative;
begin
if Assigned(onFree) then
onFree(@Self);
if OwnInstance then begin
if Assigned(instance) then
FreeAndNil(instance);
end;
Instance := nil; // in case the FInstance is IUnknown
end;
function TSMInstanceRecord.InternalCreate(cx: PJSContext; AInstance: TObject; AProto: TSMCustomProtoObject): jsval;
var
ObjRec: PSMObjectRecord;
jsobj: PJSRootedObject;
global: PJSRootedObject;
protoObj: PJSRootedObject;
begin
proto := AProto;
onFree := nil;
AddData := nil;
cx.BeginRequest;
try
global := cx.NewRootedObject(cx.CurrentGlobalOrNull);
protoObj := cx.NewRootedObject(global.ptr.ReservedSlot[proto.fSlotIndex].asObject);
jsobj := cx.NewRootedObject(cx.NewObjectWithGivenProto(@proto.FJSClass, protoObj.ptr));
try
// premature optimization is the root of evil
// as shown by valgrind profiler better to not redefine props in object
// but let's JS engine to use it from prototype
//if Length(AProto.FJSProps)>0 then
// jsobj.ptr.DefineProperties(cx,@AProto.FJSProps[0]);
Instance := AInstance;
new(ObjRec);
ObjRec.init(otInstance, @Self);
jsobj.ptr.PrivateData := ObjRec;
result := jsobj.ptr.ToJSValue;
finally
cx.FreeRootedObject(jsobj);
cx.FreeRootedObject(protoObj);
cx.FreeRootedObject(global);
end;
finally
cx.EndRequest;
end;
end;
{ TSMCustomProtoObject }
constructor TSMCustomProtoObject.Create(Cx: PJSContext; aRttiCls: TClass; aParent: PJSRootedObject; slotIndex: integer);
var
i: Cardinal;
ObjRec: PSMObjectRecord;
obj: PJSObject;
global: PJSObject;
begin
fRttiCls := aRttiCls;
fCx := Cx;
fSlotIndex := slotIndex;
FjsObjName := StringToAnsi7(fRttiCls.ClassName);
global := cx.CurrentGlobalOrNull;
global.ReservedSlot[fSlotIndex] := cx.NewJSString(fRttiCls.ClassName).ToJSVal;
FMethodsDA.Init(TypeInfo(TSMMethodDynArray), FMethods);
InitObject(aParent);
FJSClass := GetJSClass;
FJSClass.Name := PCChar(FjsObjName);
fFirstDeterministicSlotIndex := (FJSClass.flags and (JSCLASS_RESERVED_SLOTS_MASK shl JSCLASS_RESERVED_SLOTS_SHIFT))
shr JSCLASS_RESERVED_SLOTS_SHIFT;
if fFirstDeterministicSlotIndex + fDeterministicCnt >255 then
raise ESMException.Create('Too many properties');
if fFirstDeterministicSlotIndex + uint32(FMethodsDA.Count) + 1 >255 then
raise ESMException.Create('Too many methods');
FJSClass.flags := FJSClass.flags and not (JSCLASS_RESERVED_SLOTS_MASK shl JSCLASS_RESERVED_SLOTS_SHIFT) or
((fFirstDeterministicSlotIndex + fDeterministicCnt) shl JSCLASS_RESERVED_SLOTS_SHIFT);
FJSClassProto := FJSClass;
FJSClassProto.flags := FJSClassProto.flags and not (JSCLASS_RESERVED_SLOTS_MASK shl JSCLASS_RESERVED_SLOTS_SHIFT) or
((fFirstDeterministicSlotIndex + uint32(FMethodsDA.Count) + 1) shl JSCLASS_RESERVED_SLOTS_SHIFT);
cx.BeginRequest;
try
//TODO prototypes chain fjsproto
if length(FJSProps) = 0 then begin
obj := aParent.ptr.InitClass(cx ,nullObj , @FJSClassProto, nil, 0, nil , nil, nil, nil);
end else begin
SetLength(FJSProps, Length(FJSProps) + 1); // must be null terminate!!
obj := aParent.ptr.InitClass(cx ,nullObj , @FJSClassProto, nil, 0, @FJSProps[0] , nil, nil, nil);
end;
obj.ReservedSlot[fFirstDeterministicSlotIndex] := aParent.ptr.ToJSValue;
//define JS methods
if FMethodsDA.Count > 0 then
for i := 0 to FMethodsDA.Count-1 do with FMethods[i] do begin
obj.ReservedSlot[fFirstDeterministicSlotIndex + i + 1] :=
obj.DefineUCFunction(cx, PCChar16(ujsName),
Length(ujsName), call, nargs, uint32(flags)).ToJSValue;
end;
new(ObjRec);
ObjRec.init(otProto,self);
obj.PrivateData := ObjRec;
global.ReservedSlot[fSlotIndex] := obj.ToJSValue;
finally
cx.EndRequest;
end;
end;
function CustomProtoObject_freeNative(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
nativeObj: PSMInstanceRecord;
begin
Result := false;
try
if not IsInstanceObject(cx, vp.this[cx], nativeObj) then
raise ESMException.Create('Object not Native');
nativeObj.FreeNative;
Result := true;
except
on E: Exception do
begin
JSError(cx, E);
end;
end;
end;
procedure TSMCustomProtoObject.definePrototypeMethod(const ajsName: SynUnicode; const aCall: JSNative; aNargs: uintN; aFlags: TJSPropertyAttrs);
var idx: integer;
added: boolean;
begin
idx := FMethodsDA.FindHashedForAdding(ajsName, added);
if added then begin
with FMethods[idx] do begin
ujsName := ajsName;
nargs := aNargs;
call := aCall;
flags := aFlags;
end;
end else
raise ESMException.CreateUtf8('Duplicated native function %()',[ajsName]);
end;
function TSMCustomProtoObject.getMethod(const aJSFunction: PJSFunction; var obj: PJSObject): PSMMethodRec;
var
i: Cardinal;
begin
result := nil;
if FMethodsDA.Count > 0 then
for i := 0 to FMethodsDA.Count-1 do
if obj.ReservedSlot[i+1+fFirstDeterministicSlotIndex].asObject = aJSFunction then begin
Result := @FMethods[i];
break;
end;
end;
function TSMCustomProtoObject.NewSMInstance(aCx: PJSContext; argc: uintN;
var vp: JSArgRec): TObject;
var
ItemInstance: TClassInstance;
begin
ItemInstance.Init(fRttiCls);
result := ItemInstance.CreateNew;
end;
function TSMCustomProtoObject.getRTTIPropsCache(index: integer): TSMRTTIPropCache;
begin
Result := fRTTIPropsCache[index];
end;
function TSMCustomProtoObject.GetJSClass: JSClass;
begin
Result := jsdef_class; // default values
end;
procedure TSMCustomProtoObject.InitObject(aParent: PJSRootedObject);
begin
definePrototypeMethod('freeNative', @CustomProtoObject_freeNative, 0, [jspEnumerate, jspPermanent, jspReadOnly]);
end;
end.

View File

@@ -0,0 +1,129 @@
unit SyNodeReadWrite;
interface
uses
SpiderMonkey, Classes, SynCommons;
// TODO - replace this implemenattion by buffers whed buffers become fully compartible with nodeJS
/// Used for create JS method for writing data to internal class buffer
// JS syntax: write(data: ArrayBuffer|String|Object, [encoding: "utf-8"|"ucs2"|"bin"|"base64"]'
// inmplementation must provide TTextWriter to write data to
function SMWrite_impl(cx: PJSContext; argc: uintN; vals: PjsvalVector; dest: TTextWriter): jsval; cdecl;
/// Used for create JS method for reading data from internal class buffer (aStr)
// JS syntax: read([encoding=utf-8: "utf-8"|"ucs2"|"bin"|"base64"])
// implementation must provide TTextWriter to write data to
function SMRead_impl(cx: PJSContext; argc: uintN; vals: PjsvalVector; const aStr: RawByteString): jsval; cdecl;
implementation
{$ifdef MSWINDOWS}
uses
Windows; // CP_UTF8
{$endif}
function SMRead_impl(cx: PJSContext; argc: uintN; vals: PjsvalVector; const aStr: RawByteString): jsval; cdecl;
var
encoding: RawUTF8;
len: Integer;
bufObj: PJSObject;
bufData: pointer;
decodedStr: RawByteString;
begin
if (argc = 0) or ((argc=1) and vals[0].isVoid) then
encoding := 'utf-8'
else
encoding := LowerCase(vals[0].asJSString.ToUTF8(cx));
if encoding = 'utf-8' then begin
Result := cx.NewJSString(Pointer(aStr), length(aStr), CP_UTF8).ToJSVal
end else if encoding = 'Windows-1251' then begin
Result := cx.NewJSString(Pointer(aStr), length(aStr), 1251).ToJSVal
end else if encoding = 'ucs2' then begin
Result := cx.NewJSString(aStr).ToJSVal
end else if encoding = 'bin2base64' then begin
decodedStr := BinToBase64(aStr);
Result := cx.NewJSString(decodedStr).ToJSVal;
end else if (encoding = 'bin') or (encoding = 'base64') then begin
if encoding = 'base64' then
decodedStr := Base64ToBin(aStr)
else
decodedStr := aStr;
len := length(decodedStr);
bufObj := cx.NewArrayBuffer(len);
bufData := bufObj.GetArrayBufferData;
Move(pointer(decodedStr)^, bufData^, len);
Result := bufObj.ToJSValue;
end else
raise ESMException.Create('Invalid encoding');
end;
function SMWrite_impl(cx: PJSContext; argc: uintN; vals: PjsvalVector; dest: TTextWriter): jsval; cdecl;
var
encoding: RawUTF8;
len: uint32;
isShared: boolean;
bufObj: PJSObject;
bufData: Puint8Vector;
tmp1: RawByteString;
tmp2: SynUnicode;
begin
if (argc < 1) or (argc>2) then
raise ESMException.Create('Usage: write(data: ArrayBuffer|String|Object, [encoding: "utf-8"|"ucs2"|"bin"|"base64"]');
encoding := '';
if argc=2 then begin
if (argc = 2) and (vals[1].isString) then
encoding := LowerCase(vals[1].asJSString.ToUTF8(cx));
end;
case cx.TypeOfValue(vals[0]) of
JSTYPE_STRING: begin
if (encoding = '') or (encoding = 'utf-8') or (encoding = 'utf8') then // default is utf-8
vals[0].asJSString.ToUTF8(cx, dest)
else if (encoding = 'ucs2') then begin
tmp2 := vals[0].asJSString.ToSynUnicode(cx);
dest.AddNoJSONEscapeW(pointer(tmp2), length(tmp2));
end else
raise ESMException.Create('invalid encoding');
end;
JSTYPE_NUMBER:
if vals[0].isInteger then
dest.Add(vals[0].asInteger)
else
dest.AddDouble(vals[0].asDouble);
JSTYPE_OBJECT: begin
bufObj := vals[0].asObject;
if bufObj=nil then begin
Result.asBoolean := true;
Exit;
end;
if bufObj.GetBufferDataAndLength(bufData, len) then begin
if encoding = 'base64' then begin
tmp1 := BinToBase64(PAnsiChar(bufData), len);
bufData := Puint8Vector(tmp1);
len := Length(tmp1);
encoding := 'bin';
end;
if (encoding = '') or (encoding = 'bin') then begin // default is bin
if len>0 then
dest.AddNoJSONEscape(pointer(bufData), len)
end else
raise ESMException.Create('invalid encoding');
end else begin
if (encoding = '') or (encoding = 'utf-8') or (encoding = 'utf8') then // default is utf-8
vals[0].AddJSON(cx, dest)
else
raise ESMException.Create('invalid encoding');
end;
end;
JSTYPE_VOID: begin end; // do nothing
else
raise ESMException.Create('invalid parameter type for Writer');
end;
result.asBoolean := True;
end;
end.

View File

@@ -0,0 +1,890 @@
/// 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) 2020 Arnaud Bouchez
Synopse Informatique - http://synopse.info
SyNode for mORMot Copyright (C) 2020 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: TRawUTF8ListLocked;
fLogQueue: TRawUTF8ListLocked;
{$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.SafePush(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.SafePush(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.SafeClear;
fLogQueue.SafeClear;
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 := TRawUTF8ListLocked.Create();
fLogQueue := TRawUTF8ListLocked.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.SafeClear;
fLogQueue.SafeClear;
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: TRawUTF8ListLocked;
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.SafePop(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.

View File

@@ -0,0 +1,410 @@
/// SyNodeSimpleProto - create a JS prototypes with Delphi method/props realisation
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeSimpleProto;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse Informatique - http://synopse.info
SyNode for mORMot Copyright (C) 2020 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
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
}
{$I Synopse.inc}
interface
uses
SpiderMonkey,
SyNodeProto,
mORMot { PClassProp };
type
/// A prototype class for wrapping a Delphi class based on a "old" RTTI
// - create a properties in JavaScript based on the published properties of original class
// - all published methods of a original calss MUST have a TSMFastNativeCall signature
TSMSimpleRTTIProtoObject = class(TSMCustomProtoObject)
protected
procedure InitObject(aParent: PJSRootedObject); override;
/// Can be used to optimize JS engine proerty access.
// - if isReadonly setted to true property become read-only for JS engine
// - if property valu don't changed during object lifecircle set isDeterministic=true
// to prevent creating of JS value every time JS engine read property value
// If method return false propery will not be created in the JS
function GetPropertyAddInformation(cx: PJSContext; PI:PPropInfo; out isReadonly: boolean;
out isDeterministic: boolean; aParent: PJSRootedObject): boolean; virtual;
function GetJSvalFromProp(cx: PJSContext; PI:PPropInfo; instance: PSMInstanceRecord): jsval; virtual;
public
end;
function CreateJSInstanceObjForSimpleRTTI(cx: PJSContext; AInstance: TObject; aParent: PJSRootedObject=nil): jsval;
implementation
uses
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
SyNode,
SynCommons;
function JSRTTINativeMethodCall(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
fCallFn: TSMFastNativeCall;
fCallMethod: TMethod;
lfunc: PJSFunction;
jsobj: PJSObject;
Inst: PSMInstanceRecord;
mc: PSMMethodRec;
proto: PJSObject;
begin
try
// JS_ConvertValue(cx,JS_CALLEE(cx, vp),JSTYPE_FUNCTION, lfuncVal);
lfunc := vp.calleObject;
Assert(Assigned(lfunc));
jsobj := vp.thisObject[cx];
if not IsInstanceObject(cx, jsobj, Inst) then
raise ESMException.Create(SM_NOT_A_NATIVE_OBJECT);
jsobj.GetPrototype(cx, proto);
mc := TSMSimpleRTTIProtoObject(Inst^.proto).getMethod(lfunc, proto);
if mc = nil then
raise ESMException.CreateUTF8('The class has no method "%"', [lfunc.GetFunctionId().ToSynUnicode(cx)]);
fCallMethod.Code := mc^.method;
fCallMethod.Data := Pointer(Inst.instance);
fCallFn := TSMFastNativeCall(fCallMethod);
Result := fCallFn(cx, argc, vp);
except
on E: Exception do begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function GetPropCacheForWrite(cx: PJSContext; obj: PJSObject; id: jsid; var aObj: PSMInstanceRecord): PSMRTTIPropCache;
var
i: Integer;
propName: AnsiString;
found: boolean;
begin
Result := nil;
if not IsInstanceObject(cx, obj, aObj) then
raise ESMException.Create(SM_NOT_A_NATIVE_OBJECT);
propName := PJSString(id).ToAnsi(cx);
found := False;
for I := 0 to Length((AObj.proto as TSMSimpleRTTIProtoObject).FRTTIPropsCache)-1 do begin
Result := @(AObj.proto as TSMSimpleRTTIProtoObject).FRTTIPropsCache[i];
{$IFDEF SM52}
if strComparePropGetterSetter(propName, Result.jsName, false) then begin
{$ELSE}
if Result.jsName = propName then begin
{$ENDIF}
found := True;
Break;
end;
end;
if not found then
raise ESMException.CreateFmt('% not found', [propName]);
if Result.isReadOnly then
raise ESMException.CreateUtf8('Property %.% is ReadOnly', [aObj.proto.jsObjName, Result.jsName]);
end;
function JSRTTIPropWrite(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
Instance: PSMInstanceRecord;
PI: PPropInfo;
id: jsid;
val: jsval;
begin
id := jsid(vp.calleObject.FunctionId);
PI := GetPropCacheForWrite(cx, vp.thisObject[cx], id, Instance).mbr;
val := vp.argv[0];
case PI.PropType^{$IFNDEF FPC}^{$ENDIF}.Kind of
tkInteger, tkEnumeration, tkSet{$ifdef FPC},tkBool{$endif}:
PI.SetOrdProp(Instance^.instance,val.asInteger);
tkInt64:
PI.SetInt64Prop(Instance^.instance, val.asInt64);
tkFloat:
PI.SetFloatProp(Instance^.instance, val.asDouble);
tkLString,{$IFDEF FPC}tkLStringOld{$ENDIF},tkWString{$ifdef HASVARUSTRING},tkUString{$endif}:
PI.SetLongStrValue(Instance^.instance, val.asJsString.ToUTF8(cx));
else
raise ESMException.Create('NotImplemented');
end;
Result := True;
end;
function JSRTTIPropRead(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
var
Instance: PSMInstanceRecord;
proto: TSMCustomProtoObject;
propCache: PSMRTTIPropCache;
PI: PPropInfo;
rval: jsval;
this: PJSObject;
storedVal: jsval;
i: Integer;
id: PJSString;
prop_name: AnsiString;
found: Boolean;
begin
try
this := vp.thisObject[cx];
if IsProtoObject(cx, this, proto) then begin
vp.rval := JSVAL_NULL;
Result := True;
exit;
end;
if not IsInstanceObject(cx, this, Instance) then
raise ESMException.Create(SM_NOT_A_NATIVE_OBJECT);
id := vp.calleObject.FunctionId;
prop_name := ID.ToAnsi(cx);
propCache := nil;
found := false;
for i := 0 to Length((Instance.proto as TSMSimpleRTTIProtoObject).FRTTIPropsCache)-1 do begin
propCache := @(Instance.proto as TSMSimpleRTTIProtoObject).FRTTIPropsCache[i];
{$IFDEF SM52}
if strComparePropGetterSetter(prop_name, propCache.jsName, true) then begin
{$ELSE}
if propCache.jsName = prop_name then begin
{$ENDIF}
found := True;
Break;
end;
end;
if not found then
raise ESMException.CreateFmt('% not found', [prop_name]);
if (propCache.DeterministicIndex>=0) then
storedVal := this.ReservedSlot[propCache.DeterministicIndex]
else
storedVal.setVoid;
if (not storedVal.isVoid) then
rval := storedVal
else begin
PI := propCache.mbr;
rval := (Instance.proto as TSMSimpleRTTIProtoObject).GetJSvalFromProp(cx, PI, Instance);
if (propCache.DeterministicIndex>=0) then begin
// all jsvals in reserved slots are rooted automatically
this.ReservedSlot[propCache.DeterministicIndex] := rval;
end;
end;
vp.rval := rval;
Result := True;
except
on E: Exception do begin
Result := False;
JSError(cx, E);
end;
end;
end;
{ TSMSimpleRTTIProtoObject }
function TSMSimpleRTTIProtoObject.GetJSvalFromProp(cx: PJSContext;
PI: PPropInfo; instance: PSMInstanceRecord): jsval;
var
FInst: PSMInstanceRecord;
tmp: RawUTF8;
obj: TObject;
arr: TDynArray;
begin
case PI.PropType^.Kind of
tkInteger, tkEnumeration, tkSet{$ifdef FPC},tkBool{$endif}:
Result.asInteger := PI.GetOrdValue(Instance^.instance);
tkInt64:
Result.asInt64 := PI.GetInt64Value(Instance^.instance);
tkFloat:
Result.asDouble := PI.GetDoubleValue(Instance^.instance);
tkLString,{$ifdef FPC}tkLStringOld,{$endif}tkWString{$ifdef HASVARUSTRING},tkUString{$endif}: begin
PI.GetLongStrValue(Instance^.instance, tmp);
Result.asJSString := cx.NewJSString(tmp);
end;
tkClass: begin
new(FInst);
obj := PI.GetObjProp(Instance^.instance);
if obj <> nil then
Result := FInst.CreateForObj(cx, obj, TSMSimpleRTTIProtoObject, Instance.Proto)
else
Result := JSVAL_NULL;
end;
tkDynArray: begin
// MPV. WARNING. Every access to dyn array property will create a JS Array, so
// I recommend avoiding use of the dynamic arrays, or use a temp variable
arr := PI.GetDynArray(Instance^.instance);
Result.asJson[cx] := arr.SaveToJSON(true);
end;
else
raise ESMException.Create('NotImplemented');
end;
end;
procedure TSMSimpleRTTIProtoObject.InitObject(aParent: PJSRootedObject);
var
PI: PPropInfo;
i: integer;
idx: Integer;
CT: TClass;
n: integer;
added: boolean;
isReadonly: boolean;
isDeterministic: boolean;
exclude: boolean;
methods: TPublishedMethodInfoDynArray;
begin
for i := 0 to GetPublishedMethods(nil, methods, fRttiCls) - 1 do begin
idx := FMethodsDA.FindHashedForAdding(methods[i].Name, added);
if added then with FMethods[idx] do begin
ujsName := UTF8ToSynUnicode(methods[i].Name);
method := methods[i].Method.Code;
nargs := 0;
isNativeCall := true;
call := @JSRTTINativeMethodCall;
flags := [jspEnumerate];
end;
end;
fDeterministicCnt := 0;
CT := fRttiCls;
repeat
for i := 1 to InternalClassPropInfo(CT,PI) do begin
idx := Length(FJSProps);
exclude := PI^.PropType^.Kind = tkMethod;
if not exclude then
for n := 0 to idx - 1 do begin
if StrLIComp(PAnsiChar(@PI.Name[1]), PAnsiChar(FRTTIPropsCache[n].jsName), length(PI.Name)) = 0 then begin
exclude := true;
break;
end;
end;
if exclude or not GetPropertyAddInformation(fCx, PI, isReadonly, isDeterministic, aParent) then begin
PI := PI^.Next;
Continue;
end;
case PI^.PropType^{$IFNDEF FPC}^{$ENDIF}.Kind of
tkChar, {$IFDEF FPC}tkLString{$ELSE}tkString{$ENDIF}, tkWChar, tkWString, tkVariant:
begin
raise ESMException.CreateUtf8('Unsupported class property %.%', [FjsObjName, PI^.Name]);
end;
// lazy create class type property prototypes on first read (in TSMSimpleRTTIProtoObject.GetJSvalFromProp)
// tkClass:
// defineClass(Cx, PI^.PropType^{$IFNDEF FPC}^{$ENDIF}.ClassType^.ClassType, TSMSimpleRTTIProtoObject, aParent);
tkEnumeration:
defineEnum(fCx, PI^.PropType{$IFNDEF FPC_OLDRTTI}^{$ENDIF}, aParent);
end;
SetLength(FJSProps, idx + 1);
SetLength(FRTTIPropsCache, idx + 1);
FRTTIPropsCache[idx].jsName := camelize(PI.Name);
FRTTIPropsCache[idx].mbr := PI;
FRTTIPropsCache[idx].typeInfo := PI^.PropType{$IFNDEF FPC}^{$ENDIF};
FRTTIPropsCache[idx].isReadOnly := isReadonly or isDeterministic;
if isDeterministic then begin
FRTTIPropsCache[idx].DeterministicIndex := fDeterministicCnt;
Inc(fDeterministicCnt);
end else
FRTTIPropsCache[idx].DeterministicIndex := -1;
FJSProps[idx].flags := JSPROP_ENUMERATE or JSPROP_PERMANENT or JSPROP_SHARED;
FJSProps[idx].Name := PCChar(RTTIPropsCache[idx].jsName);
// FJSProps[idx].tinyid := idx;
FJSProps[idx].setter.native.info := nil;
FJSProps[idx].setter.native.op := JSRTTIPropWrite;
FJSProps[idx].getter.native.info := nil;
FJSProps[idx].getter.native.op := JSRTTIPropRead;
PI := PI^.Next;
end;
CT := CT.ClassParent;
until CT=nil;
inherited; //MPV !! do not use FMethodsDA.Add()
end;
function TSMSimpleRTTIProtoObject.GetPropertyAddInformation(cx: PJSContext;
PI: PPropInfo; out isReadonly: boolean; out isDeterministic: boolean; aParent: PJSRootedObject): boolean;
begin
isReadonly := false;
isDeterministic := false;
result := true;
end;
function CreateJSInstanceObjForSimpleRTTI(cx: PJSContext; AInstance: TObject; aParent: PJSRootedObject=nil): jsval;
var
Inst: PSMInstanceRecord;
eng: TSMEngine;
begin
new(Inst);
if (aParent = nil) then begin
eng := cx.PrivateData;
Result := Inst.CreateForObj(cx, AInstance, TSMSimpleRTTIProtoObject, eng.GlobalObject);
end else
Result := Inst.CreateForObj(cx, AInstance, TSMSimpleRTTIProtoObject, aParent);
end;
end.

View File

@@ -0,0 +1,59 @@
{
New Plugin instruction:
1. Open this project
2. Use SaveAs for saving your new project to your own directory
3. Declare new descendant of TCustomSMPlugin and override methods Init and UnInit
4. Change in *.dpr file TCustomSMPlugin to your class
}
library EmptyPlugin;
uses
FastMM4,
SysUtils,
Classes,
Windows,
SyNodeAPI,
PluginUtils in '..\PluginUtils\PluginUtils.pas';
const
MAX_THREADS = 256;
PluginType: TCustomSMPluginType = TCustomSMPlugin; //In real realization you must declare you own class child of TCustomSMPlugin and override methods Init and UnInit
var
ThreadRecs: array[0..MAX_THREADS] of TThreadRec;
threadCounter: integer;
function InitPlugin(cx: PJSContext; exports_: PJSRootedObject; require: PJSRootedObject; module: PJSRootedObject; __filename: PWideChar; __dirname: PWideChar): boolean; cdecl;
var
l: integer;
begin
l := InterlockedIncrement(threadCounter);
if l>=MAX_THREADS then
raise Exception.Create('Too many thread. Max is 256');
ThreadRecs[l].threadID := GetCurrentThreadId;
ThreadRecs[l].plugin := PluginType.Create(cx, exports_, require, module, __filename, __dirname);
result := true;
end;
function UnInitPlugin(): boolean; cdecl;
var
i: integer;
begin
for I := 0 to MAX_THREADS - 1 do
if ThreadRecs[i].threadID = GetCurrentThreadId then begin
ThreadRecs[i].threadID := 0;
FreeAndNil(ThreadRecs[i].plugin);
end;
result := true;
end;
exports InitPlugin;
exports UnInitPlugin;
begin
IsMultiThread := True; //!!IMPORTANT for FastMM
threadCounter := -1;
FillMemory(@ThreadRecs[0], SizeOf(ThreadRecs), 0);
end.

View File

@@ -0,0 +1,5 @@
#!/bin/sh
rm -rf ./.resources
./tools/core_res -i ./core_modules/ -o ./.resources/
x86_64-w64-mingw32-windres ./.resources/core_res.rc ./core_modules.res

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,133 @@
"use strict";
let {logError} = process.binding('debugger');
/**
* Turn the error |aError| into a string, without fail.
*/
function safeErrorString(aError) {
try {
let errorString = aError.toString();
if (typeof errorString == "string") {
// Attempt to attach a stack to |errorString|. If it throws an error, or
// isn't a string, don't use it.
try {
if (aError.stack) {
let stack = aError.stack.toString();
if (typeof stack == "string") {
errorString += "\nStack: " + stack;
}
}
} catch (ee) { }
// Append additional line and column number information to the output,
// since it might not be part of the stringified error.
if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") {
errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber;
}
return errorString;
}
} catch (ee) { }
// We failed to find a good error description, so do the next best thing.
return Object.prototype.toString.call(aError);
};
/**
* Report that |aWho| threw an exception, |aException|.
*/
export function reportException(aWho, aException) {
let msg = aWho + " threw an exception: " + safeErrorString(aException);
logError(msg);
};
/**
* Safely get the property value from a Debugger.Object for a given key. Walks
* the prototype chain until the property is found.
*
* @param Debugger.Object aObject
* The Debugger.Object to get the value from.
* @param String aKey
* The key to look for.
* @return Any
*/
export function getProperty(aObj, aKey) {
let root = aObj;
try {
do {
const desc = aObj.getOwnPropertyDescriptor(aKey);
if (desc) {
if ("value" in desc) {
return desc.value;
}
// Call the getter if it's safe.
return hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
}
aObj = aObj.proto;
} while (aObj);
} catch (e) {
// If anything goes wrong report the error and return undefined.
//exports.reportException("getProperty", e);
}
return undefined;
};
/**
* Determines if a descriptor has a getter which doesn't call into JavaScript.
*
* @param Object aDesc
* The descriptor to check for a safe getter.
* @return Boolean
* Whether a safe getter was found.
*/
export function hasSafeGetter(aDesc) {
// Scripted functions that are CCWs will not appear scripted until after
// unwrapping.
try {
let fn = aDesc.get.unwrap();
return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
} catch(e) {
// Avoid exception 'Object in compartment marked as invisible to Debugger'
return false;
}
};
// Calls the property with the given `name` on the given `object`, where
// `name` is a string, and `object` a Debugger.Object instance.
///
// This function uses only the Debugger.Object API to call the property. It
// avoids the use of unsafeDeference. This is useful for example in workers,
// where unsafeDereference will return an opaque security wrapper to the
// referent.
export function callPropertyOnObject(object, name) {
// Find the property.
let descriptor;
let proto = object;
do {
descriptor = proto.getOwnPropertyDescriptor(name);
if (descriptor !== undefined) {
break;
}
proto = proto.proto;
} while (proto !== null);
if (descriptor === undefined) {
throw new Error("No such property");
}
let value = descriptor.value;
if (typeof value !== "object" || value === null || !("callable" in value)) {
throw new Error("Not a callable object.");
}
// Call the property.
let result = value.call(object);
if (result === null) {
throw new Error("Code was terminated.");
}
if ("throw" in result) {
throw result.throw;
}
return result.return;
}
//exports.callPropertyOnObject = callPropertyOnObject;

View File

@@ -0,0 +1,826 @@
import * as DevToolsUtils from 'DevTools/DevToolsUtils.js';
let {global} = process.binding('debugger');
const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
"Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
"Float64Array"];
// Number of items to preview in objects, arrays, maps, sets, lists,
// collections, etc.
const OBJECT_PREVIEW_MAX_ITEMS = 10;
let _ObjectActorPreviewers = {
String: [function (objectActor, grip) {
return wrappedPrimitivePreviewer("String", String, objectActor, grip);
}],
Boolean: [function (objectActor, grip) {
return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip);
}],
Number: [function (objectActor, grip) {
return wrappedPrimitivePreviewer("Number", Number, objectActor, grip);
}],
Function: [function (objectActor, grip) {
let {_obj} = objectActor;
if (_obj.name) {
grip.name = _obj.name;
}
if (_obj.displayName) {
grip.displayName = _obj.displayName.substr(0, 500);
}
if (_obj.parameterNames) {
grip.parameterNames = _obj.parameterNames;
}
// Check if the developer has added a de-facto standard displayName
// property for us to use.
let userDisplayName;
try {
userDisplayName = _obj.getOwnPropertyDescriptor("displayName");
} catch (e) {
// Calling getOwnPropertyDescriptor with displayName might throw
// with "permission denied" errors for some functions.
//dumpn(e);
}
if (userDisplayName && typeof userDisplayName.value == "string" &&
userDisplayName.value) {
grip.userDisplayName = objectActor.getGrip(userDisplayName.value);
}
//let dbgGlobal = hooks.getGlobalDebugObject();
//if (dbgGlobal) {
//let script = dbgGlobal.makeDebuggeeValue(_obj.unsafeDereference()).script;
let script = _obj.script;
if (script) {
grip.location = {
url: script.url,
line: script.startLine
};
}
//}
return true;
}],
RegExp: [function (objectActor, grip) {
let {_obj} = objectActor;
// Avoid having any special preview for the RegExp.prototype itself.
if (!_obj.proto || _obj.proto.class != "RegExp") {
return false;
}
let str = RegExp.prototype.toString.call(_obj.unsafeDereference());
grip.displayString = objectActor.getGrip(str);
return true;
}],
Date: [function (objectActor, grip) {
let {_obj} = objectActor;
let time = Date.prototype.getTime.call(_obj.unsafeDereference());
grip.preview = {
timestamp: objectActor.getGrip(time)
};
return true;
}],
Array: [function (objectActor, grip) {
let {_obj} = objectActor;
let length = DevToolsUtils.getProperty(_obj, "length");
if (typeof length != "number") {
return false;
}
grip.preview = {
kind: "ArrayLike",
length: length
};
if (objectActor.getGripDepth() > 1) {
return true;
}
let raw = _obj.unsafeDereference();
let items = grip.preview.items = [];
for (let i = 0; i < length; ++i) {
// Array Xrays filter out various possibly-unsafe properties (like
// functions, and claim that the value is undefined instead. This
// is generally the right thing for privileged code accessing untrusted
// objects, but quite confusing for Object previews. So we manually
// override this protection by waiving Xrays on the array, and re-applying
// Xrays on any indexed value props that we pull off of it.
//let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
let desc = Object.getOwnPropertyDescriptor(raw, i);
if (desc && !desc.get && !desc.set) {
//let value = Cu.unwaiveXrays(desc.value);
let value = desc.value;
value = makeDebuggeeValueIfNeeded(_obj, value);
items.push(objectActor.getGrip(value));
} else {
items.push(null);
}
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],
Set: [function (objectActor, grip) {
let {_obj} = objectActor;
let size = DevToolsUtils.getProperty(_obj, "size");
if (typeof size != "number") {
return false;
}
grip.preview = {
kind: "ArrayLike",
length: size
};
// Avoid recursive object grips.
if (objectActor.getGripDepth() > 1) {
return true;
}
let raw = _obj.unsafeDereference();
let items = grip.preview.items = [];
// We currently lack XrayWrappers for Set, so when we iterate over
// the values, the temporary iterator objects get created in the target
// compartment. However, we _do_ have Xrays to Object now, so we end up
// Xraying those temporary objects, and filtering access to |it.value|
// based on whether or not it's Xrayable and/or callable, which breaks
// the for/of iteration.
//
// This code is designed to handle untrusted objects, so we can safely
// waive Xrays on the iterable, and relying on the Debugger machinery to
// make sure we handle the resulting objects carefully.
//for (let item of Cu.waiveXrays(Set.prototype.values.call(raw))) {
for (let item of Set.prototype.values.call(raw)) {
//item = Cu.unwaiveXrays(item);
item = makeDebuggeeValueIfNeeded(_obj, item);
items.push(objectActor.getGrip(item));
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],
/*WeakSet: [function(objectActor, grip) {
let {_obj} = objectActor;
let raw = _obj.unsafeDereference();
// We currently lack XrayWrappers for WeakSet, so when we iterate over
// the values, the temporary iterator objects get created in the target
// compartment. However, we _do_ have Xrays to Object now, so we end up
// Xraying those temporary objects, and filtering access to |it.value|
// based on whether or not it's Xrayable and/or callable, which breaks
// the for/of iteration.
//
// This code is designed to handle untrusted objects, so we can safely
// waive Xrays on the iterable, and relying on the Debugger machinery to
// make sure we handle the resulting objects carefully.
//let keys = Cu.waiveXrays(ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(raw));
let keys = Cu.waiveXrays(ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(raw));
grip.preview = {
kind: "ArrayLike",
length: keys.length
};
//// Avoid recursive object grips.
//if (hooks.getGripDepth() > 1) {
//return true;
//}
let items = grip.preview.items = [];
for (let item of keys) {
//item = Cu.unwaiveXrays(item);
item = makeDebuggeeValueIfNeeded(obj, item);
items.push(hooks.createValueGrip(item));
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],*/
Map: [function (objectActor, grip) {
let {_obj} = objectActor;
let size = DevToolsUtils.getProperty(_obj, "size");
if (typeof size != "number") {
return false;
}
grip.preview = {
kind: "MapLike",
size: size
};
if (objectActor.getGripDepth() > 1) {
return true;
}
let raw = _obj.unsafeDereference();
let entries = grip.preview.entries = [];
// Iterating over a Map via .entries goes through various intermediate
// objects - an Iterator object, then a 2-element Array object, then the
// actual values we care about. We don't have Xrays to Iterator objects,
// so we get Opaque wrappers for them. And even though we have Xrays to
// Arrays, the semantics often deny access to the entires based on the
// nature of the values. So we need waive Xrays for the iterator object
// and the tupes, and then re-apply them on the underlying values until
// we fix bug 1023984.
//
// Even then though, we might want to continue waiving Xrays here for the
// same reason we do so for Arrays above - this filtering behavior is likely
// to be more confusing than beneficial in the case of Object previews.
//for (let keyValuePair of Cu.waiveXrays(Map.prototype.entries.call(raw))) {
for (let keyValuePair of Map.prototype.entries.call(raw)) {
//let key = Cu.unwaiveXrays(keyValuePair[0]);
let key = keyValuePair[0];
//let value = Cu.unwaiveXrays(keyValuePair[1]);
let value = keyValuePair[1];
key = makeDebuggeeValueIfNeeded(_obj, key);
value = makeDebuggeeValueIfNeeded(_obj, value);
entries.push([objectActor.getGrip(key),
objectActor.getGrip(value)]);
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}]/*,
WeakMap: [function({obj, hooks}, grip) {
let raw = obj.unsafeDereference();
// We currently lack XrayWrappers for WeakMap, so when we iterate over
// the values, the temporary iterator objects get created in the target
// compartment. However, we _do_ have Xrays to Object now, so we end up
// Xraying those temporary objects, and filtering access to |it.value|
// based on whether or not it's Xrayable and/or callable, which breaks
// the for/of iteration.
//
// This code is designed to handle untrusted objects, so we can safely
// waive Xrays on the iterable, and relying on the Debugger machinery to
// make sure we handle the resulting objects carefully.
let rawEntries = Cu.waiveXrays(ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(raw));
grip.preview = {
kind: "MapLike",
size: rawEntries.length,
};
if (hooks.getGripDepth() > 1) {
return true;
}
let entries = grip.preview.entries = [];
for (let key of rawEntries) {
let value = Cu.unwaiveXrays(WeakMap.prototype.get.call(raw, key));
key = Cu.unwaiveXrays(key);
key = makeDebuggeeValueIfNeeded(obj, key);
value = makeDebuggeeValueIfNeeded(obj, value);
entries.push([hooks.createValueGrip(key),
hooks.createValueGrip(value)]);
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],
DOMStringMap: [function({obj, hooks}, grip, rawObj) {
if (!rawObj) {
return false;
}
let keys = obj.getOwnPropertyNames();
grip.preview = {
kind: "MapLike",
size: keys.length,
};
if (hooks.getGripDepth() > 1) {
return true;
}
let entries = grip.preview.entries = [];
for (let key of keys) {
let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
entries.push([key, hooks.createValueGrip(value)]);
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}],*/
};
/**
* Generic previewer for classes wrapping primitives, like String,
* Number and Boolean.
*
* @param string className
* Class name to expect.
* @param object classObj
* The class to expect, eg. String. The valueOf() method of the class is
* invoked on the given object.
* @param ObjectActor objectActor
* The object actor
* @param Object grip
* The result grip to fill in
* @return Booolean true if the object was handled, false otherwise
*/
function wrappedPrimitivePreviewer(className, classObj, objectActor, grip) {
let {_obj} = objectActor;
if (!_obj.proto || _obj.proto.class != className) {
return false;
}
let raw = _obj.unsafeDereference();
let v = null;
try {
v = classObj.prototype.valueOf.call(raw);
} catch (ex) {
// valueOf() can throw if the raw JS object is "misbehaved".
return false;
}
if (v === null) {
return false;
}
let canHandle = GenericObject(objectActor, grip, className === "String");
if (!canHandle) {
return false;
}
grip.preview.wrappedValue = objectActor.getGrip(makeDebuggeeValueIfNeeded(_obj, v));
return true;
}
function GenericObject(objectActor, grip, specialStringBehavior = false) {
let {_obj} = objectActor;
if (grip.preview || grip.displayString || objectActor.getGripDepth() > 1) {
return false;
}
let i = 0, names = [];
let preview = grip.preview = {
kind: "Object",
ownProperties: {}//Object.create(null)
};
try {
names = _obj.getOwnPropertyNames();
} catch (ex) {
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
}
preview.ownPropertiesLength = names.length;
let length;
if (specialStringBehavior) {
length = DevToolsUtils.getProperty(_obj, "length");
if (typeof length != "number") {
specialStringBehavior = false;
}
}
for (let name of names) {
if (specialStringBehavior && /^[0-9]+$/.test(name)) {
let num = parseInt(name, 10);
if (num.toString() === name && num >= 0 && num < length) {
continue;
}
}
let desc = objectActor._propertyDescriptor(name, true);
if (!desc) {
continue;
}
preview.ownProperties[name] = desc;
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
if (i < OBJECT_PREVIEW_MAX_ITEMS) {
preview.safeGetterValues = objectActor._findSafeGetterValues(
Object.keys(preview.ownProperties),
OBJECT_PREVIEW_MAX_ITEMS - i);
}
return true;
}
/**
* Make a debuggee value for the given object, if needed. Primitive values
* are left the same.
*
* Use case: you have a raw JS object (after unsafe dereference) and you want to
* send it to the client. In that case you need to use an ObjectActor which
* requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
* method works only for JS objects and functions.
*
* @param Debugger.Object obj
* @param any value
* @return object
*/
function makeDebuggeeValueIfNeeded(obj, value) {
if (value && (typeof value == "object" || typeof value == "function")) {
return obj.makeDebuggeeValue(value);
}
return value;
}
// Preview functions that do not rely on the object class.
_ObjectActorPreviewers.Object = [
function TypedArray(objectActor, grip) {
let {_obj} = objectActor;
if (TYPED_ARRAY_CLASSES.indexOf(_obj.class) == -1) {
return false;
}
let length = DevToolsUtils.getProperty(_obj, "length");
if (typeof length != "number") {
return false;
}
grip.preview = {
kind: "ArrayLike",
length: length
};
if (objectActor.getGripDepth() > 1) {
return true;
}
let raw = _obj.unsafeDereference();
//let global = Cu.getGlobalForObject(DebuggerServer);
let classProto = global[_obj.class].prototype;
// The Xray machinery for TypedArrays denies indexed access on the grounds
// that it's slow, and advises callers to do a structured clone instead.
//let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
// OBJECT_PREVIEW_MAX_ITEMS), global);
let safeView = classProto.subarray.call(raw, 0,
OBJECT_PREVIEW_MAX_ITEMS);
let items = grip.preview.items = [];
for (let i = 0; i < safeView.length; i++) {
items.push(safeView[i]);
}
return true;
},
function Error(objectActor, grip) {
let {_obj} = objectActor;
switch (_obj.class) {
case "Error":
case "EvalError":
case "RangeError":
case "ReferenceError":
case "SyntaxError":
case "TypeError":
case "URIError":
let name = DevToolsUtils.getProperty(_obj, "name");
let msg = DevToolsUtils.getProperty(_obj, "message");
let stack = DevToolsUtils.getProperty(_obj, "stack");
let fileName = DevToolsUtils.getProperty(_obj, "fileName");
let lineNumber = DevToolsUtils.getProperty(_obj, "lineNumber");
let columnNumber = DevToolsUtils.getProperty(_obj, "columnNumber");
grip.preview = {
kind: "Error",
name: objectActor.getGrip(name),
message: objectActor.getGrip(msg),
stack: objectActor.getGrip(stack),
fileName: objectActor.getGrip(fileName),
lineNumber: objectActor.getGrip(lineNumber),
columnNumber: objectActor.getGrip(columnNumber)
};
return true;
default:
return false;
}
},
/*function CSSMediaRule({obj, hooks}, grip, rawObj) {
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSMediaRule)) {
return false;
}
grip.preview = {
kind: "ObjectWithText",
text: hooks.createValueGrip(rawObj.conditionText),
};
return true;
},
function CSSStyleRule({obj, hooks}, grip, rawObj) {
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSStyleRule)) {
return false;
}
grip.preview = {
kind: "ObjectWithText",
text: hooks.createValueGrip(rawObj.selectorText),
};
return true;
},
function ObjectWithURL({obj, hooks}, grip, rawObj) {
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSImportRule ||
rawObj instanceof Ci.nsIDOMCSSStyleSheet ||
rawObj instanceof Ci.nsIDOMLocation ||
rawObj instanceof Ci.nsIDOMWindow)) {
return false;
}
let url;
if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
url = rawObj.location.href;
} else if (rawObj.href) {
url = rawObj.href;
} else {
return false;
}
grip.preview = {
kind: "ObjectWithURL",
url: hooks.createValueGrip(url),
};
return true;
},*/
/*function ArrayLike(objectActor, grip, rawObj) {
let {_obj} = objectActor;
if (isWorker || !rawObj ||
obj.class != "DOMStringList" &&
obj.class != "DOMTokenList" && !(rawObj instanceof Ci.nsIDOMMozNamedAttrMap ||
rawObj instanceof Ci.nsIDOMCSSRuleList ||
rawObj instanceof Ci.nsIDOMCSSValueList ||
rawObj instanceof Ci.nsIDOMFileList ||
rawObj instanceof Ci.nsIDOMFontFaceList ||
rawObj instanceof Ci.nsIDOMMediaList ||
rawObj instanceof Ci.nsIDOMNodeList ||
rawObj instanceof Ci.nsIDOMStyleSheetList)) {
return false;
}
if (typeof rawObj.length != "number") {
return false;
}
grip.preview = {
kind: "ArrayLike",
length: rawObj.length,
};
if (hooks.getGripDepth() > 1) {
return true;
}
let items = grip.preview.items = [];
for (let i = 0; i < rawObj.length &&
items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
items.push(hooks.createValueGrip(value));
}
return true;
},*/
/*function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) {
return false;
}
grip.preview = {
kind: "MapLike",
size: rawObj.length,
};
let entries = grip.preview.entries = [];
for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
i < rawObj.length; i++) {
let prop = rawObj[i];
let value = rawObj.getPropertyValue(prop);
entries.push([prop, hooks.createValueGrip(value)]);
}
return true;
},
function DOMNode({obj, hooks}, grip, rawObj) {
if (isWorker || obj.class == "Object" || !rawObj || !(rawObj instanceof Ci.nsIDOMNode)) {
return false;
}
let preview = grip.preview = {
kind: "DOMNode",
nodeType: rawObj.nodeType,
nodeName: rawObj.nodeName,
};
if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
preview.location = hooks.createValueGrip(rawObj.location.href);
} else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
preview.childNodesLength = rawObj.childNodes.length;
if (hooks.getGripDepth() < 2) {
preview.childNodes = [];
for (let node of rawObj.childNodes) {
let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
preview.childNodes.push(actor);
if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
}
} else if (rawObj instanceof Ci.nsIDOMElement) {
// Add preview for DOM element attributes.
if (rawObj instanceof Ci.nsIDOMHTMLElement) {
preview.nodeName = preview.nodeName.toLowerCase();
}
let i = 0;
preview.attributes = {};
preview.attributesLength = rawObj.attributes.length;
for (let attr of rawObj.attributes) {
preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
} else if (rawObj instanceof Ci.nsIDOMAttr) {
preview.value = hooks.createValueGrip(rawObj.value);
} else if (rawObj instanceof Ci.nsIDOMText ||
rawObj instanceof Ci.nsIDOMComment) {
preview.textContent = hooks.createValueGrip(rawObj.textContent);
}
return true;
},
function DOMEvent({obj, hooks}, grip, rawObj) {
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
return false;
}
let preview = grip.preview = {
kind: "DOMEvent",
type: rawObj.type,
properties: Object.create(null),
};
if (hooks.getGripDepth() < 2) {
let target = obj.makeDebuggeeValue(rawObj.target);
preview.target = hooks.createValueGrip(target);
}
let props = [];
if (rawObj instanceof Ci.nsIDOMMouseEvent) {
props.push("buttons", "clientX", "clientY", "layerX", "layerY");
} else if (rawObj instanceof Ci.nsIDOMKeyEvent) {
let modifiers = [];
if (rawObj.altKey) {
modifiers.push("Alt");
}
if (rawObj.ctrlKey) {
modifiers.push("Control");
}
if (rawObj.metaKey) {
modifiers.push("Meta");
}
if (rawObj.shiftKey) {
modifiers.push("Shift");
}
preview.eventKind = "key";
preview.modifiers = modifiers;
props.push("key", "charCode", "keyCode");
} else if (rawObj instanceof Ci.nsIDOMTransitionEvent) {
props.push("propertyName", "pseudoElement");
} else if (rawObj instanceof Ci.nsIDOMAnimationEvent) {
props.push("animationName", "pseudoElement");
} else if (rawObj instanceof Ci.nsIDOMClipboardEvent) {
props.push("clipboardData");
}
// Add event-specific properties.
for (let prop of props) {
let value = rawObj[prop];
if (value && (typeof value == "object" || typeof value == "function")) {
// Skip properties pointing to objects.
if (hooks.getGripDepth() > 1) {
continue;
}
value = obj.makeDebuggeeValue(value);
}
preview.properties[prop] = hooks.createValueGrip(value);
}
// Add any properties we find on the event object.
if (!props.length) {
let i = 0;
for (let prop in rawObj) {
let value = rawObj[prop];
if (prop == "target" || prop == "type" || value === null ||
typeof value == "function") {
continue;
}
if (value && typeof value == "object") {
if (hooks.getGripDepth() > 1) {
continue;
}
value = obj.makeDebuggeeValue(value);
}
preview.properties[prop] = hooks.createValueGrip(value);
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
}
return true;
},
function DOMException({obj, hooks}, grip, rawObj) {
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
return false;
}
grip.preview = {
kind: "DOMException",
name: hooks.createValueGrip(rawObj.name),
message: hooks.createValueGrip(rawObj.message),
code: hooks.createValueGrip(rawObj.code),
result: hooks.createValueGrip(rawObj.result),
filename: hooks.createValueGrip(rawObj.filename),
lineNumber: hooks.createValueGrip(rawObj.lineNumber),
columnNumber: hooks.createValueGrip(rawObj.columnNumber),
};
return true;
},*/
/*function PseudoArray({obj, hooks}, grip, rawObj) {
let length = 0;
// Making sure all keys are numbers from 0 to length-1
let keys = obj.getOwnPropertyNames();
if (keys.length == 0) {
return false;
}
for (let key of keys) {
if (isNaN(key) || key != length++) {
return false;
}
}
grip.preview = {
kind: "ArrayLike",
length: length,
};
// Avoid recursive object grips.
if (hooks.getGripDepth() > 1) {
return true;
}
let items = grip.preview.items = [];
let i = 0;
for (let key of keys) {
if (rawObj.hasOwnProperty(key) && i++ < OBJECT_PREVIEW_MAX_ITEMS) {
let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
items.push(hooks.createValueGrip(value));
}
}
return true;
},*/
GenericObject
];
export const ObjectActorPreviewers = _ObjectActorPreviewers;

View File

@@ -0,0 +1,538 @@
"use strict";
import * as DevToolsUtils from 'DevTools/DevToolsUtils.js';
//todo
//if (!isWorker) {
// loader.lazyImporter(this, "Parser", "resource://devtools/shared/Parser.jsm");
//}
// Provide an easy way to bail out of even attempting an autocompletion
// if an object has way too many properties. Protects against large objects
// with numeric values that wouldn't be tallied towards MAX_AUTOCOMPLETIONS.
export const MAX_AUTOCOMPLETE_ATTEMPTS = 100000;
// Prevent iterating over too many properties during autocomplete suggestions.
export const MAX_AUTOCOMPLETIONS = 1500;
const STATE_NORMAL = 0;
const STATE_QUOTE = 2;
const STATE_DQUOTE = 3;
const OPEN_BODY = "{[(".split("");
const CLOSE_BODY = "}])".split("");
const OPEN_CLOSE_BODY = {
"{": "}",
"[": "]",
"(": ")"
};
function hasArrayIndex(str) {
return /\[\d+\]$/.test(str);
}
/**
* Analyses a given string to find the last statement that is interesting for
* later completion.
*
* @param string aStr
* A string to analyse.
*
* @returns object
* If there was an error in the string detected, then a object like
*
* { err: "ErrorMesssage" }
*
* is returned, otherwise a object like
*
* {
* state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
* startPos: index of where the last statement begins
* }
*/
function findCompletionBeginning(aStr) {
let bodyStack = [];
let state = STATE_NORMAL;
let start = 0;
let c;
for (let i = 0; i < aStr.length; i++) {
c = aStr[i];
switch (state) {
// Normal JS state.
case STATE_NORMAL:
if (c == '"') {
state = STATE_DQUOTE;
}
else if (c == "'") {
state = STATE_QUOTE;
}
else if (c == ";") {
start = i + 1;
}
else if (c == " ") {
start = i + 1;
}
else if (OPEN_BODY.indexOf(c) != -1) {
bodyStack.push({
token: c,
start: start
});
start = i + 1;
}
else if (CLOSE_BODY.indexOf(c) != -1) {
var last = bodyStack.pop();
if (!last || OPEN_CLOSE_BODY[last.token] != c) {
return {
err: "syntax error"
};
}
if (c == "}") {
start = i + 1;
}
else {
start = last.start;
}
}
break;
// Double quote state > " <
case STATE_DQUOTE:
if (c == "\\") {
i++;
}
else if (c == "\n") {
return {
err: "unterminated string literal"
};
}
else if (c == '"') {
state = STATE_NORMAL;
}
break;
// Single quote state > ' <
case STATE_QUOTE:
if (c == "\\") {
i++;
}
else if (c == "\n") {
return {
err: "unterminated string literal"
};
}
else if (c == "'") {
state = STATE_NORMAL;
}
break;
}
}
return {
state: state,
startPos: start
};
}
/**
* Provides a list of properties, that are possible matches based on the passed
* Debugger.Environment/Debugger.Object and inputValue.
*
* @param object aDbgObject
* When the debugger is not paused this Debugger.Object wraps the scope for autocompletion.
* It is null if the debugger is paused.
* @param object anEnvironment
* When the debugger is paused this Debugger.Environment is the scope for autocompletion.
* It is null if the debugger is not paused.
* @param string aInputValue
* Value that should be completed.
* @param number [aCursor=aInputValue.length]
* Optional offset in the input where the cursor is located. If this is
* omitted then the cursor is assumed to be at the end of the input
* value.
* @returns null or object
* If no completion valued could be computed, null is returned,
* otherwise a object with the following form is returned:
* {
* matches: [ string, string, string ],
* matchProp: Last part of the inputValue that was used to find
* the matches-strings.
* }
*/
export function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor) {
if (aCursor === undefined) {
aCursor = aInputValue.length;
}
let inputValue = aInputValue.substring(0, aCursor);
// Analyse the inputValue and find the beginning of the last part that
// should be completed.
let beginning = findCompletionBeginning(inputValue);
// There was an error analysing the string.
if (beginning.err) {
return null;
}
// If the current state is not STATE_NORMAL, then we are inside of an string
// which means that no completion is possible.
if (beginning.state != STATE_NORMAL) {
return null;
}
let completionPart = inputValue.substring(beginning.startPos);
let lastDot = completionPart.lastIndexOf(".");
// Don't complete on just an empty string.
if (completionPart.trim() == "") {
return null;
}
// Catch literals like [1,2,3] or "foo" and return the matches from
// their prototypes.
// Don't run this is a worker, migrating to acorn should allow this
// to run in a worker - Bug 1217198.
//todo
/*if (!isWorker && lastDot > 0) {
let parser = new Parser();
parser.logExceptions = false;
let syntaxTree = parser.get(completionPart.slice(0, lastDot));
let lastTree = syntaxTree.getLastSyntaxTree();
let lastBody = lastTree && lastTree.AST.body[lastTree.AST.body.length - 1];
// Finding the last expression since we've sliced up until the dot.
// If there were parse errors this won't exist.
if (lastBody) {
let expression = lastBody.expression;
let matchProp = completionPart.slice(lastDot + 1);
if (expression.type === "ArrayExpression") {
return getMatchedProps(Array.prototype, matchProp);
} else if (expression.type === "Literal" &&
(typeof expression.value === "string")) {
return getMatchedProps(String.prototype, matchProp);
}
}
}*/
// We are completing a variable / a property lookup.
let properties = completionPart.split(".");
let matchProp = properties.pop().trimLeft();
let obj = aDbgObject;
// The first property must be found in the environment of the paused debugger
// or of the global lexical scope.
let env = anEnvironment || obj.asEnvironment();
if (properties.length === 0) {
return getMatchedPropsInEnvironment(env, matchProp);
}
let firstProp = properties.shift().trim();
if (firstProp === "this") {
// Special case for 'this' - try to get the Object from the Environment.
// No problem if it throws, we will just not autocomplete.
try {
obj = env.object;
} catch (e) {
}
}
else if (hasArrayIndex(firstProp)) {
obj = getArrayMemberProperty(null, env, firstProp);
} else {
obj = getVariableInEnvironment(env, firstProp);
}
if (!isObjectUsable(obj)) {
return null;
}
// We get the rest of the properties recursively starting from the Debugger.Object
// that wraps the first property
for (let i = 0; i < properties.length; i++) {
let prop = properties[i].trim();
if (!prop) {
return null;
}
if (hasArrayIndex(prop)) {
// The property to autocomplete is a member of array. For example
// list[i][j]..[n]. Traverse the array to get the actual element.
obj = getArrayMemberProperty(obj, null, prop);
}
else {
obj = DevToolsUtils.getProperty(obj, prop);
}
if (!isObjectUsable(obj)) {
return null;
}
}
// If the final property is a primitive
if (typeof obj != "object") {
return getMatchedProps(obj, matchProp);
}
return getMatchedPropsInDbgObject(obj, matchProp);
}
/**
* Get the array member of aObj for the given aProp. For example, given
* aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
*
* @param object aObj
* The object to operate on. Should be null if aEnv is passed.
* @param object aEnv
* The Environment to operate in. Should be null if aObj is passed.
* @param string aProp
* The property to return.
* @return null or Object
* Returns null if the property couldn't be located. Otherwise the array
* member identified by aProp.
*/
function getArrayMemberProperty(aObj, aEnv, aProp) {
// First get the array.
let obj = aObj;
let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
if (aEnv) {
obj = getVariableInEnvironment(aEnv, propWithoutIndices);
} else {
obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
}
if (!isObjectUsable(obj)) {
return null;
}
// Then traverse the list of indices to get the actual element.
let result;
let arrayIndicesRegex = /\[[^\]]*\]/g;
while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
let indexWithBrackets = result[0];
let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
let index = parseInt(indexAsText);
if (isNaN(index)) {
return null;
}
obj = DevToolsUtils.getProperty(obj, index);
if (!isObjectUsable(obj)) {
return null;
}
}
return obj;
}
/**
* Check if the given Debugger.Object can be used for autocomplete.
*
* @param Debugger.Object aObject
* The Debugger.Object to check.
* @return boolean
* True if further inspection into the object is possible, or false
* otherwise.
*/
function isObjectUsable(aObject) {
if (aObject == null) {
return false;
}
if (typeof aObject == "object" && aObject.class == "DeadObject") {
return false;
}
return true;
}
/**
* @see getExactMatch_impl()
*/
function getVariableInEnvironment(anEnvironment, aName) {
return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
}
/**
* @see getMatchedProps_impl()
*/
function getMatchedPropsInEnvironment(anEnvironment, aMatch) {
return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
}
/**
* @see getMatchedProps_impl()
*/
function getMatchedPropsInDbgObject(aDbgObject, aMatch) {
return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
}
/**
* @see getMatchedProps_impl()
*/
function getMatchedProps(aObj, aMatch) {
if (typeof aObj != "object") {
aObj = aObj.constructor.prototype;
}
return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
}
/**
* Get all properties in the given object (and its parent prototype chain) that
* match a given prefix.
*
* @param mixed aObj
* Object whose properties we want to filter.
* @param string aMatch
* Filter for properties that match this string.
* @return object
* Object that contains the matchProp and the list of names.
*/
function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties}) {
let matches = new Set();
let numProps = 0;
// We need to go up the prototype chain.
let iter = chainIterator(aObj);
for (let obj of iter) {
let props = getProperties(obj);
numProps += props.length;
// If there are too many properties to event attempt autocompletion,
// or if we have already added the max number, then stop looping
// and return the partial set that has already been discovered.
if (numProps >= MAX_AUTOCOMPLETE_ATTEMPTS ||
matches.size >= MAX_AUTOCOMPLETIONS) {
break;
}
for (let i = 0; i < props.length; i++) {
let prop = props[i];
if (prop.indexOf(aMatch) != 0) {
continue;
}
if (prop.indexOf('-') > -1) {
continue;
}
// If it is an array index, we can't take it.
// This uses a trick: converting a string to a number yields NaN if
// the operation failed, and NaN is not equal to itself.
if (+prop != +prop) {
matches.add(prop);
}
if (matches.size >= MAX_AUTOCOMPLETIONS) {
break;
}
}
}
return {
matchProp: aMatch,
matches: [...matches],
};
}
/**
* Returns a property value based on its name from the given object, by
* recursively checking the object's prototype.
*
* @param object aObj
* An object to look the property into.
* @param string aName
* The property that is looked up.
* @returns object|undefined
* A Debugger.Object if the property exists in the object's prototype
* chain, undefined otherwise.
*/
function getExactMatch_impl(aObj, aName, {chainIterator, getProperty}) {
// We need to go up the prototype chain.
let iter = chainIterator(aObj);
for (let obj of iter) {
let prop = getProperty(obj, aName, aObj);
if (prop) {
return prop.value;
}
}
return undefined;
}
var JSObjectSupport = {
chainIterator: function*(aObj) {
while (aObj) {
yield aObj;
aObj = Object.getPrototypeOf(aObj);
}
},
getProperties: function (aObj) {
return Object.getOwnPropertyNames(aObj);
},
getProperty: function () {
// getProperty is unsafe with raw JS objects.
throw "Unimplemented!";
},
};
var DebuggerObjectSupport = {
chainIterator: function*(aObj) {
while (aObj) {
yield aObj;
aObj = aObj.proto;
}
},
getProperties: function (aObj) {
return aObj.getOwnPropertyNames();
},
getProperty: function (aObj, aName, aRootObj) {
// This is left unimplemented in favor to DevToolsUtils.getProperty().
throw "Unimplemented!";
},
};
var DebuggerEnvironmentSupport = {
chainIterator: function*(aObj) {
while (aObj) {
yield aObj;
aObj = aObj.parent;
}
},
getProperties: function (aObj) {
let names = aObj.names();
// Include 'this' in results (in sorted order)
for (let i = 0; i < names.length; i++) {
if (i === names.length - 1 || names[i + 1] > "this") {
names.splice(i + 1, 0, "this");
break;
}
}
return names;
},
getProperty: function (aObj, aName) {
let result;
// Try/catch since aName can be anything, and getVariable throws if
// it's not a valid ECMAScript identifier name
try {
// TODO: we should use getVariableDescriptor() here - bug 725815.
result = aObj.getVariable(aName);
} catch (e) {
}
// FIXME: Need actual UI, bug 941287.
if (result === undefined || result.optimizedOut || result.missingArguments) {
return null;
}
return {value: result};
}
};

View File

@@ -0,0 +1,139 @@
import * as DevToolsUtils from 'DevTools/DevToolsUtils.js';
/**
* Stringify a Debugger.Object based on its class.
*
* @param Debugger.Object obj
* The object to stringify.
* @return String
* The stringification for the object.
*/
export function stringify(obj) {
if (obj.class == "DeadObject") {
const error = new Error("Dead object encountered.");
DevToolsUtils.reportException("stringify", error);
return "<dead object>";
}
const stringifier = stringifiers[obj.class] || stringifiers.Object;
try {
return stringifier(obj);
} catch (e) {
DevToolsUtils.reportException("stringify", e);
return "<failed to stringify object>";
}
};
/**
* Determine if a given value is non-primitive.
*
* @param Any value
* The value to test.
* @return Boolean
* Whether the value is non-primitive.
*/
function isObject(value) {
const type = typeof value;
return type == "object" ? value !== null : type == "function";
}
/**
* Create a function that can safely stringify Debugger.Objects of a given
* builtin type.
*
* @param Function ctor
* The builtin class constructor.
* @return Function
* The stringifier for the class.
*/
function createBuiltinStringifier(ctor) {
return obj => ctor.prototype.toString.call(obj.unsafeDereference());
}
/**
* Stringify a Debugger.Object-wrapped Error instance.
*
* @param Debugger.Object obj
* The object to stringify.
* @return String
* The stringification of the object.
*/
function errorStringify(obj) {
let name = DevToolsUtils.getProperty(obj, "name");
if (name === "" || name === undefined) {
name = obj.class;
} else if (isObject(name)) {
name = stringify(name);
}
let message = DevToolsUtils.getProperty(obj, "message");
if (isObject(message)) {
message = stringify(message);
}
if (message === "" || message === undefined) {
return name;
}
return name + ": " + message;
}
// Used to prevent infinite recursion when an array is found inside itself.
let seen = null;
const stringifiers = {
Error: errorStringify,
EvalError: errorStringify,
RangeError: errorStringify,
ReferenceError: errorStringify,
SyntaxError: errorStringify,
TypeError: errorStringify,
URIError: errorStringify,
Boolean: createBuiltinStringifier(Boolean),
Function: createBuiltinStringifier(Function),
Number: createBuiltinStringifier(Number),
RegExp: createBuiltinStringifier(RegExp),
String: createBuiltinStringifier(String),
Object: obj => "[object " + obj.class + "]",
Array: obj => {
// If we're at the top level then we need to create the Set for tracking
// previously stringified arrays.
const topLevel = !seen;
if (topLevel) {
seen = new Set();
} else if (seen.has(obj)) {
return "";
}
seen.add(obj);
const len = DevToolsUtils.getProperty(obj, "length");
let string = "";
// The following check is only required because the debuggee could possibly
// be a Proxy and return any value. For normal objects, array.length is
// always a non-negative integer.
if (typeof len == "number" && len > 0) {
for (let i = 0; i < len; i++) {
const desc = obj.getOwnPropertyDescriptor(i);
if (desc) {
const { value } = desc;
if (value != null) {
string += isObject(value) ? stringify(value) : value;
}
}
if (i < len - 1) {
string += ",";
}
}
}
if (topLevel) {
seen = null;
}
return string;
}
};

View File

@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// A basic synchronous module loader for testing the shell.
let {coreModulesPath, parseModule, setModuleResolveHook, parseModuleRes, _coreModulesInRes} = process.binding('modules');
let {loadFile, relToAbs} = process.binding('fs');
Reflect.Loader = new class {
constructor() {
this.registry = new Map();
this.loadPath = coreModulesPath;
}
resolve(name) {
return relToAbs(this.loadPath, name);
}
fetch(path) {
//return os.file.readFile(path);
return loadFile(path);
}
loadAndParse(name) {
let path = _coreModulesInRes ? name.toUpperCase() : this.resolve(name);
if (this.registry.has(path))
return this.registry.get(path);
let module;
if (_coreModulesInRes) {
module = parseModuleRes(path);
} else {
let source = this.fetch(path);
module = parseModule(source, path);
}
this.registry.set(path, module);
return module;
}
["import"](name, referrer) {
let module = this.loadAndParse(name);
module.declarationInstantiation();
return module.evaluation();
}
};
setModuleResolveHook((module, requestName) => Reflect.Loader.loadAndParse(requestName));

View File

@@ -0,0 +1 @@
../../../../../libs/Synopse/SyNode/core_modules

View File

@@ -0,0 +1,57 @@
// a duplex stream is just a stream that is both readable and writable.
// Since JS doesn't have multiple prototypal inheritance, this class
// prototypally inherits from Readable, and then parasitically from
// Writable.
'use strict';
module.exports = Duplex;
const util = require('util');
const Readable = require('_stream_readable');
const Writable = require('_stream_writable');
util.inherits(Duplex, Readable);
var keys = Object.keys(Writable.prototype);
for (var v = 0; v < keys.length; v++) {
var method = keys[v];
if (!Duplex.prototype[method])
Duplex.prototype[method] = Writable.prototype[method];
}
function Duplex(options) {
if (!(this instanceof Duplex))
return new Duplex(options);
Readable.call(this, options);
Writable.call(this, options);
if (options && options.readable === false)
this.readable = false;
if (options && options.writable === false)
this.writable = false;
this.allowHalfOpen = true;
if (options && options.allowHalfOpen === false)
this.allowHalfOpen = false;
this.once('end', onend);
}
// the no-half-open enforcer
function onend() {
// if we allow half-open state, or if the writable side ended,
// then we're ok.
if (this.allowHalfOpen || this._writableState.ended)
return;
// no more data can be written.
// But allow more writes to happen in this tick.
process.nextTick(onEndNT, this);
}
function onEndNT(self) {
self.end();
}

View File

@@ -0,0 +1,22 @@
// a passthrough stream.
// basically just the most minimal sort of Transform stream.
// Every written chunk gets output as-is.
'use strict';
module.exports = PassThrough;
const Transform = require('_stream_transform');
const util = require('util');
util.inherits(PassThrough, Transform);
function PassThrough(options) {
if (!(this instanceof PassThrough))
return new PassThrough(options);
Transform.call(this, options);
}
PassThrough.prototype._transform = function(chunk, encoding, cb) {
cb(null, chunk);
};

View File

@@ -0,0 +1,976 @@
'use strict';
module.exports = Readable;
Readable.ReadableState = ReadableState;
const EE = require('events');
const Stream = require('stream');
const Buffer = require('buffer').Buffer;
const util = require('util');
const debug = util.debuglog('stream');
const BufferList = require('internal/streams/BufferList');
var StringDecoder;
util.inherits(Readable, Stream);
function prependListener(emitter, event, fn) {
// Sadly this is not cacheable as some libraries bundle their own
// event emitter implementation with them.
if (typeof emitter.prependListener === 'function') {
return emitter.prependListener(event, fn);
} else {
// This is a hack to make sure that our error handler is attached before any
// userland ones. NEVER DO THIS. This is here only because this code needs
// to continue to work with older versions of Node.js that do not include
// the prependListener() method. The goal is to eventually remove this hack.
if (!emitter._events || !emitter._events[event])
emitter.on(event, fn);
else if (Array.isArray(emitter._events[event]))
emitter._events[event].unshift(fn);
else
emitter._events[event] = [fn, emitter._events[event]];
}
}
function ReadableState(options, stream) {
options = options || {};
// object stream flag. Used to make read(n) ignore n and to
// make all the buffer merging and length checks go away
this.objectMode = !!options.objectMode;
if (stream instanceof Stream.Duplex)
this.objectMode = this.objectMode || !!options.readableObjectMode;
// the point at which it stops calling _read() to fill the buffer
// Note: 0 is a valid value, means "don't call _read preemptively ever"
var hwm = options.highWaterMark;
var defaultHwm = this.objectMode ? 16 : 16 * 1024;
this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm;
// cast to ints.
this.highWaterMark = ~~this.highWaterMark;
// A linked list is used to store data chunks instead of an array because the
// linked list can remove elements from the beginning faster than
// array.shift()
this.buffer = new BufferList();
this.length = 0;
this.pipes = null;
this.pipesCount = 0;
this.flowing = null;
this.ended = false;
this.endEmitted = false;
this.reading = false;
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick. We set this to true at first, because any
// actions that shouldn't happen until "later" should generally also
// not happen before the first write call.
this.sync = true;
// whenever we return null, then we set a flag to say
// that we're awaiting a 'readable' event emission.
this.needReadable = false;
this.emittedReadable = false;
this.readableListening = false;
this.resumeScheduled = false;
// Crypto is kind of old and crusty. Historically, its default string
// encoding is 'binary' so we have to make this configurable.
// Everything else in the universe uses 'utf8', though.
this.defaultEncoding = options.defaultEncoding || 'utf8';
// when piping, we only care about 'readable' events that happen
// after read()ing all the bytes and not getting any pushback.
this.ranOut = false;
// the number of writers that are awaiting a drain event in .pipe()s
this.awaitDrain = 0;
// if true, a maybeReadMore has been scheduled
this.readingMore = false;
this.decoder = null;
this.encoding = null;
if (options.encoding) {
if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this.decoder = new StringDecoder(options.encoding);
this.encoding = options.encoding;
}
}
function Readable(options) {
if (!(this instanceof Readable))
return new Readable(options);
this._readableState = new ReadableState(options, this);
// legacy
this.readable = true;
if (options && typeof options.read === 'function')
this._read = options.read;
Stream.call(this);
}
// Manually shove something into the read() buffer.
// This returns true if the highWaterMark has not been hit yet,
// similar to how Writable.write() returns true if you should
// write() some more.
Readable.prototype.push = function(chunk, encoding) {
var state = this._readableState;
if (!state.objectMode && typeof chunk === 'string') {
encoding = encoding || state.defaultEncoding;
if (encoding !== state.encoding) {
chunk = Buffer.from(chunk, encoding);
encoding = '';
}
}
return readableAddChunk(this, state, chunk, encoding, false);
};
// Unshift should *always* be something directly out of read()
Readable.prototype.unshift = function(chunk) {
var state = this._readableState;
return readableAddChunk(this, state, chunk, '', true);
};
Readable.prototype.isPaused = function() {
return this._readableState.flowing === false;
};
function readableAddChunk(stream, state, chunk, encoding, addToFront) {
var er = chunkInvalid(state, chunk);
if (er) {
stream.emit('error', er);
} else if (chunk === null) {
state.reading = false;
onEofChunk(stream, state);
} else if (state.objectMode || chunk && chunk.length > 0) {
if (state.ended && !addToFront) {
const e = new Error('stream.push() after EOF');
stream.emit('error', e);
} else if (state.endEmitted && addToFront) {
const e = new Error('stream.unshift() after end event');
stream.emit('error', e);
} else {
var skipAdd;
if (state.decoder && !addToFront && !encoding) {
chunk = state.decoder.write(chunk);
skipAdd = (!state.objectMode && chunk.length === 0);
}
if (!addToFront)
state.reading = false;
// Don't add to the buffer if we've decoded to an empty string chunk and
// we're not in object mode
if (!skipAdd) {
// if we want the data now, just emit it.
if (state.flowing && state.length === 0 && !state.sync) {
stream.emit('data', chunk);
stream.read(0);
} else {
// update the buffer info.
state.length += state.objectMode ? 1 : chunk.length;
if (addToFront)
state.buffer.unshift(chunk);
else
state.buffer.push(chunk);
if (state.needReadable)
emitReadable(stream);
}
}
maybeReadMore(stream, state);
}
} else if (!addToFront) {
state.reading = false;
}
return needMoreData(state);
}
// if it's past the high water mark, we can push in some more.
// Also, if we have no data yet, we can stand some
// more bytes. This is to work around cases where hwm=0,
// such as the repl. Also, if the push() triggered a
// readable event, and the user called read(largeNumber) such that
// needReadable was set, then we ought to push more, so that another
// 'readable' event will be triggered.
function needMoreData(state) {
return !state.ended &&
(state.needReadable ||
state.length < state.highWaterMark ||
state.length === 0);
}
// backwards compatibility.
Readable.prototype.setEncoding = function(enc) {
if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this._readableState.decoder = new StringDecoder(enc);
this._readableState.encoding = enc;
return this;
};
// Don't raise the hwm > 8MB
const MAX_HWM = 0x800000;
function computeNewHighWaterMark(n) {
if (n >= MAX_HWM) {
n = MAX_HWM;
} else {
// Get the next highest power of 2 to prevent increasing hwm excessively in
// tiny amounts
n--;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
n++;
}
return n;
}
// This function is designed to be inlinable, so please take care when making
// changes to the function body.
function howMuchToRead(n, state) {
if (n <= 0 || (state.length === 0 && state.ended))
return 0;
if (state.objectMode)
return 1;
if (n !== n) {
// Only flow one buffer at a time
if (state.flowing && state.length)
return state.buffer.head.data.length;
else
return state.length;
}
// If we're asking for more than the current hwm, then raise the hwm.
if (n > state.highWaterMark)
state.highWaterMark = computeNewHighWaterMark(n);
if (n <= state.length)
return n;
// Don't have enough
if (!state.ended) {
state.needReadable = true;
return 0;
}
return state.length;
}
// you can override either this method, or the async _read(n) below.
Readable.prototype.read = function(n) {
debug('read', n);
n = parseInt(n, 10);
var state = this._readableState;
var nOrig = n;
if (n !== 0)
state.emittedReadable = false;
// if we're doing read(0) to trigger a readable event, but we
// already have a bunch of data in the buffer, then just trigger
// the 'readable' event and move on.
if (n === 0 &&
state.needReadable &&
(state.length >= state.highWaterMark || state.ended)) {
debug('read: emitReadable', state.length, state.ended);
if (state.length === 0 && state.ended)
endReadable(this);
else
emitReadable(this);
return null;
}
n = howMuchToRead(n, state);
// if we've ended, and we're now clear, then finish it up.
if (n === 0 && state.ended) {
if (state.length === 0)
endReadable(this);
return null;
}
// All the actual chunk generation logic needs to be
// *below* the call to _read. The reason is that in certain
// synthetic stream cases, such as passthrough streams, _read
// may be a completely synchronous operation which may change
// the state of the read buffer, providing enough data when
// before there was *not* enough.
//
// So, the steps are:
// 1. Figure out what the state of things will be after we do
// a read from the buffer.
//
// 2. If that resulting state will trigger a _read, then call _read.
// Note that this may be asynchronous, or synchronous. Yes, it is
// deeply ugly to write APIs this way, but that still doesn't mean
// that the Readable class should behave improperly, as streams are
// designed to be sync/async agnostic.
// Take note if the _read call is sync or async (ie, if the read call
// has returned yet), so that we know whether or not it's safe to emit
// 'readable' etc.
//
// 3. Actually pull the requested chunks out of the buffer and return.
// if we need a readable event, then we need to do some reading.
var doRead = state.needReadable;
debug('need readable', doRead);
// if we currently have less than the highWaterMark, then also read some
if (state.length === 0 || state.length - n < state.highWaterMark) {
doRead = true;
debug('length less than watermark', doRead);
}
// however, if we've ended, then there's no point, and if we're already
// reading, then it's unnecessary.
if (state.ended || state.reading) {
doRead = false;
debug('reading or ended', doRead);
} else if (doRead) {
debug('do read');
state.reading = true;
state.sync = true;
// if the length is currently zero, then we *need* a readable event.
if (state.length === 0)
state.needReadable = true;
// call internal read method
this._read(state.highWaterMark);
state.sync = false;
// If _read pushed data synchronously, then `reading` will be false,
// and we need to re-evaluate how much data we can return to the user.
if (!state.reading)
n = howMuchToRead(nOrig, state);
}
var ret;
if (n > 0)
ret = fromList(n, state);
else
ret = null;
if (ret === null) {
state.needReadable = true;
n = 0;
} else {
state.length -= n;
}
if (state.length === 0) {
// If we have nothing in the buffer, then we want to know
// as soon as we *do* get something into the buffer.
if (!state.ended)
state.needReadable = true;
// If we tried to read() past the EOF, then emit end on the next tick.
if (nOrig !== n && state.ended)
endReadable(this);
}
if (ret !== null)
this.emit('data', ret);
return ret;
};
function chunkInvalid(state, chunk) {
var er = null;
if (!(chunk instanceof Buffer) &&
typeof chunk !== 'string' &&
chunk !== null &&
chunk !== undefined &&
!state.objectMode) {
er = new TypeError('Invalid non-string/buffer chunk');
}
return er;
}
function onEofChunk(stream, state) {
if (state.ended) return;
if (state.decoder) {
var chunk = state.decoder.end();
if (chunk && chunk.length) {
state.buffer.push(chunk);
state.length += state.objectMode ? 1 : chunk.length;
}
}
state.ended = true;
// emit 'readable' now to make sure it gets picked up.
emitReadable(stream);
}
// Don't emit readable right away in sync mode, because this can trigger
// another read() call => stack overflow. This way, it might trigger
// a nextTick recursion warning, but that's not so bad.
function emitReadable(stream) {
var state = stream._readableState;
state.needReadable = false;
if (!state.emittedReadable) {
debug('emitReadable', state.flowing);
state.emittedReadable = true;
if (state.sync)
process.nextTick(emitReadable_, stream);
else
emitReadable_(stream);
}
}
function emitReadable_(stream) {
debug('emit readable');
stream.emit('readable');
flow(stream);
}
// at this point, the user has presumably seen the 'readable' event,
// and called read() to consume some data. that may have triggered
// in turn another _read(n) call, in which case reading = true if
// it's in progress.
// However, if we're not ended, or reading, and the length < hwm,
// then go ahead and try to read some more preemptively.
function maybeReadMore(stream, state) {
if (!state.readingMore) {
state.readingMore = true;
process.nextTick(maybeReadMore_, stream, state);
}
}
function maybeReadMore_(stream, state) {
var len = state.length;
while (!state.reading && !state.flowing && !state.ended &&
state.length < state.highWaterMark) {
debug('maybeReadMore read 0');
stream.read(0);
if (len === state.length)
// didn't get any data, stop spinning.
break;
else
len = state.length;
}
state.readingMore = false;
}
// abstract method. to be overridden in specific implementation classes.
// call cb(er, data) where data is <= n in length.
// for virtual (non-string, non-buffer) streams, "length" is somewhat
// arbitrary, and perhaps not very meaningful.
Readable.prototype._read = function(n) {
this.emit('error', new Error('not implemented'));
};
Readable.prototype.pipe = function(dest, pipeOpts) {
var src = this;
var state = this._readableState;
switch (state.pipesCount) {
case 0:
state.pipes = dest;
break;
case 1:
state.pipes = [state.pipes, dest];
break;
default:
state.pipes.push(dest);
break;
}
state.pipesCount += 1;
debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts);
var doEnd = (!pipeOpts || pipeOpts.end !== false) &&
dest !== process.stdout &&
dest !== process.stderr;
var endFn = doEnd ? onend : cleanup;
if (state.endEmitted)
process.nextTick(endFn);
else
src.once('end', endFn);
dest.on('unpipe', onunpipe);
function onunpipe(readable) {
debug('onunpipe');
if (readable === src) {
cleanup();
}
}
function onend() {
debug('onend');
dest.end();
}
// when the dest drains, it reduces the awaitDrain counter
// on the source. This would be more elegant with a .once()
// handler in flow(), but adding and removing repeatedly is
// too slow.
var ondrain = pipeOnDrain(src);
dest.on('drain', ondrain);
var cleanedUp = false;
function cleanup() {
debug('cleanup');
// cleanup event handlers once the pipe is broken
dest.removeListener('close', onclose);
dest.removeListener('finish', onfinish);
dest.removeListener('drain', ondrain);
dest.removeListener('error', onerror);
dest.removeListener('unpipe', onunpipe);
src.removeListener('end', onend);
src.removeListener('end', cleanup);
src.removeListener('data', ondata);
cleanedUp = true;
// if the reader is waiting for a drain event from this
// specific writer, then it would cause it to never start
// flowing again.
// So, if this is awaiting a drain, then we just call it now.
// If we don't know, then assume that we are waiting for one.
if (state.awaitDrain &&
(!dest._writableState || dest._writableState.needDrain))
ondrain();
}
// If the user pushes more data while we're writing to dest then we'll end up
// in ondata again. However, we only want to increase awaitDrain once because
// dest will only emit one 'drain' event for the multiple writes.
// => Introduce a guard on increasing awaitDrain.
var increasedAwaitDrain = false;
src.on('data', ondata);
function ondata(chunk) {
debug('ondata');
increasedAwaitDrain = false;
var ret = dest.write(chunk);
if (false === ret && !increasedAwaitDrain) {
// If the user unpiped during `dest.write()`, it is possible
// to get stuck in a permanently paused state if that write
// also returned false.
// => Check whether `dest` is still a piping destination.
if (((state.pipesCount === 1 && state.pipes === dest) ||
(state.pipesCount > 1 && state.pipes.indexOf(dest) !== -1)) &&
!cleanedUp) {
debug('false write response, pause', src._readableState.awaitDrain);
src._readableState.awaitDrain++;
increasedAwaitDrain = true;
}
src.pause();
}
}
// if the dest has an error, then stop piping into it.
// however, don't suppress the throwing behavior for this.
function onerror(er) {
debug('onerror', er);
unpipe();
dest.removeListener('error', onerror);
if (EE.listenerCount(dest, 'error') === 0)
dest.emit('error', er);
}
// Make sure our error handler is attached before userland ones.
prependListener(dest, 'error', onerror);
// Both close and finish should trigger unpipe, but only once.
function onclose() {
dest.removeListener('finish', onfinish);
unpipe();
}
dest.once('close', onclose);
function onfinish() {
debug('onfinish');
dest.removeListener('close', onclose);
unpipe();
}
dest.once('finish', onfinish);
function unpipe() {
debug('unpipe');
src.unpipe(dest);
}
// tell the dest that it's being piped to
dest.emit('pipe', src);
// start the flow if it hasn't been started already.
if (!state.flowing) {
debug('pipe resume');
src.resume();
}
return dest;
};
function pipeOnDrain(src) {
return function() {
var state = src._readableState;
debug('pipeOnDrain', state.awaitDrain);
if (state.awaitDrain)
state.awaitDrain--;
if (state.awaitDrain === 0 && EE.listenerCount(src, 'data')) {
state.flowing = true;
flow(src);
}
};
}
Readable.prototype.unpipe = function(dest) {
var state = this._readableState;
// if we're not piping anywhere, then do nothing.
if (state.pipesCount === 0)
return this;
// just one destination. most common case.
if (state.pipesCount === 1) {
// passed in one, but it's not the right one.
if (dest && dest !== state.pipes)
return this;
if (!dest)
dest = state.pipes;
// got a match.
state.pipes = null;
state.pipesCount = 0;
state.flowing = false;
if (dest)
dest.emit('unpipe', this);
return this;
}
// slow case. multiple pipe destinations.
if (!dest) {
// remove all.
var dests = state.pipes;
var len = state.pipesCount;
state.pipes = null;
state.pipesCount = 0;
state.flowing = false;
for (let i = 0; i < len; i++)
dests[i].emit('unpipe', this);
return this;
}
// try to find the right one.
const i = state.pipes.indexOf(dest);
if (i === -1)
return this;
state.pipes.splice(i, 1);
state.pipesCount -= 1;
if (state.pipesCount === 1)
state.pipes = state.pipes[0];
dest.emit('unpipe', this);
return this;
};
// set up data events if they are asked for
// Ensure readable listeners eventually get something
Readable.prototype.on = function(ev, fn) {
const res = Stream.prototype.on.call(this, ev, fn);
if (ev === 'data') {
// Start flowing on next tick if stream isn't explicitly paused
if (this._readableState.flowing !== false)
this.resume();
} else if (ev === 'readable') {
const state = this._readableState;
if (!state.endEmitted && !state.readableListening) {
state.readableListening = state.needReadable = true;
state.emittedReadable = false;
if (!state.reading) {
process.nextTick(nReadingNextTick, this);
} else if (state.length) {
emitReadable(this, state);
}
}
}
return res;
};
Readable.prototype.addListener = Readable.prototype.on;
function nReadingNextTick(self) {
debug('readable nexttick read 0');
self.read(0);
}
// pause() and resume() are remnants of the legacy readable stream API
// If the user uses them, then switch into old mode.
Readable.prototype.resume = function() {
var state = this._readableState;
if (!state.flowing) {
debug('resume');
state.flowing = true;
resume(this, state);
}
return this;
};
function resume(stream, state) {
if (!state.resumeScheduled) {
state.resumeScheduled = true;
process.nextTick(resume_, stream, state);
}
}
function resume_(stream, state) {
if (!state.reading) {
debug('resume read 0');
stream.read(0);
}
state.resumeScheduled = false;
state.awaitDrain = 0;
stream.emit('resume');
flow(stream);
if (state.flowing && !state.reading)
stream.read(0);
}
Readable.prototype.pause = function() {
debug('call pause flowing=%j', this._readableState.flowing);
if (false !== this._readableState.flowing) {
debug('pause');
this._readableState.flowing = false;
this.emit('pause');
}
return this;
};
function flow(stream) {
const state = stream._readableState;
debug('flow', state.flowing);
while (state.flowing && stream.read() !== null);
}
// wrap an old-style stream as the async data source.
// This is *not* part of the readable stream interface.
// It is an ugly unfortunate mess of history.
Readable.prototype.wrap = function(stream) {
var state = this._readableState;
var paused = false;
var self = this;
stream.on('end', function() {
debug('wrapped end');
if (state.decoder && !state.ended) {
var chunk = state.decoder.end();
if (chunk && chunk.length)
self.push(chunk);
}
self.push(null);
});
stream.on('data', function(chunk) {
debug('wrapped data');
if (state.decoder)
chunk = state.decoder.write(chunk);
// don't skip over falsy values in objectMode
if (state.objectMode && (chunk === null || chunk === undefined))
return;
else if (!state.objectMode && (!chunk || !chunk.length))
return;
var ret = self.push(chunk);
if (!ret) {
paused = true;
stream.pause();
}
});
// proxy all the other methods.
// important when wrapping filters and duplexes.
for (var i in stream) {
if (this[i] === undefined && typeof stream[i] === 'function') {
this[i] = function(method) {
return function() {
return stream[method].apply(stream, arguments);
};
}(i);
}
}
// proxy certain important events.
const events = ['error', 'close', 'destroy', 'pause', 'resume'];
events.forEach(function(ev) {
stream.on(ev, self.emit.bind(self, ev));
});
// when we try to consume some more bytes, simply unpause the
// underlying stream.
self._read = function(n) {
debug('wrapped _read', n);
if (paused) {
paused = false;
stream.resume();
}
};
return self;
};
// exposed for testing purposes only.
Readable._fromList = fromList;
// Pluck off n bytes from an array of buffers.
// Length is the combined lengths of all the buffers in the list.
// This function is designed to be inlinable, so please take care when making
// changes to the function body.
function fromList(n, state) {
// nothing buffered
if (state.length === 0)
return null;
var ret;
if (state.objectMode)
ret = state.buffer.shift();
else if (!n || n >= state.length) {
// read it all, truncate the list
if (state.decoder)
ret = state.buffer.join('');
else if (state.buffer.length === 1)
ret = state.buffer.head.data;
else
ret = state.buffer.concat(state.length);
state.buffer.clear();
} else {
// read part of list
ret = fromListPartial(n, state.buffer, state.decoder);
}
return ret;
}
// Extracts only enough buffered data to satisfy the amount requested.
// This function is designed to be inlinable, so please take care when making
// changes to the function body.
function fromListPartial(n, list, hasStrings) {
var ret;
if (n < list.head.data.length) {
// slice is the same for buffers and strings
ret = list.head.data.slice(0, n);
list.head.data = list.head.data.slice(n);
} else if (n === list.head.data.length) {
// first chunk is a perfect match
ret = list.shift();
} else {
// result spans more than one buffer
ret = (hasStrings
? copyFromBufferString(n, list)
: copyFromBuffer(n, list));
}
return ret;
}
// Copies a specified amount of characters from the list of buffered data
// chunks.
// This function is designed to be inlinable, so please take care when making
// changes to the function body.
function copyFromBufferString(n, list) {
var p = list.head;
var c = 1;
var ret = p.data;
n -= ret.length;
while (p = p.next) {
const str = p.data;
const nb = (n > str.length ? str.length : n);
if (nb === str.length)
ret += str;
else
ret += str.slice(0, n);
n -= nb;
if (n === 0) {
if (nb === str.length) {
++c;
if (p.next)
list.head = p.next;
else
list.head = list.tail = null;
} else {
list.head = p;
p.data = str.slice(nb);
}
break;
}
++c;
}
list.length -= c;
return ret;
}
// Copies a specified amount of bytes from the list of buffered data chunks.
// This function is designed to be inlinable, so please take care when making
// changes to the function body.
function copyFromBuffer(n, list) {
const ret = Buffer.allocUnsafe(n);
var p = list.head;
var c = 1;
p.data.copy(ret);
n -= p.data.length;
while (p = p.next) {
const buf = p.data;
const nb = (n > buf.length ? buf.length : n);
buf.copy(ret, ret.length - n, 0, nb);
n -= nb;
if (n === 0) {
if (nb === buf.length) {
++c;
if (p.next)
list.head = p.next;
else
list.head = list.tail = null;
} else {
list.head = p;
p.data = buf.slice(nb);
}
break;
}
++c;
}
list.length -= c;
return ret;
}
function endReadable(stream) {
var state = stream._readableState;
// If we get here before consuming all the bytes, then that is a
// bug in node. Should never happen.
if (state.length > 0)
throw new Error('"endReadable()" called on non-empty stream');
if (!state.endEmitted) {
state.ended = true;
process.nextTick(endReadableNT, state, stream);
}
}
function endReadableNT(state, stream) {
// Check that we didn't get one last unshift.
if (!state.endEmitted && state.length === 0) {
state.endEmitted = true;
stream.readable = false;
stream.emit('end');
}
}

View File

@@ -0,0 +1,195 @@
// a transform stream is a readable/writable stream where you do
// something with the data. Sometimes it's called a "filter",
// but that's not a great name for it, since that implies a thing where
// some bits pass through, and others are simply ignored. (That would
// be a valid example of a transform, of course.)
//
// While the output is causally related to the input, it's not a
// necessarily symmetric or synchronous transformation. For example,
// a zlib stream might take multiple plain-text writes(), and then
// emit a single compressed chunk some time in the future.
//
// Here's how this works:
//
// The Transform stream has all the aspects of the readable and writable
// stream classes. When you write(chunk), that calls _write(chunk,cb)
// internally, and returns false if there's a lot of pending writes
// buffered up. When you call read(), that calls _read(n) until
// there's enough pending readable data buffered up.
//
// In a transform stream, the written data is placed in a buffer. When
// _read(n) is called, it transforms the queued up data, calling the
// buffered _write cb's as it consumes chunks. If consuming a single
// written chunk would result in multiple output chunks, then the first
// outputted bit calls the readcb, and subsequent chunks just go into
// the read buffer, and will cause it to emit 'readable' if necessary.
//
// This way, back-pressure is actually determined by the reading side,
// since _read has to be called to start processing a new chunk. However,
// a pathological inflate type of transform can cause excessive buffering
// here. For example, imagine a stream where every byte of input is
// interpreted as an integer from 0-255, and then results in that many
// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in
// 1kb of data being output. In this case, you could write a very small
// amount of input, and end up with a very large amount of output. In
// such a pathological inflating mechanism, there'd be no way to tell
// the system to stop doing the transform. A single 4MB write could
// cause the system to run out of memory.
//
// However, even in such a pathological case, only a single written chunk
// would be consumed, and then the rest would wait (un-transformed) until
// the results of the previous transformed chunk were consumed.
'use strict';
module.exports = Transform;
const Duplex = require('_stream_duplex');
const util = require('util');
util.inherits(Transform, Duplex);
function TransformState(stream) {
this.afterTransform = function(er, data) {
return afterTransform(stream, er, data);
};
this.needTransform = false;
this.transforming = false;
this.writecb = null;
this.writechunk = null;
this.writeencoding = null;
}
function afterTransform(stream, er, data) {
var ts = stream._transformState;
ts.transforming = false;
var cb = ts.writecb;
if (!cb)
return stream.emit('error', new Error('no writecb in Transform class'));
ts.writechunk = null;
ts.writecb = null;
if (data !== null && data !== undefined)
stream.push(data);
cb(er);
var rs = stream._readableState;
rs.reading = false;
if (rs.needReadable || rs.length < rs.highWaterMark) {
stream._read(rs.highWaterMark);
}
}
function Transform(options) {
if (!(this instanceof Transform))
return new Transform(options);
Duplex.call(this, options);
this._transformState = new TransformState(this);
// when the writable side finishes, then flush out anything remaining.
var stream = this;
// start out asking for a readable event once data is transformed.
this._readableState.needReadable = true;
// we have implemented the _read method, and done the other things
// that Readable wants before the first _read call, so unset the
// sync guard flag.
this._readableState.sync = false;
if (options) {
if (typeof options.transform === 'function')
this._transform = options.transform;
if (typeof options.flush === 'function')
this._flush = options.flush;
}
this.once('prefinish', function() {
if (typeof this._flush === 'function')
this._flush(function(er, data) {
done(stream, er, data);
});
else
done(stream);
});
}
Transform.prototype.push = function(chunk, encoding) {
this._transformState.needTransform = false;
return Duplex.prototype.push.call(this, chunk, encoding);
};
// This is the part where you do stuff!
// override this function in implementation classes.
// 'chunk' is an input chunk.
//
// Call `push(newChunk)` to pass along transformed output
// to the readable side. You may call 'push' zero or more times.
//
// Call `cb(err)` when you are done with this chunk. If you pass
// an error, then that'll put the hurt on the whole operation. If you
// never call cb(), then you'll never get another chunk.
Transform.prototype._transform = function(chunk, encoding, cb) {
throw new Error('Not implemented');
};
Transform.prototype._write = function(chunk, encoding, cb) {
var ts = this._transformState;
ts.writecb = cb;
ts.writechunk = chunk;
ts.writeencoding = encoding;
if (!ts.transforming) {
var rs = this._readableState;
if (ts.needTransform ||
rs.needReadable ||
rs.length < rs.highWaterMark)
this._read(rs.highWaterMark);
}
};
// Doesn't matter what the args are here.
// _transform does all the work.
// That we got here means that the readable side wants more data.
Transform.prototype._read = function(n) {
var ts = this._transformState;
if (ts.writechunk !== null && ts.writecb && !ts.transforming) {
ts.transforming = true;
this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
} else {
// mark that we need a transform, so that any data that comes in
// will get processed, now that we've asked for it.
ts.needTransform = true;
}
};
function done(stream, er, data) {
if (er)
return stream.emit('error', er);
if (data !== null && data !== undefined)
stream.push(data);
// if there's nothing in the write buffer, then that means
// that nothing more will ever be provided
var ws = stream._writableState;
var ts = stream._transformState;
if (ws.length)
throw new Error('Calling transform done when ws.length != 0');
if (ts.transforming)
throw new Error('Calling transform done when still transforming');
return stream.push(null);
}

View File

@@ -0,0 +1,530 @@
// A bit simpler than readable streams.
// Implement an async ._write(chunk, encoding, cb), and it'll handle all
// the drain event emission and buffering.
'use strict';
module.exports = Writable;
Writable.WritableState = WritableState;
const util = require('util');
const internalUtil = require('internal/util');
const Stream = require('stream');
const Buffer = require('buffer').Buffer;
util.inherits(Writable, Stream);
function nop() {}
function WriteReq(chunk, encoding, cb) {
this.chunk = chunk;
this.encoding = encoding;
this.callback = cb;
this.next = null;
}
function WritableState(options, stream) {
options = options || {};
// object stream flag to indicate whether or not this stream
// contains buffers or objects.
this.objectMode = !!options.objectMode;
if (stream instanceof Stream.Duplex)
this.objectMode = this.objectMode || !!options.writableObjectMode;
// the point at which write() starts returning false
// Note: 0 is a valid value, means that we always return false if
// the entire buffer is not flushed immediately on write()
var hwm = options.highWaterMark;
var defaultHwm = this.objectMode ? 16 : 16 * 1024;
this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm;
// cast to ints.
this.highWaterMark = ~~this.highWaterMark;
this.needDrain = false;
// at the start of calling end()
this.ending = false;
// when end() has been called, and returned
this.ended = false;
// when 'finish' is emitted
this.finished = false;
// should we decode strings into buffers before passing to _write?
// this is here so that some node-core streams can optimize string
// handling at a lower level.
var noDecode = options.decodeStrings === false;
this.decodeStrings = !noDecode;
// Crypto is kind of old and crusty. Historically, its default string
// encoding is 'binary' so we have to make this configurable.
// Everything else in the universe uses 'utf8', though.
this.defaultEncoding = options.defaultEncoding || 'utf8';
// not an actual buffer we keep track of, but a measurement
// of how much we're waiting to get pushed to some underlying
// socket or file.
this.length = 0;
// a flag to see when we're in the middle of a write.
this.writing = false;
// when true all writes will be buffered until .uncork() call
this.corked = 0;
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick. We set this to true at first, because any
// actions that shouldn't happen until "later" should generally also
// not happen before the first write call.
this.sync = true;
// a flag to know if we're processing previously buffered items, which
// may call the _write() callback in the same tick, so that we don't
// end up in an overlapped onwrite situation.
this.bufferProcessing = false;
// the callback that's passed to _write(chunk,cb)
this.onwrite = function(er) {
onwrite(stream, er);
};
// the callback that the user supplies to write(chunk,encoding,cb)
this.writecb = null;
// the amount that is being written when _write is called.
this.writelen = 0;
this.bufferedRequest = null;
this.lastBufferedRequest = null;
// number of pending user-supplied write callbacks
// this must be 0 before 'finish' can be emitted
this.pendingcb = 0;
// emit prefinish if the only thing we're waiting for is _write cbs
// This is relevant for synchronous Transform streams
this.prefinished = false;
// True if the error was already emitted and should not be thrown again
this.errorEmitted = false;
// count buffered requests
this.bufferedRequestCount = 0;
// allocate the first CorkedRequest, there is always
// one allocated and free to use, and we maintain at most two
this.corkedRequestsFree = new CorkedRequest(this);
}
WritableState.prototype.getBuffer = function writableStateGetBuffer() {
var current = this.bufferedRequest;
var out = [];
while (current) {
out.push(current);
current = current.next;
}
return out;
};
Object.defineProperty(WritableState.prototype, 'buffer', {
get: internalUtil.deprecate(function() {
return this.getBuffer();
}, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' +
'instead.')
});
function Writable(options) {
// Writable ctor is applied to Duplexes, though they're not
// instanceof Writable, they're instanceof Readable.
if (!(this instanceof Writable) && !(this instanceof Stream.Duplex))
return new Writable(options);
this._writableState = new WritableState(options, this);
// legacy.
this.writable = true;
if (options) {
if (typeof options.write === 'function')
this._write = options.write;
if (typeof options.writev === 'function')
this._writev = options.writev;
}
Stream.call(this);
}
// Otherwise people can pipe Writable streams, which is just wrong.
Writable.prototype.pipe = function() {
this.emit('error', new Error('Cannot pipe, not readable'));
};
function writeAfterEnd(stream, cb) {
var er = new Error('write after end');
// TODO: defer error events consistently everywhere, not just the cb
stream.emit('error', er);
process.nextTick(cb, er);
}
// If we get something that is not a buffer, string, null, or undefined,
// and we're not in objectMode, then that's an error.
// Otherwise stream chunks are all considered to be of length=1, and the
// watermarks determine how many objects to keep in the buffer, rather than
// how many bytes or characters.
function validChunk(stream, state, chunk, cb) {
var valid = true;
var er = false;
// Always throw error if a null is written
// if we are not in object mode then throw
// if it is not a buffer, string, or undefined.
if (chunk === null) {
er = new TypeError('May not write null values to stream');
} else if (!(chunk instanceof Buffer) &&
typeof chunk !== 'string' &&
chunk !== undefined &&
!state.objectMode) {
er = new TypeError('Invalid non-string/buffer chunk');
}
if (er) {
stream.emit('error', er);
process.nextTick(cb, er);
valid = false;
}
return valid;
}
Writable.prototype.write = function(chunk, encoding, cb) {
var state = this._writableState;
var ret = false;
if (typeof encoding === 'function') {
cb = encoding;
encoding = null;
}
if (chunk instanceof Buffer)
encoding = 'buffer';
else if (!encoding)
encoding = state.defaultEncoding;
if (typeof cb !== 'function')
cb = nop;
if (state.ended)
writeAfterEnd(this, cb);
else if (validChunk(this, state, chunk, cb)) {
state.pendingcb++;
ret = writeOrBuffer(this, state, chunk, encoding, cb);
}
return ret;
};
Writable.prototype.cork = function() {
var state = this._writableState;
state.corked++;
};
Writable.prototype.uncork = function() {
var state = this._writableState;
if (state.corked) {
state.corked--;
if (!state.writing &&
!state.corked &&
!state.finished &&
!state.bufferProcessing &&
state.bufferedRequest)
clearBuffer(this, state);
}
};
Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) {
// node::ParseEncoding() requires lower case.
if (typeof encoding === 'string')
encoding = encoding.toLowerCase();
if (!Buffer.isEncoding(encoding))
throw new TypeError('Unknown encoding: ' + encoding);
this._writableState.defaultEncoding = encoding;
return this;
};
function decodeChunk(state, chunk, encoding) {
if (!state.objectMode &&
state.decodeStrings !== false &&
typeof chunk === 'string') {
chunk = Buffer.from(chunk, encoding);
}
return chunk;
}
// if we're already writing something, then just put this
// in the queue, and wait our turn. Otherwise, call _write
// If we return false, then we need a drain event, so set that flag.
function writeOrBuffer(stream, state, chunk, encoding, cb) {
chunk = decodeChunk(state, chunk, encoding);
if (chunk instanceof Buffer)
encoding = 'buffer';
var len = state.objectMode ? 1 : chunk.length;
state.length += len;
var ret = state.length < state.highWaterMark;
// we must ensure that previous needDrain will not be reset to false.
if (!ret)
state.needDrain = true;
if (state.writing || state.corked) {
var last = state.lastBufferedRequest;
state.lastBufferedRequest = new WriteReq(chunk, encoding, cb);
if (last) {
last.next = state.lastBufferedRequest;
} else {
state.bufferedRequest = state.lastBufferedRequest;
}
state.bufferedRequestCount += 1;
} else {
doWrite(stream, state, false, len, chunk, encoding, cb);
}
return ret;
}
function doWrite(stream, state, writev, len, chunk, encoding, cb) {
state.writelen = len;
state.writecb = cb;
state.writing = true;
state.sync = true;
if (writev)
stream._writev(chunk, state.onwrite);
else
stream._write(chunk, encoding, state.onwrite);
state.sync = false;
}
function onwriteError(stream, state, sync, er, cb) {
--state.pendingcb;
if (sync)
process.nextTick(cb, er);
else
cb(er);
stream._writableState.errorEmitted = true;
stream.emit('error', er);
}
function onwriteStateUpdate(state) {
state.writing = false;
state.writecb = null;
state.length -= state.writelen;
state.writelen = 0;
}
function onwrite(stream, er) {
var state = stream._writableState;
var sync = state.sync;
var cb = state.writecb;
onwriteStateUpdate(state);
if (er)
onwriteError(stream, state, sync, er, cb);
else {
// Check if we're actually ready to finish, but don't emit yet
var finished = needFinish(state);
if (!finished &&
!state.corked &&
!state.bufferProcessing &&
state.bufferedRequest) {
clearBuffer(stream, state);
}
if (sync) {
process.nextTick(afterWrite, stream, state, finished, cb);
} else {
afterWrite(stream, state, finished, cb);
}
}
}
function afterWrite(stream, state, finished, cb) {
if (!finished)
onwriteDrain(stream, state);
state.pendingcb--;
cb();
finishMaybe(stream, state);
}
// Must force callback to be called on nextTick, so that we don't
// emit 'drain' before the write() consumer gets the 'false' return
// value, and has a chance to attach a 'drain' listener.
function onwriteDrain(stream, state) {
if (state.length === 0 && state.needDrain) {
state.needDrain = false;
stream.emit('drain');
}
}
// if there's something in the buffer waiting, then process it
function clearBuffer(stream, state) {
state.bufferProcessing = true;
var entry = state.bufferedRequest;
if (stream._writev && entry && entry.next) {
// Fast case, write everything using _writev()
var l = state.bufferedRequestCount;
var buffer = new Array(l);
var holder = state.corkedRequestsFree;
holder.entry = entry;
var count = 0;
while (entry) {
buffer[count] = entry;
entry = entry.next;
count += 1;
}
doWrite(stream, state, true, state.length, buffer, '', holder.finish);
// doWrite is almost always async, defer these to save a bit of time
// as the hot path ends with doWrite
state.pendingcb++;
state.lastBufferedRequest = null;
if (holder.next) {
state.corkedRequestsFree = holder.next;
holder.next = null;
} else {
state.corkedRequestsFree = new CorkedRequest(state);
}
} else {
// Slow case, write chunks one-by-one
while (entry) {
var chunk = entry.chunk;
var encoding = entry.encoding;
var cb = entry.callback;
var len = state.objectMode ? 1 : chunk.length;
doWrite(stream, state, false, len, chunk, encoding, cb);
entry = entry.next;
// if we didn't call the onwrite immediately, then
// it means that we need to wait until it does.
// also, that means that the chunk and cb are currently
// being processed, so move the buffer counter past them.
if (state.writing) {
break;
}
}
if (entry === null)
state.lastBufferedRequest = null;
}
state.bufferedRequestCount = 0;
state.bufferedRequest = entry;
state.bufferProcessing = false;
}
Writable.prototype._write = function(chunk, encoding, cb) {
cb(new Error('_write() method is not implemented'));
};
Writable.prototype._writev = null;
Writable.prototype.end = function(chunk, encoding, cb) {
var state = this._writableState;
if (typeof chunk === 'function') {
cb = chunk;
chunk = null;
encoding = null;
} else if (typeof encoding === 'function') {
cb = encoding;
encoding = null;
}
if (chunk !== null && chunk !== undefined)
this.write(chunk, encoding);
// .end() fully uncorks
if (state.corked) {
state.corked = 1;
this.uncork();
}
// ignore unnecessary end() calls.
if (!state.ending && !state.finished)
endWritable(this, state, cb);
};
function needFinish(state) {
return (state.ending &&
state.length === 0 &&
state.bufferedRequest === null &&
!state.finished &&
!state.writing);
}
function prefinish(stream, state) {
if (!state.prefinished) {
state.prefinished = true;
stream.emit('prefinish');
}
}
function finishMaybe(stream, state) {
var need = needFinish(state);
if (need) {
if (state.pendingcb === 0) {
prefinish(stream, state);
state.finished = true;
stream.emit('finish');
} else {
prefinish(stream, state);
}
}
return need;
}
function endWritable(stream, state, cb) {
state.ending = true;
finishMaybe(stream, state);
if (cb) {
if (state.finished)
process.nextTick(cb);
else
stream.once('finish', cb);
}
state.ended = true;
stream.writable = false;
}
// It seems a linked list but it is not
// there will be only 2 of these for each stream
function CorkedRequest(state) {
this.next = null;
this.entry = null;
this.finish = (err) => {
var entry = this.entry;
this.entry = null;
while (entry) {
var cb = entry.callback;
state.pendingcb--;
cb(err);
entry = entry.next;
}
if (state.corkedRequestsFree) {
state.corkedRequestsFree.next = this;
} else {
state.corkedRequestsFree = this;
}
};
}

View File

@@ -0,0 +1,481 @@
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
//
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
//
// Originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
/**
* Assertions
* @module assert
* @memberOf module:buildin
*/
// UTILITY
const compare = process.binding('buffer').compare;
const util = require('util');
const Buffer = require('buffer').Buffer;
const pToString = (obj) => Object.prototype.toString.call(obj);
// 1. The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
// assert module must conform to the following interface.
const assert = module.exports = ok;
// 2. The AssertionError is defined in assert.
// new assert.AssertionError({ message: message,
// actual: actual,
// expected: expected })
assert.AssertionError = function AssertionError(options) {
this.name = 'AssertionError';
this.actual = options.actual;
this.expected = options.expected;
this.operator = options.operator;
if (options.message) {
this.message = options.message;
this.generatedMessage = false;
} else {
this.message = getMessage(this);
this.generatedMessage = true;
}
var stackStartFunction = options.stackStartFunction || fail;
if (Error.captureStackTrace) {
// Chrome and NodeJS
Error.captureStackTrace(this, stackStartFunction);
} else {
// FF, IE 10+ and Safari 6+. Fallback for others
let tmp_stack = (new Error).stack.split("\n").slice(1),
re = /^(.*?)@(.*?):(.*?)$/.exec(tmp_stack[1]); //[undef, undef, this.fileName, this.lineNumber] = re
this.fileName = re[2];
this.lineNumber = re[3];
this.stack = tmp_stack.join("\n");
}
};
// assert.AssertionError instanceof Error
util.inherits(assert.AssertionError, Error);
function truncate(s, n) {
return s.slice(0, n);
}
function getMessage(self) {
return truncate(util.inspect(self.actual), 128) + ' ' +
self.operator + ' ' +
truncate(util.inspect(self.expected), 128);
}
// At present only the three keys mentioned above are used and
// understood by the spec. Implementations or sub modules can pass
// other keys to the AssertionError's constructor - they will be
// ignored.
// 3. All of the following functions must throw an AssertionError
// when a corresponding condition is not met, with a message that
// may be undefined if not provided. All assertion methods provide
// both the actual and expected values to the assertion error for
// display purposes.
/**
* Throws an exception that displays the values for actual and expected separated by the provided operator.
* @param actual
* @param expected
* @param message
* @param operator
* @param stackStartFunction
*/
function fail(actual, expected, message, operator, stackStartFunction) {
throw new assert.AssertionError({
message: message,
actual: actual,
expected: expected,
operator: operator,
stackStartFunction: stackStartFunction
});
}
// EXTENSION! allows for well behaved errors defined elsewhere.
assert.fail = fail;
// 4. Pure assertion tests whether a value is truthy, as determined
// by !!guard.
// assert.ok(guard, message_opt);
// This statement is equivalent to assert.equal(true, !!guard,
// message_opt);. To test strictly for the value true, use
// assert.strictEqual(true, guard, message_opt);.
/**
* Tests if value is truthy, it is equivalent to assert.equal(true, !!value, message);
* @param value
* @param message
*/
function ok(value, message) {
if (!value) fail(value, true, message, '==', assert.ok);
}
assert.ok = ok;
// 5. The equality assertion tests shallow, coercive equality with
// ==.
// assert.equal(actual, expected, message_opt);
/**
* Tests shallow, coercive equality with the equal comparison operator ( == ).
* @param actual
* @param expected
* @param {String} [message]
*/
module.exports.equal = function equal(actual, expected, message) {
if (actual != expected) fail(actual, expected, message, '==', assert.equal);
};
// 6. The non-equality assertion tests for whether two objects are not equal
// with != assert.notEqual(actual, expected, message_opt);
/**
* Tests shallow, coercive non-equality with the not equal comparison operator ( != ).
* @param actual
* @param expected
* @param [message]
*/
module.exports.notEqual = function notEqual(actual, expected, message) {
if (actual == expected) {
fail(actual, expected, message, '!=', assert.notEqual);
}
};
// 7. The equivalence assertion tests a deep equality relation.
// assert.deepEqual(actual, expected, message_opt);
/**
* Tests for deep equality.
* @param actual
* @param expected
* @param [message]
*/
module.exports.deepEqual = function deepEqual(actual, expected, message) {
if (!_deepEqual(actual, expected, false)) {
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
}
};
/**
* Generally identical to assert.deepEqual() with two exceptions.
* First, primitive values are compared using the strict equality operator ( === ).
* Second, object comparisons include a strict equality check of their prototypes.
* @param actual
* @param expected
* @param [message]
*/
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
if (!_deepEqual(actual, expected, true)) {
fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual);
}
};
function _deepEqual(actual, expected, strict, memos) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
} else if (actual instanceof Buffer && expected instanceof Buffer) {
return compare(actual, expected) === 0;
// UB SEPCIFIC
} else if (actual instanceof ArrayBuffer && expected instanceof ArrayBuffer) {
if (actual.byteLength != expected.byteLength) return false;
var aBuf = new Uint8Array(actual), eBuf = new Uint8Array(expected);
for (var i = 0; i < aBuf.length; i++) {
if (aBuf[i] !== eBuf[i]) return false;
}
return true;
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
} else if (util.isDate(actual) && util.isDate(expected)) {
return actual.getTime() === expected.getTime();
// 7.3 If the expected value is a RegExp object, the actual value is
// equivalent if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (util.isRegExp(actual) && util.isRegExp(expected)) {
return actual.source === expected.source &&
actual.global === expected.global &&
actual.multiline === expected.multiline &&
actual.lastIndex === expected.lastIndex &&
actual.ignoreCase === expected.ignoreCase;
// 7.4. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
} else if ((actual === null || typeof actual !== 'object') &&
(expected === null || typeof expected !== 'object')) {
return strict ? actual === expected : actual == expected;
// If both values are instances of typed arrays, wrap their underlying
// ArrayBuffers in a Buffer each to increase performance
// This optimization requires the arrays to have the same type as checked by
// Object.prototype.toString (aka pToString). Never perform binary
// comparisons for Float*Arrays, though, since e.g. +0 === -0 but their
// bit patterns are not identical.
} else if (ArrayBuffer.isView(actual) && ArrayBuffer.isView(expected) &&
pToString(actual) === pToString(expected) &&
!(actual instanceof Float32Array ||
actual instanceof Float64Array)) {
return compare(Buffer.from(actual.buffer,
actual.byteOffset,
actual.byteLength),
Buffer.from(expected.buffer,
expected.byteOffset,
expected.byteLength)) === 0;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
memos = memos || {actual: [], expected: []};
const actualIndex = memos.actual.indexOf(actual);
if (actualIndex !== -1) {
if (actualIndex === memos.expected.indexOf(expected)) {
return true;
}
}
memos.actual.push(actual);
memos.expected.push(expected);
return objEquiv(actual, expected, strict, memos);
}
}
function isArguments(object) {
return Object.prototype.toString.call(object) == '[object Arguments]';
}
function objEquiv(a, b, strict, actualVisitedObjects) {
if (a === null || a === undefined || b === null || b === undefined)
return false;
// if one is a primitive, the other must be same
if (util.isPrimitive(a) || util.isPrimitive(b))
return a === b;
if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
return false;
const aIsArgs = isArguments(a);
const bIsArgs = isArguments(b);
if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs))
return false;
const ka = Object.keys(a);
const kb = Object.keys(b);
var key, i;
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length !== kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] !== kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key], strict, actualVisitedObjects))
return false;
}
return true;
}
// 8. The non-equivalence assertion tests for any deep inequality.
// assert.notDeepEqual(actual, expected, message_opt);
/**
* Tests for any deep inequality.
* @param actual
* @param expected
* @param [message]
*/
module.exports.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (_deepEqual(actual, expected, false)) {
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
}
};
assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
if (_deepEqual(actual, expected, true)) {
fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual);
}
}
// 9. The strict equality assertion tests strict equality, as determined by ===.
// assert.strictEqual(actual, expected, message_opt);
assert.strictEqual = function strictEqual(actual, expected, message) {
if (actual !== expected) {
fail(actual, expected, message, '===', assert.strictEqual);
}
};
// 10. The strict non-equality assertion tests for strict inequality, as
// determined by !==. assert.notStrictEqual(actual, expected, message_opt);
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (actual === expected) {
fail(actual, expected, message, '!==', assert.notStrictEqual);
}
};
function expectedException(actual, expected) {
if (!actual || !expected) {
return false;
}
if (Object.prototype.toString.call(expected) == '[object RegExp]') {
return expected.test(actual);
}
try {
if (actual instanceof expected) {
return true;
}
} catch (e) {
// Ignore. The instanceof check doesn't work for arrow functions.
}
if (Error.isPrototypeOf(expected)) {
return false;
}
return expected.call({}, actual) === true;
}
function _tryBlock(block) {
var error;
try {
block();
} catch (e) {
error = e;
}
return error;
}
function _throws(shouldThrow, block, expected, message) {
var actual;
if (typeof block !== 'function') {
throw new TypeError('"block" argument must be a function');
}
if (typeof expected === 'string') {
message = expected;
expected = null;
}
actual = _tryBlock(block);
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
(message ? ' ' + message : '.');
if (shouldThrow && !actual) {
fail(actual, expected, 'Missing expected exception' + message);
}
const userProvidedMessage = typeof message === 'string';
const isUnwantedException = !shouldThrow && util.isError(actual);
const isUnexpectedException = !shouldThrow && actual && !expected;
if ((isUnwantedException &&
userProvidedMessage &&
expectedException(actual, expected)) ||
isUnexpectedException) {
fail(actual, expected, 'Got unwanted exception' + message);
}
if ((shouldThrow && actual && expected &&
!expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
}
// 11. Expected to throw an error:
// assert.throws(block, Error_opt, message_opt);
/**
* Expects block to throw an error. error can be constructor, RegExp or validation function.
*
* Validate instanceof using constructor:
*
* assert.throws(function() {
* throw new Error("Wrong value");
* }, Error);
*
* Validate error message using RegExp:
*
* assert.throws(function() {
* throw new Error("Wrong value");
* }, /error/);
*
* Custom error validation:
*
* assert.throws(
* function() {
* throw new Error("Wrong value");
* },
* function(err) {
* if ( (err instanceof Error) && /value/.test(err) ) {
* return true;
* }
* },
* "unexpected error"
* );
*
* @param block
* @param [error]
* @param [message]
*/
module.exports.throws = function(block, /*optional*/error, /*optional*/message) {
_throws(true, block, error, message);
};
// EXTENSION! This is annoying to write outside this module.
/**
* Expects block not to throw an error, see assert.throws for details.
* @param block
* @param [error]
* @param [message]
*/
module.exports.doesNotThrow = function(block, /*optional*/error, /*optional*/message) {
_throws(false, block, error, message);
};
/**
* Tests if value is not a false value, throws if it is a true value. Useful when testing the first argument, error in callbacks.
* @param err
*/
module.exports.ifError = function(err) { if (err) {throw err;}};

1388
contrib/mORMot/SyNode/core_modules/node_modules/buffer.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
/*
* Fake implementation of nodejs child_process
* Throw on spawn
*/
module.exports.spawn = function(){
throw new Error('Not implemented in SyNode');
}

View File

@@ -0,0 +1,182 @@
// Copyright Joyent, Inc. and other Node contributors.
// Modified by UnityBase core team to be compatible with SyNode
var util = require('util');
/**
* Console & log output functions
* Put something to log with log levels depending on method. In case of GUI server do echo to GUI log (if enabled).
* In case of command line - echo to `stdout`.
*
* Do not create this class directly - use global {@link console} already created by UB.
*
* console.log('%s is a %s usually with weight less then %dgr', 'apple', 'fruit', 100);
* //Will output "apple is a fruit usually with weight less then 100gr"
* console.log('apple', 'fruit', 100);
* //will output "apple fruit 100"
* console.debug('something');
* // will output to log only in "Debug" build (UBD.exe)
*
* Arguments, passed to console output functions are transformed to string using {@link util.format} call.
*
* @module console
* @memberOf module:buildin
*/
/**
* Do not create directly, use {@link console} instance from `global`.
*
* console.debug('Yeh!');
*
* @class Console
* @memberOf module:buildin
*/
function Console(stdout, stderr) {
if (!(this instanceof Console)) {
return new Console(stdout, stderr);
}
if (!stdout || typeof stdout.write !== 'function') {
throw new TypeError('Console expects a writable stream instance');
}
if (!stderr) {
stderr = stdout;
}
var prop = {
writable: true,
enumerable: false,
configurable: true
};
prop.value = stdout;
Object.defineProperty(this, '_stdout', prop);
prop.value = stderr;
Object.defineProperty(this, '_stderr', prop);
prop.value = {};
Object.defineProperty(this, '_times', prop);
// bind the prototype functions to this Console instance
Object.keys(Console.prototype).forEach(function(k) {
this[k] = this[k].bind(this);
}, this);
}
/**
* Output to log with log level `Info`. Internally use util.format for create output, so
* format chars can be used:
*
* - %s - String.
* - %d - Number (both integer and float).
* - %j - JSON.
* - % - single percent sign ('%'). This does not consume an argument.
*
* console.log('%s is a %s usually with weight less then %dgr', 'apple', 'fruit', 100);
* //Will output "apple is a fruit usually with weight less then 100gr"
*
* console.log('apple', 'fruit', 100);
* //will output "apple fruit 100"
*
* console.log('the object JSON is %j', {a: 12, b: {inner: 11}});
* // will output a JSON object instead of [object Object]
*
* @param {...*}
*/
Console.prototype.log = function() {
this._stdout.write(util.format.apply(this, arguments) + '\n');
};
/**
* Output to log with log level `Debug`. In case {@link process.isDebug} is false - do nothing
* @method
* @param {...*}
*/
Console.prototype.debug = process.isDebug ?
function() {
this._stdout.write(util.format.apply(this, arguments) + '\n', 2); //UB specific
} :
function() {
};
/**
* Output to log with log level `Info` (alias for console.log)
* @method
* @param {...*}
*/
Console.prototype.info = Console.prototype.log;
/**
* Output to log with log level `Warning`. In case of OS console echo output to stderr
* @param {...*}
*/
Console.prototype.warn = function() {
this._stderr.write(util.format.apply(this, arguments) + '\n', 4); //UB specific
};
/**
* Output to log with log level `Error`. In case of OS console echo output to stderr
* @param {...*}
*/
Console.prototype.error = function() {
this._stderr.write(util.format.apply(this, arguments) + '\n', 5); //UB specific
};
/**
* Uses util.inspect on obj and prints resulting string to stdout.
* @param {Object} object
*/
Console.prototype.dir = function(object) {
this._stdout.write(util.inspect(object) + '\n');
};
/**
* Mark a time.
* @param {String} label
*/
Console.prototype.time = function(label) {
this._times[label] = Date.now();
};
/**
* Finish timer, record output
* @example
*
* console.time('100-elements');
* for (var i = 0; i < 100; i++) {
* ;
* }
* console.timeEnd('100-elements');
*
* @param {string} label
*/
Console.prototype.timeEnd = function(label) {
var time = this._times[label];
if (!time) {
throw new Error('No such label: ' + label);
}
var duration = Date.now() - time;
this.log('%s: %dms', label, duration);
};
Console.prototype.trace = function() {
// TODO probably can to do this better with V8's debug object once that is
// exposed.
var err = new Error;
err.name = 'Trace';
err.message = util.format.apply(this, arguments);
//MPV Error.captureStackTrace(err, arguments.callee);
this.error(err.stack);
};
/**
* Similar to {@link assert#ok}, but the error message is formatted as {@link util#format util.format(message...)}.
* @param expression
*/
Console.prototype.assert = function(expression) {
if (!expression) {
var arr = Array.prototype.slice.call(arguments, 1);
require('assert').ok(false, util.format.apply(this, arr));
}
};
module.exports = new Console(process.stdout, process.stderr);
module.exports.Console = Console;

View File

@@ -0,0 +1,16 @@
//fake module
exports.createHash = exports.Hash = Hash;
function Hash(algorithm, options) {
if (!(this instanceof Hash))
return new Hash(algorithm, options);
/* this._binding = new binding.Hash(algorithm);
LazyTransform.call(this, options);*/
this.fake = true;
}
exports.randomBytes = randomBytes;
function randomBytes(size, callback) {
return 'zzzzz';
}

117
contrib/mORMot/SyNode/core_modules/node_modules/dns.js generated vendored Normal file
View File

@@ -0,0 +1,117 @@
// Fake DNS node module interface
// For crequire('dns') compartibility only
const NOT_IMPLEMENTED_IN_SYNODE = 'Not implemented in SyNode'
function lookup() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function lookupService() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function getServers() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function setServers() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolve() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolve4() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolve6() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolveCname() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolveMx() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolveNs() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolveTxt() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolveSrv() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolvePtr() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolveNaptr() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function resolveSoa() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
function reverse() {
throw new Error(NOT_IMPLEMENTED_IN_SYNODE)
}
module.exports = {
lookup,
lookupService,
getServers,
setServers,
resolve,
resolve4,
resolve6,
resolveCname,
resolveMx,
resolveNs,
resolveTxt,
resolveSrv,
resolvePtr,
resolveNaptr,
resolveSoa,
reverse,
// uv_getaddrinfo flags
ADDRCONFIG: 'cares.AI_ADDRCONFIG',
V4MAPPED: 'cares.AI_V4MAPPED',
// ERROR CODES
NODATA: 'ENODATA',
FORMERR: 'EFORMERR',
SERVFAIL: 'ESERVFAIL',
NOTFOUND: 'ENOTFOUND',
NOTIMP: 'ENOTIMP',
REFUSED: 'EREFUSED',
BADQUERY: 'EBADQUERY',
BADNAME: 'EBADNAME',
BADFAMILY: 'EBADFAMILY',
BADRESP: 'EBADRESP',
CONNREFUSED: 'ECONNREFUSED',
TIMEOUT: 'ETIMEOUT',
EOF: 'EOF',
FILE: 'EFILE',
NOMEM: 'ENOMEM',
DESTRUCTION: 'EDESTRUCTION',
BADSTR: 'EBADSTR',
BADFLAGS: 'EBADFLAGS',
NONAME: 'ENONAME',
BADHINTS: 'EBADHINTS',
NOTINITIALIZED: 'ENOTINITIALIZED',
LOADIPHLPAPI: 'ELOADIPHLPAPI',
ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS',
CANCELLED: 'ECANCELLED'
};

View File

@@ -0,0 +1,561 @@
'use strict';
/**
* @module events
* @memberOf module:buildin
*/
/**
* NodeJS like EventEmitter. See also <a href="http://nodejs.org/api/events.html">NodeJS events documentation</a>
*
* To add event emitting ability to any object:
*
var myObject = {},
//compatibility EventEmitter = require('events').EventEmitter;
EventEmitter = require('events');
// add EventEmitter to myObject
EventEmitter.call(myObject);
var util = require('util');
util._extend(myObject, EventEmitter.prototype);
* In case object created via constructor function
function MyObject() {
EventEmitter.call(this);
}
util.inherits(MyObject, EventEmitter);
var myObject = new MyObject();
* Usage:
myObject.on('myEvent', function(num, str){console.log(num, str) });
myObject.emit('myEvent', 1, 'two'); // output: 1 two
*
* @class EventEmitter
* @mixin
*/
function EventEmitter() {
EventEmitter.init.call(this);
}
module.exports = EventEmitter;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
/*
* @deprecated This property not used (===false) in UB. Also deprecated in Node
*/
EventEmitter.usingDomains = false;
//UB EventEmitter.prototype.domain = undefined;
/**
* Private collection of events.
* @private
*/
EventEmitter.prototype._events = undefined;
/**
* Use set/get MaxListeners instead direct access
* @private
*/
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
/**
* @private
*/
EventEmitter.init = function() {
//UB this.domain = null;
//if (EventEmitter.usingDomains) {
// // if there is an active domain, then attach to it.
// domain = domain || require('domain');
// if (domain.active && !(this instanceof domain.Domain)) {
// this.domain = domain.active;
// }
//}
if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
this._events = {};
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
/**
* Obviously not all Emitters should be limited to 10. This function allows
* that to be increased. Set to zero for unlimited.
* @param {Number} n
*/
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || isNaN(n))
throw new TypeError('n must be a positive number');
this._maxListeners = n;
return this;
};
function $getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
/**
*
* @return {Number}
*/
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return $getMaxListeners(this);
};
// These standalone emit* functions are used to optimize calling of event
// handlers for fast cases because emit() itself often has a variable number of
// arguments and can be deoptimized because of that. These functions always have
// the same number of arguments and thus do not get deoptimized, so the code
// inside them can execute faster.
function emitNone(handler, isFn, self) {
if (isFn)
handler.call(self);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self);
}
}
function emitOne(handler, isFn, self, arg1) {
if (isFn)
handler.call(self, arg1);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1);
}
}
function emitTwo(handler, isFn, self, arg1, arg2) {
if (isFn)
handler.call(self, arg1, arg2);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1, arg2);
}
}
function emitThree(handler, isFn, self, arg1, arg2, arg3) {
if (isFn)
handler.call(self, arg1, arg2, arg3);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1, arg2, arg3);
}
}
function emitMany(handler, isFn, self, args) {
if (isFn)
handler.apply(self, args);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].apply(self, args);
}
}
/**
* Execute each of the listeners in order with the supplied arguments.
* Returns true if event had listeners, false otherwise.
*
* @param {String} type Event name
* @param {...*} eventArgs Arguments, passed to listeners
* @return {boolean}
*/
EventEmitter.prototype.emit = function emit(type) {
var er, handler, len, args, i, events/*UB domain*/;
//UB var needDomainExit = false;
var doError = (type === 'error');
events = this._events;
if (events)
doError = (doError && events.error == null);
else if (!doError)
return false;
//UB domain = this.domain;
// If there is no 'error' event listener then throw.
if (doError) {
er = arguments[1];
//UB
//if (domain) {
// if (!er)
// er = new Error('Uncaught, unspecified "error" event.');
// er.domainEmitter = this;
// er.domain = domain;
// er.domainThrown = false;
// domain.emit('error', er);
//} else
if (er instanceof Error) {
throw er; // Unhandled 'error' event
} else {
// At least give some kind of context to the user
var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
err.context = er;
throw err;
}
return false;
}
handler = events[type];
if (!handler)
return false;
//UB
//if (domain && this !== process) {
// domain.enter();
// needDomainExit = true;
//}
var isFn = typeof handler === 'function';
len = arguments.length;
switch (len) {
// fast cases
case 1:
emitNone(handler, isFn, this);
break;
case 2:
emitOne(handler, isFn, this, arguments[1]);
break;
case 3:
emitTwo(handler, isFn, this, arguments[1], arguments[2]);
break;
case 4:
emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
break;
// slower
default:
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
emitMany(handler, isFn, this, args);
}
//UB if (needDomainExit)
// domain.exit();
return true;
};
/**
* Adds a listener to the end of the listeners array for the specified event.
* Will emit `newListener` event on success.
*
* Usage sample:
*
* Session.on('login', function () {
* console.log('someone connected!');
* });
*
* Returns emitter, so calls can be chained.
*
* @param {String} type Event name
* @param {Function} listener
* @return {EventEmitter}
*/
EventEmitter.prototype.addListener = function addListener(type, listener) {
var m;
var events;
var existing;
if (typeof listener !== 'function')
throw new TypeError('listener must be a function');
events = this._events;
if (!events) {
events = this._events = {};
this._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener) {
/** @fires newListener */
this.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = this._events;
}
existing = events[type];
}
if (!existing) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
++this._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] = [existing, listener];
} else {
// If we've already got an array, just append.
existing.push(listener);
}
// Check for listener leak
if (!existing.warned) {
m = $getMaxListeners(this);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d %s listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
existing.length, type);
console.trace();
}
}
}
return this;
};
/**
* Alias for {@link EventEmitter#addListener addListener}
* @method
* @param {String} type Event name
* @param {Function} listener
* @return {EventEmitter}
*/
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
/**
* Adds a one time listener for the event. This listener is invoked only the next time the event is fired, after which it is removed.
* @param {String} type Event name
* @param {Function} listener
* @return {EventEmitter}
*/
EventEmitter.prototype.once = function once(type, listener) {
if (typeof listener !== 'function')
throw new TypeError('listener must be a function');
var fired = false;
function g() {
this.removeListener(type, g);
if (!fired) {
fired = true;
listener.apply(this, arguments);
}
}
g.listener = listener;
this.on(type, g);
return this;
};
/**
* Remove a listener from the listener array for the specified event.
* Caution: changes array indices in the listener array behind the listener.
* Emits a 'removeListener' event if the listener was removed.
*
* @param {String} type Event name
* @param {Function} listener
*/
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i;
if (typeof listener !== 'function')
throw new TypeError('listener must be a function');
events = this._events;
if (!events)
return this;
list = events[type];
if (!list)
return this;
if (list === listener || (list.listener && list.listener === listener)) {
if (--this._eventsCount === 0)
this._events = {};
else {
delete events[type];
if (events.removeListener)
/** @fires removeListener */
this.emit('removeListener', type, listener);
}
} else if (typeof list !== 'function') {
position = -1;
for (i = list.length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
position = i;
break;
}
}
if (position < 0)
return this;
if (list.length === 1) {
list[0] = undefined;
if (--this._eventsCount === 0) {
this._events = {};
return this;
} else {
delete events[type];
}
} else {
spliceOne(list, position);
}
if (events.removeListener)
this.emit('removeListener', type, listener);
}
return this;
};
/**
* Removes all listeners, or those of the specified event.
* It's not a good idea to remove listeners that were added elsewhere in the code,
* especially when it's on an emitter that you didn't create (e.g. sockets or file streams).
*
* Returns emitter, so calls can be chained.
* @param {String} type Event name
* @return {EventEmitter}
*/
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
var listeners, events;
events = this._events;
if (!events)
return this;
// not listening for removeListener, no need to emit
if (!events.removeListener) {
if (arguments.length === 0) {
this._events = {};
this._eventsCount = 0;
} else if (events[type]) {
if (--this._eventsCount === 0)
this._events = {};
else
delete events[type];
}
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
var keys = Object.keys(events);
for (var i = 0, key; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = {};
this._eventsCount = 0;
return this;
}
listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners) {
// LIFO order
do {
this.removeListener(type, listeners[listeners.length - 1]);
} while (listeners[0]);
}
return this;
};
/**
* Returns an array of listeners for the specified event.
* @param {String} type Event name
* @return {Array.<Function>}
*/
EventEmitter.prototype.listeners = function listeners(type) {
var evlistener;
var ret;
var events = this._events;
if (!events)
ret = [];
else {
evlistener = events[type];
if (!evlistener)
ret = [];
else if (typeof evlistener === 'function')
ret = [evlistener];
else
ret = arrayClone(evlistener, evlistener.length);
}
return ret;
};
/**
* Return the number of listeners for a given event.
* @param {EventEmitter} emitter
* @param {String} type
* @return {Number}
*/
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
} else {
return listenerCount.call(emitter, type);
}
};
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
const events = this._events;
if (events) {
const evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener) {
return evlistener.length;
}
}
return 0;
}
// About 1.5x faster than the two-arg version of Array#splice().
function spliceOne(list, index) {
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
list[i] = list[k];
list.pop();
}
function arrayClone(arr, i) {
var copy = new Array(i);
while (i--)
copy[i] = arr[i];
return copy;
}

348
contrib/mORMot/SyNode/core_modules/node_modules/fs.js generated vendored Normal file
View File

@@ -0,0 +1,348 @@
/**
* SyNode file-system routines. We try to implement here the same interface as in <a href="http://nodejs.org/api/fs.html">NodeJS fs</a>
*
* var fs = require('fs');
* var content = fs.readFileSync('c:\\a.txt', 'utf-8);
*
* @module fs
* @memberOf module:buildin
*/
const constants = process.binding('constants').fs
const internalFS = require('internal/fs')
const util = require('util')
const fs = exports;
const {fileStat, directoryExists, fileExists, readDir,
realpath, rename, loadFileToBuffer,
writeFile, appendFile,
deleteFile, forceDirectories, removeDir,
} = process.binding('fs')
const pathModule = require('path');
const {
assertEncoding,
stringToFlags
} = internalFS;
Object.defineProperty(exports, 'constants', {
configurable: false,
enumerable: true,
value: constants
})
const kMinPoolSpace = 128;
const { kMaxLength } = require('buffer')
const isWindows = process.platform === 'win32'
function getOptions(options, defaultOptions) {
if (options === null || options === undefined ||
typeof options === 'function') {
return defaultOptions;
}
if (typeof options === 'string') {
defaultOptions = util._extend({}, defaultOptions);
defaultOptions.encoding = options;
options = defaultOptions;
} else if (typeof options !== 'object') {
throw new TypeError('"options" must be a string or an object, got ' +
typeof options + ' instead.');
}
if (options.encoding !== 'buffer')
assertEncoding(options.encoding);
return options;
}
function nullCheck(path, callback) {
if (('' + path).indexOf('\u0000') !== -1) {
var er = new Error('Path must be a string without null bytes');
er.code = 'ENOENT';
// SyNode if (typeof callback !== 'function')
throw er;
// SyNode process.nextTick(callback, er);
// SyNode return false;
}
return true;
}
/**
* Check specified path is file (or symlynk to file)
* @param path
* @return {Boolean}
*/
exports.isFile = function isFile(path){
return fileExists(path);
};
/**
* Check specified path is folder (or symlynk to folder)
* @param path
* @return {Boolean}
*/
exports.isDir = function isDir(path){
return directoryExists(path);
};
const emptyObj = Object.create(null);
/**
* Synchronous realpath(3). Returns the resolved path (resolve symlinks, junctions on Windows, /../)
*/
exports.realpathSync = function realpathSync(p, options){
if (!options)
options = emptyObj;
else
options = getOptions(options, emptyObj);
if (typeof p !== 'string') {
// SyNode handleError((p = getPathFromURL(p)));
// SyNode if (typeof p !== 'string')
p += '';
}
nullCheck(p);
p = pathModule.resolve(p);
const cache = options[internalFS.realpathCacheKey];
const maybeCachedResult = cache && cache.get(p);
if (maybeCachedResult) {
return maybeCachedResult;
}
let res = realpath(p);
if (cache) cache.set(p, res);
return res;
};
/**
* Reads the entire contents of a TEXT file.
* If BOM found - decode text file to string using BOM
* If BOM not found - use forceUFT8 parameter.
* @param {String} fileName
* @param {Boolean} [forceUFT8] If no BOM found and forceUFT8 is True (default) - we expect file in UTF8 format, else in ascii
* @returns {String}
*/
exports.loadFile = function (fileName, forceUFT8){
return loadFile(fileName, forceUFT8);
};
/**
* Reads the entire contents of a file. If options.encoding == 'bin', then the ArrayBuffer is returned.
* If no options is specified at all - result is String as in {@link fs.loadFile}
* @param {String} fileName Absolute path to file
* @param {Object} [options]
* @param {String|Null} [options.encoding] Default to null. Possible values: 'bin'|'ascii'|'utf-8'
* @returns {String|ArrayBuffer}
*/
function readFileSync(fileName, options){
let stat = fileStat(fileName);
if (!stat) {
throw new Error('no such file or directory, open \'' + fileName + '\'');
}
if (!options || (options && (options.encoding !== 'bin'))) {
options = getOptions(options, {flag: 'r'});
}
if (options.encoding && ((options.encoding === 'ascii') || (options.encoding === 'utf8') || (options.encoding === 'utf-8'))) {
return loadFile(fileName, !(options.encoding === 'ascii'));
} else {
let buf = loadFileToBuffer(fileName) // UInt8Array
if (options.encoding === 'bin') return buf // ub 4.x compatibility mode
buf = Buffer.from(buf)
if (options.encoding)
buf = buf.toString(options.encoding);
return buf;
}
};
exports.readFileSync = readFileSync
function rethrow() {
return function(err) {
if (err) {
throw err;
}
};
}
function maybeCallback(cb) {
return typeof cb === 'function' ? cb : rethrow();
}
function makeOneArgFuncAsync(oneArgSyncFunc){
return function(arg, cb){
var _res;
var callback = maybeCallback(cb);
try {
_res = oneArgSyncFunc(arg);
callback(null, _res);
} catch(e){
callback(e);
}
}
}
exports.readFile = function readFile(fileName, options, callback_){
var stat = fileStat(fileName);
var callback = maybeCallback(arguments[arguments.length - 1]);
if (!stat) {
callback(new Error('no such file or directory, open \'' + fileName + '\''));
} else {
callback(null, readFileSync(fileName, options))
}
};
//noinspection JSUnusedLocalSymbols
/**
* Create all missing folders in the given path. Only absolute path supported. Throw error in case of fail
* @param {String} path path for creation.
* @param {Number} [mode] Ignored under Windows
*/
exports.mkdirSync = function mkdirSync(path, mode){
if (!forceDirectories(path)){
throw new Error('can\'t create directory ' + path);
}
};
/** Read file names from directory (include folder names).
* Return array of file names. In case directory not exists - throw error
* @param {String} path
* @return {Array.<String>}
*/
function readdirSync(path){
var res = readDir(path, true);
if (res == null) {
throw new Error('can not read dir ' + path);
} else {
return res;
}
};
exports.readdirSync = readdirSync;
exports.readdir = makeOneArgFuncAsync(readdirSync);
/**
* Get file statistics. Will throw in case file or folder does not exists.
* @param fileName
* @returns {Boolean|{atime: Date, mtime: Date, ctime: Date, size: number, _fileName: string, isDirectory: function}}
*/
function statSync(fileName){
var oStat;
oStat = fileStat(fileName);
if (oStat === null) throw new Error('ENOENT: no such file or directory, stat ' + fileName)
oStat._fileName = fileName;
oStat.isDirectory = function(){
return fs.isDir(this._fileName);
};
oStat.isFile = function(){
return !fs.isDir(this._fileName);
};
oStat.isSymbolicLink = function(){
return false; //TODO - implement
};
return oStat;
};
exports.statSync = statSync;
exports.lstatSync = statSync;
exports.stat = function stat(fileName, callback_){
var _stat
var callback = maybeCallback(arguments[arguments.length - 1]);
try {
_stat = statSync(fileName);
callback(null, _stat);
} catch (e) {
callback(e);
}
};
//todo - lstat is a followSymLync version of stat
exports.lstat = exports.stat;
/**
* Write to file
* Actually implements {@link UBWriter#write}
* @param {String} fileName Full absolute file path
* @param {ArrayBuffer|Object|String} data Data to write. If Object - it stringify before write
* @param {Object} [options]
* @param {String} [options.encoding] Encode data to `encoding` before write. Default to `utf-8` in case data is String or `bin` in case data is ArrayBuffer.
* One of "utf-8"|"ucs2"|"bin"|"base64".
*/
exports.writeFileSync = function writeFileSync(fileName, data, options){
//var res = writeFile(fileName, data);
var
encoding = options && options.encoding,
res;
res = encoding ? writeFile(fileName, data, encoding) : writeFile(fileName, data);
if(!res)
throw new Error('can not write file ' + fileName);
else return res;
};
/**
* Append data to a file, creating the file if it not yet exists
* Actually implement {UBWriter#write}
* @param {String} fileName Full absolute file path
* @param {ArrayBuffer|Object|String} data Data to write. `Object` are stringified before write
* @param {Object} [options]
* @param {String} [options.encoding] Encode data to `encoding` before write.
* Default to `utf-8` in case data is String or `bin` in case data is ArrayBuffer.
* Possible values: "utf-8"|"ucs2"|"bin"|"base64".
*/
exports.appendFileSync = function appendFileSync(fileName, data, options){
var
encoding = options && options.encoding,
res;
res = encoding ? appendFile(fileName, data, encoding) : appendFile(fileName, data);
if(!res)
throw new Error('can not write file ' + fileName);
else return res;
};
/**
* Check `path` exists (can be file, folder or symlync)
* @param path
* @return {Boolean}
*/
exports.existsSync = function existsSync(path){
return !!fileStat(path);
};
/**
* Delete file.
*/
function unlinkSync(path){
try{
return deleteFile(path)
}catch(e){
return false;
}
};
exports.unlinkSync = unlinkSync;
exports.unlink = makeOneArgFuncAsync(unlinkSync);
/**
* Delete non-empty directory. See {@link removeDir} for details
* @param {String} path path to remove
*/
exports.rmdirSync = function rmdirSync(path){
return removeDir(path);
};
/**
* Move (rename) file.
* @param {String} oldPath
* @param {String} newPath
*/
exports.renameSync = function renameSync(oldPath, newPath){
nullCheck(oldPath);
nullCheck(newPath);
return rename(pathModule._makeLong(oldPath),
pathModule._makeLong(newPath));
};
/**
* Fake class for nodeJS compatibility
*/
exports.ReadStream = ReadStream;
function ReadStream(){}

499
contrib/mORMot/SyNode/core_modules/node_modules/http.js generated vendored Normal file
View File

@@ -0,0 +1,499 @@
/**
* HTTP client.
* @example
*
var http = require('http');
var request = http.request({
//alternative to host/port/path is
//URL: 'http://localhost:888/getAppInfo',
host: 'localhost', port: '80', path: '/getAppInfo',
method: 'POST',
sendTimeout: 30000, receiveTimeout: 30000,
keepAlive: true,
compressionEnable: true
});
request.write('Add string to response');
var fileContent = fs.readFileSync('d:\binaryFile.txt'); // return ArrayBuffer, since encoding not passed
request.write(fileContent, 'base64'); // write file content as base64 encoded string
var response = request.end();
var http = require('http');
var assert = require('assert');
var DOMParser = require('xmldom').DOMParser;
// set global proxy settings if client is behind a proxy
// http.setGlobalProxyConfiguration('proxy.main:3249', 'localhost');
var resp = http.get('https://synopse.info/fossil/wiki/Synopse+OpenSource');
// check we are actually behind a proxy
// assert.ok(resp.headers('via').startsWith('1.1 proxy.main'), 'proxy used');
var index = resp.read();
console.log(index);
// var doc = new DOMParser().parseFromString(index);
// assert.ok(doc.documentElement.textContent.startsWith('mORMot'), 'got mORMot from mORMot');
*
* @module http
* @memberOf module:buildin
*/
const CRLF = '\r\n'
const url = require('url')
const EventEmitter = require('events').EventEmitter
const util = require('util')
const THTTPClient = process.binding('synode_http').THTTPClient
/* Global http proxy configuration.
Default value for proxy server getted form http_proxy environment variable.
Under Windows (Docker for Windows for example) HTTP_PROXY is used, so fallback to it also
*/
var
proxyConfig = {
server: process.env.http_proxy || process.env.HTTP_PROXY || '',
bypass: ''
},
connectionDefaults = {
useHTTPS: false,
useCompression: true,
keepAlive: false,
connectTimeout: 60000,
sendTimeout: 30000,
receiveTimeout: 30000
}
/**
* Configure global ( on the `http` module level) proxy server in case you can't configure it using
* either **`proxycfg.exe -u`** on Windows XP or **`netsh winhttp import proxy source=ie`** for other win version
* or by pass `options.proxyName` parameter.
*
* Settings applied only for newly created {ClientRequest} instances.
*
* See for details <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa383996(v=vs.85).aspx">this MS article</a>
*
* @param {String} proxy name of the proxy server to use in format `[[http|https]://]host[:port]` For example 'http://proxy.my.domain:3249'
* @param {String|Array} [bypass] semicolon delimited list jr array of host names or IP addresses, or host masks or both, that should not be routed through the proxy
*/
exports.setGlobalProxyConfiguration = function setGlobalProxyConfiguration (proxy, bypass) {
proxyConfig.proxy = proxy || ''
if (Array.isArray(bypass)) {
bypass = bypass.join(';')
}
proxyConfig.bypass = bypass || ''
}
/**
* Override global ( on the `http` module level) connectiuon defaults.
*
* Settings applied only for newly created {ClientRequest} instances.
*
* var http = require('http');
* http.setGlobalConnectionDefaults({receiveTimeout: 60000}); // set receive timeout to 60 sec.
*
* @param {Object} defaults
* @param {Boolean} [defaults.useHTTPS=false]
* @param {Boolean} [defaults.useCompression=true] Send 'Accept-encoding: gzip' header to server & unzip zipper responses
* @param {Boolean} [defaults.keepAlive=false] Use keep Alive HTTP protocol feature if server support it.
* @param {Number} [defaults.sendTimeout=30000] Send timeout in ms.
* @param {Number} [defaults.receiveTimeout=30000] Receive timeout in ms.
* @param {Number} [defaults.connectTimeout=60000] Connect timeout in ms.
*/
exports.setGlobalConnectionDefaults = function setGlobalConnectionDefaults (defaults) {
defaults = defaults || {}
Object.keys(connectionDefaults).forEach(function (key) {
if (defaults.hasOwnProperty(key)) {
connectionDefaults[key] = defaults[key]
}
})
}
/**
* Create new HTTP server connection. In case server behind the proxy - see {@link http.setGlobalProxyConfiguration} function.
* @param {Object|String} options Either URL string in format `protocol://host:port/path` or config
* @param {String} [options.URL] Service URL in format `protocol://host:port/path`. Will override `useHTTPS`, `server`, `host`, `port` and `path` if passed
* @param {String} [options.server] DEPRECATED. Server to connect in format 'host:port' or 'host' in case port == 80.
* @param {String} [options.host] Host to connect. If `server` not specified this value used
* @param {String} [options.port] Port. Default is 80 for HTTP or 443 for HTTPS
* @param {String} [options.path='/'] Request path. Defaults to '/'. Should include query string if any. E.G. '/index.html?page=12'
* @param {String} [options.method='GET'] HTTP method to use for request
* @param {Object<string, string>} [options.headers] An object containing request headers
* @param {Boolean} [options.useHTTPS=false]
* @param {Boolean} [options.useCompression=true] Send 'Accept-encoding: gzip' header to server & unzip zipper responses
* @param {Boolean} [options.keepAlive=false] Use keep Alive HTTP protocol feature if server support it.
* @param {Number} [options.sendTimeout=30000] Send timeout in ms.
* @param {Number} [options.receiveTimeout=30000] Receive timeout in ms.
* @param {Number} [options.connectTimeout=30000] Connect timeout in ms.
* @return {ClientRequest}
*/
exports.request = function request (options) {
var
parsedURL
if (typeof options === 'string') {
options = url.parse(options)
options.host = options.hostname
} else if (options.URL) {
parsedURL = url.parse(options.URL)
Object.assign(options, parsedURL)
options.host = options.hostname
} else if (options.server) {
var host_port = options.server.split(':')
options.host = host_port[0]
options.port = host_port[1]
}
if (!options.host) {
throw new Error('server host is mandatory')
}
if (!options.hostname) { options.hostname = options.host }
options.path = options.path || '/'
if (options.path.charAt(0) !== '/') options.path = '/' + options.path // need valid url according to the HTTP/1.1 RFC
options.headers = options.headers || {}
if (options.protocol) {
options.useHTTPS = (options.protocol === 'https:')
} else {
options.useHTTPS = options.useHTTPS == null ? connectionDefaults.useHTTPS : options.useHTTPS
}
options.port = options.port || (options.useHTTPS ? '443' : '80')
options.useCompression = options.useCompression == null ? true : options.useCompression
options.keepAlive = (options.keepAlive === true) ? 1 : connectionDefaults.keepAlive
options.sendTimeout = options.sendTimeout || connectionDefaults.sendTimeout
options.receiveTimeout = options.receiveTimeout || connectionDefaults.receiveTimeout
options.connectTimeout = options.connectTimeout || connectionDefaults.connectTimeout
options.method = options.method || 'GET'
return new ClientRequest(options)
}
var request = exports.request
function forEachSorted (obj, iterator, context) {
var keys = Object.keys(obj).sort()
keys.forEach(function (key) {
iterator.call(context, obj[key], key)
})
return keys
}
/**
* Add parameters to URL
*
* http.buildURL('/myMethod', {a: 1, b: "1212"}; // '/myMethod?a=1&b=1212
*
* @param {String} url
* @param {Object} params
* @returns {String}
*/
exports.buildURL = function buildURL (url, params) {
if (!params) {
return url
}
var parts = []
forEachSorted(params, function (value, key) {
if (value == null) {
return
}
if (!Array.isArray(value)) {
value = [value]
}
value.forEach(function (v) {
if (typeof v === 'object') {
v = JSON.stringify(v)
}
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(v))
})
})
return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&')
}
var buildUrl = exports.buildURL
/**
* Since most requests are GET requests without bodies, we provides this convenience method.
* The two difference between this method and http.request() is that
*
* - it sets the method to GET and calls req.end() automatically
* - can optionally take URLParams Object {paramName: paramValue, ..} and add parameters to request path
*
* @param {Object} options Request options as described in {@link http.request}
* @param {Object} [URLParams] optional parameters to add to options.path
* @returns {IncomingMessage}
*/
exports.get = function get (options, URLParams) {
var req = request(options)
if (URLParams) {
req.setPath(buildUrl(req.options.path, URLParams))
}
req.setMethod('GET')
return req.end()
}
/**
* This object is created internally and returned from {@link http.request}
* It represents an in-progress request whose header has already been queued.
* The header is still mutable using the {@link ClientRequest.setHeader setHeader(name, value)},
* {@link ClientRequest#getHeader getHeader(name)}, {@link ClientRequest#removeHeader removeHeader(name)} API.
* The actual header will be sent along with the {@link ClientRequest#end end()}.
*
* `path` & `method` parameter is still mutable using {@link ClientRequest#setPath setPath(path)} & {@link ClientRequest#setMethod setMethod(HTTPMethod)}
* @class ClientRequest
* @implements {UBWriter}
* @protected
* @param {Object} options
*/
function ClientRequest (options) {
this.options = Object.assign({}, options)
const _http = this.connection = new THTTPClient()
_http.initialize(options.host, options.port, options.useHTTPS, options.useCompression,
proxyConfig.proxy, proxyConfig.bypass, options.connectTimeout, options.sendTimeout, options.receiveTimeout
)
_http.keepAlive = options.keepAlive ? 1 : 0
// add EventEmitter to process object
EventEmitter.call(this)
util._extend(this, EventEmitter.prototype)
Object.defineProperty(this, 'path', {
get: function () { return this.options.path },
set: function (val) { this.options.path = val }
})
}
/**
* Write a chunk of data to request. Actual sending performed by `end()` call.
* @inheritDoc
*/
ClientRequest.prototype.write = function (data, encoding) {
this.connection.write(data, encoding)
}
/**
* Set all headers delimited by CRLF by once
* @param {String} allHeaders
*/
ClientRequest.prototype.setHeadersAsString = function (allHeaders) {
this.options._headersAsString = allHeaders
}
function makeRequestHeaders (request) {
if (request.options._headersAsString) return request.options._headersAsString
let arr = []
let head = request.options.headers
for (let prop in head) {
arr.push(prop + ': ' + head[prop])
}
return arr.join(CRLF)
}
/**
* End request by writing a last chunk of data (optional) and send request to server.
* See {@link UBWriter#write} for parameters
* @returns {IncomingMessage}
*/
ClientRequest.prototype.end = function (data, encoding) {
var
_http = this.connection,
rUlr
_http.writeEnd(data, encoding)
_http.method = this.options.method
_http.headers = makeRequestHeaders(this)
try {
_http.doRequest(this.options.path)
} catch (e) {
rUlr = (this.options.protocol || 'http:') + '//' + this.options.hostname + ':' + this.options.port + this.options.path
throw new Error('Request to ' + rUlr + ' fail. Message: ' + e.message)
}
let msg = new IncomingMessage(_http)
if (!this.emit('response', msg) ||
!msg.emit('data', new Buffer(msg.read(msg.encoding === 'binary' ? 'bin' : msg.encoding === 'utf8' ? 'utf-8' : msg.encoding)).toString(msg.encoding)) ||
!msg.emit('end')) {
return msg
}
}
/**
* Set new path for request. Usually used during several request to the same server to avoid socket recreation.
* @param {String} path New path. Should include query string if any. E.G. '/index.html?page=12'
*/
ClientRequest.prototype.setPath = function (path) {
this.options.path = path
}
/**
* Set new HTTP method for request. Usually used during several request to the same server to avoid socket recreation.
* @param {String} method
*/
ClientRequest.prototype.setMethod = function (method) {
this.options.method = method
}
/**
* Sets a single header value for implicit headers.
* If this header already exists in the to-be-sent headers, its value will be replaced.
* Use an array of strings here if you need to send multiple headers with the same name
*
* request.setHeader('Content-Type', 'text/html');
* request.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);
*
* @param {String} name
* @param {String|Array} value
*/
ClientRequest.prototype.setHeader = function (name, value) {
this.options.headers[name] = Array.isArray(value) ? value.join(';') : value
}
/**
* Reads out a header that's already been queued but not sent to the client.
* @param {String} name
* @returns {String}
*/
ClientRequest.prototype.getHeader = function (name) {
if (arguments.length < 1) {
throw new Error('`name` is required for getHeader().')
}
return this.options.headers[name]
}
/**
* Removes a header that's queued for implicit sending
* @param {String} name
*/
ClientRequest.prototype.removeHeader = function (name) {
if (arguments.length < 1) {
throw new Error('`name` is required for removeHeader().')
}
delete this.options.headers[name]
}
/**
* Result of HTTP request
* @class IncomingMessage
* @implements {UBReader}
* @param {THTTPClient} httpClient
* @protected
*/
function IncomingMessage (httpClient) {
this._http = httpClient
/**
* Default encoding for read call
* @type {String}
*/
this.encoding = 'utf-8'
/** @private */
this._parsedHeaders = null
/**
* HTTP status code. See also {STATUS_CODES}
* @type {Number}
* @readonly
*/
this.statusCode = this._http.responseStatus
// add EventEmitter to IncomingMessage object
EventEmitter.call(this)
util._extend(this, EventEmitter.prototype)
/**
* Response headers, transformed to JS object. Headers name is a keys in lower case
*/
Object.defineProperty(this, 'headers', {
get: () => this._parsedHeaders ? this._parsedHeaders : this.__doParseHeaders()
})
}
/**
* Change default encoding for read request
* @param {String} encoding
*/
IncomingMessage.prototype.setEncoding = function (encoding) {
this.encoding = encoding
}
/**
* Read a response body. See {@link UBReader#read} for parameters
* @param {String} [encoding] If omitted `this.encoding` in used
*/
IncomingMessage.prototype.read = function (encoding) {
return this._http.read(encoding || this.encoding)
}
/**
* Internal function for parse response headers
* TODO - improve node compatibility - some headers MUST me merged. See https://nodejs.org/api/http.html#http_message_headers
* @private
*/
IncomingMessage.prototype.__doParseHeaders = function () {
var
h, hObj, hPart
if (!this._parsedHeaders) {
h = this._http.responseHeaders.split(CRLF)
hObj = {}
h.forEach(function (header) {
if (header) {
hPart = header.split(': ', 2)
if (hPart.length = 2) { hObj[hPart[0].toLowerCase()] = hPart[1] }
}
})
this._parsedHeaders = hObj
}
return this._parsedHeaders
}
/**
* HTTP status codes.
* @type {Object.<number, string>}
*/
exports.STATUS_CODES = {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing', // RFC 2518, obsoleted by RFC 4918
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status', // RFC 4918
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Moved Temporarily',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
307: 'Temporary Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Time-out',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Large',
415: 'Unsupported Media Type',
416: 'Requested Range Not Satisfiable',
417: 'Expectation Failed',
418: 'I\'m a teapot', // RFC 2324
422: 'Unprocessable Entity', // RFC 4918
423: 'Locked', // RFC 4918
424: 'Failed Dependency', // RFC 4918
425: 'Unordered Collection', // RFC 4918
426: 'Upgrade Required', // RFC 2817
428: 'Precondition Required', // RFC 6585
429: 'Too Many Requests', // RFC 6585
431: 'Request Header Fields Too Large', // RFC 6585
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Time-out',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates', // RFC 2295
507: 'Insufficient Storage', // RFC 4918
509: 'Bandwidth Limit Exceeded',
510: 'Not Extended', // RFC 2774
511: 'Network Authentication Required' // RFC 6585
}

View File

@@ -0,0 +1,17 @@
/**
* HTTPS client.
* @module https
* @memberOf module:buildin
*/
let http = require('http');
exports.request = function request(options) {
options.useHTTPS = true;
return http.request(options);
}
exports.get = function request(options) {
options.useHTTPS = true;
return http.get(options);
}

View File

@@ -0,0 +1,551 @@
/* eslint documented-errors: "error" */
/* eslint alphabetize-errors: "error" */
/* eslint prefer-util-format-errors: "error" */
'use strict';
// The whole point behind this internal module is to allow Node.js to no
// longer be forced to treat every error message change as a semver-major
// change. The NodeError classes here all expose a `code` property whose
// value statically and permanently identifies the error. While the error
// message may change, the code should not.
const kCode = Symbol('code');
const kInfo = Symbol('info');
const messages = new Map();
const { kMaxLength } = process.binding('buffer');
const { defineProperty } = Object;
// Lazily loaded
var util = null;
var buffer;
function makeNodeError(Base) {
return class NodeError extends Base {
constructor(key, ...args) {
super(message(key, args));
defineProperty(this, kCode, {
configurable: true,
enumerable: false,
value: key,
writable: true
});
}
get name() {
return `${super.name} [${this[kCode]}]`;
}
set name(value) {
defineProperty(this, 'name', {
configurable: true,
enumerable: true,
value,
writable: true
});
}
get code() {
return this[kCode];
}
set code(value) {
defineProperty(this, 'code', {
configurable: true,
enumerable: true,
value,
writable: true
});
}
};
}
function lazyBuffer() {
if (buffer === undefined)
buffer = require('buffer').Buffer;
return buffer;
}
// A specialized Error that includes an additional info property with
// additional information about the error condition. The code key will
// be extracted from the context object or the ERR_SYSTEM_ERROR default
// will be used.
class SystemError extends makeNodeError(Error) {
constructor(context) {
context = context || {};
let code = 'ERR_SYSTEM_ERROR';
if (messages.has(context.code))
code = context.code;
super(code,
context.code,
context.syscall,
context.path,
context.dest,
context.message);
Object.defineProperty(this, kInfo, {
configurable: false,
enumerable: false,
value: context
});
}
get info() {
return this[kInfo];
}
get errno() {
return this[kInfo].errno;
}
set errno(val) {
this[kInfo].errno = val;
}
get syscall() {
return this[kInfo].syscall;
}
set syscall(val) {
this[kInfo].syscall = val;
}
get path() {
return this[kInfo].path !== undefined ?
this[kInfo].path.toString() : undefined;
}
set path(val) {
this[kInfo].path = val ?
lazyBuffer().from(val.toString()) : undefined;
}
get dest() {
return this[kInfo].path !== undefined ?
this[kInfo].dest.toString() : undefined;
}
set dest(val) {
this[kInfo].dest = val ?
lazyBuffer().from(val.toString()) : undefined;
}
}
class AssertionError extends Error {
constructor(options) {
if (typeof options !== 'object' || options === null) {
throw new exports.TypeError('ERR_INVALID_ARG_TYPE', 'options', 'Object');
}
var { actual, expected, message, operator, stackStartFunction } = options;
if (message) {
super(message);
} else {
if (actual && actual.stack && actual instanceof Error)
actual = `${actual.name}: ${actual.message}`;
if (expected && expected.stack && expected instanceof Error)
expected = `${expected.name}: ${expected.message}`;
if (util === null) util = require('util');
super(`${util.inspect(actual).slice(0, 128)} ` +
`${operator} ${util.inspect(expected).slice(0, 128)}`);
}
this.generatedMessage = !message;
this.name = 'AssertionError [ERR_ASSERTION]';
this.code = 'ERR_ASSERTION';
this.actual = actual;
this.expected = expected;
this.operator = operator;
Error.captureStackTrace(this, stackStartFunction);
}
}
// This is defined here instead of using the assert module to avoid a
// circular dependency. The effect is largely the same.
function internalAssert(condition, message) {
if (!condition) {
throw new AssertionError({
message,
actual: false,
expected: true,
operator: '=='
});
}
}
function message(key, args) {
const msg = messages.get(key);
internalAssert(msg, `An invalid error message key was used: ${key}.`);
let fmt;
if (typeof msg === 'function') {
fmt = msg;
} else {
if (util === null) util = require('util');
fmt = util.format;
if (args === undefined || args.length === 0)
return msg;
args.unshift(msg);
}
return String(fmt.apply(null, args));
}
// Utility function for registering the error codes. Only used here. Exported
// *only* to allow for testing.
function E(sym, val) {
messages.set(sym, typeof val === 'function' ? val : String(val));
}
module.exports = exports = {
message,
Error: makeNodeError(Error),
TypeError: makeNodeError(TypeError),
RangeError: makeNodeError(RangeError),
URIError: makeNodeError(URIError),
AssertionError,
SystemError,
E // This is exported only to facilitate testing.
};
// To declare an error message, use the E(sym, val) function above. The sym
// must be an upper case string. The val can be either a function or a string.
// The return value of the function must be a string.
// Examples:
// E('EXAMPLE_KEY1', 'This is the error value');
// E('EXAMPLE_KEY2', (a, b) => return `${a} ${b}`);
//
// Once an error code has been assigned, the code itself MUST NOT change and
// any given error code must never be reused to identify a different error.
//
// Any error code added here should also be added to the documentation
//
// Note: Please try to keep these in alphabetical order
//
// Note: Node.js specific errors must begin with the prefix ERR_
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable');
E('ERR_ASSERTION', '%s');
E('ERR_ASYNC_CALLBACK', '%s must be a function');
E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s');
E('ERR_BUFFER_OUT_OF_BOUNDS', bufferOutOfBounds);
E('ERR_BUFFER_TOO_LARGE',
`Cannot create a Buffer larger than 0x${kMaxLength.toString(16)} bytes`);
E('ERR_CANNOT_WATCH_SIGINT', 'Cannot watch for SIGINT signals');
E('ERR_CHILD_CLOSED_BEFORE_REPLY', 'Child closed before reply received');
E('ERR_CONSOLE_WRITABLE_STREAM',
'Console expects a writable stream instance for %s');
E('ERR_CPU_USAGE', 'Unable to obtain cpu usage %s');
E('ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED',
'Custom engines not supported by this OpenSSL');
E('ERR_CRYPTO_ECDH_INVALID_FORMAT', 'Invalid ECDH format: %s');
E('ERR_CRYPTO_ENGINE_UNKNOWN', 'Engine "%s" was not found');
E('ERR_CRYPTO_FIPS_FORCED',
'Cannot set FIPS mode, it was forced with --force-fips at startup.');
E('ERR_CRYPTO_FIPS_UNAVAILABLE', 'Cannot set FIPS mode in a non-FIPS build.');
E('ERR_CRYPTO_HASH_DIGEST_NO_UTF16', 'hash.digest() does not support UTF-16');
E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called');
E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed');
E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s');
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s');
E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign');
E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
'Input buffers must have the same length');
E('ERR_DNS_SET_SERVERS_FAILED', 'c-ares failed to set servers: "%s" [%s]');
E('ERR_ENCODING_INVALID_ENCODED_DATA',
'The encoded data was not valid for encoding %s');
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported');
E('ERR_FALSY_VALUE_REJECTION', 'Promise was rejected with falsy value');
E('ERR_HTTP2_CONNECT_AUTHORITY',
':authority header is required for CONNECT requests');
E('ERR_HTTP2_CONNECT_PATH',
'The :path header is forbidden for CONNECT requests');
E('ERR_HTTP2_CONNECT_SCHEME',
'The :scheme header is forbidden for CONNECT requests');
E('ERR_HTTP2_FRAME_ERROR',
(type, code, id) => {
let msg = `Error sending frame type ${type}`;
if (id !== undefined)
msg += ` for stream ${id}`;
msg += ` with code ${code}`;
return msg;
});
E('ERR_HTTP2_HEADERS_AFTER_RESPOND',
'Cannot specify additional headers after response initiated');
E('ERR_HTTP2_HEADERS_OBJECT', 'Headers must be an object');
E('ERR_HTTP2_HEADERS_SENT', 'Response has already been initiated.');
E('ERR_HTTP2_HEADER_REQUIRED', 'The %s header is required');
E('ERR_HTTP2_HEADER_SINGLE_VALUE',
'Header field "%s" must have only a single value');
E('ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND',
'Cannot send informational headers after the HTTP message has been sent');
E('ERR_HTTP2_INFO_STATUS_NOT_ALLOWED',
'Informational status codes cannot be used');
E('ERR_HTTP2_INVALID_CONNECTION_HEADERS',
'HTTP/1 Connection specific headers are forbidden: "%s"');
E('ERR_HTTP2_INVALID_HEADER_VALUE', 'Invalid value "%s" for header "%s"');
E('ERR_HTTP2_INVALID_INFO_STATUS',
'Invalid informational status code: %s');
E('ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH',
'Packed settings length must be a multiple of six');
E('ERR_HTTP2_INVALID_PSEUDOHEADER',
'"%s" is an invalid pseudoheader or is used incorrectly');
E('ERR_HTTP2_INVALID_SESSION', 'The session has been destroyed');
E('ERR_HTTP2_INVALID_SETTING_VALUE',
'Invalid value for setting "%s": %s');
E('ERR_HTTP2_INVALID_STREAM', 'The stream has been destroyed');
E('ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
'Maximum number of pending settings acknowledgements (%s)');
E('ERR_HTTP2_NO_SOCKET_MANIPULATION',
'HTTP/2 sockets should not be directly manipulated (e.g. read and written)');
E('ERR_HTTP2_OUT_OF_STREAMS',
'No stream ID is available because maximum stream ID has been reached');
E('ERR_HTTP2_PAYLOAD_FORBIDDEN',
'Responses with %s status must not have a payload');
E('ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED', 'Cannot set HTTP/2 pseudo-headers');
E('ERR_HTTP2_PUSH_DISABLED', 'HTTP/2 client has disabled push streams');
E('ERR_HTTP2_SEND_FILE', 'Only regular files can be sent');
E('ERR_HTTP2_SOCKET_BOUND',
'The socket is already bound to an Http2Session');
E('ERR_HTTP2_STATUS_101',
'HTTP status code 101 (Switching Protocols) is forbidden in HTTP/2');
E('ERR_HTTP2_STATUS_INVALID', 'Invalid status code: %s');
E('ERR_HTTP2_STREAM_CLOSED', 'The stream is already closed');
E('ERR_HTTP2_STREAM_ERROR', 'Stream closed with error code %s');
E('ERR_HTTP2_STREAM_SELF_DEPENDENCY', 'A stream cannot depend on itself');
E('ERR_HTTP2_UNSUPPORTED_PROTOCOL', 'protocol "%s" is unsupported.');
E('ERR_HTTP_HEADERS_SENT',
'Cannot %s headers after they are sent to the client');
E('ERR_HTTP_INVALID_CHAR', 'Invalid character in statusMessage.');
E('ERR_HTTP_INVALID_HEADER_VALUE', 'Invalid value "%s" for header "%s"');
E('ERR_HTTP_INVALID_STATUS_CODE', 'Invalid status code: %s');
E('ERR_HTTP_TRAILER_INVALID',
'Trailers are invalid with this transfer encoding');
E('ERR_INDEX_OUT_OF_RANGE', 'Index out of range');
E('ERR_INSPECTOR_ALREADY_CONNECTED', 'The inspector is already connected');
E('ERR_INSPECTOR_CLOSED', 'Session was closed');
E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available');
E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected');
E('ERR_INVALID_ARG_TYPE', invalidArgType);
E('ERR_INVALID_ARG_VALUE', (name, value) =>
`The value "${String(value)}" is invalid for argument "${name}"`);
E('ERR_INVALID_ARRAY_LENGTH',
(name, len, actual) => {
internalAssert(typeof actual === 'number', 'actual must be a number');
return `The array "${name}" (length ${actual}) must be of length ${len}.`;
});
E('ERR_INVALID_ASYNC_ID', 'Invalid %s value: %s');
E('ERR_INVALID_BUFFER_SIZE', 'Buffer size must be a multiple of %s');
E('ERR_INVALID_CALLBACK', 'Callback must be a function');
E('ERR_INVALID_CHAR', invalidChar);
E('ERR_INVALID_CURSOR_POS',
'Cannot set cursor row without setting its column');
E('ERR_INVALID_DOMAIN_NAME', 'Unable to determine the domain name');
E('ERR_INVALID_FD', '"fd" must be a positive integer: %s');
E('ERR_INVALID_FD_TYPE', 'Unsupported fd type: %s');
E('ERR_INVALID_FILE_URL_HOST',
'File URL host must be "localhost" or empty on %s');
E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s');
E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent');
E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]');
E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s');
E('ERR_INVALID_OPT_VALUE', (name, value) =>
`The value "${String(value)}" is invalid for option "${name}"`);
E('ERR_INVALID_OPT_VALUE_ENCODING',
'The value "%s" is invalid for option "encoding"');
E('ERR_INVALID_PERFORMANCE_MARK', 'The "%s" performance mark has not been set');
E('ERR_INVALID_PROTOCOL', 'Protocol "%s" not supported. Expected "%s"');
E('ERR_INVALID_REPL_EVAL_CONFIG',
'Cannot specify both "breakEvalOnSigint" and "eval" for REPL');
E('ERR_INVALID_SYNC_FORK_INPUT',
'Asynchronous forks do not support Buffer, Uint8Array or string input: %s');
E('ERR_INVALID_THIS', 'Value of "this" must be of type %s');
E('ERR_INVALID_TUPLE', '%s must be an iterable %s tuple');
E('ERR_INVALID_URI', 'URI malformed');
E('ERR_INVALID_URL', 'Invalid URL: %s');
E('ERR_INVALID_URL_SCHEME',
(expected) => `The URL must be ${oneOf(expected, 'scheme')}`);
E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed');
E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected');
E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe');
E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks');
E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented');
E('ERR_MISSING_ARGS', missingArgs);
E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
'The ES Module loader may not return a format of \'dynamic\' when no ' +
'dynamicInstantiate function was provided');
E('ERR_MISSING_MODULE', 'Cannot find module %s');
E('ERR_MODULE_RESOLUTION_LEGACY', '%s not found by import in %s.' +
' Legacy behavior in require() would have found it at %s');
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times');
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function');
E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object');
E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support');
E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU');
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported');
E('ERR_OUTOFMEMORY', 'Out of memory');
E('ERR_OUT_OF_RANGE', 'The "%s" argument is out of range');
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s');
E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s');
E('ERR_SERVER_ALREADY_LISTEN',
'Listen method has been called more than once without closing.');
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
E('ERR_SOCKET_BAD_BUFFER_SIZE', 'Buffer size must be a positive integer');
E('ERR_SOCKET_BAD_PORT', 'Port should be > 0 and < 65536. Received %s.');
E('ERR_SOCKET_BAD_TYPE',
'Bad socket type specified. Valid types are: udp4, udp6');
E('ERR_SOCKET_BUFFER_SIZE', 'Could not get or set buffer size: %s');
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data');
E('ERR_SOCKET_CLOSED', 'Socket is closed');
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running');
E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed');
E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed');
E('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable');
E('ERR_STREAM_NULL_VALUES', 'May not write null values to stream');
E('ERR_STREAM_PUSH_AFTER_EOF', 'stream.push() after EOF');
E('ERR_STREAM_READ_NOT_IMPLEMENTED', '_read() is not implemented');
E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT', 'stream.unshift() after end event');
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode');
E('ERR_STREAM_WRITE_AFTER_END', 'write after end');
E('ERR_SYSTEM_ERROR', sysError('A system error occurred'));
E('ERR_TLS_CERT_ALTNAME_INVALID',
'Hostname/IP does not match certificate\'s altnames: %s');
E('ERR_TLS_DH_PARAM_SIZE', 'DH parameter size %s is less than 2048');
E('ERR_TLS_HANDSHAKE_TIMEOUT', 'TLS handshake timeout');
E('ERR_TLS_RENEGOTIATION_FAILED', 'Failed to renegotiate');
E('ERR_TLS_REQUIRED_SERVER_NAME',
'"servername" is required parameter for Server.addContext');
E('ERR_TLS_SESSION_ATTACK', 'TSL session renegotiation attack detected');
E('ERR_TRANSFORM_ALREADY_TRANSFORMING',
'Calling transform done when still transforming');
E('ERR_TRANSFORM_WITH_LENGTH_0',
'Calling transform done when writableState.length != 0');
E('ERR_UNESCAPED_CHARACTERS', '%s contains unescaped characters');
E('ERR_UNHANDLED_ERROR',
(err) => {
const msg = 'Unhandled error.';
if (err === undefined) return msg;
return `${msg} (${err})`;
});
E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s');
E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: %s');
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s');
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s');
E('ERR_UNKNOWN_STDIN_TYPE', 'Unknown stdin file type');
E('ERR_UNKNOWN_STREAM_TYPE', 'Unknown stream file type');
E('ERR_V8BREAKITERATOR', 'Full ICU data not installed. ' +
'See https://github.com/nodejs/node/wiki/Intl');
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
'At least one valid performance entry type is required');
E('ERR_VALUE_OUT_OF_RANGE', (start, end, value) => {
return `The value of "${start}" must be ${end}. Received "${value}"`;
});
E('ERR_ZLIB_BINDING_CLOSED', 'zlib binding closed');
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed');
function sysError(defaultMessage) {
return function(code,
syscall,
path,
dest,
message = defaultMessage) {
if (code !== undefined)
message += `: ${code}`;
if (syscall !== undefined) {
if (code === undefined)
message += ':';
message += ` [${syscall}]`;
}
if (path !== undefined) {
message += `: ${path}`;
if (dest !== undefined)
message += ` => ${dest}`;
}
return message;
};
}
function invalidArgType(name, expected, actual) {
internalAssert(name, 'name is required');
// determiner: 'must be' or 'must not be'
let determiner;
if (typeof expected === 'string' && expected.startsWith('not ')) {
determiner = 'must not be';
expected = expected.replace(/^not /, '');
} else {
determiner = 'must be';
}
let msg;
if (Array.isArray(name)) {
var names = name.map((val) => `"${val}"`).join(', ');
msg = `The ${names} arguments ${determiner} ${oneOf(expected, 'type')}`;
} else if (name.endsWith(' argument')) {
// for the case like 'first argument'
msg = `The ${name} ${determiner} ${oneOf(expected, 'type')}`;
} else {
const type = name.includes('.') ? 'property' : 'argument';
msg = `The "${name}" ${type} ${determiner} ${oneOf(expected, 'type')}`;
}
// if actual value received, output it
if (arguments.length >= 3) {
msg += `. Received type ${actual !== null ? typeof actual : 'null'}`;
}
return msg;
}
function missingArgs(...args) {
internalAssert(args.length > 0, 'At least one arg needs to be specified');
let msg = 'The ';
const len = args.length;
args = args.map((a) => `"${a}"`);
switch (len) {
case 1:
msg += `${args[0]} argument`;
break;
case 2:
msg += `${args[0]} and ${args[1]} arguments`;
break;
default:
msg += args.slice(0, len - 1).join(', ');
msg += `, and ${args[len - 1]} arguments`;
break;
}
return `${msg} must be specified`;
}
function oneOf(expected, thing) {
internalAssert(expected, 'expected is required');
internalAssert(typeof thing === 'string', 'thing is required');
if (Array.isArray(expected)) {
const len = expected.length;
internalAssert(len > 0,
'At least one expected value needs to be specified');
expected = expected.map((i) => String(i));
if (len > 2) {
return `one of ${thing} ${expected.slice(0, len - 1).join(', ')}, or ` +
expected[len - 1];
} else if (len === 2) {
return `one of ${thing} ${expected[0]} or ${expected[1]}`;
} else {
return `of ${thing} ${expected[0]}`;
}
} else {
return `of ${thing} ${String(expected)}`;
}
}
function bufferOutOfBounds(name, isWriting) {
if (isWriting) {
return 'Attempt to write outside buffer bounds';
} else {
return `"${name}" is outside of buffer bounds`;
}
}
function invalidChar(name, field) {
let msg = `Invalid character in ${name}`;
if (field) {
msg += ` ["${field}"]`;
}
return msg;
}

View File

@@ -0,0 +1,102 @@
'use strict';
const { Buffer } = require('buffer');
const { Writable } = require('stream');
const fs = require('fs');
const util = require('util');
const {
O_APPEND,
O_CREAT,
O_EXCL,
O_RDONLY,
O_RDWR,
O_SYNC,
O_TRUNC,
O_WRONLY
} = process.binding('constants').fs;
function assertEncoding(encoding) {
if (encoding && !Buffer.isEncoding(encoding)) {
throw new Error(`Unknown encoding: ${encoding}`);
}
}
function stringToFlags(flag) {
if (typeof flag === 'number') {
return flag;
}
switch (flag) {
case 'r' : return O_RDONLY;
case 'rs' : // Fall through.
case 'sr' : return O_RDONLY | O_SYNC;
case 'r+' : return O_RDWR;
case 'rs+' : // Fall through.
case 'sr+' : return O_RDWR | O_SYNC;
case 'w' : return O_TRUNC | O_CREAT | O_WRONLY;
case 'wx' : // Fall through.
case 'xw' : return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
case 'w+' : return O_TRUNC | O_CREAT | O_RDWR;
case 'wx+': // Fall through.
case 'xw+': return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
case 'a' : return O_APPEND | O_CREAT | O_WRONLY;
case 'ax' : // Fall through.
case 'xa' : return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
case 'a+' : return O_APPEND | O_CREAT | O_RDWR;
case 'ax+': // Fall through.
case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
}
throw new Error('Unknown file open flag: ' + flag);
}
// Temporary hack for process.stdout and process.stderr when piped to files.
function SyncWriteStream(fd, options) {
Writable.call(this);
options = options || {};
this.fd = fd;
this.readable = false;
this.autoClose = options.autoClose === undefined ? true : options.autoClose;
this.on('end', () => this._destroy());
}
util.inherits(SyncWriteStream, Writable);
SyncWriteStream.prototype._write = function(chunk, encoding, cb) {
fs.writeSync(this.fd, chunk, 0, chunk.length);
cb();
return true;
};
SyncWriteStream.prototype._destroy = function() {
if (this.fd === null) // already destroy()ed
return;
if (this.autoClose)
fs.closeSync(this.fd);
this.fd = null;
return true;
};
SyncWriteStream.prototype.destroySoon =
SyncWriteStream.prototype.destroy = function() {
this._destroy();
this.emit('close');
return true;
};
module.exports = {
assertEncoding,
stringToFlags,
SyncWriteStream,
realpathCacheKey: Symbol('realpathCacheKey')
};

View File

@@ -0,0 +1,149 @@
'use strict';
// Invoke with makeRequireFunction(module) where |module| is the Module object
// to use as the context for the require() function.
function makeRequireFunction(mod) {
const Module = mod.constructor;
function require(path) {
try {
exports.requireDepth += 1;
return mod.require(path);
} finally {
exports.requireDepth -= 1;
}
}
function resolve(request, options) {
return Module._resolveFilename(request, mod, false, options);
}
require.resolve = resolve;
function paths(request) {
return Module._resolveLookupPaths(request, mod, true);
}
resolve.paths = paths;
require.main = process.mainModule;
// Enable support to add extra extension types.
require.extensions = Module._extensions;
require.cache = Module._cache;
return require;
}
/**
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
* because the buffer-to-string conversion in `fs.readFileSync()`
* translates it to FEFF, the UTF-16 BOM.
*/
function stripBOM(content) {
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
/**
* Find end of shebang line and slice it off
*/
function stripShebang(content) {
// Remove shebang
var contLen = content.length;
if (contLen >= 2) {
if (content.charCodeAt(0) === 35/*#*/ &&
content.charCodeAt(1) === 33/*!*/) {
if (contLen === 2) {
// Exact match
content = '';
} else {
// Find end of shebang line and slice it off
var i = 2;
for (; i < contLen; ++i) {
var code = content.charCodeAt(i);
if (code === 10/*\n*/ || code === 13/*\r*/)
break;
}
if (i === contLen)
content = '';
else {
// Note that this actually includes the newline character(s) in the
// new output. This duplicates the behavior of the regular expression
// that was previously used to replace the shebang line
content = content.slice(i);
}
}
}
}
return content;
}
const builtinLibs = [
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto',
'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net',
'os', 'path', 'perf_hooks', 'punycode', 'querystring', 'readline', 'repl',
'stream', 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'
];
/* SyNode
const { exposeHTTP2 } = process.binding('config');
if (exposeHTTP2)
builtinLibs.push('http2');
if (typeof process.binding('inspector').connect === 'function') {
builtinLibs.push('inspector');
builtinLibs.sort();
}
*/
function addBuiltinLibsToObject(object) {
// Make built-in modules available directly (loaded lazily).
builtinLibs.forEach((name) => {
// Goals of this mechanism are:
// - Lazy loading of built-in modules
// - Having all built-in modules available as non-enumerable properties
// - Allowing the user to re-assign these variables as if there were no
// pre-existing globals with the same name.
const setReal = (val) => {
// Deleting the property before re-assigning it disables the
// getter/setter mechanism.
delete object[name];
object[name] = val;
};
Object.defineProperty(object, name, {
get: () => {
const lib = require(name);
// Disable the current getter/setter and set up a new
// non-enumerable property.
delete object[name];
Object.defineProperty(object, name, {
get: () => lib,
set: setReal,
configurable: true,
enumerable: false
});
return lib;
},
set: setReal,
configurable: true,
enumerable: false
});
});
}
module.exports = exports = {
addBuiltinLibsToObject,
builtinLibs,
makeRequireFunction,
requireDepth: 0,
stripBOM,
stripShebang
};

View File

@@ -0,0 +1,18 @@
'use strict';
module.exports = { isLegalPort, assertPort };
// Check that the port number is not NaN when coerced to a number,
// is an integer and that it falls within the legal range of port numbers.
function isLegalPort(port) {
if ((typeof port !== 'number' && typeof port !== 'string') ||
(typeof port === 'string' && port.trim().length === 0))
return false;
return +port === (+port >>> 0) && port <= 0xFFFF;
}
function assertPort(port) {
if (typeof port !== 'undefined' && !isLegalPort(port))
throw new RangeError('"port" argument must be >= 0 and < 65536');
}

View File

@@ -0,0 +1,175 @@
'use strict';
exports.setup = setupStdio;
function setupStdio() {
var stdin, stdout, stderr;
function getStdout() {
if (stdout) return stdout;
stdout = createWritableStdioStream(1);
stdout.destroy = stdout.destroySoon = function(er) {
er = er || new Error('process.stdout cannot be closed.');
stdout.emit('error', er);
};
if (stdout.isTTY) {
process.on('SIGWINCH', () => stdout._refreshSize());
}
return stdout;
}
function getStderr() {
if (stderr) return stderr;
stderr = createWritableStdioStream(2);
stderr.destroy = stderr.destroySoon = function(er) {
er = er || new Error('process.stderr cannot be closed.');
stderr.emit('error', er);
};
if (stderr.isTTY) {
process.on('SIGWINCH', () => stderr._refreshSize());
}
return stderr;
}
function getStdin() {
if (stdin) return stdin;
const tty_wrap = process.binding('tty_wrap');
const fd = 0;
switch (tty_wrap.guessHandleType(fd)) {
case 'TTY':
const tty = require('tty');
stdin = new tty.ReadStream(fd, {
highWaterMark: 0,
readable: true,
writable: false
});
break;
case 'FILE':
const fs = require('fs');
stdin = new fs.ReadStream(null, { fd: fd, autoClose: false });
break;
case 'PIPE':
case 'TCP':
const net = require('net');
// It could be that process has been started with an IPC channel
// sitting on fd=0, in such case the pipe for this fd is already
// present and creating a new one will lead to the assertion failure
// in libuv.
if (process._channel && process._channel.fd === fd) {
stdin = new net.Socket({
handle: process._channel,
readable: true,
writable: false
});
} else {
stdin = new net.Socket({
fd: fd,
readable: true,
writable: false
});
}
// Make sure the stdin can't be `.end()`-ed
stdin._writableState.ended = true;
break;
default:
// Probably an error on in uv_guess_handle()
throw new Error('Implement me. Unknown stdin file type!');
}
// For supporting legacy API we put the FD here.
stdin.fd = fd;
// stdin starts out life in a paused state, but node doesn't
// know yet. Explicitly to readStop() it to put it in the
// not-reading state.
if (stdin._handle && stdin._handle.readStop) {
stdin._handle.reading = false;
stdin._readableState.reading = false;
stdin._handle.readStop();
}
// if the user calls stdin.pause(), then we need to stop reading
// immediately, so that the process can close down.
stdin.on('pause', () => {
if (!stdin._handle)
return;
stdin._readableState.reading = false;
stdin._handle.reading = false;
stdin._handle.readStop();
});
return stdin;
}
Object.defineProperty(process, 'stdout', {
configurable: true,
enumerable: true,
get: getStdout
});
Object.defineProperty(process, 'stderr', {
configurable: true,
enumerable: true,
get: getStderr
});
Object.defineProperty(process, 'stdin', {
configurable: true,
enumerable: true,
get: getStdin
});
process.openStdin = function() {
process.stdin.resume();
return process.stdin;
};
}
function createWritableStdioStream(fd) {
var stream;
const tty_wrap = process.binding('tty_wrap');
// Note stream._type is used for test-module-load-list.js
switch (tty_wrap.guessHandleType(fd)) {
case 'TTY':
const tty = require('tty');
stream = new tty.WriteStream(fd);
stream._type = 'tty';
break;
case 'FILE':
const fs = require('internal/fs');
stream = new fs.SyncWriteStream(fd, { autoClose: false });
stream._type = 'fs';
break;
case 'PIPE':
case 'TCP':
const net = require('net');
stream = new net.Socket({
fd: fd,
readable: false,
writable: true
});
stream._type = 'pipe';
break;
default:
// Probably an error on in uv_guess_handle()
throw new Error('Implement me. Unknown stream file type!');
}
// For supporting legacy API we put the FD here.
stream.fd = fd;
stream._isStdio = true;
return stream;
}

View File

@@ -0,0 +1,29 @@
'use strict';
const hexTable = new Array(256);
for (var i = 0; i < 256; ++i)
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
const isHexTable = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ...
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // ... 256
];
module.exports = {
hexTable,
isHexTable
};

View File

@@ -0,0 +1,72 @@
'use strict';
const Buffer = require('buffer').Buffer;
module.exports = BufferList;
function BufferList() {
this.head = null;
this.tail = null;
this.length = 0;
}
BufferList.prototype.push = function(v) {
const entry = { data: v, next: null };
if (this.length > 0)
this.tail.next = entry;
else
this.head = entry;
this.tail = entry;
++this.length;
};
BufferList.prototype.unshift = function(v) {
const entry = { data: v, next: this.head };
if (this.length === 0)
this.tail = entry;
this.head = entry;
++this.length;
};
BufferList.prototype.shift = function() {
if (this.length === 0)
return;
const ret = this.head.data;
if (this.length === 1)
this.head = this.tail = null;
else
this.head = this.head.next;
--this.length;
return ret;
};
BufferList.prototype.clear = function() {
this.head = this.tail = null;
this.length = 0;
};
BufferList.prototype.join = function(s) {
if (this.length === 0)
return '';
var p = this.head;
var ret = '' + p.data;
while (p = p.next)
ret += s + p.data;
return ret;
};
BufferList.prototype.concat = function(n) {
if (this.length === 0)
return Buffer.alloc(0);
if (this.length === 1)
return this.head.data;
const ret = Buffer.allocUnsafe(n >>> 0);
var p = this.head;
var i = 0;
while (p) {
p.data.copy(ret, i);
i += p.data.length;
p = p.next;
}
return ret;
};

View File

@@ -0,0 +1,39 @@
// LazyTransform is a special type of Transform stream that is lazily loaded.
// This is used for performance with bi-API-ship: when two APIs are available
// for the stream, one conventional and one non-conventional.
'use strict';
const stream = require('stream');
const util = require('util');
module.exports = LazyTransform;
function LazyTransform(options) {
this._options = options;
}
util.inherits(LazyTransform, stream.Transform);
[
'_readableState',
'_writableState',
'_transformState'
].forEach(function(prop, i, props) {
Object.defineProperty(LazyTransform.prototype, prop, {
get: function() {
stream.Transform.call(this, this._options);
this._writableState.decodeStrings = false;
this._writableState.defaultEncoding = 'latin1';
return this[prop];
},
set: function(val) {
Object.defineProperty(this, prop, {
value: val,
enumerable: true,
configurable: true,
writable: true
});
},
configurable: true,
enumerable: true
});
});

View File

@@ -0,0 +1,168 @@
'use strict';
debugger;
const binding = process.binding('util');
/* Orel
const prefix = `(${process.release.name}:${process.pid}) `;
const kArrowMessagePrivateSymbolIndex = binding['arrow_message_private_symbol'];
const kDecoratedPrivateSymbolIndex = binding['decorated_private_symbol'];
exports.getHiddenValue = binding.getHiddenValue;
exports.setHiddenValue = binding.setHiddenValue;
*/
// The `buffer` module uses this. Defining it here instead of in the public
// `util` module makes it accessible without having to `require('util')` there.
exports.customInspectSymbol = Symbol('util.inspect.custom');
// All the internal deprecations have to use this function only, as this will
// prepend the prefix to the actual message.
exports.deprecate = function(fn, msg) {
return exports._deprecate(fn, msg);
};
/* Orel
exports.error = function(msg) {
const fmt = `${prefix}${msg}`;
if (arguments.length > 1) {
const args = new Array(arguments.length);
args[0] = fmt;
for (let i = 1; i < arguments.length; ++i)
args[i] = arguments[i];
console.error.apply(console, args);
} else {
console.error(fmt);
}
};
exports.trace = function(msg) {
console.trace(`${prefix}${msg}`);
};*/
// Mark that a method should not be used.
// Returns a modified function which warns once by default.
// If --no-deprecation is set, then it is a no-op.
exports._deprecate = function(fn, msg) {
// Allow for deprecating things in the process of starting up.
if (global.process === undefined) {
return function() {
return exports._deprecate(fn, msg).apply(this, arguments);
};
}
if (process.noDeprecation === true) {
return fn;
}
var warned = false;
function deprecated() {
if (!warned) {
warned = true;
process.emitWarning(msg, 'DeprecationWarning', deprecated);
}
if (new.target) {
return Reflect.construct(fn, arguments, new.target);
}
return fn.apply(this, arguments);
}
// The wrapper will keep the same prototype as fn to maintain prototype chain
Object.setPrototypeOf(deprecated, fn);
if (fn.prototype) {
// Setting this (rather than using Object.setPrototype, as above) ensures
// that calling the unwrapped constructor gives an instanceof the wrapped
// constructor.
deprecated.prototype = fn.prototype;
}
return deprecated;
};
/* Orel
exports.decorateErrorStack = function decorateErrorStack(err) {
if (!(exports.isError(err) && err.stack) ||
exports.getHiddenValue(err, kDecoratedPrivateSymbolIndex) === true)
return;
const arrow = exports.getHiddenValue(err, kArrowMessagePrivateSymbolIndex);
if (arrow) {
err.stack = arrow + err.stack;
exports.setHiddenValue(err, kDecoratedPrivateSymbolIndex, true);
}
};*/
exports.isError = function isError(e) {
return exports.objectToString(e) === '[object Error]' || e instanceof Error;
};
exports.objectToString = function objectToString(o) {
return Object.prototype.toString.call(o);
};
/* Orel
const noCrypto = !process.versions.openssl;
*/
const noCrypto = false;
exports.assertCrypto = function(exports) {
if (noCrypto)
throw new Error('Node.js is not compiled with openssl crypto support');
};
exports.kIsEncodingSymbol = Symbol('node.isEncoding');
exports.normalizeEncoding = function normalizeEncoding(enc) {
if (!enc) return 'utf8';
var low;
for (;;) {
switch (enc) {
case 'utf8':
case 'utf-8':
return 'utf8';
case 'ucs2':
case 'utf16le':
case 'ucs-2':
case 'utf-16le':
return 'utf16le';
case 'binary':
return 'latin1';
case 'base64':
case 'ascii':
case 'latin1':
case 'hex':
return enc;
default:
if (low) return; // undefined
enc = ('' + enc).toLowerCase();
low = true;
}
}
};
// Filters duplicate strings. Used to support functions in crypto and tls
// modules. Implemented specifically to maintain existing behaviors in each.
exports.filterDuplicateStrings = function filterDuplicateStrings(items, low) {
if (!Array.isArray(items))
return [];
const len = items.length;
if (len <= 1)
return items;
const map = new Map();
for (var i = 0; i < len; i++) {
const item = items[i];
const key = item.toLowerCase();
if (low) {
map.set(key, key);
} else {
if (!map.has(key) || map.get(key) <= item)
map.set(key, item);
}
}
return Array.from(map.values()).sort();
};
exports.cachedResult = function cachedResult(fn) {
var result;
return () => {
if (result === undefined)
result = fn();
return result;
};
};

View File

@@ -0,0 +1,751 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const NativeModule = require('native_module');
const util = require('util');
const internalModule = require('internal/module');
// SyNode const { getURLFromFilePath } = require('internal/url');
const vm = require('vm');
const assert = require('assert').ok;
const fs = require('fs');
const internalFS = require('internal/fs');
const path = require('path');
const {
internalModuleReadFile,
internalModuleStat
} = process.binding('fs');
/* SyNode
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
const experimentalModules = !!process.binding('config').experimentalModules;
const errors = require('internal/errors');
const Loader = require('internal/loader/Loader');
const ModuleJob = require('internal/loader/ModuleJob');
const { createDynamicModule } = require('internal/loader/ModuleWrap');
*/
const preserveSymlinks = false;
const experimentalModules = false;
const errors = require('internal/errors');
//end SyNode
let ESMLoader;
function stat(filename) {
filename = path._makeLong(filename);
const cache = stat.cache;
if (cache !== null) {
const result = cache.get(filename);
if (result !== undefined) return result;
}
const result = internalModuleStat(filename);
if (cache !== null) cache.set(filename, result);
return result;
}
stat.cache = null;
function updateChildren(parent, child, scan) {
var children = parent && parent.children;
if (children && !(scan && children.includes(child)))
children.push(child);
}
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
updateChildren(parent, this, false);
this.filename = null;
this.loaded = false;
this.children = [];
}
module.exports = Module;
Module._cache = Object.create(null);
Module._pathCache = Object.create(null);
Module._extensions = Object.create(null);
var modulePaths = [];
Module.globalPaths = [];
Module.wrapper = NativeModule.wrapper;
Module.wrap = NativeModule.wrap;
Module._debug = util.debuglog('module');
// We use this alias for the preprocessor that filters it out
const debug = Module._debug;
// given a module name, and a list of paths to test, returns the first
// matching file in the following precedence.
//
// require("a.<ext>")
// -> a.<ext>
//
// require("a")
// -> a
// -> a.<ext>
// -> a/index.<ext>
// check if the directory is a package.json dir
const packageMainCache = Object.create(null);
function readPackage(requestPath) {
const entry = packageMainCache[requestPath];
if (entry)
return entry;
const jsonPath = path.resolve(requestPath, 'package.json');
const json = internalModuleReadFile(path._makeLong(jsonPath));
if (json === undefined) {
return false;
}
try {
var pkg = packageMainCache[requestPath] = JSON.parse(json).main;
} catch (e) {
e.path = jsonPath;
e.message = 'Error parsing ' + jsonPath + ': ' + e.message;
throw e;
}
return pkg;
}
function tryPackage(requestPath, exts, isMain) {
var pkg = readPackage(requestPath);
if (!pkg) return false;
var filename = path.resolve(requestPath, pkg);
return tryFile(filename, isMain) ||
tryExtensions(filename, exts, isMain) ||
tryExtensions(path.resolve(filename, 'index'), exts, isMain);
}
// In order to minimize unnecessary lstat() calls,
// this cache is a list of known-real paths.
// Set to an empty Map to reset.
const realpathCache = new Map();
// check if the file exists and is not a directory
// if using --preserve-symlinks and isMain is false,
// keep symlinks intact, otherwise resolve to the
// absolute realpath.
function tryFile(requestPath, isMain) {
const rc = stat(requestPath);
if (preserveSymlinks && !isMain) {
return rc === 0 && path.resolve(requestPath);
}
return rc === 0 && toRealPath(requestPath);
}
function toRealPath(requestPath) {
return fs.realpathSync(requestPath, {
[internalFS.realpathCacheKey]: realpathCache
});
}
// given a path, check if the file exists with any of the set extensions
function tryExtensions(p, exts, isMain) {
for (var i = 0; i < exts.length; i++) {
const filename = tryFile(p + exts[i], isMain);
if (filename) {
return filename;
}
}
return false;
}
var warned = false;
Module._findPath = function(request, paths, isMain) {
if (path.isAbsolute(request)) {
paths = [''];
} else if (!paths || paths.length === 0) {
return false;
}
var cacheKey = request + '\x00' +
(paths.length === 1 ? paths[0] : paths.join('\x00'));
var entry = Module._pathCache[cacheKey];
if (entry)
return entry;
var exts;
var trailingSlash = request.length > 0 &&
request.charCodeAt(request.length - 1) === 47/*/*/;
// For each path
for (var i = 0; i < paths.length; i++) {
// Don't search further if path doesn't exist
const curPath = paths[i];
if (curPath && stat(curPath) < 1) continue;
var basePath = path.resolve(curPath, request);
var filename;
var rc = stat(basePath);
if (!trailingSlash) {
if (rc === 0) { // File.
if (preserveSymlinks && !isMain) {
filename = path.resolve(basePath);
} else {
filename = toRealPath(basePath);
}
} else if (rc === 1) { // Directory.
if (exts === undefined)
exts = Object.keys(Module._extensions);
filename = tryPackage(basePath, exts, isMain);
}
if (!filename) {
// try it with each of the extensions
if (exts === undefined)
exts = Object.keys(Module._extensions);
filename = tryExtensions(basePath, exts, isMain);
}
}
if (!filename && rc === 1) { // Directory.
if (exts === undefined)
exts = Object.keys(Module._extensions);
filename = tryPackage(basePath, exts, isMain) ||
// try it with each of the extensions at "index"
tryExtensions(path.resolve(basePath, 'index'), exts, isMain);
}
if (filename) {
// Warn once if '.' resolved outside the module dir
if (request === '.' && i > 0) {
if (!warned) {
warned = true;
process.emitWarning(
'warning: require(\'.\') resolved outside the package ' +
'directory. This functionality is deprecated and will be removed ' +
'soon.',
'DeprecationWarning', 'DEP0019');
}
}
Module._pathCache[cacheKey] = filename;
return filename;
}
}
return false;
};
// 'node_modules' character codes reversed
var nmChars = [ 115, 101, 108, 117, 100, 111, 109, 95, 101, 100, 111, 110 ];
var nmLen = nmChars.length;
if (process.platform === 'win32') {
// 'from' is the __dirname of the module.
Module._nodeModulePaths = function(from) {
// guarantee that 'from' is absolute.
from = path.resolve(from);
// note: this approach *only* works when the path is guaranteed
// to be absolute. Doing a fully-edge-case-correct path.split
// that works on both Windows and Posix is non-trivial.
// return root node_modules when path is 'D:\\'.
// path.resolve will make sure from.length >=3 in Windows.
if (from.charCodeAt(from.length - 1) === 92/*\*/ &&
from.charCodeAt(from.length - 2) === 58/*:*/)
return [from + 'node_modules'];
const paths = [];
var p = 0;
var last = from.length;
for (var i = from.length - 1; i >= 0; --i) {
const code = from.charCodeAt(i);
// The path segment separator check ('\' and '/') was used to get
// node_modules path for every path segment.
// Use colon as an extra condition since we can get node_modules
// path for drive root like 'C:\node_modules' and don't need to
// parse drive name.
if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/) {
if (p !== nmLen)
paths.push(from.slice(0, last) + '\\node_modules');
last = i;
p = 0;
} else if (p !== -1) {
if (nmChars[p] === code) {
++p;
} else {
p = -1;
}
}
}
return paths;
};
} else { // posix
// 'from' is the __dirname of the module.
Module._nodeModulePaths = function(from) {
// guarantee that 'from' is absolute.
from = path.resolve(from);
// Return early not only to avoid unnecessary work, but to *avoid* returning
// an array of two items for a root: [ '//node_modules', '/node_modules' ]
if (from === '/')
return ['/node_modules'];
// note: this approach *only* works when the path is guaranteed
// to be absolute. Doing a fully-edge-case-correct path.split
// that works on both Windows and Posix is non-trivial.
const paths = [];
var p = 0;
var last = from.length;
for (var i = from.length - 1; i >= 0; --i) {
const code = from.charCodeAt(i);
if (code === 47/*/*/) {
if (p !== nmLen)
paths.push(from.slice(0, last) + '/node_modules');
last = i;
p = 0;
} else if (p !== -1) {
if (nmChars[p] === code) {
++p;
} else {
p = -1;
}
}
}
// Append /node_modules to handle root paths.
paths.push('/node_modules');
return paths;
};
}
// 'index.' character codes
var indexChars = [ 105, 110, 100, 101, 120, 46 ];
var indexLen = indexChars.length;
Module._resolveLookupPaths = function(request, parent, newReturn) {
if (NativeModule.nonInternalExists(request)) {
debug('looking for %j in []', request);
return (newReturn ? null : [request, []]);
}
// Check for relative path
if (request.length < 2 ||
request.charCodeAt(0) !== 46/*.*/ ||
(request.charCodeAt(1) !== 46/*.*/ &&
request.charCodeAt(1) !== 47/*/*/)) {
var paths = modulePaths;
if (parent) {
if (!parent.paths)
paths = parent.paths = [];
else
paths = parent.paths.concat(paths);
}
// Maintain backwards compat with certain broken uses of require('.')
// by putting the module's directory in front of the lookup paths.
if (request === '.') {
if (parent && parent.filename) {
paths.unshift(path.dirname(parent.filename));
} else {
paths.unshift(path.resolve(request));
}
}
debug('looking for %j in %j', request, paths);
return (newReturn ? (paths.length > 0 ? paths : null) : [request, paths]);
}
// with --eval, parent.id is not set and parent.filename is null
if (!parent || !parent.id || !parent.filename) {
// make require('./path/to/foo') work - normally the path is taken
// from realpath(__filename) but with eval there is no filename
var mainPaths = ['.'].concat(Module._nodeModulePaths('.'), modulePaths);
debug('looking for %j in %j', request, mainPaths);
return (newReturn ? mainPaths : [request, mainPaths]);
}
// Is the parent an index module?
// We can assume the parent has a valid extension,
// as it already has been accepted as a module.
const base = path.basename(parent.filename);
var parentIdPath;
if (base.length > indexLen) {
var i = 0;
for (; i < indexLen; ++i) {
if (indexChars[i] !== base.charCodeAt(i))
break;
}
if (i === indexLen) {
// We matched 'index.', let's validate the rest
for (; i < base.length; ++i) {
const code = base.charCodeAt(i);
if (code !== 95/*_*/ &&
(code < 48/*0*/ || code > 57/*9*/) &&
(code < 65/*A*/ || code > 90/*Z*/) &&
(code < 97/*a*/ || code > 122/*z*/))
break;
}
if (i === base.length) {
// Is an index module
parentIdPath = parent.id;
} else {
// Not an index module
parentIdPath = path.dirname(parent.id);
}
} else {
// Not an index module
parentIdPath = path.dirname(parent.id);
}
} else {
// Not an index module
parentIdPath = path.dirname(parent.id);
}
var id = path.resolve(parentIdPath, request);
// make sure require('./path') and require('path') get distinct ids, even
// when called from the toplevel js file
if (parentIdPath === '.' && id.indexOf('/') === -1) {
id = './' + id;
}
debug('RELATIVE: requested: %s set ID to: %s from %s', request, id,
parent.id);
var parentDir = [path.dirname(parent.filename)];
debug('looking for %j in %j', id, parentDir);
return (newReturn ? parentDir : [id, parentDir]);
};
// Check the cache for the requested file.
// 1. If a module already exists in the cache: return its exports object.
// 2. If the module is native: call `NativeModule.require()` with the
// filename and return the result.
// 3. Otherwise, create a new module for the file and save it to the cache.
// Then have it load the file contents before returning its exports
// object.
Module._load = function(request, parent, isMain) {
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
}
if (isMain && experimentalModules) {
(async () => {
// loader setup
if (!ESMLoader) {
ESMLoader = new Loader();
const userLoader = false // SyNode process.binding('config').userLoader;
if (userLoader) {
const hooks = await ESMLoader.import(userLoader);
ESMLoader = new Loader();
ESMLoader.hook(hooks);
}
}
await ESMLoader.import(getURLFromFilePath(request).pathname);
})()
.catch((e) => {
console.error(e);
process.exit(1);
});
return;
}
var filename = Module._resolveFilename(request, parent, isMain);
var cachedModule = Module._cache[filename];
if (cachedModule) {
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
return NativeModule.require(filename);
}
// Don't call updateChildren(), Module constructor already does.
var module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
tryModuleLoad(module, filename);
return module.exports;
};
function tryModuleLoad(module, filename) {
var threw = true;
try {
module.load(filename);
threw = false;
} finally {
if (threw) {
delete Module._cache[filename];
}
}
}
Module._resolveFilename = function(request, parent, isMain, options) {
if (NativeModule.nonInternalExists(request)) {
return request;
}
var paths;
if (typeof options === 'object' && options !== null &&
Array.isArray(options.paths)) {
paths = [];
for (var i = 0; i < options.paths.length; i++) {
const path = options.paths[i];
const lookupPaths = Module._resolveLookupPaths(path, parent, true);
if (!paths.includes(path))
paths.push(path);
for (var j = 0; j < lookupPaths.length; j++) {
if (!paths.includes(lookupPaths[j]))
paths.push(lookupPaths[j]);
}
}
} else {
paths = Module._resolveLookupPaths(request, parent, true);
}
// look up the filename first, since that's the cache key.
var filename = Module._findPath(request, paths, isMain);
if (!filename) {
var err = new Error(`Cannot find module '${request}'`);
err.code = 'MODULE_NOT_FOUND';
throw err;
}
return filename;
};
// Given a file name, pass it to the proper extension handler.
Module.prototype.load = function(filename) {
debug('load %j for module %j', filename, this.id);
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
if (ESMLoader) {
const url = getURLFromFilePath(filename);
const urlString = `${url}`;
if (ESMLoader.moduleMap.has(urlString) !== true) {
const ctx = createDynamicModule(['default'], url);
ctx.reflect.exports.default.set(this.exports);
ESMLoader.moduleMap.set(urlString,
new ModuleJob(ESMLoader, url, async () => ctx));
} else {
const job = ESMLoader.moduleMap.get(urlString);
if (job.reflect)
job.reflect.exports.default.set(this.exports);
}
}
};
// Loads a module at the given file path. Returns that module's
// `exports` property.
Module.prototype.require = function(path) {
assert(path, 'missing path');
assert(typeof path === 'string', 'path must be a string');
return Module._load(path, this, /* isMain */ false);
};
// Resolved path to process.argv[1] will be lazily placed here
// (needed for setting breakpoint when called with --inspect-brk)
var resolvedArgv;
// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {
content = internalModule.stripShebang(content);
// create wrapper function
var wrapper = Module.wrap(content);
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
var inspectorWrapper = null;
if (process._breakFirstLine && process._eval == null) {
if (!resolvedArgv) {
// we enter the repl if we're not given a filename argument.
if (process.argv[1]) {
resolvedArgv = Module._resolveFilename(process.argv[1], null, false);
} else {
resolvedArgv = 'repl';
}
}
// Set breakpoint on module start
if (filename === resolvedArgv) {
delete process._breakFirstLine;
inspectorWrapper = process.binding('inspector').callAndPauseOnStart;
if (!inspectorWrapper) {
const Debug = vm.runInDebugContext('Debug');
Debug.setBreakPoint(compiledWrapper, 0, 0);
}
}
}
var dirname = path.dirname(filename);
var require = internalModule.makeRequireFunction(this);
var depth = internalModule.requireDepth;
if (depth === 0) stat.cache = new Map();
var result;
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, this.exports, this.exports,
require, this, filename, dirname);
} else {
result = compiledWrapper.call(this.exports, this.exports, require, this,
filename, dirname);
}
if (depth === 0) stat.cache = null;
return result;
};
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(internalModule.stripBOM(content), filename);
};
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
try {
module.exports = JSON.parse(internalModule.stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
//Native extension for .node
Module._extensions['.node'] = function(module, filename) {
return process.dlopen(module, path._makeLong(filename));
};
//SyNode
Module._extensions[process.platform === 'win32' ? '.dll' : '.so'] = process.binding('modules').loadDll;
if (experimentalModules) {
Module._extensions['.mjs'] = function(module, filename) {
throw new errors.Error('ERR_REQUIRE_ESM', filename);
};
}
// bootstrap main module.
Module.runMain = function() {
// Load the main module--the command line argument.
Module._load(process.argv[1], null, true);
// Handle any nextTicks added in the first tick of the program
process._tickCallback();
};
Module._initPaths = function() {
const isWindows = process.platform === 'win32';
var homeDir;
if (isWindows) {
homeDir = process.env.USERPROFILE;
} else {
homeDir = process.env.HOME;
}
// $PREFIX/lib/node, where $PREFIX is the root of the Node.js installation.
var prefixDir;
// process.execPath is $PREFIX/bin/node except on Windows where it is
// $PREFIX\node.exe.
if (isWindows) {
prefixDir = path.resolve(process.execPath, '..');
} else {
prefixDir = path.resolve(process.execPath, '..', '..');
}
var paths = [path.resolve(prefixDir, 'lib', 'node')];
if (homeDir) {
paths.unshift(path.resolve(homeDir, '.node_libraries'));
paths.unshift(path.resolve(homeDir, '.node_modules'));
}
var nodePath = process.env['NODE_PATH'];
if (nodePath) {
paths = nodePath.split(path.delimiter).filter(function(path) {
return !!path;
}).concat(paths);
}
modulePaths = paths;
// clone as a shallow copy, for introspection.
Module.globalPaths = modulePaths.slice(0);
};
Module._preloadModules = function(requests) {
if (!Array.isArray(requests))
return;
// Preloaded modules have a dummy parent module which is deemed to exist
// in the current working directory. This seeds the search path for
// preloaded modules.
var parent = new Module('internal/preload', null);
try {
parent.paths = Module._nodeModulePaths(process.cwd());
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
for (var n = 0; n < requests.length; n++)
parent.require(requests[n]);
};
Module._initPaths();
// backwards compatibility
Module.Module = Module;

View File

@@ -0,0 +1,5 @@
/**
* MPV - pure fake!!!!!!
*/
module.exports = {}

53
contrib/mORMot/SyNode/core_modules/node_modules/os.js generated vendored Normal file
View File

@@ -0,0 +1,53 @@
// Copyright Joyent, Inc. and other Node contributors.
// Modified by UnityBase core team to be compatible with SyNode
/**
* @module os
* @memberOf module:buildin
*/
var util = require('util');
const {getHostname} = process.binding('os')
//MPV TODO implement
//var binding = process.binding('os');
//exports.endianness = binding.getEndianness;
//exports.loadavg = binding.getLoadAvg;
//exports.uptime = binding.getUptime;
//exports.freemem = binding.getFreeMem;
//exports.totalmem = binding.getTotalMem;
//exports.cpus = binding.getCPUs;
//exports.type = binding.getOSType;
//exports.release = binding.getOSRelease;
//exports.networkInterfaces = binding.getInterfaceAddresses;
exports.endianness = function() { return 'LE'; };
exports.arch = function() {
return process.arch;
};
exports.platform = function() {
return process.platform;
};
exports.tmpdir = function() {
return process.env.TMPDIR ||
process.env.TMP ||
process.env.TEMP ||
(process.platform === 'win32' ? 'c:\\windows\\temp' : '/tmp');
};
exports.tmpDir = exports.tmpdir;
exports.getNetworkInterfaces = util.deprecate(function() {
return exports.networkInterfaces();
}, 'getNetworkInterfaces is now called `os.networkInterfaces`.');
exports.EOL = process.platform === 'win32' ? '\r\n' : '\n';
exports.hostname = function() {
let hn = getHostname();
return hn.toLowerCase()
}
exports.hostname[Symbol.toPrimitive] = () => exports.hostname();

1603
contrib/mORMot/SyNode/core_modules/node_modules/path.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,256 @@
/* Implementation of HTML Timers (setInterval/setTimeout) based on sleep.
* @license MIT
*
* Copyright 2012 Kevin Locke <kevin@kevinlocke.name>
* Modified by UnityBase team - added priority to realise the setImmediate
*/
/*jslint bitwise: true, evil: true */
/**
* Adds methods to implement the HTML5 WindowTimers interface on a given
* object.
*
* Adds the following methods:
*
* - clearInterval
* - clearTimeout
* - setInterval
* - setTimeout<
*
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html
* for the complete specification of these methods.
*
* @module WindowTimer
*/
var WindowTimer = {};
/**
* @method makeWindowTimer
*
* @param {Object} target Object to which the methods should be added.
* @param {Function} sleep A function which sleeps for a specified number of
* milliseconds.
* @return {Function} The function which runs the scheduled timers.
*/
function makeWindowTimer(target, sleep) {
"use strict";
var counter = 1,
inCallback = false,
// Map handle -> timer
timersByHandle = {},
// Min-heap of timers by time then handle, index 0 unused
timersByTime = [ null ];
/** Compares timers based on scheduled time and handle. */
function timerCompare(t1, t2) {
// Note: Only need less-than for our uses
return t1.priority < t2.priority ? -1 : (t1.priority === t2.priority ?
(t1.time < t2.time ? -1 :
(t1.time === t2.time && t1.handle < t2.handle ? -1 : 0)) : 0);
}
/** Fix the heap invariant which may be violated at a given index */
function heapFixDown(heap, i, lesscmp) {
var j, tmp;
j = i * 2;
while (j < heap.length) {
if (j + 1 < heap.length &&
lesscmp(heap[j + 1], heap[j]) < 0) {
j = j + 1;
}
if (lesscmp(heap[i], heap[j]) < 0) {
break;
}
tmp = heap[j];
heap[j] = heap[i];
heap[i] = tmp;
i = j;
j = i * 2;
}
}
/** Fix the heap invariant which may be violated at a given index */
function heapFixUp(heap, i, lesscmp) {
var j, tmp;
while (i > 1) {
j = i >> 1; // Integer div by 2
if (lesscmp(heap[j], heap[i]) < 0) {
break;
}
tmp = heap[j];
heap[j] = heap[i];
heap[i] = tmp;
i = j;
}
}
/** Remove the timer element from the heap */
function heapPop(heap, lesscmp, timer) {
for (let index = 1; index < heap.length - 1; index++) {
if (heap[index] && heap[index].handle === timer.handle) {
heap[index] = heap[heap.length - 1];
}
}
//heap[1] = heap[heap.length - 1];
heap.pop();
heapFixDown(heap, 1, lesscmp);
}
/** Create a timer and schedule code to run at a given time */
function addTimer(code, delay, repeat, argsIfFn, priority) {
var handle, timer;
if (typeof code !== "function") {
code = String(code);
argsIfFn = null;
}
delay = Number(delay) || 0;
if (inCallback) {
delay = Math.max(delay, 4);
}
// Note: Must set handle after argument conversion to properly
// handle conformance test in HTML5 spec.
handle = counter;
counter += 1;
timer = {
args: argsIfFn,
cancel: false,
code: code,
handle: handle,
repeat: repeat ? Math.max(delay, 4) : 0,
time: new Date().getTime() + delay,
priority: priority || 0
};
timersByHandle[handle] = timer;
timersByTime.push(timer);
heapFixUp(timersByTime, timersByTime.length - 1, timerCompare);
return handle;
}
/** Cancel an existing timer */
function cancelTimer(handle, repeat) {
var timer;
if (timersByHandle.hasOwnProperty(handle)) {
timer = timersByHandle[handle];
if (repeat === (timer.repeat > 0)) {
timer.cancel = true;
}
}
}
function clearInterval(handle) {
cancelTimer(handle, true);
}
target.clearInterval = clearInterval;
function clearTimeout(handle) {
cancelTimer(handle, false);
}
target.clearTimeout = clearTimeout;
function setInterval(code, delay) {
return addTimer(
code,
delay,
true,
Array.prototype.slice.call(arguments, 2)
);
}
target.setInterval = setInterval;
function setTimeout(code, delay) {
return addTimer(
code,
delay,
false,
Array.prototype.slice.call(arguments, 2),
0
);
}
target.setTimeout = setTimeout;
function setTimeout(code, delay) {
return addTimer(
code,
delay,
false,
Array.prototype.slice.call(arguments, 2),
0
);
}
target.setTimeout = setTimeout;
function setTimeoutWithPriority(code, delay, priority) {
return addTimer(
code,
delay,
false,
Array.prototype.slice.call(arguments, 3),
priority
);
}
timerLoop.setTimeoutWithPriority = setTimeoutWithPriority;
function timerLoop(nonblocking) {
// on the way out, don't bother. it won't get fired anyway.
if (process._exiting)
return;
var now, timer;
// Note: index 0 unused in timersByTime
while (timersByTime.length > 1) {
timer = timersByTime[1];
if (timer.cancel) {
delete timersByHandle[timer.handle];
heapPop(timersByTime, timerCompare, timer);
} else {
now = new Date().getTime();
if (timer.time <= now) {
inCallback = true;
try {
if (typeof timer.code === "function") {
timer.code.apply(undefined, timer.args);
} else {
eval(timer.code);
}
} finally {
inCallback = false;
}
if (timer.repeat > 0 && !timer.cancel) {
timer.time += timer.repeat;
heapFixDown(timersByTime, 1, timerCompare);
} else {
delete timersByHandle[timer.handle];
heapPop(timersByTime, timerCompare, timer);
}
} else if (!nonblocking) {
sleep(timer.time - now);
} else {
return true;
}
}
}
return false;
};
return timerLoop;
}
if (typeof exports === "object") {
exports.makeWindowTimer = makeWindowTimer;
}

View File

@@ -0,0 +1,510 @@
/*! http://mths.be/punycode v1.2.0 by @mathias */
;(function(root) {
/**
* The `punycode` object.
* @name punycode
* @type Object
*/
var punycode,
/** Detect free variables `define`, `exports`, `module` and `require` */
freeDefine = typeof define == 'function' && typeof define.amd == 'object' &&
define.amd && define,
freeExports = typeof exports == 'object' && exports,
freeModule = typeof module == 'object' && module,
freeRequire = typeof require == 'function' && require,
/** Highest positive signed 32-bit float value */
maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
/** Bootstring parameters */
base = 36,
tMin = 1,
tMax = 26,
skew = 38,
damp = 700,
initialBias = 72,
initialN = 128, // 0x80
delimiter = '-', // '\x2D'
/** Regular expressions */
regexPunycode = /^xn--/,
regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators
/** Error messages */
errors = {
'overflow': 'Overflow: input needs wider integers to process',
'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
'invalid-input': 'Invalid input'
},
/** Convenience shortcuts */
baseMinusTMin = base - tMin,
floor = Math.floor,
stringFromCharCode = String.fromCharCode,
/** Temporary variable */
key;
/*--------------------------------------------------------------------------*/
/**
* A generic error utility function.
* @private
* @param {String} type The error type.
* @returns {Error} Throws a `RangeError` with the applicable error message.
*/
function error(type) {
throw RangeError(errors[type]);
}
/**
* A generic `Array#map` utility function.
* @private
* @param {Array} array The array to iterate over.
* @param {Function} callback The function that gets called for every array
* item.
* @returns {Array} A new array of values returned by the callback function.
*/
function map(array, fn) {
var length = array.length;
while (length--) {
array[length] = fn(array[length]);
}
return array;
}
/**
* A simple `Array#map`-like wrapper to work with domain name strings.
* @private
* @param {String} domain The domain name.
* @param {Function} callback The function that gets called for every
* character.
* @returns {Array} A new string of characters returned by the callback
* function.
*/
function mapDomain(string, fn) {
return map(string.split(regexSeparators), fn).join('.');
}
/**
* Creates an array containing the decimal code points of each Unicode
* character in the string. While JavaScript uses UCS-2 internally,
* this function will convert a pair of surrogate halves (each of which
* UCS-2 exposes as separate characters) into a single code point,
* matching UTF-16.
* @see `punycode.ucs2.encode`
* @see <http://mathiasbynens.be/notes/javascript-encoding>
* @memberOf punycode.ucs2
* @name decode
* @param {String} string The Unicode input string (UCS-2).
* @returns {Array} The new array of code points.
*/
function ucs2decode(string) {
var output = [],
counter = 0,
length = string.length,
value,
extra;
while (counter < length) {
value = string.charCodeAt(counter++);
if ((value & 0xF800) == 0xD800 && counter < length) {
// high surrogate, and there is a next character
extra = string.charCodeAt(counter++);
if ((extra & 0xFC00) == 0xDC00) { // low surrogate
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
} else {
output.push(value, extra);
}
} else {
output.push(value);
}
}
return output;
}
/**
* Creates a string based on an array of decimal code points.
* @see `punycode.ucs2.decode`
* @memberOf punycode.ucs2
* @name encode
* @param {Array} codePoints The array of decimal code points.
* @returns {String} The new Unicode string (UCS-2).
*/
function ucs2encode(array) {
return map(array, function(value) {
var output = '';
if (value > 0xFFFF) {
value -= 0x10000;
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
value = 0xDC00 | value & 0x3FF;
}
output += stringFromCharCode(value);
return output;
}).join('');
}
/**
* Converts a basic code point into a digit/integer.
* @see `digitToBasic()`
* @private
* @param {Number} codePoint The basic (decimal) code point.
* @returns {Number} The numeric value of a basic code point (for use in
* representing integers) in the range `0` to `base - 1`, or `base` if
* the code point does not represent a value.
*/
function basicToDigit(codePoint) {
return codePoint - 48 < 10
? codePoint - 22
: codePoint - 65 < 26
? codePoint - 65
: codePoint - 97 < 26
? codePoint - 97
: base;
}
/**
* Converts a digit/integer into a basic code point.
* @see `basicToDigit()`
* @private
* @param {Number} digit The numeric value of a basic code point.
* @returns {Number} The basic code point whose value (when used for
* representing integers) is `digit`, which needs to be in the range
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
* used; else, the lowercase form is used. The behavior is undefined
* if flag is non-zero and `digit` has no uppercase form.
*/
function digitToBasic(digit, flag) {
// 0..25 map to ASCII a..z or A..Z
// 26..35 map to ASCII 0..9
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
}
/**
* Bias adaptation function as per section 3.4 of RFC 3492.
* http://tools.ietf.org/html/rfc3492#section-3.4
* @private
*/
function adapt(delta, numPoints, firstTime) {
var k = 0;
delta = firstTime ? floor(delta / damp) : delta >> 1;
delta += floor(delta / numPoints);
for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
delta = floor(delta / baseMinusTMin);
}
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
}
/**
* Converts a basic code point to lowercase if `flag` is falsy, or to
* uppercase if `flag` is truthy. The code point is unchanged if it's
* caseless. The behavior is undefined if `codePoint` is not a basic code
* point.
* @private
* @param {Number} codePoint The numeric value of a basic code point.
* @returns {Number} The resulting basic code point.
*/
function encodeBasic(codePoint, flag) {
codePoint -= (codePoint - 97 < 26) << 5;
return codePoint + (!flag && codePoint - 65 < 26) << 5;
}
/**
* Converts a Punycode string of ASCII code points to a string of Unicode
* code points.
* @memberOf punycode
* @param {String} input The Punycode string of ASCII code points.
* @returns {String} The resulting string of Unicode code points.
*/
function decode(input) {
// Don't use UCS-2
var output = [],
inputLength = input.length,
out,
i = 0,
n = initialN,
bias = initialBias,
basic,
j,
index,
oldi,
w,
k,
digit,
t,
length,
/** Cached calculation results */
baseMinusT;
// Handle the basic code points: let `basic` be the number of input code
// points before the last delimiter, or `0` if there is none, then copy
// the first basic code points to the output.
basic = input.lastIndexOf(delimiter);
if (basic < 0) {
basic = 0;
}
for (j = 0; j < basic; ++j) {
// if it's not a basic code point
if (input.charCodeAt(j) >= 0x80) {
error('not-basic');
}
output.push(input.charCodeAt(j));
}
// Main decoding loop: start just after the last delimiter if any basic code
// points were copied; start at the beginning otherwise.
for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
// `index` is the index of the next character to be consumed.
// Decode a generalized variable-length integer into `delta`,
// which gets added to `i`. The overflow checking is easier
// if we increase `i` as we go, then subtract off its starting
// value at the end to obtain `delta`.
for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
if (index >= inputLength) {
error('invalid-input');
}
digit = basicToDigit(input.charCodeAt(index++));
if (digit >= base || digit > floor((maxInt - i) / w)) {
error('overflow');
}
i += digit * w;
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
if (digit < t) {
break;
}
baseMinusT = base - t;
if (w > floor(maxInt / baseMinusT)) {
error('overflow');
}
w *= baseMinusT;
}
out = output.length + 1;
bias = adapt(i - oldi, out, oldi == 0);
// `i` was supposed to wrap around from `out` to `0`,
// incrementing `n` each time, so we'll fix that now:
if (floor(i / out) > maxInt - n) {
error('overflow');
}
n += floor(i / out);
i %= out;
// Insert `n` at position `i` of the output
output.splice(i++, 0, n);
}
return ucs2encode(output);
}
/**
* Converts a string of Unicode code points to a Punycode string of ASCII
* code points.
* @memberOf punycode
* @param {String} input The string of Unicode code points.
* @returns {String} The resulting Punycode string of ASCII code points.
*/
function encode(input) {
var n,
delta,
handledCPCount,
basicLength,
bias,
j,
m,
q,
k,
t,
currentValue,
output = [],
/** `inputLength` will hold the number of code points in `input`. */
inputLength,
/** Cached calculation results */
handledCPCountPlusOne,
baseMinusT,
qMinusT;
// Convert the input in UCS-2 to Unicode
input = ucs2decode(input);
// Cache the length
inputLength = input.length;
// Initialize the state
n = initialN;
delta = 0;
bias = initialBias;
// Handle the basic code points
for (j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue < 0x80) {
output.push(stringFromCharCode(currentValue));
}
}
handledCPCount = basicLength = output.length;
// `handledCPCount` is the number of code points that have been handled;
// `basicLength` is the number of basic code points.
// Finish the basic string - if it is not empty - with a delimiter
if (basicLength) {
output.push(delimiter);
}
// Main encoding loop:
while (handledCPCount < inputLength) {
// All non-basic code points < n have been handled already. Find the next
// larger one:
for (m = maxInt, j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue >= n && currentValue < m) {
m = currentValue;
}
}
// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
// but guard against overflow
handledCPCountPlusOne = handledCPCount + 1;
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
error('overflow');
}
delta += (m - n) * handledCPCountPlusOne;
n = m;
for (j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue < n && ++delta > maxInt) {
error('overflow');
}
if (currentValue == n) {
// Represent delta as a generalized variable-length integer
for (q = delta, k = base; /* no condition */; k += base) {
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
if (q < t) {
break;
}
qMinusT = q - t;
baseMinusT = base - t;
output.push(
stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
);
q = floor(qMinusT / baseMinusT);
}
output.push(stringFromCharCode(digitToBasic(q, 0)));
bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
delta = 0;
++handledCPCount;
}
}
++delta;
++n;
}
return output.join('');
}
/**
* Converts a Punycode string representing a domain name to Unicode. Only the
* Punycoded parts of the domain name will be converted, i.e. it doesn't
* matter if you call it on a string that has already been converted to
* Unicode.
* @memberOf punycode
* @param {String} domain The Punycode domain name to convert to Unicode.
* @returns {String} The Unicode representation of the given Punycode
* string.
*/
function toUnicode(domain) {
return mapDomain(domain, function(string) {
return regexPunycode.test(string)
? decode(string.slice(4).toLowerCase())
: string;
});
}
/**
* Converts a Unicode string representing a domain name to Punycode. Only the
* non-ASCII parts of the domain name will be converted, i.e. it doesn't
* matter if you call it with a domain that's already in ASCII.
* @memberOf punycode
* @param {String} domain The domain name to convert, as a Unicode string.
* @returns {String} The Punycode representation of the given domain name.
*/
function toASCII(domain) {
return mapDomain(domain, function(string) {
return regexNonASCII.test(string)
? 'xn--' + encode(string)
: string;
});
}
/*--------------------------------------------------------------------------*/
/** Define the public API */
punycode = {
/**
* A string representing the current Punycode.js version number.
* @memberOf punycode
* @type String
*/
'version': '1.2.0',
/**
* An object of methods to convert from JavaScript's internal character
* representation (UCS-2) to decimal Unicode code points, and back.
* @see <http://mathiasbynens.be/notes/javascript-encoding>
* @memberOf punycode
* @type Object
*/
'ucs2': {
'decode': ucs2decode,
'encode': ucs2encode
},
'decode': decode,
'encode': encode,
'toASCII': toASCII,
'toUnicode': toUnicode
};
/** Expose `punycode` */
if (freeExports) {
if (freeModule && freeModule.exports == freeExports) {
// in Node.js or Ringo 0.8+
freeModule.exports = punycode;
} else {
// in Narwhal or Ringo 0.7-
for (key in punycode) {
punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
}
}
} else if (freeDefine) {
// via curl.js or RequireJS
define('punycode', punycode);
} else {
// in a browser or Rhino
root.punycode = punycode;
}
}(this));

View File

@@ -0,0 +1,254 @@
// Copyright Joyent, Inc. and other Node contributors.
// Modified by UnityBase core team to be compatible with SyNode
// Query String Utilities
/**
* @module querystring
* @memberOf module:buildin
*/
var QueryString = exports;
/**
* This module provides utilities for dealing with query strings. Call `require('url')` to use it. This is port of NodeJS <a href="http://nodejs.org/api/querystring.html">querystring</a> module.
* Small sample:
*
* var querystring = require('querystring');
* querystring.stringify({param1: 'value1', param2: ['arr1', 'arr2'], paramEmpty: '' })
* // returns 'param1=value1&param2=arr1&param2=arr2&paramEmpty='
*
* @module querystring
*/
// If obj.hasOwnProperty has been overridden, then calling
// obj.hasOwnProperty(prop) will break.
// See: https://github.com/joyent/node/issues/1707
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function charCode(c) {
return c.charCodeAt(0);
}
// a safe fast alternative to decodeURIComponent
QueryString.unescapeBuffer = function(s, decodeSpaces) {
var out = new Buffer(s.length);
var state = 'CHAR'; // states: CHAR, HEX0, HEX1
var n, m, hexchar;
for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) {
var c = s.charCodeAt(inIndex);
switch (state) {
case 'CHAR':
//noinspection FallThroughInSwitchStatementJS
switch (c) {
case charCode('%'):
n = 0;
m = 0;
state = 'HEX0';
break;
case charCode('+'):
if (decodeSpaces) c = charCode(' ');
// pass thru
default:
out[outIndex++] = c;
break;
}
break;
case 'HEX0':
state = 'HEX1';
hexchar = c;
if (charCode('0') <= c && c <= charCode('9')) {
n = c - charCode('0');
} else if (charCode('a') <= c && c <= charCode('f')) {
n = c - charCode('a') + 10;
} else if (charCode('A') <= c && c <= charCode('F')) {
n = c - charCode('A') + 10;
} else {
out[outIndex++] = charCode('%');
out[outIndex++] = c;
state = 'CHAR';
break;
}
break;
case 'HEX1':
state = 'CHAR';
if (charCode('0') <= c && c <= charCode('9')) {
m = c - charCode('0');
} else if (charCode('a') <= c && c <= charCode('f')) {
m = c - charCode('a') + 10;
} else if (charCode('A') <= c && c <= charCode('F')) {
m = c - charCode('A') + 10;
} else {
out[outIndex++] = charCode('%');
out[outIndex++] = hexchar;
out[outIndex++] = c;
break;
}
out[outIndex++] = 16 * n + m;
break;
}
}
// TODO support returning arbitrary buffers.
return out.slice(0, outIndex - 1);
};
/**
* The unescape function used by querystring.parse, provided so that it could be overridden if necessary.
*
* @param s
* @param decodeSpaces
* @return {*}
*/
QueryString.unescape = function(s, decodeSpaces) {
return QueryString.unescapeBuffer(s, decodeSpaces).toString();
};
/**
* The escape function used by querystring.stringify, provided so that it could be overridden if necessary.
* @param str
* @return {string}
*/
QueryString.escape = function(str) {
return encodeURIComponent(str);
};
var stringifyPrimitive = function(v) {
switch (typeof v) {
case 'string':
return v;
case 'boolean':
return v ? 'true' : 'false';
case 'number':
return isFinite(v) ? v : '';
default:
return '';
}
};
/**
* Serialize an object to a query string. Optionally override the default separator ('&') and assignment ('=') characters.
*
* Example:
*
* querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' })
* // returns
* 'foo=bar&baz=qux&baz=quux&corge='
*
* querystring.stringify({foo: 'bar', baz: 'qux'}, ';', ':')
* // returns
* 'foo:bar;baz:qux'
*
* @param {Object} obj
* @param {String} [sep="&"]
* @param {String} [eq="="]
*/
QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) {
sep = sep || '&';
eq = eq || '=';
if (obj === null) {
obj = undefined;
}
if (typeof obj === 'object') {
return Object.keys(obj).map(function(k) {
var ks = QueryString.escape(stringifyPrimitive(k)) + eq;
if (Array.isArray(obj[k])) {
return obj[k].map(function(v) {
return ks + QueryString.escape(stringifyPrimitive(v));
}).join(sep);
} else {
return ks + QueryString.escape(stringifyPrimitive(obj[k]));
}
}).join(sep);
}
if (!name) return '';
return QueryString.escape(stringifyPrimitive(name)) + eq +
QueryString.escape(stringifyPrimitive(obj));
};
/**
* Deserialize a query string to an object. Optionally override the default separator ('&') and assignment ('=') characters.
*
* Options object may contain maxKeys property (equal to 1000 by default), it'll be used to limit processed keys. Set it to 0 to remove key count limitation.
*
* Example:
*
* querystring.parse('foo=bar&baz=qux&baz=quux&corge')
* // returns
* { foo: 'bar', baz: ['qux', 'quux'], corge: '' }
*
* @method parse
* @param {Object} obj
* @param {String} [sep="&"]
* @param {String} [eq="="]
* @param {Object} [options]
* @param {Number} [options.maxKeys=1000]
*/
QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
var obj = {};
if (typeof qs !== 'string' || qs.length === 0) {
return obj;
}
var regexp = /\+/g;
qs = qs.split(sep);
var maxKeys = 1000;
if (options && typeof options.maxKeys === 'number') {
maxKeys = options.maxKeys;
}
var len = qs.length;
// maxKeys <= 0 means that we should not limit keys count
if (maxKeys > 0 && len > maxKeys) {
len = maxKeys;
}
for (var i = 0; i < len; ++i) {
var x = qs[i].replace(regexp, '%20'),
idx = x.indexOf(eq),
kstr, vstr, k, v;
if (idx >= 0) {
kstr = x.substr(0, idx);
vstr = x.substr(idx + 1);
} else {
kstr = x;
vstr = '';
}
try {
k = decodeURIComponent(kstr);
v = decodeURIComponent(vstr);
} catch (e) {
k = QueryString.unescape(kstr, true);
v = QueryString.unescape(vstr, true);
}
if (!hasOwnProperty(obj, k)) {
obj[k] = v;
} else if (Array.isArray(obj[k])) {
obj[k].push(v);
} else {
obj[k] = [obj[k], v];
}
}
return obj;
};

View File

@@ -0,0 +1,111 @@
'use strict';
/**
* See <a href="https://nodejs.org/api/stream.html">Node <strong>stream</strong> module documentation</a>
* @module stream
* @memberOf module:buildin
*/
module.exports = Stream;
const EE = require('events');
const util = require('util');
util.inherits(Stream, EE);
Stream.Readable = require('_stream_readable');
Stream.Writable = require('_stream_writable');
Stream.Duplex = require('_stream_duplex');
Stream.Transform = require('_stream_transform');
Stream.PassThrough = require('_stream_passthrough');
// Backwards-compat with node 0.4.x
Stream.Stream = Stream;
// old-style streams. Note that the pipe method (the only relevant
// part of this class) is overridden in the Readable class.
function Stream() {
EE.call(this);
}
Stream.prototype.pipe = function(dest, options) {
var source = this;
function ondata(chunk) {
if (dest.writable) {
if (false === dest.write(chunk) && source.pause) {
source.pause();
}
}
}
source.on('data', ondata);
function ondrain() {
if (source.readable && source.resume) {
source.resume();
}
}
dest.on('drain', ondrain);
// If the 'end' option is not supplied, dest.end() will be called when
// source gets the 'end' or 'close' events. Only dest.end() once.
if (!dest._isStdio && (!options || options.end !== false)) {
source.on('end', onend);
source.on('close', onclose);
}
var didOnEnd = false;
function onend() {
if (didOnEnd) return;
didOnEnd = true;
dest.end();
}
function onclose() {
if (didOnEnd) return;
didOnEnd = true;
if (typeof dest.destroy === 'function') dest.destroy();
}
// don't leave dangling pipes when there are errors.
function onerror(er) {
cleanup();
if (EE.listenerCount(this, 'error') === 0) {
throw er; // Unhandled stream error in pipe.
}
}
source.on('error', onerror);
dest.on('error', onerror);
// remove all the event listeners that were added.
function cleanup() {
source.removeListener('data', ondata);
dest.removeListener('drain', ondrain);
source.removeListener('end', onend);
source.removeListener('close', onclose);
source.removeListener('error', onerror);
dest.removeListener('error', onerror);
source.removeListener('end', cleanup);
source.removeListener('close', cleanup);
dest.removeListener('close', cleanup);
}
source.on('end', cleanup);
source.on('close', cleanup);
dest.on('close', cleanup);
dest.emit('pipe', source);
// Allow for unix-like usage: A.pipe(B).pipe(C)
return dest;
};

View File

@@ -0,0 +1,266 @@
'use strict';
/**
* @module string_decoder
* @memberOf module:buildin
*/
const Buffer = require('buffer').Buffer;
const internalUtil = require('internal/util');
const isEncoding = Buffer[internalUtil.kIsEncodingSymbol];
// Do not cache `Buffer.isEncoding` when checking encoding names as some
// modules monkey-patch it to support additional encodings
function normalizeEncoding(enc) {
const nenc = internalUtil.normalizeEncoding(enc);
if (typeof nenc !== 'string' &&
(Buffer.isEncoding === isEncoding || !Buffer.isEncoding(enc)))
throw new Error(`Unknown encoding: ${enc}`);
return nenc || enc;
}
// StringDecoder provides an interface for efficiently splitting a series of
// buffers into a series of JS strings without breaking apart multi-byte
// characters.
exports.StringDecoder = StringDecoder;
function StringDecoder(encoding) {
this.encoding = normalizeEncoding(encoding);
var nb;
switch (this.encoding) {
case 'utf16le':
this.text = utf16Text;
this.end = utf16End;
nb = 4;
break;
case 'utf8':
this.fillLast = utf8FillLast;
nb = 4;
break;
case 'base64':
this.text = base64Text;
this.end = base64End;
nb = 3;
break;
default:
this.write = simpleWrite;
this.end = simpleEnd;
return;
}
this.lastNeed = 0;
this.lastTotal = 0;
this.lastChar = Buffer.allocUnsafe(nb);
}
StringDecoder.prototype.write = function(buf) {
if (buf.length === 0)
return '';
var r;
var i;
if (this.lastNeed) {
r = this.fillLast(buf);
if (r === undefined)
return '';
i = this.lastNeed;
this.lastNeed = 0;
} else {
i = 0;
}
if (i < buf.length)
return (r ? r + this.text(buf, i) : this.text(buf, i));
return r || '';
};
StringDecoder.prototype.end = utf8End;
// Returns only complete characters in a Buffer
StringDecoder.prototype.text = utf8Text;
// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer
StringDecoder.prototype.fillLast = function(buf) {
if (this.lastNeed <= buf.length) {
buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed);
return this.lastChar.toString(this.encoding, 0, this.lastTotal);
}
buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length);
this.lastNeed -= buf.length;
};
// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a
// continuation byte.
function utf8CheckByte(byte) {
if (byte <= 0x7F)
return 0;
else if (byte >> 5 === 0x06)
return 2;
else if (byte >> 4 === 0x0E)
return 3;
else if (byte >> 3 === 0x1E)
return 4;
return -1;
}
// Checks at most 3 bytes at the end of a Buffer in order to detect an
// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4)
// needed to complete the UTF-8 character (if applicable) are returned.
function utf8CheckIncomplete(self, buf, i) {
var j = buf.length - 1;
if (j < i)
return 0;
var nb = utf8CheckByte(buf[j]);
if (nb >= 0) {
if (nb > 0)
self.lastNeed = nb - 1;
return nb;
}
if (--j < i)
return 0;
nb = utf8CheckByte(buf[j]);
if (nb >= 0) {
if (nb > 0)
self.lastNeed = nb - 2;
return nb;
}
if (--j < i)
return 0;
nb = utf8CheckByte(buf[j]);
if (nb >= 0) {
if (nb > 0) {
if (nb === 2)
nb = 0;
else
self.lastNeed = nb - 3;
}
return nb;
}
return 0;
}
// Validates as many continuation bytes for a multi-byte UTF-8 character as
// needed or are available. If we see a non-continuation byte where we expect
// one, we "replace" the validated continuation bytes we've seen so far with
// UTF-8 replacement characters ('\ufffd'), to match v8's UTF-8 decoding
// behavior. The continuation byte check is included three times in the case
// where all of the continuation bytes for a character exist in the same buffer.
// It is also done this way as a slight performance increase instead of using a
// loop.
function utf8CheckExtraBytes(self, buf, p) {
if ((buf[0] & 0xC0) !== 0x80) {
self.lastNeed = 0;
return '\ufffd'.repeat(p);
}
if (self.lastNeed > 1 && buf.length > 1) {
if ((buf[1] & 0xC0) !== 0x80) {
self.lastNeed = 1;
return '\ufffd'.repeat(p + 1);
}
if (self.lastNeed > 2 && buf.length > 2) {
if ((buf[2] & 0xC0) !== 0x80) {
self.lastNeed = 2;
return '\ufffd'.repeat(p + 2);
}
}
}
}
// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer.
function utf8FillLast(buf) {
const p = this.lastTotal - this.lastNeed;
var r = utf8CheckExtraBytes(this, buf, p);
if (r !== undefined)
return r;
if (this.lastNeed <= buf.length) {
buf.copy(this.lastChar, p, 0, this.lastNeed);
return this.lastChar.toString(this.encoding, 0, this.lastTotal);
}
buf.copy(this.lastChar, p, 0, buf.length);
this.lastNeed -= buf.length;
}
// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a
// partial character, the character's bytes are buffered until the required
// number of bytes are available.
function utf8Text(buf, i) {
const total = utf8CheckIncomplete(this, buf, i);
if (!this.lastNeed)
return buf.toString('utf8', i);
this.lastTotal = total;
const end = buf.length - (total - this.lastNeed);
buf.copy(this.lastChar, 0, end);
return buf.toString('utf8', i, end);
}
// For UTF-8, a replacement character for each buffered byte of a (partial)
// character needs to be added to the output.
function utf8End(buf) {
const r = (buf && buf.length ? this.write(buf) : '');
if (this.lastNeed)
return r + '\ufffd'.repeat(this.lastTotal - this.lastNeed);
return r;
}
// UTF-16LE typically needs two bytes per character, but even if we have an even
// number of bytes available, we need to check if we end on a leading/high
// surrogate. In that case, we need to wait for the next two bytes in order to
// decode the last character properly.
function utf16Text(buf, i) {
if ((buf.length - i) % 2 === 0) {
const r = buf.toString('utf16le', i);
if (r) {
const c = r.charCodeAt(r.length - 1);
if (c >= 0xD800 && c <= 0xDBFF) {
this.lastNeed = 2;
this.lastTotal = 4;
this.lastChar[0] = buf[buf.length - 2];
this.lastChar[1] = buf[buf.length - 1];
return r.slice(0, -1);
}
}
return r;
}
this.lastNeed = 1;
this.lastTotal = 2;
this.lastChar[0] = buf[buf.length - 1];
return buf.toString('utf16le', i, buf.length - 1);
}
// For UTF-16LE we do not explicitly append special replacement characters if we
// end on a partial character, we simply let v8 handle that.
function utf16End(buf) {
const r = (buf && buf.length ? this.write(buf) : '');
if (this.lastNeed) {
const end = this.lastTotal - this.lastNeed;
return r + this.lastChar.toString('utf16le', 0, end);
}
return r;
}
function base64Text(buf, i) {
const n = (buf.length - i) % 3;
if (n === 0)
return buf.toString('base64', i);
this.lastNeed = 3 - n;
this.lastTotal = 3;
if (n === 1) {
this.lastChar[0] = buf[buf.length - 1];
} else {
this.lastChar[0] = buf[buf.length - 2];
this.lastChar[1] = buf[buf.length - 1];
}
return buf.toString('base64', i, buf.length - n);
}
function base64End(buf) {
const r = (buf && buf.length ? this.write(buf) : '');
if (this.lastNeed)
return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed);
return r;
}
// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex)
function simpleWrite(buf) {
return buf.toString(this.encoding);
}
function simpleEnd(buf) {
return (buf && buf.length ? this.write(buf) : '');
}

View File

@@ -0,0 +1,7 @@
/**
* MPV - fake implementation of node.js timer
* Just for xml2js work
*/
module.exports = {
setImmediate: global.setImmediate
}

View File

@@ -0,0 +1,9 @@
/**
* MPV - Fake implementation of nodejs tty
* Always return `false` to isatty() call
* @module tty
* @memberOf module:buildin
*/
exports.isatty = function(fd) {
return false;
};

730
contrib/mORMot/SyNode/core_modules/node_modules/url.js generated vendored Normal file
View File

@@ -0,0 +1,730 @@
/**
* See <a href="https://nodejs.org/api/url.html">Node <strong>url</strong> module documentation</a>
* @module url
* @memberOf module:buildin
*/
'use strict';
const punycode = require('punycode');
exports.parse = urlParse;
exports.resolve = urlResolve;
exports.resolveObject = urlResolveObject;
exports.format = urlFormat;
exports.Url = Url;
function Url() {
this.protocol = null;
this.slashes = null;
this.auth = null;
this.host = null;
this.port = null;
this.hostname = null;
this.hash = null;
this.search = null;
this.query = null;
this.pathname = null;
this.path = null;
this.href = null;
}
// Reference: RFC 3986, RFC 1808, RFC 2396
// define these here so at least they only have to be
// compiled once on the first module load.
const protocolPattern = /^([a-z0-9.+-]+:)/i;
const portPattern = /:[0-9]*$/;
// Special case for a simple path URL
const simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/;
// RFC 2396: characters reserved for delimiting URLs.
// We actually just auto-escape these.
const delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'];
// RFC 2396: characters not allowed for various reasons.
const unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims);
// Allowed by RFCs, but cause of XSS attacks. Always escape these.
const autoEscape = ['\''].concat(unwise);
// Characters that are never ever allowed in a hostname.
// Note that any invalid chars are also handled, but these
// are the ones that are *expected* to be seen, so we fast-path them.
const nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape);
const hostEndingChars = ['/', '?', '#'];
const hostnameMaxLen = 255;
const hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/;
const hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/;
// protocols that can allow "unsafe" and "unwise" chars.
const unsafeProtocol = {
'javascript': true,
'javascript:': true
};
// protocols that never have a hostname.
const hostlessProtocol = {
'javascript': true,
'javascript:': true
};
// protocols that always contain a // bit.
const slashedProtocol = {
'http': true,
'https': true,
'ftp': true,
'gopher': true,
'file': true,
'http:': true,
'https:': true,
'ftp:': true,
'gopher:': true,
'file:': true
};
const querystring = require('querystring');
function urlParse(url, parseQueryString, slashesDenoteHost) {
if (url instanceof Url) return url;
var u = new Url();
u.parse(url, parseQueryString, slashesDenoteHost);
return u;
}
Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
if (typeof url !== 'string') {
throw new TypeError('Parameter "url" must be a string, not ' + typeof url);
}
// Copy chrome, IE, opera backslash-handling behavior.
// Back slashes before the query string get converted to forward slashes
// See: https://code.google.com/p/chromium/issues/detail?id=25916
var queryIndex = url.indexOf('?'),
splitter =
(queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#',
uSplit = url.split(splitter),
slashRegex = /\\/g;
uSplit[0] = uSplit[0].replace(slashRegex, '/');
url = uSplit.join(splitter);
var rest = url;
// trim before proceeding.
// This is to support parse stuff like " http://foo.com \n"
rest = rest.trim();
if (!slashesDenoteHost && url.split('#').length === 1) {
// Try fast path regexp
var simplePath = simplePathPattern.exec(rest);
if (simplePath) {
this.path = rest;
this.href = rest;
this.pathname = simplePath[1];
if (simplePath[2]) {
this.search = simplePath[2];
if (parseQueryString) {
this.query = querystring.parse(this.search.substr(1));
} else {
this.query = this.search.substr(1);
}
} else if (parseQueryString) {
this.search = '';
this.query = {};
}
return this;
}
}
var proto = protocolPattern.exec(rest);
if (proto) {
proto = proto[0];
var lowerProto = proto.toLowerCase();
this.protocol = lowerProto;
rest = rest.substr(proto.length);
}
// figure out if it's got a host
// user@server is *always* interpreted as a hostname, and url
// resolution will treat //foo/bar as host=foo,path=bar because that's
// how the browser resolves relative URLs.
if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
var slashes = rest.substr(0, 2) === '//';
if (slashes && !(proto && hostlessProtocol[proto])) {
rest = rest.substr(2);
this.slashes = true;
}
}
if (!hostlessProtocol[proto] &&
(slashes || (proto && !slashedProtocol[proto]))) {
// there's a hostname.
// the first instance of /, ?, ;, or # ends the host.
//
// If there is an @ in the hostname, then non-host chars *are* allowed
// to the left of the last @ sign, unless some host-ending character
// comes *before* the @-sign.
// URLs are obnoxious.
//
// ex:
// http://a@b@c/ => user:a@b host:c
// http://a@b?@c => user:a host:b path:/?@c
// v0.12 TODO(isaacs): This is not quite how Chrome does things.
// Review our test case against browsers more comprehensively.
// find the first instance of any hostEndingChars
var hostEnd = -1;
for (var i = 0; i < hostEndingChars.length; i++) {
var hec = rest.indexOf(hostEndingChars[i]);
if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
hostEnd = hec;
}
// at this point, either we have an explicit point where the
// auth portion cannot go past, or the last @ char is the decider.
var auth, atSign;
if (hostEnd === -1) {
// atSign can be anywhere.
atSign = rest.lastIndexOf('@');
} else {
// atSign must be in auth portion.
// http://a@b/c@d => host:b auth:a path:/c@d
atSign = rest.lastIndexOf('@', hostEnd);
}
// Now we have a portion which is definitely the auth.
// Pull that off.
if (atSign !== -1) {
auth = rest.slice(0, atSign);
rest = rest.slice(atSign + 1);
this.auth = decodeURIComponent(auth);
}
// the host is the remaining to the left of the first non-host char
hostEnd = -1;
for (var i = 0; i < nonHostChars.length; i++) {
var hec = rest.indexOf(nonHostChars[i]);
if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
hostEnd = hec;
}
// if we still have not hit it, then the entire thing is a host.
if (hostEnd === -1)
hostEnd = rest.length;
this.host = rest.slice(0, hostEnd);
rest = rest.slice(hostEnd);
// pull out port.
this.parseHost();
// we've indicated that there is a hostname,
// so even if it's empty, it has to be present.
this.hostname = this.hostname || '';
// if hostname begins with [ and ends with ]
// assume that it's an IPv6 address.
var ipv6Hostname = this.hostname[0] === '[' &&
this.hostname[this.hostname.length - 1] === ']';
// validate a little.
if (!ipv6Hostname) {
var hostparts = this.hostname.split(/\./);
for (var i = 0, l = hostparts.length; i < l; i++) {
var part = hostparts[i];
if (!part) continue;
if (!part.match(hostnamePartPattern)) {
var newpart = '';
for (var j = 0, k = part.length; j < k; j++) {
if (part.charCodeAt(j) > 127) {
// we replace non-ASCII char with a temporary placeholder
// we need this to make sure size of hostname is not
// broken by replacing non-ASCII by nothing
newpart += 'x';
} else {
newpart += part[j];
}
}
// we test again with ASCII char only
if (!newpart.match(hostnamePartPattern)) {
var validParts = hostparts.slice(0, i);
var notHost = hostparts.slice(i + 1);
var bit = part.match(hostnamePartStart);
if (bit) {
validParts.push(bit[1]);
notHost.unshift(bit[2]);
}
if (notHost.length) {
rest = '/' + notHost.join('.') + rest;
}
this.hostname = validParts.join('.');
break;
}
}
}
}
if (this.hostname.length > hostnameMaxLen) {
this.hostname = '';
} else {
// hostnames are always lower case.
this.hostname = this.hostname.toLowerCase();
}
if (!ipv6Hostname) {
// IDNA Support: Returns a punycoded representation of "domain".
// It only converts parts of the domain name that
// have non-ASCII characters, i.e. it doesn't matter if
// you call it with a domain that already is ASCII-only.
this.hostname = punycode.toASCII(this.hostname);
}
var p = this.port ? ':' + this.port : '';
var h = this.hostname || '';
this.host = h + p;
// strip [ and ] from the hostname
// the host field still retains them, though
if (ipv6Hostname) {
this.hostname = this.hostname.substr(1, this.hostname.length - 2);
if (rest[0] !== '/') {
rest = '/' + rest;
}
}
}
// now rest is set to the post-host stuff.
// chop off any delim chars.
if (!unsafeProtocol[lowerProto]) {
// First, make 100% sure that any "autoEscape" chars get
// escaped, even if encodeURIComponent doesn't think they
// need to be.
for (var i = 0, l = autoEscape.length; i < l; i++) {
var ae = autoEscape[i];
if (rest.indexOf(ae) === -1)
continue;
var esc = encodeURIComponent(ae);
if (esc === ae) {
esc = escape(ae);
}
rest = rest.split(ae).join(esc);
}
}
// chop off from the tail first.
var hash = rest.indexOf('#');
if (hash !== -1) {
// got a fragment string.
this.hash = rest.substr(hash);
rest = rest.slice(0, hash);
}
var qm = rest.indexOf('?');
if (qm !== -1) {
this.search = rest.substr(qm);
this.query = rest.substr(qm + 1);
if (parseQueryString) {
this.query = querystring.parse(this.query);
}
rest = rest.slice(0, qm);
} else if (parseQueryString) {
// no query string, but parseQueryString still requested
this.search = '';
this.query = {};
}
if (rest) this.pathname = rest;
if (slashedProtocol[lowerProto] &&
this.hostname && !this.pathname) {
this.pathname = '/';
}
//to support http.request
if (this.pathname || this.search) {
var p = this.pathname || '';
var s = this.search || '';
this.path = p + s;
}
// finally, reconstruct the href based on what has been validated.
this.href = this.format();
return this;
};
// format a parsed object into a url string
function urlFormat(obj) {
// ensure it's an object, and not a string url.
// If it's an obj, this is a no-op.
// this way, you can call url_format() on strings
// to clean up potentially wonky urls.
if (typeof obj === 'string') obj = urlParse(obj);
else if (typeof obj !== 'object' || obj === null)
throw new TypeError('Parameter "urlObj" must be an object, not ' +
obj === null ? 'null' : typeof obj);
else if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
return obj.format();
}
Url.prototype.format = function() {
var auth = this.auth || '';
if (auth) {
auth = encodeURIComponent(auth);
auth = auth.replace(/%3A/i, ':');
auth += '@';
}
var protocol = this.protocol || '',
pathname = this.pathname || '',
hash = this.hash || '',
host = false,
query = '';
if (this.host) {
host = auth + this.host;
} else if (this.hostname) {
host = auth + (this.hostname.indexOf(':') === -1 ?
this.hostname :
'[' + this.hostname + ']');
if (this.port) {
host += ':' + this.port;
}
}
if (this.query !== null &&
typeof this.query === 'object' &&
Object.keys(this.query).length) {
query = querystring.stringify(this.query);
}
var search = this.search || (query && ('?' + query)) || '';
if (protocol && protocol.substr(-1) !== ':') protocol += ':';
// only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
// unless they had them to begin with.
if (this.slashes ||
(!protocol || slashedProtocol[protocol]) && host !== false) {
host = '//' + (host || '');
if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
} else if (!host) {
host = '';
}
if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
if (search && search.charAt(0) !== '?') search = '?' + search;
pathname = pathname.replace(/[?#]/g, function(match) {
return encodeURIComponent(match);
});
search = search.replace('#', '%23');
return protocol + host + pathname + search + hash;
};
function urlResolve(source, relative) {
return urlParse(source, false, true).resolve(relative);
}
Url.prototype.resolve = function(relative) {
return this.resolveObject(urlParse(relative, false, true)).format();
};
function urlResolveObject(source, relative) {
if (!source) return relative;
return urlParse(source, false, true).resolveObject(relative);
}
Url.prototype.resolveObject = function(relative) {
if (typeof relative === 'string') {
var rel = new Url();
rel.parse(relative, false, true);
relative = rel;
}
var result = new Url();
var tkeys = Object.keys(this);
for (var tk = 0; tk < tkeys.length; tk++) {
var tkey = tkeys[tk];
result[tkey] = this[tkey];
}
// hash is always overridden, no matter what.
// even href="" will remove it.
result.hash = relative.hash;
// if the relative url is empty, then there's nothing left to do here.
if (relative.href === '') {
result.href = result.format();
return result;
}
// hrefs like //foo/bar always cut to the protocol.
if (relative.slashes && !relative.protocol) {
// take everything except the protocol from relative
var rkeys = Object.keys(relative);
for (var rk = 0; rk < rkeys.length; rk++) {
var rkey = rkeys[rk];
if (rkey !== 'protocol')
result[rkey] = relative[rkey];
}
//urlParse appends trailing / to urls like http://www.example.com
if (slashedProtocol[result.protocol] &&
result.hostname && !result.pathname) {
result.path = result.pathname = '/';
}
result.href = result.format();
return result;
}
if (relative.protocol && relative.protocol !== result.protocol) {
// if it's a known url protocol, then changing
// the protocol does weird things
// first, if it's not file:, then we MUST have a host,
// and if there was a path
// to begin with, then we MUST have a path.
// if it is file:, then the host is dropped,
// because that's known to be hostless.
// anything else is assumed to be absolute.
if (!slashedProtocol[relative.protocol]) {
var keys = Object.keys(relative);
for (var v = 0; v < keys.length; v++) {
var k = keys[v];
result[k] = relative[k];
}
result.href = result.format();
return result;
}
result.protocol = relative.protocol;
if (!relative.host &&
!/^file:?$/.test(relative.protocol) &&
!hostlessProtocol[relative.protocol]) {
var relPath = (relative.pathname || '').split('/');
while (relPath.length && !(relative.host = relPath.shift()));
if (!relative.host) relative.host = '';
if (!relative.hostname) relative.hostname = '';
if (relPath[0] !== '') relPath.unshift('');
if (relPath.length < 2) relPath.unshift('');
result.pathname = relPath.join('/');
} else {
result.pathname = relative.pathname;
}
result.search = relative.search;
result.query = relative.query;
result.host = relative.host || '';
result.auth = relative.auth;
result.hostname = relative.hostname || relative.host;
result.port = relative.port;
// to support http.request
if (result.pathname || result.search) {
var p = result.pathname || '';
var s = result.search || '';
result.path = p + s;
}
result.slashes = result.slashes || relative.slashes;
result.href = result.format();
return result;
}
var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
isRelAbs = (
relative.host ||
relative.pathname && relative.pathname.charAt(0) === '/'
),
mustEndAbs = (isRelAbs || isSourceAbs ||
(result.host && relative.pathname)),
removeAllDots = mustEndAbs,
srcPath = result.pathname && result.pathname.split('/') || [],
relPath = relative.pathname && relative.pathname.split('/') || [],
psychotic = result.protocol && !slashedProtocol[result.protocol];
// if the url is a non-slashed url, then relative
// links like ../.. should be able
// to crawl up to the hostname, as well. This is strange.
// result.protocol has already been set by now.
// Later on, put the first path part into the host field.
if (psychotic) {
result.hostname = '';
result.port = null;
if (result.host) {
if (srcPath[0] === '') srcPath[0] = result.host;
else srcPath.unshift(result.host);
}
result.host = '';
if (relative.protocol) {
relative.hostname = null;
relative.port = null;
if (relative.host) {
if (relPath[0] === '') relPath[0] = relative.host;
else relPath.unshift(relative.host);
}
relative.host = null;
}
mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
}
if (isRelAbs) {
// it's absolute.
result.host = (relative.host || relative.host === '') ?
relative.host : result.host;
result.hostname = (relative.hostname || relative.hostname === '') ?
relative.hostname : result.hostname;
result.search = relative.search;
result.query = relative.query;
srcPath = relPath;
// fall through to the dot-handling below.
} else if (relPath.length) {
// it's relative
// throw away the existing file, and take the new path instead.
if (!srcPath) srcPath = [];
srcPath.pop();
srcPath = srcPath.concat(relPath);
result.search = relative.search;
result.query = relative.query;
} else if (relative.search !== null && relative.search !== undefined) {
// just pull out the search.
// like href='?foo'.
// Put this after the other two cases because it simplifies the booleans
if (psychotic) {
result.hostname = result.host = srcPath.shift();
//occationaly the auth can get stuck only in host
//this especially happens in cases like
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
var authInHost = result.host && result.host.indexOf('@') > 0 ?
result.host.split('@') : false;
if (authInHost) {
result.auth = authInHost.shift();
result.host = result.hostname = authInHost.shift();
}
}
result.search = relative.search;
result.query = relative.query;
//to support http.request
if (result.pathname !== null || result.search !== null) {
result.path = (result.pathname ? result.pathname : '') +
(result.search ? result.search : '');
}
result.href = result.format();
return result;
}
if (!srcPath.length) {
// no path at all. easy.
// we've already handled the other stuff above.
result.pathname = null;
//to support http.request
if (result.search) {
result.path = '/' + result.search;
} else {
result.path = null;
}
result.href = result.format();
return result;
}
// if a url ENDs in . or .., then it must get a trailing slash.
// however, if it ends in anything else non-slashy,
// then it must NOT get a trailing slash.
var last = srcPath.slice(-1)[0];
var hasTrailingSlash = (
(result.host || relative.host || srcPath.length > 1) &&
(last === '.' || last === '..') || last === '');
// strip single dots, resolve double dots to parent dir
// if the path tries to go above the root, `up` ends up > 0
var up = 0;
for (var i = srcPath.length; i >= 0; i--) {
last = srcPath[i];
if (last === '.') {
spliceOne(srcPath, i);
} else if (last === '..') {
spliceOne(srcPath, i);
up++;
} else if (up) {
spliceOne(srcPath, i);
up--;
}
}
// if the path is allowed to go above the root, restore leading ..s
if (!mustEndAbs && !removeAllDots) {
for (; up--; up) {
srcPath.unshift('..');
}
}
if (mustEndAbs && srcPath[0] !== '' &&
(!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
srcPath.unshift('');
}
if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
srcPath.push('');
}
var isAbsolute = srcPath[0] === '' ||
(srcPath[0] && srcPath[0].charAt(0) === '/');
// put the host back
if (psychotic) {
result.hostname = result.host = isAbsolute ? '' :
srcPath.length ? srcPath.shift() : '';
//occationaly the auth can get stuck only in host
//this especially happens in cases like
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
var authInHost = result.host && result.host.indexOf('@') > 0 ?
result.host.split('@') : false;
if (authInHost) {
result.auth = authInHost.shift();
result.host = result.hostname = authInHost.shift();
}
}
mustEndAbs = mustEndAbs || (result.host && srcPath.length);
if (mustEndAbs && !isAbsolute) {
srcPath.unshift('');
}
if (!srcPath.length) {
result.pathname = null;
result.path = null;
} else {
result.pathname = srcPath.join('/');
}
//to support request.http
if (result.pathname !== null || result.search !== null) {
result.path = (result.pathname ? result.pathname : '') +
(result.search ? result.search : '');
}
result.auth = relative.auth || result.auth;
result.slashes = result.slashes || relative.slashes;
result.href = result.format();
return result;
};
Url.prototype.parseHost = function() {
var host = this.host;
var port = portPattern.exec(host);
if (port) {
port = port[0];
if (port !== ':') {
this.port = port.substr(1);
}
host = host.substr(0, host.length - port.length);
}
if (host) this.hostname = host;
};
// About 1.5x faster than the two-arg version of Array#splice().
function spliceOne(list, index) {
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
list[i] = list[k];
list.pop();
}

1209
contrib/mORMot/SyNode/core_modules/node_modules/util.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

19
contrib/mORMot/SyNode/core_modules/node_modules/vm.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
/**
* Fake vm
* @module vm
* @memberOf module:buildin
*/
const {runInThisContext, loadDll} = process.binding('modules');
/**
* Node expect this config
{
filename: filename,
lineOffset: 0,
displayErrors: true
}
*/
exports.runInThisContext = function(code, config){
return runInThisContext(code, config.filename)
}
exports.runInDebugContext = function(){};

View File

@@ -0,0 +1 @@
// fake zlib

View File

@@ -0,0 +1,255 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
const {coreModulesPath, runInThisContext, runInThisContextRes, _coreModulesInRes} = process.binding('modules');
const {loadFile} = process.binding('fs');
let Module;
/**
* @namespace process
* @property {string} startupPath Use a process.cwd() instead
* @property {string} execPath The main executable full path (including .exe file name)
*/
function startup() {
/**
* Current working directory
* @return {string|String}
*/
process.cwd = function () {
return process.startupPath;
};
/**
* List of loaded via `require` modules
* @private
* @type {Array<string>}
*/
process.moduleLoadList = [];
Module = NativeModule.require('module');
Module.call(global, ['.']);
process.mainModule = global;
//noinspection JSUndeclaredVariable
/**
* Load a module. Acts like a <a href="http://nodejs.org/api/modules.html">Node JS</a> require, with 1 difference:
*
* - in case we run in production mode (`!process.isDebug`) and minimized version of main module exists, it will be loaded.
* By "minimized version" we mean package.json `main` entry with `.min.js` extension <br>
*
* *In case you need to debug from there module is loaded set OS Environment variable*
* `>SET NODE_DEBUG=modules` *and restart server - require will put to debug log all information about how module are loaded.* Do not do this on production, of course :)
*
* @global
* @method
* @param {String} moduleName
* @returns {*}
*/
global.require = Module.prototype.require;
global.Buffer = NativeModule.require('buffer').Buffer;
//global.clearTimeout = function() {};
/**
* Block thread for a specified number of milliseconds
* @param {Number} ms millisecond to sleep
* @global
*/
global.sleep = process.binding('syNode').sleep;
const EventEmitter = NativeModule.require('events').EventEmitter;
// add EventEmitter to process object
EventEmitter.call(process);
Object.assign(process, EventEmitter.prototype);
const WindowTimer = NativeModule.require('polyfill/WindowTimer');
global._timerLoop = WindowTimer.makeWindowTimer(global, function (ms) { global.sleep(ms); });
/**
* This function is just to be compatible with node.js
* @param {Function} callback Callback (called immediately in SyNode)
*/
process.nextTick = function(callback, arg1, arg2, arg3){
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
}
// on the way out, don't bother. it won't get fired anyway.
if (process._exiting)
return;
var i, args;
switch (arguments.length) {
// fast cases
case 1:
break;
case 2:
args = [arg1];
break;
case 3:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 4; i < arguments.length; i++)
args[i - 1] = arguments[i];
break;
}
global._timerLoop.setTimeoutWithPriority.apply(undefined, [callback, 0, -1].concat(args));
};
/**
* This function is to be compatible with node.js
* @global
* @param {Function} callback
* @param {...*} arg
* @return {Number} immediateId
*/
global.setImmediate = function(callback, arg1, arg2, arg3){
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
}
// on the way out, don't bother. it won't get fired anyway.
if (process._exiting)
return;
var i, args;
switch (arguments.length) {
// fast cases
case 1:
break;
case 2:
args = [arg1];
break;
case 3:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 4; i < arguments.length; i++)
args[i - 1] = arguments[i];
break;
}
global._timerLoop.setTimeoutWithPriority.apply(undefined, [callback, 0, 1].concat(args));
};
}
function NativeModule(id) {
this.filename = id + '.js';
this.id = id;
this.exports = {};
this.loaded = false;
}
const NODE_CORE_MODULES = ['fs', 'util', 'path', 'assert', 'module', 'console', 'events','vm',
'net', 'os', 'punycode', 'querystring', 'timers', 'tty', 'url', 'child_process', 'http', 'https',
'crypto', 'zlib', 'dns', //fake modules
'buffer', 'string_decoder', 'internal/util', 'internal/module', 'stream', '_stream_readable', '_stream_writable',
'internal/streams/BufferList', '_stream_duplex', '_stream_transform', '_stream_passthrough',
'internal/fs',
'internal/errors', 'internal/querystring',
'polyfill/WindowTimer'];
NativeModule._source = {};
const PATH_DELIM = process.platform === 'win32' ? '\\' : '/'
NODE_CORE_MODULES.forEach( (module_name) => {
NativeModule._source[module_name] = _coreModulesInRes
? `node_modules/${module_name}.js`.toUpperCase()
: `${coreModulesPath}${PATH_DELIM}node_modules${PATH_DELIM}${module_name}.js`
});
NativeModule._cache = {};
NativeModule.require = function (id) {
if (id == 'native_module') {
return NativeModule;
}
var cached = NativeModule.getCached(id);
if (cached) {
return cached.exports;
}
if (!NativeModule.exists(id)) {
throw new Error('No such native module ' + id);
}
process.moduleLoadList.push('NativeModule ' + id);
var nativeModule = new NativeModule(id);
nativeModule.cache();
nativeModule.compile();
return nativeModule.exports;
};
NativeModule.getCached = function (id) {
if (NativeModule._cache.hasOwnProperty(id)) {
return NativeModule._cache[id]
} else {
return null;
}
};
NativeModule.exists = function (id) {
return NativeModule._source.hasOwnProperty(id);
};
const EXPOSE_INTERNALS = false;
/* MPV
const EXPOSE_INTERNALS = process.execArgv.some(function(arg) {
return arg.match(/^--expose[-_]internals$/);
});
*/
if (EXPOSE_INTERNALS) {
NativeModule.nonInternalExists = NativeModule.exists;
NativeModule.isInternal = function(id) {
return false;
};
} else {
NativeModule.nonInternalExists = function(id) {
return NativeModule.exists(id) && !NativeModule.isInternal(id);
};
NativeModule.isInternal = function(id) {
return id.startsWith('internal/');
};
}
NativeModule.getSource = function (id) {
return loadFile(NativeModule._source[id]);
};
NativeModule.wrap = function (script) {
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};
NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ', '\n});'
];
NativeModule.prototype.compile = function () {
let fn;
if (_coreModulesInRes) {
fn = runInThisContextRes(NativeModule._source[this.id], this.filename, true);
} else {
let source = NativeModule.getSource(this.id);
source = NativeModule.wrap(source);
fn = runInThisContext(source, this.filename, true);
}
fn(this.exports, NativeModule.require, this, this.filename);
this.loaded = true;
};
NativeModule.prototype.cache = function () {
NativeModule._cache[this.id] = this;
};
startup();
///patch ModuleLoader

View File

@@ -0,0 +1,28 @@
<!-- Build instruction for SpiderMonkey45 for Windows to use with SyNode -->
##Preparation
* Download and install **MozillaBuild**. See instruction here [MozillaBuild](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Windows_Prerequisites#mozillabuild)
* Get Mozilla Source Code from here https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Releases/45
* Apply patches.(todo: make patch)
## Build SpiderMonkey 45
Follow instruction from [Mozilla Build Documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Build_Documentation)
The valid options for configure is:
../configure --enable-ctypes --disable-jemalloc
## Minimize a library size
You can minimize a icu56 library size by customizing a languages included in the data file icudt56.dll
It can be done using [ICU Data Library Customizer](http://apps.icu-project.org/datacustom/ICUData56.html)
Use a Advanced Options in the bottom of page to filter and deselect intems you not required
Our distribution include icudt56.dll WITHOUT
- Urdu
- Chinese
- Japanese
- Korean
- Zulu
- Vietnamese

View File

@@ -0,0 +1,86 @@
** CentOS / Ubuntu notes** If you need to use libmozjs-52 on bot CentOS / Ubuntu distribution we recommend to compile it on CentOS,
because Ubintu 16.04 LTS have newer version of glibc (3.4.21)
Before proceeding Spider Monkey build pease consider reading general
[Mozilla sources build instructuions](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions)
and author's recommended approach to [build Spider Monkey](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Build_Documentation).
You may also find usefull following hiperlinks in the documents and read more about Mozilla sources in general.
Basic knowledge of C/C++ language and linux build tools whould be helpfull when reading this documentation.
The "zero" step when building Mozilla sources is to prepare build box. Do this according to the
[Mozilla Linux_Prerequisites documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Linux_Prerequisites).
It worth to note that this instruction has been checked against Ubuntu GNOME v16.04. You may find some differences building Mozilla sources on another version of Linux.
At the moment of writing this instruction there was no official release of Spider Monkey v52.
At a later time you whould be able to find official sources of this version at [SpiderMonkey Releases page](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Releases).
Currently it is possible to download [nightly build from official repository](https://hg.mozilla.org/releases)
or use source control tools to retrive sources from any of the Mozilla source control repositories.
The author recommends to use git as a tool to take sources from GitHub.
Issue the following command in a terminal window to clone GitHub repository:
```bash
git clone https://github.com/mozilla/gecko-dev.git
```
Be prepared for long wait - cloning the repository may take up to half an hour or so depending on build box hardware and the Internet connection speed.
This retrieves all Mozilla sources. A few different programs can be built using this sources like FireFox or Thunder Bird.
But there is no way to retrive only part of the sources needed to build Spider Monkey only.
Upon finish dive into the root sources directory and checkout branch esr52. Please note - you should _not_ use master branch to build Spider Monkey v52!
```bash
cd gecko-dev
git checkout esr52
```
Now it's time to apply patch to the sources provided by SyNode:
```
git apply <mORMot_sources_root>/SyNode/mozjs/esr52-git.patch
```
Spider Monkey sources are located at "js\src":
```
cd js/src
```
Next run autoconfigure comand:
```
autoconf2.13
```
The next step is to crate the directory for build artifacts. It is recommended to use _OPT.OBJ directory for that (exact spelling),
it's due to current state of .gitignore file.
```
mkdir _OPT.OBJ
cd _OPT.OBJ
```
After that configure sources. This should be done by issuing the following command from the build artifacts directory:
```
../configure --enable-ctypes --disable-jemalloc --enable-nspr-build --disable-debug-symbols
```
The options stated should be used to successfully build and run SyNode.
And now run build itself:
```
make
```
Upon successfull build go to dist/bin subdirectory and take libmozjs-52.so library.
To avoid conflicts with a packaged version of this library the name have to be changed, but issuing mv command to rename the file is not enough.
Patchelf utility not less than version 0.9 should be used to change internal SONAME field:
```
patchelf --set-soname libsynmozjs52.so libmozjs-52.so
mv libmozjs-52.so libsynmozjs52.so
```
It is recommended to copy resulting libsynmozjs52.so file to `/usr/lib` directory

View File

@@ -0,0 +1,81 @@
# SpiderMonkey 52
Main information about SpiderMonkey is [here](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey)
## Build instruction
Official Mozilla instruction is [here](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Build_Documentation)
Let's describe the details
### Preparing
In first step you must install the next items:
- Visual Studio(2013 for SpiderMonkey 45, or 2015 for SpiderMonkey 52).
- [MozillaBuild](https://wiki.mozilla.org/MozillaBuild)
- [Mercurial](https://www.mercurial-scm.org/downloads). At the moment of writing this instruction last version(4.1) could not download source code.
The source code was downloaded by version 4.0
The next step is downloading SpiderMonkey source code:
- *(recommended method)* Download from [official release page](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Releases).
At the moment of writing this instruction, version 52 was absent in this page, so we could not use this method. But 45 version was present.
- Download directly from [repository](https://hg.mozilla.org/releases) *(the version may be unstable)*. It is placed at folder `mozilla-esr<version_number>`
### Modification of SpiderMonkey source code for Delphi compability
Our main task is to translate everything that will be imported to Delphi into `extern “C”` mode, and to write wrappers to things which cannot be translated
Usually it is enough to edit this files:
- js\public\Initialization.h
- js\src\jsfriendapi.h
- js\src\jsapi.h
by adding `extern "C"` to functions that we need, and in files:
- js\src\jsapi.h
- js\src\jsapi.cpp
we must to write the wrappers to functions which we cannot import directly(that ones which use class instances).
For realization in Delphi we need to have an idea about internal structure of SpiderMonkey instances.
The simplest way is to create C++ project in Visual Studio and run it in debug mode.
Then we can create variables of unclear types and examine them in "Watch" window.
Interesting for us information is `sizeof(variable)` and offset of addresses of fields of variables.
This information is enough to understand structure.
For working with objects which allocated in stack we need to write wrappers, which works with `void*` type and apply `reinterpret_cast` to them
For SpiderMonkey 52 we created a patch `Delphi patch 52.patch` (for version from repository, *for version from release page it may be could not be applied)
### Building
Then run MozillaBuild. It located in the directory where we install it (`C:\mozilla-build` by default). There are different *.bat files in this directory.
For SpiderMonkey 45 we need `start-shell-msvc2013.bat`(for 32-bit version) or `start-shell-msvc2013-x64.bat`(for 64-bit version)
For SpiderMonkey 52 we need `start-shell-msvc2015.bat`(for 32-bit version) or `start-shell-msvc2015-x64.bat`(for 64-bit version)
Now we can see MozillaBuild console.
Then we go to the source code folder(we downloaded them to `d:\mozilla-releases\mozilla-esr52` folder).
cd d:
cd mozilla-releases\mozilla-esr52
cd js\src
Then we recommend to create new folder for binary files location
mkdir obj
cd obj
For 64-bit version we create `obj64` folder
Then run build configurator:
../configure --enable-ctypes --disable-jemalloc [--disable-sm-promise]
For 64-bit version:
../configure --enable-ctypes --disable-jemalloc [--disable-sm-promise] --host=x86_64-pc-mingw32 --target=x86_64-pc-mingw32
`--enable-ctypes` activate [ctypes](https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes)(Mozilla realization of [ffi](https://github.com/ffi/ffi) in JS layer).
`--disable-jemalloc` deactivate jemalloc(memory manager). We use FastMM and if conflicts with jemalloc - there are AV on application exit.
`--disable-sm-promise` deactivate promises if you don't need them. At the moment of writing this instruction deactivation of promises fail build.
If you not deactivate promises you must set promises callbacs `SetEnqueuePromiseJobCallback`, `SetGetIncumbentGlobalCallback` and `SetAsyncTaskCallbacks` else you get AV when work with promise.
Then we can run build
mozmake.exe
After successfully build binary files will be located in `dist\bin` folder. We need to take all `*.dll` from here.
In order to be aligned with Linux version, the name of the mozjs-52.dll file should be changed to synmozjs52.dll

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
/// `os` module support bindings for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeBinding_os;
interface
{$I Synopse.inc}
{$I SyNode.inc}
uses
SysUtils,
SynCommons,
SyNode, SpiderMonkey;
implementation
function os_getHostname(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; cdecl;
begin
result := true;
try
vp.rval := cx.NewJSString(ExeVersion.Host).ToJSVal;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
function SyNodeBindingProc_os(const aEngine: TSMEngine; const bindingNamespaceName: SynUnicode): jsval;
var
obj: PJSRootedObject;
cx: PJSContext;
const
props = JSPROP_ENUMERATE or JSPROP_ENUMERATE or JSPROP_PERMANENT;
begin
cx := aEngine.cx;
obj := cx.NewRootedObject(cx.NewObject(nil));
try
obj.ptr.DefineFunction(cx, 'getHostname', os_getHostname, 0, props);
Result := obj.ptr.ToJSValue;
finally
cx.FreeRootedObject(obj);
end;
end;
initialization
TSMEngineManager.RegisterBinding('os', SyNodeBindingProc_os);
end.

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="core_res"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
<Modes Count="0"/>
</RunParams>
<Units Count="1">
<Unit0>
<Filename Value="core_res.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="core_res"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="../.."/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,121 @@
program core_res;
uses
Classes,
SysUtils,
SynCommons;
procedure removeCR(const Script: SynUnicode);
var
c: PWideChar;
i: Integer;
begin
c := pointer(script);
for I := 1 to Length(script) do begin
if (c^ = #13) then
c^ := ' ';
Inc(c);
end;
end;
Function FindCmdLineSwitchVal(const Switch: string; out Value: string): Boolean;
{$IFDEF FPC}
var
I, L: Integer;
S, T: String;
begin
Result := False;
S := Switch;
Value := '';
S := UpperCase(S);
I := ParamCount;
while (Not Result) and (I>0) do begin
L := Length(Paramstr(I));
if (L>0) and (ParamStr(I)[1] in SwitchChars) then begin
T := Copy(ParamStr(I),2,L-1);
T := UpperCase(T);
Result := S=T;
if Result and (I <> ParamCount) then
Value := ParamStr(I+1)
end;
Dec(i);
end;
end;
{$ELSE}
begin
Result := FindCmdLineSwitch(Switch, value);
end;
{$ENDIF}
var
L, R: TStringList;
fIn, fOut: TFileName;
function transform(const L: TStringList; wrap: boolean): boolean;
var
i: integer;
s: SynUnicode;
fn, folder: TFileName;
fsOut: TFileStream;
begin
for i := 0 to L.Count - 1 do begin
fn := L[i];
s := AnyTextFileToSynUnicode(fIn + fn, true);
if s = '' then begin
writeln('File not found', fIn + fn);
exit(false);
end;
removeCR(s);
if wrap then
s := '(function (exports, require, module, __filename, __dirname) { ' + s + #10'});';
folder := ExtractFileDir(fOut + fn);
if not ForceDirectories(folder) then begin
writeln('Can''t create output folder ', folder);
exit;
end;
try
fsOut := TFileStream.Create(fOut + fn, fmCreate);
fsOut.Write(s[1], length(s)*2);
fsOut.Free;
except
on E:Exception do begin
writeln('File ', fOut + fn, ' could not be created because: ', E.Message);
raise;
end;
end;
R.Add(fn + #9'RCDATA "' + fn + '"');
end;
Result := true;
end;
begin
ExitCode := -1;
if not FindCmdLineSwitchVal('i', fIn) or
not FindCmdLineSwitchVal('o', fOut) then
begin
writeln('Create a rc with files from modules_cjs.txt & modules_es6.txt');
writeln('Files is converted to the Unicode as expected by SpiderMonkey');
writeln('Usage: ');
writeln(#9'core_res -i path/to/in/folder -o path/to/out/folder');
exit;
end;
if not ForceDirectories(fOut) then begin
writeln('Can''t create output folder ', fOut);
exit;
end;
L := TStringList.Create;
R := TStringList.Create;
try
L.LoadFromFile('./modules_cjs.txt');
if not transform(L, true) then
exit;
L.LoadFromFile('./modules_es6.txt');
if not transform(L, false) then
exit;
R.SaveToFile(fOut + 'core_res.rc');
finally
L.Free; R.Free;
end;
ExitCode := 0;
end.