gecko-web-runtime-client 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. gecko_web_runtime_client-1.1.0/PKG-INFO +293 -0
  2. gecko_web_runtime_client-1.1.0/README.md +282 -0
  3. gecko_web_runtime_client-1.1.0/gecko_web_runtime/__init__.py +51 -0
  4. gecko_web_runtime_client-1.1.0/gecko_web_runtime/__main__.py +23 -0
  5. gecko_web_runtime_client-1.1.0/gecko_web_runtime/api/__init__.py +7 -0
  6. gecko_web_runtime_client-1.1.0/gecko_web_runtime/api/context.py +110 -0
  7. gecko_web_runtime_client-1.1.0/gecko_web_runtime/api/page.py +350 -0
  8. gecko_web_runtime_client-1.1.0/gecko_web_runtime/api/pool.py +146 -0
  9. gecko_web_runtime_client-1.1.0/gecko_web_runtime/api/runtime.py +186 -0
  10. gecko_web_runtime_client-1.1.0/gecko_web_runtime/data_plane.py +111 -0
  11. gecko_web_runtime_client-1.1.0/gecko_web_runtime/diagnostics/__init__.py +14 -0
  12. gecko_web_runtime_client-1.1.0/gecko_web_runtime/diagnostics/gc_cc.py +68 -0
  13. gecko_web_runtime_client-1.1.0/gecko_web_runtime/diagnostics/logging.py +39 -0
  14. gecko_web_runtime_client-1.1.0/gecko_web_runtime/diagnostics/memory.py +194 -0
  15. gecko_web_runtime_client-1.1.0/gecko_web_runtime/diagnostics/metrics.py +551 -0
  16. gecko_web_runtime_client-1.1.0/gecko_web_runtime/diagnostics/resources.py +60 -0
  17. gecko_web_runtime_client-1.1.0/gecko_web_runtime/http.py +184 -0
  18. gecko_web_runtime_client-1.1.0/gecko_web_runtime/installer.py +198 -0
  19. gecko_web_runtime_client-1.1.0/gecko_web_runtime/models/__init__.py +5 -0
  20. gecko_web_runtime_client-1.1.0/gecko_web_runtime/models/navigation.py +41 -0
  21. gecko_web_runtime_client-1.1.0/gecko_web_runtime/pipe.py +52 -0
  22. gecko_web_runtime_client-1.1.0/gecko_web_runtime/session.py +995 -0
  23. gecko_web_runtime_client-1.1.0/gecko_web_runtime/workflows/__init__.py +6 -0
  24. gecko_web_runtime_client-1.1.0/gecko_web_runtime/workflows/turnstile.py +680 -0
  25. gecko_web_runtime_client-1.1.0/gecko_web_runtime/workflows/wechat.py +706 -0
  26. gecko_web_runtime_client-1.1.0/gecko_web_runtime_client.egg-info/PKG-INFO +293 -0
  27. gecko_web_runtime_client-1.1.0/gecko_web_runtime_client.egg-info/SOURCES.txt +30 -0
  28. gecko_web_runtime_client-1.1.0/gecko_web_runtime_client.egg-info/dependency_links.txt +1 -0
  29. gecko_web_runtime_client-1.1.0/gecko_web_runtime_client.egg-info/requires.txt +3 -0
  30. gecko_web_runtime_client-1.1.0/gecko_web_runtime_client.egg-info/top_level.txt +1 -0
  31. gecko_web_runtime_client-1.1.0/pyproject.toml +35 -0
  32. gecko_web_runtime_client-1.1.0/setup.cfg +4 -0
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.4
2
+ Name: gecko-web-runtime-client
3
+ Version: 1.1.0
4
+ Summary: Thin Python client for the Firefox-derived WPR Gecko Worker protocol
5
+ Author: WPR project
6
+ License: MPL-2.0-compatible client; see the runtime bundle notices
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Provides-Extra: diagnostics
10
+ Requires-Dist: loguru>=0.7.2; extra == "diagnostics"
11
+
12
+ # Python client
13
+
14
+ The client provides a standard-library core plus an optional Loguru-backed
15
+ diagnostics layer. From this directory:
16
+
17
+ ```powershell
18
+ $env:PYTHONPATH = "$PWD"
19
+ python ..\examples\normal_navigation.py
20
+ ```
21
+
22
+ The package resolves `runtime/gecko-web-worker.exe` relative to its own
23
+ bundle. Override paths with `WPR_RUNTIME_HOME`, `WPR_RUNTIME_DIR`,
24
+ `WPR_WORKER_EXE`, `WPR_PROFILE_ROOT`, or `WPR_LOG_ROOT` when embedding it in a
25
+ larger application.
26
+
27
+ ## Firefox Necko network client
28
+
29
+ The version-neutral `wpr-http-v1` API is exposed by
30
+ `gecko_web_runtime.AsyncWprHttpClient`:
31
+
32
+ ```python
33
+ from gecko_web_runtime import AsyncWprHttpClient
34
+
35
+ async with AsyncWprHttpClient() as client:
36
+ response = await client.get("https://example.com/")
37
+ html = await response.atext()
38
+ ```
39
+
40
+ The selected Firefox bundle launches `runtime/net-v155/gecko-net-worker.exe`
41
+ (the versioned adapter may be replaced by a later bundle). Its response body
42
+ uses shared memory and is released when `atext()`, `aread()`, or `aclose()`
43
+ completes. Set `WPR_RUNTIME_HOME`, `WPR_RUNTIME_DIR`, and
44
+ `WPR_NETWORK_WORKER_EXE` to select a different Firefox-version adapter.
45
+
46
+ To install the pinned runtime automatically from the GitHub Release:
47
+
48
+ ```powershell
49
+ python -m gecko_web_runtime install-runtime --version 155
50
+ ```
51
+
52
+ The command verifies the pinned SHA256 and stores the selected runtime under
53
+ `%LOCALAPPDATA%\WPR\runtime\155`.
54
+
55
+ For development, use `uv` from this directory so the Python environment and
56
+ lock file are reproducible:
57
+
58
+ ```powershell
59
+ cd G:\wpr-runtime-155-win64\python
60
+ uv sync --extra diagnostics
61
+ uv run python -m unittest discover -s tests\unit -v
62
+ ```
63
+
64
+ The Gecko binary is intentionally not a Python dependency. Install or select
65
+ the matching runtime separately after the environment is ready:
66
+
67
+ ```powershell
68
+ uv run python -m gecko_web_runtime install-runtime --version 155
69
+ ```
70
+
71
+ Live network workflow tests are opt-in:
72
+
73
+ ```powershell
74
+ $env:WPR_RUN_ONLINE_TESTS = "1"
75
+ uv run python -m unittest discover -s tests\integration -v
76
+ ```
77
+
78
+ To measure cold/warm navigation and the current isolation boundary:
79
+
80
+ ```powershell
81
+ uv run python tests\integration\context_reuse_benchmark.py `
82
+ --url https://example.com/ `
83
+ --out context-reuse.json
84
+ ```
85
+
86
+ The benchmark records first/second navigation timing, frame remote types,
87
+ WindowGlobal replacement, Worker PIDs and whether an isolated context sees a
88
+ Cookie from the persistent context.
89
+
90
+ An optional per-Worker `fpfile` path can be passed to the session. The Worker
91
+ reads the file before creating the remote page; its Firefox profile remains a
92
+ separate Cookie/Storage directory:
93
+
94
+ ```python
95
+ async with AsyncGeckoWorkerSession(
96
+ fpfile=r"G:\browser-runtime\fpfile.txt"
97
+ ) as session:
98
+ await session.page_create()
99
+ ```
100
+
101
+ For application code, prefer the high-level runtime facade and workflow
102
+ adapter. Frame handles, pointer coordinates, WindowGlobal rebinding and
103
+ challenge waits remain internal:
104
+
105
+ ```python
106
+ from gecko_web_runtime import AsyncGeckoRuntime
107
+
108
+ async with AsyncGeckoRuntime(fpfile=r"G:\browser-runtime\fpfile.txt") as runtime:
109
+ page = await runtime.new_page()
110
+ result = await page.navigate(
111
+ "https://nopecha.com/demo/cloudflare",
112
+ workflow="turnstile",
113
+ )
114
+ print(result.ok, result.url, len(result.html))
115
+ ```
116
+
117
+ For WeChat public-account articles, use the site adapter instead of the
118
+ Cloudflare-specific Turnstile workflow:
119
+
120
+ ```python
121
+ async with AsyncGeckoRuntime() as runtime:
122
+ page = await runtime.new_page()
123
+ result = await page.navigate(article_url, workflow="wechat_article")
124
+ if result.ok:
125
+ article_html = result.html
126
+ ```
127
+
128
+ Use `WorkflowMetrics` when an application needs a stable result/metrics
129
+ boundary. The default output contains the product chain stages (`T0`...`T9`)
130
+ plus a compact report with success, final URL, HTML size,
131
+ total/navigation/final-ready time, and parent/whole-process-tree memory peaks.
132
+ Detailed attempts, phase timings, and per-stage resource snapshots are
133
+ emitted only with `debug=True` or `emit="debug"`:
134
+
135
+ ```python
136
+ from gecko_web_runtime import WorkflowMetrics
137
+
138
+ metrics = WorkflowMetrics(name="wechat", emit="summary", print_summary=True)
139
+ summary = metrics.emit(result)
140
+ ```
141
+
142
+ Library code can set `emit="none"` and consume the returned dictionary without
143
+ printing. `WPR_METRICS_EMIT`, `WPR_METRICS_LOG_LEVEL`, `WPR_METRICS_DEBUG`,
144
+ `WPR_METRICS_PRINT`, and `WPR_METRICS_MEMORY` provide process-level defaults.
145
+
146
+ The adapter recognizes the WeChat/Tencent gate, rebinds the current top
147
+ WindowGlobal, and dispatches a real pointer sequence to the visible
148
+ verification target. It reports slider/image challenges as a bounded result
149
+ for a future dedicated solver rather than treating the challenge page as an
150
+ article. It is one-shot by default and closes the Page after the workflow
151
+ boundary; pass `WechatArticleWorkflow(close_after=False)` when intentionally
152
+ reusing the Page.
153
+
154
+ For multiple logical sessions, keep the Worker runtime alive but make the
155
+ storage boundary explicit:
156
+
157
+ ```python
158
+ async with AsyncGeckoRuntime(fpfile="fpfile-a.txt") as runtime:
159
+ async with runtime.new_context(isolation="persistent") as context_a:
160
+ page_a = await context_a.new_page()
161
+ result_a = await page_a.navigate(url_a)
162
+
163
+ async with runtime.new_context(isolation="isolated") as context_b:
164
+ page_b = await context_b.new_page()
165
+ result_b = await page_b.navigate(url_b)
166
+ ```
167
+
168
+ `persistent` reuses the runtime's current Worker while each Context receives
169
+ its native Firefox OriginAttributes boundary (`user_context_id` and optional
170
+ `private_browsing_id`). `isolated` remains the explicit separate Worker and
171
+ profile mode for callers that require process-level isolation.
172
+
173
+ Turnstile results expose phase timings in `result.timing_ms`, including
174
+ per-attempt iframe/action waits, POST, redirect, final Document readiness, and
175
+ HTML serialization. Worker PID, Working Set, Private Bytes, and CPU samples
176
+ are available in `result.resource_usage`. Sampling can be disabled for a
177
+ low-overhead benchmark with `TurnstileWorkflow(resource_sampling=False)` or
178
+ `WPR_RESOURCE_SAMPLING=0`. The integration test writes the complete result
179
+ to the path in `WPR_RESULT_OUT` when set.
180
+
181
+ Challenge retries use the `adaptive` policy by default. After a top-level
182
+ state transition it waits for the network settle window and stops immediately
183
+ on POST/redirect; it only dispatches another action when no submit arrives.
184
+ `retry_policy="legacy"` preserves the previous retry loop for A/B experiments,
185
+ while `retry_policy="generation"` is a strict diagnostic policy that requires a
186
+ new `BrowsingContext`/`WindowGlobal` before another action. The third-party
187
+ client exposes the same switch as `--retry-policy`. Debug results include
188
+ `state_change_to_submit_ms` when both observations were captured on the same
189
+ client monotonic clock.
190
+
191
+ The final boundary is configurable. `final_wait_until="complete"` waits for
192
+ the full DOM/load boundary and is the compatibility default. Set
193
+ `WPR_TURNSTILE_WAIT_UNTIL=document` (or pass `final_wait_until="document"`)
194
+ when the caller only needs the final Document HTML; the result then reports
195
+ `final_document`, `final_load`, and the selected `final_ready` boundary
196
+ separately. Document mode does not wait for the page's scripts, stylesheets,
197
+ images, or analytics resources to finish.
198
+
199
+ Large results use a separate data plane. `page.eval_shared_blob()` uses a
200
+ one-shot mapping, while `page.eval_shared_ring()`/`page.html_shared_ring()` use
201
+ a Worker-lifetime fixed-slot ring. Both return a small descriptor over the
202
+ control pipe and a read-only Windows named mapping for the bytes. Read it as
203
+ a `memoryview` or decode it with `read_text()`, then release the lease
204
+ explicitly:
205
+
206
+ ```python
207
+ blob = await page.html_shared_ring()
208
+ try:
209
+ html = blob.read_text()
210
+ finally:
211
+ await blob.release()
212
+ ```
213
+
214
+ This keeps multi-megabyte HTML out of JSON serialization and the named-pipe
215
+ response. The ring mapping is created once per Worker and reuses slots using
216
+ generation-bearing leases; `data.release` returns a slot to the ring. Set
217
+ `WPR_HTML_TRANSPORT=json`, `blob`, or `ring` to A/B the adapter boundary. The
218
+ WeChat and Turnstile adapters default to `ring` and fall back to ordinary
219
+ `page.eval` when an older Worker has no data-plane method.
220
+
221
+ The long-lived comparison probe keeps one Worker per site/transport, uses a
222
+ fresh OriginAttributes context for every sample, and records success rate,
223
+ P95, CPU, Worker/process-tree memory, and Gecko reporter stages:
224
+
225
+ ```powershell
226
+ uv run python tests\integration\data_plane_ab_benchmark.py `
227
+ --runs 10 --modes json ring
228
+ ```
229
+
230
+ The low-level `AsyncGeckoWorkerSession` API remains available for custom
231
+ workflows and backward compatibility. Turnstile is implemented in
232
+ `gecko_web_runtime.workflows.turnstile`; future site adapters belong in the
233
+ same package and are not part of the Gecko core.
234
+
235
+ The client project keeps its tests alongside the package source:
236
+
237
+ ```text
238
+ python/tests/unit/ # import/API contract tests
239
+ python/tests/integration/ # opt-in live Worker/workflow tests
240
+ ```
241
+
242
+ The larger `G:\browser-runtime\tests\probes` tree is a separate black-box
243
+ runtime audit harness. It exercises the complete bundled Gecko runtime and
244
+ stores diagnostic artifacts, so it is intentionally not included in the
245
+ Python wheel.
246
+
247
+ To enable structured application diagnostics:
248
+
249
+ ```bash
250
+ pip install gecko-web-runtime-client[diagnostics]
251
+ ```
252
+
253
+ ```python
254
+ from gecko_web_runtime.diagnostics import configure_logging
255
+
256
+ logger = configure_logging(log_file="runtime.log")
257
+ logger.info("application.start")
258
+ ```
259
+
260
+ For a real page WebRTC permission boundary, use the same session and answer
261
+ the native pending request. The Worker forwards Firefox's
262
+ `PeerConnection:response:allow` or `PeerConnection:response:deny` topic; it
263
+ does not manufacture SDP, ICE candidates, or media devices:
264
+
265
+ ```python
266
+ pending = await session.page_permission_requests()
267
+ for request in pending:
268
+ await session.page_permission_respond(request["callID"], "allow")
269
+ ```
270
+
271
+ Leaving the list unanswered preserves Firefox's normal prompt-pending
272
+ behavior. A response is scoped to the current WindowGlobal, so a navigation
273
+ that replaces it requires polling the new page's request list.
274
+
275
+ For a private GitHub Release, set `GITHUB_TOKEN`, `GH_TOKEN`, or
276
+ `WPR_GITHUB_TOKEN`, or configure Git Credential Manager for the GitHub account.
277
+ # Firefox Necko network client
278
+
279
+ The version-neutral `wpr-http-v1` API is exposed by
280
+ `gecko_web_runtime.AsyncWprHttpClient`:
281
+
282
+ ```python
283
+ from gecko_web_runtime import AsyncWprHttpClient
284
+
285
+ async with AsyncWprHttpClient() as client:
286
+ response = await client.get("https://example.com/")
287
+ html = await response.atext()
288
+ ```
289
+
290
+ The selected Firefox bundle launches `gecko-net-worker.exe`. Its response body
291
+ uses shared memory and is released when `atext()`, `aread()`, or `aclose()`
292
+ completes. Set `WPR_RUNTIME_HOME`, `WPR_RUNTIME_DIR`, and
293
+ `WPR_NETWORK_WORKER_EXE` to select a different Firefox-version adapter.
@@ -0,0 +1,282 @@
1
+ # Python client
2
+
3
+ The client provides a standard-library core plus an optional Loguru-backed
4
+ diagnostics layer. From this directory:
5
+
6
+ ```powershell
7
+ $env:PYTHONPATH = "$PWD"
8
+ python ..\examples\normal_navigation.py
9
+ ```
10
+
11
+ The package resolves `runtime/gecko-web-worker.exe` relative to its own
12
+ bundle. Override paths with `WPR_RUNTIME_HOME`, `WPR_RUNTIME_DIR`,
13
+ `WPR_WORKER_EXE`, `WPR_PROFILE_ROOT`, or `WPR_LOG_ROOT` when embedding it in a
14
+ larger application.
15
+
16
+ ## Firefox Necko network client
17
+
18
+ The version-neutral `wpr-http-v1` API is exposed by
19
+ `gecko_web_runtime.AsyncWprHttpClient`:
20
+
21
+ ```python
22
+ from gecko_web_runtime import AsyncWprHttpClient
23
+
24
+ async with AsyncWprHttpClient() as client:
25
+ response = await client.get("https://example.com/")
26
+ html = await response.atext()
27
+ ```
28
+
29
+ The selected Firefox bundle launches `runtime/net-v155/gecko-net-worker.exe`
30
+ (the versioned adapter may be replaced by a later bundle). Its response body
31
+ uses shared memory and is released when `atext()`, `aread()`, or `aclose()`
32
+ completes. Set `WPR_RUNTIME_HOME`, `WPR_RUNTIME_DIR`, and
33
+ `WPR_NETWORK_WORKER_EXE` to select a different Firefox-version adapter.
34
+
35
+ To install the pinned runtime automatically from the GitHub Release:
36
+
37
+ ```powershell
38
+ python -m gecko_web_runtime install-runtime --version 155
39
+ ```
40
+
41
+ The command verifies the pinned SHA256 and stores the selected runtime under
42
+ `%LOCALAPPDATA%\WPR\runtime\155`.
43
+
44
+ For development, use `uv` from this directory so the Python environment and
45
+ lock file are reproducible:
46
+
47
+ ```powershell
48
+ cd G:\wpr-runtime-155-win64\python
49
+ uv sync --extra diagnostics
50
+ uv run python -m unittest discover -s tests\unit -v
51
+ ```
52
+
53
+ The Gecko binary is intentionally not a Python dependency. Install or select
54
+ the matching runtime separately after the environment is ready:
55
+
56
+ ```powershell
57
+ uv run python -m gecko_web_runtime install-runtime --version 155
58
+ ```
59
+
60
+ Live network workflow tests are opt-in:
61
+
62
+ ```powershell
63
+ $env:WPR_RUN_ONLINE_TESTS = "1"
64
+ uv run python -m unittest discover -s tests\integration -v
65
+ ```
66
+
67
+ To measure cold/warm navigation and the current isolation boundary:
68
+
69
+ ```powershell
70
+ uv run python tests\integration\context_reuse_benchmark.py `
71
+ --url https://example.com/ `
72
+ --out context-reuse.json
73
+ ```
74
+
75
+ The benchmark records first/second navigation timing, frame remote types,
76
+ WindowGlobal replacement, Worker PIDs and whether an isolated context sees a
77
+ Cookie from the persistent context.
78
+
79
+ An optional per-Worker `fpfile` path can be passed to the session. The Worker
80
+ reads the file before creating the remote page; its Firefox profile remains a
81
+ separate Cookie/Storage directory:
82
+
83
+ ```python
84
+ async with AsyncGeckoWorkerSession(
85
+ fpfile=r"G:\browser-runtime\fpfile.txt"
86
+ ) as session:
87
+ await session.page_create()
88
+ ```
89
+
90
+ For application code, prefer the high-level runtime facade and workflow
91
+ adapter. Frame handles, pointer coordinates, WindowGlobal rebinding and
92
+ challenge waits remain internal:
93
+
94
+ ```python
95
+ from gecko_web_runtime import AsyncGeckoRuntime
96
+
97
+ async with AsyncGeckoRuntime(fpfile=r"G:\browser-runtime\fpfile.txt") as runtime:
98
+ page = await runtime.new_page()
99
+ result = await page.navigate(
100
+ "https://nopecha.com/demo/cloudflare",
101
+ workflow="turnstile",
102
+ )
103
+ print(result.ok, result.url, len(result.html))
104
+ ```
105
+
106
+ For WeChat public-account articles, use the site adapter instead of the
107
+ Cloudflare-specific Turnstile workflow:
108
+
109
+ ```python
110
+ async with AsyncGeckoRuntime() as runtime:
111
+ page = await runtime.new_page()
112
+ result = await page.navigate(article_url, workflow="wechat_article")
113
+ if result.ok:
114
+ article_html = result.html
115
+ ```
116
+
117
+ Use `WorkflowMetrics` when an application needs a stable result/metrics
118
+ boundary. The default output contains the product chain stages (`T0`...`T9`)
119
+ plus a compact report with success, final URL, HTML size,
120
+ total/navigation/final-ready time, and parent/whole-process-tree memory peaks.
121
+ Detailed attempts, phase timings, and per-stage resource snapshots are
122
+ emitted only with `debug=True` or `emit="debug"`:
123
+
124
+ ```python
125
+ from gecko_web_runtime import WorkflowMetrics
126
+
127
+ metrics = WorkflowMetrics(name="wechat", emit="summary", print_summary=True)
128
+ summary = metrics.emit(result)
129
+ ```
130
+
131
+ Library code can set `emit="none"` and consume the returned dictionary without
132
+ printing. `WPR_METRICS_EMIT`, `WPR_METRICS_LOG_LEVEL`, `WPR_METRICS_DEBUG`,
133
+ `WPR_METRICS_PRINT`, and `WPR_METRICS_MEMORY` provide process-level defaults.
134
+
135
+ The adapter recognizes the WeChat/Tencent gate, rebinds the current top
136
+ WindowGlobal, and dispatches a real pointer sequence to the visible
137
+ verification target. It reports slider/image challenges as a bounded result
138
+ for a future dedicated solver rather than treating the challenge page as an
139
+ article. It is one-shot by default and closes the Page after the workflow
140
+ boundary; pass `WechatArticleWorkflow(close_after=False)` when intentionally
141
+ reusing the Page.
142
+
143
+ For multiple logical sessions, keep the Worker runtime alive but make the
144
+ storage boundary explicit:
145
+
146
+ ```python
147
+ async with AsyncGeckoRuntime(fpfile="fpfile-a.txt") as runtime:
148
+ async with runtime.new_context(isolation="persistent") as context_a:
149
+ page_a = await context_a.new_page()
150
+ result_a = await page_a.navigate(url_a)
151
+
152
+ async with runtime.new_context(isolation="isolated") as context_b:
153
+ page_b = await context_b.new_page()
154
+ result_b = await page_b.navigate(url_b)
155
+ ```
156
+
157
+ `persistent` reuses the runtime's current Worker while each Context receives
158
+ its native Firefox OriginAttributes boundary (`user_context_id` and optional
159
+ `private_browsing_id`). `isolated` remains the explicit separate Worker and
160
+ profile mode for callers that require process-level isolation.
161
+
162
+ Turnstile results expose phase timings in `result.timing_ms`, including
163
+ per-attempt iframe/action waits, POST, redirect, final Document readiness, and
164
+ HTML serialization. Worker PID, Working Set, Private Bytes, and CPU samples
165
+ are available in `result.resource_usage`. Sampling can be disabled for a
166
+ low-overhead benchmark with `TurnstileWorkflow(resource_sampling=False)` or
167
+ `WPR_RESOURCE_SAMPLING=0`. The integration test writes the complete result
168
+ to the path in `WPR_RESULT_OUT` when set.
169
+
170
+ Challenge retries use the `adaptive` policy by default. After a top-level
171
+ state transition it waits for the network settle window and stops immediately
172
+ on POST/redirect; it only dispatches another action when no submit arrives.
173
+ `retry_policy="legacy"` preserves the previous retry loop for A/B experiments,
174
+ while `retry_policy="generation"` is a strict diagnostic policy that requires a
175
+ new `BrowsingContext`/`WindowGlobal` before another action. The third-party
176
+ client exposes the same switch as `--retry-policy`. Debug results include
177
+ `state_change_to_submit_ms` when both observations were captured on the same
178
+ client monotonic clock.
179
+
180
+ The final boundary is configurable. `final_wait_until="complete"` waits for
181
+ the full DOM/load boundary and is the compatibility default. Set
182
+ `WPR_TURNSTILE_WAIT_UNTIL=document` (or pass `final_wait_until="document"`)
183
+ when the caller only needs the final Document HTML; the result then reports
184
+ `final_document`, `final_load`, and the selected `final_ready` boundary
185
+ separately. Document mode does not wait for the page's scripts, stylesheets,
186
+ images, or analytics resources to finish.
187
+
188
+ Large results use a separate data plane. `page.eval_shared_blob()` uses a
189
+ one-shot mapping, while `page.eval_shared_ring()`/`page.html_shared_ring()` use
190
+ a Worker-lifetime fixed-slot ring. Both return a small descriptor over the
191
+ control pipe and a read-only Windows named mapping for the bytes. Read it as
192
+ a `memoryview` or decode it with `read_text()`, then release the lease
193
+ explicitly:
194
+
195
+ ```python
196
+ blob = await page.html_shared_ring()
197
+ try:
198
+ html = blob.read_text()
199
+ finally:
200
+ await blob.release()
201
+ ```
202
+
203
+ This keeps multi-megabyte HTML out of JSON serialization and the named-pipe
204
+ response. The ring mapping is created once per Worker and reuses slots using
205
+ generation-bearing leases; `data.release` returns a slot to the ring. Set
206
+ `WPR_HTML_TRANSPORT=json`, `blob`, or `ring` to A/B the adapter boundary. The
207
+ WeChat and Turnstile adapters default to `ring` and fall back to ordinary
208
+ `page.eval` when an older Worker has no data-plane method.
209
+
210
+ The long-lived comparison probe keeps one Worker per site/transport, uses a
211
+ fresh OriginAttributes context for every sample, and records success rate,
212
+ P95, CPU, Worker/process-tree memory, and Gecko reporter stages:
213
+
214
+ ```powershell
215
+ uv run python tests\integration\data_plane_ab_benchmark.py `
216
+ --runs 10 --modes json ring
217
+ ```
218
+
219
+ The low-level `AsyncGeckoWorkerSession` API remains available for custom
220
+ workflows and backward compatibility. Turnstile is implemented in
221
+ `gecko_web_runtime.workflows.turnstile`; future site adapters belong in the
222
+ same package and are not part of the Gecko core.
223
+
224
+ The client project keeps its tests alongside the package source:
225
+
226
+ ```text
227
+ python/tests/unit/ # import/API contract tests
228
+ python/tests/integration/ # opt-in live Worker/workflow tests
229
+ ```
230
+
231
+ The larger `G:\browser-runtime\tests\probes` tree is a separate black-box
232
+ runtime audit harness. It exercises the complete bundled Gecko runtime and
233
+ stores diagnostic artifacts, so it is intentionally not included in the
234
+ Python wheel.
235
+
236
+ To enable structured application diagnostics:
237
+
238
+ ```bash
239
+ pip install gecko-web-runtime-client[diagnostics]
240
+ ```
241
+
242
+ ```python
243
+ from gecko_web_runtime.diagnostics import configure_logging
244
+
245
+ logger = configure_logging(log_file="runtime.log")
246
+ logger.info("application.start")
247
+ ```
248
+
249
+ For a real page WebRTC permission boundary, use the same session and answer
250
+ the native pending request. The Worker forwards Firefox's
251
+ `PeerConnection:response:allow` or `PeerConnection:response:deny` topic; it
252
+ does not manufacture SDP, ICE candidates, or media devices:
253
+
254
+ ```python
255
+ pending = await session.page_permission_requests()
256
+ for request in pending:
257
+ await session.page_permission_respond(request["callID"], "allow")
258
+ ```
259
+
260
+ Leaving the list unanswered preserves Firefox's normal prompt-pending
261
+ behavior. A response is scoped to the current WindowGlobal, so a navigation
262
+ that replaces it requires polling the new page's request list.
263
+
264
+ For a private GitHub Release, set `GITHUB_TOKEN`, `GH_TOKEN`, or
265
+ `WPR_GITHUB_TOKEN`, or configure Git Credential Manager for the GitHub account.
266
+ # Firefox Necko network client
267
+
268
+ The version-neutral `wpr-http-v1` API is exposed by
269
+ `gecko_web_runtime.AsyncWprHttpClient`:
270
+
271
+ ```python
272
+ from gecko_web_runtime import AsyncWprHttpClient
273
+
274
+ async with AsyncWprHttpClient() as client:
275
+ response = await client.get("https://example.com/")
276
+ html = await response.atext()
277
+ ```
278
+
279
+ The selected Firefox bundle launches `gecko-net-worker.exe`. Its response body
280
+ uses shared memory and is released when `atext()`, `aread()`, or `aclose()`
281
+ completes. Set `WPR_RUNTIME_HOME`, `WPR_RUNTIME_DIR`, and
282
+ `WPR_NETWORK_WORKER_EXE` to select a different Firefox-version adapter.
@@ -0,0 +1,51 @@
1
+ """Portable Python client for the Firefox-derived WPR runtime bundle."""
2
+
3
+ from .session import (
4
+ AsyncGeckoWorkerSession,
5
+ GeckoWorkerSession,
6
+ RuntimeConfig,
7
+ WprProtocolError,
8
+ )
9
+ from .installer import install_runtime
10
+ from .models.navigation import TurnstileResult, WechatArticleResult
11
+ from .workflows.turnstile import TurnstileWorkflow
12
+ from .workflows.wechat import WechatArticleWorkflow
13
+ from .api.runtime import AsyncGeckoRuntime, WorkerRecyclePolicy
14
+ from .api.context import BrowserContext
15
+ from .api.page import Page
16
+ from .api.pool import AsyncGeckoWorkerPool
17
+ from .data_plane import SharedBlob, SharedBlobDescriptor
18
+ from .diagnostics.metrics import MetricsOptions, WorkflowMetrics
19
+ from .http import (
20
+ AsyncWprHttpClient,
21
+ HttpResponse,
22
+ NetworkTiming,
23
+ WprHttpClient,
24
+ WprHttpError,
25
+ )
26
+
27
+ __all__ = [
28
+ "AsyncGeckoWorkerSession",
29
+ "GeckoWorkerSession",
30
+ "RuntimeConfig",
31
+ "WprProtocolError",
32
+ "install_runtime",
33
+ "TurnstileResult",
34
+ "WechatArticleResult",
35
+ "TurnstileWorkflow",
36
+ "WechatArticleWorkflow",
37
+ "AsyncGeckoRuntime",
38
+ "WorkerRecyclePolicy",
39
+ "BrowserContext",
40
+ "Page",
41
+ "AsyncGeckoWorkerPool",
42
+ "SharedBlob",
43
+ "SharedBlobDescriptor",
44
+ "MetricsOptions",
45
+ "WorkflowMetrics",
46
+ "AsyncWprHttpClient",
47
+ "WprHttpClient",
48
+ "HttpResponse",
49
+ "NetworkTiming",
50
+ "WprHttpError",
51
+ ]
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from .installer import install_runtime
6
+
7
+
8
+ def main() -> int:
9
+ parser = argparse.ArgumentParser(prog="python -m gecko_web_runtime")
10
+ commands = parser.add_subparsers(dest="command", required=True)
11
+ install = commands.add_parser("install-runtime", help="download a pinned Gecko runtime")
12
+ install.add_argument("--version", required=True, help="runtime major version, e.g. 155")
13
+ install.add_argument("--runtime-home", help="optional installation directory")
14
+ install.add_argument("--force", action="store_true", help="replace an existing installation")
15
+ args = parser.parse_args()
16
+ if args.command == "install-runtime":
17
+ location = install_runtime(args.version, args.runtime_home, force=args.force)
18
+ print(location)
19
+ return 0
20
+
21
+
22
+ if __name__ == "__main__":
23
+ raise SystemExit(main())
@@ -0,0 +1,7 @@
1
+ """High-level application-facing runtime API."""
2
+
3
+ from .context import BrowserContext
4
+ from .page import Page
5
+ from .runtime import AsyncGeckoRuntime, WorkerRecyclePolicy
6
+
7
+ __all__ = ["AsyncGeckoRuntime", "BrowserContext", "Page", "WorkerRecyclePolicy"]