Universal Windows App 的 WebView 无法显示 JavaScript alert
结论
ScriptNotify 无法安全绕过 Package.appxmanifest 中的 URI 白名单,让任意银行页面调用 window.external.notify()。这是 UWP WebView 的安全边界,不应通过全局通配规则绕过。
处理支付流程时,应优先使用支付平台提供的 SDK、WebAuthenticationBroker,或由系统浏览器完成跳转。只有支付平台明确要求嵌入 WebView,并且能够确定所有相关域名时,才适合将这些 HTTPS 域名加入 ApplicationContentUriRules。
JavaScript 的 alert() 和 window.open() 需要分别处理:
- 对于白名单内的页面,可以将
alert()改写为window.external.notify()。 window.open()等弹窗应通过NewWindowRequested处理。confirm()和prompt()需要同步返回值,不能直接改写为异步的MessageDialog。
为什么必须配置 URI 白名单
网页调用:
window.external.notify("message");
意味着远程网页可以主动向应用发送数据。如果所有网页都能调用这个接口,恶意页面也可能借此触发应用暴露的逻辑。因此,通过 HTTP 或 HTTPS 加载远程内容时,UWP 要求页面来源必须列在清单的 ApplicationContentUriRules 中。
系统检查的是当前调用 window.external.notify() 的页面来源,并非只检查最初打开的支付网关地址。支付流程跳转到银行页面后,页面来源也随之改变。新来源不在清单中时,ScriptNotify 不会触发。
已知域名时的处理方式
如果支付平台可以提供完整的跳转域名清单,可以在 Package.appxmanifest 中逐一声明:
<uap:ApplicationContentUriRules>
<uap:Rule
Match="https://payment.example.com"
Type="include"
WindowsRuntimeAccess="all" />
<uap:Rule
Match="https://secure.bank.example"
Type="include"
WindowsRuntimeAccess="all" />
</uap:ApplicationContentUriRules>
白名单中应只包含确实需要与应用交互的 HTTPS 域名。不要为了覆盖所有银行而允许任意网站访问。这样会扩大远程页面与应用之间的信任范围,而且清单规则也未必接受如此宽泛的配置。
改写 alert() 的实现
确认当前页面来自受信任域名后,可以在导航完成时注入脚本:
private async void MyWebView_NavigationCompleted(
WebView sender,
WebViewNavigationCompletedEventArgs args)
{
if (!args.IsSuccess)
return;
Uri uri = sender.Source;
if (!IsTrustedHost(uri))
return;
const string script = @"
(function () {
if (window.__uwpAlertInstalled) {
return;
}
window.__uwpAlertInstalled = true;
window.alert = function (message) {
window.external.notify(String(message));
};
})();
";
try
{
await sender.InvokeScriptAsync(
"eval",
new[] { script });
}
catch (Exception)
{
// 页面可能已跳转、脚本执行被阻止,或 WebView 已被释放。
}
}
private static bool IsTrustedHost(Uri uri)
{
if (uri == null ||
!string.Equals(uri.Scheme, "https",
StringComparison.OrdinalIgnoreCase))
{
return false;
}
return string.Equals(
uri.Host,
"payment.example.com",
StringComparison.OrdinalIgnoreCase);
}
对应的 ScriptNotify 处理可以写成:
private async void MyWebView_ScriptNotify(
object sender,
NotifyEventArgs e)
{
var dialog = new Windows.UI.Popups.MessageDialog(
e.Value ?? string.Empty);
try
{
await dialog.ShowAsync();
}
catch (UnauthorizedAccessException)
{
// 同一时间已有其他 MessageDialog 正在显示。
}
}
这段代码只适用于清单允许的来源。即使脚本由应用通过 InvokeScriptAsync() 注入,window.external.notify() 的来源限制也不会自动失效。
页面每次发生完整导航时,JavaScript 上下文都会重新创建,所以通常需要再次注入脚本。支付页面还可能从 iframe 中调用 alert()。改写顶层页面的 window.alert,不会连带修改跨域 iframe 内的同名函数。
处理 window.open() 弹窗
JavaScript 打开的新窗口应通过 NewWindowRequested 处理,不需要依赖 ScriptNotify:
private void MyWebView_NewWindowRequested(
WebView sender,
WebViewNewWindowRequestedEventArgs args)
{
if (args.Uri == null)
return;
if (IsAllowedPaymentUri(args.Uri))
{
args.Handled = true;
MyWebView.Navigate(args.Uri);
}
}
如果要在另一个 WebView 中打开,可以将 args.NewWindow 指向新建的 WebView。项目能够使用哪些属性,取决于其 UWP SDK 版本,应以当前目标 SDK 中的 WebViewNewWindowRequestedEventArgs API 为准。
执行跳转前,应检查:
- URL 是否使用
https; - 主机名是否属于支付平台或银行的已知域名;
- 该跳转是否允许留在应用内;
- 未识别的 URL 是否应交给系统浏览器处理。
不要无条件在当前 WebView 中打开 args.Uri,否则支付页面可能把任意外部地址带入应用。
域名无法穷举时的推荐方案
如果支付流程可能进入数量不确定、无法维护白名单的银行网站,就不适合依赖 ScriptNotify。可以采用以下方式:
- 使用支付平台提供的 UWP SDK,或采用平台明确支持的集成方式。
- 使用
WebAuthenticationBroker完成浏览器式授权,再通过约定的回调 URI 获取结果。 - 通过系统浏览器打开支付页面,并使用应用 URI、后端查询或支付平台回调将结果返回应用。
- 只在支付网关自身的受信任域名中使用 JavaScript bridge,不读取或改写银行页面。
如果支付流程采用类似 OAuth 的重定向模型,可以这样处理:
Uri startUri = new Uri(
"https://payment.example.com/start?orderId=123");
Uri callbackUri = new Uri(
"myapp://payment-complete");
WebAuthenticationResult result =
await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
startUri,
callbackUri);
if (result.ResponseStatus ==
WebAuthenticationStatus.Success)
{
string response = result.ResponseData;
// 校验回调参数,并向服务端确认最终支付状态。
}
不能只根据网页弹窗、页面文字或客户端回调参数判断支付是否成功。最终状态应由应用后端向支付平台查询,或通过经过签名验证的支付通知确认。
注意事项
alert() 没有返回值,因此使用异步显示的 MessageDialog 替代它,通常可以满足提示需求。不过,原生 alert() 会阻塞 JavaScript 执行,window.external.notify() 则不会。如果页面依赖这种阻塞行为,改写后可能出现执行顺序变化。
也不要直接用同样的方式改写 confirm() 或 prompt():
window.confirm = function (message) {
window.external.notify(message);
};
confirm() 必须立即返回 true 或 false,prompt() 也要同步返回字符串,而 UWP 对话框是异步的。
因此,这种方案只适合处理受信任页面中的普通提示。对于无法控制的第三方银行页面,不应扩大 ScriptNotify 的权限,而应采用支付平台支持的外部浏览器、认证代理或官方 SDK 流程。
备注:内容仅供参考。
