Added files for Juan

This commit is contained in:
2024-08-07 11:38:48 -07:00
parent f7878e3e60
commit 795d1d72ce
2871 changed files with 621552 additions and 0 deletions

16
ExACTRegressionScripts/node_modules/.bin/chromedriver generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../chromedriver/bin/chromedriver" "$@"
else
exec node "$basedir/../chromedriver/bin/chromedriver" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/chromedriver.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\chromedriver\bin\chromedriver" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../chromedriver/bin/chromedriver" $args
} else {
& "$basedir/node$exe" "$basedir/../chromedriver/bin/chromedriver" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../chromedriver/bin/chromedriver" $args
} else {
& "node$exe" "$basedir/../chromedriver/bin/chromedriver" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/crc32 generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../crc-32/bin/crc32.njs" "$@"
else
exec node "$basedir/../crc-32/bin/crc32.njs" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/crc32.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\crc-32\bin\crc32.njs" %*

28
ExACTRegressionScripts/node_modules/.bin/crc32.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../crc-32/bin/crc32.njs" $args
} else {
& "$basedir/node$exe" "$basedir/../crc-32/bin/crc32.njs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../crc-32/bin/crc32.njs" $args
} else {
& "node$exe" "$basedir/../crc-32/bin/crc32.njs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/escodegen generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../escodegen/bin/escodegen.js" "$@"
else
exec node "$basedir/../escodegen/bin/escodegen.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/escodegen.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\escodegen\bin\escodegen.js" %*

28
ExACTRegressionScripts/node_modules/.bin/escodegen.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../escodegen/bin/escodegen.js" $args
} else {
& "$basedir/node$exe" "$basedir/../escodegen/bin/escodegen.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../escodegen/bin/escodegen.js" $args
} else {
& "node$exe" "$basedir/../escodegen/bin/escodegen.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/esgenerate generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../escodegen/bin/esgenerate.js" "$@"
else
exec node "$basedir/../escodegen/bin/esgenerate.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/esgenerate.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\escodegen\bin\esgenerate.js" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../escodegen/bin/esgenerate.js" $args
} else {
& "$basedir/node$exe" "$basedir/../escodegen/bin/esgenerate.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../escodegen/bin/esgenerate.js" $args
} else {
& "node$exe" "$basedir/../escodegen/bin/esgenerate.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/esparse generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esprima/bin/esparse.js" "$@"
else
exec node "$basedir/../esprima/bin/esparse.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/esparse.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esprima\bin\esparse.js" %*

28
ExACTRegressionScripts/node_modules/.bin/esparse.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esprima/bin/esparse.js" $args
} else {
& "$basedir/node$exe" "$basedir/../esprima/bin/esparse.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esprima/bin/esparse.js" $args
} else {
& "node$exe" "$basedir/../esprima/bin/esparse.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/esvalidate generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esprima/bin/esvalidate.js" "$@"
else
exec node "$basedir/../esprima/bin/esvalidate.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/esvalidate.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esprima\bin\esvalidate.js" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
} else {
& "$basedir/node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
} else {
& "node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/extract-zip generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../extract-zip/cli.js" "$@"
else
exec node "$basedir/../extract-zip/cli.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/extract-zip.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\extract-zip\cli.js" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../extract-zip/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../extract-zip/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../extract-zip/cli.js" $args
} else {
& "node$exe" "$basedir/../extract-zip/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/geckodriver generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../geckodriver/bin/geckodriver.js" "$@"
else
exec node "$basedir/../geckodriver/bin/geckodriver.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/geckodriver.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\geckodriver\bin\geckodriver.js" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../geckodriver/bin/geckodriver.js" $args
} else {
& "$basedir/node$exe" "$basedir/../geckodriver/bin/geckodriver.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../geckodriver/bin/geckodriver.js" $args
} else {
& "node$exe" "$basedir/../geckodriver/bin/geckodriver.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/mime generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mime/cli.js" "$@"
else
exec node "$basedir/../mime/cli.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/mime.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mime\cli.js" %*

28
ExACTRegressionScripts/node_modules/.bin/mime.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mime/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mime/cli.js" $args
} else {
& "node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/node-which generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../which/bin/which.js" "$@"
else
exec node "$basedir/../which/bin/which.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/node-which.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\which.js" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../which/bin/which.js" $args
} else {
& "$basedir/node$exe" "$basedir/../which/bin/which.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../which/bin/which.js" $args
} else {
& "node$exe" "$basedir/../which/bin/which.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/nodemon generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../nodemon/bin/nodemon.js" "$@"
else
exec node "$basedir/../nodemon/bin/nodemon.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/nodemon.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nodemon\bin\nodemon.js" %*

28
ExACTRegressionScripts/node_modules/.bin/nodemon.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
} else {
& "$basedir/node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
} else {
& "node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/nodetouch generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../touch/bin/nodetouch.js" "$@"
else
exec node "$basedir/../touch/bin/nodetouch.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/nodetouch.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\touch\bin\nodetouch.js" %*

28
ExACTRegressionScripts/node_modules/.bin/nodetouch.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../touch/bin/nodetouch.js" $args
} else {
& "$basedir/node$exe" "$basedir/../touch/bin/nodetouch.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../touch/bin/nodetouch.js" $args
} else {
& "node$exe" "$basedir/../touch/bin/nodetouch.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/semver generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
else
exec node "$basedir/../semver/bin/semver.js" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/semver.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*

28
ExACTRegressionScripts/node_modules/.bin/semver.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
ExACTRegressionScripts/node_modules/.bin/xlsx generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../xlsx/bin/xlsx.njs" "$@"
else
exec node "$basedir/../xlsx/bin/xlsx.njs" "$@"
fi

17
ExACTRegressionScripts/node_modules/.bin/xlsx.cmd generated vendored Executable file
View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\xlsx\bin\xlsx.njs" %*

28
ExACTRegressionScripts/node_modules/.bin/xlsx.ps1 generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../xlsx/bin/xlsx.njs" $args
} else {
& "$basedir/node$exe" "$basedir/../xlsx/bin/xlsx.njs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../xlsx/bin/xlsx.njs" $args
} else {
& "node$exe" "$basedir/../xlsx/bin/xlsx.njs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

2288
ExACTRegressionScripts/node_modules/.package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

View File

@@ -0,0 +1,19 @@
name: Tests
on: [push, pull_request]
jobs:
test:
name: 'Test on Node.js v${{ matrix.node }} / ${{ matrix.os }}'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
node: [10, 12, 14, 16]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm test

View File

@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at benji@testim.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018-2019 Testim.io
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.

View File

@@ -0,0 +1,51 @@
const { execSync } = require('child_process');
const path = require('path').posix;
const { canAccess, newLineRegex, sort } = require('./util');
function darwin(includeChromium = false) {
const suffixes = [
// '/Contents/MacOS/Google Chrome Canary',
'/Contents/MacOS/Google Chrome',
... includeChromium ? ['/Contents/MacOS/Chromium'] : []
];
const LSREGISTER = '/System/Library/Frameworks/CoreServices.framework' +
'/Versions/A/Frameworks/LaunchServices.framework' +
'/Versions/A/Support/lsregister';
const installations = [];
execSync(
`${LSREGISTER} -dump` +
' | grep -E -i \'(google chrome( canary)?' + (includeChromium ? '|chromium' : '') + ').app(\\s\\(0x[0-9a-f]+\\))?$\'' +
' | awk \'sub(/\\(0x[0-9a-f]+\\)/, "")\'' +
' | awk \'{$1=""; print $0}\'' +
' | awk \'{ gsub(/^[ \\t]+|[ \\t]+$/, ""); print }\'')
.toString()
.split(newLineRegex)
.forEach((inst) => {
suffixes.forEach(suffix => {
const execPath = path.join(inst.trim(), suffix);
if (canAccess(execPath)) {
installations.push(execPath);
}
});
});
// Retains one per line to maintain readability.
const priorities = [
{ regex: new RegExp(`^${process.env.HOME}/Applications/.*Chromium.app`), weight: 49 },
{ regex: new RegExp(`^${process.env.HOME}/Applications/.*Chrome.app`), weight: 50 },
// { regex: new RegExp(`^${process.env.HOME}/Applications/.*Chrome Canary.app`), weight: 51 },
{ regex: /^\/Applications\/.*Chromium.app/, weight: 99 },
{ regex: /^\/Applications\/.*Chrome.app/, weight: 100 },
// { regex: /^\/Applications\/.*Chrome Canary.app/, weight: 101 },
{ regex: /^\/Volumes\/.*Chromium.app/, weight: -3 },
{ regex: /^\/Volumes\/.*Chrome.app/, weight: -2 },
// { regex: /^\/Volumes\/.*Chrome Canary.app/, weight: -1 }
];
return sort(installations, priorities);
}
module.exports = darwin;

View File

@@ -0,0 +1,9 @@
/**
* find a executable chrome for all support system
* @returns {string} executable chrome full path
* @throws
* if no executable chrome find, ERROR_NO_INSTALLATIONS_FOUND will be throw
* if platform is not one if ['win32','darwin','linux'], ERROR_PLATFORM_NOT_SUPPORT will be throw
*/
declare function findChrome(): string;
export = findChrome;

View File

@@ -0,0 +1,39 @@
'use strict';
const ERROR_PLATFORM_NOT_SUPPORT = new Error('platform not support');
const ERROR_NO_INSTALLATIONS_FOUND = new Error('no chrome installations found');
/**
* Find a executable Chrome (or Chromium) for all supported systems.
*
* Supports macOS, Linux, and Windows.
*
* @param {boolean} includeChromium true if we should consider Chromium in our search, false otherwise.
* @returns {string} the first full path to an executable Chrome (or Chromium)
* @throws
* if no executable Chrome (or Chromium) find, ERROR_NO_INSTALLATIONS_FOUND will be throw
* if platform is not one if ['win32','darwin','linux'], ERROR_PLATFORM_NOT_SUPPORT will be throw
*/
function findChrome(includeChromium = false) {
const { platform } = process;
let installations = [];
switch (platform) {
case 'win32':
installations = require('./win32')(includeChromium);
break;
case 'darwin':
installations = require('./darwin')(includeChromium);
break;
case 'linux':
installations = require('./linux')(includeChromium);
break;
default:
throw ERROR_PLATFORM_NOT_SUPPORT;
}
if (installations.length) {
return installations[0];
} else {
throw ERROR_NO_INSTALLATIONS_FOUND;
}
}
module.exports = findChrome;

View File

@@ -0,0 +1,112 @@
const { execSync, execFileSync } = require('child_process');
const path = require('path').posix;
const fs = require('fs');
const { canAccess, sort, isExecutable, newLineRegex } = require('./util');
function findChromeExecutablesForLinuxDesktop(folder, includeChromium = false) {
const argumentsRegex = /(^[^ ]+).*/; // Take everything up to the first space
const chromeExecRegex = '^Exec=\/.*\/(google|chrome' + (includeChromium ? '|chromium' : '') + ')-.*';
let installations = [];
if (canAccess(folder)) {
// Output of the grep & print looks like:
// /opt/google/chrome/google-chrome --profile-directory
// /home/user/Downloads/chrome-linux/chrome-wrapper %U
let execPaths;
execPaths = execSync(`find "${folder}" -type f -exec grep -E "${chromeExecRegex}" "{}" \\; | awk -F '=' '{print $2}'`);
execPaths = execPaths
.toString()
.split(newLineRegex)
.map((execPath) => execPath.replace(argumentsRegex, '$1'));
execPaths.forEach((execPath) => canAccess(execPath) && installations.push(execPath));
}
return installations;
}
function findChromeExecutablesForLinux(validChromePaths, includeChromium = false) {
const executables = [
'google-chrome-stable',
'google-chrome',
... includeChromium ? ['chromium', 'chromium-browser', 'chromium/chrome'] : [] // chromium/chrome is for toradex machines where "chromium" is a directory. seen on Angstrom v2016.12
];
return executables.map(executable => {
const existingPaths = validChromePaths.map(possiblePath => {
try {
const chromePathToTest = possiblePath + '/' + executable;
if (fs.existsSync(chromePathToTest) && canAccess(chromePathToTest) && isExecutable(chromePathToTest)) {
return [ chromePathToTest ];
}
} catch (err) {
// not installed on this path or inaccessible
}
return [];
}).reduce((acc, val) => acc.concat(val), []); //.filter((foundChromePath) => foundChromePath);
// skip asking "which" command if the binary was found by searching the known paths.
if (existingPaths && existingPaths.length > 0) {
return existingPaths;
}
try {
const chromePath = execFileSync('which', [executable]).toString().split(newLineRegex)[0];
if (canAccess(chromePath)) {
return [ chromePath ];
}
} catch (err) {
// cmd which not installed.
}
return [];
}).reduce((acc, val) => acc.concat(val), []);
}
/**
* Look for linux executables in 2 ways
* 1. Look into the directories where .desktop are saved on gnome based distro's
* 2. Look for google-chrome-stable and google-chrome executables by using the which command
* If includeChromium is set, also look for chromium, chromium-browser, and chromium/chrome executables by using the which command
*/
function linux(includeChromium = false) {
let installations = [];
// 1. Look into the directories where .desktop are saved on gnome based distro's
const desktopInstallationFolders = [
path.join(require('os').homedir(), '.local/share/applications/'),
'/usr/share/applications/',
];
desktopInstallationFolders.forEach(folder => {
installations = installations.concat(findChromeExecutablesForLinuxDesktop(folder, includeChromium));
});
// 2. Look for google-chrome-stable and google-chrome (and optionally chromium, chromium-browser, and chromium-chrome) executables by using the which command
// see http://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/
const validChromePaths = [
'/usr/bin',
'/usr/local/bin',
'/usr/sbin',
'/usr/local/sbin',
'/opt/bin',
'/usr/bin/X11',
'/usr/X11R6/bin'
];
installations = installations.concat(findChromeExecutablesForLinux(validChromePaths, includeChromium));
const priorities = [
{ regex: /chromium$/, weight: 52 },
{ regex: /chrome-wrapper$/, weight: 51 },
{ regex: /google-chrome-stable$/, weight: 50 },
{ regex: /google-chrome$/, weight: 49 },
{ regex: /chromium-browser$/, weight: 48 },
{ regex: /chrome$/, weight: 47 },
];
return sort(Array.from(new Set(installations.filter(Boolean))), priorities);
}
module.exports = linux;

View File

@@ -0,0 +1,56 @@
const fs = require('fs');
const newLineRegex = /\r?\n/;
function sort(installations, priorities) {
const defaultPriority = 10;
// assign priorities
return installations
.map((inst) => {
for (const pair of priorities) {
if (pair.regex.test(inst)) {
return { path: inst, weight: pair.weight };
}
}
return { path: inst, weight: defaultPriority };
})
// sort based on priorities
.sort((a, b) => (b.weight - a.weight))
// remove priority flag
.map(pair => pair.path);
}
function canAccess(file) {
if (!file) {
return false;
}
try {
fs.accessSync(file);
return true;
} catch (e) {
return false;
}
}
function isExecutable(file) {
if (!file) {
return false;
}
try {
var stat = fs.statSync(file);
return stat && typeof stat.isFile === "function" && stat.isFile();
} catch (e) {
return false;
}
}
module.exports = {
sort,
canAccess,
isExecutable,
newLineRegex,
}

View File

@@ -0,0 +1,30 @@
const path = require('path').win32;
const { canAccess } = require('./util');
const procesEnv = process.env;
function win32(includeChromium = false) {
const installations = [];
const suffixes = [
'\\Google\\Chrome SxS\\Application\\chrome.exe',
'\\Google\\Chrome\\Application\\chrome.exe',
'\\chrome-win32\\chrome.exe',
... includeChromium ? ['\\Chromium\\Application\\chrome.exe'] : [],
// '\\Google\\Chrome Beta\\Application\\chrome.exe',
];
const prefixes = [
procesEnv.LOCALAPPDATA,
procesEnv.PROGRAMFILES,
procesEnv['PROGRAMFILES(X86)']
].filter(prefix => prefix); // filter out undefined
prefixes.forEach(prefix => suffixes.forEach(suffix => {
const chromePath = path.join(prefix, suffix);
if (canAccess(chromePath)) {
installations.push(chromePath);
}
}));
return installations;
}
module.exports = win32;

View File

@@ -0,0 +1,6 @@
(async () => {
const { getChromeVersion } = require('./index');
const version = await getChromeVersion();
console.log(version);
})();

View File

@@ -0,0 +1,130 @@
'use strict';
const findChrome = require('./chrome-finder');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const path = require('path');
const readdir = util.promisify(require('fs').readdir);
const { execSync } = require('child_process');
async function getChromeVersionFromCli(includeChromium) {
let chromePath;
try {
chromePath = findChrome(includeChromium);
} catch (err) {
return null;
}
const res = await exec(chromePath.replace(/ /g, '\\ ') + ' --version');
const version = extractChromeVersionNumer(res.stdout);
return version;
}
function extractChromeVersionNumer(chromeVersionString) {
return chromeVersionString.replace(/\D*(([0-9]+\.?)+)\s?.*/, '$1');
}
const chromeVersionRegex = /^(\d+\.){3}\d+$/g;
async function getChromeVersionWin(includeChromium) {
let chromePath;
try {
chromePath = findChrome(includeChromium);
} catch (err) {
return null;
}
const powershell = await execAndAttemptExtractingChromeVersion(`powershell -command "&{(Get-Item '${chromePath}').VersionInfo.ProductVersion}"`);
if (powershell !== null) {
return powershell;
}
const registry = await execAndAttemptExtractingChromeVersion('reg query "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon" /v version');
if (registry !== null) {
return registry;
}
const versionPath = path.dirname(chromePath);
const contents = await readdir(versionPath);
const versions = contents.filter(a => chromeVersionRegex.test(a));
// returning oldest in case there is an updated version and chrome still hasn't relaunched
const oldest = versions.sort((a, b) => a > b)[0];
return oldest;
}
async function execAndAttemptExtractingChromeVersion(command) {
try {
const { stdout } = await exec(command);
const version = extractChromeVersionNumer(stdout).trim();
if (chromeVersionRegex.test(version)) {
return version;
}
} catch (err) { ''; }
return null;
}
function getChromeVersionFromOsa(includeChromium) {
try {
const version = execSync('osascript -e \'tell application "Google Chrome" to get version\'').toString().trim();
return version;
} catch (err) {
if (!includeChromium) {
return null;
}
// else fall-through to check for Chromium below
}
try {
const version = execSync('osascript -e \'tell application "Chromium" to get version\'').toString().trim();
return version;
} catch (err) {
return null;
}
}
async function innerGetChromeVersion(includeChromium = false) {
const os = process.platform;
if (os === 'darwin') return getChromeVersionFromOsa(includeChromium);
if (os === 'linux') return getChromeVersionFromCli(includeChromium);
if (os.includes('win')) return getChromeVersionWin(includeChromium);
console.log(`${os} is not supported`);
return null;
}
/**
* Gets the version of Chrome (or Chromium) that is installed.
*
* Supports macOS, Linux, and Windows.
*
* @param {boolean} includeChromium true if we should consider Chromium in our search, false otherwise.
* @returns {string} the version number of Chrome (or Chromium), or null if the OS is not supported.
*/
async function getChromeVersion(includeChromium = false) {
const res = await innerGetChromeVersion(includeChromium);
if (typeof res === 'string') {
return res.trim();
}
return res;
}
if (require.main == module) {
getChromeVersion().then(v => console.log(v));
}
module.exports = {
getChromeVersion
};

View File

@@ -0,0 +1,27 @@
{
"name": "@testim/chrome-version",
"version": "1.1.4",
"description": "Finds the version of Chrome that is installed on your machine",
"main": "index.js",
"scripts": {
"lint": "eslint *.js --fix",
"test": "mocha"
},
"repository": {
"type": "git",
"url": "https://github.com/testimio/chrome-version.git"
},
"author": "Testim",
"license": "MIT",
"homepage": "https://github.com/testimio/chrome-version",
"keywords": [
"chrome",
"version"
],
"devDependencies": {
"chai": "4.3.4",
"eslint": "8.7.0",
"mocha": "9.1.4",
"rewire": "6.0.0"
}
}

View File

@@ -0,0 +1,27 @@
# chrome-version
[![Build Status](https://github.com/testimio/chrome-version/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/testimio/chrome-version/actions/workflows/ci.yml)
Finds the version of Chrome (or Chromium) that is installed on your machine.
## Installation
```sh
npm install @testim/chrome-version
```
## Use
```js
(async () => {
const { getChromeVersion } = require('@testim/chrome-version');
const includeChromium = false; // NOTE: set to true to also search for Chromium
const version = await getChromeVersion(includeChromium);
console.log(version);
})();
```
## Testing
```sh
npm test
```
* If no version of chrome is installed on your machine `getChromeVersion` will return null.

View File

@@ -0,0 +1,370 @@
const chai = require('chai');
const expect = chai.expect;
const Buffer = require('buffer').Buffer;
// rewrite to get access for testing
const rewire = require("rewire");
const chromeVersionModule = rewire("../index.js");
const extractChromeVersionNumer = chromeVersionModule.__get__('extractChromeVersionNumer');
const getChromeVersionFromOsa = chromeVersionModule.__get__('getChromeVersionFromOsa');
const chromeFinderLinuxModule = rewire("../chrome-finder/linux.js");
const findChromeExecutablesForLinuxDesktop = chromeFinderLinuxModule.__get__('findChromeExecutablesForLinuxDesktop');
const findChromeExecutablesForLinux = chromeFinderLinuxModule.__get__('findChromeExecutablesForLinux');
const chromeFinderDarwinModule = rewire("../chrome-finder/darwin.js");
const findChromeForDarwin = chromeFinderDarwinModule.__get__('darwin');
const chromeFinderWin32Module = rewire("../chrome-finder/win32.js");
const findChromeForWin32 = chromeFinderWin32Module.__get__('win32');
describe('Chrome Finder', function() {
describe('extractChromeVersionNumer from Google Chrome 95.0.4638', function() {
it('should return "95.0.4638"', function() {
const versionString = 'Google Chrome 95.0.4638';
const versionNumber = extractChromeVersionNumer(versionString);
expect(versionNumber).to.equal('95.0.4638');
});
});
describe('extractChromeVersionNumer from Google Chrome 96.0.4664.110', function() {
it('should return "96.0.4664.110"', function() {
const versionString = 'Google Chrome 96.0.4664.110';
const versionNumber = extractChromeVersionNumer(versionString);
expect(versionNumber).to.equal('96.0.4664.110');
});
});
describe('extractChromeVersionNumer from Google Chrome 97.0.4692.71', function() {
it('should return "97.0.4692.71"', function() {
const versionString = 'Google Chrome 97.0.4692.71';
const versionNumber = extractChromeVersionNumer(versionString);
expect(versionNumber).to.equal('97.0.4692.71');
});
});
describe('extractChromiumVersionNumer from Chromium 105.0.5195.102 built on Debian 11.4, running on Debian 11.5', function() {
it('should return "105.0.5195.102"', function() {
const versionString = 'Chromium 105.0.5195.102 built on Debian 11.4, running on Debian 11.5';
const versionNumber = extractChromeVersionNumer(versionString);
expect(versionNumber).to.equal('105.0.5195.102');
});
});
describe('extractChromiumVersionNumer from Chromium 90.0.4430.212 Fedora Project', function() {
it('should return "90.0.4430.212"', function() {
const versionString = 'Chromium 90.0.4430.212 Fedora Project';
const versionNumber = extractChromeVersionNumer(versionString);
expect(versionNumber).to.equal('90.0.4430.212');
});
});
describe('extractChromiumVersionNumer from Chromium 98.0.4753.0', function() {
it('should return "98.0.4753.0"', function() {
const versionString = 'Chromium 98.0.4753.0';
const versionNumber = extractChromeVersionNumer(versionString);
expect(versionNumber).to.equal('98.0.4753.0');
});
});
describe('getChromeVersionFromOsa when includeChromium=false', function() {
it('should only find Chrome', function() {
const includeChromium = false;
const mockVersion = '97.0.4692.71';
var execSyncCommand;
chromeVersionModule.__set__('execSync', function(command) {
execSyncCommand = command;
return Buffer.from(mockVersion);
});
const version = getChromeVersionFromOsa(includeChromium);
expect(execSyncCommand).to.include('Google Chrome');
expect(execSyncCommand).to.not.include('Chromium');
expect(version).to.equal(mockVersion);
});
});
describe('getChromeVersionFromOsa when includeChromium=true', function() {
it('should only find Chromium', function() {
const includeChromium = true;
const mockVersion = '98.0.4753.0';
var execSyncCommands = [];
chromeVersionModule.__set__('execSync', function(command) {
execSyncCommands.push(command);
if (execSyncCommands.length == 1) {
throw "not found"
}
return Buffer.from(mockVersion);
});
const version = getChromeVersionFromOsa(includeChromium);
expect(execSyncCommands.length).equal(2);
expect(execSyncCommands[0]).to.include('Google Chrome');
expect(execSyncCommands[1]).to.include('Chromium');
expect(version).to.equal(mockVersion);
});
});
});
describe('Chrome Finder Linux Module', function() {
describe('Linux Desktop when includeChromium=false', function() {
it('should only find Chrome', function() {
const includeChromium = false;
const mockChromePaths = [
'/opt/google/chrome/google-chrome',
'/home/user/Downloads/chrome-linux/chrome-wrapper'
];
chromeFinderLinuxModule.__set__('canAccess', function(file) {
return true;
});
var execSyncCommand;
chromeFinderLinuxModule.__set__('execSync', function(command) {
execSyncCommand = command;
return Buffer.from(mockChromePaths.join('\n'));
});
const mockDesktopFolder = "mock-desktop-folder";
const executables = findChromeExecutablesForLinuxDesktop(mockDesktopFolder, includeChromium);
expect(execSyncCommand).to.include(mockDesktopFolder);
expect(execSyncCommand).to.not.include('|chromium');
expect(executables).to.have.lengthOf(2);
expect(executables).to.deep.equal(mockChromePaths);
});
});
describe('Linux Desktop when includeChromium=true', function() {
it('should find Chrome and Chromium', function() {
const includeChromium = true;
const mockChromePaths = [
'/opt/google/chrome/google-chrome',
'/home/user/Downloads/chrome-linux/chrome-wrapper',
'/usr/local/bin/chromium-browser'
];
chromeFinderLinuxModule.__set__('canAccess', function(file) {
return true;
});
var execSyncCommand;
chromeFinderLinuxModule.__set__('execSync', function(command) {
execSyncCommand = command;
return Buffer.from(mockChromePaths.join('\n'));
});
const mockDesktopFolder = "mock-desktop-folder";
const executables = findChromeExecutablesForLinuxDesktop(mockDesktopFolder, includeChromium);
expect(execSyncCommand).to.include(mockDesktopFolder);
expect(execSyncCommand).to.include('|chromium');
expect(executables).to.have.lengthOf(3);
expect(executables).to.deep.equal(mockChromePaths);
});
});
describe('Linux when includeChromium=false', function() {
it('should only find Chrome', function() {
const includeChromium = false;
const mockPaths = [
'/mock-path'
];
chromeFinderLinuxModule.__set__('fs', {
existsSync: function(file) {
return true;
}
});
chromeFinderLinuxModule.__set__('canAccess', function(file) {
return true;
});
chromeFinderLinuxModule.__set__('isExecutable', function(file) {
return true;
});
const executables = findChromeExecutablesForLinux(mockPaths, includeChromium);
expect(executables).to.have.lengthOf(2);
const expectedExecutables = mockPaths.map(mockPath => ['google-chrome-stable', 'google-chrome'].map(executable => mockPath + '/' + executable)).reduce((acc, val) => acc.concat(val), []);
expect(executables).to.deep.equal(expectedExecutables);
});
});
describe('Linux when includeChromium=true', function() {
it('should find Chrome and Chromium', function() {
const includeChromium = true;
const mockPaths = [
'/mock-path'
];
chromeFinderLinuxModule.__set__('fs', {
existsSync: function(file) {
return true;
}
});
chromeFinderLinuxModule.__set__('canAccess', function(file) {
return true;
});
chromeFinderLinuxModule.__set__('isExecutable', function(file) {
return true;
});
const executables = findChromeExecutablesForLinux(mockPaths, includeChromium);
expect(executables).to.have.lengthOf(5);
const expectedExecutables = mockPaths.map(mockPath => ['google-chrome-stable', 'google-chrome', 'chromium', 'chromium-browser', 'chromium/chrome'].map(executable => mockPath + '/' + executable)).reduce((acc, val) => acc.concat(val), []);
expect(executables).to.deep.equal(expectedExecutables);
});
});
});
describe('Chrome Finder Darwin Module', function() {
describe('Darwin when includeChromium=false', function() {
it('should only find Chrome', function() {
const includeChromium = false;
const mockChromePaths = [
'/Applications/Google Chrome.app'
];
chromeFinderDarwinModule.__set__('canAccess', function(file) {
return true;
});
var execSyncCommand;
chromeFinderDarwinModule.__set__('execSync', function(command) {
execSyncCommand = command;
return Buffer.from(mockChromePaths.join('\n'));
});
const executables = findChromeForDarwin(includeChromium);
expect(execSyncCommand).to.not.include('|chromium');
expect(executables).to.have.lengthOf(1);
expect(executables).to.deep.equal(mockChromePaths.map(mockChromePath => mockChromePath + '/Contents/MacOS/Google Chrome'));
});
});
describe('Darwin when includeChromium=true', function() {
it('should only find Chrome', function() {
const includeChromium = true;
const mockChromePaths = [
'/Applications/Google Chrome.app',
'/Applications/Chromium.app'
];
const expectedExecutables = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium'
];
chromeFinderDarwinModule.__set__('canAccess', function(file) {
return expectedExecutables.includes(file);
});
var execSyncCommand;
chromeFinderDarwinModule.__set__('execSync', function(command) {
execSyncCommand = command;
return Buffer.from(mockChromePaths.join('\n'));
});
const executables = findChromeForDarwin(includeChromium);
expect(execSyncCommand).to.include('|chromium');
expect(executables).to.have.lengthOf(2);
expect(executables).to.deep.equal(expectedExecutables);
});
});
});
describe('Chrome Finder Win32 Module', function() {
describe('Win32 when includeChromium=false', function() {
it('should only find Chrome', function() {
const includeChromium = false;
chromeFinderWin32Module.__set__('canAccess', function(file) {
return true;
});
chromeFinderWin32Module.__set__('procesEnv', {
LOCALAPPDATA: 'mock-local-app-data',
PROGRAMFILES: 'mock-program-files',
'PROGRAMFILES(X86)': 'mock-program-files-x86'
});
const executables = findChromeForWin32(includeChromium);
expect(executables).to.have.lengthOf(9);
expect(executables).to.deep.equal([
'mock-local-app-data\\Google\\Chrome SxS\\Application\\chrome.exe',
'mock-local-app-data\\Google\\Chrome\\Application\\chrome.exe',
'mock-local-app-data\\chrome-win32\\chrome.exe',
'mock-program-files\\Google\\Chrome SxS\\Application\\chrome.exe',
'mock-program-files\\Google\\Chrome\\Application\\chrome.exe',
'mock-program-files\\chrome-win32\\chrome.exe',
'mock-program-files-x86\\Google\\Chrome SxS\\Application\\chrome.exe',
'mock-program-files-x86\\Google\\Chrome\\Application\\chrome.exe',
'mock-program-files-x86\\chrome-win32\\chrome.exe'
]);
});
});
describe('Win32 when includeChromium=true', function() {
it('should only find Chrome', function() {
const includeChromium = true;
chromeFinderWin32Module.__set__('canAccess', function(file) {
return true;
});
chromeFinderWin32Module.__set__('procesEnv', {
LOCALAPPDATA: 'mock-local-app-data',
PROGRAMFILES: 'mock-program-files',
'PROGRAMFILES(X86)': 'mock-program-files-x86'
});
const executables = findChromeForWin32(includeChromium);
expect(executables).to.have.lengthOf(12);
expect(executables).to.deep.equal([
'mock-local-app-data\\Google\\Chrome SxS\\Application\\chrome.exe',
'mock-local-app-data\\Google\\Chrome\\Application\\chrome.exe',
'mock-local-app-data\\chrome-win32\\chrome.exe',
'mock-local-app-data\\Chromium\\Application\\chrome.exe',
'mock-program-files\\Google\\Chrome SxS\\Application\\chrome.exe',
'mock-program-files\\Google\\Chrome\\Application\\chrome.exe',
'mock-program-files\\chrome-win32\\chrome.exe',
'mock-program-files\\Chromium\\Application\\chrome.exe',
'mock-program-files-x86\\Google\\Chrome SxS\\Application\\chrome.exe',
'mock-program-files-x86\\Google\\Chrome\\Application\\chrome.exe',
'mock-program-files-x86\\chrome-win32\\chrome.exe',
'mock-program-files-x86\\Chromium\\Application\\chrome.exe'
]);
});
});
});

View File

@@ -0,0 +1,21 @@
MIT License
quickjs-emscripten copyright (c) 2019 Jake Teton-Landis
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.

View File

@@ -0,0 +1,597 @@
# quickjs-emscripten
Javascript/Typescript bindings for QuickJS, a modern Javascript interpreter,
compiled to WebAssembly.
- Safely evaluate untrusted Javascript (up to ES2020).
- Create and manipulate values inside the QuickJS runtime ([more][values]).
- Expose host functions to the QuickJS runtime ([more][functions]).
- Execute synchronous code that uses asynchronous functions, with [asyncify][asyncify].
[Github] | [NPM] | [API Documentation][api] | [Examples][tests]
```typescript
import { getQuickJS } from "quickjs-emscripten"
async function main() {
const QuickJS = await getQuickJS()
const vm = QuickJS.newContext()
const world = vm.newString("world")
vm.setProp(vm.global, "NAME", world)
world.dispose()
const result = vm.evalCode(`"Hello " + NAME + "!"`)
if (result.error) {
console.log("Execution failed:", vm.dump(result.error))
result.error.dispose()
} else {
console.log("Success:", vm.dump(result.value))
result.value.dispose()
}
vm.dispose()
}
main()
```
[github]: https://github.com/justjake/quickjs-emscripten
[npm]: https://www.npmjs.com/package/quickjs-emscripten
[api]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md
[tests]: https://github.com/justjake/quickjs-emscripten/blob/main/ts/quickjs.test.ts
[values]: #interfacing-with-the-interpreter
[asyncify]: #asyncify
[functions]: #exposing-apis
## Usage
Install from `npm`: `npm install --save quickjs-emscripten` or `yarn add quickjs-emscripten`.
The root entrypoint of this library is the `getQuickJS` function, which returns
a promise that resolves to a [QuickJS singleton](./doc/classes/quickjs.md) when
the QuickJS WASM module is ready.
Once `getQuickJS` has been awaited at least once, you also can use the `getQuickJSSync`
function to directly access the singleton engine in your synchronous code.
### Safely evaluate Javascript code
See [QuickJS.evalCode](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/quickjs.md#evalcode)
```typescript
import { getQuickJS, shouldInterruptAfterDeadline } from "quickjs-emscripten"
getQuickJS().then((QuickJS) => {
const result = QuickJS.evalCode("1 + 1", {
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000),
memoryLimitBytes: 1024 * 1024,
})
console.log(result)
})
```
### Interfacing with the interpreter
You can use [QuickJSContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/QuickJSContext.md)
to build a scripting environment by modifying globals and exposing functions
into the QuickJS interpreter.
Each `QuickJSContext` instance has its own environment -- globals, built-in
classes -- and actions from one context won't leak into other contexts or
runtimes (with one exception, see [Asyncify][asyncify]).
Every context is created inside a
[QuickJSRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/QuickJSRuntime.md).
A runtime represents a Javascript heap, and you can even share values between
contexts in the same runtime.
```typescript
const vm = QuickJS.newContext()
let state = 0
const fnHandle = vm.newFunction("nextId", () => {
return vm.newNumber(++state)
})
vm.setProp(vm.global, "nextId", fnHandle)
fnHandle.dispose()
const nextId = vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`))
console.log("vm result:", vm.getNumber(nextId), "native state:", state)
nextId.dispose()
vm.dispose()
```
When you create a context from a top-level API like in the example above,
instead of by calling `runtime.newContext()`, a runtime is automatically created
for the lifetime of the context, and disposed of when you dispose the context.
#### Runtime
The runtime has APIs for CPU and memory limits that apply to all contexts within
the runtime in aggregate. You can also use the runtime to configure EcmaScript
module loading.
```typescript
const runtime = QuickJS.newRuntime()
// "Should be enough for everyone" -- attributed to B. Gates
runtime.setMemoryLimit(1024 * 640)
// Limit stack size
runtime.setMaxStackSize(1024 * 320)
// Interrupt computation after 1024 calls to the interrupt handler
let interruptCycles = 0
runtime.setInterruptHandler(() => ++interruptCycles > 1024)
// Toy module system that always returns the module name
// as the default export
runtime.setModuleLoader((moduleName) => `export default '${moduleName}'`)
const context = runtime.newContext()
const ok = context.evalCode(`
import fooName from './foo.js'
globalThis.result = fooName
`)
context.unwrapResult(ok).dispose()
// logs "foo.js"
console.log(context.getProp(context.global, "result").consume(context.dump))
context.dispose()
runtime.dispose()
```
### Memory Management
Many methods in this library return handles to memory allocated inside the
WebAssembly heap. These types cannot be garbage-collected as usual in
Javascript. Instead, you must manually manage their memory by calling a
`.dispose()` method to free the underlying resources. Once a handle has been
disposed, it cannot be used anymore. Note that in the example above, we call
`.dispose()` on each handle once it is no longer needed.
Calling `QuickJSContext.dispose()` will throw a RuntimeError if you've forgotten to
dispose any handles associated with that VM, so it's good practice to create a
new VM instance for each of your tests, and to call `vm.dispose()` at the end
of every test.
```typescript
const vm = QuickJS.newContext()
const numberHandle = vm.newNumber(42)
// Note: numberHandle not disposed, so it leaks memory.
vm.dispose()
// throws RuntimeError: abort(Assertion failed: list_empty(&rt->gc_obj_list), at: quickjs/quickjs.c,1963,JS_FreeRuntime)
```
Here are some strategies to reduce the toil of calling `.dispose()` on each
handle you create:
#### Scope
A
[`Scope`](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/scope.md#class-scope)
instance manages a set of disposables and calls their `.dispose()`
method in the reverse order in which they're added to the scope. Here's the
"Interfacing with the interpreter" example re-written using `Scope`:
```typescript
Scope.withScope((scope) => {
const vm = scope.manage(QuickJS.newContext())
let state = 0
const fnHandle = scope.manage(
vm.newFunction("nextId", () => {
return vm.newNumber(++state)
})
)
vm.setProp(vm.global, "nextId", fnHandle)
const nextId = scope.manage(vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)))
console.log("vm result:", vm.getNumber(nextId), "native state:", state)
// When the withScope block exits, it calls scope.dispose(), which in turn calls
// the .dispose() methods of all the disposables managed by the scope.
})
```
You can also create `Scope` instances with `new Scope()` if you want to manage
calling `scope.dispose()` yourself.
#### `Lifetime.consume(fn)`
[`Lifetime.consume`](https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/lifetime.md#consume)
is sugar for the common pattern of using a handle and then
immediately disposing of it. `Lifetime.consume` takes a `map` function that
produces a result of any type. The `map` fuction is called with the handle,
then the handle is disposed, then the result is returned.
Here's the "Interfacing with interpreter" example re-written using `.consume()`:
```typescript
const vm = QuickJS.newContext()
let state = 0
vm.newFunction("nextId", () => {
return vm.newNumber(++state)
}).consume((fnHandle) => vm.setProp(vm.global, "nextId", fnHandle))
vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)).consume((nextId) =>
console.log("vm result:", vm.getNumber(nextId), "native state:", state)
)
vm.dispose()
```
Generally working with `Scope` leads to more straight-forward code, but
`Lifetime.consume` can be handy sugar as part of a method call chain.
### Exposing APIs
To add APIs inside the QuickJS environment, you'll need to create objects to
define the shape of your API, and add properties and functions to those objects
to allow code inside QuickJS to call code on the host.
By default, no host functionality is exposed to code running inside QuickJS.
```typescript
const vm = QuickJS.newContext()
// `console.log`
const logHandle = vm.newFunction("log", (...args) => {
const nativeArgs = args.map(vm.dump)
console.log("QuickJS:", ...nativeArgs)
})
// Partially implement `console` object
const consoleHandle = vm.newObject()
vm.setProp(consoleHandle, "log", logHandle)
vm.setProp(vm.global, "console", consoleHandle)
consoleHandle.dispose()
logHandle.dispose()
vm.unwrapResult(vm.evalCode(`console.log("Hello from QuickJS!")`)).dispose()
```
#### Promises
To expose an asynchronous function that _returns a promise_ to callers within
QuickJS, your function can return the handle of a `QuickJSDeferredPromise`
created via `context.newPromise()`.
When you resolve a `QuickJSDeferredPromise` -- and generally whenever async
behavior completes for the VM -- pending listeners inside QuickJS may not
execute immediately. Your code needs to explicitly call
`runtime.executePendingJobs()` to resume execution inside QuickJS. This API
gives your code maximum control to _schedule_ when QuickJS will block the host's
event loop by resuming execution.
To work with QuickJS handles that contain a promise inside the environment, you
can convert the QuickJSHandle into a native promise using
`context.resolvePromise()`. Take care with this API to avoid 'deadlocks' where
the host awaits a guest promise, but the guest cannot make progress until the
host calls `runtime.executePendingJobs()`. The simplest way to avoid this kind
of deadlock is to always schedule `executePendingJobs` after any promise is
settled.
```typescript
const vm = QuickJS.newContext()
const fakeFileSystem = new Map([["example.txt", "Example file content"]])
// Function that simulates reading data asynchronously
const readFileHandle = vm.newFunction("readFile", (pathHandle) => {
const path = vm.getString(pathHandle)
const promise = vm.newPromise()
setTimeout(() => {
const content = fakeFileSystem.get(path)
promise.resolve(vm.newString(content || ""))
}, 100)
// IMPORTANT: Once you resolve an async action inside QuickJS,
// call runtime.executePendingJobs() to run any code that was
// waiting on the promise or callback.
promise.settled.then(vm.runtime.executePendingJobs)
return promise.handle
})
readFileHandle.consume((handle) => vm.setProp(vm.global, "readFile", handle))
// Evaluate code that uses `readFile`, which returns a promise
const result = vm.evalCode(`(async () => {
const content = await readFile('example.txt')
return content.toUpperCase()
})()`)
const promiseHandle = vm.unwrapResult(result)
// Convert the promise handle into a native promise and await it.
// If code like this deadlocks, make sure you are calling
// runtime.executePendingJobs appropriately.
const resolvedResult = await vm.resolvePromise(promiseHandle)
promiseHandle.dispose()
const resolvedHandle = vm.unwrapResult(resolvedResult)
console.log("Result:", vm.getString(resolvedHandle))
resolvedHandle.dispose()
```
#### Asyncify
Sometimes, we want to create a function that's synchronous from the perspective
of QuickJS, but prefer to implement that function _asynchronously_ in your host
code. The most obvious use-case is for EcmaScript module loading. The underlying
QuickJS C library expects the module loader function to return synchronously,
but loading data synchronously in the browser or server is somewhere between "a
bad idea" and "impossible". QuickJS also doesn't expose an API to "pause" the
execution of a runtime, and adding such an API is tricky due to the VM's
implementation.
As a work-around, we provide an alternate build of QuickJS processed by
Emscripten/Binaryen's [ASYNCIFY](https://emscripten.org/docs/porting/asyncify.html)
compiler transform. Here's how Emscripten's documentation describes Asyncify:
> Asyncify lets synchronous C or C++ code interact with asynchronous \[host] JavaScript. This allows things like:
>
> - A synchronous call in C that yields to the event loop, which allows browser events to be handled.
>
> - A synchronous call in C that waits for an asynchronous operation in \[host] JS to complete.
>
> Asyncify automatically transforms ... code into a form that can be paused and
> resumed ..., so that it is asynchronous (hence the name “Asyncify”) even though
> \[it is written] in a normal synchronous way.
This means we can suspend an _entire WebAssembly module_ (which could contain
multiple runtimes and contexts) while our host Javascript loads data
asynchronously, and then resume execution once the data load completes. This is
a very handy superpower, but it comes with a couple of major limitations:
1. _An asyncified WebAssembly module can only suspend to wait for a single
asynchronous call at a time_. You may call back into a suspended WebAssembly
module eg. to create a QuickJS value to return a result, but the system will
crash if this call tries to suspend again. Take a look at Emscripten's documentation
on [reentrancy](https://emscripten.org/docs/porting/asyncify.html#reentrancy).
2. _Asyncified code is bigger and runs slower_. The asyncified build of
Quickjs-emscripten library is 1M, 2x larger than the 500K of the default
version. There may be room for further
[optimization](https://emscripten.org/docs/porting/asyncify.html#optimizing)
Of our build in the future.
To use asyncify features, use the following functions:
- [newAsyncRuntime][]: create a runtime inside a new WebAssembly module.
- [newAsyncContext][]: create runtime and context together inside a new
WebAssembly module.
- [newQuickJSAsyncWASMModule][]: create an empty WebAssembly module.
[newasyncruntime]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#newasyncruntime
[newasynccontext]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#newasynccontext
[newquickjsasyncwasmmodule]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#newquickjsasyncwasmmodule
These functions are asynchronous because they always create a new underlying
WebAssembly module so that each instance can suspend and resume independently,
and instantiating a WebAssembly module is an async operation. This also adds
substantial overhead compared to creating a runtime or context inside an
existing module; if you only need to wait for a single async action at a time,
you can create a single top-level module and create runtimes or contexts inside
of it.
##### Async module loader
Here's an example of valuating a script that loads React asynchronously as an ES
module. In our example, we're loading from the filesystem for reproducibility,
but you can use this technique to load using `fetch`.
```typescript
const module = await newQuickJSAsyncWASMModule()
const runtime = module.newRuntime()
const path = await import("path")
const { promises: fs } = await import("fs")
const importsPath = path.join(__dirname, "../examples/imports") + "/"
// Module loaders can return promises.
// Execution will suspend until the promise resolves.
runtime.setModuleLoader((moduleName) => {
const modulePath = path.join(importsPath, moduleName)
if (!modulePath.startsWith(importsPath)) {
throw new Error("out of bounds")
}
console.log("loading", moduleName, "from", modulePath)
return fs.readFile(modulePath, "utf-8")
})
// evalCodeAsync is required when execution may suspend.
const context = runtime.newContext()
const result = await context.evalCodeAsync(`
import * as React from 'esm.sh/react@17'
import * as ReactDOMServer from 'esm.sh/react-dom@17/server'
const e = React.createElement
globalThis.html = ReactDOMServer.renderToStaticMarkup(
e('div', null, e('strong', null, 'Hello world!'))
)
`)
context.unwrapResult(result).dispose()
const html = context.getProp(context.global, "html").consume(context.getString)
console.log(html) // <div><strong>Hello world!</strong></div>
```
##### Async on host, sync in QuickJS
Here's an example of turning an async function into a sync function inside the
VM.
```typescript
const context = await newAsyncContext()
const path = await import("path")
const { promises: fs } = await import("fs")
const importsPath = path.join(__dirname, "../examples/imports") + "/"
const readFileHandle = context.newAsyncifiedFunction("readFile", async (pathHandle) => {
const pathString = path.join(importsPath, context.getString(pathHandle))
if (!pathString.startsWith(importsPath)) {
throw new Error("out of bounds")
}
const data = await fs.readFile(pathString, "utf-8")
return context.newString(data)
})
readFileHandle.consume((fn) => context.setProp(context.global, "readFile", fn))
// evalCodeAsync is required when execution may suspend.
const result = await context.evalCodeAsync(`
// Not a promise! Sync! vvvvvvvvvvvvvvvvvvvv
const data = JSON.parse(readFile('data.json'))
data.map(x => x.toUpperCase()).join(' ')
`)
const upperCaseData = context.unwrapResult(result).consume(context.getString)
console.log(upperCaseData) // 'VERY USEFUL DATA'
```
### Testing your code
This library is complicated to use, so please consider automated testing your
implementation. We highly writing your test suite to run with both the "release"
build variant of quickjs-emscripten, and also the [DEBUG_SYNC] build variant.
The debug sync build variant has extra instrumentation code for detecting memory
leaks.
The class [TestQuickJSWASMModule] exposes the memory leak detection API, although
this API is only accurate when using `DEBUG_SYNC` variant.
```typescript
// Define your test suite in a function, so that you can test against
// different module loaders.
function myTests(moduleLoader: () => Promise<QuickJSWASMModule>) {
let QuickJS: TestQuickJSWASMModule
beforeEach(async () => {
// Get a unique TestQuickJSWASMModule instance for each test.
const wasmModule = await moduleLoader()
QuickJS = new TestQuickJSWASMModule(wasmModule)
})
afterEach(() => {
// Assert that the test disposed all handles. The DEBUG_SYNC build
// variant will show detailed traces for each leak.
QuickJS.assertNoMemoryAllocated()
})
it("works well", () => {
// TODO: write a test using QuickJS
const context = QuickJS.newContext()
context.unwrapResult(context.evalCode("1 + 1")).dispose()
context.dispose()
})
}
// Run the test suite against a matrix of module loaders.
describe("Check for memory leaks with QuickJS DEBUG build", () => {
const moduleLoader = memoizePromiseFactory(() => newQuickJSWASMModule(DEBUG_SYNC))
myTests(moduleLoader)
})
describe("Realistic test with QuickJS RELEASE build", () => {
myTests(getQuickJS)
})
```
For more testing examples, please explore the typescript source of [quickjs-emscripten][ts] repository.
[ts]: https://github.com/justjake/quickjs-emscripten/blob/main/ts
[debug_sync]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/modules.md#debug_sync
[testquickjswasmmodule]: https://github.com/justjake/quickjs-emscripten/blob/main/doc/classes/TestQuickJSWASMModule.md
### Debugging
- Switch to a DEBUG build variant of the WebAssembly module to see debug log messages from the C part of this library.
- Set `process.env.QTS_DEBUG` to see debug log messages from the Javascript part of this library.
### More Documentation
[Github] | [NPM] | [API Documentation][api] | [Examples][tests]
## Background
This was inspired by seeing https://github.com/maple3142/duktape-eval
[on Hacker News](https://news.ycombinator.com/item?id=21946565) and Figma's
blogposts about using building a Javascript plugin runtime:
- [How Figma built the Figma plugin system](https://www.figma.com/blog/how-we-built-the-figma-plugin-system/): Describes the LowLevelJavascriptVm interface.
- [An update on plugin security](https://www.figma.com/blog/an-update-on-plugin-security/): Figma switches to QuickJS.
## Status & Roadmap
**Stability**: Because the version number of this project is below `1.0.0`,
\*expect occasional breaking API changes.
**Security**: This project makes every effort to be secure, but has not been
audited. Please use with care in production settings.
**Roadmap**: I work on this project in my free time, for fun. Here's I'm
thinking comes next. Last updated 2022-03-18.
1. Further work on module loading APIs:
- Create modules via Javascript, instead of source text.
- Scan source text for imports, for ahead of time or concurrent loading.
(This is possible with third-party tools, so lower priority.)
2. Higher-level tools for reading QuickJS values:
- Type guard functions: `context.isArray(handle)`, `context.isPromise(handle)`, etc.
- Iteration utilities: `context.getIterable(handle)`, `context.iterateObjectEntries(handle)`.
This better supports user-level code to deserialize complex handle objects.
3. Higher-level tools for creating QuickJS values:
- Devise a way to avoid needing to mess around with handles when setting up
the environment.
- Consider integrating
[quickjs-emscripten-sync](https://github.com/reearth/quickjs-emscripten-sync)
for automatic translation.
- Consider class-based or interface-type-based marshalling.
4. EcmaScript Modules / WebAssembly files / Deno support. This requires me to
learn a lot of new things, but should be interesting for modern browser usage.
5. SQLite integration.
## Related
- Duktape wrapped in Wasm: https://github.com/maple3142/duktape-eval/blob/main/src/Makefile
- QuickJS wrapped in C++: https://github.com/ftk/quickjspp
## Developing
This library is implemented in two languages: C (compiled to WASM with
Emscripten), and Typescript.
### The C parts
The ./c directory contains C code that wraps the QuickJS C library (in ./quickjs).
Public functions (those starting with `QTS_`) in ./c/interface.c are
automatically exported to native code (via a generated header) and to
Typescript (via a generated FFI class). See ./generate.ts for how this works.
The C code builds as both with `emscripten` (using `emcc`), to produce WASM (or
ASM.js) and with `clang`. Build outputs are checked in, so you can iterate on
the Javascript parts of the library without setting up the Emscripten toolchain.
Intermediate object files from QuickJS end up in ./build/quickjs/.
This project uses `emscripten 3.1.32`.
- On ARM64, you should install `emscripten` on your machine. For example on macOS, `brew install emscripten`.
- If _the correct version of emcc_ is not in your PATH, compilation falls back to using Docker.
On ARM64, this is 10-50x slower than native compilation, but it's just fine on x64.
Related NPM scripts:
- `yarn update-quickjs` will sync the ./quickjs folder with a
github repo tracking the upstream QuickJS.
- `yarn make-debug` will rebuild C outputs into ./build/wrapper
- `yarn make-release` will rebuild C outputs in release mode, which is the mode
that should be checked into the repo.
### The Typescript parts
The ./ts directory contains Typescript types and wraps the generated Emscripten
FFI in a more usable interface.
You'll need `node` and `yarn`. Install dependencies with `yarn install`.
- `yarn build` produces ./dist.
- `yarn test` runs the tests.
- `yarn test --watch` watches for changes and re-runs the tests.
### Yarn updates
Just run `yarn set version from sources` to upgrade the Yarn release.

View File

@@ -0,0 +1,819 @@
/**
* interface.c
*
* We primarily use JSValue* (pointer to JSValue) when communicating with the
* host javascript environment, because pointers are trivial to use for calls
* into emscripten because they're just a number!
*
* As with the quickjs.h API, a JSValueConst* value is "borrowed" and should
* not be freed. A JSValue* is "owned" and should be freed by the owner.
*
* Functions starting with "QTS_" are exported by generate.ts to:
* - interface.h for native C code.
* - ffi.ts for emscripten.
*
* We support building the following build outputs:
*
* ## 1. Native machine code
* For internal development testing purposes.
*
* ## 2. WASM via Emscripten
* For general production use.
*
* ## 3. Experimental: Asyncified WASM via Emscripten with -s ASYNCIFY=1.
* This variant supports treating async host Javascript calls as synchronous
* from the perspective of the WASM c code.
*
* The way this works is described here:
* https://emscripten.org/docs/porting/asyncify.html
*
* In this variant, any call into our C code could return a promise if it ended
* up suspended. We mark the methods we suspect might suspend due to users' code
* as returning MaybeAsync(T). This information is ignored for the regular
* build.
*/
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#include <math.h> // For NAN
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#ifdef QTS_SANITIZE_LEAK
#include <sanitizer/lsan_interface.h>
#endif
#include "../quickjs/cutils.h"
#include "../quickjs/quickjs-libc.h"
#include "../quickjs/quickjs.h"
#define PKG "quickjs-emscripten: "
#ifdef QTS_DEBUG_MODE
#define QTS_DEBUG(msg) qts_log(msg);
#define QTS_DUMP(value) qts_dump(ctx, value);
#else
#define QTS_DEBUG(msg) ;
#define QTS_DUMP(value) ;
#endif
/**
* Signal to our FFI code generator that this string argument should be passed as a pointer
* allocated by the caller on the heap, not a JS string on the stack.
* https://github.com/emscripten-core/emscripten/issues/6860#issuecomment-405818401
*/
#define BorrowedHeapChar const char
#define OwnedHeapChar char
#define JSBorrowedChar const char
/**
* Signal to our FFI code generator that this function should be called
* asynchronously when compiled with ASYNCIFY.
*/
#define MaybeAsync(T) T
/**
* Signal to our FFI code generator that this function is only available in
* ASYNCIFY builds.
*/
#define AsyncifyOnly(T) T
#define JSVoid void
#define EvalFlags int
#define EvalDetectModule int
void qts_log(char *msg) {
fputs(PKG, stderr);
fputs(msg, stderr);
fputs("\n", stderr);
}
void qts_dump(JSContext *ctx, JSValueConst value) {
const char *str = JS_ToCString(ctx, value);
if (!str) {
QTS_DEBUG("QTS_DUMP: can't dump");
return;
}
fputs(str, stderr);
JS_FreeCString(ctx, str);
putchar('\n');
}
void copy_prop_if_needed(JSContext *ctx, JSValueConst dest, JSValueConst src, const char *prop_name) {
JSAtom prop_atom = JS_NewAtom(ctx, prop_name);
JSValue dest_prop = JS_GetProperty(ctx, dest, prop_atom);
if (JS_IsUndefined(dest_prop)) {
JSValue src_prop = JS_GetProperty(ctx, src, prop_atom);
if (!JS_IsUndefined(src_prop) && !JS_IsException(src_prop)) {
JS_SetProperty(ctx, dest, prop_atom, src_prop);
}
} else {
JS_FreeValue(ctx, dest_prop);
}
JS_FreeAtom(ctx, prop_atom);
}
JSValue *jsvalue_to_heap(JSValueConst value) {
JSValue *result = malloc(sizeof(JSValue));
if (result) {
// Could be better optimized, but at -0z / -ftlo, it
// appears to produce the same binary code as a memcpy.
*result = value;
}
return result;
}
JSValue *QTS_Throw(JSContext *ctx, JSValueConst *error) {
JSValue copy = JS_DupValue(ctx, *error);
return jsvalue_to_heap(JS_Throw(ctx, copy));
}
JSValue *QTS_NewError(JSContext *ctx) {
return jsvalue_to_heap(JS_NewError(ctx));
}
/**
* Limits.
*/
/**
* Memory limit. Set to -1 to disable.
*/
void QTS_RuntimeSetMemoryLimit(JSRuntime *rt, size_t limit) {
JS_SetMemoryLimit(rt, limit);
}
/**
* Memory diagnostics
*/
JSValue *QTS_RuntimeComputeMemoryUsage(JSRuntime *rt, JSContext *ctx) {
JSMemoryUsage s;
JS_ComputeMemoryUsage(rt, &s);
// Note that we're going to allocate more memory just to report the memory usage.
// A more sound approach would be to bind JSMemoryUsage struct directly - but that's
// a lot of work. This should be okay in the mean time.
JSValue result = JS_NewObject(ctx);
// Manually generated via editor-fu from JSMemoryUsage struct definition in quickjs.h
JS_SetPropertyStr(ctx, result, "malloc_limit", JS_NewInt64(ctx, s.malloc_limit));
JS_SetPropertyStr(ctx, result, "memory_used_size", JS_NewInt64(ctx, s.memory_used_size));
JS_SetPropertyStr(ctx, result, "malloc_count", JS_NewInt64(ctx, s.malloc_count));
JS_SetPropertyStr(ctx, result, "memory_used_count", JS_NewInt64(ctx, s.memory_used_count));
JS_SetPropertyStr(ctx, result, "atom_count", JS_NewInt64(ctx, s.atom_count));
JS_SetPropertyStr(ctx, result, "atom_size", JS_NewInt64(ctx, s.atom_size));
JS_SetPropertyStr(ctx, result, "str_count", JS_NewInt64(ctx, s.str_count));
JS_SetPropertyStr(ctx, result, "str_size", JS_NewInt64(ctx, s.str_size));
JS_SetPropertyStr(ctx, result, "obj_count", JS_NewInt64(ctx, s.obj_count));
JS_SetPropertyStr(ctx, result, "obj_size", JS_NewInt64(ctx, s.obj_size));
JS_SetPropertyStr(ctx, result, "prop_count", JS_NewInt64(ctx, s.prop_count));
JS_SetPropertyStr(ctx, result, "prop_size", JS_NewInt64(ctx, s.prop_size));
JS_SetPropertyStr(ctx, result, "shape_count", JS_NewInt64(ctx, s.shape_count));
JS_SetPropertyStr(ctx, result, "shape_size", JS_NewInt64(ctx, s.shape_size));
JS_SetPropertyStr(ctx, result, "js_func_count", JS_NewInt64(ctx, s.js_func_count));
JS_SetPropertyStr(ctx, result, "js_func_size", JS_NewInt64(ctx, s.js_func_size));
JS_SetPropertyStr(ctx, result, "js_func_code_size", JS_NewInt64(ctx, s.js_func_code_size));
JS_SetPropertyStr(ctx, result, "js_func_pc2line_count", JS_NewInt64(ctx, s.js_func_pc2line_count));
JS_SetPropertyStr(ctx, result, "js_func_pc2line_size", JS_NewInt64(ctx, s.js_func_pc2line_size));
JS_SetPropertyStr(ctx, result, "c_func_count", JS_NewInt64(ctx, s.c_func_count));
JS_SetPropertyStr(ctx, result, "array_count", JS_NewInt64(ctx, s.array_count));
JS_SetPropertyStr(ctx, result, "fast_array_count", JS_NewInt64(ctx, s.fast_array_count));
JS_SetPropertyStr(ctx, result, "fast_array_elements", JS_NewInt64(ctx, s.fast_array_elements));
JS_SetPropertyStr(ctx, result, "binary_object_count", JS_NewInt64(ctx, s.binary_object_count));
JS_SetPropertyStr(ctx, result, "binary_object_size", JS_NewInt64(ctx, s.binary_object_size));
return jsvalue_to_heap(result);
}
OwnedHeapChar *QTS_RuntimeDumpMemoryUsage(JSRuntime *rt) {
char *result = malloc(sizeof(char) * 1024);
FILE *memfile = fmemopen(result, 1024, "w");
JSMemoryUsage s;
JS_ComputeMemoryUsage(rt, &s);
JS_DumpMemoryUsage(memfile, &s, rt);
fclose(memfile);
return result;
}
int QTS_RecoverableLeakCheck() {
#ifdef QTS_SANITIZE_LEAK
return __lsan_do_recoverable_leak_check();
#else
return 0;
#endif
}
int QTS_BuildIsSanitizeLeak() {
#ifdef QTS_SANITIZE_LEAK
return 1;
#else
return 0;
#endif
}
#ifdef QTS_ASYNCIFY
EM_JS(void, set_asyncify_stack_size, (size_t size), {
Asyncify.StackSize = size || 81920;
});
#endif
/**
* Set the stack size limit, in bytes. Set to 0 to disable.
*/
void QTS_RuntimeSetMaxStackSize(JSRuntime *rt, size_t stack_size) {
#ifdef QTS_ASYNCIFY
set_asyncify_stack_size(stack_size);
#endif
JS_SetMaxStackSize(rt, stack_size);
}
/**
* Constant pointers. Because we always use JSValue* from the host Javascript environment,
* we need helper fuctions to return pointers to these constants.
*/
JSValueConst QTS_Undefined = JS_UNDEFINED;
JSValueConst *QTS_GetUndefined() {
return &QTS_Undefined;
}
JSValueConst QTS_Null = JS_NULL;
JSValueConst *QTS_GetNull() {
return &QTS_Null;
}
JSValueConst QTS_False = JS_FALSE;
JSValueConst *QTS_GetFalse() {
return &QTS_False;
}
JSValueConst QTS_True = JS_TRUE;
JSValueConst *QTS_GetTrue() {
return &QTS_True;
}
/**
* Standard FFI functions
*/
JSRuntime *QTS_NewRuntime() {
return JS_NewRuntime();
}
void QTS_FreeRuntime(JSRuntime *rt) {
JS_FreeRuntime(rt);
}
JSContext *QTS_NewContext(JSRuntime *rt) {
return JS_NewContext(rt);
}
void QTS_FreeContext(JSContext *ctx) {
JS_FreeContext(ctx);
}
void QTS_FreeValuePointer(JSContext *ctx, JSValue *value) {
JS_FreeValue(ctx, *value);
free(value);
}
void QTS_FreeValuePointerRuntime(JSRuntime *rt, JSValue *value) {
JS_FreeValueRT(rt, *value);
free(value);
}
void QTS_FreeVoidPointer(JSContext *ctx, JSVoid *ptr) {
js_free(ctx, ptr);
}
void QTS_FreeCString(JSContext *ctx, JSBorrowedChar *str) {
JS_FreeCString(ctx, str);
}
JSValue *QTS_DupValuePointer(JSContext *ctx, JSValueConst *val) {
return jsvalue_to_heap(JS_DupValue(ctx, *val));
}
JSValue *QTS_NewObject(JSContext *ctx) {
return jsvalue_to_heap(JS_NewObject(ctx));
}
JSValue *QTS_NewObjectProto(JSContext *ctx, JSValueConst *proto) {
return jsvalue_to_heap(JS_NewObjectProto(ctx, *proto));
}
JSValue *QTS_NewArray(JSContext *ctx) {
return jsvalue_to_heap(JS_NewArray(ctx));
}
JSValue *QTS_NewFloat64(JSContext *ctx, double num) {
return jsvalue_to_heap(JS_NewFloat64(ctx, num));
}
double QTS_GetFloat64(JSContext *ctx, JSValueConst *value) {
double result = NAN;
JS_ToFloat64(ctx, &result, *value);
return result;
}
JSValue *QTS_NewString(JSContext *ctx, BorrowedHeapChar *string) {
return jsvalue_to_heap(JS_NewString(ctx, string));
}
JSBorrowedChar *QTS_GetString(JSContext *ctx, JSValueConst *value) {
return JS_ToCString(ctx, *value);
}
JSValue qts_get_symbol_key(JSContext *ctx, JSValueConst *value) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue Symbol = JS_GetPropertyStr(ctx, global, "Symbol");
JS_FreeValue(ctx, global);
JSValue Symbol_keyFor = JS_GetPropertyStr(ctx, Symbol, "keyFor");
JSValue key = JS_Call(ctx, Symbol_keyFor, Symbol, 1, value);
JS_FreeValue(ctx, Symbol_keyFor);
JS_FreeValue(ctx, Symbol);
return key;
}
JSValue *QTS_NewSymbol(JSContext *ctx, BorrowedHeapChar *description, int isGlobal) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue Symbol = JS_GetPropertyStr(ctx, global, "Symbol");
JS_FreeValue(ctx, global);
JSValue descriptionValue = JS_NewString(ctx, description);
JSValue symbol;
if (isGlobal != 0) {
JSValue Symbol_for = JS_GetPropertyStr(ctx, Symbol, "for");
symbol = JS_Call(ctx, Symbol_for, Symbol, 1, &descriptionValue);
JS_FreeValue(ctx, descriptionValue);
JS_FreeValue(ctx, Symbol_for);
JS_FreeValue(ctx, Symbol);
return jsvalue_to_heap(symbol);
}
symbol = JS_Call(ctx, Symbol, JS_UNDEFINED, 1, &descriptionValue);
JS_FreeValue(ctx, descriptionValue);
JS_FreeValue(ctx, Symbol);
return jsvalue_to_heap(symbol);
}
MaybeAsync(JSBorrowedChar *) QTS_GetSymbolDescriptionOrKey(JSContext *ctx, JSValueConst *value) {
JSBorrowedChar *result;
JSValue key = qts_get_symbol_key(ctx, value);
if (!JS_IsUndefined(key)) {
result = JS_ToCString(ctx, key);
JS_FreeValue(ctx, key);
return result;
}
JSValue description = JS_GetPropertyStr(ctx, *value, "description");
result = JS_ToCString(ctx, description);
JS_FreeValue(ctx, description);
return result;
}
int QTS_IsGlobalSymbol(JSContext *ctx, JSValueConst *value) {
JSValue key = qts_get_symbol_key(ctx, value);
int undefined = JS_IsUndefined(key);
JS_FreeValue(ctx, key);
if (undefined) {
return 0;
} else {
return 1;
}
}
int QTS_IsJobPending(JSRuntime *rt) {
return JS_IsJobPending(rt);
}
/*
runs pending jobs (Promises/async functions) until it encounters
an exception or it executed the passed maxJobsToExecute jobs.
Passing a negative value will run the loop until there are no more
pending jobs or an exception happened
Returns the executed number of jobs or the exception encountered
*/
MaybeAsync(JSValue *) QTS_ExecutePendingJob(JSRuntime *rt, int maxJobsToExecute, JSContext **lastJobContext) {
JSContext *pctx;
int status = 1;
int executed = 0;
while (executed != maxJobsToExecute && status == 1) {
status = JS_ExecutePendingJob(rt, &pctx);
if (status == -1) {
*lastJobContext = pctx;
return jsvalue_to_heap(JS_GetException(pctx));
} else if (status == 1) {
*lastJobContext = pctx;
executed++;
}
}
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "QTS_ExecutePendingJob(executed: %d, pctx: %p, lastJobExecuted: %p)", executed, pctx, *lastJobContext);
QTS_DEBUG(msg)
#endif
return jsvalue_to_heap(JS_NewFloat64(pctx, executed));
}
MaybeAsync(JSValue *) QTS_GetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name) {
JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
JSValue prop_val = JS_GetProperty(ctx, *this_val, prop_atom);
JS_FreeAtom(ctx, prop_atom);
return jsvalue_to_heap(prop_val);
}
MaybeAsync(void) QTS_SetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value) {
JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
JSValue extra_prop_value = JS_DupValue(ctx, *prop_value);
// TODO: should we use DefineProperty internally if this object doesn't have the property yet?
JS_SetProperty(ctx, *this_val, prop_atom, extra_prop_value); // consumes extra_prop_value
JS_FreeAtom(ctx, prop_atom);
}
void QTS_DefineProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value, JSValueConst *get, JSValueConst *set, bool configurable, bool enumerable, bool has_value) {
JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
int flags = 0;
if (configurable) {
flags = flags | JS_PROP_CONFIGURABLE;
if (has_value) {
flags = flags | JS_PROP_HAS_CONFIGURABLE;
}
}
if (enumerable) {
flags = flags | JS_PROP_ENUMERABLE;
if (has_value) {
flags = flags | JS_PROP_HAS_ENUMERABLE;
}
}
if (!JS_IsUndefined(*get)) {
flags = flags | JS_PROP_HAS_GET;
}
if (!JS_IsUndefined(*set)) {
flags = flags | JS_PROP_HAS_SET;
}
if (has_value) {
flags = flags | JS_PROP_HAS_VALUE;
}
JS_DefineProperty(ctx, *this_val, prop_atom, *prop_value, *get, *set, flags);
JS_FreeAtom(ctx, prop_atom);
}
MaybeAsync(JSValue *) QTS_Call(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, int argc, JSValueConst **argv_ptrs) {
// convert array of pointers to array of values
JSValueConst argv[argc];
int i;
for (i = 0; i < argc; i++) {
argv[i] = *(argv_ptrs[i]);
}
return jsvalue_to_heap(JS_Call(ctx, *func_obj, *this_obj, argc, argv));
}
/**
* If maybe_exception is an exception, get the error.
* Otherwise, return NULL.
*/
JSValue *QTS_ResolveException(JSContext *ctx, JSValue *maybe_exception) {
if (JS_IsException(*maybe_exception)) {
return jsvalue_to_heap(JS_GetException(ctx));
}
return NULL;
}
MaybeAsync(JSBorrowedChar *) QTS_Dump(JSContext *ctx, JSValueConst *obj) {
JSValue obj_json_value = JS_JSONStringify(ctx, *obj, JS_UNDEFINED, JS_UNDEFINED);
if (!JS_IsException(obj_json_value)) {
const char *obj_json_chars = JS_ToCString(ctx, obj_json_value);
JS_FreeValue(ctx, obj_json_value);
if (obj_json_chars != NULL) {
JSValue enumerable_props = JS_ParseJSON(ctx, obj_json_chars, strlen(obj_json_chars), "<dump>");
JS_FreeCString(ctx, obj_json_chars);
if (!JS_IsException(enumerable_props)) {
// Copy common non-enumerable props for different object types.
// Errors:
copy_prop_if_needed(ctx, enumerable_props, *obj, "name");
copy_prop_if_needed(ctx, enumerable_props, *obj, "message");
copy_prop_if_needed(ctx, enumerable_props, *obj, "stack");
// Serialize again.
JSValue enumerable_json = JS_JSONStringify(ctx, enumerable_props, JS_UNDEFINED, JS_UNDEFINED);
JS_FreeValue(ctx, enumerable_props);
JSBorrowedChar *result = QTS_GetString(ctx, &enumerable_json);
JS_FreeValue(ctx, enumerable_json);
return result;
}
}
}
#ifdef QTS_DEBUG_MODE
qts_log("Error dumping JSON:");
js_std_dump_error(ctx);
#endif
// Fallback: convert to string
return QTS_GetString(ctx, obj);
}
MaybeAsync(JSValue *) QTS_Eval(JSContext *ctx, BorrowedHeapChar *js_code, const char *filename, EvalDetectModule detectModule, EvalFlags evalFlags) {
size_t js_code_len = strlen(js_code);
if (detectModule) {
if (JS_DetectModule((const char *)js_code, js_code_len)) {
QTS_DEBUG("QTS_Eval: Detected module = true");
evalFlags |= JS_EVAL_TYPE_MODULE;
} else {
QTS_DEBUG("QTS_Eval: Detected module = false");
}
} else {
QTS_DEBUG("QTS_Eval: do not detect module");
}
return jsvalue_to_heap(JS_Eval(ctx, js_code, strlen(js_code), filename, evalFlags));
}
OwnedHeapChar *QTS_Typeof(JSContext *ctx, JSValueConst *value) {
const char *result = "unknown";
uint32_t tag = JS_VALUE_GET_TAG(*value);
if (JS_IsNumber(*value)) {
result = "number";
} else if (JS_IsBigInt(ctx, *value)) {
result = "bigint";
} else if (JS_IsBigFloat(*value)) {
result = "bigfloat";
} else if (JS_IsBigDecimal(*value)) {
result = "bigdecimal";
} else if (JS_IsFunction(ctx, *value)) {
result = "function";
} else if (JS_IsBool(*value)) {
result = "boolean";
} else if (JS_IsNull(*value)) {
result = "object";
} else if (JS_IsUndefined(*value)) {
result = "undefined";
} else if (JS_IsUninitialized(*value)) {
result = "undefined";
} else if (JS_IsString(*value)) {
result = "string";
} else if (JS_IsSymbol(*value)) {
result = "symbol";
} else if (JS_IsObject(*value)) {
result = "object";
}
char *out = strdup(result);
return out;
}
JSValue *QTS_GetGlobalObject(JSContext *ctx) {
return jsvalue_to_heap(JS_GetGlobalObject(ctx));
}
JSValue *QTS_NewPromiseCapability(JSContext *ctx, JSValue **resolve_funcs_out) {
JSValue resolve_funcs[2];
JSValue promise = JS_NewPromiseCapability(ctx, resolve_funcs);
resolve_funcs_out[0] = jsvalue_to_heap(resolve_funcs[0]);
resolve_funcs_out[1] = jsvalue_to_heap(resolve_funcs[1]);
return jsvalue_to_heap(promise);
}
void QTS_TestStringArg(const char *string) {
// pass
}
int QTS_BuildIsDebug() {
#ifdef QTS_DEBUG_MODE
return 1;
#else
return 0;
#endif
}
int QTS_BuildIsAsyncify() {
#ifdef QTS_ASYNCIFY
return 1;
#else
return 0;
#endif
}
// ----------------------------------------------------------------------------
// Module loading helpers
// ----------------------------------------------------------------------------
// C -> Host Callbacks
// Note: inside EM_JS, we need to use ['...'] subscript syntax for accessing JS
// objects, because in optimized builds, Closure compiler will mangle all the
// names.
// -------------------
// function: C -> Host
#ifdef __EMSCRIPTEN__
EM_JS(MaybeAsync(JSValue *), qts_host_call_function, (JSContext * ctx, JSValueConst *this_ptr, int argc, JSValueConst *argv, uint32_t magic_func_id), {
#ifdef QTS_ASYNCIFY
const asyncify = {['handleSleep'] : Asyncify.handleSleep};
#else
const asyncify = undefined;
#endif
return Module['callbacks']['callFunction'](asyncify, ctx, this_ptr, argc, argv, magic_func_id);
});
#endif
// Function: QuickJS -> C
JSValue qts_call_function(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
JSValue *result_ptr = qts_host_call_function(ctx, &this_val, argc, argv, magic);
if (result_ptr == NULL) {
return JS_UNDEFINED;
}
JSValue result = *result_ptr;
free(result_ptr);
return result;
}
// Function: Host -> QuickJS
JSValue *QTS_NewFunction(JSContext *ctx, uint32_t func_id, const char *name) {
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "new_function(name: %s, magic: %d)", name, func_id);
QTS_DEBUG(msg)
#endif
JSValue func_obj = JS_NewCFunctionMagic(
/* context */ ctx,
/* JSCFunctionMagic* */ &qts_call_function,
/* name */ name,
/* min argc */ 0,
/* function type */ JS_CFUNC_generic_magic,
/* magic: fn id */ func_id);
return jsvalue_to_heap(func_obj);
}
JSValueConst *QTS_ArgvGetJSValueConstPointer(JSValueConst *argv, int index) {
return &argv[index];
}
// --------------------
// interrupt: C -> Host
#ifdef __EMSCRIPTEN__
EM_JS(int, qts_host_interrupt_handler, (JSRuntime * rt), {
// Async not supported here.
// #ifdef QTS_ASYNCIFY
// const asyncify = Asyncify;
// #else
const asyncify = undefined;
// #endif
return Module['callbacks']['shouldInterrupt'](asyncify, rt);
});
#endif
// interrupt: QuickJS -> C
int qts_interrupt_handler(JSRuntime *rt, void *_unused) {
return qts_host_interrupt_handler(rt);
}
// interrupt: Host -> QuickJS
void QTS_RuntimeEnableInterruptHandler(JSRuntime *rt) {
JS_SetInterruptHandler(rt, &qts_interrupt_handler, NULL);
}
void QTS_RuntimeDisableInterruptHandler(JSRuntime *rt) {
JS_SetInterruptHandler(rt, NULL, NULL);
}
// --------------------
// load module: C -> Host
// TODO: a future version can support host returning JSModuleDef* directly;
// for now we only support loading module source code.
/*
The module loading model under ASYNCIFY is convoluted. We need to make sure we
never have an async request running concurrently for loading modules.
The first implemenation looked like this:
C HOST SUSPENDED
qts_host_load_module(name) ------> false
call rt.loadModule(name) false
Start async load module false
Suspend C true
Async load complete true
< --------------- QTS_CompileModule(source) true
QTS_Eval(source, COMPILE_ONLY) true
Loaded module has import true
qts_host_load_module(dep) -------> true
call rt.loadModule(dep) true
Start async load module true
ALREADY SUSPENDED, CRASH
We can solve this in two different ways:
1. Return to C as soon as we async load the module source.
That way, we unsuspend before calling QTS_CompileModule.
2. Once we load the module, use a new API to detect and async
load the module's downstream dependencies. This way
they're loaded synchronously so we don't need to suspend "again".
Probably we could optimize (2) to make it more performant, eg with parallel
loading, but (1) seems much easier to implement in the sort run.
*/
JSModuleDef *qts_compile_module(JSContext *ctx, const char *module_name, BorrowedHeapChar *module_body) {
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "QTS_CompileModule(ctx: %p, name: %s, bodyLength: %lu)", ctx, module_name, strlen(module_body));
QTS_DEBUG(msg)
#endif
JSValue func_val = JS_Eval(ctx, module_body, strlen(module_body), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(func_val)) {
return NULL;
}
// TODO: Is exception ok?
// TODO: set import.meta?
JSModuleDef *module = JS_VALUE_GET_PTR(func_val);
JS_FreeValue(ctx, func_val);
return module;
}
#ifdef __EMSCRIPTEN__
EM_JS(MaybeAsync(char *), qts_host_load_module_source, (JSRuntime * rt, JSContext *ctx, const char *module_name), {
#ifdef QTS_ASYNCIFY
const asyncify = {['handleSleep'] : Asyncify.handleSleep};
#else
const asyncify = undefined;
#endif
// https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
const moduleNameString = UTF8ToString(module_name);
return Module['callbacks']['loadModuleSource'](asyncify, rt, ctx, moduleNameString);
});
EM_JS(MaybeAsync(char *), qts_host_normalize_module, (JSRuntime * rt, JSContext *ctx, const char *module_base_name, const char *module_name), {
#ifdef QTS_ASYNCIFY
const asyncify = {['handleSleep'] : Asyncify.handleSleep};
#else
const asyncify = undefined;
#endif
// https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
const moduleBaseNameString = UTF8ToString(module_base_name);
const moduleNameString = UTF8ToString(module_name);
return Module['callbacks']['normalizeModule'](asyncify, rt, ctx, moduleBaseNameString, moduleNameString);
});
#endif
// load module: QuickJS -> C
// See js_module_loader in quickjs/quickjs-libc.c:567
JSModuleDef *qts_load_module(JSContext *ctx, const char *module_name, void *_unused) {
JSRuntime *rt = JS_GetRuntime(ctx);
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "qts_load_module(rt: %p, ctx: %p, name: %s)", rt, ctx, module_name);
QTS_DEBUG(msg)
#endif
char *module_source = qts_host_load_module_source(rt, ctx, module_name);
if (module_source == NULL) {
return NULL;
}
JSModuleDef *module = qts_compile_module(ctx, module_name, module_source);
free(module_source);
return module;
}
char *qts_normalize_module(JSContext *ctx, const char *module_base_name, const char *module_name, void *_unused) {
JSRuntime *rt = JS_GetRuntime(ctx);
#ifdef QTS_DEBUG_MODE
char msg[500];
sprintf(msg, "qts_normalize_module(rt: %p, ctx: %p, base_name: %s, name: %s)", rt, ctx, module_base_name, module_name);
QTS_DEBUG(msg)
#endif
char *em_module_name = qts_host_normalize_module(rt, ctx, module_base_name, module_name);
char *js_module_name = js_strdup(ctx, em_module_name);
free(em_module_name);
return js_module_name;
}
// Load module: Host -> QuickJS
void QTS_RuntimeEnableModuleLoader(JSRuntime *rt, int use_custom_normalize) {
JSModuleNormalizeFunc *module_normalize = NULL; /* use default name normalizer */
if (use_custom_normalize) {
module_normalize = &qts_normalize_module;
}
JS_SetModuleLoaderFunc(rt, module_normalize, &qts_load_module, NULL);
}
void QTS_RuntimeDisableModuleLoader(JSRuntime *rt) {
JS_SetModuleLoaderFunc(rt, NULL, NULL, NULL);
}

View File

@@ -0,0 +1,24 @@
declare function awaitYield<T>(value: T | Promise<T>): Generator<T | Promise<T>, T, T>;
declare function awaitYieldOf<T, Yielded>(generator: Generator<Yielded | Promise<Yielded>, T, Yielded>): Generator<T | Promise<T>, T, T>;
export type AwaitYield = typeof awaitYield & {
of: typeof awaitYieldOf;
};
/**
* Create a function that may or may not be async, using a generator
*
* Within the generator, call `yield* awaited(maybePromise)` to await a value
* that may or may not be a promise.
*
* If the inner function never yields a promise, it will return synchronously.
*/
export declare function maybeAsyncFn<
/** Function arguments */
Args extends any[], This,
/** Function return type */
Return,
/** Yields to unwrap */
Yielded>(that: This, fn: (this: This, awaited: AwaitYield, ...args: Args) => Generator<Yielded | Promise<Yielded>, Return, Yielded>): (...args: Args) => Return | Promise<Return>;
export type MaybeAsyncBlock<Return, This, Yielded, Args extends any[] = []> = (this: This, awaited: AwaitYield, ...args: Args) => Generator<Yielded | Promise<Yielded>, Return, Yielded>;
export declare function maybeAsync<Return, This, Yielded>(that: This, startGenerator: (this: This, await: AwaitYield) => Generator<Yielded | Promise<Yielded>, Return, Yielded>): Return | Promise<Return>;
export declare function awaitEachYieldedPromise<Yielded, Returned>(gen: Generator<Yielded | Promise<Yielded>, Returned, Yielded>): Returned | Promise<Returned>;
export {};

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.awaitEachYieldedPromise = exports.maybeAsync = exports.maybeAsyncFn = void 0;
function* awaitYield(value) {
return (yield value);
}
function awaitYieldOf(generator) {
return awaitYield(awaitEachYieldedPromise(generator));
}
const AwaitYield = awaitYield;
AwaitYield.of = awaitYieldOf;
/**
* Create a function that may or may not be async, using a generator
*
* Within the generator, call `yield* awaited(maybePromise)` to await a value
* that may or may not be a promise.
*
* If the inner function never yields a promise, it will return synchronously.
*/
function maybeAsyncFn(that, fn) {
return (...args) => {
const generator = fn.call(that, AwaitYield, ...args);
return awaitEachYieldedPromise(generator);
};
}
exports.maybeAsyncFn = maybeAsyncFn;
class Example {
constructor() {
this.maybeAsyncMethod = maybeAsyncFn(this, function* (awaited, a) {
yield* awaited(new Promise((resolve) => setTimeout(resolve, a)));
return 5;
});
}
}
function maybeAsync(that, startGenerator) {
const generator = startGenerator.call(that, AwaitYield);
return awaitEachYieldedPromise(generator);
}
exports.maybeAsync = maybeAsync;
function awaitEachYieldedPromise(gen) {
function handleNextStep(step) {
if (step.done) {
return step.value;
}
if (step.value instanceof Promise) {
return step.value.then((value) => handleNextStep(gen.next(value)), (error) => handleNextStep(gen.throw(error)));
}
return handleNextStep(gen.next(step.value));
}
return handleNextStep(gen.next());
}
exports.awaitEachYieldedPromise = awaitEachYieldedPromise;
//# sourceMappingURL=asyncify-helpers.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"asyncify-helpers.js","sourceRoot":"","sources":["../ts/asyncify-helpers.ts"],"names":[],"mappings":";;;AAAA,QAAQ,CAAC,CAAC,UAAU,CAAI,KAAqB;IAC3C,OAAO,CAAC,MAAM,KAAK,CAAM,CAAA;AAC3B,CAAC;AAED,SAAS,YAAY,CACnB,SAA4D;IAE5D,OAAO,UAAU,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAA;AACvD,CAAC;AAMD,MAAM,UAAU,GAAe,UAAwB,CAAA;AACvD,UAAU,CAAC,EAAE,GAAG,YAAY,CAAA;AAE5B;;;;;;;GAOG;AACH,SAAgB,YAAY,CAS1B,IAAU,EACV,EAI2D;IAE3D,OAAO,CAAC,GAAG,IAAU,EAAE,EAAE;QACvB,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,CAAA;QACpD,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAA;IAC3C,CAAC,CAAA;AACH,CAAC;AApBD,oCAoBC;AAED,MAAM,OAAO;IAAb;QACU,qBAAgB,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAS;YACzE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YAChE,OAAO,CAAC,CAAA;QACV,CAAC,CAAC,CAAA;IACJ,CAAC;CAAA;AAQD,SAAgB,UAAU,CACxB,IAAU,EACV,cAG2D;IAE3D,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACvD,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAA;AAC3C,CAAC;AATD,gCASC;AAED,SAAgB,uBAAuB,CACrC,GAA6D;IAI7D,SAAS,cAAc,CAAC,IAAgB;QACtC,IAAI,IAAI,CAAC,IAAI,EAAE;YACb,OAAO,IAAI,CAAC,KAAK,CAAA;SAClB;QAED,IAAI,IAAI,CAAC,KAAK,YAAY,OAAO,EAAE;YACjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CACpB,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAC1C,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAC5C,CAAA;SACF;QAED,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;AACnC,CAAC;AArBD,0DAqBC","sourcesContent":["function* awaitYield<T>(value: T | Promise<T>) {\n return (yield value) as T\n}\n\nfunction awaitYieldOf<T, Yielded>(\n generator: Generator<Yielded | Promise<Yielded>, T, Yielded>\n): Generator<T | Promise<T>, T, T> {\n return awaitYield(awaitEachYieldedPromise(generator))\n}\n\nexport type AwaitYield = typeof awaitYield & {\n of: typeof awaitYieldOf\n}\n\nconst AwaitYield: AwaitYield = awaitYield as AwaitYield\nAwaitYield.of = awaitYieldOf\n\n/**\n * Create a function that may or may not be async, using a generator\n *\n * Within the generator, call `yield* awaited(maybePromise)` to await a value\n * that may or may not be a promise.\n *\n * If the inner function never yields a promise, it will return synchronously.\n */\nexport function maybeAsyncFn<\n /** Function arguments */\n Args extends any[],\n This,\n /** Function return type */\n Return,\n /** Yields to unwrap */\n Yielded\n>(\n that: This,\n fn: (\n this: This,\n awaited: AwaitYield,\n ...args: Args\n ) => Generator<Yielded | Promise<Yielded>, Return, Yielded>\n): (...args: Args) => Return | Promise<Return> {\n return (...args: Args) => {\n const generator = fn.call(that, AwaitYield, ...args)\n return awaitEachYieldedPromise(generator)\n }\n}\n\nclass Example {\n private maybeAsyncMethod = maybeAsyncFn(this, function* (awaited, a: number) {\n yield* awaited(new Promise((resolve) => setTimeout(resolve, a)))\n return 5\n })\n}\n\nexport type MaybeAsyncBlock<Return, This, Yielded, Args extends any[] = []> = (\n this: This,\n awaited: AwaitYield,\n ...args: Args\n) => Generator<Yielded | Promise<Yielded>, Return, Yielded>\n\nexport function maybeAsync<Return, This, Yielded>(\n that: This,\n startGenerator: (\n this: This,\n await: AwaitYield\n ) => Generator<Yielded | Promise<Yielded>, Return, Yielded>\n): Return | Promise<Return> {\n const generator = startGenerator.call(that, AwaitYield)\n return awaitEachYieldedPromise(generator)\n}\n\nexport function awaitEachYieldedPromise<Yielded, Returned>(\n gen: Generator<Yielded | Promise<Yielded>, Returned, Yielded>\n): Returned | Promise<Returned> {\n type NextResult = ReturnType<typeof gen.next>\n\n function handleNextStep(step: NextResult): Returned | Promise<Returned> {\n if (step.done) {\n return step.value\n }\n\n if (step.value instanceof Promise) {\n return step.value.then(\n (value) => handleNextStep(gen.next(value)),\n (error) => handleNextStep(gen.throw(error))\n )\n }\n\n return handleNextStep(gen.next(step.value))\n }\n\n return handleNextStep(gen.next())\n}\n"]}

View File

@@ -0,0 +1,48 @@
import { QuickJSContext } from "./context";
import { QuickJSAsyncEmscriptenModule } from "./emscripten-types";
import { QuickJSAsyncFFI } from "./variants";
import { JSRuntimePointer } from "./types-ffi";
import { Lifetime } from "./lifetime";
import { QuickJSModuleCallbacks } from "./module";
import { QuickJSAsyncRuntime } from "./runtime-asyncify";
import { ContextEvalOptions, QuickJSHandle } from "./types";
import { VmCallResult } from "./vm-interface";
export type AsyncFunctionImplementation = (this: QuickJSHandle, ...args: QuickJSHandle[]) => Promise<QuickJSHandle | VmCallResult<QuickJSHandle> | void>;
/**
* Asyncified version of [[QuickJSContext]].
*
* *Asyncify* allows normally synchronous code to wait for asynchronous Promises
* or callbacks. The asyncified version of QuickJSContext can wait for async
* host functions as though they were synchronous.
*/
export declare class QuickJSAsyncContext extends QuickJSContext {
runtime: QuickJSAsyncRuntime;
/** @private */
protected module: QuickJSAsyncEmscriptenModule;
/** @private */
protected ffi: QuickJSAsyncFFI;
/** @private */
protected rt: Lifetime<JSRuntimePointer>;
/** @private */
protected callbacks: QuickJSModuleCallbacks;
/**
* Asyncified version of [[evalCode]].
*/
evalCodeAsync(code: string, filename?: string,
/** See [[EvalFlags]] for number semantics */
options?: number | ContextEvalOptions): Promise<VmCallResult<QuickJSHandle>>;
/**
* Similar to [[newFunction]].
* Convert an async host Javascript function into a synchronous QuickJS function value.
*
* Whenever QuickJS calls this function, the VM's stack will be unwound while
* waiting the async function to complete, and then restored when the returned
* promise resolves.
*
* Asyncified functions must never call other asyncified functions or
* `import`, even indirectly, because the stack cannot be unwound twice.
*
* See [Emscripten's docs on Asyncify](https://emscripten.org/docs/porting/asyncify.html).
*/
newAsyncifiedFunction(name: string, fn: AsyncFunctionImplementation): QuickJSHandle;
}

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSAsyncContext = void 0;
const context_1 = require("./context");
const debug_1 = require("./debug");
const types_1 = require("./types");
/**
* Asyncified version of [[QuickJSContext]].
*
* *Asyncify* allows normally synchronous code to wait for asynchronous Promises
* or callbacks. The asyncified version of QuickJSContext can wait for async
* host functions as though they were synchronous.
*/
class QuickJSAsyncContext extends context_1.QuickJSContext {
/**
* Asyncified version of [[evalCode]].
*/
async evalCodeAsync(code, filename = "eval.js",
/** See [[EvalFlags]] for number semantics */
options) {
const detectModule = (options === undefined ? 1 : 0);
const flags = (0, types_1.evalOptionsToFlags)(options);
let resultPtr = 0;
try {
resultPtr = await this.memory
.newHeapCharPointer(code)
.consume((charHandle) => this.ffi.QTS_Eval_MaybeAsync(this.ctx.value, charHandle.value, filename, detectModule, flags));
}
catch (error) {
(0, debug_1.debugLog)("QTS_Eval_MaybeAsync threw", error);
throw error;
}
const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
if (errorPtr) {
this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
return { error: this.memory.heapValueHandle(errorPtr) };
}
return { value: this.memory.heapValueHandle(resultPtr) };
}
/**
* Similar to [[newFunction]].
* Convert an async host Javascript function into a synchronous QuickJS function value.
*
* Whenever QuickJS calls this function, the VM's stack will be unwound while
* waiting the async function to complete, and then restored when the returned
* promise resolves.
*
* Asyncified functions must never call other asyncified functions or
* `import`, even indirectly, because the stack cannot be unwound twice.
*
* See [Emscripten's docs on Asyncify](https://emscripten.org/docs/porting/asyncify.html).
*/
newAsyncifiedFunction(name, fn) {
return this.newFunction(name, fn);
}
}
exports.QuickJSAsyncContext = QuickJSAsyncContext;
//# sourceMappingURL=context-asyncify.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"context-asyncify.js","sourceRoot":"","sources":["../ts/context-asyncify.ts"],"names":[],"mappings":";;;AAAA,uCAA0C;AAC1C,mCAAkC;AAOlC,mCAA+E;AAQ/E;;;;;;GAMG;AACH,MAAa,mBAAoB,SAAQ,wBAAc;IAWrD;;OAEG;IACH,KAAK,CAAC,aAAa,CACjB,IAAY,EACZ,WAAmB,SAAS;IAC5B,6CAA6C;IAC7C,OAAqC;QAErC,MAAM,YAAY,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAqB,CAAA;QACxE,MAAM,KAAK,GAAG,IAAA,0BAAkB,EAAC,OAAO,CAAc,CAAA;QACtD,IAAI,SAAS,GAAG,CAAmB,CAAA;QACnC,IAAI;YACF,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM;iBAC1B,kBAAkB,CAAC,IAAI,CAAC;iBACxB,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE,CACtB,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAC1B,IAAI,CAAC,GAAG,CAAC,KAAK,EACd,UAAU,CAAC,KAAK,EAChB,QAAQ,EACR,YAAY,EACZ,KAAK,CACN,CACF,CAAA;SACJ;QAAC,OAAO,KAAK,EAAE;YACd,IAAA,gBAAQ,EAAC,2BAA2B,EAAE,KAAK,CAAC,CAAA;YAC5C,MAAM,KAAK,CAAA;SACZ;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QACzE,IAAI,QAAQ,EAAE;YACZ,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;YACxD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAA;SACxD;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAA;IAC1D,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,qBAAqB,CAAC,IAAY,EAAE,EAA+B;QACjE,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAS,CAAC,CAAA;IAC1C,CAAC;CACF;AA/DD,kDA+DC","sourcesContent":["import { QuickJSContext } from \"./context\"\nimport { debugLog } from \"./debug\"\nimport { QuickJSAsyncEmscriptenModule } from \"./emscripten-types\"\nimport { QuickJSAsyncFFI } from \"./variants\"\nimport { EvalDetectModule, EvalFlags, JSRuntimePointer, JSValuePointer } from \"./types-ffi\"\nimport { Lifetime } from \"./lifetime\"\nimport { QuickJSModuleCallbacks } from \"./module\"\nimport { QuickJSAsyncRuntime } from \"./runtime-asyncify\"\nimport { ContextEvalOptions, evalOptionsToFlags, QuickJSHandle } from \"./types\"\nimport { VmCallResult } from \"./vm-interface\"\n\nexport type AsyncFunctionImplementation = (\n this: QuickJSHandle,\n ...args: QuickJSHandle[]\n) => Promise<QuickJSHandle | VmCallResult<QuickJSHandle> | void>\n\n/**\n * Asyncified version of [[QuickJSContext]].\n *\n * *Asyncify* allows normally synchronous code to wait for asynchronous Promises\n * or callbacks. The asyncified version of QuickJSContext can wait for async\n * host functions as though they were synchronous.\n */\nexport class QuickJSAsyncContext extends QuickJSContext {\n public declare runtime: QuickJSAsyncRuntime\n /** @private */\n protected declare module: QuickJSAsyncEmscriptenModule\n /** @private */\n protected declare ffi: QuickJSAsyncFFI\n /** @private */\n protected declare rt: Lifetime<JSRuntimePointer>\n /** @private */\n protected declare callbacks: QuickJSModuleCallbacks\n\n /**\n * Asyncified version of [[evalCode]].\n */\n async evalCodeAsync(\n code: string,\n filename: string = \"eval.js\",\n /** See [[EvalFlags]] for number semantics */\n options?: number | ContextEvalOptions\n ): Promise<VmCallResult<QuickJSHandle>> {\n const detectModule = (options === undefined ? 1 : 0) as EvalDetectModule\n const flags = evalOptionsToFlags(options) as EvalFlags\n let resultPtr = 0 as JSValuePointer\n try {\n resultPtr = await this.memory\n .newHeapCharPointer(code)\n .consume((charHandle) =>\n this.ffi.QTS_Eval_MaybeAsync(\n this.ctx.value,\n charHandle.value,\n filename,\n detectModule,\n flags\n )\n )\n } catch (error) {\n debugLog(\"QTS_Eval_MaybeAsync threw\", error)\n throw error\n }\n const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr)\n if (errorPtr) {\n this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr)\n return { error: this.memory.heapValueHandle(errorPtr) }\n }\n return { value: this.memory.heapValueHandle(resultPtr) }\n }\n\n /**\n * Similar to [[newFunction]].\n * Convert an async host Javascript function into a synchronous QuickJS function value.\n *\n * Whenever QuickJS calls this function, the VM's stack will be unwound while\n * waiting the async function to complete, and then restored when the returned\n * promise resolves.\n *\n * Asyncified functions must never call other asyncified functions or\n * `import`, even indirectly, because the stack cannot be unwound twice.\n *\n * See [Emscripten's docs on Asyncify](https://emscripten.org/docs/porting/asyncify.html).\n */\n newAsyncifiedFunction(name: string, fn: AsyncFunctionImplementation): QuickJSHandle {\n return this.newFunction(name, fn as any)\n }\n}\n"]}

View File

@@ -0,0 +1,371 @@
import { QuickJSDeferredPromise } from "./deferred-promise";
import type { EitherModule } from "./emscripten-types";
import { JSBorrowedCharPointer, JSContextPointer, JSRuntimePointer, JSValueConstPointer, JSValuePointer } from "./types-ffi";
import { Disposable, Lifetime, Scope } from "./lifetime";
import { ModuleMemory } from "./memory";
import { QuickJSModuleCallbacks } from "./module";
import { QuickJSRuntime } from "./runtime";
import { ContextEvalOptions, EitherFFI, JSValue, PromiseExecutor, QuickJSHandle } from "./types";
import { LowLevelJavascriptVm, SuccessOrFail, VmCallResult, VmFunctionImplementation, VmPropertyDescriptor } from "./vm-interface";
/**
* Property key for getting or setting a property on a handle with
* [[QuickJSContext.getProp]], [[QuickJSContext.setProp]], or [[QuickJSContext.defineProp]].
*/
export type QuickJSPropertyKey = number | string | QuickJSHandle;
/**
* @private
*/
declare class ContextMemory extends ModuleMemory implements Disposable {
readonly owner: QuickJSRuntime;
readonly ctx: Lifetime<JSContextPointer>;
readonly rt: Lifetime<JSRuntimePointer>;
readonly module: EitherModule;
readonly ffi: EitherFFI;
readonly scope: Scope;
/** @private */
constructor(args: {
owner: QuickJSRuntime;
module: EitherModule;
ffi: EitherFFI;
ctx: Lifetime<JSContextPointer>;
rt: Lifetime<JSRuntimePointer>;
ownedLifetimes?: Disposable[];
});
get alive(): boolean;
dispose(): void;
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage<T extends Disposable>(lifetime: T): T;
copyJSValue: (ptr: JSValuePointer | JSValueConstPointer) => any;
freeJSValue: (ptr: JSValuePointer) => void;
consumeJSCharPointer(ptr: JSBorrowedCharPointer): string;
heapValueHandle(ptr: JSValuePointer): JSValue;
}
/**
* QuickJSContext wraps a QuickJS Javascript context (JSContext*) within a
* runtime. The contexts within the same runtime may exchange objects freely.
* You can think of separate runtimes like different domains in a browser, and
* the contexts within a runtime like the different windows open to the same
* domain. The {@link runtime} references the context's runtime.
*
* This class's methods return {@link QuickJSHandle}, which wrap C pointers (JSValue*).
* It's the caller's responsibility to call `.dispose()` on any
* handles you create to free memory once you're done with the handle.
*
* Use {@link QuickJSRuntime.newContext} or {@link QuickJSWASMModule.newContext}
* to create a new QuickJSContext.
*
* Create QuickJS values inside the interpreter with methods like
* [[newNumber]], [[newString]], [[newArray]], [[newObject]],
* [[newFunction]], and [[newPromise]].
*
* Call [[setProp]] or [[defineProp]] to customize objects. Use those methods
* with [[global]] to expose the values you create to the interior of the
* interpreter, so they can be used in [[evalCode]].
*
* Use [[evalCode]] or [[callFunction]] to execute Javascript inside the VM. If
* you're using asynchronous code inside the QuickJSContext, you may need to also
* call [[executePendingJobs]]. Executing code inside the runtime returns a
* result object representing successful execution or an error. You must dispose
* of any such results to avoid leaking memory inside the VM.
*
* Implement memory and CPU constraints at the runtime level, using [[runtime]].
* See {@link QuickJSRuntime} for more information.
*
*/
export declare class QuickJSContext implements LowLevelJavascriptVm<QuickJSHandle>, Disposable {
/**
* The runtime that created this context.
*/
readonly runtime: QuickJSRuntime;
/** @private */
protected readonly ctx: Lifetime<JSContextPointer>;
/** @private */
protected readonly rt: Lifetime<JSRuntimePointer>;
/** @private */
protected readonly module: EitherModule;
/** @private */
protected readonly ffi: EitherFFI;
/** @private */
protected memory: ContextMemory;
/** @private */
protected _undefined: QuickJSHandle | undefined;
/** @private */
protected _null: QuickJSHandle | undefined;
/** @private */
protected _false: QuickJSHandle | undefined;
/** @private */
protected _true: QuickJSHandle | undefined;
/** @private */
protected _global: QuickJSHandle | undefined;
/** @private */
protected _BigInt: QuickJSHandle | undefined;
/**
* Use {@link QuickJS.createVm} to create a QuickJSContext instance.
*/
constructor(args: {
module: EitherModule;
ffi: EitherFFI;
ctx: Lifetime<JSContextPointer>;
rt: Lifetime<JSRuntimePointer>;
runtime: QuickJSRuntime;
ownedLifetimes?: Disposable[];
callbacks: QuickJSModuleCallbacks;
});
get alive(): boolean;
/**
* Dispose of this VM's underlying resources.
*
* @throws Calling this method without disposing of all created handles
* will result in an error.
*/
dispose(): void;
/**
* [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined).
*/
get undefined(): QuickJSHandle;
/**
* [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null).
*/
get null(): QuickJSHandle;
/**
* [`true`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/true).
*/
get true(): QuickJSHandle;
/**
* [`false`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/false).
*/
get false(): QuickJSHandle;
/**
* [`global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects).
* A handle to the global object inside the interpreter.
* You can set properties to create global variables.
*/
get global(): QuickJSHandle;
/**
* Converts a Javascript number into a QuickJS value.
*/
newNumber(num: number): QuickJSHandle;
/**
* Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) value.
*/
newString(str: string): QuickJSHandle;
/**
* Create a QuickJS [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) value.
* No two symbols created with this function will be the same value.
*/
newUniqueSymbol(description: string | symbol): QuickJSHandle;
/**
* Get a symbol from the [global registry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry) for the given key.
* All symbols created with the same key will be the same value.
*/
newSymbolFor(key: string | symbol): QuickJSHandle;
/**
* Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) value.
*/
newBigInt(num: bigint): QuickJSHandle;
/**
* `{}`.
* Create a new QuickJS [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer).
*
* @param prototype - Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create).
*/
newObject(prototype?: QuickJSHandle): QuickJSHandle;
/**
* `[]`.
* Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
*/
newArray(): QuickJSHandle;
/**
* Create a new [[QuickJSDeferredPromise]]. Use `deferred.resolve(handle)` and
* `deferred.reject(handle)` to fulfill the promise handle available at `deferred.handle`.
* Note that you are responsible for calling `deferred.dispose()` to free the underlying
* resources; see the documentation on [[QuickJSDeferredPromise]] for details.
*/
newPromise(): QuickJSDeferredPromise;
/**
* Create a new [[QuickJSDeferredPromise]] that resolves when the
* given native Promise<QuickJSHandle> resolves. Rejections will be coerced
* to a QuickJS error.
*
* You can still resolve/reject the created promise "early" using its methods.
*/
newPromise(promise: Promise<QuickJSHandle>): QuickJSDeferredPromise;
/**
* Construct a new native Promise<QuickJSHandle>, and then convert it into a
* [[QuickJSDeferredPromise]].
*
* You can still resolve/reject the created promise "early" using its methods.
*/
newPromise(newPromiseFn: PromiseExecutor<QuickJSHandle, Error | QuickJSHandle>): QuickJSDeferredPromise;
/**
* Convert a Javascript function into a QuickJS function value.
* See [[VmFunctionImplementation]] for more details.
*
* A [[VmFunctionImplementation]] should not free its arguments or its return
* value. A VmFunctionImplementation should also not retain any references to
* its return value.
*
* To implement an async function, create a promise with [[newPromise]], then
* return the deferred promise handle from `deferred.handle` from your
* function implementation:
*
* ```
* const deferred = vm.newPromise()
* someNativeAsyncFunction().then(deferred.resolve)
* return deferred.handle
* ```
*/
newFunction(name: string, fn: VmFunctionImplementation<QuickJSHandle>): QuickJSHandle;
newError(error: {
name: string;
message: string;
}): QuickJSHandle;
newError(message: string): QuickJSHandle;
newError(): QuickJSHandle;
/**
* `typeof` operator. **Not** [standards compliant](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof).
*
* @remarks
* Does not support BigInt values correctly.
*/
typeof(handle: QuickJSHandle): string;
/**
* Converts `handle` into a Javascript number.
* @returns `NaN` on error, otherwise a `number`.
*/
getNumber(handle: QuickJSHandle): number;
/**
* Converts `handle` to a Javascript string.
*/
getString(handle: QuickJSHandle): string;
/**
* Converts `handle` into a Javascript symbol. If the symbol is in the global
* registry in the guest, it will be created with Symbol.for on the host.
*/
getSymbol(handle: QuickJSHandle): symbol;
/**
* Converts `handle` to a Javascript bigint.
*/
getBigInt(handle: QuickJSHandle): bigint;
/**
* `Promise.resolve(value)`.
* Convert a handle containing a Promise-like value inside the VM into an
* actual promise on the host.
*
* @remarks
* You may need to call [[executePendingJobs]] to ensure that the promise is resolved.
*
* @param promiseLikeHandle - A handle to a Promise-like value with a `.then(onSuccess, onError)` method.
*/
resolvePromise(promiseLikeHandle: QuickJSHandle): Promise<VmCallResult<QuickJSHandle>>;
/**
* `handle[key]`.
* Get a property from a JSValue.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string (which will be converted automatically).
*/
getProp(handle: QuickJSHandle, key: QuickJSPropertyKey): QuickJSHandle;
/**
* `handle[key] = value`.
* Set a property on a JSValue.
*
* @remarks
* Note that the QuickJS authors recommend using [[defineProp]] to define new
* properties.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
setProp(handle: QuickJSHandle, key: QuickJSPropertyKey, value: QuickJSHandle): void;
/**
* [`Object.defineProperty(handle, key, descriptor)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
defineProp(handle: QuickJSHandle, key: QuickJSPropertyKey, descriptor: VmPropertyDescriptor<QuickJSHandle>): void;
/**
* [`func.call(thisVal, ...args)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call).
* Call a JSValue as a function.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* @returns A result. If the function threw synchronously, `result.error` be a
* handle to the exception. Otherwise `result.value` will be a handle to the
* value.
*/
callFunction(func: QuickJSHandle, thisVal: QuickJSHandle, ...args: QuickJSHandle[]): VmCallResult<QuickJSHandle>;
/**
* Like [`eval(code)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Description).
* Evaluates the Javascript source `code` in the global scope of this VM.
* When working with async code, you many need to call [[executePendingJobs]]
* to execute callbacks pending after synchronous evaluation returns.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* *Note*: to protect against infinite loops, provide an interrupt handler to
* [[setInterruptHandler]]. You can use [[shouldInterruptAfterDeadline]] to
* create a time-based deadline.
*
* @returns The last statement's value. If the code threw synchronously,
* `result.error` will be a handle to the exception. If execution was
* interrupted, the error will have name `InternalError` and message
* `interrupted`.
*/
evalCode(code: string, filename?: string,
/**
* If no options are passed, a heuristic will be used to detect if `code` is
* an ES module.
*
* See [[EvalFlags]] for number semantics.
*/
options?: number | ContextEvalOptions): VmCallResult<QuickJSHandle>;
/**
* Throw an error in the VM, interrupted whatever current execution is in progress when execution resumes.
* @experimental
*/
throw(error: Error | QuickJSHandle): any;
/**
* @private
*/
protected borrowPropertyKey(key: QuickJSPropertyKey): QuickJSHandle;
/**
* @private
*/
getMemory(rt: JSRuntimePointer): ContextMemory;
/**
* Dump a JSValue to Javascript in a best-effort fashion.
* Returns `handle.toString()` if it cannot be serialized to JSON.
*/
dump(handle: QuickJSHandle): any;
/**
* Unwrap a SuccessOrFail result such as a [[VmCallResult]] or a
* [[ExecutePendingJobsResult]], where the fail branch contains a handle to a QuickJS error value.
* If the result is a success, returns the value.
* If the result is an error, converts the error to a native object and throws the error.
*/
unwrapResult<T>(result: SuccessOrFail<T, QuickJSHandle>): T;
/** @private */
protected fnNextId: number;
/** @private */
protected fnMaps: Map<number, Map<number, VmFunctionImplementation<QuickJSHandle>>>;
/** @private */
protected getFunction(fn_id: number): VmFunctionImplementation<QuickJSHandle> | undefined;
/** @private */
protected setFunction(fn_id: number, handle: VmFunctionImplementation<QuickJSHandle>): Map<number, VmFunctionImplementation<QuickJSHandle>>;
/**
* @hidden
*/
private cToHostCallbacks;
private errorToHandle;
}
export {};

