|
5 | 5 | if (root[marker]) return; |
6 | 6 | Object.defineProperty(root, marker, { value: true, enumerable: false, configurable: false }); |
7 | 7 |
|
8 | | - const boot = Object.assign({ tabId: '', entryId: '', targetUrl: location.href, documentCookie: '' }, root.__ZP_BOOT || {}); |
| 8 | + const boot = Object.assign({ tabId: '', entryId: '', targetUrl: location.href, documentCookie: '' }, readBootConfig()); |
9 | 9 | const runtimeToken = String(boot.runtimeToken || ''); |
10 | 10 | const TARGET_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'; |
11 | 11 | const TARGET_APP_VERSION = TARGET_USER_AGENT.replace(/^Mozilla\//, ''); |
12 | 12 | const TARGET_PLATFORM = 'Win32'; |
13 | | - try { delete root.__ZP_BOOT; } catch { try { Object.defineProperty(root, '__ZP_BOOT', { value: undefined, enumerable: false }); } catch {} } |
| 13 | + clearBootConfig(); |
14 | 14 | const Native = captureNative(root); |
15 | 15 | const toStringMap = new WeakMap(); |
16 | 16 | const toStringMaskedPrototypes = new WeakSet(); |
|
31 | 31 | const canvasHookedWindows = new WeakSet(); |
32 | 32 | const audioHookedWindows = new WeakSet(); |
33 | 33 |
|
| 34 | + |
| 35 | + function readBootConfig() { |
| 36 | + const d = root.document; |
| 37 | + const el = d && d.getElementById && d.getElementById('__zp-boot'); |
| 38 | + if (el) { |
| 39 | + try { |
| 40 | + const parsed = JSON.parse(el.textContent || '{}'); |
| 41 | + if (parsed && typeof parsed === 'object') return parsed; |
| 42 | + } catch {} |
| 43 | + } |
| 44 | + return root.__ZP_BOOT || {}; |
| 45 | + } |
| 46 | + |
| 47 | + function clearBootConfig() { |
| 48 | + const d = root.document; |
| 49 | + const el = d && d.getElementById && d.getElementById('__zp-boot'); |
| 50 | + if (el) { |
| 51 | + try { el.remove(); } catch {} |
| 52 | + } |
| 53 | + try { delete root.__ZP_BOOT; } catch { try { Object.defineProperty(root, '__ZP_BOOT', { value: undefined, enumerable: false }); } catch {} } |
| 54 | + } |
34 | 55 | function captureNative(w) { |
35 | 56 | const d = w.document; |
36 | 57 | return { |
|
117 | 138 | define(proto, 'dispatchEvent', function(event) { const list = this[listenersKey] && this[listenersKey].get(event.type) || []; try { if (!event.target) Object.defineProperty(event, 'target', { value: this, configurable: true }); } catch {} const handler = this['on' + event.type]; if (typeof handler === 'function') handler.call(this, event); for (const fn of list.slice()) fn.call(this, event); return !event.defaultPrevented; }); |
118 | 139 | } |
119 | 140 | function isHTTPURL(raw) { try { const u = new URL(String(raw), baseURL); return u.protocol === 'http:' || u.protocol === 'https:'; } catch { return false; } } |
| 141 | + function hasExecutableURLScheme(raw) { return /^(?:javascript|data|vbscript):/i.test(String(raw).trim()); } |
| 142 | + function blockedURLValue(el, key) { const tag = el && el.localName; return key === 'src' && (tag === 'iframe' || tag === 'frame') ? 'about:blank' : '#'; } |
| 143 | + function blockExecutableURL(el, key, raw) { urlMeta.delete(el); Native.setAttribute.call(el, 'data-zp-target-url', ''); Native.setAttribute.call(el, 'data-zp-blocked-url', String(raw).trim()); Native.setAttribute.call(el, key, blockedURLValue(el, key)); if (key === 'src' && (el.localName === 'iframe' || el.localName === 'frame')) instrumentIframe(el); } |
120 | 144 | function targetURL(raw, base = baseURL) { return ZP.canonicalTargetURL(String(raw), base).href; } |
121 | 145 | function targetWSURL(raw, base = baseURL) { return ZP.canonicalWebSocketURL(String(raw), base.replace(/^http/, 'ws')).href; } |
122 | 146 | function shareNavURL(raw, base = baseURL) { return ZP.makeShareURL(targetURL(raw, base), proxyOrigin); } |
|
206 | 230 | function installBeacon() { if (!navigator.sendBeacon || !Native.navigatorSendBeacon) return; define(navigator, 'sendBeacon', (url, data) => Native.navigatorSendBeacon(targetURL(url), data)); } |
207 | 231 |
|
208 | 232 | function installNavigationTraps() { |
209 | | - document.addEventListener('click', ev => { const nav = clickNavigationTarget(ev); if (!nav) return; ev.preventDefault(); ev.stopImmediatePropagation(); navigateToTarget(nav.href); }, true); |
| 233 | + document.addEventListener('click', ev => { const nav = clickNavigationTarget(ev); if (!nav) return; ev.preventDefault(); ev.stopImmediatePropagation(); if (nav.href) navigateToTarget(nav.href); }, true); |
210 | 234 | document.addEventListener('submit', ev => { const f = ev.target; if (!f) return; ev.preventDefault(); submitForm(f, ev.submitter); }, true); |
211 | 235 | if (Native.formSubmit) define(HTMLFormElement.prototype, 'submit', function() { submitForm(this); }); |
212 | 236 | if (Native.formRequestSubmit) define(HTMLFormElement.prototype, 'requestSubmit', function(submitter) { submitForm(this, submitter); }); |
|
249 | 273 | if (target && target !== '_self') return null; |
250 | 274 | } |
251 | 275 | const raw = isAnchor ? el.getAttribute('data-zp-target-url') || el.getAttribute('href') : typeof el.href === 'string' ? el.href : ''; |
252 | | - if (!raw || raw[0] === '#' || /^javascript:/i.test(raw)) continue; |
| 276 | + if (!raw || raw[0] === '#') continue; |
| 277 | + if (hasExecutableURLScheme(raw)) return { href: '', element: el }; |
253 | 278 | if (isHTTPURL(raw)) return { href: raw, element: el }; |
254 | 279 | } |
255 | 280 | return null; |
|
376 | 401 | updateVirtualBase(v); |
377 | 402 | return Native.setAttribute.call(this, k, v); |
378 | 403 | } |
379 | | - if (isURLBearing(this, key) && isHTTPURL(v)) { |
380 | | - const t = targetURL(v); |
381 | | - urlMeta.set(this, t); |
382 | | - Native.setAttribute.call(this, 'data-zp-target-url', t); |
383 | | - if ((this.localName === 'iframe' || this.localName === 'frame') && key === 'src') { |
384 | | - Native.setAttribute.call(this, k, 'about:blank'); |
385 | | - shareNavURL(t).then(u => Native.setAttribute.call(this, k, u)).catch(()=>{}); |
386 | | - return; |
| 404 | + if (isURLBearing(this, key)) { |
| 405 | + if (hasExecutableURLScheme(v)) return blockExecutableURL(this, key, v); |
| 406 | + if (isHTTPURL(v)) { |
| 407 | + const t = targetURL(v); |
| 408 | + urlMeta.set(this, t); |
| 409 | + Native.setAttribute.call(this, 'data-zp-target-url', t); |
| 410 | + if ((this.localName === 'iframe' || this.localName === 'frame') && key === 'src') { |
| 411 | + Native.setAttribute.call(this, k, 'about:blank'); |
| 412 | + shareNavURL(t).then(u => Native.setAttribute.call(this, k, u)).catch(()=>{}); |
| 413 | + return; |
| 414 | + } |
| 415 | + return Native.setAttribute.call(this, k, t); |
387 | 416 | } |
388 | | - return Native.setAttribute.call(this, k, t); |
389 | 417 | } |
390 | 418 | if ((this.localName === 'iframe' || this.localName === 'frame') && key === 'srcdoc') return Native.setAttribute.call(this, k, injectSrcdoc(String(v))); |
391 | 419 | return Native.setAttribute.call(this, k, v); |
|
398 | 426 | } |
399 | 427 | function isURLBearing(el, key) { const tag = el.localName; return key === 'href' && (tag === 'a' || tag === 'area') || key === 'action' && tag === 'form' || key === 'formaction' && (tag === 'input' || tag === 'button') || key === 'src' && (tag === 'iframe' || tag === 'frame'); } |
400 | 428 | function transformHTML(s) { return s.replace(/<base\b[^>]*\shref=(["'])([\s\S]*?)\1[^>]*>/ig, (_, q, href) => baseSyncScript(href)).replace(/(<iframe\b[^>]*\ssrcdoc=["'])([\s\S]*?)(["'])/ig, (_, p, h, q) => p + injectSrcdoc(h).replace(/"/g,'"') + q); } |
401 | | - function injectSrcdoc(s) { return '<script src="/__zp/zp-core.js"><\/script><script>Object.defineProperty(window,"__ZP_BOOT",{value:' + JSON.stringify(boot).replace(/</g,'\\u003c') + ',configurable:true});try{document.currentScript.remove()}catch{}<\/script><script src="/__zp/runtime-prelude.js"><\/script>' + s; } |
| 429 | + function injectSrcdoc(s) { return '<script src="/__zp/zp-core.js"><\/script><script id="__zp-boot" type="application/json">' + bootJSON() + '<\/script><script src="/__zp/runtime-prelude.js"><\/script>' + s; } |
| 430 | + function bootJSON() { return JSON.stringify(boot).replace(/[<>&]/g, c => c === '<' ? '\\u003c' : c === '>' ? '\\u003e' : '\\u0026'); } |
402 | 431 | function baseSyncScript(raw) { return '<script>window.__ZP_SET_BASE&&window.__ZP_SET_BASE(' + JSON.stringify(String(raw)).replace(/</g,'\\u003c') + ');<\/script>'; } |
403 | 432 | function syncBaseElement(node) { |
404 | 433 | if (!node) return; |
|
430 | 459 | } |
431 | 460 | if (!isURLBearing(el, key)) return; |
432 | 461 | const raw = Native.getAttribute.call(el, key); |
| 462 | + if (hasExecutableURLScheme(raw)) { blockExecutableURL(el, key, raw); return; } |
433 | 463 | if (!raw || !isHTTPURL(raw) || String(raw).startsWith(proxyOrigin)) return; |
434 | 464 | let target; |
435 | 465 | try { target = targetURL(raw); } catch { return; } |
|
0 commit comments