From 3e67bb946b91a4ae429c4c7bf7b76dc650043262 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Thu, 8 Jan 2026 17:03:57 +0100 Subject: [PATCH 1/6] Provide an actually working implementation of the webbrowser mocked package --- include/pyjs/pre_js/init.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/include/pyjs/pre_js/init.js b/include/pyjs/pre_js/init.js index 1651d1b..8868a66 100644 --- a/include/pyjs/pre_js/init.js +++ b/include/pyjs/pre_js/init.js @@ -236,6 +236,8 @@ def _add_resolve_done_callback(future, resolve, reject): import sys import types import time +import pyjs +from js import postMessage sys.modules["fcntl"] = types.ModuleType("fcntl") sys.modules["pexpect"] = types.ModuleType("pexpect") @@ -262,17 +264,39 @@ _mock_termios() del _mock_termios def _mock_webbrowser(): + webbrowser_mock = types.ModuleType("webbrowser") + + def get(): + webbrowser_mock + def open(url, new=0, autoraise=True): - pass + try: + from js import window + + window.open(url) + except ImportError: + # Assuming we're in a web worker + # This is sent to the main thread, which will do the window.open + obj = pyjs.js.Function("url","n", + """ + return {'OPEN_TAB':{'url': url, 'new': n}} + """ + )(url, new) + postMessage(obj) + def open_new(url): return open(url, 1) + def open_new_tab(url): return open(url, 2) - webbrowser_mock = types.ModuleType("webbrowser") + # We cannot detect the current browser name from a web worker, we just pretend it's Firefox + webbrowser_mock.name = "firefox" + webbrowser_mock.get = get webbrowser_mock.open = open webbrowser_mock.open_new = open_new webbrowser_mock.open_new_tab = open_new_tab + webbrowser_mock.Error = RuntimeError sys.modules["webbrowser"] = webbrowser_mock _mock_webbrowser() From d2a93c84cafe3648b3fde6390bd537820a738d04 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Thu, 8 Jan 2026 17:06:30 +0100 Subject: [PATCH 2/6] Enhance comment --- include/pyjs/pre_js/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pyjs/pre_js/init.js b/include/pyjs/pre_js/init.js index 8868a66..72a91e7 100644 --- a/include/pyjs/pre_js/init.js +++ b/include/pyjs/pre_js/init.js @@ -276,7 +276,7 @@ def _mock_webbrowser(): window.open(url) except ImportError: # Assuming we're in a web worker - # This is sent to the main thread, which will do the window.open + # This is sent to the main thread, which will do the window.open if implemented obj = pyjs.js.Function("url","n", """ return {'OPEN_TAB':{'url': url, 'new': n}} From f13f7f35d007e2006ebbb13979cf3caf8234511c Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 9 Jan 2026 10:22:22 +0100 Subject: [PATCH 3/6] Lazy import pyjs --- include/pyjs/pre_js/init.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/pyjs/pre_js/init.js b/include/pyjs/pre_js/init.js index 72a91e7..0150ba3 100644 --- a/include/pyjs/pre_js/init.js +++ b/include/pyjs/pre_js/init.js @@ -4,7 +4,7 @@ Module._is_initialized = false Module['init_phase_1'] = async function(prefix, python_version, verbose) { - if(verbose){console.log("in init phase 1");} + if(verbose){console.log("in init phase 1");} let version_str = `${python_version[0]}.${python_version[1]}`; // list of python objects we need to delete when cleaning up @@ -19,7 +19,7 @@ Module['init_phase_1'] = async function(prefix, python_version, verbose) { var p = await Module['_wait_run_dependencies'](); if(prefix == "/"){ - Module.setenv("LANG", "en_US.UTF-8"); + Module.setenv("LANG", "en_US.UTF-8"); // LC_COLLATE="C" // LC_CTYPE="UTF-8" @@ -48,7 +48,7 @@ Module['init_phase_1'] = async function(prefix, python_version, verbose) { } - + // Module["_interpreter"] = new Module["_Interpreter"]() console.log("initialize interpreter"); @@ -164,7 +164,7 @@ Module['init_phase_1'] = async function(prefix, python_version, verbose) { return ret['ret'] } }; - if(verbose){console.log("in init phase 1 done!!");} + if(verbose){console.log("in init phase 1 done!!");} } Module['init_phase_2'] = function(prefix, python_version, verbose) { @@ -216,7 +216,7 @@ def _add_resolve_done_callback(future, resolve, reject): ensured_future.add_done_callback(done) `) - + Module._add_resolve_done_callback = Module.eval(`_add_resolve_done_callback`) Module._py_objects.push(Module._add_resolve_done_callback); @@ -236,8 +236,6 @@ def _add_resolve_done_callback(future, resolve, reject): import sys import types import time -import pyjs -from js import postMessage sys.modules["fcntl"] = types.ModuleType("fcntl") sys.modules["pexpect"] = types.ModuleType("pexpect") @@ -277,6 +275,9 @@ def _mock_webbrowser(): except ImportError: # Assuming we're in a web worker # This is sent to the main thread, which will do the window.open if implemented + import pyjs + from js import postMessage + obj = pyjs.js.Function("url","n", """ return {'OPEN_TAB':{'url': url, 'new': n}} From a7ff292bdea12d6beb063dbc3e054b8efa8080c9 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 9 Jan 2026 10:32:34 +0100 Subject: [PATCH 4/6] Only test import --- tests/tests/test_pyjs.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/tests/test_pyjs.py b/tests/tests/test_pyjs.py index 94497d2..745d6b3 100644 --- a/tests/tests/test_pyjs.py +++ b/tests/tests/test_pyjs.py @@ -23,10 +23,10 @@ if sys.version_info[0] == 3 and sys.version_info[1] >= 13: def test_ssl_import(): import ssl - + def test_import_lzma(): import lzma - + def test_js_submodule(): from pyjs.js import Function @@ -247,8 +247,8 @@ def test_to_js_dict(): def test_bytes_to_js(): - pyjs.to_js(b"\x00").byteLength == 1 - + pyjs.to_js(b"\x00").byteLength == 1 + def test_to_js_none(): @@ -393,7 +393,7 @@ def implicit_to_js(self): obj = pyjs.js_object() obj["the_value"] = self.value return obj - + class Bar(object): def __init__(self, value): self.value = value @@ -403,7 +403,7 @@ def explicit_to_js(self): obj = pyjs.js_object() obj["the_value"] = self.value return obj - + class FooBarInstance(object): def __init__(self, value): self.value = value @@ -419,8 +419,8 @@ def implicit_to_js(self): obj = pyjs.js_object() obj["the_implicit_value"] = self.value return obj - - + + foo_instance = Foo(42) js_function = pyjs.js.Function("instance", """ @@ -439,7 +439,7 @@ def implicit_to_js(self): """) assert js_function(foo_bar_instance) is True assert js_function(pyjs.to_js(foo_bar_instance)) is False - + js_function = pyjs.js.Function("instance", """ return instance.the_explicit_value === 42; """) @@ -464,13 +464,13 @@ def test_literal_map(): if (converted.get("foo") !== 1 || converted.get("bar") !== "bar" || !converted.get("foobar").includes("foo") || !converted.get("foobar").includes("bar")) { throw new Error("converted does not have the correct keys"); } - // ensure we can access the values via . + // ensure we can access the values via . if (converted.foo !== 2 || converted.bar !== "bar" || !converted.foobar.includes("foo") || !converted.foobar.includes("bar")) { throw new Error("converted does not have the correct values when accessed via ."); } return true; """) - + def test_del_attr(): obj = eval_jsfunc( @@ -696,7 +696,3 @@ def test_imports_sys(): def test_webbrowser(): from webbrowser import open, open_new, open_new_tab - - open("google.com") - open_new("google.com") - open_new_tab("google.com") From 9ac9efdefa77fb2c1440ada990b813e1ecd76d0f Mon Sep 17 00:00:00 2001 From: Thorsten Beier Date: Fri, 9 Jan 2026 11:23:49 +0100 Subject: [PATCH 5/6] Update init.js --- include/pyjs/pre_js/init.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/include/pyjs/pre_js/init.js b/include/pyjs/pre_js/init.js index 0150ba3..80a6e8c 100644 --- a/include/pyjs/pre_js/init.js +++ b/include/pyjs/pre_js/init.js @@ -268,22 +268,19 @@ def _mock_webbrowser(): webbrowser_mock def open(url, new=0, autoraise=True): - try: - from js import window - - window.open(url) - except ImportError: - # Assuming we're in a web worker + import pyjs + is_main_thread = pyjs.js.Function("""return typeof WorkerGlobalScope === "undefined" || !(self instanceof WorkerGlobalScope);""")() + if is_main_thread: + pyjs.js.window.open(url) + else: + # we're in a web worker # This is sent to the main thread, which will do the window.open if implemented - import pyjs - from js import postMessage - obj = pyjs.js.Function("url","n", """ return {'OPEN_TAB':{'url': url, 'new': n}} """ )(url, new) - postMessage(obj) + pyjs.js.postMessage(obj) def open_new(url): return open(url, 1) From 4600c0244e9d6cb97c9f12b8bdf7b0e354ebc967 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 9 Jan 2026 13:29:20 +0100 Subject: [PATCH 6/6] Apply review suggestion --- include/pyjs/pre_js/init.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/pyjs/pre_js/init.js b/include/pyjs/pre_js/init.js index 80a6e8c..2ccb5cf 100644 --- a/include/pyjs/pre_js/init.js +++ b/include/pyjs/pre_js/init.js @@ -288,8 +288,12 @@ def _mock_webbrowser(): def open_new_tab(url): return open(url, 2) - # We cannot detect the current browser name from a web worker, we just pretend it's Firefox - webbrowser_mock.name = "firefox" + webbrowser_mock.name = pyjs.js.Function(""" + return /firefox/i.test(navigator.userAgent) ? "firefox" + : /edg/i.test(navigator.userAgent) ? "edge" + : /chrome|crios/i.test(navigator.userAgent) ? "chrome" + : /safari/i.test(navigator.userAgent) ? "safari" + : "Unknown";""")() webbrowser_mock.get = get webbrowser_mock.open = open webbrowser_mock.open_new = open_new