View File

@@ -0,0 +1,691 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSContext = void 0;
const debug_1 = require("./debug");
const deferred_promise_1 = require("./deferred-promise");
const errors_1 = require("./errors");
const lifetime_1 = require("./lifetime");
const memory_1 = require("./memory");
const types_1 = require("./types");
/**
* @private
*/
class ContextMemory extends memory_1.ModuleMemory {
/** @private */
constructor(args) {
super(args.module);
this.scope = new lifetime_1.Scope();
this.copyJSValue = (ptr) => {
return this.ffi.QTS_DupValuePointer(this.ctx.value, ptr);
};
this.freeJSValue = (ptr) => {
this.ffi.QTS_FreeValuePointer(this.ctx.value, ptr);
};
args.ownedLifetimes?.forEach((lifetime) => this.scope.manage(lifetime));
this.owner = args.owner;
this.module = args.module;
this.ffi = args.ffi;
this.rt = args.rt;
this.ctx = this.scope.manage(args.ctx);
}
get alive() {
return this.scope.alive;
}
dispose() {
return this.scope.dispose();
}
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage(lifetime) {
return this.scope.manage(lifetime);
}
consumeJSCharPointer(ptr) {
const str = this.module.UTF8ToString(ptr);
this.ffi.QTS_FreeCString(this.ctx.value, ptr);
return str;
}
heapValueHandle(ptr) {
return new lifetime_1.Lifetime(ptr, this.copyJSValue, this.freeJSValue, this.owner);
}
}
/**
* QuickJSContext wraps a QuickJS Javascript context (JSContext*) within a
* runtime. The contexts within the same runtime may exchange objects freely.
* You can think of separate runtimes like different domains in a browser, and
* the contexts within a runtime like the different windows open to the same
* domain. The {@link runtime} references the context's runtime.
*
* This class's methods return {@link QuickJSHandle}, which wrap C pointers (JSValue*).
* It's the caller's responsibility to call `.dispose()` on any
* handles you create to free memory once you're done with the handle.
*
* Use {@link QuickJSRuntime.newContext} or {@link QuickJSWASMModule.newContext}
* to create a new QuickJSContext.
*
* Create QuickJS values inside the interpreter with methods like
* [[newNumber]], [[newString]], [[newArray]], [[newObject]],
* [[newFunction]], and [[newPromise]].
*
* Call [[setProp]] or [[defineProp]] to customize objects. Use those methods
* with [[global]] to expose the values you create to the interior of the
* interpreter, so they can be used in [[evalCode]].
*
* Use [[evalCode]] or [[callFunction]] to execute Javascript inside the VM. If
* you're using asynchronous code inside the QuickJSContext, you may need to also
* call [[executePendingJobs]]. Executing code inside the runtime returns a
* result object representing successful execution or an error. You must dispose
* of any such results to avoid leaking memory inside the VM.
*
* Implement memory and CPU constraints at the runtime level, using [[runtime]].
* See {@link QuickJSRuntime} for more information.
*
*/
// TODO: Manage own callback registration
class QuickJSContext {
/**
* Use {@link QuickJS.createVm} to create a QuickJSContext instance.
*/
constructor(args) {
/** @private */
this._undefined = undefined;
/** @private */
this._null = undefined;
/** @private */
this._false = undefined;
/** @private */
this._true = undefined;
/** @private */
this._global = undefined;
/** @private */
this._BigInt = undefined;
/** @private */
this.fnNextId = -32768; // min value of signed 16bit int used by Quickjs
/** @private */
this.fnMaps = new Map();
/**
* @hidden
*/
this.cToHostCallbacks = {
callFunction: (ctx, this_ptr, argc, argv, fn_id) => {
if (ctx !== this.ctx.value) {
throw new Error("QuickJSContext instance received C -> JS call with mismatched ctx");
}
const fn = this.getFunction(fn_id);
if (!fn) {
// this "throw" is not catch-able from the TS side. could we somehow handle this higher up?
throw new Error(`QuickJSContext had no callback with id ${fn_id}`);
}
return lifetime_1.Scope.withScopeMaybeAsync(this, function* (awaited, scope) {
const thisHandle = scope.manage(new lifetime_1.WeakLifetime(this_ptr, this.memory.copyJSValue, this.memory.freeJSValue, this.runtime));
const argHandles = new Array(argc);
for (let i = 0; i < argc; i++) {
const ptr = this.ffi.QTS_ArgvGetJSValueConstPointer(argv, i);
argHandles[i] = scope.manage(new lifetime_1.WeakLifetime(ptr, this.memory.copyJSValue, this.memory.freeJSValue, this.runtime));
}
try {
const result = yield* awaited(fn.apply(thisHandle, argHandles));
if (result) {
if ("error" in result && result.error) {
(0, debug_1.debugLog)("throw error", result.error);
throw result.error;
}
const handle = scope.manage(result instanceof lifetime_1.Lifetime ? result : result.value);
return this.ffi.QTS_DupValuePointer(this.ctx.value, handle.value);
}
return 0;
}
catch (error) {
return this.errorToHandle(error).consume((errorHandle) => this.ffi.QTS_Throw(this.ctx.value, errorHandle.value));
}
});
},
};
this.runtime = args.runtime;
this.module = args.module;
this.ffi = args.ffi;
this.rt = args.rt;
this.ctx = args.ctx;
this.memory = new ContextMemory({
...args,
owner: this.runtime,
});
args.callbacks.setContextCallbacks(this.ctx.value, this.cToHostCallbacks);
this.dump = this.dump.bind(this);
this.getString = this.getString.bind(this);
this.getNumber = this.getNumber.bind(this);
this.resolvePromise = this.resolvePromise.bind(this);
}
// @implement Disposable ----------------------------------------------------
get alive() {
return this.memory.alive;
}
/**
* Dispose of this VM's underlying resources.
*
* @throws Calling this method without disposing of all created handles
* will result in an error.
*/
dispose() {
this.memory.dispose();
}
// Globals ------------------------------------------------------------------
/**
* [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined).
*/
get undefined() {
if (this._undefined) {
return this._undefined;
}
// Undefined is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetUndefined();
return (this._undefined = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null).
*/
get null() {
if (this._null) {
return this._null;
}
// Null is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetNull();
return (this._null = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`true`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/true).
*/
get true() {
if (this._true) {
return this._true;
}
// True is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetTrue();
return (this._true = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`false`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/false).
*/
get false() {
if (this._false) {
return this._false;
}
// False is a constant, immutable value in QuickJS.
const ptr = this.ffi.QTS_GetFalse();
return (this._false = new lifetime_1.StaticLifetime(ptr));
}
/**
* [`global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects).
* A handle to the global object inside the interpreter.
* You can set properties to create global variables.
*/
get global() {
if (this._global) {
return this._global;
}
// The global is a JSValue, but since it's lifetime is as long as the VM's,
// we should manage it.
const ptr = this.ffi.QTS_GetGlobalObject(this.ctx.value);
// Automatically clean up this reference when we dispose
this.memory.manage(this.memory.heapValueHandle(ptr));
// This isn't technically a static lifetime, but since it has the same
// lifetime as the VM, it's okay to fake one since when the VM is
// disposed, no other functions will accept the value.
this._global = new lifetime_1.StaticLifetime(ptr, this.runtime);
return this._global;
}
// New values ---------------------------------------------------------------
/**
* Converts a Javascript number into a QuickJS value.
*/
newNumber(num) {
return this.memory.heapValueHandle(this.ffi.QTS_NewFloat64(this.ctx.value, num));
}
/**
* Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) value.
*/
newString(str) {
const ptr = this.memory
.newHeapCharPointer(str)
.consume((charHandle) => this.ffi.QTS_NewString(this.ctx.value, charHandle.value));
return this.memory.heapValueHandle(ptr);
}
/**
* Create a QuickJS [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) value.
* No two symbols created with this function will be the same value.
*/
newUniqueSymbol(description) {
const key = (typeof description === "symbol" ? description.description : description) ?? "";
const ptr = this.memory
.newHeapCharPointer(key)
.consume((charHandle) => this.ffi.QTS_NewSymbol(this.ctx.value, charHandle.value, 0));
return this.memory.heapValueHandle(ptr);
}
/**
* Get a symbol from the [global registry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry) for the given key.
* All symbols created with the same key will be the same value.
*/
newSymbolFor(key) {
const description = (typeof key === "symbol" ? key.description : key) ?? "";
const ptr = this.memory
.newHeapCharPointer(description)
.consume((charHandle) => this.ffi.QTS_NewSymbol(this.ctx.value, charHandle.value, 1));
return this.memory.heapValueHandle(ptr);
}
/**
* Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) value.
*/
newBigInt(num) {
if (!this._BigInt) {
const bigIntHandle = this.getProp(this.global, "BigInt");
this.memory.manage(bigIntHandle);
this._BigInt = new lifetime_1.StaticLifetime(bigIntHandle.value, this.runtime);
}
const bigIntHandle = this._BigInt;
const asString = String(num);
return this.newString(asString).consume((handle) => this.unwrapResult(this.callFunction(bigIntHandle, this.undefined, handle)));
}
/**
* `{}`.
* Create a new QuickJS [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer).
*
* @param prototype - Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create).
*/
newObject(prototype) {
if (prototype) {
this.runtime.assertOwned(prototype);
}
const ptr = prototype
? this.ffi.QTS_NewObjectProto(this.ctx.value, prototype.value)
: this.ffi.QTS_NewObject(this.ctx.value);
return this.memory.heapValueHandle(ptr);
}
/**
* `[]`.
* Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
*/
newArray() {
const ptr = this.ffi.QTS_NewArray(this.ctx.value);
return this.memory.heapValueHandle(ptr);
}
newPromise(value) {
const deferredPromise = lifetime_1.Scope.withScope((scope) => {
const mutablePointerArray = scope.manage(this.memory.newMutablePointerArray(2));
const promisePtr = this.ffi.QTS_NewPromiseCapability(this.ctx.value, mutablePointerArray.value.ptr);
const promiseHandle = this.memory.heapValueHandle(promisePtr);
const [resolveHandle, rejectHandle] = Array.from(mutablePointerArray.value.typedArray).map((jsvaluePtr) => this.memory.heapValueHandle(jsvaluePtr));
return new deferred_promise_1.QuickJSDeferredPromise({
context: this,
promiseHandle,
resolveHandle,
rejectHandle,
});
});
if (value && typeof value === "function") {
value = new Promise(value);
}
if (value) {
Promise.resolve(value).then(deferredPromise.resolve, (error) => error instanceof lifetime_1.Lifetime
? deferredPromise.reject(error)
: this.newError(error).consume(deferredPromise.reject));
}
return deferredPromise;
}
/**
* Convert a Javascript function into a QuickJS function value.
* See [[VmFunctionImplementation]] for more details.
*
* A [[VmFunctionImplementation]] should not free its arguments or its return
* value. A VmFunctionImplementation should also not retain any references to
* its return value.
*
* To implement an async function, create a promise with [[newPromise]], then
* return the deferred promise handle from `deferred.handle` from your
* function implementation:
*
* ```
* const deferred = vm.newPromise()
* someNativeAsyncFunction().then(deferred.resolve)
* return deferred.handle
* ```
*/
newFunction(name, fn) {
const fnId = ++this.fnNextId;
this.setFunction(fnId, fn);
return this.memory.heapValueHandle(this.ffi.QTS_NewFunction(this.ctx.value, fnId, name));
}
newError(error) {
const errorHandle = this.memory.heapValueHandle(this.ffi.QTS_NewError(this.ctx.value));
if (error && typeof error === "object") {
if (error.name !== undefined) {
this.newString(error.name).consume((handle) => this.setProp(errorHandle, "name", handle));
}
if (error.message !== undefined) {
this.newString(error.message).consume((handle) => this.setProp(errorHandle, "message", handle));
}
}
else if (typeof error === "string") {
this.newString(error).consume((handle) => this.setProp(errorHandle, "message", handle));
}
else if (error !== undefined) {
// This isn't supported in the type signature but maybe it will make life easier.
this.newString(String(error)).consume((handle) => this.setProp(errorHandle, "message", handle));
}
return errorHandle;
}
// Read values --------------------------------------------------------------
/**
* `typeof` operator. **Not** [standards compliant](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof).
*
* @remarks
* Does not support BigInt values correctly.
*/
typeof(handle) {
this.runtime.assertOwned(handle);
return this.memory.consumeHeapCharPointer(this.ffi.QTS_Typeof(this.ctx.value, handle.value));
}
/**
* Converts `handle` into a Javascript number.
* @returns `NaN` on error, otherwise a `number`.
*/
getNumber(handle) {
this.runtime.assertOwned(handle);
return this.ffi.QTS_GetFloat64(this.ctx.value, handle.value);
}
/**
* Converts `handle` to a Javascript string.
*/
getString(handle) {
this.runtime.assertOwned(handle);
return this.memory.consumeJSCharPointer(this.ffi.QTS_GetString(this.ctx.value, handle.value));
}
/**
* Converts `handle` into a Javascript symbol. If the symbol is in the global
* registry in the guest, it will be created with Symbol.for on the host.
*/
getSymbol(handle) {
this.runtime.assertOwned(handle);
const key = this.memory.consumeJSCharPointer(this.ffi.QTS_GetSymbolDescriptionOrKey(this.ctx.value, handle.value));
const isGlobal = this.ffi.QTS_IsGlobalSymbol(this.ctx.value, handle.value);
return isGlobal ? Symbol.for(key) : Symbol(key);
}
/**
* Converts `handle` to a Javascript bigint.
*/
getBigInt(handle) {
this.runtime.assertOwned(handle);
const asString = this.getString(handle);
return BigInt(asString);
}
/**
* `Promise.resolve(value)`.
* Convert a handle containing a Promise-like value inside the VM into an
* actual promise on the host.
*
* @remarks
* You may need to call [[executePendingJobs]] to ensure that the promise is resolved.
*
* @param promiseLikeHandle - A handle to a Promise-like value with a `.then(onSuccess, onError)` method.
*/
resolvePromise(promiseLikeHandle) {
this.runtime.assertOwned(promiseLikeHandle);
const vmResolveResult = lifetime_1.Scope.withScope((scope) => {
const vmPromise = scope.manage(this.getProp(this.global, "Promise"));
const vmPromiseResolve = scope.manage(this.getProp(vmPromise, "resolve"));
return this.callFunction(vmPromiseResolve, vmPromise, promiseLikeHandle);
});
if (vmResolveResult.error) {
return Promise.resolve(vmResolveResult);
}
return new Promise((resolve) => {
lifetime_1.Scope.withScope((scope) => {
const resolveHandle = scope.manage(this.newFunction("resolve", (value) => {
resolve({ value: value && value.dup() });
}));
const rejectHandle = scope.manage(this.newFunction("reject", (error) => {
resolve({ error: error && error.dup() });
}));
const promiseHandle = scope.manage(vmResolveResult.value);
const promiseThenHandle = scope.manage(this.getProp(promiseHandle, "then"));
this.unwrapResult(this.callFunction(promiseThenHandle, promiseHandle, resolveHandle, rejectHandle)).dispose();
});
});
}
// Properties ---------------------------------------------------------------
/**
* `handle[key]`.
* Get a property from a JSValue.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string (which will be converted automatically).
*/
getProp(handle, key) {
this.runtime.assertOwned(handle);
const ptr = this.borrowPropertyKey(key).consume((quickJSKey) => this.ffi.QTS_GetProp(this.ctx.value, handle.value, quickJSKey.value));
const result = this.memory.heapValueHandle(ptr);
return result;
}
/**
* `handle[key] = value`.
* Set a property on a JSValue.
*
* @remarks
* Note that the QuickJS authors recommend using [[defineProp]] to define new
* properties.
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
setProp(handle, key, value) {
this.runtime.assertOwned(handle);
// free newly allocated value if key was a string or number. No-op if string was already
// a QuickJS handle.
this.borrowPropertyKey(key).consume((quickJSKey) => this.ffi.QTS_SetProp(this.ctx.value, handle.value, quickJSKey.value, value.value));
}
/**
* [`Object.defineProperty(handle, key, descriptor)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
*
* @param key - The property may be specified as a JSValue handle, or as a
* Javascript string or number (which will be converted automatically to a JSValue).
*/
defineProp(handle, key, descriptor) {
this.runtime.assertOwned(handle);
lifetime_1.Scope.withScope((scope) => {
const quickJSKey = scope.manage(this.borrowPropertyKey(key));
const value = descriptor.value || this.undefined;
const configurable = Boolean(descriptor.configurable);
const enumerable = Boolean(descriptor.enumerable);
const hasValue = Boolean(descriptor.value);
const get = descriptor.get
? scope.manage(this.newFunction(descriptor.get.name, descriptor.get))
: this.undefined;
const set = descriptor.set
? scope.manage(this.newFunction(descriptor.set.name, descriptor.set))
: this.undefined;
this.ffi.QTS_DefineProp(this.ctx.value, handle.value, quickJSKey.value, value.value, get.value, set.value, configurable, enumerable, hasValue);
});
}
// Evaluation ---------------------------------------------------------------
/**
* [`func.call(thisVal, ...args)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call).
* Call a JSValue as a function.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* @returns A result. If the function threw synchronously, `result.error` be a
* handle to the exception. Otherwise `result.value` will be a handle to the
* value.
*/
callFunction(func, thisVal, ...args) {
this.runtime.assertOwned(func);
const resultPtr = this.memory
.toPointerArray(args)
.consume((argsArrayPtr) => this.ffi.QTS_Call(this.ctx.value, func.value, thisVal.value, args.length, argsArrayPtr.value));
const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
if (errorPtr) {
this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
return { error: this.memory.heapValueHandle(errorPtr) };
}
return { value: this.memory.heapValueHandle(resultPtr) };
}
/**
* Like [`eval(code)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Description).
* Evaluates the Javascript source `code` in the global scope of this VM.
* When working with async code, you many need to call [[executePendingJobs]]
* to execute callbacks pending after synchronous evaluation returns.
*
* See [[unwrapResult]], which will throw if the function returned an error, or
* return the result handle directly. If evaluation returned a handle containing
* a promise, use [[resolvePromise]] to convert it to a native promise and
* [[executePendingJobs]] to finish evaluating the promise.
*
* *Note*: to protect against infinite loops, provide an interrupt handler to
* [[setInterruptHandler]]. You can use [[shouldInterruptAfterDeadline]] to
* create a time-based deadline.
*
* @returns The last statement's value. If the code threw synchronously,
* `result.error` will be a handle to the exception. If execution was
* interrupted, the error will have name `InternalError` and message
* `interrupted`.
*/
evalCode(code, filename = "eval.js",
/**
* If no options are passed, a heuristic will be used to detect if `code` is
* an ES module.
*
* See [[EvalFlags]] for number semantics.
*/
options) {
const detectModule = (options === undefined ? 1 : 0);
const flags = (0, types_1.evalOptionsToFlags)(options);
const resultPtr = this.memory
.newHeapCharPointer(code)
.consume((charHandle) => this.ffi.QTS_Eval(this.ctx.value, charHandle.value, filename, detectModule, flags));
const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
if (errorPtr) {
this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
return { error: this.memory.heapValueHandle(errorPtr) };
}
return { value: this.memory.heapValueHandle(resultPtr) };
}
/**
* Throw an error in the VM, interrupted whatever current execution is in progress when execution resumes.
* @experimental
*/
throw(error) {
return this.errorToHandle(error).consume((handle) => this.ffi.QTS_Throw(this.ctx.value, handle.value));
}
/**
* @private
*/
borrowPropertyKey(key) {
if (typeof key === "number") {
return this.newNumber(key);
}
if (typeof key === "string") {
return this.newString(key);
}
// key is already a JSValue, but we're borrowing it. Return a static handle
// for internal use only.
return new lifetime_1.StaticLifetime(key.value, this.runtime);
}
/**
* @private
*/
getMemory(rt) {
if (rt === this.rt.value) {
return this.memory;
}
else {
throw new Error("Private API. Cannot get memory from a different runtime");
}
}
// Utilities ----------------------------------------------------------------
/**
* Dump a JSValue to Javascript in a best-effort fashion.
* Returns `handle.toString()` if it cannot be serialized to JSON.
*/
dump(handle) {
this.runtime.assertOwned(handle);
const type = this.typeof(handle);
if (type === "string") {
return this.getString(handle);
}
else if (type === "number") {
return this.getNumber(handle);
}
else if (type === "bigint") {
return this.getBigInt(handle);
}
else if (type === "undefined") {
return undefined;
}
else if (type === "symbol") {
return this.getSymbol(handle);
}
const str = this.memory.consumeJSCharPointer(this.ffi.QTS_Dump(this.ctx.value, handle.value));
try {
return JSON.parse(str);
}
catch (err) {
return str;
}
}
/**
* Unwrap a SuccessOrFail result such as a [[VmCallResult]] or a
* [[ExecutePendingJobsResult]], where the fail branch contains a handle to a QuickJS error value.
* If the result is a success, returns the value.
* If the result is an error, converts the error to a native object and throws the error.
*/
unwrapResult(result) {
if (result.error) {
const context = "context" in result.error ? result.error.context : this;
const cause = result.error.consume((error) => this.dump(error));
if (cause && typeof cause === "object" && typeof cause.message === "string") {
const { message, name, stack } = cause;
const exception = new errors_1.QuickJSUnwrapError("");
const hostStack = exception.stack;
if (typeof name === "string") {
exception.name = cause.name;
}
if (typeof stack === "string") {
exception.stack = `${name}: ${message}\n${cause.stack}Host: ${hostStack}`;
}
Object.assign(exception, { cause, context, message });
throw exception;
}
throw new errors_1.QuickJSUnwrapError(cause, context);
}
return result.value;
}
/** @private */
getFunction(fn_id) {
const map_id = fn_id >> 8;
const fnMap = this.fnMaps.get(map_id);
if (!fnMap) {
return undefined;
}
return fnMap.get(fn_id);
}
/** @private */
setFunction(fn_id, handle) {
const map_id = fn_id >> 8;
let fnMap = this.fnMaps.get(map_id);
if (!fnMap) {
fnMap = new Map();
this.fnMaps.set(map_id, fnMap);
}
return fnMap.set(fn_id, handle);
}
errorToHandle(error) {
if (error instanceof lifetime_1.Lifetime) {
return error;
}
return this.newError(error);
}
}
exports.QuickJSContext = QuickJSContext;
//# sourceMappingURL=context.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
export declare const QTS_DEBUG: boolean;
export declare let debugLog: {
(...data: any[]): void;
(message?: any, ...optionalParams: any[]): void;
};

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.debugLog = exports.QTS_DEBUG = void 0;
exports.QTS_DEBUG = false || Boolean(typeof process === "object" && process.env.QTS_DEBUG);
exports.debugLog = exports.QTS_DEBUG ? console.log.bind(console) : () => { };
//# sourceMappingURL=debug.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../ts/debug.ts"],"names":[],"mappings":";;;AAAa,QAAA,SAAS,GAAG,KAAK,IAAI,OAAO,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;AACpF,QAAA,QAAQ,GAAG,iBAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAA","sourcesContent":["export const QTS_DEBUG = false || Boolean(typeof process === \"object\" && process.env.QTS_DEBUG)\nexport let debugLog = QTS_DEBUG ? console.log.bind(console) : () => {}\n"]}

View File

@@ -0,0 +1,75 @@
import type { Disposable } from "./lifetime";
import type { QuickJSHandle } from "./types";
import type { QuickJSRuntime } from "./runtime";
import type { QuickJSContext } from "./context";
export type { PromiseExecutor } from "./types";
/**
* QuickJSDeferredPromise wraps a QuickJS promise [[handle]] and allows
* [[resolve]]ing or [[reject]]ing that promise. Use it to bridge asynchronous
* code on the host to APIs inside a QuickJSContext.
*
* Managing the lifetime of promises is tricky. There are three
* [[QuickJSHandle]]s inside of each deferred promise object: (1) the promise
* itself, (2) the `resolve` callback, and (3) the `reject` callback.
*
* - If the promise will be fulfilled before the end of it's [[owner]]'s lifetime,
* the only cleanup necessary is `deferred.handle.dispose()`, because
* calling [[resolve]] or [[reject]] will dispose of both callbacks automatically.
*
* - As the return value of a [[VmFunctionImplementation]], return [[handle]],
* and ensure that either [[resolve]] or [[reject]] will be called. No other
* clean-up is necessary.
*
* - In other cases, call [[dispose]], which will dispose [[handle]] as well as the
* QuickJS handles that back [[resolve]] and [[reject]]. For this object,
* [[dispose]] is idempotent.
*/
export declare class QuickJSDeferredPromise implements Disposable {
owner: QuickJSRuntime;
context: QuickJSContext;
/**
* A handle of the Promise instance inside the QuickJSContext.
* You must dispose [[handle]] or the entire QuickJSDeferredPromise once you
* are finished with it.
*/
handle: QuickJSHandle;
/**
* A native promise that will resolve once this deferred is settled.
*/
settled: Promise<void>;
private resolveHandle;
private rejectHandle;
private onSettled;
/**
* Use [[QuickJSContext.newPromise]] to create a new promise instead of calling
* this constructor directly.
* @unstable
*/
constructor(args: {
context: QuickJSContext;
promiseHandle: QuickJSHandle;
resolveHandle: QuickJSHandle;
rejectHandle: QuickJSHandle;
});
/**
* Resolve [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after resolving a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
resolve: (value?: QuickJSHandle) => void;
/**
* Reject [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after rejecting a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
reject: (value?: QuickJSHandle) => void;
get alive(): boolean;
dispose: () => void;
private disposeResolvers;
}

View File

@@ -0,0 +1,96 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSDeferredPromise = void 0;
/**
* QuickJSDeferredPromise wraps a QuickJS promise [[handle]] and allows
* [[resolve]]ing or [[reject]]ing that promise. Use it to bridge asynchronous
* code on the host to APIs inside a QuickJSContext.
*
* Managing the lifetime of promises is tricky. There are three
* [[QuickJSHandle]]s inside of each deferred promise object: (1) the promise
* itself, (2) the `resolve` callback, and (3) the `reject` callback.
*
* - If the promise will be fulfilled before the end of it's [[owner]]'s lifetime,
* the only cleanup necessary is `deferred.handle.dispose()`, because
* calling [[resolve]] or [[reject]] will dispose of both callbacks automatically.
*
* - As the return value of a [[VmFunctionImplementation]], return [[handle]],
* and ensure that either [[resolve]] or [[reject]] will be called. No other
* clean-up is necessary.
*
* - In other cases, call [[dispose]], which will dispose [[handle]] as well as the
* QuickJS handles that back [[resolve]] and [[reject]]. For this object,
* [[dispose]] is idempotent.
*/
class QuickJSDeferredPromise {
/**
* Use [[QuickJSContext.newPromise]] to create a new promise instead of calling
* this constructor directly.
* @unstable
*/
constructor(args) {
/**
* Resolve [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after resolving a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
this.resolve = (value) => {
if (!this.resolveHandle.alive) {
return;
}
this.context
.unwrapResult(this.context.callFunction(this.resolveHandle, this.context.undefined, value || this.context.undefined))
.dispose();
this.disposeResolvers();
this.onSettled();
};
/**
* Reject [[handle]] with the given value, if any.
* Calling this method after calling [[dispose]] is a no-op.
*
* Note that after rejecting a promise, you may need to call
* [[QuickJSContext.executePendingJobs]] to propagate the result to the promise's
* callbacks.
*/
this.reject = (value) => {
if (!this.rejectHandle.alive) {
return;
}
this.context
.unwrapResult(this.context.callFunction(this.rejectHandle, this.context.undefined, value || this.context.undefined))
.dispose();
this.disposeResolvers();
this.onSettled();
};
this.dispose = () => {
if (this.handle.alive) {
this.handle.dispose();
}
this.disposeResolvers();
};
this.context = args.context;
this.owner = args.context.runtime;
this.handle = args.promiseHandle;
this.settled = new Promise((resolve) => {
this.onSettled = resolve;
});
this.resolveHandle = args.resolveHandle;
this.rejectHandle = args.rejectHandle;
}
get alive() {
return this.handle.alive || this.resolveHandle.alive || this.rejectHandle.alive;
}
disposeResolvers() {
if (this.resolveHandle.alive) {
this.resolveHandle.dispose();
}
if (this.rejectHandle.alive) {
this.rejectHandle.dispose();
}
}
}
exports.QuickJSDeferredPromise = QuickJSDeferredPromise;
//# sourceMappingURL=deferred-promise.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,97 @@
import { BorrowedHeapCharPointer, JSContextPointer, JSRuntimePointer, JSValueConstPointer, JSValuePointer, OwnedHeapCharPointer } from "./types-ffi";
declare namespace Emscripten {
interface FileSystemType {
}
type EnvironmentType = "WEB" | "NODE" | "SHELL" | "WORKER";
type ValueType = "number" | "string" | "array" | "boolean";
type TypeCompatibleWithC = number | string | any[] | boolean;
type WebAssemblyImports = Array<{
name: string;
kind: string;
}>;
type WebAssemblyExports = Array<{
module: string;
name: string;
kind: string;
}>;
interface CCallOpts {
async?: boolean;
}
}
/**
* Typings for the features we use to interface with our Emscripten build of
* QuickJS.
*/
interface EmscriptenModule {
/**
* Write JS `str` to HeapChar pointer.
* https://emscripten.org/docs/api_reference/preamble.js.html#stringToUTF8
*/
stringToUTF8(str: string, outPtr: OwnedHeapCharPointer, maxBytesToRead?: number): void;
/**
* HeapChar to JS string.
* https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
*/
UTF8ToString(ptr: BorrowedHeapCharPointer, maxBytesToRead?: number): string;
lengthBytesUTF8(str: string): number;
_malloc(size: number): number;
_free(ptr: number): void;
cwrap(ident: string, returnType: Emscripten.ValueType | null, argTypes: Emscripten.ValueType[], opts?: Emscripten.CCallOpts): (...args: any[]) => any;
HEAP8: Int8Array;
HEAP16: Int16Array;
HEAP32: Int32Array;
HEAPU8: Uint8Array;
HEAPU16: Uint16Array;
HEAPU32: Uint32Array;
HEAPF32: Float32Array;
HEAPF64: Float64Array;
TOTAL_STACK: number;
TOTAL_MEMORY: number;
FAST_MEMORY: number;
}
declare const AsyncifySleepReturnValue: unique symbol;
/** @private */
export type AsyncifySleepResult<T> = T & typeof AsyncifySleepReturnValue;
/**
* Allows us to optionally suspend the Emscripten runtime to wait for a promise.
* https://emscripten.org/docs/porting/asyncify.html#ways-to-use-async-apis-in-older-engines
* ```
* EM_JS(int, do_fetch, (), {
* return Asyncify.handleSleep(function (wakeUp) {
* out("waiting for a fetch");
* fetch("a.html").then(function (response) {
* out("got the fetch response");
* // (normally you would do something with the fetch here)
* wakeUp(42);
* });
* });
* });
* ```
* @private
*/
export interface Asyncify {
handleSleep<T>(maybeAsyncFn: (wakeUp: (result: T) => void) => void): AsyncifySleepResult<T>;
}
/**
* @private
*/
export interface EmscriptenModuleCallbacks {
callFunction: (asyncify: Asyncify | undefined, ctx: JSContextPointer, this_ptr: JSValueConstPointer, argc: number, argv: JSValueConstPointer, fn_id: number) => JSValuePointer | AsyncifySleepResult<JSValuePointer>;
loadModuleSource: (asyncify: Asyncify | undefined, rt: JSRuntimePointer, ctx: JSContextPointer, module_name: string) => BorrowedHeapCharPointer | AsyncifySleepResult<BorrowedHeapCharPointer>;
normalizeModule: (asyncify: Asyncify | undefined, rt: JSRuntimePointer, ctx: JSContextPointer, module_base_name: string, module_name: string) => BorrowedHeapCharPointer | AsyncifySleepResult<BorrowedHeapCharPointer>;
shouldInterrupt: (asyncify: Asyncify | undefined, rt: JSRuntimePointer) => 0 | 1 | AsyncifySleepResult<0 | 1>;
}
export interface QuickJSEmscriptenModule extends EmscriptenModule {
type: "sync";
callbacks: EmscriptenModuleCallbacks;
}
export interface QuickJSAsyncEmscriptenModule extends EmscriptenModule {
/** @todo Implement this field */
type: "async";
callbacks: EmscriptenModuleCallbacks;
}
export type EitherModule = QuickJSEmscriptenModule | QuickJSAsyncEmscriptenModule;
export interface EmscriptenModuleLoader<T extends EmscriptenModule> {
(): Promise<T>;
}
export {};

View File

@@ -0,0 +1,15 @@
"use strict";
// This is a subset of the Emscripten type definitions from @types/emscripten
// Project: http://kripken.github.io/emscripten-site/index.html
// Definitions by: Kensuke Matsuzaki <https://github.com/zakki>
// Periklis Tsirakidis <https://github.com/periklis>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
//
// quickjs-emscripten doesn't use the full EmscriptenModule type from @types/emscripten because:
//
// - the upstream types define many properties that don't exist on our module due
// to our build settings
// - some upstream types reference web-only ambient types like WebGL stuff, which
// we don't use.
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=emscripten-types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
import type { QuickJSContext } from "./context";
/**
* Error thrown if [[QuickJSContext.unwrapResult]] unwraps an error value that isn't an object.
*/
export declare class QuickJSUnwrapError extends Error {
cause: unknown;
context?: QuickJSContext | undefined;
name: string;
constructor(cause: unknown, context?: QuickJSContext | undefined);
}
export declare class QuickJSWrongOwner extends Error {
name: string;
}
export declare class QuickJSUseAfterFree extends Error {
name: string;
}
export declare class QuickJSNotImplemented extends Error {
name: string;
}
export declare class QuickJSAsyncifyError extends Error {
name: string;
}
export declare class QuickJSAsyncifySuspended extends Error {
name: string;
}
export declare class QuickJSMemoryLeakDetected extends Error {
name: string;
}

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSMemoryLeakDetected = exports.QuickJSAsyncifySuspended = exports.QuickJSAsyncifyError = exports.QuickJSNotImplemented = exports.QuickJSUseAfterFree = exports.QuickJSWrongOwner = exports.QuickJSUnwrapError = void 0;
/**
* Error thrown if [[QuickJSContext.unwrapResult]] unwraps an error value that isn't an object.
*/
class QuickJSUnwrapError extends Error {
constructor(cause, context) {
super(String(cause));
this.cause = cause;
this.context = context;
this.name = "QuickJSUnwrapError";
}
}
exports.QuickJSUnwrapError = QuickJSUnwrapError;
class QuickJSWrongOwner extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSWrongOwner";
}
}
exports.QuickJSWrongOwner = QuickJSWrongOwner;
class QuickJSUseAfterFree extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSUseAfterFree";
}
}
exports.QuickJSUseAfterFree = QuickJSUseAfterFree;
class QuickJSNotImplemented extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSNotImplemented";
}
}
exports.QuickJSNotImplemented = QuickJSNotImplemented;
class QuickJSAsyncifyError extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSAsyncifyError";
}
}
exports.QuickJSAsyncifyError = QuickJSAsyncifyError;
class QuickJSAsyncifySuspended extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSAsyncifySuspended";
}
}
exports.QuickJSAsyncifySuspended = QuickJSAsyncifySuspended;
class QuickJSMemoryLeakDetected extends Error {
constructor() {
super(...arguments);
this.name = "QuickJSMemoryLeakDetected";
}
}
exports.QuickJSMemoryLeakDetected = QuickJSMemoryLeakDetected;
//# sourceMappingURL=errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../ts/errors.ts"],"names":[],"mappings":";;;AAEA;;GAEG;AACH,MAAa,kBAAmB,SAAQ,KAAK;IAE3C,YAAmB,KAAc,EAAS,OAAwB;QAChE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QADH,UAAK,GAAL,KAAK,CAAS;QAAS,YAAO,GAAP,OAAO,CAAiB;QADlE,SAAI,GAAG,oBAAoB,CAAA;IAG3B,CAAC;CACF;AALD,gDAKC;AAED,MAAa,iBAAkB,SAAQ,KAAK;IAA5C;;QACE,SAAI,GAAG,mBAAmB,CAAA;IAC5B,CAAC;CAAA;AAFD,8CAEC;AAED,MAAa,mBAAoB,SAAQ,KAAK;IAA9C;;QACE,SAAI,GAAG,qBAAqB,CAAA;IAC9B,CAAC;CAAA;AAFD,kDAEC;AAED,MAAa,qBAAsB,SAAQ,KAAK;IAAhD;;QACE,SAAI,GAAG,uBAAuB,CAAA;IAChC,CAAC;CAAA;AAFD,sDAEC;AAED,MAAa,oBAAqB,SAAQ,KAAK;IAA/C;;QACE,SAAI,GAAG,sBAAsB,CAAA;IAC/B,CAAC;CAAA;AAFD,oDAEC;AAED,MAAa,wBAAyB,SAAQ,KAAK;IAAnD;;QACE,SAAI,GAAG,0BAA0B,CAAA;IACnC,CAAC;CAAA;AAFD,4DAEC;AAED,MAAa,yBAA0B,SAAQ,KAAK;IAApD;;QACE,SAAI,GAAG,2BAA2B,CAAA;IACpC,CAAC;CAAA;AAFD,8DAEC","sourcesContent":["import type { QuickJSContext } from \"./context\"\n\n/**\n * Error thrown if [[QuickJSContext.unwrapResult]] unwraps an error value that isn't an object.\n */\nexport class QuickJSUnwrapError extends Error {\n name = \"QuickJSUnwrapError\"\n constructor(public cause: unknown, public context?: QuickJSContext) {\n super(String(cause))\n }\n}\n\nexport class QuickJSWrongOwner extends Error {\n name = \"QuickJSWrongOwner\"\n}\n\nexport class QuickJSUseAfterFree extends Error {\n name = \"QuickJSUseAfterFree\"\n}\n\nexport class QuickJSNotImplemented extends Error {\n name = \"QuickJSNotImplemented\"\n}\n\nexport class QuickJSAsyncifyError extends Error {\n name = \"QuickJSAsyncifyError\"\n}\n\nexport class QuickJSAsyncifySuspended extends Error {\n name = \"QuickJSAsyncifySuspended\"\n}\n\nexport class QuickJSMemoryLeakDetected extends Error {\n name = \"QuickJSMemoryLeakDetected\"\n}\n"]}

