From f89275ea4b628e60e458667465b55e7622dd2938 Mon Sep 17 00:00:00 2001 From: lanterngamescn Date: Thu, 25 Jun 2026 07:41:11 +0800 Subject: [PATCH] =?UTF-8?q?M0:=20=E5=BC=95=E5=85=A5=E6=A1=A5=20JS=EF=BC=88?= =?UTF-8?q?T-M0-06=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WebViewJavascriptBridge.js 原样复刻到 feature_bridge/src/main/resources/rawfile (与原 Android assets 去尾随空白后逐字一致,lzyzsd 协议 100% 复刻) - BridgeJsLoader:从 rawfile 读取桥 JS(getRawFileContentSync + TextDecoder)并缓存, 供 M1 BridgeController 在 onPageEnd 注入;HarmonyOS 走 runJavaScript 无需去注释 - 经 build smoke 验证 rawfile 读取 API 签名与编译通过 Co-Authored-By: Claude Opus 4.8 (1M context) --- feature_bridge/Index.ets | 7 +- .../src/main/ets/assets/BridgeJsLoader.ets | 32 ++++ .../rawfile/WebViewJavascriptBridge.js | 145 ++++++++++++++++++ 3 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 feature_bridge/src/main/ets/assets/BridgeJsLoader.ets create mode 100644 feature_bridge/src/main/resources/rawfile/WebViewJavascriptBridge.js diff --git a/feature_bridge/Index.ets b/feature_bridge/Index.ets index f085dc4..45e403c 100644 --- a/feature_bridge/Index.ets +++ b/feature_bridge/Index.ets @@ -1,3 +1,4 @@ -// feature_bridge HAR —— 桥引擎层(导出入口 SSOT) -// 各子模块实现完成后在此统一 export。占位常量确保 HAR 可编译。 -export const feature_bridge_MODULE_VERSION: string = '1.0.0'; +// feature_bridge HAR —— 桥引擎层(框架 §5,100% 复刻 lzyzsd/JsBridge 协议) +// M0:桥 JS 读取器。M1 将补 BridgeController/MessageCodec/HandlerRegistry/IWebController。 + +export { BridgeJsLoader, BRIDGE_JS_RAWFILE } from './src/main/ets/assets/BridgeJsLoader'; diff --git a/feature_bridge/src/main/ets/assets/BridgeJsLoader.ets b/feature_bridge/src/main/ets/assets/BridgeJsLoader.ets new file mode 100644 index 0000000..c2227bc --- /dev/null +++ b/feature_bridge/src/main/ets/assets/BridgeJsLoader.ets @@ -0,0 +1,32 @@ +/** + * 桥 JS 读取器(《契约规范》§2.6,框架 §5.1)。 + * + * 从 rawfile 读取 lzyzsd 原版 `WebViewJavascriptBridge.js`(与 Android 端逐字一致), + * 供 BridgeController 在 `Web().onPageEnd` 时 `runJavaScript` 注入,使 + * `window.WebViewJavascriptBridge` 与 `WebViewJavascriptBridgeReady` 事件就绪。 + * + * 注:原 Android 在注入前用正则去除以 `//` 开头的注释行(因 loadUrl("javascript:") + * 不支持注释);HarmonyOS 走 `runJavaScript`,对 `//` 注释无此限制,可直接注入原文。 + * 首次读取后缓存,避免重复 IO。 + */ +import { common } from '@kit.AbilityKit'; +import { util } from '@kit.ArkTS'; + +/** rawfile 中的桥 JS 文件名。 */ +export const BRIDGE_JS_RAWFILE: string = 'WebViewJavascriptBridge.js'; + +export class BridgeJsLoader { + private static cache: string | undefined = undefined; + + /** 读取并缓存桥 JS 文本。context 取自容器(UIAbilityContext 等)。 */ + static load(context: common.Context): string { + const cached: string | undefined = BridgeJsLoader.cache; + if (cached !== undefined) { + return cached; + } + const bytes: Uint8Array = context.resourceManager.getRawFileContentSync(BRIDGE_JS_RAWFILE); + const text: string = util.TextDecoder.create('utf-8').decodeToString(bytes); + BridgeJsLoader.cache = text; + return text; + } +} diff --git a/feature_bridge/src/main/resources/rawfile/WebViewJavascriptBridge.js b/feature_bridge/src/main/resources/rawfile/WebViewJavascriptBridge.js new file mode 100644 index 0000000..7e5ffb4 --- /dev/null +++ b/feature_bridge/src/main/resources/rawfile/WebViewJavascriptBridge.js @@ -0,0 +1,145 @@ +//notation: js file can only use this kind of comments +//since comments will cause error when use in webview.loadurl, +//comments will be remove by java use regexp +(function() { + if (window.WebViewJavascriptBridge) { + return; + } + + var messagingIframe; + var sendMessageQueue = []; + var receiveMessageQueue = []; + var messageHandlers = {}; + + var CUSTOM_PROTOCOL_SCHEME = 'yy'; + var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'; + + var responseCallbacks = {}; + var uniqueId = 1; + + // 创建队列iframe + function _createQueueReadyIframe(doc) { + messagingIframe = doc.createElement('iframe'); + messagingIframe.style.display = 'none'; + doc.documentElement.appendChild(messagingIframe); + } + + //set default messageHandler 初始化默认的消息线程 + function init(messageHandler) { + if (WebViewJavascriptBridge._messageHandler) { + throw new Error('WebViewJavascriptBridge.init called twice'); + } + WebViewJavascriptBridge._messageHandler = messageHandler; + var receivedMessages = receiveMessageQueue; + receiveMessageQueue = null; + for (var i = 0; i < receivedMessages.length; i++) { + _dispatchMessageFromNative(receivedMessages[i]); + } + } + + // 发送 + function send(data, responseCallback) { + _doSend({ + data: data + }, responseCallback); + } + + // 注册线程 往数组里面添加值 + function registerHandler(handlerName, handler) { + messageHandlers[handlerName] = handler; + } + // 调用线程 + function callHandler(handlerName, data, responseCallback) { + _doSend({ + handlerName: handlerName, + data: data + }, responseCallback); + } + + //sendMessage add message, 触发native处理 sendMessage + function _doSend(message, responseCallback) { + if (responseCallback) { + var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); + responseCallbacks[callbackId] = responseCallback; + message.callbackId = callbackId; + } + + sendMessageQueue.push(message); + messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; + } + + // 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容 + function _fetchQueue() { + var messageQueueString = JSON.stringify(sendMessageQueue); + sendMessageQueue = []; + //android can't read directly the return data, so we can reload iframe src to communicate with java + messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); + } + + //提供给native使用, + function _dispatchMessageFromNative(messageJSON) { + setTimeout(function() { + var message = JSON.parse(messageJSON); + var responseCallback; + //java call finished, now need to call js callback function + if (message.responseId) { + responseCallback = responseCallbacks[message.responseId]; + if (!responseCallback) { + return; + } + responseCallback(message.responseData); + delete responseCallbacks[message.responseId]; + } else { + //直接发送 + if (message.callbackId) { + var callbackResponseId = message.callbackId; + responseCallback = function(responseData) { + _doSend({ + responseId: callbackResponseId, + responseData: responseData + }); + }; + } + + var handler = WebViewJavascriptBridge._messageHandler; + if (message.handlerName) { + handler = messageHandlers[message.handlerName]; + } + //查找指定handler + try { + handler(message.data, responseCallback); + } catch (exception) { + if (typeof console != 'undefined') { + console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception); + } + } + } + }); + } + + //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以 + function _handleMessageFromNative(messageJSON) { + console.log(messageJSON); + if (receiveMessageQueue) { + receiveMessageQueue.push(messageJSON); + } + _dispatchMessageFromNative(messageJSON); + + } + + var WebViewJavascriptBridge = window.WebViewJavascriptBridge = { + init: init, + send: send, + registerHandler: registerHandler, + callHandler: callHandler, + _fetchQueue: _fetchQueue, + _handleMessageFromNative: _handleMessageFromNative + }; + + var doc = document; + _createQueueReadyIframe(doc); + var readyEvent = doc.createEvent('Events'); + readyEvent.initEvent('WebViewJavascriptBridgeReady'); + readyEvent.bridge = WebViewJavascriptBridge; + doc.dispatchEvent(readyEvent); +})();