v0.0.31 - Advanced JS tests: ES Modules, Web Workers, Shared Workers, WebAssembly, cross-origin fetch; Service Worker analysis plan
This commit is contained in:
@@ -326,6 +326,16 @@ block local-file web apps.
|
||||
- **Form submissions** — GET forms append query strings to the target
|
||||
page; POST forms navigate (body silently dropped, no server)
|
||||
- **`window.open()`** — opens local pages in new tabs
|
||||
- **ES Modules** — dynamic `import()` works with local module files
|
||||
- **Web Workers and Shared Workers** — `new Worker()` and
|
||||
`new SharedWorker()` with local scripts enable background computation
|
||||
threads (including cross-tab shared workers)
|
||||
- **WebAssembly** — `WebAssembly.instantiate()` works for compiled code
|
||||
- **Cross-origin fetch to remote HTTPS** — `fetch('https://example.com')`
|
||||
from a `file://` page works with no CORS blocking (confirms the
|
||||
"deprecated web security" design goal)
|
||||
- **Cross-origin remote resources** — remote `<script src>`,
|
||||
`<link rel=stylesheet>`, and `<img>` from HTTPS CDNs all load correctly
|
||||
|
||||
**Known limitations (WebKit engine quirks, not bugs):**
|
||||
|
||||
@@ -335,8 +345,10 @@ block local-file web apps.
|
||||
(valid media works perfectly)
|
||||
- Cache API (CacheStorage) rejects non-HTTP/HTTPS URLs — use IndexedDB
|
||||
instead for local-file storage
|
||||
- Service Workers don't work on `file://` (W3C spec requires HTTP/HTTPS) —
|
||||
use Web Workers for background processing instead
|
||||
|
||||
**Test site:** A comprehensive multipage test website with 45+ edge cases
|
||||
**Test site:** A comprehensive multipage test website with 53 edge cases
|
||||
is in [`tests/local-site/`](tests/local-site/index.html). See
|
||||
[`tests/local-site/LOCAL-FILE-BROWSING-REPORT.md`](tests/local-site/LOCAL-FILE-BROWSING-REPORT.md)
|
||||
for the full test report.
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# Service Workers on file:// — Analysis & Options
|
||||
|
||||
## The Problem
|
||||
|
||||
`navigator.serviceWorker.register()` on a `file://` page rejects with:
|
||||
> "serviceWorker.register() must be called with a script URL whose protocol is either HTTP or HTTPS"
|
||||
|
||||
## Where the Check Lives
|
||||
|
||||
The error message is generated **inside WebKit's web process** (the
|
||||
JavaScript engine / WebCore layer), not in the WebKitGTK C API layer.
|
||||
The check is in WebKit's `ServiceWorkerContainer::register()` C++ code
|
||||
(in `Source/WebCore/workers/service/ServiceWorkerContainer.cpp`), which
|
||||
validates the script URL protocol against a hard-coded list of
|
||||
`http` and `https`.
|
||||
|
||||
This is **not** controllable via:
|
||||
- `WebKitSettings` (no setting for SW protocol allowlist)
|
||||
- `WebKitSecurityManager` (only controls local/secure/cors/empty/no-access
|
||||
scheme classification — not SW registration eligibility)
|
||||
- `WebKitWebsiteDataManager` (only controls the storage *directory* for SW
|
||||
registrations, not the protocol gate)
|
||||
|
||||
## Options
|
||||
|
||||
### Option A: Patch WebKit (fork / rebuild) — HIGH EFFORT
|
||||
|
||||
We could fork `webkitgtk` (or maintain a patch set) that removes or
|
||||
relaxes the protocol check in `ServiceWorkerContainer.cpp`. This is the
|
||||
only way to make `navigator.serviceWorker.register()` accept `file://`
|
||||
URLs natively.
|
||||
|
||||
**Pros:**
|
||||
- Fully native — JS code works unchanged
|
||||
- SW lifecycle (install/activate/fetch events) works as designed
|
||||
|
||||
**Cons:**
|
||||
- Requires maintaining a WebKit fork or patch set against Debian's
|
||||
`webkit2gtk` package
|
||||
- Every WebKitGTK update requires re-applying the patch
|
||||
- Build complexity — WebKit is ~35MB of system lib; rebuilding from
|
||||
source is heavy
|
||||
- The SW spec assumes HTTP semantics (scope, update checks, fetch
|
||||
interception) that don't map cleanly to `file://`
|
||||
|
||||
**Verdict:** Too heavy for the benefit. Service Workers on `file://` is a
|
||||
niche use case.
|
||||
|
||||
### Option B: Register file:// as a "secure" scheme — ALREADY DONE, DOESN'T HELP
|
||||
|
||||
We already call `webkit_security_manager_register_uri_scheme_as_secure()`
|
||||
for `file` in `src/main.c:850`. This makes `file://` origins treated as
|
||||
"potentially trustworthy" for some purposes (like `navigator.geolocation`
|
||||
or `getUserMedia`), but **does not** bypass the SW registration protocol
|
||||
check — that check is a separate hard-coded `http/https` test in
|
||||
WebCore.
|
||||
|
||||
### Option C: Use a custom URI scheme instead of file:// — MODERATE EFFORT
|
||||
|
||||
Instead of loading local sites via `file:///path/to/index.html`, we could
|
||||
register a custom scheme (e.g. `local://`) via
|
||||
`webkit_web_context_register_uri_scheme()` that serves files from disk.
|
||||
If we also register `local://` as secure via `WebKitSecurityManager`, SW
|
||||
registration *might* work — but only if WebKit's SW code checks
|
||||
"potentially trustworthy origin" rather than hard-coding `http/https`.
|
||||
This needs testing.
|
||||
|
||||
**Pros:**
|
||||
- No WebKit fork needed
|
||||
- We control the scheme handler entirely
|
||||
- Could also solve the Cache API issue (Issue #5)
|
||||
|
||||
**Cons:**
|
||||
- Changes the URL model — users see `local://` instead of `file://`
|
||||
- Relative path resolution may differ
|
||||
- Still might not work if WebKit's SW check is truly hard-coded to
|
||||
`http/https` (need to test)
|
||||
- Significant implementation effort (scheme handler, path mapping,
|
||||
security registration)
|
||||
|
||||
**Verdict:** Worth investigating if SW on local files becomes important.
|
||||
The first step would be a quick test: register `local://` as secure,
|
||||
load a page, try `navigator.serviceWorker.register()`.
|
||||
|
||||
### Option D: JavaScript shim / polyfill — LOW EFFORT, PARTIAL
|
||||
|
||||
We could inject a JS shim that overrides `navigator.serviceWorker.register`
|
||||
to do something useful on `file://` — e.g., load the SW script as a Web
|
||||
Worker instead, or implement a minimal fetch interceptor.
|
||||
|
||||
**Pros:**
|
||||
- No WebKit changes
|
||||
- We already inject JS shims (`window.nostr`)
|
||||
|
||||
**Cons:**
|
||||
- Not a real Service Worker — no `fetch` event interception, no
|
||||
`install`/`activate` lifecycle, no `clients` API
|
||||
- Would only provide a subset of SW functionality
|
||||
- Complex to implement convincingly
|
||||
|
||||
**Verdict:** Only useful if someone specifically needs SW-like behavior
|
||||
on `file://` and is willing to accept a polyfill.
|
||||
|
||||
### Option E: Do nothing — RECOMMENDED
|
||||
|
||||
Service Workers are designed for HTTP/HTTPS origins. On `file://`:
|
||||
- **Web Workers** work perfectly (tested ✅) — use those for background
|
||||
computation
|
||||
- **Shared Workers** also work perfectly (tested ✅) — use those for
|
||||
cross-tab/cross-page shared background computation
|
||||
- **IndexedDB** works perfectly (tested ✅) — use that for offline
|
||||
storage
|
||||
- **Cache API** doesn't work (Issue #5) — but local files are already
|
||||
local, so caching is redundant
|
||||
- **fetch interception** (the main SW use case) is less relevant when
|
||||
everything is local
|
||||
|
||||
The "deprecated web security" design goal is about **cross-origin
|
||||
access** (CORS/SOP), which works. Service Workers are a different
|
||||
concern — they're about **offline web apps and PWA features**, which
|
||||
are inherently HTTP-oriented concepts.
|
||||
|
||||
**Verdict:** Document the limitation and move on. If a specific use case
|
||||
emerges that needs SW on `file://`, revisit Option C (custom scheme).
|
||||
+2
-2
@@ -11,9 +11,9 @@
|
||||
#ifndef SOVEREIGN_BROWSER_VERSION_H
|
||||
#define SOVEREIGN_BROWSER_VERSION_H
|
||||
|
||||
#define SB_VERSION "v0.0.30"
|
||||
#define SB_VERSION "v0.0.31"
|
||||
#define SB_VERSION_MAJOR 0
|
||||
#define SB_VERSION_MINOR 0
|
||||
#define SB_VERSION_PATCH 30
|
||||
#define SB_VERSION_PATCH 31
|
||||
|
||||
#endif /* SOVEREIGN_BROWSER_VERSION_H */
|
||||
|
||||
@@ -75,6 +75,15 @@ The issues below are polish/UX improvements.
|
||||
| 43 | `<object>` / `<embed>` with SVG | [`media.html`](media.html) | ✅ PASS | `data`/`src` resolved to file:// URL |
|
||||
| 44 | Inline SVG | [`media.html`](media.html) | ✅ PASS | |
|
||||
| 45 | Clicking a link to navigate | [`index.html`](index.html) → [`about.html`](about.html) | ✅ PASS | `click` MCP tool triggered navigation |
|
||||
| 46 | ES Modules (`import()`) | [`advanced.html`](advanced.html) | ✅ PASS | Dynamic `import()` works; exports accessible (greet, add, PI) |
|
||||
| 47 | Web Workers | [`advanced.html`](advanced.html) | ✅ PASS | `new Worker()` created; `postMessage` round-trip works (7*6=42) |
|
||||
| 47b | Shared Workers | [`advanced.html`](advanced.html) | ✅ PASS | `new SharedWorker()` created; `port.onmessage` works; ping/pong round-trip works |
|
||||
| 48 | Service Workers | [`advanced.html`](advanced.html) | ❌ FAIL | `register()` rejects: "must be called with HTTP or HTTPS". See Issue #6. |
|
||||
| 49 | WebAssembly | [`advanced.html`](advanced.html) | ✅ PASS | `WebAssembly.instantiate()` works; `add(3,4)=7`, `add(100,200)=300` |
|
||||
| 50 | Cross-origin fetch (file:// → https://) | [`advanced.html`](advanced.html) | ✅ PASS | `fetch('https://example.com')` returns status=200, type=basic, 559 chars. **No CORS blocking!** |
|
||||
| 51 | Cross-origin remote image | [`advanced.html`](advanced.html) | ✅ PASS | Remote images from placehold.co and google.com load correctly |
|
||||
| 52 | Cross-origin remote script | [`advanced.html`](advanced.html) | ✅ PASS | Remote `<script src>` from jsdelivr CDN loaded and executed |
|
||||
| 53 | Cross-origin remote stylesheet | [`advanced.html`](advanced.html) | ✅ PASS | Remote `<link rel=stylesheet>` from jsdelivr CDN loaded and applied |
|
||||
|
||||
---
|
||||
|
||||
@@ -206,6 +215,31 @@ recommended alternative and works fully.
|
||||
|
||||
---
|
||||
|
||||
### Issue #6 — Service Workers don't work on file:// (MINOR / WebKit restriction)
|
||||
|
||||
**What happens:** `navigator.serviceWorker.register()` rejects with the
|
||||
error: "serviceWorker.register() must be called with a script URL whose
|
||||
protocol is either HTTP or HTTPS".
|
||||
|
||||
**Why it matters:** Service Workers cannot be used on `file://` pages.
|
||||
This means PWA features like offline caching via service workers,
|
||||
background sync, and push notifications won't work for local-file apps.
|
||||
|
||||
**Likely cause:** This is a W3C specification restriction — Service
|
||||
Workers are only allowed on HTTP/HTTPS origins. WebKit enforces this at
|
||||
the `register()` level. This is consistent with all major browsers.
|
||||
|
||||
**Suggested fix:** None required — this is a fundamental specification
|
||||
restriction. Web Workers (regular, non-service workers) DO work on
|
||||
`file://` and can be used for background computation. For offline
|
||||
storage, use IndexedDB.
|
||||
|
||||
**Severity:** Low. Service Workers are a niche use case for local-file
|
||||
apps. Web Workers are the alternative for background processing and work
|
||||
fully.
|
||||
|
||||
---
|
||||
|
||||
## What Works Surprisingly Well
|
||||
|
||||
These are the things that could have been broken but weren't:
|
||||
@@ -248,6 +282,26 @@ These are the things that could have been broken but weren't:
|
||||
The `<audio>` element similarly works with correct duration. Media
|
||||
playback is fully functional for local files.
|
||||
|
||||
10. **ES Modules** — dynamic `import()` works on `file://` pages, allowing
|
||||
modern modular JavaScript with local module files.
|
||||
|
||||
11. **Web Workers and Shared Workers** — `new Worker()` and
|
||||
`new SharedWorker()` with local script files both work, enabling
|
||||
background computation threads and cross-tab shared workers from
|
||||
`file://` pages.
|
||||
|
||||
12. **WebAssembly** — `WebAssembly.instantiate()` works, enabling
|
||||
high-performance compiled code on `file://` pages.
|
||||
|
||||
13. **Cross-origin fetch to remote HTTPS URLs** — `fetch('https://example.com')`
|
||||
from a `file://` page returns `status=200, type=basic` with no CORS
|
||||
blocking. This confirms the "deprecated web security" design goal is
|
||||
working — local web apps can freely call remote APIs.
|
||||
|
||||
14. **Cross-origin remote resources** — remote `<script src>`, `<link
|
||||
rel=stylesheet>`, and `<img>` from HTTPS CDNs all load and execute
|
||||
correctly from `file://` pages.
|
||||
|
||||
---
|
||||
|
||||
## Recommended Actions
|
||||
@@ -327,6 +381,7 @@ tests/local-site/
|
||||
├── query.html # Query string handling
|
||||
├── encoded name.html # URL-encoded filename (literal space)
|
||||
├── 404.html # Placeholder (not actually missing)
|
||||
├── advanced.html # Advanced JS test (ES Modules, Workers, WASM, cross-origin fetch)
|
||||
├── assets/
|
||||
│ ├── site.css # Shared stylesheet
|
||||
│ ├── favicon.svg # SVG favicon
|
||||
@@ -335,6 +390,11 @@ tests/local-site/
|
||||
│ ├── data.json # JSON for fetch/XHR tests
|
||||
│ ├── home.js # External script for index.html
|
||||
│ ├── external.js # External script for scripts/external.html
|
||||
│ ├── advanced-runner.js # Advanced JS test runner
|
||||
│ ├── es-module-test.js # ES Module (import/export) test
|
||||
│ ├── worker-test.js # Web Worker script
|
||||
│ ├── shared-worker-test.js # Shared Worker script
|
||||
│ ├── sw-test.js # Service Worker script (expected to fail on file://)
|
||||
│ ├── sample.mp4 # Test video (Big Buck Bunny, gitignored)
|
||||
│ └── Neon Dream.m4a # Test audio (gitignored)
|
||||
├── scripts/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Local Site — Advanced JS Test</title>
|
||||
<link rel="stylesheet" href="assets/site.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Advanced JS Test Page</h1>
|
||||
<nav><a href="index.html">Home</a></nav>
|
||||
</header>
|
||||
<main>
|
||||
<h2>Results</h2>
|
||||
<pre id="results">Running tests...</pre>
|
||||
</main>
|
||||
<script src="assets/advanced-runner.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,247 @@
|
||||
// Advanced JS test runner — writes results to #results as tests complete.
|
||||
(function () {
|
||||
var log = [];
|
||||
function flush() {
|
||||
var el = document.getElementById('results');
|
||||
if (el) el.textContent = log.join('\n');
|
||||
}
|
||||
function add(msg) {
|
||||
log.push(msg);
|
||||
flush();
|
||||
}
|
||||
|
||||
add('=== Advanced JS Test Start ===');
|
||||
add('URL: ' + location.href);
|
||||
add('');
|
||||
|
||||
// ── 1. ES Modules ──────────────────────────────────────────────
|
||||
add('--- ES Modules ---');
|
||||
add('Testing <script type="module"> with local import...');
|
||||
|
||||
// We'll use a dynamic import() to test module loading
|
||||
try {
|
||||
import('./es-module-test.js')
|
||||
.then(function (mod) {
|
||||
add('import(): ok');
|
||||
add(' mod.greet("World") = ' + mod.greet('World'));
|
||||
add(' mod.add(2, 3) = ' + mod.add(2, 3));
|
||||
add(' mod.PI = ' + mod.PI);
|
||||
add('ES MODULES: PASS');
|
||||
testWebWorker();
|
||||
})
|
||||
.catch(function (e) {
|
||||
add('import(): ERROR - ' + e.message);
|
||||
add('ES MODULES: FAIL');
|
||||
testWebWorker();
|
||||
});
|
||||
} catch (e) {
|
||||
add('import() not supported: ' + e.message);
|
||||
add('ES MODULES: FAIL (import() not supported)');
|
||||
testWebWorker();
|
||||
}
|
||||
|
||||
// ── 2. Web Workers ─────────────────────────────────────────────
|
||||
function testWebWorker() {
|
||||
add('');
|
||||
add('--- Web Workers ---');
|
||||
add('Testing new Worker() with local script...');
|
||||
|
||||
try {
|
||||
var worker = new Worker('assets/worker-test.js');
|
||||
add('new Worker(): ok (created)');
|
||||
|
||||
worker.onmessage = function (e) {
|
||||
add('worker.onmessage: ' + e.data);
|
||||
add('WEB WORKERS: PASS');
|
||||
worker.terminate();
|
||||
testServiceWorker();
|
||||
};
|
||||
|
||||
worker.onerror = function (e) {
|
||||
add('worker.onerror: ' + (e.message || 'error'));
|
||||
add('WEB WORKERS: FAIL');
|
||||
testServiceWorker();
|
||||
};
|
||||
|
||||
// Send a message to the worker
|
||||
worker.postMessage({ cmd: 'compute', a: 7, b: 6 });
|
||||
add('postMessage sent: {cmd: "compute", a: 7, b: 6}');
|
||||
} catch (e) {
|
||||
add('new Worker(): ERROR - ' + e.message);
|
||||
add('WEB WORKERS: FAIL');
|
||||
testServiceWorker();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Service Workers ─────────────────────────────────────────
|
||||
function testServiceWorker() {
|
||||
add('');
|
||||
add('--- Service Workers ---');
|
||||
add('available: ' + ('serviceWorker' in navigator));
|
||||
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
add('SERVICE WORKERS: not available');
|
||||
testWebAssembly();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
navigator.serviceWorker.register('assets/sw-test.js')
|
||||
.then(function (reg) {
|
||||
add('register(): ok (scope=' + reg.scope + ')');
|
||||
add('SERVICE WORKERS: PASS (registered)');
|
||||
testWebAssembly();
|
||||
})
|
||||
.catch(function (e) {
|
||||
add('register(): ERROR - ' + (e.message || e));
|
||||
add('SERVICE WORKERS: FAIL');
|
||||
testWebAssembly();
|
||||
});
|
||||
} catch (e) {
|
||||
add('register(): EXCEPTION - ' + e.message);
|
||||
add('SERVICE WORKERS: FAIL');
|
||||
testWebAssembly();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 4. WebAssembly ─────────────────────────────────────────────
|
||||
function testWebAssembly() {
|
||||
add('');
|
||||
add('--- WebAssembly ---');
|
||||
add('available: ' + (typeof WebAssembly !== 'undefined'));
|
||||
|
||||
if (typeof WebAssembly === 'undefined') {
|
||||
add('WEBASSEMBLY: not available');
|
||||
testCrossOriginFetch();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Minimal Wasm module: exports an "add" function
|
||||
// (wasm binary: (module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add)))
|
||||
var bytes = new Uint8Array([
|
||||
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
||||
0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,
|
||||
0x03, 0x02, 0x01, 0x00,
|
||||
0x07, 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00,
|
||||
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b
|
||||
]);
|
||||
|
||||
WebAssembly.instantiate(bytes).then(function (result) {
|
||||
var addFn = result.instance.exports.add;
|
||||
add('instantiate(): ok');
|
||||
add(' add(3, 4) = ' + addFn(3, 4));
|
||||
add(' add(100, 200) = ' + addFn(100, 200));
|
||||
add('WEBASSEMBLY: PASS');
|
||||
testCrossOriginFetch();
|
||||
}).catch(function (e) {
|
||||
add('instantiate(): ERROR - ' + e.message);
|
||||
add('WEBASSEMBLY: FAIL');
|
||||
testCrossOriginFetch();
|
||||
});
|
||||
} catch (e) {
|
||||
add('instantiate(): EXCEPTION - ' + e.message);
|
||||
add('WEBASSEMBLY: FAIL');
|
||||
testCrossOriginFetch();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 5. Cross-origin fetch to remote HTTP/HTTPS ─────────────────
|
||||
function testCrossOriginFetch() {
|
||||
add('');
|
||||
add('--- Cross-origin fetch (file:// → https://) ---');
|
||||
|
||||
// Test 1: fetch a remote page
|
||||
add('Test 1: fetch("https://example.com")...');
|
||||
try {
|
||||
fetch('https://example.com')
|
||||
.then(function (r) {
|
||||
add(' status=' + r.status + ' ok=' + r.ok + ' type=' + r.type);
|
||||
return r.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
add(' body length=' + text.length + ' chars');
|
||||
add(' body starts with: ' + text.substring(0, 80).replace(/\n/g, ' '));
|
||||
add('CROSS-ORIGIN FETCH (page): PASS');
|
||||
testRemoteImage();
|
||||
})
|
||||
.catch(function (e) {
|
||||
add(' ERROR - ' + e.message);
|
||||
add('CROSS-ORIGIN FETCH (page): FAIL');
|
||||
testRemoteImage();
|
||||
});
|
||||
} catch (e) {
|
||||
add(' EXCEPTION - ' + e.message);
|
||||
add('CROSS-ORIGIN FETCH (page): FAIL');
|
||||
testRemoteImage();
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: load a remote image
|
||||
function testRemoteImage() {
|
||||
add('');
|
||||
add('Test 2: remote image from https://...');
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
add(' loaded: ' + img.naturalWidth + 'x' + img.naturalHeight);
|
||||
add('CROSS-ORIGIN IMAGE: PASS');
|
||||
testRemoteScript();
|
||||
};
|
||||
img.onerror = function () {
|
||||
add(' ERROR: image failed to load');
|
||||
add('CROSS-ORIGIN IMAGE: FAIL');
|
||||
testRemoteScript();
|
||||
};
|
||||
img.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/Banana-Single.jpg/240px-Banana-Single.jpg';
|
||||
add(' img.src set to remote URL...');
|
||||
}
|
||||
|
||||
// Test 3: load a remote script
|
||||
function testRemoteScript() {
|
||||
add('');
|
||||
add('Test 3: remote <script src> from https://...');
|
||||
var s = document.createElement('script');
|
||||
s.src = 'https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js';
|
||||
s.onload = function () {
|
||||
add(' loaded: script executed');
|
||||
add(' typeof confetti = ' + (typeof confetti));
|
||||
add('CROSS-ORIGIN SCRIPT: PASS');
|
||||
testRemoteStylesheet();
|
||||
};
|
||||
s.onerror = function () {
|
||||
add(' ERROR: script failed to load');
|
||||
add('CROSS-ORIGIN SCRIPT: FAIL');
|
||||
testRemoteStylesheet();
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
add(' script.src set to remote URL...');
|
||||
}
|
||||
|
||||
// Test 4: load a remote stylesheet
|
||||
function testRemoteStylesheet() {
|
||||
add('');
|
||||
add('Test 4: remote <link rel=stylesheet> from https://...');
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://cdn.jsdelivr.net/npm/water.css@2/out/light.css';
|
||||
link.onload = function () {
|
||||
add(' loaded: stylesheet applied');
|
||||
add(' stylesheet count = ' + document.styleSheets.length);
|
||||
add('CROSS-ORIGIN STYLESHEET: PASS');
|
||||
finishAll();
|
||||
};
|
||||
link.onerror = function () {
|
||||
add(' ERROR: stylesheet failed to load');
|
||||
add('CROSS-ORIGIN STYLESHEET: FAIL');
|
||||
finishAll();
|
||||
};
|
||||
document.head.appendChild(link);
|
||||
add(' link.href set to remote URL...');
|
||||
}
|
||||
|
||||
function finishAll() {
|
||||
add('');
|
||||
add('=== Advanced JS Test Complete ===');
|
||||
flush();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,16 @@
|
||||
// ES Module test — imported by advanced-runner.js via dynamic import()
|
||||
export const PI = 3.14159;
|
||||
|
||||
export function greet(name) {
|
||||
return 'Hello, ' + name + '!';
|
||||
}
|
||||
|
||||
export function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export default {
|
||||
PI: PI,
|
||||
greet: greet,
|
||||
add: add
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
// Shared Worker test — can be shared across multiple tabs/pages
|
||||
var connections = 0;
|
||||
|
||||
self.onconnect = function (e) {
|
||||
var port = e.ports[0];
|
||||
connections++;
|
||||
port.postMessage('SharedWorker connected! Total connections: ' + connections);
|
||||
port.onmessage = function (ev) {
|
||||
var msg = ev.data;
|
||||
if (msg.cmd === 'ping') {
|
||||
port.postMessage('pong from SharedWorker (connections=' + connections + ')');
|
||||
} else if (msg.cmd === 'count') {
|
||||
port.postMessage('SharedWorker has ' + connections + ' active connection(s)');
|
||||
} else {
|
||||
port.postMessage('SharedWorker received: ' + JSON.stringify(msg));
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// Service Worker test — minimal SW that just responds to install/activate
|
||||
self.addEventListener('install', function (event) {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', function (event) {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function (event) {
|
||||
// Pass-through — don't intercept anything
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
// Web Worker test — receives a message, computes, posts result back
|
||||
self.onmessage = function (e) {
|
||||
var msg = e.data;
|
||||
if (msg.cmd === 'compute') {
|
||||
var result = msg.a * msg.b;
|
||||
self.postMessage('Worker computed ' + msg.a + ' * ' + msg.b + ' = ' + result);
|
||||
} else {
|
||||
self.postMessage('Worker received unknown command: ' + msg.cmd);
|
||||
}
|
||||
};
|
||||
@@ -62,6 +62,8 @@
|
||||
<li><a href="query.html?foo=bar&baz=qux">Query-string page</a></li>
|
||||
<li><a href="hash.html#section-2">Hash-fragment page</a></li>
|
||||
<li><a href="scripts/external.html">External script page</a></li>
|
||||
<li><a href="advanced.html">Advanced JS page (ES Modules, Workers, WASM, cross-origin fetch)</a></li>
|
||||
<li><a href="storage-test.html">Automated storage test (all types)</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Inline image test</h2>
|
||||
|
||||
Reference in New Issue
Block a user