View File

@@ -0,0 +1,9 @@
/** Typescript thinks import('...js/.d.ts') needs mod.default.default */
declare function fakeUnwrapDefault<T>(mod: {
default: T;
}): T;
/** Typescript thinks import('...ts') doesn't need mod.default.default, but does */
declare function actualUnwrapDefault<T>(mod: T): T;
export declare const unwrapTypescript: typeof actualUnwrapDefault;
export declare const unwrapJavascript: typeof fakeUnwrapDefault;
export {};

View File

@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.unwrapJavascript = exports.unwrapTypescript = void 0;
/** Typescript thinks import('...js/.d.ts') needs mod.default.default */
function fakeUnwrapDefault(mod) {
// console.log("fakeUnwrapDefault", mod)
return mod.default;
}
/** Typescript thinks import('...ts') doesn't need mod.default.default, but does */
function actualUnwrapDefault(mod) {
// console.log("actualUnwrapDefault", mod)
const maybeUnwrap = mod.default;
return maybeUnwrap ?? mod;
}
// I'm not sure if this behavior is needed in all runtimes,
// or just for mocha + ts-node.
exports.unwrapTypescript = actualUnwrapDefault;
exports.unwrapJavascript = fakeUnwrapDefault;
//# sourceMappingURL=esmHelpers.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"esmHelpers.js","sourceRoot":"","sources":["../ts/esmHelpers.ts"],"names":[],"mappings":";;;AAAA,wEAAwE;AACxE,SAAS,iBAAiB,CAAI,GAAmB;IAC/C,wCAAwC;IACxC,OAAO,GAAG,CAAC,OAAY,CAAA;AACzB,CAAC;AAED,mFAAmF;AACnF,SAAS,mBAAmB,CAAI,GAAM;IACpC,0CAA0C;IAC1C,MAAM,WAAW,GAAI,GAAW,CAAC,OAAO,CAAA;IACxC,OAAO,WAAW,IAAI,GAAG,CAAA;AAC3B,CAAC;AAED,2DAA2D;AAC3D,+BAA+B;AAClB,QAAA,gBAAgB,GAAG,mBAAmB,CAAA;AACtC,QAAA,gBAAgB,GAAG,iBAAiB,CAAA","sourcesContent":["/** Typescript thinks import('...js/.d.ts') needs mod.default.default */\nfunction fakeUnwrapDefault<T>(mod: { default: T }): T {\n // console.log(\"fakeUnwrapDefault\", mod)\n return mod.default as T\n}\n\n/** Typescript thinks import('...ts') doesn't need mod.default.default, but does */\nfunction actualUnwrapDefault<T>(mod: T): T {\n // console.log(\"actualUnwrapDefault\", mod)\n const maybeUnwrap = (mod as any).default\n return maybeUnwrap ?? mod\n}\n\n// I'm not sure if this behavior is needed in all runtimes,\n// or just for mocha + ts-node.\nexport const unwrapTypescript = actualUnwrapDefault\nexport const unwrapJavascript = fakeUnwrapDefault\n"]}

