修复定位的回调问题
This commit is contained in:
@@ -4,8 +4,26 @@
|
||||
* H5 端桥协议代码,与 Native BridgeBus(@MainActor BridgeProtocol 实现)
|
||||
* 一一对应。详见 docs/H5-Native-Implementation-Design.md §3.2 / §3.3。
|
||||
*
|
||||
* 在 BridgedWebView 构造时通过 WKUserScript(.atDocumentStart)注入到所有
|
||||
* 加载的 H5 页面。H5 业务代码运行时,window.WebViewJavascriptBridge 已就绪。
|
||||
* ⚠️ 本文件的**语义**必须与原工程 msext 的 `WebViewJavascriptBridge_JS.m`
|
||||
* (marcuswestin 原版,daoqi/msext/Class/WebViewJavascriptBridge/)保持一致。
|
||||
* 传输层可以现代化(msext 用 iframe + _fetchQueue 拉取,我们用
|
||||
* WKScriptMessageHandler 直推),但**派发时机与消息字段必须逐项对齐**,
|
||||
* 否则 H5 侧同一份代码在两个壳里的执行顺序不同 → 行为不一致。
|
||||
*
|
||||
* 已对齐的 msext 语义(每一条都曾经不一致,是排查「子游戏定位拿不到」时发现的):
|
||||
*
|
||||
* 1. **异步派发**:msext `dispatchMessagesWithTimeoutSafety = true`,且
|
||||
* `disableJavscriptAlertBoxSafetyTimeout` 在整个 msext 工程里**从未被调用**,
|
||||
* 所以 Native → H5 的每一条消息都走 `setTimeout(_doDispatch)`,在**新的
|
||||
* macrotask** 上执行。早前本文件是同步派发(在 evaluateJavaScript 内直接
|
||||
* 调 handler),H5 handler 相对自身 pending 脚本/微任务的顺序与 msext 不同。
|
||||
* 2. **WVJBCallbacks 用 `setTimeout(..., 0)` flush**(msext `_callWVJBCallbacks`),
|
||||
* 不是同步 flush —— 决定 H5 的 `registerHandler` 何时生效。
|
||||
* 3. **H5 → Native 的 response 消息带 `handlerName`**(msext `_doSend({handlerName,
|
||||
* responseId, responseData})`)。
|
||||
* 4. `_disableJavascriptAlertBoxSafetyTimeout` 作为 H5 侧 handler 注册(msext 有)。
|
||||
* 5. 缺省安装 `window.onerror`(msext 在 H5 未装时兜底装一个)。
|
||||
* 6. 「收到 Native 消息但没有对应 handler」用 `console.log`(msext 原文),不是 warn。
|
||||
*
|
||||
* 协议(Native ↔ H5 双向 JSON 消息):
|
||||
* - H5 → Native:window.webkit.messageHandlers.WVJBHandler.postMessage({...})
|
||||
@@ -18,18 +36,26 @@
|
||||
;(function () {
|
||||
if (window.WebViewJavascriptBridge) { return; }
|
||||
|
||||
// msext 同款:H5 没装 onerror 时兜底装一个
|
||||
if (!window.onerror) {
|
||||
window.onerror = function (msg, url, line) {
|
||||
console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
|
||||
};
|
||||
}
|
||||
|
||||
// ── 内部状态 ─────────────────────────────────────────────
|
||||
var messageHandlers = {}; // H5 注册的 handler:name → fn(data, responseCallback)
|
||||
var responseCallbacks = {}; // 等待 Native 响应的 JS 回调:callbackId → fn(responseData)
|
||||
var nextCallbackId = 1;
|
||||
// msext `dispatchMessagesWithTimeoutSafety`:默认 true,仅在 Native 下发
|
||||
// `_disableJavascriptAlertBoxSafetyTimeout` 时置 false(msext 从不下发)。
|
||||
var dispatchMessagesWithTimeoutSafety = true;
|
||||
|
||||
// ── 对外 API ─────────────────────────────────────────────
|
||||
var bridge = window.WebViewJavascriptBridge = {
|
||||
|
||||
/**
|
||||
* H5 注册 handler,等待 Native 主动 callHandler。重名覆盖。
|
||||
* @param {string} handlerName
|
||||
* @param {function(data, responseCallback)} handler
|
||||
*/
|
||||
registerHandler: function (handlerName, handler) {
|
||||
messageHandlers[handlerName] = handler;
|
||||
@@ -37,39 +63,31 @@
|
||||
|
||||
/**
|
||||
* H5 主动调 Native handler。
|
||||
* @param {string} handlerName
|
||||
* @param {*} data 任意 JSON 可序列化值
|
||||
* @param {function(responseData)} [responseCallback]
|
||||
* msext `callHandler` 支持 2 参形式(第二个参数是函数时视为 responseCallback)。
|
||||
*/
|
||||
callHandler: function (handlerName, data, responseCallback) {
|
||||
var callbackId = null;
|
||||
if (typeof responseCallback === 'function') {
|
||||
callbackId = 'cb_' + (nextCallbackId++) + '_' + Date.now();
|
||||
responseCallbacks[callbackId] = responseCallback;
|
||||
if (arguments.length === 2 && typeof data === 'function') {
|
||||
responseCallback = data;
|
||||
data = null;
|
||||
}
|
||||
var message = { handlerName: handlerName };
|
||||
if (data !== undefined && data !== null) {
|
||||
message.data = data;
|
||||
}
|
||||
if (callbackId) {
|
||||
message.callbackId = callbackId;
|
||||
}
|
||||
_postMessageToNative(message);
|
||||
_doSend({ handlerName: handlerName, data: data }, responseCallback);
|
||||
},
|
||||
|
||||
disableJavscriptAlertBoxSafetyTimeout: function () {
|
||||
dispatchMessagesWithTimeoutSafety = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Native → H5 入口。BridgeBus.sendToJS 会注入:
|
||||
* window.WebViewJavascriptBridge._handleMessageFromObjC('<base64of JSON>')
|
||||
* base64 包装是为了避免 JSON 内容里的单引号 / 反斜杠扰乱 evaluateJavaScript 的字符串字面量。
|
||||
* base64 包装是为了避免 JSON 内容里的单引号 / 反斜杠扰乱 evaluateJavaScript
|
||||
* 的字符串字面量(msext 走的是逐个 escape,等价)。
|
||||
*
|
||||
* ⚠️ 无返回值:msext 的派发是 setTimeout 异步的,拿不到「H5 有没有跑到
|
||||
* handler」的同步结果。原生侧不要依赖它的返回值做诊断。
|
||||
*/
|
||||
_handleMessageFromObjC: function (base64String) {
|
||||
try {
|
||||
var json = _decodeBase64UTF8(base64String);
|
||||
var message = JSON.parse(json);
|
||||
_dispatchFromNative(message);
|
||||
} catch (e) {
|
||||
console.error('[WVJB] handle message from native failed:', e);
|
||||
}
|
||||
_dispatchMessageFromObjC(base64String);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,42 +97,77 @@
|
||||
try {
|
||||
window.webkit.messageHandlers.WVJBHandler.postMessage(message);
|
||||
} catch (e) {
|
||||
console.error('[WVJB] post to native failed (WVJBHandler 通道未注册?):', e);
|
||||
console.log('[WVJB] post to native failed (WVJBHandler 通道未注册?):', e);
|
||||
}
|
||||
}
|
||||
|
||||
function _dispatchFromNative(message) {
|
||||
// Native 响应 H5 早前 callHandler
|
||||
if (message.responseId) {
|
||||
var cb = responseCallbacks[message.responseId];
|
||||
if (cb) {
|
||||
delete responseCallbacks[message.responseId];
|
||||
try { cb(message.responseData); }
|
||||
catch (e) { console.error('[WVJB] response callback threw:', e); }
|
||||
function _doSend(message, responseCallback) {
|
||||
if (typeof responseCallback === 'function') {
|
||||
var callbackId = 'cb_' + (nextCallbackId++) + '_' + new Date().getTime();
|
||||
responseCallbacks[callbackId] = responseCallback;
|
||||
message['callbackId'] = callbackId;
|
||||
}
|
||||
_postMessageToNative(message);
|
||||
}
|
||||
|
||||
/// msext `_dispatchMessageFromObjC` 逐句等价:默认走 setTimeout(新 macrotask)。
|
||||
function _dispatchMessageFromObjC(base64String) {
|
||||
if (dispatchMessagesWithTimeoutSafety) {
|
||||
setTimeout(_doDispatchMessageFromObjC);
|
||||
} else {
|
||||
_doDispatchMessageFromObjC();
|
||||
}
|
||||
|
||||
function _doDispatchMessageFromObjC() {
|
||||
var message;
|
||||
try {
|
||||
message = JSON.parse(_decodeBase64UTF8(base64String));
|
||||
} catch (e) {
|
||||
console.log('[WVJB] handle message from native failed:', e);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Native 主动调用 H5 handler
|
||||
var handler = messageHandlers[message.handlerName];
|
||||
if (!handler) {
|
||||
console.warn('[WVJB] no H5 handler registered for:', message.handlerName);
|
||||
return;
|
||||
}
|
||||
var responseCallback;
|
||||
|
||||
var responseCallback;
|
||||
if (message.callbackId) {
|
||||
var responseId = message.callbackId;
|
||||
responseCallback = function (responseData) {
|
||||
if (message.responseId) {
|
||||
responseCallback = responseCallbacks[message.responseId];
|
||||
if (!responseCallback) { return; }
|
||||
responseCallback(message.responseData);
|
||||
delete responseCallbacks[message.responseId];
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.callbackId) {
|
||||
var callbackResponseId = message.callbackId;
|
||||
responseCallback = function (responseData) {
|
||||
// msext 同款:response 消息里**带 handlerName**
|
||||
_doSend({
|
||||
handlerName: message.handlerName,
|
||||
responseId: callbackResponseId,
|
||||
responseData: responseData
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var handler = messageHandlers[message.handlerName];
|
||||
if (!handler) {
|
||||
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
|
||||
// 诊断上报(不改 msext 语义,只是额外往自家通道发一条原生侧忽略的消息)
|
||||
_postMessageToNative({ diagDispatched: message.handlerName, hadHandler: false });
|
||||
} else {
|
||||
var threw = null;
|
||||
try { handler(message.data, responseCallback); }
|
||||
catch (e) { threw = String(e); }
|
||||
_postMessageToNative({
|
||||
responseId: responseId,
|
||||
responseData: responseData
|
||||
diagDispatched: message.handlerName,
|
||||
hadHandler: true,
|
||||
threw: threw
|
||||
});
|
||||
};
|
||||
// msext 不吞异常(handler 抛错会冒到 setTimeout 的全局 onerror);
|
||||
// 这里把它原样重抛,保持行为一致,只是先记录了一笔。
|
||||
if (threw !== null) { throw new Error(threw); }
|
||||
}
|
||||
}
|
||||
|
||||
try { handler(message.data, responseCallback); }
|
||||
catch (e) { console.error('[WVJB] H5 handler "' + message.handlerName + '" threw:', e); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,25 +180,26 @@
|
||||
}).join(''));
|
||||
}
|
||||
|
||||
// msext 同款:把 Native 可下发的 safety-timeout 开关注册成 H5 侧 handler
|
||||
bridge.registerHandler("_disableJavascriptAlertBoxSafetyTimeout",
|
||||
bridge.disableJavscriptAlertBoxSafetyTimeout);
|
||||
|
||||
// ── 告知原生「桥已就绪」──────────────────────────────────
|
||||
// 等价 msext 的 `__bridge_loaded__` iframe 回调 → `injectJavascriptFile` →
|
||||
// flush `startupMessageQueue`。原生在收到本消息前发出的 callHandler 必须排队,
|
||||
// 不能丢(msext 有 startupMessageQueue,早前我们是直接丢弃)。
|
||||
_postMessageToNative({ bridgeReady: true });
|
||||
|
||||
// ── marcuswestin 旧式握手兼容 ─────────────────────────────
|
||||
// 部分 H5 代码这样初始化 bridge:
|
||||
// function setupWebViewJavascriptBridge(callback) {
|
||||
// if (window.WebViewJavascriptBridge) return callback(WebViewJavascriptBridge);
|
||||
// if (window.WVJBCallbacks) return window.WVJBCallbacks.push(callback);
|
||||
// window.WVJBCallbacks = [callback];
|
||||
// /* iframe trick - WKWebView 模式不需要 */
|
||||
// }
|
||||
// setupWebViewJavascriptBridge(function (bridge) { bridge.registerHandler(...) });
|
||||
//
|
||||
// 我们在 documentStart 就注入 bridge,正常路径下 H5 拿到的 window.WebViewJavascriptBridge
|
||||
// 已存在,会立即同步 callback(bridge)。但若 H5 代码本身用了 WVJBCallbacks 队列模式
|
||||
// (Array.push 在 bridge 之前),需要在此处 flush 一次。
|
||||
if (Array.isArray(window.WVJBCallbacks)) {
|
||||
var pending = window.WVJBCallbacks;
|
||||
// msext `_callWVJBCallbacks` 用 setTimeout(..., 0),**不是同步 flush**:
|
||||
// 这决定 H5 的 registerHandler 相对页面其它顶层脚本的生效时机。
|
||||
setTimeout(function () {
|
||||
var callbacks = window.WVJBCallbacks;
|
||||
if (!callbacks) { return; }
|
||||
delete window.WVJBCallbacks;
|
||||
for (var i = 0; i < pending.length; i++) {
|
||||
try { pending[i](bridge); }
|
||||
catch (e) { console.error('[WVJB] pending WVJBCallback threw:', e); }
|
||||
for (var i = 0; i < callbacks.length; i++) {
|
||||
try { callbacks[i](bridge); }
|
||||
catch (e) { console.log('[WVJB] pending WVJBCallback threw:', e); }
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user