Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/meta/src/node-es-module-loader/loader.mts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ export async function load(
};
}

if (urlObj.searchParams.has('url')) {
return {
format: 'module',
shortCircuit: true,
source: `export default ${JSON.stringify(urlObj.pathname)};`,
};
}

if (urlObj.pathname.endsWith('.json')) {
const source = readFileSync(urlObj.pathname, 'utf8');
return {
Expand Down
8 changes: 8 additions & 0 deletions packages/php-wasm/compile/php-post-message-to-js/config.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
dnl config.m4 for extension post_message_to_js

PHP_ARG_ENABLE(post_message_to_js, whether to enable post_message_to_js support,
[ --enable-post_message_to_js Enable post_message_to_js support])

if test "$PHP_POST_MESSAGE_TO_JS" != "no"; then
PHP_NEW_EXTENSION(post_message_to_js, post_message_to_js.c, $ext_shared)
fi
5 changes: 5 additions & 0 deletions packages/php-wasm/compile/php-post-message-to-js/config.w32
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ARG_ENABLE("post_message_to_js", "Enable post_message_to_js support", "no");

if (PHP_POST_MESSAGE_TO_JS == "yes") {
EXTENSION("post_message_to_js", "post_message_to_js.c");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include "php.h"
#include "post_message_to_js.h"
#include "ext/standard/info.h"
#include <emscripten.h>

/**
* Provided by php_wasm.c:
*/
extern size_t js_module_onMessage(const char *data, char **response_buffer);

/* {{{ PHP_FUNCTION */
PHP_FUNCTION(post_message_to_js)
{
char *data;
int data_len;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &data_len) == FAILURE) {
return;
}

char *response;
size_t response_len = js_module_onMessage(data, &response);
if (response_len != -1) {
zend_string *return_string = zend_string_init(response, response_len, 0);
free(response);
RETURN_NEW_STR(return_string);
} else {
RETURN_NULL();
}
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(post_message_to_js)
{
php_info_print_table_start();
php_info_print_table_row(2, "post_message_to_js support", "enabled");
php_info_print_table_end();
}
/* }}} */

/* {{{ post_message_to_js_functions[] */
const zend_function_entry post_message_to_js_functions[] = {
PHP_FE(post_message_to_js, arginfo_post_message_to_js)
PHP_FE_END
};
/* }}} */

/* {{{ post_message_to_js_module_entry */
zend_module_entry post_message_to_js_module_entry = {
STANDARD_MODULE_HEADER,
"post_message_to_js", /* Extension name */
post_message_to_js_functions, /* zend_function_entry */
NULL, /* PHP_MINIT - Module initialization */
NULL, /* PHP_MSHUTDOWN - Module shutdown */
NULL, /* PHP_RINIT - Request initialization */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(post_message_to_js), /* PHP_MINFO - Module info */
PHP_POST_MESSAGE_TO_JS_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_POST_MESSAGE_TO_JS
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(post_message_to_js)
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef PHP_POST_MESSAGE_TO_JS_H
#define PHP_POST_MESSAGE_TO_JS_H

extern zend_module_entry post_message_to_js_module_entry;
#define phpext_post_message_to_js_ptr &post_message_to_js_module_entry

ZEND_BEGIN_ARG_INFO_EX(arginfo_post_message_to_js, 0, 1, 1)
ZEND_ARG_INFO(0, data)
ZEND_END_ARG_INFO()

PHP_FUNCTION(post_message_to_js);

#define PHP_POST_MESSAGE_TO_JS_VERSION "1.0.0"

#endif // PHP_POST_MESSAGE_TO_JS_H
4 changes: 4 additions & 0 deletions packages/php-wasm/compile/php/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ COPY ./php-wasm-memory-storage /root/php-src/ext/wasm_memory_storage
# Polyfill for dns functions
COPY ./php-wasm-dns-polyfill/ /root/php-src/ext/dns_polyfill

# Polyfill for post_message_to_js functions
COPY ./php-post-message-to-js/ /root/php-src/ext/post_message_to_js

# Work around to add ICU FLAGS to intl below 7.3 to inject -fPIC.
RUN if [[ "${PHP_VERSION:0:1}" -eq "7" && "${PHP_VERSION:2:1}" -eq "2" ]]; then \
sed -i "s/\$ICU_INCS/\$ICU_INCS \$ICU_CFLAGS/" /root/php-src/ext/intl/config.m4; \
Expand Down Expand Up @@ -369,6 +372,7 @@ CURL_LIBS="-I/root/lib/lib -L/root/lib/lib" \
--enable-tokenizer \
--enable-wasm_memory_storage \
--enable-dns_polyfill \
--enable-post_message_to_js \
$(cat /root/.php-configure-flags)

# Silence the errors "munmap() failed: [28] Invalid argument"
Expand Down
27 changes: 0 additions & 27 deletions packages/php-wasm/compile/php/php_wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -726,33 +726,6 @@ ZEND_BEGIN_ARG_INFO(arginfo_dl, 0)
ZEND_ARG_INFO(0, extension_filename)
ZEND_END_ARG_INFO()



/* Enable PHP to exchange messages with JavaScript */
PHP_FUNCTION(post_message_to_js)
{
char *data;
int data_len;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &data_len) == FAILURE)
{
return;
}

char *response;
size_t response_len = js_module_onMessage(data, &response);
if (response_len != -1)
{
zend_string *return_string = zend_string_init(response, response_len, 0);
free(response);
RETURN_NEW_STR(return_string);
}
else
{
RETURN_NULL();
}
}

/**
* select(2).
*/
Expand Down
73 changes: 69 additions & 4 deletions packages/php-wasm/node/asyncify/php_7_2.js
Original file line number Diff line number Diff line change
Expand Up @@ -7058,7 +7058,29 @@ export function init(RuntimeName, PHPLoader) {
};
PHPWASM.child_proc_by_fd = {};
PHPWASM.child_proc_by_pid = {};

PHPWASM.input_devices = {};
const originalWrite = TTY.stream_ops.write;
TTY.stream_ops.write = function (stream, ...rest) {
const retval = originalWrite(stream, ...rest);
// Implicit flush since PHP's fflush() doesn't seem to trigger the fsync event
// @TODO: Fix this at the wasm level
stream.tty.ops.fsync(stream.tty);
return retval;
};
const originalPutChar = TTY.stream_ops.put_char;
TTY.stream_ops.put_char = function (tty, val) {
/**
* Buffer newlines that Emscripten normally ignores.
*
* Emscripten doesn't do it by default because its default
* print function is console.log that implicitly adds a newline. We are overwriting
* it with an environment-specific function that outputs exaclty what it was given,
* e.g. in Node.js it's process.stdout.write(). Therefore, we need to mak sure
* all the newlines make it to the output buffer.
*/ if (val === 10) tty.output.push(val);
return originalPutChar(tty, val);
};
},
getAllWebSockets: function (sock) {
const webSockets = new Set();
Expand Down Expand Up @@ -7361,14 +7383,57 @@ export function init(RuntimeName, PHPLoader) {
* the process has already been spawned. We can only listen
* to the 'spawn' event and if it has already been spawned,
* listen to the 'exit' event.
*/ try {
*/
try {
await new Promise((resolve, reject) => {
cp.on('spawn', resolve);
cp.on('error', reject);
/**
* There was no `await` between the `spawnProcess` call
* and the `await` below so the process haven't had a chance
* to run any of the exit-related callbacks yet.
*
* Good.
*
* Let's listen to all the lifecycle events and resolve
* the promise when the process starts or immediately crashes.
*/
let resolved = false;
cp.on('spawn', () => {
if (resolved) return;
resolved = true;
resolve();
});
cp.on('error', (e) => {
if (resolved) return;
resolved = true;
reject(e);
});
cp.on('exit', function (code) {
if (resolved) return;
resolved = true;
if (code === 0) {
resolve();
} else {
reject(
new Error(`Process exited with code ${code}`)
);
}
});
/**
* If the process haven't even started after 5 seconds, something
* is wrong. Perhaps we're missing an event listener, or perhaps
* the `spawnProcess` implementation failed to dispatch the relevant
* event. Either way, let's crash to avoid blocking the proc_open()
* call indefinitely.
*/
setTimeout(() => {
if (resolved) return;
resolved = true;
reject(new Error('Process timed out'));
}, 5000);
});
} catch (e) {
console.error(e);
wakeUp(1);
wakeUp(ProcInfo.pid);
return;
}
// Now we want to pass data from the STDIN source supplied by PHP
Expand Down
73 changes: 69 additions & 4 deletions packages/php-wasm/node/asyncify/php_7_3.js
Original file line number Diff line number Diff line change
Expand Up @@ -7058,7 +7058,29 @@ export function init(RuntimeName, PHPLoader) {
};
PHPWASM.child_proc_by_fd = {};
PHPWASM.child_proc_by_pid = {};

PHPWASM.input_devices = {};
const originalWrite = TTY.stream_ops.write;
TTY.stream_ops.write = function (stream, ...rest) {
const retval = originalWrite(stream, ...rest);
// Implicit flush since PHP's fflush() doesn't seem to trigger the fsync event
// @TODO: Fix this at the wasm level
stream.tty.ops.fsync(stream.tty);
return retval;
};
const originalPutChar = TTY.stream_ops.put_char;
TTY.stream_ops.put_char = function (tty, val) {
/**
* Buffer newlines that Emscripten normally ignores.
*
* Emscripten doesn't do it by default because its default
* print function is console.log that implicitly adds a newline. We are overwriting
* it with an environment-specific function that outputs exaclty what it was given,
* e.g. in Node.js it's process.stdout.write(). Therefore, we need to mak sure
* all the newlines make it to the output buffer.
*/ if (val === 10) tty.output.push(val);
return originalPutChar(tty, val);
};
},
getAllWebSockets: function (sock) {
const webSockets = new Set();
Expand Down Expand Up @@ -7361,14 +7383,57 @@ export function init(RuntimeName, PHPLoader) {
* the process has already been spawned. We can only listen
* to the 'spawn' event and if it has already been spawned,
* listen to the 'exit' event.
*/ try {
*/
try {
await new Promise((resolve, reject) => {
cp.on('spawn', resolve);
cp.on('error', reject);
/**
* There was no `await` between the `spawnProcess` call
* and the `await` below so the process haven't had a chance
* to run any of the exit-related callbacks yet.
*
* Good.
*
* Let's listen to all the lifecycle events and resolve
* the promise when the process starts or immediately crashes.
*/
let resolved = false;
cp.on('spawn', () => {
if (resolved) return;
resolved = true;
resolve();
});
cp.on('error', (e) => {
if (resolved) return;
resolved = true;
reject(e);
});
cp.on('exit', function (code) {
if (resolved) return;
resolved = true;
if (code === 0) {
resolve();
} else {
reject(
new Error(`Process exited with code ${code}`)
);
}
});
/**
* If the process haven't even started after 5 seconds, something
* is wrong. Perhaps we're missing an event listener, or perhaps
* the `spawnProcess` implementation failed to dispatch the relevant
* event. Either way, let's crash to avoid blocking the proc_open()
* call indefinitely.
*/
setTimeout(() => {
if (resolved) return;
resolved = true;
reject(new Error('Process timed out'));
}, 5000);
});
} catch (e) {
console.error(e);
wakeUp(1);
wakeUp(ProcInfo.pid);
return;
}
// Now we want to pass data from the STDIN source supplied by PHP
Expand Down
Loading