View File

@@ -0,0 +1,5 @@
export = QuickJSRaw;
declare function QuickJSRaw(QuickJSRaw?: {}): any;
declare namespace QuickJSRaw {
export { QuickJSRaw };
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,67 @@
import { QuickJSEmscriptenModule } from "../emscripten-types";
import { JSRuntimePointer, JSContextPointer, JSContextPointerPointer, JSValuePointer, JSValueConstPointer, JSValuePointerPointer, JSValueConstPointerPointer, BorrowedHeapCharPointer, OwnedHeapCharPointer, JSBorrowedCharPointer, JSVoidPointer, EvalFlags, EvalDetectModule } from "../types-ffi";
/**
* Low-level FFI bindings to QuickJS's Emscripten module.
* See instead [[QuickJSContext]], the public Javascript interface exposed by this
* library.
*
* @unstable The FFI interface is considered private and may change.
*/
export declare class QuickJSFFI {
private module;
constructor(module: QuickJSEmscriptenModule);
/** Set at compile time. */
readonly DEBUG = false;
QTS_Throw: (ctx: JSContextPointer, error: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_NewError: (ctx: JSContextPointer) => JSValuePointer;
QTS_RuntimeSetMemoryLimit: (rt: JSRuntimePointer, limit: number) => void;
QTS_RuntimeComputeMemoryUsage: (rt: JSRuntimePointer, ctx: JSContextPointer) => JSValuePointer;
QTS_RuntimeDumpMemoryUsage: (rt: JSRuntimePointer) => OwnedHeapCharPointer;
QTS_RecoverableLeakCheck: () => number;
QTS_BuildIsSanitizeLeak: () => number;
QTS_RuntimeSetMaxStackSize: (rt: JSRuntimePointer, stack_size: number) => void;
QTS_GetUndefined: () => JSValueConstPointer;
QTS_GetNull: () => JSValueConstPointer;
QTS_GetFalse: () => JSValueConstPointer;
QTS_GetTrue: () => JSValueConstPointer;
QTS_NewRuntime: () => JSRuntimePointer;
QTS_FreeRuntime: (rt: JSRuntimePointer) => void;
QTS_NewContext: (rt: JSRuntimePointer) => JSContextPointer;
QTS_FreeContext: (ctx: JSContextPointer) => void;
QTS_FreeValuePointer: (ctx: JSContextPointer, value: JSValuePointer) => void;
QTS_FreeValuePointerRuntime: (rt: JSRuntimePointer, value: JSValuePointer) => void;
QTS_FreeVoidPointer: (ctx: JSContextPointer, ptr: JSVoidPointer) => void;
QTS_FreeCString: (ctx: JSContextPointer, str: JSBorrowedCharPointer) => void;
QTS_DupValuePointer: (ctx: JSContextPointer, val: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_NewObject: (ctx: JSContextPointer) => JSValuePointer;
QTS_NewObjectProto: (ctx: JSContextPointer, proto: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_NewArray: (ctx: JSContextPointer) => JSValuePointer;
QTS_NewFloat64: (ctx: JSContextPointer, num: number) => JSValuePointer;
QTS_GetFloat64: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => number;
QTS_NewString: (ctx: JSContextPointer, string: BorrowedHeapCharPointer) => JSValuePointer;
QTS_GetString: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => JSBorrowedCharPointer;
QTS_NewSymbol: (ctx: JSContextPointer, description: BorrowedHeapCharPointer, isGlobal: number) => JSValuePointer;
QTS_GetSymbolDescriptionOrKey: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => JSBorrowedCharPointer;
QTS_IsGlobalSymbol: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => number;
QTS_IsJobPending: (rt: JSRuntimePointer) => number;
QTS_ExecutePendingJob: (rt: JSRuntimePointer, maxJobsToExecute: number, lastJobContext: JSContextPointerPointer) => JSValuePointer;
QTS_GetProp: (ctx: JSContextPointer, this_val: JSValuePointer | JSValueConstPointer, prop_name: JSValuePointer | JSValueConstPointer) => JSValuePointer;
QTS_SetProp: (ctx: JSContextPointer, this_val: JSValuePointer | JSValueConstPointer, prop_name: JSValuePointer | JSValueConstPointer, prop_value: JSValuePointer | JSValueConstPointer) => void;
QTS_DefineProp: (ctx: JSContextPointer, this_val: JSValuePointer | JSValueConstPointer, prop_name: JSValuePointer | JSValueConstPointer, prop_value: JSValuePointer | JSValueConstPointer, get: JSValuePointer | JSValueConstPointer, set: JSValuePointer | JSValueConstPointer, configurable: boolean, enumerable: boolean, has_value: boolean) => void;
QTS_Call: (ctx: JSContextPointer, func_obj: JSValuePointer | JSValueConstPointer, this_obj: JSValuePointer | JSValueConstPointer, argc: number, argv_ptrs: JSValueConstPointerPointer) => JSValuePointer;
QTS_ResolveException: (ctx: JSContextPointer, maybe_exception: JSValuePointer) => JSValuePointer;
QTS_Dump: (ctx: JSContextPointer, obj: JSValuePointer | JSValueConstPointer) => JSBorrowedCharPointer;
QTS_Eval: (ctx: JSContextPointer, js_code: BorrowedHeapCharPointer, filename: string, detectModule: EvalDetectModule, evalFlags: EvalFlags) => JSValuePointer;
QTS_Typeof: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => OwnedHeapCharPointer;
QTS_GetGlobalObject: (ctx: JSContextPointer) => JSValuePointer;
QTS_NewPromiseCapability: (ctx: JSContextPointer, resolve_funcs_out: JSValuePointerPointer) => JSValuePointer;
QTS_TestStringArg: (string: string) => void;
QTS_BuildIsDebug: () => number;
QTS_BuildIsAsyncify: () => number;
QTS_NewFunction: (ctx: JSContextPointer, func_id: number, name: string) => JSValuePointer;
QTS_ArgvGetJSValueConstPointer: (argv: JSValuePointer | JSValueConstPointer, index: number) => JSValueConstPointer;
QTS_RuntimeEnableInterruptHandler: (rt: JSRuntimePointer) => void;
QTS_RuntimeDisableInterruptHandler: (rt: JSRuntimePointer) => void;
QTS_RuntimeEnableModuleLoader: (rt: JSRuntimePointer, use_custom_normalize: number) => void;
QTS_RuntimeDisableModuleLoader: (rt: JSRuntimePointer) => void;
}

View File

@@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickJSFFI = void 0;
/**
* Low-level FFI bindings to QuickJS's Emscripten module.
* See instead [[QuickJSContext]], the public Javascript interface exposed by this
* library.
*
* @unstable The FFI interface is considered private and may change.
*/
class QuickJSFFI {
constructor(module) {
this.module = module;
/** Set at compile time. */
this.DEBUG = false;
this.QTS_Throw = this.module.cwrap("QTS_Throw", "number", ["number", "number"]);
this.QTS_NewError = this.module.cwrap("QTS_NewError", "number", ["number"]);
this.QTS_RuntimeSetMemoryLimit = this.module.cwrap("QTS_RuntimeSetMemoryLimit", null, ["number", "number"]);
this.QTS_RuntimeComputeMemoryUsage = this.module.cwrap("QTS_RuntimeComputeMemoryUsage", "number", ["number", "number"]);
this.QTS_RuntimeDumpMemoryUsage = this.module.cwrap("QTS_RuntimeDumpMemoryUsage", "number", ["number"]);
this.QTS_RecoverableLeakCheck = this.module.cwrap("QTS_RecoverableLeakCheck", "number", []);
this.QTS_BuildIsSanitizeLeak = this.module.cwrap("QTS_BuildIsSanitizeLeak", "number", []);
this.QTS_RuntimeSetMaxStackSize = this.module.cwrap("QTS_RuntimeSetMaxStackSize", null, ["number", "number"]);
this.QTS_GetUndefined = this.module.cwrap("QTS_GetUndefined", "number", []);
this.QTS_GetNull = this.module.cwrap("QTS_GetNull", "number", []);
this.QTS_GetFalse = this.module.cwrap("QTS_GetFalse", "number", []);
this.QTS_GetTrue = this.module.cwrap("QTS_GetTrue", "number", []);
this.QTS_NewRuntime = this.module.cwrap("QTS_NewRuntime", "number", []);
this.QTS_FreeRuntime = this.module.cwrap("QTS_FreeRuntime", null, ["number"]);
this.QTS_NewContext = this.module.cwrap("QTS_NewContext", "number", ["number"]);
this.QTS_FreeContext = this.module.cwrap("QTS_FreeContext", null, ["number"]);
this.QTS_FreeValuePointer = this.module.cwrap("QTS_FreeValuePointer", null, ["number", "number"]);
this.QTS_FreeValuePointerRuntime = this.module.cwrap("QTS_FreeValuePointerRuntime", null, ["number", "number"]);
this.QTS_FreeVoidPointer = this.module.cwrap("QTS_FreeVoidPointer", null, ["number", "number"]);
this.QTS_FreeCString = this.module.cwrap("QTS_FreeCString", null, ["number", "number"]);
this.QTS_DupValuePointer = this.module.cwrap("QTS_DupValuePointer", "number", ["number", "number"]);
this.QTS_NewObject = this.module.cwrap("QTS_NewObject", "number", ["number"]);
this.QTS_NewObjectProto = this.module.cwrap("QTS_NewObjectProto", "number", ["number", "number"]);
this.QTS_NewArray = this.module.cwrap("QTS_NewArray", "number", ["number"]);
this.QTS_NewFloat64 = this.module.cwrap("QTS_NewFloat64", "number", ["number", "number"]);
this.QTS_GetFloat64 = this.module.cwrap("QTS_GetFloat64", "number", ["number", "number"]);
this.QTS_NewString = this.module.cwrap("QTS_NewString", "number", ["number", "number"]);
this.QTS_GetString = this.module.cwrap("QTS_GetString", "number", ["number", "number"]);
this.QTS_NewSymbol = this.module.cwrap("QTS_NewSymbol", "number", ["number", "number", "number"]);
this.QTS_GetSymbolDescriptionOrKey = this.module.cwrap("QTS_GetSymbolDescriptionOrKey", "number", ["number", "number"]);
this.QTS_IsGlobalSymbol = this.module.cwrap("QTS_IsGlobalSymbol", "number", ["number", "number"]);
this.QTS_IsJobPending = this.module.cwrap("QTS_IsJobPending", "number", ["number"]);
this.QTS_ExecutePendingJob = this.module.cwrap("QTS_ExecutePendingJob", "number", ["number", "number", "number"]);
this.QTS_GetProp = this.module.cwrap("QTS_GetProp", "number", ["number", "number", "number"]);
this.QTS_SetProp = this.module.cwrap("QTS_SetProp", null, ["number", "number", "number", "number"]);
this.QTS_DefineProp = this.module.cwrap("QTS_DefineProp", null, ["number", "number", "number", "number", "number", "number", "boolean", "boolean", "boolean"]);
this.QTS_Call = this.module.cwrap("QTS_Call", "number", ["number", "number", "number", "number", "number"]);
this.QTS_ResolveException = this.module.cwrap("QTS_ResolveException", "number", ["number", "number"]);
this.QTS_Dump = this.module.cwrap("QTS_Dump", "number", ["number", "number"]);
this.QTS_Eval = this.module.cwrap("QTS_Eval", "number", ["number", "number", "string", "number", "number"]);
this.QTS_Typeof = this.module.cwrap("QTS_Typeof", "number", ["number", "number"]);
this.QTS_GetGlobalObject = this.module.cwrap("QTS_GetGlobalObject", "number", ["number"]);
this.QTS_NewPromiseCapability = this.module.cwrap("QTS_NewPromiseCapability", "number", ["number", "number"]);
this.QTS_TestStringArg = this.module.cwrap("QTS_TestStringArg", null, ["string"]);
this.QTS_BuildIsDebug = this.module.cwrap("QTS_BuildIsDebug", "number", []);
this.QTS_BuildIsAsyncify = this.module.cwrap("QTS_BuildIsAsyncify", "number", []);
this.QTS_NewFunction = this.module.cwrap("QTS_NewFunction", "number", ["number", "number", "string"]);
this.QTS_ArgvGetJSValueConstPointer = this.module.cwrap("QTS_ArgvGetJSValueConstPointer", "number", ["number", "number"]);
this.QTS_RuntimeEnableInterruptHandler = this.module.cwrap("QTS_RuntimeEnableInterruptHandler", null, ["number"]);
this.QTS_RuntimeDisableInterruptHandler = this.module.cwrap("QTS_RuntimeDisableInterruptHandler", null, ["number"]);
this.QTS_RuntimeEnableModuleLoader = this.module.cwrap("QTS_RuntimeEnableModuleLoader", null, ["number", "number"]);
this.QTS_RuntimeDisableModuleLoader = this.module.cwrap("QTS_RuntimeDisableModuleLoader", null, ["number"]);
}
}
exports.QuickJSFFI = QuickJSFFI;
//# sourceMappingURL=ffi.WASM_RELEASE_SYNC.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,75 @@
import type { QuickJSWASMModule } from "./module";
import type { QuickJSRuntime, InterruptHandler } from "./runtime";
import type { QuickJSContext } from "./context";
export type { QuickJSWASMModule, QuickJSContext, QuickJSRuntime };
import type { QuickJSAsyncWASMModule } from "./module-asyncify";
import type { QuickJSAsyncRuntime } from "./runtime-asyncify";
import type { QuickJSAsyncContext, AsyncFunctionImplementation } from "./context-asyncify";
import { AsyncRuntimeOptions, ContextOptions } from "./types";
export type { QuickJSAsyncContext, QuickJSAsyncRuntime, QuickJSAsyncWASMModule, AsyncFunctionImplementation, };
import { newQuickJSWASMModule, newQuickJSAsyncWASMModule, DEBUG_ASYNC, DEBUG_SYNC, RELEASE_ASYNC, RELEASE_SYNC, SyncBuildVariant, AsyncBuildVariant } from "./variants";
export { newQuickJSWASMModule, newQuickJSAsyncWASMModule, DEBUG_ASYNC, DEBUG_SYNC, RELEASE_ASYNC, RELEASE_SYNC, SyncBuildVariant, AsyncBuildVariant, };
export * from "./vm-interface";
export * from "./lifetime";
/** Collects the informative errors this library may throw. */
export * as errors from "./errors";
export * from "./deferred-promise";
export * from "./module-test";
export type { StaticJSValue, JSValueConst, JSValue, QuickJSHandle, ContextOptions, ContextEvalOptions, RuntimeOptions, AsyncRuntimeOptions, RuntimeOptionsBase, JSModuleLoader, JSModuleLoadResult, JSModuleLoaderAsync, JSModuleLoadSuccess, JSModuleLoadFailure, JSModuleNormalizer, JSModuleNormalizerAsync, JSModuleNormalizeResult, JSModuleNormalizeFailure, JSModuleNormalizeSuccess, } from "./types";
export type { ModuleEvalOptions } from "./module";
export type { InterruptHandler, ExecutePendingJobsResult } from "./runtime";
export type { QuickJSPropertyKey } from "./context";
/**
* Get a shared singleton {@link QuickJSWASMModule}. Use this to evaluate code
* or create Javascript environments.
*
* This is the top-level entrypoint for the quickjs-emscripten library.
*
* If you need strictest possible isolation guarantees, you may create a
* separate {@link QuickJSWASMModule} via {@link newQuickJSWASMModule}.
*
* To work with the asyncified version of this library, see these functions:
*
* - {@link newAsyncRuntime}.
* - {@link newAsyncContext}.
* - {@link newQuickJSAsyncWASMModule}.
*/
export declare function getQuickJS(): Promise<QuickJSWASMModule>;
/**
* Provides synchronous access to the shared {@link QuickJSWASMModule} instance returned by {@link getQuickJS}, as long as
* least once.
* @throws If called before `getQuickJS` resolves.
*/
export declare function getQuickJSSync(): QuickJSWASMModule;
/**
* Create a new [[QuickJSAsyncRuntime]] in a separate WebAssembly module.
*
* Each runtime is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
export declare function newAsyncRuntime(options?: AsyncRuntimeOptions): Promise<QuickJSAsyncRuntime>;
/**
* Create a new [[QuickJSAsyncContext]] (with an associated runtime) in an
* separate WebAssembly module.
*
* Each context is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
export declare function newAsyncContext(options?: ContextOptions): Promise<QuickJSAsyncContext>;
/**
* Returns an interrupt handler that interrupts Javascript execution after a deadline time.
*
* @param deadline - Interrupt execution if it's still running after this time.
* Number values are compared against `Date.now()`
*/
export declare function shouldInterruptAfterDeadline(deadline: Date | number): InterruptHandler;

View File

@@ -0,0 +1,128 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.shouldInterruptAfterDeadline = exports.newAsyncContext = exports.newAsyncRuntime = exports.getQuickJSSync = exports.getQuickJS = exports.errors = exports.RELEASE_SYNC = exports.RELEASE_ASYNC = exports.DEBUG_SYNC = exports.DEBUG_ASYNC = exports.newQuickJSAsyncWASMModule = exports.newQuickJSWASMModule = void 0;
// Build variants
const variants_1 = require("./variants");
Object.defineProperty(exports, "newQuickJSWASMModule", { enumerable: true, get: function () { return variants_1.newQuickJSWASMModule; } });
Object.defineProperty(exports, "newQuickJSAsyncWASMModule", { enumerable: true, get: function () { return variants_1.newQuickJSAsyncWASMModule; } });
Object.defineProperty(exports, "DEBUG_ASYNC", { enumerable: true, get: function () { return variants_1.DEBUG_ASYNC; } });
Object.defineProperty(exports, "DEBUG_SYNC", { enumerable: true, get: function () { return variants_1.DEBUG_SYNC; } });
Object.defineProperty(exports, "RELEASE_ASYNC", { enumerable: true, get: function () { return variants_1.RELEASE_ASYNC; } });
Object.defineProperty(exports, "RELEASE_SYNC", { enumerable: true, get: function () { return variants_1.RELEASE_SYNC; } });
// Export helpers
__exportStar(require("./vm-interface"), exports);
__exportStar(require("./lifetime"), exports);
/** Collects the informative errors this library may throw. */
exports.errors = __importStar(require("./errors"));
__exportStar(require("./deferred-promise"), exports);
__exportStar(require("./module-test"), exports);
let singleton = undefined;
let singletonPromise = undefined;
/**
* Get a shared singleton {@link QuickJSWASMModule}. Use this to evaluate code
* or create Javascript environments.
*
* This is the top-level entrypoint for the quickjs-emscripten library.
*
* If you need strictest possible isolation guarantees, you may create a
* separate {@link QuickJSWASMModule} via {@link newQuickJSWASMModule}.
*
* To work with the asyncified version of this library, see these functions:
*
* - {@link newAsyncRuntime}.
* - {@link newAsyncContext}.
* - {@link newQuickJSAsyncWASMModule}.
*/
async function getQuickJS() {
singletonPromise ?? (singletonPromise = (0, variants_1.newQuickJSWASMModule)().then((instance) => {
singleton = instance;
return instance;
}));
return await singletonPromise;
}
exports.getQuickJS = getQuickJS;
/**
* Provides synchronous access to the shared {@link QuickJSWASMModule} instance returned by {@link getQuickJS}, as long as
* least once.
* @throws If called before `getQuickJS` resolves.
*/
function getQuickJSSync() {
if (!singleton) {
throw new Error("QuickJS not initialized. Await getQuickJS() at least once.");
}
return singleton;
}
exports.getQuickJSSync = getQuickJSSync;
/**
* Create a new [[QuickJSAsyncRuntime]] in a separate WebAssembly module.
*
* Each runtime is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
async function newAsyncRuntime(options) {
const module = await (0, variants_1.newQuickJSAsyncWASMModule)();
return module.newRuntime(options);
}
exports.newAsyncRuntime = newAsyncRuntime;
/**
* Create a new [[QuickJSAsyncContext]] (with an associated runtime) in an
* separate WebAssembly module.
*
* Each context is isolated in a separate WebAssembly module, so that errors in
* one runtime cannot contaminate another runtime, and each runtime can execute
* an asynchronous action without conflicts.
*
* Note that there is a hard limit on the number of WebAssembly modules in older
* versions of v8:
* https://bugs.chromium.org/p/v8/issues/detail?id=12076
*/
async function newAsyncContext(options) {
const module = await (0, variants_1.newQuickJSAsyncWASMModule)();
return module.newContext(options);
}
exports.newAsyncContext = newAsyncContext;
/**
* Returns an interrupt handler that interrupts Javascript execution after a deadline time.
*
* @param deadline - Interrupt execution if it's still running after this time.
* Number values are compared against `Date.now()`
*/
function shouldInterruptAfterDeadline(deadline) {
const deadlineAsNumber = typeof deadline === "number" ? deadline : deadline.getTime();
return function () {
return Date.now() > deadlineAsNumber;
};
}
exports.shouldInterruptAfterDeadline = shouldInterruptAfterDeadline;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
import { MaybeAsyncBlock } from "./asyncify-helpers";
import type { QuickJSHandle } from "./types";
/**
* An object that can be disposed.
* [[Lifetime]] is the canonical implementation of Disposable.
* Use [[Scope]] to manage cleaning up multiple disposables.
*/
export interface Disposable {
/**
* Dispose of the underlying resources used by this object.
*/
dispose(): void;
/**
* @returns true if the object is alive
* @returns false after the object has been [[dispose]]d
*/
alive: boolean;
}
/**
* A lifetime prevents access to a value after the lifetime has been
* [[dispose]]ed.
*
* Typically, quickjs-emscripten uses Lifetimes to protect C memory pointers.
*/
export declare class Lifetime<T, TCopy = never, Owner = never> implements Disposable {
protected readonly _value: T;
protected readonly copier?: ((value: T | TCopy) => TCopy) | undefined;
protected readonly disposer?: ((value: T | TCopy) => void) | undefined;
protected readonly _owner?: Owner | undefined;
protected _alive: boolean;
protected _constructorStack: string | undefined;
/**
* When the Lifetime is disposed, it will call `disposer(_value)`. Use the
* disposer function to implement whatever cleanup needs to happen at the end
* of `value`'s lifetime.
*
* `_owner` is not used or controlled by the lifetime. It's just metadata for
* the creator.
*/
constructor(_value: T, copier?: ((value: T | TCopy) => TCopy) | undefined, disposer?: ((value: T | TCopy) => void) | undefined, _owner?: Owner | undefined);
get alive(): boolean;
/**
* The value this Lifetime protects. You must never retain the value - it
* may become invalid, leading to memory errors.
*
* @throws If the lifetime has been [[dispose]]d already.
*/
get value(): T;
get owner(): Owner | undefined;
get dupable(): boolean;
/**
* Create a new handle pointing to the same [[value]].
*/
dup(): Lifetime<TCopy, TCopy, Owner>;
/**
* Call `map` with this lifetime, then dispose the lifetime.
* @return the result of `map(this)`.
*/
consume<O>(map: (lifetime: this) => O): O;
consume<O>(map: (lifetime: QuickJSHandle) => O): O;
/**
* Dispose of [[value]] and perform cleanup.
*/
dispose(): void;
private assertAlive;
}
/**
* A Lifetime that lives forever. Used for constants.
*/
export declare class StaticLifetime<T, Owner = never> extends Lifetime<T, T, Owner> {
constructor(value: T, owner?: Owner);
get dupable(): boolean;
dup(): this;
dispose(): void;
}
/**
* A Lifetime that does not own its `value`. A WeakLifetime never calls its
* `disposer` function, but can be `dup`ed to produce regular lifetimes that
* do.
*
* Used for function arguments.
*/
export declare class WeakLifetime<T, TCopy = never, Owner = never> extends Lifetime<T, TCopy, Owner> {
constructor(value: T, copier?: (value: T | TCopy) => TCopy, disposer?: (value: TCopy) => void, owner?: Owner);
dispose(): void;
}
/**
* Scope helps reduce the burden of manually tracking and disposing of
* Lifetimes. See [[withScope]]. and [[withScopeAsync]].
*/
export declare class Scope implements Disposable {
/**
* Run `block` with a new Scope instance that will be disposed after the block returns.
* Inside `block`, call `scope.manage` on each lifetime you create to have the lifetime
* automatically disposed after the block returns.
*
* @warning Do not use with async functions. Instead, use [[withScopeAsync]].
*/
static withScope<R>(block: (scope: Scope) => R): R;
static withScopeMaybeAsync<Return, This, Yielded>(_this: This, block: MaybeAsyncBlock<Return, This, Yielded, [Scope]>): Return | Promise<Return>;
/**
* Run `block` with a new Scope instance that will be disposed after the
* block's returned promise settles. Inside `block`, call `scope.manage` on each
* lifetime you create to have the lifetime automatically disposed after the
* block returns.
*/
static withScopeAsync<R>(block: (scope: Scope) => Promise<R>): Promise<R>;
private _disposables;
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage<T extends Disposable>(lifetime: T): T;
get alive(): boolean;
dispose(): void;
}

View File

@@ -0,0 +1,227 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Scope = exports.WeakLifetime = exports.StaticLifetime = exports.Lifetime = void 0;
const asyncify_helpers_1 = require("./asyncify-helpers");
const debug_1 = require("./debug");
const errors_1 = require("./errors");
/**
* A lifetime prevents access to a value after the lifetime has been
* [[dispose]]ed.
*
* Typically, quickjs-emscripten uses Lifetimes to protect C memory pointers.
*/
class Lifetime {
/**
* When the Lifetime is disposed, it will call `disposer(_value)`. Use the
* disposer function to implement whatever cleanup needs to happen at the end
* of `value`'s lifetime.
*
* `_owner` is not used or controlled by the lifetime. It's just metadata for
* the creator.
*/
constructor(_value, copier, disposer, _owner) {
this._value = _value;
this.copier = copier;
this.disposer = disposer;
this._owner = _owner;
this._alive = true;
this._constructorStack = debug_1.QTS_DEBUG ? new Error("Lifetime constructed").stack : undefined;
}
get alive() {
return this._alive;
}
/**
* The value this Lifetime protects. You must never retain the value - it
* may become invalid, leading to memory errors.
*
* @throws If the lifetime has been [[dispose]]d already.
*/
get value() {
this.assertAlive();
return this._value;
}
get owner() {
return this._owner;
}
get dupable() {
return !!this.copier;
}
/**
* Create a new handle pointing to the same [[value]].
*/
dup() {
this.assertAlive();
if (!this.copier) {
throw new Error("Non-dupable lifetime");
}
return new Lifetime(this.copier(this._value), this.copier, this.disposer, this._owner);
}
consume(map) {
this.assertAlive();
const result = map(this);
this.dispose();
return result;
}
/**
* Dispose of [[value]] and perform cleanup.
*/
dispose() {
this.assertAlive();
if (this.disposer) {
this.disposer(this._value);
}
this._alive = false;
}
assertAlive() {
if (!this.alive) {
if (this._constructorStack) {
throw new errors_1.QuickJSUseAfterFree(`Lifetime not alive\n${this._constructorStack}\nLifetime used`);
}
throw new errors_1.QuickJSUseAfterFree("Lifetime not alive");
}
}
}
exports.Lifetime = Lifetime;
/**
* A Lifetime that lives forever. Used for constants.
*/
class StaticLifetime extends Lifetime {
constructor(value, owner) {
super(value, undefined, undefined, owner);
}
// Static lifetime doesn't need a copier to be copiable
get dupable() {
return true;
}
// Copy returns the same instance.
dup() {
return this;
}
// Dispose does nothing.
dispose() { }
}
exports.StaticLifetime = StaticLifetime;
/**
* A Lifetime that does not own its `value`. A WeakLifetime never calls its
* `disposer` function, but can be `dup`ed to produce regular lifetimes that
* do.
*
* Used for function arguments.
*/
class WeakLifetime extends Lifetime {
constructor(value, copier, disposer, owner) {
// We don't care if the disposer doesn't support freeing T
super(value, copier, disposer, owner);
}
dispose() {
this._alive = false;
}
}
exports.WeakLifetime = WeakLifetime;
function scopeFinally(scope, blockError) {
// console.log('scopeFinally', scope, blockError)
let disposeError;
try {
scope.dispose();
}
catch (error) {
disposeError = error;
}
if (blockError && disposeError) {
Object.assign(blockError, {
message: `${blockError.message}\n Then, failed to dispose scope: ${disposeError.message}`,
disposeError,
});
throw blockError;
}
if (blockError || disposeError) {
throw blockError || disposeError;
}
}
/**
* Scope helps reduce the burden of manually tracking and disposing of
* Lifetimes. See [[withScope]]. and [[withScopeAsync]].
*/
class Scope {
constructor() {
this._disposables = new Lifetime(new Set());
}
/**
* Run `block` with a new Scope instance that will be disposed after the block returns.
* Inside `block`, call `scope.manage` on each lifetime you create to have the lifetime
* automatically disposed after the block returns.
*
* @warning Do not use with async functions. Instead, use [[withScopeAsync]].
*/
static withScope(block) {
const scope = new Scope();
let blockError;
try {
return block(scope);
}
catch (error) {
blockError = error;
throw error;
}
finally {
scopeFinally(scope, blockError);
}
}
static withScopeMaybeAsync(_this, block) {
return (0, asyncify_helpers_1.maybeAsync)(undefined, function* (awaited) {
const scope = new Scope();
let blockError;
try {
return yield* awaited.of(block.call(_this, awaited, scope));
}
catch (error) {
blockError = error;
throw error;
}
finally {
scopeFinally(scope, blockError);
}
});
}
/**
* Run `block` with a new Scope instance that will be disposed after the
* block's returned promise settles. Inside `block`, call `scope.manage` on each
* lifetime you create to have the lifetime automatically disposed after the
* block returns.
*/
static async withScopeAsync(block) {
const scope = new Scope();
let blockError;
try {
return await block(scope);
}
catch (error) {
blockError = error;
throw error;
}
finally {
scopeFinally(scope, blockError);
}
}
/**
* Track `lifetime` so that it is disposed when this scope is disposed.
*/
manage(lifetime) {
this._disposables.value.add(lifetime);
return lifetime;
}
get alive() {
return this._disposables.alive;
}
dispose() {
const lifetimes = Array.from(this._disposables.value.values()).reverse();
for (const lifetime of lifetimes) {
if (lifetime.alive) {
lifetime.dispose();
}
}
this._disposables.dispose();
}
}
exports.Scope = Scope;
//# sourceMappingURL=lifetime.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
import { EitherModule } from "./emscripten-types";
import { OwnedHeapCharPointer, JSContextPointerPointer, JSValueConstPointerPointer, JSValuePointerPointer } from "./types-ffi";
import { Lifetime } from "./lifetime";
import { QuickJSHandle } from "./types";
/**
* @private
*/
export declare class ModuleMemory {
module: EitherModule;
constructor(module: EitherModule);
toPointerArray(handleArray: QuickJSHandle[]): Lifetime<JSValueConstPointerPointer>;
newMutablePointerArray<T extends JSContextPointerPointer | JSValuePointerPointer>(length: number): Lifetime<{
typedArray: Int32Array;
ptr: T;
}>;
newHeapCharPointer(string: string): Lifetime<OwnedHeapCharPointer>;
consumeHeapCharPointer(ptr: OwnedHeapCharPointer): string;
}

View File

@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleMemory = void 0;
const lifetime_1 = require("./lifetime");
/**
* @private
*/
class ModuleMemory {
constructor(module) {
this.module = module;
}
toPointerArray(handleArray) {
const typedArray = new Int32Array(handleArray.map((handle) => handle.value));
const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;
const ptr = this.module._malloc(numBytes);
var heapBytes = new Uint8Array(this.module.HEAPU8.buffer, ptr, numBytes);
heapBytes.set(new Uint8Array(typedArray.buffer));
return new lifetime_1.Lifetime(ptr, undefined, (ptr) => this.module._free(ptr));
}
newMutablePointerArray(length) {
const zeros = new Int32Array(new Array(length).fill(0));
const numBytes = zeros.length * zeros.BYTES_PER_ELEMENT;
const ptr = this.module._malloc(numBytes);
const typedArray = new Int32Array(this.module.HEAPU8.buffer, ptr, length);
typedArray.set(zeros);
return new lifetime_1.Lifetime({ typedArray, ptr }, undefined, (value) => this.module._free(value.ptr));
}
newHeapCharPointer(string) {
const numBytes = this.module.lengthBytesUTF8(string) + 1;
const ptr = this.module._malloc(numBytes);
this.module.stringToUTF8(string, ptr, numBytes);
return new lifetime_1.Lifetime(ptr, undefined, (value) => this.module._free(value));
}
consumeHeapCharPointer(ptr) {
const str = this.module.UTF8ToString(ptr);
this.module._free(ptr);
return str;
}
}
exports.ModuleMemory = ModuleMemory;
//# sourceMappingURL=memory.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../ts/memory.ts"],"names":[],"mappings":";;;AAOA,yCAAqC;AAGrC;;GAEG;AACH,MAAa,YAAY;IACvB,YAAmB,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAE3C,cAAc,CAAC,WAA4B;QACzC,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5E,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,GAAG,UAAU,CAAC,iBAAiB,CAAA;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAA+B,CAAA;QACvE,IAAI,SAAS,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;QACxE,SAAS,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;QAChD,OAAO,IAAI,mBAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,CAAC;IAED,sBAAsB,CACpB,MAAc;QAEd,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,iBAAiB,CAAA;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAM,CAAA;QAC9C,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAA;QACzE,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACrB,OAAO,IAAI,mBAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9F,CAAC;IAED,kBAAkB,CAAC,MAAc;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACxD,MAAM,GAAG,GAAyB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAyB,CAAA;QACvF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;QAC/C,OAAO,IAAI,mBAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;IAC1E,CAAC;IAED,sBAAsB,CAAC,GAAyB;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;QACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACtB,OAAO,GAAG,CAAA;IACZ,CAAC;CACF;AAnCD,oCAmCC","sourcesContent":["import { EitherModule } from \"./emscripten-types\"\nimport {\n OwnedHeapCharPointer,\n JSContextPointerPointer,\n JSValueConstPointerPointer,\n JSValuePointerPointer,\n} from \"./types-ffi\"\nimport { Lifetime } from \"./lifetime\"\nimport { EitherFFI, QuickJSHandle } from \"./types\"\n\n/**\n * @private\n */\nexport class ModuleMemory {\n constructor(public module: EitherModule) {}\n\n toPointerArray(handleArray: QuickJSHandle[]): Lifetime<JSValueConstPointerPointer> {\n const typedArray = new Int32Array(handleArray.map((handle) => handle.value))\n const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT\n const ptr = this.module._malloc(numBytes) as JSValueConstPointerPointer\n var heapBytes = new Uint8Array(this.module.HEAPU8.buffer, ptr, numBytes)\n heapBytes.set(new Uint8Array(typedArray.buffer))\n return new Lifetime(ptr, undefined, (ptr) => this.module._free(ptr))\n }\n\n newMutablePointerArray<T extends JSContextPointerPointer | JSValuePointerPointer>(\n length: number\n ): Lifetime<{ typedArray: Int32Array; ptr: T }> {\n const zeros = new Int32Array(new Array(length).fill(0))\n const numBytes = zeros.length * zeros.BYTES_PER_ELEMENT\n const ptr = this.module._malloc(numBytes) as T\n const typedArray = new Int32Array(this.module.HEAPU8.buffer, ptr, length)\n typedArray.set(zeros)\n return new Lifetime({ typedArray, ptr }, undefined, (value) => this.module._free(value.ptr))\n }\n\n newHeapCharPointer(string: string): Lifetime<OwnedHeapCharPointer> {\n const numBytes = this.module.lengthBytesUTF8(string) + 1\n const ptr: OwnedHeapCharPointer = this.module._malloc(numBytes) as OwnedHeapCharPointer\n this.module.stringToUTF8(string, ptr, numBytes)\n return new Lifetime(ptr, undefined, (value) => this.module._free(value))\n }\n\n consumeHeapCharPointer(ptr: OwnedHeapCharPointer): string {\n const str = this.module.UTF8ToString(ptr)\n this.module._free(ptr)\n return str\n }\n}\n"]}

Some files were not shown because too many files have changed in this diff Show More